📢 新文章推送 · 每周更新优质内容 · 订阅更新 →
向下滚动
开发编程

Lumin Admin 媒体库改造:全站媒体文件管理

AI 智能总结

Lumin Admin 媒体库改造:全站媒体文件管理

📌 概述

媒体库是后台管理系统中管理图片、音频、视频等文件的核心模块。本次改造将媒体库从仅扫描 static/images/uploads/ 单一目录,升级为递归扫描整个 static/ 目录,自动识别文件类型,支持按类型筛选、来源切换(本地/远程图床)、智能上传路由等功能。

本功能基于 Vue 3 + Element Plus 前端 + Node.js Express 后端实现,支持图片/音频/视频三种媒体类型的统一管理。

一、Hugo 静态目录结构

Hugo 的 static/ 目录下的文件在构建时直接映射到站点根路径,这是媒体文件存放的标准位置:

 1static/
 2├── images/              # 图片目录
 3   ├── avatar/          # 头像
 4   ├── banners/         # 横幅
 5   ├── icons/           # 图标
 6   ├── reward/          # 赞赏码
 7   └── uploads/         # 上传图片
 8├── audios/              # 音频目录(新建)
 9├── videos/              # 视频目录(新建)
10└── libs/                # 第三方库

访问路径映射规则:

本地路径网站访问路径
static/images/photo.jpg/images/photo.jpg
static/audios/song.mp3/audios/song.mp3
static/videos/demo.mp4/videos/demo.mp4

二、后端改造

2.1 媒体类型识别

定义媒体扩展名映射表,自动识别文件类型:

 1const MEDIA_EXTENSIONS = {
 2  image: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.avif'],
 3  audio: ['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.wma'],
 4  video: ['.mp4', '.webm', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.m4v']
 5}
 6
 7function getMediaType(ext) {
 8  const e = ext.toLowerCase()
 9  for (const [type, exts] of Object.entries(MEDIA_EXTENSIONS)) {
10    if (exts.includes(e)) return type
11  }
12  return 'other'
13}

2.2 递归目录扫描

 1function scanMediaDir(dir, basePath = '') {
 2  const results = []
 3  if (!existsSync(dir)) return results
 4  for (const entry of readdirSync(dir, { withFileTypes: true })) {
 5    if (entry.name.startsWith('.')) continue
 6    const fp = join(dir, entry.name)
 7    const relPath = basePath ? basePath + '/' + entry.name : entry.name
 8    if (entry.isDirectory()) {
 9      results.push(...scanMediaDir(fp, relPath))
10    } else {
11      const ext = extname(entry.name)
12      const s = statSync(fp)
13      results.push({
14        name: entry.name,
15        path: relPath,
16        url: '/' + relPath,
17        type: getMediaType(ext),
18        size: s.size,
19        modified: s.mtime.toISOString().replace('T', ' ').slice(0, 19)
20      })
21    }
22  }
23  return results
24}

2.3 API 接口

 1app.get('/api/media', (req, res) => {
 2  let files = scanMediaDir(STATIC_DIR)
 3
 4  // 类型筛选
 5  if (req.query.type && req.query.type !== 'all') {
 6    files = files.filter(f => f.type === req.query.type)
 7  }
 8
 9  // 搜索
10  if (req.query.search) {
11    const s = req.query.search.toLowerCase()
12    files = files.filter(f => f.name.toLowerCase().includes(s) || f.path.toLowerCase().includes(s))
13  }
14
15  // 按修改时间倒序
16  files.sort((a, b) => b.modified.localeCompare(a.modified))
17
18  // 统计各类型数量
19  const counts = { image: 0, audio: 0, video: 0, other: 0 }
20  const allFiles = scanMediaDir(STATIC_DIR)
21  allFiles.forEach(f => { counts[f.type] = (counts[f.type] || 0) + 1 })
22
23  ok(res, { files, counts, total: allFiles.length })
24})

2.4 智能上传路由

根据文件类型自动路由到不同目录:

 1app.post('/api/media/upload', upload.single('file'), (req, res) => {
 2  const ext = extname(req.file.originalname)
 3  const mediaType = getMediaType(ext)
 4
 5  let targetDir = UPLOAD_DIR  // 默认 images/uploads
 6  if (mediaType === 'audio') targetDir = join(STATIC_DIR, 'audios')
 7  else if (mediaType === 'video') targetDir = join(STATIC_DIR, 'videos')
 8
 9  if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true })
10
11  const newName = Date.now() + '-' + Math.random().toString(36).slice(2, 8) + ext
12  // ... 重命名并返回 URL
13})

