diff --git a/.claude/skills/d8d-dev/journey-mode.md b/.claude/skills/d8d-dev/journey-mode.md index 4f1b714..e26a98f 100644 --- a/.claude/skills/d8d-dev/journey-mode.md +++ b/.claude/skills/d8d-dev/journey-mode.md @@ -394,14 +394,7 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这 3. 证据阶段(⛔ 强制,不得跳过): - browser_take_screenshot → 保存最终截图到 tests/e2e/screenshots/journey-epic{N}-{M}-final.png - - browser_network_requests(static=false) → 检查无 localhost API 调用 - - ⛔ **资源加载状态检查**(新增 — 每条旅程必须执行): - - `browser_network_requests(static=true, filter="\\.(jpg|jpeg|png|gif|webp|svg|mp4|webm|mov)$")` - - 过滤图片/视频资源请求,检查 HTTP 状态码 - - 任何 403/404 → 标记 `resource_status_check.found = FOUND` + 触发修复 - - 常见 403 原因:MinIO 私有 bucket 未用 presigned URL、防盗链、CORS - - 常见 404 原因:资源路径错误、文件未上传 - - 记录到 pipeline-state.yaml 的 `verification_detail.resource_status_check` + - browser_network_requests → 检查无 localhost API 调用 4. 修复阶段(⛔ 以下任一条件满足时必须执行,不得跳过): diff --git a/.claude/skills/d8d-dev/phase-00-setup.md b/.claude/skills/d8d-dev/phase-00-setup.md index 31712c5..82b0edc 100644 --- a/.claude/skills/d8d-dev/phase-00-setup.md +++ b/.claude/skills/d8d-dev/phase-00-setup.md @@ -68,6 +68,9 @@ - 标记:Epic 0 完成后此文件才可生效(项目目录需先存在) 4.5. **外网域名发现与连通性验证(NPM MCP)** + - project-slug 推导规则:取项目根目录名(basename of pwd),追加 `-group` 后缀 + - 示例:目录 `/mnt/code/231-251-template-6` → slug = `231-251-template-6-group` + - ⛔ 禁止从项目描述或 PRD 内容猜测 slug - 通过 NPM MCP 工具查询已配置的代理主机: - 使用 `get_host_report` 快速确认代理数量 - ⚠️ `list_proxy_hosts` 有已知 bug(locations 字段解析失败),用 `get_proxy_host` 逐个查询更可靠 @@ -79,9 +82,10 @@ ``` - 缺失的域名通过 `create_proxy_host` 创建(⛔ 参数要求见 `reference/platform-nextjs.md` §7 或 `reference/platform-uni-app.md`) - `block_exploits: false`(默认 true 阻断 WebSocket) - - 创建后 `update_proxy_host` 启用 WebSocket Support - SSL 由 K8S ingress 处理,不需要在 NPM 中配置 - - 创建后验证:`get_proxy_host` 确认 `block_exploits: false` 且 `allow_websocket_upgrade: true` + - ⛔ 对每个 proxy host(无论已有还是新建),必须验证并修复(此步骤不可跳过): + - `get_proxy_host` 检查 `block_exploits: false` 且 `allow_websocket_upgrade: true` + - 如不满足 → `update_proxy_host` 修正(MCP 工具不直接支持 allow_websocket_upgrade 时,用 NPM API:`PUT /api/nginx/proxy-hosts/{id}` 传 `{"allow_websocket_upgrade": true}`) - 将外网域名写入 `pipeline-context.md` 的"外网域名"部分 - **E2E 测试优先使用外网域名**,localhost 作为最后 fallback @@ -177,26 +181,6 @@ - 此步骤创建的 .env.local 不应提交到 git(应在 .gitignore 中排除) - 更新 pipeline-context.md 记录注入状态 -4.8. **MinIO 外网地址注入(⛔ 强制)** - - 检测容器环境变量 `MINIO_HOST`,将外网地址注入到 `.env` 中的 `S3_PUBLIC_URL`: - - a. 检测 MINIO_HOST: - ```bash - echo $MINIO_HOST - ``` - b. 探测 HTTPS 是否可用: - ```bash - curl -sf --max-time 5 https://$MINIO_HOST/minio/health/live && echo "HTTPS" || echo "NO-HTTPS" - ``` - c. 注入到 `.env`(⚠️ 注意 K8S ingress 会将 HTTP 升级为 HTTPS,必须使用 HTTPS): - ``` - S3_PUBLIC_URL="https://{MINIO_HOST}" - ``` - d. 代码中对外暴露的 URL(presigned URL、封面图 URL)必须使用 `S3_PUBLIC_URL` - e. 内部连接(S3Client endpoint)保持 `S3_ENDPOINT`(localhost:9000) - f. 详见 `reference/platform-nextjs.md` §8 - 5. **初始化管道状态** - 创建 `{implementation_artifacts}/pipeline-state.yaml`: ```yaml diff --git a/.claude/skills/d8d-dev/reference/platform-nextjs.md b/.claude/skills/d8d-dev/reference/platform-nextjs.md index 6026419..6285b41 100644 --- a/.claude/skills/d8d-dev/reference/platform-nextjs.md +++ b/.claude/skills/d8d-dev/reference/platform-nextjs.md @@ -230,89 +230,3 @@ NPM 代理主机默认配置会阻断 WebSocket,导致: ### 验证 `get_proxy_host` 确认:`block_exploits: false` + `allow_websocket_upgrade: true` - ---- - -## 8. MinIO 外网地址配置(⛔ 强制) - -### 问题描述 - -`S3_ENDPOINT` 默认为 `http://localhost:9000`(容器内部连接)。但对外暴露的 URL(presigned URL、封面图 URL) -如果使用 `localhost:9000`,外网用户无法访问。 - -容器环境提供 `MINIO_HOST` 环境变量(外网 MinIO 域名),需要注入为 `S3_PUBLIC_URL`。 - -### 关键区分 - -| 变量 | 用途 | 值 | -|------|------|------| -| `S3_ENDPOINT` | S3Client 内部连接 | `http://localhost:9000` | -| `S3_PUBLIC_URL` | 对外暴露的 URL(presigned、封面) | `https://{MINIO_HOST}` | - -### Dual S3Client 模式(⛔ 强制) - -AWS SDK `getSignedUrl()` 生成的 presigned URL 签名包含 `X-Amz-SignedHeaders=host`, -即签名绑定了 S3Client 的 endpoint host。**不能用字符串替换域名**(签名会失效返回 403)。 - -必须创建两个 S3Client 实例: - -```typescript -// 内部 S3Client(worker 下载、上传等) -const s3 = new S3Client({ endpoint: "http://localhost:9000", ... }); - -// 外网 S3Client(生成外网 presigned URL) -const s3Public = new S3Client({ endpoint: "https://minio-xxx.dev.d8d.fun", ... }); - -// 内部 presigned URL(worker 用) -getDownloadUrl(key) → getSignedUrl(s3, ...) // host=localhost:9000 - -// 外网 presigned URL(浏览器/stream 路由用) -getPublicDownloadUrl(key) → getSignedUrl(s3Public, ...) // host=minio-xxx.dev.d8d.fun -``` - -### 代码规则 - -- **`getDownloadUrl()`**:用内部 `s3`,返回 localhost presigned URL(worker 下载用) -- **`getPublicDownloadUrl()`**:用 `s3Public`,返回外网 presigned URL(stream 路由用) -- **`uploadFile()` 返回值**:使用 `S3_PUBLIC_URL`(公开访问 URL) -- **worker 中的封面 URL**:使用 `S3_PUBLIC_URL` -- **禁止字符串替换域名**:`url.replace(localhost, publicUrl)` 会导致 403 - -### ⚠️ HTTPS 注意 - -K8S ingress 会将 HTTP 升级为 HTTPS。`S3_PUBLIC_URL` 必须使用 `https://`, -否则 presigned URL 签名会因重定向而失效。 - ---- - -## 9. 资源 URL 可访问性检查(⛔ 强制) - -### 问题描述 - -MinIO bucket 默认私有,所有通过 URL 直接访问的资源(图片、视频)都会返回 403。 -前端 `` 或 `