├── src ├── test │ ├── resources │ │ ├── test.txt │ │ └── application-minio.yml │ └── java │ │ └── com │ │ └── pig4cloud │ │ └── plugin │ │ └── oss │ │ └── service │ │ └── MinioOssTemplateTest.java └── main │ ├── resources │ └── META-INF │ │ └── additional-spring-configuration-metadata.json │ └── java │ └── com │ └── pig4cloud │ └── plugin │ └── oss │ ├── OssAutoConfiguration.java │ ├── OssProperties.java │ ├── http │ └── OssEndpoint.java │ └── service │ └── OssTemplate.java ├── .github ├── renovate.json └── workflows │ ├── maven.yml │ └── oss-release-deploy.yml ├── .editorconfig ├── .gitignore ├── README.md ├── pom.xml └── LICENSE /src/test/resources/test.txt: -------------------------------------------------------------------------------- 1 | Hello,World! 2 | -------------------------------------------------------------------------------- /src/test/resources/application-minio.yml: -------------------------------------------------------------------------------- 1 | oss: 2 | endpoint: https://play.min.io:9000 3 | access-key: Q3AM3UQ867SPQQA43P2F 4 | secret-key: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG 5 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "baseBranches": [ 4 | "master" 5 | ], 6 | "extends": [ 7 | "config:recommended" 8 | ], 9 | "packageRules": [ 10 | { 11 | "matchPackagePatterns": [ 12 | "^org.anyline:anyline.*$" 13 | ], 14 | "enabled": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/additional-spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": [ 3 | { 4 | "name": "oss.http.enable", 5 | "type": "java.lang.Boolean", 6 | "description": "开启 oss http 端点.", 7 | "defaultValue": "false" 8 | }, 9 | { 10 | "name": "oss.http.prefix", 11 | "type": "java.lang.String", 12 | "description": "oss http 路由端点前缀.", 13 | "defaultValue": "" 14 | } 15 | ] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # 对所有文件生效 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | # 对java, xml文件生效 13 | [*.{java, xml}] 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | # 对yml, json文件生效 18 | [*.{yml, json}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | # 对后缀名为 .md 的文件生效 23 | [*.md] 24 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *# 2 | *.iml 3 | *.ipr 4 | *.iws 5 | *.jar 6 | *.sw? 7 | *~ 8 | .#* 9 | .*.md.html 10 | .DS_Store 11 | .classpath 12 | .factorypath 13 | .gradle 14 | .idea 15 | .metadata 16 | .project 17 | .recommenders 18 | .settings 19 | .springBeans 20 | /code 21 | MANIFEST.MF 22 | _site/ 23 | activemq-data 24 | bin 25 | build 26 | !/**/src/**/bin 27 | !/**/src/**/build 28 | build.log 29 | dependency-reduced-pom.xml 30 | dump.rdb 31 | interpolated*.xml 32 | lib/ 33 | manifest.yml 34 | out 35 | overridedb.* 36 | target 37 | transaction-logs 38 | .flattened-pom.xml 39 | secrets.yml 40 | .gradletasknamecache 41 | .sts4-cache -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: oss-spring-boot-starter 5 | 6 | on: 7 | push: 8 | branches: [ master,dev ] 9 | pull_request: 10 | branches: [ master,dev ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '17' 21 | distribution: 'adopt' 22 | 23 | - name: mvn clean install 24 | run: mvn clean install 25 | 26 | - name: mvn spring-javaformat:validate 27 | run: mvn spring-javaformat:validate 28 | -------------------------------------------------------------------------------- /.github/workflows/oss-release-deploy.yml: -------------------------------------------------------------------------------- 1 | name: publish maven package 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | oss-release-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Set up JDK 17 15 | uses: actions/setup-java@v4 16 | with: 17 | java-version: '17' 18 | distribution: 'adopt' 19 | cache: maven 20 | 21 | - name: Setup Maven Central 22 | uses: actions/setup-java@v4 23 | with: # overwrite settings.xml 24 | java-version: '17' 25 | distribution: 'adopt' 26 | server-id: center 27 | server-username: OSSRH_USERNAME 28 | server-password: OSSRH_PASSWORD 29 | gpg-private-key: ${{ secrets.MAVEN_GPG_KEY }} 30 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 31 | 32 | - name: Publish to Maven Central 33 | run: mvn clean deploy -P release -Dmaven.test.skip=true 34 | env: 35 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 36 | OSSRH_USERNAME: ${{ secrets.CENTER_USERNAME }} 37 | OSSRH_PASSWORD: ${{ secrets.CENTER_PASSWORD }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## oss-spring-boot-starter 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/com.pig4cloud.plugin/oss-spring-boot-starter.svg)](https://search.maven.org/artifact/com.pig4cloud.plugin/oss-spring-boot-starter) 4 | 5 | 兼容S3 协议的通用文件存储工具类 ,支持 兼容S3 协议的云存储 6 | 7 | - MINIO 8 | - 阿里云 9 | - 华为云 10 | - 腾讯云 11 | - 京东云 12 | 13 | ... 14 | 15 | ## spring boot starter依赖 16 | 17 | 18 | | 版本 | 支持 | 19 | |-------|--| 20 | | 3.1.1 | 适配 SpringBoot3.x | 21 | | 1.0.5 | 适配 SpringBoot2.x | 22 | 23 | - 方便在 web 环境下使用 `oss` ,已上传至 maven 仓库 24 | ```xml 25 | 26 | com.pig4cloud.plugin 27 | oss-spring-boot-starter 28 | ${lastVersion} 29 | 30 | ``` 31 | 32 | ## 使用方法 33 | 34 | ### 配置文件 35 | 36 | - MINIO 示例 37 | 38 | ```yaml 39 | oss: 40 | endpoint: http://minio.pig4cloud.com 41 | access-key: lengleng 42 | secret-key: lengleng 43 | ``` 44 | 45 | - 阿里云 示例 46 | 47 | ```yaml 48 | oss: 49 | endpoint: https://oss-cn-beijing.aliyuncs.com 50 | access-key: xxx 51 | secret-key: xxx 52 | path-style-access: false # 阿里云需要关闭 Please use virtual hosted style to access 53 | ``` 54 | 55 | ### 代码使用 56 | 57 | ```java 58 | @Autowired 59 | private OssTemplate template; 60 | /** 61 | * 上传文件 62 | * 文件名采用uuid,避免原始文件名中带"-"符号导致下载的时候解析出现异常 63 | * 64 | * @param file 资源 65 | * @return R(bucketName, filename) 66 | */ 67 | @PostMapping("/upload") 68 | public R upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) { 69 | template.putObject(CommonConstants.BUCKET_NAME, fileName, file.getInputStream()); 70 | return R.ok(resultMap); 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/oss/OssAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2025, lengleng All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * Neither the name of the pig4cloud.com developer nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * Author: lengleng (wangiegie@gmail.com) 16 | */ 17 | 18 | package com.pig4cloud.plugin.oss; 19 | 20 | import com.pig4cloud.plugin.oss.http.OssEndpoint; 21 | import com.pig4cloud.plugin.oss.service.OssTemplate; 22 | import org.springframework.boot.autoconfigure.AutoConfiguration; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.Configuration; 29 | 30 | /** 31 | * oss 自动配置类 32 | * 33 | * @author lengleng 34 | * @author 858695266 35 | * @author L.cm 36 | */ 37 | @AutoConfiguration 38 | @Configuration(proxyBeanMethods = false) 39 | @EnableConfigurationProperties({ OssProperties.class }) 40 | public class OssAutoConfiguration { 41 | 42 | /** 43 | * OSS操作模板 44 | * @return OSS操作模板 45 | */ 46 | @Bean 47 | @ConditionalOnMissingBean(OssTemplate.class) 48 | @ConditionalOnProperty(prefix = OssProperties.PREFIX, name = "enable", havingValue = "true", matchIfMissing = true) 49 | public OssTemplate ossTemplate(OssProperties properties) { 50 | return new OssTemplate(properties); 51 | } 52 | 53 | /** 54 | * OSS端点信息 55 | * @param template oss操作模版 56 | * @return oss远程服务端点 57 | */ 58 | @Bean 59 | @ConditionalOnWebApplication 60 | @ConditionalOnProperty(prefix = OssProperties.PREFIX, name = "http.enable", havingValue = "true") 61 | public OssEndpoint ossEndpoint(OssTemplate template) { 62 | return new OssEndpoint(template); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/oss/OssProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2025, lengleng All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * Neither the name of the pig4cloud.com developer nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * Author: lengleng (wangiegie@gmail.com) 16 | */ 17 | 18 | package com.pig4cloud.plugin.oss; 19 | 20 | import lombok.Data; 21 | import org.springframework.boot.context.properties.ConfigurationProperties; 22 | 23 | /** 24 | * oss 配置信息 25 | * 26 | * @author lengleng 27 | * @author 858695266 配置文件添加: oss: enable: true endpoint: http://127.0.0.1:9000 # 28 | * pathStyleAccess 采用nginx反向代理或者AWS S3 配置成true,支持第三方云存储配置成false pathStyleAccess: false 29 | * access-key: lengleng secret-key: lengleng bucket-name: lengleng region: custom-domain: 30 | * https://oss.xxx.com/lengleng 31 | *

