add banbanmini backend
This commit is contained in:
14
talkingq-url/api/__init__.py
Normal file
14
talkingq-url/api/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
from api.websocket import router as websocket_router
|
||||
from api.roles import router as roles_router
|
||||
from api.auth import router as auth_router
|
||||
from api.device_control import router as device_control_router
|
||||
from api.ota import router as ota_router # 新增OTA路由
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(websocket_router, tags=["WebSocket"])
|
||||
api_router.include_router(roles_router, tags=["Roles"])
|
||||
api_router.include_router(auth_router, tags=["Auth"])
|
||||
api_router.include_router(device_control_router, tags=["Device Control"])
|
||||
api_router.include_router(ota_router, tags=["OTA"]) # 注册OTA路由
|
||||
|
||||
16
talkingq-url/api/assets.py
Normal file
16
talkingq-url/api/assets.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
from fastapi import APIRouter
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from config import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def configure_static_assets(app):
|
||||
"""配置静态资源文件夹"""
|
||||
tts_audio_directory = os.path.join(settings.assets_dir, "tts_audio")
|
||||
os.makedirs(tts_audio_directory, exist_ok=True)
|
||||
|
||||
firmware_directory = os.path.join(settings.assets_dir, "firmware")
|
||||
os.makedirs(firmware_directory, exist_ok=True)
|
||||
|
||||
app.mount("/assets", StaticFiles(directory=settings.assets_dir), name="assets")
|
||||
250
talkingq-url/api/auth.py
Normal file
250
talkingq-url/api/auth.py
Normal file
@@ -0,0 +1,250 @@
|
||||
from fastapi import Depends, HTTPException, status, Request, APIRouter, Path, Query
|
||||
from fastapi.security import APIKeyHeader
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
from services.device_auth_manager import device_auth_manager
|
||||
from utils.logger import session_logger
|
||||
from config import settings
|
||||
import re
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["Auth"])
|
||||
|
||||
device_id_header = APIKeyHeader(name="X-Device-ID", auto_error=False)
|
||||
device_serial_header = APIKeyHeader(name="X-Device-Serial", auto_error=False)
|
||||
admin_api_key_header = APIKeyHeader(name="X-Admin-API-Key", auto_error=False)
|
||||
client_api_key_header = APIKeyHeader(name="X-Client-Key", auto_error=False)
|
||||
|
||||
async def verify_device(
|
||||
device_id: str = Depends(device_id_header),
|
||||
serial_number: str = Depends(device_serial_header)
|
||||
):
|
||||
"""验证设备ID和序列号"""
|
||||
if not device_id or not serial_number:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="缺少设备认证信息",
|
||||
headers={"WWW-Authenticate": "DeviceAuth"},
|
||||
)
|
||||
is_valid = await device_auth_manager.authenticate_device(device_id, serial_number)
|
||||
if not is_valid:
|
||||
session_logger.warning("system", "auth", f"设备 {device_id} 认证失败")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="设备认证失败",
|
||||
headers={"WWW-Authenticate": "DeviceAuth"},
|
||||
)
|
||||
return device_id
|
||||
|
||||
async def api_auth(
|
||||
device_id: str = Depends(verify_device)
|
||||
):
|
||||
"""API认证依赖项,只使用设备ID和序列号认证"""
|
||||
return device_id
|
||||
|
||||
async def admin_auth(api_key: str = Depends(admin_api_key_header)) -> bool:
|
||||
"""验证管理员API密钥"""
|
||||
if not api_key:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="缺少管理员API密钥",
|
||||
headers={"WWW-Authenticate": "AdminAuth"},
|
||||
)
|
||||
return api_key == settings.admin_api_key
|
||||
|
||||
async def client_auth(client_api_key: str = Depends(client_api_key_header)) -> bool:
|
||||
"""验证小程序客户端API密钥"""
|
||||
if not client_api_key:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="缺少客户端API密钥",
|
||||
headers={"WWW-Authenticate": "ClientAuth"},
|
||||
)
|
||||
|
||||
if client_api_key != settings.client_api_key:
|
||||
session_logger.warning("system", "auth", "客户端API密钥验证失败")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="客户端API密钥无效",
|
||||
headers={"WWW-Authenticate": "ClientAuth"},
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def admin_or_api_auth(
|
||||
device_id: str = Path(...),
|
||||
admin_api_key: str = Depends(admin_api_key_header),
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
) -> Tuple[str, bool]:
|
||||
"""允许管理员或设备本身访问,返回(认证设备ID, 是否管理员)"""
|
||||
is_admin = admin_api_key == settings.admin_api_key if admin_api_key else False
|
||||
|
||||
if not is_admin and authenticated_device_id != device_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="没有权限查询其他设备",
|
||||
headers={"WWW-Authenticate": "DeviceAuth"},
|
||||
)
|
||||
|
||||
return authenticated_device_id, is_admin
|
||||
|
||||
class DeviceRegistrationRequest(BaseModel):
|
||||
device_id: str = Field(..., min_length=16, max_length=64, description="设备ID,基于ESP32的MAC地址生成")
|
||||
serial_number: str = Field(..., min_length=18, max_length=64, description="序列号,包含批次前缀和唯一码")
|
||||
batch_id: Optional[str] = Field(None, description="批次ID,使用YYYYMMDD格式")
|
||||
is_active: bool = Field(True, description="设备激活状态")
|
||||
|
||||
class DeviceRegistrationResponse(BaseModel):
|
||||
status: str
|
||||
device_id: str
|
||||
serial_number: str
|
||||
batch_id: Optional[str] = None
|
||||
is_active: bool
|
||||
|
||||
@router.post("/register-device", response_model=DeviceRegistrationResponse)
|
||||
async def register_device(
|
||||
request: DeviceRegistrationRequest,
|
||||
admin_authenticated: bool = Depends(admin_auth)
|
||||
):
|
||||
"""注册新设备到认证白名单"""
|
||||
batch_id = request.batch_id
|
||||
if not batch_id and len(request.serial_number) >= 8:
|
||||
batch_id = request.serial_number[:8]
|
||||
|
||||
success = await device_auth_manager.register_device(
|
||||
request.device_id,
|
||||
request.serial_number,
|
||||
batch_id,
|
||||
request.is_active
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="设备注册失败",
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"device_id": request.device_id,
|
||||
"serial_number": request.serial_number,
|
||||
"batch_id": batch_id,
|
||||
"is_active": request.is_active
|
||||
}
|
||||
|
||||
class BatchDeviceRegistrationRequest(BaseModel):
|
||||
devices: List[DeviceRegistrationRequest]
|
||||
|
||||
class BatchDeviceRegistrationResponse(BaseModel):
|
||||
status: str
|
||||
registered_count: int
|
||||
failed_count: int
|
||||
details: List[Dict[str, Any]]
|
||||
|
||||
@router.post("/register-devices-batch", response_model=BatchDeviceRegistrationResponse)
|
||||
async def register_devices_batch(
|
||||
request: BatchDeviceRegistrationRequest,
|
||||
admin_authenticated: bool = Depends(admin_auth)
|
||||
):
|
||||
"""批量注册设备到认证白名单"""
|
||||
results = []
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for device in request.devices:
|
||||
batch_id = device.batch_id
|
||||
if not batch_id and len(device.serial_number) >= 8:
|
||||
batch_id = device.serial_number[:8]
|
||||
|
||||
success = await device_auth_manager.register_device(
|
||||
device.device_id,
|
||||
device.serial_number,
|
||||
batch_id,
|
||||
device.is_active
|
||||
)
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
results.append({
|
||||
"status": "success",
|
||||
"device_id": device.device_id,
|
||||
"serial_number": device.serial_number,
|
||||
"batch_id": batch_id
|
||||
})
|
||||
else:
|
||||
failed_count += 1
|
||||
results.append({
|
||||
"status": "failed",
|
||||
"device_id": device.device_id,
|
||||
"serial_number": device.serial_number
|
||||
})
|
||||
|
||||
return {
|
||||
"status": "completed",
|
||||
"registered_count": success_count,
|
||||
"failed_count": failed_count,
|
||||
"details": results
|
||||
}
|
||||
|
||||
@router.get("/verify-device/{device_id}")
|
||||
async def verify_device(
|
||||
device_id: str = Path(...),
|
||||
serial_number: str = None,
|
||||
auth_result: Tuple[str, bool] = Depends(admin_or_api_auth)
|
||||
):
|
||||
"""验证设备是否已正确注册 (管理员或设备自身可使用)"""
|
||||
authenticated_device_id, is_admin = auth_result
|
||||
|
||||
if is_admin and serial_number:
|
||||
is_valid = await device_auth_manager.authenticate_device(device_id, serial_number)
|
||||
elif not is_admin:
|
||||
is_valid = True
|
||||
else:
|
||||
is_valid = None
|
||||
|
||||
device_info = await device_auth_manager.get_device_info(device_id)
|
||||
|
||||
return {
|
||||
"device_id": device_id,
|
||||
"is_valid": is_valid,
|
||||
"device_info": device_info
|
||||
}
|
||||
|
||||
class SerialQueryResponse(BaseModel):
|
||||
device_id: str
|
||||
serial_number: str
|
||||
|
||||
@router.get("/query-serial", response_model=SerialQueryResponse)
|
||||
async def query_serial_by_mac(
|
||||
mac_address: str = Query(..., description="设备MAC地址"),
|
||||
client_authenticated: bool = Depends(client_auth)
|
||||
):
|
||||
"""通过MAC地址查询设备ID和序列号"""
|
||||
mac_pattern = re.compile(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$')
|
||||
if not mac_pattern.match(mac_address):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="MAC地址格式无效,应为XX:XX:XX:XX:XX:XX或XX-XX-XX-XX-XX-XX格式"
|
||||
)
|
||||
|
||||
normalized_mac = mac_address.replace(":", "").replace("-", "").upper()
|
||||
|
||||
expected_device_id = f"TalkingQ_{normalized_mac}"
|
||||
|
||||
device_info = await device_auth_manager.get_device_info(expected_device_id)
|
||||
|
||||
if not device_info or not device_info.get("is_active", False):
|
||||
session_logger.warning("system", "auth", f"未找到MAC地址{mac_address}对应的设备或设备未激活")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="未找到对应的设备信息或设备未激活"
|
||||
)
|
||||
|
||||
return {
|
||||
"device_id": expected_device_id,
|
||||
"serial_number": device_info["serial_number"]
|
||||
}
|
||||
|
||||
@router.get("/verify")
|
||||
async def verify_auth(device_id: str = Depends(api_auth)):
|
||||
"""验证设备认证状态"""
|
||||
return {"status": "authenticated", "device_id": device_id}
|
||||
97
talkingq-url/api/device_control.py
Normal file
97
talkingq-url/api/device_control.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends, Path, Body
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
from api.auth import api_auth
|
||||
from services.connection_manager import connection_manager
|
||||
from services.device_volume_manager import device_volume_manager
|
||||
from utils.logger import session_logger
|
||||
|
||||
router = APIRouter(prefix="/api/device", tags=["Device Control"])
|
||||
|
||||
class VolumeRequest(BaseModel):
|
||||
volume: int = Field(..., ge=0, le=100, description="音量值(0-100)")
|
||||
|
||||
class DeviceResponse(BaseModel):
|
||||
status: str
|
||||
message: str
|
||||
device_id: str
|
||||
|
||||
class VolumeResponse(BaseModel):
|
||||
volume: int
|
||||
device_id: str
|
||||
|
||||
@router.post("/volume/{device_id}", response_model=DeviceResponse)
|
||||
async def set_device_volume(
|
||||
device_id: str = Path(..., description="设备ID"),
|
||||
volume_request: VolumeRequest = Body(...),
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""设置设备音量"""
|
||||
try:
|
||||
await device_volume_manager.set_volume(device_id, volume_request.volume)
|
||||
session_logger.info(device_id, "volume", f"设备音量设置为 {volume_request.volume}")
|
||||
|
||||
websocket = await connection_manager.get_connection(device_id)
|
||||
if websocket:
|
||||
try:
|
||||
await websocket.send_text(f"VOLUME:{volume_request.volume}")
|
||||
session_logger.info(device_id, "volume", f"已发送实时音量设置到设备")
|
||||
except Exception as e:
|
||||
session_logger.warning(device_id, "volume", f"发送音量通知失败,但数据库已更新: {str(e)}")
|
||||
else:
|
||||
session_logger.info(device_id, "volume", "设备当前不在线,仅保存设置到数据库")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Volume set to {volume_request.volume}",
|
||||
"device_id": device_id
|
||||
}
|
||||
except Exception as e:
|
||||
session_logger.error(device_id, "volume", f"设置音量失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"设置音量失败: {str(e)}")
|
||||
|
||||
@router.get("/volume/{device_id}", response_model=VolumeResponse)
|
||||
async def get_device_volume(
|
||||
device_id: str = Path(..., description="设备ID"),
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""获取设备当前音量设置"""
|
||||
try:
|
||||
volume = await device_volume_manager.get_volume(device_id)
|
||||
return {
|
||||
"volume": volume,
|
||||
"device_id": device_id
|
||||
}
|
||||
except Exception as e:
|
||||
session_logger.error(device_id, "volume", f"获取音量失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"获取音量失败: {str(e)}")
|
||||
|
||||
class NetworkResetResponse(BaseModel):
|
||||
status: str
|
||||
message: str
|
||||
device_id: str
|
||||
|
||||
@router.post("/reset-network/{device_id}", response_model=NetworkResetResponse)
|
||||
async def reset_device_network(
|
||||
device_id: str = Path(..., description="设备ID"),
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""重置设备网络配置"""
|
||||
websocket = await connection_manager.get_connection(device_id)
|
||||
if not websocket:
|
||||
raise HTTPException(status_code=404, detail=f"设备 {device_id} 未在线或未找到")
|
||||
|
||||
try:
|
||||
await websocket.send_text("RESET_NETWORK")
|
||||
|
||||
session_logger.info(device_id, "network", "已发送网络重置命令")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Network reset initiated",
|
||||
"device_id": device_id
|
||||
}
|
||||
except Exception as e:
|
||||
session_logger.error(device_id, "network", f"网络重置命令发送失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"网络重置命令发送失败: {str(e)}")
|
||||
103
talkingq-url/api/ota.py
Normal file
103
talkingq-url/api/ota.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends, Path, Query
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, Any, Optional
|
||||
from api.auth import api_auth, client_auth
|
||||
from api.response import Response
|
||||
from services.ota_flow_controller import ota_flow_controller
|
||||
from utils.logger import session_logger
|
||||
from services.connection_manager import connection_manager
|
||||
from services.system_config_manager import system_config_manager
|
||||
|
||||
router = APIRouter(prefix="/api/ota", tags=["OTA"])
|
||||
|
||||
class OTAStatusResponse(BaseModel):
|
||||
status: str
|
||||
progress: float
|
||||
version: str
|
||||
|
||||
class FirmwareUpdateSettings(BaseModel):
|
||||
version: str
|
||||
url: str
|
||||
|
||||
@router.get("/check/{device_id}", response_model=Response)
|
||||
async def check_update(
|
||||
device_id: str = Path(..., description="设备ID"),
|
||||
authenticated: bool = Depends(api_auth)
|
||||
):
|
||||
"""检查设备是否需要更新 (APP调用)"""
|
||||
try:
|
||||
result = await ota_flow_controller.start_update_flow(device_id)
|
||||
if result["status"] == "error":
|
||||
return Response(code=-1, msg=result["message"], data={})
|
||||
return Response(code=0, msg="success", data=result)
|
||||
except Exception as e:
|
||||
session_logger.error(device_id, "ota", f"检查更新失败: {str(e)}")
|
||||
return Response(code=-1, msg=f"检查更新失败: {str(e)}", data={})
|
||||
|
||||
@router.post("/start/{device_id}", response_model=Response)
|
||||
async def start_update(
|
||||
device_id: str = Path(..., description="设备ID"),
|
||||
authenticated: bool = Depends(api_auth)
|
||||
):
|
||||
"""启动设备更新 (APP调用)"""
|
||||
try:
|
||||
result = await ota_flow_controller.execute_update(device_id)
|
||||
if result["status"] == "error":
|
||||
return Response(code=-1, msg=result["message"], data={})
|
||||
return Response(code=0, msg="success", data={"updating": True})
|
||||
except Exception as e:
|
||||
session_logger.error(device_id, "ota", f"启动更新失败: {str(e)}")
|
||||
return Response(code=-1, msg=f"启动更新失败: {str(e)}", data={"updating": False})
|
||||
|
||||
@router.get("/status/{device_id}", response_model=Response)
|
||||
async def get_update_status(
|
||||
device_id: str = Path(..., description="设备ID"),
|
||||
authenticated: bool = Depends(api_auth)
|
||||
):
|
||||
"""获取设备更新状态 (APP调用)"""
|
||||
try:
|
||||
status = await ota_flow_controller.get_update_status(device_id)
|
||||
websocket = await connection_manager.get_connection(device_id)
|
||||
status["device_online"] = websocket is not None
|
||||
return Response(code=0, msg="success", data=status)
|
||||
except Exception as e:
|
||||
session_logger.error(device_id, "ota", f"获取更新状态失败: {str(e)}")
|
||||
return Response(code=-1, msg=f"获取更新状态失败: {str(e)}", data={
|
||||
"status": "error",
|
||||
"progress": 0,
|
||||
"version": "unknown",
|
||||
"device_online": False
|
||||
})
|
||||
|
||||
@router.post("/config/firmware", response_model=Response)
|
||||
async def update_firmware_settings(
|
||||
settings: FirmwareUpdateSettings,
|
||||
admin_api_key: str = Depends(api_auth)
|
||||
):
|
||||
"""更新固件配置信息(版本和URL)"""
|
||||
try:
|
||||
await system_config_manager.update_config('latest_firmware_version', settings.version)
|
||||
await system_config_manager.update_config('update_firmware_url', settings.url)
|
||||
return Response(code=0, msg="固件配置已更新", data={
|
||||
"version": settings.version,
|
||||
"url": settings.url
|
||||
})
|
||||
except Exception as e:
|
||||
session_logger.error("system", "ota", f"更新固件配置失败: {str(e)}")
|
||||
return Response(code=-1, msg=f"更新固件配置失败: {str(e)}", data={})
|
||||
|
||||
@router.get("/config/firmware", response_model=Response)
|
||||
async def get_firmware_settings(
|
||||
admin_api_key: str = Depends(api_auth)
|
||||
):
|
||||
"""获取当前固件配置信息"""
|
||||
try:
|
||||
version_config = await system_config_manager.get_config('latest_firmware_version')
|
||||
url_config = await system_config_manager.get_config('update_firmware_url')
|
||||
return Response(code=0, msg="success", data={
|
||||
"version": version_config.config_value if version_config else "",
|
||||
"url": url_config.config_value if url_config else ""
|
||||
})
|
||||
except Exception as e:
|
||||
session_logger.error("system", "ota", f"获取固件配置失败: {str(e)}")
|
||||
return Response(code=-1, msg=f"获取固件配置失败: {str(e)}", data={})
|
||||
6
talkingq-url/api/response.py
Normal file
6
talkingq-url/api/response.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Response(BaseModel):
|
||||
code: int
|
||||
msg: str
|
||||
data: object = None
|
||||
127
talkingq-url/api/roles.py
Normal file
127
talkingq-url/api/roles.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from fastapi import APIRouter, HTTPException, Query, Depends
|
||||
from typing import Dict, List, Optional
|
||||
from pydantic import BaseModel
|
||||
from services.role_service import role_service
|
||||
from services.device_service import device_service
|
||||
from services.history_service import history_service
|
||||
from api.auth import api_auth
|
||||
|
||||
router = APIRouter(prefix="/api/roles")
|
||||
|
||||
class RoleResponse(BaseModel):
|
||||
role_key: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
languages: Optional[List[str]] = None
|
||||
|
||||
class RoleSummaryResponse(BaseModel):
|
||||
role_key: str
|
||||
name: str
|
||||
|
||||
class DeviceRoleUpdate(BaseModel):
|
||||
role_key: str
|
||||
language: Optional[str] = None
|
||||
|
||||
class ConversationMessage(BaseModel):
|
||||
user: str
|
||||
assistant: str
|
||||
timestamp: Optional[float] = None
|
||||
|
||||
class PaginatedRoleResponse(BaseModel):
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
data: List[RoleResponse]
|
||||
|
||||
class PaginatedHistoryResponse(BaseModel):
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
data: List[ConversationMessage]
|
||||
role_key: str
|
||||
role_name: str
|
||||
|
||||
@router.get("/list", response_model=PaginatedRoleResponse)
|
||||
async def list_roles(
|
||||
authenticated_device_id: str = Depends(api_auth),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
search: Optional[str] = None
|
||||
):
|
||||
"""获取角色分页列表,支持搜索"""
|
||||
return await role_service.get_roles_paginated(page, page_size, search)
|
||||
|
||||
@router.get("/summaries", response_model=List[RoleSummaryResponse])
|
||||
async def get_role_summaries(
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""获取角色简要信息列表(仅包含key和name)"""
|
||||
return await role_service.get_role_summaries()
|
||||
|
||||
@router.get("/device/{device_id}")
|
||||
async def get_device_role(
|
||||
device_id: str,
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""获取设备当前使用的角色"""
|
||||
if device_id != authenticated_device_id:
|
||||
raise HTTPException(status_code=403, detail="无权访问其他设备的配置")
|
||||
|
||||
return await device_service.get_device_role(device_id)
|
||||
|
||||
@router.put("/device/{device_id}")
|
||||
async def update_device_role(
|
||||
device_id: str,
|
||||
role_update: DeviceRoleUpdate,
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""更新设备的角色配置"""
|
||||
if device_id != authenticated_device_id:
|
||||
raise HTTPException(status_code=403, detail="无权修改其他设备的配置")
|
||||
|
||||
try:
|
||||
return await device_service.update_device_role(
|
||||
device_id,
|
||||
role_update.role_key,
|
||||
role_update.language
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"更新角色时出错: {str(e)}")
|
||||
|
||||
@router.get("/history/{device_id}", response_model=PaginatedHistoryResponse)
|
||||
async def get_conversation_history(
|
||||
device_id: str,
|
||||
authenticated_device_id: str = Depends(api_auth),
|
||||
role_key: Optional[str] = None,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
since: Optional[float] = None,
|
||||
until: Optional[float] = None
|
||||
):
|
||||
"""获取设备的对话历史记录,支持分页和时间范围筛选"""
|
||||
if device_id != authenticated_device_id:
|
||||
raise HTTPException(status_code=403, detail="无权访问其他设备的对话历史")
|
||||
|
||||
try:
|
||||
return await history_service.get_history_paginated(
|
||||
device_id, role_key, page, page_size, since, until
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取对话历史失败: {str(e)}")
|
||||
|
||||
@router.get("/history-summary/{device_id}")
|
||||
async def get_conversation_history_summary(
|
||||
device_id: str,
|
||||
authenticated_device_id: str = Depends(api_auth),
|
||||
days: int = Query(7, ge=1, le=30)
|
||||
):
|
||||
"""获取设备的对话历史摘要信息,包括每个角色的最后交互时间和消息数量"""
|
||||
if device_id != authenticated_device_id:
|
||||
raise HTTPException(status_code=403, detail="无权访问其他设备的对话历史摘要")
|
||||
|
||||
try:
|
||||
return await history_service.get_history_summary(device_id, days)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取对话历史摘要失败: {str(e)}")
|
||||
67
talkingq-url/api/system_config.py
Normal file
67
talkingq-url/api/system_config.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
from services.system_config_manager import system_config_manager
|
||||
from api.auth import api_auth
|
||||
|
||||
router = APIRouter(prefix="/api/system-config")
|
||||
|
||||
class SystemConfigResponse(BaseModel):
|
||||
key: str
|
||||
value: str
|
||||
created_at: Optional[float] = None
|
||||
updated_at: Optional[float] = None
|
||||
|
||||
class SystemConfigCreate(BaseModel):
|
||||
key: str
|
||||
value: str
|
||||
|
||||
class SystemConfigUpdate(BaseModel):
|
||||
value: str
|
||||
|
||||
@router.get("/", response_model=List[SystemConfigResponse])
|
||||
async def list_configs(authenticated_device_id: str = Depends(api_auth)):
|
||||
"""列出所有系统配置"""
|
||||
configs = await system_config_manager.list_configs()
|
||||
return [SystemConfigResponse(**c.__dict__) for c in configs]
|
||||
|
||||
@router.get("/{key}", response_model=SystemConfigResponse)
|
||||
async def get_config(key: str, authenticated_device_id: str = Depends(api_auth)):
|
||||
"""获取指定key的系统配置"""
|
||||
config = await system_config_manager.get_config(key)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="配置不存在")
|
||||
return SystemConfigResponse(**config.__dict__)
|
||||
|
||||
@router.post("/", response_model=SystemConfigResponse)
|
||||
async def create_config(
|
||||
config: SystemConfigCreate,
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""新增系统配置"""
|
||||
success = await system_config_manager.create_config(config.key, config.value)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="创建配置失败")
|
||||
new_config = await system_config_manager.get_config(config.key)
|
||||
return SystemConfigResponse(**new_config.__dict__)
|
||||
|
||||
@router.put("/{key}", response_model=SystemConfigResponse)
|
||||
async def update_config(
|
||||
key: str,
|
||||
config: SystemConfigUpdate,
|
||||
authenticated_device_id: str = Depends(api_auth)
|
||||
):
|
||||
"""更新系统配置"""
|
||||
success = await system_config_manager.update_config(key, config.value)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="更新配置失败")
|
||||
updated_config = await system_config_manager.get_config(key)
|
||||
return SystemConfigResponse(**updated_config.__dict__)
|
||||
|
||||
@router.delete("/{key}")
|
||||
async def delete_config(key: str, authenticated_device_id: str = Depends(api_auth)):
|
||||
"""删除系统配置"""
|
||||
success = await system_config_manager.delete_config(key)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="删除配置失败")
|
||||
return {"detail": "删除成功"}
|
||||
9
talkingq-url/api/websocket.py
Normal file
9
talkingq-url/api/websocket.py
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
from fastapi import APIRouter, WebSocket
|
||||
from handlers.websocket_handler import websocket_endpoint
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.websocket("/ws")
|
||||
async def websocket_route(websocket: WebSocket):
|
||||
await websocket_endpoint(websocket)
|
||||
Reference in New Issue
Block a user