由于java天生缺陷 但是项目需要上传大文件,所以做了一个分片上传,话不多说.............................
UploadFileUtil.java 工具包 utils层
/**
* 分片上传与断点续传
*
* @param multipartFileParam 分片实体
* @param targetPath 目标路径
* @return 待定
*/
public ApiResult uploadAppendFile(MultipartFileParam multipartFileParam, String targetPath) {
String[] bin=multipartFileParam.getFileName ().split ("\\.");
Map<String, String> map = new HashMap<>();
if(!bin[1].equals ("bin")){
map.put("result", "文件格式错误");
log.error ("文件格式不对,请检查升级文件");
return ResultUtil.error (map);
}
long chunk = multipartFileParam.getChunkNumber();
long totalChunks = multipartFileParam.getTotalChunks();
long fileSize = multipartFileParam.getFileSize();
String taskId = multipartFileParam.getTaskId();
MultipartFile file = multipartFileParam.getFile();
String fileName = multipartFileParam.getFileName();
// String extName = FileUtil.extName(fileName);
// String separator = FileUtil.FILE_SEPARATOR;
String localPath = targetPath + separator;
File tempFile = null;
RandomAccessFile raf = null;
InputStream is = null;
try {
if (chunk == 1) {
String tempFileName = taskId + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
File fileDir = new File(localPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
tempFile = new File(localPath, tempFileName);
if (!tempFile.exists()) {
tempFile.createNewFile();
}
raf = new RandomAccessFile(tempFile, "rw");
is = file.getInputStream();
raf.seek(0);
int len = 0;
byte[] bytes = new byte[1024 * 10];
while ((len = is.read(bytes)) != -1) {
raf.write(bytes, 0, len);
}
raf.close();
is.close();
redisUtil.setObject(UpLoadConstant.chunkNum + taskId, chunk, cacheTime);
redisUtil.setObject(UpLoadConstant.fastDfsPath + taskId, tempFile.getPath(), cacheTime);
map.put("result", "上传成功");
} else {
String path = (String) redisUtil.getObject(UpLoadConstant.fastDfsPath + taskId);
is = file.getInputStream();
raf = new RandomAccessFile(path, "rw");
raf.seek(fileSize);
int len = 0;
byte[] bytes = new byte[1024 * 10];
while ((len = is.read(bytes)) != -1) {
raf.write(bytes, 0, len);
}
redisUtil.setObject(UpLoadConstant.chunkNum + taskId, chunk, cacheTime);
raf.close();
is.close();
}
String md5 = (String) redisUtil.getObject(UpLoadConstant.task + taskId);
HashMap<String, String> redisMap = new HashMap<>();
redisMap.put("fileSize", fileSize + "");
redisMap.put("taskId", taskId);
redisUtil.setHashAsMap(UpLoadConstant.fileMd5 + md5, redisMap, cacheTime);
if (chunk == totalChunks) {
String path = (String) redisUtil.getObject(UpLoadConstant.fastDfsPath + taskId);
// FileUtil.rename(new File(path), fileName, true);//改文件名称 原名
FileUtil.rename(new File(path), "upgrade.bin", true);//指定名称 upgrade.bin
map.put("result", "上传完毕");
redisUtil.del(UpLoadConstant.fileMd5 + md5);
redisUtil.del(UpLoadConstant.task + taskId);
redisUtil.del(UpLoadConstant.chunkNum + taskId);
redisUtil.del(UpLoadConstant.fastDfsPath + taskId);
}
} catch (IOException e) {
e.printStackTrace();
String md5 = (String) redisUtil.getObject(UpLoadConstant.task + taskId);
redisUtil.del(UpLoadConstant.fileMd5 + md5);
redisUtil.del(UpLoadConstant.task + taskId);
redisUtil.del(UpLoadConstant.chunkNum + taskId);
redisUtil.del(UpLoadConstant.fastDfsPath + taskId);
map.put("result", "上传异常");
} finally {
try {
if (raf != null) {
raf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return ResultUtil.success(map);
}
/**
* 校验md5值
*
* @param md5 md5
* @return map
*/
public Map<String, Object> checkMd5(String md5) {
Map<String, Object> map = new HashMap<>();
String fileSize = "";
String taskId = "";
md5 = SecureUtil.md5(md5);
Map redisMap = redisUtil.getMap(UpLoadConstant.fileMd5 + md5);
if (MapUtil.isNotEmpty(redisMap)) {
fileSize = ( redisMap.get("fileSize").toString ());
taskId = ( redisMap.get("taskId").toString ());
}
if (StrUtil.isNotEmpty(fileSize)) {
map.put("fileSize", Long.parseLong(fileSize));
} else {
Map<String, Object> map1 = new HashMap<>();
taskId = IdUtil.simpleUUID();
map1.put("fileSize", 0);
map1.put("taskId", taskId);
redisUtil.setHashAsMap(UpLoadConstant.fileMd5 + md5, map1, cacheTime);
redisUtil.setObject(UpLoadConstant.task + taskId, md5, cacheTime);
map.put("fileSize", 0);
}
map.put("taskId", taskId);
return map;
}
RedisUtil.java 同为utils层
package vip.xiaonuo.sys.modular.upgrade.utils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Author: geng
* @Date: 2022/9/23 21:49
* @Description:
*/
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
//写入对象
public boolean setObject(final String key, Object value, Integer expireTime) {
try {
redisTemplate.opsForValue().set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//获取对象
public Object getObject(final String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
//写入集合
public boolean setList(final String key, Object value, Integer expireTime) {
try {
redisTemplate.opsForList().rightPush(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//获取集合
public List<Object> getList(final String key) {
try {
return redisTemplate.opsForList().range(key, 0, -1);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public boolean setHashAsKV(String key, Object hk, Object hv, Integer expireTime) {
try {
redisTemplate.opsForHash().put(key, hk, hv);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public boolean setHashAsMap(String key, Map map, Integer expireTime) {
try {
redisTemplate.opsForHash().putAll(key, map);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public Map getMap(String key) {
Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
if (CollectionUtils.isEmpty(map)) {
return null;
}
return map;
}
public Object getHashObject(String k1, String k2) {
return redisTemplate.opsForHash().get(k1, k2);
}
/**
* 判断是否存在key
*
* @param key key
* @return
*/
public boolean hasKey(final String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 删除key
*
* @param key key
* @return
*/
public void del(final String key) {
if (hasKey(key)) {
redisTemplate.delete(key);
}
}
/**
* 批量删除key
*
* @param keys keys
* @return
*/
public boolean delKeys(String... keys) {
for (String key : keys) {
if (hasKey(key)) {
Boolean flag = redisTemplate.delete(key);
if (flag == null) {
continue;
}
if (!flag) {
return false;
}
}
}
return true;
}
}
entity层
package vip.xiaonuo.sys.modular.upgrade.entity;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.io.Serializable;
@Data
public class MultipartFileParam implements Serializable {
private static final long serialVersionUID = 3238600879053243080L;
private String taskId;//文件传输任务ID
private long chunkNumber;//当前为第几分片
private long chunkSize;//每个分块的大小
private long totalChunks;//分片总数
private long fileSize;
private String fileName;
private String identifier;//文件唯一标识
private MultipartFile file;//分块文件传输对象
}
UpLoadConstant.java constant层
package vip.xiaonuo.sys.modular.upgrade.constant;
/**
* @Author: geng
* @Date: 2022/9/23 22:00
* @Description:
*/
public class UpLoadConstant {
private final static String uploading = "Uploading:";
private final static String file = uploading + "file:";
//当前文件传输到第几块
public final static String chunkNum = file + "chunkNum:";
//当前文件上传到fastdfs路径
public final static String fastDfsPath = file + "fastDfsPath:";
public final static String task = uploading + "task:";
public final static String fileMd5 = file + "md5:";
}
param层
package vip.xiaonuo.sys.modular.upgrade.param;
/**
* @Author: geng
* @Date: 2022/9/23 22:27
* @Description:
*/
public class ApiResult {
/**
* 错误码.
*/
private Integer code;
/**
* 提示信息.
*/
private String msg;
/**
* 具体的内容.
*/
private Object data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
package vip.xiaonuo.sys.modular.upgrade.param;
public enum CustomResponse {
SUCCESS(10000, "响应成功"),
FAILURE(10001, "响应失败");
private Integer code;
private String msg;
CustomResponse(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
package vip.xiaonuo.sys.modular.upgrade.param;
/**
* @Author: geng
* @Date: 2022/9/23 22:28
* @Description:
*/
public class ResultUtil {
public static ApiResult success(Object object) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(CustomResponse.SUCCESS.getCode());
apiResult.setMsg(CustomResponse.SUCCESS.getMsg());
apiResult.setData(object);
return apiResult;
}
public static ApiResult success() {
ApiResult apiResult = new ApiResult();
apiResult.setCode(CustomResponse.SUCCESS.getCode());
apiResult.setMsg(CustomResponse.SUCCESS.getMsg());
return apiResult;
}
public static ApiResult success(Integer code, String msg, Object obj) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(code);
apiResult.setMsg(msg);
apiResult.setData(obj);
return apiResult;
}
public static ApiResult error(Object object) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(CustomResponse.FAILURE.getCode());
apiResult.setMsg(CustomResponse.FAILURE.getMsg());
apiResult.setData(object);
return apiResult;
}
public static ApiResult errMsg(String msg) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(CustomResponse.FAILURE.getCode());
apiResult.setMsg(msg);
return apiResult;
}
public static ApiResult error() {
ApiResult apiResult = new ApiResult();
apiResult.setCode(CustomResponse.FAILURE.getCode());
apiResult.setMsg(CustomResponse.FAILURE.getMsg());
return apiResult;
}
public static ApiResult error(Integer code, String msg, Object obj) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(code);
apiResult.setMsg(msg);
apiResult.setData(obj);
return apiResult;
}
}
controller层
@GetMapping("/checkMd5")
public ApiResult checkMd5(@Param ("md5") String md5) {
Map<String, Object> map = uploadFileUtil.checkMd5(md5);
return ResultUtil.success(map);
}
@PostMapping(value = "/chunkUpload")
public ApiResult chunkUpload(MultipartFileParam multipartFileParam) {
return uploadFileUtil.uploadAppendFile(multipartFileParam, "D:\\klp");
}
前端代码
chunkUpload() {
const file = this.fileList[0]
const md5 = file.name + file.size + file.lastModified
chunkUploaMess(md5).then((res)=>{
if (res) {
console.log(res)
const start = Number(res.data.fileSize)
const taskId = res.data.taskId
if (res.data) {
this.upload(start, taskId, file)
} else {
this.upload(0, taskId, file)
}
})
}
upload(start, taskId, file) {
// 分片大小 5M
const bytePercent = 1024 * 1024 * 5
// 通过文件大小除以分片大小得出总片数
const totalChunks = Math.ceil(file.size / bytePercent)
// 起始位置+分片数 如果大于文件大小,那么终点位置就是文件大小,反之就是前者
const end = (start + bytePercent) > file.size ? file.size : (start + bytePercent)
const fileName = file.name
// 分片文件
const chunkFile = file.slice(start, end)
// 当前分片数
const currChunkNum =parseInt (start / bytePercent) + 1
const formData = new FormData()
formData.append('file', chunkFile)
formData.append('fileName', fileName)
formData.append('fileSize', start)
formData.append('taskId', taskId)
formData.append('chunkNumber', currChunkNum)
formData.append('chunkSize', bytePercent)
formData.append('totalChunks', totalChunks)
uploadsMess(formData).then((res)=>{
if (res.data.result === '上传完毕') {
alert('成功')
} else {
this.upload(end, taskId, file)
}
})
},
api
/**
* 分片
* @returns {AxiosPromise}
* @param md5
*/
export function chunkUpload(md5) {
return axios({
url: '/main/checkMd5',
method: 'get',
data: md5,
})
}
/**
* 分片
* @returns {AxiosPromise}
* @param formData
*/
export function uploads(formData) {
return axios({
url: '/main/chunkUpload',
method: 'POST',
data: formData,
contentType: false,//很重要,指定为false才能形成正确的Content-Type
processData: false, //加入这属性 processData默认为true,为true时,提交不会序列化data。
})
}