大众点评项目 基于Session的短信登录
- 需求:基于Session实现短信验证登录
- 基于Session的短信登录
- 发送手机验证码
- 实现登录 (注意MyBatisP的接口使用)
- 新的问题
SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则
- 基础+实战的Demo和Coding上传到我的代码仓库
- 在原有基础上加入一些设计模式,stream+lamdba等新的糖
- 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式
代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
Session-链接: RedisProjectDemo
Redis优化-链接: RedisProject
需求:基于Session实现短信验证登录
本项目是一个前后端分离项目,系统框架和展示如下
我们希望在登录可以通过短信验证方式进行注册/登录
基于Session的短信登录
我们获得前端参数,实现登录验证功能
我们先给出架构图
发送手机验证码
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// TODO 发送短信验证码并保存验证码
// return Result.fail("功能未完成");
return userService.sendCode(phone, session);
}
public interface IUserService extends IService<User> {
Result sendCode(String phone, HttpSession session);
Result login(LoginFormDTO loginForm, HttpSession session);
}
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
if(RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号码格式错误!");
}
String code = RandomUtil.randomNumbers(6);
//模拟发短信,将验证码放到session
session.setAttribute("code", code);
log.debug("发送短信验证码成功: "+ code);
return Result.ok();
}
实现登录 (注意MyBatisP的接口使用)
- @RequestBody是为了封装信息,实现自动配置
- @RequestParam对应了前端传来的参数对应
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// TODO 实现登录功能
// return Result.fail("功能未完成");
return userService.login(loginForm, session);
}
public interface IUserService extends IService<User> {
Result sendCode(String phone, HttpSession session);
Result login(LoginFormDTO loginForm, HttpSession session);
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//从前端拿到对应的手机号
String phone = loginForm.getPhone();
if ((RegexUtils.isPhoneInvalid(phone))) {
return Result.fail("手机格式错误!");
}
String code = loginForm.getCode();
//获取对应的code
Object cacheCode = session.getAttribute("code");
if (cacheCode == null || !cacheCode.toString().equals(code)) {
return Result.fail("验证码错误!");
}
// 这里是MyBatisP的命令
User user = query().eq("phone", phone).one();
//如果没有就去创建一个User
if(user==null){
user = createUserWithPhone(phone);
}
//将user通过BeanUtil工具类封装到DTO对象
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
将user通过BeanUtil工具类封装到DTO对象,可以大大减少不必要数据的传输
所以设置了DTO层封装数据处理
private User createUserWithPhone(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(8));
save(user);
return user;
}
- 通过ThreadLocal存放当前对象,实现全局获取效果;
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
- 实现拦截器进行登录验证
因为要对 限制的资源进行验证,所以使用了拦截器来进行处理;
public class LoginInterceptor1 implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public LoginInterceptor1(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("user");
if (user == null) {
response.setStatus(401);
return false;
}
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
UserHolder.saveUser(userDTO);
return true;
}
@Override
public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception
ex) throws Exception {
//保存到当前线程中,直接remove即可
UserHolder.removeUser();
System.out.println("123");
}
}
这里的构造注入是因为
private StringRedisTemplate stringRedisTemplate; public LoginInterceptor1(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; }
这里的LoginInterceptor
是通过new来实现的,没有配置到Bean工厂中去,所以通过这种方式实现
后面的配置类中会进行注入
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"voucher/**",
"shop-type/**",
"upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
这里是为了拦截请求,做限制处理