常用自定义注解

导航

  • 一、方法计时器
  • 二、valid 参数校验的通用返回
  • 三、接口访问频次拦截(幂等)

一、方法计时器

注解类:MethodTimer

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodTimer {
}

处理器(需要AOP和spring的支持):MethodTimerProcessor

@Slf4j
@Component
@Aspect
public class MethodTimerProcessor {/*** 处理 @MethodTimer 注解*/@Around("@annotation(methodTimer)")public Object timerAround(ProceedingJoinPoint point, MethodTimer methodTimer) throws Throwable {long beginMills = System.currentTimeMillis();// process the methodObject result = point.proceed();log.info("{} 耗时 : {} ms", point.getSignature(), System.currentTimeMillis() - beginMills);return result;}
}

使用方法:直接标记在 Controller 的方法上。

二、valid 参数校验的通用返回

注解类:ValidCommonResp

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidCommonResp {
}

处理器(aop+spring):ValidCommonRespProcessor

@Slf4j
@Aspect
@Component
public class ValidCommonRespProcessor {/*** 处理 @ValidCommonResp 注解.* 注意,BindingResult是Spring validation的校验结果,* 当参数传入 BindingResult后,Spring MVC就不再控制校验* 结果的返回,如果不希望使用 @ValidCommonResp的校验结果* 封装,请在方法中实现校验结果的处理,二者任选其一。** @author mouhaotian*/@Around("@annotation(validCommonResp)")public Object aroundAdvice(ProceedingJoinPoint point, ValidCommonResp validCommonResp) throws Throwable {Object[] args = point.getArgs();for (Object arg : args) {if (arg instanceof BindingResult) {BindingResult bindingResult = (BindingResult) arg;if (bindingResult.hasErrors()) {FieldError fieldError = bindingResult.getFieldError();CommonResp commonResp = new CommonResp(CommonCode.FAIL,fieldError.getField() + fieldError.getDefaultMessage());return R.data(commonResp);}break;}}Object result = point.proceed(args);return result;}
}

使用方法:搭配 validation 注解、BindingResult 一起使用:

    @PostMapping("/xxxx")@ValidCommonResppublic R submit(@Valid @RequestBody DoorzoneInfo doorzoneInfo, BindingResult result) {log.info("请求{}", doorzoneInfo);R commonResp = doorzoneInfoService.insertOrUpdDoorzoneInfo(doorzoneInfo);log.info("响应{}", commonResp);return commonResp;}

好处:可以替代代码块中处理 BindingResult 的逻辑。

三、接口访问频次拦截(幂等)

实现一个注解,当controller中的方法收到请求后,在一定时间之内(如10s内)拒绝接收相同参数的请求。即对后台接口的访问增加了频次限制,可以理解为一种不是特别标准的幂等。

注解 @Ide

/*** 幂等校验注解类*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ide {/*** 关键key* key是本次请求中参数的键,* 重复请求的key取自header中的rid* 用来标识这个请求的唯一性* 拦截器中会使用key从请求参数中获取value** @return String*/String key() default "";/*** 自定义key的前缀用来区分业务*/String perFix();/*** 自定义key的超时时间(基于接口)*/String expireTime();/*** 禁止重复提交的模式* 默认是全部使用*/IdeTypeEnum ideTypeEnum() default IdeTypeEnum.ALL;
}

AOP 横切处理逻辑

