From 1b6e801b12086377372d1ea1b9b7d9f97c4134c4 Mon Sep 17 00:00:00 2001 From: shunli Date: Mon, 9 Feb 2026 22:30:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3:=20=E5=AE=8C=E6=88=90=20arch?= =?UTF-8?q?itecture.md=20P5=20=E7=8E=B0=E7=8A=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=AD=A3=E5=8F=AF=E8=A7=82=E6=B5=8B=E6=80=A7?= =?UTF-8?q?=E6=A0=88=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/architecture.md | 585 +++++++++---------------------------------- 1 file changed, 125 insertions(+), 460 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 1297ea88..32e1827b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,23 +1,14 @@ # hyb-oa 系统架构概览 -**版本**:1.1 +**版本**:1.2 **日期**:2026-02-09 -**目的**:帮助新成员快速理解系统"怎么做"的全貌(并提供演进方向)。 +**目的**:描述系统当前实现(P5/F25 完备态)并为未来演进提供参考。 --- -## 阅读指南:As‑Is vs To‑Be +## 1. 部署视图 (As-Is) -- **As‑Is(现状)**:以当前仓库代码、试运行部署包为准,用于回归、排障、交接。 -- **To‑Be(前瞻)**:面向 P5+ 的演进目标,用于指导架构升级;所有未来项都会明确标注 **[To‑Be/未实现]**,避免误解为已交付。 - ---- - -## 1. 系统架构(部署视图) - -### 1.1 部署视图(As‑Is / 单机试运行) - -> 现状特点:**单机可运行、同域可部署、作业同进程**(node-cron),外部依赖最小。 +> 现状特点:**单机/集群兼容、进程级别拆分、支持云原生存储与监控。** ```mermaid graph TB @@ -25,495 +16,169 @@ graph TB 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["本地存储 (票据文件)"] + subgraph Cluster["部署环境 (Docker Compose Profiles)"] + Proxy["可选:Nginx/Caddy\nTLS/反代/前端托管"] + + subgraph Server_Nodes["计算层"] + API["Fastify API\n(JOBS_ENABLED=false)"] + Worker["Worker 进程\n(npm run worker)"] + end + + subgraph Infra_Services["基础设施"] + PG["PostgreSQL (16) + pg-boss"] + OSS["MinIO (S3 兼容) / Local FS"] + end end - subgraph External["外部服务(可选)"] + subgraph External["外部服务 / 可选集"] TencentOCR["腾讯云 OCR"] + OIDC["OIDC Provider (Okta/Authing等)"] + Prom["可选:Prometheus (抓取 /metrics)"] end - Browser -.->|可选 HTTPS| Proxy - Browser -->|dev 可直连| App - Proxy -->|/api/*| App - Proxy -->|/*| App + Browser -->|HTTPS/HTTP| Proxy + Proxy -->|/api/*| API + Proxy -->|/*| API - App -->|Prisma| PG - Jobs -->|Prisma| PG - App -->|文件读写| Storage - Jobs -->|文件读写| Storage - Jobs -->|API 调用| TencentOCR + API -->|Prisma| PG + Worker -->|pg-boss| PG + API -->|Storage Service| OSS + Worker -->|Storage Service| OSS + Worker -->|API 调用| TencentOCR + API -->|OIDC Auth| OIDC + Prom -.->|Scrape| API ``` -**说明** -- `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 / 可扩展但仍可单机) - -> 目标:在不改变“单机可跑”前提下,逐步具备“可横向扩展、可观测、可运维”的形态。 - -```mermaid -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 -``` +**关键变更说明**: +- **Worker 进程独立**:支持通过 `docker compose --profile worker` 拆分 API 与定时作业进程。 +- **存储抽象层**:适配本地文件系统与 S3/MinIO,通过 `STORAGE_DRIVER` 切换。 +- **治理接入**:内置 Prometheus 指标采集 `/metrics`。 --- -## 2. 核心业务流程(报销审批 / As‑Is) - -> 核心链路:**票据上传 →(异步)OCR → 绑定票据 → 提交报销 → 工作流审批 → 财务付款 → 票据归档**。 +## 2. 核心业务流程 (As-Is / 异步作业) ```mermaid 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 财务 + participant E as 员工/行政 + participant API as API Server + participant Q as pg-boss (Postgres) + participant W as Worker + participant O as 腾讯云/Mock OCR + participant WF as Workflow Engine - E->>S: 上传票据 (invoice_pool: pending_ocr/available) - S-->>E: 返回 invoiceId + status + E->>API: 上传票据 (invoice_pool: pending_ocr) + API->>Q: 发布 ocr.tick 任务 + API-->>E: 返回票据 ID (状态: pending_ocr) - J->>S: 认领 pending_ocr (FOR UPDATE SKIP LOCKED + 原子回写锁) - J->>O: OCR 识别(可 mock/off) - O-->>J: 识别结果/错误 - J->>S: 更新 invoice_pool (available/ocr_failed) + W->>Q: 获取任务 (Fetch Job) + W->>W: 获取票据缓存或文件 + W->>O: 调用 OCR + O-->>W: 结果 + W->>API: 更新数据库 (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 组织 / 权限 / 会话 / 审计 - -```mermaid -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 缓存) - -```mermaid -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 工作流引擎(含转审证据) - -```mermaid -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 单号序列(系统表) - -```mermaid -erDiagram - DocumentSequence { - string document_type PK - string prefix - string separator - int current_number - string reset_period - datetime updated_at - } + E->>API: 提交报销 (submit) + API->>WF: 初始化实例 (0..n 关系) + WF-->>API: 创建 Task + API->>API: 触发通知 (邮件/站内) ``` --- -## 4. 状态机 +## 3. 数据模型 (ER 图) -### 4.1 报销单状态(As‑Is) +### 3.1 核心业务与工作流 ```mermaid -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 --> [*] +erDiagram + REIMBURSEMENT_FORM ||--o{ WORKFLOW_INSTANCE : instances + LEAVE_REQUEST ||--o{ WORKFLOW_INSTANCE : instances + WORKFLOW_INSTANCE ||--|{ WORKFLOW_TASK : contains + WORKFLOW_INSTANCE ||--|{ WORKFLOW_HISTORY : logs + + REIMBURSEMENT_FORM { + uuid id PK + string form_number UK + string status + decimal total_amount + } + + WORKFLOW_INSTANCE { + uuid id PK + uuid entity_id FK + string entity_type "reimbursement | leave" + string current_status + } + + WORKFLOW_TASK { + uuid id PK + uuid instance_id FK + uuid assignee_id FK + string assignee_role + string status + } ``` -### 4.2 报销单状态(To‑Be/未实现) - -> 预留方向:补齐“作废/取消”语义,支持运营或系统终止(例如提交后发现异常、关账等)。 +### 3.2 治理、安全与存储 ```mermaid -stateDiagram-v2 - [*] --> draft - draft --> reviewing: submit - reviewing --> cancelled: cancel [To‑Be] - approved --> cancelled: cancel [To‑Be] - cancelled --> [*] -``` +erDiagram + EMPLOYEE ||--o{ DELEGATION_GRANT : principal + EMPLOYEE ||--o{ AUTH_SESSION : sessions + EMPLOYEE ||--o{ AUDIT_LOG : actor + INVOICE_POOL ||--o| OCR_RESULT : caches -### 4.3 票据池状态(As‑Is) + EMPLOYEE { + uuid id PK + string bank_account "Encrypted" + string id_number "Encrypted" + int token_version + } -```mermaid -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) -``` + DELEGATION_GRANT { + uuid id PK + uuid principal_id FK + uuid delegate_id FK + datetime valid_to + } -### 4.4 票据池状态(To‑Be/未实现) + AUDIT_LOG { + uuid id PK + string action + json extra "Context snapshot" + } -> 预留方向:增加“解绑、超时归档、人工纠错”能力(需要额外 API/作业/运营策略支撑)。 - -```mermaid -stateDiagram-v2 - bound --> available: unbind [To‑Be] - available --> archived: unused_timeout [To‑Be] - ocr_failed --> available: manual_fix [To‑Be] + INVOICE_POOL { + uuid id PK + string file_sha256 UK-Index + string storage_path "local: or s3:" + uuid linked_reimbursement_id FK "Nullable" + } ``` --- -## 5. 目录结构(简化视图) +## 4. 目录结构 ``` hyb-oa/ -├── client/ # 前端 (React + Vite + Tailwind) +├── server/ │ ├── 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/ # 门禁/备份恢复脚本 +│ │ ├── services/ +│ │ │ ├── storage/ # S3/Local 抽象 +│ │ │ ├── crypto/ # AES-256 敏感字段加密 +│ │ │ └── oidc/ # OIDC 客户端逻辑 +│ │ ├── observability/ # Metrics & Logging +│ │ └── worker.js # Worker 进程入口 +│ └── prisma/schema.prisma # 统一模型定义 +├── deploy/trialrun/ # 多 Profile Compose & Runbook +└── docs/ # 规格书与本架构图 ``` --- -## 6. 技术栈速览(As‑Is) +## 5. 展望 (To-Be / P7+) -| 层级 | 技术 | 说明 | -|---|---|---| -| 前端 | 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. 相关文档 - -- [需求规格说明书](./requirements-specification.md) - 完整的 P0 需求定义 -- [实施计划](./implementation_plan.md) - 里程碑与验收标准 -- [技术栈报告](./tech_stack_report.md) - 选型依据 - ---- - -## 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 等体系化方案)。 +- **分布式作业调度**:从 `pg-boss` 演进到支持 Dapr 或 Temporal(针对复杂长流程)。 +- **多租户/物理隔离**:支持更复杂的组织架构隔离方案。 +- **前端微模块化**:针对行政、财务、业务员拆分不同的前端 Bundle。