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

news2024/9/20 16:41:43

文章目录

  • 🌞 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LSTM长短时记忆网络【数学+图解】

文章目录 1、简介2、门控机制3、LSTM3.1、概念3.2、公式⭐3.3、特点 4、图解LSTM⭐4.1、RNN4.2、时间链条4.3、**记忆单元**&#x1f53a;4.4、LSTM 5、LSTM与GRU的对比6、应用7、训练技巧 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专…

欧拉系统网络配置

从母盘克隆出一个虚拟机openEuler-node2 如何设定网卡的名称为ehtx x为数字 在内核中禁止使用net.ifnames模块&#xff0c;这样可以让网卡显示为ethx [rootlocalhost ~]# gruuby --update-kernel ALL --args net.ifnames0修改完这个参数过后需要重启系统reboot&#xff0c;让…

4658. 质因数个数、197. 阶乘分解、模板题【线性筛求积性函数】(数论练习题)

目录 4658. 质因数个数 题目描述 运行代码 代码思路 197. 阶乘分解 题目描述 运行代码 代码思路 其他代码 代码思路 模板题【线性筛求积性函数】 题目描述 ​编辑 运行代码 代码思路 4658. 质因数个数 题目描述 4658. 质因数个数 - AcWing题库 运行代码 #in…

银河麒麟V10 审计工具 auditd 内存泄漏问题

问题描述 银河麒麟V10 SP1 审计工具 auditd 引发的内存占用过高&#xff0c; 内存占用171G&#xff0c; 内存一直不释放 解决方案 重启进程 auditd 是银河麒麟的审计工具&#xff0c; 分析是由于 yum 源的特性&#xff0c; 造成审计工具占用内存不释放&#xff0c; 重启 a…

dsc集群添加磁盘空间

在达梦数据库dsc集群保姆级部署文档_达梦数据库文档-CSDN博客这篇文档的基础上操作添加磁盘&#xff0c;进行一个扩容操作。 在生产环境中&#xff0c;数据存储集群&#xff08;DSC&#xff09;的磁盘空间不足是一个常见问题&#xff0c;这可能会导致服务中断或性能下降。为了…

代码随想录算法训练营day35 | 0-1背包理论基础、416. 分割等和子集

碎碎念&#xff1a;加油&#xff01;&#xff01; 参考&#xff1a;代码随想录 0-1背包理论基础 几类背包的区别&#xff1a; 0-1背包的每种物品只有一个 完全背包的每种物品有无限个 多重背包的每种物品的个数各不相同 01背包&#xff1a; 有n件物品和一个最多能背重量为w …

如何开发属于你的智能人才招聘系统:源码解析

今天&#xff0c;小编将从源码解析的角度&#xff0c;带你深入了解如何开发属于你的智能人才招聘系统。 一、为什么选择开发自己的智能招聘系统&#xff1f; 市面上已有许多现成的招聘系统&#xff0c;但这些系统往往无法完全满足企业个性化的需求。通过开发自有系统&#xf…

中小型水库雨水情及大坝安全监测系统完整方案

一、背景 随着气候变化和极端天气事件的频发&#xff0c;中小型水库的安全运行显得愈发重要。为确保水库大坝的稳定性和防洪功能的发挥&#xff0c;建设一套完善的雨水情及大坝安全监测系统显得尤为重要。本文将从系统背景、系统介绍、应用实例和未来展望等方面&#xff0c;对…

【HTML入门】第二十三课 - 【实战】做一个简单的图书详情页

这一节&#xff0c;我们继续用纯HTML来做一个实战小案例。 我找了一个图书详情的页面&#xff0c;就像这样&#xff1a; 这一小节&#xff0c;我们用纯HTML标签&#xff0c;来实现一下这个图书详情的内容。 目录 1 布局分析 2 用到的标签 3 实战代码 1 布局分析 我们看这张…

SQL各种注入详解加案例--持续更新

sql注入 联合查询注入案例手工注入判断是否有SQL注入漏洞 sqlmap工具注入 报错注入常用的函数updatexml()函数案例 floor()涉及的函数实现手工注入sqlmap工具注入 盲注布尔盲注案例手工注入脚本sqlmap自动化工具 时间盲注 post注入GET传参和POST传参案例手工注入sqlmap工具 二次…

