17 个源文件 + .gitignore + 默认 config.json 模板,严格按方案文档第 6 节实现。
用户核心场景:
- Windows 接收 Gotify 通知(WS /stream → AppNotificationBuilder)
- 应用内查看历史消息(打开 MainWindow 触发 GetRecentMessagesAsync,实时通过 MessageReceived 事件追加)
- 列表条目双击打开 extras.client::notification.click.url
关键变更点:
- GotifyMessage.Extras 类型由 Dictionary<string,object> 改为 Dictionary<string,JsonElement>,避免 System.Text.Json 反序列化时丢失嵌套结构
- 新增 HistoryConfig.ViewerLimit(默认 50,UI 硬上限 200)
- GotifyApiService.GetRecentMessagesAsync:since=0 拉最近 N 条,OrderByDescending(Id) 保证顺序
- GotifyWebSocketService 新增 MessageReceived 事件,MainWindow 用 Dispatcher.Invoke 切回 UI 线程追加
- NotificationService.AddArgument('url=...') 透传 click URL 给 OnNotificationInvoked
- MainWindow.xaml 增加 ListView + 刷新按钮,使用 ObservableCollection<GotifyMessage>
- 默认 config.json 用 https://gotify.example.com 占位 URL,ClientToken 留空,首启动自动写入 %LOCALAPPDATA%
依赖:Microsoft.WindowsAppSDK 2.4.0(方案文档固定版本)。
构建:本机 .NET 8 SDK 未安装,build 待用户在装好 SDK 后执行: dotnet restore && dotnet build -c Release
5.9 KiB
5.9 KiB
AGENTS.md
Windows Gotify Agent —— 在 Windows 10/11 上以托盘程序方式运行,通过 WebSocket 连接 Gotify Server,把新消息以 Windows 原生通知呈现。
用户场景
V1 核心场景(用户明确要求):
- Windows 接收 Gotify 通知(WebSocket → Windows 原生通知)
- 应用内查看历史消息(打开 MainWindow → 历史列表,自动拉最近 N 条 + 实时追加新消息)
- 列表条目支持双击跳转:消息
extras里有client::notification.click.url时,默认浏览器打开
V1 暂不做(避免范围蔓延):多 Gotify Server、消息过滤、GUI 完整配置编辑、MSI/MSIX 安装器、Linux/macOS 端。
Setup commands
- 环境:Windows 10 19041+ / Windows 11、Visual Studio 2022、.NET 8 SDK、Desktop development with .NET 工作负载
- 还原依赖:
dotnet restore - 编译:
dotnet build -c Release - 本地运行:
dotnet run -c Release - 自包含发布(推荐):
dotnet publish -c Release -r win-x64 --self-contained true - 发布产物:
bin\Release\net8.0-windows\win-x64\publish\(整个目录一起发布,不要只复制 EXE)
Project layout
WindowsGotify.csproj— SDK 风格项目文件,net8.0-windows / x64,UseWPF + UseWindowsForms,WindowsPackageType=None(unpackaged)app.manifest— asInvoker 兼容性清单(Win10/11 supportedOS)App.xaml+App.xaml.cs— 应用入口、Mutex 单实例锁、AppNotificationManager 注册/注销、生命周期编排MainWindow.xaml+MainWindow.xaml.cs— 设置窗(Gotify URL、Client Token、保存并重连、测试通知);关闭按钮只 Hide 不退出Models/—AppConfig(嵌套 Gotify/Notification/Reconnect/History/Startup 五个子配置) +GotifyMessage(+GotifyMessagePage、PagingInfo)Services/ConfigService.cs—config.json读写,路径%LOCALAPPDATA%\WindowsGotify\config.jsonServices/Logger.cs— 文本日志写入app.log,线程安全Services/StateService.cs—state.json持久化LastMessageId,断线重连/历史同步的去重锚点Services/GotifyApiService.cs—GET /message?limit=&since=拉历史补偿;GetRecentMessagesAsync拉最近 N 条(供 UI 历史查看)Services/GotifyWebSocketService.cs—/stream?token=...WebSocket 循环、自动重连(指数退避 2→5→10→20→30→60)、History 补偿;新增MessageReceived事件供 UI 实时追加Services/NotificationService.cs— 通过AppNotificationBuilder弹 Windows 原生通知,Priority ≥ 8 走 Alarm 音;若消息extras含client::notification.click.url,通过AddArgument("url=...")透传给OnNotificationInvokedServices/NotificationService.cs— 通过AppNotificationBuilder弹 Windows 原生通知,Priority ≥ 8 走 Alarm 音Utils/StartupManager.cs— 写入HKCU\Software\Microsoft\Windows\CurrentVersion\Run实现当前用户开机启动Tray/TrayIconService.cs— WinFormsNotifyIcon托盘(打开设置/测试通知/重新连接/退出)
Code style
- C# 12 / .NET 8,
<Nullable>enable</Nullable>+<ImplicitUsings>enable</ImplicitUsings> - 顶层
sealed class,文件作用域命名空间,主命名空间WindowsGotify.* - 异步 API:WebSocket、API、状态保存一律
async/await,UI 事件用async void+ try/catch using System.*;优先,然后第三方(Microsoft.Windows.AppNotifications.*/Microsoft.Win32),最后WindowsGotify.*- WPF XAML 资源引用走
x:Name,代码隐藏用_camelCase私有字段 - WebSocket 资源用
using或try/finally保证释放,ClientWebSocket.Abort()用于强制重连 - 不要把
Application Token写进此项目(只允许Client Token)
Runtime data
- 配置文件:
%LOCALAPPDATA%\WindowsGotify\config.json - 状态文件:
%LOCALAPPDATA%\WindowsGotify\state.json - 运行日志:
%LOCALAPPDATA%\WindowsGotify\logs\app.log
Testing instructions
- V1 不带自动化测试,依赖手动 + Gotify 端到端验证
- 单元自测: 托盘菜单 → 「测试通知」,应弹出标题为「Gotify 测试」的 Windows 通知
- 端到端发消息(PowerShell):
$headers = @{ "X-Gotify-Key" = "<Application Token>" } $body = @{ title = "Hadoop"; message = "NameNode 启动完成"; priority = 3 } | ConvertTo-Json Invoke-RestMethod -Uri "https://<gotify-host>/message" -Method Post -Headers $headers -ContentType "application/json" -Body $body - 反向代理(Nginx / NPM)必须透传 WebSocket Upgrade 头,详见方案文档第 14 节
- 任意代码改动后必须执行:
dotnet restore && dotnet build -c Release,并在目标 Windows 10/11 上跑一次端到端
Gotify protocol 约束
/stream是 WebSocket 端点,/message?limit=&since=是历史补偿端点,二者都只接受Client Token- 消息去重锚点 =
state.LastMessageId,只处理Id > LastMessageId的消息,启动时用since=LastMessageId拉增量 - HTTP
ClientToken走Authorization: Bearer <token>;WebSocket 走 query?token=<token>(日志中要打码为token=***) - 声音策略:
priority >= 8→AppNotificationSoundEvent.Alarm,否则Default
PR & commit conventions
- 分支策略: 默认分支
main,所有改动走 feature 分支 + PR - 提交信息: 简体中文 conventional commits,前缀使用
feat:/fix:/docs:/refactor:/chore:/build: - 改动
.csproj/Microsoft.WindowsAppSDK版本号必须在 PR 描述中单独说明,不允许顺手升级 - 涉及
Client Token/ 真实 Gotify URL 的示例值用占位符<token>/<gotify-host>,禁止提交真实凭据
Security
Client Token只能进入config.json,日志中只允许token=***打码输出- 公网部署必须使用 HTTPS + wss;ws:// / http:// 仅允许内网
- 通知点击仅允许打开经 Gotify
extras传入的 URL,不要任意打开消息文本里出现的链接 - 启动项走
HKCU\...\Run(当前用户)而非HKLM,避免提权与全员副作用 - 发布前在干净 Windows 10/11 上验证通知注册、通知中心授权、WebSocket 反代、自包含目录依赖完整