diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..734ba8e --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,111 @@ +# RuoYi-Vue 架构文档 + +> **项目版本**: 3.8.0 | **分析日期**: 2026-06-28 +> **前端**: Vue 2.6.12 + Element UI 2.15.6 | **后端**: Spring Boot 2.5.8 + JDK 1.8 + +--- + +## 文档清单 + +1. [架构概览](architecture-overview.md) + - 系统总体架构(前后端分层图、模块依赖图) + - 技术栈总结(前端 15 项、后端 14 项、数据库) + - 模块划分(8 个 Maven 模块职责与依赖关系) + - 关键设计模式(MVC、AOP 切面、单例、工厂、策略、模板方法等) + +2. [前端架构分析](frontend-architecture.md) + - 组件结构(22 个组件组、~40 个视图页面、复用程度分析) + - 路由权限(静态路由 + 动态路由 + 服务端路由,三层权限控制) + - 状态管理(Vuex 5 个模块、数据流向、状态持久化) + - API 调用层(Axios 封装、拦截器、错误处理、错误码映射) + - 问题清单(8 个问题 + 8 条优化建议) + +3. [后端架构分析](backend-architecture.md) + - 分层架构(Controller → Service → Mapper → Domain,四层架构详解) + - 安全配置(Spring Security 认证流程、JWT Token 机制、权限控制体系) + - AOP 切面(LogAspect、DataScopeAspect、DataSourceAspect、RateLimiterAspect) + - 模块依赖(Maven 多模块依赖图、模块职责、技术栈汇总) + - 问题清单(8 个问题及优化建议) + +4. [前后端交互规范](api-interaction-spec.md) + - API 接口清单(35 个前端模块、19 个 Controller、约 182 个端点) + - 请求/响应格式(AjaxResult、TableDataInfo、统一响应结构) + - 错误处理(前端响应拦截器、后端全局异常处理、错误码体系) + - 分页排序(PageHelper 实现、排序参数、过滤规范、SQL 注入防护) + - 问题清单(10 个问题 + 短/中/长期优化建议) + +5. [数据库结构分析](database-structure.md) + - 表结构清单(~49 张表:系统管理 21、Quartz 9、股票业务 11、账户记账 6) + - 表关系(ER 关系图、外键约束分析、多对多关系表) + - 索引分析(主键索引、唯一索引、索引覆盖度评估) + - 查询模式(常用查询、复杂关联查询、统计查询、性能瓶颈识别) + - 问题清单(10 个问题 + P0/P1/P2 三级优化建议含 SQL 脚本) + +6. [优化建议清单](optimization-suggestions.md) + - 15+ 条优化建议(来自上述文档的汇总提炼) + - 优先级排序(P0 紧急 / P1 重要 / P2 一般) + - 实施步骤(短期 1-2 周 / 中期 1-2 月 / 长期持续) + +--- + +## 后续重构计划 + +基于本架构分析,后续将执行以下重构任务: + +### 1. refactor-frontend-modernization — 前端重构 + +- 升级至 Vue 3 + Vite + Pinia +- 参考 `demo-dashboard-heatmap.html` 风格进行 UI 现代化 +- 引入数据可视化组件(ECharts 升级) +- 统一权限校验核心模块,消除重复代码 +- 引入统一状态持久化层 +- 将业务组件从通用组件目录分离 +- 抽取 CRUD Schema 驱动高阶组件 + +### 2. refactor-backend-api — 后端 API 规范化 + +- 统一 RESTful API 设计规范 +- 优化错误处理(HTTP 状态码与业务状态码对齐) +- 添加 Swagger/OpenAPI 接口文档 +- 替换 fastjson 为 fastjson2 或 Jackson +- 引入统一 DTO/VO 分层 +- 补全 `@Log` 操作日志注解 +- 清理调试代码(`System.out.println` → `logger`) + +### 3. refactor-database-optimization — 数据库优化 + +- 修正主键类型(`double` → `bigint`) +- 为 `trade_dates` 等表添加主键 +- 为核心查询字段添加索引(code、trade_day、user_id 等) +- 优化 `find_in_set` 查询(闭包表或整数数组方案) +- 修复 `statistics` 表字段拼写错误 +- 时间查询优化(`date_format` → 范围查询) +- 为业务表添加审计字段与软删除机制 + +### 4. refactor-coding-standards — 编码规范 + +- 制定项目编码规范文档 +- 集成 Checkstyle / SpotBugs 自动检查 +- 集成 ESLint + Prettier 前端代码规范 +- 统一命名规范(包名、类名、方法名、变量名) +- 规范注释与 Javadoc 编写 +- 建立 Git Commit 信息规范(Conventional Commits) + +--- + +## 文档导航 + +``` +docs/architecture/ +├── README.md ← 当前文件:文档索引与重构计划 +├── architecture-overview.md ← 系统总体架构概览 +├── frontend-architecture.md ← 前端架构详细分析 +├── backend-architecture.md ← 后端架构详细分析 +├── api-interaction-spec.md ← 前后端交互规范 +├── database-structure.md ← 数据库结构分析 +└── optimization-suggestions.md ← 优化建议汇总清单 +``` + +--- + +*最后更新: 2026-06-28* diff --git a/docs/architecture/api-interaction-spec.md b/docs/architecture/api-interaction-spec.md new file mode 100644 index 0000000..9af03c0 --- /dev/null +++ b/docs/architecture/api-interaction-spec.md @@ -0,0 +1,590 @@ +# RuoYi-Vue 前后端交互规范 + +> 文档版本:v1.0 | 生成日期:2026-06-28 | 适用范围:RuoYi-Vue 全项目 + +--- + +## 1. API 接口清单 + +### 1.1 接口总览 + +项目共包含 **35 个前端 API 模块文件**,**19 个后端 Controller**,梳理出 **约 180+ 个 API 端点**。 + +### 1.2 按模块分类 + +#### 1.2.1 系统管理模块(system) + +| 序号 | 模块 | 前端 API 文件 | 后端 Controller | 接口数 | 基础路径 | +|------|------|--------------|-----------------|--------|----------| +| 1 | 用户管理 | `system/user.js` | `SysUserController` | 13 | `/system/user` | +| 2 | 角色管理 | `system/role.js` | `SysRoleController` | 11 | `/system/role` | +| 3 | 菜单管理 | `system/menu.js` | `SysMenuController` | 7 | `/system/menu` | +| 4 | 部门管理 | `system/dept.js` | `SysDeptController` | 8 | `/system/dept` | +| 5 | 岗位管理 | `system/post.js` | `SysPostController` | 5 | `/system/post` | +| 6 | 字典类型 | `system/dict/type.js` | `SysDictTypeController` | 7 | `/system/dict/type` | +| 7 | 字典数据 | `system/dict/data.js` | `SysDictDataController` | 6 | `/system/dict/data` | +| 8 | 参数配置 | `system/config.js` | `SysConfigController` | 7 | `/system/config` | +| 9 | 公告管理 | `system/notice.js` | `SysNoticeController` | 5 | `/system/notice` | + +**系统管理模块小计:69 个接口** + +#### 1.2.2 监控模块(monitor) + +| 序号 | 模块 | 前端 API 文件 | 后端 Controller | 接口数 | 基础路径 | +|------|------|--------------|-----------------|--------|----------| +| 1 | 在线用户 | `monitor/online.js` | `SysUserOnlineController` | 2 | `/monitor/online` | +| 2 | 登录日志 | `monitor/logininfor.js` | `SysLogininforController` | 3 | `/monitor/logininfor` | +| 3 | 操作日志 | `monitor/operlog.js` | `SysOperlogController` | 3 | `/monitor/operlog` | +| 4 | 服务监控 | `monitor/server.js` | `ServerController` | 1 | `/monitor/server` | +| 5 | 缓存监控 | `monitor/cache.js` | `CacheController` | 1 | `/monitor/cache` | +| 6 | 定时任务 | `monitor/job.js` | `SysJobController` | 7 | `/monitor/job` | +| 7 | 任务日志 | `monitor/jobLog.js` | `SysJobLogController` | 3 | `/monitor/jobLog` | + +**监控模块小计:20 个接口** + +#### 1.2.3 业务模块 — 记账系统(booksystem) + +| 序号 | 模块 | 前端 API 文件 | 后端 Controller | 接口数 | 基础路径 | +|------|------|--------------|-----------------|--------|----------| +| 1 | 交易账户 | `booksystem/account.js` | `AccountController` | 5 | `/booksystem/account` | +| 2 | 交易记账 | `booksystem/book.js` | `AccountBookController` | 5 | `/booksystem/book` | +| 3 | 当日操作 | `booksystem/operations.js` | `OperationsController` | 5 | `/booksystem/operations` | +| 4 | 单笔统计 | `booksystem/statistics.js` | `StatisticsController` | 5 | `/booksystem/statistics` | +| 5 | 持仓统计 | `booksystem/statistictotal.js` | `StatisticsTotalController` | 5 | `/booksystem/statistictotal` | +| 6 | 当日持仓 | `booksystem/statisticremain.js` | `StatisticsRemainController` | 5 | `/booksystem/statisticremain` | + +**记账系统模块小计:30 个接口** + +#### 1.2.4 业务模块 — 股票系统(stocksystem) + +| 序号 | 模块 | 前端 API 文件 | 后端 Controller | 接口数 | 基础路径 | +|------|------|--------------|-----------------|--------|----------| +| 1 | 行情数据 | `stocksystem/stocks.js` | `StocksController` | 6 (+16 扩展) | `/stocksystem/stocks` | +| 2 | 基础数据 | `stocksystem/stockbasic.js` | `StockBasicController` | 5 | `/stocksystem/stockbasic` | +| 3 | 财务数据 | `stocksystem/financial.js` | `StockFinancialController` | 5 | `/stocksystem/financial` | +| 4 | 指数行情 | `stocksystem/stockindex.js` | `StockIndexController` | 5 | `/stocksystem/stockindex` | +| 5 | 涨跌停板 | `stocksystem/stockslimit.js` | `StocksLimitController` | 5 | `/stocksystem/stockslimit` | +| 6 | 动量结果 | `stocksystem/trends.js` | `TrendsController` | 5 (+9 扩展) | `/stocksystem/trends` | +| 7 | 动量个股 | `stocksystem/trendStocks.js` | `StocksInTrendController` | 5 | `/stocksystem/trendStocks` | +| 8 | 创新高低 | `stocksystem/newrecord.js` | `StocksNewRecordController` | 5 (+2 扩展) | `/stocksystem/newrecord` | +| 9 | 股票记账 | `stocksystem/book.js` | `StocksBookController` | 5 | `/stocksystem/book` | + +**股票系统模块小计:约 63 个接口** + +#### 1.2.5 工具与通用模块 + +| 序号 | 模块 | 前端 API 文件 | 后端 Controller | 接口数 | 基础路径 | +|------|------|--------------|-----------------|--------|----------| +| 1 | 代码生成 | `tool/gen.js` | `GenController` | 9 | `/tool/gen` | +| 2 | 登录认证 | `login.js` | `SysLoginController` / `CaptchaController` | 6 | `/login`, `/register` 等 | +| 3 | 路由信息 | `menu.js` | `SysLoginController` | 1 | `/getRouters` | +| 4 | 首页聚合 | `index.js` | 多 Controller 组合 | 16 | 分散 | +| 5 | 通用上传 | - | `CommonController` | 2 | `/common/upload`, `/common/download` | + +**工具与通用模块小计:约 34 个接口** + +### 1.3 分类统计 + +| 模块大类 | 接口数量 | 占比 | +|----------|----------|------| +| 系统管理 | ~69 | 38% | +| 监控管理 | ~20 | 11% | +| 记账系统 | ~30 | 17% | +| 股票系统 | ~63 | 35% | +| 工具与通用 | ~34 | - | +| **合计** | **~182** | **100%** | + +--- + +## 2. 请求/响应格式 + +### 2.1 统一响应格式 + +所有 API 接口统一使用 `AjaxResult` 作为响应载体,其本质是 `HashMap`。 + +#### 2.1.1 成功响应(无数据) + +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +#### 2.1.2 成功响应(带数据) + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "userId": 1, + "userName": "admin" + } +} +``` + +#### 2.1.3 成功响应(多字段 — AjaxResult.put 扩展) + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { /* 用户详情 */ }, + "roles": [ /* 角色列表 */ ], + "posts": [ /* 岗位列表 */ ], + "postIds": [1, 2], + "roleIds": [1] +} +``` + +> **注意**:后端使用 `ajax.put(key, value)` 动态追加字段,前端通过 `response.roles` 等方式访问。 + +#### 2.1.4 分页响应(TableDataInfo) + +```json +{ + "total": 100, + "rows": [ + { "userId": 1, "userName": "admin" }, + { "userId": 2, "userName": "test" } + ], + "code": 200, + "msg": "查询成功" +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `total` | `long` | 总记录数 | +| `rows` | `List` | 当前页数据列表 | +| `code` | `int` | 状态码(固定 200) | +| `msg` | `String` | 消息文本 | + +#### 2.1.5 失败响应 + +```json +{ + "code": 500, + "msg": "新增用户'admin'失败,登录账号已存在" +} +``` + +### 2.2 请求格式规范 + +#### 2.2.1 查询列表(GET) + +```javascript +// 前端 +export function listUser(query) { + return request({ url: '/system/user/list', method: 'get', params: query }) +} + +// 请求参数通过 URL Query 传递 +GET /system/user/list?pageNum=1&pageSize=10&userName=admin&status=0 +``` + +#### 2.2.2 查询详情(GET + PathVariable) + +```javascript +export function getUser(userId) { + return request({ url: '/system/user/' + userId, method: 'get' }) +} + +// 请求 +GET /system/user/1 +``` + +#### 2.2.3 新增(POST + JSON Body) + +```javascript +export function addUser(data) { + return request({ url: '/system/user', method: 'post', data: data }) +} + +// 请求头: Content-Type: application/json;charset=utf-8 +POST /system/user +Body: { "userName": "test", "phonenumber": "13800138000" } +``` + +#### 2.2.4 修改(PUT + JSON Body) + +```javascript +export function updateUser(data) { + return request({ url: '/system/user', method: 'put', data: data }) +} + +PUT /system/user +Body: { "userId": 1, "userName": "admin" } +``` + +#### 2.2.5 删除(DELETE + PathVariable) + +```javascript +export function delUser(userId) { + return request({ url: '/system/user/' + userId, method: 'delete' }) +} + +DELETE /system/user/1 +``` + +> 后端实际接收 `Long[]` 数组,支持批量删除:`DELETE /system/user/1,2,3` + +#### 2.2.6 文件下载(POST + Blob) + +```javascript +export function download(url, params, filename) { + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob' + }) +} +``` + +#### 2.2.7 登录请求(跳过 Token) + +```javascript +export function login(username, password, code, uuid) { + return request({ + url: '/login', + headers: { isToken: false }, // 不携带 Token + method: 'post', + data: { username, password, code, uuid } + }) +} +``` + +### 2.3 登录认证机制 + +| 环节 | 说明 | +|------|------| +| Token 载体 | JWT Token | +| Token 传递 | `Authorization: Bearer {token}` | +| 请求头配置 | `axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'` | +| Token 跳过 | `headers: { isToken: false }` | +| 超时时间 | 60 秒(验证码接口 20 秒) | + +--- + +## 3. 错误处理机制 + +### 3.1 前端响应拦截器 + +位置:`ruoyi-ui/src/utils/request.js` + +```javascript +service.interceptors.response.use(res => { + const code = res.data.code || 200; // 默认成功 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + + // 二进制数据直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + + if (code === 401) { + // 弹出确认框 → 重新登录 + MessageBox.confirm('登录状态已过期...', '系统提示', {...}) + return Promise.reject('无效的会话...') + } else if (code === 500) { + // 全局 Message.error 提示 + Message({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code !== 200) { + // 其他错误码 → Notification.error + Notification.error({ title: msg }) + return Promise.reject('error') + } else { + return res.data // 正常返回 + } +}, error => { + // HTTP 层错误处理 + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + Message({ message, type: 'error', duration: 5000 }) + return Promise.reject(error) +}) +``` + +### 3.2 后端全局异常处理 + +位置:`ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java` + +| 异常类型 | 处理方式 | 返回状态码 | +|----------|----------|------------| +| `AccessDeniedException` | 权限校验失败 | `403` — "没有权限,请联系管理员授权" | +| `HttpRequestMethodNotSupportedException` | 不支持的请求方法 | `500` — 异常消息 | +| `ServiceException` | 业务异常(带自定义 code) | code 或 `500` | +| `BindException` | 参数校验失败 | `500` — 校验错误消息 | +| `MethodArgumentNotValidException` | @Valid 校验失败 | `500` — 字段错误消息 | +| `DemoModeException` | 演示模式限制 | `500` — "演示模式,不允许操作" | +| `RuntimeException` | 未知运行时异常 | `500` — 异常消息 | +| `Exception` | 兜底系统异常 | `500` — 异常消息 | + +### 3.3 错误码体系 + +位置:`ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java` + +| 状态码 | 常量名 | 含义 | +|--------|--------|------| +| 200 | `SUCCESS` | 操作成功 | +| 201 | `CREATED` | 对象创建成功 | +| 202 | `ACCEPTED` | 请求已被接受 | +| 204 | `NO_CONTENT` | 操作成功但无返回数据 | +| 301 | `MOVED_PERM` | 资源已永久移除 | +| 303 | `SEE_OTHER` | 重定向 | +| 304 | `NOT_MODIFIED` | 资源未修改 | +| 400 | `BAD_REQUEST` | 参数错误 | +| 401 | `UNAUTHORIZED` | 未授权 / Token 过期 | +| 403 | `FORBIDDEN` | 访问受限 / 权限不足 | +| 404 | `NOT_FOUND` | 资源/服务未找到 | +| 405 | `BAD_METHOD` | 不支持的 HTTP 方法 | +| 409 | `CONFLICT` | 资源冲突/被锁 | +| 415 | `UNSUPPORTED_TYPE` | 不支持的媒体类型 | +| 500 | `ERROR` | 系统内部错误 | +| 501 | `NOT_IMPLEMENTED` | 接口未实现 | + +#### 前端错误码映射 + +位置:`ruoyi-ui/src/utils/errorCode.js` + +```javascript +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} +``` + +> **注意**:前端仅映射了 3 个业务错误码,其余错误码(400、409、415 等)直接 fallback 到后端返回的 `msg` 或 `default`。 + +### 3.4 错误处理流程图 + +``` +HTTP 层错误 业务层错误 + │ │ + ▼ ▼ +Network Error / Timeout res.data.code + │ │ + ▼ ▼ +Message.error code === 401 → MessageBox.confirm → 跳转登录 + code === 500 → Message.error + code !== 200 → Notification.error + code === 200 → 正常返回 res.data +``` + +--- + +## 4. 分页 / 排序 / 过滤 + +### 4.1 分页实现 + +基于 **PageHelper** 实现,采用 ThreadLocal 模式。 + +#### 4.1.1 后端处理流程 + +``` +前端请求 后端处理 + │ │ + │ GET /system/user/list │ + │ ?pageNum=1&pageSize=10 │ + ▼ ▼ + TableSupport.buildPageRequest() + │ + ▼ + PageUtils.startPage() + │ + ▼ + PageHelper.startPage(pageNum, pageSize, orderBy) + │ + ▼ + 执行 MyBatis 查询(自动添加 LIMIT) + │ + ▼ + BaseController.getDataTable(list) + │ + ▼ + new PageInfo(list).getTotal() → total + return TableDataInfo { total, rows, code, msg } +``` + +#### 4.1.2 分页参数 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `pageNum` | `Integer` | 否 | - | 当前页码(从 1 开始) | +| `pageSize` | `Integer` | 否 | - | 每页记录数 | +| `orderByColumn` | `String` | 否 | - | 排序字段名 | +| `isAsc` | `String` | 否 | - | 排序方向:`asc` 或 `desc`,多字段用逗号分隔 | +| `reasonable` | `Boolean` | 否 | - | 分页合理化(pageNum<=0 查第一页,pageNum>总页数查最后一页) | + +#### 4.1.3 后端代码示例 + +```java +@GetMapping("/list") +public TableDataInfo list(SysUser user) { + startPage(); // 从请求参数读取分页信息 + List list = userService.selectUserList(user); + return getDataTable(list); // 封装为 TableDataInfo +} +``` + +### 4.2 排序规范 + +#### 4.2.1 排序参数传递 + +```javascript +// 前端通过分页组件传递 +{ + pageNum: 1, + pageSize: 10, + orderByColumn: 'createTime', + isAsc: 'desc' +} +``` + +#### 4.2.2 多字段排序 + +``` +isAsc: createTime desc,updateTime desc +``` + +#### 4.2.3 SQL 注入防护 + +```java +// SqlUtil.escapeOrderBySql() 方法对排序字段进行转义 +// 只允许字母、数字、下划线、点号、逗号、空格、ASC、DESC +String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); +PageHelper.orderBy(orderBy); +``` + +### 4.3 过滤规范 + +#### 4.3.1 列表查询过滤 + +采用 **对象参数绑定** 方式: + +```java +// 后端 +@GetMapping("/list") +public TableDataInfo list(SysUser user) { + startPage(); + // user 对象自动绑定 query 参数中的字段 + List list = userService.selectUserList(user); + return getDataTable(list); +} + +// 前端 +GET /system/user/list?userName=admin&phonenumber=138&status=0 +``` + +#### 4.3.2 权限过滤 + +```java +// 每个接口通过 @PreAuthorize 进行权限校验 +@PreAuthorize("@ss.hasPermi('system:user:list')") +@GetMapping("/list") +public TableDataInfo list(SysUser user) { ... } +``` + +#### 4.3.3 数据权限过滤 + +```java +// 在 Service 层通过 AOP 实现数据权限(部门数据范围) +// 支持:全部数据、本部门数据、本部门及以下、仅本人数据、自定义数据范围 +``` + +### 4.4 导出/导入规范 + +| 操作 | HTTP 方法 | 路径模式 | 说明 | +|------|-----------|----------|------| +| 导出 Excel | `POST` | `/{module}/export` | 直接写入 HttpServletResponse | +| 导入 Excel | `POST` | `/{module}/importData` | MultipartFile + updateSupport 参数 | +| 下载模板 | `POST` | `/{module}/importTemplate` | 返回空模板文件 | + +--- + +## 5. 存在的问题 + +### 5.1 问题清单 + +| # | 问题类型 | 严重程度 | 问题描述 | 涉及范围 | 优化建议 | +|---|----------|----------|----------|----------|----------| +| 1 | **响应格式不统一** | 高 | `AjaxResult`(普通 CRUD)与 `TableDataInfo`(分页列表)返回结构完全不同:前者用 `data` 字段,后者用 `rows` + `total` 字段。前端需要分别处理两种响应结构,增加认知负担。 | 全部列表接口 vs 详情/操作接口 | 统一使用单一响应结构,如 `{ code, msg, data: { list, total } }`,或在 TableDataInfo 中也使用 `data` 字段包裹 | +| 2 | **错误码体系不完整** | 中 | 前端 `errorCode.js` 仅映射了 401/403/404 三个错误码,400/409/415 等 HTTP 标准错误码未做前端映射,全部 fallback 到 `default` 或后端 `msg`。后端虽定义了 16 个状态码,但实际使用集中在 200 和 500。 | 全局错误处理 | 完善前端错误码映射表;后端减少 500 的滥用,对参数错误返回 400,对业务冲突返回 409 | +| 3 | **排序字段 SQL 注入风险** | 高 | 虽然使用了 `SqlUtil.escapeOrderBySql()` 进行过滤,但正则表达式级别的过滤存在被绕过的可能。排序参数直接从请求参数读取并拼接到 SQL ORDER BY 子句中。 | `TableSupport` + `SqlUtil` | 使用白名单机制,仅允许传入已知的字段名;或使用 MyBatis 动态 SQL 的 `` 标签做安全排序 | +| 4 | **前后端 API 路径不一致** | 中 | 前端 `index.js` 调用 `/stocksystem/stocks/groupLimitlist`,而后端 `StocksController` 中该方法被注释了权限控制(`@PreAuthorize` 被注释)。另外部分接口前端使用 `/querylist`,后端映射为 `/list`,存在冗余接口。 | 股票系统模块 | 清理未使用的接口;补全权限控制;统一前后端路径命名规范 | +| 5 | **分页参数隐式依赖** | 中 | 分页参数通过 `ServletUtils.getParameter()` 从 HttpServletRequest 中隐式读取,而非通过 `@RequestParam` 显式声明。这使得 API 文档(如 Swagger)无法自动生成分页参数说明,API 可读性差。 | `TableSupport.getPageDomain()` | 使用 `@RequestParam(defaultValue = "1") Integer pageNum` 显式声明分页参数,或使用 `PageRequest` 对象作为 Controller 方法参数 | +| 6 | **HTTP 状态码与业务状态码混用** | 中 | 后端 HTTP Response 的 Status Code 始终为 200,真正的业务状态码在响应体 `code` 字段中。这导致前端无法利用 HTTP 状态码进行路由/拦截逻辑,也与 RESTful 最佳实践不符。 | 全局 | 保持当前模式(业界常见做法)但需在文档中明确说明;或改为 HTTP Status Code 与业务 code 一致 | +| 7 | **GET 请求参数转换方式不标准** | 低 | 前端 GET 请求的 `params` 被手动拼接为 URL 字符串(`tansParams` 函数),而不是使用 axios 原生的 `paramsSerializer`。这可能导致特殊字符编码问题。 | `request.js` 请求拦截器 | 使用 axios 的 `paramsSerializer` 配置,或 `URLSearchParams` 进行参数序列化 | +| 8 | **批量删除路径参数格式不一致** | 低 | 部分 Controller 使用 `@DeleteMapping("/{ids}")` 接收 `Long[]`,但 URL 中数组的传递格式(逗号分隔 vs 多次参数)缺乏统一规范,前端需要手动拼接字符串。 | 所有支持批量删除的接口 | 统一使用 `@DeleteMapping` + `@RequestParam List ids` 或 `@RequestBody` 传递数组 | +| 9 | **大量调试代码未清理** | 低 | `StocksController` 中存在大量 `System.out.println()` 调试输出(超过 50 处),以及大段被注释的代码(1000+ 行)。这些代码在生产环境中会污染日志,且增加维护成本。 | `stock-system` 模块 | 统一替换为 `logger.info/debug`;清理历史注释代码 | +| 10 | **接口文档缺失** | 中 | 项目虽引入了 Swagger 依赖(`TestController` 中有示例),但所有业务 Controller 均未添加 Swagger 注解(`@Api`、`@ApiOperation`、`@ApiParam` 等),导致无法自动生成 API 文档。 | 全部 Controller | 补充 Swagger/OpenAPI 注解;或接入 SpringDoc / Knife4j | + +### 5.2 综合优化建议 + +#### 短期优化(1-2 周) + +1. **补充前端错误码映射**:在 `errorCode.js` 中补充 400/409/415 等常见错误码的中文提示 +2. **清理调试代码**:将 `StocksController` 中的 `System.out.println` 替换为 `logger` +3. **补全权限注解**:取消 `StocksController` 中被注释的 `@PreAuthorize` + +#### 中期优化(1-2 月) + +4. **接入 API 文档**:为所有 Controller 补充 Swagger 注解,启用 Knife4j 文档页面 +5. **规范分页参数**:考虑使用 `@Validated` 显式声明分页参数 +6. **统一响应格式**:评估是否需要统一 `AjaxResult` 和 `TableDataInfo` 的字段结构 + +#### 长期优化(持续) + +7. **排序安全加固**:实施白名单机制替代正则过滤 +8. **RESTful 对齐**:评估是否将业务状态码与 HTTP 状态码对齐 +9. **API 版本管理**:当接口变更时引入 `/api/v1/` 等版本前缀 + +--- + +## 附录 + +### A. 关键技术栈 + +| 层级 | 技术 | 版本 | +|------|------|------| +| 前端框架 | Vue 2 + Element UI | 2.x | +| HTTP 客户端 | Axios | - | +| 后端框架 | Spring Boot | 2.x | +| Web 框架 | Spring MVC | - | +| 权限框架 | Spring Security | - | +| 分页组件 | PageHelper | - | +| ORM | MyBatis | - | + +### B. 核心文件路径 + +| 文件 | 路径 | +|------|------| +| 前端请求封装 | `ruoyi-ui/src/utils/request.js` | +| 前端错误码映射 | `ruoyi-ui/src/utils/errorCode.js` | +| 前端 API 目录 | `ruoyi-ui/src/api/` | +| 后端统一响应 | `ruoyi-common/.../AjaxResult.java` | +| 后端分页响应 | `ruoyi-common/.../TableDataInfo.java` | +| 后端分页工具 | `ruoyi-common/.../PageUtils.java` | +| 后端排序支持 | `ruoyi-common/.../TableSupport.java` | +| 后端全局异常 | `ruoyi-framework/.../GlobalExceptionHandler.java` | +| 后端状态码常量 | `ruoyi-common/.../HttpStatus.java` | +| 后端 BaseController | `ruoyi-common/.../BaseController.java` | + +### C. HTTP 方法使用统计 + +| 方法 | 用途 | 使用频率 | +|------|------|----------| +| `GET` | 查询列表、查询详情、查询树结构、下载 | ~45% | +| `POST` | 新增、登录、注册、导出、导入、分析 | ~30% | +| `PUT` | 修改、状态变更、授权、重置密码 | ~20% | +| `DELETE` | 删除、清空日志、刷新缓存 | ~5% | diff --git a/docs/architecture/architecture-overview.md b/docs/architecture/architecture-overview.md new file mode 100644 index 0000000..b77aadb --- /dev/null +++ b/docs/architecture/architecture-overview.md @@ -0,0 +1,264 @@ +# RuoYi-Vue 架构概览 + +> **项目版本**: 3.8.0 | **分析日期**: 2026-06-28 +> **前端**: Vue 2.6.12 + Element UI 2.15.6 | **后端**: Spring Boot 2.5.8 + JDK 1.8 + +--- + +## 1. 系统总体架构 + +### 1.1 系统架构图 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 浏览器客户端 │ +│ ┌───────────────────────────────────────────────────────────────────┐ │ +│ │ 前端层 (Vue 2 SPA) │ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │ │ +│ │ │ Views │ │ Layout │ │ Store │ │ Router │ │ Components │ │ │ +│ │ │ (40+页) │ │(侧边栏/ │ │ (Vuex 5 │ │(静态+ │ │ (22个组件组)│ │ │ +│ │ │ │ │ 标签页) │ │ modules)│ │动态路由) │ │ │ │ │ +│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └──────┬──────┘ │ │ +│ │ └───────────┴───────────┴───────────┴─────────────┘ │ │ +│ │ │ │ │ +│ │ ┌────────┴────────┐ │ │ +│ │ │ Axios 封装层 │ ← 请求/响应拦截器、错误处理 │ │ +│ │ └────────┬────────┘ │ │ +│ └───────────────────────────┼───────────────────────────────────────┘ │ +└──────────────────────────────┼──────────────────────────────────────────┘ + │ HTTP/JSON (Bearer Token) + ▼ +┌──────────────────────────────────────────────────────────────────────────┐ +│ 后端服务层 │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ ruoyi-admin (启动层 & Controller 层 — 28 个 Controller) │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │ │ +│ │ │ 系统管理(13) │ │ 监控管理(5) │ │ 业务模块(6) │ │ 工具/公共(4) │ │ │ +│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬───────┘ │ │ +│ │ └───────────────┴───────────────┴───────────────┘ │ │ +│ │ │ │ │ +│ │ ┌─────────────────────────┴──────────────────────────────┐ │ │ +│ │ │ ruoyi-framework (框架核心层) │ │ │ +│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │ │ +│ │ │ │ Security │ │ AOP 切面 │ │ Config │ │ Redis/JWT │ │ │ │ +│ │ │ │ (认证/ │ │ (4个切面) │ │ (12个配置)│ │ (Token管理)│ │ │ │ +│ │ │ │ 授权) │ │ │ │ │ │ │ │ │ │ +│ │ │ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │ │ │ +│ │ └─────────────────────────┬──────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ ┌──────────────┬───────┴───────┬──────────────┐ │ │ +│ │ ▼ ▼ ▼ ▼ │ │ +│ │ ┌──────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ +│ │ │ruoyi │ │ book- │ │ stock- │ │ ruoyi- │ │ │ +│ │ │system│ │ system │ │ system │ │ quartz │ │ │ +│ │ │(12S │ │ (6S/6M) │ │ (9M) │ │ /generator │ │ │ +│ │ │ 12M) │ │ │ │ │ │ │ │ │ +│ │ └──────┘ └──────────┘ └──────────┘ └────────────┘ │ │ +│ │ │ │ │ +│ │ ┌─────────────────────────┴──────────────────────────────┐ │ │ +│ │ │ ruoyi-common (通用工具层) │ │ │ +│ │ │ BaseController / AjaxResult / BaseEntity / 注解 / │ │ │ +│ │ │ 工具类(20+) / 异常类 / 过滤器 / 常量 / 分页组件 │ │ │ +│ │ └────────────────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────────┐ +│ 数据层 │ +│ ┌──────────────────────┐ ┌────────────────────────────┐ │ +│ │ MySQL 数据库 │ │ Redis 缓存 │ │ +│ │ ┌────────────────┐ │ │ ┌──────────────────────┐ │ │ +│ │ │ sys_* 系统表 │ │ │ │ login_tokens:{uuid} │ │ │ +│ │ │ book_* 账本表 │ │ │ │ 用户会话 / Token │ │ │ +│ │ │ stock_* 股票表 │ │ │ ├──────────────────────┤ │ │ +│ │ │ quartz_* 任务表 │ │ │ │ 限流计数器 │ │ │ +│ │ │ gen_* 生成器表 │ │ │ ├──────────────────────┤ │ │ +│ │ └────────────────┘ │ │ │ 配置缓存 / 字典缓存 │ │ │ +│ └──────────────────────┘ │ └──────────────────────┘ │ │ +│ └────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 分层说明 + +| 层级 | 职责 | 关键技术 | +|------|------|---------| +| **前端展示层** | 页面渲染、用户交互、路由导航、状态管理 | Vue 2 SPA、Element UI、Vuex、Vue Router | +| **API 通信层** | 前后端 HTTP 通信、Token 携带、错误统一处理 | Axios + 拦截器、JWT Bearer Token | +| **Controller 层** | 请求接收、参数校验、权限注解、响应封装 | Spring MVC、@PreAuthorize、@Log | +| **框架核心层** | 安全认证、AOP 切面、数据源路由、全局配置 | Spring Security、JWT、AspectJ、Druid | +| **Service 层** | 业务逻辑、事务管理、数据权限过滤 | @Transactional、@DataScope | +| **Mapper 层** | SQL 映射、动态查询、结果集映射 | MyBatis + XML + PageHelper | +| **Domain 层** | 实体模型、基类继承、VO/DTO 传输 | BaseEntity、TreeEntity | +| **数据存储层** | 持久化存储、会话缓存、限流计数 | MySQL 5.7+、Redis | + +--- + +## 2. 技术栈总结 + +### 2.1 前端技术栈 + +| 分类 | 技术 | 版本 | 用途 | +|------|------|------|------| +| 核心框架 | Vue.js | 2.6.12 | 渐进式 MVVM 框架 | +| UI 组件库 | Element UI | 2.15.6 | 桌面端组件库 | +| 路由管理 | Vue Router | 3.4.9 | SPA 路由(含动态路由) | +| 状态管理 | Vuex | 3.6.0 | 集中式状态管理(5 个模块) | +| HTTP 客户端 | Axios | 0.24.0 | HTTP 请求封装 | +| 构建工具 | Vue CLI | 4.4.6 | 项目脚手架 | +| CSS 预处理 | Sass | 1.32.13 | 样式预处理器 | +| 图表库 | ECharts | 4.9.0 | 数据可视化 | +| 富文本编辑器 | Quill | 1.3.7 | 富文本编辑 | +| 代码高亮 | highlight.js | 9.18.5 | 代码语法高亮 | +| 拖拽排序 | vuedraggable | 2.24.3 | 列表拖拽 | +| SVG 图标 | svg-sprite-loader | 5.1.1 | SVG 雪碧图 | +| 加密 | jsencrypt | 3.2.1 | RSA 密码加密 | +| 进度条 | NProgress | 0.2.0 | 页面加载进度 | +| Cookie | js-cookie | 3.0.1 | Cookie 读写 | + +### 2.2 后端技术栈 + +| 分类 | 技术 | 版本 | 用途 | +|------|------|------|------| +| 核心框架 | Spring Boot | 2.5.8 | 快速开发框架 | +| JDK | Java | 1.8 | 运行环境 | +| 安全框架 | Spring Security | 内置 | 认证与授权 | +| JWT | jjwt | 0.9.1 | Token 签发与验证 | +| ORM | MyBatis | 2.2.0 (starter) | SQL 映射框架 | +| 分页插件 | PageHelper | 1.4.0 | 物理分页 | +| 连接池 | Druid | 1.2.8 | 数据库连接池 + 监控 | +| 缓存 | Spring Data Redis | 内置 | 会话缓存、限流 | +| API 文档 | Springfox Swagger 3 | 3.0.0 | 接口文档 | +| JSON 解析 | FastJSON | 1.2.79 | JSON 序列化 | +| Excel 处理 | Apache POI | 4.1.2 | Excel 导入导出 | +| 验证码 | Kaptcha | 2.3.2 | 图形验证码 | +| 代码生成 | Velocity | 2.3 | 模板引擎 | +| 系统监控 | Oshi | 5.8.6 | 服务器信息采集 | +| 定时任务 | Quartz | 内置 | 任务调度 | + +### 2.3 数据库技术栈 + +| 技术 | 用途 | 说明 | +|------|------|------| +| MySQL 5.7+ | 主数据库 | 系统表(sys_*)、业务表(book_*/stock_*)、任务表(quartz_*) | +| Redis | 缓存/会话 | Token 存储(login_tokens:{uuid})、限流计数器、配置缓存 | + +--- + +## 3. 模块划分 + +### 3.1 模块清单 + +| 模块名 | 类型 | 职责 | 核心内容 | +|--------|------|------|----------| +| **ruoyi-admin** | 启动模块 | Spring Boot 启动入口,Controller 层聚合 | 启动类、28 个 Controller、Swagger 配置、打包部署 | +| **ruoyi-framework** | 框架核心 | 安全认证、AOP 切面、数据源、全局配置 | Security、JWT、4 个切面、12 个配置类、异步管理 | +| **ruoyi-system** | 系统管理 | 系统基础业务 | 用户/角色/菜单/部门/字典/岗位/日志 (12 Service + 12 Mapper) | +| **ruoyi-common** | 通用工具 | 基类、工具类、注解、异常、常量 | BaseController、AjaxResult、BaseEntity、20+ 工具类 | +| **ruoyi-generator** | 代码生成 | Velocity 模板驱动的代码生成器 | 表结构查询、模板渲染、2 个 Mapper | +| **ruoyi-quartz** | 定时任务 | 基于 Quartz 的任务调度管理 | 任务 CRUD、执行日志、2 个 Mapper | +| **book-system** | 账本业务 | 账务管理业务模块 | 账户/账本/操作记录/统计 (6 Service + 6 Mapper) | +| **stock-system** | 股票业务 | 股票分析业务模块 | 股票信息/行情/财务/趋势 (9 Mapper) | + +### 3.2 模块依赖图 + +``` + ┌─────────────┐ + │ ruoyi-admin │ ← 启动入口,打包部署 + └──────┬──────┘ + │ depends on + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌────────────┐ ┌────────────┐ ┌─────────────┐ + │ ruoyi- │ │ ruoyi- │ │ ruoyi- │ + │ system │ │ quartz │ │ generator │ + └──────┬─────┘ └──────┬─────┘ └──────┬──────┘ + │ │ │ + ▼ ▼ ▼ + ┌─────────────────────────────────────────────┐ + │ ruoyi-framework │ + │ (Security, AOP, DataSource, Redis, Config)│ + └──────────────────────┬──────────────────────┘ + │ depends on + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌────────────┐ ┌────────────┐ ┌─────────────┐ + │ book- │ │ stock- │ │ ruoyi- │ + │ system │ │ system │ │ common │ + └────────────┘ └────────────┘ └─────────────┘ +``` + +**依赖规则**: +- `ruoyi-admin` → 依赖所有业务模块 + framework +- 业务模块 (`ruoyi-system` / `book-system` / `stock-system` / `quartz` / `generator`) → 依赖 `ruoyi-framework` +- `ruoyi-framework` → 依赖 `ruoyi-common` +- `ruoyi-common` → 无内部依赖(底层基础模块) + +--- + +## 4. 关键设计模式 + +### 4.1 MVC 模式 + +``` +Controller (ruoyi-admin) + │ 接收请求、参数校验、权限控制 + ▼ +Service (各业务模块) + │ 业务逻辑、事务管理 + ▼ +Mapper (各业务模块) + │ SQL 映射、数据访问 + ▼ +Domain (ruoyi-common / 各模块) + 实体模型、基类继承 +``` + +- **Controller**: 统一继承 `BaseController`,获得 `startPage()`、`getDataTable()`、`toAjax()` 等通用方法 +- **Service**: 接口 + 实现分离 (`ISysUserService` / `SysUserServiceImpl`) +- **Mapper**: MyBatis XML 映射 + Java 接口,大量使用动态 SQL +- **Domain**: `BaseEntity` → `TreeEntity` 继承体系 + +### 4.2 分层架构 + +**物理分层**(Maven 多模块)与 **逻辑分层**(Controller → Service → Mapper)相结合: + +``` +┌───────────────────────────────────────────┐ +│ ruoyi-admin → Controller 层 (表现层) │ +├───────────────────────────────────────────┤ +│ ruoyi-framework → 框架支撑层 (横切关注点) │ +├───────────────────────────────────────────┤ +│ ruoyi-system → Service/Mapper (业务层) │ +│ book-system → Service/Mapper (业务层) │ +│ stock-system → Service/Mapper (业务层) │ +├───────────────────────────────────────────┤ +│ ruoyi-common → Domain/工具类 (基础层) │ +└───────────────────────────────────────────┘ +``` + +### 4.3 AOP 切面 + +| 切面 | 注解 | 通知类型 | 优先级 | 用途 | +|------|------|---------|--------|------| +| **DataSourceAspect** | `@DataSource` | `@Around` | `@Order(1)` | 多数据源动态切换 | +| **DataScopeAspect** | `@DataScope` | `@Before` | — | 数据权限 SQL 注入 | +| **LogAspect** | `@Log` | `@AfterReturning` / `@AfterThrowing` | — | 操作日志异步记录 | +| **RateLimiterAspect** | `@RateLimiter` | `@Before` | — | Redis 限流控制 | + +### 4.4 其他设计模式 + +| 模式 | 应用场景 | 实现方式 | +|------|---------|---------| +| **单例模式** | Spring Bean 默认作用域 | 配置类 (`SecurityConfig`、`DruidConfig` 等) 通过 `@Configuration` + `@Bean` 实现 | +| **工厂模式** | 异步任务创建 | `AsyncFactory` 创建不同类型的异步任务(操作日志、登录日志) | +| **策略模式** | 数据权限过滤 | `@DataScope` 注解根据不同数据范围类型(全部/本部门/本人等)生成不同的 SQL 过滤策略 | +| **模板方法模式** | BaseController | 基类定义通用流程(分页、响应),子类 Controller 继承复用 | +| **责任链模式** | Spring Security 过滤器链 | `CorsFilter → JwtAuthenticationTokenFilter → AuthenticationFilter` 依次处理 | +| **代理模式** | MyBatis Mapper | MyBatis 通过动态代理生成 Mapper 接口实现 | +| **观察者模式** | Spring 事件机制 | `AsyncManager` 通过线程池异步处理任务,解耦日志写入与主流程 | + +--- + +*文档结束* diff --git a/docs/architecture/backend-architecture.md b/docs/architecture/backend-architecture.md new file mode 100644 index 0000000..110afe5 --- /dev/null +++ b/docs/architecture/backend-architecture.md @@ -0,0 +1,470 @@ +# 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: + +| 标签 | 用途 | 示例 | +|------|------|------| +| `` | 条件查询 | `` | +| `` | 批量操作 | `` | +| `` | SQL 片段复用 | `` | +| `` | 定义可复用片段 | `` | +| `` | 动态更新 | `field = #{...},` | +| `${params.dataScope}` | 数据权限注入 | 由 DataScopeAspect 动态拼接 | + +#### ResultMap 关联映射 + +使用 `` 和 `` 实现一对多关联查询: +```xml + + +``` + +### 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 脚本(原子操作) | +| 限流维度 | 默认(方法级)、IP(IP + 方法级) | +| 可配置参数 | `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.x(fastjson2)或替换为 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`),未来接口升级时无法平滑过渡 | + +--- + +*文档结束* diff --git a/docs/architecture/database-structure.md b/docs/architecture/database-structure.md new file mode 100644 index 0000000..e8af924 --- /dev/null +++ b/docs/architecture/database-structure.md @@ -0,0 +1,455 @@ +# RuoYi-Vue 数据库结构分析 + +> 分析日期:2026-06-28 +> 数据来源:建表 SQL 文件、Mapper XML 文件、Domain 实体类 + +--- + +## 1. 表结构清单 + +### 1.1 系统管理表(sys_* / gen_* / QRTZ_*) + +系统管理表位于 `ry` 数据库,由 RuoYi 框架提供。 + +| 表名 | 字段数 | 主键 | 用途 | 备注 | +|------|--------|------|------|------| +| `sys_user` | 18 | user_id (bigint, AI) | 用户信息表 | 含 del_flag 软删除 | +| `sys_dept` | 12 | dept_id (bigint, AI) | 部门表 | 树形结构,含 ancestors | +| `sys_role` | 13 | role_id (bigint, AI) | 角色信息表 | 含 data_scope 数据权限 | +| `sys_menu` | 17 | menu_id (bigint, AI) | 菜单权限表 | 树形结构,M/C/F 三种类型 | +| `sys_post` | 9 | post_id (bigint, AI) | 岗位信息表 | - | +| `sys_dict_type` | 9 | dict_id (bigint, AI) | 字典类型表 | **唯一索引**: dict_type | +| `sys_dict_data` | 12 | dict_code (bigint, AI) | 字典数据表 | 关联 dict_type | +| `sys_config` | 9 | config_id (int, AI) | 参数配置表 | - | +| `sys_notice` | 9 | notice_id (int, AI) | 通知公告表 | content 使用 longblob | +| `sys_oper_log` | 15 | oper_id (bigint, AI) | 操作日志记录 | 大表,高频写入 | +| `sys_logininfor` | 8 | info_id (bigint, AI) | 系统访问记录 | 登录日志 | +| `sys_job` | 12 | (job_id, job_name, job_group) 联合主键 | 定时任务调度表 | - | +| `sys_job_log` | 7 | job_log_id (bigint, AI) | 定时任务调度日志 | - | +| `sys_user_role` | 2 | (user_id, role_id) 联合主键 | 用户-角色关联表 | 多对多中间表 | +| `sys_role_menu` | 2 | (role_id, menu_id) 联合主键 | 角色-菜单关联表 | 多对多中间表 | +| `sys_role_dept` | 2 | (role_id, dept_id) 联合主键 | 角色-部门关联表 | 多对多中间表 | +| `sys_user_post` | 2 | (user_id, post_id) 联合主键 | 用户-岗位关联表 | 多对多中间表 | +| `gen_table` | 17 | table_id (bigint, AI) | 代码生成业务表 | - | +| `gen_table_column` | 21 | column_id (bigint, AI) | 代码生成业务表字段 | - | +| `QRTZ_JOB_DETAILS` | 10 | (sched_name, job_name, job_group) | Quartz 任务详情 | 外键关联 | +| `QRTZ_TRIGGERS` | 14 | (sched_name, trigger_name, trigger_group) | Quartz 触发器 | FK → QRTZ_JOB_DETAILS | +| `QRTZ_SIMPLE_TRIGGERS` | 6 | (sched_name, trigger_name, trigger_group) | Quartz 简单触发器 | FK → QRTZ_TRIGGERS | +| `QRTZ_CRON_TRIGGERS` | 5 | (sched_name, trigger_name, trigger_group) | Quartz Cron 触发器 | FK → QRTZ_TRIGGERS | +| `QRTZ_BLOB_TRIGGERS` | 4 | (sched_name, trigger_name, trigger_group) | Quartz Blob 触发器 | FK → QRTZ_TRIGGERS | +| `QRTZ_CALENDARS` | 3 | (sched_name, calendar_name) | Quartz 日历 | - | +| `QRTZ_FIRED_TRIGGERS` | 11 | (sched_name, entry_id) | Quartz 已触发触发器 | - | +| `QRTZ_PAUSED_TRIGGER_GRPS` | 2 | (sched_name, trigger_group) | Quartz 暂停触发器组 | - | +| `QRTZ_SCHEDULER_STATE` | 5 | (sched_name, instance_name) | Quartz 调度器状态 | - | +| `QRTZ_LOCKS` | 2 | (sched_name, lock_name) | Quartz 锁表 | - | +| `QRTZ_SIMPROP_TRIGGERS` | 8 | (sched_name, trigger_name, trigger_group) | Quartz 简单属性触发器 | FK → QRTZ_TRIGGERS | + +### 1.2 股票业务表(nstocks 数据库) + +| 表名 | 字段数 | 主键 | 用途 | 备注 | +|------|--------|------|------|------| +| `stocks` | 15 | id (double, AI) | 全部 A 股每日交易数据 | 核心行情表,AUTO_INCREMENT=82694 | +| `stocks_tmp` | 7 | id (double, AI) | 全部 A 股每日交易数据辅助表 | 临时/辅助计算表 | +| `stock_index` | 23 | id (double, AI) | 指数交易行情 | AUTO_INCREMENT=82694 | +| `stock_financial` | 9 | id (double, AI) | A 股财务数据 | AUTO_INCREMENT=137032 | +| `stock_basis` | 15 | id (double, AI) | A 股基础信息 | 大文件(521KB),含上市日期等 | +| `stocks_in_trend` | 5 | id (double, AI) | 动量个股 | 关联 trends 表 | +| `stocks_limit_up` | 3 | id (double, AI) | 每日涨停板 | 仅 code + trade_day | +| `trends` | 9 | id (double, AI) | 动量结果(板块级别) | 含板块排名 | +| `industries` | 5 | id (double, AI) | 东财行业 2 级每日数据 | - | +| `industries_basis` | 3 | id (double, AI) | 东财行业 2 级基础信息 | 行业字典表 | +| `trade_dates` | 3 | **无主键** | 可交易日期日历 | 包含节假日信息 | + +### 1.3 账户/记账业务表(ry 数据库) + +| 表名 | 字段数 | 主键 | 用途 | 备注 | +|------|--------|------|------|------| +| `account` | 9 | id (double, AI) | 账户每日统计 | 净资产、总资产、盈亏 | +| `account_book` | 14 | id (double, AI) | 操作记录表(交易记) | 对应 Operations 实体 | +| `operations` | 16 | id (double, AI) | 操作表(含账户转入转出) | 含 deal_logic 操作逻辑 | +| `statistics` | 10 | id (double, AI) | 交易统计表单笔操作 | 清仓时统计 | +| `statistics_remain` | 10 | id (double, AI) | 统计表当日持仓 | 包含当日清仓 | +| `statistics_total` | 16 | id (double, AI) | 统计表清仓后汇总 | 总盈亏、总手续费等 | + +### 1.4 分类统计 + +| 分类 | 表数量 | 占比 | +|------|--------|------| +| 系统管理表 | 21 | 42% | +| Quartz 定时任务表 | 9 | 18% | +| 股票业务表 | 11 | 22% | +| 账户/记账业务表 | 6 | 12% | +| 代码生成表 | 2 | 4% | +| **合计** | **~49** | **100%** | + +--- + +## 2. 表关系 + +### 2.1 系统管理模块关系图 + +``` +┌─────────────┐ ┌───────────────┐ ┌─────────────┐ +│ sys_dept │◄─────│ sys_user │─────►│ sys_post │ +│ (部门树) │ │ (用户信息) │ │ (岗位) │ +└──────┬──────┘ └──────┬────────┘ └──────┬──────┘ + │ │ │ + │ ┌─────┴─────┐ ┌─────┴──────┐ + │ │sys_user_ │ │sys_user_ │ + │ │ role │ │ post │ + │ │(N-M 中间) │ │(N-M 中间) │ + │ └─────┬─────┘ └────────────┘ + │ │ + │ ┌─────┴─────┐ ┌─────────────┐ + └─────────────►│ sys_role │◄─────│ sys_role_ │ + │ (角色) │ │ dept │ + └─────┬─────┘ │(N-M 中间) │ + │ └─────────────┘ + ┌─────┴─────┐ + │sys_role_ │ ┌─────────────┐ + │ menu │◄─────│ sys_menu │ + │(N-M 中间) │ │ (菜单树) │ + └───────────┘ └─────────────┘ +``` + +### 2.2 股票业务模块关系图 + +``` +┌──────────────────┐ ┌───────────────┐ ┌──────────────────┐ +│ stock_basis │ │ stocks │ │ stock_financial │ +│ (股票基础信息) │ │ (每日行情) │ │ (财务数据) │ +└────────┬─────────┘ └───────┬───────┘ └──────────────────┘ + │ │ + │ code(逻辑关联) │ code + trade_day + │ │ + ▼ ▼ +┌──────────────────┐ ┌───────────────┐ ┌──────────────────┐ +│ industries_ │ │ stock_index │ │ stocks_in_trend │ +│ basis │ │ (指数行情) │ │ (动量个股) │ +│ (行业字典) │ └───────────────┘ └────────┬─────────┘ +└────────┬─────────┘ │ + │ code + trade_day + │ │ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ industries │ │ trends │ +│ (行业每日数据) │◄──── blemind2 关联 ──────│ (板块动量排名) │ +└──────────────────┘ └──────────────────┘ + +┌──────────────────┐ ┌───────────────┐ +│ trade_dates │ │stocks_limit_up│ +│ (交易日历) │ │ (涨停板) │ +└──────────────────┘ └───────────────┘ +``` + +### 2.3 账户/记账模块关系图 + +``` +┌─────────────┐ ┌───────────────┐ ┌─────────────┐ +│ account │ │ operations │◄───►│account_book │ +│ (账户统计) │ │ (操作记录) │ │ (交易记录) │ +└─────────────┘ └───────┬───────┘ └─────────────┘ + user_id │ pre_id + │ + ┌─────────────┼─────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌───────────┐ ┌─────────────┐ + │statistics │ │statistics │ │statistics_ │ + │ (单笔统计)│ │ _remain │ │ total │ + │ │ │(当日持仓) │ │(清仓汇总) │ + └──────────┘ └───────────┘ └─────────────┘ + user_id user_id user_id +``` + +### 2.4 外键约束分析 + +| 表名 | 外键字段 | 引用表 | 引用字段 | 约束类型 | +|------|----------|--------|----------|----------| +| QRTZ_TRIGGERS | (sched_name, job_name, job_group) | QRTZ_JOB_DETAILS | 主键 | 物理外键 | +| QRTZ_SIMPLE_TRIGGERS | (sched_name, trigger_name, trigger_group) | QRTZ_TRIGGERS | 主键 | 物理外键 | +| QRTZ_CRON_TRIGGERS | (sched_name, trigger_name, trigger_group) | QRTZ_TRIGGERS | 主键 | 物理外键 | +| QRTZ_BLOB_TRIGGERS | (sched_name, trigger_name, trigger_group) | QRTZ_TRIGGERS | 主键 | 物理外键 | +| QRTZ_SIMPROP_TRIGGERS | (sched_name, trigger_name, trigger_group) | QRTZ_TRIGGERS | 主键 | 物理外键 | + +**注意**:系统管理表和业务表之间 **没有物理外键约束**,全部采用逻辑关联(通过代码层 JOIN 实现)。 + +### 2.5 多对多关系表 + +| 中间表 | 关联实体 1 | 关联实体 2 | 用途 | +|--------|-----------|-----------|------| +| `sys_user_role` | sys_user | sys_role | 用户角色分配 | +| `sys_role_menu` | sys_role | sys_menu | 角色菜单权限 | +| `sys_role_dept` | sys_role | sys_dept | 角色数据权限范围 | +| `sys_user_post` | sys_user | sys_post | 用户岗位分配 | + +--- + +## 3. 索引分析 + +### 3.1 主键索引清单 + +| 表名 | 主键类型 | 主键字段 | 自增 | +|------|----------|----------|------| +| sys_user | 单列 | user_id (bigint) | ✅ | +| sys_dept | 单列 | dept_id (bigint) | ✅ | +| sys_role | 单列 | role_id (bigint) | ✅ | +| sys_menu | 单列 | menu_id (bigint) | ✅ | +| sys_post | 单列 | post_id (bigint) | ✅ | +| sys_dict_type | 单列 | dict_id (bigint) | ✅ | +| sys_dict_data | 单列 | dict_code (bigint) | ✅ | +| sys_config | 单列 | config_id (int) | ✅ | +| sys_notice | 单列 | notice_id (int) | ✅ | +| sys_oper_log | 单列 | oper_id (bigint) | ✅ | +| sys_logininfor | 单列 | info_id (bigint) | ✅ | +| sys_job | **联合** | (job_id, job_name, job_group) | job_id 自增 | +| sys_job_log | 单列 | job_log_id (bigint) | ✅ | +| sys_user_role | **联合** | (user_id, role_id) | ❌ | +| sys_role_menu | **联合** | (role_id, menu_id) | ❌ | +| sys_role_dept | **联合** | (role_id, dept_id) | ❌ | +| sys_user_post | **联合** | (user_id, post_id) | ❌ | +| stocks | 单列 | id (double) | ✅ | +| stock_index | 单列 | id (double) | ✅ | +| trade_dates | **无主键** | - | ❌ | + +### 3.2 唯一索引 + +| 表名 | 索引字段 | 说明 | +|------|----------|------| +| sys_dict_type | dict_type | 字典类型编码唯一 | +| sys_user | 无唯一索引 | 用户名/手机/邮箱仅有应用层校验 | + +### 3.3 普通索引 / 复合索引 + +**大部分业务表缺少索引**,仅有主键索引。以下表没有除主键外的任何索引: + +- `stocks` — 15 字段,仅有主键 +- `stock_index` — 23 字段,仅有主键 +- `stock_financial` — 9 字段,仅有主键 +- `operations` — 16 字段,仅有主键 +- `statistics` — 10 字段,仅有主键 +- `statistics_remain` — 10 字段,仅有主键 +- `statistics_total` — 16 字段,仅有主键 +- `trade_dates` — 无主键、无索引 + +### 3.4 索引使用情况评估 + +| 表名 | 索引覆盖度 | 评价 | +|------|-----------|------| +| sys_dict_type | ★★★☆☆ | 有唯一索引,但缺少 status 索引 | +| sys_user | ★★☆☆☆ | 缺少 userName、phone、email 索引 | +| sys_oper_log | ★☆☆☆☆ | 缺少 oper_time、oper_name 索引 | +| stocks | ★☆☆☆☆ | 缺少 code、trade_day 索引 | +| trade_dates | ☆☆☆☆☆ | 无主键无索引 | + +--- + +## 4. 查询模式 + +### 4.1 常用查询模式 + +#### 系统管理模块 + +| 查询场景 | 涉及表 | 查询方式 | 典型条件 | +|----------|--------|----------|----------| +| 用户登录 | sys_user | 等值查询 | user_name = ? | +| 用户列表 | sys_user + sys_dept | LEFT JOIN + 模糊查询 | del_flag='0', LIKE 姓名/手机 | +| 用户详情 | sys_user + sys_dept + sys_user_role + sys_role | 多表 LEFT JOIN | user_id = ? | +| 角色已分配用户 | sys_user + sys_dept + sys_user_role + sys_role | 多表 JOIN + DISTINCT | role_id = ? | +| 角色未分配用户 | sys_user + sys_dept + sys_user_role + sys_role | NOT IN 子查询 | role_id != ? | +| 部门树查询 | sys_dept | 递归/ancestors LIKE | parent_id = ? | +| 菜单树查询 | sys_menu | 递归/parent_id | parent_id = ? | +| 操作日志 | sys_oper_log | 分页 + 时间范围 | oper_time BETWEEN | +| 登录日志 | sys_logininfor | 分页 + 时间范围 | login_time BETWEEN | +| 字典查询 | sys_dict_type + sys_dict_data | 等值关联 | dict_type = ? | + +#### 股票业务模块 + +| 查询场景 | 涉及表 | 查询方式 | 典型条件 | +|----------|--------|----------|----------| +| 股票行情查询 | stocks | 等值 + 范围 | code = ?, trade_day = ? | +| 区间涨跌幅查询 | stocks | 范围查询 | differrange10/20/60 BETWEEN | +| 动量个股查询 | stocks_in_trend + trends | code 关联 | trade_day = ?, type = ? | +| 涨停板查询 | stocks_limit_up | 等值 | trade_day = ? | +| 财务数据查询 | stock_financial | 等值 + 排序 | code = ?, period = ? | +| 指数行情查询 | stock_index | 等值 + 范围 | code = ?, trade_day = ? | + +#### 账户记账模块 + +| 查询场景 | 涉及表 | 查询方式 | 典型条件 | +|----------|--------|----------|----------| +| 账户统计 | account | 等值查询 | trade_day = ?, user_id = ? | +| 操作记录 | operations | 等值 + 模糊 | code = ?, name LIKE, trade_day = ? | +| 当日持仓统计 | statistics_remain | 等值 | code = ?, trade_day = ?, user_id = ? | +| 清仓汇总统计 | statistics_total | 等值 | code = ?, user_id = ? | +| 单笔统计 | statistics | 等值 | code = ?, operations_id = ? | + +### 4.2 复杂关联查询 + +#### 查询 1:用户详情(4 表 JOIN) + +```sql +-- SysUserMapper.xml: selectUserVo +SELECT u.*, d.*, r.* +FROM sys_user u +LEFT JOIN sys_dept d ON u.dept_id = d.dept_id +LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id +LEFT JOIN sys_role r ON r.role_id = ur.role_id +WHERE u.user_name = ? +``` + +**分析**:4 表 LEFT JOIN,sys_user_role 和 sys_role 的 JOIN 会产生笛卡尔积膨胀(一个用户多个角色时返回多行),由 MyBatis 的 `` 标签在应用层聚合。 + +#### 查询 2:部门树递归查询 + +```sql +-- SysUserMapper.xml: deptId 条件 +SELECT t.dept_id FROM sys_dept t +WHERE find_in_set(#{deptId}, ancestors) +``` + +**分析**:使用 `find_in_set` 函数进行字符串匹配,无法使用索引,全表扫描。 + +#### 查询 3:数据范围过滤 + +```sql +-- SysUserMapper.xml: dataScope +${params.dataScope} -- 动态 SQL 拼接 +``` + +**分析**:使用 `${}` 直接拼接 SQL,存在 SQL 注入风险(虽然框架层有控制)。 + +### 4.3 统计查询 + +| 统计场景 | 涉及表 | 说明 | +|----------|--------|------| +| 账户每日盈亏 | account | 按 trade_day 聚合 assets_diff | +| 个股总盈亏 | statistics_total | 按 code + user_id 汇总 total_profit | +| 持仓盈亏 | statistics_remain | 按 trade_day 汇总 total_profit | +| 板块排名 | trends | 按 sort 排序 | + +### 4.4 性能瓶颈识别 + +| 瓶颈 | 位置 | 严重程度 | 说明 | +|------|------|----------|------| +| `find_in_set` 查询 | sys_dept.ancestors | 🔴 高 | 全表扫描,无法利用索引 | +| 日期格式化查询 | sys_user.create_time | 🟡 中 | `date_format(u.create_time,'%y%m%d')` 导致索引失效 | +| LIKE 前缀通配符 | 多处 name/phone | 🟡 中 | `LIKE '%keyword%'` 无法使用索引 | +| 大表无索引 | stocks, stock_index | 🔴 高 | 8 万+ 记录表仅主键索引 | +| NOT IN 子查询 | 未分配用户查询 | 🟡 中 | 可能被 NOT EXISTS 优化 | +| 动态 SQL 拼接 | dataScope | 🟡 中 | 安全风险 + 查询计划不可预测 | + +--- + +## 5. 存在的问题 + +### 5.1 问题清单 + +| # | 问题 | 涉及表 | 严重程度 | 影响范围 | +|---|------|--------|----------|----------| +| 1 | **主键使用 double 类型** | 所有业务表(stocks, account, operations, statistics 等) | 🔴 严重 | double 是浮点类型,用于主键可能导致精度问题和性能下降 | +| 2 | **trade_dates 表无主键** | trade_dates | 🔴 严重 | 无主键表无法保证数据唯一性,复制和备份可能出错 | +| 3 | **业务表缺少必要索引** | stocks, stock_index, stock_financial, operations 等 | 🔴 严重 | 高频查询字段(code, trade_day)无索引,导致全表扫描 | +| 4 | **statistics 表存在字段名拼写错误** | statistics | 🟡 中等 | 同时存在 `operations_id`(varchar) 和 `operateion_id`(double) 两个含义相近字段 | +| 5 | **无逻辑外键导致数据一致性风险** | sys_user_role, sys_role_menu, statistics 等 | 🟡 中等 | 删除用户/角色时,关联表数据可能残留 | +| 6 | **find_in_set 查询无法利用索引** | sys_dept | 🟡 中等 | 部门树查询性能随数据量线性下降 | +| 7 | **date_format 函数导致索引失效** | sys_user, sys_oper_log 等 | 🟡 中等 | 时间范围查询使用 `date_format()` 包装列 | +| 8 | **缺少审计字段** | 大部分业务表 | 🟢 低 | 业务表缺少 create_by, create_time, update_by, update_time | +| 9 | **缺少软删除机制** | 业务表(stocks, operations 等) | 🟢 低 | 数据删除后无法恢复,不符合审计要求 | +| 10 | **大字段 TEXT/BLOB 无分离** | sys_notice, operations.deal_logic, statistics.bz | 🟢 低 | 大文本字段与主表混合存储,影响查询性能 | + +### 5.2 优化建议 + +#### P0(必须修复) + +1. **主键类型修正**:将所有业务表的 `id double` 改为 `id bigint(20) NOT NULL AUTO_INCREMENT` + ```sql + ALTER TABLE stocks MODIFY COLUMN id bigint(20) NOT NULL AUTO_INCREMENT; + ``` + +2. **添加核心查询索引**: + ```sql + -- stocks 表 + ALTER TABLE stocks ADD INDEX idx_code_trade (code, trade_day); + ALTER TABLE stocks ADD INDEX idx_trade_day (trade_day); + + -- stock_index 表 + ALTER TABLE stock_index ADD INDEX idx_code_trade (code, trade_day); + + -- operations 表 + ALTER TABLE operations ADD INDEX idx_code_trade (code, trade_day); + ALTER TABLE operations ADD INDEX idx_user_id (user_id); + + -- statistics_remain 表 + ALTER TABLE statistics_remain ADD INDEX idx_code_trade (code, trade_day); + ALTER TABLE statistics_remain ADD INDEX idx_user_id (user_id); + ``` + +3. **trade_dates 表添加主键**: + ```sql + ALTER TABLE trade_dates ADD PRIMARY KEY (date); + ``` + +#### P1(建议修复) + +4. **修复 statistics 表字段**:合并 `operations_id` 和 `operateion_id` 为统一的外键字段 + +5. **时间查询优化**:将 `date_format(u.create_time,'%y%m%d') >= ?` 改为范围查询: + ```sql + AND u.create_time >= #{params.beginTime} + AND u.create_time < DATE_ADD(#{params.endTime}, INTERVAL 1 DAY) + ``` + +6. **部门树查询优化**:考虑使用闭包表(Closure Table)或物化路径的整数数组类型替代 `find_in_set` + +#### P2(可选改进) + +7. **添加审计字段**:为所有业务表添加 create_by, create_time, update_by, update_time + +8. **添加软删除**:为业务表添加 del_flag 字段 + +9. **大字段分离**:将 operations.deal_logic、statistics.bz 等 TEXT 字段移至扩展表 + +--- + +## 附录:数据库 ER 概览 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ RuoYi-Vue 数据库概览 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─ 系统管理 (ry) ──────────────────────────────────────────┐ │ +│ │ sys_user ←→ sys_dept (树) │ │ +│ │ sys_user ←N:M→ sys_role ←N:M→ sys_menu (树) │ │ +│ │ sys_user ←N:M→ sys_post │ │ +│ │ sys_role ←N:M→ sys_dept │ │ +│ │ sys_dict_type ←1:N→ sys_dict_data │ │ +│ │ sys_oper_log / sys_logininfor (日志) │ │ +│ │ sys_job → sys_job_log (定时任务) │ │ +│ │ gen_table ←1:N→ gen_table_column (代码生成) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─ 股票业务 (nstocks) ─────────────────────────────────────┐ │ +│ │ stock_basis ←(code)→ stocks ←(code)→ stock_financial │ │ +│ │ stocks ←(code)→ stocks_in_trend ←(code)→ trends │ │ +│ │ stock_index (独立) │ │ +│ │ industries_basis ←(name)→ industries │ │ +│ │ stocks_limit_up (独立) │ │ +│ │ trade_dates (日历) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─ 账户记账 (ry) ──────────────────────────────────────────┐ │ +│ │ account (每日统计) │ │ +│ │ operations ←(pre_id)→ operations (自引用) │ │ +│ │ operations ←→ statistics (单笔统计) │ │ +│ │ operations ←→ statistics_remain (当日持仓) │ │ +│ │ operations ←→ statistics_total (清仓汇总) │ │ +│ │ account_book (操作快照) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +*文档结束* diff --git a/docs/architecture/frontend-architecture.md b/docs/architecture/frontend-architecture.md new file mode 100644 index 0000000..9866ce7 --- /dev/null +++ b/docs/architecture/frontend-architecture.md @@ -0,0 +1,415 @@ +# RuoYi-Vue 前端架构分析 + +> **项目版本**: 3.8.0 | **技术栈**: Vue 2.6.12 + Element UI 2.15.6 + Vuex 3.6 + Vue Router 3.4.9 +> **构建工具**: Vue CLI 4.4.6 | **描述**: 摸金分析系统 +> **分析日期**: 2026-06-28 + +--- + +## 1. 组件结构分析 + +### 1.1 组件树图(ASCII) + +``` +src/ +├── components/ # 全局通用组件(22 个组件组) +│ ├── Breadcrumb/ # ── 面包屑导航 +│ ├── Crontab/ # ── Cron 表达式生成器(9 个子组件) +│ │ ├── day.vue / hour.vue / min.vue # 时间维度选择 +│ │ ├── month.vue / week.vue / year.vue +│ │ ├── second.vue / result.vue / index.vue +│ ├── DictData/ # ── 字典数据管理器(JS 模块) +│ ├── DictTag/ # ── 字典标签渲染 +│ ├── Editor/ # ── 富文本编辑器(Quill) +│ ├── FileUpload/ # ── 文件上传 +│ ├── Hamburger/ # ── 汉堡菜单按钮 +│ ├── HeaderSearch/ # ── 头部搜索(Fuse.js) +│ ├── IconSelect/ # ── SVG 图标选择器 +│ ├── ImagePreview/ # ── 图片预览 +│ ├── ImageUpload/ # ── 图片上传 +│ ├── Kdialog/ # ── K 线对话框(业务组件) +│ ├── NegativeDialog/ # ── 涨跌停对话框(业务组件) +│ ├── Pagination/ # ── 分页器 +│ ├── PanThumb/ # ── 全景缩略图 +│ ├── ParentView/ # ── 父级视图占位 +│ ├── RightPanel/ # ── 右侧面板 +│ ├── RightToolbar/ # ── 右侧工具栏 +│ ├── RuoYi/ # ── 外部链接(Doc / Git) +│ ├── Screenfull/ # ── 全屏切换 +│ ├── SizeSelect/ # ── 组件尺寸选择 +│ ├── SvgIcon/ # ── SVG 图标封装 +│ ├── ThemePicker/ # ── 主题选择器 +│ ├── TopNav/ # ── 顶部导航 +│ ├── TrendStocksDialog/ # ── 板块趋势对话框(业务) +│ └── iFrame/ # ── 内嵌 iframe +│ +├── layout/ # 布局框架 +│ ├── index.vue # ── 主布局容器 +│ ├── components/ +│ │ ├── AppMain.vue # ── 主内容区(含 keep-alive) +│ │ ├── Navbar.vue # ── 顶部导航栏 +│ │ ├── Sidebar/ # ── 侧边栏 +│ │ │ ├── index.vue / SidebarItem.vue / Item.vue / Link.vue / Logo.vue +│ │ ├── TagsView/ # ── 标签页视图 +│ │ │ ├── index.vue / ScrollPane.vue +│ │ ├── Settings/ # ── 系统设置面板 +│ │ └── InnerLink/ # ── 内部链接 +│ └── mixin/ResizeHandler.js # ── 响应式 Mixin +│ +├── views/ # 页面视图(按业务模块) +│ ├── dashboard/ # ── 仪表盘(5 个图表组件) +│ ├── system/ # ── 系统管理(8 个子模块) +│ ├── booksystem/ # ── 账本系统(6 个子模块) +│ ├── stocksystem/ # ── 股票系统(8 个子模块) +│ ├── monitor/ # ── 系统监控(7 个子模块) +│ ├── tool/ # ── 系统工具(代码生成等) +│ ├── error/ # ── 错误页面 +│ ├── login.vue / register.vue # ── 认证页面 +│ └── index.vue # ── 首页 +``` + +### 1.2 分类统计 + +| 分类 | 组件/模块数量 | 说明 | +|------|-------------|------| +| **通用 UI 组件** | 10 | Breadcrumb, Pagination, SvgIcon, Hamburger, Screenfull, SizeSelect, ThemePicker, PanThumb, ParentView, iFrame | +| **业务组件** | 4 | Kdialog, NegativeDialog, TrendStocksDialog, RightToolbar | +| **功能组件** | 7 | Crontab(9), DictTag, DictData, Editor, FileUpload, ImageUpload, ImagePreview, IconSelect, HeaderSearch | +| **布局组件** | 8 | Layout, AppMain, Navbar, Sidebar(5), TagsView(2), Settings, InnerLink | +| **视图模块** | 6 大模块 | dashboard, system, booksystem, stocksystem, monitor, tool | +| **视图页面总数** | ~40 个 | 包含子页面和嵌套路由页面 | + +### 1.3 组件复用程度分析 + +**高复用组件**(被全局注册或在多个视图中引用): +- `Pagination` — 几乎所有列表页面 +- `DictTag` — 所有涉及字典展示的页面 +- `RightToolbar` — 所有 CRUD 列表页 +- `Editor` / `FileUpload` / `ImageUpload` — 表单页面 +- `Breadcrumb` — 布局层全局使用 +- `SvgIcon` — 全局图标渲染基础组件 + +**中复用组件**(特定业务场景): +- `Crontab` — 仅定时任务模块 +- `Kdialog` / `TrendStocksDialog` / `NegativeDialog` — 股票业务模块 +- `HeaderSearch` — 仅 Navbar 使用 + +**低复用组件**(单一职责或一次性使用): +- `PanThumb` — 仅用户个人资料页 +- `RightPanel` — 仅布局设置入口 +- `RuoYi/Doc` / `RuoYi/Git` — 外部链接 + +### 1.4 组件职责分析 + +| 组件 | 职责单一性 | 评价 | +|------|----------|------| +| Crontab | 一般 | 9 个子组件拆分合理,但耦合度较高 | +| Kdialog / NegativeDialog | 低 | 业务逻辑与 UI 混合,体积过大 | +| Dashboard 图表组件 | 好 | 每个图表独立文件,职责清晰 | +| Layout 子组件 | 好 | Sidebar、Navbar、TagsView 分离清晰 | + +--- + +## 2. 路由与权限控制 + +### 2.1 路由结构图 + +``` +路由系统 +├── 静态路由 (constantRoutes) — 始终可访问 +│ ├── /redirect/:path — 重定向页 +│ ├── /login — 登录页 +│ ├── /register — 注册页 +│ ├── /404 — 404 错误页 +│ ├── /401 — 401 错误页 +│ ├── / (首页) — Dashboard +│ └── /user/profile — 个人中心 +│ +├── 动态路由 (dynamicRoutes) — 按权限加载 +│ ├── /system/user-auth/role/:userId — 分配角色 [system:user:edit] +│ ├── /system/role-auth/user/:roleId — 分配用户 [system:role:edit] +│ ├── /system/dict-data/index/:dictId — 字典数据 [system:dict:list] +│ ├── /monitor/job-log — 调度日志 [monitor:job:list] +│ └── /tool/gen-edit — 修改生成配置 [tool:gen:edit] +│ +└── 服务端路由 — 登录后从后端获取 + ├── filterAsyncRouter() ── 转换为组件对象 + ├── filterDynamicRoutes() ── 权限过滤 + └── router.addRoutes() ── 动态注册 +``` + +### 2.2 权限加载流程 + +``` +用户登录 + │ + ├─ 1. 输入用户名/密码/验证码 + ├─ 2. 调用 POST /login → 获取 token → 存入 Cookie (Admin-Token) + ├─ 3. 路由守卫 beforeEach 拦截 + │ │ + │ ├─ 有 token ? ──NO──→ 跳转 /login + │ │ │ + │ │ YES + │ │ │ + │ │ ├─ roles.length === 0 ? ──NO──→ next() + │ │ │ │ + │ │ │ YES + │ │ │ │ + │ │ │ ├─ GetInfo() → 获取用户信息(roles + permissions) + │ │ │ │ + │ │ │ └─ GenerateRoutes() → 请求 /getRouters + │ │ │ │ + │ │ │ ├─ filterAsyncRouter() → 字符串转组件 + │ │ │ ├─ filterDynamicRoutes() → 权限过滤 + │ │ │ └─ router.addRoutes() → 注册路由 + │ │ │ + │ │ └─ next({ ...to, replace: true }) → 重新导航 + │ │ + │ └─ 无 token → 白名单 ? ──YES──→ next() + │ │ + │ NO + │ │ + │ 跳转 /login?redirect=xxx + │ + └─ 4. 页面渲染 → 动态菜单展示 +``` + +### 2.3 权限控制机制 + +项目采用 **三层权限控制**: + +| 层级 | 实现方式 | 代码位置 | +|------|---------|---------| +| **路由级** | 路由守卫 + 服务端返回菜单 | `src/permission.js` + `src/store/modules/permission.js` | +| **页面级** | `v-hasPermi` / `v-hasRole` 指令 | `src/directive/permission/` | +| **按钮级** | `$auth.hasPermi()` / `$auth.hasRole()` | `src/plugins/auth.js` | + +**权限标识格式**:`模块:资源:操作`(如 `system:user:add`) +**超级权限**:`*:*:*`(拥有所有权限) +**超级角色**:`admin`(拥有所有角色权限) + +### 2.4 路由懒加载 + +```javascript +// 开发环境:使用 require 实现 +if (process.env.NODE_ENV === 'development') { + return (resolve) => require([`@/views/${view}`], resolve) +} +// 生产环境:使用 import 实现 +else { + return () => import(`@/views/${view}`) +} +``` + +--- + +## 3. 状态管理分析 + +### 3.1 Vuex 模块图 + +``` +Vuex Store +├── getters.js # 统一访问入口 +│ ├── sidebar, size, device → state.app +│ ├── visitedViews, cachedViews → state.tagsView +│ ├── token, avatar, name, roles → state.user +│ └── permission_routes, topbarRouters, sidebarRouters → state.permission +│ +├── modules/app.js # 应用级状态 +│ ├── sidebar { opened, withoutAnimation } +│ ├── device ('desktop' | 'mobile') +│ └── size ('medium' | 'small' | 'mini') +│ └── 持久化:Cookies (sidebarStatus, size) +│ +├── modules/user.js # 用户状态 +│ ├── token, name, avatar +│ ├── roles: string[] +│ ├── permissions: string[] +│ └── Actions: Login, GetInfo, LogOut, FedLogOut +│ └── 持久化:Cookies (Admin-Token) +│ +├── modules/permission.js # 权限/路由状态 +│ ├── routes, addRoutes — 完整路由表 / 动态路由 +│ ├── defaultRoutes, topbarRouters, sidebarRouters +│ └── Actions: GenerateRoutes +│ +├── modules/tagsView.js # 标签页状态 +│ ├── visitedViews: [] — 已访问的标签页 +│ ├── cachedViews: [] — 被 keep-alive 缓存的页面 +│ └── Actions: addView, delView, delOthersViews, delAllViews, +│ delRightTags, delLeftTags +│ +└── modules/settings.js # 系统设置状态 + ├── theme, sideTheme, showSettings + ├── topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle + └── 持久化:localStorage ('layout-setting') +``` + +### 3.2 数据流向 + +``` + ┌──────────────┐ + │ Vue 组件 │ + └──────┬───────┘ + │ dispatch + ▼ + ┌──────────────┐ + │ Actions │ ──→ 异步操作(API 调用) + └──────┬───────┘ + │ commit + ▼ + ┌──────────────┐ + │ Mutations │ ──→ 同步修改 state + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ State │ ──→ 响应式数据 + └──────┬───────┘ + │ + ┌──────┴───────┐ + │ Getters │ ──→ 计算属性 + └──────┬───────┘ + │ + ┌──────┴───────┐ + │ Vue 组件 │ ←── 重新渲染 + └──────────────┘ +``` + +### 3.3 状态持久化 + +| 状态 | 存储方式 | 键名 | 说明 | +|------|---------|------|------| +| 用户 Token | Cookie | `Admin-Token` | 会话认证 | +| 侧边栏状态 | Cookie | `sidebarStatus` | 展开/折叠 | +| 组件尺寸 | Cookie | `size` | Element UI 默认尺寸 | +| 布局设置 | localStorage | `layout-setting` | JSON 对象(主题、顶栏、标签页等) | + +> **注意**:Vuex 本身无持久化插件,数据分散在 Cookie 和 localStorage 中手动读写。 + +--- + +## 4. API 调用层 + +### 4.1 Axios 封装架构 + +``` +src/api/ # API 模块目录 +├── booksystem/ # ── 账本系统 API(6 个文件) +├── stocksystem/ # ── 股票系统 API(9 个文件) +├── system/ # ── 系统管理 API(7 个文件 + dict 子目录) +├── monitor/ # ── 系统监控 API(7 个文件) +├── tool/ # ── 系统工具 API +├── login.js # ── 登录/获取信息/退出 +├── menu.js # ── 获取动态路由 +└── index.js # ── 通用 API + +src/utils/request.js # Axios 实例封装 +├── baseURL: process.env.VUE_APP_BASE_API +├── timeout: 60000ms (60秒) +├── Content-Type: application/json;charset=utf-8 +└── download() 方法 # ── 文件下载封装 +``` + +### 4.2 拦截器实现 + +#### 请求拦截器(Request Interceptor) + +``` +请求发出 + │ + ├─ 1. 检查是否需要 token(config.headers.isToken === false 可跳过) + ├─ 2. 自动添加 Authorization: Bearer {token} + ├─ 3. GET 请求 params 序列化(tansParams 处理) + │ └─ 将 ?a=1&b=2 拼接到 URL,清空 config.params + └─ 4. 发送请求 +``` + +#### 响应拦截器(Response Interceptor) + +``` +收到响应 + │ + ├─ 解析业务状态码 res.data.code + │ + ├─ code === 401 → MessageBox 弹窗 → 确认后 LogOut → 跳首页 + ├─ code === 500 → Message.error(msg) → Promise.reject + ├─ code !== 200 → Notification.error({ title: msg }) → Promise.reject + ├─ code === 200 → 返回 res.data + │ + └─ HTTP 错误捕获(error) + ├─ Network Error → "后端接口连接异常" + ├─ timeout → "系统接口请求超时" + ├─ status code 4xx/5xx → "系统接口 XXX 异常" + └─ Message.error(message, duration: 5s) +``` + +### 4.3 错误处理机制 + +| 错误类型 | 处理方式 | 用户体验 | +|---------|---------|---------| +| 401 未授权 | 弹窗确认 → 退出登录 | 强提醒,需用户操作 | +| 500 服务端错误 | Message 提示 | 轻量提示,自动消失 | +| 其他业务错误 | Notification 通知 | 右上角通知 | +| 网络异常 | Message 提示(5 秒) | 长时提示 | +| 请求超时 | Message 提示(5 秒) | 长时提示 | +| 下载失败 | 解析 blob 中的 JSON 错误信息 | 自动处理登录态失效 | + +### 4.4 错误码映射 + +通过 `src/utils/errorCode.js` 实现业务码到提示文本的映射,支持: +- 预定义错误码映射 +- 默认兜底文本 +- 服务端返回的自定义 msg + +--- + +## 5. 存在的问题 + +### 5.1 问题清单 + +| # | 问题 | 影响 | 严重程度 | +|---|------|------|---------| +| 1 | **Vue 2 + Vue CLI 技术栈老化**:Vue 2 已于 2023 年底停止官方维护,Vue Router 3、Vuex 3、Vue CLI 4 均为旧版本,存在安全风险且无法使用 Composition API 等现代特性 | 长期维护成本高,无法使用新生态,第三方库兼容性逐步下降 | 🔴 高 | +| 2 | **权限逻辑重复实现**:权限校验在 4 个位置分别实现(`plugins/auth.js`、`utils/permission.js`、`directive/permission/hasPermi.js`、`directive/permission/hasRole.js`),逻辑高度重复但存在细微差异,如超级权限标识散落在各处 | 维护困难,修改权限规则需同时修改多处,易遗漏导致权限漏洞 | 🟡 中 | +| 3 | **状态持久化分散且不统一**:Token 存 Cookie、sidebar 状态存 Cookie、布局设置存 localStorage,手动读写无统一管理,无 vuex-persistedstate 等插件,刷新后部分状态可能不一致 | 状态管理混乱,调试困难,存在不同步风险 | 🟡 中 | +| 4 | **业务组件与通用组件混放**:`Kdialog`、`TrendStocksDialog`、`NegativeDialog` 等强业务耦合组件放在 `src/components/` 通用组件目录,且通过全局注册加载,增加了首屏包体积 | 首屏加载变慢,通用组件库被业务代码污染,不利于复用 | 🟡 中 | +| 5 | **视图页面高度模板化**:`system/`、`booksystem/`、`stocksystem/` 下的 CRUD 页面结构高度相似(搜索表单 + 表格 + 分页 + 弹窗),但未抽取为可配置的高阶组件或 Schema 驱动表单 | 代码冗余度高,新增 CRUD 页面工作量大,修改通用逻辑需改多个文件 | 🟡 中 | +| 6 | **生产/开发环境使用不同的懒加载方式**:开发环境用 `require()`,生产环境用 `import()`,两份逻辑增加了维护复杂度,且开发环境无法享受代码分割的优化效果 | 开发构建产物大,热更新慢,行为不一致可能导致线上问题 | 🟢 低 | +| 7 | **响应拦截器中 401 处理使用同步 location.href 跳转**:`location.href = '/index'` 是硬跳转,绕过了 Vue Router 的导航守卫和状态清理流程 | 可能导致 Vuex 状态未完全清理、标签页数据残留 | 🟢 低 | +| 8 | **缺少统一的请求重试机制**:网络波动或后端短暂不可用时,所有请求直接失败,无自动重试策略 | 用户体验下降,偶发性网络问题导致操作失败 | 🟢 低 | + +### 5.2 优化建议 + +| 优先级 | 建议 | 预期收益 | +|--------|------|---------| +| P0 | 规划升级至 Vue 3 + Vite + Pinia | 获得性能提升、Composition API、更好的类型支持 | +| P1 | 抽取统一的权限校验核心模块,消除重复代码 | 降低维护成本,消除权限逻辑不一致风险 | +| P1 | 引入 vuex-persistedstate 或自定义统一持久化层 | 状态管理规范化,减少手动读写 Cookie/LocalStorage | +| P2 | 将业务组件移至对应业务模块目录,或采用按需异步加载 | 减少首屏包体积,保持通用组件库纯净 | +| P2 | 抽取 CRUD 页面为 Schema 驱动的高阶组件 | 减少 60%+ 的 CRUD 页面代码量 | +| P3 | 统一开发/生产的组件加载方式 | 简化代码,提升开发体验 | +| P3 | 401 处理改用 router.replace() 代替 location.href | 状态清理更完整,避免残留数据 | +| P3 | 在 request.js 中添加可配置的重试机制 | 提升网络不稳定场景下的用户体验 | + +--- + +## 附录:技术栈概览 + +| 类别 | 技术 | 版本 | +|------|------|------| +| 核心框架 | Vue | 2.6.12 | +| UI 框架 | Element UI | 2.15.6 | +| 路由 | Vue Router | 3.4.9 | +| 状态管理 | Vuex | 3.6.0 | +| HTTP 客户端 | Axios | 0.24.0 | +| 构建工具 | Vue CLI | 4.4.6 | +| CSS 预处理器 | Sass | 1.32.13 | +| 图表库 | ECharts | 4.9.0 | +| 富文本 | Quill | 1.3.7 | +| 代码高亮 | highlight.js | 9.18.5 | +| 拖拽排序 | SortableJS / vuedraggable | 1.10.2 / 2.24.3 | +| SVG 图标 | svg-sprite-loader | 5.1.1 | +| 加密 | jsencrypt | 3.2.1 | +| 进度条 | NProgress | 0.2.0 | +| Cookie | js-cookie | 3.0.1 | diff --git a/docs/architecture/optimization-suggestions.md b/docs/architecture/optimization-suggestions.md new file mode 100644 index 0000000..6f685a2 --- /dev/null +++ b/docs/architecture/optimization-suggestions.md @@ -0,0 +1,500 @@ +# RuoYi-Vue 优化建议清单 + +> 文档版本:v1.0 | 生成日期:2026-06-28 +> 数据来源:前端架构分析、后端架构分析、前后端交互规范、数据库结构分析 +> 问题总数:36 条(前端 8 条、后端 8 条、交互 10 条、数据库 10 条) + +--- + +## P0 - 高优先级(立即实施) + +> 影响安全、稳定性、核心功能,建议在 1-2 周内完成。 + +### 1. fastjson 版本存在已知安全漏洞 +- **问题描述**: 项目使用 fastjson 1.2.79,该版本存在多个已知 CVE 漏洞(如 CVE-2022-25845),攻击者可利用反序列化漏洞执行远程代码。 +- **影响范围**: 全系统所有使用 JSON 序列化/反序列化的接口,包括登录、CRUD 操作、文件上传等。 +- **推荐方案**: 升级至 fastjson2(2.x 系列)或替换为 Jackson/Gson。fastjson2 API 兼容性好,迁移成本较低。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 在根 `pom.xml` 中将 fastjson 版本改为 fastjson2 依赖 + 2. 全局搜索 `com.alibaba.fastjson` 导入,替换为 `com.alibaba.fastjson2` + 3. 重点检查 `AjaxResult`、`TokenService`、`LogAspect` 中的 JSON 操作 + 4. 运行全量回归测试,确保序列化行为一致 + 5. 安全扫描验证漏洞已修复 + +### 2. 业务表主键使用 double 类型 +- **问题描述**: 所有业务表(stocks、account、operations、statistics 等)的主键使用 `double` 类型,浮点数作为主键可能导致精度丢失、索引效率低下、比较运算异常。 +- **影响范围**: 股票业务表(8 万+ 记录)、账户记账表等所有业务模块的数据查询和关联操作。 +- **推荐方案**: 将所有业务表主键改为 `bigint(20) NOT NULL AUTO_INCREMENT`。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 备份 `nstocks` 和 `ry` 数据库中涉及的业务表 + 2. 按依赖顺序执行 `ALTER TABLE` 语句修改主键类型(先子表后父表) + 3. 更新对应的 Domain 实体类,将 `id` 字段类型从 `Double` 改为 `Long` + 4. 更新 Mapper XML 中的主键引用 + 5. 验证数据完整性,运行全量测试 + +### 3. trade_dates 表无主键 +- **问题描述**: `trade_dates` 表(交易日历)没有任何主键或唯一索引,无法保证数据唯一性,数据库复制和备份可能出错。 +- **影响范围**: 所有依赖交易日历的业务查询,包括股票行情、涨跌停板等模块。 +- **推荐方案**: 将日期字段设为主键。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 检查 `trade_dates` 表结构,确认日期字段名称 + 2. 清理可能存在的重复日期记录 + 3. 执行 `ALTER TABLE trade_dates ADD PRIMARY KEY (date);` + 4. 验证查询功能正常 + +### 4. 核心业务表缺少必要索引 +- **问题描述**: `stocks`(8 万+ 记录)、`stock_index`、`stock_financial`、`operations` 等大表仅有主键索引,高频查询字段(code、trade_day、user_id)完全无索引,导致全表扫描。 +- **影响范围**: 所有股票行情查询、操作记录查询、持仓统计查询,数据量增长后性能急剧下降。 +- **推荐方案**: 为高频查询字段添加复合索引和单列索引。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 分析慢查询日志,确认高频查询模式 + 2. 为 `stocks` 表添加 `idx_code_trade(code, trade_day)` 和 `idx_trade_day(trade_day)` + 3. 为 `stock_index` 表添加 `idx_code_trade(code, trade_day)` + 4. 为 `operations` 表添加 `idx_code_trade(code, trade_day)` 和 `idx_user_id(user_id)` + 5. 为 `statistics_remain` 表添加 `idx_code_trade(code, trade_day)` 和 `idx_user_id(user_id)` + 6. 使用 `EXPLAIN` 验证索引生效 + +### 5. 排序字段存在 SQL 注入风险 +- **问题描述**: 分页排序参数直接从请求参数读取并拼接到 SQL ORDER BY 子句中,虽使用 `SqlUtil.escapeOrderBySql()` 正则过滤,但正则表达式级别的防护存在被绕过的可能。 +- **影响范围**: 所有使用分页排序的列表接口(约 50+ 个),攻击者可能通过精心构造的排序参数执行 SQL 注入。 +- **推荐方案**: 使用白名单机制替代正则过滤,仅允许传入已知的合法字段名。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 在 `TableSupport` 或 `SqlUtil` 中实现排序字段白名单校验 + 2. 为每个实体定义允许排序的字段列表 + 3. 在 `startPage()` 方法中增加白名单校验逻辑 + 4. 非法排序参数直接拒绝并记录安全日志 + 5. 使用 SQL 注入扫描工具验证修复效果 + +### 6. 响应格式不统一 +- **问题描述**: `AjaxResult`(普通 CRUD 接口)使用 `data` 字段返回数据,而 `TableDataInfo`(分页列表接口)使用 `rows` + `total` 字段,前端需要分别处理两种完全不同的响应结构。 +- **影响范围**: 前端所有 API 调用,180+ 个接口涉及两种响应格式处理,增加认知负担和维护成本。 +- **推荐方案**: 统一响应结构,推荐在 `TableDataInfo` 中也使用 `data` 字段包裹分页数据,或引入统一的 `ApiResponse` 泛型响应体。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 定义统一响应基类 `ApiResponse`,包含 `code`、`msg`、`data` + 2. 分页响应改为 `data: { list: [], total: 0 }` 结构 + 3. 前端 `request.js` 响应拦截器统一处理单一结构 + 4. 逐步迁移现有接口(可先从新接口开始) + 5. 更新前端所有列表页面的数据提取逻辑 + +### 7. ${params.dataScope} SQL 拼接注入风险 +- **问题描述**: MyBatis XML 中使用 `${params.dataScope}` 直接拼接 SQL 片段,虽然 `DataScopeAspect` 在执行前会清空参数,但若切面执行顺序被改变或绕过,将直接导致 SQL 注入。 +- **影响范围**: 所有使用 `@DataScope` 注解的 Service 方法,涉及用户、角色、部门等核心数据查询。 +- **推荐方案**: 使用参数化方案替代直接 SQL 拼接,或在切面层进行严格的 SQL 片段校验。 +- **实施优先级**: P0 +- **实施步骤**: + 1. 在 `DataScopeAspect` 中增加 SQL 片段合法性校验(仅允许特定模式) + 2. 考虑将数据权限过滤改为 MyBatis 插件方式,在 SQL 解析阶段注入 + 3. 为关键查询添加安全审计日志 + 4. 进行安全渗透测试验证 + +--- + +## P1 - 中优先级(近期实施) + +> 影响开发效率、可维护性,建议在 1-2 个月内完成。 + +### 8. 使用已废弃的 WebSecurityConfigurerAdapter +- **问题描述**: Spring Security 5.7+ 已废弃 `WebSecurityConfigurerAdapter`,项目当前继承该类进行安全配置,未来升级 Spring Boot 时将无法兼容。 +- **影响范围**: `SecurityConfig.java` 安全配置模块,影响所有认证和授权流程。 +- **推荐方案**: 改用 `SecurityFilterChain` Bean 配置方式。 +- **实施优先级**: P1 +- **实施步骤**: + 1. 移除 `WebSecurityConfigurerAdapter` 继承 + 2. 将 `configure(HttpSecurity http)` 方法改为 `@Bean SecurityFilterChain` 方法 + 3. 将 `configure(WebSecurity web)` 方法改为 `WebSecurityCustomizer` Bean + 4. 使用 `Lambda DSL` 风格编写安全规则(`.authorizeHttpRequests()` 替代 `.authorizeRequests()`) + 5. 验证登录、权限校验、匿名访问等功能正常 + +### 9. Token 解析异常被静默吞没 +- **问题描述**: `TokenService.getLoginUser()` 方法中(第 72-74 行),catch 块为空,JWT Token 解析异常被完全忽略,导致问题难以排查。 +- **影响范围**: 认证模块,所有 Token 验证失败的场景(过期、篡改、格式错误等)均无日志记录。 +- **推荐方案**: 至少记录 WARN 级别日志,包含异常信息和请求上下文。 +- **实施优先级**: P1 +- **实施步骤**: + 1. 在 `TokenService` 中注入 Logger + 2. 在 catch 块中添加 `log.warn("Token 解析失败: {}", e.getMessage())` + 3. 可选:记录请求 IP 和 User-Agent 便于安全审计 + 4. 验证日志输出正常 + +### 10. 缺少统一 DTO/VO 分层 +- **问题描述**: Domain 实体直接暴露给 Controller 层(如 `SysUser` 同时用于数据库映射和接口响应),密码等敏感字段需额外处理,字段暴露风险高。 +- **影响范围**: 全系统所有 Controller 接口,特别是用户管理、角色管理等敏感模块。 +- **推荐方案**: 引入独立的 DTO(数据传输对象)和 VO(视图对象),使用 MapStruct 或手动转换器进行映射。 +- **实施优先级**: P1 +- **实施步骤**: + 1. 在 `ruoyi-common` 中创建 `dto/` 和 `vo/` 包 + 2. 为敏感实体(SysUser、SysRole 等)创建对应的 DTO/VO + 3. Controller 层使用 DTO 接收请求、VO 返回响应 + 4. 使用 `@JsonIgnore` 或字段排除确保敏感字段不暴露 + 5. 逐步迁移现有接口 + +### 11. Vue 2 + Vue CLI 技术栈老化 +- **问题描述**: Vue 2 已于 2023 年底停止官方维护,Vue Router 3、Vuex 3、Vue CLI 4 均为旧版本,存在安全风险且无法使用 Composition API、`