feat(c2c): 添加expireOrder事务流程的详细调试日志
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
627c3c943c
commit
49ba2fcb19
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue