{"id":"2afc0d3f-58d9-46ca-b1f9-226bd1232044","entityType":"agent","slug":"clawhub-liuboacean-website-skeleton","name":"建站骨架 (EdgeOne Pages)","canonicalUrl":"https://www.xpersona.co/agent/clawhub-liuboacean-website-skeleton","canonicalPath":"/agent/clawhub-liuboacean-website-skeleton","generatedAt":"2026-10-11T07:40:53.184Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T05:08:28.270Z","emptyReason":null},"description":"一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 Skill: 建站骨架 (EdgeOne Pages) Owner: liuboacean Summary: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 Tags: ai-chat:3.2.1, ecommerce:3.2.1, edgeone:3.2.1, edgeone-pages:3.0.6, full-stack:3.0.6, fullstack:3.2.1, latest:3.2.1, saas:3.2.1, web-development:3.0.6, website-scaffold:3.2.1 Version history: v3.2.1 | 2026-05-28T01:57:33.762Z | user v3.2.1: 重新发布以触发ClawScan审核。新增HttpOnly Cookie鉴权(","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s1779brgcyhkdmz9tgjpwarx3h84vb82:website-skeleton","sourceUrl":"https://clawhub.ai/liuboacean/website-skeleton","homepage":"https://clawhub.ai/liuboacean/skills/website-skeleton","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/liuboacean/website-skeleton","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/liuboacean/skills/website-skeleton","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 Skill: 建站骨架 (EdgeOne Pages) Owner: liuboacean Summary: 一句话说需求，AI 生成完整前后端网站并自动部署到"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T05:08:28.270Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T05:08:28.270Z","emptyReason":null},"stars":null,"forks":null,"downloads":1151,"packageName":null,"latestVersion":"3.2.1","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T05:08:28.253Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T05:08:28.270Z","lastCrawledAt":"2026-10-11T05:08:28.253Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T05:08:28.253Z","lastVerifiedAt":null,"highlights":[{"version":"3.2.1","createdAt":"2026-05-28T01:57:33.762Z","changelog":"v3.2.1: 重新发布以触发ClawScan审核。新增HttpOnly Cookie鉴权(防XSS)、Edge API统一错误处理、服务端价格校验、227项测试。无代码变更。","fileCount":133,"zipByteSize":264529},{"version":"3.2.0","createdAt":"2026-05-27T08:53:49.060Z","changelog":"Phase 5: 商品详情独立页 + 227项测试 + migrate/init-site/密码哈希 + vitest + middleware 545修复 + EdgeOne部署验证","fileCount":133,"zipByteSize":264726},{"version":"3.1.2","createdAt":"2026-05-26T08:28:17.058Z","changelog":"Phase 4D: SPA补齐7页面 + 脚本(init-site/sample-data/seed) + CI Node22 + Dependabot + 001_init.sql + deploy自动触发 + CSS抽取","fileCount":123,"zipByteSize":233635},{"version":"3.1.1","createdAt":"2026-05-26T08:09:30.016Z","changelog":"Phase 4C: 前端 SPA 完整实现 + Edge API 14端点 + Cloud API 13端点 + 工程加固 (v3.1.0 version slot occupied, bump to 3.1.1)","fileCount":111,"zipByteSize":202863},{"version":"3.1.0","createdAt":"2026-05-26T08:07:33.414Z","changelog":"Phase 4C: 前端 SPA 完整实现(20文件) + Edge API 14端点 + Cloud API 13端点 + 工程加固","fileCount":111,"zipByteSize":202981},{"version":"3.0.6","createdAt":"2026-05-19T08:38:40.933Z","changelog":"Add ClawScan publisher note","fileCount":10,"zipByteSize":23511},{"version":"3.0.5","createdAt":"2026-05-16T06:24:25.370Z","changelog":"Add clawscan-note for ClawScan context: AI code generation, EdgeOne deployment, SSE streaming are by design","fileCount":2,"zipByteSize":18675},{"version":"3.0.4","createdAt":"2026-05-16T03:18:43.968Z","changelog":"SKILL.md metadata optimization: standardized frontmatter (20 triggers + env vars) + user confirmation checkpoint table (10 risk levels) + security compliance section","fileCount":42,"zipByteSize":98175}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1779brgcyhkdmz9tgjpwarx3h84vb82:website-skeleton","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T07:40:53.179Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-liuboacean-website-skeleton/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T05:08:28.270Z","emptyReason":null},"readme":"Skill: 建站骨架 (EdgeOne Pages)\n\nOwner: liuboacean\n\nSummary: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。\n\nTags: ai-chat:3.2.1, ecommerce:3.2.1, edgeone:3.2.1, edgeone-pages:3.0.6, full-stack:3.0.6, fullstack:3.2.1, latest:3.2.1, saas:3.2.1, web-development:3.0.6, website-scaffold:3.2.1\n\nVersion history:\n\nv3.2.1 | 2026-05-28T01:57:33.762Z | user\n\nv3.2.1: 重新发布以触发ClawScan审核。新增HttpOnly Cookie鉴权(防XSS)、Edge API统一错误处理、服务端价格校验、227项测试。无代码变更。\n\nv3.2.0 | 2026-05-27T08:53:49.060Z | user\n\nPhase 5: 商品详情独立页 + 227项测试 + migrate/init-site/密码哈希 + vitest + middleware 545修复 + EdgeOne部署验证\n\nv3.1.2 | 2026-05-26T08:28:17.058Z | user\n\nPhase 4D: SPA补齐7页面 + 脚本(init-site/sample-data/seed) + CI Node22 + Dependabot + 001_init.sql + deploy自动触发 + CSS抽取\n\nv3.1.1 | 2026-05-26T08:09:30.016Z | user\n\nPhase 4C: 前端 SPA 完整实现 + Edge API 14端点 + Cloud API 13端点 + 工程加固 (v3.1.0 version slot occupied, bump to 3.1.1)\n\nv3.1.0 | 2026-05-26T08:07:33.414Z | user\n\nPhase 4C: 前端 SPA 完整实现(20文件) + Edge API 14端点 + Cloud API 13端点 + 工程加固\n\nv3.0.6 | 2026-05-19T08:38:40.933Z | user\n\nAdd ClawScan publisher note\n\nv3.0.5 | 2026-05-16T06:24:25.370Z | user\n\nAdd clawscan-note for ClawScan context: AI code generation, EdgeOne deployment, SSE streaming are by design\n\nv3.0.4 | 2026-05-16T03:18:43.968Z | user\n\nSKILL.md metadata optimization: standardized frontmatter (20 triggers + env vars) + user confirmation checkpoint table (10 risk levels) + security compliance section\n\nv3.0.3 | 2026-05-16T03:18:11.242Z | user\n\nSKILL.md metadata optimization: standardized frontmatter (20 triggers + env vars) + user confirmation checkpoint table (10 risk levels) + security compliance section\n\nv3.0.2 | 2026-05-16T03:17:46.322Z | auto\n\n- Major cleanup: 62 obsolete and auxiliary files have been removed, retaining only the SKILL.md.\n- Project structure and documentation have been simplified, focusing on core skill instructions.\n- All implementation source code, utilities, migrations, and documentation (including README, changelogs, and guides) are no longer included in this version.\n\nv3.0.0 | 2026-05-15T06:35:54.630Z | auto\n\nVersion 3.0.0\n\n- Major overhaul: migrated to modular architecture with Layer 0 (core), Layer 1 (scenario stacks: e-commerce, AI assistant, admin), and Layer 2 (add-ons).\n- Added comprehensive subproject structure including edge-functions (V8+KV), cloud-functions (Node.js+D1), and shared types/constants/validators.\n- Introduced new templates for different site scenarios (e-commerce, ai-assistant, saas-admin).\n- Added extensive new modules: payment integration, order state machine, RBAC admin, analytics/SEO utilities, cron tasks, and middleware layers.\n- Replaced/removed outdated references and scripts; now includes dedicated onboarding docs (CONTRIBUTING.md, MIGRATION.md, CHANGELOG.md).\n- Improved environment variable management and onboarding, with clear env matrix and setup docs.\n- Enhanced security and reliability: stricter JWT/session handling, idempotency locks for payments, admin/stat auditing, and event-bus for frontend communication.\n\nv1.0.0 | 2026-05-15T06:15:56.363Z | auto\n\nwebsite-skeleton v1.0.0\n\n- 全新发布，基于 EdgeOne Pages 的全栈网站骨架 Skill，一句话生成可直接运行的前后端电商/AI/管理网站。\n- 内置 5 大核心模块：用户认证、购物车、支付、AI 流式对话、RBAC 管理后台。\n- 标准化流程（6 步）：需求收集、脚手架搭建、应用定制、环境变量配置、本地验证、移交部署。\n- 强调职责分离与安全规则，敏感操作必须用户确认，全流程不捏造成员。\n- 支持模块化裁剪、前端事件总线、支付幂等、订单超卖防护、AI 流。\n- 技术亮点齐全，预设多场景能力，一步自动部署到 EdgeOne Pages。\n\nArchive index:\n\nArchive v3.2.1: 133 files, 264529 bytes\n\nFiles: CHANGELOG.md (4454b), client/index.html (2084b), client/src/app.js (2270b), client/src/components/navbar.js (2112b), client/src/components/toast.js (2990b), client/src/pages/admin/dashboard.js (5353b), client/src/pages/admin/orders.js (9019b), client/src/pages/admin/products.js (14799b), client/src/pages/ai-chat.js (8114b), client/src/pages/cart.js (4785b), client/src/pages/checkout.js (7958b), client/src/pages/home.js (3787b), client/src/pages/login.js (2975b), client/src/pages/order-detail.js (8171b), client/src/pages/orders.js (5165b), client/src/pages/product-detail.js (5823b), client/src/pages/products.js (7011b), client/src/pages/profile.js (3662b), client/src/pages/register.js (3638b), client/src/services/ai.js (3535b), client/src/services/api.js (3148b), client/src/services/auth.js (2607b), client/src/services/cart.js (4899b), client/src/styles/main.css (11792b), client/src/utils/analytics.js (4111b), client/src/utils/auth.js (840b), client/src/utils/constants.js (753b), client/src/utils/escape-html.js (571b), client/src/utils/event-bus.js (1129b), client/src/utils/router.js (4923b), client/src/utils/seo.js (5577b), client/src/utils/storage.js (2022b), cloud-functions/api/_helpers.js (1951b), cloud-functions/api/admin/orders.js (1923b), cloud-functions/api/admin/products.js (4894b), cloud-functions/api/admin/stats.js (2659b), cloud-functions/api/admin/users.js (1607b), cloud-functions/api/ai/chat-stream.js (6956b), cloud-functions/api/auth/register.js (2876b), cloud-functions/api/order/cancel.js (2602b), cloud-functions/api/order/create.js (3320b), cloud-functions/api/order/detail.js (2395b), cloud-functions/api/order/transition.js (6357b), cloud-functions/api/pay/close.js (2194b), cloud-functions/api/pay/create-order.js (3471b), cloud-functions/api/pay/query.js (1680b), cloud-functions/api/pay/wx-notify.js (9572b), cloud-functions/cron/order-cron.js (5389b), cloud-functions/middleware/audit.js (3437b), cloud-functions/middleware/tenant-context.js (2944b), cloud-functions/utils/db.js (4368b), cloud-functions/utils/order-state-machine.js (5656b), CONTRIBUTING.md (1590b), db/migrations/001_init.sql (5539b), db/migrations/002_order_logs.sql (1555b), db/migrations/003_rollback.sql (921b), db/migrations/003_tenant_isolation.sql (3273b), db/seed.sql (9455b), docs/plans/phase-4a-plan.md (5210b), docs/plans/phase-4b-plan.md (3040b), edge-functions/_middleware.js (603b), edge-functions/api/admin/tenants.js (6492b), edge-functions/api/ai/history.js (2712b), edge-functions/api/analytics/event.js (2552b), edge-functions/api/auth/login.js (4353b), edge-functions/api/auth/logout.js (2680b), edge-functions/api/auth/me.js (2209b), edge-functions/api/auth/refresh.js (3653b), edge-functions/api/cart/add.js (2179b), edge-functions/api/cart/get.js (1302b), edge-functions/api/cart/remove.js (2037b), edge-functions/api/cart/update.js (2276b), edge-functions/api/orders/[id].js (2284b), edge-functions/api/orders/list.js (2604b), edge-functions/api/products/[id].js (2507b), edge-functions/api/products/categories.js (1101b), edge-functions/api/products/list.js (3320b), edge-functions/api/sitemap.xml.js (2314b), edge-functions/middleware/rbac.js (2867b), edge-functions/utils/rate-limit.js (1979b)\n\nFile v3.2.1:SKILL.md\n\n---\nname: 建站骨架 (EdgeOne Pages)\ndescription: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。\nversion: \"3.2.1\"\ncategory: openclaw-imports\ntags:\n  - edgeone\n  - website\n  - ecommerce\n  - deploy\ntriggers:\n  - 帮我建网站\n  - 建一个电商网站\n  - 做 AI 客服站\n  - 建管理后台\n  - EdgeOne Pages 建站\n  - 搭建网站\n  - 做个网站\n  - 生成网站\n  - 建站\n  - 创建网站\n  - 我要建站\n  - 商城网站\n  - 下单支付网站\n  - 全栈网站\n  - build a website\n  - create a full-stack app\n  - e-commerce website\n  - 建电商\n  - 网站骨架\n  - 部署网站\nenv:\n  JWT_SECRET:\n    description: JWT 签名密钥（HS256），Edge + Cloud 共享\n    required: true\n    secret: true\n  AI_API_KEY:\n    description: AI 模型调用 API Key（仅 AI 栈需要）\n    required: false\n    secret: true\n  WX_APPID:\n    description: 微信支付 AppID（仅电商栈需要）\n    required: false\n    secret: true\n  WX_MCHID:\n    description: 微信支付商户号（仅电商栈需要）\n    required: false\n    secret: true\n  DATABASE_URL:\n    description: D1 数据库绑定\n    required: false\n    secret: true\n---\n\n# 建站 Skill — EdgeOne Pages 全栈网站骨架\n\n> **版本：** 3.2.0 · **日期：** 2026-05-26 · **工程完缮（SPA详情页 + 227项测试 + 迁移/脚本/模板联动）**\n> **一句话描述：** 用户说一句话，AI 生成完整前后端网站，自动部署到 EdgeOne Pages。\n\n---\n\n## 一、核心设计理念\n\n```\n一次设计，无限复用 = 5 个模块 × 3 个场景 × 1 个部署平台\n```\n\n将\"建站\"拆解为 **Layer 0 基础设施** + **Layer 1 能力栈** + **Layer 2 可选增强**：\n\n| 层级 | 内容 | 性质 |\n|------|------|------|\n| **Layer 0**（Core） | SPA 骨架 + Auth + Middleware + EventBus | 必选，不可裁剪 |\n| **Layer 1**（Stack） | 🛒 电商栈 · 🤖 AI 栈 · 📊 管理栈 | 按需组合，互不依赖 |\n| **Layer 2**（Addon） | SEO · Analytics · i18n | 可选增强 |\n\n**场景模板优先**：用户选\"电商\"、\"AI 助手\"或\"管理后台\"场景，不选模块——模块由模板自动组合。\n\n---\n\n## 二、技术架构\n\n### 2.1 EdgeOne Pages 双运行时\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│  Platform Middleware（middleware.js）                        │\n│  ① CORS 预检（OPTIONS）                                     │\n│  ② CSP Header 注入                                          │\n│  ③ 轻量 Bearer 检查（公开路径放行）                           │\n│  ④ 支付回调 IP 白名单 → 直接 return，不进 Edge Middleware     │\n└──────────────────────────────────────────────────────────────┘\n                              ↓（非回调路径）\n┌──────────────────────────────────────────────────────────────┐\n│  Edge Functions Middleware（V8 + KV）                      │\n│  ⑤ JWT 详细校验（crypto.subtle）                             │\n│  ⑥ KV session 验证                                          │\n│  ⑦ KV 限流计数器（滑动窗口）                                   │\n└──────────────────────────────────────────────────────────────┘\n```\n\n### 2.2 运行时职责边界\n\n| 运行时 | 存储 | 职责 | 说明 |\n|--------|------|------|------|\n| **Edge Functions**（V8） | KV | Auth 登录/me、Products 公开读、Cart、Orders 读、AI History 读、幂等锁 | 延迟敏感、无密钥 |\n| **Cloud Functions**（Node） | D1 | Auth 注册/bcrypt、Payment 创建/回调、Admin CRUD、Orders 创建/取消、AI SSE 流 | 密钥操作、复杂事务 |\n\n> ⚠️ **平台约束（EdgeOne Pages）：**\n> - KV 仅 Edge Functions 可用，Cloud Functions 无法访问\n> - Cloud Functions 目录名必须为 `cloud-functions/`\n> - bcrypt 必须在 Cloud Functions 中执行\n\n### 2.3 分层目录结构\n\n```\nwebsite-skeleton/\n├── SKILL.md                    # 本文件，Skill 核心指令\n│\n├── templates/                  # 场景预设模板\n│   ├── e-commerce.json         # 🛒 电商场景\n│   ├── ai-assistant.json       # 🤖 AI 助手场景\n│   └── saas-admin.json         # 📊 SaaS 管理后台场景\n│\n├── sharing/                    # 跨运行时共享（构建时同步）\n│   ├── types.ts               # User/Product/Cart/Order/AISession 接口\n│   ├── constants.ts           # OrderStatus/UserRole/APIPaths 枚举\n│   ├── validators.ts           # 共享输入校验\n│   └── kv-keys.ts             # KV key 命名（含租户前缀占位）\n│\n├── client/                     # 前端 SPA\n│   ├── index.html\n│   └── src/\n│       ├── app.js             # 启动 + History API 路由\n│       ├── utils/\n│       │   ├── event-bus.js   # 全局事件总线（P0）\n│       │   ├── router.js      # History API 路由 + AuthGuard\n│       │   ├── escape-html.js # XSS 防护\n│       │   └── storage.js      # localStorage 封装\n│       ├── services/\n│       │   ├── api.js          # 统一客户端 + 拦截器\n│       │   ├── auth.js         # 内存 AuthService\n│       │   ├── cart.js         # 双模式购物车\n│       │   └── ai.js           # SSE 流式 AI\n│       └── components/         # 组件清单\n│\n├── middleware.js               # Platform Middleware\n│\n├── db/                         # 数据库迁移\n│   ├── migrations/\n│   │   └── 001_init.sql        # 建表脚本\n│   └── seed.sql                # 测试数据\n│\n├── docs/\n│   └── env-vars.md             # 环境变量矩阵\n│\n├── edge-functions/             # Edge Functions（V8 + KV）\n│   ├── _middleware.js          # JWT 校验 + KV session + 限流\n│   ├── api/\n│   │   ├── auth/login.js       # JWT 签发（Cookie） + KV session\n│   │   ├── auth/me.js          # KV session 读取\n│   │   ├── auth/refresh.js     # RT 轮换（KV version 乐观锁）\n│   │   ├── auth/logout.js      # 清除 Cookie + KV session\n│   │   ├── internal/idempotency.js  # Edge 原子幂等锁\n│   │   ├── products/list.js   # KV 缓存 + Cloud D1 回源\n│   │   ├── products/[id].js\n│   │   ├── products/categories.js\n│   │   ├── cart/*.js           # KV 购物车\n│   │   ├── orders/list.js      # D1 订单读取\n│   │   ├── orders/[id].js\n│   │   └── ai/history.js       # KV 读取 AI 会话历史\n│   └── utils/\n│       ├── kv-helper.js\n│       ├── jwt-helper.js       # crypto.subtle HS256\n│       ├── rate-limit.js        # KV 滑动窗口限流\n│       └── response.js\n│\n├── cloud-functions/            # Cloud Functions（Node.js）\n│   ├── api/\n│   │   ├── auth/register.js   # bcrypt cost=12 + D1\n│   │   ├── pay/create-order.js # 微信/支付宝预下单\n│   │   ├── pay/wx-notify.js   # Edge 幂等锁 → 业务处理\n│   │   ├── pay/ali-notify.js\n│   │   ├── pay/query.js\n│   │   ├── pay/close.js\n│   │   ├── admin/products.js   # D1 CRUD（含 version 乐观锁）\n│   │   ├── admin/orders.js    # D1 查询\n│   │   ├── admin/users.js     # D1 CRUD\n│   │   ├── admin/stats.js     # D1 聚合统计\n│   │   ├── order/create.js    # SELECT FOR UPDATE + 事务 + 指数退避\n│   │   ├── order/detail.js\n│   │   ├── order/cancel.js    # 状态机 + version 校验\n│   │   └── ai/chat-stream.js  # SSE 流式（主力实现）\n│   └── utils/\n│       ├── db.js               # D1 数据库工具（{tenant} 强制注入）\n│       ├── payment-sdk.js      # 微信V3/支付宝 SDK 封装\n│       ├── admin-guard.js\n│       └── notification-hooks.js  # 通知钩子空壳\n│\n├── references/                  # 能力参考文档\n│   ├── auth-module.md           # ✅ JWT RS256 + HS256 兼容 + KV Session\n│   ├── cart-module.md\n│   ├── payment-module.md\n│   ├── ai-chat-module.md\n│   ├── admin-module.md          # ✅ RBAC + CRUD + 运营统计 + 审计日志\n│   ├── notification-module.md   # Layer 2：邮件/微信/钉钉通知\n│   ├── order-state-machine.md   # ✅ 6状态 + 权限矩阵 + 库存联动 + 审计日志\n│   ├── edge-functions.md        # ✅ Edge Middleware + KV API + 限流\n│   ├── cloud-functions.md       # ✅ D1 事务 + bcrypt + 支付 SDK + SSE\n│   ├── kv-storage.md\n│   ├── middleware.md            # ✅ Platform + Edge 双层 + CSP + 支付 bypass\n│   └── deployment.md            # ✅ 完整部署流程 + Cron + 回滚\n│\n└── scripts/\n    ├── init-site.js             # 交互式初始化（模板优先）\n    ├── sync-sharing.js          # 构建时 shared → edge/cloud 同步\n    └── sample-data.js\n```\n\n---\n\n## 三、Auth 模块（Layer 0，Core）\n\n### API 路由\n\n| 方法 | 路径 | 运行时 | 说明 |\n|------|------|--------|------|\n| POST | `/api/auth/login` | Edge（KV） | JWT 签发 + KV session |\n| GET | `/api/auth/me` | Edge（KV） | KV session 读取 |\n| POST | `/api/auth/refresh` | Edge（KV） | RT 轮换（version 乐观锁） |\n| POST | `/api/auth/logout` | Edge（KV） | 清除 Cookie + KV session |\n| POST | `/api/auth/register` | Cloud（D1） | bcrypt cost=12 + D1 |\n\n### JWT 安全设计\n\n```\nAccess Token：短期 JWT（15min）+ HttpOnly Cookie（Secure + SameSite=Strict）\nRefresh Token：7天 TTL，存 KV rt:{userId}:meta（含 version）\n算法：Phase 1 用 HS256 + 短期 TTL，Phase 2 迁移 RS256\n```\n\n### 【v2.1 Critical 修复】RT 并发安全\n\n两个请求并发携带同一 RT，只有第一个能成功写入新 version，第二个收到 409 → 客户端稍等重试。\n\n```javascript\n// edge-functions/api/auth/refresh.js\nexport async function onRequest(context) {\n  const { RT } = await getTokens(context.request);\n  const { KV } = context.env;\n  const payload = parseJWT(RT);\n  const userId = payload.sub;\n  if (!userId) return new Response('Invalid', { status: 401 });\n\n  const current = await KV.get(`rt:${userId}:meta`);\n  const { version: oldVersion, token: oldToken } = JSON.parse(current || '{\"version\":0,\"token\":\"\"}');\n\n  if (oldToken !== RT) {\n    return new Response('Token already rotated', { status: 409 });\n  }\n\n  const newVersion = oldVersion + 1;\n  const newToken = signRT(userId, newVersion);\n\n  const ok = await KV.put(\n    `rt:${userId}:meta`,\n    JSON.stringify({ version: newVersion, token: newToken }),\n    { expirationTtl: 604800 }\n  );\n\n  if (!ok) return new Response('Concurrent rotation', { status: 409 });\n\n  return new Response(JSON.stringify({ refreshToken: newToken }), {\n    headers: { 'Content-Type': 'application/json' }\n  });\n}\n```\n\n---\n\n## 四、Cart 模块（Layer 1，电商栈）\n\n**双模式同步：**\n```\n未登录：localStorage（30d TTL 自动清理）\n登录时：localStorage → 服务端 KV（syncOnLogin()）\n已登录：服务端 KV（唯一数据源）\n```\n\n---\n\n## 五、Payment 模块（Layer 1，电商栈）\n\n### 独立回调路径\n\n```\n/api/pay/wx-notify   ← 微信支付回调（IP 白名单后直接 return，不进 Edge Middleware）\n/api/pay/ali-notify  ← 支付宝回调（独立路径）\n```\n\n### 【v2.1 Critical 修复】支付幂等原子锁\n\n微信支付平台会在回调超时后重试（最长 72h），KV 查→判→写三步非原子。解决方案：Edge Function `putIfNotExists` 原子幂等锁。\n\n```javascript\n// ===== Edge Function（唯一可访问 KV 的路径）=====\n// edge-functions/api/internal/idempotency.js\nexport async function onRequest(context) {\n  const { KV } = context.env;\n  const { out_trade_no, callback_id } = await context.request.json();\n\n  const acquired = await KV.putIfNotExists(\n    `pay:idempotency:${out_trade_no}`,\n    callback_id,\n    { expirationTtl: 86400 }   // 24h < 微信重试窗口 72h\n  );\n\n  return new Response(JSON.stringify({ acquired }), { status: 200 });\n}\n\n// ===== Cloud Function（微信回调处理）=====\n// cloud-functions/api/pay/wx-notify.js\nexport async function onRequest(request, env) {\n  const rawBody = await request.text();\n  if (!await verifyWechatSignature(rawBody, env.WX_MCH_SECRET))\n    return new Response('FAIL', { status: 401 });\n\n  const { out_trade_no, transaction_id, trade_state } = JSON.parse(rawBody);\n\n  const { acquired } = await fetch(`${env.EDGE_BASE}/api/internal/idempotency`, {\n    method: 'POST',\n    body: JSON.stringify({ out_trade_no, callback_id: transaction_id })\n  }).then(r => r.json());\n\n  if (!acquired) return new Response('SUCCESS');  // 幂等跳过，但返回 SUCCESS 止重试\n\n  if (trade_state === 'SUCCESS') await processPayment(out_trade_no, transaction_id, env);\n  return new Response('SUCCESS');\n}\n```\n\n---\n\n## 六、Order 创建原子性（v2.1 Critical 修复）\n\n高并发下，`UPDATE ... WHERE stock >= ?` 可能同时通过检查导致超卖。解决方案：`SELECT FOR UPDATE` + 乐观锁 + D1 CHECK 约束。\n\n```javascript\n// cloud-functions/api/order/create.js\nexport async function onRequest(request, env) {\n  const { userId } = await auth(request, env);\n  const { productId, quantity } = await request.json();\n  const pool = await getPool(env.DATABASE_URL);\n\n  let attempt = 0;\n  while (attempt < 3) {\n    attempt++;\n    try {\n      await pool.beginTransaction();\n\n      // ① SELECT FOR UPDATE：锁定商品行（持有行锁期间其他事务阻塞）\n      const [rows] = await pool.query(\n        'SELECT id, stock, price, version FROM products WHERE id = ? FOR UPDATE',\n        [productId]\n      );\n      if (!rows.length) { await pool.rollback(); return 404; }\n      const product = rows[0];\n\n      // ② 持有行锁期间校验库存（无竞态）\n      if (product.stock < quantity) {\n        await pool.rollback();\n        return { error: '库存不足', available: product.stock };\n      }\n\n      // ③ 乐观锁更新（双重保障）\n      const [updateResult] = await pool.query(\n        'UPDATE products SET stock = stock - ?, version = version + 1 WHERE id = ? AND version = ?',\n        [quantity, productId, product.version]\n      );\n      if (updateResult.affectedRows === 0) {\n        await pool.rollback();\n        return { error: '并发冲突，请重试' };\n      }\n\n      // ④ 创建订单（同一事务内）\n      const orderNo = generateOrderNo();\n      await pool.query(\n        `INSERT INTO orders (order_no, out_trade_no, user_id, product_id, qty, amount, status, created_at)\n         VALUES (?, ?, ?, ?, ?, ?, 'PENDING', NOW())`,\n        [orderNo, `WX_${orderNo}`, userId, productId, quantity, product.price * quantity]\n      );\n\n      await pool.commit();\n\n      // ⑤ 事务成功后，异步调用微信统一下单（不在事务内）\n      const payment = await createPayment(orderNo, product.price * quantity, env);\n      return { orderNo, payment };\n\n    } catch (err) {\n      await pool.rollback();\n      if (isRetryable(err) && attempt < 3) {\n        await sleep(100 * Math.pow(2, attempt - 1));  // 指数退避\n        continue;\n      }\n      return { error: '创建失败，请重试' };\n    }\n  }\n}\n\nfunction isRetryable(err) {\n  return err.code === 'ER_LOCK_DEADLOCK' || err.code === 'ER_LOCK_WAIT_TIMEOUT';\n}\n```\n\n---\n\n## 七、KV 分层查询策略\n\nEdgeOne Pages KV **不支持复合查询**，按以下策略分层：\n\n| 场景 | KV 层（Edge） | D1 层（Cloud） |\n|------|-------------|---------------|\n| 单商品读取 | ✅ KV 缓存 | — |\n| 商品列表（无筛选） | ✅ 缓存第1页 | — |\n| 分类+价格区间筛选 | — | ✅ Cloud D1 |\n| 搜索关键词 | — | ✅ Cloud D1 |\n| AI 会话历史（单用户） | ✅ KV | — |\n| 订单统计（多条件聚合） | — | ✅ Cloud D1 |\n\n---\n\n## 八、AI Chat 模块（Layer 1，AI 栈）\n\n**Cloud Functions SSE 实现（Edge 无法使用 waitUntil）：**\n\n```\n前端 → GET /api/ai/history（Edge，KV 读取）→ 拿到历史上下文\n    → SSE 连接 /api/ai/chat-stream（Cloud）→ 带历史 context\n    → Cloud 流式响应 + 异步写 KV 保存历史\n```\n\n---\n\n## 九、Admin 模块（Layer 1，管理栈）\n\n**RBAC 权限体系：**\n```\nrole: user   → 购物车、下单、查看自己的订单\nrole: admin  → 商品 CRUD、订单管理、用户管理、运营统计\n```\n\n---\n\n## 十、数据库 Schema\n\n```sql\n-- db/migrations/001_init.sql\n\nCREATE TABLE users (\n  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  email         VARCHAR(255) UNIQUE NOT NULL,\n  password_hash VARCHAR(255) NOT NULL,\n  role          ENUM('user','admin') DEFAULT 'user',\n  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE products (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  name        VARCHAR(255) NOT NULL,\n  price       DECIMAL(10,2) NOT NULL,    -- 服务端唯一价格来源\n  stock       INT UNSIGNED NOT NULL DEFAULT 0,\n  category_id INT UNSIGNED,\n  status      ENUM('active','inactive') DEFAULT 'active',\n  version     INT UNSIGNED DEFAULT 1,    -- 乐观锁版本号\n  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT chk_stock_positive CHECK (stock >= 0)\n);\n\nCREATE TABLE orders (\n  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  order_no      VARCHAR(64) UNIQUE NOT NULL,\n  out_trade_no  VARCHAR(128) UNIQUE,\n  user_id       BIGINT UNSIGNED NOT NULL,\n  total         DECIMAL(10,2) NOT NULL,\n  status        ENUM('pending','paid','shipped','cancelled','refunded') DEFAULT 'pending',\n  paid_at       DATETIME,\n  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,\n  FOREIGN KEY (user_id) REFERENCES users(id)\n);\n\nCREATE TABLE order_items (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  order_id    BIGINT UNSIGNED NOT NULL,\n  product_id  BIGINT UNSIGNED NOT NULL,\n  qty         INT UNSIGNED NOT NULL,\n  price       DECIMAL(10,2) NOT NULL,   -- 快照价格\n  FOREIGN KEY (order_id) REFERENCES orders(id),\n  FOREIGN KEY (product_id) REFERENCES products(id)\n);\n\nCREATE TABLE admin_logs (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  admin_id    BIGINT UNSIGNED NOT NULL,\n  action      VARCHAR(64) NOT NULL,\n  target      VARCHAR(128),\n  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX idx_products_category ON products(category_id);\nCREATE INDEX idx_products_status ON products(status);\nCREATE INDEX idx_orders_user ON orders(user_id);\nCREATE INDEX idx_orders_status ON orders(status);\nCREATE INDEX idx_orders_created ON orders(created_at);\n```\n\n---\n\n## 十一、环境变量矩阵\n\n| 环境变量 | 必填 | 用于 | 运行时 |\n|---------|------|------|--------|\n| `JWT_SECRET` | ✅ | JWT 签名（HS256） | Edge + Cloud |\n| `AI_API_KEY` | ✅（AI栈） | AI 模型调用 | Cloud |\n| `WX_APPID` | ✅（电商栈） | 微信支付 AppID | Cloud |\n| `WX_MCHID` | ✅（电商栈） | 微信支付商户号 | Cloud |\n| `WX_API_KEY` | ✅（电商栈） | 微信支付 APIv3 密钥 | Cloud |\n| `WX_CERT_PATH` | ✅（电商栈） | 微信支付证书路径 | Cloud |\n| `ALI_APP_ID` | ✅（电商栈） | 支付宝 AppID | Cloud |\n| `ALI_PRIVATE_KEY` | ✅（电商栈） | 支付宝私钥 | Cloud |\n| `DATABASE_URL` | ✅（电商+管理） | D1 数据库绑定 | Cloud |\n| `EDGE_BASE` | ✅（电商栈） | Edge Function 内部网关地址 | Cloud |\n\n---\n\n## 十二、初始化工作流\n\n```\nStep 1: 选择建站类型\n  [1] 🛒 快速电商站（推荐）\n  [2] 🤖 AI 客服站\n  [3] 📊 SaaS 管理后台\n  [4] ⚙️ 自定义模块组合\n\nStep 2: 确认预填 / 模块选择\n\nStep 3: 填写基本信息（站点名、域名）\n\nStep 4: 密钥配置（从 env-vars.md 模板读取，EdgeOne Pages 环境变量注入）\n\nStep 5: 执行 db/migrations/001_init.sql（自动或手动）\n\nStep 6: 生成代码 → edgeone deploy → 返回访问 URL\n```\n\n---\n\n## 十三、安全检查清单\n\n### 🔴 P0（上线前必须完成）\n\n- [x] 支付幂等：Edge 原子 `putIfNotExists` 锁\n- [x] 订单超卖：`SELECT FOR UPDATE` + D1 事务 + CHECK 约束\n- [x] RT 并发安全：KV version 乐观锁（409 重试）\n- [x] KV 复合查询：分层策略（KV 缓存 / D1 复杂查询）\n- [x] 支付回调路径 Platform Middleware 直接 return\n- [x] 金额服务端 D1 计算，前端永不传 price\n- [x] bcrypt cost ≥ 12（Cloud Functions 中）\n\n### 🟡 P1（正式版前完成）\n\n- [x] JWT 短期 Access Token（15min）+ RT 轮换（含并发安全版本号）\n- [x] Cookie：HttpOnly + Secure + SameSite=Strict（含 SameSite=Lax 备选方案）\n- [x] AI 聊天限流（KV 滑动窗口：未登录 10次/分钟，登录 60次/分钟）\n- [x] CSP Header（Platform Middleware 注入，含 nonce 升级路径）\n- [x] EventBus 401 自动跳转登录（含 redirect 回跳逻辑）\n- [x] Notification 钩子（Phase 2 完整适配器设计 + 事件注册机制）\n\n### 🟢 P2（Phase 3 实现）\n\n- [x] RS256 迁移（双轨并行 HS256/RS256，30 天兼容窗口）\n- [x] 订单状态机（6状态 + 权限矩阵 + version 校验 + 库存联动 + 审计日志 + 定时 Cron）\n\n---\n\n## 十五、Phase 2 详细设计（P1/P2 实现指南）\n\n---\n\n### 15.1 【P1】JWT Access Token 短期化 + RT 轮换（已实现源码）\n\n以下为 Edge Functions 完整实现，Phase 1 已集成：\n\n**JWT 签发（login.js）** — Access Token 15min + Refresh Token 7d：\n\n```javascript\n// edge-functions/api/auth/login.js\nexport async function onRequest(context) {\n  const { email, password } = await context.request.json();\n  const pool = await getCloudPool(context.env.DATABASE_URL);\n  const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);\n  if (!rows.length) return new Response('Unauthorized', { status: 401 });\n\n  const user = rows[0];\n  const ok = await bcrypt.compare(password, user.password_hash);\n  if (!ok) return new Response('Unauthorized', { status: 401 });\n\n  const now = Math.floor(Date.now() / 1000);\n  // Access Token：15min\n  const accessToken = signJWT({ sub: user.id, role: user.role, type: 'access' }, 900);\n  // Refresh Token：7d，含 version 用于乐观锁\n  const rtVersion = 1;\n  const refreshToken = signRT(user.id, rtVersion);\n\n  // KV 存 RT meta（用于轮换校验）\n  await context.env.KV.put(\n    `rt:${user.id}:meta`,\n    JSON.stringify({ version: rtVersion, token: refreshToken }),\n    { expirationTtl: 604800 }\n  );\n\n  return new Response(null, {\n    status: 302,\n    headers: {\n      'Location': '/',\n      'Set-Cookie': [\n        `at=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`,\n        `rt=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800`\n      ].join(', ')\n    }\n  });\n}\n```\n\n**RT 轮换（refresh.js）** — 并发安全，version 乐观锁：\n\n```javascript\n// edge-functions/api/auth/refresh.js\nexport async function onRequest(context) {\n  const { KV } = context.env;\n  const cookieHeader = context.request.headers.get('Cookie') || '';\n  const rtMatch = cookieHeader.match(/rt=([^;]+)/);\n  if (!rtMatch) return new Response('No RT', { status: 401 });\n\n  const oldToken = rtMatch[1];\n  const payload = parseJWT(oldToken);\n  const userId = payload.sub;\n\n  // KV version 乐观锁：只有 RT 匹配当前 version 才允许写入新 version\n  const current = await KV.get(`rt:${userId}:meta`);\n  const { version: oldVersion, token: oldStored } = JSON.parse(current || '{\"version\":0,\"token\":\"\"}');\n\n  if (oldStored !== oldToken) {\n    // 另一个 tab 已轮换，当前 RT 失效 → 返回 409 让客户端重新登录\n    return new Response('Concurrent rotation', { status: 409 });\n  }\n\n  const newVersion = oldVersion + 1;\n  const newToken = signRT(userId, newVersion);\n\n  const ok = await KV.put(\n    `rt:${userId}:meta`,\n    JSON.stringify({ version: newVersion, token: newToken }),\n    { expirationTtl: 604800 }\n  );\n  if (!ok) return new Response('Rotation failed', { status: 409 });\n\n  return new Response(JSON.stringify({ refreshToken: newToken }), {\n    headers: {\n      'Content-Type': 'application/json',\n      'Set-Cookie': `rt=${newToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800`\n    }\n  });\n}\n```\n\n**客户端轮换触发逻辑（event-bus.js 集成）**：\n\n```javascript\n// client/src/utils/event-bus.js\nEventBus.on('auth:401', async () => {\n  // Access Token 过期 → 尝试轮换 RT\n  const res = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' });\n  if (res.ok) {\n    // RT 轮换成功 → 重发原请求\n    return retryOriginalRequest();\n  }\n  // RT 也失败 → 跳转登录\n  window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);\n});\n```\n\n---\n\n### 15.2 【P1】Cookie 安全属性\n\n所有认证 Cookie 必须同时满足以下属性（缺一不可）：\n\n| 属性 | 值 | 作用 |\n|------|-----|------|\n| `HttpOnly` | 必须 | 阻止 JS 读取，防止 XSS 窃取 |\n| `Secure` | 必须 | 仅 HTTPS 传输 |\n| `SameSite=Strict` | 强烈建议 | 防止 CSRF（同站请求才带 Cookie） |\n| `SameSite=Lax` | 备选 | 允许导航带 Cookie，但阻止跨站 POST |\n| `Path=/` | AT Cookie | 全路径生效 |\n| `Path=/api/auth/refresh` | RT Cookie | 仅刷新接口可读 |\n\n**Edge Functions 签发示例**：\n\n```javascript\n// 正确\nheaders.set('Set-Cookie',\n  `at=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`\n);\n\n// 常见错误：缺少 Secure 或 SameSite\n// ❌ `at=${token}; HttpOnly` — 可被 HTTP 拦截\n// ❌ `at=${token}; HttpOnly; SameSite=None` — 无 CSRF 保护\n```\n\n**注意**：`SameSite=Strict` 会导致从外部链接跳转过来时无法携带 Cookie。如有第三方回调场景，改用 `SameSite=Lax` + CSRF Token 双保险。\n\n---\n\n### 15.3 【P1】AI 聊天限流（KV 滑动窗口）\n\n**限流策略**：\n\n| 用户状态 | 限额 | 窗口 |\n|---------|------|------|\n| 未登录（IP 级别） | 10 次/分钟 | 滑动窗口 |\n| 已登录（User ID 级别） | 60 次/分钟 | 滑动窗口 |\n\n**Edge Function 实现**：\n\n```javascript\n// edge-functions/_middleware.js 或独立限流工具\n// edge-functions/utils/rate-limit.js\n\nexport async function checkRateLimit(context, key, limit) {\n  const { KV } = context.env;\n  const now = Date.now();\n  const windowMs = 60 * 1000; // 1 分钟滑动窗口\n  const windowKey = `rl:${key}:${Math.floor(now / windowMs)}`;\n  const prevKey = `rl:${key}:${Math.floor((now - windowMs) / windowMs)}`;\n\n  const current = parseInt(await KV.get(windowKey) || '0');\n  const prev = parseInt(await KV.get(prevKey) || '0');\n\n  // 滑动窗口：当前窗口占比 + 上一窗口剩余权重\n  const prevWeight = (now % windowMs) / windowMs;\n  const totalWeight = current + prev * prevWeight;\n\n  if (totalWeight >= limit) {\n    return { allowed: false, remaining: 0, resetMs: windowMs - (now % windowMs) };\n  }\n\n  // 写入当前计数\n  await KV.put(windowKey, String(current + 1), { expirationTtl: 120 });\n  return { allowed: true, remaining: limit - Math.ceil(totalWeight) - 1, resetMs: windowMs };\n}\n\n// 在 AI Chat Edge Middleware 中调用：\n// const userId = payload?.sub || request.headers.get('CF-Connecting-IP');\n// const { allowed, resetMs } = await checkRateLimit(context, `ai:${userId}`, 60);\n// if (!allowed) return new Response('Rate limited', { status: 429, headers: { 'Retry-After': String(Math.ceil(resetMs/1000)) } });\n```\n\n---\n\n### 15.4 【P1】CSP Header（Platform Middleware 注入）\n\nCSP 在 Platform Middleware 层注入，对所有 HTML 响应生效：\n\n```javascript\n// middleware.js（项目根目录，Platform Middleware）\nexport function onRequest(context) {\n  const response = context.next();\n\n  // 仅对 HTML 响应注入 CSP\n  const contentType = response.headers.get('Content-Type') || '';\n  if (!contentType.includes('text/html')) return response;\n\n  const CSP = [\n    \"default-src 'self'\",\n    \"script-src 'self' 'unsafe-inline'\",       // Skill 生成代码含内联脚本，放行\n    \"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com\",\n    \"font-src 'self' https://fonts.gstatic.com\",\n    \"img-src 'self' data: https:\",\n    \"connect-src 'self' https://api.edgeone.dev https://api.weixin.qq.com https://openapi.alipay.com\",\n    \"frame-ancestors 'none'\",\n    \"base-uri 'self'\",\n    \"form-action 'self'\"\n  ].join('; ');\n\n  const newHeaders = new Headers(response.headers);\n  newHeaders.set('Content-Security-Policy', CSP);\n  newHeaders.set('X-Content-Type-Options', 'nosniff');\n  newHeaders.set('X-Frame-Options', 'DENY');\n  newHeaders.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n\n  return new Response(response.body, {\n    status: response.status,\n    statusText: response.statusText,\n    headers: newHeaders\n  });\n}\n```\n\n**配置说明**：\n- `connect-src` 中的域名需根据实际 AI API 和支付平台调整\n- `'unsafe-inline'` 用于 Skill 生成的内联脚本（Phase 1 MVP 可接受）\n- Phase 3 可升级为 nonce 模式消除 `unsafe-inline`\n\n---\n\n### 15.5 【P1】EventBus 401 自动跳转\n\n前端 EventBus 统一处理认证失效事件：\n\n```javascript\n// client/src/utils/event-bus.js\nclass EventBus {\n  constructor() {\n    this.listeners = {};\n    // 全局监听 fetch 401 响应\n    this._setupGlobal401Handler();\n  }\n\n  _setupGlobal401Handler() {\n    const originalFetch = window.fetch;\n    window.fetch = async (...args) => {\n      try {\n        const res = await originalFetch(...args);\n        if (res.status === 401) {\n          this.emit('auth:401', { url: args[0], response: res });\n        }\n        return res;\n      } catch (err) {\n        throw err;\n      }\n    };\n  }\n\n  on(event, handler) {\n    (this.listeners[event] ||= []).push(handler);\n    return () => this.listeners[event] = this.listeners[event].filter(h => h !== handler);\n  }\n\n  emit(event, data) {\n    (this.listeners[event] || []).forEach(h => h(data));\n  }\n}\n\nexport const eventBus = new EventBus();\n\n// 应用启动时注册 401 跳转\neventBus.on('auth:401', ({ url }) => {\n  // 排除登录页自身，避免死循环\n  if (url.includes('/api/auth/login') || url.includes('/api/auth/register')) return;\n  // 跳过 refresh 接口（它有自己的 401 处理）\n  if (url.includes('/api/auth/refresh')) return;\n  // 记录原页面路径，登录后回跳\n  const redirect = encodeURIComponent(window.location.pathname + window.location.search);\n  window.location.href = `/login?redirect=${redirect}`;\n});\n```\n\n---\n\n### 15.6 【P1】Notification 钩子详细设计\n\nNotification 作为 Layer 2 Addon，按需接入。支持多通道：邮件、微信模板消息、钉钉 Webhook。\n\n**接口设计（空壳 → Phase 2 填充适配器）**：\n\n```javascript\n// cloud-functions/utils/notification-hooks.js\n\n// 通知事件类型\nexport const NotificationEvent = {\n  ORDER_CREATED:    'order.created',\n  ORDER_PAID:       'order.paid',\n  ORDER_SHIPPED:    'order.shipped',\n  ORDER_DELIVERED:  'order.delivered',\n  USER_REGISTERED: 'user.registered',\n  PASSWORD_CHANGED: 'password.changed',\n};\n\n// 通知渠道\nexport const NotificationChannel = {\n  EMAIL:    'email',\n  WECHAT:   'wechat',   // 微信模板消息\n  DINGTALK: 'dingtalk', // 钉钉 Webhook\n  SMS:      'sms',\n};\n\n// 钩子注册表（Phase 2 填充）\nconst handlers = {\n  [NotificationEvent.ORDER_PAID]: [],\n  [NotificationEvent.USER_REGISTERED]: [],\n};\n\nexport function registerHandler(event, handler) {\n  handlers[event] ||= [];\n  handlers[event].push(handler);\n}\n\nexport async function emit(event, payload) {\n  const eventHandlers = handlers[event] || [];\n  await Promise.allSettled(\n    eventHandlers.map(h => h(payload).catch(err => console.error(`Notification handler error: ${err}`)))\n  );\n}\n\n// ===== 具体适配器示例（Phase 2 实现）=====\n\n// 邮件适配器\nregisterHandler(NotificationEvent.ORDER_PAID, async ({ order, user }) => {\n  // 需配置 SMTP 环境变量\n  if (!process.env.SMTP_HOST) return; // 无邮件配置则跳过\n  await sendEmail({\n    to: user.email,\n    subject: `订单 ${order.order_no} 支付成功`,\n    html: `<h2>感谢您的购买！</h2><p>订单号：${order.order_no}</p>`\n  });\n});\n\n// 微信模板消息适配器\nregisterHandler(NotificationEvent.ORDER_SHIPPED, async ({ order, user }) => {\n  if (!process.env.WX_TEMPLATE_ID_SHIP) return;\n  await sendWechatTemplate(user.openid, process.env.WX_TEMPLATE_ID_SHIP, {\n    keyword1: order.order_no,\n    keyword2: order.express_company + ' ' + order.express_no,\n  });\n});\n\n// 调用示例（Cloud Functions 中）\nimport { emit, NotificationEvent } from './utils/notification-hooks.js';\n\nexport async function onRequest(request, env) {\n  // 支付回调成功后触发\n  await emit(NotificationEvent.ORDER_PAID, { order, user });\n  return new Response('SUCCESS');\n}\n```\n\n**env-vars.md 补充字段**：\n\n```\nNOTIFICATION_SMTP_HOST     # 邮件 SMTP 主机\nNOTIFICATION_SMTP_PORT     # 邮件 SMTP 端口（默认 587）\nNOTIFICATION_SMTP_USER     # 邮件发件人\nNOTIFICATION_SMTP_PASS     # 邮件密码\nNOTIFICATION_FROM_EMAIL    # 发件人地址\nWX_TEMPLATE_ID_ORDER       # 微信订单通知模板 ID\nWX_TEMPLATE_ID_SHIP        # 微信发货通知模板 ID\nDINGTALK_WEBHOOK_URL       # 钉钉群 Webhook URL\n```\n\n---\n\n### 15.7 【P2】RS256 迁移方案\n\nPhase 1 使用 HS256（密钥共享，简单快速）；Phase 2 迁移到 RS256（公私钥，安全性更高）。\n\n**迁移策略：双轨并行，渐进式切换**\n\n```\nPhase 1（当前）：HS256\n  - JWT_SECRET = 对称密钥（Edge + Cloud 共享）\n\nPhase 2 迁移：\n  - 新增 JWT_PRIVATE_KEY（Cloud 签名用 RSA 私钥）\n  - 新增 JWT_PUBLIC_KEY（Edge 验证用 RSA 公钥）\n  - Edge Functions 验证用公钥（无需密钥）\n  - Cloud Functions 签名用私钥\n  - HS256 保留 30 天兼容窗口（老 token 仍可验证）\n```\n\n**生成密钥对**：\n\n```bash\n# 生成 RSA-256 密钥对\nopenssl genrsa -out private.pem 2048\nopenssl rsa -in private.pem -pubout -out public.pem\n# 将公钥 public.pem 内容填入 EdgeOne Pages 环境变量 JWT_PUBLIC_KEY\n# 将私钥 private.pem 内容填入 Cloud Functions 环境变量 JWT_PRIVATE_KEY（严格保密）\n```\n\n**Cloud Functions 签名切换**：\n\n```javascript\n// cloud-functions/utils/jwt-helper.js\nimport { SignJWT, jwtVerify } from 'jose';\n\nconst getSignKey = (env) => {\n  if (env.JWT_PRIVATE_KEY) {\n    return createPrivateKey(env.JWT_PRIVATE_KEY); // RS256\n  }\n  return new TextEncoder().encode(env.JWT_SECRET); // 兼容 HS256\n};\n\nexport async function signJWT(payload, expiresIn, env) {\n  const key = getSignKey(env);\n  return new SignJWT(payload)\n    .setProtectedHeader({ alg: env.JWT_PRIVATE_KEY ? 'RS256' : 'HS256' })\n    .setIssuedAt()\n    .setExpirationTime(`${expiresIn}s`)\n    .sign(key);\n}\n```\n\n**Edge Functions 验证（始终用公钥）**：\n\n```javascript\n// edge-functions/utils/jwt-helper.js\nexport async function verifyJWT(token, env) {\n  const publicKey = createPublicKey(env.JWT_PUBLIC_KEY); // RS256 验证\n  try {\n    const { payload } = await jwtVerify(token, publicKey);\n    return payload;\n  } catch {\n    // 30 天兼容窗口：尝试 HS256 验证（仅过渡期）\n    const secret = new TextEncoder().encode(env.JWT_SECRET);\n    try {\n      const { payload } = await jwtVerify(token, secret);\n      return { ...payload, _hs256Fallback: true }; // 标记老 token\n    } catch {\n      return null;\n    }\n  }\n}\n```\n\n---\n\n### 15.8 【P2】订单状态机详细设计\n\n**状态定义与流转**：\n\n```\n┌──────────┐  pay    ┌───────┐  ship   ┌──────────┐  confirm ┌───────────┐\n│ PENDING  │ ──────→ │ PAID  │ ─────→ │ SHIPPED  │ ──────→ │ COMPLETED │\n└──────────┘         └───────┘         └──────────┘         └───────────┘\n     │                    │                                       │\n     │ cancel (user)      │ refund (user/admin)                   │\n     ↓                    ↓                                       │\n┌──────────┐         ┌──────────┐                                 │\n│ CANCELLED│         │ REFUNDED │                                 │\n└──────────┘         └──────────┘                                 │\n                                                                  │\n                        refund (admin, COMPLETED)                 │\n                        ─────────────────────────────────────────┘\n```\n\n**合法流转规则（version 乐观锁保护）**：\n\n| 当前状态 | 允许目标状态 | 触发方 | 条件 |\n|---------|------------|--------|------|\n| PENDING | PAID | 支付回调 | 金额核对成功 |\n| PENDING | CANCELLED | 用户/系统超时 | 30min 未支付 |\n| PAID | SHIPPED | 管理员 | 填写物流信息 |\n| PAID | REFUNDED | 用户/管理员 | 退款申请 |\n| SHIPPED | COMPLETED | 用户/系统 | 7天无售后自动确认 |\n| SHIPPED | REFUNDED | 用户/管理员 | 退货退款 |\n| COMPLETED | REFUNDED | 管理员 | 特殊退款审批 |\n\n**状态机实现（D1 + version 乐观锁）**：\n\n```javascript\n// cloud-functions/api/order/cancel.js\nexport async function onRequest(request, env) {\n  const { userId, role } = await auth(request, env);\n  const { orderId, reason } = await request.json();\n  const pool = await getPool(env.DATABASE_URL);\n\n  let attempt = 0;\n  while (attempt < 3) {\n    attempt++;\n    try {\n      await pool.beginTransaction();\n\n      // ① 锁定订单行，获取当前状态和版本\n      const [rows] = await pool.query(\n        'SELECT * FROM orders WHERE id = ? FOR UPDATE',\n        [orderId]\n      );\n      if (!rows.length) { await pool.rollback(); return 404; }\n      const order = rows[0];\n\n      // ② 权限校验：用户只能取消自己的 PENDING 订单\n      if (role !== 'admin' && order.user_id !== userId) {\n        await pool.rollback(); return 403;\n      }\n\n      // ③ 状态机校验\n      const allowed = {\n        'PENDING': ['CANCELLED'],\n        'PAID': ['CANCELLED', 'REFUNDED'],     // 退款需管理员\n        'SHIPPED': ['COMPLETED', 'REFUNDED'],  // 已发货需管理员\n      };\n      const target = reason === 'user_cancel' ? 'CANCELLED' : 'REFUNDED';\n      if (!allowed[order.status]?.includes(target)) {\n        await pool.rollback();\n        return { error: `状态 ${order.status} 不允许变更为 ${target}` };\n      }\n      if (target === 'CANCELLED' && role !== 'admin' && order.status !== 'PENDING') {\n        await pool.rollback();\n        return { error: '仅 PENDING 状态可由用户取消' };\n      }\n\n      // ④ 乐观锁更新（防止并发修改）\n      const [result] = await pool.query(\n        'UPDATE orders SET status = ?, version = version + 1 WHERE id = ? AND version = ?',\n        [target, orderId, order.version]\n      );\n      if (result.affectedRows === 0) {\n        await pool.rollback(); // 版本冲突，重试\n        continue;\n      }\n\n      // ⑤ 释放库存（仅取消时回补）\n      if (target === 'CANCELLED') {\n        await pool.query(\n          'UPDATE products SET stock = stock + (SELECT qty FROM order_items WHERE order_id = ?), version = version + 1 WHERE id = (SELECT product_id FROM order_items WHERE order_id = ?)',\n          [orderId, orderId]\n        );\n      }\n\n      // ⑥ 记录操作日志\n      await pool.query(\n        'INSERT INTO admin_logs (admin_id, action, target) VALUES (?, ?, ?)',\n        [userId, `order_status_change:${order.status}→${target}`, orderId]\n      );\n\n      await pool.commit();\n\n      // ⑦ 触发通知钩子\n      await emit(NotificationEvent.ORDER_CANCELLED, { order, reason });\n\n      return { success: true, status: target };\n\n    } catch (err) {\n      await pool.rollback();\n      if (err.code === 'ER_LOCK_DEADLOCK' && attempt < 3) {\n        await sleep(100 * Math.pow(2, attempt));\n        continue;\n      }\n      return { error: '操作失败，请重试' };\n    }\n  }\n}\n```\n\n---\n\n## 十六、Phase 2 验收标准\n\n| ID | 验收项 | 验证方法 |\n|----|--------|---------|\n| P2-01 | JWT 15min AT + 7d RT + Cookie 全属性 | 登录后 DevTools 查看 Cookie 属性 |\n| P2-02 | 并发刷新 RT，第二个请求返回 409 | 两个 tab 同时触发刷新 |\n| P2-03 | EventBus 401 跳转登录并回跳 | Token 过期后触发验证 |\n| P2-04 | AI 限流：未登录 11 次请求第 11 个返回 429 | 匿名请求连续发送 |\n| P2-05 | CSP Header 存在于 HTML 响应中 | `curl -I` 查看响应头 |\n| P2-06 | 订单状态机：PENDING→CANCELLED 成功 | 调用 cancel API |\n| P2-07 | 订单状态机：PAID→CANCELLED 被拒绝（需 admin） | 用户端测试 |\n| P2-08 | Notification 钩子注册 + emit 触发 | 单元测试验证 |\n| P2-09 | RS256 双轨验证（可选 Phase 2 末期） | HS/RS 混合 token 混跑 |\n\n---\n\n## 十七、功能验证清单（Phase 2 更新）\n\n**Demo 站点：** https://website-skeleton-demo-8mv8fitk.edgeone.cool（需有效期内的 EdgeOne Pages 访问 Token）\n\n| # | 功能 | 验证方法 | 状态 |\n|---|------|---------|------|\n| V-01 | 首页商品浏览（12 个商品） | API 返回 12 个商品，含名称/价格/库存 | ✅ |\n| V-02 | 用户注册（bcrypt cost=12） | 注册成功，返回 userId/email | ✅ |\n| V-03 | 用户登录（JWT） | 登录成功，返回用户信息 | ✅ |\n| V-04 | 购物车（localStorage 持久化） | Next.js 客户端路由，需浏览器测试 | 🟡 浏览器验证 |\n| V-05 | 结账（微信/支付宝选择） | checkout 页面存在，需浏览器测试 | 🟡 浏览器验证 |\n| V-06 | 模拟支付成功回调 | confirm API 存在，需有效 session | 🟡 需 session |\n| V-07 | 我的订单（状态标签） | orders API 存在，需有效 session | 🟡 需 session |\n\n---\n\n## 十八、Phase 2 里程碑\n\n```\n✅ Phase 1 完成：安全 Critical 全部修复（7/7 P0）\n🟡 Phase 2 进行中：P1 安全加固 + P2 能力完善\n🔲 Phase 3（可选）：RS256 + nonce CSP + SSE 优化\n```\n\n> **Phase 2 完成后，网站骨架 Skill 具备生产级安全性与完整功能集。**\n\n---\n\n## 十八、Phase 3 实现（P2 编码 + Layer 2 Addon + 多租户铺垫）\n\n### Phase 3 里程碑\n\n```\n✅ Phase 1 完成：Mock 数据 Demo，架构验证\n✅ Phase 2 完成：P0/P1 安全设计 + P2 设计文档完整\n✅ Phase 3 完成：P2 实现 + Layer 2 Addon + 多租户铺垫\n```\n\n### P2-1：RS256 双轨迁移（sharing/jwt-helper.js）\n\n**实现文件：** `sharing/jwt-helper.js`\n\n- 签发：RS256 私钥（`JWT_PRIVATE_KEY` 环境变量）\n- 验证：优先 RS256，30 天内旧 HS256 token 仍可验证\n- 迁移时间线：Day 0 部署 → Day 30 移除 HS256 兼容分支\n\n```javascript\n// 签发（永远 RS256）\nconst token = await signJWT({ sub: user.id, role: 'admin' }, AT_TTL_MS, env);\n\n// 验证（自动双轨）\nconst payload = await verifyJWT(token, env);\n// payload._alg === 'RS256' → 新 token\n// payload._alg === 'HS256' → 30天兼容窗口内的旧 token\n```\n\n### P2-2：订单状态机（cloud-functions/）\n\n**实现文件：**\n- `cloud-functions/utils/order-state-machine.js` — 核心状态机 + TRANSITIONS 表 + PERMISSIONS 表\n- `cloud-functions/api/order/transition.js` — 统一状态变更 API\n- `cloud-functions/cron/order-cron.js` — 定时任务（PENDING 超时取消 / SHIPPED 自动完成）\n- `db/migrations/002_order_logs.sql` — `order_status_logs` 审计表\n\n**状态流转（6 状态）：**\n```\nPENDING → PAID → SHIPPED → COMPLETED\n    ↓        ↓        ↓\nCANCELLED  REFUNDED  REFUNDED\n```\n\n**权限矩阵：**\n| 变更 | 用户（本人） | 管理员 |\n|------|------------|--------|\n| PENDING→CANCELLED | ✅ | ✅ |\n| PAID→SHIPPED | — | ✅ |\n| PAID/SHIPPED→REFUNDED | ✅（本人） | ✅ |\n| SHIPPED→COMPLETED | ✅ | ✅ |\n\n### L2-1：SEO 模块（client/src/utils/seo.js + edge-functions/）\n\n**实现文件：**\n- `client/src/utils/seo.js` — JSON-LD 生成器 + Meta Tags + Sitemap XML 生成器\n- `edge-functions/api/sitemap.xml.js` — 动态 Sitemap API（Edge Function，5 分钟缓存）\n- `sharing/i18n/zh-CN.js` + `en-US.js` — 中英文案\n\n**JSON-LD 支持：**\n- `WebSite`（首页）\n- `Product`（产品页，含 offers/aggregateRating）\n- `BreadcrumbList`（面包屑）\n- `Organization`（组织信息）\n\n### L2-2：i18n 国际化（sharing/i18n/）\n\n**实现文件：**\n- `sharing/i18n/zh-CN.js` — 中文文案\n- `sharing/i18n/en-US.js` — 英文文案\n- `sharing/i18n/i18n.js` — 翻译函数 `t(key)` + 语言切换\n\n**使用方式：**\n```javascript\nimport { t, setLang, getLang } from './i18n.js';\n\nt('nav.home')           // → '首页'\nt('order.status.PAID')  // → '已支付'\nsetLang('en-US');       // 切换语言\n```\n\n### L2-3：Analytics 埋点（client/src/utils/analytics.js）\n\n**实现文件：**\n- `client/src/utils/analytics.js` — 埋点 SDK\n- `edge-functions/api/analytics/event.js` — 事件接收 API（KV 存储）\n\n**预定义事件：** `page_view` / `add_to_cart` / `checkout_start` / `purchase` / `signup` / `login` / `search`\n\n**特点：** `navigator.sendBeacon` 不阻塞导航，支持页面卸载时发送。\n\n### L3-1：Multi-tenant KV 前缀（sharing/kv-keys.js）\n\n**实现文件：** `sharing/kv-keys.js`\n\n所有 KV Key 统一加租户前缀：\n```\nPhase 3: \"default:session:abc123\"\nPhase 4: \"{tenant}:session:abc123\"（从 JWT payload.tenant 动态读取）\n```\n\n### Phase 3 新增文件清单\n\n```\nsharing/\n├── jwt-helper.js              ✅ RS256 + HS256 双轨\n├── kv-keys.js                 ✅ 多租户前缀\n└── i18n/\n    ├── zh-CN.js               ✅ 中文\n    ├── en-US.js               ✅ 英文\n    └── i18n.js                ✅ 翻译函数\n\ncloud-functions/\n├── utils/\n│   └── order-state-machine.js ✅ 核心状态机\n├── api/order/\n│   └── transition.js          ✅ 状态变更 API\n└── cron/\n    └── order-cron.js          ✅ 定时任务\n\nclient/src/utils/\n├── seo.js                     ✅ SEO 工具\n└── analytics.js               ✅ 埋点 SDK\n\nedge-functions/\n├── api/\n│   ├── sitemap.xml.js         ✅ Sitemap API\n│   └── analytics/event.js     ✅ 埋点接收\n\ndb/migrations/\n└── 002_order_logs.sql         ✅ 审计日志表\n\nreferences/\n├── admin-module.md            ✅ 补充\n├── edge-functions.md          ✅ 补充\n├── cloud-functions.md         ✅ 补充\n├── middleware.md              ✅ 补充\n└── deployment.md              ✅ 补充\n```\n\n---\n\n## 十九、Phase 3 验收标准\n\n| ID | 验收项 | 验证方法 |\n|----|--------|---------|\n| P3-01 | RS256：新 token 用 RS256 私钥签发 | 代码审查 + 手动 JWT 解析 |\n| P3-02 | RS256：HS256 旧 token 30 天内仍可验证 | 测试过期 token 验证 |\n| P3-03 | 订单状态机：用户取消 PENDING 订单成功 | 调用 transition API |\n| P3-04 | 订单状态机：用户无法 PAID→CANCELLED（403） | 调用 transition API |\n| P3-05 | 库存联动：取消/退款时 stock 回补 | 查询 products 表 |\n| P3-06 | 审计日志：每次状态变更写入 order_status_logs | 查询数据库 |\n| P3-07 | Cron：PENDING 超时 30 分钟自动 CANCELLED | 模拟超时订单 |\n| P3-08 | SEO JSON-LD：产品页含 schema.org 结构化数据 | 审查页面源码 |\n| P3-09 | Sitemap：/api/sitemap.xml 返回有效 XML | curl 访问 |\n| P3-10 | i18n：`t('order.status.PAID')` 正确输出中英文 | 切换语言测试 |\n| P3-11 | Analytics：add_to_cart 事件通过 sendBeacon 发送 | Network 面板验证 |\n| P3-12 | Multi-tenant：KV key 格式含 \"default:\" 前缀 | 代码审查 |\n\n---\n\n## 二十、未来演进\n\n```\nPhase 1：Mock 数据 Demo ✅\nPhase 2：P0/P1 安全设计 + P2 设计文档 ✅\nPhase 3：P2 编码实现 + Layer 2 Addon + 多租户铺垫 ✅\n\nPhase 4（规划中）：多租户 SaaS\n  - KV key 从 JWT payload.tenant 动态读取\n  - 租户隔离数据库（D1 schema）\n  - 租户管理后台\n  - 计费系统（按量/订阅）\n\nPhase 5（规划中）：npm 包化\n  npm install @site-skeleton/auth\n  npm install @site-skeleton/payment\n```\n\n*Skill 版本演进由评审驱动，每 Phase 完成后更新版本号与文档。*\n\n\n## 用户确认检查点\n\n以下操作必须在执行前暂停，向用户说明并等待确认：\n\n| # | 操作 | 检查点说明 | 风险 |\n|---|------|-----------|------|\n| 1 | **edgeone pages deploy** | 显示待部署文件清单和变更概要，确认目标项目（测试/生产） | 高 |\n| 2 | **激活真实支付网关** | 切换到正式商户号前确认：签名验证已通过、测试环境已跑通全流程 | 高 |\n| 3 | **注入生产环境变量** | 确认所有密钥（JWT_SECRET / WX_API_KEY / DATABASE_URL）已正确配置 | 高 |\n| 4 | **执行数据库迁移** | 先备份现有数据、确认迁移脚本影响范围 | 高 |\n| 5 | **启用 Cron 定时任务** | 确认 PENDING 超时（30min）和自动完成（7天）阈值符合业务需求 | 中 |\n| 6 | **生成网站代码前** | 向用户展示选择的场景模板和模块组合，确认后再执行代码生成 | 中 |\n| 7 | **绑定自定义域名** | 确认 DNS 解析已配置、SSL 证书已签发、旧域名流量迁移方案 | 中 |\n| 8 | **回滚部署** | 确认回滚到哪个版本、是否会影响正在进行的交易 | 高 |\n| 9 | **开启多租户模式** | 确认 KV key 前缀迁移策略、租户隔离方案 | 中 |\n| 10 | **升级 RS256（从 HS256）** | 确认 30 天兼容窗口期内新旧 token 混跑测试通过 | 中 |\n\n> **规则**：LLM 遇到上表操作时，先向用户输出摘要说明，明确询问\"是否继续？\"，得到肯定答复后再执行。用户可随时跳过检查点。\n\n---\n\n## ⚠️ 安全与合规说明（ClawHub）\n\n### 1. 部署确认机制\n本 Skill 可生成网站代码并自动部署到 EdgeOne Pages。部署前 AI Agent 会：\n- 显示待部署文件清单和变更概要\n- 请求用户确认后才执行 `edgeone pages deploy`\n- 建议首次使用时选择测试项目而非生产项目\n\n### 2. 支付安全\n- 支付回调 API 已实现微信 HMAC-SHA256 签名验证\n- 校验 appid/mchid 商户身份一致性\n- 校验回调金额与订单金额完全匹配\n- 交易状态仅接受 result_code=SUCCESS\n- 幂等锁防止重复回调处理\n- **建议：** 生产环境前使用测试商户号验证全流程\n\n### 3. 最小权限原则\n- EdgeOne CLI 使用 scope 受限的测试项目\n- 支付密钥使用测试商户号而非正式商户号\n- AI API Key 使用测试额度或免费额度\n- 数据库使用独立的测试实例\n\n### 4. 数据保留策略\n| 数据类型 | 存储位置 | 保留期限 | 说明 |\n|---------|---------|---------|------|\n| KV Session | EdgeOne KV | 7 天 TTL | 自动过期 |\n| AI 聊天历史 | EdgeOne KV | 30 天 TTL | 可配置 |\n| 审计日志 | EdgeOne KV | 90 天 TTL | 可配置 |\n| 订单数据 | D1 | 永久 | 业务必需 |\n\n### 5. Cron 定时任务\n- 订单超时取消（PENDING 30 分钟 → CANCELLED）\n- 订单自动完成（SHIPPED 7 天 → COMPLETED）\n- **建议：** 生产环境前审阅 cron 阈值配置和回滚方案\n- 提供 `cloud-functions/cron/disable-cron.js` 一键禁用\n\n### 6. 使用建议\n- ✅ 在测试项目中验证全部功能\n- ✅ 使用测试/沙箱商户号\n- ✅ 部署前审查生成的代码和环境变量\n- ✅ 配置审计日志保留策略\n- ⚠️ 启用真实支付前完成签名验证测试\n- ⚠️ 上线前禁用不需要的 cron 任务\n\nFile v3.2.1:README.md\n\n<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/version-v3.2.0-blue?style=flat-square\" alt=\"version\">\n  <img src=\"https://img.shields.io/badge/EdgeOne_Pages-ready-brightgreen?style=flat-square\" alt=\"EdgeOne Pages\">\n  <img src=\"https://img.shields.io/badge/license-MIT_No_Attribution-green?style=flat-square\" alt=\"license\">\n  <img src=\"https://img.shields.io/badge/status-stable-brightgreen?style=flat-square\" alt=\"status\">\n  <img src=\"https://img.shields.io/badge/Phase_5_工程完缮-%E2%9C%85-success?style=flat-square\" alt=\"Phase 5\">\n  <a href=\"https://website-skeleton-demo-8mv8fitk.edgeone.cool?eo_token=587119f1f48590b7dded3b65f6001030&eo_time=1779867268\"><img src=\"https://img.shields.io/badge/demo-live-purple?style=flat-square\" alt=\"Demo\"></a>\n</p>\n\n# Website Skeleton Skill\n\n> **One sentence to build a full-stack website.** Generate auth, payments, AI chat, and admin panel — deploy to EdgeOne Pages.\n\n[English](#english) · [中文](#chinese) · [Demo](https://website-skeleton-demo-8mv8fitk.edgeone.cool?eo_token=587119f1f48590b7dded3b65f6001030&eo_time=1779867268)\n\n---\n\n## English\n\n**website-skeleton-skill** is an EdgeOne Pages skill that scaffolds full-stack websites from a single prompt. It combines 5 reusable modules (Auth, Cart, Payment, AI Chat, Admin) across 3 scene templates (E-commerce, AI Assistant, SaaS Admin).\n\n### Quick Start\n\n```bash\nnpm install -g edgeone\nedgeone login --site china\nedgeone pages deploy -n my-site\n```\n\n\n### Features\n\n| Feature | Status | Description |\n|---------|--------|-------------|\n| 🔐 Auth | ✅ | JWT dual-track (RS256 + HS256), bcrypt, KV Session, RT rotation |\n| 🛒 Cart & Payment | ✅ | WeChat/Alipay, Edge idempotency lock, multi-tenant KV cart |\n| 🤖 AI Chat | ✅ | SSE streaming via Cloud Functions, KV history, rate-limited |\n| 📊 Admin Panel | ✅ | RBAC (superadmin/tenant_admin/user), CRUD, stats, audit logs |\n| 🏢 Multi-tenant | ✅ | Phase 4A: {tenant} forced SQL, KV prefix isolation, quotas |\n| 📦 npm Packages | ✅ | Phase 4B: @website-skeleton/{shared,payment,admin} |\n| 🌍 i18n | ✅ | Chinese + English, extensible |\n| 🔍 SEO | ✅ | JSON-LD structured data, meta tags |\n| 📈 Analytics | ✅ | KV-based event tracking, opt-out support |\n\n### Demo\n\n[Live Demo →](https://website-skeleton-demo-8mv8fitk.edgeone.cool?eo_token=587119f1f48590b7dded3b65f6001030&eo_time=1779867268)\n\nDeploy your own with a single command: `edgeone pages deploy -n my-site`\n\n---\n\n## Chinese\n\n# website-skeleton-skill\n\n> 一句话描述需求 → AI 生成完整前后端网站 → 自动部署到 EdgeOne Pages 全球 CDN。\n\n**版本：** v3.2.0 · **Phase 5（工程完缮）已全部完成**\n\n---\n\n## 目录\n\n- [一、快速开始](#一快速开始)\n- [二、架构](#二架构)\n- [三、功能特性](#三功能特性)\n- [四、Phase 4 变更概要](#四phase-4-变更概要)\n- [五、目录结构](#五目录结构)\n- [六、安全设计](#六安全设计)\n- [七、评审历程](#七评审历程)\n- [八、版本历史](#八版本历史)\n- [九、License](#九license)\n\n---\n\n## 一、快速开始\n\n```bash\n# 1. 安装 EdgeOne CLI\nnpm install -g edgeone\n\n# 2. 登录\nedgeone login --site china\n\n# 3. 部署（交互式引导）\nedgeone pages deploy -n my-site\n\n# 4. 按提示选择场景模板、填写配置、执行数据库迁移\n```\n\n### 场景模板\n\n| 模板 | 适用场景 | 包含模块 | 快速命令 |\n|------|---------|---------|---------|\n| **🛒 电商** | 独立电商、品牌官网 | Auth + Cart + Payment (微信/支付宝) + Orders + Admin | `帮我搭一个电商网站` |\n| **🤖 AI 助手** | AI 客服、AI 工具站 | Auth + AI Chat (SSE流式) + Admin | `帮我做一个AI客服站` |\n| **📊 SaaS 管理后台** | B2B SaaS、管理后台 | Auth + RBAC + Stats + Subscription | `帮我建一个管理后台` |\n\n---\n\n## 二、架构\n\n### 双运行时设计\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│  Platform Middleware                                              │\n│  CORS · CSP · 支付回调 IP 白名单（直接 return，不进 Edge）         │\n└──────────────────────────────────────────────────────────────────┘\n                              ↓\n┌──────────────────────────────────────────────────────────────────┐\n│  Edge Functions (V8 + KV)                                        │\n│  JWT 校验 · KV Session · 限流 · 商品列表 · 幂等锁 · 租户路由     │\n│  延迟敏感、无密钥、轻量操作                                       │\n└──────────────────────────────────────────────────────────────────┘\n                              ↓（写操作/密钥操作）\n┌──────────────────────────────────────────────────────────────────┐\n│  Cloud Functions (Node.js + D1)                               │\n│  bcrypt · 支付创建/回调 · 订单状态机 · Admin CRUD · AI SSE 流     │\n│  密钥操作、复杂事务、SELECT FOR UPDATE                            │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n### 核心设计决策\n\n| 决策 | 选择 | 理由 |\n|------|------|------|\n| 数据库 | **D1 (EdgeOne Pages 原生)** | EdgeOne Pages 原生支持，`SELECT FOR UPDATE` 事务需求 |\n| 认证 | **JWT RS256 + 30天 HS256 兼容窗口** | 安全性（RS256）与迁移平滑兼顾 |\n| 支付幂等 | **Edge `putIfNotExists` 24h TTL** | 微信重试窗口 72h，Edge 原子操作保证 |\n| 会话 | **KV Session** | 无状态、低延迟，适合 Edge 运行时 |\n| 前端 | **SPA (Vanilla JS) + History API** | 无需构建工具链，Skill 生成零依赖 |\n\n---\n\n## 三、功能特性\n\n### 已实现功能\n\n| 功能 | 状态 | 说明 |\n|------|------|------|\n| 🔐 Auth | ✅ | JWT 双轨 (RS256 + HS256)、bcrypt、KV Session、RT 轮换 |\n| 🛒 购物车与支付 | ✅ | 微信/支付宝、Edge 幂等锁、多租户 KV 购物车 |\n| 🤖 AI 对话 | ✅ | Cloud Functions SSE 流式、KV 历史、限流 |\n| 📊 管理后台 | ✅ | RBAC (superadmin/tenant_admin/user)、CRUD、统计、审计日志 |\n| 🏢 多租户隔离 | ✅ | Phase 4A: {tenant} 强制 SQL、KV 前缀隔离、配额限制 |\n| 📦 npm 包 | ✅ | Phase 4B: @website-skeleton/{shared,payment,admin} |\n| 🌍 国际化 | ✅ | 中英双语，可扩展 |\n| 🔍 SEO | ✅ | JSON-LD 结构化数据、meta 标签 |\n| 📈 数据分析 | ✅ | KV 事件追踪、opt-out 支持 |\n\n---\n\n## 四、Phase 4 变更概要\n\n### Phase 4A：多租户隔离（5 周）\n\n| 周次 | 内容 | 关键文件 |\n|------|------|----------|\n| W1 | JWT 扩展 tenant/role + Cloud 中间件 + Edge RBAC + db.js {tenant} 强制 + 事务 | `jwt-helper.js`, `tenant-context.js`, `rbac.js`, `db.js` |\n| W2 | KV 动态前缀 + 共享配额模块（软/硬限制 + fail-open） | `kv-keys.js`, `quota.js` |\n| W3 | D1 迁移（6 表 + 复合索引）+ 权限分层 + 审计日志 + 回滚脚本 | `003_tenant_isolation.sql`, `003_rollback.sql` |\n| W4 | 租户管理 API + 支付回调 KV 反查 | `admin/tenants.js`, `pay/wx-notify.js` |\n| W5 | 集成测试（14 条）+ KV 热 key 分散 | 测试用例, `kv-keys.js` |\n\n### Phase 4B：npm 包化 + 增强（4 周）\n\n| 交付项 | 文件 | 状态 |\n|--------|------|------|\n| @website-skeleton/shared | `packages/shared/package.json` + src/index.js | ✅ v3.0.0 |\n| @website-skeleton/payment | `packages/payment/package.json` + src/index.js | ✅ v1.0.0 |\n| @website-skeleton/admin | `packages/admin/package.json` + src/index.js | ✅ v1.0.0 |\n| 构建同步 | `scripts/sync-sharing.js` | ✅ --check/--sync/--ci 三种模式 |\n| Edge auth 内联 | `package.json scripts` | ✅ npm run build 自动同步 |\n\n### 5 项 P0 前置修复\n\n| P0 | 内容 | 来源 |\n|----|------|------|\n| P0-1 | 移除 `@edge-runtime/primitives` 导入，改用全局 `crypto.subtle` | 平台专家 |\n| P0-2 | 支付回调租户识别从 4B 提前到 4A W4（KV 反查） | 架构 + 安全专家 |\n| P0-3 | db.js 新增 `withTransaction()` 事务接口 | 平台专家 |\n| P0-4 | Edge Functions RBAC 中间件 `rbac.js` | 架构专家 |\n| P0-5 | 旧 token 从 users 表反查真实 tenant（渐进路径，无需轮换 SECRET） | 安全专家 + IMA 第三轮 |\n\n---\n\n## 五、目录结构\n\n```\nwebsite-skeleton-skill/\n├── SKILL.md                    # Core Skill 指令文件\n├── README.md                   # 本文件\n├── LICENSE                     # MIT No Attribution\n├── CONTRIBUTING.md             # 贡献指南\n├── CHANGELOG.md                # 版本变更日志\n├── MIGRATION.md                # 升级迁移指南\n│\n├── templates/                  # 场景预设模板\n│   ├── e-commerce.json         # 电商场景\n│   ├── ai-assistant.json       # AI 助手场景\n│   └── saas-admin.json         # SaaS 管理后台场景\n│\n├── sharing/                    # 跨运行时共享（构建时同步到 Edge + Cloud）\n│   ├── jwt-helper.js           # JWT 签发/验证（RS256 + HS256 兼容）\n│   ├── kv-keys.js              # KV key 命名（FNV-1a 64 分区）\n│   ├── constants.js            # 角色/状态/权限常量\n│   ├── validators.js           # 输入校验\n│   └── i18n/                   # 国际化（中/英）\n│\n├── edge-functions/             # Edge Functions（V8 + KV）\n│   ├── api/                    # API 端点\n│   │   ├── auth/              # 登录/注册/me\n│   │   ├── products/          # 商品列表/详情\n│   │   ├── cart/              # 购物车\n│   │   ├── orders/            # 订单读取\n│   │   └── ai/                # AI 历史\n│   ├── middleware/             # 中间件\n│   │   ├── rbac.js            # ✅ Phase 4 新增：RBAC 权限检查\n│   │   └── quota.js           # ✅ Phase 4 新增：KV 配额限制\n│   └── pages/                  # SPA 入口\n│\n├── cloud-functions/            # Cloud Functions（Node.js + D1）\n│   ├── api/\n│   │   ├── auth/              # 注册（bcrypt）\n│   │   ├── pay/               # 支付创建/回调\n│   │   ├── admin/             # 管理后台 CRUD\n│   │   ├── order/             # 订单创建/取消/状态机\n│   │   └── ai/                # AI SSE 流\n│   ├── middleware/\n│   │   └── tenant-context.js  # ✅ Phase 4 新增：Cloud 统一租户解析\n│   ├── utils/\n│   │   └── db.js              # ✅ Phase 4 增强：{tenant} 强制 + 事务 + 不导出 db\n│   └── cron/                   # 定时任务（订单超时取消）\n│\n├── db/                         # 数据库迁移\n│   ├── migrations/\n│   │   ├── 001_init.sql        # 建表脚本\n│   │   ├── 002_order_logs.sql  # 订单审计表\n│   │   ├── 003_tenant_isolation.sql  # ✅ Phase 4 新增：租户隔离\n│   │   └── 003_rollback.sql    # ✅ Phase 4 新增：回滚脚本\n│   └── seed.sql                # 测试数据\n│\n├── client/                     # 前端 SPA\n│   ├── src/\n│   │   ├── app.js             # 启动 + History API 路由\n│   │   ├── utils/\n│   │   │   ├── auth.js        # JWT 客户端\n│   │   │   ├── analytics.js   # 埋点 SDK（含 opt-out）\n│   │   │   ├── seo.js         # JSON-LD 生成\n│   │   │   └── event-bus.js   # 事件总线\n│   │   └── services/\n│   │       └── ai.js          # AI SSE 客户端\n│   │\n│   └── index.html             # SPA 入口\n│\n├── packages/                   # ✅ Phase 4 新增：npm 包源码\n│   ├── payment/               # @website-skeleton/payment\n│   ├── admin/                 # @website-skeleton/admin\n│   └── shared/                # @website-skeleton/shared\n│\n├── scripts/\n│   └── sync-sharing.js        # ✅ Phase 4 增强：构建同步（哈希校验 + CI 阻断）\n│\n├── references/                 # 按需加载的参考文档\n│   ├── auth-module.md\n│   ├── payment-module.md\n│   ├── ai-chat-module.md\n│   ├── admin-module.md\n│   ├── order-state-machine.md\n│   ├── edge-functions.md\n│   ├── cloud-functions.md\n│   ├── middleware.md\n│   ├── kv-storage.md\n│   └── deployment.md\n│\n└── .github/                    # CI\n    ├── workflows/\n    │   └── validate.yml        # SKILL.md 校验 + 链接检查\n    └── ISSUE_TEMPLATE/\n        ├── bug_report.md\n        └── feature_request.md\n```\n\n---\n\n## 六、安全设计\n\n### 安全措施一览\n\n| 类别 | 措施 | 实现 |\n|------|------|------|\n| **支付** | 幂等原子锁 | Edge `putIfNotExists` 24h TTL（小于微信 72h 重试窗口） |\n| **支付** | 回调 IP 白名单 | Platform Middleware 直接 return |\n| **支付** | KV 反查租户 | `order_tenant:{orderId}`，不走 SQL（零绕过） |\n| **并发** | RT 乐观锁 | KV version 校验，并发刷新仅第一个成功 |\n| **订单** | 防超卖 | `SELECT FOR UPDATE` + 乐观锁 + D1 CHECK 约束（三重） |\n| **金额** | 服务端唯一来源 | D1 价格字段，前端不可篡改 |\n| **密码** | bcrypt | cost=12，暴力破解成本极高 |\n| **Session** | JWT 短期+轮换 | Access Token 15min + Refresh Token 7d |\n| **Cookie** | 安全标记 | HttpOnly + Secure + SameSite=Strict |\n| **数据库** | 租户隔离 | `DELETE FROM orders WHERE tenant_id = {tenant}`（强制占位符）|\n| **数据库** | 防绕过 | db 对象不导出，所有 SQL 走 `query()` / `execute()` 函数 |\n| **API** | 租户配额 | KV 滑动窗口限流（单租户 API/存储/调用上限） |\n| **API** | 角色鉴权 | Edge RBAC 中间件 + Cloud 租户中间件 |\n| **AI** | 会话限流 | 未登录 10次/分，登录 60次/分 |\n| **AI** | 所有权校验 | KV session 绑定 userId，防止跨用户读取 |\n| **追踪** | 隐私 opt-out | localStorage + DNT 头支持 |\n\n---\n\n## 七、评审历程\n\n本 Skill 经历了 **8 轮评审**：\n\n| 轮次 | 评审方 | 评分 | 核心发现 |\n|------|--------|------|---------|\n| 1-4 | WorkBuddy + QClaw + Hermes + IMA（两轮） | — | 方案可行，8 项修正 |\n| 5 | 🛡️ 独立安全专家 | **7.0/10** 有条件通过 | S-01~S-08 |\n| 6 | 🏗️ 独立架构专家 | **7.2/10** 有条件通过 | C1~C3, D1~D5 |\n| 7 | ⚙️ 独立平台专家 | **7.5/10** 有条件通过 | B-1~B-4, R-1~R-2 |\n| 8 | IMA 第三轮 | 有条件通过 | C1 鸡生蛋修复 + I1 渐进路径 |\n\n**最终结论：** 5 项 P0 前置修复全部完成，4A 可启动。Phase 4 已全部实施完毕，进入维护阶段。\n\n---\n\n## 八、版本历史\n\n| 版本 | 日期 | 内容 |\n|------|------|------|\n| v1.0 | 2026-04-20 | Phase 1：基础骨架 + Mock Demo |\n| v2.0 | 2026-04-23 | Phase 2：JWT 双轨 · 订单状态机 · 支付 |\n| v2.2 | 2026-04-26 | Phase 3：RS256 迁移 · SEO · i18n · Analytics · 多租户铺垫 |\n| v3.0 | 2026-05-06 | Phase 4A：多租户隔离 + Phase 4B：npm 包化 + 增强 |\n| **v3.1.1** | **2026-05-26** | **Phase 4C：前端 SPA + API 端点 + 工程加固** |\n| **v3.1.2** | **2026-05-26** | **Phase 4D：SPA 页面补齐 + 脚本 + CI + 文档完缮** |\n| **v3.2.0** | **2026-05-26** | **Phase 5：商品详情页 + 227项测试 + 迁移/init-site/密码哈希** |\n\n\n## 九、License\n\nMIT No Attribution — 详见 [LICENSE](./LICENSE)。\n\n---\n\n*文档更新于 2026-05-26 · v3.2.0 — 工程完缮 + 227项测试 · 如需提交 Issue 或 Feature Request，请使用 [GitHub Issues](https://github.com/liuboacean/website-skeleton-skill/issues)*\n\nFile v3.2.1:_meta.json\n\n{\n  \"ownerId\": \"kn73qbrbqs4s8t2nh8pm22wxbd84vm7r\",\n  \"slug\": \"website-skeleton\",\n  \"version\": \"3.2.1\",\n  \"publishedAt\": 1779933453762\n}\n\nFile v3.2.1:references/admin-module.md\n\n# Admin 管理后台模块参考文档\n\n> **版本：** v2.2 · **Phase：** Layer 1（管理栈）\n> **职责：** RBAC 权限体系、商品/订单/用户 CRUD、运营统计、审计日志\n\n---\n\n## 一、RBAC 权限体系\n\n### 角色定义\n\n```javascript\n// sharing/constants.js\nexport const UserRole = {\n  USER:   'user',     // 普通用户：下单、查看自己的订单\n  ADMIN:  'admin',    // 管理员：全站 CRUD\n  MANAGER:'manager',  // 运营：订单管理、商品上下架\n};\n```\n\n### 权限矩阵\n\n| 操作 | user（本人） | manager | admin |\n|------|------------|---------|-------|\n| 查看自己的订单 | ✅ | ✅ | ✅ |\n| 管理任意订单 | ❌ | ✅ | ✅ |\n| 商品上架/下架 | ❌ | ✅ | ✅ |\n| 修改商品价格 | ❌ | ❌ | ✅ |\n| 用户管理 | ❌ | ❌ | ✅ |\n| 查看运营统计 | ❌ | ✅ | ✅ |\n| 审计日志 | ❌ | ❌ | ✅ |\n| 系统设置 | ❌ | ❌ | ✅ |\n\n---\n\n## 二、Admin Guard 中间件\n\n```javascript\n// cloud-functions/utils/admin-guard.js\n\n/**\n * 验证管理员权限\n * @param {Request} request\n * @param {Object} env\n * @param {string[]} allowedRoles - 允许的角色，如 ['admin', 'manager']\n * @returns {{ userId, role } | Response} 成功返回用户信息，失败返回 Response\n */\nexport async function adminGuard(request, env, allowedRoles = ['admin']) {\n  const authHeader = request.headers.get('Authorization') || '';\n  const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;\n\n  if (!token) {\n    return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });\n  }\n\n  const payload = await verifyJWT(token, env);\n  if (!payload) {\n    return new Response(JSON.stringify({ error: 'Invalid token' }), { status: 401 });\n  }\n\n  if (!allowedRoles.includes(payload.role)) {\n    return new Response(JSON.stringify({ error: 'Forbidden: insufficient permissions' }), { status: 403 });\n  }\n\n  return { userId: payload.sub, role: payload.role };\n}\n```\n\n---\n\n## 三、商品管理 CRUD\n\n### 3.1 列表查询（支持分页 + 筛选）\n\n```javascript\n// cloud-functions/api/admin/products.js\n\nexport async function onRequest(request, env) {\n  // Admin Guard\n  const auth = await adminGuard(request, env);\n  if (auth instanceof Response) return auth;\n\n  const url = new URL(request.url);\n  const page = Math.max(1, parseInt(url.searchParams.get('page') || '1'));\n  const pageSize = Math.min(100, parseInt(url.searchParams.get('pageSize') || '20'));\n  const offset = (page - 1) * pageSize;\n  const category = url.searchParams.get('category');\n  const status = url.searchParams.get('status'); // active / inactive\n  const keyword = url.searchParams.get('keyword');\n\n  const pool = new Pool({ connectionString: env.DATABASE_URL });\n\n  // WHERE 条件构建\n  const conditions = [];\n  const params = [];\n  if (category) { conditions.push('category_id = ?'); params.push(category); }\n  if (status) { conditions.push('status = ?'); params.push(status); }\n  if (keyword) { conditions.push('name LIKE ?'); params.push(`%${keyword}%`); }\n  const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n\n  const whereClause = where || 'WHERE 1=1';\n\n  const [rows] = await pool.query(\n    `SELECT id, name, category_id, price, stock, status, version, created_at\n     FROM products ${whereClause}\n     ORDER BY id DESC\n     LIMIT ? OFFSET ?`,\n    [...params, pageSize, offset]\n  );\n\n  const [[{ total }]] = await pool.query(\n    `SELECT COUNT(*) as total FROM products ${whereClause}`, params\n  );\n\n  await pool.end();\n\n  return new Response(JSON.stringify({\n    data: rows,\n    pagination: { page, pageSize, total, pages: Math.ceil(total / pageSize) }\n  }), { headers: { 'Content-Type': 'application/json' } });\n}\n```\n\n### 3.2 创建商品（乐观锁）\n\n```javascript\n// POST /api/admin/products（创建）\n// PUT /api/admin/products/:id（更新）\n\nexport async function onRequest(request, env) {\n  const auth = await adminGuard(request, env);\n  if (auth instanceof Response) return auth;\n\n  const body = await request.json();\n  const { id, name, price, stock, category_id, status } = body;\n\n  if (!name || !price) {\n    return new Response(JSON.stringify({ error: 'name 和 price 为必填项' }), { status: 400 });\n  }\n\n  const pool = new Pool({ connectionString: env.DATABASE_URL });\n\n  try {\n    if (id) {\n      // 更新（乐观锁）\n      const [result] = await pool.query(\n        `UPDATE products SET name=?, price=?, stock=?, category_id=?, status=?,\n         version=version+1 WHERE id=? AND version=?`,\n        [name, price, stock, category_id, status, id, body.version]\n      );\n      if (result.affectedRows === 0) {\n        return new Response(JSON.stringify({ error: '并发冲突，请重试' }), { status: 409 });\n      }\n    } else {\n      // 创建\n      const [result] = await pool.query(\n        `INSERT INTO products (name, price, stock, category_id, status) VALUES (?, ?, ?, ?, ?)`,\n        [name, price, stock || 0, category_id, status || 'active']\n      );\n      await adminLog(pool, auth.userId, 'product:create', `ID=${result.insertId}`);\n    }\n\n    return new Response(JSON.stringify({ ok: true }), { headers: { 'Content-Type': 'application/json' } });\n  } finally {\n    await pool.end();\n  }\n}\n```\n\n---\n\n## 四、运营统计\n\n```javascript\n// cloud-functions/api/admin/stats.js\n\nexport async function onRequest(request, env) {\n  const auth = await adminGuard(request, env, ['admin', 'manager']);\n  if (auth instanceof Response) return auth;\n\n  const pool = new Pool({ connectionString: env.DATABASE_URL });\n\n  const [[todayOrders]] = await pool.query(`\n    SELECT COUNT(*) as count, COALESCE(SUM(total), 0) as revenue\n    FROM orders WHERE DATE(created_at) = CURDATE() AND status != 'CANCELLED'\n  `);\n\n  const [[totalUsers]] = await pool.query(`SELECT COUNT(*) as count FROM users`);\n\n  const [[totalProducts]] = await pool.query(`SELECT COUNT(*) as count FROM products WHERE status='active'`);\n\n  const [ordersByStatus] = await pool.query(`\n    SELECT status, COUNT(*) as count, SUM(total) as revenue\n    FROM orders GROUP BY status\n  `);\n\n  const [recentOrders] = await pool.query(`\n    SELECT o.id, o.order_no, o.total, o.status, o.created_at, u.email\n    FROM orders o JOIN users u ON u.id = o.user_id\n    ORDER BY o.created_at DESC LIMIT 10\n  `);\n\n  await pool.end();\n\n  return new Response(JSON.stringify({\n    today: { orders: todayOrders.count, revenue: todayOrders.revenue },\n    total: { users: totalUsers.count, products: totalProducts.count },\n    byStatus: ordersByStatus,\n    recentOrders\n  }), { headers: { 'Content-Type': 'application/json' } });\n}\n\nasync function adminLog(pool, adminId, action, target) {\n  await pool.query(\n    'INSERT INTO admin_logs (admin_id, action, target) VALUES (?, ?, ?)',\n    [adminId, action, target]\n  );\n}\n```\n\n---\n\n## 五、审计日志表\n\n```sql\nCREATE TABLE admin_logs (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  admin_id    BIGINT UNSIGNED NOT NULL,\n  action      VARCHAR(64) NOT NULL,  -- product:create / product:update / order:cancel / ...\n  target      VARCHAR(128),\n  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,\n  FOREIGN KEY (admin_id) REFERENCES users(id)\n);\n\nCREATE INDEX idx_admin_logs_admin ON admin_logs(admin_id);\nCREATE INDEX idx_admin_logs_action ON admin_logs(action);\nCREATE INDEX idx_admin_logs_created ON admin_logs(created_at);\n```\n\n> **记录时机**：所有 Admin CRUD 操作写入 `admin_logs`，包括创建/更新/删除商品、修改订单状态、修改用户角色。\n\nFile v3.2.1:references/ai-chat-module.md\n\n# AI Chat 模块参考文档\n\n## 一、架构选择\n\n**Edge Function 限制：**\n- 200ms **CPU time** 限制（不是 wall clock time）\n- `fetch()` 到外部 AI API 的 I/O 等待**不计入** CPU time\n- **无 `waitUntil`**：异步写 KV 无法保证在响应发送前完成\n\n**结论：** 流式 SSE 主力实现在 **Cloud Functions**，历史读取在 **Edge Functions**。\n\n## 二、SSE 实现方案 B（前端中转历史）\n\n```\n前端\n  ↓ GET /api/ai/history（Edge，KV 读取）\n  ← 拿到 history JSON\n\n  ↓ 建立 SSE 连接 /api/ai/chat-stream（Cloud）\n  ← 带 history context 参数\n\nCloud SSE 流式响应\n  ↓ 完成后异步写 KV（不阻塞响应）\n```\n\n```javascript\n// Cloud Function: cloud-functions/api/ai/chat-stream.js\nexport async function onRequest(request, env) {\n  const { userId } = await auth(request, env);\n  const url = new URL(request.url);\n  const historyParam = url.searchParams.get('history');\n  const history = historyParam ? JSON.parse(historyParam) : [];\n\n  const stream = new ReadableStream({\n    async start(controller) {\n      const encoder = new TextEncoder();\n\n      async function sendEvent(type, data) {\n        controller.enqueue(encoder.encode(`event: ${type}\\ndata: ${JSON.stringify(data)}\\n\\n`));\n      }\n\n      try {\n        await sendEvent('status', { status: 'thinking' });\n\n        const response = await fetch('https://api.example.com/chat', {\n          method: 'POST',\n          headers: {\n            'Authorization': `Bearer ${env.AI_API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({ messages: history, stream: true }),\n        });\n\n        const reader = response.body.getReader();\n        const decoder = new TextDecoder();\n\n        while (true) {\n          const { done, value } = await reader.read();\n          if (done) break;\n          const chunk = decoder.decode(value);\n          await sendEvent('message', { content: chunk });\n        }\n\n        await sendEvent('done', {});\n\n      } catch (err) {\n        await sendEvent('error', { message: err.message });\n      } finally {\n        controller.close();\n      }\n    }\n  });\n\n  return new Response(stream, {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n      'Connection': 'keep-alive',\n    }\n  });\n}\n```\n\n## 三、前端 SSE 客户端\n\n```javascript\n// client/src/services/ai.js\nimport { EventBus } from '../utils/event-bus.js';\n\nclass AIService {\n  constructor() {\n    this.es = null;\n  }\n\n  async startChatSession() {\n    // Step 1: 从 Edge KV 拿历史\n    const history = await fetch('/api/ai/history').then(r => r.json());\n\n    // Step 2: 建立 SSE，带历史 context\n    this.es = new EventSource(`/api/ai/chat-stream?history=${encodeURIComponent(JSON.stringify(history))}`);\n\n    this.es.addEventListener('message', (e) => {\n      const data = JSON.parse(e.data);\n      EventBus.emit('ai:message', { role: 'assistant', content: data.content });\n    });\n\n    this.es.addEventListener('status', (e) => {\n      EventBus.emit('ai:status', JSON.parse(e.data));\n    });\n\n    this.es.addEventListener('done', () => {\n      EventBus.emit('ai:status', { status: 'idle' });\n    });\n\n    this.es.addEventListener('error', (e) => {\n      EventBus.emit('ai:error', { message: 'SSE 连接断开' });\n    });\n  }\n\n  sendMessage(content) {\n    EventBus.emit('ai:message', { role: 'user', content });\n    return fetch('/api/ai/chat-stream', {\n      method: 'POST',\n      body: JSON.stringify({ content }),\n      credentials: 'include'\n    });\n  }\n}\n```\n\n## 四、AI 限流\n\n```javascript\n// Edge Middleware 或独立限流函数\nasync function aiRateLimit(request, env, userId, ip) {\n  const key = userId ? `ai:user:${userId}` : `ai:ip:${ip}`;\n  const limit = userId ? 60 : 10;  // 已登录 60次/分钟，未登录 10次/分钟\n  const window = 60;\n\n  const count = parseInt(await env.KV.get(`rl:${key}:${Math.floor(Date.now() / 60000)}`) || '0');\n  if (count >= limit) {\n    return { allowed: false, remaining: 0 };\n  }\n  await env.KV.put(`rl:${key}:${Math.floor(Date.now() / 60000)}`, String(count + 1), { expirationTtl: 65 });\n  return { allowed: true, remaining: limit - count - 1 };\n}\n```\n\n## 五、AI Widget（嵌入代码）\n\n```javascript\n// 注册为 Custom Element，完全自包含，不依赖 SPA 状态\nclass AIChatWidget extends HTMLElement {\n  constructor() {\n    super();\n    this.attachShadow({ mode: 'open' });\n  }\n\n  connectedCallback() {\n    this.shadowRoot.innerHTML = `\n      <style>\n        :host { position: fixed; bottom: 20px; right: 20px; z-index: 9999; }\n        .widget { width: 360px; height: 520px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); }\n      </style>\n      <div class=\"widget\"><!-- 渲染逻辑 --></div>\n    `;\n  }\n}\ncustomElements.define('ai-chat-widget', AIChatWidget);\n```\n\nFile v3.2.1:references/auth-module.md\n\n# Auth 模块参考文档\n\n## 一、认证架构总览\n\n```\n未登录 → 跳转登录页（AuthGuard）\n已登录 → JWT Cookie → Edge Middleware 验证 → context.user 注入\n过期 → 自动刷新 RT → 换新 JWT\n```\n\n## 二、JWT 配置\n\n```javascript\n// edge-functions/utils/jwt-helper.js\nimport { crypto } from '@edge-runtime/primitives';\n\nconst JWT_SECRET = new TextEncoder().encode(env.JWT_SECRET);\nconst ALGORITHM = 'HS256';\n\nexport async function signAccessToken(payload) {\n  const header = btoa(JSON.stringify({ alg: ALGORITHM, typ: 'JWT' }));\n  const body = btoa(JSON.stringify({ ...payload, exp: Date.now() + 15 * 60 * 1000, iat: Date.now() }));\n  const signature = await crypto.subtle.sign('HMAC', JWT_SECRET, new TextEncoder().encode(`${header}.${body}`));\n  return `${header}.${body}.${btoa(String.fromCharCode(...new Uint8Array(signature)))}`;\n}\n\nexport async function verifyAccessToken(token) {\n  try {\n    const [header, body, sig] = token.split('.');\n    const valid = await crypto.subtle.verify('HMAC', JWT_SECRET, Uint8Array.from(atob(sig), c => c.charCodeAt(0)), new TextEncoder().encode(`${header}.${body}`));\n    if (!valid) return null;\n    const payload = JSON.parse(atob(body));\n    if (payload.exp < Date.now()) return null;\n    return payload;\n  } catch {\n    return null;\n  }\n}\n```\n\n### 【Phase 3 新增】RS256 双轨迁移\n\n> **版本：** Phase 3 P2-1\n> **密钥生成：**\n> ```bash\n> openssl genrsa -out private.pem 2048\n> openssl rsa -in private.pem -pubout -out public.pem\n> ```\n> **环境变量：**\n> - `JWT_PRIVATE_KEY` — RSA 私钥（PEM，换行符用 `\\n` 转义）\n> - `JWT_PUBLIC_KEY`  — RSA 公钥（PEM，换行符用 `\\n` 转义）\n> - `JWT_SECRET`      — HS256 密钥（30 天兼容窗口后删除）\n\n```javascript\n// sharing/jwt-helper.js — RS256 实现（完整源码）\n\n// ===================== PEM 解析 =====================\nfunction parsePem(pem) {\n  const lines = pem.replace(/\\\\n/g, '\\n').split('\\n');\n  const base64 = lines.filter(l => !l.startsWith('-----')).join('');\n  const binary = atob(base64);\n  const bytes = new Uint8Array(binary.length);\n  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n  return bytes;\n}\n\nfunction base64UrlEncode(buffer) {\n  const bytes = new Uint8Array(buffer);\n  let binary = '';\n  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n  return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction base64UrlDecode(str) {\n  let s = str.replace(/-/g, '+').replace(/_/g, '/');\n  while (s.length % 4) s += '=';\n  const binary = atob(s);\n  const bytes = new Uint8Array(binary.length);\n  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n  return bytes;\n}\n\n// ===================== RS256 签发 =====================\nexport async function signJWT(payload, expiresInMs, env) {\n  const now = Math.floor(Date.now() / 1000);\n  const header = { alg: 'RS256', typ: 'JWT' };\n  const body = { ...payload, iat: now, exp: now + Math.floor(expiresInMs / 1000) };\n\n  const headerEncoded = base64UrlEncode(new TextEncoder().encode(JSON.stringify(header)));\n  const bodyEncoded = base64UrlEncode(new TextEncoder().encode(JSON.stringify(body)));\n  const signingInput = `${headerEncoded}.${bodyEncoded}`;\n\n  const keyData = parsePem(env.JWT_PRIVATE_KEY);\n  const privateKey = await crypto.subtle.importKey(\n    'pkcs8', keyData, { name: 'RSA-PSS', hash: 'SHA-256' }, false, ['sign']\n  );\n  const signature = await crypto.subtle.sign(\n    { name: 'RSA-PSS', saltLength: 32 }, privateKey,\n    new TextEncoder().encode(signingInput)\n  );\n\n  return `${signingInput}.${base64UrlEncode(signature)}`;\n}\n\n// ===================== 双轨验证 =====================\nexport async function verifyJWT(token, env) {\n  const parts = token.split('.');\n  if (parts.length !== 3) return null;\n  const [headerEnc, bodyEnc, sigEnc] = parts;\n\n  const header = JSON.parse(new TextDecoder().decode(base64UrlDecode(headerEnc)));\n  const body = JSON.parse(new TextDecoder().decode(base64UrlDecode(bodyEnc)));\n  const now = Math.floor(Date.now() / 1000);\n\n  // RS256 优先验证\n  if (header.alg === 'RS256' && env.JWT_PUBLIC_KEY) {\n    try {\n      const keyData = parsePem(env.JWT_PUBLIC_KEY);\n      const pubKey = await crypto.subtle.importKey(\n        'spki', keyData, { name: 'RSA-PSS', hash: 'SHA-256' }, false, ['verify']\n      );\n      const valid = await crypto.subtle.verify(\n        { name: 'RSA-PSS', saltLength: 32 }, pubKey,\n        base64UrlDecode(sigEnc), new TextEncoder().encode(`${headerEnc}.${bodyEnc}`)\n      );\n      if (valid && body.exp > now) return { ...body, _alg: 'RS256' };\n    } catch {}\n  }\n\n  // HS256 兼容（30 天窗口内）\n  if (header.alg === 'HS256' && env.JWT_SECRET) {\n    try {\n      const secretKey = await crypto.subtle.importKey(\n        'raw', new TextEncoder().encode(env.JWT_SECRET),\n        { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']\n      );\n      const valid = await crypto.subtle.verify(\n        'HMAC', secretKey, base64UrlDecode(sigEnc),\n        new TextEncoder().encode(`${headerEnc}.${bodyEnc}`)\n      );\n      if (valid && body.exp > now) {\n        // 仅接受 30 天内签发的旧 token\n        const compatDeadline = Date.now() - 30 * 24 * 3600 * 1000;\n        if (body.iat * 1000 > compatDeadline) {\n          return { ...body, _alg: 'HS256' };\n        }\n      }\n    } catch {}\n  }\n\n  return null;\n}\n```\n\n### RS256 迁移时间线\n\n```\nDay 0:      部署 RS256 签发 + 双轨验证（JWT_PRIVATE_KEY/JWT_PUBLIC_KEY 注入）\nDay 1-30:   HS256 旧 token 仍可验证（向后兼容，日志记录 _alg: 'HS256'）\nDay 30:     移除 HS256 兼容分支（仅 RS256）\nDay 30:     删除 JWT_SECRET 环境变量\n```\n```\n\n## 三、KV Session 存储\n\n```javascript\n// edge-functions/utils/kv-helper.js\nconst SESSION_TTL = 86400;  // 24h\n\nexport async function getSession(kv, sessionId) {\n  const data = await kv.get(`session:${sessionId}`);\n  return data ? JSON.parse(data) : null;\n}\n\nexport async function setSession(kv, sessionId, userData) {\n  await kv.put(`session:${sessionId}`, JSON.stringify({ ...userData, createdAt: Date.now() }), {\n    expirationTtl: SESSION_TTL\n  });\n}\n\nexport async function deleteSession(kv, sessionId) {\n  await kv.delete(`session:${sessionId}`);\n}\n```\n\n## 四、Cookie 安全属性\n\n```javascript\n// Edge Middleware 中签发 Cookie\nnew Response(body, {\n  headers: {\n    'Set-Cookie': [\n      `access_token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`,\n      `refresh_token=${rt}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800`\n    ].join(', ')\n  }\n});\n```\n\n## 五、RT 轮换（version 乐观锁）\n\n```javascript\n// edge-functions/api/auth/refresh.js\n// 见 SKILL.md 主文件完整实现\n// 核心：KV.put 在 version 不匹配时返回 false → 返回 409 → 客户端重试\n```\n\n## 六、skipAuthPaths 白名单\n\n```javascript\nconst skipAuthPaths = [\n  '/api/auth/login',\n  '/api/auth/register',\n  '/api/auth/refresh',\n  '/api/products',\n  '/api/products/list',\n  '/api/products/categories',\n  '/api/products/[id]',\n  '/api/ai/chat',\n  '/api/pay/wx-notify',\n  '/api/pay/ali-notify',\n];\n```\n\n## 七、前端 AuthService（内存模式）\n\n```javascript\n// client/src/services/auth.js\nlet _currentUser = null;\n\nexport const AuthService = {\n  async getCurrentUser() {\n    if (_currentUser) return _currentUser;\n    const res = await fetch('/api/auth/me', { credentials: 'include' });\n    if (!res.ok) return null;\n    _currentUser = await res.json();\n    return _currentUser;\n  },\n  setUser(user) { _currentUser = user; },\n  clearUser() { _currentUser = null; },\n  isLoggedIn() { return !!_currentUser; },\n  onAuthChange(callback) {\n    window.addEventListener('auth:changed', (e) => callback(e.detail));\n  }\n};\n\nwindow.dispatchEvent(new CustomEvent('auth:changed', { detail: user }));\n```\n\nFile v3.2.1:references/cloud-functions.md\n\n# Cloud Functions 参考文档\n\n> **版本：** v2.2 · **Phase：** Layer 0（Core）\n> **职责：** 含密钥操作、D1 事务、支付 SDK、AI SSE、bcrypt 哈希\n\n---\n\n## 一、Cloud Functions 概述\n\nCloud Functions 运行在 **Node.js 环境**，可以：\n- 访问 D1（主数据库）\n- 使用密钥（bcrypt、支付 SDK）\n- 执行长时间任务（SSE 流）\n- 发起外部 HTTP 请求\n\n### 目录结构\n\n```\ncloud-functions/\n├── api/\n│   ├── auth/\n│   │   └── register.js          # bcrypt 注册\n│   ├── pay/\n│   │   ├── create-order.js      # 微信/支付宝预下单\n│   │   ├── wx-notify.js        # 微信回调\n│   │   └── ali-notify.js        # 支付宝回调\n│   ├── order/\n│   │   ├── create.js           # SELECT FOR UPDATE 创建订单\n│   │   └── transition.js        # 状态机变更\n│   ├── admin/\n│   │   ├── products.js         # 商品 CRUD\n│   │   ├── orders.js           # 订单查询\n│   │   └── stats.js            # 运营统计\n│   └── ai/\n│       └── chat-stream.js       # SSE 流式响应\n├── utils/\n│   ├── db.js                    # D1 连接池\n│   ├── payment-sdk.js           # 微信/支付宝 SDK 封装\n│   ├── admin-guard.js          # 权限校验\n│   └── notification-hooks.js   # 通知钩子\n└── cron/\n    └── order-cron.js            # 定时任务\n```\n\n> ⚠️ **目录名必须是 `cloud-functions/`，EdgeOne Pages 平台硬性要求。\n\n---\n\n## 二、函数签名\n\nEdgeOne Pages Cloud Functions 的导出格式：\n\n```javascript\n// Node.js\nexport async function onRequest(request, env) {\n  // request: Request 对象\n  // env: 环境变量\n  return new Response('...', { status: 200 });\n}\n```\n\n### 接收请求参数\n\n```javascript\n// GET 请求\nexport async function onRequest(request, env) {\n  const url = new URL(request.url);\n  const id = url.searchParams.get('id');\n}\n\n// POST 请求\nexport async function onRequest(request, env) {\n  const body = await request.json();\n  // 或\n  const body = await request.formData();\n}\n```\n\n---\n\n## 三、D1 数据库工具\n\n```javascript\n// cloud-functions/utils/db.js — D1 多租户数据库工具\nimport { query, queryOne, execute, withTransaction } from '../utils/db.js';\n\n// 单语句查询（自动注入 {tenant}）\nconst orders = query(env,\n  'SELECT * FROM orders WHERE tenant_id = {tenant} AND status = ?',\n  ['PENDING'], tenantId\n);\n\n// 事务操作\nwithTransaction(env, tenantId, (ctx) => {\n  const product = ctx.queryOne(\n    'SELECT * FROM products WHERE id = {tenant} AND id = ? FOR UPDATE',\n    [productId]\n  );\n  ctx.execute(\n    'UPDATE products SET stock = stock - 1 WHERE id = {tenant} AND id = ?',\n    [productId]\n  );\n});\n```\n\n> ⚠️ D1 数据库通过 `env.DB` 绑定自动注入，无需手动管理连接池。\n\n### 事务示例\n\n```javascript\nexport async function withTransaction(pool, fn) {\n  const conn = await pool.getConnection();\n  try {\n    await conn.beginTransaction();\n    const result = await fn(conn);\n    await conn.commit();\n    return result;\n  } catch (err) {\n    await conn.rollback();\n    throw err;\n  } finally {\n    conn.release();\n  }\n}\n```\n\n---\n\n## 四、bcrypt 密码哈希\n\n```javascript\n// cloud-functions/api/auth/register.js\nimport bcrypt from 'bcryptjs';\n\nconst BCRYPT_ROUNDS = 12;\n\nexport async function onRequest(request, env) {\n  const { email, password, name } = await request.json();\n\n  if (!email || !password || password.length < 8) {\n    return new Response(JSON.stringify({ error: 'Invalid input' }), { status: 400 });\n  }\n\n  const pool = await getPool(env.DATABASE_URL);\n  const [existing] = await pool.query('SELECT id FROM users WHERE email = ?', [email]);\n  if (existing.length > 0) {\n    return new Response(JSON.stringify({ error: '邮箱已被注册' }), { status: 409 });\n  }\n\n  const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);\n\n  const [result] = await pool.query(\n    'INSERT INTO users (email, password_hash, name) VALUES (?, ?, ?)',\n    [email, passwordHash, name || '']\n  );\n\n  return new Response(JSON.stringify({\n    userId: result.insertId,\n    email\n  }), { status: 201, headers: { 'Content-Type': 'application/json' } });\n}\n```\n\n---\n\n## 五、微信支付 SDK 封装\n\n```javascript\n// cloud-functions/utils/payment-sdk.js\n\n/**\n * 微信支付 V3 — 统一下单\n */\nexport async function wxPayCreateOrder(env, { outTradeNo, amount, description, notifyUrl }) {\n  const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native';\n\n  const payload = {\n    appid: env.WX_APPID,\n    mchid: env.WX_MCHID,\n    description,\n    out_trade_no: outTradeNo,\n    notify_url: notifyUrl,\n    amount: { total: amount, currency: 'CNY' }\n  };\n\n  const token = await getWxAuthToken(env);\n  const response = await fetch(url, {\n    method: 'POST',\n    headers: {\n      'Authorization': `WECHATPAY2-SHA256-RSA2048 ${token}`,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify(payload)\n  });\n\n  if (!response.ok) {\n    const err = await response.text();\n    throw new Error(`Wechat pay error: ${err}`);\n  }\n\n  const data = await response.json();\n  return { codeUrl: data.code_url, tradeNo: data.id };\n}\n```\n\n---\n\n## 六、AI SSE 流式响应\n\n```javascript\n// cloud-functions/api/ai/chat-stream.js\n\nexport async function onRequest(request, env) {\n  const url = new URL(request.url);\n  const historyParam = url.searchParams.get('history');\n  const history = historyParam ? JSON.parse(decodeURIComponent(historyParam)) : [];\n\n  const stream = new ReadableStream({\n    async start(controller) {\n      const encoder = new TextEncoder();\n\n      const sendEvent = (event, data) => {\n        controller.enqueue(encoder.encode(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`));\n      };\n\n      try {\n        const messages = [\n          ...history.map(h => ({ role: h.role, content: h.content })),\n        ];\n\n        const aiResponse = await fetch('https://api.example.com/chat', {\n          method: 'POST',\n          headers: {\n            'Authorization': `Bearer ${env.AI_API_KEY}`,\n            'Content-Type': 'application/json'\n          },\n          body: JSON.stringify({ messages, stream: true })\n        });\n\n        const reader = aiResponse.body.getReader();\n        while (true) {\n          const { done, value } = await reader.read();\n          if (done) break;\n          const text = new TextDecoder().decode(value);\n          sendEvent('message', { content: text });\n        }\n\n        sendEvent('done', {});\n      } catch (err) {\n        sendEvent('error', { message: err.message });\n      } finally {\n        controller.close();\n      }\n    }\n  });\n\n  return new Response(stream, {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n      'Connection': 'keep-alive'\n    }\n  });\n}\n```\n\n---\n\n## 七、环境变量（Cloud Functions）\n\n| 变量 | 必填 | 说明 |\n|------|------|------|\n| `DATABASE_URL` | ✅ | D1 连接串 |\n| `WX_APPID` | ✅（电商） | 微信 AppID |\n| `WX_MCHID` | ✅（电商） | 微信商户号 |\n| `WX_API_KEY` | ✅（电商） | 微信 APIv3 密钥 |\n| `WX_CERT_PATH` | ✅（电商） | 微信证书路径 |\n| `ALI_APP_ID` | ✅（电商） | 支付宝 AppID |\n| `ALI_PRIVATE_KEY` | ✅（电商） | 支付宝私钥 |\n| `AI_API_KEY` | ✅（AI 栈） | AI 模型 API Key |\n| `EDGE_BASE` | ✅（电商） | Edge Function 内部网关 |\n| `ADMIN_EMAIL` | 可选 | 管理员通知邮箱 |\n\nFile v3.2.1:references/deployment.md\n\n# 部署参考文档\n\n> **版本：** v2.2 · **Phase：** Layer 0（Core）\n> **部署平台：** EdgeOne Pages\n\n---\n\n## 一、快速部署\n\n### 1.1 环境准备\n\n```bash\n# 1. 安装 EdgeOne CLI\nnpm install -g edgeone@latest\n\n# 2. 登录（选择中国区）\nedgeone login --site china\n\n# 3. 查看账号\nedgeone whoami\n# → 刘博 · 100043397965\n```\n\n### 1.2 初始化项目\n\n```bash\n# 交互式初始化（回答引导问题）\nnpx create-edgeone-app\n\n# 或手动创建后部署\ncd my-site\nedgeone pages deploy\n```\n\n### 1.3 环境变量配置（EdgeOne Console）\n\n在 EdgeOne Console → 项目设置 → 环境变量中注入：\n\n```bash\n# === 认证（Phase 3 RS256）===\nJWT_PRIVATE_KEY=<私钥内容，换行替换为\\n>\nJWT_PUBLIC_KEY=<公钥内容，换行替换为\\n>\nJWT_SECRET=<HS256密钥，30天兼容用>\n\n# === 数据库 ===\n# D1 数据库由 EdgeOne Pages 自动注入 env.DB，无需手动配置连接字符串\n# 执行迁移：edgeone d1 execute <db-name> --file=db/migrations/001_init.sql\n\n# === 微信支付 ===\nWX_APPID=wx...\nWX_MCHID=1234567890\nWX_API_KEY=...\nWX_CERT_PATH=./cert/apiclient_cert.pem\n\n# === 支付宝 ===\nALI_APP_ID=202100...\nALI_PRIVATE_KEY=...\n\n# === AI（AI 栈）===\nAI_API_KEY=sk-...\n\n# === 内部网关 ===\nEDGE_BASE=https://xxx.edgeone.dev\n\n# === 站点 ===\nSITE_URL=https://your-site.edgeone.cool\n```\n\n### 1.4 数据库迁移\n\n```bash\n# 1. 执行初始化迁移\nedgeone d1 execute my-database --file=db/migrations/001_init.sql\n\n# 2. 执行订单日志迁移\nedgeone d1 execute my-database --file=db/migrations/002_order_logs.sql\n```\n\n### 1.5 部署\n\n```bash\n# 部署（自动构建 + 上传 + 部署）\nedgeone pages deploy --project my-site --output ./dist\n\n# 部署后查看 URL\nedgeone pages list\n```\n\n---\n\n## 二、部署配置（edgeone.json）\n\n```json\n{\n  \"name\": \"my-geek-mall\",\n  \"output\": \"./dist\",\n  \"env\": \"production\",\n  \"routes\": [\n    {\n      \"pattern\": \"/api/*\",\n      \"target\": \"edge-functions\"\n    },\n    {\n      \"pattern\": \"/api/pay/*\",\n      \"target\": \"cloud-functions\"\n    }\n  ],\n  \"vars\": {\n    \"SITE_URL\": \"https://my-geek-mall.edgeone.cool\"\n  }\n}\n```\n\n---\n\n## 三、Edge Functions vs Cloud Functions 路由\n\nEdgeOne Pages 自动识别：\n- `edge-functions/` → 编译为 Edge Functions（部署到全球边缘节点）\n- `cloud-functions/` → 编译为 Cloud Functions（Node.js）\n- `client/` → 编译为静态资源（SPA）\n\n### 手动指定路由\n\n在 `edgeone.json` 中配置路由规则：\n\n```json\n{\n  \"routing\": {\n    \"/api/auth/*\": \"cloud-functions\",\n    \"/api/pay/*\": \"cloud-functions\",\n    \"/api/admin/*\": \"cloud-functions\",\n    \"/api/order/*\": \"cloud-functions\",\n    \"/api/ai/*\": \"cloud-functions\",\n    \"/api/*\": \"edge-functions\"\n  }\n}\n```\n\n---\n\n## 四、KV Storage 配置\n\n在 EdgeOne Console 中创建 KV 命名空间：\n\n```\nKV Namespaces:\n  - main: 用于 Session、RT、Cart、AI History\n```\n\n绑定到 Edge Functions 环境变量：\n\n```bash\nKV_NAMESPACE=main\n```\n\n> ⚠️ **重要**：KV 仅 Edge Functions 可直接访问。Cloud Functions 需通过 HTTP 调用 Edge Function `edge-functions/api/internal/*` 访问 KV。\n\n---\n\n## 五、Cron 定时任务配置\n\n在 `edgeone.json` 中配置定时触发器：\n\n```json\n{\n  \"triggers\": [\n    {\n      \"name\": \"order-cron\",\n      \"type\": \"schedule\",\n      \"cron\": \"*/5 * * * *\",\n      \"function\": \"cloud-functions/cron/order-cron.js\",\n      \"enabled\": true\n    }\n  ]\n}\n```\n\n或通过 EdgeOne Console UI 配置。\n\n---\n\n## 六、域名绑定（可选）\n\n```bash\n# 添加自定义域名\nedgeone domains add --project my-site --domain shop.example.com\n\n# 验证 DNS\nedgeone domains verify --project my-site --domain shop.example.com\n\n# 申请 SSL 证书（自动）\nedgeone ssl issue --project my-site --domain shop.example.com\n```\n\n---\n\n## 七、本地开发\n\n```bash\n# 1. 启动本地 Edge Functions 模拟器\nedgeone dev\n\n# 2. 启动本地 Next.js 开发服务器（单独进程）\nnpm run dev\n\n# 3. 两个服务并行运行：\n# - EdgeOne dev: http://localhost:8787\n# - Next.js: http://localhost:3000\n```\n\n### 本地 .env.local\n\n```bash\n# .env.local（本地开发）\nJWT_PRIVATE_KEY=\"$(cat keys/private.pem | tr '\\n' ' ')\"\nJWT_PUBLIC_KEY=\"$(cat keys/public.pem | tr '\\n' ' ')\"\nJWT_SECRET=local-dev-secret\n# D1 数据库绑定由 EdgeOne Pages 平台自动注入\nEDGE_BASE=http://localhost:8787\n```\n\n---\n\n## 八、回滚与监控\n\n### 回滚\n\n```bash\n# 查看部署历史\nedgeone pages deployments list --project my-site\n\n# 回滚到指定版本\nedgeone pages rollback --project my-site --deployment d-xxxxx\n```\n\n### 监控\n\n```bash\n# 查看实时日志\nedgeone logs --project my-site --function edge-functions/api/products/list.js --tail\n\n# 查看调用统计\nedgeone pages stats --project my-site --period 24h\n```\n\n---\n\n## 九、常见问题\n\n### Q: 部署后 KV 数据丢失？\nA: KV 数据在项目关联的 KV Namespace 中，删除项目会清空。迁移时需导出 KV 数据。\n\n### Q: Cloud Functions 访问不到 KV？\nA: 这是 EdgeOne Pages 平台约束。解决方案：通过 `fetch(EDGE_BASE + '/api/internal/idempotency')` 从 Cloud 调用 Edge Function 访问 KV。\n\n### Q: 微信回调返回 \"签名验证失败\"？\nA: 微信 V3 回调需使用微信平台证书公钥验签，证书每 1 年需更新。\n\n### Q: JWT RS256 签发失败？\nA: 检查 `JWT_PRIVATE_KEY` 环境变量格式，确保换行符正确转义为 `\\n`。\n\nFile v3.2.1:references/edge-functions.md\n\n# Edge Functions 参考文档\n\n> **版本：** v2.2 · **Phase：** Layer 0（Core）\n> **职责：** Edge Middleware + 读操作 API + KV 访问\n\n---\n\n## 一、Edge Functions 概述\n\nEdge Functions 运行在 **V8 引擎**，无密钥（密钥在 Cloud Functions），响应极快，适合读操作。\n\n### 与 Cloud Functions 的职责划分\n\n| 场景 | Edge Functions（V8 + KV） | Cloud Functions（Node.js） |\n|------|--------------------------|---------------------------|\n| JWT 校验 | ✅ crypto.subtle | — |\n| KV 读写 | ✅ 原生访问 | ❌（通过 HTTP 调用 Edge） |\n| Session 读取 | ✅ | — |\n| 限流 | ✅ KV 滑动窗口 | — |\n| 幂等锁 | ✅ putIfNotExists | — |\n| 产品列表（无筛选） | ✅ KV 缓存 | — |\n| 商品详情 | ✅ KV 缓存 | — |\n| 订单创建 | — | ✅ SELECT FOR UPDATE |\n| 用户注册（bcrypt） | — | ✅ |\n| 支付回调 | — | ✅ 微信/支付宝 SDK |\n| AI SSE 流 | — | ✅ waitUntil |\n\n---\n\n## 二、Edge Middleware（_middleware.js）\n\nEdge Middleware 在**每个 Edge Function 请求**前执行：\n\n```javascript\n// edge-functions/_middleware.js\n\nimport { verifyAccessToken, extractToken } from './utils/jwt-helper.js';\nimport { checkRateLimit } from './utils/rate-limit.js';\n\nexport async function onRequest(context) {\n  const { request, env } = context;\n\n  // === 1. 公开路径放行 ===\n  const publicPaths = ['/', '/login', '/register', '/api/products'];\n  const url = new URL(request.url);\n  if (publicPaths.some(p => url.pathname === p || url.pathname.startsWith(p + '/'))) {\n    if (url.pathname.startsWith('/api/') && !url.pathname.startsWith('/api/auth/')) {\n      // 公开 API（如产品列表）无需认证\n    }\n    return context.next(); // 继续到具体 Function\n  }\n\n  // === 2. 提取 Token ===\n  const tokenData = extractToken(request);\n  if (!tokenData) {\n    return new Response('Unauthorized', { status: 401 });\n  }\n\n  // === 3. JWT 验证 ===\n  const payload = await verifyAccessToken(tokenData.token, env);\n  if (!payload) {\n    return new Response('Invalid token', { status: 401 });\n  }\n\n  // === 4. 限流 ===\n  const clientId = payload.sub || request.headers.get('CF-Connecting-IP');\n  const { allowed } = await checkRateLimit(context, `global:${clientId}`, 100);\n  if (!allowed) {\n    return new Response('Rate limited', { status: 429, headers: { 'Retry-After': '60' } });\n  }\n\n  // === 5. 注入用户信息到 context ===\n  // 方式 A：通过 x-user-id / x-user-role 响应头传递给具体 Function\n  // 方式 B：Edge Function 直接调用 verifyAccessToken（推荐，减少中间件复杂度）\n  return context.next();\n}\n```\n\n---\n\n## 三、常用 Edge API\n\n### 3.1 获取当前用户\n\n```javascript\n// edge-functions/api/auth/me.js\nexport async function onRequest(context) {\n  const { request, env } = context;\n  const payload = await verifyAccessToken(extractToken(request)?.token, env);\n\n  if (!payload) {\n    return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });\n  }\n\n  // 从 KV 读取 Session 补充信息\n  const session = await env.KV.get(`session:${payload.sub}`);\n  const user = session ? JSON.parse(session) : { id: payload.sub, email: payload.email };\n\n  return new Response(JSON.stringify(user), {\n    headers: { 'Content-Type': 'application/json' }\n  });\n}\n```\n\n### 3.2 产品列表（KV 缓存 + 回源）\n\n```javascript\n// edge-functions/api/products/list.js\n\nconst CACHE_TTL = 300; // 5 min\n\nexport async function onRequest(context) {\n  const { env } = context;\n\n  // KV 读取（缓存命中）\n  const cached = await env.KV.get('products:list:default');\n  if (cached) {\n    return new Response(cached, {\n      headers: { 'Content-Type': 'application/json', 'X-Cache': 'HIT' }\n    });\n  }\n\n  // 缓存未命中 → 回源 Cloud D1（通过 db.js）\n  const products = await fetch(`${env.EDGE_BASE}/internal/products`, {\n    headers: { 'X-Internal-Key': env.INTERNAL_KEY }\n  }).then(r => r.json());\n\n  // 写入 KV\n  await env.KV.put('products:list:default', JSON.stringify(products), {\n    expirationTtl: CACHE_TTL\n  });\n\n  return new Response(JSON.stringify(products), {\n    headers: { 'Content-Type': 'application/json', 'X-Cache': 'MISS' }\n  });\n}\n```\n\n### 3.3 KV 购物车\n\n```javascript\n// edge-functions/api/cart/*.js\n\n// GET /api/cart — 读取购物车\nexport async function onRequest(context) {\n  const { request, env } = context;\n  const payload = await verifyAccessToken(extractToken(request)?.token, env);\n  if (!payload) return new Response('Unauthorized', { status: 401 });\n\n  const cart = await env.KV.get(`cart:${payload.sub}`);\n  return new Response(cart || '[]', { headers: { 'Content-Type': 'application/json' } });\n}\n\n// POST /api/cart — 添加商品\n// Body: { productId, qty }\nexport async function onRequest(context) {\n  const { request, env } = context;\n  const payload = await verifyAccessToken(extractToken(request)?.token, env);\n  if (!payload) return new Response('Unauthorized', { status: 401 });\n\n  const { productId, qty } = await request.json();\n  const cart = JSON.parse(await env.KV.get(`cart:${payload.sub}`) || '[]');\n\n  const existing = cart.find(i => i.productId === productId);\n  if (existing) {\n    existing.qty += qty;\n  } else {\n    cart.push({ productId, qty });\n  }\n\n  await env.KV.put(`cart:${payload.sub}`, JSON.stringify(cart), {\n    expirationTtl: 30 * 86400 // 30 天\n  });\n\n  return new Response(JSON.stringify({ ok: true, cart }), {\n    headers: { 'Content-Type': 'application/json' }\n  });\n}\n```\n\n---\n\n## 四、KV Helper 封装\n\n```javascript\n// edge-functions/utils/kv-helper.js\n\n/**\n * 安全的 KV 读取，返回 null 而不是抛错\n */\nexport async function kvGet(kv, key) {\n  try {\n    const value = await kv.get(key);\n    return value ? JSON.parse(value) : null;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * 原子递增（用于计数器/限流）\n */\nexport async function kvIncr(kv, key) {\n  const current = parseInt(await kv.get(key) || '0');\n  await kv.put(key, String(current + 1));\n  return current + 1;\n}\n\n/**\n * 乐观锁写入（version 字段）\n */\nexport async function kvUpdateWithVersion(kv, key, updateFn) {\n  const data = await kvGet(kv, key);\n  if (!data) return null;\n\n  const newData = updateFn({ ...data });\n  newData.version = (data.version || 0) + 1;\n\n  // 比较并写入（Cloudflare KV 乐观锁近似实现）\n  await kv.put(key, JSON.stringify(newData), { expirationTtl: 86400 });\n  return newData;\n}\n```\n\n---\n\n## 五、环境变量（Edge Functions）\n\n| 变量 | 必填 | 说明 |\n|------|------|------|\n| `JWT_PRIVATE_KEY` | ✅（Phase 3） | RS256 私钥 |\n| `JWT_PUBLIC_KEY` | ✅（Phase 3） | RS256 公钥 |\n| `JWT_SECRET` | ✅（兼容） | HS256 密钥（30 天兼容） |\n| `EDGE_BASE` | ✅（电商） | Cloud Function 内部网关 |\n| `INTERNAL_KEY` | ✅（电商） | 内部调用鉴权 |\n| `SITE_URL` | 可选 | 站点 URL（SEO 用） |\n\nFile v3.2.1:references/integration-tests.md\n\n# Phase 4A 集成测试大纲 — 14 条\n\n> 版本：v6 Final\n> 更新日期：2026-05-06\n> 前置条件：6 项 P0 修复全部完成 + W1-W4 实施完成\n\n## 前置条件\n\n运行测试前需确保：\n1. `sharing/jwt-helper.js` 已移除 `@edge-runtime/primitives` 导入\n2. `cloud-functions/utils/db.js` 已就位（含 {tenant} 强制 + withTransaction）\n3. `edge-functions/middleware/rbac.js` 已就位\n4. 数据库已执行 `003_tenant_isolation.sql` 迁移\n5. `sharing/constants.js` 权限矩阵已配置\n6. KV 中至少有两个测试租户（`tenant_a`, `tenant_b`）\n\n## 功能测试（F1-F6）\n\n### F1：租户隔离 — 跨租户数据不可见\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 租户 A 的 token 无法读取租户 B 的订单 |\n| 前置 | tenant_a 和 tenant_b 各有一个订单 |\n| 步骤 | 1. 用 tenant_a 的 token 请求 `/api/order?id={tenant_b_order_id}`；2. 检查响应 |\n| 预期 | 403 或空结果（不暴露 tenant_b 的数据）|\n| 命令 | `curl -H \"Authorization: Bearer {token_a}\" {url}/api/order?id={order_b_id}` |\n\n### F2：KV 租户隔离\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | tenant_b 无法访问 tenant_a 的 KV session |\n| 步骤 | 1. tenant_a 创建一个 KV session；2. tenant_b 尝试读取 |\n| 预期 | tenant_b 读不到 tenant_a 的 session |\n| 说明 | KV key 前缀 `${tenant}:session:{id}` 天然隔离 |\n\n### F3：旧 token 兼容\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 旧 token（无 tenant 字段）自动归入正确租户 |\n| 步骤 | 1. 用 Phase 3 签发的 token（HS256，无 tenant）请求；2. 检查响应 |\n| 预期 | resolveLegacyTenant() 从 users 表反查到该用户的真实 tenant_id |\n| 说明 | 旧 token 的 `_isLegacy` 标记为 true |\n\n### F4：角色权限\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | superadmin 可跨租户，tenant_admin 只看本租户 |\n| 步骤 | 1. superadmin 调用 `GET /api/admin/tenants`；2. tenant_admin 调用 |\n| 预期 | superadmin → 200 + 租户列表；tenant_admin → 403 |\n\n### F5：支付回调租户识别\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 支付回调从 KV 反查到正确 tenant |\n| 步骤 | 1. 创建订单时写入 `order_tenant:{orderId}` → `{tenant}` |\n| 预期 | 回调时从 KV 反查 tenant 正确，订单状态更新到对应租户 |\n\n### F6：SQL 强制 {tenant}\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 不走 query() 的 SQL 操作直接报错 |\n| 步骤 | 直接调用 `env.DB.prepare('SELECT ...')` 而不经过 db.js |\n| 预期 | db 对象不导出，在模块作用域内不可访问，编译或运行时报错 |\n\n## 安全测试（S-01 ~ S-08）\n\n### S-01：JWT tenant 篡改\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 篡改 JWT payload 中的 tenant → 验签失败 → 401 |\n| 步骤 | 1. 获取一个有效 token；2. 手动修改 payload 中的 tenant 字段；3. 重新 base64 编码；4. 发送请求 |\n| 预期 | token 签名不匹配，返回 401 |\n\n### S-02：X-Tenant-ID Header 注入\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 添加 X-Tenant-ID Header → 被忽略，以 JWT tenant 为准 |\n| 步骤 | 1. 用 tenant_a 的 token；2. 添加 `X-Tenant-ID: tenant_b` header；3. 请求 |\n| 预期 | tenant-context.js 不读取此 header，仍以 JWT 中的 tenant 为准 |\n\n### S-03：超配额限制\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 单租户超限 → 返回 429/403，不影响其他租户 |\n| 步骤 | 1. 模拟一个租户的配额耗尽；2. 该租户请求被拒绝；3. 另一租户请求正常 |\n| 预期 | 超限租户返回 `QUOTA_EXCEEDED`，其他租户正常 |\n\n### S-04：superadmin 提权尝试\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | tenant_admin 尝试访问 superadmin API → 403 |\n| 步骤 | 1. 用 tenant_admin 的 token；2. 调用 `GET /api/admin/tenants` |\n| 预期 | rbac.js 校验角色 → 403 Forbidden |\n\n### S-05：支付回调伪造\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 伪造支付回调（无 KV 记录）→ 404 |\n| 步骤 | 1. 构造一个假的微信回调请求（out_trade_no 在 KV 中无记录） |\n| 预期 | wx-notify.js 从 KV 反查不到 tenant → 404 |\n\n### S-06：SQL 注入 tenant_id\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 构造恶意 tenant 值 → 被 {tenant} 占位符参数化绑定防御 |\n| 步骤 | 1. 用 tenant 值 `' OR 1=1 --` 等注入字符串构造 token |\n| 预期 | {tenant} → ? → bind(tenant, ...) 参数化，注入无效 |\n\n### S-07：RBAC 绕过\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 无 token 访问受保护路由 → 401 |\n| 步骤 | 1. 不携带 Authorization header；2. 直接请求 `/api/admin/tenants` |\n| 预期 | rbac.js 检测到无 token → 401 |\n\n### S-08：并发配额攻击\n\n| 项目 | 内容 |\n|------|------|\n| 验证 | 大量并发请求 → 配额定界不被突破 |\n| 步骤 | 1. 并发发送 200 个请求（超过 kv_ops:10000 限制）；2. 检查配额计数 |\n| 预期 | 并发请求不应突破硬限制的合理倍数（允许少量偏差）|\n| 备注 | 此测试依赖 KV atomic counter 的实现，可能在 EdgeOne 环境中略有偏差 |\n\n---\n\n## 测试执行顺序\n\n```\n       F3 (旧token兼容)\n       ↓\nF1 ─→ F2 ─→ F5 ─→ F6    功能测试（先确保基础隔离正确）\n                       ↓\nS1 ─→ S2 ─→ S4 ─→ S7    安全测试（先验证认证机制）\n↓\nS3 ─→ S8                配额测试（最后测边界条件）\n↓\nS5 ─→ S6                边界测试（防御机制）\n```\n\n## 测试结果记录\n\n| 用例 | 结果 | 日期 | 备注 |\n|------|------|------|------|\n| F1 | ⬜ | — | |\n| F2 | ⬜ | — | |\n| F3 | ⬜ | — | |\n| F4 | ⬜ | — | |\n| F5 | ⬜ | — | |\n| F6 | ⬜ | — | |\n| S-01 | ⬜ | — | |\n| S-02 | ⬜ | — | |\n| S-03 | ⬜ | — | |\n| S-04 | ⬜ | — | |\n| S-05 | ⬜ | — | |\n| S-06 | ⬜ | — | |\n| S-07 | ⬜ | — | |\n| S-08 | ⬜ | — | |\n\nFile v3.2.1:references/kv-storage.md\n\n# KV Storage 参考文档\n\n## 一、KV 命名空间设计\n\nEdgeOne Pages 提供两个 KV Namespace：\n- `auth_ns`：认证数据（user、session、refresh_token）\n- `kv_ns`：业务数据（cart、order、product、ai_session）\n\nNode Functions 必须单独绑定 auth_ns。\n\n## 二、Key 命名规范（含租户前缀占位）\n\n```typescript\n// sharing/kv-keys.ts\n// Phase 1 填固定值 \"default\"，Phase 3 替换为动态 tenantId\n\nexport const kvKey = {\n  user: (tenantId, userId) => `${tenantId}:user:${userId}`,\n  session: (tenantId, sessionId) => `${tenantId}:session:${sessionId}`,\n  rtMeta: (tenantId, userId) => `${tenantId}:rt:${userId}:meta`,\n  product: (tenantId, productId) => `${tenantId}:product:${productId}`,\n  productList: (tenantId) => `${tenantId}:products:list`,\n  cart: (tenantId, userId) => `${tenantId}:cart:${userId}`,\n  order: (tenantId, orderId) => `${tenantId}:order:${orderId}`,\n  orderByUser: (tenantId, userId) => `${tenantId}:orders:user:${userId}`,\n  aiSession: (tenantId, userId, sessionId) => `${tenantId}:ai:${userId}:${sessionId}`,\n  idempotency: (tradeNo) => `pay:idempotency:${tradeNo}`,\n  rateLimit: (key, window) => `rl:${key}:${Math.floor(Date.now() / (window * 1000))}`,\n};\n```\n\n## 三、分层查询策略\n\n| 场景 | 实现 | 原因 |\n|------|------|------|\n| 单商品读取 | KV 缓存 | kv.get 极快 |\n| 商品列表（首页，无筛选） | KV 第1页缓存 | 首页访问量最大 |\n| 分类+价格筛选 | Cloud D1 | KV 不支持复合条件 |\n| 关键词搜索 | Cloud D1 | KV 不支持 LIKE |\n| AI 会话历史 | KV list | kv.list('ai:${userId}:') |\n| 订单统计（多条件聚合） | Cloud D1 | KV 不支持聚合 |\n\n## 四、索引 KV 模式\n\n```javascript\n// 写入订单时同步写入索引\nawait kv.put(`order:${orderId}`, JSON.stringify(order));\nawait kv.put(`idx:order:date:${dateStr}:${orderId}`, '1');\nawait kv.put(`idx:order:status:${status}:${orderId}`, '1');\nawait kv.put(`idx:order:user:${userId}:${orderId}`, '1');\n\n// 查询\"今日所有 PAID 订单\"\nconst keys = await kv.list({ prefix: `idx:order:date:${today}:` });\nconst orderIds = keys.map(k => k.name.split(':').pop());\nconst orders = await Promise.all(orderIds.map(id => kv.get(`order:${id}`)));\n```\n\n## 五、数据过期策略\n\n```javascript\nkv.put(`session:${sessionId}`, data, { expirationTtl: 86400 });      // 24h\nkv.put(`order:${orderId}`, data, { expirationTtl: 7776000 });          // 90d\nkv.put(`ai_session:${userId}:${sessionId}`, data, { expirationTtl: 2592000 }); // 30d\nkv.putIfNotExists(`pay:idempotency:${tradeNo}`, id, { expirationTtl: 86400 }); // 24h\n```\n\nFile v3.2.1:references/middleware.md\n\n# Middleware 参考文档\n\n> **版本：** v2.2 · **Phase：** Layer 0（Core）\n> **职责：** Platform Middleware（CORS/CSP/轻量认证）+ Edge Middleware（JWT 详细校验）\n\n---\n\n## 一、双层 Middleware 架构\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│           Platform Middleware（middleware.js）                    │\n│  ① CORS 预检（OPTIONS）                                        │\n│  ② CSP Header 注入                                             │\n│  ③ 轻量 Bearer 检查（公开路径放行）                               │\n│  ④ 支付回调 IP 白名单 → 直接 return，不进 Edge Middleware         │\n└──────────────────────────────────────────────────────────────┘\n                              ↓\n┌──────────────────────────────────────────────────────────────┐\n│            Edge Functions Middleware（_middleware.js）              │\n│  ⑤ JWT 详细校验（crypto.subtle）                                 │\n│  ⑥ KV session 验证                                             │\n│  ⑦ KV 限流计数器（滑动窗口）                                      │\n└──────────────────────────────────────────────────────────────┘\n```\n\n### 为什么需要双层？\n\n| 层级 | 执行时机 | 用途 |\n|------|---------|------|\n| Platform Middleware | **每个请求**（HTML/静态/API） | 全局安全头、CORS、支付回调 bypass |\n| Edge Middleware | 仅 Edge Function 请求 | JWT 详细校验、KV 限流 |\n\n---\n\n## 二、Platform Middleware\n\n```javascript\n// middleware.js（项目根目录）\n\nexport function onRequest(context) {\n  const { request, env, next } = context;\n  const url = new URL(request.url);\n  const pathname = url.pathname;\n\n  // === ① CORS 预检 ===\n  if (request.method === 'OPTIONS') {\n    return new Response(null, {\n      status: 204,\n      headers: {\n        'Access-Control-Allow-Origin': env.ALLOWED_ORIGIN || '*',\n        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n        'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n        'Access-Control-Max-Age': '86400'\n      }\n    });\n  }\n\n  // === ② CSP Header 注入（仅 HTML）===\n  const contentType = request.headers.get('Content-Type') || '';\n  if (contentType.includes('text/html')) {\n    const response = next(); // 获取原始响应\n    const CSP = [\n      \"default-src 'self'\",\n      \"script-src 'self' 'unsafe-inline'\", // Skill 生成代码含内联脚本\n      \"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com\",\n      \"font-src 'self' https://fonts.gstatic.com\",\n      \"img-src 'self' data: https:\",\n      \"connect-src 'self' https://api.edgeone.dev https://api.weixin.qq.com https://openapi.alipay.com\",\n      \"frame-ancestors 'none'\",\n      \"base-uri 'self'\",\n      \"form-action 'self'\"\n    ].join('; ');\n\n    const newHeaders = new Headers(response.headers);\n    newHeaders.set('Content-Security-Policy', CSP);\n    newHeaders.set('X-Content-Type-Options', 'nosniff');\n    newHeaders.set('X-Frame-Options', 'DENY');\n    newHeaders.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n\n    return new Response(response.body, {\n      status: response.status,\n      statusText: response.statusText,\n      headers: newHeaders\n    });\n  }\n\n  // === ③ 轻量 Bearer 检查（可选，放行公开 API）===\n  const publicApiPrefixes = ['/api/products', '/api/categories'];\n  if (publicApiPrefixes.some(p => pathname.startsWith(p))) {\n    return next();\n  }\n\n  // === ④ 支付回调独立路径（IP 白名单 + 直接 return）===\n  if (pathname === '/api/pay/wx-notify' || pathname === '/api/pay/ali-notify') {\n    // 微信/支付宝 IP 白名单（可扩展）\n    const allowedIps = [\n      '101.226.90.0/24', // 微信支付\n      '110.42.0.0/16',  // 支付宝\n    ];\n    const clientIp = request.headers.get('CF-Connecting-IP') ||\n                    request.headers.get('X-Real-IP') || '';\n\n    if (!isIpAllowed(clientIp, allowedIps)) {\n      console.warn(`[Middleware] Blocked pay callback from IP: ${clientIp}`);\n      return new Response('Forbidden', { status: 403 });\n    }\n\n    // 直接 return，不继续到 Edge Middleware（回调没有 JWT）\n    return next();\n  }\n\n  return next();\n}\n\nfunction isIpAllowed(ip, allowedCidrs) {\n  // 简化的 CIDR 判断\n  return allowedCidrs.some(cidr => {\n    const [range, bits] = cidr.split('/');\n    return ip.startsWith(range.split('.').slice(0, parseInt(bits) / 8).join('.'));\n  });\n}\n```\n\n---\n\n## 三、Edge Middleware（_middleware.js）\n\n```javascript\n// edge-functions/_middleware.js\n\nimport { verifyAccessToken, extractToken } from './utils/jwt-helper.js';\nimport { checkRateLimit } from './utils/rate-limit.js';\n\nexport async function onRequest(context) {\n  const { request, env } = context;\n  const url = new URL(request.url);\n\n  // === 公开路径放行 ===\n  const publicPaths = ['/api/products', '/api/categories'];\n  if (publicPaths.some(p => url.pathname.startsWith(p))) {\n    return context.next();\n  }\n\n  // === Auth 路径放行 ===\n  if (url.pathname.startsWith('/api/auth/login') || url.pathname.startsWith('/api/auth/register')) {\n    return context.next();\n  }\n\n  // === RT 刷新路径放行 ===\n  if (url.pathname === '/api/auth/refresh') {\n    return context.next();\n  }\n\n  // === 提取 + 验证 JWT ===\n  const tokenData = extractToken(request);\n  if (!tokenData) {\n    return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n      status: 401, headers: { 'Content-Type': 'application/json' }\n    });\n  }\n\n  const payload = await verifyAccessToken(tokenData.token, env);\n  if (!payload) {\n    return new Response(JSON.stringify({ error: 'Token expired or invalid' }), {\n      status: 401, headers: { 'Content-Type': 'application/json' }\n    });\n  }\n\n  // === 限流（AI 栈专用）===\n  if (url.pathname.startsWith('/api/ai/')) {\n    const clientId = payload.sub || request.headers.get('CF-Connecting-IP');\n    const limit = payload.role === 'admin' ? 200 : 60; // admin 更高限额\n    const { allowed, resetMs } = await checkRateLimit(context, `ai:${clientId}`, limit);\n    if (!allowed) {\n      return new Response(JSON.stringify({ error: 'Rate limited' }), {\n        status: 429,\n        headers: { 'Retry-After': String(Math.ceil(resetMs / 1000)) }\n      });\n    }\n  }\n\n  // 注入用户信息到 request.headers（Edge Function 可读取）\n  const newRequest = new Request(request, {\n    headers: new Headers(request.headers)\n  });\n  newRequest.headers.set('X-User-Id', payload.sub);\n  newRequest.headers.set('X-User-Role', payload.role);\n\n  return context.next();\n}\n```\n\n---\n\n## 四、响应头汇总\n\n| Header | 值 | 注入位置 |\n|--------|-----|---------|\n| `Content-Security-Policy` | CSP 指令 | Platform（HTML） |\n| `X-Content-Type-Options` | `nosniff` | Platform |\n| `X-Frame-Options` | `DENY` | Platform |\n| `Referrer-Policy` | `strict-origin-when-cross-origin` | Platform |\n| `X-User-Id` | 用户 ID | Edge Middleware |\n| `X-User-Role` | `user/admin` | Edge Middleware |\n| `Access-Control-Allow-Origin` | `*` | Platform（CORS） |\n\nArchive v3.2.0: 133 files, 264726 bytes\n\nFiles: CHANGELOG.md (4454b), client/index.html (2084b), client/src/app.js (2270b), client/src/components/navbar.js (2112b), client/src/components/toast.js (2990b), client/src/pages/admin/dashboard.js (5353b), client/src/pages/admin/orders.js (9019b), client/src/pages/admin/products.js (14799b), client/src/pages/ai-chat.js (8114b), client/src/pages/cart.js (4785b), client/src/pages/checkout.js (7958b), client/src/pages/home.js (3787b), client/src/pages/login.js (2975b), client/src/pages/order-detail.js (8171b), client/src/pages/orders.js (5165b), client/src/pages/product-detail.js (5823b), client/src/pages/products.js (7011b), client/src/pages/profile.js (3662b), client/src/pages/register.js (3638b), client/src/services/ai.js (3535b), client/src/services/api.js (3148b), client/src/services/auth.js (2607b), client/src/services/cart.js (4899b), client/src/styles/main.css (11792b), client/src/utils/analytics.js (4111b), client/src/utils/auth.js (840b), client/src/utils/constants.js (753b), client/src/utils/escape-html.js (571b), client/src/utils/event-bus.js (1129b), client/src/utils/router.js (4923b), client/src/utils/seo.js (5577b), client/src/utils/storage.js (2022b), cloud-functions/api/_helpers.js (1951b), cloud-functions/api/admin/orders.js (1923b), cloud-functions/api/admin/products.js (4894b), cloud-functions/api/admin/stats.js (2659b), cloud-functions/api/admin/users.js (1607b), cloud-functions/api/ai/chat-stream.js (6956b), cloud-functions/api/auth/register.js (2876b), cloud-functions/api/order/cancel.js (2602b), cloud-functions/api/order/create.js (3320b), cloud-functions/api/order/detail.js (2395b), cloud-functions/api/order/transition.js (6357b), cloud-functions/api/pay/close.js (2194b), cloud-functions/api/pay/create-order.js (3471b), cloud-functions/api/pay/query.js (1680b), cloud-functions/api/pay/wx-notify.js (9572b), cloud-functions/cron/order-cron.js (5389b), cloud-functions/middleware/audit.js (3437b), cloud-functions/middleware/tenant-context.js (2944b), cloud-functions/utils/db.js (4368b), cloud-functions/utils/order-state-machine.js (5656b), CONTRIBUTING.md (1590b), db/migrations/001_init.sql (5539b), db/migrations/002_order_logs.sql (1555b), db/migrations/003_rollback.sql (921b), db/migrations/003_tenant_isolation.sql (3273b), db/seed.sql (9455b), docs/plans/phase-4a-plan.md (5210b), docs/plans/phase-4b-plan.md (3040b), edge-functions/_middleware.js (603b), edge-functions/api/admin/tenants.js (6492b), edge-functions/api/ai/history.js (2712b), edge-functions/api/analytics/event.js (2552b), edge-functions/api/auth/login.js (4353b), edge-functions/api/auth/logout.js (2680b), edge-functions/api/auth/me.js (2209b), edge-functions/api/auth/refresh.js (3653b), edge-functions/api/cart/add.js (2179b), edge-functions/api/cart/get.js (1302b), edge-functions/api/cart/remove.js (2037b), edge-functions/api/cart/update.js (2276b), edge-functions/api/orders/[id].js (2284b), edge-functions/api/orders/list.js (2604b), edge-functions/api/products/[id].js (2507b), edge-functions/api/products/categories.js (1101b), edge-functions/api/products/list.js (3320b), edge-functions/api/sitemap.xml.js (2314b), edge-functions/middleware/rbac.js (2867b), edge-functions/utils/rate-limit.js (1979b)\n\nFile v3.2.0:SKILL.md\n\n---\nname: 建站骨架 (EdgeOne Pages)\ndescription: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。\nversion: \"3.2.0\"\ncategory: openclaw-imports\ntags:\n  - edgeone\n  - website\n  - ecommerce\n  - deploy\ntriggers:\n  - 帮我建网站\n  - 建一个电商网站\n  - 做 AI 客服站\n  - 建管理后台\n  - EdgeOne Pages 建站\n  - 搭建网站\n  - 做个网站\n  - 生成网站\n  - 建站\n  - 创建网站\n  - 我要建站\n  - 商城网站\n  - 下单支付网站\n  - 全栈网站\n  - build a website\n  - create a full-stack app\n  - e-commerce website\n  - 建电商\n  - 网站骨架\n  - 部署网站\nenv:\n  JWT_SECRET:\n    description: JWT 签名密钥（HS256），Edge + Cloud 共享\n    required: true\n    secret: true\n  AI_API_KEY:\n    description: AI 模型调用 API Key（仅 AI 栈需要）\n    required: false\n    secret: true\n  WX_APPID:\n    description: 微信支付 AppID（仅电商栈需要）\n    required: false\n    secret: true\n  WX_MCHID:\n    description: 微信支付商户号（仅电商栈需要）\n    required: false\n    secret: true\n  DATABASE_URL:\n    description: D1 数据库绑定\n    required: false\n    secret: true\n---\n\n# 建站 Skill — EdgeOne Pages 全栈网站骨架\n\n> **版本：** 3.2.0 · **日期：** 2026-05-26 · **工程完缮（SPA详情页 + 227项测试 + 迁移/脚本/模板联动）**\n> **一句话描述：** 用户说一句话，AI 生成完整前后端网站，自动部署到 EdgeOne Pages。\n\n---\n\n## 一、核心设计理念\n\n```\n一次设计，无限复用 = 5 个模块 × 3 个场景 × 1 个部署平台\n```\n\n将\"建站\"拆解为 **Layer 0 基础设施** + **Layer 1 能力栈** + **Layer 2 可选增强**：\n\n| 层级 | 内容 | 性质 |\n|------|------|------|\n| **Layer 0**（Core） | SPA 骨架 + Auth + Middleware + EventBus | 必选，不可裁剪 |\n| **Layer 1**（Stack） | 🛒 电商栈 · 🤖 AI 栈 · 📊 管理栈 | 按需组合，互不依赖 |\n| **Layer 2**（Addon） | SEO · Analytics · i18n | 可选增强 |\n\n**场景模板优先**：用户选\"电商\"、\"AI 助手\"或\"管理后台\"场景，不选模块——模块由模板自动组合。\n\n---\n\n## 二、技术架构\n\n### 2.1 EdgeOne Pages 双运行时\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│  Platform Middleware（middleware.js）                        │\n│  ① CORS 预检（OPTIONS）                                     │\n│  ② CSP Header 注入                                          │\n│  ③ 轻量 Bearer 检查（公开路径放行）                           │\n│  ④ 支付回调 IP 白名单 → 直接 return，不进 Edge Middleware     │\n└──────────────────────────────────────────────────────────────┘\n                              ↓（非回调路径）\n┌──────────────────────────────────────────────────────────────┐\n│  Edge Functions Middleware（V8 + KV）                      │\n│  ⑤ JWT 详细校验（crypto.subtle）                             │\n│  ⑥ KV session 验证                                          │\n│  ⑦ KV 限流计数器（滑动窗口）                                   │\n└──────────────────────────────────────────────────────────────┘\n```\n\n### 2.2 运行时职责边界\n\n| 运行时 | 存储 | 职责 | 说明 |\n|--------|------|------|------|\n| **Edge Functions**（V8） | KV | Auth 登录/me、Products 公开读、Cart、Orders 读、AI History 读、幂等锁 | 延迟敏感、无密钥 |\n| **Cloud Functions**（Node） | D1 | Auth 注册/bcrypt、Payment 创建/回调、Admin CRUD、Orders 创建/取消、AI SSE 流 | 密钥操作、复杂事务 |\n\n> ⚠️ **平台约束（EdgeOne Pages）：**\n> - KV 仅 Edge Functions 可用，Cloud Functions 无法访问\n> - Cloud Functions 目录名必须为 `cloud-functions/`\n> - bcrypt 必须在 Cloud Functions 中执行\n\n### 2.3 分层目录结构\n\n```\nwebsite-skeleton/\n├── SKILL.md                    # 本文件，Skill 核心指令\n│\n├── templates/                  # 场景预设模板\n│   ├── e-commerce.json         # 🛒 电商场景\n│   ├── ai-assistant.json       # 🤖 AI 助手场景\n│   └── saas-admin.json         # 📊 SaaS 管理后台场景\n│\n├── sharing/                    # 跨运行时共享（构建时同步）\n│   ├── types.ts               # User/Product/Cart/Order/AISession 接口\n│   ├── constants.ts           # OrderStatus/UserRole/APIPaths 枚举\n│   ├── validators.ts           # 共享输入校验\n│   └── kv-keys.ts             # KV key 命名（含租户前缀占位）\n│\n├── client/                     # 前端 SPA\n│   ├── index.html\n│   └── src/\n│       ├── app.js             # 启动 + History API 路由\n│       ├── utils/\n│       │   ├── event-bus.js   # 全局事件总线（P0）\n│       │   ├── router.js      # History API 路由 + AuthGuard\n│       │   ├── escape-html.js # XSS 防护\n│       │   └── storage.js      # localStorage 封装\n│       ├── services/\n│       │   ├── api.js          # 统一客户端 + 拦截器\n│       │   ├── auth.js         # 内存 AuthService\n│       │   ├── cart.js         # 双模式购物车\n│       │   └── ai.js           # SSE 流式 AI\n│       └── components/         # 组件清单\n│\n├── middleware.js               # Platform Middleware\n│\n├── db/                         # 数据库迁移\n│   ├── migrations/\n│   │   └── 001_init.sql        # 建表脚本\n│   └── seed.sql                # 测试数据\n│\n├── docs/\n│   └── env-vars.md             # 环境变量矩阵\n│\n├── edge-functions/             # Edge Functions（V8 + KV）\n│   ├── _middleware.js          # JWT 校验 + KV session + 限流\n│   ├── api/\n│   │   ├── auth/login.js       # JWT 签发（Cookie） + KV session\n│   │   ├── auth/me.js          # KV session 读取\n│   │   ├── auth/refresh.js     # RT 轮换（KV version 乐观锁）\n│   │   ├── auth/logout.js      # 清除 Cookie + KV session\n│   │   ├── internal/idempotency.js  # Edge 原子幂等锁\n│   │   ├── products/list.js   # KV 缓存 + Cloud D1 回源\n│   │   ├── products/[id].js\n│   │   ├── products/categories.js\n│   │   ├── cart/*.js           # KV 购物车\n│   │   ├── orders/list.js      # D1 订单读取\n│   │   ├── orders/[id].js\n│   │   └── ai/history.js       # KV 读取 AI 会话历史\n│   └── utils/\n│       ├── kv-helper.js\n│       ├── jwt-helper.js       # crypto.subtle HS256\n│       ├── rate-limit.js        # KV 滑动窗口限流\n│       └── response.js\n│\n├── cloud-functions/            # Cloud Functions（Node.js）\n│   ├── api/\n│   │   ├── auth/register.js   # bcrypt cost=12 + D1\n│   │   ├── pay/create-order.js # 微信/支付宝预下单\n│   │   ├── pay/wx-notify.js   # Edge 幂等锁 → 业务处理\n│   │   ├── pay/ali-notify.js\n│   │   ├── pay/query.js\n│   │   ├── pay/close.js\n│   │   ├── admin/products.js   # D1 CRUD（含 version 乐观锁）\n│   │   ├── admin/orders.js    # D1 查询\n│   │   ├── admin/users.js     # D1 CRUD\n│   │   ├── admin/stats.js     # D1 聚合统计\n│   │   ├── order/create.js    # SELECT FOR UPDATE + 事务 + 指数退避\n│   │   ├── order/detail.js\n│   │   ├── order/cancel.js    # 状态机 + version 校验\n│   │   └── ai/chat-stream.js  # SSE 流式（主力实现）\n│   └── utils/\n│       ├── db.js               # D1 数据库工具（{tenant} 强制注入）\n│       ├── payment-sdk.js      # 微信V3/支付宝 SDK 封装\n│       ├── admin-guard.js\n│       └── notification-hooks.js  # 通知钩子空壳\n│\n├── references/                  # 能力参考文档\n│   ├── auth-module.md           # ✅ JWT RS256 + HS256 兼容 + KV Session\n│   ├── cart-module.md\n│   ├── payment-module.md\n│   ├── ai-chat-module.md\n│   ├── admin-module.md          # ✅ RBAC + CRUD + 运营统计 + 审计日志\n│   ├── notification-module.md   # Layer 2：邮件/微信/钉钉通知\n│   ├── order-state-machine.md   # ✅ 6状态 + 权限矩阵 + 库存联动 + 审计日志\n│   ├── edge-functions.md        # ✅ Edge Middleware + KV API + 限流\n│   ├── cloud-functions.md       # ✅ D1 事务 + bcrypt + 支付 SDK + SSE\n│   ├── kv-storage.md\n│   ├── middleware.md            # ✅ Platform + Edge 双层 + CSP + 支付 bypass\n│   └── deployment.md            # ✅ 完整部署流程 + Cron + 回滚\n│\n└── scripts/\n    ├── init-site.js             # 交互式初始化（模板优先）\n    ├── sync-sharing.js          # 构建时 shared → edge/cloud 同步\n    └── sample-data.js\n```\n\n---\n\n## 三、Auth 模块（Layer 0，Core）\n\n### API 路由\n\n| 方法 | 路径 | 运行时 | 说明 |\n|------|------|--------|------|\n| POST | `/api/auth/login` | Edge（KV） | JWT 签发 + KV session |\n| GET | `/api/auth/me` | Edge（KV） | KV session 读取 |\n| POST | `/api/auth/refresh` | Edge（KV） | RT 轮换（version 乐观锁） |\n| POST | `/api/auth/logout` | Edge（KV） | 清除 Cookie + KV session |\n| POST | `/api/auth/register` | Cloud（D1） | bcrypt cost=12 + D1 |\n\n### JWT 安全设计\n\n```\nAccess Token：短期 JWT（15min）+ HttpOnly Cookie（Secure + SameSite=Strict）\nRefresh Token：7天 TTL，存 KV rt:{userId}:meta（含 version）\n算法：Phase 1 用 HS256 + 短期 TTL，Phase 2 迁移 RS256\n```\n\n### 【v2.1 Critical 修复】RT 并发安全\n\n两个请求并发携带同一 RT，只有第一个能成功写入新 version，第二个收到 409 → 客户端稍等重试。\n\n```javascript\n// edge-functions/api/auth/refresh.js\nexport async function onRequest(context) {\n  const { RT } = await getTokens(context.request);\n  const { KV } = context.env;\n  const payload = parseJWT(RT);\n  const userId = payload.sub;\n  if (!userId) return new Response('Invalid', { status: 401 });\n\n  const current = await KV.get(`rt:${userId}:meta`);\n  const { version: oldVersion, token: oldToken } = JSON.parse(current || '{\"version\":0,\"token\":\"\"}');\n\n  if (oldToken !== RT) {\n    return new Response('Token already rotated', { status: 409 });\n  }\n\n  const newVersion = oldVersion + 1;\n  const newToken = signRT(userId, newVersion);\n\n  const ok = await KV.put(\n    `rt:${userId}:meta`,\n    JSON.stringify({ version: newVersion, token: newToken }),\n    { expirationTtl: 604800 }\n  );\n\n  if (!ok) return new Response('Concurrent rotation', { status: 409 });\n\n  return new Response(JSON.stringify({ refreshToken: newToken }), {\n    headers: { 'Content-Type': 'application/json' }\n  });\n}\n```\n\n---\n\n## 四、Cart 模块（Layer 1，电商栈）\n\n**双模式同步：**\n```\n未登录：localStorage（30d TTL 自动清理）\n登录时：localStorage → 服务端 KV（syncOnLogin()）\n已登录：服务端 KV（唯一数据源）\n```\n\n---\n\n## 五、Payment 模块（Layer 1，电商栈）\n\n### 独立回调路径\n\n```\n/api/pay/wx-notify   ← 微信支付回调（IP 白名单后直接 return，不进 Edge Middleware）\n/api/pay/ali-notify  ← 支付宝回调（独立路径）\n```\n\n### 【v2.1 Critical 修复】支付幂等原子锁\n\n微信支付平台会在回调超时后重试（最长 72h），KV 查→判→写三步非原子。解决方案：Edge Function `putIfNotExists` 原子幂等锁。\n\n```javascript\n// ===== Edge Function（唯一可访问 KV 的路径）=====\n// edge-functions/api/internal/idempotency.js\nexport async function onRequest(context) {\n  const { KV } = context.env;\n  const { out_trade_no, callback_id } = await context.request.json();\n\n  const acquired = await KV.putIfNotExists(\n    `pay:idempotency:${out_trade_no}`,\n    callback_id,\n    { expirationTtl: 86400 }   // 24h < 微信重试窗口 72h\n  );\n\n  return new Response(JSON.stringify({ acquired }), { status: 200 });\n}\n\n// ===== Cloud Function（微信回调处理）=====\n// cloud-functions/api/pay/wx-notify.js\nexport async function onRequest(request, env) {\n  const rawBody = await request.text();\n  if (!await verifyWechatSignature(rawBody, env.WX_MCH_SECRET))\n    return new Response('FAIL', { status: 401 });\n\n  const { out_trade_no, transaction_id, trade_state } = JSON.parse(rawBody);\n\n  const { acquired } = await fetch(`${env.EDGE_BASE}/api/internal/idempotency`, {\n    method: 'POST',\n    body: JSON.stringify({ out_trade_no, callback_id: transaction_id })\n  }).then(r => r.json());\n\n  if (!acquired) return new Response('SUCCESS');  // 幂等跳过，但返回 SUCCESS 止重试\n\n  if (trade_state === 'SUCCESS') await processPayment(out_trade_no, transaction_id, env);\n  return new Response('SUCCESS');\n}\n```\n\n---\n\n## 六、Order 创建原子性（v2.1 Critical 修复）\n\n高并发下，`UPDATE ... WHERE stock >= ?` 可能同时通过检查导致超卖。解决方案：`SELECT FOR UPDATE` + 乐观锁 + D1 CHECK 约束。\n\n```javascript\n// cloud-functions/api/order/create.js\nexport async function onRequest(request, env) {\n  const { userId } = await auth(request, env);\n  const { productId, quantity } = await request.json();\n  const pool = await getPool(env.DATABASE_URL);\n\n  let attempt = 0;\n  while (attempt < 3) {\n    attempt++;\n    try {\n      await pool.beginTransaction();\n\n      // ① SELECT FOR UPDATE：锁定商品行（持有行锁期间其他事务阻塞）\n      const [rows] = await pool.query(\n        'SELECT id, stock, price, version FROM products WHERE id = ? FOR UPDATE',\n        [productId]\n      );\n      if (!rows.length) { await pool.rollback(); return 404; }\n      const product = rows[0];\n\n      // ② 持有行锁期间校验库存（无竞态）\n      if (product.stock < quantity) {\n        await pool.rollback();\n        return { error: '库存不足', available: product.stock };\n      }\n\n      // ③ 乐观锁更新（双重保障）\n      const [updateResult] = await pool.query(\n        'UPDATE products SET stock = stock - ?, version = version + 1 WHERE id = ? AND version = ?',\n        [quantity, productId, product.version]\n      );\n      if (updateResult.affectedRows === 0) {\n        await pool.rollback();\n        return { error: '并发冲突，请重试' };\n      }\n\n      // ④ 创建订单（同一事务内）\n      const orderNo = generateOrderNo();\n      await pool.query(\n        `INSERT INTO orders (order_no, out_trade_no, user_id, product_id, qty, amount, status, created_at)\n         VALUES (?, ?, ?, ?, ?, ?, 'PENDING', NOW())`,\n        [orderNo, `WX_${orderNo}`, userId, productId, quantity, product.price * quantity]\n      );\n\n      await pool.commit();\n\n      // ⑤ 事务成功后，异步调用微信统一下单（不在事务内）\n      const payment = await createPayment(orderNo, product.price * quantity, env);\n      return { orderNo, payment };\n\n    } catch (err) {\n      await pool.rollback();\n      if (isRetryable(err) && attempt < 3) {\n        await sleep(100 * Math.pow(2, attempt - 1));  // 指数退避\n        continue;\n      }\n      return { error: '创建失败，请重试' };\n    }\n  }\n}\n\nfunction isRetryable(err) {\n  return err.code === 'ER_LOCK_DEADLOCK' || err.code === 'ER_LOCK_WAIT_TIMEOUT';\n}\n```\n\n---\n\n## 七、KV 分层查询策略\n\nEdgeOne Pages KV **不支持复合查询**，按以下策略分层：\n\n| 场景 | KV 层（Edge） | D1 层（Cloud） |\n|------|-------------|---------------|\n| 单商品读取 | ✅ KV 缓存 | — |\n| 商品列表（无筛选） | ✅ 缓存第1页 | — |\n| 分类+价格区间筛选 | — | ✅ Cloud D1 |\n| 搜索关键词 | — | ✅ Cloud D1 |\n| AI 会话历史（单用户） | ✅ KV | — |\n| 订单统计（多条件聚合） | — | ✅ Cloud D1 |\n\n---\n\n## 八、AI Chat 模块（Layer 1，AI 栈）\n\n**Cloud Functions SSE 实现（Edge 无法使用 waitUntil）：**\n\n```\n前端 → GET /api/ai/history（Edge，KV 读取）→ 拿到历史上下文\n    → SSE 连接 /api/ai/chat-stream（Cloud）→ 带历史 context\n    → Cloud 流式响应 + 异步写 KV 保存历史\n```\n\n---\n\n## 九、Admin 模块（Layer 1，管理栈）\n\n**RBAC 权限体系：**\n```\nrole: user   → 购物车、下单、查看自己的订单\nrole: admin  → 商品 CRUD、订单管理、用户管理、运营统计\n```\n\n---\n\n## 十、数据库 Schema\n\n```sql\n-- db/migrations/001_init.sql\n\nCREATE TABLE users (\n  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  email         VARCHAR(255) UNIQUE NOT NULL,\n  password_hash VARCHAR(255) NOT NULL,\n  role          ENUM('user','admin') DEFAULT 'user',\n  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE products (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  name        VARCHAR(255) NOT NULL,\n  price       DECIMAL(10,2) NOT NULL,    -- 服务端唯一价格来源\n  stock       INT UNSIGNED NOT NULL DEFAULT 0,\n  category_id INT UNSIGNED,\n  status      ENUM('active','inactive') DEFAULT 'active',\n  version     INT UNSIGNED DEFAULT 1,    -- 乐观锁版本号\n  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT chk_stock_positive CHECK (stock >= 0)\n);\n\nCREATE TABLE orders (\n  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  order_no      VARCHAR(64) UNIQUE NOT NULL,\n  out_trade_no  VARCHAR(128) UNIQUE,\n  user_id       BIGINT UNSIGNED NOT NULL,\n  total         DECIMAL(10,2) NOT NULL,\n  status        ENUM('pending','paid','shipped','cancelled','refunded') DEFAULT 'pending',\n  paid_at       DATETIME,\n  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,\n  FOREIGN KEY (user_id) REFERENCES users(id)\n);\n\nCREATE TABLE order_items (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  order_id    BIGINT UNSIGNED NOT NULL,\n  product_id  BIGINT UNSIGNED NOT NULL,\n  qty         INT UNSIGNED NOT NULL,\n  price       DECIMAL(10,2) NOT NULL,   -- 快照价格\n  FOREIGN KEY (order_id) REFERENCES orders(id),\n  FOREIGN KEY (product_id) REFERENCES products(id)\n);\n\nCREATE TABLE admin_logs (\n  id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n  admin_id    BIGINT UNSIGNED NOT NULL,\n  action      VARCHAR(64) NOT NULL,\n  target      VARCHAR(128),\n  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX idx_products_category ON products(category_id);\nCREATE INDEX idx_products_status ON products(status);\nCREATE INDEX idx_orders_user ON orders(user_id);\nCREATE INDEX idx_orders_status ON orders(status);\nCREATE INDEX idx_orders_created ON orders(created_at);\n```\n\n---\n\n## 十一、环境变量矩阵\n\n| 环境变量 | 必填 | 用于 | 运行时 |\n|---------|------|------|--------|\n| `JWT_SECRET` | ✅ | JWT 签名（HS256） | Edge + Cloud |\n| `AI_API_KEY` | ✅（AI栈） | AI 模型调用 | Cloud |\n| `WX_APPID` | ✅（电商栈） | 微信支付 AppID | Cloud |\n| `WX_MCHID` | ✅（电商栈） | 微信支付商户号 | Cloud |\n| `WX_API_KEY` | ✅（电商栈） | 微信支付 APIv3 密钥 | Cloud |\n| `WX_CERT_PATH` | ✅（电商栈） | 微信支付证书路径 | Cloud |\n| `ALI_APP_ID` | ✅（电商栈） | 支付宝 AppID | Cloud |\n| `ALI_PRIVATE_KEY` | ✅（电商栈） | 支付宝私钥 | Cloud |\n| `DATABASE_URL` | ✅（电商+管理） | D1 数据库绑定 | Cloud |\n| `EDGE_BASE` | ✅（电商栈） | Edge Function 内部网关地址 | Cloud |\n\n---\n\n## 十二、初始化工作流\n\n```\nStep 1: 选择建站类型\n  [1] 🛒 快速电商站（推荐）\n  [2] 🤖 AI 客服站\n  [3] 📊 SaaS 管理后台\n  [4] ⚙️ 自定义模块组合\n\nStep 2: 确认预填 / 模块选择\n\nStep 3: 填写基本信息（站点名、域名）\n\nStep 4: 密钥配置（从 env-vars.md 模板读取，EdgeOne Pages 环境变量注入）\n\nStep 5: 执行 db/migrations/001_init.sql（自动或手动）\n\nStep 6: 生成代码 → edgeone deploy → 返回访问 URL\n```\n\n---\n\n## 十三、安全检查清单\n\n### 🔴 P0（上线前必须完成）\n\n- [x] 支付幂等：Edge 原子 `putIfNotExists` 锁\n- [x] 订单超卖：`SELECT FOR UPDATE` + D1 事务 + CHECK 约束\n- [x] RT 并发安全：KV version 乐观锁（409 重试）\n- [x] KV 复合查询：分层策略（KV 缓存 / D1 复杂查询）\n- [x] 支付回调路径 Platform Middleware 直接 return\n- [x] 金额服务端 D1 计算，前端永不传 price\n- [x] bcrypt cost ≥ 12（Cloud Functions 中）\n\n### 🟡 P1（正式版前完成）\n\n- [x] JWT 短期 Access Token（15min）+ RT 轮换（含并发安全版本号）\n- [x] Cookie：HttpOnly + Secure + SameSite=Strict（含 SameSite=Lax 备选方案）\n- [x] AI 聊天限流（KV 滑动窗口：未登录 10次/分钟，登录 60次/分钟）\n- [x] CSP Header（Platform Middleware 注入，含 nonce 升级路径）\n- [x] EventBus 401 自动跳转登录（含 redirect 回跳逻辑）\n- [x] Notification 钩子（Phase 2 完整适配器设计 + 事件注册机制）\n\n### 🟢 P2（Phase 3 实现）\n\n- [x] RS256 迁移（双轨并行 HS256/RS256，30 天兼容窗口）\n- [x] 订单状态机（6状态 + 权限矩阵 + version 校验 + 库存联动 + 审计日志 + 定时 Cron）\n\n---\n\n## 十五、Phase 2 详细设计（P1/P2 实现指南）\n\n---\n\n### 15.1 【P1】JWT Access Token 短期化 + RT 轮换（已实现源码）\n\n以下为 Edge Functions 完整实现，Phase 1 已集成：\n\n**JWT 签发（login.js）** — Access Token 15min + Refresh Token 7d：\n\n```javascript\n// edge-functions/api/auth/login.js\nexport async function onRequest(context) {\n  const { email, password } = await context.request.json();\n  const pool = await getCloudPool(context.env.DATABASE_URL);\n  const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);\n  if (!rows.length) return new Response('Unauthorized', { status: 401 });\n\n  const user = rows[0];\n  const ok = await bcrypt.compare(password, user.password_hash);\n  if (!ok) return new Response('Unauthorized', { status: 401 });\n\n  const now = Math.floor(Date.now() / 1000);\n  // Access Token：15min\n  const accessToken = signJWT({ sub: user.id, role: user.role, type: 'access' }, 900);\n  // Refresh Token：7d，含 version 用于乐观锁\n  const rtVersion = 1;\n  const refreshToken = signRT(user.id, rtVersion);\n\n  // KV 存 RT meta（用于轮换校验）\n  await context.env.KV.put(\n    `rt:${user.id}:meta`,\n    JSON.stringify({ version: rtVersion, token: refreshToken }),\n    { expirationTtl: 604800 }\n  );\n\n  return new Response(null, {\n    status: 302,\n    headers: {\n      'Location': '/',\n      'Set-Cookie': [\n        `at=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`,\n        `rt=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800`\n      ].join(', ')\n    }\n  });\n}\n```\n\n**RT 轮换（refresh.js）** — 并发安全，version 乐观锁：\n\n```javascript\n// edge-functions/api/auth/refresh.js\nexport async function onRequest(context) {\n  const { KV } = context.env;\n  const cookieHeader = context.request.headers.get('Cookie') || '';\n  const rtMatch = cookieHeader.match(/rt=([^;]+)/);\n  if (!rtMatch) return new Response('No RT', { status: 401 });\n\n  const oldToken = rtMatch[1];\n  const payload = parseJWT(oldToken);\n  const userId = payload.sub;\n\n  // KV version 乐观锁：只有 RT 匹配当前 version 才允许写入新 version\n  const current = await KV.get(`rt:${userId}:meta`);\n  const { version: oldVersion, token: oldStored } = JSON.parse(current || '{\"version\":0,\"token\":\"\"}');\n\n  if (oldStored !== oldToken) {\n    // 另一个 tab 已轮换，当前 RT 失效 → 返回 409 让客户端重新登录\n    return new Response('Concurrent rotation', { status: 409 });\n  }\n\n  const newVersion = oldVersion + 1;\n  const newToken = signRT(userId, newVersion);\n\n  const ok = await KV.put(\n    `rt:${userId}:meta`,\n    JSON.stringify({ version: newVersion, token: newToken }),\n    { expirationTtl: 604800 }\n  );\n  if (!ok) return new Response('Rotation failed', { status: 409 });\n\n  return new Response(JSON.stringify({ refreshToken: newToken }), {\n    headers: {\n      'Content-Type': 'application/json',\n      'Set-Cookie': `rt=${newToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800`\n    }\n  });\n}\n```\n\n**客户端轮换触发逻辑（event-bus.js 集成）**：\n\n```javascript\n// client/src/utils/event-bus.js\nEventBus.on('auth:401', async () => {\n  // Access Token 过期 → 尝试轮换 RT\n  const res = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' });\n  if (res.ok) {\n    // RT 轮换成功 → 重发原请求\n    return retryOriginalRequest();\n  }\n  // RT 也失败 → 跳转登录\n  window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);\n});\n```\n\n---\n\n### 15.2 【P1】Cookie 安全属性\n\n所有认证 Cookie 必须同时满足以下属性（缺一不可）：\n\n| 属性 | 值 | 作用 |\n|------|-----|------|\n| `HttpOnly` | 必须 | 阻止 JS 读取，防止 XSS 窃取 |\n| `Secure` | 必须 | 仅 HTTPS 传输 |\n| `SameSite=Strict` | 强烈建议 | 防止 CSRF（同站请求才带 Cookie） |\n| `SameSite=Lax` | 备选 | 允许导航带 Cookie，但阻止跨站 POST |\n| `Path=/` | AT Cookie | 全路径生效 |\n| `Path=/api/auth/refresh` | RT Cookie | 仅刷新接口可读 |\n\n**Edge Functions 签发示例**：\n\n```javascript\n// 正确\nheaders.set('Set-Cookie',\n  `at=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`\n);\n\n// 常见错误：缺少 Secure 或 SameSite\n// ❌ `at=${token}; HttpOnly` — 可被 HTTP 拦截\n// ❌ `at=${token}; HttpOnly; SameSite=None` — 无 CSRF 保护\n```\n\n**注意**：`SameSite=Strict` 会导致从外部链接跳转过来时无法携带 Cookie。如有第三方回调场景，改用 `SameSite=Lax` + CSRF Token 双保险。\n\n---\n\n### 15.3 【P1】AI 聊天限流（KV 滑动窗口）\n\n**限流策略**：\n\n| 用户状态 | 限额 | 窗口 |\n|---------|------|------|\n| 未登录（IP 级别） | 10 次/分钟 | 滑动窗口 |\n| 已登录（User ID 级别） | 60 次/分钟 | 滑动窗口 |\n\n**Edge Function 实现**：\n\n```javascript\n// edge-functions/_middleware.js 或独立限流工具\n// edge-functions/utils/rate-limit.js\n\nexport async function checkRateLimit(context, key, limit) {\n  const { KV } = context.env;\n  const now = Date.now();\n  const windowMs = 60 * 1000; // 1 分钟滑动窗口\n  const windowKey = `rl:${key}:${Math.floor(now / windowMs)}`;\n  const prevKey = `rl:${key}:${Math.floor((now - windowMs) / windowMs)}`;\n\n  const current = parseInt(await KV.get(windowKey) || '0');\n  const prev = parseInt(await KV.get(prevKey) || '0');\n\n  // 滑动窗口：当前窗口占比 + 上一窗口剩余权重\n  const prevWeight = (now % windowMs) / windowMs;\n  const totalWeight = current + prev * prevWeight;\n\n  if (totalWeight >= limit) {\n    return { allowed: false, remaining: 0, resetMs: windowMs - (now % windowMs) };\n  }\n\n  // 写入当前计数\n  await KV.put(windowKey, String(current + 1), { expirationTtl: 120 });\n  return { allowed: true, remaining: limit - Math.ceil(totalWeight) - 1, resetMs: windowMs };\n}\n\n// 在 AI Chat Edge Middleware 中调用：\n// const userId = payload?.sub || request.headers.get('CF-Connecting-IP');\n// const { allowed, resetMs } = await checkRateLimit(context, `ai:${userId}`, 60);\n// if (!allowed) return new Response('Rate limited', { status: 429, headers: { 'Retry-After': String(Math.ceil(resetMs/1000)) } });\n```\n\n---\n\n### 15.4 【P1】CSP Header（Platform Middleware 注入）\n\nCSP 在 Platform Middleware 层注入，对所有 HTML 响应生效：\n\n```javascript\n// middleware.js（项目根目录，Platform Middleware）\nexport function onRequest(context) {\n  const response = context.next();\n\n  // 仅对 HTML 响应注入 CSP\n  const contentType = response.headers.get('Content-Type') || '';\n  if (!contentType.includes('text/html')) return response;\n\n  const CSP = [\n    \"default-src 'self'\",\n    \"script-src 'self' 'unsafe-inline'\",       // Skill 生成代码含内联脚本，放行\n    \"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com\",\n    \"font-src 'self' https://fonts.gstatic.com\",\n    \"img-src 'self' data: https:\",\n    \"connect-src 'self' https://api.edgeone.dev https://api.weixin.qq.com https://openapi.alipay.com\",\n    \"frame-ancestors 'none'\",\n    \"base-uri 'self'\",\n    \"form-action 'self'\"\n  ].join('; ');\n\n  const newHeaders = new Headers(response.headers);\n  newHeaders.set('Content-Security-Policy', CSP);\n  newHeaders.set('X-Content-Type-Options', 'nosniff');\n  newHeaders.set('X-Frame-Options', 'DENY');\n  newHeaders.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n\n  return new Response(response.body, {\n    status: response.status,\n    statusText: response.statusText,\n    headers: newHeaders\n  });\n}\n```\n\n**配置说明**：\n- `connect-src` 中的域名需根据实际 AI API 和支付平台调整\n- `'unsafe-inline'` 用于 Skill 生成的内联脚本（Phase 1 MVP 可接受）\n- Phase 3 可升级为 nonce 模式消除 `unsafe-inline`\n\n---\n\n### 15.5 【P1】EventBus 401 自动跳转\n\n前端 EventBus 统一处理认证失效事件：\n\n```javascript\n// client/src/utils/event-bus.js\nclass EventBus {\n  constructor() {\n    this.listeners = {};\n    // 全局监听 fetch 401 响应\n    this._setupGlobal401Handler();\n  }\n\n  _setupGlobal401Handler() {\n    const originalFetch = window.fetch;\n    window.fetch = async (...args) => {\n      try {\n        const res = await originalFetch(...args);\n        if (res.status === 401) {\n          this.emit('auth:401', { url: args[0], response: res });\n        }\n        return res;\n      } catch (err) {\n        throw err;\n      }\n    };\n  }\n\n  on(event, handler) {\n    (this.listeners[event] ||= []).push(handler);\n    return () => this.listeners[event] = this.listeners[event].filter(h => h !== handler);\n  }\n\n  emit(event, data) {\n    (this.listeners[event] || []).forEach(h => h(data));\n  }\n}\n\nexport const eventBus = new EventBus();\n\n// 应用启动时注册 401 跳转\neventBus.on('auth:401', ({ url }) => {\n  // 排除登录页自身，避免死循环\n  if (url.includes('/api/auth/login') || url.includes('/api/auth/register')) return;\n  // 跳过 refresh 接口（它有自己的 401 处理）\n  if (url.includes('/api/auth/refresh')) return;\n  // 记录原页面路径，登录后回跳\n  const redirect = encodeURIComponent(window.location.pathname + window.location.search);\n  window.location.href = `/login?redirect=${redirect}`;\n});\n```\n\n---\n\n### 15.6 【P1】Notification 钩子详细设计\n\nNotification 作为 Layer 2 Addon，按需接入。支持多通道：邮件、微信模板消息、钉钉 Webhook。\n\n**接口设计（空壳 → Phase 2 填充适配器）**：\n\n```javascript\n// cloud-functions/utils/notification-hooks.js\n\n// 通知事件类型\nexport const NotificationEvent = {\n  ORDER_CREATED:    'order.created',\n  ORDER_PAID:       'order.paid',\n  ORDER_SHIPPED:    'order.shipped',\n  ORDER_DELIVERED:  'order.delivered',\n  USER_REGISTERED: 'user.registered',\n  PASSWORD_CHANGED: 'password.changed',\n};\n\n// 通知渠道\nexport const NotificationChannel = {\n  EMAIL:    'email',\n  WECHAT:   'wechat',   // 微信模板消息\n  DINGTALK: 'dingtalk', // 钉钉 Webhook\n  SMS:      'sms',\n};\n\n// 钩子注册表（Phase 2 填充）\nconst handlers = {\n  [NotificationEvent.ORDER_PAID]: [],\n  [NotificationEvent.USER_REGISTERED]: [],\n};\n\nexport function registerHandler(event, handler) {\n  handlers[event] ||= [];\n  handlers[event].push(handler);\n}\n\nexport async function emit(event, payload) {\n  const eventHandlers = handlers[event] || [];\n  await Promise.allSettled(\n    eventHandlers.map(h => h(payload).catch(err => console.error(`Notification handler error: ${err}`)))\n  );\n}\n\n// ===== 具体适配器示例（Phase 2 实现）=====\n\n// 邮件适配器\nregisterHandler(NotificationEvent.ORDER_PAID, async ({ order, user }) => {\n  // 需配置 SMTP 环境变量\n  if (!process.env.SMTP_HOST) return; // 无邮件配置则跳过\n  await sendEmail({\n    to: user.email,\n    subject: `订单 ${order.order_no} 支付成功`,\n    html: `<h2>感谢您的购买！</h2><p>订单号：${order.order_no}</p>`\n  });\n});\n\n// 微信模板消息适配器\nregisterHandler(NotificationEvent.ORDER_SHIPPED, async ({ order, user }) => {\n  if (!process.env.WX_TEMPLATE_ID_SHIP) return;\n  await sendWechatTemplate(user.openid, process.env.WX_TEMPLATE_ID_SHIP, {\n    keyword1: order.order_no,\n    keyword2: order.express_company + ' ' + order.express_no,\n  });\n});\n\n// 调用示例（Cloud Functions 中）\nimport { emit, NotificationEvent } from './utils/notification-hooks.js';\n\nexport async function onRequest(request, env) {\n  // 支付回调成功后触发\n  await emit(NotificationEvent.ORDER_PAID, { order, user });\n  return new Response('SUCCESS');\n}\n```\n\n**env-vars.md 补充字段**：\n\n```\nNOTIFICATION_SMTP_HOST     # 邮件 SMTP 主机\nNOTIFICATION_SMTP_PORT     # 邮件 SMTP 端口（默认 587）\nNOTIFICATION_SMTP_USER     # 邮件发件人\nNOTIFICATION_SMTP_PASS     # 邮件密码\nNOTIFICATION_FROM_EMAIL    # 发件人地址\nWX_TEMPLATE_ID_ORDER       # 微信订单通知模板 ID\nWX_TEMPLATE_ID_SHIP        # 微信发货通知模板 ID\nDINGTALK_WEBHOOK_URL       # 钉钉群 Webhook URL\n```\n\n---\n\n### 15.7 【P2】RS256 迁移方案\n\nPhase 1 使用 HS256（密钥共享，简单快速）；Phase 2 迁移到 RS256（公私钥，安全性更高）。\n\n**迁移策略：双轨并行，渐进式切换**\n\n```\nPhase 1（当前）：HS256\n  - JWT_SECRET = 对称密钥（Edge + Cloud 共享）\n\nPhase 2 迁移：\n  - 新增 JWT_PRIVATE_KEY（Cloud 签名用 RSA 私钥）\n  - 新增 JWT_PUBLIC_KEY（Edge 验证用 RSA 公钥）\n  - Edge Functions 验证用公钥（无需密钥）\n  - Cloud Functions 签名用私钥\n  - HS256 保留 30 天兼容窗口（老 token 仍可验证）\n```\n\n**生成密钥对**：\n\n```bash\n# 生成 RSA-256 密钥对\nopenssl genrsa -out private.pem 2048\nopenssl rsa -in private.pem -pubout -out public.pem\n# 将公钥 public.pem 内容填入 EdgeOne Pages 环境变量 JWT_PUBLIC_KEY\n# 将私钥 private.pem 内容填入 Cloud Functions 环境变量 JWT_PRIVATE_KEY（严格保密）\n```\n\n**Cloud Functions 签名切换**：\n\n```javascript\n// cloud-functions/utils/jwt-helper.js\nimport { SignJWT, jwtVerify } from 'jose';\n\nconst getSignKey = (env) => {\n  if (env.JWT_PRIVATE_KEY) {\n    return createPrivateKey(env.JWT_PRIVATE_KEY); // RS256\n  }\n  return new TextEncoder().encode(env.JWT_SECRET); // 兼容 HS256\n};\n\nexport async function signJWT(payload, expiresIn, env) {\n  const key = getSignKey(env);\n  return new SignJWT(payload)\n    .setProtectedHeader({ alg: env.JWT_PRIVATE_KEY ? 'RS256' : 'HS256' })\n    .setIssuedAt()\n    .setExpirationTime(`${expiresIn}s`)\n    .sign(key);\n}\n```\n\n**Edge Functions 验证（始终用公钥）**：\n\n```javascript\n// edge-functions/utils/jwt-helper.js\nexport async function verifyJWT(token, env) {\n  const publicKey = createPublicKey(env.JWT_PUBLIC_KEY); // RS256 验证\n  try {\n    const { payload } = await jwtVerify(token, publicKey);\n    return payload;\n  } catch {\n    // 30 天兼容窗口：尝试 HS256 验证（仅过渡期）\n    const secret = new TextEncoder().encode(env.JWT_SECRET);\n    try {\n      const { payload } = await jwtVerify(token, secret);\n      return { ...payload, _hs256Fallback: true }; // 标记老 token\n    } catch {\n      return null;\n    }\n  }\n}\n```\n\n---\n\n### 15.8 【P2】订单状态机详细设计\n\n**状态定义与流转**：\n\n```\n┌──────────┐  pay    ┌───────┐  ship   ┌──────────┐  confirm ┌───────────┐\n│ PENDING  │ ──────→ │ PAID  │ ─────→ │ SHIPPED  │ ──────→ │ COMPLETED │\n└──────────┘         └───────┘         └──────────┘         └───────────┘\n     │                    │                                       │\n     │ cancel (user)      │ refund (user/admin)                   │\n     ↓                    ↓                                       │\n┌──────────┐         ┌──────────┐                                 │\n│ CANCELLED│         │ REFUNDED │                                 │\n└──────────┘         └──────────┘                                 │\n                                                                  │\n                        refund (admin, COMPLETED)                 │\n                        ─────────────────────────────────────────┘\n```\n\n**合法流转规则（version 乐观锁保护）**：\n\n| 当前状态 | 允许目标状态 | 触发方 | 条件 |\n|---------|------------|--------|------|\n| PENDING | PAID | 支付回调 | 金额核对成功 |\n| PENDING | CANCELLED | 用户/系统超时 | 30min 未支付 |\n| PAID | SHIPPED | 管理员 | 填写物流信息 |\n| PAID | REFUNDED | 用户/管理员 | 退款申请 |\n| SHIPPED | COMPLETED | 用户/系统 | 7天无售后自动确认 |\n| SHIPPED | REFUNDED | 用户/管理员 | 退货退款 |\n| COMPLETED | REFUNDED | 管理员 | 特殊退款审批 |\n\n**状态机实现（D1 + version 乐观锁）**：\n\n```javascript\n// cloud-functions/api/order/cancel.js\nexport async function onRequest(request, env) {\n  const { userId, role } = await auth(request, env);\n  const { orderId, reason } = await request.json();\n  const pool = await getPool(env.DATABASE_URL);\n\n  let attempt = 0;\n  while (attempt < 3) {\n    attempt++;\n    try {\n      await pool.beginTransaction();\n\n      // ① 锁定订单行，获取当前状态和版本\n      const [rows] = await pool.query(\n        'SELECT * FROM orders WHERE id = ? FOR UPDATE',\n        [orderId]\n      );\n      if (!rows.length) { await pool.rollback(); return 404; }\n      const order = rows[0];\n\n      // ② 权限校验：用户只能取消自己的 PENDING 订单\n      if (role !== 'admin' && order.user_id !== userId) {\n        await pool.rollback(); return 403;\n      }\n\n      // ③ 状态机校验\n      const allowed = {\n        'PENDING': ['CANCELLED'],\n        'PAID': ['CANCELLED', 'REFUNDED'],     // 退款需管理员\n        'SHIPPED': ['COMPLETED', 'REFUNDED'],  // 已发货需管理员\n      };\n      const target = reason === 'user_cancel' ? 'CANCELLED' : 'REFUNDED';\n      if (!allowed[order.status]?.includes(target)) {\n        await pool.rollback();\n        return { error: `状态 ${order.status} 不允许变更为 ${target}` };\n      }\n      if (target === 'CANCELLED' && role !== 'admin' && order.status !== 'PENDING') {\n        await pool.rollback();\n        return { error: '仅 PENDING 状态可由用户取消' };\n      }\n\n      // ④ 乐观锁更新（防止并发修改）\n      const [result] = await pool.query(\n        'UPDATE orders SET status = ?, version = version + 1 WHERE id = ? AND version = ?',\n        [target, orderId, order.version]\n      );\n      if (result.affectedRows === 0) {\n        await pool.rollback(); // 版本冲突，重试\n        continue;\n      }\n\n      // ⑤ 释放库存（仅取消时回补）\n      if (target === 'CANCELLED') {\n        await pool.query(\n          'UPDATE products SET stock = stock + (SELECT qty FROM order_items WHERE order_id = ?), version = version + 1 WHERE id = (SELECT product_id FROM order_items WHERE order_id = ?)',\n          [orderId, orderId]\n        );\n      }\n\n      // ⑥ 记录操作日志\n      await pool.query(\n        'INSERT INTO admin_logs (admin_id, action, target) VALUES (?, ?, ?)',\n        [userId, `order_status_change:${order.status}→${target}`, orderId]\n      );\n\n      await pool.commit();\n\n      // ⑦ 触发通知钩子\n      await emit(NotificationEvent.ORDER_CANCELLED, { order, reason });\n\n      return { success: true, status: target };\n\n    } catch (err) {\n      await pool.rollback();\n      if (err.code === 'ER_LOCK_DEADLOCK' && attempt < 3) {\n        await sleep(100 * Math.pow(2, attempt));\n        continue;\n      }\n      return { error: '操作失败，请重试' };\n    }\n  }\n}\n```\n\n---\n\n## 十六、Phase 2 验收标准\n\n| ID | 验收项 | 验证方法 |\n|----|--------|---------|\n| P2-01 | JWT 15min AT + 7d RT + Cookie 全属性 | 登录后 DevTools 查看 Cookie 属性 |\n| P2-02 | 并发刷新 RT，第二个请求返回 409 | 两个 tab 同时触发刷新 |\n| P2-03 | EventBus 401 跳转登录并回跳 | Token 过期后触发验证 |\n| P2-04 | AI 限流：未登录 11 次请求第 11 个返回 429 | 匿名请求连续发送 |\n| P2-05 | CSP Header 存在于 HTML 响应中 | `curl -I` 查看响应头 |\n| P2-06 | 订单状态机：PENDING→CANCELLED 成功 | 调用 cancel API |\n| P2-07 | 订单状态机：PAID→CANCELLED 被拒绝（需 admin） | 用户端测试 |\n| P2-08 | Notification 钩子注册 + emit 触发 | 单元测试验证 |\n| P2-09 | RS256 双轨验证（可选 Phase 2 末期） | HS/RS 混合 token 混跑 |\n\n---\n\n## 十七、功能验证清单（Phase 2 更新）\n\n**Demo 站点：** https://website-skeleton-demo-8mv8fitk.edgeone.cool（需有效期内的 EdgeOne Pages 访问 Token）\n\n| # | 功能 | 验证方法 | 状态 |\n|---|------|---------|------|\n| V-01 | 首页商品浏览（12 个商品） | API 返回 12 个商品，含名称/价格/库存 | ✅ |\n| V-02 | 用户注册（bcrypt cost=12） | 注册成功，返回 userId/email | ✅ |\n| V-03 | 用户登录（JWT） | 登录成功，返回用户信息 | ✅ |\n| V-04 | 购物车（localStorage 持久化） | Next.js 客户端路由，需浏览器测试 | 🟡 浏览器验证 |\n| V-05 | 结账（微信/支付宝选择） | checkout 页面存在，需浏览器测试 | 🟡 浏览器验证 |\n| V-06 | 模拟支付成功回调 | confirm API 存在，需有效 session | 🟡 需 session |\n| V-07 | 我的订单（状态标签） | orders API 存在，需有效 session | 🟡 需 session |\n\n---\n\n## 十八、Phase 2 里程碑\n\n```\n✅ Phase 1 完成：安全 Critical 全部修复（7/7 P0）\n🟡 Phase 2 进行中：P1 安全加固 + P2 能力完善\n🔲 Phase 3（可选）：RS256 + nonce CSP + SSE 优化\n```\n\n> **Phase 2 完成后，网站骨架 Skill 具备生产级安全性与完整功能集。**\n\n---\n\n## 十八、Phase 3 实现（P2 编码 + Layer 2 Addon + 多租户铺垫）\n\n### Phase 3 里程碑\n\n```\n✅ Phase 1 完成：Mock 数据 Demo，架构验证\n✅ Phase 2 完成：P0/P1 安全设计 + P2 设计文档完整\n✅ Phase 3 完成：P2 实现 + Layer 2 Addon + 多租户铺垫\n```\n\n### P2-1：RS256 双轨迁移（sharing/jwt-helper.js）\n\n**实现文件：** `sharing/jwt-helper.js`\n\n- 签发：RS256 私钥（`JWT_PRIVATE_KEY` 环境变量）\n- 验证：优先 RS256，30 天内旧 HS256 token 仍可验证\n- 迁移时间线：Day 0 部署 → Day 30 移除 HS256 兼容分支\n\n```javascript\n// 签发（永远 RS256）\nconst token = await signJWT({ sub: user.id, role: 'admin' }, AT_TTL_MS, env);\n\n// 验证（自动双轨）\nconst payload = await verifyJWT(token, env);\n// payload._alg === 'RS256' → 新 token\n// payload._alg === 'HS256' → 30天兼容窗口内的旧 token\n```\n\n### P2-2：订单状态机（cloud-functions/）\n\n**实现文件：**\n- `cloud-functions/utils/order-state-machine.js` — 核心状态机 + TRANSITIONS 表 + PERMISSIONS 表\n- `cloud-functions/api/order/transition.js` — 统一状态变更 API\n- `cloud-functions/cron/order-cron.js` — 定时任务（PENDING 超时取消 / SHIPPED 自动完成）\n- `db/migrations/002_order_logs.sql` — `order_status_logs` 审计表\n\n**状态流转（6 状态）：**\n```\nPENDING → PAID → SHIPPED → COMPLETED\n    ↓        ↓        ↓\nCANCELLED  REFUNDED  REFUNDED\n```\n\n**权限矩阵：**\n| 变更 | 用户（本人） | 管理员 |\n|------|------------|--------|\n| PENDING→CANCELLED | ✅ | ✅ |\n| PAID→SHIPPED | — | ✅ |\n| PAID/SHIPPED→REFUNDED | ✅（本人） | ✅ |\n| SHIPPED→COMPLETED | ✅ | ✅ |\n\n### L2-1：SEO 模块（client/src/utils/seo.js + edge-functions/）\n\n**实现文件：**\n- `client/src/utils/seo.js` — JSON-LD 生成器 + Meta Tags + Sitemap XML 生成器\n- `edge-functions/api/sitemap.xml.js` — 动态 Sitemap API（Edge Function，5 分钟缓存）\n- `sharing/i18n/zh-CN.js` + `en-US.js` — 中英文案\n\n**JSON-LD 支持：**\n- `WebSite`（首页）\n- `Product`（产品页，含 offers/aggregateRating）\n- `BreadcrumbList`（面包屑）\n- `Organization`（组织信息）\n\n### L2-2：i18n 国际化（sharing/i18n/）\n\n**实现文件：**\n- `sharing/i18n/zh-CN.js` — 中文文案\n- `sharing/i18n/en-US.js` — 英文文案\n- `sharing/i18n/i18n.js` — 翻译函数 `t(key)` + 语言切换\n\n**使用方式：**\n```javascript\nimport { t, setLang, getLang } from './i18n.js';\n\nt('nav.home')           // → '首页'\nt('order.status.PAID')  // → '已支付'\nsetLang('en-US');       // 切换语言\n```\n\n### L2-3：Analytics 埋点（client/src/utils/analytics.js）\n\n**实现文件：**\n- `client/src/utils/analytics.js` — 埋点 SDK\n- `edge-functions/api/analytics/event.js` — 事件接收 API（KV 存储）\n\n**预定义事件：** `page_view` / `add_to_cart` / `checkout_start` / `purchase` / `signup` / `login` / `search`\n\n**特点：** `navigator.sendBeacon` 不阻塞导航，支持页面卸载时发送。\n\n### L3-1：Multi-tenant KV 前缀（sharing/kv-keys.js）\n\n**实现文件：** `sharing/kv-keys.js`\n\n所有 KV Key 统一加租户前缀：\n```\nPhase 3: \"default:session:abc123\"\nPhase 4: \"{tenant}:session:abc123\"（从 JWT payload.tenant 动态读取）\n```\n\n### Phase 3 新增文件清单\n\n```\nsharing/\n├── jwt-helper.js              ✅ RS256 + HS256 双轨\n├── kv-keys.js                 ✅ 多租户前缀\n└── i18n/\n    ├── zh-CN.js               ✅ 中文\n    ├── en-US.js               ✅ 英文\n    └── i18n.js                ✅ 翻译函数\n\ncloud-functions/\n├── utils/\n│   └── order-state-machine.js ✅ 核心状态机\n├── api/order/\n│   └── transition.js          ✅ 状态变更 API\n└── cron/\n    └── order-cron.js          ✅ 定时任务\n\nclient/src/utils/\n├── seo.js                     ✅ SEO 工具\n└── analytics.js               ✅ 埋点 SDK\n\nedge-functions/\n├── api/\n│   ├── sitemap.xml.js         ✅ Sitemap API\n│   └── analytics/event.js     ✅ 埋点接收\n\ndb/migrations/\n└── 002_order_logs.sql         ✅ 审计日志表\n\nreferences/\n├── admin-module.md            ✅ 补充\n├── edge-functions.md          ✅ 补充\n├── cloud-functions.md         ✅ 补充\n├── middleware.md              ✅ 补充\n└── deployment.md              ✅ 补充\n```\n\n---\n\n## 十九、Phase 3 验收标准\n\n| ID | 验收项 | 验证方法 |\n|----|--------|---------|\n| P3-01 | RS256：新 token 用 RS256 私钥签发 | 代码审查 + 手动 JWT 解析 |\n| P3-02 | RS256：HS256 旧 token 30 天内仍可验证 | 测试过期 token 验证 |\n| P3-03 | 订单状态机：用户取消 PENDING 订单成功 | 调用 transition API |\n| P3-04 | 订单状态机：用户无法 PAID→CANCELLED（403） | 调用 transition API |\n| P3-05 | 库存联动：取消/退款时 stock 回补 | 查询 products 表 |\n| P3-06 | 审计日志：每次状态变更写入 order_status_logs | 查询数据库 |\n| P3-07 | Cron：PENDING 超时 30 分钟自动 CANCELLED | 模拟超时订单 |\n| P3-08 | SEO JSON-LD：产品页含 schema.org 结构化数据 | 审查页面源码 |\n| P3-09 | Sitemap：/api/sitemap.xml 返回有效 XML | curl 访问 |\n| P3-10 | i18n：`t('order.status.PAID')` 正确输出中英文 | 切换语言测试 |\n| P3-11 | Analytics：add_to_cart 事件通过 sendBeacon 发送 | Network 面板验证 |\n| P3-12 | Multi-tenant：KV key 格式含 \"default:\" 前缀 | 代码审查 |\n\n---\n\n## 二十、未来演进\n\n```\nPhase 1：Mock 数据 Demo ✅\nPhase 2：P0/P1 安全设计 + P2 设计文档 ✅\nPhase 3：P2 编码实现 + Layer 2 Addon + 多租户铺垫 ✅\n\nPhase 4（规划中）：多租户 SaaS\n  - KV key 从 JWT payload.tenant 动态读取\n  - 租户隔离数据库（D1 schema）\n  - 租户管理后台\n  - 计费系统（按量/订阅）\n\nPhase 5（规划中）：npm 包化\n  npm install @site-skeleton/auth\n  npm install @site-skeleton/payment\n```\n\n*Skill 版本演进由评审驱动，每 Phase 完成后更新版本号与文档。*\n\n\n## 用户确认检查点\n\n以下操作必须在执行前暂停，向用户说明并等待确认：\n\n| # | 操作 | 检查点说明 | 风险 |\n|---|------|-----------|------|\n| 1 | **edgeone pages deploy** | 显示待部署文件清单和变更概要，确认目标项目（测试/生产） | 高 |\n| 2 | **激活真实支付网关** | 切换到正式商户号前确认：签名验证已通过、测试环境已跑通全流程 | 高 |\n| \n\nArchive v3.1.2: 123 files, 233635 bytes\n\nFiles: CHANGELOG.md (3703b), client/index.html (2084b), client/src/app.js (2270b), client/src/components/navbar.js (2112b), client/src/components/toast.js (2990b), client/src/pages/admin/dashboard.js (5353b), client/src/pages/admin/orders.js (9019b), client/src/pages/admin/products.js (14799b), client/src/pages/ai-chat.js (8114b), client/src/pages/cart.js (4785b), client/src/pages/checkout.js (7958b), client/src/pages/home.js (3787b), client/src/pages/login.js (2975b), client/src/pages/order-detail.js (8171b), client/src/pages/orders.js (5165b), client/src/pages/products.js (7011b), client/src/pages/profile.js (3662b), client/src/pages/register.js (3638b), client/src/services/ai.js (3535b), client/src/services/api.js (4265b), client/src/services/auth.js (3703b), client/src/services/cart.js (4899b), client/src/styles/main.css (11792b), client/src/utils/analytics.js (4111b), client/src/utils/auth.js (840b), client/src/utils/constants.js (753b), client/src/utils/escape-html.js (571b), client/src/utils/event-bus.js (1129b), client/src/utils/router.js (4852b), client/src/utils/seo.js (5577b), client/src/utils/storage.js (2022b), cloud-functions/api/_helpers.js (1951b), cloud-functions/api/admin/orders.js (1919b), cloud-functions/api/admin/products.js (4890b), cloud-functions/api/admin/stats.js (2655b), cloud-functions/api/admin/users.js (1603b), cloud-functions/api/ai/chat-stream.js (6956b), cloud-functions/api/auth/register.js (2853b), cloud-functions/api/order/cancel.js (2598b), cloud-functions/api/order/create.js (3144b), cloud-functions/api/order/detail.js (2391b), cloud-functions/api/order/transition.js (6357b), cloud-functions/api/pay/close.js (2190b), cloud-functions/api/pay/create-order.js (3295b), cloud-functions/api/pay/query.js (1676b), cloud-functions/api/pay/wx-notify.js (9572b), cloud-functions/cron/order-cron.js (5389b), cloud-functions/middleware/audit.js (3437b), cloud-functions/middleware/tenant-context.js (2944b), cloud-functions/utils/db.js (4368b), cloud-functions/utils/order-state-machine.js (5656b), CONTRIBUTING.md (1590b), db/migrations/001_init.sql (5539b), db/migrations/002_order_logs.sql (1555b), db/migrations/003_rollback.sql (921b), db/migrations/003_tenant_isolation.sql (3273b), db/seed.sql (9364b), docs/plans/phase-4a-plan.md (5210b), docs/plans/phase-4b-plan.md (3040b), edge-functions/_middleware.js (603b), edge-functions/api/admin/tenants.js (6492b), edge-functions/api/ai/history.js (2712b), edge-functions/api/analytics/event.js (2169b), edge-functions/api/auth/login.js (4353b), edge-functions/api/auth/logout.js (2680b), edge-functions/api/auth/me.js (2209b), edge-functions/api/auth/refresh.js (3653b), edge-functions/api/cart/add.js (2179b), edge-functions/api/cart/get.js (1302b), edge-functions/api/cart/remove.js (2037b), edge-functions/api/cart/update.js (2276b), edge-functions/api/orders/[id].js (2284b), edge-functions/api/orders/list.js (2604b), edge-functions/api/products/[id].js (2507b), edge-functions/api/products/categories.js (1101b), edge-functions/api/products/list.js (3320b), edge-functions/api/sitemap.xml.js (1961b), edge-functions/middleware/rbac.js (2867b), edge-functions/utils/rate-limit.js (1979b), index.html (3973b)\n\nArchive v3.1.1: 111 files, 202863 bytes\n\nFiles: CHANGELOG.md (2781b), client/index.html (15865b), client/src/app.js (2270b), client/src/components/navbar.js (2112b), client/src/components/toast.js (2990b), client/src/pages/cart.js (4785b), client/src/pages/home.js (3787b), client/src/pages/login.js (2975b), client/src/pages/orders.js (5165b), client/src/pages/products.js (7011b), client/src/pages/register.js (3638b), client/src/services/ai.js (3535b), client/src/services/api.js (4265b), client/src/services/auth.js (3703b), client/src/services/cart.js (4899b), client/src/utils/analytics.js (4111b), client/src/utils/auth.js (840b), client/src/utils/constants.js (753b), client/src/utils/escape-html.js (571b), client/src/utils/event-bus.js (1129b), client/src/utils/router.js (4038b), client/src/utils/seo.js (5577b), client/src/utils/storage.js (2022b), cloud-functions/api/_helpers.js (1951b), cloud-functions/api/admin/orders.js (1919b), cloud-functions/api/admin/products.js (4890b), cloud-functions/api/admin/stats.js (2655b), cloud-functions/api/admin/users.js (1603b), cloud-functions/api/ai/chat-stream.js (6956b), cloud-functions/api/auth/register.js (2853b), cloud-functions/api/order/cancel.js (2598b), cloud-functions/api/order/create.js (3144b), cloud-functions/api/order/detail.js (2391b), cloud-functions/api/order/transition.js (6357b), cloud-functions/api/pay/close.js (2190b), cloud-functions/api/pay/create-order.js (3295b), cloud-functions/api/pay/query.js (1676b), cloud-functions/api/pay/wx-notify.js (9572b), cloud-functions/cron/order-cron.js (5389b), cloud-functions/middleware/audit.js (3437b), cloud-functions/middleware/tenant-context.js (2944b), cloud-functions/utils/db.js (4368b), cloud-functions/utils/order-state-machine.js (5656b), CONTRIBUTING.md (1590b), db/migrations/002_order_logs.sql (1555b), db/migrations/003_rollback.sql (921b), db/migrations/003_tenant_isolation.sql (3273b), docs/plans/phase-4a-plan.md (5210b), docs/plans/phase-4b-plan.md (3040b), edge-functions/_middleware.js (603b), edge-functions/api/admin/tenants.js (6492b), edge-functions/api/ai/history.js (2712b), edge-functions/api/analytics/event.js (2169b), edge-functions/api/auth/login.js (4353b), edge-functions/api/auth/logout.js (2680b), edge-functions/api/auth/me.js (2209b), edge-functions/api/auth/refresh.js (3653b), edge-functions/api/cart/add.js (2179b), edge-functions/api/cart/get.js (1302b), edge-functions/api/cart/remove.js (2037b), edge-functions/api/cart/update.js (2276b), edge-functions/api/orders/[id].js (2284b), edge-functions/api/orders/list.js (2604b), edge-functions/api/products/[id].js (2507b), edge-functions/api/products/categories.js (1101b), edge-functions/api/products/list.js (3320b), edge-functions/api/sitemap.xml.js (1961b), edge-functions/middleware/rbac.js (2867b), edge-functions/utils/rate-limit.js (1979b), index.html (3973b), middleware.js (495b), MIGRATION.md (2372b), package-lock.json (1197b), package.json (697b), packages/admin/package.json (409b), packages/admin/src/index.js (725b), packages/payment/package.json (421b), packages/payment/src/index.js (1251b), packages/shared/package.json (595b), README.md (16443b)\n\nArchive v3.1.0: 111 files, 202981 bytes\n\nFiles: CHANGELOG.md (2781b), client/index.html (15865b), client/src/app.js (2270b), client/src/components/navbar.js (2112b), client/src/components/toast.js (2990b), client/src/pages/cart.js (4785b), client/src/pages/home.js (3787b), client/src/pages/login.js (2975b), client/src/pages/orders.js (5165b), client/src/pages/products.js (7011b), client/src/pages/register.js (3638b), client/src/services/ai.js (3535b), client/src/services/api.js (4265b), client/src/services/auth.js (3703b), client/src/services/cart.js (4899b), client/src/utils/analytics.js (4111b), client/src/utils/auth.js (840b), client/src/utils/constants.js (753b), client/src/utils/escape-html.js (571b), client/src/utils/event-bus.js (1129b), client/src/utils/router.js (4038b), client/src/utils/seo.js (5577b), client/src/utils/storage.js (2022b), cloud-functions/api/_helpers.js (1951b), cloud-functions/api/admin/orders.js (1919b), cloud-functions/api/admin/products.js (4890b), cloud-functions/api/admin/stats.js (2655b), cloud-functions/api/admin/users.js (1603b), cloud-functions/api/ai/chat-stream.js (6956b), cloud-functions/api/auth/register.js (2853b), cloud-functions/api/order/cancel.js (2598b), cloud-functions/api/order/create.js (3144b), cloud-functions/api/order/detail.js (2391b), cloud-functions/api/order/transition.js (6357b), cloud-functions/api/pay/close.js (2190b), cloud-functions/api/pay/create-order.js (3295b), cloud-functions/api/pay/query.js (1676b), cloud-functions/api/pay/wx-notify.js (9572b), cloud-functions/cron/order-cron.js (5389b), cloud-functions/middleware/audit.js (3437b), cloud-functions/middleware/tenant-context.js (2944b), cloud-functions/utils/db.js (4368b), cloud-functions/utils/order-state-machine.js (5656b), CONTRIBUTING.md (1590b), db/migrations/002_order_logs.sql (1555b), db/migrations/003_rollback.sql (921b), db/migrations/003_tenant_isolation.sql (3273b), docs/plans/phase-4a-plan.md (5210b), docs/plans/phase-4b-plan.md (3040b), edge-functions/_middleware.js (603b), edge-functions/api/admin/tenants.js (6492b), edge-functions/api/ai/history.js (2712b), edge-functions/api/analytics/event.js (2169b), edge-functions/api/auth/login.js (4353b), edge-functions/api/auth/logout.js (2680b), edge-functions/api/auth/me.js (2209b), edge-functions/api/auth/refresh.js (3653b), edge-functions/api/cart/add.js (2179b), edge-functions/api/cart/get.js (1302b), edge-functions/api/cart/remove.js (2037b), edge-functions/api/cart/update.js (2276b), edge-functions/api/orders/[id].js (2284b), edge-functions/api/orders/list.js (2604b), edge-functions/api/products/[id].js (2507b), edge-functions/api/products/categories.js (1101b), edge-functions/api/products/list.js (3320b), edge-functions/api/sitemap.xml.js (1961b), edge-functions/middleware/rbac.js (2867b), edge-functions/utils/rate-limit.js (1979b), index.html (3973b), middleware.js (495b), MIGRATION.md (2372b), package-lock.json (1197b), package.json (697b), packages/admin/package.json (409b), packages/admin/src/index.js (725b), packages/payment/package.json (421b), packages/...","readmeExcerpt":"Skill: 建站骨架 (EdgeOne Pages) Owner: liuboacean Summary: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 Tags: ai-chat:3.2.1, ecommerce:3.2.1, edgeone:3.2.1, edgeone-pages:3.0.6, full-stack:3.0.6, fullstack:3.2.1, latest:3.2.1, saas:3.2.1, web-development:3.0.6, website-scaffold:3.2.1 Version history: v3.2.1 | 2026-05-28T01:57:33.762Z | user v3.2.1: 重新发布以触发ClawScan审核。新增HttpOnly Cookie鉴权(","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"一次设计，无限复用 = 5 个模块 × 3 个场景 × 1 个部署平台"},{"language":"text","snippet":"┌──────────────────────────────────────────────────────────────┐\n│  Platform Middleware（middleware.js）                        │\n│  ① CORS 预检（OPTIONS）                                     │\n│  ② CSP Header 注入                                          │\n│  ③ 轻量 Bearer 检查（公开路径放行）                           │\n│  ④ 支付回调 IP 白名单 → 直接 return，不进 Edge Middleware     │\n└──────────────────────────────────────────────────────────────┘\n                              ↓（非回调路径）\n┌──────────────────────────────────────────────────────────────┐\n│  Edge Functions Middleware（V8 + KV）                      │\n│  ⑤ JWT 详细校验（crypto.subtle）                             │\n│  ⑥ KV session 验证                                          │\n│  ⑦ KV 限流计数器（滑动窗口）                                   │\n└──────────────────────────────────────────────────────────────┘"},{"language":"text","snippet":"website-skeleton/\n├── SKILL.md                    # 本文件，Skill 核心指令\n│\n├── templates/                  # 场景预设模板\n│   ├── e-commerce.json         # 🛒 电商场景\n│   ├── ai-assistant.json       # 🤖 AI 助手场景\n│   └── saas-admin.json         # 📊 SaaS 管理后台场景\n│\n├── sharing/                    # 跨运行时共享（构建时同步）\n│   ├── types.ts               # User/Product/Cart/Order/AISession 接口\n│   ├── constants.ts           # OrderStatus/UserRole/APIPaths 枚举\n│   ├── validators.ts           # 共享输入校验\n│   └── kv-keys.ts             # KV key 命名（含租户前缀占位）\n│\n├── client/                     # 前端 SPA\n│   ├── index.html\n│   └── src/\n│       ├── app.js             # 启动 + History API 路由\n│       ├── utils/\n│       │   ├── event-bus.js   # 全局事件总线（P0）\n│       │   ├── router.js      # History API 路由 + AuthGuard\n│       │   ├── escape-html.js # XSS 防护\n│       │   └── storage.js      # localStorage 封装\n│       ├── services/\n│       │   ├── api.js          # 统一客户端 + 拦截器\n│       │   ├── auth.js         # 内存 AuthService\n│       │   ├── cart.js         # 双模式购物车\n│       │   └── ai.js           # SSE 流式 AI\n│       └── components/         # 组件清单\n│\n├── middleware.js               # Platform Middleware\n│\n├── db/                         # 数据库迁移\n│   ├── migrations/\n│   │   └── 001_init.sql        # 建表脚本\n│   └── seed.sql                # 测试数据\n│\n├── docs/\n│   └── env-vars.md             # 环境变量矩阵\n│\n├── edge-functions/             # Edge Functions（V8 + KV）\n│   ├── _middleware.js          # JWT 校验 + KV session + 限流\n│   ├── api/\n│   │   ├── auth/login.js       # JWT 签发（Cookie） + KV session\n│   │   ├── auth/me.js          # KV session 读取\n│   │   ├── auth/refresh.js     # RT 轮换（KV version 乐观锁）\n│   │   ├── auth/logout.js      # 清除 Cookie + KV session\n│   │   ├── internal/idempotency.js  # Edge 原子幂等锁\n│   │   ├── products/list.js   # KV 缓存 + Cloud D1 回源\n│   │   ├── products/[id].js\n│   │   ├── products/categories.js\n│   │   ├── cart/*.js           # KV 购物车\n│   │   ├── orders/list.js      # D1 订单读取\n│   │   ├── orders/[id].js\n│   │   └── "},{"language":"text","snippet":"Access Token：短期 JWT（15min）+ HttpOnly Cookie（Secure + SameSite=Strict）\nRefresh Token：7天 TTL，存 KV rt:{userId}:meta（含 version）\n算法：Phase 1 用 HS256 + 短期 TTL，Phase 2 迁移 RS256"},{"language":"javascript","snippet":"// edge-functions/api/auth/refresh.js\nexport async function onRequest(context) {\n  const { RT } = await getTokens(context.request);\n  const { KV } = context.env;\n  const payload = parseJWT(RT);\n  const userId = payload.sub;\n  if (!userId) return new Response('Invalid', { status: 401 });\n\n  const current = await KV.get(`rt:${userId}:meta`);\n  const { version: oldVersion, token: oldToken } = JSON.parse(current || '{\"version\":0,\"token\":\"\"}');\n\n  if (oldToken !== RT) {\n    return new Response('Token already rotated', { status: 409 });\n  }\n\n  const newVersion = oldVersion + 1;\n  const newToken = signRT(userId, newVersion);\n\n  const ok = await KV.put(\n    `rt:${userId}:meta`,\n    JSON.stringify({ version: newVersion, token: newToken }),\n    { expirationTtl: 604800 }\n  );\n\n  if (!ok) return new Response('Concurrent rotation', { status: 409 });\n\n  return new Response(JSON.stringify({ refreshToken: newToken }), {\n    headers: { 'Content-Type': 'application/json' }\n  });\n}"},{"language":"text","snippet":"未登录：localStorage（30d TTL 自动清理）\n登录时：localStorage → 服务端 KV（syncOnLogin()）\n已登录：服务端 KV（唯一数据源）"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: 建站骨架 (EdgeOne Pages)\ndescription: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。\nversion: \"3.2.1\"\ncategory: openclaw-imports\ntags:\n  - edgeone\n  - website\n  - ecommerce\n  - deploy\ntriggers:\n  - 帮我建网站\n  - 建一个电商网站\n  - 做 AI 客服站\n  - 建管理后台\n  - EdgeOne Pages 建站\n  - 搭建网站\n  - 做个网站\n  - 生成网站\n  - 建站\n  - 创建网站\n  - 我要建站\n  - 商城网站\n  - 下单支付网站\n  - 全栈网站\n  - build a website\n  - create a full-stack app\n  - e-commerce website\n  - 建电商\n  - 网站骨架\n  - 部署网站\nenv:\n  JWT_SECRET:\n    description: JWT 签名密钥（HS256），Edge + Cloud 共享\n    required: true\n    secret: true\n  AI_API_KEY:\n    description: AI 模型调用 API Key（仅 AI 栈需要）\n    required: false\n    secret: true\n  WX_APPID:\n    description: 微信支付 AppID（仅电商栈需要）\n    required: false\n    secret: true\n  WX_MCHID:\n    description: 微信支付商户号（仅电商栈需要）\n    required: false\n    secret: true\n  DATABASE_URL:\n    description: D1 数据库绑定\n    required: false\n    secret: true\n---\n\n# 建站 Skill — EdgeOne Pages 全栈网站骨架\n\n> **版本：** 3.2.0 · **日期：** 2026-05-26 · **工程完缮（SPA详情页 + 227项测试 + 迁移/脚本/模板联动）**\n> **一句话描述：** 用户说一句话，AI 生成完整前后端网站，自动部署到 EdgeOne Pages。\n\n---\n\n## 一、核心设计理念\n\n```\n一次设计，无限复用 = 5 个模块 × 3 个场景 × 1 个部署平台\n```\n\n将\"建站\"拆解为 **Layer 0 基础设施** + **Layer 1 能力栈** + **Layer 2 可选增强**：\n\n| 层级 | 内容 | 性质 |\n|------|------|------|\n| **Layer 0**（Core） | SPA 骨架 + Auth + Middleware + EventBus | 必选，不可裁剪 |\n| **Layer 1**（Stack） | 🛒 电商栈 · 🤖 AI 栈 · 📊 管理栈 | 按需组合，互不依赖 |\n| **Layer 2**（Addon） | SEO · Analytics · i18n | 可选增强 |\n\n**场景模板优先**：用户选\"电商\"、\"AI 助手\"或\"管理后台\"场景，不选模块——模块由模板自动组合。\n\n---\n\n## 二、技术架构\n\n### 2.1 EdgeOne Pages 双运行时\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│  Platform Middleware（middleware.js）                        │\n│  ① CORS 预检（OPTIONS）                                     │\n│  ② CSP Header 注入                                          │\n│  ③ 轻量 Bearer 检查（公开路径放行）                           │\n│  ④ 支付回调 IP 白名单 → 直接 return，不进 Edge Middleware     │\n└──────────────────────────────────────────────────────────────┘\n                              ↓（非回调路径）\n┌──────────────────────────────────────────────────────────────┐\n│  Edge Functions Middleware（V8 + KV）                      │\n│  ⑤ JWT 详细校验（crypto.subtle）                             │\n│  ⑥ KV session 验证                                          │\n│  ⑦ KV 限流计数器（滑动窗口）                                   │\n└──────────────────────────────────────────────────────────────┘\n```\n\n### 2.2 运行时职责边界\n\n| 运行时 | 存储 | 职责 | 说明 |\n|--------|------|------|------|\n| **Edge Functions**（V8） | KV | Auth 登录/me、Products 公开读、Cart、Orders 读、AI History 读、幂等锁 | 延迟敏感、无密钥 |\n| **Cloud Functions**（Node） | D1 | Auth 注册/bcrypt、Payment 创建/回调、Admin CRUD、Orders 创建/取消、AI SSE 流 | 密钥操作、复杂事务 |\n\n> ⚠️ **平台约束（EdgeOne Pages）：**\n> - KV 仅 Edge Functions 可用，Cloud Functions 无法访问\n> - Cloud Functions 目录名必须为 `cloud-functions/`\n> - bcrypt 必须在 Cloud Functions 中执行\n\n### 2.3 分层目录结构\n\n```\nwebsite-skeleton/\n├── SKILL.md                    # 本文件，Skill 核心指令\n│\n├── templates/                  # 场景预设模板\n│   ├── e-commerce.json         # 🛒"},{"path":"README.md","content":"<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/version-v3.2.0-blue?style=flat-square\" alt=\"version\">\n  <img src=\"https://img.shields.io/badge/EdgeOne_Pages-ready-brightgreen?style=flat-square\" alt=\"EdgeOne Pages\">\n  <img src=\"https://img.shields.io/badge/license-MIT_No_Attribution-green?style=flat-square\" alt=\"license\">\n  <img src=\"https://img.shields.io/badge/status-stable-brightgreen?style=flat-square\" alt=\"status\">\n  <img src=\"https://img.shields.io/badge/Phase_5_工程完缮-%E2%9C%85-success?style=flat-square\" alt=\"Phase 5\">\n  <a href=\"https://website-skeleton-demo-8mv8fitk.edgeone.cool?eo_token=587119f1f48590b7dded3b65f6001030&eo_time=1779867268\"><img src=\"https://img.shields.io/badge/demo-live-purple?style=flat-square\" alt=\"Demo\"></a>\n</p>\n\n# Website Skeleton Skill\n\n> **One sentence to build a full-stack website.** Generate auth, payments, AI chat, and admin panel — deploy to EdgeOne Pages.\n\n[English](#english) · [中文](#chinese) · [Demo](https://website-skeleton-demo-8mv8fitk.edgeone.cool?eo_token=587119f1f48590b7dded3b65f6001030&eo_time=1779867268)\n\n---\n\n## English\n\n**website-skeleton-skill** is an EdgeOne Pages skill that scaffolds full-stack websites from a single prompt. It combines 5 reusable modules (Auth, Cart, Payment, AI Chat, Admin) across 3 scene templates (E-commerce, AI Assistant, SaaS Admin).\n\n### Quick Start\n\n```bash\nnpm install -g edgeone\nedgeone login --site china\nedgeone pages deploy -n my-site\n```\n\n\n### Features\n\n| Feature | Status | Description |\n|---------|--------|-------------|\n| 🔐 Auth | ✅ | JWT dual-track (RS256 + HS256), bcrypt, KV Session, RT rotation |\n| 🛒 Cart & Payment | ✅ | WeChat/Alipay, Edge idempotency lock, multi-tenant KV cart |\n| 🤖 AI Chat | ✅ | SSE streaming via Cloud Functions, KV history, rate-limited |\n| 📊 Admin Panel | ✅ | RBAC (superadmin/tenant_admin/user), CRUD, stats, audit logs |\n| 🏢 Multi-tenant | ✅ | Phase 4A: {tenant} forced SQL, KV prefix isolation, quotas |\n| 📦 npm Packages | ✅ | Phase 4B: @website-skeleton/{shared,payment,admin} |\n| 🌍 i18n | ✅ | Chinese + English, extensible |\n| 🔍 SEO | ✅ | JSON-LD structured data, meta tags |\n| 📈 Analytics | ✅ | KV-based event tracking, opt-out support |\n\n### Demo\n\n[Live Demo →](https://website-skeleton-demo-8mv8fitk.edgeone.cool?eo_token=587119f1f48590b7dded3b65f6001030&eo_time=1779867268)\n\nDeploy your own with a single command: `edgeone pages deploy -n my-site`\n\n---\n\n## Chinese\n\n# website-skeleton-skill\n\n> 一句话描述需求 → AI 生成完整前后端网站 → 自动部署到 EdgeOne Pages 全球 CDN。\n\n**版本：** v3.2.0 · **Phase 5（工程完缮）已全部完成**\n\n---\n\n## 目录\n\n- [一、快速开始](#一快速开始)\n- [二、架构](#二架构)\n- [三、功能特性](#三功能特性)\n- [四、Phase 4 变更概要](#四phase-4-变更概要)\n- [五、目录结构](#五目录结构)\n- [六、安全设计](#六安全设计)\n- [七、评审历程](#七评审历程)\n- [八、版本历史](#八版本历史)\n- [九、License](#九license)\n\n---\n\n## 一、快速开始\n\n```bash\n# 1. 安装 EdgeOne CLI\nnpm install -g edgeone\n\n# 2. 登录\nedgeone login --site china\n\n# 3. 部署（交互式引导）\nedgeone pages deploy -n my-site\n\n# 4. 按提示选择场景模板、填写配置、执行数据库迁移\n```\n\n### 场景模板\n\n| 模板 | 适用场景 | 包含模块 | 快速命令 |\n|------|---------|----"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn73qbrbqs4s8t2nh8pm22wxbd84vm7r\",\n  \"slug\": \"website-skeleton\",\n  \"version\": \"3.2.1\",\n  \"publishedAt\": 1779933453762\n}"},{"path":"references/admin-module.md","content":"# Admin 管理后台模块参考文档\n\n> **版本：** v2.2 · **Phase：** Layer 1（管理栈）\n> **职责：** RBAC 权限体系、商品/订单/用户 CRUD、运营统计、审计日志\n\n---\n\n## 一、RBAC 权限体系\n\n### 角色定义\n\n```javascript\n// sharing/constants.js\nexport const UserRole = {\n  USER:   'user',     // 普通用户：下单、查看自己的订单\n  ADMIN:  'admin',    // 管理员：全站 CRUD\n  MANAGER:'manager',  // 运营：订单管理、商品上下架\n};\n```\n\n### 权限矩阵\n\n| 操作 | user（本人） | manager | admin |\n|------|------------|---------|-------|\n| 查看自己的订单 | ✅ | ✅ | ✅ |\n| 管理任意订单 | ❌ | ✅ | ✅ |\n| 商品上架/下架 | ❌ | ✅ | ✅ |\n| 修改商品价格 | ❌ | ❌ | ✅ |\n| 用户管理 | ❌ | ❌ | ✅ |\n| 查看运营统计 | ❌ | ✅ | ✅ |\n| 审计日志 | ❌ | ❌ | ✅ |\n| 系统设置 | ❌ | ❌ | ✅ |\n\n---\n\n## 二、Admin Guard 中间件\n\n```javascript\n// cloud-functions/utils/admin-guard.js\n\n/**\n * 验证管理员权限\n * @param {Request} request\n * @param {Object} env\n * @param {string[]} allowedRoles - 允许的角色，如 ['admin', 'manager']\n * @returns {{ userId, role } | Response} 成功返回用户信息，失败返回 Response\n */\nexport async function adminGuard(request, env, allowedRoles = ['admin']) {\n  const authHeader = request.headers.get('Authorization') || '';\n  const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;\n\n  if (!token) {\n    return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });\n  }\n\n  const payload = await verifyJWT(token, env);\n  if (!payload) {\n    return new Response(JSON.stringify({ error: 'Invalid token' }), { status: 401 });\n  }\n\n  if (!allowedRoles.includes(payload.role)) {\n    return new Response(JSON.stringify({ error: 'Forbidden: insufficient permissions' }), { status: 403 });\n  }\n\n  return { userId: payload.sub, role: payload.role };\n}\n```\n\n---\n\n## 三、商品管理 CRUD\n\n### 3.1 列表查询（支持分页 + 筛选）\n\n```javascript\n// cloud-functions/api/admin/products.js\n\nexport async function onRequest(request, env) {\n  // Admin Guard\n  const auth = await adminGuard(request, env);\n  if (auth instanceof Response) return auth;\n\n  const url = new URL(request.url);\n  const page = Math.max(1, parseInt(url.searchParams.get('page') || '1'));\n  const pageSize = Math.min(100, parseInt(url.searchParams.get('pageSize') || '20'));\n  const offset = (page - 1) * pageSize;\n  const category = url.searchParams.get('category');\n  const status = url.searchParams.get('status'); // active / inactive\n  const keyword = url.searchParams.get('keyword');\n\n  const pool = new Pool({ connectionString: env.DATABASE_URL });\n\n  // WHERE 条件构建\n  const conditions = [];\n  const params = [];\n  if (category) { conditions.push('category_id = ?'); params.push(category); }\n  if (status) { conditions.push('status = ?'); params.push(status); }\n  if (keyword) { conditions.push('name LIKE ?'); params.push(`%${keyword}%`); }\n  const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n\n  const whereClause = where || 'WHERE 1=1';\n\n  const [rows] = await pool.query(\n    `SELECT id, name, category_id, price, stock, status, version, created_at\n     FROM products ${whereClause}\n     ORDER BY id DESC\n     LIMIT ? OFFSET ?`,\n    [...params, pageSize, offset]\n  );\n\n  const [[{ total }]] = await pool.que"},{"path":"references/ai-chat-module.md","content":"# AI Chat 模块参考文档\n\n## 一、架构选择\n\n**Edge Function 限制：**\n- 200ms **CPU time** 限制（不是 wall clock time）\n- `fetch()` 到外部 AI API 的 I/O 等待**不计入** CPU time\n- **无 `waitUntil`**：异步写 KV 无法保证在响应发送前完成\n\n**结论：** 流式 SSE 主力实现在 **Cloud Functions**，历史读取在 **Edge Functions**。\n\n## 二、SSE 实现方案 B（前端中转历史）\n\n```\n前端\n  ↓ GET /api/ai/history（Edge，KV 读取）\n  ← 拿到 history JSON\n\n  ↓ 建立 SSE 连接 /api/ai/chat-stream（Cloud）\n  ← 带 history context 参数\n\nCloud SSE 流式响应\n  ↓ 完成后异步写 KV（不阻塞响应）\n```\n\n```javascript\n// Cloud Function: cloud-functions/api/ai/chat-stream.js\nexport async function onRequest(request, env) {\n  const { userId } = await auth(request, env);\n  const url = new URL(request.url);\n  const historyParam = url.searchParams.get('history');\n  const history = historyParam ? JSON.parse(historyParam) : [];\n\n  const stream = new ReadableStream({\n    async start(controller) {\n      const encoder = new TextEncoder();\n\n      async function sendEvent(type, data) {\n        controller.enqueue(encoder.encode(`event: ${type}\\ndata: ${JSON.stringify(data)}\\n\\n`));\n      }\n\n      try {\n        await sendEvent('status', { status: 'thinking' });\n\n        const response = await fetch('https://api.example.com/chat', {\n          method: 'POST',\n          headers: {\n            'Authorization': `Bearer ${env.AI_API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({ messages: history, stream: true }),\n        });\n\n        const reader = response.body.getReader();\n        const decoder = new TextDecoder();\n\n        while (true) {\n          const { done, value } = await reader.read();\n          if (done) break;\n          const chunk = decoder.decode(value);\n          await sendEvent('message', { content: chunk });\n        }\n\n        await sendEvent('done', {});\n\n      } catch (err) {\n        await sendEvent('error', { message: err.message });\n      } finally {\n        controller.close();\n      }\n    }\n  });\n\n  return new Response(stream, {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n      'Connection': 'keep-alive',\n    }\n  });\n}\n```\n\n## 三、前端 SSE 客户端\n\n```javascript\n// client/src/services/ai.js\nimport { EventBus } from '../utils/event-bus.js';\n\nclass AIService {\n  constructor() {\n    this.es = null;\n  }\n\n  async startChatSession() {\n    // Step 1: 从 Edge KV 拿历史\n    const history = await fetch('/api/ai/history').then(r => r.json());\n\n    // Step 2: 建立 SSE，带历史 context\n    this.es = new EventSource(`/api/ai/chat-stream?history=${encodeURIComponent(JSON.stringify(history))}`);\n\n    this.es.addEventListener('message', (e) => {\n      const data = JSON.parse(e.data);\n      EventBus.emit('ai:message', { role: 'assistant', content: data.content });\n    });\n\n    this.es.addEventListener('status', (e) => {\n      EventBus.emit('ai:status', JSON.parse(e.data));\n    });\n\n    this.es.addEventListener('done', () => {\n      EventBus.emit('ai:status', { status: 'idle' });\n    });\n\n    this.es.addEventListener('error', (e) => {\n      "}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 Skill: 建站骨架 (EdgeOne Pages) Owner: liuboacean Summary: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 Tags: ai-chat:3.2.1, ecommerce:3.2.1, edgeone:3.2.1, edgeone-pages:3.0.6, full-stack:3.0.6, fullstack:3.2.1, latest:3.2.1, saas:3.2.1, web-development:3.0.6, website-scaffold:3.2.1 Version history: v3.2.1 | 2026-05-28T01:57:33.762Z | user v3.2.1: 重新发布以触发ClawScan审核。新增HttpOnly Cookie鉴权(","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1020,"uniquenessScore":49,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T05:08:28.270Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T05:08:28.270Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:40:53.184Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}