文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@PostMapping("/upload")
public R<String> uploadFileAndPicture(MultipartFile e) {
String originFileName = e.getOriginalFilename();
String suffix = originFileName.substring(originFileName.lastIndexOf("."));
File dir = new File(basepath + originFileName);

if(!dir.exists()){
dir.mkdirs();
}
try{
e.transferTo(new File(basepath + originFileName));

}catch(Exception ex){
log.info("{}",ex.getMessage());
}
log.info("{}", originFileName);

return R.success("发送成功");
}

文件下载 - 点击文件之后可下载

后端 - java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@GetMapping("/downloadfile")
public String downloadFile(HttpServletResponse response, HttpServletRequest request, String fileName, String extend) throws IOException {
JSONObject result = new JSONObject();
File file = new File(basepath + fileName);
log.info("{}", extend);
if (!file.exists()) {
result.put("error", "下载的文件不存在");
return result.toString();
}
response.reset();
response.setContentType(extend);
response.setCharacterEncoding("utf-8");
response.setContentLength((int) file.length());
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
byte[] readeBytes = FileUtil.readBytes(file);
OutputStream os = response.getOutputStream();
os.write(readeBytes);
result.put("success", "下载成功");
return result.toString();
}

前端 - vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// api/download 就是 上面后端download的地址
axios.get(baseUrl + "/api/downloadfile", {
params: {
fileName: item.fileName,
extend: item.extend
},
// 注意这里一定要写arraybuffer, 不然可能会出错
responseType: "arraybuffer"
})
// 先转换为 Blob类型
.then(res=>new Blob([res], {type: item.extend}))
// 在转换为 File类型
.then(blob=> new File([blob], item.fileName))
.then(file=>{
item.msg = file
let reader = new FileReader()
let fileNameTemp = item.fileName
reader.onloadend = (es)=>{
item.msg = es.target.result
item.fileName = fileNameTemp
console.log(item.msg)

if(item.chatType === 1){
srcImgList.value.push(item.msg)
}
}
reader.readAsDataURL(file)
})

断点续传

断点上传

后端

  • 上传分片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@PostMapping("/uploadslice")
// File file, Long chunkcnt,String md5
public R<Integer> uploadchunks(@RequestPart("file")MultipartFile file , @RequestPart("hash") String hash, @RequestPart("chunkcnt") String chunkcnt ,@RequestPart("filename") String filename, @RequestPart("totalCnt") String totalCnt) throws IOException, InterruptedException {
log.info("{}", filename);
String suffix = filename.substring(filename.lastIndexOf("."));
String prefix = filename.substring(0, filename.lastIndexOf(".") - 1);
log.info("{}", suffix);
log.info("{}", prefix);
File dir = new File(basepath + hash+ "\\" + prefix+ "_" + chunkcnt);
if(!dir.exists()){
dir.mkdirs();
}
file.transferTo(dir);
// uploadcache.put(hash, Integer.parseInt(chunkcnt));
if(uploadcache.containsKey(hash)) uploadcache.put(hash, uploadcache.get(hash) + 1);
else uploadcache.put(hash, 0);
log.info("当前写了: {}", chunkcnt);
if(chunkcnt.equals(totalCnt)){
//执行合并
boolean ismerge = false;
while(!ismerge){
int n = Integer.parseInt(totalCnt);
for(int i = 0; i <= n; ++i){
File fileitem = new File(basepath + hash + "\\" + prefix + "_" + i);
if(!fileitem.exists()){
log.info("还有文件没有输入结束");
break;
}
if(i == n) ismerge = true;
}
}
log.info("当前的文件为{}/{}",chunkcnt ,totalCnt);
}
return R.success(1);
}
  • 合并文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RequestMapping("/merge")
public R<Integer> fileMerge(@RequestBody Map map) throws IOException {
String hash = map.get("hash").toString();
String filename = map.get("filename").toString();
Integer totalCnt = Integer.valueOf(map.get("totalCnt").toString());
log.info("开始合并");
String suffix = filename.substring(filename.lastIndexOf("."));
String prefix = filename.substring(0, filename.lastIndexOf(".") - 1);
File mergeFile = new File(basepath + filename);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(mergeFile));
// 获取到所有的files
log.info("{}", basepath + hash);
for(int i = 0; i <= totalCnt; ++i){

File file = new File(basepath + hash + "\\" + prefix + "_" +Integer.toString(i));
log.info("{}:{}", file.length(), file.getName());
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[1024];
// 记录每次读取的字节数
int len;
// 循环读取分片文件,直到读完
while ((len = bis.read(buffer)) != -1) {
// 将读取的数据写入合并后的文件
bos.write(buffer, 0, len);
}
bis.close();
}
bos.flush();
bos.close();
log.info("合并成功");
return R.success(1);
}
  • 判断这次上传的文件要从第几个分片开始上传
1
2
3
4
5
6
7
@RequestMapping("/reupload")
public R<Integer> reUpload(@RequestBody Map map) {
String hash = map.get("result").toString();
if(!uploadcache.containsKey(hash)) return R.success(0);
// 上传下一位就好了
return R.success(uploadcache.get(hash) + 1);
}

前端

你需要先下载 md5 的包才能继续往下看, 指令为npm install spark-md5

  • 将文件的内容进行分片(我这里默认是 1024 KB)
1
2
3
4
5
6
7
8
// 这里传递的参数 files 为 File格式的对象, chunkSize 表示 分片的大小(我的chunkSize 为 1024 * 1024)
function createChunks(files, chunkSize) {
const results = []
for(let i = 0; i < files.size; i += chunkSize){
results.push(files.slice(i, i + chunkSize))
}
return results
}
  • 上面的createChunks函数返回了 数组 , 我们用一个变量接受它,
1
2
let chunks = createChunks(file, 1024 * 1024)
hash(chunks) // 这个函数再下面
  • 再将文件的内容读取转换为 md5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 这里传递的参数为 file格式的对象
function hash(chunks){
const spark = new SparkMD5
return new Promise(resovle=>{
function _read(i){
if(i >= chunks.length){
//输出一个哈希
resovle(spark.end());
return ;
}
const blob = chunks[i]
const reader = new FileReader
reader.onload = e=>{
const bytes = e.target.result
spark.append(bytes)
_read(i + 1)
}
reader.readAsArrayBuffer(blob)
}
_read(0)
})
}
  • 获取上次上传文件的进度
