from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request, Response import time from app.services.terminal_service import terminal_service from app.services.jwt_service import jwt_service from app.core.response import create_api_response class TerminalCheckMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): # 1. 检查是否有 Imei 头,没有则认为是普通请求,直接放行 imei = request.headers.get("Imei") if not imei: return await call_next(request) # 2. 检查时间戳 (防重放/时钟同步) # 优先从Header获取,如果没有则尝试从Query Parameter获取 # client_time_str = request.headers.get("time") or request.query_params.get("time") # if client_time_str: # try: # client_time = int(client_time_str) # server_time = int(time.time() * 1000) # # 允许 10 分钟的误差 (10 * 60 * 1000 = 600000 ms) # # 考虑到网络延迟和设备时间未校准,设置宽松一点 # if abs(server_time - client_time) > 600000: # return create_api_response( # code="400", # message="设备时间与服务器时间差距过大,请校准时间" # ) # except ValueError: # # 时间格式错误,暂时忽略或返回错误 # pass # 3. 提取其他设备信息 device_type = request.headers.get("deviceType", "UNKNOWN") # device_info 可能是 "UNIS iMeeting a7" device_info = request.headers.get("deviceInfo", "Unknown Device") # 获取客户端IP (考虑代理) client_ip = request.client.host if "x-forwarded-for" in request.headers: client_ip = request.headers["x-forwarded-for"].split(",")[0].strip() elif "x-real-ip" in request.headers: client_ip = request.headers["x-real-ip"] # 获取当前用户ID (如果已登录) user_id = None auth_header = request.headers.get("Authorization") if auth_header and auth_header.startswith("Bearer "): try: token = auth_header.split(" ")[1] payload = jwt_service.verify_token(token) if payload: user_id = payload.get("user_id") except Exception: pass # 忽略token解析错误,只记录设备在线状态 # 4. 调用服务进行检查和更新 # 注意:这里是同步调用数据库,但在 async 中可能会阻塞 loop # 理想情况下 terminal_service 应该是 async 的,或者使用 run_in_executor # 但由于数据库操作较快,且 mysql-connector 是同步的,暂时直接调用 # 如果并发高,建议将 service 改为 async result = terminal_service.check_and_update_terminal( imei=imei, terminal_type=device_type, terminal_name=device_info, ip_address=client_ip, user_id=user_id ) if not result["allowed"]: return create_api_response( code="403", message=result["reason"] ) # 5. 放行请求 response = await call_next(request) return response