十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
这篇“怎么用token机制实现接口自动幂等”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么用token机制实现接口自动幂等”文章吧。
我们一直强调网站设计、做网站对于企业的重要性,如果您也觉得重要,那么就需要我们慎重对待,选择一个安全靠谱的网站建设公司,企业网站我们建议是要么不做,要么就做好,让网站能真正成为企业发展过程中的有力推手。专业的建站公司不一定是大公司,创新互联作为专业的网络公司选择我们就是放心。
幂等性,就是只多次操作的结果是一致的。这里可能有人会有疑问。
问:为什么要多次操作结果都一致呢?比如我查询数据,每次查出来的都一样,即使我修改了每次查出来的也都要一样吗?
答:我们说的多次,是指同一次请求中的多次操作。这个多次操作可能会在如下情况发生:
前端重复提交。比如这个业务处理需要2秒钟,我在2秒之内,提交按钮连续点了3次,如果非幂等性接口,那么后端就会处理3次。如果是查询,自然是没有影响的,因为查询本身就是幂等操作,但如果是新增,本来只是新增1条记录的,连点3次,就增加了3条,这显然不行。
响应超时而导致请求重试:在微服务相互调用的过程中,假如订单服务调用支付服务,支付服务支付成功了,但是订单服务接收支付服务返回的信息时超时了,于是订单服务进行重试,又去请求支付服务,结果支付服务又扣了一遍用户的钱。如果真这样的话,用户估计早就提着砍刀来了。
经过上面的描述,相信大家已经清楚了什么叫接口幂等性及其重要性。那么如何设计呢?大致有以下几种方案:
数据库记录状态机制:即每次操作前先查询状态,根据数据库记录的状态来判断是否要继续执行操作。比如订单服务调用支付服务,每次调用之前,先查询该笔订单的支付状态,从而避免重复操作。
token机制:请求业务接口之前,先请求token接口(会将生成的token放入redis中)获取一个token,然后请求业务接口时,带上token。在进行业务操作之前,我们先获取请求中携带的token,看看在redis中是否有该token,有的话,就删除,删除成功说明token校验通过,并且继续执行业务操作;如果redis中没有该token,说明已经被删除了,也就是已经执行过业务操作了,就不让其再进行业务操作。大致流程如下:

