dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(八)SpringMVC上传文件到FastDFS

释放双眼,带上耳机,听听看~!

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54906751

目前项目中需要存储一些文件、视频等。于是乎,查找了一些关于文件服务器资料。其中有Lustre、HDFS、Gluster、Alluxio、Ceph 、FastDFS。下面简单介绍一下:

Lustre 是一个大规模的、安全可靠的、具备高可用性的集群文件系统,它是由SUN公司开发和维护的。该项目主要的目的就是开发下一代的集群文件系统,目前可以支持超过10000个节点,数以PB的数据存储量。

HDFS Hadoop Distributed File System,简称HDFS,是一个分布式文件系统。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。

GlusterFS 是一个集群的文件系统,支持PB级的数据量。GlusterFS 通过RDMA和TCP/IP方式将分布到不同服务器上的存储空间汇集成一个大的网络化并行文件系统。

Alluxio 前身是Tachyon,是以内存为中心的分布式文件系统,拥有高性能和容错能力,能够为集群框架(如Spark、MapReduce)提供可靠的内存级速度的文件共享服务。

Ceph 是新一代开源分布式文件系统,主要目标是设计成基于POSIX的没有单点故障的分布式文件系统,提高数据的容错性并实现无缝的复制。

FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
通过以上6中文件的服务器的介绍,我们业务非常适合选择用FastDFS,所以就了解学习了一番,感觉确实颇为强大,在此再次感谢淘宝资深架构师余庆大神开源了如此优秀的轻量级分布式文件系统,本篇文章就记录一下FastDFS的最新版本5.0.9在CentOS7中的安装与配置。

1.Fastdfs的简介

了解一下基础概念,FastDFS是一个开源的轻量级分布式文件系统,由跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)三个部分组成,主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。

FastDFS系统结构如下图所示:

跟踪器和存储节点都可以由一台多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。

为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷 的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起 到了冗余备份和负载均衡的作用。

在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。

当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。

2.FastDFS的下载

Fastdfs的稳定版下载地址

3.FastDFS的 安装

详细见
CentOS 7 安装配置分布式文件系统 FastDFS 5.0.5

4.SpringMVC上传文件到FastDFS

4.1 fast_client.cnf配置


1
2
3
4
5
6
7
8
9
10
11
12
1connect_timeout = 2
2#网络超时时间
3network_timeout = 30
4#字符集
5charset = UTF-8
6#跟踪服务器的端口
7http.tracker_http_port = 9099
8http.anti_steal_token = no
9http.secret_key = FastDFS1234567890
10#跟踪服务器地址 。跟踪服务器主要是起到负载均衡的作用
11tracker_server = 192.168.0.116:22122
12

4.2 fastdfs文件上传的流程

上传文件交互过程:

  1. client询问tracker上传到的storage,不需要附加参数;
  2. tracker返回一台可用的storage;
  3. client直接和storage通讯完成文件上传。

4.3 FastDFS文件下载的流程

下载文件交互过程:

  1. client询问tracker下载文件的storage,参数为文件标识(卷名和文件名);
  2. tracker返回一台可用的storage;
  3. client直接和storage通讯完成文件下载。

需要说明的是,client为使用FastDFS服务的调用方,client也应该是一台服务器,它对tracker和storage的调用均为服务器间的调用。

