t
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
# 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);
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user