imetting/backend/app/core/middleware.py

82 lines
3.4 KiB
Python
Raw Normal View History

2026-02-06 07:57:34 +00:00
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获取
2026-02-06 09:14:37 +00:00
client_time_str = request.headers.get("time") or request.query_params.get("time")
2026-02-06 07:57:34 +00:00
2026-02-06 09:14:37 +00:00
if client_time_str:
try:
client_time = int(client_time_str)
server_time = int(time.time() * 1000)
2026-02-06 07:57:34 +00:00
2026-02-06 09:14:37 +00:00
# 允许 10 分钟的误差 (10 * 60 * 1000 = 600000 ms)
# 考虑到网络延迟和设备时间未校准,设置宽松一点
if abs(server_time - client_time) > 600000:
return create_api_response(
code="400",
message="设备时间与服务器时间差距过大,请校准时间"
)
except ValueError:
# 时间格式错误,暂时忽略或返回错误
pass
2026-02-06 07:57:34 +00:00
# 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