lesscode-flask 0.2.89__tar.gz → 0.2.92__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 (75) hide show
  1. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/PKG-INFO +2 -2
  2. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/__init__.py +1 -1
  3. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/setup/__init__.py +147 -12
  4. lesscode_flask-0.2.92/lesscode_flask/utils/fs_util.py +68 -0
  5. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask.egg-info/PKG-INFO +2 -2
  6. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask.egg-info/requires.txt +1 -1
  7. lesscode_flask-0.2.89/lesscode_flask/utils/fs_util.py +0 -55
  8. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/README.md +0 -0
  9. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/app.py +0 -0
  10. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/db/__init__.py +0 -0
  11. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/db/datasource.py +0 -0
  12. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/db/executor.py +0 -0
  13. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/export_data/__init__.py +0 -0
  14. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/export_data/data_download_handler.py +0 -0
  15. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/log/access_log_handler.py +0 -0
  16. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/access_log.py +0 -0
  17. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/base_model.py +0 -0
  18. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/parameterized_query.py +0 -0
  19. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/resource_param_template.py +0 -0
  20. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/response_result.py +0 -0
  21. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/user.py +0 -0
  22. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/model/user_limit_policy.py +0 -0
  23. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/service/access_log_service.py +0 -0
  24. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/service/base_service.py +0 -0
  25. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/service/resource_param_template_service.py +0 -0
  26. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/setting/__init__.py +0 -0
  27. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/signals.py +0 -0
  28. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/static/swagger.py +0 -0
  29. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/__init__.py +0 -0
  30. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/decorator/__init__.py +0 -0
  31. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/decorator/cache.py +0 -0
  32. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/decorator/sql_injection.py +0 -0
  33. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/decorator/swagger.py +0 -0
  34. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/dify_utils.py +0 -0
  35. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/file/file_exporter.py +0 -0
  36. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/file/file_utils.py +0 -0
  37. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/helpers.py +0 -0
  38. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/json/NotSortJSONProvider.py +0 -0
  39. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/__init__.py +0 -0
  40. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/consecutive/consecutive_limiter_handler.py +0 -0
  41. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/consecutive/redis_consecutive_limiter.py +0 -0
  42. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/limit_util.py +0 -0
  43. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/req/rate_limiter_handler.py +0 -0
  44. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/req/redis_rate_limiter.py +0 -0
  45. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/req_count/count_limiter_handler.py +0 -0
  46. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/limit/req_count/redis_count_limiter.py +0 -0
  47. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/oss/__init__.py +0 -0
  48. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/oss/aliyun_oss.py +0 -0
  49. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/oss/ks3_oss.py +0 -0
  50. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/oss/minio_oss.py +0 -0
  51. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/redis/redis_helper.py +0 -0
  52. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/request/request.py +0 -0
  53. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/sign/__init__.py +0 -0
  54. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/sign/signature.py +0 -0
  55. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/swagger/swagger_template.py +0 -0
  56. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/swagger/swagger_util.py +0 -0
  57. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/task/__init__.py +0 -0
  58. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/task/task_helper.py +0 -0
  59. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/utils/thread/thread_utils.py +0 -0
  60. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask/wsgi.py +0 -0
  61. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask.egg-info/SOURCES.txt +0 -0
  62. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask.egg-info/dependency_links.txt +0 -0
  63. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/lesscode_flask.egg-info/top_level.txt +0 -0
  64. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/query_runner/__init__.py +0 -0
  65. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/query_runner/clickhouse.py +0 -0
  66. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/query_runner/elasticsearch.py +0 -0
  67. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/query_runner/kingbase.py +0 -0
  68. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/query_runner/mysql.py +0 -0
  69. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/query_runner/pg.py +0 -0
  70. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/settings/__init__.py +0 -0
  71. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/settings/helpers.py +0 -0
  72. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/utils/__init__.py +0 -0
  73. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/redash/utils/requests_session.py +0 -0
  74. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/setup.cfg +0 -0
  75. {lesscode_flask-0.2.89 → lesscode_flask-0.2.92}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lesscode-flask
3
- Version: 0.2.89
3
+ Version: 0.2.92
4
4
  Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
5
5
  Home-page: https://lesscode-flask
