当前位置: 首页 > 科技观察

这个写的太好了!SpringBoot+Redis实现接口幂等性

时间:2023-03-21 23:31:06 科技观察

本文转载自微信公众号“小明菜市场”,可通过以下二维码关注。转载本文请联系小明菜市场公众号。引入幂等性的概念就是任意多次执行的影响和一次执行的影响是一样的。按照这个意思,最终的解释就是对数据库的影响只能是一次性的,不能重复。方法如下:为数据库创建唯一索引token机制悲观锁还是乐观锁先查询再判断小小主要是引入Redis实现自动幂等。其原理如下图所示。实现过程引入maven依赖org.springframework.bootspring-boot-starter-data-redisspring配置文件写入server.port=8080core.datasource.druid.enabled=truecore.datasource.druid.url=jdbc:mysql://192.168.1.225:3306/?useUnicode=true&characterEncoding=UTF-8core.datasource.druid.username=rootcore.datasource.druid。password=core.redis.enabled=truespring.redis.host=192.168.1.225#本机redis地址spring.redis.port=16379spring.redis.database=3spring.redis.jedis.pool.max-active=10spring.redis.jedis.pool.max-idle=10spring.redis.jedis.pool.max-wait=5sspring.redis.jedis.pool.min-idle=10引入Redis在Springboot中引入redis相关状态,SpringBoot会后面使用封装好的RedisTemplatepackagecn.smallmartial.demo.utils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.core.ValueOperations;importorg.springframework.stereotype.Component;importjava.io.Serializable;importjava.util.Objects;importjava.util.concurrent.TimeUnit;/***@Authorsmallmartial*@Date2020/4/16*@Emailsmallmarital@qq.com*/@ComponentpublicclassRedisUtil{@AutowiredprivateRedisTemplateredisTemplate;/***写入绑定**@paramkey*@paramvalue*@return*/publicbooleanset(finalStringkey,Objectvalue){booleanresult=false;try{ValueOperationsoperations=redisTemplate.opsForValue();operations.set(key,value);result=true;}catch(Exceptione){e.printStackTrace();}returnresult;}/***写入绑定保存设置时间**@paramkey*@paramvalue*@paramexpireTime*@返回*/publicbooleansetEx(finalStringkey,Objectvalue,longexpireTime){booleanresult=false;try{ValueOperationsoperations=redisTemplate.opsForValue();operations.set(key,value);redisTemplate.expire(key,expireTime,TimeUnit.SECONDS);result=true;}catch(Exceptione){e.printStackTrace();}returnresult;}/***读取存储**@paramkey*@return*/publicObjectget(finalStringkey){Objectresult=null;ValueOperationsoperations=redisTemplate.opsForValue();result=operations.get(key);returnresult;}/***删除对应的值**@paramkey*/publicbooleanremove(finalStringkey){if(exists(key)){Booleandelete=redisTemplate.delete(key);returndelete;}returnfalse;}/***判断key是否存在**@paramkey*@return*/publicbooleanexists(finalStringkey){booleanresult=false;ValueOperationsoperations=redisTemplate.opsForValue();if(Objects.nonNull(operations.get(key))){result=true;}returnresult;}}自定义注解自定义一个注解,定义该注解的目的是将其添加到需要幂等的方法中。只要一个方法被注解,它就会自动实现幂等操作代码如下@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public@interfaceAutoIdempotent{}Token的创建和token服务接口的实现,我们新建一个接口,创建token服务,主要有两个方法,一个是用来创建token的,一个是用来验证token的;}token实现类,token引用服务实现类,token引用redis服务,使用随机算法工具类创建token生成随机uuid字符串,然后放入redis。如果插入成功,则返回令牌。验证方式是从获取header中的token值。如果不存在,则直接抛出异常。这个异常信息可以直接截取返回给前端。packagecn.smallmartial.demo.service.impl;importcn.smallmartial.demo.bean.RedisKeyPrefix;importcn.smallmartial.demo.bean.ResponseCode;importcn.smallmartial.demo.exception.ApiResult;importcn.smallmartial.demo.exception.BusinessException;importcn.smallmartial.demo.service.TokenService;importcn.smallmartial.demo.utils.RedisUtil;importio.netty.util.internal.StringUtil;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;importjavax.servlet.http.HttpServletRequest;importjava.util.Random;importjava.util.UUID;/***@Authorsmallmartial*@Date2020/4/16*@Emailsmallmarital@qq.com*/@ServicepublicclassTokenServiceImplimplementsTokenService{@AutowiredprivateRedisUtilredisService;/***创建令牌**@return*/@OverridepublicStringcreateToken(){Stringstr=UUID.randomUUID().toString().replace("-","");StringBuildertoken=newStringBuilder();try{token.append(RedisKeyPrefix.TOKEN_PREFIX).append(str);redisService.setEx(token.toString(),token.toString(),10000L);booleanempty=StringUtils.isEmpty(token.toString());if(!empty){returntoken。toString();}}catch(Exceptionex){ex.printStackTrace();}returnnull;}/***检查令牌**@paramrequest*@return*/@OverridepublicbooleancheckToken(HttpServletRequestrequest)throwsException{Stringtoken=request.getHeader(RedisKeyPrefix.TOKEN_NAME);if(StringUtils.isEmpty(token)){//tokentoken不存在header=request.getParameter(RedisKeyPrefix.TOKEN_NAME);if(StringUtils.isEmpty(token)){//tokenthrownewBusinessException(ApiResult.BADARGUMENT);}}if(!redisService.exists(token)){thrownewBusinessException(ApiResult.REPETITIVE_OPERATION);}booleanremove=redisService.remove(token);if(!remove){thrownewBusinessException(ApiResult.REPETITIVE_OPERATION);}returntrue;}}拦截器配置用于拦截前端token,判断前端token是否有效@ConfigurationpublicclassWebMvcConfigurationextendsWebMvcConfigurationSupport{@BeanpublicAuthInterceptorauthInterceptor(){returnnewAuthInterceptor();}/***拦截器配置**@paramregistry*/@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(authInterceptor());//.addPathPatterns("/ksb/**")//.excludePathPatterns("/ksb/auth/**","/api/common/**","/error","/api/*");super.addInterceptors(registry);}@OverridepublicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");super.addResourceHandlers(registry);}}拦截处理器:主要用于拦截扫描到Autoldempotent到注解方法,然后调用tokenService的checkToken方法来验证token是否正确。如果捕获到异常,会将异常信息渲染成json返回给前端。这部分代码主要是链接到自定义注解部分。其主要代码如下所示@Slf4jpublicclassAuthInterceptorextendsHandlerInterceptorAdapter{@AutowiredprivateTokenServicetokenService;@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{if(!(handlerinstanceofHandlerMethod)){returntrue;}HandlerMethodhandlerMethod=(HandlerMethod)handler;Methodmethod=handlerMethod.getMethod();//ScanAutoIdempotentmethodAnnotation=method.getAnnotation(AutoIdempotent.class)markedbyApiIdempotment;if(methodAnnotation!=null){try{returntokenService.checkToken(request);//幂等性验证,验证通过则放行,验证如果失败则抛出异常,并通过统一的异常处理返回友好提示}catch(Exceptionex){thrownewBusinessException(ApiResult.REPETITIVE_OPERATION);}}returntrue;}}测试用例这里,相关测试用例模拟业务请求类并通过相关路径获取相关token,然后调用testidempot方法。该方法使用@Autoldempotent注解,拦截器将拦截所有请求。当判断处理方法有注解时,会调用TokenService中的checkToken()方法。如果出现异常,代码如下towiredprivateTokenServicetokenService;@GetMapping("/get/token")publicObjectgetToken(){Stringtoken=tokenService.createToken();returnResponseUtil.ok(token);}@AutoIdempotent@GetMapping("/test/Idempotence")publicObjecttestIdempotence(){Stringtoken="接口幂等性测试";returnResponseUtil.ok(token);}}使用浏览器访问,第一次访问使用获取的token,再次使用获取的token访问,可以看到第二次访问失败,即幂等性验证通过作者简介我是笑笑,双鱼座程序员,生活在一线城市,下期见。