神领物流——支付流程

开始准备:拉取快递员前端代码

git clone http://git.sl-express.com/sl/project-wl-kuaidiyuan-uniapp-vue3.git

1.统一下单

支付流程

在这里插入图片描述

1.1业务流程

在这里插入图片描述

1.2交易幂等性

幂等性就是针对同一个请求,不管该请求被提交了多少次,该请求都将被视为同一个请求,服务端不应该将同一个请求进行多次处理,以确认处理逻辑的正确性,针对交易性系统幂等性的设计尤为重要,否则由于网络或服务器处理超时等问题,就会造成交易混乱,最严重的后果就是乱扣用户的钱,造成投诉満天飞。

在这里插入图片描述

  • 如果根据订单号查询交易单数据,如果不存在说明新交易单,生成交易单号后直接返回,这里的交易单号也是使用雪花id。

  • 如果支付状态是已经【支付成功】或是【免单 - 不需要支付】,直接抛出异常。

  • 如果支付状态是【付款中】,此时有两种情况。

  • 如果支付渠道相同(此前使用支付宝付款,本次也是使用支付宝付款),这种情况抛出异常。

  • 如果支付渠道不同,我们是允许在生成二维码后更换支付渠道,此时需要重新生成交易单号,此时交易单号与id将不同。

  • 如果支付状态是【取消订单】或【挂账】,将id设置为原交易号,交易号重新生成,这样做的目的是既保留了原订单的交易号,又可以生成新的交易号(不重新生成的话,没有办法在支付平台进行支付申请),与之前不会有影响。

1.3HandlerFactory

这里是采用了工厂+反射模式来实现的 由于每个平台的支付的参数和返回值都不一样,所以没办法对其进行共用,只要是每个平台都需要去编写一个实现类。

集成关系

在这里插入图片描述

在这里插入图片描述

1.4分布式锁

由于下单需要保证数据不重复,要求在同一时刻,同一任务只在一个节点上运行,即保证某一方法同一时刻只能被一个线程执行。在单机环境中,应用是在同一进程下的,只需要保证单进程多线程环境中的线程安全性,通过 JAVA 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。而在多机部署环境中,不同机器不同进程,就需要在多进程下保证线程的安全性了。因此,分布式锁应运而生。

分布式锁需满足四个条件
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。
  4. 具有容错性。只要大多数Redis节点正常运行,客户端就能够获取和释放锁。

Redis分布式锁的缺点

Redis分布式锁会有个缺陷,就是在Redis哨兵模式下:

客户端1 对某个 master节点 写入了redisson锁,此时会异步复制给对应的 slave节点。但是这个过程中一旦发生master节点宕机,主备切换,slave节点从变为了 master节点。

这时 客户端2 来尝试加锁的时候,在新的master节点上也能加锁,此时就会导致多个客户端对同一个分布式锁完成了加锁。

这时系统在业务语义上一定会出现问题, 导致各种脏数据的产生

缺陷 在哨兵模式或者主从模式下,如果 master实例宕机的时候,可能导致多个客户端同时完成加锁。

1.5锁的区别

Redisson的分布式锁不仅仅提供了常规锁的功能,还包括以下特性:

  • 可重入锁:同一线程可以多次获取同一个锁。
  • 公平锁:基于Redis的有序集合实现,保证等待最久的线程最先获取锁。
  • 联锁:支持同时获取多个锁,防止死锁。
  • 红锁:在多个Redis节点上获取锁,确保高可用性。
  • 读写锁:支持读锁和写锁,允许多个读操作同时进行。

1.5.1可重入锁

什么是 “可重入”,可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁

/**
     * @create by: 陈海彬
     * @description: 测试可重入锁
     * 按名称返回锁实例。实现非公平锁定,因此不能保证线程的获取顺序。为了提高故障转移期间的可靠性,
     * 所有操作都等待传播到所有Redis从属设备。
     * @create time: 2024/2/4 21:55
     */
@Test
public void testReentrantLock() {
    RLock lock = redissonClient.getLock("anyLock");
    try {
        // 1.最常见的使用方法。
        lock.lock();
        // 2.支持过期解锁功能,10秒钟以后自动解锁,无需调用unlock方法手动释放锁
        lock.lock(10, TimeUnit.SECONDS);
        // 3.尝试加锁,最多等待3秒,上锁以后10秒自动解锁
        boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
        if (res) {
            // 获取锁成功
            System.out.println("获取锁成功");
        }
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        lock.unlock();
    }
}

1.5.2公平锁

Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程

