feat: 添加邮件功能第二阶段 - 注册邮箱验证

实现注册时的邮箱验证功能:
- 修改注册API支持邮箱验证流程
- 新增邮箱验证API (/api/verify-email/<token>)
- 新增重发验证邮件API (/api/resend-verify-email)
- 新增邮箱验证状态查询API (/api/email/verify-status)

新增文件:
- templates/email/register.html - 注册验证邮件模板
- templates/verify_success.html - 验证成功页面
- templates/verify_failed.html - 验证失败页面

修改文件:
- email_service.py - 添加发送注册验证邮件函数
- app.py - 添加邮箱验证相关API
- database.py - 添加get_user_by_email函数
- app_config.py - 添加BASE_URL配置
- templates/register.html - 支持邮箱必填切换
- templates/login.html - 添加重发验证邮件功能
- templates/admin.html - 添加注册验证开关和BASE_URL设置

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-11 21:51:07 +08:00
parent 2f762db337
commit 648cf0adf0
10 changed files with 794 additions and 20 deletions

140
app.py
View File

@@ -693,22 +693,55 @@ def register():
return jsonify({"error": "验证码错误次数过多,IP已被锁定1小时"}), 429
return jsonify({"error": message}), 400
# 检查邮箱验证是否启用
email_settings = email_service.get_email_settings()
email_verify_enabled = email_settings.get('register_verify_enabled', False) and email_settings.get('enabled', False)
# 如果启用了邮箱验证,邮箱必填
if email_verify_enabled and not email:
return jsonify({"error": "启用邮箱验证后,邮箱为必填项"}), 400
# 简单邮箱格式验证
if email and '@' not in email:
return jsonify({"error": "邮箱格式不正确"}), 400
# 获取自动审核配置
system_config = database.get_system_config()
auto_approve_enabled = system_config.get('auto_approve_enabled', 0) == 1
auto_approve_hourly_limit = system_config.get('auto_approve_hourly_limit', 10)
auto_approve_vip_days = system_config.get('auto_approve_vip_days', 7)
# 检查每小时注册限制
if auto_approve_enabled:
# 检查每小时注册限制(同时适用于自动审核和邮箱验证)
if auto_approve_enabled or email_verify_enabled:
hourly_count = database.get_hourly_registration_count()
if hourly_count >= auto_approve_hourly_limit:
return jsonify({"error": f"注册人数过多,请稍后再试(每小时限制{auto_approve_hourly_limit}人)"}), 429
user_id = database.create_user(username, password, email)
if user_id:
# 自动审核处理
if auto_approve_enabled:
# 优先级:邮箱验证 > 自动审核 > 手动审核
if email_verify_enabled and email:
# 发送验证邮件
result = email_service.send_register_verification_email(
email=email,
username=username,
user_id=user_id
)
if result['success']:
return jsonify({
"success": True,
"message": "注册成功!验证邮件已发送,请查收邮箱并点击链接完成验证",
"need_verify": True
})
else:
# 邮件发送失败,但用户已创建,返回提示
logger.error(f"注册验证邮件发送失败: {result['error']}")
return jsonify({
"success": True,
"message": f"注册成功,但验证邮件发送失败({result['error']})。请稍后在登录页面重新发送验证邮件",
"need_verify": True
})
elif auto_approve_enabled:
# 自动审核通过
database.approve_user(user_id)
# 赠送VIP天数
@@ -723,6 +756,96 @@ def register():
return jsonify({"error": "用户名已存在"}), 400
@app.route('/api/verify-email/<token>')
def verify_email(token):
"""验证邮箱 - 用户点击邮件中的链接"""
result = email_service.verify_email_token(token, email_service.EMAIL_TYPE_REGISTER)
if result:
user_id = result['user_id']
email = result['email']
# 验证成功,激活用户
database.approve_user(user_id)
# 获取自动审核配置检查是否赠送VIP
system_config = database.get_system_config()
auto_approve_vip_days = system_config.get('auto_approve_vip_days', 7)
if auto_approve_vip_days > 0:
database.set_user_vip(user_id, auto_approve_vip_days)
logger.info(f"用户邮箱验<EFBFBD><EFBFBD><EFBFBD>成功: user_id={user_id}, email={email}")
return render_template('verify_success.html')
else:
logger.warning(f"邮箱验证失败: token={token[:20]}...")
return render_template('verify_failed.html', error_message="验证链接无效或已过期,请重新注册或申请重发验证邮件")
@app.route('/api/resend-verify-email', methods=['POST'])
@require_ip_not_locked
def resend_verify_email():
"""重发验证邮件"""
data = request.json
email = data.get('email', '').strip()
captcha_session = data.get('captcha_session', '')
captcha_code = data.get('captcha', '').strip()
if not email:
return jsonify({"error": "请输入邮箱"}), 400
# 获取客户端IP用于IP限流检查
client_ip = get_client_ip()
# 检查IP限流
allowed, error_msg = check_ip_rate_limit(client_ip)
if not allowed:
return jsonify({"error": error_msg}), 429
# 验证验证码
success, message = verify_and_consume_captcha(captcha_session, captcha_code, captcha_storage, MAX_CAPTCHA_ATTEMPTS)
if not success:
is_locked = record_failed_captcha(client_ip)
if is_locked:
return jsonify({"error": "验证码错误次数过多,IP已被锁定1小时"}), 429
return jsonify({"error": message}), 400
# 查找待验证的用户
user = database.get_user_by_email(email)
if not user:
return jsonify({"error": "该邮箱未注册"}), 404
if user['status'] == 'approved':
return jsonify({"error": "该账号已验证通过,请直接登录"}), 400
# 发送验证邮件
result = email_service.resend_register_verification_email(
user_id=user['id'],
email=email,
username=user['username']
)
if result['success']:
return jsonify({"success": True, "message": "验证邮件已重新发送,请查收"})
else:
return jsonify({"error": result['error']}), 500
@app.route('/api/email/verify-status')
def get_email_verify_status():
"""获取邮箱验证功能状态公开API"""
try:
settings = email_service.get_email_settings()
return jsonify({
'email_enabled': settings.get('enabled', False),
'register_verify_enabled': settings.get('register_verify_enabled', False) and settings.get('enabled', False)
})
except Exception as e:
return jsonify({
'email_enabled': False,
'register_verify_enabled': False
})
# ==================== 验证码API ====================
import random
from task_checkpoint import get_checkpoint_manager, TaskStage
@@ -3496,8 +3619,15 @@ def update_email_settings_api():
data = request.json
enabled = data.get('enabled', False)
failover_enabled = data.get('failover_enabled', True)
register_verify_enabled = data.get('register_verify_enabled')
base_url = data.get('base_url')
email_service.update_email_settings(enabled, failover_enabled)
email_service.update_email_settings(
enabled=enabled,
failover_enabled=failover_enabled,
register_verify_enabled=register_verify_enabled,
base_url=base_url
)
return jsonify({'success': True})
except Exception as e:
logger.error(f"更新邮件设置失败: {e}")