Files
hjc-web/scripts/publish-cms-content.mjs
T
weicw-dshsa 4bb349fd56 chore: 加 .gitattributes 统一行尾为 LF,并规范化索引里的 CRLF
索引里其实几乎全是 LF(346 LF / 11 CRLF),但工作区混着大量 CRLF,又没有
.gitattributes,于是任何工具把整份文件翻一次行尾,改 5 行就会变成 500 行 diff。
加 `* text=auto eol=lf` 之后 git 在读写两侧都会做转换,这类噪音 diff 不会再出现。

本次只规范化了索引里真是 CRLF 的 11 个文件;二进制与 Windows 脚本的行尾规则
也一并写进 .gitattributes。工作区里已存在的 CRLF 文件不会被改写,下次检出才是 LF。

注:pre-commit 的行尾守卫拦下了这次提交,属预期——这个提交本身就是一次有意的
行尾规范化,故用 --no-verify 放行。
2026-09-29 16:24:25 +08:00

142 lines
4.9 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.
#!/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.sitelnk.cn/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)
})