puzhibing
4 天以前 a0e03b646f2c444b0f09687d24e9995112b99017
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
@@ -3,12 +3,15 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateItemDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistExcelDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistImportDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistQueryDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistSaveDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistStatusDTO;
import com.ruoyi.account.api.model.TAppUser;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.model.TAppUserWhitelistSite;
@@ -18,6 +21,7 @@
import com.ruoyi.account.api.vo.AppUserWhitelistImportErrorVO;
import com.ruoyi.account.api.vo.AppUserWhitelistImportResultVO;
import com.ruoyi.account.mapper.TAppUserWhitelistMapper;
import com.ruoyi.account.service.PartnerAppUserAccessService;
import com.ruoyi.account.service.TAppUserService;
import com.ruoyi.account.service.TAppUserWhitelistService;
import com.ruoyi.account.service.TAppUserWhitelistSiteService;
@@ -91,6 +95,15 @@
    /** 姓名最大长度(对应 varchar(64)) */
    private static final int MAX_NAME_LENGTH = 64;
    /** 共享模式(公司账户模式 1=共享模式,可用余额取公司可用余额) */
    private static final int ACCOUNT_MODE_SHARED = 1;
    /** 账户状态:正常 */
    private static final int ACCOUNT_STATUS_NORMAL = 1;
    /** 账户状态:欠款 */
    private static final int ACCOUNT_STATUS_OVERDUE = 2;
    /** 金额小数位(元,对应 decimal(12,2)) */
    private static final int AMOUNT_SCALE = 2;
    /** 公司账户模式:2=分配模式(可设置初始分配额度并扣减公司可用余额) */
    private static final int ACCOUNT_MODE_ALLOCATION = 2;
    /** 初始分配额度上限(对应 decimal(12,2)) */
