Browse Source

用户注册时向用户手机号发送验证码短信并记录相关数据

lifa 1 year ago
parent
commit
5c7e02c67c

+ 714 - 0
src/platform/ZhonTai.Admin/Core/Helpers/QCloudSmsHelper.cs

@@ -0,0 +1,714 @@
+using DotNetCore.CAP.Transport;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using ZhonTai.Admin.Core.Configs;
+using System.Security.Cryptography;
+
+namespace ZhonTai.Admin.Core.Helpers
+{
+    public class QCloudSmsHelper
+    {
+        public string QcloudSendNotification(string mobile, string content)
+        {
+            // 请根据实际 appid 和 appkey 进行开发,以下只作为演示 sdk 使用
+            // appid,appkey,templId申请方式可参考接入指南 https://www.qcloud.com/document/product/382/3785#5-.E7.9F.AD.E4.BF.A1.E5.86.85.E5.AE.B9.E9.85.8D.E7.BD.AE
+            int sdkappid = 1400153514;//1400153514
+            string appkey = "fe967a103fcfac54b41bddf50a2863b8"; //"fe967a103fcfac54b41bddf50a2863b8";
+            try
+            {
+                SmsSingleSenderResult singleResult;
+                SmsSingleSender singleSender = new SmsSingleSender(sdkappid, appkey);
+
+                ////string sms_content = string.Format("您的注册验证码是{0},请于{1}分钟内填写,如非本人操作,请忽略本短信。","12334","2");
+                //string sms_content = string.Format("您的验证码是{0},请于{1}分钟内填写,如非本人操作,请忽略本短信。", "", "");
+                singleResult = singleSender.Send(0, "86", mobile, content, "", "");
+                if (singleResult.errmsg != "OK")
+                {
+                    //Log.WriteLogToTxt(string.Format("Qcloud发送短信({0},{1})失败," + singleResult.ToString(), mobile, content), LogType.Error);
+                    return singleResult.ToString();
+                }
+                return singleResult.ToString() + ",0";
+            }
+            catch (Exception ex)
+            {
+                //Log.WriteLogToTxt(string.Format("Qcloud发送短信({0},{1})失败," + ex.Message, mobile, content), LogType.Error);
+                return string.Format("Qcloud发送短信({0},{1})失败," + ex.Message, mobile, content);
+            }
+        }
+    }
+
+    public class SmsSingleSender
+    {
+        int sdkappid;
+        string appkey;
+        string url = "https://yun.tim.qq.com/v5/tlssmssvr/sendsms";
+
+        SmsSenderUtil util = new SmsSenderUtil();
+
+        public SmsSingleSender(int sdkappid, string appkey)
+        {
+            this.sdkappid = sdkappid;
+            this.appkey = appkey;
+        }
+
+        /**
+         * 普通单发短信接口,明确指定内容,如果有多个签名,请在内容中以【】的方式添加到信息内容中,否则系统将使用默认签名
+         * @param type 短信类型,0 为普通短信,1 营销短信
+         * @param nationCode 国家码,如 86 为中国
+         * @param phoneNumber 不带国家码的手机号
+         * @param msg 信息内容,必须与申请的模板格式一致,否则将返回错误
+         * @param extend 扩展码,可填空
+         * @param ext 服务端原样返回的参数,可填空
+         * @return SmsSingleSenderResult
+         */
+        public SmsSingleSenderResult Send(int type, string nationCode, string phoneNumber, string msg, string extend, string ext)
+        {
+            /*
+            请求包体
+            {
+                "tel": {
+                    "nationcode": "86", 
+                    "mobile": "13788888888"
+                },
+                "type": 0, 
+                "msg": "你的验证码是1234", 
+                "sig": "fdba654e05bc0d15796713a1a1a2318c", 
+                "time": 1479888540,
+                "extend": "",
+                "ext": ""
+            }
+            应答包体
+            {
+                "result": 0,
+                "errmsg": "OK", 
+                "ext": "", 
+                "sid": "xxxxxxx", 
+                "fee": 1
+            }
+            */
+            if (0 != type && 1 != type)
+            {
+                throw new Exception("type " + type + " error");
+            }
+            if (null == extend)
+            {
+                extend = "";
+            }
+            if (null == ext)
+            {
+                ext = "";
+            }
+
+            long random = util.GetRandom();
+            long curTime = util.GetCurTime();
+
+            // 按照协议组织 post 请求包体
+            JObject data = new JObject();
+
+            JObject tel = new JObject();
+            tel.Add("nationcode", nationCode);
+            tel.Add("mobile", phoneNumber);
+
+            data.Add("tel", tel);
+            data.Add("msg", msg);
+            data.Add("type", type);
+            data.Add("sig", util.StrToHash(String.Format(
+                "appkey={0}&random={1}&time={2}&mobile={3}",
+                appkey, random, curTime, phoneNumber)));
+            data.Add("time", curTime);
+            data.Add("extend", extend);
+            data.Add("ext", ext);
+
+            string wholeUrl = url + "?sdkappid=" + sdkappid + "&random=" + random;
+            HttpWebRequest request = util.GetPostHttpConn(wholeUrl);
+            byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
+            request.ContentLength = requestData.Length;
+            Stream requestStream = request.GetRequestStream();
+            requestStream.Write(requestData, 0, requestData.Length);
+            requestStream.Close();
+
+            // 接收返回包
+            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+            Stream responseStream = response.GetResponseStream();
+            StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8"));
+            string responseStr = streamReader.ReadToEnd();
+            streamReader.Close();
+            responseStream.Close();
+            SmsSingleSenderResult result;
+            if (HttpStatusCode.OK == response.StatusCode)
+            {
+                result = util.ResponseStrToSingleSenderResult(responseStr);
+            }
+            else
+            {
+                result = new SmsSingleSenderResult();
+                result.result = -1;
+                result.errmsg = "http error " + response.StatusCode + " " + responseStr;
+            }
+            return result;
+        }
+
+        /**
+         * 指定模板单发
+         * @param nationCode 国家码,如 86 为中国
+         * @param phoneNumber 不带国家码的手机号
+         * @param templId 模板 id
+         * @param templParams 模板参数列表,如模板 {1}...{2}...{3},那么需要带三个参数
+         * @param extend 扩展码,可填空
+         * @param ext 服务端原样返回的参数,可填空
+         * @return SmsSingleSenderResult
+         */
+        public SmsSingleSenderResult SendWithParam(string nationCode, string phoneNumber, int templId, List<string> templParams, string sign, string extend, string ext)
+        {
+            /*
+            请求包体
+            {
+                "tel": {
+                    "nationcode": "86",
+                    "mobile": "13788888888"
+                },
+                "sign": "腾讯云",
+                "tpl_id": 19,
+                "params": [
+                    "验证码", 
+                    "1234",
+                    "4"
+                ],
+                "sig": "fdba654e05bc0d15796713a1a1a2318c",
+                "time": 1479888540,
+                "extend": "",
+                "ext": ""
+            }
+            应答包体
+            {
+                "result": 0,
+                "errmsg": "OK", 
+                "ext": "", 
+                "sid": "xxxxxxx", 
+                "fee": 1
+            }
+            */
+            if (null == sign)
+            {
+                sign = "";
+            }
+            if (null == extend)
+            {
+                extend = "";
+            }
+            if (null == ext)
+            {
+                ext = "";
+            }
+
+            long random = util.GetRandom();
+            long curTime = util.GetCurTime();
+
+            // 按照协议组织 post 请求包体
+            JObject data = new JObject();
+
+            JObject tel = new JObject();
+            tel.Add("nationcode", nationCode);
+            tel.Add("mobile", phoneNumber);
+
+            data.Add("tel", tel);
+            data.Add("sig", util.CalculateSigForTempl(appkey, random, curTime, phoneNumber));
+            data.Add("tpl_id", templId);
+            data.Add("params", util.SmsParamsToJSONArray(templParams));
+            data.Add("sign", sign);
+            data.Add("time", curTime);
+            data.Add("extend", extend);
+            data.Add("ext", ext);
+
+            string wholeUrl = url + "?sdkappid=" + sdkappid + "&random=" + random;
+            HttpWebRequest request = util.GetPostHttpConn(wholeUrl);
+            byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
+            request.ContentLength = requestData.Length;
+            Stream requestStream = request.GetRequestStream();
+            requestStream.Write(requestData, 0, requestData.Length);
+            requestStream.Close();
+
+            // 接收返回包
+            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+            Stream responseStream = response.GetResponseStream();
+            StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8"));
+            string responseStr = streamReader.ReadToEnd();
+            streamReader.Close();
+            responseStream.Close();
+            SmsSingleSenderResult result;
+            if (HttpStatusCode.OK == response.StatusCode)
+            {
+                result = util.ResponseStrToSingleSenderResult(responseStr);
+            }
+            else
+            {
+                result = new SmsSingleSenderResult();
+                result.result = -1;
+                result.errmsg = "http error " + response.StatusCode + " " + responseStr;
+            }
+            return result;
+        }
+    }
+
+    public class SmsSingleSenderResult
+    {
+        /*
+        {
+            "result": 0,
+            "errmsg": "OK", 
+            "ext": "", 
+            "sid": "xxxxxxx", 
+            "fee": 1
+        }
+         */
+        public int result { set; get; }
+        public string errmsg { set; get; }
+        public string ext { set; get; }
+        public string sid { set; get; }
+        public int fee { set; get; }
+
+        public override string ToString()
+        {
+            return string.Format(
+                "SmsSingleSenderResult\nresult {0}\nerrMsg {1}\next {2}\nsid {3}\nfee {4}",
+                result, errmsg, ext, sid, fee);
+        }
+    }
+
+    public class SmsMultiSender
+    {
+        int sdkappid;
+        string appkey;
+        string url = "https://yun.tim.qq.com/v5/tlssmssvr/sendmultisms2";
+
+        SmsSenderUtil util = new SmsSenderUtil();
+
+        public SmsMultiSender(int sdkappid, string appkey)
+        {
+            this.sdkappid = sdkappid;
+            this.appkey = appkey;
+        }
+
+        /**
+         * 普通群发短信接口,明确指定内容,如果有多个签名,请在内容中以【】的方式添加到信息内容中,否则系统将使用默认签名
+         * 【注意】海外短信无群发功能
+         * @param type 短信类型,0 为普通短信,1 营销短信
+         * @param nationCode 国家码,如 86 为中国
+         * @param phoneNumbers 不带国家码的手机号列表
+         * @param msg 信息内容,必须与申请的模板格式一致,否则将返回错误
+         * @param extend 扩展码,可填空
+         * @param ext 服务端原样返回的参数,可填空
+         * @return SmsMultiSenderResult
+         */
+        public SmsMultiSenderResult Send(int type, string nationCode, List<string> phoneNumbers, string msg, string extend, string ext)
+        {
+            /*
+            请求包体
+            {
+                "tel": [
+                    {
+                        "nationcode": "86", 
+                        "mobile": "13788888888"
+                    }, 
+                    {
+                        "nationcode": "86", 
+                        "mobile": "13788888889"
+                    }
+                ], 
+                "type": 0, 
+                "msg": "你的验证码是1234", 
+                "sig": "fdba654e05bc0d15796713a1a1a2318c",
+                "time": 1479888540,
+                "extend": "", 
+                "ext": ""
+            }
+            应答包体
+            {
+                "result": 0, 
+                "errmsg": "OK", 
+                "ext": "", 
+                "detail": [
+                    {
+                        "result": 0, 
+                        "errmsg": "OK", 
+                        "mobile": "13788888888", 
+                        "nationcode": "86", 
+                        "sid": "xxxxxxx", 
+                        "fee": 1
+                    }, 
+                    {
+                        "result": 0, 
+                        "errmsg": "OK", 
+                        "mobile": "13788888889", 
+                        "nationcode": "86", 
+                        "sid": "xxxxxxx", 
+                        "fee": 1
+                    }
+                ]
+            }
+            */
+            if (0 != type && 1 != type)
+            {
+                throw new Exception("type " + type + " error");
+            }
+            if (null == extend)
+            {
+                extend = "";
+            }
+            if (null == ext)
+            {
+                ext = "";
+            }
+
+            long random = util.GetRandom();
+            long curTime = util.GetCurTime();
+
+            // 按照协议组织 post 请求包体
+            JObject data = new JObject();
+            data.Add("tel", util.PhoneNumbersToJSONArray(nationCode, phoneNumbers));
+            data.Add("type", type);
+            data.Add("msg", msg);
+            data.Add("sig", util.CalculateSig(appkey, random, curTime, phoneNumbers));
+            data.Add("time", curTime);
+            data.Add("extend", extend);
+            data.Add("ext", ext);
+
+            string wholeUrl = url + "?sdkappid=" + sdkappid + "&random=" + random;
+            HttpWebRequest request = util.GetPostHttpConn(wholeUrl);
+            byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
+            request.ContentLength = requestData.Length;
+            Stream requestStream = request.GetRequestStream();
+            requestStream.Write(requestData, 0, requestData.Length);
+            requestStream.Close();
+
+            // 接收返回包
+            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+            Stream responseStream = response.GetResponseStream();
+            StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8"));
+            string responseStr = streamReader.ReadToEnd();
+            streamReader.Close();
+            responseStream.Close();
+            SmsMultiSenderResult result;
+            if (HttpStatusCode.OK == response.StatusCode)
+            {
+                result = util.ResponseStrToMultiSenderResult(responseStr);
+            }
+            else
+            {
+                result = new SmsMultiSenderResult();
+                result.result = -1;
+                result.errmsg = "http error " + response.StatusCode + " " + responseStr;
+            }
+            return result;
+        }
+
+        /**
+         * 指定模板群发
+         * 【注意】海外短信无群发功能
+         * @param nationCode 国家码,如 86 为中国
+         * @param phoneNumbers 不带国家码的手机号列表
+         * @param templId 模板 id
+         * @param params 模板参数列表
+         * @param sign 签名,如果填空,系统会使用默认签名
+         * @param extend 扩展码,可以填空
+         * @param ext 服务端原样返回的参数,可以填空
+         * @return SmsMultiSenderResult
+         */
+        public SmsMultiSenderResult SendWithParam(String nationCode, List<string> phoneNumbers, int templId, List<string> templParams, string sign, string extend, string ext)
+        {
+            /*
+            请求包体
+            {
+                "tel": [
+                    {
+                        "nationcode": "86", 
+                        "mobile": "13788888888"
+                    }, 
+                    {
+                        "nationcode": "86", 
+                        "mobile": "13788888889"
+                    }
+                ], 
+                "type": 0, 
+                "msg": "你的验证码是1234", 
+                "sig": "fdba654e05bc0d15796713a1a1a2318c",
+                "time": 1479888540,
+                "extend": "", 
+                "ext": ""
+            }
+            应答包体
+            {
+                "result": 0, 
+                "errmsg": "OK", 
+                "ext": "", 
+                "detail": [
+                    {
+                        "result": 0, 
+                        "errmsg": "OK", 
+                        "mobile": "13788888888", 
+                        "nationcode": "86", 
+                        "sid": "xxxxxxx", 
+                        "fee": 1
+                    }, 
+                    {
+                        "result": 0, 
+                        "errmsg": "OK", 
+                        "mobile": "13788888889", 
+                        "nationcode": "86", 
+                        "sid": "xxxxxxx", 
+                        "fee": 1
+                    }
+                ]
+            }
+            */
+            if (null == sign)
+            {
+                sign = "";
+            }
+            if (null == extend)
+            {
+                extend = "";
+            }
+            if (null == ext)
+            {
+                ext = "";
+            }
+
+            long random = util.GetRandom();
+            long curTime = util.GetCurTime();
+
+            // 按照协议组织 post 请求包体
+            JObject data = new JObject();
+            data.Add("tel", util.PhoneNumbersToJSONArray(nationCode, phoneNumbers));
+            data.Add("sig", util.CalculateSigForTempl(appkey, random, curTime, phoneNumbers));
+            data.Add("tpl_id", templId);
+            data.Add("params", util.SmsParamsToJSONArray(templParams));
+            data.Add("sign", sign);
+            data.Add("time", curTime);
+            data.Add("extend", extend);
+            data.Add("ext", ext);
+
+            string wholeUrl = url + "?sdkappid=" + sdkappid + "&random=" + random;
+            HttpWebRequest request = util.GetPostHttpConn(wholeUrl);
+            byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
+            request.ContentLength = requestData.Length;
+            Stream requestStream = request.GetRequestStream();
+            requestStream.Write(requestData, 0, requestData.Length);
+            requestStream.Close();
+
+            // 接收返回包
+            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+            Stream responseStream = response.GetResponseStream();
+            StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8"));
+            string responseStr = streamReader.ReadToEnd();
+            streamReader.Close();
+            responseStream.Close();
+            SmsMultiSenderResult result;
+            if (HttpStatusCode.OK == response.StatusCode)
+            {
+                result = util.ResponseStrToMultiSenderResult(responseStr);
+            }
+            else
+            {
+                result = new SmsMultiSenderResult();
+                result.result = -1;
+                result.errmsg = "http error " + response.StatusCode + " " + responseStr;
+            }
+            return result;
+        }
+    }
+
+    public class SmsMultiSenderResult
+    {
+        /*
+        {
+            "result": 0, 
+            "errmsg": "OK", 
+            "ext": "", 
+            "detail": [
+                {
+                    "result": 0, 
+                    "errmsg": "OK", 
+                    "mobile": "13788888888", 
+                    "nationcode": "86", 
+                    "sid": "xxxxxxx", 
+                    "fee": 1
+                }, 
+                {
+                    "result": 0, 
+                    "errmsg": "OK", 
+                    "mobile": "13788888889", 
+                    "nationcode": "86", 
+                    "sid": "xxxxxxx", 
+                    "fee": 1
+                }
+            ]
+        }
+            */
+        public class Detail
+        {
+            public int result { get; set; }
+            public string errmsg { get; set; }
+            public string mobile { get; set; }
+            public string nationcode { get; set; }
+            public string sid { get; set; }
+            public int fee { get; set; }
+
+            public override string ToString()
+            {
+                return string.Format(
+                        "\tDetail result {0} errmsg {1} mobile {2} nationcode {3} sid {4} fee {5}",
+                        result, errmsg, mobile, nationcode, sid, fee);
+            }
+        }
+
+        public int result;
+        public string errmsg = "";
+        public string ext = "";
+        public IList<Detail> detail;
+
+        public override string ToString()
+        {
+            if (null != detail)
+            {
+                return String.Format(
+                        "SmsMultiSenderResult\nresult {0}\nerrmsg {1}\next {2}\ndetail:\n{3}",
+                        result, errmsg, ext, String.Join("\n", detail));
+            }
+            else
+            {
+                return String.Format(
+                     "SmsMultiSenderResult\nresult {0}\nerrmsg {1}\next {2}\n",
+                     result, errmsg, ext);
+            }
+        }
+    }
+
+    public class SmsSenderUtil
+    {
+        Random random = new Random();
+        public HttpWebRequest GetPostHttpConn(string url)
+        {
+            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
+            request.Method = "POST";
+            request.ContentType = "application/x-www-form-urlencoded";
+            return request;
+        }
+        public long GetRandom()
+        {
+            return random.Next(999999) % 900000 + 100000;
+        }
+        public long GetCurTime()
+        {
+            Int32 unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
+            return unixTimestamp;
+        }
+        // 将二进制的数值转换为 16 进制字符串,如 "abc" => "616263"
+        private static string ByteArrayToHex(byte[] byteArray)
+        {
+            string returnStr = "";
+            if (byteArray != null)
+            {
+                for (int i = 0; i < byteArray.Length; i++)
+                {
+                    returnStr += byteArray[i].ToString("x2");
+                }
+            }
+            return returnStr;
+        }
+        public string StrToHash(string str)
+        {
+            SHA256 sha256 = SHA256Managed.Create();
+            byte[] resultByteArray = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str));
+            return ByteArrayToHex(resultByteArray);
+        }
+        // 将单发回包解析成结果对象
+        public SmsSingleSenderResult ResponseStrToSingleSenderResult(string str)
+        {
+            SmsSingleSenderResult result = JsonConvert.DeserializeObject<SmsSingleSenderResult>(str);
+            return result;
+        }
+
+        // 将群发回包解析成结果对象
+        public SmsMultiSenderResult ResponseStrToMultiSenderResult(string str)
+        {
+            SmsMultiSenderResult result = JsonConvert.DeserializeObject<SmsMultiSenderResult>(str);
+            return result;
+        }
+
+        public JArray SmsParamsToJSONArray(List<string> templParams)
+        {
+            JArray smsParams = new JArray();
+            foreach (string templParamsElement in templParams)
+            {
+                smsParams.Add(templParamsElement);
+            }
+            return smsParams;
+        }
+
+        public JArray PhoneNumbersToJSONArray(string nationCode, List<string> phoneNumbers)
+        {
+            JArray tel = new JArray();
+            int i = 0;
+            do
+            {
+                JObject telElement = new JObject();
+                telElement.Add("nationcode", nationCode);
+                telElement.Add("mobile", phoneNumbers.ElementAt(i));
+                tel.Add(telElement);
+            } while (++i < phoneNumbers.Count);
+
+            return tel;
+        }
+
+        public string CalculateSigForTempl(
+            string appkey,
+            long random,
+            long curTime,
+            List<string> phoneNumbers)
+        {
+            string phoneNumbersString = phoneNumbers.ElementAt(0);
+            for (int i = 1; i < phoneNumbers.Count; i++)
+            {
+                phoneNumbersString += "," + phoneNumbers.ElementAt(i);
+            }
+            return StrToHash(String.Format(
+                "appkey={0}&random={1}&time={2}&mobile={3}",
+                appkey, random, curTime, phoneNumbersString));
+        }
+
+        public string CalculateSigForTempl(
+            string appkey,
+            long random,
+            long curTime,
+            string phoneNumber)
+        {
+            List<string> phoneNumbers = new List<string>();
+            phoneNumbers.Add(phoneNumber);
+            return CalculateSigForTempl(appkey, random, curTime, phoneNumbers);
+        }
+
+        public string CalculateSig(
+            string appkey,
+            long random,
+            long curTime,
+            List<string> phoneNumbers)
+        {
+            string phoneNumbersString = phoneNumbers.ElementAt(0);
+            for (int i = 1; i < phoneNumbers.Count; i++)
+            {
+                phoneNumbersString += "," + phoneNumbers.ElementAt(i);
+            }
+            return StrToHash(String.Format(
+                    "appkey={0}&random={1}&time={2}&mobile={3}",
+                    appkey, random, curTime, phoneNumbersString));
+        }
+    }
+}

