Files
urbanLifeline/.kiro/specs/urbanlifeline-to-pigx-migration/security-config-guide.md

663 lines
17 KiB
Markdown
Raw Normal View History

2026-01-14 15:42:26 +08:00
# 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);
}
}
```