14 KiB
14 KiB
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 等体系化方案)。