ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

SpringBoot之MongoDB附件操作

2022-07-11 11:33:38  阅读:169  来源: 互联网

标签:文件 return String MongoDB file 附件 fileId public SpringBoot


前言

近期自己针对附件上传进一步学习,为了弥足项目中文件上传的漏洞,保证文件上传功能的健壮性和可用性,现在我将自己在这一块的心得总结如下:

一、pom.xml依赖的引入

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- mongodb -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

二.application.yml配置信息

server:
  port: 31091

spring:
  servlet:
    multipart:
      max-file-size: 100MB
  data:
    mongodb:
      host: localhost
      port: 27017
      database: feng

fileUploadService:
  impl: fileMongoServiceImpl

 三、MongoDB配置类

/**
 * @Description MongoDB配置类
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
@Configuration
public class MongoConfig {
    /**
     * 数据库配置信息
     */
    @Value("${spring.data.mongodb.database}")
    private String db;

    /**
     * GridFSBucket用于打开下载流
     * @param mongoClient
     * @return
     */
    @Bean
    public GridFSBucket getGridFSBucket(MongoClient mongoClient){
        MongoDatabase mongoDatabase = mongoClient.getDatabase(db);
        return GridFSBuckets.create(mongoDatabase);
    }
}

 四、MongoDB文件实体

/**
 * @Description MongoDB文件实体
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
@Document
@Builder
@Data
public class MongoFile {

    /**
     * 主键
     */
    @Id
    public String id;

    /**
     * 文件名称
     */
    public String fileName;

    /**
     * 文件大小
     */
    public long fileSize;

    /**
     * 上传时间
     */
    public Date uploadDate;

    /**
     * MD5值
     */
    public String md5;

    /**
     * 文件内容
     */
    private Binary content;

    /**
     * 文件类型
     */
    public String contentType;

    /**
     * 文件后缀名
     */
    public String suffix;

    /**
     * 文件描述
     */
    public String description;

    /**
     * 大文件管理GridFS的ID
     */
    private String gridFsId;

}

 五、返回统一消息处理类

/**
 * @Description 统一消息
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
public class ResponseMessage<T> {

    private String status;
    private String message;
    private T data;

    public static ResponseMessage<?> ok() {
        return create("0", (String)null, (Object)null);
    }

    public static ResponseMessage<?> ok(String message) {
        return create("0", message, (Object)null);
    }

    public static <T> ResponseMessage<T> ok(String message, T data) {
        return create("0", message, data);
    }

    public static <T> ResponseMessage<T> ok(T data) {
        return create("0", (String)null, data);
    }

    public static ResponseMessage<?> error() {
        return create("1", (String)null, (Object)null);
    }

    public static ResponseMessage<?> error(String message) {
        return create("1", message, (Object)null);
    }

    public static <T> ResponseMessage<T> error(String message, T data) {
        return create("1", message, data);
    }

    private static <T> ResponseMessage<T> create(String status, String message, T data) {
        ResponseMessage<T> t = new ResponseMessage();
        t.setStatus(status);
        t.setMessage(message);
        t.setData(data);
        return t;
    }

    public ResponseMessage() {
    }

    public String getStatus() {
        return this.status;
    }

    public String getMessage() {
        return this.message;
    }

    public T getData() {
        return this.data;
    }

    public void setStatus(final String status) {
        this.status = status;
    }

    public void setMessage(final String message) {
        this.message = message;
    }

    public void setData(final T data) {
        this.data = data;
    }

}

六、统一文件下载vo

/**
 * @Description 统一文件下载vo
 * @author songwp
 * @date Apr 8, 2022
 * @version 1.0
 */
@Data
public class FileExportVo {

    private String fileId;

    private String fileName;

    private String contentType;

    private String suffix;

    private long fileSize;

    @JsonIgnore
    private byte[] data;

    public FileExportVo(MongoFile mongoFile) {
        BeanUtil.copyProperties(mongoFile, this);
        if (Objects.nonNull(mongoFile.getContent())) {
            this.data = mongoFile.getContent().getData();
        }
        this.fileId = mongoFile.getId();
    }

}

 七、MD5工具类

/**
 * @Description MD5工具类
 * @date Apr 8, 2022
 * @version 1.0
 */
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();
    }
}

八、MongoDB文件仓储

