初始化提交,包含完整的邮件系统代码

This commit is contained in:
huangzhenpc
2025-02-25 19:50:00 +08:00
commit aeffc4f8b8
52 changed files with 6673 additions and 0 deletions

4
.env Normal file
View File

@@ -0,0 +1,4 @@
FLASK_ENV=production
SECRET_KEY=your_random_secure_key_here
MAIL_DOMAINS=example.com,mail.example.com
DATABASE_URL=sqlite:///email_system.db

45
.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# virtualenv
venv/
ENV/
# 日志和数据文件
logs/
*.log
email_data/
db/
*.db
# 本地设置
.env.local
# IDE相关
.idea/
.vscode/
*.swp
*.swo
# 系统文件
.DS_Store
Thumbs.db

283
DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,283 @@
# 邮箱系统部署指南
本文档提供在生产环境中部署邮箱系统的详细步骤。
## 系统要求
- Python 3.7+
- 公网IP地址
- 已配置MX记录的域名
- 开放的25端口SMTP和5000端口API
## 1. 服务器准备
### 1.1 安装基础软件包
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install -y python3 python3-pip python3-venv git ufw
# CentOS/RHEL
sudo yum update -y
sudo yum install -y python3 python3-pip git
```
### 1.2 配置防火墙
```bash
# Ubuntu/Debian (ufw)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 25/tcp # SMTP
sudo ufw allow 5000/tcp # API
sudo ufw enable
# CentOS/RHEL (firewalld)
sudo firewall-cmd --permanent --add-port=22/tcp
sudo firewall-cmd --permanent --add-port=25/tcp
sudo firewall-cmd --permanent --add-port=5000/tcp
sudo firewall-cmd --reload
```
## 2. 部署系统
### 2.1 获取代码
```bash
git clone <repository-url> /opt/email-system
cd /opt/email-system
```
### 2.2 创建虚拟环境并安装依赖
```bash
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```
### 2.3 配置系统
创建环境变量文件:
```bash
cat > .env << EOF
FLASK_ENV=production
SECRET_KEY=$(python -c 'import secrets; print(secrets.token_hex(16))')
MAIL_DOMAINS=yourdomain.com,anotherdomain.com
DATABASE_URL=sqlite:///db/email_system.db
EOF
```
修改`local_settings.py`以适应生产环境:
```bash
cat > local_settings.py << EOF
"""
生产环境配置文件
"""
# SMTP服务器配置
SMTP_HOST = '0.0.0.0' # 监听所有网络接口
SMTP_PORT = 25 # 使用标准SMTP端口
# 日志配置
LOG_LEVEL = 'INFO'
EOF
```
### 2.4 初始化数据库
```bash
mkdir -p db email_data logs
python -c "from app.models import init_db; init_db()"
```
## 3. 启动服务
### 3.1 直接启动(测试用)
```bash
python run.py --host 0.0.0.0 --port 5000 --smtp-port 25
```
### 3.2 使用Systemd服务推荐用于生产
创建服务文件:
```bash
sudo cat > /etc/systemd/system/email-system.service << EOF
[Unit]
Description=Email System
After=network.target
[Service]
User=root
WorkingDirectory=/opt/email-system
ExecStart=/opt/email-system/venv/bin/python run.py --host 0.0.0.0 --port 5000 --smtp-port 25
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
```
启动服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable email-system
sudo systemctl start email-system
```
## 4. 验证部署
### 4.1 检查服务状态
```bash
sudo systemctl status email-system
netstat -tulpn | grep -E ':(25|5000)'
curl http://localhost:5000/api/status
```
### 4.2 检查日志
```bash
tail -f logs/email_system.log
```
## 5. 故障排除
### 5.1 服务无法启动
- 检查日志文件: `cat logs/email_system.log`
- 确认端口未被占用: `netstat -tulpn | grep -E ':(25|5000)'`
- 检查Python依赖: `pip install -r requirements.txt`
### 5.2 无法接收外部邮件
- 验证MX记录配置: `dig MX yourdomain.com`
- 检查25端口是否开放: `telnet your-server-ip 25`
- 确认SMTP服务已启动: `ps aux | grep smtp`
- 检查防火墙规则: `sudo ufw status``sudo firewall-cmd --list-all`
### 5.3 常见ISP限制
许多ISP会阻止25端口的出站流量。如果遇到此问题:
- 联系ISP要求解除25端口限制
- 使用备用端口如587并在防火墙中转发到25端口
- 考虑使用云服务提供商如AWS、GCP、阿里云等的VM实例
## 6. 性能优化
### 6.1 数据库优化
对于大规模部署考虑从SQLite迁移到PostgreSQL:
```bash
# 安装PostgreSQL
sudo apt install -y postgresql postgresql-contrib
# 创建数据库和用户
sudo -u postgres psql -c "CREATE USER email_system WITH PASSWORD 'your_password';"
sudo -u postgres psql -c "CREATE DATABASE email_system_db OWNER email_system;"
```
然后更新`.env`文件中的`DATABASE_URL`:
```
DATABASE_URL=postgresql://email_system:your_password@localhost/email_system_db
```
### 6.2 使用反向代理
对于生产环境建议使用Nginx作为反向代理:
```bash
# 安装Nginx
sudo apt install -y nginx
# 配置Nginx
sudo cat > /etc/nginx/sites-available/email-system << EOF
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/email-system /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
```
## 7. 安全建议
- 启用HTTPS (使用Let's Encrypt)
- 限制API访问 (IP白名单、API密钥等)
- 定期备份数据库
- 监控系统资源使用情况
- 实现基本的邮件过滤功能
## 8. 备份与恢复
### 8.1 备份
```bash
# 创建备份脚本
cat > backup.sh << EOF
#!/bin/bash
TIMESTAMP=\$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/opt/email-system/backups/\$TIMESTAMP
mkdir -p \$BACKUP_DIR
# 停止服务
systemctl stop email-system
# 备份数据库
cp db/email_system.db \$BACKUP_DIR/
# 备份邮件数据
cp -r email_data \$BACKUP_DIR/
# 备份配置
cp .env \$BACKUP_DIR/
cp local_settings.py \$BACKUP_DIR/
# 重启服务
systemctl start email-system
echo "备份完成: \$BACKUP_DIR"
EOF
chmod +x backup.sh
```
### 8.2 恢复
```bash
# 恢复数据
BACKUP_DIR=/opt/email-system/backups/20220101_120000
# 停止服务
systemctl stop email-system
# 恢复数据库
cp $BACKUP_DIR/email_system.db db/
# 恢复邮件数据
cp -r $BACKUP_DIR/email_data ./
# 恢复配置
cp $BACKUP_DIR/.env ./
cp $BACKUP_DIR/local_settings.py ./
# 重启服务
systemctl start email-system
```

181
DNS_CONFIGURATION.md Normal file
View File

@@ -0,0 +1,181 @@
# 邮箱系统DNS配置指南
为使邮箱系统能够接收来自外部的邮件必须正确配置DNS记录。本文档详细说明了配置过程。
## 必要的DNS记录
### MX记录必须
MXMail Exchange记录指定接收域名邮件的服务器。您需要为每个要接收邮件的域名添加MX记录。
| 记录类型 | 主机记录 | 优先级 | 记录值 |
|---------|---------|-------|--------|
| MX | @ | 10 | mail.yourdomain.com |
* `@`表示针对域名本身(如`yourdomain.com`
* `优先级`数字越小表示优先级越高如果有多个MX记录邮件将首先尝试发送到优先级最高的服务器
* `记录值`应指向您的邮件服务器的主机名
### A记录必须
A记录将主机名映射到IPv4地址。您需要创建一个A记录将MX记录中使用的主机名指向您的服务器IP。
| 记录类型 | 主机记录 | 记录值 |
|---------|---------|-------|
| A | mail | 203.0.113.1 |
* `主机记录`应为MX记录中使用的主机名的前缀如果MX记录值为`mail.yourdomain.com`,则主机记录为`mail`
* `记录值`是您服务器的公网IP地址
### SPF记录推荐
SPFSender Policy Framework记录用于防止邮件欺诈指定哪些服务器被授权发送来自您域名的邮件。
| 记录类型 | 主机记录 | 记录值 |
|---------|---------|-------|
| TXT | @ | v=spf1 ip4:203.0.113.1 -all |
* `v=spf1`表示这是SPF记录的版本1
* `ip4:203.0.113.1`授权您服务器的IP地址发送邮件
* `-all`表示严格模式,任何不匹配的发件人都会被拒绝
### PTR记录重要
PTR反向DNS记录用于将IP地址映射回主机名许多邮件服务器会检查这个记录来验证发件人。
**注意:** PTR记录通常由您的ISP或云服务提供商配置需要联系他们进行设置。
请求他们将您的IP地址(如`203.0.113.1`)的PTR记录设置为您的邮件服务器域名(如`mail.yourdomain.com`)。
## 配置步骤
### 1. 登录DNS管理面板
使用您的域名注册商提供的管理面板或DNS服务提供商的控制台登录。
常见的DNS管理面板包括:
- Cloudflare
- GoDaddy
- NameCheap
- 阿里云
- 腾讯云
### 2. 添加MX记录
1. 找到DNS记录管理界面
2. 选择添加新记录
3. 记录类型选择"MX"
4. 主机记录填写"@"
5. 优先级填写"10"
6. 记录值填写指向您服务器的域名,如"mail.yourdomain.com"
7. TTL可以保持默认值或设置为3600(1小时)
8. 保存记录
### 3. 添加A记录
1. 再次选择添加新记录
2. 记录类型选择"A"
3. 主机记录填写"mail"或您在MX记录中使用的子域名
4. 记录值填写您服务器的公网IP地址
5. TTL可以保持默认值或设置为3600
6. 保存记录
### 4. 添加SPF记录
1. 再次选择添加新记录
2. 记录类型选择"TXT"
3. 主机记录填写"@"
4. 记录值填写`v=spf1 ip4:YOUR_SERVER_IP -all`替换YOUR_SERVER_IP为您的服务器IP
5. TTL可以保持默认值或设置为3600
6. 保存记录
## 验证配置
### 1. 验证MX记录
使用dig命令查询MX记录:
```bash
dig MX yourdomain.com
```
预期输出应包含您配置的MX记录:
```
yourdomain.com. 3600 IN MX 10 mail.yourdomain.com.
```
### 2. 验证A记录
```bash
dig A mail.yourdomain.com
```
预期输出应显示您服务器的IP:
```
mail.yourdomain.com. 3600 IN A 203.0.113.1
```
### 3. 验证SPF记录
```bash
dig TXT yourdomain.com
```
预期输出应包含您的SPF记录:
```
yourdomain.com. 3600 IN TXT "v=spf1 ip4:203.0.113.1 -all"
```
### 4. 验证PTR记录
```bash
dig -x YOUR_SERVER_IP
```
预期输出应将您的IP解析为您的服务器域名:
```
1.113.0.203.in-addr.arpa. 3600 IN PTR mail.yourdomain.com.
```
## 故障排除
### 记录未生效
DNS更改可能需要24-48小时在全球范围内完全生效。如果刚刚更改了记录可能需要等待一段时间才能看到效果。
可以使用不同的DNS查询工具检查传播状态:
- https://www.whatsmydns.net/
- https://dnschecker.org/
### 邮件被退回
如果邮件被接收方拒绝,可能的原因包括:
1. **没有正确的PTR记录**: 许多服务器(如Gmail)要求有匹配的PTR记录
2. **SPF记录配置错误**: 检查SPF记录语法是否正确
3. **服务器IP被列入黑名单**: 使用 https://mxtoolbox.com/blacklists.aspx 检查
### 验证综合邮件配置
使用MX Toolbox等工具进行综合检查:
https://mxtoolbox.com/SuperTool.aspx
## 多域名配置
要为系统中的每个域名都接收邮件需要为每个域名单独配置MX记录但都指向相同的邮件服务器。例如:
对于domain1.com:
```
domain1.com. MX 10 mail.yourdomain.com.
```
对于domain2.com:
```
domain2.com. MX 10 mail.yourdomain.com.
```
这样所有配置的域名收到的邮件都会被发送到同一个服务器进行处理。

43
Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
FROM python:3.9-alpine
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apk add --no-cache \
gcc \
musl-dev \
python3-dev \
libffi-dev \
openssl-dev \
bash
# 复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用程序代码
COPY . .
# 创建必要的目录
RUN mkdir -p logs email_data db
# 设置环境变量
ENV FLASK_ENV=production \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# 设置非root用户
RUN adduser -D emailuser
RUN chown -R emailuser:emailuser /app
USER root
# 暴露端口
EXPOSE 5000 25
# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:5000/api/status || exit 1
# 启动命令
CMD ["sh", "-c", "python run.py --host 0.0.0.0 --port 5000 --smtp-port 25"]

32
README.md Normal file
View File

@@ -0,0 +1,32 @@
# 邮件系统
这是一个基于Python的完整邮件系统解决方案提供SMTP服务和API接口管理。
## 功能特点
- SMTP服务器接收和发送邮件
- 邮箱管理API接口
- 配置灵活,支持多域名
- 支持Docker部署
- 包含监控和性能测试工具
## 目录结构
- `app/`: 应用主代码
- `api/`: API接口实现
- `models/`: 数据模型
- `services/`: 业务逻辑服务
- `templates/`: 模板文件
- `config.py`: 配置文件
- `run.py`: 主程序入口
- `monitor_email_system.py`: 监控工具
- `performance_test.py`: 性能测试工具
- `deploy_production.sh`: 生产环境部署脚本
## 部署方法
详细部署步骤请参考 `DEPLOYMENT_GUIDE.md`
## 许可证
此项目采用MIT许可证。

74
app/__init__.py Normal file
View File

@@ -0,0 +1,74 @@
import os
import logging
from flask import Flask
from flask_cors import CORS
# 修改相对导入为绝对导入
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import active_config
def setup_logging(app):
"""设置日志"""
log_level = getattr(logging, active_config.LOG_LEVEL.upper(), logging.INFO)
# 确保日志目录存在
log_dir = os.path.dirname(active_config.LOG_FILE)
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 配置日志
logging.basicConfig(
level=log_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(active_config.LOG_FILE),
logging.StreamHandler()
]
)
app.logger.setLevel(log_level)
return app
def create_app(config=None):
"""创建并配置Flask应用"""
app = Flask(__name__)
# 加载配置
app.config.from_object(active_config)
# 如果提供了自定义配置,加载它
if config:
app.config.from_object(config)
# 允许跨域请求
CORS(app)
# 设置日志
app = setup_logging(app)
# 确保存储邮件的目录存在
os.makedirs(active_config.MAIL_STORAGE_PATH, exist_ok=True)
# 初始化数据库
from .models import init_db
init_db()
# 注册蓝图
from .api import api_bp
app.register_blueprint(api_bp)
# 首页路由
@app.route("/")
def index():
return {
"name": "Email System",
"version": "1.0.0",
"status": "running"
}
app.logger.info('应用初始化完成')
return app

23
app/api/__init__.py Normal file
View File

@@ -0,0 +1,23 @@
# 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 *
except Exception as e:
logging.error(f"导入API路由时出错: {str(e)}")
raise

BIN
app/api/domain_routes.py Normal file

Binary file not shown.

198
app/api/email_routes.py Normal file
View File

@@ -0,0 +1,198 @@
from flask import request, jsonify, current_app, send_file
from io import BytesIO
import time
from . import api_bp
from ..models import get_session, Email, Mailbox
# 获取邮箱的所有邮件
@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 = {
'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({'error': '获取邮件列表失败', 'details': str(e)}), 500
# 获取特定邮件详情
@api_bp.route('/emails/<int:email_id>', methods=['GET'])
def get_email(email_id):
"""获取特定邮件的详细信息"""
try:
mark_as_read = request.args.get('mark_as_read', 'true').lower() == 'true'
db = get_session()
try:
email = db.query(Email).filter_by(id=email_id).first()
if not email:
return jsonify({'error': '邮件不存在'}), 404
# 标记为已读
if mark_as_read and not email.read:
email.read = True
db.commit()
# 构建详细响应
result = email.to_dict()
result['body_text'] = email.body_text
result['body_html'] = email.body_html
# 获取附件信息
attachments = []
for attachment in email.attachments:
attachments.append({
'id': attachment.id,
'filename': attachment.filename,
'content_type': attachment.content_type,
'size': attachment.size
})
result['attachments'] = attachments
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/<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

206
app/api/mailbox_routes.py Normal file
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
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)

46
app/models/__init__.py Normal file
View File

@@ -0,0 +1,46 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
import os
import sys
# 修改相对导入为绝对导入
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from config import active_config
# 创建数据库引擎
engine = create_engine(active_config.SQLALCHEMY_DATABASE_URI)
# 创建会话工厂
session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)
# 创建模型基类
Base = declarative_base()
# 获取数据库会话
def get_session():
"""获取数据库会话"""
return Session()
# 初始化数据库
def init_db():
"""初始化数据库,创建所有表"""
# 导入所有模型以确保它们被注册
from .domain import Domain
from .mailbox import Mailbox
from .email import Email
from .attachment import Attachment
# 创建表
Base.metadata.create_all(engine)
return get_session()
# 导出模型类
from .domain import Domain
from .mailbox import Mailbox
from .email import Email
from .attachment import Attachment
__all__ = ['Base', 'get_session', 'init_db', 'Domain', 'Mailbox', 'Email', 'Attachment']

71
app/models/attachment.py Normal file
View File

@@ -0,0 +1,71 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, LargeBinary
from sqlalchemy.orm import relationship
from datetime import datetime
import os
from . import Base
class Attachment(Base):
"""附件模型"""
__tablename__ = 'attachments'
id = Column(Integer, primary_key=True)
email_id = Column(Integer, ForeignKey('emails.id'), nullable=False, index=True)
filename = Column(String(255), nullable=False)
content_type = Column(String(100), nullable=True)
size = Column(Integer, nullable=False, default=0)
storage_path = Column(String(500), nullable=True) # 用于文件系统存储
content = Column(LargeBinary, nullable=True) # 用于小型附件的直接存储
created_at = Column(DateTime, default=datetime.utcnow)
# 关联关系
email = relationship("Email", back_populates="attachments")
@property
def is_stored_in_fs(self):
"""判断附件是否存储在文件系统中"""
return bool(self.storage_path and not self.content)
def save_to_filesystem(self, content, base_path):
"""将附件保存到文件系统"""
# 确保目录存在
os.makedirs(base_path, exist_ok=True)
# 创建文件路径
file_path = os.path.join(
base_path,
f"{self.email_id}_{self.id}_{self.filename}"
)
# 写入文件
with open(file_path, 'wb') as f:
f.write(content)
# 更新对象属性
self.storage_path = file_path
self.size = len(content)
self.content = None # 清空内存中的内容
return file_path
def get_content(self, attachments_dir=None):
"""获取附件内容,无论是从数据库还是文件系统"""
if self.content:
return self.content
if self.storage_path and os.path.exists(self.storage_path):
with open(self.storage_path, 'rb') as f:
return f.read()
return None
def to_dict(self):
"""转换为字典用于API响应"""
return {
"id": self.id,
"email_id": self.email_id,
"filename": self.filename,
"content_type": self.content_type,
"size": self.size,
"created_at": self.created_at.isoformat() if self.created_at else None
}

35
app/models/domain.py Normal file
View File

@@ -0,0 +1,35 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.orm import relationship
from datetime import datetime
from . import Base
class Domain(Base):
"""邮件域名模型"""
__tablename__ = 'domains'
id = Column(Integer, primary_key=True)
name = Column(String(255), unique=True, nullable=False, index=True)
description = Column(String(500), nullable=True)
active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关系
mailboxes = relationship("Mailbox", back_populates="domain", cascade="all, delete-orphan")
def __repr__(self):
return f"<Domain {self.name}>"
def to_dict(self):
"""转换为字典用于API响应"""
return {
"id": self.id,
"name": self.name,
"description": self.description,
"active": self.active,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"mailbox_count": len(self.mailboxes) if self.mailboxes else 0
}

98
app/models/email.py Normal file
View File

