在这次对接支付接口的时候,有如下场景:用户还款的时候,APP端只要请求了支付接口后,正常情况下,支付接口会同步返回结果状态,并且异步通知是否成功,支付状态以异步通知为准。这样的场景会出现一个问题,如果APP端请求了支付接口,异步通知迟迟未返回,这样一来,用户还款状态是无法更改(还款的逻辑处理实在异步通知里处理,因为一切以异步通知为准),并且对于用户来说他已经还款了,异步回调没来,可能支付成功,可能支付失败我们不知道,对于用户来说他已经支付还款了,按逻辑这一期还款他无需也不能做其他操作了,所以在回调通知来之前,这一期数据在APP端是需要做一个限制不能让用户操作,在这里加了一个“还款中”状态,标记请求支付接口之后,回调通知来之前的状态(正常情况下这段时间很短,短到让用户无法察觉)。
加“还款中”的状态是可行的,后台服务端来更新还款记录的状态,由APP端请求,APP端在请求支付接口后支付接口同步返回成功后再请求后台的接口,更新还款记录的状态。这里有个问题:请求支付接口会有一个异步通知返回,我们在异步通知里进行相应的逻辑处理,包括更新还款记录的状态为“已还”,但是APP端同步请求成功后也会请求后台更新这条还款记录为“还款中”,操作同一条数据,我们一开始的做法是更新还款中的时候判断是否是“未还”状态,但是发现如果两个更新操作的方法“同时”处理,即异步通知还未更新为“已还”,更新为“还款中”的方法进去了,检索到这条记录仍是“未还”,同样会处理成“还款中”。这样一来有可能支付成功了,还款记录的状态还是“还款中”的情况,所以我们要解决并发的问题,人为控制如果回调通知已经来了,就没必要在请求后台改成“还款中”。
这里用到的方法有点类似于“文件锁”,我们通过生成特定文件来标记是否异步通知成功(直接更新为“已还”),如果异步通知成功则不需要更新为“还款中”,如果先改成“还款中”,再改成“已还”也没有问题,如果两个方法同时进行,则根据生成的文件来标记一个方法是否提交完成,这里用到的是Spring的事务控制,另一个事务提交后再执行当前这个方法,主要做法为:在异步回调通知里的方法notifyUrl()开始的时候创建一个文件A,结束时删除这个文件;在更改成“还款中”的方法updateRpmting()开始时候也创建一个文件B,这里要判断A是否存在,如果A存在则当前线程延时60ms,等待notifyUrl()方法执行完毕,这时候还款记录的状态已经改成“已还”,不会再继续执行updateRpmting()方法改成“还款中”,当然在创建文件A的时候也会判断B是否存在,同样处理。这里生成文件的方法需要用java的synchronized来锁住,确保同一时间只有一次调用,生成一个文件。
RechargeUtil.java:(Globals.UNDER_LINE是定义的静态变量表示下划线 “_”)
//创建一个唯一文件 public static boolean creatOnlyFile(String rpmtIds,String myType, String otherType){ // 创建一个files目录下面日期为子目录的rpmtId.txt文件 String myFileName=files+"/" + DateUtils.date2Str(DateUtils.yyyyMMdd)+"/" + DigestUtils.md5Hex(rpmtIds) + Globals.UNDER_LINE + myType + ".txt"; String otherFileName=files+"/" + DateUtils.date2Str(DateUtils.yyyyMMdd)+"/" + DigestUtils.md5Hex(rpmtIds) + Globals.UNDER_LINE + otherType + ".txt"; System.out.println("文件地址为:" + myFileName); File myFile=new File(myFileName); if(!myFile.getParentFile().exists()){ myFile.getParentFile().mkdirs(); } File otherFile=new File(otherFileName); try { return createSynchronizedFile(myFile, otherFile); } catch (IOException e) { System.out.println("创建文件失败!"); e.printStackTrace(); } return false; } //同一时间创建一个唯一文件 public synchronized static boolean createSynchronizedFile(File myFile, File otherFile) throws IOException { if(!otherFile.exists()){ myFile.createNewFile(); return true; } return false; } //删除存在的锁文件 public static boolean deleteOnlyFile(String rpmtIds,String myType){
File myFile=new File(files+"/" + DateUtils.date2Str(DateUtils.yyyyMMdd)+"/" + DigestUtils.md5Hex(rpmtIds) + Globals.UNDER_LINE + myType + ".txt");
if(myFile.exists()){
return myFile.delete();
}
return true;
}
异步回调通知里调用创建文件的方法:
public void notifyUrl(PayRpmtEntity payRpmt, String noAgree ,JSONObject resultMap) throws Exception{ //1.根据“还款-支付记录表”获取rpmtIds,循环修改还款计划状态 String rpmtIds = payRpmt.getRpmtIds(); //创建一个唯一文件 int size = 0; while (size < 30 && !RechargeUtil.creatOnlyFile(rpmtIds,"01", "02")) { Thread.sleep(100); size++; } if (size >= 30) { log.info("****************************同步回调处理中****************************"); resultMap.put("ret_code", "1005"); resultMap.put("ret_msg", "支付处理失败"); return ; } // TODO 逻辑处理
// 删除锁文件 RechargeUtil.deleteOnlyFile(rpmtIds, "01"); }
更改成“还款中”的时候:
/** * 将还款计划该成还款中 * @Title: updateRpmt * @param rpmtIdsStr * @throws Exception */ public void updateRpmting(String[] rpmtIdsStr,String rpmtIds) throws Exception{ //创建一个唯一文件 int size = 0; while (size < 30 && !RechargeUtil.creatOnlyFile(rpmtIds,"02", "01")) { Thread.sleep(100); size++; } if (size >= 30) { log.info("****************************状态处理中****************************"); return ; } // TODO 逻辑处理// 删除锁文件 RechargeUtil.deleteOnlyFile(rpmtIds, "02"); }