fix: 添加auth功能

This commit is contained in:
root
2025-07-12 18:01:17 +08:00
parent 41363d182e
commit e4d4677f3a
12 changed files with 1725 additions and 91 deletions

244
python_core/api/auth_api.py Normal file
View File

@@ -0,0 +1,244 @@
"""
用户认证API接口
"""
from typing import Dict, Any, Optional
import json
from python_core.models.user import LoginRequest, RegisterRequest
from python_core.services.auth_service import auth_service
from python_core.utils.logger import logger
class AuthAPI:
"""用户认证API"""
def __init__(self):
self.auth_service = auth_service
logger.info("AuthAPI initialized")
def register(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
用户注册API
Args:
data: 注册数据
{
"username": "用户名",
"email": "邮箱",
"password": "密码",
"display_name": "显示名称" (可选)
}
Returns:
Dict: API响应
"""
try:
# 创建注册请求对象
request = RegisterRequest(
username=data.get("username", "").strip(),
email=data.get("email", "").strip(),
password=data.get("password", ""),
display_name=data.get("display_name", "").strip() or None
)
# 执行注册
response = self.auth_service.register(request)
# 返回API响应
return {
"success": response.success,
"message": response.message,
"data": {
"user": response.user,
"token": response.token,
"expires_at": response.expires_at
} if response.success else None
}
except Exception as e:
logger.error(f"Register API error: {e}")
return {
"success": False,
"message": "注册失败,请稍后重试",
"data": None
}
def login(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
用户登录API
Args:
data: 登录数据
{
"username_or_email": "用户名或邮箱",
"password": "密码"
}
Returns:
Dict: API响应
"""
try:
# 创建登录请求对象
request = LoginRequest(
username_or_email=data.get("username_or_email", "").strip(),
password=data.get("password", "")
)
# 执行登录
response = self.auth_service.login(request)
# 返回API响应
return {
"success": response.success,
"message": response.message,
"data": {
"user": response.user,
"token": response.token,
"expires_at": response.expires_at
} if response.success else None
}
except Exception as e:
logger.error(f"Login API error: {e}")
return {
"success": False,
"message": "登录失败,请稍后重试",
"data": None
}
def verify_token(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
验证token API
Args:
data: 验证数据
{
"token": "JWT token"
}
Returns:
Dict: API响应
"""
try:
token = data.get("token", "")
if not token:
return {
"success": False,
"message": "Token不能为空",
"data": None
}
# 验证token
user_info = self.auth_service.verify_token(token)
if user_info:
return {
"success": True,
"message": "Token验证成功",
"data": {
"user": user_info,
"valid": True
}
}
else:
return {
"success": False,
"message": "Token无效或已过期",
"data": {
"valid": False
}
}
except Exception as e:
logger.error(f"Verify token API error: {e}")
return {
"success": False,
"message": "Token验证失败",
"data": None
}
def get_current_user(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
获取当前用户信息API
Args:
data: 请求数据
{
"token": "JWT token"
}
Returns:
Dict: API响应
"""
try:
token = data.get("token", "")
if not token:
return {
"success": False,
"message": "Token不能为空",
"data": None
}
# 获取当前用户
user = self.auth_service.get_current_user(token)
if user:
return {
"success": True,
"message": "获取用户信息成功",
"data": {
"user": user.to_safe_dict()
}
}
else:
return {
"success": False,
"message": "用户不存在或token无效",
"data": None
}
except Exception as e:
logger.error(f"Get current user API error: {e}")
return {
"success": False,
"message": "获取用户信息失败",
"data": None
}
# 创建全局认证API实例
auth_api = AuthAPI()
# JSON-RPC风格的便捷函数
def register_user(username: str, email: str, password: str, display_name: Optional[str] = None) -> Dict[str, Any]:
"""注册用户的便捷函数"""
return auth_api.register({
"username": username,
"email": email,
"password": password,
"display_name": display_name
})
def login_user(username_or_email: str, password: str) -> Dict[str, Any]:
"""登录用户的便捷函数"""
return auth_api.login({
"username_or_email": username_or_email,
"password": password
})
def verify_user_token(token: str) -> Dict[str, Any]:
"""验证用户token的便捷函数"""
return auth_api.verify_token({
"token": token
})
def get_user_info(token: str) -> Dict[str, Any]:
"""获取用户信息的便捷函数"""
return auth_api.get_current_user({
"token": token
})

View File

@@ -11,6 +11,7 @@ import typer
from python_core.cli.commands import scene_detect
from python_core.cli.commands.template import template_app
from python_core.cli.commands.category import category_app
from python_core.cli.commands.auth import auth_app
app = typer.Typer(
name="mixvideo",
@@ -21,6 +22,7 @@ app = typer.Typer(
• 🎯 场景检测 - 智能识别视频场景变化
• 📋 模板管理 - 视频模板批量导入、列表查看、详情获取
• 🏷️ 分类管理 - 资源分类创建、更新、删除、搜索
• 👤 用户认证 - 用户注册、登录、JWT token管理
• 📤 媒体管理 - 上传、处理、组织视频文件
• ⚙️ 系统管理 - 配置、状态、存储管理
""",
@@ -32,6 +34,7 @@ app = typer.Typer(
app.add_typer(scene_detect, name="scene")
app.add_typer(template_app, name="template")
app.add_typer(category_app, name="category")
app.add_typer(auth_app, name="auth")
@app.command()
def init():

View File

@@ -0,0 +1,315 @@
"""
用户认证CLI命令
"""
from typing import Optional
import typer
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
import json
import getpass
from python_core.api.auth_api import auth_api
from python_core.services.user_storage import user_storage
from python_core.utils.jwt_auth import jwt_auth
console = Console()
auth_app = typer.Typer(name="auth", help="用户认证管理命令")
@auth_app.command("register")
def register_user(
username: str = typer.Argument(..., help="用户名"),
email: str = typer.Argument(..., help="邮箱地址"),
display_name: Optional[str] = typer.Option(None, "--display-name", "-d", help="显示名称"),
password: Optional[str] = typer.Option(None, "--password", "-p", help="密码(不提供则交互式输入)"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
):
"""注册新用户"""
try:
console.print(f"🆕 [bold blue]用户注册[/bold blue]")
console.print(f"用户名: {username}")
console.print(f"邮箱: {email}")
if display_name:
console.print(f"显示名称: {display_name}")
# 获取密码
if not password:
password = getpass.getpass("请输入密码: ")
if not password:
console.print("[red]❌ 密码不能为空[/red]")
raise typer.Exit(1)
# 执行注册
result = auth_api.register({
"username": username,
"email": email,
"password": password,
"display_name": display_name
})
if result["success"]:
console.print(f"\n✅ [bold green]注册成功![/bold green]")
console.print(f"用户ID: {result['data']['user']['id']}")
console.print(f"创建时间: {result['data']['user']['created_at']}")
if verbose:
# 显示详细信息
user_info = f"""
🆔 用户ID: {result['data']['user']['id']}
👤 用户名: {result['data']['user']['username']}
📧 邮箱: {result['data']['user']['email']}
🏷️ 显示名称: {result['data']['user']['display_name']}
📅 创建时间: {result['data']['user']['created_at']}
🔑 Token: {result['data']['token'][:50]}...
⏰ 过期时间: {result['data']['expires_at']}
"""
panel = Panel(user_info.strip(), title="用户详情", border_style="green")
console.print(panel)
else:
console.print(f"[red]❌ 注册失败: {result['message']}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]❌ 注册失败: {str(e)}[/red]")
raise typer.Exit(1)
@auth_app.command("login")
def login_user(
username_or_email: str = typer.Argument(..., help="用户名或邮箱"),
password: Optional[str] = typer.Option(None, "--password", "-p", help="密码(不提供则交互式输入)"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
):
"""用户登录"""
try:
console.print(f"🔐 [bold blue]用户登录[/bold blue]")
console.print(f"用户名/邮箱: {username_or_email}")
# 获取密码
if not password:
password = getpass.getpass("请输入密码: ")
if not password:
console.print("[red]❌ 密码不能为空[/red]")
raise typer.Exit(1)
# 执行登录
result = auth_api.login({
"username_or_email": username_or_email,
"password": password
})
if result["success"]:
console.print(f"\n✅ [bold green]登录成功![/bold green]")
console.print(f"欢迎回来,{result['data']['user']['display_name']}!")
console.print(f"最后登录: {result['data']['user']['last_login']}")
if verbose:
# 显示详细信息
user_info = f"""
🆔 用户ID: {result['data']['user']['id']}
👤 用户名: {result['data']['user']['username']}
📧 邮箱: {result['data']['user']['email']}
🏷️ 显示名称: {result['data']['user']['display_name']}
🕒 最后登录: {result['data']['user']['last_login']}
🔑 Token: {result['data']['token'][:50]}...
⏰ 过期时间: {result['data']['expires_at']}
"""
panel = Panel(user_info.strip(), title="登录信息", border_style="green")
console.print(panel)
else:
console.print(f"[red]❌ 登录失败: {result['message']}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]❌ 登录失败: {str(e)}[/red]")
raise typer.Exit(1)
@auth_app.command("verify")
def verify_token(
token: str = typer.Argument(..., help="JWT token"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
):
"""验证JWT token"""
try:
console.print(f"🔍 [bold blue]验证Token[/bold blue]")
# 验证token
result = auth_api.verify_token({
"token": token
})
if result["success"]:
console.print(f"\n✅ [bold green]Token有效![/bold green]")
user = result['data']['user']
console.print(f"用户: {user['display_name']} ({user['username']})")
console.print(f"邮箱: {user['email']}")
if verbose:
# 显示token详细信息
token_info = jwt_auth.get_token_info(token)
info_text = f"""
👤 用户信息:
ID: {user['user_id']}
用户名: {user['username']}
邮箱: {user['email']}
显示名称: {user['display_name']}
🔑 Token信息:
有效性: {'有效' if token_info['valid'] else '无效'}
签发时间: {token_info.get('issued_at', 'Unknown')}
过期时间: {token_info.get('expires_at', 'Unknown')}
剩余时间: {token_info.get('time_remaining', 'Unknown')}
"""
panel = Panel(info_text.strip(), title="Token详情", border_style="green")
console.print(panel)
else:
console.print(f"[red]❌ Token无效: {result['message']}[/red]")
if verbose:
# 显示token信息即使无效
token_info = jwt_auth.get_token_info(token)
if token_info.get('error'):
console.print(f"错误: {token_info['error']}")
else:
console.print(f"Token状态: {'过期' if token_info.get('is_expired') else '无效'}")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]❌ Token验证失败: {str(e)}[/red]")
raise typer.Exit(1)
@auth_app.command("list")
def list_users(
include_inactive: bool = typer.Option(False, "--include-inactive", "-i", help="包含非活跃用户"),
limit: int = typer.Option(20, "--limit", "-l", help="显示数量限制"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
):
"""列出所有用户"""
try:
console.print(f"👥 [bold blue]用户列表[/bold blue]")
# 获取用户列表
users = user_storage.get_all_users(include_inactive)
if not users:
console.print("📭 没有用户")
return
# 限制显示数量
total_count = len(users)
if len(users) > limit:
users = users[:limit]
console.print(f"📊 显示前 {limit} 个用户(共 {total_count} 个)")
else:
console.print(f"📊 共 {total_count} 个用户")
# 创建表格
table = Table(title="用户列表")
table.add_column("ID", style="cyan", width=8)
table.add_column("用户名", style="green")
table.add_column("邮箱", style="yellow")
table.add_column("显示名称", style="magenta")
table.add_column("状态", style="blue")
table.add_column("创建时间", style="dim")
if verbose:
table.add_column("最后登录", style="dim")
for user in users:
# 状态
status = "✅ 活跃" if user.is_active else "❌ 禁用"
# 格式化创建时间
created_at = user.created_at
if 'T' in created_at:
created_at = created_at.split('T')[0] + ' ' + created_at.split('T')[1][:8]
row = [
user.id[:8],
user.username,
user.email,
user.display_name,
status,
created_at
]
if verbose:
last_login = user.last_login or "从未登录"
if last_login != "从未登录" and 'T' in last_login:
last_login = last_login.split('T')[0] + ' ' + last_login.split('T')[1][:8]
row.append(last_login)
table.add_row(*row)
console.print(table)
except Exception as e:
console.print(f"[red]❌ 获取用户列表失败: {str(e)}[/red]")
raise typer.Exit(1)
@auth_app.command("stats")
def show_stats():
"""显示用户统计信息"""
try:
console.print(f"📊 [bold blue]用户统计信息[/bold blue]")
# 获取统计信息
stats = user_storage.get_user_count()
# 创建统计面板
stats_content = f"""
📈 [bold green]用户统计[/bold green]
总用户数: {stats['total']}
活跃用户: {stats['active']}
禁用用户: {stats['inactive']}
"""
stats_panel = Panel(stats_content.strip(), title="用户统计", border_style="green")
console.print(stats_panel)
# 显示最近注册的用户
recent_users = user_storage.get_all_users()[:5]
if recent_users:
console.print(f"\n🕒 [bold green]最近注册的用户[/bold green]")
recent_table = Table()
recent_table.add_column("用户名", style="green")
recent_table.add_column("邮箱", style="yellow")
recent_table.add_column("注册时间", style="dim")
for user in recent_users:
created_at = user.created_at
if 'T' in created_at:
created_at = created_at.split('T')[0] + ' ' + created_at.split('T')[1][:8]
recent_table.add_row(
user.username,
user.email,
created_at
)
console.print(recent_table)
except Exception as e:
console.print(f"[red]❌ 获取统计信息失败: {str(e)}[/red]")
raise typer.Exit(1)
if __name__ == "__main__":
auth_app()

View File

@@ -2,6 +2,23 @@ from dataclasses import dataclass
from typing import Dict, List, Any, Optional
@dataclass
class ResourceCategory:
"""素材分类数据结构"""
id: str
title: str # 分类标题
ai_prompt: str # AI识别提示词
color: str # 展示颜色 (hex格式如 #FF5733)
created_at: str
updated_at: str
is_active: bool = True
# is_cloud false 代表本地资源 那么只加载自己的 如果是 true 就是公用的
is_cloud: bool
# 创建用户
user_id: str
@dataclass
class MaterialInfo:
"""Material information structure"""
@@ -13,6 +30,11 @@ class MaterialInfo:
relative_path: str
duration: Optional[int] = None
size: Optional[int] = None
# is_cloud false 代表本地资源 那么只加载自己的 如果是 true 就是公用的
is_cloud: bool
# 创建用户
user_id: str
@dataclass
class TemplateInfo:
@@ -29,7 +51,7 @@ class TemplateInfo:
material_count: int
track_count: int
tags: List[str]
# local/cloud 如果类型是 local 那么只加载自己的 如果是 cloud 就是公用的
type: str
# is_cloud false 代表本地资源 那么只加载自己的 如果是 true 就是公用的
is_cloud: bool
# 创建用户
user_id: str

View File

@@ -0,0 +1,8 @@
# 用户表
from python_core.kv import kv
from .db import Db
class UserDb(Db):
def __init__(self):
self.key = "model"
pass

View File

@@ -0,0 +1,96 @@
"""
用户数据模型
"""
from dataclasses import dataclass, asdict
from datetime import datetime
from typing import Optional, Dict, Any
import uuid
import hashlib
import json
@dataclass
class User:
"""用户数据模型"""
id: str
username: str
email: str
password_hash: str
display_name: str
avatar_url: Optional[str] = None
is_active: bool = True
created_at: str = ""
updated_at: str = ""
last_login: Optional[str] = None
def __post_init__(self):
if not self.created_at:
self.created_at = datetime.now().isoformat()
if not self.updated_at:
self.updated_at = datetime.now().isoformat()
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return asdict(self)
def to_safe_dict(self) -> Dict[str, Any]:
"""转换为安全字典(不包含密码)"""
data = self.to_dict()
data.pop('password_hash', None)
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'User':
"""从字典创建用户对象"""
return cls(**data)
def update_last_login(self):
"""更新最后登录时间"""
self.last_login = datetime.now().isoformat()
self.updated_at = datetime.now().isoformat()
@dataclass
class LoginRequest:
"""登录请求数据"""
username_or_email: str
password: str
@dataclass
class RegisterRequest:
"""注册请求数据"""
username: str
email: str
password: str
display_name: Optional[str] = None
def __post_init__(self):
if not self.display_name:
self.display_name = self.username
@dataclass
class AuthResponse:
"""认证响应数据"""
success: bool
message: str
user: Optional[Dict[str, Any]] = None
token: Optional[str] = None
expires_at: Optional[str] = None
def hash_password(password: str) -> str:
"""密码哈希"""
return hashlib.sha256(password.encode()).hexdigest()
def verify_password(password: str, password_hash: str) -> bool:
"""验证密码"""
return hash_password(password) == password_hash
def generate_user_id() -> str:
"""生成用户ID"""
return str(uuid.uuid4())

View File

@@ -30,4 +30,5 @@ rich
langgraph
json-rpc
langchain_core
langchain_anthropic
langchain_anthropic
PyJWT

View File

@@ -0,0 +1,263 @@
"""
用户认证服务
"""
import re
from typing import Optional, Dict, Any
from datetime import datetime
from python_core.models.user import (
User, LoginRequest, RegisterRequest, AuthResponse,
hash_password, verify_password
)
from python_core.services.user_storage import user_storage
from python_core.utils.jwt_auth import generate_access_token, verify_access_token
from python_core.utils.logger import logger
class AuthService:
"""用户认证服务"""
def __init__(self):
self.user_storage = user_storage
logger.info("AuthService initialized")
def register(self, request: RegisterRequest) -> AuthResponse:
"""
用户注册
Args:
request: 注册请求数据
Returns:
AuthResponse: 注册响应
"""
try:
# 验证输入数据
validation_error = self._validate_register_request(request)
if validation_error:
return AuthResponse(
success=False,
message=validation_error
)
# 密码哈希
password_hash = hash_password(request.password)
# 创建用户
user = self.user_storage.create_user(
username=request.username,
email=request.email,
password_hash=password_hash,
display_name=request.display_name
)
# 生成JWT token
token_info = generate_access_token(
user_id=user.id,
username=user.username,
email=user.email
)
logger.info(f"User registered successfully: {user.username}")
return AuthResponse(
success=True,
message="注册成功",
user=user.to_safe_dict(),
token=token_info["token"],
expires_at=token_info["expires_at"]
)
except ValueError as e:
logger.warning(f"Registration failed: {e}")
return AuthResponse(
success=False,
message=str(e)
)
except Exception as e:
logger.error(f"Registration error: {e}")
return AuthResponse(
success=False,
message="注册失败,请稍后重试"
)
def login(self, request: LoginRequest) -> AuthResponse:
"""
用户登录
Args:
request: 登录请求数据
Returns:
AuthResponse: 登录响应
"""
try:
# 验证输入数据
validation_error = self._validate_login_request(request)
if validation_error:
return AuthResponse(
success=False,
message=validation_error
)
# 查找用户
user = self.user_storage.get_user_by_username_or_email(request.username_or_email)
if not user:
return AuthResponse(
success=False,
message="用户名或密码错误"
)
# 检查用户状态
if not user.is_active:
return AuthResponse(
success=False,
message="账户已被禁用"
)
# 验证密码
if not verify_password(request.password, user.password_hash):
return AuthResponse(
success=False,
message="用户名或密码错误"
)
# 更新最后登录时间
user.update_last_login()
self.user_storage.update_user(user)
# 生成JWT token
token_info = generate_access_token(
user_id=user.id,
username=user.username,
email=user.email
)
logger.info(f"User logged in successfully: {user.username}")
return AuthResponse(
success=True,
message="登录成功",
user=user.to_safe_dict(),
token=token_info["token"],
expires_at=token_info["expires_at"]
)
except Exception as e:
logger.error(f"Login error: {e}")
return AuthResponse(
success=False,
message="登录失败,请稍后重试"
)
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
"""
验证JWT token
Args:
token: JWT token字符串
Returns:
Dict: 用户信息验证失败返回None
"""
try:
# 验证token
payload = verify_access_token(token)
if not payload:
return None
# 获取用户信息
user = self.user_storage.get_user_by_id(payload["user_id"])
if not user or not user.is_active:
return None
return {
"user_id": user.id,
"username": user.username,
"email": user.email,
"display_name": user.display_name,
"avatar_url": user.avatar_url,
"last_login": user.last_login
}
except Exception as e:
logger.error(f"Token verification error: {e}")
return None
def get_current_user(self, token: str) -> Optional[User]:
"""
根据token获取当前用户
Args:
token: JWT token字符串
Returns:
User: 用户对象失败返回None
"""
try:
payload = verify_access_token(token)
if not payload:
return None
user = self.user_storage.get_user_by_id(payload["user_id"])
if not user or not user.is_active:
return None
return user
except Exception as e:
logger.error(f"Get current user error: {e}")
return None
def _validate_register_request(self, request: RegisterRequest) -> Optional[str]:
"""验证注册请求数据"""
# 验证用户名
if not request.username or len(request.username.strip()) < 3:
return "用户名至少需要3个字符"
if len(request.username) > 50:
return "用户名不能超过50个字符"
if not re.match(r'^[a-zA-Z0-9_]+$', request.username):
return "用户名只能包含字母、数字和下划线"
# 验证邮箱
if not request.email:
return "邮箱地址不能为空"
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, request.email):
return "邮箱地址格式不正确"
# 验证密码
if not request.password:
return "密码不能为空"
if len(request.password) < 6:
return "密码至少需要6个字符"
if len(request.password) > 100:
return "密码不能超过100个字符"
# 验证显示名称
if request.display_name and len(request.display_name) > 100:
return "显示名称不能超过100个字符"
return None
def _validate_login_request(self, request: LoginRequest) -> Optional[str]:
"""验证登录请求数据"""
if not request.username_or_email:
return "用户名或邮箱不能为空"
if not request.password:
return "密码不能为空"
return None
# 创建全局认证服务实例
auth_service = AuthService()

View File

@@ -144,91 +144,3 @@ class ResourceCategoryManager:
results.append(asdict(category))
return results
# 全局实例
resource_category_manager = ResourceCategoryManager()
def main():
"""命令行接口 - 使用JSON-RPC协议"""
import sys
import json
# 创建响应处理器
rpc = create_response_handler()
if len(sys.argv) < 2:
rpc.error("INVALID_REQUEST", "No command specified")
return
command = sys.argv[1]
try:
if command == "get_all_categories":
categories = resource_category_manager.get_all_categories()
rpc.success(categories)
elif command == "get_category_by_id":
if len(sys.argv) < 3:
rpc.error("INVALID_REQUEST", "Category ID required")
return
category_id = sys.argv[2]
category = resource_category_manager.get_category_by_id(category_id)
if category:
rpc.success(category)
else:
rpc.error("NOT_FOUND", "Category not found")
elif command == "create_category":
if len(sys.argv) < 5:
rpc.error("INVALID_REQUEST", "Title, prompt and color required")
return
title, ai_prompt, color = sys.argv[2], sys.argv[3], sys.argv[4]
result = resource_category_manager.create_category(title, ai_prompt, color)
rpc.success(result)
elif command == "update_category":
if len(sys.argv) < 4:
rpc.error("INVALID_REQUEST", "Category ID and update data required")
return
category_id = sys.argv[2]
update_data = json.loads(sys.argv[3])
result = resource_category_manager.update_category(
category_id,
update_data.get('title'),
update_data.get('ai_prompt'),
update_data.get('color'),
update_data.get('is_active')
)
if result:
rpc.success(result)
else:
rpc.error("NOT_FOUND", "Category not found")
elif command == "delete_category":
if len(sys.argv) < 3:
rpc.error("INVALID_REQUEST", "Category ID required")
return
category_id = sys.argv[2]
success = resource_category_manager.delete_category(category_id)
rpc.success(success)
elif command == "search_categories":
if len(sys.argv) < 3:
rpc.error("INVALID_REQUEST", "Search keyword required")
return
keyword = sys.argv[2]
results = resource_category_manager.search_categories(keyword)
rpc.success(results)
else:
rpc.error("INVALID_REQUEST", f"Unknown command: {command}")
except Exception as e:
logger.error(f"Command execution failed: {e}")
rpc.error("INTERNAL_ERROR", str(e))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,249 @@
"""
用户数据存储服务
"""
import json
import os
from pathlib import Path
from typing import List, Optional, Dict, Any
from datetime import datetime
from python_core.models.user import User, generate_user_id
from python_core.config import settings
from python_core.utils.logger import logger
class UserStorage:
"""用户数据存储服务"""
def __init__(self):
# 用户数据文件路径
self.users_file = Path(settings.temp_dir) / "cache" / "users.json"
self.users_file.parent.mkdir(parents=True, exist_ok=True)
# 内存中的用户数据
self._users: Dict[str, User] = {}
# 加载用户数据
self._load_users()
logger.info(f"UserStorage initialized with {len(self._users)} users")
def _load_users(self):
"""从文件加载用户数据"""
try:
if self.users_file.exists():
with open(self.users_file, 'r', encoding='utf-8') as f:
users_data = json.load(f)
self._users = {}
for user_data in users_data:
user = User.from_dict(user_data)
self._users[user.id] = user
logger.info(f"Loaded {len(self._users)} users from {self.users_file}")
else:
self._users = {}
logger.info("No existing users file found, starting with empty user database")
except Exception as e:
logger.error(f"Failed to load users: {e}")
self._users = {}
def _save_users(self):
"""保存用户数据到文件"""
try:
users_data = [user.to_dict() for user in self._users.values()]
with open(self.users_file, 'w', encoding='utf-8') as f:
json.dump(users_data, f, ensure_ascii=False, indent=2)
logger.debug(f"Saved {len(self._users)} users to {self.users_file}")
except Exception as e:
logger.error(f"Failed to save users: {e}")
raise
def create_user(self, username: str, email: str, password_hash: str, display_name: str) -> User:
"""
创建新用户
Args:
username: 用户名
email: 邮箱
password_hash: 密码哈希
display_name: 显示名称
Returns:
User: 创建的用户对象
Raises:
ValueError: 用户名或邮箱已存在
"""
# 检查用户名是否已存在
if self.get_user_by_username(username):
raise ValueError(f"Username '{username}' already exists")
# 检查邮箱是否已存在
if self.get_user_by_email(email):
raise ValueError(f"Email '{email}' already exists")
# 创建新用户
user = User(
id=generate_user_id(),
username=username,
email=email,
password_hash=password_hash,
display_name=display_name
)
# 保存到内存和文件
self._users[user.id] = user
self._save_users()
logger.info(f"Created new user: {username} ({email})")
return user
def get_user_by_id(self, user_id: str) -> Optional[User]:
"""根据用户ID获取用户"""
return self._users.get(user_id)
def get_user_by_username(self, username: str) -> Optional[User]:
"""根据用户名获取用户"""
for user in self._users.values():
if user.username == username:
return user
return None
def get_user_by_email(self, email: str) -> Optional[User]:
"""根据邮箱获取用户"""
for user in self._users.values():
if user.email == email:
return user
return None
def get_user_by_username_or_email(self, username_or_email: str) -> Optional[User]:
"""根据用户名或邮箱获取用户"""
# 先尝试用户名
user = self.get_user_by_username(username_or_email)
if user:
return user
# 再尝试邮箱
return self.get_user_by_email(username_or_email)
def update_user(self, user: User) -> bool:
"""
更新用户信息
Args:
user: 用户对象
Returns:
bool: 更新是否成功
"""
try:
if user.id not in self._users:
logger.warning(f"User {user.id} not found for update")
return False
# 更新时间戳
user.updated_at = datetime.now().isoformat()
# 保存到内存和文件
self._users[user.id] = user
self._save_users()
logger.info(f"Updated user: {user.username}")
return True
except Exception as e:
logger.error(f"Failed to update user {user.id}: {e}")
return False
def delete_user(self, user_id: str) -> bool:
"""
删除用户
Args:
user_id: 用户ID
Returns:
bool: 删除是否成功
"""
try:
if user_id not in self._users:
logger.warning(f"User {user_id} not found for deletion")
return False
user = self._users[user_id]
del self._users[user_id]
self._save_users()
logger.info(f"Deleted user: {user.username}")
return True
except Exception as e:
logger.error(f"Failed to delete user {user_id}: {e}")
return False
def get_all_users(self, include_inactive: bool = False) -> List[User]:
"""
获取所有用户
Args:
include_inactive: 是否包含非活跃用户
Returns:
List[User]: 用户列表
"""
users = list(self._users.values())
if not include_inactive:
users = [user for user in users if user.is_active]
# 按创建时间排序
users.sort(key=lambda x: x.created_at, reverse=True)
return users
def get_user_count(self) -> Dict[str, int]:
"""获取用户统计信息"""
all_users = list(self._users.values())
active_users = [user for user in all_users if user.is_active]
return {
"total": len(all_users),
"active": len(active_users),
"inactive": len(all_users) - len(active_users)
}
def search_users(self, keyword: str) -> List[User]:
"""
搜索用户
Args:
keyword: 搜索关键词
Returns:
List[User]: 匹配的用户列表
"""
keyword = keyword.lower().strip()
if not keyword:
return self.get_all_users()
matching_users = []
for user in self._users.values():
if (keyword in user.username.lower() or
keyword in user.email.lower() or
keyword in user.display_name.lower()):
matching_users.append(user)
# 按创建时间排序
matching_users.sort(key=lambda x: x.created_at, reverse=True)
return matching_users
# 创建全局用户存储实例
user_storage = UserStorage()

View File

@@ -0,0 +1,205 @@
"""
JWT认证工具类
"""
import jwt
import json
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from pathlib import Path
from python_core.utils.logger import logger
class JWTAuth:
"""JWT认证工具类"""
def __init__(self, secret_key: str = "mixvideo_secret_key_2024"):
self.secret_key = secret_key
self.algorithm = "HS256"
# JWT过期时间6个月
self.expires_delta = timedelta(days=180)
def generate_token(self, user_id: str, username: str, email: str) -> Dict[str, Any]:
"""
生成JWT token
Args:
user_id: 用户ID
username: 用户名
email: 邮箱
Returns:
Dict: 包含token和过期时间的字典
"""
try:
# 计算过期时间
expires_at = datetime.utcnow() + self.expires_delta
# JWT payload
payload = {
"user_id": user_id,
"username": username,
"email": email,
"iat": datetime.utcnow(), # 签发时间
"exp": expires_at, # 过期时间
"iss": "mixvideo", # 签发者
"sub": user_id # 主题用户ID
}
# 生成token
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
logger.info(f"Generated JWT token for user: {username}")
return {
"token": token,
"expires_at": expires_at.isoformat(),
"expires_in": int(self.expires_delta.total_seconds())
}
except Exception as e:
logger.error(f"Failed to generate JWT token: {e}")
raise Exception(f"Token generation failed: {str(e)}")
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
"""
验证JWT token
Args:
token: JWT token字符串
Returns:
Dict: 解码后的payload验证失败返回None
"""
try:
# 解码并验证token
payload = jwt.decode(
token,
self.secret_key,
algorithms=[self.algorithm],
options={"verify_exp": True} # 验证过期时间
)
logger.debug(f"Token verified for user: {payload.get('username')}")
return payload
except jwt.ExpiredSignatureError:
logger.warning("JWT token has expired")
return None
except jwt.InvalidTokenError as e:
logger.warning(f"Invalid JWT token: {e}")
return None
except Exception as e:
logger.error(f"JWT token verification failed: {e}")
return None
def refresh_token(self, token: str) -> Optional[Dict[str, Any]]:
"""
刷新JWT token
Args:
token: 当前的JWT token
Returns:
Dict: 新的token信息失败返回None
"""
try:
# 验证当前token忽略过期时间
payload = jwt.decode(
token,
self.secret_key,
algorithms=[self.algorithm],
options={"verify_exp": False} # 不验证过期时间
)
# 生成新token
new_token_info = self.generate_token(
user_id=payload["user_id"],
username=payload["username"],
email=payload["email"]
)
logger.info(f"Token refreshed for user: {payload['username']}")
return new_token_info
except Exception as e:
logger.error(f"Token refresh failed: {e}")
return None
def decode_token_without_verification(self, token: str) -> Optional[Dict[str, Any]]:
"""
解码token但不验证用于获取过期token的信息
Args:
token: JWT token字符串
Returns:
Dict: 解码后的payload
"""
try:
payload = jwt.decode(
token,
options={"verify_signature": False, "verify_exp": False}
)
return payload
except Exception as e:
logger.error(f"Token decode failed: {e}")
return None
def get_token_info(self, token: str) -> Dict[str, Any]:
"""
获取token信息
Args:
token: JWT token字符串
Returns:
Dict: token信息
"""
payload = self.decode_token_without_verification(token)
if not payload:
return {"valid": False, "error": "Invalid token format"}
try:
exp_timestamp = payload.get("exp")
if exp_timestamp:
exp_datetime = datetime.fromtimestamp(exp_timestamp)
is_expired = datetime.utcnow() > exp_datetime
time_remaining = exp_datetime - datetime.utcnow()
else:
is_expired = True
time_remaining = timedelta(0)
return {
"valid": not is_expired,
"user_id": payload.get("user_id"),
"username": payload.get("username"),
"email": payload.get("email"),
"issued_at": payload.get("iat"),
"expires_at": payload.get("exp"),
"is_expired": is_expired,
"time_remaining": str(time_remaining) if not is_expired else "Expired"
}
except Exception as e:
return {"valid": False, "error": str(e)}
# 创建全局JWT认证实例
jwt_auth = JWTAuth()
def generate_access_token(user_id: str, username: str, email: str) -> Dict[str, Any]:
"""生成访问token的便捷函数"""
return jwt_auth.generate_token(user_id, username, email)
def verify_access_token(token: str) -> Optional[Dict[str, Any]]:
"""验证访问token的便捷函数"""
return jwt_auth.verify_token(token)
def refresh_access_token(token: str) -> Optional[Dict[str, Any]]:
"""刷新访问token的便捷函数"""
return jwt_auth.refresh_token(token)

316
test_auth_system.py Normal file
View File

@@ -0,0 +1,316 @@
#!/usr/bin/env python3
"""
测试Python用户认证系统
"""
import sys
import os
sys.path.insert(0, '/root/projects/mixvideo_v2')
from python_core.api.auth_api import auth_api
from python_core.utils.jwt_auth import jwt_auth
from python_core.services.user_storage import user_storage
def test_user_registration():
"""测试用户注册"""
print("🧪 测试用户注册...")
try:
# 注册测试用户
result = auth_api.register({
"username": "testuser",
"email": "test@example.com",
"password": "password123",
"display_name": "测试用户"
})
if result["success"]:
print(f"✅ 注册成功: {result['data']['user']['username']}")
print(f" 用户ID: {result['data']['user']['id']}")
print(f" Token: {result['data']['token'][:50]}...")
return result['data']['token']
else:
print(f"❌ 注册失败: {result['message']}")
return None
except Exception as e:
print(f"❌ 注册测试失败: {e}")
return None
def test_user_login():
"""测试用户登录"""
print("\n🧪 测试用户登录...")
try:
# 登录测试用户
result = auth_api.login({
"username_or_email": "testuser",
"password": "password123"
})
if result["success"]:
print(f"✅ 登录成功: {result['data']['user']['username']}")
print(f" 显示名称: {result['data']['user']['display_name']}")
print(f" Token: {result['data']['token'][:50]}...")
return result['data']['token']
else:
print(f"❌ 登录失败: {result['message']}")
return None
except Exception as e:
print(f"❌ 登录测试失败: {e}")
return None
def test_token_verification(token):
"""测试Token验证"""
print("\n🧪 测试Token验证...")
try:
# 验证token
result = auth_api.verify_token({
"token": token
})
if result["success"]:
user = result['data']['user']
print(f"✅ Token验证成功: {user['username']}")
print(f" 用户ID: {user['user_id']}")
print(f" 邮箱: {user['email']}")
# 获取token详细信息
token_info = jwt_auth.get_token_info(token)
print(f" Token有效性: {token_info['valid']}")
print(f" 剩余时间: {token_info['time_remaining']}")
return True
else:
print(f"❌ Token验证失败: {result['message']}")
return False
except Exception as e:
print(f"❌ Token验证测试失败: {e}")
return False
def test_get_current_user(token):
"""测试获取当前用户"""
print("\n🧪 测试获取当前用户...")
try:
# 获取当前用户
result = auth_api.get_current_user({
"token": token
})
if result["success"]:
user = result['data']['user']
print(f"✅ 获取用户成功: {user['username']}")
print(f" 显示名称: {user['display_name']}")
print(f" 创建时间: {user['created_at']}")
print(f" 最后登录: {user['last_login']}")
return True
else:
print(f"❌ 获取用户失败: {result['message']}")
return False
except Exception as e:
print(f"❌ 获取用户测试失败: {e}")
return False
def test_user_storage():
"""测试用户存储"""
print("\n🧪 测试用户存储...")
try:
# 获取所有用户
users = user_storage.get_all_users()
print(f"✅ 获取到 {len(users)} 个用户")
# 获取用户统计
stats = user_storage.get_user_count()
print(f" 总用户数: {stats['total']}")
print(f" 活跃用户: {stats['active']}")
print(f" 禁用用户: {stats['inactive']}")
# 搜索用户
search_results = user_storage.search_users("test")
print(f" 搜索'test'找到 {len(search_results)} 个用户")
return True
except Exception as e:
print(f"❌ 用户存储测试失败: {e}")
return False
def test_duplicate_registration():
"""测试重复注册"""
print("\n🧪 测试重复注册...")
try:
# 尝试重复注册
result = auth_api.register({
"username": "testuser",
"email": "test@example.com",
"password": "password123",
"display_name": "重复用户"
})
if not result["success"]:
print(f"✅ 正确阻止重复注册: {result['message']}")
return True
else:
print(f"❌ 未能阻止重复注册")
return False
except Exception as e:
print(f"❌ 重复注册测试失败: {e}")
return False
def test_invalid_login():
"""测试无效登录"""
print("\n🧪 测试无效登录...")
try:
# 尝试错误密码登录
result = auth_api.login({
"username_or_email": "testuser",
"password": "wrongpassword"
})
if not result["success"]:
print(f"✅ 正确拒绝错误密码: {result['message']}")
else:
print(f"❌ 未能拒绝错误密码")
return False
# 尝试不存在的用户登录
result = auth_api.login({
"username_or_email": "nonexistentuser",
"password": "password123"
})
if not result["success"]:
print(f"✅ 正确拒绝不存在用户: {result['message']}")
return True
else:
print(f"❌ 未能拒绝不存在用户")
return False
except Exception as e:
print(f"❌ 无效登录测试失败: {e}")
return False
def test_jwt_features():
"""测试JWT特性"""
print("\n🧪 测试JWT特性...")
try:
# 生成token
token_info = jwt_auth.generate_token("test_user_id", "testuser", "test@example.com")
token = token_info["token"]
print(f"✅ 生成Token成功")
print(f" 过期时间: {token_info['expires_at']}")
print(f" 有效期: {token_info['expires_in']}")
# 验证token
payload = jwt_auth.verify_token(token)
if payload:
print(f"✅ Token验证成功")
print(f" 用户ID: {payload['user_id']}")
print(f" 用户名: {payload['username']}")
print(f" 签发者: {payload['iss']}")
else:
print(f"❌ Token验证失败")
return False
# 获取token信息
info = jwt_auth.get_token_info(token)
print(f"✅ Token信息获取成功")
print(f" 有效性: {info['valid']}")
print(f" 剩余时间: {info['time_remaining']}")
return True
except Exception as e:
print(f"❌ JWT特性测试失败: {e}")
return False
def main():
"""主测试函数"""
print("🚀 开始测试Python用户认证系统...")
test_results = []
# 测试JWT特性
test_results.append(("JWT特性", test_jwt_features()))
# 测试用户注册
token = test_user_registration()
test_results.append(("用户注册", token is not None))
if token:
# 测试Token验证
test_results.append(("Token验证", test_token_verification(token)))
# 测试获取当前用户
test_results.append(("获取当前用户", test_get_current_user(token)))
# 测试用户登录
login_token = test_user_login()
test_results.append(("用户登录", login_token is not None))
# 测试用户存储
test_results.append(("用户存储", test_user_storage()))
# 测试重复注册
test_results.append(("重复注册检查", test_duplicate_registration()))
# 测试无效登录
test_results.append(("无效登录检查", test_invalid_login()))
# 显示测试结果
print("\n📊 测试结果汇总:")
print("=" * 50)
passed = 0
total = len(test_results)
for test_name, result in test_results:
status = "✅ 通过" if result else "❌ 失败"
print(f"{test_name:20} {status}")
if result:
passed += 1
print("=" * 50)
print(f"总计: {passed}/{total} 个测试通过")
if passed == total:
print("\n🎉 所有测试通过Python用户认证系统工作正常")
print("\n📖 使用方法:")
print(" # 注册用户")
print(" python3 -m python_core.cli auth register username email@example.com")
print(" # 登录用户")
print(" python3 -m python_core.cli auth login username")
print(" # 验证Token")
print(" python3 -m python_core.cli auth verify <token>")
print(" # 查看用户列表")
print(" python3 -m python_core.cli auth list")
print(" # 查看统计信息")
print(" python3 -m python_core.cli auth stats")
else:
print(f"\n{total - passed} 个测试失败!")
sys.exit(1)
if __name__ == "__main__":
main()