经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
用户注册这样玩,保你平安
来源:cnblogs  作者:waynaqua  时间:2023/12/1 9:09:31  对本文有异议

前言

基本上每个系统系统都包含用户注册、发送验证码等基本操作。在前些年,我还记得我在逛 csdn、贴吧、网易新闻等网站的时候是可以不登陆也能浏览完网页内容的,但是近几年这些网站已经改成了不登陆不让用,浏览网页时不时提醒你要进行登录,对于一些不喜欢注册的用户造成了相当大的困扰。

但是不知道大家有没有想过这里面的深层逻辑,就是为什么前些年什么 csdn、贴吧、网易新闻等明明不进行登录浏览网页体验还行,现在要改成这样子?

这里面涉及的因素有很多,比如互联网发展到头、变现困难、存量环境加剧内卷等。

当公司盈利压力变大,老板眼看收益日趋降低,便开始拉领导开会,领导开完会开始 PUA 员工,一层一层递进,辅以绩效、okr 等工具制定目标结果。于是公司底层员工的想法从努力赚钱、升职加薪变成保住饭碗、养活一家老小,对于业务上的月度、季度营收要求自然是各种促进用户付费的手段应上齐上。

这里面提升付费有一个非常重要的前提就是用户,只要有了用户就有付费希望。

如果用户不注册,不留下手机号、邮箱等个人信息,互联网运营又怎么给这些用户发送营销短信和邮件。所以说强制注册本质上是为了公司利益。

只要把用户留下来,留在自己的 APP 里,收集用户信息,后续各种运营活动、支付弹窗、短信找回、活动抽奖一起上,何愁没有用户 ??。

用户信息记录的意义是为了聚集 C 端用户、收集信息,为后续运营活动(提升付费)做准备。就拿淘宝举例,个性化推荐、千人千面、双 11 活动等,这一系列运营活动说到底都是为了提升淘宝的付费金额,提升淘宝平台的 GMV。什么个性化推荐、千人千面说白了就是收集你的个人信息,你的商品点击、浏览、下单等操作都会被淘宝采集,进而通过算法模型进行商品推荐,选出你可能感兴趣的商品展示,从而提升淘宝付费金额。

OK,到这里题外话说多了,虽然说用户注册是一个很基本的逻辑,但是很多人一不小心就会掉坑里。这里我给大家介绍下 waynboot-mall 项目中用户注册是怎么玩的,为什么说可以保你平安。

waynboot-mall 项目是由我开源的一套 H5 商城项目,包含运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0 框架开发而来,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。商城模块划分合理、代码质量较高、易于部署,非常适合大家拿来学习使用。

github 地址:https://github.com/wayn111/waynboot-mall

用户注册

在 waynboot-mall 项目中,商城注册页面截图如下。

image

