zjk
2026-09-24 4c4b36e7337385ff15ff66a0ad487cdffaf9c86b
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/service/ProvincialAgentAppService.java
@@ -1,5 +1,12 @@
package com.tcm.training.admin.agent.service;
import cn.hutool.json.JSONUtil;
import cn.idev.excel.FastExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.write.metadata.WriteSheet;
import com.tcm.training.admin.agent.excel.ProvincialAgentExcelRow;
import com.tcm.training.admin.common.DateTimes;
import com.fasterxml.jackson.databind.JsonNode;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -16,27 +23,41 @@
import com.tcm.training.admin.common.PageResponse;
import com.tcm.training.admin.live.entity.Clinic;
import com.tcm.training.admin.live.mapper.ClinicMapper;
import com.tcm.training.admin.utils.VzanHttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.tcm.training.admin.common.enums.EnabledStatusEnum;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
@Slf4j
@Service
public class ProvincialAgentAppService {
    private final ProvincialAgentMapper agentMapper;
    private final AgentRegionMapper regionMapper;
    private final SysRegionMapper sysRegionMapper;
    private final ClinicMapper clinicMapper;
    private final VzanHttpUtil vzanHttpUtil;
    private volatile Map<String, SysRegion> regionCache;
    public ProvincialAgentAppService(ProvincialAgentMapper agentMapper, AgentRegionMapper regionMapper,
                                     SysRegionMapper sysRegionMapper, ClinicMapper clinicMapper) {
                                     SysRegionMapper sysRegionMapper, ClinicMapper clinicMapper, VzanHttpUtil vzanHttpUtil) {
        this.agentMapper = agentMapper;
        this.regionMapper = regionMapper;
        this.sysRegionMapper = sysRegionMapper;
        this.clinicMapper = clinicMapper;
        this.vzanHttpUtil = vzanHttpUtil;
    }
    /** 分页查询省代理。 */
    public PageResponse<ProvincialAgentListItemVO> page(int page, int pageSize, String regionCode,
                                                        String clinicName, String contactInfo, Integer status) {
        LambdaQueryWrapper<ProvincialAgent> query = new LambdaQueryWrapper<ProvincialAgent>()
@@ -47,8 +68,11 @@
        if (trim(regionCode) != null) {
            List<Long> ids = new ArrayList<Long>();
            for (AgentRegion region : regionMapper.selectList(new LambdaQueryWrapper<AgentRegion>()
                    .eq(AgentRegion::getRegionCode, trim(regionCode)))) ids.add(region.getAgentId());
            if (ids.isEmpty()) return emptyPage(page, pageSize);
                    .eq(AgentRegion::getRegionCode, trim(regionCode))))
                ids.add(region.getAgentId());
            if (ids.isEmpty()) {
                return emptyPage(page, pageSize);
            }
            query.in(ProvincialAgent::getId, ids);
        }
        if (trim(clinicName) != null) {
@@ -57,24 +81,76 @@
                    .like(Clinic::getName, trim(clinicName)))) {
                if (clinic.getAgentId() != null && !ids.contains(clinic.getAgentId())) ids.add(clinic.getAgentId());
            }
            if (ids.isEmpty()) return emptyPage(page, pageSize);
            if (ids.isEmpty()) {
                return emptyPage(page, pageSize);
            }
            query.in(ProvincialAgent::getId, ids);
        }
        IPage<ProvincialAgent> result = agentMapper.selectPage(new Page<ProvincialAgent>(page, pageSize), query);
        List<ProvincialAgentListItemVO> list = new ArrayList<ProvincialAgentListItemVO>();
        for (ProvincialAgent item : result.getRecords()) list.add(toList(item));
        for (ProvincialAgent item : result.getRecords()) {
            list.add(toList(item));
        }
        return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize());
    }
    private PageResponse<ProvincialAgentListItemVO> emptyPage(int page, int pageSize) {
        return PageResponse.of(new ArrayList<ProvincialAgentListItemVO>(), 0, page, pageSize);
    }
    /** 分批复用列表查询,确保导出筛选、区域名称及排序与页面一致。 */
    public void export(String regionCode, String clinicName, String contactInfo, Integer status,
                       HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String filename = URLEncoder.encode("省代用户列表", "UTF-8").replace("+", "%20");
        response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");
        final int batchSize = 200;
        long serialNo = 0;
        try (ExcelWriter writer = FastExcel.write(response.getOutputStream(), ProvincialAgentExcelRow.class).build()) {
            WriteSheet sheet = FastExcel.writerSheet("省代用户列表").build();
            for (int pageNumber = 1; ; pageNumber++) {
                PageResponse<ProvincialAgentListItemVO> result =
                        page(pageNumber, batchSize, regionCode, clinicName, contactInfo, status);
                List<ProvincialAgentExcelRow> rows = new ArrayList<>();
                for (ProvincialAgentListItemVO item : result.getList()) {
                    rows.add(toExcelRow(item, ++serialNo));
                }
                writer.write(rows, sheet);
                if (result.getList().isEmpty() || serialNo >= result.getTotal()) {
                    break;
                }
            }
        }
    }
    private ProvincialAgentExcelRow toExcelRow(ProvincialAgentListItemVO item, long serialNo) {
        ProvincialAgentExcelRow row = new ProvincialAgentExcelRow();
        row.setSerialNo(serialNo);
        row.setAgentName(exportText(item.getAgentName()));
        row.setContactInfo(exportText(item.getContactInfo()));
        row.setAddress(exportText(item.getAddress()));
        row.setRegionNames(exportText(String.join("、", item.getRegionNames())));
        row.setClinicCount(item.getClinicCount() == null ? 0L : item.getClinicCount());
        row.setStatus(Integer.valueOf(1).equals(item.getStatus()) ? "启用"
                : Integer.valueOf(2).equals(item.getStatus()) ? "注销" : "禁用");
        row.setCreatedAt(DateTimes.format(item.getCreatedAt()));
        return row;
    }
    private String exportText(String value) {
        return trim(value) == null ? "-" : value;
    }
    /** 查询省代理详情。 */
    public ProvincialAgentDetailVO detail(Long id) {
        return toDetail(require(id));
    }
    @Transactional
    @Transactional(rollbackFor = Exception.class)
    /** 创建省代理并同步区域及微赞代理信息。 */
    public ProvincialAgentDetailVO create(ProvincialAgentSaveRequest request) {
        // STEP 1:保存本地代理与负责区域;STEP 2:同步第三方代理;失败时整体回滚。
        boolean firstAgent = agentMapper.selectCount(new LambdaQueryWrapper<ProvincialAgent>()
                .eq(ProvincialAgent::getDeleted, 0)) == 0;
        ProvincialAgent agent = new ProvincialAgent();
@@ -85,10 +161,23 @@
        if (firstAgent) {
            clinicMapper.update(null, new LambdaUpdateWrapper<Clinic>().set(Clinic::getAgentId, agent.getId()));
        }
        log.info("调用微赞创建代理,入参 agentMobile={}, agentName={}, levelCode={}, parentMobile={}, storeName={}",
                request.getContactInfo(), request.getAgentName(), 2, null, null);
        try {
            JsonNode vzanResponse = vzanHttpUtil.createAgent(
                    request.getContactInfo(), request.getAgentName(), 2, null, null);
            log.info("微赞创建代理成功,出参={}", vzanResponse);
        } catch (RuntimeException ex) {
            log.error("微赞创建代理失败,本地事务将回滚,入参 agentMobile={}, agentName={}, levelCode={}, parentMobile={}, storeName={}",
                    request.getContactInfo(), request.getAgentName(), 1, null, request.getAgentName(), ex);
//            throw ex;
            throw new IllegalStateException("微赞创建代理失败", ex);
        }
        return detail(agent.getId());
    }
    @Transactional
    @Transactional(rollbackFor = Exception.class)
    /** 更新省代理及其负责区域。 */
    public ProvincialAgentDetailVO update(Long id, ProvincialAgentSaveRequest request) {
        ProvincialAgent agent = require(id);
        fill(agent, request);
@@ -98,7 +187,8 @@
        return detail(id);
    }
    @Transactional
    @Transactional(rollbackFor = Exception.class)
    /** 删除省代理。 */
    public void delete(Long id) {
        require(id);
        agentMapper.deleteById(id);
@@ -112,12 +202,13 @@
        agent.setAccountHolder(trim(request.getAccountHolder()));
        agent.setBranchName(trim(request.getBranchName()));
        agent.setContactInfo(trim(request.getContactInfo()));
        agent.setStatus(request.getStatus() == null ? 1 : request.getStatus());
        agent.setStatus(request.getStatus() == null ? EnabledStatusEnum.ENABLED.getValue() : request.getStatus());
        agent.setAddress(trim(request.getAddress()));
    }
    private void saveRegions(ProvincialAgent agent, ProvincialAgentSaveRequest request) {
        if (request.getRegions() == null) return;
        List<AgentRegion> regions = new ArrayList<AgentRegion>();
        for (ProvincialAgentSaveRequest.RegionItem item : request.getRegions()) {
            if (item == null || trim(item.getRegionCode()) == null) continue;
            RegionInfo info = resolveRegion(item.getRegionCode());
@@ -126,7 +217,10 @@
            region.setRegionCode(trim(item.getRegionCode()));
            region.setRegionName(info.name);
            region.setRegionLevel(info.path.size());
            regionMapper.insert(region);
            regions.add(region);
        }
        if (!regions.isEmpty()) {
            regionMapper.insertBatch(regions);
        }
    }
