# 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);
    }
}
```