前言:

我是前端程序猿一枚,不是后端的,如有写的有不规范的地方别介意哈,自己摸索了两天算是把这个功能做出来了,网上看了很多帖子没注释说实话,我看的基本是懵逼的。毕竟没有系统学过,所以现在做出来了就总结一下,自己多写点注释解释一下逻辑,让前端的小伙伴们如果也想自己做一个这个功能,可以参考参考。

包含功能:

1,前端点击通过流的形式上传视频
2,视频到后端保存到服务器本地的磁盘中
3,视频上传成功后,数据库对应出现一条信息,分别展示视频的原名,视频的唯一识别码,视频的id,视频的磁盘路径地址。
4,前端渲染出表格展示所有视频信息,根据点击播放按钮,打开对应的视频
5,后端接到请求后根据前端返回的id不同,返回不同的视频,通过视频流的形式返回播放

各文件用处解释:

SelectVideoController:后端给前端的接口写在这个文件内,其中包含两个接口。
(1)/SelectVideo/policemen/{videoId}:用来前端请求后返回对应视频流数据给前端展示视频。
(2)/SelectVideo/table:用于前端表格展示所有video数据的

uploadVideoController:后端给前端的接口写在这个文件内,其中包含一个接口
(1)/api/uploadVideo3:用于前端把本地的视频上传给后端保存在服务器磁盘并在数据库内加一条信息。

VideoUpload:数据库视频表的实体类,前端的人理解为对象。这里的变量必须和数据库的字段一样,不然报错

VideoUploadMapper:接口文件,用于后端链接数据库增删改查等操作的接口,和前端没关系,这里包含三个查询sql语句。
(1)save:用于前端上传的视频保存在数据库内增加一条信息。
(2)SelectVideoAll:用于前端表格展示所有视频信息,查询数据库所有视频信息返回
(3)SelectVideoId:用于前端传id过来,根据id查询数据库对应的一条视频信息返回

NonStaticResourceHttpRequestHandler:用于把视频转换为视频流返回给前端

Demo2Application:配置文件,这里包含一个方法
(1)multipartConfigElement:和上传视频的功能文件uploadVideoController结合的,用于限制视频大小的。

效果图

前端上传页面

前端视频数据展示页面

前端点击播放后弹框页面,我手动打码了,不用在意


mysql数据库字段

后台传过来的视频上传信息

后端代码架构,红框标出来了,其他的不用管,不是相关代码。

说一句,其他的文件有不同的功能,我也写了帖子介绍,可以自行查看,对于创建项目后各个文件作用不清楚的也可以看我其他帖子,有详细解释

后端代码

SelectVideoController

