大模型应用:一个基于AI大模型的自动邮件简报系统 - Flask + HTML 方案
摘要 本项目实现了一个轻量级的邮件自动摘要系统,使用Flask框架构建后端,HTML作为前端界面。系统通过IMAP协议收取邮件,利用AI大模型(GLM)分析邮件内容并生成摘要,最后将结果存储在SQLite数据库中。核心功能包括: 邮件收取模块:通过IMAP协议定期检查并获取新邮件 AI摘要生成:调用GLM大模型API处理邮件内容 数据存储:使用SQLite数据库保存邮件和摘要信息 Web界面:提供
·
大模型应用:一个基于AI大模型的自动邮件简报系统 - Flask + HTML 方案
项目概述
这是一个简化的邮件自动收取和总结系统,使用 Flask 作为后端,简单的 HTML 页面作为前端,无需 Docker,部署简单。
项目结构
email-digest-simple/
├── app.py
├── config.py
├── email_fetcher.py
├── glm_client.py
├── digest_generator.py
├── models.py
├── templates/
│ ├── base.html
│ ├── index.html
│ └── digest.html
├── static/
│ └── style.css
├── data/
│ └── emails.db
├── .env
├── requirements.txt
└── README.md
核心代码实现
1. 配置文件
config.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
class Config:
# 邮箱配置
EMAIL_HOST = os.getenv('EMAIL_HOST', 'imap.gmail.com')
EMAIL_PORT = int(os.getenv('EMAIL_PORT', '993'))
EMAIL_ADDRESS = os.getenv('EMAIL_ADDRESS')
EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD')
# GLM配置
GLM_API_KEY = os.getenv('GLM_API_KEY')
GLM_MODEL = os.getenv('GLM_MODEL', 'glm-4')
GLM_BASE_URL = os.getenv('GLM_BASE_URL', 'https://open.bigmodel.cn/api/paas/v4')
# 应用配置
SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-change-in-production')
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
# 调度配置
CHECK_INTERVAL_MINUTES = int(os.getenv('CHECK_INTERVAL_MINUTES', '30'))
MAX_EMAILS_PER_RUN = int(os.getenv('MAX_EMAILS_PER_RUN', '20'))
# 数据库
DATABASE_PATH = BASE_DIR / 'data' / 'emails.db'
DATABASE_PATH.parent.mkdir(exist_ok=True)
2. 数据模型
models.py
import sqlite3
from datetime import datetime
import json
from config import Config
class Database:
def __init__(self):
self.db_path = Config.DATABASE_PATH
self.init_database()
def get_connection(self):
return sqlite3.connect(self.db_path)
def init_database(self):
conn = self.get_connection()
cursor = conn.cursor()
# 创建邮件表
cursor.execute('''
CREATE TABLE IF NOT EXISTS emails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email_id TEXT UNIQUE,
subject TEXT,
sender TEXT,
recipients TEXT,
date TEXT,
body TEXT,
body_html TEXT,
summary TEXT,
processed BOOLEAN DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建简报表
cursor.execute('''
CREATE TABLE IF NOT EXISTS digests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
content TEXT,
email_count INTEGER,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
def save_email(self, email_data):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO emails
(email_id, subject, sender, recipients, date, body, body_html, summary, processed, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
email_data['email_id'],
email_data['subject'],
email_data['sender'],
json.dumps(email_data['recipients']),
email_data['date'].isoformat(),
email_data['body'],
email_data['body_html'],
email_data['summary'],
email_data['processed'],
datetime.now().isoformat()
))
conn.commit()
conn.close()
def get_processed_email_ids(self):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('SELECT email_id FROM emails WHERE processed = 1')
results = cursor.fetchall()
conn.close()
return {row[0] for row in results}
def get_latest_digest(self):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT id, date, content, email_count, created_at
FROM digests
ORDER BY date DESC
LIMIT 1
''')
result = cursor.fetchone()
conn.close()
if result:
return {
'id': result[0],
'date': result[1],
'content': json.loads(result[2]),
'email_count': result[3],
'created_at': result[4]
}
return None
def save_digest(self, digest_data):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO digests (date, content, email_count)
VALUES (?, ?, ?)
''', (
digest_data['date'].isoformat(),
json.dumps(digest_data['content'], ensure_ascii=False),
digest_data['email_count']
))
conn.commit()
conn.close()
def get_all_emails(self, limit=50):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT id, subject, sender, date, summary, processed
FROM emails
ORDER BY date DESC
LIMIT ?
''', (limit,))
results = cursor.fetchall()
conn.close()
emails = []
for row in results:
emails.append({
'id': row[0],
'subject': row[1],
'sender': row[2],
'date': row[3],
'summary': row[4],
'processed': bool(row[5])
})
return emails
3. 邮件获取器
email_fetcher.py
import imaplib
import email
from email.header import decode_header
from datetime import datetime, timedelta
import logging
from config import Config
from models import Database
logger = logging.getLogger(__name__)
class EmailFetcher:
def __init__(self):
self.db = Database()
def connect(self):
"""连接到IMAP服务器"""
try:
self.imap_server = imaplib.IMAP4_SSL(Config.EMAIL_HOST, Config.EMAIL_PORT)
self.imap_server.login(Config.EMAIL_ADDRESS, Config.EMAIL_PASSWORD)
logger.info(f"成功连接到邮箱: {Config.EMAIL_ADDRESS}")
return True
except Exception as e:
logger.error(f"连接邮箱失败: {e}")
return False
def disconnect(self):
"""断开连接"""
if hasattr(self, 'imap_server'):
try:
self.imap_server.logout()
except:
pass
def _decode_mime_words(self, s):
"""解码MIME编码的字符串"""
if not s:
return ""
decoded_fragments = decode_header(s)
fragments = []
for fragment, encoding in decoded_fragments:
if isinstance(fragment, bytes):
if encoding:
fragment = fragment.decode(encoding)
else:
fragment = fragment.decode('utf-8', errors='ignore')
fragments.append(fragment)
return ''.join(fragments)
def _get_email_body(self, msg):
"""提取邮件正文"""
body = ""
body_html = ""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
if "attachment" not in content_disposition:
charset = part.get_content_charset() or 'utf-8'
try:
payload = part.get_payload(decode=True)
if payload:
text = payload.decode(charset, errors='ignore')
if content_type == "text/plain":
body += text
elif content_type == "text/html":
body_html += text
except Exception as e:
logger.warning(f"解析邮件正文失败: {e}")
else:
charset = msg.get_content_charset() or 'utf-8'
try:
payload = msg.get_payload(decode=True)
if payload:
body = payload.decode(charset, errors='ignore')
except Exception as e:
logger.warning(f"解析邮件正文失败: {e}")
return body, body_html
def fetch_new_emails(self, since_days=1):
"""获取新的未处理邮件"""
if not self.connect():
return []
try:
self.imap_server.select('INBOX')
# 计算日期范围
since_date = (datetime.now() - timedelta(days=since_days)).strftime("%d-%b-%Y")
search_criteria = f'(SINCE "{since_date}")'
status, messages = self.imap_server.search(None, search_criteria)
if status != 'OK':
logger.error("搜索邮件失败")
return []
email_ids = messages[0].split()
logger.info(f"找到 {len(email_ids)} 封邮件")
# 限制处理数量
email_ids = email_ids[-Config.MAX_EMAILS_PER_RUN:] if email_ids else []
# 获取已处理的邮件ID
processed_ids = self.db.get_processed_email_ids()
new_emails = []
for email_id in email_ids:
email_id_str = email_id.decode('utf-8')
if email_id_str in processed_ids:
continue
try:
status, msg_data = self.imap_server.fetch(email_id, '(RFC822)')
if status != 'OK':
continue
msg = email.message_from_bytes(msg_data[0][1])
subject = self._decode_mime_words(msg.get("Subject", ""))
sender = self._decode_mime_words(msg.get("From", ""))
recipients = [self._decode_mime_words(r) for r in msg.get_all("To", [])]
date_str = msg.get("Date", "")
try:
email_date = email.utils.parsedate_to_datetime(date_str)
except:
email_date = datetime.now()
body, body_html = self._get_email_body(msg)
email_data = {
'email_id': email_id_str,
'subject': subject[:200],
'sender': sender[:100],
'recipients': recipients[:5],
'date': email_date,
'body': body[:10000],
'body_html': body_html[:10000] if body_html else None,
'summary': None,
'processed': False
}
new_emails.append(email_data)
except Exception as e:
logger.error(f"解析邮件 {email_id_str} 失败: {e}")
continue
return new_emails
except Exception as e:
logger.error(f"获取邮件失败: {e}")
return []
finally:
self.disconnect()
4. GLM客户端
glm_client.py
import requests
import json
import logging
from config import Config
logger = logging.getLogger(__name__)
class GLMClient:
def __init__(self):
self.api_key = Config.GLM_API_KEY
self.base_url = Config.GLM_BASE_URL
self.model = Config.GLM_MODEL
def summarize_email(self, email_content, subject=""):
"""使用GLM模型总结邮件内容"""
if not self.api_key:
logger.error("GLM API key 未配置")
return None
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
# 构建提示词
prompt = f"""
请为以下邮件生成简洁的中文摘要,突出重点信息:
邮件主题:{subject}
邮件内容:{email_content}
要求:
1. 摘要控制在100字以内
2. 突出关键信息和行动项
3. 语言简洁明了
"""
payload = {
"model": self.model,
"messages": [
{"role": "user", "content": prompt}
],
"temperature": 0.3,
"max_tokens": 150
}
try:
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
timeout=30
)
if response.status_code == 200:
result = response.json()
summary = result['choices'][0]['message']['content'].strip()
return summary
else:
error_text = response.text
logger.error(f"GLM API 调用失败: {response.status_code} - {error_text}")
return None
except Exception as e:
logger.error(f"GLM API 调用异常: {e}")
return None
def batch_summarize(self, emails):
"""批量总结邮件"""
for email_data in emails:
if email_data['body'].strip():
summary = self.summarize_email(email_data['body'], email_data['subject'])
email_data['summary'] = summary or "总结失败"
email_data['processed'] = True
else:
email_data['summary'] = "邮件内容为空"
email_data['processed'] = True
return emails
5. 简报生成器
digest_generator.py
from datetime import datetime
import json
class DigestGenerator:
def generate_digest_content(self, emails):
"""生成简报内容"""
digest_data = {
"generated_at": datetime.now().isoformat(),
"email_count": len(emails),
"emails": []
}
for email in emails:
digest_data["emails"].append({
"subject": email['subject'],
"sender": email['sender'],
"date": email['date'].isoformat(),
"summary": email['summary']
})
return digest_data
def create_digest(self, emails):
"""创建简报记录"""
content = self.generate_digest_content(emails)
digest = {
'date': datetime.now(),
'content': content,
'email_count': len(emails)
}
return digest
6. 主应用
app.py
import os
import threading
import time
from datetime import datetime, timedelta
import logging
from flask import Flask, render_template, request, jsonify, redirect, url_for
from apscheduler.schedulers.background import BackgroundScheduler
from config import Config
from email_fetcher import EmailFetcher
from glm_client import GLMClient
from digest_generator import DigestGenerator
from models import Database
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 加载环境变量
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.config.from_object(Config)
# 初始化组件
db = Database()
scheduler = BackgroundScheduler()
email_fetcher = EmailFetcher()
glm_client = GLMClient()
digest_generator = DigestGenerator()
def process_emails():
"""处理邮件并生成简报"""
logger = logging.getLogger(__name__)
logger.info("开始处理邮件...")
# 获取新邮件
new_emails = email_fetcher.fetch_new_emails(since_days=1)
if not new_emails:
logger.info("没有新邮件需要处理")
return
logger.info(f"发现 {len(new_emails)} 封新邮件,开始生成摘要...")
# 生成摘要
summarized_emails = glm_client.batch_summarize(new_emails)
# 保存到数据库
for email_data in summarized_emails:
db.save_email(email_data)
logger.info(f"成功保存 {len(summarized_emails)} 封邮件")
# 生成简报
digest = digest_generator.create_digest(summarized_emails)
db.save_digest(digest)
logger.info(f"简报生成完成")
@app.route('/')
def index():
"""首页 - 显示最新简报"""
latest_digest = db.get_latest_digest()
return render_template('index.html', digest=latest_digest)
@app.route('/emails')
def emails():
"""邮件列表页面"""
all_emails = db.get_all_emails(limit=100)
return render_template('digest.html', emails=all_emails)
@app.route('/trigger', methods=['POST'])
def trigger_processing():
"""手动触发邮件处理"""
try:
process_emails()
return jsonify({'success': True, 'message': '邮件处理完成'})
except Exception as e:
return jsonify({'success': False, 'message': f'处理失败: {str(e)}'})
@app.route('/health')
def health():
"""健康检查"""
return jsonify({
'status': 'healthy',
'scheduler_running': scheduler.running if scheduler else False,
'current_time': datetime.now().isoformat()
})
def start_scheduler():
"""启动定时任务"""
if not scheduler.running:
scheduler.add_job(
func=process_emails,
trigger="interval",
minutes=Config.CHECK_INTERVAL_MINUTES,
id='email_processing'
)
scheduler.start()
logging.info(f"定时任务已启动,每 {Config.CHECK_INTERVAL_MINUTES} 分钟检查一次")
if __name__ == '__main__':
# 启动定时任务
start_scheduler()
try:
app.run(
host='0.0.0.0',
port=5000,
debug=Config.DEBUG
)
except KeyboardInterrupt:
logging.info("收到中断信号,正在停止...")
finally:
if scheduler.running:
scheduler.shutdown()
7. HTML 模板
templates/base.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}邮件简报系统{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="container">
<header>
<h1>📧 邮件简报系统</h1>
<nav>
<a href="{{ url_for('index') }}" class="{% if request.endpoint == 'index' %}active{% endif %}">最新简报</a>
<a href="{{ url_for('emails') }}" class="{% if request.endpoint == 'emails' %}active{% endif %}">邮件列表</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 邮件简报系统</p>
</footer>
</div>
<script>
// 手动触发处理
function triggerProcessing() {
const button = document.getElementById('trigger-btn');
button.disabled = true;
button.textContent = '处理中...';
fetch('/trigger', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('邮件处理完成!');
location.reload();
} else {
alert('处理失败: ' + data.message);
button.disabled = false;
button.textContent = '🔄 手动处理邮件';
}
})
.catch(error => {
alert('请求失败: ' + error.message);
button.disabled = false;
button.textContent = '🔄 手动处理邮件';
});
}
</script>
</body>
</html>
templates/index.html
{% extends "base.html" %}
{% block content %}
<div class="dashboard">
<div class="controls">
<button id="trigger-btn" onclick="triggerProcessing()" class="btn btn-primary">
🔄 手动处理邮件
</button>
<button onclick="location.reload()" class="btn btn-secondary">
📥 刷新数据
</button>
</div>
{% if digest %}
<div class="digest-card">
<div class="digest-header">
<h2>📅 {{ digest.date[:10] }}</h2>
<p class="email-count">📧 {{ digest.email_count }} 封邮件</p>
</div>
<div class="email-list">
{% for email in digest.content.emails %}
<div class="email-item">
<div class="email-header">
<h3>{{ email.subject }}</h3>
<span class="sender">{{ email.sender }}</span>
</div>
<div class="email-summary">
<p>{{ email.summary or '暂无摘要' }}</p>
</div>
<div class="email-time">
{{ email.date[11:16] }}
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="no-data">
<h3>暂无邮件简报</h3>
<p>系统会自动处理新邮件,或点击上方按钮手动处理</p>
</div>
{% endif %}
<div class="system-info">
<h4>系统信息</h4>
<div class="info-grid">
<div class="info-item">
<strong>检查间隔:</strong> {{ config.CHECK_INTERVAL_MINUTES }} 分钟
</div>
<div class="info-item">
<strong>最大处理数:</strong> {{ config.MAX_EMAILS_PER_RUN }} 封/次
</div>
<div class="info-item">
<strong>邮箱地址:</strong> {{ config.EMAIL_ADDRESS }}
</div>
</div>
</div>
</div>
{% endblock %}
templates/digest.html
{% extends "base.html" %}
{% block content %}
<div class="emails-page">
<h2>邮件列表 (共 {{ emails|length }} 封)</h2>
<div class="email-list">
{% for email in emails %}
<div class="email-item {% if email.processed %}processed{% endif %}">
<div class="email-header">
<h3>{{ email.subject }}</h3>
<span class="sender">{{ email.sender }}</span>
</div>
<div class="email-summary">
<p>{{ email.summary or '暂无摘要' }}</p>
</div>
<div class="email-meta">
<span class="date">{{ email.date[:10] }} {{ email.date[11:16] }}</span>
<span class="status {% if email.processed %}success{% else %}warning{% endif %}">
{{ '已处理' if email.processed else '未处理' }}
</span>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
8. CSS 样式
static/style.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e0e0e0;
}
header h1 {
color: #2c3e50;
font-size: 2.5rem;
}
nav a {
margin-left: 20px;
text-decoration: none;
color: #666;
padding: 8px 16px;
border-radius: 4px;
transition: all 0.3s ease;
}
nav a:hover {
color: #3498db;
background-color: #f8f9fa;
}
nav a.active {
color: #3498db;
background-color: #e3f2fd;
font-weight: bold;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
margin-right: 10px;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #2980b9;
}
.btn-secondary {
background-color: #95a5a6;
color: white;
}
.btn-secondary:hover {
background-color: #7f8c8d;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.controls {
margin-bottom: 30px;
display: flex;
gap: 10px;
}
.digest-card {
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 25px;
margin-bottom: 30px;
}
.digest-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.digest-header h2 {
color: #2c3e50;
font-size: 2rem;
}
.email-count {
color: #666;
font-size: 1.2rem;
}
.email-item {
padding: 20px 0;
border-bottom: 1px solid #eee;
position: relative;
}
.email-item:last-child {
border-bottom: none;
}
.email-item.processed {
background-color: #f8f9fa;
}
.email-header {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
}
.email-header h3 {
color: #2c3e50;
font-size: 1.3rem;
font-weight: 500;
}
.sender {
color: #666;
font-size: 0.9rem;
}
.email-summary {
color: #555;
margin-bottom: 12px;
line-height: 1.5;
}
.email-summary p {
margin: 0;
}
.email-time, .date {
color: #999;
font-size: 0.85rem;
}
.email-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
}
.status {
font-size: 0.8rem;
padding: 4px 8px;
border-radius: 12px;
font-weight: bold;
}
.status.success {
background-color: #d4edda;
color: #155724;
}
.status.warning {
background-color: #fff3cd;
color: #856404;
}
.no-data {
text-align: center;
padding: 60px 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.no-data h3 {
color: #666;
margin-bottom: 15px;
}
.system-info {
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
}
.system-info h4 {
margin-bottom: 15px;
color: #2c3e50;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
}
.info-item strong {
color: #666;
margin-right: 5px;
}
footer {
margin-top: 40px;
text-align: center;
color: #666;
padding-top: 20px;
border-top: 1px solid #eee;
}
/* 响应式设计 */
@media (max-width: 768px) {
.container {
padding: 15px;
}
header {
flex-direction: column;
align-items: flex-start;
}
nav {
margin-top: 15px;
}
nav a {
margin-left: 0;
margin-right: 15px;
}
.controls {
flex-direction: column;
}
.btn {
width: 100%;
margin-right: 0;
margin-bottom: 10px;
}
.digest-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.email-header {
flex-direction: column;
gap: 5px;
}
.email-meta {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
9. 依赖文件
requirements.txt
Flask==2.3.3
python-dotenv==1.0.0
requests==2.31.0
APScheduler==3.10.4
10. 环境变量配置
.env 示例
# 邮箱配置
EMAIL_HOST=imap.gmail.com
EMAIL_PORT=993
EMAIL_ADDRESS=your_email@gmail.com
EMAIL_PASSWORD=your_app_password
# GLM配置
GLM_API_KEY=your_glm_api_key
GLM_MODEL=glm-4
# 应用配置
SECRET_KEY=your-very-secret-key-change-this-in-production
DEBUG=False
# 调度配置
CHECK_INTERVAL_MINUTES=30
MAX_EMAILS_PER_RUN=20
部署说明
1. 环境准备
# 克隆或创建项目目录
mkdir email-digest-simple
cd email-digest-simple
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
2. 配置环境变量
# 复制环境变量模板
cp .env.example .env # 如果有示例文件
# 或者直接创建 .env 文件
# 编辑 .env 文件
nano .env
重要配置说明:
EMAIL_PASSWORD
: 对于 Gmail,需要在 Google 账户设置中启用"两步验证",然后生成"应用专用密码"GLM_API_KEY
: 从 智谱AI开放平台 获取 API 密钥
3. 运行应用
# 开发模式(自动重载)
python app.py
# 生产模式
# 安装 gunicorn
pip install gunicorn
# 使用 gunicorn 运行
gunicorn -w 4 -b 0.0.0.0:5000 app:app
4. 系统服务部署(Linux)
创建 systemd 服务文件:
sudo nano /etc/systemd/system/email-digest.service
服务配置内容:
[Unit]
Description=Email Digest Simple Service
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/path/to/email-digest-simple
Environment=PATH=/path/to/email-digest-simple/venv/bin
ExecStart=/path/to/email-digest-simple/venv/bin/python app.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
启用并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable email-digest.service
sudo systemctl start email-digest.service
sudo systemctl status email-digest.service
5. Nginx 反向代理(可选)
如果需要通过域名访问,可以配置 Nginx:
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
6. 访问应用
- 本地访问: http://localhost:5000
- 服务器访问: http://your-server-ip:5000
- 健康检查: http://your-server-ip:5000/health
7. 功能说明
- 自动处理: 系统会按照配置的时间间隔自动检查新邮件并生成简报
- 手动处理: 在网页上点击"手动处理邮件"按钮可以立即处理
- 查看简报: 首页显示最新的邮件简报
- 邮件列表: 可以查看所有已处理的邮件详情
- 系统信息: 显示当前的配置信息和处理状态
8. 安全注意事项
- 密码安全: 不要将
.env
文件提交到版本控制系统 - 生产环境: 将
DEBUG
设置为False
- 防火墙: 如果在服务器上运行,确保只开放必要的端口
- 定期备份: 备份
data/emails.db
数据库文件
这个版本使用 Flask 和简单的 HTML/CSS 实现了完整的功能,部署简单,维护方便,非常适合个人使用或小团队部署。
更多推荐
所有评论(0)