first commit

This commit is contained in:
huangzhenpc
2025-02-26 18:29:10 +08:00
parent 5d21c9468c
commit a8d1b41381
38 changed files with 2878 additions and 0 deletions

25
old/app/api/__init__.py Normal file
View File

@@ -0,0 +1,25 @@
# API模块初始化文件
from flask import Blueprint
import logging
# 创建API蓝图
api_bp = Blueprint('api', __name__, url_prefix='/api')
# 注册默认路由
@api_bp.route('/')
def index():
return {
'name': 'Email System API',
'version': '1.0.0',
'status': 'running'
}
# 导入并合并所有API路由
# 为避免可能的文件读取问题改为从routes.py模块中导入所有路由定义
try:
from .routes import *
# 导入解码邮件路由模块
from .decoded_email_routes import *
except Exception as e:
logging.error(f"导入API路由时出错: {str(e)}")
raise

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,708 @@
import base64
import re
import os
import email
from email import policy
from datetime import datetime, timedelta
import psutil
import time
from flask import jsonify, request, current_app
from sqlalchemy import or_, func, desc
from . import api_bp
from ..models import Email, Domain, Mailbox, get_session
from ..services import get_mail_store
# 调试接口 - 检查邮件接收状态
@api_bp.route('/debug_email', methods=['GET'])
def debug_email():
"""
调试接口:检查某个邮箱的邮件状态并提供详细信息
查询参数:
- email: 邮箱地址 (例如: newsadd1test@nosqli.com)
"""
try:
# 获取查询参数
email_address = request.args.get('email')
# 验证邮箱地址是否有效
if not email_address or '@' not in email_address:
return jsonify({
'success': False,
'error': '无效的邮箱地址',
'message': '请提供有效的邮箱地址格式为user@domain.com'
}), 400
# 解析邮箱地址
username, domain_name = email_address.split('@', 1)
result = {
'email_address': email_address,
'username': username,
'domain': domain_name,
'system_info': {},
'logs': [],
'files': []
}
# 查询数据库
db = get_session()
try:
# 查找域名
domain = db.query(Domain).filter_by(name=domain_name).first()
if domain:
result['system_info']['domain'] = {
'id': domain.id,
'name': domain.name,
'active': domain.active,
'created_at': str(domain.created_at) if hasattr(domain, 'created_at') else None
}
# 查找邮箱
mailbox = db.query(Mailbox).filter_by(
domain_id=domain.id,
address=username
).first()
if mailbox:
result['system_info']['mailbox'] = {
'id': mailbox.id,
'address': mailbox.address,
'full_address': f"{mailbox.address}@{domain_name}",
'created_at': str(mailbox.created_at) if hasattr(mailbox, 'created_at') else None
}
# 获取邮件
emails = db.query(Email).filter_by(mailbox_id=mailbox.id).all()
result['system_info']['emails_count'] = len(emails)
result['system_info']['emails'] = []
for email_obj in emails:
email_info = {
'id': email_obj.id,
'subject': email_obj.subject,
'sender': email_obj.sender,
'received_at': str(email_obj.received_at),
'verification_code': email_obj.verification_code if hasattr(email_obj, 'verification_code') else None
}
result['system_info']['emails'].append(email_info)
else:
result['system_info']['mailbox'] = "未找到邮箱记录"
else:
result['system_info']['domain'] = "未找到域名记录"
# 查找文件
email_data_dir = current_app.config.get('MAIL_STORAGE_PATH', 'email_data')
emails_dir = os.path.join(email_data_dir, 'emails')
if os.path.exists(emails_dir):
for file_name in os.listdir(emails_dir):
if file_name.endswith('.eml'):
file_path = os.path.join(emails_dir, file_name)
file_info = {
'name': file_name,
'path': file_path,
'size': os.path.getsize(file_path),
'modified': datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
}
# 尝试读取文件内容并检查是否包含收件人地址
try:
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
content = f.read(10000) # 只读取前10000个字符用于检查
if email_address.lower() in content.lower():
file_info['contains_address'] = True
result['files'].append(file_info)
except Exception as e:
file_info['error'] = str(e)
result['files'].append(file_info)
# 检查日志
log_file = current_app.config.get('LOG_FILE', os.path.join('logs', 'email_system.log'))
if os.path.exists(log_file):
try:
with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
# 从日志尾部读取最后200行
lines = f.readlines()[-200:]
for line in lines:
if email_address.lower() in line.lower():
result['logs'].append(line.strip())
except Exception as e:
result['logs'] = [f"读取日志出错: {str(e)}"]
return jsonify({
'success': True,
'debug_info': result
}), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"调试邮件出错: {str(e)}")
return jsonify({
'success': False,
'error': '服务器错误',
'message': str(e)
}), 500
# 创建邮箱接口
@api_bp.route('/add_mailbox', methods=['POST', 'GET'])
def add_mailbox():
"""
创建新邮箱,如果域名不存在则自动创建
查询参数GET方式或表单参数POST方式:
- email: 邮箱地址 (例如: testaa@nosqli.com)
- description: 邮箱描述 (可选)
"""
try:
# 获取参数
if request.method == 'POST':
data = request.json or {}
email_address = data.get('email')
description = data.get('description', '')
else: # GET方式
email_address = request.args.get('email')
description = request.args.get('description', '')
# 验证邮箱地址
if not email_address or '@' not in email_address:
return jsonify({
'success': False,
'error': '无效的邮箱地址',
'message': '请提供有效的邮箱地址格式为user@domain.com'
}), 400
# 解析邮箱地址
username, domain_name = email_address.split('@', 1)
# 创建或查找域名和邮箱
db = get_session()
try:
# 查找域名
domain = db.query(Domain).filter_by(name=domain_name).first()
# 如果域名不存在,创建域名
if not domain:
current_app.logger.info(f"域名 {domain_name} 不存在,开始创建")
domain = Domain(
name=domain_name,
description=f"自动创建的域名 {domain_name}",
active=True
)
db.add(domain)
db.commit()
current_app.logger.info(f"域名 {domain_name} 创建成功ID: {domain.id}")
# 查询邮箱是否已存在
mailbox = db.query(Mailbox).filter_by(
domain_id=domain.id,
address=username
).first()
# 如果邮箱已存在,返回已存在信息
if mailbox:
return jsonify({
'success': True,
'message': f'邮箱 {email_address} 已存在',
'mailbox': {
'id': mailbox.id,
'address': mailbox.address,
'domain_id': mailbox.domain_id,
'full_address': f"{mailbox.address}@{domain_name}",
'description': mailbox.description
}
}), 200
# 创建邮箱
mailbox = Mailbox(
domain_id=domain.id,
address=username,
description=description or f"自动创建的邮箱 {email_address}"
)
db.add(mailbox)
db.commit()
# 返回成功信息
return jsonify({
'success': True,
'message': f'邮箱 {email_address} 创建成功',
'mailbox': {
'id': mailbox.id,
'address': mailbox.address,
'domain_id': mailbox.domain_id,
'full_address': f"{mailbox.address}@{domain_name}",
'description': mailbox.description
}
}), 201
finally:
db.close()
except Exception as e:
current_app.logger.error(f"创建邮箱出错: {str(e)}")
return jsonify({
'success': False,
'error': '服务器错误',
'message': str(e)
}), 500
# 简化的URL路径直接通过邮箱地址获取邮件
@api_bp.route('/email', methods=['GET'])
def get_email_by_address():
"""
通过邮箱地址获取邮件的简化URL
等同于 /decoded_emails?email={email_address}&latest=0
查询参数:
- email: 邮箱地址 (必填)
- latest: 是否只返回最新的邮件 (1表示是0表示否默认0)
"""
# 重用已有的解码邮件接口
return get_decoded_emails()
@api_bp.route('/decoded_emails', methods=['GET'])
def get_decoded_emails():
"""
获取指定邮箱地址的所有邮件,并返回解码后的内容
查询参数:
- email: 邮箱地址 (例如: testaa@nosqli.com)
- latest: 是否只返回最新的邮件 (1表示是0表示否默认0)
- limit: 返回邮件数量 (默认10)
- offset: 查询起始位置 (默认0)
"""
try:
# 获取查询参数
email_address = request.args.get('email')
latest = request.args.get('latest', '0') == '1'
limit = int(request.args.get('limit', 10))
offset = int(request.args.get('offset', 0))
# 验证邮箱地址是否有效
if not email_address or '@' not in email_address:
return jsonify({
'success': False,
'error': '无效的邮箱地址',
'message': '请提供有效的邮箱地址格式为user@domain.com'
}), 400
# 解析邮箱地址
username, domain_name = email_address.split('@', 1)
# 查询数据库
db = get_session()
try:
# 查找域名
domain = db.query(Domain).filter_by(name=domain_name).first()
if not domain:
return jsonify({
'success': False,
'error': '域名不存在',
'message': f'域名 {domain_name} 不存在'
}), 404
# 查找邮箱
mailbox = db.query(Mailbox).filter_by(
domain_id=domain.id,
address=username
).first()
if not mailbox:
return jsonify({
'success': False,
'error': '邮箱不存在',
'message': f'邮箱 {email_address} 不存在'
}), 404
# 获取邮件
query = db.query(Email).filter_by(mailbox_id=mailbox.id)
# 按接收时间排序,最新的在前
query = query.order_by(Email.received_at.desc())
# 如果只要最新的一封
if latest:
emails = query.limit(1).all()
else:
emails = query.limit(limit).offset(offset).all()
# 处理结果
result_emails = []
for email_obj in emails:
# 获取原始邮件文件路径
email_file_path = os.path.join(
current_app.config.get('MAIL_STORAGE_PATH', 'email_data'),
'emails',
f'email_{email_obj.id}.eml'
)
# 解码邮件内容
decoded_email = decode_email(email_obj, email_file_path)
result_emails.append(decoded_email)
# 返回结果
return jsonify({
'success': True,
'email_address': email_address,
'total_emails': query.count(),
'emails': result_emails
}), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"获取解码邮件出错: {str(e)}")
return jsonify({
'success': False,
'error': '服务器错误',
'message': str(e)
}), 500
@api_bp.route('/decoded_email/<int:email_id>', methods=['GET'])
def get_decoded_email_by_id(email_id):
"""获取指定ID的解码邮件内容"""
try:
db = get_session()
try:
# 获取邮件对象
email_obj = db.query(Email).filter_by(id=email_id).first()
if not email_obj:
return jsonify({
'success': False,
'error': '邮件不存在',
'message': f'ID为{email_id}的邮件不存在'
}), 404
# 获取原始邮件文件路径
email_file_path = os.path.join(
current_app.config.get('MAIL_STORAGE_PATH', 'email_data'),
'emails',
f'email_{email_obj.id}.eml'
)
# 解码邮件内容
decoded_email = decode_email(email_obj, email_file_path)
# 返回结果
return jsonify({
'success': True,
'email': decoded_email
}), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"获取解码邮件出错: {str(e)}")
return jsonify({
'success': False,
'error': '服务器错误',
'message': str(e)
}), 500
def decode_email(email_obj, email_file_path):
"""解析并解码邮件内容"""
# 创建基本邮件信息
result = {
'id': email_obj.id,
'subject': email_obj.subject,
'sender': email_obj.sender,
'recipients': email_obj.recipients,
'received_at': email_obj.received_at.isoformat() if email_obj.received_at else None,
'read': email_obj.read,
'has_attachments': len(email_obj.attachments) > 0 if hasattr(email_obj, 'attachments') else False
}
# 从数据库中直接获取验证码
if hasattr(email_obj, 'verification_code') and email_obj.verification_code:
result['verification_code'] = email_obj.verification_code
if hasattr(email_obj, 'verification_link') and email_obj.verification_link:
result['verification_link'] = email_obj.verification_link
# 如果邮件对象有文本内容或HTML内容直接使用
if hasattr(email_obj, 'body_text') and email_obj.body_text:
result['body_text'] = email_obj.body_text
if hasattr(email_obj, 'body_html') and email_obj.body_html:
result['body_html'] = email_obj.body_html
# 如果有原始邮件文件,尝试解析
if os.path.exists(email_file_path):
try:
# 解析.eml文件
with open(email_file_path, 'r', encoding='utf-8', errors='replace') as f:
msg = email.message_from_file(f, policy=policy.default)
# 如果没有从数据库获取到内容,尝试从文件解析
if 'body_text' not in result or 'body_html' not in result:
body_text = ""
body_html = ""
# 处理多部分邮件
if msg.is_multipart():
for part in msg.iter_parts():
content_type = part.get_content_type()
if content_type == "text/plain":
try:
body_text = part.get_content()
except Exception:
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
try:
body_text = payload.decode(charset, errors='replace')
except:
body_text = payload.decode('utf-8', errors='replace')
elif content_type == "text/html":
try:
body_html = part.get_content()
except Exception:
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
try:
body_html = payload.decode(charset, errors='replace')
except:
body_html = payload.decode('utf-8', errors='replace')
else:
# 处理单部分邮件
content_type = msg.get_content_type()
try:
if content_type == "text/plain":
body_text = msg.get_content()
elif content_type == "text/html":
body_html = msg.get_content()
except Exception:
payload = msg.get_payload(decode=True)
if payload:
charset = msg.get_content_charset() or 'utf-8'
try:
decoded = payload.decode(charset, errors='replace')
if content_type == "text/plain":
body_text = decoded
elif content_type == "text/html":
body_html = decoded
except:
pass
# 如果找到了内容,添加到结果中
if body_text and 'body_text' not in result:
result['body_text'] = body_text
if body_html and 'body_html' not in result:
result['body_html'] = body_html
# 如果仍然没有提取到验证码,尝试从内容中提取
if 'verification_code' not in result:
verification_code = extract_verification_code(result.get('body_text', ''), result.get('body_html', ''))
if verification_code:
result['verification_code'] = verification_code
except Exception as e:
current_app.logger.error(f"解析邮件文件出错: {str(e)}")
return result
def extract_verification_code(body_text, body_html):
"""从邮件内容中提取验证码"""
# 首先尝试从HTML中提取
if body_html:
# 常用的验证码模式
patterns = [
r'letter-spacing:\s*\d+px[^>]*>([^<]+)<', # 特殊样式的验证码
r'<div[^>]*>(\d{6})</div>', # 6位数字验证码在div中
r'验证码[:]\s*([A-Z0-9]{4,8})', # 中文标记的验证码
r'code[^\d]+(\d{4,8})', # 英文标记的验证码
r'\b([A-Z0-9]{6})\b' # 6位大写字母或数字
]
for pattern in patterns:
matches = re.findall(pattern, body_html)
if matches:
return matches[0].strip()
# 如果HTML中没找到尝试从纯文本中提取
if body_text:
patterns = [
r'验证码[:]\s*([A-Z0-9]{4,8})', # 中文格式
r'code[^\d]+(\d{4,8})', # 英文格式
r'\b(\d{6})\b' # 6位数字
]
for pattern in patterns:
matches = re.findall(pattern, body_text)
if matches:
return matches[0].strip()
return None
# 系统诊断接口
@api_bp.route('/system_check', methods=['GET'])
def system_check():
"""
系统诊断接口:检查邮件系统各组件状态
"""
try:
result = {
'timestamp': datetime.now().isoformat(),
'system_status': 'normal',
'components': {},
'recent_activity': {},
'mailboxes': [],
'storage': {}
}
# 检查系统资源
try:
result['components']['system'] = {
'cpu_percent': psutil.cpu_percent(),
'memory_percent': psutil.virtual_memory().percent,
'disk_usage': psutil.disk_usage('/').percent
}
except Exception as e:
result['components']['system'] = {'error': str(e)}
# 检查数据库状态
db = get_session()
try:
# 获取域名数量
domain_count = db.query(Domain).count()
# 获取邮箱数量
mailbox_count = db.query(Mailbox).count()
# 获取邮件数量
email_count = db.query(Email).count()
# 获取最新邮件
latest_emails = db.query(Email).order_by(Email.received_at.desc()).limit(5).all()
result['components']['database'] = {
'status': 'connected',
'domain_count': domain_count,
'mailbox_count': mailbox_count,
'email_count': email_count
}
# 最近活动
result['recent_activity']['latest_emails'] = [
{
'id': email.id,
'subject': email.subject,
'sender': email.sender,
'received_at': email.received_at.isoformat() if email.received_at else None
} for email in latest_emails
]
# 获取所有活跃邮箱
active_mailboxes = db.query(Mailbox).order_by(Mailbox.id).limit(10).all()
result['mailboxes'] = [
{
'id': mb.id,
'address': mb.address,
'domain_id': mb.domain_id,
'full_address': f"{mb.address}@{mb.domain.name}" if hasattr(mb, 'domain') and mb.domain else f"{mb.address}@unknown",
'email_count': db.query(Email).filter_by(mailbox_id=mb.id).count()
} for mb in active_mailboxes
]
except Exception as e:
result['components']['database'] = {'status': 'error', 'error': str(e)}
finally:
db.close()
# 检查存储状态
email_data_dir = current_app.config.get('MAIL_STORAGE_PATH', 'email_data')
try:
emails_dir = os.path.join(email_data_dir, 'emails')
attachments_dir = os.path.join(email_data_dir, 'attachments')
# 检查目录是否存在
emails_dir_exists = os.path.exists(emails_dir)
attachments_dir_exists = os.path.exists(attachments_dir)
# 计算文件数量和大小
email_files_count = 0
email_files_size = 0
if emails_dir_exists:
for file_name in os.listdir(emails_dir):
if file_name.endswith('.eml'):
email_files_count += 1
email_files_size += os.path.getsize(os.path.join(emails_dir, file_name))
attachment_files_count = 0
attachment_files_size = 0
if attachments_dir_exists:
for file_name in os.listdir(attachments_dir):
attachment_files_count += 1
attachment_files_size += os.path.getsize(os.path.join(attachments_dir, file_name))
result['storage'] = {
'emails_dir': {
'exists': emails_dir_exists,
'path': emails_dir,
'file_count': email_files_count,
'size_bytes': email_files_size,
'size_mb': round(email_files_size / (1024 * 1024), 2) if email_files_size > 0 else 0
},
'attachments_dir': {
'exists': attachments_dir_exists,
'path': attachments_dir,
'file_count': attachment_files_count,
'size_bytes': attachment_files_size,
'size_mb': round(attachment_files_size / (1024 * 1024), 2) if attachment_files_size > 0 else 0
}
}
# 检查最近的邮件文件
if emails_dir_exists and email_files_count > 0:
files = [(os.path.getmtime(os.path.join(emails_dir, f)), f)
for f in os.listdir(emails_dir) if f.endswith('.eml')]
files.sort(reverse=True)
result['recent_activity']['latest_files'] = [
{
'filename': f,
'modified': datetime.fromtimestamp(t).isoformat(),
'age_seconds': int(time.time() - t)
} for t, f in files[:5]
]
except Exception as e:
result['storage'] = {'error': str(e)}
# 整体状态评估
if ('database' in result['components'] and result['components']['database'].get('status') != 'connected'):
result['system_status'] = 'warning'
if not emails_dir_exists or not attachments_dir_exists:
result['system_status'] = 'warning'
return jsonify({
'success': True,
'status': result['system_status'],
'diagnostics': result
}), 200
except Exception as e:
current_app.logger.error(f"系统诊断出错: {str(e)}")
return jsonify({
'success': False,
'error': '系统诊断失败',
'message': str(e)
}), 500