+ 14 - 0
src/platform/ZhonTai.Admin/Domain/Sms/ISmsRepository.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ZhonTai.Admin.Core.Repositories;
+
+namespace ZhonTai.Admin.Domain.Sms
+{
+    public interface ISmsRepository : IRepositoryBase<SmsEntity>
+    {
+
+    }
+}

+ 30 - 0
src/platform/ZhonTai.Admin/Domain/Sms/SmsEntity.cs

@@ -0,0 +1,30 @@
+using ZhonTai.Admin.Core.Entities;
+using FreeSql.DataAnnotations;
+
+namespace ZhonTai.Admin.Domain.Sms
+{
+    /// <summary>
+    /// 手机短信验证码
+    /// </summary>
+    [Table(Name = "ditui_sms_code")]
+    public class SmsEntity : EntityTenant
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        [Column(Position = 1, IsPrimary = true, IsNullable = false)]
+        public long Id { get; set; }
+        /// <summary>
+        /// 手机号
+        /// </summary>
+        public string  Mobile { get; set; }
+        /// <summary>
+        /// 状态 0 未使用; 1 已使用
+        /// </summary>
+        public int Status { get; set; }
+        /// <summary>
+        /// 验证码
+        /// </summary>
+        public string Vcode { get; set; }
+    }
+}

