25 KiB
Executable File
25 KiB
Executable File
PayGateway 架构设计(最终版)
目标:高性能、高稳定、资金零差错、支付方式可插拔的支付网关 通道底座:gopay v1.5.119(9 种通道现成:支付宝/微信/抖音/QQ/通联/拉卡拉/PayPal/Apple/扫呗) 未来扩展:链上支付(crypto)→ 预留扩展位,不现在实现
1. 核心思想:三层各司其职,不重复造轮子
┌────────────────────────────────────────────────────────────┐
│ ① 协议层(对外接口,永不改变) │
│ epay: /submit.php /api.php ← New API 等易支付系 │
│ REST: /v1/orders ← 未来其他项目(预留) │
│ type=alipay|wxpay|paypal|usdt → 路由到对应通道 │
├────────────────────────────────────────────────────────────┤
│ ② 编排层(资金安全核心,与具体通道无关) │
│ 订单状态机 / 金额校验 / 幂等 / 可靠回调(outbox) / 对账 │
│ → 只认统一接口 PaymentChannel,不认任何 SDK 细节 │
├────────────────────────────────────────────────────────────┤
│ ③ 通道层(可插拔,每个通道一个包) │
│ ┌─────────────── gopay 库(现成 9 通道)──────────────┐ │
│ │ alipay / wechat / paypal / douyin / qq / allinpay │ │
│ │ lakala / saobei / apple │ │
│ └────────────────────────────────────────────────────┘ │
│ crypto/(★ 预留目录:未来链上支付,接口已留好) │
└────────────────────────────────────────────────────────────┘
职责划分:
- gopay = 通道适配器工厂(把 9 种支付 SDK 的差异封装掉)
- paygateway = 协议翻译 + 资金安全编排(不重复实现支付 SDK,只做编排)
- crypto = 未来加一个目录,接口现成
2. 通道抽象(接口 + 注册表)
2.1 统一接口
package channel
// PaymentChannel 所有支付通道必须实现的统一接口
type PaymentChannel interface {
// Name 通道标识:alipay / wechat / paypal / usdt ...
Name() string
// Kind 通道类型:回调驱动 or 监听驱动(链上)
Kind() ChannelKind // CallbackKind / ListenerKind
// Modes 支持的支付模式
Modes() []PayMode // Page / Wap / QRCode / Address
// CreatePayment 下单:返回支付凭据(跳转链接/二维码/链上地址)
CreatePayment(ctx context.Context, req *PayRequest) (*PayCredential, error)
// VerifyNotify 验签回调(CallbackKind 用)
VerifyNotify(ctx context.Context, raw []byte) (*NotifyResult, error)
// QueryOrder 主动查单(对账 + 补偿兜底)
QueryOrder(ctx context.Context, outTradeNo string) (*OrderStatus, error)
// Listener 事件监听(ListenerKind 必须实现;CallbackKind 返回 nil)
Listener() EventListener
}
type PayRequest struct {
OutTradeNo string // 网关订单号
Subject string // 商品名
AmountMinor int64 // 金额(最小单位,法币=分,链上=wei/satoshi)
Currency string // CNY / USD / USDT
NotifyURL string
ReturnURL string
PayMode PayMode // 用户选择的支付模式
}
type PayCredential struct {
PayURL string // 跳转链接
QRCode string // 二维码内容
Address string // 链上收款地址(未来)
ExpireAt time.Time
}
type NotifyResult struct {
Valid bool // 验签是否通过
ChannelTxID string // 通道交易号(去重依据)
Status string // SUCCESS / CLOSED / FAILED
AmountMinor int64 // 实付金额(必须与订单比对!)
}
2.2 注册表(配置驱动,零代码路由)
// 注册:每个通道 init() 或显式调用
func Register(c PaymentChannel) { registry[c.Name()] = c }
// 路由:协议层只写一行
ch, ok := channel.Get(c.Query("type")) // "alipay" → 支付宝通道
if !ok { c.String(400, "不支持的支付方式") }
新增一种支付方式 = 3 步:
internal/channel/wechat/wechat.go— 实现 4 个方法(内部调 gopay 的微信 SDK)Register(...)一行注册- 配置
.env加微信的密钥
→ 协议层、订单核心、存储、页面 零改动。
2.3 crypto 预留位(未来链上)
// internal/channel/crypto/ 目前只放:
// README.md — 设计说明(HD钱包/确认数/重组处理)
// stub.go — 返回 "未启用" 的占位实现(注册表可查但拒绝下单)
// 未来实现时:
// Kind() = ListenerKind
// Listener() = 区块监听器(WebSocket + 轮询兜底)
// CreatePayment() = HD 钱包派生一次性地址
// QueryOrder() = 查链上确认数
注册表已经支持 ListenerKind 通道,未来 crypto 落地 = 填实现,不动架构。
3. 资金安全编排层(最不能错的部分)
下单: 协议层 → order.Create() → ① 事务写订单(CREATED) → ② 调通道
→ ③ 事务更新支付凭据 → ④ 返回页面
支付: 通道事件(回调/监听)
→ ① 验签 → ② 金额比对(实付==订单,不等→拒绝+告警)
→ ③ 幂等检查(channel_tx_id 唯一,重复→直接应答成功)
→ ④ 条件更新状态(WHERE status='WAIT_PAY',失败→拒绝)
→ ⑤ 写 payment_events 流水 → ⑥ 写 outbox 回调任务
结算: outbox worker → 回调接入方(失败重试 1m/5m/15m/1h → dead 告警)
定时补偿:PAID 且未通知 > 30s → 补发
定时对账:通道实付 vs 订单金额
5 条铁律(写入代码评审清单):
- 金额只用最小单位 int64,永不用 float
- 状态变更必须
WHERE status=当前态(防回跳/防覆盖) - 实付金额 ≠ 订单金额 → 拒绝 + 告警
- 同一 channel_tx_id 重复事件 → 幂等返回成功
- 每笔资金事件落 payment_events,可追溯
4. 数据库设计(资金安全地基,最关键的组件)
4.1 选型决策
| 阶段 | 数据库 | 理由 |
|---|---|---|
| P1 开发/单机生产 | SQLite(modernc.org/sqlite 纯 Go,无 CGO) |
本机已有;单文件零运维;事务/行锁/唯一约束完整;交易量几百 TPS 足够 |
| 正式生产/多机 | PostgreSQL | 用户已声明基础设施有;行级锁 + 强一致 + 可扩展 |
关键:
Store接口抽象,SQLite 与 PG 实现可无缝切换。P1 直接上 SQLite,不做 JSON 过渡 —— 资金数据从一开始就落在真数据库里。
4.2 数据模型(SQLite 版,PG 同构)
-- 订单表:唯一约束防重复下单,条件更新防覆盖
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
out_trade_no TEXT NOT NULL UNIQUE, -- 接入方订单号(幂等依据)
gateway_no TEXT NOT NULL UNIQUE, -- 网关订单号
channel TEXT NOT NULL, -- alipay/wechat/paypal/usdt
channel_tx_id TEXT, -- 通道交易号(回调去重)
amount_minor INTEGER NOT NULL, -- 金额(最小单位:分)
currency TEXT NOT NULL DEFAULT 'CNY',
status TEXT NOT NULL, -- CREATED/WAIT_PAY/PAID/...
payer TEXT, -- 付款方标识
pay_url TEXT, -- 支付链接/二维码/链上地址
return_url TEXT,
notify_url TEXT,
created_at TEXT NOT NULL,
paid_at TEXT,
notified_at TEXT,
UNIQUE (channel, channel_tx_id) -- 防回调重放
);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_created ON orders(created_at);
CREATE INDEX idx_orders_channel ON orders(channel);
-- 交易流水:资金审计,只增不改
CREATE TABLE payment_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
event_type TEXT NOT NULL, -- created/paid/closed/failed/refunded
channel_tx_id TEXT,
amount_minor INTEGER NOT NULL,
raw_payload TEXT, -- 原始回调/区块数据 JSON
created_at TEXT NOT NULL
);
CREATE INDEX idx_events_order ON payment_events(order_id);
-- 可靠回调投递(Outbox)
CREATE TABLE notify_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
payload TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending', -- pending/success/dead
retry_count INTEGER NOT NULL DEFAULT 0,
next_retry_at TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX idx_notify_status ON notify_tasks(status, next_retry_at);
-- 回调投递日志
CREATE TABLE callback_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
http_status INTEGER,
response TEXT,
latency_ms INTEGER,
created_at TEXT NOT NULL
);
-- 应用日志(管理界面查询用,与文件日志双写)
CREATE TABLE app_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
level TEXT NOT NULL, -- debug/info/warn/error
scope TEXT, -- 模块:order/channel/notify/scheduler
message TEXT NOT NULL,
trace_id TEXT, -- 请求追踪 ID
fields TEXT, -- 结构化字段 JSON
created_at TEXT NOT NULL
);
CREATE INDEX idx_logs_level_time ON app_logs(level, created_at);
CREATE INDEX idx_logs_scope_time ON app_logs(scope, created_at);
CREATE INDEX idx_logs_trace ON app_logs(trace_id);
4.3 资金安全约束(写库必须遵守)
// 状态变更必须条件更新(防回跳/防覆盖)
UPDATE orders SET status='PAID', paid_at=?, channel_tx_id=?
WHERE out_trade_no=? AND status='WAIT_PAY'
// 影响行数 = 0 → 状态非法,拒绝(幂等/防覆盖双保险)
// 金额校验
实付金额(amount_minor) == 订单金额 → 才允许 PAID
不等 → 记录 payment_events(event_type='amount_mismatch') + 告警 + 拒绝
// 回调去重
INSERT INTO orders(channel, channel_tx_id) ... ON CONFLICT DO NOTHING
5. 交易不可篡改设计(资金审计账本)
目标:每一笔交易都不可篡改 —— 任何人(包括管理员/DBA)改任何一条记录都能被检测。 方案:哈希链 + 数字签名 + 只增不改 + 每日快照锚定(企业级审计账本标准做法)。
5.1 哈希链(账本防篡改核心)
genesis(固定) → entry1 → entry2 → entry3 → ... → entryN
↑ ↑ ↑
prev_hash prev_hash prev_hash ← 每条都链接前一条
entry_hash(i) = SHA256( prev_hash(i-1) + payload_hash(i) )
payload_hash(i) = SHA256( 事件全部内容: 订单号+金额+状态+通道交易号+时间+原始数据 )
篡改任意一条 → 它后面的所有 entry_hash 全部失配 → 校验必失败。 数据库整体被替换 → 与每日快照锚点比对 → 发现。
5.2 数字签名(防伪造 + 来源验证)
- 独立审计密钥对(与支付签名密钥物理分离),私钥存 env/密钥文件
- 每条 entry 的
entry_hash用私钥 ECDSA 签名 - 任何人想改数据必须重新签名 → 没有私钥就无法伪造合法记录
- 验签失败 = 数据被改过或来源可疑 → 立即告警
5.3 数据库层硬约束(SQLite/PG 双实现)
-- 账本表:只追加,禁止修改删除(SQLite 触发器强制)
CREATE TABLE audit_ledger (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prev_hash TEXT NOT NULL, -- 前一哈希(链)
payload_hash TEXT NOT NULL, -- 本条事件内容哈希
entry_hash TEXT NOT NULL, -- SHA256(prev+payload)
signature TEXT NOT NULL, -- ECDSA(entry_hash)
event_id INTEGER, -- 关联 payment_events.id
created_at TEXT NOT NULL
);
-- 禁止 UPDATE / DELETE(数据库层防篡改)
CREATE TRIGGER trg_ledger_no_update BEFORE UPDATE ON audit_ledger
BEGIN SELECT RAISE(ABORT, 'audit_ledger is append-only'); END;
CREATE TRIGGER trg_ledger_no_delete BEFORE DELETE ON audit_ledger
BEGIN SELECT RAISE(ABORT, 'audit_ledger is append-only'); END;
PG 版:DB 账号只授予 INSERT/SELECT 权限(无 UPDATE/DELETE),双保险。
5.4 每日快照(外部锚定点)
CREATE TABLE daily_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_date TEXT NOT NULL UNIQUE,
last_hash TEXT NOT NULL, -- 当日最后 entry_hash(锚点)
signature TEXT NOT NULL, -- 对锚点签名
created_at TEXT NOT NULL
);
- 每日 00:00 生成快照:当前链尾哈希 + 签名
- 可选增强:把快照哈希发布到外部(邮件/对象存储/链上)→ 防数据库整体替换
- 改任何历史数据 → 与快照锚点比对即发现
5.5 完整性校验任务(scheduler 每日 + 手动)
VerifyLedger() → 从 genesis(或上一快照)重算整条哈希链
→ 校验每条 ECDSA 签名
→ 比对 daily_snapshots 锚点
→ 全过 → INFO 记录"账本完整"
→ 任何失配 → ERROR + 管理后台红色告警(重大安全事件)
5.6 资金事件写入流程(唯一入口,防绕过)
通道回调/监听 → order服务校验通过
→ ① payment_events INSERT(业务库,只增)
→ ② audit_ledger.Append(事件内容) ← 哈希链+签名(独立账本库 ledger.db)
→ ③ 账本写入失败 → 回滚订单状态 + 告警(资金事件必须有账本记录)
所有资金变更只能走这一个入口,不允许其他代码直接写事件表。
注:账本在独立数据库中,无法与业务库同事务;因此采用"业务库先记事件 → 账本库追加 → 失败则回滚订单状态并告警"的顺序,确保资金事件必有账本记录。
5.7 管理后台联动
| 功能 | 说明 |
|---|---|
| 账本状态 | 显示最后校验时间/结果/链长 |
| 手动校验 | 按钮触发全链校验 |
| 篡改告警 | 校验失败 → 顶部红色横幅 + 告警日志 |
| 事件详情 | 每条事件可看 payload_hash / signature / 原始数据 |
5.8 独立账本数据库(ledger.db,安全隔离)
架构升级:账本从业务库中独立出来,单独一个 SQLite 文件,与订单/日志/回调完全隔离。
paygateway.db(业务库) ledger.db(账本库 🛡️)
├── orders ├── audit_ledger
├── payment_events └── daily_snapshots
├── notify_tasks
├── callback_logs
├── app_logs
└── settings
隔离带来的安全提升:
| 防护层 | 机制 | 效果 |
|---|---|---|
| 文件隔离 | 账本独立 ledger.db |
删业务库不影响账本,审计线索永存 |
| 文件权限 | chmod 0600 |
仅文件所有者可读写,其他用户/进程无法访问 |
| 触发器 | 两个表均有 UPDATE/DELETE 触发器 | 通过 SQL 无法修改/删除任何账本数据 |
| WAL 模式 | PRAGMA journal_mode=WAL |
崩溃安全,写入不丢数据 |
| FULL 同步 | PRAGMA synchronous=FULL |
每次写入等待落盘,防断电丢数据 |
| 连接隔离 | SetMaxOpenConns(1) |
单连接串行写,避免并发写入冲突 |
实现方式:
// store.LedgerDB 独立账本存储(internal/store/ledgerdb.go)
ledgerDB, err := store.NewLedgerDB("ledger.db") // 0600 + WAL + 触发器
lg, err := ledger.New(ledgerDB, cfg.LedgerPrivKey) // 账本只依赖独立库
// admin 同时注入业务库 + 账本库
adminSrv := admin.New(st, ledgerDB, ...)
5.9 管理后台 API 与前端
账本相关 API:
| 端点 | 说明 |
|---|---|
GET /api/ledger |
账本条目列表(哈希链可视化) |
POST /api/ledger/verify |
手动触发全链校验(哈希链 + 签名 + 快照锚点) |
GET /api/ledger/snapshots |
每日快照列表 |
POST /api/ledger/snapshot |
手动创建当日快照 |
GET /api/ledger/public-key |
返回 ECDSA P256 公钥(供第三方验证) |
GET /api/ledger/export |
导出账本 + 快照为 CSV(审计报告) |
前端功能(Vue3 Ledger 页面):
- 📋 账本条目:哈希链可视化(每条含 prev_hash / entry_hash / 签名)
- 🔍 校验账本:一键全链校验,弹窗展示结果(通过/失败 + 错误明细 + 快照锚点状态)
- 📸 快照管理:卡片列表 + 详情弹窗 + 锚点核对(对比快照链尾与当前链尾)
- 🔑 账本公钥:展示 PEM 公钥,可复制供外部审计验证
- ⬇️ 导出 CSV:一键下载完整审计报告(含所有条目 + 快照)
- 🔗 事件跳转:点击事件 ID 跳转订单页搜索
测试覆盖(internal/ledger/ledger_test.go):
| 测试 | 验证点 |
|---|---|
| TestAppendAndVerify | 追加 + 全链校验通过 |
| TestHashChainLinked | 哈希链正确连接 |
| TestTamperDetected | 篡改条目 → 校验失败 |
| TestTamperSignatureDetected | 篡改内容 → 签名校验失败 |
| TestSnapshotCreateAndVerify | 快照创建 + 锚点匹配 |
| TestSnapshotAnchorMismatch | 数据库替换 → 锚点不匹配 |
| TestSignatureUnique | 防重放(同内容不同事件不同签名) |
6. 日志系统设计(健壮 + 可查询)
5.1 双写架构:文件(排障)+ 数据库(查询)
应用 → slog(结构化) → ① 文件轮转日志(/var/log/paygateway/*.log)
→ ② 异步批量写 app_logs 表(管理界面查询)
5.2 技术选型
| 项 | 方案 |
|---|---|
| 日志库 | Go 标准库 slog(结构化,JSON 输出) |
| 轮转 | gopkg.in/natefinch/lumberjack.v2(按大小/天数切割,防磁盘爆) |
| 写库 | 异步批量(攒 50 条或 1s flush),避免日志拖慢交易主链路 |
| 级别 | debug / info / warn / error(env 可配) |
| 追踪 | 每个 HTTP 请求生成 trace_id(中间件),贯穿 下单→回调→通知 全链路 |
5.3 关键审计日志(钱相关的必须全记)
// 资金事件:writeEvent(orderID, "paid", amountMinor, rawPayload)
// - created / paid / closed / failed / amount_mismatch / refunded
// 回调事件:writeEvent(orderID, "notify_attempt", ..., httpStatus)
// 安全事件:writeEvent(orderID, "verify_failed", ..., 原因) ← 验签失败必须告警
7. Web 管理后台设计
6.1 形态:同进程嵌入式(单二进制部署)
- 同一 Go 进程,路由组
/admin/*(端口可独立配置ADMIN_ADDR) - 内置 HTML/JS/CSS(
embed打包),无前端构建链 - 登录鉴权:管理 token(
ADMIN_TOKENenv)+ 可选 Basic Auth;生产必须启用 - 接口全部走
/admin/api/*,返回 JSON
6.2 功能清单
| 页面 | 功能 |
|---|---|
| 仪表盘 | 今日订单量/交易额/成功率/待通知数/告警数(卡片 + 简易图表) |
| 订单管理 | 列表(状态/通道/金额/时间筛选,分页)→ 详情(状态机时间线 + 支付凭据 + 原始回调) |
| 交易流水 | payment_events 查询(按订单/事件类型/时间) |
| 日志查看 | app_logs 查询(级别/模块/trace_id 过滤,实时 tail) |
| 回调管理 | notify_tasks 列表;手动补发按钮(dead 任务重投);重试状态 |
| 通道管理 | 已注册通道列表 + 启用/停用开关(配置驱动) |
| 对账报告 | 定时对账结果(订单 vs 通道实付) |
| 账本校验 | 哈希链完整性校验 + 篡改告警(红色横幅) |
| 系统状态 | 运行时长/goroutine/内存/最近错误 |
6.3 页面路由规划
GET /admin/login # 登录页
GET /admin/ # 仪表盘(SPA,JS 拉 /admin/api/*)
GET /admin/orders # 订单页
GET /admin/logs # 日志页
GET /admin/notify # 回调页
GET /admin/api/dashboard # JSON API
GET /admin/api/orders?status=&channel=&page=
GET /admin/api/orders/:no # 订单详情(含事件流)
GET /admin/api/logs?level=&scope=&trace=&page=
GET /admin/api/notify/tasks?status=
POST /admin/api/notify/:id/retry # 手动补发
GET /admin/api/channels # 通道状态
GET /admin/api/health # 系统状态
8. 目录结构(最终目标态)
paygateway/
├── cmd/gateway/main.go # 入口:组装 config/store/registry/router
├── internal/
│ ├── config/ # .env 加载 + 通道密钥管理
│ ├── model/ # Order / 状态常量 / 金额类型
│ ├── logger/ # slog 封装:文件轮转 + 异步写库 + trace_id
│ ├── epay/ # 易支付协议(submit/api/notify 签名)
│ ├── rest/ # REST API(预留)
│ ├── order/ # 编排层:状态机/幂等/金额校验/对账
│ ├── channel/
│ │ ├── registry.go # 注册表 + Get()
│ │ ├── interface.go # PaymentChannel / PayRequest / ...
│ │ ├── alipay/ # 支付宝(gopay)——现有代码迁入
│ │ ├── wechat/ # 微信(gopay)——未来
│ │ ├── paypal/ # PayPal(gopay)——未来
│ │ └── crypto/ # ★ 预留:stub + README(未来链上)
│ ├── store/ # Store 接口 + sqlite(默认)/pg 实现
│ │ ├── store.go # Store 接口 + 数据模型
│ │ ├── sqlite.go # 业务库 SQLite 实现(订单/事件/日志)
│ │ ├── sqlite_rest.go # 业务库查询实现
│ │ └── ledgerdb.go # ★ 独立账本库(ledger.db,0600 权限 + 触发器)
│ ├── notify/ # outbox worker + 重试
│ ├── ledger/ # ★ 资金审计账本(哈希链+签名+校验)
│ ├── scheduler/ # 关单/补偿/对账/账本校验定时任务
│ ├── admin/ # ★ 管理后台:路由 + 页面(embed) + API
│ ├── web/ # 二维码页/中转页渲染
│ └── metrics/ # Prometheus(预留)
├── migrations/ # SQL 迁移(sqlite/pg)
├── docs/ARCHITECTURE.md # 本文档
└── go.mod
9. 实施计划(保持现有功能不回归)
| 阶段 | 内容 | 验收 |
|---|---|---|
| P1 基础重建 | 目录分层 + Store 接口 + SQLite 落地 + 支付宝迁入 channel/alipay + 日志系统(文件+DB) + audit_ledger 哈希链 |
现有沙箱链路回归 ✅;订单/日志/账本入库 ✅ |
| P2 管理后台 | /admin 页面(仪表盘/订单/日志/回调)+ API + token 鉴权 |
浏览器可查订单、日志、交易流水 |
| P3 资金安全 | 状态机条件更新 + 金额校验 + 幂等 + outbox 回调重试 + 账本完整性校验任务 + 独立账本库 ledger.db + 账本管理前端 | 模拟重复/篡改回调不产生错误;篡改一条记录 → 校验失败 ✅;账本独立库 + 0600 权限 + 触发器 ✅;前端校验/快照/公钥/导出 ✅ |
| P4 通道扩展 | 用 gopay 接微信(验证"3 步加通道"流程) | 微信沙箱链路跑通 |
| P5 crypto 预留 | crypto stub + README + 注册表支持 ListenerKind | type=usdt 返回"未启用" |
| P6 生产化 | 按你们基础设施切 PG + 监控 + 对账 + 部署脚本 | 生产验收 |
10. 为什么这是最佳实践
- 不重复造轮子:9 种支付 SDK 由 gopay 维护,paygateway 只做编排 → 通道越多,收益越大
- 加支付方式像插 U 盘:新通道 = 新包 + 注册 + 配置,核心零改动
- 协议层稳定:易支付/REST 接口永不变 → 现有上游(New API)不受影响
- 资金安全集中:所有通道都过同一套状态机/金额校验/幂等 → 只维护一套安全逻辑
- 链上预留:接口已含 ListenerKind + Address 凭据 → 未来 crypto 落地不动架构
- 存储可换:SQLite(单机起步) → PG(生产),
Store接口平滑切换 - 可观测性内置:日志双写(文件+DB)+ 管理后台 → 出问题能查、能补、能对账
- 交易不可篡改:哈希链 + 签名 + 只增不改 + 每日快照 → 任何篡改必被发现