[#]修改部门推送消息逻辑

This commit is contained in:
maimaishu
2026-01-05 17:46:31 +08:00
parent 88b280a33f
commit d54edcd88b
8 changed files with 368 additions and 24 deletions

View File

@@ -2,15 +2,17 @@ package com.zt.plat.module.system.api.esp;
import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.object.ObjectUtils;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO;
import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
import com.zt.plat.module.system.api.esp.dto.EspDto;
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import com.zt.plat.module.system.service.dept.IEspService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@RestController
@Validated
@@ -18,16 +20,26 @@ public class EspApiImpl implements EspApi {
@Resource
private IEspService deptService;
private IEspService espService;
@Override
public CommonResult<List<EspDto>> pushMsg(DeptSaveReqDTO syncReqDTO)
public CommonResult<Long> createDept(DeptSaveReqDTO createReqVO) {
DeptSaveReqVO reqVO = BeanUtils.toBean(createReqVO, DeptSaveReqVO.class);
Long deptId = espService.createDept(reqVO);
return success(deptId);
}
@Override
public CommonResult<List<DeptMsgRespDTO>> selectDepMsg(DeptSaveReqDTO syncReqDTO)
{
if(Objects.isNull(syncReqDTO) || null == syncReqDTO.getId())
{
return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
"ID不能为空");
}
return CommonResult.success(deptService.pushMsg(syncReqDTO));
return espService.selectDepMsg(syncReqDTO);
}
}

View File

