fix: NPM 代理配置 block_exploits=false + WebSocket Support (§7)
This commit is contained in:
@@ -77,7 +77,11 @@
|
||||
Admin: http://admin-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:3002
|
||||
Mini-H5: http://mini-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:5173
|
||||
```
|
||||
- 缺失的域名通过 `create_proxy_host` 创建(SSL 由 K8S ingress 处理,不需要在 NPM 中配置)
|
||||
- 缺失的域名通过 `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`
|
||||
- 将外网域名写入 `pipeline-context.md` 的"外网域名"部分
|
||||
- **E2E 测试优先使用外网域名**,localhost 作为最后 fallback
|
||||
|
||||
@@ -226,6 +230,20 @@
|
||||
- 不一致时以 `pipeline-state.yaml` 为准,同步更新 `sprint-status.yaml`
|
||||
- 同步后输出一行:`📋 状态同步:pipeline-state X done ↔ sprint-status Y done`
|
||||
|
||||
7.5. **平台检测与自动配置(⛔ 强制)**
|
||||
- 检测项目类型,加载对应平台方案集:
|
||||
```bash
|
||||
# 检测 Next.js 项目
|
||||
[ -f "next.config.ts" ] || [ -f "next.config.js" ] && echo "PLATFORM=nextjs"
|
||||
# 检测 uni-app 项目
|
||||
[ -f "app/mini-program/src/pages.json" ] && echo "PLATFORM=uniapp"
|
||||
```
|
||||
- **Next.js 平台**:按 `./reference/platform-nextjs.md` 逐项检测
|
||||
- NextAuth trustHost 配置(第 1 节)
|
||||
- Prisma 7 adapter 配置(第 2 节)
|
||||
- 检测不通过 → 自动修复并记录到 `pipeline-context.md` 修复记录
|
||||
- **uni-app 平台**:按 `./reference/platform-uni-app.md` 逐项检测(已有流程不变)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
232
.claude/skills/d8d-dev/reference/platform-nextjs.md
Normal file
232
.claude/skills/d8d-dev/reference/platform-nextjs.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Next.js 平台方案集
|
||||
|
||||
> **⛔ 主流程文件统一引用此文件**:所有 Next.js 平台特定内容集中于此。
|
||||
> 主流程文件中不再内联平台特定方案,改为引用本文件对应章节。
|
||||
|
||||
---
|
||||
|
||||
## 1. 外网域名认证(⛔ 强制)
|
||||
|
||||
### 问题描述
|
||||
|
||||
NextAuth v5 (5.0.0-beta.x) 默认使用 `AUTH_URL` 环境变量的固定值作为 cookie domain 和重定向 URL。
|
||||
当通过外网域名(如 `api-custom-xxx.dev.d8d.fun`)访问时,NextAuth 生成的 cookie domain 与请求域名不匹配,
|
||||
导致浏览器拒绝发送 cookie,认证失败。
|
||||
|
||||
**症状**:
|
||||
- 外网域名登录后立即跳回登录页(cookie 未保存)
|
||||
- API 测试返回 401 Unauthorized
|
||||
- localhost 登录正常,外网域名登录失败
|
||||
|
||||
### 检测命令
|
||||
|
||||
```bash
|
||||
# 检查 NextAuth 配置是否有 trustHost
|
||||
grep -q "trustHost" src/lib/auth.ts && echo "PASS: trustHost 已配置" || echo "FAIL: 缺少 trustHost"
|
||||
|
||||
# 检查环境变量
|
||||
grep -q "AUTH_TRUST_HOST" .env && echo "PASS: AUTH_TRUST_HOST 已设置" || echo "WARN: 建议添加 AUTH_TRUST_HOST=true"
|
||||
```
|
||||
|
||||
### 自动修复
|
||||
|
||||
在 NextAuth 配置对象顶部添加 `trustHost: true`:
|
||||
|
||||
```typescript
|
||||
// src/lib/auth.ts
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
trustHost: true, // ← 关键:让 NextAuth 使用请求的 host 动态推导 URL
|
||||
providers: [
|
||||
Credentials({ ... })
|
||||
],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
同时在 `.env` 中添加(双重保险):
|
||||
|
||||
```
|
||||
AUTH_TRUST_HOST=true
|
||||
```
|
||||
|
||||
### E2E 测试 Auth 策略
|
||||
|
||||
- **外网域名 E2E 测试**:使用 Playwright 浏览器登录(trustHost=true 后正常工作)
|
||||
- **API 测试**:curl + cookie jar 方式(先获取 CSRF token,再 POST 登录)
|
||||
- **禁止硬编码 localhost 做 E2E 测试**,应从 `pipeline-context.md` 读取 URL
|
||||
|
||||
---
|
||||
|
||||
## 2. Prisma 7 配置(⛔ 强制)
|
||||
|
||||
### 问题描述
|
||||
|
||||
Prisma 7 使用 `@prisma/adapter-pg` driver adapter,PrismaClient 需要 `adapter` 参数。
|
||||
`prisma generate` 的输出路径为 `src/generated/prisma/client`(非 `src/generated/prisma`)。
|
||||
|
||||
### 检测命令
|
||||
|
||||
```bash
|
||||
# 检查 Prisma adapter 是否已配置
|
||||
grep -q "@prisma/adapter-pg" src/lib/prisma.ts && echo "PASS" || echo "FAIL: 缺少 adapter-pg"
|
||||
```
|
||||
|
||||
### 已知问题与修复
|
||||
|
||||
1. **seed.ts 编译错误**:`prisma/seed.ts` 不应参与 `next build`
|
||||
- 修复:在 `tsconfig.json` 的 `exclude` 数组中添加 `"prisma/seed.ts"`
|
||||
|
||||
2. **import 路径错误**:
|
||||
- ❌ `import { PrismaClient } from '../src/generated/prisma'`
|
||||
- ✅ `import { PrismaClient } from '../src/generated/prisma/client'`
|
||||
|
||||
3. **PrismaClient 初始化**:
|
||||
```typescript
|
||||
import { PrismaClient } from '@/generated/prisma/client'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! })
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. NextAuth v5 类型问题
|
||||
|
||||
### 问题描述
|
||||
|
||||
next-auth v5 beta 的 `session.user` 类型不完整,直接添加自定义属性(如 id、role)会报 TypeScript 错误:
|
||||
```
|
||||
Conversion of type 'AdapterUser & User' to type '{ role: string }' is not possible
|
||||
```
|
||||
|
||||
### 修复模式
|
||||
|
||||
在 callbacks 中使用 `as any` 类型断言 + eslint-disable 注释:
|
||||
|
||||
```typescript
|
||||
callbacks: {
|
||||
async session({ session, token }) {
|
||||
if (session.user) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(session.user as any).id = token.id as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(session.user as any).role = token.role as string;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Playwright + React 兼容性
|
||||
|
||||
### 4.1 React 表单填写
|
||||
|
||||
**问题**:Playwright 的 `fill()` 方法在 React 18+ 应用中可能不触发 `onChange` 事件,
|
||||
导致 React state 不更新,表单提交时值为空。
|
||||
|
||||
**修复**:使用 `nativeInputValueSetter` 设置值并手动派发事件:
|
||||
|
||||
```javascript
|
||||
await page.evaluate(() => {
|
||||
const el = document.querySelector('input[type="email"]');
|
||||
const nativeSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype, 'value'
|
||||
).set;
|
||||
nativeSetter.call(el, 'test@example.com');
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
```
|
||||
|
||||
### 4.2 React 隐藏文件输入
|
||||
|
||||
**问题**:隐藏的 `<input type="file">` 在 React 中,`setInputFiles` 不会触发 `onChange` 事件,
|
||||
导致上传流程无法启动。
|
||||
|
||||
**建议**:
|
||||
- 优先通过 API (curl) 验证上传功能
|
||||
- E2E 测试时通过 `browser_run_code` 触发可见的 UI 元素而非直接操作隐藏 input
|
||||
|
||||
---
|
||||
|
||||
## 5. 单 Next.js 应用模式
|
||||
|
||||
### 适用场景
|
||||
|
||||
本项目中 API 后端和前端共用一个 Next.js 应用,与 pipeline 默认的多应用模式不同。
|
||||
|
||||
### 关键差异
|
||||
|
||||
| 项目 | 多应用模式 | 单应用模式(本项目) |
|
||||
|------|-----------|-------------------|
|
||||
| API | `api/` 子目录 | `src/app/api/` 路由 |
|
||||
| 前端 | `app/admin/` 子目录 | `src/app/` 页面 |
|
||||
| 端口 | 3001 (API) + 3002 (Admin) | 3001 (单一端口) |
|
||||
| 环境变量 | 需注入跨域 API URL | 无需注入(同源) |
|
||||
| pm2 进程 | api + admin | vividspark (单进程) |
|
||||
|
||||
### pipeline-context.md 调整
|
||||
|
||||
```
|
||||
## 前端环境变量注入状态
|
||||
- 无需注入(单 Next.js 应用,API 和前端同源)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 中间件(middleware.ts)
|
||||
|
||||
### 已知警告
|
||||
|
||||
Next.js 16 中 "middleware" file convention is deprecated,应使用 "proxy"。
|
||||
但目前仍可工作,属于 warning 级别,不影响功能。
|
||||
|
||||
### middleware 中的 Auth 检查
|
||||
|
||||
在 Edge Runtime 中不能使用完整的 `auth()` 函数,应使用 `getToken()` 进行 JWT-only 检查:
|
||||
|
||||
```typescript
|
||||
import { getToken } from "next-auth/jwt"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export async function middleware(request) {
|
||||
const token = await getToken({ req: request })
|
||||
if (!token) {
|
||||
return NextResponse.redirect(new URL('/login', request.url))
|
||||
}
|
||||
return NextResponse.next()
|
||||
}
|
||||
```
|
||||
|
||||
### 需要公开的路由
|
||||
|
||||
确保以下路由在 middleware 中被排除:
|
||||
- `/api/auth/*` — NextAuth 默认路由(CSRF、session、callback)
|
||||
- `/login` — 登录页
|
||||
- `/register` — 注册页
|
||||
- `/_next/*` — Next.js 静态资源
|
||||
|
||||
---
|
||||
|
||||
## 7. NPM 代理主机配置(⛔ 强制)
|
||||
|
||||
### 问题描述
|
||||
|
||||
`next dev` 的 HMR(热模块替换)依赖 WebSocket 连接。
|
||||
NPM 代理主机默认配置会阻断 WebSocket,导致:
|
||||
- 浏览器控制台持续 `WebSocket connection to 'ws://.../_next/webpack-hmr' failed`
|
||||
- 页面反复重载,React 状态丢失
|
||||
- Playwright E2E 测试中表单值丢失
|
||||
|
||||
### 修复
|
||||
|
||||
`create_proxy_host` 创建代理主机时 ⛔ 必须设置:
|
||||
- `block_exploits: false`(默认 true 会阻断 WebSocket 握手)
|
||||
- 创建后通过 `update_proxy_host` 启用 WebSocket Support 开关
|
||||
|
||||
### 验证
|
||||
|
||||
`get_proxy_host` 确认:`block_exploits: false` + `allow_websocket_upgrade: true`
|
||||
Reference in New Issue
Block a user