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);
}
}