Commit 596263f4177868ef6594a2d6fdf6e29889350a93
1 parent
402685bc
Add multi-tenant support for roles and menus
Showing
13 changed files
with
493 additions
and
37 deletions
src/main/java/com/diligrp/rider/controller/AdminSubstationRoleController.java
0 → 100644
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.exception.BizException; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.dto.AdminRoleMenuAssignDTO; | |
| 6 | +import com.diligrp.rider.dto.AdminRoleSaveDTO; | |
| 7 | +import com.diligrp.rider.service.SystemRoleService; | |
| 8 | +import com.diligrp.rider.service.impl.SystemRoleMenuServiceImpl; | |
| 9 | +import com.diligrp.rider.vo.AdminRoleMenuTreeVO; | |
| 10 | +import com.diligrp.rider.vo.AdminRoleVO; | |
| 11 | +import jakarta.servlet.http.HttpServletRequest; | |
| 12 | +import jakarta.validation.Valid; | |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 14 | +import org.springframework.web.bind.annotation.*; | |
| 15 | + | |
| 16 | +import java.util.List; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * 分站管理员侧角色管理接口(/api/admin/system/role/**) | |
| 20 | + * 每个分站只能看到/操作自己 cityId 下的角色,菜单只能分配 SUBSTATION/BOTH scope | |
| 21 | + */ | |
| 22 | +@RestController | |
| 23 | +@RequestMapping("/api/admin/system/role") | |
| 24 | +@RequiredArgsConstructor | |
| 25 | +public class AdminSubstationRoleController { | |
| 26 | + | |
| 27 | + private final SystemRoleService systemRoleService; | |
| 28 | + private final SystemRoleMenuServiceImpl systemRoleMenuService; | |
| 29 | + | |
| 30 | + @GetMapping("/list") | |
| 31 | + public Result<List<AdminRoleVO>> list(HttpServletRequest request) { | |
| 32 | + Long cityId = resolveCityId(request); | |
| 33 | + return Result.success(systemRoleService.listByCityId(cityId)); | |
| 34 | + } | |
| 35 | + | |
| 36 | + @PostMapping("/add") | |
| 37 | + public Result<Void> add(@Valid @RequestBody AdminRoleSaveDTO dto, HttpServletRequest request) { | |
| 38 | + Long cityId = resolveCityId(request); | |
| 39 | + systemRoleService.addForCity(dto, cityId); | |
| 40 | + return Result.success(); | |
| 41 | + } | |
| 42 | + | |
| 43 | + @PutMapping("/edit") | |
| 44 | + public Result<Void> edit(@Valid @RequestBody AdminRoleSaveDTO dto, HttpServletRequest request) { | |
| 45 | + Long cityId = resolveCityId(request); | |
| 46 | + systemRoleService.editForCity(dto, cityId); | |
| 47 | + return Result.success(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @PostMapping("/ban") | |
| 51 | + public Result<Void> ban(@RequestParam Long id, HttpServletRequest request) { | |
| 52 | + Long cityId = resolveCityId(request); | |
| 53 | + systemRoleService.banForCity(id, cityId); | |
| 54 | + return Result.success(); | |
| 55 | + } | |
| 56 | + | |
| 57 | + @PostMapping("/cancelBan") | |
| 58 | + public Result<Void> cancelBan(@RequestParam Long id, HttpServletRequest request) { | |
| 59 | + Long cityId = resolveCityId(request); | |
| 60 | + systemRoleService.cancelBanForCity(id, cityId); | |
| 61 | + return Result.success(); | |
| 62 | + } | |
| 63 | + | |
| 64 | + @DeleteMapping("/del") | |
| 65 | + public Result<Void> del(@RequestParam Long id, HttpServletRequest request) { | |
| 66 | + Long cityId = resolveCityId(request); | |
| 67 | + systemRoleService.delForCity(id, cityId); | |
| 68 | + return Result.success(); | |
| 69 | + } | |
| 70 | + | |
| 71 | + @GetMapping("/{roleId}/menu-tree") | |
| 72 | + public Result<List<AdminRoleMenuTreeVO>> menuTree(@PathVariable Long roleId, | |
| 73 | + HttpServletRequest request) { | |
| 74 | + Long cityId = resolveCityId(request); | |
| 75 | + return Result.success(systemRoleMenuService.getRoleMenuTreeForCity(roleId, cityId)); | |
| 76 | + } | |
| 77 | + | |
| 78 | + @PostMapping("/{roleId}/menus") | |
| 79 | + public Result<Void> assignMenus(@PathVariable Long roleId, | |
| 80 | + @Valid @RequestBody AdminRoleMenuAssignDTO dto, | |
| 81 | + HttpServletRequest request) { | |
| 82 | + Long cityId = resolveCityId(request); | |
| 83 | + systemRoleMenuService.assignMenusForCity(roleId, dto, cityId); | |
| 84 | + return Result.success(); | |
| 85 | + } | |
| 86 | + | |
| 87 | + private Long resolveCityId(HttpServletRequest request) { | |
| 88 | + Long cityId = (Long) request.getAttribute("cityId"); | |
| 89 | + if (cityId == null || cityId < 1) { | |
| 90 | + throw new BizException("当前账号未绑定有效城市"); | |
| 91 | + } | |
| 92 | + return cityId; | |
| 93 | + } | |
| 94 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/AdminSubstationUserController.java
0 → 100644
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.enums.AdminRoleScopeEnum; | |
| 6 | +import com.diligrp.rider.common.exception.BizException; | |
| 7 | +import com.diligrp.rider.common.result.Result; | |
| 8 | +import com.diligrp.rider.dto.ChangePasswordDTO; | |
| 9 | +import com.diligrp.rider.entity.Substation; | |
| 10 | +import com.diligrp.rider.mapper.SubstationMapper; | |
| 11 | +import com.diligrp.rider.service.RoleScopeGuardService; | |
| 12 | +import jakarta.servlet.http.HttpServletRequest; | |
| 13 | +import jakarta.validation.Valid; | |
| 14 | +import lombok.RequiredArgsConstructor; | |
| 15 | +import org.springframework.util.DigestUtils; | |
| 16 | +import org.springframework.web.bind.annotation.*; | |
| 17 | + | |
| 18 | +import java.nio.charset.StandardCharsets; | |
| 19 | +import java.util.List; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * 分站管理员侧子账号管理接口(/api/admin/substation-user/**) | |
| 23 | + * 分站管理员只能管理本 cityId 下的账号 | |
| 24 | + */ | |
| 25 | +@RestController | |
| 26 | +@RequestMapping("/api/admin/substation-user") | |
| 27 | +@RequiredArgsConstructor | |
| 28 | +public class AdminSubstationUserController { | |
| 29 | + | |
| 30 | + private final SubstationMapper substationMapper; | |
| 31 | + private final RoleScopeGuardService roleScopeGuardService; | |
| 32 | + | |
| 33 | + @GetMapping("/list") | |
| 34 | + public Result<List<Substation>> list(@RequestParam(required = false) String keyword, | |
| 35 | + HttpServletRequest request) { | |
| 36 | + Long cityId = resolveCityId(request); | |
| 37 | + Long selfId = (Long) request.getAttribute("adminId"); | |
| 38 | + LambdaQueryWrapper<Substation> wrapper = new LambdaQueryWrapper<Substation>() | |
| 39 | + .eq(Substation::getCityId, cityId) | |
| 40 | + .ne(Substation::getId, selfId) // 不返回自己 | |
| 41 | + .orderByDesc(Substation::getId); | |
| 42 | + if (keyword != null && !keyword.isBlank()) { | |
| 43 | + wrapper.and(w -> w.like(Substation::getUserLogin, keyword) | |
| 44 | + .or().like(Substation::getUserNickname, keyword) | |
| 45 | + .or().like(Substation::getMobile, keyword)); | |
| 46 | + } | |
| 47 | + List<Substation> list = substationMapper.selectList(wrapper); | |
| 48 | + // 隐藏密码字段 | |
| 49 | + list.forEach(s -> s.setUserPass(null)); | |
| 50 | + return Result.success(list); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @PostMapping("/add") | |
| 54 | + public Result<Void> add(@RequestBody Substation substation, HttpServletRequest request) { | |
| 55 | + Long cityId = resolveCityId(request); | |
| 56 | + substation.setCityId(cityId); | |
| 57 | + | |
| 58 | + Long loginExists = substationMapper.selectCount(new LambdaQueryWrapper<Substation>() | |
| 59 | + .eq(Substation::getUserLogin, substation.getUserLogin())); | |
| 60 | + if (loginExists > 0) { | |
| 61 | + throw new BizException("账号已存在,请更换"); | |
| 62 | + } | |
| 63 | + // 校验角色必须是 SUBSTATION scope 且属于本租户或全局 | |
| 64 | + roleScopeGuardService.requireRole(substation.getRoleId(), AdminRoleScopeEnum.SUBSTATION.name(), cityId); | |
| 65 | + | |
| 66 | + substation.setUserPass(encryptPass(substation.getUserPass())); | |
| 67 | + substation.setUserStatus(1); | |
| 68 | + substation.setCreateTime(System.currentTimeMillis() / 1000); | |
| 69 | + substationMapper.insert(substation); | |
| 70 | + return Result.success(); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @PutMapping("/edit") | |
| 74 | + public Result<Void> edit(@RequestBody Substation substation, HttpServletRequest request) { | |
| 75 | + Long cityId = resolveCityId(request); | |
| 76 | + Substation existing = requireOwned(substation.getId(), cityId); | |
| 77 | + | |
| 78 | + // 校验角色归属 | |
| 79 | + roleScopeGuardService.requireRole(substation.getRoleId(), AdminRoleScopeEnum.SUBSTATION.name(), cityId); | |
| 80 | + | |
| 81 | + substation.setCityId(existing.getCityId()); // 不允许修改城市 | |
| 82 | + if (substation.getUserPass() == null || substation.getUserPass().isBlank()) { | |
| 83 | + substation.setUserPass(null); | |
| 84 | + } else { | |
| 85 | + substation.setUserPass(encryptPass(substation.getUserPass())); | |
| 86 | + } | |
| 87 | + substationMapper.updateById(substation); | |
| 88 | + return Result.success(); | |
| 89 | + } | |
| 90 | + | |
| 91 | + @PostMapping("/ban") | |
| 92 | + public Result<Void> ban(@RequestParam Long id, HttpServletRequest request) { | |
| 93 | + Long cityId = resolveCityId(request); | |
| 94 | + requireOwned(id, cityId); | |
| 95 | + substationMapper.update(null, new LambdaUpdateWrapper<Substation>() | |
| 96 | + .eq(Substation::getId, id).set(Substation::getUserStatus, 0)); | |
| 97 | + return Result.success(); | |
| 98 | + } | |
| 99 | + | |
| 100 | + @PostMapping("/cancelBan") | |
| 101 | + public Result<Void> cancelBan(@RequestParam Long id, HttpServletRequest request) { | |
| 102 | + Long cityId = resolveCityId(request); | |
| 103 | + requireOwned(id, cityId); | |
| 104 | + substationMapper.update(null, new LambdaUpdateWrapper<Substation>() | |
| 105 | + .eq(Substation::getId, id).set(Substation::getUserStatus, 1)); | |
| 106 | + return Result.success(); | |
| 107 | + } | |
| 108 | + | |
| 109 | + @DeleteMapping("/del") | |
| 110 | + public Result<Void> del(@RequestParam Long id, HttpServletRequest request) { | |
| 111 | + Long cityId = resolveCityId(request); | |
| 112 | + requireOwned(id, cityId); | |
| 113 | + substationMapper.deleteById(id); | |
| 114 | + return Result.success(); | |
| 115 | + } | |
| 116 | + | |
| 117 | + @PostMapping("/changePassword") | |
| 118 | + public Result<Void> changePassword(@RequestParam Long id, | |
| 119 | + @Valid @RequestBody ChangePasswordDTO dto, | |
| 120 | + HttpServletRequest request) { | |
| 121 | + Long cityId = resolveCityId(request); | |
| 122 | + Substation sub = requireOwned(id, cityId); | |
| 123 | + if (!encryptPass(dto.getOldPassword()).equals(sub.getUserPass())) { | |
| 124 | + throw new BizException("原密码不正确"); | |
| 125 | + } | |
| 126 | + if (encryptPass(dto.getNewPassword()).equals(sub.getUserPass())) { | |
| 127 | + throw new BizException("新密码不能与原密码相同"); | |
| 128 | + } | |
| 129 | + substationMapper.update(null, new LambdaUpdateWrapper<Substation>() | |
| 130 | + .eq(Substation::getId, id) | |
| 131 | + .set(Substation::getUserPass, encryptPass(dto.getNewPassword()))); | |
| 132 | + return Result.success(); | |
| 133 | + } | |
| 134 | + | |
| 135 | + private Substation requireOwned(Long id, Long cityId) { | |
| 136 | + Substation sub = substationMapper.selectById(id); | |
| 137 | + if (sub == null) { | |
| 138 | + throw new BizException("账号不存在"); | |
| 139 | + } | |
| 140 | + if (!cityId.equals(sub.getCityId())) { | |
| 141 | + throw new BizException("无权操作其他租户的账号"); | |
| 142 | + } | |
| 143 | + return sub; | |
| 144 | + } | |
| 145 | + | |
| 146 | + private Long resolveCityId(HttpServletRequest request) { | |
| 147 | + Long cityId = (Long) request.getAttribute("cityId"); | |
| 148 | + if (cityId == null || cityId < 1) { | |
| 149 | + throw new BizException("当前账号未绑定有效城市"); | |
| 150 | + } | |
| 151 | + return cityId; | |
| 152 | + } | |
| 153 | + | |
| 154 | + private String encryptPass(String pass) { | |
| 155 | + return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8)); | |
| 156 | + } | |
| 157 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/SysRole.java
src/main/java/com/diligrp/rider/service/RoleScopeGuardService.java
| ... | ... | @@ -4,4 +4,7 @@ import com.diligrp.rider.entity.SysRole; |
| 4 | 4 | |
| 5 | 5 | public interface RoleScopeGuardService { |
| 6 | 6 | SysRole requireRole(Long roleId, String requiredScope); |
| 7 | + | |
| 8 | + /** 分站侧调用:额外校验角色属于该租户或是全局角色 */ | |
| 9 | + SysRole requireRole(Long roleId, String requiredScope, Long cityId); | |
| 7 | 10 | } | ... | ... |
src/main/java/com/diligrp/rider/service/SystemRoleService.java
| ... | ... | @@ -6,15 +6,36 @@ import com.diligrp.rider.vo.AdminRoleVO; |
| 6 | 6 | import java.util.List; |
| 7 | 7 | |
| 8 | 8 | public interface SystemRoleService { |
| 9 | + /** 平台侧调用:只返回平台全局角色(city_id=0) */ | |
| 9 | 10 | List<AdminRoleVO> list(boolean includeDisabled); |
| 10 | 11 | |
| 12 | + /** 分站侧调用:只返回该租户自己的角色(city_id=cityId) */ | |
| 13 | + List<AdminRoleVO> listByCityId(Long cityId); | |
| 14 | + | |
| 15 | + /** 平台侧新增角色(city_id=0) */ | |
| 11 | 16 | void add(AdminRoleSaveDTO dto); |
| 12 | 17 | |
| 18 | + /** 分站侧新增角色(city_id=cityId) */ | |
| 19 | + void addForCity(AdminRoleSaveDTO dto, Long cityId); | |
| 20 | + | |
| 21 | + /** 平台侧编辑(只能编辑 city_id=0 的角色) */ | |
| 13 | 22 | void edit(AdminRoleSaveDTO dto); |
| 14 | 23 | |
| 24 | + /** 分站侧编辑(只能编辑本 cityId 的角色) */ | |
| 25 | + void editForCity(AdminRoleSaveDTO dto, Long cityId); | |
| 26 | + | |
| 15 | 27 | void ban(Long id); |
| 16 | 28 | |
| 29 | + /** 分站侧禁用(校验 cityId 归属) */ | |
| 30 | + void banForCity(Long id, Long cityId); | |
| 31 | + | |
| 17 | 32 | void cancelBan(Long id); |
| 18 | 33 | |
| 34 | + /** 分站侧启用(校验 cityId 归属) */ | |
| 35 | + void cancelBanForCity(Long id, Long cityId); | |
| 36 | + | |
| 19 | 37 | void del(Long id); |
| 38 | + | |
| 39 | + /** 分站侧删除(只能删除本 cityId 的角色) */ | |
| 40 | + void delForCity(Long id, Long cityId); | |
| 20 | 41 | } | ... | ... |
src/main/java/com/diligrp/rider/service/impl/AdminMenuServiceImpl.java
| ... | ... | @@ -47,8 +47,10 @@ public class AdminMenuServiceImpl implements AdminMenuService { |
| 47 | 47 | role = sysRoleMapper.selectById(roleId); |
| 48 | 48 | } |
| 49 | 49 | if (role == null) { |
| 50 | + // Fallback:找全局内置角色(city_id=0) | |
| 50 | 51 | role = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>() |
| 51 | 52 | .eq(SysRole::getCode, fallbackCode) |
| 53 | + .eq(SysRole::getCityId, 0L) | |
| 52 | 54 | .eq(SysRole::getStatus, 1) |
| 53 | 55 | .last("LIMIT 1")); |
| 54 | 56 | } | ... | ... |
src/main/java/com/diligrp/rider/service/impl/AdminUserManageServiceImpl.java
| ... | ... | @@ -40,7 +40,7 @@ public class AdminUserManageServiceImpl implements AdminUserManageService { |
| 40 | 40 | if (exists > 0) { |
| 41 | 41 | throw new BizException("账号已存在,请更换"); |
| 42 | 42 | } |
| 43 | - roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name()); | |
| 43 | + roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name(), 0L); | |
| 44 | 44 | adminUser.setUserPass(encryptPass(adminUser.getUserPass())); |
| 45 | 45 | adminUser.setUserStatus(1); |
| 46 | 46 | adminUser.setCreateTime(System.currentTimeMillis() / 1000); |
| ... | ... | @@ -53,7 +53,7 @@ public class AdminUserManageServiceImpl implements AdminUserManageService { |
| 53 | 53 | if (existing == null) { |
| 54 | 54 | throw new BizException("平台账号不存在"); |
| 55 | 55 | } |
| 56 | - roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name()); | |
| 56 | + roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name(), 0L); | |
| 57 | 57 | if (adminUser.getUserPass() == null || adminUser.getUserPass().isBlank()) { |
| 58 | 58 | adminUser.setUserPass(null); |
| 59 | 59 | } else { | ... | ... |
src/main/java/com/diligrp/rider/service/impl/MenuBootstrapServiceImpl.java
| ... | ... | @@ -60,10 +60,12 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { |
| 60 | 60 | defaults.add(menu("dashboard", "工作台", "MENU", "/dashboard", "HomeOutlined", 0L, MenuScopeEnum.BOTH, 10)); |
| 61 | 61 | defaults.add(menu("city.manage", "租户管理", "MENU", "/city", "GlobalOutlined", 0L, MenuScopeEnum.PLATFORM, 20)); |
| 62 | 62 | defaults.add(menu("substation.manage", "分站管理", "MENU", "/substation", "ApartmentOutlined", 0L, MenuScopeEnum.PLATFORM, 30)); |
| 63 | + defaults.add(menu("substation.user", "分站账号", "MENU", "/substation/user", "TeamOutlined", 0L, MenuScopeEnum.SUBSTATION, 31)); | |
| 63 | 64 | defaults.add(menu("merchant.root", "商家管理", "DIR", "", "ShopOutlined", 0L, MenuScopeEnum.PLATFORM, 40)); |
| 64 | 65 | defaults.add(menu("merchant.enter", "入驻申请", "MENU", "/merchant/enter", "", 0L, MenuScopeEnum.PLATFORM, 41)); |
| 65 | 66 | defaults.add(menu("merchant.store", "店铺管理", "MENU", "/merchant/store", "", 0L, MenuScopeEnum.PLATFORM, 42)); |
| 66 | 67 | defaults.add(menu("rider.list", "骑手管理", "MENU", "/rider", "UserOutlined", 0L, MenuScopeEnum.BOTH, 50)); |
| 68 | + defaults.add(menu("rider.level", "骑手等级", "MENU", "/rider/level", "TrophyOutlined", 0L, MenuScopeEnum.BOTH, 55)); | |
| 67 | 69 | defaults.add(menu("rider.evaluate", "骑手评价", "MENU", "/rider/evaluate", "StarOutlined", 0L, MenuScopeEnum.BOTH, 60)); |
| 68 | 70 | defaults.add(menu("order.root", "订单管理", "DIR", "", "UnorderedListOutlined", 0L, MenuScopeEnum.BOTH, 70)); |
| 69 | 71 | defaults.add(menu("order.list", "订单列表", "MENU", "/order", "", 0L, MenuScopeEnum.BOTH, 71)); |
| ... | ... | @@ -80,6 +82,10 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { |
| 80 | 82 | defaults.add(menu("system.role", "角色管理", "MENU", "/system/role", "", 0L, MenuScopeEnum.PLATFORM, 102)); |
| 81 | 83 | defaults.add(menu("system.role_menu", "角色菜单", "MENU", "/system/role-menu", "", 0L, MenuScopeEnum.PLATFORM, 103)); |
| 82 | 84 | defaults.add(menu("admin.user", "平台账号", "MENU", "/admin-user", "", 0L, MenuScopeEnum.PLATFORM, 104)); |
| 85 | + // 分站管理员专属:站点管理目录 | |
| 86 | + defaults.add(menu("system.sub_root", "站点管理", "DIR", "", "SettingOutlined", 0L, MenuScopeEnum.SUBSTATION, 110)); | |
| 87 | + defaults.add(menu("system.sub_role", "角色管理", "MENU", "/substation/role", "", 0L, MenuScopeEnum.SUBSTATION, 111)); | |
| 88 | + defaults.add(menu("system.sub_role_menu", "角色菜单", "MENU", "/substation/role-menu", "", 0L, MenuScopeEnum.SUBSTATION, 112)); | |
| 83 | 89 | |
| 84 | 90 | Map<String, SysMenu> persisted = new LinkedHashMap<>(); |
| 85 | 91 | for (SysMenu menu : defaults) { |
| ... | ... | @@ -111,6 +117,11 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { |
| 111 | 117 | persisted.get("system.role_menu").setListOrder(103); |
| 112 | 118 | persisted.get("admin.user").setParentId(persisted.get("system.root").getId()); |
| 113 | 119 | persisted.get("admin.user").setListOrder(104); |
| 120 | + // 分站专属菜单父节点 | |
| 121 | + persisted.get("system.sub_role").setParentId(persisted.get("system.sub_root").getId()); | |
| 122 | + persisted.get("system.sub_role").setListOrder(111); | |
| 123 | + persisted.get("system.sub_role_menu").setParentId(persisted.get("system.sub_root").getId()); | |
| 124 | + persisted.get("system.sub_role_menu").setListOrder(112); | |
| 114 | 125 | |
| 115 | 126 | for (SysMenu menu : persisted.values()) { |
| 116 | 127 | sysMenuMapper.updateById(menu); | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RoleScopeGuardServiceImpl.java
| ... | ... | @@ -16,6 +16,11 @@ public class RoleScopeGuardServiceImpl implements RoleScopeGuardService { |
| 16 | 16 | |
| 17 | 17 | @Override |
| 18 | 18 | public SysRole requireRole(Long roleId, String requiredScope) { |
| 19 | + return requireRole(roleId, requiredScope, null); | |
| 20 | + } | |
| 21 | + | |
| 22 | + @Override | |
| 23 | + public SysRole requireRole(Long roleId, String requiredScope, Long cityId) { | |
| 19 | 24 | if (roleId == null || roleId < 1) { |
| 20 | 25 | throw new BizException("角色不能为空"); |
| 21 | 26 | } |
| ... | ... | @@ -29,6 +34,14 @@ public class RoleScopeGuardServiceImpl implements RoleScopeGuardService { |
| 29 | 34 | if (!requiredScope.equals(role.getRoleScope())) { |
| 30 | 35 | throw new BizException("角色归属不匹配"); |
| 31 | 36 | } |
| 37 | + // cityId != null 时,要求角色属于该租户或是全局角色(city_id=0) | |
| 38 | + if (cityId != null) { | |
| 39 | + boolean isGlobal = role.getCityId() == null || role.getCityId() == 0L; | |
| 40 | + boolean isOwned = cityId.equals(role.getCityId()); | |
| 41 | + if (!isGlobal && !isOwned) { | |
| 42 | + throw new BizException("角色不属于当前租户"); | |
| 43 | + } | |
| 44 | + } | |
| 32 | 45 | return role; |
| 33 | 46 | } |
| 34 | 47 | } | ... | ... |
src/main/java/com/diligrp/rider/service/impl/SubstationServiceImpl.java
| ... | ... | @@ -44,7 +44,7 @@ public class SubstationServiceImpl implements SubstationService { |
| 44 | 44 | .eq(Substation::getUserLogin, substation.getUserLogin())); |
| 45 | 45 | if (loginExists > 0) throw new BizException("账号已存在,请更换"); |
| 46 | 46 | |
| 47 | - roleScopeGuardService.requireRole(substation.getRoleId(), AdminRoleScopeEnum.SUBSTATION.name()); | |
| 47 | + roleScopeGuardService.requireRole(substation.getRoleId(), AdminRoleScopeEnum.SUBSTATION.name(), substation.getCityId()); | |
| 48 | 48 | substation.setUserPass(encryptPass(substation.getUserPass())); |
| 49 | 49 | substation.setUserStatus(1); |
| 50 | 50 | substation.setCreateTime(System.currentTimeMillis() / 1000); |
| ... | ... | @@ -55,7 +55,7 @@ public class SubstationServiceImpl implements SubstationService { |
| 55 | 55 | public void edit(Substation substation) { |
| 56 | 56 | Substation existing = substationMapper.selectById(substation.getId()); |
| 57 | 57 | if (existing == null) throw new BizException("分站管理员不存在"); |
| 58 | - roleScopeGuardService.requireRole(substation.getRoleId(), AdminRoleScopeEnum.SUBSTATION.name()); | |
| 58 | + roleScopeGuardService.requireRole(substation.getRoleId(), AdminRoleScopeEnum.SUBSTATION.name(), existing.getCityId()); | |
| 59 | 59 | // 密码为空则不更新 |
| 60 | 60 | if (substation.getUserPass() == null || substation.getUserPass().isBlank()) { |
| 61 | 61 | substation.setUserPass(null); | ... | ... |
src/main/java/com/diligrp/rider/service/impl/SystemRoleMenuServiceImpl.java
| ... | ... | @@ -30,10 +30,47 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { |
| 30 | 30 | private final SysRoleMenuMapper sysRoleMenuMapper; |
| 31 | 31 | private final SystemMenuServiceImpl systemMenuService; |
| 32 | 32 | |
| 33 | + // ---------------------------------------------------------------- | |
| 34 | + // 平台侧:不限菜单 scope(平台管理员可分配所有菜单) | |
| 35 | + // ---------------------------------------------------------------- | |
| 36 | + | |
| 33 | 37 | @Override |
| 34 | 38 | public List<AdminRoleMenuTreeVO> getRoleMenuTree(Long roleId) { |
| 35 | - SysRole role = requireRole(roleId); | |
| 39 | + SysRole role = requireRole(roleId, null); | |
| 40 | + List<SysMenu> allowedMenus = filterMenusByScope(systemMenuService.listAllMenus(), role); | |
| 41 | + return buildCheckedTree(roleId, allowedMenus); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + @Transactional | |
| 46 | + public void assignMenus(Long roleId, AdminRoleMenuAssignDTO dto) { | |
| 47 | + SysRole role = requireRole(roleId, null); | |
| 36 | 48 | List<SysMenu> allowedMenus = filterMenusByScope(systemMenuService.listAllMenus(), role); |
| 49 | + doAssignMenus(roleId, dto, allowedMenus); | |
| 50 | + } | |
| 51 | + | |
| 52 | + // ---------------------------------------------------------------- | |
| 53 | + // 分站侧:仅允许 SUBSTATION / BOTH scope 的菜单,且校验角色归属 cityId | |
| 54 | + // ---------------------------------------------------------------- | |
| 55 | + | |
| 56 | + public List<AdminRoleMenuTreeVO> getRoleMenuTreeForCity(Long roleId, Long cityId) { | |
| 57 | + SysRole role = requireRole(roleId, cityId); | |
| 58 | + List<SysMenu> allowedMenus = filterSubstationMenus(systemMenuService.listAllMenus()); | |
| 59 | + return buildCheckedTree(roleId, allowedMenus); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Transactional | |
| 63 | + public void assignMenusForCity(Long roleId, AdminRoleMenuAssignDTO dto, Long cityId) { | |
| 64 | + SysRole role = requireRole(roleId, cityId); | |
| 65 | + List<SysMenu> allowedMenus = filterSubstationMenus(systemMenuService.listAllMenus()); | |
| 66 | + doAssignMenus(roleId, dto, allowedMenus); | |
| 67 | + } | |
| 68 | + | |
| 69 | + // ---------------------------------------------------------------- | |
| 70 | + // 私有工具 | |
| 71 | + // ---------------------------------------------------------------- | |
| 72 | + | |
| 73 | + private List<AdminRoleMenuTreeVO> buildCheckedTree(Long roleId, List<SysMenu> allowedMenus) { | |
| 37 | 74 | List<SysRoleMenu> assigned = sysRoleMenuMapper.selectList(new LambdaQueryWrapper<SysRoleMenu>() |
| 38 | 75 | .eq(SysRoleMenu::getRoleId, roleId)); |
| 39 | 76 | Set<Long> assignedIds = assigned.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet()); |
| ... | ... | @@ -44,11 +81,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { |
| 44 | 81 | return systemMenuService.buildTree(allowedMenus, checkedMap, false); |
| 45 | 82 | } |
| 46 | 83 | |
| 47 | - @Override | |
| 48 | - @Transactional | |
| 49 | - public void assignMenus(Long roleId, AdminRoleMenuAssignDTO dto) { | |
| 50 | - SysRole role = requireRole(roleId); | |
| 51 | - List<SysMenu> allowedMenus = filterMenusByScope(systemMenuService.listAllMenus(), role); | |
| 84 | + private void doAssignMenus(Long roleId, AdminRoleMenuAssignDTO dto, List<SysMenu> allowedMenus) { | |
| 52 | 85 | Set<Long> allowedIds = allowedMenus.stream().map(SysMenu::getId).collect(Collectors.toSet()); |
| 53 | 86 | List<Long> menuIds = dto.getMenuIds() == null ? List.of() : dto.getMenuIds(); |
| 54 | 87 | for (Long menuId : menuIds) { |
| ... | ... | @@ -56,7 +89,6 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { |
| 56 | 89 | throw new BizException("存在不允许分配的菜单"); |
| 57 | 90 | } |
| 58 | 91 | } |
| 59 | - | |
| 60 | 92 | sysRoleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>() |
| 61 | 93 | .eq(SysRoleMenu::getRoleId, roleId)); |
| 62 | 94 | long now = System.currentTimeMillis() / 1000; |
| ... | ... | @@ -69,11 +101,15 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { |
| 69 | 101 | } |
| 70 | 102 | } |
| 71 | 103 | |
| 72 | - private SysRole requireRole(Long roleId) { | |
| 104 | + private SysRole requireRole(Long roleId, Long cityId) { | |
| 73 | 105 | SysRole role = sysRoleMapper.selectById(roleId); |
| 74 | 106 | if (role == null || role.getStatus() == null || role.getStatus() != 1) { |
| 75 | 107 | throw new BizException("角色不存在或已禁用"); |
| 76 | 108 | } |
| 109 | + // cityId != null 时校验归属(分站侧调用) | |
| 110 | + if (cityId != null && !cityId.equals(role.getCityId())) { | |
| 111 | + throw new BizException("无权操作其他租户的角色"); | |
| 112 | + } | |
| 77 | 113 | return role; |
| 78 | 114 | } |
| 79 | 115 | |
| ... | ... | @@ -81,6 +117,14 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { |
| 81 | 117 | return menus.stream().filter(menu -> isScopeAllowed(role, menu)).collect(Collectors.toList()); |
| 82 | 118 | } |
| 83 | 119 | |
| 120 | + /** 分站侧:只允许 SUBSTATION 或 BOTH scope 的菜单 */ | |
| 121 | + private List<SysMenu> filterSubstationMenus(List<SysMenu> menus) { | |
| 122 | + return menus.stream().filter(menu -> | |
| 123 | + MenuScopeEnum.SUBSTATION.name().equals(menu.getMenuScope()) | |
| 124 | + || MenuScopeEnum.BOTH.name().equals(menu.getMenuScope()) | |
| 125 | + ).collect(Collectors.toList()); | |
| 126 | + } | |
| 127 | + | |
| 84 | 128 | private boolean isScopeAllowed(SysRole role, SysMenu menu) { |
| 85 | 129 | if (MenuScopeEnum.BOTH.name().equals(menu.getMenuScope())) { |
| 86 | 130 | return true; | ... | ... |
src/main/java/com/diligrp/rider/service/impl/SystemRoleServiceImpl.java
| ... | ... | @@ -27,6 +27,7 @@ import java.util.Set; |
| 27 | 27 | @RequiredArgsConstructor |
| 28 | 28 | public class SystemRoleServiceImpl implements SystemRoleService { |
| 29 | 29 | |
| 30 | + /** 内置角色编码(city_id=0 且 code 在此集合内,不允许编辑/删除) */ | |
| 30 | 31 | private static final Set<String> BUILT_IN_CODES = Set.of("platform_admin", "substation_admin"); |
| 31 | 32 | |
| 32 | 33 | private final SysRoleMapper sysRoleMapper; |
| ... | ... | @@ -34,38 +35,33 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 34 | 35 | private final AdminUserMapper adminUserMapper; |
| 35 | 36 | private final SubstationMapper substationMapper; |
| 36 | 37 | |
| 38 | + // ---------------------------------------------------------------- | |
| 39 | + // 平台侧:只看/操作 city_id=0 的全局角色 | |
| 40 | + // ---------------------------------------------------------------- | |
| 41 | + | |
| 37 | 42 | @Override |
| 38 | 43 | public List<AdminRoleVO> list(boolean includeDisabled) { |
| 39 | 44 | List<SysRole> roles = sysRoleMapper.selectList(new LambdaQueryWrapper<SysRole>() |
| 45 | + .eq(SysRole::getCityId, 0L) | |
| 40 | 46 | .eq(!includeDisabled, SysRole::getStatus, 1) |
| 41 | 47 | .orderByAsc(SysRole::getId)); |
| 42 | - List<AdminRoleVO> result = new ArrayList<>(); | |
| 43 | - for (SysRole role : roles) { | |
| 44 | - result.add(toVO(role)); | |
| 45 | - } | |
| 46 | - return result; | |
| 48 | + return toVOList(roles); | |
| 47 | 49 | } |
| 48 | 50 | |
| 49 | 51 | @Override |
| 50 | 52 | public void add(AdminRoleSaveDTO dto) { |
| 51 | 53 | validateScope(dto.getRoleScope()); |
| 52 | - ensureUniqueCode(dto.getCode(), null); | |
| 53 | - | |
| 54 | - SysRole role = new SysRole(); | |
| 55 | - role.setCode(dto.getCode().trim()); | |
| 56 | - role.setName(dto.getName().trim()); | |
| 57 | - role.setRoleScope(dto.getRoleScope()); | |
| 58 | - role.setStatus(1); | |
| 59 | - role.setCreateTime(System.currentTimeMillis() / 1000); | |
| 54 | + ensureUniqueCode(dto.getCode(), null, 0L); | |
| 55 | + SysRole role = buildRole(dto, 0L); | |
| 60 | 56 | sysRoleMapper.insert(role); |
| 61 | 57 | } |
| 62 | 58 | |
| 63 | 59 | @Override |
| 64 | 60 | public void edit(AdminRoleSaveDTO dto) { |
| 65 | - if (dto.getId() == null || dto.getId() < 1) { | |
| 66 | - throw new BizException("角色ID不能为空"); | |
| 67 | - } | |
| 68 | 61 | SysRole role = requireRole(dto.getId()); |
| 62 | + if (!role.getCityId().equals(0L)) { | |
| 63 | + throw new BizException("平台侧不允许编辑分站专属角色"); | |
| 64 | + } | |
| 69 | 65 | if (isBuiltIn(role)) { |
| 70 | 66 | throw new BizException("内置角色不允许编辑"); |
| 71 | 67 | } |
| ... | ... | @@ -73,7 +69,7 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 73 | 69 | if (!role.getRoleScope().equals(dto.getRoleScope())) { |
| 74 | 70 | throw new BizException("角色范围不允许修改"); |
| 75 | 71 | } |
| 76 | - ensureUniqueCode(dto.getCode(), role.getId()); | |
| 72 | + ensureUniqueCode(dto.getCode(), role.getId(), 0L); | |
| 77 | 73 | role.setCode(dto.getCode().trim()); |
| 78 | 74 | role.setName(dto.getName().trim()); |
| 79 | 75 | sysRoleMapper.updateById(role); |
| ... | ... | @@ -82,27 +78,34 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 82 | 78 | @Override |
| 83 | 79 | public void ban(Long id) { |
| 84 | 80 | SysRole role = requireRole(id); |
| 81 | + if (!role.getCityId().equals(0L)) { | |
| 82 | + throw new BizException("平台侧不允许操作分站专属角色"); | |
| 83 | + } | |
| 85 | 84 | if (isBuiltIn(role)) { |
| 86 | 85 | throw new BizException("内置角色不允许禁用"); |
| 87 | 86 | } |
| 88 | 87 | ensureNotBound(role.getId(), "当前角色已绑定账号,不能禁用"); |
| 89 | 88 | sysRoleMapper.update(null, new LambdaUpdateWrapper<SysRole>() |
| 90 | - .eq(SysRole::getId, id) | |
| 91 | - .set(SysRole::getStatus, 0)); | |
| 89 | + .eq(SysRole::getId, id).set(SysRole::getStatus, 0)); | |
| 92 | 90 | } |
| 93 | 91 | |
| 94 | 92 | @Override |
| 95 | 93 | public void cancelBan(Long id) { |
| 96 | - requireRole(id); | |
| 94 | + SysRole role = requireRole(id); | |
| 95 | + if (!role.getCityId().equals(0L)) { | |
| 96 | + throw new BizException("平台侧不允许操作分站专属角色"); | |
| 97 | + } | |
| 97 | 98 | sysRoleMapper.update(null, new LambdaUpdateWrapper<SysRole>() |
| 98 | - .eq(SysRole::getId, id) | |
| 99 | - .set(SysRole::getStatus, 1)); | |
| 99 | + .eq(SysRole::getId, id).set(SysRole::getStatus, 1)); | |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | 102 | @Override |
| 103 | 103 | @Transactional |
| 104 | 104 | public void del(Long id) { |
| 105 | 105 | SysRole role = requireRole(id); |
| 106 | + if (!role.getCityId().equals(0L)) { | |
| 107 | + throw new BizException("平台侧不允许删除分站专属角色"); | |
| 108 | + } | |
| 106 | 109 | if (isBuiltIn(role)) { |
| 107 | 110 | throw new BizException("内置角色不允许删除"); |
| 108 | 111 | } |
| ... | ... | @@ -112,6 +115,72 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 112 | 115 | sysRoleMapper.deleteById(role.getId()); |
| 113 | 116 | } |
| 114 | 117 | |
| 118 | + // ---------------------------------------------------------------- | |
| 119 | + // 分站侧:只看/操作本 cityId 的角色 | |
| 120 | + // ---------------------------------------------------------------- | |
| 121 | + | |
| 122 | + @Override | |
| 123 | + public List<AdminRoleVO> listByCityId(Long cityId) { | |
| 124 | + List<SysRole> roles = sysRoleMapper.selectList(new LambdaQueryWrapper<SysRole>() | |
| 125 | + .eq(SysRole::getCityId, cityId) | |
| 126 | + .eq(SysRole::getStatus, 1) | |
| 127 | + .orderByAsc(SysRole::getId)); | |
| 128 | + return toVOList(roles); | |
| 129 | + } | |
| 130 | + | |
| 131 | + @Override | |
| 132 | + public void addForCity(AdminRoleSaveDTO dto, Long cityId) { | |
| 133 | + // 分站角色只能是 SUBSTATION scope | |
| 134 | + if (!AdminRoleScopeEnum.SUBSTATION.name().equals(dto.getRoleScope())) { | |
| 135 | + throw new BizException("分站角色范围只能是 SUBSTATION"); | |
| 136 | + } | |
| 137 | + ensureUniqueCode(dto.getCode(), null, cityId); | |
| 138 | + SysRole role = buildRole(dto, cityId); | |
| 139 | + sysRoleMapper.insert(role); | |
| 140 | + } | |
| 141 | + | |
| 142 | + @Override | |
| 143 | + public void editForCity(AdminRoleSaveDTO dto, Long cityId) { | |
| 144 | + SysRole role = requireRole(dto.getId()); | |
| 145 | + requireOwnedByCity(role, cityId); | |
| 146 | + ensureUniqueCode(dto.getCode(), role.getId(), cityId); | |
| 147 | + role.setCode(dto.getCode().trim()); | |
| 148 | + role.setName(dto.getName().trim()); | |
| 149 | + sysRoleMapper.updateById(role); | |
| 150 | + } | |
| 151 | + | |
| 152 | + @Override | |
| 153 | + public void banForCity(Long id, Long cityId) { | |
| 154 | + SysRole role = requireRole(id); | |
| 155 | + requireOwnedByCity(role, cityId); | |
| 156 | + ensureNotBoundForCity(role.getId(), cityId, "当前角色已绑定账号,不能禁用"); | |
| 157 | + sysRoleMapper.update(null, new LambdaUpdateWrapper<SysRole>() | |
| 158 | + .eq(SysRole::getId, id).set(SysRole::getStatus, 0)); | |
| 159 | + } | |
| 160 | + | |
| 161 | + @Override | |
| 162 | + public void cancelBanForCity(Long id, Long cityId) { | |
| 163 | + SysRole role = requireRole(id); | |
| 164 | + requireOwnedByCity(role, cityId); | |
| 165 | + sysRoleMapper.update(null, new LambdaUpdateWrapper<SysRole>() | |
| 166 | + .eq(SysRole::getId, id).set(SysRole::getStatus, 1)); | |
| 167 | + } | |
| 168 | + | |
| 169 | + @Override | |
| 170 | + @Transactional | |
| 171 | + public void delForCity(Long id, Long cityId) { | |
| 172 | + SysRole role = requireRole(id); | |
| 173 | + requireOwnedByCity(role, cityId); | |
| 174 | + ensureNotBoundForCity(role.getId(), cityId, "当前角色已绑定账号,不能删除"); | |
| 175 | + sysRoleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>() | |
| 176 | + .eq(SysRoleMenu::getRoleId, role.getId())); | |
| 177 | + sysRoleMapper.deleteById(role.getId()); | |
| 178 | + } | |
| 179 | + | |
| 180 | + // ---------------------------------------------------------------- | |
| 181 | + // 私有工具方法 | |
| 182 | + // ---------------------------------------------------------------- | |
| 183 | + | |
| 115 | 184 | private SysRole requireRole(Long id) { |
| 116 | 185 | if (id == null || id < 1) { |
| 117 | 186 | throw new BizException("角色ID不能为空"); |
| ... | ... | @@ -123,6 +192,12 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 123 | 192 | return role; |
| 124 | 193 | } |
| 125 | 194 | |
| 195 | + private void requireOwnedByCity(SysRole role, Long cityId) { | |
| 196 | + if (!cityId.equals(role.getCityId())) { | |
| 197 | + throw new BizException("无权操作其他租户的角色"); | |
| 198 | + } | |
| 199 | + } | |
| 200 | + | |
| 126 | 201 | private void validateScope(String scope) { |
| 127 | 202 | for (AdminRoleScopeEnum value : AdminRoleScopeEnum.values()) { |
| 128 | 203 | if (value.name().equals(scope)) { |
| ... | ... | @@ -132,9 +207,10 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 132 | 207 | throw new BizException("角色范围不合法"); |
| 133 | 208 | } |
| 134 | 209 | |
| 135 | - private void ensureUniqueCode(String code, Long excludeId) { | |
| 210 | + private void ensureUniqueCode(String code, Long excludeId, Long cityId) { | |
| 136 | 211 | SysRole duplicate = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>() |
| 137 | 212 | .eq(SysRole::getCode, code.trim()) |
| 213 | + .eq(SysRole::getCityId, cityId) | |
| 138 | 214 | .ne(excludeId != null, SysRole::getId, excludeId) |
| 139 | 215 | .last("LIMIT 1")); |
| 140 | 216 | if (duplicate != null) { |
| ... | ... | @@ -148,6 +224,34 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 148 | 224 | } |
| 149 | 225 | } |
| 150 | 226 | |
| 227 | + private void ensureNotBoundForCity(Long roleId, Long cityId, String message) { | |
| 228 | + Long count = substationMapper.selectCount(new LambdaQueryWrapper<Substation>() | |
| 229 | + .eq(Substation::getRoleId, roleId) | |
| 230 | + .eq(Substation::getCityId, cityId)); | |
| 231 | + if (count != null && count > 0) { | |
| 232 | + throw new BizException(message); | |
| 233 | + } | |
| 234 | + } | |
| 235 | + | |
| 236 | + private SysRole buildRole(AdminRoleSaveDTO dto, Long cityId) { | |
| 237 | + SysRole role = new SysRole(); | |
| 238 | + role.setCode(dto.getCode().trim()); | |
| 239 | + role.setName(dto.getName().trim()); | |
| 240 | + role.setRoleScope(dto.getRoleScope()); | |
| 241 | + role.setCityId(cityId); | |
| 242 | + role.setStatus(1); | |
| 243 | + role.setCreateTime(System.currentTimeMillis() / 1000); | |
| 244 | + return role; | |
| 245 | + } | |
| 246 | + | |
| 247 | + private List<AdminRoleVO> toVOList(List<SysRole> roles) { | |
| 248 | + List<AdminRoleVO> result = new ArrayList<>(); | |
| 249 | + for (SysRole role : roles) { | |
| 250 | + result.add(toVO(role)); | |
| 251 | + } | |
| 252 | + return result; | |
| 253 | + } | |
| 254 | + | |
| 151 | 255 | private AdminRoleVO toVO(SysRole role) { |
| 152 | 256 | AdminRoleVO vo = new AdminRoleVO(); |
| 153 | 257 | vo.setId(role.getId()); |
| ... | ... | @@ -163,7 +267,9 @@ public class SystemRoleServiceImpl implements SystemRoleService { |
| 163 | 267 | } |
| 164 | 268 | |
| 165 | 269 | private boolean isBuiltIn(SysRole role) { |
| 166 | - return BUILT_IN_CODES.contains(role.getCode()); | |
| 270 | + // 只有全局角色(city_id=0)且编码匹配才是内置角色 | |
| 271 | + return role.getCityId() != null && role.getCityId() == 0L | |
| 272 | + && BUILT_IN_CODES.contains(role.getCode()); | |
| 167 | 273 | } |
| 168 | 274 | |
| 169 | 275 | private long countAdminUsers(Long roleId) { | ... | ... |
src/main/resources/schema.sql
| ... | ... | @@ -379,10 +379,12 @@ CREATE TABLE `sys_role` ( |
| 379 | 379 | `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色编码', |
| 380 | 380 | `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色名称', |
| 381 | 381 | `role_scope` VARCHAR(32) NOT NULL DEFAULT 'PLATFORM' COMMENT '角色归属:PLATFORM/SUBSTATION', |
| 382 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属租户ID:0=平台全局角色,>0=分站租户专属角色', | |
| 382 | 383 | `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', |
| 383 | 384 | `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, |
| 384 | 385 | PRIMARY KEY (`id`), |
| 385 | - UNIQUE KEY `uk_role_code` (`code`) | |
| 386 | + UNIQUE KEY `uk_role_code` (`code`, `city_id`), | |
| 387 | + KEY `idx_city_scope` (`city_id`, `role_scope`) | |
| 386 | 388 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单角色表'; |
| 387 | 389 | |
| 388 | 390 | CREATE TABLE `sys_menu` ( | ... | ... |