写点什么

Spring-Boot-+-Redis- 实现接口幂等性,看这篇就太好了

用户头像
极客good
关注
发布于: 刚刚

/**


  • 判断 key 是否存在

  • @param key

  • @return


*/


public boolean exists(final String key) {


boolean result = false;


ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();


if (Objects.nonNull(operations.get(key))) {


result = true;


}


return result;


}


}

自定义注解

自定义一个注解,定义此注解的目的是把它添加到需要实现幂等的方法上,只要某个方法注解了其,都会自动实现幂等操作。其代码如下


@Target({ElementType.METHOD})


@Retention(RetentionPolicy.RUNTIME)


public @interface AutoIdempotent {


}

token 的创建和实现

token 服务接口,我们新建一个接口,创建 token 服务,里面主要是有两个方法,一个用来创建 token,一个用来验证 token


public interface TokenService {


/**


  • 创建 token

  • @return


*/


public String createToken();


/**


  • 检验 token

  • @param request

  • @return


*/


public boolean checkToken(HttpServletRequest request) throws Exception;


}


token 的实现类,token 中引用了服务的实现类,token 引用了 redis 服务,创建 token 采用随机算法工具类生成随机 uuid 字符串,然后放入 redis 中,如果放入成功,返回 token,校验方法就是从 header 中获取 token 的值,如果不存在,直接跑出异常,这个异常信息可以被直接拦截到,返回给前端。


package cn.smallmartial.demo.service.impl;


import cn.smallmartial.demo.bean.RedisKeyPrefix;


import cn.smallmartial.demo.bean.ResponseCode;


import cn.smallmartial.demo.exception.ApiResult;


import cn.smallmartial.demo.exception.BusinessException;


import cn.smallmartial.demo.service.TokenService;


import cn.smallmartial.demo.utils.RedisUtil;


import io.netty.util.internal.StringUtil;


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


import org.springframework.stereotype.Service;


import org.springframework.util.StringUtils;


import javax.servlet.http.HttpServletRequest;


import java.util.Random;


import java.util.UUID;


/**


  • @Author smallmartial

  • @Date 2020/4/16

  • @Email smallmarital@qq.com


*/


@Service


public class TokenServiceImpl implements TokenService {


@Autowired


private RedisUtil redisService;


/**


  • 创建 token

  • @return


*/


@Override


public String createToken() {


String str = UUID.randomUUID().toString().replace("-", "");


StringBuilder token = new StringBuilder();


try {


token.append(RedisKeyPrefix.TOKEN_PREFIX).append(str);


redisService.setEx(token.toString(), token.toString(), 10000L);


boolean empty = StringUtils.isEmpty(token.toString());


if (!empty) {


return token.toString();


}


} catch (Exception ex) {


ex.printStackTrace();


}


return null;


}


/**


  • 检验 token

  • @param request

  • @return


*/


@Override


public boolean checkToken(HttpServletRequest request) throws Exception {


String token = request.getHeader(RedisKeyPrefix.TOKEN_NAME);


if (StringUtils.isEmpty(token)) {// header 中不存在 token


token = request.getParameter(RedisKeyPrefix.TOKEN_NAME);


if (StringUtils.isEmpty(token)) {// parameter 中也不存在 token


throw new BusinessException(ApiResult.BADARGUMENT);


}


}


if (!redisService.exists(token)) {


throw new BusinessException(ApiResult.REPETITIVE_OPERATION);


}


boolean remove = redisService.remove(token);


if (!remove) {


throw new BusinessException(ApiResult.REPETITIVE_OPERATION);


}


return true;


}


}

拦截器的配置

用于拦截前端的 token,判断前端的 token 是否有效


@Configuration


public class WebMvcConfiguration extends WebMvcConfigurationSupport {


@Bean


public AuthInterceptor authInterceptor() {


return new AuthInterceptor();


}


/**


  • 拦截器配置

  • @param registry


*/


@Override


public void addInterceptors(InterceptorRegistry registry) {


registry.addInterceptor(authInterceptor());


// .


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


addPathPatterns("/ksb/**")


// .excludePathPatterns("/ksb/auth/", "/api/common/", "/error", "/api/*");


super.addInterceptors(registry);


}


@Override


public void addResourceHandlers(ResourceHandlerRegistry registry) {


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 返回给前端。这部分代码主要和自定义注解部分挂钩。其主要代码如下所示


@Slf4j


public class AuthInterceptor extends HandlerInterceptorAdapter {


@Autowired


private TokenService tokenService;


@Override


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


if (!(handler instanceof HandlerMethod)) {


return true;


}


HandlerMethod handlerMethod = (HandlerMethod) handler;


Method method = handlerMethod.getMethod();


//被 ApiIdempotment 标记的扫描


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


if (methodAnnotation != null) {


try {


return tokenService.checkToken(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示


} catch (Exception ex) {


throw new BusinessException(ApiResult.REPETITIVE_OPERATION);


}


}


return true;


}


}

测试用例

这里进行相关的测试用例 模拟业务请求类,通过相关的路径获得相关的 token,然后调用 testidempotence 方法,这个方法注解了 @Autoldempotent,拦截器会拦截所有的请求,当判断到处理的方法上面有该注解的时候,就会调用 TokenService 中的 checkToken() 方法,如果有异常会跑出,代码如下所示


/**


  • @Author smallmartial

  • @Date 2020/4/16

  • @Email smallmarital@qq.com


*/


@RestController


public class BusinessController {


@Autowired


private TokenService tokenService;


@GetMapping("/get/token")


public Object getToken(){


String token = tokenService.createToken();


return ResponseUtil.ok(token) ;


}


@AutoIdempotent


@GetMapping("/test/Idempotence")


public Object testIdempotence() {


String token = "接口幂等性测试";

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
Spring-Boot-+-Redis-实现接口幂等性,看这篇就太好了