77 lines
4.7 KiB
TypeScript
77 lines
4.7 KiB
TypeScript
/**
|
||
* # 脚本 导航 404 数据修复(一次性,幂等)
|
||
* 依据对原站 dusuniot.com 的逐 URL 探测结论:
|
||
* 1. 发布落地页草稿(原站已 404 但内容在库、且被站内内容/重定向规则引用):
|
||
* - contents.id=49 bluetooth-roaming-gateway(案例文章外链目标)
|
||
* - contents.id=40 iot-hardware-developer(页脚菜单组外链目标)
|
||
* - contents.id=53 ultra-convergence-solution(redirects 规则 491 的目标)
|
||
* - contents.id=64 single-board-computer(博客文章外链目标)
|
||
* 2. 修复导入内容里的畸形链接:href 中 URL 后又拼接了一次单斜杠版自身
|
||
* (…/product/x/https:/www.dusuniot.com/product/x/),还原为第一个 URL。
|
||
* 3. 补 redirects 表规则:目标多为「线上同样 404 的陈旧链接」,指向本地最近似存活页。
|
||
* 重复执行安全:草稿发布、内容修复与规则插入都先查再写。
|
||
*/
|
||
import { SQL } from "bun";
|
||
const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot");
|
||
|
||
// ── 1. 发布落地页草稿 ──
|
||
for (const id of [40, 49, 53, 64]) {
|
||
const [row] = await sql`SELECT id, slug, status FROM contents WHERE id = ${id}`;
|
||
if (!row) {
|
||
console.log(`contents.id=${id} 不存在,跳过`);
|
||
continue;
|
||
}
|
||
if (row.status === "published") {
|
||
console.log(`contents.id=${id} (${row.slug}) 已是 published`);
|
||
continue;
|
||
}
|
||
await sql`UPDATE contents SET status = 'published' WHERE id = ${id}`;
|
||
console.log(`已发布 contents.id=${id} (${row.slug})`);
|
||
}
|
||
|
||
// ── 2. 修复内容里畸形拼接的链接(URL 后跟单斜杠版自身) ──
|
||
const bad = await sql`SELECT id, slug, html FROM contents WHERE html LIKE '%/https:/www.dusuniot.com%'`;
|
||
const dupRe = /(https:\/\/www\.dusuniot\.com\/[^\s"'<>]+?)\/https:\/www\.dusuniot\.com\/[^\s"'<>]*/g;
|
||
for (const row of bad) {
|
||
const fixed = row.html.replace(dupRe, "$1");
|
||
if (fixed !== row.html) {
|
||
await sql`UPDATE contents SET html = ${fixed} WHERE id = ${row.id}`;
|
||
console.log(`已修复畸形链接: contents.id=${row.id} (${row.slug})`);
|
||
} else {
|
||
console.log(`contents.id=${row.id} (${row.slug}) 模式命中但无可替换(人工检查)`);
|
||
}
|
||
}
|
||
|
||
// ── 3. 补充 redirects 规则(source 唯一先查后插) ──
|
||
const RULES: { source: string; target: string; note: string }[] = [
|
||
// 原站已 404 的案例/新闻文:落对应归档(本站归档已可用)
|
||
{ source: "/case-study/tuya-smart-home-solution/", target: "/case-study/", note: "线上 404 的陈旧案例链接 → 案例归档" },
|
||
{ source: "/news/a-multi-protocol-smart-home-mini-pc-hub/", target: "/news/", note: "线上 404 的陈旧新闻链接 → 新闻归档" },
|
||
// 线上已 404 的落地页 → 本地最近的存活落地页(odm/zigbee-gateways 均 200 且主题相近)
|
||
{ source: "/landing-pages/oem-odm/", target: "/landing-pages/odm/", note: "线上 404 → 本地 ODM 落地页" },
|
||
{ source: "/landing-pages/tuya-router-gateway/", target: "/landing-pages/zigbee-gateways/", note: "线上 404 → 本地 Zigbee 网关落地页" },
|
||
{ source: "/landing-pages/tuya-series-products/", target: "/landing-pages/zigbee-gateways/", note: "线上 404 → 本地 Zigbee 网关落地页" },
|
||
{ source: "/landing-pages/tuya-smart-home-zigbee-gateway/", target: "/landing-pages/zigbee-gateways/", note: "线上 404 → 本地 Zigbee 网关落地页" },
|
||
// 原站 WooCommerce 属性归档(pa_type 筛选页),本站无该属性归档 → 商城列表
|
||
{ source: "/shop/type-iot-gateway/", target: "/shop/", note: "线上 200 的属性归档,本站无此筛选 → 商城" },
|
||
{ source: "/shop/type-som/", target: "/shop/", note: "线上 200 的属性归档,本站无此筛选 → 商城" },
|
||
{ source: "/shop/downlink-z-wave-or-zigbee/", target: "/shop/", note: "线上 404 的属性归档 → 商城" },
|
||
// 原站 301 后终态 404 的规格页短链 → 产品列表(本地无对应产品)
|
||
{ source: "/dsgw-075-tuya-specification/", target: "/collection/", note: "线上 301→404 → 产品列表" },
|
||
// 原站 200 但内容未导入(Elementor 页抓取导入成本高,先落 About Us;后续可导入真页替换)
|
||
{ source: "/partners/", target: "/about-us/", note: "原站合作伙伴页未导入 → 关于我们" },
|
||
];
|
||
|
||
for (const r of RULES) {
|
||
const [dup] = await sql`SELECT id FROM redirects WHERE source = ${r.source}`;
|
||
if (dup) {
|
||
console.log(`规则已存在(${dup.id}): ${r.source}`);
|
||
continue;
|
||
}
|
||
await sql`INSERT INTO redirects (source, target, code, is_regex, enabled) VALUES (${r.source}, ${r.target}, 301, false, true)`;
|
||
console.log(`+ 301 ${r.source} → ${r.target} (${r.note})`);
|
||
}
|
||
|
||
console.log("完成");
|
||
process.exit(0);
|