用户上下文打通+本地缓存Guava

文章目录

  • 🌞 Sun Frame:SpringBoot 的轻量级开发框架(个人开源项目推荐)
  • 🌟 亮点功能
  • 📦 spring cloud模块概览
  • 常用工具
  • 🔗 更多信息
  • 1.设计
  • 1.链路流程
  • 2.详细设计
  • 2.网关过滤器获取唯一标识放到Header
  • 1.LoginFilter.java
  • 2.SubjectCategoryController.java 测试
  • 3.进行测试
  • 1.用户登录
  • 2.携带token访问后端接口
  • 3.基于ThreadLocal实现上下文传递
  • 1.目录结构
  • 2.ThreadLocal的工具类 LoginContextHolder.java
  • 3.拦截器将网关传递的信息放到 LoginInterceptor.java
  • 4.配置类将自定义拦截器注册 GlobalConfig.java
  • 5.LoginUtil.java
  • 6.测试 SubjectCategoryController.java
  • 4.OpenFeign的使用
  • 1.目录结构
  • 1.sun-club-auth
  • 2.sun-club-auth-api引入依赖
  • 3.UserFeignService.java 将controller层的接口暴露
  • 4.AuthUserDTO.java 从controller剪切到api层
  • 5.Result.java和ResultCodeEnum.java从common包中剪切过来
  • 6.sun-club-auth-application-controller 引入api层的依赖,DTO和Result都使用api层的
  • 7.sun-club-subject微服务调用sun-club-auth微服务
  • 1.sun-club-infra引入sun-club-auth微服务的api包
  • 2.在sun-club-infra模块的UserRpc.java进行rpc调用
  • 3.创建接受信息的entity,UserInfo.java
  • 4.在启动类加注解,启动Feign
  • 5.测试TestFeignController.java
  • 6.测试
  • 5.OpenFeign拦截器打通微服务上下文(由调用方来写)
  • 1.FeignRequestInterceptor.java 将唯一标识放到Header中传递到其他微服务
  • 2.FeignConfiguration.java 将拦截器注入容器
  • 3.在被调用方使用过滤器将用户唯一标识存到ThreadLocal(跟前面写过的一样)
  • 1.目录结构
  • 2.ThreadLocal的工具类LoginContextHolder.java
  • 3.自定义拦截器获取Header中的唯一标识,并放到ThreadLocal中 LoginInterceptor.java
  • 4.将拦截器注入容器 GlobalConfig.java
  • 5.UserController.java 测试获取调用方传来的唯一标识
  • 4.用户上下文整体流程测试
  • 1.网关过滤器将唯一标识从redis中取出,放到Header,传到微服务模块
  • 2.Feign的调用微服务的登录拦截器,将唯一标识放到ThreadLocal中
  • 3.在调用Feign之前,进入Feign拦截器,将唯一标识放到Header中,传递给被调用方
  • 4.被调用方的登录拦截器,读取Header中的唯一标识,放到ThreadLocal中
  • 5.被调用方从ThreadLocal中可以获取到唯一标识
  • 6.本地缓存guava使用
  • 1.SubjectCategoryDomainServiceImpl.java
  • 2.测试
  • 3.本地缓存工具类
  • 1.ListLocalCacheUtil.java
  • 2.使用方式
  • 1.依赖注入
  • 2.使用
  • 3.测试
  • 🌞 Sun Frame:SpringBoot 的轻量级开发框架(个人开源项目推荐)

    Sun Frame Banner

    轻松高效的现代化开发体验

    Sun Frame 是我个人开源的一款基于 SpringBoot 的轻量级框架,专为中小型企业设计。它提供了一种快速、简单且易于扩展的开发方式。

    我们的开发文档记录了整个项目从0到1的任何细节,实属不易,请给我们一个Star!🌟
    您的支持是我们持续改进的动力。

    🌟 亮点功能

  • 组件化开发:灵活选择,简化流程。
  • 高性能:通过异步日志和 Redis 缓存提升性能。
  • 易扩展:支持多种数据库和消息队列。
  • 📦 spring cloud模块概览

  • Nacos 服务:高效的服务注册与发现。
  • Feign 远程调用:简化服务间通信。
  • 强大网关:路由与限流。
  • 常用工具

  • 日志管理:异步处理与链路追踪。
  • Redis 集成:支持分布式锁与缓存。
  • Swagger 文档:便捷的 API 入口。
  • 测试支持:SpringBoot-Test 集成。
  • EasyCode:自定义EasyCode模板引擎,一键生成CRUD。

  • 🔗 更多信息

  • 开源地址:Gitee Sun Frame
  • 详细文档:语雀文档

  • 1.设计

    1.链路流程

    img

    2.详细设计

    img

    2.网关过滤器获取唯一标识放到Header

    1.LoginFilter.java
    package com.sunxiansheng.club.gateway.filter;
    
    import cn.dev33.satoken.stp.SaTokenInfo;
    import cn.dev33.satoken.stp.StpUtil;
    import com.alibaba.cloud.commons.lang.StringUtils;
    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    /**
     * Description: 网关过滤器,在用户登录之后再次请求网关就会携带token,可以通过网关过滤器通过redis获取用户的唯一标识然后放到Header中传递到后端
     * 过滤器的优先级是比配置文件中配置的路由要低
     * @Author sun
     * @Create 2024/6/15 15:28
     * @Version 1.0
     */
    @Component
    @Slf4j
    public class LoginFilter implements GlobalFilter {
    
        @Override
        @SneakyThrows
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 获取请求
            ServerHttpRequest request = exchange.getRequest();
            // 获取一个很全的东西
            ServerHttpRequest.Builder mutate = request.mutate();
            // 获取url
            String url = request.getURI().getPath();
            log.info("网关过滤器:用户请求的url:" + url);
            // 如果发现是用户登录的请求,直接放行,注意先走的网关配置,所以前缀会被删除
            if (url.equals("/user/doLogin")) {
                return chain.filter(exchange);
            }
            // 通过sa-token框架获取用户的tokenInfo,可以通过这个对象来获取用户信息
            SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
            // 根据token获取用户的唯一标识
            String loginId = (String) tokenInfo.getLoginId();
            if (StringUtils.isBlank(loginId)) {
                log.info("网关过滤器获取用户唯一标识失败!");
            }
            // 如果到这了,就说明已经获取到了用户的唯一标识,将其放到Header中
            mutate.header("loginId", loginId);
            // 将携带了用户唯一标识的request发送到其他微服务模块
            return chain.filter(exchange.mutate().request(mutate.build()).build());
        }
    }
    
    2.SubjectCategoryController.java 测试

    image-20240615155811919

    3.进行测试
    1.用户登录
    2.携带token访问后端接口

    image-20240615161700008

    3.基于ThreadLocal实现上下文传递

    1.目录结构

    image-20240615172226319

    2.ThreadLocal的工具类 LoginContextHolder.java
    package com.sunxiansheng.subject.application.context;
    
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * Description: 上下文对象(ThreadLocal)
     * @Author sun
     * @Create 2024/6/15 16:27
     * @Version 1.0
     */
    public class LoginContextHolder {
    
        // 这个ThreadLocal持有一个Map
        private static final InheritableThreadLocal<Map<String, Object>> THREAD_LOCAL
                = new InheritableThreadLocal<>();
    
        /**
         * 为ThreadLocal持有的Map设值
         * @param key
         * @param val
         */
        public static void set(String key, Object val) {
            Map<String, Object> map = getThreadLocalMap();
            map.put(key, val);
        }
    
        /**
         * 从ThreadLocal持有的Map取值
         * @param key
         * @return
         */
        public static Object get(String key) {
            Map<String, Object> map = THREAD_LOCAL.get();
            return map.get(key);
        }
    
        /**
         * 清除ThreadLocal
         */
        public static void remove() {
            THREAD_LOCAL.remove();
        }
    
        /**
         * 初始化一个ThreadLocal持有的Map,要保证这个Map是单例的
         * @return
         */
        public static Map<String, Object> getThreadLocalMap() {
            // 获取到ThreadLocal的Map
            Map<String, Object> map = THREAD_LOCAL.get();
            // 如果是空的再创建一个Map,然后放进去
            if (Objects.isNull(map)) {
                map = new ConcurrentHashMap<>();
                THREAD_LOCAL.set(map);
            }
            // 放到ThreadLocal中
            return map;
        }
    
        // 以下为获取用户信息的方法
        public static String getLoginId() {
            return (String) getThreadLocalMap().get("loginId");
        }
    
    
    }
    
    3.拦截器将网关传递的信息放到 LoginInterceptor.java
    package com.sunxiansheng.subject.application.interceptor;
    
    import com.sunxiansheng.subject.application.context.LoginContextHolder;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * Description: 处理用户上下文的拦截器
     * @Author sun
     * @Create 2024/6/15 16:20
     * @Version 1.0
     */
    public class LoginInterceptor implements HandlerInterceptor {
    
        /**
         * 当请求到这里了,就说明,网关已经将用户的loginId放到了Header里了
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String loginId = request.getHeader("loginId");
            // 将loginId放到ThreadLocal里面
            LoginContextHolder.set("loginId", loginId);
            return true;
        }
    
    
        /**
         * 在操作结束后清除ThreadLocal,因为如果线程复用并且没清除,这个ThreadLocal还会存在,造成数据污染
         * @param request
         * @param response
         * @param handler
         * @param ex
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            LoginContextHolder.remove();
        }
    }
    
    4.配置类将自定义拦截器注册 GlobalConfig.java

    image-20240615172339191

    5.LoginUtil.java
    package com.sunxiansheng.subject.application.util;
    
    import com.sunxiansheng.subject.application.context.LoginContextHolder;
    
    /**
     * Description: 用户登录的util
     * @Author sun
     * @Create 2024/6/15 17:12
     * @Version 1.0
     */
    public class LoginUtil {
    
        /*
        获取loginId
         */
        public static String getLoginId() {
            return LoginContextHolder.getLoginId();
        }
    }
    
    6.测试 SubjectCategoryController.java

    image-20240615172455473

    image-20240615172514330

    4.OpenFeign的使用

    1.目录结构
    1.sun-club-auth

    image-20240616134039744

    2.sun-club-auth-api引入依赖
    <dependencies>
        <!-- OpenFeign依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.0.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>3.0.6</version>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>
    
    3.UserFeignService.java 将controller层的接口暴露
    package com.sunxiansheng.auth.api;
    
    import com.sunxiansheng.auth.entity.AuthUserDTO;
    import com.sunxiansheng.auth.entity.Result;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * Description: 用户服务feign
     * @Author sun
     * @Create 2024/6/16 13:20
     * @Version 1.0
     */
    @FeignClient("sub-club-auth") // 服务名
    public interface UserFeignService {
    
        @RequestMapping("/user/getUserInfo")
        public Result<AuthUserDTO> getUserInfo(@RequestBody AuthUserDTO authUserDTO);
    }
    
    4.AuthUserDTO.java 从controller剪切到api层
    5.Result.java和ResultCodeEnum.java从common包中剪切过来
    6.sun-club-auth-application-controller 引入api层的依赖,DTO和Result都使用api层的
    <!-- 引入api层 -->
    <dependency>
        <groupId>com.sun.club</groupId>
        <artifactId>sun-club-auth-api</artifactId>
        <version>1.0-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
    
    7.sun-club-subject微服务调用sun-club-auth微服务
    1.sun-club-infra引入sun-club-auth微服务的api包
    <!-- 引入鉴权模块的api层,用于微服务间的调用 -->
    <dependency>
        <groupId>com.sun.club</groupId>
        <artifactId>sun-club-auth-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    2.在sun-club-infra模块的UserRpc.java进行rpc调用
    package com.sunxiansheng.subject.infra.rpc;
    
    import com.sunxiansheng.auth.api.UserFeignService;
    import com.sunxiansheng.auth.entity.AuthUserDTO;
    import com.sunxiansheng.auth.entity.Result;
    import com.sunxiansheng.subject.infra.eneity.UserInfo;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    
    /**
     * Description:
     * @Author sun
     * @Create 2024/6/16 13:45
     * @Version 1.0
     */
    @Component
    public class UserRpc {
    
        // 直接注入FeignService
        @Resource
        private UserFeignService userFeignService;
    
        /**
         * 根据用户名来获取用户信息
         * @param userName
         * @return
         */
        public UserInfo getUserInfo(String userName) {
            AuthUserDTO authUserDTO = new AuthUserDTO();
            authUserDTO.setUserName(userName);
            // 向调用方法一样远程调用auth微服务的接口并得到结果
            Result<AuthUserDTO> result = userFeignService.getUserInfo(authUserDTO);
            // 创建一个自己的eneity用于存放data
            UserInfo userInfo = new UserInfo();
    
            // 没有成功响应,直接返回一个空的对象
            if (!result.getSuccess()) {
                return userInfo;
            }
            // 成功获取信息,将获取到的信息放到自己定义的Entity中
            // 获取到data
            AuthUserDTO data = result.getData();
            // 将data中的数据封装到自己定义的entity中
            userInfo.setUserName(data.getUserName());
            userInfo.setNickName(data.getNickName());
            return userInfo;
        }
    }
    
    3.创建接受信息的entity,UserInfo.java
    package com.sunxiansheng.subject.infra.eneity;
    
    import lombok.Data;
    
    /**
     * Description:
     * @Author sun
     * @Create 2024/6/16 13:47
     * @Version 1.0
     */
    @Data
    public class UserInfo {
    
        private String userName;
    
        private String nickName;
    }
    
    4.在启动类加注解,启动Feign

    image-20240616153405974

    5.测试TestFeignController.java
    package com.sunxiansheng.subject.application.controller;
    
    import com.sunxiansheng.subject.infra.eneity.UserInfo;
    import com.sunxiansheng.subject.infra.rpc.UserRpc;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * Description: 题目分类控制器
     * @Author sun
     * @Create 2024/5/24 9:33
     * @Version 1.0
     */
    @RestController
    @RequestMapping("/subject/category")
    @Slf4j
    public class TestFeignController {
    
        @Resource
        private UserRpc userRpc;
    
        @RequestMapping("/testFeign")
        public void testFeign() {
            UserInfo userInfo = userRpc.getUserInfo("鸡翅");
            log.info("testFeign:" + userInfo);
        }
    }
    
    6.测试

    image-20240616145116795

    image-20240616145124958

    5.OpenFeign拦截器打通微服务上下文(由调用方来写)

    1.FeignRequestInterceptor.java 将唯一标识放到Header中传递到其他微服务
    package com.sunxiansheng.subject.application.interceptor;
    
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Objects;
    
    /**
     * Description: feign的拦截器,当使用feign调用另一个微服务时需要将用户的唯一标识放到Header中
     * @Author sun
     * @Create 2024/6/16 15:01
     * @Version 1.0
     */
    @Component
    public class FeignRequestInterceptor implements RequestInterceptor {
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            // 首先,从网关过来的请求,在Header中一定存着用户的唯一标识,从请求中获取
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            if (Objects.nonNull(request)) {
                // 从请求获取唯一标识
                String loginId = request.getHeader("loginId");
                if (StringUtils.isNotBlank(loginId)) {
                    // 如果获得的标识不为空,就将其放到header中
                    requestTemplate.header("loginId", loginId);
                }
            }
    
        }
    }
    
    2.FeignConfiguration.java 将拦截器注入容器
    package com.sunxiansheng.subject.application.config;
    
    import com.sunxiansheng.subject.application.interceptor.FeignRequestInterceptor;
    import feign.RequestInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    /**
     * Description:
     * @Author sun
     * @Create 2024/6/16 15:12
     * @Version 1.0
     */
    @Configuration
    public class FeignConfiguration extends WebMvcConfigurationSupport {
    
        // 将Feign的拦截器注入进去
        @Bean
        public RequestInterceptor requestInterceptor() {
            return new FeignRequestInterceptor();
        }
    }
    
    3.在被调用方使用过滤器将用户唯一标识存到ThreadLocal(跟前面写过的一样)
    1.目录结构

    image-20240616153838689

    2.ThreadLocal的工具类LoginContextHolder.java
    package com.sunxiansheng.auth.application.context;
    
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * Description: 上下文对象(ThreadLocal)
     * @Author sun
     * @Create 2024/6/15 16:27
     * @Version 1.0
     */
    public class LoginContextHolder {
    
        // 这个ThreadLocal持有一个Map
        private static final InheritableThreadLocal<Map<String, Object>> THREAD_LOCAL
                = new InheritableThreadLocal<>();
    
        /**
         * 为ThreadLocal持有的Map设值
         * @param key
         * @param val
         */
        public static void set(String key, Object val) {
            Map<String, Object> map = getThreadLocalMap();
            map.put(key, val);
        }
    
        /**
         * 从ThreadLocal持有的Map取值
         * @param key
         * @return
         */
        public static Object get(String key) {
            Map<String, Object> map = THREAD_LOCAL.get();
            return map.get(key);
        }
    
        /**
         * 清除ThreadLocal
         */
        public static void remove() {
            THREAD_LOCAL.remove();
        }
    
        /**
         * 初始化一个ThreadLocal持有的Map,要保证这个Map是单例的
         * @return
         */
        public static Map<String, Object> getThreadLocalMap() {
            // 获取到ThreadLocal的Map
            Map<String, Object> map = THREAD_LOCAL.get();
            // 如果是空的再创建一个Map,然后放进去
            if (Objects.isNull(map)) {
                map = new ConcurrentHashMap<>();
                THREAD_LOCAL.set(map);
            }
            // 放到ThreadLocal中
            return map;
        }
    
        // 以下为获取用户信息的方法
        public static String getLoginId() {
            return (String) getThreadLocalMap().get("loginId");
        }
    
    
    }
    
    3.自定义拦截器获取Header中的唯一标识,并放到ThreadLocal中 LoginInterceptor.java
    package com.sunxiansheng.auth.application.interceptor;
    
    import com.sunxiansheng.auth.application.context.LoginContextHolder;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * Description: 处理用户上下文的拦截器
     * @Author sun
     * @Create 2024/6/15 16:20
     * @Version 1.0
     */
    public class LoginInterceptor implements HandlerInterceptor {
    
        /**
         * 当请求到这里了,就说明,网关已经将用户的loginId放到了Header里了
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String loginId = request.getHeader("loginId");
            // 将loginId放到ThreadLocal里面
            LoginContextHolder.set("loginId", loginId);
            return true;
        }
    
    
        /**
         * 在操作结束后清除ThreadLocal,因为如果线程复用并且没清除,这个ThreadLocal还会存在,造成数据污染
         * @param request
         * @param response
         * @param handler
         * @param ex
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            LoginContextHolder.remove();
        }
    }
    
    4.将拦截器注入容器 GlobalConfig.java

    image-20240616154236431

    5.UserController.java 测试获取调用方传来的唯一标识

    image-20240616154256468

    4.用户上下文整体流程测试
    1.网关过滤器将唯一标识从redis中取出,放到Header,传到微服务模块

    image-20240616154819177

    2.Feign的调用微服务的登录拦截器,将唯一标识放到ThreadLocal中

    image-20240616160615477

    3.在调用Feign之前,进入Feign拦截器,将唯一标识放到Header中,传递给被调用方

    image-20240616160721862

    4.被调用方的登录拦截器,读取Header中的唯一标识,放到ThreadLocal中

    image-20240616160801869

    5.被调用方从ThreadLocal中可以获取到唯一标识

    image-20240616160911141

    6.本地缓存guava使用

    1.SubjectCategoryDomainServiceImpl.java
    /**
     * 注意:这里使用本地缓存的原因是分类和标签的改动不大
     * @param subjectCategoryBO
     * @return
     */
    @Override
    public List<SubjectCategoryBO> queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {
        // 构建一个本地缓存的key
        String cacheKey = "categoryAndLabel." + subjectCategoryBO.getId();
        // 根据缓存key来获取id对应的分类列表
        String content = localCache.getIfPresent(cacheKey);
        // 如果缓存中没有,则从数据库中查
        List<SubjectCategoryBO> subjectCategoryBOS = new LinkedList<>();
        if (StringUtils.isBlank(content)) {
            subjectCategoryBOS = getSubjectCategoryBOS(subjectCategoryBO.getId());
            // 将其放到本地缓存中
            localCache.put(cacheKey, JSON.toJSONString(subjectCategoryBOS));
        } else {
            // 如果缓存中有,则将其序列化然后返回
            subjectCategoryBOS = JSON.parseArray(content, SubjectCategoryBO.class);
        }
        return subjectCategoryBOS;
    }
    
    2.测试

    image-20240616171528673

    image-20240616171541514

    3.本地缓存工具类
    1.ListLocalCacheUtil.java
    package com.sunxiansheng.subject.domain.util;
    
    import com.alibaba.fastjson.JSON;
    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import java.util.LinkedList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    import java.util.function.Supplier;
    
    /**
     * Description: List本地缓存工具类(guava)
     * @Author sun
     * @Create 2024/6/16 17:20
     * @Version 1.0
     */
    @Component
    public class ListLocalCacheUtil<V> {
    
        // 使用谷歌提供的本地缓存,value的类型是String,存什么数据直接序列化即可
        private Cache<String, String> localCache =
                CacheBuilder.newBuilder()
                        .maximumSize(5000)
                        .expireAfterWrite(10, TimeUnit.SECONDS)
                        .build();
    
        /**
         * 根据key来获取本地缓存中的value,如果有就直接获取,如果没有,就从db中查询
         * @param cacheKey 构建的本地缓存的key
         * @param clazz 要返回的类型
         * @param supplier 函数式接口,没有参数,返回值为DB查询结果
         * @return
         */
        public List<V> getResult(String cacheKey, Class<V> clazz, Supplier<List<V>> supplier) {
            // 要返回的list
            List<V> resultList = new LinkedList<>();
            // 根据key,获取本地缓存的内容
            String content = localCache.getIfPresent(cacheKey);
            if (StringUtils.isNotBlank(content)) {
                // 如果缓存中有,就将其序列化
                resultList = JSON.parseArray(content, clazz);
            } else {
                // 缓存中没有,List<V>作为返回值,交给数据库中查,具体逻辑交给函数式接口
                resultList = supplier.get();
                if (!CollectionUtils.isEmpty(resultList)) {
                    // 如果从数据库查出来的不是空,就序列化,然后放到缓存中
                    localCache.put(cacheKey, JSON.toJSONString(resultList));
                }
            }
            return resultList;
        }
    
    }
    
    2.使用方式
    1.依赖注入

    image-20240617121126924

    2.使用
    /**
     * 注意:这里使用本地缓存的原因是分类和标签的改动不大
     * @param subjectCategoryBO
     * @return
     */
    @Override
    public List<SubjectCategoryBO> queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {
        // 要查询的分类id
        Long categoryId = subjectCategoryBO.getId();
        // 构建一个本地缓存的key,结果是根据id查询,所以key也根据这个构建
        String cacheKey = "categoryAndLabel." + categoryId;
        // 使用本地缓存工具类,本地缓存中获取数据,如果没有再从数据库中获取,然后将其放到本地缓存中
        List<SubjectCategoryBO> subjectCategoryBOS = listLocalCacheUtil.getResult(cacheKey, SubjectCategoryBO.class,
                () -> {
                    // 函数式接口,参数:空,返回值:List<SubjectCategoryBO>,对象逻辑:从db中查List<SubjectCategoryBO>,然后返回
                    return getSubjectCategoryBOS(subjectCategoryBO.getId());
                });
        return subjectCategoryBOS;
    }
    
    3.测试

    image-20240617121034988

    作者:S-X-S

    物联沃分享整理
    物联沃-IOTWORD物联网 » 用户上下文打通+本地缓存Guava

    发表回复