问题:
- 管理后台编辑博客保存后,访问 /blog/{slug} 返回 500
- 用户上传封面图后前端不识别/裂图
根因:
1. admin [id].get.js 原样返回 DB 里的 tags 字符串 "[]",编辑页 loadItem
透传到 form.tags,保存时 JSON.stringify 再编码一次 → DB 写入 '"[]"'。
公开接口 JSON.parse 后得到字符串 "[]" 而非数组,前端 tags.join(',')
对字符串调用 → TypeError → SSR 500。
2. 上传接口保留中文文件名(\u4e00-\u9fa5 正则放行),Nginx/Nuxt 静态服务
对含中文 URL 处理不一致,静态请求 404。
修复:
- admin GET:tags 统一解析为数组返回
- admin POST/PUT:只接受数组,非数组写入空数组
- 公开 /api/content/blog|news:safeParseTags 兼容历史脏数据,
即使 DB 里是 '"[]"' / 非法 JSON 也保证输出数组
- upload.post.js:safeName 只保留 ASCII 字母数字,去除中文
- scripts/fix-articles-tags.mjs:一次性清理历史双重编码数据
61 行
2.0 KiB
JavaScript
61 行
2.0 KiB
JavaScript
import { writeFileSync, existsSync, mkdirSync } from 'fs'
|
|
import { join, extname } from 'path'
|
|
import { requireAdmin } from '../../utils/auth.js'
|
|
|
|
// 允许的图片类型
|
|
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml']
|
|
const MAX_SIZE = 10 * 1024 * 1024 // 10MB
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
requireAdmin(event)
|
|
|
|
const formData = await readMultipartFormData(event)
|
|
if (!formData || !formData.length) {
|
|
throw createError({ statusCode: 422, message: '请选择要上传的文件' })
|
|
}
|
|
|
|
const results = []
|
|
|
|
for (const file of formData) {
|
|
if (!file.filename || !file.data) continue
|
|
|
|
// 校验文件类型
|
|
const mimeType = file.type || ''
|
|
if (!ALLOWED_TYPES.includes(mimeType)) {
|
|
throw createError({ statusCode: 422, message: `不支持的文件类型: ${mimeType},仅支持 JPG/PNG/WebP/GIF/SVG` })
|
|
}
|
|
|
|
// 校验文件大小
|
|
if (file.data.length > MAX_SIZE) {
|
|
throw createError({ statusCode: 422, message: `文件过大,最大 10MB` })
|
|
}
|
|
|
|
// 生成文件名: 时间戳-原始文件名
|
|
// 只保留 ASCII 字母数字下划线短横,中文/特殊字符一律去掉,避免 URL 编码与静态服务文件名不一致导致 404
|
|
const ext = extname(file.filename).toLowerCase()
|
|
const stripped = file.filename
|
|
.replace(ext, '')
|
|
.replace(/[^a-zA-Z0-9_-]/g, '')
|
|
.slice(0, 30)
|
|
const safeName = stripped || 'img'
|
|
const fileName = `${Date.now()}-${safeName}${ext}`
|
|
|
|
// 按年月分目录
|
|
const now = new Date()
|
|
const subDir = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`
|
|
const uploadDir = join(process.cwd(), 'public', 'uploads', subDir)
|
|
|
|
if (!existsSync(uploadDir)) {
|
|
mkdirSync(uploadDir, { recursive: true })
|
|
}
|
|
|
|
const filePath = join(uploadDir, fileName)
|
|
writeFileSync(filePath, file.data)
|
|
|
|
const url = `/uploads/${subDir}/${fileName}`
|
|
results.push({ url, name: file.filename, size: file.data.length })
|
|
}
|
|
|
|
return { success: true, files: results }
|
|
})
|