feat: 新增睿美云对接模块
All checks were successful
continuous-integration/drone/push Build is passing

- 扩展 ToolConfig 配置类型,新增 external_api 类型
- 实现接口注册表,包含 90+ 睿美云开放接口定义
- 实现 TPOS SHA256WithRSA 签名鉴权
- 实现睿美云 API 客户端,支持多租户配置
- 新增代理路由 /api/ruimeiyun/call/{api_name}
- 支持接口权限控制和健康检查
This commit is contained in:
2026-01-30 17:27:58 +08:00
parent c1ba17f809
commit afcf30b519
23 changed files with 6290 additions and 4648 deletions

View 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)}"
}