【Luck-Report】缓存

导航:LuckReport专栏

一、快速了解

项目简介

Luck-Report 是一款基于开源项目 UReport2 重构的 Java 高性能报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。相较于 UReport2,Luck-Report 在技术架构上进行了全新升级,后端基于 SpringBoot 框架开发、前端采用 Vue 框架构建,技术选型贴合当下主流项目开发标准,可精准适配各类实际开发需求。

Luck-Report 提供了全新的基于网页的报表设计器,可以在 Chrome、Firefox、Edge 等各种主流浏览器运行(IE 浏览器除外)。使用 Luck-Report,打开浏览器即可完成各种复杂报表的设计制作。

Luck-Report 基于 Apache-2.0 开源协议开源

在线体验

二、 缓存

分布式或集群系统需采用分布式缓存方案,以保障多节点间报表缓存数据的一致性,下文以 Redis 为例进行说明

添加 Redis 依赖 [​](#添加 Redis 依赖)

在 pom.xml 中添加 Spring Data Redis 依赖:

xml

复制代码
<!-- Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置文件,禁用本地缓存 ​

在 application.yml 中添加以下配置,禁用本地缓存:

yaml

复制代码
luck-report:
  # ...
  # 禁用本地缓存,分布式/集群环境必须配置
  disableLocalReportCache: true

注入 Redis 工具类 Bean [​](#注入 Redis 工具类 Bean)

创建 Redis 配置类,注入 RedisTemplate Bean,序列化策略采用 JSON:

java

复制代码
package com.luck.product.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis 配置类,用于配置 RedisTemplate
 *
 * @author luckyPools
 * @since 2017年3月8日
 */
@Configuration
public class RedisConfig {

    /**
     * 配置 RedisTemplate Bean。
     * 设置 Key 使用 String 序列化器,Value 使用 JSON 序列化器
     *
     * @param connectionFactory Redis 连接工厂,由 Spring Boot 自动注入
     * @return 配置好的 RedisTemplate 实例
     */
    @Bean("remoteRedisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();

        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

实现 ReportCache 接口 [​](#实现 ReportCache 接口)

创建 Redis 缓存实现类,实现 ReportCache 接口:

java

复制代码
package com.luck.product.boot.cache;

import com.luck.report.core.cache.ReportCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis 缓存服务实现,基于 Spring Data Redis 提供分布式缓存能力
 *
 * @author luckyPools
 * @since 2017年3月8日
 */
@Service
public class RedisCache implements ReportCache {

    private static final Logger log = LoggerFactory.getLogger(RedisCache.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 检查 Redis 缓存服务是否可用。
     *
     * @return Redis 不可用返回 true,可用返回 false
     */
    @Override
    public boolean disabled() {
        try {
            redisTemplate.getConnectionFactory().getConnection().ping();
            return false;
        } catch (Exception e) {
            log.error("检查 Redis 连接状态失败", e);
            return true;
        }
    }

    /**
     * 根据键获取缓存值。
     *
     * @param key 缓存键,不能为空
     * @param <T> 返回值类型
     * @return 缓存值,不存在或已过期返回 null
     */
    @Override
    public <T> T get(String key) {
        if (key == null) {
            return null;
        }
        try {
            Object value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                return null;
            }
            @SuppressWarnings("unchecked")
            T result = (T) value;
            return result;
        } catch (Exception e) {
            log.error("获取缓存失败,key: {}", key, e);
            return null;
        }
    }

    /**
     * 根据键获取缓存值并转换为指定类型。
     *
     * @param key   缓存键,不能为空
     * @param clazz 目标类型,不能为空
     * @param <T>   返回值类型
     * @return 缓存值,不存在或已过期返回 null
     */
    @Override
    public <T> T get(String key, Class<T> clazz) {
        if (key == null || clazz == null) {
            return null;
        }
        try {
            Object value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                return null;
            }
            if (clazz.isInstance(value)) {
                return clazz.cast(value);
            }
            return null;
        } catch (Exception e) {
            log.error("获取缓存并转换类型失败,key: {}, clazz: {}", key, clazz.getName(), e);
            return null;
        }
    }

    /**
     * 存入缓存,不设置过期时间。
     *
     * @param key   缓存键,不能为空
     * @param value 缓存值,不能为空
     * @param <T>   值类型
     */
    @Override
    public <T> void put(String key, T value) {
        if (key == null || value == null) {
            return;
        }
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            log.error("存入缓存失败,key: {}", key, e);
        }
    }

    /**
     * 存入缓存,指定过期时间。
     *
     * @param key   缓存键,不能为空
     * @param value 缓存值,不能为空
     * @param time  过期时间,单位:秒
     * @param <T>   值类型
     */
    @Override
    public <T> void put(String key, T value, long time) {
        if (key == null || value == null) {
            return;
        }
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                redisTemplate.opsForValue().set(key, value);
            }
        } catch (Exception e) {
            log.error("存入缓存失败,key: {}, time: {}", key, time, e);
        }
    }

    /**
     * 判断缓存键是否存在。
     *
     * @param key 缓存键,不能为空
     * @return 存在且未过期返回 true,否则返回 false
     */
    @Override
    public boolean exists(String key) {
        if (key == null) {
            return false;
        }
        try {
            Boolean hasKey = redisTemplate.hasKey(key);
            return hasKey != null && hasKey;
        } catch (Exception e) {
            log.error("判断缓存键是否存在失败,key: {}", key, e);
            return false;
        }
    }

    /**
     * 删除指定缓存键。
     *
     * @param key 缓存键,不能为空
     */
    @Override
    public void remove(String key) {
        if (key == null) {
            return;
        }
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            log.error("删除缓存失败,key: {}", key, e);
        }
    }

    /**
     * 根据前缀查询匹配的所有缓存键。
     *
     * @param keyPatten 缓存键前缀,不能为空
     * @return 匹配的缓存键集合
     */
    @Override
    public Set<String> keys(String keyPatten) {
        if (keyPatten == null) {
            return null;
        }
        try {
            return redisTemplate.keys(keyPatten + "*");
        } catch (Exception e) {
            log.error("查询缓存键失败,keyPatten: {}", keyPatten, e);
            return null;
        }
    }

    /**
     * 设置缓存键的过期时间。
     *
     * @param key  缓存键,不能为空
     * @param time 过期时间,单位:秒
     * @return 设置成功返回 true,键不存在或已过期返回 false
     */
    @Override
    public boolean setExpire(String key, long time) {
        if (key == null || time <= 0) {
            return false;
        }
        try {
            Boolean result = redisTemplate.expire(key, time, TimeUnit.SECONDS);
            return result != null && result;
        } catch (Exception e) {
            log.error("设置缓存过期时间失败,key: {}, time: {}", key, time, e);
            return false;
        }
    }
}

配置说明:

配置项 说明
disableLocalReportCache 设置为 true 禁用本地缓存,分布式环境必须配置

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
小羊没烦恼!2 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
俊昭喜喜里2 天前
java中的继承和多态的区别
java
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商