修复 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)。
94 lines
3.5 KiB
C#
94 lines
3.5 KiB
C#
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Text.Json;
|
|
using WindowsGotify.Models;
|
|
|
|
namespace WindowsGotify.Services;
|
|
|
|
public sealed class GotifyApiService
|
|
{
|
|
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;
|
|
_logger = logger;
|
|
_httpClient = new HttpClient
|
|
{
|
|
Timeout = TimeSpan.FromSeconds(15)
|
|
};
|
|
SyncAuthHeaders();
|
|
}
|
|
|
|
/// <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>
|
|
/// 拉取 lastId 之后的增量消息(去重补齐用)。
|
|
/// </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(BuildUri(path), ct);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
await using var stream = await response.Content.ReadAsStreamAsync(ct);
|
|
var page = await JsonSerializer.DeserializeAsync<GotifyMessagePage>(stream, cancellationToken: ct);
|
|
return page?.Messages ?? new List<GotifyMessage>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 拉取最近 N 条消息(历史查看用)。since=0 让服务器从最新往旧返回。
|
|
/// </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(BuildUri(path), ct);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
await using var stream = await response.Content.ReadAsStreamAsync(ct);
|
|
var page = await JsonSerializer.DeserializeAsync<GotifyMessagePage>(stream, cancellationToken: ct);
|
|
var list = page?.Messages ?? new List<GotifyMessage>();
|
|
|
|
// Gotify 的 since=0 行为:返回数据库里所有消息中的 limit 条;按 id 降序再取一次保证正确
|
|
return list.OrderByDescending(m => m.Id).Take(Math.Clamp(limit, 1, 100)).ToList();
|
|
}
|
|
}
|