diff --git a/docs/checks/task-11.md b/docs/checks/task-11.md index 84bac5c..d3775fc 100644 --- a/docs/checks/task-11.md +++ b/docs/checks/task-11.md @@ -14,6 +14,7 @@ - `server/src/handlers/ai.ts`(重写):配置有效 → 调用 AI 并返回 `source:'ai'`;未配置 → `source:'fallback'`(「AI 尚未配置」);超时 → `source:'fallback'`(「AI 请求超时」);其余失败 → `source:'fallback'`(「AI 讲解暂时不可用」)。回退内容为题库解析 + 标签 + 答案,前端可识别。 - `server/src/schemas/api.ts`:`AiExplainBodySchema.userAnswer` 改为可选(从错题详情进入且无作答历史时省略)。 - `server/.env.example`(新增):记录 PORT / DATA_DIR / AI 配置项说明。 +- `server/src/env.ts` + `server.ts`(补充):引入 dotenv,启动时最先加载 `server/.env`(进程内环境变量优先),确保 `PORT` / `DATA_DIR` / `AI_*` 在模块读取前就位;实测 `server/.env` 中配置 AI 变量后 `aiConfigured=true` 且讲解走 AI,删除 `.env` 后自动回退。 ### 前端 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f654bee..cf9e453 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@fastify/swagger-ui': specifier: ^6.1.1 version: 6.1.1 + dotenv: + specifier: ^17.4.2 + version: 17.4.2 fastify: specifier: latest version: 5.12.1 @@ -717,6 +720,10 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -1808,6 +1815,8 @@ snapshots: detect-libc@2.1.2: {} + dotenv@17.4.2: {} + entities@7.0.1: {} esbuild@0.28.2: diff --git a/server/.env.example b/server/.env.example index a4f1520..8a38f9f 100644 --- a/server/.env.example +++ b/server/.env.example @@ -3,7 +3,7 @@ PORT=3000 # DATA_DIR=./data # AI 讲解(OpenAI 兼容 Chat Completions;不配置时讲解自动回退题库解析) -# AI_BASE_URL=https://api.openai.com/v1 -# AI_API_KEY=sk-xxxxxxxx -# AI_MODEL=gpt-4o-mini +AI_BASE_URL=https://api.deepseek.com +AI_API_KEY=sk-xxxxxxxx +AI_MODEL=deepseek-v4-flash # AI_TIMEOUT_MS=20000 diff --git a/server/package.json b/server/package.json index 74a3c5a..32af2f5 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,7 @@ "@fastify/cors": "latest", "@fastify/swagger": "^9.8.1", "@fastify/swagger-ui": "^6.1.1", + "dotenv": "^17.4.2", "fastify": "latest", "zod": "latest" }, diff --git a/server/src/env.ts b/server/src/env.ts new file mode 100644 index 0000000..eb7e3e0 --- /dev/null +++ b/server/src/env.ts @@ -0,0 +1,11 @@ +import { config } from 'dotenv' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +/** + * 启动时最先加载 server/.env(进程内已有环境变量优先,dotenv 默认不覆盖)。 + * 必须在任何读取 process.env 的模块(如 data/files.ts)之前导入, + * 否则 DATA_DIR / AI_* 等配置会读到空值。 + */ +const serverRoot = join(dirname(fileURLToPath(import.meta.url)), '..') +config({ path: join(serverRoot, '.env') }) diff --git a/server/src/server.ts b/server/src/server.ts index cfa85e3..8080dd9 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,3 +1,6 @@ +// 最先加载 server/.env(dotenv),确保 DATA_DIR / PORT / AI_* 在模块读取前就位 +import './env.js' + import { pathToFileURL } from 'node:url' import Fastify from 'fastify' import cors from '@fastify/cors'