372 lines
7.4 KiB
Markdown
372 lines
7.4 KiB
Markdown
# 许可证验证接口文档
|
||
|
||
## 📋 接口概述
|
||
|
||
**接口路径:** `POST /api/v1/auth/verify`
|
||
|
||
**功能:** 验证用户的登录 token 是否有效(用于心跳检测)
|
||
|
||
**调用频率:** 每 60 秒调用一次
|
||
|
||
---
|
||
|
||
## 📥 请求参数
|
||
|
||
### Headers
|
||
```json
|
||
{
|
||
"Content-Type": "application/json",
|
||
"Authorization": "Bearer {token}"
|
||
}
|
||
```
|
||
|
||
### Body (JSON)
|
||
```json
|
||
{
|
||
"username": "string", // 用户名
|
||
"device_id": "string", // 设备 ID
|
||
"timestamp": 1234567890 // 客户端时间戳(毫秒)
|
||
}
|
||
```
|
||
|
||
### 示例请求
|
||
```http
|
||
POST /api/v1/auth/verify HTTP/1.1
|
||
Host: 127.0.0.1:8000
|
||
Content-Type: application/json
|
||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||
|
||
```
|
||
|
||
---
|
||
|
||
## 📤 响应格式
|
||
|
||
### 成功响应(200 OK)
|
||
|
||
```json
|
||
{
|
||
"valid": true,
|
||
"username": "zuowei",
|
||
"expire_date": "2025-12-31T23:59:59Z" // 可选:账户过期时间
|
||
}
|
||
```
|
||
|
||
### 失败响应 1:Token 无效(401 Unauthorized)
|
||
|
||
```json
|
||
{
|
||
"detail": "Token 无效或已过期"
|
||
}
|
||
```
|
||
|
||
### 失败响应 2:账户已过期(403 Forbidden)
|
||
|
||
```json
|
||
{
|
||
"valid": false,
|
||
"message": "账户已过期"
|
||
}
|
||
```
|
||
|
||
### 失败响应 3:会话不存在(404 Not Found)
|
||
|
||
```json
|
||
{
|
||
"detail": "会话不存在或已登出"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 后端实现示例(FastAPI)
|
||
|
||
### 1. 在 `Server/app/api/v1/auth.py` 添加路由
|
||
|
||
```python
|
||
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:
|
||
|
||
```python
|
||
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 测试
|
||
|
||
```bash
|
||
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
|
||
}'
|
||
```
|
||
|
||
### 预期响应
|
||
|
||
**成功:**
|
||
```json
|
||
{
|
||
"valid": true,
|
||
"username": "zuowei",
|
||
"expire_date": null
|
||
}
|
||
```
|
||
|
||
**Token 无效:**
|
||
```json
|
||
{
|
||
"detail": "Could not validate credentials"
|
||
}
|
||
```
|
||
|
||
**会话不存在:**
|
||
```json
|
||
{
|
||
"detail": "会话不存在或已登出"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 数据库表设计(参考)
|
||
|
||
确保数据库有以下表和字段:
|
||
|
||
### users 表
|
||
```sql
|
||
CREATE TABLE users (
|
||
id INTEGER PRIMARY KEY,
|
||
username VARCHAR UNIQUE NOT NULL,
|
||
expire_date DATETIME, -- 账户过期时间(NULL = 永久)
|
||
...
|
||
);
|
||
```
|
||
|
||
### user_sessions 表
|
||
```sql
|
||
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. ✅ 路由已注册
|
||
```python
|
||
# Server/app/main.py
|
||
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth")
|
||
```
|
||
|
||
2. ✅ 数据库表存在
|
||
```bash
|
||
# 检查 users 和 user_sessions 表
|
||
```
|
||
|
||
3. ✅ Token 验证正常
|
||
```bash
|
||
# 测试登录获取 Token
|
||
# 测试 verify 接口
|
||
```
|
||
|
||
4. ✅ 前端心跳正常
|
||
```
|
||
# 前端每 60 秒调用一次
|
||
# 30 秒内不重复验证(有缓存)
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 完整实现清单
|
||
|
||
- [ ] 添加 `VerifyRequest` 和 `VerifyResponse` Schema
|
||
- [ ] 在 `auth.py` 添加 `/verify` 路由
|
||
- [ ] 实现 Token 验证逻辑
|
||
- [ ] 实现账户过期检查
|
||
- [ ] 实现会话活跃检查
|
||
- [ ] 更新最后活跃时间
|
||
- [ ] 测试接口(成功、失败、过期等场景)
|
||
- [ ] 部署到服务器
|
||
|
||
---
|
||
|
||
## 💡 可选增强
|
||
|
||
### 1. 添加限流(Rate Limiting)
|
||
|
||
```python
|
||
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. 添加详细日志
|
||
|
||
```python
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
@router.post("/verify")
|
||
async def verify_license(...):
|
||
logger.info(f"[Verify] {request.username} from {request.device_id}")
|
||
...
|
||
```
|
||
|
||
### 3. 返回更多信息
|
||
|
||
```python
|
||
class VerifyResponse(BaseModel):
|
||
valid: bool
|
||
username: str = None
|
||
expire_date: str = None
|
||
permissions: list = [] # 用户权限列表
|
||
online_time: int = 0 # 在线时长(秒)
|
||
```
|
||
|
||
---
|
||
|
||
**实现这个接口后,前端的心跳检测就能正常工作了!** ✅
|
||
|