From 6804a7300f97ef05772f5a41e61d1c82ac85687d Mon Sep 17 00:00:00 2001 From: haomingming Date: Fri, 26 Jun 2026 17:34:15 +0800 Subject: [PATCH] 34 --- deploy-aliyun.sh | 3 +- src/app/api/config/logo/route.js | 55 ++++++++++++++++++++++++++++++-- src/app/api/config/route.js | 30 +++++++++++------ src/app/uploads/[file]/route.js | 53 ++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 src/app/uploads/[file]/route.js diff --git a/deploy-aliyun.sh b/deploy-aliyun.sh index 586a1bd..bc15aa0 100644 --- a/deploy-aliyun.sh +++ b/deploy-aliyun.sh @@ -23,7 +23,8 @@ IMAGE_VERSION="v1.0.0" USERNAME="742065561@qq.com" # Registry 登录密码 -# 为安全起见,不在脚本中硬编码密码 +# 为安全起见,不在脚本中硬编码密码 #vH#DYxZij8zk^Jr +echo "默认密码:#vH#DYxZij8zk^Jr" PASSWORD="" REGISTRY_HOST="registry.${REGISTRY_REGION}.aliyuncs.com" diff --git a/src/app/api/config/logo/route.js b/src/app/api/config/logo/route.js index 057ad84..3f0e27e 100644 --- a/src/app/api/config/logo/route.js +++ b/src/app/api/config/logo/route.js @@ -36,8 +36,8 @@ export async function POST(request) { const buffer = Buffer.from(await file.arrayBuffer()); fs.writeFileSync(filePath, buffer); - // 返回文件路径(相对于 public 的路径) - const publicPath = `/uploads/${fileName}`; + // 返回动态访问路径,避免生产环境下 public 目录新增文件 404 + const publicPath = `/api/config/logo?file=${encodeURIComponent(fileName)}`; return Response.json({ path: publicPath, @@ -51,3 +51,54 @@ export async function POST(request) { ); } } + +// GET /api/config/logo?file=xxx - 动态读取并返回 Logo 文件 +export async function GET(request) { + try { + const { searchParams } = new URL(request.url); + const file = searchParams.get('file'); + + if (!file) { + return Response.json( + { error: '缺少文件名' }, + { status: 400 } + ); + } + + const cleanFile = path.basename(file); + const filePath = path.join(process.cwd(), 'public', 'uploads', cleanFile); + + if (!fs.existsSync(filePath)) { + return Response.json( + { error: '文件不存在' }, + { status: 404 } + ); + } + + const buffer = fs.readFileSync(filePath); + const ext = path.extname(cleanFile).toLowerCase(); + + let contentType = 'application/octet-stream'; + if (ext === '.png') contentType = 'image/png'; + else if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg'; + else if (ext === '.gif') contentType = 'image/gif'; + else if (ext === '.webp') contentType = 'image/webp'; + else if (ext === '.bmp') contentType = 'image/bmp'; + else if (ext === '.svg') contentType = 'image/svg+xml'; + + return new Response(buffer, { + headers: { + 'Content-Type': contentType, + 'Content-Length': buffer.length.toString(), + 'Content-Disposition': `inline; filename="${encodeURIComponent(cleanFile)}"`, + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }); + } catch (error) { + console.error('获取 Logo 失败:', error); + return Response.json( + { error: '服务器内部错误' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/config/route.js b/src/app/api/config/route.js index b8e4058..347f2eb 100644 --- a/src/app/api/config/route.js +++ b/src/app/api/config/route.js @@ -2,15 +2,31 @@ export const dynamic = "force-dynamic"; import getDb from '@/lib/db'; import { getUserFromRequest, unauthorizedResponse, forbiddenResponse, checkRole } from '@/lib/auth'; +function normalizeConfig(configs) { + const result = {}; + + for (const config of configs) { + let value = config.config_value; + + if (config.config_key === 'company_logo' && value) { + const match = value.match(/^\/uploads\/(.+)$/); + if (match) { + value = `/api/config/logo?file=${encodeURIComponent(match[1])}`; + } + } + + result[config.config_key] = value; + } + + return result; +} + // GET /api/config - 获取系统配置(公开,无需认证) export async function GET() { try { const db = await getDb(); const configs = db.prepare('SELECT config_key, config_value FROM system_config').all(); - const result = {}; - for (const config of configs) { - result[config.config_key] = config.config_value; - } + const result = normalizeConfig(configs); return Response.json(result); } catch (error) { console.error('获取系统配置失败:', error); @@ -46,12 +62,8 @@ export async function PUT(request) { }); updateMany(); - // 返回更新后的配置 const configs = db.prepare('SELECT config_key, config_value FROM system_config').all(); - const result = {}; - for (const config of configs) { - result[config.config_key] = config.config_value; - } + const result = normalizeConfig(configs); return Response.json(result); } catch (error) { console.error('更新系统配置失败:', error); diff --git a/src/app/uploads/[file]/route.js b/src/app/uploads/[file]/route.js new file mode 100644 index 0000000..ef7a4d7 --- /dev/null +++ b/src/app/uploads/[file]/route.js @@ -0,0 +1,53 @@ +export const dynamic = "force-dynamic"; +import path from 'path'; +import fs from 'fs'; + +// GET /uploads/:file - 兼容历史 Logo 直链 +export async function GET(request, { params }) { + try { + const file = params?.file; + + if (!file) { + return Response.json( + { error: '缺少文件名' }, + { status: 400 } + ); + } + + const cleanFile = path.basename(file); + const filePath = path.join(process.cwd(), 'public', 'uploads', cleanFile); + + if (!fs.existsSync(filePath)) { + return Response.json( + { error: '文件不存在' }, + { status: 404 } + ); + } + + const buffer = fs.readFileSync(filePath); + const ext = path.extname(cleanFile).toLowerCase(); + + let contentType = 'application/octet-stream'; + if (ext === '.png') contentType = 'image/png'; + else if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg'; + else if (ext === '.gif') contentType = 'image/gif'; + else if (ext === '.webp') contentType = 'image/webp'; + else if (ext === '.bmp') contentType = 'image/bmp'; + else if (ext === '.svg') contentType = 'image/svg+xml'; + + return new Response(buffer, { + headers: { + 'Content-Type': contentType, + 'Content-Length': buffer.length.toString(), + 'Content-Disposition': `inline; filename="${encodeURIComponent(cleanFile)}"`, + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }); + } catch (error) { + console.error('获取上传文件失败:', error); + return Response.json( + { error: '服务器内部错误' }, + { status: 500 } + ); + } +}