| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import smtplib
- from email.mime.text import MIMEText
- from email.mime.multipart import MIMEMultipart
- from email.header import Header
- import yaml
- import datetime
- from apscheduler.schedulers.background import BackgroundScheduler
- from application.self_check_service import run_all_checks
- from application.uap_message_service import send_self_check_notification
- from utils.logger_config import logger
- class SelfCheckScheduler:
- def __init__(self):
- self.scheduler = BackgroundScheduler()
- self.scheduler.add_job(self.run_daily_check, 'cron', hour=7, minute=50)
-
- def start(self):
- try:
- self.scheduler.start()
- logger.info("自检定时任务调度器已启动 (每天 07:50)")
- except Exception as e:
- logger.error(f"启动定时任务失败: {e}")
- def load_email_config(self):
- try:
- with open('email_config.yaml', 'r', encoding='utf-8') as f:
- return yaml.safe_load(f).get('email', {})
- except Exception as e:
- logger.error(f"读取邮件配置失败: {e}")
- return {}
- def format_report_html(self, results):
- """生成HTML格式的检测报告"""
- html = """
- <html>
- <head>
- <style>
- body { font-family: Arial, sans-serif; }
- .module { margin-bottom: 20px; border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
- .module-title { font-weight: bold; font-size: 1.1em; color: #333; margin-bottom: 10px; background: #f9f9f9; padding: 5px; }
- .device-table { width: 100%; border-collapse: collapse; }
- .device-table th, .device-table td { border: 1px solid #eee; padding: 8px; text-align: left; }
- .online { color: green; font-weight: bold; }
- .offline { color: red; font-weight: bold; }
- .summary { margin-bottom: 20px; padding: 15px; background: #f0f8ff; border-radius: 5px; }
- </style>
- </head>
- <body>
- <h2>🛡️ 系统设备自检报告</h2>
- <p>生成时间: """ + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """</p>
- """
- total_devices = 0
- total_online = 0
- total_offline = 0
- modules = {
- "kodi": "电视系统 (Kodi)",
- "door": "门禁系统",
- "led": "展品灯座",
- "ha": "展厅灯光 (HA)",
- "pc": "展厅PC"
- }
- details_html = ""
- for key, name in modules.items():
- device_list = results.get(key, [])
- module_online = 0
- module_total = len(device_list)
-
- rows = ""
- for device in device_list:
- is_online = device.get('is_online', False)
- if is_online:
- module_online += 1
- total_online += 1
- else:
- total_offline += 1
- total_devices += 1
- status_cls = "online" if is_online else "offline"
- status_text = "在线" if is_online else "离线"
-
- extra_info = []
- if device.get('ip'): extra_info.append(f"IP: {device.get('ip')}")
- if device.get('entity_id'): extra_info.append(f"Entity: {device.get('entity_id')}")
- if device.get('error'): extra_info.append(f"Error: {device.get('error')}")
- rows += f"""
- <tr>
- <td>{device.get('name', 'Unknown')} (ID: {device.get('id')})</td>
- <td class="{status_cls}">{status_text}</td>
- <td>{', '.join(extra_info)}</td>
- </tr>
- """
-
- if not rows:
- rows = "<tr><td colspan='3'>未配置设备或检测失败</td></tr>"
- details_html += f"""
- <div class="module">
- <div class="module-title">{name} - 在线: {module_online}/{module_total}</div>
- <table class="device-table">
- <tr>
- <th>设备名称</th>
- <th>状态</th>
- <th>详细信息</th>
- </tr>
- {rows}
- </table>
- </div>
- """
- html += f"""
- <div class="summary">
- <strong>总览:</strong> 总设备数 {total_devices},<span class="online">在线 {total_online}</span>,<span class="offline">离线/异常 {total_offline}</span>
- </div>
- {details_html}
- </body>
- </html>
- """
- return html
- def _format_summary_text(self, results):
- """从检测结果生成简短摘要文本"""
- total_devices = total_online = total_offline = 0
- modules = {"kodi", "door", "led", "ha", "pc"}
- for key in modules:
- for device in results.get(key, []):
- total_devices += 1
- if device.get('is_online'):
- total_online += 1
- else:
- total_offline += 1
- return f"总设备数 {total_devices},在线 {total_online},离线/异常 {total_offline}"
- def send_email(self, subject, content, receivers=None):
- config = self.load_email_config()
- if not config:
- logger.error("缺少邮件配置,取消发送")
- return
- smtp_server = config.get('smtp_server')
- smtp_port = config.get('smtp_port')
- username = config.get('username')
- password = config.get('password')
- sender = config.get('sender')
-
- # 如果未传入接收者,则使用配置中的默认接收者
- if not receivers:
- receivers = config.get('receivers', [])
-
- # 确保 receivers 是列表
- if isinstance(receivers, str):
- receivers = [receivers]
- use_ssl = config.get('use_ssl', True)
- if not all([smtp_server, username, password, sender, receivers]):
- logger.error("邮件配置不完整或无接收者")
- return
- message = MIMEMultipart()
- message['From'] = Header(sender, 'utf-8')
- message['To'] = Header(",".join(receivers), 'utf-8')
- message['Subject'] = Header(subject, 'utf-8')
-
- message.attach(MIMEText(content, 'html', 'utf-8'))
- try:
- if use_ssl:
- server = smtplib.SMTP_SSL(smtp_server, smtp_port)
- else:
- server = smtplib.SMTP(smtp_server, smtp_port)
-
- server.login(username, password)
- server.sendmail(sender, receivers, message.as_string())
- server.quit()
- logger.info(f"自检报告邮件已发送至: {receivers}")
- except Exception as e:
- logger.error(f"发送邮件失败: {e}")
- def run_daily_check(self):
- logger.info("开始执行每日自检任务...")
- try:
- results = run_all_checks()
- html_report = self.format_report_html(results)
- self.send_email("每日设备自检报告", html_report)
- # 向统一登录平台运维人员推送通知
- summary = self._format_summary_text(results)
- send_self_check_notification(summary)
- except Exception as e:
- logger.error(f"每日自检任务异常: {e}")
- # 单例
- scheduler_service = SelfCheckScheduler()
- def start_scheduler():
- scheduler_service.start()
|