# 配方研发智能平台 — API 参考文档 > 版本 v0.1.0 | 更新时间 2026-05-21 > 后端框架 Fastify 5 + TypeScript | 数据库 PostgreSQL (pgvector) | AI 多 Provider 适配 --- ## 目录 - [1. 通用约定](#1-通用约定) - [2. 认证系统](#2-认证系统-auth) - [3. 成分目录](#3-成分目录-ingredients) - [4. 配方记录](#4-配方记录-formulas) - [5. 颜色引擎](#5-颜色引擎-color) - [6. AI 配方推演](#6-ai-配方推演-ai) - [7. 项目管理](#7-项目管理-projects) - [8. 配置管理](#8-配置管理-config) - [9. 数据模型](#9-数据模型) - [10. AI 模板说明](#10-ai-模板说明) --- ## 1. 通用约定 ### 1.1 域名与端口 | 环境 | 前端 | 后端 | |------|------|------| | 开发 | `http://localhost:5173` | `http://localhost:3001` | 前端 dev server 自动代理 `/api` 到后端。 ### 1.2 认证方式 所有带 `🔒` 标记的接口需在请求头携带 JWT Token。认证中间件在 `src/app.ts` 中以全局 `preHandler` 方式执行。 ``` Authorization: Bearer ``` **公开接口**(无需 Token): | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/health` | 健康检查 | | POST | `/api/auth/register` | 注册 | | POST | `/api/auth/login` | 登录 | ### 1.3 统一响应信封 ```json { "data": {}, "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 } } ``` 列表接口包含 `pagination`,单条查询仅含 `data`。`DELETE` 返回 `204 No Content`。 ### 1.4 统一错误信封 ```json { "error": "错误描述", "statusCode": 400 } ``` | 状态码 | 含义 | 触发条件 | |--------|------|----------| | 400 | 参数校验失败 | Zod schema 校验不通过 | | 401 | 未认证 | Token 缺失/格式错误/过期/用户不存在 | | 404 | 资源不存在 | 记录未找到 | | 409 | 冲突 | 如删除被配方引用的成分 | | 500 | 服务器错误 | 生产环境仅返回 `Internal Server Error` | ### 1.5 输入校验 所有请求体/查询参数通过 Zod schema 校验(定义于 `src/lib/validation.ts`),校验失败时返回 400 并附带具体字段错误信息,例如: ```json { "error": "name: 配方名称不能为空; phases: 至少需要一个相" } ``` --- ## 2. 认证系统 (auth) **路由前缀** `/api/auth` **源文件** `src/routes/auth.ts` JWT 采用 HMAC-SHA256 (HS256) 签名,密钥读取自环境变量 `JWT_SECRET`,有效期 24 小时。密码哈希采用 `scrypt`(异步,salt 长度 16 字节,输出 64 字节,格式 `:`)。 ### 2.1 POST `/api/auth/register` — 注册 **请求体** | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | username | string | 是 | — | 唯一用户名 | | password | string | 是 | ≥4 位 | 明文密码,服务端哈希存储 | **响应** `201` ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "username": "wang", "role": "engineer", "token": "eyJhbGciOiJIUzI1NiIs..." } } ``` **错误** | 状态码 | 错误信息 | 说明 | |--------|----------|------| | 400 | `用户名和密码为必填项` | 缺少字段 | | 400 | `密码至少4位` | 密码过短 | | 409 | `用户名已存在` | username 重复 | ### 2.2 POST `/api/auth/login` — 登录 **请求体** 同注册,password ≥1 位即可。 **响应** `200` — body 结构同注册返回。 **错误** | 状态码 | 错误信息 | |--------|----------| | 400 | `用户名不能为空` / `密码不能为空` | | 401 | `用户名或密码错误` | ### 2.3 GET `/api/auth/me` — 当前用户信息 ``` Authorization: Bearer ``` **响应** `200` ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "username": "wang", "role": "engineer" } } ``` **错误** `401` — Token 无效或用户已删除。 --- ## 3. 成分目录 (ingredients) `🔒` **路由前缀** `/api/ingredients` **源文件** `src/routes/ingredients.ts` → `src/services/ingredientService.ts` ### 3.1 功能分类枚举 ``` emulsifier — 乳化剂 humectant — 保湿剂 thickener — 增稠剂 preservative — 防腐剂 antioxidant — 抗氧化剂 fragrance — 香精 colorant — 着色剂 ph_adjuster — pH 调节剂 sunscreen — 防晒剂 surfactant — 表面活性剂 emollient — 润肤剂 other — 其他 ``` ### 3.2 GET `/?page=&limit=&search=&category=` — 成分列表 | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | page | int | 1 | 页码 | | limit | int | 20 | 每页数量(最大 100) | | search | string | — | 模糊匹配 INCI 名称和中文名(case-insensitive) | | category | enum | — | 功能分类筛选 | **请求示例** ``` GET /api/ingredients?search=甘油&category=humectant&page=1&limit=10 ``` **响应** `200` ```json { "data": [ { "id": "uuid", "inciName": "Glycerin", "chineseName": "甘油", "functionCategory": "humectant", "supplier": "BASF", "unit": "kg", "unitPrice": 12.5, "description": "天然保湿因子", "createdAt": "2026-05-20T10:00:00.000Z" } ], "pagination": { "page": 1, "limit": 10, "total": 42, "totalPages": 5 } } ``` ### 3.3 GET `/:id` — 成分详情 **响应** `200` `{ "data": { /* 同上单条 */ } }` **错误** `404` — `{ "error": "成分不存在" }` ### 3.4 POST `/` — 创建成分 **请求体** | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | inciName | string | 是 | — | INCI 国际命名 | | chineseName | string | 是 | — | 中文常用名 | | functionCategory | enum | 是 | 见 3.1 | 功能分类 | | supplier | string | 否 | — | 供应商 | | unit | string | 否 | 默认 `"kg"` | 计量单位 | | unitPrice | number | 否 | ≥0 | 单价(元/单位) | | description | string | 否 | — | 备注描述 | ```json { "inciName": "Niacinamide", "chineseName": "烟酰胺", "functionCategory": "humectant", "supplier": "DSM", "unit": "kg", "unitPrice": 180.0, "description": "维生素 B3,美白活性物" } ``` **响应** `201` `{ "data": { /* 创建后的成分对象 */ } }` ### 3.5 PUT `/:id` — 更新成分 **请求体** — 以上字段均可选(partial update)。 ```json { "supplier": "新供应商", "unitPrice": 150.0 } ``` **响应** `200` `{ "data": { /* 更新后的成分 */ } }` **错误** `404` — 成分不存在 ### 3.6 DELETE `/:id` — 删除成分 **响应** `204 No Content` **错误** `404` — 成分不存在 **错误** `409` — `{ "error": "该成分已被配方引用,无法删除", "usageCount": 3 }` > 删除前检查 `FormulaIngredient` 表引用计数,被引用则拒绝删除。 --- ## 4. 配方记录 (formulas) `🔒` **路由前缀** `/api/formulas` **源文件** `src/routes/formulas.ts` → `src/services/formulaService.ts` ### 4.1 配方核心概念 - **配方** (Formula) — 含名称、描述、所属项目、当前版本号 - **相** (Phase) — 工艺阶段分组(如水相、油相、后添加相),有排序 - **成分关联** (FormulaIngredient) — 在某个版本的某个相中,含比例和工艺备注 - **版本** (FormulaVersion) — 每次修改成分后自动生成新版本 ### 4.2 GET `/?page=&limit=&search=&sortBy=&sortOrder=` — 配方列表 | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | page | int | 1 | 页码 | | limit | int | 20 | 每页数量(≤100) | | search | string | — | 名称/描述模糊搜索 | | sortBy | enum | `updatedAt` | `createdAt` / `updatedAt` / `name` | | sortOrder | enum | `desc` | `asc` / `desc` | **响应** `200` ```json { "data": [ { "id": "uuid", "name": "保湿精华液", "description": "清爽型保湿配方", "currentVersion": 2, "createdBy": "uuid", "createdAt": "2026-05-20T10:00:00.000Z", "updatedAt": "2026-05-21T08:30:00.000Z", "project": { "id": "uuid", "name": "夏季新品" } } ], "pagination": { "page": 1, "limit": 20, "total": 50, "totalPages": 3 } } ``` ### 4.3 GET `/:id` — 配方详情 **响应** `200` ```json { "data": { "id": "uuid", "name": "保湿精华液", "description": "清爽型保湿配方", "currentVersion": 2, "createdBy": "uuid", "createdAt": "2026-05-20T10:00:00.000Z", "updatedAt": "2026-05-21T08:30:00.000Z", "project": { "id": "uuid", "name": "夏季新品" }, "versions": [ { "id": "uuid", "versionNumber": 2, "description": "版本 v2", "snapshotData": { "phases": [...] }, "createdBy": "uuid", "createdAt": "2026-05-21T08:30:00.000Z", "phases": [ { "id": "uuid", "name": "水相", "sortOrder": 0, "ingredients": [ { "id": "uuid", "ingredientId": "uuid", "percentage": 75.5, "processNotes": "预先加热至 75°C", "ingredient": { "id": "uuid", "inciName": "Aqua", "chineseName": "水" } } ] }, { "id": "uuid", "name": "后添加相", "sortOrder": 1, "ingredients": [ { "id": "uuid", "ingredientId": "uuid", "percentage": 24.5, "processNotes": "降温至 40°C 后加入", "ingredient": { "id": "uuid", "inciName": "Niacinamide", "chineseName": "烟酰胺" } } ] } ] } ] } } ``` > 仅返回最新版本(`take: 1, orderBy versionNumber desc`),历史版本见 [版本历史接口](#47-版本历史--版本对比暂未暴露为独立-api)。 ### 4.4 POST `/` — 创建配方 **请求体** ```json { "name": "保湿精华液", "description": "夏季保湿配方 v1", "projectId": "uuid或null", "phases": [ { "name": "水相", "sortOrder": 0, "ingredients": [ { "ingredientId": "uuid", "percentage": 80.0, "processNotes": "75°C 加热搅拌" } ] }, { "name": "后添加相", "sortOrder": 1, "ingredients": [ { "ingredientId": "uuid", "percentage": 20.0, "processNotes": "40°C 加入" } ] } ] } ``` **校验规则** - `name` 必填,`phases` 必填且至少 1 个相 - 每个相至少 1 个成分 - 每个成分的 `percentage` 范围 (0, 100] - **所有成分百分比总和必须在 99.5% – 100.5% 之间** **响应** `201` `{ "data": { /* 完整 Formula 对象(含新创建的版本和关联) */ } }` > 内部执行事务:创建 Formula → 创建 FormulaVersion(v1) → 创建 Phase[] → 创建 FormulaIngredient[] → 回查并返回完整对象。 ### 4.5 PUT `/:id` — 更新配方元信息 **请求体** ```json { "name": "新名称", "description": "新描述" } ``` > 仅更新 name/description,不涉及版本变更。 **响应** `200` `{ "data": { /* 更新后的 Formula */ } }` **错误** `404` — 配方不存在 ### 4.6 PUT `/:id/composition` — 更新配方成分(创建新版本) **请求体** — 同创建配方的 `phases` 结构: ```json { "phases": [ { "name": "水相", "sortOrder": 0, "ingredients": [ { "ingredientId": "uuid", "percentage": 70.0 } ] } ] } ``` > 百分比验证规则同创建接口。执行事务:创建新版本号 → 创建 Phase[] → 创建 FormulaIngredient[] → 更新 Formula.currentVersion。 **响应** `200` `{ "data": { /* 含最新版本信息的完整 Formula */ } }` **错误** `400` — 百分比校验失败 **错误** `404` — 配方不存在 ### 4.7 版本历史 & 版本对比 > 前端路由中包含 `/formulas/:id/history` 和 `/formulas/:id/compare` 页面,对应的后端 API 尚未实现为独立端点。当前可通过配方详情接口的 `versions` 字段获取最新版本信息。 ### 4.8 DELETE `/:id` — 删除配方 **响应** `204 No Content` > 级联删除逻辑(事务内顺序执行): > 1. 查询该配方所有 FormulaVersion > 2. 删除各版本下的 Phase 记录 > 3. 删除各版本下的 FormulaIngredient 记录 > 4. 删除所有 FormulaVersion > 5. 删除 Formula **错误** `404` — 配方不存在 --- ## 5. 颜色引擎 (color) `🔒` **路由前缀** `/api/color` **源文件** `src/routes/color.ts` 使用 CIELAB 颜色空间作为内部标准,色差计算采用欧几里得距离作为初步筛选,CIEDE2000 作为精确对比(前端 colorjs.io)。 ### 5.1 POST `/recommend` — AI 配色推荐 **请求体** ```json { "targetLab": { "L": 65.0, "a": 15.0, "b": -8.0 } } ``` **处理流程** 1. 查询所有已有 ColorFormula 记录 2. 按欧几里得距离(Lab 空间中)排序取 Top 5 3. 调用 AI `recommendColorants` 生成色浆组合推荐 **响应** `200` ```json { "recommendations": [ { "colorants": [ { "name": "氧化铁红", "ratio": 0.35 }, { "name": "二氧化钛", "ratio": 0.5 }, { "name": "氧化铁黄", "ratio": 0.15 } ], "predictedDeltaE": 1.2, "confidence": 0.85 } ], "matchedFormulas": [ { "id": "uuid", "name": "珊瑚粉底液 #3", "deltaE": 0.8 } ] } ``` ### 5.2 GET `/formulas/match?L=&a=&b=&limit=` — 颜色配方匹配 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | L | number | 是 | — | CIELAB L* (0–100) | | a | number | 是 | — | CIELAB a* | | b | number | 是 | — | CIELAB b* | | limit | int | 否 | 5 | 返回数量(≤20) | **响应** `200` ```json { "data": [ { "id": "uuid", "name": "玫瑰豆沙色", "targetLab": { "L": 55, "a": 25, "b": 5 }, "deltaE": 0.5, "colorantComposition": [...], "distance": 1.23 } ] } ``` ### 5.3 POST `/formulas` — 保存颜色配方 **请求体** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | name | string | 否 | 默认 `"未命名颜色配方"` | | targetLab | object | 是 | `{ L: 0-100, a: number, b: number }` | | actualLab | object | 否 | 实际测得 Lab | | deltaE | number | 否 | ΔE(≥0) | | colorantComposition | any | 否 | 色浆组合(自由格式 JSON) | | formulaId | uuid | 否 | 关联的配方 ID | **响应** `201` `{ "data": { /* ColorFormula 对象 */ } }` --- ## 6. AI 配方推演 (ai) `🔒` **路由前缀** `/api/ai` **源文件** `src/routes/ai.ts` → `src/services/ai/index.ts` `src/services/ai/templates/` 所有 AI 能力通过 LLM API 实现(OpenAI / DeepSeek),支持 Mock 模式(返回预设 JSON)。提示词模板定义在 `src/services/ai/templates/index.ts`。 ### 6.1 POST `/predict-formula` — 预测配方指标(SSE 流) **请求体** ```json { "ingredients": [ { "name": "甘油", "percentage": 5, "category": "humectant" }, { "name": "卡波姆", "percentage": 0.5, "category": "thickener" } ] } ``` > 成分列表至少 1 个。 **响应** `200` `Content-Type: text/event-stream` ``` data: {"type":"result","content":"{\"sensoryIndex\":{\"spreadability\":78,...},...}"} ``` `content` 是 JSON 字符串,解析后格式: ```json { "sensoryIndex": { "spreadability": 78, "absorption": 82, "stickiness": 25, "overall": 75 }, "stabilityScore": 85, "costEstimate": 45.5, "confidence": 0.85, "reasoning": "甘油提供基础保湿,卡波姆增加稠度..." } ``` | 字段 | 范围 | 说明 | |------|------|------| | sensoryIndex.spreadability | 0–100 | 铺展性 | | sensoryIndex.absorption | 0–100 | 吸收速度 | | sensoryIndex.stickiness | 0–100 | 黏腻度 | | sensoryIndex.overall | 0–100 | 综合肤感 | | stabilityScore | 0–100 | 稳定性评分 | | costEstimate | 数值 | 估算单位成本(元/kg) | | confidence | 0–1 | 预测置信度 | **错误流** `data: {"type":"error","content":"..."}` ### 6.2 POST `/explore-formula` — 配方推演(SSE 流) **请求体** — 至少设置一个约束条件(`costLimit`、`targetMetrics` 或 `excludeIngredients`) ```json { "baseFormulaName": "原保湿精华", "baseIngredients": [ { "name": "甘油", "percentage": 5 } ], "costLimit": 50, "keepIngredients": ["甘油", "烟酰胺"], "excludeIngredients": ["酒精", "香精"], "targetMetrics": { "stability": 85, "sensory": 80 } } ``` | 字段 | 类型 | 说明 | |------|------|------| | baseFormulaName | string? | 基础配方名称 | | baseIngredients | array? | 基础成分及比例 | | costLimit | number? | 成本上限(元/kg) | | keepIngredients | string[]? | 必须保留的成分名 | | excludeIngredients | string[]? | 必须排除的成分名 | | targetMetrics | object? | 目标指标(自由 key/value) | **响应** `200` `Content-Type: text/event-stream` ``` data: {"type":"option","option":{...}} data: {"type":"option","option":{...}} data: {"type":"done"} ``` 每个 `option` 对象: ```json { "name": "降本方案 A", "changes": [ { "action": "reduce", "ingredient": "烟酰胺", "oldPercentage": 5, "newPercentage": 3 }, { "action": "add", "ingredient": "丁二醇", "oldPercentage": null, "newPercentage": 2 } ], "predictedMetrics": { "costEstimate": 42.0, "stabilityScore": 82 }, "reasoning": "用丁二醇替代部分烟酰胺可降低成本同时维持保湿效果" } ``` ### 6.3 POST `/extract-formula` — AI 提取配方文本 **请求体** ```json { "text": "水相:去离子水(75%)、甘油(5%)、丁二醇(3%),加热至75度。\n油相:角鲨烷(10%)、乳化蜡(5%),加热至80度。\n后添加:防腐剂(1%)、香精(1%),降温至40度加入。" } ``` **响应** `200` ```json { "data": [ { "inciName": "Aqua", "chineseName": "去离子水", "percentage": 75, "phase": "水相", "processNotes": "加热至75度" }, { "inciName": "Glycerin", "chineseName": "甘油", "percentage": 5, "phase": "水相", "processNotes": "" } ] } ``` **错误** `500` — `{ "error": "AI 提取失败,请重试" }` ### 6.4 GET `/search?q=` — AI 自然语言搜索 | 参数 | 类型 | 说明 | |------|------|------| | q | string(必填) | 自然语言搜索词 | ``` GET /api/ai/search?q=适合敏感肌的保湿配方不含酒精 ``` **处理流程** 1. AI 解析自然语言 → 提取关键词和筛选条件 2. 用关键词执行 PostgreSQL ILIKE 搜索 3. AI 不可用时降级为直接关键词搜索 **响应** `200` ```json { "data": [ { "id": "uuid", "name": "敏感肌保湿乳", "description": "无酒精、无香精", "currentVersion": 1, "project": { "name": "敏感肌系列" } } ], "keywords": ["敏感肌", "保湿", "无酒精"] } ``` --- ## 7. 项目管理 (projects) `🔒` **路由前缀** `/api/projects` **源文件** `src/routes/projects.ts` → `src/services/projectService.ts` ### 7.1 GET `/` — 项目列表 **响应** `200` ```json { "data": [ { "id": "uuid", "name": "夏季新品", "description": "2026 夏季产品线", "createdBy": "uuid", "createdAt": "2026-05-20T10:00:00.000Z", "_count": { "formulas": 5 } } ] } ``` ### 7.2 POST `/` — 创建项目 **请求体** ```json { "name": "夏季新品", "description": "2026 夏季产品线" } ``` **响应** `201` `{ "data": { /* Project 对象 */ } }` ### 7.3 PUT `/:id` — 更新项目 **请求体** ```json { "name": "2026 秋季新品" } ``` **响应** `200` `{ "data": { /* 更新后的 Project */ } }` ### 7.4 DELETE `/:id` — 删除项目 **响应** `204 No Content` --- ## 8. 配置管理 (config) `🔒` **路由前缀** `/api/config` **源文件** `src/routes/config.ts` → `src/lib/configStore.ts` API Key 存储于服务器端 `runtime/config.json` 文件,不通过客户端 localStorage 传输。服务启动时自动加载,运行时可通过接口更新。 ### 8.1 GET `/` — 获取配置状态 **响应** `200` ```json { "aiMock": "true", "hasOpenAI": true, "hasDeepseek": false } ``` > 不返回 API Key 实际值,仅返回是否已配置。 ### 8.2 PUT `/` — 更新配置 **请求体** ```json { "openaiKey": "sk-...", "deepseekKey": "sk-...", "openaiBaseUrl": "https://api.openai.com/v1", "deepseekBaseUrl": "https://api.deepseek.com/v1", "aiMock": "false" } ``` > 所有字段均可选。更新后自动 re-init AI Provider 并清空缓存。 **响应** `200` `{ "ok": true }` ### 8.3 POST `/test` — 测试 AI Provider 连接 **请求体** ```json { "provider": "openai" } ``` `provider` 取值:`"openai"` | `"deepseek"` **响应** `200` ```json { "ok": true, "model": "gpt-4o" } ``` **错误** ```json { "ok": false, "error": "Provider \"openai\" 未配置" } ``` --- ## 9. 数据模型 ### 9.1 实体关系图 ``` User ──1:N──→ Formula ──1:N──→ FormulaVersion ──1:N──→ Phase │ │ │ │ │ 1:N │ │ │ FormulaIngredient ──N:1──→ Ingredient │ │ │ 1:N │ ├──────→ ColorFormula │ └──────→ Project ──1:N──→ Formula ``` ### 9.2 Prisma Schema 摘要 **User** (`users`) - `id` UUID PK - `username` 唯一 - `passwordHash` — scrypt(salt:16, output:64) hex - `role` — `engineer` / `admin` - `createdAt` **Project** (`projects`) - `id` UUID PK - `name` String - `description` String? - `createdBy` → User - `createdAt` **Formula** (`formulas`) - `id` UUID PK - `name` String - `description` String? - `projectId` → Project? - `currentVersion` Int (default 1) - `createdBy` → User - `createdAt` / `updatedAt` - `embedding` vector(1536)? — pgvector 向量,用于语义搜索 **FormulaVersion** (`formula_versions`) - `id` UUID PK - `formulaId` → Formula - `versionNumber` Int — (formulaId, versionNumber) UNIQUE - `description` String? - `snapshotData` JSON — 版本快照 - `createdBy` → User - `createdAt` **Phase** (`phases`) - `id` UUID PK - `formulaId` → FormulaVersion - `name` String - `sortOrder` Int **FormulaIngredient** (`formula_ingredients`) - `id` UUID PK - `formulaVersionId` → FormulaVersion - `phaseId` → Phase - `ingredientId` → Ingredient - `percentage` Float - `processNotes` String? **Ingredient** (`ingredients`) - `id` UUID PK - `inciName` String - `chineseName` String - `functionCategory` enum — 见 3.1 - `supplier` String? - `unit` String (default "kg") - `unitPrice` Float? - `description` String? - `createdAt` **ColorFormula** (`color_formulas`) - `id` UUID PK - `name` String - `targetLab` JSON — { L, a, b } - `actualLab` JSON? — { L, a, b } - `deltaE` Float? - `colorantComposition` JSON? - `formulaId` → Formula? - `createdBy` → User - `createdAt` **AIAuditLog** (`ai_audit_logs`) - `id` UUID PK - `capability` String — 调用的 AI 能力名 - `modelName` String — 使用的模型 - `promptHash` String — 提示词哈希 - `tokensUsed` Int? - `durationMs` Int? - `createdAt` --- ## 10. AI 模板说明 所有 AI 调用使用预定义提示词模板(`src/services/ai/templates/index.ts`),JSON 模式约束输出格式。 ### 10.1 模板-能力映射 | 模板函数 | 能力 | system prompt 角色 | |----------|------|--------------------| | `predictMetricsPrompt` | 预测配方指标 | 资深化妆品配方工程师 | | `parseNLQueryPrompt` | 自然语言搜索 | 查询解析器 | | `generateFormulaPrompt` | 配方推演 | 资深化妆品配方工程师 | | `recommendColorantsPrompt` | 配色推荐 | 化妆品色彩专家 | | `extractFormulaPrompt` | 配方文本提取 | 数据结构化提取 | ### 10.2 AI Service 架构 ``` Route (ai.ts) │ ▼ AIService (services/ai/index.ts) ├── LRUCache (200 条, 带 TTL) ├── RateLimiter (10 req/s) ├── 重试 (最多 3 次) ├── fallback → mock 模式 └── audit log → ai_audit_logs 表 │ ▼ AIProvider 接口 ├── createOpenAIProvider (api.openai.com/v1) └── createDeepSeekProvider (api.deepseek.com/v1) ``` **Mock 模式**:当 `AI_MOCK=true` 或 AI API 不可用时,返回预设数据(定义在 `MOCK_RESPONSES` 中)。 ### 10.3 可用 Provider | Provider | 默认模型 | Base URL | |----------|----------|----------| | openai | gpt-4o | `https://api.openai.com/v1` | | deepseek | deepseek-chat | `https://api.deepseek.com/v1` | > 通过 `PUT /api/config` 切换,需提供对应 API Key。可通过 `POST /api/config/test` 验证连接。 ### 10.4 缓存策略 `LRUCache` 容量 200 条,Key 为 `sha256(prompt)`,默认 TTL 根据能力不同设置。`reload()` 时清空全部缓存。 --- ## 附录 ### A. 环境变量 | 变量 | 默认值 | 说明 | |------|--------|------| | `PORT` | 3001 | 后端端口 | | `DATABASE_URL` | `postgresql://colorfull:colorfull@localhost:5432/colorfull` | 数据库连接 | | `JWT_SECRET` | `dev-secret-change-me` | JWT 签名密钥(生产环境必须覆盖) | | `AI_DEFAULT_MODEL` | `deepseek-chat` | 默认 AI 模型 | | `AI_MOCK` | `true` | 是否启用 Mock 模式 | | `OPENAI_API_KEY` | — | OpenAI API Key | | `DEEPSEEK_API_KEY` | — | DeepSeek API Key | | `OPENAI_BASE_URL` | — | OpenAI 自定义 Base URL | | `DEEPSEEK_BASE_URL` | — | DeepSeek 自定义 Base URL | ### B. Docker 服务 ```bash # 启动 PostgreSQL (pgvector) + MinIO docker compose up -d # 数据库连接信息 Host: localhost:5432 Database: colorfull User: colorfull Password: colorfull ``` ### C. 项目脚本 ```bash # 后端 cd backend pnpm dev # 开发模式 (tsx watch) pnpm test # 运行测试 pnpm db:migrate # Prisma 迁移 pnpm db:seed # 种子数据 # 前端 cd frontend pnpm dev # Vite 开发服务器 pnpm build # 生产构建 pnpm test # 运行测试 pnpm lint # ESLint ```