+ 18 - 0
src/platform/ZhonTai.Admin/Repositories/Sms/SmsRepository.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ZhonTai.Admin.Core.Db.Transaction;
+using ZhonTai.Admin.Domain.Sms;
+
+namespace ZhonTai.Admin.Repositories.Sms
+{
+    public class SmsRepository : AdminRepositoryBase<SmsEntity>, ISmsRepository
+    {
+        public SmsRepository(UnitOfWorkManagerCloud muowm) : base(muowm)
+        {
+
+        }
+    }
+}

+ 68 - 13
src/platform/ZhonTai.Admin/Services/DiTuiAPI/DiTuiAPIService.cs

@@ -25,6 +25,8 @@ using Gma.QrCodeNet.Encoding;
 using System.Drawing.Imaging;
 using System.IO;
 using System.Drawing;
+using ZhonTai.Admin.Core.Helpers;
+using ZhonTai.Admin.Domain.Sms;
 
 namespace ZhonTai.Admin.Services.DiTuiAPI
 {
@@ -39,12 +41,14 @@ namespace ZhonTai.Admin.Services.DiTuiAPI
         private readonly ITenantRepository _tenantRepository;
         private readonly IOrgRepository _orgRepository;
         private readonly IHttpContextAccessor _httpContextAccessor;
+        private readonly ISmsRepository _smsRepository;
         public DiTuiAPIService(
             IPlatformUserRepository platformUserRepository,
             AppConfig appConfig,
             ITenantRepository tenantRepository,
             IHttpContextAccessor httpContextAccessor,
-            IOrgRepository orgRepository
+            IOrgRepository orgRepository,
+            ISmsRepository smsRepository
             )
         {
             _platformUserRepository = platformUserRepository;
@@ -52,6 +56,7 @@ namespace ZhonTai.Admin.Services.DiTuiAPI
             _tenantRepository = tenantRepository;
             _httpContextAccessor = httpContextAccessor;
             _orgRepository = orgRepository;
+            _smsRepository = smsRepository;
         }
 
         /// <summary>
@@ -250,8 +255,8 @@ namespace ZhonTai.Admin.Services.DiTuiAPI
             //entity.Type = UserType.DefaultUser;
         
             entity.Password = MD5Encrypt.Encrypt32(input.Password);
-            // 注册口注册用户皆为下级角色
-            entity.Role = "2";
+            // 注册口注册用户皆为下级角色 20 
+            entity.Role = "20";
             entity.ParentId = parentUser.ParentId + parentUser.Id + "_";
             entity.TenantId = parentUser.TenantId;
 
@@ -313,6 +318,42 @@ namespace ZhonTai.Admin.Services.DiTuiAPI
             return userInfo;
         }
 
