SpringBoot學習筆記(十一:使用MongoDB存儲文件 )



@


感謝各位的閱讀。博主的這篇文章只是博主的學習筆記,有些問題博主也不太清楚,所以沒有解答,抱歉。 源碼比較亂,大家將就着看:https://gitee.com/fighter3/dairly-learn.git


一、MongoDB存儲文件


1、MongoDB存儲小文件

MongoDB是一個面向文檔的數據庫,使用BSON(Binary JSON:二進制JSON)格式來存儲數據。


BSON格式

在這里插入圖片描述


BSON支持在一個文檔中最多存儲16MB的二進制數據。如果存儲的是小於16M的文件,可以直接將文件轉換為二進制數據,以文檔形式存入集合。

Java中文件和二進制轉換也比較簡單:

  • 文件轉換為byte數組
public static byte[] fileToByte(File file) throws IOException{
    byte[] bytes = null;
    FileInputStream fis = null;
    try{
        fis = new FileInputStream(file);
        bytes = new bytes[(int) file.length()];
        fis.read(bytes);
    }catch(IOException e){
        e.printStackTrace();
        throw e;
    }finally{
        fis.close();
    }
    return bytes;
}
  • byte數組轉換為文件
  
  public static void bytesToFile(byte[] bFile, String fileDest) {

        FileOutputStream fileOuputStream = null;

        try {
            fileOuputStream = new FileOutputStream(fileDest);
            fileOuputStream.write(bFile);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileOuputStream != null) {
                try {
                    fileOuputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

如果是實現文件下載功能,可以把字節碼直接寫進流中。


2、MongoDB存儲大文件

MongoDB單個文檔的存儲限制是16M,如果要存儲大於16M的文件,就要用到MongoDB GridFS。

GridFS是Mongo的一個子模塊,使用GridFS可以基於MongoDB來持久存儲文件。並且支持分布式應用(文件分布存儲和讀取)。作為MongoDB中二進制數據存儲在數據庫中的解決方案,通常用來處理大文件。

GridFS不是MongoDB自身特性,只是一種將大型文件存儲在MongoDB的文件規范,所有官方支持的驅動均實現了GridFS規范。GridFS制定大文件在數據庫中如何處理,通過開發語言驅動來完成、通過API接口來存儲檢索大文件。


2.1、GridFS存儲原理

GridFS使用兩個集合(collection)存儲文件。一個集合是chunks, 用於存儲文件內容的二進制數據;一個集合是files,用於存儲文件的元數據。

GridFS會將兩個集合放在一個普通的buket中,並且這兩個集合使用buket的名字作為前綴。MongoDB的GridFs默認使用fs命名的buket存放兩個文件集合。因此存儲文件的兩個集合分別會命名為集合fs.files ,集合fs.chunks。

當把一個文件存儲到GridFS時,如果文件大於chunksize (每個chunk塊大小為256KB),會先將文件按照chunk的大小分割成多個chunk塊,最終將chunk塊的信息存儲在fs.chunks集合的多個文檔中。然后將文件信息存儲在fs.files集合的唯一一份文檔中。其中fs.chunks集合中多個文檔中的file_id字段對應fs.files集中文檔”_id”字段。

讀文件時,先根據查詢條件在files集合中找到對應的文檔,同時得到“_id”字段,再根據“_id”在chunks集合中查詢所有“files_id”等於“_id”的文檔。最后根據“n”字段順序讀取chunk的“data”字段數據,還原文件。


GridFS存儲過程

在這里插入圖片描述

fs.files 集合存儲文件的元數據,以類json格式文檔形式存儲。每在GridFS存儲一個文件,則會在fs.files集合中對應生成一個文檔。


fs.files集合中文檔的存儲內容

在這里插入圖片描述


fs.chunks 集合存儲文件文件內容的二進制數據,以類json格式文檔形式存儲。每在GridFS存儲一個文件,GridFS就會將文件內容按照chunksize大小(chunk容量為256k)分成多個文件塊,然后將文件塊按照類json格式存在.chunks集合中,每個文件塊對應fs.chunk集合中一個文檔。一個存儲文件會對應一到多個chunk文檔。


fs.chunks集合中文檔的存儲內容

在這里插入圖片描述


2.2、GridFS使用


2.2.1、使用shell命令

mongoDB提供mingofiles工具,可以使用命令行來操作GridFS。其實有四個主要命令,分別為:

  • put —存儲命令
  • get —獲取命令
  • list —列表命令
  • delete —刪除命令

操作實例:

  • 存儲文件
    向數據庫中存儲文件的格式:mongofiles -d 數據庫名字 -l "要上傳的文件的完整路徑名" put "上傳后的文件名"

在這里插入圖片描述 在filetest數據庫中就會多出2個集合,它們存儲了GridFS文件系統的所有文件信息,查詢這兩個集合就能看到上傳的文件的一些信息:

在這里插入圖片描述

  • 列出文件
    查看GridFS文件系統中所有文件:mongofiles -d 數據庫名字 list

在這里插入圖片描述

  • 獲取文件
    從GridFS文件系統中下載一個文件到本地:mongofiles -d 數據庫名字 -l "將文件保存在本地的完整路徑名" get "GridFS文件系統中的文件名" ,如果不寫-l以及后面的路徑參數,則保存到當前位置。

在這里插入圖片描述

  • 刪除文件
    刪除GridFS文件系統中的某個文件:mongofiles -d 數據庫名字 delete " 文件名 "

在這里插入圖片描述

2.2.2、使用API

MongoDB支持多種編程語言驅動。比如c、java、C#、nodeJs等。因此可以使用這些語言MongoDB驅動API操作,擴展GridFS。

以Java為例:

  • 依賴包和版本:
    org.mongodb:3.2.2
    mongo-java-driver:3.2.2

  • 公共方法

public MongoDatabase mongoDatabase() throws Exception{
    MongoClient mongoClient = new MongoClient(mongoHost, mongoPort);
    return mongoClient.getDatabase(dbName);
}
  • 上傳文件
@Test
public void upload() throws Exception {
    // 獲取文件流
    File file = new File("E:/zookeeper-3.4.8.tar.gz");
    InputStream in = new FileInputStream(file);
    // 創建一個容器,傳入一個`MongoDatabase`類實例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 上傳
    ObjectId fileId = bucket.uploadFromStream(UUID.randomUUID().toString(), in);
    System.out.println("上傳完成。 文件ID:"+fileId);
}
  • 查找文件
@Test
public void findOne() throws Exception {
    // 獲取文件ID
    String objectId = "57fbaffcec773716ecc54ef4";
    // 創建一個容器,傳入一個`MongoDatabase`類實例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 獲取內容
    GridFSFindIterable gridFSFindIterable = bucket.find(Filters.eq("_id", new ObjectId(objectId)));
    GridFSFile gridFSFile = gridFSFindIterable.first();
    System.out.println("filename: " + gridFSFile.getFilename());
}
  • 下載文件
@Test
public void download() throws Exception {
    // 獲取文件ID
    String objectId = "57fbaffcec773716ecc54ef4";
    // 獲取文件流
    File file = new File("D:/zookeeper-3.4.8.tar.gz");
    // 創建一個容器,傳入一個`MongoDatabase`類實例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 創建輸出流
    OutputStream os = new FileOutputStream(file);
    // 下載
    bucket.downloadToStream(new ObjectId(objectId), os);
    System.out.println("下載完成。");
}
  • 刪除文件
@Test
public void delete() throws Exception {
    // 獲取文件ID
    String objectId = "57fbaffcec773716ecc54ef4";
    // 創建一個容器,傳入一個`MongoDatabase`類實例db
    GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
    // 刪除
    bucket.delete(new ObjectId(objectId));
    System.out.println("刪除完成。");
}

二、SpringBoot整合MongoDB存儲文件

MongoDB可以將文件直接存儲在文檔或者通過GridFS存儲大文件,這里同樣進行SpringBoot整合MongoDB的兩種實現。


1、MongoDB存儲小文件

SpringBoot整合MongoDB將文件以文檔形式直接存入集合,和普通的MongDB存儲區別不大。

1.1、添加依賴

  • spring-boot-starter-data-mongodb:用來操作MongoDB
  • spring-boot-starter-thymeleaf:前端頁面采用thymeleaf模板
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <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>

1.2、配置

server.address=localhost
server.port=8081

# thymeleaf配置,開發環境不啟用緩存,正式環境下請啟用緩存,提高性能
spring.thymeleaf.cache=false
# thymeleaf對html元素格式要求嚴格,設置它的mode為HTML,忘記結束標簽后不會報錯
spring.thymeleaf.mode=HTML

# 編碼
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true

# MongoDB 配置
# 連接url
spring.data.mongodb.uri=mongodb://test:test@localhost:27017/filetest


# 文件上傳限制
spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=50MB

1.3、模型層

文件模型類:

/**
 * @Author 三分惡
 * @Date 2020/1/11
 * @Description 文檔類
 */
@Document
public class FileModel {

    @Id  // 主鍵
    private String id;
    private String name; // 文件名稱
    private String contentType; // 文件類型
    private long size;
    private Date uploadDate;
    private String md5;
    private Binary content; // 文件內容
    private String path; // 文件路徑

   /**
   *省略getter/setter
   */
    protected FileModel() {
    }


    public FileModel(String name, String contentType, long size,  Binary content) {
        this.name = name;
        this.contentType = contentType;
        this.size = size;
        this.content = content;
    }

  
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FileModel)) return false;
        FileModel fileModel = (FileModel) o;
        return size == fileModel.size &&
                Objects.equals(id, fileModel.id) &&
                Objects.equals(name, fileModel.name) &&
                Objects.equals(contentType, fileModel.contentType) &&
                Objects.equals(uploadDate, fileModel.uploadDate) &&
                Objects.equals(md5, fileModel.md5) &&
                Objects.equals(content, fileModel.content) &&
                Objects.equals(path, fileModel.path);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, contentType, size, uploadDate, md5, content, path);
    }
}

