1、后台菜单增加定时任务和银行交易

2、增加后台配置银行账户和请求网址
This commit is contained in:
2026-08-13 15:12:16 +08:00
parent 33a5cc4f94
commit 7dcedf974d
22 changed files with 1741 additions and 3 deletions
@@ -0,0 +1,4 @@
from app.services.bank.client import query_bank_transactions, BankApiError
from app.services.bank.service import query_transactions_with_log
__all__ = ["query_bank_transactions", "BankApiError", "query_transactions_with_log"]
+76
View File
@@ -0,0 +1,76 @@
"""银行交易查询 API 客户端。"""
import httpx
from app.config import settings
class BankApiError(RuntimeError):
"""银行接口错误。"""
def __init__(
self,
message: str,
retryable: bool = False,
http_status: int | None = None,
):
super().__init__(message)
self.retryable = retryable
self.http_status = http_status
async def query_bank_transactions(
acct_no: str,
start_date: str,
end_date: str,
dc_flag: int | None = None,
page: int = 1,
page_size: int = 20,
) -> dict:
"""查询银行交易流水。
调用外部银行接口,按账号和日期范围查询交易记录。
:param acct_no: 我方银行账号
:param start_date: 开始日期 (YYYY-MM-DD)
:param end_date: 结束日期 (YYYY-MM-DD)
:param dc_flag: 借贷方向 (0-借/出金, 1-贷/入金, None-全部)
:param page: 页码
:param page_size: 每页数量 (最大 100)
:return: 接口返回的原始数据
"""
base = (settings.BANK_API_BASE or "").rstrip("/")
if not base:
raise BankApiError("银行接口地址未配置 (BANK_API_BASE)", retryable=False)
url = f"{base}/api/v1/internal/get-blank-transfer-acct-time"
headers = {
"api-key": settings.BANK_API_KEY or "",
"Content-Type": "application/json",
}
payload: dict = {
"acct_no": acct_no,
"start_date": start_date,
"end_date": end_date,
"page": page,
"page_size": min(page_size, 100),
}
if dc_flag is not None:
payload["dc_flag"] = dc_flag
timeout = httpx.Timeout(30.0)
try:
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
response = await client.post(url, headers=headers, json=payload)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise BankApiError(
f"银行接口返回错误: HTTP {e.response.status_code}",
retryable=e.response.status_code >= 500,
http_status=e.response.status_code,
) from e
except httpx.TimeoutException as e:
raise BankApiError("银行接口请求超时", retryable=True) from e
except httpx.RequestError as e:
raise BankApiError(f"银行接口请求失败: {e}", retryable=True) from e
@@ -0,0 +1,50 @@
"""银行接口请求文件日志记录器。
使用 get_logger 模式,日志输出到 log/bank_api/bank_api-YYYY-MM-DD.log。
"""
import json
from datetime import datetime, timezone
from app.utils.logger import get_logger
bank_api_logger = get_logger("bank_api", "bank_api")
def log_bank_api_request(
acct_no: str,
request_params: dict | None,
response_data: dict | None,
status_code: int | None,
is_success: bool,
error_msg: str | None = None,
admin_id: str | None = None,
duration_ms: int | None = None,
) -> None:
"""记录银行接口请求到日志文件。
:param acct_no: 查询的银行账号
:param request_params: 请求参数
:param response_data: 响应数据
:param status_code: HTTP 状态码
:param is_success: 是否成功
:param error_msg: 错误信息
:param admin_id: 操作管理员 ID
:param duration_ms: 请求耗时(毫秒)
"""
log_data = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"acct_no": acct_no,
"request_params": request_params,
"response_data": response_data,
"status_code": status_code,
"is_success": is_success,
"error_msg": error_msg,
"admin_id": admin_id,
"duration_ms": duration_ms,
}
line = json.dumps(log_data, ensure_ascii=False, default=str)
if is_success:
bank_api_logger.info(line)
else:
bank_api_logger.error(line)
@@ -0,0 +1,47 @@
"""银行交易查询服务 — 接口请求日志写入文件。"""
import time
from app.services.bank.client import query_bank_transactions, BankApiError
from app.services.bank.file_logger import log_bank_api_request
async def query_transactions_with_log(
admin_id: str,
**kwargs,
) -> dict:
"""查询银行流水并记录接口日志到文件。
:param admin_id: 操作管理员 ID
:param kwargs: 透传给 query_bank_transactions 的参数
:return: 接口返回的原始数据
"""
acct_no = kwargs.get("acct_no", "")
request_params = {k: v for k, v in kwargs.items()}
start_time = time.monotonic()
try:
result = await query_bank_transactions(**kwargs)
duration_ms = int((time.monotonic() - start_time) * 1000)
log_bank_api_request(
acct_no=acct_no,
request_params=request_params,
response_data=result,
status_code=200,
is_success=True,
admin_id=admin_id,
duration_ms=duration_ms,
)
return result
except BankApiError as e:
duration_ms = int((time.monotonic() - start_time) * 1000)
log_bank_api_request(
acct_no=acct_no,
request_params=request_params,
response_data=None,
status_code=e.http_status,
is_success=False,
error_msg=str(e),
admin_id=admin_id,
duration_ms=duration_ms,
)
raise