个人简介:Java领域优质创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~
登录流程
:
发送验证码
:
用户输入手机号,点击发送按钮进行手机号提交,程序会校验手机号是否合法,不合法时要求用户重新输入手机号,合法则在后台生成对应的验证码并保存至session
,之后通过短信方式将验证码发送给用户。
什么是HttpSession?
HttpSession是Java Web中的一个接口,它提供了一种在服务器端存储和检索用户相关信息的机制。当用户第一次访问Web应用程序时,服务器会为该用户创建一个唯一的session ID,并将该ID存储在一个名为JSESSIONID的cookie中,然后将该ID与一个新的HttpSession对象相关联。在用户与Web应用程序交互期间,可以使用HttpSession对象来存储和检索与该用户相关的信息。当用户关闭浏览器或超过session超时时间时,session对象将被销毁。
以下是获取和使用HttpSession对象的常用方法:
1.获取HttpSession对象:
HttpSession session = request.getSession();
2.向session中存储数据:
session.setAttribute("key", value);
3.从session中获取数据:
Object value = session.getAttribute("key");
4.从session中删除数据:
session.removeAttribute("key");
5.使session失效:
session.invalidate();
注册、登录
:校验登陆状态
:
用户在客户端发起请求时,Cookie会携带用户的 JsessionId 后台,后台根据 JsessionId 从session中获取用户信息,如果没有用户信息就表示未登录,会对请求进行拦截,如果有用户信息,将其存入到本地线程 ThreadLocal 中并放行。
为什么使用ThreadLocal:
什么是 JsessionId ?
JSessionId是Java Web应用程序中的一个会话标识符,用于跟踪用户与Web应用程序之间的会话。当用户第一次访问Web应用程序时,服务器会为该用户创建一个唯一的JSessionId,并将其存储在cookie中。在随后的请求中,浏览器会将该cookie发送回服务器,以便服务器可以识别用户并维护会话状态。
在Java Web应用程序中,可以使用HttpSession对象来访问和管理会话状态。
业务逻辑实现
:
统一返回类型 实体类
:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Boolean success;
private String errorMsg;
private Object data;
private Long total;
public static Result ok(){
return new Result(true, null, null, null);
}
public static Result ok(Object data){
return new Result(true, null, data, null);
}
public static Result ok(List<?> data, Long total){
return new Result(true, null, data, total);
}
public static Result fail(String errorMsg){
return new Result(false, errorMsg, null, null);
}
}
校验手机号、邮箱、验证码格式
:
public class RegexUtils {
/**
* 是否是无效手机格式
* @param phone 要校验的手机号
* @return true:符合,false:不符合
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexPatterns.PHONE_REGEX);
}
/**
* 是否是无效邮箱格式
* @param email 要校验的邮箱
* @return true:符合,false:不符合
*/
public static boolean isEmailInvalid(String email){
return mismatch(email, RegexPatterns.EMAIL_REGEX);
}
/**
* 是否是无效验证码格式
* @param code 要校验的验证码
* @return true:符合,false:不符合
*/
public static boolean isCodeInvalid(String code){
return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
}
// 校验是否不符合正则格式
private static boolean mismatch(String str, String regex){
if (StrUtil.isBlank(str)) { //Hutool工具:StrUtil
return true;
}
return !str.matches(regex);
}
}
发送短信验证码 业务
:
@Override
public Result sendCode(String phone, HttpSession session) {
//1. 手机号不合法?
if(RegexUtils.isPhoneInvalid(phone)){
//2. 不合法,返回错误信息
return Result.fail("手机号格式错误!");
}
//3. 借助工具类,生成验证码(Hutool工具)
String code = RandomUtil.randomNumbers(6);
//4. 保存验证码至session域
session.setAttribute("code",code);
//5. 发送验证码
log.debug("发送短信验证码成功,验证码: " + code); //日志、方便控制台查看
/*
调用验证码服务...(具体逻辑参照具体服务供应商的文档)
*/
//6. 返回ok
return Result.ok();
}
登录、注册 业务
:
/**
* session实现登录功能
* @param loginForm
* @param session
* @return
*/
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1. 校验手机号
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//2. 返回错误信息
return Result.fail("手机号格式错误");
}
//3. 校验验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if(code == null || !code.toString().equals(cacheCode)){
//不一致,返回错误信息
return Result.fail("验证码错误");
}
// 一致,根据手机号获取用户
User user = this.query().eq("phone", phone).one(); //(mybatisPlus提供的Service层方法)
//5. 判断用户是否存在
if(user == null){
//6. 不存在,创建新用户
user = new User();
user.setPhone(phone); //设置phone
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); //设置随机昵称
this.save(user); // 存入数据库(mybatisPlus提供的Service层方法)
}
//7. 用户存在,存入session域
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
//返回ok
return Result.ok();
}
创建并设置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();
}
}
校验登陆状态 拦截器
:
/**
* TODO 登录 拦截器
* @author .29.
* @create 2023-11-26 16:37
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取session
HttpSession session = request.getSession();
//2. 获取用户
Object user = session.getAttribute("user");
//3. 验证用户是否存在
if(user == null){
//4. 不存在,进行拦截,返回401状态码
response.setStatus(401);
return false;
}
//5. 存在,存入ThreadLocal(自定义工具类UserHolder,作用:创建并设置ThreadLocal)
UserHolder.saveUser((UserDTO) user);
//6. 放行
return true;
}
}
Spring Boot使用Spring MVC拦截器的步骤如下:
1.创建一个拦截器类并实现HandlerInterceptor接口
,该接口包含三个方法:preHandle、postHandle和afterCompletion。preHandle方法在请求处理之前调用,postHandle方法在请求处理之后调用,afterCompletion方法在视图渲染之后调用。
2.在拦截器类上使用@Component
或@Configuration
注解将其声明为Spring组件。
3.创建一个配置类并实现WebMvcConfigurer
接口,该接口包含一个addInterceptors方法,用于注册拦截器。
4.在addInterceptors方法中使用addInterceptor方法注册拦截器,并使用addPathPatterns方法指定要拦截的请求路径。
使拦截器生效 SpringMvc配置类
:
/**
* TODO SpringMVC配置类,使拦截器生效
* @author .29.
* @create 2023-11-26 16:49
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1. 添加登录拦截器、同时设置无需拦截的路径
registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(0); //order默认0,order值越大拦截器越后执行
}
}