pixelarraylib 1.0.8__tar.gz → 1.1.0__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.
Files changed (46) hide show
  1. {pixelarraylib-1.0.8/pixelarraylib.egg-info → pixelarraylib-1.1.0}/PKG-INFO +1 -1
  2. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/__init__.py +1 -1
  3. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/acr.py +249 -0
  4. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/aliyun_email.py +122 -0
  5. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/sms.py +59 -0
  6. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/monitor/feishu.py +65 -1
  7. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/scripts/__init__.py +1 -1
  8. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0/pixelarraylib.egg-info}/PKG-INFO +1 -1
  9. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pyproject.toml +1 -1
  10. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/LICENSE +0 -0
  11. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/MANIFEST.in +0 -0
  12. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/README.md +0 -0
  13. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/__main__.py +0 -0
  14. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/__init__.py +0 -0
  15. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/billing.py +0 -0
  16. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/content_scanner.py +0 -0
  17. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/domain.py +0 -0
  18. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/eci.py +0 -0
  19. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/ecs.py +0 -0
  20. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/eip.py +0 -0
  21. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/fc.py +0 -0
  22. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/oss.py +0 -0
  23. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/aliyun/sts.py +0 -0
  24. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/db_utils/mysql.py +0 -0
  25. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/db_utils/redis.py +0 -0
  26. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/decorators/__init__.py +0 -0
  27. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/decorators/decorators.py +0 -0
  28. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/gitlab/__init__.py +0 -0
  29. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/gitlab/code_analyzer.py +0 -0
  30. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/gitlab/pypi_package_manager.py +0 -0
  31. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/monitor/__init__.py +0 -0
  32. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/net/request.py +0 -0
  33. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/scripts/build_website.py +0 -0
  34. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/scripts/collect_code_to_txt.py +0 -0
  35. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/scripts/create_test_case_files.py +0 -0
  36. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/scripts/nginx_proxy_to_ecs.py +0 -0
  37. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/scripts/remove_empty_lines.py +0 -0
  38. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/system/__init__.py +0 -0
  39. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib/system/common.py +0 -0
  40. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib.egg-info/SOURCES.txt +0 -0
  41. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib.egg-info/dependency_links.txt +0 -0
  42. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib.egg-info/entry_points.txt +0 -0
  43. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib.egg-info/requires.txt +0 -0
  44. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/pixelarraylib.egg-info/top_level.txt +0 -0
  45. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/requirements.txt +0 -0
  46. {pixelarraylib-1.0.8 → pixelarraylib-1.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraylib
3
- Version: 1.0.8
3
+ Version: 1.1.0
4
4
  Summary: PixelArray Python开发工具库 - 包含阿里云服务、数据库工具、装饰器、监控等功能
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -20,7 +20,7 @@ PixelArray Python开发工具库
20
20
  from pixelarraylib.gitlab import pypi_package_manager
21
21
  """
22
22
 
23
- __version__ = "1.0.8"
23
+ __version__ = "1.1.0"
24
24
  __author__ = "PixelArray"
25
25
  __email__ = "qi.lu@pixelarrayai.com"
26
26
 
@@ -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:
@@ -15,10 +18,19 @@ class Feishu:
15
18
  "thirdparty微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b1e6237a-1323-4ad9-96f4-d74de5cdc00f",
16
19
  "picturebed服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/d3c2e68c-3ed3-4832-9b66-76db5bd69b42",
17
20
  "picturetransform服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e975aa0a-acef-4e3f-bee4-6dc507b87ebd",
21
+ "cloudstorage服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/a632d0bc-e400-40ce-a3bb-1b9b9ee634f4",
18
22
  }
19
23
 
20
- def __init__(self, channel_name):
24
+ def __init__(
25
+ self,
26
+ channel_name,
27
+ app_id: Optional[str] = None,
28
+ app_secret: Optional[str] = None,
29
+ ):
21
30
  self.webhook_url = self.channel_map[channel_name]
31
+ self.app_id = app_id
32
+ self.app_secret = app_secret
33
+ self._access_token = None
22
34
 
23
35
  def send(self, text):
24
36
  print(text)
@@ -132,3 +144,55 @@ class Feishu:
132
144
  template: str = "turquoise",
133
145
  ):
134
146
  return await asyncio.to_thread(self.send_table, headers, rows, title, template)
147
+
148
+ def send_file(
149
+ self,
150
+ file_url: Optional[str] = None,
151
+ title: Optional[str] = None,
152
+ template: str = "turquoise",
153
+ ):
154
+ """
155
+ description:
156
+ 发送文件到飞书群
157
+ 通过文件下载链接发送包含文件下载按钮的卡片消息
158
+ parameters:
159
+ file_url(str): 文件下载链接(如 OSS 链接),必需参数
160
+ title(str): 消息标题,如果为 None 则使用文件名
161
+ template(str): 卡片模板颜色,默认 "turquoise"
162
+ return:
163
+ bool: 发送是否成功
164
+ """
165
+ if not file_url:
166
+ print("错误:必须提供 file_url(文件下载链接)")
167
+ return False
168
+ headers = {"Content-Type": "application/json; charset=utf-8"}
169
+ markdown_content = f"[点击下载文件]({file_url})"
170
+
171
+ card = {
172
+ "config": {"wide_screen_mode": True, "enable_forward": True},
173
+ "header": {
174
+ "title": {"tag": "plain_text", "content": title},
175
+ "template": template,
176
+ },
177
+ "elements": [
178
+ {"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
179
+ ],
180
+ }
181
+ payload = {"msg_type": "interactive", "card": card}
182
+
183
+ try:
184
+ resp = requests.post(
185
+ self.webhook_url, headers=headers, data=json.dumps(payload)
186
+ )
187
+ if resp.ok:
188
+ result = resp.json()
189
+ return bool(result.get("StatusCode") == 0 or result.get("code") == 0)
190
+ else:
191
+ print(f"发送文件消息失败: {resp.status_code}, {resp.text}")
192
+ return False
193
+ except Exception as e:
194
+ print(f"发送文件消息异常: {e}")
195
+ return False
196
+
197
+ async def send_file_async(self, file_url: str, title: str, template: str):
198
+ return await asyncio.to_thread(self.send_file, file_url, title, template)
@@ -3,7 +3,7 @@ ArrayLib 脚本工具包
3
3
  包含各种实用的命令行工具和脚本
4
4
  """
5
5
 
6
- __version__ = "1.0.8"
6
+ __version__ = "1.1.0"
7
7
  __author__ = "Lu qi"
8
8
 
9
9
  # 导出主要的脚本函数
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraylib
3
- Version: 1.0.8
3
+ Version: 1.1.0
4
4
  Summary: PixelArray Python开发工具库 - 包含阿里云服务、数据库工具、装饰器、监控等功能
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pixelarraylib"
7
- version = "1.0.8"
7
+ version = "1.1.0"
8
8
  authors = [
9
9
  {name = "Lu qi", email = "qi.lu@pixelarrayai.com"},
10
10
  ]
File without changes
File without changes
File without changes
File without changes