Files
CloudSearch/packages/backend/src/routes/upload.routes.ts

125 lines
4.1 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 { Router, Request, Response } from 'express';
import multer from 'multer';
import sharp from 'sharp';
import path from 'path';
import fs from 'fs';
import { authMiddleware } from '../admin/auth.service';
import { updateSystemConfig } from '../admin/system-config.service';
const router = Router();
// ============ Upload ============
/**
* POST /api/admin/upload-fallback-image
* Upload a fallback cover image for search results without covers.
* Recommended: 320×180 JPEG/PNG (16:9), max 2MB.
*/
const uploadDir = path.resolve('/app/uploads/fallback');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const fallbackStorage = multer.diskStorage({
destination: (_req, _file, cb) => cb(null, uploadDir),
filename: (_req, _file, cb) => {
const ext = '.jpg';
cb(null, `fallback_cover_tmp${ext}`);
},
});
const upload = multer({
storage: fallbackStorage,
limits: { fileSize: 2 * 1024 * 1024 }, // 2MB max
fileFilter: (_req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('仅支持图片文件JPEG/PNG'));
}
},
});
router.post('/admin/upload-fallback-image', authMiddleware, upload.single('image'), async (req: Request, res: Response) => {
try {
if (!req.file) {
res.status(400).json({ error: '请选择要上传的图片' });
return;
}
// 压缩最大宽度320pxJPEG quality 80
const outPath = path.resolve(uploadDir, 'fallback_cover.jpg');
await sharp(req.file.path)
.resize(320, undefined, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 80 })
.toFile(outPath);
// 删除原始上传文件(如果路径不同)
if (req.file.path !== outPath) {
fs.unlink(req.file.path, () => {});
}
const url = `/api/uploads/fallback/fallback_cover.jpg`;
updateSystemConfig('search_fallback_image', url);
const stat = fs.statSync(outPath);
res.json({ success: true, url, message: `✅ 兜底图已压缩上传 (${(stat.size / 1024).toFixed(1)}KB)` });
} catch (err: any) {
res.status(500).json({ error: err.message || '上传失败' });
}
});
/**
* POST /api/admin/upload-logo
* Upload a site logo image displayed on search page (home link) and homepage.
* Recommended: 320×60 or similar wide/banner ratio, JPEG/PNG/WebP, max 2MB.
*/
const logoUploadDir = path.resolve('/app/uploads/logo');
if (!fs.existsSync(logoUploadDir)) {
fs.mkdirSync(logoUploadDir, { recursive: true });
}
const logoStorage = multer.diskStorage({
destination: (_req, _file, cb) => cb(null, logoUploadDir),
filename: (_req, _file, cb) => {
cb(null, `site_logo_tmp.png`);
},
});
const logoUpload = multer({
storage: logoStorage,
limits: { fileSize: 2 * 1024 * 1024 }, // 2MB max
fileFilter: (_req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('仅支持图片文件JPEG/PNG/WebP'));
}
},
});
router.post('/admin/upload-logo', authMiddleware, logoUpload.single('image'), async (req: Request, res: Response) => {
try {
if (!req.file) {
res.status(400).json({ error: '请选择要上传的图片' });
return;
}
// 压缩最大宽度640pxPNG格式
const outPath = path.resolve(logoUploadDir, 'site_logo.png');
await sharp(req.file.path)
.resize(640, undefined, { fit: 'inside', withoutEnlargement: true })
.png({ compressionLevel: 9 })
.toFile(outPath);
if (req.file.path !== outPath) {
fs.unlink(req.file.path, () => {});
}
const url = `/api/uploads/logo/site_logo.png`;
updateSystemConfig('site_logo', url);
const stat = fs.statSync(outPath);
res.json({ success: true, url, message: `✅ 站点图标已压缩上传 (${(stat.size / 1024).toFixed(1)}KB)` });
} catch (err: any) {
res.status(500).json({ error: err.message || '上传失败' });
}
});
import { startQrLogin, getQrLoginStatus, cancelQrLogin } from '../cloud/qr-login.service';
// ===== 夸克扫码登录 (不需要 auth用户未登录时也需要能用) =====
export default router;