SpringBoot + Redis如何解决重复提交问题(幂等)

网友投稿 952 2022-11-15

SpringBoot + Redis如何解决重复提交问题(幂等)

SpringBoot + Redis如何解决重复提交问题(幂等)

目录幂等:解决方案:一、搭建Redis服务二、自定义注解三、Token创建和校验四、-配置五、正常Sevice类六、Controller类七、测试

在开发中,一个对外暴露的接口可能会面临瞬间的大量重复请求,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等

幂等:

任意多次执行所产生的影响均与一次执行的影响相同。最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。

解决方案:

数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据

token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token(本次案例使用)

悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)

先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行

一、搭建Redis服务

package com.ckw.idempotence.service;

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 9:42

* @description: redis工具类

*/

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

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

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

import org.springframework.data.redis.serializer.RedisSeriamfHqHHFulizer;

import org.springframework.data.redis.serializer.StringRedisSerializer;

import org.springframework.stereotype.Component;

import java.io.Serializable;

import java.util.concurrent.TimeUnit;

/**

* redis工具类

*/

@Component

public class RedisService {

private RedisTemplate redisTemplate;

@Autowired(required = false)

public void setRedisTemplate(RedisTemplate redisTemplate) {

RedisSerializer stringSerializer = new StringRedisSerializer();

redisTemplate.setKeySerializer(stringSerializer);

redisTemplate.setValueSerializer(stringSerializer);

redisTemplate.setHashKeySerializer(stringSerializer);

redisTemplate.setHashValueSerializer(stringSerializer);

this.redisTemplate = redisTemplate;

}

/**

* 写入缓存

*

* @param key

* @param value

* @return

*/

public boolean set(final String key, Object value) {

boolean result = false;

try {

ValueOperations operations = redisTemplate.opsForValue();

operations.set(key, value);

result = true;

http:// } catch (Exception e) {

e.printStackTrace();

}

return result;

}

/**

* 写入缓存设置时效时间

*

* @param key

* @param value

* @return

*/

public boolean setEx(final String key, Object value, Long expireTime) {

boolean result = false;

try {

ValueOperations operations = redisTemplate.opsForValue();

operations.set(key, value);

redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);

result = true;

} catch (Exception e) {

e.printStackTrace();

}

return result;

}

/**

* 判断缓存中是否有对应的value

*

* @param key

* @return

*/

public boolean exists(final String key) {

return redisTemplate.hasKey(key);

}

/**

* 读取缓存

* @param key

* @return

*/

public Object get(final String key) {

Object o = null;

ValueOperations valueOperations = redisTemplate.opsForValue();

return valueOperations.get(key);

}

/**

* 删除对应的value

* @param key

*/

public Boolean remove(final String key) {

if(exists(key)){

return redisTemplate.delete(key);

}

return false;

}

}

二、自定义注解

作用:-拦截请求时,判断调用的地址对应的Controller方法是否有自定义注解,有的话说明该接口方法进行 幂等

package com.ckw.idempotence.annotion;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 9:55

* @description:

*/

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

public @interface AutoIdempotent {

}

三、Token创建和校验

package com.ckw.idempotence.service;

import com.ckw.idempotence.exectionhandler.BaseException;

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.UUID;

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 9:56

* @description: token服务

*/

@Service

public class TokenService {

@Autowired RedisService redisService;

//创建token

public String createToken() {

//使用UUID代表token

UUID uuid = UUID.randomUUID();

String token = uuid.toString();

//存入redis

boolean b = redisService.setEx(token, token, 10000L);

return token;

}

//检验请求头或者请求参数中是否有token

public boolean checkToken(HttpServletRequest request) {

String token = request.getHeader("token");

//如果header中是空的

if(StringUtils.isEmpty(token)){

//从request中拿

token = request.getParameter("token");

if(StringUtils.isEmpty(token)){

throw new BaseException(20001, "缺少参数token");

}

}

//如果从header中拿到的token不正确

if(!redisService.exists(token)){

throw new BaseException(20001, "不能重复提交-------token不正确、空");

}

//token正确 移除token

if(!redisService.remove(token)){

throw new BaseException(20001, "token移除失败");

}

return true;

}

}

这里用到了自定义异常和自定义响应体如下

自定义异常:

package com.ckw.idempotence.exectionhandler;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

/**

* @author ckw

* @version 1.0

* @date 2020/5/16 20:58

* @description: 自定义异常类

*/

@Data

@AllArgsConstructor

@NoArgsConstructor

public class BaseException extends RuntimeException {

private Integer code;

private String msg;

}

设置统一异常处理:

package com.ckw.idempotence.exectionhandler;

import com.ckw.idempotence.utils.R;

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

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

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

/**

* @author ckw

* @version 1.0

* @date 2020/5/16 20:45

* @description: 统一异常处理器

*/

@ControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)

@ResponseBody

public R error(Exception e){

e.printStackTrace();

return R.error();

}

@ExceptionHandler(BaseException.class)

@ResponseBody

public R error(BaseException e){

e.printStackTrace();

return R.error().message(e.getMsg()).code(e.getCode());

}

}

自定义响应体:

package com.ckw.idempotence.utils;

import lombok.Data;

import java.util.HashMap;

import java.util.Map;

/**

* @author ckw

* @version 1.0

* @date 2020/5/16 18:35

* @description: 返回结果

*/

@Data

public class R {

private Boolean success;

private Integer code;

private String message;

private Map data = new HashMap();

private R() {

}

//封装返回成功

public static R ok(){

R r = new R();

r.setSuccess(true);

r.setCode(ResultCode.SUCCESS);

r.setMessage("成功");

return r;

}

//封装返回失败

public static R error(){

R r = new R();

r.setSuccess(false);

r.setCode(ResultCode.ERROR);

r.setMessage("失败");

return r;

}

public R success(Boolean success){

this.setSuccess(success);

return this;

}

public R message(String message){

this.setMessage(message);

return this;

}

public R code(Integer code){

this.setCode(code);

return this;

}

public R data(String key, Object value){

this.data.put(key, value);

return this;

}

public R data(Map map){

this.setData(map);

return this;

}

}

自定义响应码:

package com.ckw.idempotence.utils;

import lombok.Data;

import java.util.HashMap;

import java.util.Map;

/**

* @author ckw

* @version 1.0

* @date 2020/5/16 18:35

* @description: 返回结果

*/

@Data

public class R {

private Boolean success;

private Integer code;

private String message;

private Map data = new HashMap();

private R() {

}

//封装返回成功

public static R ok(){

R r = new R();

r.setSuccess(true);

r.setCode(ResultCode.SUCCESS);

r.setMessage("成功");

return r;

}

//封装返回失败

public static R error(){

R r = new R();

r.setSuccess(false);

r.setCode(ResultCode.ERROR);

r.setMessage("失败");

return r;

}

public R success(Boolean success){

this.setSuccess(success);

return this;

}

public R message(String message){

this.setMessage(message);

return this;

}

public R code(Integer code){

this.setCode(code);

return this;

}

public R data(String key, Object value){

this.data.put(key, value);

return this;

}

public R data(Map map){

this.setData(map);

return this;

}

}

四、-配置

1、-配置类

package com.ckw.idempotence.config;

import com.ckw.idempotence.interceptor.AutoIdempotentInterceptor;

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;

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 10:07

* @description: -配置类

*/

@Configuration

public class WebConfiguration implements WebMvcConfigurer {

@Autowired

private AutoIdempotentInterceptor autoIdempotentInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(autoIdempotentInterceptor);

}

}

2、-类

package com.ckw.idempotence.interceptor;

import com.ckw.idempotence.annotion.AutoIdempotent;

import com.ckw.idempotence.service.TokenService;

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 javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.lang.reflect.Method;

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 10:11

* @description: 拦截重复提交数据

*/

@Component

public class AutoIdempotentInterceptor implements HandlerInterceptor {

@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();

//拿到方法上面的自定义注解

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

//如果不等于null说明该方法要进行幂等

if(null != annotation){

return tokenService.checkToken(request);

}

return true;

}

}

五、正常Sevice类

package com.ckw.idempotence.service;

import org.springframework.stereotype.Service;

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 10:04

* @description:

*/

@Service

public class TestService {

public String testMethod(){

return "正常业务逻辑";

}

}

六、Controller类

package com.ckw.idempotence.controller;

import com.ckw.idempotence.annotion.AutoIdempotent;

import com.ckw.idempotence.service.TestService;

import com.ckw.idempotence.service.TokenService;

import com.ckw.idempotence.utils.R;

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

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

/**

* @author ckw

* @version 1.0

* @date 2020/6/11 9:58

* @description:

*/

@RestController

@CrossOrigin

@RequestMapping("/Idempotence")

public class TestController {

@Autowired

private TokenService tokenService;

@Autowired

private TestService testService;

@GetMapping("/getToken")

public R getToken(){

String token = tokenService.createToken();

return R.ok().data("token",token);

}

//相当于添加数据接口(测试时 连续点击添加数据按钮 看结果是否是添加一条数据还是多条数据)

@AutoIdempotent

@PostMapping("/test/addData")

public R addData(){

String s = testService.testMethod();

return R.ok().data("data",s);

}

}

七、测试

第一次点击:

第二次点击:

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

上一篇:MapReduce在集群执行任务时报错:Initia
下一篇:CAD绘制填充并设置填充透明度
相关文章

 发表评论

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