From 83188a6c8ea2f123519378da736382a9136a4331 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期一, 28 九月 2026 14:47:09 +0800
Subject: [PATCH] 新增加功能和修改已有功能
---
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java | 561 ++++++++++++++++++++++++++++++++++++++++---------------
1 files changed, 408 insertions(+), 153 deletions(-)
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
index db281a9..5d32587 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
@@ -29,6 +29,9 @@
import com.ruoyi.common.core.web.domain.BasePojo;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.other.api.domain.TFleet;
+import com.ruoyi.other.api.domain.TGroupCompany;
+import com.ruoyi.other.api.feignClient.GroupCompanyClient;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.feignClient.SysUserClient;
import com.ruoyi.system.api.model.LoginUser;
@@ -36,6 +39,11 @@
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
@@ -45,6 +53,8 @@
import javax.annotation.Resource;
import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -54,6 +64,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -63,10 +74,10 @@
@Service
public class TAppUserWhitelistServiceImpl extends ServiceImpl<TAppUserWhitelistMapper, TAppUserWhitelist> implements TAppUserWhitelistService {
+ private static final Logger log = LoggerFactory.getLogger(TAppUserWhitelistServiceImpl.class);
+
private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
private static final String IMPORT_HEADER_PHONE = "用户手机号";
- private static final String IMPORT_HEADER_BENEFIT_TYPE = "享受权益";
- private static final String IMPORT_HEADER_WHITELIST_SITE = "白名单电站";
private static final int BENEFIT_ALL_FREE = 1;
private static final int BENEFIT_ELECTRIC_FREE = 2;
private static final int BENEFIT_SERVICE_FREE = 3;
@@ -74,6 +85,20 @@
private static final int SCOPE_SELECTED_SITE = 2;
private static final int SCOPE_PARTNER = 3;
private static final int STATUS_ENABLED = 1;
+ private static final int STATUS_DISABLED = 2;
+ /** 姓名最大长度(对应 varchar(64)) */
+ private static final int MAX_NAME_LENGTH = 64;
+
+ /** 公司账户模式:2=分配模式(可设置初始分配额度并扣减公司可用余额) */
+ private static final int ACCOUNT_MODE_ALLOCATION = 2;
+ /** 初始分配额度上限(对应 decimal(12,2)) */
+ private static final BigDecimal MAX_INIT_QUOTA = new BigDecimal("99999999.99");
+ /** 初始分配额度并发锁前缀:新增按手机号、编辑按ID串行,防止并发下重复扣减或退还公司余额 */
+ private static final String QUOTA_LOCK_PREFIX = "APP_USER_WHITELIST_QUOTA:";
+ /** 获取锁最长等待时间(秒) */
+ private static final long LOCK_WAIT_SECONDS = 5L;
+ /** 锁自动释放时间(秒),兜住异常未解锁的场景 */
+ private static final long LOCK_LEASE_SECONDS = 30L;
@Resource
private TAppUserWhitelistSiteService whitelistSiteService;
@@ -89,6 +114,10 @@
private SysUserClient sysUserClient;
@Resource
private PlatformTransactionManager transactionManager;
+ @Autowired
+ private GroupCompanyClient groupCompanyClient;
+ @Autowired
+ private RedissonClient redissonClient;
@Override
public Page<TAppUserWhitelist> pageList(AppUserWhitelistQueryDTO queryDTO) {
@@ -99,6 +128,10 @@
String username = getUsername();
LambdaQueryChainWrapper<TAppUserWhitelist> query = this.lambdaQuery()
.like(StringUtils.isNotBlank(queryDTO.getPhone()), TAppUserWhitelist::getPhone, queryDTO.getPhone())
+ .like(StringUtils.isNotBlank(queryDTO.getName()), TAppUserWhitelist::getName, queryDTO.getName())
+ .eq(queryDTO.getCompanyId() != null, TAppUserWhitelist::getCompanyId, queryDTO.getCompanyId())
+ .eq(queryDTO.getFleetId() != null, TAppUserWhitelist::getFleetId, queryDTO.getFleetId())
+ .eq(queryDTO.getStatus() != null, TAppUserWhitelist::getStatus, queryDTO.getStatus())
.eq(TAppUserWhitelist::getDelFlag, false);
if (currentPartnerId != null) {
if (StringUtils.isNotBlank(username)) {
@@ -171,7 +204,6 @@
throw new ServiceException("导入数据不能为空");
}
- Map<String, Site> siteCodeMap = getSiteCodeMap();
Map<String, AppUserWhitelistSaveDTO> validRows = new LinkedHashMap<>();
Map<String, Integer> validRowNumbers = new HashMap<>();
List<AppUserWhitelistImportErrorVO> errors = new ArrayList<>();
@@ -179,12 +211,12 @@
for (int i = 0; i < rows.size(); i++) {
AppUserWhitelistImportDTO row = rows.get(i);
int rowNum = titleNum + i + 2;
- List<String> rowErrors = validateImportRow(row, siteCodeMap);
+ List<String> rowErrors = validateImportRow(row);
if (!rowErrors.isEmpty()) {
errors.add(new AppUserWhitelistImportErrorVO(rowNum, normalize(row.getPhone()), String.join(";", rowErrors)));
continue;
}
- AppUserWhitelistSaveDTO dto = buildSaveDTO(row, siteCodeMap);
+ AppUserWhitelistSaveDTO dto = buildSaveDTO(row);
if (!importPhones.add(dto.getPhone())) {
errors.add(new AppUserWhitelistImportErrorVO(rowNum, dto.getPhone(), "导入文件中手机号重复"));
continue;
@@ -314,9 +346,7 @@
return false;
}
ExcelUtil<AppUserWhitelistImportDTO> util = new ExcelUtil<>(AppUserWhitelistImportDTO.class);
- return IMPORT_HEADER_PHONE.equals(normalize(String.valueOf(util.getCellValue(row, 0))))
- && IMPORT_HEADER_BENEFIT_TYPE.equals(normalize(String.valueOf(util.getCellValue(row, 1))))
- && IMPORT_HEADER_WHITELIST_SITE.equals(normalize(String.valueOf(util.getCellValue(row, 2))));
+ return IMPORT_HEADER_PHONE.equals(normalize(String.valueOf(util.getCellValue(row, 0))));
}
private void validateSaveDTO(AppUserWhitelistSaveDTO saveDTO) {
@@ -327,17 +357,36 @@
if (!isValidPhone(saveDTO.getPhone())) {
throw new ServiceException("请输入正确的用户手机号");
}
- if (!isValidBenefit(saveDTO.getBenefitType())) {
+ saveDTO.setName(normalize(saveDTO.getName()));
+ if (saveDTO.getName() != null && saveDTO.getName().isEmpty()) {
+ saveDTO.setName(null);
+ }
+ if (StringUtils.isNotBlank(saveDTO.getName()) && saveDTO.getName().length() > MAX_NAME_LENGTH) {
+ throw new ServiceException("姓名长度不能超过" + MAX_NAME_LENGTH + "个字符");
+ }
+ // 状态不传默认启用,仅允许1=启用、2=停用
+ Integer status = saveDTO.getStatus() == null ? STATUS_ENABLED : saveDTO.getStatus();
+ if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
+ throw new ServiceException("请选择正确的状态");
+ }
+ saveDTO.setStatus(status);
+ // 享受权益不传默认全部免费,传了仍按原规则校验
+ Integer benefitType = saveDTO.getBenefitType() == null ? BENEFIT_ALL_FREE : saveDTO.getBenefitType();
+ if (!isValidBenefit(benefitType)) {
throw new ServiceException("请选择正确的享受权益");
}
- if (!isValidScope(saveDTO.getScopeType())) {
+ saveDTO.setBenefitType(benefitType);
+ // 白名单电站不传默认全部电站,传了仍按原规则校验
+ Integer scopeType = saveDTO.getScopeType() == null ? SCOPE_ALL_SITE : saveDTO.getScopeType();
+ if (!isValidScope(scopeType)) {
throw new ServiceException("请选择正确的白名单电站范围");
}
- if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)
+ saveDTO.setScopeType(scopeType);
+ if (Objects.equals(scopeType, SCOPE_SELECTED_SITE)
&& (saveDTO.getSiteIds() == null || saveDTO.getSiteIds().isEmpty())) {
throw new ServiceException("请选择白名单电站");
}
- if (Objects.equals(saveDTO.getScopeType(), SCOPE_PARTNER) && saveDTO.getPartnerId() == null) {
+ if (Objects.equals(scopeType, SCOPE_PARTNER) && saveDTO.getPartnerId() == null) {
throw new ServiceException("请选择加盟商");
}
validatePartnerDataScope(saveDTO);
@@ -354,81 +403,155 @@
if (getByPhone(saveDTO.getPhone()) != null) {
throw new ServiceException("该手机号已存在白名单配置");
}
- TAppUserWhitelist whitelist = new TAppUserWhitelist();
- TAppUser appUser = appUserService.lambdaQuery()
- .eq(TAppUser::getPhone, saveDTO.getPhone())
- .last("LIMIT 1")
- .one();
- whitelist.setCreateBy(getUsername());
- whitelist.setAppUserId(appUser == null ? null : appUser.getId());
- whitelist.setPhone(saveDTO.getPhone());
- whitelist.setBenefitType(saveDTO.getBenefitType());
- whitelist.setScopeType(saveDTO.getScopeType());
- whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
- whitelist.setStatus(STATUS_ENABLED);
- whitelist.setUpdateBy(getUsername());
- whitelist.setRemark(saveDTO.getRemark());
- whitelist.setDelFlag(false);
- if (whitelist.getId() == null) {
- this.save(whitelist);
- }
-
- whitelistSiteService.deleteByWhitelistId(whitelist.getId());
- if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
- Long whitelistId = whitelist.getId();
- List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
- .filter(Objects::nonNull)
- .distinct()
- .map(siteId -> {
- TAppUserWhitelistSite site = new TAppUserWhitelistSite();
- site.setWhitelistId(whitelistId);
- site.setSiteId(siteId);
- site.setDelFlag(false);
- return site;
- }).collect(Collectors.toList());
- if (!sites.isEmpty()) {
- whitelistSiteService.saveBatch(sites);
+ // 同手机号并发新增串行化:手机号重复校验与额度扣减在同一临界区内
+ RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + "PHONE:" + saveDTO.getPhone());
+ boolean locked = tryQuotaLock(lock, "该手机号正在处理中,请稍后重试");
+ List<Runnable> compensations = new ArrayList<>();
+ try {
+ if (getByPhone(saveDTO.getPhone()) != null) {
+ throw new ServiceException("该手机号已存在白名单配置");
}
+ BigDecimal quota = normalizeQuota(saveDTO.getInitQuota());
+ Integer companyId = resolveRelationCompanyId(saveDTO);
+ if (quota.signum() > 0) {
+ requireAllocationCompany(companyId);
+ deductBalance(companyId, quota);
+ compensations.add(() -> refundQuietly(companyId, quota));
+ }
+ TAppUserWhitelist whitelist = new TAppUserWhitelist();
+ TAppUser appUser = appUserService.lambdaQuery()
+ .eq(TAppUser::getPhone, saveDTO.getPhone())
+ .last("LIMIT 1")
+ .one();
+ whitelist.setCreateBy(getUsername());
+ whitelist.setAppUserId(appUser == null ? null : appUser.getId());
+ whitelist.setPhone(saveDTO.getPhone());
+ whitelist.setName(saveDTO.getName());
+ whitelist.setBenefitType(saveDTO.getBenefitType());
+ whitelist.setScopeType(saveDTO.getScopeType());
+ whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
+ whitelist.setCompanyId(companyId);
+ whitelist.setFleetId(saveDTO.getFleetId());
+ whitelist.setInitQuota(quota.signum() > 0 ? quota : null);
+ whitelist.setStatus(saveDTO.getStatus());
+ whitelist.setUpdateBy(getUsername());
+ whitelist.setRemark(saveDTO.getRemark());
+ whitelist.setDelFlag(false);
+ if (whitelist.getId() == null) {
+ this.save(whitelist);
+ }
+
+ whitelistSiteService.deleteByWhitelistId(whitelist.getId());
+ if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
+ Long whitelistId = whitelist.getId();
+ List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
+ .filter(Objects::nonNull)
+ .distinct()
+ .map(siteId -> {
+ TAppUserWhitelistSite site = new TAppUserWhitelistSite();
+ site.setWhitelistId(whitelistId);
+ site.setSiteId(siteId);
+ site.setDelFlag(false);
+ return site;
+ }).collect(Collectors.toList());
+ if (!sites.isEmpty()) {
+ whitelistSiteService.saveBatch(sites);
+ }
+ }
+ } catch (RuntimeException ex) {
+ compensate(compensations);
+ throw ex;
+ } finally {
+ unlockQuota(lock, locked);
}
}
private void updateWhitelist(AppUserWhitelistSaveDTO saveDTO) {
- TAppUserWhitelist whitelist = this.getById(saveDTO.getId());
- if (whitelist == null) {
+ TAppUserWhitelist existing = this.getById(saveDTO.getId());
+ if (existing == null) {
throw new ServiceException("白名单用户不存在");
}
- assertPartnerVisible(whitelist);
- TAppUser appUser = appUserService.lambdaQuery()
- .eq(TAppUser::getPhone, saveDTO.getPhone())
- .last("LIMIT 1")
- .one();
- whitelist.setAppUserId(appUser == null ? null : appUser.getId());
- whitelist.setPhone(saveDTO.getPhone());
- whitelist.setBenefitType(saveDTO.getBenefitType());
- whitelist.setScopeType(saveDTO.getScopeType());
- whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
- whitelist.setStatus(STATUS_ENABLED);
- whitelist.setUpdateBy(getUsername());
- whitelist.setRemark(saveDTO.getRemark());
- whitelist.setDelFlag(false);
- this.baseMapper.updateByIdIgnoreLogic(whitelist);
-
- whitelistSiteService.deleteByWhitelistId(whitelist.getId());
- if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
- Long whitelistId = whitelist.getId();
- List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
- .filter(Objects::nonNull)
- .distinct()
- .map(siteId -> {
- TAppUserWhitelistSite site = new TAppUserWhitelistSite();
- site.setWhitelistId(whitelistId);
- site.setSiteId(siteId);
- site.setDelFlag(false);
- return site;
- }).collect(Collectors.toList());
- if (!sites.isEmpty()) {
- whitelistSiteService.saveBatch(sites);
+ assertPartnerVisible(existing);
+ // 同白名单并发编辑串行化:额度差额计算、公司余额扣减/退还必须在临界区内,防止重复扣减或重复退还
+ RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + saveDTO.getId());
+ boolean locked = tryQuotaLock(lock, "该白名单额度正在处理中,请稍后重试");
+ List<Runnable> compensations = new ArrayList<>();
+ try {
+ TAppUserWhitelist whitelist = this.getById(saveDTO.getId());
+ if (whitelist == null) {
+ throw new ServiceException("白名单用户不存在");
}
+ assertPartnerVisible(whitelist);
+ BigDecimal newQuota = normalizeQuota(saveDTO.getInitQuota());
+ Integer newCompanyId = resolveRelationCompanyId(saveDTO);
+ if (newQuota.signum() > 0) {
+ requireAllocationCompany(newCompanyId);
+ }
+ Integer oldCompanyId = whitelist.getCompanyId();
+ BigDecimal oldQuota = normalizeQuota(whitelist.getInitQuota());
+ if (Objects.equals(oldCompanyId, newCompanyId)) {
+ // 同一公司按差额调整:调增扣差额,调减退差额
+ BigDecimal diff = newQuota.subtract(oldQuota);
+ if (diff.signum() > 0 && newCompanyId != null) {
+ deductBalance(newCompanyId, diff);
+ compensations.add(() -> refundQuietly(newCompanyId, diff));
+ } else if (diff.signum() < 0 && newCompanyId != null) {
+ BigDecimal refund = diff.negate();
+ refundBalance(newCompanyId, refund);
+ compensations.add(() -> deductQuietly(newCompanyId, refund));
+ }
+ } else {
+ // 跨公司:旧公司全额退还,新公司全额扣减
+ if (oldCompanyId != null && oldQuota.signum() > 0) {
+ refundBalance(oldCompanyId, oldQuota);
+ compensations.add(() -> deductQuietly(oldCompanyId, oldQuota));
+ }
+ if (newCompanyId != null && newQuota.signum() > 0) {
+ deductBalance(newCompanyId, newQuota);
+ compensations.add(() -> refundQuietly(newCompanyId, newQuota));
+ }
+ }
+ TAppUser appUser = appUserService.lambdaQuery()
+ .eq(TAppUser::getPhone, saveDTO.getPhone())
+ .last("LIMIT 1")
+ .one();
+ whitelist.setAppUserId(appUser == null ? null : appUser.getId());
+ whitelist.setPhone(saveDTO.getPhone());
+ whitelist.setName(saveDTO.getName());
+ whitelist.setBenefitType(saveDTO.getBenefitType());
+ whitelist.setScopeType(saveDTO.getScopeType());
+ whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
+ whitelist.setCompanyId(newCompanyId);
+ whitelist.setFleetId(saveDTO.getFleetId());
+ whitelist.setInitQuota(newQuota.signum() > 0 ? newQuota : null);
+ whitelist.setStatus(saveDTO.getStatus());
+ whitelist.setUpdateBy(getUsername());
+ whitelist.setRemark(saveDTO.getRemark());
+ whitelist.setDelFlag(false);
+ this.baseMapper.updateByIdIgnoreLogic(whitelist);
+
+ whitelistSiteService.deleteByWhitelistId(whitelist.getId());
+ if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
+ Long whitelistId = whitelist.getId();
+ List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
+ .filter(Objects::nonNull)
+ .distinct()
+ .map(siteId -> {
+ TAppUserWhitelistSite site = new TAppUserWhitelistSite();
+ site.setWhitelistId(whitelistId);
+ site.setSiteId(siteId);
+ site.setDelFlag(false);
+ return site;
+ }).collect(Collectors.toList());
+ if (!sites.isEmpty()) {
+ whitelistSiteService.saveBatch(sites);
+ }
+ }
+ } catch (RuntimeException ex) {
+ compensate(compensations);
+ throw ex;
+ } finally {
+ unlockQuota(lock, locked);
}
}
@@ -442,7 +565,7 @@
return this.baseMapper.selectValidByPhone(phone);
}
- private List<String> validateImportRow(AppUserWhitelistImportDTO row, Map<String, Site> siteCodeMap) {
+ private List<String> validateImportRow(AppUserWhitelistImportDTO row) {
List<String> errors = new ArrayList<>();
if (row == null) {
errors.add("空行");
@@ -452,57 +575,17 @@
if (!isValidPhone(phone)) {
errors.add("用户手机号格式错误");
}
- Integer benefitType = parseInteger(row.getBenefitType());
- if (!isValidBenefit(benefitType)) {
- errors.add("享受权益只能填写1、2、3");
- }
- String whitelistSite = normalize(row.getWhitelistSite());
- if (StringUtils.isBlank(whitelistSite)) {
- errors.add("白名单电站不能为空");
- } else if (!"all".equalsIgnoreCase(whitelistSite)) {
- for (String code : whitelistSite.split(",")) {
- String siteCode = normalize(code);
- if (StringUtils.isBlank(siteCode)) {
- errors.add("白名单电站存在空编号");
- } else if (!siteCodeMap.containsKey(siteCode)) {
- errors.add("站点编号不存在:" + siteCode);
- }
- }
- }
return errors;
}
- private AppUserWhitelistSaveDTO buildSaveDTO(AppUserWhitelistImportDTO row, Map<String, Site> siteCodeMap) {
+ /** 导入数据统一按默认值处理:全部免费 + 全部电站 */
+ private AppUserWhitelistSaveDTO buildSaveDTO(AppUserWhitelistImportDTO row) {
AppUserWhitelistSaveDTO dto = new AppUserWhitelistSaveDTO();
dto.setPhone(normalize(row.getPhone()));
- dto.setBenefitType(parseInteger(row.getBenefitType()));
- String whitelistSite = normalize(row.getWhitelistSite());
- if ("all".equalsIgnoreCase(whitelistSite)) {
- dto.setScopeType(SCOPE_ALL_SITE);
- dto.setSiteIds(Collections.emptyList());
- } else {
- dto.setScopeType(SCOPE_SELECTED_SITE);
- List<Integer> siteIds = new ArrayList<>();
- for (String code : whitelistSite.split(",")) {
- Site site = siteCodeMap.get(normalize(code));
- if (site != null) {
- siteIds.add(site.getId());
- }
- }
- dto.setSiteIds(siteIds.stream().distinct().collect(Collectors.toList()));
- }
+ dto.setBenefitType(BENEFIT_ALL_FREE);
+ dto.setScopeType(SCOPE_ALL_SITE);
+ dto.setSiteIds(Collections.emptyList());
return dto;
- }
-
- private Map<String, Site> getSiteCodeMap() {
- List<Site> sites = getCurrentSelectableSites();
- Map<String, Site> siteCodeMap = new HashMap<>();
- for (Site site : sites) {
- if (site != null && StringUtils.isNotBlank(site.getCode())) {
- siteCodeMap.put(site.getCode(), site);
- }
- }
- return siteCodeMap;
}
private void fillDisplay(List<TAppUserWhitelist> records) {
@@ -536,6 +619,59 @@
}
record.setSiteList(sites);
record.setSiteIds(sites.stream().map(TAppUserWhitelistSite::getSiteId).collect(Collectors.toList()));
+ }
+ fillCompanyNames(records);
+ fillFleetNames(records);
+ }
+
+ /** 列表回填所属公司名称(远程失败仅记录日志,不影响列表查询) */
+ private void fillCompanyNames(List<TAppUserWhitelist> records) {
+ Map<Integer, String> nameMap = new HashMap<>();
+ for (TAppUserWhitelist record : records) {
+ Integer companyId = record.getCompanyId();
+ if (companyId == null) {
+ continue;
+ }
+ if (nameMap.containsKey(companyId)) {
+ record.setCompanyName(nameMap.get(companyId));
+ continue;
+ }
+ String companyName = null;
+ try {
+ TGroupCompany company = loadCompany(companyId);
+ companyName = company == null ? null : company.getName();
+ } catch (Exception ex) {
+ log.warn("回填公司名称失败,companyId={}", companyId, ex);
+ }
+ nameMap.put(companyId, companyName);
+ record.setCompanyName(companyName);
+ }
+ }
+
+ /** 列表回填所属车队名称(批量查询,远程失败仅记录日志,不影响列表查询) */
+ private void fillFleetNames(List<TAppUserWhitelist> records) {
+ List<Integer> fleetIds = records.stream()
+ .map(TAppUserWhitelist::getFleetId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ if (fleetIds.isEmpty()) {
+ return;
+ }
+ try {
+ R<List<TFleet>> response = groupCompanyClient.listFleetsByIds(fleetIds);
+ if (response == null || !R.isSuccess(response) || response.getData() == null) {
+ return;
+ }
+ Map<Integer, String> nameMap = new HashMap<>();
+ for (TFleet fleet : response.getData()) {
+ if (fleet != null && fleet.getId() != null) {
+ nameMap.put(fleet.getId(), fleet.getFleetName());
+ }
+ }
+ records.forEach(record -> record.setFleetName(nameMap.get(record.getFleetId())));
+ } catch (Exception ex) {
+ log.warn("回填车队名称失败,fleetIds={}", fleetIds, ex);
}
}
@@ -623,21 +759,155 @@
return Objects.equals(saveDTO.getScopeType(), SCOPE_PARTNER) ? saveDTO.getPartnerId() : null;
}
- private List<Site> getCurrentSelectableSites() {
- Integer currentPartnerId = getCurrentPartnerId();
- if (currentPartnerId == null) {
- R<List<Site>> siteResult = siteClient.getSiteAll();
- return siteResult == null || siteResult.getData() == null ? Collections.emptyList() : siteResult.getData();
+ /** 校验并回填所属公司与车队:车队必须归属所选公司;未选公司时按车队自动带出公司 */
+ private Integer resolveRelationCompanyId(AppUserWhitelistSaveDTO saveDTO) {
+ Integer companyId = saveDTO.getCompanyId();
+ if (companyId != null && loadCompany(companyId) == null) {
+ throw new ServiceException("所属公司不存在或已删除");
}
- Set<Integer> siteIds = getCurrentPartnerSiteIds(currentPartnerId);
- if (siteIds.isEmpty()) {
- return Collections.emptyList();
+ Integer fleetId = saveDTO.getFleetId();
+ if (fleetId != null) {
+ TFleet fleet = loadFleet(fleetId);
+ if (fleet == null) {
+ throw new ServiceException("所属车队不存在或已删除");
+ }
+ if (companyId == null) {
+ companyId = fleet.getCompanyId();
+ } else if (!companyId.equals(fleet.getCompanyId())) {
+ throw new ServiceException("所属车队不属于所选公司");
+ }
}
- R<List<Site>> siteResult = siteClient.getSiteByIds(new ArrayList<>(siteIds));
- List<Site> sites = siteResult == null || siteResult.getData() == null ? Collections.emptyList() : siteResult.getData();
- return sites.stream()
- .filter(site -> site != null && Objects.equals(site.getPartnerId(), currentPartnerId))
- .collect(Collectors.toList());
+ return companyId;
+ }
+
+ /** 规范化初始分配额度:null 视为 0,不允许负数、超过2位小数或超出上限 */
+ private BigDecimal normalizeQuota(BigDecimal quota) {
+ if (quota == null) {
+ return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ }
+ if (quota.signum() < 0) {
+ throw new ServiceException("初始分配额度不能小于0");
+ }
+ if (quota.stripTrailingZeros().scale() > 2) {
+ throw new ServiceException("初始分配额度最多保留2位小数");
+ }
+ BigDecimal normalized = quota.setScale(2, RoundingMode.HALF_UP);
+ if (normalized.compareTo(MAX_INIT_QUOTA) > 0) {
+ throw new ServiceException("初始分配额度不能超过" + MAX_INIT_QUOTA.toPlainString());
+ }
+ return normalized;
+ }
+
+ /** 校验公司为启用且分配模式(可设置初始分配额度) */
+ private void requireAllocationCompany(Integer companyId) {
+ if (companyId == null) {
+ throw new ServiceException("请选择所属公司后再填写初始分配额度");
+ }
+ TGroupCompany company = loadCompany(companyId);
+ if (company == null) {
+ throw new ServiceException("所属公司不存在或已删除");
+ }
+ if (!Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(company.getAccountMode())) {
+ throw new ServiceException("仅分配模式的公司可填写初始分配额度");
+ }
+ if (!Integer.valueOf(1).equals(company.getStatus())) {
+ throw new ServiceException("所属公司已停用,无法填写初始分配额度");
+ }
+ }
+
+ private TGroupCompany loadCompany(Integer companyId) {
+ R<TGroupCompany> response = groupCompanyClient.getCompanyById(companyId);
+ if (response == null || !R.isSuccess(response)) {
+ throw new ServiceException("公司信息查询失败:" + (response == null ? "服务无响应" : response.getMsg()));
+ }
+ return response.getData();
+ }
+
+ private TFleet loadFleet(Integer fleetId) {
+ R<TFleet> response = groupCompanyClient.getFleetById(fleetId);
+ if (response == null || !R.isSuccess(response)) {
+ throw new ServiceException("车队信息查询失败:" + (response == null ? "服务无响应" : response.getMsg()));
+ }
+ return response.getData();
+ }
+
+ /** 扣减公司可用余额(余额不足或服务异常时抛出业务异常) */
+ private void deductBalance(Integer companyId, BigDecimal amount) {
+ R<BigDecimal> response = groupCompanyClient.deductBalance(companyId, amount);
+ if (response == null || !R.isSuccess(response)) {
+ throw new ServiceException("公司可用余额扣减失败:" + (response == null ? "服务无响应" : response.getMsg()));
+ }
+ }
+
+ /** 退还公司可用余额(失败时抛出业务异常) */
+ private void refundBalance(Integer companyId, BigDecimal amount) {
+ R<BigDecimal> response = groupCompanyClient.refundBalance(companyId, amount);
+ if (response == null || !R.isSuccess(response)) {
+ throw new ServiceException("公司可用余额退还失败:" + (response == null ? "服务无响应" : response.getMsg()));
+ }
+ }
+
+ /** 静默退还(补偿场景使用,失败仅记录日志) */
+ private void refundQuietly(Integer companyId, BigDecimal amount) {
+ try {
+ R<BigDecimal> response = groupCompanyClient.refundBalance(companyId, amount);
+ if (response == null || !R.isSuccess(response)) {
+ log.error("公司可用余额退还失败,需人工核对,companyId={}, amount={}, msg={}",
+ companyId, amount, response == null ? "服务无响应" : response.getMsg());
+ }
+ } catch (Exception ex) {
+ log.error("公司可用余额退还异常,需人工核对,companyId={}, amount={}", companyId, amount, ex);
+ }
+ }
+
+ /** 静默扣减(补偿场景使用,失败仅记录日志) */
+ private void deductQuietly(Integer companyId, BigDecimal amount) {
+ try {
+ R<BigDecimal> response = groupCompanyClient.deductBalance(companyId, amount);
+ if (response == null || !R.isSuccess(response)) {
+ log.error("公司可用余额回滚扣减失败,需人工核对,companyId={}, amount={}, msg={}",
+ companyId, amount, response == null ? "服务无响应" : response.getMsg());
+ }
+ } catch (Exception ex) {
+ log.error("公司可用余额回滚扣减异常,需人工核对,companyId={}, amount={}", companyId, amount, ex);
+ }
+ }
+
+ /** 保存/更新失败时反向补偿已执行的余额操作(倒序执行) */
+ private void compensate(List<Runnable> compensations) {
+ for (int i = compensations.size() - 1; i >= 0; i--) {
+ try {
+ compensations.get(i).run();
+ } catch (Exception ex) {
+ log.error("公司可用余额补偿失败,需人工核对", ex);
+ }
+ }
+ }
+
+ /** 抢占并发锁,抢占失败抛出业务异常 */
+ private boolean tryQuotaLock(RLock lock, String busyMessage) {
+ try {
+ if (!lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS)) {
+ throw new ServiceException(busyMessage);
+ }
+ return true;
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ throw new ServiceException(busyMessage);
+ }
+ }
+
+ private void unlockQuota(RLock lock, boolean locked) {
+ if (!locked) {
+ return;
+ }
+ try {
+ if (lock.isHeldByCurrentThread()) {
+ lock.unlock();
+ }
+ } catch (Exception ex) {
+ log.warn("释放白名单额度并发锁失败", ex);
+ }
}
private Set<Integer> getCurrentPartnerSiteIds(Integer partnerId) {
@@ -706,21 +976,6 @@
return Objects.equals(scopeType, SCOPE_ALL_SITE)
|| Objects.equals(scopeType, SCOPE_SELECTED_SITE)
|| Objects.equals(scopeType, SCOPE_PARTNER);
- }
-
- private Integer parseInteger(String value) {
- try {
- String normalizedValue = normalize(value);
- if (StringUtils.isBlank(normalizedValue)) {
- return null;
- }
- if (normalizedValue.matches("^\\d+\\.0+$")) {
- normalizedValue = normalizedValue.substring(0, normalizedValue.indexOf("."));
- }
- return Integer.valueOf(normalizedValue);
- } catch (Exception e) {
- return null;
- }
}
private String normalize(String value) {
--
Gitblit v1.7.1