Spaces:
Running
Running
File size: 16,168 Bytes
d5d20be |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
"""
焕影小程序功能服务端的基本工具函数,以类的形式封装
"""
try: # 加上这个try的原因在于本地环境和云函数端的import形式有所不同
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
except ImportError:
try:
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client
except ImportError:
raise ImportError("请下载腾讯云COS相关代码包:pip install cos-python-sdk-v5")
import requests
import datetime
import json
from .error import ProcessError
import os
local_path_ = os.path.dirname(__file__)
class GetConfig(object):
@staticmethod
def hy_sdk_client(Id:str, Key:str):
# 从cos中寻找文件
REGION: str = 'ap-beijing'
TOKEN = None
SCHEME: str = 'https'
BUCKET: str = 'hy-sdk-config-1305323352'
client_config = CosConfig(Region=REGION,
SecretId=Id,
SecretKey=Key,
Token=TOKEN,
Scheme=SCHEME)
return CosS3Client(client_config), BUCKET
def load_json(self, path:str, default_download=False):
try:
if os.path.isdir(path):
raise ProcessError("请输入具体的配置文件路径,而非文件夹!")
if default_download is True:
print(f"\033[34m 默认强制重新下载配置文件...\033[0m")
raise FileNotFoundError
with open(path) as f:
config = json.load(f)
return config
except FileNotFoundError:
dir_name = os.path.dirname(path)
try:
os.makedirs(dir_name)
except FileExistsError:
pass
base_name = os.path.basename(path)
print(f"\033[34m 正在从COS中下载配置文件...\033[0m")
print(f"\033[31m 请注意,接下来会在{dir_name}路径下生成文件{base_name}...\033[0m")
Id = input("请输入SecretId:")
Key = input("请输入SecretKey:")
client, bucket = self.hy_sdk_client(Id, Key)
data_bytes = client.get_object(Bucket=bucket,Key=base_name)["Body"].get_raw_stream().read()
data = json.loads(data_bytes.decode("utf-8"))
# data["SecretId"] = Id # 未来可以把这个加上
# data["SecretKey"] = Key
with open(path, "w") as f:
data_str = json.dumps(data, ensure_ascii=False)
# 如果 ensure_ascii 是 true (即默认值),输出保证将所有输入的非 ASCII 字符转义。
# 如果 ensure_ascii 是 false,这些字符会原样输出。
f.write(data_str)
f.close()
print(f"\033[32m 配置文件保存成功\033[0m")
return data
except json.decoder.JSONDecodeError:
print(f"\033[31m WARNING: 配置文件为空!\033[0m")
return {}
def load_file(self, cloud_path:str, local_path:str):
"""
从COS中下载文件到本地,本函数将会被默认执行的,在使用的时候建议加一些限制.
:param cloud_path: 云端的文件路径
:param local_path: 将云端文件保存在本地的路径
"""
if os.path.isdir(cloud_path):
raise ProcessError("请输入具体的云端文件路径,而非文件夹!")
if os.path.isdir(local_path):
raise ProcessError("请输入具体的本地文件路径,而非文件夹!")
dir_name = os.path.dirname(local_path)
base_name = os.path.basename(local_path)
try:
os.makedirs(dir_name)
except FileExistsError:
pass
cloud_name = os.path.basename(cloud_path)
print(f"\033[31m 请注意,接下来会在{dir_name}路径下生成文件{base_name}\033[0m")
Id = input("请输入SecretId:")
Key = input("请输入SecretKey:")
client, bucket = self.hy_sdk_client(Id, Key)
print(f"\033[34m 正在从COS中下载文件: {cloud_name}, 此过程可能耗费一些时间...\033[0m")
data_bytes = client.get_object(Bucket=bucket,Key=cloud_path)["Body"].get_raw_stream().read()
# data["SecretId"] = Id # 未来可以把这个加上
# data["SecretKey"] = Key
with open(local_path, "wb") as f:
# 如果 ensure_ascii 是 true (即默认值),输出保证将所有输入的非 ASCII 字符转义。
# 如果 ensure_ascii 是 false,这些字符会原样输出。
f.write(data_bytes)
f.close()
print(f"\033[32m 文件保存成功\033[0m")
class CosConf(GetConfig):
"""
从安全的角度出发,将一些默认配置文件上传至COS中,接下来使用COS和它的子类的时候,在第一次使用时需要输入Cuny给的id和key
用于连接cos存储桶,下载配置文件.
当然,在service_default_download = False的时候,如果在运行路径下已经有conf/service_config.json文件了,
那么就不用再次下载了,也不用输入id和key
事实上这只需要运行一次,因为配置文件将会被下载至源码文件夹中
如果要自定义路径,请在继承的子类中编写__init__函数,将service_path定向到指定路径
"""
def __init__(self) -> None:
# 下面这些参数是类的共享参数
self.__SECRET_ID: str = None # 服务的id
self.__SECRET_KEY: str = None # 服务的key
self.__REGION: str = None # 服务的存储桶地区
self.__TOKEN: str = None # 服务的token,目前一直是None
self.__SCHEME: str = None # 服务的访问协议,默认实际上是https
self.__BUCKET: str = None # 服务的存储桶
self.__SERVICE_CONFIG: dict = None # 服务的配置文件
self.service_path: str = f"{local_path_}/conf/service_config.json"
# 配置文件路径,默认是函数运行的路径下的conf文件夹
self.service_default_download = False # 是否在每次访问配置的时候都重新下载文件
@property
def service_config(self):
if self.__SERVICE_CONFIG is None or self.service_default_download is True:
self.__SERVICE_CONFIG = self.load_json(self.service_path, self.service_default_download)
return self.__SERVICE_CONFIG
@property
def client(self):
client_config = CosConfig(Region=self.region,
SecretId=self.secret_id,
SecretKey=self.secret_key,
Token=self.token,
Scheme=self.scheme)
return CosS3Client(client_config)
def get_key(self, key:str):
try:
data = self.service_config[key]
if data == "None":
return None
else:
return data
except KeyError:
print(f"\033[31m没有对应键值{key},默认返回None\033[0m")
return None
@property
def secret_id(self):
if self.__SECRET_ID is None:
self.__SECRET_ID = self.get_key("SECRET_ID")
return self.__SECRET_ID
@secret_id.setter
def secret_id(self, value:str):
self.__SECRET_ID = value
@property
def secret_key(self):
if self.__SECRET_KEY is None:
self.__SECRET_KEY = self.get_key("SECRET_KEY")
return self.__SECRET_KEY
@secret_key.setter
def secret_key(self, value:str):
self.__SECRET_KEY = value
@property
def region(self):
if self.__REGION is None:
self.__REGION = self.get_key("REGION")
return self.__REGION
@region.setter
def region(self, value:str):
self.__REGION = value
@property
def token(self):
# if self.__TOKEN is None:
# self.__TOKEN = self.get_key("TOKEN")
# 这里可以注释掉
return self.__TOKEN
@token.setter
def token(self, value:str):
self.__TOKEN= value
@property
def scheme(self):
if self.__SCHEME is None:
self.__SCHEME = self.get_key("SCHEME")
return self.__SCHEME
@scheme.setter
def scheme(self, value:str):
self.__SCHEME = value
@property
def bucket(self):
if self.__BUCKET is None:
self.__BUCKET = self.get_key("BUCKET")
return self.__BUCKET
@bucket.setter
def bucket(self, value):
self.__BUCKET = value
def downloadFile_COS(self, key, bucket:str=None, if_read:bool=False):
"""
从COS下载对象(二进制数据), 如果下载失败就返回None
"""
CosBucket = self.bucket if bucket is None else bucket
try:
# 将本类的Debug继承给抛弃了
# self.debug_print(f"Download from {CosBucket}", font_color="blue")
obj = self.client.get_object(
Bucket=CosBucket,
Key=key
)
if if_read is True:
data = obj["Body"].get_raw_stream().read() # byte
return data
else:
return obj
except Exception as e:
print(f"\033[31m下载失败! 错误描述:{e}\033[0m")
return None
def showFileList_COS_base(self, key, bucket, marker:str=""):
"""
返回cos存储桶内部的某个文件夹的内部名称
:param key: cos云端的存储路径
:param bucket: cos存储桶名称,如果没指定名称(None)就会寻找默认的存储桶
:param marker: 标记,用于记录上次查询到哪里了
ps:如果需要修改默认的存储桶配置,请在代码运行的时候加入代码 s.bucket = 存储桶名称 (s是对象实例)
返回的内容存储在response["Content"],不过返回的数据大小是有限制的,具体内容还是请看官方文档。
"""
response = self.client.list_objects(
Bucket=bucket,
Prefix=key,
Marker=marker
)
return response
def showFileList_COS(self, key, bucket:str=None)->list:
"""
实现查询存储桶中所有对象的操作,因为cos的sdk有返回数据包大小的限制,所以我们需要进行一定的改动
"""
marker = ""
file_list = []
CosBucket = self.bucket if bucket is None else bucket
while True: # 轮询
response = self.showFileList_COS_base(key, CosBucket, marker)
try:
file_list.extend(response["Contents"])
except KeyError as e:
print(e)
raise
if response['IsTruncated'] == 'false': # 接下来没有数据了,就退出
break
marker = response['NextMarker']
return file_list
def uploadFile_COS(self, buffer, key, bucket:str=None):
"""
从COS上传数据,需要注意的是必须得是二进制文件
"""
CosBucket = self.bucket if bucket is None else bucket
try:
self.client.put_object(
Bucket=CosBucket,
Body=buffer,
Key=key
)
return True
except Exception as e:
print(e)
return False
class FuncDiary(CosConf):
filter_dict = {"60a5e13da00e6e0001fd53c8": "Cuny",
"612c290f3a9af4000170faad": "守望平凡",
"614de96e1259260001506d6c": "林泽毅-焕影一新"}
def __init__(self, func_name: str, uid: str, error_conf_path: str = f"{local_path_}/conf/func_error_conf.json"):
"""
日志类的实例化
Args:
func_name: 功能名称,影响了日志投递的路径
"""
super().__init__()
# 配置文件路径,默认是函数运行的路径下的conf文件夹
self.service_path: str = os.path.join(os.path.dirname(error_conf_path), "service_config.json")
self.error_dict = self.load_json(path=error_conf_path)
self.__up: str = f"wx/invokeFunction_c/{datetime.datetime.now().strftime('%Y/%m/%d/%H')}/{func_name}/"
self.func_name: str = func_name
# 下面这个属性是的日志名称的前缀
self.__start_time = datetime.datetime.now().timestamp()
h_point = datetime.datetime.strptime(datetime.datetime.now().strftime('%Y/%m/%d/%H'), '%Y/%m/%d/%H')
h_point_timestamp = h_point.timestamp()
self.__prefix = int(self.__start_time - h_point_timestamp).__str__() + "_"
self.__uid = uid
self.__diary = None
def __str__(self):
return f"<{self.func_name}> DIARY for {self.__uid}"
@property
def content(self):
return self.__diary
@content.setter
def content(self, value: str):
if not isinstance(value, dict):
raise TypeError("content 只能是字典!")
if "status" in value:
raise KeyError("status字段已被默认占用,请在日志信息中更换字段名称!")
if self.__diary is None:
self.__diary = value
else:
raise PermissionError("为了减小日志对整体代码的影响,<content>只能被覆写一次!")
def uploadDiary_COS(self, status_id: str, suffix: str = "", bucket: str = "hy-hcy-data-logs-1306602019"):
if self.__diary is None:
self.__diary = {"status": self.error_dict[status_id]}
if status_id == "0000":
self.__up += f"True/{self.__uid}/"
else:
self.__up += f"False/{self.__uid}/"
interval = int(10 * (datetime.datetime.now().timestamp() - self.__start_time))
prefix = self.__prefix + status_id + "_" + interval.__str__()
self.__diary["status"] = self.error_dict[status_id]
name = prefix + "_" + suffix if len(suffix) != 0 else prefix
self.uploadFile_COS(buffer=json.dumps(self.__diary), key=self.__up + name, bucket=bucket)
print(f"{self}上传成功.")
class ResponseWebSocket(CosConf):
# 网关推送地址
__HOST:str = None
@property
def sendBackHost(self):
if self.__HOST is None:
self.__HOST = self.get_key("HOST")
return self.__HOST
@sendBackHost.setter
def sendBackHost(self, value):
self.__HOST = value
def sendMsg_toWebSocket(self, message,connectionID:str = None):
if connectionID is not None:
retmsg = {'websocket': {}}
retmsg['websocket']['action'] = "data send"
retmsg['websocket']['secConnectionID'] = connectionID
retmsg['websocket']['dataType'] = 'text'
retmsg['websocket']['data'] = json.dumps(message)
requests.post(self.sendBackHost, json=retmsg)
print("send success!")
else:
pass
@staticmethod
def create_Msg(status, msg):
"""
本方法用于创建一个用于发送到WebSocket客户端的数据
输入的信息部分,需要有如下几个参数:
1. id,固定为"return-result"
2. status,如果输入为1则status=true, 如果输入为-1则status=false
3. obj_key, 图片的云端路径, 这是输入的msg本身自带的
"""
msg['status'] = "false" if status == -1 else 'true' # 其实最好还是用bool
msg['id'] = "async-back-msg"
msg['type'] = "funcType"
msg["format"] = "imageType"
return msg
# 功能服务类
class Service(ResponseWebSocket):
"""
服务的主函数,封装了cos上传/下载功能以及与api网关的一键通讯
将类的实例变成一个可被调用的对象,在服务运行的时候,只需要运行该对象即可
当然,因为是类,所以支持继承和修改
"""
@classmethod
def process(cls, *args, **kwargs):
"""
处理函数,在使用的时候请将之重构
"""
pass
@classmethod
def __call__(cls, *args, **kwargs):
pass
|