技术架构

端 → 进程 → 中间件 → 模块 → 数据 → 事务 → 护栏

module deep module(SSOT §5.2:costing / inventory / approvalbook / money) seam / 非挂载(Non-Goal 保留代码) leak / 漂移
核对状态(2026-10-05):本页 T1、T2、T4、T5、T6 的 160 条论断(T3 为 import 实测) 已由 Codex 逐条对照「页面 ↔ 代码 ↔ 文档」,controller 抽查复跑 rg 证据通过;160 条论断中 一致 119 / 部分一致 23 / 不一致 18。不一致 / 部分一致的论断已按代码现实订正;代码偏离 PRD / ADR 或 SSOT 的项以红字标出,汇总在观察页。

T1系统全景:端 × 进程 × 基础设施

flowchart TB
  subgraph FE["前端 · Turborepo · 共享 @tlk/api · shared-api · types · utils · region"]
    ERP["商户端
boss/erp :5173 · React 19 + AntD 5 + Zustand(50+ 页面目录)
native/erp-app · React Native + paper"] MALL["商城端(客户)
apps/mall :5175 · React 19 + AntD 5
apps/miniapp · Taro + React 18 · native/mall-app"] ADM["平台端
boss/admin :5174 运营后台"] end subgraph BE["后端 · Go 1.25 · chi · pgx · 模块化单体"] SRV["cmd/server :8080
/api/v1/erp/* · /api/v1/mall/* · /api/v1/public/device/*"] PLT["cmd/platform-server :8081
/api/v1/platform/*"] end subgraph INF["基础设施"] PG[("PostgreSQL 16
共享 schema · tenant_id 隔离")] RD[("Redis
限流 · rbac/sysconfig/master 缓存")] NT[("NATS Core
通知推送 at-most-once")] RF[("RustFS
图片 / 附件")] end subgraph EXT["外部 I/O(payintegration 为可插拔通道,ADR-0020)"] PAY["payintegration
微信支付 / 支付宝 / 银联"] SMS["pkg/sms"] GEO["pkg/geocode"] end ADM --> PLT ERP --> SRV MALL --> SRV SRV --> PG SRV -. "可选:不可用则降级" .-> RD SRV -. "可选:不可用则降级" .-> NT SRV --> RF PLT --> PG PLT -. "可选" .-> RD PLT -. "可选" .-> NT SRV --> PAY SRV --> SMS SRV --> GEO PLT -. "共享库拉模式:sys_tenant / sys_subscription / sys_device(ADR-0036,无 RPC)" .- SRV classDef deep fill:#0f172a,color:#fff,stroke:#0f172a; class SRV deep

T2一次请求穿过什么:中间件 → handler → module → 数据库

1 全局
RequestID · RealIP · Recoverer · Compress(5) · CORS · MaxBodySize(10MB) · AccessLog · RateLimit(Redis 200/min;Redis 不可用时不启用,fail-open)
2 身份
mw.Auth(JWT, "erp" | "mall") → 校验签名与端别;JWT.tenant_id 为身份权威
3 租户
mw.Tenant(DB) → 每请求 ValidateTenant,X-Tenant-Id 须与 JWT 一致(否则 403)
4 吊销
mw.TokenRevocationCheck(AuthSvc)
5 数据范围
mw.LoadDataScope(DB, Redis) → 仓库 / 部门 / 业务员行级过滤上下文
6 权限
RequireModuleAccess(RBAC, module)(有效挂载 33 处;router 文本命中 36 含 3 处注释)· RequirePermissionCode · 单据 approve{1,2,3} 精确码(依据 approval-level-matrix / approvalbook PRD;模块挂载依据 ADR-0031)
7 handler
api/erp/*.go(88 个非测试文件)· api/mall · api/platform → 解码 / DTO / 状态码映射,错误由统一响应层处理;不含业务
8 module
internal/<module>/service.go → 业务规则 · 表驱动状态机 · 核心终审 / 跨模块写路径:TenantBegin 开事务并显式传 pgx.Tx(普通读与单表 CRUD 不强制开事务)
9 SQL
sqlc:queries/*.sql → sqlcgen(61 包);裸 SQL 仅限白名单并标 // RAWSQL:
10 守卫
pkg/database guardedTx:对 tenant_id=$N / INSERT 列位做值级注入(context 租户覆盖 bind 值);TENANT_SQL_GUARD=strict:缺 tenant_id 的业务 SQL panic;无法定位 bind 位的形态仅 warn + 透传
11 存储
PostgreSQL 16 · 共享 schema · 无 DB 外键(ADR-0022)· numeric 金额 · FOR UPDATE 热点行
商城链路差异

同样 Auth("mall") → Tenant(DB),商城用户为「租户内客户账号」(ADR-0046),登录时 platform.CheckSubscription 校验 mall SKU。

平台进程链路

:8081 中间件更短:RequestID · RealIP · Recoverer · CORS · MaxBody(22MB) · AccessLog · RateLimit,无 Tenant 校验,使用 platform 自身登录。

三道门禁各管一事:RBAC 决定「谁能调接口」;数据范围决定「看哪些行」;审批(模块自管状态机 + approvalbook 签字流水)决定「单据能否生效」。review 时别混。

T3模块拓扑:按真实 import 深度分带

带号 = 该包沿 import 链到叶子的最长距离(脚本计算),不是规范里的 L0–L4 标签;两者对照见 3c。每个 chip 是一个 internal 包。

深度 6printregistry · 注册表(ADR-0054),import 28 个包——是登记表,不是业务编排
深度 5mallpurchasepurchasereturnout
深度 4salesoutpurchaseinstocktake
深度 3ordersalesreturnpaymentinvoicearapoffsetwarehousetransferassemblydamageclosingcloudorderdashboard
深度 2costingfinanceitemreportbillprintdirectvendortoolbar
深度 1inventorymasterrbacpricingcashierquotationreturnorderplatformnotifmgrfreightassetprepaidpurchaseexpensepurchaseqcrepairsalescommissionseed
深度 0(叶子)approvalbooksysconfigauditnotificationglauthbillchainserialpromotioncustomfieldfileprinttemplatepayintegrationwmsdeliverydistributionfieldworkpayrollmembersync

绿底 = 高扇入横切包;虚线 = Non-Goal 保留代码(路由不挂载)。wms 位于叶子带,是因为它不 import salesout——验货出库由 cmd/server/wms_salesout_adapter.go 在 main 里注入(真实 seam)。

3b 被依赖最多的包(fan-in)
sysconfig
25
approvalbook
21
notification
19
audit
19
inventory
13
gl
11
finance
10
costing
9
item
6
auth
5

inventory 的 13 个依赖方 = ADR-0056 后调用 ApplyMovement 的单据模块(salesout / salesreturn / purchasein / purchasereturnout / warehouse / damage / assembly / transfer / directvendor …)+ costing。

3c 规范里的「五层」(订正前:两份文档定义不同;现已以 DESIGN_PATTERNS §7.1 为准)
层DESIGN_PATTERNS §7.1OVERVIEW §2.0.2(v0.6 订正前)
L0pkg/money · database · billno(基础设施)platform · auth · rbac · file · seed
L1master · item · rbac · sysconfigmaster · item · customfield
L2inventory · costing · finance · gl · approvalbookpricing · promotion · inventory · costing · sysconfig · approvalbook · billchain
L3order · purchase · salesout · salesreturn · transfer全部单据 + payment · cashier · gl · asset · invoice
L4api/erp · apps/mall(接入)mall · cloudorder · wms · delivery · report · dashboard(编排)

订正前的冲突:gl 在前者属 L2、在后者属 L3;rbac L1 vs L0;sysconfig L1 vs L2。已由 ARCHITECTURE_OVERVIEW.md v0.7 对齐,详见观察页。

3d 核心业务模块的真实 import(省略:人人都依赖的 approvalbook / audit / notification / sysconfig,以及 item / master / serial 等档案类依赖)点击图打开大图(可缩放 / 滚动)
flowchart LR
  subgraph L4["编排 / 接入"]
    mall["mall
商城 Checkout 编排"] wms["wms
仓储作业"] end subgraph L3["单据生命周期"] purchase["purchase
1207 / 1211 / 1209"] purchasein["purchasein 1212"] purchasereturnout["purchasereturnout 1214"] order["order 2221"] salesout["salesout 2222"] salesreturn["salesreturn 2224"] returnorder["returnorder 3308"] wh["warehouse · transfer
assembly · damage"] stocktake["stocktake 3300"] payment["payment 2323 / 1313"] invoice["invoice"] arapoffset["arapoffset 1323"] end subgraph L2["核算 / 流水 / 台账"] costing["costing
6 种成本方式"] inventory["inventory
余额 + 台账 唯一写入方"] finance["finance
应收应付台账"] cashier["cashier 出纳"] gl["gl 总账"] end mall --> order & salesout & payment & returnorder wms -. "main.go 注入适配器" .-> salesout purchase --> purchasein & salesout & invoice & finance purchasein --> costing & inventory & finance & gl & payment purchasereturnout --> purchasein & costing & inventory & finance order --> inventory & finance salesout --> costing & inventory & finance & payment salesreturn --> costing & inventory & finance wh --> costing & inventory stocktake --> wh payment --> finance & gl invoice --> finance & gl arapoffset --> finance & gl costing --> inventory finance --> cashier & gl cashier --> gl classDef deepc fill:#0f172a,color:#fff,stroke:#0f172a; class costing,inventory deepc

T4数据架构:三层模型、租户绝缘、schema 治理

ERP 三层数据模型(SSOT §2)
① 单据层 Bills(Intent)
表头 + 明细 · 表驱动状态机 · 各模块自管 · 引用基础资料快照不靠外键(ADR-0022)· 按形态专表(ADR-0018)
orderssales_out_billssales_return_billspurchase_orderspurchase_inbound_billspurchase_return_billsdirect_bills
▼ 终审:模块内落账(如 salesout.applyOutboundPosting)在同一个 pgx.Tx 内
② 流水与层账层:历史事实层只追加,派生 / 工作层可重建
SSOT §2.2(2026-10-05 按裁决订正)分两层:历史事实层——inventory_ledger(正常路径只追加,维护入口例外见 ADR-0056 §4)、gl_voucher_entries(已审核 / 已过账 / 机制凭证不可改,仅草稿保存时 DELETE 后重插,gl.sql:138);派生 / 工作层——FIFO 层账可重放重建(costing.sql:565-858 耗层 UPDATE、反审 DELETE)、approval_records 为可变签发表(approvalbook.sql:11,39,反审删行;痕迹归 audit_logs,「只要有记录就行」)
inventory_ledgergl_voucher_entries(草稿可重写)fifo_in_layers(派生)fifo_out_layers(派生)approval_records(签发表)
▼ 派生 / 重放 / 期间推进
③ 余额快照层 Snapshots(可由流水重放)
热点行锁(单据头 FOR UPDATE;库存扣减为原子条件 UPDATE)· 关账后期间冻结 · 草稿乐观锁 version 与商城通用下单幂等键尚未落地(SQL 中无 version 条件;ADR-0047 将幂等键排除在范围外,仅有支付回调 / 履约的局部幂等短路;已立 issue #414 / #415)
inventory客户 / 供应商余额gl_balancescost_periods
租户绝缘:四道防线
1写法:每条业务 SQL 手写 WHERE tenant_id,禁止 RLS 兜底
2静态:make lint-tenant 扫 Go 内 SQL 与 queries/*.sql
3运行时:guardedTx 值级注入 + TENANT_SQL_GUARD=strict
4请求:mw.Tenant 每请求校验;JWT.tenant_id 为权威
!零租户硬编码:差异走 sysconfig 参数 / 角色;非标走 ext_attrs(JSONB) / customfield;一等列准入四问
缓存边界(ADR-0038 / SSOT §1.1)

Redis 使用面见 ADR-0038:rbac / notifmgr / master(13 个实体)/ sysconfig / customfield / platform,以及 dashboard、auth 的辅助缓存,不止租户配置 / 权限树 / 编号序列(编号走 DB 的 billno,不走 Redis)。SSOT 纪律已于 2026-10-05 订正为「业务决策路径(库存扣减、信用、结算、成本、余额)严禁读缓存;只读展示聚合允许 ≤60s 短 TTL」,dashboard 的 60s KPI 缓存(dashboard/cache.go:40-49)据此合规并已补登 ADR-0038;库存 / 金额明细不缓存。平台写的数据业务不缓存,业务缓存的数据平台不写 → 无需跨进程失效。

schema → 迁移 → 查询 → 验证:声明式工具链
flowchart LR
  S["db/schema/*.sql
21 份 · Atlas SSOT(ADR-0024)"] -- "make atlas-diff name=x" --> M["db/migrations
192 份(以目录为准)· timestamp 自动命名"] M -- "make atlas-apply" --> PG[("本地 Docker PG
localhost:15432 · tlk_saas")] S -. "make atlas-drift
守卫脚本:schema ↔ migrations(ADR-0024 意图为 CI;仓库内未找到 workflow)" .- M Q["internal/<module>/queries/*.sql
61 包 · 手写 tenant_id"] -- "make sqlc" --> G["sqlcgen/
类型安全 Queries(.gitignore)"] G --> SV["Service
经 *sqlc.Queries 或域内 Repository
禁止通用泛型 Repository"] Q -- "make sql-verify
全部查询在真实 PG 上 PREPARE" --> PG Q -- "make lint-tenant" --> LT["静态租户检查"] SV --> PG classDef guard fill:#fef3c7,stroke:#d97706; class SV guard

新系统零存储过程 / 触发器(ADR-0023),业务逻辑一律在 Go service + sqlc;旧系统 166 个存过只作考古。ERD 见 docs/DATABASE_ERD.md(域数以其 §1 为准,当前 19 个活跃域;改关系列须同步)。

T5事务与异步:一单终审 = 一个事务

以「销售出库 2222 终审」为例(顺序已对照 salesout/service.go 与 approval_posting.go 核实;守卫读走池见观察页)
sequenceDiagram
  autonumber
  participant H as api/erp handler
  participant S as salesout
  participant C as costing
  participant I as inventory
  participant A as approvalbook
  participant F as finance
  participant P as payment
  participant DB as PostgreSQL tx
  H->>S: Approve(level)
  S->>DB: TenantBegin · 锁单头 FOR UPDATE
  S->>S: 守卫:期间 / 信用 / 专款余额(读走池,不在事务快照内)
  S->>C: Outbound(tx, bill) → 逐行成本;回读成本
  S->>I: ApplyMovement(tx, SignedQty<0, SignedAmount)
  I->>DB: 负库存策略 · 条件扣减 · upsert 余额 · 追加台账
  S->>DB: 序列号消费 · order.ApplyFulfillment 回写履行量 · 价格历史
  S->>A: book.Approve 签字(ctx tx)
  S->>DB: UpdateApprovalStatus · syncTotalCostAmount · billchain.LinkBills
  S->>P: 商城路径:CreatePaymentTx 建 2323(ERP 手工出库无此步)
  S->>F: CreateReceivableTx(tx) 应收(有客户 · 金额>0 · basis≠invoice)
  S->>P: AutoSettleForBillTx(tx) 按已有 2323 自动核销
  S->>DB: Commit
  S-->>H: 提交后:操作日志 · settled hooks · 押金凭证 · approvedHooks · NATS 通知
  Note over S,P: 仅商城 2323 路径另有 AfterCommitNotify → gl.TryAutoGenerate;普通 2222 无 push,凭证走 pull
          
同步(核心链路)
  • Unit of Work:调用方开 tx,显式贯穿下游,同 Commit / Rollback
  • 跨模块联动严禁跨事务;任一步失败整单回滚
  • 央批 HookRegistry 已于 2026-09-06 删除,终审联动由模块在事务内直接调用;但模块内仍有 approvedHooks / cancelledHooks 本地钩子(commit 后 best-effort,如 distribution 佣金),遗漏不会被框架发现
异步(旁路)
  • NATS 主题 tlk.{tenantID}.{eventType} → notifmgr 消费 → notifications 表 + SSE
  • at-most-once,不上 JetStream / Outbox(需要时另立 ADR)
  • pkg/database/after_commit.go:提交后副作用登记延后执行
定时任务(cmd/server/main.go)
  • 每日:标记过期订阅
  • 每 5 分钟:取消超时未支付商城订单(mall PRD v0.34)

T6质量护栏:从 PRD 到真实 PG

PRD 先行 · internal/<module>/PRD.md(模板 PRD_TEMPLATE.md):先改 PRD 再改码;PRD 为唯一事实来源;须写清约束与边界
ADR / specs · docs/adr 57 份;跨模块方案 docs/specs;冲突以 PRD / ADR 为准
单元 / 业务规则 · *_test.go 与 tests/bizrules(pgxmock,db:nil)
模块联动 · *_integration_test.go(internal/testsim 仅为演示 fixture,不属正确性证据)
真实 PG 行为测试(少数先例) · 当前十余个文件(含 localpg 系列与 sql_verify),例如 inventory movement、salesout / order 联动、audit、pricing;costing fifo_replay 是性能基准;环境变量 DSN 在才跑
make sql-verify · 全部 sqlc 查询 PREPARE(首次抓出 1839 条里 15 条坏查询)
make lint-tenant · atlas-drift · lint
审查矩阵 · tlk-code-review matrix.md(逐行 PASS/FAIL)· tlk-module-acceptance 三源对照 · audit/AUDIT_ROLLUP.md 进度单一事实源
测试策略正在转向(ADR-0057,Proposed):pgxmock 只测 SQL 文本匹配,锁不住 SQL 语义 / 并发锁 / 租户绑定 / 约束 / 事务原子性。拟新增 integration build-tag 的真实 PG 层作终审,pgxmock 退回纯逻辑层。ADR-0057 记录时点 costing 为 40 个测试文件 / 510 条 mock 期望行;当前扫描为 43 个文件 / 429 条 ExpectQuery+ExpectExec。
日落四清(功能裁撤纪律)

清路由 → 清权限种子 → 清 sysconfig 参数 → 清文档断言(写入 BUSINESS_DOMAIN §9 Non-Goals)。

深模块改造工作流(SSOT §6)

识别浅接口 → Design It Twice → PRD / ADR 补齐 → 在新 interface 上写行为测试(替换而非叠加)→ 切断调用方泄露 → tlk-code-review。

下一站
业务架构
同一套系统,从业务视角怎么流转
桥
业务×技术对照
每个业务域落到哪个模块、哪张表
回到
首页
全部架构图入口