当前位置: 首页 > news >正文

做任务网站源码/上海seo关键词优化

做任务网站源码,上海seo关键词优化,app制作教程课件,模块网站怎么做本文介绍:SpringBoot 中大文件(分片上传)断点续传与极速秒传功能的实现使用背景介绍由于涉及到大文件就会出现前端提交到后端很慢或者超时的现象。所以本文讲一下断点 / 分片续传的方案。以下提供三种方案 : 前提前端进行根据文件阈值进行切割…

本文介绍:SpringBoot 中大文件(分片上传)断点续传与极速秒传功能的实现

使用背景介绍

由于涉及到大文件就会出现前端提交到后端很慢或者超时的现象。所以本文讲一下断点 / 分片续传的方案。以下提供三种方案 : 前提前端进行根据文件阈值进行切割分片提交多个分片到后台,每次与后台交互进行一个分片交互。涉及前端进度条的问题,可以使用假进度条实现(如果使用真进度条,需要频繁请求后端方知上传真实进度,此方式抛弃)

一、利用数据库记录上传分片的进度

前端把大文件进行按照设定的文件阈值进行分片好,进行提交后台进行上传分片文件,每次上传完分片数据库记录分片信息,下次前端提交上传时,校验该分片是否符合下个分片,整个大文件的分片提交完,则合并分片文件到正式存储目录

二、利用多个临时文件记录上传分片进度

前端把大文件进行按照设定的文件阈值进行分片好,进行提交后台进行上传分片文件,每次上传完分片创建临时文件记录分片信息,下次前端提交上传时,利用是否存在该分片文件且校验分片文件大小,如果不一致则删除分片文件,接收前端上传文件进行上传,整个大文件的分片提交完,则合并分片文件到正式存储目录

三、利用单个临时文件记录上传进度

前端把大文件进行按照设定的文件阈值进行分片好,进行提交后台进行上传分片文件,每次上传分片判断临时文件是否存在,否则需创建临时文件(代表该操作的唯一标识文件),上传的分片文件内容输入到这个临时文件,下次前端提交分片上传时,利用是否存在该临时文件且校验临时文件的大小是否匹配上传的分片开始处大小(每次交互前 前端需请求询问后端该文件上传的最终大小是多少, 以便前端进行续传)继续输入分片内容到临时文件,接收前端上传文件进行上传,整个大文件的分片提交完,则把临时文件拷贝到正式存储的文件进行存储。

推荐使用第三种,相比数据库记录方式可以对第三方进行解耦。

这里采用了两种方法去实现

方法一:借用数据库和多文件实现断点续传

该方法对数据有要求,需要提供分片个数的数据字段存储

引入 jar 包

<!--lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--文件上传依赖--><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version></dependency><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency><!-- mysql的依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- mybatis-plus依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.2</version></dependency>

配置文件

spring.resources.static-locations=classpath:/static
server.port=8000#设置上传图片的路径
file.basepath=D:/BaiduNetdiskDownload/# 设置单个文件大小
spring.servlet.multipart.max-file-size= 50MB
# 设置单次请求文件的总大小
spring.servlet.multipart.max-request-size= 50MB#设置要连接的mysql数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

创建文件存储表,该表一些字段对文件存储需要用到

create table file
(id INTEGER primary key AUTO_INCREMENT comment 'id',path varchar(100) not null COMMENT '相对路径',name varchar(100) COMMENT '文件名',suffix varchar(10) COMMENT '文件后缀',size int COMMENT '文件大小|字节B',created_at BIGINT(20) COMMENT '文件创建时间',updated_at bigint(20) COMMENT '文件修改时间',shard_index int comment '已上传分片',shard_size int COMMENT '分片大小|B',shard_total int COMMENT '分片总数',file_key varchar(100) COMMENT '文件标识'
)

创建实体类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName(value = "file")
public class FileDTO {/*** id*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 相对路径*/private String path;/*** 文件名*/private String name;/*** 后缀*/private String suffix;/*** 大小|字节B*/private Integer size;/*** 创建时间*/private Long createdAt;/*** 修改时间*/private Long updatedAt;/*** 已上传分片*/private Integer shardIndex;/*** 分片大小|B*/private Integer shardSize;/*** 分片总数*/private Integer shardTotal;/*** 文件标识*/private String fileKey;}

创建 mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.upload.entity.FileDTO;
import org.springframework.stereotype.Repository;@Repository
public interface FileMapper extends BaseMapper<FileDTO> {
}

