# Trading Service (交易服务) 开发指导 ## 1. 服务概述 ### 1.1 核心职责 Trading Service 负责积分股的买卖交易、K线数据生成、以及维护币价上涨机制。 **主要功能:** - 处理积分股买卖订单 - 计算卖出销毁量(确保卖出不降价) - 管理流通池 - 生成K线数据(多周期) - 处理交易手续费 - 维护交易明细账 ### 1.2 技术栈 - **框架**: NestJS + TypeScript - **数据库**: PostgreSQL (事务型) - **ORM**: Prisma - **消息队列**: Kafka - **缓存**: Redis (K线缓存、价格缓存) ### 1.3 端口分配 - HTTP: 3022 - 数据库: rwa_trading --- ## 2. 架构设计 ### 2.1 目录结构 ``` trading-service/ ├── src/ │ ├── api/ │ │ ├── controllers/ │ │ │ ├── trade.controller.ts # 买卖API │ │ │ ├── order.controller.ts # 订单查询API │ │ │ ├── kline.controller.ts # K线数据API │ │ │ ├── price.controller.ts # 价格查询API │ │ │ └── health.controller.ts │ │ └── dto/ │ │ ├── request/ │ │ │ ├── buy-shares.request.ts │ │ │ ├── sell-shares.request.ts │ │ │ └── get-kline.request.ts │ │ └── response/ │ │ ├── trade-result.response.ts │ │ ├── order.response.ts │ │ ├── kline.response.ts │ │ └── price.response.ts │ │ │ ├── application/ │ │ ├── commands/ │ │ │ ├── buy-shares.command.ts │ │ │ ├── sell-shares.command.ts │ │ │ ├── cancel-order.command.ts │ │ │ └── aggregate-kline.command.ts │ │ ├── queries/ │ │ │ ├── get-user-orders.query.ts │ │ │ ├── get-kline-data.query.ts │ │ │ ├── get-current-price.query.ts │ │ │ └── get-trade-history.query.ts │ │ ├── services/ │ │ │ ├── trade-execution.service.ts │ │ │ ├── sell-burn-calculator.service.ts │ │ │ └── kline-aggregator.service.ts │ │ ├── event-handlers/ │ │ │ ├── price-updated.handler.ts │ │ │ └── shares-burned.handler.ts │ │ └── schedulers/ │ │ ├── kline-aggregation.scheduler.ts # K线聚合定时器 │ │ └── price-tick.scheduler.ts # 价格记录 │ │ │ ├── domain/ │ │ ├── aggregates/ │ │ │ ├── trade-order.aggregate.ts │ │ │ ├── kline-bar.aggregate.ts │ │ │ └── trade-transaction.aggregate.ts │ │ ├── repositories/ │ │ │ ├── trade-order.repository.interface.ts │ │ │ ├── trade-transaction.repository.interface.ts │ │ │ ├── kline.repository.interface.ts │ │ │ └── price-tick.repository.interface.ts │ │ ├── value-objects/ │ │ │ ├── order-type.vo.ts │ │ │ ├── kline-period.vo.ts │ │ │ └── trade-amount.vo.ts │ │ ├── events/ │ │ │ ├── trade-completed.event.ts │ │ │ ├── order-created.event.ts │ │ │ └── kline-updated.event.ts │ │ └── services/ │ │ ├── sell-multiplier-calculator.domain-service.ts │ │ └── fee-calculator.domain-service.ts │ │ │ ├── infrastructure/ │ │ ├── persistence/ │ │ │ ├── prisma/ │ │ │ │ └── prisma.service.ts │ │ │ ├── repositories/ │ │ │ │ ├── trade-order.repository.impl.ts │ │ │ │ ├── trade-transaction.repository.impl.ts │ │ │ │ ├── kline.repository.impl.ts │ │ │ │ └── price-tick.repository.impl.ts │ │ │ └── unit-of-work/ │ │ │ └── unit-of-work.service.ts │ │ ├── kafka/ │ │ │ ├── mining-event-consumer.service.ts │ │ │ ├── event-publisher.service.ts │ │ │ └── kafka.module.ts │ │ ├── redis/ │ │ │ ├── price-cache.service.ts │ │ │ ├── kline-cache.service.ts │ │ │ └── order-book-cache.service.ts │ │ └── infrastructure.module.ts │ │ │ ├── shared/ │ ├── config/ │ ├── app.module.ts │ └── main.ts │ ├── prisma/ │ ├── schema.prisma │ └── migrations/ ├── package.json ├── tsconfig.json ├── Dockerfile └── docker-compose.yml ``` --- ## 3. 数据库设计 ### 3.1 核心表结构 ```sql -- ============================================ -- 交易订单表 -- ============================================ CREATE TABLE trade_orders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), order_no VARCHAR(32) NOT NULL UNIQUE, -- 订单号 account_sequence VARCHAR(20) NOT NULL, order_type VARCHAR(10) NOT NULL, -- BUY / SELL order_status VARCHAR(20) NOT NULL, -- PENDING / COMPLETED / FAILED / CANCELLED -- 数量 share_amount DECIMAL(30,10) NOT NULL, -- 积分股数量 burn_amount DECIMAL(30,10) DEFAULT 0, -- 卖出销毁量 effective_amount DECIMAL(30,10), -- 有效数量(含销毁) -- 价格 price_at_order DECIMAL(30,18) NOT NULL, -- 下单时价格 execution_price DECIMAL(30,18), -- 成交价格 -- 金额 green_points_amount DECIMAL(30,10) NOT NULL, -- 绿积分金额 fee_amount DECIMAL(30,10) DEFAULT 0, -- 手续费 fee_rate DECIMAL(10,6) DEFAULT 0.10, -- 手续费率 10% net_amount DECIMAL(30,10), -- 净额(扣除手续费后) -- 卖出倍数(卖出时使用) sell_multiplier DECIMAL(20,10), -- 计算参数快照(审计用) black_hole_at_order DECIMAL(30,10), circulation_pool_at_order DECIMAL(30,10), share_pool_at_order DECIMAL(30,10), -- 版本号 version INT DEFAULT 1, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), executed_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_trade_orders_account ON trade_orders(account_sequence); CREATE INDEX idx_trade_orders_status ON trade_orders(order_status); CREATE INDEX idx_trade_orders_created ON trade_orders(created_at); -- ============================================ -- 交易流水表(明细账) -- ============================================ CREATE TABLE trade_transactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), order_id UUID NOT NULL REFERENCES trade_orders(id), account_sequence VARCHAR(20) NOT NULL, transaction_type VARCHAR(30) NOT NULL, -- SHARE_DEBIT / SHARE_CREDIT / GREEN_POINT_DEBIT / GREEN_POINT_CREDIT / FEE / BURN asset_type VARCHAR(20) NOT NULL, -- SHARE / GREEN_POINT amount DECIMAL(30,10) NOT NULL, balance_before DECIMAL(30,10), balance_after DECIMAL(30,10), -- 关联的销毁记录(如果是BURN类型) related_burn_id UUID, memo VARCHAR(200), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_trade_transactions_order ON trade_transactions(order_id); CREATE INDEX idx_trade_transactions_account ON trade_transactions(account_sequence); -- ============================================ -- K线数据表 -- ============================================ CREATE TABLE kline_data ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), period_type VARCHAR(10) NOT NULL, -- 1m/5m/15m/30m/1h/4h/1d/1w/1M/1Q/1Y period_start TIMESTAMP WITH TIME ZONE NOT NULL, period_end TIMESTAMP WITH TIME ZONE NOT NULL, -- OHLC open_price DECIMAL(30,18) NOT NULL, high_price DECIMAL(30,18) NOT NULL, low_price DECIMAL(30,18) NOT NULL, close_price DECIMAL(30,18) NOT NULL, -- 成交量 volume DECIMAL(30,10) DEFAULT 0, -- 积分股成交量 green_points_volume DECIMAL(30,10) DEFAULT 0, -- 绿积分成交量 trade_count INT DEFAULT 0, -- 成交笔数 -- 买卖统计 buy_volume DECIMAL(30,10) DEFAULT 0, sell_volume DECIMAL(30,10) DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(period_type, period_start) ); CREATE INDEX idx_kline_period ON kline_data(period_type, period_start); -- ============================================ -- 价格快照表(每分钟) -- ============================================ CREATE TABLE price_ticks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tick_time TIMESTAMP WITH TIME ZONE NOT NULL, price DECIMAL(30,18) NOT NULL, -- 状态快照 share_pool_green_points DECIMAL(30,10), black_hole_amount DECIMAL(30,10), circulation_pool DECIMAL(30,10), effective_supply DECIMAL(30,10), -- 该分钟内的交易统计 minute_volume DECIMAL(30,10) DEFAULT 0, minute_trade_count INT DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_price_ticks_time ON price_ticks(tick_time); -- ============================================ -- 手续费配置表 -- ============================================ CREATE TABLE fee_configs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), fee_type VARCHAR(20) NOT NULL, -- BUY / SELL fee_rate DECIMAL(10,6) NOT NULL DEFAULT 0.10, -- 10% is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 初始化数据 INSERT INTO fee_configs (fee_type, fee_rate) VALUES ('BUY', 0.10), ('SELL', 0.10); -- ============================================ -- 流通池状态表(本地缓存,与mining-service同步) -- ============================================ CREATE TABLE circulation_pool_state ( id UUID PRIMARY KEY DEFAULT '00000000-0000-0000-0000-000000000001', circulation_pool DECIMAL(30,10) DEFAULT 0, last_synced_from_mining TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- ============================================ -- 已处理事件(幂等性) -- ============================================ CREATE TABLE processed_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id VARCHAR(100) NOT NULL UNIQUE, event_type VARCHAR(50) NOT NULL, source_service VARCHAR(50), processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); ``` --- ## 4. 核心业务逻辑 ### 4.1 卖出交易逻辑(核心) ```typescript /** * 卖出积分股 * 核心机制:通过提前销毁未来的积分股,确保卖出不会导致价格下跌 */ async executeSellOrder(command: SellSharesCommand): Promise { const { accountSequence, shareAmount } = command; return await this.unitOfWork.runInTransaction(async (tx) => { // 1. 获取当前状态 const state = await this.getMiningState(); const currentPrice = new Decimal(state.currentPrice); // 2. 计算卖出倍数 // 倍数 = (100亿 - 销毁量) ÷ (200万 - 流通池量) const multiplier = this.calculateSellMultiplier(state); // 3. 计算卖出销毁量 // 卖出销毁量 = 卖出积分股 × 倍数 const burnAmount = shareAmount.multipliedBy(multiplier); // 4. 计算有效数量(卖出量 + 销毁量) const effectiveAmount = shareAmount.plus(burnAmount); // 5. 计算交易额 // 卖出交易额 = 有效数量 × 积分股价 const grossAmount = effectiveAmount.multipliedBy(currentPrice); // 6. 计算手续费(10%) const feeRate = await this.getFeeRate('SELL'); const feeAmount = grossAmount.multipliedBy(feeRate); // 7. 计算净额 const netAmount = grossAmount.minus(feeAmount); // 8. 验证用户余额 const shareAccount = await this.shareAccountRepo.getByAccountSequence(tx, accountSequence); if (shareAccount.availableBalance.lessThan(shareAmount)) { throw new InsufficientBalanceException('积分股余额不足'); } // 9. 创建订单 const order = await this.orderRepo.create(tx, { orderNo: generateOrderNo(), accountSequence, orderType: 'SELL', orderStatus: 'COMPLETED', shareAmount, burnAmount, effectiveAmount, priceAtOrder: currentPrice, executionPrice: currentPrice, greenPointsAmount: grossAmount, feeAmount, feeRate, netAmount, sellMultiplier: multiplier, blackHoleAtOrder: state.blackHoleAmount, circulationPoolAtOrder: state.circulationPool, sharePoolAtOrder: state.sharePoolGreenPoints, executedAt: new Date(), }); // 10. 扣减用户积分股 await this.shareAccountRepo.deductBalance(tx, accountSequence, shareAmount); // 11. 记录交易流水 - 积分股扣减 await this.transactionRepo.create(tx, { orderId: order.id, accountSequence, transactionType: 'SHARE_DEBIT', assetType: 'SHARE', amount: shareAmount, balanceBefore: shareAccount.availableBalance, balanceAfter: shareAccount.availableBalance.minus(shareAmount), }); // 12. 积分股进入流通池 await this.updateCirculationPool(tx, shareAmount); // 13. 记录交易流水 - 销毁 await this.transactionRepo.create(tx, { orderId: order.id, accountSequence, transactionType: 'BURN', assetType: 'SHARE', amount: burnAmount, memo: `卖出触发销毁,倍数: ${multiplier.toString()}`, }); // 14. 从积分股池扣减绿积分给用户 await this.deductFromSharePool(tx, netAmount); // 15. 增加用户绿积分(调用 wallet-service 或发送事件) await this.creditGreenPoints(accountSequence, netAmount); // 16. 记录交易流水 - 绿积分增加 await this.transactionRepo.create(tx, { orderId: order.id, accountSequence, transactionType: 'GREEN_POINT_CREDIT', assetType: 'GREEN_POINT', amount: netAmount, }); // 17. 手续费注入积分股池 await this.injectFeeToSharePool(tx, feeAmount); // 18. 记录交易流水 - 手续费 await this.transactionRepo.create(tx, { orderId: order.id, accountSequence, transactionType: 'FEE', assetType: 'GREEN_POINT', amount: feeAmount, memo: '卖出手续费,注入积分股池', }); // 19. 发布事件 - 触发 mining-service 执行销毁 await this.eventPublisher.publish('trading.trade-completed', { eventId: uuid(), orderNo: order.orderNo, orderType: 'SELL', accountSequence, shareAmount: shareAmount.toString(), burnAmount: burnAmount.toString(), greenPointsAmount: grossAmount.toString(), feeAmount: feeAmount.toString(), executedAt: new Date().toISOString(), }); // 20. 更新K线数据 await this.updateKlineData(tx, currentPrice, shareAmount, 'SELL'); return { orderId: order.id, orderNo: order.orderNo, shareAmount: shareAmount.toString(), burnAmount: burnAmount.toString(), effectiveAmount: effectiveAmount.toString(), grossAmount: grossAmount.toString(), feeAmount: feeAmount.toString(), netAmount: netAmount.toString(), multiplier: multiplier.toString(), price: currentPrice.toString(), }; }); } /** * 计算卖出倍数 * 倍数 = (100亿 - 销毁量) ÷ (200万 - 流通池量) */ private calculateSellMultiplier(state: MiningState): Decimal { const totalToBurn = new Decimal('10000000000'); // 100亿 const originalPool = new Decimal('2000000'); // 200万 const numerator = totalToBurn.minus(state.blackHoleAmount); const denominator = originalPool.minus(state.circulationPool); if (denominator.isZero() || denominator.isNegative()) { throw new TradingException('流通池已满,无法卖出'); } return numerator.dividedBy(denominator); } ``` ### 4.2 买入交易逻辑 ```typescript /** * 买入积分股 */ async executeBuyOrder(command: BuySharesCommand): Promise { const { accountSequence, greenPointsAmount } = command; return await this.unitOfWork.runInTransaction(async (tx) => { // 1. 获取当前状态 const state = await this.getMiningState(); const currentPrice = new Decimal(state.currentPrice); // 2. 计算手续费(10%) const feeRate = await this.getFeeRate('BUY'); const feeAmount = greenPointsAmount.multipliedBy(feeRate); // 3. 计算净额(用于买入的金额) const netAmount = greenPointsAmount.minus(feeAmount); // 4. 计算可买入积分股数量 // 从流通池购买 const shareAmount = netAmount.dividedBy(currentPrice); // 5. 验证流通池余额 const circulationPool = await this.getCirculationPool(); if (circulationPool.lessThan(shareAmount)) { throw new InsufficientLiquidityException('流通池积分股不足'); } // 6. 验证用户绿积分余额(调用 wallet-service) const hasBalance = await this.checkGreenPointsBalance(accountSequence, greenPointsAmount); if (!hasBalance) { throw new InsufficientBalanceException('绿积分余额不足'); } // 7. 创建订单 const order = await this.orderRepo.create(tx, { orderNo: generateOrderNo(), accountSequence, orderType: 'BUY', orderStatus: 'COMPLETED', shareAmount, priceAtOrder: currentPrice, executionPrice: currentPrice, greenPointsAmount, feeAmount, feeRate, netAmount, executedAt: new Date(), }); // 8. 扣减用户绿积分 await this.deductGreenPoints(accountSequence, greenPointsAmount); // 9. 从流通池扣减积分股 await this.updateCirculationPool(tx, shareAmount.negated()); // 10. 增加用户积分股余额 await this.shareAccountRepo.addBalance(tx, accountSequence, shareAmount); // 11. 绿积分进入积分股池 await this.injectToSharePool(tx, netAmount); // 12. 手续费也进入积分股池 await this.injectFeeToSharePool(tx, feeAmount); // 13. 记录交易流水 await this.createBuyTransactions(tx, order, shareAmount, greenPointsAmount, feeAmount, netAmount); // 14. 发布事件 await this.eventPublisher.publish('trading.trade-completed', { eventId: uuid(), orderNo: order.orderNo, orderType: 'BUY', accountSequence, shareAmount: shareAmount.toString(), greenPointsAmount: greenPointsAmount.toString(), feeAmount: feeAmount.toString(), executedAt: new Date().toISOString(), }); // 15. 更新K线数据 await this.updateKlineData(tx, currentPrice, shareAmount, 'BUY'); return { orderId: order.id, orderNo: order.orderNo, shareAmount: shareAmount.toString(), grossAmount: greenPointsAmount.toString(), feeAmount: feeAmount.toString(), netAmount: netAmount.toString(), price: currentPrice.toString(), }; }); } ``` ### 4.3 K线数据聚合 ```typescript /** * K线聚合定时器 * 支持周期:1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w, 1M, 1Q, 1Y */ @Cron('* * * * *') // 每分钟 async aggregateKlineData(): Promise { const now = new Date(); // 聚合各周期K线 await this.aggregate1MinuteKline(now); if (now.getMinutes() % 5 === 0) { await this.aggregate5MinuteKline(now); } if (now.getMinutes() % 15 === 0) { await this.aggregate15MinuteKline(now); } if (now.getMinutes() % 30 === 0) { await this.aggregate30MinuteKline(now); } if (now.getMinutes() === 0) { await this.aggregate1HourKline(now); if (now.getHours() % 4 === 0) { await this.aggregate4HourKline(now); } if (now.getHours() === 0) { await this.aggregate1DayKline(now); // 周、月、季、年在日线基础上聚合 } } } /** * 聚合1分钟K线 */ private async aggregate1MinuteKline(endTime: Date): Promise { const startTime = subMinutes(endTime, 1); // 获取该分钟内的所有价格快照 const ticks = await this.priceTickRepo.findByTimeRange(startTime, endTime); if (ticks.length === 0) { // 无交易,使用上一个K线的收盘价 const lastKline = await this.klineRepo.getLastKline('1m'); if (lastKline) { await this.klineRepo.create({ periodType: '1m', periodStart: startTime, periodEnd: endTime, openPrice: lastKline.closePrice, highPrice: lastKline.closePrice, lowPrice: lastKline.closePrice, closePrice: lastKline.closePrice, volume: new Decimal(0), tradeCount: 0, }); } return; } // 计算OHLC const openPrice = ticks[0].price; const closePrice = ticks[ticks.length - 1].price; const highPrice = Decimal.max(...ticks.map(t => t.price)); const lowPrice = Decimal.min(...ticks.map(t => t.price)); const volume = ticks.reduce((sum, t) => sum.plus(t.minuteVolume), new Decimal(0)); const tradeCount = ticks.reduce((sum, t) => sum + t.minuteTradeCount, 0); await this.klineRepo.create({ periodType: '1m', periodStart: startTime, periodEnd: endTime, openPrice, highPrice, lowPrice, closePrice, volume, tradeCount, }); // 更新缓存 await this.klineCache.updateLatest('1m', { openPrice: openPrice.toString(), highPrice: highPrice.toString(), lowPrice: lowPrice.toString(), closePrice: closePrice.toString(), volume: volume.toString(), }); } ``` --- ## 5. 服务间通信 ### 5.1 订阅的事件 | Topic | 来源服务 | 数据内容 | 处理方式 | |-------|---------|---------|---------| | `mining.price-updated` | mining-service | 价格更新 | 更新本地价格缓存 | | `mining.shares-burned` | mining-service | 销毁事件 | 记录销毁对交易的影响 | ### 5.2 发布的事件 | Topic | 事件类型 | 订阅者 | |-------|---------|-------| | `trading.trade-completed` | TradeCompleted | mining-service(更新流通池、触发销毁) | | `trading.kline-updated` | KlineUpdated | mining-admin(实时图表) | ### 5.3 与 wallet-service 交互 ```typescript // 买入时:扣减绿积分 await this.walletClient.deductGreenPoints(accountSequence, amount, orderId); // 卖出时:增加绿积分 await this.walletClient.creditGreenPoints(accountSequence, amount, orderId); ``` --- ## 6. Redis 缓存结构 ```typescript // 当前价格缓存 interface PriceCache { key: 'trading:price:current'; ttl: 10; // 10秒 data: { price: string; updatedAt: string; }; } // K线缓存(各周期最新一根) interface KlineCache { key: `trading:kline:${periodType}:latest`; ttl: 60; // 60秒 data: KlineBar; } // K线历史缓存(按需加载) interface KlineHistoryCache { key: `trading:kline:${periodType}:history`; ttl: 300; // 5分钟 data: KlineBar[]; } ``` --- ## 7. K线周期说明 | 周期代码 | 说明 | 聚合频率 | |---------|------|---------| | `1m` | 1分钟 | 每分钟 | | `5m` | 5分钟 | 每5分钟 | | `15m` | 15分钟 | 每15分钟 | | `30m` | 30分钟 | 每30分钟 | | `1h` | 1小时 | 每小时 | | `4h` | 4小时 | 每4小时 | | `1d` | 1天 | 每天0点 | | `1w` | 1周 | 每周一0点 | | `1M` | 1月 | 每月1号0点 | | `1Q` | 1季度 | 每季度首日 | | `1Y` | 1年 | 每年1月1日 | --- ## 8. 关键计算公式汇总 ``` 卖出倍数 = (100亿 - 销毁量) ÷ (200万 - 流通池量) 卖出销毁量 = 卖出积分股 × 倍数 卖出交易额 = (卖出量 + 卖出销毁量) × 积分股价 买入获得量 = (绿积分 - 手续费) ÷ 积分股价 手续费 = 交易额 × 10% ``` --- ## 9. 关键注意事项 ### 9.1 价格同步 - 从 mining-service 获取实时价格 - 本地缓存价格,TTL 10秒 - 下单时锁定当前价格 ### 9.2 流通池管理 - 卖出:积分股进入流通池 - 买入:从流通池购买 - 与 mining-service 保持同步 ### 9.3 原子性 - 交易流水与余额变更在同一事务 - 发布事件使用 Outbox Pattern ### 9.4 K线精度 - 价格使用 `DECIMAL(30,18)` - 成交量使用 `DECIMAL(30,10)` --- ## 10. 开发检查清单 - [ ] 实现卖出交易逻辑(含销毁计算) - [ ] 实现买入交易逻辑 - [ ] 实现交易明细账记录 - [ ] 实现K线聚合(所有周期) - [ ] 实现价格查询API - [ ] 实现K线查询API - [ ] 配置与 wallet-service 交互 - [ ] 配置与 mining-service 事件同步 - [ ] 编写单元测试 - [ ] 性能测试K线查询 --- ## 11. 启动命令 ```bash # 开发环境 npm run start:dev # 生成 Prisma Client npx prisma generate # 运行迁移 npx prisma migrate dev # 生产环境 npm run build && npm run start:prod ```