From 5a1ced606baa38fd64e4345aa1705ce104f8a743 Mon Sep 17 00:00:00 2001 From: ggyy <34892002@qq.com> Date: Thu, 30 Apr 2026 11:54:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20SEO=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- server/routes/index.ts | 4 +++ server/routes/robots.ts | 23 +++++++++++++++ server/routes/sitemap.ts | 62 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 server/routes/robots.ts create mode 100644 server/routes/sitemap.ts diff --git a/package.json b/package.json index 7bc36a2..c6f4f07 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.2.1", + "version": "1.2.2", "scripts": { "dev": "vike dev", "build": "bun run db:generate && vike build", diff --git a/server/routes/index.ts b/server/routes/index.ts index c798739..c674e83 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -4,6 +4,8 @@ import { registerBepusdtRoutes } from "./payment-bepusdt"; import { registerEpayRoutes } from "./payment-epay"; import { registerAlipayRoutes } from "./payment-alipay"; import { registerStripeRoutes } from "./payment-stripe"; +import { registerRobotsRoutes } from "./robots"; +import { registerSitemapRoutes } from "./sitemap"; // 集中注册所有 `/api/*` 路由,避免入口文件散落多个 register 调用。 export function registerApiRoutes(app: Hono) { @@ -12,5 +14,7 @@ export function registerApiRoutes(app: Hono) { registerEpayRoutes(app); registerAlipayRoutes(app); registerStripeRoutes(app); + registerRobotsRoutes(app); + registerSitemapRoutes(app); } diff --git a/server/routes/robots.ts b/server/routes/robots.ts new file mode 100644 index 0000000..09b0c49 --- /dev/null +++ b/server/routes/robots.ts @@ -0,0 +1,23 @@ +import type { Hono } from "hono"; + +export function registerRobotsRoutes(app: Hono) { + app.get("/robots.txt", (c) => { + const origin = new URL(c.req.url).origin; + + let robotsTxt = `User-agent: * +Disallow: /admin +Disallow: /admin/* +Disallow: /api/ + +# 禁止爬取管理后台页面 +# 禁止爬取API端点 + +Sitemap: ${origin}/sitemap.xml +`; + + return c.text(robotsTxt, 200, { + "Content-Type": "text/plain", + "Cache-Control": "public, max-age=86400", + }); + }); +} diff --git a/server/routes/sitemap.ts b/server/routes/sitemap.ts new file mode 100644 index 0000000..02ef7d8 --- /dev/null +++ b/server/routes/sitemap.ts @@ -0,0 +1,62 @@ +import type { Hono } from "hono"; +import type { PrismaClient } from "../../generated/prisma/client"; +import { logger } from "../../lib/logger"; + +export function registerSitemapRoutes(app: Hono) { + app.get("/sitemap.xml", async (c) => { + const origin = new URL(c.req.url).origin; + + // 静态页面 + const staticPages = [ + { loc: `${origin}/`, changefreq: "daily", priority: "1.0" }, + { loc: `${origin}/query`, changefreq: "weekly", priority: "0.5" }, + ]; + + // 尝试从数据库查询上架商品,失败时降级为仅静态页面 + let productPages: { loc: string; changefreq: string; priority: string }[] = []; + try { + const universalContext = (c as any).get("universalContext") as { prisma: PrismaClient } | undefined; + const prisma = universalContext?.prisma; + if (prisma) { + const products = await prisma.product.findMany({ + where: { status: "ACTIVE" }, + select: { slug: true }, + orderBy: { sort: "asc" }, + }); + productPages = products.map((p) => ({ + loc: `${origin}/product/${p.slug}`, + changefreq: "weekly", + priority: "0.8", + })); + } + } catch (error) { + logger.error(error instanceof Error ? error : new Error(String(error)), { + event: "sitemap.products_query_failed", + source: "sitemap", + }); + // 降级:仅使用静态页面,不影响网站运行 + } + + const allPages = [...staticPages, ...productPages]; + + const urlEntries = allPages + .map( + (page) => ` + ${page.loc} + ${page.changefreq} + ${page.priority} + `, + ) + .join("\n"); + + const sitemap = ` + +${urlEntries} +`; + + return c.text(sitemap, 200, { + "Content-Type": "application/xml", + "Cache-Control": "public, max-age=86400", + }); + }); +}