使用vue+elementui进行前端开发, 实现在dialog中 带进度条的上传大文件页面
- <el-form :model="ruleForm" ref="ruleForm" :label-width="formLabelWidth" :rules="theRules" >
- <el-form-item prop="jar" :label-width="formLabelWidth">
- <label slot="lable" style="font-weight: lighter">上传文件</label>
- <el-upload
- ref="upload"
- action=""
- :http-request="handleFile"
- :on-preview="handlePreview"
- :on-remove="handleRemove"
- :before-remove="beforeRemove"
- :on-change="handleChange"
- :multiple="false"
- :limit="1"
- :file-list="fileList"
- accept=".tar">
- <el-button slot="trigger" size="small" type="primary" :disabled="fileButtonDisabled">选择应用包</el-button>
- <el-button style="margin-left: 10px" size="small" type="success" @click="uploadFile" :disabled="fileButtonDisabled">上传</el-button>
- </el-upload>
- </el-form-item>
-
- <el-form-item>
- <label slot="lable" style="font-weight: lighter"></label>
- <el-progress :text-inside="true" :stroke-width="15" :percentage="filePercentage"></el-progress>
- </el-form-item>
- </el-form>
-
- <div slot="footer" class="dialog-footer">
- <el-button @click="handleClose('ruleForm')">取 消</el-button>
- <el-button type="primary" @click="submitForm('ruleForm')">确 定</el-button>
- </div>
复制
这里使用axios有一个坑需要注意一下,必须是这个指定的header,且必须用Promise包起来。
- data: function () {
- return {
- ruileForms: {
- file: '',
- jar: ''
- }
- theRules: {
- //jar:[{required:true, message:"请上传tar包", trigger:'blur'}]
- }
- fileList:[],
- filePercentage: 0, // 文件上传进度条
- fileLocation: '', // 文件在后台方式的位置
- fileCancelUpload: false, // 取消文件分片上传
- fileButtonDisabled: false, //文件上传按钮禁用
- }
- }
- methods: {
- submitForm(formName) {
- this.$refs[formName].validate((valid) => {
- if (valid) {
- if (this.fileLocation == "") {
- this.$message({message:"请上传文件", type:'fail'})
- }
-
- let formData = new FormData()
- formData.append("fileLocation", this.fileLocation)
-
- // 设置header头
- let config = {
- headers: {
- 'Content-Type':'multipart/form-data',
- }
- }
-
- Axios.post('/api/fileUpload', formData, config)
- .then((response) => {
- if (response.data.result == true) {
- this.$message({message:"成功", type:'success'})
- this.resetForm('ruleForm')
-
- }
- })
- .catch((err) => {
- console.log(err)
- })
-
- }
-
- })
- },
-
- handleFile() {
- // 空方法
- },
- handleChange(file, fileList) {
- // 文件改变时
- this.fileList = fileList
- },
- handleRemove(file, fileList) {
- this.fileCancelUpload = true
- this.filePercentage = 0 //进度条置空
- this.fileList = []
- this.fileButtonDisabled = false // 上传可点击
- },
- beforeRemove(file, fileList) {
- return this.$confirm('确定移除 ${file.name} ?');
- },
-
- //上传文件
- uploadFile() {
- let file = this.fileList[0] ? this.fileList[0].raw : ""
- if (file == "") {//判断文件是否存在
- this.$message({message:"未选择文件", type:'fail'})
- return;
- }
-
- if (file.size > 50 * 1024 * 1024) {
- //判断文件大小
- this.$message({message:"文件不能大于50M", type:'fail'})
- return;
- }
-
- //判断文件类型
- if (file.type.indexOf("tar") == "-1") { //application/x-tar
- //判断文件大小
- this.$message({message:"文件必须为tar包", type:'fail'})
- return;
- }
-
- if (file.name.length > 30) {
- //判断文件大小
- this.$message({message:"文件名大于30个字符", type:'fail'})
- return;
- }
-
- this.fileButtonDisabled = true
- // 唯一标识
- var uiqueIdentifier = this.Id + '-' + parseInt(new Data().getTime() / 1000)
- console.log(uiqueIdentifier)
- this.uploadBySplit(file, uiqueIdentifier, 0)
-
- },
- //分片上传
- uploadBySplit(file, identifier, i) {
-
- //如果取消上传直接初始化为最初状态
- if (this.fileCancelUpload) {
- this.fileCancelUpload = false
- this.filePercentage = 0
- this.fileList = []
- this.fileButtonDisabled = false
- }
-
- var chunkSize = 1024 * 1024 * 1; //分片大小1M
- var size = file.size; //总大小
- var totalChunks = Math.ceil(size/chunkSize);
-
- //分片停止条件
- if (i == totalChunks) {
- this.$message({message:"上传成功", type:'success'})
- return;
- }
-
- //计算每一篇的起始位置和结束位置
- var start = i * chunkSize;
- var end = Math.min(size, start + chunkSize);
- var fileData= file.slice(start, end)
-
- //文件分块上传
- var reader = new FileReader();
- reader.readAsBinaryString(fileData);
- reader.onload = function(e) {
- let formData = new FormData();
- formData.append('chunkNumber', i+1); //当前第几片,从0开始,文件下表从1计算。
- formData.append('chunkSize', chunkSize); //当前分片大小
- formData.append('currentChunkSize', fileData.size); //当前块大小
- formData.append('totalSize', size) //总的大小
- formData.append('identifier', identifier) //唯一标识
- formData.append('filename', file.name) //文件名
- formData.append('type', file.type) //文件类型
- // formData.append('relativePath', "/") //相对路径,暂时没用
- formData.append('totalChunks', totalChunks) //总片数
- formData.append('file', fileData) //总片数
-
- // 必须用这个Promise包起来axios,不然有问题
- return new Promise((resolve, reject) => {
-
- // 必须用这个类型的头,并且要包括boundary
- let config = {
- headers: {
- 'Content-Type':'multipart/form-data; charset=utf-8; boundary="another cool boundary";',
- }
- }
-
- Axios.post('/api/upload', formData, config)
- .then((response) => {
- if (response.data.result == true) {
- if (response.data.data != "") {
- this.fileLocation=response.data.data//合并后文件放置的位置
- }
-
- //上传进度
- var process =Math.random(end/ size*1000);
- this.fileLocation = process
- i++;
- this.uploadBySplit(file, identifier, i);
- resolve(response.data)
- }else {
- this.$message({message:"分片上传失败", type:'fail'})
- reject(err)
- }
- })
- .catch((err) => {
- console.log(err)
- })
-
- })
- }
- }
-
-
-
- }
- @ResponseBody
- @RequestMapping(value="/upload", method = RequestMethod.Post)
- public JsonResult upload(HttpServletRequest request, Chunk chunk) {
- try {
-
- boolen isMutipart = ServletFileUpload.isMutipartContent(request);
- if(isMutipart) {
- MultipartFile file = chunk.getFile();
-
- if(file == null) {
- return JsonResult.failure("文件为空");
- }
-
- File outFile = new File(generatePath(chunk));
- InputStream inputStream = file.getInputStream();
- FileUtils.copyInputStreamToFile(inputStream, outFile);
-
- //判断所有分片是否全部上传完成,完成就合并
- File dir = new File(generateFileDir(chunk));
- File[] files = dir.listFiles();
- if (files.length == chunk.getTotalChunks()) {
- String filePath = mergeFile(chunk); //合并文件
- return JsonResult.success(filePath);
- }
-
- }
-
-
- return JsonResult.success();
- } catch (Exception e) {
- log.error(e)
- return JsonResult.failure("系统错误");
- }
- }
-
- // 获取上传文件路径
- public String generatePath(Chunk chunk) {
- StringBuilder sb = new StringBuilder();
- sb.append(uploadDir).append("/").append(chunk.getIdentifier());
- if (!Files.isWritable(Paths.get(sb.toString()))) {
- log.info("path not exist, create path:" , sb.toString());
- }
- try {
- Files.createDiretories(Paths.get(sb.toString()));
- } catch (IOExeception e){
- log.error(e)
- }
-
- return sb.append("/").append(chunk.getFilename()).append("-").append(chunk.getChunkNumber().toString());
- }
-
- //获取切片路径
- public String getnerateFileDir(Chunk chunk) {
- StringBuilder sb = new StringBuilder();
- sb.append(uploadDir).append("/")/append(chunk.getIdentifier());
- return sb.toString();
- }
-
- //合并文件
- public String mergeFile(Chunk chunk) {
- String filename = chunk.getFilename(); //文件名
- String folder = generateFileDir(chunk); //文件路径
- String file = folder + File.separator + filename; //生成文件名
- merge(file, folder, filename); //合并
- return chunk.getIdentifier() + + File.separator + filename; //返回相对路径
- }
-
- //合并
- public static void merge(String targetFile, String folder) {
- try {
- Files.createFile(Paths.get(targetFile));
- Files.list(Paths.get(folder))
- .filter(path -> path.getFileName().toString().contains("-"))
- .sorted((o1, o2) -> {
- String p1 = o1.getFileName().toString();
- String p2 = o2.getFileName().toString();
- int i1 = p1.lastIndexOf("-");
- int i2 = p2.lastIndexOf("-");
- return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
- })
- .forEach(path -> {
- try {
- //以追加的形式写入文件
- Files.write(Paths.get(targetFile), Files.readAllBytes(path), StandardOpenOption.APPEND);
- //合并后删除该块
- Files.delete(path);
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
复制
chunk 块结构
- @Data
- @Entity
- public class Chunk implements Serializable {
- @Id
- @GeneratedValue
- private Long id;
- /**
- * 当前文件块,从1开始
- */
- @Column(nullable = false)
- private Integer chunkNumber;
- /**
- * 分块大小
- */
- @Column(nullable = false)
- private Long chunkSize;
- /**
- * 当前分块大小
- */
- @Column(nullable = false)
- private Long currentChunkSize;
- /**
- * 总大小
- */
- @Column(nullable = false)
- private Long totalSize;
- /**
- * 文件标识
- */
- @Column(nullable = false)
- private String identifier;
- /**
- * 文件名
- */
- @Column(nullable = false)
- private String filename;
- /**
- * 相对路径
- */
- @Column(nullable = false)
- private String relativePath;
- /**
- * 总块数
- */
- @Column(nullable = false)
- private Integer totalChunks;
- /**
- * 文件类型
- */
- @Column
- private String type;
- @Transient
- private MultipartFile file;
- }