幂等性原本是数学上的概念,公式:f(x)=f(f(x)) 能够成立的数学性质。用在编程领域,则意为对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的
。举个简单例子来说,就是我们在添加一个学生信息的时候,由于某种原因(网络抖动之类),导致发送多次请求,只能保存一次提交的信息。
项目所用技术:SpringBoot+redis,导包以及redis工具类此处省略,只写校验流程,以免篇幅过长。
/**
* @author lqh
* @date 2020/9/21
* 接口幂等性拦截器
*/
@Component
public class IdempotenceInterceptor implements HandlerInterceptor{
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
if(!(handler instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod= (HandlerMethod) handler;
Method method=handlerMethod.getMethod();
Idempotence methodIdempotence=method.getAnnotation(Idempotence.class);
if(methodIdempotence != null){
// 幂等性校验, 校验通过则放行, 校验失败则抛出异常(自定义异常,返回重复提交信息)
check(request);
}
return true;
}
private void check(HttpServletRequest request) {
tokenService.checkToken(request);
}
}
/**
* @author lqh
* @date 2020/9/21
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private IdempotenceInterceptor idempotenceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(idempotenceInterceptor);
}
}
/**
* @author lqh
* @date 2020/9/21
* * 自定义注解,在需要保证幂等性接口的Controller的方法上使用此注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotence {
}
public interface TokenService {
/**
* 生成token
* @return
*/
ServerResponse createToken();
/**
* 校验token
* @param request
*/
void checkToken(HttpServletRequest request);
}
@Service("TokenService")
public class TokenServiceImpl implements TokenService {
private static final String TOKEN_NAME="token";
@Autowired
private RedisTemplate businessTemplate;
@Override
public ServerResponse createToken() {
String str= RandomUtil.UUID32();
StrBuilder token=new StrBuilder();
token.append(Constant.TOKEN_PREFIX).append(str);
RedisUtil.set(token.toString(),token.toString(),300,businessTemplate);
return ServerResponse.success(token.toString());
}
@Override
public void checkToken(HttpServletRequest request) {
String token=request.getHeader(TOKEN_NAME);
if(StringUtils.isBlank(token)){
token=request.getParameter(TOKEN_NAME);
if(StringUtils.isBlank(token)){
throw new ServiceException(ResponseCodeEnum.ILLEGAL_ARGUMENT.getMsg());
}
}
if(!RedisUtil.hasKey(token,businessTemplate)){
throw new ServiceException(ResponseCodeEnum.REPETITIVE_OPERATION.getMsg());
}
if(!RedisUtil.del(businessTemplate,token)){
throw new ServiceException(ResponseCodeEnum.REPETITIVE_OPERATION.getMsg());
}
}
}
校验方法中的删除token一定校验是否删除成功,不能采用直接删除token的方式
(如果多个线程同时操作到删除这个地方,不校验的话,会出现并发问题,仍然会有重复提交操作的发生)
@RestController
@RequestMapping("/token")
public class TokenController {
@Autowired
private TokenService tokenService;
@RequestMapping("/getToken")
public ServerResponse token() {
return tokenService.createToken();
}
/* @Idempotence
@RequestMapping("/testToken")
public ServerResponse testToken() {
return ServerResponse.success("测试接口");
}*/
}
public class ServiceException extends RuntimeException{
private String code;
private String msg;
public ServiceException() {
}
public ServiceException(String msg) {
this.msg = msg;
}
public ServiceException(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
public enum ResponseCodeEnum {
// 系统模块
SUCCESS(200, "操作成功"),
SAVE_SUCCESS(201,"保存成功"),
DELETE_SUCCESS(202,"删除成功!"),
UPDATE_SUCCESS(403,"更新成功!"),
ERROR(400, "操作失败"),
SAVE_ERROR(401,"保存失败"),
DELETE_ERROR(402,"删除失败!"),
UPDATE_ERROR(403,"更新成功"),
SERVER_ERROR(500, "服务器异常"),
EXCEPTION(-1,"Exception"),
// 通用模块 1xxxx
ILLEGAL_ARGUMENT(10000, "参数不合法"),
REPETITIVE_OPERATION(10001, "请勿重复操作"),
ACCESS_LIMIT(10002, "请求太频繁, 请稍后再试"),
MAIL_SEND_SUCCESS(10003, "邮件发送成功"),
PARAMETER_NOT_EMPTY(10004,"参数不能为空"),
GRMMAR_RULES_ILLEGAL(10005,"语法规则有误,请检查!"),
//**模块
;
ResponseCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
这里采用更新操作说明,假设你要修改学生的基本信息。