Skip to content

Latest commit

 

History

History
235 lines (169 loc) · 9.93 KB

File metadata and controls

235 lines (169 loc) · 9.93 KB

LedgerMind 开发实施手册 (Developer Manual)

版本: v1.5.0

适用对象: 后端开发工程师、架构师、系统分析师

密级: Internal

1. 任务拆解与实施指南 (Tasks & Implementation)

本章节详细描述了开发过程中必须完成的核心任务,以及对应的代码级落地策略

✅ 任务一:构建高并发原子记账引擎 (Core Ledger)

目标: 实现一个支持高并发、幂等性的转账接口。
难点: 防止“超卖”(余额扣成负数)、防止重复扣款。

1.1 数据库设计 (Schema)

  • 原则: 必须使用 BIGINT 存储金额(单位:分/Wei)。禁止 FLOAT。
  • 表结构: 参见 deploy/postgres/init.sql。

1.2 核心业务逻辑 (Golang 实现)

文件: 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  
})  

}

✅ 任务二:Web3 充值监听与抗回滚处理 (Chain Indexer)

目标: 监听以太坊链上转账,充值到用户余额,但必须防止区块链分叉(Reorg)。
难点: 链上数据不可信,只有经过 N 个区块确认后的数据才可信。

2.1 状态机设计

充值订单 (DepositOrder) 必须包含以下状态流转:

  • DETECTED: 扫描到链上交易,但在“危险区”(最新 6 个区块内)。
  • CONFIRMED: 交易深度超过 6-12 个区块,视为安全。
  • PROCESSED: 核心账本已成功入账。

2.2 核心业务逻辑 (Worker 实现)

文件: 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)  
}  

}

✅ 任务三:基于 LLM 的智能财务分析 (AI Analyst)

目标: 允许财务人员输入“查询上周亏损最高的用户”,系统自动输出结果。
难点: 防止 AI 产生恶意的 SQL(如 DROP TABLE)或泄露敏感数据。

3.1 深度防御架构 (Defense in Depth)

  1. 物理隔离: AI 服务连接数据库时,必须使用 Read-Only (只读) 账号。
  2. Schema 注入: 将精简版的 DB Schema(仅表名、字段名)注入 Prompt,不要注入真实数据。

3.2 核心业务逻辑 (Python/LangChain)

文件: 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()

2. 数据库详细规范 (Database Specification)

在本项目中,Schema 即法律。所有约束必须在 DB 层定义,不能只依赖代码。

2.1 核心表结构 (accounts)

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 资产账户  

);

2.2 核心表结构 (entries)

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);

3. 面试防御与架构思考 (Architecture Decisions)

当面试官或审计人员询问以下问题时,请参考以下标准答案:

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 真的生成了恶意代码,数据库引擎也会直接拒绝执行。

4. 故障排查与日志规范 (Observability)

所有日志必须包含 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

文档结束。请根据此手册进行开发。