From 01bccf76ae7bcac536993ea7a7aa10ef846871a9 Mon Sep 17 00:00:00 2001 From: vincent Date: Wed, 4 Nov 2026 09:10:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=86=E8=AF=84=E6=8E=A5=E5=8F=A3=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/supervision/CommonController.java | 28 +++ .../supervision/SysUserCommonController.java | 167 ++++++++++++++++++ .../domain/bo/ChangePhonenumberBo.java | 26 +++ .../domain/bo/QueryUserInfoBo.java | 22 +++ .../system/service/ISysUserService.java | 19 ++ .../service/impl/SysUserServiceImpl.java | 44 +++++ 6 files changed, 306 insertions(+) create mode 100644 zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/SysUserCommonController.java create mode 100644 zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/ChangePhonenumberBo.java create mode 100644 zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/QueryUserInfoBo.java diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java index 9ea7c229..c7a37294 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; +import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.web.core.BaseController; import org.dromara.system.service.ICryptoSensitiveHsmService; @@ -78,4 +79,31 @@ public class CommonController extends BaseController { } } + /** + * 同步操作日志密评 + * @return + */ + @GetMapping("/syncIntegrityHashModifiedOperLogs") + public R syncIntegrityHashModifiedOperLogs() { + log.info("同步操作日志密评开始"); + cryptoSensitiveHsmService.syncIntegrityHashModifiedOperLogs(); + log.info("同步操作日志密评结束"); + return R.ok("同步操作日志密评成功", null); + } + + + + /** + * 同步操作日志密评 + * @return + */ + @GetMapping("/initSysUserSensitiveData") + public R initSysUserSensitiveData() { + log.info("同步用户密评开始"); + String loginName = "liufangyan1"; + DataPermissionHelper.ignore(() -> cryptoSensitiveHsmService.initSysUserSensitiveData(loginName)); + return R.ok("同步操作日志密评成功", null); + } + + } diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/SysUserCommonController.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/SysUserCommonController.java new file mode 100644 index 00000000..5b732c6c --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/SysUserCommonController.java @@ -0,0 +1,167 @@ +package com.zdxt.enforcementcode.controller.web.supervision; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.zdxt.enforcementcode.common.util.CommonUtil; +import com.zdxt.enforcementcode.domain.bo.ChangePhonenumberBo; +import com.zdxt.enforcementcode.common.util.ZwSmsClient; +import com.zdxt.enforcementcode.domain.bo.QueryUserInfoBo; +import com.zdxt.enforcementcode.domain.bo.thirdparty.SIQMsgParamBo; +import com.zdxt.enforcementcode.service.thirdparty.ISIQService; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.helper.DataPermissionHelper; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.web.core.BaseController; +import org.dromara.system.domain.SysUser; +import org.dromara.system.mapper.SysUserMapper; +import org.dromara.system.service.ICryptoSensitiveHsmService; +import org.dromara.system.service.ISysUserService; +import org.dromara.system.domain.vo.SysUserVo; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 监督端-通用接口 + */ +@Slf4j +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/web/sysuser/common") +public class SysUserCommonController extends BaseController { + + private final ISysUserService userService; + private final SysUserMapper sysUserMapper; + private final ICryptoSensitiveHsmService cryptoSensitiveHsmService; + + /** + * 更改用户手机号码 + *

密评双轨口径: phonenumber 列写 HSM 密文(SM4-CBC 随机 IV, 不可等值比较), + * crypto_remark_two 备份 AES 原逻辑值; 判重/查询均走备份轨道

