fix: GotifyApiService 改为绝对 URI + 实时重读 URL/Token

修复 bug:用户改完 Gotify URL 后,历史 API 仍然请求老地址(127.0.0.1:8080)。

根因:HttpClient.BaseAddress 在构造后只读,GotifyApiService 在构造函数里把 _config.Gotify.Url 写进 BaseAddress 后就不再更新,而用户通过主窗改 URL 只是改了 in-memory _config 对象,不会重建 HttpClient。

修法:
- HttpClient 不再设 BaseAddress,所有请求走绝对 URI(BuildUri 每次从 _config 重新组装)
- 加 SyncAuthHeaders 方法,URL 或 Token 变了就重置 DefaultRequestHeaders.Authorization
- _config 字段类型从 GotifyConfig 改成 AppConfig,这样能读到完整引用,GetRecentMessagesAsync 之类的才会用新值

现在点「保存并重连」后,刷历史/WebSocket/通知 全部会用新 URL,不用重启 app。

验证:日志里看到从 127.0.0.1:8080 → kennethcheng.cc:50334,401 是 server 端 token 不认(下一步核对 Gotify 后台 Client Token)。
This commit is contained in:
2026-09-06 20:39:14 +08:00
parent 4c2575317e
commit 5d5df1b5db
+42 -7
View File
@@ -7,22 +7,55 @@ namespace WindowsGotify.Services;
public sealed class GotifyApiService
{
private readonly GotifyConfig _config;
private readonly AppConfig _config;
private readonly Logger _logger;
private readonly HttpClient _httpClient;
private readonly object _httpLock = new();
private string _appliedBaseUrl = "";
private string _appliedToken = "";
public GotifyApiService(AppConfig config, Logger logger)
{
_config = config.Gotify;
_config = config;
_logger = logger;
_httpClient = new HttpClient
{
BaseAddress = new Uri(_config.Url.TrimEnd('/') + "/"),
Timeout = TimeSpan.FromSeconds(15)
};
SyncAuthHeaders();
}
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _config.ClientToken);
/// <summary>
/// 检测 URL/Token 是否变了,变了就刷新 HttpClient 的 Authorization。
/// 注意:HttpClient.BaseAddress 构造后只读,所以我们用绝对 URI 绕过。
/// </summary>
private void SyncAuthHeaders()
{
var baseUrl = (_config.Gotify.Url ?? "").TrimEnd('/');
var token = _config.Gotify.ClientToken ?? "";
if (_appliedBaseUrl == baseUrl && _appliedToken == token)
return;
lock (_httpLock)
{
if (_appliedBaseUrl == baseUrl && _appliedToken == token)
return;
// 显式给默认 header;实际请求走绝对 URI,所以 BaseAddress 永远为空
_httpClient.DefaultRequestHeaders.Authorization =
string.IsNullOrEmpty(token) ? null : new AuthenticationHeaderValue("Bearer", token);
_appliedBaseUrl = baseUrl;
_appliedToken = token;
}
}
private Uri BuildUri(string path)
{
var baseUrl = (_config.Gotify.Url ?? "").TrimEnd('/');
if (string.IsNullOrEmpty(baseUrl))
throw new InvalidOperationException("Gotify URL 未配置。请在主窗填入 URL 并保存。");
return new Uri(baseUrl + "/" + path);
}
/// <summary>
@@ -30,8 +63,9 @@ public sealed class GotifyApiService
/// </summary>
public async Task<List<GotifyMessage>> GetMessagesSinceAsync(long sinceId, int limit, CancellationToken ct)
{
SyncAuthHeaders();
var path = $"message?limit={Math.Clamp(limit, 1, 100)}&since={Math.Max(0, sinceId)}";
using var response = await _httpClient.GetAsync(path, ct);
using var response = await _httpClient.GetAsync(BuildUri(path), ct);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(ct);
@@ -44,8 +78,9 @@ public sealed class GotifyApiService
/// </summary>
public async Task<List<GotifyMessage>> GetRecentMessagesAsync(int limit, CancellationToken ct)
{
SyncAuthHeaders();
var path = $"message?limit={Math.Clamp(limit, 1, 100)}&since=0";
using var response = await _httpClient.GetAsync(path, ct);
using var response = await _httpClient.GetAsync(BuildUri(path), ct);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(ct);