@@ -128,19 +141,35 @@
    @Resource
    private TCompanyAllocateRecordService companyAllocateRecordService;
    @Resource
    private PartnerAppUserAccessService partnerAppUserAccessService;
    @Override
    public Page<TAppUserWhitelist> pageList(AppUserWhitelistQueryDTO queryDTO) {
        if (queryDTO == null) {
            queryDTO = new AppUserWhitelistQueryDTO();
        }
        AppUserWhitelistQueryDTO safeQuery = queryDTO == null ? new AppUserWhitelistQueryDTO() : queryDTO;
        Page<TAppUserWhitelist> page = buildListQuery(safeQuery)
                .orderByDesc(BasePojo::getCreateTime)
                .page(Page.of(safeQuery.getPageCurr(), safeQuery.getPageSize()));
        fillDisplay(page.getRecords());
        return page;
    }
    /**
     * 列表与导出共用的查询条件(含合作商数据范围):条件为空则不参与过滤;
     * 合作商账号只能看到自身合作商录入或本人创建的白名单数据
     */
    private LambdaQueryChainWrapper<TAppUserWhitelist> buildListQuery(AppUserWhitelistQueryDTO queryDTO) {
        AppUserWhitelistQueryDTO safeQuery = queryDTO == null ? new AppUserWhitelistQueryDTO() : queryDTO;
        // 公司账号强制按所属公司过滤,覆盖前端传入的公司筛选
        safeQuery.setCompanyId(partnerAppUserAccessService.resolveCompanyFilter(safeQuery.getCompanyId()));
        Integer currentPartnerId = getCurrentPartnerId();
        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())
                .like(StringUtils.isNotBlank(safeQuery.getPhone()), TAppUserWhitelist::getPhone, safeQuery.getPhone())
                .like(StringUtils.isNotBlank(safeQuery.getName()), TAppUserWhitelist::getName, safeQuery.getName())
                .eq(safeQuery.getCompanyId() != null, TAppUserWhitelist::getCompanyId, safeQuery.getCompanyId())
                .eq(safeQuery.getFleetId() != null, TAppUserWhitelist::getFleetId, safeQuery.getFleetId())
                .eq(safeQuery.getStatus() != null, TAppUserWhitelist::getStatus, safeQuery.getStatus())
                .eq(TAppUserWhitelist::getDelFlag, false);
        if (currentPartnerId != null) {
            if (StringUtils.isNotBlank(username)) {
@@ -151,10 +180,79 @@
                query.eq(TAppUserWhitelist::getPartnerId, currentPartnerId);
            }
        }
        Page<TAppUserWhitelist> page = query.orderByDesc(BasePojo::getCreateTime)
                .page(Page.of(queryDTO.getPageCurr(), queryDTO.getPageSize()));
        fillDisplay(page.getRecords());
        return page;
        return query;
    }
    @Override
    public List<AppUserWhitelistExcelDTO> selectExportList(AppUserWhitelistQueryDTO queryDTO) {
        List<TAppUserWhitelist> records = buildListQuery(queryDTO)
                .orderByDesc(BasePojo::getCreateTime)
                .list();
        if (records == null || records.isEmpty()) {
            return Collections.emptyList();
        }
        Map<Integer, String> fleetNameMap = loadFleetNameMap(records.stream()
                .map(TAppUserWhitelist::getFleetId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList()));
        Map<Integer, TGroupCompany> companyMap = loadCompanyMap(records.stream()
                .map(TAppUserWhitelist::getCompanyId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList()));
        List<AppUserWhitelistExcelDTO> items = new ArrayList<>(records.size());
        for (TAppUserWhitelist record : records) {
            TGroupCompany company = companyMap.get(record.getCompanyId());
            Integer accountMode = company == null ? null : company.getAccountMode();
            BigDecimal balance = resolveAvailableBalance(accountMode, record.getBalance(),
                    company == null ? null : company.getAvailableBalance());
            AppUserWhitelistExcelDTO item = new AppUserWhitelistExcelDTO();
            item.setPhone(record.getPhone());
            item.setName(record.getName());
            item.setCompanyName(company == null ? null : company.getName());
            item.setFleetName(fleetNameMap.get(record.getFleetId()));
            item.setAccountMode(accountMode);
            item.setAvailableBalance(balance);
            item.setOverrun(resolveOverrun(balance));
            item.setAccountStatus(resolveAccountStatus(balance));
            item.setStatus(record.getStatus());
            item.setCompanyId(record.getCompanyId());
            item.setFleetId(record.getFleetId());
            item.setBalance(record.getBalance());
            items.add(item);
        }
        return items;
    }
    @Override
    public boolean updateStatus(AppUserWhitelistStatusDTO statusDTO) {
        if (statusDTO == null || statusDTO.getId() == null
                || (!Objects.equals(statusDTO.getStatus(), STATUS_ENABLED)
                && !Objects.equals(statusDTO.getStatus(), STATUS_DISABLED))) {
            throw new ServiceException("白名单用户状态参数错误");
        }
        TAppUserWhitelist existing = this.getById(statusDTO.getId());
        if (existing == null) {
            throw new ServiceException("白名单用户不存在或已删除");
        }
        assertPartnerVisible(existing);
        // 公司账号越权校验:只能操作本公司数据
        partnerAppUserAccessService.assertCompanyVisible(existing.getCompanyId());
        if (Objects.equals(existing.getStatus(), statusDTO.getStatus())) {
            // 状态未变化视为幂等成功,避免无意义的更新人与更新时间变更
            return true;
        }
        LambdaUpdateChainWrapper<TAppUserWhitelist> updateWrapper = lambdaUpdate()
                .eq(TAppUserWhitelist::getId, statusDTO.getId())
                .set(TAppUserWhitelist::getStatus, statusDTO.getStatus())
                .setSql("update_time = now()");
        String username = getUsername();
        if (StringUtils.isNotBlank(username)) {
            updateWrapper.set(TAppUserWhitelist::getUpdateBy, username);
        }
        if (!updateWrapper.update()) {
            throw new ServiceException("白名单用户状态更新失败,请重试");
        }
        return true;
    }
    @Override
@@ -164,6 +262,8 @@
            throw new ServiceException("白名单用户不存在");
        }
        assertPartnerVisible(whitelist);
        // 公司账号越权校验:只能查看本公司数据
        partnerAppUserAccessService.assertCompanyVisible(whitelist.getCompanyId());
        fillDisplay(Collections.singletonList(whitelist));
        return whitelist;
    }
@@ -195,6 +295,8 @@
                continue;
            }
            assertPartnerVisible(whitelist);
            // 公司账号越权校验:只能删除本公司数据
            partnerAppUserAccessService.assertCompanyVisible(whitelist.getCompanyId());
            this.removeById(whitelistId);
            whitelistSiteService.remove(Wrappers.lambdaQuery(TAppUserWhitelistSite.class)
                    .eq(TAppUserWhitelistSite::getWhitelistId, whitelistId));