/captcha 生成图形验证码接口

  1. @ResponseBody
  2. @RequestMapping("/captcha")
  3. public R captcha() {
  4. // 1. 创建验证码对象,定义验证码图形的长、宽、以及字数
  5. SpecCaptcha specCaptcha = new SpecCaptcha(80, 32, 4);
  6. // 2. 生成验证码
  7. String verCode = specCaptcha.text().toLowerCase();
  8. // 3. 生成验证码唯一key
  9. String captchaKey = IdUtil.getUid();
  10. // 4. 存入redis并设置过期时间为30分钟
  11. redisCache.setCacheObject(captchaKey, verCode, SysConstants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
  12. // 5. 将key和base64返回给前端
  13. return R.success().add("captchaKey", captchaKey).add("image", specCaptcha.toBase64());
  14. }

验证码接口基本是每个系统都有的接口,验证码主要是为了防止别人直接调用接口进行注册操作,是一个安全措施。现在市面上流行的有图形验证码、滑块验证码、点选验证码等,waynboot-mall 项目中使用的图形验证码,大家有兴趣可以了解 tianai-captcha 这个项目,包含滑块验证码、点选验证码等。现在我们对验证码接口进行讲解,

  • 第一步,创建验证码对象,定义验证码图形的长、宽、以及字数(这里创建的 SpecCaptcha 对象来自 easy-captcha 项目)
  • 第二步,生成验证码 verCode
  • 第三步,为验证码生成唯一 captchaKey
  • 第四步,将 captchaKey 作为 key, verCode 作为 value,存入 redis 并设置过期时间
  • 第五步,将 captchaKey 以及验证码图像的 base64 编码返回给前端

image

前端在调用完 /captcha 接口后,会拿到 captchaKey 以及验证码图像的 base64 编码,之后前端就可以将 base64 编码作为 img 标签 src 属性用作图形验证码展示。

用户输入邮箱和图形验证码后就可以点击发送邮箱验证码了。

调用发送邮箱验证码接口时会将 captchaKey、验证码、手机号等信息一起传给服务端。

/sendEmailCode 发送邮箱验证码接口

  1. @PostMapping("/sendEmailCode")
  2. public R sendEmailCode(@RequestBody RegistryObj registryObj) {
  3. String captchaKey = registryObj.getCaptchaKey();
  4. String captchaCode = registryObj.getCaptchaCode();
  5. String mobile = registryObj.getMobile();
  6. if (StringUtils.isBlank(captchaKey)) {
  7. return R.error(CUSTOM_ERROR.setMsg("图形验证码错误"));
  8. }
  9. if (StringUtils.isBlank(captchaCode)) {
  10. return R.error(CUSTOM_ERROR.setMsg("图形验证码为空"));
  11. }
  12. if (StringUtils.isBlank(mobile)) {
  13. return R.error(CUSTOM_ERROR.setMsg("手机号为空"));
  14. }
  15. String redisCode = redisCache.getCacheObject(captchaKey);
  16. // 判断验证码code
  17. if (!redisCode.equals(captchaCode.trim().toLowerCase())) {
  18. return R.error(USER_CAPTCHA_CODE_ERROR);
  19. }
  20. // 验证手机号是否唯一
  21. long count = iMemberService.count(Wrappers.lambdaQuery(Member.class).eq(Member::getMobile, mobile));
  22. if (count > 0) {
  23. return R.error(USER_PHONE_HAS_REGISTER_ERROR);
  24. }
  25. // 生成邮箱验证码code
  26. String emailCode = RandomUtil.randomString(6);
  27. // 生成邮箱验证码唯一key
  28. String emailKey = RedisKeyEnum.EMAIL_KEY_CACHE.getKey(IdUtil.getUid());
  29. // 存入redis并设置过期时间为20分钟
  30. redisCache.setCacheObject(emailKey, emailCode + "_" + mobile, RedisKeyEnum.EMAIL_KEY_CACHE.getExpireSecond());
  31. commonThreadPoolTaskExecutor.execute(() -> {
  32. EmailConfig emailConfig = mailConfigService.getById(1L);
  33. SendMailVO sendMailVO = new SendMailVO();
  34. sendMailVO.setSubject("mall商城注册通知");
  35. sendMailVO.setContent("邮箱验证码:" + emailCode);
  36. sendMailVO.setTos(Collections.singletonList(registryObj.getEmail()));
  37. MailUtil.sendMail(emailConfig, sendMailVO, false, false);
  38. });
  39. return R.success().add("emailKey", emailKey);
  40. }

一般商城系统中,发送邮箱验证码、短信验证码时都需要进行验证码输入这一步骤,这是为了防止别人直接通过接口调用的形式,浪费我们系统的资源,特别是发送手机验证码、邮件这种资源。发送邮箱验证码接口讲解如下,

  • 第一步,校验 captchaKey、captchaCode、mobile 必传参数
  • 第二步,根据 captchaKey 读取 redis 中存放的验证码 code,与用户输入 captchaCode 进行比较
  • 第三步,验证用户手机号是否唯一
  • 第四步,生成六位邮箱验证码 emailCode
  • 第五步,生成邮箱验证码唯一 emailKey
  • 第六步,将 emailKey 作为 key, emailCode_mobile 作为 value,存入 redis 并设置过期时间(注意这一步将用户手机号,也存入 Redis 是为了防止用户在获取完邮箱验证码后修改手机号,这一点很重要,很多开发同学都忘了这一步)
  • 第七步,使用线程池异步发送验证码邮件

image

前端在调用完 /sendEmailCode 接口后,就可以拿到 emailKey。

这样等用户输入邮箱里的验证码后,点击注册按钮,我们就可能正式开始注册操作了。

/registry 用户注册

  1. @PostMapping("/registry")
  2. public R registry(@RequestBody RegistryObj registryObj) {
  3. // 验证两次密码输入是否一致
  4. if (!StringUtils.equalsIgnoreCase(registryObj.getPassword(), registryObj.getConfirmPassword())) {
  5. return R.error(USER_TWO_PASSWORD_NOT_SAME_ERROR);
  6. }
  7. // 验证用户手机号是否唯一
  8. long count = iMemberService.count(Wrappers.lambdaQuery(Member.class).eq(Member::getMobile, registryObj.getMobile()));
  9. if (count > 0) {
  10. return R.error(USER_PHONE_HAS_REGISTER_ERROR);
  11. }
  12. // 判断图形验证码
  13. String redisCaptchaCode = redisCache.getCacheObject(registryObj.getCaptchaKey());
  14. if (registryObj.getCaptchaCode() == null || !redisCaptchaCode.equals(registryObj.getCaptchaCode().trim().toLowerCase())) {
  15. return R.error(USER_CAPTCHA_CODE_ERROR);
  16. }
  17. // 判断邮箱验证码
  18. String value = redisCache.getCacheObject(registryObj.getEmailKey());
  19. String[] split = value.split("_");
  20. if (split.length < 2) {
  21. return R.error(ReturnCodeEnum.USER_EMAIL_CODE_ERROR);
  22. }
  23. String redisEmailCode = split[0];
  24. String mobile = split[1];
  25. // 判断发送邮箱验证码的手机号是否与用户当前传入手机号一致
  26. if (!StringUtils.equalsIgnoreCase(mobile, registryObj.getMobile())) {
  27. return R.error(ReturnCodeEnum.USER_REGISTER_MOBILE_ERROR);
  28. }
  29. // 判断用户输入邮箱验证码是否正确
  30. if (registryObj.getEmailCode() == null || !redisEmailCode.equals(registryObj.getEmailCode().trim().toLowerCase())) {
  31. return R.error(ReturnCodeEnum.USER_EMAIL_CODE_ERROR);
  32. }
  33. // 删除验证码
  34. redisCache.deleteObject(registryObj.getCaptchaKey());
  35. redisCache.deleteObject(registryObj.getEmailKey());
  36. Member member = new Member();
  37. long time = System.currentTimeMillis();
  38. member.setNickname("昵称" + time / 1000);
  39. String avatar = SysConstants.DEFAULT_AVATAR;
  40. member.setAvatar(avatar);
  41. member.setMobile(registryObj.getMobile());
  42. member.setEmail(registryObj.getEmail());
  43. member.setPassword(SecurityUtils.encryptPassword(registryObj.getPassword()));
  44. member.setCreateTime(new Date());
  45. return R.result(iMemberService.save(member));
  46. }

注册接口,需要逻辑完善,所以这里的校验逻辑会比较多,因为一个商城最重要的几个接口就是注册、登录、下单、支付等。

除了能让用户正常注册外,有时候还需要确保用户一个手机号只能注册一个账号,完成对用户手机号在商城的唯一性保障。除了先查询用户手机号是否已存在外,还需要对用户 member 表的手机号字段设置唯一索引来完成。注册接口讲解如下,

唯一索引可以防止用户重复点击注册按钮,保证一个手机号只能注册一个用户。

  • 第一步,验证用户输入两次密码是否一致
  • 第二步,验证用户输入的手机号是否唯一
  • 第三步,验证用户输入的图形验证码是否于 Redis 中存储一致
  • 第四步,验证发送邮箱验证码的手机号是否于 Redis 中存储一致
  • 第五步,验证用户输入的邮箱验证码是否于 Redis 中存储一致
  • 第六步,校验通过,开始删除图形验证码、邮箱验证码
  • 第七步,启动线程池,异步进行用户保存操作

image

最后聊两句

用户注册说简单是很简单,但是校验逻辑一定要做好!这是我的踩坑经验,现在我传授给你,希望能帮你平安??。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

原文链接:https://www.cnblogs.com/waynaqua/p/17868631.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号