架构 - 后端从 flat routes/ 重构为 modules/<domain>/ 模块化结构(8个模块) - 四层架构:Route -> Service -> Repository -> Prisma - 新增 shared/ 基础设施(AppError 异常体系、ALS 上下文、prom-client 指标) - 前端 Toast/Skeleton/Alert 组件基建 + formulaService 模板 安全 - JWT 签名算法修复(HS256 用 createHmac 而非 createHash) - 密码哈希 async scrypt + timingSafeEqual - API Key 从 localStorage 迁移至服务端 runtime/config.json - Helmet 安全头 + rate-limit 全局限流 100 req/min - 全局 auth preHandler + RBAC + Ownership 中间件 颜色引擎 - 色匹配切换为 cube 粗筛 + CIEDE2000 精排 - PantoneColor 表 + 种子数据 + 搜索端点 - AI 配色 Prompt 注入成分库 colorant 列表 配方推演 - 本地优化引擎(同 category 替换 + 成本排序) - baseFormulaId 支持 + Pareto 散点图 文档 - ADR-0003 四层架构、ADR-0004 RBAC 授权模型 - 更新 ADR-0001/0002 - api-reference.md(29端点)、project-overview.md 部署 - Dockerfile * 2 + nginx.conf + docker-compose.prod.yml - 健康探针 + 优雅关闭 + pg_dump 备份脚本 - ESLint + Prettier + tsconfig strict
26 KiB
配方研发智能平台 — API 参考文档
版本 v0.1.0 | 更新时间 2026-05-21
后端框架 Fastify 5 + TypeScript | 数据库 PostgreSQL (pgvector) | AI 多 Provider 适配
目录
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 统一响应信封
{
"data": {},
"pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 }
}
列表接口包含 pagination,单条查询仅含 data。DELETE 返回 204 No Content。
1.4 统一错误信封
{ "error": "错误描述", "statusCode": 400 }
| 状态码 | 含义 | 触发条件 |
|---|---|---|
| 400 | 参数校验失败 | Zod schema 校验不通过 |
| 401 | 未认证 | Token 缺失/格式错误/过期/用户不存在 |
| 404 | 资源不存在 | 记录未找到 |
| 409 | 冲突 | 如删除被配方引用的成分 |
| 500 | 服务器错误 | 生产环境仅返回 Internal Server Error |
1.5 输入校验
所有请求体/查询参数通过 Zod schema 校验(定义于 src/lib/validation.ts),校验失败时返回 400 并附带具体字段错误信息,例如:
{ "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
{
"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
{
"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
{
"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 | 否 | — | 备注描述 |
{
"inciName": "Niacinamide",
"chineseName": "烟酰胺",
"functionCategory": "humectant",
"supplier": "DSM",
"unit": "kg",
"unitPrice": 180.0,
"description": "维生素 B3,美白活性物"
}
响应 201 { "data": { /* 创建后的成分对象 */ } }
3.5 PUT /:id — 更新成分
请求体 — 以上字段均可选(partial update)。
{ "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
{
"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
{
"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),历史版本见 版本历史接口。
4.4 POST / — 创建配方
请求体
{
"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 — 更新配方元信息
请求体
{ "name": "新名称", "description": "新描述" }
仅更新 name/description,不涉及版本变更。
响应 200 { "data": { /* 更新后的 Formula */ } }
错误 404 — 配方不存在
4.6 PUT /:id/composition — 更新配方成分(创建新版本)
请求体 — 同创建配方的 phases 结构:
{
"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
级联删除逻辑(事务内顺序执行):
- 查询该配方所有 FormulaVersion
- 删除各版本下的 Phase 记录
- 删除各版本下的 FormulaIngredient 记录
- 删除所有 FormulaVersion
- 删除 Formula
错误 404 — 配方不存在
5. 颜色引擎 (color) 🔒
路由前缀 /api/color
源文件 src/routes/color.ts
使用 CIELAB 颜色空间作为内部标准,色差计算采用欧几里得距离作为初步筛选,CIEDE2000 作为精确对比(前端 colorjs.io)。
5.1 POST /recommend — AI 配色推荐
请求体
{
"targetLab": {
"L": 65.0,
"a": 15.0,
"b": -8.0
}
}
处理流程
- 查询所有已有 ColorFormula 记录
- 按欧几里得距离(Lab 空间中)排序取 Top 5
- 调用 AI
recommendColorants生成色浆组合推荐
响应 200
{
"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
{
"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 流)
请求体
{
"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 字符串,解析后格式:
{
"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)
{
"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 对象:
{
"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 提取配方文本
请求体
{
"text": "水相:去离子水(75%)、甘油(5%)、丁二醇(3%),加热至75度。\n油相:角鲨烷(10%)、乳化蜡(5%),加热至80度。\n后添加:防腐剂(1%)、香精(1%),降温至40度加入。"
}
响应 200
{
"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=适合敏感肌的保湿配方不含酒精
处理流程
- AI 解析自然语言 → 提取关键词和筛选条件
- 用关键词执行 PostgreSQL ILIKE 搜索
- AI 不可用时降级为直接关键词搜索
响应 200
{
"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
{
"data": [
{
"id": "uuid",
"name": "夏季新品",
"description": "2026 夏季产品线",
"createdBy": "uuid",
"createdAt": "2026-05-20T10:00:00.000Z",
"_count": { "formulas": 5 }
}
]
}
7.2 POST / — 创建项目
请求体
{ "name": "夏季新品", "description": "2026 夏季产品线" }
响应 201 { "data": { /* Project 对象 */ } }
7.3 PUT /:id — 更新项目
请求体
{ "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
{
"aiMock": "true",
"hasOpenAI": true,
"hasDeepseek": false
}
不返回 API Key 实际值,仅返回是否已配置。
8.2 PUT / — 更新配置
请求体
{
"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 连接
请求体
{ "provider": "openai" }
provider 取值:"openai" | "deepseek"
响应 200
{ "ok": true, "model": "gpt-4o" }
错误
{ "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)
idUUID PKusername唯一passwordHash— scrypt(salt:16, output:64) hexrole—engineer/admincreatedAt
Project (projects)
idUUID PKnameStringdescriptionString?createdBy→ UsercreatedAt
Formula (formulas)
idUUID PKnameStringdescriptionString?projectId→ Project?currentVersionInt (default 1)createdBy→ UsercreatedAt/updatedAtembeddingvector(1536)? — pgvector 向量,用于语义搜索
FormulaVersion (formula_versions)
idUUID PKformulaId→ FormulaversionNumberInt — (formulaId, versionNumber) UNIQUEdescriptionString?snapshotDataJSON — 版本快照createdBy→ UsercreatedAt
Phase (phases)
idUUID PKformulaId→ FormulaVersionnameStringsortOrderInt
FormulaIngredient (formula_ingredients)
idUUID PKformulaVersionId→ FormulaVersionphaseId→ PhaseingredientId→ IngredientpercentageFloatprocessNotesString?
Ingredient (ingredients)
idUUID PKinciNameStringchineseNameStringfunctionCategoryenum — 见 3.1supplierString?unitString (default "kg")unitPriceFloat?descriptionString?createdAt
ColorFormula (color_formulas)
idUUID PKnameStringtargetLabJSON — { L, a, b }actualLabJSON? — { L, a, b }deltaEFloat?colorantCompositionJSON?formulaId→ Formula?createdBy→ UsercreatedAt
AIAuditLog (ai_audit_logs)
idUUID PKcapabilityString — 调用的 AI 能力名modelNameString — 使用的模型promptHashString — 提示词哈希tokensUsedInt?durationMsInt?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 服务
# 启动 PostgreSQL (pgvector) + MinIO
docker compose up -d
# 数据库连接信息
Host: localhost:5432
Database: colorfull
User: colorfull
Password: colorfull
C. 项目脚本
# 后端
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