@Test
public void testFairLock() {
    // 获取公平锁————实现了公平的锁定,因此它保证了线程的获取顺序
    RLock fairLock = redissonClient.getFairLock("anyLock");
    try {
        // 最常见的方法
        fairLock.lock();

        // 支持过期解锁功能,10秒后真的解锁,无需调用unlock方法解锁
        fairLock.lock(10, TimeUnit.SECONDS);

        // 尝试加锁,最多等待100秒,上锁10秒自动解锁
        boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        fairLock.unlock();
    }
}

1.5.3联锁

可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例

@Test
public void test() {
    // 1.获取不同的锁
    RLock lock1 = redissonClient.getLock("lock1");
    RLock lock2 = redissonClient.getLock("lock2");
    RLock lock3 = redissonClient.getLock("lock3");

    RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);

    try {
        // 同时加锁:lock1、lock2、lock3,所有的锁都上锁成功才算成功
        lock.lock();

        // 尝试加锁,最多等待100秒,上锁10秒后自动解锁
        lock.tryLock(100, 10, TimeUnit.SECONDS);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

1.5.4红锁

与联锁类似,但是红锁可以从不同的redissonClient获取到锁,并且红锁在大部分节点上锁成功就算成功。

@Test
public void testRedLock() {
    // 1.获取不同的锁
    RLock lock1 = redissonClient.getLock("lock1");
    RLock lock2 = redissonClient.getLock("lock2");
    RLock lock3 = redissonClient.getLock("lock3");

    RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);

    try {
        // 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。
        lock.lock();

        // 尝试加锁,最多等待100秒,上锁10秒后自动解锁
        lock.tryLock(100, 10, TimeUnit.SECONDS);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

1.5.5读写锁

该对象允许同时有多个读取锁,但是最多只能有一个写入锁

@Test
public void testReadWriteLock() {
    RReadWriteLock lock = redissonClient.getReadWriteLock("anyLock");

    // 常用的方法
    lock.readLock().lock();
    lock.writeLock().lock();


    // 支持过期解锁功能
    lock.readLock().lock(10, TimeUnit.SECONDS);
    lock.writeLock().lock(10, TimeUnit.SECONDS);
}

2.查询订单

用户创建交易后,到底有没有支付成功,还是取消支付,这个可以通过查询交易单接口查询的,支付宝和微信也都提供了这样的接口服务。

对于其提供的两种方式,个人觉得提供微信的方式更为妥当,因为其增加了支付超时处理。

2.1流程图

在这里插入图片描述

2.2 支付宝交易查询

在这里插入图片描述

2.3微信交易查询

在这里插入图片描述

3.申请退款

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,

将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

支持一个交易单分多次退款,退款金额总额不能大于总支付的总金额,并且最多20次退款

3.1流程图

在这里插入图片描述

3.2微信申请退款

由于支付宝相对逻辑较简单,此处只画微信的逻辑图

微信v3 版本最大区别是参数为JSON
在这里插入图片描述

4.查询退款

4.1流程图

在这里插入图片描述

4.2微信退款查询

在这里插入图片描述

5.下载交易账单

发起相应请求然后将结果通过EasyExcel生成即可。

6.同步支付状态

  • 在用户支付成功后,【步骤4】支付平台会通知【支付微服务】,这个就是异步通知,需要在【支付微服务】中对外暴露接口
  • 由于网络的不确定性,异步通知可能出现故障【步骤6】
  • 支付微服务中需要有定时任务,查询正在支付中的订单的状态
  • 可以看出【异步通知】与【主动定时查询】这两种方式是互不的,缺一不可。

6.1异步通知

首先设置好回调地址

6.1.1更新交易单

在这里插入图片描述

6.1.2微信异步通知

在这里插入图片描述

6.2定时任务

定时任务采用xxl-job分布式任务来使用

xxl-job支持的路由策略非常丰富:

  • FIRST(第一个):固定选择第一个机器;
  • LAST(最后一个):固定选择最后一个机器;
  • ROUND(轮询):在线的机器按照顺序一次执行一个
  • RANDOM(随机):随机选择在线的机器;
  • CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
  • LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
  • LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举;
  • FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
  • BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
  • SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;

6.2.1分片广播方式查询支付状态

每次最多查询{tradingCount}个未完成的交易单,交易单id与shardTotal取模,值等于shardIndex进行处理

:最久未使用的机器优先被选举;

  • FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
  • BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
  • SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;

6.2.1分片广播方式查询支付状态

每次最多查询{tradingCount}个未完成的交易单,交易单id与shardTotal取模,值等于shardIndex进行处理

在这里插入图片描述

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零晚.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值