Skip to content

线程的使用背景 ​

在我们登录之后,进行某些接口操作的时候,我们需要记录一下用户的id,该怎么办呢?

首先在每一次前端的请求中会携带token,而在之前token下发的时候,我们将用户的id和username存入到了token的载荷中。但是token到达后台之后,我们如何从Service实现类中获取呢?

这时候我们需要用到一个对象,ThreadLocal(线程局部变量),他可以做到在一个线程中共享数据,如下图所示:

ThreadLocal的常见方法:

  • public void set(T value)`` 设置当前线程的线程局部变量的值
  • public T get()`` 返回当前线程所对应的线程局部变量的值
  • public void remove()`` 移除当前线程的线程局部变量

获取当前登录用户 ​

具体操作步骤如下:

  1. 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();
    }
}
  1. 在登录拦截器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方法执行了.....");
    }
}
  1. 在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了。