diff --git a/cleanup_orphan_roles.sql b/cleanup_orphan_roles.sql new file mode 100644 index 0000000..03ff2c7 --- /dev/null +++ b/cleanup_orphan_roles.sql @@ -0,0 +1,40 @@ +-- ============================================================ +-- 清理孤儿业务角色(指向不存在的应用) +-- 场景:角色授权页 RoleModuleTree?roleId=2 报 "角色所属应用不存在: 3" +-- 根因:sys_app_role 中存在 all_app_flag=0 的普通角色,其 app_id 指向 +-- application_info 中已不存在的应用(应用被删,当时无级联清理)。 +-- 说明:本脚本只读诊断 + 备份 + 清理,请先执行第 1 步确认,再执行第 2/3 步。 +-- ============================================================ + +-- 1) 诊断:列出全部"指向不存在应用"的普通角色(孤儿角色) +SELECT r.id AS role_id, + r.name AS role_name, + r.app_id AS missing_app_id +FROM sys_app_role r +WHERE r.all_app_flag = 0 + AND r.app_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM application_info a WHERE a.id = r.app_id + ); + +-- 2) 备份(执行清理前先保留,便于回滚) +CREATE TABLE IF NOT EXISTS _bak_orphan_role LIKE sys_app_role; +INSERT INTO _bak_orphan_role +SELECT * FROM sys_app_role r +WHERE r.all_app_flag = 0 + AND r.app_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM application_info a WHERE a.id = r.app_id + ); + +CREATE TABLE IF NOT EXISTS _bak_orphan_role_rel LIKE sys_app_role_module_rel; +INSERT INTO _bak_orphan_role_rel +SELECT * FROM sys_app_role_module_rel +WHERE role_id IN (SELECT id FROM _bak_orphan_role); + +-- 3) 清理:先删模块授权关系,再删孤儿角色本身 +DELETE FROM sys_app_role_module_rel +WHERE role_id IN (SELECT id FROM _bak_orphan_role); + +DELETE FROM sys_app_role +WHERE id IN (SELECT id FROM _bak_orphan_role); diff --git a/src/main/java/com/yunhai/userbase/controller/api/IdentityController.java b/src/main/java/com/yunhai/userbase/controller/api/IdentityController.java new file mode 100644 index 0000000..4c3c669 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/controller/api/IdentityController.java @@ -0,0 +1,62 @@ +package com.yunhai.userbase.controller.api; + +import com.yunhai.platform.utils.model.BoaUser; +import com.yunhai.userbase.config.Config; +import com.yunhai.userbase.model.identity.IdentityCurrentVo; +import com.yunhai.userbase.model.identity.IdentityOptionVo; +import com.yunhai.userbase.model.identity.IdentitySwitchRequest; +import com.yunhai.userbase.service.impl.IdentityService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 用户身份(平台身份角色 × 学校)管理接口。

