springboot 接口幂等


springboot 接口幂等

                            

                                    

********************

接口幂等

                                                    

幂等:多次操作与一次操作,产生的结果相同

应用:解决表单重复提交造成的数据重复、超时重试引发的重复操作等问题

               

数据插入幂等

对数据记录中的字段做唯一性限制(如订单号),将字段设为主键、或者添加唯一索引

                           

数据更新幂等

#使用乐观锁
update t set value=new_value and version=version+1 
where value=#{value} and version=#{version}
重复提交,由于version值已经改变,后续更新操作不会执行


#业务自带状态标识(如订单status),处理方式类似乐观锁 
status => 0:已下单、1:已支付、2已发货、3:确认收货、4:退款中、5:已退款
update order set status=3 where order_id=#{orderId} and status=2
重复提交时,由于status已经改变,后续更新操作不会执行

                       

token 标识

                

客户端获取token,将token设置为下次请求的header

请求到达接口前先进行检验,如果没有token、或者token不等,则直接返回

如果token检验通过,执行接口,并返回操作结果

                                          

                                                        

********************

示例:token实现接口幂等

                    

****************

annotation 层

                                   

Idempotent

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}

                      

****************

util 层

                  

StoreUtil

public class StoreUtil {

    private static final Map<String,String> map=new HashMap<>();

    public static void putValue(String key, String value){
        map.put(key, value);
    }

    public static String getValue(String key){
        return map.get(key);
    }

    public static void removeValue(String key){
        map.remove(key);
    }
}

                         

****************

service 层

                   

IdempotentService

@Service
public class IdempotentService {

    public Map<String,String> createToken(){
        Map<String,String> result = new HashMap<>();

        String tokenName = RandomStringUtils.randomAlphanumeric(6);
        String tokenValue = RandomStringUtils.randomNumeric(10);
        result.put("tokenName",tokenName);
        result.put("tokenValue",tokenValue);

        StoreUtil.putValue(tokenName,tokenValue);

        return result;
    }

    public boolean checkToken(String tokenName, String tokenValue){
        if (tokenName==null || tokenValue==null){
            return false;
        }

        synchronized (this){
            if (tokenValue.equals(StoreUtil.getValue(tokenName))){
                StoreUtil.removeValue(tokenName);

                return true;
            }
        }

        return false;
    }
}

                                

****************

interceptor 层

                   

CustomInterceptor

@Component
public class CustomInterceptor implements HandlerInterceptor {

    @Resource
    private IdempotentService idempotentService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod){
            Method method = ((HandlerMethod) handler).getMethod();

            if (method.isAnnotationPresent(Idempotent.class)){
                String tokenName = request.getHeader("tokenName");
                String tokenValue = request.getHeader("tokenValue");
                System.out.println(tokenName+"  "+tokenValue);

                if (!idempotentService.checkToken(tokenName, tokenValue)){
                    JSONObject object=new JSONObject();
                    object.put("code","111111");
                    object.put("status","error");
                    object.put("msg","token 检验失败");
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().println(object);

                    return false;
                }
            }
        }

        return true;
    }
}

                         

****************

config 层

                   

WebConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private CustomInterceptor customInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customInterceptor);
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("index");
    }
}

                       

****************

controller 层

                    

HelloController

@RestController
public class HelloController {

    @Resource
    private IdempotentService idempotentService;

    @RequestMapping("/token")
    public Map<String, String> getToken(){
        return idempotentService.createToken();
    }

    @Idempotent
    @RequestMapping("/hello")
    public Map<String,Object> hello(String name, Integer age){
        System.out.println(name+"  "+age);

        Map<String,Object> result = new HashMap<>();
        result.put("code","000000");
        result.put("status","success");

        return result;
    }
}

                        

****************

前端页面

                       

index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>$Title$</title>
    <script src="/jquery/jquery-3.6.0.min.js"></script>
    <script src="/layui-v2.6.8/layui/layui.js"></script>
    <link rel="stylesheet" href="/layui-v2.6.8/layui/css/layui.css">
    <script>
        $(function (){
            let tokenName, tokenValue;

            $.get({
                url: "/token",
                success: function (result){
                    tokenName = result.tokenName;
                    tokenValue = result.tokenValue;
                    layer.msg(tokenName+"  "+tokenValue);

                    $("#btn").click(function (){
                        $.ajax({
                            type: "get",
                            url: "/hello",
                            headers: {
                                tokenName: tokenName,
                                tokenValue: tokenValue
                            },
                            data: {
                                name: $("#name").val(),
                                age: $("#age").val()
                            },
                            success: function (result){
                                layer.msg(result.status.toString())
                            }
                        })
                    })
                }
            })
        })
    </script>
</head>
<body>
    <div th:align="center">
        <div class="layui-form-item">
            <label class="layui-form-label">name</label>
            <div class="layui-input-block">
                <input type="text" id="name" required  lay-verify="required" placeholder="请输入name" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">age</label>
            <div class="layui-input-inline">
                <input type="text" id="age" required lay-verify="required" placeholder="请输入age" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <div class="layui-input-block">
                <button id="btn" class="layui-btn">提交</button>
            </div>
        </div>
    </div>
</body>
</html>

                      

                          

********************

使用测试

                     

localhost:8080/index

         

               

控制台输出

2021-08-25 20:20:40.608  INFO 12508 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2021-08-25 20:20:42.082  WARN 12508 --- [nio-8080-exec-9] o.a.c.util.SessionIdGeneratorBase        : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [126] milliseconds.
jkjBTq  4624310419
瓜田李下  20

                     

                             

********************

jmeter 测试

                            

获取token

                    

                      

添加线程组

                    

                   

线程组下添加 HTTP Request

          

                      

添加 HTTP Header Manager:设置请求头

          

                     

添加 View Reqults Tree:查看请求结果

          

              

               

点击运行,查看请求结果

          

           

            

           

四次请求,只有一次请求执行成功

            

                                                                    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值