소스 검색

新增通用API短信接口,调整验证码缓存过期时间支持配置自定义

Linhaibo 5 달 전
부모
커밋
9863ce7322

+ 20 - 0
Admin.NET/Admin.NET.Application/Configuration/SMS.json

@@ -3,6 +3,7 @@
 
   // 短信配置
   "SMS": {
+    "VerifyCodeExpireSeconds": 60, // 验证码缓存过期时间(秒),默认60秒
     // 阿里云短信
     "Aliyun": {
       "AccessKeyId": "",
@@ -35,6 +36,25 @@
           "Content": ""
         }
       ]
+    },
+    // 自定义短信接口
+    "Custom": {
+        "Enabled": false, // 是否启用自定义短信接口
+        "Method": "GET", // 请求方法: GET 或 POST
+        "ApiUrl": "https://api.xxxx.com/sms?u=xxxx&key=59e03f49c3dbb5033&m={mobile}&c={content}", // API接口地址,支持占位符: {mobile}, {content}, {code}
+        "ContentType": "application/x-www-form-urlencoded", // POST请求的Content-Type: application/json 或 application/x-www-form-urlencoded
+        "PostData": "", // POST请求的数据模板,支持占位符,JSON 格式示例: {"mobile":"{mobile}","content":"{content}","apikey":"your_key"};Form 格式示例: mobile={mobile}&content={content}&apikey=your_key
+        "SuccessFlag": "0", // 成功响应标识,响应内容包含此字符串则认为发送成功
+        "Templates": [
+            {
+                "Id": "0",
+                "Content": "您的验证码为:{code},请勿泄露于他人!"
+            },
+            {
+                "Id": "1",
+                "Content": "注册成功,感谢您的注册,请妥善保管您的账户信息"
+            }
+        ]
     }
   }
 }

+ 70 - 0
Admin.NET/Admin.NET.Core/Option/SMSOptions.cs

@@ -12,6 +12,12 @@ namespace Admin.NET.Core;
 public sealed class SMSOptions : IConfigurableOptions
 {
     /// <summary>
+    /// 验证码缓存过期时间(秒)
+    /// 默认: 60秒
+    /// </summary>
+    public int VerifyCodeExpireSeconds { get; set; } = 60;
+
+    /// <summary>
     /// Aliyun
     /// </summary>
     public SMSSettings Aliyun { get; set; }
@@ -20,6 +26,11 @@ public sealed class SMSOptions : IConfigurableOptions
     /// Tencentyun
     /// </summary>
     public SMSSettings Tencentyun { get; set; }
+
+    /// <summary>
+    /// Custom 自定义短信接口
+    /// </summary>
+    public CustomSMSSettings Custom { get; set; }
 }
 
 public sealed class SMSSettings
@@ -63,4 +74,63 @@ public class SmsTemplate
     public string SignName { get; set; }
     public string TemplateCode { get; set; }
     public string Content { get; set; }
+}
+
+/// <summary>
+/// 自定义短信配置
+/// </summary>
+public sealed class CustomSMSSettings
+{
+    /// <summary>
+    /// 是否启用自定义短信接口
+    /// </summary>
+    public bool Enabled { get; set; }
+
+    /// <summary>
+    /// API 接口地址模板
+    /// 支持占位符: {mobile} - 手机号, {content} - 短信内容, {code} - 验证码
+    /// 示例: https://api.xxxx.com/sms?u=xxxx&key=59e03f49c3dbb5033&m={mobile}&c={content}
+    /// </summary>
+    public string ApiUrl { get; set; }
+
+    /// <summary>
+    /// 请求方法 (GET/POST)
+    /// </summary>
+    public string Method { get; set; } = "GET";
+
+    /// <summary>
+    /// POST 请求的 Content-Type (application/json 或 application/x-www-form-urlencoded)
+    /// 默认: application/x-www-form-urlencoded
+    /// </summary>
+    public string ContentType { get; set; } = "application/x-www-form-urlencoded";
+
+    /// <summary>
+    /// POST 请求的数据模板(支持占位符)
+    /// JSON 格式示例: {"mobile":"{mobile}","content":"{content}","apikey":"your_key"}
+    /// Form 格式示例: mobile={mobile}&content={content}&apikey=your_key
+    /// </summary>
+    public string PostData { get; set; }
+
+    /// <summary>
+    /// 成功响应标识(用于判断发送是否成功)
+    /// 如果响应内容包含此字符串,则认为发送成功
+    /// </summary>
+    public string SuccessFlag { get; set; } = "0";
+
+    /// <summary>
+    /// 短信模板列表
+    /// </summary>
+    public List<SmsTemplate> Templates { get; set; }
+
+    /// <summary>
+    /// 获取模板
+    /// </summary>
+    public SmsTemplate GetTemplate(string id = "0")
+    {
+        foreach (var template in Templates)
+        {
+            if (template.Id == id) { return template; }
+        }
+        return null;
+    }
 }

+ 91 - 3
Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs

@@ -38,10 +38,18 @@ public class SysSmsService : IDynamicApiController, ITransient
     [DisplayName("发送短信")]
     public async Task SendSms([Required] string phoneNumber, string templateId = "0")
     {
-        if (!string.IsNullOrWhiteSpace(_smsOptions.Aliyun.AccessKeyId) && !string.IsNullOrWhiteSpace(_smsOptions.Aliyun.AccessKeySecret))
+        if (_smsOptions.Custom != null && _smsOptions.Custom.Enabled && !string.IsNullOrWhiteSpace(_smsOptions.Custom.ApiUrl))
+        {
+            await CustomSendSms(phoneNumber, templateId);
+        }
+        else if (!string.IsNullOrWhiteSpace(_smsOptions.Aliyun.AccessKeyId) && !string.IsNullOrWhiteSpace(_smsOptions.Aliyun.AccessKeySecret))
+        {
             await AliyunSendSms(phoneNumber, templateId);
+        }
         else
+        {
             await TencentSendSms(phoneNumber, templateId);
+        }
     }
 
     /// <summary>
@@ -97,7 +105,7 @@ public class SysSmsService : IDynamicApiController, ITransient
         if (sendSmsResponse.Body.Code == "OK" && sendSmsResponse.Body.Message == "OK")
         {
             // var bizId = sendSmsResponse.Body.BizId;
-            _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(60));
+            _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(_smsOptions.VerifyCodeExpireSeconds));
         }
         else
         {
@@ -179,7 +187,7 @@ public class SysSmsService : IDynamicApiController, ITransient
         if (resp.SendStatusSet[0].Code == "Ok" && resp.SendStatusSet[0].Message == "send success")
         {
             // var bizId = sendSmsResponse.Body.BizId;
-            _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(60));
+            _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(_smsOptions.VerifyCodeExpireSeconds));
         }
         else
         {
@@ -217,4 +225,84 @@ public class SysSmsService : IDynamicApiController, ITransient
         };
         return cred;
     }
+
+    /// <summary>
+    /// 自定义短信接口发送短信 📨
+    /// </summary>
+    /// <param name="phoneNumber">手机号</param>
+    /// <param name="templateId">短信模板id</param>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [DisplayName("自定义短信接口发送短信")]
+    public async Task CustomSendSms([DataValidation(ValidationTypes.PhoneNumber)] string phoneNumber, string templateId = "0")
+    {
+        if (_smsOptions.Custom == null || !_smsOptions.Custom.Enabled)
+            throw Oops.Oh("自定义短信接口未启用");
+
+        if (string.IsNullOrWhiteSpace(_smsOptions.Custom.ApiUrl))
+            throw Oops.Oh("自定义短信接口地址未配置");
+
+        // 生成随机验证码
+        var verifyCode = Random.Shared.Next(100000, 999999);
+
+        // 获取模板
+        var template = _smsOptions.Custom.GetTemplate(templateId);
+        if (template == null)
+            throw Oops.Oh($"短信模板[{templateId}]不存在");
+
+        // 替换模板内容中的占位符
+        var content = template.Content.Replace("{code}", verifyCode.ToString());
+
+        try
+        {
+            using var httpClient = new HttpClient();
+            httpClient.Timeout = TimeSpan.FromSeconds(30);
+
+            HttpResponseMessage response;
+
+            //替换URL占位符
+            var url = _smsOptions.Custom.ApiUrl
+                .Replace("{templateId}", templateId)
+                .Replace("{mobile}", phoneNumber)
+                .Replace("{content}", Uri.EscapeDataString(content))
+                .Replace("{code}", verifyCode.ToString());
+
+            if (_smsOptions.Custom.Method.ToUpper() == "POST")
+            {
+                // 替换占位符
+                var postData = _smsOptions.Custom.PostData?
+                    .Replace("{templateId}", templateId)
+                    .Replace("{mobile}", phoneNumber)
+                    .Replace("{content}", content)
+                    .Replace("{code}", verifyCode.ToString());
+                HttpContent httpContent = new StringContent(postData ?? string.Empty, Encoding.UTF8, _smsOptions.Custom.ContentType ?? "application/x-www-form-urlencoded");
+                response = await httpClient.PostAsync(url, httpContent);
+            }
+            else
+            {
+                // GET 请求
+                response = await httpClient.GetAsync(url);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+
+            // 判断是否发送成功
+            if (response.IsSuccessStatusCode && responseContent.Contains(_smsOptions.Custom.SuccessFlag))
+            {
+                if (_smsOptions.Custom.ApiUrl.Contains("{code}") || template.Content.Contains("{code}") || (_smsOptions.Custom.PostData?.Contains("{code}") == true))
+                {
+                    // 如果模板含有验证码,则添加到缓存
+                    _sysCacheService.Set($"{CacheConst.KeyPhoneVerCode}{phoneNumber}", verifyCode, TimeSpan.FromSeconds(_smsOptions.VerifyCodeExpireSeconds));
+                }
+            }
+            else
+            {
+                throw Oops.Oh($"短信发送失败:{responseContent}");
+            }
+        }
+        catch (Exception ex)
+        {
+            throw Oops.Oh($"短信发送异常:{ex.Message}");
+        }
+    }
 }