feat: 添加邮件功能第一阶段 - 邮件基础设施
新增功能: - 创建 email_service.py 邮件服务模块 - 支持多SMTP配置(主备切换、故障转移) - 发送纯文本/HTML邮件 - 发送带附件邮件(支持ZIP压缩) - 异步发送队列(多线程工作池) - 每日发送限额控制 - 发送日志记录和统计 - 数据库表结构 - smtp_configs: 多SMTP配置表 - email_settings: 全局邮件设置 - email_tokens: 邮件验证Token - email_logs: 邮件发送日志 - email_stats: 邮件发送统计 - API接口 - GET/POST /yuyx/api/email/settings: 全局邮件设置 - CRUD /yuyx/api/smtp/configs: SMTP配置管理 - POST /yuyx/api/smtp/configs/<id>/test: 测试SMTP连接 - POST /yuyx/api/smtp/configs/<id>/primary: 设为主配置 - GET /yuyx/api/email/stats: 邮件统计 - GET /yuyx/api/email/logs: 邮件日志 - POST /yuyx/api/email/logs/cleanup: 清理日志 - 后台管理页面 - 新增"邮件配置"Tab - 全局邮件开关、故障转移开关 - SMTP配置列表管理 - 添加/编辑SMTP配置弹窗 - 邮件发送统计展示 - 邮件日志查询和清理 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
208
app.py
208
app.py
@@ -3463,7 +3463,7 @@ def checkpoint_resume(task_id):
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@app.route('/yuyx/api/checkpoint/<task_id>/abandon', methods=['POST'])
|
||||
@admin_required
|
||||
@admin_required
|
||||
def checkpoint_abandon(task_id):
|
||||
try:
|
||||
if checkpoint_mgr.abandon_task(task_id):
|
||||
@@ -3472,6 +3472,198 @@ def checkpoint_abandon(task_id):
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
# ==================== 邮件服务API ====================
|
||||
import email_service
|
||||
|
||||
@app.route('/yuyx/api/email/settings', methods=['GET'])
|
||||
@admin_required
|
||||
def get_email_settings_api():
|
||||
"""获取全局邮件设置"""
|
||||
try:
|
||||
settings = email_service.get_email_settings()
|
||||
return jsonify(settings)
|
||||
except Exception as e:
|
||||
logger.error(f"获取邮件设置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/email/settings', methods=['POST'])
|
||||
@admin_required
|
||||
def update_email_settings_api():
|
||||
"""更新全局邮件设置"""
|
||||
try:
|
||||
data = request.json
|
||||
enabled = data.get('enabled', False)
|
||||
failover_enabled = data.get('failover_enabled', True)
|
||||
|
||||
email_service.update_email_settings(enabled, failover_enabled)
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
logger.error(f"更新邮件设置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs', methods=['GET'])
|
||||
@admin_required
|
||||
def get_smtp_configs_api():
|
||||
"""获取所有SMTP配置列表"""
|
||||
try:
|
||||
configs = email_service.get_smtp_configs(include_password=False)
|
||||
return jsonify(configs)
|
||||
except Exception as e:
|
||||
logger.error(f"获取SMTP配置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs', methods=['POST'])
|
||||
@admin_required
|
||||
def create_smtp_config_api():
|
||||
"""创建SMTP配置"""
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
# 验证必填字段
|
||||
if not data.get('host'):
|
||||
return jsonify({'error': 'SMTP服务器地址不能为空'}), 400
|
||||
if not data.get('username'):
|
||||
return jsonify({'error': 'SMTP用户名不能为空'}), 400
|
||||
|
||||
config_id = email_service.create_smtp_config(data)
|
||||
return jsonify({'success': True, 'id': config_id})
|
||||
except Exception as e:
|
||||
logger.error(f"创建SMTP配置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs/<int:config_id>', methods=['GET'])
|
||||
@admin_required
|
||||
def get_smtp_config_api(config_id):
|
||||
"""获取单个SMTP配置详情"""
|
||||
try:
|
||||
config = email_service.get_smtp_config(config_id, include_password=False)
|
||||
if not config:
|
||||
return jsonify({'error': '配置不存在'}), 404
|
||||
return jsonify(config)
|
||||
except Exception as e:
|
||||
logger.error(f"获取SMTP配置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs/<int:config_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_smtp_config_api(config_id):
|
||||
"""更新SMTP配置"""
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
if email_service.update_smtp_config(config_id, data):
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'error': '更新失败'}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"更新SMTP配置<EFBFBD><EFBFBD>败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs/<int:config_id>', methods=['DELETE'])
|
||||
@admin_required
|
||||
def delete_smtp_config_api(config_id):
|
||||
"""删除SMTP配置"""
|
||||
try:
|
||||
if email_service.delete_smtp_config(config_id):
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'error': '删除失败'}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"删除SMTP配置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs/<int:config_id>/test', methods=['POST'])
|
||||
@admin_required
|
||||
def test_smtp_config_api(config_id):
|
||||
"""测试SMTP配置"""
|
||||
try:
|
||||
data = request.json
|
||||
test_email = data.get('email', '')
|
||||
|
||||
if not test_email:
|
||||
return jsonify({'error': '请提供测试邮箱'}), 400
|
||||
|
||||
# 简单验证邮箱格式
|
||||
import re
|
||||
if not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', test_email):
|
||||
return jsonify({'error': '邮箱格式不正确'}), 400
|
||||
|
||||
result = email_service.test_smtp_config(config_id, test_email)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"测试SMTP配置失败: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/smtp/configs/<int:config_id>/primary', methods=['POST'])
|
||||
@admin_required
|
||||
def set_primary_smtp_config_api(config_id):
|
||||
"""设置主SMTP配置"""
|
||||
try:
|
||||
if email_service.set_primary_smtp_config(config_id):
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'error': '设置失败'}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"设置主SMTP配置失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/email/stats', methods=['GET'])
|
||||
@admin_required
|
||||
def get_email_stats_api():
|
||||
"""获取邮件发送统计"""
|
||||
try:
|
||||
stats = email_service.get_email_stats()
|
||||
return jsonify(stats)
|
||||
except Exception as e:
|
||||
logger.error(f"获取邮件统计失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/email/logs', methods=['GET'])
|
||||
@admin_required
|
||||
def get_email_logs_api():
|
||||
"""获取邮件发送日志"""
|
||||
try:
|
||||
page = request.args.get('page', 1, type=int)
|
||||
page_size = request.args.get('page_size', 20, type=int)
|
||||
email_type = request.args.get('type', None)
|
||||
status = request.args.get('status', None)
|
||||
|
||||
# 限制page_size范围
|
||||
page_size = min(max(page_size, 10), 100)
|
||||
|
||||
result = email_service.get_email_logs(page, page_size, email_type, status)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"获取邮件日志失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/yuyx/api/email/logs/cleanup', methods=['POST'])
|
||||
@admin_required
|
||||
def cleanup_email_logs_api():
|
||||
"""清理过期邮件日志"""
|
||||
try:
|
||||
data = request.json or {}
|
||||
days = data.get('days', 30)
|
||||
|
||||
# 限制days范围
|
||||
days = min(max(days, 7), 365)
|
||||
|
||||
deleted = email_service.cleanup_email_logs(days)
|
||||
return jsonify({'success': True, 'deleted': deleted})
|
||||
except Exception as e:
|
||||
logger.error(f"清理邮件日志失败: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
# 初始化浏览器池(在后台线程中预热,不阻塞启动)
|
||||
# ==================== 用户定时任务API ====================
|
||||
|
||||
@@ -3815,6 +4007,13 @@ def cleanup_on_exit():
|
||||
except:
|
||||
pass
|
||||
|
||||
# 3.5 关闭邮件队列
|
||||
print("- 关闭邮件队列...")
|
||||
try:
|
||||
email_service.shutdown_email_queue()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 4. 关闭数据库连接池
|
||||
print("- 关闭数据库连接池...")
|
||||
try:
|
||||
@@ -3850,6 +4049,13 @@ if __name__ == '__main__':
|
||||
checkpoint_mgr = get_checkpoint_manager()
|
||||
print("✓ 任务断点管理器已初始化")
|
||||
|
||||
# 初始化邮件服务
|
||||
try:
|
||||
email_service.init_email_service()
|
||||
print("✓ 邮件服务已初始化")
|
||||
except Exception as e:
|
||||
print(f"警告: 邮件服务初始化失败: {e}")
|
||||
|
||||
# 启动内存清理调度器
|
||||
start_cleanup_scheduler()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user