阿里云OSS上传功能
# 前言
在单体应用和分布式应用上的文件上传功能,是有很大不同的。
# 单体应用:
部署在一台服务器上,文件一般保存在项目某个位置下
# 普通上传的分布式应用:
某一个服务要经常被服务,一台机器不够用,部署多台机器,当上传时,负载均衡到某个服务器,保存在某个机器的项目某个位置下。
但是当其他服务想要获取文件了,负债均衡到其他服务器,但是其他服务器没有这个文件了。
# 解决方案
上传文件统一放在文件系统中,统一在同一个地方读写,就没有问题了。
一般来说有两种方式,一种是自建服务器,另一种是云储存。
自建服务器,需要有专人去维护,搭建,买服务器,买流量,后期可能还要集群。
在这个时代,需要快速开发,快速迭代,弹性成本,那么推荐选择云存储服务。
# 阿里云OSS服务
# 简介
SpringCloud Alibaba-OSS
对象存储服务(Ojbect Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性拓展,多种存储类型供选择,全面优化存储成本。
- 登陆阿里云
- 开通OSS服务
- 管理控制台
- 更多>>API文档>>帮助帮助中心打开
要想使用,得先了解专业术语。
资源术语
中文 | 英文 | 说明 |
---|---|---|
存储空间 | Bucket | 存储空间是用于存储对象的容器,所有的对象都必须隶属于某个存储空间。 |
对象/文件 | Object | 对象是 OSS 存储数据的基本单元,也被称为OSS的文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。 |
地域 | Region | 地域表示 OSS 的数据中心所在物理位置。您可以根据费用、请求来源等综合选择数据存储的地域。详情请查看OSS已经开通的Region (opens new window)。 |
访问域名 | Endpoint | Endpoint 表示OSS对外服务的访问域名。OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。具体的内容请参见各个Region对应的Endpoint (opens new window)。 |
访问密钥 | AccessKey | AccessKey,简称 AK,指的是访问身份验证中用到的AccessKeyId 和AccessKeySecret。OSS通过使用AccessKeyId 和AccessKeySecret对称加密的方法来验证某个请求的发送者身份。AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密。 |
一般推荐一个项目创建一个存储空间。
在开发阶段,对象存储服务的费用也不会超过1元,可以放心开发。
# 创建存储空间
- 存储类型
开发阶段低频访问足以,上生产则改为标准。
- 服务器
https://ping.gaomeluo.com/aliyun/
根据ping自己所在地,选一个延迟最小的。
- 读写权限
公共读
- 服务端加密方式
无
- 日志
暂时不开通。
- 版本控制
暂时不开通。
# 设置跨域访问
# 测试上传文件
直接上传一个图片,然后复制url发现可以打开图片,说明已经正常了。
# 上传方式对比
阿里云对象存储上传方式 | 途径 | |
---|---|---|
普通上传方式 | 用户-应用服务器-OSS | 需要经过自己的服务器,在大量用户下容易带来瓶颈 |
服务端签名后直传 | 1.用户向应用服务器请求上传Policy 2.应用服务器返回上传Policy 3.用户直接上传到OSS |
# 引入依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
2
3
4
5
# 创建测试代码
简单上传-文件流上传
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
String accessKeyId = "<yourAccessKeyId>";
String accessKeySecret = "<yourAccessKeySecret>";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流。
InputStream inputStream = new FileInputStream("<yourlocalFile>");
ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
创建子账号
- 选择编程访问,确定
- 验证手机
分配权限:AliyunOSSFullAccess 管理对象存储服务(OSS)权限
# 使用封装好的alibaba OSS组件
官方文档
https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample
1.引入依赖
因为后来可能有很多服务都要用到上传,所以在 common中导入该依赖
<!--引入阿里云封装好的cloud oss-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
2
3
4
5
导入成功后,会发现已经能自动导入了sdk
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<name>Spring Cloud Starter Alibaba Cloud OSS</name>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alicloud-oss</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2.Configure accessKeyId, secretAccessKey and region in application.properties.
在配置文件中配置
// application.properties
alibaba.cloud.access-key=your-ak
alibaba.cloud.secret-key=your-sk
alibaba.cloud.oss.endpoint=***
2
3
4
测试上传
@Autowired
OSSClient ossClient;
@Test
void testUpload() throws FileNotFoundException {
// 上传文件流。
InputStream inputStream = null;
inputStream = new FileInputStream("C:\\Users\\SaulJ\\Downloads\\Compressed\\docs\\pics\\huawei.png");
ossClient.putObject("elitemall", "huawei.png", inputStream);
// 关闭OSSClient。
ossClient.shutdown();
System.out.println("上传成功....");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用第三方服务来统一管理。
- 新建一个第三方服务模块
- 选中Spring Web和Open Feign
- 添加依赖
<dependency>
<groupId>com.elite.mall</groupId>
<artifactId>elitemall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--common模块的直接删除,重新在这个模块引入阿里云封装好的cloud oss-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<!-- 2.2.3版本不兼容OSS服务 -->
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
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
- 把该微服务注册到配置中心
elitemall-third-party/src/main/resources/bootstrap.properties
spring.application.name=elitemall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=4e557e0f-6e01-4cbd-a850-3bfc471dec17
spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true
2
3
4
5
6
7
- 使用配置中心的文件oss.yml
spring:
cloud:
alicloud:
access-key:
secret-key:
oss:
endpoint:
2
3
4
5
6
7
- 把第三方服务注册到注册中心
elitemall-third-party/src/main/resources/application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: elitemall-third-party
2
3
4
5
6
7
elitemall-third-party/src/main/java/com/elite/mall/thirdparty/ElitemallThirdPartyApplication.java
package com.elite.mall.thirdparty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class ElitemallThirdPartyApplication {
public static void main(String[] args) {
SpringApplication.run(ElitemallThirdPartyApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 启动第三服务,在nacos查看服务列表是否启动成功
- 重新测试一次,去阿里云oss控制台的文件管理中查看文件是否上传成功
# 创建服务端签名直传接口
参考链接
- 对象存储 OSS (opens new window) >
- 最佳实践 (opens new window) >
- Web端上传数据至OSS (opens new window) >
- Web端PostObject直传实践 (opens new window) >
- 服务端签名后直传
https://help.aliyun.com/document_detail/31926.html?spm=a2c4g.11186623.6.1684.24616d1cPu9fW2
elitemall-third-party/src/main/java/com/elite/mall/thirdparty/service/impl/OssServiceImpl.java
package com.elite.mall.thirdparty.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.elite.mall.thirdparty.service.OssService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@Service("OssService")
public class OssServiceImpl implements OssService {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Override
public Map<String, String> getPolicy() {
// host的格式为 bucketname.endpoint
String host = "https://" + bucket + "." + endpoint;
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
String callbackUrl = "http://88.88.88.88:8888";
// 用户上传文件时指定的前缀。
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/";
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return respMap;
}
}
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
70
71
72
73
74
75
76
77
elitemall-third-party/src/main/java/com/elite/mall/thirdparty/controller/OssController.java
package com.elite.mall.thirdparty.controller;
import com.elite.mall.common.utils.CommonResult;
import com.elite.mall.thirdparty.service.OssService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/thirdparty/oss")
public class OssController {
@Autowired
OssService ossService;
@GetMapping
public CommonResult getPolicy(){
return CommonResult.ok().put("data", ossService.getPolicy());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
访问接口,发现已经能够获取签名信息
# 注册到网关中
elitemall-gateway/src/main/resources/application.yml
- id: third_party_route
uri: lb://elitemall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/(?<segment>/?.*), /$\{segment}
2
3
4
5
6
访问网关,查看是否能自动跳转路由
http://localhost:88/api/thirdparty/oss
# 完成文件上传功能
联调前端vue项目,完成文件上传功能
<template>
<div>
<el-upload
action="http://elitemall.oss-us-east-1.aliyuncs.com"
:data="dataObj"
list-type="picture"
:multiple="false"
:show-file-list="showFileList"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:on-progress="uploadFileProcess"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">
只能上传jpg/png文件,且不超过10MB
</div>
</el-upload>
<div class="file">
<div v-for="(item, index) in fileArr" :key="index">
<div v-if="item.percentage">
<el-progress :percentage="Math.round(item.percentage)"></el-progress>
</div>
</div>
</div>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt="" />
</el-dialog>
</div>
</template>
<script>
import { policy } from './policy'
import { getUUID } from '@/utils'
export default {
name: 'singleUpload',
props: {
value: String
},
computed: {
imageUrl() {
return this.value
},
imageName() {
if (this.value != null && this.value !== '') {
return this.value.substr(this.value.lastIndexOf('/') + 1)
} else {
return null
}
},
fileList() {
return [
{
name: this.imageName,
url: this.imageUrl
}
]
},
showFileList: {
get: function() {
return (
this.value !== null && this.value !== '' && this.value !== undefined
)
},
set: function(newValue) {}
}
},
data() {
return {
dataObj: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: ''
// callback:'',
},
dialogVisible: false,
fileArr: []
}
},
methods: {
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('')
this.fileArr = fileList
},
handlePreview(file) {
this.dialogVisible = true
},
beforeUpload(file) {
let _self = this
return new Promise((resolve, reject) => {
policy()
.then(response => {
_self.dataObj.policy = response.data.policy
_self.dataObj.signature = response.data.signature
_self.dataObj.ossaccessKeyId = response.data.accessid
_self.dataObj.key = response.data.dir + getUUID() + '_${filename}'
_self.dataObj.dir = response.data.dir
_self.dataObj.host = response.data.host
resolve(true)
})
.catch(err => {
reject(false)
})
})
},
handleUploadSuccess(res, file) {
console.log('上传成功...')
this.showFileList = true
this.fileList.pop()
this.fileList.push({
name: file.name,
url:
this.dataObj.host +
'/' +
this.dataObj.key.replace('${filename}', file.name)
})
this.emitInput(this.fileList[0].url)
},
/**
* 文件上传过程中的函数(在这里获取进度条的进度)
*/
uploadFileProcess(event, file, fileList) {
this.fileArr = fileList
}
}
}
</script>
<style></style>
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136