
导航:LuckReport专栏
一、快速了解
项目简介
Luck-Report 是一款基于开源项目 UReport2 重构的 Java 高性能报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。相较于 UReport2,Luck-Report 在技术架构上进行了全新升级,后端基于 SpringBoot 框架开发、前端采用 Vue 框架构建,技术选型贴合当下主流项目开发标准,可精准适配各类实际开发需求。
Luck-Report 提供了全新的基于网页的报表设计器,可以在 Chrome、Firefox、Edge 等各种主流浏览器运行(IE 浏览器除外)。使用 Luck-Report,打开浏览器即可完成各种复杂报表的设计制作。
Luck-Report 基于 Apache-2.0 开源协议开源
在线体验
- 体验地址:https://www.quzhe.top/luck-report/report/designer
- 源码地址:https://gitee.com/LuckyPools/luck-report
- 文档地址:https://www.quzhe.top/luck-report-blog/report
二、 缓存
分布式或集群系统需采用分布式缓存方案,以保障多节点间报表缓存数据的一致性,下文以 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 禁用本地缓存,分布式环境必须配置 |