208 lines
6.1 KiB
JavaScript
208 lines
6.1 KiB
JavaScript
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 (patch.likes_seed !== undefined) {
|
||
const v = toInt(patch.likes_seed, -1);
|
||
if (v < 0) return { id, ok: false, error: 'likes_seed 须为非负整数' };
|
||
data.likes_seed = v;
|
||
hasField = true;
|
||
}
|
||
|
||
if (patch.downloads_seed !== undefined) {
|
||
const v = toInt(patch.downloads_seed, -1);
|
||
if (v < 0)
|
||
return { id, ok: false, error: 'downloads_seed 须为非负整数' };
|
||
data.downloads_seed = v;
|
||
hasField = true;
|
||
}
|
||
|
||
if (patch.likes !== undefined) {
|
||
const v = toInt(patch.likes, -1);
|
||
if (v < 0) return { id, ok: false, error: 'likes 须为非负整数' };
|
||
data.likes = v;
|
||
hasField = true;
|
||
}
|
||
|
||
if (patch.downloads !== undefined) {
|
||
const v = toInt(patch.downloads, -1);
|
||
if (v < 0) return { id, ok: false, error: 'downloads 须为非负整数' };
|
||
data.downloads = v;
|
||
hasField = true;
|
||
}
|
||
|
||
if (!hasField) {
|
||
return {
|
||
id,
|
||
ok: false,
|
||
error: '未包含可更新字段(ageMin/ageMax、tags、seed 或 stats)',
|
||
};
|
||
}
|
||
|
||
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 : '批量更新失败',
|
||
};
|
||
}
|
||
};
|