编辑 | blame | 历史 | 原始文档

用户手机号唯一性完善说明

📋 问题背景

在支持手机号登录后,手机号成为了用户登录的重要凭证。但经过检查发现,虽然**用户添加/修改**功能已有手机号唯一性校验,但在**用户导入**和**用户同步**功能中缺少该校验,可能导致手机号重复,造成登录混乱。

⚠️ 存在的风险

1. 登录冲突

  • 多个用户使用相同手机号,导致手机号登录时无法确定登录的是哪个用户
  • 可能造成用户登录到错误的账户

2. 数据一致性问题

  • 手机号作为唯一标识,出现重复违背了数据设计原则
  • 影响基于手机号的业务逻辑

3. 安全隐患

  • 手机号重复可能被利用进行身份冒用
  • 短信验证码等功能可能发送到错误的用户

🔧 完善方案

1. 用户导入功能增强

文件: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

方法: importUser

新增功能

  • ✅ 导入新用户前,校验手机号是否已被其他用户使用
  • ✅ 更新用户时,校验手机号是否被其他用户(排除自己)占用
  • ✅ 同时校验邮箱唯一性
  • ✅ 详细的错误提示,包含冲突的用户名

实现代码

// 新增用户时
if (StringUtils.isNotEmpty(user.getPhonenumber()))
{
    SysUser phoneCheck = userMapper.checkPhoneUnique(user.getPhonenumber());
    if (StringUtils.isNotNull(phoneCheck))
    {
        failureNum++;
        failureMsg.append("<br/>" + 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("<br/>" + failureNum + "、账号 " + user.getUserName() 
            + " 更新失败:手机号码 " + user.getPhonenumber() 
            + " 已被用户 " + phoneCheck.getUserName() + " 使用");
        continue;
    }
}

2. 用户同步功能增强

文件: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java

2.1 创建新用户 (createNewUser)

增强点:
- ✅ 创建前校验手机号唯一性
- ✅ 如手机号重复,记录警告日志并跳过创建
- ✅ 避免因手机号冲突导致同步失败

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)

增强点:
- ✅ 更新前校验手机号唯一性(排除自己)
- ✅ 如手机号被占用,记录警告日志并跳过手机号更新
- ✅ 继续更新其他字段,不影响整体同步流程

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 使用

用户同步

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: 同步更新用户,手机号改为其他人的

输入: 更新用户手机号为已被占用的号码
预期: 跳过手机号更新,其他字段正常更新,记录警告日志

🔒 数据库约束建议

虽然代码层面已完善校验,但建议在数据库层面也添加约束:

-- 为手机号字段添加唯一索引
CREATE UNIQUE INDEX idx_sys_user_phonenumber 
ON sys_user(phonenumber) 
WHERE phonenumber IS NOT NULL AND phonenumber != '' AND del_flag = '0';

注意:
- 仅对非空、非空字符串、未删除的记录建立唯一约束
- 允许多条记录的手机号为NULL或空字符串

📚 相关文档

🎉 总结

通过本次完善:
1. ✅ 全面覆盖了所有用户创建/更新入口的手机号唯一性校验
2. ✅ 避免了手机号重复导致的登录混乱
3. ✅ 提升了数据一致性和系统安全性
4. ✅ 提供了清晰的错误提示和日志记录
5. ✅ 采用了合理的容错策略,保证自动化同步的稳定性

现在系统可以安全地使用手机号作为登录凭证!🎊