增加银行流水和定时任务程序
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
"""定时任务执行器。
|
||||
|
||||
支持两种任务类型:
|
||||
- external_api: 调用外部 HTTP 接口
|
||||
- internal_method: 动态导入并执行内部函数
|
||||
仅支持内部方法执行类型,供 Celery Beat 定时调用。
|
||||
|
||||
任务在 Celery Worker 中执行,通过 run_async 桥接异步操作。
|
||||
配置 JSON 格式(示例):
|
||||
{
|
||||
"module": "app.services.bank.sync_service",
|
||||
"function": "sync_bank_transactions",
|
||||
"url": "http://...",
|
||||
"api_key": "...",
|
||||
"acct_no": "...",
|
||||
"start_date": "2026-08-01",
|
||||
"end_date": "2026-08-14"
|
||||
}
|
||||
|
||||
其中 module 和 function 指定要调用的函数,其余字段作为 kwargs 传给该函数。
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -12,7 +21,6 @@ import logging
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import httpx
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models.base import async_session
|
||||
@@ -40,45 +48,14 @@ def _update_task_status(task_id: str, status: str, error_msg: str | None = None)
|
||||
run_async(_do())
|
||||
|
||||
|
||||
def _execute_external_api(config: str | None) -> dict:
|
||||
"""执行外部 API 调用。"""
|
||||
cfg = json.loads(config or "{}")
|
||||
url = cfg.get("url", "").strip()
|
||||
method = cfg.get("method", "GET").upper()
|
||||
headers = cfg.get("headers") or {}
|
||||
payload = cfg.get("payload")
|
||||
timeout_val = float(cfg.get("timeout", 30))
|
||||
|
||||
if not url:
|
||||
raise ValueError("外部接口地址 (url) 未配置")
|
||||
|
||||
start = time.monotonic()
|
||||
try:
|
||||
with httpx.Client(timeout=httpx.Timeout(timeout_val, connect=10.0)) as client:
|
||||
if method == "GET":
|
||||
resp = client.get(url, headers=headers, params=payload)
|
||||
elif method == "DELETE":
|
||||
resp = client.delete(url, headers=headers, params=payload)
|
||||
else:
|
||||
resp = client.request(method, url, headers=headers, json=payload)
|
||||
duration_ms = int((time.monotonic() - start) * 1000)
|
||||
resp.raise_for_status()
|
||||
return {
|
||||
"status_code": resp.status_code,
|
||||
"duration_ms": duration_ms,
|
||||
"body": resp.text[:2000],
|
||||
}
|
||||
except httpx.HTTPError as e:
|
||||
duration_ms = int((time.monotonic() - start) * 1000)
|
||||
raise RuntimeError(f"外部接口请求失败: {e} (耗时 {duration_ms}ms)") from e
|
||||
|
||||
|
||||
def _execute_internal_method(config: str | None) -> dict:
|
||||
"""执行内部方法调用。"""
|
||||
"""执行内部方法调用。
|
||||
|
||||
配置 JSON 中 module 和 function 指定要调用的函数,其余字段作为 kwargs 传入。
|
||||
"""
|
||||
cfg = json.loads(config or "{}")
|
||||
module_path = cfg.get("module", "").strip()
|
||||
function_name = cfg.get("function", "").strip()
|
||||
args = cfg.get("args") or []
|
||||
module_path = cfg.pop("module", "").strip()
|
||||
function_name = cfg.pop("function", "").strip()
|
||||
|
||||
if not module_path or not function_name:
|
||||
raise ValueError("内部方法需要指定 module 和 function")
|
||||
@@ -90,8 +67,9 @@ def _execute_internal_method(config: str | None) -> dict:
|
||||
if func is None or not callable(func):
|
||||
raise ValueError(f"模块 {module_path} 中不存在可调用函数 {function_name}")
|
||||
|
||||
# 剩余字段作为 kwargs 传给函数
|
||||
start = time.monotonic()
|
||||
result = func(*args)
|
||||
result = func(**cfg)
|
||||
duration_ms = int((time.monotonic() - start) * 1000)
|
||||
return {
|
||||
"duration_ms": duration_ms,
|
||||
@@ -101,10 +79,7 @@ def _execute_internal_method(config: str | None) -> dict:
|
||||
|
||||
@celery_app.task(name="execute_scheduled_task", bind=True, ignore_result=True) # type: ignore[call-arg]
|
||||
def execute_scheduled_task(self, task_id: str):
|
||||
"""执行定时任务(Celery 任务入口)。
|
||||
|
||||
通过 run_async 桥接到异步上下文读取任务配置并执行。
|
||||
"""
|
||||
"""执行定时任务(Celery 任务入口)。"""
|
||||
|
||||
async def _run():
|
||||
async with async_session() as db:
|
||||
@@ -118,21 +93,14 @@ def execute_scheduled_task(self, task_id: str):
|
||||
return
|
||||
|
||||
task_config = task.config
|
||||
task_type = task.task_type
|
||||
|
||||
try:
|
||||
if task_type == "external_api":
|
||||
exec_result = _execute_external_api(task_config)
|
||||
elif task_type == "internal_method":
|
||||
exec_result = _execute_internal_method(task_config)
|
||||
else:
|
||||
raise ValueError(f"未知的任务类型: {task_type}")
|
||||
|
||||
exec_result = _execute_internal_method(task_config)
|
||||
_update_task_status(task_id, "success")
|
||||
logger.info("定时任务执行成功: %s (%s) -> %s", task_id, task_type, exec_result)
|
||||
logger.info("定时任务执行成功: %s -> %s", task_id, exec_result)
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
_update_task_status(task_id, "error", error_msg)
|
||||
logger.exception("定时任务执行失败: %s (%s)", task_id, task_type)
|
||||
logger.exception("定时任务执行失败: %s", task_id)
|
||||
|
||||
run_async(_run())
|
||||
|
||||
Reference in New Issue
Block a user