幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次
比如:
本文采用第2种方式实现, 即通过redis + token机制实现接口幂等性校验
本文主要处理场景:同一个用户,一个请求,在规定的时间内只能发起1次请求。
这边主要处理的防止页面重复提交,为保证幂等性,请求接口时,后端通过header或者接口请求参数获取登录信息+请求路径判断redis中是否存在此key。
请勿重复操作
提示说明:
org.springframework.boot
spring-boot-starter-data-redis
#########################本地开发环境#########################
##spring boot 配置
server.port=8004
spring.application.name=share
############################################################
## MySQL配置
spring.datasource.url=jdbc:mysql://192.168.1.12:3306/share?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
############################################################
## Redis配置
spring.redis.host=192.168.1.12
#spring.redis.password=
spring.redis.database=1
spring.redis.port=6379
############################################################
2.自定义注解 @ReSubmitCheck
import java.lang.annotation.*;
/**
* 在需要保证接口幂等性的Controller的方法上使用此注解
* 重复提交校验注解
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReSubmitCheck {
//校验几秒内重复提交
int seconds() default 3;
}
3. 防止重复提交切面处理器 PreventReSummitAspect
import com.city.share.annotation.ReSubmitCheck;
import com.city.share.enums.ResultEnum;
import com.city.share.exception.ShareException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
/**
* 防重复点击
*/
@Aspect
@Component
@Slf4j
public class PreventReSummitAspect {
/**
* redis工具类
*/
@Autowired
private StringRedisTemplate redisTemplate;
@Before("@annotation(reSubmitCheck)")
public void preventReSubmit(JoinPoint joinPoint, ReSubmitCheck reSubmitCheck) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取用户登录的accesstoken
HttpServletRequest request = attributes.getRequest();
String token = request.getParameter("accesstoken");
if (token == null) {
throw new ShareException(ResultEnum.ON_LOGIN);
}
String lockKey = "ReSubmit:" + token + "_" + request.getServletPath();
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, reSubmitCheck.seconds(), TimeUnit.SECONDS);
if (!result) {
System.out.println("重复请求:"+lockKey);
throw new ShareException(ResultEnum.RESUBMIT_ERROR);
}
}
}
4.测试controller HolleContraller
import com.city.share.Dto.Result;
import com.city.share.Utils.ResultUtil;
import com.city.share.annotation.ReSubmitCheck;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
@RestController
public class HolleContraller implements Serializable{
@GetMapping("/holle")
@ReSubmitCheck(seconds=10)//这边设置了10秒内不能重复访问
public Result holleTest(){
System.out.println("hello spring boot");
return ResultUtil.success("hello spring boot");
}
}
OK, 目前为止, 校验代码准备就绪, 接下来测试验证
访问:127.0.0.1:8004/holle?accesstoken=123456
查看redis
测试接口安全性: 利用jmeter测试工具模拟10个并发请求
请求结果:因为都是在10秒内,所以只有第一个请求成功
其实思路很简单, 就是每次请求保证唯一性, 从而保证幂等性, 通过spring aop+注解, 就不用每次请求都写重复代码, 其实也可以利用拦截器实现。
如果小伙伴有什么疑问或者建议欢迎提出
源码地址:https://download.csdn.net/download/zppiio/85309475https://download.csdn.net/download/zppiio/85309475