From 5771b42c251f727529e13386556fe7ed800dd20b Mon Sep 17 00:00:00 2001 From: johnniang Date: Mon, 18 Mar 2019 21:16:19 +0800 Subject: [PATCH] Add custom temporary store support --- pom.xml | 8 ++ .../java/cc/ryanc/halo/cache/CacheStore.java | 44 ++++++++ .../cc/ryanc/halo/cache/CacheWrapper.java | 38 +++++++ .../ryanc/halo/cache/InMemoryCacheStore.java | 31 ++++++ .../cc/ryanc/halo/cache/StringCacheStore.java | 101 ++++++++++++++++++ .../ryanc/halo/config/HaloConfiguration.java | 9 ++ 6 files changed, 231 insertions(+) create mode 100644 src/main/java/cc/ryanc/halo/cache/CacheStore.java create mode 100644 src/main/java/cc/ryanc/halo/cache/CacheWrapper.java create mode 100644 src/main/java/cc/ryanc/halo/cache/InMemoryCacheStore.java create mode 100644 src/main/java/cc/ryanc/halo/cache/StringCacheStore.java diff --git a/pom.xml b/pom.xml index f5f5fdda2..42152d9e7 100755 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ 7.2.18 0.4.8 0.12.1 + 3.8.1 @@ -213,6 +214,13 @@ 2.9.2 + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + diff --git a/src/main/java/cc/ryanc/halo/cache/CacheStore.java b/src/main/java/cc/ryanc/halo/cache/CacheStore.java new file mode 100644 index 000000000..264b56f02 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/cache/CacheStore.java @@ -0,0 +1,44 @@ +package cc.ryanc.halo.cache; + +import org.springframework.lang.NonNull; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * Cache store interface. + * + * @param cache key type + * @param cache value type + * @author johnniang + * * + */ +public interface CacheStore { + + /** + * Gets by cache key. + * + * @param key must not be null + * @return cache value + */ + @NonNull + Optional get(@NonNull K key); + + /** + * Puts a cache. + * + * @param key cache key must not be null + * @param value cache value must not be null + * @param timeout the key expiration must not be less than 0 + * @param timeUnit timeout unit + */ + void put(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit); + + /** + * Delete a key. + * + * @param key cache key must not be null + */ + void delete(@NonNull K key); + +} diff --git a/src/main/java/cc/ryanc/halo/cache/CacheWrapper.java b/src/main/java/cc/ryanc/halo/cache/CacheWrapper.java new file mode 100644 index 000000000..4e6de7066 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/cache/CacheWrapper.java @@ -0,0 +1,38 @@ +package cc.ryanc.halo.cache; + +import lombok.*; + +import java.util.Date; + +/** + * Cache wrapper. + * + * @author johnniang + */ +@Data +@EqualsAndHashCode +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class CacheWrapper { + + /** + * Cache key. + */ + private String key; + + /** + * Cache data + */ + private T data; + + /** + * Expired time. + */ + private Date expireAt; + + /** + * Create time. + */ + private Date createAt; +} diff --git a/src/main/java/cc/ryanc/halo/cache/InMemoryCacheStore.java b/src/main/java/cc/ryanc/halo/cache/InMemoryCacheStore.java new file mode 100644 index 000000000..aae592fbd --- /dev/null +++ b/src/main/java/cc/ryanc/halo/cache/InMemoryCacheStore.java @@ -0,0 +1,31 @@ +package cc.ryanc.halo.cache; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * In-memory cache store. + * + * @author johnniang + */ +public class InMemoryCacheStore extends StringCacheStore { + + private final static ConcurrentHashMap cacheContainer = new ConcurrentHashMap<>(); + + @Override + public Optional get(String key) { + return Optional.ofNullable(cacheContainer.get(key)); + } + + @Override + public void put(String key, String value, long timeout, TimeUnit timeUnit) { + cacheContainer.put(key, value); + } + + @Override + public void delete(String key) { + // TODO Consider to delete the cache periodic + cacheContainer.remove(key); + } +} diff --git a/src/main/java/cc/ryanc/halo/cache/StringCacheStore.java b/src/main/java/cc/ryanc/halo/cache/StringCacheStore.java new file mode 100644 index 000000000..196bffca5 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/cache/StringCacheStore.java @@ -0,0 +1,101 @@ +package cc.ryanc.halo.cache; + +import cc.ryanc.halo.exception.ServiceException; +import cc.ryanc.halo.utils.JsonUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * Wechat cache store. + * + * @author johnniang + */ +@Slf4j +public abstract class StringCacheStore implements CacheStore { + + public void putForWechat(String key, T value, long timeout, TimeUnit timeUnit) { + Assert.hasText(key, "Cache key must not be blank"); + Assert.notNull(value, "Cache value must not be null"); + Assert.isTrue(timeout > 0, "Timeout must not be less than 0"); + Assert.notNull(timeUnit, "Time unit must not be null"); + + // Convert to second + Long seconds = timeUnit.toSeconds(timeout); + + // Round the seconds + if (seconds == 0) { + seconds = 1L; + } + + Date now = new Date(); + + // Calculate expire at + Date expireAt = DateUtils.addSeconds(now, seconds.intValue()); + + // Build cache wrapper + CacheWrapper wrapper = new CacheWrapper<>(); + wrapper.setCreateAt(now); + wrapper.setExpireAt(expireAt); + wrapper.setKey(key); + wrapper.setData(value); + + try { + // Convert wrapper to json + String valueJson = JsonUtils.objectToJson(wrapper); + // Put the the value json to cache store + put(key, valueJson, timeout, timeUnit); + } catch (JsonProcessingException e) { + throw new ServiceException("Failed to convert object to json", e).setErrorData(wrapper); + } + } + + @SuppressWarnings("unchecked") + @NonNull + public Optional getForWechat(@NonNull String key, @NonNull Class type) { + Assert.hasText(key, "Cache key must not be blank"); + Assert.notNull(type, "Cache type must not be null"); + + return get(key).map(value -> { + try { + CacheWrapper cacheWrapper = JsonUtils.jsonToObject(value, CacheWrapper.class); + + if (cacheWrapper == null) { + log.error("Cache wrapper is null, key: [{}]", key); + return null; + } + + log.debug("Cache wrapper: [{}]", cacheWrapper); + + Date now = new Date(); + + if (cacheWrapper.getExpireAt().before(now)) { + // Expired then delete it + log.debug("Cache key: [{}] has been expired", key); + + delete(key); + return null; + } + + Object data = cacheWrapper.getData(); + + if (data != null && data.getClass().isAssignableFrom(type)) { + return (T) data; + } + + log.error("Data type: [{}], but specified type: [{}]", data == null ? null : data.getClass(), type); + throw new ServiceException("Cache value type is mismatched with the specified type"); + } catch (IOException e) { + throw new ServiceException("Failed to convert from json to object", e).setErrorData(value); + } + }); + } + +} diff --git a/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java b/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java index 541c88331..6e8a52e44 100644 --- a/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java +++ b/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java @@ -1,5 +1,7 @@ package cc.ryanc.halo.config; +import cc.ryanc.halo.cache.InMemoryCacheStore; +import cc.ryanc.halo.cache.StringCacheStore; import cc.ryanc.halo.config.properties.HaloProperties; import cc.ryanc.halo.filter.CorsFilter; import cc.ryanc.halo.filter.LogFilter; @@ -8,6 +10,7 @@ import cc.ryanc.halo.security.filter.ApiAuthenticationFilter; import cc.ryanc.halo.security.handler.AdminAuthenticationFailureHandler; import cc.ryanc.halo.security.handler.DefaultAuthenticationFailureHandler; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -23,6 +26,12 @@ import org.springframework.core.Ordered; @EnableConfigurationProperties(HaloProperties.class) public class HaloConfiguration { + @Bean + @ConditionalOnMissingBean + public StringCacheStore stringCacheStore() { + return new InMemoryCacheStore(); + } + /** * Creates a CorsFilter. *