This commit is contained in:
JackZhan 2024-04-18 14:00:25 +08:00 committed by GitHub
commit 0d1a5f9929
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 565 additions and 311 deletions

View File

@ -88,6 +88,10 @@
<artifactId>lucene-highlighter</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
@ -164,4 +168,4 @@
</plugins>
</build>
</project>
</project>

View File

@ -1,5 +1,6 @@
package com.blossom.backend;
import com.blossom.backend.config.ContextInitializer;
import com.blossom.backend.config.PropertiesCheckListener;
import com.blossom.common.base.BaseConstants;
import com.blossom.expand.tracker.core.Tracker;
@ -23,6 +24,7 @@ public class APP {
Tracker.start("APPLICATION_START", TrackerConstants.SPAN_TYPE_APPLICATION_RUN);
SpringApplication app = new SpringApplication(APP.class);
app.addListeners(new PropertiesCheckListener());
app.addInitializers(new ContextInitializer());
app.run(args);
BaseConstants.desc();
Tracker.end();

View File

@ -1,111 +1,111 @@
//package com.blossom.backend.base.auth.redis;
//
//import com.blossom.backend.base.auth.AuthConstant;
//import com.blossom.backend.base.auth.AuthProperties;
//import com.blossom.backend.base.auth.repo.TokenRepository;
//import com.blossom.backend.base.auth.filters.AuthFilterProxy;
//import com.blossom.backend.base.auth.filters.HttpFirewall;
//import com.blossom.backend.base.auth.filters.RequestLogFilter;
//import com.blossom.backend.base.auth.filters.WhiteListFilter;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
//import org.springframework.core.annotation.Order;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.stereotype.Component;
//
//import javax.servlet.FilterChain;
//import javax.servlet.ServletException;
//import javax.servlet.ServletRequest;
//import javax.servlet.ServletResponse;
//import java.io.IOException;
//
///**
// * redis token 拦截器实现
// *
// * @author xzzz
// */
//@Slf4j
//@Component
//@Order(AuthConstant.AUTH_FILTER_PROXY)
//@ConditionalOnClass(RedisTemplate.class)
//@ConditionalOnProperty(value = "project.auth.type", havingValue = "redis")
//public class RedisTokenAuthFilterProxy extends AuthFilterProxy {
// /**
// * 防火墙对象
// */
// protected final HttpFirewall httpFirewall = new HttpFirewall();
// /**
// * 日期过滤器
// */
// protected final RequestLogFilter logFilter;
// /**
// * 白名单过滤器
// */
// protected final WhiteListFilter whiteListFilter;
// /**
// * 授权检查过滤器
// */
// private final RedisTokenValidateFilter redisTokenValidateFilter;
// /**
// * 唯一授权检查过滤器
// */
// private final RedisTokenUniqueFilter redisTokenUniqueFilter;
// /**
// * 授权重置过滤器
// */
// private final RedisTokenExpireResetFilter redisTokenExpireResetFilter;
//
// public RedisTokenAuthFilterProxy(AuthProperties properties, TokenRepository tokenRepository) {
// super(properties);
// this.logFilter = new RequestLogFilter(properties);
// this.whiteListFilter = new WhiteListFilter(properties);
// this.redisTokenValidateFilter = new RedisTokenValidateFilter(tokenRepository);
// this.redisTokenUniqueFilter = new RedisTokenUniqueFilter(tokenRepository);
// this.redisTokenExpireResetFilter = new RedisTokenExpireResetFilter(tokenRepository);
// }
//
// /**
// * <p>1. 防火墙校验
// * <p>2. 授权校验
// * <ol>
// * <li>日志</li>
// * <li>白名单</li>
// * <li>授权校验</li>
// * <li>授权续期</li>
// * </ol>
// *
// * @param request request
// * @param response response
// * @param chain chain
// */
// @Override
// protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// /*
// 1. 执行防火墙
// */
// httpFirewall.wall(request);
//
// /*
// 2. 授权校验
// */
// // 2.1 日志
// logFilter.doFilter(request, response);
// // 2.2 白名单
// whiteListFilter.doFilter(request, response);
// // 2.3 token 校验
// redisTokenValidateFilter.doFilter(request, response);
// // 2.4 token 唯一校验
// redisTokenUniqueFilter.doFilter(request, response);
// // 2.5 过期时间刷新
// redisTokenExpireResetFilter.doFilter(request, response);
//
// /*
// 虚拟过滤器链执行完毕后交由原生过滤器继续执行
// */
// chain.doFilter(request, response);
//
// log.debug("[AUTHORIZ] **Proxy** >> 原生过滤器: request 请求结束");
// }
//
//}
package com.blossom.backend.base.auth.redis;
import com.blossom.backend.base.auth.AuthConstant;
import com.blossom.backend.base.auth.AuthProperties;
import com.blossom.backend.base.auth.filters.AuthFilterProxy;
import com.blossom.backend.base.auth.filters.HttpFirewall;
import com.blossom.backend.base.auth.filters.RequestLogFilter;
import com.blossom.backend.base.auth.filters.WhiteListFilter;
import com.blossom.backend.base.auth.repo.TokenRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* redis token 拦截器实现
*
* @author xzzz
*/
@Slf4j
@Component
@Order(AuthConstant.AUTH_FILTER_PROXY)
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(value = "project.auth.type", havingValue = "redis")
public class RedisTokenAuthFilterProxy extends AuthFilterProxy {
/**
* 防火墙对象
*/
protected final HttpFirewall httpFirewall = new HttpFirewall();
/**
* 日期过滤器
*/
protected final RequestLogFilter logFilter;
/**
* 白名单过滤器
*/
protected final WhiteListFilter whiteListFilter;
/**
* 授权检查过滤器
*/
private final RedisTokenValidateFilter redisTokenValidateFilter;
/**
* 唯一授权检查过滤器
*/
private final RedisTokenUniqueFilter redisTokenUniqueFilter;
/**
* 授权重置过滤器
*/
private final RedisTokenExpireResetFilter redisTokenExpireResetFilter;
public RedisTokenAuthFilterProxy(AuthProperties properties, TokenRepository tokenRepository) {
super(properties);
this.logFilter = new RequestLogFilter(properties);
this.whiteListFilter = new WhiteListFilter(properties);
this.redisTokenValidateFilter = new RedisTokenValidateFilter(tokenRepository);
this.redisTokenUniqueFilter = new RedisTokenUniqueFilter(tokenRepository);
this.redisTokenExpireResetFilter = new RedisTokenExpireResetFilter(tokenRepository);
}
/**
* <p>1. 防火墙校验
* <p>2. 授权校验
* <ol>
* <li>日志</li>
* <li>白名单</li>
* <li>授权校验</li>
* <li>授权续期</li>
* </ol>
*
* @param request request
* @param response response
* @param chain chain
*/
@Override
protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/*
1. 执行防火墙
*/
httpFirewall.wall(request);
/*
2. 授权校验
*/
// 2.1 日志
logFilter.doFilter(request, response);
// 2.2 白名单
whiteListFilter.doFilter(request, response);
// 2.3 token 校验
redisTokenValidateFilter.doFilter(request, response);
// 2.4 token 唯一校验
redisTokenUniqueFilter.doFilter(request, response);
// 2.5 过期时间刷新
redisTokenExpireResetFilter.doFilter(request, response);
/*
虚拟过滤器链执行完毕后交由原生过滤器继续执行
*/
chain.doFilter(request, response);
log.debug("[AUTHORIZ] **Proxy** >> 原生过滤器: request 请求结束");
}
}

