作者 朱兆平

重复提交判定

package com.tianbo.analysis.annotation;
import com.alibaba.fastjson.JSONObject;
import com.tianbo.analysis.controller.bean.ResponseReason;
import com.tianbo.analysis.model.ResultJson;
import com.tianbo.analysis.tools.ResubmitLock;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@Aspect
@Component
public class ReSubmitAspect {
private final static String DATA = "data";
private final static Object PRESENT = new Object();
@Around("@annotation(com.tianbo.analysis.annotation.ReSubmitCheck)")
public Object handleResubmit(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//获取注解信息
ReSubmitCheck annotation = method.getAnnotation(ReSubmitCheck.class);
int delaySeconds = annotation.delaySeconds();
Object[] pointArgs = joinPoint.getArgs();
String key = "";
//获取第一个参数
Object firstParam = pointArgs[0];
//解析参数
if (firstParam != null) {
//生成加密参数 使用了content_MD5的加密方式
key = ResubmitLock.handleKey(JSONObject.toJSONString(firstParam));
}
//执行锁
boolean lock = false;
try {
//设置解锁key
lock = ResubmitLock.getInstance().lock(key, PRESENT);
if (lock) {
//放行
return joinPoint.proceed();
} else {
//响应重复提交异常
return new ResultJson(ResponseReason.REAPET_SUBMIT_ERR);
}
} finally {
//设置解锁key和解锁时间
ResubmitLock.getInstance().unLock(lock, key, delaySeconds);
}
}
}
... ...
package com.tianbo.analysis.annotation;
import java.lang.annotation.*;
/**
* @author mrz
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReSubmitCheck {
/**
* 延时时间 在延时多久后可以再次提交
*
* @return Time unit is one second
*/
int delaySeconds() default 5;
}
... ...
package com.tianbo.analysis.controller;
import com.tianbo.analysis.annotation.ReSubmitCheck;
import com.tianbo.analysis.annotation.UserPermissionCheck;
import com.tianbo.analysis.dao.SENDPLANMapper;
import com.tianbo.analysis.model.ResultJson;
import com.tianbo.analysis.model.SENDPLAN;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/sendplan")
public class SendPlanController {
@Resource
SENDPLANMapper sendplanMapper;
@UserPermissionCheck
@PostMapping("add")
public ResultJson add(@RequestBody SENDPLAN sendplan,@CookieValue("username") String username,@CookieValue("userid") String userid){
return sendplanMapper.insertSelective(sendplan)>0? new ResultJson("200","新增成功") : new ResultJson("400","新增失败");
}
@UserPermissionCheck
@PostMapping("del")
public ResultJson del(@RequestBody SENDPLAN sendplan,@CookieValue("username") String username,@CookieValue("userid") String userid){
return sendplanMapper.deleteByPrimaryKey(sendplan.getCarrier())>0? new ResultJson("200","新增成功") : new ResultJson("400","新增失败");
}
@ReSubmitCheck
@GetMapping("list")
public ResultJson<List<SENDPLAN>> list(@RequestParam(value = "carrier",required = false) String carrier){
List result = sendplanMapper.list(carrier);
return new ResultJson("200","查询成功",result);
}
}
... ...
... ... @@ -6,6 +6,7 @@ public enum ResponseReason {
MT8204_EDIT_ERR("8204401","直接改配申请更新失败"),
MT8204_DEL_ERR("8204402","直接改配申请更新失败"),
MT8204_SEND_ERR("8204404","直接改配发送失败"),
REAPET_SUBMIT_ERR("400001","请勿重复提交,请于5秒后尝试"),
MT8204_BATCH_SUCCESS("200","直接改配批量申请操作成功");
private String code;
private String msg;
... ...
package com.tianbo.analysis.dao;
import com.tianbo.analysis.model.SENDPLAN;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface SENDPLANMapper {
int deleteByPrimaryKey(String carrier);
... ... @@ -8,4 +11,6 @@ public interface SENDPLANMapper {
int insert(SENDPLAN record);
int insertSelective(SENDPLAN record);
List<SENDPLAN> list(@Param("carrier") String carrier);
}
... ...
package com.tianbo.analysis.tools;
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;
/**
*
* @author mrz
* 重复提交判定锁
*/
@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()
// 最大缓存 100 个
// .maximumSize(1000)
// 设置写缓存后 5 秒钟过期
// .expireAfterWrite(5, TimeUnit.SECONDS)
// .build();
private ResubmitLock() {
}
/**
* 静态内部类 单例模式
*
* @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 是原子操作保证线程安全
*
* @param key 对应的key
* @param value
* @return
*/
public boolean lock(final String key, Object value) {
return Objects.isNull(LOCK_CACHE.putIfAbsent(key, value));
}
/**
* 延时释放锁 用以控制短时间内的重复提交
*
* @param lock 是否需要解锁
* @param key 对应的key
* @param delaySeconds 延时时间
*/
public void unLock(final boolean lock, final String key, final int delaySeconds) {
if (lock) {
EXECUTOR.schedule(() -> {
LOCK_CACHE.remove(key);
}, delaySeconds, TimeUnit.SECONDS);
}
}
}
... ...
... ... @@ -446,7 +446,8 @@ GROUP BY
createdate,
status AS status,
receiptinformation,
'MT1201' AS messagetype
'MT1201' AS messagetype,
PRODUCTNAME
FROM
originmanifestmaster UNION ALL
SELECT
... ... @@ -462,7 +463,8 @@ GROUP BY
createdate,
status,
receiptinformation,
talltype AS messagetype
talltype AS messagetype,
PRODUCTNAME
FROM
tallymaster
WHERE
... ... @@ -494,7 +496,8 @@ GROUP BY
createdate,
status,
receiption as receiptinformation,
'MT1201-1' as messagetype
'MT1201-1' as messagetype,
PRODUCTNAME
from originmanifestsecondary
where originmanifestmasterautoid= #{autoid}
UNION ALL
... ... @@ -506,7 +509,8 @@ GROUP BY
createdate,
status,
receiptinformation,
'MT5201-1' as messagetype
'MT5201-1' as messagetype,
PRODUCTNAME
from tallysecondary
where tallymasterid= #{autoid}
</select>
... ...
... ... @@ -590,7 +590,8 @@ select * from(
status AS status,
concat( receiptinformation, arrived_ahead ) AS receiptinformation,
'MT2201' AS messagetype,
customsstatus
customsstatus,
PRODUCTNAME
FROM
preparemaster
<include refid="TreeCondition" />
... ... @@ -609,7 +610,8 @@ select * from(
status,
receiptinformation,
'MT3201' AS messagetype,
arrived_ahead AS customsstatus
arrived_ahead AS customsstatus,
PRODUCTNAME
FROM
arrivedmaster
<include refid="TreeCondition" />
... ... @@ -628,7 +630,8 @@ select * from(
status,
receiption AS receiptinformation,
'MT4201' AS messagetype,
'' AS customsstatus
'' AS customsstatus,
PRODUCTNAME
FROM
departuresloading
<where>
... ... @@ -660,7 +663,8 @@ select * from(
status,
receiptinformation,
talltype AS messagetype,
'' AS customsstatus
'' AS customsstatus,
PRODUCTNAME
FROM
tallymaster
<where>
... ... @@ -694,7 +698,8 @@ select * from(
status,
receiptinformation,
'MT2201-1' as messagetype,
customsstatus
customsstatus,
PRODUCTNAME
from preparesecondary
where PREPAREMASTERID= #{autoid}
... ... @@ -709,7 +714,8 @@ select * from(
status,
receiption as receiptinformation,
'MT3201-1' as messagetype,
'' as customsstatus
'' as customsstatus,
PRODUCTNAME
from arrivedsecondary
where ARRIVEDMASTERID= #{autoid}
UNION ALL
... ... @@ -723,7 +729,8 @@ select * from(
status,
receiptinformation,
'MT5202-1' as messagetype,
'' as customsstatus
'' as customsstatus,
PRODUCTNAME
from tallysecondary
where tallymasterID= #{autoid}
</select>
... ...
... ... @@ -25,4 +25,11 @@
</if>
</trim>
</insert>
<select id="list" parameterType="java.lang.String" resultMap="BaseResultMap">
select CARRIER from CGONMS.SENDPLAN
<if test="carrier != null and carrier != ''" >
where CARRIER = #{carrier,jdbcType=VARCHAR}
</if>
</select>
</mapper>
... ...