企业级重构:四层模块化架构 + 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
This commit is contained in:
qichi.liang
2026-05-21 17:29:52 +08:00
parent 5240505a2e
commit c58ca26969
99 changed files with 6275 additions and 1353 deletions

View File

@@ -1,5 +1,12 @@
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: '最常用的保湿剂,吸湿性强' },
@@ -18,34 +25,45 @@ const ingredients = [
{ 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: 18.00, description: '食品级防腐剂,酸性环境有效' },
{ inciName: 'Ethylhexylglycerin', chineseName: '乙基己基甘油', functionCategory: IngredientCategory.preservative, supplier: '舒美', unitPrice: 220.00, description: '多功能防腐增效剂' },
{ inciName: 'Tocopherol', chineseName: '生育酚维生素E', functionCategory: IngredientCategory.antioxidant, supplier: '帝斯曼', unitPrice: 320.00, description: '天然抗氧化剂,油溶性' },
{ inciName: 'Ascorbic Acid', chineseName: '抗坏血酸维生素C', functionCategory: IngredientCategory.antioxidant, supplier: '帝斯曼', unitPrice: 450.00, description: '强效抗氧化剂,美白功效' },
{ inciName: 'BHT', chineseName: '丁羟甲苯', functionCategory: IngredientCategory.antioxidant, supplier: '伊士曼', unitPrice: 40.00, description: '合成抗氧化剂,油脂保护' },
{ inciName: 'Dimethicone', chineseName: '聚二甲基硅氧烷', functionCategory: IngredientCategory.emollient, supplier: '道康宁', unitPrice: 45.00, description: '硅油,丝滑肤感' },
{ inciName: 'Caprylic/Capric Triglyceride', chineseName: '辛酸/癸酸甘油三酯', functionCategory: IngredientCategory.emollient, supplier: '巴斯夫', unitPrice: 50.00, description: '轻质润肤酯,铺展性好' },
{ inciName: 'Squalane', chineseName: '角鲨烷', functionCategory: IngredientCategory.emollient, supplier: 'Amyris', unitPrice: 280.00, description: '植物角鲨烷,亲肤极佳' },
{ inciName: 'Isopropyl Myristate', chineseName: '肉豆蔻酸异丙酯', functionCategory: IngredientCategory.emollient, supplier: '禾大', unitPrice: 30.00, description: '渗透促进剂和润肤酯' },
{ inciName: 'Jojoba Oil', chineseName: '霍霍巴籽油', functionCategory: IngredientCategory.emollient, supplier: '德之馨', unitPrice: 150.00, description: '液态蜡酯,与皮脂相似' },
{ inciName: 'Sodium Laureth Sulfate', chineseName: '月桂醇聚醚硫酸酯钠', functionCategory: IngredientCategory.surfactant, supplier: '巴斯夫', unitPrice: 12.00, description: '阴离子表面活性剂,起泡清洁' },
{ inciName: 'Cocamidopropyl Betaine', chineseName: '椰油酰胺丙基甜菜碱', functionCategory: IngredientCategory.surfactant, supplier: '索尔维', unitPrice: 22.00, description: '两性表面活性剂,温和清洁' },
{ inciName: 'Decyl Glucoside', chineseName: '癸基葡糖苷', functionCategory: IngredientCategory.surfactant, supplier: '巴斯夫', unitPrice: 70.00, description: '离子APG表活极温和' },
{ inciName: 'CI 77491', chineseName: '氧化铁红', functionCategory: IngredientCategory.colorant, supplier: '森馨', unitPrice: 85.00, description: '无机颜料,红色系' },
{ inciName: 'CI 77492', chineseName: '氧化铁黄', functionCategory: IngredientCategory.colorant, supplier: '森馨', unitPrice: 85.00, description: '无机颜料,黄色系' },
{ inciName: 'CI 77499', chineseName: '氧化铁黑', functionCategory: IngredientCategory.colorant, supplier: '森馨', unitPrice: 85.00, description: '无机颜料,黑色系' },
{ inciName: 'CI 77891', chineseName: '二氧化钛', functionCategory: IngredientCategory.colorant, supplier: '科', unitPrice: 40.00, description: '白色颜料和物理防晒剂' },
{ inciName: 'Mica', chineseName: '云母', functionCategory: IngredientCategory.colorant, supplier: '默克', unitPrice: 120.00, description: '珠光颜料基材' },
{ inciName: 'Citric Acid', chineseName: '柠檬酸', functionCategory: IngredientCategory.ph_adjuster, supplier: '英轩', unitPrice: 10.00, description: '酸性pH调节剂果酸类' },
{ inciName: 'Sodium Hydroxide', chineseName: '氢氧化钠', functionCategory: IngredientCategory.ph_adjuster, supplier: '万华化学', unitPrice: 8.00, description: '碱性pH调节剂' },
{ inciName: 'Triethanolamine', chineseName: '三乙醇胺', functionCategory: IngredientCategory.ph_adjuster, supplier: '陶氏化学', unitPrice: 20.00, description: 'pH调节剂和中和剂' },
{ inciName: 'Ethylhexyl Methoxycinnamate', chineseName: '甲氧基肉桂酸乙基己酯', functionCategory: IngredientCategory.sunscreen, supplier: '巴斯夫', unitPrice: 65.00, description: 'UVB化学防晒剂' },
{ inciName: 'Bis-Ethylhexyloxyphenol Methoxyphenyl Triazine', chineseName: '双-乙基己氧苯酚甲氧苯基三嗪', functionCategory: IngredientCategory.sunscreen, supplier: '巴斯夫', unitPrice: 550.00, description: '广谱UVA/UVB防晒剂Tinosorb S' },
{ inciName: 'Parfum', chineseName: '香精', functionCategory: IngredientCategory.fragrance, supplier: '奇华顿', unitPrice: 300.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() {
console.log('开始导入成分种子数据...')
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,
@@ -54,6 +72,65 @@ async function main() {
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()