前面文件系统平台搭建好了,现在就要写客户端代码在系统中实现上传下载,这里只是简单的测试代码。
官网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 实现文件上传下载(六)