引入依賴javascript
<?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 http://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.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
前臺(兩個js框架自行下載)html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Index</title> <style> html, body { margin: 0; padding: 0; height: 100%; min-height: 100%; } .header { padding: 1px; position: relative; left: 0; top: 0; width: 100%; height: 70px; background-color: #4E3384; color: #c7acff; } .header h2 { text-align: center; } .header a { display: block; position: absolute; top: 18px; right: 15px; padding: 8px 15px; background-color: #a27bf1; color: #fff; border-radius: 3px; text-decoration: none; } .container { min-height: 100%; } .main { max-width: 1200px; margin: 30px auto; text-align: center; } .file-wrap { position: relative; padding: 8px 10px; background-color: #ad0660; color: #fff; text-decoration: none; font-size: 14px; border-radius: 3px; margin: 60px 25px; display: inline-block; } .file-wrap:hover { background-color: #d80b7a; } .file-input { font-size: 0; position: absolute; left: 0; top: 0; width: 100%; height: 100%; cursor: pointer; opacity: 0; } </style> </head> <body> <div class="container"> <div class="header"> <h2>文件上傳</h2> </div> <div class="main"> <a href="javascript:;" class="file-wrap">單文件上傳 <input type="file" id="singleFile" name="singleFile" class="file-input"> </a> <a href="javascript:;" class="file-wrap">多文件上傳 <input type="file" id="multiFile" name="multiFile" class="file-input" multiple> </a> <div id="imgDiv"></div> </div> </div> <script th:src="@{js/jquery-3.3.1.min.js}"></script> <script th:src="@{js/ajaxfileupload.js}"></script> <script> $(document).on('change', '#singleFile', function () { $.ajaxFileUpload({ url: '/upload/single', // 用於文件上傳的服務器端請求地址 secureuri: false, // 是否須要安全協議,通常設置爲false fileElementId: 'singleFile', // 文件上傳域的ID dataType: 'json', // 返回值類型 通常設置爲json // 服務器成功響應處理函數 success: function (data, status) { alert(data.msg); if (data.code == 1){ $('#imgDiv').append($('<img src="'+ data.data +'">')); } }, // 服務器響應失敗處理函數 error: function (data, status, e) { alert(e); } }); $('#singleFile').val(''); }) $(document).on('change', '#multiFile', function () { $.ajaxFileUpload({ url: '/upload/multi', // 用於文件上傳的服務器端請求地址 secureuri: false, // 是否須要安全協議,通常設置爲false fileElementId: 'multiFile', // 文件上傳域的ID dataType: 'json', // 返回值類型 通常設置爲json // 服務器成功響應處理函數 success: function (data, status) { alert(data.msg); if (data.code == 1){ for (var i = 0; i < data.data.length; i++){ $('#imgDiv').append($('<img src="'+ data.data[i] +'">')); } } }, // 服務器響應失敗處理函數 error: function (data, status, e) { alert(e); } }); $('#multiFile').val(''); }) </script> </body> </html>
最後是Java代碼前端
這個是通用的返回結果vue
package com.example.demo; import lombok.Data; @Data public class BaseResponse<T> { private T data; private int code = 1; // 0-false;1-true;默認1 private String msg = "success"; }
下面是核心上傳代碼(一個單文件上傳,一個多文件上傳)java
package com.example.demo; import org.apache.commons.io.FileUtils; 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 javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @RestController @RequestMapping("/upload") public class FileController { @PostMapping("/single") public BaseResponse<String> single(@RequestParam("singleFile") MultipartFile file, HttpServletRequest req) throws IOException { String fileName = file.getOriginalFilename(); String fileType = fileName.substring(fileName.lastIndexOf(".")); String newFileName = new Date().getTime() + ""; String fileSize = FileUtils.byteCountToDisplaySize(file.getSize()); System.out.println("文件名:" + fileName); System.out.println("文件大小:" + fileSize); String path = req.getServletContext().getRealPath("/MyFiles/"); // 保存在項目運行目錄下的MyFiles文件夾 File targetFile = new File(path + newFileName + fileType); FileUtils.copyInputStreamToFile(file.getInputStream(), targetFile); String imgPath = targetFile.getPath(); System.out.println("保存路徑:" + imgPath); // String url = req.getScheme() + "://" + req.getServerName() + req.getContextPath() + // "/MyFiles/" + newFileName + fileType; String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath() + "/MyFiles/" + newFileName + fileType; System.out.println("URL:" + url); BaseResponse<String> response = new BaseResponse<>(); response.setData(url); return response; } @PostMapping("/multi") public BaseResponse<List<String>> multi(@RequestParam("multiFile") MultipartFile[] files, HttpServletRequest req) throws IOException { List<String> urls = new ArrayList<>(); for (MultipartFile file : files){ String fileName = file.getOriginalFilename(); String fileType = fileName.substring(fileName.lastIndexOf(".")); String newFileName = new Date().getTime() + ""; String fileSize = FileUtils.byteCountToDisplaySize(file.getSize()); System.out.println("文件名:" + fileName); System.out.println("文件大小:" + fileSize); String path = req.getServletContext().getRealPath("/MyFiles/"); File targetFile = new File(path + newFileName + fileType); FileUtils.copyInputStreamToFile(file.getInputStream(), targetFile); String imgPath = targetFile.getPath(); System.out.println("保存路徑:" + imgPath); String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath() + "/MyFiles/" + newFileName + fileType; System.out.println("URL:" + url); urls.add(url); System.out.println("======================================="); } BaseResponse<List<String>> response = new BaseResponse<>(); response.setData(urls); return response; } }
最後你能夠配置上傳文件大小,在application.propertiesjquery
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MB
啓動項目:http://localhost:8080/ios
後臺打印:web
這種狀況不一樣於SpringMVC,我只能以我目前開發的狀況來講明。咱們是先後端分離的項目,前端用vue+vuetify+VueX+Axios,大概思路就是搞一個按鈕,當change事件發生就執行上傳操做。ajax
<v-btn dark small color="blue darken-1">上傳文件 <input type="file" id="pbFileInput" class="file-input" @change="uploadFile('pbFileInput')"/> </v-btn>
..spring
// id 爲文件域的id uploadFile: function(id){ let me = this; let formData = new window.FormData(); formData.append('file',document.querySelector('#'+id).files[0]) let options = { // 設置axios的參數 headers: { 'Content-Type': 'multipart/form-data' } } me.$store.state.axios.post('/upload',formData, options) .then(function (response) { let data = response.data; if (data.code == 0){ console.log(data); document.querySelector('#'+id).value = ''; // 解決上傳第二次不能選擇同一文件 } else{ console.log(data.msg) } }) .catch(function (error) { console.log(error); }); }
後端(這個是借鑑網友的代碼,還能夠用)
private static final String BASE_PATH = "/MyFiles/"; @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono<BaseResponse<String>> requestBodyFlux(@RequestPart("file") FilePart filePart) throws IOException { String base = BASE_PATH; // 存放在當前磁盤的根目錄 System.out.println(filePart.filename()); Path path = Paths.get(base); if (!Files.exists(path)){ Files.createDirectories(path); } Path file = Files.createFile(Paths.get(base + filePart.filename())); // 方法一 AsynchronousFileChannel channel = AsynchronousFileChannel.open(file, StandardOpenOption.WRITE); DataBufferUtils.write(filePart.content(), channel, 0) .doOnComplete(() -> { System.out.println("finish"); }) .subscribe(); // 方法二 // filePart.transferTo(file.toFile()); System.out.println(file.toString()); BaseResponse<String> response = new BaseResponse<>(); response.setData(filePart.filename()); // 把文件名傳回給前端 return Mono.just(response); }
填坑:網友的代碼也不是萬能的哦
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono<BaseResponse<String>> requestBodyFlux(@RequestPart("file") FilePart filePart, @RequestHeader("uniqueId") String uniqueId) throws IOException { String base = baseConfiguration.getPbUploadPath(); // 存放在當前磁盤的根目錄 if (StringUtils.isEmpty(uniqueId)){ uniqueId = randomNumber(); // 每一個pb協議有個獨立的文件夾名稱 } String filename = filePart.filename(); log.info("=======================上傳文件======================="); log.info(filename); log.info(uniqueId); Path path = Paths.get(org.apache.commons.lang3.StringUtils.appendIfMissing(base, "/") + uniqueId + "/"); if (!Files.exists(path)){ Files.createDirectories(path); } // 若是存在同名文件,先刪除 Path targetPath = Paths.get(org.apache.commons.lang3.StringUtils.appendIfMissing(base, "/") + uniqueId + "/" + filename); if (Files.exists(targetPath)){ boolean b = Files.deleteIfExists(targetPath); log.info("已存在同名文件:" + filename + ",先刪除:" + b); } // 再創建新的 Path tempFile = Files.createFile(targetPath); // 方法一 AsynchronousFileChannel channel = AsynchronousFileChannel.open(tempFile, StandardOpenOption.WRITE); DataBufferUtils.write(filePart.content(), channel, 0) .doOnComplete(() -> { log.info("文件寫入完畢..."); // 不關閉的話若是再上傳同一個文件,會報錯:java.nio.file.AccessDeniedException,由於資源被佔用,沒法刪除 log.info("文件流關閉..."); try { channel.close(); } catch (IOException e) { e.printStackTrace(); log.info("文件流關閉失敗..."); } }) .subscribe(); // 方法二 // filePart.transferTo(tempFile.toFile()); log.info(tempFile.toString()); log.info("=======================--------======================="); BaseResponse<String> response = new BaseResponse<>(); response.setData(filename + "," + uniqueId); // 把惟一id和文件名傳回給前端 return Mono.just(response); }
生成隨機文件夾名字
private String randomNumber(){ long time = new Date().getTime(); String s = time + ""; Random random = new Random(); for (int i = 0; i < 4; i++){ s += random.nextInt(10); } return s; }
我已經測試這種方式能夠行得通