一、大致流程:
客户端先发送get请求到服务端获取服务端生成的一个token,同时服务端将这个token存入到redis中用于后续做判断。客户端每向服务端发送一个请求都要在请求头或请求地址栏携带这个token,如果是第一次请求,则服务端会从redis中将这个token删除,客户端如果再携带这个token来访问服务端,因为服务端已经将这个token从reids中删除了所以后续的这些请求都为重复请求。
二、具体实现
环境搭建:引入Web和Reids依赖,配置reids参数(host,port,password)
1.自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Idempontent {
}
2.创建控制类和方法,在指定方法上添加自定义注解
@RestController
public class HelloController {
@Autowired
TokenService tokenService;
@PostMapping("/hello")
@Idempontent
public String helloPost() {
return "hello post";
}
@GetMapping("/hello")
public String helloGet() {
return "hello get";
}
@GetMapping("/generate")
public String generateToken() {
return tokenService.generateToken();
}
}
3.定义响应实体类
public class RespBean {
private String msg;
private Object data;
private Integer status;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
4.定义拦截器,获取控制类方法上有自定义注解的方法用于后续操作
@Component
public class IdempontentInterceptor implements HandlerInterceptor {
@Autowired
TokenService tokenService;
/**
* 在 Controller 中的方法执行之前会触发该方法
* @param request
* @param response
* @param handler 这个参数实际上就是你的处理器
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
//这个实际上就是具体的接口方法
HandlerMethod hm = (HandlerMethod) handler;
//获取接口方法上的 Idempontent 注解
Idempontent methodAnnotation = hm.getMethodAnnotation(Idempontent.class);
if (methodAnnotation != null) {
//说明这个接口需要进行幂等性处理
boolean result = tokenService.checkToken(request);
if (!result) {
RespBean respBean = new RespBean();
respBean.setStatus(500);
respBean.setMsg("请求重复");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(respBean));
return false;
}
}
}
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 {
}
}
5.配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
IdempontentInterceptor idempontentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idempontentInterceptor)
.addPathPatterns("/**");
}
}
6.在service中获取请求头或请求参数里的token,结合reids判断该token是否存在,如果存在则将其删除
@Service
public class TokenService {
@Autowired
RedisService redisService;
public boolean checkToken(HttpServletRequest request) {
//先从请求头中获取令牌
String token = request.getHeader("token");
//如果请求头中没有令牌,则从请求参数中获取令牌
if (token == null || "".equals(token)) {
token = request.getParameter("token");
if (token == null || "".equals(token)) {
//说明请求没有携带令牌
return false;
}
}
boolean exists = redisService.exists(token);
if (exists) {
//redis 中令牌存在
boolean delResult = redisService.deleteToken(token);
return delResult;
}
return false;
}
public String generateToken() {
String s = UUID.randomUUID().toString();
redisService.saveToken(s);
return s;
}
}
@Service
public class RedisService {
@Autowired
StringRedisTemplate redisTemplate;
public boolean exists(String token) {
return redisTemplate.hasKey(token);
}
public boolean deleteToken(String token) {
if (exists(token)) {
return redisTemplate.delete(token);
}
return false;
}
public void saveToken(String token) {
redisTemplate.opsForValue().set(token, token);
}
}
三、业务测试
1.get请求获取token
2.post第一次请求
3.多次请求
以上就是Token+Redis实现接口幂等性处理的具体流程。