using MAX.Framework.Commons; using MAX.Framework.Dtos; using MAX.Framework.Validation; using MAX.Plugins; using Microsoft.AspNetCore.WebUtilities; using Store.Modules.CloudProduct.Dtos; using System.Text.Json; using System.Text.Json.Serialization; namespace Store.Modules.CloudProduct.Services; /// 调用外部网站的图片识别额度接口,单例复用 HTTP 连接。 public sealed class ImageAnalysisQuotaService : IDisposable { private readonly HttpClient client; private readonly PluginContext context; private readonly string ApiKey; /// 从站点根地址创建客户端,定期更新连接以跟随 DNS 变化。 public ImageAnalysisQuotaService(PluginContext context) { this.context = context; var url = context.RequireConfiguration("ImageAnalysisQuota:Url"); ApiKey = context.RequireConfiguration("ImageAnalysisQuota:APIKey"); if (!Uri.TryCreate(url, UriKind.Absolute, out var endpoint) || (endpoint.Scheme != Uri.UriSchemeHttp && endpoint.Scheme != Uri.UriSchemeHttps)) throw new InvalidOperationException("ImageAnalysisQuota:Url 必须是 HTTP 或 HTTPS 站点地址。"); if (string.IsNullOrEmpty(ApiKey)) throw new InvalidOperationException("ApiKey 不能为空"); client = new HttpClient(new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(5), AllowAutoRedirect = false }) { BaseAddress = endpoint }; } /// 随服务释放客户端及连接池。 public void Dispose() => client.Dispose(); /// /// 使用指定用户的识别额度,返回外部接口原始响应;HTTP 失败直接抛出异常。 /// 额度扣减不自动重试,避免请求已执行但响应丢失时重复扣减。 /// public async Task UseImageAnalysisQuotaAsync(UseImageAnalysisQuotaRequest request, CancellationToken cancellationToken = default) { RequestValidator.Validate(request); // 外部协议要求 token 和 username 放在查询字符串中,统一编码特殊字符。 var requestUrl = QueryHelpers.AddQueryString("/wxxd_user/UseImageAnalysisQuota", new Dictionary { ["token"] = ApiKey, ["username"] = request.UserName }); using var response = await client.PostAsync(requestUrl, null, cancellationToken); response.EnsureSuccessStatusCode(); // shortcut: 尚未提供响应协议,明确业务成功字段后再添加响应 DTO 和业务失败判断。 string html = await response.Content.ReadAsStringAsync(cancellationToken); var result = JsonSerializer.Deserialize(html); if (result != null && result.Success) { return true; } throw new AppException(result.Msg); } /// /// 使用指定用户的识别额度,返回外部接口原始响应;HTTP 失败直接抛出异常。 /// 额度扣减不自动重试,避免请求已执行但响应丢失时重复扣减。 /// public async Task GetUserImageAnalysisQuota(UseImageAnalysisQuotaRequest request, CancellationToken cancellationToken = default) { RequestValidator.Validate(request); // 外部协议要求 token 和 username 放在查询字符串中,统一编码特殊字符。 var requestUrl = QueryHelpers.AddQueryString("/wxxd_user/GetUserImageAnalysisQuota", new Dictionary { ["token"] = ApiKey, ["username"] = request.UserName }); using var response = await client.PostAsync(requestUrl, null, cancellationToken); response.EnsureSuccessStatusCode(); // shortcut: 尚未提供响应协议,明确业务成功字段后再添加响应 DTO 和业务失败判断。 string html = await response.Content.ReadAsStringAsync(cancellationToken); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var result = JsonSerializer.Deserialize>(html, options); if (result != null && result.Success) { return result.Data; } throw new AppException(result.Msg); } }