微信小程序可以将图片、文档、视频等文件上传到OSS,实现文件的云端存储和分发。
方案概览
微信小程序上传文件到OSS的过程如下:
要实现微信小程序上传文件到OSS,只需两步:
配置ECS:在ECS,创建一个实例,用于从STS服务获取一个临时访问凭证,然后使用临时访问凭证为微信小程序生成上传文件到OSS所需的凭证签名。
配置微信小程序:在小程序平台,使用Bucket域名配置微信小程序的合法域名,确保小程序向OSS发送文件的请求不会被微信拦截;在微信小程序端,实现从ECS获取签名并使用签名上传文件到OSS的功能。
操作步骤
步骤一:配置ECS
在实际部署时,如果您已经有自己的ECS服务器,则无需创建该ECS实例,可直接进行第2步ECS绑定RAM角色。
一. 创建并连接ECS实例
操作一:创建ECS实例
请您进入自定义购买页面,并根据如下各模块的内容,创建或选择购买ECS实例所需的基础资源。
选择地域 & 付费类型
根据业务需求,选择合适的付费类型。本文选择按量付费模式,此模式操作相对灵活。
基于业务场景对时延的要求,选择地域。通常来说离ECS实例的物理距离越近,网络时延越低,访问速度越快。本文以选择华东1(杭州)为例。
创建专有网络VPC & 交换机
创建VPC时,请您选择和ECS相同的地域,并根据业务需求规划网段。本文以创建华东1(杭州)地域的VPC和交换机为例。创建完毕后返回ECS购买页,刷新并选择VPC及交换机。
说明创建VPC时,可同时创建交换机。
选择规格 & 镜像
选择实例的规格及镜像,镜像为实例确定安装的操作系统及版本。本文选择的实例规格为
ecs.e-c1m1.large
,在满足测试需求的同时,价格较为实惠。镜像为公共镜像Alibaba Cloud Linux 3.2104 LTS 64位
。
选择存储
为ECS实例选择系统盘,并按需选择数据盘。本文实现简单Web系统搭建,只需要系统盘存储操作系统,无需数据盘。
绑定公网IP
本实例需要支持公网访问。为了简化操作,本文选择直接为实例分配公网IP。您也可以在创建实例后,为实例绑定弹性公网IP,具体操作,请参见将EIP绑定至ECS实例。
说明若未绑定公网IP,将无法使用SSH或RDP通过公网直接访问实例,也无法通过公网验证实例中Web服务的搭建。
本文选择按使用流量的带宽计费模式。此模式只需为所消耗的公网流量付费。更多信息,请参见公网带宽计费。
创建安全组
为实例创建安全组。安全组是一种虚拟网络防火墙,能够控制ECS实例的出入流量。创建时,需要设置放行以下指定端口,便于后续访问ECS实例。
端口范围:SSH(22)、RDP(3389)、HTTP(80)、HTTPS(443)。
说明端口范围处选中的是ECS实例上运行的应用需开放的端口。
此处创建的安全组默认设置0.0.0.0/0作为源的规则。0.0.0.0/0表示允许全网段设备访问指定的端口,如果您知道请求端的IP地址,建议后续设置为具体的IP范围。具体操作,请参见修改安全组规则。
创建密钥对
密钥对可作为登录时证明个人身份的安全凭证,创建完成后,必须下载私钥,以供后续连接ECS实例时使用。创建完毕后返回ECS购买页,刷新并选择密钥对。
root
具有操作系统的最高权限,使用root
作为登录名可能会导致安全风险,建议您选择ecs-user
作为登录名。说明创建密钥对后,私钥会自动下载,请您关注浏览器的下载记录,保存
.pem
格式的私钥文件。
创建并查看ECS实例
创建或选择好ECS实例所需的基础资源后,勾选《云服务器ECS服务条款》、《云服务器ECS退订说明》,单击确认下单。在提示成功的对话框中,单击管理控制台,即可在控制台查看到创建好的ECS实例。请您保存以下数据,以便在后续操作中使用。
实例ID:便于在实例列表中查询到该实例。
地域:便于在实例列表中查询到该实例。
公网IP地址:便于在后续使用ECS实例时,做Web服务的部署结果验证。
操作二:连接ECS实例
在云服务器ECS控制台的实例列表页面,根据地域、实例ID找到创建好的ECS实例,单击操作列下的远程连接。
在远程连接对话框中,单击通过Workbench远程连接对应的立即登录。
在登录实例对话框中,选择认证方式为SSH密钥认证,用户名为
ecs-user
,输入或上传创建密钥对时下载的私钥文件,单击确定,即可登录ECS实例。说明私钥文件在创建密钥对时自动下载到本地,请您关注浏览器的下载记录,查找
.pem
格式的私钥文件。显示如下页面后,即说明您已成功登录ECS实例。
二. ECS绑定RAM角色
进入RAM访问控制的创建角色页面。
在创建角色页面勾选阿里云服务,单击下一步。
勾选普通服务角色,填写角色名称并选择受信服务为云服务器后,单击完成,即可完成创建。
创建完毕后单击为角色授权 > 新增授权,在权限策略中勾选
ALiyunOSSFullAccess
权限后单击确认新增授权。说明如果您熟悉访问控制权限,那么也可以直接进入创建权限策略。这样能够进行更精准的自定义授权,之后再将自定义权限授予所创建的RAM角色。
进入云服务器ECS的实例页面,在页面上方选择ECS实例所处地域,然后在实例页面单击目标实例右侧按钮,单击授予/收回RAM角色。
在授予/收回RAM角色弹出框选择目标RAM角色,完成ECS绑定RAM角色。
三. ECS服务端配置依赖
请执行以下命令安装获取临时访问凭证所需要的依赖项。
Python
执行以下命令,安装Credentials工具。
sudo pip install oss2
sudo pip install alibabacloud_credentials
Java
在Maven项目中,导入以下依赖。
<!-- https://mvnrepository.com/artifact/com.aliyun/credentials-java -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>credentials-java</artifactId>
<version>0.3.4</version>
</dependency>
<dependency>
<groupId>com.aliyun.kms</groupId>
<artifactId>kms-transfer-client</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
四. ECS服务端计算签名
您可以参考以下代码,在ECS服务端进行V4签名的计算工作。关于policy表单域详细配置信息,请参见policy表单域。
Python
请参考如下代码来完成Python服务端的V4签名计算。
from flask import Flask, jsonify
import base64
import hmac
import hashlib
import os
import datetime
import json
import time
from alibabacloud_credentials.client import Client as CredClient
from alibabacloud_credentials.models import Config as CredConfig
app = Flask(__name__)
def hmacsha256(key, data):
"""
计算HMAC-SHA256哈希值的函数
:param key: 用于计算哈希的密钥,字节类型
:param data: 要进行哈希计算的数据,字符串类型
:return: 计算得到的HMAC-SHA256哈希值,字节类型
"""
try:
mac = hmac.new(key, data.encode(), hashlib.sha256)
hmacBytes = mac.digest()
return hmacBytes
except Exception as e:
raise RuntimeError(f"Failed to calculate HMAC-SHA256 due to {e}")
@app.route('/generate_signature', methods=['GET'])
def generate_signature():
"""
处理生成签名信息的请求,执行相关逻辑流程,包括获取环境变量、创建策略、构造待签名字符串、计算签名等操作,
并返回生成的签名信息。
:return: JSON格式的响应,包含签名信息的字典,格式如下:
{
"policy": "policy字符串",
"x-oss-signature-version": "OSS4-HMAC-SHA256",
"x-oss-credential": "accesskeyid/日期/cn-hangzhou/oss/aliyun_v4_request",
"x-oss-date": "请求时间",
"signature": "签名",
"security_token": "安全令牌"
}
"""
credentialConfig = CredConfig(
# 固定值无需更改
type='ecs_ram_role',
# 请替换为ECS扮演的角色名称
role_name='role_name'
)
credentialsClient = CredClient(credentialConfig)
credential = credentialsClient.get_credential()
# 获取AK
accesskeyid = credential.access_key_id
# 获取SK
accesskeysecret = credential.access_key_secret
# 获取Token
security_token = credential.security_token
now = int(time.time())
# 将时间戳转换为datetime对象
dt_obj = datetime.datetime.utcfromtimestamp(now)
# 在当前时间增加3小时,设置为请求的过期时间
dt_obj_plus_3h = dt_obj + datetime.timedelta(hours=3)
# 请求时间
dt_obj_1 = dt_obj.strftime('%Y%m%dT%H%M%S') + 'Z'
# 请求日期
dt_obj_2 = dt_obj.strftime('%Y%m%d')
# 请求过期时间
expiration_time = dt_obj_plus_3h.strftime('%Y-%m-%dT%H:%M:%S.000Z')
# 步骤1:创建policy。
policy = {
"expiration": expiration_time,
"conditions": [
{"bucket": "bucket_name"}, #请替换为目标Bucket名称
{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
{"x-oss-credential": f"{accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request"},
{"x-oss-security-token": security_token},
{"x-oss-date": dt_obj_1},
]
}
policy_str = json.dumps(policy).strip()
# 步骤2:构造待签名字符串(StringToSign)
stringToSign = base64.b64encode(policy_str.encode()).decode()
# 步骤3:计算SigningKey
dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).encode(), dt_obj_2)
dateRegionKey = hmacsha256(dateKey, "cn-hangzhou")
dateRegionServiceKey = hmacsha256(dateRegionKey, "oss")
signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request")
# 步骤4:计算Signature
result = hmacsha256(signingKey, stringToSign)
signature = result.hex()
return jsonify({
"policy": stringToSign, #表单域
"x_oss_signature_version": "OSS4-HMAC-SHA256", #指定签名的版本和算法,固定值为OSS4-HMAC-SHA256
"x_oss_credential": f"{accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request", #指明派生密钥的参数集
"x_oss_date": dt_obj_1, #请求的时间
"signature": signature, #签名认证描述信息
"security_token": security_token #安全令牌
})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
Java
请参考如下代码来完成Java服务端的V4签名计算。您也可以下载完整的Spring Boot示例工程upload_server.zip运用到您的项目当中。
获取签名信息的工具类示例。
package com.example.demo.util; import com.aliyun.credentials.models.CredentialModel; import com.aliyun.oss.common.auth.Credentials; import com.aliyun.oss.common.auth.CredentialsProvider; import com.aliyun.oss.common.auth.DefaultCredentials; import com.aliyun.oss.common.utils.BinaryUtil; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; // 用于生成阿里云POST文件上传所需签名及相关配置信息的工具类 public class GenerateSignature { // 生成签名及相关配置信息的主要方法 public String getSignature() throws JsonProcessingException { com.aliyun.credentials.models.Config config = new com.aliyun.credentials.models.Config(); config.setType("ecs_ram_role"); //固定值无需更改 config.setRoleName("roleName"); //请替换为ECS扮演的角色名称 final com.aliyun.credentials.Client credentialsClient = new com.aliyun.credentials.Client(config); // 创建一个匿名内部类实现CredentialsProvider接口,用于提供阿里云OSS操作所需的凭证信息 CredentialsProvider credentialsProvider = new CredentialsProvider() { @Override public void setCredentials(Credentials credentials) { } @Override public Credentials getCredentials() { CredentialModel credential = credentialsClient.getCredential(); // 返回accessKeyId、secretAccessKey和securityToken return new DefaultCredentials(credential.getAccessKeyId(), credential.getAccessKeySecret(), credential.getSecurityToken()); } }; String accessKeyId = credentialsProvider.getCredentials().getAccessKeyId(); //获取AK String secretAccessKey = credentialsProvider.getCredentials().getSecretAccessKey(); //获取SK String securityToken = credentialsProvider.getCredentials().getSecurityToken(); //获取Token // 格式化请求日期 long now = System.currentTimeMillis() / 1000; ZonedDateTime dtObj = ZonedDateTime.ofInstant(Instant.ofEpochSecond(now), ZoneId.of("UTC")); ZonedDateTime dtObjPlus3h = dtObj.plusHours(3); //在当前时间增加3小时,设置为请求的过期时间 //请求时间 DateTimeFormatter dtObj1Formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"); String dtObj1 = dtObj.format(dtObj1Formatter); //请求日期 DateTimeFormatter dtObj2Formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); String dtObj2 = dtObj.format(dtObj2Formatter); //请求过期时间 DateTimeFormatter expirationTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); String expirationTime = dtObjPlus3h.format(expirationTimeFormatter); // 步骤1:创建policy String policy = "{" + "\"expiration\": \"" + expirationTime + "\"," + "\"conditions\": [" + "{\"bucket\": \"cjl2\"}," + //请替换为目标Bucket名称 "{\"x-oss-signature-version\": \"OSS4-HMAC-SHA256\"}," + "{\"x-oss-credential\": \"" + accessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request\"}," + "{\"x-oss-security-token\": \"" + securityToken + "\"}," + "{\"x-oss-date\": \"" + dtObj1 + "\"}" + "]" + "}"; // 步骤2:构造待签名字符串(StringToSign) String stringToSign = new String(Base64.encodeBase64(policy.getBytes())); // 步骤3:计算SigningKey byte[] dateKey = hmacsha256(("aliyun_v4" + secretAccessKey).getBytes(), dtObj2); byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou"); byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss"); byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request"); // 步骤4:计算Signature byte[] result = hmacsha256(signingKey, stringToSign); String signature = BinaryUtil.toHex(result); Map<String, String> messageMap = new HashMap<>(); messageMap.put("security_token", securityToken); //安全令牌 messageMap.put("signature", signature); //签名认证描述信息 messageMap.put("x_oss_date", dtObj1); //请求的时间 messageMap.put("x_oss_credential", accessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request"); //指明派生密钥的参数集 messageMap.put("x_oss_signature_version", "OSS4-HMAC-SHA256"); //指定签名的版本和算法,固定值为OSS4-HMAC-SHA256 messageMap.put("policy", stringToSign); //表单域 ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.writeValueAsString(messageMap); } // 使用HMAC-SHA256算法计算给定密钥和数据的哈希值的静态方法 public static byte[] hmacsha256(byte[] key,String data){ try { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(secretKeySpec); byte[] hmacBytes = mac.doFinal(data.getBytes()); return hmacBytes; }catch (Exception e){ throw new RuntimeException("Failed to calculate HMAC-SHA256", e); } } }
访问接口示例。
package com.example.demo.controller; import com.example.demo.util.GenerateSignature; import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class VxController { @GetMapping("/generate_signature") public String generate_signature() throws JsonProcessingException { GenerateSignature generateSignature = new GenerateSignature(); return generateSignature.getSignature(); } }
步骤二:配置微信小程序
为了确保小程序向OSS发送文件的请求不会被微信拦截,在小程序平台,使用Bucket域名配置微信小程序的合法域名。
在微信小程序端,使用从ECS服务端获取到的V4签名凭证信息,发送请求上传文件到OSS。完整示例工程及操作步骤如下。
下载小程序端示例工程uploadoss.zip并部署。
请编辑位于此路径下的 "uploadoss/pages/index/index.js"文件,完成小程序端上传文件相关配置。
自定义上传文件名称。
填写ECS实例服务端接口地址。
进入Bucket列表,选择目标Bucket后在Bucket内的左侧导航栏,单击概览,然后在访问端口区域复制Bucket域名。并将其填写到如图所示的标注位置。
结果验证
编译运行后,在微信小程序界面单击上传文件。
在Bucket列表页面,选择您之前创建的用来存放用户上传文件的Bucket并打开,您可以在上传列表中看到您通过小程序上传的文件。