@@ -221,6 +323,8 @@
        }
        for (TAppUserWhitelist record : records) {
            assertPartnerVisible(record);
            // 公司账号越权校验:只能解绑本公司数据
            partnerAppUserAccessService.assertCompanyVisible(record.getCompanyId());
        }
        List<Long> boundIds = records.stream()
                .filter(record -> record.getFleetId() != null)
@@ -433,6 +537,8 @@
    @Override
    public AppUserWhitelistAllocateVO getAllocateOverview(Integer companyId) {
        // 公司账号只能查看本公司额度概览,覆盖前端传入的公司
        companyId = partnerAppUserAccessService.resolveCompanyFilter(companyId);
        if (companyId == null) {
            throw new ServiceException("请选择公司");
        }
@@ -446,6 +552,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppUserWhitelistAllocateVO allocateQuota(AppUserWhitelistAllocateDTO allocateDTO) {
        // 公司账号只能给本公司分配额度,覆盖前端传入的公司
        allocateDTO.setCompanyId(partnerAppUserAccessService.resolveCompanyFilter(allocateDTO.getCompanyId()));
        Map<Long, BigDecimal> amounts = validateAllocateRequest(allocateDTO);
        Integer companyId = allocateDTO.getCompanyId();
        // 公司必须存在、启用且为分配模式(共享模式不支持额度分配)
@@ -592,6 +700,13 @@
        if (Objects.equals(scopeType, SCOPE_PARTNER) && saveDTO.getPartnerId() == null) {
            throw new ServiceException("请选择加盟商");
        }
        // 所属公司与所属车队必填:白名单用户归属企业,充电完全按企业优惠结算
        if (saveDTO.getCompanyId() == null) {
            throw new ServiceException("请选择所属公司");
        }
        if (saveDTO.getFleetId() == null) {
            throw new ServiceException("请选择所属车队");
        }
        validatePartnerDataScope(saveDTO);
        TAppUserWhitelist exists = getByPhone(saveDTO.getPhone());
        if (saveDTO.getId() == null && exists != null) {
@@ -676,6 +791,8 @@
            throw new ServiceException("白名单用户不存在");
        }
        assertPartnerVisible(existing);
        // 公司账号越权校验:只能编辑本公司数据
        partnerAppUserAccessService.assertCompanyVisible(existing.getCompanyId());
        // 同白名单并发编辑串行化:额度差额计算、公司余额扣减/退还必须在临界区内,防止重复扣减或重复退还
        RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + saveDTO.getId());
        boolean locked = tryQuotaLock(lock, "该白名单额度正在处理中,请稍后重试");
@@ -847,48 +964,102 @@
            record.setSiteList(sites);
            record.setSiteIds(sites.stream().map(TAppUserWhitelistSite::getSiteId).collect(Collectors.toList()));
        }
        fillCompanyNames(records);
        fillCompanyInfo(records);
        fillFleetNames(records);
    }
    /** 列表回填所属公司名称(远程失败仅记录日志,不影响列表查询) */
    private void fillCompanyNames(List<TAppUserWhitelist> records) {
        Map<Integer, String> nameMap = new HashMap<>();
    /**
     * 列表回填所属公司名称与账户信息(账户模式、可用余额、超额、账户状态)。
     * 按公司ID去重后一次批量远程查询,远程失败仅记录日志并留空,不影响列表查询
     */
    private void fillCompanyInfo(List<TAppUserWhitelist> records) {
        Map<Integer, TGroupCompany> companyMap = loadCompanyMap(records.stream()
                .map(TAppUserWhitelist::getCompanyId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList()));
        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);
            TGroupCompany company = companyMap.get(record.getCompanyId());
            Integer accountMode = company == null ? null : company.getAccountMode();
            BigDecimal balance = resolveAvailableBalance(accountMode, record.getBalance(),
                    company == null ? null : company.getAvailableBalance());
            record.setCompanyName(company == null ? null : company.getName());
            record.setAccountMode(accountMode);
            record.setAvailableBalance(balance);
            record.setOverrun(resolveOverrun(balance));
            record.setAccountStatus(resolveAccountStatus(balance));
        }
    }
    /** 按ID批量加载公司(远程失败或服务降级时返回空Map,由调用方按未知处理) */
    private Map<Integer, TGroupCompany> loadCompanyMap(List<Integer> companyIds) {
        List<Integer> distinctIds = companyIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (distinctIds.isEmpty()) {
            return new HashMap<>();
        }
        try {
            R<List<TGroupCompany>> response = groupCompanyClient.listCompaniesByIds(distinctIds);
            if (response == null || !R.isSuccess(response) || response.getData() == null) {
                return new HashMap<>();
            }
            Map<Integer, TGroupCompany> companyMap = new HashMap<>();
            for (TGroupCompany company : response.getData()) {
                if (company != null && company.getId() != null) {
                    companyMap.put(company.getId(), company);
                }
            }
            return companyMap;
        } catch (Exception ex) {
            log.warn("回填公司账户信息失败,companyIds={}", distinctIds, ex);
            return new HashMap<>();
        }
    }
    /** 计算可用余额:分配模式=白名单用户自身余额,共享模式=公司可用余额,账户模式未知时留空 */
    private BigDecimal resolveAvailableBalance(Integer accountMode, BigDecimal whitelistBalance, BigDecimal companyBalance) {
        if (Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(accountMode)) {
            return whitelistBalance;
        }
        if (Integer.valueOf(ACCOUNT_MODE_SHARED).equals(accountMode)) {
            return companyBalance;
        }
        return null;
    }
    /** 超额:可用余额为负时取其绝对值,其余情况为空 */
    private BigDecimal resolveOverrun(BigDecimal balance) {
        if (balance == null || balance.signum() >= 0) {
            return null;
        }
        return balance.abs().setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
    }
    /** 账户状态:可用余额为负=欠款,非负=正常,可用余额未知时为空 */
    private Integer resolveAccountStatus(BigDecimal balance) {
        if (balance == null) {
            return null;
        }
        return balance.signum() < 0 ? ACCOUNT_STATUS_OVERDUE : ACCOUNT_STATUS_NORMAL;
    }
    /** 列表回填所属车队名称(批量查询,远程失败仅记录日志,不影响列表查询) */
    private void fillFleetNames(List<TAppUserWhitelist> records) {
        List<Integer> fleetIds = records.stream()
        Map<Integer, String> nameMap = loadFleetNameMap(records.stream()
                .map(TAppUserWhitelist::getFleetId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (fleetIds.isEmpty()) {
            return;
                .collect(Collectors.toList()));
        records.forEach(record -> record.setFleetName(nameMap.get(record.getFleetId())));
    }
    /** 按ID批量加载车队名称(远程失败返回空Map,由调用方按未知处理) */
    private Map<Integer, String> loadFleetNameMap(List<Integer> fleetIds) {
        List<Integer> distinctIds = fleetIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (distinctIds.isEmpty()) {
            return new HashMap<>();
        }
        try {
            R<List<TFleet>> response = groupCompanyClient.listFleetsByIds(fleetIds);
            R<List<TFleet>> response = groupCompanyClient.listFleetsByIds(distinctIds);
            if (response == null || !R.isSuccess(response) || response.getData() == null) {
                return;
                return new HashMap<>();
            }
            Map<Integer, String> nameMap = new HashMap<>();
            for (TFleet fleet : response.getData()) {
@@ -896,9 +1067,10 @@
                    nameMap.put(fleet.getId(), fleet.getFleetName());
                }
            }
            records.forEach(record -> record.setFleetName(nameMap.get(record.getFleetId())));
            return nameMap;
        } catch (Exception ex) {
            log.warn("回填车队名称失败,fleetIds={}", fleetIds, ex);
            log.warn("回填车队名称失败,fleetIds={}", distinctIds, ex);
            return new HashMap<>();
        }
    }
@@ -988,6 +1160,8 @@
    /** 校验并回填所属公司与车队:车队必须归属所选公司;未选公司时按车队自动带出公司 */
    private Integer resolveRelationCompanyId(AppUserWhitelistSaveDTO saveDTO) {
        // 公司账号新增/编辑白名单用户时强制归属本公司,覆盖前端传入的公司
        saveDTO.setCompanyId(partnerAppUserAccessService.resolveCompanyFilter(saveDTO.getCompanyId()));
        Integer companyId = saveDTO.getCompanyId();
        if (companyId != null && loadCompany(companyId) == null) {
            throw new ServiceException("所属公司不存在或已删除");