SpringBoot实现接口等幂次校验的示例代码

网友投稿 250 2022-09-04


SpringBoot实现接口等幂次校验的示例代码

目录主流的实现方案如下:第一步:书写redis工具类第二步、书写token工具类第三步:定义注解,使用在方法上,当控制层的方法上被注释时,表示该请求为等幂性请求第四步:拦截器配置。选择前置拦截器,每次请求都校验到达的方法上是否有等幂性注解,如果有则进行token校验第五步:对拦截器进行url模式匹配,并注入spring容器第六步:控制层

接口等幂性通俗的来说就是同一时间内,发起多次请求只有一次请求成功;其目的时防止多次提交,数据重复入库,表单验证网络延迟重复提交等问题。

比如:

订单接口, 不能多次创建订单支付接口, 重复支付同一笔订单只能扣一次钱支付宝回调接口, 可能会多次回调, 必须处理重复回调普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次等等

主流的实现方案如下:

1、唯一索引:给表加唯一索引,该方法最简单,当数据重复插入时,直接报sql异常,对应用影响不大;

alter table 表名 add unique(字段)

示例,两个字段为唯一索引,如果出现完全一样的order_name,create_time就直接重复报异常;

alter table 'order' add unique(order_name,create_time)

2、先查询后判断:入库时先查询是否有该数据,如果没有则插入。否则不插入;

3、token机制:发起请求的时候先去redis获取token,将获取的token放入请求的hearder,当请求到达服务端的时候拦截请求,对请求的hearder中的token,进行校验,如果校验通过则放开拦截,删除token,否则使用自定义异常返回错误信息。

第一步:书写redis工具类

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component

public class RedisUtils {

@Autowired

private RedisTemplate redisTemplate;

/**

* 判断key是否存在

* @param key 键

* @return

*/

public boolean hasKey(String key){

try {

return redisTemplate.hasKey(key);

}catch (Exception e){

e.printStackTrace();

return false;

}

}

/**

* 删除key

* @param key 键

* @return

*/

public Boolean del(String key){

if (key != null && key.length() > 0){

return redisTemplate.delete(key);

}else {

return false;

}

}

/**

* 普通缓存获取

* @param key 键

* @return

*/

public Object get(String key){

return key == null ? null : redisTemplate.opsForValue().get(key);

}

/**

* 普通缓存放入并设置时间

* @param key 键

* @param value 值

* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期

* @return

*/

public boolean set(String key,Object value,long time){

try {

if (time > 0){

redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

}

return true;

}catch (Exception e){

e.printStackTrace();

return false;

}

}

}

第二步、书写token工具类

import com.smile.project.exception.utils.CodeMsg;

import com.smile.project.exception.utils.CommonException;

import io.netty.util.concurrent.GlobalEventExecutor;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.util.DigestUtils;

import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

import java.util.UUID;

/**

* 使用uuid生成随机字符串,

* 通过md5加密防止token被解密,保证token的唯一性与安全性

* 设置过期时间为30秒,即在30秒内之恶能提交成功一次请求

*/

@Component

public class TokenUtils {

@Autowired

RedisUtils redisUtils;

//token过期时间为30秒

private final static Long TOKEN_EXPIRE = 30L;

private final static String TOKEN_NAME = "token";

/**

* 生成token放入缓存

*/

public String generateToken(){

String uuid = UUID.randomUUID().toString();

String token = DigestUtils.md5DigestAsHex(uuid.getBytes());

redisUtils.set(TOKEN_NAME,token,TOKEN_EXPIRE);

return token;

}

/**

* token校验

*/

public boolean verifyToken(HttpServletRequest request){

String token = request.getHeader(TOKEN_NAME);

//header中不存在token

if (StringUtils.isEmpty(token)){

//抛出自定义异常

System.out.println("token不存在");

throw new CommonException(CodeMsg.NOT_TOKEN);

}

//缓存中不存在

if (!redisUtils.hasKey(TOKEN_NAME)){

System.out.println("token已经过期");

throw new CommonException(CodeMsg.TIME_OUT_TOKEN);

}

String cachToken = (String) redisUtils.get(TOKEN_NAME);

if (!token.equals(cachToken)){

System.out.println("token检验失败");

throw new CommonException(CodeMsg.VALIDA_ERROR_TOKEN);

}

//移除token

Boolean del = redisUtils.dfSnaKltcdel(TOKEN_NAME);

if (!del){

System.out.println("token删除失败");

throw new CommonException(CodeMsg.DEL_ERROR_TOKEN);

}

return true;

}

}

第三步:定义注解,使用在方法上,当控制层的方法上被注释时,表示该请求为等幂性请求

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lanhttp://g.annotation.Target;

/**

* 当控制层的方法上被注释时,表示该请求为等幂性请求

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Idempotent {

}

第四步:拦截器配置。选择前置拦截器,每次请求都校验到达的方法上是否有等幂性注解,如果有则进行token校验

import com.smile.project.redis.utils.Idempotent;

import com.smile.project.redis.utils.TokenUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.lang.reflect.Method;

@Component

public class IdempotentInterceptor implements HandlerInterceptor {

@Autowired

private TokenUtils tokenUtils;

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (!(handler instanceof HandlerMethod)){

return true;

}

//对有Idempotent注解的方法进行拦截校验

HandlerMethod handlerMethod = (HandlerMethod) handler;

Method method = handlerMethod.getMethod();

Idempotent methodAnnotation = method.getAnnotation(Idempotent.class);

if (methodAnnotation != null){

//token校验

tokenUtils.verifyToken(request);

}

return true;

}

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}

}

第五步:对拦截器进行url模式匹配,并注入spring容器

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**

* 对拦截器进行url模式匹配,并注入spring容器

*/

@Configuration

public class WebConfiguration implements WebMvcConfigurer {

@Autowired

IdempotentInterceptor idempotentInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) {

//拦截所有请求

registry.addInterceptor(idempotentInterceptor).addPathPatterns("/**");

}

}

第六步:控制层

对控制层进行编写,发起请求时通过getToken方法获取token,将获取的token放入hearder后,再请求具体方法。正常请求具体方法的时候注意在hearder中加入token,否则是失败

import com.alibaba.fastjson.JSONObject;

import com.smile.project.exception.utils.CodeMsg;

import com.smile.project.exception.utils.ResultPage;

import com.smile.project.redis.utils.Idempotent;

import com.smile.project.redis.utils.TokenUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

public class SmileController {

@Autowired

TokenUtils tokenUtils;

@GetMapping("smile/token")

public ResultPage getToken(){

String token = tokenUtils.generateToken();

JSONObject jsonObject = new JSONObject();

jsonObject.put("token",token);

return ResultPage.success(CodeMsg.SUCCESS,jsonObject);

}

@Idempotent

@GetMapping("smile/test")

public ResultPage testIdempotent(){

return ResultPage.success(CodeMsg.SUCCESS,"校验成功");

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:性能相关异步IO(高频数据 异步性)
下一篇:Twisted异步编程(twisted异步处理框架)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~