├── 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 | [](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 |
--------------------------------------------------------------------------------