package com.example.demo.controller;import com.example.demo.entity.VideoUpload;
import com.example.demo.mapper.VideoUploadMapper;
import com.example.demo.utils.NonStaticResourceHttpRequestHandler;import lombok.AllArgsConstructor;import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;//前端获取后端视频流
@RestController
@RequestMapping("/SelectVideo")
@AllArgsConstructor
public class SelectVideoController {//引入返回视频流的组件private final NonStaticResourceHttpRequestHandler nonStaticResourceHttpRequestHandler;//把后端链接数据库接口引入进来@ResourceVideoUploadMapper videoUploadMapper;//解决跨域的注解@CrossOrigin(origins = "*", maxAge = 3600)//查询视频流的接口@GetMapping("/policemen/{videoId}")//前两个参数不管,第三个参数videoId代表前端传过来的视频的id号public void videoPreview(HttpServletRequest request, HttpServletResponse response,@PathVariable("videoId") Integer videoId) throws Exception {//调用查询方法,把前端传来的id传过去,查询对应的视频信息。VideoUpload videoPathList = videoUploadMapper.SelectVideoId(videoId);//从视频信息中单独把视频路径信息拿出来保存String videoPathUrl=videoPathList.getVideoUrl();//保存视频磁盘路径Path filePath = Paths.get(videoPathUrl );//Files.exists:用来测试路径文件是否存在if (Files.exists(filePath)) {//获取视频的类型,比如是MP4这样String mimeType = Files.probeContentType(filePath);if (StringUtils.hasText(mimeType)) {//判断类型,根据不同的类型文件来处理对应的数据response.setContentType(mimeType);}//转换视频流部分request.setAttribute(NonStaticResourceHttpRequestHandler.ATTR_FILE, filePath);nonStaticResourceHttpRequestHandler.handleRequest(request, response);} else {response.setStatus(HttpServletResponse.SC_NOT_FOUND);response.setCharacterEncoding(StandardCharsets.UTF_8.toString());}}//查询视频表格列表的接口@CrossOrigin(origins = "*", maxAge = 3600)@GetMapping("/table")public List<VideoUpload> videoTable() {//调用搜索方法查询所有视频信息,成表格展示前端return videoUploadMapper.SelectVideoAll();}}

uploadVideoController

package com.example.demo.controller;
import java.io.File;
import java.util.*;import com.example.demo.mapper.VideoUploadMapper;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;//接口:前端视频上传
@RestController
//一级地址
@RequestMapping("/api")
public class uploadVideoController {@ResourceVideoUploadMapper videoUploadMapper;//解决跨域的注解@CrossOrigin(origins = "*", maxAge = 3600)//二级地址@PostMapping(value = "/uploadVideo3")@ResponseBody//Map<String,String>: map是键值对形式组成的集合,类似前端的数组但是里面是键值对形式的,前后两个string代表键和值都是字符串格式的。//post请求传入的参数:MultipartFile file(理解为springmvc框架给我们提供的工具类,代表视频流数据),SavePath(前台传来的地址路径,也是用来后端保存在服务器哪个文件夹的地址)public Map<String,String> savaVideoTest(@RequestParam("file") MultipartFile file,@RequestParam String SavePath)//throws IllegalStateException写在方法的前面是可以抛出异常状态的,如果有错误会把错误信息发出来对应下面的try和catchthrows IllegalStateException {//new一个map集合出来Map<String,String> resultMap = new HashMap<>();try{//获取文件后缀,因此此后端代码可接收一切文件,上传格式前端限定String fileExt = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1).toLowerCase();// 重构文件名称System.out.println("前端传递的保存路径:"+SavePath);//UUID(全局唯一标识符)randomUUID(随机生成标识符)toString(转成字符串)replaceAll(替换字符方法,因为随机生成的里面包括了 - ,这里意思是把 - 全部换成空)String pikId = UUID.randomUUID().toString().replaceAll("-", "");//视频名字拼接:唯一标识符加上点,再加上上面的视频后缀也就是MP4之类的。就组成了现在的视频名字,比如这样:c7bbc1f9664947a287d35dd7cdc48a95.mp4String newVideoName = pikId + "." + fileExt;System.out.println("重构文件名防止上传同名文件:"+newVideoName);//保存视频的原始名字String videoNameText = file.getOriginalFilename();System.out.println("视频原名:"+videoNameText);//保存视频url路径地址String videoUrl = SavePath + "/" + newVideoName;//调用数据库接口插入数据库方法save,把视频原名,视频路径,视频的唯一标识码传进去存到数据库内videoUploadMapper.save(videoNameText,videoUrl,newVideoName);//判断SavePath这个路径也就是需要保存视频的文件夹是否存在File filepath = new File(SavePath, file.getOriginalFilename());if (!filepath.getParentFile().exists()) {//如果不存在,就创建一个这个路径的文件夹。filepath.getParentFile().mkdirs();}//保存视频:把视频按照前端传来的地址保存进去,还有视频的名字用唯一标识符显示,需要其他的名字可改这File fileSave = new File(SavePath, newVideoName);//下载视频到文件夹中file.transferTo(fileSave);//构造Map将视频信息返回给前端//视频名称重构后的名称:这里put代表添加进map集合内,和前端的push一样。括号内是前面字符串是键,后面是值resultMap.put("newVideoName",newVideoName);//正确保存视频成功,则设置返回码为200resultMap.put("resCode","200");//返回视频保存路径resultMap.put("VideoUrl",SavePath + "/" + newVideoName);//到这里全部保存好了,把整个map集合返给前端return  resultMap;}catch (Exception e){//在命令行打印异常信息在程序中出错的位置及原因e.printStackTrace();//返回有关异常的详细描述性消息。e.getMessage();//保存视频错误则设置返回码为400resultMap.put("resCode","400");//这时候错误了,map里面就添加了错误的状态码400并返回给前端看return  resultMap ;}}}

VideoUpload

package com.example.demo.entity;//视频数据库实体类
public class VideoUpload {private int id;private String videoName;private String videoUrl;private String videoUUID;public VideoUpload(int id, String videoName, String videoUrl, String videoUUID) {this.id = id;this.videoName = videoName;this.videoUrl = videoUrl;this.videoUUID = videoUUID;}public VideoUpload() {}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getVideoName() {return videoName;}public void setVideoName(String videoName) {this.videoName = videoName;}public String getVideoUrl() {return videoUrl;}public void setVideoUrl(String videoUrl) {this.videoUrl = videoUrl;}public String getVideoUUID() {return videoUUID;}public void setVideoUUID(String videoUUID) {this.videoUUID = videoUUID;}
}

NonStaticResourceHttpRequestHandler

package com.example.demo.utils;import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.http.HttpServletRequest;
import java.nio.file.Path;
//返回视频流@Component
public class NonStaticResourceHttpRequestHandler extends ResourceHttpRequestHandler {public final static String ATTR_FILE = "NON-STATIC-FILE";@Overrideprotected Resource getResource(HttpServletRequest request) {final Path filePath = (Path) request.getAttribute(ATTR_FILE);return new FileSystemResource(filePath);}}

VideoUploadMapper

package com.example.demo.mapper;import com.example.demo.entity.VideoUpload;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.transaction.annotation.Transactional;import java.util.List;public interface VideoUploadMapper {//插入数据到数据库内,目前需要把id加上,不会自动生成id,不然报错@Update("INSERT INTO `video_upload`( `videoName`, `videoUrl`, `videoUUID`) VALUES (#{videoName},#{videoUrl},#{videoUUID});")//事务注解:可加可不加@Transactional//接收传过来的参数数据void save(String videoName,String videoUrl,String videoUUID);//查询数据库内表名为video_upload的全部数据返回@Select("select * from video_upload")//方法:以数组的形式返回数据库信息List<VideoUpload> SelectVideoAll();//查询数据库内表名为video_upload的id=videoId的那一条数据@Select("select * from video_upload where id = #{videoId}")//方法:以表格格式(就是数据库字段一样的格式直接返回一个对象里面包含各个字段和信息)返回VideoUpload SelectVideoId(Integer videoId);
}

Demo2Application

package com.example.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.util.unit.DataSize;
import org.springframework.context.annotation.Bean;
import javax.servlet.MultipartConfigElement;@SpringBootApplication
//这里写的是告诉spring框架mapper接口在什么位置,然后找到对应的地方扫描。不写框架会找不到接口
@MapperScan("com.example.demo.mapper")
public class Demo2Application {public static void main(String[] args) {SpringApplication.run(Demo2Application.class, args);}/*** 文件上传配置* @return*/@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory factory = new MultipartConfigFactory();//单个文件最大factory.setMaxFileSize(DataSize.parse("400MB")); //KB,MB/// 设置总上传数据总大小factory.setMaxRequestSize(DataSize.parse("400MB"));return factory.createMultipartConfig();}}

前端代码

视频上传页面

html

