FastDFS的安装我们还是通过Docker来安装实现吧,直接在Linux上还装还是比较繁琐的,但就学习而言Docker安装还是非常高效的。Docker环境请自行安装哦,不清楚的可以看看我的Docker专题的内容。https://blog.csdn.net/qq_38526573/category_9619681.html
首先我们可以通过 docker search fastdfs
来查询下有哪些镜像文件。
我们看到搜索到的镜像还是蛮多的,这里我们使用 delron/fastdfs
你也可以尝试使用其他的镜像来安装,你也可以制作自己的镜像来给别人使用哦,只是不同的镜像在使用的时候配置会有一些不一样,有些镜像没有提供Nginx的相关配置,使用的时候会繁琐一点。接下来通过 docker pull delron/fastdfs
命令把镜像拉取下来。
首先我们需要通过Docker命令来创建Tracker服务。命令为
docker run -d --name tracker --network=host -v /mydata/fastdfs/tracker:/var/fdfs delron/fastdfs tracker
tracker服务默认的端口为22122,-v 实现了容器和本地目录的挂载操作。
接下来创建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服务的,相关的端口为
服务 | 默认端口 |
---|---|
tracker | 22122 |
storage | 23000 |
Nginx | 8888 |
当然如果你发现这些相关的端口被占用了,或者想要对应的修改端口信息也可以的。要修改你可以先进入容器中查看下相关的配置文件信息。
然后查看storage.conf文件
这个是storage监听的Nginx的端口8888,如果要修改那么我们还需要修改Nginx中的服务配置,这块的配置在 /usr/local/nginx/conf
目录下
查看下文件
所以要修改端口号的话,这两个位置都得修改了。当然本文我们就使用默认的端口号来使用了。
好了,安装我们已经完成了,那么到底是否可以使用呢?我们来测试下。首先在虚拟机的/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的服务安装成功了。
首先我们来看下如何实现FastDFS中提供的JavaAPI来直接实现对应的文件上传和下载操作。
先来看下文件上传的流程
上传流程的文字梳理为:
客户端访问Tracker
Tracker 返回Storage的ip和端口
客户端直接访问Storage,把文件内容和元数据发送过去。
Storage返回文件存储id。包含了组名和文件名
首先创建一个普通的maven项目,然后引入对应的依赖
cn.bestwu fastdfs-client-java 1.27 org.apache.commons commons-lang3 3.4
然后编写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
返回后的字符串的结构说明
文件下载的流程,如下
文件下载的流程为:
client询问tracker需要下载的文件的storage,参数为文件的标识(group加文件名)。
tracker根据客户端的参数返回一台可用的storage。
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是线程不安全的。那么我们的解决方案
对文件的操作的每个方法我们做同步处理
每次操作文件的时候我们都获取一个新的StorageClient对象
第一种方式效率肯定是最低的,第二种方式每次都要建立新的连接效率同样的会受到影响,这时最好的方式其实是把StorageClient交给我们自定义的连接池来管理
我们在实际工作中基本都是和SpringBoot整合在一起来使用的,那么我们就来看看FastDFS是如何在SpringBoot项目中来使用的。首先创建一个普通的SpringBoot项目,然后导入fastdfs-spring-boot-starter这个依赖。
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test com.luhuiguo fastdfs-spring-boot-starter 0.2.0
既然是一个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()); } }
文件操作成功