feat: 更新 replace-team-qrcode-url 脚本以支持 staff-qrcode 集合的前缀替换

This commit is contained in:
Jafeng 2026-04-15 11:39:12 +08:00
parent 525d9fa1d8
commit 73e9875d40

View File

@ -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();