zoukankan      html  css  js  c++  java
  • 对接第三方支付接口-类似文件锁的编程小技巧

      在这次对接支付接口的时候,有如下场景:用户还款的时候,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");
        }
  • 相关阅读:
    小白安装使用Redis
    Mysql的Sql语句优化
    maximo入门----用户使用提要
    时不时刷刷BOSS 看看技术需求
    2019.7.10整理
    docker使用入门
    docker之windows安装&centOS安装
    HashTable学习
    Hashmap学习
    红黑树学习
  • 原文地址:https://www.cnblogs.com/mark8080/p/6201763.html
Copyright © 2011-2022 走看看