1.4、持久層

采用MongoRepository的方式操作MongoDB,只需在接口里繼承MongoRepository,就可以調用MongoRepository的內置方法。

public interface FileRepository extends MongoRepository<FileModel,String> {
}

1.5、服務層

服務層接口:

public interface FileService {
    /**
     * 保存文件
     */
    FileModel saveFile(FileModel file);

    /**
     * 刪除文件
     */
    void removeFile(String id);

    /**
     * 根據id獲取文件
     */
    Optional<FileModel> getFileById(String id);

    /**
     * 分頁查詢,按上傳時間降序
     * @return
     */
    List<FileModel> listFilesByPage(int pageIndex, int pageSize);
}

服務層實現類:

@Service
public class FileServiceImpl implements FileService {
    @Autowired
    private FileRepository fileRepository;

    @Override
    public FileModel saveFile(FileModel file) {
        return fileRepository.save(file);
    }

    @Override
    public void removeFile(String id) {
        fileRepository.deleteById(id);
    }

    @Override
    public Optional<FileModel> getFileById(String id) {
        return fileRepository.findById(id);
    }

    @Override
    public List<FileModel> listFilesByPage(int pageIndex, int pageSize) {
        Page<FileModel> page = null;
        List<FileModel> list = null;
        Sort sort = Sort.by(Sort.Direction.DESC,"uploadDate");
        Pageable pageable = PageRequest.of(pageIndex, pageSize, sort);
        page = fileRepository.findAll(pageable);
        list = page.getContent();
        return list;
    }
}

