资讯动态

Java+Vue+SpringBoot多媒体素材库项目设计与部署实战

发布时间:2026/10/10 13:29:28 来源:尧图企业网站定制
手边正好有一套基于 Java Vue SpringBoot 的多媒体素材库项目连着程序、数据库、设计报告、部署教程和答辩指导一起整理出来了。这类项目在大学里很常见很多人拿它做毕业设计、课程设计或者是想入门前后端分离的练手项目。我前后跑通了整个链路从建表到视频预览从本地调试到服务器部署中间踩了不少坑。今天就把这套东西的核心设计、实现细节和最容易翻车的地方一次写清楚。这套多媒体素材库本质上是一个资源管理系统支持图片、视频、音频、文档的在线管理和预览。后端用 SpringBoot 提供接口前端用 Vue 搭界面数据库负责存元数据文件本身落到本地磁盘或者对象存储。对学习者来说它覆盖了登录鉴权、文件上传、分页查询、跨域配置、流媒体播放这些高频技术点拿它应付面试里的项目提问也很合适。下面直接按项目从设计到落地的顺序展开内容偏实操每一步都能对照着改。1. 项目整体设计与技术选型思路1.1 这个多媒体素材库到底要解决什么问题先说清楚业务目标素材库要能让用户登录后上传素材按分类浏览素材看到图片就在线预览看到视频能在网页里直接播放还能编辑素材信息、删除无用资源最后再加上简单的用户权限管理。这就是一个“能跑通、能演示、能答辩”的完整闭环。很多人一开始容易把功能想复杂加了一堆收藏、评论、积分兑换。我建议先保住主干主干就是素材的上传、列表、预览、删除、分类筛选外加登录和分页。把这些做完以后如果时间还有富余再加统计图表、回收站、标签体系这类加分项。这个项目我最终实现的主功能就是围绕“素材”这一核心实体展开的所有设计都是为了让素材能快速被传进来、被查出来、被看到。1.2 为什么选 Java Vue SpringBoot 这套组合技术选型不是炫技而是要兼顾学习成本和企业生产习惯。Java 是大学课程里覆盖率最高的语言SpringBoot 又把 Spring 的繁琐配置基本抹平了写接口的效率非常高。Vue 作为前端框架对后端同学来说尤其友好模板语法接近原生 HTML数据绑定不需要手动操作 DOM。数据库选 MySQL和 MyBatis-Plus 配合后连基础的增删改查都可以少写大量重复代码。这里有一个关键点SpringBoot 版本一定要和 Java 版本匹配。如果你本地装的是 Java 8尽量选 SpringBoot 2.7.x如果直接用了 SpringBoot 3.x那 Java 版本必须到 17 以上否则启动直接报错。我见过太多人卡在版本太高的问题上明明代码没问题就是起不来。1.3 项目目录结构总览整个项目划分成 backend 和 frontend 两个目录前后端彻底分离。material-studio/ ├── backend/ # SpringBoot 后端工程 │ ├── src/main/java/com/example/material/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务逻辑 │ │ ├── mapper/ # MyBatis-Plus 数据访问 │ │ ├── entity/ # 实体类 │ │ ├── config/ # 配置类 │ │ └── common/ # 通用返回结果、异常处理 │ └── src/main/resources/ │ ├── application.yml │ └── sql/ # 初始化脚本 └── frontend/ # Vue 前端工程 ├── src/ │ ├── api/ # axios 封装 │ ├── components/ # 通用组件 │ ├── router/ # 路由配置 │ ├── views/ # 页面 │ └── App.vue └── package.json这种结构最大的好处是职责清楚。答辩的时候老师问“项目怎么分层的”你可以直接指出来controller 只做参数接收和结果返回service 处理业务逻辑mapper 负责数据库操作。前端组件再按视图和业务拆分页面看起来也很清晰。2. 数据库设计与核心表结构拆解2.1 用户与权限模块设计任何系统都绕不开用户表素材库也需要区分管理员和普通用户。我的设计里加入了sys_user表字段包括 id、username、password、nick_name、avatar、role、create_time。密码不能用明文我用的是 BCrypt 加密存储Spring Security 或者整合spring-security-crypto都可以做到。提供登录接口后前端把 token 存在本地请求时放到请求头里后端再拦截校验。后端拦截我选择了简单的拦截器方式而不是整套 Spring Security因为素材库的权限模型比较轻量只需要区分“有没有登录”和“是不是管理员”。用拦截器配合自定义注解代码量少也容易讲清楚。如果你追求更规范的做法再用 Spring Security 也不迟但要注意 SpringBoot 版本不同配置方式差异很大。2.2 素材表与分类表设计素材表是核心字段不能只放一个文件路径还要记录素材类型、大小、格式、上传人、下载次数、状态等信息。我最终的表结构如下CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 素材标题, type varchar(20) NOT NULL COMMENT 类型image/video/audio/document, url varchar(500) DEFAULT NULL COMMENT 访问路径, local_path varchar(500) DEFAULT NULL COMMENT 本地存储路径, size bigint DEFAULT 0 COMMENT 文件大小字节, format varchar(50) DEFAULT NULL COMMENT 文件格式如mp4、jpg, category_id bigint DEFAULT NULL COMMENT 分类ID, user_id bigint DEFAULT NULL COMMENT 上传用户ID, download_count int DEFAULT 0 COMMENT 下载次数, status tinyint DEFAULT 1 COMMENT 1正常 0禁用, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;分类表category就简单了id、name、description、sort。素材表和分类表之间存在外键关系但在实际代码里我并没有强外键而是通过逻辑关联查询分类名称。这样做的好处是删除分类时更灵活不会因为外键约束导致误删后一堆报错。答辩时可以解释为“为了保证扩展性和删除便利性”。除了这两张表你还可以加一张统计表或者用 SQL 实时统计。通常不需要单独建表直接count(*) group by type就能获得各类型素材数量。2.3 MyBatis-Plus 实体映射与建表 SQL 生成思路很多同学好奇能不能根据 Java 实体类自动生成建表 SQLMyBatis-Plus 本身不提供完整的“实体转建表语句”能力但它提供了代码生成器可以从数据库表反向生成实体类。反过来操作的话实体类写好后我建议你用手写 SQL 的方式维护表结构同时通过实体上的注解保持映射一致。举个例子实体类里用TableName(material)指定表名用TableId(type IdType.AUTO)标记主键自增用TableField(local_path)映射下划线字段。当实体类写清楚后建表 SQL 的字段顺序和类型也基本能推导出来。你要是真想把实体类直接转 SQL也有一些 IDEA 插件可以做检查或生成但最稳妥的办法还是先设计表再写实体类或者两者同步维护。毕竟 MyBatis-Plus 的BaseMapper已经帮我们做了实体属性和表字段的映射只要表存在增删改查就是现成的。MyBatis-Plus 自带分页插件使用非常简单在配置类里注册MybatisPlusInterceptor然后添加PaginationInnerInterceptor(DbType.MYSQL)。这样写page接口时只需要把PageT对象传进 mapper 方法SQL 会自动拼上LIMIT。3. 后端 SpringBoot 核心实现要点3.1 工程搭建与基础配置后端工程建议直接用 IDEA 初始化 Spring Initializr选择需要的依赖Spring Web、MySQL Driver、MyBatis-Plus、Lombok。很多人会在 MyBatis-Plus 的引用上迷糊它的 Maven 坐标是com.baomidou:mybatis-plus-boot-starter不是官方spring-boot-starter-jdbc。如果用了 SpringBoot 3.x还需要引入mybatis-plus-jsqlparser这个额外模块否则分页插件会报错这是版本太高带来的一个常见坑。application.yml里除了配置数据源还要注意时区和文件上传大小限制server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/material_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 500MB max-request-size: 600MB jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted id-type: auto文件上传大小必须调大否则视频素材超过 1MB 就会直接报错。map-underscore-to-camel-case开启后数据库的category_id会自动映射到实体类的categoryId不用手写 resultMap。3.2 素材增删改查接口实现后端接口不要堆在 Controller 里一大坨每个接口只负责收参数把逻辑下沉到 Service。我写一个简化版的 MaterialController展示增删改查的整体结构RestController RequestMapping(/api/material) public class MaterialController { Resource private MaterialService materialService; PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file, RequestParam(title) String title, RequestParam(categoryId) Long categoryId) { return Result.ok(materialService.upload(file, title, categoryId)); } GetMapping(/page) public ResultIPageMaterialVO page(RequestParam(defaultValue 1) long page, RequestParam(defaultValue 10) long limit, RequestParam(required false) String type, RequestParam(required false) String keyword) { return Result.ok(materialService.findPage(page, limit, type, keyword)); } GetMapping(/{id}) public ResultMaterialVO detail(PathVariable Long id) { return Result.ok(materialService.getDetail(id)); } PutMapping(/{id}) public ResultBoolean update(PathVariable Long id, RequestBody MaterialDTO dto) { return Result.ok(materialService.updateMaterial(id, dto)); } DeleteMapping(/{id}) public ResultBoolean delete(PathVariable Long id) { return Result.ok(materialService.deleteMaterial(id)); } }Service 里要注意查询列表时不能只返回实体类还要带上分类名称和上传人昵称所以需要定义 VO 对象。分页查询用 LambdaQueryWrapper 构造条件public IPageMaterialVO findPage(long page, long limit, String type, String keyword) { PageMaterial p new Page(page, limit); LambdaQueryWrapperMaterial wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(type), Material::getType, type) .and(StringUtils.hasText(keyword), qw - qw .like(Material::getTitle, keyword) .or().like(Material::getFormat, keyword)); IPageMaterial materialPage materialMapper.selectPage(p, wrapper); return convertToVO(materialPage); }这段代码里eq条件只有在 type 不为空时才生效避免了手动拼接 SQL 的繁琐。selectPage是 MyBatis-Plus 提供的通用方法直接返回分页结果前端只需要把 total 和 records 用起来即可。3.3 文件上传与媒体访问路径设计文件存储是多媒体素材库最容易出问题的地方。你不希望把所有文件存到数据库里那样数据库会迅速膨胀。正确做法是把文件存到服务器磁盘数据库只记录路径。我采用的是本地磁盘存储方案public String upload(MultipartFile file, String title, Long categoryId) { String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) suffix; String monthDir new SimpleDateFormat(yyyy-MM).format(new Date()); File dir new File(uploadDir / monthDir); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, fileName)); // 保存数据库记录url/files/yyyy-MM/uuid.jpg }为了让前端能通过 URL 访问到文件需要把本地的上传目录映射为静态资源路径。SpringBoot 里可以写一个配置类实现WebMvcConfigurerConfiguration public class WebConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadDir /); } }这里一定要写file:前缀否则 Spring 会把它当成 classpath 下的路径导致文件永远访问不到。类似的“路径魔改”问题还有 slash 结尾官方推荐在addResourceLocations末尾加上斜杠。3.4 SpringBoot 版本太高导致的兼容性问题SpringBoot 从 2.x 升级到 3.x不仅仅是 Java 版本要求变了很多依赖的坐标和配置方式都变了。比如javax.servlet换成了jakarta.servlet很多老代码的包名要改。另一个变化是自动配置文件SpringBoot 2.x 用spring.factoriesSpringBoot 3.x 改用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。如果你拿到的是传统自定义 starter在 3.x 里就会失效。我的建议是如果只是做课设和毕设不要去追求最新版。直接用 SpringBoot 2.7.18 搭配 Java 8稳定性最高网上资料也最多遇到问题随便一搜就有答案。非要用 3.x 的话数据库驱动要选com.mysql.cj.jdbc.DriverMyBatis-Plus 要用 3.5.3 以上的版本并且额外引入 jsqlparser 依赖注意分页插件的包路径从com.baomidou.mybatisplus.extension.plugins变成了com.baomidou.mybatisplus.extension.plugins.inner。4. 前端 Vue 实现与多媒体播放细节4.1 Vue 环境安装与项目初始化前端我用了 Vue 2.7 Element UI组件库生态成熟网上遇到问题容易找解决方案。如果你电脑上还没有 Node.js先去官网下载长期支持版然后安装 cnpm 或者直接用 npm。为了加速依赖安装建议设置镜像源。node -v npm -v npm config set registry https://registry.npmmirror.com # 创建项目 npm install -g vue/cli vue create material-frontend创建项目时选择 Manually select features勾选 Router 和 Vuex可选。进入项目目录后安装额外依赖npm install element-ui axios vue-router --save npm install hls.js --save不少同学在npm install阶段失败多半是网络问题切换镜像源后基本能解决。还有一类问题是 Node 版本太高导致 node-sass 编译失败解决办法是换成sass替代node-sass或者把 Node 版本降到 16 左右。4.2 页面结构与 Vue Router、插槽的使用前端页面我划分为三个核心视图登录页、素材列表页、素材上传页。用 Vue Router 做路由切换访问/login跳转登录登录后进入/material。路由守卫里需要检查 tokenrouter.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path ! /login !token) { next(/login); } else { next(); } });在素材列表页里每个素材卡片就是一个子组件。为了让父页面能灵活控制卡片底部按钮我用了插槽。这是 Vue 一个非常实用又容易忽略的知识点template material-card v-foritem in list :keyitem.id :materialitem template #actions el-button sizemini typeprimary clickhandlePreview(item)预览/el-button el-button sizemini typedanger clickhandleDelete(item)删除/el-button /template /material-card /template子组件MaterialCard中只需要在需要扩展的位置留出slot nameactions/slot父组件就能往这个位置填充任意内容。答辩时被问到“组件复用怎么做”就可以拿这个例子回答。4.3 m3u8 视频播放组件的落地写法视频预览是整个项目里最能出效果的点。m3u8 是一种由多段切片组成的播放列表文件常用于直播和点播。问题在于原生video在大多数浏览器里不支持直接播放 HLS 流所以需要引入 hls.js。我的播放组件这样写template div classvideo-player-wrapper video refvideoEl controls classvideo-player/video /div /template script import Hls from hls.js; export default { name: M3u8Player, props: { src: { type: String, required: true } }, mounted() { this.play(this.src); }, methods: { play(url) { const video this.$refs.videoEl; if (Hls.isSupported()) { const hls new Hls(); hls.loadSource(url); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () { video.play(); }); } else if (video.canPlayType(application/vnd.apple.mpegurl)) { // Safari 原生支持 video.src url; video.addEventListener(loadedmetadata, () video.play()); } else { this.$message.error(当前浏览器不支持播放); } } }, beforeDestroy() { if (this.hls) { this.hls.destroy(); } } }; /script注意几个细节。第一hls.js实例在组件销毁时一定要destroy()否则内存泄漏。第二跨域问题视频域名需要支持 CORS或在 Nginx 里加上对应响应头。第三播放地址不能过期比如后端生成签名 URL 后必须在有效期内访问。如果你拿到的 m3u8 里有中文路径记得用encodeURI编码。5. 数据库操作与查询性能优化5.1 常用增删改查操作与分页插件整个项目的数据库操作全部基于 MyBatis-Plus。单表增删改查可以直接用BaseMapper里提供的方法不怎么写 SQL。比如materialMapper.insert(material); materialMapper.updateById(material); materialMapper.deleteById(id); materialMapper.selectById(id);复杂查询才需要写 Wrapper 或自定义 XML。比如按分类和关键词组合查询使用 LambdaQueryWrapper 就能完成不易出错。真正的分页查询要靠分页插件注册方式如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页时传入的页码从 1 开始而不是从 0 开始。前端分页组件如果默认从 0 开始和后端一对接就会出现第一页查不到数据的问题。这个细节虽然小但排查起来很费时间建议一开始就约定好。5.2 多条件筛选与统计查询素材列表肯定要有筛选条件类型、分类、上传时间、关键词。四个条件同时出现时SQL 的 where 条件要动态拼接。MyBatis-Plus 的 Wrapper 写法可以判定条件推荐下面这种LambdaQueryWrapperMaterial wrapper new LambdaQueryWrapper(); wrapper.eq(type ! null !type.isEmpty(), Material::getType, type); wrapper.eq(categoryId ! null, Material::getCategoryId, categoryId); wrapper.ge(startTime ! null, Material::getCreateTime, startTime); wrapper.le(endTime ! null, Material::getCreateTime, endTime); wrapper.like(keyword ! null !keyword.isEmpty(), Material::getTitle, keyword);除了列表我还加了一个统计接口用 GROUP BY 统计不同素材类型数量public ListMapString, Object countByType() { QueryWrapperMaterial wrapper new QueryWrapper(); wrapper.select(type, count(*) as count) .groupBy(type); return materialMapper.selectMaps(wrapper); }这个接口配合前端 ECharts 可以做环形图展示答辩时展示效果非常直观。注意selectMaps返回的字段名是数据库列名前端字段要按type、count来取。5.3 数据库同步与备份建议很多同学只会在本地跑项目到部署阶段才意识到数据库数据丢了就全没了。我建议开发期间至少用可视化工具Navicat、DataGrip、或者 DBx 这类轻量工具做好数据导入导出。如果需要从开发机同步到服务器数据库常用的办法是导出 SQL 文件然后在服务器执行mysqldump -uroot -p material_db material_db.sql scp material_db.sql root服务器IP:/tmp/ mysql -uroot -p material_db /tmp/material_db.sql数据库同步类工具确实存在但这类项目规模用不上别让自己陷入工具选型的内耗。备份策略最简单的是每天导出一份 SQL或者直接使用云数据库的自动备份功能。本地开发时注意 MySQL 8.0 的默认字符集是 utf8mb4服务器如果也保持一致就不会出现中文乱码。6. 部署上线与常见问题排查6.1 前后端打包与 Nginx 部署本地开发完最终要跑在服务器上。后端打包mvn clean package -DskipTests生成target/material-backend-0.0.1-SNAPSHOT.jar。前端打包npm run build生成dist目录。服务器上我一般用一个 Nginx 同时托管前端静态资源并反向代理后端接口server { listen 80; server_name yourdomain.com; root /opt/material/frontend/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /files/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } }这里try_files配置很重要否则 Vue Router 在 history 模式下刷新页面会 404。/api和/files的代理保证前端请求和后端静态资源都能正常访问。6.2 服务器环境准备与数据库导入服务器如果是 CentOS 或 Ubuntu先装好 JDK 和 MySQL。Jar 包可以直接用nohup java -jar启动但更规范的做法是用 systemd 托管服务。创建一个material-backend.service[Unit] DescriptionMaterial Backend Afternetwork.target [Service] Userroot WorkingDirectory/opt/material/backend ExecStart/usr/bin/java -jar /opt/material/backend/material-backend.jar Restartalways [Install] WantedBymulti-user.target然后把 SQL 文件导入服务器数据库mysql -uroot -p -e create database material_db default character set utf8mb4; mysql -uroot -p material_db /opt/material/backend/sql/init.sql数据库导入时最容易出错的点是密码含特殊字符。如果密码里有或#在application.yml里要谨慎处理必要时用引号包起来否则 SpringBoot 读取数据源会解析失败。6.3 典型故障清单与修复办法这部分是实战中高频踩坑的记录我整理成一张表遇到同类问题可以快速对照排查。症状可能原因解决办法后端启动报Unable to open connection数据库地址、账号密码错误检查application.yml确认服务器 MySQL 端口开放使用ping和telnet测试网络连通性前端访问接口 502Nginx 没有代理到后端服务检查 jar 包是否启动成功ps -efVue 刷新页面 404路由没有回退到 index.htmlNginx location 加上try_files $uri $uri/ /index.html;上传大文件失败SpringBoot 上传大小限制设置max-file-size和max-request-sizeNginxclient_max_body_size也要同步调大m3u8 视频只有声音或黑屏切片路径是相对路径浏览器解析异常把流地址换成绝对地址或将切片相对路径改为绝对路径hls.js 报跨域错误视频服务未设置 CORSNginx 加add_header Access-Control-Allow-Origin *;MyBatis-Plus 分页无效缺少分页插件或插件版本过低确认注册了PaginationInnerInterceptorSpringBoot3 需要额外引入 jsqlparser这些坑都不是什么高深问题但每一项都可能耗掉半天。我的习惯是先在本地复现再一步步缩小范围能够定位到“前端、后端、数据库、Nginx”四层里的某一层基本就成功一半。7. 答辩指导与项目讲解心得7.1 如何讲清楚项目亮点答辩最重要的不是代码能跑得多溜而是你能不能把设计思路讲明白。讲项目时我建议按这个顺序组织语言首先说明项目背景“这是一个面向多媒体资源的统一管理平台解决文件分散、格式杂、查找难的问题。” 接着讲技术选型“后端采用 SpringBoot MyBatis-Plus前端采用 Vue Element UI使用 MySQL 存储元数据文件存储在本地磁盘并通过 Nginx 对外提供访问。” 再讲核心实现“文件上传后生成唯一文件名按月份分目录存储数据库只保存路径视频播放使用 hls.js 支持 m3u8 格式分页查询用 MyBatis-Plus 分页插件。”最后一定要给出一个有说服力的细节。比如“在上传时校验文件类型根据扩展名自动判断素材类型并生成对应预览组件”这种业务细节比单纯说“我用了 SpringBoot”要打动人得多。7.2 高频答辩追问与应答思路老师通常会从几个固定角度追问提前准备好答案可以避免现场卡壳。第一个问题通常是你为什么用 MyBatis-Plus 而不是 MyBatis回答要点减少单表 CRUD 重复代码内置分页插件和逻辑删除SQL 通过 Wrapper 动态拼接更安全减少 XML 维护成本。同时承认复杂多表查询仍会使用自定义 SQL看到项目不只是一味依赖框架。第二个问题是文件上传是怎么实现的回答要点MultipartFile 接收文件UUID 重新命名避免文件名冲突按月分目录防止单目录文件过多数据库保存相对路径通过静态资源映射或 Nginx 暴露访问路径。 面试官可能继续问文件名冲突、超大文件、断点续传这些都留作扩展点。第三个问题是分页查询的原理。回答要点MyBatis-Plus 分页插件会拦截 SQL自动在原有查询语句上拼接LIMIT并执行一条 COUNT 语句。前端只传 page 和 limit返回 total 计算总页数。第四个问题是系统有哪些不足。要诚实回答同时给出改进方向。比如“目前文件存储在本地磁盘后续可替换为对象存储没有做视频转码如果需要兼容更多格式可以接入 FFmpeg管理员没有做细粒度的权限控制以后可以用 RBAC 模型扩展。”这样的回答反而能体现你对项目有完整思考。最后如果被问到 m3u8 播放原理就围绕“播放列表文件 切片 流式加载”来讲m3u8 文件记录的是多个 ts 视频分片的地址播放器按顺序加载并播放所以可以实现边下边播。这套项目我做下来最大的体会是“先跑通再优化”比“一开始就搞复杂架构”重要得多。第一次做的时候我在 m3u8 播放上浪费了两天后来发现就是跨域和地址拼接的问题代码逻辑非常简单。如果你正在做类似的毕设或课设建议先把上传、列表、预览这条主链路跑通再考虑扩展标签、统计和权限这些加分项。部署时也千万别跳过 Nginx 那一步从一开始就在本地模拟线上环境能少走很多弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