+ * + * @param bo 更改手机号参数(userId + phonenumber) + */ + @PostMapping("/changePhonenumber") + public R changePhonenumber(@Validated @RequestBody ChangePhonenumberBo bo) { + Long userId = bo.getUserId(); + String phonenumber = bo.getPhonenumber(); + SysUserVo user = userService.selectUserById(userId); + if (ObjectUtil.isNull(user)) { + return R.fail("用户不存在"); + } + // 双轨判重: 走 crypto_remark_two 确定性备份轨道(HSM 密文随机 IV 无法等值比较) + if (!cryptoSensitiveHsmService.checkPhoneUnique(userId, phonenumber)) { + return R.fail("修改失败,该手机号已被其他账号使用"); + } + // 双轨写入: phonenumber=HSM密文 + crypto_remark_two=AES备份(原子更新; HSM 失败保持原值并返回失败) + if (!cryptoSensitiveHsmService.savePhoneDualTrack(userId, phonenumber)) { + return R.fail("手机号修改失败,请稍后重试"); + } + // mobile_number 列(市认证扩展字段, AES 形态)同步更新, 避免两手机号字段口径不一致 + String aesPhone = cryptoSensitiveHsmService.normalizePhoneToAes(phonenumber); + if (StringUtils.isNotBlank(aesPhone)) { + sysUserMapper.update(null, Wrappers.lambdaUpdate() + .eq(SysUser::getUserId, userId) + .set(SysUser::getMobileNumber, aesPhone)); + } + // 完整性摘要按库中实际值刷新(摘要口径与库中值一致, 避免完整性校验误报篡改) + try { + SysUser updated = sysUserMapper.selectById(userId); + cryptoSensitiveHsmService.saveIntegrityHash("sys_user", String.valueOf(userId), + "password,phonenumber,certificateNumber", + updated.getPassword() != null ? updated.getPassword() : "", + updated.getPhonenumber() != null ? updated.getPhonenumber() : "", + updated.getCertificateNumber() != null ? updated.getCertificateNumber() : ""); + } catch (Exception e) { + log.warn("手机号修改后完整性摘要刷新失败, userId={}", userId, e); + } + return R.ok(); + } + + /** + * 根据手机号查询用户 + *

密评双轨口径: phonenumber 已为 HSM 密文(随机 IV 不可等值比较), + * 归一化后走 crypto_remark_two 备份轨道等值查询, 兼容未双轨化存量行

+ * + * @param phonenumber 手机号(明文, 11 位) + */ + @GetMapping("/getUserByPhonenumber") + public R> getUserByPhonenumber( + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + @RequestParam String phonenumber) { + SysUserVo user = userService.selectUserByPhonenumber(phonenumber); + if (ObjectUtil.isNull(user)) { + return R.fail("未找到该手机号对应的用户"); + } + Map data = new HashMap<>(); + data.put("userId", user.getUserId()); + data.put("userName", user.getUserName()); + data.put("loginName", user.getLoginName()); + data.put("nickName", user.getNickName()); + data.put("deptId", user.getDeptId()); + data.put("status", user.getStatus()); + // 双轨解密展示(备份轨道优先, 兼容明文/AES/HSM 三种存量形态) + String phone = CommonUtil.decryptPhoneForDisplay(user.getPhonenumber(), user.getCryptoRemarkTwo()); + data.put("phonenumber", phone != null ? phone : ""); + return R.ok(data); + } + + /** + * 根据用户名称 + 手机号查询用户信息 + *

密评双轨口径: 手机号条件走 crypto_remark_two 备份轨道等值查询; 用户名称模糊匹配; + * 存量手机号无唯一约束可能重复, 返回全部匹配用户(按更新时间倒序)

+ * + * @param bo 查询参数(userName + phonenumber, 至少填写一项) + */ + @PostMapping("/queryUserInfo") + public R>> queryUserInfo(@Validated @RequestBody QueryUserInfoBo bo) { + if (StringUtils.isBlank(bo.getUserName()) && StringUtils.isBlank(bo.getPhonenumber())) { + return R.fail("用户名称与手机号至少填写一项"); + } + List users = userService.selectUserListByCondition(bo.getUserName(), bo.getPhonenumber()); + if (ObjectUtil.isEmpty(users)) { + return R.fail("未找到符合条件的用户"); + } + List> rows = new ArrayList<>(users.size()); + for (SysUserVo user : users) { + rows.add(buildUserRow(user)); + } + return R.ok(rows); + } + + /** + * 组装用户返回数据(手工挑字段, 规避 password/cryptoRemark* 等敏感字段外泄) + */ + private Map buildUserRow(SysUserVo user) { + Map data = new HashMap<>(); + data.put("userId", user.getUserId()); + data.put("userName", user.getUserName()); + data.put("loginName", user.getLoginName()); + data.put("nickName", user.getNickName()); + data.put("deptId", user.getDeptId()); + data.put("status", user.getStatus()); + data.put("lawCertificateNum", user.getLawCertificateNum()); + // 双轨解密展示(备份轨道优先, 兼容明文/AES/HSM 三种存量形态) + String phone = CommonUtil.decryptPhoneForDisplay(user.getPhonenumber(), user.getCryptoRemarkTwo()); + data.put("phonenumber", phone != null ? phone : ""); + return data; + } + + +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/ChangePhonenumberBo.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/ChangePhonenumberBo.java new file mode 100644 index 00000000..be2c2c0e --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/ChangePhonenumberBo.java @@ -0,0 +1,26 @@ +package com.zdxt.enforcementcode.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +/** + * 监督端-更改用户手机号参数 + */ +@Data +public class ChangePhonenumberBo { + + /** + * 目标用户ID + */ + @NotNull(message = "用户ID不能为空") + private Long userId; + + /** + * 新手机号(明文, 11位) + */ + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phonenumber; +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/QueryUserInfoBo.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/QueryUserInfoBo.java new file mode 100644 index 00000000..74773237 --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/QueryUserInfoBo.java @@ -0,0 +1,22 @@ +package com.zdxt.enforcementcode.domain.bo; + +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +/** + * 监督端-用户信息组合查询参数 + */ +@Data +public class QueryUserInfoBo { + + /** + * 用户名称(模糊匹配, 与手机号至少填写一项) + */ + private String userName; + + /** + * 手机号(明文, 11位, 与用户名称至少填写一项) + */ + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phonenumber; +} diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ISysUserService.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ISysUserService.java index de0193f2..2b684fe9 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ISysUserService.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ISysUserService.java @@ -266,6 +266,25 @@ public interface ISysUserService { */ List selectUserListByLoginName(String loginName); + /** + * 通过手机号查询用户列表 + *

存量数据手机号无唯一约束可能重复, 返回全部匹配用户(按更新时间倒序)

+ * + * @param phonenumber 手机号 + * @return 用户列表信息 + */ + List selectUserListByPhonenumber(String phonenumber); + + /** + * 通过用户名称 + 手机号组合条件查询用户列表 + *

两个条件均可选但至少一项: 用户名称模糊匹配; 手机号走密评双轨备份轨道等值查询

+ * + * @param userName 用户名称(可空) + * @param phonenumber 手机号(可空) + * @return 用户列表信息(按更新时间倒序) + */ + List selectUserListByCondition(String userName, String phonenumber); + /** * 获取当前用户信息 * diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index dfbc8291..e2bec036 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java @@ -854,6 +854,50 @@ public class SysUserServiceImpl implements ISysUserService, UserService { return baseMapper.selectVoList(lqw); } + /** + * 通过手机号查询用户 + * + * @param phonenumber 手机号 + * @return 用户对象信息 + */ + @Override + public List selectUserListByPhonenumber(String phonenumber) { + if (StringUtils.isBlank(phonenumber)) { + return new ArrayList<>(); + } + // 与单条方法同口径: HSM 密文不可等值比较, 归一化后走 crypto_remark_two 备份轨道(兼容未双轨化存量行) + String aesForm = cryptoSensitiveHsmService.normalizePhoneToAes(phonenumber); + if (StringUtils.isBlank(aesForm)) { + return new ArrayList<>(); + } + return baseMapper.selectVoList(new LambdaQueryWrapper() + .and(w -> w.eq(SysUser::getCryptoRemarkTwo, aesForm) + .or().eq(SysUser::getPhonenumber, aesForm)) + .orderByDesc(SysUser::getUpdateTime) + .orderByDesc(SysUser::getUserId)); + } + + @Override + public List selectUserListByCondition(String userName, String phonenumber) { + if (StringUtils.isBlank(userName) && StringUtils.isBlank(phonenumber)) { + return new ArrayList<>(); + } + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + if (StringUtils.isNotBlank(phonenumber)) { + // 手机号走双轨备份轨道(HSM 密文随机 IV 不可等值比较); 归一化失败宁可返回空, 避免条件失效全表扫 + String aesForm = cryptoSensitiveHsmService.normalizePhoneToAes(phonenumber); + if (StringUtils.isBlank(aesForm)) { + return new ArrayList<>(); + } + lqw.and(w -> w.eq(SysUser::getCryptoRemarkTwo, aesForm) + .or().eq(SysUser::getPhonenumber, aesForm)); + } + lqw.like(StringUtils.isNotBlank(userName), SysUser::getUserName, userName); + lqw.orderByDesc(SysUser::getUpdateTime) + .orderByDesc(SysUser::getUserId); + return baseMapper.selectVoList(lqw); + } + /** * 获取当前用户信息 *