Files
doodle-mini/cloudfunctions/worksheetsBatchPatch/index.js
T
2026-05-06 17:28:51 +08:00

179 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
const BATCH_SIZE = 10;
function toInt(value, fallback = 0) {
const n = Number(value);
if (!Number.isFinite(n)) return fallback;
return Math.round(n);
}
/** 与 miniprogram/utils/debugPublish.inferGradeFromAge 保持一致 */
function inferGradeFromAge(ageMin, ageMax) {
const centerAge = Math.round((ageMin + ageMax) / 2);
const ageGradeMap = {
2: -4,
3: -3,
4: -2,
5: -1,
6: 0,
7: 1,
8: 2,
9: 3,
10: 4,
11: 5,
12: 6,
};
return ageGradeMap[centerAge] ?? 0;
}
function normalizeTags(value) {
if (!Array.isArray(value)) return null;
return value.map((t) => String(t || '').trim()).filter(Boolean);
}
function buildUpdateData(db, patch) {
const id = String(patch.id || '').trim();
if (!id) return { id: null, ok: false, error: '缺少 id' };
const data = { updatedAt: db.serverDate() };
let hasField = false;
const hasAgeMin = patch.ageMin !== undefined && patch.ageMin !== null;
const hasAgeMax = patch.ageMax !== undefined && patch.ageMax !== null;
if (hasAgeMin !== hasAgeMax) {
return {
id,
ok: false,
error: 'ageMin 与 ageMax 必须同时提供',
};
}
if (hasAgeMin && hasAgeMax) {
const ageMin = toInt(patch.ageMin);
const ageMax = toInt(patch.ageMax);
if (ageMin < 0 || ageMax < ageMin) {
return { id, ok: false, error: '年龄范围不合法' };
}
data.ageMin = ageMin;
data.ageMax = ageMax;
data.grade =
patch.grade !== undefined && patch.grade !== null
? toInt(patch.grade)
: inferGradeFromAge(ageMin, ageMax);
hasField = true;
}
if (patch.tags !== undefined) {
const tags = normalizeTags(patch.tags);
if (tags === null) {
return { id, ok: false, error: 'tags 须为数组' };
}
data.tags = tags;
hasField = true;
}
if (!hasField) {
return {
id,
ok: false,
error: '未包含可更新字段(ageMin/ageMax 或 tags',
};
}
return { id, ok: true, data };
}
async function runInBatches(items, batchSize, worker) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const chunk = items.slice(i, i + batchSize);
results.push(...(await Promise.all(chunk.map(worker))));
}
return results;
}
exports.main = async (event) => {
try {
const db = cloud.database();
const patches = Array.isArray(event.patches) ? event.patches : [];
const dryRun = !!event.dryRun;
const invalidResults = [];
const validUpdates = [];
for (const p of patches) {
const update = buildUpdateData(db, p);
if (dryRun) {
invalidResults.push(
update.ok
? {
id: update.id,
ok: true,
dryRun: true,
wouldSet: {
...update.data,
updatedAt: '[serverDate]',
},
}
: update,
);
continue;
}
if (!update.ok) {
invalidResults.push(update);
continue;
}
validUpdates.push(update);
}
const updateResults = dryRun
? []
: await runInBatches(
validUpdates,
BATCH_SIZE,
async ({ id, data }) => {
try {
await db
.collection('worksheets')
.doc(id)
.update({ data });
return { id, ok: true };
} catch (error) {
const message =
error instanceof Error
? error.message
: '更新失败';
return {
id,
ok: false,
error: /document.*not.*exist|not.*found/i.test(
message,
)
? 'worksheet 不存在'
: message,
};
}
},
);
const results = [...invalidResults, ...updateResults];
const failed = results.filter((r) => !r.ok).length;
return {
success: failed === 0,
dryRun,
total: results.length,
failed,
results,
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : '批量更新失败',
};
}
};