@@ -0,0 +1,98 @@
import os
import json
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean, JSON
from sqlalchemy.orm import relationship
from datetime import datetime
import re
import sys
from . import Base
import config
active_config = config.active_config
class Email(Base):
"""电子邮件模型"""
__tablename__ = 'emails'
id = Column(Integer, primary_key=True)
mailbox_id = Column(Integer, ForeignKey('mailboxes.id'), nullable=False, index=True)
sender = Column(String(255), nullable=False)
recipients = Column(String(1000), nullable=False)
subject = Column(String(500), nullable=True)
body_text = Column(Text, nullable=True)
body_html = Column(Text, nullable=True)
received_at = Column(DateTime, default=datetime.utcnow)
read = Column(Boolean, default=False)
headers = Column(JSON, nullable=True)
# 提取的验证码和链接
verification_code = Column(String(100), nullable=True)
verification_link = Column(String(1000), nullable=True)
# 关联关系
mailbox = relationship("Mailbox", back_populates="emails")
attachments = relationship("Attachment", back_populates="email", cascade="all, delete-orphan")
def save_raw_email(self, raw_content):
"""保存原始邮件内容到文件"""
storage_path = active_config.MAIL_STORAGE_PATH
mailbox_dir = os.path.join(storage_path, str(self.mailbox_id))
os.makedirs(mailbox_dir, exist_ok=True)
# 保存原始邮件内容
file_path = os.path.join(mailbox_dir, f"{self.id}.eml")
with open(file_path, 'wb') as f:
f.write(raw_content)
def extract_verification_data(self):
"""
尝试从邮件内容中提取验证码和验证链接
这个方法会在邮件保存时自动调用
"""
# 合并文本和HTML内容用于搜索
content = f"{self.subject} {self.body_text or ''}"
# 提取可能的验证码4-8位数字或字母组合
code_patterns = [
r'\b[A-Z0-9]{4,8}\b', # 大写字母和数字
r'验证码[:]\s*([A-Z0-9]{4,8})', # 中文格式
r'验证码是[:]\s*([A-Z0-9]{4,8})', # 中文格式2
r'code[:]\s*([A-Z0-9]{4,8})', # 英文格式
]
for pattern in code_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
self.verification_code = matches[0]
break
# 提取验证链接
link_patterns = [
r'https?://\S+(?:verify|confirm|activate)\S+',
r'https?://\S+(?:token|auth|account)\S+',
]
for pattern in link_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
self.verification_link = matches[0]
break
def __repr__(self):
return f"<Email {self.id}: {self.subject}>"
def to_dict(self):
"""转换为字典用于API响应"""
return {
"id": self.id,
"mailbox_id": self.mailbox_id,
"sender": self.sender,
"recipients": self.recipients,
"subject": self.subject,
"received_at": self.received_at.isoformat() if self.received_at else None,
"read": self.read,
"verification_code": self.verification_code,
"verification_link": self.verification_link,
"has_attachments": len(self.attachments) > 0 if self.attachments else False
}

50
app/models/mailbox.py Normal file
View File

@@ -0,0 +1,50 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
import secrets
from . import Base
class Mailbox(Base):
"""邮箱模型"""
__tablename__ = 'mailboxes'
id = Column(Integer, primary_key=True)
address = Column(String(255), unique=True, nullable=False, index=True)
domain_id = Column(Integer, ForeignKey('domains.id'), nullable=False)
password_hash = Column(String(255), nullable=True)
description = Column(String(500), nullable=True)
active = Column(Boolean, default=True)
api_key = Column(String(64), unique=True, default=lambda: secrets.token_hex(16))
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
last_accessed = Column(DateTime, nullable=True)
# 关系
domain = relationship("Domain", back_populates="mailboxes")
emails = relationship("Email", back_populates="mailbox", cascade="all, delete-orphan")
@property
def full_address(self):
"""获取完整邮箱地址 (包含域名)"""
return f"{self.address}@{self.domain.name}"
def __repr__(self):
return f"<Mailbox {self.full_address}>"
def to_dict(self):
"""转换为字典用于API响应"""
return {
"id": self.id,
"address": self.address,
"domain_id": self.domain_id,
"domain_name": self.domain.name if self.domain else None,
"full_address": self.full_address,
"description": self.description,
"active": self.active,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"last_accessed": self.last_accessed.isoformat() if self.last_accessed else None,
"email_count": len(self.emails) if self.emails else 0
}

44
app/services/__init__.py Normal file
View File

@@ -0,0 +1,44 @@
# 服务层初始化文件
# 这里将导入所有服务模块以便于统一调用
from .smtp_server import SMTPServer
from .email_processor import EmailProcessor
from .mail_store import MailStore
# 全局服务实例
_smtp_server = None
_email_processor = None
_mail_store = None
def register_smtp_server(instance):
"""注册SMTP服务器实例"""
global _smtp_server
_smtp_server = instance
def register_email_processor(instance):
"""注册邮件处理器实例"""
global _email_processor
_email_processor = instance
def register_mail_store(instance):
"""注册邮件存储实例"""
global _mail_store
_mail_store = instance
def get_smtp_server():
"""获取SMTP服务器实例"""
return _smtp_server
def get_email_processor():
"""获取邮件处理器实例"""
return _email_processor
def get_mail_store():
"""获取邮件存储实例"""
return _mail_store
__all__ = [
'SMTPServer', 'EmailProcessor', 'MailStore',
'register_smtp_server', 'register_email_processor', 'register_mail_store',
'get_smtp_server', 'get_email_processor', 'get_mail_store'
]

View File

@@ -0,0 +1,123 @@
import logging
import re
import threading
import time
from queue import Queue
logger = logging.getLogger(__name__)
class EmailProcessor:
"""邮件处理器,负责处理邮件并提取验证信息"""
def __init__(self, mail_store):
"""
初始化邮件处理器
参数:
mail_store: 邮件存储服务实例
"""
self.mail_store = mail_store
self.processing_queue = Queue()
self.is_running = False
self.worker_thread = None
def start(self):
"""启动邮件处理器"""
if self.is_running:
logger.warning("邮件处理器已在运行")
return False
self.is_running = True
self.worker_thread = threading.Thread(
target=self._processing_worker,
daemon=True
)
self.worker_thread.start()
logger.info("邮件处理器已启动")
return True
def stop(self):
"""停止邮件处理器"""
if not self.is_running:
logger.warning("邮件处理器未在运行")
return False
self.is_running = False
if self.worker_thread:
self.worker_thread.join(timeout=5.0)
self.worker_thread = None
logger.info("邮件处理器已停止")
return True
def queue_email_for_processing(self, email_id):
"""将邮件添加到处理队列"""
self.processing_queue.put(email_id)
return True
def _processing_worker(self):
"""处理队列中的邮件的工作线程"""
while self.is_running:
try:
# 获取队列中的邮件最多等待1秒
try:
email_id = self.processing_queue.get(timeout=1.0)
except:
continue
# 处理邮件
self._process_email(email_id)
# 标记任务完成
self.processing_queue.task_done()
except Exception as e:
logger.error(f"处理邮件时出错: {str(e)}")
def _process_email(self, email_id):
"""处理单个邮件,提取验证码和链接"""
# 从邮件存储获取邮件
email_data = self.mail_store.get_email_by_id(email_id, mark_as_read=False)
if not email_data:
logger.warning(f"找不到ID为 {email_id} 的邮件")
return False
# 提取验证码和链接已经在Email模型的extract_verification_data方法中实现
# 这里可以添加更复杂的提取逻辑或后处理
logger.info(f"邮件 {email_id} 处理完成")
return True
@staticmethod
def extract_verification_code(content):
"""从内容中提取验证码"""
code_patterns = [
r'\b[A-Z0-9]{4,8}\b', # 基本验证码格式
r'验证码[:]\s*([A-Z0-9]{4,8})',
r'验证码是[:]\s*([A-Z0-9]{4,8})',
r'code[:]\s*([A-Z0-9]{4,8})',
r'码[:]\s*(\d{4,8})' # 纯数字验证码
]
for pattern in code_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
return matches[0]
return None
@staticmethod
def extract_verification_link(content):
"""从内容中提取验证链接"""
link_patterns = [
r'(https?://\S+(?:verify|confirm|activate)\S+)',
r'(https?://\S+(?:token|auth|account)\S+)',
r'href\s*=\s*["\']([^"\']+(?:verify|confirm|activate)[^"\']*)["\']'
]
for pattern in link_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
return matches[0]
return None

263
app/services/mail_store.py Normal file
View File

@@ -0,0 +1,263 @@
import logging
import os
import email
from email.policy import default
from sqlalchemy.orm import Session
from datetime import datetime
import re
from ..models.domain import Domain
from ..models.mailbox import Mailbox
from ..models.email import Email
from ..models.attachment import Attachment
logger = logging.getLogger(__name__)
class MailStore:
"""邮件存储服务,负责保存和检索邮件"""
def __init__(self, db_session_factory, storage_path=None):
"""
初始化邮件存储服务
参数:
db_session_factory: 数据库会话工厂函数
storage_path: 附件存储路径
"""
self.db_session_factory = db_session_factory
self.storage_path = storage_path or os.path.join(os.getcwd(), 'email_data')
# 确保存储目录存在
if not os.path.exists(self.storage_path):
os.makedirs(self.storage_path)
async def save_email(self, sender, recipient, message, raw_data):
"""
保存一封电子邮件
参数:
sender: 发件人地址
recipient: 收件人地址
message: 解析后的邮件对象
raw_data: 原始邮件数据
返回:
成功返回邮件ID失败返回None
"""
# 从收件人地址中提取用户名和域名
try:
address, domain_name = recipient.split('@', 1)
except ValueError:
logger.warning(f"无效的收件人地址格式: {recipient}")
return None
# 获取数据库会话
db = self.db_session_factory()
try:
# 检查域名是否存在且活跃
domain = db.query(Domain).filter_by(name=domain_name, active=True).first()
if not domain:
logger.warning(f"不支持的域名: {domain_name}")
return None
# 查找或创建邮箱
mailbox = db.query(Mailbox).filter_by(address=address, domain_id=domain.id).first()
if not mailbox:
# 自动创建新邮箱
mailbox = Mailbox(
address=address,
domain_id=domain.id,
active=True
)
db.add(mailbox)
db.flush() # 获取ID但不提交
logger.info(f"已为 {recipient} 自动创建邮箱")
# 提取邮件内容
subject = message.get('subject', '')
# 获取文本和HTML内容
body_text = None
body_html = None
attachments_data = []
if message.is_multipart():
for part in message.walk():
content_type = part.get_content_type()
content_disposition = part.get_content_disposition()
# 处理文本内容
if content_disposition is None or content_disposition == 'inline':
if content_type == 'text/plain' and not body_text:
body_text = part.get_content()
elif content_type == 'text/html' and not body_html:
body_html = part.get_content()
# 处理附件
elif content_disposition == 'attachment':
filename = part.get_filename()
if filename:
content = part.get_payload(decode=True)
if content:
attachments_data.append({
'filename': filename,
'content_type': content_type,
'data': content,
'size': len(content)
})
else:
# 非多部分邮件
content_type = message.get_content_type()
if content_type == 'text/plain':
body_text = message.get_content()
elif content_type == 'text/html':
body_html = message.get_content()
# 创建邮件记录
email_obj = Email(
mailbox_id=mailbox.id,
sender=sender,
recipients=recipient,
subject=subject,
body_text=body_text,
body_html=body_html,
headers={k: v for k, v in message.items()}
)
# 保存邮件
db.add(email_obj)
db.flush() # 获取ID但不提交
# 提取验证信息
email_obj.extract_verification_data()
# 保存附件
for attachment_data in attachments_data:
attachment = Attachment(
email_id=email_obj.id,
filename=attachment_data['filename'],
content_type=attachment_data['content_type'],
size=attachment_data['size']
)
db.add(attachment)
db.flush()
# 决定存储位置
if attachment_data['size'] > 1024 * 1024: # 大于1MB的存储到文件系统
attachments_dir = os.path.join(self.storage_path, 'attachments')
attachment.save_to_filesystem(attachment_data['data'], attachments_dir)
else:
# 小附件直接存储在数据库
attachment.content = attachment_data['data']
# 提交所有更改
db.commit()
logger.info(f"邮件已成功保存: {sender} -> {recipient}, ID: {email_obj.id}")
return email_obj.id
except Exception as e:
db.rollback()
logger.error(f"保存邮件时出错: {str(e)}")
return None
finally:
db.close()
def get_emails_for_mailbox(self, mailbox_id, limit=50, offset=0, unread_only=False):
"""获取指定邮箱的邮件列表"""
db = self.db_session_factory()
try:
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()
return {
'total': total,
'items': [email.to_dict() for email in emails]
}
except Exception as e:
logger.error(f"获取邮件列表时出错: {str(e)}")
return {'total': 0, 'items': []}
finally:
db.close()
def get_email_by_id(self, email_id, mark_as_read=True):
"""获取指定ID的邮件详情"""
db = self.db_session_factory()
try:
email = db.query(Email).filter(Email.id == email_id).first()
if not email:
return None
if mark_as_read and not email.read:
email.read = True
email.last_read = datetime.utcnow()
db.commit()
# 获取附件信息
attachments = [attachment.to_dict() for attachment in email.attachments]
# 构建完整响应
result = email.to_dict()
result['body_text'] = email.body_text
result['body_html'] = email.body_html
result['attachments'] = attachments
return result
except Exception as e:
db.rollback()
logger.error(f"获取邮件详情时出错: {str(e)}")
return None
finally:
db.close()
def delete_email(self, email_id):
"""删除指定ID的邮件"""
db = self.db_session_factory()
try:
email = db.query(Email).filter(Email.id == email_id).first()
if not email:
return False
db.delete(email)
db.commit()
return True
except Exception as e:
db.rollback()
logger.error(f"删除邮件时出错: {str(e)}")
return False
finally:
db.close()
def get_attachment_content(self, attachment_id):
"""获取附件内容"""
db = self.db_session_factory()
try:
attachment = db.query(Attachment).filter(Attachment.id == attachment_id).first()
if not attachment:
return None
content = attachment.get_content()
return {
'content': content,
'filename': attachment.filename,
'content_type': attachment.content_type
}
except Exception as e:
logger.error(f"获取附件内容时出错: {str(e)}")
return None
finally:
db.close()

136
app/services/smtp_server.py Normal file
View File

@@ -0,0 +1,136 @@
import asyncio
import logging
import email
import platform
from email.policy import default
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import SMTP as SMTPProtocol
from aiosmtpd.handlers import Message
import os
import sys
import threading
from ..models.domain import Domain
from ..models.mailbox import Mailbox
logger = logging.getLogger(__name__)
# 检测是否Windows环境
IS_WINDOWS = platform.system().lower() == 'windows'
class EmailHandler(Message):
"""处理接收的电子邮件"""
def __init__(self, mail_store):
super().__init__()
self.mail_store = mail_store
def handle_message(self, message):
"""处理邮件消息这是Message类的抽象方法必须实现"""
# 这个方法在异步DATA处理完成后被调用但我们的邮件处理逻辑已经在handle_DATA中实现
# 所以这里只是一个空实现
return
async def handle_DATA(self, server, session, envelope):
"""处理接收到的邮件数据"""
try:
# 获取收件人和发件人
peer = session.peer
mail_from = envelope.mail_from
rcpt_tos = envelope.rcpt_tos
# 获取原始邮件内容
data = envelope.content
mail = email.message_from_bytes(data, policy=default)
# 保存邮件到存储服务
for rcpt in rcpt_tos:
result = await self.mail_store.save_email(mail_from, rcpt, mail, data)
# 记录日志
if result:
logger.info(f"邮件已保存: {mail_from} -> {rcpt}, 主题: {mail.get('Subject')}")
else:
logger.warning(f"邮件未保存: {mail_from} -> {rcpt}, 可能是无效地址")
return '250 Message accepted for delivery'
except Exception as e:
logger.error(f"处理邮件时出错: {str(e)}")
return '451 Requested action aborted: error in processing'
# 为Windows环境自定义SMTP控制器
if IS_WINDOWS:
class WindowsSafeController(Controller):
"""Windows环境安全的Controller跳过连接测试"""
def _trigger_server(self):
"""Windows环境下跳过SMTP服务器自检连接测试"""
# 在Windows环境下我们跳过自检连接测试
logger.info("Windows环境: 跳过SMTP服务器连接自检")
return
class SMTPServer:
"""SMTP服务器实现"""
def __init__(self, host='0.0.0.0', port=25, mail_store=None):
self.host = host
self.port = port
self.mail_store = mail_store
self.controller = None
self.server_thread = None
def start(self):
"""启动SMTP服务器"""
if self.controller:
logger.warning("SMTP服务器已经在运行")
return
try:
handler = EmailHandler(self.mail_store)
# 根据环境选择适当的Controller
if IS_WINDOWS:
# Windows环境使用自定义Controller
logger.info(f"Windows环境: 使用自定义Controller启动SMTP服务器 {self.host}:{self.port}")
self.controller = WindowsSafeController(
handler,
hostname=self.host,
port=self.port
)
else:
# 非Windows环境使用标准Controller
self.controller = Controller(
handler,
hostname=self.host,
port=self.port
)
# 在单独的线程中启动服务器
self.server_thread = threading.Thread(
target=self.controller.start,
daemon=True
)
self.server_thread.start()
logger.info(f"SMTP服务器已启动在 {self.host}:{self.port}")
return True
except Exception as e:
logger.error(f"启动SMTP服务器失败: {str(e)}")
return False
def stop(self):
"""停止SMTP服务器"""
if not self.controller:
logger.warning("SMTP服务器没有运行")
return
try:
self.controller.stop()
self.controller = None
self.server_thread = None
logger.info("SMTP服务器已停止")
return True
except Exception as e:
logger.error(f"停止SMTP服务器失败: {str(e)}")
return False

0
app/templates/index.html Normal file
View File