+        /// <summary>
+        /// 获取手机验证码
+        /// </summary>
+        /// <param name="phone"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [AllowAnonymous]
+        [NoOprationLog]
+        public string SendCode(string phone)
+        {
+             //= new ISmsRepository();
+            var result = _smsRepository.Select.DisableGlobalFilter(FilterNames.Tenant)
+                .Where(a => a.Mobile == phone && a.Status == 0 && a.CreatedTime >= DateTime.Now.AddMinutes(-2))
+                .First(a => new
+                {
+                    Time = a.CreatedTime,
+                    Status = a.Status
+                }) ;
+            if(result is not null)
+            {
+                throw ResultOutput.Exception(string.Format("您已于 {0} 获取过验证码,请查看手机短信中的验证码或稍后重试",result.Time));
+            }
+            string racode = "您的验证码是:{0}。请不要把验证码泄露给其他人。";
+            string vCode = this.GenerateRandom(6);
+            racode = string.Format(racode, vCode);
+            var obj = new QCloudSmsHelper().QcloudSendNotification(phone, racode);
+            var smsEntity = new SmsEntity();
+            smsEntity.Mobile = phone;
+            smsEntity.Vcode = vCode;
+            smsEntity.Status = 0;
+            var insResult = _smsRepository.Insert(smsEntity);
+            var userId = insResult.Id;
+
+            return userId.ToString();
+        }
+
         /// <summary>
         /// 字符串转Qrcode
         /// </summary>
