CKEditor 5 + SpringBoot實戰(四):SpringBoot 實現文件上傳

在本系列的文章中,我將介紹如何在Spring Boot Application中使用CKEditor編輯器。介紹的內容包括基本環境的搭建,文件上傳,SpringData JPA數據持久化,CKEditor5的安裝,CKEditor圖片上傳,CKEditor插入視頻,獲取/設置CKEditor內容等。

在本章節中,主要介紹的內容是在SpringBoot中實現圖片上傳功能,其中包括上傳路徑的設置,圖片路徑映射和上傳業務代碼的編寫。

文件上傳

所謂的文件上傳就是將客戶端的資源通過網絡傳輸到服務端,其本質就是IO流操作。服務端通過IO流讀取客戶端數據,然後對數據進行解析,獲取目標文件數據後,將數據存儲到服務端磁盤中。

引入依賴

要實現文件上傳,首先需要將所需要的依賴包導入到項目中。這裏我們僅導入commons-fileupload和commons-io依賴包。通常,commons-fileupload依賴需要和commons-io一起搭配使用,其中封裝了大量的用於操作文件上傳的功能,可以幫助我們簡化文件上傳代碼的編寫。打開pom.xml配置文件,並加入如下的配置:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

存儲路徑

上面我們提到,文件上傳的本質就是通過IO流將客戶端數據存儲到服務端的磁盤上。因此,我們需要在服務端的磁盤上規劃一個存儲空間,用於存儲客戶端上傳的文件。這裏我將客戶端上傳的文件存儲到當前項目的類路徑下的images文件夾中。

首先,在com.ramostear.ckeditor.common包中創建一個Consts類,然後通過ClassPathResource類獲得當前項目的類路徑。代碼如下:

package com.ramostear.ckeditor.common;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;

public class Consts {

    public static String FILE_STORAGE_ROOT = getRootPath();

    private static String getRootPath(){
        try {
            Resource resource = new ClassPathResource("");
            return resource.getFile().getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在代碼中,獲取到的類路徑賦值給了公有靜態常量 FILE_STORAGE_ROOT,該常量將在圖片路徑映射和文件上傳業務代碼中被使用。

上傳配置

一般情況下,客戶端的數據可以通過Form表單傳遞到服務端,但如果是圖片等大文件數據,服務端將無法直接解析這些數據(如果是圖片文件,也可以將圖片通過Base64轉碼後,將獲得的二進制文本傳遞到服務端),因此,我們需要配置一個專門用於解析客戶端上傳文件的解析器,另外,如果上傳的是圖片文件,通常需要在上傳成功後顯示這些圖片,在SpringBoot中,客戶端是無法直接去加載這些文件的,這就要求我們還得手動去映射圖片地址。

下面,將介紹如何在SpringBoot中配置文件上傳的相關參數。最簡單的配置方式是配置類繼承 WebMvcConfigurationSupport類,然後重寫其中對應的方法。首先是文件解析器,這裏我們實例化一個CommonsMultipartResolver來作爲上傳文件的解析器,然後設置其允許上傳文件的大小和文件的字符編碼,代碼如下:

@Bean
public CommonsMultipartResolver multipartResolver(){
    CommonsMultipartResolver resolver = new CommonsMultipartResolver();
    resolver.setMaxUploadSize(600000000);
    resolver.setDefaultEncoding("UTF-8");
    return resolver;
}

提示:

在代碼中,setMaxUploadSize()方法用於限制上傳文件大小的最大值,單位爲字節,如果你不想對上傳文件做大小限制,可將值設置爲-1,默認值也是-1。

最後,爲例能夠在客戶端瀏覽器中顯示上傳的圖片,我們還需要覆蓋WebMvcConfigurationSupport類中的addResourceHandlers()方法,然後對我們定義的圖片上傳路徑做映射。代碼如下:

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/**")
        .addResourceLocations("classpath:/static/")
        .addResourceLocations("file:"+ Consts.FILE_STORAGE_ROOT+"/images");
    super.addResourceHandlers(registry);
}

在上述代碼中,將”classpath:/static/“和文件上傳路徑下的圖片地址映射爲”/**”,例如,上傳到服務器的圖片存儲路徑爲”C:/ckeditor5-springboot/images/demo.png”,那麼該圖片的在客戶端瀏覽器中的請求地址爲:”http://localhost:8080/demo.png"。完整的配置類如下:

package com.ramostear.ckeditor.config;

import com.ramostear.ckeditor.common.Consts;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class MvcConfiguration extends WebMvcConfigurationSupport {

    @Bean
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(600000000);
        resolver.setDefaultEncoding("UTF-8");
        return resolver;
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .addResourceLocations("file:"+ Consts.FILE_STORAGE_ROOT+"/images/");
        super.addResourceHandlers(registry);
    }
}

提示:

別忘記添加配置類的@Configuration註解以及配置方法上的@Bean註解。

實現文件上傳

以上環節都準備好後,我們將要實現服務端獲取客戶端上傳文件,並保存到磁盤中的功能。首先,定義一個FileManager接口類,並提供文件上傳和刪除文件方法,代碼如下:

package com.ramostear.ckeditor.service;

import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.util.Collection;

public interface FileManager {

    String upload(CommonsMultipartFile multipartFile);

    boolean remove(String url);

    void remove(Collection<String> urls);
}

upload()方法用於將客戶端上傳的文件寫入到指定的上傳目錄中,remove()方法在刪除內容時被調用。接下來,在FileManagerImpl.java類中實現上述三個方法。

upload

對於文件上傳,首先得獲取文件存儲的路徑,然後將上傳文件重命名,最後將上傳文件數據寫入到目標文件中,文件的讀寫我們將使用CommonsMultipartFile提供的transferTo()方法來實現,代碼如下:

@Override
public String upload(CommonsMultipartFile multipartFile) {
    String storageRoot = Consts.FILE_STORAGE_ROOT+ File.separator+"images";
    String path = "";
    String suffix = Objects.requireNonNull(multipartFile.getOriginalFilename())
        .substring(multipartFile.getOriginalFilename().lastIndexOf("."));
    String fileName = SIMPLE_DATE_FORMAT.format(new Date())+"-"
        + UUID.randomUUID().toString().replaceAll("-","").toLowerCase()
        + suffix;
    File file = new File(storageRoot+File.separator+fileName);
    if(!file.getParentFile().exists()){
        file.getParentFile().mkdirs();
    }
    try {
        multipartFile.transferTo(file);
        path = "/"+fileName;
    }catch (IOException e){
        e.printStackTrace();
    }
    return path;
}

文件上傳成功後,我們需要將文件的相對地址返回到上一層。

remove

刪除文件的方法比較簡單,在上傳目錄中查找傳入的文件,如果找到,則刪除該文件,反之則不做任何操作。代碼如下:

@Override
public boolean remove(String url) {
    String path = Consts.FILE_STORAGE_ROOT+ File.separator+"images"+url;
    File file = new File(path);
    if(file.exists() && file.isFile()){
        return file.delete();
    }
    return false;
}

FileManagerImpl.java類的完整代碼如下:

package com.ramostear.ckeditor.service.impl;

import com.ramostear.ckeditor.common.Consts;
import com.ramostear.ckeditor.service.FileManager;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;

@Service(value = "fileManager")
public class FileManagerImpl implements FileManager {
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");

    @Override
    public String upload(CommonsMultipartFile multipartFile) {
        String storageRoot = Consts.FILE_STORAGE_ROOT+ File.separator+"images";
        String path = "";
        String suffix = Objects.requireNonNull(multipartFile.getOriginalFilename())
                .substring(multipartFile.getOriginalFilename().lastIndexOf("."));
        String fileName = SIMPLE_DATE_FORMAT.format(new Date())+"-"
                + UUID.randomUUID().toString().replaceAll("-","").toLowerCase()
                + suffix;
        File file = new File(storageRoot+File.separator+fileName);
        if(!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }
        try {
            multipartFile.transferTo(file);
            path = "/"+fileName;
        }catch (IOException e){
            e.printStackTrace();
        }
        return path;
    }

    @Override
    public boolean remove(String url) {
        String path = Consts.FILE_STORAGE_ROOT+ File.separator+"images"+url;
        File file = new File(path);
        if(file.exists() && file.isFile()){
            return file.delete();
        }
        return false;
    }

    @Override
    public void remove(Collection<String> urls) {
        if(!CollectionUtils.isEmpty(urls)){
            urls.stream().allMatch(this::remove);
        }
    }
}

提示:

別忘記在實現類上添加@Service註解。

上傳控制器

最後,我們通過Spring MVC來實現圖片上傳控制器。根據查閱官網相關API可知,CKEditor是通過POST請求的方式將名爲“upload”的文件提交到後臺,後臺將數據處理完成後,需要返回如下格式的數據:

{
    "uploaded":true,
    "filename":"filename",
    "url":'url'
}

當圖片上傳成功後,uploaded的值爲true,反之爲false。

下面是上傳圖片控制器的詳細代碼:

package com.ramostear.ckeditor.controller;

import com.alibaba.fastjson.JSONObject;
import com.ramostear.ckeditor.service.FileManager;
import org.apache.commons.lang3.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.commons.CommonsMultipartFile;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/upload")
public class FileUploadController {

    private final FileManager fileManager;

    FileUploadController(FileManager fileManager){
        this.fileManager = fileManager;
    }

    @PostMapping("/image")
    public JSONObject image(@RequestParam(name = "upload")CommonsMultipartFile file){
        JSONObject json = new JSONObject();
        if(file == null || file.isEmpty()){
            json.put("uploaded",false);
            json.put("url","");
            return json;
        }
        if(StringUtils.isBlank(file.getOriginalFilename()) || !isAllow(file.getOriginalFilename())){
            json.put("uploaded",false);
            json.put("url","");
            return json;
        }
        String url = fileManager.upload(file);
        if(StringUtils.isBlank(url)){
            json.put("uploaded",false);
            json.put("url","");
            return json;
        }else{
            json.put("uploaded",true);
            json.put("url",url);
            return json;
        }
    }

    private boolean isAllow(String fileName){
        String[] allowFiles = {".gif",".png",".jpg",".jpeg",".bpm",".svg"};
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        List<String> suffixList = Arrays.stream(allowFiles).collect(Collectors.toList());
        return suffixList.contains(suffix);
    }
}

其中,isAllow()方法用於限定上傳的文件只能是圖片類型,在FileUploadController類中,使用基於構造方法注入的方式將FileManager實例化對象注入到其中,並完成圖片上傳操作。另外,在該類中使用到了StringUtils工具類和Alibaba FastJson類,因此,你還需要向pom.xml文件中添加如下的依賴:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.57</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

上傳測試

由於目前還未搭建前端頁面,因此使用瀏覽器來測試文件上傳功能,但我們可以藉助Postman來測試文件上傳功能。打開Postman軟件,然後新建一個請求,請求方式爲POST,接着在地址欄輸入“http://localhost:8080/upload/image ” ;最後,在Body選項欄下,數據提交格式爲form-data,KEY的值爲“upload”,類型爲”file”,VALUE爲待上傳的圖片。配置完成後點擊”Send”按鈕進行測試。測試流程如下圖:

圖片上傳成功後,我們可以在項目的target/classes/images/目錄下查看到剛剛上傳的圖片“20200707-3edd0218d4194f4f803e710573f07390.png”,如下圖:

除此之外,我們開可以在瀏覽器中輸入“ http://localhost:8080/20200707-3edd0218d4194f4f803e710573f07390.png ”來訪問剛纔上傳的圖片,如下圖:

本章小結

在本章內容中,詳細介紹了使用SpringBoot實現文件上傳的詳細過程,並使用Postman工具對文件上傳功能做了測試。在下一章中,將介紹SpringBoot和Freemaker模板引擎的整合。

未經作者允許,請勿擅自轉載,轉載請註明文章作者和出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章