1. 引入依赖

<!-- AWS-S3 -->
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.11.374</version>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-cbor</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-cbor</artifactId>
    <version>2.14.1</version>
</dependency>

2. 配置类

@Data
@Configuration
@ConfigurationProperties(prefix = "amazon.s3")
public class AmazonS3Properties {

    private String accessKey;

    private String secretKey;

    private String serviceEndpoint;

    private String bucketName;
}

3. 配置信息

amazon:
  s3:
    access-key: 访问密钥 ID
    secret-key: 私有密钥
    service-endpoint: S3 服务接入域名 / 内网地址
    bucket-name: 存储桶名称

4. S3 连接配置类

@Configuration
@Slf4j
public class AmazonS3Configuration {

    @Resource
    private AmazonS3Properties amazonS3Properties;

    @Bean
    public AmazonS3 amazonS3() {
        try {
            AWSCredentials credentials = new BasicAWSCredentials(amazonS3Properties.getAccessKey(), amazonS3Properties.getSecretKey());
            AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3ClientBuilder.standard();
            amazonS3ClientBuilder.setPathStyleAccessEnabled(true);
            return amazonS3ClientBuilder
                    .withCredentials(new AWSStaticCredentialsProvider(credentials))
                    .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(amazonS3Properties.getServiceEndpoint(), "")).build();
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException("Amazons3 build failed");
        }
    }
 
//    若加这个bean,选择第二个AmazonService
//    @Bean
//    public AmazonService amazonService(AmazonS3 amazonS3) {
//        return new AmazonService(amazonS3, amazonS3Properties.getBucketName());
//    }
}

5. S3 对象存储操作类

5.1 写法一

/**
 * Amazon S3 对象存储操作类
 */
@Service
public class AmazonService {

    @Resource
    private AmazonS3 amazonS3;

    /**
     * 默认bucket名称
     */
    @Value("${amazon.s3.bucket-name:}")
    private String bucketName;

    /**
     * 初始化生成bucket
     * @param bucketName
     */
    @PostConstruct
    public void init(String bucketName) {
        if (!amazonS3.doesBucketExistV2(bucketName)) {
            amazonS3.createBucket(bucketName);
        }
    }

    /**
     * 上传文件
     * @param key  目录+文件形式 如:root/user/1.txt,同key会覆盖文件
     */
    public void upload(String key, File file) {
        amazonS3.putObject(bucketName, key, file);
    }

    /**
     * 上传文件
     * @param key  目录+文件形式 如:root/user/1.txt,同key会覆盖文件
     */
    public void upload(String key, InputStream inputStream) {
        upload(key, inputStream, null);
    }

    /**
     * 上传文件
     * @param key            目录+文件形式 如:root/user/1.txt,同key会覆盖文件
     * @param inputStream    文件输入流, 使用完成后建议外部关闭流,避免资源泄漏
     * @param objectMetadata 文件元数据(可设置文件类型、长度、缓存策略、自定义属性等)
     *                       例如:Content-Type、Content-Length、Content-Disposition
     */
    public void upload(String key, InputStream inputStream, ObjectMetadata objectMetadata) {
        amazonS3.putObject(bucketName, key, inputStream, objectMetadata);
    }


