#!/usr/bin/env node /** * CMS 内容批量发布脚本 * ============================================================================ * 背景(2026-08-05): * website-admin 后台的状态枚举与 CMS 后端语义相反——后台点「发布 / 上架」写入 * status=1,而 CMS 权威语义是 0=已发布、1=待审核、2=已下线, * C 端 website-template(server/api/{article,product,case}/list.get.ts)只查 * status=0,于是后台明明「已发布」,官网却一条都不显示。 * * 后台枚举已在本次一并修正,但存量数据仍停在 status=1,需要用本脚本批量刷成 0。 * * 用法: * # 1) 先空跑,只打印将要修改的内容,不写任何数据 * ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs * * # 2) 确认无误后真正执行 * ADMIN_TOKEN="eyJ..." TENANT_ID=10546 APPLY=1 node scripts/publish-cms-content.mjs * * # 只处理文章 * ADMIN_TOKEN="eyJ..." TENANT_ID=10546 MODULES=article APPLY=1 node scripts/publish-cms-content.mjs * * token 获取:后台 DevTools → Network → 任意 XHR → Request Headers → Authorization * (连同 "Bearer " 前缀一起复制即可,脚本会自动补全) * ============================================================================ */ const API_BASE = process.env.CMS_API_BASE || 'https://hjc-api.websoft.top/api' const TOKEN_RAW = process.env.ADMIN_TOKEN || '' const TENANT_ID = process.env.TENANT_ID || '10546' const APPLY = process.env.APPLY === '1' const MODULES = (process.env.MODULES || 'article,product,case') .split(',') .map((s) => s.trim()) .filter(Boolean) /** CMS 权威语义:0=已发布 / 在售(C 端可见) */ const PUBLISHED = 0 /** 各模块的接口路径与主键字段 */ const MODULE_CONF = { article: { path: 'cms-article', idKey: 'articleId', titleKey: 'title' }, product: { path: 'cms-product', idKey: 'productId', titleKey: 'productName' }, case: { path: 'cms-case', idKey: 'caseId', titleKey: 'title' } } if (!TOKEN_RAW) { console.error('❌ 缺少 ADMIN_TOKEN。') console.error(' ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs') process.exit(1) } const TOKEN = TOKEN_RAW.startsWith('Bearer ') ? TOKEN_RAW : `Bearer ${TOKEN_RAW}` const headers = { 'Content-Type': 'application/json', Authorization: TOKEN, TenantId: String(TENANT_ID) } async function fetchList(conf) { const url = `${API_BASE}/cms/${conf.path}/page?page=1&limit=200` const res = await fetch(url, { headers }) const json = await res.json().catch(() => null) if (!json || json.code !== 0) { throw new Error(`拉取列表失败 (${res.status}): ${JSON.stringify(json)?.slice(0, 200)}`) } return json.data?.list || [] } async function updateStatus(conf, id) { const url = `${API_BASE}/cms/${conf.path}/status` const res = await fetch(url, { method: 'PUT', headers, body: JSON.stringify({ [conf.idKey]: id, status: PUBLISHED }) }) const text = await res.text() let json = null try { json = JSON.parse(text) } catch { /* 非 JSON 响应 */ } return { ok: res.ok && (json === null || json.code === 0), status: res.status, body: text.slice(0, 200) } } async function run() { console.log(`租户 ${TENANT_ID} | 模块 ${MODULES.join(', ')} | 模式 ${APPLY ? '实际执行' : '空跑预演'}`) console.log('─'.repeat(70)) let totalPending = 0 let totalOk = 0 let totalFail = 0 for (const mod of MODULES) { const conf = MODULE_CONF[mod] if (!conf) { console.log(`跳过未知模块 ${mod}`) continue } let list try { list = await fetchList(conf) } catch (e) { console.log(`\n[${mod}] 拉取失败:${e.message}`) continue } const pending = list.filter((it) => Number(it.status) !== PUBLISHED) console.log(`\n[${mod}] 共 ${list.length} 条,其中 ${pending.length} 条未发布(status !== 0)`) totalPending += pending.length for (const it of pending) { const id = it[conf.idKey] ?? it.id const title = String(it[conf.titleKey] || it.title || '(无标题)').slice(0, 30) if (!APPLY) { console.log(` · [预演] ${id} status=${it.status} → 0 | ${title}`) continue } const r = await updateStatus(conf, id) if (r.ok) { totalOk++ console.log(` ✓ ${id} → 已发布 | ${title}`) } else { totalFail++ console.log(` ✗ ${id} 失败 HTTP ${r.status} ${r.body} | ${title}`) } } } console.log('\n' + '─'.repeat(70)) if (APPLY) { console.log(`完成:成功 ${totalOk} 条,失败 ${totalFail} 条`) console.log('前台若有缓存,等待缓存过期或重启 Node 进程后即可看到内容。') } else { console.log(`空跑结束:共 ${totalPending} 条待发布。加 APPLY=1 重新运行即可实际写入。`) } } run().catch((e) => { console.error('执行出错:', e) process.exit(1) })