# 数据流与模块交互详图 > 本文档补充系统各模块之间的数据流转细节和交互时序。 --- ## 一、资源管理全链路数据流 ### 1.1 资源 CRUD 完整数据流 ```mermaid sequenceDiagram participant U as 用户 participant V as ResourceList.vue participant A as api/resource.js participant C as ResourceController participant S as ResourceServiceImpl participant M as ResourceMapper participant DB as MySQL Note over U,DB: ── 分页查询 ── U->>V: 输入筛选条件 / 翻页 V->>A: resourcePageApi(params) A->>C: GET /resource/page?keyword=&typeId=&status=&page=&size= C->>S: page(query) S->>M: selectPage + 条件构造 M->>DB: SELECT * FROM resource WHERE ... DB-->>M: 分页结果 S->>S: 批量组装 VO
(关联类型名、服务商名、负责人名、关联项目) S-->>C: IPage C-->>A: Result.ok(data) A-->>V: 渲染表格 Note over U,DB: ── 新增资源 ── U->>V: 点击"新增",填写表单 V->>A: resourceSaveApi(dto) A->>C: POST /resource {name, typeId, providerId, ...} C->>S: save(dto) S->>S: DTO → Entity 转换 S->>S: 计算初始状态
(根据 expire_date) S->>M: insert(resource) M->>DB: INSERT INTO resource S->>S: 关联项目 + 设置分摊比例 S->>M: batch insert project_resource S->>S: 记录操作日志 S-->>C: 成功 C-->>A: Result.ok() A-->>V: 关闭弹窗,刷新列表 Note over U,DB: ── 编辑资源 ── U->>V: 点击"编辑" V->>A: resourceDetailApi(id) A->>C: GET /resource/{id} C->>S: getDetail(id) S->>M: selectById + 关联查询 M->>DB: SELECT ... S-->>C: ResourceVO C-->>A: Result.ok(data) A-->>V: 回填表单 U->>V: 修改后提交 V->>A: resourceSaveApi(dto) A->>C: POST /resource {id, name, ...} C->>S: save(dto) S->>M: updateById M->>DB: UPDATE resource SET ... S->>S: 更新项目关联 + 分摊比例 S-->>C: 成功 C-->>A: Result.ok() Note over U,DB: ── 删除资源 ── U->>V: 点击"删除",确认 V->>A: resourceDeleteApi(id) A->>C: DELETE /resource/{id} C->>S: delete(id) S->>M: deleteById (逻辑删除) M->>DB: UPDATE resource SET deleted=1 S->>M: 删除关联的 project_resource S->>S: 记录操作日志 S-->>C: 成功 C-->>A: Result.ok() ``` ### 1.2 资源导出 Excel 数据流 ```mermaid sequenceDiagram participant U as 用户 participant V as ResourceList.vue participant A as api/resource.js participant C as ResourceController participant S as ResourceServiceImpl participant EE as EasyExcel participant DB as MySQL U->>V: 点击"导出 Excel" V->>A: resourceExportApi(params) A->>C: GET /resource/export?keyword=&typeId=&status= C->>S: list(query) S->>DB: 查询全量数据(不分页) DB-->>S: List S->>S: Entity → ResourceExportVO
(状态转文字、类型名、服务商名) S-->>C: List C->>EE: EasyExcel.write(response, ResourceExportVO.class) EE->>C: 生成 .xlsx 字节流 C-->>A: Content-Type: application/octet-stream
Content-Disposition: attachment A-->>V: Blob 响应 V->>V: 创建下载链接
触发浏览器下载 V-->>U: 文件保存对话框 ``` --- ## 二、项目管理数据流 ### 2.1 项目树形查询 ```mermaid sequenceDiagram participant V as ProjectList.vue participant A as api/project.js participant C as ProjectController participant S as ProjectServiceImpl participant DB as MySQL V->>A: projectTreeApi() A->>C: GET /project/tree C->>S: tree() S->>DB: SELECT * FROM project WHERE deleted=0 DB-->>S: List S->>S: 分离顶层/子项目 S->>S: 构建树形结构:
顶层项目 → children[] loop 每个顶层项目 S->>DB: 查询关联资源数 S->>DB: 查询子项目列表 loop 每个子项目 S->>DB: 查询子项目关联资源数 end end S-->>C: List (树形) C-->>A: Result.ok(data) A-->>V: el-table tree 模式渲染 ``` ### 2.2 项目详情与资源关联 ```mermaid sequenceDiagram participant U as 用户 participant V as ProjectDetail.vue participant A as api/project.js participant RA as api/resource.js participant C as ProjectController participant S as ProjectServiceImpl participant DB as MySQL U->>V: 进入项目详情页 V->>A: projectDetailApi(id) A->>C: GET /project/{id} C->>S: getDetail(id) S->>DB: 查询项目 + 子项目 + 关联资源 DB-->>S: 聚合数据 S-->>C: ProjectVO C-->>A: Result.ok(data) A-->>V: 渲染基本信息 + 资源列表 U->>V: 点击"关联资源" V->>RA: resourcePageApi(未关联筛选) RA-->>V: 可选资源列表 U->>V: 多选资源 + 设置分摊比例 V->>A: POST /resource/relate Note over V,A: {resourceIds: [1,2,3],
projectId: 5,
allocations: [{resourceId:1, percent:50},...]} A->>C: POST /resource/relate C->>S: 批量插入 project_resource S->>DB: INSERT INTO project_resource DB-->>S: 成功 S-->>C: 成功 C-->>A: Result.ok() A-->>V: 刷新资源列表 ``` --- ## 三、费用统计数据流 ### 3.1 按项目维度统计 ```mermaid flowchart LR subgraph 查询 Q["GET /resource/cost-stat/project
?days=30"] end subgraph SQL["ResourceMapper.xml"] SQL1["SELECT
p.name AS projectName,
COUNT(DISTINCT r.id) AS resourceCount,
SUM(r.cost * pr.allocation_percent / 100) AS totalCost
FROM project p
LEFT JOIN project_resource pr ON ...
LEFT JOIN resource r ON ...
GROUP BY p.id"] end subgraph 结果 R1["CostStatVO
{projectName, resourceCount, totalCost}"] end Q --> SQL1 SQL1 --> R1 ``` ### 3.2 采购费用台账数据流 ```mermaid flowchart TD subgraph 数据来源 R["resource 表
初始采购记录"] RR["resource_renewal 表
续费记录"] end subgraph 合并逻辑["ResourceServiceImpl.mergeProcurementCosts()"] Q["ProcurementCostQuery
名称/类型/服务商/费用类型/月份"] Merge["合并初始采购 + 续费记录
UNION ALL"] Filter["按条件筛选"] Page["分页"] end subgraph 输出 VO["ProcurementCostVO
资源名、类型、服务商、项目、占比、
分摊费用、计费周期、购买时长、
类型标签(初始采购/续费)、
发生日期、到期日期"] end R --> Merge RR --> Merge Q --> Filter Merge --> Filter Filter --> Page Page --> VO ``` --- ## 四、认证授权数据流 ### 4.1 JWT Token 生命周期 ```mermaid sequenceDiagram participant C as 客户端 participant F as JwtAuthenticationFilter participant J as JwtUtil participant US as UserDetailsServiceImpl participant DB as MySQL Note over C,DB: ── Token 生成 ── C->>F: POST /auth/login F->>US: loadUserByUsername() US->>DB: SELECT * FROM sys_user WHERE username=? DB-->>US: SysUser US-->>F: LoginUser (UserDetails) F->>J: generateToken(userId, username) J->>J: HS256 签名
payload: {userId, username, iat, exp} J-->>F: JWT String F-->>C: {token, userId, username, role} Note over C,DB: ── Token 校验 (每次请求) ── C->>F: GET /resource/page
Authorization: Bearer eyJhbG... F->>F: 提取 Header 中的 Token F->>J: parseToken(token) J->>J: 验签 + 检查过期 alt Token 有效 J-->>F: Claims {userId, username} F->>US: loadUserByUsername() US->>DB: 查询用户 DB-->>US: SysUser US-->>F: LoginUser F->>F: SecurityContextHolder.setContext() F-->>F: chain.doFilter() → Controller else Token 过期 J-->>F: ExpiredJwtException F-->>C: 401 Unauthorized else Token 无效 J-->>F: SignatureException F-->>C: 401 Unauthorized end ``` ### 4.2 登录安全防护 ```mermaid flowchart TD Login["登录请求"] --> CheckUser{"用户存在?"} CheckUser -->|否| Fail1["返回: 账号或密码错误"] CheckUser -->|是| CheckStatus{"账号已启用?"} CheckStatus -->|否| Fail2["返回: 账号已禁用"] CheckStatus -->|是| CheckLock{"15 分钟内失败 ≥ 5 次?"} CheckLock -->|是| Fail3["返回: 账号已锁定"] CheckLock -->|否| CheckPwd{"密码正确?"} CheckPwd -->|否| IncFail["失败计数 + 1"] IncFail --> Fail4["返回: 密码错误"] CheckPwd -->|是| ResetFail["清除失败计数"] ResetFail --> GenToken["生成 JWT Token"] GenToken --> Log["记录登录日志"] Log --> Success["返回 token + 用户信息"] ``` --- ## 五、定时任务数据流 ### 5.1 资源状态刷新 + 到期提醒 ```mermaid flowchart TD subgraph 定时触发 Cron["cron: 0 0 8 * * ?
每天 08:00"] end subgraph 阶段一["阶段一:状态刷新"] Cron --> Refresh["refreshExpireStatus()"] Refresh --> Q1["查询所有未停用资源"] Q1 --> Loop1{"遍历每个资源"} Loop1 --> Calc["计算 daysUntilExpiry
= expire_date - today"] Calc --> S1{"expire_date < today?"} S1 -->|是| Set1["status = 3 (已过期)"] S1 -->|否| S2{"daysUntilExpiry ≤ 30?"} S2 -->|是| Set2["status = 2 (即将到期)"] S2 -->|否| Set3["status = 1 (使用中)"] Set1 --> Update["UPDATE resource SET status"] Set2 --> Update Set3 --> Update Update --> Loop1 end subgraph 阶段二["阶段二:邮件提醒"] Update --> Scan["扫描需要提醒的资源"] Scan --> Q2["查询 status IN (2, 3) 的资源"] Q2 --> CheckLog{"查 expire_notify_log
是否已发过?"} CheckLog -->|已发| Skip["跳过"] CheckLog -->|未发| Stage{"判断阶段"} Stage -->|≤30天 >7天| Far["stage=far
普通提醒"] Stage -->|≤7天 >0天| Near["stage=near
紧急提醒"] Stage -->|已过期| Expired["stage=expired
过期通知"] Far --> Send["发送邮件给负责人"] Near --> Send Expired --> Send Send --> LogNotify["INSERT expire_notify_log"] end ``` --- ## 六、前端状态管理数据流 ### 6.1 用户状态管理 (Pinia) ```mermaid flowchart TD subgraph 登录流程 Input["输入账号密码"] --> CallLogin["userStore.login()"] CallLogin --> API["loginApi() → POST /auth/login"] API --> Resp["响应: {token, userId, role}"] Resp --> SetToken["setToken(token)"] SetToken --> LS["localStorage.setItem('token', ...)"] SetToken --> State["state.token = token"] State --> FetchMe["fetchMe() → GET /auth/me"] FetchMe --> SetInfo["state.userInfo = {...}"] SetInfo --> Redirect["router.push(redirect || '/dashboard')"] end subgraph 请求拦截 Req["Axios 请求"] --> GetToken["localStorage.getItem('token')"] GetToken --> AddHeader["headers.Authorization = 'Bearer ' + token"] AddHeader --> Send["发送请求"] end subgraph 响应拦截 Res["Axios 响应"] --> CheckCode{"code === 200?"} CheckCode -->|否| Error["ElMessage.error(msg)"] CheckCode -->|是| Return["返回 data"] Error --> Check401{"code === 401?"} Check401 -->|是| ClearToken["clearToken()"] ClearToken --> GoLogin["router.push('/login')"] end ``` ### 6.2 路由守卫流程 ```mermaid flowchart TD Nav["用户导航"] --> Guard["router.beforeEach()"] Guard --> HasToken{"localStorage
有 token?"} HasToken -->|是| IsLogin{"目标是 /login?"} IsLogin -->|是| Redirect["→ /dashboard
(已登录不显示登录页)"] IsLogin -->|否| Allow["放行"] HasToken -->|否| IsPublic{"是公开页?
(/login)"} IsPublic -->|是| Allow IsPublic -->|否| ToLogin["→ /login?redirect=当前路径"] ``` --- ## 七、模块间交互矩阵 ```mermaid graph TD subgraph 资源模块 Res["Resource"] Renew["ResourceRenewal"] Expire["ExpireNotify"] end subgraph 项目模块 Proj["Project"] ProjRes["ProjectResource"] end subgraph 用户模块 User["SysUser"] Auth["Auth/JWT"] end subgraph 系统模块 Log["OperationLog"] Dict["ResourceType/Provider"] Dash["Dashboard"] end Res -->|owner_id| User Res -->|type_id| Dict Res -->|provider_id| Dict Res -->|续费| Renew Res -->|关联| ProjRes Res -->|到期提醒| Expire Res -->|操作日志| Log Proj -->|owner_id| User Proj -->|父子| Proj Proj -->|关联| ProjRes ProjRes -->|project_id| Proj ProjRes -->|resource_id| Res Renew -->|resource_id| Res Renew -->|operator_id| User Expire -->|resource_id| Res Log -->|user_id| User Dash -->|统计| Res Dash -->|统计| Proj Auth -->|认证| User ``` --- ## 八、关键设计决策 | 决策 | 方案 | 理由 | |------|------|------| | 认证方式 | JWT (无状态) | 前后端分离,无需服务端 session | | 密码存储 | BCrypt 哈希 | Spring Security 内置支持,安全性高 | | 登录锁定 | 内存计数 + 15 分钟过期 | 简单有效,防暴力破解 | | 项目结构 | 两层(顶层 + 子项目) | 满足业务需求,避免过深嵌套 | | 费用分摊 | project_resource 表存储百分比 | 灵活支持均分/自定义,尾差修正 | | 续费记录 | 独立表,不回写主表金额 | 保留完整历史,支持删除回滚 | | 到期提醒 | 三阶段 + 日志表去重 | 避免重复发送,提醒有梯度 | | 资源状态 | 定时任务刷新 + 操作时即时刷新 | 双保险,数据一致性 | | 逻辑删除 | MyBatis-Plus @TableLogic | 数据可恢复,审计友好 | | 配置分离 | dev/prod profile | 开发便利 + 生产安全 | | 敏感信息 | 环境变量注入 | 不进代码仓库 | | Excel 导出 | EasyExcel | 内存友好,大数据量不 OOM | | 前端图表 | ECharts + useChart 封装 | 主题切换支持,代码复用 | | 前端状态 | Pinia (Composition API) | Vue 3 官方推荐,类型安全 |