6 changed files with 306 additions and 0 deletions
@ -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; |
|||
|
|||
/** |
|||
* 更改用户手机号码 |
|||
* <p>密评双轨口径: phonenumber 列写 HSM 密文(SM4-CBC 随机 IV, 不可等值比较), |
|||
* crypto_remark_two 备份 AES 原逻辑值; 判重/查询均走备份轨道</p> |
|||
* |
|||
* @param bo 更改手机号参数(userId + phonenumber) |
|||
*/ |
|||
@PostMapping("/changePhonenumber") |
|||
public R<Void> 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.<SysUser>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(); |
|||
} |
|||
|
|||
/** |
|||
* 根据手机号查询用户 |
|||
* <p>密评双轨口径: phonenumber 已为 HSM 密文(随机 IV 不可等值比较), |
|||
* 归一化后走 crypto_remark_two 备份轨道等值查询, 兼容未双轨化存量行</p> |
|||
* |
|||
* @param phonenumber 手机号(明文, 11 位) |
|||
*/ |
|||
@GetMapping("/getUserByPhonenumber") |
|||
public R<Map<String, Object>> 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<String, Object> 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); |
|||
} |
|||
|
|||
/** |
|||
* 根据用户名称 + 手机号查询用户信息 |
|||
* <p>密评双轨口径: 手机号条件走 crypto_remark_two 备份轨道等值查询; 用户名称模糊匹配; |
|||
* 存量手机号无唯一约束可能重复, 返回全部匹配用户(按更新时间倒序)</p> |
|||
* |
|||
* @param bo 查询参数(userName + phonenumber, 至少填写一项) |
|||
*/ |
|||
@PostMapping("/queryUserInfo") |
|||
public R<List<Map<String, Object>>> queryUserInfo(@Validated @RequestBody QueryUserInfoBo bo) { |
|||
if (StringUtils.isBlank(bo.getUserName()) && StringUtils.isBlank(bo.getPhonenumber())) { |
|||
return R.fail("用户名称与手机号至少填写一项"); |
|||
} |
|||
List<SysUserVo> users = userService.selectUserListByCondition(bo.getUserName(), bo.getPhonenumber()); |
|||
if (ObjectUtil.isEmpty(users)) { |
|||
return R.fail("未找到符合条件的用户"); |
|||
} |
|||
List<Map<String, Object>> rows = new ArrayList<>(users.size()); |
|||
for (SysUserVo user : users) { |
|||
rows.add(buildUserRow(user)); |
|||
} |
|||
return R.ok(rows); |
|||
} |
|||
|
|||
/** |
|||
* 组装用户返回数据(手工挑字段, 规避 password/cryptoRemark* 等敏感字段外泄) |
|||
*/ |
|||
private Map<String, Object> buildUserRow(SysUserVo user) { |
|||
Map<String, Object> 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; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
Loading…
Reference in new issue