using System.Text.Json; using MoShiro.Backend.Application; namespace MoShiro.Backend; /// /// 用 IP 归属查询服务(GET {地址}/lookup?ip=…)查国家和运营商。 /// 失败(超时、网络不通、服务返回错误)一律返回 null,不往外抛:展示信息没有也不影响别的功能。 /// public sealed class HttpGeoLookup : IGeoLookup, IDisposable { private readonly HttpClient _http; public HttpGeoLookup(string baseUrl) { _http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"), Timeout = TimeSpan.FromSeconds(6) }; _http.DefaultRequestHeaders.UserAgent.ParseAdd("MoShiro"); } public async ValueTask LookupAsync(string host, CancellationToken ct) { try { using HttpResponseMessage response = await _http.GetAsync("lookup?ip=" + Uri.EscapeDataString(host), ct); if (!response.IsSuccessStatusCode) { return null; } using JsonDocument doc = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(ct), cancellationToken: ct); return Parse(doc.RootElement); } catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or JsonException or InvalidOperationException) { return null; } } /// 只取国家代码、ASN、组织名;其余字段(城市、经纬度、网段……)一概不读。 public static GeoInfo? Parse(JsonElement root) { if (root.TryGetProperty("found", out JsonElement found) && found.ValueKind == JsonValueKind.False) { return null; } bool isPrivate = root.TryGetProperty("is_private", out JsonElement priv) && priv.ValueKind == JsonValueKind.True; string? country = root.TryGetProperty("country_region", out JsonElement region) && region.TryGetProperty("iso_code", out JsonElement code) && code.ValueKind == JsonValueKind.String ? code.GetString() : null; int? asn = null; string? organization = null; if (root.TryGetProperty("network", out JsonElement network)) { if (network.TryGetProperty("asn", out JsonElement asnValue) && asnValue.TryGetInt32(out int number)) { asn = number; } string? isp = network.TryGetProperty("isp", out JsonElement ispValue) && ispValue.ValueKind == JsonValueKind.String ? ispValue.GetString() : null; string? org = network.TryGetProperty("organization", out JsonElement orgValue) && orgValue.ValueKind == JsonValueKind.String ? orgValue.GetString() : null; organization = string.IsNullOrWhiteSpace(isp) ? org : isp; } return new GeoInfo(country, asn, organization, isPrivate); } public void Dispose() => _http.Dispose(); } /// 不查(MoShiro__GeoLookupUrl 留空)。 public sealed class NoGeoLookup : IGeoLookup { public ValueTask LookupAsync(string host, CancellationToken ct) => ValueTask.FromResult(null); }