一. 基本概念

Fastdfs是用C语言编写的一款开源的分布式文件系统。Fastdfs为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 Fastdfs很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

Fastdfs架构包括Tracker serverStorage server。客户端请求Tracker server 进行文件上传、下载,通过 Tracker server 调度,最终由Storage server完成文件上传和下载。

Tracker server作用是负载均衡和调度,通过Tracker server在文件上传时可以根据一些策略找到Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。

二. Fastdfs系统结构

Storage作用是文件存储,客户端上传的文件最终存储在Storage服务器上。

服务端两个角色:

  • Tracker:作用是负载均衡和调度

  • Storage:作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上

  • 客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。如:/group1/M00/01/B2/CgAIC2Md0nWAKjOCAAQRef-xdIY168.jpg

  • 组名/卷:文件上传后所在的Storage组名称,在文件上传成功后有Storage服务器返回,需要客户端自行保存

  • 虚拟磁盘路径:Storage配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推

  • 数据两级目录:Storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件
  • 文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息
1
2
3
4
5
0. storage会定期向tracker上传当前状态【心跳机制】
1. 客户端发送请求,请求tracker【调度中心】
2. tracker选择哪一个storage能使用,并且返回能用的storage
3. 客户端把上传的附件,上传到storage中去
4. storage会向客户端返回上传文件的路径
  • 可以使用docker搭建fastdfs文件服务器

三. 使用FastDfs系统

  1. 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--fastdfs-->
    <dependency>
    <groupId>cn.bestwu</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27</version>
    </dependency>

    <!--用来获取文件扩展名的包-->
    <dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.8.0</version>
    </dependency>
  2. 添加配置文件fdfs_client.conf 到资源文件夹

    1
    2
    3
    tracker_server=123.207.27.208:22122  
    #22122是tracker的端口 - 上传
    #访问直接使用域名:123.207.27.208
  3. 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。
    ClientGlobal.init("D:/maven_work/fastDFS-demo/src/fdfs_client.conf");
    // 2、创建一个 TrackerClient 对象。直接 new 一个。
    TrackerClient trackerClient = new TrackerClient();
    // 3、使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
    TrackerServer trackerServer = trackerClient.getConnection();
    // 4、创建一个 StorageServer 的引用,值为 null
    StorageServer storageServer = null;
    // 5、创建一个 StorageClient 对象,需要两个参数 TrackerServer 对象、StorageServer 的引用
    StorageClient storageClient = new StorageClient(trackerServer, storageServer);
    // 6、使用 StorageClient 对象上传图片。
    //扩展名不带“.”
    String[] strings = storageClient.upload_file("D:/pic/benchi.jpg", "jpg",null);
    // 7、返回数组。包含组名和图片的路径。
    /*
    组名:group1
    路径: M00/01/B3/CgAIC2Md3KGAGa0xAAALg4Ak6_M929.jpg
    */
    for (String string : strings) {
    System.out.println(string);
    }
  4. 访问上传图片

    1
    http://123.207.27.208/group1/M00/01/B3/CgAIC2Md3KGAGa0xAAALg4Ak6_M929.jpg

四. FastDfsUtils工具类

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
package io.coderyeah.basic.util;

import org.csource.fastdfs.*;

