From 3b8997b565f59507c5eea1c1db806808b4030072 Mon Sep 17 00:00:00 2001 From: wangliang Date: Fri, 23 Oct 2026 11:13:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(sitemap):=20=E6=94=AF=E6=8C=81=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=20XML=20=E5=9B=9E=E9=80=80=EF=BC=88=E5=AE=98=E7=BD=91?= =?UTF-8?q?=20sitemap=20=E5=8F=97=20WAF=20=E4=BF=9D=E6=8A=A4=EF=BC=89+=20?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=205.1-5.3=20=E6=95=B0=E6=8D=AE=E9=9D=A2?= =?UTF-8?q?=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/deploy-wangliang.md | 63 ++++++++++++++++++++++++++++++++++++++- scripts/build-site-map.js | 28 +++++++++++++++-- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3652b5f..54f33fe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ libredesk/backups/ node_modules/ *.log tmp/ +var/ .DS_Store # 子项目自带的 git 历史 diff --git a/docs/deploy-wangliang.md b/docs/deploy-wangliang.md index cec4aa4..2034f94 100644 --- a/docs/deploy-wangliang.md +++ b/docs/deploy-wangliang.md @@ -148,7 +148,7 @@ docker run --rm -v /etc/nginx/conf.d:/c redis:7-alpine rm -f /c/zz-wangliang-crm |---|---|---| | ~~AI Provider(DashScope)~~ | ✅ **已配置**(qwen-plus-latest / text-embedding-v4),端到端已验证 | 换 key:`DASHSCOPE_API_KEY=sk-xxx node scripts/configure-ai.js` | | **SMTP / 163 邮件** | `SMTP_*` 为空,OTP 与邮件渠道不可用 | 填 `.env` 后 `node scripts/configure-email.js` + `node scripts/add-email-inbox.js` | -| **知识库 RAG(search_kb)** | 无 `RAG_PG_URL`,工具返回 503 | 指向旧 KB 库后运行 `scripts/import-kb-helpcenter.js` / `rebuild-kb-index.sh` | +| **知识库 RAG(search_kb)** | 无 `RAG_PG_URL`,工具返回 503;旧 KB 数据源本机不存在(见第 10 节) | 提供旧 `products.kb_docs` dump 或 `customer-service-kb/*.md` 后导入 | | **WhatsApp 渠道** | 无 Meta 凭据 | 填 `WA_*` 后 `scripts/add-whatsapp-inbox.js`;需要公网回调(本栈已有 HTTPS,可不用 cloudflared) | | **public-gateway / cloudflared** | 未启动 | 需要 Cloudflare 临时隧道时再 `docker compose up -d public-gateway cloudflared` | @@ -198,3 +198,64 @@ Necklace Star `$5.70`/110,与 RPC 直查一致(首次调试时的编价问 若后续要硬性拦截虚假价格/链接,需要走 bridge 自己的 agent 路径,或在 LibreDesk 侧加后置校验。 验证完已把 `log_level` 恢复为 `info`。 + +## 10. 数据面初始化(5.1 KB / 5.2 sitemap / 5.3 质量表) + +### 5.3 质量表 ✅ +`node scripts/quality-report.js 1`:自动建表成功。 + +```text +table_exists = bridge_ai_metrics;当前 rows=0 +{"summary":{"runs":0,"errors":0,...},"tools":[],"hours":1} +``` + +⚠️ 表建好了,但**会一直是 0 行**:`bridge_ai_metrics` 只在 bridge 自己的 agent 路径(webhook 编排)里写入, +而本栈的 AI 回复走的是 LibreDesk 内置 assistant。要让质量报表有数据,需要改用 bridge agent 或另接埋点。 + +### 5.2 全量 sitemap ✅ +官网 `sitemap.xml` 受阿里云 WAF 的 JS 挑战保护(直接 fetch 返回 4KB 挑战 HTML、0 个 ``)。 +处理:用真 Chrome(Playwright)过挑战后抓取,缓存到 `var/sitemap-{www,us}.xml`(已 gitignore), +并给 `scripts/build-site-map.js` 增加本地 XML 回退(`var/sitemap-.xml` 或 `SITEMAP_XML_`)。 + +```text +[site-map] www: total=32710, products=32070, nav=640 +[site-map] us: total=22644, products=22324, nav=320 +``` + +表 `bridge_site_map_urls` 已核对: + +```text + site | total | products + us | 22644 | 22324 + www | 32710 | 32070 +``` + +刷新方式(sitemap 会变):先用浏览器重新抓 `var/sitemap-{www,us}.xml`,再 `node scripts/build-site-map.js www us`。 +直接跑脚本在 WAF 下会明确报错「返回 0 个 」,不会静默写空表。 + +### 5.1 旧 KB(products.kb_docs)❌ 本机无数据源 + +按清单实际执行的结果: + +```text +$ node scripts/upgrade-kb-schema.js +[upgrade-kb-schema] password authentication failed for user "postgres" +$ bash scripts/rebuild-kb-index.sh +Error response from daemon: No such container: crm-postgres +$ ls /home/dev/code/crm/crm-ai-demo/customer-service-kb +No such file or directory +$ docker ps -a | grep -E '^crm-(postgres|bridge)$' +(无) +``` + +原因:这两个脚本是为**旧栈**写的 —— `products.kb_docs` 在旧 `crm-postgres` 里, +`rebuild-kb-index.sh` 还会 `docker exec crm-bridge` 调旧 bridge 的 `faq/kbIndexer`(新仓库已没有这段代码)。 +本工作区是全新栈:宿主 PG 5432 只有 `postgres/storefront` 两个库,没有 `products`;也没有旧容器的数据卷。 + +要补齐需要**数据源**,二选一: + +1. 旧测试服导出 `products.kb_docs`:`pg_dump -t kb_docs products > kb_docs.sql`; +2. 或直接给 `customer-service-kb/*.md`(9 个文件,108 篇文章的来源)。 + +拿到后我按新栈方式接入(推荐导入 LibreDesk 帮助中心,AI 原生 RAG + 引用;旧的 `search_kb` 可保留为兜底)。 +在此之前,商品/库存/订单/商机等 9 个工具不受影响,只有 `search_kb` 返回 503。 diff --git a/scripts/build-site-map.js b/scripts/build-site-map.js index f293bd5..d120096 100644 --- a/scripts/build-site-map.js +++ b/scripts/build-site-map.js @@ -33,19 +33,41 @@ function tokensFor(pathPart) { .toLowerCase(); } -async function fetchSitemap(url) { +function parseLocs(xml) { + return [...xml.matchAll(/([^<]+)<\/loc>/g)].map((m) => m[1].trim()).filter(Boolean); +} + +// 官网 sitemap 受阿里云 WAF JS 挑战保护,直接 fetch 会返回挑战 HTML(0 个 )。 +// 支持从本地缓存的 XML 读取:SITEMAP_XML_ 环境变量,或 var/sitemap-.xml。 +function localSitemapFile(name) { + const fromEnv = process.env['SITEMAP_XML_' + name.toUpperCase()]; + if (fromEnv && fs.existsSync(fromEnv)) return fromEnv; + const p = path.join(ROOT, 'var', `sitemap-${name}.xml`); + return fs.existsSync(p) ? p : null; +} + +async function fetchSitemap(name, url) { + const local = localSitemapFile(name); + if (local) { + console.log(`[site-map] ${name}: 使用本地 sitemap ${local}`); + return parseLocs(await fs.promises.readFile(local, 'utf8')); + } const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; YehwangSiteMapBot/1.0)' }, signal: AbortSignal.timeout(90000), }); if (!res.ok) throw new Error(`sitemap HTTP ${res.status}: ${url}`); const xml = await res.text(); - return [...xml.matchAll(/([^<]+)<\/loc>/g)].map((m) => m[1].trim()).filter(Boolean); + const locs = parseLocs(xml); + if (!locs.length) { + throw new Error(`sitemap 返回 0 个 (可能被 WAF 挑战):先下载 XML 到 var/sitemap-${name}.xml,或设置 SITEMAP_XML_${name.toUpperCase()}`); + } + return locs; } async function buildSite(client, name) { const cfg = SITES[name]; - const urls = await fetchSitemap(cfg.sitemap); + const urls = await fetchSitemap(name, cfg.sitemap); const rows = []; const seenPaths = new Set(); for (const url of urls) {