0318-海宁菜单调整+北仑客群部分开发

This commit is contained in:
2026-03-18 16:39:23 +08:00
parent fedf789511
commit 5996173abd
61 changed files with 3388 additions and 462 deletions

View File

@@ -50,14 +50,14 @@ public class CustGroupController extends BaseController {
}
/**
* 获取客群详情
* 根据ID查询客群详情
*/
@ApiOperation("获取客群详情")
@Log(title = "客群管理-获取客群详情")
@ApiOperation("根据ID查询客群详情")
@Log(title = "客群管理-查询客群详情")
@GetMapping("/{id}")
public AjaxResult getCustGroup(@PathVariable Long id) {
CustGroupVO vo = custGroupService.getCustGroup(id);
return AjaxResult.success(vo);
CustGroupVO custGroup = custGroupService.getCustGroup(id);
return AjaxResult.success(custGroup);
}
/**
@@ -83,17 +83,6 @@ public class CustGroupController extends BaseController {
return AjaxResult.success(custGroupService.createCustGroupByTemplate(custGroup, file));
}
/**
* 更新客群
*/
@ApiOperation("更新客群")
@Log(title = "客群管理-更新客群", businessType = BusinessType.UPDATE)
@PostMapping("/update")
public AjaxResult updateCustGroup(@RequestBody @Valid CustGroup custGroup) {
String result = custGroupService.updateCustGroup(custGroup);
return AjaxResult.success(result);
}
/**
* 更新客群(网格导入)
*/
@@ -150,15 +139,4 @@ public class CustGroupController extends BaseController {
return AjaxResult.success(result);
}
/**
* 手动移除客群客户
*/
@ApiOperation("手动移除客群客户")
@Log(title = "客群管理-手动移除客户", businessType = BusinessType.DELETE)
@PostMapping("/removeMembers")
public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List<Long> memberIds) {
String result = custGroupService.removeMembers(groupId, memberIds);
return AjaxResult.success(result);
}
}

View File

@@ -0,0 +1,54 @@
package com.ruoyi.group.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO;
import com.ruoyi.group.domain.vo.CustGroupMemberVO;
import com.ruoyi.group.service.ICustGroupMemberService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 客群客户Controller
*
* @author ruoyi
*/
@Api(tags = "客群客户接口")
@RestController
@RequestMapping("/group/member")
public class CustGroupMemberController extends BaseController {
@Resource
private ICustGroupMemberService custGroupMemberService;
/**
* 分页查询客群客户列表
*/
@ApiOperation("分页查询客群客户列表")
@Log(title = "客群客户-查询客户列表")
@GetMapping("/list/{groupId}")
public TableDataInfo listCustGroupMembers(@PathVariable Long groupId,
CustGroupMemberQueryDTO dto) {
startPage();
List<CustGroupMemberVO> list = custGroupMemberService.listCustGroupMembers(groupId, dto);
return getDataTable(list);
}
/**
* 手动移除客群客户
*/
@ApiOperation("手动移除客群客户")
@Log(title = "客群客户-手动移除客户", businessType = BusinessType.DELETE)
@PostMapping("/remove")
public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List<Long> memberIds) {
String result = custGroupMemberService.removeMembers(groupId, memberIds);
return AjaxResult.success(result);
}
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.group.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 客群客户查询DTO
*
* @author ruoyi
*/
@Data
@ApiModel(description = "客群客户查询条件")
public class CustGroupMemberQueryDTO {
/**
* 客户类型0=个人, 1=商户, 2=企业
*/
@ApiModelProperty(value = "客户类型")
private String custType;
/**
* 客户姓名
*/
@ApiModelProperty(value = "客户姓名")
private String custName;
}

View File

