Trong công việc, chúng ta thường cần tải lên và tải xuống các tệp. Trong quá khứ, tệp thường được lưu trữ trên máy chủ web. Tuy nhiên, với việc triển khai nhiều máy chủ, một máy chủ tệp độc lập là cần thiết. Trước đây, NFS được sử dụng cho chia sẻ tệp giữa nhiều máy chủ web, và rsync được sử dụng để đồng bộ hóa tệp giữa các máy chủ tệp. Các giải pháp này đã trở nên lỗi thời do độ phức tạp cao. Hiện nay, FastDFS, một hệ thống tệp phân tán nhẹ, đang được ưa chuộng vì dễ dàng triển khai và duy trì.
FastDFS, phát triển bởi Yu Qing từ Alibaba, là một hệ thống tệp phân tán mã nguồn mở, quản lý tệp (lưu trữ, đồng bộ, tải lên/xuống/xóa), đặc biệt phù hợp cho dịch vụ trực tuyến dựa trên tệp như trang web hình ảnh và video. Bài viết này sẽ hướng dẫn cách triển khai FastDFS đơn giản bằng Docker Compose, và cung cấp mã demo để kết nối với FastDFS, thực hiện các thao tác tải lên, tải xuống, và xóa tệp.
Giới thiệu về Kiến trúc FastDFS
FastDFS bao gồm TrackerServer và StorageServer. TrackerServer chịu trách nhiệm cân bằng tải và điều phối, trong khi StorageServer lưu trữ tệp. Mỗi TrackerServer có vị trí ngang hàng, thu thập trạng thái của StorageServer. StorageServer có thể được chia thành nhiều nhóm, mỗi nhóm lưu trữ tệp khác nhau, và các thành viên trong cùng một nhóm sẽ đồng bộ tệp.
Triển khai FastDFS trên Máy Đơn
Giả sử IP máy ảo của bạn là 192.168.136.128, đã cài đặt docker và docker-compose. Đầu tiên, tạo hai thư mục để lưu trữ dữ liệu của TrackerServer và StorageServer:
mkdir -p /app/fastdfs/tracker
mkdir -p /app/fastdfs/storage
Tiếp theo, vào thư mục /app/fastdfs và tạo file docker-compose.yml với nội dung sau:
version: '3.5'
services:
tracker:
image: delron/fastdfs
container_name: tracker
network_mode: host
volumes:
- /app/fastdfs/tracker:/var/fdfs
command: tracker
storage:
image: delron/fastdfs
container_name: storage
network_mode: host
volumes:
- /app/fastdfs/storage:/var/fdfs
environment:
TRACKER_SERVER: 192.168.136.128:22122
GROUP_NAME: group1
command: storage
depends_on:
- tracker
Chạy lệnh sau để khởi động các container:
cd /app/fastdfs
docker-compose up -d
Sau khi khởi động, bạn có thể truy cập FastDFS thông qua các địa chỉ sau:
- TrackerServer: 192.168.136.128:22122
- StorageServer: 192.168.136.128:23000
- Nginx: http://192.168.136.128:8888
Xây dựng Dự án
Tạo một dự án Spring Boot với cấu trúc sau:
WebMvcConfigđể cấu hình Knife4j.FastDfsControllercung cấp các API để thao tác với FastDFS.FastDfsServicechứa các phương thức thao tác với FastDFS.
File pom.xml:
<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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jobs</groupId>
<artifactId>springboot_fastdfs</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.27.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
</project>
File application.yml:
server:
port: 8090
knife4j:
enable: true
production: false
fdfs:
so-timeout: 2000
connect-timeout: 1000
tracker-list:
- 192.168.136.128:22122
web-server-url: http://192.168.136.128:8888/
Spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
Chi tiết Mã Nguồn
Lớp FastDfsService:
package com.jobs.service;
import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
@Slf4j
@Service
public class FastDfsService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
public String getFileFullUrl(String fdfsPath) {
return webServer.getWebServerUrl() + fdfsPath;
}
public String uploadFile(MultipartFile file) {
try {
StorePath storePath = client.uploadFile(file.getInputStream(), file.getSize(),
FilenameUtils.getExtension(file.getOriginalFilename()), null);
return storePath.getFullPath();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public String uploadFile(File file) {
try {
if (file.isDirectory()) {
log.error("Vui lòng tải lên tệp, không phải thư mục");
return null;
}
FileInputStream inputStream = new FileInputStream(file);
StorePath storePath = client.uploadFile(inputStream, file.length(),
FilenameUtils.getExtension(file.getName()), null);
return webServer.getWebServerUrl() + storePath.getFullPath();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public byte[] downloadFile(String fdfsPath) {
if (StringUtils.isEmpty(fdfsPath)) {
return null;
}
try {
StorePath storePath = StorePath.parseFromUrl(fdfsPath);
return client.downloadFile(storePath.getGroup(), storePath.getPath(), new DownloadByteArray());
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
public Boolean deleteFile(String fdfsPath) {
if (StringUtils.isEmpty(fdfsPath)) {
return false;
}
try {
StorePath storePath = StorePath.parseFromUrl(fdfsPath);
client.deleteFile(storePath.getGroup(), storePath.getPath());
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
}
Lớp FastDfsController:
package com.jobs.controller;
import com.jobs.service.FastDfsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Api(tags = "Các giao diện thao tác FastDFS")
@RestController
@RequestMapping("/fdfs")
public class FastDfsController {
@Autowired
private FastDfsService fastDfsService;
@ApiOperation("Tải lên tệp")
@ApiImplicitParam(name = "file", value = "Tệp cần tải lên",
dataType = "java.io.File", paramType = "query", required = true)
@PostMapping("/upload")
public Map uploadFile(@RequestPart("file") MultipartFile file) {
String fdfsPath = fastDfsService.uploadFile(file);
Map<String, String> map = new HashMap<>();
if (StringUtils.isNotBlank(fdfsPath)) {
map.put("fdfs_path", fdfsPath);
map.put("file_url", fastDfsService.getFileFullUrl(fdfsPath));
}
return map;
}
@ApiOperation(value = "Tải xuống tệp", produces = "application/octet-stream")
@ApiImplicitParam(name = "fdfsPath", value = "Đường dẫn tệp fdfs (không bao gồm tên miền web)", required = true)
@GetMapping("/download")
public void downloadFile(String fdfsPath, HttpServletResponse response) {
String fileName = FilenameUtils.getName(fdfsPath);
byte[] bytes = fastDfsService.downloadFile(fdfsPath);
if (bytes != null) {
try {
response.setContentType("application/octet-stream;charset=utf-8");
fileName = new String(fileName.getBytes(), "iso8859-1");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
IOUtils.write(bytes, response.getOutputStream());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
@ApiOperation("Xóa tệp")
@ApiImplicitParam(name = "fdfsPath", value = "Đường dẫn tệp fdfs (không bao gồm tên miền web)", required = true)
@PostMapping("/delete")
public String deleteFile(String fdfsPath) {
Boolean flag = fastDfsService.deleteFile(fdfsPath);
return flag ? "delete success" : "delete fail";
}
}
Kiểm tra và Xác minh
Khởi động dự án (cổng 8090) và truy cập http://localhost:8090/doc.html để mở giao diện tài liệu Knife4j. Thử nghiệm các chức năng tải lên, tải xuống, và xóa tệp thông qua các API.