diff --git a/apps/server/src/modules/acquisition-organize/services/acquisition.ts b/apps/server/src/modules/acquisition-organize/services/acquisition.ts index edebcf38..d215091e 100644 --- a/apps/server/src/modules/acquisition-organize/services/acquisition.ts +++ b/apps/server/src/modules/acquisition-organize/services/acquisition.ts @@ -233,17 +233,24 @@ export async function createSlotsFromTemplates( /** 追踪中的订阅里没有自己收录版本的,逐个补建;返回是否建了 */ async function backfillSubscriptionSlots( - exec: Exec, + db: AoDeps["ctx"]["db"], subs: readonly SubscriptionRow[], slots: readonly Pick[], ): Promise { let made = 0; for (const s of subs) - if (s.media_entity_id && !slots.some((x) => x.subscription_id === s.id)) - made += await ensureSubscriptionSlot(exec, s); + if (s.media_entity_id && !slots.some((x) => x.subscription_id === s.id)) made += await ensureSlotLocked(db, s); return made > 0; } +/** 评估路径上补建:锁订阅行,免得并发的两次评估各建一个 */ +function ensureSlotLocked(db: AoDeps["ctx"]["db"], sub: SubscriptionRow): Promise { + return db.transaction().execute(async (trx) => { + await trx.selectFrom("subscription").select("id").where("id", "=", sub.id).forUpdate().execute(); + return ensureSubscriptionSlot(trx, sub); + }); +} + /** 订阅没有收录策略模板时的隐式缺省收录版本:无硬条件,表单的质量预设 / 包含排除随后由订阅参数写入 */ export const DEFAULT_SLOT_TEMPLATE: SlotTemplate = { name: "默认版本" }; @@ -377,7 +384,7 @@ async function reevaluate(c: Ctx, input: EvaluateInput): Promise { }); expect(ok.body.data).toMatchObject({ revision: 2, softPreferences: { hdr: ["DV"] } }); const list = await h.api("GET", `/acquisition-slots?mediaEntityId=${e}`); - expect(list.body.data.items).toHaveLength(1); + // 新建订阅时已自动建了该订阅自己的「默认版本」 + expect(list.body.data.items.map((x: { name: string }) => x.name).sort()).toEqual(["1080p", "默认版本"].sort()); expect(h.engine.enqueued.some((q) => q.task === "evaluateAcquisition")).toBe(true); });