fix: 催办按任务类型分流 + 领取事务加锁防超发

- 临期/超期/自动释放查询按 task_type 区分完成口径:内容任务看 publish_url,截图任务看 result_submitted_at,截图任务不再被永久误判为未发布
- portal 领取改为事务内 SELECT ... FOR UPDATE 锁定可用内容,消除并发领取超发窗口
This commit is contained in:
ABAPPLO
2026-09-02 13:56:07 +08:00
parent e1005081fe
commit 5cb056507d
3 changed files with 192 additions and 87 deletions

View File

@@ -563,18 +563,6 @@ async function handlePost(request: Request) {
{ status: 400 },
);
}
const available = await db
.prepare(
`SELECT id FROM contents
WHERE task_id = ? AND status = 'available'
ORDER BY COALESCE(source_row, 999999), created_at, id
LIMIT ${quantity}`,
)
.bind(task.id)
.all<{ id: string }>();
if (available.results.length === 0) {
return Response.json({ error: "当前任务已领完" }, { status: 409 });
}
const partnerId = wecomIdentity
? (await resolveWecomClaimant(db, wecomIdentity.externalUserId)).partnerId
: `partner-ext-${hashText(
@@ -585,86 +573,102 @@ async function handlePost(request: Request) {
: (claimantIdentifier?.display ?? "");
const claimId = uid("claim");
const claimToken = crypto.randomUUID().replaceAll("-", "");
const statements: DatabaseStatement[] = [
wecomIdentity
? db
.prepare(
`INSERT INTO partners
(id, name, wecom_name, owner, claimed_total, completed_total,
wecom_external_user_id)
VALUES (?, ?, ?, '外部KOC', 0, 0, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
wecom_name = excluded.wecom_name,
wecom_external_user_id = excluded.wecom_external_user_id`,
)
.bind(
partnerId,
claimantName,
claimantName,
wecomIdentity.externalUserId,
)
: db
.prepare(
`INSERT INTO partners
(id, name, wecom_name, owner, claimed_total, completed_total)
VALUES (?, ?, ?, '外部KOC', 0, 0)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
wecom_name = excluded.wecom_name`,
)
.bind(partnerId, claimantName, claimantName),
db
const claimResult = await db.transaction(async (tx) => {
const available = await tx
.prepare(
`INSERT INTO claims
(id, task_id, partner_id, claimant_name, claim_token, quantity)
VALUES (?, ?, ?, ?, ?, ?)`,
`SELECT id FROM contents
WHERE task_id = ? AND status = 'available'
ORDER BY COALESCE(source_row, 999999), created_at, id
LIMIT ${quantity}
FOR UPDATE`,
)
.bind(
claimId,
task.id,
partnerId,
claimantName,
claimToken,
available.results.length,
),
];
for (const content of available.results) {
.bind(task.id)
.all<{ id: string }>();
if (available.results.length === 0) return null;
const statements: DatabaseStatement[] = [
wecomIdentity
? db
.prepare(
`INSERT INTO partners
(id, name, wecom_name, owner, claimed_total, completed_total,
wecom_external_user_id)
VALUES (?, ?, ?, '外部KOC', 0, 0, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
wecom_name = excluded.wecom_name,
wecom_external_user_id = excluded.wecom_external_user_id`,
)
.bind(
partnerId,
claimantName,
claimantName,
wecomIdentity.externalUserId,
)
: db
.prepare(
`INSERT INTO partners
(id, name, wecom_name, owner, claimed_total, completed_total)
VALUES (?, ?, ?, '外部KOC', 0, 0)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
wecom_name = excluded.wecom_name`,
)
.bind(partnerId, claimantName, claimantName),
db
.prepare(
`INSERT INTO claims
(id, task_id, partner_id, claimant_name, claim_token, quantity)
VALUES (?, ?, ?, ?, ?, ?)`,
)
.bind(
claimId,
task.id,
partnerId,
claimantName,
claimToken,
available.results.length,
),
];
for (const content of available.results) {
statements.push(
db
.prepare(
`INSERT INTO distributions
(id, task_id, content_id, partner_id, claim_id, status)
VALUES (?, ?, ?, ?, ?, 'claimed')`,
)
.bind(uid("dist"), task.id, content.id, partnerId, claimId),
db
.prepare(
`UPDATE contents SET status = 'allocated'
WHERE id = ? AND status = 'available'`,
)
.bind(content.id),
);
}
statements.push(
db
.prepare(
`INSERT INTO distributions
(id, task_id, content_id, partner_id, claim_id, status)
VALUES (?, ?, ?, ?, ?, 'claimed')`,
`UPDATE tasks SET claimed_quantity = claimed_quantity + ?
WHERE id = ?`,
)
.bind(uid("dist"), task.id, content.id, partnerId, claimId),
.bind(available.results.length, task.id),
db
.prepare(
`UPDATE contents SET status = 'allocated'
WHERE id = ? AND status = 'available'`,
`UPDATE partners SET claimed_total = claimed_total + ?
WHERE id = ?`,
)
.bind(content.id),
.bind(available.results.length, partnerId),
);
}
statements.push(
db
.prepare(
`UPDATE tasks SET claimed_quantity = claimed_quantity + ?
WHERE id = ?`,
)
.bind(available.results.length, task.id),
db
.prepare(
`UPDATE partners SET claimed_total = claimed_total + ?
WHERE id = ?`,
)
.bind(available.results.length, partnerId),
);
await db.batch(statements);
return Response.json({
claimToken,
claimedCount: available.results.length,
for (const statement of statements) {
await tx.execute(statement.sql, statement.values());
}
return { claimToken, claimedCount: available.results.length };
});
if (!claimResult) {
return Response.json({ error: "当前任务已领完" }, { status: 409 });
}
return Response.json(claimResult);
}
if (body.action === "create_delegation") {