@@ -74,14 +74,15 @@ public class DeptDO extends TenantBaseDO {
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 是否公司
*/
private Boolean isCompany;
/**
* 是否集团
*/
private Boolean isGroup;
/**
* 是否公司
*/
private Boolean isCompany;
/**
* 部门来源类型

View File

@@ -1,26 +1,37 @@
package com.zt.plat.module.system.service.dept;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.annotations.VisibleForTesting;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO;
import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
import com.zt.plat.module.system.api.esp.dto.EspDto;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.dept.EspMapper;
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.seata.common.result.Result;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -33,13 +44,31 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
@Validated
public class EspServiceImpl implements IEspService {
@Resource
private DeptExternalCodeService deptExternalCodeService;
@Resource
private EspMapper espMapper;
@Resource
private DeptMapper deptMapper;
@Resource
private CacheManager cacheManager;
@Resource
private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer;
private static final String ROOT_CODE_PREFIX = "ZT";
private static final String EXTERNAL_CODE_PREFIX = "CU";
private static final int CODE_SEGMENT_LENGTH = 3;
private static final int MAX_SEQUENCE = 999;
private static final int BATCH_SIZE = 1000;
private static final Comparator<DeptDO> DEPT_COMPARATOR = Comparator
.comparing(DeptDO::getSort, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(DeptDO::getId, Comparator.nullsLast(Comparator.naturalOrder()));
@Override
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#createReqVO.deptId", beforeInvocation = false)
public Long createDeptPushMsg(EspSaveRespVo createReqVO) {
@@ -103,12 +132,243 @@ public class EspServiceImpl implements IEspService {
return espMapper.selectListByDeptId(deptId);
}
@Override
public List<EspDto> pushMsg(DeptSaveReqDTO syncReqDTO) {
return BeanUtils.toBean(espMapper.selectpushMsg(syncReqDTO), EspDto.class);
@Override
@GlobalTransactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
public Long createDept(DeptSaveReqVO createReqVO) {
// 允许上级组织为空,视为顶级组织
createReqVO.setParentId(normalizeParentId(createReqVO.getParentId()));
// 创建时默认有效
createReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
// 默认部门来源:未指定时视为外部部门
if (createReqVO.getDeptSource() == null) {
createReqVO.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
}
// 校验父部门的有效性
validateParentDept(null, createReqVO.getParentId());
// 校验部门名的唯一性
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
// 生成并校验部门编码所有来源统一走生成逻辑iWork 不再豁免)
if (Boolean.TRUE.equals(createReqVO.getDelayCodeGeneration())) {
createReqVO.setCode(null);
} else {
String resolvedCode = generateDeptCode(createReqVO.getParentId(), createReqVO.getDeptSource());
validateDeptCodeUnique(null, resolvedCode);
createReqVO.setCode(resolvedCode);
}
// 插入部门
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
// 设置部门来源(前置已默认化,此处兜底)
if (dept.getDeptSource() == null) {
dept.setDeptSource(DeptSourceEnum.EXTERNAL.getSource());
}
int insert = deptMapper.insert(dept);
if (insert == 0) {
throw exception(USER_DEPT_SAVE_EXISTS);
}
// 外部编码映射
upsertExternalMappingIfPresent(dept.getId(), createReqVO);
// 发布部门创建事件
databusChangeProducer.sendDeptCreatedMessage(dept);
//推送消息
DeptSaveReqDTO deptSaveReqDTO = new DeptSaveReqDTO();
BeanUtils.copyProperties(dept,deptSaveReqDTO);
return dept.getId();
}
@Override
public CommonResult<List<DeptMsgRespDTO>> selectDepMsg(DeptSaveReqDTO syncReqDTO) {
if (syncReqDTO == null) {
return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST);
}
LambdaQueryWrapper<DeptPushMsgDO> wrapper = new LambdaQueryWrapper<>();
if(syncReqDTO.getId() != null){
wrapper.eq(DeptPushMsgDO::getId, syncReqDTO.getId());
}
List<DeptPushMsgDO> list = espMapper.selectList(wrapper);
if (CollectionUtils.isEmpty(list)){
return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND);
}
ArrayList<DeptMsgRespDTO> lists = new ArrayList<>(list.size());
for (DeptPushMsgDO deptPushMsgDO : list){
DeptMsgRespDTO deptMsgRespDTO = new DeptMsgRespDTO();
BeanUtils.copyProperties(deptPushMsgDO, deptMsgRespDTO);
lists.add(deptMsgRespDTO);
}
return CommonResult.success(lists);
}
private Long normalizeParentId(Long parentId) {
return parentId == null ? DeptDO.PARENT_ID_ROOT : parentId;
}
@VisibleForTesting
void validateParentDept(Long id, Long parentId) {
if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
return;
}
// 1. 不能设置自己为父部门
if (Objects.equals(id, parentId)) {
throw exception(DEPT_PARENT_ERROR);
}
// 2. 父部门不存在
DeptDO parentDept = deptMapper.selectById(parentId);
if (parentDept == null) {
return;
}
// 3. 递归校验父部门,如果父部门是自己的子部门,则报错,避免形成环路
if (id == null) { // id 为空,说明新增,不需要考虑环路
return;
}
for (int i = 0; i < Short.MAX_VALUE; i++) {
// 3.1 校验环路
parentId = parentDept.getParentId();
if (Objects.equals(id, parentId)) {
throw exception(DEPT_PARENT_IS_CHILD);
}
// 3.2 继续递归下一级父部门
if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
break;
}
parentDept = deptMapper.selectById(parentId);
if (parentDept == null) {
break;
}
}
}
private String generateDeptCode(Long parentId, Integer deptSource) {
Long effectiveParentId = normalizeParentId(parentId);
String prefix = resolveCodePrefix(effectiveParentId, deptSource);
int nextSequence = determineNextSequence(effectiveParentId, prefix);
assertSequenceRange(nextSequence);
return prefix + formatSequence(nextSequence);
}
@VisibleForTesting
void validateDeptNameUnique(Long id, Long parentId, String name) {
Long effectiveParentId = normalizeParentId(parentId);
if (Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT)) {
return;
}
DeptDO dept = deptMapper.selectByParentIdAndName(effectiveParentId, name);
if (dept == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的部门
if (id == null) {
throw exception(DEPT_NAME_DUPLICATE);
}
if (ObjectUtil.notEqual(dept.getId(), id)) {
throw exception(DEPT_NAME_DUPLICATE);
}
}
@VisibleForTesting
void validateDeptCodeUnique(Long id, String code) {
if (StrUtil.isBlank(code)) {
return;
}
DeptDO dept = deptMapper.selectByCode(code);
if (dept == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的部门
if (id == null) {
throw exception(DEPT_CODE_DUPLICATE);
}
if (ObjectUtil.notEqual(dept.getId(), id)) {
throw exception(DEPT_CODE_DUPLICATE);
}
}
private void upsertExternalMappingIfPresent(Long deptId, DeptSaveReqVO reqVO) {
String systemCode = StrUtil.trimToNull(reqVO.getExternalSystemCode());
String externalCode = StrUtil.trimToNull(reqVO.getExternalDeptCode());
if (StrUtil.hasEmpty(systemCode, externalCode) || deptId == null) {
return;
}
String externalName = StrUtil.trimToNull(reqVO.getExternalDeptName());
deptExternalCodeService.saveOrUpdateDeptExternalCode(deptId, systemCode, externalCode, externalName, reqVO.getStatus());
}
private String resolveCodePrefix(Long parentId, Integer deptSource) {
boolean isExternal = Objects.equals(deptSource, DeptSourceEnum.EXTERNAL.getSource());
if (DeptDO.PARENT_ID_ROOT.equals(parentId)) {
return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX;
}
DeptDO parentDept = deptMapper.selectById(parentId);
if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) {
return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX;
}
String parentCode = parentDept.getCode();
if (isExternal) {
if (parentCode.startsWith(EXTERNAL_CODE_PREFIX)) {
return parentCode;
}
if (parentCode.startsWith(ROOT_CODE_PREFIX)) {
return EXTERNAL_CODE_PREFIX + parentCode.substring(ROOT_CODE_PREFIX.length());
}
return EXTERNAL_CODE_PREFIX;
}
return parentCode;
}
private int determineNextSequence(Long parentId, String prefix) {
DeptDO lastChild = deptMapper.selectLastChildByCode(parentId, prefix);
Integer sequence = parseSequence(lastChild != null ? lastChild.getCode() : null, prefix);
if (sequence != null) {
return sequence + 1;
}
return deptMapper.selectListByParentId(parentId, null).stream()
.map(DeptDO::getCode)
.map(code -> parseSequence(code, prefix))
.filter(Objects::nonNull)
.max(Integer::compareTo)
.map(val -> val + 1)
.orElse(1);
}
private void assertSequenceRange(int sequence) {
if (sequence > MAX_SEQUENCE) {
throw exception(DEPT_CODE_OUT_OF_RANGE);
}
}
private String formatSequence(int sequence) {
return StrUtil.padPre(String.valueOf(sequence), CODE_SEGMENT_LENGTH, '0');
}
private Integer parseSequence(String code, String prefix) {
if (StrUtil.isBlank(code) || StrUtil.isBlank(prefix) || !code.startsWith(prefix)) {
return null;
}
String suffix = code.substring(prefix.length());
if (suffix.length() != CODE_SEGMENT_LENGTH || !StrUtil.isNumeric(suffix)) {
return null;
}
return Integer.parseInt(suffix);
}
private DeptPushMsgDO validateExists(Long id) {
if (id == null) {
throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS);

View File

@@ -1,13 +1,14 @@
package com.zt.plat.module.system.service.dept;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO;
import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
import com.zt.plat.module.system.api.esp.dto.EspDto;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
import java.util.List;
/**
* 部门推送消息 Service 接口
*/
@@ -47,11 +48,18 @@ public interface IEspService {
*/
List<DeptPushMsgDO> getPushMsgByDeptId(Long deptId);
/**
* 创建部门
* @param reqVO 部门信息
* @return 部门编号
*/
Long createDept(DeptSaveReqVO reqVO);
/**
* 推送部门数据到外部系统
* @param syncReqDTO 同步请求
*/
List<EspDto> pushMsg(DeptSaveReqDTO syncReqDTO);
CommonResult<List<DeptMsgRespDTO>> selectDepMsg(DeptSaveReqDTO syncReqDTO);
}