using System.Collections.ObjectModel; using System.ComponentModel; using System.Net.Http; using System.Net.Http.Json; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Threading; namespace OmniNotifyTest; public partial class MainWindow : Window, INotifyPropertyChanged { private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(4) }; private readonly DispatcherTimer _autoTimer = new(); private int _autoSent; private int _presetIndex; private MessagePreset? _selectedPreset; private string _statusText = "准备就绪。请确认 OmniNotify 正在运行,且目标频道已在主程序中创建。"; public MainWindow() { InitializeComponent(); DataContext = this; Channels.Add("default"); Channels.Add("build"); Channels.Add("monitor"); Channels.Add("urgent"); Presets.Add(new MessagePreset("普通消息", "测试通知", "这是一条来自 OmniNotify 测试器的普通消息。")); Presets.Add(new MessagePreset("长正文", "长文本排版测试", "第一行:用于观察弹窗宽度、最大高度和换行。\r\n第二行:这段正文会稍微长一些,用来测试截断、跑马灯或拆分显示。\r\n第三行:消息发送时间 {time}。")); Presets.Add(new MessagePreset("空标题", "", "只有正文,没有标题。")); Presets.Add(new MessagePreset("构建成功", "Build finished", "Release 构建完成,所有测试通过。")); Presets.Add(new MessagePreset("告警", "CPU 使用率过高", "monitor 节点检测到 CPU 持续高于 90%,请检查后台任务。")); Presets.Add(new MessagePreset("特殊字符", "Symbols !@#$%", "中文、English、数字 12345,以及 JSON 转义字符:\" \\ /。")); SelectedPreset = Presets[0]; ApplyPreset(SelectedPreset); _autoTimer.Tick += async (_, _) => await SendAutoTickAsync(); } public event PropertyChangedEventHandler? PropertyChanged; public ObservableCollection Channels { get; } = []; public ObservableCollection Presets { get; } = []; public ObservableCollection Logs { get; } = []; public string Endpoint { get; set; } = "http://127.0.0.1:19845/notify"; public string SelectedChannel { get; set; } = "default"; public string MessageTitle { get; set; } = ""; public string Body { get; set; } = ""; public int IntervalMilliseconds { get; set; } = 1000; public int AutoLimit { get; set; } = 20; public int BurstCount { get; set; } = 10; public bool RotatePresets { get; set; } = true; public string StatusText { get => _statusText; set => SetField(ref _statusText, value); } public MessagePreset? SelectedPreset { get => _selectedPreset; set => SetField(ref _selectedPreset, value); } private async void CheckServer_Click(object sender, RoutedEventArgs e) { try { var baseUri = new Uri(Endpoint); var root = new Uri(baseUri.GetLeftPart(UriPartial.Authority)); var response = await Http.GetAsync(root); StatusText = response.IsSuccessStatusCode ? $"服务可达:{root}" : $"服务响应异常:HTTP {(int)response.StatusCode}"; } catch (Exception ex) { StatusText = $"服务不可达:{ex.Message}"; } } private async void SendOnce_Click(object sender, RoutedEventArgs e) { await SendCurrentAsync("手动"); } private void StartAuto_Click(object sender, RoutedEventArgs e) { _autoSent = 0; _autoTimer.Interval = TimeSpan.FromMilliseconds(Math.Clamp(IntervalMilliseconds, 100, 60000)); _autoTimer.Start(); StatusText = $"自动发送已开始:间隔 {_autoTimer.Interval.TotalMilliseconds:0} ms。"; } private void StopAuto_Click(object sender, RoutedEventArgs e) { StopAuto("自动发送已停止。"); } private async void Burst_Click(object sender, RoutedEventArgs e) { var count = Math.Clamp(BurstCount, 1, 200); StatusText = $"开始批量发送 {count} 条消息。"; for (var i = 0; i < count; i++) { if (RotatePresets) { ApplyNextPreset(); } await SendCurrentAsync("批量", i + 1); } StatusText = $"批量发送完成:{count} 条。"; } private void AddChannel_Click(object sender, RoutedEventArgs e) { var channel = string.IsNullOrWhiteSpace(SelectedChannel) ? "default" : SelectedChannel.Trim(); if (!Channels.Contains(channel)) { Channels.Add(channel); } SelectedChannel = channel; OnPropertyChanged(nameof(SelectedChannel)); } private void UseIllegalChannel_Click(object sender, RoutedEventArgs e) { SelectedChannel = $"illegal-{DateTime.Now:HHmmss}"; OnPropertyChanged(nameof(SelectedChannel)); StatusText = "已切换到一个通常不存在的频道,可用于测试 IllegalChannel。"; } private void ClearLog_Click(object sender, RoutedEventArgs e) { Logs.Clear(); StatusText = "日志已清空。"; } private void Preset_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { if (SelectedPreset is not null) { ApplyPreset(SelectedPreset); } } private async Task SendAutoTickAsync() { if (AutoLimit > 0 && _autoSent >= AutoLimit) { StopAuto($"自动发送完成:{_autoSent} 条。"); return; } if (RotatePresets) { ApplyNextPreset(); } _autoSent++; await SendCurrentAsync("自动", _autoSent); } private async Task SendCurrentAsync(string mode, int? sequence = null) { var channel = string.IsNullOrWhiteSpace(SelectedChannel) ? "default" : SelectedChannel.Trim(); var message = new NotifyMessage( channel, ExpandTokens(MessageTitle, sequence), ExpandTokens(Body, sequence)); try { var response = await Http.PostAsJsonAsync(Endpoint, message); var result = await response.Content.ReadAsStringAsync(); AddLog(channel, message.Title, (int)response.StatusCode, string.IsNullOrWhiteSpace(result) ? response.ReasonPhrase ?? mode : result); StatusText = $"{mode}发送完成:HTTP {(int)response.StatusCode} {response.ReasonPhrase}"; } catch (Exception ex) { AddLog(channel, message.Title, 0, ex.Message); StatusText = $"{mode}发送失败:{ex.Message}"; } } private void ApplyPreset(MessagePreset preset) { MessageTitle = preset.Title; Body = preset.Body; OnPropertyChanged(nameof(MessageTitle)); OnPropertyChanged(nameof(Body)); } private void ApplyNextPreset() { if (Presets.Count == 0) { return; } _presetIndex = (_presetIndex + 1) % Presets.Count; SelectedPreset = Presets[_presetIndex]; ApplyPreset(SelectedPreset); } private string ExpandTokens(string value, int? sequence) { return value .Replace("{time}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")) .Replace("{seq}", (sequence ?? Logs.Count + 1).ToString()); } private void StopAuto(string message) { _autoTimer.Stop(); StatusText = message; } private void AddLog(string channel, string title, int statusCode, string result) { Logs.Insert(0, new SendLogItem(DateTime.Now, channel, title, statusCode == 0 ? "-" : statusCode.ToString(), result)); while (Logs.Count > 500) { Logs.RemoveAt(Logs.Count - 1); } } private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) { return; } field = value; OnPropertyChanged(propertyName); } } public sealed record MessagePreset(string Name, string Title, string Body); public sealed record NotifyMessage(string Channel, string Title, string Body); public sealed record SendLogItem(DateTime Time, string Channel, string Title, string StatusCode, string Result) { public string TimeText => Time.ToString("HH:mm:ss"); }