实用指南:点评中是如何实现短信登录的

news/2025/10/7 11:25:14/文章来源:https://www.cnblogs.com/ljbguanli/p/19128296

实用指南:点评中是如何实现短信登录的

点评中是如何实现短信登录的

首先在这个项目中 我们主要还是通过session来实现的验证码登录

根据这个图片我们很清楚的知道 首先要制造一个随机的验证码以确保并且保存到redis中(等下我会说为什么不存在session中)接下来前端传来验证码 后端校验后 判断是否为新用户 如果是新用户 那么注册一个用户 将昵称设置为随机字段 手机号为唯一字段 如果存在 就保存在redis中 返回随机token给客户端 接下来前端传来手机号 我们从redis中拿到用户信息 保存到Threadlocal中 放行请求

public
class UserServiceImpl
extends ServiceImpl<
UserMapper
, User>
implements IUserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
private
static
final String LOGIN_CODE_KEY = "login:code:"
;
private
static
final Long LOGIN_CODE_TTL = 2L
;
/**
* 发送手机验证码
*/
public Result sendCode(@RequestParam
("phone"
) String phone, HttpSession session) {
// 发送短信验证码并保存验证码
if (RegexUtils.isEmailInvalid(phone)
) {
return Result.fail("邮箱格式不正确"
)
;
}
String code = MailUtils.achieveCode(
)
;
// 将验证码存入Redis,设置有效期为2分钟
stringRedisTemplate.opsForValue(
).set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL
, TimeUnit.MINUTES
)
;
log.info("发送登录验证码:{}"
, code)
;
try {
MailUtils.sendTestMail(phone, code)
;
}
catch (MessagingException e) {
throw
new RuntimeException(e)
;
}
return Result.ok(
)
;
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1.校验手机号
String phone = loginForm.getPhone(
)
;
if (RegexUtils.isEmailInvalid(phone)
) {
return Result.fail("邮箱格式不正确"
)
;
}
//2. 校验验证码
String cacheCode = stringRedisTemplate.opsForValue(
).get(LOGIN_CODE_KEY + phone)
;
String code = loginForm.getCode(
)
;
log.info("登录校验 - 手机号: {}, 输入的验证码: {}, 缓存的验证码: {}"
, phone, code, cacheCode)
;
if (cacheCode ==
null || !cacheCode.equals(code)
) {
// 不一致,报错
return Result.fail("验证码错误"
)
;
}
//3. 验证通过后删除验证码
stringRedisTemplate.delete(LOGIN_CODE_KEY + phone)
;
//4. 根据手机号查询用户
User user = query(
).eq("phone"
, phone).one(
)
;
//5. 判断用户是否存在
if (user ==
null
) {
// 6.不存在,创建新用户并保存
user = createUserWithPhone(phone)
;
}
//7.存在,保存用户信息到redis中
UserDTO userDTO =
new UserDTO(
)
;
BeanUtils.copyProperties(user, userDTO)
;
session.setAttribute("user"
, userDTO)
;
// 将对象中字段全部转成string类型,StringRedisTemplate只能存字符串类型的数据
Map<
String
, Object> userMap = BeanUtil.beanToMap(userDTO,
new HashMap<
>(
)
,
CopyOptions.create(
).setIgnoreNullValue(true
).
setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString(
)
)
)
;
String token = UUID.randomUUID(
).toString(
)
;
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash(
).putAll(tokenKey, userMap)
;
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL
, TimeUnit.MINUTES
)
;
//8.返回token
return Result.ok(token)
;
}
private User createUserWithPhone(String phone) {
User user =
new User(
)
;
// 如果是邮箱,生成一个随机的手机号作为唯一标识
if (phone.contains("@"
)
) {
// 生成一个随机的11位数字作为phone
String randomPhone;
User exist;
do {
// 生成13开头的11位随机手机号
randomPhone = "13" + RandomUtil.randomNumbers(9
)
;
// 检查该手机号是否已存在
exist = query(
).eq("phone"
, randomPhone).one(
)
;
}
while (exist !=
null
)
;
// 如果存在,继续生成直到找到一个不存在的
user.setPhone(randomPhone)
;
// 可以考虑将邮箱保存到其他字段,如果有需要的话
}
else {
user.setPhone(phone)
;
}
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10
)
)
;
save(user)
;
return user;
}
}