4.4 FastDFSUtil 的封装


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
1package com.lidong.dubbo.util;
2
3import org.csource.common.MyException;
4import org.csource.common.NameValuePair;
5import org.csource.fastdfs.*;
6import org.slf4j.Logger;
7import org.slf4j.LoggerFactory;
8import org.springframework.core.io.ClassPathResource;
9import org.springframework.web.multipart.MultipartFile;
10
11import javax.servlet.http.HttpServletResponse;
12import java.io.*;
13import java.util.HashMap;
14import java.util.Map;
15
16/**
17 * @项目名称:lidong-dubbo
18 * @类名:FastDFSUtil
19 * @类的描述: FastDFS 上传文件到文件服务器
20 * @作者:lidong
21 * @创建时间:2017/2/6 下午5:23
22 * @公司:chni
23 * @QQ:1561281670
24 * @邮箱:lidong1665@163.com
25 */
26public class FastDFSUtil {
27
28    private final static
29
30    Logger logger = LoggerFactory.getLogger(FastDFSUtil.class);
31
32
33    /**
34     *上传服务器本地文件-通过Linux客户端,调用客户端命令上传
35     * @param filePath 文件绝对路径
36     * @return Map&lt;String,Object&gt; code-返回代码, group-文件组, msg-文件路径/错误信息
37     */
38    public static Map&lt;String, Object&gt; uploadLocalFile(String filePath) {
39        Map&lt;String, Object&gt; retMap = new HashMap&lt;String, Object&gt;();
40        /**
41         * 1.上传文件的命令
42         */
43        String command = &quot;fdfs_upload_file /etc/fdfs/client.conf  &quot; + filePath;
44        /**
45         * 2.定义文件的返回信息
46         */
47        String fileId = &quot;&quot;;
48        InputStreamReader inputStreamReader = null;
49        BufferedReader bufferedReader = null;
50        try {
51            /**
52             * 3.通过调用api, 执行linux命令上传文件
53             */
54            Process process = Runtime.getRuntime().exec(command);
55            /**
56             * 4.读取上传后返回的信息
57             */
58             inputStreamReader = new InputStreamReader(process.getInputStream());
59             bufferedReader = new BufferedReader(inputStreamReader);
60            String line;
61            if ((line = bufferedReader.readLine()) != null) {
62                fileId = line;
63            }
64            /**
65             * 5.如果fileId包含M00,说明文件已经上传成功。否则文件上传失败
66             */
67            if (fileId.contains(&quot;M00&quot;)) {
68                retMap.put(&quot;code&quot;, &quot;0000&quot;);
69                retMap.put(&quot;group&quot;, fileId.substring(0, 6));
70                retMap.put(&quot;msg&quot;, fileId.substring(7, fileId.length()));
71            } else {
72                retMap.put(&quot;code&quot;, &quot;0001&quot;);  //上传错误
73                retMap.put(&quot;msg&quot;, fileId);   //返回信息
74            }
75
76        } catch (Exception e) {
77            logger.error(&quot;IOException:&quot; + e.getMessage());
78            retMap.put(&quot;code&quot;, &quot;0002&quot;);
79            retMap.put(&quot;msg&quot;, e.getMessage());
80        }finally {
81            if (inputStreamReader!=null){
82                try {
83                    inputStreamReader.close();
84                } catch (IOException e) {
85                    e.printStackTrace();
86                }
87            }
88            if (bufferedReader != null) {
89                try {
90                    bufferedReader.close();
91                } catch (IOException e) {
92                    e.printStackTrace();
93                }
94            }
95        }
96        return retMap;
97    }
98
99
100    /**
101     * Description: 直接通过fdfs java客户端上传到服务器-读取本地文件上传
102     *
103     * @param filePath 本地文件绝对路径
104     * @return Map&lt;String,Object&gt; code-返回代码, group-文件组, msg-文件路径/错误信息
105     */
106    public static Map&lt;String, Object&gt; upload(String filePath) {
107        Map&lt;String, Object&gt; retMap = new HashMap&lt;String, Object&gt;();
108        File file = new File(filePath);
109        TrackerServer trackerServer = null;
110        StorageServer storageServer = null;
111        if (file.isFile()) {
112            try {
113                String tempFileName = file.getName();
114                byte[] fileBuff = FileUtil.getBytesFromFile(file);
115                String fileId = &quot;&quot;;
116                //截取后缀
117                String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(&quot;.&quot;) + 1);
118                ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1);
119                StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1();
120                storageServer = configAndConnectionServer.getStorageServer();
121                trackerServer = configAndConnectionServer.getTrackerServer();
122
123                /**
124              * 4.设置文件的相关属性。调用客户端的upload_file1的方法上传文件
125              */
126                NameValuePair[] metaList = new NameValuePair[3];
127                //原始文件名称
128                metaList[0] = new NameValuePair(&quot;fileName&quot;, tempFileName);
129                //文件后缀
130                metaList[1] = new NameValuePair(&quot;fileExtName&quot;, fileExtName);
131                //文件大小
132                metaList[2] = new NameValuePair(&quot;fileLength&quot;, String.valueOf(file.length()));
133                //开始上传文件
134                fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList);
135                retMap = handleResult(retMap, fileId);
136            } catch (Exception e) {
137                e.printStackTrace();
138                retMap.put(&quot;code&quot;, &quot;0002&quot;);
139                retMap.put(&quot;msg&quot;, e.getMessage());
140            }finally {
141                /**
142                 * 5.关闭跟踪服务器的连接
143                 */
144                colse(storageServer, trackerServer);
145            }
146        } else {
147            retMap.put(&quot;code&quot;, &quot;0001&quot;);
148            retMap.put(&quot;msg&quot;, &quot;error:本地文件不存在!&quot;);
149        }
150        return retMap;
151    }
152
153
154    /**
155     * Description:远程选择上传文件-通过MultipartFile
156     *
157     * @param file 文件流
158     * @return Map&lt;String,Object&gt; code-返回代码, group-文件组, msg-文件路径/错误信息
159     */
160    public static Map&lt;String, Object&gt; upload(MultipartFile file) {
161        Map&lt;String, Object&gt; retMap = new HashMap&lt;String, Object&gt;();
162        TrackerServer trackerServer = null;
163        StorageServer storageServer = null;
164        try {
165            if (file.isEmpty()) {
166                retMap.put(&quot;code&quot;, &quot;0001&quot;);
167                retMap.put(&quot;msg&quot;, &quot;error:文件为空!&quot;);
168            } else {
169                ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1);
170                StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1();
171                storageServer = configAndConnectionServer.getStorageServer();
172                trackerServer = configAndConnectionServer.getTrackerServer();
173                String tempFileName = file.getOriginalFilename();
174                //设置元信息
175                NameValuePair[] metaList = new NameValuePair[3];
176                //原始文件名称
177                metaList[0] = new NameValuePair(&quot;fileName&quot;, tempFileName);
178                //文件后缀
179                byte[] fileBuff = file.getBytes();
180                String fileId = &quot;&quot;;
181                //截取后缀
182                String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(&quot;.&quot;) + 1);
183
184                metaList[1] = new NameValuePair(&quot;fileExtName&quot;, fileExtName);
185                //文件大小
186                metaList[2] = new NameValuePair(&quot;fileLength&quot;, String.valueOf(file.getSize()));
187                /**
188                 * 4.调用客户端呢的upload_file1的方法开始上传文件
189                 */
190                fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList);
191                retMap = handleResult(retMap, fileId);
192            }
193        } catch (Exception e) {
194            retMap.put(&quot;code&quot;, &quot;0002&quot;);
195            retMap.put(&quot;msg&quot;, &quot;error:文件上传失败!&quot;);
196        }finally {
197            /**
198             * 5.关闭跟踪服务器的连接
199             */
200            colse(storageServer, trackerServer);
201        }
202        return retMap;
203    }
204
205
206    /**
207     * 下载文件
208     *
209     * @param response
210     * @param filepath 数据库存的文件路径
211     * @param downname 下载后的名称
212     *                 filepath M00/开头的文件路径
213     *                 group 文件所在的组 如:group0
214     * @throws IOException
215     */
216    public static void download(HttpServletResponse response, String group, String filepath, String downname) {
217        StorageServer storageServer = null;
218        TrackerServer trackerServer = null;
219        try {
220            ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0);
221            StorageClient storageClient = configAndConnectionServer.getStorageClient();
222            storageServer = configAndConnectionServer.getStorageServer();
223            trackerServer = configAndConnectionServer.getTrackerServer();
224
225            /**
226             *4.调用客户端的下载download_file的方法
227             */
228            byte[] b = storageClient.download_file(group, filepath);
229            if (b == null) {
230                logger.error(&quot;Error1 : file not Found!&quot;);
231                response.getWriter().write(&quot;Error1 : file not Found!&quot;);
232            } else {
233                logger.info(&quot;下载文件..&quot;);
234                downname = new String(downname.getBytes(&quot;utf-8&quot;), &quot;ISO8859-1&quot;);
235                response.setHeader(&quot;Content-Disposition&quot;, &quot;attachment;fileName=&quot; + downname);
236                OutputStream out = response.getOutputStream();
237                out.write(b);
238                out.close();
239            }
240        } catch (Exception e) {
241            e.printStackTrace();
242            try {
243                response.getWriter().write(&quot;Error1 : file not Found!&quot;);
244            } catch (IOException e1) {
245                e1.printStackTrace();
246            }
247        }finally {
248            /**
249             * 5.关闭跟踪服务器的连接
250             */
251            colse(storageServer, trackerServer);
252        }
253    }
254
255    /**
256     * 删除文件
257     *
258     * @param group 文件分组,  filepath 已M00/ 开头的文件路径
259     * @return Map&lt;String,Object&gt; code-返回代码,  msg-错误信息
260     */
261    public static Map&lt;String, Object&gt; delete(String group, String filepath) {
262        Map&lt;String, Object&gt; retMap = new HashMap&lt;String, Object&gt;();
263        StorageServer storageServer = null;
264        TrackerServer trackerServer = null;
265        try {
266            ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0);
267            StorageClient storageClient = configAndConnectionServer.getStorageClient();
268            storageServer = configAndConnectionServer.getStorageServer();
269            trackerServer = configAndConnectionServer.getTrackerServer();
270            /**
271             * 4.调用客户端的delete_file方法删除文件
272             */
273            int i = storageClient.delete_file(group, filepath);
274            if (i == 0) {
275                retMap.put(&quot;code&quot;, &quot;0000&quot;);
276                retMap.put(&quot;msg&quot;, &quot;删除成功!&quot;);
277            } else {
278                retMap.put(&quot;code&quot;, &quot;0001&quot;);
279                retMap.put(&quot;msg&quot;, &quot;文件不存在!&quot;);
280            }
281        } catch (Exception e) {
282            e.printStackTrace();
283            retMap.put(&quot;code&quot;, &quot;0002&quot;);
284            retMap.put(&quot;msg&quot;, &quot;删除失败!&quot;);
285        } finally {
286            /**
287             * 5.关闭跟踪服务器的连接
288             */
289            colse(storageServer, trackerServer);
290        }
291
292        return retMap;
293
294    }
295
296    /**
297     * 关闭服务器
298     *
299     * @param storageServer
300     * @param trackerServer
301     */
302    private static void colse(StorageServer storageServer, TrackerServer trackerServer) {
303        if (storageServer != null &amp;&amp; trackerServer != null) {
304            try {
305                storageServer.close();
306                trackerServer.close();
307            } catch (IOException e) {
308                e.printStackTrace();
309            }
310
311        }
312    }
313
314    /**
315     * 处理上传到文件服务器之后,返回来的结果
316     *
317     * @param retMap
318     * @param fileId
319     * @return
320     */
321    private static Map&lt;String, Object&gt; handleResult(Map&lt;String, Object&gt; retMap, String fileId) {
322        if (!fileId.equals(&quot;&quot;) &amp;&amp; fileId != null) {
323            retMap.put(&quot;code&quot;, &quot;0000&quot;);
324            retMap.put(&quot;group&quot;, fileId.substring(0, 6));
325            retMap.put(&quot;msg&quot;, fileId.substring(7, fileId.length()));
326        } else {
327            retMap.put(&quot;code&quot;, &quot;0003&quot;);
328            retMap.put(&quot;msg&quot;, &quot;error:上传失败!&quot;);
329        }
330
331        return retMap;
332    }
333
334    /**
335     * @项目名称:lidong-dubbo
336     * @类名:FastDFSUtil
337     * @类的描述: ConfigAndConnectionServer
338     * @作者:lidong
339     * @创建时间:2017/2/7 上午8:47
340     * @公司:chni
341     * @QQ:1561281670
342     * @邮箱:lidong1665@163.com
343     */
344    private static class ConfigAndConnectionServer {
345        private TrackerServer trackerServer;
346        private StorageServer storageServer;
347        private StorageClient storageClient;
348        private StorageClient1 storageClient1;
349
350
351        public TrackerServer getTrackerServer() {
352            return trackerServer;
353        }
354
355        public StorageServer getStorageServer() {
356            return storageServer;
357        }
358
359        public StorageClient getStorageClient() {
360            return storageClient;
361        }
362
363        public StorageClient1 getStorageClient1() {
364            return storageClient1;
365        }
366
367        public ConfigAndConnectionServer invoke(int flag) throws IOException, MyException {
368            /**
369             * 1.读取fastDFS客户端配置文件
370             */
371            ClassPathResource cpr = new ClassPathResource(&quot;fdfs_client.conf&quot;);
372            /**
373             * 2.配置文件的初始化信息
374             */
375            ClientGlobal.init(cpr.getClassLoader().getResource(&quot;fdfs_client.conf&quot;).getPath());
376            TrackerClient tracker = new TrackerClient();
377            /**
378             * 3.建立连接
379             */
380            trackerServer = tracker.getConnection();
381            storageServer = null;
382            /**
383             * 如果flag=0时候,构造StorageClient对象否则构造StorageClient1
384             */
385            if (flag == 0) {
386                storageClient = new StorageClient(trackerServer, storageServer);
387            } else {
388                storageClient1 = new StorageClient1(trackerServer, storageServer);
389            }
390            return this;
391        }
392    }
393}
394
395
396

