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;
}
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
这里其实不需要用这个,文件名只是显示作用,因此建议存文件完整路径的时候,其中的文件名用时间戳,而文件名用单独一个字段存实际的文件名,用于前端显示,如果使用时间戳,那么根据路径去获取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 "";
}
}
}
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);
}
}
注:这里的异常处理就省略了。
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);
}
}
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;
}
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);
}
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;
}
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、上传成功,前端页面返回json串
3、上传成功,数据库插入成功
4、以上操作是上传到本地,现在是上传到FastDFS服务器上。
postman截图:
fastdfs服务器的storage存储目录:
5、从fastdfs服务器下载图片到本地,访问路径,浏览器会帮你下载。
http://localhost:8080/upload/downloadFile?filesName=timg.jpg&&filesPath=group1/M00/00/00/wKg7g1wN8GyAZ4clAADWMhcxZx879.jpeg
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