如何在Spring Boot中向Amazon S3上传文件

1,221 阅读5分钟

如何在Spring Boot中向Amazon S3上传文件

亚马逊简单存储(Amazon S3)是亚马逊网络服务提供的一项服务,提供可扩展、安全和性能良好的对象存储。本文将介绍如何使用Spring Boot向Amazon S3上传文件。

前提条件

  • 对[Java]有一定了解。
  • 在你的电脑上安装了Java开发工具包。
  • 你喜欢的IDE。我使用[Intellij社区版],它是免费的。

亚马逊网络服务账户

在我们开始创建我们的应用程序之前,请到[亚马逊控制台],并创建一个账户。你将获得12个月的免费访问各种亚马逊网络服务的机会,你可以用它来测试各种亚马逊服务。

注册后,前往[亚马逊控制台],在控制台提供的搜索框中搜索Amazon S3。

![Amazon search console]

亚马逊S3桶

从上面的步骤中选择Amazon S3后,创建一个新的S3桶,我们将用它来存储我们将从我们的应用程序上传的文件。

Amazon creating S3 bucket

将该桶命名为spring-amazon-storage ,并将所有其他设置保留为默认,然后创建该桶。

访问和密匙

My Security Credentials 导航菜单创建一个新的访问密钥,如下图所示。复制生成的访问权限和密匙,因为我们将使用它们从我们将要创建的应用程序中访问该桶。

Amazon secret key

创建应用程序

我们将使用spring initializr来创建我们的应用程序。前往spring initializr并创建一个新的Spring Boot应用程序,添加h2,spring boot dev tools,spring data jpaspring web 作为依赖,然后生成该项目。

Spring Boot create application

解压缩下载的项目,并在你最喜欢的IDE中打开它。

添加Amazon SDK的依赖性

亚马逊SDK使我们能够从我们的应用程序与各种亚马逊服务进行交互。在pom.xml 文件中添加Amazon SDK的依赖关系,如下所示。

    <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk -->
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk</artifactId>
        <version>1.11.931</version>
    </dependency>

项目结构

config/
|--- AmazonConfig.java
|--- BucketName.java
controllers/
|--- TodoController.java
domain/
|--- Todo.java
repositories/
|--- TodoRepository.java
service/
|--- FileStore.java
|--- TodoService.java
|--- TodoServiceImpl.java
SpringAmazonApplication.java

配置包

在配置包中,我们有两个Java文件,一个用来验证Amazon S3,另一个包含了桶的名称。

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AmazonConfig {
    @Bean
    public AmazonS3 s3() {
        AWSCredentials awsCredentials =
                new BasicAWSCredentials("accessKey", "secretKey");
        return AmazonS3ClientBuilder
                .standard()
                .withRegion("ap-south-1")
                .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                .build();

    }
}

上面的AmazonConfig 类被注解为@Configuration 注解,以使其作为一个配置类提供给Spring上下文。利用我们之前从亚马逊控制台得到的亚马逊凭证,我们将使用我们之前添加到pom.xml 中的Amazon-SDK中的AmazonS3ClientBuilder ,对S3进行认证。

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum BucketName {
    TODO_IMAGE("spring-amazon-storage");
    private final String bucketName;
}

在上面的BucketName 枚举中,我们传入我们先前在亚马逊控制台创建的桶的名称。这个桶将被用来存储我们所有的文件上传。

  • @AllArgsConstructor 注释生成了一个构造函数,其中包含枚举中的 变量。bucketName
  • @Getter 注释为枚举中的 变量生成一个getter。bucketName

领域包

在这个包中,我们有Todo 模型,在数据库中代表我们的Todo

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
public class Todo {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    private String description;
    private String imagePath;
    private String imageFileName;

}
  • @Data 注释为 类生成了 , , 和 方法。Todo getters setters toString equals
  • @AllArgsConstructor 注释为 类生成一个带有所有参数的构造函数。Todo
  • @NoArgsConstructor 注释为 类生成一个没有参数的构造函数。Todo
  • @Builder 注释为 类创建了一个构建器模式。Todo
  • @Entity 注释使 类成为一个数据库实体。Todo
  • @Id 注释将 字段标记为数据库中的主键。id
  • @GeneratedValue 注释使 字段在新的 被保存到数据库中时自动增加。id todo

仓库包

在这个包中,我们有一个存储库类,它扩展了JPA的CrudRepository 接口,使得它可以执行各种数据库查询。

public interface TodoRepository extends CrudRepository<Todo, Long> {
    Todo findByTitle(String title);
}

服务包

@AllArgsConstructor
@Service
public class FileStore {
    private final AmazonS3 amazonS3;

    public void upload(String path,
                       String fileName,
                       Optional<Map<String, String>> optionalMetaData,
                       InputStream inputStream) {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        optionalMetaData.ifPresent(map -> {
            if (!map.isEmpty()) {
                map.forEach(objectMetadata::addUserMetadata);
            }
        });
        try {
            amazonS3.putObject(path, fileName, inputStream, objectMetadata);
        } catch (AmazonServiceException e) {
            throw new IllegalStateException("Failed to upload the file", e);
        }
    }

