scheduler_service.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import smtplib
  2. from email.mime.text import MIMEText
  3. from email.mime.multipart import MIMEMultipart
  4. from email.header import Header
  5. import yaml
  6. import datetime
  7. from apscheduler.schedulers.background import BackgroundScheduler
  8. from application.self_check_service import run_all_checks
  9. from application.uap_message_service import send_self_check_notification
  10. from utils.logger_config import logger
  11. class SelfCheckScheduler:
  12. def __init__(self):
  13. self.scheduler = BackgroundScheduler()
  14. self.scheduler.add_job(self.run_daily_check, 'cron', hour=7, minute=50)
  15. def start(self):
  16. try:
  17. self.scheduler.start()
  18. logger.info("自检定时任务调度器已启动 (每天 07:50)")
  19. except Exception as e:
  20. logger.error(f"启动定时任务失败: {e}")
  21. def load_email_config(self):
  22. try:
  23. with open('email_config.yaml', 'r', encoding='utf-8') as f:
  24. return yaml.safe_load(f).get('email', {})
  25. except Exception as e:
  26. logger.error(f"读取邮件配置失败: {e}")
  27. return {}
  28. def format_report_html(self, results):
  29. """生成HTML格式的检测报告"""
  30. html = """
  31. <html>
  32. <head>
  33. <style>
  34. body { font-family: Arial, sans-serif; }
  35. .module { margin-bottom: 20px; border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
  36. .module-title { font-weight: bold; font-size: 1.1em; color: #333; margin-bottom: 10px; background: #f9f9f9; padding: 5px; }
  37. .device-table { width: 100%; border-collapse: collapse; }
  38. .device-table th, .device-table td { border: 1px solid #eee; padding: 8px; text-align: left; }
  39. .online { color: green; font-weight: bold; }
  40. .offline { color: red; font-weight: bold; }
  41. .summary { margin-bottom: 20px; padding: 15px; background: #f0f8ff; border-radius: 5px; }
  42. </style>
  43. </head>
  44. <body>
  45. <h2>🛡️ 系统设备自检报告</h2>
  46. <p>生成时间: """ + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """</p>
  47. """
  48. total_devices = 0
  49. total_online = 0
  50. total_offline = 0
  51. modules = {
  52. "kodi": "电视系统 (Kodi)",
  53. "door": "门禁系统",
  54. "led": "展品灯座",
  55. "ha": "展厅灯光 (HA)",
  56. "pc": "展厅PC"
  57. }
  58. details_html = ""
  59. for key, name in modules.items():
  60. device_list = results.get(key, [])
  61. module_online = 0
  62. module_total = len(device_list)
  63. rows = ""
  64. for device in device_list:
  65. is_online = device.get('is_online', False)
  66. if is_online:
  67. module_online += 1
  68. total_online += 1
  69. else:
  70. total_offline += 1
  71. total_devices += 1
  72. status_cls = "online" if is_online else "offline"
  73. status_text = "在线" if is_online else "离线"
  74. extra_info = []
  75. if device.get('ip'): extra_info.append(f"IP: {device.get('ip')}")
  76. if device.get('entity_id'): extra_info.append(f"Entity: {device.get('entity_id')}")
  77. if device.get('error'): extra_info.append(f"Error: {device.get('error')}")
  78. rows += f"""
  79. <tr>
  80. <td>{device.get('name', 'Unknown')} (ID: {device.get('id')})</td>
  81. <td class="{status_cls}">{status_text}</td>
  82. <td>{', '.join(extra_info)}</td>
  83. </tr>
  84. """
  85. if not rows:
  86. rows = "<tr><td colspan='3'>未配置设备或检测失败</td></tr>"
  87. details_html += f"""
  88. <div class="module">
  89. <div class="module-title">{name} - 在线: {module_online}/{module_total}</div>
  90. <table class="device-table">
  91. <tr>
  92. <th>设备名称</th>
  93. <th>状态</th>
  94. <th>详细信息</th>
  95. </tr>
  96. {rows}
  97. </table>
  98. </div>
  99. """
  100. html += f"""
  101. <div class="summary">
  102. <strong>总览:</strong> 总设备数 {total_devices},<span class="online">在线 {total_online}</span>,<span class="offline">离线/异常 {total_offline}</span>
  103. </div>
  104. {details_html}
  105. </body>
  106. </html>
  107. """
  108. return html
  109. def _format_summary_text(self, results):
  110. """从检测结果生成简短摘要文本"""
  111. total_devices = total_online = total_offline = 0
  112. modules = {"kodi", "door", "led", "ha", "pc"}
  113. for key in modules:
  114. for device in results.get(key, []):
  115. total_devices += 1
  116. if device.get('is_online'):
  117. total_online += 1
  118. else:
  119. total_offline += 1
  120. return f"总设备数 {total_devices},在线 {total_online},离线/异常 {total_offline}"
  121. def send_email(self, subject, content, receivers=None):
  122. config = self.load_email_config()
  123. if not config:
  124. logger.error("缺少邮件配置,取消发送")
  125. return
  126. smtp_server = config.get('smtp_server')
  127. smtp_port = config.get('smtp_port')
  128. username = config.get('username')
  129. password = config.get('password')
  130. sender = config.get('sender')
  131. # 如果未传入接收者,则使用配置中的默认接收者
  132. if not receivers:
  133. receivers = config.get('receivers', [])
  134. # 确保 receivers 是列表
  135. if isinstance(receivers, str):
  136. receivers = [receivers]
  137. use_ssl = config.get('use_ssl', True)
  138. if not all([smtp_server, username, password, sender, receivers]):
  139. logger.error("邮件配置不完整或无接收者")
  140. return
  141. message = MIMEMultipart()
  142. message['From'] = Header(sender, 'utf-8')
  143. message['To'] = Header(",".join(receivers), 'utf-8')
  144. message['Subject'] = Header(subject, 'utf-8')
  145. message.attach(MIMEText(content, 'html', 'utf-8'))
  146. try:
  147. if use_ssl:
  148. server = smtplib.SMTP_SSL(smtp_server, smtp_port)
  149. else:
  150. server = smtplib.SMTP(smtp_server, smtp_port)
  151. server.login(username, password)
  152. server.sendmail(sender, receivers, message.as_string())
  153. server.quit()
  154. logger.info(f"自检报告邮件已发送至: {receivers}")
  155. except Exception as e:
  156. logger.error(f"发送邮件失败: {e}")
  157. def run_daily_check(self):
  158. logger.info("开始执行每日自检任务...")
  159. try:
  160. results = run_all_checks()
  161. html_report = self.format_report_html(results)
  162. self.send_email("每日设备自检报告", html_report)
  163. # 向统一登录平台运维人员推送通知
  164. summary = self._format_summary_text(results)
  165. send_self_check_notification(summary)
  166. except Exception as e:
  167. logger.error(f"每日自检任务异常: {e}")
  168. # 单例
  169. scheduler_service = SelfCheckScheduler()
  170. def start_scheduler():
  171. scheduler_service.start()