+ * 走 CAS 会话路线({@code /{platform}/identity/}),当前用户从 CAS 会话注入的 + * {@link BoaUser#getId()} 取,不接受外部传入 userId(防越权)。
+ * 三个端点对应「切换身份」业务:
+ * ① {@code GET current} 当前激活身份(无切换记录则兜底主身份);
+ * ② {@code GET available} 全部可选身份清单;
+ * ③ {@code POST switch} 切换到指定 (roleId, schoolId),服务端校验属主后写入当前激活状态。 + * + * @author chenx + * @date 2026/8/18 + */ +@RestController +@RequestMapping(Config.API_PATH + "/identity/") +public class IdentityController { + + @Autowired + private IdentityService identityService; + + /** + * 当前激活身份。 + */ + @GetMapping("current") + public IdentityCurrentVo current(BoaUser authUserWithDept) { + return identityService.getCurrent(authUserWithDept.getId()); + } + + /** + * 全部可选身份(正常态),供「切换身份」页渲染。 + */ + @GetMapping("available") + public List available(BoaUser authUserWithDept) { + return identityService.listAvailable(authUserWithDept.getId()); + } + + /** + * 切换身份:校验属主后在 sys_user_current_role 写当前激活状态。 + */ + @PostMapping("switch") + public IdentityCurrentVo switchIdentity(BoaUser authUserWithDept, + @RequestBody @Validated IdentitySwitchRequest request) { + return identityService.switchIdentity(authUserWithDept.getId(), request); + } +} diff --git a/src/main/java/com/yunhai/userbase/entity/SysUserRoleRel.java b/src/main/java/com/yunhai/userbase/entity/SysUserRoleRel.java new file mode 100644 index 0000000..07cb6a6 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/entity/SysUserRoleRel.java @@ -0,0 +1,86 @@ +package com.yunhai.userbase.entity; + +import com.yunhai.userbase.common.converter.StatusEnumConverter; +import com.yunhai.userbase.enums.StatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * @author chenx + * @date 2026/8/18 + * @description 用户-SysRoleEnum角色-学校 三元关联(平台身份角色的权威源)。 + *

区别于 {@link SysUserAppRoleRel}(业务权限角色):本表表达"用户在哪个学校下激活哪个平台身份角色" + * (如"刘文娟在 A 学校是老师"),角色取值对应 {@link com.yunhai.userbase.enums.SysRoleEnum} 的 key。

+ *

不继承 BaseEntity:高频读写关系表,自定义字段更轻量,与 {@link SysUserGroupRel} 风格一致。

+ */ +@Data +@NoArgsConstructor +@Entity +@Table( + name = "sys_user_role_rel", + uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "roleId", "schoolId"}), + indexes = { + @Index(columnList = "userId"), + @Index(columnList = "schoolId"), + @Index(columnList = "roleId") + } +) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysUserRoleRel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @NotNull(groups = Update.class) + private Integer id; + + /** + * 用户ID + */ + @Column(nullable = false) + private Integer userId; + + /** + * 角色ID(SysRoleEnum.key,1-7) + */ + @Column(nullable = false) + private Integer roleId; + + /** + * 学校组ID(=type=SCHOOL_GROUP 的 sys_group.id);区域/学段/根直属用户填 0 + */ + @Column(nullable = false) + private Integer schoolId; + + /** + * 是否主身份标记:每 userId 最多 1 条 isMain=1(应用层校验,不靠 uk) + */ + @Column(nullable = false) + private Integer isMain = 0; + + /** + * 排序号:越小越靠前 + */ + @Column(name = "sort_no", nullable = false) + private Integer sortNo = 0; + + /** + * 状态:0=禁用 1=正常(StatusEnum) + */ + @Convert(converter = StatusEnumConverter.class) + @Column(nullable = false) + private StatusEnum status = StatusEnum.NORMAL; + + @Column(name = "create_time", nullable = false, updatable = false) + private LocalDateTime createTime; + + @Column(name = "update_time", nullable = false) + private LocalDateTime updateTime; + + public @interface Update { + } +} diff --git a/src/main/java/com/yunhai/userbase/model/identity/IdentityCurrentVo.java b/src/main/java/com/yunhai/userbase/model/identity/IdentityCurrentVo.java new file mode 100644 index 0000000..60adcaa --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/identity/IdentityCurrentVo.java @@ -0,0 +1,32 @@ +package com.yunhai.userbase.model.identity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 当前激活身份(来自 {@code sys_user_current_role};无切换记录时兜底主身份)。
+ * + * @author chenx + * @date 2026/8/18 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IdentityCurrentVo { + + /** 当前激活角色ID(SysRoleEnum.key) */ + private Integer roleId; + + /** 当前激活角色名 */ + private String roleName; + + /** 当前激活学校ID(=学校组 sys_group.id;无学校时为 0) */ + private Integer schoolId; + + /** 当前激活学校名 */ + private String schoolName; + + /** 是否兜底(无主动切换记录时为 true,提示前端可引导用户显式切换) */ + private Boolean fallback; +} diff --git a/src/main/java/com/yunhai/userbase/model/identity/IdentityOptionVo.java b/src/main/java/com/yunhai/userbase/model/identity/IdentityOptionVo.java new file mode 100644 index 0000000..c06e657 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/identity/IdentityOptionVo.java @@ -0,0 +1,33 @@ +package com.yunhai.userbase.model.identity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户可选身份(一条 = 某学校下的某平台角色)。
+ * 数据源:{@code sys_user_role_rel}(平台身份角色权威源)。
+ * + * @author chenx + * @date 2026/8/18 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IdentityOptionVo { + + /** 角色ID(SysRoleEnum.key,1-7) */ + private Integer roleId; + + /** 角色名(老师/校领导/学生…,查枚举快取) */ + private String roleName; + + /** 学校ID(=学校组 sys_group.id;区域/学段/直属无学校时为 0) */ + private Integer schoolId; + + /** 学校名(冗余,免 join sys_group) */ + private String schoolName; + + /** 是否主身份(isMain=1) */ + private Boolean isMain; +} diff --git a/src/main/java/com/yunhai/userbase/model/identity/IdentitySwitchRequest.java b/src/main/java/com/yunhai/userbase/model/identity/IdentitySwitchRequest.java new file mode 100644 index 0000000..f7f88d7 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/identity/IdentitySwitchRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.userbase.model.identity; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 切换身份入参:指定目标学校与该学校下的角色。
+ * 服务端校验 (userId, roleId, schoolId) 在 {@code sys_user_role_rel} 中存在且正常态,
+ * 不存在则拒绝(防止越权切换到非授权身份)。
+ * + * @author chenx + * @date 2026/8/18 + */ +@Data +public class IdentitySwitchRequest { + + /** 目标角色ID(SysRoleEnum.key) */ + @NotNull(message = "角色ID不能为空") + private Integer roleId; + + /** 目标学校ID(=学校组 sys_group.id;无学校维度传 0) */ + @NotNull(message = "学校ID不能为空") + private Integer schoolId; +} diff --git a/src/main/java/com/yunhai/userbase/model/sysuser/SysUserMoveRequest.java b/src/main/java/com/yunhai/userbase/model/sysuser/SysUserMoveRequest.java new file mode 100644 index 0000000..abad1a7 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/sysuser/SysUserMoveRequest.java @@ -0,0 +1,24 @@ +package com.yunhai.userbase.model.sysuser; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @description 开放接口:移动用户到指定组请求(语义同后台「调整组」——覆盖式把用户组归属替换为目标组,目标设为主组)。 + */ +@Data +public class SysUserMoveRequest { + + /** 待移动用户ID。 */ + @NotNull(message = "用户ID不能为空") + private Integer userId; + + /** 目标组ID(移动后的主归属组,必须存在)。 */ + @NotNull(message = "目标组ID不能为空") + private Integer groupId; + + /** 学校组ID:限定移动范围,仅允许将用户在同一学校(组)范围内移动。 */ + @NotNull(message = "学校ID不能为空") + private Integer schoolId; +} diff --git a/src/main/java/com/yunhai/userbase/repository/SysUserCurrentRoleRepository.java b/src/main/java/com/yunhai/userbase/repository/SysUserCurrentRoleRepository.java new file mode 100644 index 0000000..bd79400 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/repository/SysUserCurrentRoleRepository.java @@ -0,0 +1,17 @@ +package com.yunhai.userbase.repository; + +import com.yunhai.userbase.entity.SysUserCurrentRole; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.Optional; + +/** + * @author chenx + * @date 2026/8/18 + * @description 用户当前激活身份仓储(一对一:user_id 唯一) + */ +public interface SysUserCurrentRoleRepository extends JpaRepository, JpaSpecificationExecutor { + + Optional findByUserId(Integer userId); +} diff --git a/src/main/java/com/yunhai/userbase/repository/SysUserRoleRelRepository.java b/src/main/java/com/yunhai/userbase/repository/SysUserRoleRelRepository.java new file mode 100644 index 0000000..44b7af7 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/repository/SysUserRoleRelRepository.java @@ -0,0 +1,39 @@ +package com.yunhai.userbase.repository; + +import com.yunhai.userbase.entity.SysUserRoleRel; +import com.yunhai.userbase.enums.StatusEnum; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * @author chenx + * @date 2026/8/18 + * @description 用户-SysRoleEnum角色-学校 关联仓储(平台身份角色权威源) + */ +public interface SysUserRoleRelRepository extends JpaRepository, JpaSpecificationExecutor { + + /** 用户全部可选身份(含禁用态由调用方过滤) */ + List findByUserId(Integer userId); + + /** 用户某状态下的全部可选身份 */ + List findByUserIdAndStatus(Integer userId, StatusEnum status); + + /** 用户主身份(isMain=1),最多 1 条 */ + Optional findByUserIdAndIsMain(Integer userId, Integer isMain); + + /** 用户在某学校下、某状态下的全部角色关联 */ + List findByUserIdAndSchoolIdAndStatus(Integer userId, Integer schoolId, StatusEnum status); + + /** 用户所有正常态的 roleId 去重列表(用于 BoaUser.roleIds) */ + @Query("SELECT DISTINCT r.roleId FROM SysUserRoleRel r WHERE r.userId = :userId AND r.status = :status") + List findDistinctRoleIdsByUserIdAndStatus(@Param("userId") Integer userId, @Param("status") StatusEnum status); + + /** 用户所有正常态的 schoolId 去重列表(用于 BoaUser.schools,schoolId>0 的过滤在调用方做) */ + @Query("SELECT DISTINCT r.schoolId FROM SysUserRoleRel r WHERE r.userId = :userId AND r.status = :status") + List findDistinctSchoolIdsByUserIdAndStatus(@Param("userId") Integer userId, @Param("status") StatusEnum status); +} diff --git a/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java b/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java new file mode 100644 index 0000000..ecbdd3e --- /dev/null +++ b/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java @@ -0,0 +1,146 @@ +package com.yunhai.userbase.service.impl; + +import com.yunhai.platform.utils.exception.DException; +import com.yunhai.userbase.entity.SysGroup; +import com.yunhai.userbase.entity.SysUserCurrentRole; +import com.yunhai.userbase.entity.SysUserRoleRel; +import com.yunhai.userbase.enums.IEnum; +import com.yunhai.userbase.enums.StatusEnum; +import com.yunhai.userbase.enums.SysRoleEnum; +import com.yunhai.userbase.model.identity.IdentityCurrentVo; +import com.yunhai.userbase.model.identity.IdentityOptionVo; +import com.yunhai.userbase.model.identity.IdentitySwitchRequest; +import com.yunhai.userbase.repository.SysGroupRepository; +import com.yunhai.userbase.repository.SysUserCurrentRoleRepository; +import com.yunhai.userbase.repository.SysUserRoleRelRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 用户身份服务:管理「平台身份角色 × 学校」维度的当前激活状态与可选清单。

