前言:
上一篇關於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>