概述
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";
import {api_getBucketAndRegionSelf
} from "@/api/common";
import {OSS_BASE_URL
} from '../config';
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, 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;}
}
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}/`;
}
function calculateMD5(file) {return new Promise((resolve, reject) => {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();console.log("执行md5值计算MP");const fs = uni.getFileSystemManager();fs.readFile({filePath: file, encoding: 'base64',success: (res) => {const binary = uni.base64ToArrayBuffer(res.data); const spark = new SparkMD5.ArrayBuffer();spark.append(binary);resolve(spark.end());},fail: reject,});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); const spark = new SparkMD5.ArrayBuffer();spark.append(binary);resolve(spark.end());};reader.onerror = reject;});}, reject);});
}
async function getMD5FileName(file) {const md5 = await calculateMD5(file);console.log(md5)return;const fileType = file.substring(file.lastIndexOf("."));return `${md5}${fileType}`;
}
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实现的
import jsonfrom sts.sts import Sts
import hashlib
import hmac
import base64
import time
from datetime import datetime, timedelta
secret_id = ''
secret_key = ''
bucket = ''
region = ''def get_temporary_credential():"""获取临时密钥:return:"""config = {'duration_seconds': 1800,'secret_id': secret_id,'secret_key': secret_key,'bucket': bucket,'region': region,'allow_prefix': ['*'],'allow_actions': ['name/cos:PutObject','name/cos:PostObject','name/cos:InitiateMultipartUpload','name/cos:ListMultipartUploads','name/cos:ListParts','name/cos:UploadPart','name/cos:CompleteMultipartUpload'],}try:sts = Sts(config)response = sts.get_credential()print(response)response['bucket'] = bucketresponse['region'] = regionreturn responseexcept Exception as e:raise Exception("腾讯OSS临时密钥获取异常!")def get_bucketAndRegion():"""获取bucket 桶id 和region地域:return:"""data = {"bucket": bucket,"region": region}return datadef 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 = keyPathnow = int(time.time())exp = now + 900q_key_time = f"{now};{exp}"q_sign_algorithm = 'sha1'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()sign_key = hmac.new(tmp_secret_key.encode(), q_key_time.encode(), hashlib.sha1).hexdigest()string_to_sign = hashlib.sha1(json.dumps(policy).encode()).hexdigest()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 }