11 KiB
11 KiB
权限注解转换指南
概述
本指南详细说明了如何将 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 基本转换
// 转换前
@PreAuthorize("hasAuthority('workcase:ticket:create')")
public ResultDomain<TbWorkcaseDTO> createWorkcase(@RequestBody TbWorkcaseDTO workcase) {
return workcaseService.createWorkcase(workcase);
}
// 转换后
@PreAuthorize("@pms.hasPermission('workcase_ticket_add')")
public R<TbWorkcaseDTO> createWorkcase(@RequestBody TbWorkcaseDTO workcase) {
return R.ok(workcaseService.createWorkcase(workcase));
}
1.2 多权限判断
// 转换前
@PreAuthorize("hasAuthority('workcase:ticket:view') or hasAuthority('workcase:ticket:admin')")
public ResultDomain<List<TbWorkcaseDTO>> listWorkcase() {
return ResultDomain.success(workcaseService.list());
}
// 转换后
@PreAuthorize("@pms.hasPermission('workcase_ticket_view') or @pms.hasPermission('workcase_ticket_admin')")
public R<List<TbWorkcaseDTO>> listWorkcase() {
return R.ok(workcaseService.list());
}
步骤2:用户信息获取转换
2.1 Service层用户信息获取
// 转换前
@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 远程用户服务调用
// 转换前
@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<SysUser> result = remoteUserService.selectById(assigneeId);
if (!result.isSuccess() || result.getData() == null) {
throw new BusinessException("用户不存在");
}
SysUser assignee = result.getData();
// 处理逻辑...
}
}
步骤3:响应格式转换
3.1 Controller响应转换
// 转换前
@RestController
@RequestMapping("/api/workcase")
public class WorkcaseController {
// 单个对象返回
@GetMapping("/{id}")
public ResultDomain<TbWorkcaseDTO> getById(@PathVariable String id) {
TbWorkcaseDTO workcase = workcaseService.getById(id);
if (workcase == null) {
return ResultDomain.fail("工单不存在");
}
return ResultDomain.success(workcase);
}
// 列表返回
@GetMapping("/list")
public ResultDomain<List<TbWorkcaseDTO>> list() {
List<TbWorkcaseDTO> list = workcaseService.list();
ResultDomain<List<TbWorkcaseDTO>> result = ResultDomain.success();
result.setDataList(list); // 注意:使用dataList字段
return result;
}
}
// 转换后
@RestController
@RequestMapping("/workcase")
public class WorkcaseController {
// 单个对象返回
@GetMapping("/{id}")
public R<TbWorkcaseDTO> getById(@PathVariable String id) {
TbWorkcaseDTO workcase = workcaseService.getById(id);
if (workcase == null) {
return R.failed("工单不存在");
}
return R.ok(workcase);
}
// 列表返回
@GetMapping("/list")
public R<List<TbWorkcaseDTO>> list() {
List<TbWorkcaseDTO> list = workcaseService.list();
return R.ok(list); // 直接返回列表,不使用dataList
}
// 分页返回
@GetMapping("/page")
public R<IPage<TbWorkcaseDTO>> page(Page page) {
return R.ok(workcaseService.page(page));
}
}
步骤4:数据权限适配
4.1 添加租户隔离
// 转换前 - 无租户隔离
@Service
public class WorkcaseServiceImpl {
public List<TbWorkcaseDTO> listMyWorkcase() {
Long userId = JwtUtils.getUserId();
return workcaseMapper.selectByUserId(userId);
}
}
// 转换后 - 支持租户隔离
@Service
public class WorkcaseServiceImpl {
public List<TbWorkcaseDTO> listMyWorkcase() {
PigxUser user = SecurityUtils.getUser();
QueryWrapper<TbWorkcaseDTO> wrapper = new QueryWrapper<>();
wrapper.eq("tenant_id", user.getTenantId()) // 租户隔离
.eq("create_by", user.getId());
return workcaseMapper.selectList(wrapper);
}
}
批量转换工具
使用IDE批量替换
IntelliJ IDEA 正则替换
- 查找模式 (启用正则表达式):
@PreAuthorize\("hasAuthority\('([^:]+):([^:]+):([^']+)'\)"\)
- 替换为:
@PreAuthorize("@pms.hasPermission('$1_$2_$3')")
- 动作映射 (执行第二次替换):
- 查找:
_create'替换为:_add' - 查找:
_update'替换为:_edit' - 查找:
_delete'替换为:_del'
命令行批量转换脚本
#!/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</R</g" {} \;
需要手工处理的情况
1. 复杂的权限逻辑
// 需要手工审查的复杂权限
@PreAuthorize("hasAuthority('workcase:ticket:view') and #workcase.createBy == authentication.principal.userId")
public ResultDomain<TbWorkcaseDTO> getMyWorkcase(@PathVariable String id, TbWorkcaseDTO workcase) {
// 这种情况需要根据pigx的数据权限机制重新设计
}
2. 自定义权限判断
// 转换前
if (SecurityContextHolder.getContext().getAuthentication().getAuthorities()
.contains(new SimpleGrantedAuthority("workcase:ticket:admin"))) {
// 管理员逻辑
}
// 转换后
if (SecurityUtils.getUser().getAuthorities()
.contains("workcase_ticket_admin")) {
// 管理员逻辑
}
3. 异步任务中的用户信息
// 转换前
@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: 可以通过以下方式调试:
- 查看 pigx 日志中的权限判断记录
- 使用 SecurityUtils.getUser().getAuthorities() 查看当前用户权限
- 检查 sys_menu 表中的权限配置
- 验证 sys_role_menu 表中的角色权限关联