SpringBoot 中大文件(分片上傳)斷點(diǎn)續(xù)傳與極速秒傳功能的實(shí)現(xiàn)
1.創(chuàng)建SpringBoot項(xiàng)目
本項(xiàng)目采用springboot + mybatis-plus +jquery +thymeleaf組成
2.項(xiàng)目流程圖
3.在pom中添加以下依賴
<!--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>
4.在application.properties配置文件中
spring.resources.static-locations=classpath:/static server.port=8000 #設(shè)置上傳圖片的路徑 file.basepath=D:/BaiduNetdiskDownload/ # 設(shè)置單個(gè)文件大小 spring.servlet.multipart.max-file-size= 50MB # 設(shè)置單次請(qǐng)求文件的總大小 spring.servlet.multipart.max-request-size= 50MB ##設(shè)置要連接的mysql數(shù)據(jù)庫(kù) 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
5.在數(shù)據(jù)庫(kù)創(chuàng)建表
create table file ( id INTEGER primary key AUTO_INCREMENT comment 'id', path varchar(100) not null COMMENT '相對(duì)路徑', name varchar(100) COMMENT '文件名', suffix varchar(10) COMMENT '文件后綴', size int COMMENT '文件大小|字節(jié)B', created_at BIGINT(20) COMMENT '文件創(chuàng)建時(shí)間', updated_at bigint(20) COMMENT '文件修改時(shí)間', shard_index int comment '已上傳分片', shard_size int COMMENT '分片大小|B', shard_total int COMMENT '分片總數(shù)', file_key varchar(100) COMMENT '文件標(biāo)識(shí)' )
6.創(chuàng)建實(shí)體類
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; /** * 相對(duì)路徑 */ private String path; /** * 文件名 */ private String name; /** * 后綴 */ private String suffix; /** * 大小|字節(jié)B */ private Integer size; /** * 創(chuàng)建時(shí)間 */ private Long createdAt; /** * 修改時(shí)間 */ private Long updatedAt; /** * 已上傳分片 */ private Integer shardIndex; /** * 分片大小|B */ private Integer shardSize; /** * 分片總數(shù) */ private Integer shardTotal; /** * 文件標(biāo)識(shí) */ private String fileKey; }
7.創(chuàng)建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> { }
8.創(chuàng)建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 { @Autowired private FileMapper fileMapper; //保存文件 public void save(FileDTO file1){ //根據(jù) 數(shù)據(jù)庫(kù)的 文件標(biāo)識(shí)來(lái)查詢 當(dāng)前視頻 是否存在 LambdaQueryWrapper<FileDTO> lambda = new QueryWrapper<FileDTO>().lambda(); lambda.eq(FileDTO::getFileKey,file1.getFileKey()); List<FileDTO> fileDTOS = fileMapper.selectList(lambda); //如果存在就話就修改 if(fileDTOS.size()!=0){ //根據(jù)key來(lái)修改 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; } }
9.創(chuàng)建utils
import lombok.Data; /** * 統(tǒng)一返回值 * * @author zhangshuai * */ @Data public class Result { // 成功狀態(tài)碼 public static final int SUCCESS_CODE = 200; // 請(qǐng)求失敗狀態(tài)碼 public static final int FAIL_CODE = 500; // 查無(wú)資源狀態(tài)碼 public static final int NOTF_FOUNT_CODE = 404; // 無(wú)權(quán)訪問(wèn)狀態(tài)碼 public static final int ACCESS_DINE_CODE = 403; /** * 狀態(tài)碼 */ private int code; /** * 提示信息 */ private String msg; /** * 數(shù)據(jù)信息 */ private Object data; /** * 請(qǐng)求成功 * * @return */ public static Result ok() { Result r = new Result(); r.setCode(SUCCESS_CODE); r.setMsg("請(qǐng)求成功!"); r.setData(null); return r; } /** * 請(qǐng)求失敗 * * @return */ public static Result fail() { Result r = new Result(); r.setCode(FAIL_CODE); r.setMsg("請(qǐng)求失??!"); r.setData(null); return r; } /** * 請(qǐng)求成功,自定義信息 * * @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; } /** * 請(qǐng)求失敗,自定義信息 * * @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; } /** * 請(qǐng)求成功,自定義信息,自定義數(shù)據(jù) * * @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; } /** * 請(qǐng)求失敗,自定義信息,自定義數(shù)據(jù) * * @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; } }
10.創(chuàng)建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 { @Autowired FileService fileService; public static final String BUSINESS_NAME = "普通分片上傳"; // 設(shè)置圖片上傳路徑 @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") @ResponseBody public 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("-", ""); // 獲取文件的擴(kuò)展名 String ext = FilenameUtils.getExtension(file.getOriginalFilename()); //設(shè)置圖片新的名字 String fileName = new StringBuffer().append(key).append(".").append(suffix).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4 //這個(gè)是分片的名字 String localfileName = new StringBuffer(fileName) .append(".") .append(shardIndex) .toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1 // 以絕對(duì)路徑保存重名命后的圖片 File targeFile=new File(basePath,localfileName); //上傳這個(gè)圖片 file.transferTo(targeFile); //數(shù)據(jù)庫(kù)持久化這個(gè)數(shù)據(jù) 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); //插入到數(shù)據(jù)庫(kù)中 //保存的時(shí)候 去處理一下 這個(gè)邏輯 fileService.save(file1); //判斷當(dāng)前是不是最后一個(gè)分頁(yè) 如果不是就繼續(xù)等待其他分頁(yè) 合并分頁(yè) if(shardIndex .equals(shardTotal) ){ file1.setPath(basePath+fileName); this.merge(file1); } return "上傳成功"; } @RequestMapping("/check") @ResponseBody public Result check(String key){ List<FileDTO> check = fileService.check(key); //如果這個(gè)key存在的話 那么就獲取上一個(gè)分片去繼續(xù)上傳 if(check.size()!=0){ return Result.ok("查詢成功",check.get(0)); } return Result.fail("查詢失敗,可以添加"); } /** * @author fengxinglie * 合并分頁(yè) */ private void merge(FileDTO fileDTO) throws FileNotFoundException, InterruptedException { //合并分片開始 log.info("分片合并開始"); String path = fileDTO.getPath(); //獲取到的路徑 沒(méi)有.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個(gè)分片 fileInputStream = new FileInputStream(new File(basePath + path + "." + (i + 1))); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1 while ((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流關(guān)閉"); } catch (Exception e) { log.error("IO流關(guān)閉", e); } } log.info("分片結(jié)束了"); //告訴java虛擬機(jī)去回收垃圾 至于什么時(shí)候回收 這個(gè)取決于 虛擬機(jī)的決定 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("刪除分片結(jié)束"); } }
11.創(chuàng)建html頁(yè)面
<!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"> //上傳文件的話 得 單獨(dú)出來(lái) function test1(shardIndex) { console.log(shardIndex); //永安里from表單提交 var fd = new FormData(); //獲取表單中的file var file=$('#inputfile').get(0).files[0]; //文件分片 以20MB去分片 var shardSize = 20 * 1024 * 1024; //定義分片索引 var shardIndex = shardIndex; //定義分片的起始位置 var start = (shardIndex-1) * shardSize; //定義分片結(jié)束的位置 file哪里來(lái)的? var end = Math.min(file.size,start + shardSize); //從文件中截取當(dāng)前的分片數(shù)據(jù) var fileShard = file.slice(start,end); //分片的大小 var size = file.size; //總片數(shù) var shardTotal = Math.ceil(size / shardSize); //文件的后綴名 var fileName = file.name; var suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase(); //把視頻的信息存儲(chǔ)為一個(gè)字符串 var filedetails=file.name+file.size+file.type+file.lastModifiedDate; //使用當(dāng)前文件的信息用md5加密生成一個(gè)key 這個(gè)加密是根據(jù)文件的信息來(lái)加密的 如果相同的文件 加的密還是一樣的 var key = hex_md5(filedetails); var key10 = parseInt(key,16); //把加密的信息 轉(zhuǎn)為一個(gè)64位的 var key62 = Tool._10to62(key10); //前面的參數(shù)必須和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){ //這里應(yīng)該是一個(gè)遞歸調(diào)用 if(shardIndex < shardTotal){ var index=shardIndex +1; test1(index); }else { alert(data) } }, error:function(){ //請(qǐng)求出錯(cuò)處理 } }) //發(fā)送ajax請(qǐng)求把參數(shù)傳遞到后臺(tái)里面 } //判斷這個(gè)加密文件存在不存在 function check() { var file=$('#inputfile').get(0).files[0]; //把視頻的信息存儲(chǔ)為一個(gè)字符串 var filedetails=file.name+file.size+file.type+file.lastModifiedDate; //使用當(dāng)前文件的信息用md5加密生成一個(gè)key 這個(gè)加密是根據(jù)文件的信息來(lái)加密的 如果相同的文件 加的密還是一樣的 var key = hex_md5(filedetails); var key10 = parseInt(key,16); //把加密的信息 轉(zhuǎn)為一個(gè)64位的 var key62 = Tool._10to62(key10); //檢查這個(gè)key存在不存在 $.ajax({ url:"/file/check", type:"post", data:{'key':key62}, success:function (data) { console.log(data); if(data.code==500){ //這個(gè)方法必須抽離出來(lái) test1(1); } else { if(data.data.shardIndex == data.data.shardTotal) { alert("極速上傳成功"); }else { //找到這個(gè)是第幾片 去重新上傳 test1(parseInt(data.data.shardIndex)); } } } }) } </script> <body> <table border="1px solid red"> <tr> <td>文件1</td> <td> <input name="file" type="file" id="inputfile"/> </td> </tr> <tr> <td></td> <td> <button onclick="check()">提交</button> </td> </tr> </table> </body> </html>
12.測(cè)試
12.2:查看數(shù)據(jù)庫(kù)
12.3 :已經(jīng)上傳成功 重新上傳一次
12.4:當(dāng)我上傳一半的時(shí)候 (顯示上傳了一個(gè)文件 然后我們繼續(xù)上傳)
12.5:繼續(xù)上傳 查看目錄 發(fā)現(xiàn)已經(jīng)成功了
13.注意事項(xiàng)
13.1:程序中并沒(méi)有去判斷 你存在不存在 上傳圖片的地址 所以如果打算使用代碼 請(qǐng)創(chuàng)建 上傳圖片的目錄 D:/BaiduNetdiskDownload/
13.2 :使用代碼前請(qǐng)修改配置文件中的 數(shù)據(jù)庫(kù)連接
14.代碼 直達(dá)地址
到此這篇關(guān)于SpringBoot 中大文件(分片上傳)斷點(diǎn)續(xù)傳與極速秒傳功能的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)springboot大文件斷點(diǎn)續(xù)傳秒傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ IDEA 部署 Web 項(xiàng)目,看這一篇夠了!
這篇文章主要介紹了IntelliJ IDEA 部署 Web 項(xiàng)目的圖文教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05Java中類轉(zhuǎn)json的基類實(shí)現(xiàn)
這篇文章主要介紹了Java中類轉(zhuǎn)json的基類實(shí)現(xiàn),需要的朋友可以參考下2021-01-01SpringCloud學(xué)習(xí)筆記之SpringCloud搭建父工程的過(guò)程圖解
SpringCloud是分布式微服務(wù)架構(gòu)的一站式解決方案,十多種微服務(wù)架構(gòu)落地技術(shù)的集合體,俗稱微服務(wù)全家桶,這篇文章主要介紹了SpringCloud學(xué)習(xí)筆記(一)搭建父工程,需要的朋友可以參考下2021-10-10Netty分布式高性能工具類FastThreadLocal和Recycler分析
這篇文章主要為大家介紹了Netty分布式高性能工具類FastThreadLocal和Recycler分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03springcloud?gateway無(wú)法路由問(wèn)題的解決
gateway網(wǎng)關(guān)的重要作用之一便是進(jìn)行路由轉(zhuǎn)發(fā)工作,下面這篇文章主要給大家介紹了關(guān)于springcloud?gateway無(wú)法路由問(wèn)題的解決方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05淺談spring中isolation和propagation的用法
這篇文章主要介紹了淺談spring中isolation 和propagation的用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07IDEA創(chuàng)建springboot依賴下載很慢的解決方法
maven會(huì)使用遠(yuǎn)程倉(cāng)庫(kù)來(lái)加載依賴,是一個(gè)國(guó)外的網(wǎng)站,所以會(huì)很慢,本文主要介紹了IDEA創(chuàng)建springboot依賴下載很慢的解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12一個(gè)簡(jiǎn)單的Java文件讀取的進(jìn)度條
這篇文章主要介紹了一個(gè)簡(jiǎn)單的Java文件讀取的進(jìn)度條,寫一個(gè)可以使用的?demo,涉及到了文件的相對(duì)路徑問(wèn)題,需要的朋友可以參考下2023-04-04