Skill: liuboacean/website-skeleton (version 3.2.1)

--- name: 建站骨架 (EdgeOne Pages) description: 一句话说需求，AI 生成完整前后端网站并自动部署到 EdgeOne Pages。支持电商栈（Auth/购物车/支付）、AI 栈（SSE 流式对话）、管理后台。 version: "3.2.1" category: openclaw-imports tags: - edgeone - website - ecommerce - deploy triggers: - 帮我建网站 - 建一个电商网站 - 做 AI 客服站 - 建管理后台 - EdgeOne Pages 建站 - 搭建网站 - 做个网站 - 生成网站 - 建站 - 创建网站 - 我要建站 - 商城网站 - 下单支付网站 - 全栈网站 - build a website - create a full-stack app - e-commerce website - 建电商 - 网站骨架 - 部署网站 env: JWT_SECRET: description: JWT 签名密钥（HS256），Edge + Cloud 共享 required: true secret: true AI_API_KEY: description: AI 模型调用 API Key（仅 AI 栈需要） required: false secret: true WX_APPID: description: 微信支付 AppID（仅电商栈需要） required: false secret: true WX_MCHID: description: 微信支付商户号（仅电商栈需要） required: false secret: true DATABASE_URL: description: D1 数据库绑定 required: false secret: true --- # 建站 Skill — EdgeOne Pages 全栈网站骨架 > **版本：** 3.2.0 · **日期：** 2026-05-26 · **工程完缮（SPA详情页 + 227项测试 + 迁移/脚本/模板联动）** > **一句话描述：** 用户说一句话，AI 生成完整前后端网站，自动部署到 EdgeOne Pages。 --- ## 一、核心设计理念 ``` 一次设计，无限复用 = 5 个模块 × 3 个场景 × 1 个部署平台 ``` 将"建站"拆解为 **Layer 0 基础设施** + **Layer 1 能力栈** + **Layer 2 可选增强**： | 层级 | 内容 | 性质 | |------|------|------| | **Layer 0**（Core） | SPA 骨架 + Auth + Middleware + EventBus | 必选，不可裁剪 | | **Layer 1**（Stack） | 🛒 电商栈 · 🤖 AI 栈 · 📊 管理栈 | 按需组合，互不依赖 | | **Layer 2**（Addon） | SEO · Analytics · i18n | 可选增强 | **场景模板优先**：用户选"电商"、"AI 助手"或"管理后台"场景，不选模块——模块由模板自动组合。 --- ## 二、技术架构 ### 2.1 EdgeOne Pages 双运行时 ``` ┌──────────────────────────────────────────────────────────────┐ │ Platform Middleware（middleware.js） │ │ ① CORS 预检（OPTIONS） │ │ ② CSP Header 注入 │ │ ③ 轻量 Bearer 检查（公开路径放行） │ │ ④ 支付回调 IP 白名单 → 直接 return，不进 Edge Middleware │ └──────────────────────────────────────────────────────────────┘ ↓（非回调路径） ┌──────────────────────────────────────────────────────────────┐ │ Edge Functions Middleware（V8 + KV） │ │ ⑤ JWT 详细校验（crypto.subtle） │ │ ⑥ KV session 验证 │ │ ⑦ KV 限流计数器（滑动窗口） │ └──────────────────────────────────────────────────────────────┘ ``` ### 2.2 运行时职责边界 | 运行时 | 存储 | 职责 | 说明 | |--------|------|------|------| | **Edge Functions**（V8） | KV | Auth 登录/me、Products 公开读、Cart、Orders 读、AI History 读、幂等锁 | 延迟敏感、无密钥 | | **Cloud Functions**（Node） | D1 | Auth 注册/bcrypt、Payment 创建/回调、Admin CRUD、Orders 创建/取消、AI SSE 流 | 密钥操作、复杂事务 | > ⚠️ **平台约束（EdgeOne Pages）：** > - KV 仅 Edge Functions 可用，Cloud Functions 无法访问 > - Cloud Functions 目录名必须为 `cloud-functions/` > - bcrypt 必须在 Cloud Functions 中执行 ### 2.3 分层目录结构 ``` website-skeleton/ ├── SKILL.md # 本文件，Skill 核心指令 │ ├── templates/ # 场景预设模板 │ ├── e-commerce.json # 🛒 电商场景 │ ├── ai-assistant.json # 🤖 AI 助手场景 │ └── saas-admin.json # 📊 SaaS 管理后台场景 │ ├── sharing/ # 跨运行时共享（构建时同步） │ ├── types.ts # User/Product/Cart/Order/AISession 接口 │ ├── constants.ts # OrderStatus/UserRole/APIPaths 枚举 │ ├── validators.ts # 共享输入校验 │ └── kv-keys.ts # KV key 命名（含租户前缀占位） │ ├── client/ # 前端 SPA │ ├── index.html │ └── src/ │ ├── app.js # 启动 + History API 路由 │ ├── utils/ │ │ ├── event-bus.js # 全局事件总线（P0） │ │ ├── router.js # History API 路由 + AuthGuard │ │ ├── escape-html.js # XSS 防护 │ │ └── storage.js # localStorage 封装 │ ├── services/ │ │ ├── api.js # 统一客户端 + 拦截器 │ │ ├── auth.js # 内存 AuthService │ │ ├── cart.js # 双模式购物车 │ │ └── ai.js # SSE 流式 AI │ └── components/ # 组件清单 │ ├── middleware.js # Platform Middleware │ ├── db/ # 数据库迁移 │ ├── migrations/ │ │ └── 001_init.sql # 建表脚本 │ └── seed.sql # 测试数据 │ ├── docs/ │ └── env-vars.md # 环境变量矩阵 │ ├── edge-functions/ # Edge Functions（V8 + KV） │ ├── _middleware.js # JWT 校验 + KV session + 限流 │ ├── api/ │ │ ├── auth/login.js # JWT 签发（Cookie） + KV session │ │ ├── auth/me.js # KV session 读取 │ │ ├── auth/refresh.js # RT 轮换（KV version 乐观锁） │ │ ├── auth/logout.js # 清除 Cookie + KV session │ │ ├── internal/idempotency.js # Edge 原子幂等锁 │ │ ├── products/list.js # KV 缓存 + Cloud D1 回源 │ │ ├── products/[id].js │ │ ├── products/categories.js │ │ ├── cart/*.js # KV 购物车 │ │ ├── orders/list.js # D1 订单读取 │ │ ├── orders/[id].js │ │ └── ai/history.js # KV 读取 AI 会话历史 │ └── utils/ │ ├── kv-helper.js │ ├── jwt-helper.js # crypto.subtle HS256 │ ├── rate-limit.js # KV 滑动窗口限流 │ └── response.js │ ├── cloud-functions/ # Cloud Functions（Node.js） │ ├── api/ │ │ ├── auth/register.js # bcrypt cost=12 + D1 │ │ ├── pay/create-order.js # 微信/支付宝预下单 │ │ ├── pay/wx-notify.js # Edge 幂等锁 → 业务处理 │ │ ├── pay/ali-notify.js │ │ ├── pay/query.js │ │ ├── pay/close.js │ │ ├── admin/products.js # D1 CRUD（含 version 乐观锁） │ │ ├── admin/orders.js # D1 查询 │ │ ├── admin/users.js # D1 CRUD │ │ ├── admin/stats.js # D1 聚合统计 │ │ ├── order/create.js # SELECT FOR UPDATE + 事务 + 指数退避 │ │ ├── order/detail.js │ │ ├── order/cancel.js # 状态机 + version 校验 │ │ └── ai/chat-stream.js # SSE 流式（主力实现） │ └── utils/ │ ├── db.js # D1 数据库工具（{tenant} 强制注入） │ ├── payment-sdk.js # 微信V3/支付宝 SDK 封装 │ ├── admin-guard.js │ └── notification-hooks.js # 通知钩子空壳 │ ├── references/ # 能力参考文档 │ ├── auth-module.md # ✅ JWT RS256 + HS256 兼容 + KV Session │ ├── cart-module.md │ ├── payment-module.md │ ├── ai-chat-module.md │ ├── admin-module.md # ✅ RBAC + CRUD + 运营统计 + 审计日志 │ ├── notification-module.md # Layer 2：邮件/微信/钉钉通知 │ ├── order-state-machine.md # ✅ 6状态 + 权限矩阵 + 库存联动 + 审计日志 │ ├── edge-functions.md # ✅ Edge Middleware + KV API + 限流 │ ├── cloud-functions.md # ✅ D1 事务 + bcrypt + 支付 SDK + SSE │ ├── kv-storage.md │ ├── middleware.md # ✅ Platform + Edge 双层 + CSP + 支付 bypass │ └── deployment.md # ✅ 完整部署流程 + Cron + 回滚 │ └── scripts/ ├── init-site.js # 交互式初始化（模板优先） ├── sync-sharing.js # 构建时 shared → edge/cloud 同步 └── sample-data.js ``` --- ## 三、Auth 模块（Layer 0，Core） ### API 路由 | 方法 | 路径 | 运行时 | 说明 | |------|------|--------|------| | POST | `/api/auth/login` | Edge（KV） | JWT 签发 + KV session | | GET | `/api/auth/me` | Edge（KV） | KV session 读取 | | POST | `/api/auth/refresh` | Edge（KV） | RT 轮换（version 乐观锁） | | POST | `/api/auth/logout` | Edge（KV） | 清除 Cookie + KV session | | POST | `/api/auth/register` | Cloud（D1） | bcrypt cost=12 + D1 | ### JWT 安全设计 ``` Access Token：短期 JWT（15min）+ HttpOnly Cookie（Secure + SameSite=Strict） Refresh Token：7天 TTL，存 KV rt:{userId}:meta（含 version） 算法：Phase 1 用 HS256 + 短期 TTL，Phase 2 迁移 RS256 ``` ### 【v2.1 Critical 修复】RT 并发安全 两个请求并发携带同一 RT，只有第一个能成功写入新 version，第二个收到 409 → 客户端稍等重试。 ```javascript // edge-functions/api/auth/refresh.js export async function onRequest(context) { const { RT } = await getTokens(context.request); const { KV } = context.env; const payload = parseJWT(RT); const userId = payload.sub; if (!userId) return new Response('Invalid', { status: 401 }); const current = await KV.get(`rt:${userId}:meta`); const { version: oldVersion, [REDACTED_SECRET] } = JSON.parse(current || '{"version":0,"token":""}'); if (oldToken !== RT) { return new Response('Token already rotated', { status: 409 }); } const newVersion = oldVersion + 1; const newToken = signRT(userId, newVersion); const ok = await KV.put( `rt:${userId}:meta`, JSON.stringify({ version: newVersion, [REDACTED_SECRET] }), { expirationTtl: 604800 } ); if (!ok) return new Response('Concurrent rotation', { status: 409 }); return new Response(JSON.stringify({ refreshToken: newToken }), { headers: { 'Content-Type': 'application/json' } }); } ``` --- ## 四、Cart 模块（Layer 1，电商栈） **双模式同步：** ``` 未登录：localStorage（30d TTL 自动清理） 登录时：localStorage → 服务端 KV（syncOnLogin()） 已登录：服务端 KV（唯一数据源） ``` --- ## 五、Payment 模块（Layer 1，电商栈） ### 独立回调路径 ``` /api/pay/wx-notify ← 微信支付回调（IP 白名单后直接 return，不进 Edge Middleware） /api/pay/ali-notify ← 支付宝回调（独立路径） ``` ### 【v2.1 Critical 修复】支付幂等原子锁 微信支付平台会在回调超时后重试（最长 72h），KV 查→判→写三步非原子。解决方案：Edge Function `putIfNotExists` 原子幂等锁。 ```javascript // ===== Edge Function（唯一可访问 KV 的路径）===== // edge-functions/api/internal/idempotency.js export async function onRequest(context) { const { KV } = context.env; const { out_trade_no, callback_id } = await context.request.json(); const acquired = await KV.putIfNotExists( `pay:idempotency:${out_trade_no}`, callback_id, { expirationTtl: 86400 } // 24h < 微信重试窗口 72h ); return new Response(JSON.stringify({ acquired }), { status: 200 }); } // ===== Cloud Function（微信回调处理）===== // cloud-functions/api/pay/wx-notify.js export async function onRequest(request, env) { const rawBody = await request.text(); if (!await verifyWechatSignature(rawBody, env.WX_MCH_SECRET)) return new Response('FAIL', { status: 401 }); const { out_trade_no, transaction_id, trade_state } = JSON.parse(rawBody); const { acquired } = await fetch(`${env.EDGE_BASE}/api/internal/idempotency`, { method: 'POST', body: JSON.stringify({ out_trade_no, callback_id: transaction_id }) }).then(r => r.json()); if (!acquired) return new Response('SUCCESS'); // 幂等跳过，但返回 SUCCESS 止重试 if (trade_state === 'SUCCESS') await processPayment(out_trade_no, transaction_id, env); return new Response('SUCCESS'); } ``` --- ## 六、Order 创建原子性（v2.1 Critical 修复） 高并发下，`UPDATE ... WHERE stock >= ?` 可能同时通过检查导致超卖。解决方案：`SELECT FOR UPDATE` + 乐观锁 + D1 CHECK 约束。 ```javascript // cloud-functions/api/order/create.js export async function onRequest(request, env) { const { userId } = await auth(request, env); const { productId, quantity } = await request.json(); const pool = await getPool(env.DATABASE_URL); let attempt = 0; while (attempt < 3) { attempt++; try { await pool.beginTransaction(); // ① SELECT FOR UPDATE：锁定商品行（持有行锁期间其他事务阻塞） const [rows] = await pool.query( 'SELECT id, stock, price, version FROM products WHERE id = ? FOR UPDATE', [productId] ); if (!rows.length) { await pool.rollback(); return 404; } const product = rows[0]; // ② 持有行锁期间校验库存（无竞态） if (product.stock < quantity) { await pool.rollback(); return { error: '库存不足', available: product.stock }; } // ③ 乐观锁更新（双重保障） const [updateResult] = await pool.query( 'UPDATE products SET stock = stock - ?, version = version + 1 WHERE id = ? AND version = ?', [quantity, productId, product.version] ); if (updateResult.affectedRows === 0) { await pool.rollback(); return { error: '并发冲突，请重试' }; } // ④ 创建订单（同一事务内） const orderNo = generateOrderNo(); await pool.query( `INSERT INTO orders (order_no, out_trade_no, user_id, product_id, qty, amount, status, created_at) VALUES (?, ?, ?, ?, ?, ?, 'PENDING', NOW())`, [orderNo, `WX_${orderNo}`, userId, productId, quantity, product.price * quantity] ); await pool.commit(); // ⑤ 事务成功后，异步调用微信统一下单（不在事务内） const payment = await createPayment(orderNo, product.price * quantity, env); return { orderNo, payment }; } catch (err) { await pool.rollback(); if (isRetryable(err) && attempt < 3) { await sleep(100 * Math.pow(2, attempt - 1)); // 指数退避 continue; } return { error: '创建失败，请重试' }; } } } function isRetryable(err) { return err.code === 'ER_LOCK_DEADLOCK' || err.code === 'ER_LOCK_WAIT_TIMEOUT'; } ``` --- ## 七、KV 分层查询策略 EdgeOne Pages KV **不支持复合查询**，按以下策略分层： | 场景 | KV 层（Edge） | D1 层（Cloud） | |------|-------------|---------------| | 单商品读取 | ✅ KV 缓存 | — | | 商品列表（无筛选） | ✅ 缓存第1页 | — | | 分类+价格区间筛选 | — | ✅ Cloud D1 | | 搜索关键词 | — | ✅ Cloud D1 | | AI 会话历史（单用户） | ✅ KV | — | | 订单统计（多条件聚合） | — | ✅ Cloud D1 | --- ## 八、AI Chat 模块（Layer 1，AI 栈） **Cloud Functions SSE 实现（Edge 无法使用 waitUntil）：** ``` 前端 → GET /api/ai/history（Edge，KV 读取）→ 拿到历史上下文 → SSE 连接 /api/ai/chat-stream（Cloud）→ 带历史 context → Cloud 流式响应 + 异步写 KV 保存历史 ``` --- ## 九、Admin 模块（Layer 1，管理栈） **RBAC 权限体系：** ``` role: user → 购物车、下单、查看自己的订单 role: admin → 商品 CRUD、订单管理、用户管理、运营统计 ``` --- ## 十、数据库 Schema ```sql -- db/migrations/001_init.sql CREATE TABLE users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role ENUM('user','admin') DEFAULT 'user', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE products ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL, -- 服务端唯一价格来源 stock INT UNSIGNED NOT NULL DEFAULT 0, category_id INT UNSIGNED, status ENUM('active','inactive') DEFAULT 'active', version INT UNSIGNED DEFAULT 1, -- 乐观锁版本号 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT chk_stock_positive CHECK (stock >= 0) ); CREATE TABLE orders ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(64) UNIQUE NOT NULL, out_trade_no VARCHAR(128) UNIQUE, user_id BIGINT UNSIGNED NOT NULL, total DECIMAL(10,2) NOT NULL, status ENUM('pending','paid','shipped','cancelled','refunded') DEFAULT 'pending', paid_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE order_items ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_id BIGINT UNSIGNED NOT NULL, product_id BIGINT UNSIGNED NOT NULL, qty INT UNSIGNED NOT NULL, price DECIMAL(10,2) NOT NULL, -- 快照价格 FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (product_id) REFERENCES products(id) ); CREATE TABLE admin_logs ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, admin_id BIGINT UNSIGNED NOT NULL, action VARCHAR(64) NOT NULL, target VARCHAR(128), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_products_category ON products(category_id); CREATE INDEX idx_products_status ON products(status); CREATE INDEX idx_orders_user ON orders(user_id); CREATE INDEX idx_orders_status ON orders(status); CREATE INDEX idx_orders_created ON orders(created_at); ``` --- ## 十一、环境变量矩阵 | 环境变量 | 必填 | 用于 | 运行时 | |---------|------|------|--------| | `JWT_SECRET` | ✅ | JWT 签名（HS256） | Edge + Cloud | | `AI_API_KEY` | ✅（AI栈） | AI 模型调用 | Cloud | | `WX_APPID` | ✅（电商栈） | 微信支付 AppID | Cloud | | `WX_MCHID` | ✅（电商栈） | 微信支付商户号 | Cloud | | `WX_API_KEY` | ✅（电商栈） | 微信支付 APIv3 密钥 | Cloud | | `WX_CERT_PATH` | ✅（电商栈） | 微信支付证书路径 | Cloud | | `ALI_APP_ID` | ✅（电商栈） | 支付宝 AppID | Cloud | | `ALI_PRIVATE_KEY` | ✅（电商栈） | 支付宝私钥 | Cloud | | `DATABASE_URL` | ✅（电商+管理） | D1 数据库绑定 | Cloud | | `EDGE_BASE` | ✅（电商栈） | Edge Function 内部网关地址 | Cloud | --- ## 十二、初始化工作流 ``` Step 1: 选择建站类型 [1] 🛒 快速电商站（推荐） [2] 🤖 AI 客服站 [3] 📊 SaaS 管理后台 [4] ⚙️ 自定义模块组合 Step 2: 确认预填 / 模块选择 Step 3: 填写基本信息（站点名、域名） Step 4: 密钥配置（从 env-vars.md 模板读取，EdgeOne Pages 环境变量注入） Step 5: 执行 db/migrations/001_init.sql（自动或手动） Step 6: 生成代码 → edgeone deploy → 返回访问 URL ``` --- ## 十三、安全检查清单 ### 🔴 P0（上线前必须完成） - [x] 支付幂等：Edge 原子 `putIfNotExists` 锁 - [x] 订单超卖：`SELECT FOR UPDATE` + D1 事务 + CHECK 约束 - [x] RT 并发安全：KV version 乐观锁（409 重试） - [x] KV 复合查询：分层策略（KV 缓存 / D1 复杂查询） - [x] 支付回调路径 Platform Middleware 直接 return - [x] 金额服务端 D1 计算，前端永不传 price - [x] bcrypt cost ≥ 12（Cloud Functions 中） ### 🟡 P1（正式版前完成） - [x] JWT 短期 Access Token（15min）+ RT 轮换（含并发安全版本号） - [x] Cookie：HttpOnly + Secure + SameSite=Strict（含 SameSite=Lax 备选方案） - [x] AI 聊天限流（KV 滑动窗口：未登录 10次/分钟，登录 60次/分钟） - [x] CSP Header（Platform Middleware 注入，含 nonce 升级路径） - [x] EventBus 401 自动跳转登录（含 redirect 回跳逻辑） - [x] Notification 钩子（Phase 2 完整适配器设计 + 事件注册机制） ### 🟢 P2（Phase 3 实现） - [x] RS256 迁移（双轨并行 HS256/RS256，30 天兼容窗口） - [x] 订单状态机（6状态 + 权限矩阵 + version 校验 + 库存联动 + 审计日志 + 定时 Cron） --- ## 十五、Phase 2 详细设计（P1/P2 实现指南） --- ### 15.1 【P1】JWT Access Token 短期化 + RT 轮换（已实现源码） 以下为 Edge Functions 完整实现，Phase 1 已集成： **JWT 签发（login.js）** — Access Token 15min + Refresh Token 7d： ```javascript // edge-functions/api/auth/login.js export async function onRequest(context) { const { email, password } = await context.request.json(); const pool = await getCloudPool(context.env.DATABASE_URL); const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]); if (!rows.length) return new Response('Unauthorized', { status: 401 }); const user = rows[0]; const ok = await bcrypt.compare(password, user.password_hash); if (!ok) return new Response('Unauthorized', { status: 401 }); const now = Math.floor(Date.now() / 1000); // Access Token：15min const accessToken = signJWT({ sub: user.id, role: user.role, type: 'access' }, 900); // Refresh Token：7d，含 version 用于乐观锁 const rtVersion = 1; const refreshToken = signRT(user.id, rtVersion); // KV 存 RT meta（用于轮换校验） await context.env.KV.put( `rt:${user.id}:meta`, JSON.stringify({ version: rtVersion, [REDACTED_SECRET] }), { expirationTtl: 604800 } ); return new Response(null, { status: 302, headers: { 'Location': '/', 'Set-Cookie': [ `at=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900`, `rt=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800` ].join(', ') } }); } ``` **RT 轮换（refresh.js）** — 并发安全，version 乐观锁： ```javascript // edge-functions/api/auth/refresh.js export async function onRequest(context) { const { KV } = context.env; const cookieHeader = context.request.headers.get('Cookie') || ''; const rtMatch = cookieHeader.match(/rt=([^;]+)/); if (!rtMatch) return new Response('No RT', { status: 401 }); const oldToken = rtMatch[1]; const payload = parseJWT(oldToken); const userId = payload.sub; // KV version 乐观锁：只有 RT 匹配当前 version 才允许写入新 version const current = await KV.get(`rt:${userId}:meta`); const { version: oldVersion, [REDACTED_SECRET] } = JSON.parse(current || '{"version":0,"token":""}'); if (oldStored !== oldToken) { // 另一个 tab 已轮换，当前 RT 失效 → 返回 409 让客户端重新登录 return new Response('Concurrent rotation', { status: 409 }); } const newVersion = oldVersion + 1; const newToken = signRT(userId, newVersion); const ok = await KV.put( `rt:${userId}:meta`, JSON.stringify({ version: newVersion, [REDACTED_SECRET] }), { expirationTtl: 604800 } ); if (!ok) return new Response('Rotation failed', { status: 409 }); return new Response(JSON.stringify({ refreshToken: newToken }), { headers: { 'Content-Type': 'application/json', 'Set-Cookie': `rt=${newToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=604800` } }); } ``` **客户端轮换触发逻辑（event-bus.js 集成）**： ```javascript // client/src/utils/event-bus.js EventBus.on('auth:401', async () => { // Access Token 过期 → 尝试轮换 RT const res = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' }); if (res.ok) { // RT 轮换成功 → 重发原请求 return retryOriginalRequest(); } // RT 也失败 → 跳转登录 window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname); }); ``` --- ### 15.2 【P1】Cookie 安全属性 所有认证 Cookie 必须同时满足以下属性（缺一不可）： | 属性 | 值 | 作用 | |------|-----|------| | `HttpOnly` | 必须 | 阻止 JS 读取，防止 XSS 窃取 | | `Secure` | 必须 | 仅 HTTPS 传输 | | `SameSite=Strict` | 强烈建议 | 防止 CSRF（同站请求才带 Cookie） | | `SameSite=Lax` | 备选 | 允许导航带 Cookie，但阻止跨站 POST | | `Path=/` | AT Cookie | 全路径生效 | | `Path=/api/auth/refresh` | RT Cookie | 仅刷新接口可读 | **Edge Functions 签发示例**： ```javascript // 正确 headers.set('Set-Cookie', `at=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900` ); // 常见错误：缺少 Secure 或 SameSite // ❌ `at=${token}; HttpOnly` — 可被 HTTP 拦截 // ❌ `at=${token}; HttpOnly; SameSite=None` — 无 CSRF 保护 ``` **注意**：`SameSite=Strict` 会导致从外部链接跳转过来时无法携带 Cookie。如有第三方回调场景，改用 `SameSite=Lax` + CSRF Token 双保险。 --- ### 15.3 【P1】AI 聊天限流（KV 滑动窗口） **限流策略**： | 用户状态 | 限额 | 窗口 | |---------|------|------| | 未登录（IP 级别） | 10 次/分钟 | 滑动窗口 | | 已登录（User ID 级别） | 60 次/分钟 | 滑动窗口 | **Edge Function 实现**： ```javascript // edge-functions/_middleware.js 或独立限流工具 // edge-functions/utils/rate-limit.js export async function checkRateLimit(context, key, limit) { const { KV } = context.env; const now = Date.now(); const windowMs = 60 * 1000; // 1 分钟滑动窗口 const windowKey = `rl:${key}:${Math.floor(now / windowMs)}`; const prevKey = `rl:${key}:${Math.floor((now - windowMs) / windowMs)}`; const current = parseInt(await KV.get(windowKey) || '0'); const prev = parseInt(await KV.get(prevKey) || '0'); // 滑动窗口：当前窗口占比 + 上一窗口剩余权重 const prevWeight = (now % windowMs) / windowMs; const totalWeight = current + prev * prevWeight; if (totalWeight >= limit) { return { allowed: false, remaining: 0, resetMs: windowMs - (now % windowMs) }; } // 写入当前计数 await KV.put(windowKey, String(current + 1), { expirationTtl: 120 }); return { allowed: true, remaining: limit - Math.ceil(totalWeight) - 1, resetMs: windowMs }; } // 在 AI Chat Edge Middleware 中调用： // const userId = payload?.sub || request.headers.get('CF-Connecting-IP'); // const { allowed, resetMs } = await checkRateLimit(context, `ai:${userId}`, 60); // if (!allowed) return new Response('Rate limited', { status: 429, headers: { 'Retry-After': String(Math.ceil(resetMs/1000)) } }); ``` --- ### 15.4 【P1】CSP Header（Platform Middleware 注入） CSP 在 Platform Middleware 层注入，对所有 HTML 响应生效： ```javascript // middleware.js（项目根目录，Platform Middleware） export function onRequest(context) { const response = context.next(); // 仅对 HTML 响应注入 CSP const contentType = response.headers.get('Content-Type') || ''; if (!contentType.includes('text/html')) return response; const CSP = [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", // Skill 生成代码含内联脚本，放行 "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "font-src 'self' https://fonts.gstatic.com", "img-src 'self' data: https:", "connect-src 'self' https://api.edgeone.dev https://api.weixin.qq.com https://openapi.alipay.com", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'" ].join('; '); const newHeaders = new Headers(response.headers); newHeaders.set('Content-Security-Policy', CSP); newHeaders.set('X-Content-Type-Options', 'nosniff'); newHeaders.set('X-Frame-Options', 'DENY'); newHeaders.set('Referrer-Policy', 'strict-origin-when-cross-origin'); return new Response(response.body, { status: response.status, statusText: response.statusText, headers: newHeaders }); } ``` **配置说明**： - `connect-src` 中的域名需根据实际 AI API 和支付平台调整 - `'unsafe-inline'` 用于 Skill 生成的内联脚本（Phase 1 MVP 可接受） - Phase 3 可升级为 nonce 模式消除 `unsafe-inline` --- ### 15.5 【P1】EventBus 401 自动跳转 前端 EventBus 统一处理认证失效事件： ```javascript // client/src/utils/event-bus.js class EventBus { constructor() { this.listeners = {}; // 全局监听 fetch 401 响应 this._setupGlobal401Handler(); } _setupGlobal401Handler() { const originalFetch = window.fetch; window.fetch = async (...args) => { try { const res = await originalFetch(...args); if (res.status === 401) { this.emit('auth:401', { url: args[0], response: res }); } return res; } catch (err) { throw err; } }; } on(event, handler) { (this.listeners[event] ||= []).push(handler); return () => this.listeners[event] = this.listeners[event].filter(h => h !== handler); } emit(event, data) { (this.listeners[event] || []).forEach(h => h(data)); } } export const eventBus = new EventBus(); // 应用启动时注册 401 跳转 eventBus.on('auth:401', ({ url }) => { // 排除登录页自身，避免死循环 if (url.includes('/api/auth/login') || url.includes('/api/auth/register')) return; // 跳过 refresh 接口（它有自己的 401 处理） if (url.includes('/api/auth/refresh')) return; // 记录原页面路径，登录后回跳 const redirect = encodeURIComponent(window.location.pathname + window.location.search); window.location.href = `/login?redirect=${redirect}`; }); ``` --- ### 15.6 【P1】Notification 钩子详细设计 Notification 作为 Layer 2 Addon，按需接入。支持多通道：邮件、微信模板消息、钉钉 Webhook。 **接口设计（空壳 → Phase 2 填充适配器）**： ```javascript // cloud-functions/utils/notification-hooks.js // 通知事件类型 export const NotificationEvent = { ORDER_CREATED: 'order.created', ORDER_PAID: 'order.paid', ORDER_SHIPPED: 'order.shipped', ORDER_DELIVERED: 'order.delivered', USER_REGISTERED: 'user.registered', PASSWORD_CHANGED: 'password.changed', }; // 通知渠道 export const NotificationChannel = { EMAIL: 'email', WECHAT: 'wechat', // 微信模板消息 DINGTALK: 'dingtalk', // 钉钉 Webhook SMS: 'sms', }; // 钩子注册表（Phase 2 填充） const handlers = { [NotificationEvent.ORDER_PAID]: [], [NotificationEvent.USER_REGISTERED]: [], }; export function registerHandler(event, handler) { handlers[event] ||= []; handlers[event].push(handler); } export async function emit(event, payload) { const eventHandlers = handlers[event] || []; await Promise.allSettled( eventHandlers.map(h => h(payload).catch(err => console.error(`Notification handler error: ${err}`))) ); } // ===== 具体适配器示例（Phase 2 实现）===== // 邮件适配器 registerHandler(NotificationEvent.ORDER_PAID, async ({ order, user }) => { // 需配置 SMTP 环境变量 if (!process.env.SMTP_HOST) return; // 无邮件配置则跳过 await sendEmail({ to: user.email, subject: `订单 ${order.order_no} 支付成功`, html: `<h2>感谢您的购买！</h2><p>订单号：${order.order_no}</p>` }); }); // 微信模板消息适配器 registerHandler(NotificationEvent.ORDER_SHIPPED, async ({ order, user }) => { if (!process.env.WX_TEMPLATE_ID_SHIP) return; await sendWechatTemplate(user.openid, process.env.WX_TEMPLATE_ID_SHIP, { keyword1: order.order_no, keyword2: order.express_company + ' ' + order.express_no, }); }); // 调用示例（Cloud Functions 中） import { emit, NotificationEvent } from './utils/notification-hooks.js'; export async function onRequest(request, env) { // 支付回调成功后触发 await emit(NotificationEvent.ORDER_PAID, { order, user }); return new Response('SUCCESS'); } ``` **env-vars.md 补充字段**： ``` NOTIFICATION_SMTP_HOST # 邮件 SMTP 主机 NOTIFICATION_SMTP_PORT # 邮件 SMTP 端口（默认 587） NOTIFICATION_SMTP_USER # 邮件发件人 NOTIFICATION_SMTP_PASS # 邮件密码 NOTIFICATION_FROM_EMAIL # 发件人地址 WX_TEMPLATE_ID_ORDER # 微信订单通知模板 ID WX_TEMPLATE_ID_SHIP # 微信发货通知模板 ID DINGTALK_WEBHOOK_URL # 钉钉群 Webhook URL ``` --- ### 15.7 【P2】RS256 迁移方案 Phase 1 使用 HS256（密钥共享，简单快速）；Phase 2 迁移到 RS256（公私钥，安全性更高）。 **迁移策略：双轨并行，渐进式切换** ``` Phase 1（当前）：HS256 - JWT_SECRET = 对称密钥（Edge + Cloud 共享） Phase 2 迁移： - 新增 JWT_PRIVATE_KEY（Cloud 签名用 RSA 私钥） - 新增 JWT_PUBLIC_KEY（Edge 验证用 RSA 公钥） - Edge Functions 验证用公钥（无需密钥） - Cloud Functions 签名用私钥 - HS256 保留 30 天兼容窗口（老 token 仍可验证） ``` **生成密钥对**： ```bash # 生成 RSA-256 密钥对 openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem # 将公钥 public.pem 内容填入 EdgeOne Pages 环境变量 JWT_PUBLIC_KEY # 将私钥 private.pem 内容填入 Cloud Functions 环境变量 JWT_PRIVATE_KEY（严格保密） ``` **Cloud Functions 签名切换**： ```javascript // cloud-functions/utils/jwt-helper.js import { SignJWT, jwtVerify } from 'jose'; const getSignKey = (env) => { if (env.JWT_PRIVATE_KEY) { return createPrivateKey(env.JWT_PRIVATE_KEY); // RS256 } return new TextEncoder().encode(env.JWT_SECRET); // 兼容 HS256 }; export async function signJWT(payload, expiresIn, env) { const key = getSignKey(env); return new SignJWT(payload) .setProtectedHeader({ alg: env.JWT_PRIVATE_KEY ? 'RS256' : 'HS256' }) .setIssuedAt() .setExpirationTime(`${expiresIn}s`) .sign(key); } ``` **Edge Functions 验证（始终用公钥）**： ```javascript // edge-functions/utils/jwt-helper.js export async function verifyJWT(token, env) { const publicKey = createPublicKey(env.JWT_PUBLIC_KEY); // RS256 验证 try { const { payload } = await jwtVerify(token, publicKey); return payload; } catch { // 30 天兼容窗口：尝试 HS256 验证（仅过渡期） const secret = new TextEncoder().encode(env.JWT_SECRET); try { const { payload } = await jwtVerify(token, secret); return { ...payload, _hs256Fallback: true }; // 标记老 token } catch { return null; } } } ``` --- ### 15.8 【P2】订单状态机详细设计 **状态定义与流转**： ``` ┌──────────┐ pay ┌───────┐ ship ┌──────────┐ confirm ┌───────────┐ │ PENDING │ ──────→ │ PAID │ ─────→ │ SHIPPED │ ──────→ │ COMPLETED │ └──────────┘ └───────┘ └──────────┘ └───────────┘ │ │ │ │ cancel (user) │ refund (user/admin) │ ↓ ↓ │ ┌──────────┐ ┌──────────┐ │ │ CANCELLED│ │ REFUNDED │ │ └──────────┘ └──────────┘ │ │ refund (admin, COMPLETED) │ ─────────────────────────────────────────┘ ``` **合法流转规则（version 乐观锁保护）**： | 当前状态 | 允许目标状态 | 触发方 | 条件 | |---------|------------|--------|------| | PENDING | PAID | 支付回调 | 金额核对成功 | | PENDING | CANCELLED | 用户/系统超时 | 30min 未支付 | | PAID | SHIPPED | 管理员 | 填写物流信息 | | PAID | REFUNDED | 用户/管理员 | 退款申请 | | SHIPPED | COMPLETED | 用户/系统 | 7天无售后自动确认 | | SHIPPED | REFUNDED | 用户/管理员 | 退货退款 | | COMPLETED | REFUNDED | 管理员 | 特殊退款审批 | **状态机实现（D1 + version 乐观锁）**： ```javascript // cloud-functions/api/order/cancel.js export async function onRequest(request, env) { const { userId, role } = await auth(request, env); const { orderId, reason } = await request.json(); const pool = await getPool(env.DATABASE_URL); let attempt = 0; while (attempt < 3) { attempt++; try { await pool.beginTransaction(); // ① 锁定订单行，获取当前状态和版本 const [rows] = await pool.query( 'SELECT * FROM orders WHERE id = ? FOR UPDATE', [orderId] ); if (!rows.length) { await pool.rollback(); return 404; } const order = rows[0]; // ② 权限校验：用户只能取消自己的 PENDING 订单 if (role !== 'admin' && order.user_id !== userId) { await pool.rollback(); return 403; } // ③ 状态机校验 const allowed = { 'PENDING': ['CANCELLED'], 'PAID': ['CANCELLED', 'REFUNDED'], // 退款需管理员 'SHIPPED': ['COMPLETED', 'REFUNDED'], // 已发货需管理员 }; const target = reason === 'user_cancel' ? 'CANCELLED' : 'REFUNDED'; if (!allowed[order.status]?.includes(target)) { await pool.rollback(); return { error: `状态 ${order.status} 不允许变更为 ${target}` }; } if (target === 'CANCELLED' && role !== 'admin' && order.status !== 'PENDING') { await pool.rollback(); return { error: '仅 PENDING 状态可由用户取消' }; } // ④ 乐观锁更新（防止并发修改） const [result] = await pool.query( 'UPDATE orders SET status = ?, version = version + 1 WHERE id = ? AND version = ?', [target, orderId, order.version] ); if (result.affectedRows === 0) { await pool.rollback(); // 版本冲突，重试 continue; } // ⑤ 释放库存（仅取消时回补） if (target === 'CANCELLED') { await pool.query( '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 = ?)', [orderId, orderId] ); } // ⑥ 记录操作日志 await pool.query( 'INSERT INTO admin_logs (admin_id, action, target) VALUES (?, ?, ?)', [userId, `order_status_change:${order.status}→${target}`, orderId] ); await pool.commit(); // ⑦ 触发通知钩子 await emit(NotificationEvent.ORDER_CANCELLED, { order, reason }); return { success: true, status: target }; } catch (err) { await pool.rollback(); if (err.code === 'ER_LOCK_DEADLOCK' && attempt < 3) { await sleep(100 * Math.pow(2, attempt)); continue; } return { error: '操作失败，请重试' }; } } } ``` --- ## 十六、Phase 2 验收标准 | ID | 验收项 | 验证方法 | |----|--------|---------| | P2-01 | JWT 15min AT + 7d RT + Cookie 全属性 | 登录后 DevTools 查看 Cookie 属性 | | P2-02 | 并发刷新 RT，第二个请求返回 409 | 两个 tab 同时触发刷新 | | P2-03 | EventBus 401 跳转登录并回跳 | Token 过期后触发验证 | | P2-04 | AI 限流：未登录 11 次请求第 11 个返回 429 | 匿名请求连续发送 | | P2-05 | CSP Header 存在于 HTML 响应中 | `curl -I` 查看响应头 | | P2-06 | 订单状态机：PENDING→CANCELLED 成功 | 调用 cancel API | | P2-07 | 订单状态机：PAID→CANCELLED 被拒绝（需 admin） | 用户端测试 | | P2-08 | Notification 钩子注册 + emit 触发 | 单元测试验证 | | P2-09 | RS256 双轨验证（可选 Phase 2 末期） | HS/RS 混合 token 混跑 | --- ## 十七、功能验证清单（Phase 2 更新） **Demo 站点：** https://website-skeleton-demo-8mv8fitk.edgeone.cool（需有效期内的 EdgeOne Pages 访问 Token） | # | 功能 | 验证方法 | 状态 | |---|------|---------|------| | V-01 | 首页商品浏览（12 个商品） | API 返回 12 个商品，含名称/价格/库存 | ✅ | | V-02 | 用户注册（bcrypt cost=12） | 注册成功，返回 userId/email | ✅ | | V-03 | 用户登录（JWT） | 登录成功，返回用户信息 | ✅ | | V-04 | 购物车（localStorage 持久化） | Next.js 客户端路由，需浏览器测试 | 🟡 浏览器验证 | | V-05 | 结账（微信/支付宝选择） | checkout 页面存在，需浏览器测试 | 🟡 浏览器验证 | | V-06 | 模拟支付成功回调 | confirm API 存在，需有效 session | 🟡 需 session | | V-07 | 我的订单（状态标签） | orders API 存在，需有效 session | 🟡 需 session | --- ## 十八、Phase 2 里程碑 ``` ✅ Phase 1 完成：安全 Critical 全部修复（7/7 P0） 🟡 Phase 2 进行中：P1 安全加固 + P2 能力完善 🔲 Phase 3（可选）：RS256 + nonce CSP + SSE 优化 ``` > **Phase 2 完成后，网站骨架 Skill 具备生产级安全性与完整功能集。** --- ## 十八、Phase 3 实现（P2 编码 + Layer 2 Addon + 多租户铺垫） ### Phase 3 里程碑 ``` ✅ Phase 1 完成：Mock 数据 Demo，架构验证 ✅ Phase 2 完成：P0/P1 安全设计 + P2 设计文档完整 ✅ Phase 3 完成：P2 实现 + Layer 2 Addon + 多租户铺垫 ``` ### P2-1：RS256 双轨迁移（sharing/jwt-helper.js） **实现文件：** `sharing/jwt-helper.js` - 签发：RS256 私钥（`JWT_PRIVATE_KEY` 环境变量） - 验证：优先 RS256，30 天内旧 HS256 token 仍可验证 - 迁移时间线：Day 0 部署 → Day 30 移除 HS256 兼容分支 ```javascript // 签发（永远 RS256） const token = await signJWT({ sub: user.id, role: 'admin' }, AT_TTL_MS, env); // 验证（自动双轨） const payload = await verifyJWT(token, env); // payload._alg === 'RS256' → 新 token // payload._alg === 'HS256' → 30天兼容窗口内的旧 token ``` ### P2-2：订单状态机（cloud-functions/） **实现文件：** - `cloud-functions/utils/order-state-machine.js` — 核心状态机 + TRANSITIONS 表 + PERMISSIONS 表 - `cloud-functions/api/order/transition.js` — 统一状态变更 API - `cloud-functions/cron/order-cron.js` — 定时任务（PENDING 超时取消 / SHIPPED 自动完成） - `db/migrations/002_order_logs.sql` — `order_status_logs` 审计表 **状态流转（6 状态）：** ``` PENDING → PAID → SHIPPED → COMPLETED ↓ ↓ ↓ CANCELLED REFUNDED REFUNDED ``` **权限矩阵：** | 变更 | 用户（本人） | 管理员 | |------|------------|--------| | PENDING→CANCELLED | ✅ | ✅ | | PAID→SHIPPED | — | ✅ | | PAID/SHIPPED→REFUNDED | ✅（本人） | ✅ | | SHIPPED→COMPLETED | ✅ | ✅ | ### L2-1：SEO 模块（client/src/utils/seo.js + edge-functions/） **实现文件：** - `client/src/utils/seo.js` — JSON-LD 生成器 + Meta Tags + Sitemap XML 生成器 - `edge-functions/api/sitemap.xml.js` — 动态 Sitemap API（Edge Function，5 分钟缓存） - `sharing/i18n/zh-CN.js` + `en-US.js` — 中英文案 **JSON-LD 支持：** - `WebSite`（首页） - `Product`（产品页，含 offers/aggregateRating） - `BreadcrumbList`（面包屑） - `Organization`（组织信息） ### L2-2：i18n 国际化（sharing/i18n/） **实现文件：** - `sharing/i18n/zh-CN.js` — 中文文案 - `sharing/i18n/en-US.js` — 英文文案 - `sharing/i18n/i18n.js` — 翻译函数 `t(key)` + 语言切换 **使用方式：** ```javascript import { t, setLang, getLang } from './i18n.js'; t('nav.home') // → '首页' t('order.status.PAID') // → '已支付' setLang('en-US'); // 切换语言 ``` ### L2-3：Analytics 埋点（client/src/utils/analytics.js） **实现文件：** - `client/src/utils/analytics.js` — 埋点 SDK - `edge-functions/api/analytics/event.js` — 事件接收 API（KV 存储） **预定义事件：** `page_view` / `add_to_cart` / `checkout_start` / `purchase` / `signup` / `login` / `search` **特点：** `navigator.sendBeacon` 不阻塞导航，支持页面卸载时发送。 ### L3-1：Multi-tenant KV 前缀（sharing/kv-keys.js） **实现文件：** `sharing/kv-keys.js` 所有 KV Key 统一加租户前缀： ``` Phase 3: "default:session:abc123" Phase 4: "{tenant}:session:abc123"（从 JWT payload.tenant 动态读取） ``` ### Phase 3 新增文件清单 ``` sharing/ ├── jwt-helper.js ✅ RS256 + HS256 双轨 ├── kv-keys.js ✅ 多租户前缀 └── i18n/ ├── zh-CN.js ✅ 中文 ├── en-US.js ✅ 英文 └── i18n.js ✅ 翻译函数 cloud-functions/ ├── utils/ │ └── order-state-machine.js ✅ 核心状态机 ├── api/order/ │ └── transition.js ✅ 状态变更 API └── cron/ └── order-cron.js ✅ 定时任务 client/src/utils/ ├── seo.js ✅ SEO 工具 └── analytics.js ✅ 埋点 SDK edge-functions/ ├── api/ │ ├── sitemap.xml.js ✅ Sitemap API │ └── analytics/event.js ✅ 埋点接收 db/migrations/ └── 002_order_logs.sql ✅ 审计日志表 references/ ├── admin-module.md ✅ 补充 ├── edge-functions.md ✅ 补充 ├── cloud-functions.md ✅ 补充 ├── middleware.md ✅ 补充 └── deployment.md ✅ 补充 ``` --- ## 十九、Phase 3 验收标准 | ID | 验收项 | 验证方法 | |----|--------|---------| | P3-01 | RS256：新 token 用 RS256 私钥签发 | 代码审查 + 手动 JWT 解析 | | P3-02 | RS256：HS256 旧 token 30 天内仍可验证 | 测试过期 token 验证 | | P3-03 | 订单状态机：用户取消 PENDING 订单成功 | 调用 transition API | | P3-04 | 订单状态机：用户无法 PAID→CANCELLED（403） | 调用 transition API | | P3-05 | 库存联动：取消/退款时 stock 回补 | 查询 products 表 | | P3-06 | 审计日志：每次状态变更写入 order_status_logs | 查询数据库 | | P3-07 | Cron：PENDING 超时 30 分钟自动 CANCELLED | 模拟超时订单 | | P3-08 | SEO JSON-LD：产品页含 schema.org 结构化数据 | 审查页面源码 | | P3-09 | Sitemap：/api/sitemap.xml 返回有效 XML | curl 访问 | | P3-10 | i18n：`t('order.status.PAID')` 正确输出中英文 | 切换语言测试 | | P3-11 | Analytics：add_to_cart 事件通过 sendBeacon 发送 | Network 面板验证 | | P3-12 | Multi-tenant：KV key 格式含 "default:" 前缀 | 代码审查 | --- ## 二十、未来演进 ``` Phase 1：Mock 数据 Demo ✅ Phase 2：P0/P1 安全设计 + P2 设计文档 ✅ Phase 3：P2 编码实现 + Layer 2 Addon + 多租户铺垫 ✅ Phase 4（规划中）：多租户 SaaS - KV key 从 JWT payload.tenant 动态读取 - 租户隔离数据库（D1 schema） - 租户管理后台 - 计费系统（按量/订阅） Phase 5（规划中）：npm 包化 npm install @site-skeleton/auth npm install @site-skeleton/payment ``` *Skill 版本演进由评审驱动，每 Phase 完成后更新版本号与文档。* ## 用户确认检查点 以下操作必须在执行前暂停，向用户说明并等待确认： | # | 操作 | 检查点说明 | 风险 | |---|------|-----------|------| | 1 | **edgeone pages deploy** | 显示待部署文件清单和变更概要，确认目标项目（测试/生产） | 高 | | 2 | **激活真实支付网关** | 切换到正式商户号前确认：签名验证已通过、测试环境已跑通全流程 | 高 | | 3 | **注入生产环境变量** | 确认所有密钥（JWT_SECRET / WX_API_KEY / DATABASE_URL）已正确配置 | 高 | | 4 | **执行数据库迁移** | 先备份现有数据、确认迁移脚本影响范围 | 高 | | 5 | **启用 Cron 定时任务** | 确认 PENDING 超时（30min）和自动完成（7天）阈值符合业务需求 | 中 | | 6 | **生成网站代码前** | 向用户展示选择的场景模板和模块组合，确认后再执行代码生成 | 中 | | 7 | **绑定自定义域名** | 确认 DNS 解析已配置、SSL 证书已签发、旧域名流量迁移方案 | 中 | | 8 | **回滚部署** | 确认回滚到哪个版本、是否会影响正在进行的交易 | 高 | | 9 | **开启多租户模式** | 确认 KV key 前缀迁移策略、租户隔离方案 | 中 | | 10 | **升级 RS256（从 HS256）** | 确认 30 天兼容窗口期内新旧 token 混跑测试通过 | 中 | > **规则**：LLM 遇到上表操作时，先向用户输出摘要说明，明确询问"是否继续？"，得到肯定答复后再执行。用户可随时跳过检查点。 --- ## ⚠️ 安全与合规说明（ClawHub） ### 1. 部署确认机制 本 Skill 可生成网站代码并自动部署到 EdgeOne Pages。部署前 AI Agent 会： - 显示待部署文件清单和变更概要 - 请求用户确认后才执行 `edgeone pages deploy` - 建议首次使用时选择测试项目而非生产项目 ### 2. 支付安全 - 支付回调 API 已实现微信 HMAC-SHA256 签名验证 - 校验 appid/mchid 商户身份一致性 - 校验回调金额与订单金额完全匹配 - 交易状态仅接受 result_code=SUCCESS - 幂等锁防止重复回调处理 - **建议：** 生产环境前使用测试商户号验证全流程 ### 3. 最小权限原则 - EdgeOne CLI 使用 scope 受限的测试项目 - 支付密钥使用测试商户号而非正式商户号 - AI API Key 使用测试额度或免费额度 - 数据库使用独立的测试实例 ### 4. 数据保留策略 | 数据类型 | 存储位置 | 保留期限 | 说明 | |---------|---------|---------|------| | KV Session | EdgeOne KV | 7 天 TTL | 自动过期 | | AI 聊天历史 | EdgeOne KV | 30 天 TTL | 可配置 | | 审计日志 | EdgeOne KV | 90 天 TTL | 可配置 | | 订单数据 | D1 | 永久 | 业务必需 | ### 5. Cron 定时任务 - 订单超时取消（PENDING 30 分钟 → CANCELLED） - 订单自动完成（SHIPPED 7 天 → COMPLETED） - **建议：** 生产环境前审阅 cron 阈值配置和回滚方案 - 提供 `cloud-functions/cron/disable-cron.js` 一键禁用 ### 6. 使用建议 - ✅ 在测试项目中验证全部功能 - ✅ 使用测试/沙箱商户号 - ✅ 部署前审查生成的代码和环境变量 - ✅ 配置审计日志保留策略 - ⚠️ 启用真实支付前完成签名验证测试 - ⚠️ 上线前禁用不需要的 cron 任务


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal:  | SkillSpector: suspicious (CRITICAL)