+ * 身份 = (userId, roleId, schoolId) 三元组,权威源为 {@code sys_user_role_rel};
+ * 当前激活状态存 {@code sys_user_current_role}(1:1,每用户一条)。
+ * 本服务不处理业务权限角色(sys_app_role),那套与学校维度无关。 + * + * @author chenx + * @date 2026/8/18 + */ +@Service +public class IdentityService { + + @Autowired + private SysUserRoleRelRepository sysUserRoleRelRepository; + @Autowired + private SysUserCurrentRoleRepository sysUserCurrentRoleRepository; + @Autowired + private SysGroupRepository sysGroupRepository; + + /** + * 用户全部可选身份(正常态),用于「切换身份」页渲染。 + */ + public List listAvailable(Integer userId) { + List rels = sysUserRoleRelRepository + .findByUserIdAndStatus(userId, StatusEnum.NORMAL); + List result = new ArrayList<>(rels.size()); + for (SysUserRoleRel rel : rels) { + IdentityOptionVo vo = new IdentityOptionVo(); + vo.setRoleId(rel.getRoleId()); + vo.setRoleName(roleName(rel.getRoleId())); + vo.setSchoolId(rel.getSchoolId()); + vo.setSchoolName(schoolName(rel.getSchoolId())); + vo.setIsMain(rel.getIsMain() != null && rel.getIsMain() == 1); + result.add(vo); + } + return result; + } + + /** + * 当前激活身份:优先读 sys_user_current_role;无切换记录时兜底主身份(isMain=1)。 + */ + public IdentityCurrentVo getCurrent(Integer userId) { + Optional currentOpt = sysUserCurrentRoleRepository.findByUserId(userId); + IdentityCurrentVo vo = new IdentityCurrentVo(); + if (currentOpt.isPresent()) { + SysUserCurrentRole current = currentOpt.get(); + vo.setRoleId(current.getRoleId()); + vo.setRoleName(roleName(current.getRoleId())); + vo.setSchoolId(current.getSchoolId()); + vo.setSchoolName(current.getSchoolName()); + vo.setFallback(false); + return vo; + } + // 兜底:主身份整条记录(isMain=1) + SysUserRoleRel mainRel = sysUserRoleRelRepository.findByUserIdAndIsMain(userId, 1).orElse(null); + if (mainRel != null) { + vo.setRoleId(mainRel.getRoleId()); + vo.setRoleName(roleName(mainRel.getRoleId())); + vo.setSchoolId(mainRel.getSchoolId()); + vo.setSchoolName(schoolName(mainRel.getSchoolId())); + vo.setFallback(true); + return vo; + } + // 连主身份都没有(测试期初数据尚未建立):返回空对象,前端引导创建身份 + vo.setFallback(true); + return vo; + } + + /** + * 切换身份:校验 (userId, roleId, schoolId) 在 sys_user_role_rel 正常态中存在, + * 再 UPSERT sys_user_current_role(按 userId 唯一)。 + */ + public IdentityCurrentVo switchIdentity(Integer userId, IdentitySwitchRequest request) { + Integer roleId = request.getRoleId(); + Integer schoolId = request.getSchoolId(); + // 校验目标身份确属该用户且正常态,防越权切换 + SysUserRoleRel rel = sysUserRoleRelRepository + .findByUserIdAndSchoolIdAndStatus(userId, schoolId, StatusEnum.NORMAL) + .stream() + .filter(r -> Objects.equals(r.getRoleId(), roleId)) + .findFirst() + .orElse(null); + if (rel == null) { + throw new DException("目标身份不存在或非正常态,无法切换"); + } + + SysUserCurrentRole current = sysUserCurrentRoleRepository.findByUserId(userId).orElse(null); + if (current == null) { + current = new SysUserCurrentRole(); + current.setUserId(userId); + } + current.setRoleId(roleId); + current.setSchoolId(schoolId); + current.setSchoolName(schoolName(schoolId)); + current.setSwitchTime(LocalDateTime.now()); + sysUserCurrentRoleRepository.save(current); + + IdentityCurrentVo vo = new IdentityCurrentVo(); + vo.setRoleId(roleId); + vo.setRoleName(roleName(roleId)); + vo.setSchoolId(schoolId); + vo.setSchoolName(current.getSchoolName()); + vo.setFallback(false); + return vo; + } + + private String roleName(Integer roleId) { + if (roleId == null) { + return null; + } + SysRoleEnum e = IEnum.getByKey(SysRoleEnum.class, roleId); + return e != null ? e.getValue() : null; + } + + private String schoolName(Integer schoolId) { + if (schoolId == null || schoolId <= 0) { + return null; + } + SysGroup g = sysGroupRepository.findById(schoolId).orElse(null); + return g != null ? g.getName() : null; + } +} diff --git a/src/main/resources/sql/fix_roles_mapping.sql b/src/main/resources/sql/fix_roles_mapping.sql new file mode 100644 index 0000000..b224e4b --- /dev/null +++ b/src/main/resources/sql/fix_roles_mapping.sql @@ -0,0 +1,78 @@ +-- ============================================================ +-- 修复 sys_user.roles 旧映射 -> SysRoleEnum 新映射 +-- ------------------------------------------------------------ +-- 背景: +-- 前端表单与导出注释历史上使用「旧映射」: +-- 1=管理员 2=老师 3=校领导 4=局领导 5=学生 6=家长 7=工作号 +-- 而后端权威枚举 SysRoleEnum 现为: +-- 1=老师 2=校领导 3=学生 4=家长 5=局领导 6=工作号 7=管理员 +-- 旧表单勾选「老师」会提交 value=2,被后端按 SysRoleEnum 存成「校领导」, +-- 于是教师/校领导的 roles 出现 [2,3] 这类错位值。 +-- +-- 本脚本把存量 roles 从「旧映射」逐值改写为 SysRoleEnum 真值。 +-- 前端已改为直接吃 GetSysDict.sysrole(SysRoleEnum 真源),新数据不会再错位。 +-- +-- ⚠️ 重要前提(执行前必读): +-- 1) 本脚本假设 sys_user.roles 的全部现有值都是「旧映射」格式。 +-- 若部分用户曾通过「批量导入」创建(导入路径一直用 SysRoleEnum 新值), +-- 这些行已是新格式,执行本脚本会将其再次错位 —— 必须先跑下面的校验 SQL 确认。 +-- 2) 必须先执行备份语句;本脚本不自动执行,请由人工在数据库上运行。 +-- 3) roles 列在 MySQL 中存为 TEXT(内容为 JSON 数组串,如 "[2,3]"), +-- 下方 UPDATE 用 MySQL 8.0 的 JSON_TABLE / JSON_ARRAYAGG,请确认数据库为 8.0。 +-- ============================================================ + +-- ---------- 第 0 步:备份(务必先执行,可逆) ---------- +CREATE TABLE sys_user_roles_bak_20260817 AS +SELECT id, roles, type FROM sys_user; + +-- ---------- 第 1 步:只读校验,看清现有格式 ---------- +-- 重点看 distinct roles 的值: +-- 若看到 [2,3] / [1] / [7] 这类(旧映射含义:老师/管理员/工作号),说明是旧格式; +-- 若看到 [1,2](新映射含义:老师+校领导),说明已是新格式。 +-- 只有当所有行都符合「旧格式」语义时,才执行第 2 步。 +SELECT roles, COUNT(*) AS cnt +FROM sys_user +WHERE roles IS NOT NULL AND roles <> '' AND roles <> '[]' +GROUP BY roles +ORDER BY cnt DESC; + +-- ---------- 第 2 步:旧映射 -> SysRoleEnum 新映射 逐值替换 ---------- +-- 旧/新 双射: +-- 1(管理员)->7 2(老师)->1 3(校领导)->2 4(局领导)->5 +-- 5(学生)->3 6(家长)->4 7(工作号)->6 +UPDATE sys_user u +SET u.roles = ( + SELECT JSON_ARRAYAGG( + CASE j.r + WHEN 1 THEN 7 -- 旧:管理员 -> 新:管理员 + WHEN 2 THEN 1 -- 旧:老师 -> 新:老师 + WHEN 3 THEN 2 -- 旧:校领导 -> 新:校领导 + WHEN 4 THEN 5 -- 旧:局领导 -> 新:局领导 + WHEN 5 THEN 3 -- 旧:学生 -> 新:学生 + WHEN 6 THEN 4 -- 旧:家长 -> 新:家长 + WHEN 7 THEN 6 -- 旧:工作号 -> 新:工作号 + ELSE j.r + END + ) + FROM JSON_TABLE(CAST(u.roles AS JSON), '$[*]' COLUMNS (r INT PATH '$')) AS j +) +WHERE u.roles IS NOT NULL AND u.roles <> '' AND u.roles <> '[]'; + +-- ---------- 第 3 步(可选):重算 type 列(用户类型) ---------- +-- type 由 resolveUserType 推导:含 ADMIN(7) -> 2(学校管理员),否则 0(普通用户); +-- 超管账号(id 10000~10002, type=1)保持不变。旧数据里 type 曾被旧映射带偏,这里一并纠正。 +UPDATE sys_user +SET type = 2 +WHERE type <> 1 + AND JSON_CONTAINS(CAST(roles AS JSON), JSON_ARRAY(7)); + +UPDATE sys_user +SET type = 0 +WHERE type <> 1 + AND NOT JSON_CONTAINS(CAST(roles AS JSON), JSON_ARRAY(7)); + +-- ---------- 回滚(如需) ---------- +-- INSERT INTO sys_user (id, roles, type) +-- SELECT id, roles, type FROM sys_user_roles_bak_20260817 +-- ON DUPLICATE KEY UPDATE roles = VALUES(roles), type = VALUES(type); +-- DROP TABLE sys_user_roles_bak_20260817;