美文网首页
使⽤ Spring Boot 上传⽂件到 FastDFS

使⽤ Spring Boot 上传⽂件到 FastDFS

作者: hemiao3000 | 来源:发表于2022-05-05 23:23 被阅读0次

什么是 FastDFS

FastDFS 是⼀个开源的轻量级分布式⽂件系统,它解决了⼤数据量存储和负载均衡等问题,特别适合以中小⽂件(建议范围:4 KB < file_size < 500 MB)为载体的在线服务,如相册⽹站、视频⽹站等。

FastDFS 由 C 语⾔开发,⽀持 Linux、FreeBSD 等 UNIX 系统类文件系统(不是通⽤的⽂件系统),只能通 过专有 API 访问(⽬前提供了 C、Java 和 PHP API)。

FastDFS 可以看做是基于⽂件的 Key Value Pair 存储系统,称作分布式⽂件存储服务会更合适。

FastDFS 特性

  • ⽂件不分块存储,上传的⽂件和 OS ⽂件系统中的⽂件⼀⼀对应
  • ⽀持相同内容的⽂件只保存⼀份,节约磁盘空间 下载⽂件⽀持 HTTP 协议,可以使⽤内置 Web Server,也可以和其他 Web Server 配合使⽤
  • ⽀持在线扩容
  • ⽀持主从⽂件
  • 存储服务器上可以保存⽂件属性(meta-data)V2.0 ⽹络通信采⽤ libevent,⽀持⼤并发访问,整体性 能更好

FastDFS 相关概念

FastDFS 服务端有三个⻆⾊:跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端 (Client)。

  • Tracker Server:跟踪服务器,主要做调度⼯作,起负载均衡的作⽤。在内存记录集群中所有存储组和 存储服务器的状态信息,是客户端和数据服务器交互的枢纽。
  • Storage Server:存储服务器(⼜称存储节点或数据服务器),⽂件和⽂件属性(Meta Data)都保存 到存储服务器上。Storage Server 直接利⽤ OS 的⽂件系统调⽤管理⽂件。
  • Client:客户端,作为业务请求的发起⽅,通过专有接⼝,使⽤ TCP/IP 协议与跟踪器服务器或存储节 点进⾏数据交互。FastDFS 向使⽤者提供基本⽂件访问接⼝,如 upload、download、append、delete 等,以客户端库的⽅式提供给⽤户使⽤。

通过⼀张图来看⼀下 FastDFS 的运⾏机制:

image.png

Tracker 相当于 FastDFS 的⼤脑,不论是上传还是下载都是通过 Tracker 来分配资源;客户端⼀般可以使⽤ Ngnix 等静态服务器来调⽤或者做⼀部分的缓存;存储服务器内部分为卷(或者叫做组),卷与卷之间是平 ⾏的关系,可以根据资源的使⽤情况随时增加,卷内服务器⽂件相互同步备份,以达到容灾的⽬的。

上传机制

⾸先客户端请求 Tracker 服务获取到存储服务器的 IP 地址和端⼝,然后客户端根据返回的 IP 地址和端⼝号 请求上传⽂件,存储服务器接收到请求后⽣产⽂件,并且将⽂件内容写⼊磁盘并返回给客户端 file_id、路径 信息、⽂件名等信息,客户端保存相关信息上传完毕。

image.png

下载机制

客户端带上⽂件名信息请求 Tracker 服务获取到存储服务器的 IP 地址和端⼝,然后客户端根据返回的 IP 地 址和端⼝号请求下载⽂件,存储服务器接收到请求后返回⽂件给客户端。

image.png

Spring Boot 集成 FastDFS

pom 包配置

引⼊ FastDFS 的 Java 客户端:

<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27-SNAPSHOT</version>
</dependency>

fastdfs-client-java 为 FastDFS 的 Java 客户端,⽤来和 FastDFS 集群进⾏交互。

FastDFS 配置

项⽬ resources ⽬录下添加 fdfs_client.conf ⽂件:

connect_timeout = 60 # 连接超时时间 
network_timeout = 60 # ⽹络超时时间 
charset = UTF-8 # 编码格式
http.tracker_http_port = 8080 # tracker 端⼝ 
http.anti_steal_token = no # token 防盗链功能 
http.secret_key = 123456 # 密钥 
# tracer server 列表,多个 tracer server 的话,分⾏列出 
tracker_server = 192.168.53.85:22122 
tracker_server = 192.168.53.86:22122

