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 的值}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/866174.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

堆结构、堆排序

堆 是完全二叉树&#xff0c;类似这种样式的 而这种有右子节点&#xff0c;没左子节点的就不是完全二叉树 分为大根堆和小根堆 大根堆是二叉树里每一颗子树的父节点都是这颗子树里最大的&#xff0c;即每一棵子树最大值是头节点的值 小根堆相反 把数组中从0开始的一段数人…

Spring Bean生命周期

Bean生命周期&#xff1a; 创建 Bean 的实例&#xff1a;Bean 容器首先会找到配置文件中的 Bean 定义&#xff0c;然后使用 Java 反射 API 来创建 Bean 的实例。 Bean 属性赋值/填充&#xff1a;为 Bean 设置相关属性和依赖&#xff0c;例如Autowired 等注解注入的对象、Value…

强强联合!当RAG遇到长上下文,滑铁卢大学发布LongRAG,效果领先GPT-4 Turbo 50%

过犹不及——《论语先进》 大学考试时&#xff0c;有些老师允许带备cheet sheet&#xff08;忘纸条&#xff09;,上面记着关键公式和定义,帮助我们快速作答提高分数。传统的检索增强生成(RAG)方法也类似,试图找出精准的知识片段来辅助大语言模型(LLM)。 但这种方法其实有问题…

React@16.x(48)路由v5.x(13)源码(5)- 实现 Switch

目录 1&#xff0c;原生 Switch 的渲染内容2&#xff0c;实现 1&#xff0c;原生 Switch 的渲染内容 对如下代码来说&#xff1a; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; function News() {return <div className"p…

MySQL体系架构

1.1.MySQL的分支与变种 MySQL变种有好几个&#xff0c;主要有三个久经考验的主流变种&#xff1a;Percona Server&#xff0c;MariaDB和 Drizzle。它们都有活跃的用户社区和一些商业支持&#xff0c;均由独立的服务供应商支持。同时还有几个优秀的开源关系数据库&#xff0c;值…

JVM专题之Java对象内存模型

一个Java对象在内存中包括3个部分: 对象头、实例数据和对齐填充 数据 内存 -- CPU 寄存器 -127 补码 10000001 - 11111111 32位的处理器 一次能够去处理32个二进制位 4字节的数据 64位操作系统 8字节 2的64次方的寻址空间 指针压缩…

从零开始:大模型简介与应用|实战系列

实战系列 相信有不少伙伴对大模型有所耳闻&#xff0c;但也是一知半解&#xff0c;也许你知道很重要可以为自己的工作提供帮助但是不知道该如何结合&#xff0c;又或是转行的过程中并不知道从何入手&#xff0c;网上的教程要么不包含具体的操作步骤要么需要好几篇合在一起才能…

鸿蒙小案例-首选项工具类

一个简单的首选项工具类 主要提供方法 初始化 init()方法建议在EntryAbility-》onWindowStageCreate 方法中使用 没多少东西&#xff0c;放一下测试代码 import { PrefUtil } from ./PrefUtil; import { promptAction } from kit.ArkUI;Entry Component struct PrefIndex {St…

在window上搭建docker

1、打开Hyper-V安装 在地址栏输入控制面板&#xff0c;然后回车 勾选Hyper-V安装&#xff0c;如果没有找到Hyper-V&#xff0c;那么请走第2步 2、如果没有Hyper-V(可选&#xff09;第一步无法打开 家庭版本需要开启Hyper-V 创建一个文本文档&#xff0c;后缀名称为.bat.名称…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【密钥生成介绍及算法规格】

密钥生成介绍及算法规格 当业务需要使用HUKS生成随机密钥&#xff0c;并由HUKS进行安全保存时&#xff0c;可以调用HUKS的接口生成密钥。 注意&#xff1a; 密钥别名中禁止包含个人数据等敏感信息。 开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harm…

【C++】 ——【模板初阶】——基础详解

目录 1. 泛型编程 1.1 泛型编程的概念 1.2 泛型编程的历史与发展 1.3 泛型编程的优势 1.4 泛型编程的挑战 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6 函数模板的特化 2.7 函数模板的使…

html+css+js淘宝商品界面

点击商品&#xff0c;alert弹出商品ID 图片使用了占位符图片&#xff0c;加载可能会慢一点 你可以把它换成自己的图片&#x1f603;源代码在图片后面 效果图 源代码 <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"…

时空预测+特征分解!高性能!EMD-Transformer和Transformer多变量交通流量时空预测对比

时空预测特征分解&#xff01;高性能&#xff01;EMD-Transformer和Transformer多变量交通流量时空预测对比 目录 时空预测特征分解&#xff01;高性能&#xff01;EMD-Transformer和Transformer多变量交通流量时空预测对比效果一览基本介绍程序设计参考资料 效果一览 基本介绍…

番外篇 | YOLOv8改进之即插即用全维度动态卷积ODConv + 更换Neck网络为GFPN

前言:Hello大家好,我是小哥谈。本文所做出的改进是在YOLOv8中引入即插即用全维度动态卷积ODConv和更换Neck网络为GFPN,希望大家学习之后能够有所收获~!🌈 目录 🚀1.基础概念 🚀2.网络结构 🚀3.添加步骤 🚀4.改进方法 🍀🍀步骤1:block.py文件修改…

在TkinterGUI界面显示WIFI网络摄像头(ESP32s3)视频画面

本实验结合了之前写过的两篇文章Python调用摄像头&#xff0c;实时显示视频在Tkinter界面以及ESP32 S3搭载OV2640摄像头释放热点&#xff08;AP&#xff09;工作模式–Arduino程序&#xff0c;当然如果手头有其他可以获得网络摄像头的URL即用于访问摄像头视频流的网络地址&…

解析MySQL核心技术:视图的实用指南与实践案例

在数据库管理中&#xff0c;MySQL视图&#xff08;View&#xff09;是一种强大的功能&#xff0c;利用它可以简化复杂查询、提高数据安全性以及增强代码的可维护性。本篇文章将详细介绍MySQL视图的相关知识&#xff0c;包括视图的创建、修改、删除、使用场景以及常见的最佳实践…

Python学生信息管理系统(完整代码)

引言&#xff1a;&#xff08;假装不是一个大学生课设&#xff09;在现代教育管理中&#xff0c;学生管理系统显得尤为重要。这种系统能够帮助教育机构有效地管理学生资料、成绩、出勤以及其他教育相关活动&#xff0c;从而提高管理效率并减少人为错误。通过使用Python&#xf…

亚马逊跟卖选品erp采集,跟卖卖家的选品利器,提升选品效率!

今天给亚马逊跟卖卖家&#xff0c;分享我现在在用的两种选品方式&#xff0c;做个铺货或者是跟卖都可以&#xff0c;是不是很多卖家选品现在都是亚马逊前端页面或是新品榜单选择产品跟卖&#xff0c;这样找品这就相当于大海捞针&#xff0c;而且新品榜单的产品你能看到那其他卖…

经典卷积神经网络 LeNet

一、实例图片 #我们传入的是28*28&#xff0c;所以加了padding net nn.Sequential(nn.Conv2d(1, 6, kernel_size5, padding2), nn.Sigmoid(),nn.AvgPool2d(kernel_size2, stride2),nn.Conv2d(6, 16, kernel_size5), nn.Sigmoid(),nn.AvgPool2d(kernel_size2, stride2),nn.Flat…