@@ -355,18 +396,32 @@ namespace ZhonTai.Admin.Services.DiTuiAPI
 
             var token = LazyGetRequiredService<IUserToken>().Create(new[]
             {
-            new Claim(ClaimAttributes.UserId, user.Id.ToString(), ClaimValueTypes.Integer64),
-            new Claim(ClaimAttributes.UserName, user.UserName),
-            new Claim(ClaimAttributes.Name, user.Name),
-            new Claim(ClaimAttributes.UserType, user.Type.ToInt().ToString(), ClaimValueTypes.Integer32),
-            new Claim(ClaimAttributes.TenantId, user.TenantId.ToString(), ClaimValueTypes.Integer64),
-            new Claim(ClaimAttributes.TenantType, user.Tenant?.TenantType.ToInt().ToString(), ClaimValueTypes.Integer32),
-            new Claim(ClaimAttributes.DbKey, user.Tenant?.DbKey??"")
-        });
+                new Claim(ClaimAttributes.UserId, user.Id.ToString(), ClaimValueTypes.Integer64),
+                new Claim(ClaimAttributes.UserName, user.UserName),
+                new Claim(ClaimAttributes.Name, user.Name),
+                new Claim(ClaimAttributes.UserType, user.Type.ToInt().ToString(), ClaimValueTypes.Integer32),
+                new Claim(ClaimAttributes.TenantId, user.TenantId.ToString(), ClaimValueTypes.Integer64),
+                new Claim(ClaimAttributes.TenantType, user.Tenant?.TenantType.ToInt().ToString(), ClaimValueTypes.Integer32),
+                new Claim(ClaimAttributes.DbKey, user.Tenant?.DbKey??"")
+             });
 
             return token;
         }