其他方案:接口幂等性设计还有很多其他方案,比如全局唯一id、乐观锁等。本文主要讲token机制的使用,若感兴趣可以自行研究。
1、pom.xml:主要是引入了redis相关依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 redis.clients jedis org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.apache.commons commons-lang3 org.json json 20190722
2、application.yml:主要是配置redis
server: port: 6666spring: application: name: idempotent-api redis: host: 192.168.2.43 port: 6379
3、业务代码:
•新建一个枚举,列出常用返回信息,如下:
@Getter@AllArgsConstructorpublic enum ResultEnum { REPEATREQUEST(405, "重复请求"), OPERATEEXCEPTION(406, "操作异常"), HEADERNOTOKEN(407, "请求头未携带token"), ERRORTOKEN(408, "token正确") ; private Integer code; private String msg;}
•新建一个JsonUtil,当请求异常时往页面中输出json:
public class JsonUtil { private JsonUtil() {} public static void writeJsonToPage(HttpServletResponse response, String msg) { PrintWriter writer = null; response.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=utf-8"); try { writer = response.getWriter(); writer.print(msg); } catch (IOException e) { } finally { if (writer != null) writer.close(); } }}
@Componentpublic class RedisUtil {private RedisUtil() {}private static RedisTemplate redisTemplate;@Autowiredpublic void setRedisTemplate(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {redisTemplate.setKeySerializer(new StringRedisSerializer());//设置序列化Value的实例化对象redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());RedisUtil.redisTemplate = redisTemplate;}/*** 设置key-value,过期时间为timeout秒* @param key* @param value* @param timeout*/public static void setString(String key, String value, Long timeout) {redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);}/*** 设置key-value* @param key* @param value*/public static void setString(String key, String value) {redisTemplate.opsForValue().set(key, value);}/*** 获取key-value* @param key* @return*/public static String getString(String key) {return (String) redisTemplate.opsForValue().get(key);}/*** 判断key是否存在* @param key* @return*/public static boolean isExist(String key) {return redisTemplate.hasKey(key);}/*** 删除key* @param key* @return*/public static boolean delKey(String key) {return redisTemplate.delete(key);}}
public class TokenUtil {private TokenUtil() {}private static final String KEY = "token";private static final String CODE = "code";private static final String MSG = "msg";private static final String JSON = "json";private static final String RESULT = "result";/*** 生成token并放入redis中* @return*/public static String createToken() {String token = UUID.randomUUID().toString();RedisUtil.setString(KEY, token, 60L);return RedisUtil.getString(KEY);}/*** 校验token* @param request* @return* @throws JSONException*/public static MapcheckToken(HttpServletRequest request) throws JSONException { String headerToken = request.getHeader(KEY);JSONObject json = new JSONObject();MapresultMap = new HashMap<>(); // 请求头中没有携带token,直接返回falseif (StringUtils.isEmpty(headerToken)) {json.put(CODE, ResultEnum.HEADERNOTOKEN.getCode());json.put(MSG, ResultEnum.HEADERNOTOKEN.getMsg());resultMap.put(RESULT, false);resultMap.put(JSON, json.toString());return resultMap;}if (StringUtils.isEmpty(RedisUtil.getString(KEY))) {// 如果redis中没有token,说明已经访问成功过了,直接返回falsejson.put(CODE, ResultEnum.REPEATREQUEST.getCode());json.put(MSG, ResultEnum.REPEATREQUEST.getMsg());resultMap.put(RESULT, false);resultMap.put(JSON, json.toString());return resultMap;} else {// 如果redis中有token,就删除掉,删除成功返回true,删除失败返回falseString redisToken = RedisUtil.getString(KEY);boolean result = false;if (!redisToken.equals(headerToken)) {json.put(CODE, ResultEnum.ERRORTOKEN.getCode());json.put(MSG, ResultEnum.ERRORTOKEN.getMsg());} else {result = RedisUtil.delKey(KEY);String msg = result ? null : ResultEnum.OPERATEEXCEPTION.getMsg();json.put(CODE, 400);json.put(MSG, msg);}resultMap.put(RESULT, result);resultMap.put(JSON, json.toString());return resultMap;}}}
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NeedIdempotent {}public class IdempotentInterceptor implements HandlerInterceptor{@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Object object) throws JSONException {// 拦截的不是方法,直接放行if (!(object instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) object;Method method = handlerMethod.getMethod();// 如果是方法,并且有@NeedIdempotent注解,就自动幂等if (method.isAnnotationPresent(NeedIdempotent.class)) {MapresultMap = TokenUtil.checkToken(httpServletRequest); boolean result = (boolean) resultMap.get("result");String json = (String) resultMap.get("json");if (!result) {JsonUtil.writeJsonToPage(httpServletResponse, json);}return result;} else {return true;}}@Overridepublic void postHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, Object o,ModelAndView modelAndView) {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, Exception e) {}}
@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(idempotentInterceptor()).addPathPatterns("/**");}@Beanpublic IdempotentInterceptor idempotentInterceptor() {return new IdempotentInterceptor();}}
•最后新建一个controller,就可以愉快地进行测试了:
@RestController@RequestMapping("/idempotent")public class IdempotentApiController {@NeedIdempotent@GetMapping("/hello")public String hello() {return "are you ok?";}@GetMapping("/token")public String token() {return TokenUtil.createToken();}}
访问/token,不需要什么校验,访问/hello,就会自动幂等,每一次访问都要先获取token,一个token不能用两次。
以上就是关于“怎么用token机制实现接口自动幂等”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注创新互联行业资讯频道。