Files
color_full/backend/prisma/seed.ts
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

144 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { prisma } from '../src/lib/prisma.js'
import { IngredientCategory } from '../src/generated/prisma/enums.js'
import { randomBytes, scryptSync } from 'crypto'
function makeHash(password: string): string {
const salt = randomBytes(16)
const hash = scryptSync(password, salt, 64).toString('hex')
return `${salt.toString('hex')}:${hash}`
}
const ingredients = [
{ inciName: 'Glycerin', chineseName: '甘油', functionCategory: IngredientCategory.humectant, supplier: '丰益油脂', unitPrice: 15.00, description: '最常用的保湿剂,吸湿性强' },
{ inciName: 'Butylene Glycol', chineseName: '丁二醇', functionCategory: IngredientCategory.humectant, supplier: '大赛璐', unitPrice: 35.00, description: '多功能保湿剂,兼有防腐增效作用' },
{ inciName: 'Propylene Glycol', chineseName: '丙二醇', functionCategory: IngredientCategory.humectant, supplier: '陶氏化学', unitPrice: 20.00, description: '保湿剂和溶剂' },
{ inciName: 'Sodium Hyaluronate', chineseName: '透明质酸钠', functionCategory: IngredientCategory.humectant, supplier: '华熙生物', unitPrice: 2800.00, description: '高分子保湿剂,锁水能力极强' },
{ inciName: 'Panthenol', chineseName: '泛醇', functionCategory: IngredientCategory.humectant, supplier: '巴斯夫', unitPrice: 180.00, description: '维生素B5前体保湿修复' },
{ inciName: 'Trehalose', chineseName: '海藻糖', functionCategory: IngredientCategory.humectant, supplier: '林原', unitPrice: 120.00, description: '天然保湿因子,保护细胞膜' },
{ inciName: 'Betaine', chineseName: '甜菜碱', functionCategory: IngredientCategory.humectant, supplier: '杜邦', unitPrice: 60.00, description: '天然氨基酸保湿剂' },
{ inciName: 'Cetearyl Alcohol', chineseName: '鲸蜡硬脂醇', functionCategory: IngredientCategory.emulsifier, supplier: '巴斯夫', unitPrice: 25.00, description: 'O/W型乳化剂和增稠剂' },
{ inciName: 'Glyceryl Stearate', chineseName: '甘油硬脂酸酯', functionCategory: IngredientCategory.emulsifier, supplier: '禾大', unitPrice: 30.00, description: '温和乳化剂,自乳化型' },
{ inciName: 'Ceteareth-20', chineseName: '鲸蜡硬脂醇聚醚-20', functionCategory: IngredientCategory.emulsifier, supplier: '巴斯夫', unitPrice: 40.00, description: 'O/W型高效乳化剂' },
{ inciName: 'Polysorbate 60', chineseName: '聚山梨醇酯-60', functionCategory: IngredientCategory.emulsifier, supplier: '禾大', unitPrice: 35.00, description: '亲水性乳化剂' },
{ inciName: 'Lecithin', chineseName: '卵磷脂', functionCategory: IngredientCategory.emulsifier, supplier: '嘉吉', unitPrice: 200.00, description: '天然磷脂乳化剂,仿生亲肤' },
{ inciName: 'Carbomer', chineseName: '卡波姆', functionCategory: IngredientCategory.thickener, supplier: '路博润', unitPrice: 80.00, description: '高效增稠剂,需中和' },
{ inciName: 'Xanthan Gum', chineseName: '黄原胶', functionCategory: IngredientCategory.thickener, supplier: 'CP Kelco', unitPrice: 65.00, description: '天然多糖增稠剂,耐电解质' },
{ inciName: 'Hydroxyethylcellulose', chineseName: '羟乙基纤维素', functionCategory: IngredientCategory.thickener, supplier: '亚什兰', unitPrice: 90.00, description: '非离子增稠剂,透明度好' },
{ inciName: 'Phenoxyethanol', chineseName: '苯氧乙醇', functionCategory: IngredientCategory.preservative, supplier: '巴斯夫', unitPrice: 55.00, description: '广谱防腐剂,最常用之一' },
{ inciName: 'Sodium Benzoate', chineseName: '苯甲酸钠', functionCategory: IngredientCategory.preservative, supplier: '默克', unitPrice: 30.00, description: '酸性防腐剂pH<5.5 有效' },
{ inciName: 'Tocopherol', chineseName: '生育酚维生素E', functionCategory: IngredientCategory.antioxidant, supplier: '巴斯夫', unitPrice: 250.00, description: '天然抗氧化剂,保护配方稳定性' },
{ inciName: 'BHT', chineseName: '丁羟甲苯', functionCategory: IngredientCategory.antioxidant, supplier: '默克', unitPrice: 45.00, description: '合成抗氧化剂,延长保质期' },
{ inciName: 'Fragrance', chineseName: '香精', functionCategory: IngredientCategory.fragrance, supplier: '奇华顿', unitPrice: 120.00, description: '化妆品用香精,供应商定制' },
{ inciName: 'Iron Oxide Red', chineseName: '氧化铁红', functionCategory: IngredientCategory.colorant, supplier: '默克', unitPrice: 85.00, description: '红色颜料,无机着色剂' },
{ inciName: 'Iron Oxide Yellow', chineseName: '氧化铁黄', functionCategory: IngredientCategory.colorant, supplier: '默克', unitPrice: 80.00, description: '黄色颜料,无机着色剂' },
{ inciName: 'Iron Oxide Black', chineseName: '氧化铁黑', functionCategory: IngredientCategory.colorant, supplier: '默克', unitPrice: 75.00, description: '黑色颜料,无机着色剂' },
{ inciName: 'Titanium Dioxide', chineseName: '二氧化钛', functionCategory: IngredientCategory.colorant, supplier: '巴斯夫', unitPrice: 120.00, description: '白色颜料,物理防晒剂' },
{ inciName: 'Triethanolamine', chineseName: '三乙醇胺', functionCategory: IngredientCategory.ph_adjuster, supplier: '陶氏化学', unitPrice: 25.00, description: 'pH调节剂常与卡波姆配合使用' },
{ inciName: 'Citric Acid', chineseName: '柠檬酸', functionCategory: IngredientCategory.ph_adjuster, supplier: '默克', unitPrice: 20.00, description: 'pH调节剂降低体系pH值' },
{ inciName: 'Avobenzone', chineseName: '阿伏苯宗', functionCategory: IngredientCategory.sunscreen, supplier: '巴斯夫', unitPrice: 350.00, description: 'UVA 化学防晒剂,广谱吸收' },
{ inciName: 'Octinoxate', chineseName: '桂皮酸盐', functionCategory: IngredientCategory.sunscreen, supplier: '巴斯夫', unitPrice: 180.00, description: 'UVB 化学防晒剂,最常用之一' },
{ inciName: 'Sodium Lauryl Sulfate', chineseName: '月桂醇硫酸酯钠', functionCategory: IngredientCategory.surfactant, supplier: '禾大', unitPrice: 20.00, description: '阴离子表面活性剂,强力清洁' },
{ inciName: 'Cocamidopropyl Betaine', chineseName: '椰油酰胺丙基甜菜碱', functionCategory: IngredientCategory.surfactant, supplier: '禾大', unitPrice: 35.00, description: '两性表面活性剂,温和清洁' },
{ inciName: 'Caprylic/Capric Triglyceride', chineseName: '辛酸/癸酸甘油三酯', functionCategory: IngredientCategory.emollient, supplier: '巴斯夫', unitPrice: 65.00, description: '中性油脂,铺展性好' },
{ inciName: 'Dimethicone', chineseName: '聚二甲基硅氧烷', functionCategory: IngredientCategory.emollient, supplier: '陶氏化学', unitPrice: 55.00, description: '硅油类润肤剂,赋予顺滑肤感' },
{ inciName: 'Squalane', chineseName: '角鲨烷', functionCategory: IngredientCategory.emollient, supplier: '阿莫科', unitPrice: 200.00, description: '天然润肤剂,亲肤性极佳' },
{ inciName: 'Water', chineseName: '去离子水', functionCategory: IngredientCategory.other, supplier: '自制', unitPrice: 0.10, description: '最常用溶剂,化妆品基础成分' },
{ inciName: 'Alcohol Denat.', chineseName: '变性乙醇', functionCategory: IngredientCategory.other, supplier: '默克', unitPrice: 30.00, description: '溶剂,收剑毛孔,需注意刺激性' },
{ inciName: 'Cyclopentasiloxane', chineseName: '环五聚二甲基硅氧烷', functionCategory: IngredientCategory.emollient, supplier: '陶氏化学', unitPrice: 80.00, description: '挥发性硅油,赋予丝滑肤感' },
{ inciName: 'Allantoin', chineseName: '尿囊素', functionCategory: IngredientCategory.other, supplier: '默克', unitPrice: 100.00, description: '舒缓抗炎成分,促进伤口愈合' },
{ inciName: 'Niacinamide', chineseName: '烟酰胺', functionCategory: IngredientCategory.humectant, supplier: 'DSM', unitPrice: 350.00, description: '维生素B3美白控油多功能活性物' },
{ inciName: 'Salicylic Acid', chineseName: '水杨酸', functionCategory: IngredientCategory.other, supplier: '默克', unitPrice: 60.00, description: '角质剥脱成分,改善粉刺' },
{ inciName: 'Urea', chineseName: '尿素', functionCategory: IngredientCategory.humectant, supplier: '默克', unitPrice: 15.00, description: '天然保湿因子NMF温和去角质' },
]
async function main() {
const adminExists = await prisma.user.findUnique({ where: { username: 'admin' } })
if (adminExists) {
await prisma.user.update({
where: { username: 'admin' },
data: { passwordHash: makeHash('admin123') },
})
} else {
await prisma.user.create({
data: { username: 'admin', passwordHash: makeHash('admin123'), role: 'admin' },
})
}
console.log('管理员账号已就绪 (admin / admin123)')
await prisma.ingredient.createMany({
data: ingredients,
skipDuplicates: true,
})
const count = await prisma.ingredient.count()
console.log(`成分种子数据导入完成!共 ${count} 条记录`)
const pantoneColors = [
{ code: '185 C', name: 'Vibrant Red', L: 48, a: 68, b: 48 },
{ code: '186 C', name: 'Strong Red', L: 44, a: 63, b: 42 },
{ code: '286 C', name: 'Strong Blue', L: 30, a: 12, b: -52 },
{ code: '287 C', name: 'Deep Blue', L: 27, a: 10, b: -48 },
{ code: '354 C', name: 'Bright Green', L: 55, a: -52, b: 28 },
{ code: '355 C', name: 'Deep Green', L: 50, a: -48, b: 24 },
{ code: '109 C', name: 'Golden Yellow', L: 82, a: 8, b: 95 },
{ code: '110 C', name: 'Deep Yellow', L: 76, a: 10, b: 85 },
{ code: '151 C', name: 'Orange', L: 65, a: 40, b: 70 },
{ code: '152 C', name: 'Deep Orange', L: 60, a: 35, b: 62 },
{ code: '205 C', name: 'Pink', L: 58, a: 42, b: -2 },
{ code: '206 C', name: 'Deep Pink', L: 50, a: 48, b: -4 },
{ code: 'Process Black C', name: 'Black', L: 18, a: 1, b: 0 },
{ code: 'Warm Red C', name: 'Warm Red', L: 48, a: 65, b: 42 },
{ code: 'Cool Gray 7 C', name: 'Cool Gray', L: 58, a: 0, b: -2 },
{ code: '7499 C', name: 'Pale Green', L: 72, a: -20, b: 22 },
{ code: '7416 C', name: 'Coral Pink', L: 55, a: 38, b: 18 },
{ code: '7417 C', name: 'Deep Coral', L: 50, a: 42, b: 22 },
{ code: '7421 C', name: 'Rose', L: 42, a: 50, b: -8 },
{ code: '7520 C', name: 'Peach', L: 70, a: 18, b: 28 },
{ code: '7548 C', name: 'Warm Beige', L: 72, a: 8, b: 15 },
{ code: '7610 C', name: 'Light Beige', L: 78, a: 4, b: 10 },
{ code: '7612 C', name: 'Sand', L: 65, a: 6, b: 14 },
{ code: '7614 C', name: 'Taupe', L: 55, a: 4, b: 8 },
{ code: '7621 C', name: 'Rose Gold', L: 62, a: 22, b: 10 },
]
await prisma.pantoneColor.createMany({
data: pantoneColors,
skipDuplicates: true,
})
console.log(`潘通色种子数据导入完成!共 ${pantoneColors.length} 条记录`)
const user = await prisma.user.findUnique({ where: { username: 'admin' } })
if (user) {
const demoIngs = await prisma.ingredient.findMany({ take: 4 })
const formulaExists = await prisma.formula.findFirst({ where: { name: '基础保湿精华(示例)' } })
if (!formulaExists && demoIngs.length >= 2) {
await prisma.$transaction(async (tx) => {
const f = await tx.formula.create({
data: { name: '基础保湿精华(示例)', description: '种子数据示例配方', createdBy: user.id, currentVersion: 1 },
})
const v = await tx.formulaVersion.create({
data: { formulaId: f.id, versionNumber: 1, description: '初始版本', snapshotData: {}, createdBy: user.id },
})
const phase = await tx.phase.create({ data: { name: '水相', formulaId: v.id, sortOrder: 0 } })
await tx.formulaIngredient.createMany({
data: [
{ formulaVersionId: v.id, phaseId: phase.id, ingredientId: demoIngs[0]!.id, percentage: 80 },
{ formulaVersionId: v.id, phaseId: phase.id, ingredientId: demoIngs[1]!.id, percentage: 15 },
{ formulaVersionId: v.id, phaseId: phase.id, ingredientId: demoIngs[2]!.id, percentage: 5 },
],
})
})
console.log('示例配方创建成功')
}
}
}
main()
.catch((e) => {
console.error('种子数据导入失败:', e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})