Venv复制可以减少Pip install

接前面的一篇博客《PyCharm找不到Python咋办》中遇到的步骤&#xff1a; 有上图的提示&#xff0c;说明需要将原来的venv进行清空。&#xff08;还可以用重命名的方式&#xff0c;这样venv当中的库可以保留&#xff0c;如果Python的版本和原来一样的话&#xff0c;直接覆盖回来…

10个日常开发必备的 JavaScript 代码片段

在 Web 开发领域&#xff0c;开发效率是关键。为什么每次开始新项目时都要重新发明轮子&#xff1f;今天&#xff0c;本文汇总整理了一些方便日常开发使用的 JavaScript 代码片段&#xff0c;超越了 Lodash 和 day.js 等常见代码片段&#xff0c;提升你的开发效率&#xff0c;减…

x264 中像素扩边算法源码分析

介绍 在视频编码中,特别是在使用x264这样的H.264/MPEG-4 AVC编码器时,像素扩边(或称为边缘扩展)是一项重要的预处理步骤。像素扩边的目的是在帧的边缘添加额外的像素,这样在编码过程中可以应用滤波器,如去块滤波器(deblocking filter)和水平/垂直滤波器,而不会受到帧…

Mysql开启SSL

等二测出未开启SSL,如下 have_openssl、have_ssl都是DISABLED也不知道当时为啥没开&#xff0c;看最近的都是开启的,整改必去得开了&#xff0c;开启步骤 1.生成秘钥 进入mysql的bin目录下&#xff0c;运行 ./mysql_ssl_rsa_setup运行后会生成证书 默认证书会在mysql的data…

PTrade常见问题系列20

量化周末测试不打印日志&#xff0c;需要如何测试&#xff1f; 需要将量化服务器后台时间&#xff0c;和ptrade终端所在的电脑时间改到下周一或周五&#xff0c;具体测试方案如下&#xff1a; 先停止nginx&#xff0c;并且备份nfs以防恢复异常。 1.重置生产账号A的密码(若测试…

sed 简易使用指南

sed 简易使用指南 1 sed 介绍2 查找3 替换4 反向引用5 删除6 cai&#xff08;菜&#xff09; 导言&#xff1a; 笔者之前花了较多时间学习并整理了sed命令相关的内容&#xff0c;以及一些进阶内容。但是&#xff0c;到后来使用也就只记得那么几个简单的选项&#xff0c;再高级的…

程序员进阶之路:缓存、网络、内存与案例

编辑推荐 适读人群 &#xff1a;本书适合有一定开发经验的开发人员&#xff0c;想要突破初级层次&#xff0c;迈向高级开发的程序员阅读。 【与时俱进】本书基于Linux 6.0及以上版本讲解书中涉及的各个模块&#xff0c;有助于读者理解现代Linux内核&#xff0c;掌握实用的技术…

XML动态sql查询当前时间之前的信息报错

如图&#xff0c;sql语句在数据库里可以正常运行但是再XML文件不可以正常运行&#xff0c;报错。 原因&#xff1a;在XML中小于号"<"是会被默认认定成文一个标签的开始&#xff0c;所以用小于号就会报错。 解决办法&#xff1a; 1.把表达式反过来改成大于号 2…

新手教学系列——使用 Redis 实现任务队列:先进先出和后进先出的选择

在分布式系统和高并发场景下,任务队列是管理任务调度和执行的关键工具。Redis 作为一种高效的内存数据库,提供了简单且强大的数据结构,可以方便地实现任务队列。本文将深入探讨如何利用 Redis 实现先进先出(FIFO)和后进先出(LIFO)两种任务队列,并介绍其应用场景和实现方…

11153 kill boss

这个问题可以通过模拟游戏的过程来解决。我们可以遍历每一轮的出招&#xff0c;根据出招的规则来计算每一轮的伤害&#xff0c;并更新Acmer和Boss的血量。如果在某一轮结束后&#xff0c;Acmer的血量小于等于0&#xff0c;那么Boss赢&#xff1b;如果Boss的血量小于等于0&#…