WuYongJie d1c6efc79d
Some checks failed
CI Check / check (push) Has been cancelled
feat: NAS 部署与 Harbor 推送流程
2025-12-03 14:39:06 +08:00

303 lines
8.9 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 { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { createMaterialLog } from '@/lib/material-log'
// GET /api/materials/[id] - 获取单个素材详情
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const material = await prisma.material.findUnique({
where: { id: params.id },
select: {
id: true,
userId: true,
productModel: true,
materialKind: true,
brandName: true,
seriesName: true,
skuCode: true,
materialTexture: true,
categoryName: true,
sceneName: true,
platformName: true,
materialType: true,
renderer: true,
renderTime: true,
imageResolution: true,
colorMode: true,
authorName: true,
previewUrl: true,
videoCoverUrl: true,
videoPreview540Url: true,
videoPreviewClipUrl: true,
videoProcessingStatus: true,
videoDuration: true,
zipFileName: true,
zipFileSize: true,
downloadCount: true,
likeCount: true,
displayImages: true,
createdAt: true,
templateId: true,
user: {
select: {
id: true,
username: true,
avatar: true,
}
}
}
})
if (!material) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
// 返回素材信息(转换BigInt为字符串)
return NextResponse.json({
id: material.id,
userId: material.userId,
productModel: material.productModel,
brandName: material.brandName,
seriesName: material.seriesName,
skuCode: material.skuCode,
materialTexture: material.materialTexture,
categoryName: material.categoryName,
sceneName: material.sceneName,
platformName: material.platformName,
materialType: material.materialType,
renderer: material.renderer,
renderTime: material.renderTime,
imageResolution: material.imageResolution,
colorMode: material.colorMode,
authorName: material.authorName,
previewUrl: material.previewUrl,
materialKind:
(material.materialKind || '').trim().toUpperCase() ||
(material.materialType === '视频' ? 'VIDEO' : 'IMAGE'),
videoCoverUrl: material.videoCoverUrl,
videoPreview540Url: material.videoPreview540Url,
videoPreviewClipUrl: material.videoPreviewClipUrl,
videoProcessingStatus: material.videoProcessingStatus,
videoDuration: material.videoDuration,
zipFileName: material.zipFileName,
zipFileSize: material.zipFileSize.toString(),
downloadCount: material.downloadCount,
likeCount: material.likeCount,
displayImages: material.displayImages,
createdAt: material.createdAt,
templateId: material.templateId,
user: {
username: material.user.username,
avatar: material.user.avatar
}
})
} catch (error: any) {
console.error('[Materials] GET [id] error:', error)
return NextResponse.json(
{ error: '获取素材失败' },
{ status: 500 }
)
}
}
// PUT /api/materials/[id] - 更新素材信息
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user) {
return NextResponse.json(
{ error: '请先登录' },
{ status: 401 }
)
}
const body = await request.json()
const {
productModel,
categoryName,
sceneName,
materialType,
renderer,
authorName,
brandName,
seriesName,
skuCode,
materialTexture,
renderTime,
imageResolution,
colorMode
} = body
// 验证必填字段
if (!productModel || !categoryName || !sceneName || !materialType || !renderer || !authorName) {
return NextResponse.json(
{ error: '缺少必填字段' },
{ status: 400 }
)
}
// 获取素材信息
const material = await prisma.material.findUnique({
where: { id: params.id }
})
if (!material) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
// 权限检查:只有素材上传者或管理员可以编辑
const userFromSession = session.user as any
if (material.userId !== userFromSession.id && userFromSession.role !== 'ADMIN') {
return NextResponse.json(
{ error: '您没有权限编辑此素材' },
{ status: 403 }
)
}
// 记录修改前的值,用于日志
const oldValues: Record<string, any> = {
productModel: material.productModel,
categoryName: material.categoryName,
sceneName: material.sceneName,
materialType: material.materialType,
renderer: material.renderer,
authorName: material.authorName,
brandName: material.brandName,
seriesName: material.seriesName,
skuCode: material.skuCode,
materialTexture: material.materialTexture,
renderTime: material.renderTime,
imageResolution: material.imageResolution,
colorMode: material.colorMode,
}
// 更新素材信息
const updateData: any = {
productModel,
categoryName,
sceneName,
materialType,
renderer,
authorName,
updatedAt: new Date()
}
const assignOptional = (key: string, value: any) => {
if (value !== undefined) {
updateData[key] = value === null || value === '' ? null : value
}
}
assignOptional('brandName', brandName)
assignOptional('seriesName', seriesName)
assignOptional('skuCode', skuCode)
assignOptional('materialTexture', materialTexture)
assignOptional('renderTime', renderTime)
assignOptional('imageResolution', imageResolution)
assignOptional('colorMode', colorMode)
const updatedMaterial = await prisma.material.update({
where: { id: params.id },
data: updateData
})
// 记录修改详情到日志(用于监控仪表板)
const changes: string[] = []
const fieldNames: Record<string, string> = {
productModel: '产品型号',
categoryName: '一级分类',
sceneName: '二级分类',
materialType: '素材类型',
renderer: '渲染器',
authorName: '作者花名',
brandName: '品牌名称',
seriesName: '系列/产品线',
skuCode: 'SKU编码',
materialTexture: '材质/工艺',
renderTime: '渲染耗时',
imageResolution: '输出分辨率',
colorMode: '颜色模式',
}
Object.keys(updateData).forEach((key) => {
if (key === 'updatedAt') return
const oldVal = oldValues[key] ?? null
const newVal = updateData[key] ?? null
if (String(oldVal) !== String(newVal)) {
const fieldName = fieldNames[key] || key
changes.push(`${fieldName}: "${oldVal ?? '(空)'}" → "${newVal ?? '(空)'}"`)
}
})
if (changes.length > 0) {
console.log(`[MaterialUpdate] Material ${params.id} updated by ${userFromSession.id}:`, changes.join('; '))
// 记录详细修改日志到数据库
const changesObj: Record<string, { oldValue: any; newValue: any }> = {}
Object.keys(updateData).forEach((key) => {
if (key === 'updatedAt') return
const oldVal = oldValues[key] ?? null
const newVal = updateData[key] ?? null
if (String(oldVal) !== String(newVal)) {
changesObj[key] = {
oldValue: oldVal ?? null,
newValue: newVal ?? null,
}
}
})
if (Object.keys(changesObj).length > 0) {
await createMaterialLog({
materialId: params.id,
userId: userFromSession.id,
action: 'UPDATE',
changes: changesObj,
})
}
}
return NextResponse.json({
success: true,
message: '素材信息更新成功',
material: {
id: updatedMaterial.id,
productModel: updatedMaterial.productModel,
brandName: updatedMaterial.brandName,
seriesName: updatedMaterial.seriesName,
skuCode: updatedMaterial.skuCode,
materialTexture: updatedMaterial.materialTexture,
categoryName: updatedMaterial.categoryName,
sceneName: updatedMaterial.sceneName,
materialType: updatedMaterial.materialType,
renderer: updatedMaterial.renderer,
renderTime: updatedMaterial.renderTime,
imageResolution: updatedMaterial.imageResolution,
colorMode: updatedMaterial.colorMode,
authorName: updatedMaterial.authorName
}
})
} catch (error: any) {
console.error('[Materials] PUT [id] error:', error)
return NextResponse.json(
{ error: '更新素材失败' },
{ status: 500 }
)
}
}