web页面直传oss

web页面直传oss

客户端直传是指客户端直接上传文件到对象存储OSS。相对于服务端代理上传,客户端直传避免了业务服务器中转文件,提高了上传速度,节省了服务器资源。

为什么客户端直传

在典型的服务端和客户端架构下,常见的文件上传方式是服务端代理上传:客户端将文件上传到业务服务器,然后业务服务器将文件上传到OSS。在这个过程中,一份数据需要在网络上传输两次,会造成网络资源的浪费,增加服务端的资源开销。为了解决这一问题,您可以在客户端直连OSS来完成文件上传,无需经过业务服务器中转。

image-20241226161402473

服务端签名直传并设置上传回调

本文主要介绍如何基于Post Policy的使用规则在服务端通过各种语言代码完成签名,并且设置上传回调,然后通过表单直传数据到OSS。

大多数情况下,用户上传文件后,应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等,为此OSS提供了上传回调方案。

流程介绍

当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器时,需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。

image-20241226162101887

修改CORS

客户端进行表单直传到OSS时,会从浏览器向OSS发送带有Origin的请求消息。OSS对带有Origin头的请求消息会进行跨域规则(CORS)的验证。因此需要为Bucket设置跨域规则以支持Post方法。

  1. 登录OSS管理控制台
  2. 单击Bucket 列表,然后单击目标Bucket名称。
  3. 在左侧导航栏,选择数据安全 > 跨域设置
  4. 单击创建规则,配置如下图所示。

image-20241226162224637

获取上传token

1
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
public Object ossToken(@RequestParam(value = "type", required = false, defaultValue = "direct/") String type) {
DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory.newDefaultCredentialProvider("E2", "C6");

// Endpoint以华东1(杭州)为例,其他Region请按实际情况填写。
String endpoint = "oss-cn-hangzhou.aliyuncs.com";
// 填写Bucket名称。
String bucket = "shop";
// 填写Host名称,格式为https://bucketname.endpoint。
String host = "https://shop.oss-cn-hangzhou.aliyuncs.com";
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
String callbackUrl = "https://.../platform-admin/sys/oss/callback";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
String dir = type;

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
try {
long expireTime = 60;
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 accessId = credentialsProvider.getCredentials().getAccessKeyId();
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);

Map<String, String> 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));

JSONObject jasonCallback = new JSONObject();
jasonCallback.put("callbackUrl", callbackUrl);
jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
respMap.put("callback", base64CallbackBody);

return RestResponse.ok(respMap);
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}

return null;
}

设置callback返回值

1
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
@PostMapping("/callback")
public void callback(@RequestBody String body, HttpServletRequest request, HttpServletResponse response) throws IOException {
log.info("callback:{}", body);
String link = "";
String decode = URLDecoder.decode(body);
System.out.println("decode = " + decode);
String[] split = decode.split("&");
for (String string : split) {
System.out.println("key = " + string.split("=")[0] + " \tvalue = " + string.split("=")[1]);
if (string.split("=")[0].equals("filename")) {
link = string.split("=")[1];
}
break;
}
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
log.info("headerName:{},headerValue:{}", headerName, headerValue);
}
// 创建OSSClient实例。

if (StringUtils.isNotBlank(link)) {
SysOssEntity ossEntity = new SysOssEntity();
ossEntity.setUrl("https://...hangzhou.aliyuncs.com/"+link);
ossEntity.setCreateDate(new Date());
ossEntity.setCreateUserId("11");
ossEntity.setCreateUserOrgNo("01");
sysOssService.save(ossEntity);
}

JSONObject respMap = new JSONObject();
respMap.put("success", StringUtils.isNotBlank(link));
respMap.put("code", StringUtils.isNotBlank(link)?0:1);
respMap.put("msg", StringUtils.isNotBlank(link)?"操作成功":"操作失败");
Map<String, String> data = new HashMap<String, String>();
data.put("OssUrl", link);
respMap.put("data", data);
response.getOutputStream().write(respMap.toJSONString().getBytes(StandardCharsets.UTF_8));
response.getOutputStream().flush();
}

Vue上传组件

1
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
<template>
<span>
<el-upload class="avatar-uploader el-upload-text" :action='url'
:show-file-list="false" :data="ossData"
:on-success="handleVideoSuccess1" :before-upload="beforeUploadVideo1"
:on-progress="uploadVideoProcess"
:on-error="uploadFail"
>
<el-button style="margin: 20px 0" type="primary"><i
class="el-icon-plus avatar-uploader-icon"></i>{{ buttonLable }}
</el-button>
</el-upload>
<el-progress v-if="uploadFlag && videoUploadPercent > 0 "
</span>
</template>
<script>
export default {
props: {
buttonLable: {
type: String,
default: '上传',
required: true
},
uploadType: {
type: String,
default: 'direct/'
}
},
data () {
return {
url: 'https://....oss-cn-hangzhou.aliyuncs.com',
videoUploadPercent: 0,
uploadFlag: false,
ossData: {
policy: '',
signature: '',
key: '',
OSSAccessKeyId: '',
dir: '',
host: '',
callback: ''
}
}
},
methods: {
getOssToken (val, file) {
return new Promise(async (resolve, reject) => {
this.$http({
url: `/sys/oss/ossToken`,
method: 'get',
params: {
'type': val
}
}).then(({data}) => {
let newStr = file.name.replace(/\s/g, '')
this.ossData.OSSAccessKeyId = data.data.accessid
this.ossData.callback = data.data.callback
this.ossData.dir = data.data.dir
this.ossData.policy = data.data.policy
this.ossData.host = data.data.host
this.ossData.signature = data.data.signature
this.ossData.expire = data.data.expire
this.ossData.key = 'direct/' + this.getUUID() + newStr
resolve(true)
}).catch(err => {
console.log(err)
// eslint-disable-next-line prefer-promise-reject-errors
reject(false)
})
})
},

getUUID () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
})
},
async beforeUploadVideo1 (file) {
// 同步等待
await this.getOssToken('direct/', file)
},
uploadVideoProcess (event, file, fileList) {
this.uploadFlag = true
this.videoFlag = true
this.videoUploadPercent = parseInt(file.percentage.toFixed(0))
},
handleVideoSuccess1 ({data}, file) {
this.videoUploadPercent = 100
this.uploadFlag = false
console.log(data, 'uploadVideoProcess')
this.$emit('getOssUrl', this.url + '/' + data.OssUrl)
this.$message({type: 'success', message: '上传成功'})
},
uploadFail () {
this.uploadFlag = false
this.videoUploadPercent = 0
}
}
}
</script>


如何通过Java在服务端签名直传并设置上传回调_对象存储(OSS)-阿里云帮助中心

使用阿里云OSS的服务端签名后直传功能 - Johnson木木 - 博客园