第04章 - 微服务模块详解

第04章 - 微服务模块详解

4.1 模块概述

4.1.1 模块划分

RuoYi-Cloud按照功能职责将系统划分为多个微服务模块:

┌─────────────────────────────────────────────────────────────────────────────┐
│                            微服务模块架构                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        基础设施层                                    │    │
│  ├───────────────┬───────────────┬───────────────┬───────────────────┤    │
│  │    Nacos      │     Redis     │     MySQL     │    Sentinel       │    │
│  │  注册/配置     │     缓存      │     数据库    │    流量控制        │    │
│  └───────────────┴───────────────┴───────────────┴───────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        网关层 (Gateway)                              │    │
│  │                      路由转发、鉴权过滤、限流                         │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        认证层 (Auth)                                 │    │
│  │                      登录认证、Token管理                              │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        业务层 (Modules)                              │    │
│  ├───────────────┬───────────────┬───────────────┬───────────────────┤    │
│  │    System     │      Gen      │      Job      │      File         │    │
│  │   系统管理    │   代码生成    │   定时任务    │    文件服务        │    │
│  └───────────────┴───────────────┴───────────────┴───────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        监控层 (Visual)                               │    │
│  │                      服务监控、性能分析                               │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                        通用层 (Common)                               │    │
│  │            核心工具、安全模块、日志模块、缓存模块等                    │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.1.2 模块职责

模块名称 端口 职责说明
ruoyi-gateway 8080 统一入口,路由转发,鉴权过滤,限流控制
ruoyi-auth 9200 用户认证,Token生成与验证
ruoyi-system 9201 系统管理核心功能
ruoyi-gen 9202 代码生成器
ruoyi-job 9203 定时任务调度
ruoyi-file 9300 文件上传下载
ruoyi-monitor 9100 服务监控

4.2 ruoyi-common 通用模块

4.2.1 模块结构

ruoyi-common/
├── ruoyi-common-core/           # 核心模块
├── ruoyi-common-datascope/      # 数据权限
├── ruoyi-common-datasource/     # 多数据源
├── ruoyi-common-log/            # 日志模块
├── ruoyi-common-redis/          # 缓存模块
├── ruoyi-common-seata/          # 分布式事务
├── ruoyi-common-security/       # 安全模块
├── ruoyi-common-sensitive/      # 数据脱敏
└── ruoyi-common-swagger/        # 接口文档

4.2.2 ruoyi-common-core 核心模块

主要功能

核心模块包含了系统中所有服务共用的基础代码:

ruoyi-common-core/
└── src/main/java/com/ruoyi/common/core/
    ├── constant/                # 常量定义
    │   ├── CacheConstants.java  # 缓存常量
    │   ├── Constants.java       # 通用常量
    │   ├── GenConstants.java    # 代码生成常量
    │   ├── HttpStatus.java      # HTTP状态码
    │   ├── SecurityConstants.java # 安全常量
    │   ├── ServiceNameConstants.java # 服务名称
    │   ├── TokenConstants.java  # Token常量
    │   └── UserConstants.java   # 用户常量
    ├── context/                 # 上下文工具
    │   └── SecurityContextHolder.java
    ├── domain/                  # 基础实体
    │   └── R.java              # 统一响应结果
    ├── enums/                   # 枚举定义
    ├── exception/               # 异常定义
    │   ├── GlobalException.java # 全局异常
    │   ├── InnerAuthException.java # 内部认证异常
    │   ├── PreAuthorizeException.java # 权限异常
    │   └── ServiceException.java # 业务异常
    ├── text/                    # 文本工具
    ├── utils/                   # 工具类
    │   ├── DateUtils.java       # 日期工具
    │   ├── JwtUtils.java        # JWT工具
    │   ├── PageUtils.java       # 分页工具
    │   ├── ServletUtils.java    # Servlet工具
    │   ├── SpringUtils.java     # Spring工具
    │   ├── StringUtils.java     # 字符串工具
    │   └── ...
    └── web/                     # Web相关
        ├── controller/          # 基础控制器
        ├── domain/              # Web实体
        └── page/                # 分页支持

统一响应结果 R

public class R<T> implements Serializable {
    
