mirror of
https://github.com/rnvm9wjdtj-bot/myaps_api.git
synced 2026-06-02 05:54:40 +00:00
优化提示器在项目中的复用性及可维护性
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
########################################################################
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user