# 用户手机号唯一性完善说明 ## 📋 问题背景 在支持手机号登录后,手机号成为了用户登录的重要凭证。但经过检查发现,虽然**用户添加/修改**功能已有手机号唯一性校验,但在**用户导入**和**用户同步**功能中缺少该校验,可能导致手机号重复,造成登录混乱。 ## ⚠️ 存在的风险 ### 1. 登录冲突 - 多个用户使用相同手机号,导致手机号登录时无法确定登录的是哪个用户 - 可能造成用户登录到错误的账户 ### 2. 数据一致性问题 - 手机号作为唯一标识,出现重复违背了数据设计原则 - 影响基于手机号的业务逻辑 ### 3. 安全隐患 - 手机号重复可能被利用进行身份冒用 - 短信验证码等功能可能发送到错误的用户 ## 🔧 完善方案 ### 1. 用户导入功能增强 **文件**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java` **方法**: `importUser` #### 新增功能 - ✅ 导入新用户前,校验手机号是否已被其他用户使用 - ✅ 更新用户时,校验手机号是否被其他用户(排除自己)占用 - ✅ 同时校验邮箱唯一性 - ✅ 详细的错误提示,包含冲突的用户名 #### 实现代码 ```java // 新增用户时 if (StringUtils.isNotEmpty(user.getPhonenumber())) { SysUser phoneCheck = userMapper.checkPhoneUnique(user.getPhonenumber()); if (StringUtils.isNotNull(phoneCheck)) { failureNum++; failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:手机号码 " + user.getPhonenumber() + " 已被用户 " + phoneCheck.getUserName() + " 使用"); continue; } } // 更新用户时 if (StringUtils.isNotEmpty(user.getPhonenumber())) { SysUser phoneCheck = userMapper.checkPhoneUnique(user.getPhonenumber()); if (StringUtils.isNotNull(phoneCheck) && !phoneCheck.getUserId().equals(u.getUserId())) { failureNum++; failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 更新失败:手机号码 " + user.getPhonenumber() + " 已被用户 " + phoneCheck.getUserName() + " 使用"); continue; } } ``` ### 2. 用户同步功能增强 **文件**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java` #### 2.1 创建新用户 (`createNewUser`) **增强点**: - ✅ 创建前校验手机号唯一性 - ✅ 如手机号重复,记录警告日志并跳过创建 - ✅ 避免因手机号冲突导致同步失败 ```java if (StringUtils.isNotEmpty(dto.getPhonenumber())) { // 校验手机号是否已被其他用户使用 SysUser phoneCheck = sysUserMapper.checkPhoneUnique(dto.getPhonenumber()); if (StringUtils.isNotNull(phoneCheck)) { log.warn("创建用户失败,手机号 {} 已被用户 {} 使用,跳过用户 {}", dto.getPhonenumber(), phoneCheck.getUserName(), dto.getUserName()); return; // 跳过创建 } newUser.setPhonenumber(dto.getPhonenumber()); } ``` #### 2.2 更新现有用户 (`updateExistingUser`) **增强点**: - ✅ 更新前校验手机号唯一性(排除自己) - ✅ 如手机号被占用,记录警告日志并跳过手机号更新 - ✅ 继续更新其他字段,不影响整体同步流程 ```java if (StringUtils.isNotEmpty(dto.getPhonenumber())) { // 校验手机号是否已被其他用户使用(排除自己) SysUser phoneCheck = sysUserMapper.checkPhoneUnique(dto.getPhonenumber()); if (StringUtils.isNotNull(phoneCheck) && !phoneCheck.getUserId().equals(existingUser.getUserId())) { log.warn("更新用户 {} 失败,手机号 {} 已被用户 {} 使用,跳过手机号更新", existingUser.getUserName(), dto.getPhonenumber(), phoneCheck.getUserName()); } else { existingUser.setPhonenumber(dto.getPhonenumber()); } } ``` ## 📊 完善覆盖范围 | 功能模块 | 原状态 | 完善后状态 | 说明 | |---------|--------|-----------|------| | 用户添加 | ✅ 已有校验 | ✅ 保持 | Controller层已实现 | | 用户修改 | ✅ 已有校验 | ✅ 保持 | Controller层已实现 | | 用户导入 | ❌ 缺少校验 | ✅ 已完善 | 新增+更新均校验 | | 用户同步(新增) | ❌ 缺少校验 | ✅ 已完善 | 创建前校验 | | 用户同步(更新) | ❌ 缺少校验 | ✅ 已完善 | 更新前校验 | ## 🔍 校验策略 ### 新增用户 ``` 检查手机号 → 已存在? → 是 → 拒绝创建,记录错误 → 否 → 允许创建 ``` ### 更新用户 ``` 检查手机号 → 已存在? → 是 → 是自己? → 是 → 允许更新 → 否 → 拒绝更新,记录错误 → 否 → 允许更新 ``` ## 📝 修改文件清单 | # | 文件 | 修改内容 | 行数变化 | |---|------|---------|---------| | 1 | `SysUserServiceImpl.java` | 用户导入增加手机号/邮箱唯一性校验 | +50 | | 2 | `UserSyncServiceImpl.java` | 用户同步增加手机号唯一性校验 | +19 | **总计**: +69 行 ## ⚡ 处理策略差异 ### 用户导入 - **策略**: 严格拒绝 - **原因**: 导入是批量操作,需要保证数据质量 - **行为**: 记录详细错误信息,终止该条记录导入 ### 用户同步 - **策略**: 容错处理 - **原因**: 同步是自动化操作,不应因个别字段冲突影响整体 - **行为**: 记录警告日志,跳过冲突字段,继续其他字段同步 ## 🎯 错误提示示例 ### 用户导入 ``` 很抱歉,导入失败!共 2 条数据格式不正确,错误如下: 1、账号 zhangsan 导入失败:手机号码 13800138000 已被用户 lisi 使用 2、账号 wangwu 导入失败:邮箱 test@example.com 已被用户 zhaoliu 使用 ``` ### 用户同步 ```log 2025-10-23 10:30:45 WARN 创建用户失败,手机号 13800138000 已被用户 lisi 使用,跳过用户 zhangsan 2025-10-23 10:30:46 WARN 更新用户 wangwu 失败,手机号 13900139000 已被用户 zhaoliu 使用,跳过手机号更新 ``` ## ✅ 测试场景 ### 1. 用户导入测试 #### 场景1.1: 导入新用户,手机号重复 ``` 输入: 用户名=test001, 手机号=13800138000(已被admin使用) 预期: 导入失败,提示"手机号码 13800138000 已被用户 admin 使用" ``` #### 场景1.2: 导入新用户,手机号唯一 ``` 输入: 用户名=test001, 手机号=13900139999(未使用) 预期: 导入成功 ``` #### 场景1.3: 更新用户,手机号改为其他人的 ``` 输入: 用户名=test001(已存在), 手机号=13800138000(被admin使用) 预期: 更新失败,提示"手机号码 13800138000 已被用户 admin 使用" ``` #### 场景1.4: 更新用户,手机号不变 ``` 输入: 用户名=test001(已存在,手机号=13900139999), 手机号=13900139999 预期: 更新成功 ``` ### 2. 用户同步测试 #### 场景2.1: 同步新用户,手机号重复 ``` 输入: OA用户(手机号=13800138000,已被系统用户使用) 预期: 跳过创建,记录警告日志 ``` #### 场景2.2: 同步更新用户,手机号改为其他人的 ``` 输入: 更新用户手机号为已被占用的号码 预期: 跳过手机号更新,其他字段正常更新,记录警告日志 ``` ## 🔒 数据库约束建议 虽然代码层面已完善校验,但建议在数据库层面也添加约束: ```sql -- 为手机号字段添加唯一索引 CREATE UNIQUE INDEX idx_sys_user_phonenumber ON sys_user(phonenumber) WHERE phonenumber IS NOT NULL AND phonenumber != '' AND del_flag = '0'; ``` **注意**: - 仅对非空、非空字符串、未删除的记录建立唯一约束 - 允许多条记录的手机号为NULL或空字符串 ## 📚 相关文档 - [用户登录支持手机号功能说明](./用户登录支持手机号功能说明.md) - [用户同步功能说明](./用户同步功能说明.md) ## 🎉 总结 通过本次完善: 1. ✅ 全面覆盖了所有用户创建/更新入口的手机号唯一性校验 2. ✅ 避免了手机号重复导致的登录混乱 3. ✅ 提升了数据一致性和系统安全性 4. ✅ 提供了清晰的错误提示和日志记录 5. ✅ 采用了合理的容错策略,保证自动化同步的稳定性 现在系统可以安全地使用手机号作为登录凭证!🎊