推薦:Vue學習彙總
使用Vue和Spring Boot實作檔案上傳
推薦
- 使用Vue和Spring Boot實作檔案下載下傳
檔案上傳是日常開發中很常見的業務需求,這裡部落客通過使用Vue和Spring Boot實作一個簡單的檔案上傳小例子。
前端:
<template>
<div class="blog">
<div class="upload">
<input type="file" @change="fileChoose($event)">
<button @click="submit()"><span>送出</span></button>
</div>
</div>
</template>
<script>
export default{
name:'blog',
data(){
return {
file:'',
}
},
methods:{
fileChoose(event){
this.file = event.target.files[0]; //擷取檔案
console.log(event)
},
submit(){
let file = new FormData();
file.append("file", this.file);
this.axios.post('/file/upload',
file ,
{headers: {'Content-Type': 'multipart/form-data'}}
).then((res)=>{
this.$message.success('檔案上傳成功');
}).catch((res)=>{
this.$message.error('檔案上傳失敗');
})
},
}
}
</script>
不懂
FormData
可以自己去百度一下,
{headers: {'Content-Type': 'multipart/form-data'}}
這個需要加上。
頁面如下圖所示:
router:js
:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);
export default new Router({
routes:[
{
path:'/',
redirect:'/blog',
},
{
path: '/blog',
name: 'blog',
component: () => import('./pages/blog.vue')
}
]
});
vue.config.js
:
module.exports = {
devServer:{
host:'localhost',
port:8080,
proxy:{
'/api':{
target:'http://127.0.0.1:8081',
changeOrigin:true,
pathRewrite:{
'/api':''
}
}
}
},
lintOnSave:false,
productionSourceMap:true
}
後端:
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kaven</groupId>
<artifactId>system</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>system</name>
<description>系統</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
:
spring:
servlet:
multipart:
max-file-size: 500MB
max-request-size: 500MB
logging:
pattern:
console: '[%p]-%m%n'
server:
port: 8081
啟動類:
package com.kaven.system;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
FileController
:
package com.kaven.system.controller;
import com.kaven.system.enums.ResponseEnum;
import com.kaven.system.vo.ResponseVo;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping(value="/upload", headers="content-type=multipart/form-data")
public ResponseVo upload(@RequestParam(value = "file") MultipartFile file) throws IOException, InterruptedException {
System.out.println("有檔案上傳請求進來了");
FileOutputStream fileOutputStream = null;
InputStream inputStream = null;
try {
if (file != null) {
String fileName = file.getOriginalFilename();
if (StringUtils.isNotBlank(fileName)) {
java.io.File outFile = new java.io.File("E:\\"+fileName);
if (outFile.getParentFile() != null || !outFile.getParentFile().isDirectory()) {
// 建立父檔案夾
outFile.getParentFile().mkdirs();
}
fileOutputStream = new FileOutputStream(outFile);
inputStream = file.getInputStream();
IOUtils.copy(inputStream, fileOutputStream);
}
}
else {
return ResponseVo.error(ResponseEnum.File_NOT_EXIST);
}
} catch (Exception e) {
e.printStackTrace();
return ResponseVo.error(ResponseEnum.ERROR);
} finally {
if (fileOutputStream != null) {
fileOutputStream.flush();
fileOutputStream.close();
}
}
return ResponseVo.success(ResponseEnum.SUCCESS);
}
}
ResponseVo<T>
:
package com.kaven.system.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kaven.system.enums.ResponseEnum;
import lombok.Data;
@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ResponseVo<T> {
private Integer status;
private String msg;
private T data;
private ResponseVo(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
private ResponseVo(Integer status, T data) {
this.status = status;
this.data = data;
}
public static <T> ResponseVo<T> success(T data){
return new ResponseVo<T>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T> ResponseVo<T> error(ResponseEnum responseEnum){
return new ResponseVo<T>(responseEnum.getCode() , responseEnum.getDesc());
}
}
ResponseEnum
:
package com.kaven.system.enums;
import lombok.Getter;
@Getter
public enum ResponseEnum {
ERROR(-1 ,"服務端錯誤"),
SUCCESS(0, "成功"),
File_NOT_EXIST(13 , "檔案為空"),
;
Integer code;
String desc;
ResponseEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
測試:
分析:
this.file = event.target.files[0]; //擷取檔案
為什麼擷取檔案是這行代碼?
通過
console.log(event)
,從
Console
中可以看到選擇檔案觸發的事件:
從上圖可以知道,通過
srcElement
也可以擷取檔案,也确實如此。
<template>
<div class="blog">
<div class="upload">
<input type="file" @change="fileChoose($event)">
<button @click="submit()"><span>送出</span></button>
</div>
</div>
</template>
<script>
export default{
name:'blog',
data(){
return {
file:'',
}
},
methods:{
fileChoose(event){
this.file = event.srcElement.files[0]; //擷取檔案
console.log(event)
},
submit(){
let file = new FormData();
file.append("file", this.file);
this.axios.post('/file/upload',
file ,
{headers: {'Content-Type': 'multipart/form-data'}}
).then((res)=>{
this.$message.success('檔案上傳成功');
}).catch((res)=>{
this.$message.error('檔案上傳失敗');
})
},
}
}
</script>
但是這種方式不推薦使用。
由上圖可知,還可以進一步擷取檔案名稱、大小、類型。
而後端通過
MultipartFile
類型執行個體來接收檔案。
MultipartFile
源碼如下:
package org.springframework.web.multipart;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
public interface MultipartFile extends InputStreamSource {
/**
* Return the name of the parameter in the multipart form.
* @return the name of the parameter (never {@code null} or empty)
*/
String getName();
/**
* Return the original filename in the client's filesystem.
* <p>This may contain path information depending on the browser used,
* but it typically will not with any other than Opera.
* @return the original filename, or the empty String if no file has been chosen
* in the multipart form, or {@code null} if not defined or not available
* @see org.apache.commons.fileupload.FileItem#getName()
* @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename
*/
@Nullable
String getOriginalFilename();
/**
* Return the content type of the file.
* @return the content type, or {@code null} if not defined
* (or no file has been chosen in the multipart form)
*/
@Nullable
String getContentType();
/**
* Return whether the uploaded file is empty, that is, either no file has
* been chosen in the multipart form or the chosen file has no content.
*/
boolean isEmpty();
/**
* Return the size of the file in bytes.
* @return the size of the file, or 0 if empty
*/
long getSize();
/**
* Return the contents of the file as an array of bytes.
* @return the contents of the file as bytes, or an empty byte array if empty
* @throws IOException in case of access errors (if the temporary store fails)
*/
byte[] getBytes() throws IOException;
/**
* Return an InputStream to read the contents of the file from.
* <p>The user is responsible for closing the returned stream.
* @return the contents of the file as stream, or an empty stream if empty
* @throws IOException in case of access errors (if the temporary store fails)
*/
@Override
InputStream getInputStream() throws IOException;
/**
* Return a Resource representation of this MultipartFile. This can be used
* as input to the {@code RestTemplate} or the {@code WebClient} to expose
* content length and the filename along with the InputStream.
* @return this MultipartFile adapted to the Resource contract
* @since 5.1
*/
default Resource getResource() {
return new MultipartFileResource(this);
}
/**
* Transfer the received file to the given destination file.
* <p>This may either move the file in the filesystem, copy the file in the
* filesystem, or save memory-held contents to the destination file. If the
* destination file already exists, it will be deleted first.
* <p>If the target file has been moved in the filesystem, this operation
* cannot be invoked again afterwards. Therefore, call this method just once
* in order to work with any storage mechanism.
* <p><b>NOTE:</b> Depending on the underlying provider, temporary storage
* may be container-dependent, including the base directory for relative
* destinations specified here (e.g. with Servlet 3.0 multipart handling).
* For absolute destinations, the target file may get renamed/moved from its
* temporary location or newly copied, even if a temporary copy already exists.
* @param dest the destination file (typically absolute)
* @throws IOException in case of reading or writing errors
* @throws IllegalStateException if the file has already been moved
* in the filesystem and is not available anymore for another transfer
* @see org.apache.commons.fileupload.FileItem#write(File)
* @see javax.servlet.http.Part#write(String)
*/
void transferTo(File dest) throws IOException, IllegalStateException;
/**
* Transfer the received file to the given destination file.
* <p>The default implementation simply copies the file input stream.
* @since 5.1
* @see #getInputStream()
* @see #transferTo(File)
*/
default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
}
}
根據
MultipartFile
源碼,修改一下檔案上傳接口:
package com.kaven.system.controller;
import com.kaven.system.enums.ResponseEnum;
import com.kaven.system.vo.ResponseVo;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping(value="/upload", headers="content-type=multipart/form-data")
public ResponseVo upload(@RequestParam(value = "file") MultipartFile file) throws IOException, InterruptedException {
System.out.println("有檔案上傳請求進來了");
try {
if (file != null) {
String fileName = file.getOriginalFilename();
if (StringUtils.isNotBlank(fileName)) {
long start = System.currentTimeMillis();
Path path = Paths.get("E:/"+fileName);
try {
if(!Files.exists(path))
Files.createFile(path);
} catch (IOException e) {
e.printStackTrace();
}
file.transferTo(path);
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
}
else {
return ResponseVo.error(ResponseEnum.File_NOT_EXIST);
}
} catch (Exception e) {
e.printStackTrace();
return ResponseVo.error(ResponseEnum.ERROR);
}
return ResponseVo.success(ResponseEnum.SUCCESS);
}
}
前端分别上傳259KB、31.6MB、315MB的檔案。
測試之前的方法:
之前的方法耗時更少,但代碼稍微多一點點。