技术文档
方法相同入参 AOP 使用说明
Shopee 刊登业务梳理文档
亚马逊刊登相关业务知识
刊登监控设计方案
亚马逊跟卖设计
Fanno 刊登业务梳理文档
日志收集 方案设计
PIB 刊登方案设计
Temu 刊登方案设计
ShopTokenInfo Aop集成MBC方案
香港定时卡刊登任务方案
push-message 、receive-message 省流方案
OZON 刊登方案设计
Lazada视频上传方案
广州同步店铺框架搭建
Shopify翻版
Amazon交接
TikTok 由global模式切换replicate模式调研
tiktok 折扣活动对接方案
Target 初期调研
本文档使用 MrDoc 发布
-
+
首页
方法相同入参 AOP 使用说明
#### 设计初衷 对于刊登场景,目前使用 rocketmq 来实现异步进行刊登的目的,但是在大量刊登场景下发现消息存在重复消费的问题,导致刊登业务上的重复,造成重复数据。 #### 设计方案 改方案对于被调用的方法来实现,如果连续调用同一个方法,在一段时间(可配置)内,但是使用了相同的入参,那么该调用将触发该限制。 于是我们定义了一个注解,```@CallNoRepeat``` 用于标记需要限制重复调用的方法,便于程序检测到。 ```java @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @SuppressWarnings("all") public @interface CallNoRepeat { /** * 时间范围 */ long time() default 1L; /** * 时间单位 */ TimeUnit unit() default TimeUnit.SECONDS; /** * 处理方案 */ FallBack fallback() default FallBack.RETURN; /** * 回调方法:fallback = FallBack.CALLBACK 时必填 */ String callbackMethod() default "defaultCallbackMethod"; /** * 触发 CallNoRepeat 后的方案 */ enum FallBack { // 直接退出 RETURN, // 回调,当使用此配置时,请配置自定义 callbackMethod CALLBACK, // 直接抛出 CallNoRepeatException 异常 EXCEPTION } } ``` 当然还少不了使用时对该功能的一些配置,实现类如下: ```java @Getter @Setter @ConfigurationProperties(prefix = "mdc.biz.method-call-no-repeat") public class MdcMethodCallNoRepeatProperties { /** * 是否启用 */ private boolean enable = false; /** * 平台 */ private String platform = "default"; /** * 对应redis template bean */ private String redisTemplateBean = "redisTemplate"; } ``` 当然,核心还是一个 ```AOP``` 来实现的对方法的拦截处理,所以 ```AOP``` 如下: ```java @Slf4j @Aspect @Order(200) @RequiredArgsConstructor @SuppressWarnings("all") public class MethodCallNoRepeatAop { private final RedisUtils redisUtils; private final MdcMethodCallNoRepeatProperties methodCallNoRepeatProperties; @Pointcut(value = "@annotation(com.mabang.product.annotation.CallNoRepeat)") public void pointCut() { } @Around(value = "pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); if (args == null || args.length <= 0) { log.warn("被去重方法 {} 没有入参,已忽略拦截。", method.getName()); return joinPoint.proceed(); } String flag = JSONObject.toJSONString(args); String key = DigestUtils.md5Hex(flag); // 判断是否重复消费 if (!checkRepeat(method, key)) { log.error("被去重方法 {} 入参重复,已拦截。入参如下:{} ", method.getName(), flag); fallback(joinPoint, method, key); return null; } return joinPoint.proceed(); } /** * fallback 处理 * * @param joinPoint * @param method * @param key */ private void fallback(ProceedingJoinPoint joinPoint, Method method, String key) { CallNoRepeat callNoRepeat = method.getAnnotation(CallNoRepeat.class); switch (callNoRepeat.fallback()) { case RETURN: return; case CALLBACK: handleCallBack(joinPoint, method, callNoRepeat, key); break; case EXCEPTION: throwCallNoRepeatException(key); } } /** * 判断刊登消息是否重复消费 * * @param method 被切入的方法 * @param flag 标识 用于检查相同入参的重复性调用 * @return true 为正常消费 false 为重复消费 */ private boolean checkRepeat(Method method, String flag) { CallNoRepeat callNoRepeat = method.getAnnotation(CallNoRepeat.class); String key = methodCallNoRepeatProperties.getPlatform() + ":" + method.getName() + ":" + flag; if (redisUtils.get(key) != null) { return false; } return redisUtils.setIfAbsent(key, "EXIST", callNoRepeat.time(), callNoRepeat.unit()); } /** * 执行 callback 方法 * * @param joinPoint * @param method * @param callNoRepeat * @param key */ private void handleCallBack(ProceedingJoinPoint joinPoint, Method method, CallNoRepeat callNoRepeat, String key) { String callbackMethodName = callNoRepeat.callbackMethod(); Method callbackMethod = findCallbackMethodFromMethodClass(method, callbackMethodName, callNoRepeat, key); if (null != callbackMethod) { try { callbackMethod.setAccessible(true); callbackMethod.invoke(joinPoint.getTarget(), joinPoint.getArgs()); } catch (Exception e) { log.error("触发 CallNoRepeat 限制时,自定义 <{}> callback 方法执行异常:{}", callbackMethodName, ExceptionUtils.getMessage(e) + ":" + e.getMessage()); } } else { defaultCallBackMethod(method); } } /** * 执行抛出 CallNoRepeatException * * @param key */ private void throwCallNoRepeatException(String key) { throwCallNoRepeatException(key, "触发 CallNoRepeat ,请等待一段时间后重试。"); } private Method findCallbackMethodFromMethodClass(Method method, String callbackMethodName, CallNoRepeat callNoRepeat, String key) { Class<?> clazz = method.getDeclaringClass(); try { return clazz.getDeclaredMethod(callbackMethodName, method.getParameterTypes()); } catch (NoSuchMethodException e) { log.error("触发 CallNoRepeat 限制时,未找到自定义 <{}> callback 方法。", callbackMethodName); throwCallNoRepeatException(key, "触发 CallNoRepeat 限制时,未找到自定义 <" + callbackMethodName + "> callback 方法。"); } return null; } private static void defaultCallBackMethod(Method method) { log.warn("方法 <{}> 触发 CallNoRepeat 限制,进入默认 callback 。 ", method.getName()); } private void throwCallNoRepeatException(String key, String message) { long expire = redisUtils.getExpire(key); expire = expire < 0 ? 0 : expire; throw new CallNoRepeatException((expire + 1) * 1000l, message); } } ``` 触发限制时处理方案抛出异常,所以我们定义了一个 ```CallNoRepeatException``` 异常: ```java public class CallNoRepeatException extends BizException { private long expire; public CallNoRepeatException(long expire, String errorMsg) { super(500, errorMsg); this.expire = expire; } public long getExpire() { return expire; } } ``` #### 使用方式 1、导入相关依赖 ```xml <dependency> <groupId>com.mabang</groupId> <artifactId>mc-base-util</artifactId> <version>${project.version}</version> </dependency> ``` 2、开启 ```CallNoRepeat``` 功能。(未开启则不起作用,后续配置将无效) ```yaml mdc: biz: method-call-no-repeat: enable: true platform: wish # 使用平台 redisTemplateBean: callNoRepeatTemplete ``` 3、配置业务```Redis``` 。 ```yaml mdc: biz: method-call-no-repeat: enable: true platform: wish # 使用平台 redis-template-bean: callNoRepeatTemplete redis: enable: true auto-write: true primary: defaultTemplete servers: callNoRepeatTemplete: host: redis-orderdetail.mabangerp.com port: 6379 password: vO0fMXbUkeKRq4hk timeout: 10000 database: 11 ``` 4、配置完了,程序如何使用呢?我们默认定义了三种 ```fallback``` 方案 > 1、```RETURN```:直接返回,拦截方法的调用。 > 2、```CALLBACK```:调用一个可配置的 callback 方法,入参需要和原方法一致。 > 3、```EXCEPTION```:直接抛出 CallNoRepeatException 异常。 5、```RETURN 方案```:假设我们定义了一个 ```testCallNoRepeat``` 方法,入参为一个 ```String``` 类型的字符串, 配置他在4秒内不能使用相同的参数调用。 ```java @CallNoRepeat(time = 4, fallback = CallNoRepeat.FallBack.RETURN) public void testCallNoRepeat(String str) { System.out.println(str); } ``` 6、```CALLBACK 方案```:上一步定义的方法我们改为 ```CALLBACK```方案,并设置 ```CALLBACK``` 方法。 ```java @CallNoRepeat(time = 4, fallback = CallNoRepeat.FallBack.CALLBACK, callbackMethod = "testCallNoRepeatCallBack") public void testCallNoRepeat(String str) { System.out.println(str); } private void testCallNoRepeatCallBack(String str) { System.out.println(str + " callback"); } ``` 7、```EXCEPTION 方案```:上一步定义的方法我们改为 ```EXCEPTION```方案,当触发```CallNoRepeat```时,将抛出异常,异常里面包含有剩余时间,当时间超出时可继续调用。 ```java @CallNoRepeat(time = 4, fallback = CallNoRepeat.FallBack.EXCEPTION) public void testCallNoRepeat(String str) { System.out.println(str); } ```
chenjunan
2022年5月9日 18:36
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码