云博:架构设计 | 接口幂等性原则,防重复提交Token治理

admin 5个月前 (05-23) 科技 46 0

本文源码:GitHub·点这里 || GitEE·点这里

一、幂等性观点

1、幂等简介

编程中一个幂等操作的特点是其随便多次执行所发生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会发生同样的作用影响。

2、HTTP请求

遵照Http协议的请求,越来越强调Rest请求气概,可以更好的规范和明白接口的设计

GET:用于获取资源,不应有副作用,所以是幂等的;

POST:用于建立资源,重复提交POST请求可能发生两个差别的资源,有副作用不知足幂等性;

PUT:用于更新操作,重复提交PUT请求只会对其URL中指定的资源有副作用,知足幂等性;

DELETE:用于删除资源,有副作用,但它应该知足幂等性;

HEAD:和GET本质是一样的,但HEAD不含有出现数据,仅是HTTP头信息,没有副作用,知足幂等性;

OPTIONS:用于获取当前URL所支持的请求方式,知足幂等性;

二、场景营业剖析

1、订单支付

现实开发中,经常会面临订单支付问题,基本流程如下:

  • 客户端提议订单支付请求 ;
  • 支付前系统内陆相关营业处置 ;
  • 请求第三方支付服务执行扣款;
  • 第三方支付返回处置效果;
  • 内陆服务基于支付效果响应客户端;

该营业流程中要处置相当庞大的问题,好比事务,分布式事务,接口延迟超时,客户端重复提交等等,这里只基于幂等接口角度来看该流程,其他问题后续再聊。

2、幂等接口

当上述流程的支付请求有明确效果的时刻:失败或乐成,这样营业流程都好处置,然则例如支付场景若是请求超时,若何判断服务的效果状态:客户端请求超时,内陆服务超时,请求支付超时,支付回调超时,客户端响应超时等等。

这就需要设计流程化的状态治理。

3、基础操作案例

模拟治理上述流程,设计幂等接口:

表结构设计

