Files
DP/tempdocs/许可证验证接口文档.md

7.4 KiB
Raw Blame History

许可证验证接口文档

📋 接口概述

接口路径: POST /api/v1/auth/verify

功能: 验证用户的登录 token 是否有效(用于心跳检测)

调用频率: 每 60 秒调用一次


📥 请求参数

Headers

{
  "Content-Type": "application/json",
  "Authorization": "Bearer {token}"
}

Body (JSON)

{
  "username": "string",       // 用户名
  "device_id": "string",      // 设备 ID
  "timestamp": 1234567890     // 客户端时间戳(毫秒)
}

示例请求

POST /api/v1/auth/verify HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{
  "username": "zuowei",
  "device_id": "065dfbc8-9539-405e-8af0-7cfd56000e6",
  "timestamp": 1702828800000
}

📤 响应格式

成功响应200 OK

{
  "valid": true,
  "username": "zuowei",
  "expire_date": "2025-12-31T23:59:59Z"  // 可选:账户过期时间
}

失败响应 1Token 无效401 Unauthorized

{
  "detail": "Token 无效或已过期"
}

失败响应 2账户已过期403 Forbidden

{
  "valid": false,
  "message": "账户已过期"
}

失败响应 3会话不存在404 Not Found

{
  "detail": "会话不存在或已登出"
}

🔧 后端实现示例FastAPI

1. 在 Server/app/api/v1/auth.py 添加路由

from fastapi import Depends, HTTPException, status
from pydantic import BaseModel
from app.core.security import get_current_user
from app.db import get_db
from sqlalchemy.orm import Session
from app.models.user import User, UserSession
from datetime import datetime, timezone

class VerifyRequest(BaseModel):
    """验证请求"""
    username: str
    device_id: str
    timestamp: int

class VerifyResponse(BaseModel):
    """验证响应"""
    valid: bool
    username: str = None
    expire_date: str = None

@router.post("/verify", response_model=VerifyResponse)
async def verify_license(
    request: VerifyRequest,
    current_user: dict = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """
    验证许可证(心跳检测)
    检查:
    1. Token 是否有效(通过 Depends(get_current_user) 自动验证)
    2. 用户是否存在
    3. 会话是否活跃
    4. 账户是否过期
    """
    
    # 1. 查询用户
    user = db.query(User).filter(User.username == request.username).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="用户不存在"
        )
    
    # 2. 检查账户是否过期
    if user.expire_date:
        expire_dt = user.expire_date
        if expire_dt.tzinfo is None:
            expire_dt = expire_dt.replace(tzinfo=timezone.utc)
        
        if datetime.now(timezone.utc) > expire_dt:
            return VerifyResponse(
                valid=False,
                username=request.username,
                expire_date=user.expire_date.isoformat() if user.expire_date else None
            )
    
    # 3. 检查会话是否活跃
    session = db.query(UserSession).filter(
        UserSession.user_id == user.id,
        UserSession.device_id == request.device_id,
        UserSession.active == True
    ).first()
    
    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="会话不存在或已登出"
        )
    
    # 4. 更新最后活跃时间
    session.last_seen_at = datetime.now(timezone.utc)
    db.commit()
    
    # 5. 返回验证成功
    return VerifyResponse(
        valid=True,
        username=request.username,
        expire_date=user.expire_date.isoformat() if user.expire_date else None
    )

2. 更新 Server/app/schemas/auth.py

添加验证相关的 Schema

class VerifyRequest(BaseModel):
    """验证请求模型"""
    username: str
    device_id: str
    timestamp: int

class VerifyResponse(BaseModel):
    """验证响应模型"""
    valid: bool
    username: Optional[str] = None
    expire_date: Optional[str] = None

🧪 测试验证接口

使用 curl 测试

curl -X POST http://127.0.0.1:8000/api/v1/auth/verify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -d '{
    "username": "zuowei",
    "device_id": "test-device-123",
    "timestamp": 1702828800000
  }'

预期响应

成功:

{
  "valid": true,
  "username": "zuowei",
  "expire_date": null
}

Token 无效:

{
  "detail": "Could not validate credentials"
}

会话不存在:

{
  "detail": "会话不存在或已登出"
}

📊 数据库表设计(参考)

确保数据库有以下表和字段:

users 表

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username VARCHAR UNIQUE NOT NULL,
    expire_date DATETIME,  -- 账户过期时间NULL = 永久)
    ...
);

user_sessions 表

CREATE TABLE user_sessions (
    id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL,
    device_id VARCHAR NOT NULL,
    active BOOLEAN DEFAULT TRUE,
    last_seen_at DATETIME,  -- 最后活跃时间
    ...
    FOREIGN KEY (user_id) REFERENCES users(id)
);

🔐 安全注意事项

  1. Token 验证

    • 使用 Depends(get_current_user) 确保 Token 有效
    • Token 过期自动返回 401
  2. 会话管理

    • 验证成功时更新 last_seen_at
    • 可用于统计在线时长
  3. 过期检查

    • 支持账户过期时间
    • 过期返回 valid: false
  4. 频率控制

    • 前端已做 30 秒缓存,减少请求频率
    • 后端可添加 Rate Limiting

🚀 部署检查

添加接口后,确认:

  1. 路由已注册

    # Server/app/main.py
    app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth")
    
  2. 数据库表存在

    # 检查 users 和 user_sessions 表
    
  3. Token 验证正常

    # 测试登录获取 Token
    # 测试 verify 接口
    
  4. 前端心跳正常

    # 前端每 60 秒调用一次
    # 30 秒内不重复验证(有缓存)
    

📝 完整实现清单

  • 添加 VerifyRequestVerifyResponse Schema
  • auth.py 添加 /verify 路由
  • 实现 Token 验证逻辑
  • 实现账户过期检查
  • 实现会话活跃检查
  • 更新最后活跃时间
  • 测试接口(成功、失败、过期等场景)
  • 部署到服务器

💡 可选增强

1. 添加限流Rate Limiting

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@router.post("/verify")
@limiter.limit("120/minute")  # 每分钟最多 120 次
async def verify_license(...):
    ...

2. 添加详细日志

import logging

logger = logging.getLogger(__name__)

@router.post("/verify")
async def verify_license(...):
    logger.info(f"[Verify] {request.username} from {request.device_id}")
    ...

3. 返回更多信息

class VerifyResponse(BaseModel):
    valid: bool
    username: str = None
    expire_date: str = None
    permissions: list = []  # 用户权限列表
    online_time: int = 0    # 在线时长(秒)

实现这个接口后,前端的心跳检测就能正常工作了!