线程的使用背景
在我们登录之后,进行某些接口操作的时候,我们需要记录一下用户的id,该怎么办呢?
首先在每一次前端的请求中会携带token,而在之前token下发的时候,我们将用户的id和username存入到了token的载荷中。但是token到达后台之后,我们如何从Service实现类中获取呢?
这时候我们需要用到一个对象,ThreadLocal(线程局部变量),他可以做到在一个线程中共享数据,如下图所示:


ThreadLocal的常见方法:
public void set(T value)``设置当前线程的线程局部变量的值public T get()``返回当前线程所对应的线程局部变量的值public void remove()``移除当前线程的线程局部变量
获取当前登录用户
具体操作步骤如下:
qk-common模块的com.cghbh.util包中创建一个工具类UserHolder,在工具类中操作ThreadLocal
java
package com.cghbh.util;
public class UserHolder {
private static final ThreadLocal<Integer> CURRENT_USER = new ThreadLocal<>();
/**
* 存储用户信息到当前线程
*
* @param userId 用户ID
*/
public static void setUserId(Integer userId) {
CURRENT_USER.set(userId);
}
/**
* 获取线程中的用户信息
*
* @return 用户ID
*/
public static Integer getUserId() {
return CURRENT_USER.get();
}
/**
* 清空线程中的用户信息
*/
public static void removeUserId() {
CURRENT_USER.remove();
}
}- 在登录拦截器
LoginInterceptor中,解析完当前登录用户的id之后,将其存入到ThreadLocal中,在用完之后需要将其删除。
java
package com.cghbh.interceptor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.cghbh.util.JwtUtil;
import com.cghbh.util.UserHolder;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
/**
* 前置拦截 : 在控制层接口方法(目标方法)执行之前执行
*
* @param request 本次的HTTP请求对象
* @param response 本次的HTTP响应对象
* @param handler 拦截的目标方法对象
* @return true: 放行到目标资源 false : 不放行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取请求中的token
String token = request.getHeader("token");
// 2. 判断请求头中的token令牌是否存在,如果不存在返回 401 状态码
if (StrUtil.isBlank(token)) {
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
return false;
}
// 3. 校验token是否有效 , 校验失败返回 401 状态码
try {
Claims claims = JwtUtil.parseToken(token);
log.info("解析出来的token载荷信息:{}", claims);
Integer userId = claims.get("id", Integer.class);
UserHolder.setUserId(userId);
} catch (Exception e) {
log.error("令牌校验失败,原始令牌:{},失败原因:{}", token, e.getMessage());
response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
return false;
}
// 4. 放行,访问目标资源
return true;
}
/**
* 后置拦截 : 在目标方法执行之后执行
*
* @param request 本次的HTTP请求对象
* @param response 本次的HTTP响应对象
* @param handler 拦截的目标方法对象
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor中的postHandle方法执行了.....");
}
/**
* 完成时拦截 : 在服务器响应完毕之后执行
*
* @param request 本次的HTTP请求对象
* @param response 本次的HTTP响应对象
* @param handler 拦截的目标方法对象
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
UserHolder.removeUserId();
log.info("LoginInterceptor中的afterCompletion方法执行了.....");
}
}- 在Service程序中,从ThreadLocal中获取当前登录用户的id
java
package com.cghbh.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cghbh.common.PageResult;
import com.cghbh.dto.CluePageDto;
import com.cghbh.dto.ClueTrackDto;
import com.cghbh.dto.FalseMarkClueDto;
import com.cghbh.entity.Business;
import com.cghbh.entity.Clue;
import com.cghbh.entity.ClueTrackRecord;
import com.cghbh.entity.User;
import com.cghbh.exception.BusinessException;
import com.cghbh.mapper.BusinessMapper;
import com.cghbh.mapper.ClueMapper;
import com.cghbh.mapper.ClueTrackRecordMapper;
import com.cghbh.mapper.UserMapper;
import com.cghbh.service.ClueService;
import com.cghbh.util.UserHolder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
@RequiredArgsConstructor
@Service
public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements ClueService {
private final ClueMapper clueMapper;
private final UserMapper userMapper;
private final ClueTrackRecordMapper clueTrackRecordMapper;
private final BusinessMapper businessMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean followClue(ClueTrackDto clueTrackDto) {
// 先判断线索是否存在
Clue clue = clueMapper.selectById(clueTrackDto.getId());
if (clue == null) {
throw new BusinessException("当前线索不存在,无法跟进");
}
BeanUtil.copyProperties(clueTrackDto, clue, "id", "phone", "channel");
System.out.println("数据测试:" + clue);
// 修改线索跟进状态为跟进中
clue.setStatus(3);
// 更新数据到数据库
int row1 = clueMapper.updateById(clue);
// 2. 新增一条线索跟进记录
ClueTrackRecord trackRecord = new ClueTrackRecord();
trackRecord.setClueId(clue.getId());
// 从当前线程中获取当前登录用户id
Integer userId = UserHolder.getUserId();
// 设置线索跟进人id(当前登录系统的用户id)
trackRecord.setUserId(userId);
trackRecord.setSubject(clue.getSubject());
trackRecord.setLevel(clue.getLevel());
trackRecord.setRecord(clueTrackDto.getRecord());
trackRecord.setNextTime(clue.getNextTime());
trackRecord.setType(1);
int row2 = clueTrackRecordMapper.insert(trackRecord);
return row1 == 1 && row2 == 1;
}
}经过上面的代码改造之后,我们就可以正常获取到不同登录的id了。