redis的优点

首先我们来看session会出现的问题

什么是Session集群共享问题?

在分布式集群环境中,会话(Session)共享是一个常见的挑战。默认情况下,Web 应用程序的会话是保存在单个服务器上的,当请求不经过该服务器时,会话信息无法被访问。

Session集群共享问题造成哪些问题?

服务器之间无法实现会话状态的共享。比如:在当前这个服务器上用户已经完成了登录,Session中存储了用户的信息,能够判断用户已登录,但是在另一个服务器的Session中没有用户信息,无法调用显示没有登录的服务器上的服务
如何解决Session集群共享问题?

方案一:Session拷贝(不推荐)

Tomcat提供了Session拷贝功能,通过配置Tomcat可以实现Session的拷贝,但是这会增加服务器的额外内存开销,同时会带来数据一致性问题

方案二:Redis缓存(推荐)

Redis缓存具有Session存储一样的特点,基于内存、存储结构可以是key-value结构、数据共享

Redis缓存相较于传统Session存储的优点:

  1. 高性能和可伸缩性:Redis 是一个内存数据库,具有快速的读写能力。相比于传统的 Session 存储方式,将会话数据存储在 Redis 中可以大大提高读写速度和处理能力。此外,Redis 还支持集群和分片技术,可以实现水平扩展,处理大规模的并发请求。
  2. 可靠性和持久性:Redis 提供了持久化机制,可以将内存中的数据定期或异步地写入磁盘,以保证数据的持久性。这样即使发生服务器崩溃或重启,会话数据也可以被恢复。
    丰富的数据结构:Redis 不仅仅是一个键值存储数据库,它还支持多种数据结构,如字符串、列表、哈希、集合和有序集合等。这些数据结构的灵活性使得可以更方便地存储和操作复杂的会话数据。
  3. 分布式缓存功能:Redis 作为一个高效的缓存解决方案,可以用于缓存会话数据,减轻后端服务器的负载。与传统的 Session 存储方式相比,使用 Redis 缓存会话数据可以大幅提高系统的性能和可扩展性。
  4. 可用性和可部署性:Redis 是一个强大而成熟的开源工具,有丰富的社区支持和活跃的开发者社区。它可以轻松地与各种编程语言和框架集成,并且可以在多个操作系统上运行。
    PS:但是Redis费钱,而且增加了系统的复杂度

从前面的分析来看,显然Redis是要优于Session的,但是Redis中有很多数据结构,我们应该选择哪种数据结构来存储用户信息才能够更优呢?可能大多数同学都会想到使用 String 类型的数据据结构,但是这里我推荐使用 Hash结构!

Hash 结构与 String 结构类型的比较:
1 . String 数据结构是以 JSON 字符串的形式保存,更加直观,操作也更加简单,但是 JSON 结构会有很多非必须的内存开销,比如双引号、大括号,内存占用比 Hash 更高
2. Hash 数据结构是以 Hash 表的形式保存,可以对单个字段进行CRUD,更加灵活
Redis替代Session需要考虑的问题:

  1. 选择合适的数据结构,了解 Hash 比 String 的区别
  2. 选择合适的key,为key设置一个业务前缀,方便区分和分组,为key拼接一个UUID,避免key冲突防止数据覆盖
  3. 选择合适的存储粒度,对于验证码这类数据,一般设置TTL为3min即可,防止大量缓存数据的堆积,而对于用户信息这类数据可以稍微设置长一点,比如30min,防止频繁对Redis进行IO操作

