结合 TransmittableThreadLocal 进行接口【多表不同用户登录】 的全链追踪

9 篇文章 0 订阅

业务场景:
原生是一个考试系统,在后续需求中,需要添加一个中介用户进行登录,进行中介的对应的业务操作,但是原来版本写的太死了,在c端只能一种用户登录,然后我进了升级
一、登录逻辑差不多是一致的,创建token/sessionKey相关值,然后将对应的数据存放在redis中
二、在springboot的WebMvcConfigurer配置添加拦截器,做好对应c端请求的拦截过滤以及校验,这里拦截器c端的统一以/api开头
三、在拦截器中,根据/api/以后的前缀执行不同的处理,(只要实现ApiSessionKeyFilter中),将不同的登录用户信息从redis中取出,放入到TransmittableThreadLocal线程本地变量中
四、业务代码中直接获取出当前用户信息即可

在这里插入图片描述
ApiSessionInterceptor.java

import cn.hutool.extra.spring.SpringUtil;
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;
import java.util.Map;

/**
 * ThirdSession拦截器
 * 校验每个请求的ThirdSession
 *
 * @author Zheng
 */
@Component
public class ApiSessionInterceptor implements HandlerInterceptor {

    /**
     * 判断接口是否添加了需要携带session的注解 @MiniLogin
     * 以及请求是否携带sessionKey,并且判断session是否过期
     *
     * @param request  Request
     * @param response Response
     * @param handler  handler
     * @return 是否通行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断静态资源放行
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            //判断访问的control是否添加ApiLogin注解
            if (method.hasMethodAnnotation(ApiLoginOpen.class)) {
                // 添加open接口得放开访问
                return Boolean.TRUE;
            }

            String requestURI = request.getRequestURI();

            Map<String, ApiSessionKeyFilter> apiSessionKeyFilterBeanByNameMap
                    = SpringUtil.getBeansOfType(ApiSessionKeyFilter.class);

            for (Map.Entry<String, ApiSessionKeyFilter> apiSessionKeyFilterEntry : apiSessionKeyFilterBeanByNameMap.entrySet()) {
                ApiSessionKeyFilter filter = apiSessionKeyFilterEntry.getValue();
                if (requestURI.startsWith(filter.getUrlPrefix())) {
                    return filter.filter(request, response);
                }
            }
            throw new Exception("无权限");
        } else {
            return Boolean.TRUE;
        }
    }
}

ResourcesConfig.class

 */
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiSessionInterceptor).addPathPatterns("/api/**")
                .excludePathPatterns("/api/captcha");
    }
}

ApiSessionHolder.java

/**
 * 全链路追踪
 * thirdSession工具类
 *
 * @author Zheng
 */
@UtilityClass
public class ApiSessionHolder {
    private final ThreadLocal<Object> THREAD_LOCAL_THIRD_SESSION = new TransmittableThreadLocal<>();

    /**
     * TTL 设置 当前登录学员信息
     *
     * @param obj 登录用户信息
     */
    public void setSessionObj(Object obj) {
        THREAD_LOCAL_THIRD_SESSION.set(obj);
    }

    /**
     * 获取TTL中的LoginStudent
     *
     * @return 登录学员信息
     */
    public Object getSessionObj() {
        return THREAD_LOCAL_THIRD_SESSION.get();
    }

    /**
     * 获取 SessionObj 的id信息
     *
     * @return id数
     */
    public Long getSessionObjId() {
        Object sessionObj = getSessionObj();
        if (sessionObj == null) {
            return null;
        }
        Object id = ReflectUtil.getFieldValue(sessionObj, "id");
        if (id == null) {
            return null;
        }
        if (id instanceof Long) {
            return (Long) id;
        }
        return null;
    }

    /**
     * 清除
     */
    public void clear() {
        THREAD_LOCAL_THIRD_SESSION.remove();
    }
}

