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
|