Files
urbanLifeline/.kiro/specs/urbanlifeline-to-pigx-migration/permission-annotation-guide.md
2026-01-14 15:42:26 +08:00

11 KiB
Raw Blame History

权限注解转换指南

概述

本指南详细说明了如何将 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 正则替换

  1. 查找模式 (启用正则表达式):
@PreAuthorize\("hasAuthority\('([^:]+):([^:]+):([^']+)'\)"\)
  1. 替换为:
@PreAuthorize("@pms.hasPermission('$1_$2_$3')")
  1. 动作映射 (执行第二次替换):
  • 查找: _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: 可以通过以下方式调试:

  1. 查看 pigx 日志中的权限判断记录
  2. 使用 SecurityUtils.getUser().getAuthorities() 查看当前用户权限
  3. 检查 sys_menu 表中的权限配置
  4. 验证 sys_role_menu 表中的角色权限关联

相关文档