diff --git a/scripts/replace-team-qrcode-url.js b/scripts/replace-team-qrcode-url.js index 2be7ccc..3f361b4 100644 --- a/scripts/replace-team-qrcode-url.js +++ b/scripts/replace-team-qrcode-url.js @@ -5,6 +5,8 @@ * 目的: * - 扫描 corp.team collection * - 将 team.qrcodes[].qrcode 中旧前缀替换为新前缀 + * - 扫描 corp.staff-qrcode collection + * - 将 staff-qrcode.archiveLink.url 中旧前缀替换为新前缀 * * 安全: * - 默认 dry-run(不落库) @@ -70,6 +72,7 @@ function createDefaultOptions() { return { dbName: "corp", collection: "team", + staffCollection: "staff-qrcode", batchDocs: 200, skipDocs: 0, limitDocs: 0, @@ -107,6 +110,7 @@ function parseArgs(argv) { if (!key) return; if (key === "dbname" || key === "db") opts.dbName = value; else if (key === "collection") opts.collection = value; + else if (key === "staffcollection") opts.staffCollection = value; else if (key === "mongouri" || key === "mongourl") opts.mongoUri = value; else if (key === "mongourienv") opts.mongoUriEnv = value; else if (key === "mongohost") opts.mongoHost = value; @@ -144,6 +148,9 @@ function parseArgs(argv) { } else if (a === "--collection") { opts.collection = takeValue(i); i++; + } else if (a === "--staffCollection") { + opts.staffCollection = takeValue(i); + i++; } else if (a === "--mongoUri" || a === "--mongoUrl" || a === "--mongoURL") { opts.mongoUri = takeValue(i); i++; @@ -290,6 +297,75 @@ function updateTeamDoc(doc, { oldPrefix, newPrefix }) { return { changed, nextQrcodes }; } +function updateStaffQrcodeDoc(doc, { oldPrefix, newPrefix }) { + const archiveLink = + doc && doc.archiveLink && typeof doc.archiveLink === "object" + ? doc.archiveLink + : null; + if (!archiveLink) { + return { changed: 0, nextArchiveLink: archiveLink }; + } + const current = archiveLink.url; + const next = replacePrefix(current, oldPrefix, newPrefix); + if (next === current) { + return { changed: 0, nextArchiveLink: archiveLink }; + } + return { + changed: 1, + nextArchiveLink: { ...archiveLink, url: next }, + }; +} + +async function processCollectionDocs({ + collection, + filter, + batchDocs, + sortById, + skipDocs, + limitDocs, + dryRun, + updateDoc, + buildUpdate, + describeDoc, +}) { + let cursor = collection.find(filter, { batchSize: batchDocs }); + if (sortById) cursor = cursor.sort({ _id: 1 }); + if (skipDocs) cursor = cursor.skip(skipDocs); + if (limitDocs) cursor = cursor.limit(limitDocs); + + let scanned = 0; + let matchedDocs = 0; + let replacedUrls = 0; + let batchIndex = 0; + + while (await cursor.hasNext()) { + const batch = []; + while (batch.length < batchDocs && (await cursor.hasNext())) { + batch.push(await cursor.next()); + } + if (batch.length === 0) break; + batchIndex++; + console.log( + `[step] ${collection.collectionName} batch#${batchIndex} 读取文档 ${batch.length} 条(已扫描 ${scanned} 条)` + ); + + for (const doc of batch) { + scanned++; + const { changed, ...payload } = updateDoc(doc); + if (changed === 0) continue; + matchedDocs++; + replacedUrls += changed; + console.log(`[match] ${collection.collectionName} ${describeDoc(doc)} replaced=${changed}`); + + if (!dryRun) { + await collection.updateOne({ _id: doc._id }, buildUpdate(payload)); + } + } + } + + return { scanned, matchedDocs, replacedUrls }; +} + async function main() { const opts = parseArgs(process.argv); applyEnvFallbacks(opts); @@ -336,52 +412,57 @@ async function main() { try { const db = client.db(opts.dbName); - const col = db.collection(opts.collection); + const teamCol = db.collection(opts.collection); + const staffCol = db.collection(opts.staffCollection); const filter = {}; if (isNonEmptyString(opts.corpId)) filter.corpId = opts.corpId; - console.log(`[step] collection=${opts.dbName}.${opts.collection} corpId=${opts.corpId || "全部"} mode=${opts.dryRun ? "dry-run" : "apply"}`); - let cursor = col.find(filter, { batchSize: opts.batchDocs }); - if (opts.sortById) cursor = cursor.sort({ _id: 1 }); - if (opts.skipDocs) cursor = cursor.skip(opts.skipDocs); - if (opts.limitDocs) cursor = cursor.limit(opts.limitDocs); - - let scanned = 0; - let matchedDocs = 0; - let replacedUrls = 0; - let batchIndex = 0; - - while (await cursor.hasNext()) { - const batch = []; - while (batch.length < opts.batchDocs && (await cursor.hasNext())) { - batch.push(await cursor.next()); - } - if (batch.length === 0) break; - batchIndex++; - console.log(`[step] batch#${batchIndex} 读取文档 ${batch.length} 条(已扫描 ${scanned} 条)`); - - for (const doc of batch) { - scanned++; - const { changed, nextQrcodes } = updateTeamDoc(doc, { + console.log( + `[step] collection=${opts.dbName}.${opts.collection} corpId=${opts.corpId || "全部"} mode=${opts.dryRun ? "dry-run" : "apply"}` + ); + const teamStats = await processCollectionDocs({ + collection: teamCol, + filter, + batchDocs: opts.batchDocs, + sortById: opts.sortById, + skipDocs: opts.skipDocs, + limitDocs: opts.limitDocs, + dryRun: opts.dryRun, + updateDoc: (doc) => + updateTeamDoc(doc, { oldPrefix: opts.oldPrefix, newPrefix: opts.newPrefix, - }); - if (changed === 0) continue; - matchedDocs++; - replacedUrls += changed; - console.log(`[match] _id=${doc._id} teamId=${doc.teamId || ""} corpId=${doc.corpId || ""} replaced=${changed}`); - - if (!opts.dryRun) { - await col.updateOne( - { _id: doc._id }, - { $set: { qrcodes: nextQrcodes } } - ); - } - } - } + }), + buildUpdate: ({ nextQrcodes }) => ({ $set: { qrcodes: nextQrcodes } }), + describeDoc: (doc) => + `_id=${doc._id} teamId=${doc.teamId || ""} corpId=${doc.corpId || ""}`, + }); console.log( - `[summary] scanned=${scanned} matchedDocs=${matchedDocs} replacedUrls=${replacedUrls} mode=${opts.dryRun ? "dry-run" : "apply"}` + `[step] collection=${opts.dbName}.${opts.staffCollection} corpId=${opts.corpId || "全部"} mode=${opts.dryRun ? "dry-run" : "apply"}` + ); + const staffStats = await processCollectionDocs({ + collection: staffCol, + filter, + batchDocs: opts.batchDocs, + sortById: opts.sortById, + skipDocs: opts.skipDocs, + limitDocs: opts.limitDocs, + dryRun: opts.dryRun, + updateDoc: (doc) => + updateStaffQrcodeDoc(doc, { + oldPrefix: opts.oldPrefix, + newPrefix: opts.newPrefix, + }), + buildUpdate: ({ nextArchiveLink }) => ({ + $set: { archiveLink: nextArchiveLink }, + }), + describeDoc: (doc) => + `_id=${doc._id} qrcodeId=${doc.qrcodeId || ""} corpId=${doc.corpId || ""}`, + }); + + console.log( + `[summary] team: scanned=${teamStats.scanned} matchedDocs=${teamStats.matchedDocs} replacedUrls=${teamStats.replacedUrls} | staff-qrcode: scanned=${staffStats.scanned} matchedDocs=${staffStats.matchedDocs} replacedUrls=${staffStats.replacedUrls} mode=${opts.dryRun ? "dry-run" : "apply"}` ); } finally { await client.close();