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, {