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:
182
email_service.py
182
email_service.py
@@ -107,6 +107,8 @@ def init_email_tables():
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
enabled INTEGER DEFAULT 0,
|
||||
failover_enabled INTEGER DEFAULT 1,
|
||||
register_verify_enabled INTEGER DEFAULT 0,
|
||||
base_url TEXT DEFAULT '',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
@@ -188,26 +190,67 @@ def get_email_settings() -> Dict[str, Any]:
|
||||
"""获取全局邮件设置"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT enabled, failover_enabled, updated_at FROM email_settings WHERE id = 1")
|
||||
# 先检查表结构,添加新字段(兼容旧版本数据库)
|
||||
try:
|
||||
cursor.execute("SELECT register_verify_enabled FROM email_settings LIMIT 1")
|
||||
except:
|
||||
cursor.execute("ALTER TABLE email_settings ADD COLUMN register_verify_enabled INTEGER DEFAULT 0")
|
||||
conn.commit()
|
||||
try:
|
||||
cursor.execute("SELECT base_url FROM email_settings LIMIT 1")
|
||||
except:
|
||||
cursor.execute("ALTER TABLE email_settings ADD COLUMN base_url TEXT DEFAULT ''")
|
||||
conn.commit()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT enabled, failover_enabled, register_verify_enabled, base_url, updated_at
|
||||
FROM email_settings WHERE id = 1
|
||||
""")
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return {
|
||||
'enabled': bool(row[0]),
|
||||
'failover_enabled': bool(row[1]),
|
||||
'updated_at': row[2]
|
||||
'register_verify_enabled': bool(row[2]) if row[2] is not None else False,
|
||||
'base_url': row[3] or '',
|
||||
'updated_at': row[4]
|
||||
}
|
||||
return {'enabled': False, 'failover_enabled': True, 'updated_at': None}
|
||||
return {
|
||||
'enabled': False,
|
||||
'failover_enabled': True,
|
||||
'register_verify_enabled': False,
|
||||
'base_url': '',
|
||||
'updated_at': None
|
||||
}
|
||||
|
||||
|
||||
def update_email_settings(enabled: bool, failover_enabled: bool) -> bool:
|
||||
def update_email_settings(
|
||||
enabled: bool,
|
||||
failover_enabled: bool,
|
||||
register_verify_enabled: bool = None,
|
||||
base_url: str = None
|
||||
) -> bool:
|
||||
"""更新全局邮件设置"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
|
||||
# 构建动态更新语句
|
||||
updates = ['enabled = ?', 'failover_enabled = ?', 'updated_at = CURRENT_TIMESTAMP']
|
||||
params = [int(enabled), int(failover_enabled)]
|
||||
|
||||
if register_verify_enabled is not None:
|
||||
updates.append('register_verify_enabled = ?')
|
||||
params.append(int(register_verify_enabled))
|
||||
|
||||
if base_url is not None:
|
||||
updates.append('base_url = ?')
|
||||
params.append(base_url)
|
||||
|
||||
cursor.execute(f"""
|
||||
UPDATE email_settings
|
||||
SET enabled = ?, failover_enabled = ?, updated_at = CURRENT_TIMESTAMP
|
||||
SET {', '.join(updates)}
|
||||
WHERE id = 1
|
||||
""", (int(enabled), int(failover_enabled)))
|
||||
""", params)
|
||||
conn.commit()
|
||||
return True
|
||||
|
||||
@@ -1057,6 +1100,131 @@ def cleanup_expired_tokens() -> int:
|
||||
return deleted
|
||||
|
||||
|
||||
# ============ 注册验证邮件 ============
|
||||
|
||||
def send_register_verification_email(
|
||||
email: str,
|
||||
username: str,
|
||||
user_id: int,
|
||||
base_url: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
发送注册验证邮件
|
||||
|
||||
Args:
|
||||
email: 用户邮箱
|
||||
username: 用户名
|
||||
user_id: 用户ID
|
||||
base_url: 网站基础URL
|
||||
|
||||
Returns:
|
||||
{'success': bool, 'error': str, 'token': str}
|
||||
"""
|
||||
# 检查发送频率限制
|
||||
if not check_rate_limit(email, EMAIL_TYPE_REGISTER):
|
||||
return {
|
||||
'success': False,
|
||||
'error': '发送太频繁,请稍后再试',
|
||||
'token': None
|
||||
}
|
||||
|
||||
# 生成验证Token
|
||||
token = generate_email_token(email, EMAIL_TYPE_REGISTER, user_id)
|
||||
|
||||
# 获取base_url
|
||||
if not base_url:
|
||||
settings = get_email_settings()
|
||||
base_url = settings.get('base_url', '')
|
||||
|
||||
if not base_url:
|
||||
# 尝试从配置获取
|
||||
try:
|
||||
from app_config import Config
|
||||
base_url = Config.BASE_URL
|
||||
except:
|
||||
base_url = 'http://localhost:51233'
|
||||
|
||||
# 生成验证链接
|
||||
verify_url = f"{base_url.rstrip('/')}/api/verify-email/{token}"
|
||||
|
||||
# 读取邮件模板
|
||||
template_path = os.path.join(os.path.dirname(__file__), 'templates', 'email', 'register.html')
|
||||
try:
|
||||
with open(template_path, 'r', encoding='utf-8') as f:
|
||||
html_template = f.read()
|
||||
except FileNotFoundError:
|
||||
# 使用简单的HTML模板
|
||||
html_template = """
|
||||
<html>
|
||||
<body>
|
||||
<h1>邮箱验证</h1>
|
||||
<p>您好,{{ username }}!</p>
|
||||
<p>请点击下面的链接验证您的邮箱地址:</p>
|
||||
<p><a href="{{ verify_url }}">{{ verify_url }}</a></p>
|
||||
<p>此链接24小时内有效。</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# 替换模板变量
|
||||
html_body = html_template.replace('{{ username }}', username)
|
||||
html_body = html_body.replace('{{ verify_url }}', verify_url)
|
||||
|
||||
# 纯文本版本
|
||||
text_body = f"""
|
||||
您好,{username}!
|
||||
|
||||
感谢您注册知识管理平台。请点击下面的链接验证您的邮箱地址:
|
||||
|
||||
{verify_url}
|
||||
|
||||
此链接24小时内有效。
|
||||
|
||||
如果您没有注册过账号,请忽略此邮件。
|
||||
"""
|
||||
|
||||
# 发送邮件
|
||||
result = send_email(
|
||||
to_email=email,
|
||||
subject='【知识管理平台】邮箱验证',
|
||||
body=text_body,
|
||||
html_body=html_body,
|
||||
email_type=EMAIL_TYPE_REGISTER,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
if result['success']:
|
||||
return {'success': True, 'error': '', 'token': token}
|
||||
else:
|
||||
return {'success': False, 'error': result['error'], 'token': None}
|
||||
|
||||
|
||||
def resend_register_verification_email(user_id: int, email: str, username: str) -> Dict[str, Any]:
|
||||
"""
|
||||
重发注册验证邮件
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
email: 用户邮箱
|
||||
username: 用户名
|
||||
|
||||
Returns:
|
||||
{'success': bool, 'error': str}
|
||||
"""
|
||||
# 检查是否有未过期的token
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
# 先使旧token失效
|
||||
cursor.execute("""
|
||||
UPDATE email_tokens SET used = 1
|
||||
WHERE user_id = ? AND token_type = ? AND used = 0
|
||||
""", (user_id, EMAIL_TYPE_REGISTER))
|
||||
conn.commit()
|
||||
|
||||
# 发送新的验证邮件
|
||||
return send_register_verification_email(email, username, user_id)
|
||||
|
||||
|
||||
# ============ 异步发送队列 ============
|
||||
|
||||
class EmailQueue:
|
||||
|
||||
Reference in New Issue
Block a user