**登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**

用户登录——>数据加密数据库比对——>生成jwt令牌封装返回——>拦截器统一拦截进行jwt校验-并将数据放入本地线程中。

0、 ThreadLocal

介绍:

ThreadLocal 并不是一个Thread,而是Thread的线程局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

常用方法:

  • public void set(T value) 设置当前线程的线程局部变量的值

  • public T get() 返回当前线程所对应的线程局部变量的值

  • public void remove() 移除当前线程的线程局部变量

  • 常量类方便灵活修改资源或数据。

  • 异常抛出

  • 拦截器时将登录数据存储到线程中(前端每一次调用接口都是一次单独的线程操作)

1、业务逻辑代码开发
/*** 员工管理*/
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {
​@Autowiredprivate EmployeeService employeeService;@Autowiredprivate JwtProperties jwtProperties;
​/*** 登录** @param employeeLoginDTO* @return*/@PostMapping("/login")@ApiOperation(value = "员工登录")public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {log.info("员工登录:{}", employeeLoginDTO);
​Employee employee = employeeService.login(employeeLoginDTO);
​//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();
​claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
​String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);
​//构建封装还回前端对象EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder().id(employee.getId()).userName(employee.getUsername()).name(employee.getName()).token(token).build();
​return Result.success(employeeLoginVO);}