6
6
  Author: Chao.yy
@@ -11,7 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: Flask==3.0.3
14
- Requires-Dist: lesscode-utils==0.0.91
14
+ Requires-Dist: lesscode-utils==0.0.95
15
15
  Requires-Dist: Flask-Login==0.6.3
16
16
  Requires-Dist: redis==5.1.1
17
17
  Requires-Dist: flask-swagger-ui==4.11.1
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.89"
1
+ __version__ = "0.2.92"
2
2
 
3
3
  import functools
4
4
  import logging
@@ -1,4 +1,5 @@
1
1
  import inspect
2
+ import json
2
3
  import uuid
3
4
  from datetime import datetime
4
5
  from importlib import import_module
@@ -6,6 +7,7 @@ from logging.handlers import TimedRotatingFileHandler
6
7
 
7
8
  import requests
8
9
  from flask import current_app, Response, request
10
+ from lesscode_utils.encryption_algorithm import AES
9
11
 
10
12
  from lesscode_flask import download_func_dict, SQ_Blueprint
11
13
  from lesscode_flask.db import db
@@ -94,6 +96,7 @@ def setup_blueprint(app, path=None, pkg_name="handlers", blueprint_map=None):
94
96
  while f"{blueprint_name}-{index}" in blueprint_map:
95
97
  index += 1
96
98
  return f"{blueprint_name}-{index}"
99
+
97
100
  if path is None:
98
101
  # 项目内Handler的文件路径,使用当前工作目录作为根
99
102
  path = os.path.join(os.getcwd(), pkg_name)
@@ -169,7 +172,122 @@ def _normalize_methods(raw_methods, supported_methods):
169
172
  return [item for item in methods if item in supported_methods] or ["POST"]
170
173
 
171
174
 
