前面文件系统平台搭建好了,现在就要写客户端代码在系统中实现上传下载,这里只是简单的测试代码。
官网Git:https://github.com/happyfish100/fastdfs-client-java
一、项目搭建
1、引入依赖包
引入依赖包这个有意思,目前有下面三个 jar 包都有人在用,但是用后两者的比较多。
第一个 org.csource 需要在 pom 中引入用友云仓库地址才能导入包,个人觉得这点很不爽。
第二个在第一个基础上多了一些封装增加了些方法
第三个支持 SpringBoot 的 application.yml 配置,但是目前对FastDFS支持的版本还没有达到最新。
我这里选择第二个
<!-- https://mvnrepository.com/artifact/org.csource/fastdfs-client-java --> <dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27-RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/net.oschina.zcx7878/fastdfs-client-java --> <dependency> <groupId>net.oschina.zcx7878</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27.0.0</version> </dependency> <!-- https://github.com/tobato/FastDFS_Client --> <dependency> <groupId>com.github.tobato</groupId> <artifactId>fastdfs-client</artifactId> <version>1.25.2-RELEASE</version> </dependency>
完整 build.gradle 文件
buildscript {
ext {
springBootVersion = '2.1.2.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile group: 'net.oschina.zcx7878', name: 'fastdfs-client-java', version: '1.27.0.0'
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.54'
implementation 'org.springframework.boot:spring-boot-starter-web'
compile('org.springframework.boot:spring-boot-configuration-processor')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}2、引入配置文件
配置文件可以是 conf 或 properties 文件,当然,也是可以在 Spring Boot 的 yml 文件。
三种方式都会讲解,实际应用中,选择其中一个即可。
创建 fdfs_client.conf 文件
创建 fastdfs-client.properties 文件
创建 application.yml 文件
参考官方文档:https://github.com/happyfish100/fastdfs-client-java
fdfs_client.conf
connect_timeout = 10 network_timeout = 30 charset = UTF-8 # tracker 服务端口 http.tracker_http_port = 8080 # token 防盗链功能 http.anti_steal_token = true # 密钥(与 http.conf 文件一致) http.secret_key = FastDFS1234567890000 # tracker 服务地址 tracker_server = 172.16.119.129:22122
fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 10 fastdfs.network_timeout_in_seconds = 30 fastdfs.charset = UTF-8 fastdfs.http_anti_steal_token = false fastdfs.http_secret_key = FastDFS1234567890 fastdfs.http_tracker_http_port = 80 fastdfs.tracker_servers = 172.16.119.129:22122
application.yml
# FastDFS fastdfs: connect_timeout_in_seconds: 10 network_timeout_in_seconds: 30 charset: UTF-8 http_anti_steal_token: false http_secret_key: FastDFS1234567890 http_tracker_http_port: 80 tracker_servers: 172.16.119.129:22122 # url 自定义的,用来配置 nginx 对外访问文件的 url url: http://172.16.119.128:8888/
配置文件
二、测试类
编写测试类
/**
* FastDFS Client Java 测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestFastDFS {
@Autowired
private FastDFSConf fastDFSConf;
private TrackerServer trackerServer;
private StorageClient storageClient;
@Before
public void before() throws Exception {
// 1、加载 FastDFS 客户端的配置文件
// 方式1
String filePath = new ClassPathResource("/fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
// 方式2
// ClientGlobal.init ByProperties("fastdfs-client.properties");
// 方式3
// Properties prop = new Properties();
// prop.setProperty("fastdfs.connect_timeout_in_seconds", fastDFSConf.getConnect_timeout_in_seconds());
// prop.setProperty("fastdfs.network_timeout_in_seconds", fastDFSConf.getNetwork_timeout_in_seconds());
// prop.setProperty("fastdfs.charset", fastDFSConf.getCharset());
// prop.setProperty("fastdfs.tracker_servers", fastDFSConf.getTracker_servers());
// ClientGlobal.initByProperties(prop);
System.out.println("network_timeout=" + ClientGlobal.getG_network_timeout() + "ms");
System.out.println("charset=" + ClientGlobal.getG_charset());
// 2、创建 Tracker 客户端
TrackerClient trackerClient = new TrackerClient();
trackerServer = trackerClient.getConnection();
// 3、定义 storage 客户端
StorageServer storageServer = null;
storageClient = new StorageClient(trackerServer, storageServer);
}
/**
* 上传
*/
@Test
public void upload() throws Exception {
// 文件元信息
NameValuePair[] metaList = new NameValuePair[3];
metaList[0] = new NameValuePair("author", "刘仁奎");
metaList[1] = new NameValuePair("date", "2019-01-19");
metaList[2] = new NameValuePair("fileName", "wallpaper");
// 执行上传
String[] result = storageClient.upload_file("/Users/liurenkui/Desktop/mm.jpeg", "jpeg", metaList);
// System.out.println(Arrays.toString(result));
System.out.println(JSON.toJSON(result));
}
/**
* 查询文件信息
*/
@Test
public void getInfo() throws Exception {
// 查询文件信息
FileInfo fileInfo = storageClient.query_file_info("group1", "M00/00/00/rBB3gFxDPKmAE1a8AACAKO7Mtfo01.jpeg");
System.out.println("FileInfo:" + JSON.toJSONString(fileInfo));
// 查询文件元数据
NameValuePair[] metadata = storageClient.get_metadata("group1", "M00/00/00/rBB3gFxDPKmAE1a8AACAKO7Mtfo01.jpeg");
System.out.println("MetaData:" + JSON.toJSONString(metadata));
}
/**
* 下载
*/
@Test
public void download() throws Exception {
// 方式1
// byte[] bytes = storageClient.download_file("group1", "M00/00/00/rBB3gFxCzjuAHOQyAACAKO7Mtfo41.jpeg");
// FileOutputStream stream = new FileOutputStream(new File("/Users/liurenkui/Desktop/a.jpeg"));
// stream.write(bytes);
// stream.close();
// 方式2
int downloadFile = storageClient.download_file("group1", "M00/00/00/rBB3gFxDPKmAE1a8AACAKO7Mtfo01.jpeg", "/Users/liurenkui/Desktop/b.jpg");
System.out.println(downloadFile);
}
/**
* 跟踪服务器Url
*/
@Test
public void getTrackerUrl() {
String path = "http://" + trackerServer.getInetSocketAddress().getHostString() + ":" + ClientGlobal.getG_tracker_http_port() + "/";
System.out.println(path);
}
/**
* 关闭客户端
*/
@After
public void after() throws Exception {
trackerServer.close();
}
}仔细看 before 方法,加载配置文件的三种方式
// 1、加载 FastDFS 客户端的配置文件
// 方式1
// String filePath = new ClassPathResource("/fdfs_client.conf").getFile().getAbsolutePath();
// ClientGlobal.init(filePath);
ClientGlobal.init("fdfs_client.conf");
// 方式2
// ClientGlobal.init ByProperties("fastdfs-client.properties");
// 方式3
// Properties prop = new Properties();
// prop.setProperty("fastdfs.connect_timeout_in_seconds", fastDFSConf.getConnect_timeout_in_seconds());
// prop.setProperty("fastdfs.network_timeout_in_seconds", fastDFSConf.getNetwork_timeout_in_seconds());
// prop.setProperty("fastdfs.charset", fastDFSConf.getCharset());
// prop.setProperty("fastdfs.tracker_servers", fastDFSConf.getTracker_servers());
// ClientGlobal.initByProperties(prop);其中第三种方式,就是根据 yml 文件中获取的自定义配置,对应 FastDFSConf.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "fastdfs")
public class FastDFSConf {
private String connect_timeout_in_seconds;
private String network_timeout_in_seconds;
private String charset;
private String http_anti_steal_token;
private String http_secret_key;
private String http_tracker_http_port;
private String tracker_servers;
private String url;
// 省略 getter、setter
}响应结果
上传
[group1, M00/00/00/rBB3gFxCzjuAHOQyAACAKO7Mtfo41.jpeg]
查询文件信息
FileInfo:{"crc32":-288573958,"createTimestamp":1547882043000,"fileSize":32808,"sourceIpAddr":"172.16.119.128"}
MetaData:[{"name":"author","value":"刘仁奎"},{"name":"date","value":"2019-01-19"},{"name":"fileName","value":"wallpaper"}]跟踪服务器Url
http://172.16.119.129:8080/
三、封装 Controller 接口
经过上述的测试没有问题,接下来就是封装客户端上传文件的 API 接口了。
1、封装接口
@RestController
@RequestMapping("/file")
public class UploadController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private FastDFSConf fastDFSConf;
/**
* 文件上传
* <p>
* 从 MultipartFile中 读取文件流信息,然后使用 FastDFSClient 将文件上传到 FastDFS 中
*/
@PostMapping("/upload")
public String singleFileUpload(@RequestParam("file") MultipartFile multipartFile) {
if (multipartFile.isEmpty()) {
return "请选择要上传到文件";
}
try {
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();
// 上传
Map<String, String> metaData = new HashMap<>();
metaData.put("author", "刘仁奎");
metaData.put("webSite", "程序喵");
metaData.put("home", "http://www.ibloger.net");
String[] fileAbsolutePath = FastDFSClientUtil.upload(fileName, file_buff, ext, metaData);
if (fileAbsolutePath == null) {
logger.error("上传文件异常,请重新上传!");
}
// 完整路径
String path = fastDFSConf.getUrl() + fileAbsolutePath[0] + "/" + fileAbsolutePath[1];
logger.info("path = {}", path);
return path;
} catch (Exception e) {
logger.error("上传失败", e);
}
return null;
}
}编写工具类 FastDFSClientUtil.java
/**
* 封装 FastDFS 上传工具类
*/
public class FastDFSClientUtil {
private static final Logger logger = LoggerFactory.getLogger(FastDFSClientUtil.class);
/**
* 加载配置文件
*/
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (Exception e) {
logger.error("FastDFS 初始化异常!", e);
}
}
/**
* 上传
*/
public static String[] upload(String name, byte[] content, String ext) {
return upload(name, content, ext);
}
/**
* 上传(带元数据)
*
* @param name 文件名
* @param content 文件流
* @param ext 扩展名
* @param metaMap 元数据
* @return
*/
public static String[] upload(String name, byte[] content, String ext, Map<String, String> metaMap) {
String[] uploadResults = null;
StorageClient storageClient = null;
NameValuePair[] nameValuePairs = null;
try {
storageClient = getTrackerClient();
if (!CollectionUtils.isEmpty(metaMap)) {
Map<String, String> map = metaMap;
List<NameValuePair> metaList = new ArrayList<>();
map.forEach((k, v) -> metaList.add(new NameValuePair(k, v)));
nameValuePairs = metaList.toArray(new NameValuePair[]{});
}
uploadResults = storageClient.upload_file(content, ext, nameValuePairs);
} catch (IOException e) {
logger.error("IO异常:" + name, e);
} catch (Exception e) {
logger.error("上传异常:" + name, e);
}
if (uploadResults == null && storageClient != null) {
logger.error("上传失败,异常代码:" + storageClient.getErrorCode());
}
logger.info("上传成功:" + JSON.toJSON(uploadResults));
return uploadResults;
}
/**
* 获取文件信息
*
* @param groupName
* @param remoteFileName
* @return
*/
public static FileInfo getFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getTrackerClient();
return storageClient.get_file_info(groupName, remoteFileName);
} catch (Exception e) {
logger.error("Fast DFS 获取文件信息异常", e);
}
return null;
}
/**
* 下载
*
* @param groupName
* @param remoteFileName
* @return
*/
public static InputStream downFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getTrackerClient();
byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
InputStream ins = new ByteArrayInputStream(fileByte);
return ins;
} catch (Exception e) {
logger.error("Fast DFS 获取文件异常", e);
}
return null;
}
/**
* 删除
*/
public static void deleteFile(String groupName, String remoteFileName) throws Exception {
StorageClient storageClient = getTrackerClient();
int i = storageClient.delete_file(groupName, remoteFileName);
logger.info("删除文件成功:" + i);
}
private static StorageClient getTrackerClient() throws IOException {
TrackerServer trackerServer = getTrackerServer();
StorageClient storageClient = new StorageClient(trackerServer, null);
return storageClient;
}
private static TrackerServer getTrackerServer() throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerServer;
}
}2、测试接口
完成上面带步骤,就可以测试了。
上传图片使用 Content-Type:application/x-www-form-urlencoded 格式
上传成功后返回一个完整 url 路径。点击可查询上传文件内容。
访问图片
3、扩展说明
在上传接口中,我固定添加了 metaData 元信息。那么在文件成功上传后,stroage 目录中会多出一个 -m 的文件,用来存储 metaData 数据。 反之,如果不添加 metaData 信息,也不会多出一个 -m 的文件了。
四、权限控制
上面使用 nginx 支持http方式访问文件,但所有人都能直接访问这个文件服务器了,所以做一下权限控制。
FastDFS 的权限控制是在服务端开启 token 验证,客户端根据(文件名、当前unix时间戳、秘钥)获取 token,在地址中带上 token 参数即可通过 http 方式访问文件。
1、修改 /etc/fdfs/http.conf
# 设置为true表示开启token验证 http.anti_steal.check_token=true # 设置token失效的时间单位为秒(s),默认600 http.anti_steal.token_ttl=900 # 密钥,跟客户端配置文件的 fastdfs.http_secret_key 保持一致,长度不应超过128个字节 http.anti_steal.secret_key=TingFeng123 # 如果token检查失败,返回的页面 # http.anti_steal.token_check_fail=fastdfs/page/403.html http.anti_steal.token_check_fail=/etc/fdfs/check_fail.jpeg
我这里在 /etc/fdfs/ 目录下,拷贝了一张 check_fail.jpeg 的文件
2、配置客户端
客户端只需要设置如下两个参数即可,两边的密钥保持一致。
# token 防盗链功能
fastdfs.http_anti_steal_token=true
# 密钥
fastdfs.http_secret_key=TingFeng123
3、测试
(1)无 token
(2) token 无效
(3) token 有效
五、参考文章
https://www.cnblogs.com/chiangchou/p/fastdfs.html#_label4_0
未经允许请勿转载:程序喵 » SpringBoot + Gradle 整合 FastDFS 实现文件上传下载(六)
程序喵