public interface EmployeeService {
​/*** 员工登录* @param employeeLoginDTO* @return*/Employee login(EmployeeLoginDTO employeeLoginDTO);

@Service
public class EmployeeServiceImpl implements EmployeeService {
​@Autowiredprivate EmployeeMapper employeeMapper;
​/*** 员工登录** @param employeeLoginDTO* @return*/public Employee login(EmployeeLoginDTO employeeLoginDTO) {String username = employeeLoginDTO.getUsername();String password = employeeLoginDTO.getPassword();
​//1、根据用户名查询数据库中的数据Employee employee = employeeMapper.getByUsername(username);
​//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)if (employee == null) {//账号不存在throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);}
​//密码比对//对前端传过来的明文密码进行md5加密处理password = DigestUtils.md5DigestAsHex(password.getBytes());if (!password.equals(employee.getPassword())) {//密码错误throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);}
​if (employee.getStatus() == StatusConstant.DISABLE) {//账号被锁定throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);}
​//3、返回实体对象return employee;}

@Mapper
public interface EmployeeMapper {
​/*** 根据用户名查询员工* @param username* @return*/@Select("select * from employee where username = #{username}")Employee getByUsername(String username);

2、工具类

JWT工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
​
public class JwtUtil {/*** 生成jwt* 使用Hs256算法, 私匙使用固定秘钥** @param secretKey jwt秘钥* @param ttlMillis jwt过期时间(毫秒)* @param claims    设置的信息* @return*/public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
​// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);
​// 设置jwt的bodyJwtBuilder builder = Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);
​return builder.compact();}
​/*** Token解密** @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个* @param token     加密后的token* @return*/public static Claims parseJWT(String secretKey, String token) {// 得到DefaultJwtParserClaims claims = Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}
​
}
​

3、ThreadLocal线程操作工具
public class BaseContext {
​public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
​public static void setCurrentId(Long id) {threadLocal.set(id);}
​public static Long getCurrentId() {return threadLocal.get();}
​public static void removeCurrentId() {threadLocal.remove();}
​
}

全局异常处理器

捕获sql异常和业务异常

/*** 全局异常处理器,处理项目中抛出的业务异常*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
​/*** 捕获业务异常* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(BaseException ex){log.error("异常信息:{}", ex.getMessage());return Result.error(ex.getMessage());}
​/*** 处理SQL异常* (唯一值重复)* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){//Duplicate entry 'zhangsan' for key 'employee.idx_username'String message = ex.getMessage();if(message.contains("Duplicate entry")){String[] split = message.split(" ");String username = split[2];String msg = username + MessageConstant.ALREADY_EXISTS;return Result.error(msg);}else{return Result.error(MessageConstant.UNKNOWN_ERROR);}}
}

常量类

用于区分员工与用户。

public class JwtClaimsConstant {
​public static final String EMP_ID = "empId";//员工(可登录pc)public static final String USER_ID = "userId";//用户//public static final String PHONE = "phone";//public static final String USERNAME = "username";//public static final String NAME = "name";
​
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {
​/*** 管理端员工生成jwt令牌相关配置*/private String adminSecretKey;private long adminTtl;private String adminTokenName;
​/*** 用户端微信用户生成jwt令牌相关配置*/private String userSecretKey;private long userTtl;private String userTokenName;
​
}

异常类
/*** 业务异常*/
public class BaseException extends RuntimeException {
​public BaseException() {}
​public BaseException(String msg) {super(msg);}
​
}

4、拦截器

员工登录拦截

import io.jsonwebtoken.Claims;
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;
​
/*** jwt令牌校验的拦截器*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
​@Autowiredprivate JwtProperties jwtProperties;
​/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}
​//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());
​//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);
​//将登录数据存储到线程中BaseContext.setCurrentId(empId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}

用户登录拦截

import io.jsonwebtoken.Claims;
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;
​
/*** jwt令牌校验的拦截器*/
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
​@Autowiredprivate JwtProperties jwtProperties;
​/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}
​//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getUserTokenName());
​//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());log.info("当前用户的id:", userId);BaseContext.setCurrentId(userId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}
​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/239281.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算的挑战和机遇

目录 前言 一、边缘计算 &#xff08;一&#xff09;为什么需要边缘计算 &#xff08;二&#xff09;什么是边缘计算 &#xff08;三&#xff09;边缘计算体系架构 &#xff08;四&#xff09;边缘计算的好处 二、案例分析 &#xff08;一&#xff09;云卸载 &#xf…

三个视频提取软件一键快速提取

在当今的社交媒体时代&#xff0c;短视频分享已经成为一种流行的表达方式。然而&#xff0c;有时我们遇到视频过长或水印影响观看体验的情况。这时&#xff0c;一款快速、高效的短视频提取软件就显得尤为重要。今天&#xff0c;我们就为大家推荐三款优秀的短视频提取软件。 水…

【MATLAB】Linux版本 高分辨率屏 调整显示缩放

0 引言 安装了linux版本的MATLAB R2023b之后&#xff0c;发现工具栏字体很小不方便使用&#xff0c;所以上网找到了MATLAB论坛上某位大佬的教程&#xff1a;参考链接&#xff0c;放在这里供各位参考 。 1 环境 这里注明我的matlab安装环境仅供参考&#xff0c;未在其他环境下…

高级RAG技术、以及算法实现

知识库地址&#xff1a;Advanced RAG techniques 检索增强生成&#xff08;Retrieval Augmented Generation, RAG&#xff09;为大语言模型&#xff08;Large Language Model, LLM&#xff09;提供了一种机制&#xff0c;通过从数据源检索到的信息为其生成的答案提供依据。简而…

【VTKExamples::PolyData】第四期 DijkstraGraphGeodesicPath

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例DijkstraGraphGeodesicPath,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. DijkstraGraphGeodesicPath /…

【期末不挂科-C++考前速过系列P1】大二C++第1次过程考核(3道简述题&7道代码题)【解析,注释】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C复习系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

把屏幕变成毫米测量工具

主要功能&#xff1a; 根据屏幕的像素个数和物理长度对应关系&#xff0c;计算设置的实际长度&#xff0c;把屏幕当成直尺用。功能模仿FastStone里面的屏幕标尺工具 运行演示&#xff1a; 其他说明&#xff1a; 1.软件打开时会自动获取屏幕像素个数和物理长度&#xff0c;也可…

基于爬虫和Kettle的豆瓣电影的采集与预处理

一&#xff1a;爬虫 1、爬取的目标 将豆瓣电影网上的电影的基本信息&#xff0c;比如&#xff1a;电影名称、导演、电影类型、国家、上映年份、评分、评论人数爬取出来&#xff0c;并将爬取的结果放入csv文件中&#xff0c;方便存储。 2、网站结构 图1豆瓣网网站结构详…

Parallel patterns: convolution —— An introduction to stencil computation

在接下来的几章中&#xff0c;我们将讨论一组重要的并行计算模式。这些模式是许多并行应用中出现的广泛并行算法的基础。我们将从卷积开始&#xff0c;这是一种流行的阵列操作&#xff0c;以各种形式用于信号处理、数字记录、图像处理、视频处理和计算机视觉。在这些应用领域&a…

尚硅谷离线数仓之采集平台

1. 用户行为日志 数据流向流程图如下&#xff0c;其中红框表示用户行为日志数据的流向图。 1.1 行为日志内容 行为日志主要包括以下几个内容 页面浏览记录动作记录曝光记录启动记录错误记录 页面浏览记录 动作记录 曝光记录 启动记录 1.2 用户行为日志格式 页面日志启动…

Radzen Blazor Studio 脚手架框架解读

背景 组织管理管理准备使用Blazor这个工具实现&#xff0c;因为其有对应的 scaffold 脚手架&#xff0c;先构建数据库&#xff0c;然后通过向导&#xff0c;生成CRUD以及对应的接口&#xff0c;那么有必要看一下&#xff0c;其内部的代码结构是什么样的。 结构 接口层 有两类…

STM32-04-STM32时钟树

STM32时钟树 什么是时钟&#xff1f; 时钟是具有周期性的脉冲信号&#xff0c;最常用的是占空比50%的方波。&#xff08;时钟是单片机的脉搏&#xff0c;搞懂时钟走向及关系&#xff0c;对单片机使用至关重要&#xff09;。 时钟树 时钟源 2个外部时钟源 高速外部振荡器(HSE…

vue中el-radio无法默认选中

页面上不生效&#xff0c;默认什么都不选中 <el-radio-group v-model"queryParams.videoUrlType"><el-radio :label"1">本地上传</el-radio><el-radio :label"2">外部链接</el-radio> </el-radio-group>da…

vue el-table最后一页所有数据批量删除或者单个删除,自动回到上一页,包括单条删除

批量删除单条删除//判断数据是否可以满一页isFillList () {const totalPage Math.ceil((this.docDateTotal - this.changeDocData.length) / this.docPageSize) // 总页数this.docPage this.docPage > totalPage ? totalPage : this.docPagethis.docPage this.docPage &…

高级 Python 面试问题与解答

文章目录 专栏导读1.什么是PIP&#xff1f;2.什么是 zip 函数&#xff1f;3.Python 中的 __init __ () 是什么&#xff1f;4.Python 中的访问说明符是什么&#xff1f;5.Python 中的单元测试是什么&#xff1f;6.Python全局解释器锁&#xff08;GIL&#xff09;&#xff1f;7.P…

docker-consul部署

目录 一、环境 二、consul服务器 三、registrator服务器 四、consul-template 一、环境 consul服务器 192.168.246.10 运行consul服务、nginx服务、consul-template守护进程 registrator服务器 192.168.246.11 运行registrator容器、运行ngi…

看完这篇你就知道了!人气爆表的6款Sketch插件大揭秘!

Sketch作为一种在线设计工具&#xff0c;一直是许多设计师的最爱。它不仅能快速建立原型&#xff0c;还能提供丰富的插件&#xff0c;以满足不同的需求。 今天&#xff0c;小抄将与大家分享6款流行的Sketch插件供参考。这些插件都是小抄精心挑选的&#xff0c;支持Windows、Ma…

flink1.14.5使用CDH6.3.2的yarn提交作业

使用CDH6.3.2安装了hadoop集群&#xff0c;但是CDH不支持flink的安装&#xff0c;网上有CDH集成flink的文章&#xff0c;大都比较麻烦&#xff1b;但其实我们只需要把flink的作业提交到yarn集群即可&#xff0c;接下来以CDH yarn为基础&#xff0c;flink on yarn模式的配置步骤…

React18-树形菜单-递归

文章目录 案例分析技巧通信展示效果实现代码技巧点技巧点 Refer to 案例分析 https://github.com/dL-hx/manager-fe/commit/85faf3b1ae9a925513583feb02b9a1c87fb462f7 从接口获取城市数据,渲染出一个树形菜单 要求: 可以展开和收起 技巧 学会递归渲染出一个树形菜单, 并点击后…

加密经济学:Web3时代的新经济模型

随着Web3技术的迅猛发展&#xff0c;我们正迈入一个全新的数字经济时代。加密经济学作为这一时代的核心&#xff0c;不仅在数字货币领域崭露头角&#xff0c;更是重新定义了传统经济模型&#xff0c;为我们开启了一个充满创新和机遇的新纪元。 1. 去中心化的经济体系 Web3时代…