All checks were successful
continuous-integration/drone/push Build is passing
- 扩展 ToolConfig 配置类型,新增 external_api 类型
- 实现接口注册表,包含 90+ 睿美云开放接口定义
- 实现 TPOS SHA256WithRSA 签名鉴权
- 实现睿美云 API 客户端,支持多租户配置
- 新增代理路由 /api/ruimeiyun/call/{api_name}
- 支持接口权限控制和健康检查
288 lines
7.7 KiB
Python
288 lines
7.7 KiB
Python
"""
|
||
睿美云代理路由
|
||
|
||
提供统一的睿美云接口代理能力,支持:
|
||
- 多租户配置隔离
|
||
- 接口权限控制
|
||
- 统一日志记录
|
||
- 错误处理
|
||
"""
|
||
|
||
import logging
|
||
from typing import Optional, Dict, Any, List
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from pydantic import BaseModel
|
||
from sqlalchemy.orm import Session
|
||
|
||
from ..database import get_db
|
||
from ..services.ruimeiyun import RuimeiyunClient, RUIMEIYUN_APIS, get_api_definition
|
||
from ..services.ruimeiyun.client import RuimeiyunError
|
||
from ..services.ruimeiyun.registry import get_all_modules, get_api_list_by_module, get_api_summary
|
||
from .auth import get_current_user
|
||
from ..models.user import User
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter(prefix="/ruimeiyun", tags=["睿美云代理"])
|
||
|
||
|
||
# ========================================
|
||
# Schemas
|
||
# ========================================
|
||
|
||
class RuimeiyunCallRequest(BaseModel):
|
||
"""睿美云接口调用请求"""
|
||
params: Optional[Dict[str, Any]] = None # URL 参数
|
||
body: Optional[Dict[str, Any]] = None # 请求体
|
||
|
||
|
||
class RuimeiyunRawCallRequest(BaseModel):
|
||
"""睿美云原始接口调用请求"""
|
||
method: str # HTTP 方法
|
||
path: str # API 路径
|
||
params: Optional[Dict[str, Any]] = None
|
||
body: Optional[Dict[str, Any]] = None
|
||
|
||
|
||
# ========================================
|
||
# API Endpoints
|
||
# ========================================
|
||
|
||
@router.post("/call/{api_name}")
|
||
async def call_ruimeiyun_api(
|
||
api_name: str,
|
||
request: RuimeiyunCallRequest,
|
||
tenant_id: str = Query(..., description="租户ID"),
|
||
user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""
|
||
调用睿美云接口(通过接口名称)
|
||
|
||
Args:
|
||
api_name: 接口名称,如 customer.search, order.list
|
||
request: 请求参数
|
||
tenant_id: 租户ID
|
||
|
||
Returns:
|
||
睿美云接口返回的数据
|
||
|
||
示例:
|
||
POST /api/ruimeiyun/call/customer.search?tenant_id=xxx
|
||
Body: {"params": {"keyword": "13800138000", "page": 1, "size": 20}}
|
||
"""
|
||
try:
|
||
client = RuimeiyunClient(tenant_id, db)
|
||
result = await client.call(
|
||
api_name=api_name,
|
||
params=request.params,
|
||
body=request.body
|
||
)
|
||
|
||
if result.success:
|
||
return {
|
||
"success": True,
|
||
"data": result.data
|
||
}
|
||
else:
|
||
return {
|
||
"success": False,
|
||
"error": result.error,
|
||
"raw": result.raw_response
|
||
}
|
||
|
||
except RuimeiyunError as e:
|
||
logger.error(f"睿美云调用失败: {api_name}, {e}")
|
||
raise HTTPException(status_code=e.status_code, detail=str(e))
|
||
except Exception as e:
|
||
logger.exception(f"睿美云调用异常: {api_name}")
|
||
raise HTTPException(status_code=500, detail=f"调用失败: {str(e)}")
|
||
|
||
|
||
@router.post("/call-raw")
|
||
async def call_ruimeiyun_raw(
|
||
request: RuimeiyunRawCallRequest,
|
||
tenant_id: str = Query(..., description="租户ID"),
|
||
user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""
|
||
直接调用睿美云接口(通过路径)
|
||
|
||
用于调用未在注册表中定义的接口
|
||
|
||
Args:
|
||
request: 请求参数(包含 method, path, params, body)
|
||
tenant_id: 租户ID
|
||
|
||
Returns:
|
||
睿美云接口返回的数据
|
||
|
||
示例:
|
||
POST /api/ruimeiyun/call-raw?tenant_id=xxx
|
||
Body: {
|
||
"method": "GET",
|
||
"path": "/api/v1/tpos/customer/customer-search",
|
||
"params": {"keyword": "13800138000"}
|
||
}
|
||
"""
|
||
try:
|
||
client = RuimeiyunClient(tenant_id, db)
|
||
result = await client.call_raw(
|
||
method=request.method,
|
||
path=request.path,
|
||
params=request.params,
|
||
body=request.body
|
||
)
|
||
|
||
if result.success:
|
||
return {
|
||
"success": True,
|
||
"data": result.data
|
||
}
|
||
else:
|
||
return {
|
||
"success": False,
|
||
"error": result.error,
|
||
"raw": result.raw_response
|
||
}
|
||
|
||
except RuimeiyunError as e:
|
||
logger.error(f"睿美云调用失败: {request.path}, {e}")
|
||
raise HTTPException(status_code=e.status_code, detail=str(e))
|
||
except Exception as e:
|
||
logger.exception(f"睿美云调用异常: {request.path}")
|
||
raise HTTPException(status_code=500, detail=f"调用失败: {str(e)}")
|
||
|
||
|
||
# ========================================
|
||
# 接口元数据
|
||
# ========================================
|
||
|
||
@router.get("/apis")
|
||
async def list_apis(
|
||
module: Optional[str] = None,
|
||
user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取可用的接口列表
|
||
|
||
Args:
|
||
module: 模块名称(可选),如 customer, order
|
||
|
||
Returns:
|
||
接口列表
|
||
"""
|
||
if module:
|
||
apis = get_api_list_by_module(module)
|
||
return {
|
||
"module": module,
|
||
"count": len(apis),
|
||
"apis": apis
|
||
}
|
||
else:
|
||
return {
|
||
"count": len(RUIMEIYUN_APIS),
|
||
"summary": get_api_summary(),
|
||
"apis": RUIMEIYUN_APIS
|
||
}
|
||
|
||
|
||
@router.get("/apis/{api_name}")
|
||
async def get_api_info(
|
||
api_name: str,
|
||
user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取接口详情
|
||
|
||
Args:
|
||
api_name: 接口名称,如 customer.search
|
||
|
||
Returns:
|
||
接口定义
|
||
"""
|
||
api_def = get_api_definition(api_name)
|
||
if not api_def:
|
||
raise HTTPException(status_code=404, detail=f"接口不存在: {api_name}")
|
||
|
||
return {
|
||
"name": api_name,
|
||
**api_def
|
||
}
|
||
|
||
|
||
@router.get("/modules")
|
||
async def list_modules(
|
||
user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取所有模块列表
|
||
|
||
Returns:
|
||
模块名称列表和每个模块的接口数量
|
||
"""
|
||
modules = get_all_modules()
|
||
summary = get_api_summary()
|
||
|
||
return {
|
||
"modules": [
|
||
{"name": m, "count": summary.get(m, 0)}
|
||
for m in modules
|
||
]
|
||
}
|
||
|
||
|
||
# ========================================
|
||
# 健康检查
|
||
# ========================================
|
||
|
||
@router.get("/health/{tenant_id}")
|
||
async def check_ruimeiyun_health(
|
||
tenant_id: str,
|
||
user: User = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""
|
||
检查租户的睿美云连接状态
|
||
|
||
Args:
|
||
tenant_id: 租户ID
|
||
|
||
Returns:
|
||
连接状态信息
|
||
"""
|
||
try:
|
||
client = RuimeiyunClient(tenant_id, db)
|
||
|
||
# 调用门店列表接口测试连接
|
||
result = await client.call("tenant.list")
|
||
|
||
if result.success:
|
||
return {
|
||
"status": "connected",
|
||
"tenant_id": tenant_id,
|
||
"base_url": client.config.base_url,
|
||
"account": client.config.account,
|
||
"message": "连接正常"
|
||
}
|
||
else:
|
||
return {
|
||
"status": "error",
|
||
"tenant_id": tenant_id,
|
||
"message": result.error
|
||
}
|
||
|
||
except RuimeiyunError as e:
|
||
return {
|
||
"status": "error",
|
||
"tenant_id": tenant_id,
|
||
"message": str(e)
|
||
}
|
||
except Exception as e:
|
||
return {
|
||
"status": "error",
|
||
"tenant_id": tenant_id,
|
||
"message": f"检查失败: {str(e)}"
|
||
}
|