/*** 注解执行器 处理重复请求 和串行指定条件的请求* <p>* 两种模式的拦截* 1.rid 是针对每一次请求的* 2.key+val 是针对相同参数请求* </p>* <p>* 另根据谢新的建议对所有参数进行加密检验,提供思路,可以自行扩展* DigestUtils.md5Hex(userId + "-" + request.getRequestURL().toString()+"-"+ JSON.toJSONString(request.getParameterMap()));* 或 DigestUtils.md5Hex(ip + "-" + request.getRequestURL().toString()+"-"+ JSON.toJSONString(request.getParameterMap()));* </p>*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
@ConditionalOnClass(RedisService.class)
public class IdeAspect extends BaseAspect {private final ThreadLocal<String> PER_FIX_KEY = new ThreadLocal<String>();/*** 配置注解后 默认开启*/private final boolean enable = true;/*** request请求头中的key*/private final static String HEADER_RID_KEY = "RID";/*** redis中锁的key前缀*/private static final String REDIS_KEY_PREFIX = "RID:";/*** 锁等待时长*/private static int LOCK_WAIT_TIME = 10;private final RedisService redisService;@AutowiredIdeAspectConfig ideAspectConfig;@Pointcut("@annotation(cn.com.bmac.wolf.core.ide.annotation.Ide)")public void watchIde() {}@Before("watchIde()")public void doBefore(JoinPoint joinPoint) {Ide ide = getAnnotation(joinPoint, Ide.class);if (enable && null != ide) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (null == attributes) {throw new IdeException("请求数据为空");}HttpServletRequest request = attributes.getRequest();//根据配置文件中的超时时间赋值if (Func.isNotBlank(ideAspectConfig.getExpireTime())) {if(Func.isNumeric(ideAspectConfig.getExpireTime())){LOCK_WAIT_TIME = Integer.parseInt(ideAspectConfig.getExpireTime());}}//根据注解传参赋值if(Func.isNotBlank(ide.expireTime())){LOCK_WAIT_TIME = Integer.parseInt(ide.expireTime());}//1.判断模式if (ide.ideTypeEnum() == IdeTypeEnum.ALL || ide.ideTypeEnum() == IdeTypeEnum.RID) {//2.1.通过rid模式判断是否属于重复提交String rid = request.getHeader(HEADER_RID_KEY);if (Func.isNotBlank(rid)) {Boolean result = redisService.tryLock(REDIS_KEY_PREFIX + rid, LOCK_WAIT_TIME);if (!result) {throw new IdeException("命中RID重复请求");}log.debug("msg1=当前请求已成功记录,且标记为0未处理,,{}={}", HEADER_RID_KEY, rid);} else {log.warn("msg1=header没有rid,防重复提交功能失效,,remoteHost={}" + request.getRemoteHost());}}boolean isApiExpireTime = false;if (ide.ideTypeEnum() == IdeTypeEnum.ALL|| ide.ideTypeEnum() == IdeTypeEnum.KEY) {//2.2.通过自定义key模式判断是否属于重复提交String key = ide.key();if (Func.isNotBlank(key)) {String val = "";Object[] paramValues = joinPoint.getArgs();String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();//获取自定义key的valueString[] keys = key.split("\\|");for(int i = 0; i < keys.length; i++){for (int j = 0; j < paramNames.length; j++) {//BindingResult 不能转json,会导致线程报错终止if (paramValues[j] instanceof BindingResult) {continue;}String params = JSON.toJSONString(paramValues[j]);if (params.startsWith("{")) {//如果是对象//通过key获取valueJSONObject jsonObject = JSON.parseObject(params);val = val + jsonObject.getString(keys[i]);} else if (keys[i].equals(paramNames[j])) {//如果是单个k=vval = val + params;} else {//如果自定义的key,在请求参数中没有此参数,说明非法请求log.warn("自定义的key,在请求参数中没有此参数,防重复提交功能失效");}}}//判断重复提交的条件String perFix = "";if (Func.isNotBlank(val)) {String[] perFixs = ide.perFix().split("\\|");int perFixsLength = perFixs.length;for(int i = 0; i < perFixs.length; i++){if(Func.isNotBlank(perFix)){perFix = perFix + ":" + perFixs[i];}else{perFix = perFixs[i];}}perFix = perFix + ":" + val;Boolean result = true;try {result = redisService.tryLock(perFix, LOCK_WAIT_TIME);} catch (Exception e) {log.error("获取redis锁发生异常", e);throw e;}if (!result) {String targetName = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();log.error("msg1=不允许重复执行,,key={},,targetName={},,methodName={}", perFix, targetName, methodName);throw new IdeException("不允许重复提交");}//存储在当前线程PER_FIX_KEY.set(perFix);log.info("msg1=当前请求已成功锁定:{}", perFix);} else {log.warn("自定义的key,在请求参数中value为空,防重复提交功能失效");}}}}}@After("watchIde()")public void doAfter(JoinPoint joinPoint) throws Throwable {try {Ide ide = getAnnotation(joinPoint, Ide.class);if (enable && null != ide) {if (ide.ideTypeEnum() == IdeTypeEnum.ALL|| ide.ideTypeEnum() == IdeTypeEnum.RID) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String rid = request.getHeader(HEADER_RID_KEY);if (Func.isNotBlank(rid)) {try {log.info("msg1=当前请求已成功处理,,rid={}", rid);} catch (Exception e) {log.error("释放redis锁异常", e);}}PER_FIX_KEY.remove();}if (ide.ideTypeEnum() == IdeTypeEnum.ALL|| ide.ideTypeEnum() == IdeTypeEnum.KEY) {// 自定义keyString key = ide.key();if (Func.isNotBlank(key) && Func.isNotBlank(PER_FIX_KEY.get())) {try {log.info("msg1=当前请求已成功释放,,key={}", PER_FIX_KEY.get());PER_FIX_KEY.set(null);PER_FIX_KEY.remove();} catch (Exception e) {log.error("释放redis锁异常", e);}}}}} catch (Exception e) {log.error(e.getMessage(), e);}}
}

