05-数据流与模块交互详图.md 14 KB

数据流与模块交互详图

本文档补充系统各模块之间的数据流转细节和交互时序。


一、资源管理全链路数据流

1.1 资源 CRUD 完整数据流

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<br/>(关联类型名、服务商名、负责人名、关联项目)
    S-->>C: IPage<ResourceVO>
    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: 计算初始状态<br/>(根据 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 数据流

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<Resource>
    S->>S: Entity → ResourceExportVO<br/>(状态转文字、类型名、服务商名)
    S-->>C: List<ResourceExportVO>
    C->>EE: EasyExcel.write(response, ResourceExportVO.class)
    EE->>C: 生成 .xlsx 字节流
    C-->>A: Content-Type: application/octet-stream<br/>Content-Disposition: attachment
    A-->>V: Blob 响应
    V->>V: 创建下载链接<br/>触发浏览器下载
    V-->>U: 文件保存对话框

二、项目管理数据流

2.1 项目树形查询

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<Project>

    S->>S: 分离顶层/子项目
    S->>S: 构建树形结构:<br/>顶层项目 → children[]

    loop 每个顶层项目
        S->>DB: 查询关联资源数
        S->>DB: 查询子项目列表
        loop 每个子项目
            S->>DB: 查询子项目关联资源数
        end
    end

    S-->>C: List<ProjectVO> (树形)
    C-->>A: Result.ok(data)
    A-->>V: el-table tree 模式渲染

2.2 项目详情与资源关联

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],<br/>projectId: 5,<br/>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 按项目维度统计

flowchart LR
    subgraph 查询
        Q["GET /resource/cost-stat/project<br/>?days=30"]
    end

    subgraph SQL["ResourceMapper.xml"]
        SQL1["SELECT<br/>  p.name AS projectName,<br/>  COUNT(DISTINCT r.id) AS resourceCount,<br/>  SUM(r.cost * pr.allocation_percent / 100) AS totalCost<br/>FROM project p<br/>LEFT JOIN project_resource pr ON ...<br/>LEFT JOIN resource r ON ...<br/>GROUP BY p.id"]
    end

    subgraph 结果
        R1["CostStatVO<br/>{projectName, resourceCount, totalCost}"]
    end

    Q --> SQL1
    SQL1 --> R1

3.2 采购费用台账数据流

flowchart TD
    subgraph 数据来源
        R["resource 表<br/>初始采购记录"]
        RR["resource_renewal 表<br/>续费记录"]
    end

    subgraph 合并逻辑["ResourceServiceImpl.mergeProcurementCosts()"]
        Q["ProcurementCostQuery<br/>名称/类型/服务商/费用类型/月份"]
        Merge["合并初始采购 + 续费记录<br/>UNION ALL"]
        Filter["按条件筛选"]
        Page["分页"]
    end

    subgraph 输出
        VO["ProcurementCostVO<br/>资源名、类型、服务商、项目、占比、<br/>分摊费用、计费周期、购买时长、<br/>类型标签(初始采购/续费)、<br/>发生日期、到期日期"]
    end

    R --> Merge
    RR --> Merge
    Q --> Filter
    Merge --> Filter
    Filter --> Page
    Page --> VO

四、认证授权数据流

4.1 JWT Token 生命周期

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 签名<br/>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<br/>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 登录安全防护

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 资源状态刷新 + 到期提醒

flowchart TD
    subgraph 定时触发
        Cron["cron: 0 0 8 * * ?<br/>每天 08:00"]
    end

    subgraph 阶段一["阶段一:状态刷新"]
        Cron --> Refresh["refreshExpireStatus()"]
        Refresh --> Q1["查询所有未停用资源"]
        Q1 --> Loop1{"遍历每个资源"}
        Loop1 --> Calc["计算 daysUntilExpiry<br/>= 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<br/>是否已发过?"}
        CheckLog -->|已发| Skip["跳过"]
        CheckLog -->|未发| Stage{"判断阶段"}
        Stage -->|≤30天 >7天| Far["stage=far<br/>普通提醒"]
        Stage -->|≤7天 >0天| Near["stage=near<br/>紧急提醒"]
        Stage -->|已过期| Expired["stage=expired<br/>过期通知"]
        Far --> Send["发送邮件给负责人"]
        Near --> Send
        Expired --> Send
        Send --> LogNotify["INSERT expire_notify_log"]
    end

六、前端状态管理数据流

6.1 用户状态管理 (Pinia)

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 路由守卫流程

flowchart TD
    Nav["用户导航"] --> Guard["router.beforeEach()"]
    Guard --> HasToken{"localStorage<br/>有 token?"}
    HasToken -->|是| IsLogin{"目标是 /login?"}
    IsLogin -->|是| Redirect["→ /dashboard<br/>(已登录不显示登录页)"]
    IsLogin -->|否| Allow["放行"]
    HasToken -->|否| IsPublic{"是公开页?<br/>(/login)"}
    IsPublic -->|是| Allow
    IsPublic -->|否| ToLogin["→ /login?redirect=当前路径"]

七、模块间交互矩阵

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 官方推荐,类型安全