1、前言
转眼又要过了一年了 好久没写博客了,人不学就要落后,今天有时间把以前弄的发送阿里云短信验证码登录记录一下。
2、准备条件
1)去阿里云官网注册一个账号。有账号直接登录就行,以前新人好像有免费的短信可以学习 ,现在我们只能购买了 先开通短信服务然后去购买 购买链接
2)跳转到控制台的短信服务点击国内消息签名模板 右边点添加签名。后面在模板管理哪里添加模板 。
模板这个签名的意思就是 发送短信验证码的头部,类似于发票的抬头文字 一般个人只能申请一个验证码签名。企业的不知道(没试过。。。)。模板就是具体的内容,里面可以使登录的验证码 注册的验证码 找回密码的验证码 加上签名就是 【xxx时光管】 您好,你登录的验证码是520886。【】不用写在签名里面 发短信的时候会自动加上。个人的签名不能包含一些企业的文字 到时候他会告诉你签名、模板注意事项。
3)查看个人的accessKeyId跟accessSecret 右上角点个人头像点击AccessKey管理。没有的就创建一个,有的话就看列表点击查看Secret 然后验证一下就可以了,记住这两个key
3、Core 代码
1)写一个发送验证码的辅助类SMS
public class SMS { /// <summary> /// 发送短信 /// </summary> /// <param name="phone">手机号码</param> /// <param name="code">验证码</param> /// <param name="templateName">验证码模板</param> /// <param name="accessKeyId">accessKeyId= LTAIA8bLXzkMsKR4JB8</param> /// <param name="secret">secret= SamAiMzbdC0yS1lo6u9O9r</param> /// <param name="signName">签名名称</param> /// <returns></returns> public static SendMessageDto SendSMS(string phone, string code, string templateName, string accessKeyId, string secret, string signName) { var ret = new SendMessageDto(); IClientProfile profile = DefaultProfile.GetProfile("cn-hangzhou", accessKeyId, secret); DefaultAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest { Method = MethodType.POST, Domain = "dysmsapi.aliyuncs.com", Version = "2017-05-25", Action = "SendSms" }; request.AddQueryParameters("PhoneNumbers", phone); request.AddQueryParameters("SignName", signName); request.AddQueryParameters("TemplateCode", templateName); request.AddQueryParameters("TemplateParam", $"{{\'code\':\'{code}\'}}"); // request.Protocol = ProtocolType.HTTP; try { CommonResponse response = client.GetCommonResponse(request); var result = Encoding.Default.GetString(response.HttpResponse.Content); var resultJson = JsonConvert.DeserializeObject<SendMessageDto>(result); ret = resultJson; } catch (ServerException e) { Console.WriteLine(e); } catch (ClientException e) { Console.WriteLine(e); } return ret; } } public class SendMessageDto { /// <summary> /// 状态码的描述。 /// </summary> public string Message { get; set; } /// <summary> /// 请求ID。 /// </summary> public string RequestId { get; set; } /// <summary> /// 发送回执ID,可根据该ID在接口QuerySendDetails中查询具体的发送状态。 /// </summary> public string BizId { get; set; } /// <summary> /// 请求状态码。 返回OK代表请求成功。 /// </summary> public string Code { get; set; } }
2)验证码配置类SMSConfig
/// <summary> /// 验证码配置类 /// </summary> public class SMSConfig { public string AccessKeyId { get; set; } public string AccessSecret { get; set; } public string SignName { get; set; } public Login Login { get; set; } public Regist Regist { get; set; } public Reset Reset { get; set; } } /// <summary> /// 验证码模板 /// </summary> public class Login { /// <summary> /// 类型 1登录 2注册 3找回、重置密码 /// </summary> //public int Type { get; set; } /// <summary> /// 模板名称 /// </summary> public string TemplateName { get; set; } } public class Regist { /// <summary> /// 模板名称 /// </summary> public string TemplateName { get; set; } } public class Reset { /// <summary> /// 模板名称 /// </summary> public string TemplateName { get; set; } }
3)数据库建立一张表存储验证码
public partial class SysSendSms { /// <summary> /// Id /// </summary> [Key] [Column("Id", Order = 0)] [Required()] [Display(Name = "Id")] public int Id { get; set; } /// <summary> /// 验证码 /// </summary> [Column("Code")] [StringLength(20, ErrorMessage = "{0}长度不能超过20个字符")] [Display(Name = "验证码")] public string Code { get; set; } /// <summary> /// 手机号 /// </summary> [Column("Phone")] [StringLength(20, ErrorMessage = "{0}长度不能超过20个字符")] [Display(Name = "手机号")] public string Phone { get; set; } /// <summary> /// 发送时间 /// </summary> [Column("SendTime")] [Display(Name = "发送时间")] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd HH:mm:ss:ffff}")] public DateTime? SendTime { get; set; } = DateTime.Now; /// <summary> /// gid /// </summary> [Column("Guid")] [StringLength(36, ErrorMessage = "{0}长度不能超过36个字符")] [Display(Name = "Guid")] public string Guid { get; set; } /// <summary> /// 次数 /// </summary> [Column("Count")] [Display(Name = "次数")] public int? Count { get; set; } /// <summary> /// 是否用了 /// </summary> [Column("IsUsed")] [Display(Name = "领取数量")] public bool? IsUsed { get; set; } /// <summary> /// 类型1登陆 2注册 /// </summary> [Column("Type")] [Display(Name = "类型1登陆 2注册")] public int? Type { get; set; } }
这个type可以自己定义,我这边演示一个登陆的发送验证码
4)发送短信方法
public async Task<Result> SendSMSAsync(SendSmsDto sendSmsDto) { var code = new Random().Next(1000, 9999).ToString(); var templateName = sendSmsDto.Type switch { 1 => _smsConfig.Value.Login.TemplateName, 2 => _smsConfig.Value.Regist.TemplateName, _ => _smsConfig.Value.Login.TemplateName }; return await Send(sendSmsDto.Phone, sendSmsDto.Type, code, templateName); }
/// <summary> /// 发送短信验证码 /// </summary> /// <param name="phone">手机</param> /// <param name="type">类型(后面数据库好区分,防止乱发)</param> /// <param name="code">验证码</param> /// <param name="templateName">模板申请下来对应的Code</param> /// <returns></returns> private async Task<Result> Send(string phone, int type, string code, string templateName) { //手机号相同 密码相同 没有用过的 var model = _db.SysSendSms.OrderByDescending(x=>x.SendTime).FirstOrDefault(x => x.Phone == phone && x.Type == type && x.IsUsed == false); var guid = Guid.NewGuid().ToString("N"); var dt = DateTime.Now;//时间 if (type == 1)//登录 { var ckuser = _db.Customer.Any(x => x.Phone == phone); if(!ckuser) { var cus = new Customer { Phone = phone, LoginID = phone, Gid = Guid.NewGuid().ToString("N"), Pwd = SecurityHelper.MD5(phone) }; _db.Customer.Add(cus); _db.SaveChanges(); return await CkSend(phone, type, code, templateName, model, guid, dt); } else return await CkSend(phone, type, code, templateName, model, guid, dt); } else return Result.ToFail("请求参数错误,type"); }
检查发送验证码的方法 为了防止恶意乱发
/// <summary> /// 看看以前发过没有 /// </summary> /// <param name="phone"></param> /// <param name="type"></param> /// <param name="code"></param> /// <param name="templateName"></param> /// <param name="model"></param> /// <param name="guid"></param> /// <param name="dt"></param> /// <returns></returns> private async Task<Result> CkSend(string phone, int type, string code, string templateName, SysSendSms model, string guid, DateTime dt) { if (model == null)//没有发送过 { var sendMess = SMS.SendSMS(phone, code, templateName, _smsConfig.Value.AccessKeyId, _smsConfig.Value.AccessSecret, _smsConfig.Value.SignName); if (sendMess.Code == "OK") { await SaveData(phone, type, code, guid, dt); return Result.ToSuccess(new { code }); } else return Result.ToFail("短信验证码发送失败" + sendMess.Code + sendMess.Message); } else { var co = model.Count;//发送次数 if (co >= 5 && dt.AddHours(-5) < model.SendTime)//大于3次并且时间在3个小时内 return Result.ToFail("短信验证码发送频繁,请3个小时再试"); else /*(co >= 8 && dt.AddDays(-1) < model.SendTime)//大于8次并且时间在24个小时内*/ { var sendMess = SMS.SendSMS(phone, code, templateName, _smsConfig.Value.AccessKeyId, _smsConfig.Value.AccessSecret, _smsConfig.Value.SignName); if (sendMess.Code == "OK") { model.Count++; model.SendTime = DateTime.Now; model.Code = code; _db.SaveChanges(); return Result.ToSuccess(new { code }); } else return Result.ToFail("短信验证码发送失败"+ sendMess.Code+sendMess.Message); } } } /// <summary> /// 保存发送信息到数据库 /// </summary> /// <param name="phone"></param> /// <param name="type"></param> /// <param name="code"></param> /// <param name="guid"></param> /// <param name="dt"></param> /// <returns></returns> private async Task SaveData(string phone, int type, string code, string guid, DateTime dt) { var modelSMS = new SysSendSms { Code = code, Phone = phone, SendTime = dt, Guid = guid, Count = 1, IsUsed = false, Type = type }; _db.SysSendSms.Add(modelSMS); await _db.SaveChangesAsync(); }
我这边的逻辑其实是写死的 type只能是1 就是这个用户不在数据库里面先创建用户 然后发送验证码。在里面就直接发送验证码。下面是登录方法
public async Task<Result> LoginAsync(LoginDto loginDto) { try { var model = _db.Customer.FirstOrDefault(x => x.LoginID == loginDto.Phone); if (model != null) { var send = _db.SysSendSms.OrderByDescending(x => x.SendTime).FirstOrDefault(x => x.Code == loginDto.Code); if (send == null) return Result.ToFail("手机验证码错误"); else { Dictionary<string, string> keyValues = new Dictionary<string, string>() { {"Id",model.LoginID } }; var token = _tokenHelper.CreateToken(keyValues); send.IsUsed = true; model.Token = token.TokenStr; await _db.SaveChangesAsync(); return Result.ToSuccess(token); } } else return Result.ToFail("用户不存在"); } catch (Exception ex) { return Result.ToError(ex); } }
public class LoginDto { /// <summary> /// 手机号码 /// </summary> public string Phone { get; set; } /// <summary> /// 验证码 /// </summary> public string Code { get; set; } }
如果验证成功了就要把数据库发送短信验证码的字段IsUse改成true
我的模板 签名都写在配置文件里面
//短信验证码配置 所有配置来自阿里云官方 "SMSConfig": { "AccessKeyId": "LTAI4GB1mA8bLXzkMsKR4JB8", "AccessSecret": "SamAiMzbZ4ufoNRndC0yS1lo6u9O9r", "SignName": "真猴吃", //签名名称 "Login": { "TemplateName": "SMS_200700770" //登录模板代码 }, "Regist": { "TemplateName": "SMS_180347559" //注册模板代码 }, "Reset": { "TemplateName": "SMS_192835767" //找回密码模板代码 } },
这与上面的接收类一样的格式 当然在Core里面别忘了配置连接字符串 再调用的类或者接口在startup里面注册
services.Configure<SMSConfig>(Configuration.GetSection("SMSConfig"));
4、参考、在线调试