    public byte[] download(String path, String key) {
        try {
            S3Object object = amazonS3.getObject(path, key);
            S3ObjectInputStream objectContent = object.getObjectContent();
            return IOUtils.toByteArray(objectContent);
        } catch (AmazonServiceException | IOException e) {
            throw new IllegalStateException("Failed to download the file", e);
        }
    }

}

在上面的FileStore 类中,我们有用于从Amazon S3上传和下载文件的逻辑。

upload 方法中,我们传入。

  • path 是存储文件的Amazon S3桶的路径。
  • fileName 是被上传文件的实际名称。当从S3下载文件时,它将被用作密钥。
  • optionalMetaData 地图包含文件的详细信息,如文件类型和文件大小。
  • inputStream 包含应该被保存到Amazon S3的实际文件。
      ObjectMetadata objectMetadata = new ObjectMetadata();
      optionalMetaData.ifPresent(map -> {
            if (!map.isEmpty()) {
                map.forEach(objectMetadata::addUserMetadata);
            }
        });

上面的代码块在optionalMetaData map中循环,将所有的文件信息添加到S3的objectMetaData

  • amazonS3.putObject(path, fileName, inputStream, objectMetadata); 将文件保存到Amazon S3桶中。

download 方法中。

  • S3Object object = amazonS3.getObject(path, key); 从传入的路径下载文件,文件名与 方法中传入的键相似。getObject
  • S3ObjectInputStream objectContent = object.getObjectContent(); 从Amazon S3返回的对象中获得一个inputStream。
  • IOUtils.toByteArray(objectContent) 将输入流转换为可通过Restful APIs发送的 。byteArray
public interface TodoService {
    Todo saveTodo(String title, String description, MultipartFile file);

    byte[] downloadTodoImage(Long id);

    List<Todo> getAllTodos();
}

上面的TodoService 接口包含了我们要实现的各种方法,以便能够保存和获取todos

@Service
@AllArgsConstructor
public class TodoServiceImpl implements TodoService {
    private final FileStore fileStore;
    private final TodoRepository repository;

    @Override
    public Todo saveTodo(String title, String description, MultipartFile file) {
        //check if the file is empty
        if (file.isEmpty()) {
            throw new IllegalStateException("Cannot upload empty file");
        }
        //Check if the file is an image
        if (!Arrays.asList(IMAGE_PNG.getMimeType(),
                IMAGE_BMP.getMimeType(),
                IMAGE_GIF.getMimeType(),
                IMAGE_JPEG.getMimeType()).contains(file.getContentType())) {
            throw new IllegalStateException("FIle uploaded is not an image");
        }
        //get file metadata
        Map<String, String> metadata = new HashMap<>();
        metadata.put("Content-Type", file.getContentType());
        metadata.put("Content-Length", String.valueOf(file.getSize()));
        //Save Image in S3 and then save Todo in the database
        String path = String.format("%s/%s", BucketName.TODO_IMAGE.getBucketName(), UUID.randomUUID());
        String fileName = String.format("%s", file.getOriginalFilename());
        try {
            fileStore.upload(path, fileName, Optional.of(metadata), file.getInputStream());
        } catch (IOException e) {
            throw new IllegalStateException("Failed to upload file", e);
        }
        Todo todo = Todo.builder()
                .description(description)
                .title(title)
                .imagePath(path)
                .imageFileName(fileName)
                .build();
        repository.save(todo);
        return repository.findByTitle(todo.getTitle());
    }

    @Override
    public byte[] downloadTodoImage(Long id) {
        Todo todo = repository.findById(id).get();
        return fileStore.download(todo.getImagePath(), todo.getImageFileName());
    }

    @Override
    public List<Todo> getAllTodos() {
        List<Todo> todos = new ArrayList<>();
        repository.findAll().forEach(todos::add);
        return todos;
    }
}

在上面的TodoServiceImpl ,我们提供了用于保存和获取所有todos 的方法的实现。

控制者包

在这个包中,我们有TodoController 类,它处理传入的HTTP请求。


@RestController
@RequestMapping("api/v1/todo")
@AllArgsConstructor
@CrossOrigin("*")
public class TodoController {
    TodoService service;

    @GetMapping
    public ResponseEntity<List<Todo>> getTodos() {
        return new ResponseEntity<>(service.getAllTodos(), HttpStatus.OK);
    }

    @PostMapping(
            path = "",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public ResponseEntity<Todo> saveTodo(@RequestParam("title") String title,
                                         @RequestParam("description") String description,
                                         @RequestParam("file") MultipartFile file) {
        return new ResponseEntity<>(service.saveTodo(title, description, file), HttpStatus.OK);
    }

    @GetMapping(value = "{id}/image/download")
    public byte[] downloadTodoImage(@PathVariable("id") Long id) {
        return service.downloadTodoImage(id);
    }
}

测试我们从S3桶上传和下载的情况

Amazon S3 upload

总结

恭喜你!现在你已经学会了如何上传和下载。现在你已经学会了如何从Amazon S3上传和下载文件,继续实现向Amazon S3上传多个文件的逻辑。