public class SmsCaptchaManager : DomainService, ICaptchaManager{private readonly ISmsService SmsService;private readonly UserManager _userManager;private readonly SmsCaptchaTokenCache captchaTokenCache;public static TimeSpan TokenCacheDuration = TimeSpan.FromMinutes(5);public SmsCaptchaManager(ISmsService SmsService,UserManager userManager,SmsCaptchaTokenCache captchaTokenCache){this.SmsService=SmsService;_userManager=userManager;this.captchaTokenCache=captchaTokenCache;}}
新建SendCaptchaAsync方法,作为短信发送和缓存Token方法,CommonHelp中的GetRandomCaptchaNumber()用于生成随机6位验证码 , 发送完毕后,将此验证码作为缓存条目的Key值存入
public async Task SendCaptchaAsync(long userId, string phoneNumber, string purpose){var captcha = CommonHelper.GetRandomCaptchaNumber();var model = new SendSmsRequest();model.PhoneNumbers= phoneNumber;model.SignName="MatoApp";model.TemplateCode= purpose switch{CaptchaPurpose.BIND_PHONENUMBER => "SMS_255330989",CaptchaPurpose.UNBIND_PHONENUMBER => "SMS_255330923",CaptchaPurpose.LOGIN => "SMS_255330901",CaptchaPurpose.IDENTITY_VERIFICATION => "SMS_255330974"};model.TemplateParam= JsonConvert.SerializeObject(new { code = captcha });var result = await SmsService.SendSmsAsync(model);if (string.IsNullOrEmpty(result.BizId) && result.Code!="OK"){throw new UserFriendlyException("验证码发送失败,错误信息:"+result.Message);}await captchaTokenCache.SetAsync(captcha, new SmsCaptchaTokenCacheItem(){PhoneNumber=phoneNumber,UserId=userId,Purpose=purpose}, absoluteExpireTime: DateTimeOffset.Now.Add(TokenCacheDuration));}
绑定手机号功能实现
public async Task BindAsync(string token){SmsCaptchaTokenCacheItem currentItem = await GetToken(token);if (currentItem==null || currentItem.Purpose!=CaptchaPurpose.BIND_PHONENUMBER){throw new UserFriendlyException("验证码不正确或已过期");}var user = await _userManager.GetUserByIdAsync(currentItem.UserId);if (user.IsPhoneNumberConfirmed){throw new UserFriendlyException("已绑定手机,请先解绑后再绑定");}user.PhoneNumber=currentItem.PhoneNumber;user.IsPhoneNumberConfirmed=true;await _userManager.UpdateAsync(user);await RemoveToken(token);}
解绑手机号功能实现
public async Task UnbindAsync(string token){SmsCaptchaTokenCacheItem currentItem = await GetToken(token);if (currentItem==null|| currentItem.Purpose!=CaptchaPurpose.UNBIND_PHONENUMBER){throw new UserFriendlyException("验证码不正确或已过期");}var user = await _userManager.GetUserByIdAsync(currentItem.UserId);user.IsPhoneNumberConfirmed=false;await _userManager.UpdateAsync(user);await RemoveToken(token);}
验证功能实现
public async Task<bool> VerifyCaptchaAsync(string token, string purpose = CaptchaPurpose.IDENTITY_VERIFICATION){SmsCaptchaTokenCacheItem currentItem = await GetToken(token);if (currentItem==null || currentItem.Purpose!=purpose){return false;}await RemoveToken(token);return true;}
实际业务中可能还需要Email验证,我也建立了电子邮箱验证码的领域服务类,只不过没有实现它 , 动手能力强的读者可以试着完善这个小案例:)
文章插图
Api实现AppService层创建CaptchaAppService.cs,并写好接口
public class CaptchaAppService : ApplicationService{private readonly SmsCaptchaManager captchaManager;public CaptchaAppService(SmsCaptchaManager captchaManager){this.captchaManager=captchaManager;}[HttpPost]public async Task SendAsync(SendCaptchaInput input){await captchaManager.SendCaptchaAsync(input.UserId, input.PhoneNumber, input.Type);}[HttpPost]public async Task VerifyAsync(VerifyCaptchaInput input){await captchaManager.VerifyCaptchaAsync(input.Token);}[HttpPost]public async Task UnbindAsync(VerifyCaptchaInput input){await captchaManager.UnbindAsync(input.Token);}[HttpPost]public async Task BindAsync(VerifyCaptchaInput input){await captchaManager.BindAsync(input.Token);}}
文章插图
至此我们就完成了验证码相关逻辑的接口下一章将介绍如何重写Abp默认方法,以集成手机号登录功能 。
注意!不要将本示例作为生产级代码使用本示例中,验证码校验的接口并没有做严格加密 , 6位验证码也很容易被破解,因此需要考虑这些安全问题 。在实际生产代码中 , 验证的参数常用手机号+验证码做哈希运算保证安全 。
项目地址Github:matoapp-samples
【一 Abp.Zero 手机号免密登录验证与号码绑定功能的实现:验证码模块】
推荐阅读
- 三 Java多线程-ThreadPool线程池
- c++ 模板 指针类型偏特化
- 一个实用的 vite + vue3 组件库脚手架工具,提升开发效率
- 二、.Net Core搭建Ocelot
- 用一台笔记本电脑如何赚钱(笔记本电脑赚钱的办法)
- 有哪些在家用电脑就可以赚钱的办法,一天七八十就可以了
- 一 Java多线程-线程生命周期
- 创建.NET程序Dump的几种姿势
- 一台虚拟机,基于docker搭建大数据HDP集群
- 支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用