Files
color_full/docs/api-reference.md

1077 lines
26 KiB
Markdown
Raw Normal View History

# 配方研发智能平台 — 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>
```
**公开接口**(无需 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 字节,格式 `<salt_hex>:<hash_hex>`)。
### 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 <token>
```
**响应** `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* (0100) |
| 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 | 0100 | 铺展性 |
| sensoryIndex.absorption | 0100 | 吸收速度 |
| sensoryIndex.stickiness | 0100 | 黏腻度 |
| sensoryIndex.overall | 0100 | 综合肤感 |
| stabilityScore | 0100 | 稳定性评分 |
| costEstimate | 数值 | 估算单位成本(元/kg |
| confidence | 01 | 预测置信度 |
**错误流** `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
```