package com.ruoyi.web.upload.service.impl;
import com.ruoyi.web.upload.dto.FileChunkDTO;
import com.ruoyi.web.upload.dto.FileChunkResultDTO;
import com.ruoyi.web.upload.service.IUploadService;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.*;
/**
* @ProjectName UploadServiceImpl
* @author Administrator
* @version 1.0.0
* @Description 附件分片上传
* @createTime 2022/4/13 0013 15:59
*/
@Service
@SuppressWarnings("all")
public class UploadServiceImpl implements IUploadService {
private Logger logger = LoggerFactory.getLogger(UploadServiceImpl.class);
@Autowired
private RedisTemplate redisTemplate;
@Value("${ruoyi.profile}")
private String uploadFolder;
/**
* 检查文件是否存在,如果存在则跳过该文件的上传,如果不存在,返回需要上传的分片集合
* 检查分片是否存在
○ 检查目录下的文件是否存在。
○ 检查redis存储的分片是否存在。
○ 判断分片数量和总分片数量是否一致。
如果文件存在并且分片上传完毕,标识已经完成附件的上传,可以进行秒传操作。
如果文件不存在或者分片为上传完毕,则返回false并返回已经上传的分片信息。
* @param chunkDTO
* @return
*/
@Override
public FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO) {
//1.检查文件是否已上传过
//1.1)检查在磁盘中是否存在
String fileFolderPath = getFileFolderPath(chunkDTO.getIdentifier());
logger.info("fileFolderPath-->{}", fileFolderPath);
String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename());
File file = new File(filePath);
boolean exists = file.exists();
//1.2)检查Redis中是否存在,并且所有分片已经上传完成。
Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
if (uploaded != null && uploaded.size() == chunkDTO.getTotalChunks() && exists) {
return new FileChunkResultDTO(true);
}
File fileFolder = new File(fileFolderPath);
if (!fileFolder.exists()) {
boolean mkdirs = fileFolder.mkdirs();
logger.info("准备工作,创建文件夹,fileFolderPath:{},mkdirs:{}", fileFolderPath, mkdirs);
}
// 断点续传,返回已上传的分片
return new FileChunkResultDTO(false, uploaded);
}
/**
* 上传分片
* 上传附件分片
○ 判断目录是否存在,如果不存在则创建目录。
○ 进行切片的拷贝,将切片拷贝到指定的目录。
○ 将该分片写入redis
* @param chunkDTO
*/
@Override
public void uploadChunk(FileChunkDTO chunkDTO) {
//分块的目录
String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier());
logger.info("分块的目录 -> {}", chunkFileFolderPath);
File chunkFileFolder = new File(chunkFileFolderPath);
if (!chunkFileFolder.exists()) {
boolean mkdirs = chunkFileFolder.mkdirs();
logger.info("创建分片文件夹:{}", mkdirs);
}
//写入分片
try (
InputStream inputStream = chunkDTO.getFile().getInputStream();
FileOutputStream outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunkDTO.getChunkNumber()))
) {
IOUtils.copy(inputStream, outputStream);
logger.info("文件标识:{},chunkNumber:{}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber());
//将该分片写入redis
long size = saveToRedis(chunkDTO);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean mergeChunk(String identifier, String fileName, Integer totalChunks) throws IOException {
return mergeChunks(identifier, fileName, totalChunks);
}
/**
* 合并分片
*
* @param identifier
* @param filename
*/
private boolean mergeChunks(String identifier, String filename, Integer totalChunks) {
String chunkFileFolderPath = getChunkFileFolderPath(identifier);
String filePath = getFilePath(identifier, filename);
// 检查分片是否都存在
if (checkChunks(chunkFileFolderPath, totalChunks)) {
File chunkFileFolder = new File(chunkFileFolderPath);
File mergeFile = new File(filePath);
File[] chunks = chunkFileFolder.listFiles();
// 切片排序1、2/3、---
List fileList = Arrays.asList(chunks);
Collections.sort(fileList, (Comparator<File>) (o1, o2) -> {
return Integer.parseInt(o1.getName()) - (Integer.parseInt(o2.getName()));
});
try {
RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
byte[] bytes = new byte[1024];
for (File chunk : chunks) {
RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r");
int len;
while ((len = randomAccessFileReader.read(bytes)) != -1) {
randomAccessFileWriter.write(bytes, 0, len);
}
randomAccessFileReader.close();
}
randomAccessFileWriter.close();
} catch (Exception e) {
return false;
}
return true;
}
return false;
}
/**
* 检查分片是否都存在
* @param chunkFileFolderPath
* @param totalChunks
* @return
*/
private boolean checkChunks(String chunkFileFolderPath, Integer totalChunks) {
try {
for (int i = 1; i <= totalChunks + 1; i++) {
File file = new File(chunkFileFolderPath + File.separator + i);
if (file.exists()) {
continue;
} else {
return false;
}
}
} catch (Exception e) {
return false;
}
return true;
}
/**
* 分片写入Redis
* 判断切片是否已存在,如果未存在,则创建基础信息,并保存。
* @param chunkDTO
*/
private synchronized long saveToRedis(FileChunkDTO chunkDTO) {
Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
if (uploaded == null) {
uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber()));
HashMap<String, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("uploaded", uploaded);
objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
// objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFilename()));
objectObjectHashMap.put("path", chunkDTO.getFilename());
redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap);
} else {
uploaded.add(chunkDTO.getChunkNumber());
redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded);
}
return uploaded.size();
}
/**
* 得到文件的绝对路径
*
* @param identifier
* @param filename
* @return
*/
private String getFilePath(String identifier, String filename) {
String ext = filename.substring(filename.lastIndexOf("."));
// retu
java+vue断点续传核心代码
需积分: 0 128 浏览量
更新于2023-08-29
收藏 11KB ZIP 举报
在IT行业中,断点续传是一项非常实用的技术,尤其在大文件传输时,它能显著提升用户体验并提高网络效率。本话题将详细讲解基于Java后端和Vue.js前端实现的断点续传的核心代码和原理。
让我们理解断点续传的基本概念。断点续传是指在文件传输过程中,如果因为网络中断或其他原因导致传输失败,用户可以在同一位置(即上次断开的“断点”)重新开始传输,而不是从头开始。这大大减少了在网络不稳定环境下的文件传输时间。
Java作为后端处理的主要职责是提供文件分块、接收上传的文件块以及组合这些块以恢复完整文件。Vue.js作为前端框架,主要用于用户交互界面的展示,控制文件的选取、分块、上传以及进度显示。
1. **Java服务端**:
- **服务初始化**:Java后端需要设置一个接口,用于处理文件的分块请求,获取文件大小并确定分块大小。
- **文件分块**:根据文件大小,将文件分割成多个小块,每个块都有自己的标识,便于后续匹配。
- **存储分块**:将接收到的每个文件块存储到服务器的临时目录,同时记录每个块的状态(是否已接收完整)。
- **组合文件**:当所有块都上传完毕,按照标识顺序将这些块组合成原始文件。
2. **Vue.js前端**:
- **文件选择**:使用HTML5的File API,让用户选择要上传的文件,并获取文件大小。
- **文件分块**:前端也需要将文件分块,这里可以采用Blob对象的slice方法,设定分块大小与后端保持一致。
- **上传管理**:维护一个状态表,跟踪每个块的上传状态,并调用API逐个上传。
- **断点记录**:在本地持久化当前的上传进度,包括已上传的块和每个块的上传状态,以便于断点续传。
- **进度反馈**:更新用户界面的进度条,显示当前的上传进度。
在压缩包中的文件可能包含以下内容:
- `result`:可能包含后端处理结果的接口定义和实现,如文件上传成功后的响应。
- `service`:Java的服务层代码,处理文件的分块、接收和组合逻辑。
- `controller`:Java的控制器层,负责处理HTTP请求,与前端进行通信。
- `js`:前端的JavaScript代码,实现Vue.js应用的逻辑,包括文件选择、分块、上传和断点续传功能。
- `dto`:数据传输对象(Data Transfer Object),在Java和Vue.js之间交换数据的模型类。
通过这些核心代码,我们可以实现一个完整的断点续传系统。在实际项目中,还需要考虑错误处理、安全性、性能优化等问题,例如文件的MD5校验以确保完整性,以及使用HTTPS保证数据传输的安全性。断点续传技术的应用,使得大文件上传在现代Web应用中变得更为流畅和可靠。
婲落ヽ紅顏誶
- 粉丝: 67
- 资源: 10
最新资源
- 西门子828D 840Dsl数控程序PLC西门子数控程序中文注释,详细解释介绍 对于维修人员,或者想学习PLC编程的工程师,初学者 西门子828D和840Dsl是西门子公司生产的数控系统,用于控
- 数字化旅社管理:客房收费系统的构建
- HTML5足球运动赛事网站模板源码.zip
- 基于IEEE33节点的配电网重构,采用最优流法开展了配电网重构工作,得到重构方案,应打开的开关数等,同时对比了重构前后的网损和电压结果 -以下内容来源于第三方解读,仅供参考 这段代码是一个用于电力
- 课程设计-基于单片机的单相电度表设计
- asdjhfjsnlkdmv
- 基于python的二手房数据分析完整源码+说明文档+分析报告+数据(高分项目)
- Matlab基于BP神经网络的气象预测,天气预测 BP神经网络具有任意复杂的模式分类能力和优良的多维函数映射能力,解决了简单感知器不能解决的异或(Exclusive OR,XOR)和一些其他问题
- bzzzhsjfsjlg;g;df''d'ffgg
- 跨平台古诗词展演:新媒体系统开发
- SpringCloudAlibaba技术栈-Dubbo
- BIOS刷新工具,笔记本BIOS工具
- 英国电站13台变压器冷却油中溶解气体分析数据数(2010-2015)
- 文化探索:深入了解各地风土人情
- 中国智慧工地行业市场研究(2023)Word(63页).docx
- 智慧建管&智慧工地PPT(33页).pptx