1015 lines
34 KiB
Python
1015 lines
34 KiB
Python
|
|
# =============================================================================
|
|||
|
|
# 企微IT智能服务台 — 管理后台 API 路由
|
|||
|
|
# =============================================================================
|
|||
|
|
# 说明:管理后台的全部路由端点,统一 /admin/ 前缀
|
|||
|
|
# 包含 7 组 API:
|
|||
|
|
# 1. 运营总览仪表盘
|
|||
|
|
# 2. 功能开关/参数管理
|
|||
|
|
# 3. 坐席人员管理
|
|||
|
|
# 4. 外部系统集成配置
|
|||
|
|
# 5. 快速回复管理(审核)
|
|||
|
|
# 6. 消息分配模式
|
|||
|
|
# 7. 会话监控
|
|||
|
|
# 8. 全局搜索
|
|||
|
|
# 所有接口需要 admin 角色权限
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
from typing import Optional
|
|||
|
|
from uuid import UUID
|
|||
|
|
|
|||
|
|
from fastapi import APIRouter, Depends, Query
|
|||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
|
|
|||
|
|
from app.api.agents import get_current_agent
|
|||
|
|
from app.database import get_db
|
|||
|
|
from app.models.agent import Agent
|
|||
|
|
from app.schemas.admin import (
|
|||
|
|
AgentCreateRequest,
|
|||
|
|
AgentUpdateRequest,
|
|||
|
|
AssignmentModeUpdateRequest,
|
|||
|
|
ConfigUpdateRequest,
|
|||
|
|
IntegrationUpdateRequest,
|
|||
|
|
QuickReplyReviewRequest,
|
|||
|
|
)
|
|||
|
|
from app.services import admin_service
|
|||
|
|
from app.utils.response import AppException, success_response
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
# 创建路由器
|
|||
|
|
router = APIRouter(prefix="/admin")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
# 管理后台权限校验依赖
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
async def require_admin(
|
|||
|
|
agent: Agent = Depends(get_current_agent),
|
|||
|
|
) -> Agent:
|
|||
|
|
"""管理后台权限校验:仅 role='admin' 可访问。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
agent: 当前坐席(通过认证依赖注入)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Agent: 具有管理权限的坐席对象
|
|||
|
|
|
|||
|
|
Raises:
|
|||
|
|
AppException: 非管理员角色(错误码 1004)
|
|||
|
|
"""
|
|||
|
|
if agent.role != "admin":
|
|||
|
|
raise AppException(1004, "无管理权限")
|
|||
|
|
return agent
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 1. 运营总览仪表盘
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/dashboard/overview ----------
|
|||
|
|
@router.get("/dashboard/overview")
|
|||
|
|
async def get_dashboard_overview(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取仪表盘统计数据。
|
|||
|
|
|
|||
|
|
返回在线坐席数、今日会话数、待审核数、集成健康状态等。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
admin: 管理员(权限校验)
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含仪表盘数据
|
|||
|
|
"""
|
|||
|
|
overview = await admin_service.get_dashboard_overview(db)
|
|||
|
|
return success_response(data=overview.model_dump())
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 2. 功能开关/参数管理
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/configs ----------
|
|||
|
|
@router.get("/configs")
|
|||
|
|
async def get_configs(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取全部配置项(按功能分组)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含配置分组列表
|
|||
|
|
"""
|
|||
|
|
groups = await admin_service.get_config_groups(db)
|
|||
|
|
return success_response(data={
|
|||
|
|
"groups": [g.model_dump() for g in groups]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- PUT /api/admin/configs/{key} ----------
|
|||
|
|
@router.put("/configs/{key}")
|
|||
|
|
async def update_config(
|
|||
|
|
key: str,
|
|||
|
|
body: ConfigUpdateRequest,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""更新单个配置项(同时记录变更日志)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
key: 配置键
|
|||
|
|
body: 更新请求体
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含变更信息
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.update_config(db, key, body.value, admin.id)
|
|||
|
|
return success_response(data=result)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/configs/{key}/history ----------
|
|||
|
|
@router.get("/configs/{key}/history")
|
|||
|
|
async def get_config_history(
|
|||
|
|
key: str,
|
|||
|
|
limit: int = Query(20, ge=1, le=100, description="返回条数上限"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取指定配置项的变更历史。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
key: 配置键
|
|||
|
|
limit: 返回条数上限
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含变更历史列表
|
|||
|
|
"""
|
|||
|
|
items = await admin_service.get_config_history(db, key, limit)
|
|||
|
|
return success_response(data={
|
|||
|
|
"items": [item.model_dump() for item in items]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 3. 坐席人员管理
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/agents ----------
|
|||
|
|
@router.get("/agents")
|
|||
|
|
async def list_admin_agents(
|
|||
|
|
status: Optional[str] = Query(None, description="按状态筛选: online/offline/busy"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取坐席列表(管理视图,含角色/技能标签)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
status: 按状态筛选(可选)
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含坐席列表
|
|||
|
|
"""
|
|||
|
|
items = await admin_service.list_admin_agents(db, status)
|
|||
|
|
return success_response(data={
|
|||
|
|
"items": [item.model_dump() for item in items]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- POST /api/admin/agents ----------
|
|||
|
|
@router.post("/agents")
|
|||
|
|
async def create_agent(
|
|||
|
|
body: AgentCreateRequest,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""添加坐席。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
body: 创建请求体
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含创建的坐席信息
|
|||
|
|
"""
|
|||
|
|
agent = await admin_service.create_agent(
|
|||
|
|
db,
|
|||
|
|
user_id=body.user_id,
|
|||
|
|
name=body.name,
|
|||
|
|
role=body.role,
|
|||
|
|
skill_tags=body.skill_tags,
|
|||
|
|
max_load=body.max_load,
|
|||
|
|
)
|
|||
|
|
return success_response(data=agent.model_dump())
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- PUT /api/admin/agents/{id} ----------
|
|||
|
|
@router.put("/agents/{agent_id}")
|
|||
|
|
async def update_agent(
|
|||
|
|
agent_id: str,
|
|||
|
|
body: AgentUpdateRequest,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""编辑坐席(角色/技能标签/负载上限)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
agent_id: 坐席ID
|
|||
|
|
body: 更新请求体
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含更新后的坐席信息
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.update_agent(
|
|||
|
|
db,
|
|||
|
|
agent_id=agent_id,
|
|||
|
|
role=body.role,
|
|||
|
|
skill_tags=body.skill_tags,
|
|||
|
|
max_load=body.max_load,
|
|||
|
|
)
|
|||
|
|
return success_response(data=result)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- DELETE /api/admin/agents/{id} ----------
|
|||
|
|
@router.delete("/agents/{agent_id}")
|
|||
|
|
async def delete_agent(
|
|||
|
|
agent_id: str,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""移除坐席。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
agent_id: 坐席ID
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式
|
|||
|
|
"""
|
|||
|
|
await admin_service.delete_agent(db, agent_id)
|
|||
|
|
return success_response(data=None, message="坐席已移除")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- POST /api/admin/agents/{id}/otp-unbind ----------
|
|||
|
|
@router.post("/agents/{agent_id}/otp-unbind")
|
|||
|
|
async def admin_unbind_agent_otp(
|
|||
|
|
agent_id: str,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""强制解绑坐席的OTP。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
agent_id: 坐席ID
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式
|
|||
|
|
"""
|
|||
|
|
from app.models.agent import Agent as AgentModel
|
|||
|
|
from sqlalchemy import select
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
stmt = select(AgentModel).where(AgentModel.id == agent_id)
|
|||
|
|
result = await db.execute(stmt)
|
|||
|
|
agent = result.scalars().first()
|
|||
|
|
|
|||
|
|
if not agent:
|
|||
|
|
raise AppException(1001, "坐席不存在")
|
|||
|
|
|
|||
|
|
agent.otp_secret = None
|
|||
|
|
agent.otp_enabled = 0
|
|||
|
|
agent.updated_at = datetime.now()
|
|||
|
|
db.add(agent)
|
|||
|
|
await db.flush()
|
|||
|
|
|
|||
|
|
logger.info(f"管理员强制解绑OTP: agent={agent.user_id}")
|
|||
|
|
return success_response(data={"message": "OTP已解绑"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 4. 外部系统集成配置
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations ----------
|
|||
|
|
@router.get("/integrations")
|
|||
|
|
async def get_integrations(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取集成系统列表及配置状态。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含集成系统列表
|
|||
|
|
"""
|
|||
|
|
items = await admin_service.get_integrations(db)
|
|||
|
|
return success_response(data={
|
|||
|
|
"items": [item.model_dump() for item in items]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- PUT /api/admin/integrations/{id} ----------
|
|||
|
|
@router.put("/integrations/{integration_id}")
|
|||
|
|
async def update_integration(
|
|||
|
|
integration_id: str,
|
|||
|
|
body: IntegrationUpdateRequest,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""更新集成配置(支持 url_key 和 access_key 两种模式)。
|
|||
|
|
|
|||
|
|
- url_key 模式(Dify / RAGFlow):传入 api_url + api_key
|
|||
|
|
- access_key 模式(火绒安全):传入 access_key_id + access_key_secret + base_url
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
integration_id: 集成系统ID(如 dify/ragflow/huorong)
|
|||
|
|
body: 更新请求体
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含更新后的集成信息
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.update_integration(
|
|||
|
|
db,
|
|||
|
|
integration_id=integration_id,
|
|||
|
|
# url_key 模式字段
|
|||
|
|
api_url=body.api_url or "",
|
|||
|
|
api_key=body.api_key or "",
|
|||
|
|
# access_key 模式字段(火绒)
|
|||
|
|
access_key_id=body.access_key_id or "",
|
|||
|
|
access_key_secret=body.access_key_secret or "",
|
|||
|
|
# account_password 模式字段(联软)
|
|||
|
|
api_account=body.api_account or "",
|
|||
|
|
api_password=body.api_password or "",
|
|||
|
|
validate_key=body.validate_key or "",
|
|||
|
|
base_url=body.base_url or "",
|
|||
|
|
agent_id=admin.id,
|
|||
|
|
)
|
|||
|
|
return success_response(data=result.model_dump())
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 5. 快速回复管理
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/quick-replies/pending ----------
|
|||
|
|
@router.get("/quick-replies/pending")
|
|||
|
|
async def list_pending_quick_replies(
|
|||
|
|
category: Optional[str] = Query(None, description="按分类筛选"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取待审核快速回复模板列表。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
category: 按分类筛选(可选)
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含待审核模板列表
|
|||
|
|
"""
|
|||
|
|
items = await admin_service.list_pending_quick_replies(db, category)
|
|||
|
|
return success_response(data={
|
|||
|
|
"items": [item.model_dump() for item in items]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- PUT /api/admin/quick-replies/{id}/review ----------
|
|||
|
|
@router.put("/quick-replies/{template_id}/review")
|
|||
|
|
async def review_quick_reply(
|
|||
|
|
template_id: str,
|
|||
|
|
body: QuickReplyReviewRequest,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""审核快速回复模板(通过/驳回)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
template_id: 模板ID
|
|||
|
|
body: 审核请求体
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含审核结果
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.review_quick_reply(
|
|||
|
|
db,
|
|||
|
|
template_id=template_id,
|
|||
|
|
action=body.action,
|
|||
|
|
reason=body.reason,
|
|||
|
|
agent_id=admin.id,
|
|||
|
|
)
|
|||
|
|
return success_response(data=result)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 6. 消息分配模式
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/assignment-mode ----------
|
|||
|
|
@router.get("/assignment-mode")
|
|||
|
|
async def get_assignment_mode(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取当前分配模式。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含分配模式信息
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.get_assignment_mode(db)
|
|||
|
|
return success_response(data=result.model_dump())
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- PUT /api/admin/assignment-mode ----------
|
|||
|
|
@router.put("/assignment-mode")
|
|||
|
|
async def update_assignment_mode(
|
|||
|
|
body: AssignmentModeUpdateRequest,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""切换分配模式(阶段一仅允许手动接单)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
body: 更新请求体
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含更新后的分配模式
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.update_assignment_mode(db, body.mode, admin.id)
|
|||
|
|
return success_response(data=result.model_dump())
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 7. 会话监控
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/monitor/sessions ----------
|
|||
|
|
@router.get("/monitor/sessions")
|
|||
|
|
async def get_monitor_sessions(
|
|||
|
|
status: Optional[str] = Query(None, description="按会话状态筛选"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取实时会话列表(Demo预览)。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
status: 按会话状态筛选(可选)
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含会话监控数据
|
|||
|
|
"""
|
|||
|
|
result = await admin_service.get_monitor_sessions(db, status)
|
|||
|
|
return success_response(data=result.model_dump())
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 8. 全局搜索
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/search ----------
|
|||
|
|
@router.get("/search")
|
|||
|
|
async def global_search(
|
|||
|
|
q: str = Query(..., min_length=1, description="搜索关键词"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""搜索配置项、坐席、快速回复。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
q: 搜索关键词
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 统一响应格式,包含搜索结果
|
|||
|
|
"""
|
|||
|
|
items = await admin_service.global_search(db, q)
|
|||
|
|
return success_response(data={
|
|||
|
|
"items": [item.model_dump() for item in items]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 9. 火绒安全集成 API
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 说明:火绒终端安全系统的数据代理端点
|
|||
|
|
# - 测试连接:验证 AccessKey 签名是否正确
|
|||
|
|
# - 终端列表:代理查询火绒终端
|
|||
|
|
# - 终端详情:代理查询终端详细信息
|
|||
|
|
# - 漏洞信息:代理查询高危漏洞
|
|||
|
|
# - 病毒事件:代理查询病毒事件统计
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- POST /api/admin/integrations/huorong/test ----------
|
|||
|
|
@router.post("/integrations/huorong/test")
|
|||
|
|
async def test_huorong_connection(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""测试火绒API连接。
|
|||
|
|
|
|||
|
|
使用当前保存的 AccessKey 配置调用火绒 _list 接口验证连接。
|
|||
|
|
仅请求1条数据,最小化对火绒系统的影响。
|
|||
|
|
失败时返回调试信息帮助排查凭据问题。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 包含 success(bool) 和 message(str),失败时含 debug 信息
|
|||
|
|
"""
|
|||
|
|
from app.integrations.huorong.config import get_huorong_client
|
|||
|
|
from app.integrations.huorong.exceptions import HuorongConfigError, HuorongError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_huorong_client(db)
|
|||
|
|
result = await client.test_connection()
|
|||
|
|
# 附带凭据摘要(脱敏),方便排查
|
|||
|
|
result["debug"] = {
|
|||
|
|
"base_url": client.base_url,
|
|||
|
|
"access_key_id": client.access_key_id,
|
|||
|
|
"key_length": len(client.access_key_secret),
|
|||
|
|
}
|
|||
|
|
# 失败时附加排查提示
|
|||
|
|
if not result.get("success"):
|
|||
|
|
result["debug_hint"] = (
|
|||
|
|
"请检查: 1) 登录火绒控制中心 → 中心设置 → 通用设置 → API接口 → 管理密钥,"
|
|||
|
|
"确认 AccessKey ID 和 Secret 正确; 2) 确认 AccessKey 未过期或被撤销; "
|
|||
|
|
"3) 确认当前网络可访问火绒控制中心内网地址"
|
|||
|
|
)
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except HuorongConfigError as e:
|
|||
|
|
return success_response(data={
|
|||
|
|
"success": False,
|
|||
|
|
"message": e.message,
|
|||
|
|
})
|
|||
|
|
except HuorongError as e:
|
|||
|
|
# 认证失败时返回调试信息帮助用户排查
|
|||
|
|
return success_response(data={
|
|||
|
|
"success": False,
|
|||
|
|
"message": f"测试失败: {e.message}",
|
|||
|
|
"debug_hint": "请检查: 1) 火绒控制中心 > 中心设置 > API接口 > 管理密钥 确认凭据正确; 2) AccessKey 是否已过期或被撤销",
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations/huorong/terminals ----------
|
|||
|
|
@router.get("/integrations/huorong/terminals")
|
|||
|
|
async def list_huorong_terminals(
|
|||
|
|
group_id: Optional[str] = Query(None, description="分组ID"),
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
per_page: int = Query(20, ge=1, le=100, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""查询火绒终端列表。
|
|||
|
|
|
|||
|
|
代理火绒 /api/clnts/_list 接口,返回终端基本信息。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
group_id: 分组ID(可选)
|
|||
|
|
page: 页码
|
|||
|
|
per_page: 每页条数
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 终端列表数据
|
|||
|
|
"""
|
|||
|
|
from app.integrations.huorong.config import get_huorong_client
|
|||
|
|
from app.integrations.huorong.exceptions import HuorongConfigError, HuorongError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_huorong_client(db)
|
|||
|
|
result = await client.list_terminals(group_id=group_id, page=page, per_page=per_page)
|
|||
|
|
# 序列化 Pydantic 模型
|
|||
|
|
result["items"] = [item.model_dump() for item in result["items"]]
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except HuorongConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except HuorongError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations/huorong/terminals/{client_id} ----------
|
|||
|
|
@router.get("/integrations/huorong/terminals/{client_id}")
|
|||
|
|
async def get_huorong_terminal_detail(
|
|||
|
|
client_id: str,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""查询火绒终端详细信息。
|
|||
|
|
|
|||
|
|
代理火绒 /api/clnts/_info2 接口,返回终端硬件/软件/资产/网络配置。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
client_id: 终端唯一ID
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 终端详细信息
|
|||
|
|
"""
|
|||
|
|
from app.integrations.huorong.config import get_huorong_client
|
|||
|
|
from app.integrations.huorong.exceptions import HuorongConfigError, HuorongError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_huorong_client(db)
|
|||
|
|
detail = await client.get_terminal_detail(client_id)
|
|||
|
|
return success_response(data=detail.model_dump())
|
|||
|
|
except HuorongConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except HuorongError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations/huorong/leaks ----------
|
|||
|
|
@router.get("/integrations/huorong/leaks")
|
|||
|
|
async def list_huorong_leaks(
|
|||
|
|
group_id: Optional[str] = Query(None, description="分组ID"),
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
per_page: int = Query(20, ge=1, le=100, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""查询火绒高危漏洞终端。
|
|||
|
|
|
|||
|
|
代理火绒 /api/clnts/_leak 接口。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
group_id: 分组ID(可选)
|
|||
|
|
page: 页码
|
|||
|
|
per_page: 每页条数
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 漏洞终端列表
|
|||
|
|
"""
|
|||
|
|
from app.integrations.huorong.config import get_huorong_client
|
|||
|
|
from app.integrations.huorong.exceptions import HuorongConfigError, HuorongError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_huorong_client(db)
|
|||
|
|
result = await client.list_terminal_leaks(group_id=group_id, page=page, per_page=per_page)
|
|||
|
|
result["items"] = [item.model_dump() for item in result["items"]]
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except HuorongConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except HuorongError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations/huorong/virus-events ----------
|
|||
|
|
@router.get("/integrations/huorong/virus-events")
|
|||
|
|
async def list_huorong_virus_events(
|
|||
|
|
client_id: Optional[str] = Query(None, description="终端ID(type=0时需提供)"),
|
|||
|
|
group_id: Optional[str] = Query(None, description="分组ID(type=1时需提供)"),
|
|||
|
|
query_type: int = Query(2, ge=0, le=2, description="查询类型: 0=按终端ID, 1=按分组ID, 2=全部"),
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
per_page: int = Query(20, ge=1, le=100, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""查询火绒病毒事件统计。
|
|||
|
|
|
|||
|
|
代理火绒 /api/clnts/_virus_events 接口。
|
|||
|
|
火绒API要求 type 参数: 0=按client_id查, 1=按group_id查, 2=查全部。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
client_id: 终端ID(type=0时需提供)
|
|||
|
|
group_id: 分组ID(type=1时需提供)
|
|||
|
|
query_type: 查询类型,默认2(全部)
|
|||
|
|
page: 页码
|
|||
|
|
per_page: 每页条数
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 病毒事件统计数据
|
|||
|
|
"""
|
|||
|
|
from app.integrations.huorong.config import get_huorong_client
|
|||
|
|
from app.integrations.huorong.exceptions import HuorongConfigError, HuorongError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_huorong_client(db)
|
|||
|
|
result = await client.get_virus_events(
|
|||
|
|
client_id=client_id,
|
|||
|
|
group_id=group_id,
|
|||
|
|
query_type=query_type,
|
|||
|
|
page=page,
|
|||
|
|
per_page=per_page,
|
|||
|
|
)
|
|||
|
|
result["items"] = [item.model_dump() for item in result["items"]]
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except HuorongConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except HuorongError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 10. 联软LV7000 安全集成 API
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 说明:联软终端管理系统的数据代理端点
|
|||
|
|
# - 测试连接:验证账号密码+Token获取
|
|||
|
|
# - 终端查询:按员工账号/IP/MAC查终端(核心映射接口)
|
|||
|
|
# - 终端详情:极详细硬件+软件+资产+网络信息
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- POST /api/admin/integrations/lianruan/test ----------
|
|||
|
|
@router.post("/integrations/lianruan/test")
|
|||
|
|
async def test_lianruan_connection(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""测试联软API连接。
|
|||
|
|
|
|||
|
|
通过获取Token验证:IP白名单 + 账号密码 + Token。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 包含 success(bool) 和 message(str)
|
|||
|
|
"""
|
|||
|
|
from app.integrations.lianruan.config import get_lianruan_client
|
|||
|
|
from app.integrations.lianruan.exceptions import LianruanConfigError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_lianruan_client(db)
|
|||
|
|
result = await client.test_connection()
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except LianruanConfigError as e:
|
|||
|
|
return success_response(data={
|
|||
|
|
"success": False,
|
|||
|
|
"message": e.message,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations/lianruan/terminals ----------
|
|||
|
|
@router.get("/integrations/lianruan/terminals")
|
|||
|
|
async def query_lianruan_terminals(
|
|||
|
|
strusername: Optional[str] = Query(None, description="员工账号(核心映射字段)"),
|
|||
|
|
strdevname: Optional[str] = Query(None, description="计算机名"),
|
|||
|
|
strdevip: Optional[str] = Query(None, description="IP地址"),
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
per_page: int = Query(20, ge=1, le=100, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""查询联软终端设备(核心映射接口)。
|
|||
|
|
|
|||
|
|
⭐ strusername 参数可直接按员工账号查终端!这是联软最大的优势。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
strusername: 员工账号(映射金钥匙)
|
|||
|
|
strdevname: 计算机名
|
|||
|
|
strdevip: IP地址
|
|||
|
|
page: 页码
|
|||
|
|
per_page: 每页条数
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 终端列表数据
|
|||
|
|
"""
|
|||
|
|
from app.integrations.lianruan.config import get_lianruan_client
|
|||
|
|
from app.integrations.lianruan.exceptions import LianruanConfigError, LianruanError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_lianruan_client(db)
|
|||
|
|
result = await client.query_dev_by_params(
|
|||
|
|
strusername=strusername or "",
|
|||
|
|
strdevname=strdevname or "",
|
|||
|
|
strdevip=strdevip or "",
|
|||
|
|
page=page,
|
|||
|
|
per_page=per_page,
|
|||
|
|
)
|
|||
|
|
result["items"] = [item.model_dump() for item in result["items"]]
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except LianruanConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except LianruanError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 11. P2: 会话审计
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/audit/conversations ----------
|
|||
|
|
@router.get("/audit/conversations")
|
|||
|
|
async def list_audit_conversations(
|
|||
|
|
status: Optional[str] = Query(None, description="按状态筛选"),
|
|||
|
|
agent_id: Optional[str] = Query(None, description="按坐席筛选"),
|
|||
|
|
keyword: Optional[str] = Query(None, description="按员工姓名/消息摘要搜索"),
|
|||
|
|
date_from: Optional[str] = Query(None, description="开始日期 YYYY-MM-DD"),
|
|||
|
|
date_to: Optional[str] = Query(None, description="结束日期 YYYY-MM-DD"),
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
page_size: int = Query(20, ge=1, le=100, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取会话审计列表(支持分页+多条件筛选)。"""
|
|||
|
|
result = await admin_service.list_audit_conversations(
|
|||
|
|
db, status=status, agent_id=agent_id, keyword=keyword,
|
|||
|
|
date_from=date_from, date_to=date_to, page=page, page_size=page_size,
|
|||
|
|
)
|
|||
|
|
return success_response(data=result)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/audit/conversations/{id} ----------
|
|||
|
|
@router.get("/audit/conversations/{conversation_id}")
|
|||
|
|
async def get_audit_conversation_detail(
|
|||
|
|
conversation_id: str,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取会话审计详情(含消息列表)。"""
|
|||
|
|
result = await admin_service.get_audit_conversation_detail(db, conversation_id)
|
|||
|
|
if not result:
|
|||
|
|
raise AppException(3004, "会话不存在")
|
|||
|
|
return success_response(data=result)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 12. P2: 坐席绩效统计
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/agent-performance ----------
|
|||
|
|
@router.get("/agent-performance")
|
|||
|
|
async def get_agent_performance(
|
|||
|
|
date_from: Optional[str] = Query(None, description="开始日期 YYYY-MM-DD"),
|
|||
|
|
date_to: Optional[str] = Query(None, description="结束日期 YYYY-MM-DD"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取坐席绩效统计。"""
|
|||
|
|
items = await admin_service.get_agent_performance(db, date_from=date_from, date_to=date_to)
|
|||
|
|
return success_response(data={"items": items})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 13. P2: 系统日志
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/system-logs ----------
|
|||
|
|
@router.get("/system-logs")
|
|||
|
|
async def get_system_logs(
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
page_size: int = Query(50, ge=1, le=200, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""获取系统日志(配置变更日志)。"""
|
|||
|
|
result = await admin_service.get_system_logs(db, page=page, page_size=page_size)
|
|||
|
|
return success_response(data=result)# ---------- GET /api/admin/integrations/lianruan/terminals/{devname}/detail ----------
|
|||
|
|
@router.get("/integrations/lianruan/terminals/{devname}/detail")
|
|||
|
|
async def get_lianruan_terminal_detail(
|
|||
|
|
devname: str,
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""查询联软终端详细信息(极详细)。
|
|||
|
|
|
|||
|
|
比火绒_info2更丰富,含逻辑磁盘使用率、显示器、内存条详情。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
devname: 计算机名
|
|||
|
|
admin: 管理员
|
|||
|
|
db: 数据库会话
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Dict: 终端详细信息
|
|||
|
|
"""
|
|||
|
|
from app.integrations.lianruan.config import get_lianruan_client
|
|||
|
|
from app.integrations.lianruan.exceptions import LianruanConfigError, LianruanError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_lianruan_client(db)
|
|||
|
|
detail = await client.get_dev_all_info(strdevname=devname)
|
|||
|
|
return success_response(data=detail.model_dump())
|
|||
|
|
except LianruanConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except LianruanError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ==========================================================================
|
|||
|
|
# 14. RAGFlow 知识检索集成 API
|
|||
|
|
# ==========================================================================
|
|||
|
|
|
|||
|
|
# ---------- POST /api/admin/integrations/ragflow/test ----------
|
|||
|
|
@router.post("/integrations/ragflow/test")
|
|||
|
|
async def test_ragflow_connection(
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""测试 RAGFlow API 连接。"""
|
|||
|
|
from app.integrations.ragflow.config import get_ragflow_client
|
|||
|
|
from app.integrations.ragflow.exceptions import RagflowConfigError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_ragflow_client(db)
|
|||
|
|
result = await client.test_connection()
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except RagflowConfigError as e:
|
|||
|
|
return success_response(data={
|
|||
|
|
"success": False,
|
|||
|
|
"message": e.message,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- GET /api/admin/integrations/ragflow/datasets ----------
|
|||
|
|
@router.get("/integrations/ragflow/datasets")
|
|||
|
|
async def list_ragflow_datasets(
|
|||
|
|
page: int = Query(1, ge=1, description="页码"),
|
|||
|
|
page_size: int = Query(20, ge=1, le=100, description="每页条数"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""列出 RAGFlow 知识库(数据集)。"""
|
|||
|
|
from app.integrations.ragflow.config import get_ragflow_client
|
|||
|
|
from app.integrations.ragflow.exceptions import RagflowConfigError, RagflowError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_ragflow_client(db)
|
|||
|
|
result = await client.list_datasets(page=page, page_size=page_size)
|
|||
|
|
result["items"] = [item.model_dump() for item in result["items"]]
|
|||
|
|
return success_response(data=result)
|
|||
|
|
except RagflowConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except RagflowError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- POST /api/admin/integrations/ragflow/retrieval ----------
|
|||
|
|
@router.post("/integrations/ragflow/retrieval")
|
|||
|
|
async def ragflow_retrieval(
|
|||
|
|
question: str = Query(..., min_length=1, description="查询问题"),
|
|||
|
|
dataset_ids: Optional[str] = Query(None, description="数据集ID列表(逗号分隔)"),
|
|||
|
|
top_k: int = Query(5, ge=1, le=20, description="返回结果数量"),
|
|||
|
|
admin: Agent = Depends(require_admin),
|
|||
|
|
db: AsyncSession = Depends(get_db),
|
|||
|
|
):
|
|||
|
|
"""RAGFlow 知识检索测试。"""
|
|||
|
|
from app.integrations.ragflow.config import get_ragflow_client
|
|||
|
|
from app.integrations.ragflow.exceptions import RagflowConfigError, RagflowError
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = await get_ragflow_client(db)
|
|||
|
|
ds_ids = None
|
|||
|
|
if dataset_ids:
|
|||
|
|
ds_ids = [id.strip() for id in dataset_ids.split(",") if id.strip()]
|
|||
|
|
|
|||
|
|
result = await client.retrieval(
|
|||
|
|
question=question,
|
|||
|
|
dataset_ids=ds_ids,
|
|||
|
|
top_k=top_k,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return success_response(data={
|
|||
|
|
"chunks": [chunk.model_dump() for chunk in result.chunks],
|
|||
|
|
"doc_aggs": [agg.model_dump() for agg in result.doc_aggs],
|
|||
|
|
"total": result.total,
|
|||
|
|
})
|
|||
|
|
except RagflowConfigError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "config_missing"})
|
|||
|
|
except RagflowError as e:
|
|||
|
|
return success_response(data={"error": e.message, "error_code": "api_error"})
|