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

17 KiB
Raw Blame History

SecurityUtils 和 RemoteUserService 配置指南

1. 概述

本指南详细说明如何在 pigx 平台中配置和使用 SecurityUtils 和 RemoteUserService实现用户信息获取和远程用户服务调用。

2. SecurityUtils 使用指南

2.1 SecurityUtils 介绍

SecurityUtils 是 pigx 平台提供的安全工具类,用于获取当前登录用户信息、权限判断等安全相关操作。

2.2 Maven 依赖

<dependency>
    <groupId>com.pig4cloud</groupId>
    <artifactId>pigx-common-security</artifactId>
</dependency>

2.3 基本使用

2.3.1 获取当前用户信息

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 在实体中自动填充用户信息

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;
}

配置自动填充处理器:

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 权限判断

@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 异步任务中使用

@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 依赖

<dependency>
    <groupId>com.pig4cloud</groupId>
    <artifactId>pigx-upms-api</artifactId>
</dependency>

3.3 启用 Feign 客户端

在启动类或配置类上添加注解:

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 基本使用

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 错误处理

@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 配置熔断降级

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("用户服务暂时不可用");
            }
        };
    }
}

配置文件中启用熔断:

feign:
  sentinel:
    enabled: true
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000

4. 部门服务调用

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 缓存用户信息

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 批量查询优化

@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 租户隔离实现

@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. 配置的超时时间太短

解决方案

feign:
  client:
    config:
      default:
        connectTimeout: 10000  # 连接超时10秒
        readTimeout: 10000     # 读取超时10秒

Q3: 多租户数据混乱

原因

  1. 未正确设置 tenant_id
  2. 查询时未添加租户条件

解决方案

  1. 使用 MyBatis-Plus 的自动填充
  2. 配置全局租户拦截器
@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: 如何在没有用户上下文的情况下调用服务

@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 实现示例:

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