    /** 成功 */
    public static final int SUCCESS = Constants.SUCCESS;
    
    /** 失败 */
    public static final int FAIL = Constants.FAIL;
    
    private int code;
    private String msg;
    private T data;
    
    public static <T> R<T> ok() {
        return restResult(null, SUCCESS, "操作成功");
    }
    
    public static <T> R<T> ok(T data) {
        return restResult(data, SUCCESS, "操作成功");
    }
    
    public static <T> R<T> ok(T data, String msg) {
        return restResult(data, SUCCESS, msg);
    }
    
    public static <T> R<T> fail() {
        return restResult(null, FAIL, "操作失败");
    }
    
    public static <T> R<T> fail(String msg) {
        return restResult(null, FAIL, msg);
    }
    
    public static <T> R<T> fail(int code, String msg) {
        return restResult(null, code, msg);
    }
    
    private static <T> R<T> restResult(T data, int code, String msg) {
        R<T> apiResult = new R<>();
        apiResult.setCode(code);
        apiResult.setData(data);
        apiResult.setMsg(msg);
        return apiResult;
    }
}

基础控制器 BaseController

public class BaseController {
    
    /**
     * 返回成功
     */
    public AjaxResult success() {
        return AjaxResult.success();
    }
    
    public AjaxResult success(String message) {
        return AjaxResult.success(message);
    }
    
    public AjaxResult success(Object data) {
        return AjaxResult.success(data);
    }
    
    /**
     * 返回失败消息
     */
    public AjaxResult error() {
        return AjaxResult.error();
    }
    
    public AjaxResult error(String message) {
        return AjaxResult.error(message);
    }
    
    /**
     * 响应返回结果
     */
    protected AjaxResult toAjax(int rows) {
        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
    }
    
    protected AjaxResult toAjax(boolean result) {
        return result ? success() : error();
    }
    
    /**
     * 设置请求分页数据
     */
    protected void startPage() {
        PageUtils.startPage();
    }
    
    /**
     * 响应请求分页数据
     */
    protected TableDataInfo getDataTable(List<?> list) {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(new PageInfo(list).getTotal());
        return rspData;
    }
}

4.2.3 ruoyi-common-security 安全模块

安全注解

// 登录校验注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresLogin {
}

// 权限校验注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
    String[] value() default {};
    Logical logical() default Logical.AND;
}

// 角色校验注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {
    String[] value() default {};
    Logical logical() default Logical.AND;
}

// 内部服务调用校验注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InnerAuth {
    boolean isUser() default false;
}

权限校验工具 SecurityUtils

public class SecurityUtils {
    
    /**
     * 获取用户ID
     */
    public static Long getUserId() {
        return SecurityContextHolder.getUserId();
    }
    
    /**
     * 获取用户名称
     */
    public static String getUsername() {
        return SecurityContextHolder.getUserName();
    }
    
    /**
     * 获取用户key
     */
    public static String getUserKey() {
        return SecurityContextHolder.getUserKey();
    }
    
    /**
     * 获取登录用户信息
     */
    public static LoginUser getLoginUser() {
        return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class);
    }
    
    /**
     * 是否为管理员
     */
    public static boolean isAdmin(Long userId) {
        return userId != null && 1L == userId;
    }
}

权限校验切面

@Aspect
@Component
public class PreAuthorizeAspect {
    
    /**
     * 构建前置通知
     */
    @Before("@annotation(requiresLogin)")
    public void doBeforeLogin(RequiresLogin requiresLogin) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            throw new NotLoginException("用户未登录");
        }
    }
    
    @Before("@annotation(requiresPermissions)")
    public void doBeforePermission(JoinPoint joinPoint, RequiresPermissions requiresPermissions) {
        Set<String> permissions = SecurityUtils.getLoginUser().getPermissions();
        String[] requirePerms = requiresPermissions.value();
        Logical logical = requiresPermissions.logical();
        
        // 权限校验逻辑...
    }
    
    @Before("@annotation(requiresRoles)")  
    public void doBeforeRole(JoinPoint joinPoint, RequiresRoles requiresRoles) {
        Set<String> roles = SecurityUtils.getLoginUser().getRoles();
        String[] requireRole = requiresRoles.value();
        Logical logical = requiresRoles.logical();
        
        // 角色校验逻辑...
    }
}

4.2.4 ruoyi-common-log 日志模块

日志注解

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 模块
     */
    String title() default "";
    
    /**
     * 功能
     */
    BusinessType businessType() default BusinessType.OTHER;
    
    /**
     * 操作人类别
     */
    OperatorType operatorType() default OperatorType.MANAGE;
    
    /**
     * 是否保存请求的参数
     */
    boolean isSaveRequestData() default true;
    
    /**
     * 是否保存响应的参数
     */
    boolean isSaveResponseData() default true;
    
    /**
     * 排除指定的请求参数
     */
    String[] excludeParamNames() default {};
}

业务类型枚举

public enum BusinessType {
    OTHER,      // 其它
    INSERT,     // 新增
    UPDATE,     // 修改
    DELETE,     // 删除
    GRANT,      // 授权
    EXPORT,     // 导出
    IMPORT,     // 导入
    FORCE,      // 强退
    GENCODE,    // 生成代码
    CLEAN,      // 清空数据
}

日志切面处理

@Aspect
@Component
public class LogAspect {
    
    @Autowired
    private AsyncLogService asyncLogService;
    
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }
    
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        handleLog(joinPoint, controllerLog, e, null);
    }
    
    protected void handleLog(JoinPoint joinPoint, Log controllerLog, Exception e, Object jsonResult) {
        try {
            // 获取当前的用户
            LoginUser loginUser = SecurityUtils.getLoginUser();
            
            // 构建日志实体
            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            
            // 请求的地址
            String ip = IpUtils.getIpAddr();
            operLog.setOperIp(ip);
            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
            
            if (loginUser != null) {
                operLog.setOperName(loginUser.getUsername());
            }
            
            if (e != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            
            // 异步保存到数据库
            asyncLogService.saveSysLog(operLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
        }
    }
}

4.2.5 ruoyi-common-redis 缓存模块

Redis配置

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
        
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

Redis服务工具类

@Component
public class RedisService {
    
    @Autowired
    public RedisTemplate redisTemplate;
    
    /**
     * 缓存基本的对象
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 缓存基本的对象,并设置过期时间
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
    
    /**
     * 获得缓存的基本对象
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
    
    /**
     * 删除单个对象
     */
    public boolean deleteObject(final String key) {
        return redisTemplate.delete(key);
    }
    
    /**
     * 删除集合对象
     */
    public boolean deleteObject(final Collection collection) {
        return redisTemplate.delete(collection) > 0;
    }
    
    /**
     * 缓存List数据
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }
    
    /**
     * 获得缓存的list对象
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
    
    /**
     * 缓存Set
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }
    
    /**
     * 缓存Map
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }
    
    /**
     * 获得缓存的Map
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 判断 key是否存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
    
    /**
     * 设置有效时间
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }
    
    /**
     * 获取有效时间
     */
    public long getExpire(final String key) {
        return redisTemplate.getExpire(key);
    }
}

4.2.6 ruoyi-common-datascope 数据权限模块

数据权限注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
    /**
     * 部门表的别名
     */
    String deptAlias() default "";
    
    /**
     * 用户表的别名
     */
    String userAlias() default "";
    
    /**
     * 权限字符(用于多个角色匹配符合条件的权限)
     */
    String permission() default "";
}

数据权限切面

@Aspect
@Component
public class DataScopeAspect {
    
    /** 全部数据权限 */
    public static final String DATA_SCOPE_ALL = "1";
    
    /** 自定数据权限 */
    public static final String DATA_SCOPE_CUSTOM = "2";
    
    /** 部门数据权限 */
    public static final String DATA_SCOPE_DEPT = "3";
    
    /** 部门及以下数据权限 */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
    
    /** 仅本人数据权限 */
    public static final String DATA_SCOPE_SELF = "5";
    
    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }
    
    protected void handleDataScope(JoinPoint joinPoint, DataScope controllerDataScope) {
        // 获取当前用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser != null) {
            SysUser currentUser = loginUser.getSysUser();
            // 如果是超级管理员,则不过滤数据
            if (!SecurityUtils.isAdmin(currentUser.getUserId())) {
                String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), 
                    PermissionContextHolder.getContext());
                dataScopeFilter(joinPoint, currentUser, 
                    controllerDataScope.deptAlias(),
                    controllerDataScope.userAlias(), permission);
            }
        }
    }
    
    /**
     * 数据范围过滤
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, 
            String userAlias, String permission) {
        StringBuilder sqlString = new StringBuilder();
        List<String> conditions = new ArrayList<String>();
        
        for (SysRole role : user.getRoles()) {
            String dataScope = role.getDataScope();
            if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {
                continue;
            }
            
            if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
                    && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
                continue;
            }
            
            if (DATA_SCOPE_ALL.equals(dataScope)) {
                sqlString = new StringBuilder();
                conditions.add(dataScope);
                break;
            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
                sqlString.append(StringUtils.format(
                    " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", 
                    deptAlias, role.getRoleId()));
            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
                sqlString.append(StringUtils.format(
                    " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                    deptAlias, user.getDeptId(), user.getDeptId()));
            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
                if (StringUtils.isNotBlank(userAlias)) {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                } else {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
                }
            }
            conditions.add(dataScope);
        }
        
        // 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制
        if (StringUtils.isEmpty(conditions)) {
            sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
        }
        
        if (StringUtils.isNotBlank(sqlString.toString())) {
            Object params = joinPoint.getArgs()[0];
            if (params != null && params instanceof BaseEntity) {
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }
}

4.2.7 ruoyi-common-sensitive 数据脱敏模块

脱敏注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
    SensitiveStrategy strategy();
}

脱敏策略枚举

public enum SensitiveStrategy {
    /**
     * 用户名
     */
    USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
    
    /**
     * 身份证
     */
    ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
    
    /**
     * 手机号
     */
    PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
    
    /**
     * 地址
     */
    ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****")),
    
    /**
     * 电子邮件
     */
    EMAIL(s -> s.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4")),
    
    /**
     * 银行卡
     */
    BANK_CARD(s -> s.replaceAll("(\\d{4})\\d+(\\d{4})", "$1****$2")),
    
    /**
     * 车牌号
     */
    CAR_LICENSE(s -> s.replaceAll("(\\w)(\\w+)(\\w)", "$1****$3"));
    
    private final Function<String, String> desensitizer;
    
    SensitiveStrategy(Function<String, String> desensitizer) {
        this.desensitizer = desensitizer;
    }
    
    public Function<String, String> desensitizer() {
        return desensitizer;
    }
}

使用示例

public class SysUser extends BaseEntity {
    
    @Sensitive(strategy = SensitiveStrategy.PHONE)
    private String phonenumber;
    
    @Sensitive(strategy = SensitiveStrategy.EMAIL)
    private String email;
    
    @Sensitive(strategy = SensitiveStrategy.ID_CARD)
    private String idCard;
}

4.3 ruoyi-api 接口模块

4.3.1 远程服务接口定义

远程用户服务

@FeignClient(contextId = "remoteUserService", 
    value = ServiceNameConstants.SYSTEM_SERVICE, 
    fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService {
    
    /**
     * 通过用户名查询用户信息
     */
    @GetMapping("/user/info/{username}")
    R<LoginUser> getUserInfo(@PathVariable("username") String username, 
        @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
    
    /**
     * 注册用户信息
     */
    @PostMapping("/user/register")
    R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, 
        @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
    
    /**
     * 记录登录信息
     */
    @PostMapping("/user/recordlogin")
    R<Boolean> recordUserLogin(@RequestBody SysUser sysUser, 
        @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

远程日志服务

@FeignClient(contextId = "remoteLogService", 
    value = ServiceNameConstants.SYSTEM_SERVICE, 
    fallbackFactory = RemoteLogFallbackFactory.class)
public interface RemoteLogService {
    
    /**
     * 保存系统日志
     */
    @PostMapping("/operlog")
    R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, 
        @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
    
    /**
     * 保存访问记录
     */
    @PostMapping("/logininfor")
    R<Boolean> saveLogininfor(@RequestBody SysLogininfor sysLogininfor, 
        @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

远程文件服务

@FeignClient(contextId = "remoteFileService", 
    value = ServiceNameConstants.FILE_SERVICE, 
    fallbackFactory = RemoteFileFallbackFactory.class)
public interface RemoteFileService {
    
    /**
     * 上传文件
     */
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    R<SysFile> upload(@RequestPart(value = "file") MultipartFile file);
}

4.3.2 服务降级处理

降级工厂实现

@Component
public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserService> {
    
    private static final Logger log = LoggerFactory.getLogger(RemoteUserFallbackFactory.class);
    
    @Override
    public RemoteUserService create(Throwable throwable) {
        log.error("用户服务调用失败:{}", throwable.getMessage());
        return new RemoteUserService() {
            @Override
            public R<LoginUser> getUserInfo(String username, String source) {
                return R.fail("获取用户失败:" + throwable.getMessage());
            }
            
            @Override
            public R<Boolean> registerUserInfo(SysUser sysUser, String source) {
                return R.fail("注册用户失败:" + throwable.getMessage());
            }
            
            @Override
            public R<Boolean> recordUserLogin(SysUser sysUser, String source) {
                return R.fail("记录登录信息失败:" + throwable.getMessage());
            }
        };
    }
}

4.4 服务间通信

4.4.1 OpenFeign调用

配置

# Feign配置
feign:
  sentinel:
    enabled: true                 # 开启Sentinel支持
  okhttp:
    enabled: true                 # 使用OkHttp
  httpclient:
    enabled: false                # 禁用Apache HttpClient
  client:
    config:
      default:
        connectTimeout: 10000     # 连接超时
        readTimeout: 10000        # 读取超时
  compression:
    request:
      enabled: true               # 请求压缩
      min-request-size: 8192
    response:
      enabled: true               # 响应压缩

使用示例

@RestController
@RequestMapping("/auth")
public class TokenController {
    
    @Autowired
    private RemoteUserService remoteUserService;
    
    @PostMapping("/login")
    public R<?> login(@RequestBody LoginBody form) {
        // 调用远程用户服务
        R<LoginUser> userResult = remoteUserService.getUserInfo(
            form.getUsername(), 
            SecurityConstants.INNER);
        
        if (R.FAIL == userResult.getCode()) {
            return R.fail(userResult.getMsg());
        }
        
        LoginUser userInfo = userResult.getData();
        // 后续登录逻辑...
    }
}

4.4.2 请求头传递

内部服务标识

// 服务间调用时添加内部服务标识
@RequestHeader(SecurityConstants.FROM_SOURCE) String source

// SecurityConstants.FROM_SOURCE = "from-source"
// SecurityConstants.INNER = "inner"

Feign请求拦截器

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    
    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        if (httpServletRequest != null) {
            Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
            
            // 传递用户信息请求头
            String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
            if (StringUtils.isNotEmpty(userId)) {
                requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);
            }
            
            String userKey = headers.get(SecurityConstants.USER_KEY);
            if (StringUtils.isNotEmpty(userKey)) {
                requestTemplate.header(SecurityConstants.USER_KEY, userKey);
            }
            
            String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
            if (StringUtils.isNotEmpty(userName)) {
                requestTemplate.header(SecurityConstants.DETAILS_USERNAME, 
                    URLEncoder.encode(userName, Constants.UTF8));
            }
            
            String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
            if (StringUtils.isNotEmpty(authentication)) {
                requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
            }
        }
    }
}

4.5 小结

本章详细介绍了RuoYi-Cloud的微服务模块设计,包括:

  1. 模块划分:了解各模块的职责和端口分配
  2. 通用模块:掌握core、security、log、redis等核心模块
  3. API模块:理解远程服务接口定义和降级处理
  4. 服务通信:掌握OpenFeign调用和请求头传递机制

这些模块构成了RuoYi-Cloud的基础架构,理解它们对于后续的二次开发至关重要。


上一章:环境准备与安装部署 | 返回目录 | 下一章:认证与授权中心

posted @ 2026-01-08 14:05  我才是银古  阅读(4)  评论(0)    收藏  举报