t
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
# 权限注解转换指南
|
||||
|
||||
## 概述
|
||||
本指南详细说明了如何将 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)
|
||||
Reference in New Issue
Block a user