# 多身份功能 - 低风险渐进式实现方案 > 创建时间:2026-02-26 > 核心思想:不破坏现有逻辑,逐步扩展功能 --- ## 🎯 设计思路 ### 核心原则 1. **保留现有逻辑** - 不修改现有代码 2. **增量式开发** - 只添加新功能,不改旧功能 3. **向后兼容** - 新旧逻辑并存 4. **可回滚** - 每个阶段都可以独立回滚 ### 关键设计 - 保留 `user.user_type` 作为**主身份**(默认身份) - 新增 `user_roles` 表存储**附加身份** - 现有代码继续使用 `user.user_type`,不受影响 - 新功能使用 `user_roles` 表 --- ## 📅 分阶段实施方案 ### 阶段1:数据库扩展(风险:⭐ 极低) **工作内容:** 只添加新表,不修改现有表 **时间:** 2小时 **风险:** ⭐ 极低(只是添加新表,不影响现有功能) #### 1.1 创建 user_roles 表 ```sql -- 用户角色关联表 CREATE TABLE IF NOT EXISTS `user_roles` ( `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', `user_id` BIGINT NOT NULL COMMENT '用户ID', `role_type` VARCHAR(50) NOT NULL COMMENT '角色类型:teacher/manager/distributor/provider/parent', `is_primary` TINYINT DEFAULT 0 COMMENT '是否主身份:0=否,1=是', `status` TINYINT DEFAULT 1 COMMENT '状态:0=禁用,1=启用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX `idx_user_id` (`user_id`), INDEX `idx_role_type` (`role_type`), UNIQUE KEY `uk_user_role` (`user_id`, `role_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表'; -- 初始化数据:将现有用户的主身份同步到 user_roles 表 INSERT INTO `user_roles` (`user_id`, `role_type`, `is_primary`, `status`) SELECT `id` as user_id, `user_type` as role_type, 1 as is_primary, 1 as status FROM `user` WHERE `user_type` IS NOT NULL ON DUPLICATE KEY UPDATE `is_primary` = 1; ``` **验证:** ```sql -- 检查数据是否同步成功 SELECT u.id, u.phone, u.user_type as primary_role, GROUP_CONCAT(ur.role_type) as all_roles FROM user u LEFT JOIN user_roles ur ON u.id = ur.user_id GROUP BY u.id LIMIT 10; ``` **影响:** ✅ 无影响,只是添加新表 --- ### 阶段2:后端支持(风险:⭐⭐ 低) **工作内容:** 添加新接口,不修改现有接口 **时间:** 1天 **风险:** ⭐⭐ 低(新增接口,不影响现有功能) #### 2.1 创建实体类 ```java // UserRole.java - 新建文件 @Data @TableName("user_roles") public class UserRole { @TableId(type = IdType.AUTO) private Long id; private Long userId; private String roleType; private Integer isPrimary; private Integer status; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; } ``` #### 2.2 创建 Mapper ```java // UserRoleMapper.java - 新建文件 @Mapper public interface UserRoleMapper extends BaseMapper { /** * 获取用户的所有角色 */ @Select("SELECT * FROM user_roles WHERE user_id = #{userId} AND status = 1") List selectByUserId(Long userId); /** * 获取用户的主身份 */ @Select("SELECT * FROM user_roles WHERE user_id = #{userId} AND is_primary = 1 AND status = 1") UserRole selectPrimaryRole(Long userId); } ``` #### 2.3 创建 Service ```java // UserRoleService.java - 新建文件 @Service @RequiredArgsConstructor public class UserRoleService { private final UserRoleMapper userRoleMapper; /** * 获取用户的所有角色 */ public List getUserRoles(Long userId) { List roles = userRoleMapper.selectByUserId(userId); return roles.stream() .map(UserRole::getRoleType) .collect(Collectors.toList()); } /** * 添加角色(不影响主身份) */ @Transactional public boolean addRole(Long userId, String roleType) { // 检查是否已存在 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserRole::getUserId, userId) .eq(UserRole::getRoleType, roleType); if (userRoleMapper.selectCount(wrapper) > 0) { throw new BusinessException("该身份已存在"); } // 添加新角色 UserRole userRole = new UserRole(); userRole.setUserId(userId); userRole.setRoleType(roleType); userRole.setIsPrimary(0); // 附加身份 userRole.setStatus(1); return userRoleMapper.insert(userRole) > 0; } /** * 切换主身份 */ @Transactional public boolean switchPrimaryRole(Long userId, String roleType) { // 检查该角色是否存在 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserRole::getUserId, userId) .eq(UserRole::getRoleType, roleType) .eq(UserRole::getStatus, 1); UserRole targetRole = userRoleMapper.selectOne(wrapper); if (targetRole == null) { throw new BusinessException("该身份不存在"); } // 取消所有主身份标记 userRoleMapper.update(null, new LambdaUpdateWrapper() .eq(UserRole::getUserId, userId) .set(UserRole::getIsPrimary, 0) ); // 设置新的主身份 targetRole.setIsPrimary(1); userRoleMapper.updateById(targetRole); // 同步更新 user 表的 user_type(保持兼容) User user = new User(); user.setId(userId); user.setRole(roleType); userMapper.updateById(user); return true; } } ``` #### 2.4 创建 Controller ```java // UserRoleController.java - 新建文件 @RestController @RequestMapping("/api/user/roles") @RequiredArgsConstructor public class UserRoleController { private final UserRoleService userRoleService; /** * 获取当前用户的所有身份 */ @GetMapping("/list") public Result> getUserRoles() { Long userId = SecurityUtils.getCurrentUserId(); List roles = userRoleService.getUserRoles(userId); return Result.success(roles); } /** * 申请新身份 */ @PostMapping("/apply") public Result applyRole(@RequestParam String roleType) { Long userId = SecurityUtils.getCurrentUserId(); userRoleService.addRole(userId, roleType); return Result.success(); } /** * 切换主身份 */ @PostMapping("/switch") public Result switchRole(@RequestParam String roleType) { Long userId = SecurityUtils.getCurrentUserId(); userRoleService.switchPrimaryRole(userId, roleType); return Result.success(); } } ``` **影响:** ✅ 只添加新接口,现有功能不受影响 --- ### 阶段3:前端集成(风险:⭐⭐ 低) **工作内容:** 添加新功能,不修改现有逻辑 **时间:** 1天 **风险:** ⭐⭐ 低(新增功能,不影响现有页面) #### 3.1 修改 store/user.js ```javascript // 只添加新方法,不修改现有方法 export const useUserStore = defineStore('user', { state: () => ({ token: uni.getStorageSync('token') || '', userInfo: uni.getStorageSync('userInfo') || null, isLogin: false, currentRole: uni.getStorageSync('currentRole') || 'user', // ✅ 新增:所有身份列表 allRoles: uni.getStorageSync('allRoles') || [] }), getters: { // 现有 getters 保持不变... // ✅ 新增:是否有多个身份 hasMultipleRoles: (state) => state.allRoles.length > 1, // ✅ 新增:可切换的身份列表 availableRoles: (state) => { return state.allRoles.map(role => ({ value: role, label: getRoleName(role), icon: getRoleIcon(role) })) } }, actions: { // 现有 actions 保持不变... // ✅ 新增:加载所有身份 async loadAllRoles() { try { const res = await uni.request({ url: '/api/user/roles/list', method: 'GET', header: { Authorization: this.token } }) if (res.data.code === 200) { this.allRoles = res.data.data uni.setStorageSync('allRoles', res.data.data) } } catch (error) { console.error('加载身份列表失败:', error) } }, // ✅ 新增:切换身份(调用后端API) async switchRoleWithApi(roleType) { try { const res = await uni.request({ url: '/api/user/roles/switch', method: 'POST', data: { roleType }, header: { Authorization: this.token } }) if (res.data.code === 200) { this.currentRole = roleType uni.setStorageSync('currentRole', roleType) // 刷新页面 uni.reLaunch({ url: '/pages/index/index' }) uni.showToast({ title: '切换成功', icon: 'success' }) } else { uni.showToast({ title: res.data.message || '切换失败', icon: 'none' }) } } catch (error) { console.error('切换身份失败:', error) uni.showToast({ title: '切换失败', icon: 'none' }) } }, // ✅ 新增:申请新身份 async applyNewRole(roleType) { try { const res = await uni.request({ url: '/api/user/roles/apply', method: 'POST', data: { roleType }, header: { Authorization: this.token } }) if (res.data.code === 200) { // 重新加载身份列表 await this.loadAllRoles() uni.showToast({ title: '申请成功', icon: 'success' }) return true } else { uni.showToast({ title: res.data.message || '申请失败', icon: 'none' }) return false } } catch (error) { console.error('申请身份失败:', error) uni.showToast({ title: '申请失败', icon: 'none' }) return false } } } }) // 辅助函数 function getRoleName(role) { const roleMap = { user: '家长', parent: '家长', teacher: '陪伴员', manager: '管理师', distributor: '分销员', serviceProvider: '服务商' } return roleMap[role] || '未知' } function getRoleIcon(role) { const iconMap = { user: '👨‍👩‍👧', parent: '👨‍👩‍👧', teacher: '👨‍🏫', manager: '👔', distributor: '💼', serviceProvider: '🏢' } return iconMap[role] || '❓' } ``` #### 3.2 修改登录逻辑 ```javascript // pages/login/index.vue async handleLogin() { try { // 现有登录逻辑... const res = await loginApi.login(this.form) if (res.code === 200) { const userStore = useUserStore() userStore.setToken(res.data.token) userStore.setUserInfo(res.data.userInfo) userStore.setRole(res.data.userInfo.role) // ✅ 新增:加载所有身份 await userStore.loadAllRoles() // 跳转首页... } } catch (error) { // 错误处理... } } ``` #### 3.3 优化身份切换组件 ```vue ``` **影响:** ✅ 只添加新组件和功能,现有页面不受影响 --- ## 🎯 实施步骤 ### 第1天:数据库 + 后端 1. ✅ 执行数据库脚本(30分钟) 2. ✅ 创建实体类和Mapper(1小时) 3. ✅ 创建Service和Controller(2小时) 4. ✅ 测试后端接口(1小时) 5. ✅ 验证数据一致性(30分钟) ### 第2天:前端集成 1. ✅ 修改 store/user.js(1小时) 2. ✅ 修改登录逻辑(30分钟) 3. ✅ 创建身份切换组件(2小时) 4. ✅ 创建申请身份页面(1小时) 5. ✅ 联调测试(1.5小时) ### 第3天:测试上线 1. ✅ 完整功能测试(2小时) 2. ✅ 边界情况测试(1小时) 3. ✅ 性能测试(1小时) 4. ✅ 上线部署(1小时) **总工作量:3天** --- ## ✅ 优势分析 ### 1. 风险极低 ⭐ - 不修改现有代码,只添加新功能 - 现有逻辑继续使用 `user.user_type` - 新功能使用 `user_roles` 表 - 两套逻辑并存,互不影响 ### 2. 可回滚 🔄 - 每个阶段都可以独立回滚 - 删除 `user_roles` 表即可完全回滚 - 不影响现有数据 ### 3. 向后兼容 ✅ - 保留 `user.user_type` 字段 - 切换主身份时同步更新 `user.user_type` - 现有代码无需修改 ### 4. 性能优化 ⚡ - 使用索引优化查询 - 可以添加缓存(Redis) - 不影响现有接口性能 ### 5. 易于扩展 🚀 - 未来可以添加更多身份 - 可以添加身份审核流程 - 可以添加身份权限管理 --- ## 🧪 测试清单 ### 功能测试 - [ ] 用户登录后能看到所有身份 - [ ] 切换身份功能正常 - [ ] 申请新身份功能正常 - [ ] 主身份标记正确 - [ ] user.user_type 同步更新 ### 兼容性测试 - [ ] 现有功能不受影响 - [ ] 单身份用户正常使用 - [ ] 多身份用户正常使用 - [ ] 角色权限验证正常 ### 性能测试 - [ ] 查询性能正常 - [ ] 切换身份响应快 - [ ] 不影响现有接口性能 ### 边界测试 - [ ] 重复申请身份的处理 - [ ] 切换到不存在的身份 - [ ] 并发切换身份 - [ ] 数据一致性验证 --- ## 📊 风险对比 | 方案 | 风险 | 工作量 | 影响范围 | |------|------|--------|----------| | 完全重构 | 🔴🔴🔴🔴 高 | 7-10天 | 全部代码 | | **渐进式方案** | ⭐ 极低 | 3天 | 只添加新功能 | --- ## 🎯 总结 这个方案的核心优势: 1. **不破坏现有逻辑** - 保留 `user.user_type`,现有代码不受影响 2. **增量式开发** - 只添加新表和新接口 3. **风险可控** - 每个阶段都可以独立测试和回滚 4. **快速上线** - 3天即可完成 5. **易于维护** - 代码清晰,逻辑简单 **这是最安全、最快速的实现方案!** ✅