172
- def _build_proxy_view(app, target_url, title, description=None, dynamic_params=None):
175
+ def _build_forward_headers(app):
176
+ headers = {}
177
+ for key, value in request.headers:
178
+ if key.lower() not in {"host", "content-length"}:
179
+ headers[key] = value
180
+ # 转调时按配置补充自定义请求头,用于能力平台鉴权/路由。
181
+ data_source_key = app.config.get("DATA_SOURCE_KEY")
182
+ data_source_value = app.config.get("DATA_SOURCE_VALUE")
183
+ if data_source_key and data_source_value is not None:
184
+ headers[str(data_source_key)] = str(data_source_value)
185
+ return headers
186
+
187
+
188
+ def _build_download_key(target_url, request_method):
189
+ key_seed = f"{str(request_method).upper()}::{target_url}"
190
+ # 保持原有 AES 风格,同时拼接首尾片段,避免只截前缀导致不同接口冲突。
191
+ encrypted = AES.encrypt(key_seed)
192
+ return f"{encrypted[:8]}{encrypted[-8:]}"
193
+
194
+
195
+ def _is_auto_export_route(route):
196
+ route = str(route or "").lower()
197
+ return "/list/" in route
198
+
199
+
200
+ def _apply_path_params(route, request_params):
201
+ target_route = str(route or "")
202
+ for param_name in list(request_params.keys()):
203
+ placeholder = f"<{param_name}>"
204
+ legacy_placeholder = f"{{{param_name}}}"
205
+ if placeholder not in target_route and legacy_placeholder not in target_route:
206
+ continue
207
+ value = str(request_params.pop(param_name))
208
+ target_route = target_route.replace(placeholder, value).replace(legacy_placeholder, value)
209
+ return target_route
210
+
211
+
212
+ def _inject_download_key(payload, download_key):
213
+ if not download_key or not isinstance(payload, dict):
214
+ return payload
215
+ # 自动注册接口仍然沿用老的 download_key 协议,优先回填到 data 节点。
216
+ data_payload = payload.get("data")
217
+ if isinstance(data_payload, dict):
218
+ data_payload["download_key"] = download_key
219
+ else:
220
+ payload["download_key"] = download_key
221
+ return payload
222
+
223
+
224
+ def _extract_auto_export_result(payload):
225
+ if isinstance(payload, list):
226
+ return {"dataSource": payload, "columns": []}
227
+ if not isinstance(payload, dict):
228
+ raise Exception("自动导出接口返回格式不支持")
229
+
230
+ # 兼容能力平台常见的列表返回结构,统一转换成 data_download 可消费的格式。
231
+ candidate_list = [payload]
232
+ data_payload = payload.get("data")
233
+ if isinstance(data_payload, dict):
234
+ candidate_list.insert(0, data_payload)
235
+
236
+ for candidate in candidate_list:
237
+ if not isinstance(candidate, dict):
238
+ continue
239
+ if isinstance(candidate.get("dataSource"), list):
240
+ return candidate
241
+ for key in ("list", "rows", "records", "items", "result"):
242
+ value = candidate.get(key)
243
+ if isinstance(value, list):
244
+ return {
245
+ "dataSource": value,
246
+ "columns": candidate.get("columns") or candidate.get("column") or []
247
+ }
248
+ if isinstance(value, dict) and isinstance(value.get("dataSource"), list):
249
+ return value
250
+ raise Exception("自动导出接口缺少可识别的数据列表")
251
+
252
+
253
+ def _register_auto_export_handler(app, target_url, request_method):
254
+ download_key = _build_download_key(target_url, request_method)
255
+
256
+ def _auto_export_func(offset=0, size=10, **kwargs):
257
+ # 自动注册接口没有本地业务函数,这里动态构造一个“导出函数”给 data_download 调用。
258
+ request_params = {"offset": offset, "size": size}
259
+ request_params.update(kwargs)
260
+ export_url = _apply_path_params(target_url, request_params)
261
+ headers = _build_forward_headers(app)
262
+ request_method_upper = str(request_method).upper()
263
+ request_kwargs = {
264
+ "method": request_method_upper,
265
+ "url": export_url,
266
+ "headers": headers,
267
+ "timeout": 30,
268
+ }
269
+ if request_method_upper == "GET":
270
+ request_kwargs["params"] = request_params
271
+ resp = requests.request(**request_kwargs)
272
+ else:
273
+ request_kwargs["json"] = request_params
274
+ resp = requests.request(**request_kwargs)
275
+ # 某些 /list/ 接口可能不是 JSON 入参;仅在常见参数错误场景下回退到表单透传。
276
+ if resp.status_code in {400, 415, 422}:
277
+ request_kwargs.pop("json", None)
278
+ form_headers = {k: v for k, v in headers.items() if k.lower() != "content-type"}
279
+ request_kwargs["headers"] = form_headers
280
+ request_kwargs["data"] = request_params
281
+ resp = requests.request(**request_kwargs)
282
+ resp.raise_for_status()
283
+ return _extract_auto_export_result(resp.json())
284
+
285
+ _auto_export_func.__name__ = f"auto_export_{uuid.uuid4().hex}"
286
+ download_func_dict[download_key] = _auto_export_func
287
+ return download_key
288
+
289
+
290
+ def _build_proxy_view(app, target_url, title, description=None, dynamic_params=None, download_key=None):
173
291
  # 生成一个代理视图:把当前请求透明转发到能力平台目标地址。
174
292
  def _proxy_view(**path_params):
175
293
  url = target_url
