- 扩展 ToolConfig 配置类型,新增 external_api 类型
- 实现接口注册表,包含 90+ 睿美云开放接口定义
- 实现 TPOS SHA256WithRSA 签名鉴权
- 实现睿美云 API 客户端,支持多租户配置
- 新增代理路由 /api/ruimeiyun/call/{api_name}
- 支持接口权限控制和健康检查
This commit is contained in:
287
backend/app/routers/ruimeiyun.py
Normal file
287
backend/app/routers/ruimeiyun.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
睿美云代理路由
|
||||
|
||||
提供统一的睿美云接口代理能力,支持:
|
||||
- 多租户配置隔离
|
||||
- 接口权限控制
|
||||
- 统一日志记录
|
||||
- 错误处理
|
||||
"""
|
||||
|
||||
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)}"
|
||||
}
|
||||
Reference in New Issue
Block a user