分布式文件存储系统FastDFS[3]-通过Docker安装并且从客户端进行上传下载测试

news2024/11/17 4:59:05

一、FastDFS安装

 

  FastDFS的安装我们还是通过Docker来安装实现吧,直接在Linux上还装还是比较繁琐的,但就学习而言Docker安装还是非常高效的。Docker环境请自行安装哦,不清楚的可以看看我的Docker专题的内容。https://blog.csdn.net/qq_38526573/category_9619681.html

1.拉取镜像文件

  首先我们可以通过 docker search fastdfs 来查询下有哪些镜像文件。

  我们看到搜索到的镜像还是蛮多的,这里我们使用 delron/fastdfs 你也可以尝试使用其他的镜像来安装,你也可以制作自己的镜像来给别人使用哦,只是不同的镜像在使用的时候配置会有一些不一样,有些镜像没有提供Nginx的相关配置,使用的时候会繁琐一点。接下来通过 docker pull delron/fastdfs命令把镜像拉取下来。

 

2.构建Tracker服务

  首先我们需要通过Docker命令来创建Tracker服务。命令为

docker run -d --name tracker --network=host -v /mydata/fastdfs/tracker:/var/fdfs delron/fastdfs tracker

  tracker服务默认的端口为22122,-v 实现了容器和本地目录的挂载操作。

3.构建Storage服务

  接下来创建Storage服务,具体的执行命令如下

docker run -d --name storage --network=host  -e TRACKER_SERVER=192.168.56.100:22122 -v /mydata/fastdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage

  在执行上面命令的时候要注意对应的修改下,其中TRACKER_SERVER中的ip要修改为你的Tracker服务所在的服务IP地址。

 

  默认情况下在Storage服务中是帮我们安装了Nginx服务的,相关的端口为

服务默认端口
tracker22122
storage23000
Nginx8888

  当然如果你发现这些相关的端口被占用了,或者想要对应的修改端口信息也可以的。要修改你可以先进入容器中查看下相关的配置文件信息。

 

  然后查看storage.conf文件

  这个是storage监听的Nginx的端口8888,如果要修改那么我们还需要修改Nginx中的服务配置,这块的配置在 /usr/local/nginx/conf目录下

 

  查看下文件

所以要修改端口号的话,这两个位置都得修改了。当然本文我们就使用默认的端口号来使用了。

4.测试图片上传

  好了,安装我们已经完成了,那么到底是否可以使用呢?我们来测试下。首先在虚拟机的/mydata/fastdfs/storage下保存一张图片。

 

  然后我们再进入到storage容器中。并且进入到 /var/fdfs目录下,可以看到我们挂载的文件了

  然后执行如下命令即可完成图片的上传操作

/usr/bin/fdfs_upload_file /etc/fdfs/client.conf 1.jpg

  通过上面的提示我们看到文件上传成功了,而且返回了文件在storage中存储的信息。这时我们就可以通过这个信息来拼接访问的地址在浏览器中访问了:http://192.168.56.100:8888/group1/M00/00/00/wKg4ZGHcKLSAXibaAAezMuUrlS8235.jpg

 

  好了到这儿FastDFS的服务安装成功了。

二、客户端操作

1.Fastdfs-java-client

  首先我们来看下如何实现FastDFS中提供的JavaAPI来直接实现对应的文件上传和下载操作。

1.1 文件上传

  先来看下文件上传的流程

  上传流程的文字梳理为:

  1. 客户端访问Tracker

  2. Tracker 返回Storage的ip和端口

  3. 客户端直接访问Storage,把文件内容和元数据发送过去。

  4. Storage返回文件存储id。包含了组名和文件名

  首先创建一个普通的maven项目,然后引入对应的依赖

<dependencies>
    <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>
</dependencies>

 然后编写FastDFS的配置文件,内容如下:注意ip修改为你自己对应的ip即可

connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
tracker_server = 192.168.56.100:22122

  然后导入对应的工具类,在工具类中完成了StorageClient的实例化,并提供了相关的上传和下载的方法。

package com.bobo.fastdfs.config;

import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.*;

public class FastDFSClient {
    private static final String CONF_FILENAME = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "fdfs_client.conf";

    private static StorageClient storageClient = null;

    /**
     * 只加载一次.
     */
    static {
        try {
            ClientGlobal.init(CONF_FILENAME);
            TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
            TrackerServer trackerServer = trackerClient.getConnection();
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            storageClient = new StorageClient(trackerServer, storageServer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param inputStream
     *    上传的文件输入流
     * @param fileName
     *    上传的文件原始名
     * @return
     */
    public static String[] uploadFile(InputStream inputStream, String fileName) {
        try {
            // 文件的元数据
            NameValuePair[] meta_list = new NameValuePair[2];
            // 第一组元数据,文件的原始名称
            meta_list[0] = new NameValuePair("file name", fileName);
            // 第二组元数据
            meta_list[1] = new NameValuePair("file length", inputStream.available()+"");
            // 准备字节数组
            byte[] file_buff = null;
            if (inputStream != null) {
                // 查看文件的长度
                int len = inputStream.available();
                // 创建对应长度的字节数组
                file_buff = new byte[len];
                // 将输入流中的字节内容,读到字节数组中。
                inputStream.read(file_buff);
            }
            // 上传文件。参数含义:要上传的文件的内容(使用字节数组传递),上传的文件的类型(扩展名),元数据
            String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
            return fileids;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     *
     * @param file
     *            文件
     * @param fileName
     *            文件名
     * @return 返回Null则为失败
     */
    public static String[] uploadFile(File file, String fileName) {
        FileInputStream fis = null;
        try {
            NameValuePair[] meta_list = null; // new NameValuePair[0];
            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 = storageClient.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" 如果不指定该值,默认为group1
     * @param remoteFileName
     *            例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"
     * @return 0为成功,非0为失败,具体为错误代码
     */
    public static int deleteFile(String groupName, String remoteFileName) {
        try {
            int result = storageClient.delete_file(groupName == null ? "group1" : groupName, remoteFileName);
            return result;
        } catch (Exception ex) {
            return 0;
        }
    }

    /**
     * 修改一个已经存在的文件
     *
     * @param oldGroupName
     *            旧的组名
     * @param oldFileName
     *            旧的文件名
     * @param file
     *            新文件
     * @param fileName
     *            新文件名
     * @return 返回空则为失败
     */
    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;
    }

    /**
     * 文件下载
     *
     * @param groupName 卷名
     * @param remoteFileName 文件名
     * @return 返回一个流
     */
    public static InputStream downloadFile(String groupName, String remoteFileName) {
        try {
            byte[] bytes = storageClient.download_file(groupName, remoteFileName);
            InputStream inputStream = new ByteArrayInputStream(bytes);
            return inputStream;
        } catch (Exception ex) {
            return null;
        }
    }

    public static NameValuePair[] getMetaDate(String groupName, String remoteFileName){
        try{
            NameValuePair[] nvp = storageClient.get_metadata(groupName, remoteFileName);
            return nvp;
        }catch(Exception ex){
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件后缀名(不带点).
     *
     * @return 如:"jpg" or "".
     */
    private static String getFileExt(String fileName) {
        if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
            return "";
        } else {
            return fileName.substring(fileName.lastIndexOf(".") + 1); // 不带最后的点
        }
    }

}

 

 

 然后我们就可以来测试上传的操作了。

    public static void main(String[] args) {
        try {
            File file = new File("D:/2.jpg");
            InputStream is = new FileInputStream(file);
            String fileName = UUID.randomUUID().toString()+".jpg";
            String[] result = FastDFSClient.uploadFile(is, fileName);
            System.out.println(Arrays.toString(result));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

 

访问即可:http://192.168.56.100:8888/group1/M00/00/00/wKg4ZGHcUE6AZA2UAAW8dIX5p50374.jpg

  返回后的字符串的结构说明

1.2 文件下载

  文件下载的流程,如下

 

  文件下载的流程为:

  1. client询问tracker需要下载的文件的storage,参数为文件的标识(group加文件名)。

  2. tracker根据客户端的参数返回一台可用的storage。

  3. client根据返回的storage直接完成对应的文件的下载。

  有了上面的基础,文件下载就非常简单了,我们只需要根据前面上传的文件的group和文件的存储路径就可以通过StorageClient中提供的downloadFile方法把对应的文件下载下来了,具体的代码如下

    /**
     * 文件下载
     */
    public static void downloadFile(){
        try {
            InputStream is = FastDFSClient
                    .downloadFile("group1", "M00/00/00/wKg4ZGHcUE6AZA2UAAW8dIX5p50374.jpg");
            OutputStream os = new FileOutputStream(new File("D:/12.jpg"));
            int index = 0 ;
            while((index = is.read())!=-1){
                os.write(index);
            }
            os.flush();
            os.close();
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

注意:StorageClient是线程不安全的。那么我们的解决方案

  1. 对文件的操作的每个方法我们做同步处理

  2. 每次操作文件的时候我们都获取一个新的StorageClient对象

第一种方式效率肯定是最低的,第二种方式每次都要建立新的连接效率同样的会受到影响,这时最好的方式其实是把StorageClient交给我们自定义的连接池来管理

2.SpringBoot整合

  我们在实际工作中基本都是和SpringBoot整合在一起来使用的,那么我们就来看看FastDFS是如何在SpringBoot项目中来使用的。首先创建一个普通的SpringBoot项目,然后导入fastdfs-spring-boot-starter这个依赖。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
​
        <dependency>
            <groupId>com.luhuiguo</groupId>
            <artifactId>fastdfs-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

  既然是一个starter,那么必然会在spring.factories文件中提供对应的自动配置类。

  可以看到给我们提供的配置类为FdfsAutoConfiguration进入后可以看到帮我们注入了很多的核心对象。

 

  然后可以看到系统提供的配置信息,前缀为 fdfs

  然后我们就可以在application.properties中配置FastDFS的配置信息了。

 

 

  配置完成后我们就可以测试文件的上传下载操作了

@SpringBootTest
class FastDfsSpringBootApplicationTests {
​
    @Autowired
    public FastFileStorageClient storageClient;
​
​
    @Test
    void contextLoads() throws Exception{
        File file = new File("d:\\2.jpg");
        StorePath path = storageClient.uploadFile(null,new FileInputStream(file),file.length(),file.getName());
        System.out.println(path.getFullPath());
    }
​
}

文件操作成功

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73111.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【jenkins】1. 安装jenkins (docker-compose)

环境 ubuntu 20docker服务器 ip:xxx.xxx.xxx.xxx 步骤 1. 【编写安装文件】windows下 - 编写 docker-compose.yaml version: "3.1" services:jenkins:image: jenkins/jenkinsrestart: alwayscontainer_name: jenkinsports:- 58080:8080- 50000:50000volumes:- ./…

5.31 综合案例2.0 - 在线MP3音乐播放器

综合案例2.0 - 在线MP3音乐播放器一、案例说明二、准备器件三、案例连线四、代码代码说明复制五、测试一、案例说明 本案例制作一个联网下载声音文件的MP3播放器。 案例功能说明&#xff1a; 案例使用一块IC035串口屏和两个按键通过按键可以切换音乐&#xff0c;下载播放或删除…

瑜岿科技综合能源管理系统助力企业节能降耗工作

能源是国民经济的基础&#xff0c;更是城市赖以发展的动力。优化能源结构、大力发展可再生能源、提高机房能效、实现建筑智慧节能是行业绿色发展重点。在国家碳达峰碳中和的重大战略决策背景下&#xff0c;我国能源体制改革深入推进&#xff0c;能源生产和消费发生重大变革&…

如今社交电商不可忽略的一个点——消费增值

消费增值是一个基于消费返利理论的全新商业消费生态&#xff0c;一个让用户从优惠消费—免单消费—挣钱消费&#xff0c;所突破的新消费、新业态模式—增值消费&#xff0c;一个从电商化的平台走向品牌化的平台&#xff0c;让平台脱离单打独斗的方式&#xff0c;通过商家联盟方…

编译原理 2 - 词法分析

第3章 词法分析3.1 词法分析器的功能和结构3.2 状态转换图3.3 正则文法 和 正则表达式3.4 有限自动机 DFA与NFA测试第3章 词法分析 重点&#xff1a;① 词法分析器的输入、输出&#xff1b;② 用于识别符号的状态转移图的构造&#xff1b;③ 根据状态转移图实现词法分析器 难点…

【MATLAB教程案例55】GoogleNet网络的MATLAB编程学习和实现,以步态识别为例进行仿真分析

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 1.软件版本 2.GoogleNet理论概述

基于Qt的桌面客户端组件化框架DT 开源啦

这个是本人在工作中基于QT开发的组件化桌面开发框架&#xff0c;目前打算开源出来提供给大家&#xff0c;节省大家的开发时间和效率&#xff0c;希望对大家的开发有所帮助&#xff0c;也欢迎提出意见和改进建议 1. 为什么开源DT框架 DT框架&#xff0c;本身就是基于QT为基础搭…

深度学习——含并行连接的网络(GoogLeNet)笔记

GoogLeNet中基本的卷积块是Inception块。 1.Inception块&#xff1a;4个路径从不同的层面抽取信息&#xff0c;然后在输出通道合并 ①1*1的卷积层&#xff0c;减少通道数&#xff0c;降低模型的复杂度 ② 1*1的卷积层&#xff0c;减少通道数&#xff0c;降低模型复杂度。然后…

volatile关键字和synchronized关键字

参考博客&#xff1a;https://www.cnblogs.com/cg-ww/p/14540450.html 1、volatile的作用&#xff1a;关键作用是使变量在多个线程之间可见 程序并没有因为我修改之后结束运行&#xff0c;因为线程对共享变量具有不可见性&#xff0c;main线程修改布尔值之后&#xff0c;子线…

【读点论文】MicroNet: Towards Image Recognition with Extremely Low FLOPs,在极高限制下的轻量化网络

MicroNet: Towards Image Recognition with Extremely Low FLOPs Abstract 这篇论文旨在以极低的计算成本解决性能大幅下降的问题。提出了微分解卷积&#xff0c;将卷积矩阵分解为低秩矩阵&#xff0c;将稀疏连接整合到卷积中。 提出了一个新的动态激活函数-- Dynamic Shift …

精准管控 | AIRIOT数字油库智能化解决方案

在油库管理的过程中&#xff0c;储油罐区普遍存在分布空间范围广、安全防爆要求高、监控点多、布线复杂、自动化系统集成难度大等问题&#xff0c;传统的油库管理手段相对落后、管理环境复杂&#xff0c;企业在监测监控、设备设施管理、日常运行等方面存在诸多难点&#xff1a;…

Openldap2.5.13编译安装

Openldap2.5.13编译安装 本篇文章没有很全面&#xff0c;采用的是编译安装&#xff0c;yum安装参考我的另外一篇文章 一、准备 openssl-1.1.1.tar.gz 下载地址&#xff1a;https://ftp.openssl.org/source/old/1.1.1/openssl-1.1.1.tar.gz openldap-2.5.13.tgz 下载地址&#…

APS计划排产软件在仪器仪表行业的应用

仪器仪表是指用以产生、测量精密量的设备和装置&#xff0c;包括对精密量的观察、监视、测定、验证、记录、传输、变换、显示、分析处理与控制。仪器仪表是仪器仪表的一个重要分支。伴随国家出台政策大力发展精密仪器仪表下游行业&#xff0c;下游应用领域对精密仪器仪表的需求…

React 学习笔记:组件生命周期

组件生命周期 生命周期一个事物从创建到最后消亡经历的过程&#xff0c;在这个过程中会有不同的状态&#xff08;新生儿、少年&#xff09;&#xff0c;所以在 16.8 版本以前生命周期只有类组件才有&#xff0c;函数组件是没有这个概念的&#xff0c;因为它没有状态&#xff0…

Diffusion-LM Improves Controllable Text Generation 扩散语言模型改善可控文本生成

论文链接&#xff1a;https://arxiv.org/abs/2205.14217 代码链接&#xff1a;https://github.com/XiangLi1999/Diffusion-LM 一、简介 无需重训模型控制语言模型的表现是自然语言生成&#xff08;NLG&#xff09;的一个重要开放问题&#xff0c;近期工作已经在简单句子属性控…

解读小红书2022年母婴行业报告:心智种草的流量密码

母婴用户代际更迭&#xff0c;90后晋升为母婴消费主力军。新一代宝爸宝妈的关注点在哪里&#xff1f;品牌该如何通过小红书满足ta们的进阶需求&#xff0c;为母婴消费注入新活力&#xff1f; 本文将解读小红书官方发布的《2022年母婴行业人群洞察报告》&#xff0c;基于上千名用…

Ubuntu20运行SegNeXt代码提取道路水体(二)——SegNeXt源代码安装到测试环境配置全过程摸索

首先我们在第一篇里面已经下载了SegNeXt代码 打开源代码 查看readme文件 我们先安装一下里面提到的torchprofile 链接在这 其实只要这个语句就能安装 pip install torchprofile这一步没什么问题 很顺利 接下来继续按照他的教程走 我们在&#xff08;一&#xff09;已经安装了…

快手小程序怎么开发

快手小程序开发很简单&#xff0c;直接找一个快手小程序开发平台就可以。 大家可以按照下面这个步骤去完成&#xff0c;完成后就能开发出一个快手小程序啦。 操作像玩消消乐那么简单&#xff01; 不过在开发快手小程序前&#xff0c;需要先检查以下这几样东西有没有准备好&a…

小白学流程引擎-FLowable(五) — BPMN2.0模型规范

前言&#xff1a; 不用到处百度BPMN2的博客了&#xff0c;本篇文章带你系统掌握BPMN2规范的核心知识点。全文2万字&#xff0c;全覆盖BPMN2知识点&#xff0c;图文并茂&#xff0c;泡杯咖啡&#xff0c;慢慢细品&#xff5e; 一、BPMN是什么 BPMN&#xff08;Business Proces…

ChatGPT的理解

参考 李宏毅老师讲解 思维导图 ChatGPT 对标 instruct GPT 本质 GPT的社会化 训练过程 1 学习文字接龙 无监督学习 大量的自我学习 生成的答案具有随机性 自己修炼 2 人类老师引导文字接龙的方向 监督学习 标注:(问题提示&#xff0c;答案)对&#xff0c;引导gpt生…