32 | * bucket 设置公共读权限 33 | */ 34 | @Data 35 | @ConfigurationProperties(prefix = OssProperties.PREFIX) 36 | public class OssProperties { 37 | 38 | /** 39 | * 配置前缀 40 | */ 41 | public static final String PREFIX = "oss"; 42 | 43 | /** 44 | * 是否启用 oss,默认为:true 45 | */ 46 | private boolean enable = true; 47 | 48 | /** 49 | * 对象存储服务的URL 50 | */ 51 | private String endpoint; 52 | 53 | /** 54 | * 自定义域名 55 | */ 56 | private String customDomain; 57 | 58 | /** 59 | * true path-style nginx 反向代理和S3默认支持 pathStyle {http://endpoint/bucketname} false 60 | * supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style 61 | * 模式{http://bucketname.endpoint} 62 | */ 63 | private Boolean pathStyleAccess = true; 64 | 65 | /** 66 | * Option to enable using chunked encoding when signing the request payload aliyun 67 | * 需要关闭 68 | */ 69 | private Boolean chunkedEncodingEnabled = false; 70 | 71 | /** 72 | * 区域 73 | */ 74 | private String region; 75 | 76 | /** 77 | * Access key就像用户ID,可以唯一标识你的账户 78 | */ 79 | private String accessKey; 80 | 81 | /** 82 | * Secret key是你账户的密码 83 | */ 84 | private String secretKey; 85 | 86 | /** 87 | * 默认的存储桶名称 88 | */ 89 | private String bucketName; 90 | 91 | } 92 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 3.5.8 10 | 11 | 12 | com.pig4cloud.plugin 13 | oss-spring-boot-starter 14 | 3.1.2 15 | oss-spring-boot-starter 16 | 兼容S3 协议的通用文件存储工具类 17 | 18 | 19 | 17 20 | 2.39.6 21 | 3.1.3 22 | 1.5.22 23 | 2.1.10 24 | 0.0.29 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | provided 32 | 33 | 34 | 36 | 37 | 38 | software.amazon.awssdk 39 | s3 40 | ${aws.version} 41 | compile 42 | 43 | 44 | 45 | 46 | jakarta.validation 47 | jakarta.validation-api 48 | provided 49 | 50 | 51 | 52 | 53 | io.swagger 54 | swagger-annotations 55 | ${swagger.version} 56 | provided 57 | 58 | 59 | io.swagger.core.v3 60 | swagger-annotations 61 | ${swagger.v3.version} 62 | provided 63 | 64 | 65 | 67 | 68 | org.projectlombok 69 | lombok 70 | provided 71 | 72 | 73 | 74 | 75 | net.dreamlu 76 | mica-auto 77 | ${mica.auto.version} 78 | provided 79 | 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-starter-test 85 | test 86 | 87 | 88 | org.junit.vintage 89 | junit-vintage-engine 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | io.spring.javaformat 101 | spring-javaformat-maven-plugin 102 | ${spring.checkstyle.plugin} 103 | 104 | 105 | 106 | 107 | 108 | 109 | release 110 | 111 | 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-source-plugin 116 | 2.2.1 117 | 118 | 119 | package 120 | 121 | jar-no-fork 122 | 123 | 124 | 125 | 126 | 127 | 128 | org.apache.maven.plugins 129 | maven-javadoc-plugin 130 | 3.3.2 131 | 132 | private 133 | true 134 | UTF-8 135 | UTF-8 136 | UTF-8 137 | none 138 | false 139 | 140 | 141 | 142 | package 143 | 144 | jar 145 | 146 | 147 | 148 | 149 | 150 | 151 | org.apache.maven.plugins 152 | maven-gpg-plugin 153 | 3.0.1 154 | 155 | 156 | sign-artifacts 157 | verify 158 | 159 | sign 160 | 161 | 162 | 163 | 164 | 165 | --pinentry-mode 166 | loopback 167 | 168 | 169 | 170 | 171 | org.sonatype.central 172 | central-publishing-maven-plugin 173 | 0.5.0 174 | true 175 | 176 | center 177 | true 178 | published 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/test/java/com/pig4cloud/plugin/oss/service/MinioOssTemplateTest.java: -------------------------------------------------------------------------------- 1 | package com.pig4cloud.plugin.oss.service; 2 | 3 | import lombok.SneakyThrows; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.util.ResourceUtils; 13 | import software.amazon.awssdk.services.s3.model.Bucket; 14 | import software.amazon.awssdk.services.s3.model.HeadObjectResponse; 15 | 16 | import java.io.BufferedReader; 17 | import java.io.FileInputStream; 18 | import java.io.InputStream; 19 | import java.io.InputStreamReader; 20 | import java.io.OutputStreamWriter; 21 | import java.net.HttpURLConnection; 22 | import java.net.URL; 23 | import java.util.Optional; 24 | 25 | /** 26 | * minio模版测试 27 | * 28 | * @author lishangbu 29 | * @date 2021/1/25 30 | */ 31 | @SpringBootApplication(scanBasePackages = "com.pig4cloud.plugin.oss") 32 | @SpringBootTest 33 | @ActiveProfiles("minio") 34 | public class MinioOssTemplateTest { 35 | 36 | /** 37 | * 测试用OSS名字 38 | */ 39 | private static final String TEST_BUCKET_NAME = "test-oss"; 40 | 41 | /** 42 | * 测试用文件名,该文件在测试资源文件夹下 43 | */ 44 | private static final String TEST_OBJECT_NAME = "test.txt"; 45 | 46 | /** 47 | * 测试上传用文件名,该文件在测试资源文件夹下 48 | */ 49 | private static final String TEST_UPLOAD_OBJECT_NAME = "testUpload.txt"; 50 | 51 | @Autowired 52 | private OssTemplate ossTemplate; 53 | 54 | @BeforeEach 55 | @SneakyThrows 56 | public void init() { 57 | ossTemplate.createBucket(TEST_BUCKET_NAME); 58 | ossTemplate.putObject(TEST_BUCKET_NAME, TEST_OBJECT_NAME, 59 | new FileInputStream(ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + TEST_OBJECT_NAME))); 60 | 61 | } 62 | 63 | /** 64 | * 测试获取存储桶 65 | */ 66 | @Test 67 | public void getBucket() { 68 | Optional bucket = ossTemplate.getBucket(TEST_BUCKET_NAME); 69 | Assertions.assertEquals(TEST_BUCKET_NAME, bucket.get().name()); 70 | } 71 | 72 | /** 73 | * 获取对象 74 | */ 75 | @Test 76 | @SneakyThrows 77 | public void getObject() { 78 | InputStream inputStream = ossTemplate.getObject(TEST_BUCKET_NAME, TEST_OBJECT_NAME); 79 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 80 | StringBuilder content = new StringBuilder(); 81 | String line; 82 | while ((line = reader.readLine()) != null) { 83 | content.append(line); 84 | } 85 | reader.close(); 86 | inputStream.close(); 87 | 88 | // 断言返回的文本包含文件的内容 89 | Assertions.assertTrue(content.toString().contains("Hello,World!")); 90 | } 91 | 92 | /** 93 | * 获取对象URL 94 | */ 95 | @Test 96 | public void getObjectUrl() { 97 | String url = ossTemplate.getObjectURL(TEST_BUCKET_NAME, TEST_OBJECT_NAME, 3); 98 | System.out.println("URL: " + url); 99 | // 断言生成的链接必定包含过期时间字段 100 | Assertions.assertTrue(url.contains("X-Amz-Expires")); 101 | } 102 | 103 | @AfterEach 104 | @SneakyThrows 105 | public void destroy() { 106 | ossTemplate.removeObject(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME); 107 | ossTemplate.removeObject(TEST_BUCKET_NAME, TEST_OBJECT_NAME); 108 | ossTemplate.removeBucket(TEST_BUCKET_NAME); 109 | Optional afterDeleteBucket = ossTemplate.getBucket(TEST_BUCKET_NAME); 110 | Assertions.assertEquals(Optional.empty(), afterDeleteBucket); 111 | } 112 | 113 | @Test 114 | @SneakyThrows 115 | public void getObjectUpload() { 116 | String testObjectContent = "it is a png"; 117 | String url = ossTemplate.getObjectURL(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME, 1, "PUT"); 118 | // 断言生成的链接必定包含过期时间字段 119 | Assertions.assertTrue(url.contains("X-Amz-Expires")); 120 | System.out.println("URL: " + url); 121 | 122 | Assertions.assertThrows(Exception.class, () -> { 123 | try (InputStream stream = ossTemplate.getObject(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME)) { 124 | // 文件不存在应该抛出异常 125 | } 126 | }); 127 | 128 | Assertions.assertEquals(200, upload(url, testObjectContent)); 129 | 130 | try (InputStream inputStream = ossTemplate.getObject(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME)) { 131 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 132 | StringBuilder content = new StringBuilder(); 133 | String line; 134 | while ((line = reader.readLine()) != null) { 135 | content.append(line); 136 | } 137 | reader.close(); 138 | 139 | // 断言返回的文本包含文件的内容 140 | Assertions.assertTrue(content.toString().contains(testObjectContent)); 141 | } 142 | } 143 | 144 | @Test 145 | @SneakyThrows 146 | public void getObjectUploadExpired() { 147 | String testObjectContent = "it is another png"; 148 | String url = ossTemplate.getObjectURL(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME, 1, "PUT"); 149 | // 断言生成的链接必定包含过期时间字段 150 | Assertions.assertTrue(url.contains("X-Amz-Expires")); 151 | System.out.println("URL: " + url); 152 | 153 | Assertions.assertThrows(Exception.class, () -> { 154 | try (InputStream stream = ossTemplate.getObject(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME)) { 155 | // 文件不存在应该抛出异常 156 | } 157 | }); 158 | 159 | Thread.sleep(1100 * 60); 160 | Assertions.assertEquals(403, upload(url, testObjectContent)); 161 | 162 | Assertions.assertThrows(Exception.class, () -> { 163 | try (InputStream stream = ossTemplate.getObject(TEST_BUCKET_NAME, TEST_UPLOAD_OBJECT_NAME)) { 164 | // 文件不存在应该抛出异常 165 | } 166 | }); 167 | } 168 | 169 | @SneakyThrows 170 | private int upload(String url, String content) { 171 | // Create the connection and use it to upload the new object using the pre-signed 172 | // URL. 173 | URL opurl = new URL(url); 174 | HttpURLConnection connection = (HttpURLConnection) opurl.openConnection(); 175 | connection.setDoOutput(true); 176 | connection.setRequestMethod("PUT"); 177 | OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream()); 178 | out.write(content); 179 | out.close(); 180 | 181 | // Check the HTTP response code. To complete the upload and make the object 182 | // available, 183 | // you must interact with the connection object in some way. 184 | System.out.println("HTTP response code: " + connection.getResponseCode()); 185 | return connection.getResponseCode(); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/oss/http/OssEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2025, lengleng All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * Neither the name of the pig4cloud.com developer nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * Author: lengleng (wangiegie@gmail.com) 16 | */ 17 | 18 | package com.pig4cloud.plugin.oss.http; 19 | 20 | import com.pig4cloud.plugin.oss.service.OssTemplate; 21 | import io.swagger.annotations.Api; 22 | import io.swagger.v3.oas.annotations.tags.Tag; 23 | import jakarta.validation.constraints.NotBlank; 24 | import jakarta.validation.constraints.NotNull; 25 | import lombok.Cleanup; 26 | import lombok.RequiredArgsConstructor; 27 | import lombok.SneakyThrows; 28 | import net.dreamlu.mica.auto.annotation.AutoIgnore; 29 | import org.springframework.http.HttpStatus; 30 | import org.springframework.validation.annotation.Validated; 31 | import org.springframework.web.bind.annotation.*; 32 | import org.springframework.web.multipart.MultipartFile; 33 | import software.amazon.awssdk.services.s3.model.Bucket; 34 | import software.amazon.awssdk.services.s3.model.HeadObjectResponse; 35 | import software.amazon.awssdk.services.s3.model.S3Object; 36 | 37 | import java.io.InputStream; 38 | import java.time.Instant; 39 | import java.util.HashMap; 40 | import java.util.List; 41 | import java.util.Map; 42 | 43 | /** 44 | * oss 对外提供服务端点 45 | * 46 | * @author lengleng 47 | * @author 858695266 48 | *

49 | * oss.info 50 | */ 51 | @Validated 52 | @AutoIgnore 53 | @RestController 54 | @RequiredArgsConstructor 55 | @RequestMapping("${oss.http.prefix:}/oss") 56 | @Api(tags = "oss:http接口") 57 | @Tag(name = "OssEndpoint", description = "oss:http接口") 58 | public class OssEndpoint { 59 | 60 | /** 61 | * OSS操作模板 62 | */ 63 | private final OssTemplate ossTemplate; 64 | 65 | /** 66 | * Bucket Endpoints 67 | */ 68 | @PostMapping("/bucket/{bucketName}") 69 | public Bucket createBucket(@PathVariable @NotBlank String bucketName) { 70 | ossTemplate.createBucket(bucketName); 71 | return ossTemplate.getBucket(bucketName).get(); 72 | } 73 | 74 | @GetMapping("/bucket") 75 | public List getBuckets() { 76 | return ossTemplate.getAllBuckets(); 77 | } 78 | 79 | @GetMapping("/bucket/{bucketName}") 80 | public Bucket getBucket(@PathVariable @NotBlank String bucketName) { 81 | return ossTemplate.getBucket(bucketName) 82 | .orElseThrow(() -> new IllegalArgumentException("Bucket Name not found!")); 83 | } 84 | 85 | @DeleteMapping("/bucket/{bucketName}") 86 | @ResponseStatus(HttpStatus.ACCEPTED) 87 | public void deleteBucket(@PathVariable @NotBlank String bucketName) { 88 | ossTemplate.removeBucket(bucketName); 89 | } 90 | 91 | /** 92 | * Object Endpoints 93 | */ 94 | @SneakyThrows 95 | @PostMapping("/object/{bucketName}") 96 | public Map createObject(@RequestBody @NotNull MultipartFile object, 97 | @PathVariable @NotBlank String bucketName) { 98 | @Cleanup 99 | InputStream inputStream = object.getInputStream(); 100 | String name = object.getOriginalFilename(); 101 | 102 | ossTemplate.putObject(bucketName, name, inputStream, object.getSize(), object.getContentType()); 103 | HeadObjectResponse objectInfo = ossTemplate.getObjectInfo(bucketName, name); 104 | 105 | Map result = new HashMap<>(); 106 | result.put("key", name); 107 | result.put("bucketName", bucketName); 108 | result.put("eTag", objectInfo.eTag()); 109 | result.put("lastModified", objectInfo.lastModified()); 110 | result.put("size", objectInfo.contentLength()); 111 | return result; 112 | } 113 | 114 | /** 115 | * Object Endpoints 116 | */ 117 | @SneakyThrows 118 | @PostMapping("/object/{bucketName}/{objectName}") 119 | public Map createObject(@RequestBody @NotNull MultipartFile object, 120 | @PathVariable @NotBlank String bucketName, @PathVariable @NotBlank String objectName) { 121 | @Cleanup 122 | InputStream inputStream = object.getInputStream(); 123 | ossTemplate.putObject(bucketName, objectName, inputStream, object.getSize(), object.getContentType()); 124 | HeadObjectResponse objectInfo = ossTemplate.getObjectInfo(bucketName, objectName); 125 | 126 | Map result = new HashMap<>(); 127 | result.put("key", objectName); 128 | result.put("bucketName", bucketName); 129 | result.put("eTag", objectInfo.eTag()); 130 | result.put("lastModified", objectInfo.lastModified()); 131 | result.put("size", objectInfo.contentLength()); 132 | return result; 133 | } 134 | 135 | @GetMapping("/object/{bucketName}/{objectName}") 136 | public List filterObject(@PathVariable @NotBlank String bucketName, 137 | @PathVariable @NotBlank String objectName) { 138 | return ossTemplate.getAllObjectsByPrefix(bucketName, objectName); 139 | } 140 | 141 | @GetMapping("/object/{bucketName}/{objectName}/{expires}") 142 | public Map getObjectUrl(@PathVariable @NotBlank String bucketName, 143 | @PathVariable @NotBlank String objectName, @PathVariable @NotNull Integer expires) { 144 | Map responseBody = new HashMap<>(8); 145 | // Put Object info 146 | responseBody.put("bucket", bucketName); 147 | responseBody.put("object", objectName); 148 | responseBody.put("url", ossTemplate.getObjectURL(bucketName, objectName, expires)); 149 | responseBody.put("expires", expires); 150 | return responseBody; 151 | } 152 | 153 | @GetMapping("/object/put/{bucketName}/{objectName}/{expires}") 154 | public Map getPutObjectUrl(@PathVariable @NotBlank String bucketName, 155 | @PathVariable @NotBlank String objectName, @PathVariable @NotNull Integer expires) { 156 | Map responseBody = new HashMap<>(8); 157 | // Put Object info 158 | responseBody.put("bucket", bucketName); 159 | responseBody.put("object", objectName); 160 | responseBody.put("url", ossTemplate.getPutObjectURL(bucketName, objectName, expires)); 161 | responseBody.put("expires", expires); 162 | return responseBody; 163 | } 164 | 165 | @ResponseStatus(HttpStatus.ACCEPTED) 166 | @DeleteMapping("/object/{bucketName}/{objectName}/") 167 | public void deleteObject(@PathVariable @NotBlank String bucketName, @PathVariable @NotBlank String objectName) { 168 | ossTemplate.removeObject(bucketName, objectName); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | n 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/com/pig4cloud/plugin/oss/service/OssTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2025, lengleng All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * Neither the name of the pig4cloud.com developer nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * Author: lengleng (wangiegie@gmail.com) 16 | */ 17 | 18 | package com.pig4cloud.plugin.oss.service; 19 | 20 | import com.pig4cloud.plugin.oss.OssProperties; 21 | import lombok.RequiredArgsConstructor; 22 | import org.springframework.beans.factory.InitializingBean; 23 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; 24 | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; 25 | import software.amazon.awssdk.core.sync.RequestBody; 26 | import software.amazon.awssdk.core.sync.ResponseTransformer; 27 | import software.amazon.awssdk.regions.Region; 28 | import software.amazon.awssdk.services.s3.S3Client; 29 | import software.amazon.awssdk.services.s3.S3Configuration; 30 | import software.amazon.awssdk.services.s3.model.*; 31 | import software.amazon.awssdk.services.s3.presigner.S3Presigner; 32 | import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; 33 | import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; 34 | import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; 35 | import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; 36 | 37 | import java.io.IOException; 38 | import java.io.InputStream; 39 | import java.net.URI; 40 | import java.time.Duration; 41 | import java.util.List; 42 | import java.util.Optional; 43 | import java.util.stream.Collectors; 44 | 45 | /** 46 | * aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS,腾讯云COS,七牛云,京东云,minio 等} 47 | * 48 | * @author lengleng 49 | * @author 858695266 50 | * @author L.cm 51 | * @date 2020/5/23 6:36 上午 52 | * @since 1.0 53 | */ 54 | @RequiredArgsConstructor 55 | public class OssTemplate implements InitializingBean { 56 | 57 | private final OssProperties ossProperties; 58 | 59 | private S3Client s3Client; 60 | 61 | private S3Presigner s3Presigner; 62 | 63 | /** 64 | * 创建bucket 65 | * @param bucketName bucket名称 66 | */ 67 | public void createBucket(String bucketName) { 68 | if (!headBucket(bucketName)) { 69 | CreateBucketRequest createBucketRequest = CreateBucketRequest.builder().bucket(bucketName).build(); 70 | s3Client.createBucket(createBucketRequest); 71 | } 72 | } 73 | 74 | /** 75 | * 判断bucket是否存在 76 | * @param bucketName bucket名称 77 | * @return 是否存在 78 | */ 79 | public boolean headBucket(String bucketName) { 80 | try { 81 | HeadBucketRequest headBucketRequest = HeadBucketRequest.builder().bucket(bucketName).build(); 82 | s3Client.headBucket(headBucketRequest); 83 | return true; 84 | } 85 | catch (NoSuchBucketException e) { 86 | return false; 87 | } 88 | } 89 | 90 | /** 91 | * 获取全部bucket 92 | *

93 | * 94 | * @see AWS API 96 | * Documentation 97 | */ 98 | public List getAllBuckets() { 99 | ListBucketsResponse listBucketsResponse = s3Client.listBuckets(); 100 | return listBucketsResponse.buckets(); 101 | } 102 | 103 | /** 104 | * @param bucketName bucket名称 105 | * @see AWS API 107 | * Documentation 108 | */ 109 | public Optional getBucket(String bucketName) { 110 | return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst(); 111 | } 112 | 113 | /** 114 | * @param bucketName bucket名称 115 | * @see AWS API 117 | * Documentation 118 | */ 119 | public void removeBucket(String bucketName) { 120 | DeleteBucketRequest deleteBucketRequest = DeleteBucketRequest.builder().bucket(bucketName).build(); 121 | s3Client.deleteBucket(deleteBucketRequest); 122 | } 123 | 124 | /** 125 | * 根据文件前置查询文件 126 | * @param bucketName bucket名称 127 | * @param prefix 前缀 128 | * @see AWS API 130 | * Documentation 131 | */ 132 | public List getAllObjectsByPrefix(String bucketName, String prefix) { 133 | ListObjectsV2Request listObjectsRequest = ListObjectsV2Request.builder().bucket(bucketName).prefix(prefix) 134 | .build(); 135 | 136 | ListObjectsV2Response listObjectsResponse = s3Client.listObjectsV2(listObjectsRequest); 137 | return listObjectsResponse.contents(); 138 | } 139 | 140 | /** 141 | * 获取文件外链,只用于下载 142 | * @param bucketName bucket名称 143 | * @param objectName 文件名称 144 | * @param minutes 过期时间,单位分钟,请注意该值必须小于7天 145 | * @return url 146 | */ 147 | public String getObjectURL(String bucketName, String objectName, int minutes) { 148 | return getObjectURL(bucketName, objectName, Duration.ofMinutes(minutes)); 149 | } 150 | 151 | /** 152 | * 获取文件外链,只用于下载 153 | * @param bucketName bucket名称 154 | * @param objectName 文件名称 155 | * @param expires 过期时间,请注意该值必须小于7天 156 | * @return url 157 | */ 158 | public String getObjectURL(String bucketName, String objectName, Duration expires) { 159 | GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(objectName).build(); 160 | 161 | GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder().signatureDuration(expires) 162 | .getObjectRequest(getObjectRequest).build(); 163 | 164 | PresignedGetObjectRequest presignedGetObjectRequest = s3Presigner.presignGetObject(getObjectPresignRequest); 165 | return presignedGetObjectRequest.url().toString(); 166 | } 167 | 168 | /** 169 | * 获取文件上传外链,只用于上传 170 | * @param bucketName bucket名称 171 | * @param objectName 文件名称 172 | * @param minutes 过期时间,单位分钟,请注意该值必须小于7天 173 | * @return url 174 | */ 175 | public String getPutObjectURL(String bucketName, String objectName, int minutes) { 176 | return getPutObjectURL(bucketName, objectName, Duration.ofMinutes(minutes)); 177 | } 178 | 179 | /** 180 | * 获取文件上传外链,只用于上传 181 | * @param bucketName bucket名称 182 | * @param objectName 文件名称 183 | * @param expires 过期时间,请注意该值必须小于7天 184 | * @return url 185 | */ 186 | public String getPutObjectURL(String bucketName, String objectName, Duration expires) { 187 | PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(bucketName).key(objectName).build(); 188 | 189 | PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder().signatureDuration(expires) 190 | .putObjectRequest(putObjectRequest).build(); 191 | 192 | PresignedPutObjectRequest presignedPutObjectRequest = s3Presigner.presignPutObject(putObjectPresignRequest); 193 | return presignedPutObjectRequest.url().toString(); 194 | } 195 | 196 | /** 197 | * 获取文件外链(兼容v1 API的方法签名) 198 | * @param bucketName bucket名称 199 | * @param objectName 文件名称 200 | * @param minutes 过期时间,单位分钟,请注意该值必须小于7天 201 | * @param httpMethod 请求方法(GET/PUT) 202 | * @return url 203 | */ 204 | public String getObjectURL(String bucketName, String objectName, int minutes, String httpMethod) { 205 | return getObjectURL(bucketName, objectName, Duration.ofMinutes(minutes), httpMethod); 206 | } 207 | 208 | /** 209 | * 获取文件外链 210 | * @param bucketName bucket名称 211 | * @param objectName 文件名称 212 | * @param expires 过期时间,请注意该值必须小于7天 213 | * @param httpMethod 请求方法(GET/PUT) 214 | * @return url 215 | */ 216 | public String getObjectURL(String bucketName, String objectName, Duration expires, String httpMethod) { 217 | if ("PUT".equalsIgnoreCase(httpMethod)) { 218 | return getPutObjectURL(bucketName, objectName, expires); 219 | } 220 | else { 221 | return getObjectURL(bucketName, objectName, expires); 222 | } 223 | } 224 | 225 | /** 226 | * 获取文件URL(公共访问) 227 | *

228 | * If the object identified by the given bucket and key has public read permissions 229 | * then this URL can be directly accessed to retrieve the object's data. 230 | * @param bucketName bucket名称 231 | * @param objectName 文件名称 232 | * @return url 233 | */ 234 | public String getObjectURL(String bucketName, String objectName) { 235 | return String.format("%s/%s/%s", ossProperties.getEndpoint(), bucketName, objectName); 236 | } 237 | 238 | /** 239 | * 获取文件 240 | * @param bucketName bucket名称 241 | * @param objectName 文件名称 242 | * @return 二进制流 243 | * @see AWS 244 | * API Documentation 245 | */ 246 | public InputStream getObject(String bucketName, String objectName) { 247 | GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(objectName).build(); 248 | 249 | return s3Client.getObject(getObjectRequest, ResponseTransformer.toInputStream()); 250 | } 251 | 252 | /** 253 | * 上传文件 254 | * @param bucketName bucket名称 255 | * @param objectName 文件名称 256 | * @param stream 文件流 257 | * @throws IOException IOException 258 | */ 259 | public void putObject(String bucketName, String objectName, InputStream stream) throws IOException { 260 | putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream"); 261 | } 262 | 263 | /** 264 | * 上传文件 指定 contextType 265 | * @param bucketName bucket名称 266 | * @param objectName 文件名称 267 | * @param stream 文件流 268 | * @param contextType 文件类型 269 | * @throws IOException IOException 270 | */ 271 | public void putObject(String bucketName, String objectName, String contextType, InputStream stream) 272 | throws IOException { 273 | putObject(bucketName, objectName, stream, stream.available(), contextType); 274 | } 275 | 276 | /** 277 | * 上传文件 278 | * @param bucketName bucket名称 279 | * @param objectName 文件名称 280 | * @param stream 文件流 281 | * @param size 大小 282 | * @param contextType 类型 283 | * @see AWS 284 | * API Documentation 285 | */ 286 | public PutObjectResponse putObject(String bucketName, String objectName, InputStream stream, long size, 287 | String contextType) { 288 | PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(bucketName).key(objectName) 289 | .contentType(contextType).contentLength(size).build(); 290 | 291 | return s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(stream, size)); 292 | } 293 | 294 | /** 295 | * 获取文件信息 296 | * @param bucketName bucket名称 297 | * @param objectName 文件名称 298 | * @return 文件头信息 299 | * @see AWS 300 | * API Documentation 301 | */ 302 | public HeadObjectResponse getObjectInfo(String bucketName, String objectName) { 303 | HeadObjectRequest headObjectRequest = HeadObjectRequest.builder().bucket(bucketName).key(objectName).build(); 304 | 305 | return s3Client.headObject(headObjectRequest); 306 | } 307 | 308 | /** 309 | * 删除文件 310 | * @param bucketName bucket名称 311 | * @param objectName 文件名称 312 | * @see AWS API 314 | * Documentation 315 | */ 316 | public void removeObject(String bucketName, String objectName) { 317 | DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(bucketName).key(objectName) 318 | .build(); 319 | 320 | s3Client.deleteObject(deleteObjectRequest); 321 | } 322 | 323 | @Override 324 | public void afterPropertiesSet() throws Exception { 325 | // 创建 S3 客户端 326 | this.s3Client = S3Client.builder().endpointOverride(URI.create(ossProperties.getEndpoint())) 327 | .region(Region.of(ossProperties.getRegion() != null ? ossProperties.getRegion() : "us-east-1")) 328 | .credentialsProvider(StaticCredentialsProvider 329 | .create(AwsBasicCredentials.create(ossProperties.getAccessKey(), ossProperties.getSecretKey()))) 330 | .serviceConfiguration( 331 | S3Configuration.builder().pathStyleAccessEnabled(ossProperties.getPathStyleAccess()) 332 | .chunkedEncodingEnabled(ossProperties.getChunkedEncodingEnabled()).build()) 333 | .build(); 334 | 335 | // 创建 S3 Presigner 336 | this.s3Presigner = S3Presigner.builder().endpointOverride(URI.create(ossProperties.getEndpoint())) 337 | .region(Region.of(ossProperties.getRegion() != null ? ossProperties.getRegion() : "us-east-1")) 338 | .credentialsProvider(StaticCredentialsProvider 339 | .create(AwsBasicCredentials.create(ossProperties.getAccessKey(), ossProperties.getSecretKey()))) 340 | .serviceConfiguration( 341 | S3Configuration.builder().pathStyleAccessEnabled(ossProperties.getPathStyleAccess()).build()) 342 | .build(); 343 | } 344 | 345 | } 346 | --------------------------------------------------------------------------------