diff --git a/CLAUDE.md b/CLAUDE.md index 71ca98a..3113f38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,107 +2,156 @@ ## 目标 -本文件用于约束 AI 在本项目内的实现方式,确保代码简洁、可维护、不过度设计。 +本文档用于约束 AI 在本项目内的实现方式,确保代码简洁、可维护、安全,并与当前业务链路保持一致。 ## 核心原则 -- 只做当前需要的功能,不做无意义兼容层。 -- 保持单一入口和单一职责,避免重复路径和重复逻辑。 -- 默认最小改动,优先复用已有工具函数和类型。 -- 注释和文档要清晰,特别是接口字段与错误处理语义。 +- 默认最小改动,优先复用现有工具函数、类型和目录结构。 +- 保持单一入口和单一职责,避免同一业务暴露多套路由或重复实现。 +- 前端不接触 NewAPI session、完整 API Key、Lsky token 等敏感信息。 +- 注释和日志要解释意图、边界和排查信息,不输出密码、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/user 的本地别名路由。 -- 对外路径由前端统一调用 auth 前缀,不允许同一业务暴露两套路由。 +- 认证相关接口统一放在 `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 session、完整 API Key 等敏感信息。 -- 前端通过 /api/auth/me 恢复登录状态,通过 /api/auth/logout 清理登录状态。 -- /api/auth/ready 只负责检查当前账号运行环境是否就绪:存在 name 为 AIArtStudio、status 为 1、未删除的 Token 即视为就绪。 -- ready 检查不到可用 Token 时,由后端使用固定参数创建 AIArtStudio Token。 -- /api/auth/ready 不返回完整 key,也不调用完整 key 获取接口;返回文案使用“环境已准备 / 环境初始化完成”等业务表达,不向前端暴露 API Key 细节。 +- 登录成功后,后端从 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 必须使用 server/utils/fetch.ts 中的统一入口。 -- 普通未登录请求使用 newApiFetch。 -- 需要读取上游响应头的请求使用 newApiFetchRaw,目前主要用于登录时提取 Set-Cookie。 -- 需要 NewAPI 登录态的请求使用 newApiAuthedFetch,由服务端从 httpOnly cookie 中读取 session 和 userId 后补齐 Cookie 与 New-Api-User。 -- 上游基地址只在 fetch.ts 的 BASE_URL 维护一次。 -- 禁止在各个 handler 内重复写 baseURL。 -- 当前策略是硬编码基地址,按项目要求保持简单直接。 +- 调用 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` 作为当前生图主链路。 -## Token 处理规范 +## 生图、图床与历史记录 -- NewAPI Token 相关通用逻辑放在 server/utils/newApiTokens.ts。 -- handler 不直接拼装 Token 列表、创建参数或 ready 判定逻辑。 -- Token 列表接口返回的脱敏 key 只用于后端判断,不透出给前端。 -- 完整 key 获取接口必须单独设计服务端流程,默认不要在登录或 ready 阶段调用。 -- 上游 Token 内部结构类型优先留在 server/utils 内;只有前后端共享的返回契约才放入 shared/types。 +- `POST /api/images/generate` 当前同步等待生图完成后返回,不向前端透传流式进度。 +- 生图成功后返回: + - `imageUrl`:NewAPI 上游返回的图片 URL,前端当前优先展示。 + - `hostedImageUrl`:Lsky 图床归档后的 URL,归档失败时为 `null`。 + - `revisedPrompt`:流式 chat completions 当前没有等价字段,通常为空。 +- 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` 或类似大文本存图方案。 +- 历史列表不返回大体积数据;详情可返回完整上游响应和 Lsky 上传响应。 -## 请求体处理规范 +## 数据库与 Prisma -- 所有接口先校验请求体:只接受 JSON 对象或 null。 -- 对上游透传字段必须使用白名单数组过滤。 -- 仅透传类型正确且文档声明的字段,忽略多余字段。 -- 不做隐式字段转换,不做猜测性补全。 +- 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` 记录全局统计。 +- 统计更新失败只记录日志,不应阻断用户拿到已经生成的图片。 +- 需要变更数据库结构时,同时维护 Prisma migration 和 `create_tables.sql`。 -## 响应与错误处理规范 +## 日志规范 -- 统一使用 createSuccessResponse 和 createErrorResponse 返回结构。 -- 上游异常统一通过 createUpstreamErrorResponse 处理。 -- 错误处理策略:尽量保留上游状态码与原始错误数据,只补统一响应外壳。 -- 避免在每个 handler 内复制复杂的错误解析代码。 +- API handler 使用 `server/utils/logging.ts` 的 `createApiLogger` 和 `toSafeLogError`。 +- 日志必须包含足够排查信息,例如 `requestId`、阶段、`userId`、`recordId`、分页、耗时、状态。 +- 生图日志阶段建议保持清晰: + - `read_body` + - `read_user_id` + - `create_running_record` + - `get_api_key` + - `call_image_stream_api` + - `upload_lsky` + - `finish_success_record` +- 不记录密码、cookie、完整 API key、Lsky token、完整 prompt、图片二进制、base64。 +- 全局请求日志中间件只记录 method、path、status、耗时,不记录请求体和响应体。 + +## 请求、响应与错误处理 + +- 所有 JSON 接口先校验请求体:只接受 JSON 对象或 `null`,数组、字符串等无效 body 直接返回 400。 +- 对上游透传字段必须使用白名单数组过滤,只透传类型正确且文档声明的字段。 +- 统一使用 `createSuccessResponse`、`createErrorResponse` 返回结构。 +- 上游异常统一通过 `createUpstreamErrorResponse` 转换。 +- 401 或登录态失效时统一清理本地 auth cookie。 +- 不在 handler 内重复实现复杂错误解析,通用判断放到 `server/utils`。 ## 类型规范 -- 用户相关请求/响应类型放在 shared/types/user.ts。 -- 公共响应类型放在 shared/types/index.ts。 -- OpenAI 调用基础类型放在 shared/types/openai.ts。 -- 新增接口必须先补类型,再写 handler。 -- 每个类型字段都要有中文注释,说明字段含义与可选性。 -- 仅前端需要感知的接口契约放入 shared/types;服务端内部上游适配类型不要扩散到 shared。 +- 用户相关请求/响应类型放在 `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 扩散。 +- TypeScript 严格类型优先,避免 `any` 扩散。 +- 新增通用逻辑优先抽到 `server/utils`,handler 只负责入参校验、调用和统一响应。 - 修改优先小步快改,不重构无关代码。 -- 保持现有目录结构和命名风格。 -- 新增通用逻辑优先抽到 server/utils,避免在 handler 重复实现。 +- 注释使用中文,描述意图与边界,不写机械解释。 +- 不删除已有有价值注释;确需重写文件时,要保留或迁移原注释表达的信息。 ## AI 执行清单 -当 AI 新增一个 API 时,按以下顺序执行: +当 AI 新增或调整 API 时,按以下顺序执行: -1. 在 shared/types 中定义请求与响应类型,并写字段注释。 -2. 在 server/api/auth 新建对应 handler。 -3. 在 handler 中完成请求体校验和白名单过滤。 -4. 按请求场景选择 newApiFetch、newApiFetchRaw 或 newApiAuthedFetch 调用上游接口。 -5. 可复用的业务逻辑优先抽到 server/utils,handler 只负责入参、调用和统一响应。 -6. 用统一响应工具返回成功与错误结果。 -7. 自检 TypeScript 报错后再结束。 +1. 先阅读现有 handler、utils、shared types、Prisma schema,确认真实实现。 +2. 更新共享类型和服务端内部类型。 +3. 在 handler 中完成请求体校验、白名单过滤、鉴权和日志。 +4. 将可复用业务逻辑抽到 `server/utils`。 +5. 涉及数据库时同步修改 Prisma schema、migration、`create_tables.sql`。 +6. 确认敏感信息不进入前端响应、日志或数据库不该保存的字段。 +7. 运行 `pnpm typecheck`;涉及 Prisma schema 时运行 `prisma generate`,必要时同步数据库。 ## 非目标 - 不为未来不确定需求预建兼容路由。 - 不引入与当前需求无关的抽象层。 - 不在多个地方维护同一配置值。 +- 不保存生成图片 base64。 +- 不把服务端密钥、session 或 token 暴露给前端。 diff --git a/app/components/ImageGenerateCom.vue b/app/components/ImageGenerateCom.vue index 31ddcf1..598ea11 100644 --- a/app/components/ImageGenerateCom.vue +++ b/app/components/ImageGenerateCom.vue @@ -1,6 +1,9 @@