149
basic_test.py Normal file
View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import traceback
import time
import sys
# API配置
API_BASE_URL = "http://localhost:5000/api"
def print_separator(title):
"""打印分隔线"""
print("\n" + "=" * 80)
print(title.center(80))
print("=" * 80 + "\n")
def create_domain(name, description=""):
"""创建域名"""
print(f"创建域名: {name}")
try:
data = {
"name": name,
"description": description
}
response = requests.post(f"{API_BASE_URL}/domains", json=data)
print(f"响应状态码: {response.status_code}")
if response.status_code in (200, 201):
result = response.json()
print("响应内容:")
print(result)
return result.get("domain")
else:
print(f"创建域名失败: {response.text}")
return None
except Exception as e:
print(f"创建域名时出错: {str(e)}")
traceback.print_exc()
return None
def create_mailbox(domain_id, address):
"""创建邮箱"""
print(f"创建邮箱: {address} (域名ID: {domain_id})")
try:
data = {
"domain_id": domain_id,
"address": address
}
response = requests.post(f"{API_BASE_URL}/mailboxes", json=data)
print(f"响应状态码: {response.status_code}")
if response.status_code in (200, 201):
result = response.json()
print("响应内容:")
print(result)
return result.get("mailbox")
else:
print(f"创建邮箱失败: {response.text}")
return None
except Exception as e:
print(f"创建邮箱时出错: {str(e)}")
traceback.print_exc()
return None
def get_all_domains():
"""获取所有域名"""
print("获取所有域名")
try:
response = requests.get(f"{API_BASE_URL}/domains")
print(f"响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
domains = result.get("domains", [])
print(f"找到 {len(domains)} 个域名:")
for domain in domains:
print(f" - {domain.get('name')} (ID: {domain.get('id')})")
return domains
else:
print(f"获取域名失败: {response.text}")
return []
except Exception as e:
print(f"获取域名时出错: {str(e)}")
traceback.print_exc()
return []
def get_all_mailboxes():
"""获取所有邮箱"""
print("获取所有邮箱")
try:
response = requests.get(f"{API_BASE_URL}/mailboxes")
print(f"响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
mailboxes = result.get("mailboxes", [])
print(f"找到 {len(mailboxes)} 个邮箱:")
for mailbox in mailboxes:
print(f" - {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
return mailboxes
else:
print(f"获取邮箱失败: {response.text}")
return []
except Exception as e:
print(f"获取邮箱时出错: {str(e)}")
traceback.print_exc()
return []
def main():
try:
print_separator("基础功能测试")
# 测试获取所有域名
print_separator("测试获取域名")
domains = get_all_domains()
# 测试创建新域名
print_separator("测试创建域名")
test_domain = create_domain("test-domain.com", "测试域名")
if test_domain:
print(f"成功创建域名: {test_domain.get('name')} (ID: {test_domain.get('id')})")
# 测试获取所有邮箱
print_separator("测试获取邮箱")
mailboxes = get_all_mailboxes()
# 测试创建新邮箱
print_separator("测试创建邮箱")
test_mailbox = create_mailbox(test_domain.get('id'), "testuser")
if test_mailbox:
print(f"成功创建邮箱: {test_mailbox.get('full_address')} (ID: {test_mailbox.get('id')})")
else:
print("创建邮箱失败")
else:
print("创建域名失败")
print_separator("测试完成")
return 0
except Exception as e:
print(f"测试过程中发生错误: {str(e)}")
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())

68
check_db.py Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from app.models import get_session, Domain, Mailbox, Email
def check_database():
"""直接从数据库获取邮件信息"""
session = get_session()
try:
# 获取所有域名
print("获取所有域名...")
domains = session.query(Domain).all()
print(f"找到 {len(domains)} 个域名:")
for domain in domains:
print(f" - {domain.name} (ID: {domain.id})")
# 获取所有邮箱
print("\n获取所有邮箱...")
mailboxes = session.query(Mailbox).all()
print(f"找到 {len(mailboxes)} 个邮箱:")
for mailbox in mailboxes:
print(f" - {mailbox.full_address} (ID: {mailbox.id})")
# 获取所有邮件
print("\n获取所有邮件...")
emails = session.query(Email).all()
print(f"找到 {len(emails)} 封邮件:")
for i, email in enumerate(emails, 1):
print(f"\n--- 邮件 {i} ---")
print(f"ID: {email.id}")
print(f"邮箱ID: {email.mailbox_id}")
# 获取邮箱信息
mailbox = session.query(Mailbox).filter_by(id=email.mailbox_id).first()
mailbox_address = mailbox.full_address if mailbox else "未知"
print(f"收件人: {mailbox_address}")
print(f"发件人: {email.sender}")
print(f"主题: {email.subject}")
print(f"接收时间: {email.received_at}")
print(f"是否已读: {email.read}")
# 打印邮件内容
print("\n邮件文本内容摘要:")
if email.body_text:
print(email.body_text[:200] + "..." if len(email.body_text) > 200 else email.body_text)
else:
print("<无文本内容>")
print("\n邮件HTML内容摘要:")
if email.body_html:
print(email.body_html[:200] + "..." if len(email.body_html) > 200 else email.body_html)
else:
print("<无HTML内容>")
# 提取验证码
if hasattr(email, 'verification_code') and email.verification_code:
print(f"\n验证码: {email.verification_code}")
print("-" * 50)
finally:
session.close()
if __name__ == "__main__":
check_database()

View File

@@ -0,0 +1,355 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
邮箱系统部署准备检查脚本
用于验证服务器环境是否满足系统运行的要求
"""
import os
import sys
import platform
import socket
import subprocess
import importlib
import shutil
import json
from datetime import datetime
# 颜色输出
class Colors:
HEADER = '\033[95m'
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
# 检查结果
class CheckResult:
PASS = "PASS"
WARN = "WARN"
FAIL = "FAIL"
INFO = "INFO"
def print_header(title):
"""打印检查标题"""
print(f"\n{Colors.HEADER}{Colors.BOLD}{'=' * 80}{Colors.ENDC}")
print(f"{Colors.HEADER}{Colors.BOLD}{title.center(80)}{Colors.ENDC}")
print(f"{Colors.HEADER}{Colors.BOLD}{'=' * 80}{Colors.ENDC}\n")
def print_result(check_name, result, message, details=None):
"""打印检查结果"""
if result == CheckResult.PASS:
status = f"{Colors.GREEN}[{result}]{Colors.ENDC}"
elif result == CheckResult.WARN:
status = f"{Colors.YELLOW}[{result}]{Colors.ENDC}"
elif result == CheckResult.FAIL:
status = f"{Colors.RED}[{result}]{Colors.ENDC}"
else:
status = f"{Colors.BLUE}[{result}]{Colors.ENDC}"
print(f"{status} {check_name}: {message}")
if details:
print(f" {Colors.BLUE}Details:{Colors.ENDC} {details}")
def check_python_version():
"""检查Python版本"""
required_version = (3, 7)
current_version = sys.version_info
if current_version >= required_version:
result = CheckResult.PASS
message = f"Python版本 {sys.version.split()[0]} 满足要求 (最低要求: {required_version[0]}.{required_version[1]})"
else:
result = CheckResult.FAIL
message = f"Python版本 {sys.version.split()[0]} 低于要求的最低版本 {required_version[0]}.{required_version[1]}"
print_result("Python版本", result, message)
return result == CheckResult.PASS
def check_os():
"""检查操作系统"""
system = platform.system()
release = platform.release()
result = CheckResult.INFO
message = f"操作系统: {system} {release}"
if system.lower() == "windows":
details = "Windows环境需要特殊配置请确保已正确设置local_settings.py"
else:
details = "Linux环境适合生产部署"
print_result("操作系统", result, message, details)
return True
def check_port_availability(host='0.0.0.0', ports=[25, 5000]):
"""检查端口是否可用"""
all_available = True
for port in ports:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((host, port))
sock.close()
result = CheckResult.PASS
message = f"端口 {port} 可用"
details = None
except socket.error as e:
all_available = False
result = CheckResult.FAIL
message = f"端口 {port} 已被占用或无权限访问"
details = f"错误: {str(e)}"
# 对于Windows环境和低端口提供额外提示
if platform.system().lower() == "windows" and port < 1024:
details += "\n Windows环境下需要管理员权限才能使用低端口或者使用大于1024的端口"
print_result(f"端口 {port}", result, message, details)
return all_available
def check_dependencies():
"""检查依赖库是否已安装"""
required_packages = [
"flask", "python-dotenv", "aiosmtpd", "sqlalchemy",
"pydantic", "requests", "pytz", "email-validator",
"psutil"
]
missing_packages = []
for package in required_packages:
try:
importlib.import_module(package.replace("-", "_"))
result = CheckResult.PASS
message = f"已安装"
except ImportError:
missing_packages.append(package)
result = CheckResult.FAIL
message = f"未安装"
print_result(f"依赖库 {package}", result, message)
if missing_packages:
print(f"\n{Colors.YELLOW}缺少以下依赖库,可以使用以下命令安装:{Colors.ENDC}")
print(f"pip install {' '.join(missing_packages)}")
return len(missing_packages) == 0
def check_network():
"""检查网络连接"""
try:
# 尝试连接到一个外部DNS服务器
socket.create_connection(("8.8.8.8", 53), timeout=5)
result = CheckResult.PASS
message = "网络连接正常"
except socket.error:
result = CheckResult.WARN
message = "无法连接到互联网,可能会影响部分功能"
print_result("网络连接", result, message)
return result == CheckResult.PASS
def check_disk_space():
"""检查磁盘空间"""
# 获取当前脚本所在目录的可用空间
if platform.system().lower() == "windows":
try:
import ctypes
free_bytes = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(
ctypes.c_wchar_p(os.getcwd()),
None, None,
ctypes.pointer(free_bytes)
)
free_space = free_bytes.value / (1024 * 1024 * 1024) # GB
except:
free_space = None
else:
try:
st = os.statvfs(os.getcwd())
free_space = (st.f_bavail * st.f_frsize) / (1024 * 1024 * 1024) # GB
except:
free_space = None
if free_space is not None:
if free_space > 5:
result = CheckResult.PASS
message = f"磁盘空间充足: {free_space:.2f} GB可用"
elif free_space > 1:
result = CheckResult.WARN
message = f"磁盘空间有限: {free_space:.2f} GB可用"
else:
result = CheckResult.FAIL
message = f"磁盘空间不足: {free_space:.2f} GB可用"
else:
result = CheckResult.INFO
message = "无法检查磁盘空间"
print_result("磁盘空间", result, message)
return result != CheckResult.FAIL
def check_permissions():
"""检查文件和目录权限"""
# 检查当前目录是否可写
try:
test_file = os.path.join(os.getcwd(), ".permission_test")
with open(test_file, "w") as f:
f.write("test")
os.remove(test_file)
result = CheckResult.PASS
message = "当前目录可写"
except:
result = CheckResult.FAIL
message = "当前目录不可写,可能影响系统运行"
print_result("文件权限", result, message)
return result == CheckResult.PASS
def check_firewall():
"""检查防火墙状态"""
result = CheckResult.INFO
if platform.system().lower() == "linux":
# 检查常见的Linux防火墙
if shutil.which("ufw"):
try:
output = subprocess.check_output(["sudo", "ufw", "status"], universal_newlines=True)
if "inactive" in output.lower():
message = "UFW防火墙未启用"
else:
message = "UFW防火墙已启用请确保已开放25和5000端口"
except:
message = "无法检查UFW防火墙状态"
elif shutil.which("firewall-cmd"):
try:
subprocess.check_call(["firewall-cmd", "--state"], stdout=subprocess.DEVNULL)
message = "Firewalld防火墙已启用请确保已开放25和5000端口"
except:
message = "Firewalld防火墙未启用或无法检查状态"
else:
message = "未检测到常见防火墙,请手动确认防火墙状态"
elif platform.system().lower() == "windows":
message = "Windows环境下请确保Windows防火墙已允许Python和相关端口"
else:
message = "未知操作系统,请手动检查防火墙状态"
print_result("防火墙状态", result, message)
return True
def check_smtp_tools():
"""检查是否存在常用SMTP测试工具"""
tools = ["telnet", "nc", "curl"]
available_tools = []
for tool in tools:
if shutil.which(tool):
available_tools.append(tool)
if available_tools:
result = CheckResult.PASS
message = f"可用的网络工具: {', '.join(available_tools)}"
else:
result = CheckResult.WARN
message = "未找到常用网络工具,可能影响故障排查"
print_result("网络工具", result, message)
return True
def generate_report(checks):
"""生成检查报告"""
report = {
"timestamp": datetime.now().isoformat(),
"system": platform.system(),
"platform": platform.platform(),
"python_version": sys.version,
"checks": checks,
"summary": {
"pass": sum(1 for c in checks.values() if c["result"] == CheckResult.PASS),
"warn": sum(1 for c in checks.values() if c["result"] == CheckResult.WARN),
"fail": sum(1 for c in checks.values() if c["result"] == CheckResult.FAIL),
"total": len(checks)
}
}
with open("deployment_check_report.json", "w") as f:
json.dump(report, f, indent=2)
print(f"\n{Colors.BLUE}检查报告已保存到 deployment_check_report.json{Colors.ENDC}")
def main():
"""主函数"""
print_header("邮箱系统部署环境检查")
print(f"检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"检查目录: {os.getcwd()}")
checks = {}
# 基础环境检查
print_header("1. 基础环境检查")
checks["python_version"] = {"result": CheckResult.PASS if check_python_version() else CheckResult.FAIL}
checks["os"] = {"result": CheckResult.INFO}
check_os()
# 网络检查
print_header("2. 网络检查")
checks["network"] = {"result": CheckResult.PASS if check_network() else CheckResult.WARN}
checks["ports"] = {"result": CheckResult.PASS if check_port_availability() else CheckResult.FAIL}
checks["firewall"] = {"result": CheckResult.INFO}
check_firewall()
# 依赖检查
print_header("3. 依赖检查")
checks["dependencies"] = {"result": CheckResult.PASS if check_dependencies() else CheckResult.FAIL}
# 系统资源检查
print_header("4. 系统资源检查")
checks["disk_space"] = {"result": CheckResult.PASS if check_disk_space() else CheckResult.FAIL}
checks["permissions"] = {"result": CheckResult.PASS if check_permissions() else CheckResult.FAIL}
# 工具检查
print_header("5. 工具检查")
checks["smtp_tools"] = {"result": CheckResult.PASS if check_smtp_tools() else CheckResult.WARN}
# 总结
print_header("检查结果摘要")
passes = sum(1 for c in checks.values() if c["result"] == CheckResult.PASS)
warns = sum(1 for c in checks.values() if c["result"] == CheckResult.WARN)
fails = sum(1 for c in checks.values() if c["result"] == CheckResult.FAIL)
infos = sum(1 for c in checks.values() if c["result"] == CheckResult.INFO)
print(f"{Colors.GREEN}通过: {passes}{Colors.ENDC}")
print(f"{Colors.YELLOW}警告: {warns}{Colors.ENDC}")
print(f"{Colors.RED}失败: {fails}{Colors.ENDC}")
print(f"{Colors.BLUE}信息: {infos}{Colors.ENDC}")
print(f"总检查项: {len(checks)}")
if fails > 0:
print(f"\n{Colors.RED}系统存在{fails}项关键问题,需要解决后再部署{Colors.ENDC}")
readiness = "不适合部署"
elif warns > 0:
print(f"\n{Colors.YELLOW}系统存在{warns}项警告,建议处理后再部署{Colors.ENDC}")
readiness = "需谨慎部署"
else:
print(f"\n{Colors.GREEN}系统检查通过,可以进行部署{Colors.ENDC}")
readiness = "可以部署"
print(f"\n{Colors.BOLD}部署就绪状态: {readiness}{Colors.ENDC}")
# 生成报告
generate_report(checks)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n检查被用户中断")
except Exception as e:
print(f"\n{Colors.RED}检查过程中出错: {str(e)}{Colors.ENDC}")
import traceback
traceback.print_exc()

103
check_emails.py Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import json
import os
# 基础URL
BASE_URL = "http://localhost:5000/api"
def check_emails():
"""检查系统中的邮件"""
try:
# 获取域名列表
print("获取域名列表...")
domains_response = requests.get(f"{BASE_URL}/domains")
domains_data = domains_response.json()
# 从响应中提取domains列表
domains = domains_data.get('domains', [])
print(f"系统中的域名: {len(domains)}")
for domain in domains:
print(f"- {domain.get('name', '未知')} (ID: {domain.get('id', '未知')})")
# 获取邮箱列表
print("\n获取邮箱列表...")
mailboxes_response = requests.get(f"{BASE_URL}/mailboxes")
mailboxes_data = mailboxes_response.json()
# 从响应中提取mailboxes列表
mailboxes = mailboxes_data.get('mailboxes', [])
print(f"系统中的邮箱: {len(mailboxes)}")
if len(mailboxes) == 0:
print("还没有创建邮箱,创建一个新邮箱...")
# 获取第一个域名的ID
if domains:
domain_id = domains[0].get('id')
print(f"使用域名ID: {domain_id}")
# 创建一个新邮箱
create_data = {
"domain_id": domain_id,
"address": "user",
"password": "password123",
"description": "测试用户"
}
create_response = requests.post(f"{BASE_URL}/mailboxes", json=create_data)
print(f"创建邮箱响应状态: {create_response.status_code}")
print(f"创建邮箱响应内容: {create_response.text}")
# 再次获取邮箱列表
mailboxes_response = requests.get(f"{BASE_URL}/mailboxes")
mailboxes_data = mailboxes_response.json()
mailboxes = mailboxes_data.get('mailboxes', [])
# 遍历邮箱列表
for mailbox in mailboxes:
print(f"- {mailbox.get('address', '未知')}@{mailbox.get('domain_name', '?')} (ID: {mailbox.get('id', '未知')})")
# 获取该邮箱的邮件
mailbox_id = mailbox.get('id')
if mailbox_id:
print(f" 获取邮箱ID={mailbox_id}的邮件...")
emails_response = requests.get(f"{BASE_URL}/mailboxes/{mailbox_id}/emails")
emails_data = emails_response.json()
# 从响应中提取emails列表
emails = emails_data.get('emails', [])
print(f" 邮件数量: {len(emails)}")
for email in emails:
print(f" - 主题: {email.get('subject', '无主题')}")
print(f" 从: {email.get('from_addr', '未知')}")
print(f" 发送时间: {email.get('created_at', '未知')}")
print(f" ID: {email.get('id', '未知')}")
if not emails:
print(" 无邮件")
# 检查存储目录
email_data_dir = os.path.join(os.getcwd(), 'email_data')
if os.path.exists(email_data_dir):
print(f"\n检查存储目录: {email_data_dir}")
file_count = 0
for root, dirs, files in os.walk(email_data_dir):
for file in files:
if file.endswith('.eml'):
print(f"- 发现邮件文件: {os.path.join(root, file)}")
file_count += 1
if file_count == 0:
print("存储目录中没有找到.eml文件")
else:
print(f"\n存储目录不存在: {email_data_dir}")
except Exception as e:
print(f"检查失败: {str(e)}")
if __name__ == "__main__":
check_emails()

62
check_server.py Normal file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import requests
import sys
import traceback
def check_port_open(host, port):
"""检查指定端口是否开放"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
s.shutdown(socket.SHUT_RDWR)
return True
except Exception as e:
return False
finally:
s.close()
def check_api_status():
"""检查API服务器状态"""
try:
response = requests.get("http://localhost:5000/api/status")
return response.status_code == 200, response.text
except Exception as e:
return False, str(e)
def main():
print("检查服务器状态...")
# 检查HTTP服务器端口
http_port_open = check_port_open("localhost", 5000)
print(f"HTTP服务器端口 (5000) 状态: {'开放' if http_port_open else '关闭'}")
# 检查SMTP服务器端口
smtp_port_open = check_port_open("localhost", 3825)
print(f"SMTP服务器端口 (3825) 状态: {'开放' if smtp_port_open else '关闭'}")
# 检查API状态
if http_port_open:
api_ok, api_response = check_api_status()
print(f"API状态: {'正常' if api_ok else '异常'}")
print(f"API响应: {api_response}")
# 总结状态
if http_port_open and smtp_port_open:
print("\n服务器状态: 正常运行")
return 0
else:
print("\n服务器状态: 异常")
print("请确保服务器已启动并且端口未被占用。")
print("启动命令: python run.py --port 5000 --smtp-port 3825 --debug")
return 1
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as e:
print(f"检查过程发生错误: {str(e)}")
traceback.print_exc()
sys.exit(1)

102
check_specific_mailbox.py Normal file
View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import json
import os
import html
import re
# 基础URL
BASE_URL = "http://localhost:5000/api"
def check_mailbox_emails(mailbox_id):
"""检查特定邮箱的邮件"""
try:
# 获取邮箱信息
mailbox_response = requests.get(f"{BASE_URL}/mailboxes/{mailbox_id}")
mailbox_data = mailbox_response.json()
if not mailbox_data.get('success', False):
print(f"获取邮箱信息失败: {mailbox_data.get('error', '未知错误')}")
return
mailbox = mailbox_data.get('mailbox', {})
print(f"邮箱信息: {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
# 获取该邮箱的邮件
emails_response = requests.get(f"{BASE_URL}/mailboxes/{mailbox_id}/emails")
emails_data = emails_response.json()
if not emails_data.get('success', False):
print(f"获取邮件失败: {emails_data.get('error', '未知错误')}")
return
# 从响应中提取emails列表
emails = emails_data.get('emails', [])
print(f"邮件数量: {len(emails)}")
# 遍历显示邮件内容
for i, email in enumerate(emails, 1):
print(f"\n--- 邮件 {i} ---")
print(f"ID: {email.get('id', '未知')}")
print(f"主题: {email.get('subject', '无主题')}")
print(f"发件人: {email.get('from_addr', '未知')}")
print(f"接收时间: {email.get('created_at', '未知')}")
# 获取邮件详情
email_id = email.get('id')
if email_id:
# 打印完整的邮件信息,包括所有字段
print("\n邮件详细信息:")
for key, value in email.items():
print(f" {key}: {value}")
# 尝试提取验证码直接从邮件主题或内容
email_subject = email.get('subject', '')
email_body_text = email.get('body_text', '')
email_body_html = email.get('body_html', '')
print("\n邮件正文:")
if email_body_text:
print(f"--- 文本内容 ---\n{email_body_text[:200]}...")
if email_body_html:
print(f"--- HTML内容 ---\n{email_body_html[:200]}...")
# 尝试从原始内容中提取验证码
verification_code = None
# 从HTML内容中查找
if email_body_html:
code_match = re.search(r'letter-spacing: 5px[^>]*>([^<]+)<', email_body_html)
if code_match:
verification_code = code_match.group(1).strip()
# 从文本内容中查找6位数字
if not verification_code and email_body_text:
code_match = re.search(r'\b(\d{6})\b', email_body_text)
if code_match:
verification_code = code_match.group(1)
if verification_code:
print(f"\n提取到的验证码: {verification_code}")
else:
print("\n未能提取到验证码")
print("-------------------")
if not emails:
print("此邮箱没有邮件")
except Exception as e:
print(f"查询失败: {str(e)}")
def main():
# 邮箱ID - testaa@nosqli.com 的ID应该是2
mailbox_id = 2
print(f"检查邮箱ID {mailbox_id} 的邮件...")
check_mailbox_emails(mailbox_id)
if __name__ == "__main__":
main()

79
config.py Normal file
View File

@@ -0,0 +1,79 @@
import os
import secrets
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
class BaseConfig:
"""基础配置类"""
# 应用设置
SECRET_KEY = os.environ.get('SECRET_KEY') or secrets.token_hex(16)
DEBUG = False
# 数据库设置
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///email_system.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
# SMTP服务器设置
SMTP_HOST = '0.0.0.0'
SMTP_PORT = 25
# 存储设置
MAIL_STORAGE_PATH = os.path.join(os.getcwd(), 'email_data')
# 日志设置
LOG_LEVEL = 'INFO'
LOG_FILE = os.path.join(os.getcwd(), 'logs', 'email_system.log')
# API设置
API_RATE_LIMIT = '200/hour'
API_TOKEN_EXPIRE = 3600 * 24 * 7 # 7天
# 邮件域名配置
MAIL_DOMAINS = os.environ.get('MAIL_DOMAINS', 'example.com,mail.example.com').split(',')
class DevelopmentConfig(BaseConfig):
"""开发环境配置"""
DEBUG = True
LOG_LEVEL = 'DEBUG'
# 使用内存数据库方便开发
# SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
class TestingConfig(BaseConfig):
"""测试环境配置"""
TESTING = True
DEBUG = True
# 使用内存数据库进行测试
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
# 使用随机端口进行测试
SMTP_PORT = 2525
class ProductionConfig(BaseConfig):
"""生产环境配置"""
DEBUG = False
LOG_LEVEL = 'ERROR'
# SMTP服务器设置
SMTP_PORT = 25
# API设置
API_RATE_LIMIT = '100/hour'
# 配置映射
config_by_name = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
# 根据环境变量选择配置
active_config = config_by_name.get(os.environ.get('FLASK_ENV', 'default'), DevelopmentConfig)

80
create_test_domain.py Normal file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import json
# 基础URL
BASE_URL = "http://localhost:5000/api"
def create_domain(name, description):
"""创建一个新域名"""
print(f"创建域名: {name}")
url = f"{BASE_URL}/domains"
data = {
"name": name,
"description": description
}
try:
response = requests.post(url, json=data)
print(f"状态码: {response.status_code}")
print(f"响应内容: {response.text}")
response.raise_for_status()
result = response.json()
print(f"域名创建成功: {result}")
return result.get("domain", {})
except Exception as e:
print(f"域名创建失败: {str(e)}")
return None
def create_mailbox(domain_id, address, description=""):
"""在指定域名下创建邮箱"""
print(f"在域 {domain_id} 下创建邮箱: {address}")
url = f"{BASE_URL}/mailboxes"
data = {
"domain_id": domain_id,
"address": address,
"description": description
}
try:
response = requests.post(url, json=data)
print(f"状态码: {response.status_code}")
print(f"响应内容: {response.text}")
response.raise_for_status()
result = response.json()
print(f"邮箱创建成功: {result}")
return result.get("mailbox", {})
except Exception as e:
print(f"邮箱创建失败: {str(e)}")
return None
def main():
# 检查API状态
try:
status_response = requests.get(f"{BASE_URL}/status")
print(f"API状态: {status_response.status_code}")
print(f"API响应: {status_response.text[:200]}...")
except Exception as e:
print(f"API状态检查失败: {str(e)}")
# 创建测试域名 nosqli.com
domain = create_domain("nosqli.com", "测试域名")
if not domain:
print("无法继续,域名创建失败")
return
# 获取域名ID
domain_id = domain.get("id")
# 创建测试邮箱 testaa@nosqli.com
mailbox = create_mailbox(domain_id, "testaa", "测试用户")
print("\n测试环境准备完成:")
print(f"- 域名: nosqli.com (ID: {domain_id})")
if mailbox:
print(f"- 邮箱: testaa@nosqli.com (ID: {mailbox.get('id')})")
if __name__ == "__main__":
main()

1
deploy_email_system.sh Normal file
View File

@@ -0,0 +1 @@

248
deploy_production.sh Normal file
View File

@@ -0,0 +1,248 @@
#!/bin/bash
#
# 邮箱系统生产环境部署和启动脚本
# 使用前请确保已经阅读DEPLOYMENT_GUIDE.md和DNS_CONFIGURATION.md文档
#
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # 无颜色
# 显示标题
echo -e "${BLUE}=======================================================${NC}"
echo -e "${BLUE} 邮箱系统生产环境部署脚本 ${NC}"
echo -e "${BLUE}=======================================================${NC}"
echo ""
# 检查是否为root用户
if [ "$EUID" -ne 0 ]; then
echo -e "${YELLOW}警告: 此脚本未以root用户运行可能无法绑定25端口${NC}"
read -p "是否继续? (y/n): " continue_without_root
if [ "$continue_without_root" != "y" ]; then
echo "退出部署"
exit 1
fi
fi
# 检查Python版本
echo -e "${BLUE}[1/7]${NC} 检查Python版本..."
if command -v python3 &>/dev/null; then
python_version=$(python3 --version)
echo -e "${GREEN}检测到 $python_version${NC}"
# 检查Python版本是否符合要求 (>= 3.7)
python_major=$(python3 -c "import sys; print(sys.version_info.major)")
python_minor=$(python3 -c "import sys; print(sys.version_info.minor)")
if [ "$python_major" -ge 3 ] && [ "$python_minor" -ge 7 ]; then
echo -e "${GREEN}Python版本满足要求${NC}"
else
echo -e "${RED}错误: 需要Python 3.7或更高版本${NC}"
exit 1
fi
else
echo -e "${RED}错误: 未找到Python 3${NC}"
echo "请安装Python 3.7或更高版本"
exit 1
fi
# 检查必要的目录
echo -e "${BLUE}[2/7]${NC} 创建必要的目录..."
mkdir -p logs email_data db
echo -e "${GREEN}目录创建完成${NC}"
# 检查依赖
echo -e "${BLUE}[3/7]${NC} 检查并安装依赖..."
if [ -f "requirements.txt" ]; then
pip3 install --no-cache-dir -r requirements.txt
if [ $? -eq 0 ]; then
echo -e "${GREEN}依赖安装成功${NC}"
else
echo -e "${RED}依赖安装失败${NC}"
exit 1
fi
else
echo -e "${RED}错误: requirements.txt文件不存在${NC}"
exit 1
fi
# 设置配置文件
echo -e "${BLUE}[4/7]${NC} 配置系统..."
# 创建.env文件
if [ ! -f ".env" ]; then
echo "创建.env文件..."
# 生成随机密钥
SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(16))')
read -p "请输入邮件域名,多个域名用逗号分隔 (默认: example.com): " mail_domains
mail_domains=${mail_domains:-example.com}
cat > .env << EOF
FLASK_ENV=production
SECRET_KEY=${SECRET_KEY}
MAIL_DOMAINS=${mail_domains}
DATABASE_URL=sqlite:///db/email_system.db
EOF
echo -e "${GREEN}.env文件创建成功${NC}"
else
echo -e "${YELLOW}注意: .env文件已存在跳过创建${NC}"
fi
# 设置生产环境配置
if [ -f "production_local_settings.py" ]; then
if [ -f "local_settings.py" ]; then
echo "备份现有local_settings.py..."
mv local_settings.py local_settings.py.bak
fi
echo "复制生产环境配置..."
cp production_local_settings.py local_settings.py
echo -e "${GREEN}生产环境配置设置完成${NC}"
else
echo -e "${YELLOW}警告: production_local_settings.py不存在无法设置生产环境配置${NC}"
fi
# 检查端口
echo -e "${BLUE}[5/7]${NC} 检查系统端口..."
# 检查25端口
nc -z -w5 127.0.0.1 25 &>/dev/null
if [ $? -eq 0 ]; then
echo -e "${YELLOW}警告: 端口25已被占用${NC}"
echo "要使用标准SMTP端口25您需要停止现有服务或修改配置使用其他端口"
read -p "是否使用备用端口3825? (y/n): " use_alt_port
if [ "$use_alt_port" = "y" ]; then
# 修改local_settings.py中的SMTP端口
sed -i 's/SMTP_PORT = 25/SMTP_PORT = 3825/g' local_settings.py
echo -e "${GREEN}已修改配置使用端口3825${NC}"
smtp_port=3825
else
echo "请手动修改local_settings.py中的SMTP_PORT配置"
smtp_port=25
fi
else
echo -e "${GREEN}端口25可用${NC}"
smtp_port=25
fi
# 检查5000端口
nc -z -w5 127.0.0.1 5000 &>/dev/null
if [ $? -eq 0 ]; then
echo -e "${YELLOW}警告: 端口5000已被占用${NC}"
echo "请确保端口5000未被其他服务使用"
read -p "是否使用备用端口5001? (y/n): " use_alt_api_port
if [ "$use_alt_api_port" = "y" ]; then
api_port=5001
else
echo "继续使用端口5000可能导致启动失败"
api_port=5000
fi
else
echo -e "${GREEN}端口5000可用${NC}"
api_port=5000
fi
# 初始化数据库
echo -e "${BLUE}[6/7]${NC} 初始化数据库..."
python3 -c "from app.models import init_db; init_db()"
if [ $? -eq 0 ]; then
echo -e "${GREEN}数据库初始化成功${NC}"
else
echo -e "${RED}数据库初始化失败${NC}"
exit 1
fi
# 创建systemd服务文件
echo -e "${BLUE}[7/7]${NC} 创建系统服务..."
if [ "$EUID" -eq 0 ]; then
# 获取当前工作目录的绝对路径
WORKING_DIR=$(pwd)
cat > /etc/systemd/system/email-system.service << EOF
[Unit]
Description=Email System Service
After=network.target
[Service]
User=root
WorkingDirectory=${WORKING_DIR}
ExecStart=$(which python3) run.py --host 0.0.0.0 --port ${api_port} --smtp-port ${smtp_port}
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable email-system
echo -e "${GREEN}系统服务创建并启用成功${NC}"
else
echo -e "${YELLOW}注意: 未以root用户运行跳过创建系统服务${NC}"
echo "您可以使用以下命令手动创建系统服务:"
echo "sudo bash -c 'cat > /etc/systemd/system/email-system.service << EOF"
echo "[Unit]"
echo "Description=Email System Service"
echo "After=network.target"
echo ""
echo "[Service]"
echo "User=root"
echo "WorkingDirectory=$(pwd)"
echo "ExecStart=$(which python3) run.py --host 0.0.0.0 --port ${api_port} --smtp-port ${smtp_port}"
echo "Restart=always"
echo "RestartSec=10"
echo ""
echo "[Install]"
echo "WantedBy=multi-user.target"
echo "EOF'"
echo "sudo systemctl daemon-reload"
echo "sudo systemctl enable email-system"
fi
# 部署总结
echo ""
echo -e "${BLUE}=======================================================${NC}"
echo -e "${BLUE} 部署完成 ${NC}"
echo -e "${BLUE}=======================================================${NC}"
echo ""
echo -e "API服务端口: ${GREEN}${api_port}${NC}"
echo -e "SMTP服务端口: ${GREEN}${smtp_port}${NC}"
echo -e "工作目录: ${GREEN}$(pwd)${NC}"
echo -e "日志文件: ${GREEN}$(pwd)/logs/email_system.log${NC}"
echo ""
# 启动服务
if [ "$EUID" -eq 0 ]; then
echo -e "${BLUE}正在启动邮箱系统服务...${NC}"
systemctl start email-system
sleep 2
systemctl status email-system --no-pager
echo ""
echo -e "${GREEN}邮箱系统已启动并运行${NC}"
echo "您可以使用以下命令管理服务:"
echo " 查看状态: sudo systemctl status email-system"
echo " 停止服务: sudo systemctl stop email-system"
echo " 重启服务: sudo systemctl restart email-system"
echo " 查看日志: sudo journalctl -u email-system -f"
else
echo -e "${YELLOW}服务未启动,您可以使用以下命令手动启动服务:${NC}"
echo "sudo systemctl start email-system"
echo ""
echo "或者直接运行应用:"
echo "python3 run.py --host 0.0.0.0 --port ${api_port} --smtp-port ${smtp_port}"
fi
echo ""
echo -e "${BLUE}部署后检查事项:${NC}"
echo "1. 确认您的DNS记录已正确配置 (请参考DNS_CONFIGURATION.md)"
echo "2. 验证邮件接收功能 (执行 python3 test_smtp_server.py)"
echo "3. 检查防火墙是否允许${api_port}${smtp_port}端口的流量"
echo ""
echo -e "${GREEN}如有任何问题,请查看日志文件或联系系统管理员${NC}"

49
docker-compose.yml Normal file
View File

@@ -0,0 +1,49 @@
version: '3.8'
services:
email_system:
build:
context: .
dockerfile: Dockerfile
container_name: email_system
restart: unless-stopped
ports:
- "5000:5000" # API端口
- "25:25" # SMTP端口
volumes:
- ./email_data:/app/email_data # 邮件数据持久化
- ./db:/app/db # 数据库持久化
- ./logs:/app/logs # 日志持久化
environment:
- FLASK_ENV=production
- MAIL_DOMAINS=example.com,test-domain.com
# 可以在此处设置更多环境变量
networks:
- email_network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/api/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
# 如果需要添加数据库服务,可以在此处添加
# 例如:
# db:
# image: postgres:13-alpine
# volumes:
# - postgres_data:/var/lib/postgresql/data/
# environment:
# - POSTGRES_PASSWORD=password
# - POSTGRES_USER=user
# - POSTGRES_DB=email_db
# networks:
# - email_network
networks:
email_network:
driver: bridge
# 如果使用外部数据库,添加卷定义
# volumes:
# postgres_data:

298
email_analyzer.py Normal file
View File

@@ -0,0 +1,298 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import re
import json
import logging
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger('EmailAnalyzer')
class EmailAnalyzer:
"""邮件分析类,用于操作邮箱系统并分析邮件内容"""
def __init__(self, api_base_url="http://localhost:5000/api", smtp_host="localhost", smtp_port=3825):
"""初始化邮件分析器"""
self.api_base_url = api_base_url
self.smtp_host = smtp_host
self.smtp_port = smtp_port
logger.info(f"初始化邮件分析器: API={api_base_url}, SMTP={smtp_host}:{smtp_port}")
# API相关方法
def get_all_domains(self):
"""获取所有域名"""
try:
response = requests.get(f"{self.api_base_url}/domains")
if response.status_code == 200:
domains = response.json().get('domains', [])
logger.info(f"获取到 {len(domains)} 个域名")
return domains
else:
logger.error(f"获取域名失败: HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取域名出错: {str(e)}")
return []
def create_domain(self, domain_name, description=""):
"""创建新域名"""
try:
data = {
"name": domain_name,
"description": description
}
response = requests.post(f"{self.api_base_url}/domains", json=data)
if response.status_code in (200, 201):
result = response.json()
domain = result.get("domain", {})
logger.info(f"成功创建域名: {domain_name} (ID: {domain.get('id')})")
return domain
else:
logger.error(f"创建域名失败: HTTP {response.status_code} - {response.text}")
return None
except Exception as e:
logger.error(f"创建域名出错: {str(e)}")
return None
def get_all_mailboxes(self):
"""获取所有邮箱"""
try:
response = requests.get(f"{self.api_base_url}/mailboxes")
if response.status_code == 200:
mailboxes = response.json().get('mailboxes', [])
logger.info(f"获取到 {len(mailboxes)} 个邮箱")
return mailboxes
else:
logger.error(f"获取邮箱失败: HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取邮箱出错: {str(e)}")
return []
def create_mailbox(self, domain_id, address):
"""创建新邮箱"""
try:
data = {
"domain_id": domain_id,
"address": address
}
response = requests.post(f"{self.api_base_url}/mailboxes", json=data)
if response.status_code in (200, 201):
result = response.json()
mailbox = result.get("mailbox", {})
logger.info(f"成功创建邮箱: {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
return mailbox
else:
logger.error(f"创建邮箱失败: HTTP {response.status_code} - {response.text}")
return None
except Exception as e:
logger.error(f"创建邮箱出错: {str(e)}")
return None
def get_mailbox_emails(self, mailbox_id):
"""获取指定邮箱的所有邮件"""
try:
response = requests.get(f"{self.api_base_url}/mailboxes/{mailbox_id}/emails")
if response.status_code == 200:
emails = response.json().get('emails', [])
logger.info(f"获取到邮箱ID={mailbox_id}{len(emails)} 封邮件")
return emails
else:
logger.error(f"获取邮件失败: HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取邮件出错: {str(e)}")
return []
def get_email_detail(self, email_id):
"""获取指定邮件的详细信息"""
try:
response = requests.get(f"{self.api_base_url}/emails/{email_id}")
if response.status_code == 200:
email = response.json().get('email', {})
logger.info(f"获取到邮件ID={email_id}的详细信息")
return email
else:
logger.error(f"获取邮件详情失败: HTTP {response.status_code}")
return {}
except Exception as e:
logger.error(f"获取邮件详情出错: {str(e)}")
return {}
# SMTP相关方法
def send_email(self, from_addr, to_addr, subject, body_text="", body_html=""):
"""发送邮件"""
try:
# 创建邮件
msg = MIMEMultipart('alternative')
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject
# 添加文本内容
if body_text:
msg.attach(MIMEText(body_text, 'plain'))
# 添加HTML内容
if body_html:
msg.attach(MIMEText(body_html, 'html'))
# 连接SMTP服务器并发送
server = smtplib.SMTP(self.smtp_host, self.smtp_port)
server.set_debuglevel(1) # 开启调试
server.sendmail(from_addr, to_addr, msg.as_string())
server.quit()
logger.info(f"成功发送邮件: {from_addr} -> {to_addr}, 主题: {subject}")
return True
except Exception as e:
logger.error(f"发送邮件失败: {str(e)}")
return False
def send_verification_code(self, to_addr, code_length=6):
"""发送验证码邮件"""
# 生成随机验证码
import random
import string
code = ''.join(random.choices(string.digits, k=code_length))
# 邮件主题
subject = f'您的验证码 - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
# 邮件HTML内容
html = f'''
<html>
<body>
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 5px;">
<h2 style="color: #333;">验证码</h2>
<p>您好!</p>
<p>您的验证码是:</p>
<div style="background-color: #f7f7f7; padding: 15px; font-size: 24px; font-weight: bold; text-align: center; letter-spacing: 5px; margin: 20px 0; border-radius: 4px;">
{code}
</div>
<p>此验证码将在30分钟内有效。</p>
<p>如果这不是您请求的,请忽略此邮件。</p>
<p>谢谢!</p>
<div style="margin-top: 30px; padding-top: 15px; border-top: 1px solid #eee; font-size: 12px; color: #999;">
此邮件由系统自动发送,请勿回复。
</div>
</div>
</body>
</html>
'''
# 发送邮件
result = self.send_email('noreply@system.com', to_addr, subject, body_html=html)
return result, code
# 邮件分析方法
def extract_verification_code(self, email):
"""从邮件中提取验证码"""
# 如果邮件对象已经包含验证码字段,直接返回
if email.get('verification_code'):
return email.get('verification_code')
# 尝试从HTML内容中提取
body_html = email.get('body_html', '')
if body_html:
# 尝试匹配验证码样式
code_match = re.search(r'letter-spacing: 5px[^>]*>([^<]+)<', body_html)
if code_match:
return code_match.group(1).strip()
# 尝试从文本内容中提取6位数字验证码
body_text = email.get('body_text', '')
if body_text:
code_match = re.search(r'\b(\d{6})\b', body_text)
if code_match:
return code_match.group(1)
# 无法提取到验证码
return None
def extract_verification_link(self, email):
"""从邮件中提取验证链接"""
# 如果邮件对象已经包含验证链接字段,直接返回
if email.get('verification_link'):
return email.get('verification_link')
# 尝试从HTML内容中提取链接
body_html = email.get('body_html', '')
if body_html:
link_match = re.search(r'href=[\'"]([^\'"]*verify[^\'"]*)[\'"]', body_html)
if link_match:
return link_match.group(1)
# 尝试从文本内容中提取链接
body_text = email.get('body_text', '')
if body_text:
link_match = re.search(r'https?://\S+?(?:verify|confirm|activate)\S+', body_text)
if link_match:
return link_match.group(0)
# 无法提取到验证链接
return None
# 使用示例
def main():
# 创建邮件分析器实例
analyzer = EmailAnalyzer()
# 获取所有域名
domains = analyzer.get_all_domains()
for domain in domains:
print(f"域名: {domain.get('name')} (ID: {domain.get('id')})")
# 如果没有域名,创建一个
if not domains:
domain = analyzer.create_domain("test-domain.com", "测试域名")
print(f"创建域名: {domain.get('name')} (ID: {domain.get('id')}")
else:
domain = domains[0]
# 获取所有邮箱
mailboxes = analyzer.get_all_mailboxes()
for mailbox in mailboxes:
print(f"邮箱: {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
# 如果没有邮箱,创建一个
if not mailboxes:
mailbox = analyzer.create_mailbox(domain.get('id'), "test-user")
print(f"创建邮箱: {mailbox.get('full_address')} (ID: {mailbox.get('id')}")
else:
mailbox = mailboxes[0]
# 发送一封测试邮件
to_addr = mailbox.get('full_address')
sent, code = analyzer.send_verification_code(to_addr)
print(f"验证码邮件发送{'成功' if sent else '失败'}, 验证码: {code}")
# 等待2秒让邮件被处理
import time
time.sleep(2)
# 获取邮箱中的邮件
mailbox_id = mailbox.get('id')
emails = analyzer.get_mailbox_emails(mailbox_id)
print(f"获取到 {len(emails)} 封邮件")
# 分析最新的邮件
if emails:
latest_email = emails[0] # 假设按时间降序排列
print(f"最新邮件: {latest_email.get('subject')}")
verification_code = analyzer.extract_verification_code(latest_email)
print(f"提取到的验证码: {verification_code}")
verification_link = analyzer.extract_verification_link(latest_email)
print(f"提取到的验证链接: {verification_link}")
if __name__ == "__main__":
main()

21
get_code.py Normal file
View File

@@ -0,0 +1,21 @@
import requests
# 获取邮箱ID为2的所有邮件
response = requests.get("http://localhost:5000/api/mailboxes/2/emails")
data = response.json()
# 打印响应状态
print(f"API响应状态: {response.status_code}")
print(f"API响应内容: {data}")
# 如果成功,提取验证码
if data.get('success', False):
emails = data.get('emails', [])
for email in emails:
print(f"\n邮件ID: {email.get('id')}")
print(f"主题: {email.get('subject')}")
print(f"发件人: {email.get('sender')}")
print(f"接收时间: {email.get('received_at')}")
print(f"验证码字段: {email.get('verification_code')}")
else:
print(f"获取邮件失败: {data.get('error', '未知错误')}")

81
get_full_email.py Normal file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import json
from pprint import pprint
def get_all_domains():
"""获取所有域名"""
response = requests.get("http://localhost:5000/api/domains")
if response.status_code == 200:
return response.json().get('domains', [])
return []
def get_all_mailboxes():
"""获取所有邮箱"""
response = requests.get("http://localhost:5000/api/mailboxes")
if response.status_code == 200:
return response.json().get('mailboxes', [])
return []
def get_mailbox_emails(mailbox_id):
"""获取指定邮箱的所有邮件"""
response = requests.get(f"http://localhost:5000/api/mailboxes/{mailbox_id}/emails")
print(f"API响应状态码: {response.status_code}")
if response.status_code == 200:
return response.json().get('emails', [])
return []
def get_email_detail(email_id):
"""获取指定邮件的详细信息"""
response = requests.get(f"http://localhost:5000/api/emails/{email_id}")
if response.status_code == 200:
return response.json().get('email', {})
return {}
def main():
# 获取所有域名
print("获取所有域名...")
domains = get_all_domains()
print(f"找到 {len(domains)} 个域名:")
for domain in domains:
print(f" - {domain.get('name')} (ID: {domain.get('id')})")
# 获取所有邮箱
print("\n获取所有邮箱...")
mailboxes = get_all_mailboxes()
print(f"找到 {len(mailboxes)} 个邮箱:")
for mailbox in mailboxes:
print(f" - {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
# 遍历所有邮箱获取邮件
for mailbox in mailboxes:
mailbox_id = mailbox.get('id')
mailbox_address = mailbox.get('full_address')
print(f"\n获取邮箱 {mailbox_address} (ID: {mailbox_id}) 的邮件...")
emails = get_mailbox_emails(mailbox_id)
print(f"找到 {len(emails)} 封邮件")
for index, email in enumerate(emails, 1):
email_id = email.get('id')
print(f"\n--- 邮件 {index} (ID: {email_id}) ---")
print(f"主题: {email.get('subject')}")
print(f"发件人: {email.get('sender')}")
print(f"接收时间: {email.get('received_at')}")
print("\n完整邮件信息:")
# 打印所有字段
for key, value in email.items():
print(f" - {key}: {value}")
# 获取邮件验证码相关字段
verification_code = email.get('verification_code')
if verification_code:
print(f"\n提取到的验证码: {verification_code}")
print("-" * 50)
if __name__ == "__main__":
main()

214
guihua.doc Normal file
View File

@@ -0,0 +1,214 @@
用户要求我们规划系统的基本结构,这与之前我们已经分析和完善的系统是一致的。我们已经查看了现有代码,发现系统已经有了基本的框架,包括:
SMTP服务器使用aiosmtpd
数据库模型使用SQLAlchemy
Web界面和API使用Flask
配置文件和启动脚本
让我回顾一下已经存在的文件和目录,然后规划一个更完整的结构:
已有结构:
app/
init_.py
api/
init_.py
routes.py
domain_routes.py
email_routes.py
mailbox_routes.py
models/
init_.py
domain.py
email.py
mailbox.py
attachment.py
services/
init_.py
smtp_server.py
email_processor.py
mail_store.py
config.py
run.py
requirements.txt
Dockerfile
docker-compose.yml
start.sh
start_windows.ps1
init_1panel.sh
现在需要规划一个更完整的结构,包括:
更完善的目录结构
SMTP服务器设计
数据库模型设计
Web界面和API设计
5. 配置和启动脚本
我将按照用户的要求,规划一个完整的系统结构。
</think>
我将帮您规划这个邮箱系统的完整结构。根据之前的分析,系统已有基本框架,我们可以进一步规划和完善
emailsystem/
├── app/ # 应用主目录
│ ├── __init__.py # 应用初始化
│ ├── api/ # API接口
│ │ ├── __init__.py
│ │ ├── routes.py # 主路由
│ │ ├── domain_routes.py # 域名管理
│ │ ├── email_routes.py # 邮件管理
│ │ └── mailbox_routes.py # 邮箱管理
│ ├── models/ # 数据库模型
│ │ ├── __init__.py
│ │ ├── domain.py # 域名模型
│ │ ├── email.py # 邮件模型
│ │ ├── mailbox.py # 邮箱模型
│ │ └── attachment.py # 附件模型
│ ├── services/ # 核心服务
│ │ ├── __init__.py
│ │ ├── smtp_server.py # SMTP服务器
│ │ ├── email_processor.py # 邮件处理器
│ │ └── mail_store.py # 邮件存储
│ ├── static/ # 静态资源
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── templates/ # 模板文件
│ │ ├── base.html # 基础模板
│ │ ├── index.html # 首页
│ │ ├── dashboard.html # 控制面板
│ │ └── mailbox/ # 邮箱相关页面
│ └── utils/ # 工具函数
│ ├── __init__.py
│ ├── validators.py # 验证工具
│ └── extractors.py # 信息提取工具
├── db/ # 数据库文件
├── email_data/ # 邮件存储目录
├── logs/ # 日志目录
├── tests/ # 测试目录
├── .env # 环境变量
├── config.py # 配置文件
├── requirements.txt # 依赖列表
├── run.py # 主启动文件
├── Dockerfile # Docker构建文件
├── docker-compose.yml # Docker Compose配置
├── start.sh # Linux启动脚本
├── start_windows.ps1 # Windows启动脚本
└── init_1panel.sh # 1Panel初始化脚本
2. SMTP服务器设计 (aiosmtpd)
SMTP服务器负责接收邮件并存储到系统中。
主要功能:
监听指定端口接收SMTP连接
验证收件人地址是否在系统中
接收并解析邮件内容
将邮件内容转发给邮件存储服务
支持多域名配置
实现方案:
使用aiosmtpd库创建异步SMTP服务器
在smtp_server.py中实现SMTPServer类
自定义处理器处理接收到的邮件
使用线程模式运行服务器,避免阻塞主进程
3. 数据库模型设计 (SQLAlchemy)
数据库设计采用SQLAlchemy ORM使用SQLite作为存储后端。
主要模型:
Domain (域名)
id: 主键
name: 域名
description: 描述
active: 是否激活
created_at: 创建时间
Mailbox (邮箱)
id: 主键
username: 用户名
domain_id: 外键关联Domain
description: 描述
active: 是否激活
created_at: 创建时间
last_accessed: 上次访问时间
Email (邮件)
id: 主键
mailbox_id: 外键关联Mailbox
sender: 发件人
subject: 主题
content_text: 文本内容
content_html: HTML内容
received_at: 接收时间
read: 是否已读
verification_links: 验证链接JSON
verification_codes: 验证码JSON
raw_data: 原始邮件数据
Attachment (附件)
id: 主键
email_id: 外键关联Email
filename: 文件名
content_type: 内容类型
size: 大小
storage_path: 存储路径
4. Web界面和API设计 (Flask)
API设计:
域名管理API
GET /api/domains: 获取所有域名
POST /api/domains: 创建新域名
GET /api/domains/<id>: 获取指定域名
PUT /api/domains/<id>/toggle: 启用/禁用域名
邮箱管理API
GET /api/mailboxes: 获取所有邮箱
POST /api/mailboxes: 创建新邮箱
POST /api/mailboxes/batch: 批量创建邮箱
GET /api/mailboxes/<id>: 获取指定邮箱
GET /api/mailboxes/<id>/emails: 获取邮箱的所有邮件
邮件管理API
GET /api/emails/<id>: 获取指定邮件
GET /api/emails/<id>/verification: 获取邮件中的验证信息
GET /api/mailboxes/<id>/poll: 轮询新邮件
4. 系统状态API
GET /api/status: 获取系统状态
Web界面设计:
1. 首页/登录页
系统概述
登录入口
控制面板
系统状态概览
最近接收邮件统计
邮箱使用情况
域名管理
域名列表
添加/编辑域名
邮箱管理
邮箱列表
创建/批量创建邮箱
邮件查看
邮件列表
邮件详情
验证码/链接提取
5. 配置和启动脚本
配置文件 (config.py):
基础配置类
开发环境配置
测试环境配置
生产环境配置
根据环境变量选择配置
环境变量 (.env):
FLASK_ENV: 运行环境
SECRET_KEY: 安全密钥
MAIL_DOMAINS: 邮件域名
DATABASE_URL: 数据库连接字符串
启动脚本:
run.py: 主启动文件
start.sh: Linux启动脚本
start_windows.ps1: Windows启动脚本
init_1panel.sh: 1Panel初始化脚本
Docker部署:
Dockerfile: 容器构建配置
docker-compose.yml: 多容器配置
实现建议
ARM优化:
减少内存使用,避免大量对象创建
异步处理耗时操作
配置适当的线程数和工作进程数
安全性考虑:
对敏感操作添加认证
定期清理过期邮件
在Docker中使用非root用户运行
性能优化:
使用连接池管理数据库连接
实现邮件缓存机制
定时任务处理耗时操作
监控与维护:
详细的日志记录
系统状态API

80
init_1panel.sh Normal file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
# 邮箱系统 1Panel 初始化脚本
# 用于在1Panel环境中首次部署系统
echo "开始初始化邮箱系统..."
# 检查是否在ARM架构上运行
ARCH=$(uname -m)
if [[ $ARCH == "aarch64" || $ARCH == "arm64" ]]; then
echo "检测到ARM架构: $ARCH"
else
echo "警告: 当前架构 $ARCH 不是ARM可能会影响性能优化"
fi
# 创建必要的目录
mkdir -p logs email_data db
echo "创建目录完成"
# 检查环境变量文件是否存在,不存在则创建
if [ ! -f .env ]; then
echo "创建.env文件..."
# 生成随机密钥
SECRET_KEY=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)
cat > .env << EOF
FLASK_ENV=production
SECRET_KEY=${SECRET_KEY}
MAIL_DOMAINS=example.com,mail.example.com
DATABASE_URL=sqlite:///db/email_system.db
EOF
echo ".env文件已创建"
fi
# 安装Python依赖
echo "安装Python依赖..."
pip install --no-cache-dir -r requirements.txt
echo "依赖安装完成"
# 初始化数据库
echo "初始化数据库..."
python -c "from app.models import init_db; init_db()"
echo "数据库初始化完成"
# 检查SMTP服务器端口是否可用
PORT_CHECK=$(netstat -ln | grep ':25 ' | wc -l)
if [ $PORT_CHECK -gt 0 ]; then
echo "警告: 端口25已被占用可能需要修改SMTP端口配置"
# 自动修改为备用端口587
sed -i 's/SMTP_PORT = 25/SMTP_PORT = 587/g' config.py
echo "已将SMTP端口更改为587"
fi
# 创建服务管理脚本
cat > email_system.service << EOF
[Unit]
Description=Email System Service
After=network.target
[Service]
User=root
WorkingDirectory=$(pwd)
ExecStart=$(which gunicorn) --bind 0.0.0.0:5000 --workers 4 run:app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
echo "服务管理脚本已创建: email_system.service"
echo "可以使用以下命令安装系统服务:"
echo "sudo cp email_system.service /etc/systemd/system/"
echo "sudo systemctl daemon-reload"
echo "sudo systemctl enable email_system.service"
echo "sudo systemctl start email_system.service"
echo "初始化完成,系统已准备就绪"
echo "可以通过以下命令启动系统:"
echo "python run.py --host 0.0.0.0 --port 5000"

25
local_settings.py Normal file
View File

@@ -0,0 +1,25 @@
"""
Windows环境下的本地设置
这个文件包含仅适用于本地Windows开发环境的设置
不会影响生产环境部署
"""
import platform
import logging
# 是否启用Windows特殊设置
IS_WINDOWS = platform.system().lower() == 'windows'
# Windows环境特定配置
if IS_WINDOWS:
# SMTP服务器配置
SMTP_HOST = '127.0.0.1' # 在Windows上使用localhost而不是0.0.0.0
SMTP_PORT = 3825 # 使用高端口避免权限问题
# 日志配置
LOG_LEVEL = 'DEBUG'
# 调试信息
logging.info(f"已加载Windows环境特殊配置: SMTP端口={SMTP_PORT}")
else:
# 非Windows环境下不做特殊处理
pass

708
monitor_email_system.py Normal file
View File

@@ -0,0 +1,708 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
邮件系统监控脚本
此脚本用于监控邮件系统的状态,包括:
- API可用性
- SMTP连接
- 磁盘使用量
- 系统资源使用情况
- 邮件队列状态
使用方法:
python monitor_email_system.py [--host HOST] [--api-port API_PORT] [--smtp-port SMTP_PORT] [--alert]
选项:
--host HOST 服务器主机名或IP地址 (默认: localhost)
--api-port API_PORT API服务端口 (默认: 5000)
--smtp-port SMTP_PORT SMTP服务端口 (默认: 25)
--alert 启用警报 (通过邮件发送)
--interval SECONDS 监控间隔,单位为秒 (默认: 300)
--log 将结果记录到日志文件
--help 显示帮助信息
"""
import os
import sys
import time
import socket
import smtplib
import argparse
import requests
import platform
import json
import logging
import datetime
import subprocess
import psutil
from pathlib import Path
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# 颜色定义(终端输出)
class Colors:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
ENDC = '\033[0m'
BOLD = '\033[1m'
# 配置日志
def setup_logging(log_enabled):
logger = logging.getLogger('email_system_monitor')
logger.setLevel(logging.INFO)
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(console_format)
logger.addHandler(console_handler)
# 文件处理器(如果启用)
if log_enabled:
log_dir = Path('logs')
log_dir.mkdir(exist_ok=True)
log_file = log_dir / 'email_system_monitor.log'
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO)
file_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_format)
logger.addHandler(file_handler)
return logger
def print_header(title):
"""打印格式化的标题"""
print(f"\n{Colors.BLUE}{Colors.BOLD}{'=' * 50}{Colors.ENDC}")
print(f"{Colors.BLUE}{Colors.BOLD}{title.center(50)}{Colors.ENDC}")
print(f"{Colors.BLUE}{Colors.BOLD}{'=' * 50}{Colors.ENDC}\n")
def print_status(name, status, message=""):
"""打印状态信息"""
if status == "OK":
status_color = f"{Colors.GREEN}OK{Colors.ENDC}"
elif status == "WARNING":
status_color = f"{Colors.YELLOW}WARNING{Colors.ENDC}"
else:
status_color = f"{Colors.RED}ERROR{Colors.ENDC}"
print(f"{name.ljust(30)}: {status_color} {message}")
return status
def check_api_status(host, port, logger):
"""检查API服务状态"""
url = f"http://{host}:{port}/api/status"
try:
response = requests.get(url, timeout=5)
if response.status_code == 200:
data = response.json()
logger.info(f"API服务正常运行 - 状态: {data.get('status', 'OK')}")
return "OK", data
else:
logger.warning(f"API服务返回非正常状态码: {response.status_code}")
return "WARNING", {"message": f"状态码: {response.status_code}"}
except requests.RequestException as e:
logger.error(f"API服务连接失败: {str(e)}")
return "ERROR", {"message": f"连接错误: {str(e)}"}
def check_smtp_status(host, port, logger):
"""检查SMTP服务状态"""
try:
with socket.create_connection((host, port), timeout=5) as sock:
# 接收欢迎消息
data = sock.recv(1024).decode('utf-8')
if data.startswith('220'):
# 发送EHLO命令
sock.sendall(b'EHLO example.com\r\n')
response = sock.recv(1024).decode('utf-8')
logger.info("SMTP服务正常运行")
return "OK", {"message": "SMTP服务响应正常", "response": response.split('\n')[0]}
else:
logger.warning(f"SMTP服务返回非标准欢迎消息: {data}")
return "WARNING", {"message": f"非标准欢迎消息: {data}"}
except (socket.timeout, socket.error, ConnectionRefusedError) as e:
logger.error(f"SMTP服务连接失败: {str(e)}")
return "ERROR", {"message": f"连接错误: {str(e)}"}
def check_disk_usage(logger):
"""检查磁盘使用情况"""
try:
# 获取当前目录磁盘使用情况
disk = psutil.disk_usage('.')
# 计算使用百分比
used_percent = disk.percent
if used_percent < 70:
status = "OK"
elif used_percent < 90:
status = "WARNING"
else:
status = "ERROR"
# 格式化大小
total_gb = disk.total / (1024**3)
used_gb = disk.used / (1024**3)
free_gb = disk.free / (1024**3)
message = f"使用率: {used_percent:.1f}% (已用: {used_gb:.1f}GB, 可用: {free_gb:.1f}GB, 总计: {total_gb:.1f}GB)"
logger.info(f"磁盘使用情况: {message}")
return status, {
"used_percent": used_percent,
"used_gb": used_gb,
"free_gb": free_gb,
"total_gb": total_gb,
"message": message
}
except Exception as e:
logger.error(f"检查磁盘使用情况时出错: {str(e)}")
return "ERROR", {"message": f"错误: {str(e)}"}
def check_email_data_size(logger):
"""检查邮件数据目录大小"""
try:
email_data_path = Path('email_data')
if not email_data_path.exists():
logger.warning("邮件数据目录不存在")
return "WARNING", {"message": "邮件数据目录不存在"}
# 计算目录大小
total_size = 0
for path in email_data_path.rglob('*'):
if path.is_file():
total_size += path.stat().st_size
# 转换为MB
size_mb = total_size / (1024**2)
if size_mb < 100:
status = "OK"
elif size_mb < 500:
status = "WARNING"
else:
status = "ERROR"
message = f"邮件数据大小: {size_mb:.2f}MB"
logger.info(message)
return status, {
"size_mb": size_mb,
"message": message
}
except Exception as e:
logger.error(f"检查邮件数据大小时出错: {str(e)}")
return "ERROR", {"message": f"错误: {str(e)}"}
def check_system_resources(logger):
"""检查系统资源使用情况"""
try:
# CPU使用率
cpu_percent = psutil.cpu_percent(interval=1)
# 内存使用情况
memory = psutil.virtual_memory()
memory_percent = memory.percent
# 系统负载
if platform.system() != 'Windows':
load1, load5, load15 = os.getloadavg()
load_status = "OK"
# 检查系统负载
cpu_count = psutil.cpu_count()
if load5 > cpu_count * 0.7:
load_status = "WARNING"
if load5 > cpu_count:
load_status = "ERROR"
load_message = f"系统负载: {load1:.2f}, {load5:.2f}, {load15:.2f} (1, 5, 15 min)"
else:
load_status = "OK"
load1, load5, load15 = 0, 0, 0
load_message = "Windows系统不支持负载检查"
# CPU状态
if cpu_percent < 70:
cpu_status = "OK"
elif cpu_percent < 90:
cpu_status = "WARNING"
else:
cpu_status = "ERROR"
# 内存状态
if memory_percent < 70:
memory_status = "OK"
elif memory_percent < 90:
memory_status = "WARNING"
else:
memory_status = "ERROR"
# 综合状态
if cpu_status == "ERROR" or memory_status == "ERROR" or load_status == "ERROR":
status = "ERROR"
elif cpu_status == "WARNING" or memory_status == "WARNING" or load_status == "WARNING":
status = "WARNING"
else:
status = "OK"
message = (
f"CPU使用率: {cpu_percent:.1f}%, "
f"内存使用率: {memory_percent:.1f}% "
f"({memory.used / (1024**3):.1f}GB/{memory.total / (1024**3):.1f}GB)"
)
logger.info(f"系统资源: {message}")
logger.info(load_message)
return status, {
"cpu_percent": cpu_percent,
"memory_percent": memory_percent,
"memory_used_gb": memory.used / (1024**3),
"memory_total_gb": memory.total / (1024**3),
"load1": load1,
"load5": load5,
"load15": load15,
"message": message,
"load_message": load_message
}
except Exception as e:
logger.error(f"检查系统资源时出错: {str(e)}")
return "ERROR", {"message": f"错误: {str(e)}"}
def check_log_files(logger):
"""检查日志文件大小和错误情况"""
try:
log_dir = Path('logs')
if not log_dir.exists():
logger.warning("日志目录不存在")
return "WARNING", {"message": "日志目录不存在"}
log_files = list(log_dir.glob('*.log'))
if not log_files:
logger.warning("未找到日志文件")
return "WARNING", {"message": "未找到日志文件"}
total_size = 0
error_count = 0
large_files = []
# 检查最近的错误日志
for log_file in log_files:
# 检查文件大小
size_mb = log_file.stat().st_size / (1024**2)
total_size += size_mb
if size_mb > 100:
large_files.append((log_file.name, size_mb))
# 检查文件中的错误
try:
# 只检查最后100行
if log_file.exists():
with log_file.open('r', encoding='utf-8', errors='ignore') as f:
# 使用逆序读取最后100行来寻找错误
lines = f.readlines()[-100:] if len(lines := f.readlines()) > 100 else lines
for line in lines:
if "ERROR" in line or "Exception" in line or "Error" in line:
error_count += 1
except Exception as e:
logger.error(f"读取日志文件 {log_file} 时出错: {str(e)}")
# 确定状态
if error_count > 10 or len(large_files) > 2:
status = "ERROR"
elif error_count > 0 or large_files:
status = "WARNING"
else:
status = "OK"
message = f"日志总大小: {total_size:.2f}MB, 最近错误数: {error_count}"
if large_files:
message += f", 大文件: {', '.join([f'{name} ({size:.1f}MB)' for name, size in large_files])}"
logger.info(f"日志文件状态: {message}")
return status, {
"total_size_mb": total_size,
"error_count": error_count,
"large_files": large_files,
"message": message
}
except Exception as e:
logger.error(f"检查日志文件时出错: {str(e)}")
return "ERROR", {"message": f"错误: {str(e)}"}
def check_process_status(logger):
"""检查关联进程状态"""
try:
python_processes = []
for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'cpu_percent', 'memory_percent', 'create_time']):
try:
# 过滤出Python进程并且命令行中包含run.py
if 'python' in proc.info['name'].lower() and proc.info['cmdline'] and any('run.py' in cmd for cmd in proc.info['cmdline']):
# 计算运行时间
uptime = datetime.datetime.now() - datetime.datetime.fromtimestamp(proc.info['create_time'])
# 获取CPU和内存使用情况
cpu_percent = proc.info['cpu_percent']
memory_percent = proc.info['memory_percent']
python_processes.append({
'pid': proc.info['pid'],
'cmdline': ' '.join(proc.info['cmdline']),
'cpu_percent': cpu_percent,
'memory_percent': memory_percent,
'uptime': str(uptime).split('.')[0] # 去除微秒
})
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
if not python_processes:
logger.warning("未找到邮件系统相关进程")
return "WARNING", {"message": "未找到邮件系统相关进程"}
status = "OK"
message = f"找到 {len(python_processes)} 个邮件系统相关进程"
logger.info(message)
# 检查是否有进程使用过高的资源
high_resource_procs = []
for proc in python_processes:
if proc['cpu_percent'] > 50 or proc['memory_percent'] > 30:
high_resource_procs.append(proc)
status = "WARNING"
if high_resource_procs:
logger.warning(f"发现 {len(high_resource_procs)} 个进程使用较高资源")
return status, {
"processes": python_processes,
"high_resource": high_resource_procs,
"message": message
}
except Exception as e:
logger.error(f"检查进程状态时出错: {str(e)}")
return "ERROR", {"message": f"错误: {str(e)}"}
def send_alert_email(subject, message, to_email, from_email, smtp_host, smtp_port, logger):
"""发送警报邮件"""
try:
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = to_email
# 添加HTML内容
html_content = f"""
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; }}
.header {{ background-color: #f44336; color: white; padding: 10px; }}
.content {{ padding: 15px; }}
.footer {{ background-color: #f1f1f1; padding: 10px; font-size: 0.8em; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #f2f2f2; }}
.error {{ color: #f44336; }}
.warning {{ color: #ff9800; }}
.ok {{ color: #4CAF50; }}
</style>
</head>
<body>
<div class="header">
<h2>邮件系统监控警报</h2>
</div>
<div class="content">
{message}
</div>
<div class="footer">
<p>此邮件由邮件系统监控脚本自动发送,请勿回复。</p>
<p>发送时间: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
</div>
</body>
</html>
"""
msg.attach(MIMEText(html_content, 'html'))
# 连接SMTP服务器并发送
with smtplib.SMTP(smtp_host, smtp_port) as server:
server.sendmail(from_email, to_email, msg.as_string())
logger.info(f"成功发送警报邮件到 {to_email}")
return True
except Exception as e:
logger.error(f"发送警报邮件失败: {str(e)}")
return False
def format_duration(seconds):
"""格式化持续时间"""
days, remainder = divmod(seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, seconds = divmod(remainder, 60)
parts = []
if days > 0:
parts.append(f"{int(days)}")
if hours > 0 or parts:
parts.append(f"{int(hours)}小时")
if minutes > 0 or parts:
parts.append(f"{int(minutes)}分钟")
if seconds > 0 or not parts:
parts.append(f"{int(seconds)}")
return "".join(parts)
def generate_report(results, host, api_port, smtp_port, start_time, logger):
"""生成监控报告"""
end_time = time.time()
duration = end_time - start_time
# 计算总体状态
overall_status = "OK"
for check_name, (status, _) in results.items():
if status == "ERROR":
overall_status = "ERROR"
break
elif status == "WARNING" and overall_status != "ERROR":
overall_status = "WARNING"
# 创建报告
report = {
"timestamp": datetime.datetime.now().isoformat(),
"host": host,
"api_port": api_port,
"smtp_port": smtp_port,
"duration": f"{duration:.2f}",
"overall_status": overall_status,
"checks": {}
}
# 添加检查结果
for check_name, (status, data) in results.items():
report["checks"][check_name] = {
"status": status,
"data": data
}
# 保存报告到文件
report_dir = Path('logs')
report_dir.mkdir(exist_ok=True)
report_file = report_dir / f"monitor_report_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
try:
with open(report_file, 'w') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
logger.info(f"监控报告已保存到 {report_file}")
except Exception as e:
logger.error(f"保存监控报告失败: {str(e)}")
return report, overall_status
def format_html_report(report):
"""格式化HTML报告"""
status_colors = {
"OK": "green",
"WARNING": "orange",
"ERROR": "red"
}
overall_color = status_colors.get(report["overall_status"], "black")
html = f"""
<h2>邮件系统监控报告</h2>
<p><strong>时间:</strong> {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
<p><strong>主机:</strong> {report["host"]}</p>
<p><strong>总体状态:</strong> <span style="color: {overall_color}; font-weight: bold;">{report["overall_status"]}</span></p>
<h3>检查结果:</h3>
<table border="1" style="border-collapse: collapse; width: 100%;">
<tr>
<th>检查项</th>
<th>状态</th>
<th>详情</th>
</tr>
"""
for check_name, check_data in report["checks"].items():
status = check_data["status"]
color = status_colors.get(status, "black")
# 格式化消息
if "message" in check_data["data"]:
message = check_data["data"]["message"]
else:
# 尝试提取有用的信息
message_parts = []
for key, value in check_data["data"].items():
if key != "message" and not isinstance(value, dict) and not isinstance(value, list):
message_parts.append(f"{key}: {value}")
message = ", ".join(message_parts)
html += f"""
<tr>
<td>{check_name}</td>
<td style="color: {color}; font-weight: bold;">{status}</td>
<td>{message}</td>
</tr>
"""
html += """
</table>
"""
return html
def main():
parser = argparse.ArgumentParser(description='邮件系统监控脚本')
parser.add_argument('--host', default='localhost', help='服务器主机名或IP地址')
parser.add_argument('--api-port', type=int, default=5000, help='API服务端口')
parser.add_argument('--smtp-port', type=int, default=25, help='SMTP服务端口')
parser.add_argument('--alert', action='store_true', help='启用警报')
parser.add_argument('--interval', type=int, default=300, help='监控间隔,单位为秒')
parser.add_argument('--log', action='store_true', help='将结果记录到日志文件')
parser.add_argument('--daemon', action='store_true', help='以守护进程模式运行')
args = parser.parse_args()
# 设置日志
logger = setup_logging(args.log)
# 警报设置
alert_settings = {
'to_email': 'admin@example.com',
'from_email': 'monitor@example.com',
'smtp_host': 'localhost',
'smtp_port': args.smtp_port
}
# 打印监控信息
logger.info("邮件系统监控启动")
logger.info(f"主机: {args.host}, API端口: {args.api_port}, SMTP端口: {args.smtp_port}")
if args.daemon:
logger.info(f"守护进程模式,间隔: {args.interval}")
# 记录启动次数
run_count = 0
# 循环运行(如果是守护进程模式)
while True:
run_count += 1
start_time = time.time()
try:
if not args.daemon or run_count == 1:
print_header("邮件系统监控")
# 执行检查
results = {}
# 检查API状态
api_status, api_data = check_api_status(args.host, args.api_port, logger)
results["API服务"] = (api_status, api_data)
if not args.daemon or run_count == 1:
print_status("API服务", api_status, api_data.get("message", ""))
# 检查SMTP状态
smtp_status, smtp_data = check_smtp_status(args.host, args.smtp_port, logger)
results["SMTP服务"] = (smtp_status, smtp_data)
if not args.daemon or run_count == 1:
print_status("SMTP服务", smtp_status, smtp_data.get("message", ""))
# 检查磁盘使用情况
disk_status, disk_data = check_disk_usage(logger)
results["磁盘使用情况"] = (disk_status, disk_data)
if not args.daemon or run_count == 1:
print_status("磁盘使用情况", disk_status, disk_data.get("message", ""))
# 检查邮件数据大小
email_data_status, email_data_info = check_email_data_size(logger)
results["邮件数据"] = (email_data_status, email_data_info)
if not args.daemon or run_count == 1:
print_status("邮件数据", email_data_status, email_data_info.get("message", ""))
# 检查系统资源
resources_status, resources_data = check_system_resources(logger)
results["系统资源"] = (resources_status, resources_data)
if not args.daemon or run_count == 1:
print_status("系统资源", resources_status, resources_data.get("message", ""))
if platform.system() != 'Windows':
print(f" {resources_data.get('load_message', '')}")
# 检查日志文件
logs_status, logs_data = check_log_files(logger)
results["日志文件"] = (logs_status, logs_data)
if not args.daemon or run_count == 1:
print_status("日志文件", logs_status, logs_data.get("message", ""))
# 检查进程状态
process_status, process_data = check_process_status(logger)
results["进程状态"] = (process_status, process_data)
if not args.daemon or run_count == 1:
print_status("进程状态", process_status, process_data.get("message", ""))
# 显示进程详情
if process_data.get("processes"):
print("\n进程详情:")
for i, proc in enumerate(process_data["processes"]):
print(f" 进程 {i+1}: PID {proc['pid']}, CPU {proc['cpu_percent']:.1f}%, "
f"内存 {proc['memory_percent']:.1f}%, 运行时间 {proc['uptime']}")
# 生成报告
report, overall_status = generate_report(results, args.host, args.api_port, args.smtp_port, start_time, logger)
# 显示总体状态
if not args.daemon or run_count == 1:
print("\n" + "-" * 50)
status_msg = print_status("总体状态", overall_status)
print("-" * 50)
# 发送警报邮件(如果启用)
if args.alert and (overall_status == "WARNING" or overall_status == "ERROR"):
logger.warning(f"发现问题,正在发送警报邮件...")
# 格式化HTML报告
html_report = format_html_report(report)
# 发送邮件
subject = f"[{'警告' if overall_status == 'WARNING' else '错误'}] 邮件系统监控报告"
send_alert_email(
subject,
html_report,
alert_settings['to_email'],
alert_settings['from_email'],
alert_settings['smtp_host'],
alert_settings['smtp_port'],
logger
)
# 如果是守护进程模式,等待下一次检查
if args.daemon:
execution_time = time.time() - start_time
sleep_time = max(1, args.interval - execution_time)
if run_count == 1:
print(f"\n守护进程模式已启动,间隔 {args.interval}")
print(f"下次检查将在 {format_duration(sleep_time)} 后进行...")
time.sleep(sleep_time)
else:
break
except KeyboardInterrupt:
logger.info("监控被用户中断")
print("\n监控已停止")
break
except Exception as e:
logger.error(f"监控过程中发生错误: {str(e)}")
if not args.daemon:
raise
time.sleep(args.interval)
if __name__ == "__main__":
main()

504
performance_test.py Normal file
View File

@@ -0,0 +1,504 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
邮箱系统性能测试脚本 (轻量版)
用于测试系统基本功能的响应时间和资源使用情况
"""
import requests
import smtplib
import time
import datetime
import random
import string
import argparse
import psutil
import threading
import json
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from concurrent.futures import ThreadPoolExecutor
# 颜色输出
class Colors:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
CYAN = '\033[96m'
BOLD = '\033[1m'
ENDC = '\033[0m'
def print_header(title):
"""打印格式化的标题"""
print(f"\n{Colors.BLUE}{Colors.BOLD}{'=' * 60}{Colors.ENDC}")
print(f"{Colors.BLUE}{Colors.BOLD}{title.center(60)}{Colors.ENDC}")
print(f"{Colors.BLUE}{Colors.BOLD}{'=' * 60}{Colors.ENDC}\n")
def print_info(message):
"""打印信息消息"""
print(f"{Colors.CYAN}[INFO] {message}{Colors.ENDC}")
def print_success(message):
"""打印成功消息"""
print(f"{Colors.GREEN}[SUCCESS] {message}{Colors.ENDC}")
def print_warning(message):
"""打印警告消息"""
print(f"{Colors.YELLOW}[WARNING] {message}{Colors.ENDC}")
def print_error(message):
"""打印错误消息"""
print(f"{Colors.RED}[ERROR] {message}{Colors.ENDC}")
def generate_random_string(length=8):
"""生成随机字符串"""
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def monitor_resources(duration=10, interval=1):
"""监控系统资源使用"""
print_info(f"开始监控系统资源使用 ({duration}秒)")
# 初始化结果数据
samples = []
start_time = time.time()
end_time = start_time + duration
# 收集样本
while time.time() < end_time:
# 获取当前进程
current_process = psutil.Process(os.getpid())
# 系统CPU和内存
cpu_percent = psutil.cpu_percent(interval=0.1)
memory = psutil.virtual_memory()
# 当前进程的CPU和内存
process_cpu = current_process.cpu_percent(interval=0.1)
process_memory = current_process.memory_info().rss / 1024 / 1024 # MB
# 记录样本
sample = {
'timestamp': time.time() - start_time,
'system_cpu': cpu_percent,
'system_memory': memory.percent,
'process_cpu': process_cpu,
'process_memory': process_memory
}
samples.append(sample)
# 等待下一个间隔
time.sleep(interval)
# 计算平均值
if samples:
avg_system_cpu = sum(s['system_cpu'] for s in samples) / len(samples)
avg_system_memory = sum(s['system_memory'] for s in samples) / len(samples)
avg_process_cpu = sum(s['process_cpu'] for s in samples) / len(samples)
avg_process_memory = sum(s['process_memory'] for s in samples) / len(samples)
print_info(f"资源监控完成,平均值:")
print(f" 系统CPU使用率: {avg_system_cpu:.1f}%")
print(f" 系统内存使用率: {avg_system_memory:.1f}%")
print(f" 进程CPU使用率: {avg_process_cpu:.1f}%")
print(f" 进程内存使用: {avg_process_memory:.1f}MB")
return {
'samples': samples,
'averages': {
'system_cpu': avg_system_cpu,
'system_memory': avg_system_memory,
'process_cpu': avg_process_cpu,
'process_memory': avg_process_memory
}
}
return None
def test_api_responsiveness(api_base_url="http://localhost:5000/api", num_requests=10):
"""测试API响应性能"""
print_info(f"开始测试API响应性能 ({num_requests}个请求)")
endpoints = [
'/status',
'/domains',
'/mailboxes'
]
results = {}
for endpoint in endpoints:
url = f"{api_base_url}{endpoint}"
response_times = []
success_count = 0
print_info(f"测试端点: {endpoint}")
for i in range(num_requests):
try:
start_time = time.time()
response = requests.get(url, timeout=5)
end_time = time.time()
response_time = (end_time - start_time) * 1000 # 毫秒
response_times.append(response_time)
if response.status_code == 200:
success_count += 1
print(f" 请求 {i+1}/{num_requests}: {response_time:.2f}ms (成功)")
else:
print(f" 请求 {i+1}/{num_requests}: {response_time:.2f}ms (失败: {response.status_code})")
except Exception as e:
print(f" 请求 {i+1}/{num_requests}: 失败 ({str(e)})")
# 计算结果
if response_times:
avg_time = sum(response_times) / len(response_times)
min_time = min(response_times)
max_time = max(response_times)
success_rate = (success_count / num_requests) * 100
results[endpoint] = {
'avg_time': avg_time,
'min_time': min_time,
'max_time': max_time,
'success_rate': success_rate
}
print(f" 结果: 平均 {avg_time:.2f}ms, 最小 {min_time:.2f}ms, 最大 {max_time:.2f}ms, 成功率 {success_rate:.1f}%")
else:
results[endpoint] = {
'error': '所有请求失败'
}
print_error(" 所有请求失败")
return results
def send_test_email(smtp_host, smtp_port, from_addr, to_addr):
"""发送测试邮件并计时"""
message_id = f"{generate_random_string()}@test.local"
subject = f"测试邮件 {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
# 创建邮件
msg = MIMEMultipart()
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject
msg['Message-ID'] = message_id
# 添加文本内容
body = f"""
这是一封测试邮件。
发送时间: {datetime.datetime.now().isoformat()}
唯一ID: {message_id}
此邮件用于测试系统性能。
"""
msg.attach(MIMEText(body, 'plain'))
try:
# 连接SMTP服务器
start_time = time.time()
server = smtplib.SMTP(smtp_host, smtp_port)
# 发送邮件
server.sendmail(from_addr, to_addr, msg.as_string())
# 关闭连接
server.quit()
end_time = time.time()
# 计算响应时间
response_time = (end_time - start_time) * 1000 # 毫秒
return {
'success': True,
'response_time': response_time,
'message_id': message_id,
'subject': subject
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
def test_email_sending(smtp_host="localhost", smtp_port=3825, num_emails=5, concurrent=False):
"""测试邮件发送性能"""
from_addr = "sender@example.com"
to_addr = "recipient@example.com"
print_info(f"开始测试邮件发送性能 ({num_emails}封邮件, {'并发' if concurrent else '顺序'})")
results = []
start_time = time.time()
if concurrent:
# 并发发送
with ThreadPoolExecutor(max_workers=min(num_emails, 10)) as executor:
futures = []
for i in range(num_emails):
futures.append(executor.submit(
send_test_email, smtp_host, smtp_port, from_addr, to_addr
))
for i, future in enumerate(futures):
result = future.result()
results.append(result)
if result['success']:
print(f" 邮件 {i+1}/{num_emails}: {result['response_time']:.2f}ms (成功)")
else:
print(f" 邮件 {i+1}/{num_emails}: 失败 ({result['error']})")
else:
# 顺序发送
for i in range(num_emails):
result = send_test_email(smtp_host, smtp_port, from_addr, to_addr)
results.append(result)
if result['success']:
print(f" 邮件 {i+1}/{num_emails}: {result['response_time']:.2f}ms (成功)")
else:
print(f" 邮件 {i+1}/{num_emails}: 失败 ({result['error']})")
end_time = time.time()
total_time = (end_time - start_time) * 1000 # 毫秒
# 计算结果
success_count = sum(1 for r in results if r['success'])
success_rate = (success_count / num_emails) * 100
if success_count > 0:
response_times = [r['response_time'] for r in results if r['success']]
avg_time = sum(response_times) / len(response_times)
min_time = min(response_times)
max_time = max(response_times)
print_info(f"邮件发送测试完成:")
print(f" 成功率: {success_rate:.1f}% ({success_count}/{num_emails})")
print(f" 平均响应时间: {avg_time:.2f}ms")
print(f" 最小响应时间: {min_time:.2f}ms")
print(f" 最大响应时间: {max_time:.2f}ms")
print(f" 总执行时间: {total_time:.2f}ms")
return {
'success_rate': success_rate,
'avg_time': avg_time,
'min_time': min_time,
'max_time': max_time,
'total_time': total_time,
'results': results
}
else:
print_error("所有邮件发送失败")
return {
'success_rate': 0,
'error': '所有发送失败',
'results': results
}
def test_creating_mailboxes(api_base_url="http://localhost:5000/api", domain_id=1, num_mailboxes=5, concurrent=False):
"""测试创建邮箱的性能"""
print_info(f"开始测试创建邮箱性能 ({num_mailboxes}个邮箱, {'并发' if concurrent else '顺序'})")
results = []
start_time = time.time()
def create_mailbox(index):
"""创建单个邮箱"""
address = f"testuser{generate_random_string()}_{index}"
data = {
"domain_id": domain_id,
"address": address
}
try:
start_time = time.time()
response = requests.post(f"{api_base_url}/mailboxes", json=data, timeout=5)
end_time = time.time()
response_time = (end_time - start_time) * 1000 # 毫秒
return {
'success': response.status_code in (200, 201),
'status_code': response.status_code,
'response_time': response_time,
'address': address,
'response': response.json() if response.status_code in (200, 201) else None
}
except Exception as e:
return {
'success': False,
'error': str(e),
'address': address
}
if concurrent:
# 并发创建
with ThreadPoolExecutor(max_workers=min(num_mailboxes, 10)) as executor:
futures = []
for i in range(num_mailboxes):
futures.append(executor.submit(create_mailbox, i))
for i, future in enumerate(futures):
result = future.result()
results.append(result)
if result['success']:
print(f" 邮箱 {i+1}/{num_mailboxes}: {result['response_time']:.2f}ms (成功)")
else:
error_msg = result.get('error', f"状态码: {result.get('status_code')}")
print(f" 邮箱 {i+1}/{num_mailboxes}: 失败 ({error_msg})")
else:
# 顺序创建
for i in range(num_mailboxes):
result = create_mailbox(i)
results.append(result)
if result['success']:
print(f" 邮箱 {i+1}/{num_mailboxes}: {result['response_time']:.2f}ms (成功)")
else:
error_msg = result.get('error', f"状态码: {result.get('status_code')}")
print(f" 邮箱 {i+1}/{num_mailboxes}: 失败 ({error_msg})")
end_time = time.time()
total_time = (end_time - start_time) * 1000 # 毫秒
# 计算结果
success_count = sum(1 for r in results if r['success'])
success_rate = (success_count / num_mailboxes) * 100
if success_count > 0:
response_times = [r['response_time'] for r in results if r['success']]
avg_time = sum(response_times) / len(response_times)
min_time = min(response_times)
max_time = max(response_times)
print_info(f"创建邮箱测试完成:")
print(f" 成功率: {success_rate:.1f}% ({success_count}/{num_mailboxes})")
print(f" 平均响应时间: {avg_time:.2f}ms")
print(f" 最小响应时间: {min_time:.2f}ms")
print(f" 最大响应时间: {max_time:.2f}ms")
print(f" 总执行时间: {total_time:.2f}ms")
return {
'success_rate': success_rate,
'avg_time': avg_time,
'min_time': min_time,
'max_time': max_time,
'total_time': total_time,
'results': results
}
else:
print_error("所有邮箱创建失败")
return {
'success_rate': 0,
'error': '所有创建失败',
'results': results
}
def save_results(results, filename="performance_results.json"):
"""保存测试结果到文件"""
try:
with open(filename, 'w') as f:
json.dump(results, f, indent=2)
print_success(f"结果已保存到 {filename}")
return True
except Exception as e:
print_error(f"保存结果失败: {str(e)}")
return False
def run_all_tests(args):
"""运行所有测试"""
results = {
'timestamp': datetime.datetime.now().isoformat(),
'config': vars(args),
'tests': {}
}
# 测试API响应
print_header("API响应性能测试")
api_results = test_api_responsiveness(
api_base_url=args.api_url,
num_requests=args.api_requests
)
results['tests']['api_responsiveness'] = api_results
# 测试邮件发送
print_header("邮件发送性能测试")
email_results = test_email_sending(
smtp_host=args.smtp_host,
smtp_port=args.smtp_port,
num_emails=args.email_count,
concurrent=args.concurrent
)
results['tests']['email_sending'] = email_results
# 测试创建邮箱
if not args.skip_mailbox:
print_header("创建邮箱性能测试")
mailbox_results = test_creating_mailboxes(
api_base_url=args.api_url,
domain_id=args.domain_id,
num_mailboxes=args.mailbox_count,
concurrent=args.concurrent
)
results['tests']['mailbox_creation'] = mailbox_results
# 监控资源使用
print_header("系统资源监控")
resource_results = monitor_resources(
duration=args.monitor_duration,
interval=1
)
results['tests']['resources'] = resource_results
# 保存结果
if args.save:
save_results(results, args.output)
return results
def main():
parser = argparse.ArgumentParser(description='邮箱系统性能测试脚本 (轻量版)')
parser.add_argument('--api-url', default='http://localhost:5000/api', help='API基础URL')
parser.add_argument('--smtp-host', default='localhost', help='SMTP服务器主机')
parser.add_argument('--smtp-port', type=int, default=3825, help='SMTP服务器端口')
parser.add_argument('--domain-id', type=int, default=1, help='用于创建邮箱的域名ID')
parser.add_argument('--api-requests', type=int, default=10, help='API测试请求数量')
parser.add_argument('--email-count', type=int, default=5, help='测试邮件数量')
parser.add_argument('--mailbox-count', type=int, default=5, help='测试创建的邮箱数量')
parser.add_argument('--monitor-duration', type=int, default=10, help='资源监控持续时间(秒)')
parser.add_argument('--concurrent', action='store_true', help='启用并发测试')
parser.add_argument('--skip-mailbox', action='store_true', help='跳过邮箱创建测试')
parser.add_argument('--save', action='store_true', help='保存测试结果')
parser.add_argument('--output', default='performance_results.json', help='结果输出文件')
args = parser.parse_args()
print_header("邮箱系统性能测试 (轻量版)")
print_info("测试配置:")
print(f" API URL: {args.api_url}")
print(f" SMTP服务器: {args.smtp_host}:{args.smtp_port}")
print(f" 域名ID: {args.domain_id}")
print(f" API请求数: {args.api_requests}")
print(f" 邮件数量: {args.email_count}")
print(f" 邮箱数量: {args.mailbox_count}")
print(f" 并发模式: {'' if args.concurrent else ''}")
print(f" 资源监控时间: {args.monitor_duration}")
try:
run_all_tests(args)
print_header("测试完成")
return 0
except KeyboardInterrupt:
print_warning("\n测试被用户中断")
return 1
except Exception as e:
print_error(f"\n测试过程中出错: {str(e)}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,104 @@
"""
生产环境配置文件
此文件包含在生产环境中运行邮箱系统的优化设置
部署时请重命名为local_settings.py
"""
import platform
import os
import logging
from logging.handlers import RotatingFileHandler
import multiprocessing
# 基础配置
IS_PRODUCTION = True
LOG_LEVEL = 'INFO' # 可选: DEBUG, INFO, WARNING, ERROR, CRITICAL
# SMTP服务器配置
SMTP_HOST = '0.0.0.0' # 监听所有网络接口
SMTP_PORT = 25 # 标准SMTP端口
# 如果ISP阻止了25端口可以使用备用端口
# SMTP_PORT = 587 # 备用端口
# 性能优化
# 自动根据CPU核心数调整工作线程数
CPU_COUNT = multiprocessing.cpu_count()
WORKER_THREADS = max(2, min(CPU_COUNT * 2, 8)) # 至少2个线程最多8个线程
# 邮件处理优化
MAX_EMAIL_SIZE = 25 * 1024 * 1024 # 25MB单个邮件最大大小
EMAIL_BATCH_SIZE = 50 # 批处理邮件数量
EMAIL_RETENTION_DAYS = 30 # 邮件保留天数,超过此天数的邮件将被自动清理
# 存储路径配置
MAIL_STORAGE_PATH = os.path.join(os.getcwd(), 'email_data')
LOG_FILE = os.path.join(os.getcwd(), 'logs', 'email_system.log')
# 安全设置
ENABLE_RATE_LIMITING = True # 启用API请求限流
API_RATE_LIMIT = '120/minute' # 每分钟最大请求数
ENABLE_IP_WHITELIST = False # 是否启用IP白名单
IP_WHITELIST = ['127.0.0.1'] # 允许访问API的IP列表
# 垃圾邮件过滤设置
ENABLE_SPAM_FILTER = True # 启用简单的垃圾邮件过滤
SPAM_KEYWORDS = ['casino', 'lottery', 'viagra', 'prescription', 'winner', 'prize', 'bitcoin']
MAX_RECIPIENTS_PER_EMAIL = 50 # 单封邮件最大收件人数量
# 邮件域名白名单(只接收这些域名的邮件,空列表表示接收所有)
DOMAIN_WHITELIST = []
# 日志配置
def setup_logging():
"""配置系统日志"""
log_dir = os.path.dirname(LOG_FILE)
if not os.path.exists(log_dir):
os.makedirs(log_dir)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 文件处理器 - 使用循环日志文件最大10MB保留10个备份
file_handler = RotatingFileHandler(
LOG_FILE, maxBytes=10*1024*1024, backupCount=10
)
file_handler.setFormatter(formatter)
file_handler.setLevel(getattr(logging, LOG_LEVEL))
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
console_handler.setLevel(getattr(logging, LOG_LEVEL))
# 配置根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, LOG_LEVEL))
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler)
logging.info("生产环境日志系统初始化完成")
# 自动清理功能
ENABLE_AUTO_CLEANUP = True # 启用自动清理
CLEANUP_INTERVAL_HOURS = 24 # 每24小时运行一次清理
# 监控设置
ENABLE_MONITORING = True # 启用系统监控
MONITOR_INTERVAL_MINUTES = 5 # 监控间隔(分钟)
# 在应用启动时设置日志
try:
setup_logging()
logging.info("已加载生产环境配置")
except Exception as e:
print(f"日志设置失败: {str(e)}")
# 系统启动时记录配置信息
if IS_PRODUCTION:
logging.info(f"SMTP服务器配置: {SMTP_HOST}:{SMTP_PORT}")
logging.info(f"工作线程数: {WORKER_THREADS}")
logging.info(f"邮件存储路径: {MAIL_STORAGE_PATH}")
logging.info(f"日志级别: {LOG_LEVEL}")
# 创建必要的目录
os.makedirs(MAIL_STORAGE_PATH, exist_ok=True)
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)

BIN
requirements.txt Normal file

Binary file not shown.

111
run.py Normal file
View File

@@ -0,0 +1,111 @@
import os
import argparse
import logging
import platform
from app import create_app
from app.services import SMTPServer, MailStore, EmailProcessor
from app.services import register_smtp_server, register_mail_store, register_email_processor
from app.models import get_session
from flask import Flask
# 检测是否Windows环境
IS_WINDOWS = platform.system().lower() == 'windows'
# 解析命令行参数
parser = argparse.ArgumentParser(description='启动邮箱系统')
parser.add_argument('--host', type=str, default='0.0.0.0', help='HTTP服务器主机地址')
parser.add_argument('--port', type=int, default=5000, help='HTTP服务器端口')
parser.add_argument('--smtp-host', type=str, help='SMTP服务器主机地址')
parser.add_argument('--smtp-port', type=int, help='SMTP服务器端口')
parser.add_argument('--debug', action='store_true', help='启用调试模式')
args = parser.parse_args()
# 创建Flask应用
app = create_app()
# Windows环境下的特殊处理
if IS_WINDOWS:
try:
# 尝试导入本地设置
import local_settings
if getattr(local_settings, 'SMTP_HOST', None):
app.config['SMTP_HOST'] = local_settings.SMTP_HOST
logging.info(f"使用Windows特殊设置: SMTP_HOST={local_settings.SMTP_HOST}")
if getattr(local_settings, 'SMTP_PORT', None):
app.config['SMTP_PORT'] = local_settings.SMTP_PORT
logging.info(f"使用Windows特殊设置: SMTP_PORT={local_settings.SMTP_PORT}")
except ImportError:
logging.warning("未找到local_settings.py将使用标准配置")
# 更新SMTP配置如果命令行参数中指定了
if args.smtp_host:
app.config['SMTP_HOST'] = args.smtp_host
if args.smtp_port:
app.config['SMTP_PORT'] = args.smtp_port
# 初始化服务实例
mail_store = MailStore(get_session)
smtp_server = SMTPServer(
host=app.config.get('SMTP_HOST', '127.0.0.1' if IS_WINDOWS else '0.0.0.0'),
port=app.config.get('SMTP_PORT', 3825 if IS_WINDOWS else 25),
mail_store=mail_store
)
email_processor = EmailProcessor(mail_store)
# 注册服务实例到全局对象
register_mail_store(mail_store)
register_smtp_server(smtp_server)
register_email_processor(email_processor)
def start_services():
"""启动所有服务"""
logging.info("正在启动所有服务...")
# 启动邮件处理器
if email_processor.start():
logging.info("邮件处理器启动成功")
else:
logging.error("邮件处理器启动失败")
# 启动SMTP服务器
if smtp_server.start():
logging.info(f"SMTP服务器启动成功监听 {smtp_server.host}:{smtp_server.port}")
else:
logging.error("SMTP服务器启动失败")
logging.info("所有服务已启动")
def stop_services():
"""停止所有服务"""
logging.info("正在停止所有服务...")
# 停止SMTP服务器
if smtp_server.stop():
logging.info("SMTP服务器已停止")
# 停止邮件处理器
if email_processor.stop():
logging.info("邮件处理器已停止")
logging.info("所有服务已停止")
@app.teardown_appcontext
def teardown_appcontext(exception=None):
"""应用上下文销毁时停止所有服务"""
stop_services()
if __name__ == '__main__':
try:
# 启动服务
start_services()
# 启动Web服务器
app.run(
host=args.host,
port=args.port,
debug=args.debug
)
finally:
# 确保在退出时停止所有服务
stop_services()

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import smtplib
import random
import string
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
def generate_verification_code(length=6):
"""生成随机验证码"""
return ''.join(random.choices(string.digits, k=length))
def send_verification_email(smtp_host, smtp_port, to_email):
"""发送带验证码的邮件"""
# 生成6位随机验证码
verification_code = generate_verification_code()
# 创建邮件
msg = MIMEMultipart()
msg['From'] = 'noreply@system.com'
msg['To'] = to_email
msg['Subject'] = f'您的验证码 - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
# 邮件正文
body = f'''
<html>
<body>
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 5px;">
<h2 style="color: #333;">验证码</h2>
<p>您好!</p>
<p>您的验证码是:</p>
<div style="background-color: #f7f7f7; padding: 15px; font-size: 24px; font-weight: bold; text-align: center; letter-spacing: 5px; margin: 20px 0; border-radius: 4px;">
{verification_code}
</div>
<p>此验证码将在30分钟内有效。</p>
<p>如果这不是您请求的,请忽略此邮件。</p>
<p>谢谢!</p>
<div style="margin-top: 30px; padding-top: 15px; border-top: 1px solid #eee; font-size: 12px; color: #999;">
此邮件由系统自动发送,请勿回复。
</div>
</div>
</body>
</html>
'''
msg.attach(MIMEText(body, 'html'))
# 连接SMTP服务器并发送
try:
server = smtplib.SMTP(smtp_host, smtp_port)
server.set_debuglevel(1) # 开启调试模式
print(f"连接到SMTP服务器: {smtp_host}:{smtp_port}")
# 发送邮件
server.sendmail(msg['From'], msg['To'], msg.as_string())
print(f"成功发送验证码邮件到: {to_email}")
print(f"验证码: {verification_code}")
server.quit()
return True, verification_code
except Exception as e:
print(f"发送邮件失败: {str(e)}")
return False, verification_code
if __name__ == "__main__":
# SMTP服务器设置
smtp_host = 'localhost' # 本地SMTP服务器
smtp_port = 3825 # SMTP端口
# 收件人邮箱
to_email = 'testaa@nosqli.com'
# 发送验证码邮件
success, code = send_verification_email(smtp_host, smtp_port, to_email)
if success:
print(f"验证码邮件已发送,验证码: {code}")
else:
print("邮件发送失败请检查SMTP服务器设置")

35
simple_check.py Normal file
View File

@@ -0,0 +1,35 @@
import requests
# 直接查询特定邮箱ID的邮件
mailbox_id = 2 # testaa@nosqli.com 的ID
try:
print(f"获取邮箱ID={mailbox_id}的邮件...")
response = requests.get(f"http://localhost:5000/api/mailboxes/{mailbox_id}/emails")
print(f"响应状态: {response.status_code}")
if response.status_code == 200:
data = response.json()
print("API响应内容:")
print(data)
if data.get('success', False):
emails = data.get('emails', [])
print(f"\n找到 {len(emails)} 封邮件")
for email in emails:
print("\n邮件信息:")
print(f"ID: {email.get('id')}")
print(f"主题: {email.get('subject')}")
print(f"发件人: {email.get('sender')}")
print(f"时间: {email.get('received_at')}")
print(f"验证码: {email.get('verification_code')}")
else:
print(f"获取邮件失败: {data.get('error')}")
else:
print(f"API请求失败: {response.text}")
except Exception as e:
print(f"发生错误: {str(e)}")
import traceback
traceback.print_exc()

30
simple_test.py Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
print("Python环境测试成功")
print("邮件系统测试脚本")
# 打印系统信息
import sys
import platform
import os
print("\n系统信息:")
print(f"Python版本: {sys.version}")
print(f"操作系统: {platform.system()} {platform.version()}")
print(f"当前目录: {os.getcwd()}")
# 检查必要的库
try:
import requests
print("\n导入requests库成功")
except ImportError:
print("\n导入requests库失败")
try:
import flask
print("导入flask库成功")
except ImportError:
print("导入flask库失败")
print("\n测试完成!")

35
start.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# 创建必要的目录
mkdir -p logs email_data db
# 检查环境变量文件是否存在,不存在则创建
if [ ! -f .env ]; then
echo "创建.env文件..."
cat > .env << EOF
FLASK_ENV=production
SECRET_KEY=$(openssl rand -hex 16)
MAIL_DOMAINS=example.com,mail.example.com
EOF
echo ".env文件已创建"
fi
# 初始化数据库
echo "初始化数据库..."
python -c "from app.models import init_db; init_db()"
# 启动服务
echo "启动服务..."
if [ "$1" == "docker" ]; then
# 使用Docker启动
docker-compose up -d
echo "服务已在Docker中启动"
else
# 直接启动
echo "直接启动应用..."
# 检查是否以root权限运行25端口需要
if [ "$(id -u)" -ne 0 ] && [ "$SMTP_PORT" -lt 1024 ]; then
echo "警告: 在端口 $SMTP_PORT 运行SMTP服务需要root权限"
fi
python run.py --host 0.0.0.0 --port 5000 --smtp-port ${SMTP_PORT:-25}
fi

30
start_windows.ps1 Normal file
View File

@@ -0,0 +1,30 @@
# 创建必要的目录
New-Item -ItemType Directory -Path "logs", "email_data", "db" -Force | Out-Null
# 检查环境变量文件是否存在,不存在则创建
if (-not (Test-Path ".env")) {
Write-Host "创建.env文件..."
$secretKey = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | ForEach-Object {[char]$_})
Set-Content -Path ".env" -Value @"
FLASK_ENV=production
SECRET_KEY=$secretKey
MAIL_DOMAINS=example.com,mail.example.com
"@
Write-Host ".env文件已创建"
}
# 初始化数据库
Write-Host "初始化数据库..."
python -c "from app.models import init_db; init_db()"
# 启动服务
Write-Host "启动服务..."
$smtpPort = if ($env:SMTP_PORT) { $env:SMTP_PORT } else { 25 }
# 检查是否以管理员权限运行25端口需要
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin -and $smtpPort -lt 1024) {
Write-Host "警告: 在端口 $smtpPort 运行SMTP服务需要管理员权限" -ForegroundColor Yellow
}
python run.py --host 0.0.0.0 --port 5000 --smtp-port $smtpPort

284
test_core_features.py Normal file
View File

@@ -0,0 +1,284 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import smtplib
import time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import logging
import sys
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger('CoreFeatureTest')
# API配置
API_BASE_URL = "http://localhost:5000/api"
SMTP_HOST = "localhost"
SMTP_PORT = 3825
def print_divider(title=""):
"""打印分隔线"""
line = "=" * 80
if title:
print(f"\n{line}\n{title.center(80)}\n{line}\n")
else:
print(f"\n{line}\n")
def get_all_domains():
"""获取所有域名"""
try:
response = requests.get(f"{API_BASE_URL}/domains")
if response.status_code == 200:
domains = response.json().get('domains', [])
logger.info(f"获取到 {len(domains)} 个域名")
return domains
else:
logger.error(f"获取域名失败: HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取域名出错: {str(e)}")
return []
def create_domain(domain_name, description=""):
"""创建新域名"""
try:
data = {
"name": domain_name,
"description": description
}
response = requests.post(f"{API_BASE_URL}/domains", json=data)
if response.status_code in (200, 201):
result = response.json()
domain = result.get("domain", {})
logger.info(f"成功创建域名: {domain_name} (ID: {domain.get('id')})")
return domain
else:
logger.error(f"创建域名失败: HTTP {response.status_code} - {response.text}")
return None
except Exception as e:
logger.error(f"创建域名出错: {str(e)}")
return None
def get_all_mailboxes():
"""获取所有邮箱"""
try:
response = requests.get(f"{API_BASE_URL}/mailboxes")
if response.status_code == 200:
mailboxes = response.json().get('mailboxes', [])
logger.info(f"获取到 {len(mailboxes)} 个邮箱")
return mailboxes
else:
logger.error(f"获取邮箱失败: HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取邮箱出错: {str(e)}")
return []
def create_mailbox(domain_id, address):
"""创建新邮箱"""
try:
data = {
"domain_id": domain_id,
"address": address
}
response = requests.post(f"{API_BASE_URL}/mailboxes", json=data)
if response.status_code in (200, 201):
result = response.json()
mailbox = result.get("mailbox", {})
logger.info(f"成功创建邮箱: {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
return mailbox
else:
logger.error(f"创建邮箱失败: HTTP {response.status_code} - {response.text}")
return None
except Exception as e:
logger.error(f"创建邮箱出错: {str(e)}")
return None
def get_mailbox_emails(mailbox_id):
"""获取指定邮箱的所有邮件"""
try:
response = requests.get(f"{API_BASE_URL}/mailboxes/{mailbox_id}/emails")
if response.status_code == 200:
emails = response.json().get('emails', [])
logger.info(f"获取到邮箱ID={mailbox_id}{len(emails)} 封邮件")
return emails
else:
logger.error(f"获取邮件失败: HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取邮件出错: {str(e)}")
return []
def send_email(from_addr, to_addr, subject, body_text="", body_html=""):
"""发送邮件"""
try:
# 创建邮件
msg = MIMEMultipart('alternative')
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject
# 添加文本内容
if body_text:
msg.attach(MIMEText(body_text, 'plain'))
# 添加HTML内容
if body_html:
msg.attach(MIMEText(body_html, 'html'))
# 连接SMTP服务器并发送
server = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
server.set_debuglevel(1) # 开启调试
server.sendmail(from_addr, to_addr, msg.as_string())
server.quit()
logger.info(f"成功发送邮件: {from_addr} -> {to_addr}, 主题: {subject}")
return True
except Exception as e:
logger.error(f"发送邮件失败: {str(e)}")
return False
def test_create_multiple_domains():
"""测试创建多个域名"""
print_divider("测试创建多个域名")
# 创建3个不同的域名
domains = []
test_domains = [
{"name": "example1.com", "desc": "测试域名1"},
{"name": "example2.com", "desc": "测试域名2"},
{"name": "example3.com", "desc": "测试域名3"}
]
for domain_info in test_domains:
domain = create_domain(domain_info["name"], domain_info["desc"])
if domain:
domains.append(domain)
# 验证域名是否创建成功
all_domains = get_all_domains()
print(f"\n当前系统中共有 {len(all_domains)} 个域名:")
for domain in all_domains:
print(f" - {domain.get('name')} (ID: {domain.get('id')})")
return domains
def test_create_multiple_mailboxes(domains):
"""测试为每个域名创建多个邮箱"""
print_divider("测试创建多个邮箱")
mailboxes = []
# 为每个域名创建多个邮箱
for domain in domains:
domain_id = domain.get('id')
domain_name = domain.get('name')
print(f"\n为域名 {domain_name} 创建邮箱:")
# 为每个域名创建2个邮箱
users = ["user1", "user2"]
for user in users:
mailbox = create_mailbox(domain_id, user)
if mailbox:
mailboxes.append(mailbox)
print(f" - 创建了邮箱: {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
# 验证邮箱是否创建成功
all_mailboxes = get_all_mailboxes()
print(f"\n当前系统中共有 {len(all_mailboxes)} 个邮箱:")
for mailbox in all_mailboxes:
print(f" - {mailbox.get('full_address')} (ID: {mailbox.get('id')})")
return mailboxes
def test_email_receiving(mailboxes):
"""测试邮件接收功能"""
print_divider("测试邮件接收功能")
if not mailboxes:
print("没有可用的邮箱,无法测试邮件接收功能")
return False
# 选择第一个邮箱作为发件人
sender_mailbox = mailboxes[0]
from_addr = sender_mailbox.get('full_address')
# 发送邮件到其他所有邮箱
for recipient_mailbox in mailboxes[1:]:
to_addr = recipient_mailbox.get('full_address')
subject = f"测试邮件 - 从 {from_addr} 发送到 {to_addr}"
body_text = f"""
你好!
这是一封测试邮件,发送时间:{time.strftime('%Y-%m-%d %H:%M:%S')}
此邮件用于测试邮件系统的接收功能。
来自: {from_addr}
"""
success = send_email(from_addr, to_addr, subject, body_text)
if success:
print(f"成功发送邮件: {from_addr} -> {to_addr}")
else:
print(f"发送邮件失败: {from_addr} -> {to_addr}")
# 等待邮件处理
print("\n等待5秒钟让系统处理邮件...")
time.sleep(5)
# 检查每个邮箱是否收到邮件
print("\n检查邮箱收件情况:")
for mailbox in mailboxes[1:]: # 跳过发件人邮箱
mailbox_id = mailbox.get('id')
mailbox_address = mailbox.get('full_address')
emails = get_mailbox_emails(mailbox_id)
if emails:
print(f" - 邮箱 {mailbox_address} 收到了 {len(emails)} 封邮件")
for email in emails:
print(f" * 邮件ID: {email.get('id')}, 主题: {email.get('subject')}")
print(f" 发件人: {email.get('sender')}, 时间: {email.get('received_at')}")
else:
print(f" - 邮箱 {mailbox_address} 没有收到任何邮件")
return True
def main():
try:
print_divider("邮件系统核心功能测试")
print("此脚本将测试以下功能:")
print(" 1. 创建多个不同的域名")
print(" 2. 为每个域名创建多个邮箱")
print(" 3. 测试邮件接收功能")
# 测试创建多个域名
domains = test_create_multiple_domains()
# 测试创建多个邮箱
if domains:
mailboxes = test_create_multiple_mailboxes(domains)
# 测试邮件接收功能
if mailboxes:
test_email_receiving(mailboxes)
else:
print("邮箱创建失败,无法测试邮件接收功能")
else:
print("域名创建失败,无法测试邮箱创建和邮件接收功能")
print_divider("测试完成")
except Exception as e:
print(f"测试过程中发生错误: {str(e)}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

64
test_smtp.py Normal file
View File

@@ -0,0 +1,64 @@
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import sys
def send_test_email(host='localhost', port=2525, sender='test@example.com', recipient='user@example.com'):
"""发送测试邮件"""
# 创建邮件内容
msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = '测试邮件 - 邮箱系统'
# 添加正文
body = """
这是一封测试邮件。
邮箱系统测试。
验证码: TEST123
验证链接: http://example.com/verify?code=123456
"""
msg.attach(MIMEText(body, 'plain'))
try:
# 连接SMTP服务器
server = smtplib.SMTP(host, port)
server.set_debuglevel(1) # 启用调试
# 发送邮件
server.sendmail(sender, recipient, msg.as_string())
# 关闭连接
server.quit()
print(f"邮件发送成功: {sender} -> {recipient}")
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False
if __name__ == "__main__":
# 获取命令行参数
host = 'localhost'
port = 2525
sender = 'test@example.com'
recipient = 'user@example.com'
# 处理命令行参数
if len(sys.argv) > 1:
host = sys.argv[1]
if len(sys.argv) > 2:
port = int(sys.argv[2])
if len(sys.argv) > 3:
sender = sys.argv[3]
if len(sys.argv) > 4:
recipient = sys.argv[4]
print(f"发送测试邮件到SMTP服务器 {host}:{port}")
print(f"发件人: {sender}")
print(f"收件人: {recipient}")
send_test_email(host, port, sender, recipient)

377
test_smtp_server.py Normal file
View File

@@ -0,0 +1,377 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SMTP服务器测试脚本
用于验证邮件系统的发送和接收功能
"""
import smtplib
import argparse
import socket
import time
import sys
import json
import random
import string
import requests
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
# ANSI颜色代码
class Colors:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
BOLD = '\033[1m'
ENDC = '\033[0m'
def generate_random_string(length=8):
"""生成随机字符串"""
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def print_step(step, message):
"""打印测试步骤"""
print(f"\n{Colors.BLUE}{Colors.BOLD}[步骤 {step}]{Colors.ENDC} {message}")
def print_success(message):
"""打印成功消息"""
print(f"{Colors.GREEN}{message}{Colors.ENDC}")
def print_warning(message):
"""打印警告消息"""
print(f"{Colors.YELLOW}{message}{Colors.ENDC}")
def print_error(message):
"""打印错误消息"""
print(f"{Colors.RED}{message}{Colors.ENDC}")
def check_smtp_connection(host, port, timeout=5):
"""检查SMTP服务器连接"""
print_step(1, f"检查SMTP服务器连接 ({host}:{port})")
try:
# 尝试创建TCP连接
sock = socket.create_connection((host, port), timeout)
banner = sock.recv(1024).decode('utf-8', errors='ignore')
sock.close()
if banner and banner.startswith('2'):
print_success(f"SMTP服务器连接成功服务器响应: {banner.strip()}")
return True
else:
print_warning(f"SMTP服务器连接成功但响应不标准: {banner.strip()}")
return True
except socket.timeout:
print_error(f"连接超时请检查SMTP服务器是否运行在 {host}:{port}")
return False
except ConnectionRefusedError:
print_error(f"连接被拒绝请检查SMTP服务器是否运行在 {host}:{port}")
return False
except Exception as e:
print_error(f"连接失败: {str(e)}")
return False
def check_smtp_handshake(host, port):
"""测试SMTP握手过程"""
print_step(2, "测试SMTP协议握手")
try:
# 创建SMTP连接并打印通信过程
smtp = smtplib.SMTP(host, port, timeout=10)
smtp.set_debuglevel(1) # 打开调试模式
# 发送EHLO命令
_, ehlo_response = smtp.ehlo()
ehlo_success = all(code == 250 for code, _ in ehlo_response)
if ehlo_success:
print_success("SMTP握手成功服务器支持的功能:")
for code, feature in ehlo_response:
print(f" {feature.decode('utf-8')}")
else:
print_warning("SMTP握手部分成功可能有功能受限")
smtp.quit()
return ehlo_success
except Exception as e:
print_error(f"SMTP握手失败: {str(e)}")
return False
def check_api_status(api_base_url):
"""检查API服务器状态"""
print_step(3, f"检查API服务器状态 ({api_base_url})")
try:
response = requests.get(f"{api_base_url}/status", timeout=5)
response.raise_for_status()
data = response.json()
print_success("API服务器状态正常")
print(f" 状态信息: {json.dumps(data, indent=2)}")
return True
except requests.exceptions.RequestException as e:
print_error(f"API服务器连接失败: {str(e)}")
return False
def get_test_mailbox(api_base_url):
"""获取或创建测试邮箱"""
print_step(4, "获取测试邮箱")
try:
# 获取所有域名
response = requests.get(f"{api_base_url}/domains")
response.raise_for_status()
domains = response.json().get("domains", [])
if not domains:
print_error("没有可用的域名,请先创建域名")
return None
# 获取第一个域名
domain = domains[0]
domain_id = domain.get("id")
domain_name = domain.get("name")
print_success(f"使用域名: {domain_name} (ID: {domain_id})")
# 生成随机地址前缀
address_prefix = f"test-{generate_random_string(6)}"
# 创建新邮箱
mailbox_data = {
"domain_id": domain_id,
"address": address_prefix
}
response = requests.post(f"{api_base_url}/mailboxes", json=mailbox_data)
response.raise_for_status()
mailbox = response.json().get("mailbox", {})
mailbox_id = mailbox.get("id")
full_address = mailbox.get("full_address")
if not mailbox_id or not full_address:
print_error("创建邮箱失败,使用现有邮箱")
# 尝试获取现有邮箱
response = requests.get(f"{api_base_url}/mailboxes")
response.raise_for_status()
mailboxes = response.json().get("mailboxes", [])
if not mailboxes:
print_error("没有可用的邮箱")
return None
mailbox = mailboxes[0]
mailbox_id = mailbox.get("id")
full_address = mailbox.get("full_address")
print_success(f"使用邮箱: {full_address} (ID: {mailbox_id})")
return {"id": mailbox_id, "address": full_address}
except Exception as e:
print_error(f"获取邮箱失败: {str(e)}")
return None
def send_test_email(smtp_host, smtp_port, recipient, verbose=False):
"""发送测试邮件"""
print_step(5, f"发送测试邮件到 {recipient}")
# 生成随机验证码
verification_code = ''.join(random.choices(string.digits, k=6))
# 生成随机主题标识
subject_id = generate_random_string(8)
try:
# 创建邮件
msg = MIMEMultipart()
msg['From'] = 'test@example.com'
msg['To'] = recipient
msg['Subject'] = f'测试邮件 {subject_id} - 验证码 {verification_code}'
# 添加HTML内容
html_content = f"""
<html>
<body>
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 5px;">
<h2 style="color: #333;">邮箱系统测试</h2>
<p>您好!</p>
<p>这是一封测试邮件,用于验证邮箱系统的功能。</p>
<p>您的验证码是:</p>
<div style="background-color: #f7f7f7; padding: 15px; font-size: 24px; font-weight: bold; text-align: center; letter-spacing: 5px; margin: 20px 0; border-radius: 4px;">
{verification_code}
</div>
<p>此测试邮件发送时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>测试ID: {subject_id}</p>
<p>请不要回复此邮件。</p>
<div style="margin-top: 30px; padding-top: 15px; border-top: 1px solid #eee; font-size: 12px; color: #999;">
此邮件由系统自动发送,用于测试目的。
</div>
</div>
</body>
</html>
"""
msg.attach(MIMEText(html_content, 'html'))
# 连接到SMTP服务器并发送
if verbose:
server = smtplib.SMTP(smtp_host, smtp_port)
server.set_debuglevel(1) # 开启详细输出
else:
server = smtplib.SMTP(smtp_host, smtp_port)
server.send_message(msg)
server.quit()
print_success(f"邮件发送成功!")
print(f" 验证码: {verification_code}")
print(f" 测试ID: {subject_id}")
return {"verification_code": verification_code, "subject_id": subject_id}
except Exception as e:
print_error(f"邮件发送失败: {str(e)}")
return None
def check_email_received(api_base_url, mailbox_id, test_data, max_attempts=10, delay=2):
"""检查邮件是否被接收"""
print_step(6, "检查邮件接收状态")
verification_code = test_data.get("verification_code")
subject_id = test_data.get("subject_id")
print(f"正在查找包含验证码 {verification_code} 和测试ID {subject_id} 的邮件...")
for attempt in range(max_attempts):
try:
print(f"尝试 {attempt + 1}/{max_attempts}...")
response = requests.get(f"{api_base_url}/mailboxes/{mailbox_id}/emails")
response.raise_for_status()
emails = response.json().get("emails", [])
if not emails:
print_warning("未找到邮件,等待中...")
time.sleep(delay)
continue
# 查找包含测试ID的邮件
test_email = None
for email in emails:
subject = email.get("subject", "")
if subject_id in subject:
test_email = email
break
if test_email:
email_id = test_email.get("id")
received_at = test_email.get("received_at") or test_email.get("created_at")
print_success(f"找到测试邮件! ID: {email_id}, 接收时间: {received_at}")
# 获取详细信息
response = requests.get(f"{api_base_url}/emails/{email_id}")
response.raise_for_status()
email_detail = response.json().get("email", {})
# 尝试从邮件中提取验证码
body_html = email_detail.get("body_html", "")
extracted_code = None
if verification_code in body_html:
print_success("邮件内容中包含正确的验证码")
extracted_code = verification_code
else:
print_warning("无法在邮件内容中找到验证码")
return {
"success": True,
"email_id": email_id,
"verification_code": extracted_code,
"received_at": received_at
}
else:
print_warning("未找到包含测试ID的邮件等待中...")
time.sleep(delay)
except Exception as e:
print_error(f"检查邮件失败: {str(e)}")
time.sleep(delay)
print_error(f"{max_attempts} 次尝试后仍未找到测试邮件")
return {"success": False}
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='SMTP服务器测试工具')
parser.add_argument('--smtp-host', default='localhost', help='SMTP服务器地址默认为localhost')
parser.add_argument('--smtp-port', type=int, default=3825, help='SMTP服务器端口默认为3825')
parser.add_argument('--api-url', default='http://localhost:5000/api', help='API服务器URL默认为http://localhost:5000/api')
parser.add_argument('--verbose', action='store_true', help='显示详细的SMTP通信过程')
args = parser.parse_args()
print(f"{Colors.BOLD}邮箱系统SMTP服务器测试{Colors.ENDC}")
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"SMTP服务器: {args.smtp_host}:{args.smtp_port}")
print(f"API服务器: {args.api_url}")
# 检查SMTP连接
if not check_smtp_connection(args.smtp_host, args.smtp_port):
print_error("SMTP服务器连接测试失败无法继续后续测试")
return 1
# 测试SMTP握手
if not check_smtp_handshake(args.smtp_host, args.smtp_port):
print_warning("SMTP握手测试失败但将继续后续测试")
# 检查API状态
if not check_api_status(args.api_url):
print_error("API服务器连接测试失败无法继续后续测试")
return 1
# 获取测试邮箱
mailbox = get_test_mailbox(args.api_url)
if not mailbox:
print_error("无法获取测试邮箱,测试终止")
return 1
# 发送测试邮件
test_data = send_test_email(args.smtp_host, args.smtp_port, mailbox["address"], args.verbose)
if not test_data:
print_error("发送测试邮件失败,测试终止")
return 1
# 检查邮件接收
result = check_email_received(args.api_url, mailbox["id"], test_data)
# 打印测试结果摘要
print(f"\n{Colors.BOLD}测试结果摘要{Colors.ENDC}")
if result.get("success"):
print_success("邮件发送和接收测试全部成功!")
print(f" 邮箱: {mailbox['address']}")
print(f" 邮件ID: {result.get('email_id')}")
print(f" 验证码: {test_data.get('verification_code')}")
print(f" 接收时间: {result.get('received_at')}")
print(f"\n{Colors.GREEN}{Colors.BOLD}恭喜!您的邮箱系统运行正常,可以进行部署。{Colors.ENDC}")
return 0
else:
print_error("测试失败: 发送的邮件未被接收")
print(f" 邮箱: {mailbox['address']}")
print(f" 验证码: {test_data.get('verification_code')}")
print(f"\n{Colors.RED}{Colors.BOLD}请检查系统配置并解决问题后再次测试。{Colors.ENDC}")
return 1
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
print("\n测试被用户中断")
sys.exit(130)
except Exception as e:
print(f"\n{Colors.RED}测试过程中出现未处理的异常: {str(e)}{Colors.ENDC}")
import traceback
traceback.print_exc()
sys.exit(1)