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

632 lines
16 KiB
Markdown
Raw Permalink Normal View History

2026-02-28 17:26:03 +08:00
# 多身份功能 - 低风险渐进式实现方案
> 创建时间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<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
```java
// 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
```java
// 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
```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
<!-- 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. **易于维护** - 代码清晰,逻辑简单
**这是最安全、最快速的实现方案!** ✅