我们的采购人员使用微信小程序采购的简化流程如下:
1、录入采购单;
2、点击支付按钮给供货商打款。
采购人员反馈当天点击付款后,收到支付失败的消息,但供货商收到了两笔款。
查看生产日志,发现同一秒调用两次支付请求:
2019-01-07 19:13:05|100.116.251.32|role=apiAdmin|id=120|username=18974060489|name=胡建军|/api/admin/purchase/payment.jhtml|success|-|2532
2019-01-07 19:13:05|100.116.251.12|role=apiAdmin|id=120|username=18974060489|name=胡建军|/api/admin/purchase/payment.jhtml|success|-|2337
后端支付的简化逻辑:
当两个线程同时进行上述流程时,首先汇集采购记录表数据,创建支付记录数据(sn不同);调用微信支付的支付接口,此时都能付款成功并都返回支付结果;将支付成功的结果回写支付记录表,再修改采购记录表数据的采购状态。此次发生异常,同一条记录不能被修改两次采购状态,整个事务回滚,导致并没有创建支付记录但确实调用微信支付付款成功了。
上述流程有几点改进:
1、微信小程序端支付按钮设置锁,只允许点击一次;
2、支付整个流程同步化(生产环境调用支付的频次并不高,付款整个流程测试时间大约2秒,采用最粗暴的方法,整个代码块加锁)。
使用如下单元测试测试支付:
@Test
public void testpayment() {
Purchase purchase = purchaseService.find(59503L);
Admin admin = adminService.find(3L);
// 单线程示例
// Message msg =
// purchaseOrderService.payment(Lists.newArrayList(purchase), admin);
// System.err.println(msg);
System.err.println(Thread.currentThread().getName());
// 多线程示例
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 3; ++i) {
Task task = new Task(purchase, admin);
FutureTask<Message> futureTask = new FutureTask<Message>(task);
executor.submit(futureTask);
try {
System.out.println("task运行结果" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
System.out.println("所有任务执行完毕");
}
class Task implements Callable<Message> {
private Purchase purchase;
private Admin admin;
public Task() {
super();
}
public Task(Purchase purchase, Admin admin) {
super();
this.purchase = purchase;
this.admin = admin;
}
@Override
public Message call() throws Exception {
System.err.println("子线程在进行计算");
Message msg = purchaseOrderService.payment(Lists.newArrayList(purchase), admin);
System.err.println(msg);
return msg;
}
}
调用微信的截图发现不加synchronized,三个线程会产生三次支付。
加synchronized,三个线程只会产生一次支付,测试日志如下,另外两个线程阻塞住,在第一个线程支付成功后,再去汇集采购记录表数据,但此时采购的状态已经被改变不会再汇集,报“请选择同一个供货商,且状态为等待主管审核或会计审核通过!”错误。
success
task运行结果success
子线程在进行计算
请选择同一个供货商,且状态为等待主管审核或会计审核通过!
task运行结果请选择同一个供货商,且状态为等待主管审核或会计审核通过!
子线程在进行计算
请选择同一个供货商,且状态为等待主管审核或会计审核通过!
task运行结果请选择同一个供货商,且状态为等待主管审核或会计审核通过!
所有任务执行完毕
知识点
1、synchronized