1.6、控制層

@Controller
public class FileController {
    @Autowired
    private FileService fileService;

    @Value("${server.address}")
    private String serverAddress;

    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/")
    public String index(Model model) {
        // 展示最新二十條數據
        model.addAttribute("files", fileService.listFilesByPage(0, 20));
        return "index";
    }

    /**
     * 分頁查詢文件
     */
    @GetMapping("files/{pageIndex}/{pageSize}")
    @ResponseBody
    public List<FileModel> listFilesByPage(@PathVariable int pageIndex, @PathVariable int pageSize) {
        return fileService.listFilesByPage(pageIndex, pageSize);
    }

    /**
     * 獲取文件片信息
     */
    @GetMapping("files/{id}")
    @ResponseBody
    public ResponseEntity<Object> serveFile(@RequestParam("id") String id) throws UnsupportedEncodingException {

        Optional<FileModel> file = fileService.getFileById(id);

        if (file.isPresent()) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=" + new String(file.get().getName().getBytes("utf-8"),"ISO-8859-1"))
                    .header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
                    .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
                    .body(file.get().getContent().getData());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
        }

    }

    /**
     * 在線顯示文件
     */
    @GetMapping("/view")
    @ResponseBody
    public ResponseEntity<Object> serveFileOnline(@RequestParam("id") String id) {

        Optional<FileModel> file = fileService.getFileById(id);

        if (file.isPresent()) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + file.get().getName() + "\"")
                    .header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())
                    .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
                    .body(file.get().getContent().getData());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
        }

    }

    /**
     * 上傳
     */
    @PostMapping("/")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

        try {
            FileModel f = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getSize(),
                    new Binary(file.getBytes()));
            f.setMd5(MD5Util.getMD5(file.getInputStream()));
            fileService.saveFile(f);
            System.out.println(f);
        } catch (IOException | NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            redirectAttributes.addFlashAttribute("message", "Your " + file.getOriginalFilename() + " is wrong!");
            return "redirect:/";
        }

        redirectAttributes.addFlashAttribute("message",
                "You successfully uploaded " + file.getOriginalFilename() + "!");

        return "redirect:/";
    }

    /**
     * 上傳接口
     */
    @PostMapping("/upload")
    @ResponseBody
    public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
        FileModel returnFile = null;
        try {
            FileModel f = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getSize(),
                    new Binary(file.getBytes()));
            f.setMd5(MD5Util.getMD5(file.getInputStream()));
            returnFile = fileService.saveFile(f);
            String path = "//" + serverAddress + ":" + serverPort + "/view/" + returnFile.getId();
            return ResponseEntity.status(HttpStatus.OK).body(path);

        } catch (IOException | NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
        }

    }

    /**
     * 刪除文件
     */
    @GetMapping("/delete")
    @ResponseBody
    public ResponseEntity<String> deleteFile( @RequestParam("id") String id) {

        try {
            fileService.removeFile(id);
            return ResponseEntity.status(HttpStatus.OK).body("DELETE Success!");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
    }
}

