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ù)庫 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ù)庫創(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ù)庫的 文件標(biāo)識(shí)來查詢 當(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來修改
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;
// 查無資源狀態(tài)碼
public static final int NOTF_FOUNT_CODE = 404;
// 無權(quá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ù)庫持久化這個(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ù)庫中
//保存的時(shí)候 去處理一下 這個(gè)邏輯
fileService.save(file1);
//判斷當(dāng)前是不是最后一個(gè)分頁 如果不是就繼續(xù)等待其他分頁 合并分頁
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
* 合并分頁
*/
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個(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頁面
<!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ú)出來
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哪里來的?
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ù)文件的信息來加密的 如果相同的文件 加的密還是一樣的
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ù)文件的信息來加密的 如果相同的文件 加的密還是一樣的
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è)方法必須抽離出來
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.測試

12.2:查看數(shù)據(jù)庫

12.3 :已經(jīng)上傳成功 重新上傳一次

12.4:當(dāng)我上傳一半的時(shí)候 (顯示上傳了一個(gè)文件 然后我們繼續(xù)上傳)

12.5:繼續(xù)上傳 查看目錄 發(fā)現(xiàn)已經(jīng)成功了


13.注意事項(xiàng)
13.1:程序中并沒有去判斷 你存在不存在 上傳圖片的地址 所以如果打算使用代碼 請(qǐng)創(chuàng)建 上傳圖片的目錄 D:/BaiduNetdiskDownload/
13.2 :使用代碼前請(qǐng)修改配置文件中的 數(shù)據(jù)庫連接
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)目的圖文教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Java中類轉(zhuǎn)json的基類實(shí)現(xiàn)
這篇文章主要介紹了Java中類轉(zhuǎn)json的基類實(shí)現(xiàn),需要的朋友可以參考下2021-01-01
SpringCloud學(xué)習(xí)筆記之SpringCloud搭建父工程的過程圖解
SpringCloud是分布式微服務(wù)架構(gòu)的一站式解決方案,十多種微服務(wù)架構(gòu)落地技術(shù)的集合體,俗稱微服務(wù)全家桶,這篇文章主要介紹了SpringCloud學(xué)習(xí)筆記(一)搭建父工程,需要的朋友可以參考下2021-10-10
Netty分布式高性能工具類FastThreadLocal和Recycler分析
這篇文章主要為大家介紹了Netty分布式高性能工具類FastThreadLocal和Recycler分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
淺談spring中isolation和propagation的用法
這篇文章主要介紹了淺談spring中isolation 和propagation的用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
IDEA創(chuàng)建springboot依賴下載很慢的解決方法
maven會(huì)使用遠(yuǎn)程倉庫來加載依賴,是一個(gè)國外的網(wǎng)站,所以會(huì)很慢,本文主要介紹了IDEA創(chuàng)建springboot依賴下載很慢的解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12

