Vue Elementui+SpringBoot做大文件切片上传
导读:主要思想是前端做大文件的MD5计算和大文件的切分,后台做小文件的合并和MD5的校验,直接上代码。前端VUE:<template> <el-upload ref="upload" class="uploa...
主要思想是前端做大文件的MD5计算和大文件的切分,后台做小文件的合并和MD5的校验,直接上代码。
前端VUE:
template> el-upload ref="upload" class="upload-demo" action="#" :http-request="handleUpload" :on-change="handleFileChange" :before-upload="handleBeforeUpload" :auto-upload="true" :multiple="false" :file-list="fileList"> el-button slot="trigger" size="small" type="primary"> 选取文件/el-button> !-- el-button style="margin-left: 10px; " size="small" type="success" @click="handleUpload"> 上传文件/el-button> --> /el-upload> /template> script> import SparkMD5 from 'spark-md5' import axios from "axios"; import { getKey,completeUpload } from "@/api/fileupload"; export default { data() { return { percent: 0, fileList: [], chunkSize: 6 * 1024 * 1024, // 分片大小为2MB } } , methods: { handleFileChange(file) { this.fileList = [file] } , handleBeforeUpload(file) { // 根据文件大小判断是否需要分片 if (file.size > this.chunkSize) { file.chunked = true file.percent = 0 } return true } , handleUpload(params) { const file = params.file let startTime = new Date().getTime() if (file.chunked) { const loading = this.$loading({ lock: true, text: '大文件正在计算M5,请稍后', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' } ); const readerTotal = new FileReader() readerTotal.readAsArrayBuffer(file) readerTotal.onload = () => { const sparkTotal = new SparkMD5.ArrayBuffer() sparkTotal.append(readerTotal.result) const md5 = sparkTotal.end() let query = { name:md5} getKey(query).then(resKey=> { const totalChunks = Math.ceil(file.size / this.chunkSize) let currentChunk = 0 const config = { headers: { "Content-Type": "multipart/form-data" } , onUploadProgress: (progressEvent) => { this.percent = Math.round((currentChunk + 1) / totalChunks * 100) loading.setText('大文件正在分片上传,请稍后'+this.percent+'%') } , } ; const reader = new FileReader() const spark = new SparkMD5.ArrayBuffer() const uploadChunk = () => { const start = currentChunk * this.chunkSize const end = Math.min(file.size, start + this.chunkSize) reader.readAsArrayBuffer(file.slice(start, end)) reader.onload = () => { spark.append(reader.result) const chunk = new Blob([reader.result]) chunk.file = file chunk.currentChunk = currentChunk chunk.totalChunks = totalChunks chunk.sparkMD5 = spark.end() const formData = new FormData() formData.append('chunk', chunk) formData.append('filename', file.name) formData.append('totalChunks', totalChunks) formData.append('key', resKey.data) formData.append('currentChunk', currentChunk) axios.post(process.env.VUE_APP_BASE_API + "app/fileupload/file/upload/chunk", formData, config) .then(response => { if (currentChunk totalChunks - 1) { currentChunk++ uploadChunk() } else { let data = { key:resKey.data,fileName:file.name} completeUpload(data).then(res=> { this.fileList = [] loading.close(); this.percent = 0 this.$message.success('上传成功') let endTime = new Date().getTime() console.log('Chunk Cost:'+endTime-startTime) } ) } } ) .catch(error => { this.$message.error(error.message) } ) } } uploadChunk() } ); } } else { const loading = this.$loading({ lock: true, text: '正在上传,请稍后', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' } ); const self = this; const file = params.file const formData = new FormData(); formData.append(self.upload_name, file); const config = { headers: { "Content-Type": "multipart/form-data" } , onUploadProgress: (progressEvent) => { this.percent = ((progressEvent.loaded / progressEvent.total) * 100) | 0; loading.setText('正在上传,请稍后'+this.percent+'%') } , } ; axios .post( process.env.VUE_APP_BASE_API + "app/fileupload/file/upload", formData, config ) .then((res) => { loading.close(); this.$message.success('上传成功') this.percent = 0 let endTime = new Date().getTime() console.log('Common Cost:'+endTime-startTime) } ) .catch((err) => { console.log(err); } ); } } } } /script>
api/fileupload.js
import request from '@/utils/request' export function getKey(query) { return request({ url: '/app/fileupload/getKey', method: 'get', params: query } ) } export function completeUpload(data) { return request({ url: '/app/fileupload/completeUpload', method: 'post', data } ) }
utils/request.js
import axios from 'axios' import { MessageBox, Message } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' import router from '@/router' axios.defaults.headers.common['Content-Type'] = 'application/json; charset=UTF-8' axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' axios.defaults.withCredentials = true // create an axios instance const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests timeout: 50000 // request timeout } ) // request interceptor service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { // let each request carry token // ['X-Token'] is a custom headers key // please modify it according to the actual situation config.headers['X-Token'] = getToken() } return config } , error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ response => { const res = response.data if(res.ret===200 || res.ret===404){ return res; } // if the custom code is not 20000, it is judged as an error. if (res.code !== 20000) { // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; // if (res.code === 50008 || res.code === 50012 || res.code === 50014) { if (res.code === 50014) { // to re-login MessageBox.confirm('您已经登出了,您可以取消继续待在此页面或者重新登录', '登出确认', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { store.dispatch('user/resetToken').then(() => { if (res.data === 2) { router.push(`/login`) } else { router.push(`/member/login`) } // location.reload() // const type = this.$store.getters.type // if (type === 1) { // this.$router.push(`/member/login?redirect=${ this.$route.fullPath} `) // } else { // this.$router.push(`/login?redirect=${ this.$route.fullPath} `) // } } ) } ) } else { Message({ message: res.message || 'Error', type: 'error', duration: 5 * 1000 } ) } return Promise.reject(new Error(res.message || 'Error')) } else { return res } } , error => { Message({ message: '网络异常', type: 'error', duration: 5 * 1000 } ) return Promise.reject(error) } ) export default service
自行安装axios,spark-md5库,下面就是后端代码
import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import java.io.*; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; @RestController @RequestMapping("/fileupload") public class FileUploadController { @Value("${ file.dir} ") private String filedir; @Autowired private MessageUtils messageUtils; @RequestMapping(value="/getKey") public @ResponseBody ResponseData getKey(@RequestParam String name) throws Exception{ ResponseData rest = new ResponseData(); rest.setRet(20000); rest.setMsg("success"); String key = UUID.randomUUID().toString(); rest.setData(key); AppConstants.fileKeyMd5Map.put(key,name); AppConstants.fileKeyChunkMap.put(key,new ArrayList> ()); return rest; } @RequestMapping(value="/completeUpload") public @ResponseBody ResponseData completeUpload(@RequestBody FileCompleteUploadDto fileCompleteUploadDto,HttpServletRequest request) throws Exception{ ResponseData rest = new ResponseData(); rest.setRet(20000); rest.setMsg("success"); String key = fileCompleteUploadDto.getKey(); String md5 = AppConstants.fileKeyMd5Map.get(key); //按照顺序合并文件 ListFileChunkObjDto> fileChunkObjDtoList = AppConstants.fileKeyChunkMap.get(key); ListFileChunkObjDto> newfileChunkObjDtoList = fileChunkObjDtoList.stream().sorted(Comparator.comparing(FileChunkObjDto::getIndex)).collect(Collectors.toList()); String fileExt = fileCompleteUploadDto.getFileName().substring(fileCompleteUploadDto.getFileName().lastIndexOf(".") + 1).toLowerCase(); String localHost = UrlUtils.getLocalRealIp(); String port = String.valueOf(request.getLocalPort()); SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); String newFileName = df.format(new Date()) + "_" + localHost + "_" + port + "_" + new Random().nextInt(1000) + "." + fileExt; File newFile = new File(tifiledir+newFileName); FileChannel resultfileChannel = new FileOutputStream(newFile).getChannel(); for(FileChunkObjDto fileChunkObjDto:newfileChunkObjDtoList){ FileChannel fileChannel = new FileInputStream(tifiledir+fileChunkObjDto.getFileName()).getChannel(); resultfileChannel.transferFrom(fileChannel,resultfileChannel.size(),fileChannel.size()); fileChannel.close(); } resultfileChannel.close(); //校验md5 // System.out.println("a md5:"+md5); // System.out.println("b md5:"+ FileUtils.getMD5(tifiledir+newFileName)); for(FileChunkObjDto fileChunkObjDto:newfileChunkObjDtoList){ try { Path sourcePath = Paths.get(tifiledir + fileChunkObjDto.getFileName()); Files.delete(sourcePath); } catch (Exception e){ } } //删除key AppConstants.fileKeyMd5Map.remove(key); AppConstants.fileKeyChunkMap.remove(key); return rest; } @RequestMapping(value="/file/upload/chunk") public @ResponseBody ResponseData uploadChunkFile(@RequestParam MapString,String> map, HttpServletRequest request) throws Exception{ ResponseData rest = new ResponseData(); rest.setRet(20000); rest.setMsg("success"); MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; MapString, MultipartFile> fileMap = multipartRequest.getFileMap(); String key = map.get("key"); String currentChunk = map.get("currentChunk"); ListFileChunkObjDto> fileChunkObjDtoList = AppConstants.fileKeyChunkMap.get(key); FileChunkObjDto fileChunkObjDto = new FileChunkObjDto(); fileChunkObjDto.setIndex(Integer.parseInt(currentChunk)); String ctxPath = filedir; //创建文件夹 File file = new File(ctxPath); if (!file.exists()) { file.mkdirs(); } String localHost = UrlUtils.getLocalRealIp(); String port = String.valueOf(request.getLocalPort()); SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); for (Map.EntryString, MultipartFile> entity : fileMap.entrySet()) { // 上传文件名 MultipartFile mf = entity.getValue(); String newFileName = df.format(new Date()) + "_" + localHost + "_" + port + "_" + new Random().nextInt(1000); fileChunkObjDto.setFileName(newFileName); fileChunkObjDtoList.add(fileChunkObjDto); String filePath = ctxPath + newFileName; File uploadFile = new File(filePath); InputStream in = null; FileOutputStream out = null; try { byte b[] = new byte[1024 * 1024]; int i = 0; in = mf.getInputStream(); out = new FileOutputStream(uploadFile); while ((i = in.read(b)) != -1) { //写出读入的字节数组,每次从0到所读入的位置,不会多写 out.write(b, 0, i); } } catch (IOException e) { e.printStackTrace(); } finally { try { //关闭流 if (in != null) { in.close(); } if (out != null) { out.close(); } } catch (IOException e) { throw new RuntimeException(e); } } } return rest; } @RequestMapping(value="/file/upload") public @ResponseBody ResponseData uploadFile(@RequestParam MapString,String> map, HttpServletRequest request) throws Exception{ ResponseData rest = new ResponseData(); rest.setRet(20000); rest.setMsg("success"); MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; MapString, MultipartFile> fileMap = multipartRequest.getFileMap(); String ctxPath = filedir; //创建文件夹 File file = new File(ctxPath); if (!file.exists()) { file.mkdirs(); } String fileName = null; String localHost = UrlUtils.getLocalRealIp(); String port = String.valueOf(request.getLocalPort()); SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); for (Map.EntryString, MultipartFile> entity : fileMap.entrySet()) { // 上传文件名 MultipartFile mf = entity.getValue(); fileName = mf.getOriginalFilename(); String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); String newFileName = df.format(new Date()) + "_" + localHost + "_" + port + "_" + new Random().nextInt(1000) + "." + fileExt; String filePath = ctxPath + newFileName; File uploadFile = new File(filePath); InputStream in = null; FileOutputStream out = null; try { byte b[] = new byte[1024 * 1024]; int i = 0; in = mf.getInputStream(); out = new FileOutputStream(uploadFile); while ((i = in.read(b)) != -1) { //写出读入的字节数组,每次从0到所读入的位置,不会多写 out.write(b, 0, i); } } catch (IOException e) { e.printStackTrace(); } finally { try { //关闭流 if (in != null) { in.close(); } if (out != null) { out.close(); } } catch (IOException e) { throw new RuntimeException(e); } } } return rest; } }
AppConstants.java
public static final MapString,String> fileKeyMd5Map = new HashMap> (){ } ; public static final MapString, ListFileChunkObjDto> > fileKeyChunkMap = new HashMap> (){ } ;
UrlUtils.java
public class UrlUtils { public static int validateUrl(String url){ URL u = null; HttpURLConnection urlconn=null; int state = 0; do{ try { u = new URL(url); urlconn = (HttpURLConnection) u.openConnection(); //System.out.println(urlconn.); urlconn.setConnectTimeout(2000); state = urlconn.getResponseCode(); } catch(SocketTimeoutException e){ state = 2; e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); break; } } while(false); return state; } public static String getLocalRealIp(){ InetAddress addr = null; try { addr = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } byte[] ipAddr = addr.getAddress(); //String ipAddrStr = ""; StringBuilder ipAddrStr = new StringBuilder(); for (int i = 0; i ipAddr.length; i++) { if (i > 0) { ipAddrStr.append("."); } ipAddrStr.append(ipAddr[i] & 0xFF); } //System.out.println(ipAddrStr); return ipAddrStr.toString(); } /** * 获取当前网络ip * @param request * @return */ public static String getIpAddr(HttpServletRequest request){ String ipAddress = request.getHeader("x-forwarded-for"); if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){ //根据网卡取本机配置的IP InetAddress inet=null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress= inet.getHostAddress(); } } //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if(ipAddress!=null & & ipAddress.length()> 15){ //"***.***.***.***".length() = 15 if(ipAddress.indexOf(",")> 0){ ipAddress = ipAddress.substring(0,ipAddress.indexOf(",")); } } return ipAddress; } public static void main(String []args){ System.out.println(getLocalRealIp()); } }
ResponseData.java
import com.alibaba.fastjson.JSON; import com.github.pagehelper.PageInfo; import org.springframework.util.Assert; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; /** * 目前vue前台解析返回格式是这样的 */ public class ResponseData extends LinkedHashMapString, Object> { //设置返回标准 public static final String RET = "code"; //返回代码 public static final String MSG = "message"; //返回信息 public static final String DATA = "data"; //其他内容 public static final String TIMESTAMP = "timestamp"; public ResponseData() { this.setRet(200).setMsg("请求成功").setTimestamp(System.currentTimeMillis()); } public ResponseData(int ret, String msg) { this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()); } public ResponseData(int ret, String msg, Object attributeValue) { this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()).setData(attributeValue); } public ResponseData(int ret, String msg, PageInfo pageData) { this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()).setPageData(pageData); } public ResponseData(int ret, String msg, String attributeName, Object attributeValue) { this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()).addAttribute(attributeName, attributeValue); } public int getRet() { return (Integer)super.get(RET); } public ResponseData setRet(int ret) { this.put(RET, ret); return this; } public long getTimestamp() { return (Long)super.get(TIMESTAMP); } public ResponseData setTimestamp(long timestamp) { this.put(TIMESTAMP, timestamp); return this; } public String getMsg() { return (String)super.get(MSG); } public ResponseData setMsg(String msg) { this.put(MSG, msg); return this; } public ResponseData setData(Object attributeValue) { this.put(DATA, attributeValue); return this; } public ResponseData setPageData(PageInfo pageinfo){ PageData pageData = new PageData(pageinfo); this.put(DATA,pageData); return this; } public ResponseData addAttribute(String attributeName, Object attributeValue) { Assert.notNull(attributeName, "Model attribute name must not be null"); this.put(attributeName, attributeValue); return this; } public ResponseData addAllAttributes(MapString, ?> attributes) { if (attributes != null) { this.putAll(attributes); } return this; } public ResponseData mergeAttributes(MapString, ?> attributes) { if (attributes != null) { Iterator var2 = attributes.entrySet().iterator(); while(var2.hasNext()) { Map.EntryString,?> entry = (Map.EntryString,?> )var2.next(); if (!this.containsKey(entry.getKey())) { this.put(entry.getKey(), entry.getValue()); } } } return this; } public boolean containsAttribute(String attributeName) { return this.containsKey(attributeName); } public String toJsonString() { return JSON.toJSONString(this); } }
FileChunkObjDto.java和FileCompleteUploadDto.java
@Data public class FileChunkObjDto { private Integer index; private String fileName; } @Data public class FileCompleteUploadDto { private String key; private String fileName; }
md5的比较校验可以自行做业务判断,缓存的处理可以使用redis做终端断点续传的长期存储改造。
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Vue Elementui+SpringBoot做大文件切片上传
本文地址: https://pptw.com/jishu/292978.html