版本: v1.5.0
适用对象: 后端开发工程师、架构师、系统分析师
密级: Internal
本章节详细描述了开发过程中必须完成的核心任务,以及对应的代码级落地策略。
目标: 实现一个支持高并发、幂等性的转账接口。
难点: 防止“超卖”(余额扣成负数)、防止重复扣款。
- 原则: 必须使用 BIGINT 存储金额(单位:分/Wei)。禁止 FLOAT。
- 表结构: 参见 deploy/postgres/init.sql。
文件: services/ledger/usecase/transfer.go
开发者必须实现以下 ExecuteTransfer 逻辑:
// 伪代码展示核心防御逻辑
func (uc *UseCase) ExecuteTransfer(ctx context.Context, req TransferRequest) error {
// 1. 【防御】参数校验:金额必须大于0
if req.Amount <= 0 { return ErrInvalidAmount }
return uc.db.Transaction(func(tx \*gorm.DB) error {
// 2\. 【防御】幂等性检查 (Idempotency)
// 利用 reference\_id 的唯一索引冲突来快速失败
// 如果数据库报错 "duplicate key value violates unique constraint",则直接返回成功(视作已处理)或错误
if err := tx.Create(\&Transaction{RefID: req.RefID, ...}).Error; err \!= nil {
return HandleDuplicateError(err)
}
// 3\. 【核心】扣减发送方 (带乐观锁版本号控制)
// SQL: UPDATE accounts SET balance \= balance \- ?, version \= version \+ 1
// WHERE id \= ? AND balance \>= ? AND version \= ?
result := tx.Model(\&Account{}).
Where("id \= ? AND balance \>= ? AND version \= ?", req.FromID, req.Amount, req.FromAccountVersion).
Updates(map\[string\]interface{}{
"balance": gorm.Expr("balance \- ?", req.Amount),
"version": gorm.Expr("version \+ 1"),
})
if result.RowsAffected \== 0 {
// 重点:可能是余额不足,也可能是版本号变了(并发冲突)
return ErrConcurrentConflictOrInsufficientFunds
}
// 4\. 【核心】增加接收方
// 接收方通常不需要强版本控制,除非它是透支账户
if err := tx.Model(\&Account{}).Where("id \= ?", req.ToID).
Update("balance", gorm.Expr("balance \+ ?", req.Amount)).Error; err \!= nil {
return err
}
// 5\. 【合规】写入分录日志 (Double Entry)
// 必须严格写入一借一贷两条记录,用于日后审计
tx.Create(\&Entry{AccountID: req.FromID, Direction: "DEBIT", Amount: req.Amount})
tx.Create(\&Entry{AccountID: req.ToID, Direction: "CREDIT", Amount: req.Amount})
return nil
})
}
目标: 监听以太坊链上转账,充值到用户余额,但必须防止区块链分叉(Reorg)。
难点: 链上数据不可信,只有经过 N 个区块确认后的数据才可信。
充值订单 (DepositOrder) 必须包含以下状态流转:
- DETECTED: 扫描到链上交易,但在“危险区”(最新 6 个区块内)。
- CONFIRMED: 交易深度超过 6-12 个区块,视为安全。
- PROCESSED: 核心账本已成功入账。
文件: services/indexer/worker/eth_scanner.go
const SafeBlockDepth = 12 // 定义安全深度
func (w *Worker) ProcessBlocks() {
// 获取链上最新高度
latestHeight := w.ethClient.GetBlockNumber()
// 计算安全高度 (只处理这个高度之前的区块)
safeHeight := latestHeight - SafeBlockDepth
// 获取本地数据库已处理到的高度
dbHeight := w.repo.GetLastScannedHeight()
for height := dbHeight \+ 1; height \<= safeHeight; height++ {
block := w.ethClient.GetBlock(height)
for \_, tx := range block.Transactions {
if isTransferToHotWallet(tx) {
// 1\. 幂等性写入充值记录表
// INSERT IGNORE INTO deposits (tx\_hash, status) VALUES (?, 'CONFIRMED')
err := w.repo.SaveDeposit(tx.Hash, tx.Value, StatusConfirmed)
if err \== nil {
// 2\. 触发账本入账 (RPC 调用 Core Ledger)
// 借: HotWalletAsset, 贷: UserLiability
w.ledgerClient.Deposit(tx.From, tx.Value)
}
}
}
// 更新游标
w.repo.UpdateLastScannedHeight(height)
}
}
目标: 允许财务人员输入“查询上周亏损最高的用户”,系统自动输出结果。
难点: 防止 AI 产生恶意的 SQL(如 DROP TABLE)或泄露敏感数据。
- 物理隔离: AI 服务连接数据库时,必须使用 Read-Only (只读) 账号。
- Schema 注入: 将精简版的 DB Schema(仅表名、字段名)注入 Prompt,不要注入真实数据。
文件: services/ai/agent.py
def text_to_sql_safe(user_query):
# 1. 权限控制:检查用户是否有权查看财务数据
if not check_permission(user_context):
raise ForbiddenError()
\# 2\. Prompt 构建 (System Prompt)
schema\_info \= "Table accounts (id, name, balance); Table entries (amount, created\_at, direction)"
prompt \= f"""
Role: PostgreSQL Expert.
Task: Convert text to SQL.
Schema: {schema\_info}
Constraints:
\- ONLY SELECT statements allowed.
\- LIMIT 10 by default if not specified.
Input: {user\_query}
"""
\# 3\. LLM 生成
raw\_sql \= llm.generate(prompt)
\# 4\. 【关键防御】代码级 SQL 审计
forbidden\_keywords \= \['DELETE', 'UPDATE', 'INSERT', 'DROP', 'ALTER', 'TRUNCATE', 'GRANT', 'EXEC'\]
if any(word in raw\_sql.upper() for word in forbidden\_keywords):
log\_security\_alert(user\_query)
return "Error: Query violates security policy."
\# 5\. 执行 (使用只读连接)
with db\_readonly\_engine.connect() as conn:
return conn.execute(text(raw\_sql)).fetchall()
在本项目中,Schema 即法律。所有约束必须在 DB 层定义,不能只依赖代码。
CREATE TABLE accounts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL,
currency VARCHAR(10) NOT NULL, -- USD, BTC, ETH
\-- 【重点】
\-- balance 是 entries 的聚合快照。
\-- 任何时候,balance 都必须等于 sum(entries where account\_id \= id)
\-- 我们通过应用层事务保证这一致性
balance BIGINT NOT NULL DEFAULT 0,
\-- 乐观锁版本号
version BIGINT NOT NULL DEFAULT 0,
type VARCHAR(20) NOT NULL CHECK (type IN ('ASSET', 'LIABILITY', 'EQUITY')),
UNIQUE(user\_id, currency, type) \-- 防止同一个用户创建两个 BTC 资产账户
);
CREATE TABLE entries (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
transaction_id UUID REFERENCES transactions(id),
account_id UUID REFERENCES accounts(id),
direction VARCHAR(10) NOT NULL CHECK (direction IN ('DEBIT', 'CREDIT')),
amount BIGINT NOT NULL CHECK (amount \> 0), \-- 只能是正数
created\_at TIMESTAMP DEFAULT CURRENT\_TIMESTAMP
);
-- 索引优化:用于计算特定账户在特定时间段的流水
CREATE INDEX idx_entries_acc_time ON entries(account_id, created_at);
当面试官或审计人员询问以下问题时,请参考以下标准答案:
Q1: 你们为什么不直接在 Redis 里扣款?那样不是更快吗?
Answer: Redis 速度虽快,但它主要保证的是 AP (可用性) 或最终一致性,且持久化机制 (AOF/RDB) 在极端宕机下可能丢数据。
作为金融系统,我们首要保证的是 CP (一致性)。我们选择 PostgreSQL 作为 Source of Truth,利用其成熟的 ACID 事务机制。Redis 仅用于做读缓存(Cache-Aside)和分布式锁,不存储核心账本数据。
Q2: 如何证明你们的系统没有“把钱算错”?
Answer: 我们采用了复式记账法。每一笔交易 (Transaction) 内部,所有 Entry 的 Debit 总和必须等于 Credit 总和。系统内部有一个异步的 Reconciler (对账机器人),它每天凌晨会扫描当天的 transactions,重新计算所有 entries 的 sum,如果发现借贷不平,立刻报警并冻结相关账户。
Q3: AI 生成 SQL 很危险,如何防止 SQL 注入?
Answer: 除了常规的参数化查询外,我们实施了最小权限原则 (Least Privilege)。AI 服务连接数据库使用的是一个专用的 readonly_user,这个数据库用户在 Postgres 层面就被剥夺了 INSERT/UPDATE/DELETE 权限。即使 AI 真的生成了恶意代码,数据库引擎也会直接拒绝执行。
所有日志必须包含 TraceID,以便跨服务追踪。
正确示例 (Golang):
logger.Info("transfer started",
zap.String("trace_id", ctx.Value("trace_id")),
zap.String("ref_id", req.RefID),
zap.Int64("amount", req.Amount),
)
错误示例:
fmt.Printf("transfer started, amount: %d", req.Amount) // 禁止使用 Printf,无法进入 ELK
文档结束。请根据此手册进行开发。