单独配置一个拦截器用户刷新Redis中的token:在基于Session实现短信验证码登录时,我们只配置了一个拦截器,这里需要另外再配置一个拦截器专门用户刷新存入Redis中的 token,因为我们现在改用Redis了,为了防止用户在操作网站时突然由于Redis中的 token 过期,导致直接退出网站,严重影响用户体验。那为什么不把刷新的操作放到一个拦截器中呢,因为之前的那个拦截器只是用来拦截一些需要进行登录校验的请求,对于哪些不需要登录校验的请求是不会走拦截器的,刷新操作显然是要针对所有请求比较合理,所以单独创建一个拦截器拦截一切请求,刷新Redis中的Key

登录拦截器:

public
class LoginInterceptor
implements HandlerInterceptor {
/**
* 前置拦截器,用于判断用户是否登录
*/
@Override
public
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 判断当前用户是否已登录
if (ThreadLocalUtls.getUser(
) ==
null
){
// 当前用户未登录,直接拦截
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED
)
;
return false
;
}
// 用户存在,直接放行
return true
;
}
}
public
class RefreshTokenInterceptor
implements HandlerInterceptor {
// new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)
// 所以这里需要再配置类中注入,然后通过构造器传入到当前类中
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 1、获取token,并判断token是否存在
String token = request.getHeader("authorization"
)
;
if (StrUtil.isBlank(token)
){
// token不存在,说明当前用户未登录,不需要刷新直接放行
return true
;
}
// 2、判断用户是否存在
String tokenKey = LOGIN_USER_KEY + token;
Map<
Object
, Object> userMap = stringRedisTemplate.opsForHash(
).entries(tokenKey)
;
if (userMap.isEmpty(
)
){
// 用户不存在,说明当前用户未登录,不需要刷新直接放行
return true
;
}
// 3、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理,比如:方便获取和使用用户信息,Redis获取用户信息是具有侵入性的
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap,
new UserDTO(
)
, false
)
;
ThreadLocalUtls.saveUser(BeanUtil.copyProperties(userMap, UserDTO.
class
)
)
;
// 4、刷新token有效期
stringRedisTemplate.expire(token, LOGIN_USER_TTL
, TimeUnit.MINUTES
)
;
return true
;
}
}
@Configuration
public
class WebMvcConfig
implements WebMvcConfigurer {
// new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)
// 所以这里需要再配置类中注入,然后通过构造器传入到当前类中
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public
void addInterceptors(InterceptorRegistry registry) {
// 添加登录拦截器
registry.addInterceptor(
new LoginInterceptor(
)
)
// 设置放行请求
.excludePathPatterns(
"/user/code"
,
"/user/login"
,
"/blog/hot"
,
"/shop/**"
,
"/shop-type/**"
,
"/upload/**"
,
"/voucher/**"
).order(1
)
;
// 优先级默认都是0,值越大优先级越低
// 添加刷新token的拦截器
registry.addInterceptor(
new RefreshTokenInterceptor(stringRedisTemplate)
).addPathPatterns("/**"
).order(0
)
;
}
}

需要注意的是 我这里用到的是qq邮箱来发验证码 所以还需要配置qq邮箱的工具类

