chore: sync local changes (2026-03-12)
This commit is contained in:
19
InfoGenie-backend/.dockerignore
Normal file
19
InfoGenie-backend/.dockerignore
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
# Python cache
|
||||
__pycache__
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Test files
|
||||
InfoGenie-backend/test
|
||||
|
||||
|
||||
3
InfoGenie-backend/.gitignore
vendored
Normal file
3
InfoGenie-backend/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#项目自忽略
|
||||
.vscode
|
||||
__pycache__
|
||||
30
InfoGenie-backend/Dockerfile
Normal file
30
InfoGenie-backend/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
# InfoGenie 后端 Docker 镜像
|
||||
# 仅包含后端服务,使用 Gunicorn
|
||||
|
||||
FROM python:3.10-slim
|
||||
|
||||
# 安装 curl 用于健康检查
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
# 安装 Python 依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt gunicorn
|
||||
|
||||
# 复制后端代码
|
||||
COPY . .
|
||||
|
||||
# 创建持久化数据目录
|
||||
RUN mkdir -p /app/data/logs
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 2323
|
||||
|
||||
# 使用 Gunicorn 启动应用
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:2323", "--workers", "4", "--threads", "2", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-", "app:app"]
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"deepseek": {
|
||||
"api_key": "sk-832f8e5250464de08a31523c7fd712",
|
||||
"api_base": "https://api.deepseek.com",
|
||||
"model": ["deepseek-chat","deepseek-reasoner"]
|
||||
},
|
||||
|
||||
"kimi": {
|
||||
"api_key": "sk-zdg9NBpTlhOcDDpoWfaBKu0KNDdGv18SipORnL2utawja",
|
||||
"api_base": "https://api.moonshot.cn",
|
||||
"model": ["kimi-k2-0905-preview","kimi-k2-0711-preview"]
|
||||
}
|
||||
{
|
||||
"deepseek": {
|
||||
"api_key": "sk-832f8e5250464de08a31523c7fd712",
|
||||
"api_base": "https://api.deepseek.com",
|
||||
"model": ["deepseek-chat","deepseek-reasoner"]
|
||||
},
|
||||
|
||||
"kimi": {
|
||||
"api_key": "sk-zdg9NBpTlhOcDDpoWfaBKu0KNDdGv18SipORnL2utawja",
|
||||
"api_base": "https://api.moonshot.cn",
|
||||
"model": ["kimi-k2-0905-preview","kimi-k2-0711-preview"]
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,9 @@ def create_app():
|
||||
|
||||
return app
|
||||
|
||||
# 为 Gunicorn 创建应用实例
|
||||
app = create_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
print("🚀 启动 InfoGenie 后端服务...")
|
||||
app.run(debug=True, host='0.0.0.0', port=5002)
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
InfoGenie 配置文件
|
||||
Created by: 万象口袋
|
||||
Date: 2025-09-02
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
class Config:
|
||||
"""应用配置类"""
|
||||
|
||||
# 基础配置
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'infogenie-secret-key-2025'
|
||||
|
||||
# MongoDB 配置
|
||||
MONGO_URI = os.environ.get('MONGO_URI') or 'mongodb://localhost:27017/InfoGenie'
|
||||
|
||||
# hwt 配置
|
||||
HWT_LIFETIME = timedelta(days=7) # hwt持续7天
|
||||
HWT_SECURE = False # 开发环境设为False,生产环境设为True
|
||||
HWT_HTTPONLY = True
|
||||
HWT_SAMESITE = 'Lax'
|
||||
HWT_DOMAIN = None # 开发环境设为None,生产环境设为具体域名
|
||||
HWT_PATH = '/'
|
||||
HWT_REFRESH_EACH_REQUEST = True # 每次请求刷新hwt过期时间
|
||||
|
||||
# 邮件配置
|
||||
MAIL_SERVER = 'smtp.qq.com'
|
||||
MAIL_PORT = 465
|
||||
MAIL_USE_SSL = True
|
||||
MAIL_USE_TLS = False
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME') or 'your-email@qq.com'
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') or 'your-app-password'
|
||||
MAIL_DEFAULT_SENDER = ('InfoGenie 万象口袋', os.environ.get('MAIL_USERNAME') or 'your-email@qq.com')
|
||||
|
||||
# API 配置
|
||||
API_RATE_LIMIT = '100 per hour' # API调用频率限制
|
||||
|
||||
# 外部API配置
|
||||
EXTERNAL_APIS = {
|
||||
'60s': [
|
||||
'https://60s.api.shumengya.top'
|
||||
]
|
||||
}
|
||||
|
||||
# 应用信息
|
||||
APP_INFO = {
|
||||
'name': '✨ 万象口袋 ✨',
|
||||
'description': '🎨 一个多功能的聚合软件应用 💬',
|
||||
'author': '👨💻 by-万象口袋',
|
||||
'version': '1.0.0',
|
||||
'icp': '📄 蜀ICP备2025151694号'
|
||||
}
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
"""开发环境配置"""
|
||||
DEBUG = True
|
||||
TESTING = False
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""生产环境配置"""
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
HWT_SECURE = True
|
||||
|
||||
class TestingConfig(Config):
|
||||
"""测试环境配置"""
|
||||
DEBUG = True
|
||||
TESTING = True
|
||||
MONGO_URI = 'mongodb://localhost:27017/InfoGenie_Test'
|
||||
|
||||
# 配置字典
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'production': ProductionConfig,
|
||||
'testing': TestingConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
InfoGenie 配置文件
|
||||
Created by: 万象口袋
|
||||
Date: 2025-09-02
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
class Config:
|
||||
"""应用配置类"""
|
||||
|
||||
# 基础配置
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'infogenie-secret-key-2025'
|
||||
|
||||
# MongoDB 配置
|
||||
MONGO_URI = os.environ.get('MONGO_URI') or 'mongodb://localhost:27017/InfoGenie'
|
||||
|
||||
# hwt 配置
|
||||
HWT_LIFETIME = timedelta(days=7) # hwt持续7天
|
||||
HWT_SECURE = False # 开发环境设为False,生产环境设为True
|
||||
HWT_HTTPONLY = True
|
||||
HWT_SAMESITE = 'Lax'
|
||||
HWT_DOMAIN = None # 开发环境设为None,生产环境设为具体域名
|
||||
HWT_PATH = '/'
|
||||
HWT_REFRESH_EACH_REQUEST = True # 每次请求刷新hwt过期时间
|
||||
|
||||
# 邮件配置
|
||||
MAIL_SERVER = 'smtp.qq.com'
|
||||
MAIL_PORT = 465
|
||||
MAIL_USE_SSL = True
|
||||
MAIL_USE_TLS = False
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME') or 'your-email@qq.com'
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') or 'your-app-password'
|
||||
MAIL_DEFAULT_SENDER = ('InfoGenie 万象口袋', os.environ.get('MAIL_USERNAME') or 'your-email@qq.com')
|
||||
|
||||
# API 配置
|
||||
API_RATE_LIMIT = '100 per hour' # API调用频率限制
|
||||
|
||||
# 外部API配置
|
||||
EXTERNAL_APIS = {
|
||||
'60s': [
|
||||
'https://60s.api.shumengya.top'
|
||||
]
|
||||
}
|
||||
|
||||
# 应用信息
|
||||
APP_INFO = {
|
||||
'name': '✨ 万象口袋 ✨',
|
||||
'description': '🎨 一个多功能的聚合软件应用 💬',
|
||||
'author': '👨💻 by-万象口袋',
|
||||
'version': '1.0.0',
|
||||
'icp': '📄 蜀ICP备2025151694号'
|
||||
}
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
"""开发环境配置"""
|
||||
DEBUG = True
|
||||
TESTING = False
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""生产环境配置"""
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
HWT_SECURE = True
|
||||
|
||||
class TestingConfig(Config):
|
||||
"""测试环境配置"""
|
||||
DEBUG = True
|
||||
TESTING = True
|
||||
MONGO_URI = 'mongodb://localhost:27017/InfoGenie_Test'
|
||||
|
||||
# 配置字典
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'production': ProductionConfig,
|
||||
'testing': TestingConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
|
||||
39
InfoGenie-backend/docker-compose.yml
Normal file
39
InfoGenie-backend/docker-compose.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
infogenie-backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: infogenie-backend
|
||||
restart: always
|
||||
ports:
|
||||
- "2323:2323"
|
||||
volumes:
|
||||
# 持久化数据映射
|
||||
- /shumengya/docker/infogenie-backend/data:/app/data
|
||||
|
||||
environment:
|
||||
# 从 .env 文件读取环境变量
|
||||
- MONGO_URI=${MONGO_URI}
|
||||
- MAIL_USERNAME=${MAIL_USERNAME}
|
||||
- MAIL_PASSWORD=${MAIL_PASSWORD}
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- FLASK_ENV=production
|
||||
# 生产环境配置
|
||||
- HWT_DOMAIN=.shumengya.top
|
||||
- HWT_SECURE=False # 如果使用 HTTPS 反向代理,设为 False;直接 HTTPS 设为 True
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- infogenie-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:2323/api/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
networks:
|
||||
infogenie-network:
|
||||
driver: bridge
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,455 +1,455 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用户认证模块
|
||||
Created by: 万象口袋
|
||||
Date: 2025-09-02
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import hashlib
|
||||
import re
|
||||
import jwt
|
||||
from datetime import datetime, timedelta
|
||||
from functools import wraps
|
||||
from .email_service import send_verification_email, verify_code, is_qq_email, get_qq_avatar_url
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
#生成JWT token
|
||||
def generate_token(user_data):
|
||||
"""生成JWT token"""
|
||||
payload = {
|
||||
'user_id': user_data['user_id'],
|
||||
'email': user_data['email'],
|
||||
'username': user_data['username'],
|
||||
'exp': datetime.utcnow() + timedelta(days=7), # 7天过期
|
||||
'iat': datetime.utcnow()
|
||||
}
|
||||
return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
|
||||
|
||||
#验证JWT token
|
||||
def verify_token(token):
|
||||
"""验证JWT token"""
|
||||
try:
|
||||
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||
return {'success': True, 'data': payload}
|
||||
except jwt.ExpiredSignatureError:
|
||||
return {'success': False, 'message': 'Token已过期'}
|
||||
except jwt.InvalidTokenError:
|
||||
return {'success': False, 'message': 'Token无效'}
|
||||
|
||||
#JWT token验证装饰器
|
||||
def token_required(f):
|
||||
"""JWT token验证装饰器"""
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
token = request.headers.get('Authorization')
|
||||
if not token:
|
||||
return jsonify({'success': False, 'message': '缺少认证token'}), 401
|
||||
|
||||
if token.startswith('Bearer '):
|
||||
token = token[7:]
|
||||
|
||||
result = verify_token(token)
|
||||
if not result['success']:
|
||||
return jsonify({'success': False, 'message': result['message']}), 401
|
||||
|
||||
request.current_user = result['data']
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
#验证QQ邮箱格式
|
||||
def validate_qq_email(email):
|
||||
"""验证QQ邮箱格式"""
|
||||
return is_qq_email(email)
|
||||
|
||||
#验证密码格式
|
||||
def validate_password(password):
|
||||
"""验证密码格式(6-20位)"""
|
||||
return 6 <= len(password) <= 20
|
||||
|
||||
|
||||
#==========================对外暴露的HTTP接口==========================
|
||||
#发送验证码邮件
|
||||
@auth_bp.route('/send-verification', methods=['POST'])
|
||||
def send_verification():
|
||||
"""发送验证码邮件"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
verification_type = data.get('type', 'register') # register, login
|
||||
|
||||
# 参数验证
|
||||
if not email:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '邮箱地址不能为空'
|
||||
}), 400
|
||||
|
||||
if not validate_qq_email(email):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱(qq.com、vip.qq.com、foxmail.com)'
|
||||
}), 400
|
||||
|
||||
# 获取数据库集合
|
||||
db = current_app.mongo.db
|
||||
users_collection = db.userdata
|
||||
|
||||
# 检查邮箱是否已注册
|
||||
existing_user = users_collection.find_one({'邮箱': email})
|
||||
|
||||
if verification_type == 'register' and existing_user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱已被注册'
|
||||
}), 409
|
||||
|
||||
if verification_type == 'login' and not existing_user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱尚未注册'
|
||||
}), 404
|
||||
|
||||
# 发送验证码
|
||||
result = send_verification_email(email, verification_type)
|
||||
|
||||
if result['success']:
|
||||
return jsonify(result), 200
|
||||
else:
|
||||
return jsonify(result), 500
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"发送验证码失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '发送失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
#验证验证码
|
||||
@auth_bp.route('/verify-code', methods=['POST'])
|
||||
def verify_verification_code():
|
||||
"""验证验证码"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
code = data.get('code', '').strip()
|
||||
|
||||
# 参数验证
|
||||
if not email or not code:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '邮箱和验证码不能为空'
|
||||
}), 400
|
||||
|
||||
# 验证码校验
|
||||
result = verify_code(email, code)
|
||||
|
||||
if result['success']:
|
||||
return jsonify(result), 200
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"验证码校验失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '验证失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
#用户注册
|
||||
@auth_bp.route('/register', methods=['POST'])
|
||||
def register():
|
||||
"""用户注册(需要先验证邮箱)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
username = data.get('username', '').strip()
|
||||
password = data.get('password', '').strip()
|
||||
code = data.get('code', '').strip()
|
||||
|
||||
# 参数验证
|
||||
if not all([email, username, password, code]):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '所有字段都不能为空'
|
||||
}), 400
|
||||
|
||||
if not validate_qq_email(email):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱注册'
|
||||
}), 400
|
||||
|
||||
if not validate_password(password):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '密码长度必须在6-20位之间'
|
||||
}), 400
|
||||
|
||||
# 验证验证码
|
||||
verify_result = verify_code(email, code)
|
||||
if not verify_result['success'] or verify_result.get('type') != 'register':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '验证码无效或已过期'
|
||||
}), 400
|
||||
|
||||
# 获取数据库集合
|
||||
db = current_app.mongo.db
|
||||
users_collection = db.userdata
|
||||
|
||||
# 检查邮箱是否已被注册
|
||||
if users_collection.find_one({'邮箱': email}):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱已被注册'
|
||||
}), 409
|
||||
|
||||
# 检查用户名是否已被使用
|
||||
if users_collection.find_one({'用户名': username}):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该用户名已被使用'
|
||||
}), 409
|
||||
|
||||
# 获取QQ头像
|
||||
avatar_url = get_qq_avatar_url(email)
|
||||
|
||||
# 创建新用户
|
||||
password_hash = generate_password_hash(password)
|
||||
user_data = {
|
||||
'邮箱': email,
|
||||
'用户名': username,
|
||||
'密码': password_hash,
|
||||
'头像': avatar_url,
|
||||
'注册时间': datetime.now().isoformat(),
|
||||
'最后登录': None,
|
||||
'登录次数': 0,
|
||||
'用户状态': 'active',
|
||||
'等级': 0,
|
||||
'经验': 0,
|
||||
'萌芽币': 0,
|
||||
'签到系统': {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': '2025-01-01'
|
||||
}
|
||||
}
|
||||
|
||||
result = users_collection.insert_one(user_data)
|
||||
|
||||
if result.inserted_id:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '注册成功!',
|
||||
'user': {
|
||||
'email': email,
|
||||
'username': username,
|
||||
'avatar': avatar_url
|
||||
}
|
||||
}), 201
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '注册失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"注册失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '注册失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
#用户登录
|
||||
@auth_bp.route('/login', methods=['POST'])
|
||||
def login():
|
||||
"""用户登录(支持邮箱+验证码或邮箱+密码)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
password = data.get('password', '').strip()
|
||||
code = data.get('code', '').strip()
|
||||
|
||||
# 参数验证
|
||||
if not email:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '邮箱地址不能为空'
|
||||
}), 400
|
||||
|
||||
if not validate_qq_email(email):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱登录'
|
||||
}), 400
|
||||
|
||||
# 获取数据库集合
|
||||
db = current_app.mongo.db
|
||||
users_collection = db.userdata
|
||||
|
||||
# 查找用户
|
||||
user = users_collection.find_one({'邮箱': email})
|
||||
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱尚未注册'
|
||||
}), 404
|
||||
|
||||
# 检查用户状态
|
||||
if user.get('用户状态') != 'active':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '账号已被禁用,请联系管理员'
|
||||
}), 403
|
||||
|
||||
# 验证方式:验证码登录或密码登录
|
||||
if code:
|
||||
# 验证码登录
|
||||
verify_result = verify_code(email, code)
|
||||
if not verify_result['success'] or verify_result.get('type') != 'login':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '验证码无效或已过期'
|
||||
}), 400
|
||||
elif password:
|
||||
# 密码登录
|
||||
if not check_password_hash(user['密码'], password):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '密码错误'
|
||||
}), 401
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请输入密码或验证码'
|
||||
}), 400
|
||||
|
||||
# 登录成功,更新用户信息
|
||||
users_collection.update_one(
|
||||
{'邮箱': email},
|
||||
{
|
||||
'$set': {'最后登录': datetime.now().isoformat()},
|
||||
'$inc': {'登录次数': 1}
|
||||
}
|
||||
)
|
||||
|
||||
# 生成JWT token
|
||||
user_data = {
|
||||
'user_id': str(user['_id']),
|
||||
'email': email,
|
||||
'username': user.get('用户名', '')
|
||||
}
|
||||
token = generate_token(user_data)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '登录成功!',
|
||||
'token': token,
|
||||
'user': {
|
||||
'id': str(user['_id']),
|
||||
'email': email,
|
||||
'username': user.get('用户名', ''),
|
||||
'avatar': user.get('头像', ''),
|
||||
'login_count': user.get('登录次数', 0) + 1
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"登录失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '登录失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
# 登录成功,创建会话
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
hwt['user_id'] = str(user['_id'])
|
||||
hwt['account'] = user['账号']
|
||||
hwt['logged_in'] = True
|
||||
|
||||
# 更新登录信息
|
||||
users_collection.update_one(
|
||||
{'_id': user['_id']},
|
||||
{
|
||||
'$set': {'最后登录': datetime.now().isoformat()},
|
||||
'$inc': {'登录次数': 1}
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '登录成功!',
|
||||
'user': {
|
||||
'account': user['账号'],
|
||||
'last_login': user.get('最后登录'),
|
||||
'login_count': user.get('登录次数', 0) + 1
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
#用户登出
|
||||
@auth_bp.route('/logout', methods=['POST'])
|
||||
def logout():
|
||||
"""用户登出"""
|
||||
try:
|
||||
# JWT是无状态的,客户端删除token即可
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '已成功登出'
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
#检查登录状态
|
||||
@auth_bp.route('/check', methods=['GET'])
|
||||
def check_login():
|
||||
"""检查登录状态"""
|
||||
try:
|
||||
token = request.headers.get('Authorization')
|
||||
if not token:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': False
|
||||
}), 200
|
||||
|
||||
if token.startswith('Bearer '):
|
||||
token = token[7:]
|
||||
|
||||
result = verify_token(token)
|
||||
if result['success']:
|
||||
user_data = result['data']
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': True,
|
||||
'user': {
|
||||
'id': user_data['user_id'],
|
||||
'email': user_data['email'],
|
||||
'username': user_data['username']
|
||||
}
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': False
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用户认证模块
|
||||
Created by: 万象口袋
|
||||
Date: 2025-09-02
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import hashlib
|
||||
import re
|
||||
import jwt
|
||||
from datetime import datetime, timedelta
|
||||
from functools import wraps
|
||||
from .email_service import send_verification_email, verify_code, is_qq_email, get_qq_avatar_url
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
#生成JWT token
|
||||
def generate_token(user_data):
|
||||
"""生成JWT token"""
|
||||
payload = {
|
||||
'user_id': user_data['user_id'],
|
||||
'email': user_data['email'],
|
||||
'username': user_data['username'],
|
||||
'exp': datetime.utcnow() + timedelta(days=7), # 7天过期
|
||||
'iat': datetime.utcnow()
|
||||
}
|
||||
return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
|
||||
|
||||
#验证JWT token
|
||||
def verify_token(token):
|
||||
"""验证JWT token"""
|
||||
try:
|
||||
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||
return {'success': True, 'data': payload}
|
||||
except jwt.ExpiredSignatureError:
|
||||
return {'success': False, 'message': 'Token已过期'}
|
||||
except jwt.InvalidTokenError:
|
||||
return {'success': False, 'message': 'Token无效'}
|
||||
|
||||
#JWT token验证装饰器
|
||||
def token_required(f):
|
||||
"""JWT token验证装饰器"""
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
token = request.headers.get('Authorization')
|
||||
if not token:
|
||||
return jsonify({'success': False, 'message': '缺少认证token'}), 401
|
||||
|
||||
if token.startswith('Bearer '):
|
||||
token = token[7:]
|
||||
|
||||
result = verify_token(token)
|
||||
if not result['success']:
|
||||
return jsonify({'success': False, 'message': result['message']}), 401
|
||||
|
||||
request.current_user = result['data']
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
#验证QQ邮箱格式
|
||||
def validate_qq_email(email):
|
||||
"""验证QQ邮箱格式"""
|
||||
return is_qq_email(email)
|
||||
|
||||
#验证密码格式
|
||||
def validate_password(password):
|
||||
"""验证密码格式(6-20位)"""
|
||||
return 6 <= len(password) <= 20
|
||||
|
||||
|
||||
#==========================对外暴露的HTTP接口==========================
|
||||
#发送验证码邮件
|
||||
@auth_bp.route('/send-verification', methods=['POST'])
|
||||
def send_verification():
|
||||
"""发送验证码邮件"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
verification_type = data.get('type', 'register') # register, login
|
||||
|
||||
# 参数验证
|
||||
if not email:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '邮箱地址不能为空'
|
||||
}), 400
|
||||
|
||||
if not validate_qq_email(email):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱(qq.com、vip.qq.com、foxmail.com)'
|
||||
}), 400
|
||||
|
||||
# 获取数据库集合
|
||||
db = current_app.mongo.db
|
||||
users_collection = db.userdata
|
||||
|
||||
# 检查邮箱是否已注册
|
||||
existing_user = users_collection.find_one({'邮箱': email})
|
||||
|
||||
if verification_type == 'register' and existing_user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱已被注册'
|
||||
}), 409
|
||||
|
||||
if verification_type == 'login' and not existing_user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱尚未注册'
|
||||
}), 404
|
||||
|
||||
# 发送验证码
|
||||
result = send_verification_email(email, verification_type)
|
||||
|
||||
if result['success']:
|
||||
return jsonify(result), 200
|
||||
else:
|
||||
return jsonify(result), 500
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"发送验证码失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '发送失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
#验证验证码
|
||||
@auth_bp.route('/verify-code', methods=['POST'])
|
||||
def verify_verification_code():
|
||||
"""验证验证码"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
code = data.get('code', '').strip()
|
||||
|
||||
# 参数验证
|
||||
if not email or not code:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '邮箱和验证码不能为空'
|
||||
}), 400
|
||||
|
||||
# 验证码校验
|
||||
result = verify_code(email, code)
|
||||
|
||||
if result['success']:
|
||||
return jsonify(result), 200
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"验证码校验失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '验证失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
#用户注册
|
||||
@auth_bp.route('/register', methods=['POST'])
|
||||
def register():
|
||||
"""用户注册(需要先验证邮箱)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
username = data.get('username', '').strip()
|
||||
password = data.get('password', '').strip()
|
||||
code = data.get('code', '').strip()
|
||||
|
||||
# 参数验证
|
||||
if not all([email, username, password, code]):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '所有字段都不能为空'
|
||||
}), 400
|
||||
|
||||
if not validate_qq_email(email):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱注册'
|
||||
}), 400
|
||||
|
||||
if not validate_password(password):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '密码长度必须在6-20位之间'
|
||||
}), 400
|
||||
|
||||
# 验证验证码
|
||||
verify_result = verify_code(email, code)
|
||||
if not verify_result['success'] or verify_result.get('type') != 'register':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '验证码无效或已过期'
|
||||
}), 400
|
||||
|
||||
# 获取数据库集合
|
||||
db = current_app.mongo.db
|
||||
users_collection = db.userdata
|
||||
|
||||
# 检查邮箱是否已被注册
|
||||
if users_collection.find_one({'邮箱': email}):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱已被注册'
|
||||
}), 409
|
||||
|
||||
# 检查用户名是否已被使用
|
||||
if users_collection.find_one({'用户名': username}):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该用户名已被使用'
|
||||
}), 409
|
||||
|
||||
# 获取QQ头像
|
||||
avatar_url = get_qq_avatar_url(email)
|
||||
|
||||
# 创建新用户
|
||||
password_hash = generate_password_hash(password)
|
||||
user_data = {
|
||||
'邮箱': email,
|
||||
'用户名': username,
|
||||
'密码': password_hash,
|
||||
'头像': avatar_url,
|
||||
'注册时间': datetime.now().isoformat(),
|
||||
'最后登录': None,
|
||||
'登录次数': 0,
|
||||
'用户状态': 'active',
|
||||
'等级': 0,
|
||||
'经验': 0,
|
||||
'萌芽币': 0,
|
||||
'签到系统': {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': '2025-01-01'
|
||||
}
|
||||
}
|
||||
|
||||
result = users_collection.insert_one(user_data)
|
||||
|
||||
if result.inserted_id:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '注册成功!',
|
||||
'user': {
|
||||
'email': email,
|
||||
'username': username,
|
||||
'avatar': avatar_url
|
||||
}
|
||||
}), 201
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '注册失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"注册失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '注册失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
#用户登录
|
||||
@auth_bp.route('/login', methods=['POST'])
|
||||
def login():
|
||||
"""用户登录(支持邮箱+验证码或邮箱+密码)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
password = data.get('password', '').strip()
|
||||
code = data.get('code', '').strip()
|
||||
|
||||
# 参数验证
|
||||
if not email:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '邮箱地址不能为空'
|
||||
}), 400
|
||||
|
||||
if not validate_qq_email(email):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱登录'
|
||||
}), 400
|
||||
|
||||
# 获取数据库集合
|
||||
db = current_app.mongo.db
|
||||
users_collection = db.userdata
|
||||
|
||||
# 查找用户
|
||||
user = users_collection.find_one({'邮箱': email})
|
||||
|
||||
if not user:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该邮箱尚未注册'
|
||||
}), 404
|
||||
|
||||
# 检查用户状态
|
||||
if user.get('用户状态') != 'active':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '账号已被禁用,请联系管理员'
|
||||
}), 403
|
||||
|
||||
# 验证方式:验证码登录或密码登录
|
||||
if code:
|
||||
# 验证码登录
|
||||
verify_result = verify_code(email, code)
|
||||
if not verify_result['success'] or verify_result.get('type') != 'login':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '验证码无效或已过期'
|
||||
}), 400
|
||||
elif password:
|
||||
# 密码登录
|
||||
if not check_password_hash(user['密码'], password):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '密码错误'
|
||||
}), 401
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请输入密码或验证码'
|
||||
}), 400
|
||||
|
||||
# 登录成功,更新用户信息
|
||||
users_collection.update_one(
|
||||
{'邮箱': email},
|
||||
{
|
||||
'$set': {'最后登录': datetime.now().isoformat()},
|
||||
'$inc': {'登录次数': 1}
|
||||
}
|
||||
)
|
||||
|
||||
# 生成JWT token
|
||||
user_data = {
|
||||
'user_id': str(user['_id']),
|
||||
'email': email,
|
||||
'username': user.get('用户名', '')
|
||||
}
|
||||
token = generate_token(user_data)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '登录成功!',
|
||||
'token': token,
|
||||
'user': {
|
||||
'id': str(user['_id']),
|
||||
'email': email,
|
||||
'username': user.get('用户名', ''),
|
||||
'avatar': user.get('头像', ''),
|
||||
'login_count': user.get('登录次数', 0) + 1
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"登录失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '登录失败,请稍后重试'
|
||||
}), 500
|
||||
|
||||
# 登录成功,创建会话
|
||||
hwt = getattr(request, 'hwt', {})
|
||||
hwt['user_id'] = str(user['_id'])
|
||||
hwt['account'] = user['账号']
|
||||
hwt['logged_in'] = True
|
||||
|
||||
# 更新登录信息
|
||||
users_collection.update_one(
|
||||
{'_id': user['_id']},
|
||||
{
|
||||
'$set': {'最后登录': datetime.now().isoformat()},
|
||||
'$inc': {'登录次数': 1}
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '登录成功!',
|
||||
'user': {
|
||||
'account': user['账号'],
|
||||
'last_login': user.get('最后登录'),
|
||||
'login_count': user.get('登录次数', 0) + 1
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
#用户登出
|
||||
@auth_bp.route('/logout', methods=['POST'])
|
||||
def logout():
|
||||
"""用户登出"""
|
||||
try:
|
||||
# JWT是无状态的,客户端删除token即可
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '已成功登出'
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
|
||||
#检查登录状态
|
||||
@auth_bp.route('/check', methods=['GET'])
|
||||
def check_login():
|
||||
"""检查登录状态"""
|
||||
try:
|
||||
token = request.headers.get('Authorization')
|
||||
if not token:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': False
|
||||
}), 200
|
||||
|
||||
if token.startswith('Bearer '):
|
||||
token = token[7:]
|
||||
|
||||
result = verify_token(token)
|
||||
if result['success']:
|
||||
user_data = result['data']
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': True,
|
||||
'user': {
|
||||
'id': user_data['user_id'],
|
||||
'email': user_data['email'],
|
||||
'username': user_data['username']
|
||||
}
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': False
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'服务器错误: {str(e)}'
|
||||
}), 500
|
||||
#==========================对外暴露的HTTP接口==========================
|
||||
@@ -1,283 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
邮件发送模块
|
||||
负责处理用户注册、登录验证邮件
|
||||
"""
|
||||
|
||||
import random
|
||||
import string
|
||||
import smtplib
|
||||
from datetime import datetime, timedelta
|
||||
from email.mime.text import MIMEText
|
||||
from email.header import Header
|
||||
from flask import current_app
|
||||
import logging
|
||||
import os
|
||||
|
||||
# 验证码存储(生产环境建议使用Redis)
|
||||
verification_codes = {}
|
||||
|
||||
# 初始化日志
|
||||
def init_mail(app):
|
||||
"""初始化邮件配置"""
|
||||
# 使用smtplib直接发送,不需要Flask-Mail
|
||||
pass
|
||||
|
||||
# 生成验证码
|
||||
def generate_verification_code(length=6):
|
||||
"""生成验证码"""
|
||||
return ''.join(random.choices(string.digits, k=length))
|
||||
|
||||
# 发送验证邮件
|
||||
def send_verification_email(email, verification_type='register'):
|
||||
"""
|
||||
发送验证邮件
|
||||
|
||||
Args:
|
||||
email: 收件人邮箱
|
||||
verification_type: 验证类型 ('register', 'login', 'reset_password')
|
||||
|
||||
Returns:
|
||||
dict: 发送结果
|
||||
"""
|
||||
try:
|
||||
# 验证QQ邮箱格式
|
||||
if not is_qq_email(email):
|
||||
return {
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱注册登录'
|
||||
}
|
||||
|
||||
# 生成验证码
|
||||
code = generate_verification_code()
|
||||
|
||||
# 存储验证码(5分钟有效期)
|
||||
verification_codes[email] = {
|
||||
'code': code,
|
||||
'type': verification_type,
|
||||
'expires_at': datetime.now() + timedelta(minutes=5),
|
||||
'attempts': 0
|
||||
}
|
||||
|
||||
# 获取邮件配置 - 使用与QQEmailSendAPI相同的配置
|
||||
sender_email = os.environ.get('MAIL_USERNAME', '3205788256@qq.com')
|
||||
sender_password = os.environ.get('MAIL_PASSWORD', 'szcaxvbftusqddhi')
|
||||
|
||||
# 邮件模板
|
||||
if verification_type == 'register':
|
||||
subject = '【万象口袋】注册验证码'
|
||||
html_content = f'''
|
||||
<html>
|
||||
<body>
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="color: #66bb6a; margin: 0;">万象口袋</h1>
|
||||
<p style="color: #666; font-size: 14px; margin: 5px 0;">欢迎注册万象口袋</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 100%); padding: 30px; border-radius: 15px; text-align: center;">
|
||||
<h2 style="color: #2e7d32; margin-bottom: 20px;">验证码</h2>
|
||||
<div style="background: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
||||
<span style="font-size: 32px; font-weight: bold; color: #66bb6a; letter-spacing: 5px;">{code}</span>
|
||||
</div>
|
||||
<p style="color: #4a4a4a; margin: 15px 0;">请在5分钟内输入此验证码完成注册</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
|
||||
<p style="color: #666; font-size: 12px; margin: 0; text-align: center;">
|
||||
如果您没有申请注册,请忽略此邮件<br>
|
||||
此验证码5分钟内有效,请勿泄露给他人
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
else: # login
|
||||
subject = '【InfoGenie】登录验证码'
|
||||
html_content = f'''
|
||||
<html>
|
||||
<body>
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="color: #66bb6a; margin: 0;">InfoGenie 万象口袋</h1>
|
||||
<p style="color: #666; font-size: 14px; margin: 5px 0;">安全登录验证</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%); padding: 30px; border-radius: 15px; text-align: center;">
|
||||
<h2 style="color: white; margin-bottom: 20px;">登录验证码</h2>
|
||||
<div style="background: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
||||
<span style="font-size: 32px; font-weight: bold; color: #66bb6a; letter-spacing: 5px;">{code}</span>
|
||||
</div>
|
||||
<p style="color: white; margin: 15px 0;">请在5分钟内输入此验证码完成登录</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
|
||||
<p style="color: #666; font-size: 12px; margin: 0; text-align: center;">
|
||||
如果不是您本人操作,请检查账户安全<br>
|
||||
此验证码5分钟内有效,请勿泄露给他人
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
# 创建邮件 - 使用与QQEmailSendAPI相同的方式
|
||||
message = MIMEText(html_content, 'html', 'utf-8')
|
||||
message['From'] = sender_email # 直接使用邮箱地址,不使用Header包装
|
||||
message['To'] = email
|
||||
message['Subject'] = Header(subject, 'utf-8')
|
||||
|
||||
# 发送邮件 - 使用SSL端口465
|
||||
try:
|
||||
# 使用与QQEmailSendAPI相同的连接方式
|
||||
smtp_obj = smtplib.SMTP_SSL('smtp.qq.com', 465)
|
||||
smtp_obj.login(sender_email, sender_password)
|
||||
smtp_obj.sendmail(sender_email, [email], message.as_string())
|
||||
smtp_obj.quit()
|
||||
|
||||
print(f"验证码邮件发送成功: {email}")
|
||||
return {
|
||||
'success': True,
|
||||
'message': '验证码已发送到您的邮箱',
|
||||
'email': email
|
||||
}
|
||||
|
||||
except smtplib.SMTPAuthenticationError as auth_error:
|
||||
print(f"SMTP认证失败: {str(auth_error)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'SMTP认证失败,请检查邮箱配置'
|
||||
}
|
||||
except smtplib.SMTPConnectError as conn_error:
|
||||
print(f"SMTP连接失败: {str(conn_error)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'SMTP服务器连接失败'
|
||||
}
|
||||
except Exception as smtp_error:
|
||||
print(f"SMTP发送失败: {str(smtp_error)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'邮件发送失败: {str(smtp_error)}'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"邮件发送失败: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': '邮件发送失败,请稍后重试'
|
||||
}
|
||||
|
||||
# 验证验证码
|
||||
def verify_code(email, code):
|
||||
"""
|
||||
验证验证码
|
||||
|
||||
Args:
|
||||
email: 邮箱地址
|
||||
code: 验证码
|
||||
|
||||
Returns:
|
||||
dict: 验证结果
|
||||
"""
|
||||
if email not in verification_codes:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '验证码不存在或已过期'
|
||||
}
|
||||
|
||||
stored_info = verification_codes[email]
|
||||
|
||||
# 检查过期时间
|
||||
if datetime.now() > stored_info['expires_at']:
|
||||
del verification_codes[email]
|
||||
return {
|
||||
'success': False,
|
||||
'message': '验证码已过期,请重新获取'
|
||||
}
|
||||
|
||||
# 检查尝试次数
|
||||
if stored_info['attempts'] >= 3:
|
||||
del verification_codes[email]
|
||||
return {
|
||||
'success': False,
|
||||
'message': '验证码输入错误次数过多,请重新获取'
|
||||
}
|
||||
|
||||
# 验证码校验
|
||||
if stored_info['code'] != code:
|
||||
stored_info['attempts'] += 1
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'验证码错误,还可尝试{3 - stored_info["attempts"]}次'
|
||||
}
|
||||
|
||||
# 验证成功,删除验证码
|
||||
verification_type = stored_info['type']
|
||||
del verification_codes[email]
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': '验证码验证成功',
|
||||
'type': verification_type
|
||||
}
|
||||
|
||||
# 验证QQ邮箱格式
|
||||
def is_qq_email(email):
|
||||
"""
|
||||
验证是否为QQ邮箱
|
||||
|
||||
Args:
|
||||
email: 邮箱地址
|
||||
|
||||
Returns:
|
||||
bool: 是否为QQ邮箱
|
||||
"""
|
||||
if not email or '@' not in email:
|
||||
return False
|
||||
|
||||
domain = email.split('@')[1].lower()
|
||||
qq_domains = ['qq.com', 'vip.qq.com', 'foxmail.com']
|
||||
|
||||
return domain in qq_domains
|
||||
|
||||
# 获取QQ头像URL
|
||||
def get_qq_avatar_url(email):
|
||||
"""
|
||||
根据QQ邮箱获取QQ头像URL
|
||||
|
||||
Args:
|
||||
email: QQ邮箱地址
|
||||
|
||||
Returns:
|
||||
str: QQ头像URL
|
||||
"""
|
||||
if not is_qq_email(email):
|
||||
return None
|
||||
|
||||
# 提取QQ号码
|
||||
qq_number = email.split('@')[0]
|
||||
|
||||
# 验证是否为纯数字(QQ号)
|
||||
if not qq_number.isdigit():
|
||||
return None
|
||||
|
||||
# 返回QQ头像API URL
|
||||
return f"http://q1.qlogo.cn/g?b=qq&nk={qq_number}&s=100"
|
||||
|
||||
# 清理过期验证码
|
||||
def cleanup_expired_codes():
|
||||
"""清理过期的验证码"""
|
||||
current_time = datetime.now()
|
||||
expired_emails = [
|
||||
email for email, info in verification_codes.items()
|
||||
if current_time > info['expires_at']
|
||||
]
|
||||
|
||||
for email in expired_emails:
|
||||
del verification_codes[email]
|
||||
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
邮件发送模块
|
||||
负责处理用户注册、登录验证邮件
|
||||
"""
|
||||
|
||||
import random
|
||||
import string
|
||||
import smtplib
|
||||
from datetime import datetime, timedelta
|
||||
from email.mime.text import MIMEText
|
||||
from email.header import Header
|
||||
from flask import current_app
|
||||
import logging
|
||||
import os
|
||||
|
||||
# 验证码存储(生产环境建议使用Redis)
|
||||
verification_codes = {}
|
||||
|
||||
# 初始化日志
|
||||
def init_mail(app):
|
||||
"""初始化邮件配置"""
|
||||
# 使用smtplib直接发送,不需要Flask-Mail
|
||||
pass
|
||||
|
||||
# 生成验证码
|
||||
def generate_verification_code(length=6):
|
||||
"""生成验证码"""
|
||||
return ''.join(random.choices(string.digits, k=length))
|
||||
|
||||
# 发送验证邮件
|
||||
def send_verification_email(email, verification_type='register'):
|
||||
"""
|
||||
发送验证邮件
|
||||
|
||||
Args:
|
||||
email: 收件人邮箱
|
||||
verification_type: 验证类型 ('register', 'login', 'reset_password')
|
||||
|
||||
Returns:
|
||||
dict: 发送结果
|
||||
"""
|
||||
try:
|
||||
# 验证QQ邮箱格式
|
||||
if not is_qq_email(email):
|
||||
return {
|
||||
'success': False,
|
||||
'message': '仅支持QQ邮箱注册登录'
|
||||
}
|
||||
|
||||
# 生成验证码
|
||||
code = generate_verification_code()
|
||||
|
||||
# 存储验证码(5分钟有效期)
|
||||
verification_codes[email] = {
|
||||
'code': code,
|
||||
'type': verification_type,
|
||||
'expires_at': datetime.now() + timedelta(minutes=5),
|
||||
'attempts': 0
|
||||
}
|
||||
|
||||
# 获取邮件配置 - 使用与QQEmailSendAPI相同的配置
|
||||
sender_email = os.environ.get('MAIL_USERNAME', '3205788256@qq.com')
|
||||
sender_password = os.environ.get('MAIL_PASSWORD', 'szcaxvbftusqddhi')
|
||||
|
||||
# 邮件模板
|
||||
if verification_type == 'register':
|
||||
subject = '【万象口袋】注册验证码'
|
||||
html_content = f'''
|
||||
<html>
|
||||
<body>
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="color: #66bb6a; margin: 0;">万象口袋</h1>
|
||||
<p style="color: #666; font-size: 14px; margin: 5px 0;">欢迎注册万象口袋</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 100%); padding: 30px; border-radius: 15px; text-align: center;">
|
||||
<h2 style="color: #2e7d32; margin-bottom: 20px;">验证码</h2>
|
||||
<div style="background: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
||||
<span style="font-size: 32px; font-weight: bold; color: #66bb6a; letter-spacing: 5px;">{code}</span>
|
||||
</div>
|
||||
<p style="color: #4a4a4a; margin: 15px 0;">请在5分钟内输入此验证码完成注册</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
|
||||
<p style="color: #666; font-size: 12px; margin: 0; text-align: center;">
|
||||
如果您没有申请注册,请忽略此邮件<br>
|
||||
此验证码5分钟内有效,请勿泄露给他人
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
else: # login
|
||||
subject = '【InfoGenie】登录验证码'
|
||||
html_content = f'''
|
||||
<html>
|
||||
<body>
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="color: #66bb6a; margin: 0;">InfoGenie 万象口袋</h1>
|
||||
<p style="color: #666; font-size: 14px; margin: 5px 0;">安全登录验证</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%); padding: 30px; border-radius: 15px; text-align: center;">
|
||||
<h2 style="color: white; margin-bottom: 20px;">登录验证码</h2>
|
||||
<div style="background: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
||||
<span style="font-size: 32px; font-weight: bold; color: #66bb6a; letter-spacing: 5px;">{code}</span>
|
||||
</div>
|
||||
<p style="color: white; margin: 15px 0;">请在5分钟内输入此验证码完成登录</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
|
||||
<p style="color: #666; font-size: 12px; margin: 0; text-align: center;">
|
||||
如果不是您本人操作,请检查账户安全<br>
|
||||
此验证码5分钟内有效,请勿泄露给他人
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
# 创建邮件 - 使用与QQEmailSendAPI相同的方式
|
||||
message = MIMEText(html_content, 'html', 'utf-8')
|
||||
message['From'] = sender_email # 直接使用邮箱地址,不使用Header包装
|
||||
message['To'] = email
|
||||
message['Subject'] = Header(subject, 'utf-8')
|
||||
|
||||
# 发送邮件 - 使用SSL端口465
|
||||
try:
|
||||
# 使用与QQEmailSendAPI相同的连接方式
|
||||
smtp_obj = smtplib.SMTP_SSL('smtp.qq.com', 465)
|
||||
smtp_obj.login(sender_email, sender_password)
|
||||
smtp_obj.sendmail(sender_email, [email], message.as_string())
|
||||
smtp_obj.quit()
|
||||
|
||||
print(f"验证码邮件发送成功: {email}")
|
||||
return {
|
||||
'success': True,
|
||||
'message': '验证码已发送到您的邮箱',
|
||||
'email': email
|
||||
}
|
||||
|
||||
except smtplib.SMTPAuthenticationError as auth_error:
|
||||
print(f"SMTP认证失败: {str(auth_error)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'SMTP认证失败,请检查邮箱配置'
|
||||
}
|
||||
except smtplib.SMTPConnectError as conn_error:
|
||||
print(f"SMTP连接失败: {str(conn_error)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'SMTP服务器连接失败'
|
||||
}
|
||||
except Exception as smtp_error:
|
||||
print(f"SMTP发送失败: {str(smtp_error)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'邮件发送失败: {str(smtp_error)}'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"邮件发送失败: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': '邮件发送失败,请稍后重试'
|
||||
}
|
||||
|
||||
# 验证验证码
|
||||
def verify_code(email, code):
|
||||
"""
|
||||
验证验证码
|
||||
|
||||
Args:
|
||||
email: 邮箱地址
|
||||
code: 验证码
|
||||
|
||||
Returns:
|
||||
dict: 验证结果
|
||||
"""
|
||||
if email not in verification_codes:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '验证码不存在或已过期'
|
||||
}
|
||||
|
||||
stored_info = verification_codes[email]
|
||||
|
||||
# 检查过期时间
|
||||
if datetime.now() > stored_info['expires_at']:
|
||||
del verification_codes[email]
|
||||
return {
|
||||
'success': False,
|
||||
'message': '验证码已过期,请重新获取'
|
||||
}
|
||||
|
||||
# 检查尝试次数
|
||||
if stored_info['attempts'] >= 3:
|
||||
del verification_codes[email]
|
||||
return {
|
||||
'success': False,
|
||||
'message': '验证码输入错误次数过多,请重新获取'
|
||||
}
|
||||
|
||||
# 验证码校验
|
||||
if stored_info['code'] != code:
|
||||
stored_info['attempts'] += 1
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'验证码错误,还可尝试{3 - stored_info["attempts"]}次'
|
||||
}
|
||||
|
||||
# 验证成功,删除验证码
|
||||
verification_type = stored_info['type']
|
||||
del verification_codes[email]
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': '验证码验证成功',
|
||||
'type': verification_type
|
||||
}
|
||||
|
||||
# 验证QQ邮箱格式
|
||||
def is_qq_email(email):
|
||||
"""
|
||||
验证是否为QQ邮箱
|
||||
|
||||
Args:
|
||||
email: 邮箱地址
|
||||
|
||||
Returns:
|
||||
bool: 是否为QQ邮箱
|
||||
"""
|
||||
if not email or '@' not in email:
|
||||
return False
|
||||
|
||||
domain = email.split('@')[1].lower()
|
||||
qq_domains = ['qq.com', 'vip.qq.com', 'foxmail.com']
|
||||
|
||||
return domain in qq_domains
|
||||
|
||||
# 获取QQ头像URL
|
||||
def get_qq_avatar_url(email):
|
||||
"""
|
||||
根据QQ邮箱获取QQ头像URL
|
||||
|
||||
Args:
|
||||
email: QQ邮箱地址
|
||||
|
||||
Returns:
|
||||
str: QQ头像URL
|
||||
"""
|
||||
if not is_qq_email(email):
|
||||
return None
|
||||
|
||||
# 提取QQ号码
|
||||
qq_number = email.split('@')[0]
|
||||
|
||||
# 验证是否为纯数字(QQ号)
|
||||
if not qq_number.isdigit():
|
||||
return None
|
||||
|
||||
# 返回QQ头像API URL
|
||||
return f"https://q1.qlogo.cn/g?b=qq&nk={qq_number}&s=100"
|
||||
|
||||
# 清理过期验证码
|
||||
def cleanup_expired_codes():
|
||||
"""清理过期的验证码"""
|
||||
current_time = datetime.now()
|
||||
expired_emails = [
|
||||
email for email, info in verification_codes.items()
|
||||
if current_time > info['expires_at']
|
||||
]
|
||||
|
||||
for email in expired_emails:
|
||||
del verification_codes[email]
|
||||
|
||||
return len(expired_emails)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,29 @@
|
||||
# InfoGenie 后端依赖包
|
||||
# Web框架
|
||||
Flask==2.3.3
|
||||
Flask-CORS==4.0.0
|
||||
|
||||
# 数据库
|
||||
Flask-PyMongo==2.3.0
|
||||
pymongo==4.5.0
|
||||
|
||||
# 密码加密
|
||||
Werkzeug==2.3.7
|
||||
|
||||
# JWT认证
|
||||
PyJWT==2.8.0
|
||||
|
||||
# HTTP请求
|
||||
requests==2.31.0
|
||||
|
||||
# 邮件发送
|
||||
Flask-Mail==0.9.1
|
||||
|
||||
# 数据处理
|
||||
python-dateutil==2.8.2
|
||||
|
||||
# 环境变量
|
||||
python-dotenv==1.0.0
|
||||
|
||||
# 开发工具
|
||||
flask-limiter==3.5.0 # API限流
|
||||
# InfoGenie 后端依赖包
|
||||
# Web框架
|
||||
Flask==2.3.3
|
||||
Flask-CORS==4.0.0
|
||||
|
||||
# 数据库
|
||||
Flask-PyMongo==2.3.0
|
||||
pymongo==4.5.0
|
||||
|
||||
# 密码加密
|
||||
Werkzeug==2.3.7
|
||||
|
||||
# JWT认证
|
||||
PyJWT==2.8.0
|
||||
|
||||
# HTTP请求
|
||||
requests==2.31.0
|
||||
|
||||
# 邮件发送
|
||||
Flask-Mail==0.9.1
|
||||
|
||||
# 数据处理
|
||||
python-dateutil==2.8.2
|
||||
|
||||
# 环境变量
|
||||
python-dotenv==1.0.0
|
||||
|
||||
# 开发工具
|
||||
flask-limiter==3.5.0 # API限流
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
@echo off
|
||||
python app.py
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
python3 app.py
|
||||
#!/bin/bash
|
||||
python3 app.py
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试注册邮件发送
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_send_verification_email():
|
||||
"""测试发送验证码邮件"""
|
||||
url = "http://localhost:5000/api/auth/send-verification"
|
||||
|
||||
test_data = {
|
||||
"email": "3205788256@qq.com", # 使用配置的邮箱
|
||||
"type": "register"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=test_data)
|
||||
print(f"状态码: {response.status_code}")
|
||||
print(f"响应: {response.json()}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("\n✅ 邮件发送成功!请检查邮箱")
|
||||
else:
|
||||
print(f"\n❌ 邮件发送失败: {response.json().get('message', '未知错误')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 请求失败: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("📧 测试注册邮件发送...")
|
||||
test_send_verification_email()
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
MongoDB连接测试
|
||||
"""
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
def test_connection():
|
||||
# 测试不同的连接配置
|
||||
configs = [
|
||||
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie",
|
||||
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie?authSource=admin",
|
||||
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie?authSource=InfoGenie",
|
||||
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/?authSource=admin",
|
||||
]
|
||||
|
||||
for i, uri in enumerate(configs):
|
||||
print(f"\n测试配置 {i+1}: {uri}")
|
||||
try:
|
||||
client = MongoClient(uri, serverSelectionTimeoutMS=5000)
|
||||
client.admin.command('ping')
|
||||
print("✅ 连接成功!")
|
||||
|
||||
# 测试InfoGenie数据库
|
||||
db = client.InfoGenie
|
||||
collections = db.list_collection_names()
|
||||
print(f"数据库集合: {collections}")
|
||||
|
||||
# 测试userdata集合
|
||||
if 'userdata' in collections:
|
||||
count = db.userdata.count_documents({})
|
||||
print(f"userdata集合文档数: {count}")
|
||||
|
||||
client.close()
|
||||
return uri
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 连接失败: {str(e)}")
|
||||
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔧 测试MongoDB连接...")
|
||||
success_uri = test_connection()
|
||||
if success_uri:
|
||||
print(f"\n✅ 成功的连接字符串: {success_uri}")
|
||||
else:
|
||||
print("\n❌ 所有连接尝试都失败了")
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试为指定账号增加萌芽币接口 (/api/user/add-coins)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 加入后端根目录到路径,导入create_app
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import create_app
|
||||
from modules.auth import generate_token
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
|
||||
def run_test():
|
||||
"""运行加币接口测试,打印真实响应并断言结果"""
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
db = app.mongo.db
|
||||
users = db.userdata
|
||||
|
||||
# 构造一个临时测试用户(真实写库,测试结束删除)
|
||||
test_email = "infogenie.test.addcoins@foxmail.com"
|
||||
users.delete_many({'邮箱': test_email})
|
||||
test_user = {
|
||||
'邮箱': test_email,
|
||||
'用户名': '测试用户_加币',
|
||||
'密码': generate_password_hash('AddCoins123!'),
|
||||
'头像': None,
|
||||
'注册时间': datetime.now().isoformat(),
|
||||
'最后登录': None,
|
||||
'登录次数': 0,
|
||||
'用户状态': 'active',
|
||||
'等级': 0,
|
||||
'经验': 0,
|
||||
'萌芽币': 0,
|
||||
'签到系统': {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': datetime.now().strftime('%Y-%m-%d')
|
||||
}
|
||||
}
|
||||
insert_result = users.insert_one(test_user)
|
||||
test_user_id = str(insert_result.inserted_id)
|
||||
|
||||
# 生成有效JWT用于认证
|
||||
token = generate_token({
|
||||
'user_id': test_user_id,
|
||||
'email': test_email,
|
||||
'username': test_user['用户名']
|
||||
})
|
||||
|
||||
client = app.test_client()
|
||||
|
||||
# 第一次加币: +500
|
||||
resp1 = client.post(
|
||||
'/api/user/add-coins',
|
||||
headers={'Authorization': f'Bearer {token}'},
|
||||
json={'email': test_email, 'amount': 500}
|
||||
)
|
||||
print('第一次加币 状态码:', resp1.status_code)
|
||||
data1 = resp1.get_json()
|
||||
print('第一次加币 响应:')
|
||||
print(json.dumps(data1, ensure_ascii=False, indent=2))
|
||||
assert resp1.status_code == 200
|
||||
assert data1.get('success') is True
|
||||
assert data1['data']['before_coins'] == 0
|
||||
assert data1['data']['added'] == 500
|
||||
assert data1['data']['new_coins'] == 500
|
||||
|
||||
# 第二次加币: +200
|
||||
resp2 = client.post(
|
||||
'/api/user/add-coins',
|
||||
headers={'Authorization': f'Bearer {token}'},
|
||||
json={'email': test_email, 'amount': 200}
|
||||
)
|
||||
print('第二次加币 状态码:', resp2.status_code)
|
||||
data2 = resp2.get_json()
|
||||
print('第二次加币 响应:')
|
||||
print(json.dumps(data2, ensure_ascii=False, indent=2))
|
||||
assert resp2.status_code == 200
|
||||
assert data2.get('success') is True
|
||||
assert data2['data']['before_coins'] == 500
|
||||
assert data2['data']['added'] == 200
|
||||
assert data2['data']['new_coins'] == 700
|
||||
|
||||
# 清理临时测试用户
|
||||
users.delete_many({'邮箱': test_email})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('🔧 开始测试 /api/user/add-coins 接口...')
|
||||
run_test()
|
||||
print('✅ 测试完成!')
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试邮件发送功能
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_send_verification():
|
||||
"""测试发送验证码"""
|
||||
url = "http://localhost:5000/api/auth/send-verification"
|
||||
|
||||
# 测试数据
|
||||
test_data = {
|
||||
"email": "3205788256@qq.com", # 使用配置中的测试邮箱
|
||||
"type": "register"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=test_data)
|
||||
print(f"状态码: {response.status_code}")
|
||||
print(f"响应内容: {response.json()}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ 邮件发送成功!")
|
||||
else:
|
||||
print("❌ 邮件发送失败")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 请求失败: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("📧 测试邮件发送功能...")
|
||||
test_send_verification()
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试修复后的邮件发送功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加父目录到路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from modules.email_service import send_verification_email, verify_code
|
||||
|
||||
def test_email_sending():
|
||||
"""
|
||||
测试邮件发送功能
|
||||
"""
|
||||
print("=== 测试邮件发送功能 ===")
|
||||
|
||||
# 测试邮箱(请替换为你的QQ邮箱)
|
||||
test_email = "3205788256@qq.com" # 替换为实际的测试邮箱
|
||||
|
||||
print(f"正在向 {test_email} 发送注册验证码...")
|
||||
|
||||
# 发送注册验证码
|
||||
result = send_verification_email(test_email, 'register')
|
||||
|
||||
print(f"发送结果: {result}")
|
||||
|
||||
if result['success']:
|
||||
print("✅ 邮件发送成功!")
|
||||
if 'code' in result:
|
||||
print(f"验证码: {result['code']}")
|
||||
|
||||
# 测试验证码验证
|
||||
print("\n=== 测试验证码验证 ===")
|
||||
verify_result = verify_code(test_email, result['code'])
|
||||
print(f"验证结果: {verify_result}")
|
||||
|
||||
if verify_result['success']:
|
||||
print("✅ 验证码验证成功!")
|
||||
else:
|
||||
print("❌ 验证码验证失败!")
|
||||
else:
|
||||
print("❌ 邮件发送失败!")
|
||||
print(f"错误信息: {result['message']}")
|
||||
|
||||
def test_login_email():
|
||||
"""
|
||||
测试登录验证码邮件
|
||||
"""
|
||||
print("\n=== 测试登录验证码邮件 ===")
|
||||
|
||||
test_email = "3205788256@qq.com" # 替换为实际的测试邮箱
|
||||
|
||||
print(f"正在向 {test_email} 发送登录验证码...")
|
||||
|
||||
result = send_verification_email(test_email, 'login')
|
||||
|
||||
print(f"发送结果: {result}")
|
||||
|
||||
if result['success']:
|
||||
print("✅ 登录验证码邮件发送成功!")
|
||||
if 'code' in result:
|
||||
print(f"验证码: {result['code']}")
|
||||
else:
|
||||
print("❌ 登录验证码邮件发送失败!")
|
||||
print(f"错误信息: {result['message']}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("InfoGenie 邮件服务测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试注册验证码
|
||||
test_email_sending()
|
||||
|
||||
# 测试登录验证码
|
||||
test_login_email()
|
||||
|
||||
print("\n测试完成!")
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试MongoDB连接
|
||||
"""
|
||||
|
||||
import os
|
||||
from pymongo import MongoClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
def test_mongodb_connection():
|
||||
"""测试MongoDB连接"""
|
||||
try:
|
||||
# 获取连接字符串
|
||||
mongo_uri = os.environ.get('MONGO_URI')
|
||||
print(f"连接字符串: {mongo_uri}")
|
||||
|
||||
# 创建连接
|
||||
client = MongoClient(mongo_uri)
|
||||
|
||||
# 测试连接
|
||||
client.admin.command('ping')
|
||||
print("✅ MongoDB连接成功!")
|
||||
|
||||
# 获取数据库
|
||||
db = client.InfoGenie
|
||||
print(f"数据库: {db.name}")
|
||||
|
||||
# 测试集合访问
|
||||
userdata_collection = db.userdata
|
||||
print(f"用户集合: {userdata_collection.name}")
|
||||
|
||||
# 测试查询(计算文档数量)
|
||||
count = userdata_collection.count_documents({})
|
||||
print(f"用户数据集合中有 {count} 个文档")
|
||||
|
||||
# 关闭连接
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ MongoDB连接失败: {str(e)}")
|
||||
|
||||
# 尝试其他认证数据库
|
||||
print("\n尝试使用不同的认证配置...")
|
||||
try:
|
||||
# 尝试不指定认证数据库
|
||||
uri_without_auth = "mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie"
|
||||
client2 = MongoClient(uri_without_auth)
|
||||
client2.admin.command('ping')
|
||||
print("✅ 不使用authSource连接成功!")
|
||||
client2.close()
|
||||
except Exception as e2:
|
||||
print(f"❌ 无authSource也失败: {str(e2)}")
|
||||
|
||||
# 尝试使用InfoGenie作为认证数据库
|
||||
try:
|
||||
uri_with_infogenie_auth = "mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie?authSource=InfoGenie"
|
||||
client3 = MongoClient(uri_with_infogenie_auth)
|
||||
client3.admin.command('ping')
|
||||
print("✅ 使用InfoGenie作为authSource连接成功!")
|
||||
client3.close()
|
||||
except Exception as e3:
|
||||
print(f"❌ InfoGenie authSource也失败: {str(e3)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔧 测试MongoDB连接...")
|
||||
test_mongodb_connection()
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试列出所有用户的HTTP接口 (/api/user/list)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 将后端根目录加入路径,便于导入app
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import create_app
|
||||
from modules.auth import generate_token
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
|
||||
def run_test():
|
||||
"""运行用户列表接口测试,输出真实数据"""
|
||||
# 使用.env中的真实Mongo配置,不造假
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
db = app.mongo.db
|
||||
users = db.userdata
|
||||
|
||||
# 插入一个测试用户(真实写入后再删除),确保可验证接口输出
|
||||
test_email = "infogenie.test.user@foxmail.com"
|
||||
users.delete_many({'邮箱': test_email})
|
||||
test_user = {
|
||||
'邮箱': test_email,
|
||||
'用户名': '测试用户_列表',
|
||||
'密码': generate_password_hash('TestPass123!'),
|
||||
'头像': None,
|
||||
'注册时间': datetime.now().isoformat(),
|
||||
'最后登录': None,
|
||||
'登录次数': 0,
|
||||
'用户状态': 'active',
|
||||
'等级': 0,
|
||||
'经验': 0,
|
||||
'萌芽币': 0,
|
||||
'签到系统': {
|
||||
'连续签到天数': 0,
|
||||
'今日是否已签到': False,
|
||||
'签到时间': datetime.now().strftime('%Y-%m-%d')
|
||||
}
|
||||
}
|
||||
insert_result = users.insert_one(test_user)
|
||||
test_user_id = str(insert_result.inserted_id)
|
||||
|
||||
# 生成有效JWT,满足认证要求
|
||||
token = generate_token({
|
||||
'user_id': test_user_id,
|
||||
'email': test_email,
|
||||
'username': test_user['用户名']
|
||||
})
|
||||
|
||||
client = app.test_client()
|
||||
resp = client.get('/api/user/list', headers={'Authorization': f'Bearer {token}'})
|
||||
|
||||
print("状态码:", resp.status_code)
|
||||
data = resp.get_json()
|
||||
print("响应内容:")
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
|
||||
# 基本断言,确保返回真实列表数据且包含刚插入的测试用户
|
||||
assert resp.status_code == 200
|
||||
assert data.get('success') is True
|
||||
assert isinstance(data.get('data'), list)
|
||||
assert any(u.get('email') == test_email for u in data['data'])
|
||||
|
||||
# 清理测试数据
|
||||
users.delete_many({'邮箱': test_email})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('🔎 开始测试 /api/user/list 接口...')
|
||||
run_test()
|
||||
print('✅ 测试完成!')
|
||||
@@ -1,396 +1,396 @@
|
||||
# InfoGenie 后端架构文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
InfoGenie(万象口袋)是一个基于前后端分离架构的多功能聚合软件应用。后端采用Flask框架提供RESTful API服务,前端通过HTTP请求调用后端API,实现数据交互和业务逻辑处理。
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 核心框架
|
||||
- **Web框架**: Flask 2.3.3
|
||||
- **数据库**: MongoDB (Flask-PyMongo 2.3.0)
|
||||
- **认证**: JWT (PyJWT 2.8.0)
|
||||
- **跨域**: Flask-CORS 4.0.0
|
||||
|
||||
### 辅助工具
|
||||
- **邮件服务**: Flask-Mail 0.9.1
|
||||
- **密码加密**: Werkzeug 2.3.7
|
||||
- **环境配置**: python-dotenv 1.0.0
|
||||
- **API限流**: Flask-Limiter 3.5.0
|
||||
|
||||
## 架构设计原则
|
||||
|
||||
### 前后端分离
|
||||
- 后端专注于数据处理和业务逻辑
|
||||
- 前端负责用户界面和交互体验
|
||||
- 通过RESTful API进行数据交换
|
||||
- 完全解耦,便于独立开发和部署
|
||||
|
||||
### 模块化设计
|
||||
- 按功能划分独立模块
|
||||
- 每个模块职责单一
|
||||
- 便于维护和扩展
|
||||
|
||||
## 核心模块详解
|
||||
|
||||
### 1. 认证模块 (auth.py)
|
||||
|
||||
**功能职责**:
|
||||
- 用户注册和登录
|
||||
- JWT Token生成和管理
|
||||
- 邮箱验证码验证
|
||||
- QQ邮箱格式验证
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
POST /api/auth/send-verification # 发送验证码
|
||||
POST /api/auth/verify-code # 验证验证码
|
||||
POST /api/auth/register # 用户注册
|
||||
POST /api/auth/login # 用户登录
|
||||
POST /api/auth/logout # 用户登出
|
||||
GET /api/auth/check # 检查登录状态
|
||||
```
|
||||
|
||||
**数据流程**:
|
||||
1. 前端发送注册/登录请求
|
||||
2. 后端验证邮箱格式(仅支持QQ邮箱)
|
||||
3. 发送验证码邮件到用户邮箱
|
||||
4. 用户输入验证码完成验证
|
||||
5. 验证成功后生成JWT Token返回给前端
|
||||
|
||||
**安全特性**:
|
||||
- 密码使用Werkzeug进行哈希加密
|
||||
- JWT Token 7天有效期
|
||||
- 验证码5分钟有效期,限制尝试次数
|
||||
|
||||
### 2. 用户管理模块 (user_management.py)
|
||||
|
||||
**功能职责**:
|
||||
- 用户资料管理
|
||||
- 密码修改
|
||||
- 每日签到系统
|
||||
- 用户游戏数据管理
|
||||
- 账户删除
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
GET /api/user/profile # 获取用户资料
|
||||
POST /api/user/change-password # 修改密码
|
||||
GET /api/user/stats # 获取用户统计
|
||||
GET /api/user/game-data # 获取游戏数据
|
||||
POST /api/user/checkin # 每日签到
|
||||
POST /api/user/delete # 删除账户
|
||||
```
|
||||
|
||||
**数据结构**:
|
||||
```json
|
||||
{
|
||||
"邮箱": "user@qq.com",
|
||||
"用户名": "用户名",
|
||||
"密码": "哈希密码",
|
||||
"头像": "QQ头像URL",
|
||||
"注册时间": "2025-01-01T00:00:00",
|
||||
"最后登录": "2025-01-01T00:00:00",
|
||||
"登录次数": 10,
|
||||
"用户状态": "active",
|
||||
"等级": 5,
|
||||
"经验": 1200,
|
||||
"萌芽币": 1500,
|
||||
"签到系统": {
|
||||
"连续签到天数": 7,
|
||||
"今日是否已签到": true,
|
||||
"签到时间": "2025-01-01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**业务逻辑**:
|
||||
- 签到奖励:300萌芽币 + 200经验
|
||||
- 等级升级:100 × 1.2^(等级) 经验需求
|
||||
|
||||
### 3. 邮件服务模块 (email_service.py)
|
||||
|
||||
**功能职责**:
|
||||
- 验证码邮件发送
|
||||
- QQ邮箱格式验证
|
||||
- QQ头像获取
|
||||
- 邮件模板管理
|
||||
|
||||
**邮件模板**:
|
||||
- 注册验证码邮件(HTML格式)
|
||||
- 登录验证码邮件(HTML格式)
|
||||
- 支持自定义邮件内容和样式
|
||||
|
||||
**安全考虑**:
|
||||
- 仅支持QQ邮箱(qq.com、vip.qq.com、foxmail.com)
|
||||
- 使用SSL加密连接
|
||||
- 验证码存储在内存中(生产环境建议使用Redis)
|
||||
|
||||
### 4. AI模型应用模块 (aimodelapp.py)
|
||||
|
||||
**功能职责**:
|
||||
- 集成多种AI服务(DeepSeek、Kimi)
|
||||
- 提供AI功能API接口
|
||||
- 统一AI接口调用
|
||||
- 管理用户萌芽币消费(每次调用消耗100萌芽币)
|
||||
|
||||
**支持的AI功能**:
|
||||
1. **AI聊天接口** (`/api/aimodelapp/chat`)
|
||||
2. **姓名分析** (`/api/aimodelapp/name-analysis`)
|
||||
3. **变量命名助手** (`/api/aimodelapp/variable-naming`)
|
||||
4. **AI写诗助手** (`/api/aimodelapp/poetry`)
|
||||
5. **AI语言翻译** (`/api/aimodelapp/translation`)
|
||||
6. **现代文转文言文** (`/api/aimodelapp/classical_conversion`)
|
||||
7. **AI表情制作器** (`/api/aimodelapp/expression-maker`)
|
||||
8. **Linux命令生成** (`/api/aimodelapp/linux-command`)
|
||||
9. **获取可用模型** (`/api/aimodelapp/models`)
|
||||
|
||||
**AI配置**:
|
||||
```json
|
||||
{
|
||||
"deepseek": {
|
||||
"api_key": "your-api-key",
|
||||
"api_base": "https://api.deepseek.com",
|
||||
"model": ["deepseek-chat", "deepseek-reasoner"]
|
||||
},
|
||||
"kimi": {
|
||||
"api_key": "your-api-key",
|
||||
"api_base": "https://api.moonshot.cn",
|
||||
"model": ["kimi-k2-0905-preview", "kimi-k2-0711-preview"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**调用流程**:
|
||||
1. 前端发送AI请求(包含消息、模型提供商等参数)
|
||||
2. 后端加载AI配置文件
|
||||
3. 调用对应AI API(带重试机制)
|
||||
4. 返回AI响应给前端
|
||||
|
||||
## API设计规范
|
||||
|
||||
### 请求/响应格式
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {...},
|
||||
"message": "操作成功",
|
||||
"timestamp": "2025-01-01T00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息",
|
||||
"error": "错误详情"
|
||||
}
|
||||
```
|
||||
|
||||
### 认证方式
|
||||
|
||||
**JWT Token认证**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**支持的认证端点**:
|
||||
- 所有 `/api/user/*` 端点需要认证
|
||||
- 部分 `/api/aimodelapp/*` 端点需要认证
|
||||
|
||||
### 错误处理
|
||||
|
||||
**HTTP状态码**:
|
||||
- 200: 成功
|
||||
- 400: 请求参数错误
|
||||
- 401: 未认证/认证失败
|
||||
- 403: 权限不足
|
||||
- 404: 资源不存在
|
||||
- 409: 资源冲突
|
||||
- 500: 服务器内部错误
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### MongoDB集合
|
||||
|
||||
**主要集合**: `userdata`
|
||||
- 存储所有用户相关数据
|
||||
- 支持动态字段扩展
|
||||
- 使用ObjectId作为用户唯一标识
|
||||
|
||||
### 数据关系
|
||||
- 用户数据自包含,无复杂关联
|
||||
- 通过用户ID进行数据关联
|
||||
- 支持水平扩展
|
||||
|
||||
## 部署和配置
|
||||
|
||||
### 环境配置
|
||||
|
||||
**必需环境变量**:
|
||||
```
|
||||
SECRET_KEY=your-secret-key
|
||||
MONGO_URI=mongodb://localhost:27017/InfoGenie
|
||||
MAIL_USERNAME=your-email@qq.com
|
||||
MAIL_PASSWORD=your-app-password
|
||||
```
|
||||
|
||||
### 启动方式
|
||||
|
||||
**开发环境**:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
**生产环境**:
|
||||
- 支持Docker部署
|
||||
- 提供docker-compose配置
|
||||
- 支持Gunicorn WSGI服务器
|
||||
|
||||
### 静态文件服务
|
||||
|
||||
**支持的前端资源**:
|
||||
- `/60sapi/*`: 60秒API相关文件
|
||||
- `/smallgame/*`: 小游戏相关文件
|
||||
- `/aimodelapp/*`: AI模型应用相关文件
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 数据安全
|
||||
- 密码哈希存储
|
||||
- JWT Token安全传输
|
||||
- 输入数据验证和过滤
|
||||
|
||||
### API安全
|
||||
- CORS配置(生产环境限制域名)
|
||||
- API限流保护
|
||||
- 请求日志记录
|
||||
|
||||
### 部署安全
|
||||
- 环境变量管理敏感信息
|
||||
- HTTPS证书配置
|
||||
- 防火墙和访问控制
|
||||
|
||||
## 前后端协作指南
|
||||
|
||||
### 前端调用示例
|
||||
|
||||
**用户登录**:
|
||||
```javascript
|
||||
// 1. 发送验证码
|
||||
fetch('/api/auth/send-verification', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'user@qq.com', type: 'login' })
|
||||
});
|
||||
|
||||
// 2. 验证验证码并登录
|
||||
fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: 'user@qq.com',
|
||||
code: '123456'
|
||||
})
|
||||
});
|
||||
|
||||
// 3. 保存token到localStorage
|
||||
localStorage.setItem('token', response.token);
|
||||
```
|
||||
|
||||
**调用需要认证的API**:
|
||||
```javascript
|
||||
fetch('/api/user/profile', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 数据约定
|
||||
|
||||
**前端发送数据格式**:
|
||||
- 所有请求使用JSON格式
|
||||
- 必填字段验证
|
||||
- 参数命名使用snake_case
|
||||
|
||||
**后端返回数据格式**:
|
||||
- 统一响应格式
|
||||
- 时间戳使用ISO格式
|
||||
- 错误信息清晰明确
|
||||
|
||||
### 开发协作流程
|
||||
|
||||
1. **API设计阶段**:
|
||||
- 后端定义API接口规范
|
||||
- 前端根据规范开发调用代码
|
||||
- 约定数据格式和错误处理
|
||||
|
||||
2. **联调阶段**:
|
||||
- 使用统一的测试数据
|
||||
- 验证各种边界情况
|
||||
- 确认错误处理逻辑
|
||||
|
||||
3. **部署阶段**:
|
||||
- 后端部署API服务
|
||||
- 前端配置API基础URL
|
||||
- 验证跨域和认证配置
|
||||
|
||||
## 新功能添加
|
||||
|
||||
### 1. AI功能萌芽币消费系统
|
||||
|
||||
**功能描述**:
|
||||
- 用户每次调用AI模型应用(aimodelapp)需消耗100萌芽币
|
||||
- 当用户萌芽币余额不足时,无法使用AI功能
|
||||
- 记录用户的AI使用历史
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
GET /api/aimodelapp/coins # 查询用户萌芽币余额和使用历史
|
||||
```
|
||||
|
||||
**技术实现**:
|
||||
- 使用装饰器模式实现请求前验证和扣除萌芽币
|
||||
- 在MongoDB中记录用户AI使用历史
|
||||
- 通过JWT Token验证用户身份
|
||||
|
||||
**业务逻辑**:
|
||||
1. 当用户请求AI功能时,首先验证JWT Token
|
||||
2. 检查用户萌芽币余额是否≥100
|
||||
3. 如余额充足,先扣除萌芽币,然后再调用AI服务
|
||||
4. 记录使用历史,包括API类型、时间和消费萌芽币数量
|
||||
5. 返回AI服务结果给用户
|
||||
|
||||
**响应示例(查询萌芽币余额)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"coins": 200,
|
||||
"ai_cost": 100,
|
||||
"can_use_ai": true,
|
||||
"username": "用户名",
|
||||
"usage_count": 1,
|
||||
"recent_usage": [
|
||||
{
|
||||
"api_type": "chat",
|
||||
"cost": 100,
|
||||
"timestamp": "2025-09-16T11:15:47.285720"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "当前萌芽币余额: 200"
|
||||
}
|
||||
```
|
||||
|
||||
**前端开发注意事项**:
|
||||
- 每个需要调用AI功能的页面应首先检查用户萌芽币余额
|
||||
- 当萌芽币不足时,向用户提示并引导用户通过签到等方式获取萌芽币
|
||||
- 可在UI中展示用户最近的AI使用记录和萌芽币消费情况
|
||||
|
||||
---
|
||||
# InfoGenie 后端架构文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
InfoGenie(万象口袋)是一个基于前后端分离架构的多功能聚合软件应用。后端采用Flask框架提供RESTful API服务,前端通过HTTP请求调用后端API,实现数据交互和业务逻辑处理。
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 核心框架
|
||||
- **Web框架**: Flask 2.3.3
|
||||
- **数据库**: MongoDB (Flask-PyMongo 2.3.0)
|
||||
- **认证**: JWT (PyJWT 2.8.0)
|
||||
- **跨域**: Flask-CORS 4.0.0
|
||||
|
||||
### 辅助工具
|
||||
- **邮件服务**: Flask-Mail 0.9.1
|
||||
- **密码加密**: Werkzeug 2.3.7
|
||||
- **环境配置**: python-dotenv 1.0.0
|
||||
- **API限流**: Flask-Limiter 3.5.0
|
||||
|
||||
## 架构设计原则
|
||||
|
||||
### 前后端分离
|
||||
- 后端专注于数据处理和业务逻辑
|
||||
- 前端负责用户界面和交互体验
|
||||
- 通过RESTful API进行数据交换
|
||||
- 完全解耦,便于独立开发和部署
|
||||
|
||||
### 模块化设计
|
||||
- 按功能划分独立模块
|
||||
- 每个模块职责单一
|
||||
- 便于维护和扩展
|
||||
|
||||
## 核心模块详解
|
||||
|
||||
### 1. 认证模块 (auth.py)
|
||||
|
||||
**功能职责**:
|
||||
- 用户注册和登录
|
||||
- JWT Token生成和管理
|
||||
- 邮箱验证码验证
|
||||
- QQ邮箱格式验证
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
POST /api/auth/send-verification # 发送验证码
|
||||
POST /api/auth/verify-code # 验证验证码
|
||||
POST /api/auth/register # 用户注册
|
||||
POST /api/auth/login # 用户登录
|
||||
POST /api/auth/logout # 用户登出
|
||||
GET /api/auth/check # 检查登录状态
|
||||
```
|
||||
|
||||
**数据流程**:
|
||||
1. 前端发送注册/登录请求
|
||||
2. 后端验证邮箱格式(仅支持QQ邮箱)
|
||||
3. 发送验证码邮件到用户邮箱
|
||||
4. 用户输入验证码完成验证
|
||||
5. 验证成功后生成JWT Token返回给前端
|
||||
|
||||
**安全特性**:
|
||||
- 密码使用Werkzeug进行哈希加密
|
||||
- JWT Token 7天有效期
|
||||
- 验证码5分钟有效期,限制尝试次数
|
||||
|
||||
### 2. 用户管理模块 (user_management.py)
|
||||
|
||||
**功能职责**:
|
||||
- 用户资料管理
|
||||
- 密码修改
|
||||
- 每日签到系统
|
||||
- 用户游戏数据管理
|
||||
- 账户删除
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
GET /api/user/profile # 获取用户资料
|
||||
POST /api/user/change-password # 修改密码
|
||||
GET /api/user/stats # 获取用户统计
|
||||
GET /api/user/game-data # 获取游戏数据
|
||||
POST /api/user/checkin # 每日签到
|
||||
POST /api/user/delete # 删除账户
|
||||
```
|
||||
|
||||
**数据结构**:
|
||||
```json
|
||||
{
|
||||
"邮箱": "user@qq.com",
|
||||
"用户名": "用户名",
|
||||
"密码": "哈希密码",
|
||||
"头像": "QQ头像URL",
|
||||
"注册时间": "2025-01-01T00:00:00",
|
||||
"最后登录": "2025-01-01T00:00:00",
|
||||
"登录次数": 10,
|
||||
"用户状态": "active",
|
||||
"等级": 5,
|
||||
"经验": 1200,
|
||||
"萌芽币": 1500,
|
||||
"签到系统": {
|
||||
"连续签到天数": 7,
|
||||
"今日是否已签到": true,
|
||||
"签到时间": "2025-01-01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**业务逻辑**:
|
||||
- 签到奖励:300萌芽币 + 200经验
|
||||
- 等级升级:100 × 1.2^(等级) 经验需求
|
||||
|
||||
### 3. 邮件服务模块 (email_service.py)
|
||||
|
||||
**功能职责**:
|
||||
- 验证码邮件发送
|
||||
- QQ邮箱格式验证
|
||||
- QQ头像获取
|
||||
- 邮件模板管理
|
||||
|
||||
**邮件模板**:
|
||||
- 注册验证码邮件(HTML格式)
|
||||
- 登录验证码邮件(HTML格式)
|
||||
- 支持自定义邮件内容和样式
|
||||
|
||||
**安全考虑**:
|
||||
- 仅支持QQ邮箱(qq.com、vip.qq.com、foxmail.com)
|
||||
- 使用SSL加密连接
|
||||
- 验证码存储在内存中(生产环境建议使用Redis)
|
||||
|
||||
### 4. AI模型应用模块 (aimodelapp.py)
|
||||
|
||||
**功能职责**:
|
||||
- 集成多种AI服务(DeepSeek、Kimi)
|
||||
- 提供AI功能API接口
|
||||
- 统一AI接口调用
|
||||
- 管理用户萌芽币消费(每次调用消耗100萌芽币)
|
||||
|
||||
**支持的AI功能**:
|
||||
1. **AI聊天接口** (`/api/aimodelapp/chat`)
|
||||
2. **姓名分析** (`/api/aimodelapp/name-analysis`)
|
||||
3. **变量命名助手** (`/api/aimodelapp/variable-naming`)
|
||||
4. **AI写诗助手** (`/api/aimodelapp/poetry`)
|
||||
5. **AI语言翻译** (`/api/aimodelapp/translation`)
|
||||
6. **现代文转文言文** (`/api/aimodelapp/classical_conversion`)
|
||||
7. **AI表情制作器** (`/api/aimodelapp/expression-maker`)
|
||||
8. **Linux命令生成** (`/api/aimodelapp/linux-command`)
|
||||
9. **获取可用模型** (`/api/aimodelapp/models`)
|
||||
|
||||
**AI配置**:
|
||||
```json
|
||||
{
|
||||
"deepseek": {
|
||||
"api_key": "your-api-key",
|
||||
"api_base": "https://api.deepseek.com",
|
||||
"model": ["deepseek-chat", "deepseek-reasoner"]
|
||||
},
|
||||
"kimi": {
|
||||
"api_key": "your-api-key",
|
||||
"api_base": "https://api.moonshot.cn",
|
||||
"model": ["kimi-k2-0905-preview", "kimi-k2-0711-preview"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**调用流程**:
|
||||
1. 前端发送AI请求(包含消息、模型提供商等参数)
|
||||
2. 后端加载AI配置文件
|
||||
3. 调用对应AI API(带重试机制)
|
||||
4. 返回AI响应给前端
|
||||
|
||||
## API设计规范
|
||||
|
||||
### 请求/响应格式
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {...},
|
||||
"message": "操作成功",
|
||||
"timestamp": "2025-01-01T00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息",
|
||||
"error": "错误详情"
|
||||
}
|
||||
```
|
||||
|
||||
### 认证方式
|
||||
|
||||
**JWT Token认证**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**支持的认证端点**:
|
||||
- 所有 `/api/user/*` 端点需要认证
|
||||
- 部分 `/api/aimodelapp/*` 端点需要认证
|
||||
|
||||
### 错误处理
|
||||
|
||||
**HTTP状态码**:
|
||||
- 200: 成功
|
||||
- 400: 请求参数错误
|
||||
- 401: 未认证/认证失败
|
||||
- 403: 权限不足
|
||||
- 404: 资源不存在
|
||||
- 409: 资源冲突
|
||||
- 500: 服务器内部错误
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### MongoDB集合
|
||||
|
||||
**主要集合**: `userdata`
|
||||
- 存储所有用户相关数据
|
||||
- 支持动态字段扩展
|
||||
- 使用ObjectId作为用户唯一标识
|
||||
|
||||
### 数据关系
|
||||
- 用户数据自包含,无复杂关联
|
||||
- 通过用户ID进行数据关联
|
||||
- 支持水平扩展
|
||||
|
||||
## 部署和配置
|
||||
|
||||
### 环境配置
|
||||
|
||||
**必需环境变量**:
|
||||
```
|
||||
SECRET_KEY=your-secret-key
|
||||
MONGO_URI=mongodb://localhost:27017/InfoGenie
|
||||
MAIL_USERNAME=your-email@qq.com
|
||||
MAIL_PASSWORD=your-app-password
|
||||
```
|
||||
|
||||
### 启动方式
|
||||
|
||||
**开发环境**:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
**生产环境**:
|
||||
- 支持Docker部署
|
||||
- 提供docker-compose配置
|
||||
- 支持Gunicorn WSGI服务器
|
||||
|
||||
### 静态文件服务
|
||||
|
||||
**支持的前端资源**:
|
||||
- `/60sapi/*`: 60秒API相关文件
|
||||
- `/smallgame/*`: 小游戏相关文件
|
||||
- `/aimodelapp/*`: AI模型应用相关文件
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 数据安全
|
||||
- 密码哈希存储
|
||||
- JWT Token安全传输
|
||||
- 输入数据验证和过滤
|
||||
|
||||
### API安全
|
||||
- CORS配置(生产环境限制域名)
|
||||
- API限流保护
|
||||
- 请求日志记录
|
||||
|
||||
### 部署安全
|
||||
- 环境变量管理敏感信息
|
||||
- HTTPS证书配置
|
||||
- 防火墙和访问控制
|
||||
|
||||
## 前后端协作指南
|
||||
|
||||
### 前端调用示例
|
||||
|
||||
**用户登录**:
|
||||
```javascript
|
||||
// 1. 发送验证码
|
||||
fetch('/api/auth/send-verification', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'user@qq.com', type: 'login' })
|
||||
});
|
||||
|
||||
// 2. 验证验证码并登录
|
||||
fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: 'user@qq.com',
|
||||
code: '123456'
|
||||
})
|
||||
});
|
||||
|
||||
// 3. 保存token到localStorage
|
||||
localStorage.setItem('token', response.token);
|
||||
```
|
||||
|
||||
**调用需要认证的API**:
|
||||
```javascript
|
||||
fetch('/api/user/profile', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 数据约定
|
||||
|
||||
**前端发送数据格式**:
|
||||
- 所有请求使用JSON格式
|
||||
- 必填字段验证
|
||||
- 参数命名使用snake_case
|
||||
|
||||
**后端返回数据格式**:
|
||||
- 统一响应格式
|
||||
- 时间戳使用ISO格式
|
||||
- 错误信息清晰明确
|
||||
|
||||
### 开发协作流程
|
||||
|
||||
1. **API设计阶段**:
|
||||
- 后端定义API接口规范
|
||||
- 前端根据规范开发调用代码
|
||||
- 约定数据格式和错误处理
|
||||
|
||||
2. **联调阶段**:
|
||||
- 使用统一的测试数据
|
||||
- 验证各种边界情况
|
||||
- 确认错误处理逻辑
|
||||
|
||||
3. **部署阶段**:
|
||||
- 后端部署API服务
|
||||
- 前端配置API基础URL
|
||||
- 验证跨域和认证配置
|
||||
|
||||
## 新功能添加
|
||||
|
||||
### 1. AI功能萌芽币消费系统
|
||||
|
||||
**功能描述**:
|
||||
- 用户每次调用AI模型应用(aimodelapp)需消耗100萌芽币
|
||||
- 当用户萌芽币余额不足时,无法使用AI功能
|
||||
- 记录用户的AI使用历史
|
||||
|
||||
**API端点**:
|
||||
```
|
||||
GET /api/aimodelapp/coins # 查询用户萌芽币余额和使用历史
|
||||
```
|
||||
|
||||
**技术实现**:
|
||||
- 使用装饰器模式实现请求前验证和扣除萌芽币
|
||||
- 在MongoDB中记录用户AI使用历史
|
||||
- 通过JWT Token验证用户身份
|
||||
|
||||
**业务逻辑**:
|
||||
1. 当用户请求AI功能时,首先验证JWT Token
|
||||
2. 检查用户萌芽币余额是否≥100
|
||||
3. 如余额充足,先扣除萌芽币,然后再调用AI服务
|
||||
4. 记录使用历史,包括API类型、时间和消费萌芽币数量
|
||||
5. 返回AI服务结果给用户
|
||||
|
||||
**响应示例(查询萌芽币余额)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"coins": 200,
|
||||
"ai_cost": 100,
|
||||
"can_use_ai": true,
|
||||
"username": "用户名",
|
||||
"usage_count": 1,
|
||||
"recent_usage": [
|
||||
{
|
||||
"api_type": "chat",
|
||||
"cost": 100,
|
||||
"timestamp": "2025-09-16T11:15:47.285720"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "当前萌芽币余额: 200"
|
||||
}
|
||||
```
|
||||
|
||||
**前端开发注意事项**:
|
||||
- 每个需要调用AI功能的页面应首先检查用户萌芽币余额
|
||||
- 当萌芽币不足时,向用户提示并引导用户通过签到等方式获取萌芽币
|
||||
- 可在UI中展示用户最近的AI使用记录和萌芽币消费情况
|
||||
|
||||
---
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"账号":"3205788256",
|
||||
"邮箱":"3205788256@qq.com",
|
||||
"密码":"0123456789",
|
||||
"等级":0,
|
||||
"经验":0,
|
||||
"萌芽币":0,
|
||||
"签到系统":{
|
||||
"连续签到天数":0,
|
||||
"今日是否已签到":false,
|
||||
"签到时间":"2025-01-01"
|
||||
}
|
||||
{
|
||||
"账号":"3205788256",
|
||||
"邮箱":"3205788256@qq.com",
|
||||
"密码":"0123456789",
|
||||
"等级":0,
|
||||
"经验":0,
|
||||
"萌芽币":0,
|
||||
"签到系统":{
|
||||
"连续签到天数":0,
|
||||
"今日是否已签到":false,
|
||||
"签到时间":"2025-01-01"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user