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

663 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SecurityUtils 和 RemoteUserService 配置指南
## 1. 概述
本指南详细说明如何在 pigx 平台中配置和使用 SecurityUtils 和 RemoteUserService实现用户信息获取和远程用户服务调用。
## 2. SecurityUtils 使用指南
### 2.1 SecurityUtils 介绍
SecurityUtils 是 pigx 平台提供的安全工具类,用于获取当前登录用户信息、权限判断等安全相关操作。
### 2.2 Maven 依赖
```xml
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-security</artifactId>
</dependency>
```
### 2.3 基本使用
#### 2.3.1 获取当前用户信息
```java
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
@Service
public class WorkcaseServiceImpl implements WorkcaseService {
public void example() {
// 获取完整用户对象
PigxUser pigxUser = SecurityUtils.getUser();
// 获取用户ID
Long userId = pigxUser.getId();
// 获取用户名
String username = pigxUser.getUsername();
// 获取租户ID重要多租户隔离
Long tenantId = pigxUser.getTenantId();
// 获取部门ID
Long deptId = pigxUser.getDeptId();
// 获取用户角色列表
List<Long> roles = pigxUser.getRoles();
// 获取用户权限列表
Collection<String> authorities = pigxUser.getAuthorities();
}
}
```
#### 2.3.2 在实体中自动填充用户信息
```java
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
@Data
@TableName("tb_workcase")
public class TbWorkcase {
private String workcaseId;
// 自动填充创建人
@TableField(fill = FieldFill.INSERT)
private String createBy;
// 自动填充更新人
@TableField(fill = FieldFill.UPDATE)
private String updateBy;
// 自动填充租户ID
@TableField(fill = FieldFill.INSERT)
private Long tenantId;
}
```
配置自动填充处理器:
```java
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
PigxUser user = SecurityUtils.getUser();
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "createBy", String.class, user.getUsername());
this.strictInsertFill(metaObject, "tenantId", Long.class, user.getTenantId());
this.strictInsertFill(metaObject, "delFlag", String.class, "0");
}
@Override
public void updateFill(MetaObject metaObject) {
PigxUser user = SecurityUtils.getUser();
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "updateBy", String.class, user.getUsername());
}
}
```
### 2.4 权限判断
```java
@RestController
@RequestMapping("/workcase")
public class WorkcaseController {
// 方法级权限判断
@PreAuthorize("@pms.hasPermission('workcase_ticket_add')")
@PostMapping
public R<TbWorkcase> create(@RequestBody TbWorkcase workcase) {
return R.ok(workcaseService.save(workcase));
}
// 代码中权限判断
@GetMapping("/admin-only")
public R<?> adminFunction() {
PigxUser user = SecurityUtils.getUser();
// 检查是否有特定权限
if (!user.getAuthorities().contains("workcase_ticket_admin")) {
return R.failed("没有管理员权限");
}
// 执行管理员功能
return R.ok();
}
}
```
### 2.5 异步任务中使用
```java
@Service
public class AsyncService {
// 错误示例:异步线程中可能获取不到用户信息
@Async
public void wrongAsyncMethod() {
PigxUser user = SecurityUtils.getUser(); // 可能为null
}
// 正确示例:传递用户信息
@Async
public void correctAsyncMethod(PigxUser user) {
// 使用传入的用户信息
Long userId = user.getId();
Long tenantId = user.getTenantId();
// 执行异步逻辑
}
// 调用异步方法
public void callAsync() {
PigxUser user = SecurityUtils.getUser();
correctAsyncMethod(user);
}
}
```
## 3. RemoteUserService 配置和使用
### 3.1 RemoteUserService 介绍
RemoteUserService 是通过 Feign 调用 pigx-upms 服务获取用户信息的远程服务接口。
### 3.2 Maven 依赖
```xml
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-upms-api</artifactId>
</dependency>
```
### 3.3 启用 Feign 客户端
在启动类或配置类上添加注解:
```java
import com.pig4cloud.pigx.common.feign.annotation.EnablePigxFeignClients;
@EnablePigxFeignClients
@SpringBootApplication
public class WorkcaseApplication {
public static void main(String[] args) {
SpringApplication.run(WorkcaseApplication.class, args);
}
}
```
### 3.4 基本使用
```java
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.common.core.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class WorkcaseServiceImpl implements WorkcaseService {
@Autowired
private RemoteUserService remoteUserService;
/**
* 根据用户ID获取用户信息
*/
public SysUser getUserById(Long userId) {
R<SysUser> result = remoteUserService.selectById(userId, SecurityConstants.FROM_IN);
if (result.isSuccess() && result.getData() != null) {
return result.getData();
}
throw new BusinessException("用户不存在");
}
/**
* 根据用户名获取用户信息
*/
public SysUser getUserByUsername(String username) {
R<UserInfo> result = remoteUserService.info(username, SecurityConstants.FROM_IN);
if (result.isSuccess() && result.getData() != null) {
return result.getData().getSysUser();
}
throw new BusinessException("用户不存在");
}
/**
* 批量获取用户信息
*/
public List<SysUser> getUsersByIds(List<Long> userIds) {
List<SysUser> users = new ArrayList<>();
for (Long userId : userIds) {
R<SysUser> result = remoteUserService.selectById(userId, SecurityConstants.FROM_IN);
if (result.isSuccess() && result.getData() != null) {
users.add(result.getData());
}
}
return users;
}
}
```
### 3.5 错误处理
```java
@Service
public class WorkcaseServiceImpl {
@Autowired
private RemoteUserService remoteUserService;
public void assignWorkcase(String workcaseId, Long assigneeId) {
try {
// 调用远程服务
R<SysUser> result = remoteUserService.selectById(assigneeId, SecurityConstants.FROM_IN);
// 检查调用是否成功
if (!result.isSuccess()) {
log.error("获取用户信息失败: {}", result.getMsg());
throw new BusinessException("获取用户信息失败");
}
// 检查数据是否存在
SysUser assignee = result.getData();
if (assignee == null) {
throw new BusinessException("用户不存在");
}
// 执行分配逻辑
doAssign(workcaseId, assignee);
} catch (FeignException e) {
// 处理Feign调用异常
log.error("远程服务调用失败", e);
throw new BusinessException("系统繁忙,请稍后重试");
}
}
}
```
### 3.6 配置熔断降级
```java
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.admin.api.feign.factory.RemoteUserServiceFallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class RemoteUserServiceFallbackImpl implements RemoteUserServiceFallbackFactory {
@Override
public RemoteUserService create(Throwable throwable) {
return new RemoteUserService() {
@Override
public R<SysUser> selectById(Long id, String from) {
log.error("调用用户服务失败", throwable);
return R.failed("用户服务暂时不可用");
}
@Override
public R<UserInfo> info(String username, String from) {
log.error("调用用户服务失败", throwable);
return R.failed("用户服务暂时不可用");
}
};
}
}
```
配置文件中启用熔断:
```yaml
feign:
sentinel:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
```
## 4. 部门服务调用
```java
import com.pig4cloud.pigx.admin.api.feign.RemoteDeptService;
import com.pig4cloud.pigx.admin.api.entity.SysDept;
@Service
public class DeptRelatedService {
@Autowired
private RemoteDeptService remoteDeptService;
/**
* 获取部门信息
*/
public SysDept getDeptById(Long deptId) {
R<SysDept> result = remoteDeptService.selectById(deptId, SecurityConstants.FROM_IN);
if (result.isSuccess() && result.getData() != null) {
return result.getData();
}
return null;
}
/**
* 获取部门树
*/
public List<SysDept> getDeptTree() {
R<List<SysDept>> result = remoteDeptService.tree(SecurityConstants.FROM_IN);
if (result.isSuccess() && result.getData() != null) {
return result.getData();
}
return new ArrayList<>();
}
}
```
## 5. 最佳实践
### 5.1 缓存用户信息
```java
import org.springframework.cache.annotation.Cacheable;
@Service
public class UserCacheService {
@Autowired
private RemoteUserService remoteUserService;
@Cacheable(value = "user", key = "#userId")
public SysUser getUserById(Long userId) {
R<SysUser> result = remoteUserService.selectById(userId, SecurityConstants.FROM_IN);
if (result.isSuccess() && result.getData() != null) {
return result.getData();
}
return null;
}
}
```
### 5.2 批量查询优化
```java
@Service
public class BatchUserService {
@Autowired
private RemoteUserService remoteUserService;
/**
* 批量获取用户信息(优化版)
*/
public Map<Long, SysUser> getUserMap(List<Long> userIds) {
if (CollectionUtils.isEmpty(userIds)) {
return new HashMap<>();
}
// 使用并行流提高效率
return userIds.parallelStream()
.map(userId -> remoteUserService.selectById(userId, SecurityConstants.FROM_IN))
.filter(result -> result.isSuccess() && result.getData() != null)
.map(R::getData)
.collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
}
}
```
### 5.3 租户隔离实现
```java
@Service
public class TenantIsolationService {
/**
* 查询时自动添加租户条件
*/
public List<TbWorkcase> listByTenant() {
PigxUser user = SecurityUtils.getUser();
QueryWrapper<TbWorkcase> wrapper = new QueryWrapper<>();
wrapper.eq("tenant_id", user.getTenantId());
return workcaseMapper.selectList(wrapper);
}
/**
* 保存时自动设置租户ID
*/
public void saveWithTenant(TbWorkcase workcase) {
PigxUser user = SecurityUtils.getUser();
workcase.setTenantId(user.getTenantId());
workcaseMapper.insert(workcase);
}
}
```
## 6. 常见问题
### Q1: SecurityUtils.getUser() 返回 null
**原因**
1. 未登录或 token 过期
2. 在异步线程中调用
3. 在定时任务中调用
**解决方案**
1. 检查 token 有效性
2. 在异步方法调用前获取用户信息并传递
3. 定时任务使用系统用户或指定用户
### Q2: RemoteUserService 调用超时
**原因**
1. 网络问题
2. pigx-upms 服务未启动
3. 配置的超时时间太短
**解决方案**
```yaml
feign:
client:
config:
default:
connectTimeout: 10000 # 连接超时10秒
readTimeout: 10000 # 读取超时10秒
```
### Q3: 多租户数据混乱
**原因**
1. 未正确设置 tenant_id
2. 查询时未添加租户条件
**解决方案**
1. 使用 MyBatis-Plus 的自动填充
2. 配置全局租户拦截器
```java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加租户拦截器
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public Expression getTenantId() {
PigxUser user = SecurityUtils.getUser();
return new LongValue(user.getTenantId());
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要租户隔离的表
return "sys_user".equals(tableName);
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}
```
### Q4: 如何在没有用户上下文的情况下调用服务
```java
@Service
public class SystemService {
/**
* 使用内部调用标识
*/
public void systemCall() {
// 使用 FROM_IN 标识内部调用
R<SysUser> result = remoteUserService.selectById(1L, SecurityConstants.FROM_IN);
}
/**
* 模拟系统用户
*/
public void executeAsSystem() {
// 创建系统用户上下文
PigxUser systemUser = new PigxUser();
systemUser.setId(0L);
systemUser.setUsername("system");
systemUser.setTenantId(1L);
// 执行逻辑
doSystemWork(systemUser);
}
}
```
## 7. 迁移检查清单
- [ ] 所有 JwtUtils 替换为 SecurityUtils
- [ ] 所有 UserService 替换为 RemoteUserService
- [ ] 所有实体添加 tenant_id 字段
- [ ] 配置 MyBatis-Plus 自动填充
- [ ] 配置 Feign 客户端
- [ ] 添加错误处理和熔断降级
- [ ] 测试用户信息获取
- [ ] 测试远程服务调用
- [ ] 测试租户数据隔离
## 8. 参考代码示例
完整的 Service 实现示例:
```java
package com.pig4cloud.pigx.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.app.entity.TbWorkcase;
import com.pig4cloud.pigx.app.mapper.WorkcaseMapper;
import com.pig4cloud.pigx.app.service.WorkcaseService;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class WorkcaseServiceImpl extends ServiceImpl<WorkcaseMapper, TbWorkcase>
implements WorkcaseService {
private final RemoteUserService remoteUserService;
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean createWorkcase(TbWorkcase workcase) {
// 获取当前用户
PigxUser currentUser = SecurityUtils.getUser();
// 设置创建人信息
workcase.setCreateBy(currentUser.getUsername());
workcase.setTenantId(currentUser.getTenantId());
workcase.setDeptId(currentUser.getDeptId());
// 保存工单
return this.save(workcase);
}
@Override
public Boolean assignWorkcase(String workcaseId, Long assigneeId) {
// 获取被分配人信息
R<SysUser> result = remoteUserService.selectById(assigneeId, SecurityConstants.FROM_IN);
if (!result.isSuccess() || result.getData() == null) {
throw new RuntimeException("用户不存在");
}
SysUser assignee = result.getData();
// 更新工单
TbWorkcase workcase = this.getById(workcaseId);
workcase.setAssigneeId(assigneeId);
workcase.setAssigneeName(assignee.getUsername());
return this.updateById(workcase);
}
@Override
public List<TbWorkcase> listMyWorkcase() {
PigxUser user = SecurityUtils.getUser();
QueryWrapper<TbWorkcase> wrapper = new QueryWrapper<>();
wrapper.eq("tenant_id", user.getTenantId())
.eq("create_by", user.getUsername())
.orderByDesc("create_time");
return this.list(wrapper);
}
}
```