@@ -177,15 +295,7 @@ def _build_proxy_view(app, target_url, title, description=None, dynamic_params=N
177
295
  value = str(value)
178
296
  url = url.replace(f"<{key}>", value).replace(f"{{{key}}}", value)
179
297
 
180
- headers = {}
181
- for key, value in request.headers:
182
- if key.lower() not in {"host", "content-length"}:
183
- headers[key] = value
184
- # 转调时按配置补充自定义请求头,用于能力平台鉴权/路由。
185
- data_source_key = app.config.get("DATA_SOURCE_KEY")
186
- data_source_value = app.config.get("DATA_SOURCE_VALUE")
187
- if data_source_key and data_source_value is not None:
188
- headers[str(data_source_key)] = str(data_source_value)
298
+ headers = _build_forward_headers(app)
189
299
 
190
300
  resp = requests.request(
191
301
  method=request.method,
@@ -198,6 +308,19 @@ def _build_proxy_view(app, target_url, title, description=None, dynamic_params=N
198
308
 
199
309
  excluded_headers = {"content-encoding", "content-length", "transfer-encoding", "connection"}
200
310
  response_headers = [(k, v) for k, v in resp.headers.items() if k.lower() not in excluded_headers]
311
+ if download_key:
312
+ try:
313
+ # 列表代理接口返回 JSON 时,把 download_key 挂回响应,前端可直接复用原导出流程。
314
+ payload = json.loads(resp.content)
315
+ payload = _inject_download_key(payload, download_key)
316
+ return Response(
317
+ json.dumps(payload, ensure_ascii=False),
318
+ status=resp.status_code,
319
+ headers=response_headers,
320
+ content_type=resp.headers.get("Content-Type", "application/json")
321
+ )
322
+ except Exception:
323
+ pass
201
324
  return Response(resp.content, status=resp.status_code, headers=response_headers)
202
325
 
203
326
  _proxy_view._title = title
@@ -388,8 +511,17 @@ def setup_api_blueprint(app, exclude_route_method_keys=None, existing_blueprint_
388
511
  title = item.get("title") or f"{bp_name}-{index + 1}"
389
512
  endpoint = f"auto_dynamic_{group_id}_{index}_{'_'.join(available_methods).lower()}"
390
513
  description = item.get("description") or title
514
+ download_key = None
515
+ if _is_auto_export_route(remote_route):
516
+ # 约定 /list/ 路由支持导出,因此额外挂载 download_key -> 动态导出函数。
517
+ download_key = _register_auto_export_handler(
518
+ app,
519
+ target_url=target_url,
520
+ request_method=available_methods[0]
521
+ )
391
522
  view_func = _build_proxy_view(app, target_url=target_url, title=title,
392
- description=description, dynamic_params=item.get("dynamic_params"))
523
+ description=description, dynamic_params=item.get("dynamic_params"),
524
+ download_key=download_key)
393
525
  dynamic_bp.add_url_rule(local_route, endpoint=endpoint, view_func=view_func, methods=available_methods)
394
526
  group_route_count += 1
395
527
  total_route_count += 1
@@ -628,7 +760,7 @@ def setup_resource_register(app):
628
760
  url_rules_dict = {}
629
761
  for blueprint_name, blueprint in app.blueprints.items():
630
762
  group_key = f'{blueprint_name}|{blueprint.url_prefix}'
631
- if blueprint.url_prefix not in ["/swagger-ui"]:
763
+ if "swagger-ui" not in blueprint.url_prefix:
632
764
  url_rules_dict[group_key] = []
633
765
  # 遍历全局 URL 规则
634
766
  for rule in app.url_map.iter_rules():
@@ -715,6 +847,9 @@ def setup_data_download(app):
715
847
  func = download_func_dict.get(download_key, {})
716
848
  request_param = {"offset": offset, "size": size}
717
849
  signature = inspect.signature(func)
850
+ if any(parameter.kind == inspect.Parameter.VAR_KEYWORD for parameter in signature.parameters.values()):
851
+ # 动态导出函数统一使用 **kwargs,直接透传前端筛选条件即可。
852
+ request_param.update(params)
718
853
  for parameter_name, parameter in signature.parameters.items():
719
854
  if params.get(parameter_name):
720
855
  request_param[parameter_name] = params[parameter_name]
@@ -0,0 +1,68 @@
1
+ import hashlib
2
+ import logging
3
+
4
+ import json
5
+
6
+ import requests
7
+
8
+ from lesscode_flask.utils.helpers import app_config
9
+ from lesscode_flask.utils.redis.redis_helper import RedisHelper
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ _FS_WEBHOOK_CACHE_TTL = 3 * 60 * 60 # 3小时,单位秒
14
+
15
+
16
+ def fs_webhook(webhook_url: str, title: str, fs_content: list):
17
+ """
18
+ 发送飞书 webhook 消息。3小时内相同内容不重复发送。
19
+
20
+ :param webhook_url: 飞书 webhook 的 URL 地址
21
+ :param title: 消息的标题
22
+ :param fs_content: 消息内容列表
23
+ """
24
+ # 构建缓存 key:对 webhook_url + title + content 做 MD5 哈希
25
+ cache_raw = (
26
+ f"{webhook_url}:{title}:"
27
+ f"{json.dumps(fs_content, ensure_ascii=False, sort_keys=True)}"
28
+ )
29
+ cache_key = "fs_webhook:" + hashlib.md5(cache_raw.encode("utf-8")).hexdigest()
30
+
31
+ # 检查 Redis 中是否已存在该 key(3小时内已发送过)
32
+ redis = None
33
+ try:
34
+ redis_oauth_key = app_config.get("REDIS_OAUTH_KEY", "redis")
35
+ redis = RedisHelper(redis_oauth_key)
36
+ if redis.sync_exists(cache_key):
37
+ logger.info("fs_webhook 重复内容,跳过发送,cache_key=%s", cache_key)
38
+ return
39
+ except Exception: # pylint: disable=broad-except
40
+ logger.warning("fs_webhook Redis 检查失败,继续发送")
41
+
42
+ headers = {'Content-Type': 'application/json;charset=utf-8'}
43
+
44
+ # 构造飞书消息的 JSON 结构
45
+ json_text = {
46
+ "msg_type": "post",
47
+ "content": {
48
+ "post": {
49
+ "zh_cn": {
50
+ "title": title,
51
+ "content": [
52
+ fs_content
53
+ ]
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ # 发送 POST 请求并记录响应结果
60
+ result = requests.post(webhook_url, json.dumps(json_text), headers=headers, timeout=10).content
61
+ logger.info(result)
62
+
63
+ # 发送成功后写入 Redis 缓存,3小时内不再重复发送
64
+ if redis is not None:
65
+ try:
66
+ redis.sync_set(cache_key, "1", ex=_FS_WEBHOOK_CACHE_TTL)
67
+ except Exception: # pylint: disable=broad-except
68
+ logger.warning("fs_webhook Redis 写入缓存失败")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lesscode-flask
3
- Version: 0.2.89
3
+ Version: 0.2.92
4
4
  Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
5
5
  Home-page: https://lesscode-flask
6
6
  Author: Chao.yy
@@ -11,7 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: Flask==3.0.3
14
- Requires-Dist: lesscode-utils==0.0.91
14
+ Requires-Dist: lesscode-utils==0.0.95
15
15
  Requires-Dist: Flask-Login==0.6.3
16
16
  Requires-Dist: redis==5.1.1
17
17
  Requires-Dist: flask-swagger-ui==4.11.1
@@ -1,5 +1,5 @@
1
1
  Flask==3.0.3
2
- lesscode-utils==0.0.91
2
+ lesscode-utils==0.0.95
3
3
  Flask-Login==0.6.3
4
4
  redis==5.1.1
5
5
  flask-swagger-ui==4.11.1
@@ -1,55 +0,0 @@
1
- import logging
2
-
3
- import json
4
-
5
- import requests
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
- def fs_webhook(webhook_url:str,title:str, fs_content:list):
10
- """
11
- 发送飞书 webhook 消息
12
-
13
- 参数:
14
- webhook_url (str): 飞书 webhook 的 URL 地址
15
- title (str): 消息的标题
16
- fs_content (str or list): 消息内容,可以是字符串或字符串列表
17
-
18
- 返回值:
19
- 无返回值,直接发送 HTTP 请求
20
- """
21
- headers = {'Content-Type': 'application/json;charset=utf-8'}
22
-
23
- # # 根据内容类型构建飞书消息格式
24
- # if isinstance(content, list):
25
- # fs_content = []
26
- # for i in content:
27
- # fs_content.append({
28
- # "tag": "text",
29
- # "text": f"{i}\n"
30
- # })
31
- # else:
32
- # fs_content = [{
33
- # "tag": "text",
34
- # "text": f"{content}\n"
35
- # }]
36
-
37
- # 构造飞书消息的 JSON 结构
38
- json_text = {
39
- "msg_type": "post",
40
- "content": {
41
- "post": {
42
- "zh_cn": {
43
- "title": title,
44
- "content": [
45
- fs_content
46
- ]
47
- }
48
- }
49
- }
50
- }
51
-
52
- # 发送 POST 请求并记录响应结果
53
- result = requests.post(webhook_url, json.dumps(json_text), headers=headers).content
54
- logger.info(result)
55
-