一、搭建FastDFS服务
    搭建参考以下文章:
    第一篇:FastDFS详细介绍 https://blog.csdn.net/m0_37797991/article/details/73381648
    第二篇:FastDFS搭建 https://blog.csdn.net/m0_37797991/article/details/73381739
    第三篇:FastDFS整合Nginx https://blog.csdn.net/m0_37797991/article/details/73385161
    第四篇:FastDFS整合SpringMvc实现上传 https://blog.csdn.net/m0_37797991/article/details/73394873

    因为现在最流行的是springboot,所以我们在这次的例子中采用springboot来整合FastDFS

    常用命令:
    上传文件:/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /www/wwwroot/image/1.jpg
    启动storage_nginx:/usr/local/nginx2/sbin/nginx
    启动tracker_nginx:/usr/local/nginx/sbin/nginx
    启动tracker:service fdfs_trackerd start(创建软连接后)
    启动storage:service fdfs_storaged start(创建软连接后:ln -s /usr/bin/fdfs_storaged /usr/local/bin)
    查看storage是否注册到了tracker中:/usr/bin/fdfs_monitor /etc/fdfs/storage.conf

    二、具体的代码
    配置:

    1. #fastdfs配置
    2. fdfs.connect-timeout=600
    3. fdfs.so-timeout=1500
    4. fdfs.trackerList[0]=60.205.229.111:22122
    5. fdfs.thumbImage.height=150
    6. fdfs.thumbImage.width=150
    7. spring.jmx.enabled=false
    8. fdfs.pool.max-total=200

    配置类:

    1. package com.zym.fdfs.config;
    2. import com.github.tobato.fastdfs.FdfsClientConfig;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.context.annotation.EnableMBeanExport;
    5. import org.springframework.context.annotation.Import;
    6. import org.springframework.jmx.support.RegistrationPolicy;
    7. @Configuration
    8. @Import(FdfsClientConfig.class)//导入client
    9. @EnableMBeanExport(registration= RegistrationPolicy.IGNORE_EXISTING)
    10. public class FastClient {//类名无特定限制
    11. }

    工具类:

    1. package com.zym.fdfs.util;
    2. import cn.hutool.core.codec.Base64;
    3. import com.github.tobato.fastdfs.domain.fdfs.StorePath;
    4. import com.github.tobato.fastdfs.domain.proto.storage.DownloadFileStream;
    5. import com.github.tobato.fastdfs.domain.upload.FastFile;
    6. import com.github.tobato.fastdfs.service.FastFileStorageClient;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.stereotype.Component;
    9. import org.springframework.web.multipart.MultipartFile;
    10. import javax.activation.MimetypesFileTypeMap;
    11. import javax.servlet.http.HttpServletRequest;
    12. import javax.servlet.http.HttpServletResponse;
    13. import java.io.File;
    14. import java.io.FileInputStream;
    15. import java.io.IOException;
    16. import java.security.MessageDigest;
    17. import java.text.DecimalFormat;
    18. import java.text.SimpleDateFormat;
    19. import java.util.Date;
    20. @Component
    21. public class FastFileUtil {
    22. @Autowired
    23. private FastFileStorageClient fastFileStorageClient;
    24. /**
    25. * 定义GB的计算常量
    26. */
    27. private static final int GB = 1024 * 1024 * 1024;
    28. /**
    29. * 定义MB的计算常量
    30. */
    31. private static final int MB = 1024 * 1024;
    32. /**
    33. * 定义KB的计算常量
    34. */
    35. private static final int KB = 1024;
    36. /**
    37. * 格式化小数
    38. */
    39. private static final DecimalFormat DF = new DecimalFormat("0.00");
    40. /**
    41. * MultipartFile转FastFile
    42. */
    43. public static FastFile toFastFile(MultipartFile multipartFile, FileGroup fileGroup) {
    44. // 获取文件名
    45. FastFile file = null;
    46. try {
    47. // MultipartFile to FastFile
    48. file = new FastFile.Builder()
    49. .withFile(multipartFile.getInputStream()
    50. ,multipartFile.getSize()
    51. ,multipartFile.getOriginalFilename())
    52. .toGroup(fileGroup.getGroup())
    53. .build();
    54. } catch (IOException e) {
    55. e.printStackTrace();
    56. }
    57. return file;
    58. }
    59. public StorePath uploadFastFile(MultipartFile multipartFile, FileGroup fileGroup){
    60. FastFile file = null;
    61. StorePath storePath = null;
    62. try {
    63. // MultipartFile to FastFile
    64. file = new FastFile.Builder()
    65. .withFile(multipartFile.getInputStream()
    66. ,multipartFile.getSize()
    67. ,getExtensionName(multipartFile.getOriginalFilename()))
    68. .toGroup(fileGroup.getGroup())
    69. .build();
    70. storePath = fastFileStorageClient.uploadFile(file);
    71. } catch (IOException e) {
    72. e.printStackTrace();
    73. }
    74. return storePath;
    75. }
    76. /**
    77. * 获取文件扩展名,不带 .
    78. */
    79. public static String getExtensionName(String filename) {
    80. if ((filename != null) && (filename.length() > 0)) {
    81. int dot = filename.lastIndexOf('.');
    82. if ((dot >-1) && (dot < (filename.length() - 1))) {
    83. return filename.substring(dot + 1);
    84. }
    85. }
    86. return filename;
    87. }
    88. /**
    89. * Java文件操作 获取不带扩展名的文件名
    90. */
    91. public static String getFileNameNoEx(String filename) {
    92. if ((filename != null) && (filename.length() > 0)) {
    93. int dot = filename.lastIndexOf('.');
    94. if ((dot >-1) && (dot < (filename.length()))) {
    95. return filename.substring(0, dot);
    96. }
    97. }
    98. return filename;
    99. }
    100. /**
    101. * 文件大小转换
    102. */
    103. public static String getSize(long size){
    104. String resultSize;
    105. if (size / GB >= 1) {
    106. //如果当前Byte的值大于等于1GB
    107. resultSize = DF.format(size / (float) GB) + "GB ";
    108. } else if (size / MB >= 1) {
    109. //如果当前Byte的值大于等于1MB
    110. resultSize = DF.format(size / (float) MB) + "MB ";
    111. } else if (size / KB >= 1) {
    112. //如果当前Byte的值大于等于1KB
    113. resultSize = DF.format(size / (float) KB) + "KB ";
    114. } else {
    115. resultSize = size + "B ";
    116. }
    117. return resultSize;
    118. }
    119. /**
    120. * 将文件名解析成文件的上传路径
    121. */
    122. public static File upload(MultipartFile file, String filePath) {
    123. Date date = new Date();
    124. SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
    125. String name = getFileNameNoEx(file.getOriginalFilename());
    126. String suffix = getExtensionName(file.getOriginalFilename());
    127. String nowStr = "-" + format.format(date);
    128. try {
    129. String fileName = name + nowStr + "." + suffix;
    130. String path = filePath + fileName;
    131. // getCanonicalFile 可解析正确各种路径
    132. File dest = new File(path).getCanonicalFile();
    133. // 检测是否存在目录
    134. if (!dest.getParentFile().exists()) {
    135. dest.getParentFile().mkdirs();
    136. }
    137. // 文件写入
    138. file.transferTo(dest);
    139. return dest;
    140. } catch (Exception e) {
    141. e.printStackTrace();
    142. }
    143. return null;
    144. }
    145. public static String fileToBase64(File file) throws Exception {
    146. FileInputStream inputFile = new FileInputStream(file);
    147. String base64;
    148. byte[] buffer = new byte[(int)file.length()];
    149. inputFile.read(buffer);
    150. inputFile.close();
    151. base64= Base64.encode(buffer);
    152. return base64.replaceAll("[\\s*\t\n\r]", "");
    153. }
    154. public static String getFileType(String type) {
    155. String documents = "txt doc pdf ppt pps xlsx xls docx";
    156. String music = "mp3 wav wma mpa ram ra aac aif m4a";
    157. String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
    158. String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
    159. if(image.contains(type)){
    160. return "图片";
    161. } else if(documents.contains(type)){
    162. return "文档";
    163. } else if(music.contains(type)){
    164. return "音乐";
    165. } else if(video.contains(type)){
    166. return "视频";
    167. } else {
    168. return "其他";
    169. }
    170. }
    171. public static String getFileTypeByMimeType(String type) {
    172. String mimeType = new MimetypesFileTypeMap().getContentType("." + type);
    173. return mimeType.split("/")[0];
    174. }
    175. public static void checkSize(long maxSize, long size) throws Exception {
    176. // 1M
    177. int len = 1024 * 1024;
    178. if(size > (maxSize * len)){
    179. throw new Exception("文件超出规定大小");
    180. }
    181. }
    182. private static String getMd5(byte[] bytes) {
    183. // 16进制字符
    184. char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    185. try {
    186. MessageDigest mdTemp = MessageDigest.getInstance("MD5");
    187. mdTemp.update(bytes);
    188. byte[] md = mdTemp.digest();
    189. int j = md.length;
    190. char[] str = new char[j * 2];
    191. int k = 0;
    192. // 移位 输出字符串
    193. for (byte byte0 : md) {
    194. str[k++] = hexDigits[byte0 >>> 4 & 0xf];
    195. str[k++] = hexDigits[byte0 & 0xf];
    196. }
    197. return new String(str);
    198. } catch (Exception e) {
    199. e.printStackTrace();
    200. }
    201. return null;
    202. }
    203. /**
    204. * 下载文件
    205. * @param request /
    206. * @param response /
    207. * @param path /
    208. */
    209. public void downloadFile(HttpServletRequest request, HttpServletResponse response, String path){
    210. response.setHeader("Content-Disposition", "attachment; filename="+getFileName(path));
    211. response.setCharacterEncoding(request.getCharacterEncoding());
    212. response.setContentType("application/octet-stream");
    213. DownloadFileStream downloadCallback = null;
    214. try {
    215. downloadCallback = new DownloadFileStream(response.getOutputStream());
    216. } catch (IOException e) {
    217. e.printStackTrace();
    218. }
    219. StorePath storePath = StorePath.parseFromUrl(path);
    220. fastFileStorageClient.downloadFile(storePath.getGroup(),storePath.getPath(),downloadCallback);
    221. try {
    222. response.flushBuffer();
    223. } catch (IOException e) {
    224. e.printStackTrace();
    225. }
    226. }
    227. // 删除文件
    228. public void deleteFile(String path){
    229. fastFileStorageClient.deleteFile(path);
    230. }
    231. /**
    232. * 文件路径获取文件名
    233. */
    234. public static String getFileName(String filePath) {
    235. if ((filePath != null) && (filePath.length() > 0)) {
    236. int dot = filePath.lastIndexOf('/');
    237. if ((dot >-1) && (dot < (filePath.length() - 1))) {
    238. return filePath.substring(dot + 1);
    239. }
    240. }
    241. return filePath;
    242. }
    243. }

    controller:

    1. package com.zym.fdfs.controller;
    2. import com.github.tobato.fastdfs.domain.fdfs.StorePath;
    3. import com.zym.fdfs.util.FastFileUtil;
    4. import com.zym.fdfs.util.FileGroup;
    5. import io.swagger.annotations.Api;
    6. import io.swagger.annotations.ApiOperation;
    7. import io.swagger.annotations.ApiParam;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.web.bind.annotation.*;
    10. import org.springframework.web.multipart.MultipartFile;
    11. import java.io.IOException;
    12. @Api(tags = "FastDFS测试")
    13. @RestController
    14. @RequestMapping("/api/fastdfs")
    15. public class FileController {
    16. @Autowired
    17. private FastFileUtil fastFileUtil;
    18. @ResponseBody
    19. @ApiOperation(value = "上传文件",httpMethod = "POST")
    20. @PostMapping("/upload")
    21. public String uploadFile(@ApiParam("文件") MultipartFile file) throws IOException {
    22. StorePath storePath = fastFileUtil.uploadFastFile(file,FileGroup.AVATAR);
    23. return "文件上传成功,存储地址为:"+storePath.getFullPath();
    24. }
    25. }

    启动项目进行测试:
    image.png
    用swagger模拟上传文件,上传该图片:
    image.png
    用服务器的ip加上该地址去查看图片:
    image.png
    至此,简单的FastDFS与springboot的整合就完成了
    源码地址:https://gitee.com/zym213/springboot_fastdfs.git