package com.hmdp.utils
;
import javax.mail.*
;
import javax.mail.internet.InternetAddress
;
import javax.mail.internet.MimeMessage
;
import javax.mail.internet.MimeMessage.RecipientType
;
import java.util.*
;
public
class MailUtils {
public
static
void main(String[] args)
throws MessagingException {
sendTestMail("收件人邮箱@qq.com"
,
new MailUtils(
).achieveCode(
)
)
;
}
public
static
void sendTestMail(String email, String code)
throws MessagingException {
Properties props =
new Properties(
)
;
props.put("mail.smtp.auth"
, "true"
)
;
props.put("mail.smtp.host"
, "smtp.qq.com"
)
;
props.put("mail.smtp.port"
, "465"
)
;
props.put("mail.smtp.socketFactory.class"
, "javax.net.ssl.SSLSocketFactory"
)
;
props.put("mail.smtp.socketFactory.port"
, "465"
)
;
props.put("mail.smtp.socketFactory.fallback"
, "false"
)
;
props.put("mail.user"
, "198733702@qq.com"
)
;
props.put("mail.password"
, "lvxzwwixjiewcbcf"
)
;
Authenticator authenticator =
new Authenticator(
) {
protected PasswordAuthentication getPasswordAuthentication(
) {
String userName = props.getProperty("mail.user"
)
;
String password = props.getProperty("mail.password"
)
;
return
new PasswordAuthentication(userName, password)
;
}
}
;
Session mailSession = Session.getInstance(props, authenticator)
;
MimeMessage message =
new MimeMessage(mailSession)
;
InternetAddress from =
new InternetAddress(props.getProperty("mail.user"
)
)
;
message.setFrom(from)
;
InternetAddress
to =
new InternetAddress(email)
;
message.setRecipient(RecipientType.TO
,
to
)
;
message.setSubject("Kyle's Blog 邮件测试"
)
;
message.setContent("尊敬的用户:你好!\n注册验证码为:" + code + "(有效期为一分钟,请勿告知他人)"
, "text/html;charset=UTF-8"
)
;
Transport.send(message)
;
}
public
static String achieveCode(
) {
String[] beforeShuffle =
new String[]{
"2"
, "3"
, "4"
, "5"
, "6"
, "7"
, "8"
, "9"
,
"A"
, "B"
, "C"
, "D"
, "E"
, "F"
, "G"
, "H"
,
"I"
, "J"
, "K"
, "L"
, "M"
, "N"
, "P"
, "Q"
,
"R"
, "S"
, "T"
, "U"
, "V"
, "W"
, "X"
, "Y"
, "Z"
,
"a"
, "b"
, "c"
, "d"
, "e"
, "f"
, "g"
, "h"
,
"i"
, "j"
, "k"
, "l"
, "m"
, "n"
, "p"
, "q"
,
"r"
, "s"
, "t"
, "u"
, "v"
, "w"
, "x"
, "y"
, "z"
}
;
List<
String> list = Arrays.asList(beforeShuffle)
;
Collections.shuffle(list)
;
StringBuilder sb =
new StringBuilder(
)
;
for (String s : list) {
sb.append(s)
;
}
return sb.substring(3
, 8
)
;
}
}

这段代码不仅配置了工具类 还写了随机生成验证码的方法

mail:
host: smtp.qq.com
port: 465
username: 198733702@qq.com
password: x
protocol: smtps

不要忘记配置qq邮箱的yml文件

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

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

相关文章

[数学 - 方差 标准差 ]

假设和平均距离 比如两个人的射击成绩,我们要挑选一个“更加稳定”的选手:平均值相同时:假设样本数据是 \(x_1, x_2, \dots, x_n\) \(\bar{x}\) 表示这组数据的平均数。 所以,样本数据到 \(\bar{x}\) 的“平均距离…

黄南州wap网站建设公司代理ip大全

数据库请求接口封装 uniapp中提供了plus.sqlite接口&#xff0c;在这里我们对常用的数据库请求操作进行了二次封装 这里的dbName、dbPath、recordsTable 可以根据你的需求自己命名 module.exports {/** * type {String} 数据库名称*/dbName: salary,/*** 数据库地址* type {…

鄢陵县北京网站建设长沙网站制作工作室

需求&#xff1a;页面跳转到目标页面之后&#xff0c;对应的顶部路由高亮 上面的更多 跳转到 学情分析下面的学生分析 <template><div class"topBar" ref"topBar" v-loading.fullscreen.lock"fullscreenLoading"><div class&quo…

二分法算法技巧-思维提升 - 教程

二分法算法技巧-思维提升 - 教程2025-10-07 11:08 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !import…

开源量子模拟引擎:Quantum ESPRESSO本地部署教程,第一性原理计算轻松入门! - 实践

开源量子模拟引擎:Quantum ESPRESSO本地部署教程,第一性原理计算轻松入门! - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; f…

基于Python+Vue开发的大学竞赛报名管理系统源码+运行步骤

项目简介该项目是基于Python+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习…

详细介绍:QT常用控件(1)

详细介绍:QT常用控件(1)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", …

网站制作素材绿色商城网站模板