/**
* fastDfs工具类
*/
public class FastDfsUtils {

//从classpath:resources中的内容最终会编译到classpath下
public static String CONF_FILENAME = FastDfsUtils.class.getClassLoader().getResource("fdfs_client.conf").getFile();


/**
* 上传文件
*
* @param file
* @param extName
* @return
*/
public static String upload(byte[] file, String extName) {

try {
ClientGlobal.init(CONF_FILENAME);

TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;

StorageClient storageClient = new StorageClient(trackerServer, storageServer);

String fileIds[] = storageClient.upload_file(file, extName, null);

System.out.println(fileIds.length);
System.out.println("组名:" + fileIds[0]);
System.out.println("路径: " + fileIds[1]);
// /group1/M00/00/09/rBEACmKXF8-AUc6KAANsldwx3H4713.jpg
return "/" + fileIds[0] + "/" + fileIds[1];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 上传文件
*
* @param extName
* @return
*/
public static String upload(String path, String extName) {

try {
ClientGlobal.init(CONF_FILENAME);

TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
String fileIds[] = storageClient.upload_file(path, extName, null);

System.out.println(fileIds.length);
System.out.println("组名:" + fileIds[0]);
System.out.println("路径: " + fileIds[1]);
return "/" + fileIds[0] + "/" + fileIds[1];

} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 下载文件
*
* @param groupName
* @param fileName
* @return
*/
public static byte[] download(String groupName, String fileName) {
try {

ClientGlobal.init(CONF_FILENAME);

TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;

StorageClient storageClient = new StorageClient(trackerServer, storageServer);
byte[] b = storageClient.download_file(groupName, fileName);
return b;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 删除文件
*
* @param groupName
* @param fileName
*/
public static void delete(String groupName, String fileName) {
try {
ClientGlobal.init(CONF_FILENAME);

TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;

StorageClient storageClient = new StorageClient(trackerServer, storageServer);
int i = storageClient.delete_file(groupName, fileName);
System.out.println(i == 0 ? "删除成功" : "删除失败:" + i);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("删除异常," + e.getMessage());
}
}

// public static void main(String[] args) {
// FastDfsUtils.delete("group1","M00/00/0F/oYYBAGJ6IGaAWQeOAAbMJw3URKE510.gif");
// }
}

五. 文件上传接口

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
package io.coderyeah.basic.controller;

import io.coderyeah.basic.Result;
import io.coderyeah.basic.util.FastDfsUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@Api(value = "文件的上传和下载接口", tags = "fastDfs文件上传")
@RestController
@RequestMapping("/fastDfs")
public class FastDfsController {
/**
* // @RequestPart 对复杂表单项的处理,默认name="file",与上传文件的name属性值一致
*/
@ApiOperation("通过fastDfs文件上传到服务器")
@PostMapping()
public Result upload(@RequestPart(value = "file", required = true) MultipartFile file) {
// 1.截取文件后缀名 如png jpg
final String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
try {
// 2.将图片上传服务器将文件名返回前端
// /group1/M00/01/B2/CgAIC2Md0nWAKjOCAAQRef-xdIY168.jpg
final String filepath = FastDfsUtils.upload(file.getBytes(), suffix);
return Result.success(filepath,null); // 返回服务器文件路径
} catch (IOException e) {
e.printStackTrace();
return Result.fail("上传失败!" + e.getMessage());
}
}

@ApiOperation("删除文件")
@DeleteMapping()
public Result delete(@RequestParam("path") String path) {
try {
// 1.截取组名 /group1/M00/01/B2/CgAIC2Md0nWAKjOCAAQRef-xdIY168.jpg
// 1.1临时路径
final String tempPath = path.substring(1); // group1/M00/01/B2/CgAIC2Md0nWAKjOCAAQRef-xdIY168.jpg
final String groupName = tempPath.substring(0, tempPath.indexOf("/")); // group1
System.out.println(tempPath);
System.out.println(groupName);
// 2.远程路径
final String remotePath = tempPath.substring(tempPath.indexOf("/") + 1);
FastDfsUtils.delete(groupName, remotePath);
return Result.success(null);
} catch (Exception e) {
e.printStackTrace();
return Result.fail("删除失败" + e.getMessage());
}
}
}

六. 上传流程

1
2
3
4
5
6
7
8
9
10
前端:
1.准备el-upload上传组件
2.当我们点击上传按钮的时候,其实是在触发el-upload的action
3.action调用后端接口:http://localhost:8080/fastDfs
4.后端上传成功之后,返回到el-upload的:on-success="handleSuccess"
5.在handleSuccess中,将后端返回的resultObj绑定给模型层的logo
后端:
1.接收请求@RequestPart MultipartFile file
2.调用FastDfsUtils完成文件上传得到返回值path
3.然后将path设置给封装结果类的data传回给前端

七. 文件上传表单项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<el-form-item prop="logo" label="店铺Logo">
<!--
:on-preview="handlePreview" - 点击图片名时触发此函数 - 可以在这里做放大功能,这里不做
:on-remove="handleRemove" - 点击删除时触发
:on-success="handleSuccess" - 上传成功之后触发
:file-list="fileList" - 文件列表
action="http://localhost:8080/fastDfs/" - 文件上传接口地址
-->
<el-upload class="upload-demo"
action="http://localhost:8080/fastDfs/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="handleSuccess"
:file-list="fileList"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</el-form-item>

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//文件上传的业务逻辑
handleSuccess(response, file, fileList) {
this.shop.logo = response.data;
console.log(response.data)
},

//文件删除的业务逻辑
handleRemove(file, fileList) {
var filePath = file.response.data;
this.$http.delete("/fastDfs?path=" + filePath)
.then(res => {
if (res.data.success) {
this.$message({
message: '删除成功!',
type: 'success'
});
} else {
this.$message({
message: '删除失败!',
type: 'error'
});
}
})