当前位置: 首页 > news >正文

FastDFS分布式文件系统

FastDFS 完整使用指南

本文档基于实际项目,全面讲解 FastDFS 分布式文件系统的使用方法、工作原理和最佳实践


📚 目录

  1. FastDFS 是什么
  2. 项目书写流程概览
  3. 详细开发步骤
  4. FastDFS 工作原理深度解析
  5. 完整的文件操作流程
  6. FastDFS 核心功能详解
  7. 常见问题与解答
  8. 下次开发时的快速上手指南

FastDFS 是什么

🎯 核心定位

FastDFS 是一个开源的分布式文件系统,专为互联网应用设计,具有:

  • 高性能 - 单台服务器可支撑百万级文件存储
  • 高可用 - 支持冗余备份,自动故障转移
  • 分布式 - 支持横向扩展,无中心节点设计
  • 负载均衡 - 自动负载均衡,智能选择存储服务器
  • 轻量级 - 使用 C 语言实现,占用资源少
  • 简单易用 - API 简单,易于集成

🤔 为什么需要 FastDFS?

不使用分布式文件系统的后果:

  • ❌ 文件存储在应用服务器,占用大量磁盘空间
  • ❌ 文件访问消耗应用服务器带宽和性能
  • ❌ 水平扩展困难,无法共享文件
  • ❌ 单点故障风险高
  • ❌ 缺乏文件管理和监控手段

使用 FastDFS 的好处:

  • ✅ 文件与应用分离,互不影响
  • ✅ 支持高并发访问,提供 HTTP 服务
  • ✅ 自动负载均衡和故障转移
  • ✅ 支持主从备份,数据安全可靠
  • ✅ 适合存储大量小文件(4KB - 500MB)
  • ✅ 内置防盗链、限速、访问控制等功能

🏗️ FastDFS 架构组成

FastDFS 架构
│
├─ Tracker Server(跟踪服务器)
│  ├─ 管理 Storage Server
│  ├─ 记录文件存储位置
│  ├─ 负载均衡调度
│  └─ 集群协调
│
├─ Storage Server(存储服务器)
│  ├─ 实际存储文件
│  ├─ 提供文件上传下载
│  ├─ 文件同步备份
│  └─ 文件元数据管理
│
└─ Client(客户端)├─ 应用程序集成├─ 调用 FastDFS API└─ 连接 Tracker/Storage

角色说明:

  1. Tracker Server(跟踪服务器)

    • 负责调度和管理
    • 记录所有 Storage Server 的状态
    • 接收客户端请求,返回可用的 Storage Server
    • 不存储实际文件,只存储元数据
    • 支持集群部署(建议至少 2 台)
  2. Storage Server(存储服务器)

    • 实际存储文件
    • 提供文件上传、下载、删除等操作
    • 支持分组(Group),同组内服务器互为备份
    • 每个组可以独立扩容
    • 内置 HTTP 服务器(通过 Nginx 模块)
  3. Client(客户端)

    • 集成在应用程序中
    • 通过 Java API 连接 FastDFS
    • 上传时连接 Tracker 获取 Storage 地址
    • 下载时可直接连接 Storage 或通过 HTTP

项目书写流程概览

📋 开发步骤清单

第一步:基础配置├─ pom.xml(Maven 依赖 - FastDFS 客户端)├─ application.yml(数据库配置、文件上传大小限制)├─ fdfs.properties(FastDFS 连接配置)└─ 数据库表结构设计(存储文件元信息)第二步:工具类开发 ⭐ 核心└─ FastDFSUtils.java(封装上传、下载、删除、修改操作)第三步:数据层开发├─ 实体类(Flower.java)└─ Mapper 接口(FlowerMapper.java)第四步:业务层开发├─ Service 接口(FlowerService.java)└─ Service 实现(FlowerServiceImpl.java - 调用 FastDFS 工具类)第五步:控制层开发└─ Controller(FlowerController.java - 处理文件上传下载)第六步:启动类└─ SpringBootMain.java第七步:前端页面├─ save.html(文件上传页面 - multipart/form-data)└─ success.html(文件展示页面 - 显示图片和下载链接)第八步:测试└─ DemoTest.java(单元测试 - 测试上传、下载、删除)

详细开发步骤


第一步:基础配置

1.1 创建 Maven 项目

为什么使用 Maven?

  • 依赖管理自动化(不需要手动下载 jar 包)
  • 统一的项目结构
  • 方便的版本管理
  • 简化项目打包和发布

项目结构:

fastdfs01/
├─ src/
│  ├─ main/
│  │  ├─ java/
│  │  │  └─ com/jr/
│  │  │     ├─ controller/      # 控制器
│  │  │     ├─ mapper/          # 数据访问层
│  │  │     ├─ pojo/            # 实体类
│  │  │     ├─ service/         # 业务层
│  │  │     ├─ util/            # 工具类(FastDFS 封装)
│  │  │     └─ SpringBootMain.java  # 启动类
│  │  └─ resources/
│  │     ├─ application.yml     # Spring Boot 配置
│  │     ├─ fdfs.properties     # FastDFS 配置
│  │     ├─ static/             # 静态资源
│  │     └─ templates/          # Thymeleaf 模板
│  └─ test/                     # 测试代码
└─ pom.xml                      # Maven 配置

1.2 配置 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"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.jr.dz18</groupId><artifactId>fastdfs01</artifactId><version>1.0-SNAPSHOT</version><!-- 继承 Spring Boot 父项目,用于版本管理 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.2</version></parent><dependencies><!-- ⭐ FastDFS 客户端核心依赖 --><dependency><groupId>cn.bestwu</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27</version></dependency><!-- Apache Commons Lang3:提供字符串工具类 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency><!-- Spring Boot Web 启动器:提供 Web 功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Thymeleaf 模板引擎:用于渲染 HTML 页面 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- MyBatis 启动器:持久层框架 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency><!-- MySQL 数据库驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- Lombok:简化实体类代码 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- JUnit 测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies><build><!-- 资源拷贝插件:确保配置文件、页面等被正确打包 --><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory><includes><include>**/*.yml</include><include>**/*.xml</include><include>**/*.html</include><include>**/*.js</include><include>**/*.properties</include></includes></resource></resources></build>
</project>

💡 核心依赖说明:

  • fastdfs-client-java:FastDFS 的 Java 客户端,提供文件操作 API
  • commons-lang3:字符串工具类,用于文件扩展名处理
  • spring-boot-starter-web:提供文件上传功能(MultipartFile)

1.3 配置 application.yml

# 服务器端口配置
server:port: 8080# Spring 配置
spring:# 文件上传配置servlet:multipart:max-file-size: 10MB      # 单个文件最大大小max-request-size: 10MB   # 请求最大大小(适用于多文件上传)# 数据源配置datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/jdbc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: root# MyBatis 配置
mybatis:# 实体类包路径type-aliases-package: com.jr.pojo# Mapper XML 文件位置mapper-locations: classpath:com/jr/mapper/*.xml# 配置configuration:# 控制台输出 SQL 语句(开发时方便调试)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