Binary file not shown.

376
old/app/api/email_routes.py Normal file
View File

@@ -0,0 +1,376 @@
from flask import request, jsonify, current_app, send_file
from io import BytesIO
import time
import os
import logging
from . import api_bp
from ..models import get_session, Email, Mailbox, Domain
from ..utils import email_parser
from ..config import config
# 获取邮箱的所有邮件
@api_bp.route('/mailboxes/<int:mailbox_id>/emails', methods=['GET'])
def get_mailbox_emails(mailbox_id):
"""获取指定邮箱的所有邮件"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 50))
unread_only = request.args.get('unread_only', 'false').lower() == 'true'
offset = (page - 1) * limit
db = get_session()
try:
# 检查邮箱是否存在
mailbox = db.query(Mailbox).filter_by(id=mailbox_id).first()
if not mailbox:
return jsonify({'error': '邮箱不存在'}), 404
# 查询邮件
query = db.query(Email).filter(Email.mailbox_id == mailbox_id)
if unread_only:
query = query.filter(Email.read == False)
# 获取总数
total = query.count()
# 分页获取邮件
emails = query.order_by(Email.received_at.desc()) \
.limit(limit) \
.offset(offset) \
.all()
# 返回结果
result = {
'success': True,
'total': total,
'page': page,
'limit': limit,
'emails': [email.to_dict() for email in emails]
}
return jsonify(result), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"获取邮件列表出错: {str(e)}")
return jsonify({'success': False, 'error': '获取邮件列表失败', 'details': str(e)}), 500
# 获取特定邮件详情
@api_bp.route('/emails/<int:email_id>', methods=['GET'])
def get_email(email_id):
"""
获取单个邮件的详细信息
"""
try:
email_id = int(email_id)
session = get_session()
email = session.query(Email).filter(Email.id == email_id).first()
if not email:
return jsonify({
'success': False,
'error': f'未找到ID为{email_id}的邮件'
}), 404
# 获取邮件正文内容
body_text = None
body_html = None
try:
# 尝试从文件中读取邮件内容
if email.id:
email_path = os.path.join(config.DATA_DIR, 'emails', f'email_{email.id}.eml')
if os.path.exists(email_path):
logging.info(f"从文件读取邮件内容: {email_path}")
with open(email_path, 'r', encoding='utf-8', errors='ignore') as f:
try:
raw_email = f.read()
msg = email_parser.parsestr(raw_email)
if msg.is_multipart():
# 处理多部分邮件
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
# 跳过附件
if "attachment" in content_disposition:
continue
# 处理文本内容
if content_type == "text/plain":
payload = part.get_payload(decode=True)
charset = part.get_content_charset() or 'utf-8'
try:
body_text = payload.decode(charset, errors='replace')
except Exception as e:
logging.error(f"解码纯文本内容失败: {e}")
body_text = payload.decode('utf-8', errors='replace')
# 处理HTML内容
elif content_type == "text/html":
payload = part.get_payload(decode=True)
charset = part.get_content_charset() or 'utf-8'
try:
body_html = payload.decode(charset, errors='replace')
except Exception as e:
logging.error(f"解码HTML内容失败: {e}")
body_html = payload.decode('utf-8', errors='replace')
else:
# 处理单部分邮件
content_type = msg.get_content_type()
payload = msg.get_payload(decode=True)
charset = msg.get_content_charset() or 'utf-8'
try:
decoded_content = payload.decode(charset, errors='replace')
except Exception as e:
logging.error(f"解码内容失败: {e}")
decoded_content = payload.decode('utf-8', errors='replace')
if content_type == "text/plain":
body_text = decoded_content
elif content_type == "text/html":
body_html = decoded_content
except Exception as e:
logging.error(f"解析邮件文件失败: {e}")
except Exception as e:
logging.error(f"读取邮件内容时出错: {e}")
# 如果文件读取失败,使用数据库中的内容
if body_text is None:
body_text = email.body_text
if body_html is None:
body_html = email.body_html
logging.info(f"邮件ID={email_id} 正文长度: text={len(body_text or '')}字节, html={len(body_html or '')}字节")
# 返回邮件信息,包括正文内容
return jsonify({
'success': True,
'email': {
'id': email.id,
'subject': email.subject,
'sender': email.sender,
'recipients': email.recipients,
'received_at': email.received_at.isoformat(),
'verification_code': email.verification_code,
'verification_link': email.verification_link,
'body_text': body_text,
'body_html': body_html
}
})
except Exception as e:
logging.error(f"获取邮件时出错: {str(e)}")
return jsonify({
'success': False,
'error': f'获取邮件时发生错误: {str(e)}'
}), 500
finally:
session.close()
# 删除邮件
@api_bp.route('/emails/<int:email_id>', methods=['DELETE'])
def delete_email(email_id):
"""删除特定邮件"""
try:
db = get_session()
try:
email = db.query(Email).filter_by(id=email_id).first()
if not email:
return jsonify({'error': '邮件不存在'}), 404
db.delete(email)
db.commit()
return jsonify({'message': '邮件已删除'}), 200
except Exception as e:
db.rollback()
raise
finally:
db.close()
except Exception as e:
current_app.logger.error(f"删除邮件出错: {str(e)}")
return jsonify({'error': '删除邮件失败', 'details': str(e)}), 500
# 下载附件
@api_bp.route('/attachments/<int:attachment_id>', methods=['GET'])
def download_attachment(attachment_id):
"""下载特定的附件"""
try:
from ..models import Attachment
db = get_session()
try:
attachment = db.query(Attachment).filter_by(id=attachment_id).first()
if not attachment:
return jsonify({'error': '附件不存在'}), 404
# 获取附件内容
content = attachment.get_content()
if not content:
return jsonify({'error': '附件内容不可用'}), 404
# 创建内存文件对象
file_obj = BytesIO(content)
# 返回文件下载响应
return send_file(
file_obj,
mimetype=attachment.content_type,
as_attachment=True,
download_name=attachment.filename
)
finally:
db.close()
except Exception as e:
current_app.logger.error(f"下载附件出错: {str(e)}")
return jsonify({'error': '下载附件失败', 'details': str(e)}), 500
# 获取最新邮件 (轮询API)
@api_bp.route('/mailboxes/<int:mailbox_id>/poll', methods=['GET'])
def poll_new_emails(mailbox_id):
"""轮询指定邮箱的新邮件"""
try:
# 获取上次检查时间
last_check = request.args.get('last_check')
if last_check:
try:
last_check_time = float(last_check)
except ValueError:
return jsonify({'error': '无效的last_check参数'}), 400
else:
last_check_time = time.time() - 300 # 默认检查最近5分钟
db = get_session()
try:
# 检查邮箱是否存在
mailbox = db.query(Mailbox).filter_by(id=mailbox_id).first()
if not mailbox:
return jsonify({'error': '邮箱不存在'}), 404
# 查询新邮件
new_emails = db.query(Email).filter(
Email.mailbox_id == mailbox_id,
Email.received_at >= time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(last_check_time))
).order_by(Email.received_at.desc()).all()
# 返回结果
result = {
'mailbox_id': mailbox_id,
'count': len(new_emails),
'emails': [email.to_dict() for email in new_emails],
'timestamp': time.time()
}
return jsonify(result), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"轮询新邮件出错: {str(e)}")
return jsonify({'error': '轮询新邮件失败', 'details': str(e)}), 500
# 通过邮箱地址获取最新邮件
@api_bp.route('/emails/by-address', methods=['GET'])
def get_emails_by_address():
"""
通过邮箱地址获取最新邮件
参数:
email_address: 完整邮箱地址 (例如: user@example.com)
limit: 返回的邮件数量 (默认: 10)
since: 从指定时间戳后获取邮件 (可选)
unread_only: 是否只返回未读邮件 (默认: false)
返回:
最新的邮件列表
"""
try:
email_address = request.args.get('email_address')
if not email_address or '@' not in email_address:
return jsonify({'success': False, 'error': '无效的邮箱地址'}), 400
limit = int(request.args.get('limit', 10))
unread_only = request.args.get('unread_only', 'false').lower() == 'true'
since = request.args.get('since')
# 解析邮箱地址
try:
username, domain_name = email_address.split('@', 1)
except ValueError:
return jsonify({
'success': False,
'error': '邮箱地址格式无效'
}), 400
db = get_session()
try:
# 查找域名
domain = db.query(Domain).filter_by(name=domain_name, active=True).first()
if not domain:
return jsonify({
'success': False,
'error': f'域名 {domain_name} 不存在或未激活'
}), 404
# 查找邮箱
mailbox = db.query(Mailbox).filter_by(address=username, domain_id=domain.id).first()
if not mailbox:
# 自动创建邮箱 - 批量注册场景
mailbox = Mailbox(
address=username,
domain_id=domain.id,
description=f"自动创建 ({email_address})",
active=True
)
db.add(mailbox)
db.flush() # 获取新创建邮箱的ID
logging.info(f"自动创建邮箱: {email_address}, ID={mailbox.id}")
# 查询邮件
query = db.query(Email).filter(Email.mailbox_id == mailbox.id)
if unread_only:
query = query.filter(Email.read == False)
if since:
try:
since_time = float(since)
query = query.filter(
Email.received_at >= time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(since_time))
)
except ValueError:
logging.warning(f"无效的since参数: {since}")
# 获取最新的邮件
emails = query.order_by(Email.received_at.desc()).limit(limit).all()
# 获取总数
total = query.count()
# 提交数据库变更
db.commit()
# 返回结果
return jsonify({
'success': True,
'email_address': email_address,
'mailbox_id': mailbox.id,
'total': total,
'count': len(emails),
'emails': [email.to_dict() for email in emails],
'timestamp': time.time()
}), 200
except Exception as e:
db.rollback()
raise
finally:
db.close()
except Exception as e:
current_app.logger.error(f"获取邮件时出错: {str(e)}")
return jsonify({'success': False, 'error': '获取邮件失败'}), 500

View File

@@ -0,0 +1,206 @@
from flask import request, jsonify, current_app
from sqlalchemy.exc import IntegrityError
import random
import string
from . import api_bp
from ..models import get_session, Domain, Mailbox
# 获取所有邮箱
@api_bp.route('/mailboxes', methods=['GET'])
def get_mailboxes():
"""获取所有邮箱列表"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 50))
offset = (page - 1) * limit
db = get_session()
try:
# 查询总数
total = db.query(Mailbox).count()
# 获取分页数据
mailboxes = db.query(Mailbox).order_by(Mailbox.created_at.desc()) \
.limit(limit) \
.offset(offset) \
.all()
# 转换为字典列表
result = {
'total': total,
'page': page,
'limit': limit,
'mailboxes': [mailbox.to_dict() for mailbox in mailboxes]
}
return jsonify(result), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"获取邮箱列表出错: {str(e)}")
return jsonify({'error': '获取邮箱列表失败', 'details': str(e)}), 500
# 创建邮箱
@api_bp.route('/mailboxes', methods=['POST'])
def create_mailbox():
"""创建新邮箱"""
try:
data = request.json
# 验证必要参数
if not data or 'domain_id' not in data:
return jsonify({'error': '缺少必要参数'}), 400
db = get_session()
try:
# 查询域名是否存在
domain = db.query(Domain).filter_by(id=data['domain_id'], active=True).first()
if not domain:
return jsonify({'error': '指定的域名不存在或未激活'}), 404
# 生成或使用给定地址
if 'address' not in data or not data['address']:
# 生成随机地址
address = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
else:
address = data['address']
# 创建邮箱
mailbox = Mailbox(
address=address,
domain_id=domain.id,
description=data.get('description', ''),
active=True
)
db.add(mailbox)
db.commit()
return jsonify({
'message': '邮箱创建成功',
'mailbox': mailbox.to_dict()
}), 201
except IntegrityError:
db.rollback()
return jsonify({'error': '邮箱地址已存在'}), 409
except Exception as e:
db.rollback()
raise
finally:
db.close()
except Exception as e:
current_app.logger.error(f"创建邮箱出错: {str(e)}")
return jsonify({'error': '创建邮箱失败', 'details': str(e)}), 500
# 批量创建邮箱
@api_bp.route('/mailboxes/batch', methods=['POST'])
def batch_create_mailboxes():
"""批量创建邮箱"""
try:
data = request.json
# 验证必要参数
if not data or 'domain_id' not in data or 'count' not in data:
return jsonify({'error': '缺少必要参数'}), 400
domain_id = data['domain_id']
count = min(int(data['count']), 100) # 限制最大数量为100
prefix = data.get('prefix', '')
db = get_session()
try:
# 查询域名是否存在
domain = db.query(Domain).filter_by(id=domain_id, active=True).first()
if not domain:
return jsonify({'error': '指定的域名不存在或未激活'}), 404
created_mailboxes = []
# 批量创建
for _ in range(count):
# 生成随机地址
if prefix:
address = f"{prefix}{random.randint(1000, 9999)}"
else:
address = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
# 尝试创建,如果地址已存在则重试
retries = 0
while retries < 3: # 最多尝试3次
try:
mailbox = Mailbox(
address=address,
domain_id=domain.id,
active=True
)
db.add(mailbox)
db.flush() # 验证但不提交
created_mailboxes.append(mailbox)
break
except IntegrityError:
db.rollback()
# 地址已存在,重新生成
address = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
retries += 1
# 提交所有更改
db.commit()
return jsonify({
'message': f'成功创建 {len(created_mailboxes)} 个邮箱',
'mailboxes': [mailbox.to_dict() for mailbox in created_mailboxes]
}), 201
except Exception as e:
db.rollback()
raise
finally:
db.close()
except Exception as e:
current_app.logger.error(f"批量创建邮箱出错: {str(e)}")
return jsonify({'error': '批量创建邮箱失败', 'details': str(e)}), 500
# 获取特定邮箱
@api_bp.route('/mailboxes/<int:mailbox_id>', methods=['GET'])
def get_mailbox(mailbox_id):
"""获取指定ID的邮箱信息"""
try:
db = get_session()
try:
mailbox = db.query(Mailbox).filter_by(id=mailbox_id).first()
if not mailbox:
return jsonify({'error': '邮箱不存在'}), 404
return jsonify(mailbox.to_dict()), 200
finally:
db.close()
except Exception as e:
current_app.logger.error(f"获取邮箱详情出错: {str(e)}")
return jsonify({'error': '获取邮箱详情失败', 'details': str(e)}), 500
# 删除邮箱
@api_bp.route('/mailboxes/<int:mailbox_id>', methods=['DELETE'])
def delete_mailbox(mailbox_id):
"""删除指定ID的邮箱"""
try:
db = get_session()
try:
mailbox = db.query(Mailbox).filter_by(id=mailbox_id).first()
if not mailbox:
return jsonify({'error': '邮箱不存在'}), 404
db.delete(mailbox)
db.commit()
return jsonify({'message': '邮箱已删除'}), 200
except Exception as e:
db.rollback()
raise
finally:
db.close()
except Exception as e:
current_app.logger.error(f"删除邮箱出错: {str(e)}")
return jsonify({'error': '删除邮箱失败', 'details': str(e)}), 500

