- 一、导入黑马点评项目
- 1. 导入SQL
- 2. 前后端分离
- 3. 导入后端项目
- 3.1 将后端项目导入到 Idea 中
- 3.2 注意:修改application.yaml文件中的mysql、redis地址信息
- 3.3 启动项目
- 4. 导入前端项目
- 4.1 导入nginx文件夹
- 4.2 运行前端项目
- 二、基于Session实现登录流程
- 1. 发送短信验证码
- 2. 短信验证码登录、注册
- 3. 登录验证功能
- 三、集群的session共享问题
- 四、基于Redis实现共享session的登录功能
- 1. 选择合适的数据结构存入Redis
- 2. 发送短信验证码
- 3. 短信验证码登录、注册
- 4. 解决token刷新问题
- 黑马点评项目主要包括以下功能:
- 这一章主要介绍短信登录功能,短信登录功能是基于Redis的共享session实现的
- 需要项目资料的私信我
其中的表有:
- tb_user:用户表
- tb_user_info:用户详情表
- tb_shop:商户信息表
- tb_shop_type:商户类型表
- tb_blog:用户日记表(达人探店日记)
- tb_follow:用户关注表
- tb_voucher:优惠券表
- tb_voucher_order:优惠券的订单表
注意:Mysql的版本采用5.7及以上版本
- 将mysql、redis地址信息修改为自己的信息
- 启动项目后,在浏览器访问:http://localhost:8081/shop-type/list ,如果可以看到数据则证明运行没有问题
- 将nginx文件夹复制到任意目录,要确保该目录不包含中文、特殊字符和空格,例如:
- 在nginx所在目录下打开一个CMD窗口,输入命令启动nginx:
start nginx.exe
- 打开chrome浏览器,在空白页面点击鼠标右键,选择检查,即可打开开发者工具:
- 然后访问: http://127.0.0.1:8080 ,即可看到页面:
- 后端将生成的验证码和用户信息保存到session中,并将sessionId返回给前端保存到cookie中
- 用户登录时,会携带cookie向后端发起请求,后端进行校验时,从cookie中获取sessionId,通过sessionId可以从session中获取用户信息并保存到ThreadLocal中
- 后续每个线程都有一份ThreadLocal中的用户副本信息,不同线程拿到用户信息后可以实现不同的操作,从而起到线程隔离作用
主要代码:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// 发送短信验证码并保存验证码
return userService.sendCode(phone, session);
}
}
@Service @Slf4j public class UserServiceImpl extends ServiceImpl2. 短信验证码登录、注册implements IUserService { @Override public Result sendCode(String phone, HttpSession session) { // 1.使用工具类校验手机号 if (RegexUtils.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
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// 实现登录功能
return userService.login(loginForm, session);
}
- UserServiceImpl
@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");
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
// 3.验证码不一致,则报错
return Result.fail("验证码错误");
}
// 4.验证码一致,根据手机号查询用户
User user = query().eq("phone", phone).one();
// 5.判断用户是否存在
if (user == null) {
// 6.用户不存在,则创建用户并保存
user = createUserWithPhone(phone);
}
// 7.保存用户信息到session中,UserDTO只包含简单的用户信息,
// 而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
// 8.返回ok
return Result.ok();
}
private User createUserWithPhone(String phone) {
// 1.创建用户
User user = new User();
user.setPhone(phone);
// 随机设置昵称 user_mrkuw05lok
user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
// 2.保存用户
save(user);
return user;
}
3. 登录验证功能
- 用户请求登录时,会携带cookie,cookie中包含JSEESIONID
- 为了避免用户请求每个controller时,每次都去校验用户信息,所以可以加拦截器
- 拦截器只需在用户请求访问时,校验一次后将用户信息保存到ThreadLocal中,供后续线程使用
主要代码:
- 在工具类中编写ThreadLocal
public class UserHolder {
private static final ThreadLocal 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 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((User)user);
// 6.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// 请求结束后移除用户,防止ThreadLocal造成内存泄漏
UserHolder.removeUser();
}
}
- 在配置类中添加拦截器配置类
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截器
registry.addInterceptor(new LoginInterceptor())
// 排除不需要拦截的路径
.excludePathPatterns(
"/shop
@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;
// 1.获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
// 不存在,则拦截,返回401状态码
response.setStatus(401);
return false;
}
// 2.通过token获取redis中的用户
Map
登录拦截器的代码:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.判断是否需要拦截(ThreadLocal中是否有用户)
if (UserHolder.getUser() == null) {
// 没有,需要拦截,设置状态码
response.setStatus(401);
// 拦截
return false;
}
// 有用户,则放行
return true;
}
}
@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"
).order(1);
// token刷新的拦截器,order越小,执行优先级越高,所以token刷新的拦截器先执行
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**")
.excludePathPatterns(
// RefreshTokenInterceptor拦截器也需要放行"/user/code","/user/login",不然token过期后再重新登录就会一直被拦截
"/user/code",
"/user/login")
.order(0);
}
}



