隐藏页面特效

个人博客项目笔记_04

1|01. 注册


1|11.1 接口说明


接口url:/register

请求方式:POST

请求参数:

参数名称 参数类型 说明
account string 账号
password string 密码
nickname string 昵称

返回数据:

{ "success": true, "code": 200, "msg": "success", "data": "token" }

1|21.2 Controller


package com.cherriesovo.blog.controller; import com.cherriesovo.blog.service.LoginService; import com.cherriesovo.blog.vo.Result; import com.cherriesovo.blog.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("register") public class RegisterController { @Autowired private LoginService loginService; @PostMapping public Result register(@RequestBody LoginParam loginParam){ //sso单点登录,后期如果把登陆注册功能提出去(单独的服务 可以独立提供接口服务) return loginService.register(loginParam); } }

参数LoginParam类中 添加新的参数nickname。

package com.cherriesovo.blog.vo.params; import lombok.Data; import org.apache.commons.lang3.StringUtils; @Data public class LoginParam { private String account; private String password; private String nickname; }

1|31.3 Service


package com.cherriesovo.blog.service; import com.cherriesovo.blog.dao.pojo.SysUser; import com.cherriesovo.blog.vo.Result; public interface SysUserService { //根据账户查询用户 SysUser findUserByAccount(String account); //保存用户 void save(SysUser sysUser); }
package com.cherriesovo.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.cherriesovo.blog.dao.mapper.SysUserMapper; import com.cherriesovo.blog.dao.pojo.SysUser; import com.cherriesovo.blog.service.LoginService; import com.cherriesovo.blog.service.SysUserService; import com.cherriesovo.blog.utils.JWTUtils; import com.cherriesovo.blog.vo.ErrorCode; import com.cherriesovo.blog.vo.LoginUserVo; import com.cherriesovo.blog.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.Map; @Service public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper sysUserMapper; @Override public SysUser findUserByAccount(String account) { LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); //SELECT * FROM sys_user WHERE account = '指定的account' LIMIT 1 queryWrapper.eq(SysUser::getAccount,account); queryWrapper.last("limit 1"); return sysUserMapper.selectOne(queryWrapper); } @Override public void save(SysUser sysUser) { //注意 保存用户的id会自动生成 默认生成的id 是分布式id 采用了雪花算法 //采用框架提供的insert()插入数据 this.sysUserMapper.insert(sysUser); } }
  1. redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser), 1, TimeUnit.DAYS);
    • redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
      • key: Redis 中的键值,这里是以 "TOKEN_" 开头加上用户的 token。
      • value: 要存储的值,这里是用户信息对象 sysUser 转换成的 JSON 字符串,使用了 JSON.toJSONString() 方法。
      • timeout: 过期时间,这里设置为 1。
      • timeUnit: 过期时间单位,这里设置为 TimeUnit.DAYS,表示一天。

这段代码的作用是将用户信息存入 Redis 中,并设置了一天的过期时间。通常这样做是为了实现用户登录状态的持久化,以及实现一定的缓存机制。

package com.cherriesovo.blog.service.impl; import com.alibaba.fastjson.JSON; import com.cherriesovo.blog.dao.pojo.SysUser; import com.cherriesovo.blog.service.LoginService; import com.cherriesovo.blog.service.SysUserService; import com.cherriesovo.blog.utils.JWTUtils; import com.cherriesovo.blog.vo.ErrorCode; import com.cherriesovo.blog.vo.Result; import com.cherriesovo.blog.vo.params.LoginParam; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Map; import java.util.concurrent.TimeUnit; @Service @Transactional public class LoginServiceImpl implements LoginService { @Override public Result register(LoginParam loginParam) { /* * 1、判断参数是否合法 * 2、判断账户是否存在,存在则返回账户已被注册 * 3、如果账户不存在,注册用户 * 4、生成token * 5、存入redis并返回 * 6、注意 加上事务,一旦中间出现任何问题,需要回滚 * */ //判断参数 String account = loginParam.getAccount(); String password = loginParam.getPassword(); String nickname = loginParam.getNickname(); if (StringUtils.isBlank(account) || StringUtils.isBlank(password) || StringUtils.isBlank(nickname) ){ return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } //判断账户是否存在,存在则返回账户已被注册 SysUser sysUser = sysUserService.findUserByAccount(account); if (sysUser != null){ return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),ErrorCode.ACCOUNT_EXIST.getMsg()); } //如果账户不存在,注册用户 sysUser = new SysUser(); sysUser.setNickname(nickname); sysUser.setAccount(account); sysUser.setPassword(DigestUtils.md5Hex(password+slat)); //将密码与盐值拼接后进行 MD5 加密,并设置为用户的密码。 sysUser.setCreateDate(System.currentTimeMillis()); sysUser.setLastLogin(System.currentTimeMillis()); sysUser.setAvatar("/static/img/logo.b3a48c0.png"); sysUser.setAdmin(1); //1 为true sysUser.setDeleted(0); // 0 为false,设置用户是否被删除,这里将其设置为 0,代表未删除。 sysUser.setSalt("");//设置用户的盐值为空字符串,这里是一个占位符,实际应用中根据需要生成盐值 sysUser.setStatus(""); sysUser.setEmail(""); this.sysUserService.save(sysUser); //通过用户id生成token String token = JWTUtils.createToken(sysUser.getId()); //使用 RedisTemplate 将用户信息以 JSON 格式存入 Redis 中,并设置了过期时间为一天 redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); return Result.success(token); } }
//ErrorCode类中添加 ACCOUNT_EXIST(10004,"账号已存在"),

1|41.4 加事务


@Service @Transactional public class LoginServiceImpl implements LoginService {}

当然 一般建议加在 接口上,通用一些。

测试的时候 可以将redis 停掉,那么redis连接异常后,新添加的用户 应该执行回滚操作。

1|51.5 测试


2|02. 登录拦截器


每次访问需要登录的资源的时候,都需要在代码中进行判断,一旦登录的逻辑有所改变,代码都得进行变动,非常不合适。

那么可不可以统一进行登录判断呢?

可以,使用拦截器,进行登录拦截,如果遇到需要登录才能访问的接口,如果未登录,拦截器直接返回,并跳转登录页面。

2|12.1 拦截器实现


  1. public class LoginInterceptor implements HandlerInterceptor:

    LoginInterceptor 类实现了 HandlerInterceptor 接口,拦截请求并在进入 Controller 方法之前进行处理。

  2. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception:

    这是拦截器中的 preHandle 方法,用于在请求到达 Controller 方法之前进行处理。让我解释这个方法的参数和作用:

    • Object handler: 表示被拦截的处理器对象,通常是一个 Controller 方法。在这个方法中,我们可以对请求进行拦截、处理和转发。
    • boolean: 方法的返回类型为布尔值,用于指示是否允许请求继续执行。如果返回 true,则请求将继续执行后续的拦截器或进入 Controller 方法;如果返回 false,则请求将被拦截,不会继续执行后续的拦截器或进入 Controller 方法。
  3. if (!(handler instanceof HandlerMethod)){ //handler可能是RequestResourceHandler springboot程序访问静态资源默认去classpath下的static目录去查询 return true; }

    这段代码用于检查 handler 是否为 HandlerMethod 的实例,如果不是,则认为是请求静态资源,直接放行。

    • HandlerMethod 是 Spring MVC 中用于处理请求的处理器方法的封装类。通常情况下,Controller 中的方法会被包装成 HandlerMethod 实例。
    • RequestResourceHandler 是 Spring Boot 默认用于处理静态资源的处理器。当请求的路径匹配到静态资源时,会由 RequestResourceHandler 处理。

    因此,这段代码的逻辑是:如果 handler 不是 HandlerMethod 的实例,即不是 Controller 中的处理器方法,则认为是请求静态资源,直接放行,不进行拦截处理。

package com.cherriesovo.blog.handler; import com.alibaba.fastjson.JSON; import com.cherriesovo.blog.dao.pojo.SysUser; import com.cherriesovo.blog.service.LoginService; import com.cherriesovo.blog.vo.ErrorCode; import com.cherriesovo.blog.vo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @Slf4j //日志 public class LoginInterceptor implements HandlerInterceptor { @Autowired private LoginService loginService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //该方法在执行controller方法之前执行 /* * 1、需要判断请求的接口路径是否为HandlerMethod(controller方法) * 2、判断token是否为空,如果为空 未登录 * 3、如果token不为空,登录验证loginService checkToken * 4、如果认证成功,放行即可 * */ if (!(handler instanceof HandlerMethod)){ //handler可能是RequestResourceHandler springboot程序访问静态资源默认去classpath下的static目录去查询 return true; } String token = request.getHeader("Authorization"); //打印日志 log.info("=================request start==========================="); String requestURI = request.getRequestURI(); //获取uri,即客户端请求的资源路径 log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}", token); log.info("=================request end==========================="); if (token == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录"); response.setContentType("application/json;charset=utf-8"); //设置 HTTP 响应的内容类型为 JSON 格式 response.getWriter().print(JSON.toJSONString(result)); return false; } SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } //至此是登录状态,放行 return true; } }

2|22.2 使拦截器生效


package com.cherriesovo.blog.config; import com.cherriesovo.blog.handler.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMVCConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addCorsMappings(CorsRegistry registry) { //跨域配置,不可设置为*,不安全, 前后端分离项目,可能域名不一致 //本地测试 端口不一致 也算跨域 //允许http://localhost:8080访问所有端口 registry.addMapping("/**").allowedOrigins("http://localhost:8080"); } @Override public void addInterceptors(InterceptorRegistry registry) { //拦截test接口。后续遇到需要拦截的接口时再进行配置 registry.addInterceptor(loginInterceptor).addPathPatterns("/test"); } }

2|32.3 测试


package com.mszlu.blog.controller; import com.mszlu.blog.vo.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test") public class TestController { @RequestMapping public Result test(){ return Result.success(null); } }

3|03. ThreadLocal保存用户信息


ThreadLocal 是 Java 提供的一个线程局部变量类,它允许我们在每个线程中存储和获取各自的值,而不会被其他线程共享。每个线程都拥有自己独立的 ThreadLocal 实例,可以在其中存储数据,这些数据对其他线程是不可见的。

ThreadLocal 的主要特点包括:

  1. 线程隔离:每个线程都拥有自己独立的 ThreadLocal 对象实例,通过该实例可以存储和获取线程私有的数据。
  2. 数据共享:在同一个线程内部,ThreadLocal 可以在多个方法之间共享数据,而不需要通过参数传递或全局变量。
  3. 线程安全:由于每个线程拥有自己的 ThreadLocal 实例,因此对 ThreadLocal 的操作是线程安全的,不会受到其他线程的干扰。
  4. 高效性:ThreadLocal 使用线程的 ThreadLocalMap 存储数据,底层是一个数组结构,查找速度快,不会引起线程间的竞争。

ThreadLocal 的常见用途包括:

  1. 保存用户上下文信息:可以在 Web 请求处理过程中将用户信息存储在 ThreadLocal 中,便于后续的业务处理方法获取用户信息,而不必每次都去查询数据库。
  2. 避免参数传递:可以在同一个线程的不同方法之间共享数据,避免参数传递的复杂性。
  3. 线程安全的日期格式化:可以使用 ThreadLocal 存储日期格式化对象,确保在多线程环境下的安全使用。

需要注意的是,由于 ThreadLocal 是与线程绑定的,因此在使用完 ThreadLocal 后需要及时清理以避免内存泄漏。通常在线程结束时,或在合适的时机调用 remove() 方法进行清理。

  1. 定义了一个 UserThreadLocal 类,用于在当前线程中存储和获取用户信息。

  2. private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();:

    声明一个 ThreadLocal 对象,用于保存用户信息。ThreadLocal 是一个线程局部变量,可以在每个线程中存储各自的值,而不会被其他线程共享。

  3. public static void put(SysUser sysUser): 将传入的 sysUser 对象存储在当前线程ThreadLocal 中。

  4. public static SysUser get(): 从当前线程ThreadLocal 中获取存储的 SysUser 对象。

  5. public static void remove(): 从当前线程ThreadLocal 中移除存储的 SysUser 对象。

package com.cherriesovo.blog.utils; import com.cherriesovo.blog.dao.pojo.SysUser; public class UserThreadLocal { private UserThreadLocal(){} private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>(); public static void put(SysUser sysUser){ LOCAL.set(sysUser); } public static SysUser get(){ return LOCAL.get(); } public static void remove(){ LOCAL.remove(); } }
package com.cherriesovo.blog.handler; import com.alibaba.fastjson.JSON; import com.cherriesovo.blog.dao.pojo.SysUser; import com.cherriesovo.blog.service.LoginService; import com.cherriesovo.blog.utils.UserThreadLocal; import com.cherriesovo.blog.vo.ErrorCode; import com.cherriesovo.blog.vo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @Slf4j //日志 public class LoginInterceptor implements HandlerInterceptor { @Autowired private LoginService loginService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //该方法在执行controller方法之前执行 /* * 1、需要判断请求的接口路径是否为HandlerMethod(controller方法) * 2、判断token是否为空,如果为空 未登录 * 3、如果token不为空,登录验证loginService checkToken * 4、如果认证成功,放行即可 * */ if (!(handler instanceof HandlerMethod)){ //handler可能是RequestResourceHandler springboot程序访问静态资源默认去classpath下的static目录去查询 return true; } String token = request.getHeader("Authorization"); log.info("=================request start==========================="); String requestURI = request.getRequestURI(); log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}", token); log.info("=================request end==========================="); if (token == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } //至此是登录状态,放行 //我希望在controller中直接获取用户信息,怎么获取 UserThreadLocal.put(sysUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //如果不删除,Threadlocal中用完的信息会有内存泄漏的风险 UserThreadLocal.remove(); } }
package com.cherriesovo.blog.controller; import com.cherriesovo.blog.dao.pojo.SysUser; import com.cherriesovo.blog.utils.UserThreadLocal; import com.cherriesovo.blog.vo.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test") public class TestController { @RequestMapping public Result test(){ // SysUser SysUser sysUser = UserThreadLocal.get(); System.out.println(sysUser); return Result.success(null); } }

__EOF__

本文作者CherriesOvO
本文链接https://www.cnblogs.com/zyj3955/p/18127290.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   CherriesOvO  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2021-04-10 关于ECharts图表反复修改都无法显示的解决方案
点击右上角即可分享
微信分享提示