技术架构
端 → 进程 → 中间件 → 模块 → 数据 → 事务 → 护栏
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
- 两进程不互相 HTTP 调用:平台写、业务每请求从库里读(mw.Tenant 每请求校验租户状态,冻结即时生效);NATS 仅推通知,不进审核主链路(ADR-0036 / SSOT §1.1)。
- ERP 与商城共用 cmd/server 同一进程、同一套 internal/*;鉴权链、handler 分组与 JWT 端别("erp" / "mall")不同(商城组没有 ERP 的 RequireModuleAccess)。Redis / NATS 启动失败时服务带警告继续运行(无缓存 / 无限流 / 无通知)。
- 严禁拆微服务、引入 DDD 聚合 / 事件溯源 / 完整 CQRS / 通用泛型 Repository(SSOT §1.2 Non-Goals)。
T2一次请求穿过什么:中间件 → handler → module → 数据库
同样 Auth("mall") → Tenant(DB),商城用户为「租户内客户账号」(ADR-0046),登录时 platform.CheckSubscription 校验 mall SKU。
:8081 中间件更短:RequestID · RealIP · Recoverer · CORS · MaxBody(22MB) · AccessLog · RateLimit,无 Tenant 校验,使用 platform 自身登录。
T3模块拓扑:按真实 import 深度分带
带号 = 该包沿 import 链到叶子的最长距离(脚本计算),不是规范里的 L0–L4 标签;两者对照见 3c。每个 chip 是一个 internal 包。
绿底 = 高扇入横切包;虚线 = Non-Goal 保留代码(路由不挂载)。wms 位于叶子带,是因为它不 import salesout——验货出库由 cmd/server/wms_salesout_adapter.go 在 main 里注入(真实 seam)。
inventory 的 13 个依赖方 = ADR-0056 后调用 ApplyMovement 的单据模块(salesout / salesreturn / purchasein / purchasereturnout / warehouse / damage / assembly / transfer / directvendor …)+ costing。
| 层 | DESIGN_PATTERNS §7.1 | OVERVIEW §2.0.2(v0.6 订正前) |
|---|---|---|
| L0 | pkg/money · database · billno(基础设施) | platform · auth · rbac · file · seed |
| L1 | master · item · rbac · sysconfig | master · item · customfield |
| L2 | inventory · costing · finance · gl · approvalbook | pricing · promotion · inventory · costing · sysconfig · approvalbook · billchain |
| L3 | order · purchase · salesout · salesreturn · transfer | 全部单据 + payment · cashier · gl · asset · invoice |
| L4 | api/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 对齐,详见观察页。
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 治理
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;库存 / 金额明细不缓存。平台写的数据业务不缓存,业务缓存的数据平台不写 → 无需跨进程失效。
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事务与异步:一单终审 = 一个事务
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:提交后副作用登记延后执行
- 每日:标记过期订阅
- 每 5 分钟:取消超时未支付商城订单(mall PRD v0.34)
T6质量护栏:从 PRD 到真实 PG
清路由 → 清权限种子 → 清 sysconfig 参数 → 清文档断言(写入 BUSINESS_DOMAIN §9 Non-Goals)。
识别浅接口 → Design It Twice → PRD / ADR 补齐 → 在新 interface 上写行为测试(替换而非叠加)→ 切断调用方泄露 → tlk-code-review。