其他相关类

@Data
@Component
@ConfigurationProperties(prefix = "ide")
public class IdeAspectConfig {/*** 过期时间 秒*/private String expireTime;}
@Getter
@AllArgsConstructor
public enum IdeTypeEnum {/*** 0+1*/ALL(0, "ALL"),/*** ruid 是针对每一次请求的*/RID(1, "RID"),/*** key+val 是针对相同参数请求*/KEY(2, "KEY");private final Integer index;private final String title;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 系统架构_Python之优化系统架构的方案

方案3&#xff1a; 改变系统架构在开始多进程之前&#xff0c;先简单说明一下python GIL, 之前自己对他也有些误解。因为python GIL的机制存在&#xff0c;同时运行的线程只有一个&#xff0c;但这个线程在不同时刻可以运行在不同的核上&#xff0c;这个调度是由操作系统完成的…

JVM垃圾收集器——G1

导航引言一、G1 介绍1.1 适用场景1.2 设计初衷1.3 关注焦点1.4 工作模式1.5 堆的逻辑结构1.6 主要收集目标1.7 停顿预测模型1.8 拷贝和压缩1.9 与 CMS 和 Parallel 收集器的比较1.10 固定停顿目标二、堆的逻辑分区2.1 region2.2 CSet2.3 RSet2.4 Card Table三、G1 的工作原理3.…

的mvc_简述PHP网站开发的MVC模式

为了提高开发时候的代码重用和开发速度&#xff0c;php使用了mvc的模式&#xff0c;主要是对代码的功能进行了分类&#xff0c;M&#xff1a;model主要是对数据库进行操作&#xff0c;v&#xff1a;view主要是前端html文件操作&#xff0c;c&#xff1a;controller主要是编写基…

CAP 原则与 BASE 理论

导航引言一、CAP 原则1.1 Consistency 一致性1.2 Available 可用性1.3 Partition tolerance 分区容错性1.4 CAP 的矛盾1.5 CAP 的组合场景二、BASE 理论2.1 基本可用2.2 软状态2.3 最终一致性2.3.1 因果一致性2.3.2 读自身所写2.3.3 会话一致性2.3.4 单调读一致性2.3.5 单调写一…

java 教室借用管理系统_[内附完整源码和文档] 基于JAVA语言的学生选课信息管理系统...

摘 要本系统运用Java面向对象的方法设计而成。近年来&#xff0c;学生选课系统越来越在高校学生群体中得到普及&#xff0c;其所承担的功能也变得越来越丰富&#xff0c;所起到的作用也变得越来越重要&#xff0c;在被学校学生重视的同时&#xff0c;也意味着它的功能要更加完善…

jMeter 模拟 web 高并发请求

导航一、jmeter 简介与下载二、接口压测设置三、实战演示一、jmeter 简介与下载 Apache JMeter是Apache组织开发的基于Java的压力测试工具。 最初被设计用于Web应用测试&#xff0c;但后来扩展到其他测试领域。JMeter 可以用于对服务器、网络或对象模拟巨大的负载&#xff0c…

实施文档_建设工程监理全套资料范本,Word文档附百份案例表格,超实用

建设工程监理全套资料范本&#xff0c;Word文档附百份案例表格&#xff0c;超实用在日常工作中&#xff0c;监理人员不仅需要经常跑腿儿检查&#xff0c;同时还需要提交许许多多的资料存档&#xff0c;甚至可能需要熬夜码字。今天整理的监理资料范本&#xff0c;既能让监理人员…

微服务架构 —— 服务雪崩与容错方案

导航一、什么是服务雪崩二、雪崩效应的三个核心原因三、容错四、业界常见容错思路五、常见容错组件一、什么是服务雪崩 服务雪崩 指的是微服务架构中&#xff0c;微服务各节点之间由于网络通信异常或微服务自身故障等问题&#xff0c;导致请求堆积、任务堆积&#xff0c;消耗和…

手游方舟怎么输入代码_明日方舟再次登顶失败,为了不发十连奖励,鹰角实力控分?...

在明日方舟新版本活动“孤岛风云”正式上线后&#xff0c;关于干员的强度和游戏剧情的讨论也在最近多了起来。尤其是在一周年卫星干员山落地&#xff0c;并且人气干员塞雷娅背后的故事揭晓之后&#xff0c;明日方舟的热度也在玩家圈子中迅速的攀升&#xff0c;成为近期话题量十…

ios 图片逆时针旋转_iphone-IOS 竖直拍照被旋转,image-orientation 让图片自动旋转

本文目录结构IOS 垂直拍照的时候会遇到&#xff0c;PC 端读取的时候&#xff0c;逆时针旋转了 90 度的问题&#xff1b;安卓、PS 处理的&#xff0c;相册选择截图等不会出现这个问题&#xff1b;场景说明&#xff1a;这个图片在浏览器里&#xff0c;如果您单独打开的时候&#…

Spring Cloud Alibaba —— Sentinel 入门

导航一、什么是Sentinel1.1 Sentinel 的优点二、整合 Sentinel 演示三、Sentinel控制台与微服务通信的原理四、Sentinel 流控演示一、什么是Sentinel Sentinel 是阿里开源的用于提供微服务架构容错方案的组件。它以流量作为切入点&#xff0c;从流量控制、熔断降级、系统负载保…

websocket 获取连接id_websocket建立连接时能传递参数吗

展开全部您可以这样!在js传参的时候参数就和其他地址一样传就行 比如var wsUrl ws://localhost:8080/ScadaWebSocket/ScadaSocket/我的参数webSocketnew WebSocket(wsUrl);后台的类上面的注解这样写ServerEndpoint(value "/ScadaSocket/{param}")注意上面的花3231…

Spring Cloud Alibaba —— Sentinel 详细使用

导航引言一、Sentinel的两个基本概念二、流控规则2.1 基本选项2.2 高级选项三、熔断(降级)规则四、热点规则五、授权规则&#xff08;了解&#xff09;六、系统规则&#xff08;了解&#xff09;七、自定义异常返回八、SentinelResource九、Sentinel 规则持久化&#xff08;待补…

扫地机器人单扫和双扫_小米扫拖机器人体验:再见了,拖把君

小米在2016年首次推出了扫地机器人&#xff0c;凭借产品力和性价比&#xff0c;可以说为中国家庭的智能清洁概念普及&#xff0c;立下一功。不过&#xff0c;近两年因为一直没有推出扫拖一体产品&#xff0c;急得民间高手都开始自己动手给米家扫地机改造拖地功能了&#xff0c;…

Spring Cloud —— Gateway 服务网关

导航一、什么是服务网关二、业界常见网关组件三、Spring Cloud Gateway四、Gateway 快速入门4.1 创建 gateway 服务4.2 添加 gateway 依赖和 nacos 依赖4.3 配置路由信息4.4 测试路由转发五、Gateway 执行流程六、Gateway 断言6.1 内置路由断言工厂6.2 自定义路由断言工厂七、G…

图形显卡_选核芯显卡还是独立显卡?这才是决定笔记本电脑性能的关键

买笔记本电脑的时候&#xff0c;选核芯显卡还是独立显卡是很多朋友纠结的问题。核芯显卡是建立在和处理器同一内核芯片上的图形处理单元&#xff0c;而独立显卡拥有单独的图形核心和独立的显存。那么具体哪个更好呢&#xff1f;这里就来介绍一下。两者各自的特点核芯显卡和传统…

Spring Cloud —— 链路追踪技术

导航一、什么是链路追踪二、Spring Cloud Sleuth2.1 相关概念三、Sleuth 入门案例四、Zipkin 的集成4.1 Zipkin 介绍4.2 Zipkin 服务端安装4.3 Zipkin 客户端安装五、Zipkin 数据持久化5.1 MySQL 数据持久化5.2 Elasticsearch 数据持久化一、什么是链路追踪 在大型系统的微服务…

使用vim的重不重要_VIM高级操作,经常用vim的应该多学习。多开发效率很有大帮助!...

Vim是号称“编辑器之神”的文本编辑软件&#xff0c;自从接触Vim以来&#xff0c;基本上都是用Vim来修改和编写代码和配置文件的。但是我一直只会用最基本的命令&#xff0c;虽然把HJKL的定位键已操纵地很熟练。但是Vim其他强大的地方却几乎没有触及过。学一样东西&#xff0c;…

bool类型数组转换成一个整数_「PHP」常用的数组键值操作函数,面试重点

数组键值操作函数1、array_values ( array $array ) : array返回数组中所有的值的数组$a[name>jikeshiguangji,age>26];print_r(array_values($a));运行结果&#xff1a;$aarray("name">"jikeshiguangji","age">"26");pri…

Spring Cloud —— 消息队列与 RocketMQ

导航一、什么是 MQ二、常见的 MQ 产品三、RocketMQ 概念与架构设计3.1 基本概念1、消息模型&#xff08;Message Model&#xff09;2、生产者与消费者&#xff08;Producer & Consumer&#xff09;3、主题&#xff08;Topic&#xff09;4、代理服务器与名称服务&#xff08…