第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的微服务模块设计,包括:
- 模块划分:了解各模块的职责和端口分配
- 通用模块:掌握core、security、log、redis等核心模块
- API模块:理解远程服务接口定义和降级处理
- 服务通信:掌握OpenFeign调用和请求头传递机制
这些模块构成了RuoYi-Cloud的基础架构,理解它们对于后续的二次开发至关重要。

浙公网安备 33010602011771号