2b69686795
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
142 lines
4.9 KiB
JavaScript
142 lines
4.9 KiB
JavaScript
#!/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://cms-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)
|
||
})
|