imetting/backend/app/core/middleware.py

82 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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