总览:
sql:
架构:
后端部署:
前端:
基于session登录:
1、发送验证码
public Result sendCode(String phone, HttpSession session) {
// 1、校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号输入有误");
}
// 2、生成验证码,使用hutool工具类
String code = RandomUtil.randomNumbers(6);
// 3、保存验证码
// session.setAttribute("code", code);
// 3.1 保存验证码到redis
redisUtil.set(LOGIN_CODE_KEY + phone, code, 60);
// 4、发送验证码
log.debug("发送验证码:{}", code);
return Result.ok("验证码发送成功");
}
2、短信验证码登录、注册
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1、校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号输入有误");
}
// 2、校验验证码
// Object cacheCode = session.getAttribute("code");
// 2.1 从redis中获取验证码
Object cacheCode = redisUtil.get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
return Result.fail("验证码错误");
}
// 3、根据手机号查询用户
User user = this.lambdaQuery().eq(User::getPhone, phone).one();
if (user == null) {
user = finduser(phone);
}
// session.setAttribute("user", user);
// 4、保存用户信息到redis
// 生成token
String token = UUID.randomUUID().toString();
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((filedName, fieldValue) -> fieldValue.toString()));
redisUtil.hmset(LOGIN_USER_KEY + token, map, 60 * 30);
return Result.ok(token);
}
private User finduser(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + phone.substring(7));
this.save(user);
return user;
// TODO 注册
}
3、实现登录校验拦截器
LoginIntetceptor.java
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1、获取session中的用户信息
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
// 2、判断用户是否存在
if (user == null) {
// 3、如果不存在
// response.sendRedirect("/login.html");
response.setStatus(401);
return false;
}
// 4、如果存在,放行
UserHolder.saveUser(userDTO);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
MvcConfig.java
package com.hmdp.config;
import com.hmdp.utils.LoginIntetceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configurationpublic
class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntetceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login",
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/**",
"/user/code",
"/user/login");
}
}
4、集群的session共享问题
5、基于Redis实现共享session问题
Redis保存对象选用数据结构:
key设计要求:
1、唯一性
2、方便后续调用
修改校验流程:
前端设计:
登录成功后将返回的key存入本地sessionstorage,
然后在每次拦截时都将携带存入token(如果有token存在)
UserServiceImpl.java:
public Result sendCode(String phone, HttpSession session) {
// 1、校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号输入有误");
}
// 2、生成验证码,使用hutool工具类
String code = RandomUtil.randomNumbers(6);
// 3、保存验证码
// session.setAttribute("code", code);
// 3.1 保存验证码到redis
redisUtil.set(LOGIN_CODE_KEY + phone, code, 60);
// 4、发送验证码
log.debug("发送验证码:{}", code);
return Result.ok("验证码发送成功");
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1、校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号输入有误");
}
// 2、校验验证码
// Object cacheCode = session.getAttribute("code");
// 2.1 从redis中获取验证码
Object cacheCode = redisUtil.get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
return Result.fail("验证码错误");
}
// 3、根据手机号查询用户
User user = this.lambdaQuery().eq(User::getPhone, phone).one();
if (user == null) {
user = finduser(phone);
}
// session.setAttribute("user", user);
// 4、保存用户信息到redis
// 生成token
String token = UUID.randomUUID().toString();
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((filedName, fieldValue) -> fieldValue.toString()));
redisUtil.hmset(LOGIN_USER_KEY + token, map, 60 * 30);
return Result.ok(token);
}
LoginIntetceptor.java:
注意由于该拦截器未注入ioc容器,故无法使用@Autowired注入RedisUtil,因此在MvcConfig处注入RedisConfig。配合使用构造方法进行注入
public class LoginIntetceptor implements HandlerInterceptor {
private RedisUtil redisUtil;
public LoginIntetceptor(RedisUtil redisUtil) {
this.redisUtil = redisUtil;
}
@Override
public Boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1、获取session中的用户信息
// HttpSession session = request.getSession();
// 1.1 获取请求头中的token
String token=request.getHeader("authorization");
if (ObjectUtils.isEmpty(token)) {
response.setStatus(401);
return false;
}
// 1.2 基于token从redis中获取用户信息
Map<Object, Object> userMap = redisUtil.hmget(LOGIN_USER_KEY + token);
// 2、判断用户是否存在
if (userMap.isEmpty()) {
// 3、如果不存在
// response.sendRedirect("/login.html");
response.setStatus(401);
return false;
}
// 4、如果存在,放行
// 4.1 将map转换为userDTO
UserDTO userDTO = new UserDTO();
BeanUtil.fillBeanWithMap(userMap, userDTO, false);
UserHolder.saveUser(userDTO);
// 5、刷新用户的过期时间
redisUtil.expire(LOGIN_USER_KEY + token, 60*30);
return true;
}
6、登录拦截器的优化
RefreshInterceptor:
public class RefreshInterceptor implements HandlerInterceptor {
public RedisUtil redisUtil;
public RefreshInterceptor(RedisUtil redisUtil){
this.redisUtil=redisUtil;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1、获取session中的用户信息
// HttpSession session = request.getSession();
// 1.1 获取请求头中的token
String token=request.getHeader("authorization");
if (ObjectUtils.isEmpty(token)) {
return true;
}
// 1.2 基于token从redis中获取用户信息
Map<Object, Object> userMap = redisUtil.hmget(LOGIN_USER_KEY + token);
// 2、判断用户是否存在
if (userMap.isEmpty()) {
// 3、如果不存在
// response.sendRedirect("/login.html");
return true;
}
// 4、如果存在,放行
// 4.1 将map转换为userDTO
UserDTO userDTO = new UserDTO();
BeanUtil.fillBeanWithMap(userMap, userDTO, false);
UserHolder.saveUser(userDTO);
// 5、刷新用户的过期时间
redisUtil.expire(LOGIN_USER_KEY + token, 60*30);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
LoginInterceptor:
public class LoginIntetceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断是否需要拦截
if (UserHolder.getUser()==null){
response.setStatus(401);
return false;
}
return true;
}
}