flnxi-Wu/lib/template-validator.ts
WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

472 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.

/**
* 模板验证工具
*
* 提供统一的模板验证逻辑,支持内置模板和自定义模板
*
* @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<TemplateValidationResult> {
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<Map<string, TemplateValidationResult>> {
const startTime = Date.now()
const results = new Map<string, TemplateValidationResult>()
// 分离内置模板和自定义模板
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
}