style(system): 登录后更新部门信息功能调整

This commit is contained in:
2026-06-12 11:49:44 +08:00
parent 0b6b5856d1
commit 7acee51892
2 changed files with 361 additions and 3 deletions
@@ -3,12 +3,20 @@ package com.renkang.anyan.utils;
import cn.hutool.core.util.StrUtil;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
@@ -29,7 +37,7 @@ public class StringUtils {
public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
// 返回按照 X.509 标准进行编码的密钥的字节
// x509EncodedKeySpec2 是一种规范、规格
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(publicKeyText));
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 让密钥工厂按照指定的 规范 生成公钥
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
@@ -40,7 +48,8 @@ public class StringUtils {
byte[] result = cipher.doFinal(text.getBytes());
// 返回 Base64编码的字符串
return org.apache.commons.codec.binary.Base64.encodeBase64String(result);
return Base64.getEncoder().encodeToString(result);
// return org.apache.commons.codec.binary.Base64.encodeBase64String(result);
}
@@ -112,4 +121,122 @@ public class StringUtils {
return filePath.getFileName().toString();
}
}
// ------------------- AES 加解密(带盐值处理)-------------------
/** 内部密钥,仅用于日志脱敏,非高强度安全场景 */
private static final String AES_PASSWORD = "XJPlatform@2024#Secure";
/** 盐值长度(字节) */
private static final int SALT_LENGTH = 8;
/** 初始化向量长度(字节) */
private static final int IV_LENGTH = 16;
/** AES 密钥长度 */
private static final int KEY_LENGTH = 128;
/** PBKDF2 迭代次数 */
private static final int PBKDF2_ITERATIONS = 1000;
/** AES 加密算法 */
private static final String AES_CIPHER = "AES/CBC/PKCS5Padding";
/**
* AES 加密(带随机盐值和 IV,每次加密结果不同)
*
* @param plainText 明文
* @return Base64 密文(格式:盐值 + IV + 加密数据)
*/
public static String aesEncrypt(String plainText) {
if (plainText == null || plainText.isEmpty()) {
return plainText;
}
try {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(salt);
random.nextBytes(iv);
SecretKeySpec keySpec = deriveKey(salt);
Cipher cipher = Cipher.getInstance(AES_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
byte[] combined = new byte[SALT_LENGTH + IV_LENGTH + encrypted.length];
System.arraycopy(salt, 0, combined, 0, SALT_LENGTH);
System.arraycopy(iv, 0, combined, SALT_LENGTH, IV_LENGTH);
System.arraycopy(encrypted, 0, combined, SALT_LENGTH + IV_LENGTH, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
} catch (Exception e) {
System.err.println("AES加密失败: " + e.getMessage());
e.printStackTrace();
return plainText;
}
}
/**
* AES 解密(解析盐值 + IV + 加密数据)
*
* @param cipherText Base64 密文
* @return 明文
*/
public static String aesDecrypt(String cipherText) {
if (cipherText == null || cipherText.isEmpty()) {
return cipherText;
}
try {
byte[] combined = Base64.getDecoder().decode(cipherText);
byte[] salt = new byte[SALT_LENGTH];
byte[] iv = new byte[IV_LENGTH];
byte[] encrypted = new byte[combined.length - SALT_LENGTH - IV_LENGTH];
System.arraycopy(combined, 0, salt, 0, SALT_LENGTH);
System.arraycopy(combined, SALT_LENGTH, iv, 0, IV_LENGTH);
System.arraycopy(combined, SALT_LENGTH + IV_LENGTH, encrypted, 0, encrypted.length);
SecretKeySpec keySpec = deriveKey(salt);
Cipher cipher = Cipher.getInstance(AES_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
System.err.println("AES解密失败: " + e.getMessage());
e.printStackTrace();
return cipherText;
}
}
/**
* 通过 PBKDF2 从盐值和内部密钥派生 AES 密钥
*/
private static SecretKeySpec deriveKey(byte[] salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(AES_PASSWORD.toCharArray(), salt, PBKDF2_ITERATIONS, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
} catch (Exception e) {
throw new RuntimeException("派生AES密钥失败", e);
}
}
public static void main(String[] args) throws Exception {
// --- RSA 公钥加密测试(安眼平台)---
// String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJL0JkqsUoK6kt3JyogsgqNp9VDGDp+t3ZAGMbVoMPdHNT2nfiIVh9ZMNHF7g2XiAa8O8AQWyh2PjMR0NiUSVQMCAwEAAQ==";
// String password = "Aa%13999527523";
// String rsaEncrypted = encryptByPublicKey(publicKey, password);
// System.out.println("RSA加密: " + rsaEncrypted);
//
// // --- AES 加解密测试(日志脱敏)---
// String originalText = "Aa%13999527523";
// String aesEncrypted = aesEncrypt(originalText);
String aesDecrypted = aesDecrypt("RuKy7jFDJR0rEJiwxgcyX6RFq8qP5FEgWZuMNxuVkKlbHhjJ2SzLOA==");
// System.out.println("AES加密: " + aesEncrypted);
System.out.println("AES解密: " + aesDecrypted);
// System.out.println("匹配: " + originalText.equals(aesDecrypted));
// 验证每次加密结果不同(盐值和IV随机)
// String aesEncrypted2 = aesEncrypt(originalText);
// System.out.println("再次加密: " + aesEncrypted2);
// System.out.println("两次密文不同: " + !aesEncrypted.equals(aesEncrypted2));
}
}
@@ -30,6 +30,7 @@ import org.jeecg.modules.system.entity.HealthUserEmployeeEx;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.manager.DepartCacheManager;
import org.jeecg.modules.system.manager.UserCacheManager;
import org.jeecg.modules.system.model.SysLoginModel;
import org.jeecg.modules.system.service.*;
import org.jeecg.util.RSAEncryptUtils;
@@ -37,6 +38,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import javax.json.Json;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
@@ -60,6 +62,7 @@ public class LoginAnYanServiceImpl implements ILoginAnYanService {
private final TaskExecutor taskExecutor;
private final LoginService loginService;
private final DepartCacheManager departCacheManager;
private final UserCacheManager userCacheManager;
private static final String CLIENT_TYPE_PC = "pc";
private static final String CLIENT_TYPE_APP = "app";
@@ -107,10 +110,12 @@ public class LoginAnYanServiceImpl implements ILoginAnYanService {
*/
public Result<JSONObject> loginBusiness(SysLoginModel sysLoginModel, String username, String clientType) {
// 密码解密
log.info(sysLoginModel.getPassword());
String decryptedPassword = RSAEncryptUtils.decrypt1(sysLoginModel.getPassword(), CommonConstant.PRIVATE_KEY);
if (StrUtil.isBlank(decryptedPassword)) {
return Result.error("密码解密失败");
}
log.info("解密密码:"+ com.renkang.anyan.utils.StringUtils.aesEncrypt(decryptedPassword));
// 获取安眼token
AuthResult anYanAuthResult = anYanClient.getAuthTokenByPassword(username, decryptedPassword);
@@ -307,13 +312,17 @@ public class LoginAnYanServiceImpl implements ILoginAnYanService {
* @author PengJ
* @date 2025/10/29 9:37
*/
private String checkDepartAndUpdate(AnYanUserInfoResult.EmployeeUnitDTO employeeUnitDTO) {
private String checkDepartAndUpdateOld(AnYanUserInfoResult.EmployeeUnitDTO employeeUnitDTO) {
log.info(JSONObject.toJSONString(employeeUnitDTO));
//{"directDepartmentCode":"1102134","directDepartmentName":"集输班组","email":"hs_aikepaerg@petrochina.com.cn","employeeNum":"80488058","enabledFlag":"1","gender":0,"levelPath":"00000100|00131100|1100620|1102134","mobile":"13999527523","name":"艾克帕尔·玉素甫","sectionUnitCode":"1100620","sectionUnitName":"运行维护三中心","unitCode":"00131100","unitName":"采油一厂","unitNamePath":"root|采油一厂|运行维护三中心|集输班组"}
String levelPath = employeeUnitDTO.getLevelPath();
String orgId = "";
if (StringUtils.isNotBlank(levelPath)) {
String parentId = "";
String[] unitCodes = levelPath.split("\\|");
int index = 0;
for (String unitCode : unitCodes) {
index ++;
//判断是否为顶级组织
if (StringUtils.isBlank(unitCode) || "00000100".equals(unitCode)) {
parentId = sysDepartService.getOne(new LambdaQueryWrapper<SysDepart>()
@@ -333,6 +342,8 @@ public class LoginAnYanServiceImpl implements ILoginAnYanService {
orgName = employeeUnitDTO.getUnitName();
} else if (unitCode.equals(employeeUnitDTO.getSectionUnitCode())) {
orgName = employeeUnitDTO.getSectionUnitName();
} else if (unitCode.equals(employeeUnitDTO.getDirectDepartmentCode())) {//缺失了直属单位
orgName = employeeUnitDTO.getDirectDepartmentName();
}
if (StringUtils.isBlank(orgName)) {
continue;
@@ -399,14 +410,234 @@ public class LoginAnYanServiceImpl implements ILoginAnYanService {
}
}
}
String sendParentId = unitCodes[index-1];
// 父id赋值
parentId = depart.getId();
log.info(sendParentId + "----" +parentId);
orgId = depart.getId();
}
}
return orgId;
}
/**
* 检查并更新部门信息,返回最底层部门ID
* <p>
* levelPath 按 | 分隔各级单位编码,第一位 00000100 为系统根节点需跳过。
* unitNamePath 按 | 分隔各级单位名称,与 levelPath 一一对应。
* depart.id = levelPath 中的编码值,depart.parentId = 前一位编码值(跳过 00000100 后父级为系统根组织)。
* orgCode 层级格式:一级 A01,二级 A01A01,三级 A01A01A01,新增时查询同级最大值 +1。
* 已存在部门:若 parentId 或名称有变化则更新,parentId 变更时同步重新生成 orgCode。
*
* @param employeeUnitDTO 安眼用户信息
* @return 最底层部门 ID
*/
private String checkDepartAndUpdate(AnYanUserInfoResult.EmployeeUnitDTO employeeUnitDTO) {
log.info(JSONObject.toJSONString(employeeUnitDTO));
String levelPath = employeeUnitDTO.getLevelPath();
String unitNamePath = employeeUnitDTO.getUnitNamePath();
if (StringUtils.isBlank(levelPath) || StringUtils.isBlank(unitNamePath)) {
log.warn("levelPath或unitNamePath为空,跳过部门更新");
return "";
}
String[] codes = levelPath.split("\\|");
String[] names = unitNamePath.split("\\|");
if (codes.length != names.length) {
log.warn("levelPath与unitNamePath长度不一致,codes={}names={}", codes.length, names.length);
return "";
}
// 获取系统根组织ID作为默认父级
String parentId = getRootDepartId();
String orgId = "";
for (int i = 0; i < codes.length; i++) {
String code = codes[i];
String name = names[i];
// 跳过根节点标识
if ("00000100".equals(code) || StringUtils.isBlank(code)) {
continue;
}
if (StringUtils.isBlank(name)) {
log.warn("部门名称为空,跳过 code={}", code);
continue;
}
SysDepart depart = sysDepartService.getById(code);
boolean departmentChanged = false;
if (depart == null) {
// 新增部门
depart = new SysDepart();
depart.setId(code);
depart.setParentId(parentId);
depart.setDepartName(name);
depart.setOrgCode(generateOrgCode(parentId));
depart.setCreateTime(new Date());
depart.setStatus("1");
depart.setDelFlag("0");
depart.setOrgCategory("2");
depart.setIzLeaf(CommonConstant.IS_LEAF);
sysDepartService.save(depart);
departmentChanged = true;
} else {
// 检查是否需要更新
String oldOrgCode = depart.getOrgCode();
boolean changed = false;
boolean parentChanged = false;
if (!parentId.equals(depart.getParentId())) {
depart.setParentId(parentId);
parentChanged = true;
}
if (!name.equals(depart.getDepartName())) {
depart.setDepartName(name);
changed = true;
}
// parentId 变了,或 orgCode 与父级不一致(如部门被移动但 orgCode 未更新),需重新生成
if (parentChanged || !orgCodeMatchesParent(depart.getOrgCode(), parentId)) {
depart.setOrgCode(generateOrgCode(parentId));
changed = true;
}
if (changed) {
depart.setUpdateTime(new Date());
sysDepartService.updateById(depart);
// 若 orgCode 发生了变化,同步更新 sysUser 中的 orgCode 并刷新用户缓存
String newOrgCode = depart.getOrgCode();
if (!newOrgCode.equals(oldOrgCode)) {
log.info("部门orgCode变更,code={}, oldOrgCode={}, newOrgCode={}", code, oldOrgCode, newOrgCode);
// 先查出受影响用户的ID,用于后续刷新缓存
List<SysUser> affectedUsers = sysUserService.list(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getId)
.eq(SysUser::getOrgCode, oldOrgCode));
// 批量更新 sys_user 表 orgCode
sysUserService.update(new LambdaUpdateWrapper<SysUser>()
.eq(SysUser::getOrgCode, oldOrgCode)
.set(SysUser::getOrgCode, newOrgCode));
// 刷新受影响用户的缓存
if (affectedUsers != null && !affectedUsers.isEmpty()) {
for (SysUser user : affectedUsers) {
userCacheManager.updateUserCache(user.getId());
}
log.info("已刷新 {} 个用户的缓存", affectedUsers.size());
}
}
}
departmentChanged = changed;
}
// 若父级是叶子节点,更新为非叶子节点
SysDepart parent = sysDepartService.getById(parentId);
if (parent != null && CommonConstant.IS_LEAF.equals(parent.getIzLeaf())) {
boolean updated = sysDepartService.update(new LambdaUpdateWrapper<SysDepart>()
.eq(SysDepart::getId, parentId)
.set(SysDepart::getIzLeaf, CommonConstant.NOT_LEAF));
if (updated) {
departmentChanged = true;
}
}
// 根据是否有子节点维护本级叶子状态
long childCount = sysDepartService.count(new LambdaQueryWrapper<SysDepart>()
.eq(SysDepart::getParentId, code)
.eq(SysDepart::getDelFlag, "0"));
int expectedLeaf = childCount == 0 ? CommonConstant.IS_LEAF : CommonConstant.NOT_LEAF;
if (!Integer.valueOf(expectedLeaf).equals(depart.getIzLeaf())) {
boolean updated = sysDepartService.update(new LambdaUpdateWrapper<SysDepart>()
.eq(SysDepart::getId, code)
.set(SysDepart::getIzLeaf, expectedLeaf));
if (updated) {
departmentChanged = true;
}
}
// 仅当部门信息有变更时才更新缓存
if (departmentChanged) {
updateCache(depart);
}
parentId = code;
orgId = code;
}
return orgId;
}
/**
* 查询系统根组织IDparentId为空、orgCategory=1、orgCode=A01
*/
private String getRootDepartId() {
SysDepart root = sysDepartService.getOne(new LambdaQueryWrapper<SysDepart>()
.and(w -> w.isNull(SysDepart::getParentId).or().eq(SysDepart::getParentId, ""))
.eq(SysDepart::getOrgCategory, "1")
.eq(SysDepart::getOrgCode, "A01"));
return root != null ? root.getId() : "";
}
/**
* 生成部门 orgCode
* <p>
* 查询同级兄弟部门中 orgCode 最大值,提取最后一段 AXX 序号 +1。
* 格式:一级直接返回 A01,二级为 父级orgCode + A01,后续依此类推。
*
* @param parentId 父部门ID
* @return 新生成的 orgCode
*/
private String generateOrgCode(String parentId) {
String prefix;
if (StringUtils.isBlank(parentId)) {
prefix = "";
} else {
SysDepart parent = sysDepartService.getById(parentId);
prefix = parent != null ? parent.getOrgCode() : "";
}
List<SysDepart> siblings = sysDepartService.list(new LambdaQueryWrapper<SysDepart>()
.eq(SysDepart::getParentId, parentId)
.eq(SysDepart::getDelFlag, "0")
.orderByDesc(SysDepart::getOrgCode));
// 只取 orgCode 与父级前缀一致的兄弟,过滤掉历史脏数据
if (siblings != null && StringUtils.isNotBlank(prefix)) {
siblings.removeIf(s -> !s.getOrgCode().startsWith(prefix));
}
if (siblings == null || siblings.isEmpty()) {
return prefix + "A01";
}
// 取 orgCode 最大的,提取最后一段 AXX 序号
String maxCode = siblings.get(0).getOrgCode();
String lastSegment = maxCode.substring(maxCode.length() - 3);
int seq = Integer.parseInt(lastSegment.substring(1));
return prefix + "A" + String.format("%02d", seq + 1);
}
/**
* 检查部门 orgCode 是否与父级 orgCode 一致
* <p>
* 子级 orgCode 必须以父级 orgCode 为前缀。
* 例如父级 A01A06,子级应为 A01A06A01、A01A06A02 等。
*
* @param childOrgCode 子部门 orgCode
* @param parentId 父部门 ID
* @return true=一致,false=不一致需修正
*/
private boolean orgCodeMatchesParent(String childOrgCode, String parentId) {
if (StringUtils.isBlank(parentId) || StringUtils.isBlank(childOrgCode)) {
return true;
}
SysDepart parent = sysDepartService.getById(parentId);
if (parent == null || StringUtils.isBlank(parent.getOrgCode())) {
return true;
}
return childOrgCode.startsWith(parent.getOrgCode());
}
/**
* @author: pj
* @description: 更新部门缓存