SpringBoot實現檔案上傳功能詳解

天喬巴夏丶發表於2020-11-13

利用SpirngBoot實現檔案上傳功能

零、本篇要點

  • 介紹SpringBoot對檔案上傳的自動配置。
  • 介紹MultipartFile介面。
  • 介紹SpringBoot+Thymeleaf檔案上傳demo的整合。
  • 介紹對檔案型別,檔名長度等判斷方法。

一、SpringBoot對檔案處理相關自動配置

自動配置是SpringBoot為我們提供的便利之一,開發者可以在不作任何配置的情況下,使用SpringBoot提供的預設設定,如處理檔案需要的MultipartResolver。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {

	private final MultipartProperties multipartProperties;

	public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
		this.multipartProperties = multipartProperties;
	}

	@Bean
	@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
	public MultipartConfigElement multipartConfigElement() {
		return this.multipartProperties.createMultipartConfig();
	}

	@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
	@ConditionalOnMissingBean(MultipartResolver.class)
	public StandardServletMultipartResolver multipartResolver() {
		StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
		multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
		return multipartResolver;
	}

}
  • Spring3.1之後支援StandardServletMultipartResolver,且預設使用StandardServletMultipartResolver,它的優點在於:使用Servlet所提供的功能支援,不需要依賴任何其他的專案。
  • 想要自動配置生效,需要配置spring.servlet.multipart.enabled=true,當然這個配置預設就是true。
  • 相關的配置設定在MultipartProperties中,其中欄位就是對應的屬性設定,經典欄位有:
    • enabled:是否開啟檔案上傳自動配置,預設開啟。
    • location:上傳檔案的臨時目錄。
    • maxFileSize:最大檔案大小,以位元組為單位,預設為1M。
    • maxRequestSize:整個請求的最大容量,預設為10M。
    • fileSizeThreshold:檔案大小達到該閾值,將寫入臨時目錄,預設為0,即所有檔案都會直接寫入磁碟臨時檔案中。
    • resolveLazily:是否惰性處理請求,預設為false。
  • 我們也可以自定義處理的細節,需要實現MultipartResolver介面。

二、處理上傳檔案MultipartFile介面

SpringBoot為我們提供了MultipartFile強大介面,讓我們能夠獲取上傳檔案的詳細資訊,如原始檔名,內容型別等等,介面內容如下:

public interface MultipartFile extends InputStreamSource {
    String getName(); //獲取引數名
    @Nullable
    String getOriginalFilename();//原始的檔名
    @Nullable
    String getContentType();//內容型別
    boolean isEmpty();
    long getSize(); //大小
    byte[] getBytes() throws IOException;// 獲取位元組陣列
    InputStream getInputStream() throws IOException;//以流方式進行讀取
    default Resource getResource() {
        return new MultipartFileResource(this);
    }
    // 將上傳的檔案寫入檔案系統
    void transferTo(File var1) throws IOException, IllegalStateException;
	// 寫入指定path
    default void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
    }
}

三、SpringBoot+Thymeleaf整合demo

1、編寫控制器

/**
 * 檔案上傳
 *
 * @author Summerday
 */
@Controller
public class FileUploadController {

    private static final String UPLOADED_FOLDER = System.getProperty("user.dir");

    @GetMapping("/")
    public String index() {
        return "file";
    }

    @PostMapping("/upload")
    public String singleFileUpload(@RequestParam("file") MultipartFile file,
                                   RedirectAttributes redirectAttributes) throws IOException {

        if (file.isEmpty()) {
            redirectAttributes.addFlashAttribute("msg", "檔案為空,請選擇你的檔案上傳");
            return "redirect:uploadStatus";

        }
        saveFile(file);
        redirectAttributes.addFlashAttribute("msg", "上傳檔案" + file.getOriginalFilename() + "成功");
        redirectAttributes.addFlashAttribute("url", "/upload/" + file.getOriginalFilename());
        return "redirect:uploadStatus";
    }

    private void saveFile(MultipartFile file) throws IOException {
        Path path = Paths.get(UPLOADED_FOLDER + "/" + file.getOriginalFilename());
        file.transferTo(path);
    }

    @GetMapping("/uploadStatus")
    public String uploadStatus() {
        return "uploadStatus";
    }
}

2、編寫頁面file.html

