feat: 新增用户管理
ocs-nuxt / deploy (push) Successful in 1m46s

This commit is contained in:
2026-05-24 23:26:43 +08:00
parent 3c467df01d
commit 90412a3688
22 changed files with 1620 additions and 114 deletions
+170 -98
View File
@@ -1,131 +1,203 @@
# CLAUDE.md
## 目标
## 项目定位
文档用于约束 AI 在本项目内的实现方式,确保代码简洁、可维护、安全,并与当前 Nuxt、Nitro、Prisma 项目结构保持一致
项目是基于 Nuxt 4 / Nitro 的 OCS AI 答题服务,兼容 OCS AnswererWrapper 的题库接口,同时提供网页登录、答题工作台、Dashboard、个人 API 配置和 superadmin 系统配置后台
核心技术栈:
- Nuxt 4、Vue 3、Pinia、VueUse、Tailwind CSS v4、shadcn-vue / reka-ui、lucide-vue-next。
- Better Auth 邮箱密码登录,Prisma 7 + `@prisma/adapter-mariadb` 连接 MySQL/MariaDB。
- OpenAI 兼容 Chat Completions 流式调用;对 OCS 客户端仍返回普通 JSON。
- pnpm 10.33.0Node 22 Alpine 用于 Docker 镜像。
## 核心原则
- 默认最小改动,优先复用现有目录结构、工具函数、类型定义和项目约定
- 保持单一入口和单一职责,避免同一业务暴露多套路由或重复实现
- 前端不接触数据库连接串、session、完整 API Key、token、cookie 等敏感信息
- 所有返回给前端的 `msg` 必须使用本地业务表达,不暴露内部服务名、第三方来源、token、key、连接串或调试细节
- 内部异常统一返回“服务器内部错误”;具体异常写入服务端日志
- 注释和日志要解释意图、边界和排查信息,不输出密码、cookie、完整 key、token、图片二进制、base64 或完整敏感请求内容
- 已存在的说明性注释不要随意删除;确需重写文件时,保留或迁移原注释表达的信息
- 不要写任何可能阻塞整个进程的代码。
- 不为未来不确定需求预建兼容路由、抽象层或配置项。
- 默认最小改动,优先复用现有目录结构、工具函数、共享类型、store 和 service
- 前端不接触数据库连接串、session 细节、完整 API Key、token、cookie 或上游响应体
- API 返回给前端或 OCS 的 `msg` 必须是本地业务表达,不暴露 OpenAI、Better Auth、Prisma、token、key、连接串或调试细节
- 内部异常统一返回“服务器内部错误”;具体异常只能进入服务端安全日志
- 日志记录 requestId、阶段、用户/记录标识、分页、耗时、状态等排查信息;不得记录密码、cookie、完整 key、token、完整 prompt、图片二进制、base64、完整请求体或响应体
- 不写可能阻塞整个进程的代码;流、DB、外部请求都要有明确边界或错误出口
- 不为未来不确定需求预建兼容路由、抽象层、配置项或多套入口
## 目录结构约定
- 前端应用代码放在 `app/`
- `app/assets/`:需要被构建工具处理的资源
- `app/components/`Vue 组件。
- `app/composables/`Vue composables
- `app/layouts/`:页面布局组件
- `app/middleware/`:前端路由中间件
- `app/pages/`文件路由页面
- `app/plugins/`Nuxt 应用插件
- `app/services/`:前端服务封装,按业务拆分
- `app/stores/`Pinia store,按业务拆分
- `app/utils/`:前端通用工具
- 服务端代码放在 `server/`
- `server/api/`JSON API 路由
- `server/routes/`:服务端非 API 路由,例如动态 sitemap
- `server/middleware/`:服务端中间件
- `server/plugins/`Nitro 服务端插件。
- `server/utils/`:服务端通用工具和外部服务适配。
- 前后端都需要复用的类型和纯函数放在 `shared/`
- 静态公开文件放在 `public/`,不经过构建处理。
- 不再使用 `@prisma/nuxt`;Prisma 只允许在服务端工具和 API 路由中使用。
- `app/`前端应用代码。
- `app/pages/`:页面路由,当前包括首页答题、Dashboard、登录、注册
- `app/components/`业务组件;`app/components/ui/` 是 shadcn-vue 组件,尽量不要手改生成件。
- `app/services/`:前端 API 封装,组件不要直接写 `$fetch` 调业务接口
- `app/stores/`:Pinia 状态与动作;组件通过 store 消费状态和发起业务操作
- `app/interfaces/`:前端类型出口,主要重导出 `shared/types`
- `app/utils/`前端工具,例如 Better Auth client、错误文案、剪贴板等
- `server/`Nitro 服务端代码
- `server/api/`JSON API 与 Better Auth 路由
- `server/middleware/`服务端中间件,当前包含 API 统一鉴权和注册开关守卫
- `server/plugins/`:Nitro 启动插件,当前用于补全默认系统配置
- `server/utils/`:服务端业务工具、DB、鉴权、OpenAI、日志、配置、答题逻辑。
- `shared/`:前后端共享类型和纯函数。新增或修改接口契约时优先更新这里
- `prisma/schema.prisma`:数据库结构唯一来源;Prisma Client 输出到 `prisma/generated`,不要改回 `app/generated`
- `public/`:静态公开文件,不经过构建处理
## API 路由规范
## 认证与权限
- API 路由按业务模块放在 `server/api/<module>/` 或明确的 `server/api/*.ts` 文件中
- 不为同一能力创建多个本地别名路由
- 临时排查接口不要长期保留,例如数据库健康检查类接口排查结束后必须删除
- Handler 只负责入参校验、鉴权、调用业务函数和统一响应;可复用逻辑放入 `server/utils`
- 新增或修改接口时,同步维护共享类型、服务端实现和前端调用。
- Better Auth 实例在 `server/utils/auth.ts`,使用 Prisma MySQL/MariaDB 适配器,只启用邮箱密码登录
- 注册用户时通过 Better Auth `databaseHooks` 自动生成 32 位 hex `apiToken`
- `apiToken` 供 OCS 油猴脚本跨域调用 `/api/search` 使用;客户端不能自行写入,刷新走 `POST /api/user/refresh-token`
- `server/middleware/api-auth.ts` 默认保护所有 `/api/*` 路由;只有 `publicApiRoutes` 中声明的公开路由放行
- 当前公开路由:
- `/api/auth/**`Better Auth 登录、注册、退出、get-session 等。
- `GET /api/health`:健康检查,不含敏感信息。
- `/api/search`:有自己的 session/apiToken 双通道鉴权。
- `OPTIONS` 预检请求在中间件中直接返回 204。
- superadmin 接口必须在 handler 内二次校验 `role === "superadmin"`,不能只依赖通用 middleware。
- 注册开关由 `allow_registration` 系统配置控制,`server/middleware/registration-guard.ts` 拦截 `POST /api/auth/sign-up/email`
## API 规范
- 新增接口默认需要登录;只有确实公开的接口才能加入 `server/utils/api-auth-rules.ts`
- Handler 只负责入参校验、鉴权/权限边界、调用业务函数和统一响应;复用逻辑放到 `server/utils`
- 非 OCS 接口使用 `server/utils/response.ts`
- 成功:`apiOk(data)`,结构为 `{ code: 0, data, msg: "请求成功" }`
- 失败:`apiErr(code, msg)``data` 固定为 `null`
- `/api/search` 是 OCS 兼容接口,不使用 `apiOk/apiErr`
- 成功固定 `{ code: 1, question, answer }`
- 失败固定 `{ code: 0, msg }`
- 所有 JSON body 先校验为普通对象或 `null`;数组、字符串等无效 body 返回 400。
- 对客户端传入字段必须白名单过滤,只接受当前接口声明的字段和正确类型。
- 新增或修改接口时,同步维护 `shared/types`、服务端实现、前端 service、Pinia store 和组件消费。
- API 响应只返回前端需要的字段,不透出内部响应体、数据库异常、第三方错误详情或完整调试对象。
## 请求、响应与错误处理
## 答题流程与缓存
- 所有 JSON 接口先校验请求体:只接受 JSON 对象或 `null`,数组、字符串等无效 body 直接返回 400
- 对客户端传入和第三方透传字段必须使用白名单过滤,只接受类型正确且当前接口声明的字段
- 成功响应的 `msg` 只写本地业务结果,例如“创建成功”“更新成功”“查询成功”
- 错误响应的 `msg` 只写本地业务错误,例如“未登录”“请求体必须是 JSON 对象”“记录不存在”
- 内部或第三方异常统一转换为“服务器内部错误”,具体异常必须在返回前写入服务端日志
- 401 或登录态失效时统一清理本地 auth cookie
- 不在 handler 内重复实现复杂错误解析,通用判断放到 `server/utils`
- `/api/search` 兼容 GET、JSON POST、urlencoded POST、multipart/form-data
- 鉴权顺序:优先读取网页登录 session;没有 session 时使用请求中的 `token``User.apiToken`
- `title/type/options` 会标准化为 `SearchParams`,只把题目长度、题型和是否有选项写日志
- 缓存不再是进程内内存缓存;答案记录写入 `qa_record`,按 `userId + md5(title|type|options)` 查询最近答案
- 清缓存不删除历史记录,而是更新 `User.cacheClearedAt`;缓存查询只命中该时间之后的记录
- 调 OpenAI 前必须通过 `getUserEffectiveApiConfig(userId)` 取得生效配置,保证系统 key 不会发往用户自定义 endpoint
- 多选题继续使用 `#` 分隔;`server/utils/answer.ts` 中的 OCS 响应格式不可随意改动
## 配置模型
- OpenAI 运行配置以数据库 `system_config` 为准,不再依赖 `OPENAI_API_KEY` 等旧环境变量。
- `server/plugins/initConfig.ts` 启动时补全缺失的默认系统配置;不覆盖已有值。
- `server/utils/sysConfig.ts` 对系统配置做 60 秒 TTL 内存缓存;superadmin 更新配置后必须调用 `invalidateSysConfigCache()`
- 系统配置接口:
- `GET /api/admin/system-config`:仅 superadmin,可读,API Key 只返回遮蔽预览。
- `PUT /api/admin/system-config`:仅 superadmin,可写,`openAiApiKey` 为空字符串表示不修改已有 key。
- 用户自定义配置保存在 `user_config.value` JSON 中:
- `GET /api/user/settings`:只返回遮蔽后的 key 预览。
- `POST|PUT /api/user/settings`:白名单字段,空字符串 key 表示不修改旧 key。
- 用户启用自定义配置时,只有 `customApiBase``customApiKey` 同时非空,才使用用户 endpoint + key;否则 base 和 key 完全回退系统配置。
- `DATABASE_URL``BETTER_AUTH_SECRET``BETTER_AUTH_URL` 仍来自环境变量;`.env.example` 只放这些运行必需项。
## 数据库与 Prisma
- Prisma schema 位于 `prisma/schema.prisma`,实际数据库结构以 Prisma 为准。
- 当前使用 Prisma 7 + `@prisma/adapter-mariadb`
- Prisma Client 生成目录为 `prisma/generated`,不要改回 `app/generated`
- Prisma Client 统一从 `server/utils/db.ts` 创建和复用;业务代码不要直接 `new PrismaClient()`
- 数据库连接只在服务端读取 `DATABASE_URL`,前端不允许读取或间接获取连接信息
- 修改数据库结构时,优先使用 Prisma migration;不要绕过 Prisma 手动改表。
- 数据库结构变更的标准流程:
1. 修改 `prisma/schema.prisma`
2. 新增对应 migration,或在全新开发库确认可接受时说明原因后使用 `prisma db push`
3. 运行 `.\node_modules\.bin\prisma.CMD generate` 重新生成客户端。
4. 对实际数据库执行同步命令,并确认 schema 已落库。
5. 运行类型检查;涉及服务启动或构建行为时再运行构建。
- 当前使用 Prisma 7 + `@prisma/adapter-mariadb`Prisma Client 从 `server/utils/db.ts` 创建和复用
- 业务代码不要直接 `new PrismaClient()`,不要在前端读取或间接获取数据库连接信息
- `DATABASE_URL` 只允许服务端读取;日志和响应都不能输出完整连接串
- `prisma.config.ts` 在 Prisma 命令加载时需要 `DATABASE_URL`Docker build 使用占位值,运行时由容器环境变量覆盖
- 修改数据库结构的标准流程:
1. 先阅读 `prisma/schema.prisma`、相关 handler、`server/utils` 和共享类型。
2. 修改 schema
3. 优先新增 Prisma migration;若当前开发库确认可接受 `prisma db push`,必须说明原因
4. 运行 `.\node_modules\.bin\prisma.CMD generate` 重新生成客户端。
5. 对实际数据库执行同步命令,并确认 schema 已落库。
6. 运行类型检查;涉及服务启动或构建行为时再运行构建。
- 如果 migration 状态和数据库实际结构不一致,先停下来检查原因,不要直接强行部署或 resolve。
- 数据库不要保存大体积二进制、base64 或敏感明文;确需保存排查字段时必须确认不会返回给前端
- 数据库不要保存大体积二进制、base64 或敏感明文;API Key 如需保存,只能服务端读取,前端只看遮蔽预览
## 日志规范
## 前端约定
- 日志必须包含足够排查信息,例如 `requestId`、阶段、用户标识、记录 id、分页、耗时、状态。
- 全局请求日志只记录 method、path、status、耗时,不记录请求体和响应体。
- 内部响应、第三方响应、数据库异常等细节只允许写入服务端日志或服务端排查字段,不允许通过接口返回给前端。
- 不记录密码、cookie、完整 API key、token、完整连接串、完整敏感 prompt、图片二进制或 base64。
- 后台任务失败不能影响主请求成功结果时,应记录失败摘要,并在业务状态中使用本地描述。
## 类型规范
- 只有前后端共享的接口契约放入 `shared/types`
- 服务端内部适配类型留在 `server/utils` 或对应 handler 内。
- 新增或修改接口时先更新共享类型,再更新服务端和前端调用。
- 类型字段应有中文注释,说明含义、可选性和是否可能为空。
- TypeScript 严格类型优先,避免 `any` 扩散;确需使用时必须收敛在边界处。
## 前端接入约定
- 前端服务封装放在 `app/services/`,按业务模块拆分。
- 所有接口请求逻辑和相关状态统一放在对应 Pinia store 中,不在组件内直接调用 service。
- Store 文件放在 `app/stores/` 下,按业务模块单独拆文件。
- 组件只负责 UI 渲染和交互,通过 `useXxxStore()``storeToRefs()` 消费状态,通过 store action 发起操作。
- Store 内部的通用常量作为具名导出,供组件引用,不在组件中重复定义
- 组件不得展示内部服务名、第三方错误、token、key、连接串或原始异常信息
- 接口调用放在 `app/services/`;业务状态、loading、错误文案和 toast 放在 Pinia store
- Store 内部通用常量作为具名导出,供组件引用,不在组件中重复定义
- 认证状态由 `app/plugins/auth.ts` 做 SSR/client 共用初始化,避免首屏水合不一致。
- `/dashboard``app/middleware/auth.ts` 保护,未登录跳 `/login`
- 深色模式通过 `useCookie("color-mode")` 持久化,避免 localStorage 导致 SSR 水合不匹配。
- 页面与组件不得展示内部服务名、第三方错误、token、key、连接串或原始异常信息。
- shadcn-vue 配置见 `components.json`;新增 UI 优先使用现有 `app/components/ui`、lucide 图标和 Tailwind token。
## 代码风格
- 修改优先小步快改,不重构无关代码
- 新增通用逻辑优先抽到 `server/utils``app/utils``shared` 中合适的位置
- `server/` 下新增或大改文件时,文件顶部写一行中文用途说明,格式建议为 `// server/... - 用途说明`
- 复杂 `defineEventHandler` 上方写清楚接口流程、鉴权边界、数据是否入库、哪些信息不会返回前端
- 注释使用中文,描述意图与边界,不写机械解释
- 不删除已有有价值注释
- TypeScript 严格类型优先,避免 `any` 扩散;确需使用时收敛在边界处
- 新增或大改 `server/` 文件时,文件顶部写一行中文用途说明,格式建议为 `// server/... - 用途说明`
- 复杂 `defineEventHandler` 上方写清楚流程、鉴权边界、数据是否入库、哪些信息不会返回前端
- 注释使用中文,描述意图、边界和排查信息,不写机械解释
- 不删除已有有价值注释;重写文件时保留或迁移原注释表达的信息
- import 排序受 `eslint-plugin-simple-import-sort` 约束
- `app/components/ui/**` 在 ESLint 中忽略,通常视为生成代码,不做无关手改。
## 常用命令
- 安装依赖:`pnpm install`
- 开发启动:`pnpm dev`
- 构建:`pnpm build`
- 预览构建产物:`pnpm preview`
- Nuxt 静态生成:`pnpm generate`
- Prisma generate`.\node_modules\.bin\prisma.CMD generate`
- 类型检查:`.\node_modules\.bin\nuxi.CMD typecheck`
- ESLint`.\node_modules\.bin\eslint.CMD .`
执行说明:
- 涉及 Prisma schema 时必须运行 Prisma generate。
- 涉及 API、共享类型、store、组件联动时至少运行类型检查。
- 涉及构建、Nitro 插件、Nuxt 配置、Docker 行为时再运行构建。
- 当前仓库未声明专门的 `test` 脚本,不要假设存在单元测试命令。
## Docker 与部署
- `Dockerfile.base` 基于 `node:22-alpine`,启用 corepack 并激活 pnpm 10.33.0。
- `Dockerfile``ocs-base:latest` 构建应用,复制源码、安装依赖、执行 `pnpm build`,最终用 `node .output/server/index.mjs` 启动。
- `.dockerignore` 排除 `node_modules/``.nuxt/``.output/``prisma/generated/`、本地 env 和日志。
- 容器默认 `PORT=3000``TZ=Asia/Shanghai`
- 构建阶段的 `DATABASE_URL` 是占位值;运行时必须提供真实数据库连接和 Better Auth 环境变量。
## 可用 Skills
本项目本地 `.agents/skills` 包含以下 Better Auth 相关 skills,遇到对应任务时优先查阅:
- `better-auth-best-practices`Better Auth 服务端/客户端、数据库适配、session、插件和环境变量。
- `create-auth-skill`:为 TypeScript/JavaScript 项目脚手架或扩展认证流程。
- `email-and-password-best-practices`:邮箱密码、邮箱验证、密码重置和密码策略。
- `better-auth-security-best-practices`:速率限制、secret、CSRF、trusted origins、cookie/session 安全和审计。
- `organization-best-practices`:组织、多租户、成员、邀请、团队和 RBAC。
- `two-factor-authentication-best-practices`:TOTP、OTP、备份码、可信设备和 2FA 登录流。
当前会话还可用的通用 skills / 插件能力:
- `frontend-design`:构建或美化前端页面、组件、应用体验。
- `openai-docs`:查询 OpenAI 官方文档和最新 API/模型建议。
- `imagegen``hatch-pet`:生成位图视觉素材或 Codex pet。
- `documents:documents``presentations:Presentations``spreadsheets:Spreadsheets`:处理 Word、PPT、表格文件。
- GitHub 插件 skills`github:github``github:gh-address-comments``github:gh-fix-ci``github:yeet`
- `skill-creator``skill-installer``plugin-creator`:创建/安装 Codex skills 或插件。
使用 skills 时先读对应 `SKILL.md`,只加载与当前任务直接相关的引用文件,不要为了普通代码改动批量读取全部资料。
## AI 执行清单
AI 新增或调整 API、数据结构或核心业务逻辑时,按以下顺序执行:
当新增或调整 API、数据结构、认证、配置或核心业务逻辑时,按以下顺序执行:
1. 阅读现有 handler、utils、shared types、Prisma schema,确认真实实现
2. 更新共享类型和服务端内部类型
3. 在 handler 中完成请求体校验、白名单过滤、鉴权和日志
4. 将可复用业务逻辑抽到合适的 `server/utils``app/utils``shared`
5. 涉及数据库时同步修改 Prisma schema、migration,并对实际数据库执行同步
6. 确认前端响应中的 `msg``data` 不包含内部或第三方细节
7. 确认敏感信息不进入前端响应、日志或不该保存的数据库字段
8. 运行类型检查;涉及 Prisma schema 时必须运行 `prisma generate`涉及服务启动或构建行为时再运行构建。
1. 阅读真实实现:handler、middleware、`server/utils``shared/types`、Pinia store、Prisma schema。
2. 明确鉴权边界:公开路由、session、apiToken、superadmin 二次校验
3. 更新共享类型,再更新服务端、前端 service、store 和组件
4. 对请求体做普通对象校验和字段白名单过滤
5. 确认响应 `msg``data` 不包含内部或第三方细节
6. 确认敏感信息不进入前端响应、日志或不该保存的数据库字段
7. 涉及数据库时同步 schema、migration 或 db push、Prisma generate 和实际数据库。
8. 运行类型检查;涉及 Prisma schema 时必须 Prisma generate涉及服务启动或构建行为时再构建。
## 非目标
-为未来不确定需求预建兼容路由
-引入与当前需求无关的抽象层
-恢复旧的 `ACCESS_TOKEN` 单全局令牌模式
-恢复旧的 OpenAI 环境变量配置作为主配置来源
- 不为同一能力创建多个本地别名路由。
- 不新增长期保留的临时排查接口,例如数据库健康检查类接口。
- 不引入与当前需求无关的抽象层、兼容层或配置项。
- 不在多个地方维护同一配置值。
- 不保存大体积 base64敏感明文。
- 不把服务端密钥、session、token、cookie数据库连接信息暴露给前端。
- 不保存大体积 base64敏感明文或上游完整响应体
- 不把服务端密钥、session、token、cookie数据库连接信息或内部错误暴露给前端。