# 权限注解转换指南 ## 概述 本指南详细说明了如何将 urbanLifelineServ 的权限注解迁移到 pigx 平台的权限体系。 ## 核心变更 ### 1. 权限注解格式 | 特性 | urbanLifelineServ | pigx | |------|------------------|------| | 注解类 | @PreAuthorize | @PreAuthorize | | 权限判断方法 | hasAuthority() | @pms.hasPermission() | | 权限标识格式 | module:resource:action | module_resource_action | | 分隔符 | 冒号 `:` | 下划线 `_` | ### 2. 用户信息获取 | 功能 | urbanLifelineServ | pigx | |------|------------------|------| | 获取用户ID | JwtUtils.getUserId() | SecurityUtils.getUser().getId() | | 获取用户名 | JwtUtils.getUsername() | SecurityUtils.getUser().getUsername() | | 获取租户ID | 不支持 | SecurityUtils.getUser().getTenantId() | | 获取部门ID | 不支持 | SecurityUtils.getUser().getDeptId() | ### 3. 响应格式 | 特性 | urbanLifelineServ | pigx | |------|------------------|------| | 响应类 | ResultDomain | R | | 成功响应 | ResultDomain.success(data) | R.ok(data) | | 失败响应 | ResultDomain.fail(msg) | R.failed(msg) | | 列表字段 | dataList | data | ## 转换步骤 ### 步骤1:权限注解转换 #### 1.1 基本转换 ```java // 转换前 @PreAuthorize("hasAuthority('workcase:ticket:create')") public ResultDomain createWorkcase(@RequestBody TbWorkcaseDTO workcase) { return workcaseService.createWorkcase(workcase); } // 转换后 @PreAuthorize("@pms.hasPermission('workcase_ticket_add')") public R createWorkcase(@RequestBody TbWorkcaseDTO workcase) { return R.ok(workcaseService.createWorkcase(workcase)); } ``` #### 1.2 多权限判断 ```java // 转换前 @PreAuthorize("hasAuthority('workcase:ticket:view') or hasAuthority('workcase:ticket:admin')") public ResultDomain> listWorkcase() { return ResultDomain.success(workcaseService.list()); } // 转换后 @PreAuthorize("@pms.hasPermission('workcase_ticket_view') or @pms.hasPermission('workcase_ticket_admin')") public R> listWorkcase() { return R.ok(workcaseService.list()); } ``` ### 步骤2:用户信息获取转换 #### 2.1 Service层用户信息获取 ```java // 转换前 @Service public class WorkcaseServiceImpl implements WorkcaseService { public TbWorkcaseDTO createWorkcase(TbWorkcaseDTO workcase) { Long userId = JwtUtils.getUserId(); String username = JwtUtils.getUsername(); workcase.setCreateBy(userId); workcase.setCreateByName(username); workcase.setCreateTime(new Date()); return workcaseMapper.insert(workcase); } } // 转换后 @Service public class WorkcaseServiceImpl implements WorkcaseService { public TbWorkcaseDTO createWorkcase(TbWorkcaseDTO workcase) { PigxUser user = SecurityUtils.getUser(); workcase.setCreateBy(user.getId()); workcase.setCreateByName(user.getUsername()); workcase.setTenantId(user.getTenantId()); // 新增:租户隔离 workcase.setDeptId(user.getDeptId()); // 新增:部门信息 workcase.setCreateTime(LocalDateTime.now()); return workcaseMapper.insert(workcase); } } ``` #### 2.2 远程用户服务调用 ```java // 转换前 @Service public class WorkcaseServiceImpl { @Autowired private UserService userService; public void assignWorkcase(String workcaseId, Long assigneeId) { User assignee = userService.getById(assigneeId); if (assignee == null) { throw new BusinessException("用户不存在"); } // 处理逻辑... } } // 转换后 @Service public class WorkcaseServiceImpl { @Autowired private RemoteUserService remoteUserService; public void assignWorkcase(String workcaseId, Long assigneeId) { R result = remoteUserService.selectById(assigneeId); if (!result.isSuccess() || result.getData() == null) { throw new BusinessException("用户不存在"); } SysUser assignee = result.getData(); // 处理逻辑... } } ``` ### 步骤3:响应格式转换 #### 3.1 Controller响应转换 ```java // 转换前 @RestController @RequestMapping("/api/workcase") public class WorkcaseController { // 单个对象返回 @GetMapping("/{id}") public ResultDomain getById(@PathVariable String id) { TbWorkcaseDTO workcase = workcaseService.getById(id); if (workcase == null) { return ResultDomain.fail("工单不存在"); } return ResultDomain.success(workcase); } // 列表返回 @GetMapping("/list") public ResultDomain> list() { List list = workcaseService.list(); ResultDomain> result = ResultDomain.success(); result.setDataList(list); // 注意:使用dataList字段 return result; } } // 转换后 @RestController @RequestMapping("/workcase") public class WorkcaseController { // 单个对象返回 @GetMapping("/{id}") public R getById(@PathVariable String id) { TbWorkcaseDTO workcase = workcaseService.getById(id); if (workcase == null) { return R.failed("工单不存在"); } return R.ok(workcase); } // 列表返回 @GetMapping("/list") public R> list() { List list = workcaseService.list(); return R.ok(list); // 直接返回列表,不使用dataList } // 分页返回 @GetMapping("/page") public R> page(Page page) { return R.ok(workcaseService.page(page)); } } ``` ### 步骤4:数据权限适配 #### 4.1 添加租户隔离 ```java // 转换前 - 无租户隔离 @Service public class WorkcaseServiceImpl { public List listMyWorkcase() { Long userId = JwtUtils.getUserId(); return workcaseMapper.selectByUserId(userId); } } // 转换后 - 支持租户隔离 @Service public class WorkcaseServiceImpl { public List listMyWorkcase() { PigxUser user = SecurityUtils.getUser(); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("tenant_id", user.getTenantId()) // 租户隔离 .eq("create_by", user.getId()); return workcaseMapper.selectList(wrapper); } } ``` ## 批量转换工具 ### 使用IDE批量替换 #### IntelliJ IDEA 正则替换 1. **查找模式** (启用正则表达式): ```regex @PreAuthorize\("hasAuthority\('([^:]+):([^:]+):([^']+)'\)"\) ``` 2. **替换为**: ```regex @PreAuthorize("@pms.hasPermission('$1_$2_$3')") ``` 3. **动作映射** (执行第二次替换): - 查找: `_create'` 替换为: `_add'` - 查找: `_update'` 替换为: `_edit'` - 查找: `_delete'` 替换为: `_del'` ### 命令行批量转换脚本 ```bash #!/bin/bash # convert-permissions.sh # 查找所有Java文件并转换权限注解 find . -name "*.java" -type f -exec sed -i.bak \ -e "s/@PreAuthorize(\"hasAuthority('\([^:]*\):\([^:]*\):\([^']*\)')\")/@PreAuthorize(\"@pms.hasPermission('\1_\2_\3')\")/g" \ -e "s/_create')/_add')/g" \ -e "s/_update')/_edit')/g" \ -e "s/_delete')/_del')/g" {} \; # 转换JwtUtils为SecurityUtils find . -name "*.java" -type f -exec sed -i.bak \ -e "s/JwtUtils.getUserId()/SecurityUtils.getUser().getId()/g" \ -e "s/JwtUtils.getUsername()/SecurityUtils.getUser().getUsername()/g" {} \; # 转换ResultDomain为R find . -name "*.java" -type f -exec sed -i.bak \ -e "s/ResultDomain.success(/R.ok(/g" \ -e "s/ResultDomain.fail(/R.failed(/g" \ -e "s/ResultDomain getMyWorkcase(@PathVariable String id, TbWorkcaseDTO workcase) { // 这种情况需要根据pigx的数据权限机制重新设计 } ``` ### 2. 自定义权限判断 ```java // 转换前 if (SecurityContextHolder.getContext().getAuthentication().getAuthorities() .contains(new SimpleGrantedAuthority("workcase:ticket:admin"))) { // 管理员逻辑 } // 转换后 if (SecurityUtils.getUser().getAuthorities() .contains("workcase_ticket_admin")) { // 管理员逻辑 } ``` ### 3. 异步任务中的用户信息 ```java // 转换前 @Async public void processAsync() { Long userId = JwtUtils.getUserId(); // 异步线程中可能获取不到 } // 转换后 @Async public void processAsync() { // 需要在调用异步方法前获取用户信息并传递 PigxUser user = SecurityUtils.getUser(); processAsyncWithUser(user); } ``` ## 测试验证清单 ### 权限测试 - [ ] 所有 @PreAuthorize 注解已转换为 @pms.hasPermission 格式 - [ ] 权限标识符已从冒号改为下划线 - [ ] 动作已正确映射 (create→add, update→edit, delete→del) - [ ] 多权限判断逻辑正确 ### 用户信息测试 - [ ] SecurityUtils.getUser() 能正确获取用户信息 - [ ] 租户ID正确设置到业务数据 - [ ] RemoteUserService 调用正常 ### 响应格式测试 - [ ] 所有接口返回 R 格式 - [ ] 前端能正确解析新的响应格式 - [ ] 错误信息正确传递 ### 数据权限测试 - [ ] 租户数据隔离正常 - [ ] 部门数据权限正常 - [ ] 个人数据权限正常 ## 常见问题 ### Q1: @pms.hasPermission 中的 @pms 是什么? A: `@pms` 是 pigx 权限管理系统的 SpEL 表达式前缀,用于调用权限判断方法。这是 pigx 框架的固定写法,必须保留。 ### Q2: 为什么要将 create 改为 add? A: 这是 pigx 平台的命名规范,保持统一的动作命名有助于权限管理的标准化。常见映射: - create → add (新增) - update → edit (编辑) - delete → del (删除) - view → view (查看) ### Q3: 如何处理没有对应 pigx 用户的情况? A: 所有业务用户必须在 pigx 的 sys_user 表中存在。如果是数据迁移,需要先创建对应的 pigx 用户,或建立用户映射关系。 ### Q4: 租户ID是必须的吗? A: 是的。pigx 是多租户系统,所有业务表都需要 tenant_id 字段。即使是单租户使用,也需要设置默认租户ID(通常为1)。 ### Q5: 如何调试权限问题? A: 可以通过以下方式调试: 1. 查看 pigx 日志中的权限判断记录 2. 使用 SecurityUtils.getUser().getAuthorities() 查看当前用户权限 3. 检查 sys_menu 表中的权限配置 4. 验证 sys_role_menu 表中的角色权限关联 ## 相关文档 - [权限标识映射表](./permission-mapping.md) - [数据库迁移指南](./database-migration.md) - [前端适配指南](./frontend-migration.md)