1.7、工具類

md5工具類:

public class MD5Util {
    /**
     * 獲取該輸入流的MD5值
     */
    public static String getMD5(InputStream is) throws NoSuchAlgorithmException, IOException {
        StringBuffer md5 = new StringBuffer();
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] dataBytes = new byte[1024];

        int nread = 0;
        while ((nread = is.read(dataBytes)) != -1) {
            md.update(dataBytes, 0, nread);
        };
        byte[] mdbytes = md.digest();

        // convert the byte to hex format
        for (int i = 0; i < mdbytes.length; i++) {
            md5.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        return md5.toString();
    }
}

1.8、前端頁面

前端頁面index.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<h1 style="text-align: center">文件服務</h1>
<br>
<div >
    <a href="/">首頁</a>
</div>

<br><br>
<div th:if="${message}" style="margin-left: 10%">
    <h2 th:text="${message}"/>
</div>

<br>
<div >
    <form method="POST" enctype="multipart/form-data" action="/">
        <table>
            <tr><td>上傳文件:</td><td><input type="file" name="file" /></td></tr>
            <tr><td></td><td><input type="submit" value="上傳" /></td></tr>
        </table>

    </form>
</div>
<br><br>

<div style="margin-left: 5%">
    <h3 style="text-align: center">文件列表</h3>
    <table border="1">
        <thead>
        <tr style="background-color: beige">
            <td>文件名</td>
            <td>文件ID</td>
            <td>contentType</td>
            <td>文件大小</td>
            <td>上傳時間</td>
            <td>md5</td>
            <td>操作</td>
        </tr>
        </thead>
        <tbody>
        <tr th:if="${files.size()} eq 0">
            <td colspan="3">沒有文件信息!!</td>
        </tr>
        <tr th:each="file : ${files}">
            <td><a th:href="'files/'+${file.id}" th:text="${file.name}" /></td>
            <td th:text="${file.id}" ></td>
            <td th:text="${file.contentType}" ></td>
            <td th:text="${file.size}" ></td>
            <td th:text="${file.uploadDate}" ></td>
            <td th:text="${file.md5}" ></td>
            <td><a target="_blank" th:href="@{/view(id=${file.id})}">預覽</a>|<a th:href="@{/delete(id=${file.id})}">刪除</a></td>
        </tr>
        </tbody>
    </table>
</div>

</body>

<style>
    body{
        text-align: center;
    }

    table{
        margin: auto;
    }

</style>


1.9、運行效果

  • 上傳文件:

在這里插入圖片描述

在這里插入圖片描述


  • 預覽
    在這里插入圖片描述

  • 下載

在這里插入圖片描述


  • 刪除
    在這里插入圖片描述

在文件的操作過程中,可以通過可視化工具或shell來查看存儲在MongoDB中的文件:

  • 可以看到,在fileModel集合中存儲了我們上傳的文件,文件的內容是以二進制的形式存儲

