fix: avoid cross-module write lock inversion
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { createHash } from 'node:crypto';
|
||||
import {
|
||||
InjectSaasAction,
|
||||
InjectSaasCrud,
|
||||
@@ -56,20 +57,24 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
||||
const unitPrice = decimal(goods.price, '商品价格');
|
||||
const amount = multiplyDecimal(unitPrice, input.quantity);
|
||||
|
||||
// The order row is kept in the outer managed transaction. If the remote
|
||||
// stock reservation fails, Runtime rolls this local CRUD mutation back.
|
||||
// Complete the remote write before the local eventized CRUD acquires the
|
||||
// tenant-wide outbox sequence lock. Holding that lock across a synchronous
|
||||
// module call would make the provider wait for this transaction itself.
|
||||
await this.reserveStock(
|
||||
{
|
||||
goodsId: input.goodsId,
|
||||
quantity: input.quantity,
|
||||
},
|
||||
{ idempotencyKey: businessKey('reserve', input.orderNo) }
|
||||
);
|
||||
|
||||
const order = await this.orders.add({
|
||||
order_no: input.orderNo,
|
||||
goods_id: String(input.goodsId),
|
||||
quantity: input.quantity,
|
||||
unit_price: unitPrice,
|
||||
amount,
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
await this.reserveStock({
|
||||
goodsId: input.goodsId,
|
||||
quantity: input.quantity,
|
||||
status: 'created',
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -98,6 +103,16 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
||||
return { id: input.id, status: 'cancelled', restoredStock: 0 };
|
||||
}
|
||||
|
||||
const goodsId = safeInteger(order.goods_id, '商品 ID');
|
||||
await this.releaseStock(
|
||||
{
|
||||
goodsId,
|
||||
quantity: order.quantity,
|
||||
orderNo: order.order_no,
|
||||
},
|
||||
{ idempotencyKey: businessKey('cancel', String(order.id)) }
|
||||
);
|
||||
|
||||
const update = await this.orders.update({
|
||||
id: order.id,
|
||||
status: 'cancelled',
|
||||
@@ -106,13 +121,6 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
||||
return { id: input.id, status: 'cancelled', restoredStock: 0 };
|
||||
}
|
||||
|
||||
const goodsId = safeInteger(order.goods_id, '商品 ID');
|
||||
await this.releaseStock({
|
||||
goodsId,
|
||||
quantity: order.quantity,
|
||||
orderNo: order.order_no,
|
||||
});
|
||||
|
||||
return {
|
||||
id: input.id,
|
||||
status: 'cancelled',
|
||||
@@ -134,6 +142,12 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
||||
}
|
||||
}
|
||||
|
||||
function businessKey(operation: string, identity: string): string {
|
||||
return createHash('sha256')
|
||||
.update(`${operation}:${identity}`, 'utf8')
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
function decimal(value: unknown, label: string): string {
|
||||
const normalized = String(value ?? '').trim();
|
||||
if (!/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(normalized)) {
|
||||
|
||||
Reference in New Issue
Block a user