首发于 渔码
亚马逊S3文件和目录API操作干货

亚马逊S3文件和目录API操作干货

前言:

国内云厂商也有对象存储服务,比如阿里云的OSS,但是如果你的应用面向国外用户,可能会面临一些数据国外要求不能存在中国的问题,那你就需要使用Google的cloud storage或者亚马逊的S3这类国外的对象存储服务,这篇文章用JAVA代码向读者展示S3里文件和目录的增删改查怎么做。

首先你需要在S3上先创建一个Bucket,然后集成官方的S3开发SDK,我用的是V2版接口的SDK:

<dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.16.60</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <version>2.16.60</version>
        </dependency>

项目配置参数:

aws:
  access-key: 你的API访问key,亚马逊登录账号后可查看
  access-secret: 你的API访问密钥,亚马逊登录账号后可创建
  bucket: 你的桶名

客户端初始化工具类:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
@Data
@Component
public class AwsS3Utils {
    @Value("${aws.access-key}")
    private String accessKeyId;
    @Value("${aws.access-secret}")
    private String secretAccessKey;
    @Value(("${aws.bucket}"))
    private String bucket;
    private static final Region region = Region.US_EAST_1;// 替换成你自己的region
    public S3Client getS3Client() {
        AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
        S3Configuration s3Config = S3Configuration.builder().pathStyleAccessEnabled(true).build();
        try {
            S3Client s3 = S3Client.builder()
                    .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials))
                    .region(region)
                    .serviceConfiguration(s3Config)
                    .build();
            return s3;
        } catch (Exception ex) {
            throw new RuntimeException(ex.getMessage());
    public S3Presigner getS3Presigner() {
        try {
            AwsCredentialsProvider awsCredentialsProvider = () -> {
                AwsCredentials awsCredentials = new AwsCredentials() {
                    @Override
                    public String accessKeyId() {
                        return accessKeyId;
                    @Override
                    public String secretAccessKey() {
                        return secretAccessKey;
                return awsCredentials;
            S3Presigner presigner = S3Presigner.builder()
                    .region(region)
                    .credentialsProvider(awsCredentialsProvider)
                    .build();
            return presigner;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());

文件目录创建:

public NameNormalVo addDirMsg(CreateDirDto params, String userId) {
        log.info("[FileManageServiceImpl.addDirMsg] enter service. params:[{}], user_id:{}", params, userId);
        NameNormalVo result = new NameNormalVo();
        try {
            // 要创建的目录名
            String name = params.getName().trim();
            // 前缀 在什么目录下创建,根目录可传空,如果是在dir1下创建dir2,那name就是dir2,prefix就是dir1
            String prefix = params.getPrefix().trim() + "/";
            S3Client s3Client = awsS3Utils.getS3Client();
            software.amazon.awssdk.services.s3.model.PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(awsS3Utils.getBucket())
                    .key(prefix + name + "/")
                    .build();
            RequestBody requestBody = RequestBody.empty();
            s3Client.putObject(putObjectRequest, requestBody);
            result.setName(params.getName());
        } catch (NorthException ex) {
            log.error("[FileManageServiceImpl.addDirMsg] add dir msg failed!Error is:[{}].", ex.getMessage());
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new NorthException(NorthErrorEnum.SERVER_INTERNAL_ERROR);
        log.info("[FileManageServiceImpl.addDirMsg] out service.");
        return result;
    }

效果:

修改文件名或者修改目录名 (注意,S3没有直接修改的API,修改方式是调用copy API copy出新的资源,然后再删除老的):

public NameNormalVo updateDirMsg(UpdateDirDto params, String userId) {
        log.info("[FileManageServiceImpl.updateDirMsg] enter service. params:[{}], user_id:{}", params, userId);
        NameNormalVo result = new NameNormalVo();
        try {
            String name = params.getName().trim();
            String prefix = params.getPrefix() + "/";
            boolean isDir = false;
            if (!prefix.contains(".")) {
                isDir = true;
            S3Client s3Client = awsS3Utils.getS3Client();
            if (!isDir) {
                // 修改文件名,先复制一个文件
                String newKey = prefix.substring(0, prefix.lastIndexOf("/")) + "/" + name + prefix.substring(prefix.indexOf("."));
                CopyObjectRequest copyReq = CopyObjectRequest.builder()
                        .copySource(URLEncoder.encode(awsS3Utils.getBucket() + "/" + prefix, "UTF-8"))// 注意!这里要带上桶名,否则会报错找不到桶
                        .destinationBucket(awsS3Utils.getBucket())
                        .destinationKey(newKey)
                        .contentDisposition("attachment; filename=" + URLEncoder.encode(name + prefix.substring(prefix.indexOf(".")), "UTF-8"))
                        .metadataDirective(MetadataDirective.REPLACE)
                        .build();
                s3Client.copyObject(copyReq);
                // 对旧名称的文件进行删除
                DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                        .bucket(awsS3Utils.getBucket())
                        .key(prefix)
                        .build();
                s3Client.deleteObject(deleteObjectRequest);
            } else {
                // 如果是目录,遍历目录下文件依次复制删除
                ListObjectsV2Request.Builder builder = ListObjectsV2Request.builder();
                // 设置bucket
                builder.bucket(awsS3Utils.getBucket());
                builder.prefix(prefix);
                ListObjectsV2Request listObjReq = builder.build();
                ListObjectsV2Response listObjRes = s3Client.listObjectsV2(listObjReq);
                // 这里会得到目录下每一层级的资源路径,包括子目录和文件
                List<S3Object> s3ObjectList = listObjRes.contents();
                if (s3ObjectList != null && s3ObjectList.size() > 0) {
                    for (S3Object s3Object : s3ObjectList) {
                        String newPrefix = prefix.substring(0, prefix.lastIndexOf("/") + 1) + name;
                        String newKey = s3Object.key().replace(prefix, newPrefix);
                        if (!s3Object.key().contains(".")) {
                            // 目录下的子目录在新的复制路径下创建,相当于复制了目录
                            software.amazon.awssdk.services.s3.model.PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                                    .bucket(awsS3Utils.getBucket())
                                    .key(newKey)
                                    .build();
                            RequestBody requestBody = RequestBody.empty();
                            s3Client.putObject(putObjectRequest, requestBody);
                        } else {
                            // 目录下的文件直接复制
                            CopyObjectRequest copyReq = CopyObjectRequest.builder()
                                    .copySource(URLEncoder.encode(awsS3Utils.getBucket() + "/" + s3Object.key(), "UTF-8"))
                                    .destinationBucket(awsS3Utils.getBucket())
                                    .destinationKey(newKey)
                                    .contentDisposition("attachment; filename=" + URLEncoder.encode(name + newKey.substring(newKey.indexOf(".")), "UTF-8"))
                                    .metadataDirective(MetadataDirective.REPLACE)
                                    .build();
                            s3Client.copyObject(copyReq);
                    // 复制完删除,但是S3 API不支持指定一个文件夹路径连带文件一起删除,必须遍历文件夹下资源路径依次
                    //都删除完才能删除文件夹成功
                    for (S3Object s3Object : s3ObjectList) {
                        DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                                .bucket(awsS3Utils.getBucket())
                                .key(s3Object.key())
                                .build();
                        // 复制完删除
                        s3Client.deleteObject(deleteObjectRequest);
            result.setName(name);
        } catch (NorthException ex) {
            log.error("[FileManageServiceImpl.updateDirMsg] add dir msg failed!Error is:[{}].", ex.getMessage());
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new NorthException(NorthErrorEnum.SERVER_INTERNAL_ERROR);
        log.info("[FileManageServiceImpl.updateDirMsg] out service.");
        return result;
    }

查询当前目录下面一级的子目录和文件:

        log.info("[FileManageServiceImpl.getAwsResources] enter service.user_id:{}", userId);
        SelfPageInfo<SaveFilesItemVo> result = new SelfPageInfo<>();
        try {
            ListObjectsV2Request.Builder builder = ListObjectsV2Request.builder();
            // 设置bucket
            builder.bucket(awsS3Utils.getBucket());
            builder.maxKeys(1000);
            // 要查询的目录前缀
            builder.prefix(prefix + "/");
            ListObjectsV2Request listObjReq = builder.build();
            S3Client s3Client = awsS3Utils.getS3Client();
            ListObjectsV2Response listObjRes = s3Client.listObjectsV2(listObjReq);
            List<SaveFilesItemVo> files = new ArrayList<>();
            List<S3Object> s3ObjectList = listObjRes.contents();
            if (s3ObjectList != null && s3ObjectList.size() > 0) {
                List<String> uniqueDirs = new ArrayList<>();
                for (S3Object s3Object : s3ObjectList) {
                    boolean isDir = false;
                    String path = s3Object.key();
                    String fileName = path.replace(prefix, "");
                    if (fileName.contains("/")) {
                        isDir = true;
                        fileName = fileName.substring(0, fileName.indexOf("/"));
                    // 对子目录进行去重
                    if (!uniqueDirs.contains(fileName) && !StringUtils.isBlank(fileName)) {
                        SaveFilesItemVo filesItemVo = SaveFilesItemVo.builder().build();
                        long timeStamp = s3Object.lastModified().getEpochSecond() * 1000;
                        filesItemVo.setFileName(fileName);
                        filesItemVo.setSize(s3Object.size().intValue());
                        filesItemVo.setDirFlag(isDir);
                        filesItemVo.setTimestamp(timeStamp);
                        path = path.replace(userId + "/", "");
                        filesItemVo.setPath(path.substring(0, path.indexOf(fileName)) + fileName);
                        files.add(filesItemVo);
                        uniqueDirs.add(fileName);
        } catch (NorthException ex) {
            log.error("[FileManageServiceImpl.getAwsResources] query aws resources failed!Error is:[{}].", ex.getMessage());
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new NorthException(NorthErrorEnum.SERVER_INTERNAL_ERROR);
        log.info("[FileManageServiceImpl.getAwsResources] out service.");
        return result;

删除文件或者文件夹:

        log.info("[FileManageServiceImpl.deleteFileResource] enter service. prefix:[{}], user_id:{}", prefix, userId);
        PathNormalVo result = new PathNormalVo();
        result.setPath(prefix);
        try {
            // 如果删除dir1下的1.png,那prefix就是dir1/1.png,如果是删除目录,那prefix就是dir1/
            boolean isDir = false;
            if (!prefix.contains(".")) {
                isDir = true;
            S3Client s3Client = awsS3Utils.getS3Client();
            if (!isDir) {
                // 删除文件
                DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                        .bucket(awsS3Utils.getBucket())
                        .key(prefix)
                        .build();
                s3Client.deleteObject(deleteObjectRequest);
            } else {
                // 删除文件夹及下面的子目录和文件
                ListObjectsV2Request.Builder builder = ListObjectsV2Request.builder();
                // 设置bucket
                builder.bucket(awsS3Utils.getBucket());
                builder.prefix(prefix);
                ListObjectsV2Request listObjReq = builder.build();
                ListObjectsV2Response listObjRes = s3Client.listObjectsV2(listObjReq);
                List<S3Object> s3ObjectList = listObjRes.contents();
                if (s3ObjectList != null && s3ObjectList.size() > 0) {
                    for (S3Object s3Object : s3ObjectList) {
                        DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                                .bucket(awsS3Utils.getBucket())
                                .key(s3Object.key())
                                .build();
                        // 复制完删除
                        s3Client.deleteObject(deleteObjectRequest);
        } catch (NorthException ex) {
            log.error("[FileManageServiceImpl.deleteFileResource] delete file resource msg failed!Error is:[{}].", ex.getMessage());
            throw ex;
        } catch (Exception ex) {