在這里插入圖片描述

2、MongoDB存儲大文件

Spring Data MongoDB提供了GridFsOperations接口以及相應的實現GridFsTemplate,可以和GridFs交互。

這里在上一個工程的基礎上進行改造。


2.1、依賴

和上一個工程相比,這里添加開源工具包hutool的依賴:

         <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.1</version>
        </dependency>

2.2、啟動類

@SpringBootApplication
public class SpringbootFileGridfsApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootFileGridfsApplication.class, args);
    }

    //Tomcat large file upload connection reset
    @Bean
    public TomcatServletWebServerFactory tomcatEmbedded() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
            if ((connector.getProtocolHandler() instanceof AbstractHttp11Protocol<
                    ?>)) {
                //-1 means unlimited
                ((AbstractHttp11Protocol<?>) connector.getProtocolHandler()).setMaxSwallowSize(-1);
            }
        });
        return tomcat;
    }
}

TomcatServletWebServerFactory() ⽅法主要是為了解決上傳文件較大時出現連接重置的問題,這個異常后台是捕捉不到的:

在這里插入圖片描述

2.3、配置

  • application.properties
# MongoDB 配置
# 連接uri
#spring.data.mongodb.uri=mongodb://test:test@localhost:27017/filetest
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=filetest
spring.data.mongodb.username=test
spring.data.mongodb.password=test

# 文件上傳限制
spring.servlet.multipart.max-file-size=1020MB
spring.servlet.multipart.max-request-size=1020MB
  • 配置類
/**
 * @Author 三分惡
 * @Date 2020/1/11
 * @Description
 */
@Configuration
public class MongoConfig {
    //獲取配置文件中數據庫信息
    @Value("${spring.data.mongodb.database}")
    String db;

    ////GridFSBucket用於打開下載流
    @Bean
    public GridFSBucket getGridFSBucket(MongoClient mongoClient){
        MongoDatabase mongoDatabase = mongoClient.getDatabase(db);
        GridFSBucket bucket = GridFSBuckets.create(mongoDatabase);
        return bucket;
    }
}

2.4、實體類

  • 文件實體類
/**
 * @Author 三分惡
 * @Date 2020/1/11
 * @Description
 */
@Document
public class FileDocument {
    @Id  // 主鍵
    private String id;
    private String name;        // 文件名稱
    private long size;          // 文件大小
    private Date uploadDate;    // 上傳時間
    private String md5;         // 文件MD5值
    private byte[] content;     // 文件內容
    private String contentType; // 文件類型
    private String suffix;      // 文件后綴名
    private String description; // 文件描述
    private String gridfsId;    // 大文件管理GridFS的ID
   
   /**
   * 省略getter、setter、equales、hashCode、toString方法
   */
}
  • 接口結果實體類
**
 * @Author 三分惡
 * @Date 2020/1/11
 * @Description 公用數據返回模型
 */
public class ResponseModel {
    public static final String Success = "success";
    public static final String Fail = "fail";

    private String code = "fail";
    private String message = "";
    private String data;

    //私有構造函數,此類不允許手動實例化,需要調用getInstance()獲取實例
    private ResponseModel() {
    }

    /**
     * 返回默認的實例
     * @return
     */
    public static ResponseModel getInstance() {
        ResponseModel model = new ResponseModel();
        model.setCode(ResponseModel.Fail);
        return model;
    }
   
   /**
   *省略getter/setter
   */

}

2.5、服務層

上一個實例里采用MongReposity來操作MongoDB,這里操作MongoDB采用MongoTemplate,操作GridFs采用GridFsTemplate。

/**
 * @Author 三分惡
 * @Date 2020/1/11
 * @Description
 */
@Service
public class FileServiceImpl implements FileService {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private GridFsTemplate gridFsTemplate;

    @Autowired
    private GridFSBucket gridFSBucket;


    /**
     * 保存文件
     * @param file
     * @return
     */
    @Override
    public FileDocument saveFile(FileDocument file) {
        file = mongoTemplate.save(file);
        return file;
    }

    /**
     * 上傳文件到Mongodb的GridFs中
     * @param in
     * @param contentType
     * @return
     */
    @Override
    public String uploadFileToGridFS(InputStream in , String contentType){
        String gridfsId = IdUtil.simpleUUID();
        //將文件存儲進GridFS中
        gridFsTemplate.store(in, gridfsId , contentType);
        return gridfsId;
    }


