Genex 后端开发指南
DDD + Clean Architecture + 微服务 | NestJS + Go + Kong + PostgreSQL
1. 技术栈总览
| 层级 |
技术 |
用途 |
| API网关 |
Kong |
限流、认证、路由、负载均衡 |
| 主要业务服务 |
NestJS (TypeScript) |
业务逻辑微服务 |
| 高性能服务 |
Go |
撮合引擎、链上事件监听、高频任务 |
| 翻译层 |
Go + Redis |
地址映射、Gas代付、术语翻译 |
| 数据库 |
PostgreSQL 15+ |
核心业务数据 |
| 时序数据库 |
TimescaleDB |
行情数据、监控指标 |
| 缓存 |
Redis Cluster |
热点数据、会话、订单簿缓存 |
| 消息队列 |
Kafka |
事件驱动、链上事件监听 |
| 搜索 |
Elasticsearch |
全文检索、日志分析 |
| AI/ML |
FastAPI (Python) |
信用评分、价格预测、异常检测 |
2. 架构设计:三层五域
2.1 五大业务域
| 域 |
服务名 |
语言 |
职责 |
| D1 发行域 |
issuer-service |
NestJS |
发行方入驻、券管理、发行审批 |
| D2 交易域 |
trading-service |
Go |
撮合引擎、订单簿、做市商 |
| D3 清算域 |
clearing-service |
NestJS + Go |
链上结算、兑付清算、退款 |
| D4 合规域 |
compliance-service |
NestJS |
KYC/AML、OFAC、Travel Rule |
| D5 AI域 |
ai-service |
FastAPI |
信用评分、定价、异常检测 |
| 通用 |
user-service |
NestJS |
用户注册、认证、Profile |
| 通用 |
translate-service |
Go |
UX翻译层(地址映射、Gas代付) |
| 通用 |
notification-service |
NestJS |
消息推送、邮件、短信 |
| 通用 |
chain-indexer |
Go |
链上事件监听、数据索引 |
2.2 服务间通信
┌──────────────────────────────────────────────┐
│ Kong API Gateway │
│ 限流 | OAuth 2.1 + JWT | 路由 | 负载均衡 │
├──────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ user │ │ issuer │ │ trading │ │
│ │ service │ │ service │ │ service(Go)│ │
│ └────┬────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │
│ └────────────┼──────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ Kafka │ │
│ └─────┬─────┘ │
│ ┌────────────┼───────────────┐ │
│ ┌────┴────┐ ┌─────┴──────┐ ┌─────┴─────┐ │
│ │clearing │ │compliance │ │ai-service │ │
│ │service │ │service │ │(FastAPI) │ │
│ └─────────┘ └────────────┘ └───────────┘ │
└──────────────────────────────────────────────┘
- 同步通信:gRPC(服务间直接调用,如撮合→结算)
- 异步通信:Kafka事件驱动(链上事件、通知、审计日志)
3. DDD + Clean Architecture(NestJS服务)
3.1 模块结构(以issuer-service为例)
issuer-service/
├── src/
│ ├── main.ts
│ ├── app.module.ts
│ ├── domain/ # 领域层(纯业务逻辑,零外部依赖)
│ │ ├── entities/
│ │ │ ├── issuer.entity.ts # 发行方聚合根
│ │ │ ├── coupon-template.entity.ts
│ │ │ └── credit-rating.vo.ts # 值对象
│ │ ├── repositories/
│ │ │ └── issuer.repository.ts # Repository接口
│ │ ├── services/
│ │ │ ├── issuer-domain.service.ts
│ │ │ └── credit-scoring.service.ts
│ │ └── events/
│ │ ├── issuer-approved.event.ts
│ │ └── coupon-issued.event.ts
│ ├── application/ # 应用层(用例编排)
│ │ ├── commands/
│ │ │ ├── register-issuer.command.ts
│ │ │ ├── create-coupon.command.ts
│ │ │ └── handlers/
│ │ ├── queries/
│ │ │ ├── get-issuer.query.ts
│ │ │ └── handlers/
│ │ └── dto/
│ │ ├── create-issuer.dto.ts
│ │ └── create-coupon.dto.ts
│ ├── infrastructure/ # 基础设施层(外部实现)
│ │ ├── persistence/
│ │ │ ├── issuer.repository.impl.ts
│ │ │ ├── issuer.orm-entity.ts
│ │ │ └── typeorm.config.ts
│ │ ├── messaging/
│ │ │ └── kafka-publisher.ts
│ │ └── external/
│ │ └── chain-client.ts # 链上交互
│ └── interfaces/ # 接口层(HTTP/gRPC)
│ ├── http/
│ │ ├── issuer.controller.ts
│ │ └── coupon.controller.ts
│ └── grpc/
│ └── issuer.grpc-controller.ts
├── test/
└── package.json
3.2 领域实体
// src/domain/entities/issuer.entity.ts
export class Issuer {
readonly id: string;
readonly companyName: string;
private _creditRating: CreditRating;
private _issuanceQuota: number;
private _tier: IssuerTier;
private _status: IssuerStatus;
// 信用评分四因子
updateCreditScore(metrics: CreditMetrics): void {
const score =
0.35 * metrics.redemptionRate +
0.25 * (1 - metrics.breakageRatio) +
0.20 * Math.log(metrics.marketTenure + 1) / Math.log(37) +
0.20 * metrics.userSatisfaction;
this._creditRating = CreditRating.fromScore(score);
this._issuanceQuota = this._creditRating.calculateQuota();
}
// 发券校验
canIssueCoupon(params: CreateCouponParams): Result<void> {
if (this._status !== IssuerStatus.ACTIVE)
return Result.fail('发行方状态异常');
if (params.totalValue > this.remainingQuota)
return Result.fail('超出发行额度');
if (params.expiryDays > 365)
return Result.fail('Utility Track有效期不得超过12个月');
return Result.ok();
}
}
3.3 应用层Command
// src/application/commands/handlers/create-coupon.handler.ts
@CommandHandler(CreateCouponCommand)
export class CreateCouponHandler {
constructor(
private issuerRepo: IssuerRepository,
private eventBus: EventBus,
) {}
async execute(command: CreateCouponCommand): Promise<CouponDraft> {
const issuer = await this.issuerRepo.findById(command.issuerId);
if (!issuer) throw new NotFoundException('发行方不存在');
// 领域校验
const validation = issuer.canIssueCoupon(command.params);
if (validation.isFailure) throw new BadRequestException(validation.error);
// 创建券草稿(待审核)
const draft = CouponDraft.create(issuer, command.params);
await this.issuerRepo.saveCouponDraft(draft);
// 发布领域事件
this.eventBus.publish(new CouponDraftCreatedEvent(draft));
return draft;
}
}
4. Go 高性能服务
4.1 撮合引擎
// trading-service/internal/matching/engine.go
type MatchingEngine struct {
orderBooks map[string]*OrderBook // couponId → OrderBook
mu sync.RWMutex
}
type OrderBook struct {
Bids *redblacktree.Tree // 买单:价格降序
Asks *redblacktree.Tree // 卖单:价格升序
}
func (e *MatchingEngine) SubmitOrder(order *Order) []*Trade {
e.mu.Lock()
defer e.mu.Unlock()
book := e.getOrCreateBook(order.CouponID)
// Utility Track价格校验
if order.CouponType == CouponTypeUtility && order.Price > order.FaceValue {
return nil // 拒绝溢价单
}
// 撮合:价格优先 → 时间优先
trades := book.Match(order)
// 成交后发布事件到Kafka
for _, trade := range trades {
e.publisher.Publish("trade.matched", trade)
}
return trades
}
4.2 链上事件监听
// chain-indexer/internal/indexer/indexer.go
type ChainIndexer struct {
ethClient *ethclient.Client
kafka sarama.SyncProducer
contracts map[string]common.Address
}
func (i *ChainIndexer) Start(ctx context.Context) {
// 监听Genex Chain事件
query := ethereum.FilterQuery{
Addresses: []common.Address{
i.contracts["CouponFactory"],
i.contracts["Settlement"],
i.contracts["Redemption"],
},
}
logs := make(chan types.Log)
sub, _ := i.ethClient.SubscribeFilterLogs(ctx, query, logs)
for {
select {
case log := <-logs:
event := i.parseEvent(log)
i.kafka.SendMessage(&sarama.ProducerMessage{
Topic: "chain.events",
Value: sarama.ByteEncoder(event.Marshal()),
})
case err := <-sub.Err():
log.Error("subscription error", "err", err)
// 重连逻辑
case <-ctx.Done():
return
}
}
}
5. UX翻译层
核心创新:将所有Web3操作翻译为Web2用户可理解的操作。
// translate-service/internal/service/translate.go
type TranslateService struct {
mappingDB *redis.Client // 手机号→链上地址映射
mpcClient MPCClient // MPC钱包服务
paymaster PaymasterClient // Gas代付服务
}
// 手机号→链上地址
func (s *TranslateService) ResolveAddress(phone string) (common.Address, error) {
addr, err := s.mappingDB.Get(ctx, "phone:"+phone).Result()
if err == redis.Nil {
return common.Address{}, ErrUserNotFound
}
return common.HexToAddress(addr), nil
}
// 购买券:法币→链上原子交换
func (s *TranslateService) ExecutePurchase(req PurchaseRequest) (*PurchaseResult, error) {
// 1. 法币支付确认
// 2. 法币→稳定币转换
// 3. 调用Settlement合约原子交换(Gas由Paymaster代付)
// 4. 返回"订单号"(映射TX Hash)
}
6. API设计
6.1 RESTful规范
API规范:
风格: RESTful
版本控制: URL路径 /api/v1/
认证: OAuth 2.1 + JWT
限流:
普通用户: 100 req/min
发行方: 1,000 req/min
做市商: 10,000 req/min
响应格式: JSON
错误处理: RFC 7807 Problem Details
6.2 核心API端点
| 域 |
路径前缀 |
主要接口 |
| 用户 |
/api/v1/users |
注册、登录、KYC、资产查询 |
| 券 |
/api/v1/coupons |
发行、查询、转让、核销 |
| 交易 |
/api/v1/trades |
挂单、撤单、成交、历史 |
| 支付 |
/api/v1/payments |
充值、提现、法币转换 |
| 发行方 |
/api/v1/issuers |
入驻、审核、数据统计 |
| 合规 |
/api/v1/compliance |
KYC、AML、审计日志 |
| 翻译层 |
/api/v1/translate |
地址映射、Gas代付 |
7. 数据库设计
7.1 PostgreSQL核心表
-- 用户表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
phone VARCHAR(20) UNIQUE,
email VARCHAR(100) UNIQUE,
kyc_level SMALLINT NOT NULL DEFAULT 0,
wallet_mode VARCHAR(10) NOT NULL DEFAULT 'standard',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 发行方表
CREATE TABLE issuers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_name VARCHAR(200) NOT NULL,
business_license VARCHAR(100),
credit_rating VARCHAR(5) NOT NULL DEFAULT 'BBB',
credit_score NUMERIC(5,2) NOT NULL DEFAULT 60.00,
issuance_quota NUMERIC(15,2) NOT NULL DEFAULT 100000,
tier VARCHAR(10) NOT NULL DEFAULT 'silver',
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 券表(链下缓存,权威数据在链上)
CREATE TABLE coupons (
id UUID PRIMARY KEY,
chain_token_id BIGINT UNIQUE, -- 链上Token ID
issuer_id UUID REFERENCES issuers(id),
face_value NUMERIC(12,2) NOT NULL,
current_price NUMERIC(12,2),
expiry_date DATE NOT NULL,
coupon_type VARCHAR(10) NOT NULL DEFAULT 'utility',
status VARCHAR(20) NOT NULL DEFAULT 'available',
resale_count SMALLINT NOT NULL DEFAULT 0,
max_resale_count SMALLINT NOT NULL DEFAULT 3,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 地址映射表(翻译层核心)
CREATE TABLE address_mappings (
user_id UUID REFERENCES users(id),
chain_address VARCHAR(42) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id)
);
8. 安全规范
| 领域 |
规范 |
| 传输 |
TLS 1.3 + 服务间mTLS |
| 认证 |
OAuth 2.1 + JWT(短有效期 + Refresh Token) |
| 授权 |
RBAC |
| 数据加密 |
AES-256(敏感字段),HSM管理密钥 |
| API安全 |
Rate Limiting + CORS + CSRF防护 |
| SQL注入 |
ORM参数化查询(TypeORM / sqlx) |
| 审计 |
全链路操作日志,不可篡改 |
9. 部署架构
# Kubernetes部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: issuer-service
spec:
replicas: 3
template:
spec:
containers:
- name: issuer-service
image: genex/issuer-service:latest
resources:
requests: { cpu: "250m", memory: "512Mi" }
limits: { cpu: "1000m", memory: "1Gi" }
env:
- name: DB_HOST
valueFrom:
configMapKeyRef: { name: db-config, key: host }
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: db-secrets, key: password }
多区域部署
| 区域 |
部署 |
角色 |
| AWS美国(主) |
全量服务 + 主数据库 + Genex Chain主验证节点 |
|
| AWS新加坡 |
亚太服务 + 灾备数据库 + 区域验证节点 |
|
| 香港 |
监管节点 + 审计接口 |
|
10. 监控与可观测性
| 组件 |
工具 |
用途 |
| 指标 |
Prometheus + Grafana |
服务指标、业务指标 |
| 日志 |
ELK Stack |
日志收集分析 |
| 链路追踪 |
Jaeger |
分布式调用追踪 |
| 告警 |
AlertManager + PagerDuty |
异常告警 |
11. 手续费与收入计算
11.1 交易手续费(核心收入)
// src/domain/services/fee-calculation.service.ts
export class FeeCalculationService {
// Maker-Taker模型
private readonly FEE_RATES = {
taker: { buy: 0.005, sell: 0.005 }, // Taker 0.5%
maker: { buy: 0.001, sell: 0.001 }, // Maker 0.1%(做市商激励)
};
// 发行方分层手续费率
private readonly ISSUER_FEE_RATES: Record<IssuerTier, number> = {
silver: 0.015, // 白银 1.5%
gold: 0.012, // 黄金 1.2%
platinum: 0.010, // 铂金 1.0%
diamond: 0.008, // 钻石 0.8%
};
calculateTradeFee(trade: Trade): TradeFees {
const isMaker = (side: 'buyer' | 'seller') =>
trade[side].isMaker;
return {
buyerFee: trade.price * (isMaker('buyer') ? this.FEE_RATES.maker.buy : this.FEE_RATES.taker.buy),
sellerFee: trade.price * (isMaker('seller') ? this.FEE_RATES.maker.sell : this.FEE_RATES.taker.sell),
};
}
calculateIssuanceFee(issuer: Issuer, totalValue: number): number {
// 新入驻首月享黄金层级
const tier = issuer.isFirstMonth ? 'gold' : issuer.tier;
return totalValue * this.ISSUER_FEE_RATES[tier];
}
}
11.2 Breakage收益计算与分配
// src/domain/services/breakage.service.ts
export class BreakageService {
/**
* 券过期后Breakage计算:
* 未使用的券面值 → 发行方Breakage收入
* 平台按比例分润(发行服务协议约定)
*/
async processExpiredCoupons(): Promise<void> {
const expired = await this.couponRepo.findExpiredUnprocessed();
for (const coupon of expired) {
const breakageAmount = coupon.faceValue;
const platformShare = breakageAmount * 0.10; // 平台分润10%(可配置)
const issuerShare = breakageAmount - platformShare;
await this.financeService.recordBreakage({
couponId: coupon.id,
issuerId: coupon.issuerId,
totalAmount: breakageAmount,
platformShare,
issuerShare,
expiredAt: coupon.expiryDate,
});
// 更新券状态
await this.couponRepo.markBreakageProcessed(coupon.id);
// 更新发行方信用评分(Breakage率因子)
this.eventBus.publish(new CouponExpiredEvent(coupon));
}
}
}
// 定时任务:每日批量处理过期券
@Cron('0 2 * * *') // 每天凌晨2点
async handleExpiredCoupons() {
await this.breakageService.processExpiredCoupons();
}
11.3 退款机制
// src/domain/services/refund.service.ts
interface RefundPolicy {
primaryMarket: {
windowDays: number; // 发行方可配退款窗口(默认7天)
fullRefund: boolean; // 一级市场全额退(含手续费)
};
secondaryMarket: {
requireArbitration: true; // 二级市场需仲裁
feeRefund: false; // 手续费不退
};
}
export class RefundService {
async processRefund(request: RefundRequest): Promise<RefundResult> {
// 已核销/已过期券不可退
if (['redeemed', 'expired'].includes(request.coupon.status)) {
throw new BadRequestException('已核销或已过期券不可退款');
}
// 一级市场退款:发行方退款窗口内
if (request.type === 'primary') {
const withinWindow = this.isWithinRefundWindow(request);
if (!withinWindow) throw new BadRequestException('超出退款期限');
// 调用Settlement合约反向原子交换
return this.chainClient.executeRefund(request);
}
// 二级市场退款:需仲裁裁决
if (request.type === 'secondary') {
return this.disputeService.createRefundCase(request);
}
}
}
12. 做市商系统
12.1 做市商准入与管理
// src/domain/entities/market-maker.entity.ts
export class MarketMaker {
id: string;
userId: string;
kycLevel: KycLevel; // 必须KYC L3
depositAmount: number; // 最低保证金
status: 'active' | 'suspended' | 'terminated';
obligations: MarketMakerObligations;
}
interface MarketMakerObligations {
minSpreadBps: number; // 最大价差(基点)
minDepthPerSide: number; // 单边最小挂单深度
uptimePercent: number; // 最低在线时间 (95%)
maxResponseMs: number; // 报价响应时间上限
}
12.2 做市商专用API
// 低延迟专用接口(10,000 req/min)
@Controller('api/v1/mm')
export class MarketMakerController {
@Post('batch-orders')
async batchOrders(@Body() dto: BatchOrderDto) {
// 批量挂单/撤单(单次最多100单)
}
@Get('orderbook/:couponId')
async getOrderBook(@Param('couponId') id: string) {
// 实时订单簿快照
}
@Ws('mm/stream')
async streamMarketData() {
// WebSocket低延迟行情推送
}
}
12.3 Spoofing/Layering检测
// 做市商行为监控
export class MarketManipulationDetector {
/**
* Spoofing检测:大量挂单后短时间内撤单
* Layering检测:多层挂单制造虚假深度
*/
detect(orders: OrderActivity[]): ManipulationAlert[] {
const alerts: ManipulationAlert[] = [];
// 挂单→撤单比率异常(>80%撤单率)
const cancelRate = orders.filter(o => o.cancelled).length / orders.length;
if (cancelRate > 0.8) {
alerts.push({ type: 'spoofing', severity: 'high', cancelRate });
}
// 短时间多层挂单检测
const layeringPattern = this.detectLayeringPattern(orders);
if (layeringPattern) alerts.push(layeringPattern);
return alerts;
}
}
13. 三因子定价引擎
// src/domain/services/pricing.service.ts
export class PricingService {
/**
* P = F × (1 - dt - rc - lp)
* dt = Time Discount = f(剩余有效期)
* rc = Credit Risk = f(发行方信用评分)
* lp = Liquidity Premium = f(市场供需)
*/
calculateSuggestedPrice(coupon: Coupon, issuer: Issuer, market: MarketData): PriceSuggestion {
const F = coupon.faceValue;
const totalDays = differenceInDays(coupon.expiryDate, coupon.issuedDate);
const remainDays = differenceInDays(coupon.expiryDate, new Date());
// 时间折扣 (0% - 15%)
const dt = ((totalDays - remainDays) / totalDays) * 0.15;
// 信用风险溢价 (0% - 20%)
const CREDIT_MAP: Record<CreditRating, number> = {
AAA: 0, AA: 0.03, A: 0.06, BBB: 0.10, BB: 0.15,
};
const rc = CREDIT_MAP[issuer.creditRating] || 0.20;
// 流动性溢价 (-5% - 10%)
const supplyDemandRatio = (market.sellOrders - market.buyOrders) / (market.totalOrders || 1);
const lp = Math.max(-0.05, Math.min(0.10, supplyDemandRatio * 0.1));
const price = F * (1 - dt - rc - lp);
// Utility Track: 价格上限 = 面值
const cappedPrice = coupon.type === 'utility' ? Math.min(price, F) : price;
return { suggestedPrice: cappedPrice, factors: { dt, rc, lp } };
}
}
14. AI/ML 模型服务
14.1 信用评分引擎
# ai-service/models/credit_scoring.py
import lightgbm as lgb
from sklearn.model_selection import cross_val_score
class CreditScoringModel:
"""
发行方信用评分:四因子基础 + ML增强
特征:核销率、Breakage率、市场存续月数、用户满意度、历史违约、行业
"""
def __init__(self):
self.model = lgb.LGBMRegressor(
n_estimators=500, learning_rate=0.05,
max_depth=6, num_leaves=31,
)
def predict_credit_score(self, issuer_features: dict) -> float:
# 四因子基础分
base = (0.35 * issuer_features['redemption_rate'] +
0.25 * (1 - issuer_features['breakage_ratio']) +
0.20 * math.log(issuer_features['tenure_months'] + 1) / math.log(37) +
0.20 * issuer_features['user_satisfaction'])
# ML调整(基于更多特征)
ml_adjustment = self.model.predict([self._extract_features(issuer_features)])[0]
return min(100, max(0, base * 100 * 0.7 + ml_adjustment * 0.3))
14.2 价格预测模型
# ai-service/models/price_prediction.py
from prophet import Prophet
import torch.nn as nn
class LSTMPricePredictor(nn.Module):
"""LSTM序列预测:历史价格→未来价格走势"""
def __init__(self, input_size=5, hidden_size=64, num_layers=2):
super().__init__()
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, 1)
def forward(self, x):
out, _ = self.lstm(x)
return self.fc(out[:, -1, :])
class ProphetSeasonalPredictor:
"""Prophet周期性预测:节假日/季节性价格模式"""
def predict(self, history: pd.DataFrame, periods: int = 30):
model = Prophet(yearly_seasonality=True, weekly_seasonality=True)
model.fit(history)
future = model.make_future_dataframe(periods=periods)
return model.predict(future)
14.3 异常检测系统
# ai-service/models/anomaly_detection.py
from sklearn.ensemble import IsolationForest
class TransactionAnomalyDetector:
"""
异常交易检测:Isolation Forest + 规则引擎
实时消费Kafka trade.events,标记可疑交易
"""
def __init__(self):
self.isolation_forest = IsolationForest(
contamination=0.01, n_estimators=200, random_state=42
)
def detect(self, transaction: dict) -> AnomalyResult:
features = self._extract_features(transaction)
# ML检测
score = self.isolation_forest.decision_function([features])[0]
# 规则引擎检测
rule_alerts = self._apply_rules(transaction)
return AnomalyResult(
ml_score=score,
is_anomaly=score < -0.5 or len(rule_alerts) > 0,
alerts=rule_alerts,
)
def _apply_rules(self, tx: dict) -> list:
alerts = []
if tx['amount'] > 10000: alerts.append('large_amount')
if tx['frequency_1h'] > 20: alerts.append('high_frequency')
if tx['geolocation_change']: alerts.append('geo_anomaly')
return alerts
14.4 智能推荐引擎(P2优先级)
# 协同过滤 + 内容推荐 混合推荐
class CouponRecommender:
def recommend(self, user_id: str, top_k: int = 10):
# 协同过滤:相似用户购买历史
cf_scores = self.collaborative_filter(user_id)
# 内容推荐:基于券属性(品牌、类别、折扣率)
content_scores = self.content_based(user_id)
# 混合加权
return self.blend(cf_scores, content_scores, top_k)
15. AML反洗钱系统
15.1 AML检测规则引擎
// compliance-service/src/domain/services/aml-detection.service.ts
export class AmlDetectionService {
// 已识别洗钱路径检测
private readonly RULES: AmlRule[] = [
// 1. 买券洗钱:脏钱买券→P2P转给另一账户→卖出提现
{ name: 'buy_transfer_withdraw', detect: this.detectBuyTransferWithdraw },
// 2. 分散洗钱:一个账户→P2P分散转给大量小账户
{ name: 'fan_out', detect: this.detectFanOut },
// 3. 发行方自洗:关联账户自买自卖
{ name: 'self_dealing', detect: this.detectSelfDealing },
// 4. 跨境洗钱:A国买→P2P转B国→B国提现
{ name: 'cross_border', detect: this.detectCrossBorder },
// 5. Structuring:拆分交易规避阈值
{ name: 'structuring', detect: this.detectStructuring },
];
async detectFanOut(userId: string, window: TimeWindow): Promise<Alert | null> {
const transfers = await this.getP2PTransfers(userId, window);
const uniqueRecipients = new Set(transfers.map(t => t.recipientId));
// 24h内向>10个不同账户P2P转移
if (uniqueRecipients.size > 10) {
return { type: 'fan_out', severity: 'high', details: { recipients: uniqueRecipients.size } };
}
return null;
}
async detectStructuring(userId: string, window: TimeWindow): Promise<Alert | null> {
const txs = await this.getTransactions(userId, window);
// 检测是否有多笔接近$3,000阈值的交易($2,500-$2,999)
const nearThreshold = txs.filter(t => t.amount >= 2500 && t.amount < 3000);
if (nearThreshold.length >= 3) {
return { type: 'structuring', severity: 'critical', details: { count: nearThreshold.length } };
}
return null;
}
}
15.2 交易图谱分析
// 构建用户间交易关系图谱
export class TransactionGraphService {
async buildGraph(timeWindow: TimeWindow): Promise<TransactionGraph> {
const edges = await this.tradeRepo.getTransferEdges(timeWindow);
const graph = new Graph();
for (const edge of edges) {
graph.addEdge(edge.sender, edge.receiver, {
amount: edge.amount, timestamp: edge.timestamp,
});
}
// 检测环形转移
const cycles = graph.detectCycles();
// 检测扇入/扇出模式
const fanPatterns = graph.detectFanPatterns(threshold: 10);
// 关联账户聚类
const clusters = graph.communityDetection();
return { graph, cycles, fanPatterns, clusters };
}
}
15.3 SAR自动生成
// 可疑交易报告(Suspicious Activity Report)
export class SarService {
async generateSar(alert: AmlAlert): Promise<SarReport> {
return {
filingType: 'initial',
subjectInfo: await this.getUserInfo(alert.userId),
suspiciousActivity: {
dateRange: alert.timeWindow,
amount: alert.totalAmount,
instruments: 'Digital Coupon Assets',
description: this.generateNarrative(alert),
},
// FinCEN BSA E-Filing格式
};
}
}
16. OFAC合规服务
// compliance-service/src/infrastructure/external/ofac.service.ts
export class OfacService {
private sdnList: Map<string, SdnEntry> = new Map();
// 名单同步(24h内更新)
@Cron('0 */6 * * *') // 每6小时同步
async syncSdnList(): Promise<void> {
// 主选:Chainalysis,备选:Elliptic/TRM Labs
const entries = await this.chainalysis.getLatestSdnList();
this.sdnList = new Map(entries.map(e => [e.id, e]));
this.logger.log(`OFAC SDN list synced: ${entries.length} entries`);
}
// 注册时筛查
async screenOnRegistration(user: UserRegistrationDto): Promise<OfacScreenResult> {
return this.screenPerson({
name: user.fullName,
nationality: user.nationality,
address: user.address,
dateOfBirth: user.dob,
});
}
// 每笔交易实时筛查
async screenTransaction(buyer: string, seller: string): Promise<OfacScreenResult> {
const buyerResult = await this.screenAddress(buyer);
const sellerResult = await this.screenAddress(seller);
if (buyerResult.isMatch || sellerResult.isMatch) {
// 命中:立即冻结 + 上报
await this.freezeAndReport(buyerResult, sellerResult);
}
return { buyerResult, sellerResult };
}
// P2P转移链上地址筛查
async screenChainAddress(address: string): Promise<boolean> {
return this.chainalysis.checkAddress(address);
}
}
17. Travel Rule合规(TRISA/TRP)
// compliance-service/src/domain/services/travel-rule.service.ts
export class TravelRuleService {
/**
* FATF Travel Rule: ≥$3,000转移需传递身份信息
* 接入TRISA协议实现跨平台信息传递
*/
async processP2PTransfer(transfer: P2PTransferRequest): Promise<void> {
if (transfer.amount >= 3000) {
// 验证双方KYC≥L2
await this.verifyKycLevel(transfer.senderId, KycLevel.L2);
await this.verifyKycLevel(transfer.receiverId, KycLevel.L2);
// 身份信息哈希写入链上
const senderHash = this.hashIdentity(await this.getKycInfo(transfer.senderId));
const receiverHash = this.hashIdentity(await this.getKycInfo(transfer.receiverId));
await this.chainClient.recordTravelRule(
transfer.senderAddress, transfer.receiverAddress,
senderHash, receiverHash,
);
// TRISA跨平台传递(如接收方在其他平台)
if (transfer.isExternalReceiver) {
await this.trisaClient.sendTravelRuleInfo({
originator: await this.getOriginatorInfo(transfer.senderId),
beneficiary: await this.getBeneficiaryInfo(transfer.receiverId),
});
}
}
}
}
18. 税务合规
// compliance-service/src/domain/services/tax.service.ts
export class TaxComplianceService {
// IRS Form 1099-DA / 1099-B 生成
async generate1099(userId: string, taxYear: number): Promise<Tax1099Report> {
const trades = await this.tradeRepo.getUserTrades(userId, taxYear);
const proceeds = trades.reduce((sum, t) => sum + t.sellPrice, 0);
const costBasis = trades.reduce((sum, t) => sum + t.buyPrice, 0);
return {
form: proceeds > 600 ? '1099-DA' : null, // >$600 threshold
recipientTIN: await this.getUserTIN(userId),
grossProceeds: proceeds,
costBasis,
gainOrLoss: proceeds - costBasis,
transactions: trades.map(t => this.formatTaxTransaction(t)),
};
}
// FATCA跨境税务(外国账户持有人)
async checkFatcaObligation(userId: string): Promise<boolean> {
const user = await this.userRepo.findById(userId);
return user.nationality !== 'US' && user.hasUsSourceIncome;
}
// 发行方Breakage收入税务处理
async generateIssuerTaxReport(issuerId: string, taxYear: number): Promise<IssuerTaxReport> {
const breakageIncome = await this.financeRepo.getBreakageIncome(issuerId, taxYear);
const salesRevenue = await this.financeRepo.getSalesRevenue(issuerId, taxYear);
return { breakageIncome, salesRevenue, totalTaxableIncome: breakageIncome + salesRevenue };
}
}
19. 数据隐私合规(CCPA/GDPR)
// user-service/src/domain/services/privacy.service.ts
export class PrivacyService {
/**
* 用户数据删除流程:
* 链上仅地址和哈希(不可删除,但不构成PII)
* 链下PII全部可删除
*
* 例外:AML/BSA法规要求交易记录保留≥5年
*/
async processDeleteRequest(userId: string): Promise<DeleteResult> {
// 检查数据保留义务
const retentionCheck = await this.checkRetentionObligation(userId);
if (retentionCheck.hasActiveObligation) {
return { status: 'deferred', reason: 'AML/BSA 5年保留期未满', deferUntil: retentionCheck.expiresAt };
}
// 1. 删除链下所有PII
await this.userRepo.deletePersonalData(userId); // 姓名、手机号、邮箱、身份证
await this.kycRepo.deleteKycMaterials(userId); // KYC资料(人脸、证件照)
// 2. 删除映射表记录
await this.mappingRepo.deleteMapping(userId); // 手机号→地址映射
// 3. 链上地址变为匿名(不可逆删除映射后地址无法关联到人)
// 4. Travel Rule链下明文删除,链上仅哈希(不可逆)
return { status: 'completed', deletedAt: new Date() };
}
}
20. 数据留存与审计
// 数据保留策略
const DATA_RETENTION_POLICIES = {
transactionRecords: { minYears: 5, source: 'BSA/AML' },
kycDocuments: { minYears: 5, source: 'BSA/CDD' },
sarReports: { minYears: 5, source: 'FinCEN' },
auditLogs: { minYears: 7, source: 'SOX' },
chainData: { minYears: Infinity, source: 'blockchain' },
};
// 审计日志(append-only,不可篡改)
export class AuditLogService {
async log(entry: AuditEntry): Promise<void> {
await this.appendOnlyStore.write({
timestamp: new Date(),
actor: entry.actorId,
action: entry.action,
resource: entry.resource,
details: entry.details,
ipAddress: entry.ip,
hash: this.computeChainHash(entry), // 链式哈希防篡改
});
}
}
21. 券生命周期管理
// 券状态流转
enum CouponStatus {
MINTED = 'minted', // 已铸造(链上)
LISTED = 'listed', // 已上架
SOLD = 'sold', // 已售出
IN_CIRCULATION = 'in_circulation', // 流通中(二级市场)
REDEEMED = 'redeemed', // 已兑付
EXPIRED = 'expired', // 已过期
RECALLED = 'recalled', // 已召回
}
// 过期自动处理
@Cron('0 0 * * *') // 每日凌晨
async processExpiredCoupons(): Promise<void> {
const expiring = await this.couponRepo.findExpiringToday();
for (const coupon of expiring) {
await this.couponRepo.updateStatus(coupon.id, CouponStatus.EXPIRED);
// Breakage收益计算
await this.breakageService.calculateAndDistribute(coupon);
// 通知持有人
await this.notificationService.send(coupon.holderId, {
type: 'coupon_expired',
couponName: coupon.name,
});
}
}
22. 争议与纠纷处理
// 争议处理流程
interface DisputeCase {
id: string;
type: 'buyer_complaint' | 'seller_complaint' | 'refund_request';
status: 'submitted' | 'evidence_collection' | 'arbitration' | 'resolved' | 'escalated';
buyerId: string;
sellerId: string;
orderId: string;
chainEvidence: ChainEvidence[]; // 链上不可篡改证据
slaDeadline: Date; // 24h响应,72h处理
}
export class DisputeService {
async createDispute(dto: CreateDisputeDto): Promise<DisputeCase> {
const chainEvidence = await this.chainClient.getTransactionProof(dto.orderId);
const dispute = DisputeCase.create({ ...dto, chainEvidence });
// SLA:24h内响应
await this.slaService.scheduleReminder(dispute.id, '24h');
return dispute;
}
async arbitrate(disputeId: string, decision: ArbitrationDecision): Promise<void> {
if (decision.refundApproved) {
await this.refundService.processRefund(decision.refundDetails);
}
}
}
23. 客服系统
// 工单系统
interface Ticket {
id: string;
userId: string;
category: 'transaction' | 'account' | 'coupon' | 'compliance' | 'other';
priority: 'low' | 'medium' | 'high' | 'urgent';
status: 'open' | 'in_progress' | 'waiting_user' | 'resolved' | 'closed';
sla: { responseTime: '24h', resolutionTime: '72h' };
}
// 发行方专属客服通道(铂金/钻石层级)
export class IssuerSupportService {
getDedicatedChannel(issuer: Issuer): SupportChannel {
if (['platinum', 'diamond'].includes(issuer.tier)) {
return { type: 'dedicated', responseTime: '1h', manager: this.getAccountManager(issuer.id) };
}
return { type: 'standard', responseTime: '24h' };
}
}
24. 安全事件响应计划
24.1 事件分级
| 级别 |
定义 |
响应时限 |
示例 |
| P0 |
资产被盗/合约漏洞被利用 |
15分钟内启动 |
MPC密钥泄露、合约攻击 |
| P1 |
数据泄露/系统被入侵 |
1小时内响应 |
用户数据泄露、映射表篡改 |
| P2 |
局部服务异常/可疑活动 |
4小时内响应 |
API攻击、异常登录 |
| P3 |
潜在风险/安全隐患 |
24小时内评估 |
依赖库漏洞 |
24.2 P0应急流程
// 自动化应急响应
export class IncidentResponseService {
async handleP0Incident(incident: SecurityIncident): Promise<void> {
// 1. 自动触发紧急冻结(Governance合约)
await this.chainClient.triggerEmergencyFreeze(incident.affectedAddresses);
// 2. 通知安全团队(PagerDuty)
await this.pagerDuty.trigger({
severity: 'critical',
description: incident.description,
});
// 3. 保全证据
await this.forensics.preserveEvidence({
logs: await this.collectLogs(incident.timeRange),
chainState: await this.chainClient.getStateSnapshot(),
systemSnapshot: await this.infrastructure.getSnapshot(),
});
// 4. 用户通知(≤24h,CCPA要求72h内通知受影响用户)
await this.notificationService.scheduleBreachNotification(incident);
}
}
24.3 Bug Bounty
| 严重程度 |
奖励范围 |
示例 |
| Critical |
$50K-$100K |
合约资金盗取、MPC密钥泄露 |
| High |
$10K-$50K |
权限绕过、数据泄露 |
| Medium |
$2K-$10K |
信息泄露、DoS |
| Low |
$500-$2K |
低影响漏洞 |
25. 灾难恢复与业务连续性
25.1 DR指标
| 指标 |
目标 |
| RPO(恢复点目标) |
< 1分钟 |
| RTO(恢复时间目标) |
< 15分钟 |
25.2 备份与故障转移
# 数据库备份策略
postgresql:
replication: streaming_replication # 实时同步至备用区域
backup:
full: weekly # 每周全量备份
incremental: every_15_minutes # 每15分钟增量备份
retention: 90_days # 备份保留90天
failover:
automatic: true # 自动故障转移
health_check_interval: 5s
# 链下数据快照
orderbook:
snapshot: every_5_minutes # 订单簿快照
wal: continuous # 预写日志持续记录
# 链上数据:区块链本身分布式存储,天然灾备
genex_chain:
validators: 3 # ≥3自有验证节点,不同地理位置
third_party_rpc: [alchemy, quicknode] # 备用RPC节点
25.3 业务连续性
| 故障场景 |
应对策略 |
| 交易系统故障 |
自动切换备用撮合引擎,未完成订单状态保护 |
| 链节点故障 |
多节点冗余(≥3个自有 + 第三方RPC备用) |
| 法币通道故障 |
≥2家支付服务商,自动热备切换 |
| MPC密钥服务故障 |
密钥分片跨地理位置存储,Fireblocks/Fordefi热备 |
| 年度DR演练 |
每年≥1次全量灾难恢复演练并记录 |
26. 映射表安全方案
手机号→地址映射表是最高价值安全资产,被篡改将导致资产错误转移。
// translate-service/src/security/mapping-security.service.ts
export class MappingSecurityService {
/**
* 映射记录创建/修改需多方签名:
* 平台服务器 + HSM + 第三方审计节点
*/
async createMapping(userId: string, chainAddress: string): Promise<void> {
// 1. MPC多方签名
const signature = await this.mpcSigner.multiSign({
parties: ['platform_server', 'hsm_module', 'audit_node'],
threshold: 2, // 至少2方签名
data: { userId, chainAddress },
});
// 2. 写入加密数据库(AES-256,HSM管理密钥)
await this.encryptedStore.write(userId, chainAddress, signature);
// 3. 记录到append-only审计日志
await this.auditLog.append({
action: 'mapping_created',
userId, chainAddress, signature,
});
}
// 定期链上锚定(Merkle Root)
@Cron('0 * * * *') // 每小时
async anchorToChain(): Promise<void> {
const merkleRoot = await this.computeMappingMerkleRoot();
await this.chainClient.anchorMerkleRoot(merkleRoot);
}
// 完整性校验
async verifyIntegrity(): Promise<boolean> {
const currentRoot = await this.computeMappingMerkleRoot();
const chainRoot = await this.chainClient.getLatestMerkleRoot();
return currentRoot === chainRoot;
}
}
27. 发行方违约处理
// issuer-service/src/domain/services/default-handling.service.ts
export class IssuerDefaultService {
async handleDefault(issuerId: string, severity: 'minor' | 'major' | 'critical'): Promise<void> {
switch (severity) {
case 'minor':
// 降级 + 额度缩减
await this.issuerRepo.downgradeCreditRating(issuerId, 1);
break;
case 'major':
// 冻结发行 + 启用保障资金
await this.issuerRepo.freezeIssuance(issuerId);
if (await this.hasGuaranteeFund(issuerId)) {
await this.activateGuaranteeFund(issuerId);
}
break;
case 'critical':
// 跑路:冻结账户 + 链上标记风险券 + 通知持有人
await this.issuerRepo.freezeAccount(issuerId);
await this.chainClient.markRiskCoupons(issuerId);
await this.notifyAllHolders(issuerId, '发行方异常,请关注券状态');
break;
}
}
}
28. 多币种与法币通道
// 多稳定币支持
interface StablecoinConfig {
primary: 'USDC';
secondary: 'USDT';
oracle: 'chainlink'; // 汇率预言机
}
// 法币通道热备
export class FiatGatewayService {
private providers: FiatProvider[] = [
{ name: 'primary_bank', priority: 1, status: 'active' },
{ name: 'backup_processor', priority: 2, status: 'standby' },
];
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
for (const provider of this.providers.sort((a, b) => a.priority - b.priority)) {
try {
if (provider.status === 'active' || provider.status === 'standby') {
return await provider.process(request);
}
} catch (error) {
this.logger.error(`Fiat provider ${provider.name} failed, trying next`);
provider.status = 'failed';
continue; // 自动切换
}
}
throw new ServiceUnavailableException('All fiat providers unavailable');
}
}
// 汇率管理
export class ExchangeRateService {
async getRate(from: string, to: string): Promise<ExchangeRate> {
// Oracle获取实时汇率
const rate = await this.oracleClient.getRate(from, to);
return { rate: rate.value, lockedUntil: addMinutes(new Date(), 15) }; // 锁定15分钟
}
}
29. 链上对账
// 链上数据 vs 链下账本对账
export class ReconciliationService {
// 实时对账(链上数据即账本)
async reconcile(): Promise<ReconciliationResult> {
const chainBalances = await this.chainClient.getAllBalances();
const dbBalances = await this.financeRepo.getAllBalances();
const discrepancies: Discrepancy[] = [];
for (const [userId, chainBal] of chainBalances) {
const dbBal = dbBalances.get(userId);
if (Math.abs(chainBal - dbBal) > 0.01) {
discrepancies.push({ userId, chainBalance: chainBal, dbBalance: dbBal });
}
}
if (discrepancies.length > 0) {
await this.alertService.sendAlert('reconciliation_mismatch', discrepancies);
}
return { totalChecked: chainBalances.size, discrepancies };
}
}
30. 容量规划
| 阶段 |
用户规模 |
日交易量 |
基础设施 |
| Phase 1 (MVP) |
10万 |
50万笔 |
单区域K8s(3节点) |
| Phase 2 (商业化) |
100万 |
500万笔 |
双区域K8s + 热备(5+节点) |
| Phase 3 (金融化) |
1,000万 |
5,000万笔 |
多区域集群 + GCFN节点 |
关键性能指标
| 指标 |
目标 |
| API响应时间(P99) |
< 200ms |
| 撮合引擎延迟 |
< 10ms |
| 翻译层额外耗时 |
< 50ms |
| 系统可用性 |
> 99.9% SLA |
31. SDK开发计划
| SDK |
语言/平台 |
功能范围 |
优先级 |
| genex-js |
JavaScript/Node.js |
全功能(发行、交易、核销、数据查询) |
P0 |
| genex-java |
Java/Android |
全功能 + Android原生集成 |
P0 |
| genex-python |
Python |
数据分析、信用查询、批量操作 |
P1 |
| genex-swift |
Swift/iOS |
移动端核销、用户端功能 |
P1 |
| genex-go |
Go |
高性能服务端集成、做市商接入 |
P2 |
开发者门户
- 沙箱环境(Testnet API + 测试券 + 测试USDC)
- API文档(OpenAPI 3.0 + Swagger UI)
- SDK快速入门指南
- Webhook事件订阅
文档版本: v2.0
基于: Genex 券交易平台 - 软件需求规格说明书 v4.1
技术栈: NestJS + Go + Kong + PostgreSQL + Kafka + Redis
更新: 补充手续费/Breakage/退款/做市商/定价引擎/AI-ML/AML/OFAC/Travel Rule/税务/隐私/安全IR/DR/映射表安全/多币种/对账/容量规划/SDK