基于通达信交易接口,session实现短信登录
发送短信验证码
流程如下:实现UserController下的sendCode方法:
/**
* 发送手机验证码
*/
@PostMapping("/code")
public Result sendCode(@RequestParam("phone") String phone, Http通达信交易接口,session session) {
// 发送短信验证码并保存验证码
return userService.sendCode(phone, session);
}
在userService下添加sendCode方法,根据流程实现每一步的功能,校验手机号以及生成验证码的功能都是直接封装好的,在这里仅仅是利用后端模拟了发送验证码的功能:
public Result sendCode(String phone, HttpSession session) {
//1、校验手机号
if(isPhoneInvalid(phone)){
//2、手机号无效,返回错误信息
return Result.fail("手机号无效!");
}
//3、手机号有效,生成验证码
String code = RandomUtil.randomNumbers(6);
//4、将验证码保存在session中
session.setAttribute("code", code);
//5、模拟发送验证码
log.debug("发送验证码成功:验证码:{}", code);
//返回ok
return Result.ok();
}
前端验证码获取成功
短信验证码登录注册
流程如下:实现UserController的下的login方法:
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// 实现登录功能
return userService.login(loginForm, session);
}
在UserService下添加login方法,同样根据流程实现功能,在这里隐藏了用户的敏感信息,如密码等,仅在session中存入电话,头像以及昵称信息,通过BeanUtil.copyProperties实现属性复制,将User对象转化为UserDto:
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1、校验手机号
String phone = loginForm.getPhone();
if(isPhoneInvalid(phone)){
//2、手机号无效,返回错误信息
return Result.fail("手机号无效!");
}
//手机号有效
//4、取出session中的验证码
Object cacheCode = session.getAttribute("code");
//5、取出登录时输入的验证码
String loginCode = loginForm.getCode();
//6、校验验证码,如果验证码过期或者未获取到,以及验证码不正确
if(cacheCode == null || !cacheCode.toString().equals(loginCode)){
//7、返回错误信息
return Result.fail("验证码错误");
}
//8、查询用户是否存在
User user = query().eq("phone", phone).one();
//9、不存在,创建新用户
if(user == null) {
user = createUserWithPhone(phone);
}
//10、将用户信息存入session
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));//隐藏用户的敏感信息,仅返回用户手机号,id,头像
return Result.ok();
}
private User createUserWithPhone(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
//保存用户
save(user);
return user;
}
校验登录状态
在第2步中,我们已经实现了短信验证码登录注册的功能,但是没有做登录校验。上中的请求是携带了cookie的,cookie里是包含了JSESSIONID的。
服务端可以基于JSESSIONID来获得session,再从session里取出用户,进而来判断该用户是否存在。
但是这个流程里有一个问题,我们需要在每一个controller里来写这些业务逻辑。
我们可以通过拦截器来统一拦截判断,最后决定是否放行。
此外,如果要做分布式session,会存在系统负担和性能以及安全问题。考虑到系统负担和安全,我们可以在拦截器拦截到之后,将session中的用户信息保存到ThreadLocal中。每一个进入Tomcat的请求都是一个独立的线程,那么将来ThreadLocal就会在线程内开启一个独立的空间去保存这些请求。这样一来,不同的用户访问controller,都是一个独立的线程,每一个线程都有自己的用户信息,相互独立不干扰,controller从ThreadLocal中取出用户信息。)
我们新建一个拦截器loginIntercepter:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1、获取session
HttpSession session = request.getSession();
//2、获取session中的用户
Object user = session.getAttribute("user");
//3、判断用户是否存在
if(user == null) {
//4、不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
//5、存在,保存信息到ThreadLocal
UserHolder.saveUser((UserDTO) user);
//6、放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//7、移除用户
UserHolder.removeUser();
}
}
接下来我们配置这个拦截器,新建一个MvcConfig实现WebMvcConfigurer接口,将拦截器添加,并且排除掉不需要拦截的路径:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
session共享问题的分析
文章为作者独立观点,不代表股票配资公司观点