一、前端:
1、request请求util:
import axios from 'axios'
// 配置session跨域
axios.defaults.withCredentials = true
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api的base_url
timeout: 5000 // 请求超时时间
})
// request拦截器
service.interceptors.request.use(config => {
if (store.getters.token) {
config.headers['token'] = store.getters.token // 让每个请求携带自定义token 请根据实际情况自行修改
}
//文件上传
if (config.method === 'post') {
if (config.upload) { // 上传图片则添加headers
config.headers['Content-Type'] = 'multipart/form-data'
config.timeout = 360000
}
}
return config
}, error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
})
// respone拦截器
service.interceptors.response.use(
response => {
/**
* code为非20000是抛错 可结合自己业务进行修改
*/
const res = response.data
if (res.code !== 200 && res.code !=300) {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
// 401 token失效
if (res.code === 401) {
// MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload()// 为了重新实例化vue-router对象 避免bug
// })
// })
store.dispatch('FedLogOut').then(() => {
location.reload()// 为了重新实例化vue-router对象 避免bug
})
}
return Promise.reject('error')
} else {
return response.data
}
},
error => {
console.log('err' + error)// for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
2、api:
import request from '@/utils/request'
export function upload(formDatas) {
return request({
url: '/upload/upload',
method: 'post',
upload: true,
data: formDatas
})
}
3、页面:
(1)单独文件上传
<template>
<div>
<div>
<!-- <a href="javacript:void(0)" @click="exportTemplate">下载模板</a> -->
<el-button @click="exportTemplate">下载</el-button>
</div>
<div class="upload_btn">
<span>上传图片</span>
<input
type="file"
multiple
accept="image/gif, image/jpeg, image/png, image/jpg"
@change="uploadImg($event)"
/>
</div>
</div>
</template>
<script>
import { upload, download } from "@/api/upload";
export default {
methods: {
uploadImg(ev) {
var files = ev.target.files;
let formData = new FormData();
if (!files || files.length === 0) return;
for (var i = 0; i < files.length; i++) {
formData.append("files", files[i]);
}
upload(formData)
.then(res => {
alert("上传成功");
})
.catch(() => {});
},
exportTemplate() {
debugger
let baseURL = process.env.BASE_API;
let url = baseURL + "file/download";
alert(url)
window.location.href = url;
},
exportTemplate1() {
//download().then(res => {});
download()
.then(res => {
// debugger;
// if (!res) {
// return;
// }
// const url = window.URL.createObjectURL(new Blob([res]));
// const link = document.createElement("a");
// link.style.display = "none";
// link.href = url;
// link.setAttribute("download", "user.xlsx");
// document.body.appendChild(link);
// link.click();
})
.catch(err => {
// this.$message.error(err);
});
}
}
};
</script>
(2)还可以把选择文件和上传分开为两步、并且formData携带其他的参数:
<!-- @format -->
<template>
<div class="container">
<el-row class="file-div">
<el-form>
<el-form-item label="附件:" label-width="120px">
<!--上传-->
<el-row>
<el-col :span="8" class="file-operate">
<label class="button text-overflow" for="messageFile">
<Icon type="ios-cloud-upload-outline" size="16" />
选择文件
</label>
<input
:disabled="isDisable"
multiple
ref="messageFile"
@change="fileHandler()"
type="file"
id="messageFile"
style="position: absolute; clip: rect(0 0 0 0)"
accept=".png, .jpeg, .jpg, application/pdf, .txt, .xls, .xlsx, .doc, .docx, .wps, .rtf, .ppt, .pptx, .pps, .ppsx,.mp4,.mp3,.zip"
/>
</el-col>
<el-col :span="4">
<el-button
@click="upload"
:disabled="isDisable"
size="small"
class="submit-btn"
>上传</el-button
></el-col
>
</el-row>
<!--回显-->
<el-row v-for="(item, index) in files" :key="index">
<el-col :span="12"
><span class="file-name">{{ item.fileName }}</span></el-col
>
<el-col :span="2">
<a @click="downloadFile(item.downloadUrl)" v-if="item.downloadUrl"
>下载</a
>
</el-col>
<el-col :span="2">
<a @click="delFile(item.fileName)" v-if="!isDisable"
>删除</a
></el-col
>
</el-row>
</el-form-item>
</el-form>
</el-row>
</div>
</template>
<script>
export default {
props: { id: {}, isDisable: { default: false }},
data() {
return {
files: [],
selectFiles: []
};
},
created() {
if (this.id != null) {
//id不会空,后台查询回显
this.getFiles();
}
},
watch: {
//变化则传给后台
files: {
handler: function() {
var _this = this;
this.$emit('fileChange', _this.files);
},
deep: true
}
},
methods: {
//回显
getFiles() {
this.$api['user/getFiles']({
formId: this.id
}).then(data => {
this.files = data;
});
},
//change
fileHandler() {
let currFiles = this.$refs.messageFile.files;
for (let i = 0; i < currFiles.length; i++) {
this.selectFiles.push(currFiles[i]);
}
for (let i = 0; i < currFiles.length; i++) {
this.files.push({ fileName: currFiles[i].name });
}
},
//上传
async upload() {
let formData = new FormData();
formData.append('formId', this.id);
//此次需要上传的文件
let fileSise = 0;
for (let i = 0; i < this.selectFiles.length; i++) {
formData.append('files', this.selectFiles[i]);
fileSise += this.selectFiles[i].size;
}
if (fileSise >= 104857600) {
this.$Message.error('文件总和不得大于100M');
document.querySelector('#messageFile').reset();
return;
}
//已有的文件
let fileNameContextIdMap = new Map();
this.files.forEach(item => {
let contextId = item.contextId;
if (contextId != null) {
fileNameContextIdMap.set(item.fileName, contextId);
}
});
//map转json
if (fileNameContextIdMap.size > 0) {
let obj = Object.create(null);
for (let [k, v] of fileNameContextIdMap) {
obj[k] = v;
}
formData.append('fileNameContextIdMap', JSON.stringify(obj));
}
//上传接口
await this.$api['file/uploadFile'](formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 300000
})
.then(data => {
// this.$Message.success({
// content: '上传成功!',
// duration: 3,
// closable: true
// });
})
.finally(() => {
this.selectFiles = [];
this.getFiles();
});
},
//下载
downloadFile(downloadFile) {
let downloadElement = document.createElement('a');
let href = downloadFile;
downloadElement.href = href;
document.body.appendChild(downloadElement);
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
},
//删除文件
delFile(fileName) {
this.files = this.files.filter(item => {
return item.fileName !== fileName;
});
//待上传的文件
let handleFiles = [];
for (let i = 0; i < this.selectFiles.length; i++) {
let selectFilName = this.selectFiles[i].name;
if (fileName != selectFilName) {
handleFiles.push(this.selectFiles[i]);
}
}
this.selectFiles = handleFiles;
}
}
};
</script>
注意:
(1)以上下载是前端get请求下载文件到本地,如果是post请求,需要这样:
在请求参数后加:
{ responseType: 'blob' }
再构造下载:
let downloadElement = document.createElement('a');
let blob = new Blob([res.data], { type: 'application/vnd.ms-excel;charset=utf-8' });
let href = URL.createObjectURL(blob);
downloadElement.href = href;
document.body.appendChild(downloadElement);
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
如我的:
// 导出
exportExcel() {
this.$Modal.confirm({
title: '导出提示',
content: `是否导出?`,
onOk: () => {
let params = {
provinceId: this.searchData.provinceId,
cityId: this.searchData.cityId,
districtId: this.searchData.districtId
};
this.$api['user/exportExcel'](params,{ responseType: 'blob' }).then(res => {
let downloadElement = document.createElement('a');
let blob = new Blob([res.data], { type: 'application/vnd.ms-excel;charset=utf-8' });
let href = URL.createObjectURL(blob);
downloadElement.href = href;
document.body.appendChild(downloadElement);
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
});
}
});
},
(2)清空file文件的方法:不可以使用$refs的reset方法,可以使用
this.$refs.batchImport.value = '';
或者
ev.target.value = '';
二、后端:
1、不拦截下载接口:
/**白名单
* 需要放行的URL
*/
private static final String[] AUTH_WHITELIST = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/static",
"/static/diagram-viewer",
"/static/**",
"/index.html",
"/code/**", //验证码
"/user/logout/**",
"/file/download/**"
};
2、接口:
(1)接收文件数组
package com.demo.rest;
import com.demo.dto.JWTUserDTO;
import com.demo.dto.ResponseMessage;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
@RequestMapping("/file")
@RestController
public class UploadController {
//上传
@RequestMapping("/upload")
public ResponseMessage uploadFile(@RequestBody MultipartFile[] files) throws IOException {
JWTUserDTO jwtUser = (JWTUserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
for (MultipartFile file : files) {
long size = file.getSize();
String fileName = file.getOriginalFilename();
System.out.println(fileName + " " + size);
}
return ResponseMessage.success("上传成功");
}
//下载工程模板文件到前端浏览器
@RequestMapping("/download")
public ResponseMessage download(HttpServletResponse response, HttpServletRequest request) {
String fileName = "user.xlsx";
File file = new File(this.getClass().getClassLoader().getResource("")
.getPath() + File.separator + "templates" + File.separator + "user.xlsx");
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
byte[] buffer = new byte[1024];
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
bis.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
return ResponseMessage.success("导出失败");
} finally {
try {
bis.close();
fis.close();
} catch (Exception e) {
return ResponseMessage.success("导出失败");
}
}
return ResponseMessage.success("导出成功");
}
}
(2)只接收一个文件,去掉数组即可:
@PostMapping("upload")
public ResponseMessage uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
String result = fileService.uploadFile(file);
if (StringUtils.isBlank(result)){
return ResponseMessage.error("上传失败");
}else{
return ResponseMessage.success("上传成功",result);
}
}
(3)如果还携带其他参数:使用@ModelAttribute接收对象
@PostMapping(value = "/uploadFile")
public ResponseMessage uploadFile(@ModelAttribute MaterialVO materialVO)
public class MaterialVO{
private Integer formId;
private MultipartFile[] files;
...get set方法...
}