diff --git a/backend/services/trading-service/src/application/services/order.service.ts b/backend/services/trading-service/src/application/services/order.service.ts index 95ccb5f9..2967f7d9 100644 --- a/backend/services/trading-service/src/application/services/order.service.ts +++ b/backend/services/trading-service/src/application/services/order.service.ts @@ -196,28 +196,137 @@ export class OrderService { // 卖方实际收到:总收益 - 手续费 const sellerReceiveAmount = new Money(sellerGrossAmount.value.minus(tradeFee.value)); - // 保存成交记录(包含销毁信息、手续费和来源标识) - // quantity 存储有效积分股(含销毁倍数),originalQuantity 存储原始卖出数量 - await this.prisma.trade.create({ - data: { - tradeNo: match.trade.tradeNo, - buyOrderId: match.buyOrder.id!, - sellOrderId: match.sellOrder.id!, - buyerSequence: match.buyOrder.accountSequence, - sellerSequence: match.sellOrder.accountSequence, - price: match.trade.price.value, - quantity: effectiveQuantity.value, // 有效积分股(倍数后) - originalQuantity: tradeQuantity.value, // 原始卖出数量 - burnQuantity: burnQuantity.value, - effectiveQty: effectiveQuantity.value, - amount: sellerReceiveAmount.value, - fee: tradeFee.value, - buyerSource: match.buyOrder.source, - sellerSource: match.sellOrder.source, - }, - }); + // ========== 使用事务确保成交记录、订单状态、账户余额的原子性 ========== + // 修复 Bug:之前没有使用事务,导致成交记录创建成功但账户余额更新可能失败 + await this.prisma.$transaction(async (tx) => { + // 1. 保存成交记录 + await tx.trade.create({ + data: { + tradeNo: match.trade.tradeNo, + buyOrderId: match.buyOrder.id!, + sellOrderId: match.sellOrder.id!, + buyerSequence: match.buyOrder.accountSequence, + sellerSequence: match.sellOrder.accountSequence, + price: match.trade.price.value, + quantity: effectiveQuantity.value, + originalQuantity: tradeQuantity.value, + burnQuantity: burnQuantity.value, + effectiveQty: effectiveQuantity.value, + amount: sellerReceiveAmount.value, + fee: tradeFee.value, + buyerSource: match.buyOrder.source, + sellerSource: match.sellOrder.source, + }, + }); - // 卖出的积分股进入流通池 + // 2. 更新买单状态 + await tx.order.update({ + where: { id: match.buyOrder.id! }, + data: { + status: match.buyOrder.status, + filledQuantity: match.buyOrder.filledQuantity.value, + remainingQuantity: match.buyOrder.remainingQuantity.value, + averagePrice: match.buyOrder.averagePrice.value, + totalAmount: match.buyOrder.totalAmount.value, + completedAt: match.buyOrder.completedAt, + }, + }); + + // 3. 更新卖单状态(含销毁信息) + await tx.order.update({ + where: { id: match.sellOrder.id! }, + data: { + status: match.sellOrder.status, + filledQuantity: match.sellOrder.filledQuantity.value, + remainingQuantity: match.sellOrder.remainingQuantity.value, + averagePrice: match.sellOrder.averagePrice.value, + totalAmount: match.sellOrder.totalAmount.value, + burnQuantity: burnQuantity.value, + burnMultiplier: burnQuantity.isZero() + ? 0 + : burnQuantity.value.dividedBy(match.sellOrder.filledQuantity.value), + effectiveQuantity: effectiveQuantity.value, + completedAt: match.sellOrder.completedAt, + }, + }); + + // 4. 更新买方账户(扣除冻结现金,增加积分股) + const buyerAccountRecord = await tx.tradingAccount.findUnique({ + where: { accountSequence: match.buyOrder.accountSequence }, + }); + if (buyerAccountRecord) { + const newFrozenCash = Number(buyerAccountRecord.frozenCash) - buyerPayAmount.value.toNumber(); + const newCashBalance = Number(buyerAccountRecord.cashBalance) - buyerPayAmount.value.toNumber(); + const newShareBalance = Number(buyerAccountRecord.shareBalance) + tradeQuantity.value.toNumber(); + const newTotalBought = Number(buyerAccountRecord.totalBought) + tradeQuantity.value.toNumber(); + + await tx.tradingAccount.update({ + where: { accountSequence: match.buyOrder.accountSequence }, + data: { + frozenCash: newFrozenCash, + cashBalance: newCashBalance, + shareBalance: newShareBalance, + totalBought: newTotalBought, + }, + }); + + // 记录买方交易流水 + await tx.tradingTransaction.create({ + data: { + accountSequence: match.buyOrder.accountSequence, + type: 'BUY', + assetType: 'SHARE', + amount: tradeQuantity.value, + balanceBefore: buyerAccountRecord.shareBalance, + balanceAfter: newShareBalance, + referenceId: match.trade.tradeNo, + referenceType: 'TRADE', + description: '买入成交', + }, + }); + } + + // 5. 更新卖方账户(扣除冻结积分股,增加现金) + const sellerAccountRecord = await tx.tradingAccount.findUnique({ + where: { accountSequence: match.sellOrder.accountSequence }, + }); + if (sellerAccountRecord) { + const newFrozenShares = Number(sellerAccountRecord.frozenShares) - tradeQuantity.value.toNumber(); + const newShareBalance = Number(sellerAccountRecord.shareBalance) - tradeQuantity.value.toNumber(); + const newCashBalance = Number(sellerAccountRecord.cashBalance) + sellerReceiveAmount.value.toNumber(); + const newTotalSold = Number(sellerAccountRecord.totalSold) + tradeQuantity.value.toNumber(); + + await tx.tradingAccount.update({ + where: { accountSequence: match.sellOrder.accountSequence }, + data: { + frozenShares: newFrozenShares, + shareBalance: newShareBalance, + cashBalance: newCashBalance, + totalSold: newTotalSold, + }, + }); + + // 记录卖方交易流水 + await tx.tradingTransaction.create({ + data: { + accountSequence: match.sellOrder.accountSequence, + type: 'SELL', + assetType: 'SHARE', + amount: tradeQuantity.value, + balanceBefore: sellerAccountRecord.shareBalance, + balanceAfter: newShareBalance, + referenceId: match.trade.tradeNo, + referenceType: 'TRADE', + description: '卖出成交', + }, + }); + } + }, { + timeout: 30000, // 30秒超时 + }); + // ========== 事务结束 ========== + + // 卖出的积分股进入流通池(非关键操作,可以在事务外执行) try { await this.circulationPoolRepository.addSharesFromSell( tradeQuantity, @@ -229,7 +338,7 @@ export class OrderService { this.logger.warn(`Failed to add shares to circulation pool: ${error}`); } - // 10%手续费进入积分股池(greenPoints) + // 10%手续费进入积分股池(非关键操作,可以在事务外执行) try { await this.sharePoolRepository.feeIn( tradeFee, @@ -246,24 +355,6 @@ export class OrderService { this.logger.error(`Failed to add trade fee to share pool: ${error}`); } - // 更新订单(包含销毁信息) - await this.orderRepository.save(match.buyOrder); - await this.orderRepository.saveWithBurnInfo(match.sellOrder, burnQuantity, effectiveQuantity); - - // 更新买方账户(支付原始交易额) - const buyerAccount = await this.accountRepository.findByAccountSequence(match.buyOrder.accountSequence); - if (buyerAccount) { - buyerAccount.executeBuy(tradeQuantity, buyerPayAmount, match.trade.tradeNo); - await this.accountRepository.save(buyerAccount); - } - - // 更新卖方账户(收到扣除手续费后的金额) - const sellerAccount = await this.accountRepository.findByAccountSequence(match.sellOrder.accountSequence); - if (sellerAccount) { - sellerAccount.executeSell(tradeQuantity, sellerReceiveAmount, match.trade.tradeNo); - await this.accountRepository.save(sellerAccount); - } - this.logger.log( `Trade executed: ${match.trade.tradeNo}, price=${match.trade.price.toFixed(8)}, ` + `qty=${tradeQuantity.toFixed(8)}, burn=${burnQuantity.toFixed(8)}, ` +