ApiSessionKeyFilter.java

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * ApiSessionFilter
 *
 * @author SERVER
 * @since 2024/3/29 14:15
 */
public interface ApiSessionKeyFilter {

    /**
     * 获取apiUrl 模块地址信息
     *
     * @return 模块名称
     */
    String getModelUrl();

    /**
     * 获取地址信息
     *
     * @return re
     */
    default String getUrlPrefix() {
        return "/api" + (StrUtil.isBlank(this.getModelUrl()) ? "" : "/" + this.getModelUrl());
    }

    /**
     * 过滤sessionKey信息过滤
     *
     * @param request  请求信息
     * @param response 返回信息
     * @return true/false
     */
    boolean filter(HttpServletRequest request, HttpServletResponse response) throws Exception ;

    /**
     * 向页面返回的
     *
     * @param response
     * @param r
     * @throws IOException
     */
    default void writerPrint(HttpServletResponse response, AjaxResult r) throws IOException {
        //返回超时错误码
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        writer.print(JSONUtil.parseObj(r));
        if (writer != null) {
            writer.close();
        }
    }
}

常量以及key相关值生成工具
ApiSessionUtils.class

import cn.hutool.core.util.StrUtil;
import org.apache.poi.util.StringUtil;

/**
 * ApiSessionUtils
 *
 * @author zheng
 * @since 2023/10/25 20:30
 */
public class ApiSessionUtils {

    /**
     * 创建缓存前缀用于批量删除
     *
     * @param clazz              类型便阿门
     * @param distinguishedContents 区别信息内容
     * @return 缓存键前缀
     */
    public static String createCacheKeyPrefix(Class<?> clazz, String... distinguishedContents) {
        return ApiSessionConst.SESSION_BEGIN +
                StrUtil.COLON +
                clazz.getSimpleName() +
                StrUtil.COLON +
                StringUtil.join(distinguishedContents, StrUtil.COLON);
    }

    /**
     * 获取缓存的key信息
     *
     * @param clazz   数据类型
     * @param sessionKey 会话中带的key信息
     * @return 缓存的key信息
     */
    public static String createCacheKey(Class<?> clazz, String sessionKey) {
        return ApiSessionConst.SESSION_BEGIN +
                StrUtil.COLON +
                clazz.getSimpleName() +
                StrUtil.COLON +
                sessionKey;
    }


    /**
     * 创建sessionKey
     *
     * @param distinguishedContents 区分内容信息
     * @return SessionKey
     */
    public static String createSessionKey(String... distinguishedContents) {
        return StringUtil.join(distinguishedContents, StrUtil.COLON);
    }

}

ApiSessionConst.java

/**
 * @author Zheng
 */
public class ApiSessionConst {
    /**
     * 小程序登录。存在redis中的学员信息键名拼接前缀
     */
    public final static String SESSION_BEGIN = "api:session";

    /**
     * 小程序登录。存在redis中的学员信息过期时间(单位:小时)
     */
    public final static Integer SESSION_TIME_OUT = 24;

    /**
     * 小程序登录。存在 header 中的参数键
     */
    public final static String SESSION_KEY = "session-key";
}

注意

TransmittableThreadLocal 是阿里提供的工具类
它是ThreadLocal的一个增强版,可以在线程池等多线程环境下使用,解决了ThreadLocal在多线程环境下的一些问题。
在多线程环境下,ThreadLocal可以避免线程安全问题,但是在使用线程池等多线程环境时,ThreadLocal可能会出现一些问题。例如,当使用线程池时,线程池中的线程可能会被多个任务共享,如果使用ThreadLocal存储数据,可能会导致数据被错误地共享。>TransmittableThreadLocal解决了这个问题,它可以在多线程环境下正确地传递数据。具体来说,当一个线程从线程池中获取到一个线程时,TransmittableThreadLocal会自动将当前线程的ThreadLocal副本传递给新的线程,从而保证数据的正确性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值