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
|
||||
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.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||
|
||||
|
@ -38,32 +38,30 @@ public class InMemoryCacheStore extends StringCacheStore {
|
|||
}
|
||||
|
||||
@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.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||
|
||||
log.debug("Preparing to put key: [{}], value: [{}]", key, cacheWrapper);
|
||||
|
||||
// Put the cache wrapper
|
||||
CacheWrapper<String> putCacheWrapper = cacheContainer.putIfAbsent(key, cacheWrapper);
|
||||
// Get the value before
|
||||
Optional<String> valueOptional = get(key);
|
||||
|
||||
if (putCacheWrapper == null) {
|
||||
putCacheWrapper = cacheWrapper;
|
||||
}
|
||||
|
||||
boolean isEqual = cacheWrapper.equals(putCacheWrapper);
|
||||
|
||||
if (isEqual) {
|
||||
log.debug("Put successfully");
|
||||
} else {
|
||||
if (valueOptional.isPresent()) {
|
||||
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
|
||||
public void delete(String key) {
|
||||
public synchronized void delete(String key) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
|
||||
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();
|
||||
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) {
|
||||
emptyNames.add(propertyName);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.UserOutputDTO;
|
||||
import cc.ryanc.halo.model.enums.BlogProperties;
|
||||
|
@ -10,7 +11,6 @@ import io.swagger.annotations.ApiOperation;
|
|||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
|
@ -57,7 +57,8 @@ public class AdminController {
|
|||
}
|
||||
|
||||
@PostMapping("login")
|
||||
@ApiOperation("Logins with session")
|
||||
@ApiOperation("Login with session")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public UserOutputDTO login(@Valid @RequestBody LoginParam loginParam, HttpServletRequest request) {
|
||||
return new UserOutputDTO().convertFrom(userService.login(loginParam.getUsername(), loginParam.getPassword(), request.getSession()));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue