FastDFS+SpringBoot+Mybatis 整合 實現文件上傳下載

前言:   

     上一篇關於FastDFS+SpringBoot整合,沒有跟數據庫交互,因此另開一篇博客,記錄下FastDFS+SpringBoot+Mybatis 整合實現文件上傳下載,這裏用的是MySQL。用的依然是tobato/FastDFS_Client的客戶端。

一、項目結構:

注:博客尾部附完整pom.xml。

二、配置類

1、ComponetImport:(fastdfs客戶端人家寫好的,直接拷過來用即可)

package com.lucifer.fastdfs.config;

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;

/**
 * @author: Lucifer
 * @create: 2018-12-05 15:48
 * @description:
 **/
@Configuration
@Import(FdfsClientConfig.class)
/**
 *  解決jmx重複註冊bean的問題
 */
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class ComponetImport {
    // 導入依賴組件
}

2、UploadProperties(這是對應着application.yml中自定義的配置)

package com.lucifer.fastdfs.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

/**
 * @author: Lucifer
 * @create: 2018-12-05 15:48
 * @description:
 **/

@Data
@ConfigurationProperties(prefix = "Lucifer.upload")
public class UploadProperties {

    private String uploadPathName;
    private String baseUrl;
    private List<String> allowTypes;
    private String fileExtName;
    private String groupName;
}

三、application.yml(spring boot的主配置文件)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/znbt?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
  servlet:
    multipart:
      max-file-size: 5MB #限制文件上傳的大小

# ===================================================================
# 分佈式文件系統FDFS配置
# ===================================================================
fdfs:
  so-timeout: 1501 #上傳的超時時間
  connect-timeout: 601 #連接超時時間
  thumb-image:             #縮略圖生成參數
    width: 150
    height: 150
  tracker-list:            #TrackerList參數,支持多個
    - 192.168.59.131:22122


Lucifer:
  upload:
    groupName: group1   #fastdfs組名
    uploadPathName: D:/upload/file/ #上傳存儲本地文件的位置
    baseUrl: 192.168.59.131/
    fileExtName: jpg   #上傳文件以什麼格式存儲,暫時用不上
    allowTypes:   #允許上傳的文件類型
      - image/jpeg
      - image/png

#打印sql#
logging:
  level:
    com.lucifer.fastdfs.Mapper: debug

四、FileUtil(工具類,生成文件的md5,爲了區分文件是否重複上傳)

package com.lucifer.fastdfs.util;

import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;

/**
 * @author: Lucifer
 * @create: 2018-12-04 17:23
 * @description:
 **/
public class FileUtil {

    /**
     * 獲取文件的md5
     * @param filePath 文件地址
     * @return
     */
    public static String getFileMd5Value(String filePath) {
        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) {
            return "";
        }
        byte[] buffer = new byte[2048];
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            FileInputStream in = new FileInputStream(file);
            while (true) {
                int len = in.read(buffer, 0, 2048);
                if (len != -1) {
                    digest.update(buffer, 0, len);
                } else {
                    break;
                }
            }
            in.close();

            byte[] md5Bytes = digest.digest();
            StringBuffer hexValue = new StringBuffer();
            for (int i = 0; i < md5Bytes.length; i++) {
                int val = ((int) md5Bytes[i]) & 0xff;
                if (val < 16) {
                    hexValue.append("0");
                }
                hexValue.append(Integer.toHexString(val));
            }
            return hexValue.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

    }
}

五、FastdfsApplication(spring boot的啓動類)

package com.lucifer.fastdfs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan(basePackages="com.lucifer.fastdfs.Mapper")
public class FastdfsApplication {

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

六、UploadFileController(文件操作的Controller層)

package com.lucifer.fastdfs.controller;

import com.lucifer.fastdfs.Service.UploadFileService;
import com.lucifer.fastdfs.pojo.UploadFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @author: Lucifer
 * @create: 2018-12-04 15:36
 * @description:
 **/
@Controller
@RequestMapping("/upload")
public class UploadFileController {

    private static final Logger log = LoggerFactory.getLogger(UploadFileController.class);

    @Autowired
    private UploadFileService uploadFileService;


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

    /**
     * 批量上傳文件到本地
     *
     * @param files
     * @return
     * @throws IOException
     */
    @PostMapping(value = "/uploadLocal")
    @ResponseBody
    public List<UploadFile> uploadFile(@RequestParam("file") MultipartFile[] files) throws IOException {
        List<UploadFile> mapList = uploadFileService.UploadFilesToLocal(files);
        return mapList;
    }

