# 权限注解转换指南

## 概述
本指南详细说明了如何将 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<T> | R<T> |
| 成功响应 | 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<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 多权限判断

```java
// 转换前
@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层用户信息获取

```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<SysUser> 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<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 添加租户隔离

```java
// 转换前 - 无租户隔离
@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. **查找模式** (启用正则表达式):
```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</R</g" {} \;
```

## 需要手工处理的情况

### 1. 复杂的权限逻辑

```java
// 需要手工审查的复杂权限
@PreAuthorize("hasAuthority('workcase:ticket:view') and #workcase.createBy == authentication.principal.userId")
public ResultDomain<TbWorkcaseDTO> 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<T> 格式
- [ ] 前端能正确解析新的响应格式
- [ ] 错误信息正确传递

### 数据权限测试

- [ ] 租户数据隔离正常
- [ ] 部门数据权限正常
- [ ] 个人数据权限正常

## 常见问题

### 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)