@@ -59,7 +59,6 @@ public class CustGroup {
* 所属机构ID
*/
@ApiModelProperty(value = "所属机构ID", name = "deptId")
@TableField(fill = FieldFill.INSERT)
private Long deptId;
/**
@@ -74,13 +73,6 @@ public class CustGroup {
@ApiModelProperty(value = "可见部门ID列表逗号分隔", name = "shareDeptIds")
private String shareDeptIds;
/**
* 共享部门ID列表非表字段用于接收前端传参
*/
@ApiModelProperty(value = "共享部门ID列表", name = "shareDeptIdList")
@TableField(exist = false)
private List<Long> shareDeptIdList;
/**
* 客群状态0=正常, 1=已禁用
*/
@@ -109,14 +101,12 @@ public class CustGroup {
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**

View File

@@ -63,14 +63,12 @@ public class CustGroupMember {
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**

View File

@@ -65,10 +65,10 @@ public class CustGroupVO {
private Integer shareEnabled;
/**
* 可见部门ID列表
* 可见部门ID列表(逗号分隔)
*/
@ApiModelProperty(value = "可见部门ID列表", name = "shareDeptIds")
private List<Long> shareDeptIds;
@ApiModelProperty(value = "可见部门ID列表(逗号分隔)", name = "shareDeptIds")
private String shareDeptIds;
/**
* 客群状态0=正常, 1=已禁用
@@ -114,6 +114,49 @@ public class CustGroupVO {
@ApiModelProperty(value = "备注", name = "remark")
private String remark;
/**
* 有效期截止时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "有效期截止时间", name = "validTime")
private Date validTime;
/**
* 创建状态0=创建中, 1=创建成功, 2=创建失败
*/
@ApiModelProperty(value = "创建状态0=创建中, 1=创建成功, 2=创建失败", name = "createStatus")
private String createStatus;
/**
* 网格类型0=绩效网格, 1=地理网格, 2=绘制网格
*/
@ApiModelProperty(value = "网格类型", name = "gridType")
private String gridType;
/**
* 绩效业务类型retail=零售, corporate=公司
*/
@ApiModelProperty(value = "绩效业务类型", name = "cmpmBizType")
private String cmpmBizType;
/**
* 客户经理列表(逗号分隔)
*/
@ApiModelProperty(value = "客户经理列表", name = "gridUserNames")
private String gridUserNames;
/**
* 地理网格ID列表逗号分隔
*/
@ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds")
private String regionGridIds;
/**
* 绘制网格ID列表逗号分隔
*/
@ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds")
private String drawGridIds;
/**
* 客户列表
*/

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.group.domain.dto.CustGroupQueryDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.vo.CustGroupVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -13,6 +14,7 @@ import java.util.List;
*
* @author ruoyi
*/
@Mapper
public interface CustGroupMapper extends BaseMapper<CustGroup> {
/**
@@ -31,4 +33,12 @@ public interface CustGroupMapper extends BaseMapper<CustGroup> {
* @return 客群VO列表
*/
List<CustGroupVO> selectCustGroupList(@Param("dto") CustGroupQueryDTO dto);
/**
* 根据ID查询客群详情
*
* @param id 客群ID
* @return 客群VO
*/
CustGroupVO selectCustGroupById(@Param("id") Long id);
}

View File

@@ -1,21 +1,36 @@
package com.ruoyi.group.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO;
import com.ruoyi.group.domain.entity.CustGroupMember;
import com.ruoyi.group.domain.vo.CustGroupMemberVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 客群客户关联Mapper接口
*
* @author ruoyi
*/
@Mapper
public interface CustGroupMemberMapper extends BaseMapper<CustGroupMember> {
/**
* 查询客群客户数量
* 分页查询客群客户列表
*
* @param groupId 客群ID
* @return 数量
* @param dto 查询条件
* @return 客户列表
*/
Long countByGroupId(@Param("groupId") Long groupId);
List<CustGroupMemberVO> selectCustGroupMemberList(@Param("groupId") Long groupId,
@Param("dto") CustGroupMemberQueryDTO dto);
/**
* 批量插入客群客户INSERT IGNORE遇到重复键自动跳过
*
* @param memberList 客户列表
*/
void batchInsertMembers(@Param("list") List<CustGroupMember> memberList);
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.group.service;
import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO;
import com.ruoyi.group.domain.vo.CustGroupMemberVO;
import java.util.List;
/**
* 客群客户服务接口
*
* @author ruoyi
*/
public interface ICustGroupMemberService {
/**
* 分页查询客群客户列表
*
* @param groupId 客群ID
* @param dto 查询条件
* @return 客户列表
*/
List<CustGroupMemberVO> listCustGroupMembers(Long groupId, CustGroupMemberQueryDTO dto);
/**
* 手动移除客群客户
*
* @param groupId 客群ID
* @param memberIds 客户ID列表
* @return 结果
*/
String removeMembers(Long groupId, List<Long> memberIds);
}

View File

@@ -23,6 +23,14 @@ public interface ICustGroupService {
*/
List<CustGroupVO> listCustGroup(CustGroupQueryDTO dto);
/**
* 根据ID查询客群详情
*
* @param id 客群ID
* @return 客群VO
*/
CustGroupVO getCustGroup(Long id);
/**
* 异步创建客群(模板导入)
*
@@ -57,14 +65,6 @@ public interface ICustGroupService {
*/
String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file);
/**
* 更新客群
*
* @param custGroup 客群实体
* @return 结果消息
*/
String updateCustGroup(CustGroup custGroup);
/**
* 删除客群
*
@@ -73,14 +73,6 @@ public interface ICustGroupService {
*/
String deleteCustGroup(List<Long> idList);
/**
* 获取客群详情
*
* @param id 客群ID
* @return 客群VO
*/
CustGroupVO getCustGroup(Long id);
/**
* 检查客群名称是否存在
*
@@ -97,15 +89,6 @@ public interface ICustGroupService {
*/
String getCreateStatus(Long id);
/**
* 手动移除客群客户
*
* @param groupId 客群ID
* @param memberIds 客群成员ID列表
* @return 结果消息
*/
String removeMembers(Long groupId, List<Long> memberIds);
/**
* 更新动态客群(定时任务调用)
* 根据原始导入条件重新查询并更新客户列表

View File

@@ -0,0 +1,57 @@
package com.ruoyi.group.service.impl;
import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.entity.CustGroupMember;
import com.ruoyi.group.domain.vo.CustGroupMemberVO;
import com.ruoyi.group.mapper.CustGroupMapper;
import com.ruoyi.group.mapper.CustGroupMemberMapper;
import com.ruoyi.group.service.ICustGroupMemberService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
/**
* 客群客户服务实现类
*
* @author ruoyi
*/
@Service
public class CustGroupMemberServiceImpl implements ICustGroupMemberService {
@Resource
private CustGroupMemberMapper custGroupMemberMapper;
@Resource
private CustGroupMapper custGroupMapper;
@Override
public List<CustGroupMemberVO> listCustGroupMembers(Long groupId, CustGroupMemberQueryDTO dto) {
return custGroupMemberMapper.selectCustGroupMemberList(groupId, dto);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String removeMembers(Long groupId, List<Long> memberIds) {
// 检查客群是否存在
CustGroup custGroup = custGroupMapper.selectById(groupId);
if (custGroup == null) {
return "客群不存在";
}
// 删除客户关联
memberIds.forEach(memberId -> {
CustGroupMember member = custGroupMemberMapper.selectById(memberId);
if (member != null && member.getGroupId().equals(groupId)) {
// 设置手动移除标识
member.setManualRemove(1);
// 逻辑删除
custGroupMemberMapper.deleteById(memberId);
}
});
return "移除成功";
}
}

View File

@@ -2,36 +2,28 @@ package com.ruoyi.group.service.impl;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.ibs.cmpm.domain.entity.GridCmpm;
import com.ruoyi.ibs.cmpm.mapper.GridCmpmMapper;
import com.ruoyi.ibs.draw.domain.entity.DrawGridShapeRelate;
import com.ruoyi.ibs.draw.domain.entity.DrawShapeCust;
import com.ruoyi.ibs.draw.mapper.DrawGridShapeRelateMapper;
import com.ruoyi.ibs.draw.mapper.DrawShapeCustMapper;
import com.ruoyi.ibs.grid.domain.vo.CustVO;
import com.ruoyi.ibs.cmpm.domain.vo.GridCmpmVO;
import com.ruoyi.ibs.cmpm.service.GridCmpmService;
import com.ruoyi.ibs.draw.mapper.DrawGridCustUserUnbindMapper;
import com.ruoyi.ibs.grid.service.RegionGridListService;
import com.ruoyi.group.domain.dto.CustGroupMemberTemplate;
import com.ruoyi.group.domain.dto.CustGroupQueryDTO;
import com.ruoyi.group.domain.dto.GridImportDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.entity.CustGroupMember;
import com.ruoyi.group.domain.vo.CustGroupMemberVO;
import com.ruoyi.group.domain.vo.CustGroupVO;
import com.ruoyi.group.mapper.CustGroupMapper;
import com.ruoyi.group.mapper.CustGroupMemberMapper;
import com.ruoyi.group.service.ICustGroupService;
import com.ruoyi.ibs.grid.domain.entity.RegionCustUser;
import com.ruoyi.ibs.grid.domain.entity.RegionGrid;
import com.ruoyi.ibs.grid.mapper.RegionCustUserMapper;
import com.ruoyi.ibs.grid.mapper.RegionGridMapper;
import com.ruoyi.ibs.handler.DynamicTableNameHelper;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -61,31 +53,31 @@ public class CustGroupServiceImpl implements ICustGroupService {
private ExecutorService executorService;
@Resource
private GridCmpmMapper gridCmpmMapper;
@Resource
private RegionCustUserMapper regionCustUserMapper;
@Resource
private RegionGridMapper regionGridMapper;
private GridCmpmService gridCmpmService;
@Resource
private RegionGridListService regionGridListService;
@Resource
private DrawShapeCustMapper drawShapeCustMapper;
private DrawGridCustUserUnbindMapper drawGridCustUserUnbindMapper;
@Resource
private DrawGridShapeRelateMapper drawGridShapeRelateMapper;
@Resource
private SysDeptMapper sysDeptMapper;
private TransactionTemplate transactionTemplate;
@Override
public List<CustGroupVO> listCustGroup(CustGroupQueryDTO dto) {
return custGroupMapper.selectCustGroupList(dto);
}
@Override
public CustGroupVO getCustGroup(Long id) {
CustGroupVO custGroup = custGroupMapper.selectCustGroupById(id);
if (custGroup == null) {
throw new ServiceException("客群不存在");
}
return custGroup;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file) {
@@ -176,21 +168,6 @@ public class CustGroupServiceImpl implements ICustGroupService {
return String.valueOf(custGroup.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustGroup(CustGroup custGroup) {
CustGroup existGroup = custGroupMapper.selectById(custGroup.getId());
if (existGroup == null) {
throw new ServiceException("客群不存在");
}
// 检查客群名称是否存在(排除自己)
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
custGroupMapper.updateById(custGroup);
return "客群更新成功";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustGroupByGrid(GridImportDTO gridImportDTO) {
@@ -200,10 +177,17 @@ public class CustGroupServiceImpl implements ICustGroupService {
if (existGroup == null) {
throw new ServiceException("客群不存在");
}
// 检查客群是否正在创建或更新
if ("0".equals(existGroup.getCreateStatus())) {
throw new ServiceException("客群正在处理中,请稍后再试");
}
// 更新客群基本信息
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
// 检查网格条件是否发生变化
boolean gridConditionChanged = isGridConditionChanged(existGroup, gridImportDTO);
// 重新查询数据库,获取最新状态
CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId());
latestGroup.setGroupName(custGroup.getGroupName());
@@ -212,7 +196,48 @@ public class CustGroupServiceImpl implements ICustGroupService {
latestGroup.setValidTime(custGroup.getValidTime());
latestGroup.setShareEnabled(custGroup.getShareEnabled());
latestGroup.setShareDeptIds(custGroup.getShareDeptIds());
// 保存新的网格导入条件
String gridType = gridImportDTO.getGridType();
latestGroup.setGridType(gridType);
if ("0".equals(gridType)) {
latestGroup.setCmpmBizType(gridImportDTO.getCmpmBizType());
if (gridImportDTO.getUserNames() != null && !gridImportDTO.getUserNames().isEmpty()) {
latestGroup.setGridUserNames(String.join(",", gridImportDTO.getUserNames()));
} else {
latestGroup.setGridUserNames(null);
}
} else if ("1".equals(gridType)) {
if (gridImportDTO.getRegionGridIds() != null && !gridImportDTO.getRegionGridIds().isEmpty()) {
latestGroup.setRegionGridIds(gridImportDTO.getRegionGridIds().stream()
.map(String::valueOf).collect(Collectors.joining(",")));
} else {
latestGroup.setRegionGridIds(null);
}
} else if ("2".equals(gridType)) {
if (gridImportDTO.getDrawGridIds() != null && !gridImportDTO.getDrawGridIds().isEmpty()) {
latestGroup.setDrawGridIds(gridImportDTO.getDrawGridIds().stream()
.map(String::valueOf).collect(Collectors.joining(",")));
} else {
latestGroup.setDrawGridIds(null);
}
}
// 更新数据库
latestGroup.setUpdateBy(SecurityUtils.getUsername());
latestGroup.setUpdateTime(new Date());
custGroupMapper.updateById(latestGroup);
// 如果网格条件没有变化,直接返回成功
if (!gridConditionChanged) {
log.info("客群网格条件未变化跳过客户重新导入客群ID{}", custGroup.getId());
return "客群更新成功(客户列表无需变更)";
}
// 网格条件发生变化,需要重新导入客户
log.info("客群网格条件已变化开始重新导入客户客群ID{}", custGroup.getId());
// 设置更新状态为"更新中"
latestGroup.setCreateStatus("0");
custGroupMapper.updateById(latestGroup);
// 重新设置回DTO确保异步线程能获取到正确的ID和状态
gridImportDTO.setCustGroup(latestGroup);
@@ -223,6 +248,80 @@ public class CustGroupServiceImpl implements ICustGroupService {
return "客群更新中";
}
/**
* 检查网格条件是否发生变化
*/
private boolean isGridConditionChanged(CustGroup existGroup, GridImportDTO gridImportDTO) {
// 首先检查网格类型是否变化
String newGridType = gridImportDTO.getGridType();
String oldGridType = existGroup.getGridType();
// 如果旧记录没有网格类型信息,说明需要导入
if (oldGridType == null) {
return true;
}
// 网格类型变化
if (!newGridType.equals(oldGridType)) {
log.info("网格类型变化:旧={}, 新={}", oldGridType, newGridType);
return true;
}
// 根据网格类型检查具体条件
if ("0".equals(newGridType)) {
// 绩效网格:比较业务类型和客户经理列表
String oldBizType = existGroup.getCmpmBizType();
String newBizType = gridImportDTO.getCmpmBizType();
List<String> newUserNames = gridImportDTO.getUserNames();
if (!Objects.equals(oldBizType, newBizType)) {
log.info("绩效业务类型变化:旧={}, 新={}", oldBizType, newBizType);
return true;
}
// 比较客户经理列表
String oldUserNames = existGroup.getGridUserNames();
String newUserNamesStr = newUserNames == null || newUserNames.isEmpty()
? null : String.join(",", newUserNames);
if (!Objects.equals(oldUserNames, newUserNamesStr)) {
log.info("客户经理列表变化:旧={}, 新={}", oldUserNames, newUserNamesStr);
return true;
}
} else if ("1".equals(newGridType)) {
// 地理网格比较网格ID列表
String oldRegionIds = existGroup.getRegionGridIds();
List<Long> newRegionIds = gridImportDTO.getRegionGridIds();
String newRegionIdsStr = newRegionIds == null || newRegionIds.isEmpty()
? null : newRegionIds.stream().map(String::valueOf).sorted().collect(Collectors.joining(","));
// 归一化比较(排序后比较)
String oldRegionIdsSorted = oldRegionIds == null ? null :
Arrays.stream(oldRegionIds.split(",")).sorted().collect(Collectors.joining(","));
if (!Objects.equals(oldRegionIdsSorted, newRegionIdsStr)) {
log.info("地理网格ID列表变化旧={}, 新={}", oldRegionIdsSorted, newRegionIdsStr);
return true;
}
} else if ("2".equals(newGridType)) {
// 绘制网格比较网格ID列表
String oldDrawIds = existGroup.getDrawGridIds();
List<Long> newDrawIds = gridImportDTO.getDrawGridIds();
String newDrawIdsStr = newDrawIds == null || newDrawIds.isEmpty()
? null : newDrawIds.stream().map(String::valueOf).sorted().collect(Collectors.joining(","));
// 归一化比较(排序后比较)
String oldDrawIdsSorted = oldDrawIds == null ? null :
Arrays.stream(oldDrawIds.split(",")).sorted().collect(Collectors.joining(","));
if (!Objects.equals(oldDrawIdsSorted, newDrawIdsStr)) {
log.info("绘制网格ID列表变化旧={}, 新={}", oldDrawIdsSorted, newDrawIdsStr);
return true;
}
}
return false;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file) {
@@ -231,6 +330,10 @@ public class CustGroupServiceImpl implements ICustGroupService {
if (existGroup == null) {
throw new ServiceException("客群不存在");
}
// 检查客群是否正在创建或更新
if ("0".equals(existGroup.getCreateStatus())) {
throw new ServiceException("客群正在处理中,请稍后再试");
}
// 更新客群基本信息
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
@@ -243,7 +346,11 @@ public class CustGroupServiceImpl implements ICustGroupService {
latestGroup.setValidTime(custGroup.getValidTime());
latestGroup.setShareEnabled(custGroup.getShareEnabled());
latestGroup.setShareDeptIds(custGroup.getShareDeptIds());
// 设置更新状态为"更新中"
latestGroup.setCreateStatus("0");
// 更新数据库
latestGroup.setUpdateBy(SecurityUtils.getUsername());
latestGroup.setUpdateTime(new Date());
custGroupMapper.updateById(latestGroup);
// 获取当前用户部门编码(异步线程中无法获取)
String headId = SecurityUtils.getHeadId();
@@ -269,82 +376,6 @@ public class CustGroupServiceImpl implements ICustGroupService {
return "客群删除成功";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String removeMembers(Long groupId, List<Long> memberIds) {
if (memberIds == null || memberIds.isEmpty()) {
throw new ServiceException("请选择要移除的客户");
}
// 检查客群是否存在
CustGroup custGroup = custGroupMapper.selectById(groupId);
if (custGroup == null) {
throw new ServiceException("客群不存在");
}
// 标记为手动移除(软删除)
for (Long memberId : memberIds) {
CustGroupMember member = custGroupMemberMapper.selectById(memberId);
if (member != null && member.getGroupId().equals(groupId)) {
member.setManualRemove(1);
member.setDelFlag(1);
custGroupMemberMapper.updateById(member);
}
}
return "成功移除 " + memberIds.size() + " 个客户";
}
@Override
public CustGroupVO getCustGroup(Long id) {
CustGroup custGroup = custGroupMapper.selectById(id);
if (custGroup == null) {
throw new ServiceException("客群不存在");
}
CustGroupVO vo = new CustGroupVO();
vo.setId(custGroup.getId());
vo.setGroupName(custGroup.getGroupName());
vo.setGroupMode(custGroup.getGroupMode());
vo.setCreateMode(custGroup.getCreateMode());
vo.setUserName(custGroup.getUserName());
vo.setNickName(custGroup.getNickName());
vo.setDeptId(custGroup.getDeptId());
vo.setShareEnabled(custGroup.getShareEnabled());
vo.setGroupStatus(custGroup.getGroupStatus());
vo.setRemark(custGroup.getRemark());
vo.setCreateBy(custGroup.getCreateBy());
vo.setCreateTime(custGroup.getCreateTime());
vo.setUpdateBy(custGroup.getUpdateBy());
vo.setUpdateTime(custGroup.getUpdateTime());
// 处理共享部门ID列表
if (StringUtils.isNotEmpty(custGroup.getShareDeptIds())) {
List<Long> deptIds = new ArrayList<>();
for (String idStr : custGroup.getShareDeptIds().split(",")) {
if (StringUtils.isNotEmpty(idStr)) {
deptIds.add(Long.valueOf(idStr));
}
}
vo.setShareDeptIds(deptIds);
}
// 查询客户列表
LambdaQueryWrapper<CustGroupMember> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustGroupMember::getGroupId, id);
List<CustGroupMember> memberList = custGroupMemberMapper.selectList(wrapper);
List<CustGroupMemberVO> memberVOList = new ArrayList<>();
for (CustGroupMember member : memberList) {
CustGroupMemberVO memberVO = new CustGroupMemberVO();
memberVO.setId(member.getId());
memberVO.setGroupId(member.getGroupId());
memberVO.setCustType(member.getCustType());
memberVO.setCustId(member.getCustId());
memberVO.setCustName(member.getCustName());
memberVO.setCustIdc(member.getCustIdc());
memberVO.setSocialCreditCode(member.getSocialCreditCode());
memberVO.setCreateTime(member.getCreateTime());
memberVOList.add(memberVO);
}
vo.setCustList(memberVOList);
vo.setCustCount(memberVOList.size());
return vo;
}
@Override
public boolean checkGroupNameExist(String groupName) {
LambdaQueryWrapper<CustGroup> wrapper = new LambdaQueryWrapper<>();
@@ -486,14 +517,14 @@ public class CustGroupServiceImpl implements ICustGroupService {
gridImportDTO.setRegionGridIds(Arrays.stream(custGroup.getRegionGridIds().split(","))
.map(Long::valueOf).collect(Collectors.toList()));
}
newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO));
newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO, headId));
} else if ("2".equals(gridType)) {
// 绘制网格
if (StringUtils.isNotEmpty(custGroup.getDrawGridIds())) {
gridImportDTO.setDrawGridIds(Arrays.stream(custGroup.getDrawGridIds().split(","))
.map(Long::valueOf).collect(Collectors.toList()));
}
newMemberList.addAll(importFromDrawGrid(custGroup, gridImportDTO));
newMemberList.addAll(importFromDrawGrid(custGroup, gridImportDTO, headId));
}
// 计算差异
@@ -601,51 +632,71 @@ public class CustGroupServiceImpl implements ICustGroupService {
}
memberList.add(member);
}
// 批量插入
int batchSize = 1000;
int successCount = 0;
int skippedCount = 0;
int restoredCount = 0;
for (int i = 0; i < memberList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, memberList.size());
List<CustGroupMember> batchList = memberList.subList(i, endIndex);
for (CustGroupMember member : batchList) {
try {
custGroupMemberMapper.insert(member);
successCount++;
} catch (DuplicateKeyException e) {
// 客户已存在,检查是否是被手动移除的
LambdaQueryWrapper<CustGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId())
.eq(CustGroupMember::getCustId, member.getCustId())
.eq(CustGroupMember::getCustType, member.getCustType());
CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper);
if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) {
// 是被手动移除的客户,清除标记并恢复
existMember.setManualRemove(0);
existMember.setDelFlag(0);
existMember.setCustName(member.getCustName());
custGroupMemberMapper.updateById(existMember);
restoredCount++;
log.debug("恢复手动移除的客户groupId={}, custId={}", member.getGroupId(), member.getCustId());
} else {
// 正常存在的客户,跳过
skippedCount++;
log.debug("客户已存在跳过groupId={}, custId={}", member.getGroupId(), member.getCustId());
// 使用编程式事务:先删除旧客户,再插入新客户
transactionTemplate.executeWithoutResult(status -> {
// 删除该客群的所有旧客户
log.info("开始删除客群旧客户模板导入客群ID{}", custGroup.getId());
LambdaQueryWrapper<CustGroupMember> memberWrapper = new LambdaQueryWrapper<>();
memberWrapper.eq(CustGroupMember::getGroupId, custGroup.getId());
custGroupMemberMapper.delete(memberWrapper);
log.info("客群旧客户删除完成模板导入客群ID{}", custGroup.getId());
// 批量插入新客户
log.info("开始批量插入客户模板导入客群ID{},客户总数:{}", custGroup.getId(), memberList.size());
int batchSize = 1000;
int successCount = 0;
int skippedCount = 0;
int restoredCount = 0;
for (int i = 0; i < memberList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, memberList.size());
List<CustGroupMember> batchList = memberList.subList(i, endIndex);
log.info("处理批次 [{}/{}],本批大小:{}", i / batchSize + 1, (memberList.size() + batchSize - 1) / batchSize, batchList.size());
for (CustGroupMember member : batchList) {
try {
custGroupMemberMapper.insert(member);
successCount++;
} catch (DuplicateKeyException e) {
// 客户已存在,检查是否是被手动移除的
LambdaQueryWrapper<CustGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId())
.eq(CustGroupMember::getCustId, member.getCustId())
.eq(CustGroupMember::getCustType, member.getCustType());
CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper);
if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) {
// 是被手动移除的客户,清除标记并恢复
existMember.setManualRemove(0);
existMember.setDelFlag(0);
existMember.setCustName(member.getCustName());
custGroupMemberMapper.updateById(existMember);
restoredCount++;
log.debug("恢复手动移除的客户groupId={}, custId={}", member.getGroupId(), member.getCustId());
} else {
// 正常存在的客户,跳过
skippedCount++;
log.debug("客户已存在跳过groupId={}, custId={}", member.getGroupId(), member.getCustId());
}
}
}
}
}
log.info("客群客户导入完成模板客群ID{},成功:{},跳过重复:{},恢复:{}",
custGroup.getId(), successCount, skippedCount, restoredCount);
// 更新创建状态为成功
custGroup.setCreateStatus("1");
custGroupMapper.updateById(custGroup);
log.info("客群客户导入完成模板客群ID{},成功:{},跳过重复:{},恢复:{}",
custGroup.getId(), successCount, skippedCount, restoredCount);
// 更新创建状态为成功
custGroup.setCreateStatus("1");
custGroup.setUpdateBy(custGroup.getCreateBy());
custGroup.setUpdateTime(new Date());
custGroupMapper.updateById(custGroup);
});
} catch (Exception e) {
log.error("客群客户导入失败客群ID{},异常:{}", custGroup.getId(), e.getMessage(), e);
// 注意:由于删除和插入在同一事务中,插入失败会自动回滚删除操作,无需手动清理
// 更新创建状态为失败
custGroup.setCreateStatus("2");
custGroup.setUpdateBy(custGroup.getCreateBy());
custGroup.setUpdateTime(new Date());
custGroupMapper.updateById(custGroup);
log.error("客群客户导入失败客群ID{}", custGroup.getId(), e);
throw new ServiceException("客群客户导入失败: " + e.getMessage());
}
}
@@ -664,63 +715,122 @@ public class CustGroupServiceImpl implements ICustGroupService {
memberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId));
} else if ("1".equals(gridType)) {
// 地理网格
memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO));
memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO, headId));
} else if ("2".equals(gridType)) {
// 绘制网格
memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO));
memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO, headId));
}
if (memberList.isEmpty()) {
throw new ServiceException("未查询到任何客户");
}
// 批量插入
int batchSize = 1000;
int successCount = 0;
int skippedCount = 0;
int restoredCount = 0;
for (int i = 0; i < memberList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, memberList.size());
List<CustGroupMember> batchList = memberList.subList(i, endIndex);
for (CustGroupMember member : batchList) {
try {
custGroupMemberMapper.insert(member);
successCount++;
} catch (DuplicateKeyException e) {
// 客户已存在,检查是否是被手动移除的
LambdaQueryWrapper<CustGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId())
.eq(CustGroupMember::getCustId, member.getCustId())
.eq(CustGroupMember::getCustType, member.getCustType());
CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper);
if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) {
// 是被手动移除的客户,清除标记并恢复
existMember.setManualRemove(0);
existMember.setDelFlag(0);
existMember.setCustName(member.getCustName());
custGroupMemberMapper.updateById(existMember);
restoredCount++;
log.debug("恢复手动移除的客户groupId={}, custId={}", member.getGroupId(), member.getCustId());
} else {
// 正常存在的客户,跳过
skippedCount++;
log.debug("客户已存在跳过groupId={}, custId={}", member.getGroupId(), member.getCustId());
// 使用编程式事务:先删除旧客户,再插入新客户
transactionTemplate.executeWithoutResult(status -> {
// 删除该客群的所有旧客户
log.info("开始删除客群旧客户客群ID{}", custGroup.getId());
LambdaQueryWrapper<CustGroupMember> memberWrapper = new LambdaQueryWrapper<>();
memberWrapper.eq(CustGroupMember::getGroupId, custGroup.getId());
custGroupMemberMapper.delete(memberWrapper);
log.info("客群旧客户删除完成客群ID{}", custGroup.getId());
// 批量插入新客户
log.info("开始批量插入客户客群ID{},客户总数:{}", custGroup.getId(), memberList.size());
// 分批批量插入每批1000条
int batchSize = 1000;
int totalInserted = 0;
int totalRestored = 0;
int totalSkipped = 0;
for (int i = 0; i < memberList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, memberList.size());
List<CustGroupMember> batchList = memberList.subList(i, endIndex);
log.info("处理批次 [{}/{}],本批大小:{}", i / batchSize + 1,
(memberList.size() + batchSize - 1) / batchSize, batchList.size());
// SQL层面的批量插入
custGroupMemberMapper.batchInsertMembers(batchList);
// 查询本批中被手动移除的客户,需要恢复
List<CustGroupMember> toRestore = findManualRemovedToRestore(custGroup.getId(), batchList);
if (!toRestore.isEmpty()) {
// 恢复被手动移除的客户
for (CustGroupMember m : toRestore) {
batchList.stream()
.filter(b -> b.getCustId().equals(m.getCustId()) && b.getCustType().equals(m.getCustType()))
.findFirst()
.ifPresent(origin -> m.setCustName(origin.getCustName()));
m.setManualRemove(0);
custGroupMemberMapper.updateById(m);
}
log.info("本批恢复被手动移除的客户:{} 条", toRestore.size());
totalRestored += toRestore.size();
}
totalInserted += batchList.size() - toRestore.size();
totalSkipped += toRestore.size();
}
}
log.info("客群客户导入完成网格客群ID{}成功{}跳过重复:{}恢复{}",
custGroup.getId(), successCount, skippedCount, restoredCount);
// 更新创建状态为成功
custGroup.setCreateStatus("1");
custGroupMapper.updateById(custGroup);
log.info("客群客户导入完成网格客群ID{}插入{}复:{}跳过{}",
custGroup.getId(), totalInserted, totalRestored, totalSkipped);
// 更新创建状态为成功
custGroup.setCreateStatus("1");
custGroup.setUpdateBy(custGroup.getCreateBy());
custGroup.setUpdateTime(new Date());
log.info("准备更新客群状态为成功客群ID{}", custGroup.getId());
custGroupMapper.updateById(custGroup);
log.info("客群状态更新成功完成客群ID{}", custGroup.getId());
});
} catch (Exception e) {
// 先记录原始异常(必须第一时间记录,避免后续异常覆盖)
log.error("==========客群客户导入异常========== 客群ID{},异常类型:{},异常消息:{}",
custGroup.getId(), e.getClass().getName(), e.getMessage(), e);
// 注意:由于删除和插入在同一事务中,插入失败会自动回滚删除操作,无需手动清理
// 更新创建状态为失败
custGroup.setCreateStatus("2");
custGroupMapper.updateById(custGroup);
log.error("客群客户导入失败客群ID{}", custGroup.getId(), e);
try {
custGroup.setCreateStatus("2");
custGroup.setUpdateBy(custGroup.getCreateBy());
custGroup.setUpdateTime(new Date());
log.info("准备更新客群状态为失败客群ID{}", custGroup.getId());
custGroupMapper.updateById(custGroup);
log.info("客群状态更新为失败完成客群ID{}", custGroup.getId());
} catch (Exception updateException) {
log.error("==========更新客群状态为失败也异常了========== 客群ID{},异常类型:{},异常消息:{}",
custGroup.getId(), updateException.getClass().getName(), updateException.getMessage(), updateException);
}
throw new ServiceException("客群客户导入失败: " + e.getMessage());
}
}
/**
* 查找需要恢复的被手动移除的客户
*
* @param groupId 客群ID
* @param batchList 本批导入的客户列表
* @return 需要恢复的客户列表
*/
private List<CustGroupMember> findManualRemovedToRestore(Long groupId, List<CustGroupMember> batchList) {
// 构建本批客户的 (custId, custType) 集合
Set<String> batchKeys = batchList.stream()
.map(m -> m.getCustId() + "|" + m.getCustType())
.collect(Collectors.toSet());
// 查询该客群所有被手动移除的客户
LambdaQueryWrapper<CustGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustGroupMember::getGroupId, groupId)
.eq(CustGroupMember::getManualRemove, 1)
.eq(CustGroupMember::getDelFlag, 0);
List<CustGroupMember> manualRemovedList = custGroupMemberMapper.selectList(queryWrapper);
// 筛选出本批中需要恢复的
return manualRemovedList.stream()
.filter(m -> batchKeys.contains(m.getCustId() + "|" + m.getCustType()))
.collect(Collectors.toList());
}
/**
* 从绩效网格导入客户
*/
@@ -735,13 +845,13 @@ public class CustGroupServiceImpl implements ICustGroupService {
gridTypes.add("corporate");
gridTypes.add("corporate_account");
} else {
throw new ServiceException("请选择绩效网格业务类型(零售/公司");
throw new ServiceException("请选择绩效网格业务类型(零售/对公/对公账户");
}
// 查询客户
for (String userName : gridImportDTO.getUserNames()) {
for (String gridType : gridTypes) {
List<GridCmpm> cmpmList = gridCmpmMapper.getGridCmpmByUserName(userName, headId, gridType);
for (GridCmpm cmpm : cmpmList) {
List<GridCmpmVO> cmpmList = gridCmpmService.selectManageListForImport(gridType, userName, headId);
for (GridCmpmVO cmpm : cmpmList) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(cmpm.getCustId());
@@ -760,25 +870,21 @@ public class CustGroupServiceImpl implements ICustGroupService {
/**
* 从地理网格导入客户
*/
private List<CustGroupMember> importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO) {
private List<CustGroupMember> importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) {
List<CustGroupMember> memberList = new ArrayList<>();
// 查询地理网格获取编码
if (gridImportDTO.getRegionGridIds() == null || gridImportDTO.getRegionGridIds().isEmpty()) {
throw new ServiceException("请选择地理网格");
}
List<RegionGrid> regionGrids = regionGridMapper.selectBatchIds(gridImportDTO.getRegionGridIds());
// 使用 selectAllCustFromGrid 方法查询所有客户(不限制客户类型)
for (RegionGrid regionGrid : regionGrids) {
List<RegionCustUser> custUsers = regionGridListService.selectAllCustFromGrid(regionGrid);
for (RegionCustUser custUser : custUsers) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(custUser.getCustId());
member.setCustName(custUser.getCustName());
member.setCustType(custUser.getCustType());
member.setCreateTime(new Date());
memberList.add(member);
}
// 直接根据网格ID列表批量查询所有客户SQL中直接拼接headId绕过MyBatis-Plus拦截器
List<RegionCustUser> custUsers = regionGridListService.selectAllCustByGridIds(gridImportDTO.getRegionGridIds(), headId);
for (RegionCustUser custUser : custUsers) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(custUser.getCustId());
member.setCustName(custUser.getCustName());
member.setCustType(custUser.getCustType());
member.setCreateTime(new Date());
memberList.add(member);
}
return memberList;
}
@@ -786,27 +892,21 @@ public class CustGroupServiceImpl implements ICustGroupService {
/**
* 从绘制网格导入客户
*/
private List<CustGroupMember> importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO) {
private List<CustGroupMember> importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) {
List<CustGroupMember> memberList = new ArrayList<>();
if (gridImportDTO.getDrawGridIds() == null || gridImportDTO.getDrawGridIds().isEmpty()) {
throw new ServiceException("请选择绘制网格");
}
// 查询绘制网格关联的图形ID
// 使用 selectCustByDrawGridId 方法直接在SQL中拼接headId绕过拦截器
for (Long gridId : gridImportDTO.getDrawGridIds()) {
LambdaQueryWrapper<DrawGridShapeRelate> relateWrapper = new LambdaQueryWrapper<>();
relateWrapper.eq(DrawGridShapeRelate::getGridId, gridId);
List<DrawGridShapeRelate> relates = drawGridShapeRelateMapper.selectList(relateWrapper);
for (DrawGridShapeRelate relate : relates) {
// 根据图形ID查询客户
LambdaQueryWrapper<DrawShapeCust> custWrapper = new LambdaQueryWrapper<>();
custWrapper.eq(DrawShapeCust::getShapeId, relate.getShapeId());
List<DrawShapeCust> shapeCusts = drawShapeCustMapper.selectList(custWrapper);
for (DrawShapeCust shapeCust : shapeCusts) {
List<RegionCustUser> custUsers = drawGridCustUserUnbindMapper.selectCustByDrawGridId(gridId, headId);
if (custUsers != null && !custUsers.isEmpty()) {
for (RegionCustUser custUser : custUsers) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(shapeCust.getCustId());
member.setCustName(shapeCust.getCustName());
member.setCustType(shapeCust.getCustType());
member.setCustId(custUser.getCustId());
member.setCustName(custUser.getCustName());
member.setCustType(custUser.getCustType());
member.setCreateTime(new Date());
memberList.add(member);
}