<html xmlns:th="http://www.thymeleaf.org">
    <!--suppress ALL-->
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>檔案上傳介面</title>
    </head>
    <body>
        <div>
            <form method="POST" enctype="multipart/form-data" action="/upload">
                <table>
                    <tr><td><input type="file" name="file" /></td></tr>
                    <tr><td></td><td><input type="submit" value="上傳" /></td></tr>
                </table>
            </form>

        </div>
    </body>
</html>

3、編寫頁面uploadStatus.html

<!--suppress ALL-->
<html xmlns:th="http://www.thymeleaf.org">

<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>檔案上傳介面</title>
    </head>
    <body>
        <div th:if="${msg}">
            <h2 th:text="${msg}"/>
        </div>
        <div >
            <img src="" th:src="${url}" alt="">
        </div>
    </body>
</html>

4、編寫配置

server.port=8081
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

5、配置虛擬路徑對映

這一步是非常重要的,我們將檔案上傳到伺服器上時,我們需要將我們的請求路徑和伺服器上的路徑進行對應,不然很有可能檔案上傳成功,但訪問失敗:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    private static final String UPLOADED_FOLDER = System.getProperty("user.dir");

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/upload/**")
                .addResourceLocations("file:///" + UPLOADED_FOLDER + "/");
    }
}

對應關係需要自己去定義,如果訪問失敗,可以試著列印以下路徑,看看是否缺失了路徑分隔符。

注意:如果addResourceHandler不要寫成處理/**,這樣會攔截掉其他的請求

6、測試頁面

執行mvn spring-boot:run,啟動程式,訪問http://localhost:8081/,選擇檔案,點選上傳按鈕,我們的專案目錄下出現了mongo.jpg,並且頁面也成功顯示:

四、SpringBoot的Restful風格,返回url

/**
 * 檔案上傳
 *
 * @author Summerday
 */
@RestController
public class FileUploadRestController {

    /**
     * 檔名長度
     */
    private static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 允許的檔案型別
     */
    private static final String[] ALLOWED_EXTENSIONS = {
            "jpg", "img", "png", "gif"
    };

    /**
     * 專案路徑
     */
    private static final String UPLOADED_FOLDER = System.getProperty("user.dir");

    @PostMapping("/restUpload")
    public Map<String,Object> singleFileUpload(@RequestParam("file") MultipartFile file) throws Exception {

        if (file.isEmpty()) {
            throw new Exception("檔案為空!");
        }
        String filename = upload(file);
        String url = "/upload/" + filename;
        Map<String,Object> map = new HashMap<>(2);
        map.put("msg","上傳成功");
        map.put("url",url);
        return map;
    }


    /**
     * 上傳方法
     */
    private String upload(MultipartFile file) throws Exception {
        int len = file.getOriginalFilename().length();
        if (len > DEFAULT_FILE_NAME_LENGTH) {
            throw new Exception("檔名超出限制!");
        }
        String extension = getExtension(file);
        if(!isValidExtension(extension)){
            throw new Exception("檔案格式不正確");
        }
        // 自定義檔名
        String filename = getPathName(file);
        // 獲取file物件
        File desc = getFile(filename);
        // 寫入file
        file.transferTo(desc);
        return filename;
    }

    /**
     * 獲取file物件
     */
    private File getFile(String filename) throws IOException {
        File file = new File(UPLOADED_FOLDER + "/" + filename);
        if(!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }
        if(!file.exists()){
            file.createNewFile();
        }
        return file;
    }

    /**
     * 驗證檔案型別是否正確
     */
    private boolean isValidExtension(String extension) {
        for (String allowedExtension : ALLOWED_EXTENSIONS) {
            if(extension.equalsIgnoreCase(allowedExtension)){
                return true;
            }
        }
        return false;
    }

    /**
     * 此處自定義檔名,uuid + extension
     */
    private String getPathName(MultipartFile file) {
        String extension = getExtension(file);
        return UUID.randomUUID().toString() + "." + extension;
    }

    /**
     * 獲取副檔名
     */
    private String getExtension(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        return originalFilename.substring(originalFilename.lastIndexOf('.') + 1);
    }
}

五、原始碼下載

本文內容均為對優秀部落格及官方文件總結而得,原文地址均已在文中參考閱讀處標註。最後,文中的程式碼樣例已經全部上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn,另有其他SpringBoot的整合哦。

六、參考閱讀

相關文章