-
-
+        /// <summary>
+        /// 生成指定长度的数字验证码
+        /// </summary>
+        /// <param name="Length"></param>
+        /// <returns></returns>
+        private string GenerateRandom(int Length)
+        {
+            char[] constant = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+            System.Text.StringBuilder newRandom = new System.Text.StringBuilder(62);
+            Random rd = new Random();
+            for (int i = 0; i < Length; i++)
+            {
+                newRandom.Append(constant[rd.Next(10)]);
+            }
+            return newRandom.ToString();
+        }
     }
 }

+ 82 - 0
src/platform/ZhonTai.Admin/ZhonTai.Admin.xml

@@ -1936,6 +1936,49 @@
             <param name="path"></param>
             <param name="skipCloumn">1跳过表头设置 0不跳过</param>
         </member>
+        <member name="M:ZhonTai.Admin.Core.Helpers.SmsSingleSender.Send(System.Int32,System.String,System.String,System.String,System.String,System.String)">
+            普通单发短信接口,明确指定内容,如果有多个签名,请在内容中以【】的方式添加到信息内容中,否则系统将使用默认签名
+            @param type 短信类型,0 为普通短信,1 营销短信
+            @param nationCode 国家码,如 86 为中国
+            @param phoneNumber 不带国家码的手机号
+            @param msg 信息内容,必须与申请的模板格式一致,否则将返回错误
+            @param extend 扩展码,可填空
+            @param ext 服务端原样返回的参数,可填空
+            @return SmsSingleSenderResult
+        </member>
+        <member name="M:ZhonTai.Admin.Core.Helpers.SmsSingleSender.SendWithParam(System.String,System.String,System.Int32,System.Collections.Generic.List{System.String},System.String,System.String,System.String)">
+            指定模板单发
+            @param nationCode 国家码,如 86 为中国
+            @param phoneNumber 不带国家码的手机号
+            @param templId 模板 id
+            @param templParams 模板参数列表,如模板 {1}...{2}...{3},那么需要带三个参数
+            @param extend 扩展码,可填空
+            @param ext 服务端原样返回的参数,可填空
+            @return SmsSingleSenderResult
+        </member>
+        <member name="M:ZhonTai.Admin.Core.Helpers.SmsMultiSender.Send(System.Int32,System.String,System.Collections.Generic.List{System.String},System.String,System.String,System.String)">
+            普通群发短信接口,明确指定内容,如果有多个签名,请在内容中以【】的方式添加到信息内容中,否则系统将使用默认签名
+            【注意】海外短信无群发功能
+            @param type 短信类型,0 为普通短信,1 营销短信
+            @param nationCode 国家码,如 86 为中国
+            @param phoneNumbers 不带国家码的手机号列表
+            @param msg 信息内容,必须与申请的模板格式一致,否则将返回错误
+            @param extend 扩展码,可填空
+            @param ext 服务端原样返回的参数,可填空
+            @return SmsMultiSenderResult
+        </member>
+        <member name="M:ZhonTai.Admin.Core.Helpers.SmsMultiSender.SendWithParam(System.String,System.Collections.Generic.List{System.String},System.Int32,System.Collections.Generic.List{System.String},System.String,System.String,System.String)">
+            指定模板群发
+            【注意】海外短信无群发功能
+            @param nationCode 国家码,如 86 为中国
+            @param phoneNumbers 不带国家码的手机号列表
+            @param templId 模板 id
+            @param params 模板参数列表
+            @param sign 签名,如果填空,系统会使用默认签名
+            @param extend 扩展码,可以填空
+            @param ext 服务端原样返回的参数,可以填空
+            @return SmsMultiSenderResult
+        </member>
         <member name="T:ZhonTai.Admin.Core.Helpers.UploadHelper">
             <summary>
             文件上传帮助类