341
old/app/api/routes.py Normal file
View File

@@ -0,0 +1,341 @@
from flask import Blueprint, request, jsonify, current_app
import json
from datetime import datetime, timedelta
import os
import time
import psutil
import sys
import platform
from sqlalchemy import func
from ..models import get_session, Domain, Mailbox, Email
from ..services import get_smtp_server, get_email_processor
api_bp = Blueprint('api', __name__, url_prefix='/api')
@api_bp.route('/domains', methods=['GET'])
def get_domains():
"""获取所有可用域名"""
db = get_session()
try:
domains = db.query(Domain).filter_by(active=True).all()
return jsonify({
'success': True,
'domains': [domain.to_dict() for domain in domains]
})
except Exception as e:
current_app.logger.exception(f"获取域名失败: {str(e)}")
return jsonify({'success': False, 'error': '获取域名失败'}), 500
finally:
db.close()
@api_bp.route('/domains', methods=['POST'])
def create_domain():
"""创建新域名"""
data = request.json
if not data or 'name' not in data:
return jsonify({'success': False, 'error': '缺少必要字段'}), 400
db = get_session()
try:
# 检查域名是否已存在
domain_exists = db.query(Domain).filter_by(name=data['name']).first()
if domain_exists:
return jsonify({'success': False, 'error': '域名已存在'}), 400
# 创建新域名
domain = Domain(
name=data['name'],
description=data.get('description', ''),
active=data.get('active', True)
)
db.add(domain)
db.commit()
return jsonify({
'success': True,
'message': '域名创建成功',
'domain': domain.to_dict()
})
except Exception as e:
db.rollback()
current_app.logger.exception(f"创建域名失败: {str(e)}")
return jsonify({'success': False, 'error': '创建域名失败'}), 500
finally:
db.close()
@api_bp.route('/mailboxes', methods=['GET'])
def get_mailboxes():
"""获取所有邮箱"""
db = get_session()
try:
mailboxes = db.query(Mailbox).all()
return jsonify({
'success': True,
'mailboxes': [mailbox.to_dict() for mailbox in mailboxes]
})
except Exception as e:
current_app.logger.exception(f"获取邮箱失败: {str(e)}")
return jsonify({'success': False, 'error': '获取邮箱失败'}), 500
finally:
db.close()
@api_bp.route('/mailboxes', methods=['POST'])
def create_mailbox():
"""创建新邮箱"""
data = request.json
if not data or 'address' not in data or 'domain_id' not in data:
return jsonify({'success': False, 'error': '缺少必要字段'}), 400
db = get_session()
try:
# 检查域名是否存在
domain = db.query(Domain).filter_by(id=data['domain_id'], active=True).first()
if not domain:
return jsonify({'success': False, 'error': '域名不存在或未激活'}), 400
# 检查邮箱是否已存在
mailbox_exists = db.query(Mailbox).filter_by(
address=data['address'], domain_id=data['domain_id']).first()
if mailbox_exists:
return jsonify({'success': False, 'error': '邮箱已存在'}), 400
# 创建新邮箱
mailbox = Mailbox(
address=data['address'],
domain_id=data['domain_id'],
description=data.get('description', ''),
active=data.get('active', True)
)
db.add(mailbox)
db.commit()
return jsonify({
'success': True,
'message': '邮箱创建成功',
'mailbox': mailbox.to_dict()
})
except Exception as e:
db.rollback()
current_app.logger.exception(f"创建邮箱失败: {str(e)}")
return jsonify({'success': False, 'error': '创建邮箱失败'}), 500
finally:
db.close()
@api_bp.route('/mailboxes/batch', methods=['POST'])
def batch_create_mailboxes():
"""批量创建邮箱"""
data = request.json
if not data or 'domain_id' not in data or 'usernames' not in data or not isinstance(data['usernames'], list):
return jsonify({'success': False, 'error': '缺少必要字段或格式不正确'}), 400
domain_id = data['domain_id']
usernames = data['usernames']
description = data.get('description', '')
db = get_session()
try:
# 检查域名是否存在
domain = db.query(Domain).filter_by(id=domain_id, active=True).first()
if not domain:
return jsonify({'success': False, 'error': '域名不存在或未激活'}), 400
created_mailboxes = []
existed_mailboxes = []
for username in usernames:
# 检查邮箱是否已存在
mailbox_exists = db.query(Mailbox).filter_by(
username=username, domain_id=domain_id).first()
if mailbox_exists:
existed_mailboxes.append(username)
continue
# 创建新邮箱
mailbox = Mailbox(
username=username,
domain_id=domain_id,
description=description,
active=True
)
db.add(mailbox)
created_mailboxes.append(username)
db.commit()
return jsonify({
'success': True,
'message': f'成功创建 {len(created_mailboxes)} 个邮箱,{len(existed_mailboxes)} 个已存在',
'created': created_mailboxes,
'existed': existed_mailboxes
})
except Exception as e:
db.rollback()
current_app.logger.exception(f"批量创建邮箱失败: {str(e)}")
return jsonify({'success': False, 'error': '批量创建邮箱失败'}), 500
finally:
db.close()
@api_bp.route('/mailboxes/<int:mailbox_id>', methods=['GET'])
def get_mailbox(mailbox_id):
"""获取特定邮箱的信息"""
db = get_session()
try:
mailbox = db.query(Mailbox).filter_by(id=mailbox_id).first()
if not mailbox:
return jsonify({'success': False, 'error': '邮箱不存在'}), 404
# 更新最后访问时间
mailbox.last_accessed = datetime.utcnow()
db.commit()
return jsonify({
'success': True,
'mailbox': mailbox.to_dict()
})
except Exception as e:
current_app.logger.exception(f"获取邮箱信息失败: {str(e)}")
return jsonify({'success': False, 'error': '获取邮箱信息失败'}), 500
finally:
db.close()
@api_bp.route('/mailboxes/<int:mailbox_id>/emails', methods=['GET'])
def get_emails(mailbox_id):
"""获取邮箱中的所有邮件"""
db = get_session()
try:
mailbox = db.query(Mailbox).filter_by(id=mailbox_id).first()
if not mailbox:
return jsonify({'success': False, 'error': '邮箱不存在'}), 404
# 更新最后访问时间
mailbox.last_accessed = datetime.utcnow()
db.commit()
emails = db.query(Email).filter_by(mailbox_id=mailbox_id).order_by(Email.received_at.desc()).all()
return jsonify({
'success': True,
'emails': [email.to_dict() for email in emails]
})
except Exception as e:
current_app.logger.exception(f"获取邮件失败: {str(e)}")
return jsonify({'success': False, 'error': '获取邮件失败'}), 500
finally:
db.close()
@api_bp.route('/emails/<int:email_id>', methods=['GET'])
def get_email(email_id):
"""获取特定邮件的详细内容"""
db = get_session()
try:
email = db.query(Email).filter_by(id=email_id).first()
if not email:
return jsonify({'success': False, 'error': '邮件不存在'}), 404
# 标记为已读
if not email.read:
email.read = True
db.commit()
return jsonify({
'success': True,
'email': email.to_dict()
})
except Exception as e:
current_app.logger.exception(f"获取邮件详情失败: {str(e)}")
return jsonify({'success': False, 'error': '获取邮件详情失败'}), 500
finally:
db.close()
@api_bp.route('/emails/<int:email_id>/verification', methods=['GET'])
def get_verification_info(email_id):
"""获取邮件中的验证信息(链接和验证码)"""
db = get_session()
try:
email = db.query(Email).filter_by(id=email_id).first()
if not email:
return jsonify({'success': False, 'error': '邮件不存在'}), 404
verification_links = json.loads(email.verification_links) if email.verification_links else []
verification_codes = json.loads(email.verification_codes) if email.verification_codes else []
return jsonify({
'success': True,
'email_id': email_id,
'verification_links': verification_links,
'verification_codes': verification_codes
})
except Exception as e:
current_app.logger.exception(f"获取验证信息失败: {str(e)}")
return jsonify({'success': False, 'error': '获取验证信息失败'}), 500
finally:
db.close()
@api_bp.route('/status', methods=['GET'])
def system_status():
"""获取系统状态"""
session = get_session()
# 获取基本统计信息
domain_count = session.query(func.count(Domain.id)).scalar()
mailbox_count = session.query(func.count(Mailbox.id)).scalar()
email_count = session.query(func.count(Email.id)).scalar()
# 获取最近24小时的邮件数量
recent_emails = session.query(func.count(Email.id)).filter(
Email.received_at > datetime.now() - timedelta(hours=24)
).scalar()
# 获取系统资源信息
cpu_percent = psutil.cpu_percent(interval=0.5)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
# 获取服务状态
smtp_server = get_smtp_server()
email_processor = get_email_processor()
smtp_status = "running" if smtp_server and smtp_server.controller else "stopped"
processor_status = "running" if email_processor and email_processor.is_running else "stopped"
# 构建响应
status = {
"system": {
"uptime": round(time.time() - psutil.boot_time()),
"time": datetime.now().isoformat(),
"platform": platform.platform(),
"python_version": sys.version
},
"resources": {
"cpu_percent": cpu_percent,
"memory_percent": memory.percent,
"memory_used": memory.used,
"memory_total": memory.total,
"disk_percent": disk.percent,
"disk_used": disk.used,
"disk_total": disk.total
},
"application": {
"domain_count": domain_count,
"mailbox_count": mailbox_count,
"email_count": email_count,
"recent_emails_24h": recent_emails,
"storage_path": os.path.abspath("email_data"),
"services": {
"smtp_server": smtp_status,
"email_processor": processor_status
}
}
}
return jsonify(status)