    /**
     * 刪除文件
     * @param id
     */
    @Override
    public void removeFile(String id) {
        //根據id查詢文件
        FileDocument fileDocument = mongoTemplate.findById(id , FileDocument.class );
        if(fileDocument!=null){
            //根據文件ID刪除fs.files和fs.chunks中的記錄
            Query deleteFileQuery = new Query().addCriteria(Criteria.where("filename").is(fileDocument.getGridfsId()));
            gridFsTemplate.delete(deleteFileQuery);
            //刪除集合fileDocment中的數據
            Query deleteQuery=new Query(Criteria.where("id").is(id));
            mongoTemplate.remove(deleteQuery,FileDocument.class);
        }
    }

    /**
     * 根據id查看文件
     * @param id
     * @return
     */
    @Override
    public Optional<FileDocument> getFileById(String id){
        FileDocument fileDocument = mongoTemplate.findById(id , FileDocument.class );
        if(fileDocument != null){
            Query gridQuery = new Query().addCriteria(Criteria.where("filename").is(fileDocument.getGridfsId()));
            try {
                //根據id查詢文件
                GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
                //打開流下載對象
                GridFSDownloadStream in = gridFSBucket.openDownloadStream(fsFile.getObjectId());
                if(in.getGridFSFile().getLength() > 0){
                    //獲取流對象
                    GridFsResource resource = new GridFsResource(fsFile, in);
                    //獲取數據
                    fileDocument.setContent(IoUtil.readBytes(resource.getInputStream()));
                    return Optional.of(fileDocument);
                }else {
                    fileDocument = null;
                    return Optional.empty();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
        return Optional.empty();
    }


    /**
     * 分頁列出文件
     * @param pageIndex
     * @param pageSize
     * @return
     */
    @Override
    public List<FileDocument> listFilesByPage(int pageIndex, int pageSize) {
        Query query = new Query().with(Sort.by(Sort.Direction.DESC, "uploadDate"));
        long skip = (pageIndex -1) * pageSize;
        query.skip(skip);
        query.limit(pageSize);
        Field field = query.fields();
        field.exclude("content");
        List<FileDocument> files = mongoTemplate.find(query , FileDocument.class );
        return files;

    }
}


2.6、控制層

控制層變動不大,主要是調用服務層方法的返回值和參數有變化:

@Controller
public class FileController {
    @Autowired
    private FileService fileService;

    @Value("${server.address}")
    private String serverAddress;

    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/")
    public String index(Model model) {
        // 展示最新二十條數據
        model.addAttribute("files", fileService.listFilesByPage(0, 20));
        return "index";
    }

    /**
     * 分頁查詢文件
     */
    @GetMapping("files/{pageIndex}/{pageSize}")
    @ResponseBody
    public List<FileDocument> listFilesByPage(@PathVariable int pageIndex, @PathVariable int pageSize) {
        return fileService.listFilesByPage(pageIndex, pageSize);
    }

    /**
     * 獲取文件片信息
     */
    @GetMapping("files/{id}")
    @ResponseBody
    public ResponseEntity<Object> serveFile(@PathVariable String id) throws UnsupportedEncodingException {

        Optional<FileDocument> file = fileService.getFileById(id);

        if (file.isPresent()) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=" + new String(file.get().getName().getBytes("utf-8"),"ISO-8859-1"))
                    .header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
                    .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
                    .body(file.get().getContent());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
        }

    }

    /**
     * 在線顯示文件
     */
    @GetMapping("/view")
    @ResponseBody
    public ResponseEntity<Object> serveFileOnline(@RequestParam("id") String id) {
        Optional<FileDocument> file = fileService.getFileById(id);
        if (file.isPresent()) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=" + file.get().getName())
                    .header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())
                    .header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
                    .header(HttpHeaders.CONTENT_LENGTH , file.get().getSize() + "")
                    .body(file.get().getContent());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not found");
        }


    }

    /**
     * 上傳
     */
    @PostMapping("/")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

