Files
Web_AutoNotificatPhone_Serv…/Models/TimerClass.cs

743 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using AutoNotificatPhone.Controllers;
using Common;
using Microsoft.Extensions.Configuration;
using NLog;
using Npgsql;
using System.Diagnostics;
namespace AutoNotificatPhone.Models
{
/// <summary>
/// 定时后台服务:
/// 1) 每分钟固定时刻执行巡检
/// 2) 执行整点/定时通知
/// 3) 执行 Redis 指标告警
/// 4) 执行 PostgreSQL 心跳检查告警
/// </summary>
public class TimerClass : BackgroundService
{
// NLog 记录器
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
// 告警通知接收手机号
private static readonly string Mobile1 = "13509214696";
private static readonly string Mobile2 = "16620970520";
// 每日定时任务触发小时(北京时间)
private static readonly HashSet<int> DailyTaskHours = [10, 15, 22];
// 每分钟在第 30 秒触发一次巡检
private const int RunSecond = 30;
// 主循环异常后的重试等待时间(秒)
private const int RetryDelaySeconds = 10;
// 默认短信/电话任务过期时间(秒)
private const int SmsDeadlineSeconds = 1800;
private const int CallDeadlineSeconds = 900;
// 扩展短信/电话任务过期时间(秒)
private const int ExtendedSmsDeadlineSeconds = 3600;
private const int ExtendedCallDeadlineSeconds = 1800;
// Kafka 心跳超时阈值(分钟)
private const int KafkaStaleMinutes = 5;
// 数据库连接失败累计到 N 次才触发一次告警,避免告警风暴
private const int KafkaDbAlertTriggerCount = 8;
// 接收包“低值”判定阈值
private const int RecvPackageLowThreshold = 70000;
// 用于防止同一整点任务重复执行
private readonly Dictionary<DateTime, bool> _executedTasks = new();
// 复用 API 控制器发送短信/电话任务
private readonly CallAndMsgController _callAndMsgController = new();
// 从 appsettings 读取 Postgres 配置
private readonly IConfiguration _configuration;
// Kafka 数据库连接失败计数器
private int _kafkaDbConnectionAlertCount;
/// <summary>
/// 构造函数,注入配置对象。
/// </summary>
/// <param name="configuration">应用配置(用于读取 Postgres 节点)</param>
public TimerClass(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// 后台服务主循环。
/// </summary>
/// <param name="cancellationToken">服务取消令牌</param>
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
// 等待到下一次固定执行时刻(每分钟第 RunSecond 秒)
await DelayUntilNextRunAsync(cancellationToken);
// 检查电话机进程在线状态并写日志
LogPhoneStatus(CheckPhoneIsOnline());
// 执行整点/每日通知任务
RunHourlyNotificationTask();
// 执行各项系统检查任务
CheckCpuThreshold();
CheckRcuOnline();
CheckTotalSendPackage();
CheckTotalRecvPackage();
await CheckKafkaHeartbeatAsync();
}
catch (TaskCanceledException)
{
// 服务停止时会进入这里
Logger.Error("任务被取消");
break;
}
catch (Exception ex)
{
// 主循环兜底异常,稍后重试
Logger.Error($"主循环发生错误: {ex.Message}");
await Task.Delay(TimeSpan.FromSeconds(RetryDelaySeconds), cancellationToken);
}
}
}
/// <summary>
/// 等待到下一次固定执行时间。
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
private async Task DelayUntilNextRunAsync(CancellationToken cancellationToken)
{
var now = DateTime.UtcNow;
var nextRunTime = CalculateNextRunTime(now);
var delayTime = nextRunTime - now;
// 到点前阻塞等待
await Task.Delay(delayTime, cancellationToken);
}
/// <summary>
/// 计算下一次执行时间点(每分钟第 RunSecond 秒)。
/// </summary>
/// <param name="now">当前 UTC 时间</param>
/// <returns>下一次执行时间</returns>
private static DateTime CalculateNextRunTime(DateTime now)
{
var nextRunTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, RunSecond);
// 如果当前秒已过触发点,则顺延到下一分钟
return now.Second >= RunSecond ? nextRunTime.AddMinutes(1) : nextRunTime;
}
/// <summary>
/// 输出电话机在线状态日志。
/// </summary>
/// <param name="isOnline">是否在线</param>
private static void LogPhoneStatus(bool isOnline)
{
Logger.Error(isOnline
? "电话机在线,开始判断!+++++str+++++"
: "电话机不在线,下面内容可能不会执行!+++++err+++++");
}
/// <summary>
/// 通过本机进程名判断电话机程序是否运行。
/// </summary>
/// <returns>在线返回 true否则 false</returns>
private static bool CheckPhoneIsOnline()
{
try
{
// 约定进程名为 Telephone
return Process.GetProcessesByName("Telephone").Length > 0;
}
catch (Exception ex)
{
Logger.Error($"电话机进程检查失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 整点通知任务调度(北京时间):
/// - 非整点直接返回
/// - 同一整点只执行一次
/// - 10/15/22 点执行每日任务,其余整点执行整点短信
/// </summary>
private void RunHourlyNotificationTask()
{
// 当前北京时间
var beijingTime = DateTime.UtcNow.AddHours(8);
// 仅整点触发
if (beijingTime.Minute != 0)
{
return;
}
// 当前整点键,用于去重
var hourlyKey = new DateTime(beijingTime.Year, beijingTime.Month, beijingTime.Day, beijingTime.Hour, 0, 0);
if (_executedTasks.ContainsKey(hourlyKey))
{
// 避免重复执行
return;
}
Logger.Error($"准备执行整点短信任务 - 时间点: {hourlyKey:yyyy-MM-dd HH:mm}");
// 每日固定时点执行“每日任务”,否则执行“整点短信”
if (DailyTaskHours.Contains(beijingTime.Hour))
{
ExecuteDailyTask(beijingTime);
}
else
{
SendHourlySms(beijingTime);
}
// 标记当前整点已执行
_executedTasks[hourlyKey] = true;
// 清理历史日期记录
CleanupOldTasks(beijingTime);
}
/// <summary>
/// 清理前一天及更早的整点执行记录。
/// </summary>
/// <param name="currentTime">当前时间(北京时间)</param>
private void CleanupOldTasks(DateTime currentTime)
{
var keysToRemove = _executedTasks.Keys.Where(key => key.Date < currentTime.Date).ToList();
foreach (var key in keysToRemove)
{
_executedTasks.Remove(key);
}
}
/// <summary>
/// 发送整点短信。
/// </summary>
/// <param name="beijingTime">当前北京时间</param>
private void SendHourlySms(DateTime beijingTime)
{
try
{
// 生成展示时间文本
var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var dateTimeStr = $"{beijingTime.Month}月{beijingTime.Day}日{beijingTime.Hour}点";
var smsContent = $"[BLV运维提示] 整点系统状态报告。当前时间:{dateTimeStr}";
// 仅发送短信到 Mobile1
var request = CreateSmsRequest(
type: "2",
deadline: currentTimestamp + SmsDeadlineSeconds,
phone: Mobile1,
caller: "整点报告",
content: smsContent);
// 投递短信任务
_callAndMsgController.SendToPhone(request);
}
catch (Exception ex)
{
Logger.Error($"发送整点短信时出错:{ex.Message}");
}
}
/// <summary>
/// 执行每日定时通知(短信 + 电话)。
/// </summary>
/// <param name="beijingTime">当前北京时间</param>
private void ExecuteDailyTask(DateTime beijingTime)
{
try
{
var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var dateTimeStr = $"{beijingTime.Month}月{beijingTime.Day}日{beijingTime.Hour}点";
var smsContent = $"[BLV运维提示] 每日定时通知。当前时间为:{dateTimeStr}";
var callContent = $"BLV运维提示 每日定时通知 当前时间为 {dateTimeStr}";
// 两路短信 + 一路电话
var smsRequest1 = CreateSmsRequest("2", currentTimestamp + SmsDeadlineSeconds, Mobile1, "每日定时通知", smsContent);
var smsRequest2 = CreateSmsRequest("2", currentTimestamp + SmsDeadlineSeconds, Mobile2, "每日定时通知", smsContent);
var callRequest = CreateSmsRequest("1", currentTimestamp + CallDeadlineSeconds, Mobile1, "每日定时通知", callContent);
// 执行发送并检查结果
var smsResult1 = _callAndMsgController.SendToPhone(smsRequest1);
var smsResult2 = _callAndMsgController.SendToPhone(smsRequest2);
var callResult = _callAndMsgController.SendToPhone(callRequest);
if (!smsResult1.isok || !smsResult2.isok || !callResult.isok)
{
Logger.Error($"发送每日定时通知失败短信1={smsResult1.message} 短信2={smsResult2.message} 电话={callResult.message}");
}
}
catch (Exception ex)
{
Logger.Error($"执行每日任务时出错:{ex.Message}");
}
}
/// <summary>
/// CPU 阈值检查:
/// 1) 先判断监控程序是否失联
/// 2) 再判断 CPU 指标是否超过阈值
/// </summary>
private void CheckCpuThreshold()
{
try
{
// 监控程序最后上报时间(来自 Redis
var detectTimeString = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_DetectTime");
if (!string.IsNullOrEmpty(detectTimeString) &&
DateTime.TryParse(detectTimeString, out var detectTime) &&
(DateTime.UtcNow - detectTime).TotalMinutes > 10)
{
// 超过 10 分钟未更新,触发监控失联告警
ExecuteMonitorUnavailableAlert(detectTime);
return;
}
// 拉取 CPU 指标
var cpuMax = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_CPUMax");
var cpuAvg = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_CPUAvg");
var cpuMaxValues = ParseCsvToIntList(cpuMax);
var cpuAvgValues = ParseCsvToIntList(cpuAvg);
// 规则:平均 CPU >= 80 的点达到 6 个触发告警
if (CheckThreshold(cpuAvgValues, threshold: 80, requiredCount: 6))
{
var cpuMinValues = ParseCsvToIntList(CSRedisCacheHelper.redis1.Get<string>("UDPPackage_CPUMin"));
ExecuteCpuAlert(cpuMaxValues, cpuMinValues, cpuAvgValues);
}
}
catch (Exception ex)
{
Logger.Error($"CPU阈值检查错误: {ex.Message}");
}
}
/// <summary>
/// 检查 RCU 在线数量。
/// </summary>
private void CheckRcuOnline()
{
CheckRedisValue(
redisKey: "RCUOnLine",
baselineCount: 8,
thresholdRatio: 0.8,
alertAction: ExecuteRcuOnlineAlert,
logPrefix: "RCU主机的在线数量");
}
/// <summary>
/// 检查 RCU 总发送包数量。
/// </summary>
private void CheckTotalSendPackage()
{
CheckRedisValue(
redisKey: "UDPPackage_TotalSendPackage",
baselineCount: 8,
thresholdRatio: 0.6,
alertAction: ExecuteTotalSendPackageAlert,
logPrefix: "RCU主机的通讯数量");
}
/// <summary>
/// 检查 RCU 总接收包数量。
/// 除了通用阈值逻辑外,还增加“最后 3 个值都低于固定阈值”的快速告警。
/// </summary>
private void CheckTotalRecvPackage()
{
try
{
// 获取接收包时序数据
var valueString = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_TotalRecvPackage");
if (string.IsNullOrEmpty(valueString))
{
return;
}
var values = ParseCsvToIntList(valueString);
if (values.Count < 10)
{
// 数据点不足,无法按规则判定
return;
}
// 特殊规则:最后 3 个点都很低,立即告警
if (values.Count >= 3 && values[^3] < RecvPackageLowThreshold && values[^2] < RecvPackageLowThreshold && values[^1] < RecvPackageLowThreshold)
{
ExecuteTotalRecvPackageAlert([values[^3], values[^2], values[^1]]);
return;
}
// 回退到通用阈值规则
CheckRedisValue(
redisKey: "UDPPackage_TotalRecvPackage",
baselineCount: 8,
thresholdRatio: 0.75,
alertAction: ExecuteTotalRecvPackageAlert,
logPrefix: "RCU主机的通讯数量");
}
catch (Exception ex)
{
Logger.Error($"总接收包数量检查错误: {ex.Message}");
}
}
/// <summary>
/// 通用 Redis 时序指标检查:
/// - 以前 baselineCount 个点的平均值作为基线
/// - 计算阈值(平均值 * thresholdRatio
/// - 若后续两个点都低于阈值则触发告警
/// </summary>
/// <param name="redisKey">Redis 键</param>
/// <param name="baselineCount">基线样本数量</param>
/// <param name="thresholdRatio">阈值比例</param>
/// <param name="alertAction">告警动作</param>
/// <param name="logPrefix">日志前缀</param>
private void CheckRedisValue(string redisKey, int baselineCount, double thresholdRatio, Action<List<int>> alertAction, string logPrefix)
{
try
{
// 从 Redis 读取 CSV 字符串
var valueString = CSRedisCacheHelper.redis1.Get<string>(redisKey);
if (string.IsNullOrEmpty(valueString))
{
return;
}
var values = ParseCsvToIntList(valueString);
if (values.Count < 10)
{
return;
}
// 计算阈值
var average = values.Take(baselineCount).Average();
var threshold = average * thresholdRatio;
// 后续两个点均低于阈值才触发
if (values[baselineCount] < threshold && values[baselineCount + 1] < threshold)
{
alertAction(values);
}
}
catch (Exception ex)
{
Logger.Error($"{logPrefix}检查错误: {ex.Message}");
}
}
/// <summary>
/// RCU 在线数量告警。
/// </summary>
private void ExecuteRcuOnlineAlert(List<int> values)
{
SendAlert(
smsContent: $"[BLV运维提示] RCU主机在线数量低于正常值请立即检查。数据{string.Join(",", values)}",
callContent: "BLV运维提示 RCU主机在线数量低于正常值 请立即检查",
alertType: "RCU-在线数量警报");
}
/// <summary>
/// RCU 发送数量告警。
/// </summary>
private void ExecuteTotalSendPackageAlert(List<int> values)
{
SendAlert(
smsContent: $"[BLV运维提示] RCU发送数量低于预期值请立即检查。数据{string.Join(",", values)}",
callContent: "BLV运维提示 RCU发送数量低于预期值 请立即检查",
alertType: "RCU-通讯数量警报");
}
/// <summary>
/// RCU 接收数量告警。
/// </summary>
private void ExecuteTotalRecvPackageAlert(List<int> values)
{
SendAlert(
smsContent: $"[BLV运维提示] RCU接收数量低于预期值请立即检查。数据{string.Join(",", values)}",
callContent: "BLV运维提示 RCU接收数量低于预期值 请立即检查",
alertType: "RCU-通讯数量警报");
}
/// <summary>
/// CPU 告警。
/// </summary>
private void ExecuteCpuAlert(List<int> cpuMax, List<int> cpuMin, List<int> cpuAvg)
{
// 拼接 CPU 指标明细
var dataString = $"AVG:{string.Join(",", cpuAvg)},MAX:{string.Join(",", cpuMax)},MIN:{string.Join(",", cpuMin)}";
SendAlert(
smsContent: $"[BLV运维提示] RCU服务器的CPU使用率告警。{dataString}",
callContent: "BLV运维提示 RCU服务器的CPU使用率告警 请立即检查",
alertType: "RCU-CPU警报");
}
/// <summary>
/// 监控程序失联告警。
/// </summary>
/// <param name="detectTime">最后检测时间(当前版本仅用于语义传参)</param>
private void ExecuteMonitorUnavailableAlert(DateTime detectTime)
{
SendAlert(
smsContent: "[BLV运维提示] RCU服务器的监控程序无法访问请立即检查。",
callContent: "BLV运维提示 RCU服务器的监控程序无法访问 请立即检查",
alertType: "RCU-监控程序警报",
extendedDeadline: true);
}
/// <summary>
/// 检查 PostgreSQL 心跳表,判断 Kafka 入库是否活跃。
/// </summary>
private async Task CheckKafkaHeartbeatAsync()
{
try
{
// 从配置构建连接串
var connectionString = BuildPostgresConnectionString();
if (string.IsNullOrWhiteSpace(connectionString))
{
Logger.Error("Postgres配置缺失无法检查Kafka入库心跳");
// 配置缺失等价于连接失败告警路径
ExecuteKafkaDbConnectionAlert();
return;
}
// 建立数据库连接
await using var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync();
// 查询最近数据中的最新 write_ts_ms
const string sql = @"SELECT write_ts_ms
FROM (
SELECT write_ts_ms
FROM heartbeat.heartbeat_events
ORDER BY ts_ms DESC
LIMIT 3000
) AS recent_events
ORDER BY write_ts_ms DESC
LIMIT 1;";
await using var command = new NpgsqlCommand(sql, connection);
var result = await command.ExecuteScalarAsync();
// 空结果按数据库异常路径处理
if (result == null || result == DBNull.Value)
{
Logger.Error("Kafka入库心跳ts_ms查询结果为空");
ExecuteKafkaDbConnectionAlert();
return;
}
// 解析时间戳(毫秒)
if (!long.TryParse(result.ToString(), out var lastTsMs))
{
Logger.Error("Kafka入库心跳ts_ms解析失败");
ExecuteKafkaDbConnectionAlert();
return;
}
// 按“当前时间 - 最新入库时间”判断是否超时
var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (nowMs - lastTsMs > TimeSpan.FromMinutes(KafkaStaleMinutes).TotalMilliseconds)
{
Logger.Error($"Kafka入库心跳超过{KafkaStaleMinutes}分钟未更新");
ExecuteKafkaInactiveAlert();
}
}
catch (Exception ex)
{
Logger.Error($"Kafka入库心跳检查错误: {ex.Message}");
ExecuteKafkaDbConnectionAlert();
}
}
/// <summary>
/// 从配置读取 Postgres 参数并生成连接字符串。
/// </summary>
/// <returns>可用连接串;若关键配置缺失则返回 null</returns>
private string? BuildPostgresConnectionString()
{
// 约定配置节点Postgres
var section = _configuration.GetSection("Postgres");
var host = section["Host"];
var portString = section["Port"];
var database = section["Database"];
var username = section["User"];
var password = section["Password"];
var maxConnectionsString = section["MaxConnections"];
var idleTimeoutMsString = section["IdleTimeoutMs"];
// 必填项校验
if (string.IsNullOrWhiteSpace(host) ||
string.IsNullOrWhiteSpace(portString) ||
string.IsNullOrWhiteSpace(database) ||
string.IsNullOrWhiteSpace(username) ||
string.IsNullOrWhiteSpace(password))
{
return null;
}
// 端口格式校验
if (!int.TryParse(portString, out var port))
{
return null;
}
// 构建基础连接串
var builder = new NpgsqlConnectionStringBuilder
{
Host = host,
Port = port,
Database = database,
Username = username,
Password = password
};
// 连接池最大连接数(可选)
if (int.TryParse(maxConnectionsString, out var maxConnections) && maxConnections > 0)
{
builder.MaxPoolSize = maxConnections;
}
// 空闲连接生命周期ms -> s
if (int.TryParse(idleTimeoutMsString, out var idleTimeoutMs) && idleTimeoutMs > 0)
{
builder.ConnectionIdleLifetime = Math.Max(1, idleTimeoutMs / 1000);
}
return builder.ConnectionString;
}
/// <summary>
/// Kafka 入库停滞告警。
/// </summary>
private void ExecuteKafkaInactiveAlert()
{
SendAlert(
smsContent: "[BLV运维提示] BLS数据库3分钟内入库数据为0。",
callContent: "BLV运维提示 BLS数据库3分钟内入库数据为0",
alertType: "BLS-数据库入库警报",
extendedDeadline: true);
}
/// <summary>
/// Kafka 数据库连接异常告警(带计数节流)。
/// </summary>
private void ExecuteKafkaDbConnectionAlert()
{
// 每次失败计数 +1累计到阈值再告警
_kafkaDbConnectionAlertCount++;
if (_kafkaDbConnectionAlertCount < KafkaDbAlertTriggerCount)
{
return;
}
// 触发一次后清零重新计数
SendAlert(
smsContent: "[BLV运维提示] 数据库连接失败!",
callContent: "[BLV运维提示] 数据库连接失败",
alertType: "BLS-数据库连接警报",
extendedDeadline: true);
_kafkaDbConnectionAlertCount = 0;
}
/// <summary>
/// 将逗号分隔字符串解析为整型列表;解析失败项按 0 处理。
/// </summary>
/// <param name="valueString">CSV 字符串</param>
/// <returns>整型列表</returns>
private static List<int> ParseCsvToIntList(string valueString)
{
if (string.IsNullOrEmpty(valueString))
{
return [];
}
return valueString
.Split(',')
.Select(item => int.TryParse(item, out var number) ? number : 0)
.ToList();
}
/// <summary>
/// 判断列表中是否至少有 requiredCount 个值达到 threshold。
/// </summary>
/// <param name="values">待检查值集合</param>
/// <param name="threshold">阈值</param>
/// <param name="requiredCount">最少命中数量</param>
/// <returns>满足返回 true</returns>
private static bool CheckThreshold(List<int> values, int threshold, int requiredCount)
{
return values.Count >= requiredCount && values.Count(v => v >= threshold) >= requiredCount;
}
/// <summary>
/// 统一告警发送入口(两条短信 + 一通电话)。
/// </summary>
/// <param name="smsContent">短信内容</param>
/// <param name="callContent">电话播报内容</param>
/// <param name="alertType">告警类型(用于 caller 与日志)</param>
/// <param name="extendedDeadline">是否使用扩展过期时间</param>
private void SendAlert(string smsContent, string callContent, string alertType, bool extendedDeadline = false)
{
// 计算任务过期时间
var nowSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var smsDeadline = nowSeconds + (extendedDeadline ? ExtendedSmsDeadlineSeconds : SmsDeadlineSeconds);
var callDeadline = nowSeconds + (extendedDeadline ? ExtendedCallDeadlineSeconds : CallDeadlineSeconds);
// 构建请求两路短信Mobile1/2+ 一路电话Mobile1
var smsRequest1 = CreateSmsRequest("2", smsDeadline, Mobile1, alertType, smsContent);
var smsRequest2 = CreateSmsRequest("2", smsDeadline, Mobile2, alertType, smsContent);
var callRequest = CreateSmsRequest("1", callDeadline, Mobile1, alertType, callContent);
// 调用 API 投递任务
var smsResult1 = _callAndMsgController.SendToPhone(smsRequest1);
var smsResult2 = _callAndMsgController.SendToPhone(smsRequest2);
var callResult = _callAndMsgController.SendToPhone(callRequest);
// 任意一路失败都记录错误日志
if (!smsResult1.isok || !smsResult2.isok || !callResult.isok)
{
Logger.Error($"发送{alertType}通知失败: 短信1={smsResult1.message} 短信2={smsResult2.message} 电话={callResult.message}");
}
}
/// <summary>
/// 创建短信/电话请求对象。
/// </summary>
/// <param name="type">1=电话2=短信</param>
/// <param name="deadline">截止时间Unix 秒)</param>
/// <param name="phone">目标手机号</param>
/// <param name="caller">任务标识/来电名称</param>
/// <param name="content">内容</param>
/// <returns>SmsRequest 对象</returns>
private static SmsRequest CreateSmsRequest(string type, long deadline, string phone, string caller, string content)
{
return new SmsRequest
{
// 业务类型1 电话 / 2 短信)
Type = type,
// 截止时间
DeadLine = deadline,
// 创建时间
StartingPoint = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
// 目标号码
PhoneNumber = phone,
// 任务标识
CallerName = caller,
// 消息内容
Content = content
};
}
}
}