2.5 安全删除

删除接口支持按完整路径删除,增加路径安全检查:

1app.delete('/api/media/:filename', (req, res) => {
2  const decodedName = decodeURIComponent(req.params.filename)
3  const filePath = join(STATIC_DIR, decodedName)
4  if (!filePath.startsWith(STATIC_DIR)) return fail(res, 403, 'Access denied')
5  if (!existsSync(filePath)) return fail(res, 404, 'File not found')
6  unlinkSync(filePath)
7  ok(res, { status: 'ok' })
8})

2.6 静态文件服务

为音频和视频目录添加静态文件服务:

1app.use('/images', express.static(IMAGES_DIR))
2app.use('/audios', express.static(AUDIOS_DIR))
3app.use('/videos', express.static(VIDEOS_DIR))

2.7 文件上传限制

multer 配置从 20MB 提升到 100MB,支持所有主流媒体格式:

 1const upload = multer({
 2  dest: UPLOAD_DIR,
 3  limits: { fileSize: 100 * 1024 * 1024 },
 4  fileFilter: (req, file, cb) => {
 5    const allowed = [
 6      '.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico', '.bmp', '.avif',
 7      '.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.wma',
 8      '.mp4', '.webm', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.m4v'
 9    ]
10    cb(null, allowed.includes(extname(file.originalname).toLowerCase()))
11  }
12})

三、前端改造

3.1 类型筛选栏

支持按媒体类型筛选,显示各类型数量:

1<el-radio-group v-model="typeFilter" @change="loadMedia">
2  <el-radio-button value="all">全部 {{ totalCounts.total || 0 }}</el-radio-button>
3  <el-radio-button value="image">图片 {{ totalCounts.image || 0 }}</el-radio-button>
4  <el-radio-button value="audio">音频 {{ totalCounts.audio || 0 }}</el-radio-button>
5  <el-radio-button value="video">视频 {{ totalCounts.video || 0 }}</el-radio-button>
6</el-radio-group>

3.2 来源切换

支持本地和远程图床切换:

1<el-radio-group v-model="sourceFilter" @change="handleSourceChange">
2  <el-radio-button value="local">本地</el-radio-button>
3  <el-radio-button v-for="p in remoteProviders" :key="p.key" :value="p.key">
4    {{ p.name }}
5  </el-radio-button>
6</el-radio-group>

远程图床列表从图床配置 API 动态获取已启用且已配置的图床。

3.3 不同类型的预览

网格视图中,不同类型使用不同的预览方式:

类型预览方式背景色
图片缩略图默认 #f5f5f8
音频耳机图标绿色渐变 #e6f9f0 → #d1fae5
视频播放图标黄色渐变 #fff8e6 → #fef3c7
其他文档图标灰色渐变 #f1f5f9 → #e2e8f0

3.4 预览弹窗

点击文件弹出预览弹窗,不同类型使用不同播放器:

1<img v-if="previewFileData?.type === 'image'" :src="previewFileData.url" />
2<audio v-else-if="previewFileData?.type === 'audio'" :src="previewFileData.url" controls />
3<video v-else-if="previewFileData?.type === 'video'" :src="previewFileData.url" controls />

弹窗底部显示文件元数据:路径、大小、修改时间、可点击的链接。

四、文件变更清单

文件变更
admin/backend/server.js媒体 API 全面重构,递归扫描 + 类型识别 + 智能路由
admin/frontend/src/views/Media/index.vue前端页面重构,类型筛选 + 来源切换 + 多类型预览
admin/frontend/src/api/index.jsAPI 函数参数调整
myblog/static/audios/新建音频目录
myblog/static/videos/新建视频目录
版权声明

本文作者 Lumin

本文链接 https://www.zhengquan.xyz/code/lumin-admin-media/

许可协议 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

请作者喝杯咖啡 ☕

  • 微信打赏
    微信支付
  • 支付宝打赏
    支付宝
点击按钮查看打赏二维码
🎁 推荐工具
试试这些实用在线工具,提升工作效率
前往工具集 →

留言评论

期待你的想法

评论加载中