    /**
     * 通过key获取url
     */
    public String getUrl(String key) {
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, key);
//        request.setExpiration(new Date(System.currentTimeMillis() + 60L * 60L * 24L * 365L * 99L * 1000L));
        String originUrl = amazonS3.generatePresignedUrl(request).toString();
        return originUrl.substring(0, originUrl.indexOf("?"));
    }

    /**
     * 获取带有认证参数的url
     */
    public String getUrlWithAuthParam(String key) {
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, key);
        return amazonS3.generatePresignedUrl(request).toString();
    }

    /**
     * 获取文件输入流
     */
    public InputStream getObject(String objectKey) {
        // 从 S3 获取文件
        S3Object s3Object = amazonS3.getObject(bucketName, objectKey);
        return s3Object.getObjectContent();
    }

    /**
     * 删除文件
     */
    public void deleteObject(String key) {
        amazonS3.deleteObject(bucketName, key);
    }

    /**
     * 拷贝文件
     * @param key            原key
     * @param destinationKey 目标key
     */
    public void copy(String key, String destinationKey) {
        amazonS3.copyObject(bucketName, key, bucketName, destinationKey);
    }


    /**
     * 获取对象元数据
     * @param objectKey 对象key
     * @return 对象元数据
     */
    public ObjectMetadata getObjectMetadata(String objectKey) {
        return amazonS3.getObjectMetadata(bucketName, objectKey);
    }

    /**
     * 根据URL或objectKey获取S3文件的大小(字节数)
     * @param identifier 可以是S3文件的URL(格式:https://xxx.com/桶名/objectKey),也可以是objectKey
     * @return 文件大小(字节数)
     * @throws Exception 解析URL失败或获取S3元数据失败时抛出
     */
    public long getFileSize(String identifier) throws Exception {
        // 1. 判断输入是URL还是objectKey,并解析出正确的objectKey
        String objectKey;
        if (isUrl(identifier)) {
            objectKey = parseObjectKeyFromUrl(identifier);
        } else {
            objectKey = identifier;
        }
        if (objectKey == null || objectKey.isEmpty()) {
            throw new Exception("无法从给定的URL中解析出有效的S3对象键(Object Key)" + identifier);
        }

        // 2. 获取S3对象的元数据
        ObjectMetadata metadata = amazonS3.getObjectMetadata(bucketName, objectKey);

        // 3. 返回文件大小(字节数)
        return metadata.getContentLength();
    }

    /**
     * 判断字符串是否为URL
     * @param str 待判断的字符串
     * @return true=是URL,false=不是URL
     */
    private boolean isUrl(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        return str.startsWith("http://") || str.startsWith("https://");
    }

    /**
     * 从自定义S3 URL中直接移除头部桶名,提取objectKey
     * 示例URL: https://xxx.com/任意桶名/folder1/file.txt → objectKey = folder1/file.txt
     */
    public String parseObjectKeyFromUrl(String urlStr) {
        // 空值校验,避免空指针
        if (urlStr == null || urlStr.trim().isEmpty()) {
            return "";
        }
        urlStr = urlStr.trim();

        try {
            urlStr = URLDecoder.decode(urlStr, StandardCharsets.UTF_8.name());
        } catch (Exception e) {
            e.printStackTrace();
        }

        int queryIndex = urlStr.lastIndexOf("?");
        if (queryIndex != -1) {
            urlStr = urlStr.substring(0, queryIndex);
        }

        // 第一步:移除HTTP/HTTPS协议头
        if (urlStr.startsWith("https://")) {
            urlStr = urlStr.substring("https://".length());
        } else if (urlStr.startsWith("http://")) {
            urlStr = urlStr.substring("http://".length());
        }

        // 第二步:按/分割,同时处理连续/的情况(split("/+") 匹配一个或多个/)
        String[] parts = urlStr.split("/+");

        // 第三步:如果分割后长度<=1(只有域名,无后续路径),返回空字符串
        if (parts.length <= 1) {
            return "";
        }

        // 第四步:截取从索引1开始的部分(跳过域名),拼接为对象键
        String[] objectKeyParts = Arrays.copyOfRange(parts, 1, parts.length);
        String objectKey = String.join("/", objectKeyParts);

        // 第五步:移除末尾的/(如果有)
        if (objectKey.endsWith("/")) {
            objectKey = objectKey.substring(0, objectKey.length() - 1);
        }

        // 第六步:移除桶名前缀(如果有)
        if (objectKey.startsWith(bucketName + "/")) {
            objectKey = objectKey.substring((bucketName + "/").length());
        }

        return objectKey;
    }
}

5.2 写法二

/**
 * Amazon S3 对象存储操作类
 */
public class AmazonService {

    @Resource
    private AmazonS3 amazonS3;

    /**
     * 默认bucket名称
     */
    private String bucketName;

    public AmazonService(AmazonS3 amazonS3, String bucketName) {
        this.amazonS3 = amazonS3;
        this.bucketName = bucketName;
    }

    //... 后面的代码和第一个一致
}

6. 使用示例

@RestController
public class UploadFileController {

    @Resource
    private AmazonService amazonService;

    @PostMapping("/uploadFile")
    public String uploadFile(@RequestParam("file") MultipartFile multipartFile) {

        String url = null;
        try {
            String fileName = multipartFile.getOriginalFilename();
            String suffix = this.getFileSuffix(fileName);
            String key = this.getOSSObjectKey(suffix);
            this.amazonService.upload(key, new ByteArrayInputStream(multipartFile.getBytes()));
            url = this.amazonService.getUrl(key);
        } catch (IOException e) {
            throw new RuntimeException("PDF转换失败", e);
        }

        return url;
    }

    /**
     * 获取文件后缀名
     */
    public String getFileSuffix(String fileName) {
        if ((fileName != null) && (!fileName.isEmpty())) {
            int dot = fileName.lastIndexOf('.');
            if ((dot > -1) && (dot < (fileName.length() - 1))) {
                return fileName.substring(dot + 1);
            }
        }
        return fileName;
    }

    /**
     * 获取文件Key,如:2020/7/17/dd48176a-a512-4377-bd80-c8e5d2910be3.jpg
     */
    public String getOSSObjectKey(String suffix) {
        Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH) + 1;
        int day = c.get(Calendar.DAY_OF_MONTH);
        StringBuilder obj = new StringBuilder();
        obj.append("/").append(year).append("/").append(month).append("/").append(day).
                append("/").append(UUID.randomUUID().toString());
        if (suffix != null) {
            obj.append(".").append(suffix);
        }
        return obj.toString();
    }
}

更多推荐