feat: 完善用户管理系统字段维护和唯一性验证

• 移除用户名唯一约束,允许员工重名
• 新增手机号和设备序列号唯一性验证(前端+后端)
• 优化用户操作抽屉UI,更紧凑美观的设计
• 修复新增/修改用户时字段维护逻辑:
  - org_id, org_name, customer_id 从 sys_org_units 动态获取
  - user_type 根据 sys_role.is_admin 动态计算
  - admin_level 根据 sys_org_units.level 动态设置
• 员工卡号调整到工作信息栏,布局更合理
• 添加数据库迁移脚本移除用户名唯一约束
• 增强用户创建/编辑的数据完整性和一致性

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
BrunoGao
2025-09-22 15:15:58 +08:00
co-authored by Claude
parent aa41afaf00
commit d51ca6bdf7
5 changed files with 233 additions and 6 deletions
@@ -0,0 +1,27 @@
-- 移除 sys_user 表的 user_name 唯一约束
-- 原因: 员工名字可以重复,应该通过用户ID区分,而不是用户名
-- 作者: bruno.gao
-- 时间: 2025-09-22
-- 检查并删除 user_name 唯一约束
-- 注意:不同数据库可能有不同的约束名称
-- 方法1: 如果约束名为 idx_user_name_unique
ALTER TABLE sys_user DROP INDEX IF EXISTS idx_user_name_unique;
-- 方法2: 如果约束名为 UK_user_name
ALTER TABLE sys_user DROP INDEX IF EXISTS UK_user_name;
-- 方法3: 如果约束名为 ux_sys_user_user_name
ALTER TABLE sys_user DROP INDEX IF EXISTS ux_sys_user_user_name;
-- 方法4: 通用方法 - 查看所有约束后再删除
-- 可以先运行以下查询查看所有约束:
-- SHOW INDEX FROM sys_user WHERE Column_name = 'user_name';
-- 添加注释说明
ALTER TABLE sys_user COMMENT = '用户表 - user_name字段不再强制唯一,员工可以重名,通过ID区分';
-- 验证约束删除是否成功的查询
-- SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
-- WHERE TABLE_NAME = 'sys_user' AND CONSTRAINT_TYPE = 'UNIQUE';
@@ -0,0 +1,13 @@
-- 移除 sys_user 表的 user_name 唯一约束
-- 原因: 员工名字可以重复,应该通过用户ID区分,而不是用户名
-- 作者: bruno.gao
-- 时间: 2025-09-22
-- 删除 user_name 唯一约束索引
DROP INDEX idx_user_name_unique ON sys_user;
-- 添加注释说明
ALTER TABLE sys_user COMMENT = '用户表 - user_name字段不再强制唯一,员工可以重名,通过ID区分';
-- 验证约束删除是否成功
SELECT 'Username unique constraint removed successfully' as result;
@@ -209,5 +209,25 @@ public class SysUserController {
return Result.data(sysUserFacade.batchImportUsers(file, orgIds));
}
@GetMapping("/check_phone")
@Operation(operationId = "11", summary = "检查手机号是否已存在(仅检查未删除用户)")
public Result<Boolean> checkPhoneExists(
@Parameter(description = "手机号") @RequestParam("phone") String phone,
@Parameter(description = "排除的用户ID", required = false) @RequestParam(value = "excludeUserId", required = false) Long excludeUserId,
@Parameter(description = "是否删除标识(0-未删除,1-已删除)", required = false) @RequestParam(value = "isDeleted", defaultValue = "0") Integer isDeleted) {
boolean exists = sysUserService.checkPhoneExists(phone, excludeUserId, isDeleted);
return Result.data(exists);
}
@GetMapping("/check_device_sn")
@Operation(operationId = "12", summary = "检查设备序列号是否已存在(仅检查未删除用户)")
public Result<Boolean> checkDeviceSnExists(
@Parameter(description = "设备序列号") @RequestParam("deviceSn") String deviceSn,
@Parameter(description = "排除的用户ID", required = false) @RequestParam(value = "excludeUserId", required = false) Long excludeUserId,
@Parameter(description = "是否删除标识(0-未删除,1-已删除)", required = false) @RequestParam(value = "isDeleted", defaultValue = "0") Integer isDeleted) {
boolean exists = sysUserService.checkDeviceSnExists(deviceSn, excludeUserId, isDeleted);
return Result.data(exists);
}
}
@@ -506,4 +506,30 @@ public interface ISysUserService extends IService<SysUser> {
* @CreateTime 2025-09-12
*/
List<SysUser> getUsersByType(Integer userType, Long orgId, Long customerId);
/**
* 检查手机号是否已存在(仅检查未删除用户)
* <p>用于用户创建/更新时的手机号唯一性验证</p>
*
* @param phone 手机号
* @param excludeUserId 排除的用户ID(编辑时用)
* @param isDeleted 是否删除标识(0-未删除,1-已删除)
* @return true-已存在,false-不存在
* @author bruno.gao
* @CreateTime 2025-09-22
*/
boolean checkPhoneExists(String phone, Long excludeUserId, Integer isDeleted);
/**
* 检查设备序列号是否已存在(仅检查未删除用户)
* <p>用于用户创建/更新时的设备唯一性验证</p>
*
* @param deviceSn 设备序列号
* @param excludeUserId 排除的用户ID(编辑时用)
* @param isDeleted 是否删除标识(0-未删除,1-已删除)
* @return true-已存在,false-不存在
* @author bruno.gao
* @CreateTime 2025-09-22
*/
boolean checkDeviceSnExists(String deviceSn, Long excludeUserId, Integer isDeleted);
}
@@ -179,11 +179,20 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
String password = DigestUtils.sha256Hex(sha256HexPwd + sysUserBO.getSalt());
sysUserBO.setPassword(password);
// 生成用户卡号
// 设置组织信息
SysOrgUnits orgUnit = null;
if (ObjectUtils.isNotEmpty(sysUserBO.getOrgIds())) {
// 获取部门code
SysOrgUnits orgUnit = sysOrgUnitsService.getById(sysUserBO.getOrgIds().get(0));
// 获取第一个组织作为主要组织
orgUnit = sysOrgUnitsService.getById(sysUserBO.getOrgIds().get(0));
if (orgUnit != null) {
// 设置组织信息(冗余字段,优化查询性能)
sysUserBO.setOrgId(orgUnit.getId());
sysUserBO.setOrgName(orgUnit.getName());
sysUserBO.setCustomerId(orgUnit.getCustomerId());
log.info("✅ 新增用户设置组织信息: orgId={}, orgName={}, customerId={}",
orgUnit.getId(), orgUnit.getName(), orgUnit.getCustomerId());
// 查询该部门现有用户数
Long userCount = sysUserOrgService.count(new LambdaQueryWrapper<SysUserOrg>()
.eq(SysUserOrg::getOrgId, orgUnit.getId()));
@@ -191,10 +200,14 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
// 生成卡号:部门code-序号
String cardNumber = orgUnit.getCode() + "-" + String.format("%03d", userCount + 1);
sysUserBO.setUserCardNumber(cardNumber);
} else {
log.warn("⚠️ 未找到组织信息: orgId={}", sysUserBO.getOrgIds().get(0));
}
} else {
log.warn("⚠️ 新增用户时未指定组织ID");
}
// 保存用户基本信息
// 保存用户基本信息(先保存,获取用户ID)
boolean saved = super.save(sysUserBO);
if (!saved) {
throw new RuntimeException("保存用户基本信息失败");
@@ -215,6 +228,50 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
throw new RuntimeException("保存用户部门关系失败");
}
}
// 根据用户角色设置用户类型和管理级别
Integer userType = 0; // 默认普通用户
Integer adminLevel = 0; // 默认非管理员
// 获取用户角色信息
List<SysRoleBO> userRoles = sysRoleService.queryRoleListWithUserId(sysUserBO.getId());
if (ObjectUtils.isNotEmpty(userRoles)) {
// 检查是否有管理员角色
boolean hasAdminRole = userRoles.stream()
.anyMatch(role -> role.getIsAdmin() != null && role.getIsAdmin() == 1);
if (hasAdminRole) {
userType = 1; // 管理员
// 根据组织层级设置管理级别
if (orgUnit != null && orgUnit.getLevel() != null) {
switch (orgUnit.getLevel()) {
case 1: // 租户级
adminLevel = 2; // 租户级管理员
break;
case 2: // 部门级
adminLevel = 1; // 部门级管理员
break;
default:
adminLevel = 1; // 默认部门级管理员
break;
}
} else {
adminLevel = 1; // 默认部门级管理员
}
}
}
// 更新用户类型和管理级别
sysUserBO.setUserType(userType);
sysUserBO.setAdminLevel(adminLevel);
boolean typeUpdated = super.updateById(sysUserBO);
if (!typeUpdated) {
log.warn("⚠️ 更新用户类型和管理级别失败");
}
log.info("✅ 新增用户设置用户类型: userType={}, adminLevel={}, 角色数量={}",
userType, adminLevel, userRoles.size());
// 处理设备绑定
if (StringUtils.hasText(sysUserBO.getDeviceSn())) {
@@ -280,17 +337,53 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
Long primaryOrgId = sysUserBO.getOrgIds().get(0); // 取第一个作为主要组织
SysOrgUnits primaryOrg = sysOrgUnitsService.getById(primaryOrgId);
if (primaryOrg != null) {
// 根据用户角色和组织层级重新计算user_type和admin_level
Integer userType = 0; // 默认普通用户
Integer adminLevel = 0; // 默认非管理员
// 获取用户角色信息
List<SysRoleBO> userRoles = sysRoleService.queryRoleListWithUserId(sysUserBO.getId());
if (ObjectUtils.isNotEmpty(userRoles)) {
// 检查是否有管理员角色
boolean hasAdminRole = userRoles.stream()
.anyMatch(role -> role.getIsAdmin() != null && role.getIsAdmin() == 1);
if (hasAdminRole) {
userType = 1; // 管理员
// 根据组织层级设置管理级别
if (primaryOrg.getLevel() != null) {
switch (primaryOrg.getLevel()) {
case 1: // 租户级
adminLevel = 2; // 租户级管理员
break;
case 2: // 部门级
adminLevel = 1; // 部门级管理员
break;
default:
adminLevel = 1; // 默认部门级管理员
break;
}
} else {
adminLevel = 1; // 默认部门级管理员
}
}
}
SysUser userToUpdate = new SysUser();
userToUpdate.setId(sysUserBO.getId());
userToUpdate.setOrgId(primaryOrgId);
userToUpdate.setOrgName(primaryOrg.getName());
userToUpdate.setCustomerId(primaryOrg.getCustomerId()); // 同时更新租户ID
userToUpdate.setUserType(userType); // 更新用户类型
userToUpdate.setAdminLevel(adminLevel); // 更新管理级别
boolean orgInfoUpdated = super.updateById(userToUpdate);
if (!orgInfoUpdated) {
log.warn("⚠️ 同步更新用户组织信息失败: userId={}, orgId={}", sysUserBO.getId(), primaryOrgId);
} else {
log.info("✅ 已同步更新用户组织信息: userId={}, orgId={}, orgName={}, customerId={}",
sysUserBO.getId(), primaryOrgId, primaryOrg.getName(), primaryOrg.getCustomerId());
log.info("✅ 已同步更新用户信息: userId={}, orgId={}, orgName={}, customerId={}, userType={}, adminLevel={}",
sysUserBO.getId(), primaryOrgId, primaryOrg.getName(), primaryOrg.getCustomerId(), userType, adminLevel);
}
}
}
@@ -1698,4 +1791,52 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return this.list(queryWrapper);
}
@Override
public boolean checkPhoneExists(String phone, Long excludeUserId, Integer isDeleted) {
log.info("🔍 检查手机号唯一性,phone: {}, excludeUserId: {}, isDeleted: {}", phone, excludeUserId, isDeleted);
if (phone == null || phone.trim().isEmpty()) {
return false;
}
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getPhone, phone)
.eq(SysUser::getDeleted, isDeleted); // 只检查指定删除状态的用户
// 排除当前用户(编辑时用)
if (excludeUserId != null) {
queryWrapper.ne(SysUser::getId, excludeUserId);
}
long count = this.count(queryWrapper);
boolean exists = count > 0;
log.info("📱 手机号 {} 检查结果: {}", phone, exists ? "已存在" : "可用");
return exists;
}
@Override
public boolean checkDeviceSnExists(String deviceSn, Long excludeUserId, Integer isDeleted) {
log.info("🔍 检查设备序列号唯一性,deviceSn: {}, excludeUserId: {}, isDeleted: {}", deviceSn, excludeUserId, isDeleted);
if (deviceSn == null || deviceSn.trim().isEmpty()) {
return false;
}
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getDeviceSn, deviceSn)
.eq(SysUser::getDeleted, isDeleted); // 只检查指定删除状态的用户
// 排除当前用户(编辑时用)
if (excludeUserId != null) {
queryWrapper.ne(SysUser::getId, excludeUserId);
}
long count = this.count(queryWrapper);
boolean exists = count > 0;
log.info("📱 设备序列号 {} 检查结果: {}", deviceSn, exists ? "已存在" : "可用");
return exists;
}
}