FastDFS可用于分布式系统环境中自行搭建文件服务器,可以达到服务器资源动静分离,请求分流的效果。本篇简单概述下其使用方法。
准备工作:
-
搭建springboot脚手架并成功运行,可参考历史分享springboot+mybatis
-
启动FastDFS服务(tracker, storage及nginx)(搭建配置FastDFS服务,后续会在运维章节另行讲述)
2.FastDFS配置com.github.tobato fastdfs-client1.26.7
2.1 yml
fdfs: # 文件下载拉取地址 web-server-url: https://img.xxx.com so-timeout: 3000 connect-timeout: 1200 thumb-image: # 缩略图 width: 60 height: 60 tracker-list: 192.168.2.9:22122 pool: jmx-enabled: false #禁止JMX重复注册,影响springboot JMX监控
2.2 FastDFS client config
import com.github.tobato.fastdfs.FdfsClientConfig;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Import;@Import({FdfsClientConfig.class})@SpringBootApplicationpublic class FdfsApplication { public static void main(String[] args) { SpringApplication.run(FdfsApplication.class, args); }}
3.FastDFS使用
3.1 fdfs util
import com.github.tobato.fastdfs.domain.fdfs.StorePath;import com.github.tobato.fastdfs.exception.FdfsUnsupportStorePathException;import com.github.tobato.fastdfs.exception.FdfsUploadImageException;import com.github.tobato.fastdfs.service.FastFileStorageClient;import lombok.extern.slf4j.Slf4j;import net.coobird.thumbnailator.Thumbnails;import org.apache.commons.io.FilenameUtils;import org.apache.commons.io.IOUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.web.multipart.MultipartFile;import javax.imageio.ImageIO;import java.awt.*;import java.awt.image.BufferedImage;import java.io.*;import java.util.Arrays;import java.util.List;import java.util.UUID;@Slf4jpublic final class FdfsUtil { private static final List IMAGE_TYPE =
Arrays.asList("jpg", "jpeg", "png", "bmp", "tif", "gif", "webp"); private static final List VIDEO_TYPE = Arrays.asList("mp4", "avi"); private static final String IMG_LINUX_LOCATION = "/usr/local/data/img"; public static UploadFile upload(FastFileStorageClient fastFileStorageClient,
MultipartFile file, String imageServer,
Byte needThumbnail, Byte needSize)
throws IOException { UploadFile uploadFile = new UploadFile(
file.getOriginalFilename(), file.getSize(), file.getContentType()); uploadFile(fastFileStorageClient, file.getInputStream(),
uploadFile, imageServer, needThumbnail, needSize); return uploadFile; } public static UploadFile upload(FastFileStorageClient fastFileStorageClient,
File file, String imageServer,
Byte needThumbnail, Byte needSize) { try (InputStream is = new FileInputStream(file)) { UploadFile uploadFile = new UploadFile(
file.getName(), file.length(), null); uploadFile(fastFileStorageClient, is, uploadFile,
imageServer, needThumbnail, needSize); return uploadFile; } catch (Exception e){ throw new CommonException("上传文件失败", e); } } private static void uploadFile(FastFileStorageClient fastFileStorageClient,
InputStream inputStream, UploadFile uploadFile,
String imageServer, Byte needThumbnail,
Byte needSize) throws IOException { log.info("文件上传 originalFileName={}, fileSize={}",
uploadFile.getOriginalFilename(), uploadFile.getFileSize()); // 获取文件后缀 String fileExtName = FilenameUtils.getExtension(
uploadFile.getOriginalFilename()).toLowerCase(); // 限制图片文件上传大小 if(isSupportType(fileExtName)){ validateUploadImage(uploadFile, fileExtName); } // 缓存文件流 byte[] fileStream = IOUtils.toByteArray(inputStream); // 上传原图或文件 uploadOrigFile(fastFileStorageClient, uploadFile,
imageServer, fileStream, fileExtName); // 上传图片缩略图 if(isSupportType(fileExtName)){ uploadImageThumbnails(fastFileStorageClient, uploadFile,
imageServer, fileStream, fileExtName, needThumbnail, needSize); } log.info("上传文件地址={}", uploadFile.getFullFilename()); } private static void validateUploadImage(UploadFile uploadFile,
String fileExtName){ // gif文件,大小校验,超过4兆,不上传 if("gif".equals(fileExtName)){ if(uploadFile.getFileSize() > 4 * 1024 * 1024){ throw new CommonException("GIF动图大小不能超过4M"); } } else if(VIDEO_TYPE.contains(fileExtName)){ if(uploadFile.getFileSize() > 40 * 1024 * 1024){ throw new CommonException("视频大小不能超过40M"); } } else{ // 普通图片文件不能超过10M if(uploadFile.getFileSize() > 10 * 1024 * 1024){ throw new CommonException("上传单张图片不能超过10M"); } } } private static void uploadOrigFile(FastFileStorageClient fastFileStorageClient,
UploadFile uploadFile, String imageServer, byte[] fileStream, String fileExtName){ try (ByteArrayInputStream is = new ByteArrayInputStream(fileStream)) { StorePath originalStorePath = fastFileStorageClient.uploadFile(
is, uploadFile.getFileSize(), fileExtName, null); uploadFile.setFullFilename(imageServer.concat("/").concat(
originalStorePath.getFullPath())); } catch (IOException e) { log.error("upload Image error", e.getCause()); throw new FdfsUploadImageException("upload Image error", e.getCause()); } } private static void uploadImageThumbnails(
FastFileStorageClient fastFileStorageClient,
UploadFile uploadFile, String imageServer,
byte[] fileStream, String fileExtName,
Byte needThumbnail, Byte needSize)
throws IOException { // 默认图片缩略图取原图 uploadFile.setThumbnail(uploadFile.getFullFilename()); // 缩略图也需要计算图片尺寸大小 if(Constant.YES.equals(needSize) || Constant.YES.equals(needThumbnail)){ // 获取图片尺寸 BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(
fileStream)); if(bufferedImage != null){ uploadFile.setWidth(bufferedImage.getWidth()); uploadFile.setHeight(bufferedImage.getHeight()); // 压缩处理 if(Constant.YES.equals(needThumbnail)){ InputStream is = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); float outputQuality = getOutputQuality(
uploadFile.getFileSize()); if(!"jpg".equals(fileExtName)){ // 先转成jpg String newPicPath = IMG_LINUX_LOCATION + File.separator
+ UUID.randomUUID().toString() + ".jpg"; Thumbnails.of(new ByteArrayInputStream(fileStream))
.scale(1f).toFile(newPicPath); // 原比例不变,大小压缩 Thumbnails.of(newPicPath).scale(1f)
.outputQuality(outputQuality).toOutputStream(out); // 删除jpg图片 new File(newPicPath).delete(); } else { Thumbnails.of(new ByteArrayInputStream(fileStream))
.scale(1f).outputQuality(outputQuality)
.toOutputStream(out); } is = new ByteArrayInputStream(out.toByteArray()); // 上传缩略图 StorePath thumbnailStorePath = fastFileStorageClient
.uploadFile(is, is.available(), fileExtName, null); uploadFile.setThumbnail(imageServer.concat("/")
.concat(thumbnailStorePath.getFullPath())); log.info("上传缩略图地址={}", uploadFile.getThumbnail()); } catch (IOException e) { log.error("upload ThumbImage error", e.getCause()); throw new FdfsUploadImageException(
"upload ThumbImage error",e.getCause()); } finally { IOUtils.closeQuietly(is); } } } } } private static float getOutputQuality(long fileSize){ if (fileSize < 64 * 1024) { return 1f; } else if (fileSize < 256 * 1024) { return 0.4f; } else if (fileSize < 2 * 1024 * 1024) { return 0.2f; } return 0.1f; } private static boolean isSupportType(String fileExtName) { return IMAGE_TYPE.contains(fileExtName) || VIDEO_TYPE.contains(fileExtName); } public static void deleteFile(FastFileStorageClient fastFileStorageClient,
String fileUrl) { if (StringUtils.isEmpty(fileUrl)) { return; } try { StorePath storePath = StorePath.parseFromUrl(fileUrl); fastFileStorageClient.deleteFile(
storePath.getGroup(), storePath.getPath()); } catch (FdfsUnsupportStorePathException e) { log.warn("异常信息:", e); } }}
3.2 前端上传文件API
import com.github.tobato.fastdfs.service.FastFileStorageClient;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.apache.commons.io.FilenameUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import java.util.ArrayList;import java.util.List;@Slf4j@RestController@RequestMapping("/fdfs")@Api(tags = "文件上传")public class FdfsController { @Value("${fdfs.web-server-url}") private String imageServer; @Autowired private FastFileStorageClient fastFileStorageClient; @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "文件上传", httpMethod = "POST") public String upload(@RequestParam("file") MultipartFile file, @RequestParam(value = "needSize", defaultValue = "0") Byte needSize, @RequestParam(value = "needThumbnail", defaultValue = "0") Byte needThumbnail) { try { long start = System.currentTimeMillis(); // 获取文件后缀 String fileExtName = FilenameUtils.getExtension(
file.getOriginalFilename()).toUpperCase(); UploadFile uploadFile = FdfsUtil.upload(
fastFileStorageClient, file, imageServer, needThumbnail, needSize); long end = System.currentTimeMillis(); log.info("文件上传耗时:{} 耗秒", (end - start)); return JSON.toJSONString(uploadFile); } catch (Exception e) { log.error("上传文件异常:", e); } log.warn(String.format("文件 %s 上传失败", file.getOriginalFilename())); return "上传文件异常"; }}
3.3 服务端上传文件
@Value("${fdfs.web-server-url}") private String imageServer; @Autowired private FastFileStorageClient fastFileStorageClient;
File file = new File("xxxx"); UploadFile uploadFile = FdfsUtil.upload(
fastFileStorageClient, file, imageServer, null, null); String fileUrl = uploadFile.getFullFilename(); file.deleteOnExit(); return fileUrl;
4.FastDFS工作过程
client 连接请求FastDFS,需借助Nginx做http请求代理转发。



