Files
StoreWeb/Plugins/CloudProduct/Services/ImageAnalysisQuotaService.cs

99 lines
3.9 KiB
C#

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;
/// <summary>调用外部网站的图片识别额度接口,单例复用 HTTP 连接。</summary>
public sealed class ImageAnalysisQuotaService : IDisposable
{
private readonly HttpClient client;
private readonly PluginContext<CloudProductPlugin> context;
private readonly string ApiKey;
/// <summary>从站点根地址创建客户端,定期更新连接以跟随 DNS 变化。</summary>
public ImageAnalysisQuotaService(PluginContext<CloudProductPlugin> 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 };
}
/// <summary>随服务释放客户端及连接池。</summary>
public void Dispose() => client.Dispose();
/// <summary>
/// 使用指定用户的识别额度,返回外部接口原始响应;HTTP 失败直接抛出异常。
/// 额度扣减不自动重试,避免请求已执行但响应丢失时重复扣减。
/// </summary>
public async Task<bool> UseImageAnalysisQuotaAsync(UseImageAnalysisQuotaRequest request,
CancellationToken cancellationToken = default)
{
RequestValidator.Validate(request);
// 外部协议要求 token 和 username 放在查询字符串中,统一编码特殊字符。
var requestUrl = QueryHelpers.AddQueryString("/wxxd_user/UseImageAnalysisQuota", new Dictionary<string, string?>
{
["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<ApiResult>(html);
if (result != null && result.Success)
{
return true;
}
throw new AppException(result.Msg);
}
/// <summary>
/// 使用指定用户的识别额度,返回外部接口原始响应;HTTP 失败直接抛出异常。
/// 额度扣减不自动重试,避免请求已执行但响应丢失时重复扣减。
/// </summary>
public async Task<int> GetUserImageAnalysisQuota(UseImageAnalysisQuotaRequest request,
CancellationToken cancellationToken = default)
{
RequestValidator.Validate(request);
// 外部协议要求 token 和 username 放在查询字符串中,统一编码特殊字符。
var requestUrl = QueryHelpers.AddQueryString("/wxxd_user/GetUserImageAnalysisQuota", new Dictionary<string, string?>
{
["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<ApiResult<int>>(html, options);
if (result != null && result.Success)
{
return result.Data;
}
throw new AppException(result.Msg);
}
}