丰城建设网站,网站开发的费用属于什么科目,设计师外包平台,今标 网站建设引言
项目开发中我们有时会用到一些第三方付费的接口#xff0c;这些接口的每次调用都会产生一些费用#xff0c;有时会有别有用心之人恶意调用我们的接口#xff0c;造成经济损失#xff1b;或者有时需要对一些执行时间比较长的的接口进行频率限制#xff0c;这里我就简…引言
项目开发中我们有时会用到一些第三方付费的接口这些接口的每次调用都会产生一些费用有时会有别有用心之人恶意调用我们的接口造成经济损失或者有时需要对一些执行时间比较长的的接口进行频率限制这里我就简单演示一下我的解决思路
主要使用spring的aop特性实现功能
代码实现
首先需要一个注解找个注解可以理解为一个坐标标记该注解的接口都将进行访问频率限制
package com.yang.prevent;import java.lang.annotation.*;/*** 接口防刷注解*/
Documented
Target({ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
public interface Prevent {/*** 限制的时间值秒默认60s*/long value() default 60;/*** 限制规定时间内访问次数默认只能访问一次*/long times() default 1;/*** 提示*/String message() default ;/*** 策略*/PreventStrategy strategy() default PreventStrategy.DEFAULT;
}value就是限制周期times是在一个周期内访问次数message是访问频率过多时的提示信息strategy就是一个限制策略是自定义的如下
package com.yang.prevent;/*** 防刷策略枚举*/
public enum PreventStrategy {/*** 默认(60s内不允许再次请求*/DEFAULT
}下面就是aop拦截的具体代码
package com.yang.prevent;import com.yang.common.StatusCode;
import com.yang.constant.redis.RedisKey;
import com.yang.exception.BusinessException;
import com.yang.utils.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import java.util.concurrent.TimeUnit;/*** 防刷切面实现类*/
Aspect
Component
public class PreventAop {Resourceprivate RedisTemplateString, Long redisTemplate;/*** 切入点*/Pointcut(annotation(com.yang.prevent.Prevent))public void pointcut() {}/*** 处理前*/Before(pointcut())public void joinPoint(JoinPoint joinPoint) throws Exception {// 获取调用者ipRequestAttributes requestAttributes RequestContextHolder.currentRequestAttributes();HttpServletRequest httpServletRequest ((ServletRequestAttributes) requestAttributes).getRequest();String userIP IpUtils.getUserIP(httpServletRequest);// 获取调用接口方法名MethodSignature methodSignature (MethodSignature) joinPoint.getSignature();Method method joinPoint.getTarget().getClass().getMethod(methodSignature.getName(),methodSignature.getParameterTypes()); // 获取该接口方法String methodFullName method.getDeclaringClass().getName() method.getName(); // 获取到方法名Prevent preventAnnotation method.getAnnotation(Prevent.class); // 获取该接口上的prevent注解为了使用该注解内的参数// 执行对应策略entrance(preventAnnotation, userIP, methodFullName);}/*** 通过prevent注册判断执行策略* param prevent 该接口的prevent注解对象* param userIP 访问该接口的用户ip* param methodFullName 该接口方法名*/private void entrance(Prevent prevent, String userIP, String methodFullName) throws Exception {PreventStrategy strategy prevent.strategy(); // 获取校验策略if (Objects.requireNonNull(strategy) PreventStrategy.DEFAULT) { // 默认就是default策略执行default策略方法defaultHandle(userIP, prevent, methodFullName);} else {throw new BusinessException(StatusCode.FORBIDDEN, 无效的策略);}}/*** Default测试执行方法* param userIP 访问该接口的用户ip* param prevent 该接口的prevent注解对象* param methodFullName 该接口方法名*/private void defaultHandle(String userIP, Prevent prevent, String methodFullName) throws Exception {String base64StrIP toBase64String(userIP); // 加密用户ip避免ip存在一些特殊字符作为redis的key不合法long expire prevent.value(); // 获取访问限制时间long times prevent.times(); // 获取访问限制次数// 限制特定时间内访问特定次数long count redisTemplate.opsForValue().increment(RedisKey.PREVENT_METHOD_NAME base64StrIP : methodFullName, 1); // 访问次数1if (count 1) { // 如果访问次数为1则重置访问限制时间即redis超时时间redisTemplate.expire(RedisKey.PREVENT_METHOD_NAME base64StrIP : methodFullName,expire,TimeUnit.SECONDS);}if (count times) { // 如果访问次数超出访问限制次数则禁止访问// 如果有限制信息则使用限制信息没有则使用默认限制信息String errorMessage !StringUtils.isEmpty(prevent.message()) ? prevent.message() : expire 秒内不允许重复请求;throw new BusinessException(StatusCode.FORBIDDEN, errorMessage);}}/*** 对象转换为base64字符串* param obj 对象值* return base64字符串*/private String toBase64String(String obj) throws Exception {if (StringUtils.isEmpty(obj)) {return null;}Base64.Encoder encoder Base64.getEncoder();byte[] bytes obj.getBytes(StandardCharsets.UTF_8);return encoder.encodeToString(bytes);}
}注释写的很清楚了这里我简单说一下关键方法defaultHandle
1首先加密ip原因就是避免ip存在一些特殊字符作为redis的key不合法该ip是组成redis主键的一部分redis主键格式为polar:prevent:加密ip:方法名
这样就能区分不同ip同一ip下区分不同方法的访问频率
2expire和times都是从Prevent注解中获取的参数默认是60s内最多访问1次可以自定义
3然后接口访问次数1该设备ip下如果该接口访问次数为1则说明这是这个ip第一次访问该接口或者是该接口的频率限制已经解除即该接口访问次数1前redis中没有该ip对应接口的限制记录所以需要重新设置对应超时时间表示新的一轮频率限制开始如果访问次数超过最大次数则禁止访问该接口直到该轮频率限制结束redis缓存的记录超时消失才可以再次访问该接口 这个方法理解了其他就不难了其他方法就是给这个方法传参或者作为校验或数据处理的
下面测试一下分别标记两个接口一个使用Prevent默认参数一个使用自定义参数 调用getToleById接口意思是60s内只能调用该接口1次
第一次调用成功redis键值为1 第二次失败需要等60s redis键值变成了2 getRoleList是自定义参数意思是20s内最多只能访问该接口5次
未超出频率限制 超出频率限制 总体流程就是这样了aop理解好了不难也比较实用可以在自己项目中使用