v0.10.36: 文章分类成员同步(import-live-terms,+16/-308 对齐原站 REST)+ /blog/ 越界分页真 404(对齐原站)+ docs 数据滞后记录核实更新
This commit is contained in:
@@ -124,8 +124,11 @@
|
|||||||
侧栏 = 社交图标 + CONTACT US 表单 + IoT Gateways 折叠组;正文 Elementor 直出 +
|
侧栏 = 社交图标 + CONTACT US 表单 + IoT Gateways 折叠组;正文 Elementor 直出 +
|
||||||
pageCss + 图片 WP 尺寸变体解析(medium→-300x272 类文件,服务端目录扫描缓存)。
|
pageCss + 图片 WP 尺寸变体解析(medium→-300x272 类文件,服务端目录扫描缓存)。
|
||||||
- page_css 导入扩展至全部文章:254 篇有 post-<id>.css 已导入,355 篇原站 404(无自定义 CSS)。
|
- page_css 导入扩展至全部文章:254 篇有 post-<id>.css 已导入,355 篇原站 404(无自定义 CSS)。
|
||||||
- 已知数据差异:/blog/ 本地 category-blog 302 篇(26 页)vs 原站当前 7 页(dump 后原站
|
- 已知数据差异:/blog/ ~~本地 302 篇 vs 原站 7 页~~ ✅ v0.10.36 核实:原站分类成员实为
|
||||||
调整了分类成员);文章内文逐节高度仍有数据快照/lazy 假象差。
|
318 篇(REST categories=53 = 本地同步后一致),"7 页"系取证只抓到分页窗口尾页;
|
||||||
|
两侧均为 27 页(page/28 原站 404 → 本站分页路由越界已改真 404);
|
||||||
|
文章内文逐节高度仍有数据快照/lazy 假象差。分类成员同步脚本 `scripts/import-live-terms.ts`
|
||||||
|
(幂等,dry 支持)。
|
||||||
- P1 产品详情 `/product/$slug` v0.10.27 ✅ 产品单模板一比一(elementor_library 2741,从
|
- P1 产品详情 `/product/$slug` v0.10.27 ✅ 产品单模板一比一(elementor_library 2741,从
|
||||||
legacy dump 补录入库 slug=product-template;内嵌模板 29972「IoT Gateways for
|
legacy dump 补录入库 slug=product-template;内嵌模板 29972「IoT Gateways for
|
||||||
Recommendation」同步补录):hero 图廊 484×617/标题/meta(Model/Category)/短描述精确、
|
Recommendation」同步补录):hero 图廊 484×617/标题/meta(Model/Category)/短描述精确、
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dusuniot-rebuild",
|
"name": "dusuniot-rebuild",
|
||||||
"version": "0.10.35",
|
"version": "0.10.36",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* # 脚本 同步文章分类成员(dump 快照 → 原站当前状态)
|
||||||
|
*
|
||||||
|
* 背景:/blog/ 归档本地 302 篇(26 页)vs 原站当前 7 页——dump 之后原站调整了
|
||||||
|
* Blog/Gateway 等分类的成员。本脚本按原站 REST API 的 categories 重建本地
|
||||||
|
* content_terms 分类关联(仅调整 REST 返回的文章;本地有而原站已删除的不动)。
|
||||||
|
*
|
||||||
|
* 用法:bun scripts/import-live-terms.ts [--dry]
|
||||||
|
*/
|
||||||
|
import { SQL } from "bun";
|
||||||
|
|
||||||
|
const LIVE = "https://www.dusuniot.com";
|
||||||
|
const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot", { max: 1 });
|
||||||
|
const dry = process.argv.includes("--dry");
|
||||||
|
|
||||||
|
/** 本地分类 legacy_id → id(WP term id 与 legacy_id 一致) */
|
||||||
|
const catRows = await sql`select id, legacy_id, slug from categories where taxonomy = 'category' and legacy_id is not null`;
|
||||||
|
const catByLegacy = new Map<number, number>(catRows.map((r: any) => [Number(r.legacy_id), r.id as number]));
|
||||||
|
|
||||||
|
/** 本地已发布文章 legacy_id → content id */
|
||||||
|
const posts = await sql`select id, legacy_id from contents where type = 'post' and status = 'published' and legacy_id is not null`;
|
||||||
|
const contentByLegacy = new Map<number, number>(posts.map((r: any) => [Number(r.legacy_id), r.id as number]));
|
||||||
|
console.log("本地已发布文章:", posts.length);
|
||||||
|
|
||||||
|
/** 逐页拉原站文章的分类(_fields 精简返回) */
|
||||||
|
const liveCats = new Map<number, number[]>(); // wp post id → term ids
|
||||||
|
let page = 1;
|
||||||
|
while (true) {
|
||||||
|
const res = await fetch(`${LIVE}/wp-json/wp/v2/posts?per_page=100&page=${page}&_fields=id,categories`, { headers: { "User-Agent": "Mozilla/5.0" } });
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 400) break; // 翻页越界
|
||||||
|
console.log("REST", res.status, "停止");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const items = (await res.json()) as { id: number; categories: number[] }[];
|
||||||
|
for (const it of items) liveCats.set(it.id, it.categories || []);
|
||||||
|
if (items.length < 100) break;
|
||||||
|
page++;
|
||||||
|
await new Promise((r) => setTimeout(r, 400)); // 限速(429 防护)
|
||||||
|
}
|
||||||
|
console.log("原站文章:", liveCats.size);
|
||||||
|
|
||||||
|
let added = 0, removed = 0;
|
||||||
|
for (const [wpId, cats] of liveCats) {
|
||||||
|
const cid = contentByLegacy.get(wpId);
|
||||||
|
if (!cid) continue;
|
||||||
|
const localRows = await sql`select category_id from content_terms where content_id = ${cid}`;
|
||||||
|
const localSet = new Set(localRows.map((r: any) => r.category_id as number));
|
||||||
|
const liveSet = new Set(cats.map((t) => catByLegacy.get(t)).filter((x): x is number => x != null));
|
||||||
|
for (const add of liveSet) {
|
||||||
|
if (localSet.has(add)) continue;
|
||||||
|
added++;
|
||||||
|
if (!dry) await sql`insert into content_terms (content_id, category_id) values (${cid}, ${add}) on conflict do nothing`;
|
||||||
|
}
|
||||||
|
for (const rm of localSet) {
|
||||||
|
if (liveSet.has(rm)) continue;
|
||||||
|
removed++;
|
||||||
|
if (!dry) await sql`delete from content_terms where content_id = ${cid} and category_id = ${rm}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`分类成员同步: 新增 ${added} 移除 ${removed}${dry ? "(dry 未写库)" : ""}`);
|
||||||
|
await sql.end();
|
||||||
@@ -2,13 +2,17 @@
|
|||||||
* # 路由 博客归档分页 /blog/page/$page/
|
* # 路由 博客归档分页 /blog/page/$page/
|
||||||
* 与原站一致的第 2+ 页归档(每页 12 篇,日期倒序)。
|
* 与原站一致的第 2+ 页归档(每页 12 篇,日期倒序)。
|
||||||
*/
|
*/
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, notFound } from "@tanstack/react-router";
|
||||||
import { BlogArchive, loadBlogArchive } from "~/components/website/BlogArchive";
|
import { BlogArchive, loadBlogArchive } from "~/components/website/BlogArchive";
|
||||||
|
|
||||||
export const Route = createFileRoute("/blog/page/$page")({
|
export const Route = createFileRoute("/blog/page/$page")({
|
||||||
loader: async ({ params }) => {
|
loader: async ({ params }) => {
|
||||||
const n = Number(params.page);
|
const n = Number(params.page);
|
||||||
return loadBlogArchive(Number.isFinite(n) && n > 1 ? Math.floor(n) : 1);
|
const data = await loadBlogArchive(Number.isFinite(n) && n > 1 ? Math.floor(n) : 1);
|
||||||
|
// 越界页真 404(原站 /blog/page/28/ 404;软 404 影响 SEO)
|
||||||
|
if (!Number.isFinite(n) || n < 1 || (data.page > 1 && data.page > Math.ceil(data.list.total / data.list.pageSize)))
|
||||||
|
throw notFound();
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
head: ({ loaderData }) => ({
|
head: ({ loaderData }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
|
|||||||
Reference in New Issue
Block a user