多参数方法的问题相信很多人曾经都写过多参数的构造方法&#xff0c;就像下面示例的代码。当想要创建一个给全部属性赋值的实例的时候&#xff0c;就会利用这个多参数的构造方法。但是&#xff0c;当类的属性特别多的时候&#xff0c;你还会这么写吗&#xff1f;如果你写了一个…

Generate First, Then Sample: Enhancing Fake News Detection with LLM-Augmented Reinforced Sampling

论文阅读1. 任务 如何提高假新闻检测的效果,特别是针对假新闻本身的检测能力 2. 研究背景与问题提出:假新闻指包含虚假信息的新闻,其在社交媒体上的传播对公共卫生、政治等关键社会议题构成严重威胁。现有假新闻检测…

开发电商网站多少钱刷赞网站推广软件

文末下载上传环境源码客户端js检查一般都是在网页上写一段javascript脚本&#xff0c;校验上传文件的后缀名&#xff0c;有白名单形式也有黑名单形式。查看源代码可以看到有如下代码对上传文件类型进行了限制&#xff1a;我们可以看到对上传文件类型进行了限制。绕过方法1.我们…

手机网站网站开发流程开发软件开发

目录 一、常见的压缩包的扩展名 二、常见的压缩和解压指令 1、tar 2、tar gzip&#xff08;.tar.gz&#xff09; (或 .tgz) 3、tar bzip2&#xff08;.tar.bz2&#xff09; 4、zip 5、gzip 6、bzip2 7、xz 8、rar 9、7z 三、安装解压工具 一、常见的压缩包的扩展…

数据大屏

数据大屏 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1…

K8S上采用helm部署 Prometheus + Grafana

K8S上采用helm部署 Prometheus + Grafanapre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "M…

AI元人文的硅基基石体系:EPU+VPU+WBUC+WAUC深度解析——声明Ai解析

AI元人文的硅基基石体系:EPU+VPU+WBUC+WAUC深度解析 一、引言:硅基生命的人文基因 在2025年阿里云栖大会上,"云智一体,碳硅共生"的主题引发了人们对未来智能形态的深刻思考。随着人工智能技术的迅猛发展…

做熊猫tv网站的目的互联网电子商务网站开发技术

前言 踩着前人的肩膀&#xff0c;努力前行。参考了很多前人的文章。 1.变量声明const和let es6之前声明变量只能用var&#xff0c;var的特点是无论声明在何处&#xff0c;都会被视为声明在函数的最顶部(不在函数内即在全局作用域的最顶部) function test(){if(false){var name …

优秀网站建设哪家专业网页打不开用什么浏览器

引子 网络层能够被分解为两个相互作用的部分&#xff1a; 数据平面和控制平面。 网络层概述 路由器具有截断的协议栈&#xff0c;即没有网络层以上的部分。 如下图所示&#xff0c;是一个简单网络&#xff1a; 转发和路由选择&#xff1a;数据平面和控制平面 网络层的作用…

做画册去什么网站找素材网站建设新闻咨询

官方文档 在前面 文章目录 uboot常见命令学习环境变量网络控制台uboot标准启动其他 升级uboot或内核bin和uimg以及booti和bootm的区别制作uImage更换内核更换uboot后续计划 uboot常见命令学习 环境变量 Environment Variables环境变量 autostart 如果值为yes&#xff0c;则会…

电脑做网站服务器需要什么软件手机网站 侧边栏导航

&#x1f451;专栏内容&#xff1a;Java⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、泛型1、什么是泛型2、泛型的语法 二、泛型类的使用1、泛型类的语法2、泛型如何编译的2.1、擦除机制2.2、为什么不能实例化泛…

详细介绍:VSCode+Cline 安装配置及使用说明

详细介绍:VSCode+Cline 安装配置及使用说明2025-10-07 10:44 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: bl…

淮安软件园哪家做网站建设网站需要有什么特色

当我们谈论编程中的数据结构时&#xff0c;顺序容器是不可忽视的一个重要概念。顺序容器是一种能够按照元素添加的顺序来存储和检索数据的数据结构。它们提供了简单而直观的方式来组织和管理数据&#xff0c;为程序员提供了灵活性和性能的平衡。 Qt 中提供了丰富的容器类&…