Files
color_full/backend/prisma/schema.prisma
qichi.liang c58ca26969 企业级重构:四层模块化架构 + RBAC授权 + 安全加固 + 颜色引擎/配方推演增强
架构
- 后端从 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
2026-05-21 17:29:52 +08:00

188 lines
5.3 KiB
Plaintext

generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
enum UserRole {
engineer
admin
}
enum IngredientCategory {
emulsifier
humectant
thickener
preservative
antioxidant
fragrance
colorant
ph_adjuster
sunscreen
surfactant
emollient
other
}
model User {
id String @id @default(uuid())
username String @unique
passwordHash String @map("password_hash")
role UserRole @default(engineer)
createdAt DateTime @default(now()) @map("created_at")
formulas Formula[] @relation("FormulaCreator")
colorFormulas ColorFormula[] @relation("ColorFormulaCreator")
formulaVersions FormulaVersion[]
projects Project[]
@@map("users")
}
model Project {
id String @id @default(uuid())
name String
description String?
createdBy String @map("created_by")
createdAt DateTime @default(now()) @map("created_at")
creator User @relation(fields: [createdBy], references: [id])
formulas Formula[]
@@map("projects")
}
model Formula {
id String @id @default(uuid())
name String
description String?
projectId String? @map("project_id")
currentVersion Int @default(1) @map("current_version")
createdBy String @map("created_by")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
embedding Unsupported("vector(1536)")?
project Project? @relation(fields: [projectId], references: [id])
creator User @relation("FormulaCreator", fields: [createdBy], references: [id])
versions FormulaVersion[]
colorFormulas ColorFormula[]
@@index([projectId])
@@index([createdBy])
@@map("formulas")
}
model FormulaVersion {
id String @id @default(uuid())
formulaId String @map("formula_id")
versionNumber Int @map("version_number")
description String?
snapshotData Json @map("snapshot_data")
createdBy String @map("created_by")
createdAt DateTime @default(now()) @map("created_at")
formula Formula @relation(fields: [formulaId], references: [id])
creator User @relation(fields: [createdBy], references: [id])
phases Phase[]
ingredients FormulaIngredient[]
@@unique([formulaId, versionNumber])
@@index([formulaId])
@@map("formula_versions")
}
model Ingredient {
id String @id @default(uuid())
inciName String @map("inci_name")
chineseName String @map("chinese_name")
functionCategory IngredientCategory @map("function_category")
supplier String?
unit String @default("kg")
unitPrice Decimal? @map("unit_price") @db.Decimal(10, 2)
description String?
createdAt DateTime @default(now()) @map("created_at")
formulaIngredients FormulaIngredient[]
@@index([functionCategory])
@@map("ingredients")
}
model Phase {
id String @id @default(uuid())
name String
formulaId String @map("formula_id")
sortOrder Int @default(0) @map("sort_order")
formulaVersion FormulaVersion @relation(fields: [formulaId], references: [id])
ingredients FormulaIngredient[]
@@index([formulaId])
@@map("phases")
}
model FormulaIngredient {
id String @id @default(uuid())
formulaVersionId String @map("formula_version_id")
phaseId String? @map("phase_id")
ingredientId String @map("ingredient_id")
percentage Decimal @db.Decimal(5, 2)
processNotes String? @map("process_notes")
formulaVersion FormulaVersion @relation(fields: [formulaVersionId], references: [id])
phase Phase? @relation(fields: [phaseId], references: [id])
ingredient Ingredient @relation(fields: [ingredientId], references: [id])
@@index([formulaVersionId])
@@index([ingredientId])
@@map("formula_ingredients")
}
model ColorFormula {
id String @id @default(uuid())
name String
targetLab Json @map("target_lab")
actualLab Json? @map("actual_lab")
deltaE Float? @map("delta_e")
colorantComposition Json? @map("colorant_composition")
formulaId String? @map("formula_id")
createdBy String @map("created_by")
createdAt DateTime @default(now()) @map("created_at")
formula Formula? @relation(fields: [formulaId], references: [id])
creator User @relation("ColorFormulaCreator", fields: [createdBy], references: [id])
@@index([formulaId])
@@index([createdBy])
@@map("color_formulas")
}
model AiAuditLog {
id String @id @default(uuid())
capability String
modelName String @map("model_name")
promptHash String @map("prompt_hash")
tokensUsed Int? @map("tokens_used")
durationMs Int? @map("duration_ms")
createdAt DateTime @default(now()) @map("created_at")
@@index([capability])
@@index([createdAt])
@@map("ai_audit_logs")
}
model PantoneColor {
id String @id @default(uuid())
code String @unique
name String
L Float
a Float
b Float
@@map("pantone_colors")
}