Files
omni-notify-test/MainWindow.xaml.cs
home-PC 658154aca7 feat(app): 初始化 OmniNotify 测试工具
创建基于 .NET 8 WPF 的桌面测试程序,支持配置通知接口、频道、标题和正文,并通过内置模板快速发送不同类型的测试消息。

实现服务检测、单次发送、自动定时发送、模板轮换、批量发送、非法频道切换和发送日志记录,便于验证 OmniNotify 本地消息 API 的基础行为与异常场景。

Initial-Commit: true
2026-05-19 01:30:40 +08:00

262 lines
8.7 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 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<string> Channels { get; } = [];
public ObservableCollection<MessagePreset> Presets { get; } = [];
public ObservableCollection<SendLogItem> 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<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.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");
}