174 lines
11 KiB
Markdown
174 lines
11 KiB
Markdown
# CLAUDE.md
|
||
|
||
## 目标
|
||
|
||
本文档用于约束 AI 在本项目内的实现方式,确保代码简洁、可维护、安全,并与当前业务链路保持一致。
|
||
|
||
## 核心原则
|
||
|
||
- 默认最小改动,优先复用现有工具函数、类型和目录结构。
|
||
- 保持单一入口和单一职责,避免同一业务暴露多套路由或重复实现。
|
||
- 前端不接触 NewAPI session、完整 API Key、Lsky token 等敏感信息。
|
||
- 所有返回给前端的 `msg` 必须使用本地业务表达,不提及“上游”“NewAPI”“Lsky”“供应商”“token”“key”等内部来源;内部异常统一返回“服务器内部错误”。
|
||
- 注释和日志要解释意图、边界和排查信息,不输出密码、cookie、完整 key、token、图片二进制或完整 prompt。
|
||
- 已存在的说明性注释不要随意删除,尤其是 `server/utils/openai.ts` 里的模型调用、视觉输入、流式解析等上下文注释。
|
||
- 不要写任何可能阻塞整个进程的代码。
|
||
|
||
## API 路由规范
|
||
|
||
- 认证相关接口统一放在 `server/api/auth`:
|
||
- `POST /api/auth/register`
|
||
- `POST /api/auth/login`
|
||
- `GET /api/auth/me`
|
||
- `GET /api/auth/logout`
|
||
- `GET /api/auth/ready`
|
||
- 生图相关接口统一放在 `server/api/images`:
|
||
- `POST /api/images/generate`
|
||
- `GET /api/images/history`
|
||
- `GET /api/images/history/:id`
|
||
- `DELETE /api/images/history/:id`
|
||
- `GET /api/images/stats`
|
||
- 不创建 `server/api/user` 之类的本地别名路由。
|
||
- 临时排查接口不要长期保留,例如数据库健康检查类接口排查结束后必须删除。
|
||
|
||
## 登录与环境准备
|
||
|
||
- 登录成功后,后端从 NewAPI `Set-Cookie` 中提取 `session`,写入本站 httpOnly cookie,同时保存 `newapi_user_id`。
|
||
- 前端通过 `/api/auth/me` 恢复登录状态,通过 `/api/auth/logout` 清理登录状态。
|
||
- 登录成功后前端可调用 `/api/auth/ready`,用于确认当前 NewAPI 用户是否具备可用的 `AIArtStudio` Token。
|
||
- `/api/auth/ready` 只检查或创建环境:
|
||
- 只认 `name === "AIArtStudio"`、`status === 1`、未软删除的 Token。
|
||
- 没有可用 Token 时,后端按固定参数创建。
|
||
- 不调用完整 key 获取接口,不向前端返回 key。
|
||
- 返回文案使用“环境已准备 / 环境初始化完成”等业务表达,不暴露 API Key 细节。
|
||
- 完整 key 只允许在服务端真实调用模型前按需获取,相关逻辑集中在 `server/utils/newApiTokens.ts`。
|
||
|
||
## 上游与模型调用
|
||
|
||
- 调用 NewAPI 用户、Token 等上游接口,使用 `server/utils/fetch.ts` 的统一入口:
|
||
- `newApiFetch`:普通未登录请求。
|
||
- `newApiFetchRaw`:需要读取上游响应头时使用,目前主要用于登录提取 `Set-Cookie`。
|
||
- `newApiAuthedFetch`:需要 NewAPI 登录态时使用,由服务端从 httpOnly cookie 读取 session/userId 并补齐请求头。
|
||
- 生图模型调用集中在 `server/utils/openai.ts`。
|
||
- 当前生图上游使用 `POST https://api.qflink.xyz/v1/chat/completions`,参数固定为:
|
||
- `model: "gpt-image-2"`
|
||
- `stream: true`
|
||
- `messages: [{ role: "user", content: prompt }]`
|
||
- 生图通过 `askImgStream` 读取 SSE 流,累积 `choices[].delta.content`,从 Markdown 图片或普通 URL 中提取图片地址。
|
||
- `askStream` 保留给 Responses API 的其他流式文本/多模态场景,不强行复用于 chat completions SSE。
|
||
- 不再使用 `/v1/images/generations` 作为当前生图主链路。
|
||
|
||
## 生图、图床与历史记录
|
||
|
||
- `POST /api/images/generate` 当前同步等待生图完成后立即返回,不向前端透传流式进度,也不等待图床归档完成。
|
||
- 生图成功后返回:
|
||
- `imageUrl`:生成图片访问地址,前端当前优先展示。
|
||
- `revisedPrompt`:流式 chat completions 当前没有等价字段,通常为空。
|
||
- 生图主链路先把记录落库为 `SUCCEEDED`,并清空 `hostedImageUrl`、`hostedResponse`、`imageMimeType`、`errorMessage`;之后再由响应后的后台任务异步上传 Lsky。
|
||
- 历史记录中的图床归档状态约定:
|
||
- `status === "SUCCEEDED"` 且 `hostedImageUrl === null` 且 `errorMessage === null`:表示图片已生成、仍在归档中。
|
||
- `status === "SUCCEEDED"` 且 `hostedImageUrl === null` 且 `errorMessage !== null`:表示归档失败。
|
||
- `hostedImageUrl !== null`:表示归档成功。
|
||
- Lsky 归档逻辑集中在 `server/utils/lsky.ts`:
|
||
- 配置从 `LSKY_BASE_URL`、`LSKY_TOKEN`、`LSKY_STORAGE_ID` 读取。
|
||
- 上传时先下载上游图片,再用 `multipart/form-data` 调 Lsky `/upload`。
|
||
- 文件名格式为 `userId_username_timestamp_recordId.ext`。
|
||
- tags 固定包含 `AIArtStudio`、`user:{userId}`、`record:{recordId}`、`model:gpt-image-2`。
|
||
- 图床上传失败不影响本次生图成功:接口仍立即返回 `imageUrl`,数据库记录归档失败提示;前端可见文案只能说“图片归档失败”等本地描述。
|
||
- 数据库不保存图片 base64;不要恢复 `image_base64` 或类似大文本存图方案。
|
||
- 历史列表和详情都不向前端返回完整上游响应、图床响应或内部错误详情;这些内容可以入库供服务端排查,但 API 响应应固定隐藏或置空。
|
||
|
||
## 数据库与 Prisma
|
||
|
||
- Prisma schema 位于 `prisma/schema.prisma`,实际数据库同步以 Prisma 为准。
|
||
- 根目录 `create_tables.sql` 是人工可读建表参考,字段变更时必须同步更新。
|
||
- Prisma Client 从 `~~/app/generated/prisma/client` 引入,统一由 `server/utils/prisma.ts` 创建和复用。
|
||
- 当前使用 Prisma 7 + `@prisma/adapter-mariadb`,`DATABASE_URL` 在运行时解析为 MariaDB pool config。
|
||
- 登录或 `/api/auth/me` 成功后会 upsert `User` 快照,主键使用 NewAPI 用户 id。
|
||
- 生图记录写入 `ImageGeneration`:
|
||
- 开始时写 `RUNNING`。
|
||
- 生成成功时先写 `SUCCEEDED`、上游 URL、完整上游响应、耗时。
|
||
- 后台归档成功后再补写图床 URL、MIME、完整图床响应。
|
||
- 后台归档失败时只补写本地错误文案,不把记录改回失败。
|
||
- 失败时写 `FAILED`、错误信息、耗时。
|
||
- 删除历史采用软删除 `deletedAt`。
|
||
- `GenerationStats` 使用固定 id `global` 记录全局统计。
|
||
- `runningRequests` 只表示生图进行中,不包含图床归档阶段。
|
||
- 统计更新失败只记录日志,不应阻断用户拿到已经生成的图片。
|
||
- 需要变更数据库结构时,同时维护 Prisma migration 和 `create_tables.sql`。
|
||
|
||
## 日志规范
|
||
|
||
- API handler 使用 `server/utils/logging.ts` 的 `createApiLogger` 和 `toSafeLogError`。
|
||
- 日志必须包含足够排查信息,例如 `requestId`、阶段、`userId`、`recordId`、分页、耗时、状态。
|
||
- 上游错误、内部响应、图床响应、数据库异常等细节只允许写入服务端日志或数据库排查字段,不允许通过接口 `msg`、`data`、历史详情等返回给前端。
|
||
- 生图日志阶段建议保持清晰:
|
||
- `read_body`
|
||
- `read_user_id`
|
||
- `create_running_record`
|
||
- `get_api_key`
|
||
- `call_image_stream_api`
|
||
- `finish_success_record`
|
||
- 图床归档改为响应后的后台日志阶段,继续打印 `recordId`、归档是否成功、失败摘要等信息,但不影响主请求返回。
|
||
- 不记录密码、cookie、完整 API key、Lsky token、完整 prompt、图片二进制、base64。
|
||
- 全局请求日志中间件只记录 method、path、status、耗时,不记录请求体和响应体。
|
||
|
||
## 请求、响应与错误处理
|
||
|
||
- 所有 JSON 接口先校验请求体:只接受 JSON 对象或 `null`,数组、字符串等无效 body 直接返回 400。
|
||
- 对上游透传字段必须使用白名单数组过滤,只透传类型正确且文档声明的字段。
|
||
- 统一使用 `createSuccessResponse`、`createErrorResponse` 返回结构。
|
||
- `createSuccessResponse` 的 `msg` 只写本地业务结果,例如“登录成功”“生图完成”“环境已准备”。
|
||
- `createErrorResponse` 的 `msg` 只写本地业务错误,例如“未登录”“请求体必须是 JSON 对象”“生图记录不存在”。
|
||
- 上游或内部异常统一通过 `createUpstreamErrorResponse` 转换,并固定对前端返回“服务器内部错误”;具体异常必须在调用前写入服务端日志。
|
||
- 不把上游 `message`、`statusMessage`、响应体、错误 data、完整响应对象、图床错误信息、数据库错误信息透出给前端。
|
||
- 401 或登录态失效时统一清理本地 auth cookie。
|
||
- 不在 handler 内重复实现复杂错误解析,通用判断放到 `server/utils`。
|
||
|
||
## 类型规范
|
||
|
||
- 用户相关请求/响应类型放在 `shared/types/user.ts`。
|
||
- 公共响应类型放在 `shared/types/index.ts`。
|
||
- 生图、历史、统计、OpenAI 基础调用类型放在 `shared/types/openai.ts`。
|
||
- 只有前后端共享的接口契约放入 `shared/types`;服务端内部上游适配类型留在 `server/utils` 或对应 handler 内。
|
||
- 新增或修改接口时先更新共享类型,再更新服务端和前端调用。
|
||
- 类型字段应有中文注释,说明含义、可选性和是否可能为空。
|
||
|
||
## 前端接入约定
|
||
|
||
- 用户服务在 `app/services/user_service.ts`,图片服务在 `app/services/image_service.ts`。
|
||
- 登录成功后可以调用 `UserReady()` 准备环境;`/api/auth/me` 恢复登录状态时不自动调用 ready。
|
||
- 当前首页生图组件为 `app/components/ImageGenerateCom.vue`,优先展示响应中的 `imageUrl`。
|
||
- `hostedImageUrl` 不在生成接口中返回,只在历史记录中作为归档结果字段保留;后续需要切换展示来源时再调整前端策略。
|
||
|
||
## 代码风格
|
||
|
||
- TypeScript 严格类型优先,避免 `any` 扩散。
|
||
- 新增通用逻辑优先抽到 `server/utils`,handler 只负责入参校验、调用和统一响应。
|
||
- 修改优先小步快改,不重构无关代码。
|
||
- `server/` 下每个文件顶部必须有 `// server/... - 用途说明` 注释。
|
||
- 复杂 `defineEventHandler` 上方必须写清楚接口流程、鉴权边界、数据是否入库、哪些信息不会返回前端。
|
||
- 注释使用中文,描述意图与边界,不写机械解释。
|
||
- 不删除已有有价值注释;确需重写文件时,要保留或迁移原注释表达的信息。
|
||
|
||
## AI 执行清单
|
||
|
||
当 AI 新增或调整 API 时,按以下顺序执行:
|
||
|
||
1. 先阅读现有 handler、utils、shared types、Prisma schema,确认真实实现。
|
||
2. 更新共享类型和服务端内部类型。
|
||
3. 在 handler 中完成请求体校验、白名单过滤、鉴权和日志。
|
||
4. 将可复用业务逻辑抽到 `server/utils`。
|
||
5. 涉及数据库时同步修改 Prisma schema、migration、`create_tables.sql`。
|
||
6. 确认前端响应中的 `msg`、`data`、历史详情不包含上游/internal 细节;内部错误统一显示“服务器内部错误”。
|
||
7. 确认敏感信息不进入前端响应、日志或数据库不该保存的字段。
|
||
8. 运行 `pnpm typecheck`;涉及 Prisma schema 时运行 `prisma generate`,必要时同步数据库。
|
||
|
||
## 非目标
|
||
|
||
- 不为未来不确定需求预建兼容路由。
|
||
- 不引入与当前需求无关的抽象层。
|
||
- 不在多个地方维护同一配置值。
|
||
- 不保存生成图片 base64。
|
||
- 不把服务端密钥、session 或 token 暴露给前端。
|