Add custom temporary store support

pull/137/head
johnniang 2019-03-18 21:16:19 +08:00
parent 58e5da4ac5
commit 5771b42c25
6 changed files with 231 additions and 0 deletions

View File

@ -48,6 +48,7 @@
<qiniu-java-sdk.version>7.2.18</qiniu-java-sdk.version> <qiniu-java-sdk.version>7.2.18</qiniu-java-sdk.version>
<thumbnailator.version>0.4.8</thumbnailator.version> <thumbnailator.version>0.4.8</thumbnailator.version>
<commonmark.version>0.12.1</commonmark.version> <commonmark.version>0.12.1</commonmark.version>
<commons-lang3.version>3.8.1</commons-lang3.version>
</properties> </properties>
<dependencies> <dependencies>
@ -213,6 +214,13 @@
<version>2.9.2</version> <version>2.9.2</version>
</dependency> </dependency>
<!-- Apache commons lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View File

@ -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 <K> cache key type
* @param <V> cache value type
* @author johnniang
* *
*/
public interface CacheStore<K, V> {
/**
* Gets by cache key.
*
* @param key must not be null
* @return cache value
*/
@NonNull
Optional<V> 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);
}

View File

@ -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<T> {
/**
* Cache key.
*/
private String key;
/**
* Cache data
*/
private T data;
/**
* Expired time.
*/
private Date expireAt;
/**
* Create time.
*/
private Date createAt;
}

View File

@ -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<String, String> cacheContainer = new ConcurrentHashMap<>();
@Override
public Optional<String> 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);
}
}

View File

@ -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<String, String> {
public <T> 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<T> 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 <T> Optional<T> getForWechat(@NonNull String key, @NonNull Class<T> 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);
}
});
}
}

View File

@ -1,5 +1,7 @@
package cc.ryanc.halo.config; 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.config.properties.HaloProperties;
import cc.ryanc.halo.filter.CorsFilter; import cc.ryanc.halo.filter.CorsFilter;
import cc.ryanc.halo.filter.LogFilter; 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.AdminAuthenticationFailureHandler;
import cc.ryanc.halo.security.handler.DefaultAuthenticationFailureHandler; import cc.ryanc.halo.security.handler.DefaultAuthenticationFailureHandler;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -23,6 +26,12 @@ import org.springframework.core.Ordered;
@EnableConfigurationProperties(HaloProperties.class) @EnableConfigurationProperties(HaloProperties.class)
public class HaloConfiguration { public class HaloConfiguration {
@Bean
@ConditionalOnMissingBean
public StringCacheStore stringCacheStore() {
return new InMemoryCacheStore();
}
/** /**
* Creates a CorsFilter. * Creates a CorsFilter.
* *