背景:有机会从0-1架构设计智慧消防领域的解决方案项目,涉及到文件的存储,果断放弃了传统的服务器直接文件存储和ftp存储,选择使用国内比较热的fastdfs,新的项目肯定选择使用最新的版本V6.07,记录下Fastdfs的搭建及项目中封装使用
开发环境:springboot(2.3.5.RELEASE)+fastdfs(V6.07)+ubuntu
一、安装fdfs
1、下载最新版本的fastdfs,FastDFS 发行版 - Gitee.com,上传到linux指定目录
2、进入tar包所在目录,执行tar -zxvf libfastcommon-1.0.53.tar.gz
3、解压完成后,进入解压目录libfastcommon-1.0.53,执行./make.sh install
如果报错,安装gcc即可
说明:如果不知道需要安装哪个版本软件,可以执行 sudo apt-cache search gcc(软件名),查询远程仓库有哪些版本可以按照,然后执行apt-get install安装即可
安装make
执行make
执行sudo ./make.sh install
4、 进入fastdfs-6.07目录,执行sudo ./make.sh
执行sudo ./make.sh install
5、配置tracker
进入/etc/fdfs目录,有三个.sample后缀的文件(自动生成的fdfs模板配置文件),通过cp命令cp tracker.conf.sample tracker.conf,创建tracker配置文件
编辑tracker.conf:vi tracker.conf,修改相关参数
启动tracker执行命令:fdfs_trackerd /etc/fdfs/tracker.conf start
查看tracker启动日志 和端口
6、配置storage
进入/etc/fdfs目录,cp storage.conf.sample storage.conf,创建storage配置文件,执行vi storage.conf,修改相关参数
启动storage,执行fdfs_storaged /etc/fdfs/storage.conf start
查看启动日志
通过monitor来查看storage是否成功绑定,执行命令:fdfs_monitor /etc/fdfs/storage.conf
至此fastdfs单机版安装完毕,如果需要部署集群,可以参考下官网的搭建文件
二、nginx配置,用于前端直接通过ngnix访问文件或者图片,在nginx配置目录下(默认安装/etc/nginx/conf.d),新建fdfs.conf文件,内容如下
server {
listen 18811;
server_name localhost 192.168.103.202;
location /group1/M00{
alias /usr/local/env/fastdfs/storage/data/;
autoindex on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
重启ngnix,执行ngnix -s reload命令
三、springboot项目fastdfs工具类封装及使用,直接上可执行代码
import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.Charset;
/**
* Fdfs配置类
*
* @author wangfenglei
*/
@Component
public class FdfsUtil {
private static Logger log = LoggerFactory.getLogger(FdfsUtil.class);
@Autowired
private FastFileStorageClient storageClient;
@Value("${fdfs.resHost}")
private String resHost;
/**
* 上传文件
*
* @param file 文件对象
* @return 文件访问地址
* @throws Exception 异常
*/
public String uploadFile(MultipartFile file) throws Exception {
StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
return getResAccessUrl(storePath);
}
/**
* 上传图片
*
* @param bufferedImage 图片缓冲流
* @param imageExtension 图片扩展名,如png
* @return 文件访问地址
* @throws Exception 异常
*/
public String uploadImage(BufferedImage bufferedImage, String imageExtension) throws Exception {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, imageExtension, os);
InputStream input = new ByteArrayInputStream(os.toByteArray());
StorePath storePath = storageClient.uploadFile(input, os.size(), imageExtension, null);
return getResAccessUrl(storePath);
}
/**
* 上传文件
*
* @param filePath 文件全路径
* @return 文件可访问地址
* @throws Exception 异常
*/
public String uploadFile(String filePath) throws Exception {
File file = new File(filePath);
StorePath storePath = storageClient.uploadFile(new FileInputStream(file), file.length(), FilenameUtils.getExtension(filePath), null);
return getResAccessUrl(storePath);
}
/**
* 将一段字符串生成一个文件上传
*
* @param content 文件内容
* @param fileExtension 文件扩展名
* @return 文件可访问路径
*/
public String uploadFile(String content, String fileExtension) {
byte[] buff = content.getBytes(Charset.forName("UTF-8"));
ByteArrayInputStream stream = new ByteArrayInputStream(buff);
StorePath storePath = storageClient.uploadFile(stream, buff.length, fileExtension, null);
return getResAccessUrl(storePath);
}
/**
* 下载文件
*
* @param fileUrl 文件路径,http全路径和group开头
* @return 文件二进制数组
* @throws Exception 异常
*/
public byte[] downloadFile(String fileUrl) throws Exception {
int index = fileUrl.indexOf("group");
String groupUrl = fileUrl.substring(fileUrl.indexOf("group"));
String group = groupUrl.substring(0, fileUrl.indexOf("/") + (index > 0 ? 1 : 0));
String path = groupUrl.substring(fileUrl.indexOf("/") + (index > 0 ? 2 : 1));
DownloadByteArray downloadByteArray = new DownloadByteArray();
byte[] bytes = storageClient.downloadFile(group, path, downloadByteArray);
return bytes;
}
/**
* 查看文件的信息
*
* @param groupName 组
* @param path 路径
* @return 文件信息
*/
public FileInfo getFile(String groupName, String path) {
FileInfo fileInfo = null;
log.info("文件信息入参,groupName:{},path:{}", groupName, path);
try {
fileInfo = storageClient.queryFileInfo(groupName, path);
log.info("文件信息:{}", fileInfo.toString());
} catch (Exception e) {
log.error("Non IO Exception: Get File from Fast DFS failed", e);
}
return fileInfo;
}
/**
* 上传图片并且生成缩略图
*
* @param file 上传文件
* @return 文件访问路径
* @throws Exception 异常
*/
public String uploadImageAndCrtThumbImage(MultipartFile file) throws Exception {
StorePath storePath = storageClient.uploadImageAndCrtThumbImage(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
return getResAccessUrl(storePath);
}
/**
* 删除文件
*
* @param fileUrl 文件访问地址
*/
public void deleteFile(String fileUrl) {
if (null == fileUrl || "".equals(fileUrl)) {
return;
}
try {
StorePath storePath = StorePath.parseFromUrl(fileUrl);
storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 可通过浏览器访问路径
*
* @param storePath 存储路径
* @return 可访问路径
*/
private String getResAccessUrl(StorePath storePath) {
String fileUrl = resHost + "/" + storePath.getFullPath();
return fileUrl;
}
public static void main(String[] args) {
String fileUrl = "group1/M00/00/00/wKhnymELkZGAYe3NAAABG6VUfqA418.png";
System.out.println(fileUrl.indexOf("group"));
String groupUrl = fileUrl.substring(fileUrl.indexOf("group"));
String group = groupUrl.substring(0, fileUrl.indexOf("/") + 1);
String path = groupUrl.substring(fileUrl.indexOf("/") + 2);
System.out.println(groupUrl);
System.out.println(group);
System.out.println(path);
}
}
application.yml文件配置
fdfs:
soTimeout: 1500
connectTimeout: 600
#缩略图生成参数
thumbImage:
width: 150
height: 150
#TrackerList参数,支持多个
trackerList:
- 192.168.103.202:22122
#- 192.168.103.202:22122
pool:
#不限制
max-total: -1
#通过nginx 访问地址
resHost: http://192.168.103.202:18811
文件上传
import com.wfl.common.constant.CommonConstant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.wfl.firefighting.util.FdfsUtil;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import sun.misc.BASE64Decoder;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 文件上传控制类
*
* @author wangfenglei
*/
@Api(tags = "文件上传")
@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {
@Autowired
private FdfsUtil fdfsUtil;
/**
* 上传单个文件
*
* @param request http请求
* @return 文件路径
*/
@ApiOperation(value = "文件上传-上传单个文件", notes = "上传多个文件,返回文件可访问路径")
@PostMapping("/uploadFile")
public Result<?> uploadFile(HttpServletRequest request) {
String filePath = "";
try {
MultipartHttpServletRequest servletRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = servletRequest.getFileMap();
for (Map.Entry<String, MultipartFile> entry : fileMap.entrySet()) {
MultipartFile file = entry.getValue();
filePath = fdfsUtil.uploadFile(file);
break;
}
} catch (Exception e) {
log.error(e.toString(), e);
return Result.error("上传失败");
}
return Result.OK(filePath);
}
}
文件下载
直接前端调用window.open(url),浏览器默认下载功能,也可以自己封装个文件下载通用工具类,以下为本人封装的文件操作工具类,可用于文件下载
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
/**
* 文件工具类
*
* @author wangfenglei
*/
@Slf4j
public class FileUtil {
/**
* 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
*
* @param destPath 存放目录
*/
public static void mkdirs(String destPath) {
File file = new File(destPath);
//当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
}
/**
* 获取系统临时目录
*
* @return 系统临时目录
*/
public static String getSystemTempDirPath() {
return System.getProperty("java.io.tmpdir");
}
/**
* 获得指定文件的byte数组
*
* @param filePath 文件绝对路径
* @return 数组
*/
public static byte[] fileToByte(String filePath) {
ByteArrayOutputStream byteArrayOutputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException("file not exists");
}
byteArrayOutputStream = new ByteArrayOutputStream((int) file.length());
bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
int buf_size = 1024;
byte[] buffer = new byte[buf_size];
int len = 0;
while (-1 != (len = bufferedInputStream.read(buffer, 0, buf_size))) {
byteArrayOutputStream.write(buffer, 0, len);
}
return byteArrayOutputStream.toByteArray();
} catch (Exception e) {
log.error(e.toString(), e);
return null;
} finally {
try {
if (null != byteArrayOutputStream) {
byteArrayOutputStream.close();
}
if (null != bufferedInputStream) {
bufferedInputStream.close();
}
} catch (Exception e) {
}
}
}
/**
* 根据byte数组,生成文件
*
* @param bfile 文件数组
* @param filePath 文件存放路径
* @param fileName 文件名称
*/
public static void byteToFile(byte[] bfile, String filePath, String fileName) {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file;
try {
File dir = new File(filePath);
//判断文件目录是否存在
if (!dir.exists() && !dir.isDirectory()) {
dir.mkdirs();
}
file = new File(filePath + File.separator + fileName);
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(bfile);
} catch (Exception e) {
log.error(e.toString(), e);
} finally {
try {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
} catch (Exception e) {
}
}
}
/**
* 文件下载
*
* @param response 响应
* @param FileFullPath 文件全路径
*/
public static void downFile(HttpServletResponse response, String FileFullPath) {
InputStream ins = null;
BufferedInputStream bins = null;
OutputStream outs = null;
BufferedOutputStream bouts = null;
try {
File file = new File(FileFullPath);
if (file.exists()) {
ins = new FileInputStream(FileFullPath);
bins = new BufferedInputStream(ins);
outs = response.getOutputStream();
bouts = new BufferedOutputStream(outs);
response.setContentType("application/x-download");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(FileFullPath, "UTF-8"));
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
bouts.write(buffer, 0, bytesRead);
}
bouts.flush();
}
} catch (Exception e) {
log.error("文件下载出错", e);
} finally {
if (null != bins) {
try {
bins.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != ins) {
try {
ins.close();
} catch (IOException e) {
}
}
if (null != bouts) {
try {
bouts.close();
} catch (IOException e) {
}
}
if (null != outs) {
try {
outs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 文件下载
*
* @param response 响应
* @param inputStream 文件输入刘
* @param fileName 文件名
*/
public static void downFile(HttpServletResponse response, InputStream inputStream, String fileName) {
BufferedInputStream bins = null;
OutputStream outs = null;
BufferedOutputStream bouts = null;
try {
bins = new BufferedInputStream(inputStream);
outs = response.getOutputStream();
bouts = new BufferedOutputStream(outs);
response.setContentType("application/x-download");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
bouts.write(buffer, 0, bytesRead);
}
bouts.flush();
} catch (Exception e) {
log.error("文件下载出错", e);
} finally {
if (null != bins) {
try {
bins.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
}
}
if (null != bouts) {
try {
bouts.close();
} catch (IOException e) {
}
}
if (null != outs) {
try {
outs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
配合FdfsUtil封装,实现文件inputStream流下载
byte[] byteArr = fdfsUtil.downloadFile(device.getQrcode());
FileUtil.downFile(response,new ByteArrayInputStream(byteArr),"downloadFileName.txt");