HTML页面
这里只是显示页面大概内容,就不把CSS贴出来了
<div class="file-manager"><div class="hr-line-dashed"></div><label title="上传图片" for="inputFile" class="btn btn-primary"><input type="file" accept=".mp3,.mp4,.rar,.7z,.iso,.zip" name="file" id="inputFile" class="hide"> 选择文件</label><div><input type="text" disabled="" id="fileText" class="form-control"></div><div>保存路径(不填为默认路径):<input type="text" name="saveFilePath" placeholder="C:/Download(注意反斜杠)" value="E:/Download" id="saveFilePath" class="form-control"></div><button onclick="upload()" class="btn btn-primary btn-block">上传文件(直接合并方式)</button><div><span>上传进度</span><small class="pull-right" id="upFilePer">未上传文件 </small></div><div class="progress progress-small"><div style="width: 0%;" class="progress-bar" id="upFilePerImg"></div></div><div class="hr-line-dashed"></div></div>
<script src="js/jquery.min.js?v=2.1.4"></script>
<script src="js/plugins/layer/layer.min.js"></script>
<script src="js/request.js"></script>
效果图
JS
$(document).ready(function () {$('input[type="file"]').change(function() {var fileName = $(this).prop('files')[0].name;$("#upFilePer").text("0%");$("#upFilePerImg").attr("style", "width: 0%");$("#fileText").val(fileName);});});async function upload(){var fileList = $("#inputFile")[0].files;if(fileList && fileList.length > 0) {const chunkSize = 5242880;const chunkMinSize = 10485760;let chunkFlag = false;for(let i = 0; i < fileList.length; i++) {if(fileList[i].size > chunkMinSize) {chunkFlag = true;}const chunkNum = Math.ceil(fileList[i].size / chunkSize)let fileMd5 = ''if (chunkNum >= 2) {let startMd5 = await getMD5(fileList[i].slice(0, 1 * chunkSize))let endMd5 = await getMD5(fileList[i].slice((chunkNum-1) * chunkSize, chunkNum * chunkSize))fileMd5 = startMd5 + endMd5;} else {fileMd5 = await getMD5(fileList[i])}const res = await checkFileExist(fileMd5);const { code, data, msg } = res;if(code === 21000) {layer.msg('文件秒传成功')console.log('文件在服务器已存在,文件上传成功(大文件秒传原理就是不传)')continue;} else {if(chunkFlag) {var loadIndex = layer.load("文件上传中。。。");let start = new Date()for(let currentChunk = 0; currentChunk < chunkNum; currentChunk++) {let formData = new FormData();formData.append("saveFilePath", $("#saveFilePath").val());formData.append("chunkFlag", chunkFlag);formData.append("chunks", chunkNum);formData.append("currentChunk", currentChunk);formData.append("chunkSize", chunkSize);formData.append('type', fileList[i].type)formData.append("size", fileList[i].size);formData.append("name", fileList[i].name);formData.append("fileMd5", fileMd5);let currentChunkMd5 = await getMD5(fileList[i].slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize));formData.append("currentChunkMd5", currentChunkMd5);formData.append("file", fileList[i].slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize));let res = await uploadFile(formData);if(res && res.code == 20000){var cc = parseFloat(currentChunk) + 1;var cn = parseFloat(chunkNum);var num = (cc/cn) * 100;num = parseFloat(num).toFixed(2);$("#upFilePer").text(num + "%");$("#upFilePerImg").attr("style", "width: " + num + "%");}else{success = false;layer.msg(res.message);break;}}let end = new Date()layer.close(loadIndex);setTimeout(() => {layer.msg(fileList[i].name + "上传完成,耗时:" + (end - start));},200)} else {let formData = new FormData();formData.append("saveFilePath", $("#saveFilePath").val());formData.append("chunkFlag", chunkFlag);formData.append('type', fileList[i].type)formData.append("size", fileList[i].size);formData.append("name", fileList[i].name);formData.append("fileMd5", fileMd5);formData.append("file", fileList[i]);let res = await uploadFile(formData);if(res && res.code == 20000){$("#upFilePer").text("100%");$("#upFilePerImg").attr("style", "width: 100%");layer.msg(fileList[i].name + "上传完成!" );}else{layer.msg(res.message);}}}}} else {console.log('请先选择文件')layer.msg('请先选择文件');}}function getMD5(file, fileListID){return new Promise((resove, reject) => {const spark = new SparkMD5.ArrayBuffer()const fileReader = new FileReader()fileReader.onload = (e) => {spark.append(e.target.result)const _md5 = spark.end()resove(_md5)}fileReader.readAsArrayBuffer(file)})}async function checkFileExist(fileMd5Id){return await getUrlAsync("admin/checkFileExist", {fileMd5Id: fileMd5Id}) ;}
function getUrlAsync(strUrl, dataParams) {return new Promise((resove, reject) => {$.ajax({url: strUrl,data: dataParams,type: "GET",dataType: 'json',success: function (result) {if (result.code === 20000) {return resove(result);} else if (result.code === 20002) {layer.msg(result.message);setTimeout(function () {window.top.location.href = "index.html";}, 1500);} else {layer.alert(result.message);}},error: function (XMLHttpRequest, textStatus, errorThrown) {layer.msg("连接超时:" + XMLHttpRequest.status + ","+ XMLHttpRequest.readyState + "," + textStatus);},complete: function (XMLHttpRequest, textStatus) {this; }});})
}function uploadFile(formdata) {return new Promise((resove, reject) => {$.ajax({url:"admin/uploadFile",type:'post',contentType:false,processData:false,data:formdata,success: function (result) {if (result.code === 20000) {resove(result);} else if (result.code === 20002) {layer.msg(result.message);setTimeout(function () {window.top.location.href = "index.html";}, 500);} else {reject(result);}},error: function (XMLHttpRequest, textStatus, errorThrown) {layer.msg("连接超时:" + XMLHttpRequest.status + ","+ XMLHttpRequest.readyState + "," + textStatus);reject();}})})
}
JAVA后台
adminCotroller
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/admin")
public class AdminCotroller {private static final Logger logger = LoggerFactory.getLogger(AdminCotroller.class);@Autowiredprivate FileUploadService fileUploadService;@PostMapping("/uploadFile")@ResponseBodypublic R uploadFile(FileUploadDTO dto){if (ObjectUtils.isEmpty(dto)) {return R.error("参数错误,文件上传失败");}if (dto.getChunkFlag()) {return fileUploadService.chunkFileUpload(dto);} else {return fileUploadService.singleFileUpload(dto);}}}
FileUploadDTO.java
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class FileUploadDTO {@NotNull(message = "是否分片不能为空")private Boolean chunkFlag;@NotNull(message = "文件不能为空")private MultipartFile file;@NotBlank(message = "文件名不能为空")private String name;private Long size;@NotBlank(message = "文件md5不能为空")private String fileMd5;private String type;private Integer currentChunk;private Long chunkSize;private Integer chunks;private String currentChunkMd5;private String saveFilePath;
}
R.java
import java.io.Serializable;
public class R implements Serializable {private static final long serialVersionUID = -4301232631736358183L;private boolean status;private int code;private Object data;private String message;public static R success() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE, ServerConstant.MESSAGE_SUCCESS);}public static R successMsg() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE, ServerConstant.ERROR_SAVE_SUCCESS);}public static R success(Object data) {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE, data, ServerConstant.MESSAGE_SUCCESS);}public static R success(String message) {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_SUCCESS_CODE,message);}public static R error(int code, String errorMessage) {return new R(ServerConstant.IS_SUCCESS, code, errorMessage);}public static R error() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_SAVE_FILE);}public static R error(String message) {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, message);}public static R errorException() {return new R(ServerConstant.IS_ERROR, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_EXCEPTION);}public static R errorException(String message) {return new R(ServerConstant.IS_ERROR, message);}public static R errorDataFormat() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_DATA_FORMAT);}public static R errorRequestsTooFrequently() {return new R(ServerConstant.IS_SUCCESS, ServerConstant.MESSAGE_ERROR_CODE, ServerConstant.ERROR_REQUESTS_TOO_FREQUENTLY);}public R() {}public R(boolean status, int code, Object data, String message) {this.status = status;this.code = code;this.data = data;this.message = message;}public R(boolean status, int code, String message) {this.status = status;this.code = code;this.message = message;}public R(boolean status, String message) {this.status = status;this.code = ServerConstant.MESSAGE_SUCCESS_CODE;this.message = message;}public boolean isStatus() {return status;}public void setStatus(boolean status) {this.status = status;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "R{" +"code=" + code +", data=" + data +", message='" + message + '\'' +'}';}
}
ServerConstant.java
import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;public interface ServerConstant {public static final boolean IS_SUCCESS = true;public static final boolean IS_ERROR = false;public static final Integer IS_SHOW = 1;public static final Integer IS_NOT_SHOW = 0;public static final Integer IS_NOT_DELETE = 0;public static final Integer IS_DELETE = 1;public static final Integer SIZE = 35;public static final Integer CURRENT = 1;public static final long COMPARE = 10 * 60 * 60 * 60;public static final int MESSAGE_SUCCESS_CODE = 20000;public static final String MESSAGE_SUCCESS = "ok";public static final int MESSAGE_ERROR_CODE = 20001;public static final int MESSAGE_LOG_FILE_CODE = 20002;public static final int FILE_EXSIT = 21000;public static final int FILE_GET_HASH = 220000;public static final String FILE_GET_HASH_ING = "FILE_GET_HASH_ING";public static final String FILE_GET_HASH_FINISH = "FILE_GET_HASH_FINISH";public static final String ERROR_SAVE_SUCCESS = "操作成功!";public static final String ERROR_SAVE_FILE = "操作失败!";public static final String ERROR_DATA_FORMAT = "数据格式错误!";public static final String ERROR_REQUESTS_TOO_FREQUENTLY = "请稍后再试!";public static final String ERROR_EXCEPTION = "请重试!";public static final String COMMUNICATION_TOKEN = "communicationToken";public static final String OPEN = "open";public static final String CLOSE = "close";public static final int FILE_MD5_CHECK_EXCEPTION = 23000;public static final String MD5_CHECK_EXCEPTION = "MD5校验异常";public static final String FILE_EXSIT_MSG = "文件已存在!";public static final int FILE_UPLOAD_COMPLETED = 21001;public static final int FILE_UPLOAD_UPLOADING = 21002;public static final int FILE_UPLOAD_FAILED = 21003;public static final int FILE_NOT_FIND = 21004;public static final String WX_ACCESS_TOKEN = "WX_ACCESS_TOKEN";public static Cache<String, FILE_STATE> fileStateCahe = CacheUtil.newFIFOCache(100);}
FileUploadService.java
package com.xyhz.backup.server.service;import com.xyhz.backup.server.entity.VO.FileUploadDTO;
import com.xyhz.backup.server.entity.VO.R;
public interface FileUploadService {R chunkFileUpload(FileUploadDTO dto);R singleFileUpload(FileUploadDTO dto);}
FileUploadServiceImpl.java
@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {@Autowiredprivate FileUploadUtil fileUploadUtil;@Autowiredprivate SysFileMapper sysFileMapper;@Autowiredprivate SysChunkRecordMapper sysChunkRecordMapper;@Overridepublic R chunkFileUpload(FileUploadDTO dto) {UploadResultVo vo = new UploadResultVo();try {MultipartFile multipartFile = dto.getFile();String filePath = fileUploadUtil.getFilePath(dto.getSaveFilePath());File file = new File(filePath, dto.getName());RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");if (randomAccessFile.length() == 0L) {randomAccessFile.setLength(dto.getSize());}long pos = dto.getCurrentChunk() * dto.getChunkSize();FileChannel channel = randomAccessFile.getChannel();MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, pos, multipartFile.getSize());map.put(multipartFile.getBytes());cleanBuffer(map);channel.close();randomAccessFile.close();String chunkKid = saveSysChunkRecord(file, dto);vo.setChunkKid(chunkKid).setUploaded(Boolean.TRUE);if (dto.getCurrentChunk() == dto.getChunks() - 1) {LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysChunkRecord::getFileMd5, dto.getFileMd5());Integer integer = sysChunkRecordMapper.selectCount(wrapper);int flag = 0;while (!integer.equals(dto.getChunks()) && flag < 10) {integer = sysChunkRecordMapper.selectCount(wrapper);flag++;}if(integer.equals(dto.getChunks())) {SysFile fileInfo = buildSysFile(dto, file);int insert = sysFileMapper.insert(fileInfo);if (insert == 1) {cleanChunkData(dto.getFileMd5());}vo.setFileKid(fileInfo.getKid()).setUploaded(Boolean.TRUE);} else {cleanChunkData(dto.getFileMd5());return R.error("分片上传失败!");}}return R.success(vo);} catch (Exception e) {log.error("分片上传失败!",e);return R.error("分片上传失败!");}}@Overridepublic R singleFileUpload(FileUploadDTO dto) {try {UploadResultVo vo = new UploadResultVo();SysFile sysFile = queryMd5(dto.getFileMd5());if (null != sysFile) {vo.setFileKid(sysFile.getKid()).setUploaded(Boolean.TRUE);return R.success(vo);}String filePath = fileUploadUtil.getFilePath(dto.getSaveFilePath());File file = new File(filePath, dto.getName());dto.getFile().transferTo(file);SysFile fileInfo = buildSysFile(dto, file);sysFileMapper.insert(fileInfo);vo.setFileKid(fileInfo.getKid()).setUploaded(Boolean.TRUE);return R.success(vo);} catch (IOException e) {log.error("单文件上传失败!",e);return R.error("单文件上传失败!");}}private void cleanChunkData(String md5) {LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<SysChunkRecord>().eq(SysChunkRecord::getFileMd5, md5);sysChunkRecordMapper.delete(wrapper);}private SysFile buildSysFile(FileUploadDTO dto, File file) {return SysFile.builder().fileMd5(dto.getFileMd5()).fileName(dto.getName()).filePath(file.getAbsolutePath()).fileType(dto.getType()).fileSize(dto.getSize()).createTime(LocalDateTime.now()).extension(dto.getName().substring(dto.getName().lastIndexOf("."))).build();}
}
SysChunkRecordMapper.java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import server.entity.SysChunkRecord;public interface SysChunkRecordMapper extends BaseMapper<SysChunkRecord> {
}
SysChunkRecord.java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDateTime;
@TableName(value ="sys_chunk_record")
@Data
@Accessors(chain = true)
public class SysChunkRecord implements Serializable {@TableId(type = IdType.ASSIGN_UUID)private String kid;private String chunkFileName;private String chunkFilePath;private String fileMd5;private String currentChunkMd5;private Long chunkSize;private Integer currentChunk;private Integer chunks;private LocalDateTime createTime;@TableField(exist = false)private static final long serialVersionUID = 1L;
}
FileUploadUtil.java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;@Slf4j
@Component
public class FileUploadUtil {public String getFilePath(String saveFilePath){if (StringUtils.isEmpty(saveFilePath)) {saveFilePath = "E:/Download"}File file = new File(saveFilePath);if (!file.exists()) {file.mkdirs();}DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");LocalDate now = LocalDate.now();String format = formatter.format(now);String datePath = saveFilePath + File.separator + format;File fullFile = new File(datePath);if (!fullFile.exists()) {fullFile.mkdirs();}return datePath;}
}
SysFile.java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@TableName(value ="sys_file")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysFile implements Serializable {@TableId(type = IdType.ASSIGN_UUID)private String kid;private String fileName;private String extension;private Long fileSize;private String fileType;private String filePath;private String fileMd5;private String remark;private LocalDateTime createTime;@TableField(exist = false)private static final long serialVersionUID = 1L;
}
Maven
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-core</artifactId><version>3.4.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.0.M2</version></dependency>