/** * 模板验证工具 * * 提供统一的模板验证逻辑,支持内置模板和自定义模板 * * @module template-validator * @created 2025-12-13 */ import { prisma } from '@/lib/prisma' // 内置模板key列表 export const BUILTIN_TEMPLATE_KEYS = [ '3dmax-material', 'psd-material', 'audio-material', 'video-material', 'model-material', 'other-material', ] as const export type BuiltinTemplateKey = typeof BUILTIN_TEMPLATE_KEYS[number] /** * 模板状态枚举 */ export enum TemplateStatus { DRAFT = 'draft', ACTIVE = 'active', ARCHIVED = 'archived', } /** * 模板验证结果 */ export type TemplateValidationResult = { valid: boolean isBuiltin: boolean templateKey?: string templateName?: string templateStatus?: string error?: string errorCode?: string validationTime?: number } /** * 模板验证错误代码 */ export enum TemplateValidationError { INVALID_BUILTIN_KEY = 'INVALID_BUILTIN_KEY', TEMPLATE_NOT_FOUND = 'TEMPLATE_NOT_FOUND', TEMPLATE_NOT_ACTIVE = 'TEMPLATE_NOT_ACTIVE', DATABASE_ERROR = 'DATABASE_ERROR', } /** * 验证模板ID是否有效 * * @param templateId - 模板ID(可以是内置模板或自定义模板) * @param options - 验证选项 * @returns 验证结果 * * @example * ```typescript * const result = await validateTemplateId('builtin-video-material') * if (!result.valid) { * console.error(result.error) * } * ``` */ export async function validateTemplateId( templateId: string | null | undefined, options: { checkStatus?: boolean // 是否检查模板状态,默认true logPerformance?: boolean // 是否记录性能日志,默认true } = {} ): Promise { const { checkStatus = true, logPerformance = true } = options const startTime = Date.now() // 如果没有提供 templateId,视为有效(向后兼容) if (!templateId) { const result: TemplateValidationResult = { valid: true, isBuiltin: false, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation('empty', result) } return result } // 检查是否为内置模板(builtin-* 格式) const isBuiltin = templateId.startsWith('builtin-') if (isBuiltin) { // 内置模板:验证模板key是否在预设列表中 const templateKey = templateId.replace('builtin-', '') as BuiltinTemplateKey if (!BUILTIN_TEMPLATE_KEYS.includes(templateKey)) { const result: TemplateValidationResult = { valid: false, isBuiltin: true, templateKey, error: `无效的内置模板:${templateId}。有效的内置模板包括:${BUILTIN_TEMPLATE_KEYS.map(k => `builtin-${k}`).join(', ')}`, errorCode: TemplateValidationError.INVALID_BUILTIN_KEY, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation(templateId, result) } return result } // 获取内置模板名称 const { BUILTIN_TEMPLATE_NAMES } = await import('@/lib/upload-template-presets') const templateName = BUILTIN_TEMPLATE_NAMES[templateKey] const result: TemplateValidationResult = { valid: true, isBuiltin: true, templateKey, templateName, templateStatus: TemplateStatus.ACTIVE, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation(templateId, result) } return result } // 自定义模板:从数据库验证 try { const template = await prisma.uploadTemplate.findUnique({ where: { id: templateId }, select: { id: true, name: true, status: true, }, }) if (!template) { const result: TemplateValidationResult = { valid: false, isBuiltin: false, error: `自定义模板不存在:${templateId}。请检查模板是否已被删除或ID是否正确。`, errorCode: TemplateValidationError.TEMPLATE_NOT_FOUND, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation(templateId, result) } return result } // 检查模板状态 if (checkStatus && template.status !== TemplateStatus.ACTIVE) { const result: TemplateValidationResult = { valid: false, isBuiltin: false, templateName: template.name, templateStatus: template.status, error: `模板"${template.name}"当前状态为"${template.status}",无法使用。请联系管理员启用该模板。`, errorCode: TemplateValidationError.TEMPLATE_NOT_ACTIVE, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation(templateId, result) } return result } const result: TemplateValidationResult = { valid: true, isBuiltin: false, templateName: template.name, templateStatus: template.status, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation(templateId, result) } return result } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误' console.error('[TemplateValidator] Error checking custom template:', { templateId, error: errorMessage, stack: error instanceof Error ? error.stack : undefined, }) const result: TemplateValidationResult = { valid: false, isBuiltin: false, error: `验证模板时发生错误:${errorMessage}`, errorCode: TemplateValidationError.DATABASE_ERROR, validationTime: Date.now() - startTime, } if (logPerformance) { logValidation(templateId, result) } return result } } /** * 记录验证日志 * * @param templateId - 模板ID * @param result - 验证结果 */ function logValidation(templateId: string, result: TemplateValidationResult): void { const logLevel = result.valid ? 'info' : 'warn' const logData = { timestamp: new Date().toISOString(), module: 'template-validator', action: 'validate', templateId, valid: result.valid, isBuiltin: result.isBuiltin, templateKey: result.templateKey, templateName: result.templateName, templateStatus: result.templateStatus, error: result.error, errorCode: result.errorCode, validationTime: result.validationTime, } if (logLevel === 'info') { console.log(JSON.stringify(logData)) } else { console.warn(JSON.stringify(logData)) } // 性能警告:如果验证时间超过100ms if (result.validationTime && result.validationTime > 100) { console.warn(`[TemplateValidator] Slow validation detected: ${result.validationTime}ms for template ${templateId}`) } } /** * 检查模板ID是否为内置模板 * * @param templateId - 模板ID * @returns 是否为内置模板 */ export function isBuiltinTemplate(templateId: string | null | undefined): boolean { if (!templateId) return false return templateId.startsWith('builtin-') } /** * 从内置模板ID提取模板key * * @param templateId - 内置模板ID(如 builtin-video-material) * @returns 模板key(如 video-material) */ export function extractBuiltinTemplateKey(templateId: string): BuiltinTemplateKey | null { if (!isBuiltinTemplate(templateId)) return null const key = templateId.replace('builtin-', '') return BUILTIN_TEMPLATE_KEYS.includes(key as BuiltinTemplateKey) ? (key as BuiltinTemplateKey) : null } /** * 获取模板的完整信息(包括字段) * * @param templateId - 模板ID * @returns 模板信息,如果不存在返回 null * * @example * ```typescript * const template = await getTemplateWithFields('builtin-video-material') * if (template) { * console.log(template.fields) * } * ``` */ export async function getTemplateWithFields(templateId: string) { const startTime = Date.now() try { if (isBuiltinTemplate(templateId)) { // 内置模板需要动态生成 const { createBuiltinTemplate } = await import('@/lib/upload-template-presets') const templateKey = extractBuiltinTemplateKey(templateId) if (!templateKey) { console.warn(`[TemplateValidator] Invalid builtin template key: ${templateId}`) return null } const template = createBuiltinTemplate(templateKey) const duration = Date.now() - startTime console.log(JSON.stringify({ timestamp: new Date().toISOString(), module: 'template-validator', action: 'getTemplateWithFields', templateId, isBuiltin: true, fieldCount: template.fields.length, duration, })) return template } // 自定义模板从数据库读取 const template = await prisma.uploadTemplate.findUnique({ where: { id: templateId }, include: { fields: { orderBy: { orderIndex: 'asc' }, }, }, }) const duration = Date.now() - startTime if (template) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), module: 'template-validator', action: 'getTemplateWithFields', templateId, isBuiltin: false, templateName: template.name, fieldCount: template.fields.length, duration, })) } else { console.warn(`[TemplateValidator] Template not found: ${templateId}`) } return template } catch (error) { const duration = Date.now() - startTime console.error('[TemplateValidator] Error getting template with fields:', { templateId, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, duration, }) return null } } /** * 批量验证多个模板ID * * @param templateIds - 模板ID数组 * @returns 验证结果数组 * * @example * ```typescript * const results = await validateMultipleTemplateIds([ * 'builtin-video-material', * 'custom-template-id' * ]) * ``` */ export async function validateMultipleTemplateIds( templateIds: string[] ): Promise> { const startTime = Date.now() const results = new Map() // 分离内置模板和自定义模板 const builtinIds: string[] = [] const customIds: string[] = [] for (const id of templateIds) { if (isBuiltinTemplate(id)) { builtinIds.push(id) } else { customIds.push(id) } } // 验证内置模板(无需数据库查询) for (const id of builtinIds) { const result = await validateTemplateId(id, { logPerformance: false }) results.set(id, result) } // 批量查询自定义模板(减少数据库往返) if (customIds.length > 0) { try { const templates = await prisma.uploadTemplate.findMany({ where: { id: { in: customIds } }, select: { id: true, name: true, status: true, }, }) const templateMap = new Map(templates.map(t => [t.id, t])) for (const id of customIds) { const template = templateMap.get(id) if (!template) { results.set(id, { valid: false, isBuiltin: false, error: `自定义模板不存在:${id}`, errorCode: TemplateValidationError.TEMPLATE_NOT_FOUND, }) } else if (template.status !== TemplateStatus.ACTIVE) { results.set(id, { valid: false, isBuiltin: false, templateName: template.name, templateStatus: template.status, error: `模板"${template.name}"当前状态为"${template.status}",无法使用`, errorCode: TemplateValidationError.TEMPLATE_NOT_ACTIVE, }) } else { results.set(id, { valid: true, isBuiltin: false, templateName: template.name, templateStatus: template.status, }) } } } catch (error) { console.error('[TemplateValidator] Error in batch validation:', error) // 如果批量查询失败,逐个验证 for (const id of customIds) { const result = await validateTemplateId(id, { logPerformance: false }) results.set(id, result) } } } const duration = Date.now() - startTime console.log(JSON.stringify({ timestamp: new Date().toISOString(), module: 'template-validator', action: 'validateMultiple', totalCount: templateIds.length, builtinCount: builtinIds.length, customCount: customIds.length, validCount: Array.from(results.values()).filter(r => r.valid).length, duration, })) return results }