170 lines
5.8 KiB
C#
170 lines
5.8 KiB
C#
using System.Text.Json;
|
|
using AlibabaCloud.OpenApiClient.Models;
|
|
using AlibabaCloud.SDK.Dysmsapi20170525.Models;
|
|
using Tiku.Application.Auth;
|
|
using Tiku.Application.Tenancy;
|
|
using Tiku.Domain.Tenancy;
|
|
using DysmsClient = AlibabaCloud.SDK.Dysmsapi20170525.Client;
|
|
|
|
namespace Tiku.Infrastructure.Auth;
|
|
|
|
internal sealed class AliyunSmsProvider(
|
|
ITenantExternalProviderConfigService providerConfigService) : ISmsProvider
|
|
{
|
|
private const string ProviderCode = "aliyun_sms";
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
public async Task<SmsProviderSendResult> SendAsync(
|
|
SmsProviderSendRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
TenantExternalProviderAccount account;
|
|
try
|
|
{
|
|
account = await providerConfigService.GetActiveProviderAsync(
|
|
request.TenantId,
|
|
TenantExternalProviderCapability.Sms,
|
|
null,
|
|
cancellationToken);
|
|
}
|
|
catch (TenantExternalProviderException)
|
|
{
|
|
return new SmsProviderSendResult("noop", "accepted");
|
|
}
|
|
|
|
if (string.Equals(account.Provider, "noop", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(account.Provider, "local_dev", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return new SmsProviderSendResult(account.Provider, "accepted");
|
|
}
|
|
|
|
if (!string.Equals(account.Provider, ProviderCode, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new SmsProviderException(
|
|
$"SMS provider '{account.Provider}' is not supported.",
|
|
"sms_provider_unsupported");
|
|
}
|
|
|
|
var signName = Required(account.ConfigPublic, "signName", "smsSignName");
|
|
var templateCode = ResolveTemplateCode(account.ConfigPublic, request.Purpose);
|
|
var accessKeyId = Required(account.SecretPayload, "accessKeyId");
|
|
var accessKeySecret = Required(account.SecretPayload, "accessKeySecret");
|
|
var endpoint = Optional(account.ConfigPublic, "endpoint") ?? "dysmsapi.aliyuncs.com";
|
|
var regionId = Optional(account.ConfigPublic, "regionId", "region") ?? "cn-hangzhou";
|
|
|
|
var client = new DysmsClient(new Config
|
|
{
|
|
AccessKeyId = accessKeyId,
|
|
AccessKeySecret = accessKeySecret,
|
|
Endpoint = endpoint,
|
|
RegionId = regionId
|
|
});
|
|
|
|
var sendRequest = new SendSmsRequest
|
|
{
|
|
PhoneNumbers = request.Phone,
|
|
SignName = signName,
|
|
TemplateCode = templateCode,
|
|
TemplateParam = BuildTemplateParam(account.ConfigPublic, request),
|
|
OutId = request.TenantId.ToString("N")
|
|
};
|
|
|
|
try
|
|
{
|
|
var response = await client.SendSmsAsync(sendRequest).WaitAsync(cancellationToken);
|
|
var body = response.Body;
|
|
if (body is null)
|
|
{
|
|
throw new SmsProviderException(
|
|
"Aliyun SMS returned an empty response.",
|
|
"aliyun_sms_empty_response");
|
|
}
|
|
|
|
if (!string.Equals(body.Code, "OK", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new SmsProviderException(
|
|
$"Aliyun SMS send failed: {body.Code}.",
|
|
"aliyun_sms_send_rejected");
|
|
}
|
|
|
|
return new SmsProviderSendResult(
|
|
ProviderCode,
|
|
body.Code ?? "OK",
|
|
body.BizId ?? body.RequestId);
|
|
}
|
|
catch (SmsProviderException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception exception) when (exception is not OperationCanceledException)
|
|
{
|
|
throw new SmsProviderException(
|
|
"Aliyun SMS send failed.",
|
|
"aliyun_sms_send_failed",
|
|
exception);
|
|
}
|
|
}
|
|
|
|
private static string ResolveTemplateCode(JsonElement config, SmsPurpose purpose)
|
|
{
|
|
if (config.ValueKind == JsonValueKind.Object &&
|
|
config.TryGetProperty("templateCodes", out var templateCodes) &&
|
|
templateCodes.ValueKind == JsonValueKind.Object)
|
|
{
|
|
var purposeKey = purpose.ToString().ToLowerInvariant();
|
|
if (templateCodes.TryGetProperty(purposeKey, out var purposeTemplate) &&
|
|
purposeTemplate.ValueKind == JsonValueKind.String &&
|
|
!string.IsNullOrWhiteSpace(purposeTemplate.GetString()))
|
|
{
|
|
return purposeTemplate.GetString()!;
|
|
}
|
|
}
|
|
|
|
return Required(config, "templateCode", "smsTemplateCode");
|
|
}
|
|
|
|
private static string BuildTemplateParam(JsonElement config, SmsProviderSendRequest request)
|
|
{
|
|
var codeField = Optional(config, "codeField") ?? "code";
|
|
return JsonSerializer.Serialize(
|
|
new Dictionary<string, string>
|
|
{
|
|
[codeField] = request.Code
|
|
},
|
|
JsonOptions);
|
|
}
|
|
|
|
private static string Required(JsonElement element, params string[] keys)
|
|
{
|
|
var value = Optional(element, keys);
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
throw new SmsProviderException(
|
|
"Aliyun SMS provider configuration is incomplete.",
|
|
"aliyun_sms_config_incomplete");
|
|
}
|
|
|
|
private static string? Optional(JsonElement element, params string[] keys)
|
|
{
|
|
if (element.ValueKind != JsonValueKind.Object)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (var key in keys)
|
|
{
|
|
if (element.TryGetProperty(key, out var property) &&
|
|
property.ValueKind == JsonValueKind.String &&
|
|
!string.IsNullOrWhiteSpace(property.GetString()))
|
|
{
|
|
return property.GetString();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|