创建 service

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.upload.dao.FileMapper;
import com.example.demo.upload.entity.FileDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class FileService {@Autowiredprivate FileMapper fileMapper;//保存文件public void save(FileDTO file1){//根据 数据库的 文件标识来查询 当前视频 是否存在LambdaQueryWrapper<FileDTO> lambda = new QueryWrapper<FileDTO>().lambda();lambda.eq(FileDTO::getFileKey,file1.getFileKey());List<FileDTO> fileDTOS = fileMapper.selectList(lambda);//如果存在就话就修改if(fileDTOS.size()!=0){//根据key来修改LambdaQueryWrapper<FileDTO> lambda1 = new QueryWrapper<FileDTO>().lambda();lambda1.eq(FileDTO::getFileKey,file1.getFileKey());fileMapper.update(file1,lambda1);}else{//不存在就添加fileMapper.insert(file1);}}//检查文件public List<FileDTO> check(String key){LambdaQueryWrapper<FileDTO> lambda = new QueryWrapper<FileDTO>().lambda();lambda.eq(FileDTO::getFileKey,key);List<FileDTO> dtos = fileMapper.selectList(lambda);return dtos;}}

创建 utils

import lombok.Data;/*** 统一返回值** @author zhangshuai**/
@Data
public class Result {// 成功状态码public static final int SUCCESS_CODE = 200;// 请求失败状态码public static final int FAIL_CODE = 500;// 查无资源状态码public static final int NOTF_FOUNT_CODE = 404;// 无权访问状态码public static final int ACCESS_DINE_CODE = 403;/*** 状态码*/private int code;/*** 提示信息*/private String msg;/*** 数据信息*/private Object data;/*** 请求成功** @return*/public static Result ok() {Result r = new Result();r.setCode(SUCCESS_CODE);r.setMsg("请求成功!");r.setData(null);return r;}/*** 请求失败** @return*/public static Result fail() {Result r = new Result();r.setCode(FAIL_CODE);r.setMsg("请求失败!");r.setData(null);return r;}/*** 请求成功,自定义信息** @param msg* @return*/public static Result ok(String msg) {Result r = new Result();r.setCode(SUCCESS_CODE);r.setMsg(msg);r.setData(null);return r;}/*** 请求失败,自定义信息** @param msg* @return*/public static Result fail(String msg) {Result r = new Result();r.setCode(FAIL_CODE);r.setMsg(msg);r.setData(null);return r;}/*** 请求成功,自定义信息,自定义数据** @param msg* @return*/public static Result ok(String msg, Object data) {Result r = new Result();r.setCode(SUCCESS_CODE);r.setMsg(msg);r.setData(data);return r;}/*** 请求失败,自定义信息,自定义数据** @param msg* @return*/public static Result fail(String msg, Object data) {Result r = new Result();r.setCode(FAIL_CODE);r.setMsg(msg);r.setData(data);return r;}public Result code(Integer code){this.setCode(code);return this;}public Result data(Object data){this.setData(data);return this;}public Result msg(String msg){this.setMsg(msg);return this;}}

创建 controller

import com.example.demo.upload.entity.FileDTO;
import com.example.demo.upload.service.FileService;
import com.example.demo.upload.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;import java.io.*;
import java.util.List;
import java.util.UUID;@Controller
@RequestMapping("/file")
@Slf4j
public class FileController {@AutowiredFileService fileService;public static final String BUSINESS_NAME = "普通分片上传";// 设置图片上传路径@Value("${file.basepath}")private String basePath;@RequestMapping("/show")public String show(){return "file";}/*** 上传* @param file* @param suffix* @param shardIndex* @param shardSize* @param shardTotal* @param size* @param key* @return* @throws IOException* @throws InterruptedException*/@RequestMapping("/upload")@ResponseBodypublic String upload(MultipartFile file,String suffix,Integer shardIndex,Integer shardSize,Integer shardTotal,Integer size,String key) throws IOException, InterruptedException {log.info("上传文件开始");//文件的名称String name = UUID.randomUUID().toString().replaceAll("-", "");// 获取文件的扩展名String ext = FilenameUtils.getExtension(file.getOriginalFilename());//设置图片新的名字String fileName = new StringBuffer().append(key).append(".").append(suffix).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4//这个是分片的名字String localfileName = new StringBuffer(fileName).append(".").append(shardIndex).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1// 以绝对路径保存重名命后的图片File targeFile=new File(basePath,localfileName);//上传这个图片file.transferTo(targeFile);//数据库持久化这个数据FileDTO file1=new FileDTO();file1.setPath(basePath+localfileName);file1.setName(name);file1.setSuffix(ext);file1.setSize(size);file1.setCreatedAt(System.currentTimeMillis());file1.setUpdatedAt(System.currentTimeMillis());file1.setShardIndex(shardIndex);file1.setShardSize(shardSize);file1.setShardTotal(shardTotal);file1.setFileKey(key);//插入到数据库中//保存的时候 去处理一下 这个逻辑fileService.save(file1);//判断当前是不是最后一个分页 如果不是就继续等待其他分页 合并分页if(shardIndex .equals(shardTotal) ){file1.setPath(basePath+fileName);this.merge(file1);}return "上传成功";}@RequestMapping("/check")@ResponseBodypublic Result check(String key){List<FileDTO> check = fileService.check(key);//如果这个key存在的话 那么就获取上一个分片去继续上传if(check.size()!=0){return Result.ok("查询成功",check.get(0));}return Result.fail("查询失败,可以添加");}/*** @author fengxinglie* 合并分页*/private void merge(FileDTO fileDTO) throws FileNotFoundException, InterruptedException {//合并分片开始log.info("分片合并开始");String path = fileDTO.getPath(); //获取到的路径 没有.1 .2 这样的东西//截取视频所在的路径path = path.replace(basePath,"");Integer shardTotal= fileDTO.getShardTotal();File newFile = new File(basePath + path);FileOutputStream outputStream = new FileOutputStream(newFile,true); // 文件追加写入FileInputStream fileInputStream = null; //分片文件byte[] byt = new byte[10 * 1024 * 1024];int len;try {for (int i = 0; i < shardTotal; i++) {// 读取第i个分片fileInputStream = new FileInputStream(new File(basePath + path + "." + (i + 1))); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1while ((len = fileInputStream.read(byt)) != -1) {outputStream.write(byt, 0, len);}}} catch (IOException e) {log.error("分片合并异常", e);} finally {try {if (fileInputStream != null) {fileInputStream.close();}outputStream.close();log.info("IO流关闭");} catch (Exception e) {log.error("IO流关闭", e);}}log.info("分片结束了");//告诉java虚拟机去回收垃圾 至于什么时候回收 这个取决于 虚拟机的决定System.gc();//等待100毫秒 等待垃圾回收去 回收完垃圾Thread.sleep(100);log.info("删除分片开始");for (int i = 0; i < shardTotal; i++) {String filePath = basePath + path + "." + (i + 1);File file = new File(filePath);boolean result = file.delete();log.info("删除{},{}", filePath, result ? "成功" : "失败");}log.info("删除分片结束");}}

创建 html 页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript" src="/md5.js"></script>
<script type="text/javascript" src="/tool.js"></script>
<script type="text/javascript">//上传文件的话 得 单独出来function test1(shardIndex) {console.log(shardIndex);//永安里from表单提交var fd = new FormData();//获取表单中的filevar file=$('#inputfile').get(0).files[0];//文件分片 以20MB去分片var shardSize = 20 * 1024 * 1024;//定义分片索引var shardIndex = shardIndex;//定义分片的起始位置var start = (shardIndex-1) * shardSize;//定义分片结束的位置 file哪里来的?var end = Math.min(file.size,start + shardSize);//从文件中截取当前的分片数据var fileShard = file.slice(start,end);//分片的大小var size = file.size;//总片数var shardTotal = Math.ceil(size / shardSize);//文件的后缀名var fileName = file.name;var suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();//把视频的信息存储为一个字符串var filedetails=file.name+file.size+file.type+file.lastModifiedDate;//使用当前文件的信息用md5加密生成一个key 这个加密是根据文件的信息来加密的 如果相同的文件 加的密还是一样的var key = hex_md5(filedetails);var key10 = parseInt(key,16);//把加密的信息 转为一个64位的var key62 = Tool._10to62(key10);//前面的参数必须和controller层定义的一样fd.append('file',fileShard);fd.append('suffix',suffix);fd.append('shardIndex',shardIndex);fd.append('shardSize',shardSize);fd.append('shardTotal',shardTotal);fd.append('size',size);fd.append("key",key62)$.ajax({url:"/file/upload",type:"post",cache: false,data:fd,processData: false,contentType: false,success:function(data){//这里应该是一个递归调用if(shardIndex < shardTotal){var index=shardIndex +1;test1(index);}else{alert(data)}},error:function(){//请求出错处理}})//发送ajax请求把参数传递到后台里面}//判断这个加密文件存在不存在function check() {var file=$('#inputfile').get(0).files[0];//把视频的信息存储为一个字符串var filedetails=file.name+file.size+file.type+file.lastModifiedDate;//使用当前文件的信息用md5加密生成一个key 这个加密是根据文件的信息来加密的 如果相同的文件 加的密还是一样的var key = hex_md5(filedetails);var key10 = parseInt(key,16);//把加密的信息 转为一个64位的var key62 = Tool._10to62(key10);//检查这个key存在不存在$.ajax({url:"/file/check",type:"post",data:{'key':key62},success:function (data) {console.log(data);if(data.code==500){//这个方法必须抽离出来test1(1);}else{if(data.data.shardIndex == data.data.shardTotal){alert("极速上传成功");}else{//找到这个是第几片 去重新上传test1(parseInt(data.data.shardIndex));}}}})}</script>
<body><table border="1px solid red"><tr><td>文件1</td><td><input /></td></tr><tr><td></td><td><button onclick="check()">提交</button></td></tr></table>
</body>
</html>

启动项目就直接访问页面测试,没毛病的哦,我这个基本借鉴别人的,然后方法二是我自己写的,可以优化套入你需要的格式就行

方法二:单文件断点续传

借用方法一的一些类,我把代码写在控制层的

  /*** 临时文件是否存在,存在是否是完整的文件(是就是秒传),不是完整的就分片继续上传,不完整告诉前台从哪里开始继续传** @param key  文件的唯一标识* @param size 总文件大小* @return*/@PostMapping("checkKey")@ResponseBodypublic Result checkSingleFile(@RequestParam("key") String key, @RequestParam("size") Integer size) {//临时单文件File newFile = new File(basePath + key);if (newFile.exists()) {if (newFile.length() == (size)) {return Result.ok("秒传成功", 0);}return Result.ok("秒传成功", newFile.length());} else {return Result.fail("查询失败,可以添加");}}/*** 单个临时文件分片上传** @param file* @param fileName* @param size* @param key* @return* @throws FileNotFoundException*/@PostMapping("singleFile")@ResponseBodypublic String uploadOneFile(MultipartFile file,String fileName,Integer size,String key) throws FileNotFoundException {//临时单文件File newFile = new File(basePath + key);// 文件追加写入FileOutputStream outputStream = new FileOutputStream(newFile, true);//分片文件FileInputStream fileInputStream = null;byte[] byt = new byte[10 * 1024 * 1024];int len;try {System.err.println(file.getSize());fileInputStream = new FileInputStream(multipartFileToFile(file));while ((len = fileInputStream.read(byt)) != -1) {outputStream.write(byt, 0, len);}} catch (Exception e) {log.error("分片合并异常", e);} finally {try {if (fileInputStream != null) {fileInputStream.close();}outputStream.close();log.info("IO流关闭");} catch (Exception e) {log.error("IO流关闭", e);}}if (newFile.length() == size) {log.info("分片拼接结束结束了,修改成真是的文件名称");newFile.renameTo(new File(basePath + fileName));}//告诉java虚拟机去回收垃圾 至于什么时候回收  这个取决于 虚拟机的决定System.gc();return "success";}/*** MultipartFile 转 File** @param file* @throws Exception*/public static File multipartFileToFile(MultipartFile file) throws Exception {File toFile = null;if (file.equals("") || file.getSize() <= 0) {file = null;} else {InputStream ins = null;ins = file.getInputStream();toFile = new File(file.getOriginalFilename());inputStreamToFile(ins, toFile);ins.close();}return toFile;}/*** InputStream 转 File** @param ins* @param file*/public static void inputStreamToFile(InputStream ins, File file) {try {OutputStream os = new FileOutputStream(file);int bytesRead = 0;byte[] buffer = new byte[10 * 1024 * 1024];while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {os.write(buffer, 0, bytesRead);}os.close();ins.close();} catch (Exception e) {e.printStackTrace();}}

这个页面的代码,我写的是

 //这个单临时文件    start==================function test2(stratSize) {//永安里from表单提交var fd = new FormData();//获取表单中的filevar file = $('#inputfile').get(0).files[0];//文件分片  以20MB去分片var shardSize = 1 * 1024 * 1024;//定义分片索引var shardIndex = 1;if (stratSize != '' && stratSize != 0) {shardIndex = stratSize/shardSize;}//定义分片的起始位置var start = (shardIndex - 1) * shardSize;//定义分片结束的位置  file哪里来的?var end = Math.min(file.size, start + shardSize);//从文件中截取当前的分片数据var fileShard = file.slice(start, end);//分片的大小var size = file.size;console.log("分片开始位置:" + start + "====结束位置:"+end + "=====文件大小:" + size);//总片数var shardTotal = Math.ceil(size / shardSize);//文件名var fileName = file.name;var suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();//把视频的信息存储为一个字符串var filedetails = file.name + file.size + file.type + file.lastModifiedDate;//使用当前文件的信息用md5加密生成一个key 这个加密是根据文件的信息来加密的  如果相同的文件 加的密还是一样的var key = hex_md5(filedetails);var key10 = parseInt(key, 16);//把加密的信息 转为一个64位的var key62 = Tool._10to62(key10);//前面的参数必须和controller层定义的一样fd.append('file', fileShard);fd.append('fileName', fileName);fd.append('size', size);fd.append("key", key62);$.ajax({url: "/file/singleFile",type: "post",cache: false,data: fd,processData: false,contentType: false,success: function (data) {//这里应该是一个递归调用if (shardIndex < shardTotal) {var startSize = (shardIndex + 1) * shardSize;test2(startSize);} else {alert(data)}},error: function () {//请求出错处理}})//发送ajax请求把参数传递到后台里面}//判断这个加密文件存在不存在function check1() {var file = $('#inputfile').get(0).files[0];//把视频的信息存储为一个字符串var filedetails = file.name + file.size + file.type + file.lastModifiedDate;//使用当前文件的信息用md5加密生成一个key 这个加密是根据文件的信息来加密的  如果相同的文件 加的密还是一样的var key = hex_md5(filedetails);var key10 = parseInt(key, 16);//把加密的信息 转为一个64位的var key62 = Tool._10to62(key10);//检查这个key存在不存在$.ajax({url: "/file/checkKey",type: "post",data: {'key': key62,'size':file.size},success: function (data) {console.log(data);if (data.code == 500) {//这个方法必须抽离出来test2(0);} else {if (data.data == 0) {alert("极速上传成功");} else {test2(data.data);}}}})}// 这个单临时文件    end==================

方法二测试,你会发现秒传的那个会有点问题的,就是秒传那个只有第一次上传成功了,第二上传成功了,才会返回秒传的,我这个也没修改,应该这个也是按照现实的业务逻辑需要或不需要的,我那个是涉及到文件重命名,才会有的,看实际需要使用吧

参考:https://www.jb51.net/article/190808.htm

完整代码:https://download.csdn.net/download/qq_41134142/13712605


作者:人称江湖不留手

来源链接:

https://blog.csdn.net/qq_41134142/article/details/111307363

http://www.jmfq.cn/news/5214331.html

相关文章:

  • 哪个网站可以做服装批发/免费建立个人网站凡科
  • 做调查问卷权威网站/百度个人中心登录
  • 汕头市城市建设总公司网站/昆山网站制作哪家好
  • 上海高端品牌网站建设/中国电信视频app下载
  • 制作闹钟网站/百度安装应用
  • 阿里云的网站程序如何做/爱站工具包的主要功能
  • 扬州 网站建设/艾瑞指数
  • 遵义市住房和城乡建设局网站/百度24小时人工客服电话
  • 怎么创建公司的个人网站/百度关键词热度查询
  • 织梦茶叶网站模板免费下载/营销推广seo
  • 金华职院优质校建设网站/优化关键词怎么做
  • 杭州企业标志设计/seo职位
  • 诸城哪有做公司网站的/社区推广方法有哪些
  • 目前网站类型主要包括哪几种/班级优化大师官方免费下载
  • 网站外链如何建设最有用/活动营销方案
  • 四川建设招投标网站/求网址
  • 网站建设入门/百度官网电话
  • wordpress自定义站点/对百度竞价排名的看法
  • 鞍山做百度网站一年多少钱/网址大全qq浏览器
  • 网站上的二维码怎么做的/最新军事头条
  • 微信商城和网站建设/网址提交
  • wordpress主题上传/网站的seo方案
  • 如何在交易网站做电子印章/品牌传播推广方案
  • 网站建设经/最快的新闻发布平台
  • jsp网站开发怎么调试/seo建设
  • 竟标网站源码/seo接单平台
  • 赣州网站建设策划/搜索引擎优化的根本目的
  • 大连网站建设介绍/友情链接检测结果
  • 长春是几线城市2020排名/seo交流中心
  • 3网站建设/拉新app推广平台排名