概述
uniapp使用临时签名上传腾讯云oss对象储存方案,支持小程序、app、h5;
前端不依赖腾讯云SDK工具类;
后端使用python实现,需要安装qcloud-python-sts;
其中计算文件md5值使用了条件编译,因为每个环境获取ArrayBuffer方案不一样都不兼容;
pip install qcloud-python-sts==3.1.6
那些踩过的坑
- 官方方案小程序SDK,但是小程序SDK在APP环境由于无法获取file://类型地址的文件;
- 官方方案JS-SDK,此方案由于要使用file对象然而APP端无法使用Blob工具类;
uniapp实现
import SparkMD5 from "spark-md5"; //md5工具类 使用npm安装
import {
api_getBucketAndRegionSelf
} from "@/api/common";//此处是后端拿临时密钥等信息的
import {
OSS_BASE_URL
} from '../config';//此处获取到的是自定义的域名
/**
* 上传文件 路径为 年/月/日/keypath/fileMD5.xx
* @param {string} keyPath 文件分路径(可留空) 例:users/user1
* @param {string} file 文件路径
* @returns {Promise<string|null>} 文件上传后的URL
*/
async function putObjectAutoPath(keyPath, file) {
console.log("getMD5FileName")
try {
console.log("getMD5FileName")
const md5FileName = await getMD5FileName(file);
const datePath = getDatePath();
const uploadPath = `${datePath}${keyPath.trim() ? `${keyPath.trim()}/` : ''}${md5FileName}`;
console.log("上传路径为:" + uploadPath);
console.log("图片路径=>" + file);
const res = await api_getBucketAndRegionSelf(uploadPath);
console.log(res)
const formData = {
key: res.data.cosKey,
policy: res.data.policy, // 这个传 policy 的 base64 字符串
success_action_status: 200,
'q-sign-algorithm': res.data.qSignAlgorithm,
'q-ak': res.data.qAk,
'q-key-time': res.data.qKeyTime,
'q-signature': res.data.qSignature,
'x-cos-security-token': res.data.securityToken
};
const uploadResult = await uploadFile('https://' + res.data.cosHost, file, formData);
console.log('上传成功:', uploadResult);
return OSS_BASE_URL + res.data.cosKey;
} catch (error) {
console.error('上传失败:', error);
throw error;
}
}
/**
* 生成文件夹路径 [时间命名]
* @returns {string} keyPath
*/
function getDatePath() {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `/${year}/${month}/${day}/`;
}
/**
* 计算文件的 MD5 哈希值
* @param {File|string} file 文件对象或文件路径
* @returns {Promise<string>} MD5 哈希值
*/
function calculateMD5(file) {
return new Promise((resolve, reject) => {
// 在 Web 环境下使用 FileReader
//#ifdef H5
console.log("执行md5值计算H5", file);
const xhr = new XMLHttpRequest();
xhr.open('GET', file, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const reader = new FileReader();
reader.onload = (e) => {
const binary = e.target.result;
const spark = new SparkMD5.ArrayBuffer();
spark.append(binary);
resolve(spark.end());
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
} else {
reject(new Error('Failed to fetch blob'));
}
};
xhr.onerror = reject;
xhr.send();
//#endif
//#ifndef H5
//#ifndef APP-PLUS
console.log("执行md5值计算MP");
const fs = uni.getFileSystemManager();
fs.readFile({
filePath: file, // 文件路径
encoding: 'base64',
success: (res) => {
const binary = uni.base64ToArrayBuffer(res.data); // 将 base64 转换为 ArrayBuffer
const spark = new SparkMD5.ArrayBuffer();
spark.append(binary);
resolve(spark.end());
},
fail: reject,
});
//#endif
//#endif
//#ifdef APP-PLUS
console.log("执行md5值计算APP");
plus.io.resolveLocalFileSystemURL(file, (entry) => {
entry.file((fileObj) => {
const reader = new plus.io.FileReader();
reader.readAsDataURL(file);
reader.onloadend = (evt) => {
const binary = uni.base64ToArrayBuffer(evt.target
.result); // 将 base64 转换为 ArrayBuffer
const spark = new SparkMD5.ArrayBuffer();
spark.append(binary);
resolve(spark.end());
};
reader.onerror = reject;
});
}, reject);
//#endif
});
}
/**
* 获取文件MD5名称
* @param {string} file 文件路径
* @returns {Promise<string>} MD5文件名
*/
async function getMD5FileName(file) {
const md5 = await calculateMD5(file);
console.log(md5)
return;
const fileType = file.substring(file.lastIndexOf("."));
return `${md5}${fileType}`;
}
/**
* 文件上传
* @param {Object} url
* @param {Object} filePath
* @param {Object} formData
* @returns {Promise<string|null>}
*/
function uploadFile(url, filePath, formData) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: url,
filePath: filePath,
name: 'file',
formData: formData,
success: (res) => {
if (res.statusCode === 200) {
resolve(res);
} else {
reject(new Error(`上传失败,状态码:${res.statusCode}, 响应信息:${res.data}`));
}
},
error: (err) => {
console.log("图片上传失败=》" + res)
reject(err);
},
});
});
}
export {
putObjectAutoPath
};
python实现的
#!/usr/bin/env python
# coding=utf-8
import json
from sts.sts import Sts
import hashlib
import hmac
import base64
import time
from datetime import datetime, timedelta
#腾讯云 secret_id
secret_id = ''
#腾讯云 secret_key
secret_key = ''
#bucketId 储存桶ID
bucket = ''
#存储桶所在地域
region = ''
def get_temporary_credential():
"""
获取临时密钥
:return:
"""
config = {
# 请求URL,域名部分必须和domain保持一致
# 使用外网域名时:https://sts.tencentcloudapi.com/
# 使用内网域名时:https://sts.internal.tencentcloudapi.com/
# 'url': 'https://sts.tencentcloudapi.com/',
# # 域名,非必须,默认为 sts.tencentcloudapi.com
# # 内网域名:sts.internal.tencentcloudapi.com
# 'domain': 'sts.tencentcloudapi.com',
# 临时密钥有效时长,单位是秒
'duration_seconds': 1800,
'secret_id': secret_id,
# 固定密钥
'secret_key': secret_key,
# 设置网络代理
# 'proxy': {
# 'http': 'xx',
# 'https': 'xx'
# },
# 换成你的 bucket
'bucket': bucket,
# 换成 bucket 所在地区
'region': region,
# 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径
# 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
'allow_prefix': ['*'],
# 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
'allow_actions': [
# 简单上传
'name/cos:PutObject',
'name/cos:PostObject',
# 分片上传
'name/cos:InitiateMultipartUpload',
'name/cos:ListMultipartUploads',
'name/cos:ListParts',
'name/cos:UploadPart',
'name/cos:CompleteMultipartUpload'
],
# # 临时密钥生效条件,关于condition的详细设置规则和COS支持的condition类型可以参考 https://cloud.tencent.com/document/product/436/71306
# "condition": {
# "ip_equal":{
# "qcs:ip":[
# "10.217.182.3/24",
# "111.21.33.72/24",
# ]
# }
# }
}
try:
sts = Sts(config)
response = sts.get_credential()
print(response)
# 添加新的属性
response['bucket'] = bucket
response['region'] = region
return response
except Exception as e:
raise Exception("腾讯OSS临时密钥获取异常!")
def get_bucketAndRegion():
"""
获取bucket 桶id 和region地域
:return:
"""
data = {
"bucket": bucket,
"region": region
}
return data
def get_temporary_credential_self_upload(keyPath):
"""
获取腾讯云oss凭证 适用于POST上传请求【不依赖腾讯SDK】
"""
#获取临时签名
credentials_data = get_temporary_credential().get("credentials")
tmp_secret_id = credentials_data.get("tmpSecretId")
tmp_secret_key = credentials_data.get("tmpSecretKey")
session_token = credentials_data.get("sessionToken")
# 开始计算凭证
cos_host = f"{bucket}.cos.{region}.myqcloud.com"
cos_key = keyPath
now = int(time.time())
exp = now + 900
q_key_time = f"{now};{exp}"
q_sign_algorithm = 'sha1'
# 生成上传要用的 policy
policy = {
'expiration': (datetime.utcfromtimestamp(exp)).isoformat() + 'Z',
'conditions': [
{'q-sign-algorithm': q_sign_algorithm},
{'q-ak': tmp_secret_id},
{'q-sign-time': q_key_time},
{'bucket': bucket},
{'key': cos_key},
]
}
policy_encoded = base64.b64encode(json.dumps(policy).encode()).decode()
# 步骤一:生成 SignKey
sign_key = hmac.new(tmp_secret_key.encode(), q_key_time.encode(), hashlib.sha1).hexdigest()
# 步骤二:生成 StringToSign
string_to_sign = hashlib.sha1(json.dumps(policy).encode()).hexdigest()
# 步骤三:生成 Signature
q_signature = hmac.new(sign_key.encode(), string_to_sign.encode(), hashlib.sha1).hexdigest()
return {
'cosHost': cos_host,
'cosKey': cos_key,
'policy': policy_encoded,
'qSignAlgorithm': q_sign_algorithm,
'qAk': tmp_secret_id,
'qKeyTime': q_key_time,
'qSignature': q_signature,
'securityToken': session_token # 如果 SecretId、SecretKey 是临时密钥,要返回对应的 sessionToken 的值
}
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » uniapp+python使用临时签名上传腾讯云oss对象储存方案
发表评论 取消回复