mirror of https://github.com/halo-dev/halo
Complete CacheLock support
parent
8d04bec3b2
commit
11f28f11da
|
@ -27,7 +27,7 @@ public class InMemoryCacheStore extends StringCacheStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void putInternal(String key, CacheWrapper<String> cacheWrapper) {
|
synchronized void putInternal(String key, CacheWrapper<String> cacheWrapper) {
|
||||||
Assert.hasText(key, "Cache key must not be blank");
|
Assert.hasText(key, "Cache key must not be blank");
|
||||||
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||||
|
|
||||||
|
@ -38,32 +38,30 @@ public class InMemoryCacheStore extends StringCacheStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Boolean putInternalIfAbsent(String key, CacheWrapper<String> cacheWrapper) {
|
synchronized Boolean putInternalIfAbsent(String key, CacheWrapper<String> cacheWrapper) {
|
||||||
Assert.hasText(key, "Cache key must not be blank");
|
Assert.hasText(key, "Cache key must not be blank");
|
||||||
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||||
|
|
||||||
log.debug("Preparing to put key: [{}], value: [{}]", key, cacheWrapper);
|
log.debug("Preparing to put key: [{}], value: [{}]", key, cacheWrapper);
|
||||||
|
|
||||||
// Put the cache wrapper
|
// Get the value before
|
||||||
CacheWrapper<String> putCacheWrapper = cacheContainer.putIfAbsent(key, cacheWrapper);
|
Optional<String> valueOptional = get(key);
|
||||||
|
|
||||||
if (putCacheWrapper == null) {
|
if (valueOptional.isPresent()) {
|
||||||
putCacheWrapper = cacheWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isEqual = cacheWrapper.equals(putCacheWrapper);
|
|
||||||
|
|
||||||
if (isEqual) {
|
|
||||||
log.debug("Put successfully");
|
|
||||||
} else {
|
|
||||||
log.warn("Failed to put the cache, because the key: [{}] has been present already", key);
|
log.warn("Failed to put the cache, because the key: [{}] has been present already", key);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isEqual;
|
// Put the cache wrapper
|
||||||
|
putInternal(key, cacheWrapper);
|
||||||
|
|
||||||
|
log.debug("Put successfully");
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String key) {
|
public synchronized void delete(String key) {
|
||||||
Assert.hasText(key, "Cache key must not be blank");
|
Assert.hasText(key, "Cache key must not be blank");
|
||||||
|
|
||||||
cacheContainer.remove(key);
|
cacheContainer.remove(key);
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package cc.ryanc.halo.cache.lock;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache lock annotation.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 3/28/19
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
public @interface CacheLock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache prefix, default is ""
|
||||||
|
*
|
||||||
|
* @return cache prefix
|
||||||
|
*/
|
||||||
|
@AliasFor("value")
|
||||||
|
String prefix() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of prefix, default is ""
|
||||||
|
*
|
||||||
|
* @return alias of prefix
|
||||||
|
*/
|
||||||
|
@AliasFor("prefix")
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expired time, default is 5.
|
||||||
|
*
|
||||||
|
* @return expired time
|
||||||
|
*/
|
||||||
|
long expired() default 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time unit, default is TimeUnit.SECONDS.
|
||||||
|
*
|
||||||
|
* @return time unit
|
||||||
|
*/
|
||||||
|
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delimiter, default is ':'
|
||||||
|
*
|
||||||
|
* @return delimiter
|
||||||
|
*/
|
||||||
|
String delimiter() default ":";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether delete cache after method invocation.
|
||||||
|
*
|
||||||
|
* @return true if delete cache after method invocation; false otherwise
|
||||||
|
*/
|
||||||
|
boolean autoDelete() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether trace the request info.
|
||||||
|
*
|
||||||
|
* @return true if trace the request info; false otherwise
|
||||||
|
*/
|
||||||
|
boolean traceRequest() default false;
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package cc.ryanc.halo.cache.lock;
|
||||||
|
|
||||||
|
import cc.ryanc.halo.cache.StringCacheStore;
|
||||||
|
import cc.ryanc.halo.exception.FrequentAccessException;
|
||||||
|
import cc.ryanc.halo.exception.ServiceException;
|
||||||
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interceptor for cache lock annotation.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 3/28/19
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Aspect
|
||||||
|
@Configuration
|
||||||
|
public class CacheLockInterceptor {
|
||||||
|
|
||||||
|
private final static String CACHE_LOCK_PREFOX = "cache_lock_";
|
||||||
|
|
||||||
|
private final static String CACHE_LOCK_VALUE = "locked";
|
||||||
|
|
||||||
|
private final StringCacheStore cacheStore;
|
||||||
|
|
||||||
|
private final HttpServletRequest httpServletRequest;
|
||||||
|
|
||||||
|
public CacheLockInterceptor(StringCacheStore cacheStore,
|
||||||
|
HttpServletRequest httpServletRequest) {
|
||||||
|
this.cacheStore = cacheStore;
|
||||||
|
this.httpServletRequest = httpServletRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Around("@annotation(cc.ryanc.halo.cache.lock.CacheLock)")
|
||||||
|
public Object interceptCacheLock(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
|
// Get method signature
|
||||||
|
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
|
||||||
|
log.debug("Starting locking: [{}]", methodSignature.toString());
|
||||||
|
|
||||||
|
// Get cache lock
|
||||||
|
CacheLock cacheLock = methodSignature.getMethod().getAnnotation(CacheLock.class);
|
||||||
|
|
||||||
|
// Build cache lock key
|
||||||
|
String cacheLockKey = buildCacheLockKey(cacheLock, joinPoint);
|
||||||
|
|
||||||
|
log.debug("Built lock key: [{}]", cacheLockKey);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get from cache
|
||||||
|
Boolean cacheResult = cacheStore.putIfAbsent(cacheLockKey, CACHE_LOCK_VALUE, cacheLock.expired(), cacheLock.timeUnit());
|
||||||
|
|
||||||
|
if (cacheResult == null) {
|
||||||
|
throw new ServiceException("Unknown reason of cache " + cacheLockKey).setErrorData(cacheLockKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cacheResult) {
|
||||||
|
throw new FrequentAccessException("Frequent access").setErrorData(cacheLockKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed the method
|
||||||
|
return joinPoint.proceed();
|
||||||
|
} finally {
|
||||||
|
// Delete the cache
|
||||||
|
if (cacheLock.autoDelete()) {
|
||||||
|
cacheStore.delete(cacheLockKey);
|
||||||
|
log.debug("Deleted the cache lock: [{}]", cacheLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildCacheLockKey(@NonNull CacheLock cacheLock, @NonNull ProceedingJoinPoint joinPoint) {
|
||||||
|
Assert.notNull(cacheLock, "Cache lock must not be null");
|
||||||
|
Assert.notNull(joinPoint, "Proceeding join point must not be null");
|
||||||
|
|
||||||
|
// Get the method
|
||||||
|
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
|
||||||
|
// Build the cache lock key
|
||||||
|
StringBuilder cacheKeyBuilder = new StringBuilder(CACHE_LOCK_PREFOX);
|
||||||
|
|
||||||
|
String delimiter = cacheLock.delimiter();
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(cacheLock.prefix())) {
|
||||||
|
cacheKeyBuilder.append(cacheLock.prefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cache lock key building
|
||||||
|
Annotation[][] parameterAnnotations = methodSignature.getMethod().getParameterAnnotations();
|
||||||
|
|
||||||
|
for (int i = 0; i < parameterAnnotations.length; i++) {
|
||||||
|
log.debug("Parameter annotation[{}] = {}", i, parameterAnnotations[i]);
|
||||||
|
|
||||||
|
for (int j = 0; j < parameterAnnotations[i].length; j++) {
|
||||||
|
Annotation annotation = parameterAnnotations[i][j];
|
||||||
|
log.debug("Parameter annotation[{}][{}]: {}", i, j, annotation);
|
||||||
|
if (annotation instanceof CacheParam) {
|
||||||
|
// Get current argument
|
||||||
|
Object arg = joinPoint.getArgs()[i];
|
||||||
|
log.debug("Cache param args: [{}]", arg);
|
||||||
|
|
||||||
|
// Append to the cache key
|
||||||
|
cacheKeyBuilder.append(delimiter).append(arg.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheLock.traceRequest()) {
|
||||||
|
// Append http request info
|
||||||
|
cacheKeyBuilder.append(delimiter).append(ServletUtil.getClientIP(httpServletRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheKeyBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cc.ryanc.halo.cache.lock;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache parameter annotation.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 3/28/19
|
||||||
|
*/
|
||||||
|
@Target(ElementType.PARAMETER)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
public @interface CacheParam {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cc.ryanc.halo.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequent access exception.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 3/28/19
|
||||||
|
*/
|
||||||
|
public class FrequentAccessException extends BadRequestException {
|
||||||
|
|
||||||
|
public FrequentAccessException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrequentAccessException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,7 +123,7 @@ public class BeanUtils {
|
||||||
String propertyName = propertyDescriptor.getName();
|
String propertyName = propertyDescriptor.getName();
|
||||||
Object propertyValue = beanWrapper.getPropertyValue(propertyName);
|
Object propertyValue = beanWrapper.getPropertyValue(propertyName);
|
||||||
|
|
||||||
// if propertye value is equal to null, add it to empty name set
|
// if property value is equal to null, add it to empty name set
|
||||||
if (propertyValue == null) {
|
if (propertyValue == null) {
|
||||||
emptyNames.add(propertyName);
|
emptyNames.add(propertyName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cc.ryanc.halo.web.controller.admin.api;
|
package cc.ryanc.halo.web.controller.admin.api;
|
||||||
|
|
||||||
|
import cc.ryanc.halo.cache.lock.CacheLock;
|
||||||
import cc.ryanc.halo.model.dto.CountOutputDTO;
|
import cc.ryanc.halo.model.dto.CountOutputDTO;
|
||||||
import cc.ryanc.halo.model.dto.UserOutputDTO;
|
import cc.ryanc.halo.model.dto.UserOutputDTO;
|
||||||
import cc.ryanc.halo.model.enums.BlogProperties;
|
import cc.ryanc.halo.model.enums.BlogProperties;
|
||||||
|
@ -10,7 +11,6 @@ import io.swagger.annotations.ApiOperation;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +57,8 @@ public class AdminController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("login")
|
@PostMapping("login")
|
||||||
@ApiOperation("Logins with session")
|
@ApiOperation("Login with session")
|
||||||
|
@CacheLock(autoDelete = false, traceRequest = true)
|
||||||
public UserOutputDTO login(@Valid @RequestBody LoginParam loginParam, HttpServletRequest request) {
|
public UserOutputDTO login(@Valid @RequestBody LoginParam loginParam, HttpServletRequest request) {
|
||||||
return new UserOutputDTO().convertFrom(userService.login(loginParam.getUsername(), loginParam.getPassword(), request.getSession()));
|
return new UserOutputDTO().convertFrom(userService.login(loginParam.getUsername(), loginParam.getPassword(), request.getSession()));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue