基于spring gateway 的静态资源缓存实现

由于子项目比较多,子项目都是通过嵌套的方式实现的。就会导致子页面加载比较慢,影响客户体验

实现思路(AI搜的--!):

1、通过spring boot缓存实现静态资源缓存

2、在gateway过滤器,对静态资源进行缓存

直接上代码:

XML 复制代码
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
java 复制代码
package com.xxx.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Wang
 * 创建时间: 2023/11/15 10:19
 * 功能描述:静态资源缓存
 */
@Slf4j
@Component
public class StaticResourceFilter implements GlobalFilter, Ordered {


    private static final String STATIC_RESOURCE_PATTERN = "\\.(html|css|js|png|jpg|jpeg|gif|woff2|woff)$";
    private final WebClient webClient;
    private final CacheManager cacheManager;
    List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());

    public StaticResourceFilter(WebClient webClient, CacheManager cacheManager) {
        this.webClient = webClient;
        this.cacheManager = cacheManager;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI uriInfo = request.getURI();
        String staticResourcePath = getUrl(uriInfo);
        if (isStaticResource(staticResourcePath) && !synchronizedList.contains(staticResourcePath)) {
            //Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
            String cacheKey = request.getURI().toString();
            Cache cache = cacheManager.getCache("staticResources");

            // 尝试从缓存中获取静态资源
            Object cachedResource = cache.get(cacheKey);
            if (cachedResource != null) {
                if (cachedResource instanceof SimpleValueWrapper) {
                    cachedResource = ((SimpleValueWrapper) cachedResource).get();
                }

                // 如果缓存中存在,直接返回缓存的资源
                ServerHttpResponse response = exchange.getResponse();
                HttpHeaders headers = response.getHeaders();

                String fileSuffix = staticResourcePath.replaceAll(".*(\\.[a-zA-Z0-9]+)$", "$1");
                // 根据文件后缀设置MIME类型
                switch (fileSuffix) {
                    case ".html":
                        headers.setContentType(MediaType.TEXT_HTML);
                        break;
                    case ".js":
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/javascript");
                        break;
                    case ".css":
                        headers.set(HttpHeaders.CONTENT_TYPE, "text/css");
                        break;
                    case ".png":
                        headers.setContentType(MediaType.IMAGE_PNG);
                        break;
                    case ".jpg":
                    case ".jpeg":
                        headers.setContentType(MediaType.IMAGE_JPEG);
                        break;
                    case ".woff":
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/font-woff");
                        break;
                    case ".woff2":
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/font-woff2");
                        break;
                    case ".ttf":
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/x-font-ttf");
                        break;
                    case ".eot":
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/vnd.ms-fontobject");
                        break;
                    default:
                        headers.setContentType(MediaType.ALL);
                        break;
                }

                // 这里假设缓存的内容是字节数组,您可以根据实际情况进行调整
                DataBuffer dataBuffer = response.bufferFactory().wrap((byte[]) cachedResource);
                return response.writeWith(Mono.just(dataBuffer));
            }
            // 如果缓存不存在,则继续请求下游服务获取资源,并将其缓存起来
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                getResourceFromDownstream(staticResourcePath, cacheKey, cache);
            }));
        }

        // 继续处理其他过滤器或请求
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 根据请求路径判断是否为静态资源请求
     *
     * @param staticResourcePath 请求路径
     */
    private boolean isStaticResource(String staticResourcePath) {
        Pattern pattern = Pattern.compile(STATIC_RESOURCE_PATTERN);
        Matcher matcher = pattern.matcher(staticResourcePath);
        return matcher.find();
    }

    /**
     * 请求下游服务静态资源的方法,这里只是一个示例,您需要根据实际情况实现此方法
     *
     * @param cache              缓存
     * @param staticResourcePath URL
     * @param cacheKey           缓存Key
     */
    private void getResourceFromDownstream(String staticResourcePath, String cacheKey, Cache cache) {

        synchronizedList.add(staticResourcePath);
        Mono<byte[]> mono = webClient.get().uri(staticResourcePath).retrieve().bodyToMono(byte[].class);
        // 处理响应数据
        mono.subscribe(res -> {
            synchronizedList.remove(staticResourcePath);
            cache.put(cacheKey, res);
        }, error -> {
            log.error("请求下游服务静态资源失败:{},\n错误详情:{}", staticResourcePath, error.toString());
        });
    }

    /**
     * 获取静态资源地址
     *
     * @param uri uri
     * @return 静态资源地址
     */
    private String getUrl(URI uri) {
        String path = uri.getPath();
        String host = uri.getHost();
        int port = uri.getPort();

        // 下游服务的地址是
        String downstreamUrl = String.format("http://%s:%s%s", host, port, path);
        return downstreamUrl;
    }
}
相关推荐
亚历克斯神1 小时前
Java 职业发展:2026 指南
java·spring·微服务
披着羊皮不是狼2 小时前
(7)为 RAG 系统接入 Redis Stack 实现向量持久化
数据库·redis·缓存
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【13】 检查点 (Checkpoint) 机制及各类持久化实现
java·人工智能·spring
難釋懷3 小时前
数据同步策略
缓存
行走的搬运工4 小时前
Spring Security_05
java·spring·mybatis
我登哥MVP4 小时前
【Spring6笔记】 - 11 - JDBCTemplate
java·数据库·spring boot·mysql·spring
也许明天y4 小时前
Spring AI 核心原理解析:基于 1.1.4 版本拆解底层架构
java·后端·spring
希望永不加班4 小时前
SpringBoot 自定义 Starter:从零开发一个私有 Starter
java·spring boot·后端·spring·mybatis
callJJ5 小时前
Spring AI ETL 数据处理管道实战指南:从原始文档到向量索引
java·人工智能·spring·ai·etl·spring ai
程序员潘子5 小时前
【保姆级教程】B 站缓存 m4s 文件转 MP4,无损合成一行命令搞定
缓存·ffmpeg·ffmpeg\