import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/lib/auth' import { prisma } from '@/lib/prisma' import { defaultLogos, featureFlags } from '@/lib/config' import { cache } from '@/lib/cache' import { ensureSystemSettingsOptionalColumns } from '@/lib/system-settings-columns' import { canUseSystemSettings } from '@/lib/license' const CACHE_KEY = 'system-settings' const CACHE_TTL = 60000 // 1分钟缓存 // GET - 获取系统设置(带缓存) export async function GET() { try { await ensureSystemSettingsOptionalColumns() // 先尝试从缓存获取 const cachedSettings = cache.get(CACHE_KEY) if (cachedSettings) { return NextResponse.json(cachedSettings) } // 获取或创建系统设置 // 使用 select 来避免查询不存在的字段 let settings = await prisma.systemSettings.findFirst({ select: { id: true, headerBgColor: true, siteName: true, premiumRatio: true, updatedAt: true, createdAt: true, headerLogoUrl: true, loginLogoUrl: true, s3AccessKey: true, s3Bucket: true, s3Endpoint: true, s3PublicUrl: true, s3Region: true, s3SecretKey: true, enableRegistration: true, enableUserActivation: true, enableAiImageEdit: true, enableTunnelService: true, // homeDeleteButtonMode 是可选列,这里不直接在 select 中声明,避免旧版本类型不匹配 // enabledBuiltinTemplates 可能不存在,先不查询 }, }) as any // 如果字段存在,尝试查询它 try { const fullSettings = await prisma.$queryRaw>` SELECT "enabledBuiltinTemplates", "homeDeleteButtonMode", "premiumRatio" FROM "system_settings" LIMIT 1 ` if (fullSettings[0] && settings) { settings.enabledBuiltinTemplates = fullSettings[0].enabledBuiltinTemplates settings.homeDeleteButtonMode = fullSettings[0].homeDeleteButtonMode || 'ADMIN_ONLY' settings.premiumRatio = fullSettings[0].premiumRatio ?? 10 // 默认值10% } } catch { // 字段不存在,设置为默认值 if (settings) { settings.enabledBuiltinTemplates = null settings.homeDeleteButtonMode = 'ADMIN_ONLY' settings.premiumRatio = settings.premiumRatio ?? 10 // 确保有默认值 } } const OLD_HEADER_DEFAULT = '/logo/logo2.png' const OLD_LOGIN_DEFAULT = '/logo/logo1.png' if (!settings) { // 如果不存在,创建默认设置(包含默认 LOGO 与对象存储配置) settings = await prisma.systemSettings.create({ data: { headerBgColor: '#1f2937', headerLogoUrl: defaultLogos.header, loginLogoUrl: defaultLogos.login, siteName: 'FLNXI素材库', premiumRatio: 10, enableUserActivation: false, enableRegistration: true, enableAiImageEdit: featureFlags.aiEditEnabled, enableTunnelService: featureFlags.tunnelEnabled, // 对象存储相关字段:初次创建时直接带上当前环境值,便于在面板中展示“系统当前值” s3Endpoint: process.env.S3_ENDPOINT || undefined, s3AccessKey: process.env.S3_ACCESS_KEY_ID || undefined, s3SecretKey: process.env.S3_SECRET_ACCESS_KEY || undefined, s3Bucket: process.env.S3_BUCKET || undefined, s3Region: process.env.S3_REGION || 'us-east-1', s3PublicUrl: process.env.S3_PUBLIC_DOMAIN || undefined, }, }) as any } else { // 已存在的旧数据,如果 LOGO 或对象存储字段为空,则自动补上默认值,并保存一次 let needUpdate = false const updateData: { headerLogoUrl?: string loginLogoUrl?: string s3Endpoint?: string s3AccessKey?: string s3SecretKey?: string s3Bucket?: string s3Region?: string s3PublicUrl?: string } = {} // LOGO 默认值兜底或纠正 const shouldSwapLegacyDefaults = settings.headerLogoUrl === OLD_HEADER_DEFAULT && settings.loginLogoUrl === OLD_LOGIN_DEFAULT if (!settings.headerLogoUrl || shouldSwapLegacyDefaults) { updateData.headerLogoUrl = defaultLogos.header settings.headerLogoUrl = defaultLogos.header needUpdate = true } if (!settings.loginLogoUrl || shouldSwapLegacyDefaults) { updateData.loginLogoUrl = defaultLogos.login settings.loginLogoUrl = defaultLogos.login needUpdate = true } // 对象存储字段:为空时,用当前环境变量补上,方便面板展示“系统当前值” if (!settings.s3Endpoint && process.env.S3_ENDPOINT) { updateData.s3Endpoint = process.env.S3_ENDPOINT settings.s3Endpoint = process.env.S3_ENDPOINT needUpdate = true } if (!settings.s3AccessKey && process.env.S3_ACCESS_KEY_ID) { updateData.s3AccessKey = process.env.S3_ACCESS_KEY_ID settings.s3AccessKey = process.env.S3_ACCESS_KEY_ID needUpdate = true } if (!settings.s3SecretKey && process.env.S3_SECRET_ACCESS_KEY) { updateData.s3SecretKey = process.env.S3_SECRET_ACCESS_KEY settings.s3SecretKey = process.env.S3_SECRET_ACCESS_KEY needUpdate = true } if (!settings.s3Bucket && process.env.S3_BUCKET) { updateData.s3Bucket = process.env.S3_BUCKET settings.s3Bucket = process.env.S3_BUCKET needUpdate = true } if (!settings.s3Region && process.env.S3_REGION) { updateData.s3Region = process.env.S3_REGION settings.s3Region = process.env.S3_REGION needUpdate = true } if (!settings.s3PublicUrl && process.env.S3_PUBLIC_DOMAIN) { updateData.s3PublicUrl = process.env.S3_PUBLIC_DOMAIN settings.s3PublicUrl = process.env.S3_PUBLIC_DOMAIN needUpdate = true } if (needUpdate) { await prisma.systemSettings.update({ where: { id: settings.id }, data: updateData, }) } } // 存入缓存 cache.set(CACHE_KEY, settings, CACHE_TTL) return NextResponse.json(settings) } catch (error) { console.error('[SystemSettings] Get error:', error) return NextResponse.json( { error: '获取系统设置失败' }, { status: 500 } ) } } // PUT - 更新系统设置(管理员权限) export async function PUT(request: NextRequest) { try { await ensureSystemSettingsOptionalColumns() const session = await getServerSession(authOptions) const userRole = (session?.user as any)?.role const isSuperAdmin = Boolean((session?.user as any)?.isSuperAdmin) if (!session || userRole !== 'ADMIN') { return NextResponse.json( { error: '只有管理员可以访问系统设置' }, { status: 403 } ) } // 检查系统设置功能是否可用(免费版只能查看,不能修改) if (!canUseSystemSettings()) { return NextResponse.json( { error: '系统设置功能需要标准版或专业版许可证,请升级后使用' }, { status: 403 } ) } const body = await request.json() const { headerBgColor, siteName, headerLogoUrl, loginLogoUrl, s3Endpoint, s3AccessKey, s3SecretKey, s3Bucket, s3Region, s3PublicUrl, enableUserActivation, enableRegistration, enableAiImageEdit, enableTunnelService, enabledBuiltinTemplates, homeDeleteButtonMode, premiumRatio, } = body let sanitizedPremiumRatio: number | undefined if (premiumRatio !== undefined) { const numericRatio = Number(premiumRatio) if (!Number.isFinite(numericRatio)) { return NextResponse.json( { error: '精品作品比例必须为数字' }, { status: 400 } ) } sanitizedPremiumRatio = Math.min(100, Math.max(0, Number(numericRatio.toFixed(2)))) } // 只有最高权限账号可以修改 enableUserActivation if ( (enableUserActivation !== undefined || enableAiImageEdit !== undefined || enableTunnelService !== undefined) && !isSuperAdmin ) { return NextResponse.json( { error: '只有最高权限账号可以修改受保护的功能开关' }, { status: 403 } ) } // 获取或创建系统设置 let settings = await prisma.systemSettings.findFirst() if (!settings) { // 创建(可选列如 homeDeleteButtonMode 使用原生 SQL 单独更新,以兼容旧版本 Prisma 类型) settings = await prisma.systemSettings.create({ data: { headerBgColor: headerBgColor || '#1f2937', headerLogoUrl: headerLogoUrl ?? defaultLogos.header, loginLogoUrl: loginLogoUrl ?? defaultLogos.login, siteName: siteName || 'FLNXI素材库', premiumRatio: sanitizedPremiumRatio ?? 10, enableUserActivation: enableUserActivation !== undefined ? enableUserActivation : false, enableRegistration: enableRegistration !== undefined ? enableRegistration : true, enableAiImageEdit: enableAiImageEdit !== undefined ? enableAiImageEdit : featureFlags.aiEditEnabled, enableTunnelService: enableTunnelService !== undefined ? enableTunnelService : featureFlags.tunnelEnabled, } }) // 通过原生 SQL 写入可选列,避免类型不匹配 try { await prisma.$executeRawUnsafe( `UPDATE "system_settings" SET "homeDeleteButtonMode" = $1 WHERE "id" = $2`, homeDeleteButtonMode ?? 'ADMIN_ONLY', settings.id ) } catch (e) { console.error('[SystemSettings] Failed to set homeDeleteButtonMode on create:', e) } } else { // 更新 const updateData: any = {} if (headerBgColor !== undefined) updateData.headerBgColor = headerBgColor if (siteName !== undefined) updateData.siteName = siteName if (headerLogoUrl !== undefined) updateData.headerLogoUrl = headerLogoUrl if (loginLogoUrl !== undefined) updateData.loginLogoUrl = loginLogoUrl if (s3Endpoint !== undefined) updateData.s3Endpoint = s3Endpoint if (s3AccessKey !== undefined) updateData.s3AccessKey = s3AccessKey if (s3SecretKey !== undefined) updateData.s3SecretKey = s3SecretKey if (s3Bucket !== undefined) updateData.s3Bucket = s3Bucket if (s3Region !== undefined) updateData.s3Region = s3Region if (s3PublicUrl !== undefined) updateData.s3PublicUrl = s3PublicUrl if (enableUserActivation !== undefined) updateData.enableUserActivation = enableUserActivation if (enableRegistration !== undefined) updateData.enableRegistration = enableRegistration if (enableAiImageEdit !== undefined) updateData.enableAiImageEdit = enableAiImageEdit if (enableTunnelService !== undefined) updateData.enableTunnelService = enableTunnelService if (enabledBuiltinTemplates !== undefined) updateData.enabledBuiltinTemplates = enabledBuiltinTemplates if (sanitizedPremiumRatio !== undefined) updateData.premiumRatio = sanitizedPremiumRatio settings = await prisma.systemSettings.update({ where: { id: settings.id }, data: updateData, }) // 单独更新可选列 homeDeleteButtonMode if (homeDeleteButtonMode !== undefined) { try { await prisma.$executeRawUnsafe( `UPDATE "system_settings" SET "homeDeleteButtonMode" = $1 WHERE "id" = $2`, homeDeleteButtonMode, settings.id ) } catch (e) { console.error('[SystemSettings] Failed to update homeDeleteButtonMode:', e) } } } // 清除缓存,强制下次重新加载 cache.delete(CACHE_KEY) return NextResponse.json(settings) } catch (error) { console.error('Update system settings error:', error) return NextResponse.json( { error: '更新系统设置失败' }, { status: 500 } ) } }