用户注册这样玩,保你平安

news2024/11/15 11:20:17

前言

基本上每个系统系统都包含用户注册、发送验证码等基本操作。在前些年,我还记得我在逛 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 项目中,商城注册页面截图如下。

/captcha 生成图形验证码接口

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

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

  • 第一步,创建验证码对象,定义验证码图形的长、宽、以及字数(这里创建的 SpecCaptcha 对象来自 easy-captcha 项目)

  • 第二步,生成验证码 verCode

  • 第三步,为验证码生成唯一 captchaKey

  • 第四步,将 captchaKey 作为 key, verCode 作为 value,存入 redis 并设置过期时间

  • 第五步,将 captchaKey 以及验证码图像的 base64 编码返回给前端

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

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

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

/sendEmailCode 发送邮箱验证码接口

@PostMapping("/sendEmailCode")
public R sendEmailCode(@RequestBody RegistryObj registryObj) {
    String captchaKey = registryObj.getCaptchaKey();
    String captchaCode = registryObj.getCaptchaCode();
    String mobile = registryObj.getMobile();
    if (StringUtils.isBlank(captchaKey)) {
        return R.error(CUSTOM_ERROR.setMsg("图形验证码错误"));
    }
    if (StringUtils.isBlank(captchaCode)) {
        return R.error(CUSTOM_ERROR.setMsg("图形验证码为空"));
    }
    if (StringUtils.isBlank(mobile)) {
        return R.error(CUSTOM_ERROR.setMsg("手机号为空"));
    }
    String redisCode = redisCache.getCacheObject(captchaKey);
    // 判断验证码code
    if (!redisCode.equals(captchaCode.trim().toLowerCase())) {
        return R.error(USER_CAPTCHA_CODE_ERROR);
    }
    // 验证手机号是否唯一
    long count = iMemberService.count(Wrappers.lambdaQuery(Member.class).eq(Member::getMobile, mobile));
    if (count > 0) {
        return R.error(USER_PHONE_HAS_REGISTER_ERROR);
    }
    // 生成邮箱验证码code
    String emailCode = RandomUtil.randomString(6);
    // 生成邮箱验证码唯一key
    String emailKey = RedisKeyEnum.EMAIL_KEY_CACHE.getKey(IdUtil.getUid());
    // 存入redis并设置过期时间为20分钟
    redisCache.setCacheObject(emailKey, emailCode + "_" + mobile,  RedisKeyEnum.EMAIL_KEY_CACHE.getExpireSecond());
    commonThreadPoolTaskExecutor.execute(() -> {
        EmailConfig emailConfig = mailConfigService.getById(1L);
        SendMailVO sendMailVO = new SendMailVO();
        sendMailVO.setSubject("mall商城注册通知");
        sendMailVO.setContent("邮箱验证码:" + emailCode);
        sendMailVO.setTos(Collections.singletonList(registryObj.getEmail()));
        MailUtil.sendMail(emailConfig, sendMailVO, false, false);
    });
    return R.success().add("emailKey", emailKey);
}

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

  • 第一步,校验 captchaKey、captchaCode、mobile 必传参数

  • 第二步,根据 captchaKey 读取 redis 中存放的验证码 code,与用户输入 captchaCode 进行比较

  • 第三步,验证用户手机号是否唯一

  • 第四步,生成六位邮箱验证码 emailCode

  • 第五步,生成邮箱验证码唯一 emailKey

  • 第六步,将 emailKey 作为 key, emailCode_mobile 作为 value,存入 redis 并设置过期时间(注意这一步将用户手机号,也存入 Redis 是为了防止用户在获取完邮箱验证码后修改手机号,这一点很重要,很多开发同学都忘了这一步)

  • 第七步,使用线程池异步发送验证码邮件

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

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

/registry 用户注册