⚙️ 配置说明:

  • max-file-size:限制单个文件大小,防止超大文件占用资源
  • max-request-size:限制整个请求大小
  • 如果上传大文件,需要相应调整这两个参数

1.4 配置 fdfs.properties

# 连接超时时间(秒)
fastdfs.connect_timeout_in_seconds=10# 网络超时时间(秒)
fastdfs.network_timeout_in_seconds=30# 字符编码
fastdfs.charset=UTF-8# ⭐ Tracker 服务器地址(多个用逗号分隔)
# 格式:IP:端口号
# 端口号默认为 22122
fastdfs.tracker_servers=192.168.1.110:22122# HTTP 访问端口(如果配置了 Nginx)
# 默认为 8888,与 Storage 服务器的 Nginx 配置一致
# fastdfs.http_tracker_http_port=8888

🔧 配置说明:

  1. tracker_servers

    • 这是最重要的配置
    • 指定 Tracker Server 的地址和端口
    • 如果有多个 Tracker,用逗号分隔
    • 示例:192.168.1.110:22122,192.168.1.111:22122
  2. 连接超时和网络超时

    • 根据网络环境调整
    • 内网环境可以设置较小值(5-10 秒)
    • 跨机房访问建议设置较大值(30-60 秒)
  3. HTTP 端口

    • 用于直接通过 HTTP 访问文件
    • 需要在 Storage Server 上配置 Nginx + FastDFS 模块
    • 访问格式:http://IP:8888/组名/文件路径

1.5 数据库表结构设计

核心表结构:

