写在前面

瑞吉外卖的基础框架已经搭建好了,在这个基础之上,进行优选的开发。

负责的模块

我所负责的模块是:

  • 用户端的登录、登出
  • 短信的发送
  • 用户地址簿

实现内容

用户端的登录

需求分析

为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。手机验证码登录有如下优点:

  • 方便快捷,无需注册,直接登录
  • 使用短信验证码作为登陆凭证,无需记忆密码
  • 安全性高

登录流程:

输入手机号->获取验证码->输入验证码->点击登录->登陆成功

注意:通过手机验证码登录,手机号是区分不同用户的标识

数据模型

通过手机验证码登录时,涉及的表为 user 表,即用户表。结构如下:

image-20210807231948412

前端页面分析

在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:

  1. 登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送 ajax 请求,在服务端调用短信服务 API 给指定手机号发送验证码短信。

image-20210807233018171

在登录页面输入验证码,点击【登录】按钮,发送 ajax 请求,在服务端处理登录请求。

image-20210807233336029

如果服务端返回的登录成功,页面将会把当前登录用户的手机号存储在 sessionStorage中,并跳转到移动的首页页面。

开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这 2 次请求即可,分别是获取短信验证码 和 登录请求,具体的请求信息如下:

1). 获取短信验证码

请求 说明
请求方式 POST
请求路径 /user/sendMsg
请求参数 {"phone":"13100001111"}

2). 登录

请求 说明
请求方式 POST
请求路径 /user/login
请求参数 {"phone":"13100001111", "code":"1111"}

代码开发

在开发业务功能前,先将需要用到的类和接口基本结构创建好:

User-Entity、UserMapper、UserService、UserServiceImpl、UserController、SMSUtils、ValidateCodeUtils。

这边的具体代码,可以参考我的github:takeout项目。在此不做赘述。

功能实现

  • 修改LoginCheckFilter:

    • 前面我们已经完成了 LoginCheckFilter 过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码登录时,发送的两个请求(获取验证码和登录)需要在此过滤器处理时直接放行。

      image-20210807235349089

      对于移动的端的页面,也是用户登录之后,才可以访问的,那么这个时候就需要在 LoginCheckFilter 中进行判定,如果移动端用户已登录,我们获取到用户登录信息,存入 ThreadLocal 中(在后续的业务处理中,如果需要获取当前登录用户 ID,直接从 ThreadLocal 中获取),然后放行。

      增加如下逻辑:

1
2
3
4
5
6
7
8
9
10
//4-2、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("user") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));

Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);

filterChain.doFilter(request,response);
return;
}
代码位置: ![img](http://yanjob.tpddns.net:22919/assets/2022-09-27-09-58-16.7ac04a26.png)
  • 发送验证码

    • 在 UserController 中创建方法 ,处理登录页面的请求,为指定手机号发送短信验证码,同时需要将手机号对应的验证码保存到 Session,方便后续登录时进行比对。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 发送手机短信验证码
* @param user
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
//获取手机号
String phone = user.getPhone();
if(StringUtils.isNotEmpty(phone)){
//生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);

//调用阿里云提供的短信服务API完成发送短信
//SMSUtils.sendMessage("瑞吉外卖","",phone,code);

//需要将生成的验证码保存到Session
session.setAttribute(phone,code);
return R.success("手机验证码短信发送成功");
}
return R.error("短信发送失败");
}
> 备注: > > 这里发送短信我们只需要调用封装的工具类中的方法即可,我们这个功能流程跑通,在测试中我们不用真正的发送短信,只需要将验证码信息,通过日志输出,登录时,我们直接从控制台就可以看到生成的验证码(实际上也就是发送到我们手机上的验证码)
  • 验证码登录

    • 在 UserController 中增加登录的方法 login,该方法的具体逻辑为:

      1). 获取前端传递的手机号和验证码 2). 从 Session 中获取到手机号对应的正确的验证码 3). 进行验证码的比对 , 如果比对失败, 直接返回错误信息 4). 如果比对成功, 需要根据手机号查询当前用户, 如果用户不存在, 则自动注册一个新用户 5). 将登录用户的 ID 存储 Session 中

      具体代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 移动端用户登录
* @param map
* @param session
* @return
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
log.info(map.toString());
//获取手机号
String phone = map.get("phone").toString();
//获取验证码
String code = map.get("code").toString();
//从Session中获取保存的验证码
Object codeInSession = session.getAttribute(phone);

//进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
if(codeInSession != null && codeInSession.equals(code)){
//如果能够比对成功,说明登录成功

LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);

User user = userService.getOne(queryWrapper);
if(user == null){
//判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("user",user.getId());
return R.success(user);
}
return R.error("登录失败");
}