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",
+ });
+ });
+}