fix: avoid cross-module write lock inversion
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Provide } from '@midwayjs/core';
|
import { Provide } from '@midwayjs/core';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
import {
|
import {
|
||||||
InjectSaasAction,
|
InjectSaasAction,
|
||||||
InjectSaasCrud,
|
InjectSaasCrud,
|
||||||
@@ -56,20 +57,24 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
|||||||
const unitPrice = decimal(goods.price, '商品价格');
|
const unitPrice = decimal(goods.price, '商品价格');
|
||||||
const amount = multiplyDecimal(unitPrice, input.quantity);
|
const amount = multiplyDecimal(unitPrice, input.quantity);
|
||||||
|
|
||||||
// The order row is kept in the outer managed transaction. If the remote
|
// Complete the remote write before the local eventized CRUD acquires the
|
||||||
// stock reservation fails, Runtime rolls this local CRUD mutation back.
|
// 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({
|
const order = await this.orders.add({
|
||||||
order_no: input.orderNo,
|
order_no: input.orderNo,
|
||||||
goods_id: String(input.goodsId),
|
goods_id: String(input.goodsId),
|
||||||
quantity: input.quantity,
|
quantity: input.quantity,
|
||||||
unit_price: unitPrice,
|
unit_price: unitPrice,
|
||||||
amount,
|
amount,
|
||||||
status: 'pending',
|
status: 'created',
|
||||||
});
|
|
||||||
|
|
||||||
await this.reserveStock({
|
|
||||||
goodsId: input.goodsId,
|
|
||||||
quantity: input.quantity,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -98,6 +103,16 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
|||||||
return { id: input.id, status: 'cancelled', restoredStock: 0 };
|
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({
|
const update = await this.orders.update({
|
||||||
id: order.id,
|
id: order.id,
|
||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
@@ -106,13 +121,6 @@ export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
|||||||
return { id: input.id, status: 'cancelled', restoredStock: 0 };
|
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 {
|
return {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
status: 'cancelled',
|
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 {
|
function decimal(value: unknown, label: string): string {
|
||||||
const normalized = String(value ?? '').trim();
|
const normalized = String(value ?? '').trim();
|
||||||
if (!/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(normalized)) {
|
if (!/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(normalized)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user