        try {
            FileDocument fileDocument = new FileDocument();
            fileDocument.setName(file.getOriginalFilename());
            fileDocument.setSize(file.getSize());
            fileDocument.setContentType(file.getContentType());
            fileDocument.setUploadDate(new Date());
            String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
            fileDocument.setSuffix(suffix);
            fileDocument.setMd5(MD5Util.getMD5(file.getInputStream()));
            //將文件存入gridFs
            String gridfsId = fileService.uploadFileToGridFS(file.getInputStream() , file.getContentType());
            fileDocument.setGridfsId(gridfsId);
            fileDocument = fileService.saveFile(fileDocument);
            System.out.println(fileDocument);
        } catch (IOException | NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            redirectAttributes.addFlashAttribute("message", "Your " + file.getOriginalFilename() + " is wrong!");
            return "redirect:/";
        }

        redirectAttributes.addFlashAttribute("message",
                "You successfully uploaded " + file.getOriginalFilename() + "!");

        return "redirect:/";
    }

    /**
     * 上傳接口
     */
    @PostMapping("/upload")
    @ResponseBody
    public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
        FileDocument returnFile = null;
        try {
            FileDocument fileDocument = new FileDocument();
            fileDocument.setName(file.getOriginalFilename());
            fileDocument.setSize(file.getSize());
            fileDocument.setContentType(file.getContentType());
            fileDocument.setUploadDate(new Date());
            String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
            fileDocument.setSuffix(suffix);
            fileDocument.setMd5(MD5Util.getMD5(file.getInputStream()));
            //將文件存入gridFs
            String gridfsId = fileService.uploadFileToGridFS(file.getInputStream() , file.getContentType());
            fileDocument.setGridfsId(gridfsId);
            returnFile = fileService.saveFile(fileDocument);
            String path = "//" + serverAddress + ":" + serverPort + "/view/" + returnFile.getId();
            return ResponseEntity.status(HttpStatus.OK).body(path);

        } catch (IOException | NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
        }

    }

    /**
     * 刪除文件
     */
    @GetMapping("/delete")
    @ResponseBody
    public ResponseModel deleteFile( @RequestParam("id") String id) {
        ResponseModel model = ResponseModel.getInstance();
        if(!StrUtil.isEmpty(id)){
            fileService.removeFile(id);
            model.setCode(ResponseModel.Success);
            model.setMessage("刪除成功");
        }else {
            model.setMessage("請傳入文件id");
        }
        return model;
    }
}



  • 前端頁面沒有變動。

2.7、運行效果

  • 上傳文件
    這里我們選擇一個比較大的mp4文件
    在這里插入圖片描述在這里插入圖片描述

  • 預覽
    預覽還存在問題,后台會報錯:org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class edu.hpu.domain.ResponseModel] with preset Content-Type 'video/mp4'
    待解決

在這里插入圖片描述

  • 下載
    在這里插入圖片描述

  • 刪除
    在這里插入圖片描述


在上傳和刪除數據的過程中,可以通過可視化工具或shell來查看MongoDB中的數據

  • fileDocment中的數據:fileDocment是一個普通的集合,對應地以文檔的形式存儲了FileDocument實例

在這里插入圖片描述

  • fs.files中的數據:文件的元數據
    在這里插入圖片描述

  • fs.chunks中的數據:file被切分成若干個chunks,存儲了文件的二進制數據
    在這里插入圖片描述





本文為學習筆記類博客,學習資料來源見參考!



【1】:MongoDB GridFS
【2】:Mongodb的文件存儲GridFs
【3】:MongoDB學習筆記(五) MongoDB文件存取操作
【4】:《MongoDB大數據權威處理指南》
【5】:java文件轉二進制
【6】:Java將文件轉為字節數組
【7】:java文件下載的幾種方式
【8】:文件和byte數組之間相互轉換
【9】:關於知名數據庫MongoDB,有個功能你不可不知!
【10】:MongoDB 學習筆記(五):固定集合、GridFS文件系統與服務器端腳本
【11】:GridFS 基於 MongoDB 的分布式文件存儲系統
【12】:SpringBoot Mongodb文件存儲服務器
【13】:MongoDB文件服務器搭建
【14】:基於 MongoDB 及 Spring Boot 的文件服務器的實現
【15】:SpringBoot中使用GridFS
【16】:SpringBoot2.x集成mongoDB4.0實現音頻文件的上傳下載功能
【17】:10.18. GridFS Support
【18】:GridFS in Spring Data MongoDB
【19】:純潔的微笑 《精通SpringBoot 42講》
【20】:JAVA 應用 / hutool / hutool系列教材 (一)- 介紹 - 簡介


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM