Procházet zdrojové kódy

添加对账单下载接口

Veronique před 7 měsíci
rodič
revize
3b3b116c9d

+ 3 - 0
conf/application-yjWebOne.xml

@@ -47,6 +47,7 @@
             <property name="erpDataMain.url" value="http://192.168.1.24:8080/apis"/>
 
             <property name="orderImportRoot" value="${APP_HOME}/importOrder"/>
+            <property name="downloadBillRoot" value="D:\\mft\\down"/>
 
             <!--支付超时分钟-->
             <property name="payTimeOutMinutes" value="30"/>
@@ -73,6 +74,7 @@
             <service value="com.yinjie.heating.webcore.rest.ERPUpdateRest"/>
             <service value="com.yinjie.heating.webcore.rest.SystemRest"/>
             <service value="com.yinjie.heating.webcore.rest.TestRest"/>
+            <service value="com.yinjie.heating.webcore.rest.ThirdCallAppRest"/>
             <service value="com.yinjie.heating.webcore.rest.DocInfoRest">
                 <isUseScriptService value="true"/>
             </service>
@@ -97,6 +99,7 @@
             <service value="com.yinjie.heating.webcore.rest.ERPUpdateRest"/>
             <service value="com.yinjie.heating.webcore.rest.SystemRest"/>
             <service value="com.yinjie.heating.webcore.rest.TestRest"/>
+            <service value="com.yinjie.heating.webcore.rest.ThirdCallAppRest"/>
             <service value="com.yinjie.heating.webcore.rest.DocInfoRest">
                 <isUseScriptService value="true"/>
             </service>

+ 168 - 0
conf/script/1000/callThird/BE_Call_DownloadBillFile.groovy

@@ -0,0 +1,168 @@
+import Ignore_ReadUTF8File as ReadUTF8File
+import com.dySweetFishPlugin.tool.crypto.encoder.BASE64Decoder
+import com.sweetfish.convert.json.JsonConvert
+import com.sweetfish.service.RetResult
+import com.yinjie.heating.common.api.BusinessExecutor
+import com.yinjie.heating.common.datas.ERPModule
+import com.yinjie.heating.common.entity.base.ProcessMapItem
+import com.yinjie.heating.common.entity.callthird.DownloadBillResponse
+import com.yinjie.heating.common.entity.heating.HeatingApp
+import com.yinjie.heating.common.tool.PaySignatureUtil
+import org.apache.commons.lang3.StringUtils
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.rex.RMap
+
+import javax.annotation.Resource
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.SecretKey
+import javax.crypto.spec.IvParameterSpec
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.security.KeyFactory
+import java.security.PublicKey
+import java.security.SecureRandom
+import java.security.spec.X509EncodedKeySpec
+import java.time.LocalDateTime
+
+class BE_Call_DownloadBillFile implements BusinessExecutor<ProcessMapItem, DownloadBillResponse> {
+    private final Logger logger = LogManager.getLogger(this.getClass().getSimpleName())
+
+    @Resource
+    private JsonConvert jsonConvert
+
+    @Resource(name = "property.downloadBillRoot")
+    private String downloadBillRoot
+
+    @Override
+    String scriptName() {
+        return "外部热力服务商调用-下载对账单,需加密"
+    }
+
+    @Override
+    ERPModule module() {
+        return ERPModule.CALL_THIRD
+    }
+
+    private static byte[] encryptFile(SecretKey aesKey, byte[] fileData) throws Exception {
+        // 生成随机的16字节初始化向量 (IV)
+        byte[] iv = new byte[16];
+        new SecureRandom().nextBytes(iv);
+        IvParameterSpec ivSpec = new IvParameterSpec(iv);
+
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
+
+        // 将IV和加密后的数据一起返回,IV不需要保密,但需要用于解密
+        byte[] encryptedData = cipher.doFinal(fileData);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(iv);
+        outputStream.write(encryptedData);
+        return outputStream.toByteArray();
+    }
+
+    private static byte[] encryptAESKey(String publicKeyStr, SecretKey aesKey) throws Exception {
+        byte[] keyBytes = (new BASE64Decoder()).decodeBuffer(publicKeyStr);
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(keySpec);
+
+        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        return cipher.doFinal(aesKey.getEncoded());
+    }
+
+
+    RetResult<DownloadBillResponse> execute(ProcessMapItem source) {
+        long currentTime = LocalDateTime.now().toDate().time
+        RMap params = source.itemData
+        String dataSourceId = source.dataSourceId
+        long supplierCode = source.supplierCode
+
+        String payDate = params.getString("payDate")
+        HeatingApp app = params.get("app") as HeatingApp
+
+        params.remove("app")//避免影响下面验签
+
+        //接口均返回正确,错误放在result里
+        if (app == null) {
+            logger.error("下载对账单传入app为空")
+            return RetResult.<DownloadBillResponse> successT().result(new DownloadBillResponse.Builder()
+                    .respCode("DEF0010")
+                    .respMsg("用户不存在")
+                    .timeStamp((new Date()).time as String)
+                    .build())
+        }
+
+        if (StringUtils.isBlank(payDate)) {
+            logger.error("下载对账单传入参数不正确")
+            return RetResult.<DownloadBillResponse> successT().result(new DownloadBillResponse.Builder()
+                    .respCode("DEF0006")
+                    .respMsg("系统错误")
+                    .timeStamp((new Date()).time as String)
+                    .build())
+        }
+
+        //签名
+        String sign = params.getString("sign")
+        if (PaySignatureUtil.verifySign(sign, "RSA_1_256", params, app.supplyPublicKey, "")) {
+            logger.info("下载对账单验签成功")
+
+            //查找对应日期的文件夹是否存在
+
+            Path dirPath = Paths.get(downloadBillRoot + File.separator + payDate)
+            if (Files.exists(dirPath) && (Files.isDirectory(dirPath))) {
+                Path filePath = Paths.get(downloadBillRoot + File.separator + payDate + File.separator + "GD_" + payDate + "_" + app.upperChannelAppId + ".txt")
+                if (Files.exists(filePath)) {
+                    //读取文件内容并加密
+                    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES")
+                    keyGenerator.init(128) // 使用128位密钥长度
+                    SecretKey aesKey = keyGenerator.generateKey()
+
+                    String fileStr = ReadUTF8File.execute(filePath, false, logger)
+                    byte[] fileData = fileStr.getBytes("UTF-8")
+                    byte[] encryptedFileData = encryptFile(aesKey, fileData)  //加密的文件
+                    byte[] encryptedAESKey = encryptAESKey(app.supplyPublicKey, aesKey)  //加密的密钥
+
+
+                    return RetResult.<DownloadBillResponse> successT().result(new DownloadBillResponse.Builder()
+                            .respCode("00000")
+                            .respMsg("success")
+                            .appId(app.appId)
+                            .fileKey(encryptedAESKey)
+                            .fileContent(encryptedFileData)
+                            .timeStamp((new Date()).time as String)
+                            .build())
+
+                } else {
+                    logger.error("找不到对账单" + filePath.toString())
+                    return RetResult.<DownloadBillResponse> successT().result(new DownloadBillResponse.Builder()
+                            .respCode("DEF0020")
+                            .respMsg("该日期账单文件暂未生成")
+                            .timeStamp((new Date()).time as String)
+                            .build())
+                }
+            } else {
+                logger.error("找不到对账单日期" + payDate + "的目录")
+                return RetResult.<DownloadBillResponse> successT().result(new DownloadBillResponse.Builder()
+                        .respCode("DEF0020")
+                        .respMsg("该日期账单文件暂未生成")
+                        .timeStamp((new Date()).time as String)
+                        .build())
+            }
+
+
+        } else {
+            logger.error("下载对账单验签失败")
+            return RetResult.<DownloadBillResponse> successT().result(new DownloadBillResponse.Builder()
+                    .respCode("DEF0030")
+                    .respMsg("验签失败")
+                    .timeStamp((new Date()).time as String)
+                    .build())
+        }
+
+
+    }
+}

+ 2 - 2
conf/sqlroot/com/yinjie/heating/business/dao/HeatingDocDao.dql

@@ -5,12 +5,12 @@ select * from $table$;
 insert into $table$ (id, appId, appName, appPublicKey,
 appPrivateKey, supplyPublicKey, status,
 chargeFrom, chargeUserName, verifyKind,
-whiteIps, createBy, createTime,
+whiteIps, upperChannelAppId, createBy, createTime,
 createTimeLong, updateBy, updateTime,
 updateTimeLong ) values ( #{id}, #{appid}, #{appname}, #{apppublickey},
 #{appprivatekey}, #{supplypublickey}, #{status},
 #{chargefrom}, #{chargeusername}, #{verifykind},
-#{whiteips}, #{createby}, #{createtime},
+#{whiteips}, #{upperchannelappid}, #{createby}, #{createtime},
 #{createtimelong}, #{updateby}, #{updatetime},
 #{updatetimelong} );
 

+ 13 - 0
yjBusiness/src/main/java/com/yinjie/heating/business/service/heating/CallThirdAppServiceImpl.java

@@ -9,6 +9,7 @@ import com.yinjie.heating.common.base.BaseService;
 import com.yinjie.heating.common.datas.ERPModule;
 import com.yinjie.heating.common.entity.base.ProcessMapItem;
 import com.yinjie.heating.common.entity.callthird.BaseResponse;
+import com.yinjie.heating.common.entity.callthird.DownloadBillResponse;
 import com.yinjie.heating.common.entity.callthird.QueryFeeResponse;
 import com.yinjie.heating.common.entity.heating.HeatingApp;
 import org.rex.RMap;
@@ -51,4 +52,16 @@ public class CallThirdAppServiceImpl extends BaseService implements CallThirdApp
                         .supplierCode(supplierCode)
                         .build());
     }
+
+    @Override
+    public RetResult<DownloadBillResponse> callDownloadBill(RMap params, HeatingApp heatingApp, String dataSourceId, long supplierCode) {
+        params.put("app", heatingApp);
+
+        return handleScript("Call_DownloadBillFile", ERPModule.CALL_THIRD, dataSourceId, supplierCode,
+                () -> ProcessMapItem.newBuilder()
+                        .itemData(params)
+                        .dataSourceId(dataSourceId)
+                        .supplierCode(supplierCode)
+                        .build());
+    }
 }

+ 4 - 0
yjCommon/src/main/java/com/yinjie/heating/common/api/heating/CallThirdAppService.java

@@ -4,8 +4,10 @@ import com.sweetfish.service.RetResult;
 import com.sweetfish.util.AutoLoad;
 import com.yinjie.heating.common.api.ScriptService;
 import com.yinjie.heating.common.entity.callthird.BaseResponse;
+import com.yinjie.heating.common.entity.callthird.DownloadBillResponse;
 import com.yinjie.heating.common.entity.callthird.QueryFeeResponse;
 import com.yinjie.heating.common.entity.heating.HeatingApp;
+import org.rex.RMap;
 
 import java.util.Date;
 
@@ -19,4 +21,6 @@ public interface CallThirdAppService extends ScriptService {
     RetResult<BaseResponse> callRequestPay(String billKey, String payDate, String bankBillNo, String payAmount,
                                            HeatingApp heatingApp, String dataSourceId, long supplierCode);
 
+    RetResult<DownloadBillResponse> callDownloadBill(RMap params, HeatingApp heatingApp, String dataSourceId, long supplierCode);
+
 }

+ 127 - 0
yjCommon/src/main/java/com/yinjie/heating/common/entity/callthird/DownloadBillResponse.java

@@ -0,0 +1,127 @@
+package com.yinjie.heating.common.entity.callthird;
+
+import org.rex.RMap;
+
+import java.util.Date;
+
+public class DownloadBillResponse extends BaseResponse {
+    private byte[] fileContent;
+    private byte[] fileKey;
+
+    private DownloadBillResponse(Builder builder) {
+        setCreateBy(builder.createBy);
+        setCreateTime(builder.createTime);
+        setCreateTimeLong(builder.createTimeLong);
+        setUpdateBy(builder.updateBy);
+        setUpdateTime(builder.updateTime);
+        setUpdateTimeLong(builder.updateTimeLong);
+        setAppId(builder.appId);
+        setRespCode(builder.respCode);
+        setRespMsg(builder.respMsg);
+        setTimeStamp(builder.timeStamp);
+        setFileContent(builder.fileContent);
+        setFileKey(builder.fileKey);
+    }
+
+    public byte[] getFileContent() {
+        return fileContent;
+    }
+
+    public void setFileContent(byte[] fileContent) {
+        this.fileContent = fileContent;
+    }
+
+    public byte[] getFileKey() {
+        return fileKey;
+    }
+
+    public void setFileKey(byte[] fileKey) {
+        this.fileKey = fileKey;
+    }
+
+    public DownloadBillResponse() {
+    }
+
+
+    public static final class Builder {
+        private long createBy;
+        private Date createTime;
+        private long createTimeLong;
+        private long updateBy;
+        private Date updateTime;
+        private long updateTimeLong;
+        private String appId;
+        private String respCode;
+        private String respMsg;
+        private String timeStamp;
+        private byte[] fileContent;
+        private byte[] fileKey;
+
+        public Builder() {
+        }
+
+        public Builder createBy(long val) {
+            createBy = val;
+            return this;
+        }
+
+        public Builder createTime(Date val) {
+            createTime = val;
+            return this;
+        }
+
+        public Builder createTimeLong(long val) {
+            createTimeLong = val;
+            return this;
+        }
+
+        public Builder updateBy(long val) {
+            updateBy = val;
+            return this;
+        }
+
+        public Builder updateTime(Date val) {
+            updateTime = val;
+            return this;
+        }
+
+        public Builder updateTimeLong(long val) {
+            updateTimeLong = val;
+            return this;
+        }
+
+        public Builder appId(String val) {
+            appId = val;
+            return this;
+        }
+
+        public Builder respCode(String val) {
+            respCode = val;
+            return this;
+        }
+
+        public Builder respMsg(String val) {
+            respMsg = val;
+            return this;
+        }
+
+        public Builder timeStamp(String val) {
+            timeStamp = val;
+            return this;
+        }
+
+        public Builder fileContent(byte[] val) {
+            fileContent = val;
+            return this;
+        }
+
+        public Builder fileKey(byte[] val) {
+            fileKey = val;
+            return this;
+        }
+
+        public DownloadBillResponse build() {
+            return new DownloadBillResponse(this);
+        }
+    }
+}

+ 120 - 107
yjCommon/src/main/java/com/yinjie/heating/common/entity/heating/HeatingApp.java

@@ -10,150 +10,163 @@ import java.util.List;
 /**
  * Created by jlutt on 2019-05-31.
  * 缴费对外应用接口
+ *
  * @author jlutt
  */
 public class HeatingApp extends BaseEntity {
 
-  @RColumn("id")
-  @Comment("id")
-  private long id;
+    @RColumn("id")
+    @Comment("id")
+    private long id;
 
-  @RColumn("appid")
-  @Comment("appid应用id,要保证唯一")
-  private String appId;
+    @RColumn("appid")
+    @Comment("appid应用id,要保证唯一")
+    private String appId;
 
-  @RColumn("appname")
-  @Comment("appid应用名称")
-  private String appName;
+    @RColumn("appname")
+    @Comment("appid应用名称")
+    private String appName;
 
-  @RColumn("apppublickey")
-  @Comment("应用公钥,提供给开发商,验证签名")
-  private String appPublicKey;
+    @RColumn("apppublickey")
+    @Comment("应用公钥,提供给开发商,验证签名")
+    private String appPublicKey;
 
-  @RColumn("appprivatekey")
-  @Comment("应用私钥,给服务商使用,发送给开发商内容进行加签")
-  private String appPrivateKey;
+    @RColumn("appprivatekey")
+    @Comment("应用私钥,给服务商使用,发送给开发商内容进行加签")
+    private String appPrivateKey;
 
-  @RColumn("supplypublickey")
-  @Comment("开发商上传的公钥,用于服务商在开发商调用接口时验签")
-  private String supplyPublicKey;
+    @RColumn("supplypublickey")
+    @Comment("开发商上传的公钥,用于服务商在开发商调用接口时验签")
+    private String supplyPublicKey;
 
-  @RColumn("status")
-  @Comment("应用状态0正常 1禁用")
-  private int status;
+    @RColumn("status")
+    @Comment("应用状态0正常 1禁用")
+    private int status;
 
-  @RColumn("chargefrom")
-  @Comment("收款来源 请从2开始 0现金 1支付宝已经占用,不要重复")
-  private int chargeFrom;
+    @RColumn("chargefrom")
+    @Comment("收款来源 请从2开始 0现金 1支付宝已经占用,不要重复")
+    private int chargeFrom;
 
-  @RColumn("chargeusername")
-  @Comment("对应收费操作人名称")
-  private String chargeUserName;
+    @RColumn("chargeusername")
+    @Comment("对应收费操作人名称")
+    private String chargeUserName;
 
-  @RColumn("verifykind")
-  @Comment("验证方式 0密钥验证 1ip限制")
-  private int verifyKind;
+    @RColumn("verifykind")
+    @Comment("验证方式 0密钥验证 1ip限制")
+    private int verifyKind;
 
-  @RColumn("whiteips")
-  @Comment("白名单ip,多个用逗号分隔")
-  private String whiteIps;
+    @RColumn("whiteips")
+    @Comment("白名单ip,多个用逗号分隔")
+    private String whiteIps;
 
-  @Comment("已注册的接口")
-  private List<HeatingAppInvoker> invokerList = new ArrayList<HeatingAppInvoker>();
+    @RColumn("upperchannelappid")
+    @Comment("对接上游(银行)系统中进件的id")
+    private String upperChannelAppId;
 
-  public long getId() {
-    return id;
-  }
+    @Comment("已注册的接口")
+    private List<HeatingAppInvoker> invokerList = new ArrayList<HeatingAppInvoker>();
 
-  public void setId(long id) {
-    this.id = id;
-  }
+    public long getId() {
+        return id;
+    }
 
-  public String getAppId() {
-    return appId;
-  }
+    public void setId(long id) {
+        this.id = id;
+    }
 
-  public void setAppId(String appId) {
-    this.appId = appId;
-  }
+    public String getAppId() {
+        return appId;
+    }
 
-  public String getAppPublicKey() {
-    return appPublicKey;
-  }
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
 
-  public void setAppPublicKey(String appPublicKey) {
-    this.appPublicKey = appPublicKey;
-  }
+    public String getAppPublicKey() {
+        return appPublicKey;
+    }
 
-  public String getAppPrivateKey() {
-    return appPrivateKey;
-  }
+    public void setAppPublicKey(String appPublicKey) {
+        this.appPublicKey = appPublicKey;
+    }
 
-  public void setAppPrivateKey(String appPrivateKey) {
-    this.appPrivateKey = appPrivateKey;
-  }
+    public String getAppPrivateKey() {
+        return appPrivateKey;
+    }
 
-  public String getSupplyPublicKey() {
-    return supplyPublicKey;
-  }
+    public void setAppPrivateKey(String appPrivateKey) {
+        this.appPrivateKey = appPrivateKey;
+    }
 
-  public void setSupplyPublicKey(String supplyPublicKey) {
-    this.supplyPublicKey = supplyPublicKey;
-  }
+    public String getSupplyPublicKey() {
+        return supplyPublicKey;
+    }
 
-  public String getAppName() {
-    return appName;
-  }
+    public void setSupplyPublicKey(String supplyPublicKey) {
+        this.supplyPublicKey = supplyPublicKey;
+    }
 
-  public void setAppName(String appName) {
-    this.appName = appName;
-  }
+    public String getAppName() {
+        return appName;
+    }
 
-  public int getStatus() {
-    return status;
-  }
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
 
-  public void setStatus(int status) {
-    this.status = status;
-  }
+    public int getStatus() {
+        return status;
+    }
 
-  public int getChargeFrom() {
-    return chargeFrom;
-  }
+    public void setStatus(int status) {
+        this.status = status;
+    }
 
-  public void setChargeFrom(int chargeFrom) {
-    this.chargeFrom = chargeFrom;
-  }
+    public int getChargeFrom() {
+        return chargeFrom;
+    }
 
-  public String getChargeUserName() {
-    return chargeUserName;
-  }
+    public void setChargeFrom(int chargeFrom) {
+        this.chargeFrom = chargeFrom;
+    }
 
-  public void setChargeUserName(String chargeUserName) {
-    this.chargeUserName = chargeUserName;
-  }
+    public String getChargeUserName() {
+        return chargeUserName;
+    }
 
-  public int getVerifyKind() {
-    return verifyKind;
-  }
+    public void setChargeUserName(String chargeUserName) {
+        this.chargeUserName = chargeUserName;
+    }
 
-  public void setVerifyKind(int verifyKind) {
-    this.verifyKind = verifyKind;
-  }
+    public int getVerifyKind() {
+        return verifyKind;
+    }
 
-  public String getWhiteIps() {
-    return whiteIps;
-  }
+    public void setVerifyKind(int verifyKind) {
+        this.verifyKind = verifyKind;
+    }
 
-  public void setWhiteIps(String whiteIps) {
-    this.whiteIps = whiteIps;
-  }
+    public String getWhiteIps() {
+        return whiteIps;
+    }
 
-  public List<HeatingAppInvoker> getInvokerList() {
-    return invokerList;
-  }
+    public void setWhiteIps(String whiteIps) {
+        this.whiteIps = whiteIps;
+    }
 
-  public void setInvokerList(List<HeatingAppInvoker> invokerList) {
-    this.invokerList = invokerList;
-  }
+    public String getUpperChannelAppId() {
+        return upperChannelAppId;
+    }
+
+    public void setUpperChannelAppId(String upperChannelAppId) {
+        this.upperChannelAppId = upperChannelAppId;
+    }
+
+    public List<HeatingAppInvoker> getInvokerList() {
+        return invokerList;
+    }
+
+    public void setInvokerList(List<HeatingAppInvoker> invokerList) {
+        this.invokerList = invokerList;
+    }
 }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 104 - 1
yjWebCore/src/main/java/com/yinjie/heating/webcore/rest/TestRest.java


+ 57 - 0
yjWebCore/src/main/java/com/yinjie/heating/webcore/rest/ThirdCallAppRest.java

@@ -0,0 +1,57 @@
+package com.yinjie.heating.webcore.rest;
+
+import com.dySweetFishPlugin.sql.RMapUtils;
+import com.sweetfish.net.http.*;
+import com.sweetfish.service.Local;
+import com.sweetfish.service.RetResult;
+import com.sweetfish.util.AutoLoad;
+import com.yinjie.heating.common.api.heating.CallThirdAppService;
+import com.yinjie.heating.common.api.heating.HeatingDocService;
+import com.yinjie.heating.common.base.BaseService;
+import com.yinjie.heating.common.datas.ERPHeader;
+import com.yinjie.heating.common.datas.HttpCode;
+import com.yinjie.heating.common.entity.callthird.BaseResponse;
+import com.yinjie.heating.common.entity.callthird.DownloadBillResponse;
+import com.yinjie.heating.common.entity.callthird.QueryFeeResponse;
+import com.yinjie.heating.common.entity.heating.HeatingApp;
+import com.yinjie.heating.common.entity.site.ERPTokenUser;
+import org.rex.RMap;
+
+import javax.annotation.Resource;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Created by jlutt on 2020-07-29
+ * ERP平台基础服务
+ *
+ * @author jlutt
+ */
+@AutoLoad(false)
+@Local
+@RestService(name = "call", moduleid = 1, comment = "商户方接口")
+@SuppressWarnings({"rawtypes", "unused"})
+public class ThirdCallAppRest extends BaseService {
+
+    //写死算了,没啥用
+    private final String DATA_SOURCE_ID = "erp001";
+    private final long SUPPLIER_CODE = 1000L;
+
+    @Resource
+    CallThirdAppService callThirdAppService;
+    @Resource
+    HeatingDocService heatingDocService;
+
+    @RestMapping(name = "downloadBill", sort = 1, methods = {"POST"})
+    public CompletableFuture<DownloadBillResponse> downloadBill(
+            @RestHeader(name = "appId") String appId,
+            @RestBody RMap params) {
+        return CompletableFuture.supplyAsync(
+                () -> {
+                    HeatingApp app = heatingDocService.getRedisHeatingApp(appId, SUPPLIER_CODE);
+
+                    RetResult<DownloadBillResponse> result = callThirdAppService.callDownloadBill(params, app, DATA_SOURCE_ID, SUPPLIER_CODE);
+                    return result.getResult(); //这里直接返回,有什么错误在脚本中处理完
+                }, getExecutor()
+        );
+    }
+}