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
| @@ -18,6 +18,9 @@ public class SysRole { | @@ -18,6 +18,9 @@ public class SysRole { | ||
| 18 | 18 | ||
| 19 | private String roleScope; | 19 | private String roleScope; |
| 20 | 20 | ||
| 21 | + /** 所属租户ID:0=平台全局角色,>0=分站租户专属角色 */ | ||
| 22 | + private Long cityId; | ||
| 23 | + | ||
| 21 | private Integer status; | 24 | private Integer status; |
| 22 | 25 | ||
| 23 | private Long createTime; | 26 | private Long createTime; |
src/main/java/com/diligrp/rider/service/RoleScopeGuardService.java
| @@ -4,4 +4,7 @@ import com.diligrp.rider.entity.SysRole; | @@ -4,4 +4,7 @@ import com.diligrp.rider.entity.SysRole; | ||
| 4 | 4 | ||
| 5 | public interface RoleScopeGuardService { | 5 | public interface RoleScopeGuardService { |
| 6 | SysRole requireRole(Long roleId, String requiredScope); | 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,15 +6,36 @@ import com.diligrp.rider.vo.AdminRoleVO; | ||
| 6 | import java.util.List; | 6 | import java.util.List; |
| 7 | 7 | ||
| 8 | public interface SystemRoleService { | 8 | public interface SystemRoleService { |
| 9 | + /** 平台侧调用:只返回平台全局角色(city_id=0) */ | ||
| 9 | List<AdminRoleVO> list(boolean includeDisabled); | 10 | List<AdminRoleVO> list(boolean includeDisabled); |
| 10 | 11 | ||
| 12 | + /** 分站侧调用:只返回该租户自己的角色(city_id=cityId) */ | ||
| 13 | + List<AdminRoleVO> listByCityId(Long cityId); | ||
| 14 | + | ||
| 15 | + /** 平台侧新增角色(city_id=0) */ | ||
| 11 | void add(AdminRoleSaveDTO dto); | 16 | void add(AdminRoleSaveDTO dto); |
| 12 | 17 | ||
| 18 | + /** 分站侧新增角色(city_id=cityId) */ | ||
| 19 | + void addForCity(AdminRoleSaveDTO dto, Long cityId); | ||
| 20 | + | ||
| 21 | + /** 平台侧编辑(只能编辑 city_id=0 的角色) */ | ||
| 13 | void edit(AdminRoleSaveDTO dto); | 22 | void edit(AdminRoleSaveDTO dto); |
| 14 | 23 | ||
| 24 | + /** 分站侧编辑(只能编辑本 cityId 的角色) */ | ||
| 25 | + void editForCity(AdminRoleSaveDTO dto, Long cityId); | ||
| 26 | + | ||
| 15 | void ban(Long id); | 27 | void ban(Long id); |
| 16 | 28 | ||
| 29 | + /** 分站侧禁用(校验 cityId 归属) */ | ||
| 30 | + void banForCity(Long id, Long cityId); | ||
| 31 | + | ||
| 17 | void cancelBan(Long id); | 32 | void cancelBan(Long id); |
| 18 | 33 | ||
| 34 | + /** 分站侧启用(校验 cityId 归属) */ | ||
| 35 | + void cancelBanForCity(Long id, Long cityId); | ||
| 36 | + | ||
| 19 | void del(Long id); | 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,8 +47,10 @@ public class AdminMenuServiceImpl implements AdminMenuService { | ||
| 47 | role = sysRoleMapper.selectById(roleId); | 47 | role = sysRoleMapper.selectById(roleId); |
| 48 | } | 48 | } |
| 49 | if (role == null) { | 49 | if (role == null) { |
| 50 | + // Fallback:找全局内置角色(city_id=0) | ||
| 50 | role = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>() | 51 | role = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>() |
| 51 | .eq(SysRole::getCode, fallbackCode) | 52 | .eq(SysRole::getCode, fallbackCode) |
| 53 | + .eq(SysRole::getCityId, 0L) | ||
| 52 | .eq(SysRole::getStatus, 1) | 54 | .eq(SysRole::getStatus, 1) |
| 53 | .last("LIMIT 1")); | 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,7 +40,7 @@ public class AdminUserManageServiceImpl implements AdminUserManageService { | ||
| 40 | if (exists > 0) { | 40 | if (exists > 0) { |
| 41 | throw new BizException("账号已存在,请更换"); | 41 | throw new BizException("账号已存在,请更换"); |
| 42 | } | 42 | } |
| 43 | - roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name()); | 43 | + roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name(), 0L); |
| 44 | adminUser.setUserPass(encryptPass(adminUser.getUserPass())); | 44 | adminUser.setUserPass(encryptPass(adminUser.getUserPass())); |
| 45 | adminUser.setUserStatus(1); | 45 | adminUser.setUserStatus(1); |
| 46 | adminUser.setCreateTime(System.currentTimeMillis() / 1000); | 46 | adminUser.setCreateTime(System.currentTimeMillis() / 1000); |
| @@ -53,7 +53,7 @@ public class AdminUserManageServiceImpl implements AdminUserManageService { | @@ -53,7 +53,7 @@ public class AdminUserManageServiceImpl implements AdminUserManageService { | ||
| 53 | if (existing == null) { | 53 | if (existing == null) { |
| 54 | throw new BizException("平台账号不存在"); | 54 | throw new BizException("平台账号不存在"); |
| 55 | } | 55 | } |
| 56 | - roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name()); | 56 | + roleScopeGuardService.requireRole(adminUser.getRoleId(), AdminRoleScopeEnum.PLATFORM.name(), 0L); |
| 57 | if (adminUser.getUserPass() == null || adminUser.getUserPass().isBlank()) { | 57 | if (adminUser.getUserPass() == null || adminUser.getUserPass().isBlank()) { |
| 58 | adminUser.setUserPass(null); | 58 | adminUser.setUserPass(null); |
| 59 | } else { | 59 | } else { |
src/main/java/com/diligrp/rider/service/impl/MenuBootstrapServiceImpl.java
| @@ -60,10 +60,12 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | @@ -60,10 +60,12 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | ||
| 60 | defaults.add(menu("dashboard", "工作台", "MENU", "/dashboard", "HomeOutlined", 0L, MenuScopeEnum.BOTH, 10)); | 60 | defaults.add(menu("dashboard", "工作台", "MENU", "/dashboard", "HomeOutlined", 0L, MenuScopeEnum.BOTH, 10)); |
| 61 | defaults.add(menu("city.manage", "租户管理", "MENU", "/city", "GlobalOutlined", 0L, MenuScopeEnum.PLATFORM, 20)); | 61 | defaults.add(menu("city.manage", "租户管理", "MENU", "/city", "GlobalOutlined", 0L, MenuScopeEnum.PLATFORM, 20)); |
| 62 | defaults.add(menu("substation.manage", "分站管理", "MENU", "/substation", "ApartmentOutlined", 0L, MenuScopeEnum.PLATFORM, 30)); | 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 | defaults.add(menu("merchant.root", "商家管理", "DIR", "", "ShopOutlined", 0L, MenuScopeEnum.PLATFORM, 40)); | 64 | defaults.add(menu("merchant.root", "商家管理", "DIR", "", "ShopOutlined", 0L, MenuScopeEnum.PLATFORM, 40)); |
| 64 | defaults.add(menu("merchant.enter", "入驻申请", "MENU", "/merchant/enter", "", 0L, MenuScopeEnum.PLATFORM, 41)); | 65 | defaults.add(menu("merchant.enter", "入驻申请", "MENU", "/merchant/enter", "", 0L, MenuScopeEnum.PLATFORM, 41)); |
| 65 | defaults.add(menu("merchant.store", "店铺管理", "MENU", "/merchant/store", "", 0L, MenuScopeEnum.PLATFORM, 42)); | 66 | defaults.add(menu("merchant.store", "店铺管理", "MENU", "/merchant/store", "", 0L, MenuScopeEnum.PLATFORM, 42)); |
| 66 | defaults.add(menu("rider.list", "骑手管理", "MENU", "/rider", "UserOutlined", 0L, MenuScopeEnum.BOTH, 50)); | 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 | defaults.add(menu("rider.evaluate", "骑手评价", "MENU", "/rider/evaluate", "StarOutlined", 0L, MenuScopeEnum.BOTH, 60)); | 69 | defaults.add(menu("rider.evaluate", "骑手评价", "MENU", "/rider/evaluate", "StarOutlined", 0L, MenuScopeEnum.BOTH, 60)); |
| 68 | defaults.add(menu("order.root", "订单管理", "DIR", "", "UnorderedListOutlined", 0L, MenuScopeEnum.BOTH, 70)); | 70 | defaults.add(menu("order.root", "订单管理", "DIR", "", "UnorderedListOutlined", 0L, MenuScopeEnum.BOTH, 70)); |
| 69 | defaults.add(menu("order.list", "订单列表", "MENU", "/order", "", 0L, MenuScopeEnum.BOTH, 71)); | 71 | defaults.add(menu("order.list", "订单列表", "MENU", "/order", "", 0L, MenuScopeEnum.BOTH, 71)); |
| @@ -80,6 +82,10 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | @@ -80,6 +82,10 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | ||
| 80 | defaults.add(menu("system.role", "角色管理", "MENU", "/system/role", "", 0L, MenuScopeEnum.PLATFORM, 102)); | 82 | defaults.add(menu("system.role", "角色管理", "MENU", "/system/role", "", 0L, MenuScopeEnum.PLATFORM, 102)); |
| 81 | defaults.add(menu("system.role_menu", "角色菜单", "MENU", "/system/role-menu", "", 0L, MenuScopeEnum.PLATFORM, 103)); | 83 | defaults.add(menu("system.role_menu", "角色菜单", "MENU", "/system/role-menu", "", 0L, MenuScopeEnum.PLATFORM, 103)); |
| 82 | defaults.add(menu("admin.user", "平台账号", "MENU", "/admin-user", "", 0L, MenuScopeEnum.PLATFORM, 104)); | 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 | Map<String, SysMenu> persisted = new LinkedHashMap<>(); | 90 | Map<String, SysMenu> persisted = new LinkedHashMap<>(); |
| 85 | for (SysMenu menu : defaults) { | 91 | for (SysMenu menu : defaults) { |
| @@ -111,6 +117,11 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | @@ -111,6 +117,11 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | ||
| 111 | persisted.get("system.role_menu").setListOrder(103); | 117 | persisted.get("system.role_menu").setListOrder(103); |
| 112 | persisted.get("admin.user").setParentId(persisted.get("system.root").getId()); | 118 | persisted.get("admin.user").setParentId(persisted.get("system.root").getId()); |
| 113 | persisted.get("admin.user").setListOrder(104); | 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 | for (SysMenu menu : persisted.values()) { | 126 | for (SysMenu menu : persisted.values()) { |
| 116 | sysMenuMapper.updateById(menu); | 127 | sysMenuMapper.updateById(menu); |
src/main/java/com/diligrp/rider/service/impl/RoleScopeGuardServiceImpl.java
| @@ -16,6 +16,11 @@ public class RoleScopeGuardServiceImpl implements RoleScopeGuardService { | @@ -16,6 +16,11 @@ public class RoleScopeGuardServiceImpl implements RoleScopeGuardService { | ||
| 16 | 16 | ||
| 17 | @Override | 17 | @Override |
| 18 | public SysRole requireRole(Long roleId, String requiredScope) { | 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 | if (roleId == null || roleId < 1) { | 24 | if (roleId == null || roleId < 1) { |
| 20 | throw new BizException("角色不能为空"); | 25 | throw new BizException("角色不能为空"); |
| 21 | } | 26 | } |
| @@ -29,6 +34,14 @@ public class RoleScopeGuardServiceImpl implements RoleScopeGuardService { | @@ -29,6 +34,14 @@ public class RoleScopeGuardServiceImpl implements RoleScopeGuardService { | ||
| 29 | if (!requiredScope.equals(role.getRoleScope())) { | 34 | if (!requiredScope.equals(role.getRoleScope())) { |
| 30 | throw new BizException("角色归属不匹配"); | 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 | return role; | 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,7 +44,7 @@ public class SubstationServiceImpl implements SubstationService { | ||
| 44 | .eq(Substation::getUserLogin, substation.getUserLogin())); | 44 | .eq(Substation::getUserLogin, substation.getUserLogin())); |
| 45 | if (loginExists > 0) throw new BizException("账号已存在,请更换"); | 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 | substation.setUserPass(encryptPass(substation.getUserPass())); | 48 | substation.setUserPass(encryptPass(substation.getUserPass())); |
| 49 | substation.setUserStatus(1); | 49 | substation.setUserStatus(1); |
| 50 | substation.setCreateTime(System.currentTimeMillis() / 1000); | 50 | substation.setCreateTime(System.currentTimeMillis() / 1000); |
| @@ -55,7 +55,7 @@ public class SubstationServiceImpl implements SubstationService { | @@ -55,7 +55,7 @@ public class SubstationServiceImpl implements SubstationService { | ||
| 55 | public void edit(Substation substation) { | 55 | public void edit(Substation substation) { |
| 56 | Substation existing = substationMapper.selectById(substation.getId()); | 56 | Substation existing = substationMapper.selectById(substation.getId()); |
| 57 | if (existing == null) throw new BizException("分站管理员不存在"); | 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 | if (substation.getUserPass() == null || substation.getUserPass().isBlank()) { | 60 | if (substation.getUserPass() == null || substation.getUserPass().isBlank()) { |
| 61 | substation.setUserPass(null); | 61 | substation.setUserPass(null); |
src/main/java/com/diligrp/rider/service/impl/SystemRoleMenuServiceImpl.java
| @@ -30,10 +30,47 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | @@ -30,10 +30,47 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | ||
| 30 | private final SysRoleMenuMapper sysRoleMenuMapper; | 30 | private final SysRoleMenuMapper sysRoleMenuMapper; |
| 31 | private final SystemMenuServiceImpl systemMenuService; | 31 | private final SystemMenuServiceImpl systemMenuService; |
| 32 | 32 | ||
| 33 | + // ---------------------------------------------------------------- | ||
| 34 | + // 平台侧:不限菜单 scope(平台管理员可分配所有菜单) | ||
| 35 | + // ---------------------------------------------------------------- | ||
| 36 | + | ||
| 33 | @Override | 37 | @Override |
| 34 | public List<AdminRoleMenuTreeVO> getRoleMenuTree(Long roleId) { | 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 | List<SysMenu> allowedMenus = filterMenusByScope(systemMenuService.listAllMenus(), role); | 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 | List<SysRoleMenu> assigned = sysRoleMenuMapper.selectList(new LambdaQueryWrapper<SysRoleMenu>() | 74 | List<SysRoleMenu> assigned = sysRoleMenuMapper.selectList(new LambdaQueryWrapper<SysRoleMenu>() |
| 38 | .eq(SysRoleMenu::getRoleId, roleId)); | 75 | .eq(SysRoleMenu::getRoleId, roleId)); |
| 39 | Set<Long> assignedIds = assigned.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet()); | 76 | Set<Long> assignedIds = assigned.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet()); |
| @@ -44,11 +81,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | @@ -44,11 +81,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | ||
| 44 | return systemMenuService.buildTree(allowedMenus, checkedMap, false); | 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 | Set<Long> allowedIds = allowedMenus.stream().map(SysMenu::getId).collect(Collectors.toSet()); | 85 | Set<Long> allowedIds = allowedMenus.stream().map(SysMenu::getId).collect(Collectors.toSet()); |
| 53 | List<Long> menuIds = dto.getMenuIds() == null ? List.of() : dto.getMenuIds(); | 86 | List<Long> menuIds = dto.getMenuIds() == null ? List.of() : dto.getMenuIds(); |
| 54 | for (Long menuId : menuIds) { | 87 | for (Long menuId : menuIds) { |
| @@ -56,7 +89,6 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | @@ -56,7 +89,6 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | ||
| 56 | throw new BizException("存在不允许分配的菜单"); | 89 | throw new BizException("存在不允许分配的菜单"); |
| 57 | } | 90 | } |
| 58 | } | 91 | } |
| 59 | - | ||
| 60 | sysRoleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>() | 92 | sysRoleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>() |
| 61 | .eq(SysRoleMenu::getRoleId, roleId)); | 93 | .eq(SysRoleMenu::getRoleId, roleId)); |
| 62 | long now = System.currentTimeMillis() / 1000; | 94 | long now = System.currentTimeMillis() / 1000; |
| @@ -69,11 +101,15 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | @@ -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 | SysRole role = sysRoleMapper.selectById(roleId); | 105 | SysRole role = sysRoleMapper.selectById(roleId); |
| 74 | if (role == null || role.getStatus() == null || role.getStatus() != 1) { | 106 | if (role == null || role.getStatus() == null || role.getStatus() != 1) { |
| 75 | throw new BizException("角色不存在或已禁用"); | 107 | throw new BizException("角色不存在或已禁用"); |
| 76 | } | 108 | } |
| 109 | + // cityId != null 时校验归属(分站侧调用) | ||
| 110 | + if (cityId != null && !cityId.equals(role.getCityId())) { | ||
| 111 | + throw new BizException("无权操作其他租户的角色"); | ||
| 112 | + } | ||
| 77 | return role; | 113 | return role; |
| 78 | } | 114 | } |
| 79 | 115 | ||
| @@ -81,6 +117,14 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | @@ -81,6 +117,14 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService { | ||
| 81 | return menus.stream().filter(menu -> isScopeAllowed(role, menu)).collect(Collectors.toList()); | 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 | private boolean isScopeAllowed(SysRole role, SysMenu menu) { | 128 | private boolean isScopeAllowed(SysRole role, SysMenu menu) { |
| 85 | if (MenuScopeEnum.BOTH.name().equals(menu.getMenuScope())) { | 129 | if (MenuScopeEnum.BOTH.name().equals(menu.getMenuScope())) { |
| 86 | return true; | 130 | return true; |
src/main/java/com/diligrp/rider/service/impl/SystemRoleServiceImpl.java
| @@ -27,6 +27,7 @@ import java.util.Set; | @@ -27,6 +27,7 @@ import java.util.Set; | ||
| 27 | @RequiredArgsConstructor | 27 | @RequiredArgsConstructor |
| 28 | public class SystemRoleServiceImpl implements SystemRoleService { | 28 | public class SystemRoleServiceImpl implements SystemRoleService { |
| 29 | 29 | ||
| 30 | + /** 内置角色编码(city_id=0 且 code 在此集合内,不允许编辑/删除) */ | ||
| 30 | private static final Set<String> BUILT_IN_CODES = Set.of("platform_admin", "substation_admin"); | 31 | private static final Set<String> BUILT_IN_CODES = Set.of("platform_admin", "substation_admin"); |
| 31 | 32 | ||
| 32 | private final SysRoleMapper sysRoleMapper; | 33 | private final SysRoleMapper sysRoleMapper; |
| @@ -34,38 +35,33 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -34,38 +35,33 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 34 | private final AdminUserMapper adminUserMapper; | 35 | private final AdminUserMapper adminUserMapper; |
| 35 | private final SubstationMapper substationMapper; | 36 | private final SubstationMapper substationMapper; |
| 36 | 37 | ||
| 38 | + // ---------------------------------------------------------------- | ||
| 39 | + // 平台侧:只看/操作 city_id=0 的全局角色 | ||
| 40 | + // ---------------------------------------------------------------- | ||
| 41 | + | ||
| 37 | @Override | 42 | @Override |
| 38 | public List<AdminRoleVO> list(boolean includeDisabled) { | 43 | public List<AdminRoleVO> list(boolean includeDisabled) { |
| 39 | List<SysRole> roles = sysRoleMapper.selectList(new LambdaQueryWrapper<SysRole>() | 44 | List<SysRole> roles = sysRoleMapper.selectList(new LambdaQueryWrapper<SysRole>() |
| 45 | + .eq(SysRole::getCityId, 0L) | ||
| 40 | .eq(!includeDisabled, SysRole::getStatus, 1) | 46 | .eq(!includeDisabled, SysRole::getStatus, 1) |
| 41 | .orderByAsc(SysRole::getId)); | 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 | @Override | 51 | @Override |
| 50 | public void add(AdminRoleSaveDTO dto) { | 52 | public void add(AdminRoleSaveDTO dto) { |
| 51 | validateScope(dto.getRoleScope()); | 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 | sysRoleMapper.insert(role); | 56 | sysRoleMapper.insert(role); |
| 61 | } | 57 | } |
| 62 | 58 | ||
| 63 | @Override | 59 | @Override |
| 64 | public void edit(AdminRoleSaveDTO dto) { | 60 | public void edit(AdminRoleSaveDTO dto) { |
| 65 | - if (dto.getId() == null || dto.getId() < 1) { | ||
| 66 | - throw new BizException("角色ID不能为空"); | ||
| 67 | - } | ||
| 68 | SysRole role = requireRole(dto.getId()); | 61 | SysRole role = requireRole(dto.getId()); |
| 62 | + if (!role.getCityId().equals(0L)) { | ||
| 63 | + throw new BizException("平台侧不允许编辑分站专属角色"); | ||
| 64 | + } | ||
| 69 | if (isBuiltIn(role)) { | 65 | if (isBuiltIn(role)) { |
| 70 | throw new BizException("内置角色不允许编辑"); | 66 | throw new BizException("内置角色不允许编辑"); |
| 71 | } | 67 | } |
| @@ -73,7 +69,7 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -73,7 +69,7 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 73 | if (!role.getRoleScope().equals(dto.getRoleScope())) { | 69 | if (!role.getRoleScope().equals(dto.getRoleScope())) { |
| 74 | throw new BizException("角色范围不允许修改"); | 70 | throw new BizException("角色范围不允许修改"); |
| 75 | } | 71 | } |
| 76 | - ensureUniqueCode(dto.getCode(), role.getId()); | 72 | + ensureUniqueCode(dto.getCode(), role.getId(), 0L); |
| 77 | role.setCode(dto.getCode().trim()); | 73 | role.setCode(dto.getCode().trim()); |
| 78 | role.setName(dto.getName().trim()); | 74 | role.setName(dto.getName().trim()); |
| 79 | sysRoleMapper.updateById(role); | 75 | sysRoleMapper.updateById(role); |
| @@ -82,27 +78,34 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -82,27 +78,34 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 82 | @Override | 78 | @Override |
| 83 | public void ban(Long id) { | 79 | public void ban(Long id) { |
| 84 | SysRole role = requireRole(id); | 80 | SysRole role = requireRole(id); |
| 81 | + if (!role.getCityId().equals(0L)) { | ||
| 82 | + throw new BizException("平台侧不允许操作分站专属角色"); | ||
| 83 | + } | ||
| 85 | if (isBuiltIn(role)) { | 84 | if (isBuiltIn(role)) { |
| 86 | throw new BizException("内置角色不允许禁用"); | 85 | throw new BizException("内置角色不允许禁用"); |
| 87 | } | 86 | } |
| 88 | ensureNotBound(role.getId(), "当前角色已绑定账号,不能禁用"); | 87 | ensureNotBound(role.getId(), "当前角色已绑定账号,不能禁用"); |
| 89 | sysRoleMapper.update(null, new LambdaUpdateWrapper<SysRole>() | 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 | @Override | 92 | @Override |
| 95 | public void cancelBan(Long id) { | 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 | sysRoleMapper.update(null, new LambdaUpdateWrapper<SysRole>() | 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 | @Override | 102 | @Override |
| 103 | @Transactional | 103 | @Transactional |
| 104 | public void del(Long id) { | 104 | public void del(Long id) { |
| 105 | SysRole role = requireRole(id); | 105 | SysRole role = requireRole(id); |
| 106 | + if (!role.getCityId().equals(0L)) { | ||
| 107 | + throw new BizException("平台侧不允许删除分站专属角色"); | ||
| 108 | + } | ||
| 106 | if (isBuiltIn(role)) { | 109 | if (isBuiltIn(role)) { |
| 107 | throw new BizException("内置角色不允许删除"); | 110 | throw new BizException("内置角色不允许删除"); |
| 108 | } | 111 | } |
| @@ -112,6 +115,72 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -112,6 +115,72 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 112 | sysRoleMapper.deleteById(role.getId()); | 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 | private SysRole requireRole(Long id) { | 184 | private SysRole requireRole(Long id) { |
| 116 | if (id == null || id < 1) { | 185 | if (id == null || id < 1) { |
| 117 | throw new BizException("角色ID不能为空"); | 186 | throw new BizException("角色ID不能为空"); |
| @@ -123,6 +192,12 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -123,6 +192,12 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 123 | return role; | 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 | private void validateScope(String scope) { | 201 | private void validateScope(String scope) { |
| 127 | for (AdminRoleScopeEnum value : AdminRoleScopeEnum.values()) { | 202 | for (AdminRoleScopeEnum value : AdminRoleScopeEnum.values()) { |
| 128 | if (value.name().equals(scope)) { | 203 | if (value.name().equals(scope)) { |
| @@ -132,9 +207,10 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -132,9 +207,10 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 132 | throw new BizException("角色范围不合法"); | 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 | SysRole duplicate = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>() | 211 | SysRole duplicate = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>() |
| 137 | .eq(SysRole::getCode, code.trim()) | 212 | .eq(SysRole::getCode, code.trim()) |
| 213 | + .eq(SysRole::getCityId, cityId) | ||
| 138 | .ne(excludeId != null, SysRole::getId, excludeId) | 214 | .ne(excludeId != null, SysRole::getId, excludeId) |
| 139 | .last("LIMIT 1")); | 215 | .last("LIMIT 1")); |
| 140 | if (duplicate != null) { | 216 | if (duplicate != null) { |
| @@ -148,6 +224,34 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -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 | private AdminRoleVO toVO(SysRole role) { | 255 | private AdminRoleVO toVO(SysRole role) { |
| 152 | AdminRoleVO vo = new AdminRoleVO(); | 256 | AdminRoleVO vo = new AdminRoleVO(); |
| 153 | vo.setId(role.getId()); | 257 | vo.setId(role.getId()); |
| @@ -163,7 +267,9 @@ public class SystemRoleServiceImpl implements SystemRoleService { | @@ -163,7 +267,9 @@ public class SystemRoleServiceImpl implements SystemRoleService { | ||
| 163 | } | 267 | } |
| 164 | 268 | ||
| 165 | private boolean isBuiltIn(SysRole role) { | 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 | private long countAdminUsers(Long roleId) { | 275 | private long countAdminUsers(Long roleId) { |
src/main/resources/schema.sql
| @@ -379,10 +379,12 @@ CREATE TABLE `sys_role` ( | @@ -379,10 +379,12 @@ CREATE TABLE `sys_role` ( | ||
| 379 | `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色编码', | 379 | `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色编码', |
| 380 | `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色名称', | 380 | `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色名称', |
| 381 | `role_scope` VARCHAR(32) NOT NULL DEFAULT 'PLATFORM' COMMENT '角色归属:PLATFORM/SUBSTATION', | 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 | `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', | 383 | `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', |
| 383 | `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | 384 | `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, |
| 384 | PRIMARY KEY (`id`), | 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 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单角色表'; | 388 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单角色表'; |
| 387 | 389 | ||
| 388 | CREATE TABLE `sys_menu` ( | 390 | CREATE TABLE `sys_menu` ( |