当前位置: 首页 > news >正文

uniapp+python使用临时签名上传腾讯云oss对象储存方案

概述

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 H5console.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-PLUSconsole.log("执行md5值计算MP");const fs = uni.getFileSystemManager();fs.readFile({filePath: file, // 文件路径encoding: 'base64',success: (res) => {const binary = uni.base64ToArrayBuffer(res.data); // 将 base64 转换为 ArrayBufferconst spark = new SparkMD5.ArrayBuffer();spark.append(binary);resolve(spark.end());},fail: reject,});//#endif//#endif//#ifdef APP-PLUSconsole.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 转换为 ArrayBufferconst 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 jsonfrom 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'] = 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'# 生成上传要用的 policypolicy = {'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()# 步骤一:生成 SignKeysign_key = hmac.new(tmp_secret_key.encode(), q_key_time.encode(), hashlib.sha1).hexdigest()# 步骤二:生成 StringToSignstring_to_sign = hashlib.sha1(json.dumps(policy).encode()).hexdigest()# 步骤三:生成 Signatureq_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 的值}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 脑干出血:揭秘其成因、症状与治疗方法
  • 【C#】函数方法、属性分文件编写
  • 尚品汇-(十四)
  • 个人IP如何做好定位?
  • 基于SpringBoot的休闲娱乐代理售票系统
  • 稀疏之美:在Mojo模型中实现特征的稀疏表示
  • MySQL之备份与恢复和MySQL用户工具(一)
  • 【系统架构设计师】八、系统工程基础知识(系统工程|系统性能)
  • 接口分组:内部调用与第三方调用接口
  • Qt | QPen 类(画笔)
  • 三级_网络技术_04_中小型网络系统总体规划与设计
  • 6、Redis系统-数据结构-01-String
  • 21.《C语言》——【位操作符】
  • Python酷库之旅-第三方库Pandas(009)
  • React-Native中关于图片问题知识总结
  • [笔记] php常见简单功能及函数
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • exif信息对照
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • SegmentFault 2015 Top Rank
  • SpiderData 2019年2月16日 DApp数据排行榜
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • springMvc学习笔记(2)
  • Vue.js源码(2):初探List Rendering
  • 大快搜索数据爬虫技术实例安装教学篇
  • 检测对象或数组
  • 简析gRPC client 连接管理
  • 爬虫模拟登陆 SegmentFault
  • 设计模式 开闭原则
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • Prometheus VS InfluxDB
  • ​linux启动进程的方式
  • # Kafka_深入探秘者(2):kafka 生产者
  • #define用法
  • #FPGA(基础知识)
  • #HarmonyOS:Web组件的使用
  • $GOPATH/go.mod exists but should not goland
  • (03)光刻——半导体电路的绘制
  • (27)4.8 习题课
  • (Java)【深基9.例1】选举学生会
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (SERIES12)DM性能优化
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (七)glDrawArry绘制
  • (四)鸿鹄云架构一服务注册中心
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .NET 5种线程安全集合
  • .Net CoreRabbitMQ消息存储可靠机制
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .net refrector
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET构架之我见