文件
hyb-oa/docs/architecture.md
T
wangsli 19441cb03a docs: 修订架构图与ER图(As-Is/To-Be)(F42)
- 拆分 As-Is/To-Be,避免文档超前实现
- 修正部署视图/OCR 异步/ER 基数与可空关系
- 补对标能力清单与演进路线
2026-02-09 17:45:39 +08:00

14 KiB
原始文件 Blame 文件历史

hyb-oa 系统架构概览

版本:1.1
日期:2026-02-09
目的:帮助新成员快速理解系统"怎么做"的全貌(并提供演进方向)。


阅读指南:As‑Is vs To‑Be

  • As‑Is(现状):以当前仓库代码、试运行部署包为准,用于回归、排障、交接。
  • To‑Be(前瞻):面向 P5+ 的演进目标,用于指导架构升级;所有未来项都会明确标注 [To‑Be/未实现],避免误解为已交付。

1. 系统架构(部署视图)

1.1 部署视图(As‑Is / 单机试运行)

现状特点:单机可运行、同域可部署、作业同进程(node-cron),外部依赖最小。

graph TB
  subgraph Client["客户端"]
    Browser["浏览器 (React SPA)"]
  end

  subgraph Node["单机(dev/trialrun)"]
    Proxy["可选:Nginx/Caddy\nTLS/反代/限流/上传体积控制"]
    App["Fastify Server\n(API + 可选静态托管 client/dist)"]
    Jobs["同进程 Jobs (node-cron)\nOCR Job / 委派回收 Job"]
    PG["PostgreSQL (15+/16)"]
    Storage["本地存储 (票据文件)"]
  end

  subgraph External["外部服务(可选)"]
    TencentOCR["腾讯云 OCR"]
  end

  Browser -.->|可选 HTTPS| Proxy
  Browser -->|dev 可直连| App
  Proxy -->|/api/*| App
  Proxy -->|/*| App

  App -->|Prisma| PG
  Jobs -->|Prisma| PG
  App -->|文件读写| Storage
  Jobs -->|文件读写| Storage
  Jobs -->|API 调用| TencentOCR

说明

  • PostgreSQL 版本口径:开发 compose 可为 15,试运行包建议 16(两者均可)。
  • Proxy(Nginx/Caddy)是可选项:试运行/上线建议配置(TLS、上传限制、限流),开发联调可跳过。
  • App 可通过 SERVE_CLIENT_DIST=true 托管 client/dist,实现同域 cookie 与 SPA fallback。
  • Jobs 现状与 App 同进程(node-cron);未来可拆分独立 Worker(见 To‑Be)。

1.2 部署视图(To‑Be / 可扩展但仍可单机)

目标:在不改变“单机可跑”前提下,逐步具备“可横向扩展、可观测、可运维”的形态。

graph TB
  subgraph Client["客户端"]
    Browser["浏览器 (React SPA)"]
  end

  subgraph Edge["边缘层 [To‑Be/未实现]"]
    Proxy["Nginx/Caddy/WAF\nTLS/反代/限流/上传策略"]
  end

  subgraph App["应用层"]
    API["Fastify API (stateless)\n可选静态托管 client/dist"]
    Worker["Job Worker [To‑Be/未实现]\nOCR / 委派回收 / 清理作业"]
    Queue["队列/Redis [To‑Be/未实现]\nBullMQ/Streams(可选)"]
  end

  subgraph Data["数据层"]
    PG["PostgreSQL\nPgBouncer [To‑Be/未实现]"]
    ObjectStorage["对象存储 [To‑Be/未实现]\nS3/OSS/MinIO"]
  end

  subgraph Obs["可观测性 [To‑Be/未实现]"]
    Logs["结构化日志聚合\nLoki/ELK"]
    Metrics["metrics\nPrometheus"]
  end

  subgraph External["外部服务"]
    TencentOCR["腾讯云 OCR"]
  end

  Browser --> Proxy
  Proxy --> API

  API --> PG
  Worker --> PG

  API --> ObjectStorage
  Worker --> ObjectStorage

  Worker <--> Queue
  Worker --> TencentOCR

  API --> Logs
  Worker --> Logs
  API --> Metrics
  Worker --> Metrics

2. 核心业务流程(报销审批 / As‑Is)

核心链路:票据上传 →(异步)OCR → 绑定票据 → 提交报销 → 工作流审批 → 财务付款 → 票据归档。

sequenceDiagram
  participant E as 员工
  participant S as Server(Fastify)
  participant J as OCR Job(node-cron)
  participant O as 腾讯云OCR
  participant WF as 工作流引擎
  participant M as 经理
  participant F as 财务

  E->>S: 上传票据 (invoice_pool: pending_ocr/available)
  S-->>E: 返回 invoiceId + status

  J->>S: 认领 pending_ocr (FOR UPDATE SKIP LOCKED + 原子回写锁)
  J->>O: OCR 识别(可 mock/off)
  O-->>J: 识别结果/错误
  J->>S: 更新 invoice_pool (available/ocr_failed)

  E->>S: GET /api/invoice-pool/:id (轮询/刷新)
  S-->>E: 返回最新 status 与字段

  E->>S: 绑定票据到报销单 (available -> bound)
  E->>S: 提交报销 (draft -> reviewing)
  S->>WF: 创建 workflow_instance + 首个 task
  WF-->>M: 待办 task(assignee_id 或 assignee_role)

  M->>S: approve / return / reject / transfer
  WF->>WF: 推进流程并写 workflow_history

  F->>S: pay (approved -> paid)
  S->>S: bound 票据 -> archived (used_complete)
  S->>S: 写 audit_logs(关键运营/安全动作)

证据链边界(现状)

  • workflow_history:流程证据(针对某个 workflow_instance/task 的动作轨迹),用于还原审批过程。
  • audit_logs:合规/安全证据(登录、委派回收、用户管理等跨域操作),用于审计与风控追踪。

3. 数据模型(ER 图 / As‑Is)

说明:为可读性仅列关键字段与关系;具体字段以 Prisma schema 为准。

3.1 组织 / 权限 / 会话 / 审计

erDiagram
  Department ||--o{ Employee : contains
  Department ||--o{ Department : children

  Employee ||--o{ EmployeeRole : has
  Employee ||--o{ AuthSession : sessions
  Employee ||--o{ PasswordResetCode : resets
  Employee ||--o{ AuditLog : writes
  Employee ||--o{ DelegationGrant : principal
  Employee ||--o{ DelegationGrant : delegate

  Department {
    uuid id PK
    string name
    uuid parent_id FK
  }

  Employee {
    uuid id PK
    string employee_no UK
    uuid department_id FK
    uuid direct_manager_id FK
    string status
    int token_version
  }

  EmployeeRole {
    uuid employee_id PK,FK
    string role_code PK
  }

  AuthSession {
    uuid id PK
    uuid employee_id FK
    string device_fingerprint
    datetime expires_at
    datetime revoked_at
  }

  PasswordResetCode {
    uuid id PK
    uuid employee_id FK
    datetime expires_at
    datetime used_at
  }

  DelegationGrant {
    uuid id PK
    uuid principal_employee_id FK
    uuid delegate_employee_id FK
    datetime valid_from
    datetime valid_to
    datetime revoked_at
  }

  AuditLog {
    uuid id PK
    string request_id
    uuid actor_id FK
    string action
    string target_type
    uuid target_id
    datetime created_at
  }

3.2 报销与票据(含 OCR 缓存)

erDiagram
  Employee ||--o{ ReimbursementForm : owns
  Employee ||--o{ ReimbursementForm : submits
  Department ||--o{ ReimbursementForm : belongs

  ReimbursementForm ||--o{ ReimbursementItem : contains
  ExpenseType ||--o{ ReimbursementItem : categorizes

  Employee ||--o{ InvoicePool : owns
  Employee ||--o{ InvoicePool : uploads
  ReimbursementForm ||--o{ InvoicePool : links
  ReimbursementItem ||--o{ InvoicePool : linksItem
  OcrResult ||--o{ InvoicePool : cachedFor

  ReimbursementForm {
    uuid id PK
    string form_number UK
    uuid owner_id FK
    uuid submitter_id FK
    uuid department_id FK
    string status
    decimal total_amount
    string payment_voucher_no
  }

  ReimbursementItem {
    uuid id PK
    uuid reimbursement_id FK
    uuid expense_type_id FK
    decimal amount
    string description
  }

  ExpenseType {
    uuid id PK
    string code UK
    string name
    string category
  }

  InvoicePool {
    uuid id PK
    uuid owner_employee_id FK
    uuid uploaded_by_employee_id FK
    string storage_path
    string file_sha256
    string status
    uuid linked_reimbursement_id FK
    uuid linked_item_id FK
    uuid ocr_result_id FK
  }

  OcrResult {
    uuid id PK
    string file_sha256
    string ocr_provider
    string ocr_type
    json result_json
  }

关系备注(关键纠偏)

  • InvoicePool.linked_reimbursement_id / linked_item_id / ocr_result_id 均为 可空字段(票据可长期处于 available/pending_ocr 未绑定状态)。

3.3 工作流引擎(含转审证据)

erDiagram
  WorkflowDefinition ||--o{ WorkflowInstance : creates
  ReimbursementForm ||--o{ WorkflowInstance : instances
  WorkflowInstance ||--o{ WorkflowTask : tasks
  WorkflowInstance ||--o{ WorkflowHistory : history
  WorkflowTask ||--o{ WorkflowHistory : taskHistory

  Employee ||--o{ WorkflowTask : assigned
  Employee ||--o{ WorkflowTask : completed
  Employee ||--o{ WorkflowHistory : actions

  ReimbursementForm ||--o{ ApprovalTransfer : transfers
  Employee ||--o{ ApprovalTransfer : from
  Employee ||--o{ ApprovalTransfer : to

  WorkflowDefinition {
    uuid id PK
    string entity_type
    int version
    bool is_active
    json definition_json
  }

  WorkflowInstance {
    uuid id PK
    uuid workflow_id FK
    string entity_type
    uuid entity_id FK
    uuid submitter_id FK
    string current_status
    string current_step_id
    json context_data
  }

  WorkflowTask {
    uuid id PK
    uuid instance_id FK
    string step_id
    string step_name
    uuid assignee_id FK
    string assignee_role
    string status
    string outcome
  }

  WorkflowHistory {
    uuid id PK
    uuid instance_id FK
    uuid task_id FK
    uuid actor_id FK
    string action
    json payload
  }

  ApprovalTransfer {
    uuid id PK
    uuid reimbursement_id FK
    uuid from_approver_id FK
    uuid to_approver_id FK
    string reason
    string status
  }

  ApprovalRule {
    uuid id PK
    string name
    decimal min_amount
    decimal max_amount
    string level1_role
    string level2_role
    string level3_role
    bool enabled
    int priority
  }

关系备注(关键纠偏)

  • ReimbursementForm -> WorkflowInstance 是 0..n(同一报销单可能多次提交产生多个实例)。
  • WorkflowTask.assignee_id 与 assignee_role 在语义上通常 互斥(实现允许为空/组合以支持候选/角色任务)。
  • WorkflowHistory.task_id 在实现中为 可空字段(例如非 task 维度的历史事件)。
  • ApprovalRule 是规则配置表:用于构建审批步骤,通常以 workflow_history.payload 等形式被引用(并非强制外键关联)。

3.4 单号序列(系统表)

erDiagram
  DocumentSequence {
    string document_type PK
    string prefix
    string separator
    int current_number
    string reset_period
    datetime updated_at
  }

4. 状态机

4.1 报销单状态(As‑Is)

stateDiagram-v2
  [*] --> draft: create
  draft --> reviewing: submit
  reviewing --> draft: return / withdraw
  reviewing --> rejected: reject
  reviewing --> approved: final approve
  approved --> paid: pay
  rejected --> draft: edit & resubmit
  paid --> [*]

4.2 报销单状态(To‑Be/未实现)

预留方向:补齐“作废/取消”语义,支持运营或系统终止(例如提交后发现异常、关账等)。

stateDiagram-v2
  [*] --> draft
  draft --> reviewing: submit
  reviewing --> cancelled: cancel [To‑Be]
  approved --> cancelled: cancel [To‑Be]
  cancelled --> [*]

4.3 票据池状态(As‑Is)

stateDiagram-v2
  [*] --> pending_ocr: upload (OCR enabled)
  [*] --> available: upload (OCR off/mock)
  pending_ocr --> available: OCR success
  pending_ocr --> ocr_failed: OCR failed / duplicate
  available --> bound: bind
  bound --> archived: pay (used_complete)

4.4 票据池状态(To‑Be/未实现)

预留方向:增加“解绑、超时归档、人工纠错”能力(需要额外 API/作业/运营策略支撑)。

stateDiagram-v2
  bound --> available: unbind [To‑Be]
  available --> archived: unused_timeout [To‑Be]
  ocr_failed --> available: manual_fix [To‑Be]

5. 目录结构(简化视图)

hyb-oa/
├── client/                 # 前端 (React + Vite + Tailwind)
│   ├── src/
│   │   ├── api/            # API 封装
│   │   ├── auth/           # 会话与权限映射
│   │   ├── components/     # UI 组件
│   │   ├── layouts/        # 布局
│   │   ├── pages/          # 页面
│   │   ├── query/          # React Query 统一封装
│   │   ├── router/         # 路由与守卫
│   │   └── styles/         # Token/基础样式
│   └── tests/e2e/          # Playwright E2E
│
├── server/                 # 后端 (Fastify + Prisma)
│   ├── src/
│   │   ├── routes/         # API 路由
│   │   ├── services/       # 业务逻辑 (OCR 等)
│   │   ├── jobs/           # 定时任务 (OCR/委派回收)
│   │   └── utils/          # 工具函数
│   ├── prisma/             # 数据库 Schema & 迁移
│   └── scripts/            # 回归脚本
│
├── deploy/trialrun/        # 试运行部署包
├── docs/                   # 文档 (需求/实施/架构)
├── issues/                 # Issue CSV 执行合同
└── scripts/                # 门禁/备份恢复脚本

6. 技术栈速览(As‑Is)

层级 技术 说明
前端 React 18 + Vite + Tailwind SPA;配套 React Query、Radix primitives、Playwright E2E
后端 Fastify + Prisma Node.js;JWT + refresh cookie;RBAC 权限
数据库 PostgreSQL 15+(试运行建议 16) Prisma ORM;关键队列/作业依赖 SQL 锁语义
OCR 腾讯云(可 mock/off) 异步作业 + 并发锁;失败降级不阻塞上传
部署 Docker Compose 单机试运行;可选反代同域
CI GitHub Actions 最小门禁 + 回归 + E2E(按开关分层执行)

7. 相关文档


8. 对标与演进(不造轮子)

本节只列“可借鉴能力清单”,不代表已实现。

8.1 工作流(Camunda / Flowable / Activiti 思想)

  • Candidate / Claim / Unclaim:候选人/候选组与认领机制(减少“角色任务抢占冲突”,提升可控性)。
  • SLA / Due date / Escalation:任务期限与超时升级(试运行后期高频诉求)。
  • History & Audit 分层:流程历史(workflow_history)与合规审计(audit_logs)职责边界清晰。

8.2 费用报销(Odoo / ERPNext 的单据分层)

  • 业务单据 vs 付款凭证/对账:先保持 payment_voucher_no/payment_date/channel 的轻量字段;后续可演进独立 payment_transactions 与对账状态,不影响现有 API。

8.3 合规审计(企业级通用实践)

  • 统一 request_id/trace_id:贯穿 API 与作业日志(便于追踪、复盘、举证)。
  • 最小可观测性:ready/metrics/log 聚合(先最小可用,后续再引入 OpenTelemetry 等体系化方案)。