1. 新增外部系统编码部门编码关联管理
2. 新增统一的 api 对外门户管理 3. 修正各个模块的 api 命名
This commit is contained in:
@@ -55,6 +55,15 @@ public class DeptController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("init-codes")
|
||||
@Operation(summary = "初始化部门编码", description = "按照层级自动为全部部门重新生成编码")
|
||||
@PreAuthorize("@ss.hasPermission('system:dept:init-code')")
|
||||
@TenantIgnore
|
||||
public CommonResult<Boolean> initializeDeptCodes() {
|
||||
deptService.initializeDeptCodes();
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("delete")
|
||||
@Operation(summary = "删除部门")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
|
||||
@@ -19,7 +19,7 @@ public class DeptSaveReqVO {
|
||||
@Schema(description = "部门编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "部门编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "DEPT_001")
|
||||
@Schema(description = "部门编码", example = "ZT001001")
|
||||
@Size(max = 50, message = "部门编码长度不能超过 50 个字符")
|
||||
private String code;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.zt.plat.module.system.controller.admin.sync;
|
||||
|
||||
import com.zt.plat.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import com.zt.plat.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
import com.zt.plat.framework.security.core.LoginUser;
|
||||
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
||||
@@ -558,7 +559,7 @@ public class SyncController {
|
||||
syncLogService.logDecryptResult(logId, bimRequestId, bodyJson, authUser, true);
|
||||
|
||||
// 签名验证
|
||||
boolean signatureValid = SyncVerifyUtil.verifySignature(map, "MD5");
|
||||
boolean signatureValid = CryptoSignatureUtils.verifySignature(map, CryptoSignatureUtils.SIGNATURE_TYPE_MD5);
|
||||
syncLogService.logSignatureVerifyResult(logId, signatureValid);
|
||||
if (!signatureValid) {
|
||||
throw exception(SYNC_SIGNATURE_VERIFY_FAILED);
|
||||
@@ -608,8 +609,8 @@ public class SyncController {
|
||||
String bodyJson;
|
||||
String jsonString = JSON.toJSONString(object);
|
||||
try {
|
||||
bodyJson = SyncVerifyUtil.encrypt(jsonString, encryptKey, "AES");
|
||||
} catch (Exception e) {
|
||||
bodyJson = CryptoSignatureUtils.encrypt(jsonString, encryptKey, CryptoSignatureUtils.ENCRYPT_TYPE_AES);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw exception(SYNC_DECRYPT_TYPE);
|
||||
}
|
||||
return bodyJson;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -88,6 +89,20 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定父部门下编码最大的子部门
|
||||
*
|
||||
* @param parentId 父部门ID
|
||||
* @return 编码最大的子部门
|
||||
*/
|
||||
default DeptDO selectLastChildByCode(Long parentId) {
|
||||
return selectOne(new LambdaQueryWrapper<DeptDO>()
|
||||
.eq(DeptDO::getParentId, parentId)
|
||||
.isNotNull(DeptDO::getCode)
|
||||
.orderByDesc(DeptDO::getCode)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门编码查询部门
|
||||
*
|
||||
|
||||
@@ -152,4 +152,9 @@ public interface DeptService {
|
||||
* @return 公司列表
|
||||
*/
|
||||
List<DeptDO> getAllCompanyList();
|
||||
|
||||
/**
|
||||
* 按照新的编码规则初始化全部部门编码
|
||||
*/
|
||||
void initializeDeptCodes();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||
import com.zt.plat.framework.common.pojo.CompanyDeptInfo;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
import com.zt.plat.framework.datapermission.core.annotation.DataPermission;
|
||||
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||
@@ -21,9 +22,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@@ -47,6 +50,13 @@ public class DeptServiceImpl implements DeptService {
|
||||
@Resource
|
||||
private UserDeptMapper userDeptMapper;
|
||||
|
||||
private static final String ROOT_CODE_PREFIX = "ZT";
|
||||
private static final int CODE_SEGMENT_LENGTH = 3;
|
||||
private static final int MAX_SEQUENCE = 999;
|
||||
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_CHILDREN_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
||||
@@ -60,8 +70,10 @@ public class DeptServiceImpl implements DeptService {
|
||||
validateParentDept(null, createReqVO.getParentId());
|
||||
// 校验部门名的唯一性
|
||||
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
|
||||
// 校验部门编码的唯一性
|
||||
validateDeptCodeUnique(null, createReqVO.getCode());
|
||||
// 生成并校验部门编码
|
||||
String generatedCode = generateDeptCode(createReqVO.getParentId());
|
||||
createReqVO.setCode(generatedCode);
|
||||
validateDeptCodeUnique(null, generatedCode);
|
||||
|
||||
// 插入部门
|
||||
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
|
||||
@@ -82,15 +94,29 @@ public class DeptServiceImpl implements DeptService {
|
||||
updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||
}
|
||||
// 校验自己存在
|
||||
validateDeptExists(updateReqVO.getId());
|
||||
DeptDO originalDept = getRequiredDept(updateReqVO.getId());
|
||||
// 校验父部门的有效性
|
||||
validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());
|
||||
// 校验部门名的唯一性
|
||||
validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
|
||||
// 如果上级发生变化,需要重新生成编码并同步子级
|
||||
Long newParentId = normalizeParentId(updateReqVO.getParentId());
|
||||
Long oldParentId = normalizeParentId(originalDept.getParentId());
|
||||
boolean parentChanged = !Objects.equals(newParentId, oldParentId);
|
||||
if (parentChanged) {
|
||||
String newCode = generateDeptCode(updateReqVO.getParentId());
|
||||
updateReqVO.setCode(newCode);
|
||||
} else {
|
||||
updateReqVO.setCode(originalDept.getCode());
|
||||
}
|
||||
|
||||
// 更新部门
|
||||
DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);
|
||||
deptMapper.updateById(updateObj);
|
||||
|
||||
if (parentChanged) {
|
||||
refreshChildCodesRecursively(updateObj.getId(), updateReqVO.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,10 +138,18 @@ public class DeptServiceImpl implements DeptService {
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
getRequiredDept(id);
|
||||
}
|
||||
|
||||
private DeptDO getRequiredDept(Long id) {
|
||||
if (id == null) {
|
||||
throw exception(DEPT_NOT_FOUND);
|
||||
}
|
||||
DeptDO dept = deptMapper.selectById(id);
|
||||
if (dept == null) {
|
||||
throw exception(DEPT_NOT_FOUND);
|
||||
}
|
||||
return dept;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -186,6 +220,98 @@ public class DeptServiceImpl implements DeptService {
|
||||
}
|
||||
}
|
||||
|
||||
private String generateDeptCode(Long parentId) {
|
||||
Long effectiveParentId = normalizeParentId(parentId);
|
||||
String prefix = ROOT_CODE_PREFIX;
|
||||
if (!DeptDO.PARENT_ID_ROOT.equals(effectiveParentId)) {
|
||||
DeptDO parentDept = deptMapper.selectById(effectiveParentId);
|
||||
if (parentDept == null) {
|
||||
throw exception(DEPT_PARENT_NOT_EXITS);
|
||||
}
|
||||
if (StrUtil.isBlank(parentDept.getCode())) {
|
||||
throw exception(DEPT_PARENT_CODE_NOT_INITIALIZED);
|
||||
}
|
||||
prefix = parentDept.getCode();
|
||||
}
|
||||
|
||||
int nextSequence = determineNextSequence(effectiveParentId, prefix);
|
||||
assertSequenceRange(nextSequence);
|
||||
return prefix + formatSequence(nextSequence);
|
||||
}
|
||||
|
||||
private int determineNextSequence(Long parentId, String prefix) {
|
||||
DeptDO lastChild = deptMapper.selectLastChildByCode(parentId);
|
||||
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 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 void refreshChildCodesRecursively(Long parentId, String parentCode) {
|
||||
rebuildCodes(parentId, parentCode, id -> deptMapper.selectListByParentId(id, null));
|
||||
}
|
||||
|
||||
private void rebuildCodes(Long parentId, String parentCode, Function<Long, List<DeptDO>> childrenSupplier) {
|
||||
List<DeptDO> children = sortChildren(childrenSupplier.apply(parentId));
|
||||
if (CollUtil.isEmpty(children)) {
|
||||
return;
|
||||
}
|
||||
int sequence = 1;
|
||||
for (DeptDO child : children) {
|
||||
assertSequenceRange(sequence);
|
||||
String childCode = parentCode + formatSequence(sequence++);
|
||||
updateDeptCode(child.getId(), childCode);
|
||||
rebuildCodes(child.getId(), childCode, childrenSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
private List<DeptDO> sortChildren(List<DeptDO> children) {
|
||||
if (CollUtil.isEmpty(children)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
children.sort(DEPT_COMPARATOR);
|
||||
return children;
|
||||
}
|
||||
|
||||
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 Long normalizeParentId(Long parentId) {
|
||||
return parentId == null ? DeptDO.PARENT_ID_ROOT : parentId;
|
||||
}
|
||||
|
||||
private void updateDeptCode(Long deptId, String code) {
|
||||
DeptDO update = new DeptDO();
|
||||
update.setId(deptId);
|
||||
update.setCode(code);
|
||||
deptMapper.updateById(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeptDO getDept(Long id) {
|
||||
return deptMapper.selectById(id);
|
||||
@@ -465,4 +591,27 @@ public class DeptServiceImpl implements DeptService {
|
||||
return deptMapper.selectAllCompanyList(CommonStatusEnum.ENABLE.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
@TenantIgnore
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void initializeDeptCodes() {
|
||||
List<DeptDO> allDepts = deptMapper.selectList();
|
||||
if (CollUtil.isEmpty(allDepts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, List<DeptDO>> childrenMap = allDepts.stream()
|
||||
.collect(Collectors.groupingBy(dept -> normalizeParentId(dept.getParentId())));
|
||||
Function<Long, List<DeptDO>> childrenSupplier = id -> new ArrayList<>(childrenMap.getOrDefault(id, Collections.emptyList()));
|
||||
List<DeptDO> rootDepts = sortChildren(childrenSupplier.apply(DeptDO.PARENT_ID_ROOT));
|
||||
|
||||
int sequence = 1;
|
||||
for (DeptDO root : rootDepts) {
|
||||
assertSequenceRange(sequence);
|
||||
String code = ROOT_CODE_PREFIX + formatSequence(sequence++);
|
||||
updateDeptCode(root.getId(), code);
|
||||
rebuildCodes(root.getId(), code, childrenSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface EbanOAuth2Service {
|
||||
private String mobile;
|
||||
private String deptName;
|
||||
private String uid;
|
||||
private String displayName;
|
||||
private String rawUserInfoJson;
|
||||
private EbanOAuth2ServiceImpl.EbanTokenInfo tokenInfo; // 添加Token信息
|
||||
|
||||
@@ -70,6 +71,9 @@ public interface EbanOAuth2Service {
|
||||
public String getUid() { return uid; }
|
||||
public void setUid(String uid) { this.uid = uid; }
|
||||
|
||||
public String getDisplayName() { return displayName; }
|
||||
public void setDisplayName(String displayName) { this.displayName = displayName; }
|
||||
|
||||
public String getRawUserInfoJson() { return rawUserInfoJson; }
|
||||
public void setRawUserInfoJson(String rawUserInfoJson) { this.rawUserInfoJson = rawUserInfoJson; }
|
||||
|
||||
|
||||
@@ -77,15 +77,16 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID);
|
||||
}
|
||||
|
||||
String uid = userInfo.getUid();
|
||||
if (StrUtil.isBlank(uid)) {
|
||||
String displayName = StrUtil.trim(StrUtil.blankToDefault(userInfo.getDisplayName(), userInfo.getUsername()));
|
||||
if (StrUtil.isBlank(displayName)) {
|
||||
log.error("E办OAuth2用户信息缺少displayName与loginName,无法匹配账号: {}", JSONUtil.toJsonStr(userInfo));
|
||||
throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID);
|
||||
}
|
||||
|
||||
Long userId = parseUid(uid);
|
||||
AdminUserDO user = userService.getUser(userId);
|
||||
AdminUserDO user = userService.getUserByUsername(displayName);
|
||||
if (user == null) {
|
||||
createLoginLog(null, uid, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS);
|
||||
createLoginLog(null, displayName, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS);
|
||||
log.warn("E办OAuth2用户displayName未在系统中找到对应账号: {}", displayName);
|
||||
throw exception(AUTH_LOGIN_EBAN_USER_NOT_SYNC);
|
||||
}
|
||||
|
||||
@@ -105,13 +106,13 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
tokenInfo.getAccessToken(),
|
||||
tokenInfo.getRefreshToken(),
|
||||
tokenInfo.getExpiresIn(),
|
||||
uid,
|
||||
userInfo.getUid(),
|
||||
userInfoJson
|
||||
);
|
||||
log.info("成功保存E办token,userId={}, uid={}", user.getId(), uid);
|
||||
log.info("成功保存E办token,userId={}, uid={}, displayName={}", user.getId(), userInfo.getUid(), displayName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("保存E办token失败,userId={}, uid={}", user.getId(), uid, e);
|
||||
log.error("保存E办token失败,userId={}, uid={}, displayName={}", user.getId(), userInfo.getUid(), displayName, e);
|
||||
}
|
||||
|
||||
return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
|
||||
@@ -132,21 +133,16 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
if (StrUtil.isBlank(userInfo.getUid()) && StrUtil.isNotBlank(tokenInfo.getUid())) {
|
||||
userInfo.setUid(tokenInfo.getUid());
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private Long parseUid(String uid) {
|
||||
try {
|
||||
return Long.parseLong(uid);
|
||||
} catch (NumberFormatException ex) {
|
||||
log.warn("E办uid无法解析: {}", uid);
|
||||
throw exception(AUTH_LOGIN_EBAN_USER_NOT_SYNC);
|
||||
if (StrUtil.isBlank(userInfo.getDisplayName()) && StrUtil.isNotBlank(tokenInfo.getDisplayName())) {
|
||||
userInfo.setDisplayName(StrUtil.trim(tokenInfo.getDisplayName()));
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private String buildBasicUserInfoJson(EbanUserInfo userInfo) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("uid", userInfo.getUid());
|
||||
jsonObject.put("displayName", userInfo.getDisplayName());
|
||||
jsonObject.put("loginName", userInfo.getUsername());
|
||||
jsonObject.put("realName", userInfo.getRealName());
|
||||
jsonObject.put("email", userInfo.getEmail());
|
||||
@@ -164,6 +160,7 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
private Integer expiresIn;
|
||||
private String uid;
|
||||
private String createDate;
|
||||
private String displayName;
|
||||
|
||||
// 构造函数和getter/setter方法
|
||||
public EbanTokenInfo() {}
|
||||
@@ -190,6 +187,9 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
|
||||
public String getCreateDate() { return createDate; }
|
||||
public void setCreateDate(String createDate) { this.createDate = createDate; }
|
||||
|
||||
public String getDisplayName() { return displayName; }
|
||||
public void setDisplayName(String displayName) { this.displayName = displayName; }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,10 +231,13 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
Integer expiresIn = jsonResponse.getInt("expires_in");
|
||||
String uid = jsonResponse.getStr("uid");
|
||||
String createDate = jsonResponse.getStr("createDate");
|
||||
String displayName = jsonResponse.getStr("displayName");
|
||||
|
||||
log.info("成功获取E办access_token,uid={}, expires_in={}", uid, expiresIn);
|
||||
log.info("成功获取E办access_token,uid={}, displayName={}, expires_in={}", uid, displayName, expiresIn);
|
||||
|
||||
return new EbanTokenInfo(accessToken, refreshToken, expiresIn, uid, createDate);
|
||||
EbanTokenInfo tokenInfo = new EbanTokenInfo(accessToken, refreshToken, expiresIn, uid, createDate);
|
||||
tokenInfo.setDisplayName(displayName);
|
||||
return tokenInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,6 +280,7 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
userInfo.setMobile(userJson.getStr("mobile"));
|
||||
userInfo.setDeptName(userJson.getStr("deptName"));
|
||||
userInfo.setUid(userJson.getStr("uid"));
|
||||
userInfo.setDisplayName(resolveDisplayName(userJson));
|
||||
userInfo.setRawUserInfoJson(userJson.toString());
|
||||
|
||||
if (StrUtil.isBlank(userInfo.getRealName()) && userJson.containsKey("spRoleList")) {
|
||||
@@ -296,6 +300,23 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private String resolveDisplayName(JSONObject userJson) {
|
||||
String displayName = userJson.getStr("displayName");
|
||||
if (StrUtil.isBlank(displayName)) {
|
||||
displayName = userJson.getStr("display_name");
|
||||
}
|
||||
if (StrUtil.isBlank(displayName)) {
|
||||
displayName = userJson.getStr("displayname");
|
||||
}
|
||||
if (StrUtil.isBlank(displayName)) {
|
||||
displayName = userJson.getStr("loginName");
|
||||
}
|
||||
if (StrUtil.isBlank(displayName)) {
|
||||
displayName = userJson.getStr("realName");
|
||||
}
|
||||
return StrUtil.trim(displayName);
|
||||
}
|
||||
|
||||
private JSONObject parseResponseBody(String body, String action) {
|
||||
try {
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
package com.zt.plat.module.system.util.sync;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.symmetric.DES;
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
import com.zt.plat.module.system.enums.common.SexEnum;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.zt.plat.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS;
|
||||
import static com.zt.plat.module.system.enums.ErrorCodeConstants.SYNC_DECRYPT_TYPE;
|
||||
|
||||
/**
|
||||
@@ -25,105 +12,13 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.SYNC_DECRYPT_TY
|
||||
public class SyncVerifyUtil {
|
||||
|
||||
public static String decrypt(String ciphertext, String key, String type) {
|
||||
if ("AES".equalsIgnoreCase(type)) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
|
||||
byte[] result = cipher.doFinal(Base64.getDecoder().decode(ciphertext.getBytes()));
|
||||
return new String(result, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw exception(SYNC_DECRYPT_TYPE);
|
||||
}
|
||||
} else if ("DES".equalsIgnoreCase(type)) {
|
||||
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] desKey = new byte[8];
|
||||
System.arraycopy(keyBytes, 0, desKey, 0, Math.min(keyBytes.length, desKey.length));
|
||||
DES des = SecureUtil.des(desKey);
|
||||
|
||||
byte[] encryptedBytes = Base64.getDecoder().decode(ciphertext);
|
||||
return new String(des.decrypt(encryptedBytes), StandardCharsets.UTF_8);
|
||||
} else {
|
||||
try {
|
||||
return CryptoSignatureUtils.decrypt(ciphertext, key, type);
|
||||
} catch (IllegalArgumentException | IllegalStateException ex) {
|
||||
throw exception(SYNC_DECRYPT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成与原始代码兼容的密钥
|
||||
*/
|
||||
private static SecretKeySpec getSecretKey(String password) {
|
||||
try {
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
random.setSeed(password.getBytes());
|
||||
kg.init(128, random);
|
||||
SecretKey secretKey = kg.generateKey();
|
||||
return new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对称加密(Base64 格式输出)
|
||||
* @param plaintext 明文内容
|
||||
* @param key 密钥
|
||||
* @param type 加密类型,支持 AES、DES
|
||||
* @return 密文(Base64 格式)
|
||||
*/
|
||||
public static String encrypt(String plaintext, String key, String type) {
|
||||
if ("AES".equalsIgnoreCase(type)) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
byte[] byteContent = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
|
||||
byte[] result = cipher.doFinal(byteContent);
|
||||
return Base64.getEncoder().encodeToString(result);
|
||||
} catch (Exception e) {
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
} else if ("DES".equalsIgnoreCase(type)) {
|
||||
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] desKey = new byte[8];
|
||||
System.arraycopy(keyBytes, 0, desKey, 0, Math.min(keyBytes.length, desKey.length));
|
||||
DES des = SecureUtil.des(desKey);
|
||||
byte[] encrypted = des.encrypt(plaintext.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(encrypted);
|
||||
} else {
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verifySignature(Map<String, Object> reqMap, String type) {
|
||||
// 排序并拼接参数,忽略 signature 字段
|
||||
Map<String, Object> sortedMap = new TreeMap<>(reqMap);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if ("signature".equals(key) || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
sb.append(key).append("=").append(entry.getValue()).append("&");
|
||||
}
|
||||
if (!sb.isEmpty()) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
// 取出请求中的 signature
|
||||
String provided = (String) reqMap.get("signature");
|
||||
if (provided == null) {
|
||||
return false;
|
||||
}
|
||||
// 计算签名
|
||||
String computed;
|
||||
if ("MD5".equalsIgnoreCase(type)) {
|
||||
computed = SecureUtil.md5(sb.toString());
|
||||
} else if ("SHA256".equalsIgnoreCase(type)) {
|
||||
computed = SecureUtil.sha256(sb.toString());
|
||||
} else {
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
return provided.equalsIgnoreCase(computed);
|
||||
}
|
||||
|
||||
/**
|
||||
* e 办性别编码转换为内部性别编码
|
||||
* 外部:女=0,男=1
|
||||
|
||||
@@ -35,6 +35,18 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
||||
@Resource
|
||||
private DeptMapper deptMapper;
|
||||
|
||||
private Long createDept(Long parentId, String name, int sort) {
|
||||
DeptSaveReqVO reqVO = new DeptSaveReqVO();
|
||||
reqVO.setParentId(parentId);
|
||||
reqVO.setName(name);
|
||||
reqVO.setSort(sort);
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setDeptSource(1);
|
||||
reqVO.setIsCompany(false);
|
||||
reqVO.setIsGroup(false);
|
||||
return deptService.createDept(reqVO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDept() {
|
||||
// 准备参数
|
||||
@@ -42,6 +54,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
||||
o.setId(null); // 防止 id 被设置
|
||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||
o.setStatus(randomCommonStatus());
|
||||
o.setCode(null);
|
||||
}).setDeptSource(1);
|
||||
|
||||
// 调用
|
||||
@@ -50,13 +63,35 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
||||
assertNotNull(deptId);
|
||||
// 校验记录的属性是否正确
|
||||
DeptDO deptDO = deptMapper.selectById(deptId);
|
||||
assertPojoEquals(reqVO, deptDO, "id");
|
||||
assertPojoEquals(reqVO, deptDO, "id", "code");
|
||||
assertEquals("ZT001", deptDO.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDept_childCodeGeneration() {
|
||||
Long parentId = createDept(DeptDO.PARENT_ID_ROOT, "总部", 1);
|
||||
DeptDO parentDept = deptMapper.selectById(parentId);
|
||||
|
||||
DeptSaveReqVO childReq = new DeptSaveReqVO();
|
||||
childReq.setParentId(parentId);
|
||||
childReq.setName("事业部");
|
||||
childReq.setSort(1);
|
||||
childReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
childReq.setDeptSource(1);
|
||||
Long childId = deptService.createDept(childReq);
|
||||
|
||||
DeptDO childDept = deptMapper.selectById(childId);
|
||||
assertEquals(parentDept.getCode() + "001", childDept.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDept() {
|
||||
// mock 数据
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus())).setDeptSource(null);
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> {
|
||||
o.setStatus(randomCommonStatus());
|
||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||
}).setDeptSource(null);
|
||||
dbDeptDO.setCode("ZT001");
|
||||
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
DeptSaveReqVO reqVO = randomPojo(DeptSaveReqVO.class, o -> {
|
||||
@@ -65,12 +100,38 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
||||
o.setId(dbDeptDO.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
}).setDeptSource(1);
|
||||
reqVO.setCode(dbDeptDO.getCode());
|
||||
|
||||
// 调用
|
||||
deptService.updateDept(reqVO);
|
||||
// 校验是否更新正确
|
||||
DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, deptDO);
|
||||
assertPojoEquals(reqVO, deptDO, "code");
|
||||
assertEquals("ZT001", deptDO.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDept_parentChangedRebuildsCodes() {
|
||||
Long parentAId = createDept(DeptDO.PARENT_ID_ROOT, "A公司", 1);
|
||||
Long parentBId = createDept(DeptDO.PARENT_ID_ROOT, "B公司", 2);
|
||||
Long childId = createDept(parentAId, "子部门", 1);
|
||||
Long grandChildId = createDept(childId, "子部门-一组", 1);
|
||||
|
||||
DeptDO parentB = deptMapper.selectById(parentBId);
|
||||
|
||||
DeptSaveReqVO updateReq = new DeptSaveReqVO();
|
||||
updateReq.setId(childId);
|
||||
updateReq.setName("子部门");
|
||||
updateReq.setParentId(parentBId);
|
||||
updateReq.setSort(1);
|
||||
updateReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
updateReq.setDeptSource(1);
|
||||
deptService.updateDept(updateReq);
|
||||
|
||||
DeptDO updatedChild = deptMapper.selectById(childId);
|
||||
DeptDO updatedGrandChild = deptMapper.selectById(grandChildId);
|
||||
assertEquals(parentB.getCode() + "001", updatedChild.getCode());
|
||||
assertEquals(updatedChild.getCode() + "001", updatedGrandChild.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -293,4 +354,44 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
||||
assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_ENABLE, deptDO.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeDeptCodes() {
|
||||
DeptDO root1 = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||
o.setSort(1);
|
||||
o.setCode(null);
|
||||
}).setDeptSource(null);
|
||||
deptMapper.insert(root1);
|
||||
DeptDO root2 = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||
o.setSort(2);
|
||||
o.setCode(null);
|
||||
}).setDeptSource(null);
|
||||
deptMapper.insert(root2);
|
||||
DeptDO child1 = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(root1.getId());
|
||||
o.setSort(1);
|
||||
o.setCode(null);
|
||||
}).setDeptSource(null);
|
||||
deptMapper.insert(child1);
|
||||
DeptDO child2 = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(root1.getId());
|
||||
o.setSort(2);
|
||||
o.setCode(null);
|
||||
}).setDeptSource(null);
|
||||
deptMapper.insert(child2);
|
||||
|
||||
deptService.initializeDeptCodes();
|
||||
|
||||
DeptDO updatedRoot1 = deptMapper.selectById(root1.getId());
|
||||
DeptDO updatedRoot2 = deptMapper.selectById(root2.getId());
|
||||
DeptDO updatedChild1 = deptMapper.selectById(child1.getId());
|
||||
DeptDO updatedChild2 = deptMapper.selectById(child2.getId());
|
||||
|
||||
assertEquals("ZT001", updatedRoot1.getCode());
|
||||
assertEquals("ZT002", updatedRoot2.getCode());
|
||||
assertEquals("ZT001001", updatedChild1.getCode());
|
||||
assertEquals("ZT001002", updatedChild2.getCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user