    /**
     * 刪除單個文件
     *
     * @param fileName
     * @param id
     */
    @DeleteMapping(value = "/del")
    @ResponseBody
    public void delfile(@RequestParam String fileName, String id) {
        uploadFileService.deleteFile(fileName, id);
    }

    /**
     * 批量上傳文件到fastdfs服務器
     *
     * @param uploadFiles
     * @return
     * @throws IOException
     */
    @PostMapping(value = "/uploadServer")
    @ResponseBody
    public List<UploadFile> uploadFileServer(@RequestBody List<UploadFile> uploadFiles) throws IOException {
        List<UploadFile> mapList = uploadFileService.UploadFilesToServer(uploadFiles);
        return mapList;
    }


    /**
     * 單個文件下載
     *
     * @param filesName
     * @param filesPath
     */
    @GetMapping(value = "downloadFile")
    public void downloadFile(@RequestParam String filesName, String filesPath, HttpServletResponse response) throws IOException {
        uploadFileService.downloadFile(filesName, filesPath, response);
    }


}

七、UploadFile(實體類)

package com.lucifer.fastdfs.pojo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Table;
import java.util.Date;

/**
 * @author: Lucifer
 * @create: 2018-12-04 16:18
 * @description:
 **/
@Data
@Table(name = "upload_file")
public class UploadFile {

    @ApiModelProperty(value = "ID")
    @Column(name = "id")
    private String id;

    @ApiModelProperty(value = "上傳時間")
    @Column(name = "uploadTime")
    private Date uploadTime;

    @ApiModelProperty(value = "文件名")
    @Column(name = "filesName")
    private String filesName;

    @ApiModelProperty(value = "文件地址")
    @Column(name = "filesPath")
    private String filesPath;

    @ApiModelProperty(value = "文件類型")
    @Column(name = "filesType")
    private String filesType;

    @ApiModelProperty(value = "文件MD5值")
    @Column(name = "filesMD5")
    private String filesMD5;

    @ApiModelProperty(value = "文件大小")
    @Column(name = "fileSize")
    private Long fileSize;

}

八、UploadFileMapper(mapper接口,對數據庫進行操作)

package com.lucifer.fastdfs.Mapper;

import com.lucifer.fastdfs.pojo.UploadFile;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import tk.mybatis.mapper.common.BaseMapper;
import tk.mybatis.mapper.common.MySqlMapper;


public interface UploadFileMapper extends BaseMapper<UploadFile>, MySqlMapper<UploadFile> {

    @Select("select * from upload_file where id = #{id}")
    UploadFile selectById(String id);

    @Select("select count(*) from upload_file where filesMD5 = #{filesMD5}")
    int selectByFilesMD5(String filesMD5);

    @Delete("delete from upload_file where id = #{id}")
    void deleteById(String id);

    @Update("UPDATE upload_file SET uploadTime = #{uploadTime},filesPath = #{filesPath} WHERE id = #{id}")
    void updateById(UploadFile uploadFile);


}

九、UploadFileService(service層)

package com.lucifer.fastdfs.Service;

import com.lucifer.fastdfs.pojo.UploadFile;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;


public interface UploadFileService {

    /**
     * 批量上傳
     *
     * @param files
     * @return
     */
    public List<UploadFile> UploadFilesToLocal(MultipartFile[] files) throws IOException;

    /**
     * 根據文件名稱刪除文件
     *
     * @param fileName
     * @return
     */
    public void deleteFile(String fileName, String id);

    /**
     * 上傳文件到fastdfs圖片服務器
     *
     * @param uploadFiles
     * @return
     */
    List<UploadFile> UploadFilesToServer(List<UploadFile> uploadFiles);

    /**
     * 從fastdfs服務器下載文件
     *
     * @param filesName
     * @param filesPath
     */
    void downloadFile(String filesName, String filesPath, HttpServletResponse response) throws IOException;
}

十、UploadFileServiceImpl(實現類)

package com.lucifer.fastdfs.Service;

import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.domain.ThumbImageConfig;
import com.github.tobato.fastdfs.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.lucifer.fastdfs.Mapper.UploadFileMapper;
import com.lucifer.fastdfs.config.UploadProperties;
import com.lucifer.fastdfs.pojo.UploadFile;
import com.lucifer.fastdfs.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;


/**
 * @author: Lucifer
 * @create: 2018-12-04 15:43
 * @description:
 **/
@Slf4j
@Service
@EnableConfigurationProperties(UploadProperties.class)
public class UploadFileServiceImpl implements UploadFileService {