/**
 * @Description MongoDB文件仓储
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
public interface MongoFileRepository extends MongoRepository<MongoFile, String> {

九、文件上传业务接口

/**
 * @Description 文件上传接口
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
public interface FileUploadService {

    /**
     * 文件上传
     * @param file
     * @return
     */
    FileExportVo uploadFile(MultipartFile file) throws Exception;

    /**
     * 多文件上传
     * @param files
     * @return
     */
    List<FileExportVo> uploadFiles(List<MultipartFile> files);

    /**
     * 文件下载
     * @param fileId
     * @return
     */
    FileExportVo downloadFile(String fileId);

    /**
     * 文件删除
     * @param fileId
     */
    void removeFile(String fileId);

}

十、MongoDB文件上传实现类

/**
 * @Description MongoDB文件上传实现类
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
@Slf4j
@Service("fileMongoServiceImpl")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class FileMongoServiceImpl implements FileUploadService {

    private final MongoFileRepository mongoFileRepository;
    private final MongoTemplate mongoTemplate;
    private final GridFsTemplate gridFsTemplate;
    private final GridFSBucket gridFSBucket;

    /**
     * 多文件上传
     * @param files
     * @return
     */
    @Override
    public List<FileExportVo> uploadFiles(List<MultipartFile> files) {

        return files.stream().map(file -> {
            try {
                return this.uploadFile(file);
            } catch (Exception e) {
                log.error("文件上传失败", e);
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 文件上传
     * @param file
     * @return
     * @throws Exception
     */
    @Override
    public FileExportVo uploadFile(MultipartFile file) throws Exception {
        if (file.getSize() > 16777216) {
            return this.saveGridFsFile(file);
        } else {
            return this.saveBinaryFile(file);
        }
    }

    /**
     * 文件下载
     * @param fileId
     * @return
     */
    @Override
    public FileExportVo downloadFile(String fileId) {
        Optional<MongoFile> option = this.getBinaryFileById(fileId);

        if (option.isPresent()) {
            MongoFile mongoFile = option.get();
            if(Objects.isNull(mongoFile.getContent())){
                option = this.getGridFsFileById(fileId);
            }
        }

        return option.map(FileExportVo::new).orElse(null);
    }

    /**
     * 文件删除
     * @param fileId
     */
    @Override
    public void removeFile(String fileId) {
        Optional<MongoFile> option = this.getBinaryFileById(fileId);

        if (option.isPresent()) {
            if (Objects.nonNull(option.get().getGridFsId())) {
                this.removeGridFsFile(fileId);
            } else {
                this.removeBinaryFile(fileId);
            }
        }
    }

    /**
     * 删除Binary文件
     * @param fileId
     */
    public void removeBinaryFile(String fileId) {
        mongoFileRepository.deleteById(fileId);
    }

    /**
     * 删除GridFs文件
     * @param fileId
     */
    public void removeGridFsFile(String fileId) {
        // TODO 根据id查询文件
        MongoFile mongoFile = mongoTemplate.findById(fileId, MongoFile.class );
        if(Objects.nonNull(mongoFile)){
            // TODO 根据文件ID删除fs.files和fs.chunks中的记录
            Query deleteFileQuery = new Query().addCriteria(Criteria.where("filename").is(mongoFile.getGridFsId()));
            gridFsTemplate.delete(deleteFileQuery);
            // TODO 删除集合mongoFile中的数据
            Query deleteQuery = new Query(Criteria.where("id").is(fileId));
            mongoTemplate.remove(deleteQuery, MongoFile.class);
        }
    }

    /**
     * 保存Binary文件(小文件)
     * @param file
     * @return
     * @throws Exception
     */
    public FileExportVo saveBinaryFile(MultipartFile file) throws Exception {

        String suffix = getFileSuffix(file);

        MongoFile mongoFile = mongoFileRepository.save(
                MongoFile.builder()
                        .fileName(file.getOriginalFilename())
                        .fileSize(file.getSize())
                        .content(new Binary(file.getBytes()))
                        .contentType(file.getContentType())
                        .uploadDate(new Date())
                        .suffix(suffix)
                        .md5(MD5Util.getMD5(file.getInputStream()))
                        .build()
        );

        return new FileExportVo(mongoFile);
    }

    /**
     * 保存GridFs文件(大文件)
     * @param file
     * @return
     * @throws Exception
     */
    public FileExportVo saveGridFsFile(MultipartFile file) throws Exception {
        String suffix = getFileSuffix(file);

        String gridFsId = this.storeFileToGridFS(file.getInputStream(), file.getContentType());

        MongoFile mongoFile = mongoTemplate.save(
                MongoFile.builder()
                        .fileName(file.getOriginalFilename())
                        .fileSize(file.getSize())
                        .contentType(file.getContentType())
                        .uploadDate(new Date())
                        .suffix(suffix)
                        .md5(MD5Util.getMD5(file.getInputStream()))
                        .gridFsId(gridFsId)
                        .build()
        );

        return new FileExportVo(mongoFile);
    }

    /**
     * 上传文件到Mongodb的GridFs中
     * @param in
     * @param contentType
     * @return
     */
    public String storeFileToGridFS(InputStream in, String contentType){
        String gridFsId = IdUtil.simpleUUID();
        // TODO 将文件存储进GridFS中
        gridFsTemplate.store(in, gridFsId , contentType);
        return gridFsId;
    }

    /**
     * 获取Binary文件
     * @param id
     * @return
     */
    public Optional<MongoFile> getBinaryFileById(String id) {
        return mongoFileRepository.findById(id);
    }

    /**
     * 获取Grid文件
     * @param id
     * @return
     */
    public Optional<MongoFile> getGridFsFileById(String id){
        MongoFile mongoFile = mongoTemplate.findById(id , MongoFile.class );
        if(Objects.nonNull(mongoFile)){
            Query gridQuery = new Query().addCriteria(Criteria.where("filename").is(mongoFile.getGridFsId()));
            try {
                // TODO 根据id查询文件
                GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
                // TODO 打开流下载对象
                GridFSDownloadStream in = gridFSBucket.openDownloadStream(fsFile.getObjectId());
                if(in.getGridFSFile().getLength() > 0){
                    // TODO 获取流对象
                    GridFsResource resource = new GridFsResource(fsFile, in);
                    // TODO 获取数据
                    mongoFile.setContent(new Binary(IoUtil.readBytes(resource.getInputStream())));
                    return Optional.of(mongoFile);
                }else {
                    return Optional.empty();
                }
            }catch (IOException e){
                log.error("获取MongoDB大文件失败", e);
            }
        }

        return Optional.empty();
    }

    /**
     * 获取文件后缀
     * @param file
     * @return
     */
    private String getFileSuffix(MultipartFile file) {
        String suffix = "";
        if (Objects.requireNonNull(file.getOriginalFilename()).contains(".")) {
            suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        }
        return suffix;
    }

十一、文件上传接口-控制器

/**
 * @Description 文件上传接口
 * @author songwp
 * @date Apr 17, 2022
 * @version 1.0
 */
@Slf4j
@RestController
@RequestMapping("/file")
public class FileUploadController {

    /**
     * 文件上传实现类
     */
    @Resource
    private FileUploadService fileUploadService;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public ResponseMessage<?> uploadFile(@RequestParam(value = "file") MultipartFile file) {
        try {
            return ResponseMessage.ok("上传成功", fileUploadService.uploadFile(file));
        } catch (Exception e) {
            log.error("文件上传失败:", e);
            return ResponseMessage.error(e.getMessage());
        }
    }

    /**
     * 多文件上传
     * @param files
     * @return
     */
    @PostMapping("/uploadFiles")
    public ResponseMessage<?> uploadFile(@RequestParam(value = "files") List<MultipartFile> files) {
        try {
            return ResponseMessage.ok("上传成功", fileUploadService.uploadFiles(files));
        } catch (Exception e) {
            return ResponseMessage.error(e.getMessage());
        }
    }

    /**
     * 文件下载
     * @param fileId
     * @return
     */
    @GetMapping("/download/{fileId}")
    public ResponseEntity<Object> fileDownload(@PathVariable(name = "fileId") String fileId) {
        FileExportVo fileExportVo = fileUploadService.downloadFile(fileId);

        if (Objects.nonNull(fileExportVo)) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + fileExportVo.getFileName() + "\"")
                    .header(HttpHeaders.CONTENT_TYPE, fileExportVo.getContentType())
                    .header(HttpHeaders.CONTENT_LENGTH, fileExportVo.getFileSize() + "").header("Connection", "close")
                    .body(fileExportVo.getData());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("file does not exist");
        }
    }

    /**
     * 文件删除
     * @param fileId
     * @return
     */
    @DeleteMapping("/remove/{fileId}")
    public ResponseMessage<?> removeFile(@PathVariable(name = "fileId") String fileId) {
        fileUploadService.removeFile(fileId);
        return ResponseMessage.ok("删除成功");
    }

}

十二、接口测试

 1.单个文件上传

 

    2.多文件上传

 

  3.文件下载

 

  4.文件删除

 

 

 

 

  

  

标签:文件,return,String,MongoDB,file,附件,fileId,public,SpringBoot
来源: https://www.cnblogs.com/songweipeng/p/16465852.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有