安全支付有多种方案 ,以两种方案为例子(1)基于状态机/基于mysql 乐观锁(2)基于redis的分布式锁
首先是基于myql数据库乐观锁实现方案如下:
1.状态机
2.远程调用
3.使用spring 编程事务。
/**
* @program: esportingplus
* @description: 注意支付采用非事务方式运行
* (原因在于,Spring 事务具有传播特性,如果不采用非事务运行模式,一旦该方法被一个拥有事务的方法调用了,那么该方法会加入到已有的事务中,这点不处理很可怕,
* 在事务中涉及到外部三方调用,网络传输,会长时间占用数据库连接资源,导致资源不能释放,一旦在高并发情况下,很快会将数据库连接消耗殆尽,从而导致数据库处理缓慢
* 甚至崩溃,无法对外提供服务,同时会影响其他业务。
* )
* @author: dongjue
* @create: 2020-06-05 08:23
*/
@Component
@Transactional(propagation = Propagation.NEVER)
public class PayTemplete {
@Resource
TransactionTemplate transactionTemplate;
@Resource
TicketOrderMapper ticketOrderMapper;
public void pay(String orderNo) {
/**
* 此处加锁采用状态机实现方式,多线程,或多进程环境中,数据库排队处理只会有一条数据修改成功,返回影响行数1.(线程安全)
*/
Boolean lock = transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
boolean lock = 1== ticketOrderMapper.updateStatusByOrderNoAndStatus(orderNo, OrderStatusEnum.LOCK_PAY.getCode(), OrderStatusEnum.PRE_PAY.getCode());
if(lock){
/**
* 插入流水记录,记录流水处理状态为受理中
*/
}
return lock;
}
});
if(!lock){ return; }
/**
* 远程调用
* 一般银行提供的服务是非幂等的(一个订单发起多次请求,返回不一致的结果,如果是幂等的则会返回一致的结果
* (例如:第一次请求失败,二次请求相同订单号可能返回已存在或已处理,请换一个订单,如果幂等则需返回同首次一样的成功或失败))
* 因此采用生成流水号,流水表方式处理(中间表兼容银行非幂等处理问题)。
*/
//........远程调用..........
/**
* 本地结构状态更新(此处省略简单的成功失败状态判断处理)
*
* 更新订单处理状态及订单流水状态(需同步)
*/
transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
// 更新订单状态
// 更新流水状态
return true;
}
});
}
}
注意:此方法注意几个细节(1)数据库连接资源问题 (2)非幂等行问题 (3)数据状态一致性问题
后续处理...