v0.13.0: 前台导航修复(loader 服务函数化防客户端退化)、全站图片占位图+懒加载、favicon、分页圆点、导航 1400px 居中、缺失图片补齐脚本
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* # 脚本 补齐缺失的上传图片(从原站 dusuniot.com 拉取)
|
||||
* 背景:导入另抓的 html/meta 引用了 uploads 里的图片,但部分文件未同步到
|
||||
* public/uploads(如 landing-pages 分类卡片 2026/11 目录)。本脚本:
|
||||
* 1. 扫库(contents.html/meta/excerpt、products 文本列、media.url)收集全部 uploads 引用路径;
|
||||
* 2. 本地缺失的文件从 https://www.dusuniot.com/wp-content/uploads/<path> 下载补齐;
|
||||
* 3. 输出成功/失败清单。可重复执行(幂等,跳过已存在文件)。
|
||||
*/
|
||||
import { mkdirSync, existsSync, writeFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { initDb } from "~/db";
|
||||
import { db } from "~/db";
|
||||
import { contents, products, media } from "~/db/schema";
|
||||
|
||||
const PUBLIC_ROOT = join(process.cwd(), "public", "uploads");
|
||||
const ORIGIN = "https://www.dusuniot.com/wp-content/uploads/";
|
||||
|
||||
await initDb();
|
||||
|
||||
/** # 成员变量 引用路径去重集合(含 html/meta/excerpt/products/media 全部来源) **/
|
||||
const paths = new Set<string>();
|
||||
const addFromText = (s: unknown) => {
|
||||
if (s == null || typeof s !== "string") return;
|
||||
for (const m of s.matchAll(/(?:https?:\/\/(?:www\.)?dusuniot\.com)?\/wp-content\/uploads\/([\w\-./%~()+!@^[\]!'*,]+?\.(?:png|jpe?g|webp|gif|svg|ico|mp4|pdf))/gi)) {
|
||||
paths.add(m[1]);
|
||||
}
|
||||
for (const m of s.matchAll(/\/uploads\/((?:\d{4}\/\d{2}\/)?[\w\-./%~()+!@^[\]!'*,]+?\.(?:png|jpe?g|webp|gif|svg|ico|mp4|pdf))/gi)) {
|
||||
paths.add(m[1]);
|
||||
}
|
||||
};
|
||||
|
||||
// contents:html/meta/excerpt
|
||||
for (const row of await db.select({ html: contents.html, meta: contents.meta, excerpt: contents.excerpt, seoTitle: contents.seoTitle }).from(contents)) {
|
||||
addFromText(row.html);
|
||||
addFromText(row.excerpt);
|
||||
addFromText(row.seoTitle);
|
||||
addFromText(row.meta);
|
||||
}
|
||||
// products:常见文本/jsonb 列
|
||||
for (const row of await db.select().from(products)) {
|
||||
for (const v of Object.values(row as Record<string, unknown>)) addFromText(v);
|
||||
}
|
||||
// media 表登记的 URL
|
||||
for (const row of await db.select({ url: media.url }).from(media)) addFromText(row.url);
|
||||
|
||||
console.log("引用路径总数:", paths.size);
|
||||
|
||||
let ok = 0;
|
||||
let okExisting = 0;
|
||||
const failed: string[] = [];
|
||||
for (const p of [...paths].sort()) {
|
||||
// 解码 WP URL 编码与 query
|
||||
const clean = p.split("?")[0];
|
||||
const local = join(PUBLIC_ROOT, decodeURIComponent(clean));
|
||||
if (existsSync(local)) {
|
||||
okExisting++;
|
||||
continue;
|
||||
}
|
||||
const url = ORIGIN + clean;
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
failed.push(`${res.status} ${clean}`);
|
||||
continue;
|
||||
}
|
||||
const buf = new Uint8Array(await res.arrayBuffer());
|
||||
if (buf.byteLength < 100) {
|
||||
failed.push(`DOCSIZE ${clean}`);
|
||||
continue;
|
||||
}
|
||||
mkdirSync(dirname(local), { recursive: true });
|
||||
writeFileSync(local, buf);
|
||||
ok++;
|
||||
} catch (e) {
|
||||
failed.push(`ERR ${clean}: ${(e as Error).message.slice(0, 80)}`);
|
||||
}
|
||||
}
|
||||
console.log(`已存在 ${okExisting},新下载 ${ok},失败 ${failed.length}`);
|
||||
for (const f of failed.slice(0, 40)) console.log(" fail:", f);
|
||||
Reference in New Issue
Block a user