MongoDB MongoTemplate GridFSBuckets存取文件

此文章转载自:http://t.zoukankan.com/three-fighter-p-12641771.html#25%E3%80%81%E6%9C%8D%E5%8A%A1%E5%B1%82
https://www.cnblogs.com/three-fighter/p/12641771.html

目录
一、MongoDB存储文件
1、MongoDB存储小文件
2、MongoDB存储大文件
2.1、GridFS存储原理
2.2、GridFS使用
2.2.1、使用shell命令
2.2.2、使用API
二、SpringBoot整合MongoDB存储文件
1、MongoDB存储小文件
1.1、添加依赖
1.2、配置
1.3、模型层
1.4、持久层
1.5、服务层
1.6、控制层
1.7、工具类
1.8、前端页面
1.9、运行效果
2、MongoDB存储大文件
2.1、依赖
2.2、启动类
2.3、配置
2.4、实体类
2.5、服务层
2.6、控制层
2.7、运行效果

一、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
	@ApiOperation("在线显示文件(预览)")
	public void serveFileOnline(@RequestParam("id") Integer id, HttpServletRequest request, HttpServletResponse response) throws IOException {

		InspectionReport inspectionReport = inspectionReportService.queryById(id);
		ProjectFileModel projectFileModel = projectFileModelDao.queryRecord(inspectionReport.getFileModelId());

		request.setCharacterEncoding("UTF-8");
		response.setContentType("application/pdf");
		OutputStream out = response.getOutputStream();
		out.write(projectFileModel.getContent().getData());
		out.flush();
		out.close();
	}

    /**
     * 上传
     */
    @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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值