186 lines
6.0 KiB
TypeScript
186 lines
6.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getServerSession } from 'next-auth'
|
||
import { authOptions } from '@/lib/auth'
|
||
import { prisma } from '@/lib/prisma'
|
||
import { ensureMaterialLogsTable } from '@/lib/material-log'
|
||
|
||
export const dynamic = 'force-dynamic'
|
||
|
||
/**
|
||
* 获取素材操作日志
|
||
* 包括上传、删除、修改等操作
|
||
*/
|
||
export async function GET(req: NextRequest) {
|
||
try {
|
||
const session = await getServerSession(authOptions)
|
||
|
||
if (!session || (session.user as any).role !== 'ADMIN') {
|
||
return NextResponse.json({ error: '无权限' }, { status: 403 })
|
||
}
|
||
|
||
await ensureMaterialLogsTable()
|
||
|
||
const { searchParams } = new URL(req.url)
|
||
const limit = parseInt(searchParams.get('limit') || '100')
|
||
const offset = parseInt(searchParams.get('offset') || '0')
|
||
|
||
// 获取素材操作日志
|
||
let materialLogs: any[] = []
|
||
try {
|
||
// 尝试使用 Prisma 查询
|
||
materialLogs = await (prisma as any).materialLog.findMany({
|
||
take: limit,
|
||
skip: offset,
|
||
orderBy: { timestamp: 'desc' },
|
||
include: {
|
||
user: {
|
||
select: {
|
||
username: true,
|
||
email: true,
|
||
},
|
||
},
|
||
material: {
|
||
select: {
|
||
productModel: true,
|
||
},
|
||
},
|
||
},
|
||
})
|
||
} catch (prismaError: any) {
|
||
console.error('[MaterialLogs] Prisma query error:', prismaError)
|
||
// 如果 Prisma 查询失败,尝试使用原始 SQL
|
||
try {
|
||
const rawLogs = await prisma.$queryRaw<Array<{
|
||
id: string
|
||
action: string
|
||
materialId: string
|
||
userId: string
|
||
timestamp: Date
|
||
changes: any
|
||
username: string | null
|
||
email: string | null
|
||
productModel: string | null
|
||
}>>`
|
||
SELECT
|
||
ml.id,
|
||
ml."action",
|
||
ml."materialId",
|
||
ml."userId",
|
||
ml."timestamp",
|
||
ml."changes",
|
||
u.username,
|
||
u.email,
|
||
m."productModel"
|
||
FROM "material_logs" ml
|
||
LEFT JOIN "users" u ON ml."userId" = u.id
|
||
LEFT JOIN "materials" m ON ml."materialId" = m.id
|
||
ORDER BY ml."timestamp" DESC
|
||
LIMIT ${limit}
|
||
OFFSET ${offset}
|
||
`
|
||
materialLogs = rawLogs.map((log) => ({
|
||
id: log.id,
|
||
action: log.action,
|
||
materialId: log.materialId,
|
||
userId: log.userId,
|
||
timestamp: log.timestamp,
|
||
changes: log.changes,
|
||
user: log.username ? { username: log.username, email: log.email } : null,
|
||
material: log.productModel ? { productModel: log.productModel } : null,
|
||
}))
|
||
} catch (sqlError: any) {
|
||
console.error('[MaterialLogs] SQL query error:', sqlError)
|
||
// 如果SQL也失败,返回空数组而不是抛出错误
|
||
materialLogs = []
|
||
}
|
||
}
|
||
|
||
// 字段名映射
|
||
const fieldNames: Record<string, string> = {
|
||
productModel: '产品型号',
|
||
categoryName: '一级分类',
|
||
sceneName: '二级分类',
|
||
materialType: '素材类型',
|
||
renderer: '渲染器',
|
||
authorName: '作者花名',
|
||
brandName: '品牌名称',
|
||
seriesName: '系列/产品线',
|
||
skuCode: 'SKU编码',
|
||
materialTexture: '材质/工艺',
|
||
renderTime: '渲染耗时',
|
||
imageResolution: '输出分辨率',
|
||
colorMode: '颜色模式',
|
||
}
|
||
|
||
const ACTION_META: Record<
|
||
string,
|
||
{
|
||
label: string
|
||
type: 'upload' | 'update' | 'delete' | 'toggle' | 'other'
|
||
}
|
||
> = {
|
||
UPLOAD: { label: '上传素材', type: 'upload' },
|
||
UPDATE: { label: '修改素材', type: 'update' },
|
||
DELETE: { label: '删除素材', type: 'delete' },
|
||
TOGGLE_PREMIUM: { label: '切换精品状态', type: 'toggle' },
|
||
}
|
||
|
||
// 构建日志列表
|
||
const logs = materialLogs.map((log: any) => {
|
||
const normalizedAction = typeof log.action === 'string' ? log.action.toUpperCase() : ''
|
||
const actionMeta = ACTION_META[normalizedAction] || {
|
||
label: log.action || '其它操作',
|
||
type: 'other' as const,
|
||
}
|
||
const actionText = actionMeta.label
|
||
|
||
// 构建详细修改内容
|
||
const changeDetails: string[] = []
|
||
const materialName = log.material?.productModel || '未知素材'
|
||
const username = log.user?.username || '未知用户'
|
||
|
||
if (log.changes && typeof log.changes === 'object') {
|
||
const changes = log.changes as Record<string, { oldValue: any; newValue: any }>
|
||
Object.entries(changes).forEach(([key, change]) => {
|
||
const fieldName = fieldNames[key] || key
|
||
const oldVal = change.oldValue ?? '(空)'
|
||
const newVal = change.newValue ?? '(空)'
|
||
changeDetails.push(`(${fieldName}:${oldVal} --> ${newVal})`)
|
||
})
|
||
}
|
||
|
||
if (changeDetails.length === 0) {
|
||
if (normalizedAction === 'UPLOAD') {
|
||
changeDetails.push(`(${username}上传了素材:${materialName})`)
|
||
} else if (normalizedAction === 'DELETE') {
|
||
changeDetails.push(`(${username}删除了素材:${materialName})`)
|
||
} else {
|
||
changeDetails.push(`(${username}${actionText}:${materialName})`)
|
||
}
|
||
}
|
||
|
||
return {
|
||
id: log.id,
|
||
type: actionMeta.type,
|
||
action: actionText,
|
||
actionDetail: changeDetails,
|
||
materialId: log.materialId,
|
||
materialName: materialName,
|
||
userId: log.userId,
|
||
username: username,
|
||
userEmail: log.user?.email || '未知邮箱',
|
||
timestamp: log.timestamp instanceof Date ? log.timestamp.toISOString() : (typeof log.timestamp === 'string' ? log.timestamp : new Date(log.timestamp).toISOString()),
|
||
}
|
||
})
|
||
|
||
return NextResponse.json({
|
||
logs,
|
||
total: logs.length,
|
||
})
|
||
} catch (error) {
|
||
console.error('[MaterialLogs] GET error:', error)
|
||
return NextResponse.json({ error: '获取日志失败' }, { status: 500 })
|
||
}
|
||
}
|
||
|