CREATE TABLE `dp_order_state` (
	`order_id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
	`token_id` VARCHAR (50) DEFAULT NULL COMMENT '防重复提交',
	`state` INT (1) DEFAULT '1' COMMENT '1建立订单,2内陆营业,3支付营业',
	PRIMARY KEY (`order_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '订单状态表';

CREATE TABLE `dp_state_record` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
	`order_id` BIGINT (20) NOT NULL COMMENT '订单id',
	`state_dec` VARCHAR (50) DEFAULT NULL COMMENT '状态形貌',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '状态纪录表';

模拟营业流程

将订单建立,内陆营业,支付营业,离开分段治理提交。分阶段测试异常熔断的营业。

@Service
public CLass OrderServiceImpl implements OrderService {

    @Resource
    private OrderStateMAPPer orderStateMapper ;
    @Resource
    private StateRecordMapper stateRecordMapper ;

    @Override
    public OrderState queryOrder(OrderState orderState) {
        Map<String,Object> paramMap = new HashMap<>() ;
        paramMap.put("order_id",orderState.getOrderId());
        List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
        if (orderStateList != null && orderStateList.size()>0){
            return orderStateList.get(0) ;
        }
        return null ;
    }

    @Override
    public boolean createOrder(OrderState orderState) {
        int sAVeRes = orderStateMapper.insert(orderState);
        if (saveRes > 0){
            saveStateRecord(orderState.getOrderId(),"订单建立乐成");
        }
        return saveRes > 0 ;
    }

    @Override
    public boolean localBiz(OrderState orderState) {
        orderState.setState(2);
        int updateRes = orderStateMapper.updateState(orderState) ;
        if (updateRes > 0){
            saveStateRecord(orderState.getOrderId(),"内陆营业乐成");
        }
        return updateRes > 0;
    }

    @Override
    public boolean paymentBiz(OrderState orderState) {
        orderState.setState(3);
        int updateRes = orderStateMapper.updateState(orderState) ;
        if (updateRes > 0){
            saveStateRecord(orderState.getOrderId(),"支付营业乐成");
        }
        return updateRes > 0;
    }

    private void saveStateRecord (Long orderId,String stateDec){
        StateRecord stateRecord = new StateRecord() ;
        stateRecord.setOrderId(orderId);
        stateRecord.setStateDec(stateDec);
        stateRecordMapper.insert(stateRecord) ;
    }
}

测试接口

凭据订单状态,分段抵偿执行未完成的营业,若是该订单已经完成,多次提交不影响最终效果。

@Api(value = "OrderController")
@RestController
public class OrderController {

    @Resource
    private OrderService orderService ;

    @PostMapping("/subMitOrder")
    public String submitOrder (OrderState orderState){
        OrderState orderState01 = orderService.queryOrder(orderState) ;
        if (orderState01 == null){
            // 正常营业流程
            orderService.createOrder(orderState) ;
            orderService.localBiz(orderState) ;
            orderService.paymentBiz(orderState) ;
        } else {
            switch (orderState01.getState()){
                case 1:
                    // 订单建立乐成:后推执行内陆和支付营业
                    orderService.localBiz(orderState01) ;
                    orderService.paymentBiz(orderState01) ;
                    break ;
                case 2:
                    // 订单内陆营业乐成:后推执行支付营业
                    orderService.paymentBiz(orderState01) ;
                    break ;
                default:
                    break ;
            }
        }
        return "success" ;
    }
}

絮叨一句:现实开发中,该流程是不会由页面多次提交完成,订单是不能重复提交的,下面会演示若何控制,这里营业是执行后推到完成,也可能营业向前清算,把整个流程置为失败,这里涉及要害状态判断,要选取一个状态作为乐成或失败的标识,判断后续操作流程。在分布式系统中这种庞大流程最难处置的是分布式事务,最终一致性问题,后续再聊。

三、接口重复提交

1、表单重复提交

在现实情况中,接口若是处置时间过长,用户可能会点击多次提交按钮,导致数据重复。

常见的一个解决方案:在表单提交中隐藏一个token_id参数,一起提交到接口服务中,数据库存储订单和关联的tokenId,若是多次提交,直接返回页面提醒信息即可。

2、演示案例

订单关联Token查询

@Service
public class OrderServiceImpl implements OrderService {
    @Override
    public Boolean queryToken(OrderState orderState) {
        Map<String,Object> paramMap = new HashMap<>() ;
        paramMap.put("order_id",orderState.getOrderId());
        paramMap.put("token_id",orderState.getTokenId());
        List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
        return orderStateList.size() > 0 ;
    }
}

测试接口

@RestController
public class OrderController {
    @Resource
    private OrderService orderService ;

    @PostMapping("/repeatSub")
    public String repeatSub (OrderState orderState){
        boolean flag = orderService.queryToken(orderState) ;
        if (flag){
            return "请勿重复提交订单" ;
        }
        return "success" ;
    }
}

四、源代码地址

GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推荐阅读:数据和架构治理

序号 题目
A01 数据源治理:主从库动态路由,AOP模式读写星散
A02 数据源治理:基于JDBC模式,适配和治理动态数据源
A03 数据源治理:动态权限校验,表结构和数据迁徙流程
A04 数据源治理:关系型分库分表,列式库分布式盘算
A05 数据源治理:PostGreSQL环境整合,JSON类型应用
A06 数据源治理:基于DataX组件,同步数据和源码剖析
A07 数据源治理:OLAP查询引擎,ClickHouse集群化治理
C01 架构基础:单服务.集群.分布式,基本区别和联系
C02 架构设计:分布式营业系统中,全局ID天生计谋
C03 架构设计:分布式系统调剂,Zookeeper集群化治理
,

SuNBet

Sunbet www.169894.com Sunbet是进入Sunbet的主力站点。Sunbet开放Sunbet会员开户网址(www.sunbet.us)、Sunbet代理开户(www.sunbet.us)、Sunbet手机版下载(www.sunbet.us)、Sunbet电脑客户端下载(www.sunbet.us)等业务。

皇冠APP声明:该文看法仅代表作者自己,与本平台无关。转载请注明:云博:架构设计 | 接口幂等性原则,防重复提交Token治理

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:734
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1068
  • 评论总数:403
  • 浏览总数:23259