@@ -3636,6 +3679,31 @@
             角色
             </summary>
         </member>
+        <member name="T:ZhonTai.Admin.Domain.Sms.SmsEntity">
+            <summary>
+            手机短信验证码
+            </summary>
+        </member>
+        <member name="P:ZhonTai.Admin.Domain.Sms.SmsEntity.Id">
+            <summary>
+            ID
+            </summary>
+        </member>
+        <member name="P:ZhonTai.Admin.Domain.Sms.SmsEntity.Mobile">
+            <summary>
+            手机号
+            </summary>
+        </member>
+        <member name="P:ZhonTai.Admin.Domain.Sms.SmsEntity.Status">
+            <summary>
+            状态 0 未使用; 1 已使用
+            </summary>
+        </member>
+        <member name="P:ZhonTai.Admin.Domain.Sms.SmsEntity.Vcode">
+            <summary>
+            验证码
+            </summary>
+        </member>
         <member name="T:ZhonTai.Admin.Domain.TenantPermission.TenantPermissionEntity">
             <summary>
             租户权限
@@ -5751,6 +5819,13 @@
             </summary>
             <returns></returns>
         </member>
+        <member name="M:ZhonTai.Admin.Services.DiTuiAPI.DiTuiAPIService.SendCode(System.String)">
+            <summary>
+            获取手机验证码
+            </summary>
+            <param name="phone"></param>
+            <returns></returns>
+        </member>
         <member name="M:ZhonTai.Admin.Services.DiTuiAPI.DiTuiAPIService.GetQRCode(System.String,System.Int32)">
             <summary>
             字符串转Qrcode
@@ -5766,6 +5841,13 @@
             <param name="user">用户信息</param>
             <returns></returns>
         </member>
+        <member name="M:ZhonTai.Admin.Services.DiTuiAPI.DiTuiAPIService.GenerateRandom(System.Int32)">
+            <summary>
+            生成指定长度的数字验证码
+            </summary>
+            <param name="Length"></param>
+            <returns></returns>
+        </member>
         <member name="P:ZhonTai.Admin.Services.DiTuiAPI.Dto.BindQrcodeInput.ProjectId">
             <summary>
             项目ID