类B站风格的视频网站,支持多分辨率视频播放、后台管理、自动转码。
- 视频卡片网格列表展示
- 按分类筛选、关键词搜索
- video.js 播放器,支持 360p / 480p / 720p / 1080p 清晰度切换
- HTTP Range 请求,支持拖动进度条和断点续传
- 响应式布局,适配手机和桌面
- 管理员登录(JWT 鉴权)
- 视频上传(支持拖拽,最大 10GB)
- 视频编辑 / 删除
- 分类管理(增删)
- 转码状态实时显示(5 秒轮询)
- 仪表盘统计概览
- FFmpeg 自动队列转码
- H.264 + AAC 编码,兼容所有浏览器
- 4 个分辨率并行转码:1080p (5Mbps) / 720p (2.5Mbps) / 480p (1Mbps) / 360p (500kbps)
- 转码完成后自动删除原始文件释放磁盘空间
- 转码失败的视频会保留记录,方便排查
| 层级 | 技术 |
|---|---|
| 前端 | Vue 3 + Vite + Vue Router + Pinia |
| 播放器 | video.js 8 |
| 后端 | Node.js + Express 4 |
| 数据库 | SQLite (better-sqlite3) |
| 转码 | FFmpeg |
| 认证 | JWT + bcrypt |
toxing/
├── .env # 环境变量配置
├── .gitignore
├── package.json # 根依赖(后端 + 开发工具)
│
├── server/ # 后端
│ ├── index.js # Express 入口,中间件挂载
│ ├── db/
│ │ └── init.js # 数据库建表、种子数据
│ ├── middleware/
│ │ └── auth.js # JWT 鉴权中间件
│ ├── routes/
│ │ ├── auth.js # 登录接口(含暴力破解防护)
│ │ ├── videos.js # 视频公开 API + 管理员 API
│ │ └── categories.js # 分类 API
│ ├── services/
│ │ ├── transcoder.js # FFmpeg 转码队列
│ │ └── password.js # 密码哈希工具
│ ├── uploads/ # 原始视频上传目录
│ └── videos/ # 转码输出目录(按视频 ID 分文件夹)
│
└── client/ # 前端
├── index.html
├── vite.config.js
├── package.json
└── src/
├── main.js # 应用入口
├── App.vue
├── style.css # 全局样式(B站风格)
├── router/index.js # 路由配置
├── stores/auth.js # Pinia 认证状态
├── api/index.js # Axios 封装(自动携带 Token)
├── components/
│ ├── SiteHeader.vue # 前台导航栏
│ └── AdminHeader.vue # 后台导航栏
└── views/
├── Home.vue # 首页 - 视频列表
├── VideoPlay.vue # 视频播放页
└── admin/
├── Login.vue # 管理员登录
├── Layout.vue # 后台布局(侧边栏)
├── Dashboard.vue # 仪表盘
└── VideoManage.vue # 视频管理(上传/编辑/删除/分类)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键自增 |
| title | TEXT | 视频标题 |
| description | TEXT | 视频描述 |
| category_id | INTEGER | 分类 ID(外键) |
| original_path | TEXT | 原始视频路径(转码后删除) |
| cover_path | TEXT | 封面图路径(预留) |
| status | TEXT | pending / transcoding / completed / failed |
| created_at | DATETIME | 创建时间 |
| duration | INTEGER | 视频时长(秒) |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键自增 |
| video_id | INTEGER | 关联视频 ID(外键,级联删除) |
| resolution | TEXT | 360p / 480p / 720p / 1080p |
| file_path | TEXT | 转码后文件绝对路径 |
| file_size | INTEGER | 文件大小(字节) |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键自增 |
| name | TEXT | 分类名称(唯一) |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键自增 |
| username | TEXT | 用户名(唯一) |
| password_hash | TEXT | bcrypt 加盐哈希 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/videos |
视频列表(分页、分类筛选、搜索) |
| GET | /api/videos/:id |
视频详情(含分辨率列表) |
| GET | /api/videos/:id/stream/:resolution |
视频流(支持 Range) |
| GET | /api/categories |
所有分类 |
| POST | /api/auth/login |
管理员登录 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/admin/videos |
视频管理列表(含所有状态) |
| POST | /api/admin/videos |
上传视频(multipart/form-data) |
| PUT | /api/admin/videos/:id |
编辑视频信息 |
| DELETE | /api/admin/videos/:id |
删除视频及所有文件 |
| GET | /api/admin/videos/:id/status |
转码状态查询 |
| POST | /api/admin/categories |
创建分类 |
| DELETE | /api/admin/categories/:id |
删除分类(有视频时拒绝) |
| GET | /api/admin/auth/info |
当前管理员信息 |
GET /api/videos
| 参数 | 类型 | 说明 |
|---|---|---|
| page | int | 页码,默认 1 |
| limit | int | 每页条数,默认 12 |
| category_id | int | 分类 ID |
| search | string | 搜索关键词(匹配标题) |
POST /api/admin/videos (multipart/form-data)
| 字段 | 类型 | 说明 |
|---|---|---|
| video | file | 视频文件(mp4/mkv/avi/mov/wmv/flv/webm) |
| title | string | 标题(可选,默认取文件名) |
| description | string | 描述 |
| category_id | int | 分类 ID |
- Node.js >= 18
- FFmpeg(需加入系统 PATH)
npm install
cd client && npm install && cd ..编辑 .env 文件:
# 生产环境务必更换为一个随机长字符串
JWT_SECRET=your-random-secret-here
PORT=3000
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-strong-password生成随机密钥:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"# 同时启动后端和前端(后端 :3000,前端 :5173)
npm run dev# 构建前端
npm run build
# 启动服务(前端静态文件由 Express 托管)
npm run server访问 http://localhost:3000 即可。
管理员上传视频
│
▼
文件存入 server/uploads/
数据库记录 status = pending
│
▼
转码队列取出任务
status = transcoding
│
▼
ffprobe 获取视频时长
│
▼
FFmpeg 并行转码 4 个分辨率
┌─────────┬─────────┬─────────┬─────────┐
│ 1080p │ 720p │ 480p │ 360p │
│ 5Mbps │ 2.5Mbps │ 1Mbps │ 500kbps │
│ preset: │ medium │ │ │
└────┬────┴────┬────┴────┬────┴────┬────┘
│ │ │ │
▼ ▼ ▼ ▼
写入 video_resolutions 表
│
▼
status = completed
删除原始文件
每个分辨率输出到 server/videos/{视频ID}/{分辨率}.mp4。
| 措施 | 说明 |
|---|---|
| 密码加密 | bcrypt 自动生成唯一盐,10 轮哈希 |
| 暴力破解防护 | 同一账号 5 次失败后锁定 15 分钟 |
| JWT | 24 小时过期,请求头 Bearer Token |
| 限流 | 全局 100 次/15 分钟,登录 10 次/15 分钟,上传 20 次/小时 |
| HTTP 安全头 | helmet 中间件(XSS、MIME、点击劫持等防护) |
| SQL 注入 | 参数化查询(better-sqlite3 prepared statements) |
| 文件上传 | 白名单限制视频格式,10GB 大小上限 |
Q: FFmpeg 转码很慢怎么办?
转码速度取决于 CPU 性能和视频分辨率。可以通过修改 server/services/transcoder.js 中的 preset 参数加速:
ultrafast— 最快,文件较大fast— 较快,质量尚可medium— 默认,平衡速度和质量slow— 较慢,压缩率更高
Q: 上传大文件时浏览器超时?
前端已使用 Axios 的 onUploadProgress 显示进度。如 Nginx 代理需要调大超时:
client_max_body_size 10G;
proxy_read_timeout 3600s;Q: 如何修改管理员密码?
直接修改 .env 中的 ADMIN_PASSWORD,然后删除 server/data.db 重新初始化。或编写脚本通过 bcrypt 生成新哈希更新数据库。
Q: 数据库文件在哪里?
SQLite 数据库文件为 server/data.db,首次启动自动创建。生产环境注意定期备份。