Reference: https://mp.weixin.qq.com/s/2vtIDRChxwUjIBqOgDiIZA

With local locks (focus of this article)

Principle:
Use the ConcurrentHashMap concurrent container putIfAbsent method, and ScheduledThreadPoolExecutor timing task, also can use guava cache mechanism, Gauva also allows key generation with caches
Content-MD5
Content-md5 refers to the MD5 value of the Body. MD5 is calculated only when the Body is not a Form Form. MD5 is directly encrypted for parameters and parameter names
MD5 is approximately unique in a range of classes that are considered unique and of course sufficient for low concurrency
The local lock applies only to applications in single-machine deployment.

1. Configuration Comments

import java.lang.annotation.*; @target (ElementType.METHOD) @Retention(RetentionPolicy.runtime) @Documented Public @interface Resubmit {/** * Delay time How long after the delay can you submit * * @ againreturn Time unit is one second
     */
    int delaySeconds() default 20;
}Copy the code

② Instantiate the lock

import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; Slf4j public Final class ResubmitLock {private static final ConcurrentHashMap<String, Object> LOCK_CACHE = new ConcurrentHashMap<>(200); private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(5, new ThreadPoolExecutor.DiscardPolicy()); // private static final Cache<String, Object> CACHES = cacheBuilder.newBuilder () // Max cache 100 //.maximumSize(1000) // Set write cache to expire after 5 seconds //.expireAfterWrite(5, TimeUnit.SECONDS) // .build(); privateResubmitLock() {} /** * static inner class singleton ** @return
     */
    private static class SingletonInstance {
        private static final ResubmitLock INSTANCE = new ResubmitLock();
    }

    public static ResubmitLock getInstance() {
        return SingletonInstance.INSTANCE;
    }


    public static String handleKey(String param) {
        return DigestUtils.md5Hex(param == null ? "": param); } /** * putIfAbsent is the thread safe atomic operation ** @param key * @param value * @return
     */
    public boolean lock(final String key, Object value) {
        returnObjects.isNull(LOCK_CACHE.putIfAbsent(key, value)); } /** * Delay release lock to control repeated commit within a short period ** @param lock whether to unlock @param key * @param delaySeconds Delay time */ public void unLock(final boolean lock, final String key, final int delaySeconds) {if(lock) { EXECUTOR.schedule(() -> { LOCK_CACHE.remove(key); }, delaySeconds, TimeUnit.SECONDS); }}}Copy the code

(3) the AOP aspects

import com.alibaba.fastjson.JSONObject; import com.cn.xxx.common.annotation.Resubmit; import com.cn.xxx.common.annotation.impl.ResubmitLock; import com.cn.xxx.common.dto.RequestDTO; import com.cn.xxx.common.dto.ResponseDTO; import com.cn.xxx.common.enums.ResponseCode; import lombok.extern.log4j.Log4j; 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.stereotype.Component; import java.lang.reflect.Method; /** * @className RequestDataAspect * @description * @author lijing * @date 2019/05/16 17:05 **/ @log4j@aspect @Component public class ResubmitDataAspect { private final static String DATA ="data";
    private final static Object PRESENT = new Object();

    @Around("@annotation(com.cn.xxx.common.annotation.Resubmit)") public Object handleResubmit(ProceedingJoinPoint joinPoint) throws Throwable { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); Resubmit annotation = method.getannotation (resubmit.class); int delaySeconds = annotation.delaySeconds(); Object[] pointArgs = joinPoint.getArgs(); String key =""; Object firstParam = pointArgs[0];if(firstParam instanceof RequestDTO) {JSONObject RequestDTO = jsonObject.parseObject (firstparam.toString); JSONObject data = JSONObject.parseObject(requestDTO.getString(DATA));if(data ! = null) { StringBuffer sb = new StringBuffer(); data.forEach((k, v) -> { sb.append(v); }); Key = resubmitlock. handleKey(sb.tostring ()); }} // execute the lock Boolean lock =false; Try {// Set unlock key lock = resubmitLock.getInstance ().lock(key, PRESENT);if(lock) {// releasereturn joinPoint.proceed();
            } else{// Response repeated commit exceptionreturnnew ResponseDTO<>(ResponseCode.REPEAT_SUBMIT_OPERATION_EXCEPTION); }} finally {// Set the unLock key and unLock time resubmitLock. getInstance().unlock (lock, key, delaySeconds); }}}Copy the code

Annotate the use case

@ApiOperation(value = "Save my post interface", notes = "Save my post interface")
    @PostMapping("/posts/save")
    @Resubmit(delaySeconds = 10)
    public ResponseDTO<BaseResponseDataDTO> saveBbsPosts(@RequestBody @Validated RequestDTO<BbsPostsRequestDTO> requestDto) {
        return bbsPostsBizService.saveBbsPosts(requestDto);
    }Copy the code
So that’s the local lock for idempotent commits using Content-MD5 encryption so as long as the parameters don’t change, the encryption value of the parameters doesn’t change, and the key exists, the commit is blocked
Of course, some other signature verification can be used to generate a fixed signature during a certain submission and submit it to the back end. The unified signature can be used as the verification token of each submission and processed in the cache according to the back end resolution.