1077 lines
26 KiB
Markdown
1077 lines
26 KiB
Markdown
|
|
# 配方研发智能平台 — 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* (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
|
|||
|
|
```
|