对接第三方文件上传
# 对接第三方云服务的文件上传
在实际开发中,企业通常会选择使用第三方云服务进行文件存储,如阿里云 OSS、腾讯云 COS、亚马逊 S3 和七牛云等。这些云服务可以提供稳定、高效、低成本的文件存储解决方案。以下是几种常见的云存储对接实现方式:
# 一、常见第三方云服务文件上传方案
# 1. 对接阿里云 OSS
阿里云对象存储 OSS 是一种适合存储海量数据的云服务,支持图片、视频、文档等多种类型文件的存储和管理。
实现步骤:
- 添加 Maven 依赖:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.13.0</version>
</dependency>
2
3
4
5
- 配置 OSS 参数(如
application.yml
或application.properties
):
aliyun:
oss:
# 阿里云 OSS 的访问区域节点(Endpoint),一般格式为 oss-<region>.aliyuncs.com
# 示例:oss-cn-hangzhou.aliyuncs.com 表示华东 1 区域
endpoint: oss-cn-hangzhou.aliyuncs.com
# 访问密钥 ID(AccessKeyId),用于标识你的阿里云账号
# 示例:LTAI4F9sdfhsdiufi739
accessKeyId: <你的AccessKeyId>
# 访问密钥 Secret(AccessKeySecret),用于验证你对阿里云账号的访问权限
# 示例:oHg5dfshf834jhsdfbsdfsd874hrjsdbsf
accessKeySecret: <你的AccessKeySecret>
# 存储桶名称(BucketName),用于指定你要上传文件的 OSS 存储空间
# 示例:my-app-bucket
bucketName: <你的BucketName>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 阿里云工具类
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
@Component
public class AliyunOssUtil {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
// 默认上传到根目录的方法
public String upload(MultipartFile file) {
return upload(file, "");
}
// 上传文件到指定文件夹的方法
public String upload(MultipartFile file, String folderPath) {
if (file.isEmpty()) {
throw new IllegalArgumentException("文件为空,请选择有效文件进行上传");
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
assert originalFilename != null;
String fileName = UUID.randomUUID().toString() + "_" + originalFilename;
// 如果 folderPath 不以斜杠结尾,自动添加斜杠
if (!folderPath.isEmpty() && !folderPath.endsWith("/")) {
folderPath += "/";
}
// 拼接文件完整路径
String filePath = folderPath + fileName;
// 创建 OSS 客户端实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try (InputStream inputStream = file.getInputStream()) {
// 上传文件流到 OSS
ossClient.putObject(bucketName, filePath, inputStream);
return "https://" + bucketName + "." + endpoint + "/" + filePath;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件上传失败", e);
} finally {
// 关闭 OSS 客户端
ossClient.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
关键点:
OSSClientBuilder().build()
:创建 OSS 客户端实例,参数包括endpoint
、accessKeyId
和accessKeySecret
。putObject(bucketName, fileName, inputStream)
:将文件流上传到 OSS,bucketName
是你的存储空间名称,fileName
是文件在 OSS 上的名称。- 配置项:所有配置项(如
endpoint
、accessKeyId
等)都应通过@Value
注解从配置文件中加载,避免硬编码。
在阿里云 OSS 中,不需要显式创建文件夹。OSS 自动支持通过路径来模拟文件夹结构。也就是说,即使文件夹不存在,只要你上传的文件路径中包含文件夹的部分,OSS 会自动创建这些“文件夹”。
假设你上传一个文件到 images/avatars/
目录,路径为:
images/avatars/yourfile.jpg
即使 images/avatars/
目录在 OSS 中不存在,阿里云 OSS 也会自动处理,将路径中的 images/avatars/
作为“文件夹”结构来存储 yourfile.jpg
。因此,不会因为文件夹不存在而报错。
注意事项
路径前面不应该加 /
,因为阿里云 OSS 是基于路径来模拟文件夹的。如果在路径前面加了 /
,就会导致文件路径从根目录开始,而这在 OSS 中会导致文件路径不符合预期。
# 2. 对接腾讯云 COS
腾讯云对象存储 COS 是一种高性能、安全、易用的云存储服务,适用于各类业务场景的数据存储需求。
实现步骤:
- 添加 Maven 依赖:
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.26</version>
</dependency>
2
3
4
5
- 配置 COS 参数:
tencent:
cos:
# 腾讯云 COS 的访问密钥 ID(SecretId),用于标识你的腾讯云账号
# 示例:AKIDfjksdjsdfsdfsdhf748rhjsdf
secretId: <你的SecretId>
# 腾讯云 COS 的访问密钥 Secret(SecretKey),用于验证你对腾讯云账号的访问权限
# 示例:fjsdhfkjsdhfksdjfhksdjfhjksdhfksdjfhk
secretKey: <你的SecretKey>
# 存储桶所在的区域名称(Region),指定你的 COS 服务所在的地域
# 示例:ap-guangzhou 表示华南广州区
region: ap-guangzhou
# 存储桶名称(BucketName),用于指定你要上传文件的 COS 存储空间
# 示例:my-app-bucket
bucketName: <你的BucketName>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 腾讯云工具类
import com.qcloud.cos.COSClient;
import com.qcloud.cos.COSCredentials;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.region.Region;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@Component
public class TencentCosUtil {
@Value("${tencent.cos.secretId}")
private String secretId;
@Value("${tencent.cos.secretKey}")
private String secretKey;
@Value("${tencent.cos.region}")
private String region;
@Value("${tencent.cos.bucketName}")
private String bucketName;
public String upload(MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("文件为空,请选择有效文件进行上传");
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
assert originalFilename != null;
String fileName = UUID.randomUUID().toString() + "_" + originalFilename;
// 初始化用户身份信息(secretId, secretKey)
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
ClientConfig clientConfig = new ClientConfig(new Region(region));
COSClient cosClient = new COSClient(cred, clientConfig);
try {
// 临时存储文件到本地,COS 不直接支持 MultipartFile,需要先转换为 File
File localFile = File.createTempFile("temp", null);
file.transferTo(localFile);
// 创建上传请求对象
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, localFile);
cosClient.putObject(putObjectRequest);
// 删除临时文件
localFile.delete();
return "https://" + bucketName + ".cos." + region + ".myqcloud.com/" + fileName;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件上传失败", e);
} finally {
// 关闭 COS 客户端
cosClient.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
关键点:
BasicCOSCredentials(secretId, secretKey)
:创建 COS 身份验证对象。PutObjectRequest(bucketName, fileName, localFile)
:创建上传请求,注意 COS 不支持直接使用MultipartFile
,需要先转换为File
。- 文件清理:上传完成后,务必删除临时文件,避免占用服务器资源。
# 3. 对接亚马逊 S3
Amazon S3 是一款高可用、持久化存储服务,适合需要全球访问的企业级应用。
实现步骤:
- 添加 Maven 依赖:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.1000</version>
</dependency>
2
3
4
5
- 配置 S3 参数:
aws:
s3:
# AWS S3 的访问密钥 ID(AccessKeyId),用于标识你的 AWS 账号
# 示例:AKIAIOSFODNN7EXAMPLE
accessKeyId: <你的AccessKeyId>
# AWS S3 的访问密钥 Secret(SecretAccessKey),用于验证你对 AWS 账号的访问权限
# 示例:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
secretAccessKey: <你的SecretAccessKey>
# 存储桶所在的区域名称(Region),指定你的 S3 服务所在的地域
# 示例:us-west-2 表示美国西部(俄勒冈州)
region: us-west-2
# 存储桶名称(BucketName),用于指定你要上传文件的 S3 存储空间
# 示例:my-app-bucket
bucketName: <你的BucketName>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 亚马逊工具类
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@Component
public class AwsS3Util {
@Value("${aws.s3.accessKeyId}")
private String accessKeyId;
@Value("${aws.s3.secretAccessKey}")
private String secretAccessKey;
@Value("${aws.s3.region}")
private String region;
@Value("${aws.s3.bucketName}")
private String bucketName;
public String upload(MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("文件为空,请选择有效文件进行上传");
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
assert originalFilename != null;
String fileName = UUID.randomUUID().toString() + "_" + originalFilename;
// 创建 AWS 认证对象
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKeyId, secretAccessKey);
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(Regions.fromName(region))
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
try {
// 临时存储文件到本地,S3 不直接支持 MultipartFile,需要先转换为 File
File localFile = File.createTempFile("temp", null);
file.transferTo(localFile);
// 创建上传请求对象
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, localFile);
s3Client.putObject(putObjectRequest);
// 删除临时文件
localFile.delete();
return "https://" + bucketName + ".s3." + region + ".amazonaws.com/" + fileName;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件上传失败", e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
关键点:
BasicAWSCredentials(accessKeyId, secretAccessKey)
:创建 AWS 认证对象。PutObjectRequest(bucketName, fileName, localFile)
:创建 S3 文件上传请求。- 全球访问:亚马逊 S3 适合需要全球用户访问的场景,支持多区域存储。
# 4. 对接七牛云
七牛云提供的对象存储服务适合国内开发者使用,操作简单,集成度高,支持多种文件上传方式。
实现步骤:
- 添加 Maven 依赖:
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.7.0</version>
</dependency>
2
3
4
5
- 配置七牛云参数:
qiniu:
# 七牛云的访问密钥(AccessKey),用于标识你的七牛云账号
# 示例:Qksfhjksdhfksjdhfksjdfhksdjhfksjdf
accessKey: <你的AccessKey>
# 七牛云的访问密钥 Secret(SecretKey),用于验证你对七牛云账号的访问权限
# 示例:jskfhkshdfkjsdhfkjshdfksdjfhksd
secretKey: <你的SecretKey>
# 存储桶名称(BucketName),用于指定你要上传文件的七牛云存储空间
# 示例:my-app-bucket
bucket: <你的BucketName>
# 访问域名(Domain),用于指定你在七牛云配置的自定义域名,用于文件的外网访问
# 示例:https://cdn.my-app.com
domain: <你的Domain>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 七牛云工具类
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Component
public class QiniuOssUtil {
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.secretKey}")
private String secretKey;
@Value("${qiniu.bucket}")
private String bucket;
@Value("${qiniu.domain}")
private String domain;
public String upload(MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("文件为空,请选择有效文件进行上传");
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
assert originalFilename != null;
String fileName = UUID.randomUUID().toString() + "_" + originalFilename;
// 构建配置
Configuration cfg = new Configuration();
UploadManager uploadManager = new UploadManager(cfg);
// 生成上传凭证
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
// 上传文件
byte[] uploadBytes = file.getBytes();
DefaultPutRet putRet = uploadManager.put(uploadBytes, fileName, upToken);
// 返回文件访问地址
return domain + "/" + putRet.key;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件上传失败", e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
关键点:
Auth.create(accessKey, secretKey)
:生成七牛云的身份认证对象。uploadManager.put(uploadBytes, fileName, upToken)
:上传文件到七牛云,支持字节数组上传。- 返回地址:文件上传成功后,通过
domain + "/" + putRet.key
构建文件访问路径。
# 5. 统一使用工具类的方式
你可以将这些工具类注入到你的服务或控制器中,然后调用相应的 upload
方法来完成文件上传并返回文件的访问路径。例如:
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
private final AliyunOssUtil aliyunOssUtil;
private final TencentCosUtil tencentCosUtil;
private final AwsS3Util awsS3Util;
private final QiniuOssUtil qiniuOssUtil;
public FileUploadController(AliyunOssUtil aliyunOssUtil, TencentCosUtil tencentCosUtil, AwsS3Util awsS3Util, QiniuOssUtil qiniuOssUtil) {
this.aliyunOssUtil = aliyunOssUtil;
this.tencentCosUtil = tencentCosUtil;
this.awsS3Util = awsS3Util;
this.qiniuOssUtil = qiniuOssUtil;
}
@PostMapping("/aliyun")
public String uploadToAliyun(@RequestParam("file") MultipartFile file) {
return aliyunOssUtil.upload(file);
}
@PostMapping("/tencent")
public String uploadToTencent(@RequestParam("file") MultipartFile file) {
return tencentCosUtil.upload(file);
}
@PostMapping("/aws")
public String uploadToAws(@RequestParam("file") MultipartFile file) {
return awsS3Util.upload(file);
}
@PostMapping("/qiniu")
public String uploadToQiniu(@RequestParam("file") MultipartFile file) {
return qiniuOssUtil.upload(file);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
在前端请求相应的接口时即可获得文件的访问路径。
# 二、方式对比总结
对接云服务 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
阿里云 OSS | 国内访问速度快,集成简单,支持丰富的 API | 服务费用相对较高 | 国内市场应用,大量文件存储 |
腾讯云 COS | 腾讯云生态内集成好,支持丰富的安全和加速功能 | 需要将 MultipartFile 转换为 File | 腾讯云生态应用,文件加速需求 |
Amazon S3 | 全球访问性能优越,支持多区域存储,稳定可靠 | 服务费用较高,配置相对复杂 | 全球市场应用,面向国际用户 |
七牛云 | 国内开发者友好,操作简单,支持多种上传方式 | 访问域名需配置,功能相对单一 | 中小型企业或个人项目的文件存储 |
# 三、重要 API 和参数详解
OSSClientBuilder.build()
(阿里云 OSS):构建 OSS 客户端实例,用于与 OSS 进行交互。BasicCOSCredentials(secretId, secretKey)
(腾讯云 COS):创建 COS 认证对象,用于验证 API 请求的合法性。BasicAWSCredentials(accessKeyId, secretAccessKey)
(亚马逊 S3):创建 AWS 认证对象,支持与 S3 进行安全通信。Auth.create(accessKey, secretKey)
(七牛云):生成七牛云的认证对象,获取上传凭证。putObject(bucketName, fileName, inputStream)
:云存储上传文件的核心方法,负责将文件流上传到指定的存储桶中。
# 四、策略模式对接第三方服务
为了更好地理解如何使用策略模式对接多个云服务,下面的代码将分块展示,并在每个代码块中加上详细注释,解释每一步的实现、设计理由以及好处。
# 1. 策略接口的定义
首先,我们定义一个通用的策略接口 CloudStorageStrategy
,所有云服务的实现都必须遵循该接口。这样做的好处是可以保持代码的统一性和扩展性。
/**
* 云存储策略接口,定义文件上传的通用方法。
* 所有云服务的具体实现都必须实现该接口,以保证一致性。
*/
public interface CloudStorageStrategy {
/**
* 文件上传接口,所有云存储实现都需提供该方法。
*
* @param file MultipartFile 对象,前端上传的文件。
* @return 文件上传后的访问 URL。
*/
String upload(MultipartFile file);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
设计理由:
- 使用策略接口将云服务的实现抽象化,确保每种云服务都有统一的上传接口。
- 这样做的好处是增加了代码的灵活性,后续新增或更改云服务时只需实现该接口即可。
# 2. 阿里云 OSS 的具体实现
接下来,我们实现阿里云 OSS 的文件上传策略。
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
/**
* 阿里云 OSS 实现类,负责处理文件上传到阿里云 OSS 的逻辑。
*/
public class AliyunOssStrategy implements CloudStorageStrategy {
private final OSS ossClient;
private final String bucketName;
private final String endpoint;
/**
* 构造函数,初始化阿里云 OSS 客户端。
*
* @param endpoint 阿里云 OSS 访问区域节点,如 oss-cn-hangzhou.aliyuncs.com
* @param accessKeyId 阿里云的 AccessKeyId
* @param accessKeySecret 阿里云的 AccessKeySecret
* @param bucketName 存储桶名称
*/
public AliyunOssStrategy(String endpoint, String accessKeyId, String accessKeySecret, String bucketName) {
// 创建 OSS 客户端实例
this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
this.bucketName = bucketName;
this.endpoint = endpoint;
}
/**
* 上传文件到阿里云 OSS
*
* @param file 前端上传的文件
* @return 上传后的文件访问 URL
*/
@Override
public String upload(MultipartFile file) {
try {
// 生成唯一文件名,避免文件名冲突
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
InputStream inputStream = file.getInputStream();
// 上传文件到 OSS
ossClient.putObject(bucketName, fileName, inputStream);
// 返回文件的访问路径
return "https://" + bucketName + "." + endpoint + "/" + fileName;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
} finally {
// 关闭 OSS 客户端,释放资源
ossClient.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
实现说明:
- 使用
OSSClientBuilder().build()
创建 OSS 客户端,传入访问区域节点、AccessKeyId 和 AccessKeySecret。 - 使用
UUID
生成唯一文件名,确保文件不重名,这样可以避免覆盖同名文件。 - 调用
putObject()
方法将文件流上传到指定的存储桶中。 - 上传完成后关闭 OSS 客户端,释放资源。
好处:
- 统一的文件名生成策略确保文件存储的唯一性。
OSSClientBuilder
提供的客户端配置简单易用,集成了阿里云的 SDK 功能。
# 3. 腾讯云 COS 的具体实现
import com.qcloud.cos.COSClient;
import com.qcloud.cos.COSCredentials;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.region.Region;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* 腾讯云 COS 实现类,负责处理文件上传到腾讯云 COS 的逻辑。
*/
public class TencentCosStrategy implements CloudStorageStrategy {
private final COSClient cosClient;
private final String bucketName;
/**
* 构造函数,初始化腾讯云 COS 客户端。
*
* @param secretId 腾讯云的 SecretId
* @param secretKey 腾讯云的 SecretKey
* @param region COS 服务的区域名称,如 ap-guangzhou
* @param bucketName 存储桶名称
*/
public TencentCosStrategy(String secretId, String secretKey, String region, String bucketName) {
// 初始化用户身份信息
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
ClientConfig clientConfig = new ClientConfig(new Region(region));
this.cosClient = new COSClient(cred, clientConfig);
this.bucketName = bucketName;
}
/**
* 上传文件到腾讯云 COS
*
* @param file 前端上传的文件
* @return 上传后的文件访问 URL
*/
@Override
public String upload(MultipartFile file) {
try {
// 生成唯一文件名,避免文件名冲突
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
// 临时存储文件到本地,COS 不支持 MultipartFile,需转换为 File
File localFile = File.createTempFile("temp", null);
file.transferTo(localFile);
// 上传文件到 COS
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, localFile);
cosClient.putObject(putObjectRequest);
// 删除临时文件
localFile.delete();
// 返回文件的访问路径
return "https://" + bucketName + ".cos." + cosClient.getClientConfig().getRegion().getRegionName() + ".myqcloud.com/" + fileName;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
} finally {
// 关闭 COS 客户端,释放资源
cosClient.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
实现说明:
- 腾讯云 COS 不直接支持
MultipartFile
,所以需要将其转换为File
再上传。 - 使用临时文件存储的方式实现了文件的中转,上传完成后及时删除临时文件,避免占用服务器资源。
好处:
- 腾讯云 COS 的 SDK 提供了丰富的 API,可以灵活配置存储策略,如文件生命周期管理和访问权限控制。
- 分区域存储策略使得在不同地域访问时都能获得较快的响应。
# 4. 亚马逊 S3 的具体实现
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* 亚马逊 S3 实现类,负责处理文件上传到 Amazon S3 的逻辑。
*/
public class AwsS3Strategy implements CloudStorageStrategy {
private final AmazonS3 s3Client;
private final String bucketName;
/**
* 构造函数,初始化亚马逊 S3 客户端。
*
* @param accessKeyId AWS 的 AccessKeyId
* @param secretAccessKey AWS 的 SecretAccessKey
* @param region S3 服务的区域,如 us-west-2
* @param bucketName 存储桶名称
*/
public AwsS3Strategy(String accessKeyId, String secretAccessKey, String region, String bucketName) {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKeyId, secretAccessKey);
this.s3Client = AmazonS3ClientBuilder.standard()
.withRegion(Regions.fromName(region))
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
this.bucketName = bucketName;
}
/**
* 上传文件到亚马逊 S3
*
* @param file 前端上传的文件
* @return 上传后的文件访问 URL
*/
@Override
public String upload(MultipartFile file) {
try {
// 生成唯一文件名,避免文件名冲突
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
// 临时存储文件到本地,S3 不支持 MultipartFile,需转换为 File
File localFile = File.createTempFile("temp", null);
file.transferTo(localFile);
// 上传文件到 S3
s3Client.putObject(new PutObjectRequest
(bucketName, fileName, localFile));
// 删除临时文件
localFile.delete();
// 返回文件的访问路径
return "https://" + bucketName + ".s3." + s3Client.getRegionName() + ".amazonaws.com/" + fileName;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
实现说明:
- 与腾讯云 COS 类似,Amazon S3 也需要将
MultipartFile
转换为File
。 - S3 的 API 提供了全球化的服务,可以根据项目需求在不同的区域选择最优的存储位置。
好处:
- S3 是全球化服务的首选,适合有跨地域访问需求的项目。
- AWS 的 SDK 配置灵活,可以轻松集成其他 AWS 服务。
# 5. 七牛云的具体实现
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import com.google.gson.Gson;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
/**
* 七牛云实现类,负责处理文件上传到七牛云的逻辑。
*/
public class QiniuOssStrategy implements CloudStorageStrategy {
private final UploadManager uploadManager;
private final Auth auth;
private final String bucketName;
private final String domain;
/**
* 构造函数,初始化七牛云客户端。
*
* @param accessKey 七牛云的 AccessKey
* @param secretKey 七牛云的 SecretKey
* @param bucketName 存储桶名称
* @param domain 访问域名
*/
public QiniuOssStrategy(String accessKey, String secretKey, String bucketName, String domain) {
this.uploadManager = new UploadManager(new Configuration());
this.auth = Auth.create(accessKey, secretKey);
this.bucketName = bucketName;
this.domain = domain;
}
/**
* 上传文件到七牛云
*
* @param file 前端上传的文件
* @return 上传后的文件访问 URL
*/
@Override
public String upload(MultipartFile file) {
try {
// 生成唯一文件名,避免文件名冲突
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
// 获取文件的字节数据
byte[] uploadBytes = file.getBytes();
// 生成上传凭证
String upToken = auth.uploadToken(bucketName);
// 上传文件到七牛云
com.qiniu.http.Response response = uploadManager.put(uploadBytes, fileName, upToken);
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
// 返回文件的访问路径
return domain + "/" + putRet.key;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
实现说明:
- 七牛云的实现相对简单,支持直接上传字节数组,不需要中间文件转换。
- 上传凭证通过
Auth.create()
生成,确保上传操作的合法性。
好处:
- 七牛云 SDK 易用性强,集成成本低,适合国内的中小型企业项目。
- 简单配置即可实现稳定的文件上传服务。
# 6. 策略上下文类
策略上下文类用于管理和调用不同的云存储策略。这样做的好处是可以在运行时动态选择不同的云服务,增强了系统的灵活性。
/**
* 云存储策略上下文类,负责管理和调用具体的云存储实现。
*/
public class CloudStorageContext {
private CloudStorageStrategy strategy;
/**
* 设置当前使用的云存储策略。
*
* @param strategy 云存储策略实现
*/
public void setStrategy(CloudStorageStrategy strategy) {
this.strategy = strategy;
}
/**
* 上传文件,调用具体策略的上传实现。
*
* @param file 前端上传的文件
* @return 上传后的文件访问 URL
*/
public String uploadFile(MultipartFile file) {
return strategy.upload(file);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
设计理由:
- 通过上下文类管理策略,可以在运行时根据需求动态切换不同的云存储实现。
- 这样的设计提高了代码的灵活性和可扩展性,增加新的云服务实现时只需创建一个新的策略类并注册到上下文即可。
# 7. 工厂类用于生成具体的策略实现
使用工厂模式根据配置动态生成云存储策略实现类。
import java.util.Map;
/**
* 云存储策略工厂类,根据配置动态生成具体的策略实现。
*/
public class CloudStorageFactory {
/**
* 根据类型获取对应的云存储策略实现。
*
* @param type 云服务类型,如 "aliyun", "tencent", "aws", "qiniu"
* @param config 配置参数,用于初始化策略实现
* @return 云存储策略实现类
*/
public static CloudStorageStrategy getStrategy(String type, Map<String, String> config) {
switch (type.toLowerCase()) {
case "aliyun":
return new AliyunOssStrategy(
config.get("endpoint"),
config.get("accessKeyId"),
config.get("accessKeySecret"),
config.get("bucketName")
);
case "tencent":
return new TencentCosStrategy(
config.get("secretId"),
config.get("secretKey"),
config.get("region"),
config.get("bucketName")
);
case "aws":
return new AwsS3Strategy(
config.get("accessKeyId"),
config.get("secretAccessKey"),
config.get("region"),
config.get("bucketName")
);
case "qiniu":
return new QiniuOssStrategy(
config.get("accessKey"),
config.get("secretKey"),
config.get("bucket"),
config.get("domain")
);
default:
throw new IllegalArgumentException("未知的云服务类型");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
设计理由:
- 工厂模式允许在不暴露实例化逻辑的情况下,动态创建不同的云存储策略实现。
- 这种设计确保代码的扩展性,即使未来需要增加新的云服务类型,只需扩展工厂类即可。
# 8. 配置文件(application.yml
)
首先,配置文件中定义各个云服务的参数。
cloud:
default-type: aliyun # 设置全局默认云服务类型
aliyun:
# 阿里云 OSS 的访问区域节点
endpoint: oss-cn-hangzhou.aliyuncs.com
# 阿里云的访问密钥 ID
accessKeyId: your-aliyun-access-key-id
# 阿里云的访问密钥 Secret
accessKeySecret: your-aliyun-access-key-secret
# 阿里云的存储桶名称
bucketName: your-aliyun-bucket-name
tencent:
# 腾讯云 COS 的访问密钥 ID
secretId: your-tencent-secret-id
# 腾讯云 COS 的访问密钥 Secret
secretKey: your-tencent-secret-key
# 腾讯云 COS 服务的区域名称
region: ap-guangzhou
# 腾讯云 COS 的存储桶名称
bucketName: your-tencent-bucket-name
aws:
# AWS S3 的访问密钥 ID
accessKeyId: your-aws-access-key-id
# AWS S3 的访问密钥 Secret
secretAccessKey: your-aws-secret-access-key
# AWS S3 服务的区域名称
region: us-west-2
# AWS S3 的存储桶名称
bucketName: your-aws-bucket-name
qiniu:
# 七牛云的访问密钥
accessKey: your-qiniu-access-key
# 七牛云的访问密钥 Secret
secretKey: your-qiniu-secret-key
# 七牛云的存储桶名称
bucket: your-qiniu-bucket-name
# 七牛云的访问域名
domain: https://cdn.my-app.com
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 9. 配置类读取配置
使用 @ConfigurationProperties
注解来读取配置文件中的内容,并将其绑定到配置类中。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 云存储配置类,读取 application.yml 中的配置。
*/
@Component
@ConfigurationProperties(prefix = "cloud") // 读取以 "cloud" 开头的配置
public class CloudStorageProperties {
// 默认云服务类型
private String defaultType;
// 阿里云配置
private Map<String, String> aliyun;
// 腾讯云配置
private Map<String, String> tencent;
// 亚马逊 S3 配置
private Map<String, String> aws;
// 七牛云配置
private Map<String, String> qiniu;
// Getter 和 Setter 方法
public String getDefaultType() {
return defaultType;
}
public void setDefaultType(String defaultType) {
this.defaultType = defaultType;
}
public Map<String, String> getAliyun() {
return aliyun;
}
public void setAliyun(Map<String, String> aliyun) {
this.aliyun = aliyun;
}
public Map<String, String> getTencent() {
return tencent;
}
public void setTencent(Map<String, String> tencent) {
this.tencent = tencent;
}
public Map<String, String> getAws() {
return aws;
}
public void setAws(Map<String, String> aws) {
this.aws = aws;
}
public Map<String, String> getQiniu() {
return qiniu;
}
public void setQiniu(Map<String, String> qiniu) {
this.qiniu = qiniu;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
注释说明:
@ConfigurationProperties(prefix = "cloud")
:指定读取配置文件中以cloud
开头的配置。- 每个云服务的配置都用
Map<String, String>
类型来存储,这样可以灵活地获取配置项。
# 10. 控制器中使用配置类
在控制器中,通过注入 CloudStorageProperties
类来获取具体的云服务配置。
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
@Autowired
private CloudStorageProperties cloudStorageProperties; // 读取配置类
@PostMapping
public String uploadFile(
@RequestParam(value = "cloudType", required = false) String cloudType, // 云服务类型参数设置为可选
@RequestParam("file") MultipartFile file) {
// 如果前端未传递 cloudType,使用全局默认值
if (cloudType == null || cloudType.trim().isEmpty()) {
cloudType = cloudStorageProperties.getDefaultType();
}
// 根据云服务类型获取配置(配置设置在yml文件中)
Map<String, String> config = getConfigForCloud(cloudType);
// 获取云存储策略(通过云服务类型+对应的具体配置生成对应的策略实现类)
CloudStorageStrategy strategy = CloudStorageFactory.getStrategy(cloudType, config);
// 设置策略上下文(将策略实现类放入到策略上下文中)
CloudStorageContext context = new CloudStorageContext();
context.setStrategy(strategy);
// 上传文件并返回结果
return context.uploadFile(file);
}
// 根据传入的云服务类型读取对应的云服务配置
private Map<String, String> getConfigForCloud(String cloudType) {
switch (cloudType.toLowerCase()) {
case "aliyun":
return cloudStorageProperties.getAliyun();
case "tencent":
return cloudStorageProperties.getTencent();
case "aws":
return cloudStorageProperties.getAws();
case "qiniu":
return cloudStorageProperties.getQiniu();
default:
throw new IllegalArgumentException("未知的云服务类型:" + cloudType);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
关键点说明:
@Autowired
:注入CloudStorageProperties
类,Spring Boot 会自动将配置文件中的内容绑定到该类中。getConfigForCloud(String cloudType)
:根据传入的云服务类型(如aliyun
、tencent
等)返回对应的配置。
配置文件读取的工作流程:
- 配置文件中的云服务参数通过
CloudStorageProperties
类读取并保存。 - 控制器接收前端请求,传入的
cloudType
决定选择哪种云服务。 - 根据
cloudType
获取对应的配置,并通过工厂模式生成云存储策略。 - 上下文类调用具体策略,实现文件上传。
总结
- 策略模式:将不同云服务的上传逻辑抽象成策略接口,使得代码结构清晰且易于扩展。
- 工厂模式:在运行时动态创建不同的云服务策略,实现了对云服务选择的灵活控制。
- 上下文管理:通过上下文类封装策略调用,简化了业务逻辑,实现了代码的解耦。
# 五、整体代码执行流程
在这个方案中,主要涉及策略模式、工厂模式、配置文件管理和控制器请求处理。通过这些模式和设计,整个流程实现了灵活的云服务对接。下面我将详细梳理整个代码的执行流程。
1. 前端发起文件上传请求
前端会通过一个 POST 请求将文件上传至后端,并且可能会传递一个 cloudType
参数,表示希望使用哪个云服务上传文件。
axios.post('/api/upload', formData, {
params: {
cloudType: 'aliyun' // 选择使用阿里云上传
}
});
2
3
4
5
2. Spring Boot 接收请求并进入控制器
在控制器中,接收到 cloudType
和文件参数:
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
@Autowired
private CloudStorageProperties cloudStorageProperties; // 注入配置类
@PostMapping
public String uploadFile(
@RequestParam(value = "cloudType", required = false) String cloudType, // 接收云服务类型参数
@RequestParam("file") MultipartFile file) { // 接收上传的文件
// ...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
3. 确定使用的云服务策略
在控制器中,根据前端传递的 cloudType
参数确定使用哪种云服务。如果 cloudType
为空,则使用配置文件中定义的默认云服务类型:
if (cloudType == null || cloudType.trim().isEmpty()) {
cloudType = cloudStorageProperties.getDefaultType();
}
2
3
4. 从配置文件中获取对应的云服务配置
根据确定的 cloudType
,从配置类中获取对应云服务的配置信息:
Map<String, String> config = getConfigForCloud(cloudType);
getConfigForCloud(cloudType)
方法通过 switch
语句返回具体的配置:
private Map<String, String> getConfigForCloud(String cloudType) {
switch (cloudType.toLowerCase()) {
case "aliyun":
return cloudStorageProperties.getAliyun();
case "tencent":
return cloudStorageProperties.getTencent();
case "aws":
return cloudStorageProperties.getAws();
case "qiniu":
return cloudStorageProperties.getQiniu();
default:
throw new IllegalArgumentException("未知的云服务类型:" + cloudType);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
返回的 Map<String, String>
格式
对于不同的云服务,Map<String, String>
的结构是这样的:以阿里云(Aliyun)为例
{
"endpoint" : "oss-cn-hangzhou.aliyuncs.com",
"accessKeyId" : "your-aliyun-access-key-id",
"accessKeySecret" : "your-aliyun-access-key-secret",
"bucketName" : "your-aliyun-bucket-name"
}
2
3
4
5
6
5. 使用工厂模式创建具体的云服务策略实现
接着,通过 CloudStorageFactory
工厂类,使用 cloudType
和配置数据创建对应的策略实现:
CloudStorageStrategy strategy = CloudStorageFactory.getStrategy(cloudType, config);
工厂类的实现:通过type
确定云服务的类型,将config
里面的配置传入到具体的云服务实现类中
public static CloudStorageStrategy getStrategy(String type, Map<String, String> config) {
switch (type.toLowerCase()) {
case "aliyun":
return new AliyunOssStrategy(
config.get("endpoint"),
config.get("accessKeyId"),
config.get("accessKeySecret"),
config.get("bucketName")
);
case "tencent":
return new TencentCosStrategy(
config.get("secretId"),
config.get("secretKey"),
config.get("region"),
config.get("bucketName")
);
// 其他云服务策略实现...
default:
throw new IllegalArgumentException("未知的云服务类型:" + type);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
返回的 strategy
是一个 AliyunOssStrategy
实例,格式如下:
AliyunOssStrategy strategy = new AliyunOssStrategy(
"oss-cn-hangzhou.aliyuncs.com", // Endpoint
"your-aliyun-access-key-id", // AccessKeyId
"your-aliyun-access-key-secret", // AccessKeySecret
"your-aliyun-bucket-name" // BucketName
);
2
3
4
5
6
这个 strategy
是 CloudStorageStrategy
接口的一个实现类,包含 upload(MultipartFile file)
方法,负责将文件上传到阿里云 OSS。
6. 设置策略上下文并调用上传方法
工厂返回的策略实现类被设置到策略上下文中,并调用其 uploadFile
方法:
CloudStorageContext context = new CloudStorageContext();
context.setStrategy(strategy);
return context.uploadFile(file);
2
3
策略上下文的实现:
public class CloudStorageContext {
private CloudStorageStrategy strategy;
public void setStrategy(CloudStorageStrategy strategy) {
this.strategy = strategy;
}
public String uploadFile(MultipartFile file) {
return strategy.upload(file);
}
}
2
3
4
5
6
7
8
9
10
11
提示
将策略实现类放到上下文中是为了实现策略模式的灵活性和扩展性。上下文管理策略的选择和执行,客户端只需要通过上下文调用方法,而无需关注具体的策略细节。这种设计提高了系统的灵活性、可维护性和可扩展性。
7. 执行云服务策略的上传逻辑
策略实现类中的 upload
方法会执行具体的文件上传逻辑。以阿里云 OSS 为例:
@Override
public String upload(MultipartFile file) {
try {
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
InputStream inputStream = file.getInputStream();
ossClient.putObject(bucketName, fileName, inputStream);
return "https://" + bucketName + "." + endpoint + "/" + fileName;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
} finally {
ossClient.shutdown();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
8. 返回上传结果
文件上传成功后,返回上传后的文件访问 URL,控制器将结果返回给前端。
9. 前端接收上传结果并展示
前端接收到响应结果后,展示或处理文件上传后的 URL,如在页面中显示图片、生成下载链接等。
整体执行流程图解
- 前端发起上传请求,携带文件和
cloudType
。 - 后端控制器接收请求,根据
cloudType
确定云服务类型。 - 通过工厂模式生成对应的策略实现。
- 策略上下文类设置并调用上传方法。
- 具体的云服务策略实现上传文件至对应的云服务。
- 控制器返回文件访问 URL 给前端。
- 前端展示上传结果。