优化提示器在项目中的复用性及可维护性

This commit is contained in:
2026-05-07 22:01:48 +08:00
parent d69e8b88ea
commit 1ab48cc102
10 changed files with 299 additions and 40 deletions
-4
View File
@@ -53,10 +53,6 @@ else:
THIS_BASE_URL = PROTOCOL + "127.0.0.1:" + str(PORT)
PLANNER_MAILS = os.getenv("PLANNER_MAILS", "")
ENGINEER_MAILS = os.getenv("ENGINEER_MAILS", "")
# 事件流量控制
MAX_EVENTS_BATCH_SIZE = max(1, int(os.getenv("MAX_EVENTS_BATCH_SIZE") or 1))
MAX_EVENTS_PER_SECOND = max(1, int(os.getenv("MAX_EVENTS_PER_SECOND") or 1))
+8 -12
View File
@@ -21,8 +21,8 @@ from core.settings import MYAPS_DB_SET, MYAPS_MAIN_DB, THIS_BASE_URL, SCHEDULER_
from .._base import (
get_scheduler_minute, cron_task, CLIENT_LOGGER, CLIENT_SESSION, PROJECT_JSON_FILE,
ApsPayloadSponsor, EventResultPoster, get_session, CacheItem,
QqEmailReminder, AlertType, async_rate_limit, event_batch_handler,
TSupply, PLANNER_MAILS, ENGINEER_MAILS, async_service_operation, batch_service_operation
AlertType, async_rate_limit, event_batch_handler,
TSupply, async_service_operation, batch_service_operation
)
@@ -59,18 +59,14 @@ hacyxs_tplus_conn = get_tplus_conn()
# ⬇️ 通知相关
#################################################################################
planner_email_reminder = QqEmailReminder(
smtp_user="2982212683@qq.com",
smtp_password="jyboujldhplddhdf",
email_from="2982212683@qq.com",
email_to=PLANNER_MAILS,
)
# 从 send_alert.py 导入业务告警专用提示器
from .remind import bus_reminder, ops_reminder
# ⬇️binlog监听告警注册
from apps.data_opt.utils.binlog_listener import binlog_listener as bl
bl.register_alert_handler(planner_email_reminder)
bl.register_alert_handler(ops_reminder)
CLIENT_LOGGER.info("binlog监听告警提醒器已注册")
@@ -183,7 +179,7 @@ def create_custom_rs_push_model(_aps: ApsPayloadSponsor):
return CustomRsPushModel
@event_batch_handler(reminder=planner_email_reminder)
@event_batch_handler(reminder=bus_reminder)
@batch_service_operation(module="事件处理")
async def batch_handle_pl_status_a2e(event_data_list: list[dict], _erp: EventResultPoster, description="下达生产加工单至 T+"):
await TplusMo.create_batch(
@@ -196,7 +192,7 @@ async def batch_handle_pl_status_a2e(event_data_list: list[dict], _erp: EventRes
)
@event_batch_handler(reminder=planner_email_reminder, remind_start=False)
@event_batch_handler(reminder=bus_reminder, remind_start=False)
@batch_service_operation(module="事件处理")
async def batch_handle_pl_to_mo(event_data_list: list[dict], _erp: EventResultPoster, description="推送领料申请至 T+"):
await TplusRs.create_batch(
@@ -207,7 +203,7 @@ async def batch_handle_pl_to_mo(event_data_list: list[dict], _erp: EventResultPo
)
@event_batch_handler(reminder=planner_email_reminder)
@event_batch_handler(reminder=bus_reminder)
@batch_service_operation(module="事件处理")
async def batch_handle_pr_status_a2e(pr_data_list: list[dict], _erp: EventResultPoster, description="推送请购单至 T+"):
await TplusPr.create(
+12
View File
@@ -46,5 +46,17 @@
"itemno": "P01",
"plant": "chaoyue",
"planner": "chaoyue"
},
"ops_reminder": {
"smtp_user": "2982212683@qq.com",
"smtp_password": "jyboujldhplddhdf",
"email_from": "2982212683@qq.com",
"email_to": "2982212683@qq.com"
},
"bus_reminder": {
"smtp_user": "2982212683@qq.com",
"smtp_password": "jyboujldhplddhdf",
"email_from": "2982212683@qq.com",
"email_to": "2982212683@qq.com"
}
}
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
租户独立告警发送脚本
可被 service_daemon.ps1 或其他外部程序调用
用法:
python remind.py --message "服务异常" --level error --subject "告警测试"
"""
import argparse
import asyncio
import json
import os
import sys
from datetime import datetime
# 将项目根目录添加到路径
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, project_root)
from globalobjects.reminder import QqEmailReminder
def load_project_config():
"""从环境变量和JSON配置文件加载项目配置"""
# 读取 .env 文件获取 PROJECT_JSON 配置
env_path = os.path.join(project_root, '.env')
project_json = 'dev'
if os.path.exists(env_path):
with open(env_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith('PROJECT_JSON='):
project_json = line.split('=', 1)[1].strip()
break
# 构建配置文件路径
project_dir = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
config_path = os.path.join(project_root, 'project_files', project_dir, f'{project_json}.json')
if not os.path.exists(config_path):
raise FileNotFoundError(f"配置文件不存在: {config_path}")
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
# 全局配置缓存
project_config = load_project_config()
ops_config = project_config.get('ops_reminder', {})
bus_config = project_config.get('bus_reminder', {})
ops_reminder = QqEmailReminder(
smtp_user=ops_config.get('smtp_user', ''),
smtp_password=ops_config.get('smtp_password', ''),
email_from=ops_config.get('email_from', ''),
email_to=ops_config.get('email_to', ''),
)
bus_reminder = QqEmailReminder(
smtp_user=bus_config.get('smtp_user', ''),
smtp_password=bus_config.get('smtp_password', ''),
email_from=bus_config.get('email_from', ''),
email_to=bus_config.get('email_to', ''),
)
async def send_alert(message: str, level: str = "warning", subject: str = None):
"""发送告警"""
if subject is None:
level_prefix = {
"info": "️ 信息",
"warning": "⚠️ 警告",
"error": "❌ 错误"
}.get(level, "🔔")
subject = f"{level_prefix} 系统告警"
alert_content = {
"level": level,
"message": message,
"timestamp": datetime.now().isoformat(),
"source": "service_daemon"
}
await ops_reminder.remind(alert_content)
return True
def main():
parser = argparse.ArgumentParser(description="项目告警发送工具")
parser.add_argument("--message", required=True, help="告警消息内容")
parser.add_argument("--level", default="warning",
choices=["info", "warning", "error"], help="告警级别")
parser.add_argument("--subject", help="邮件主题(可选)")
args = parser.parse_args()
try:
asyncio.run(send_alert(args.message, args.level, args.subject))
print(f"[SUCCESS] Alert sent: {args.subject or args.message}")
return 0
except Exception as e:
print(f"[ERROR] Failed to send alert: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
+2 -10
View File
@@ -229,19 +229,11 @@ async def task_push_seasonpr_to_srm():
#################################################################################
# ⬇️APS事件
#################################################################################
from core.settings import PLANNER_MAILS
planner_email_reminder = QqEmailReminder(
smtp_user="2982212683@qq.com",
smtp_password="jyboujldhplddhdf",
email_from="2982212683@qq.com",
email_to=PLANNER_MAILS,
)
from .remind import ops_reminder, bus_reminder
@event_batch_handler(reminder=planner_email_reminder)
@event_batch_handler(reminder=bus_reminder)
async def batch_handle_pl_status_a2e(event_data_list: List[Dict], _erp: EventResultPoster, description="PL 单据下达"):
"""
Args:
+12
View File
@@ -40,5 +40,17 @@
"planner": "haida",
"leadday_e": 1,
"leadday_f": 1
},
"ops_reminder": {
"smtp_user": "2982212683@qq.com",
"smtp_password": "jyboujldhplddhdf",
"email_from": "2982212683@qq.com",
"email_to": "2982212683@qq.com"
},
"bus_reminder": {
"smtp_user": "2982212683@qq.com",
"smtp_password": "jyboujldhplddhdf",
"email_from": "2982212683@qq.com",
"email_to": "2982212683@qq.com"
}
}
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
租户独立告警发送脚本
可被 service_daemon.ps1 或其他外部程序调用
用法:
python remind.py --message "服务异常" --level error --subject "告警测试"
"""
import argparse
import asyncio
import json
import os
import sys
from datetime import datetime
# 将项目根目录添加到路径
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, project_root)
from globalobjects.reminder import QqEmailReminder
def load_project_config():
"""从环境变量和JSON配置文件加载项目配置"""
# 读取 .env 文件获取 PROJECT_JSON 配置
env_path = os.path.join(project_root, '.env')
project_json = 'dev'
if os.path.exists(env_path):
with open(env_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith('PROJECT_JSON='):
project_json = line.split('=', 1)[1].strip()
break
# 构建配置文件路径
project_dir = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
config_path = os.path.join(project_root, 'project_files', project_dir, f'{project_json}.json')
if not os.path.exists(config_path):
raise FileNotFoundError(f"配置文件不存在: {config_path}")
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
# 全局配置缓存
project_config = load_project_config()
ops_config = project_config.get('ops_reminder', {})
bus_config = project_config.get('bus_reminder', {})
ops_reminder = QqEmailReminder(
smtp_user=ops_config.get('smtp_user', ''),
smtp_password=ops_config.get('smtp_password', ''),
email_from=ops_config.get('email_from', ''),
email_to=ops_config.get('email_to', ''),
)
bus_reminder = QqEmailReminder(
smtp_user=bus_config.get('smtp_user', ''),
smtp_password=bus_config.get('smtp_password', ''),
email_from=bus_config.get('email_from', ''),
email_to=bus_config.get('email_to', ''),
)
async def send_alert(message: str, level: str = "warning", subject: str = None):
"""发送告警"""
if subject is None:
level_prefix = {
"info": "️ 信息",
"warning": "⚠️ 警告",
"error": "❌ 错误"
}.get(level, "🔔")
subject = f"{level_prefix} 系统告警"
alert_content = {
"level": level,
"message": message,
"timestamp": datetime.now().isoformat(),
"source": "service_daemon"
}
await ops_reminder.remind(alert_content)
return True
def main():
parser = argparse.ArgumentParser(description="项目告警发送工具")
parser.add_argument("--message", required=True, help="告警消息内容")
parser.add_argument("--level", default="warning",
choices=["info", "warning", "error"], help="告警级别")
parser.add_argument("--subject", help="邮件主题(可选)")
args = parser.parse_args()
try:
asyncio.run(send_alert(args.message, args.level, args.subject))
print(f"[SUCCESS] Alert sent: {args.subject or args.message}")
return 0
except Exception as e:
print(f"[ERROR] Failed to send alert: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
+1 -1
View File
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta
from pydantic import BaseModel as PydanticModel
# from tortoise import Tortoise
from core.settings import MAX_EVENTS_PER_SECOND, SCHEDULER_MINUTE, PLANNER_MAILS, ENGINEER_MAILS
from core.settings import MAX_EVENTS_PER_SECOND, SCHEDULER_MINUTE
from globalobjects.globalconst import OrderStatusEnum
-4
View File
@@ -58,10 +58,6 @@ MAX_EVENTS_BATCH_SIZE=1
# 全局 每秒 最大处理事件数,最少为 1
MAX_EVENTS_PER_SECOND=10
# 计划员邮箱,用于发送操作通知
PLANNER_MAILS=2982212683@qq.com
# 运维人员邮箱,用于发送告警通知
ENGINEER_MAILS=2982212683@qq.com
########################################################################
+40 -9
View File
@@ -1,6 +1,7 @@
param (
[string]$ServiceName = "",
[string]$LogDir = "",
[string]$ProjectDir = "",
[string]$EmailEnabled = "false",
[string]$EmailTo = "",
[string]$EmailFrom = "",
@@ -67,6 +68,9 @@ if (Test-Path $EnvFile) {
if ($envVariables.ContainsKey("SERVICE_NAME")) { $ServiceName = $envVariables["SERVICE_NAME"] }
if ($envVariables.ContainsKey("SERVICE_DAEMON_LOG_DIR")) { $LogDir = $envVariables["SERVICE_DAEMON_LOG_DIR"] }
# Project directory (tenant)
if ($envVariables.ContainsKey("PROJECT_DIR")) { $ProjectDir = $envVariables["PROJECT_DIR"] }
# Email configuration
if ($envVariables.ContainsKey("SERVICE_DAEMON_EMAIL_ENABLED")) { $EmailEnabled = $envVariables["SERVICE_DAEMON_EMAIL_ENABLED"].ToLower() }
if ($envVariables.ContainsKey("SERVICE_DAEMON_EMAIL_TO")) {
@@ -132,7 +136,8 @@ function Write-Log {
function Send-EmailNotification {
param (
[string]$Subject,
[string]$Body
[string]$Body,
[string]$Level = "error"
)
if ($EmailEnabled -ne "true") {
@@ -140,6 +145,32 @@ function Send-EmailNotification {
return
}
# 优先使用租户的 remind.py 脚本
if (-not [string]::IsNullOrEmpty($ProjectDir)) {
$remindScript = Join-Path $projectRoot "project_files\$ProjectDir\remind.py"
if (Test-Path $remindScript) {
try {
$pythonExe = "python"
$result = & $pythonExe $remindScript --message $Body --level $Level --subject $Subject 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Log "Email notification sent via tenant script: $Subject"
return
} else {
Write-Log "Tenant remind.py failed (exit code: $LASTEXITCODE): $result" -Level "WARN"
# 继续尝试使用 PowerShell 发送作为降级方案
}
} catch {
Write-Log "Failed to execute tenant remind.py: $_" -Level "WARN"
# 继续尝试使用 PowerShell 发送作为降级方案
}
} else {
Write-Log "Tenant remind.py not found: $remindScript" -Level "WARN"
}
}
# 降级方案:使用 PowerShell Send-MailMessage
if (([string]::IsNullOrEmpty($EmailTo) -and $EmailTo -isnot [array]) -or [string]::IsNullOrEmpty($EmailFrom) -or [string]::IsNullOrEmpty($SmtpServer)) {
Write-Log "Email configuration incomplete, skipping email send" -Level "WARN"
return
@@ -157,7 +188,7 @@ function Send-EmailNotification {
} else {
$recipients = $EmailTo
}
Write-Log "Email notification sent successfully to $recipients: $Subject"
Write-Log "Email notification sent via PowerShell to $recipients: $Subject"
} catch {
Write-Log "Failed to send email notification: $_" -Level "ERROR"
}
@@ -208,7 +239,7 @@ function Check-ServiceStatus {
$message = "Service $ServiceName status is abnormal: $($service.Status)"
Write-Log $message -Level "ERROR"
Send-EmailNotification -Subject "服务异常:$ServiceName" -Body $message
Send-EmailNotification -Subject "服务异常:$ServiceName" -Body $message -Level "error"
Send-SystemNotification -Title "服务异常" -Message $message
if ($AutoRestart) {
@@ -221,20 +252,20 @@ function Check-ServiceStatus {
if ($serviceAfterRestart.Status -eq "Running") {
$successMessage = "Service $ServiceName restarted successfully"
Write-Log $successMessage
Send-EmailNotification -Subject "服务已重启:$ServiceName" -Body $successMessage
Send-EmailNotification -Subject "服务已重启:$ServiceName" -Body $successMessage -Level "info"
Send-SystemNotification -Title "服务已重启" -Message $successMessage
return $true
} else {
$failMessage = "Service $ServiceName restart failed, current status: $($serviceAfterRestart.Status)"
Write-Log $failMessage -Level "ERROR"
Send-EmailNotification -Subject "服务重启失败:$ServiceName" -Body $failMessage
Send-EmailNotification -Subject "服务重启失败:$ServiceName" -Body $failMessage -Level "error"
Send-SystemNotification -Title "服务重启失败" -Message $failMessage
return $false
}
} catch {
$errorMessage = "Failed to restart service $ServiceName: $_"
Write-Log $errorMessage -Level "ERROR"
Send-EmailNotification -Subject "服务重启失败:$ServiceName" -Body $errorMessage
Send-EmailNotification -Subject "服务重启失败:$ServiceName" -Body $errorMessage -Level "error"
Send-SystemNotification -Title "服务重启失败" -Message $errorMessage
return $false
}
@@ -245,7 +276,7 @@ function Check-ServiceStatus {
} catch {
$errorMessage = "Service $ServiceName not found or access denied: $_"
Write-Log $errorMessage -Level "ERROR"
Send-EmailNotification -Subject "服务不存在:$ServiceName" -Body $errorMessage
Send-EmailNotification -Subject "服务不存在:$ServiceName" -Body $errorMessage -Level "error"
Send-SystemNotification -Title "服务不存在" -Message $errorMessage
return $false
}
@@ -272,7 +303,7 @@ function Check-ServiceHealth {
if ($errorCount -gt 0) {
$message = "Service $ServiceName has recent errors in log file: $errorCount errors in last 5 minutes"
Write-Log $message -Level "WARN"
Send-EmailNotification -Subject "服务日志异常:$ServiceName" -Body $message
Send-EmailNotification -Subject "服务日志异常:$ServiceName" -Body $message -Level "warning"
Send-SystemNotification -Title "服务日志异常" -Message $message
return $false
}
@@ -313,7 +344,7 @@ try {
Main
} catch {
Write-Log "Fatal error in service daemon: $_" -Level "ERROR"
Send-EmailNotification -Subject "守护脚本错误" -Body "Service daemon script encountered a fatal error: $_"
Send-EmailNotification -Subject "守护脚本错误" -Body "Service daemon script encountered a fatal error: $_" -Level "error"
Send-SystemNotification -Title "守护脚本错误" -Message "Service daemon script encountered a fatal error"
exit 1
}