ForcePilot/backend/package/yuxi/channel/gateway/validation.py
Kris ecd3c90e80 feat(channel/gateway): 新增完整网关通道模块
新增设备身份管理、认证限流、并发通道、Webhook路由、RBAC权限控制、SSE/轮询降级等全套网关通道功能,包含:
1. 设备身份生成与签名验证
2. 设备令牌认证与速率限制
3. 内存+数据库双重设备注册表
4. 并发通道限流管理
5. Webhook安全处理与路由
6. RBAC权限校验系统
7. OpenAI API兼容适配层
8. Tailscale认证支持
9. HTTP轮询降级机制
2026-05-21 10:26:33 +08:00

111 lines
3.2 KiB
Python

from functools import wraps
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from yuxi.channel.gateway.protocol import GatewayErrorCode, RpcRequest, RpcResponse
class ErrorShape(BaseModel):
code: str
message: str = ""
data: Any | None = None
retryable: bool = False
retryAfterMs: int | None = None
class RequestFrameSchema(BaseModel):
type: str = "request"
id: str
method: str
params: dict | None = None
sessionId: str | None = None
class ResponseFrameSchema(BaseModel):
type: str = "response"
id: str
ok: bool = True
result: dict | None = None
error: ErrorShape | None = None
class EventFrameSchema(BaseModel):
type: str = "event"
event: str
data: dict | None = None
timestamp: float = 0.0
seq: int | None = None
stateVersion: int | None = None
class StartAccountParams(BaseModel):
model_config = ConfigDict(extra="allow")
channel_type: str = Field(..., min_length=1, max_length=50)
account_id: str = Field(default="default", min_length=1, max_length=100)
config: dict = Field(default_factory=dict)
class StopAccountParams(BaseModel):
model_config = ConfigDict(extra="allow")
channel_type: str = Field(..., min_length=1, max_length=50)
account_id: str = Field(default="default", min_length=1, max_length=100)
force: bool = False
class SendMessageParams(BaseModel):
model_config = ConfigDict(extra="allow")
channel_type: str = Field(..., min_length=1, max_length=50)
account_id: str = Field(default="default", min_length=1, max_length=100)
target_id: str = Field(..., min_length=1, max_length=200)
text: str = Field(..., min_length=1, max_length=4096)
msg_type: str = Field(default="text")
media_url: str | None = None
extra: dict = Field(default_factory=dict)
class ProbeParams(BaseModel):
model_config = ConfigDict(extra="allow")
channel_type: str = Field(..., min_length=1, max_length=50)
account_id: str = Field(default="default", min_length=1, max_length=100)
class DiagnoseParams(BaseModel):
model_config = ConfigDict(extra="allow")
channel_type: str = Field(..., min_length=1, max_length=50)
account_id: str = Field(default="default", min_length=1, max_length=100)
class RepairParams(BaseModel):
model_config = ConfigDict(extra="allow")
channel_type: str = Field(..., min_length=1, max_length=50)
step_id: str = Field(..., min_length=1, max_length=100)
account_id: str = Field(default="default", min_length=1, max_length=100)
def validate_params(schema_cls: type[BaseModel]):
def decorator(func):
@wraps(func)
async def wrapper(request: RpcRequest, *args, **kwargs):
try:
validated = schema_cls(**(request.params or {}))
except ValidationError as e:
return RpcResponse(
id=request.id,
ok=False,
error_code=GatewayErrorCode.INVALID_PARAMS,
error_message=f"参数校验失败: {e}",
)
request._validated_params = validated
return await func(request, *args, **kwargs)
return wrapper
return decorator