@PostMapping("/registry")
public R registry(@RequestBody RegistryObj registryObj) {
    // 验证两次密码输入是否一致
    if (!StringUtils.equalsIgnoreCase(registryObj.getPassword(), registryObj.getConfirmPassword())) {
        return R.error(USER_TWO_PASSWORD_NOT_SAME_ERROR);
    }
    // 验证用户手机号是否唯一
    long count = iMemberService.count(Wrappers.lambdaQuery(Member.class).eq(Member::getMobile, registryObj.getMobile()));
    if (count > 0) {
        return R.error(USER_PHONE_HAS_REGISTER_ERROR);
    }

    // 判断图形验证码
    String redisCaptchaCode = redisCache.getCacheObject(registryObj.getCaptchaKey());
    if (registryObj.getCaptchaCode() == null || !redisCaptchaCode.equals(registryObj.getCaptchaCode().trim().toLowerCase())) {
        return R.error(USER_CAPTCHA_CODE_ERROR);
    }

    // 判断邮箱验证码
    String value = redisCache.getCacheObject(registryObj.getEmailKey());
    String[] split = value.split("_");
    if (split.length < 2) {
        return R.error(ReturnCodeEnum.USER_EMAIL_CODE_ERROR);
    }
    String redisEmailCode = split[0];
    String mobile = split[1];
    // 判断发送邮箱验证码的手机号是否与用户当前传入手机号一致
    if (!StringUtils.equalsIgnoreCase(mobile, registryObj.getMobile())) {
        return R.error(ReturnCodeEnum.USER_REGISTER_MOBILE_ERROR);
    }
    // 判断用户输入邮箱验证码是否正确
    if (registryObj.getEmailCode() == null || !redisEmailCode.equals(registryObj.getEmailCode().trim().toLowerCase())) {
        return R.error(ReturnCodeEnum.USER_EMAIL_CODE_ERROR);
    }
    // 删除验证码
    redisCache.deleteObject(registryObj.getCaptchaKey());
    redisCache.deleteObject(registryObj.getEmailKey());
    Member member = new Member();
    long time = System.currentTimeMillis();
    member.setNickname("昵称" + time / 1000);
    String avatar = SysConstants.DEFAULT_AVATAR;
    member.setAvatar(avatar);
    member.setMobile(registryObj.getMobile());
    member.setEmail(registryObj.getEmail());
    member.setPassword(SecurityUtils.encryptPassword(registryObj.getPassword()));
    member.setCreateTime(new Date());
    return R.result(iMemberService.save(member));
}

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

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

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

  • 第一步,验证用户输入两次密码是否一致

  • 第二步,验证用户输入的手机号是否唯一

  • 第三步,验证用户输入的图形验证码是否于 Redis 中存储一致

  • 第四步,验证发送邮箱验证码的手机号是否于 Redis 中存储一致

  • 第五步,验证用户输入的邮箱验证码是否于 Redis 中存储一致

  • 第六步,校验通过,开始删除图形验证码、邮箱验证码

  • 第七步,启动线程池,异步进行用户保存操作

最后聊两句

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1272468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于B/S架构的医院一体化电子病历编辑器源码

电子病历在线制作、管理和使用的一体化电子病历解决方案&#xff0c;通过一体化的设计&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。电子病历系统将临床医护需要的诊疗资料以符合临床思维的方法展示。建立以病人为中心&#xff0c;以临床诊疗信息为主线…

【前端开发】Next.js与Nest.js之间的差异2023

在快节奏的网络开发领域&#xff0c;JavaScript已成为构建可靠且引人入胜的在线应用程序的标准语言。然而&#xff0c;随着对适应性强、高效的在线服务的需求不断增加&#xff0c;开发人员通常不得不从广泛的库和框架中进行选择&#xff0c;以满足其项目的要求。Next.js和Nest.…

Electron+Ts+Vue+Vite桌面应用系列:TypeScript常用语法详解

文章目录 1️⃣ TypeScript常用讲解1.1 使用1.2 字符串1.3 数字1.3 布尔1.4 数组1.5 元组1.6 枚举1.7 any1.8 void1.9 object1.10 函数指定返回值的类型1.11 联合类型1.12 类型断言1.13 接口1.14 函数类型1.15 类类型1.16 泛型 2️⃣ 类2.1 类的基本写法2.2 类的继承2.3 类的修…

指数退避和抖动

目录 引入 OCC 添加退避机制 添加抖动机制 小结 引入 OCC 乐观并发控制&#xff08;Optimistic Concurrency Control&#xff0c;OCC&#xff09;是一种既能保证多个写入者安全地修改单个对象又能避免丢失写入的古老方法OCC具有三个优点&#xff1a;只要底层存储可用&#…

windows配置服务开机自启和保活

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、下载WinSW&#xff1f;二、使用步骤1.解压2.配置3.安装服务4.服务启停5.服务卸载6.开机自启7.保活 总结 前言 写了一个程序或者是exe&#xff0c;或者是ba…

压缩包批量处理工具中文免费版1.7

软件功能&#xff1a; 压缩文件批量处理工具中文免费版是一款可以帮助用户来进行批量修改压缩文件的软件&#xff0c;支持的压缩格式有rar/zip/7z/tar 1、可批量向压缩包添加文件&#xff08;包括url快捷方式&#xff09; 2、可批量从压缩包删除文件&#xff08;支持删除二级目…

mysql中字符串截取与拆分

按分隔符把字符串拆成多行 引言截取字符串一、left(str,length)二、right(str,length)三、截取特定长度的字符串四、按分隔符截取 分割字符串一、分割成多列二、分割成多行 总结 引言 截取和拆分字符串在编程生涯中是普遍存在的&#xff0c;在sql中也不例外&#xff0c;下面就…

Linux常用命令----rmdir命令

文章目录 1. 简介2. 参数含义3. 常见用法及实例4. 注意事项5. 结语 1. 简介 rmdir命令在Linux系统中用于删除空的目录。它是一个基本的命令行工具&#xff0c;用于维护文件系统的组织结构。与rm命令不同&#xff0c;rmdir只能删除空目录&#xff0c;这提供了一种安全机制&…

小米的算法部署岗对新手是真的友好

大家好啊&#xff0c;我是董董灿。 自从开始写一些AI行业的岗位介绍&#xff0c;就养成了一个习惯&#xff0c;在上下班的路上经常就会打开某聘瞧一瞧。 导致之前一年不看的某聘认为我要看机会换工作&#xff0c;疯狂给我推猎头&#xff0c;然后电话就进来了。 不堪骚扰的我…

全网最牛最全面的Jmeter接口测试:jmeter_逻辑控制器_事务控制器

事务&#xff1a; 性能测试中&#xff0c;事务指的是从端到端&#xff0c;一个完整的操作过程&#xff0c;比如一次登录、一次 筛选条件查询&#xff0c;一次支付等&#xff1b;技术上讲&#xff1a;事务就是由1个或多个请求组成的 事务控制器 事务控制器类似简单控制器&…

【数据结构】源码角度剖析PriorityQueue

目录 认识 Queue 认识 PriorityQueue PriorityQueue为什么要用二叉堆&#xff1f; PriorityQueue构造方法源码分析 PriorityQueue 的属性 构造方法 JDK1.8传入不可比较的对象 JDK17传入不可比较的对象 传入带有Collection接口的对象 instanceof 关键字 Offer方法分析…

finebi 新手入门案例

finebi 新手入门案例 连锁超市销售数据分析 步骤&#xff1a; 准备公共数据新建分析主题处理数据在数据中分析在图形中分析数据大屏 准备公共数据 点击公共数据 点击新建文件夹 修改文件夹名称 上传数据 鼠标悬停在文件夹上&#xff0c;右侧出现 鼠标悬停在文件夹上&#x…

matplotlib与opencv图像读取与显示的问题

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 最近在用opencv和matplotlib展示图片,但是遇到了一些问题,这里展开说说 首先需要明确的是,opencv和matplotlib读取图片都是通道在最后,而前者默认可见光图像是BGR,后者是RGB.此外还有PIL以及imageio等读取图像的工具…

python——第十五天

面向对象和面向对象编程 面向对象编程&#xff1a; C语言是一门面向过程的编程语言&#xff01;&#xff01;&#xff01; 面向对象的编程思想 就是分门别类的一种能力 面向对象的概念 类&#xff1a; 对一类事物的统称 对象&#xff1a; 一类事物中的具体案例 面向对象的…

【论文阅读】1 SkyChain:一个深度强化学习的动态区块链分片系统

SkyChain 一、文献简介二、引言及重要信息2.1 研究背景2.2 研究目的和意义2.3 文献的创新点 三、研究内容3.1模型3.2自适应分类账协议3.2.1状态块创建3.2.2合并过程3.2.3拆分过程 3.3评价框架3.3.1性能3.3.1.1共识延迟3.3.1.2重新分片延迟3.3.1.3处理事务数3.3.1.4 约束 3.3.2 …

PromptRank:使用Prompt进行无监督关键词提取

论文题目&#xff1a;PromptRank: Unsupervised Keyphrase Extraction Using Prompt   论文日期&#xff1a;2023/05/15(ACL 2023)   论文地址&#xff1a;https://arxiv.org/abs/2305.04490   GitHub地址&#xff1a;https://github.com/HLT-NLP/PromptRank 文章目录 Ab…

【laBVIEW学习】4.声音播放,自定义图标,滚动条设置

一。声音播放&#xff08;报错&#xff0c;未实现&#xff09; 1.报错4810 2.解决方法&#xff1a; 暂时未解决。 二。图片修改 1.目标&#xff1a;灯泡---》自定义灯泡 2.步骤&#xff1a; 1.右键点击--》自定义运行 表示可以制作自定义类型 2.右键--》打开自定义类型 这样就…

文件管理技巧大公开,轻松批量归类相同名称文件到指定文件夹!

在日常生活和工作中&#xff0c;我们经常需要处理大量的文件&#xff0c;包括文档、图片、音频、视频等。然而&#xff0c;随着时间的推移&#xff0c;文件数量不断增加&#xff0c;管理起来也变得越来越困难。为了更高效地整理和分类这些文件&#xff0c;今天我们将向大家介绍…

MySQL处理并发访问和高负载的关键技术和策略

我深知在数据库管理中处理并发访问和高负载的重要性。在这篇文章中&#xff0c;我将探讨MySQL处理并发访问和高负载的关键技术和策略&#xff0c;以帮助读者更好地优化数据库性能。 图片来源&#xff1a;MySQL处理并发访问和高负载的关键技术和策略 MySQL数据库在处理并发访问…

电子学会C/C++编程等级考试2023年03月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:和数(2023.3) 给定一个正整数序列,判断其中有多少个数,等于数列中其他两个数的和。 比如,对于数列1 2 3 4, 这个问题的答案就是2, 因为3 = 2 + 1, 4 = 1 + 3。 时间限制:10000 内存限制:65536输入 共两行,第一行是数列中…