View File

@ -1,31 +1,31 @@
//package com.blossom.backend.base.auth.redis;
//
//import cn.hutool.core.lang.UUID;
//import com.blossom.backend.base.auth.pojo.AccessToken;
//import com.blossom.backend.base.auth.token.TokenEncoder;
//import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.stereotype.Component;
//
///**
// * token 编解码器, 简单生成一个 UUID token, token 信息无加解密, 编解码关系
// *
// * @author xzzz
// */
//@Component
//@ConditionalOnClass(RedisTemplate.class)
//@ConditionalOnProperty(value = "project.auth.type", havingValue = "redis")
//public class RedisTokenEncoder implements TokenEncoder {
//
// /**
// * 生成一个 uuid 作为 token
// *
// * @param accessToken token
// * @return uuid
// */
// @Override
// public String encode(AccessToken accessToken) {
// return UUID.randomUUID().toString(true);
// }
//}
package com.blossom.backend.base.auth.redis;
import cn.hutool.core.lang.UUID;
import com.blossom.backend.base.auth.pojo.AccessToken;
import com.blossom.backend.base.auth.token.TokenEncoder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
* token 编解码器, 简单生成一个 UUID token, token 信息无加解密, 编解码关系
*
* @author xzzz
*/
@Component
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(value = "project.auth.type", havingValue = "redis")
public class RedisTokenEncoder implements TokenEncoder {
/**
* 生成一个 uuid 作为 token
*
* @param accessToken token
* @return uuid
*/
@Override
public String encode(AccessToken accessToken) {
return UUID.randomUUID().toString(true);
}
}

