玩转阿里云服务器(三)- Spring项目集成FastDFS文件服务器
Spring项目集成FastDFS文件服务器
0.引言
在现代软件开发中,文件的管理与存储是至关重要的一环。随着分布式系统和云计算的发展,传统的文件管理方式逐渐无法满足高并发、高性能以及高可用性的需求。FastDFS作为一种轻量级的开源分布式文件系统,因其高效的文件存储、同步及访问功能,逐渐成为解决大容量存储和负载均衡问题的首选方案。Spring Boot作为现代Java开发中广泛使用的框架,其简洁的配置和丰富的生态系统使得开发者能够快速构建高效、稳定的应用。本文将详细介绍如何在Spring Boot项目中集成FastDFS文件服务器,通过这一整合实现文件的高效上传和下载,为应用程序提供强大的文件处理能力。
1.环境准备
1.1 云服务器安装部署FastDFS
- 服务器一台 Ubuntu 16.04及以上版本
- docker 18.09.9
阿里云部署FastDFS文件服务器参考上一篇文章:玩转阿里云服务器(二)- 安装FastDFS文件服务器教程
1.2 本地机器
- JAVA开发环境
- Maven环境
- IDEA开发工具
2、构建项目
2.1 创建项目
- 创建新项目
很简单,打开我们IDEA编译器,找到左上角的File->New->Project,然后选择Spring Initializr,然后填写项目名称,选择项目存放路径,然后点击Next。
- 选择依赖
下一步是选择一些内嵌的依赖,这里我们直接跳过也可以,后面用本博的pom配置即可,然后点击Create。
- 打开项目
然后打开刚创建好的项目,应该是张这样。
2.1 配置项目
- 配置maven
通过设置或者根据下图找到maven设置,配置上自己的maven路径。
- 配置pom
根据自己的java版本和maven版本,配置自己的pom.xml。复制下文的也可以。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 接下来的5行,需要根据自己创建的项目来配置-->
<groupId>com.daniel</groupId>
<artifactId>file-services</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>file-services</name>
<description>file-services</description>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 数据库驱动连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- FastDFS -->
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.2</version>
</dependency>
<!-- 提供对外接口访问-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- application.properties
本文中使用的application.properties默认的就行,不做修改配置。
- 配置FastDFS连接
在scr/main/resources(与application.properties同级)下创建一个文件“fdfs_client.properties”,然后配置如下:
#fastDFS
connect_timeout_in_seconds = 5
network_timeout_in_seconds = 30
charset = UTF-8
tracker_server = FastDFS_IP:22122 //FastDFS文件服务器的ip
- 重载配置依赖
配置完上面几项之后,刷新一下项目的pom,按照下图即可。
2.2 编写代码
在com.daniel.file下创建一下几个包:util、resp、controller、dto,此时的项目结构如下图:
- util工具类
首先创建我们基础返回枚举,在util包下创建EnumResultCode,代码如下:
package com.daniel.file.util;
import lombok.Getter;
/**
* @Author daniel
* @Date 2024/3/4 11:27
* @Since 1.0.0.0
* @description:统一返回结果状态信息类
*/
@Getter
public enum EnumResultCode {
SUCCESS(200,"成功"),
FAIL(201, "失败"),
SERVICE_ERROR(2012, "服务异常"),
SYS_ERROR(500,"系统异常"),
DATA_ERROR(204, "数据异常"),
ILLEGAL_REQUEST(205, "非法请求"),
REPEAT_SUBMIT(206, "重复提交"),
ARGUMENT_VALID_ERROR(210, "参数校验异常"),
LOGIN_AUTH(208, "未登陆"),
PERMISSION(209, "没有权限"),
ACCOUNT_ERROR(214, "账号不正确"),
ACCOUNT_EXIST(215,"账号已存在"),
PASSWORD_ERROR(216, "密码不正确"),
LOGIN_MOBLE_ERROR( 217, "账号或密码不正确"),
ACCOUNT_STOP( 218, "账号已停用"),
NODE_ERROR( 219, "该节点下有子节点,不可以删除")
;
public Integer code;
public String message;
EnumResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
然后创建我们最为重要的,FastDFS服务器工具类,这个是我们集成文件服务器的重中之重,在util包中创建FastDFSUtil.java。
package com.daniel.file.util;
import com.daniel.file.dto.FastDFSFile;
import lombok.extern.slf4j.Slf4j;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @Author daniel
* @Date 2024/12/17 15:06
* @description: FastDFS工具类
*/
@Slf4j
public class FastDFSUtil {
private static Logger logger = LoggerFactory.getLogger(FastDFSUtil.class);
/***
* 初始化tracker信息
*/
static {
try {
String filePath = new ClassPathResource("fdfs_client.properties").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (Exception e) {
logger.error("FastDFS Client Init Fail!", e);
}
}
/****
* 文件上传
* @param file : 要上传的文件信息封装->FastDFSFile
* @return String[]
* 1:文件上传所存储的组名
* 2:文件存储路径
*/
public static String[] upload(FastDFSFile file) {
logger.info("File Name: " + file.getName() + "File Length:" + file.getContent().length);
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("author", file.getAuthor());
long startTime = System.currentTimeMillis();
String[] uploadResults = null;
StorageClient storageClient = null;
try {
storageClient = getTrackerClient();
log.info("file.getExt():{}", file.getExt());
uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
} catch (IOException e) {
logger.error("IO Exception when uploadind the file:" + file.getName(), e);
} catch (Exception e) {
logger.error("Non IO Exception when uploadind the file:" + file.getName(), e);
}
logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime) + " ms");
if (uploadResults == null && storageClient != null) {
logger.error("upload file fail, error code:" + storageClient.getErrorCode());
}
String groupName = uploadResults[0];
String remoteFileName = uploadResults[1];
logger.info("upload file successfully!!!" + "group_name:" + groupName + ", remoteFileName:" + " " + remoteFileName);
return uploadResults;
}
/***
* 获取文件信息
* @param groupName:组名
* @param remoteFileName:文件存储完整名
*/
public static FileInfo getFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getTrackerClient();
return storageClient.get_file_info(groupName, remoteFileName);
} catch (IOException e) {
logger.error("IO Exception: Get File from Fast DFS failed", e);
} catch (Exception e) {
logger.error("Non IO Exception: Get File from Fast DFS failed", 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 (IOException e) {
logger.error("IO Exception: Get File from Fast DFS failed", e);
} catch (Exception e) {
logger.error("Non IO Exception: Get File from Fast DFS failed", e);
}
return null;
}
/***
* 文件删除实现
* @param groupName:组名
* @param remoteFileName:文件存储完整名
*/
public static void deleteFile(String groupName, String remoteFileName)
throws Exception {
StorageClient storageClient = getTrackerClient();
int i = storageClient.delete_file(groupName, remoteFileName);
logger.info("delete file successfully!!!" + i);
}
/***
* 获取组信息
* @param groupName :组名
*/
public static StorageServer[] getStoreStorages(String groupName)
throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerClient.getStoreStorages(trackerServer, groupName);
}
/***
* 根据文件组名和文件存储路径获取Storage服务的IP、端口信息
* @param groupName :组名
* @param remoteFileName :文件存储完整名
*/
public static ServerInfo[] getFetchStorages(String groupName,
String remoteFileName) throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName);
}
/***
* 获取Tracker服务地址
*/
public static String getTrackerUrl() throws IOException {
return "http://" + getTrackerServer().getInetSocketAddress().getHostString() + ":" + ClientGlobal.getG_tracker_http_port() + "/";
}
/***
* 获取StorageClient
* @return
* @throws Exception
*/
private static StorageClient getTrackerClient() throws IOException {
TrackerServer trackerServer = getTrackerServer();
StorageClient storageClient = new StorageClient(trackerServer, null);
return storageClient;
}
/***
* 获取TrackerServer
*/
private static TrackerServer getTrackerServer() throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerServer;
}
}
- resp响应类
我们希望代码的接口都用统一的返回格式,在resp包中创建BaseResponse.java。
package com.daniel.file.resp;
/**
* @Author daniel
* @Date 2024/3/4 11:25
* @Since 1.0.0.0
* @description:全局统一返回结果类
*/
import com.daniel.file.util.EnumResultCode;
import lombok.Data;
@Data
public class BaseResponse<T> {
//返回码
private Integer code;
//返回消息
private String message;
//返回数据
private T data;
public BaseResponse(){}
// 返回数据
private static <T> BaseResponse<T> build(T data) {
BaseResponse<T> result = new BaseResponse<T>();
if (data != null) {
result.setData(data);
}
return result;
}
private static <T> BaseResponse<T> build(Integer code, String message) {
BaseResponse<T> result = new BaseResponse<T>();
result.setCode(code);
result.setMessage(message);
return result;
}
private static <T> BaseResponse<T> build(T body, Integer code, String message) {
BaseResponse<T> result = build(body);
result.setCode(code);
result.setMessage(message);
return result;
}
private static <T> BaseResponse<T> build(EnumResultCode resultCodeEnum, T body) {
BaseResponse<T> result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static<T> BaseResponse<T> success(){
return BaseResponse.success(null);
}
/**
* 操作成功
* @param data baseCategory1List
* @param <T>
* @return
*/
public static<T> BaseResponse<T> success(T data){
BaseResponse<T> result = build(data);
return build(EnumResultCode.SUCCESS, data);
}
public static<T> BaseResponse<T> fail(Integer code, String message) {
BaseResponse<T> result = build(code,message);
return result;
}
private BaseResponse<T> message(String msg){
this.setMessage(msg);
return this;
}
private BaseResponse<T> code(Integer code){
this.setCode(code);
return this;
}
}
- Dto数据传输
我们需要封装一个Dto,用于与FastDFS之间的数据传输,在Dto包中创建FastDFSFileDto.java。
package com.daniel.file.dto;
import lombok.Data;
/**
* @Author daniel
* @Date 2024/12/17 15:04
* @description: FastDFS文件实体
*/
@Data
public class FastDFSFileDto {
/**
* 文件名字
*/
private String name;
/**
* 文件内容
*/
private byte[] content;
/**
* 文件扩展名
*/
private String ext;
/**
* 文件MD5摘要值
*/
private String md5;
/**
* 文件创建作者
*/
private String author;
/**
* 文件组
*/
private String group;
/**
* 文件路径
*/
private String filePath;
public FastDFSFileDto(String name, byte[] content, String ext, String height,
String width, String author) {
super();
this.name = name;
this.content = content;
this.ext = ext;
this.author = author;
}
public FastDFSFileDto(String name, byte[] content, String ext) {
super();
this.name = name;
this.content = content;
this.ext = ext;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public String getExt() {
return ext;
}
public void setExt(String ext) {
this.ext = ext;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
- controller层
最后一个就是我们的web层,提供外部接口访问,也就是我们要通过前端或者说是postman等上传文件和下载文件的web接口层,在controller包中创建FastDFSController.java。
package com.daniel.file.controller;
import com.daniel.file.dto.FastDFSFileDto;
import com.daniel.file.resp.BaseResponse;
import com.daniel.file.util.EnumResultCode;
import com.daniel.file.util.FastDFSUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.IOException;
import java.io.InputStream;
/**
* @Author daniel
* @Date 2024/12/17 15:10
* @description: TODO
*/
@RestController
@RequestMapping("/fastDFS")
public class FastDFSController {
final String FDFS_GROUP = "group1";
private static Logger logger = LoggerFactory.getLogger(FastDFSController.class);
@GetMapping("/")
public String index() {
return "upload";
}
@RequestMapping(value = "/upload_file", method = RequestMethod.POST)
public String singleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
return "redirect:uploadStatus";
}
try {
String path = saveFile(file);
return path;
} catch (Exception e) {
logger.error("upload file failed", e);
}
return "redirect:/uploadStatus";
}
@GetMapping("/uploadStatus")
public String uploadStatus() {
return "uploadStatus";
}
/**
* @param multipartFile
* @return
* @throws IOException
*/
public String saveFile(MultipartFile multipartFile) throws IOException {
String[] fileAbsolutePath = {};
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();
FastDFSFileDto file = new FastDFSFileDto(fileName, file_buff, ext);
try {
fileAbsolutePath = FastDFSUtil.upload(file); //upload to fastdfs
} catch (Exception e) {
logger.error("upload file Exception!", e);
}
String path = FastDFSUtil.getTrackerUrl() + fileAbsolutePath[0] + "/" + fileAbsolutePath[1];
return path;
}
@RequestMapping(value = "/download_file", method = RequestMethod.POST)
public BaseResponse singleFileDownload(@RequestParam("file") String filePath) {
InputStream inputStream = FastDFSUtil.downFile(FDFS_GROUP, filePath);
if(inputStream == null){
return BaseResponse.fail(EnumResultCode.FAIL.code,"文件不存在");
}
return BaseResponse.success("文件下载成功");
}
}
- 完整项目结构
完成上面代码编写后,我们的项目完成目录如下:
然后运行项目的启动类,即可启动我们的项目。
3、测试验证
项目创建完成和启动成功后,我们便可以通过postman上传文件和下载文件了。
3.1、上传文件
在postman新建一个请求,请求地址为:127.0.0.1:8080/fastDFS/upload_file,请求类型为:post,请求体类型为:form-data,具体如下:
点击发送之后,请求到我们刚刚启动的项目服务,可以回到项目去看看。请求成功之后,会返回文件服务器上的文件路径,复制这个路径到浏览器上就可以浏览或者下载该文件了。
3.2、下载文件
下载文件除了通过前面路径去访问文件之后,我们代码中有时候也是需要处理文件的,比如文件流。所以我们还需要通过代码去下载文件服务器上的文件。
同理,在postman新建一个请求,请求地址为:127.0.0.1:8080/fastDFS/download_file,请求类型为:post,请求体类型为:form-data,具体如下:
点击发送之后,请求到我们刚刚启动的项目服务,可以回到项目去看看。
4、总结
恭喜您完成了Spring项目集成FastDFS分布式文件系统的任务,希望这个任务能够帮助您更好的理解FastDFS分布式文件系统。
未来,下一篇文章,我们将探讨如何将这个项目快速部署到云服务器上,并且后续将计息探讨如何提供这些接口给别的项目服务去调用上传和下载文件。