feat(report): 添加定时生成PDF报告功能并重构邮件发送任务

将PDF生成逻辑从邮件发送任务中分离,新增独立定时任务
更新README文档说明PDF生成配置和使用方法
This commit is contained in:
2026-01-09 20:05:25 +08:00
parent 23ca4fbee2
commit c4d307136a
2 changed files with 111 additions and 11 deletions

View File

@@ -423,6 +423,87 @@ def setup_daily_report_task():
- 配置监控系统(如 Prometheus + Grafana - 配置监控系统(如 Prometheus + Grafana
- 启用 Celery 监控工具(如 Flower - 启用 Celery 监控工具(如 Flower
## 定时生成PDF文件配置
除了定时发送邮件外系统还支持单独定时生成PDF文件便于在服务器上保存历史报告。
### 1. 配置定时生成PDF任务
#### 方式一:通过 Django 管理后台(推荐)
1. 登录 Django 管理后台(/houtai
2. 找到 **Periodic tasks**(周期性任务)
3. 点击 **Add** 按钮添加新任务
4. 配置任务:
- **Name**: 任务名称每日PDF报告生成
- **Task (registered)**: 选择 `core.tasks.generate_daily_pdf_report`
- **Interval**: 设置执行间隔(如:每天)
- **Enabled**: 勾选启用
5. 点击 **Save** 保存
#### 方式二:通过代码配置
在 `core/tasks.py` 中添加定时任务配置:
```python
from celery import shared_task
from django_celery_beat.models import PeriodicTask, IntervalSchedule
# 创建或更新PDF生成定时任务
def setup_daily_pdf_task():
# 创建每天执行的间隔
schedule, created = IntervalSchedule.objects.get_or_create(
every=1,
period=IntervalSchedule.DAYS,
)
# 创建或更新定时任务
task, created = PeriodicTask.objects.update_or_create(
name='Daily PDF Generation Task',
defaults={
'interval': schedule,
'task': 'core.tasks.generate_daily_pdf_report',
},
)
return task
```
然后在 Django 启动时调用此函数(如在 `core/apps.py` 中)。
### 2. PDF文件存储位置
生成的PDF文件默认存储在项目的 `reports` 目录下,命名格式为 `report_YYYY-MM-DD.pdf`。
可以在 `settings.py` 中修改存储位置:
```python
# Reports files configuration
REPORTS_URL = '/reports/'
REPORTS_ROOT = BASE_DIR / 'reports' # 可以修改为其他路径
```
### 3. 验证PDF生成任务
- 查看 Celery 日志:`tail -f /var/log/celery/worker.log`
- 检查PDF文件是否生成`ls -la /path/to/diary-family/reports/`
- 在 Django 管理后台查看任务执行记录
### 4. 常见问题排查
1. **PDF生成失败**
- 检查 WeasyPrint 库是否正确安装:`pip list | grep weasyprint`
- 检查系统字体是否完整PDF生成需要系统字体支持
- 查看 Celery 日志中的错误信息
2. **任务执行但没有生成文件**
- 检查 `REPORTS_ROOT` 目录权限:确保 Celery 进程有写入权限
- 检查磁盘空间是否充足
- 查看 Celery 日志中的详细信息
3. **PDF内容不完整**
- 检查模板文件 `core/templates/core/report_pdf.html` 是否完整
- 确保所有依赖的 CSS 和图像资源都能正常访问
## Celery 监控(可选) ## Celery 监控(可选)
使用 Flower 监控 Celery 任务: 使用 Flower 监控 Celery 任务:

View File

@@ -32,9 +32,9 @@ from .models import (
) )
@shared_task @shared_task
def send_daily_report(): def generate_daily_pdf_report():
"""发送每日报告""" """生成每日PDF报告"""
logger.info("开始执行每日报告发送任务") logger.info("开始执行每日PDF报告生成任务")
# 检查WeasyPrint是否可用 # 检查WeasyPrint是否可用
if not is_weasyprint_available(): if not is_weasyprint_available():
@@ -44,14 +44,6 @@ def send_daily_report():
today = timezone.now().date() today = timezone.now().date()
today_str = today.strftime('%Y-%m-%d') today_str = today.strftime('%Y-%m-%d')
# 获取系统配置
config = SystemConfig.get_config()
# 检查邮件配置是否完整
if not all([config.smtp_server, config.smtp_username, config.smtp_password, config.recipient_email]):
logger.error("邮件配置不完整,无法发送邮件")
return False
# 生成报告数据 # 生成报告数据
report_date = today report_date = today
yesterday = report_date - timedelta(days=1) yesterday = report_date - timedelta(days=1)
@@ -92,16 +84,43 @@ def send_daily_report():
from weasyprint import HTML from weasyprint import HTML
HTML(string=html_string).write_pdf(pdf_path) HTML(string=html_string).write_pdf(pdf_path)
logger.info(f"PDF报告生成成功: {pdf_path}") logger.info(f"PDF报告生成成功: {pdf_path}")
return True
except Exception as e: except Exception as e:
logger.error(f"PDF报告生成失败: {str(e)}") logger.error(f"PDF报告生成失败: {str(e)}")
return False return False
@shared_task
def send_daily_report():
"""发送每日报告"""
logger.info("开始执行每日报告发送任务")
# 先生成PDF报告
pdf_generated = generate_daily_pdf_report()
if not pdf_generated:
logger.error("PDF报告生成失败无法发送邮件")
return False
today = timezone.now().date()
today_str = today.strftime('%Y-%m-%d')
# 获取系统配置
config = SystemConfig.get_config()
# 检查邮件配置是否完整
if not all([config.smtp_server, config.smtp_username, config.smtp_password, config.recipient_email]):
logger.error("邮件配置不完整,无法发送邮件")
return False
# 发送邮件 # 发送邮件
subject = f"家庭日报 - {today_str}" subject = f"家庭日报 - {today_str}"
message = f"这是您的家庭日报,日期:{today_str}" message = f"这是您的家庭日报,日期:{today_str}"
from_email = config.smtp_username from_email = config.smtp_username
recipient_list = [config.recipient_email] recipient_list = [config.recipient_email]
# PDF文件路径
pdf_file = f"report_{today_str}.pdf"
pdf_path = os.path.join(settings.REPORTS_ROOT, pdf_file)
try: try:
email = EmailMessage( email = EmailMessage(
subject=subject, subject=subject,