peixue-dev/Archive/[一次性]多身份功能-低风险实现方案-2026-02-26.md

16 KiB
Raw Blame History

多身份功能 - 低风险渐进式实现方案

创建时间2026-02-26
核心思想:不破坏现有逻辑,逐步扩展功能


🎯 设计思路

核心原则

  1. 保留现有逻辑 - 不修改现有代码
  2. 增量式开发 - 只添加新功能,不改旧功能
  3. 向后兼容 - 新旧逻辑并存
  4. 可回滚 - 每个阶段都可以独立回滚

关键设计

  • 保留 user.user_type 作为主身份(默认身份)
  • 新增 user_roles 表存储附加身份
  • 现有代码继续使用 user.user_type,不受影响
  • 新功能使用 user_roles

📅 分阶段实施方案

阶段1数据库扩展风险 极低)

工作内容: 只添加新表,不修改现有表

时间: 2小时

风险: 极低(只是添加新表,不影响现有功能)

1.1 创建 user_roles 表

-- 用户角色关联表
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;

验证:

-- 检查数据是否同步成功
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 创建实体类

// 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

// UserRoleMapper.java - 新建文件
@Mapper
public interface UserRoleMapper extends BaseMapper<UserRole> {
    
    /**
     * 获取用户的所有角色
     */
    @Select("SELECT * FROM user_roles WHERE user_id = #{userId} AND status = 1")
    List<UserRole> 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

// UserRoleService.java - 新建文件
@Service
@RequiredArgsConstructor
public class UserRoleService {
    
    private final UserRoleMapper userRoleMapper;
    
    /**
     * 获取用户的所有角色
     */
    public List<String> getUserRoles(Long userId) {
        List<UserRole> roles = userRoleMapper.selectByUserId(userId);
        return roles.stream()
            .map(UserRole::getRoleType)
            .collect(Collectors.toList());
    }
    
    /**
     * 添加角色(不影响主身份)
     */
    @Transactional
    public boolean addRole(Long userId, String roleType) {
        // 检查是否已存在
        LambdaQueryWrapper<UserRole> 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<UserRole> 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<UserRole>()
                .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

// UserRoleController.java - 新建文件
@RestController
@RequestMapping("/api/user/roles")
@RequiredArgsConstructor
public class UserRoleController {
    
    private final UserRoleService userRoleService;
    
    /**
     * 获取当前用户的所有身份
     */
    @GetMapping("/list")
    public Result<List<String>> getUserRoles() {
        Long userId = SecurityUtils.getCurrentUserId();
        List<String> roles = userRoleService.getUserRoles(userId);
        return Result.success(roles);
    }
    
    /**
     * 申请新身份
     */
    @PostMapping("/apply")
    public Result<Void> applyRole(@RequestParam String roleType) {
        Long userId = SecurityUtils.getCurrentUserId();
        userRoleService.addRole(userId, roleType);
        return Result.success();
    }
    
    /**
     * 切换主身份
     */
    @PostMapping("/switch")
    public Result<Void> switchRole(@RequestParam String roleType) {
        Long userId = SecurityUtils.getCurrentUserId();
        userRoleService.switchPrimaryRole(userId, roleType);
        return Result.success();
    }
}

影响: 只添加新接口,现有功能不受影响


阶段3前端集成风险 低)

工作内容: 添加新功能,不修改现有逻辑

时间: 1天

风险: 低(新增功能,不影响现有页面)

3.1 修改 store/user.js

// 只添加新方法,不修改现有方法
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 修改登录逻辑

// 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 优化身份切换组件

<!-- pages/user/components/RoleSelector.vue - 新建组件 -->
<template>
  <view class="role-selector">
    <view class="current-role">
      <text class="label">当前身份</text>
      <text class="value">{{ currentRoleName }}</text>
    </view>
    
    <!-- 多身份时显示切换选项 -->
    <view v-if="hasMultipleRoles" class="role-list">
      <view class="section-title">切换身份</view>
      <view 
        v-for="role in availableRoles" 
        :key="role.value"
        class="role-item"
        :class="{ active: role.value === currentRole }"
        @click="switchRole(role.value)"
      >
        <text class="role-icon">{{ role.icon }}</text>
        <text class="role-name">{{ role.label }}</text>
        <text v-if="role.value === currentRole" class="check">✓</text>
      </view>
    </view>
    
    <!-- 申请新身份 -->
    <view class="apply-section">
      <view class="section-title">申请新身份</view>
      <button class="apply-btn" @click="showApplyModal">+ 申请其他身份</button>
    </view>
  </view>
</template>

<script>
import { useUserStore } from '@/store/user'

export default {
  setup() {
    const userStore = useUserStore()
    
    return {
      currentRole: computed(() => userStore.currentRole),
      currentRoleName: computed(() => userStore.roleName),
      hasMultipleRoles: computed(() => userStore.hasMultipleRoles),
      availableRoles: computed(() => userStore.availableRoles),
      
      async switchRole(roleType) {
        if (roleType === userStore.currentRole) return
        
        uni.showModal({
          title: '确认切换',
          content: `确定要切换到${getRoleName(roleType)}身份吗?`,
          success: async (res) => {
            if (res.confirm) {
              await userStore.switchRoleWithApi(roleType)
            }
          }
        })
      },
      
      showApplyModal() {
        uni.navigateTo({
          url: '/pages/user/apply-role'
        })
      }
    }
  }
}
</script>

影响: 只添加新组件和功能,现有页面不受影响


🎯 实施步骤

第1天数据库 + 后端

  1. 执行数据库脚本30分钟
  2. 创建实体类和Mapper1小时
  3. 创建Service和Controller2小时
  4. 测试后端接口1小时
  5. 验证数据一致性30分钟

第2天前端集成

  1. 修改 store/user.js1小时
  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. 易于维护 - 代码清晰,逻辑简单

这是最安全、最快速的实现方案!