653472def3
对齐 VeighNa 4.4 原生 Qt UI,新增成交监控、资金监控、网关管理、全局配置等页面与 API,功能对等性 98.5%。 - 新增 API: /api/v1/trades, /api/v1/accounts, /api/v1/settings, 网关扩展 - 新增前端页面: 成交、资金、合约、网关、全局配置、微信通知 - 扩展导航菜单与实时数据推送 - 补充需求分析与实现计划文档
191 lines
5.3 KiB
Python
191 lines
5.3 KiB
Python
"""
|
|
依赖注入模块
|
|
提供认证、数据库等依赖注入函数
|
|
"""
|
|
from typing import Generator, Optional
|
|
from fastapi import Depends, HTTPException, status
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
from jose import JWTError, jwt
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# JWT 配置
|
|
SECRET_KEY = "sanguo_secret_key_change_in_production" # 生产环境应从配置读取
|
|
ALGORITHM = "HS256"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
|
|
|
|
|
security = HTTPBearer()
|
|
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
"""创建 JWT Token"""
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.utcnow() + expires_delta
|
|
else:
|
|
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
|
|
to_encode.update({"exp": expire})
|
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
return encoded_jwt
|
|
|
|
|
|
def verify_token(token: str) -> dict:
|
|
"""验证 JWT Token"""
|
|
try:
|
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
return payload
|
|
except JWTError as e:
|
|
logger.warning(f"Token verification failed: {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authentication credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
|
|
async def get_current_user(
|
|
credentials: HTTPAuthorizationCredentials = Depends(security)
|
|
) -> dict:
|
|
"""
|
|
获取当前用户依赖
|
|
从 Authorization header 中解析 JWT Token
|
|
"""
|
|
token = credentials.credentials
|
|
|
|
try:
|
|
payload = verify_token(token)
|
|
|
|
username: str = payload.get("sub")
|
|
if username is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authentication credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
# 返回用户信息(实际应从数据库获取)
|
|
return {
|
|
"username": username,
|
|
"is_active": payload.get("is_active", True),
|
|
"exp": payload.get("exp")
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error getting current user: {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Could not validate credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
|
|
async def get_optional_user(
|
|
credentials: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer(auto_error=False))
|
|
) -> Optional[dict]:
|
|
"""
|
|
可选的用户认证依赖
|
|
允许未登录用户访问,但如果提供了 Token 则会验证
|
|
"""
|
|
if credentials is None:
|
|
return None
|
|
|
|
try:
|
|
return await get_current_user(credentials)
|
|
except HTTPException:
|
|
return None
|
|
|
|
|
|
# ============================================
|
|
# VeighNa 服务依赖
|
|
# ============================================
|
|
|
|
async def get_vn_service():
|
|
"""
|
|
获取 VeighNa 服务实例
|
|
如果服务未初始化,抛出异常
|
|
使用动态导入避免循环导入问题
|
|
"""
|
|
# 动态获取 vn_service,避免循环导入时的静态绑定问题
|
|
import sys
|
|
api_module = sys.modules.get('sanguo_web.api')
|
|
if api_module is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="API module not loaded"
|
|
)
|
|
|
|
vn_service = getattr(api_module, 'vn_service', None)
|
|
|
|
if vn_service is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="VeighNa service is not initialized"
|
|
)
|
|
|
|
return vn_service
|
|
|
|
|
|
# ============================================
|
|
# 简单认证(开发环境)
|
|
# ============================================
|
|
|
|
# 硬编码的用户数据库(生产环境应使用真实数据库)
|
|
FAKE_USERS_DB = {
|
|
"admin": {
|
|
"username": "admin",
|
|
"full_name": "Administrator",
|
|
"email": "admin@sanguo.com",
|
|
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36Wh0/mqGKnKM0lQ5lEqxKe", # "secret"
|
|
"is_active": True,
|
|
}
|
|
}
|
|
|
|
|
|
def authenticate_user(username: str, password: str) -> Optional[dict]:
|
|
"""
|
|
验证用户凭证
|
|
生产环境应使用数据库和环境变量存储密码
|
|
"""
|
|
import os
|
|
|
|
# 从环境变量读取管理员密码
|
|
admin_password = os.environ.get("ADMIN_PASSWORD", "admin123")
|
|
|
|
# 验证管理员账户
|
|
if username == "admin" and password == admin_password:
|
|
return {
|
|
"username": "admin",
|
|
"is_active": True
|
|
}
|
|
|
|
# 生产环境应使用数据库和 passlib
|
|
# user = FAKE_USERS_DB.get(username)
|
|
# if not user:
|
|
# return None
|
|
# if not user["is_active"]:
|
|
# return None
|
|
# from passlib.context import CryptContext
|
|
# pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
# if not pwd_context.verify(password, user["hashed_password"]):
|
|
# return None
|
|
# return user
|
|
|
|
return None
|
|
|
|
|
|
__all__ = [
|
|
"create_access_token",
|
|
"verify_token",
|
|
"get_current_user",
|
|
"get_optional_user",
|
|
"get_vn_service",
|
|
"authenticate_user",
|
|
]
|