FastDFS+SpringBoot+Mybatis 整合 实现文件上传下载

前言:   

     上一篇关于FastDFS+SpringBoot整合,没有跟数据库交互,因此另开一篇博客,记录下FastDFS+SpringBoot+Mybatis 整合实现文件上传下载,这里用的是MySQL。用的依然是tobato/FastDFS_Client的客户端。

一、项目结构:

FastDFS+SpringBoot+Mybatis 整合 实现文件上传下载_第1张图片

注:博客尾部附完整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 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,为了区分文件是否重复上传)

这里其实不需要用这个,文件名只是显示作用,因此建议存文件完整路径的时候,其中的文件名用时间戳,而文件名用单独一个字段存实际的文件名,用于前端显示,如果使用时间戳,那么根据路径去获取md值,肯定是不一样的;

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层)

注:这里的异常处理就省略了。

这里可以用了两种方式,直接上传服务器的盘中或者fastdfs,可以直接上传到fastdfs;

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(@RequestParam("file") MultipartFile[] files) throws Exception {
        List 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 uploadFileServer(@RequestBody List uploadFiles) throws IOException {
        List 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, MySqlMapper {

    @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 UploadFilesToLocal(MultipartFile[] files) throws Exception;

    /**
     * 根据文件名称删除文件
     *
     * @param fileName
     * @return
     */
    public void deleteFile(String fileName, String id) throws Exception;

    /**
     * 上传文件到fastdfs图片服务器
     *
     * @param uploadFiles
     * @return
     */
    List UploadFilesToServer(List uploadFiles);

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

十、UploadFileServiceImpl(实现类)

        此处注意:@Transactional(rollbackFor = Exception.class)是只要抛出异常,事务就会回滚,但是注意异常需要抛出,如果try{}catch{}捕获了异常,却未抛出,事务不会回滚的。

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 throw Exception{


    @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 UploadFilesToLocal(MultipartFile[] files) throws Exception{
        if (files == null || files.length == 0) {
            return null;
        }
        List fileList = new ArrayList<>();
        List listMDs = new ArrayList();
        Map stringMap = new HashMap<>();
        try {
            //如果没有该路径,就创建
            File filePath = new File(properties.getUploadPathName(),fileOriginalFilename);
            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();
						throw new IOException("文件上传失败");
                    }
                    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);
        } catch (Exception e) {
            for (int i = 0; i < listMDs.size(); i++) {
                new File(listMDs.get(i)).delete();
            }
            log.info("文件上传失败,正在清理文件==================>,{}", e.getMessage());
            e.printStackTrace();
	    throws new Exception(e.getMessage);
        }
        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 UploadFilesToServer(List uploadFiles) throw Exception{
        List 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());
				throw new FileNotFoundException(e.getMessage);
            } finally {
                //关闭io流
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    log.info(e.getMessage());
                    e.printStackTrace();
		    throw new IOException(e.getMessage);
                }
            }

        }
        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:




    
    文件上传




批量文件上传示例

文件1:

文件2:

十二、效果图

1、前端页面效果:

FastDFS+SpringBoot+Mybatis 整合 实现文件上传下载_第2张图片

2、上传成功,前端页面返回json串 

3、上传成功,数据库插入成功

4、以上操作是上传到本地,现在是上传到FastDFS服务器上。

postman截图:

FastDFS+SpringBoot+Mybatis 整合 实现文件上传下载_第3张图片

fastdfs服务器的storage存储目录:

5、从fastdfs服务器下载图片到本地,访问路径,浏览器会帮你下载。

http://localhost:8080/upload/downloadFile?filesName=timg.jpg&&filesPath=group1/M00/00/00/wKg7g1wN8GyAZ4clAADWMhcxZx879.jpeg

 

附:pom.xml:



    4.0.0

    com.lucifer
    fastdfs
    0.0.1-SNAPSHOT
    jar

    fastdfs
    Demo project for Spring Boot

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.1.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.2
        

        
            mysql
            mysql-connector-java
            runtime
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            io.springfox
            springfox-swagger2
            2.7.0
        
        
            io.springfox
            springfox-swagger-ui
            2.7.0
        
        
            tk.mybatis
            mapper-spring-boot-starter
            2.0.4
        

        
            com.github.tobato
            fastdfs-client
            1.26.4
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    



 

你可能感兴趣的:(SpringBoot技术篇,Mybatis实战篇,FastDFS实战篇)