From 8fb28b98c75367f644f4080e442fa812ef8c5521 Mon Sep 17 00:00:00 2001 From: zhaoxing <2558691827@qq.com> Date: Sun, 1 Nov 2026 11:12:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(work-order):=20=E6=94=B6=E7=B4=A7=E8=A1=A5?= =?UTF-8?q?=E8=B5=84=E6=96=99=E9=93=BE=E6=8E=A5=E8=BF=87=E6=9C=9F=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../work-order-material-link-repo.ts | 2 +- .../work-order-material-link-service.ts | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/backend/src/repositories/worker-platform/work-order-material-link-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-material-link-repo.ts index c8ffc530..79ef6438 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-material-link-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-material-link-repo.ts @@ -128,7 +128,7 @@ export async function markWorkOrderMaterialLinkUsedWithClient( const result = await client.query( `UPDATE work_order_material_links SET status = 'used', used_at = $2, updated_at = $2 - WHERE id = $1 AND status = 'active' + WHERE id = $1 AND status = 'active' AND expires_at > $2 RETURNING *`, [Number(linkId), now], ) diff --git a/apps/backend/src/services/worker-platform/work-order-material-link-service.ts b/apps/backend/src/services/worker-platform/work-order-material-link-service.ts index 6b31c569..4c9decf2 100644 --- a/apps/backend/src/services/worker-platform/work-order-material-link-service.ts +++ b/apps/backend/src/services/worker-platform/work-order-material-link-service.ts @@ -149,7 +149,6 @@ export async function createAdminWorkOrderMaterialLink( workOrderId: number | string, actorName = '', ) { - const now = nowIso() const { ttlMinutes } = getWorkerMaterialLinkConfig() return withTransaction(async (client) => { @@ -169,6 +168,8 @@ export async function createAdminWorkOrderMaterialLink( } assertWorkOrderAllowedForMaterialLink(order) + // 锁到手后再取时间,避免等锁期间旧链接过期却仍按旧时间复用。 + const now = nowIso() const existing = await findActiveWorkOrderMaterialLinkWithClient(client, order.id, now) if (existing) { return { @@ -254,7 +255,6 @@ export async function submitPublicWorkOrderMaterialLink(token: string, payload: }) } - const now = nowIso() const result = await withTransaction(async (client) => { // 先锁工单再锁链接,和后台生成链接保持同样的加锁顺序,避免死锁。 const order = await getWorkOrderForUpdateWithClient(client, preliminary.work_order_id) @@ -272,6 +272,8 @@ export async function submitPublicWorkOrderMaterialLink(token: string, payload: }) } + // 锁到手后再取时间:等锁期间链接可能已经过期,不能沿用进入事务前的时间。 + const now = nowIso() assertMaterialLinkRecordUsable(link, now) assertWorkOrderAllowedForMaterialLink(order) @@ -328,11 +330,13 @@ export async function submitPublicWorkOrderMaterialLink(token: string, payload: const used = await markWorkOrderMaterialLinkUsedWithClient(client, link.id, now) if (!used) { - // 并发提交时另一个请求已经把链接用掉,当前事务整体回滚。 - throw createHttpError('链接已使用,如需修改资料请联系客服重新获取', { - statusCode: 410, - errorCode: 'material_link_used', - }) + // 并发提交,或刚好在写入瞬间过期:按当前状态给出准确原因,事务整体回滚。 + const latest = await getWorkOrderMaterialLinkForUpdateWithClient(client, link.id) + if (latest) { + const reason = resolveWorkOrderMaterialLinkUnavailableReason(latest, now) + if (reason) throwMaterialLinkUnavailable(reason) + } + throwMaterialLinkUnavailable('used') } await createWorkOrderEventWithClient(client, {