chore: initial commit - CloudSearch v0.0.2
This commit is contained in:
125
packages/backend/src/routes/upload.routes.ts
Normal file
125
packages/backend/src/routes/upload.routes.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
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;
|
||||
}
|
||||
// 压缩:最大宽度320px,JPEG 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;
|
||||
}
|
||||
// 压缩:最大宽度640px,PNG格式
|
||||
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;
|
||||
Reference in New Issue
Block a user