View File

@ -1,85 +1,96 @@
//package com.blossom.backend.base.auth.redis;
//
//import cn.hutool.core.util.StrUtil;
//import com.blossom.backend.base.auth.TokenUtil;
//import com.blossom.backend.base.auth.pojo.AccessToken;
//import com.blossom.backend.base.auth.repo.TokenRepository;
//import com.blossom.common.base.exception.XzException500;
//import com.blossom.common.base.util.json.JsonUtil;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
//import org.springframework.data.redis.core.RedisTemplate;
//import org.springframework.data.redis.core.StringRedisTemplate;
//import org.springframework.scheduling.annotation.Async;
//import org.springframework.stereotype.Component;
//
//import java.util.concurrent.TimeUnit;
//
///**
// * redis token 存储
// *
// * @author xzzz
// */
//@Slf4j
//@Component
//@ConditionalOnClass(RedisTemplate.class)
//@ConditionalOnProperty(value = "project.auth.type", havingValue = "redis")
//public class RedisTokenRepository implements TokenRepository {
//
// @Autowired
// private StringRedisTemplate redisTemplate;
//
// @Override
// public void saveToken(AccessToken accessToken) {
// if (accessToken == null || StrUtil.isBlank(accessToken.getToken())) {
// throw new XzException500("无法保存空的AccessToken");
// }
// redisTemplate.opsForValue().set(
// TokenUtil.buildTokenKey(accessToken.getToken()),
// JsonUtil.toJson(accessToken),
// accessToken.getDuration(),
// TimeUnit.SECONDS);
// }
//
// @Override
// public AccessToken getToken(String token) {
// if (StrUtil.isBlank(token)) {
// return null;
// }
// return JsonUtil.toObj(redisTemplate.opsForValue().get(TokenUtil.buildTokenKey(token)), AccessToken.class);
// }
//
// @Async
// @Override
// public void remove(String token) {
// log.info("异步删除 token");
// redisTemplate.delete(TokenUtil.buildTokenKey(token));
// }
//
// @Override
// public void saveUniqueToken(AccessToken accessToken) {
// if (accessToken == null || StrUtil.isBlank(accessToken.getToken())) {
// throw new XzException500("无法保存空的AccessToken");
// }
//
// if (accessToken.getMultiPlaceLogin()) {
// return;
// }
//
// redisTemplate.opsForValue().set(
// TokenUtil.buildUniqueTokenKey(String.valueOf(accessToken.getUserId())),
// accessToken.getToken(),
// accessToken.getDuration(),
// TimeUnit.SECONDS);
// }
//
// @Override
// public String getUniqueToken(String userId) {
// if (StrUtil.isBlank(userId)) {
// return null;
// }
// return redisTemplate.opsForValue().get(TokenUtil.buildUniqueTokenKey(userId));
// }
//}
package com.blossom.backend.base.auth.redis;
import cn.hutool.core.util.StrUtil;
import com.blossom.backend.base.auth.TokenUtil;
import com.blossom.backend.base.auth.pojo.AccessToken;
import com.blossom.backend.base.auth.repo.TokenRepository;
import com.blossom.common.base.exception.XzException500;
import com.blossom.common.base.util.json.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* redis token 存储
*
* @author xzzz
*/
@Slf4j
@Component
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(value = "project.auth.type", havingValue = "redis")
public class RedisTokenRepository implements TokenRepository {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void saveToken(AccessToken accessToken) {
if (accessToken == null || StrUtil.isBlank(accessToken.getToken())) {
throw new XzException500("无法保存空的AccessToken");
}
redisTemplate.opsForValue().set(
TokenUtil.buildTokenKey(accessToken.getToken()),
JsonUtil.toJson(accessToken),
accessToken.getDuration(),
TimeUnit.SECONDS);
}
@Override
public AccessToken getToken(String token) {
if (StrUtil.isBlank(token)) {
return null;
}
return JsonUtil.toObj(redisTemplate.opsForValue().get(TokenUtil.buildTokenKey(token)), AccessToken.class);
}
@Async
@Override
public void remove(String token) {
log.info("异步删除 token");
redisTemplate.delete(TokenUtil.buildTokenKey(token));
}
@Override
public void removeAll(Long userId) {
// redisTemplate.delete(userId);
// Map<String, AccessToken> maps = tokenCache.asMap();
// maps.forEach((k, t) -> {
// if (t.getUserId().equals(userId)) {
// tokenCache.invalidate(k);
// }
// });
}
@Override
public void saveUniqueToken(AccessToken accessToken) {
if (accessToken == null || StrUtil.isBlank(accessToken.getToken())) {
throw new XzException500("无法保存空的AccessToken");
}
if (accessToken.getMultiPlaceLogin()) {
return;
}
redisTemplate.opsForValue().set(
TokenUtil.buildUniqueTokenKey(String.valueOf(accessToken.getUserId())),
accessToken.getToken(),
accessToken.getDuration(),
TimeUnit.SECONDS);
}
@Override
public String getUniqueToken(String userId) {
if (StrUtil.isBlank(userId)) {
return null;
}
return redisTemplate.opsForValue().get(TokenUtil.buildUniqueTokenKey(userId));
}
}

