@@ -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 ) ;