@@ -195,17 +289,39 @@
    private RegionInfo resolveRegion(String code) {
        List<String> path = new ArrayList<String>();
        List<String> names = new ArrayList<String>();
        SysRegion current = sysRegionMapper.selectByCode(code);
        SysRegion current = getRegionCache().get(code);
        while (current != null) {
            path.add(0, current.getCode());
            names.add(0, current.getName());
            current = current.getParentId() == null || current.getParentId() == 0 ? null : sysRegionMapper.selectById(current.getParentId());
            current = current.getParentId() == null || current.getParentId() == 0 ? null : getRegionById(current.getParentId());
        }
        if (path.isEmpty()) {
            path.add(code);
            names.add(code);
        }
        return new RegionInfo(path, String.join("", names));
    }
    private Map<String, SysRegion> getRegionCache() {
        Map<String, SysRegion> cache = regionCache;
        if (cache == null) {
            synchronized (this) {
                cache = regionCache;
                if (cache == null) {
                    cache = new LinkedHashMap<String, SysRegion>();
                    for (SysRegion region : sysRegionMapper.selectAllOrdered()) cache.put(region.getCode(), region);
                    regionCache = cache;
                }
            }
        }
        return cache;
    }
    private SysRegion getRegionById(Long id) {
        for (SysRegion region : getRegionCache().values()) if (id.equals(region.getId())) {
            return region;
        }
        return null;
    }
    private String trim(String value) {
@@ -223,4 +339,4 @@
            this.name = name;
        }
    }
}
}