You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RuoYi-Vue/docs/architecture/backend-architecture.md

471 lines
22 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# RuoYi-Vue 后端架构分析
> 版本3.8.0 | 分析日期2026-06-28 | 框架Spring Boot 2.5.8
---
## 1. 分层架构分析
### 1.1 整体分层概览
项目采用经典的 **四层架构**Controller → Service → Mapper → Domain各层职责清晰通过 Maven 多模块进行物理隔离。
```
┌─────────────────────────────────────────────────────────────┐
│ ruoyi-admin (启动层) │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
│ │ Controller │ │ Config │ │ Application │ │
│ │ (18个) │ │ (Swagger) │ │ RuoYiApplication │ │
│ └──────┬──────┘ └─────────────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ruoyi-framework (框架层) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │ Aspect │ │ Security │ │ Config │ │ Manager │ │ │
│ │ │ (4个切面)│ │ (JWT) │ │ (12个) │ │ (异步) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ruoyi- │ │book- │ │ stock- │ │
│ │system │ │system │ │ system │ │
│ │(15 Mapper│ │(6 Mapper │ │ (9 Mapper │ │
│ │ 12 Service│ │ 6 Service│ │ ... │ │
│ │ 12 Domain)│ │ 6 Domain)│ │ │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ruoyi-common (通用工具层) │ │
│ │ BaseController / AjaxResult / BaseEntity / 注解 / │ │
│ │ 工具类(20+) / 异常类 / 过滤器 / 常量 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 1.2 Controller 层分析
**文件位置**`ruoyi-admin/src/main/java/com/ruoyi/web/controller/`
#### Controller 统计
| 分类 | 数量 | 文件列表 |
|------|------|----------|
| 系统管理 | 13 | SysConfigController, SysDeptController, SysDictDataController, SysDictTypeController, SysIndexController, SysLoginController, SysMenuController, SysNoticeController, SysPostController, SysProfileController, SysRegisterController, SysRoleController, SysUserController |
| 监控管理 | 5 | CacheController, ServerController, SysLogininforController, SysOperlogController, SysUserOnlineController |
| 业务模块 | 6 | AccountBookController, AccountController, OperationsController, StatisticsController, StatisticsRemainController, StatisticsTotalController |
| 工具/公共 | 4 | CaptchaController, CommonController, SwaggerController, TestController |
| **合计** | **28** | — |
#### 设计模式
1. **统一继承 BaseController**:所有 Controller 继承 `BaseController`,获得分页、响应、用户上下文等通用能力。
2. **标准 CRUD 接口模式**
```
GET /list → 分页列表查询
GET /{id} → 详情查询
POST / → 新增
PUT / → 修改
DELETE /{ids} → 批量删除
POST /export → Excel 导出
POST /importData → Excel 导入
```
3. **权限注解驱动**:每个接口通过 `@PreAuthorize("@ss.hasPermi('module:entity:action')")` 进行权限控制。
4. **操作日志注解**:通过 `@Log(title = "模块名", businessType = BusinessType.XXX)` 声明式记录操作日志。
### 1.3 Service 层分析
**文件位置**
- 系统模块:`ruoyi-system/src/main/java/com/ruoyi/system/service/`
- 账务模块:`book-system/src/main/java/com/ruoyi/booksystem/service/`
#### 接口与实现分离
采用标准的 **接口 + 实现类** 模式:
```
ISysUserService (接口)
└── SysUserServiceImpl (实现)
```
| 模块 | 接口数 | 实现类数 |
|------|--------|----------|
| ruoyi-system | 12 | 12 |
| book-system | 6 | 6 |
#### 事务管理
使用 `@Transactional` 注解管理事务,主要在以下场景使用:
| 场景 | 方法示例 |
|------|----------|
| 新增用户(含角色/岗位关联) | `insertUser()` |
| 修改用户(含角色/岗位关联) | `updateUser()` |
| 删除用户(含关联清理) | `deleteUserById()`, `deleteUserByIds()` |
| 用户授权角色 | `insertUserAuth()` |
**事务策略特点**
- 仅在涉及多表操作的复合方法上加 `@Transactional`
- 简单 CRUD`updateUserStatus`)不使用事务
- 使用默认的 `REQUIRED` 传播级别和 `RuntimeException` 回滚策略
#### 数据权限注解
Service 层通过 `@DataScope(deptAlias = "d", userAlias = "u")` 注解实现数据权限过滤,在方法执行前由 AOP 切面动态注入 SQL 过滤条件。
### 1.4 Mapper 层分析
**文件位置**
- XML 映射:`{module}/src/main/resources/mapper/{module}/`
- Java 接口:`{module}/src/main/java/com/ruoyi/{module}/mapper/`
#### MyBatis 映射文件统计
| 模块 | XML 文件数 | 主要功能 |
|------|-----------|----------|
| ruoyi-system | 11 | 用户、角色、菜单、部门、字典、岗位、日志、配置、公告 |
| book-system | 6 | 账户、账本、操作、统计 |
| stock-system | 9 | 股票、行情、财务、趋势 |
| ruoyi-quartz | 2 | 定时任务 |
| ruoyi-generator | 2 | 代码生成 |
| **合计** | **30** | — |
#### 动态 SQL 使用
`SysUserMapper.xml` 为例,大量使用 MyBatis 动态 SQL
| 标签 | 用途 | 示例 |
|------|------|------|
| `<if>` | 条件查询 | `<if test="userName != null and userName != ''">` |
| `<foreach>` | 批量操作 | `<foreach collection="array" item="userId">` |
| `<include>` | SQL 片段复用 | `<include refid="selectUserVo"/>` |
| `<sql>` | 定义可复用片段 | `<sql id="selectUserVo">` |
| `<set>` | 动态更新 | `<set><if test="...">field = #{...},</if></set>` |
| `${params.dataScope}` | 数据权限注入 | 由 DataScopeAspect 动态拼接 |
#### ResultMap 关联映射
使用 `<association>``<collection>` 实现一对多关联查询:
```xml
<association property="dept" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
```
### 1.5 Domain 层分析
#### 基类体系
```
BaseEntity (基础实体)
├── searchValue, createBy, createTime, updateBy, updateTime, remark, params
├── TreeEntity (树形实体)
│ └── parentId, ancestors
└── AjaxResult (统一响应)
└── code, msg, data (继承 HashMap)
```
#### 核心实体类
| 实体 | 位置 | 用途 |
|------|------|------|
| SysUser | ruoyi-common | 用户信息 |
| SysRole | ruoyi-common | 角色信息 |
| SysMenu | ruoyi-common | 菜单/权限 |
| SysDept | ruoyi-common | 部门信息 |
| LoginUser | ruoyi-common | 登录用户上下文 |
| SysConfig/SysNotice/SysOperLog | ruoyi-system | 系统业务实体 |
| Account/AccountBook/Statistics | book-system | 账务业务实体 |
---
## 2. 安全配置
### 2.1 Spring Security 配置
**配置文件**`ruoyi-framework/.../config/SecurityConfig.java`
#### 认证流程
```
HTTP Request
┌──────────────────────┐
│ CorsFilter │ ← 跨域处理
└──────────┬───────────┘
┌──────────────────────┐
│ JwtAuthentication │ ← JWT Token 验证
│ TokenFilter │
└──────────┬───────────┘
┌──────────────────────┐
│ UsernamePassword │ ← Spring Security 内置
│ AuthenticationFilter │
└──────────┬───────────┘
┌──────────────────────┐
│ Controller Handler │
└──────────────────────┘
```
#### 安全配置要点
| 配置项 | 值/策略 |
|--------|---------|
| CSRF | 禁用(无状态 Token 认证) |
| Session | STATELESS无会话 |
| 密码加密 | BCryptPasswordEncoder |
| 匿名路径 | `/login`, `/register`, `/captchaImage` |
| 公共资源 | `/**/*.html`, `/**/*.css`, `/**/*.js`, `/profile/**` |
| Swagger | `/swagger-ui.html`, `/swagger-resources/**` |
| 监控 | `/druid/**` |
| 其他所有请求 | 需要认证 |
#### 方法级安全
```java
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
```
- 启用 `@PreAuthorize``@PostAuthorize` 方法级权限控制
- 启用 `@Secured` 注解支持
### 2.2 JWT Token 机制
**核心类**`ruoyi-framework/.../web/service/TokenService.java`
#### Token 结构
```
JWT Header + Payload Redis 缓存
┌─────────────────────┐ ┌──────────────────────┐
│ { │ │ login_tokens:{uuid} │
│ LOGIN_USER_KEY │──uuid──→ │ { │
│ : uuid │ │ LoginUser Object │
│ } │ │ TTL: 30min │
└─────────────────────┘ └──────────────────────┘
```
#### 关键参数
| 参数 | 配置项 | 默认值 |
|------|--------|--------|
| Token Header | `token.header` | `Authorization` |
| Token Secret | `token.secret` | 自定义密钥 |
| Token 有效期 | `token.expireTime` | 30 分钟 |
| 自动刷新阈值 | 硬编码 | 剩余不足 20 分钟时刷新 |
#### Token 工作流程
1. **登录** → 生成 UUID → 存入 Redis → JWT 携带 UUID → 返回 Token
2. **请求** → 提取 Token → 解析 UUID → Redis 获取用户 → 验证有效期
3. **刷新** → 距过期不足 20 分钟 → 自动续期 Redis TTL
4. **登出** → 删除 Redis 中的用户缓存
### 2.3 权限控制体系
#### 权限模型
```
用户 (SysUser)
└── 角色 (SysRole) [多对多]
└── 菜单/权限 (SysMenu) [多对多]
└── 权限标识: "system:user:list"
```
#### 三种权限控制方式
| 方式 | 注解/实现 | 层级 | 用途 |
|------|-----------|------|------|
| URL 级 | Spring Security `antMatchers` | 网关 | 公开/匿名资源 |
| 方法级 | `@PreAuthorize("@ss.hasPermi('...')")` | Controller | 功能权限 |
| 数据级 | `@DataScope(deptAlias, userAlias)` | Service | 数据范围过滤 |
#### PermissionService 权限判断
```java
@Service("ss")
public class PermissionService {
hasPermi("system:user:list") // 是否拥有某权限
lacksPermi("system:user:list") // 是否不拥有某权限
hasAnyPermi("a,b,c") // 是否拥有任一权限
hasRole("admin") // 是否拥有某角色
lacksRole("admin") // 是否不拥有某角色
hasAnyRoles("admin,common") // 是否拥有任一角色
}
```
权限标识约定:`{模块}:{实体}:{操作}`,如 `system:user:list`、`booksystem:account:add`。
---
## 3. AOP 切面分析
**文件位置**`ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/`
### 3.1 切面总览
```
┌─────────────────────────────────────────────────────┐
│ AOP 切面 (4个) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ LogAspect │ │ DataScope │ │
│ │ @AfterRet │ │ @Before │ │
│ │ @AfterThrow │ │ 数据权限过滤 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ DataSource │ │ RateLimiter │ │
│ │ @Around │ │ @Before │ │
│ │ 多数据源切换 │ │ Redis 限流 │ │
│ │ Order(1) │ │ │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
```
### 3.2 日志切面 (LogAspect)
| 属性 | 值 |
|------|-----|
| 触发方式 | `@annotation(Log)` |
| 通知类型 | `@AfterReturning`(成功)、`@AfterThrowing`(异常) |
| 记录内容 | 操作用户、IP、URL、方法名、请求参数、响应结果、业务类型、状态 |
| 写入策略 | **异步写入**AsyncManager + AsyncFactory不阻塞主线程 |
| 参数过滤 | 自动过滤 MultipartFile、HttpServletRequest 等不可序列化对象 |
### 3.3 数据权限切面 (DataScopeAspect)
| 属性 | 值 |
|------|-----|
| 触发方式 | `@annotation(DataScope)` |
| 通知类型 | `@Before` |
| 权限级别 | 5 种:全部(1)、自定义(2)、本部门(3)、本部门及以下(4)、仅本人(5) |
| 实现原理 | 在查询参数 `params.dataScope` 中注入 SQL 片段XML 中通过 `${params.dataScope}` 拼接 |
| 安全机制 | 执行前先清空 `dataScope` 参数防止 SQL 注入 |
**SQL 拼接示例**
```sql
-- 本部门权限
AND (d.dept_id = 100)
-- 本部门及以下
AND (d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = 100
OR find_in_set(100, ancestors)))
```
### 3.4 数据源切面 (DataSourceAspect)
| 属性 | 值 |
|------|-----|
| 触发方式 | `@annotation(DataSource)``@within(DataSource)` |
| 通知类型 | `@Around` |
| 优先级 | `@Order(1)`(最高优先级) |
| 实现原理 | 通过 `ThreadLocal` 设置当前线程的数据源标识,执行完毕后清理 |
| 底层支持 | `DynamicDataSource`(继承 `AbstractRoutingDataSource` |
### 3.5 限流切面 (RateLimiterAspect)
| 属性 | 值 |
|------|-----|
| 触发方式 | `@annotation(RateLimiter)` |
| 通知类型 | `@Before` |
| 存储介质 | Redis + Lua 脚本(原子操作) |
| 限流维度 | 默认方法级、IPIP + 方法级) |
| 可配置参数 | `key`(键前缀)、`time`(时间窗口/秒)、`count`(最大请求数) |
| 默认值 | 60 秒内最多 100 次请求 |
| 失败响应 | 抛出 `ServiceException("访问过于频繁,请稍候再试")` |
---
## 4. 模块依赖
### 4.1 模块依赖图
```
┌─────────────┐
│ ruoyi-admin │ ← 启动入口,打包部署
└──────┬──────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌──────────────┐
│ruoyi- │ │ruoyi- │ │ ruoyi- │
│system │ │quartz │ │ generator │
└─────┬─────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ ruoyi-framework │
│ (Security, AOP, DataSource, Config) │
└────────────────────┬─────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌──────────────┐
│book- │ │stock- │ │ ruoyi- │
│system │ │system │ │ common │
└─────┬─────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
└──────────────┼───────────────┘
(所有业务模块依赖)
```
### 4.2 模块职责
| 模块 | 职责 | 核心内容 |
|------|------|----------|
| **ruoyi-admin** | 启动入口 & Controller 层 | SpringBoot 启动类、Controller、Swagger 配置 |
| **ruoyi-framework** | 框架核心 | Security 配置、JWT、AOP 切面、数据源、Redis、MyBatis 配置 |
| **ruoyi-system** | 系统管理业务 | 用户、角色、菜单、部门、字典、岗位、日志的 Service/Mapper/Domain |
| **ruoyi-common** | 通用工具 | 基类、工具类、注解、常量、异常、过滤器、分页组件 |
| **book-system** | 账务业务模块 | 账本、账户、操作记录、统计的 CRUD |
| **stock-system** | 股票业务模块 | 股票信息、行情、财务、趋势分析 |
| **ruoyi-quartz** | 定时任务 | 基于 Quartz 的任务调度 |
| **ruoyi-generator** | 代码生成 | 基于 Velocity 模板的代码生成器 |
### 4.3 技术栈汇总
| 分类 | 技术 | 版本 |
|------|------|------|
| 核心框架 | Spring Boot | 2.5.8 |
| JDK | Java | 1.8 |
| 安全框架 | Spring Security + JWT | 内置 + jjwt 0.9.1 |
| ORM | MyBatis | 2.2.0 (spring-boot-starter) |
| 分页 | PageHelper | 1.4.0 |
| 数据库连接池 | Druid | 1.2.8 |
| 缓存 | Redis (Spring Data Redis) | 内置 |
| API 文档 | Springfox Swagger 3 | 3.0.0 |
| JSON | FastJSON | 1.2.79 |
| Excel | Apache POI | 4.1.2 |
| 验证码 | Kaptcha | 2.3.2 |
| 代码生成 | Velocity | 2.3 |
| 系统监控 | Oshi | 5.8.6 |
---
## 5. 存在的问题
| # | 问题 | 严重等级 | 影响范围 | 优化建议 |
|---|------|----------|----------|----------|
| 1 | **fastjson 版本存在已知漏洞** | 🔴 高 | 全系统 | fastjson 1.2.79 存在多个 CVE 漏洞(如 CVE-2022-25845建议升级至 2.xfastjson2或替换为 Jackson/Gson |
| 2 | **使用已废弃的 WebSecurityConfigurerAdapter** | 🟡 中 | 安全配置 | Spring Security 5.7+ 已废弃 `WebSecurityConfigurerAdapter`,应改用 `SecurityFilterChain` Bean 配置方式 |
| 3 | **Token 解析异常被静默吞没** | 🟡 中 | 认证模块 | `TokenService.getLoginUser()` 中 catch 块为空(第 72-74 行),异常被完全忽略,建议至少记录 WARN 日志 |
| 4 | **SQL 注入风险:${params.dataScope}** | 🟡 中 | 数据权限层 | MyBatis XML 中 `${params.dataScope}` 使用 `${}` 直接拼接,虽有 DataScopeAspect 前置清空防护,但仍存在绕过风险;建议使用参数化方案或严格校验 |
| 5 | **业务 Controller 缺少 @Log 注解** | 🟢 低 | book-system | `book-system` 下的 Controller 中 `AccountController` 等部分接口缺少 `@Log` 操作日志注解,审计不完整 |
| 6 | **缺少统一 DTO/VO 分层** | 🟢 低 | 全系统 | Domain 实体直接暴露给 Controller 层(如 `SysUser` 同时用于数据库映射和接口响应),密码等敏感字段需额外处理;建议引入独立的 DTO/VO 对象 |
| 7 | **Service 层直接注入 Mapper而非通过接口** | 🟢 低 | 全系统 | `SysUserServiceImpl` 中使用 `@Autowired private SysUserMapper userMapper` 直接注入实现类,虽然 MyBatis 通过代理实现,但不符合面向接口编程原则 |
| 8 | **缺少接口版本控制** | 🟢 低 | API 设计 | 所有 API 路径无版本号(如 `/api/v1/system/user`),未来接口升级时无法平滑过渡 |
---
*文档结束*