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
