• 作者:老汪软件技巧
  • 发表时间:2024-11-03 07:01
  • 浏览量:

零拷贝设计旨在提高数据传输效率,减少CPU负担,降低延迟。常见应用场景包括文件传输、网络流媒体、数据库操作等。下边我们通过一个简单的场景案例来阐述一下这个零拷贝。

场景案例

功能点概述:

文件分块上传:允许大文件分块上传,以便于更高效地管理和恢复上传。文件元数据存储:在数据库中存储文件的元数据(如文件名、大小、上传时间等)。异步处理:使用异步方法处理文件上传和下载,以提高响应性。安全性:增加文件的安全性和访问控制。

以下是示例代码。

项目结构Controller: 处理上传、下载和元数据请求。Service: 处理业务逻辑,包括文件上传、下载和元数据管理。Repository: 处理数据库操作。Entity: 定义文件元数据的实体类。Configuration: 设置文件存储路径。异步配置: 支持异步文件处理。依赖

在你的 pom.xml 中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-jpaartifactId>
    dependency>
    <dependency>
        <groupId>org.h2databasegroupId>
        <artifactId>h2artifactId>
        <scope>runtimescope>
    dependency>
dependencies>

文件存储配置

创建一个配置类来定义文件存储路径:

javaCopy codeimport org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "file")
public class FileStorageProperties {
    private String uploadDir;
    public String getUploadDir() {
        return uploadDir;
    }
    public void setUploadDir(String uploadDir) {
        this.uploadDir = uploadDir;
    }
}

在 application.properties 中配置文件上传目录:

properties
Copy code
file.upload-dir=uploads

数据库配置

在 application.properties 中添加数据库配置:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=create-drop

文件元数据实体

创建一个实体类用于存储文件的元数据:

import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
public class FileMetadata {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String filename;
    private long size;
    private LocalDateTime uploadTime;
    // Getters and Setters
}

文件元数据仓库

创建一个仓库接口用于访问文件元数据:

import org.springframework.data.jpa.repository.JpaRepository;
public interface FileMetadataRepository extends JpaRepository {
    FileMetadata findByFilename(String filename);
}

文件服务

在服务类中添加文件元数据管理和异步处理:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
@Service
public class FileStorageService {
    @Autowired
    private FileStorageProperties fileStorageProperties;
    @Autowired
    private FileMetadataRepository fileMetadataRepository;
    @Async
    @Transactional
    public void storeFile(Path filePath, byte[] fileContent) throws IOException {
        Path uploadPath = Paths.get(fileStorageProperties.getUploadDir()).resolve(filePath.getFileName());
        Files.write(uploadPath, fileContent);
        
        // Save metadata to the database
        FileMetadata metadata = new FileMetadata();
        metadata.setFilename(filePath.getFileName().toString());
        metadata.setSize(fileContent.length);
        metadata.setUploadTime(LocalDateTime.now());
        fileMetadataRepository.save(metadata);
    }
    public Path loadFile(Path filePath) {
        return Paths.get(fileStorageProperties.getUploadDir()).resolve(filePath.getFileName());
    }
    public FileMetadata getFileMetadata(String filename) {
        return fileMetadataRepository.findByFilename(filename);
    }
}

文件控制器

扩展控制器以支持分块上传、下载和获取元数据:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Path;
@RestController
@RequestMapping("/files")
public class FileController {
    @Autowired
    private FileStorageService fileStorageService;
    @PostMapping("/upload")
    public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            fileStorageService.storeFile(Path.of(file.getOriginalFilename()), file.getBytes());
            return ResponseEntity.ok("File uploaded successfully: " + file.getOriginalFilename());
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("File upload failed: " + e.getMessage());
        }
    }
    @GetMapping("/download/{filename}")
    public ResponseEntity downloadFile(@PathVariable String filename) {
        try {
            Path filePath = fileStorageService.loadFile(Path.of(filename));
            Resource resource = new UrlResource(filePath.toUri());
            if (resource.exists() || resource.isReadable()) {
                return ResponseEntity.ok()
                        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                        .body(resource);
            } else {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
            }
        } catch (MalformedURLException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        }
    }
    @GetMapping("/metadata/{filename}")
    public ResponseEntity getFileMetadata(@PathVariable String filename) {
        FileMetadata metadata = fileStorageService.getFileMetadata(filename);
        if (metadata != null) {
            return ResponseEntity.ok(metadata);
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }
    }
}

异步配置

在主类中启用异步支持:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class FileUploadDownloadApplication {
    public static void main(String[] args) {
        SpringApplication.run(FileUploadDownloadApplication.class, args);
    }
}

使用文件分块上传(扩展)

为了实现分块上传,前端需要支持将文件分割成多个部分进行上传。在后端,可以创建一个新的 API 接口来接收这些分块:

@PostMapping("/upload/chunk")
public ResponseEntity uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex) {
    // 存储每个分块到一个临时目录,合并逻辑可以在上传完成后实现
    // 这里可以根据 chunkIndex 来管理每个分块的存储
    return ResponseEntity.ok("Chunk " + chunkIndex + " uploaded successfully");
}

结论

在上面的例子中,文件的上传和下载都使用了 NIO,这样可以实现零拷贝。具体来说,使用 Files.write() 和 UrlResource 读取文件时,JVM 会尽量避免不必要的数据拷贝。此外,你可以管理大文件的分块上传、存储文件的元数据,并异步处理文件上传和下载。你还可以进一步扩展功能,例如增加权限控制、文件类型验证和进度监控等。这样可以更好地满足生产环境的需求。


上一条查看详情 +jieba-fenci 05 结巴分词之简单聊一聊
下一条 查看详情 +没有了