sunpengfei
2025-08-28 4ab0bcef19b78f9ae0339d4b78de68a7297f7683
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
using Furion.FriendlyException;
using Furion.HttpRemote;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace FlexJobApi.Core
{
    public class AliyunSmsUtils
    {
        private readonly IOptions<AliyunOptions> options;
        private readonly IHttpRemoteService httpRemoteService;
 
        public AliyunSmsUtils(
            IOptions<AliyunOptions> options,
            IHttpRemoteService httpRemoteService)
        {
            this.options = options;
            this.httpRemoteService = httpRemoteService;
        }
 
        /// <summary>
        /// 发送短信
        /// </summary>
        /// <param name="phoneNumber">手机号码</param>
        /// <param name="templateCode">模板代码</param>
        /// <param name="templateParam">模板参数</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns></returns>
        /// <exception cref="Oops"></exception>
        public async Task SendAsync(string phoneNumber, EnumSmsTemplateCode templateCode, string templateParam, CancellationToken cancellationToken)
        {
            if (options.Value.SMS?.Enable != true)
            {
                return;
            }
            if (options.Value.SMS != null
                && options.Value.SMS.Version.IsNotNull()
                && options.Value.SMS.RegionId.IsNotNull()
                && options.Value.SMS.SignName.IsNotNull()
                && options.Value.SMS.AccessKeyId.IsNotNull()
                && options.Value.SMS.AccessSecret.IsNotNull())
            {
                var _templateCode = options.Value.SMS.TemplateCodes[templateCode.ToString()];
                var _params = new Dictionary<string, string>
                {
                    {"Action", "SendSms"},
                    {"Version", options.Value.SMS.Version},
                    {"RegionId", options.Value.SMS.RegionId},
                    {"PhoneNumbers", phoneNumber},
                    {"SignName", options.Value.SMS.SignName},
                    {"TemplateCode",  _templateCode}
                };
                if (!string.IsNullOrWhiteSpace(templateParam))
                {
                    _params.Add("TemplateParam", templateParam);
                }
                var timestamp = DateTime.Now.ToUniversalTime()
                    .ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.CreateSpecificCulture("en-US"));
 
                _params.Add("AccessKeyId", options.Value.SMS.AccessKeyId);
                _params.Add("Timestamp", timestamp);
                _params.Add("Format", "JSON");
                _params.Add("SignatureMethod", "HMAC-SHA1");
                _params.Add("SignatureVersion", "1.0");
                _params.Add("SignatureNonce", Guid.NewGuid().ToString());
 
                //排序
                var sortDic = new SortedDictionary<string, string>(_params, StringComparer.Ordinal);
 
                //生成Url参数
                var urlParams = "";
                foreach (var dic in sortDic)
                {
                    urlParams += $"{PercentEncode(dic.Key)}={PercentEncode(dic.Value)}&";
                }
                urlParams = urlParams.TrimEnd('&');
 
                //签名
                var stringToSign = $"GET&{PercentEncode("/")}&{PercentEncode(urlParams)}";
                string signature = PercentEncode(ToHmacsha1(stringToSign, options.Value.SMS.AccessSecret + "&"));
 
                var res = await httpRemoteService.GetAsStringAsync($"http://dysmsapi.aliyuncs.com/?Signature={signature}&{urlParams}");
                var callback = res.JsonTo(new
                {
                    Code = "",
                    Message = "",
                    RequestId = "",
                    BizId = ""
                });
                if (callback == null || callback.Code != "OK")
                {
                    throw Oops.Oh(EnumErrorCodeType.s510, $"发送短信失败:{callback?.Message},请联系管理员");
                }
            }
        }
 
        /// <summary>
        /// 排除敏感字符串
        /// </summary>
        /// <param name="value">文本</param>
        /// <returns>排除后文本</returns>
        private string PercentEncode(string value)
        {
            var stringBuilder = new StringBuilder();
            var text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
            var bytes = Encoding.GetEncoding("UTF-8").GetBytes(value);
            foreach (var b in bytes)
            {
                var c = (char)b;
                if (text.IndexOf(c) >= 0)
                    stringBuilder.Append(c);
                else
                    stringBuilder.Append("%").Append(
                        string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c));
            }
            return stringBuilder.ToString();
        }
 
        /// <summary>
        /// HMAC-SHA1加密
        /// </summary>
        /// <param name="content"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string ToHmacsha1(string content, string key)
        {
            var hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(key));
            var bytes = hmacsha1.ComputeHash(Encoding.UTF8.GetBytes(content));
            return Convert.ToBase64String(bytes);
        }
    }
}