View File

@ -0,0 +1,21 @@
package com.blossom.backend.config;
import com.blossom.common.base.exception.XzExceptionSys;
import com.blossom.common.cache.CacheTypeEnum;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* 系统参数初始化
*/
public class ContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
boolean enabled = Boolean.parseBoolean(environment.getProperty("project.base.enabled.redis"));
boolean status = CacheTypeEnum.redis.name().equals(environment.getProperty("project.cache.type")) && !enabled;
XzExceptionSys.throwBy(status, "请在配置文件中开启 redis 【project.base.enabled.redis】");
}
}

View File

@ -3,17 +3,15 @@ package com.blossom.backend.server.article.draft;
import cn.hutool.core.util.ObjUtil;
import com.blossom.common.base.exception.XzException400;
import com.blossom.common.base.util.security.SHA256Util;
import com.blossom.common.cache.caffeine.DynamicExpiry;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.blossom.common.cache.CacheService;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.util.Objects;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 文章临时访问
@ -24,18 +22,9 @@ import java.util.concurrent.TimeUnit;
@Service
public class ArticleTempVisitService {
/**
* 存放文章ID的缓存
*
* @since 1.13.0 支持动态过期时间
*/
private final Cache<String, TempVisit> tempVisitCache = Caffeine.newBuilder()
.expireAfter(new DynamicExpiry())
.initialCapacity(500)
.removalListener((String key, TempVisit value, RemovalCause cause) ->
log.info("remove temp visit articleId [" + Objects.requireNonNull(value).getArticleId() + "]")
)
.build();
@Lazy
@Resource
private CacheService<String, TempVisit> cacheService;
/**
* 生成一个缓存 key, key 并非文章的摘要码,
@ -48,9 +37,8 @@ public class ArticleTempVisitService {
public String create(Long articleId, Long userId, Long duration) {
XzException400.throwBy(ObjUtil.isNull(articleId), "文章ID为必填项");
String key = SHA256Util.encode(UUID.randomUUID().toString());
tempVisitCache.policy().expireVariably().ifPresent(e -> {
e.put(key, new TempVisit(articleId, userId), duration, TimeUnit.MINUTES);
});
TempVisit value = new TempVisit(articleId, userId);
cacheService.put(key, value, duration * 60);
return key;
}
@ -62,13 +50,14 @@ public class ArticleTempVisitService {
* @return 文章ID, 文章ID不存在时返回 null
*/
public TempVisit get(String key) {
return tempVisitCache.getIfPresent(key);
return cacheService.get(key,TempVisit.class).orElse(null);
}
/**
* 临时访问对象
*/
@Data
@NoArgsConstructor
public static class TempVisit {
/**
* 文章ID

View File

@ -3,45 +3,38 @@ package com.blossom.backend.server.article.draft;
import com.blossom.backend.server.doc.DocService;
import com.blossom.backend.server.folder.FolderTypeEnum;
import com.blossom.common.base.exception.XzException500;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import lombok.AllArgsConstructor;
import com.blossom.common.cache.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@Component
@AllArgsConstructor
public class ImportManager {
private final DocService docService;
@Autowired
private DocService docService;
private final ReentrantLock LOCK = new ReentrantLock();
/**
* 批量导入时的批次缓存, 一个批次的导入只会从数据库获取一次排序, 后续排序从缓存中递增
*/
private final Cache<String, AtomicInteger> batchCache = Caffeine.newBuilder()
.initialCapacity(50)
.expireAfterWrite(120, TimeUnit.MINUTES)
.removalListener((String location, AtomicInteger i, RemovalCause cause) ->
log.info("batch import [" + location + "] has been deleted")
)
.build();
@Resource
private CacheService<String, AtomicInteger> cacheService;
/**
* 并发导入时的排序获取
*/
public Integer getSort(String batchId, Long pid, Long userId) {
AtomicInteger sort = batchCache.getIfPresent(batchId);
AtomicInteger sort = cacheService.get(batchId, AtomicInteger.class).orElse(null);
if (null == sort) {
try {
LOCK.tryLock(1000, TimeUnit.MILLISECONDS);
sort = batchCache.getIfPresent(batchId);
sort = cacheService.get(batchId, AtomicInteger.class).orElse(null);
if (null == sort) {
sort = initBatchCount(batchId, pid, userId);
}
@ -58,7 +51,7 @@ public class ImportManager {
private AtomicInteger initBatchCount(String batchId, Long pid, Long userId) {
System.out.println("初始化导入排序");
AtomicInteger i = new AtomicInteger(docService.selectMaxSortByPid(pid, userId, FolderTypeEnum.ARTICLE) + 1);
batchCache.put(batchId, i);
cacheService.put(batchId, i, 120 * 60L);
return i;
}
}

View File

@ -9,19 +9,19 @@ import com.blossom.backend.thirdparty.hefeng.pojo.*;
import com.blossom.common.base.exception.XzException400;
import com.blossom.common.base.util.json.JsonUtil;
import com.blossom.common.base.util.okhttp.HttpUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.blossom.common.cache.CacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 天气查询
@ -42,16 +42,9 @@ public class WeatherManager {
private static final String URL_HOURLY = "https://devapi.heweather.net/v7/weather/24h";
private static final String SUCCESS = "200";
/**
*
*/
private final Cache<String, WeatherRes> weatherCache = Caffeine.newBuilder()
.initialCapacity(50)
.expireAfterWrite(45, TimeUnit.MINUTES)
.removalListener((String location, WeatherRes weather, RemovalCause cause) ->
log.info("Weather cache [" + location + "] has been deleted")
)
.build();
@Lazy
@Resource
private CacheService<String, WeatherRes> cacheService;
@Autowired
private ParamService paramService;
@ -59,12 +52,8 @@ public class WeatherManager {
/**
* 查询天气信息
*/
@Cacheable(cacheNames = "weather:location", key = "#location")
public WeatherRes findWeatherAll(String location) {
WeatherRes cache = weatherCache.getIfPresent(location);
if (cache != null) {
log.debug("[BLOSSOM] get weather from cache: {}", location);
return cache;
}
log.info("[BLOSSOM] refresh weather: {}", location);
Map<String, String> maps = initParam(location);
if (maps == null) {
@ -143,7 +132,6 @@ public class WeatherManager {
} else {
log.error("获取小时预报失败, resp: {}", cityStr);
}
weatherCache.put(location, weather);
return weather;
}
@ -151,7 +139,7 @@ public class WeatherManager {
* 清除缓存
*/
public void clear(String location) {
weatherCache.invalidate(location);
cacheService.remove(location);
}
/**

View File

@ -6,6 +6,12 @@ spring:
password: jasmine888
hikari:
max-lifetime: 120000
redis:
database: 0
host: 127.0.0.1
password:
port: 6379
timeout: 3000 # 连接超时时间 单位 ms毫秒
logging:
level:
@ -29,6 +35,8 @@ logging:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ project ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
project:
base:
enabled:
redis: false #是否需要使用 redis
time-zone: GMT+8
version: @project.version@
ex:
@ -64,6 +72,8 @@ project:
- /assets/**
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ Cache/Redis ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cache: # 缓存
#项目的缓存方式 redis/caffeine
type: caffeine
names-config: # 缓存键超时时间配置
- name: test_cache1
seconds: 10
@ -86,4 +96,4 @@ project:
# 请以 /pic 结尾, 如果你在 nginx 中配置有代理, 注意别忘了添加你的代理路径
domain: "http://www.google.com/"
# 请以 / 开头, / 结尾, 简短的路径在文章中有更好的显示效果, 过长一定程度会使文章内容混乱
default-path: "/home/bl/img/"
default-path: "/home/bl/img/"

View File

@ -6,6 +6,12 @@ spring:
password: jasmine888
hikari:
max-lifetime: 120000
redis:
database: 1
host: 127.0.0.1
password:
port: 6379
timeout: 3000 # 连接超时时间 单位 ms毫秒
logging:
level:
@ -20,6 +26,8 @@ logging:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ project ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
project:
base:
enabled:
redis: false #是否需要使用 redis
time-zone: GMT+8
version: @project.version@
ex:
@ -30,9 +38,9 @@ project:
# 动态日志级别配置
log:
duration: 600000 # 动态日志级别 6 分钟后失效
restore-duration: 30000 # 30 秒判断一次是否失效
restore-duration: 5000 # 30 秒判断一次是否失效
db:
slow-interval: 200 # 慢SQL
slow-interval: 500 # 慢SQL
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ Auth ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
auth: # 授权
enabled: true # 开启授权
@ -56,6 +64,8 @@ project:
- /assets/**
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ Cache/Redis ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cache: # 缓存
#项目的缓存方式 redis/caffeine
type: caffeine
names-config: # 缓存键超时时间配置
- name: test_cache1
seconds: 10
@ -79,4 +89,4 @@ project:
# 注意:在下方示例中, /bl 即为 nginx 反向代理路径, 如果你的访问路径中不包含反向代理或路径不同, 请酌情删除或修改
domain: "http://www.google.com/"
# 请以 / 开头, / 结尾, 简短的路径在文章中有更好的显示效果, 过长一定程度会使文章内容混乱
default-path: "/home/bl/img/"
default-path: "/home/bl/img/"

View File

@ -9,6 +9,12 @@ spring:
password: jasmine888
hikari:
max-lifetime: 120000
redis:
database: 0
host: 127.0.0.1
password:
port: 6379
timeout: 3000 # 连接超时时间 单位 ms毫秒
logging:
level:
@ -23,6 +29,8 @@ logging:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ project ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
project:
base:
enabled:
redis: false #是否需要使用 redis
time-zone: GMT+8
version: @project.version@
ex:
@ -58,6 +66,8 @@ project:
- /assets/**
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ Cache/Redis ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cache: # 缓存
#项目的缓存方式 redis/caffeine
type: caffeine
names-config: # 缓存键超时时间配置
- name: test_cache1
seconds: 10
@ -81,4 +91,4 @@ project:
# 注意:在下方示例中, /bl 即为 nginx 反向代理路径, 如果你的访问路径中不包含反向代理或路径不同, 请酌情删除或修改
domain: "https://www.wangyunf.com/blall/pic/"
# 请以 / 开头, / 结尾, 简短的路径在文章中有更好的显示效果, 过长一定程度会使文章内容混乱
default-path: "/home/blall/img/"
default-path: "/home/blall/img/"

View File

@ -27,6 +27,11 @@ public class BaseProperties {
*/
private String version = "xyz";
/**
* 中间件启用配置
*/
private Enabled enabled = new Enabled();
/**
* 异常处理信息配置
*/
@ -37,6 +42,16 @@ public class BaseProperties {
*/
private Log log = new Log();
@Data
public static class Enabled {
/**
* 是否开启 Redis
*/
private Boolean redis = false;
}
@Data
public static class Ex {
/**

View File

@ -0,0 +1,27 @@
package com.blossom.common.base.exception;
import cn.hutool.core.util.StrUtil;
import com.blossom.common.base.pojo.RCode;
/**
* @author xzzz
*/
public class XzExceptionSys extends XzAbstractException {
public XzExceptionSys(String message) {
super(RCode.SYSTEM_INITIATE_ERROR.getCode(), message);
}
public XzExceptionSys(String message, String... args) {
super(RCode.SYSTEM_INITIATE_ERROR.getCode(), String.format(message, args));
}
public static void throwBy(boolean expression, String msg) {
if (expression) {
if (StrUtil.isBlank(msg)) {
msg = RCode.SYSTEM_INITIATE_ERROR.getMsg();
}
throw new XzExceptionSys(msg);
}
}
}

View File

@ -35,6 +35,9 @@ public enum RCode implements IRCode {
/* ──────────────────────────────────────────── 503 ────────────────────────────────────────*/
SERVER_UNAVAILABLE ("50300", "服务器暂时无法处理请求, 请稍后再试"),
/* ──────────────────────────────────────────── 900 ────────────────────────────────────────*/
SYSTEM_INITIATE_ERROR("90001", "系统启动失败"),
;
@Getter

View File

@ -5,7 +5,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.blossom.common.base.config.WebMvcConfig,\
com.blossom.common.base.exception.ExceptionAdviceByGlobal,\
com.blossom.common.base.exception.ExceptionAdviceByXz,\
com.blossom.common.base.util.spring.SpringUtil,\
com.blossom.common.base.log.DynamicLogRepositoryCaffeine
com.blossom.common.base.util.spring.SpringUtil

View File

@ -0,0 +1,24 @@
package com.blossom.common.cache;
import java.util.Optional;
/**
* 抽象缓存接口
*
* @param <K> 缓存Key
* @param <V> 缓存值
*/
public interface CacheService<K, V> {
void put(K key, V value);
/**
*
* @param key
* @param value
* @param expirationInSeconds 过期时间
*/
void put(K key, V value, Long expirationInSeconds);
Optional<V> get(K key, Class<V> valueType);
void remove(K key);
void removeAll();
}

View File

@ -0,0 +1,12 @@
package com.blossom.common.cache;
/**
* 缓存类型
*
*/
public enum CacheTypeEnum {
redis,
caffeine,
}

View File

@ -17,6 +17,11 @@ import java.util.List;
@ConfigurationProperties(prefix = "project.cache")
public class CommonCacheProperties {
/**
* 缓存类型 redis/caffeine
*/
private CacheTypeEnum type = CacheTypeEnum.caffeine;
/**
* 缓存的 key 配置
*/

View File

@ -5,7 +5,7 @@ import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit;
*/
@Slf4j
@EnableCaching
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
@ConditionalOnProperty(value = "project.cache.type", havingValue = "caffeine")
public class CaffeineCacheConfig {
/**

View File

@ -0,0 +1,57 @@
package com.blossom.common.cache.caffeine;
import com.blossom.common.cache.CacheService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* Caffeine 缓存操作
* @param <K>
* @param <V>
*/
@Component
@Slf4j
@ConditionalOnProperty(value = "project.cache.type", havingValue = "caffeine")
public class CaffeineCacheService<K, V> implements CacheService<K, V> {
private final Cache<K, V> cache;
@Autowired
public CaffeineCacheService() {
log.info("[ CACHE] 业务缓存使用 : Caffeine");
cache = Caffeine.newBuilder().build();
}
@Override
public void put(K key, V value) {
cache.put(key, value);
}
@Override
public void put(K key, V value, Long expirationInSeconds) {
cache.policy().expireVariably().ifPresent(e -> e.put(key, value, expirationInSeconds, TimeUnit.SECONDS));
}
@Override
public Optional<V> get(K key, Class<V> valueType) {
return Optional.ofNullable(cache.getIfPresent(key));
}
@Override
public void remove(K key) {
cache.invalidate(key);
}
@Override
public void removeAll() {
cache.invalidateAll();
}
}

View File

@ -1,11 +1,13 @@
package com.blossom.common.base.log;
package com.blossom.common.cache.caffeine;
import com.blossom.common.base.BaseProperties;
import com.blossom.common.base.log.DynamicLogRepository;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.blossom.common.base.BaseProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.stereotype.Component;
@ -21,7 +23,7 @@ import java.util.concurrent.TimeUnit;
*/
@Slf4j
@Component("dynamicLogRepositoryCaffeine")
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
@ConditionalOnProperty(value = "project.cache.type", havingValue = "caffeine")
public class DynamicLogRepositoryCaffeine implements DynamicLogRepository {
private final LoggingSystem loggingSystem;
@ -29,7 +31,7 @@ public class DynamicLogRepositoryCaffeine implements DynamicLogRepository {
private final ScheduledExecutorService clearUpScheduled = Executors.newScheduledThreadPool(1);
public DynamicLogRepositoryCaffeine(LoggingSystem loggingSystem, BaseProperties properties) {
log.debug("[ BASE] 日志级别存储 : Caffeine, 配置持续时长[{}ms], 刷新间隔[{}ms]",
log.info("[ BASE] 日志级别存储 : Caffeine, 配置持续时长[{}ms], 刷新间隔[{}ms]",
properties.getLog().getDuration(), properties.getLog().getRestoreDuration());
cache = Caffeine.newBuilder()
.initialCapacity(100)

View File

@ -7,12 +7,12 @@ import com.blossom.common.base.util.json.JsonUtil;
import com.blossom.common.base.util.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@ -32,7 +32,7 @@ import java.util.concurrent.TimeUnit;
*/
@Slf4j
@Component("DynamicLogRepositoryRedis")
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(value = "project.cache.type", havingValue = "redis")
public class DynamicLogRepositoryRedis implements DynamicLogRepository {
private final StringRedisTemplate redisTemplate;

View File

@ -3,14 +3,14 @@ package com.blossom.common.cache.redis;
import com.blossom.common.base.util.json.JsonUtil;
import com.blossom.common.cache.CommonCacheProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
@ -26,7 +26,7 @@ import java.util.Map;
*/
@Slf4j
@EnableCaching
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(value = "project.cache.type", havingValue = "redis")
public class RedisCacheConfig {
private static final Integer DEFAULT_TIME_OUT = 60 * 60 * 4;

View File

@ -0,0 +1,67 @@
package com.blossom.common.cache.redis;
import com.blossom.common.base.util.json.JsonUtil;
import com.blossom.common.cache.CacheService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* Redis 缓存操作
* @param <K>
* @param <V>
*/
@Component
@Slf4j
@ConditionalOnProperty(value = "project.cache.type", havingValue = "redis")
public class RedisCacheService<K, V> implements CacheService<K, V> {
public RedisCacheService() {
log.info("[ CACHE] 业务缓存使用 : Redis");
}
@Autowired
private RedisTemplate<K, String> redisTemplate;
@Override
public void put(K key, V value) {
redisTemplate.opsForValue().set(key, JsonUtil.toJson(value));
}
@Override
public void put(K key, V value, Long expirationInSeconds) {
redisTemplate.opsForValue().set(key, JsonUtil.toJson(value));
redisTemplate.expire(key, expirationInSeconds, TimeUnit.SECONDS);
}
@Override
public Optional<V> get(K key, Class<V> valueType) {
return Optional.ofNullable(JsonUtil.toObj(redisTemplate.opsForValue().get(key), valueType));
}
@Override
public void remove(K key) {
redisTemplate.delete(key);
}
@Override
public void removeAll() {
redisTemplate.execute((RedisCallback<Object>) connection -> {
connection.flushDb();
return null;
});
}
}

View File

@ -1,5 +1,6 @@
package com.blossom.common.cache.redis;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@ -10,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@ConfigurationProperties(prefix = "project.redis")
@ConditionalOnProperty(value = "project.base.enabled.redis", havingValue = "true")
public class RedisProperties {
}

View File

@ -1,8 +1,11 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.blossom.common.cache.CommonCacheProperties,\
com.blossom.common.cache.caffeine.CaffeineCacheConfig,\
com.blossom.common.cache.caffeine.CaffeineCacheService,\
com.blossom.common.cache.redis.RedisProperties,\
com.blossom.common.cache.redis.RedisCacheConfig,\
com.blossom.common.cache.redis.RedisCacheService,\
com.blossom.common.cache.redis.RedisConfiguration,\
com.blossom.common.cache.redis.DynamicLogRepositoryRedis
com.blossom.common.cache.redis.DynamicLogRepositoryRedis,\
com.blossom.common.cache.caffeine.DynamicLogRepositoryCaffeine