    @Autowired
    private UploadFileMapper uploadFileMapper;

    @Autowired
    private FastFileStorageClient storageClient;

    @Autowired
    private ThumbImageConfig thumbImageConfig;

    @Autowired
    private UploadProperties properties;

    /**
     * 批量上傳本地
     *
     * @param files
     * @return
     * @throws IOException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<UploadFile> UploadFilesToLocal(MultipartFile[] files) {
        if (files == null || files.length == 0) {
            return null;
        }
        List<UploadFile> fileList = new ArrayList<>();
        List<String> listMDs = new ArrayList();
        Map<String, Integer> stringMap = new HashMap<>();
        try {
            //如果沒有該路徑,就創建,此處路徑需要加空格,否者不會創建image文件夾
            File filePath = new File(properties.getUploadPathName() + " ");
            for (int i = 0; i < files.length; i++) {
                if (!filePath.getParentFile().exists()) {
                    filePath.getParentFile().mkdirs();
                }
                if (files[i].getSize() > 0) {
                    //檢查文件類型
                    checkFileType(i, files[i]);
                    String fileOriginalFilename = files[i].getOriginalFilename().replaceAll(" ", "");
                    File file = new File(filePath + fileOriginalFilename);
                    try {
                        files[i].transferTo(file);
                    } catch (IOException e) {
                        log.info("第 " + (i + 1) + " 個文件上傳失敗 ==> ,{}" + e.getMessage());
                        e.printStackTrace();
                    }
                    UploadFile uploadFile = new UploadFile();
                    String uid = UUID.randomUUID().toString().replaceAll("-", "");
                    uploadFile.setId(uid);
                    uploadFile.setFilesName(fileOriginalFilename);
                    uploadFile.setFilesType(files[i].getContentType());
                    uploadFile.setFilesPath(filePath + fileOriginalFilename);
                    uploadFile.setUploadTime(new Date());
                    uploadFile.setFileSize(file.length());
                    uploadFile.setFilesMD5(FileUtil.getFileMd5Value(filePath + fileOriginalFilename));
                    fileList.add(i, uploadFile);
                    log.info("第" + (i + 1) + "個FilesMD5:" + uploadFile.getFilesMD5());
                    listMDs.add(i, uploadFile.getFilesPath());
                    //提示重複文件
                    if (stringMap.containsKey(uploadFile.getFilesMD5())) {
                        throw new RuntimeException("第" + (stringMap.get(uploadFile.getFilesMD5()) + 1) + "個文件與第" + (i + 1) + "個上傳的文件內容一致,勿重複上傳");
                    } else {
                        stringMap.put(uploadFile.getFilesMD5(), i);
                    }
                    //數據庫根據filesMD5檢查文件是否已經存在
                    if (uploadFileMapper.selectByFilesMD5(uploadFile.getFilesMD5()) > 0) {
                        throw new RuntimeException("第" + (i + 1) + "個文件,數據庫已存在");
                    }
                }
            }
            /*僞造異常,測試文件部分上傳失敗,是否會清空此次上傳的所有文件
            fileList.get(10000000);*/
            //批量插入到數據庫
            uploadFileMapper.insertList(fileList);
            log.info("文件上傳成功!");
        } catch (Exception e) {
            for (int i = 0; i < listMDs.size(); i++) {
                new File(listMDs.get(i)).delete();
            }
            log.info("文件上傳失敗,正在清理文件==================>,{}", e.getMessage());
            e.printStackTrace();
        }
        return fileList;
    }

    /**
     * 根據文件名刪除本地文件,根據id刪除數據庫數據
     *
     * @param fileName
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFile(String fileName, String id) {
        boolean b = false;
        File file = new File(properties.getUploadPathName() + " " + fileName);
        System.out.println("路徑:" + file.getPath());
        UploadFile uploadFile = new UploadFile();
        uploadFile.setId(id);
        if (uploadFileMapper.selectById(id) == null) {
            throw new RuntimeException("文件id:" + id + "數據庫中不存在!");
        }
        //判斷文件是否存在
        if (file.exists()) {
            b = file.delete();
        }
        if (b) {
            uploadFileMapper.deleteById(id);
            System.out.println("刪除文件成功");
        } else {
            System.out.println("刪除文件失敗");
            throw new RuntimeException("刪除文件失敗");
        }
    }

    /**
     * 批量上傳文件到服務器
     *
     * @param uploadFiles
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<UploadFile> UploadFilesToServer(List<UploadFile> uploadFiles) {
        List<UploadFile> uploadFileList = new ArrayList<>();
        FileInputStream fileInputStream = null;
        for (int i = 0; i < uploadFiles.size(); i++) {
            File file = new File(uploadFiles.get(i).getFilesPath());
            String fileType = uploadFiles.get(i).getFilesType();
            String subFileType = fileType.substring(fileType.indexOf("/") + 1);
            try {
                log.info("開始上傳文件==============>{}", uploadFiles.get(i).getFilesName());
                fileInputStream = new FileInputStream(file);
                //上傳
                StorePath storePath = this.storageClient.uploadFile(fileInputStream, uploadFiles.get(i).getFileSize(), subFileType, null);
                UploadFile uploadFile = new UploadFile();
                uploadFile.setId(uploadFiles.get(i).getId());
                uploadFile.setFilesPath(storePath.getFullPath());
                log.info("文件存儲在服務器的路徑==============>{}", properties.getBaseUrl() + storePath.getFullPath());
                uploadFile.setUploadTime(new Date());
                //數據庫修改
                uploadFileMapper.updateById(uploadFile);
                log.info("文件上傳服務器成功=========>{}", uploadFiles.get(i).getFilesName());
                uploadFileList.add(uploadFileMapper.selectById(uploadFiles.get(i).getId()));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                log.info("本地文件被刪除了,文件找不到=======>{}", e.getMessage());
            } finally {
                //關閉io流
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    log.info(e.getMessage());
                    e.printStackTrace();
                }
            }

        }
        return uploadFileList;
    }

    /**
     * 下載文件
     *
     * @param filesName
     * @param filesPath
     */
    @Override
    public void downloadFile(String filesName, String filesPath, HttpServletResponse response) throws IOException {

         //這裏的filesPath需要是這種格式的:M00/00/00/wKg7g1wN2YyAAH1MAADJbaKmScw004.jpg
        filesPath=filesPath.substring(properties.getGroupName().length()+1);
        DownloadByteArray downloadByteArray = new DownloadByteArray();
        //剛開始用的是DownloadFileWriter,結果得到bytes的字節數不對,導致下載後,文件打不開
        //DownloadFileWriter callback = new DownloadFileWriter(filesName);
        byte[] bytes = this.storageClient.downloadFile(properties.getGroupName(), filesPath, downloadByteArray);
        System.out.println(bytes.length);
        //支持中文名稱,避免亂碼
        response.setContentType("application/force-download");
        response.addHeader("Content-Disposition", "attachment;fileName=" + new String(filesName.getBytes("UTF-8"), "iso-8859-1"));
        response.setCharacterEncoding("UTF-8");
        OutputStream outputStream = response.getOutputStream();
        outputStream.write(bytes);
    }

    /**
     * 校驗文件
     *
     * @param file
     * @throws IOException
     */
    public void checkFileType(int i, MultipartFile file) throws IOException {
        String type = file.getContentType();
        //校驗文件類型是否被允許可以上傳
        if (!properties.getAllowTypes().contains(type)) {
            log.info("第" + (i + 1) + "個文件類型不允許上傳========>,{}", type);
            throw new RuntimeException("第" + (i + 1) + "個文件類型不允許上傳");
        }
        //校驗文件內容是否爲圖片
        BufferedImage image = ImageIO.read(file.getInputStream());
        if (image == null) {
            log.info("第" + (i + 1) + "個文件內容不符合要求");
            throw new RuntimeException("上傳失敗," + "第" + (i + 1) + "個文件內容不符合要求");
        }
    }


}

十一、前端頁面(這裏只寫了上傳的)

index.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>文件上傳</title>
</head>
<body>

<hr/>
<h2>批量文件上傳示例</h2>

<div>
    <form method="POST" enctype="multipart/form-data"
          action="/uploads/uploadLocal">
        <p>
            文件1:<input type="file" name="file"/>
        </p>
        <p>
            文件2:<input type="file" name="file"/>
        </p>
        <p>
            <input type="submit" value="上傳"/>
        </p>
    </form>
</div>

</body>
</html>

十二、效果圖

1、前端頁面效果:

2、上傳成功,前端頁面返回json串 

3、上傳成功,數據庫插入成功

4、以上操作是上傳到本地,現在是上傳到FastDFS服務器上。

postman截圖:

fastdfs服務器的storage存儲目錄:

5、從fastdfs服務器下載圖片到本地,訪問路徑,瀏覽器會幫你下載。

 

附:pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lucifer</groupId>
    <artifactId>fastdfs</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>fastdfs</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 文檔生成 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>

        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.26.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

 

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