4.5 SpringMVC 上传文件到Fastdfs文件服务器


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1@RequestMapping(&quot;/upload&quot;)
2    public String addUser(@RequestParam(&quot;file&quot;) CommonsMultipartFile[] files,
3                          HttpServletRequest request){
4
5        for(int i = 0;i&lt;files.length;i++){
6            logger.info(&quot;fileName--&gt;&quot; + files[i].getOriginalFilename()+&quot;     file-size---&gt;&quot;+files[i].getSize());
7            Map&lt;String, Object&gt; retMap = FastDFSUtil.upload(files[i]);
8            String code = (String) retMap.get(&quot;code&quot;);
9            String group = (String) retMap.get(&quot;group&quot;);
10            String msg = (String) retMap.get(&quot;msg&quot;);
11
12            if (&quot;0000&quot;.equals(code)){
13                logger.info(&quot;文件上传成功&quot;);
14                //TODO:将上传文件的路径保存到mysql数据库
15            }else {
16                logger.info(&quot;文件上传失败&quot;);
17            }
18
19
20        }
21        return &quot;/success&quot;;
22    }
23
24

基本上就这么多。大家在学习的过程中如果遇到问题。可以直接在下面评论、吐槽。

代码地址

给TA打赏
共{{data.count}}人
人已打赏
安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

安全运维

设计模式的设计原则

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索