SpringBoot + Gradle 整合 FastDFS 实现文件上传下载(六)

前面文件系统平台搭建好了,现在就要写客户端代码在系统中实现上传下载,这里只是简单的测试代码。

官网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、引入配置文件

配置文件可以是 confproperties 文件,当然,也是可以在 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/

配置文件

fdfs-java-1.jpg

二、测试类

编写测试类

/**
 * 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 格式

fdfs-java-2.jpg

上传成功后返回一个完整 url 路径。点击可查询上传文件内容。

访问图片

fdfs-java-3.jpg

3、扩展说明

在上传接口中,我固定添加了 metaData 元信息。那么在文件成功上传后,stroage 目录中会多出一个 -m 的文件,用来存储 metaData 数据。 反之,如果不添加 metaData 信息,也不会多出一个 -m 的文件了。

fdfs-java-4.jpg

四、权限控制

上面使用 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

fdfs-java-5.jpg

(2) token 无效

fdfs-java-6.jpg

(3) token 有效

fdfs-java-7.jpg

五、参考文章

https://www.cnblogs.com/chiangchou/p/fastdfs.html#_label4_0


未经允许请勿转载:程序喵 » SpringBoot + Gradle 整合 FastDFS 实现文件上传下载(六)

点  赞 (2) 打  赏
分享到: