pixelarraylib 1.0.8__tar.gz → 1.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pixelarraylib-1.0.8/pixelarraylib.egg-info → pixelarraylib-1.0.9}/PKG-INFO +1 -1
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/__init__.py +1 -1
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/acr.py +249 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/aliyun_email.py +122 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/sms.py +59 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/monitor/feishu.py +64 -1
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/scripts/__init__.py +1 -1
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9/pixelarraylib.egg-info}/PKG-INFO +1 -1
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pyproject.toml +1 -1
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/LICENSE +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/MANIFEST.in +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/README.md +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/__main__.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/__init__.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/billing.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/content_scanner.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/domain.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/eci.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/ecs.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/eip.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/fc.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/oss.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/aliyun/sts.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/db_utils/mysql.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/db_utils/redis.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/decorators/__init__.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/decorators/decorators.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/gitlab/__init__.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/gitlab/code_analyzer.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/gitlab/pypi_package_manager.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/monitor/__init__.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/net/request.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/scripts/build_website.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/scripts/collect_code_to_txt.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/scripts/create_test_case_files.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/scripts/nginx_proxy_to_ecs.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/scripts/remove_empty_lines.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/system/__init__.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib/system/common.py +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib.egg-info/SOURCES.txt +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib.egg-info/dependency_links.txt +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib.egg-info/entry_points.txt +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib.egg-info/requires.txt +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/pixelarraylib.egg-info/top_level.txt +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/requirements.txt +0 -0
- {pixelarraylib-1.0.8 → pixelarraylib-1.0.9}/setup.cfg +0 -0
|
@@ -263,3 +263,252 @@ class ACRUtils:
|
|
|
263
263
|
except Exception as e:
|
|
264
264
|
print(f"获取仓库详情失败: {e}")
|
|
265
265
|
return {}, False
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ACRUtilsAsync:
|
|
269
|
+
def __init__(self, region_id: str, access_key_id: str, access_key_secret: str):
|
|
270
|
+
self.region_id = region_id
|
|
271
|
+
self.access_key_id = access_key_id
|
|
272
|
+
self.access_key_secret = access_key_secret
|
|
273
|
+
self.client = self._create_client()
|
|
274
|
+
|
|
275
|
+
def _create_client(self):
|
|
276
|
+
credential = CredentialClient()
|
|
277
|
+
config = open_api_models.Config(
|
|
278
|
+
credential=credential,
|
|
279
|
+
access_key_id=self.access_key_id,
|
|
280
|
+
access_key_secret=self.access_key_secret,
|
|
281
|
+
)
|
|
282
|
+
# Endpoint 请参考 https://api.aliyun.com/product/cr
|
|
283
|
+
config.endpoint = f"cr.{self.region_id}.aliyuncs.com"
|
|
284
|
+
return cr20181201Client(config)
|
|
285
|
+
|
|
286
|
+
async def list_instances(self):
|
|
287
|
+
"""
|
|
288
|
+
description:
|
|
289
|
+
列出容器镜像服务实例
|
|
290
|
+
parameters:
|
|
291
|
+
None
|
|
292
|
+
return:
|
|
293
|
+
dict: 包含实例列表的响应数据
|
|
294
|
+
success(bool): 是否成功
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
request = cr_20181201_models.ListInstanceRequest()
|
|
299
|
+
response = await self.client.list_instance_async(request)
|
|
300
|
+
return response.body.to_map(), True
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(f"列出实例失败: {e}")
|
|
303
|
+
return {}, False
|
|
304
|
+
|
|
305
|
+
async def create_namespace(self, instance_id: str, namespace_name: str):
|
|
306
|
+
"""
|
|
307
|
+
description:
|
|
308
|
+
创建命名空间
|
|
309
|
+
parameters:
|
|
310
|
+
instance_id (str): 实例ID
|
|
311
|
+
namespace_name (str): 命名空间名称
|
|
312
|
+
return:
|
|
313
|
+
dict: 创建结果
|
|
314
|
+
success(bool): 是否成功
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
request = cr_20181201_models.CreateNamespaceRequest(
|
|
319
|
+
instance_id=instance_id, namespace_name=namespace_name
|
|
320
|
+
)
|
|
321
|
+
response = await self.client.create_namespace_async(request)
|
|
322
|
+
return response.body.to_map(), True
|
|
323
|
+
except Exception as e:
|
|
324
|
+
print(f"创建命名空间失败: {e}")
|
|
325
|
+
return {}, False
|
|
326
|
+
|
|
327
|
+
async def delete_namespace(self, instance_id: str, namespace_name: str):
|
|
328
|
+
"""
|
|
329
|
+
description:
|
|
330
|
+
删除命名空间
|
|
331
|
+
parameters:
|
|
332
|
+
instance_id (str): 实例ID
|
|
333
|
+
namespace_name (str): 命名空间名称
|
|
334
|
+
return:
|
|
335
|
+
dict: 删除结果
|
|
336
|
+
success(bool): 是否成功
|
|
337
|
+
"""
|
|
338
|
+
try:
|
|
339
|
+
request = cr_20181201_models.DeleteNamespaceRequest(
|
|
340
|
+
instance_id=instance_id, namespace_name=namespace_name
|
|
341
|
+
)
|
|
342
|
+
response = await self.client.delete_namespace_async(request)
|
|
343
|
+
return response.body.to_map(), True
|
|
344
|
+
except Exception as e:
|
|
345
|
+
print(f"删除命名空间失败: {e}")
|
|
346
|
+
return {}, False
|
|
347
|
+
|
|
348
|
+
async def list_namespaces(self, instance_id: str):
|
|
349
|
+
"""
|
|
350
|
+
description:
|
|
351
|
+
列出命名空间
|
|
352
|
+
parameters:
|
|
353
|
+
instance_id (str): 实例ID
|
|
354
|
+
return:
|
|
355
|
+
dict: 包含命名空间列表的响应数据
|
|
356
|
+
success(bool): 是否成功
|
|
357
|
+
"""
|
|
358
|
+
try:
|
|
359
|
+
request = cr_20181201_models.ListNamespaceRequest(instance_id=instance_id)
|
|
360
|
+
response = await self.client.list_namespace_async(request)
|
|
361
|
+
return response.body.to_map(), True
|
|
362
|
+
except Exception as e:
|
|
363
|
+
print(f"列出命名空间失败: {e}")
|
|
364
|
+
return {}, False
|
|
365
|
+
|
|
366
|
+
async def exists_namespace(self, instance_id: str, namespace_name: str):
|
|
367
|
+
"""
|
|
368
|
+
description:
|
|
369
|
+
判断命名空间是否存在
|
|
370
|
+
parameters:
|
|
371
|
+
instance_id (str): 实例ID
|
|
372
|
+
namespace_name (str): 命名空间名称
|
|
373
|
+
return:
|
|
374
|
+
bool: 是否存在
|
|
375
|
+
"""
|
|
376
|
+
response, success = await self.list_namespaces(instance_id)
|
|
377
|
+
if not success:
|
|
378
|
+
return False, False
|
|
379
|
+
namespace_names = [
|
|
380
|
+
namespace["NamespaceName"] for namespace in response["Namespaces"]
|
|
381
|
+
]
|
|
382
|
+
return namespace_name in namespace_names, True
|
|
383
|
+
|
|
384
|
+
async def create_repository(
|
|
385
|
+
self,
|
|
386
|
+
instance_id: str,
|
|
387
|
+
namespace_name: str,
|
|
388
|
+
repository_name: str,
|
|
389
|
+
repository_type: str,
|
|
390
|
+
summary: str,
|
|
391
|
+
):
|
|
392
|
+
"""
|
|
393
|
+
description:
|
|
394
|
+
创建仓库
|
|
395
|
+
parameters:
|
|
396
|
+
instance_id (str): 实例ID
|
|
397
|
+
namespace_name (str): 命名空间名称
|
|
398
|
+
repository_name (str): 仓库名称
|
|
399
|
+
repository_type (str): 仓库类型
|
|
400
|
+
summary (str): 仓库摘要
|
|
401
|
+
return:
|
|
402
|
+
dict: 创建结果
|
|
403
|
+
success(bool): 是否成功
|
|
404
|
+
"""
|
|
405
|
+
if not re.fullmatch(r"[a-z\-]+", repository_name):
|
|
406
|
+
print("仓库名称只能包含小写英文字母和横杠")
|
|
407
|
+
return {}, False
|
|
408
|
+
try:
|
|
409
|
+
request = cr_20181201_models.CreateRepositoryRequest(
|
|
410
|
+
instance_id=instance_id,
|
|
411
|
+
repo_namespace_name=namespace_name,
|
|
412
|
+
repo_name=repository_name,
|
|
413
|
+
repo_type=repository_type,
|
|
414
|
+
summary=summary,
|
|
415
|
+
)
|
|
416
|
+
response = await self.client.create_repository_async(request)
|
|
417
|
+
return response.body.to_map(), True
|
|
418
|
+
except Exception as e:
|
|
419
|
+
print(f"创建仓库失败: {e}")
|
|
420
|
+
return {}, False
|
|
421
|
+
|
|
422
|
+
async def list_repositories(self, instance_id: str, namespace_name: str):
|
|
423
|
+
"""
|
|
424
|
+
description:
|
|
425
|
+
列出仓库
|
|
426
|
+
parameters:
|
|
427
|
+
instance_id (str): 实例ID
|
|
428
|
+
namespace_name (str): 命名空间名称
|
|
429
|
+
return:
|
|
430
|
+
dict: 包含仓库列表的响应数据
|
|
431
|
+
success(bool): 是否成功
|
|
432
|
+
"""
|
|
433
|
+
try:
|
|
434
|
+
request = cr_20181201_models.ListRepositoryRequest(
|
|
435
|
+
instance_id=instance_id, repo_namespace_name=namespace_name
|
|
436
|
+
)
|
|
437
|
+
response = await self.client.list_repository_async(request)
|
|
438
|
+
return response.body.to_map(), True
|
|
439
|
+
except Exception as e:
|
|
440
|
+
print(f"列出仓库失败: {e}")
|
|
441
|
+
return {}, False
|
|
442
|
+
|
|
443
|
+
async def exists_repository(
|
|
444
|
+
self, instance_id: str, namespace_name: str, repository_name: str
|
|
445
|
+
):
|
|
446
|
+
"""
|
|
447
|
+
description:
|
|
448
|
+
判断仓库是否存在
|
|
449
|
+
parameters:
|
|
450
|
+
instance_id (str): 实例ID
|
|
451
|
+
namespace_name (str): 命名空间名称
|
|
452
|
+
repository_name (str): 仓库名称
|
|
453
|
+
return:
|
|
454
|
+
bool: 是否存在
|
|
455
|
+
"""
|
|
456
|
+
response, success = await self.list_repositories(instance_id, namespace_name)
|
|
457
|
+
if not success:
|
|
458
|
+
return False, False
|
|
459
|
+
repository_names = [
|
|
460
|
+
repository["RepoName"] for repository in response["Repositories"]
|
|
461
|
+
]
|
|
462
|
+
return repository_name in repository_names, True
|
|
463
|
+
|
|
464
|
+
async def delete_repository(
|
|
465
|
+
self, instance_id: str, namespace_name: str, repository_id: str
|
|
466
|
+
):
|
|
467
|
+
"""
|
|
468
|
+
description:
|
|
469
|
+
删除仓库
|
|
470
|
+
parameters:
|
|
471
|
+
instance_id (str): 实例ID
|
|
472
|
+
namespace_name (str): 命名空间名称
|
|
473
|
+
repository_id (str): 仓库ID
|
|
474
|
+
return:
|
|
475
|
+
dict: 删除结果
|
|
476
|
+
success(bool): 是否成功
|
|
477
|
+
"""
|
|
478
|
+
try:
|
|
479
|
+
request = cr_20181201_models.DeleteRepositoryRequest(
|
|
480
|
+
instance_id=instance_id,
|
|
481
|
+
repo_namespace_name=namespace_name,
|
|
482
|
+
repo_id=repository_id,
|
|
483
|
+
)
|
|
484
|
+
response = await self.client.delete_repository_async(request)
|
|
485
|
+
return response.body.to_map(), True
|
|
486
|
+
except Exception as e:
|
|
487
|
+
print(f"删除仓库失败: {e}")
|
|
488
|
+
return {}, False
|
|
489
|
+
|
|
490
|
+
async def get_repository(
|
|
491
|
+
self, instance_id: str, namespace_name: str, repository_name: str
|
|
492
|
+
):
|
|
493
|
+
"""
|
|
494
|
+
description:
|
|
495
|
+
获取仓库详情信息
|
|
496
|
+
parameters:
|
|
497
|
+
instance_id (str): 实例ID
|
|
498
|
+
namespace_name (str): 命名空间名称
|
|
499
|
+
repository_name (str): 仓库名称
|
|
500
|
+
return:
|
|
501
|
+
dict: 详情信息
|
|
502
|
+
success(bool): 是否成功
|
|
503
|
+
"""
|
|
504
|
+
try:
|
|
505
|
+
request = cr_20181201_models.GetRepositoryRequest(
|
|
506
|
+
instance_id=instance_id,
|
|
507
|
+
repo_namespace_name=namespace_name,
|
|
508
|
+
repo_name=repository_name,
|
|
509
|
+
)
|
|
510
|
+
response = await self.client.get_repository_async(request)
|
|
511
|
+
return response.body.to_map(), True
|
|
512
|
+
except Exception as e:
|
|
513
|
+
print(f"获取仓库详情失败: {e}")
|
|
514
|
+
return {}, False
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import os
|
|
2
3
|
import time
|
|
3
4
|
import random
|
|
@@ -128,3 +129,124 @@ class AliyunEmailSender:
|
|
|
128
129
|
except Exception as e:
|
|
129
130
|
print(traceback.format_exc())
|
|
130
131
|
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class AliyunEmailSenderAsync:
|
|
135
|
+
def __init__(self, access_key_id, access_key_secret):
|
|
136
|
+
self.client = Dm20151123Client(
|
|
137
|
+
open_api_models.Config(
|
|
138
|
+
access_key_id=access_key_id,
|
|
139
|
+
access_key_secret=access_key_secret,
|
|
140
|
+
endpoint="dm.aliyuncs.com",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
async def generate_verification_code(self, length=6):
|
|
145
|
+
"""
|
|
146
|
+
description:
|
|
147
|
+
生成数字验证码
|
|
148
|
+
parameters:
|
|
149
|
+
length(int): 验证码长度
|
|
150
|
+
return:
|
|
151
|
+
str: 验证码
|
|
152
|
+
"""
|
|
153
|
+
return await asyncio.to_thread(
|
|
154
|
+
lambda: "".join(str(random.randint(0, 9)) for _ in range(length))
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def create_verification_email_content(
|
|
158
|
+
self, username, verification_code, email_type, validity=15
|
|
159
|
+
):
|
|
160
|
+
"""
|
|
161
|
+
description:
|
|
162
|
+
创建邮件HTML内容
|
|
163
|
+
parameters:
|
|
164
|
+
username(str): 用户名
|
|
165
|
+
validity(int): 验证码有效期
|
|
166
|
+
return:
|
|
167
|
+
str: 邮件HTML内容
|
|
168
|
+
"""
|
|
169
|
+
email_type_str = "注册" if email_type == "register" else "找回密码"
|
|
170
|
+
return f"""
|
|
171
|
+
<html>
|
|
172
|
+
<head>
|
|
173
|
+
<style>
|
|
174
|
+
body {{ font-family: Arial, sans-serif; line-height: 1.6; }}
|
|
175
|
+
.container {{ max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e1e8f7; border-radius: 10px; }}
|
|
176
|
+
.header {{ background: linear-gradient(to right, #1a2980, #26d0ce); color: white; padding: 15px; text-align: center; border-radius: 8px; }}
|
|
177
|
+
.code {{ font-size: 28px; font-weight: bold; text-align: center; margin: 25px 0; letter-spacing: 5px; color: #e74c3c; }}
|
|
178
|
+
.footer {{ margin-top: 30px; text-align: center; color: #777; font-size: 12px; }}
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<div class="container">
|
|
183
|
+
<div class="header">
|
|
184
|
+
<h2>北京矩阵像素科技有限公司</h2>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<p>尊敬的 <strong>{username}</strong>:</p>
|
|
188
|
+
<p>您正在进行邮箱{email_type_str}验证,您的验证码为:</p>
|
|
189
|
+
<div class="code">{verification_code}</div>
|
|
190
|
+
<p>该验证码 <strong>{validity}分钟</strong> 内有效,请尽快完成验证。</p>
|
|
191
|
+
<p>如非本人操作,请忽略此邮件。</p>
|
|
192
|
+
|
|
193
|
+
<div class="footer">
|
|
194
|
+
<p>阿里云邮件推送服务 | 安全验证</p>
|
|
195
|
+
<p>© {time.strftime('%Y')} 企业注册系统 版权所有</p>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</body>
|
|
199
|
+
</html>
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def get_email_subject(self, email_type):
|
|
203
|
+
if email_type == "register":
|
|
204
|
+
return "您的注册验证码"
|
|
205
|
+
elif email_type == "forgot":
|
|
206
|
+
return "您的找回密码验证码"
|
|
207
|
+
else:
|
|
208
|
+
return "您的验证码"
|
|
209
|
+
|
|
210
|
+
async def send_verification_email(
|
|
211
|
+
self, to_address, verification_code, email_type, username="用户"
|
|
212
|
+
):
|
|
213
|
+
"""
|
|
214
|
+
description:
|
|
215
|
+
发送验证邮件
|
|
216
|
+
parameters:
|
|
217
|
+
to_address(str): 收件人邮箱地址
|
|
218
|
+
email_type(str): 邮件类型
|
|
219
|
+
username(str): 用户名
|
|
220
|
+
return:
|
|
221
|
+
dict: 发送结果
|
|
222
|
+
"""
|
|
223
|
+
assert email_type in [
|
|
224
|
+
"register",
|
|
225
|
+
"forgot",
|
|
226
|
+
], "邮件类型错误,当前只有register和forgot两种类型"
|
|
227
|
+
try:
|
|
228
|
+
response = await self.client.single_send_mail_with_options_async(
|
|
229
|
+
dm_20151123_models.SingleSendMailRequest(
|
|
230
|
+
account_name="captcha_new@captcha.pixelarrayai.com",
|
|
231
|
+
address_type=1,
|
|
232
|
+
to_address=to_address,
|
|
233
|
+
subject=self.get_email_subject(email_type),
|
|
234
|
+
html_body=self.create_verification_email_content(
|
|
235
|
+
username=username,
|
|
236
|
+
verification_code=verification_code,
|
|
237
|
+
email_type=email_type,
|
|
238
|
+
),
|
|
239
|
+
reply_to_address=False,
|
|
240
|
+
from_alias="系统验证", # 发信人昵称
|
|
241
|
+
),
|
|
242
|
+
util_models.RuntimeOptions(),
|
|
243
|
+
)
|
|
244
|
+
if response.status_code == 200:
|
|
245
|
+
return True
|
|
246
|
+
else:
|
|
247
|
+
print(f"发送验证邮件失败: {response}")
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
print(traceback.format_exc())
|
|
252
|
+
return False
|
|
@@ -5,8 +5,11 @@ from alibabacloud_tea_openapi import models as open_api_models
|
|
|
5
5
|
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
|
|
6
6
|
from alibabacloud_tea_util import models as util_models
|
|
7
7
|
from pixelarraylib.monitor.feishu import Feishu
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
8
10
|
feishu_alert = Feishu("devtoolkit服务报警")
|
|
9
11
|
|
|
12
|
+
|
|
10
13
|
class SMSUtils:
|
|
11
14
|
def __init__(self, access_key_id, access_key_secret):
|
|
12
15
|
self.sms_cilent = Dysmsapi20170525Client(
|
|
@@ -57,3 +60,59 @@ class SMSUtils:
|
|
|
57
60
|
except Exception as e:
|
|
58
61
|
print("短信发送失败: " + traceback.format_exc())
|
|
59
62
|
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SMSUtilsAsync:
|
|
66
|
+
def __init__(self, access_key_id, access_key_secret):
|
|
67
|
+
self.sms_cilent = Dysmsapi20170525Client(
|
|
68
|
+
open_api_models.Config(
|
|
69
|
+
type="access_key",
|
|
70
|
+
access_key_id=access_key_id,
|
|
71
|
+
access_key_secret=access_key_secret,
|
|
72
|
+
endpoint="dysmsapi.aliyuncs.com",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def generate_verification_code(self, length=6):
|
|
77
|
+
"""
|
|
78
|
+
description:
|
|
79
|
+
生成数字验证码
|
|
80
|
+
parameters:
|
|
81
|
+
length(int): 验证码长度
|
|
82
|
+
return:
|
|
83
|
+
str: 验证码
|
|
84
|
+
"""
|
|
85
|
+
return await asyncio.to_thread(
|
|
86
|
+
lambda: "".join(str(random.randint(0, 9)) for _ in range(length))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def send_verification_code(self, phone_numbers, verification_code):
|
|
90
|
+
"""
|
|
91
|
+
description:
|
|
92
|
+
发送验证码给指定手机号
|
|
93
|
+
param:
|
|
94
|
+
phone_numbers: 手机号
|
|
95
|
+
verification_code: 验证码(6位数字)
|
|
96
|
+
return:
|
|
97
|
+
flag(bool): 是否发送成功
|
|
98
|
+
"""
|
|
99
|
+
send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
|
|
100
|
+
sign_name="北京矩阵像素科技有限公司",
|
|
101
|
+
template_code="SMS_318235798",
|
|
102
|
+
phone_numbers=phone_numbers,
|
|
103
|
+
template_param=f'{{"code":"{verification_code}"}}',
|
|
104
|
+
)
|
|
105
|
+
runtime = util_models.RuntimeOptions()
|
|
106
|
+
try:
|
|
107
|
+
response = await self.sms_cilent.send_sms_with_options_async(
|
|
108
|
+
send_sms_request, runtime
|
|
109
|
+
)
|
|
110
|
+
flag = bool(
|
|
111
|
+
response and response.status_code == 200 and response.body.code == "OK"
|
|
112
|
+
)
|
|
113
|
+
if not flag:
|
|
114
|
+
print(f"短信发送失败: {response}")
|
|
115
|
+
return flag
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print("短信发送失败: " + traceback.format_exc())
|
|
118
|
+
return False
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import json
|
|
3
3
|
import asyncio
|
|
4
|
+
import csv
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class Feishu:
|
|
@@ -17,8 +20,16 @@ class Feishu:
|
|
|
17
20
|
"picturetransform服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e975aa0a-acef-4e3f-bee4-6dc507b87ebd",
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
channel_name,
|
|
26
|
+
app_id: Optional[str] = None,
|
|
27
|
+
app_secret: Optional[str] = None,
|
|
28
|
+
):
|
|
21
29
|
self.webhook_url = self.channel_map[channel_name]
|
|
30
|
+
self.app_id = app_id
|
|
31
|
+
self.app_secret = app_secret
|
|
32
|
+
self._access_token = None
|
|
22
33
|
|
|
23
34
|
def send(self, text):
|
|
24
35
|
print(text)
|
|
@@ -132,3 +143,55 @@ class Feishu:
|
|
|
132
143
|
template: str = "turquoise",
|
|
133
144
|
):
|
|
134
145
|
return await asyncio.to_thread(self.send_table, headers, rows, title, template)
|
|
146
|
+
|
|
147
|
+
def send_file(
|
|
148
|
+
self,
|
|
149
|
+
file_url: Optional[str] = None,
|
|
150
|
+
title: Optional[str] = None,
|
|
151
|
+
template: str = "turquoise",
|
|
152
|
+
):
|
|
153
|
+
"""
|
|
154
|
+
description:
|
|
155
|
+
发送文件到飞书群
|
|
156
|
+
通过文件下载链接发送包含文件下载按钮的卡片消息
|
|
157
|
+
parameters:
|
|
158
|
+
file_url(str): 文件下载链接(如 OSS 链接),必需参数
|
|
159
|
+
title(str): 消息标题,如果为 None 则使用文件名
|
|
160
|
+
template(str): 卡片模板颜色,默认 "turquoise"
|
|
161
|
+
return:
|
|
162
|
+
bool: 发送是否成功
|
|
163
|
+
"""
|
|
164
|
+
if not file_url:
|
|
165
|
+
print("错误:必须提供 file_url(文件下载链接)")
|
|
166
|
+
return False
|
|
167
|
+
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
168
|
+
markdown_content = f"[点击下载文件]({file_url})"
|
|
169
|
+
|
|
170
|
+
card = {
|
|
171
|
+
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
172
|
+
"header": {
|
|
173
|
+
"title": {"tag": "plain_text", "content": title},
|
|
174
|
+
"template": template,
|
|
175
|
+
},
|
|
176
|
+
"elements": [
|
|
177
|
+
{"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
payload = {"msg_type": "interactive", "card": card}
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
resp = requests.post(
|
|
184
|
+
self.webhook_url, headers=headers, data=json.dumps(payload)
|
|
185
|
+
)
|
|
186
|
+
if resp.ok:
|
|
187
|
+
result = resp.json()
|
|
188
|
+
return bool(result.get("StatusCode") == 0 or result.get("code") == 0)
|
|
189
|
+
else:
|
|
190
|
+
print(f"发送文件消息失败: {resp.status_code}, {resp.text}")
|
|
191
|
+
return False
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"发送文件消息异常: {e}")
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
async def send_file_async(self, file_url: str, title: str, template: str):
|
|
197
|
+
return await asyncio.to_thread(self.send_file, file_url, title, template)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|