        <el-tab-pane label="业务视频" name="second"><el-form ref="form" :model="form" label-width="80px"><el-form-item label="上传视频"><el-uploadclass="avatar-uploader el-upload--text":drag="Plus"action="http://localhost:8001/api/uploadVideo3"multiple:show-file-list="false":data="{ SavePath: this.Path.url }":on-success="handleVideoSuccess":before-upload="beforeUploadVideo":on-progress="uploadVideoProcess"><i v-if="Plus" class="el-icon-upload"></i><div v-if="Plus" class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div><el-progressv-if="videoFlag == true"type="circle":percentage="videoUploadPercent"style="margin-top: 30px"></el-progress><div class="el-upload__tip" slot="tip">只能上传mp4/flv/avi文件,且不超过300M</div></el-upload></el-form-item><el-form-item><el-button type="primary" @click="onSubmit">立即创建</el-button></el-form-item></el-form></el-tab-pane>

JS

 data() {return {//视频部分videoForm: {videoId: '',videoUrl: ''},videoFlag: false,Plus: true,//上传视频时带的参数,这个地址就是后端保存磁盘的地址。可以更改。不建议放F盘,有的电脑可能没有F盘,只有C和DPath: {url: 'D:/video/videoUpload'},videoUploadPercent: 0};},methods:{//视频部分// 视频上传前执行beforeUploadVideo (file) {//文件大小const isLt300M = file.size / 1024 / 1024 < 300//视频后缀检查if (['video/mp4', 'video/ogg', 'video/flv', 'video/avi', 'video/wmv', 'video/rmvb'].indexOf(file.type) === -1) {this.$message.error('请上传正确的视频格式')return false}if (!isLt300M) {this.$message.error('上传视频大小不能超过300MB哦!')return false}},// 视频上传过程中执行uploadVideoProcess (event, file, fileList) {this.Plus = falsethis.videoFlag = truethis.videoUploadPercent =+ file.percentage.toFixed(0)},// 视频上传成功是执行handleVideoSuccess (res, file) {this.Plus = falsethis.videoUploadPercent = 100console.log(res)// 如果为200代表视频保存成功if (res.resCode === '200') {// 接收视频传回来的名称和保存地址// 至于怎么使用看你啦~this.videoForm.videoId = res.newVidoeNamethis.videoForm.videoUrl = res.VideoUrlthis.$message.success('视频上传成功!')} else {this.$message.error('视频上传失败,请重新上传!')}}},
};

视频表格展示播放页面

<template><div class="release_wrap"><div class="release_title">业 务 视 频</div><el-card class="release_card"><el-buttontype="primary"roundicon="el-icon-arrow-left"style="margin-bottom: 40px"@click="jump_home">返回</el-button><el-table stripe :data="tableData" style="width: 100%" height="600px"><el-table-column prop="videoName" label="视频名称" min-width="280"></el-table-column><el-table-column label="操作"><template slot-scope="scope"><el-buttonsize="mini"type="primary"@click="playVideo(scope.$index, scope.row)">播放</el-button></template></el-table-column></el-table></el-card><el-dialog:modal="false"title="视频播放":visible.sync="dialogVisible"width="40%"><p class="video_title">{{videoName}}</p><video:src="`http://localhost:8001/SelectVideo/policemen/${videoId}`"controls="controls"width="100%"@canplay="getVidDur()"id="myvideo"></video></el-dialog></div>
</template><script>
var video = () => {var videoTime = document.getElementById("myvideo");console.log(videoTime.duration); //获取视频时长console.log(videoTime.currentTime); //获取视频当前播放时间
};
import { mapState } from "vuex";
export default {data() {return {title: "",videolist: "",//表格数据tableData: [],//弹框组件隐藏dialogVisible: false,//用于保存视频的idvideoId: 0,//保存视频的名称videoName:''};},computed: {//引入vuex中state的变量,可以直接this.xxx调用到...mapState(["article"]),},created() {this.getVideoInfo();},methods: {jump_home() {this.$router.go(-1);},getVidDur() {video();},//获取video表格数据getVideoInfo() {this.$axios.get("/SelectVideo/table").then((res) => {this.tableData = res;console.log(res);});},//点击播放按钮playVideo(i, val) {//显示弹框this.dialogVisible = true;//保存视频名字this.videoName=val.videoName;//保存视频idthis.videoId=val.id;console.log(i, val);},},
};
</script><style  scoped lang='scss'>
.release_wrap {background-image: url("../assets/home1.jpg");width: 100%;height: 100%;position: relative;
}
//卡片效果
.release_card {width: 70%;position: absolute;top: 200px;left: 50%;transform: translateX(-50%);box-shadow: 0 0.3px 0.7px rgba(0, 0, 0, 0.126),0 0.9px 1.7px rgba(0, 0, 0, 0.179), 0 1.8px 3.5px rgba(0, 0, 0, 0.224),0 3.7px 7.3px rgba(0, 0, 0, 0.277), 0 10px 20px rgba(0, 0, 0, 0.4);transition: 0.5s ease; //改变大小&:hover {box-shadow: 0 0.7px 1px rgba(0, 0, 0, 0.157),0 1.7px 2.6px rgba(0, 0, 0, 0.224), 0 3.5px 5.3px rgba(0, 0, 0, 0.28),0 7.3px 11px rgba(0, 0, 0, 0.346), 0 20px 30px rgba(0, 0, 0, 0.5);}
}
.el-tag + .el-tag {margin-left: 10px;
}
.button-new-tag {margin-left: 10px;height: 32px;line-height: 30px;padding-top: 0;padding-bottom: 0;
}
.input-new-tag {width: 90px;margin-left: 10px;vertical-align: bottom;
}
// title效果
.release_title {text-align: center;font-size: 38px;font-weight: bold;position: absolute;top: 75px;left: 50%;transform: translateX(-50%);font-family: Lato, sans-serif; //字体letter-spacing: 4px; //字符间距空白text-transform: uppercase; //转换文本,控制大小写background: linear-gradient(90deg,rgba(0, 0, 0, 1) 0%,rgba(255, 255, 255, 1) 50%,rgba(0, 0, 0, 1) 100%);background-size: 80%; //背景大小background-repeat: no-repeat; //背景平铺不重复// below two lines create text gradient effectcolor: rgba(237, 227, 231, 0.7); //颜色透明background-clip: text; //规定背景的绘制区域在文字上animation: shining 3s linear infinite;
}
@keyframes shining {from {background-position: -500%; //背景图像的起始位置}to {background-position: 500%; //背景图像的结束位置}
}
.video_title {text-align: center;font-size: 22px;font-weight: bold;letter-spacing: 3px;
}
</style>

【视频流上传播放功能】前后端分离用springboot-vue简单实现视频流上传和播放功能【详细注释版本,包含前后端代码】相关推荐

  1. Nginx实现前后端分离(springboot+vue)+双机互备

    背景介绍 项目采用springboot+vue开发,之 前项目布署时,都是采用pom中配置,把vue打包的dist文件copy到springboot项目中resource/static下做的,这样每次 ...

  2. 前后端分离的用户验证原理及Spring Boot + JWT的框架搭建(附完整的框架代码)之二

    本篇承接上一篇,关于Session以及JWT Token参考: 前后端分离的用户验证原理及Spring Boot + JWT的框架搭建(附完整的框架代码)之一 框架整体描述 框架使用Spring Bo ...

  3. Python Web前后端分离框架Django+Vue搭建

    Python Web前后端分离框架Django+Vue搭建 对前面所学知识的归纳整理,感兴趣的可以看看,欢迎指正. 一.前后端分离框架介绍 本项目基于 Python 的 Web 框架开发,采用前后端分 ...

  4. SpringCloud学习笔记018---SpringBoot前后端分离_集成_SpringSecurity_简单实现

    SpringBoot前后端分离_集成_SpringSecurity_简单实现 1.新建SpringBoot项目,可以使用idea,快速创建    file-create-project->选择w ...

  5. 基于web的前后端分离nodejs和vue.js医院分诊系统

    (1)系统设置模块:包括权限管理和用户信息管理.此模块主要功能包括:添加.修改.删除和查看用户信息,给用户分配权限进行角色管理. (2)用户管理:用户进行登录和注册,进行挂号 (3)患者管理模块:此模 ...

  6. SpringBoot+Vue+Mybatis-plus 博客(七):完成友链管理前后端对接

    SpringBoot+Vue+Mybatis-plus 博客:个人博客介绍及效果展示 SpringBoot+Vue+Mybatis-plus 博客(一):完成博客后台前端登录页面.后端登录接口 Spr ...

  7. 若依前后端分离版源码分析-前端头像上传后传递到后台以及在服务器上存储和数据库存储设计

    场景 使用若依前后端分离版本时,分析其头像上传机制. 可作为续参考学习. 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获 ...

  8. 单realm模式下前后端分离实现springboot+shiro+jwt+vue整合

    shiro+jwt实现前后端分离 一.RBAC概念 基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注.在R ...

  9. 前后端分离:SpringBoot治好了我的时间内耗

    1.前后端分离为了什么 1.1前言 ​ 本文作为系列文章的第一篇,是铁柱在工作之余对自我学习的总结,以下内容是搭建基础的前后端分离的Demo来展开的,需要对MySQL,Spring,SpringMVC ...

  10. 前后端分离的用户验证原理及Spring Boot + JWT的框架搭建(附完整的框架代码)之一

    Java Web项目开发方式 Java Web的开发有以下几种: 单纯JSP开发 结合模板引擎的JSP开发(比如Thymeleaf),模板引擎提供了更多页面和数据结合的组件,很大程度减轻了页面开发的工 ...

最新文章

  1. (八)pdf的构成之文件体(page属性)
  2. java se 与j2se_关于java:J2EE和J2SE项目之间的区别
  3. Multidex实现简要分析
  4. DeepFocus,基于AI实现更逼真的VR图像
  5. mysql 多数据库事务_多数据库事务处理
  6. java拆分数据查相等_scikit learn:train_test_split,我可以确保在不同的数据集上进行相同的拆分...
  7. linux运行win7,Windows7 上运行docker实战
  8. 在Linux系统下生产者消费者,Linux线程编程之生产者消费者问题
  9. Servlet过滤器和监听器知识总结
  10. shell脚本 把一个文件的内容全部转换为大写
  11. Python入门经典学习1-乳腺癌分类问题
  12. 微信小程序超级占内存_可能没有想象的那么美好——微信小程序存储占用与清理实测...
  13. Java类加载机制--类加载过程(解析)
  14. Spring5,最全教程,带你认识IOC容器和AOP切面
  15. 如何给老年唱戏机下载有声小说
  16. 20170216.双目摄像机标定参数说明
  17. python sin_Python数字sin()方法
  18. 如何把数字金额转换成中文大写
  19. 什么是一等对象 first-class object(第一类对象)?
  20. 她是一位中学计算机老师的英文,计算机柳城中学英语组 讲课老师 : 陈文化.ppt...

热门文章

  1. 轻松转换矢量图的小工具Vector Magic
  2. 2021深圳杯d题数学建模 基于一个微分对策问题的机器学习能力定量评价
  3. 二阶常系数微分方程求解步骤
  4. 全球十大机器人运动控制品牌
  5. DSF 洛谷 P1294 高手去散步
  6. get请求和post请求的区别
  7. 模拟数据生成工具--Faker
  8. [渝粤教育] 西南科技大学 液压与气压传动 在线考试复习资料(1)
  9. 城市大数据及开放数据索引
  10. WO Mic -免费话筒