配置⽂件设置了连接的超时时间、编码格式以及 tracker_server 地址等信息。

详细内容参考:fastdfs-client-java

封装 FastDFS 上传⼯具类

封装 FastDFSFile,⽂件基础信息包括⽂件名、内容、⽂件类型、作者等。

public class FastDFSFile {
    private String name;
    private byte[] content;
    private String ext;
    private String md5;
    private String author;
    
    // 省略getter、setter
}

接下来封装 FastDFSClient 类,FastDFSClient 主要封装最基础的操作,包含上传、下载、删除等⽅法。 FastDFSFile 任务可以是 FastDFS 上传⽂件的封装,操作时每⼀个⽂件对应⼀个实例。

⾸先在类加载的时候读取配置信息,并进⾏初始化。

static {
    try {
        String filePath = new ClassPathResource("fdfs_client.conf").getFile().getA bsolutePath();; ClientGlobal.init(filePath);
    } 
    catch (Exception e) { 
        logger.error("FastDFS Client Init Fail!",e);
    }
}

ClientGlobal.init ⽅法 会读取配置⽂件,并初始化对应的属性。

FastDFSClient 类中第⼀个⽅法——⽂件上传。

1.⽂件上传

使⽤ FastDFS 提供的客户端 storageClient 来进⾏⽂件上传,最后将上传结果返回。

public static String[] upload(FastDFSFile file) {
    logger.info("File Name: " + file.getName() 
            + " File Length:" + file.getContent().length);
            
    // ⽂件属性信息    
    NameValuePair[] meta_list = new NameValuePair[1];
    meta_list[0] = new NameValuePair("author", file.getAuthor());
    long startTime = System.currentTimeMillis();
    String[] uploadResults = null;
    StorageClient storageClient=null;
    try {
        // 获取        
        storageClient = getStorageClient();
        // 上传
        uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);    
    } catch (IOException e) {
        logger.error("IO Exception when uploadind the file:" + file.getName(), e);    
    } catch (Exception e) {
        logger.error("Non IO Exception when uploadind the file:" + file.getName(), e);   
    }    
    
    logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime ) + " ms");   
    // 验证上传结果    
    if (uploadResults == null && storageClient!=null) {        
        logger.error("upload file fail, error code:" + storageClient.getErrorCode(        ));    
    }   
    // 上传⽂件成功会返回 groupName。    
    logger.info("upload file successfully!!!" + "group_name:" + uploadResults[0] +", remoteFileName:" + " " + uploadResults[1]);
    
    return uploadResults;
}

其中:

  • NameValuePair,主要存储⽂件的⼀些基础属性,如作者信息、创建时间等;
  • getStorageClient(),封装了获取客户端的⽅法。

⾸先获取 TrackerServer 信息,使⽤ TrackerServer 构建出每次操作的客户端实例 StorageClient。详细代码 如下:

private static StorageClient getStorageClient() throws IOException { 
    TrackerServer trackerServer = getTrackerServer(); 
    StorageClient storageClient = new StorageClient(trackerServer, null); 
    return storageClient; 
}

下⾯为封装获取 TrackerServer 的⽅法:

private static TrackerServer getTrackerServer() throws IOException {
    TrackerClient trackerClient = new TrackerClient(); 
    TrackerServer trackerServer = trackerClient.getConnection(); 
    return trackerServer; 
}

2. 获取文件

根据 groupName 和⽂件名获取⽂件信息。group 也可称为卷,同组内服务器上的⽂件是完全相同的,同⼀组 内的 storage server 之间是对等的,⽂件上传、删除等操作可以在任意⼀台 storage server 上进⾏。


public static FileInfo getFile(String groupName, String remoteFileName) { 
    try { 
        storageClient = new StorageClient(trackerServer, storageServer); 
        return storageClient.get_file_info(groupName, remoteFileName); 
    } catch (IOException e) { 
        logger.error("IO Exception: Get File from Fast DFS failed", e); 
    } catch (Exception e) { 
        logger.error("Non IO Exception: Get File from Fast DFS failed", e); 
    } 
    return null; 
}

3.下载文件

根据 storageClient 的 API 获取⽂件的字节流并返回:

public static InputStream downFile(String groupName, String remoteFileName) { 
    try { 
        StorageClient storageClient = getStorageClient(); 
        byte[] fileByte = storageClient.download_file(groupName, remoteFileName); 
        InputStream ins = new ByteArrayInputStream(fileByte); 
        return ins; 
    } catch (IOException e) { 
        logger.error("IO Exception: Get File from Fast DFS failed", e);
    } catch (Exception e) { 
        logger.error("Non IO Exception: Get File from Fast DFS failed", e); 
    } 
    return null; 
}

4. 删除⽂件

根据⽂件名和组删除对应的⽂件。

public static void deleteFile(String groupName, String remoteFileName) throws Exception { 
    StorageClient storageClient = getStorageClient(); 
    int i = storageClient.delete_file(groupName, remoteFileName);
    logger.info("delete file successfully!!!" + i); 
}

当使⽤ FastDFS 时,直接调⽤ FastDFSClient 对应的⽅法即可。

编写上传控制类

从 MultipartFile 中读取⽂件信息,然后使⽤ FastDFSClient 将⽂件上传到 FastDFS 集群中,封装⼀个 saveFile() ⽅法⽤来调⽤上⾯封装的 FastDFS ⼯具类,将 MultipartFile ⽂件上传到 FastDFS 中,并返回上 传后⽂件的地址信息。

public String saveFile(MultipartFile multipartFile) throws IOException { 
    String[] fileAbsolutePath={}; 
    String fileName=multipartFile.getOriginalFilename(); 
    String ext = fileName.substring(fileName.lastIndexOf(".") + 1); 
    byte[] file_buff = null; 
    InputStream inputStream=multipartFile.getInputStream();
    if(inputStream!=null){ 
        int len1 = inputStream.available(); 
        file_buff = new byte[len1]; 
        inputStream.read(file_buff); 
    } 
    inputStream.close(); 
    FastDFSFile file = new FastDFSFile(fileName, file_buff, ext); 
    try { 
        fileAbsolutePath = FastDFSClient.upload(file); 
        // upload to fastdfs 
    } catch (Exception e) { 
        logger.error("upload file Exception!",e); 
    } if (fileAbsolutePath==null) { 
        logger.error("upload file failed,please upload again!"); 
    } 
    
    String path=FastDFSClient.getTrackerUrl()+fileAbsolutePath[0]+ "/"+fileAbsolut ePath[1]; 
    return path; 
}

当上传请求传递到后端时,调⽤上⾯⽅法 saveFile()。

@PostMapping("/upload") public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { 
    if (file.isEmpty()) { 
        redirectAttributes.addFlashAttribute("message", "Please select a file to u pload"); 
        return "redirect:uploadStatus"; 
    } 
    
    try { 
        String path=saveFile(file); 
        redirectAttributes.addFlashAttribute("message", "You successfully uploaded '" + file.getOriginalFilename() + "'");
        redirectAttributes.addFlashAttribute("path","file path url '" + path + "'" );
    } catch (Exception e) { 
        logger.error("upload file failed",e); 
    } 
    return "redirect:/uploadStatus"; 
}

上传成功之后,将⽂件的路径展示到⻚⾯,效果图如下:

image.png

在浏览器中访问此 URL,可以看到成功通过 FastDFS 展示:

image.png

在实际项⽬使⽤中可以给 Tracker 配置好固定域名,将返回的地址信息存储到数据库中,前端业务调⽤时直 接获取地址展示即可。

总结

整体上传逻辑:在⻚⾯上传⽂件后台由 MultipartFile 接收,接着将 MultipartFile ⽂件转发为 FastDFS ⽂件封 装类 FastDFSFile,调⽤ FastDFSClient 类的封装⽅法,将⽂件(FastDFSFile)上传到 FastDFS 集群中, 成功后集群中⽂件存储位置返回到⻚⾯。

FastDFS 是⼀款⾮常优秀的中⼩⽂件存储系统,结合 Spring Boot 的相关特性很容易将 FastDFS 集成到项⽬中,⽅便前端业务进⾏处理。

相关文章

网友评论

      本文标题:使⽤ Spring Boot 上传⽂件到 FastDFS

      本文链接:https://www.haomeiwen.com/subject/djqayrtx.html