- C2: ColorPicker and EditModal reusable components extracted - C2: tag-manage and category-manage pages use EditModal (remove ~120 dup lines) - C3: Upload logic extracted to server/src/utils/upload.ts - C4: /recurring/sync wrapped in DB transaction - C7: Backup download uses API_BASE (no hardcoded URLs) - C9: getAvatarUrl() utility function, used in 5 files - C1: getCurrentMonth() documented as aligned with server date.ts
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import multer from 'multer'
|
|
import path from 'path'
|
|
import crypto from 'crypto'
|
|
import fs from 'fs'
|
|
|
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'
|
|
|
|
/** 创建指定子目录的 multer 配置 */
|
|
export function createUploadConfig(subDir: string, maxFileSize = 5 * 1024 * 1024) {
|
|
const targetDir = path.resolve(UPLOAD_DIR, subDir)
|
|
|
|
// 确保目录存在
|
|
if (!fs.existsSync(targetDir)) {
|
|
fs.mkdirSync(targetDir, { recursive: true })
|
|
}
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: (_req, _file, cb) => {
|
|
// 运行时再次确认(可能被手动删除)
|
|
if (!fs.existsSync(targetDir)) {
|
|
fs.mkdirSync(targetDir, { recursive: true })
|
|
}
|
|
cb(null, targetDir)
|
|
},
|
|
filename: (_req, file, cb) => {
|
|
const ext = path.extname(file.originalname).toLowerCase() || '.jpg'
|
|
const prefix = subDir === 'avatars' ? '' : 'notif_'
|
|
cb(null, `${prefix}${Date.now()}_${crypto.randomInt(1000, 9999)}${ext}`)
|
|
}
|
|
})
|
|
|
|
return multer({
|
|
storage,
|
|
limits: { fileSize: maxFileSize },
|
|
fileFilter: (_req, file, cb) => {
|
|
const allowed = ['.jpg', '.jpeg', '.png', '.webp']
|
|
const ext = path.extname(file.originalname).toLowerCase()
|
|
if (allowed.includes(ext)) {
|
|
cb(null, true)
|
|
} else {
|
|
cb(new Error('仅支持 JPEG、PNG、WebP 格式'))
|
|
}
|
|
}
|
|
})
|
|
}
|