# SecurityUtils 和 RemoteUserService 配置指南 ## 1. 概述 本指南详细说明如何在 pigx 平台中配置和使用 SecurityUtils 和 RemoteUserService,实现用户信息获取和远程用户服务调用。 ## 2. SecurityUtils 使用指南 ### 2.1 SecurityUtils 介绍 SecurityUtils 是 pigx 平台提供的安全工具类,用于获取当前登录用户信息、权限判断等安全相关操作。 ### 2.2 Maven 依赖 ```xml com.pig4cloud pigx-common-security ``` ### 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 roles = pigxUser.getRoles(); // 获取用户权限列表 Collection 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 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 com.pig4cloud pigx-upms-api ``` ### 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 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 result = remoteUserService.info(username, SecurityConstants.FROM_IN); if (result.isSuccess() && result.getData() != null) { return result.getData().getSysUser(); } throw new BusinessException("用户不存在"); } /** * 批量获取用户信息 */ public List getUsersByIds(List userIds) { List users = new ArrayList<>(); for (Long userId : userIds) { R 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 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 selectById(Long id, String from) { log.error("调用用户服务失败", throwable); return R.failed("用户服务暂时不可用"); } @Override public R 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 result = remoteDeptService.selectById(deptId, SecurityConstants.FROM_IN); if (result.isSuccess() && result.getData() != null) { return result.getData(); } return null; } /** * 获取部门树 */ public List getDeptTree() { R> 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 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 getUserMap(List 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 listByTenant() { PigxUser user = SecurityUtils.getUser(); QueryWrapper 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 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 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 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 listMyWorkcase() { PigxUser user = SecurityUtils.getUser(); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("tenant_id", user.getTenantId()) .eq("create_by", user.getUsername()) .orderByDesc("create_time"); return this.list(wrapper); } } ```