refactor(iter-v2): T02 code quality - shared components, transactions, utils

- 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
This commit is contained in:
2026-06-11 09:49:17 +08:00
parent d170df5c51
commit 5a0ae0b28d
17 changed files with 350 additions and 338 deletions

View File

@@ -0,0 +1,45 @@
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 格式'))
}
}
})
}