-- 花卉表(示例业务表)
CREATE TABLE `flower` (`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '花卉ID',`name` VARCHAR(50) NOT NULL COMMENT '花卉名称',`price` DOUBLE NOT NULL COMMENT '花卉价格',`production` VARCHAR(100) COMMENT '花卉产地',-- ⭐ FastDFS 相关字段`orname` VARCHAR(200) COMMENT '原始文件名',`groupname` VARCHAR(50) COMMENT 'FastDFS 组名(如:group1)',`remotefilename` VARCHAR(200) COMMENT 'FastDFS 远程文件路径'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='花卉信息表';

💡 为什么要保存这些字段?

  1. orname(原始文件名)

    • 保存用户上传的原始文件名
    • 用于下载时设置文件名
    • 用于显示文件信息
  2. groupname(组名)

    • FastDFS 返回的组名(如:group1)
    • 下载和删除时需要提供
    • 示例:group1
  3. remotefilename(远程文件路径)

    • FastDFS 返回的文件存储路径
    • 下载和删除时需要提供
    • 示例:M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.png

完整的文件访问 URL:

http://192.168.1.110:8888/group1/M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.png\_____/ \___________________________________________/组名              远程文件路径

示例数据:

-- 插入测试数据
INSERT INTO flower VALUES 
(1, '玫瑰花', 25.50, '云南', 'rose.jpg', 'group1', 'M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.jpg');INSERT INTO flower VALUES 
(2, '百合花', 30.00, '山东', 'lily.png', 'group1', 'M00/00/00/wKgBbmjd18yAHJK2BBq3vcdVOs9428.png');

第二步:FastDFS 工具类开发 ⭐ 核心

FastDFSUtils.java(完整版)

package com.jr.util;import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;import java.io.*;
import java.util.Properties;/*** FastDFS 工具类* * 功能:* 1. 文件上传(支持 InputStream 和 File)* 2. 文件下载* 3. 文件删除* 4. 文件修改* 5. 获取文件元数据* * 使用静态初始化块在类加载时初始化 FastDFS 客户端连接*/
public final class FastDFSUtils {/*** 定义静态属性,Properties 和 StorageClient* * Properties:存储 fdfs.properties 配置* StorageClient:FastDFS 存储客户端,用于文件操作*/private final static Properties PROPERTIES;private final static StorageClient STORAGE_CLIENT;/*** ⭐ 静态初始化代码块* * 作用:在类加载时执行,初始化 FastDFS 连接* * 执行时机:* 1. 第一次使用 FastDFSUtils 时* 2. 在任何方法调用之前* 3. 只执行一次(单例模式)* * 异常处理:* - 静态初始化块中的异常无法被外部捕获* - 抛出 ExceptionInInitializerError 终止类加载* - 确保不会在连接失败的情况下继续执行*/static {try {// 第一步:创建 Properties 对象PROPERTIES = new Properties();// 第二步:加载 fdfs.properties 配置文件// 使用类加载器从 classpath 读取配置文件PROPERTIES.load(FastDFSUtils.class.getClassLoader().getResourceAsStream("fdfs.properties"));// 第三步:使用 ClientGlobal 初始化 FastDFS 客户端全局配置// 解析配置文件中的 tracker_servers、超时时间等ClientGlobal.initByProperties(PROPERTIES);// 第四步:创建 Tracker 客户端对象TrackerClient trackerClient = new TrackerClient();// 第五步:连接到 Tracker Server// 返回 TrackerServer 对象,代表与 Tracker 的连接TrackerServer trackerServer = trackerClient.getConnection();// 第六步:通过 Tracker 获取可用的 Storage Server// Tracker 会根据负载均衡策略选择一个 StorageStorageServer storageServer = trackerClient.getStoreStorage(trackerServer);// 第七步:创建 Storage 客户端对象// 用于执行文件上传、下载、删除等操作STORAGE_CLIENT = new StorageClient(trackerServer, storageServer);} catch (Exception e) {// 静态初始化异常,抛出 Error 终止程序throw new ExceptionInInitializerError(e);}}/*** 文件上传(通过 InputStream)⭐ 推荐* * 优点:* 1. 支持保存文件元数据(原始文件名、文件大小)* 2. 适合处理 Web 上传(MultipartFile.getInputStream())* 3. 节省内存(流式处理)* * @param inputStream 上传的文件输入流* @param fileName    上传的文件原始名(用于提取扩展名和保存元数据)* @return String[2] - [0]:组名(如:group1),[1]:远程文件路径*/public static String[] uploadFile(InputStream inputStream, String fileName) {try {// 第一步:准备文件元数据(Meta Data)// 元数据会存储在 FastDFS 中,可以通过 API 查询NameValuePair[] meta_list = new NameValuePair[2];// 元数据1:原始文件名meta_list[0] = new NameValuePair("file name", fileName);// 元数据2:文件大小meta_list[1] = new NameValuePair("file length", inputStream.available() + "");// 第二步:将 InputStream 转换为字节数组byte[] file_buff = null;if (inputStream != null) {// 获取文件大小int len = inputStream.available();// 创建字节数组file_buff = new byte[len];// 读取输入流到字节数组inputStream.read(file_buff);}// 第三步:调用 FastDFS API 上传文件// 参数:// - file_buff:文件内容(字节数组)// - getFileExt(fileName):文件扩展名(如:jpg)// - meta_list:元数据String[] fileids = STORAGE_CLIENT.upload_file(file_buff, getFileExt(fileName), meta_list);// 第四步:返回结果// fileids[0] = 组名(如:group1)// fileids[1] = 远程文件路径(如:M00/00/00/xxx.jpg)return fileids;} catch (Exception ex) {ex.printStackTrace();return null;}}/*** 文件上传(通过 File 对象)* * 特点:不保存元数据* * 适用场景:* - 本地文件上传* - 批量处理文件* * @param file     文件对象* @param fileName 文件名* @return String[2] - [0]:组名,[1]:远程文件路径*/public static String[] uploadFile(File file, String fileName) {FileInputStream fis = null;try {// 不保存元数据NameValuePair[] meta_list = null;// 打开文件输入流fis = new FileInputStream(file);// 读取文件内容到字节数组byte[] file_buff = null;if (fis != null) {int len = fis.available();file_buff = new byte[len];fis.read(file_buff);}// 上传文件String[] fileids = STORAGE_CLIENT.upload_file(file_buff, getFileExt(fileName), meta_list);return fileids;} catch (Exception ex) {return null;} finally {// 关闭输入流if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 文件删除* * 注意:* - 删除操作不可逆!* - 删除后文件无法恢复* - 建议使用软删除(数据库标记删除,文件保留)* * @param groupName      组名(如:group1)* @param remoteFileName 远程文件路径(如:M00/00/00/xxx.jpg)* @return 0 为成功,非 0 为失败(具体错误代码)*/public static int deleteFile(String groupName, String remoteFileName) {try {// 调用 FastDFS API 删除文件// 如果 groupName 为空,默认使用 group1int result = STORAGE_CLIENT.delete_file(groupName == null ? "group1" : groupName, remoteFileName);return result;} catch (Exception ex) {return 0;}}/*** 文件修改* * 实现原理:* 1. 上传新文件* 2. 删除旧文件* * 注意:* - 不是真正的"修改",而是"替换"* - 文件路径会改变* - 需要更新数据库中的文件路径* * @param oldGroupName 旧文件组名* @param oldFileName  旧文件路径* @param file         新文件* @param fileName     新文件名* @return String[2] - [0]:新文件组名,[1]:新文件路径*/public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {String[] fileids = null;try {// 第一步:上传新文件fileids = uploadFile(file, fileName);if (fileids == null) {return null;}// 第二步:删除旧文件int delResult = deleteFile(oldGroupName, oldFileName);if (delResult != 0) {return null;}} catch (Exception ex) {return null;}return fileids;}/*** 文件下载* * 返回 InputStream,可以:* 1. 直接输出到浏览器(在线预览或下载)* 2. 保存到本地文件* 3. 进行其他处理(如:压缩、转码)* * @param groupName      组名* @param remoteFileName 远程文件路径* @return InputStream 文件输入流*/public static InputStream downloadFile(String groupName, String remoteFileName) {try {// 调用 FastDFS API 下载文件// 返回字节数组byte[] bytes = STORAGE_CLIENT.download_file(groupName, remoteFileName);// 将字节数组转换为 InputStreamInputStream inputStream = new ByteArrayInputStream(bytes);return inputStream;} catch (Exception ex) {return null;}}/*** 获取文件元数据* * 可以查询:* - 原始文件名* - 文件大小* - 上传时间* - 自定义元数据* * @param groupName      组名* @param remoteFileName 远程文件路径* @return NameValuePair[] 元数据数组*/public static NameValuePair[] getMetaDate(String groupName, String remoteFileName) {try {NameValuePair[] nvp = STORAGE_CLIENT.get_metadata(groupName, remoteFileName);return nvp;} catch (Exception ex) {ex.printStackTrace();return null;}}/*** 获取文件后缀名(不带点)* * 示例:* - "test.jpg" -> "jpg"* - "test.tar.gz" -> "gz"* - "test" -> ""* * @param fileName 文件名* @return 文件扩展名*/private static String getFileExt(String fileName) {if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {return "";} else {return fileName.substring(fileName.lastIndexOf(".") + 1);}}/*** 提供获取 Storage 客户端对象的工具方法* * 用于高级操作(如:Appender 文件、分片上传)* * @return StorageClient 对象*/public static StorageClient getStorageClient() {return STORAGE_CLIENT;}/*** 私有构造器,防止实例化* * 工具类不应该被实例化,所有方法都是静态的*/private FastDFSUtils() {}
}

🔑 核心理解点:

  1. 静态初始化块的作用

    • 在类加载时执行一次
    • 初始化 FastDFS 连接
    • 连接失败会抛出 Error 终止程序
  2. 为什么使用静态成员

    • 避免重复创建连接
    • 提高性能(连接复用)
    • 线程安全(StorageClient 是线程安全的)
  3. 上传方法的选择

    • Web 应用:使用 uploadFile(InputStream, String)
    • 本地文件:使用 uploadFile(File, String)
  4. 文件路径的组成

    group1/M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.jpg
    \_____/ \___________________________________________/组名              远程文件路径
    

第三步:实体类

Flower.java

package com.jr.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;import java.io.Serializable;/*** 花卉实体类* 对应数据库的 flower 表*/
@Component          // 注册为 Spring Bean
@AllArgsConstructor // Lombok:自动生成全参构造器
@NoArgsConstructor  // Lombok:自动生成无参构造器
@Data               // Lombok:自动生成 getter/setter/toString/equals/hashCode
public class Flower implements Serializable {private Integer id;            // 花卉IDprivate String name;           // 花卉名称private Double price;          // 花卉价格private String production;     // 花卉产地// ⭐ FastDFS 相关字段private String orname;         // 原始文件名private String groupname;      // FastDFS 组名(如:group1)private String remotefilename; // FastDFS 远程文件路径
}

💡 为什么要实现 Serializable?

  • 支持对象序列化
  • 可以存储到 Session
  • 可以通过网络传输
  • 可以缓存到 Redis

第四步:Mapper 层(数据访问层)

FlowerMapper.java

package com.jr.mapper;import com.jr.pojo.Flower;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;import java.util.List;/*** 花卉数据访问层*/
@Component
@Mapper  // MyBatis 注解,标记为 Mapper 接口
public interface FlowerMapper {/*** 插入花卉信息* * 包含 FastDFS 返回的组名和文件路径* * @param flower 花卉对象* @return 影响行数*/@Insert("INSERT INTO flower VALUES(" +"DEFAULT, #{name}, #{price}, #{production}, " +"#{orname}, #{groupname}, #{remotefilename})")int insert(Flower flower);/*** 查询所有花卉* * 用于前端展示列表* * @return 花卉列表*/@Select("SELECT * FROM flower")List<Flower> selectAll();
}

🔍 SQL 解析:

-- 插入语句
INSERT INTO flower VALUES(DEFAULT,              -- id 自增'玫瑰花',              -- name25.50,                -- price'云南',                -- production'rose.jpg',           -- orname(原始文件名)'group1',             -- groupname(FastDFS 组名)'M00/00/00/xxx.jpg'   -- remotefilename(FastDFS 路径)
)

第五步:Service 层(业务层)

5.1 FlowerService.java(接口)

package com.jr.service;import com.jr.pojo.Flower;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.List;/*** 花卉业务接口*/
public interface FlowerService {/*** 保存花卉信息(包含文件上传)* * @param flower 花卉信息* @param photo  上传的文件* @return 影响行数* @throws IOException IO 异常*/int save(Flower flower, MultipartFile photo) throws IOException;/*** 查询所有花卉* * @return 花卉列表*/List<Flower> findAll();
}

5.2 FlowerServiceImpl.java(实现类)⭐ 核心

package com.jr.service.Impl;import com.jr.mapper.FlowerMapper;
import com.jr.pojo.Flower;
import com.jr.service.FlowerService;
import com.jr.util.FastDFSUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.io.InputStream;
import java.util.List;/*** 花卉业务实现类* * ⭐ 核心逻辑:* 1. 将上传的文件保存到 FastDFS* 2. 获取 FastDFS 返回的组名和文件路径* 3. 将文件信息和业务数据一起保存到数据库*/
@Service  // 注册为 Spring Bean
public class FlowerServiceImpl implements FlowerService {@Autowiredprivate FlowerMapper flowerMapper;/*** 保存花卉信息(包含文件上传)* * 执行流程:* 1. 获取上传文件的输入流* 2. 调用 FastDFSUtils 上传文件到 FastDFS* 3. 获取 FastDFS 返回的组名和路径* 4. 将文件信息设置到 flower 对象* 5. 插入数据库* * @param flower 花卉信息* @param photo  上传的文件(MultipartFile)* @return 影响行数* @throws IOException IO 异常*/@Overridepublic int save(Flower flower, MultipartFile photo) throws IOException {// 第一步:获取上传文件的输入流InputStream inputStream = photo.getInputStream();// 第二步:调用 FastDFSUtils 上传文件// 返回数组:[0]=组名,[1]=文件路径String[] strings = FastDFSUtils.uploadFile(inputStream, photo.getOriginalFilename());// 第三步:设置文件信息到 flower 对象flower.setOrname(photo.getOriginalFilename());  // 原始文件名flower.setGroupname(strings[0]);                // 组名(如:group1)flower.setRemotefilename(strings[1]);           // 文件路径(如:M00/00/00/xxx.jpg)// 第四步:插入数据库return flowerMapper.insert(flower);}/*** 查询所有花卉* * @return 花卉列表*/@Overridepublic List<Flower> findAll() {return flowerMapper.selectAll();}
}

🔍 深度解析:MultipartFile 是什么?

// MultipartFile 是 Spring 提供的文件上传接口
// 常用方法:MultipartFile photo = ...;// 1. 获取原始文件名
String fileName = photo.getOriginalFilename();
// 示例:rose.jpg// 2. 获取文件大小(字节)
long size = photo.getSize();
// 示例:1024000(约 1MB)// 3. 获取文件类型(MIME Type)
String contentType = photo.getContentType();
// 示例:image/jpeg// 4. 获取输入流(⭐ 最常用)
InputStream inputStream = photo.getInputStream();
// 用于读取文件内容// 5. 判断是否为空
boolean isEmpty = photo.isEmpty();
// true 表示用户没有选择文件// 6. 保存到本地(不推荐,应该用 FastDFS)
photo.transferTo(new File("D:/upload/rose.jpg"));

第六步:Controller 层

FlowerController.java

package com.jr.controller;import com.jr.pojo.Flower;
import com.jr.service.FlowerService;
import com.jr.util.FastDFSUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;/*** 花卉控制器* * 功能:* 1. 页面路由* 2. 文件上传* 3. 文件下载* 4. 数据查询*/
@Controller
public class FlowerController {@Autowiredprivate FlowerService flowerService;/*** 动态路由处理* * 示例:* - 访问 /save → 返回 "save" → 渲染 save.html* - 访问 /success → 返回 "success" → 渲染 success.html* * @param url 路径变量* @return 视图名称*/@RequestMapping("/{url}")public String url(@PathVariable String url) {return url;}/*** 文件上传处理 ⭐ 核心* * 处理流程:* 1. 接收表单参数(flower 对象)* 2. 接收上传的文件(photo)* 3. 调用 Service 保存(包含文件上传到 FastDFS)* 4. 返回结果页面* * @param flower 花卉信息* @param photo  上传的文件* @return 视图名称* @throws IOException IO 异常*/@RequestMapping("/save1")public String save(Flower flower, MultipartFile photo) throws IOException {int save = flowerService.save(flower, photo);if (save > 0) {return "success";  // 跳转到成功页面} else {return "save";     // 返回上传页面}}/*** 查询所有花卉(Ajax 接口)* * @ResponseBody:将返回值转换为 JSON* * 返回示例:* [*   {*     "id": 1,*     "name": "玫瑰花",*     "price": 25.5,*     "production": "云南",*     "orname": "rose.jpg",*     "groupname": "group1",*     "remotefilename": "M00/00/00/xxx.jpg"*   }* ]* * @return 花卉列表(JSON)*/@RequestMapping("/getAll")@ResponseBodypublic List<Flower> getAll() {return flowerService.findAll();}/*** 文件下载 ⭐ 核心* * 处理流程:* 1. 从 FastDFS 下载文件(得到 InputStream)* 2. 设置响应头(告诉浏览器这是一个下载文件)* 3. 将 InputStream 写入响应的 OutputStream* 4. 浏览器弹出下载对话框* * @param gname    组名(如:group1)* @param orname   远程文件路径(如:M00/00/00/xxx.jpg)* @param response HttpServletResponse 对象* @throws IOException IO 异常*/@RequestMapping("/download")@ResponseBodypublic void download(String gname, String orname, HttpServletResponse response) throws IOException {// 第一步:生成随机文件名(防止中文乱码)// UUID 确保文件名唯一String uuname = UUID.randomUUID() + ".png";// 第二步:设置响应头// content-disposition:告诉浏览器这是一个附件,需要下载// attachment:以附件形式下载// filename:下载时的文件名response.setHeader("content-disposition", "attachment;filename=" + uuname);// 第三步:从 FastDFS 下载文件InputStream inputStream = FastDFSUtils.downloadFile(gname, orname);// 第四步:获取响应的输出流ServletOutputStream outputStream = response.getOutputStream();// 第五步:将输入流的内容复制到输出流// IOUtils.copy():Apache Commons IO 提供的工具方法IOUtils.copy(inputStream, outputStream);// 第六步:关闭流outputStream.close();inputStream.close();}
}

🔑 关键理解点:

  1. MultipartFile 参数自动绑定

    // 表单中的 name="photo" 会自动绑定到参数
    public String save(Flower flower, MultipartFile photo)
    
  2. 文件下载的响应头

    // attachment:附件(下载)
    response.setHeader("content-disposition", "attachment;filename=" + fileName);// inline:内联(在线预览,适用于图片、PDF)
    response.setHeader("content-disposition", "inline;filename=" + fileName);
    
  3. 流的复制

    // 手动复制(不推荐)
    byte[] buffer = new byte[1024];
    int len;
    while ((len = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, len);
    }// 使用工具类(推荐)
    IOUtils.copy(inputStream, outputStream);
    

第七步:启动类

SpringBootMain.java

package com.jr;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Spring Boot 启动类*/
@SpringBootApplication  // 标记为 Spring Boot 应用
public class SpringBootMain {public static void main(String[] args) {SpringApplication.run(SpringBootMain.class, args);System.out.println("========================================");System.out.println("⭐ FastDFS 应用启动成功!");System.out.println("⭐ 访问地址:http://localhost:8080/save");System.out.println("========================================");}
}

启动时会发生什么?

1. Spring Boot 启动
2. 加载 FastDFSUtils 类
3. 执行静态初始化块- 读取 fdfs.properties- 连接 Tracker Server- 创建 StorageClient
4. 扫描所有 @Component, @Service, @Controller 注解的类
5. 创建 Bean 并注入依赖关系
6. MyBatis 扫描 Mapper 接口
7. Thymeleaf 配置模板路径
8. 启动内置 Tomcat,监听 8080 端口
9. 应用就绪,可以接受请求

第八步:前端页面

8.1 save.html(文件上传页面)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>花卉添加</title><style>body {font-family: Arial, sans-serif;background-color: #f5f5f5;padding: 50px;}h2 {color: #333;}form {background-color: white;padding: 30px;border-radius: 10px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);max-width: 500px;}p {margin-bottom: 15px;}input[type="text"], input[type="file"] {width: 100%;padding: 10px;border: 1px solid #ddd;border-radius: 5px;box-sizing: border-box;}input[type="submit"] {width: 100%;padding: 12px;background-color: #4CAF50;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;}input[type="submit"]:hover {background-color: #45a049;}</style>
</head>
<body><h2>花卉信息添加</h2><!-- ⭐ 文件上传表单的三个要点:1. method="post"      - 必须是 POST 请求2. enctype="multipart/form-data"  - 必须设置(支持文件上传)3. input type="file"  - 文件选择控件--><form action="/save1" method="post" enctype="multipart/form-data"><p>花卉名称:<input type="text" name="name" required/></p><p>花卉价格:<input type="text" name="price" required/></p><p>花卉产地:<input type="text" name="production" required/></p><p>花卉图片:<input type="file" name="photo" accept="image/*" required/></p><p><input type="submit" value="提交"/></p></form><p style="margin-top: 20px;"><a href="/success">查看已上传的花卉</a></p>
</body>
</html>

🔑 关键点:

  1. enctype=“multipart/form-data”

    • 必须设置!否则无法上传文件
    • 告诉浏览器使用 multipart 编码
    • 支持文件二进制传输
  2. name 属性的对应关系

    <!-- 前端 -->
    <input type="text" name="name"/>
    <input type="file" name="photo"/><!-- 后端 -->
    public String save(Flower flower, MultipartFile photo)
    // name 字段自动绑定到 flower.name
    // photo 字段自动绑定到 photo 参数
    
  3. accept 属性

    <!-- 只允许上传图片 -->
    <input type="file" accept="image/*"/><!-- 只允许上传 PDF -->
    <input type="file" accept="application/pdf"/><!-- 允许多种类型 -->
    <input type="file" accept="image/*,.pdf,.doc,.docx"/>
    

8.2 success.html(文件展示页面)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>花卉列表</title><!-- 引入 jQuery --><script type="text/javascript" src="../js/jquery-1.8.3.js"></script><style>body {font-family: Arial, sans-serif;background-color: #f5f5f5;padding: 50px;}h2 {color: #333;}table {width: 100%;background-color: white;border-collapse: collapse;box-shadow: 0 2px 10px rgba(0,0,0,0.1);}thead {background-color: #4CAF50;color: white;}td {padding: 12px;text-align: center;border-bottom: 1px solid #ddd;}img {cursor: pointer;transition: transform 0.3s;}img:hover {transform: scale(3);}a {color: #4CAF50;text-decoration: none;}a:hover {text-decoration: underline;}</style><script type="text/javascript">$(document).ready(function () {// 页面加载完成后,发送 Ajax 请求获取花卉列表$.get("getAll", function (dt) {// dt 是服务器返回的 JSON 数组JSON.stringify(dt);// 清空表格内容$("tbody").empty();// 遍历数据,动态生成表格行for (var i = 0; i < dt.length; i++) {// 构造完整的图片 URL// 格式:http://IP:端口/组名/文件路径var imgUrl = 'http://192.168.1.110:8888/' + dt[i].groupname + '/' + dt[i].remotefilename;// 创建表格行$("<tr>" +"<td>" + dt[i].id + "</td>" +"<td>" + dt[i].name + "</td>" +"<td>" + dt[i].price + "</td>" +"<td>" + dt[i].production + "</td>" +"<td>" +"<img height='20px' width='20px' " +"title='" + dt[i].orname + "' " +"src='" + imgUrl + "'/>" +"</td>" +"<td>" +"<a href='download?gname=" + dt[i].groupname + "&&orname=" + dt[i].remotefilename + "'>下载</a>" +"</td>" +"</tr>").appendTo("tbody");}});});</script>
</head>
<body><h2>花卉信息列表</h2><table><thead><tr><td>花卉编号</td><td>花卉名称</td><td>价钱</td><td>产地</td><td>图片</td><td>操作</td></tr></thead><tbody></tbody></table><p style="margin-top: 20px;"><a href="/save">添加新花卉</a></p>
</body>
</html>

🔍 图片访问原理:

1. 前端构造 URL:http://192.168.1.110:8888/group1/M00/00/00/xxx.jpg2. 浏览器发送请求到 Storage Server 的 Nginx3. Nginx + FastDFS 模块解析请求:- 组名:group1- 文件路径:M00/00/00/xxx.jpg4. Nginx 从磁盘读取文件:/data/fastdfs/storage/data/M00/00/00/xxx.jpg5. 返回文件内容给浏览器6. 浏览器显示图片

第九步:单元测试

DemoTest.java

package com.jr;import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.junit.Test;
import com.jr.util.FastDFSUtils;import java.io.*;
import java.util.Arrays;/*** FastDFS 功能测试*/
public class DemoTest {/*** 测试文件上传* * 两种上传方式:* 1. File 对象:不保存元数据* 2. InputStream:保存元数据(推荐)*/@Testpublic void test01() throws FileNotFoundException {// 方式1:使用 File 上传(不保存元数据)/*String[] strings = FastDFSUtils.uploadFile(new File("C:\\Users\\CuiDa\\Desktop\\壁纸\\1.png"), "1.png");System.out.println(Arrays.toString(strings));*/// 方式2:使用 InputStream 上传(保存元数据)⭐ 推荐String[] strings = FastDFSUtils.uploadFile(new FileInputStream(new File("C:\\Users\\CuiDa\\Desktop\\壁纸\\1.png")),"1.png");// 输出结果:[group1, M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.png]System.out.println(Arrays.toString(strings));System.out.println("组名:" + strings[0]);System.out.println("文件路径:" + strings[1]);// ⭐ 访问 URL:// http://192.168.1.110:8888/group1/M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.png}/*** 测试文件下载*/@Testpublic void test2() throws IOException {// 第一步:从 FastDFS 下载文件(得到 InputStream)InputStream inputStream = FastDFSUtils.downloadFile("group1", "M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.png");// 第二步:指定本地保存路径OutputStream outputStream = new FileOutputStream("D:\\fastdfs\\11.png");// 第三步:复制流IOUtils.copy(inputStream, outputStream);// 第四步:关闭流inputStream.close();outputStream.close();System.out.println("文件下载成功!保存到:D:\\fastdfs\\11.png");}/*** 测试文件删除*/@Testpublic void test3() {// 删除文件// 返回值:0 表示成功,非 0 表示失败int result = FastDFSUtils.deleteFile("group1", "M00/00/00/wKgBbmjeGuyAWIWOAAp2ubcUNr8520.png");if (result == 0) {System.out.println("文件删除成功!");} else {System.out.println("文件删除失败!错误代码:" + result);}}
}

FastDFS 工作原理深度解析

🔍 核心概念

1. FastDFS 文件上传流程
客户端(应用程序)↓
① 发送上传请求到 Tracker Server"我要上传一个文件,请分配 Storage"↓
Tracker Server↓
② 根据负载均衡策略选择一个 Storage Server策略:轮询、按剩余空间、按上传次数等↓
③ 返回 Storage Server 的 IP 和端口"你可以将文件上传到 192.168.1.110:23000"↓
客户端↓
④ 连接到指定的 Storage Server↓
⑤ 上传文件内容(二进制流)↓
Storage Server↓
⑥ 将文件保存到磁盘路径生成规则:/data/fastdfs/storage/data/M00/00/00/xxx.jpg⑦ 生成文件ID(包含组名和路径)group1/M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.jpg⑧ 如果配置了主从复制,同步到从Storage↓
⑨ 返回文件ID给客户端↓
客户端↓
⑩ 保存文件ID到数据库

💡 关键理解点:

  1. Tracker 不存储文件

    • Tracker 只负责调度和管理
    • 文件实际存储在 Storage Server
    • Tracker 存储的是元数据(哪个文件在哪个 Storage)
  2. 文件路径的含义

    M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.jpg
    \_/ \___/ \________________________________/|    |              ||    |              文件名(自动生成,包含时间戳、IP等信息)|    二级目录(根据文件数量自动创建)存储路径(M00 对应配置文件中的第一个 store_path)
    
  3. 负载均衡策略

    • 轮询(Round Robin)
    • 随机(Random)
    • 按剩余空间(Free Space)
    • 按上传次数(Upload Count)
2. FastDFS 文件下载流程

方式1:通过客户端API下载

客户端↓
① 发送下载请求到 Tracker(提供文件ID)"我要下载 group1/M00/00/00/xxx.jpg"↓
Tracker Server↓
② 根据组名(group1)查找对应的 Storage Server↓
③ 返回 Storage Server 的 IP 和端口"文件在 192.168.1.110:23000"↓
客户端↓
④ 连接到 Storage Server↓
⑤ 发送文件路径↓
Storage Server↓
⑥ 从磁盘读取文件↓
⑦ 返回文件内容(二进制流)↓
客户端↓
⑧ 接收文件内容

方式2:通过HTTP直接访问(推荐)

浏览器↓
① 访问 URLhttp://192.168.1.110:8888/group1/M00/00/00/xxx.jpg↓
Nginx(Storage Server 上)↓
② FastDFS Nginx 模块解析 URL- 组名:group1- 文件路径:M00/00/00/xxx.jpg↓
③ 根据路径读取文件/data/fastdfs/storage/data/M00/00/00/xxx.jpg↓
④ 返回文件内容↓
浏览器↓
⑤ 显示图片或下载文件

💡 为什么推荐HTTP方式?

  • ✅ 直接访问,无需经过应用服务器
  • ✅ 减轻应用服务器压力
  • ✅ 充分利用 Nginx 的性能优势
  • ✅ 支持浏览器缓存
  • ✅ 支持断点续传
3. FastDFS 文件同步机制
主 Storage Server                   从 Storage Server↓                                   ↑
① 接收客户端上传                         |↓                                   |
② 保存文件到磁盘                         |↓                                   |
③ 将文件写入 binlog                      |↓                                   |
④ 通过 binlog 同步到从 Storage  ----------┘

同步特点:

  • 异步同步(不影响上传性能)
  • 断点续传(网络故障后自动恢复)
  • 增量同步(只同步变化的文件)
  • 一主多从(一个主 Storage 可以有多个从 Storage)

完整的文件操作流程

场景 1:用户上传图片

用户在浏览器选择图片(rose.jpg)→ 点击"提交"按钮↓
POST /save1(multipart/form-data)↓
1. FlowerController.save() 接收请求- Flower 对象:{name:"玫瑰花", price:25.5, production:"云南"}- MultipartFile 对象:{originalFilename:"rose.jpg", size:102400, ...}↓
2. FlowerService.save() 业务处理- 获取文件输入流:photo.getInputStream()↓
3. FastDFSUtils.uploadFile() 上传到 FastDFS- 连接 Tracker Server- Tracker 返回 Storage Server 地址- 连接 Storage Server- 上传文件内容- Storage 保存文件到磁盘- 返回文件ID:["group1", "M00/00/00/xxx.jpg"]↓
4. 设置文件信息到 Flower 对象- flower.setOrname("rose.jpg")- flower.setGroupname("group1")- flower.setRemotefilename("M00/00/00/xxx.jpg")↓
5. FlowerMapper.insert() 保存到数据库- 插入记录到 flower 表↓
6. 返回 "success" 视图↓
7. Thymeleaf 渲染 success.html↓
8. 浏览器显示成功页面

场景 2:用户查看图片列表

用户访问:http://localhost:8080/success↓
1. FlowerController.url("success")- 返回 "success" 视图↓
2. Thymeleaf 渲染 success.html- 返回 HTML 页面给浏览器↓
3. 浏览器执行 JavaScript- jQuery 发送 Ajax 请求:$.get("/getAll")↓
4. FlowerController.getAll()- 调用 FlowerService.findAll()- 调用 FlowerMapper.selectAll()- 从数据库查询所有花卉记录- 返回 JSON 数组↓
5. JavaScript 接收数据- 遍历数据,动态生成表格行- 构造图片 URL:http://192.168.1.110:8888/group1/M00/00/00/xxx.jpg- 插入到表格↓
6. 浏览器加载图片- 向 FastDFS 的 Nginx 发送请求- Nginx 返回图片内容- 浏览器显示图片

场景 3:用户下载文件

用户点击"下载"链接↓
GET /download?gname=group1&orname=M00/00/00/xxx.jpg↓
1. FlowerController.download()- 接收参数:gname="group1", orname="M00/00/00/xxx.jpg"↓
2. FastDFSUtils.downloadFile()- 连接 Tracker Server- Tracker 返回 Storage Server 地址- 连接 Storage Server- 发送下载请求- Storage 返回文件内容(字节数组)- 转换为 InputStream↓
3. 设置响应头- content-disposition: attachment;filename=xxx.png- 告诉浏览器这是一个下载文件↓
4. 将 InputStream 写入响应流- IOUtils.copy(inputStream, outputStream)↓
5. 浏览器弹出下载对话框- 用户选择保存位置- 文件保存到本地

FastDFS 核心功能详解

功能 1:文件上传

实现方式:

// 方式1:通过 InputStream(推荐)
InputStream is = multipartFile.getInputStream();
String[] result = FastDFSUtils.uploadFile(is, "photo.jpg");// 方式2:通过 File 对象
File file = new File("D:/test.jpg");
String[] result = FastDFSUtils.uploadFile(file, "test.jpg");

返回值:

String[] result = ["group1", "M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.jpg"];
result[0] // 组名
result[1] // 文件路径

完整的访问URL:

http://192.168.1.110:8888/group1/M00/00/00/wKgBbmjd17yABDG1AAp2ubcUNr8317.jpg
\___________________/\_____/\___________________________________________/服务器地址         组名              文件路径

功能 2:文件下载

实现方式:

// 下载文件(返回 InputStream)
InputStream is = FastDFSUtils.downloadFile("group1", "M00/00/00/xxx.jpg");// 保存到本地
FileOutputStream fos = new FileOutputStream("D:/download.jpg");
IOUtils.copy(is, fos);
fos.close();
is.close();// 或者直接输出到浏览器(在 Controller 中)
ServletOutputStream os = response.getOutputStream();
IOUtils.copy(is, os);
os.close();
is.close();

功能 3:文件删除

实现方式:

int result = FastDFSUtils.deleteFile("group1", "M00/00/00/xxx.jpg");
if (result == 0) {System.out.println("删除成功");
} else {System.out.println("删除失败,错误代码:" + result);
}

注意事项:

  • ⚠️ 删除操作不可逆!
  • ⚠️ 建议使用软删除(数据库标记,文件保留)
  • ⚠️ 删除前确认文件没有被其他地方引用

功能 4:文件修改

实现方式:

// 上传新文件并删除旧文件
String[] result = FastDFSUtils.modifyFile("group1",              // 旧文件组名"M00/00/00/old.jpg",   // 旧文件路径new File("D:/new.jpg"), // 新文件"new.jpg"              // 新文件名
);// 更新数据库中的文件路径
flower.setGroupname(result[0]);
flower.setRemotefilename(result[1]);
flowerMapper.update(flower);

功能 5:获取文件元数据

实现方式:

NameValuePair[] metadata = FastDFSUtils.getMetaDate("group1", "M00/00/00/xxx.jpg");
for (NameValuePair pair : metadata) {System.out.println(pair.getName() + " = " + pair.getValue());
}// 输出:
// file name = rose.jpg
// file length = 102400

常见问题与解答

Q1:FastDFS 和传统文件存储的区别?

A: 主要区别:

特性传统存储(应用服务器)FastDFS(分布式)
存储位置应用服务器磁盘独立的存储服务器
扩展性难以扩展易于横向扩展
性能占用应用服务器资源专用存储,性能高
高可用单点故障支持主从备份
负载均衡需要手动实现自动负载均衡
访问方式通过应用服务器直接 HTTP 访问

Q2:上传文件时出现"连接超时"错误怎么办?

A: 排查步骤:

  1. 检查 Tracker Server 是否启动

    # Linux 命令
    ps -ef | grep fdfs_trackerd# 如果没有运行,启动 Tracker
    /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
    
  2. 检查网络连接

    # 测试端口是否开放
    telnet 192.168.1.110 22122# 检查防火墙
    firewall-cmd --list-ports
    
  3. 检查配置文件

    # fdfs.properties
    fastdfs.tracker_servers=192.168.1.110:22122  # IP 和端口是否正确?
    fastdfs.connect_timeout_in_seconds=10         # 超时时间是否太短?
    
  4. 增加超时时间

    fastdfs.connect_timeout_in_seconds=30
    fastdfs.network_timeout_in_seconds=60
    

Q3:图片无法显示(404 错误)怎么办?

A: 排查步骤:

  1. 检查 Storage Server 的 Nginx 是否启动

    ps -ef | grep nginx# 启动 Nginx
    /usr/local/nginx/sbin/nginx
    
  2. 检查 Nginx 配置

    # /usr/local/nginx/conf/nginx.conf
    location ~ /group[0-9]/ {ngx_fastdfs_module;
    }
    
  3. 检查访问 URL 是否正确

    正确格式:
    http://192.168.1.110:8888/group1/M00/00/00/xxx.jpg常见错误:
    - 缺少组名:http://...//M00/00/00/xxx.jpg
    - 端口错误:http://...:22122/... (应该是 8888)
    - 路径错误:http://.../group1/M00/xxx.jpg (缺少目录)
    
  4. 检查文件是否真实存在

    # 在 Storage Server 上查看
    ls -l /data/fastdfs/storage/data/M00/00/00/
    

Q4:文件上传大小限制怎么调整?

A: 需要修改多处配置:

  1. Spring Boot 配置(application.yml)

    spring:servlet:multipart:max-file-size: 100MB      # 单个文件最大 100MBmax-request-size: 100MB   # 请求最大 100MB
    
  2. Nginx 配置(如果通过 Nginx 上传)

    # /usr/local/nginx/conf/nginx.conf
    http {client_max_body_size 100m;  # 允许上传 100MB
    }
    
  3. 重启服务

    # 重启 Nginx
    /usr/local/nginx/sbin/nginx -s reload# 重启 Spring Boot 应用
    

Q5:如何实现文件秒传(相同文件只存一份)?

A: FastDFS 默认不支持,需要自己实现:

@Service
public class FileService {/*** 文件上传(支持秒传)*/public String upload(MultipartFile file) throws Exception {// 1. 计算文件 MD5String md5 = DigestUtils.md5Hex(file.getInputStream());// 2. 查询数据库,看是否已存在相同 MD5 的文件FileInfo existFile = fileMapper.selectByMd5(md5);if (existFile != null) {// 文件已存在,秒传成功(返回已有的文件路径)return existFile.getFilePath();}// 3. 文件不存在,上传到 FastDFSString[] result = FastDFSUtils.uploadFile(file.getInputStream(), file.getOriginalFilename());// 4. 保存文件信息到数据库(包含 MD5)FileInfo fileInfo = new FileInfo();fileInfo.setMd5(md5);fileInfo.setGroupName(result[0]);fileInfo.setRemoteFileName(result[1]);fileMapper.insert(fileInfo);return fileInfo.getFilePath();}
}

下次开发时的快速上手指南

🚀 快速开发步骤(FastDFS 项目)

第一步:引入依赖
<!-- pom.xml -->
<dependency><groupId>cn.bestwu</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version>
</dependency>
第二步:配置文件
# fdfs.properties
fastdfs.connect_timeout_in_seconds=10
fastdfs.network_timeout_in_seconds=30
fastdfs.charset=UTF-8
fastdfs.tracker_servers=192.168.1.110:22122
# application.yml
spring:servlet:multipart:max-file-size: 10MBmax-request-size: 10MB
第三步:复制工具类
复制 FastDFSUtils.java 到项目的 util 包
第四步:实体类添加字段
private String orname;         // 原始文件名
private String groupname;      // FastDFS 组名
private String remotefilename; // FastDFS 文件路径
第五步:Service 层调用
@Service
public class FileServiceImpl {public int upload(Entity entity, MultipartFile file) throws IOException {// 上传到 FastDFSString[] result = FastDFSUtils.uploadFile(file.getInputStream(), file.getOriginalFilename());// 设置文件信息entity.setOrname(file.getOriginalFilename());entity.setGroupname(result[0]);entity.setRemotefilename(result[1]);// 保存到数据库return mapper.insert(entity);}
}
第六步:Controller 处理
@Controller
public class FileController {// 上传@RequestMapping("/upload")public String upload(Entity entity, MultipartFile file) throws IOException {service.upload(entity, file);return "success";}// 下载@RequestMapping("/download")@ResponseBodypublic void download(String gname, String rname, HttpServletResponse response) throws IOException {response.setHeader("content-disposition", "attachment;filename=" + UUID.randomUUID() + ".jpg");InputStream is = FastDFSUtils.downloadFile(gname, rname);IOUtils.copy(is, response.getOutputStream());is.close();}
}
第七步:前端页面
<!-- 上传表单 -->
<form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file" required/><button type="submit">上传</button>
</form><!-- 显示图片 -->
<img src="http://192.168.1.110:8888/{{groupname}}/{{remotefilename}}"/><!-- 下载链接 -->
<a href="/download?gname={{groupname}}&rname={{remotefilename}}">下载</a>

📋 核心配置清单

配置项说明示例
tracker_serversTracker 服务器地址192.168.1.110:22122
connect_timeout连接超时时间(秒)10
network_timeout网络超时时间(秒)30
max-file-size最大文件大小10MB
HTTP 端口Nginx 端口8888

🎯 最佳实践

  1. 文件命名规范

    使用 FastDFS 自动生成的文件名,不要自定义
    原因:
    - 自动包含时间戳
    - 自动包含服务器信息
    - 防止文件名冲突
    
  2. 数据库设计

    -- 必须保存的字段
    CREATE TABLE file_info (orname VARCHAR(200),         -- 原始文件名groupname VARCHAR(50),       -- 组名remotefilename VARCHAR(200)  -- 文件路径
    );-- 可选字段
    file_size BIGINT,              -- 文件大小
    file_type VARCHAR(50),         -- 文件类型
    upload_time DATETIME,          -- 上传时间
    md5 VARCHAR(32)                -- 文件 MD5(用于秒传)
    
  3. 异常处理

    try {String[] result = FastDFSUtils.uploadFile(...);if (result == null) {throw new RuntimeException("文件上传失败");}
    } catch (Exception e) {log.error("文件上传异常", e);throw new BusinessException("文件上传失败,请稍后重试");
    }
    
  4. 性能优化

    // 1. 使用连接池(FastDFSUtils 已实现单例)
    // 2. 大文件使用异步上传
    @Async
    public void uploadAsync(MultipartFile file) {// 异步上传
    }// 3. 图片压缩后再上传
    BufferedImage compressed = Thumbnails.of(file.getInputStream()).scale(0.5)  // 缩小到 50%.asBufferedImage();
    
  5. 安全防护

    // 1. 文件类型校验
    String contentType = file.getContentType();
    if (!contentType.startsWith("image/")) {throw new RuntimeException("只允许上传图片");
    }// 2. 文件大小校验
    if (file.getSize() > 10 * 1024 * 1024) {throw new RuntimeException("文件大小不能超过 10MB");
    }// 3. 文件名校验
    String fileName = file.getOriginalFilename();
    if (fileName.contains("../") || fileName.contains("..\\")) {throw new RuntimeException("文件名非法");
    }
    

🎓 总结

FastDFS 的核心价值

  1. 解决文件存储问题

    • 文件与应用分离
    • 支持海量文件存储
    • 提供高性能访问
  2. 提高系统可用性

    • 支持主从备份
    • 自动故障转移
    • 负载均衡
  3. 简化开发

    • 提供简单的 API
    • 支持 HTTP 直接访问
    • 无需关心存储细节

下次开发时记住这些

  1. ✅ 复制 FastDFSUtils 工具类
  2. ✅ 配置 fdfs.properties(Tracker 地址)
  3. ✅ 实体类添加三个字段(orname、groupname、remotefilename)
  4. ✅ 表单设置 enctype=“multipart/form-data”
  5. ✅ 图片访问格式:http://IP:8888/group1/M00/00/00/xxx.jpg

关键概念回顾

概念说明
Tracker Server跟踪服务器,负责调度和管理
Storage Server存储服务器,实际存储文件
Group组,同组内服务器互为备份
FileID文件ID,包含组名和路径
StorageClient存储客户端,用于文件操作
MultipartFileSpring 提供的文件上传接口

🎉 恭喜您!现在您已经全面掌握了 FastDFS 的使用方法和工作原理!

下次开发时,只需按照本文档的步骤操作,就能快速集成 FastDFS! 🚀

http://www.dtcms.com/a/454613.html

相关文章:

  • 动态规划 - 背包问题
  • 科耐美安维可三文鱼焕颜精华液问世:妆字号无创水光引领护肤新趋势
  • dede减肥网站源码酒店建筑设计网站
  • 网站模板紫色网站做任务包括什么
  • 双等位基因:遗传学中的核心概念、分子机制与跨领域应用解析--随笔13
  • 广丰区建设局网站什么软件可以免费引流
  • 个人网站可以做信息网站吗专业网站建设办公
  • 百度云域名买了之后建设网站免费网站空间可访问
  • 制作动画的网站模板如何用运行打开wordpress
  • 北京模板网站开发北京注册公司规定
  • 江苏省住房和城乡建设局网站首页磁业 东莞网站建设
  • 建设校园网站的好处专业团队介绍文案
  • 儿童早教网站模板建设网站要那些
  • 淘宝客如何做自己的网站网架加工费多少钱一吨
  • 什么做婚车网站最大wordpress 点击弹出层
  • 网站建设丂金手指科杰新兴街做网站公司
  • (四) Dotnet为AI控制台添加日志输出
  • php做企业网站管理系统购物网站制作公司
  • Shell test 命令详解
  • html网站素材网网站建设课程设计文献综述
  • 现在还做自适应网站网站设计要学什么
  • 品牌网站建设的关键事项网站遮罩是什么
  • 营销型网站建设排名网站建设公司需要申请icp吗
  • 江门营销网站建设推广的公司
  • 呼和浩特企业网站排名优化昌邑建设局网站
  • 手机网站建设多少钿个人在线做网站免费
  • 企业网站开发时间好看的网站设计
  • 公司的服务器能建设网站吗中国住建网证书查询
  • 乐清建设路小学网站淘宝网站开发多少金额
  • 大四记录10.7