mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 12:00:14 +00:00
主要更新: 📚 文档改进 - 新增 AI_CONFIGURATION.md:详细的 AI 提供商配置指南 - 新增 CHINA_MIRROR_GUIDE.md:中国镜像加速指南 - 删除 6 个过时文档(DEPLOYMENT.md, QUICK_START.md 等) - 更新 README.md:添加 Gemini 功能特性和 AI 提供商对比表 🧹 脚本清理 - 删除 11 个重复/过时脚本(从 25 个减少到 14 个) - 删除 PostgreSQL 相关脚本(项目使用 MySQL) - 删除重复的启动脚本(start_windows.bat, start_app.bat 等) ⚙️ 配置文件更新 - 更新 .env.example:添加 Gemini 配置示例和说明 - 默认 AI_PROVIDER 改为 gemini(推荐) - 添加各提供商的获取 API Key 链接 🎯 核心改进 - 突出 Gemini 原生 PDF 理解优势 - 提供清晰的 AI 提供商选择指南 - 简化项目结构,提高可维护性 清理内容: - 删除 docs: DEPLOYMENT.md, DOCKER_MIRROR_SETUP.md, GITHUB_PUSH_GUIDE.md, QUICK_START.md, README_QUICKSTART.md, START_HERE.txt - 删除 scripts: auto_setup_and_run.bat, check_postgres.bat, logs_windows.bat, push_to_github.bat, quick_config.bat, restart_docker.bat, setup_docker_mirror.bat, start_app.bat, start_postgres.bat, start_windows.bat, start_windows_china.bat 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
"""
|
|
Utility functions
|
|
"""
|
|
import hashlib
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
from jose import JWTError, jwt
|
|
from passlib.context import CryptContext
|
|
import os
|
|
|
|
# Password hashing
|
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
|
|
# JWT settings
|
|
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-this")
|
|
ALGORITHM = "HS256"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
"""Hash a password"""
|
|
return pwd_context.hash(password)
|
|
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Verify a password against a hash"""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
"""Create a JWT access 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 decode_access_token(token: str) -> Optional[dict]:
|
|
"""Decode a JWT access token"""
|
|
try:
|
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
return payload
|
|
except JWTError:
|
|
return None
|
|
|
|
|
|
def normalize_content(content: str) -> str:
|
|
"""
|
|
Normalize content for deduplication.
|
|
Removes whitespace, punctuation, and converts to lowercase.
|
|
"""
|
|
# Remove all whitespace
|
|
normalized = re.sub(r'\s+', '', content)
|
|
# Remove punctuation
|
|
normalized = re.sub(r'[^\w\u4e00-\u9fff]', '', normalized)
|
|
# Convert to lowercase
|
|
normalized = normalized.lower()
|
|
return normalized
|
|
|
|
|
|
def calculate_content_hash(content: str) -> str:
|
|
"""
|
|
Calculate MD5 hash of normalized content for deduplication.
|
|
"""
|
|
normalized = normalize_content(content)
|
|
return hashlib.md5(normalized.encode('utf-8')).hexdigest()
|
|
|
|
|
|
def get_file_extension(filename: str) -> str:
|
|
"""Get file extension from filename"""
|
|
return filename.rsplit('.', 1)[-1].lower() if '.' in filename else ''
|
|
|
|
|
|
def is_allowed_file(filename: str) -> bool:
|
|
"""Check if file extension is allowed"""
|
|
allowed_extensions = {'txt', 'pdf', 'doc', 'docx', 'xlsx', 'xls'}
|
|
return get_file_extension(filename) in allowed_extensions
|