feat(c2c): 添加expireOrder事务流程的详细调试日志

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-01 03:09:50 -08:00
parent 627c3c943c
commit 49ba2fcb19
1 changed files with 53 additions and 17 deletions

View File

@ -682,7 +682,15 @@ export class C2cService {
async processExpiredOrders(): Promise<number> { async processExpiredOrders(): Promise<number> {
// 计算 PENDING 订单的过期截止时间 // 计算 PENDING 订单的过期截止时间
const pendingCutoff = new Date(Date.now() - DEFAULT_PENDING_TIMEOUT_HOURS * 60 * 60 * 1000); const pendingCutoff = new Date(Date.now() - DEFAULT_PENDING_TIMEOUT_HOURS * 60 * 60 * 1000);
this.logger.debug(`[EXPIRY] 开始扫描超时订单, PENDING截止时间: ${pendingCutoff.toISOString()}`);
const expiredOrders = await this.c2cOrderRepository.findExpiredOrders(pendingCutoff); const expiredOrders = await this.c2cOrderRepository.findExpiredOrders(pendingCutoff);
if (expiredOrders.length === 0) {
return 0;
}
this.logger.log(`[EXPIRY] 发现 ${expiredOrders.length} 个超时订单: ${expiredOrders.map(o => `${o.orderNo}(${o.type}/${o.status})`).join(', ')}`);
let processedCount = 0; let processedCount = 0;
for (const order of expiredOrders) { for (const order of expiredOrders) {
@ -690,14 +698,11 @@ export class C2cService {
await this.expireOrder(order); await this.expireOrder(order);
processedCount++; processedCount++;
} catch (error) { } catch (error) {
this.logger.error(`处理超时订单失败: ${order.orderNo}`, error); this.logger.error(`[EXPIRY] 处理超时订单失败: ${order.orderNo}`, error);
} }
} }
if (processedCount > 0) { this.logger.log(`[EXPIRY] 本轮处理完成: 成功 ${processedCount}/${expiredOrders.length}`);
this.logger.log(`处理了 ${processedCount} 个超时订单`);
}
return processedCount; return processedCount;
} }
@ -709,17 +714,23 @@ export class C2cService {
const lockKey = `c2c:expire:${order.orderNo}`; const lockKey = `c2c:expire:${order.orderNo}`;
const lockValue = await this.redis.acquireLock(lockKey, 30); const lockValue = await this.redis.acquireLock(lockKey, 30);
if (!lockValue) { if (!lockValue) {
return; // 其他进程正在处理 this.logger.debug(`[EXPIRY] 订单 ${order.orderNo} 正被其他进程处理,跳过`);
return;
} }
try { try {
// 重新获取订单,确保状态一致 // 重新获取订单,确保状态一致
const freshOrder = await this.c2cOrderRepository.findByOrderNo(order.orderNo); const freshOrder = await this.c2cOrderRepository.findByOrderNo(order.orderNo);
if (!freshOrder || ( if (!freshOrder) {
this.logger.warn(`[EXPIRY] 订单 ${order.orderNo} 不存在,跳过`);
return;
}
if (
freshOrder.status !== C2C_ORDER_STATUS.PENDING && freshOrder.status !== C2C_ORDER_STATUS.PENDING &&
freshOrder.status !== C2C_ORDER_STATUS.MATCHED && freshOrder.status !== C2C_ORDER_STATUS.MATCHED &&
freshOrder.status !== C2C_ORDER_STATUS.PAID freshOrder.status !== C2C_ORDER_STATUS.PAID
)) { ) {
this.logger.debug(`[EXPIRY] 订单 ${order.orderNo} 状态已变为 ${freshOrder.status},无需处理`);
return; return;
} }
@ -728,27 +739,39 @@ export class C2cService {
const isSell = freshOrder.type === C2C_ORDER_TYPE.SELL; const isSell = freshOrder.type === C2C_ORDER_TYPE.SELL;
let restoreOrderNo: string | null = null; let restoreOrderNo: string | null = null;
this.logger.log(
`[EXPIRY] 开始处理订单 ${freshOrder.orderNo}: ` +
`类型=${freshOrder.type}, 状态=${freshOrder.status}, ` +
`数量=${freshOrder.quantity}, 价格=${freshOrder.price}, ` +
`maker=${freshOrder.makerAccountSequence}, taker=${freshOrder.takerAccountSequence || '无'}, ` +
`需恢复=${shouldRestore}`,
);
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
// 1. 解冻卖方的积分值(如果需要) // 1. 解冻卖方的积分值(如果需要)
if (freshOrder.status === C2C_ORDER_STATUS.PENDING) { if (freshOrder.status === C2C_ORDER_STATUS.PENDING) {
// PENDING SELL解冻 maker
if (isSell) { if (isSell) {
this.logger.log(`[EXPIRY][TX] 步骤1: 解冻 PENDING SELL maker ${freshOrder.makerAccountSequence} 的 frozenCash -= ${quantityDecimal}`);
await tx.tradingAccount.update({ await tx.tradingAccount.update({
where: { accountSequence: freshOrder.makerAccountSequence }, where: { accountSequence: freshOrder.makerAccountSequence },
data: { frozenCash: { decrement: quantityDecimal.toNumber() } }, data: { frozenCash: { decrement: quantityDecimal.toNumber() } },
}); });
} else {
this.logger.debug(`[EXPIRY][TX] 步骤1: PENDING BUY 无冻结资产,跳过解冻`);
} }
// PENDING BUY无冻结
} else if (!isSell && freshOrder.takerAccountSequence) { } else if (!isSell && freshOrder.takerAccountSequence) {
// MATCHED/PAID BUY解冻 taker卖方 // MATCHED/PAID BUY解冻 taker卖方
this.logger.log(`[EXPIRY][TX] 步骤1: 解冻 ${freshOrder.status} BUY taker ${freshOrder.takerAccountSequence} 的 frozenCash -= ${quantityDecimal}`);
await tx.tradingAccount.update({ await tx.tradingAccount.update({
where: { accountSequence: freshOrder.takerAccountSequence }, where: { accountSequence: freshOrder.takerAccountSequence },
data: { frozenCash: { decrement: quantityDecimal.toNumber() } }, data: { frozenCash: { decrement: quantityDecimal.toNumber() } },
}); });
} else if (isSell && shouldRestore) {
this.logger.log(`[EXPIRY][TX] 步骤1: ${freshOrder.status} SELL 保持冻结不变,冻结量将转给恢复的 PENDING 订单`);
} }
// MATCHED/PAID SELL + shouldRestore不解冻冻结直接转给新 PENDING 订单
// 2. 标记订单为过期 // 2. 标记订单为过期
this.logger.log(`[EXPIRY][TX] 步骤2: 标记订单 ${freshOrder.orderNo} 状态 ${freshOrder.status} -> EXPIRED`);
await tx.c2cOrder.update({ await tx.c2cOrder.update({
where: { orderNo: freshOrder.orderNo }, where: { orderNo: freshOrder.orderNo },
data: { data: {
@ -761,6 +784,12 @@ export class C2cService {
if (shouldRestore) { if (shouldRestore) {
const priceDecimal = new Decimal(freshOrder.price); const priceDecimal = new Decimal(freshOrder.price);
restoreOrderNo = this.generateOrderNo(); restoreOrderNo = this.generateOrderNo();
const totalAmount = priceDecimal.mul(quantityDecimal).toString();
this.logger.log(
`[EXPIRY][TX] 步骤3: 恢复为新 PENDING 订单 ${restoreOrderNo}, ` +
`类型=${freshOrder.type}, maker=${freshOrder.makerAccountSequence}, ` +
`数量=${quantityDecimal}, 总金额=${totalAmount}`,
);
await tx.c2cOrder.create({ await tx.c2cOrder.create({
data: { data: {
orderNo: restoreOrderNo, orderNo: restoreOrderNo,
@ -772,7 +801,7 @@ export class C2cService {
makerNickname: freshOrder.makerNickname, makerNickname: freshOrder.makerNickname,
price: freshOrder.price, price: freshOrder.price,
quantity: quantityDecimal.toString(), quantity: quantityDecimal.toString(),
totalAmount: priceDecimal.mul(quantityDecimal).toString(), totalAmount,
minAmount: '0', minAmount: '0',
maxAmount: '0', maxAmount: '0',
paymentMethod: isSell ? freshOrder.paymentMethod : null, paymentMethod: isSell ? freshOrder.paymentMethod : null,
@ -783,17 +812,24 @@ export class C2cService {
remark: freshOrder.remark, remark: freshOrder.remark,
}, },
}); });
} else {
this.logger.debug(`[EXPIRY][TX] 步骤3: PENDING 订单无需恢复`);
} }
}); });
// 事务成功后记日志 // 事务成功后记日志
this.logger.log(`C2C订单已过期: ${freshOrder.orderNo}, 原状态: ${freshOrder.status}`); this.logger.log(
if (restoreOrderNo) { `[EXPIRY] 事务提交成功: 订单 ${freshOrder.orderNo} (${freshOrder.type}/${freshOrder.status}) -> EXPIRED` +
this.logger.log(`过期恢复: 订单 ${freshOrder.orderNo} 已恢复为新 PENDING 订单 ${restoreOrderNo}, 数量: ${quantityDecimal}`); (restoreOrderNo ? `, 恢复为 ${restoreOrderNo} (PENDING, 数量=${quantityDecimal})` : ''),
} );
} catch (error: any) { } catch (error: any) {
// 事务失败 = 全部回滚,不会出现不一致状态 // 事务失败 = 全部回滚,不会出现不一致状态
this.logger.error(`expireOrder 事务失败: ${order.orderNo}: ${error.message}`); this.logger.error(
`[EXPIRY] 事务失败,已回滚: 订单=${order.orderNo}, ` +
`错误=${error.message}`,
error.stack,
);
throw error;
} finally { } finally {
await this.redis.releaseLock(lockKey, lockValue); await this.redis.releaseLock(lockKey, lockValue);
} }