1
let reuploadcnt = await reUpload(reuploadobj)
  • 这里就是对每一个分片进行下载(这里是单线程下载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
for(let i = reuploadcnt; i < chunks.length; ++i){
// const loop = async(index, i, result, filesizes)=>{
if(stopupload.value[index] === true){
return
}
// let file = chunks[i]
let chunkcnt = i
let md5 = result
const formData = new FormData()
const blob = chunks[i]
formData.append('file', blob)
formData.append('hash', result)
formData.append('chunkcnt', i)
formData.append('filename', files.name)
formData.append('totalCnt', chunks.length - 1)
await axios.post('/api/uploadslice', (formData),{
onUploadProgress: function(progressEvent){
function sendHeartbeat() {
setTimeout(function() {
if (socket.readyState === WebSocket.OPEN) {
socket.send(''); // 发送空的心跳包给服务器
sendHeartbeat(); // 递归调用发送心跳包函数,以保持连接活跃
} else {
console.log('WebSocket连接已关闭');
}
}, 5000); // 每30秒发送一次心跳包给服务器
}
if( valueUploadList.value[index] + ( (progressEvent.loaded * 100) / filesizes) <= ((i + 1) * 1024 * 1024 * 100) / filesizes)
valueUploadList.value[index] += ( (progressEvent.loaded * 100) / filesizes)
else
valueUploadList.value[index] = ((i + 1) * 1024 * 1024 * 100) / filesizes
if(stopupload.value[index] === true){
return
}
}
})
if(stopupload.value[index] === true){
return
}

// }

}
  • 在所有文件上传完成之后开始合并操作
1
2
3
4
5
6
let merge = {
hash: result,
filename: files.name,
totalCnt: chunks.length - 1
}
const finish = await fileMerge(merge)

断点下载

类似于断点上传, 只不过是把过程放过来

后端

  • 判断请求的文件的大小
1
2
3
4
5
6
@RequestMapping("/getsize")
public R<Long> getFileSize(@RequestBody Map map){
String fileName = map.get("fileName").toString();
File file = new File(basepath + fileName);
return R.success(file.length());
}
  • 判断上一次 下载到哪个分片
1
2
3
4
5
6
7
8
@PostMapping("/redownload")
public R<Integer> redownload(@RequestBody Map map){
String fileName = map.get("fileName").toString();
String userid = map.get("userid").toString();
String encrypted= DigestUtils.md5DigestAsHex((basepath + fileName + userid).getBytes());
if(downloadcache.containsKey(encrypted) == false) return R.success(0);
return R.success(downloadcache.get(encrypted) + 1);
}
  • 下载分片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@RequestMapping("/downloadslicefile")
public String downloadSliceFile(HttpServletResponse response, HttpServletRequest request , String fileName, String extend, Long start, Long end, int curcnt, String userid, int totalcnt) throws IOException {
int contentLength = request.getContentLength();
log.info("{} : {}" , start, end);
JSONObject result = new JSONObject();
File file = new File(basepath + fileName);
if (!file.exists()) {
result.put("error", "下载的文件不存在");
return result.toString();
}
response.reset();
response.setCharacterEncoding("utf-8");
response.setContentLength((int) file.length());
// 请求头一定要写不让发不出来
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
response.setHeader("Accept-Range", "bytes");
String contentRange = String.valueOf(new StringBuffer("bytes ").append(start).append("-").append(end));
response.setHeader("Content-Range", contentRange);
response.setHeader("Content-Length", String.valueOf(end - start + 1));
RandomAccessFile rf = new RandomAccessFile(basepath + fileName, "r");
rf.seek(start);
OutputStream os = response.getOutputStream();
byte[] bytes = new byte[(int) (end - start + 1)];
int len = rf.read(bytes);
if(len != -1) os.write(bytes ,0, (int) (end - start + 1));
rf.close();
os.close();
result.put("success", "文件下载成功");

String encrypted= DigestUtils.md5DigestAsHex((basepath + fileName + userid).getBytes());
downloadcache.put(encrypted, curcnt);
log.info("{}: {}", curcnt, totalcnt);
if(curcnt == totalcnt){
downloadcache.remove(encrypted);
}
return result.toString();

}

前端

  • 请求文件的大小
1
let sizehigh = await getSize(getsizeobj)
  • 获取下载的分片个数
1
let downloadcnt = await redownload(redownloadobj)
  • 对每一个分片进行下载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
for(let i = startsize;i < sizehigh; i += len){
len = Math.min(sizehigh - i, 1024 * 1024)
await axios.get('/api/downloadslicefile',{
params: {
fileName: item.fileName,
extend: item.extend,
start: i,
end: i + len - 1,
curcnt: curcnt,
userid: userstore.userid,
totalcnt: totalcnt
},
headers: {
range: `bytes=${i}-${i + len - 1}`
},
responseType: "arraybuffer",
onDownloadProgress(progressEvent){
if(valueList.value[index] + (progressEvent.loaded * 100) / sizehigh > ((curcnt + 1) * 1024 * 1024) / sizehigh * 100)
valueList.value[index] = ((curcnt + 1) * 1024 * 1024) / sizehigh * 100
else
valueList.value[index] += (progressEvent.loaded * 100) / sizehigh;
console.log('当前进度条位: ', valueList.value[index] + ' 正在传送 ' + curcnt + ' ' + ((curcnt + 1) * 1024 * 1024) / sizehigh * 100);
if(stopdownload.get(index) === true){
arraymap.set(index, arrayBufferArray)
console.log(curcnt);
return
}
}
}).then(clip=>{
arrayBufferArray.push(clip)
// console.log(clip);
curcnt += 1
})
}
  • 下载完成之后 合并文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function concatArrayBuffer(arrayBufferArray) {
let totalLength = 0;
arrayBufferArray.forEach(arrayBuffer => {
totalLength += arrayBuffer.byteLength;
});
const result = new Uint8Array(totalLength);
let offset = 0;
arrayBufferArray.forEach(arrayBuffer => {
result.set(new Uint8Array(arrayBuffer), offset);
offset += arrayBuffer.byteLength;
});
return result;
}
if(i + len === sizehigh){
let res = concatArrayBuffer(arrayBufferArray)
const blob = await new Blob([res], {type: item.extend})
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.download = item.fileName
a.href = url
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
ElNotification({
type: 'success',
title: '获得了一个文件🎉',
message: '下载成功🥳'
})
arraymap.delete(index)
resetValue(index)
}
  • 如果中途停止下载, 利用一个map对象存储对应index 的 已下载的数据
1
2
3
4
if(stopdownload.get(index) === true){
arraymap.set(index, arrayBufferArray)
return
}