Преглед изворни кода

Merge remote-tracking branch 'origin/master'

wanghao пре 3 месеци
родитељ
комит
ca46f99447
41 измењених фајлова са 1580 додато и 1544 уклоњено
  1. 3 0
      commons/src/main/java/com/jzg/commons/entity/dto/AgreementRulesAddParam.java
  2. 5 0
      commons/src/main/java/com/jzg/commons/entity/dto/AgreementRulesEditParam.java
  3. 8 0
      commons/src/main/java/com/jzg/commons/entity/finance/dto/ExportSuperviseSettlementExcelDto.java
  4. 3 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/SettlementReportQueryVo.java
  5. 3 0
      commons/src/main/java/com/jzg/commons/entity/po/PtlAgreementUndwrtRules.java
  6. 13 13
      commons/src/main/java/com/jzg/commons/entity/quote/vo/aggregated/QuoteEchoVo.java
  7. 3 0
      commons/src/main/java/com/jzg/commons/entity/vo/AgreementRulesVo.java
  8. 6 0
      commons/src/main/java/com/jzg/commons/entity/vo/IncomeAndExpenseVo.java
  9. 2 1
      commons/src/main/java/com/jzg/commons/util/BuildDescUtils.java
  10. 67 41
      tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/build/AnShengCrawlerQuoteResultsVoBuild.java
  11. 74 4
      tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/service/impl/AnShengCrawlerRequestImpl.java
  12. 2 0
      tenant/insurance/quotation-chengtai/src/main/java/com/jzg/quotation/chengtai/api/build/ChengTaiAPIQuoteResultsVoBuild.java
  13. 6 0
      tenant/insurance/quotation-dajia/src/main/java/com/jzg/quotation/dajia/crawler/build/DaJiaCrawlerQuoteResultsVoBuild.java
  14. 6 5
      tenant/insurance/quotation-dajia/src/main/java/com/jzg/quotation/dajia/crawler/service/impl/DaJiaCrawlerRequestImpl.java
  15. 51 25
      tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/entity/request/GuoRenCrawlerProposalNoSaveRequest.java
  16. 20 6
      tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/service/impl/GuoRenCrawlerQuoteProcessServiceImpl.java
  17. 21 8
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerQuoteRequest.java
  18. 824 1174
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java
  19. 1 1
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/async/AsyncGetPayLink.java
  20. 17 2
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/controller/QrCodeController.java
  21. 14 13
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsOrdersServiceImpl.java
  22. 8 0
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/build/TkCrawlerQuoteResultsBuild.java
  23. 7 0
      tenant/insurance/quotation-taiping/src/main/java/com/jzg/quotation/taiping/crawler/service/impl/TaipingCrawlerRequestImpl.java
  24. 32 11
      tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/build/TaiShanCrawlerQuoteRequestVoBuild.java
  25. 77 38
      tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/service/impl/TaiShanCrawlerRequestImpl.java
  26. 3 0
      tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/entity/Request/ZmCrawlerQuotedPriceRequest.java
  27. 1 1
      tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/build/ZiJinCrawlerQuoteResultsVoBuild.java
  28. 1 1
      tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/component/ZiJinCrawlerCacheComponent.java
  29. 90 60
      tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/component/ZiJinCrawlerRequestComponent.java
  30. 10 0
      tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/entity/ZiJinCrawlerConfigureParameters.java
  31. 40 66
      tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/service/impl/ZiJinCrawlerQuoteProcessServiceImpl.java
  32. 33 44
      tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/service/impl/ZiJinCrawlerRequestImpl.java
  33. 18 0
      tenant/organization/src/main/java/com/jzg/organization/controller/ReceivableController.java
  34. 8 8
      tenant/organization/src/main/java/com/jzg/organization/mapper/ReceivableMapper.java
  35. 9 0
      tenant/organization/src/main/java/com/jzg/organization/service/ReceivableService.java
  36. 10 8
      tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlAgreementCostServiceImpl.java
  37. 2 2
      tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlAgreementUndwrtRulesServiceImpl.java
  38. 1 2
      tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlSchemeServiceImpl.java
  39. 39 8
      tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java
  40. 18 0
      tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserAccountServiceImpl.java
  41. 24 2
      tenant/organization/src/main/resources/mapper/ReceivableMapper.xml

+ 3 - 0
commons/src/main/java/com/jzg/commons/entity/dto/AgreementRulesAddParam.java

@@ -21,6 +21,9 @@ public class AgreementRulesAddParam {
     @Schema(description = "生效截止时间")
     private String endTime;
 
+    @Schema(description = "人工输入描述")
+    private String inputDescription;
+
     @Schema(description = "规则属性")
     private List<PtlAgreementUndwrtRulesAttrLink> rulesAttrList;
 

+ 5 - 0
commons/src/main/java/com/jzg/commons/entity/dto/AgreementRulesEditParam.java

@@ -2,6 +2,7 @@ package com.jzg.commons.entity.dto;
 
 import com.jzg.commons.entity.po.PtlAgreementUndwrtRulesAttrLink;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
@@ -27,6 +28,10 @@ public class AgreementRulesEditParam {
     @Schema(description = "生效截止时间")
     private String endTime;
 
+    @Schema(description = "人工输入描述")
+    private String inputDescription;
+
+    @NotEmpty(message = "规则属性不能为空")
     @Schema(description = "规则属性(单条模式)")
     private List<PtlAgreementUndwrtRulesAttrLink> rulesAttrList;
 

+ 8 - 0
commons/src/main/java/com/jzg/commons/entity/finance/dto/ExportSuperviseSettlementExcelDto.java

@@ -1,5 +1,6 @@
 package com.jzg.commons.entity.finance.dto;
 
+import com.alibaba.excel.annotation.ExcelIgnore;
 import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -19,10 +20,12 @@ public class ExportSuperviseSettlementExcelDto {
     private String settlementId;
 
     @Schema(description = "结算状态")
+    @ExcelProperty(value = "结算状态")
     private String settlementStatus;
 
     // 结算状态转义
     @Schema(description = "结算状态")
+    @ExcelProperty(value = "结算状态")
     private String settlementStatusName;
 
     @Schema(description = "保险公司")
@@ -30,6 +33,7 @@ public class ExportSuperviseSettlementExcelDto {
     private String companyName;
 
     @Schema(description = "保险公司序号")
+    @ExcelIgnore
     private String companyId;
 
     @Schema(description = "开票方")
@@ -42,6 +46,7 @@ public class ExportSuperviseSettlementExcelDto {
 
     // 应收项转义
     @Schema(description = "应收项")
+    @ExcelProperty(value = "应收项")
     private String invoiceTypeName;
 
     @Schema(description = "税点")
@@ -73,12 +78,15 @@ public class ExportSuperviseSettlementExcelDto {
     private String invoiceId;
 
     @Schema(description = "附件id")
+    @ExcelIgnore
     private String fileId;
 
     @Schema(description = "附件url")
+    @ExcelIgnore
     private String fileUrl;
 
     @Schema(description = "订单号")
+    @ExcelProperty(value = "订单号")
     private String orderNo;
 
 }

+ 3 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/SettlementReportQueryVo.java

@@ -48,4 +48,7 @@ public class SettlementReportQueryVo extends PageRequest {
 
     @Schema(description = "对接人")
     private String receiver;
+
+    @Schema(description = "开票状态 0-未开票 1-已开票")
+    private String invoiceStatus;
 }

+ 3 - 0
commons/src/main/java/com/jzg/commons/entity/po/PtlAgreementUndwrtRules.java

@@ -29,6 +29,9 @@ public class PtlAgreementUndwrtRules extends BaseModel {
     @Schema(description = "生效结束时间")
     private String endTime;
 
+    @Schema(description = "人工输入描述")
+    private String inputDescription;
+
     /** 规则描述 */
     @Schema(description = "规则描述")
     private String ruleDescription;

+ 13 - 13
commons/src/main/java/com/jzg/commons/entity/quote/vo/aggregated/QuoteEchoVo.java

@@ -371,20 +371,20 @@ public class QuoteEchoVo extends QuoteVo {
             }
         });
         // 添加交强险种信息
-//        if(ProductsType.isContainDj(insOrdersVo.getProductId())){
-//            KindInfoVo jqKindInfoVo = new KindInfoVo();
-//            jqKindInfoVo.setKindCode("J1");
-//            jqKindInfoVo.setKindName("机动车交通事故责任强制保险");
-//            if(Objects.nonNull(insOrdersVo.getInsOrdersCosts().getJqPremium())) {
-//                jqKindInfoVo.setPremium(insOrdersVo.getInsOrdersCosts().getJqPremium().toString());
-//            }
-//            jqKindInfoVo.setAmount("200000");
-//            jqKindInfoVo.setServiceTimes("200000");
-//            jqKindInfoVo.setUnitAmount("200000");
-//            insOrdersVo.getKindInfoVo().add(0,jqKindInfoVo);
-//        }
+        if(ProductsType.isContainDj(insOrdersVo.getProductId())){
+            KindInfoVo jqKindInfoVo = new KindInfoVo();
+            jqKindInfoVo.setKindCode("J1");
+            jqKindInfoVo.setKindName("机动车交通事故责任强制保险");
+            if(Objects.nonNull(insOrdersVo.getInsOrdersCosts().getJqPremium())) {
+                jqKindInfoVo.setPremium(insOrdersVo.getInsOrdersCosts().getJqPremium().toString());
+            }
+            jqKindInfoVo.setAmount("200000");
+            jqKindInfoVo.setServiceTimes("200000");
+            jqKindInfoVo.setUnitAmount("200000");
+            insOrdersVo.getKindInfoVo().add(0,jqKindInfoVo);
+        }
         // 移除交强
-        insOrdersVo.getKindInfoVo().removeIf(item -> "J1".equals(item.getKindCode()));
+//        insOrdersVo.getKindInfoVo().removeIf(item -> "J1".equals(item.getKindCode()));
 
         this.setKindInfoVo(insOrdersVo.getKindInfoVo());
         this.setAccidentalDrivings(insOrdersVo.getAccidentalDrivings());

+ 3 - 0
commons/src/main/java/com/jzg/commons/entity/vo/AgreementRulesVo.java

@@ -29,6 +29,9 @@ public class AgreementRulesVo extends BaseModel {
     @Schema(description = "生效截止时间")
     private String endTime;
 
+    @Schema(description = "人工输入描述")
+    private String inputDescription;
+
     /** 规则描述 */
     @Schema(description = "规则描述")
     private String ruleDescription;

+ 6 - 0
commons/src/main/java/com/jzg/commons/entity/vo/IncomeAndExpenseVo.java

@@ -53,5 +53,11 @@ public class IncomeAndExpenseVo {
 
         @Schema(description = "银行名称")
         private String bankName;
+
+        @Schema(description = "用户名")
+        private String userName;
+
+        @Schema(description = "银行卡号")
+        private String bankCardNum;
     }
 }

+ 2 - 1
commons/src/main/java/com/jzg/commons/util/BuildDescUtils.java

@@ -13,9 +13,10 @@ import java.util.stream.Collectors;
 public class BuildDescUtils {
 
 
-    public static String buildDesc(List<PtlAgreementUndwrtRulesAttr> linkList, List<PtlAgreementUndwrtRulesAttr> undwrtRulesAttrList) {
+    public static String buildDesc(String companyId, List<PtlAgreementUndwrtRulesAttr> linkList, List<PtlAgreementUndwrtRulesAttr> undwrtRulesAttrList) {
         StringBuilder desc = new StringBuilder();
         Map<String, PtlAgreementUndwrtRulesAttr> attrMap = undwrtRulesAttrList.stream()
+                .filter(attr -> "all".equals(attr.getCompanyIds()) || attr.getCompanyIds().equals(companyId))
                 .collect(Collectors.toMap(PtlAgreementUndwrtRulesAttr::getAttrCode, Function.identity()));
 
         for (PtlAgreementUndwrtRulesAttr link : linkList) {

+ 67 - 41
tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/build/AnShengCrawlerQuoteResultsVoBuild.java

@@ -2,7 +2,7 @@ package com.jzg.quotation.ansheng.crawler.build;
 
 
 import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSONObject;
 import com.jzg.commons.constants.dict.InsOrderStatusEnum;
 import com.jzg.commons.entity.quote.vo.KindInfoVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
@@ -15,7 +15,11 @@ import com.jzg.quotation.commons.component.ConversionOfQuoteInformation;
 import lombok.extern.slf4j.Slf4j;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
 
 
 @Slf4j
@@ -52,7 +56,7 @@ public final class AnShengCrawlerQuoteResultsVoBuild {
                 quoteResultsVo.setCiRiskInfoVo(buildCiRiskInfoVo(contract.getPolicyBase()));
                 buildRiskInfoVo(quoteVo.getCiRiskInfoVo(), contract.getPolicyBase());
                 quoteResultsVo.getCosts().setCiPremium(String.valueOf(contract.getPolicyBase().getTotalActualPremium()));
-                quoteResultsVo.getCosts().setVvTax(String.valueOf(contract.getTargets().get(0).getVehicleTax().getVehicleTaxAmount()));
+                quoteResultsVo.getCosts().setVvTax(getVvTax(contract));
             // 商业险
             } else if (DepartmentPlanCode.PLAN_109.getCode()
                     .equals(contract.getPolicyBase().getPlanCode()) || DepartmentPlanCode.PLAN_113.getCode()
@@ -76,55 +80,77 @@ public final class AnShengCrawlerQuoteResultsVoBuild {
         return quoteResultsVo;
     }
 
+    public static String getVvTax(AnShengCrawlerCalculatePremiumApplyResponse.DataDTO.ContractsDTO contract) {
+        BigDecimal total = Optional.ofNullable(contract)
+                .map(c -> Optional.ofNullable(c.getTargets()).filter(list -> !list.isEmpty()).map(list -> list.get(0)))
+                .flatMap(targetOpt -> targetOpt)
+                .map(target -> Optional.ofNullable(target.getVehicleTax()))
+                .flatMap(taxOpt -> taxOpt)
+                .map(tax -> {
+                    BigDecimal taxAmt = tax.getVehicleTaxAmount() == null ? BigDecimal.ZERO : BigDecimal.valueOf(tax.getVehicleTaxAmount());
+                    BigDecimal lateAmt = tax.getLateFeeAmount() == null ? BigDecimal.ZERO : BigDecimal.valueOf(tax.getLateFeeAmount());
+                    return taxAmt.add(lateAmt);
+                })
+                .orElse(BigDecimal.ZERO)
+                .setScale(2, RoundingMode.HALF_UP);
+        return total.toPlainString();
+    }
+
 
-    private static QuoteResultsVo.ScoreVo buildScoreVo( AnShengCrawlerCalculatePremiumApplyResponse calculatePremiumApply) {
+
+    /**
+     * 组装打分ScoreVo
+     */
+    private static QuoteResultsVo.ScoreVo buildScoreVo(AnShengCrawlerCalculatePremiumApplyResponse calculatePremiumApply) {
         QuoteResultsVo.ScoreVo scoreVo = new QuoteResultsVo.ScoreVo();
         JSONArray scoreArray = new JSONArray();
         AnShengCrawlerCalculatePremiumApplyResponse.DataDTO data = calculatePremiumApply.getData();
-        String basicstring = data.getBasicstring();
-        if (StringUtils.isNotEmpty(basicstring)){
-            String[] parts = basicstring.split(":");
-            scoreArray.add(new JSONObject()
-                    .fluentPut("scoreName",parts.length > 0 ? parts[0].trim() : "")
-                    .fluentPut("scoreKey","basicstring")
-                    .fluentPut("score",parts.length > 1 ? parts[1].trim() : ""));
-        }
-        String blackDivisor = data.getBlackDivisor();
-        if (StringUtils.isNotEmpty(blackDivisor)){
-            String[] black = blackDivisor.split(":");
-            scoreArray.add(new JSONObject()
-                    .fluentPut("scoreName",black.length > 0 ? black[0].trim() : "")
-                    .fluentPut("scoreKey","blackDivisor")
-                    .fluentPut("score",black.length > 0 ? black[1].trim() : ""));
-        }
-        String comprehensiveScoreStr = data.getComprehensiveScoreStr();
-        if (StringUtils.isNotEmpty(comprehensiveScoreStr)){
-            String[] comprehensiveScore = comprehensiveScoreStr.split(":");
-            scoreArray.add(new JSONObject()
-                    .fluentPut("scoreName",comprehensiveScore.length > 0 ? comprehensiveScore[0].trim() : "")
-                    .fluentPut("scoreKey","comprehensiveScoreStr")
-                    .fluentPut("score",comprehensiveScore.length > 0 ? comprehensiveScore[1].trim() : ""));
-        }
-        String riskScore = data.getRiskScore();
-        if (StringUtils.isNotEmpty(riskScore)){
-            String[] riskScoreStr = riskScore.split(":");
-            scoreArray.add(new JSONObject()
-                    .fluentPut("scoreName",riskScoreStr.length > 0 ? riskScoreStr[0].trim() : "")
-                    .fluentPut("scoreKey","riskScore")
-                    .fluentPut("score",riskScoreStr.length > 0 ? riskScoreStr[1].trim() : ""));
+        if (data == null) {
+            scoreVo.setScoreJson(scoreArray);
+            return scoreVo;
         }
-        String unifiedScore = data.getUnifiedScore();
-        if (StringUtils.isNotEmpty(unifiedScore)){
-            String[] unifiedScoreStr = unifiedScore.split(":");
-            scoreArray.add(new JSONObject()
-                    .fluentPut("scoreName",unifiedScoreStr.length > 0 ? unifiedScoreStr[0].trim() : "")
-                    .fluentPut("scoreKey","unifiedScore")
-                    .fluentPut("score",unifiedScoreStr.length > 0 ? unifiedScoreStr[1].trim() : ""));
+        // 1. 统一配置:字段key + 取值函数
+        record ScoreItem(String scoreKey, Function<AnShengCrawlerCalculatePremiumApplyResponse.DataDTO, String> valueFunc) {}
+        List<ScoreItem> itemList = new ArrayList<>();
+        itemList.add(new ScoreItem("basicstring", AnShengCrawlerCalculatePremiumApplyResponse.DataDTO::getBasicstring));
+        itemList.add(new ScoreItem("blackDivisor", AnShengCrawlerCalculatePremiumApplyResponse.DataDTO::getBlackDivisor));
+        itemList.add(new ScoreItem("comprehensiveScoreStr", AnShengCrawlerCalculatePremiumApplyResponse.DataDTO::getComprehensiveScoreStr));
+        itemList.add(new ScoreItem("riskScore", AnShengCrawlerCalculatePremiumApplyResponse.DataDTO::getRiskScore));
+        itemList.add(new ScoreItem("unifiedScore", AnShengCrawlerCalculatePremiumApplyResponse.DataDTO::getUnifiedScore));
+
+        final String splitChar = ":";
+        for (ScoreItem item : itemList) {
+            String sourceStr = item.valueFunc().apply(data);
+            fillScoreItem(scoreArray, splitChar, item.scoreKey(), sourceStr);
         }
+
         scoreVo.setScoreJson(scoreArray);
         return scoreVo;
     }
 
+    /**
+     * 通用填充单个打分JSON对象,消除重复逻辑 + 规避下标越界
+     * @param scoreArray 外层JSON数组
+     * @param splitChar 分隔符
+     * @param scoreKey 唯一标识key
+     * @param sourceStr 原始待分割字符串
+     */
+    private static void fillScoreItem(JSONArray scoreArray, String splitChar, String scoreKey, String sourceStr) {
+        if (StringUtils.isEmpty(sourceStr)) {
+            return;
+        }
+        String[] parts = sourceStr.split(splitChar, 2);
+        String scoreName = parts.length >= 1 ? parts[0].trim() : "";
+        String scoreVal = parts.length >= 2 ? parts[1].trim() : "";
+
+        JSONObject jsonObj = JSONObject.of(
+                "scoreName", scoreName,
+                "scoreKey", scoreKey,
+                "score", scoreVal
+        );
+        scoreArray.add(jsonObj);
+    }
+
 
 
     /**

+ 74 - 4
tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/service/impl/AnShengCrawlerRequestImpl.java

@@ -1,6 +1,7 @@
 package com.jzg.quotation.ansheng.crawler.service.impl;
 
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.jzg.commons.constants.dict.InsOrderStatusEnum;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
@@ -33,7 +34,11 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpHeaders;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -56,6 +61,14 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
 
     private final AnShengCrawlerQuoteProcessService anShengCrawlerQuoteProcessService;
 
+
+    /** 最大重试次数:获取支付链接 */
+    private static final int MAX_PAY_LINK_RETRY = 15;
+    /** 每次重试间隔休眠毫秒:3秒 */
+    private static final long RETRY_SLEEP_MILLIS = 3000L;
+
+
+
     @Override
     public void login(LoginBase loginBase) {
         anShengCrawlerLoginService.login(loginBase.getUsername(), loginBase.getPassword());
@@ -221,7 +234,15 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
         saveThePolicyNumber(ordersNo, applyApplicationResponse);
         UnderwritingResultsVo underwritingResultsVo = new UnderwritingResultsVo();
         AnShengCrawlerOrder anShengCrawlerOrder = anShengCrawlerOrderService.getById(ordersNo);
-        if (!applyApplicationResponse.getCode().equals(200)) {
+
+        PaymentLinkResultsVo paymentLinkResultsVo = new PaymentLinkResultsVo();
+        if (applyApplicationResponse.getCode().equals(200)) {
+            PaymentLinkVo paymentLinkVo = new PaymentLinkVo();
+            paymentLinkVo.setOrder(underwritingVo.getOrder());
+            paymentLinkResultsVo = loopAcquisitionPaymentLink(paymentLinkVo);
+        }
+
+        if (!applyApplicationResponse.getCode().equals(200) && StringUtils.isEmpty(paymentLinkResultsVo.getPaymentLink())) {
             underwritingResultsVo.setUnderwritingResult(applyApplicationResponse.getMessage());
             underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.AUDIT_FAIL);
             return underwritingResultsVo;
@@ -231,12 +252,62 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
             if (anShengCrawlerOrder.getNiInfos().size() > 0) {
                 underwritingResultsVo.setJyApplyNo(anShengCrawlerOrder.getNiInfos().get(0).getNiPolicyNumber());
             }
+            underwritingResultsVo.setPaymentLink(paymentLinkResultsVo.getPaymentLink());
             underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
         }
-        underwritingResultsVo.setPaymentLink("");
         return underwritingResultsVo;
     }
 
+    /**
+     * 循环重试获取支付链接
+     * @param paymentLinkVo 请求参数
+     * @return 支付链接结果
+     */
+    private PaymentLinkResultsVo loopAcquisitionPaymentLink(PaymentLinkVo paymentLinkVo) {
+        if (paymentLinkVo == null || paymentLinkVo.getOrder() == null) {
+            log.error("获取支付链接入参为空");
+            return new PaymentLinkResultsVo();
+        }
+        String ordersNo = paymentLinkVo.getOrder().getOrdersNo();
+        PaymentLinkResultsVo linkResult = null;
+        for (int retryCount = 0; retryCount < MAX_PAY_LINK_RETRY; retryCount++) {
+            try {
+                linkResult = getPaymentLink(paymentLinkVo);
+                log.info("订单[{}]第{}次获取支付链接,返回结果:{}", ordersNo, retryCount + 1, JSONObject.toJSONString(linkResult));
+                if (linkResult != null && StrUtil.isNotBlank(linkResult.getPaymentLink())) {
+                    log.info("订单[{}]第{}次成功获取支付链接", ordersNo, retryCount + 1);
+                    break;
+                }
+            } catch (Exception e) {
+                log.error("订单[{}]第{}次调用获取支付链接接口异常", ordersNo, retryCount + 1, e);
+            }
+            if (retryCount != MAX_PAY_LINK_RETRY - 1) {
+                boolean sleepSuccess = sleep(RETRY_SLEEP_MILLIS);
+                if (!sleepSuccess) {
+                    log.warn("订单[{}]支付链接重试休眠被中断,终止重试", ordersNo);
+                    break;
+                }
+            }
+        }
+        return linkResult == null ? new PaymentLinkResultsVo() : linkResult;
+    }
+
+    /**
+     * @param millis 休眠毫秒数
+     * @return true=正常休眠完成;false=被中断
+     */
+    private boolean sleep(long millis) {
+        try {
+            TimeUnit.MILLISECONDS.sleep(millis);
+            return true;
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+            return false;
+        }
+    }
+
+
+
     /**
      * 保存投保单号
      *
@@ -276,7 +347,6 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
         // 获取支付链接
         AnShengCrawlerPaymentRequest anShengCrawlerPaymentRequest = new AnShengCrawlerPaymentRequest(anShengCrawlerOrder, userDetailResponse.getDepartmentCode(), anShengCrawlerOrder.getTotalPremium());
         String paymentUrl = anShengCrawlerRequestComponent.payment(anShengCrawlerPaymentRequest, httpHeaders);
-        log.info("获取安盛支付二维码地址长度:{}", StringUtils.isNotEmpty(paymentUrl) ? paymentUrl.length() : "无法获取支付链接");
         return new PaymentLinkResultsVo(true, paymentUrl, "");
     }
 

+ 2 - 0
tenant/insurance/quotation-chengtai/src/main/java/com/jzg/quotation/chengtai/api/build/ChengTaiAPIQuoteResultsVoBuild.java

@@ -100,6 +100,7 @@ public final class ChengTaiAPIQuoteResultsVoBuild {
             scoreVo.setCiClaims(premiumCalculationResponse.getCompRisk().getBasePart().getClaimsNum());
             if (null != premiumCalculationResponse.getCompRisk().getBasePart().getDiscount()) {
                 scoreVo.setJqAdjustRate(premiumCalculationResponse.getCompRisk().getBasePart().getDiscount().toString());
+                scoreVo.setCiClaims(premiumCalculationResponse.getCompRisk().getBasePart().getClaimsNum());
             }
         }
         if (null != premiumCalculationResponse.getCommRisk() && null != premiumCalculationResponse.getCommRisk().getBasePart()) {
@@ -107,6 +108,7 @@ public final class ChengTaiAPIQuoteResultsVoBuild {
             scoreVo.setSyRenewal(premiumCalculationResponse.getCommRisk().getBasePart().getRenewalFlag());
             if (null != premiumCalculationResponse.getCommRisk().getBasePart().getDiscount()){
                 scoreVo.setSyAdjustRate(premiumCalculationResponse.getCommRisk().getBasePart().getDiscount().toString());
+                scoreVo.setBiClaims(premiumCalculationResponse.getCommRisk().getBasePart().getClaimsNum());
             }
         }
         scoreVo.setLossRation(premiumCalculationResponse.getYztScore().getCentile().toString());

+ 6 - 0
tenant/insurance/quotation-dajia/src/main/java/com/jzg/quotation/dajia/crawler/build/DaJiaCrawlerQuoteResultsVoBuild.java

@@ -65,6 +65,12 @@ public final class DaJiaCrawlerQuoteResultsVoBuild {
         quoteResultsVo.getCosts().setNiPremium(daJiaCrawlerSaveResponse.getDriveInsurance().getProductInfo().getPremium().equals("") ? "0.00" : daJiaCrawlerSaveResponse.getDriveInsurance().getProductInfo().getPremium());
         // 交强保费 车船税 商业保费
         buildOtherInfo(quoteResultsVo, daJiaCrawlerSaveResponse.getCompulsoryInsurance(), daJiaCrawlerSaveResponse.getBusinessInsurance());
+        BusinessInsuranceDTO.ProductInfoDTO productInfo = daJiaCrawlerSaveResponse.getBusinessInsurance().getProductInfo();
+        CompulsoryInsuranceDTO.ProductInfoDTO compulsoryInsuranceProductInfo = daJiaCrawlerSaveResponse.getCompulsoryInsurance().getProductInfo();
+        quoteResultsVo.setCiStartDate(compulsoryInsuranceProductInfo.getStartDate());
+        quoteResultsVo.setCiEndDate(compulsoryInsuranceProductInfo.getEndDate());
+        quoteResultsVo.setBiStartDate(productInfo.getStartDate());
+        quoteResultsVo.setBiEndDate(productInfo.getEndDate());
         return quoteResultsVo;
     }
 

+ 6 - 5
tenant/insurance/quotation-dajia/src/main/java/com/jzg/quotation/dajia/crawler/service/impl/DaJiaCrawlerRequestImpl.java

@@ -24,7 +24,6 @@ import com.jzg.quotation.dajia.crawler.component.DaJiaCrawlerRequestComponent;
 import com.jzg.quotation.dajia.crawler.entity.DaJiaCrawlerConfigureParameters;
 import com.jzg.quotation.dajia.crawler.entity.po.DaJiaCrawlerOrder;
 import com.jzg.quotation.dajia.crawler.entity.request.*;
-import com.jzg.quotation.dajia.crawler.entity.request.quote.BusinessInsuranceDTO;
 import com.jzg.quotation.dajia.crawler.entity.request.quote.CompulsoryInsuranceDTO;
 import com.jzg.quotation.dajia.crawler.entity.request.quote.ProposalInfoDTO;
 import com.jzg.quotation.dajia.crawler.entity.response.*;
@@ -163,11 +162,12 @@ public class DaJiaCrawlerRequestImpl implements DaJiaCrawlerRequest {
             String startTime = extractDuplicateEndTime(message);
             String endTime = TimeProcessing.minusOneSecondAfterYear(startTime);
             CompulsoryInsuranceDTO compulsoryInsuranceDTO = daJiaCrawlerPriceRequest.getCompulsoryInsurance();
-            BusinessInsuranceDTO businessInsuranceDTO = daJiaCrawlerPriceRequest.getBusinessInsurance();
+
             compulsoryInsuranceDTO.getProductInfo().setStartDate(startTime);
             compulsoryInsuranceDTO.getProductInfo().setEndDate(endTime);
-            businessInsuranceDTO.getProductInfo().setStartDate(startTime);
-            businessInsuranceDTO.getProductInfo().setEndDate(endTime);
+//            BusinessInsuranceDTO businessInsuranceDTO = daJiaCrawlerPriceRequest.getBusinessInsurance();
+//            businessInsuranceDTO.getProductInfo().setStartDate(startTime);
+//            businessInsuranceDTO.getProductInfo().setEndDate(endTime);
             crawlerPriceResponse = daJiaCrawlerRequestComponent.price(daJiaCrawlerPriceRequest, headers);
         }
         String lastPolicyNo = crawlerPriceResponse.getCompulsoryInsurance().getProductInfo().getLastPolicyNo();
@@ -177,12 +177,13 @@ public class DaJiaCrawlerRequestImpl implements DaJiaCrawlerRequest {
         log.info("报价返回参数:{}", JSONObject.toJSONString(crawlerPriceResponse));
         // 3.1 保存订单到保险公司系统
         DaJiaCrawlerSaveRequest daJiaCrawlerSaveRequest = new DaJiaCrawlerSaveRequest(crawlerPriceResponse, userCode, quoteVo);
+        log.info("大家保存数据:{}", JSONObject.toJSONString(daJiaCrawlerSaveRequest));
         DaJiaCrawlerSaveResponse daJiaCrawlerSaveResponse = daJiaCrawlerRequestComponent.save(daJiaCrawlerSaveRequest, headers);
         log.info("daJiaCrawlerSaveResponse:{}", JSONObject.toJSONString(daJiaCrawlerSaveResponse));
         // 3.2 初始化订单数据
         DaJiaCrawlerInitDataRequest daJiaCrawlerInitDataRequest = new DaJiaCrawlerInitDataRequest(daJiaCrawlerSaveResponse.getCompulsoryInsurance(), daJiaCrawlerSaveResponse.getBusinessInsurance());
         daJiaCrawlerRequestComponent.initData(daJiaCrawlerInitDataRequest, headers);
-
+        log.info("大家初始化数据:{}", JSONObject.toJSONString(daJiaCrawlerInitDataRequest));
         // 4.0 缓存订单数据供后续操作使用
         daJiaCrawlerCacheComponent.cacheDaJiaCrawlerSaveResponse(daJiaCrawlerSaveResponse, daJiaCrawlerSaveResponse.getProposalInfo().getOrderNo());
 

+ 51 - 25
tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/entity/request/GuoRenCrawlerProposalNoSaveRequest.java

@@ -49,49 +49,75 @@ public class GuoRenCrawlerProposalNoSaveRequest implements Serializable {
     }
 
     /**
-     * 从试算响应中提取报价ID,回填到保存请求的bc字段中
+     * 从试算响应中提取报价ID及关键字段,回填到保存请求中
      * 解决保司合规规则"报价ID缺失"问题
      */
     private void fillProposalNoFromResponse(GuoRenCrawlerCalculationPremiumData response) {
         if (response == null) {
             return;
         }
-        // 1. 从响应的BC中回填proposalNo到请求的BC
+        // 1. 从响应的BC中回填关键字段到请求的BC
         GuoRenCrawlerCalculationPremiumData.BcDTO responseBc = response.getBc();
-        if (responseBc != null) {
-            // 回填BC.prpTmain.proposalNo
-            if (responseBc.getPrpTmain() != null && StringUtils.hasText(responseBc.getPrpTmain().getProposalNo())) {
-                String bcProposalNo = responseBc.getPrpTmain().getProposalNo();
-                String bcDemandNo = responseBc.getPrpTmain().getDemandNo();
-                if (this.bc.getPrpTmain() != null) {
-                    this.bc.getPrpTmain().setProposalNo(bcProposalNo);
-                    this.bc.getPrpTmain().setDemandNo(bcDemandNo);
-                }
-                if (this.bc.getPrpTitemCar() != null) {
-                    this.bc.getPrpTitemCar().setProposalNo(bcProposalNo);
-                }
+        if (responseBc != null && responseBc.getPrpTmain() != null && this.bc.getPrpTmain() != null) {
+            String bcProposalNo = responseBc.getPrpTmain().getProposalNo();
+            String bcDemandNo = responseBc.getPrpTmain().getDemandNo();
+            String bcCertiNo = responseBc.getPrpTmain().getCertiNo();
+            String bcCarsonlyid = responseBc.getPrpTmain().getCarsonlyid();
+            if (StringUtils.hasText(bcProposalNo)) {
+                this.bc.getPrpTmain().setProposalNo(bcProposalNo);
+            }
+            if (StringUtils.hasText(bcDemandNo)) {
+                this.bc.getPrpTmain().setDemandNo(bcDemandNo);
+            }
+            if (StringUtils.hasText(bcCertiNo)) {
+                this.bc.getPrpTmain().setCertiNo(bcCertiNo);
+                this.bc.getPrpTmain().setCertino1(bcCertiNo);
+            }
+            if (StringUtils.hasText(bcCarsonlyid)) {
+                this.bc.getPrpTmain().setCarsonlyid(bcCarsonlyid);
+            }
+            if (this.bc.getPrpTitemCar() != null && StringUtils.hasText(bcProposalNo)) {
+                this.bc.getPrpTitemCar().setProposalNo(bcProposalNo);
             }
         }
-        // 2. 从响应的CI中回填proposalNo到ciInsureDemand
+        // 2. 从响应的CI中回填proposalNo/demandNo,并传播到子对象
         GuoRenCrawlerCalculationPremiumData.CiDTO responseCi = response.getCi();
-        if (responseCi != null && responseCi.getPrpTmain() != null
-                && StringUtils.hasText(responseCi.getPrpTmain().getProposalNo())) {
+        if (responseCi != null && responseCi.getPrpTmain() != null) {
             String ciProposalNo = responseCi.getPrpTmain().getProposalNo();
-            String demandNo = responseCi.getPrpTmain().getDemandNo();
+            String ciDemandNo = responseCi.getPrpTmain().getDemandNo();
+            // 回填ciInsureDemand
             if (this.ci != null && this.ci.getCiInsureDemand() != null) {
-                this.ci.getCiInsureDemand().setProposalNo(ciProposalNo);
-                this.ci.getCiInsureDemand().setDemandNo(demandNo);
+                if (StringUtils.hasText(ciProposalNo)) {
+                    this.ci.getCiInsureDemand().setProposalNo(ciProposalNo);
+                }
+                if (StringUtils.hasText(ciDemandNo)) {
+                    this.ci.getCiInsureDemand().setDemandNo(ciDemandNo);
+                }
+            }
+            // 传播demandNo到ciCarShipTaxQGDemandSchema
+            if (this.ci != null && this.ci.getCiCarShipTaxQGDemandSchema() != null) {
+                String effectiveDemandNo = StringUtils.hasText(ciDemandNo) ? ciDemandNo
+                        : (this.ci.getCiInsureDemand() != null ? this.ci.getCiInsureDemand().getDemandNo() : null);
+                if (StringUtils.hasText(effectiveDemandNo)) {
+                    this.ci.getCiCarShipTaxQGDemandSchema().setDemandNo(effectiveDemandNo);
+                }
+                if (StringUtils.hasText(ciProposalNo)) {
+                    this.ci.getCiCarShipTaxQGDemandSchema().setProposalNo(ciProposalNo);
+                }
             }
         }
-        // 3. 从响应的BI中回填proposalNo到ciInsureDemand
+        // 3. 从响应的BI中回填proposalNo/demandNo到ciInsureDemand
         GuoRenCrawlerCalculationPremiumData.BiDTO responseBi = response.getBi();
-        if (responseBi != null && responseBi.getPrpTmain() != null
-                && StringUtils.hasText(responseBi.getPrpTmain().getProposalNo())) {
+        if (responseBi != null && responseBi.getPrpTmain() != null) {
             String biProposalNo = responseBi.getPrpTmain().getProposalNo();
             String biDemandNo = responseBi.getPrpTmain().getDemandNo();
             if (this.bi != null && this.bi.getCiInsureDemand() != null) {
-                this.bi.getCiInsureDemand().setProposalNo(biProposalNo);
-                this.bi.getCiInsureDemand().setDemandNo(biDemandNo);
+                if (StringUtils.hasText(biProposalNo)) {
+                    this.bi.getCiInsureDemand().setProposalNo(biProposalNo);
+                }
+                if (StringUtils.hasText(biDemandNo)) {
+                    this.bi.getCiInsureDemand().setDemandNo(biDemandNo);
+                }
             }
         }
     }

+ 20 - 6
tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/service/impl/GuoRenCrawlerQuoteProcessServiceImpl.java

@@ -111,7 +111,7 @@ public class GuoRenCrawlerQuoteProcessServiceImpl implements GuoRenCrawlerQuoteP
     public HttpHeaders getHeaders(String username) {
         HttpHeaders headers = new HttpHeaders();
         String token = guoRenCrawlerCacheComponent.getToken(username);
-//        String token = "eyJzdWIiOiJXMzAwMDM5OSIsInVzZXJJZCI6IjQ5NjE4MSIsIm5hbWUiOiLmnY7mmKXmooUiLCJleHAiOjE3Nzg5MjY2MDB9.UYGTs0DQgbZJinMWJExUV18JhfJW23JonveQVVq6N5Muy_vu6Py8pL-AI5Ohv0BDoCCfdOD7SjLmiCqGy4uU9130O1_QlLzyxsriiBKUPXPJKbipftpqSdjYyFSgfNSIySO99hs005Ip89HnkaT34ephMA-C8jyyxGShQdNkuxI";
+//        String token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJXMzAwMDM5OSIsInVzZXJJZCI6IjQ5NjE4MSIsIm5hbWUiOiLmnY7mmKXmooUiLCJleHAiOjE3ODEyNDU1NDJ9.YsfJuQ58NaPa-7aUrYjh0SdvRkxhsiejP648H39j6h1PztC0b5Rdb8GOi5oGkVew_CI-T5j0VeaYfSFF-DrqrT_LReqtbl-iM0_OeN8acFLNcXbhfv80mRz1HqPZrUXrwngS-8Q6eF42gOJVxAX_nSFNdJpX5977A3_KJdmVbxs";
         if (!StringUtils.hasText(token)) {
             throw new TokenExpireException("500", "国任财险账号登录过期,请重新登录");
         }
@@ -215,11 +215,25 @@ public class GuoRenCrawlerQuoteProcessServiceImpl implements GuoRenCrawlerQuoteP
                 calculationPremium.getCi().getPrpTmain().getProposalNo());
         crawlerCalculationPremiumRequest.getCi().setCiInsureDemand(calculationPremium.getCi().getCiInsureDemand());
         crawlerCalculationPremiumRequest.getCi().setPrptengageList(calculationPremium.getCi().getPrptengageList());
-        // BC也需要回填proposalNo
-        if (calculationPremium.getBc() != null && calculationPremium.getBc().getPrpTmain() != null
-                && org.springframework.util.StringUtils.hasText(calculationPremium.getBc().getPrpTmain().getProposalNo())) {
-            crawlerCalculationPremiumRequest.getBc().getPrpTmain().setProposalNo(
-                    calculationPremium.getBc().getPrpTmain().getProposalNo());
+        // BC也需要回填proposalNo/demandNo/certiNo/carsonlyid
+        if (calculationPremium.getBc() != null && calculationPremium.getBc().getPrpTmain() != null) {
+            if (org.springframework.util.StringUtils.hasText(calculationPremium.getBc().getPrpTmain().getProposalNo())) {
+                crawlerCalculationPremiumRequest.getBc().getPrpTmain().setProposalNo(
+                        calculationPremium.getBc().getPrpTmain().getProposalNo());
+            }
+            if (org.springframework.util.StringUtils.hasText(calculationPremium.getBc().getPrpTmain().getDemandNo())) {
+                crawlerCalculationPremiumRequest.getBc().getPrpTmain().setDemandNo(
+                        calculationPremium.getBc().getPrpTmain().getDemandNo());
+            }
+            if (org.springframework.util.StringUtils.hasText(calculationPremium.getBc().getPrpTmain().getCertiNo())) {
+                String certiNo = calculationPremium.getBc().getPrpTmain().getCertiNo();
+                crawlerCalculationPremiumRequest.getBc().getPrpTmain().setCertiNo(certiNo);
+                crawlerCalculationPremiumRequest.getBc().getPrpTmain().setCertino1(certiNo);
+            }
+            if (org.springframework.util.StringUtils.hasText(calculationPremium.getBc().getPrpTmain().getCarsonlyid())) {
+                crawlerCalculationPremiumRequest.getBc().getPrpTmain().setCarsonlyid(
+                        calculationPremium.getBc().getPrpTmain().getCarsonlyid());
+            }
         }
         if (crawlerCalculationPremiumRequest.getBi().getPrpTmain() != null) {
             crawlerCalculationPremiumRequest.getBi().getPrpTmain().setDemandNo(demandNoBi);

+ 21 - 8
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerQuoteRequest.java

@@ -35,8 +35,8 @@ public class HtCrawlerQuoteRequest implements Serializable {
     @Serial
     private static final long serialVersionUID = -3648463870224027622L;
 
-    public HtCrawlerQuoteRequest(QuoteVo quoteVo, String mealid, String basePartJSON, String personalInsuredJSON) {
-        DataDTO dataDTO = new DataDTO(quoteVo, mealid, basePartJSON, personalInsuredJSON);
+    public HtCrawlerQuoteRequest(QuoteVo quoteVo, String mealid, String limitAmount, String basePartJSON, String personalInsuredJSON) {
+        DataDTO dataDTO = new DataDTO(quoteVo, mealid, limitAmount, basePartJSON, personalInsuredJSON);
         ObjectMapper objectMapper = new ObjectMapper();
         Map<String, Object> dataMap = objectMapper.convertValue(dataDTO, new TypeReference<Map<String, Object>>() {});
 
@@ -68,7 +68,7 @@ public class HtCrawlerQuoteRequest implements Serializable {
         @Serial
         private static final long serialVersionUID = 3131283514749202563L;
 
-        public DataDTO(QuoteVo quoteVo, String mealid, String basePartJSON, String personalInsuredJSON) {
+        public DataDTO(QuoteVo quoteVo, String mealid, String limitAmount, String basePartJSON, String personalInsuredJSON) {
             RiskInfoVo ciRiskInfoVo = quoteVo.getCiRiskInfoVo();
             RiskInfoVo biRiskInfoVo = quoteVo.getBiRiskInfoVo();
             VehicleAndVesselTaxVo taxVo = quoteVo.getVehicleAndVesselTaxVo();
@@ -87,7 +87,7 @@ public class HtCrawlerQuoteRequest implements Serializable {
             this.afloatValue = "";
             this.hastax = this.isInsureTraffic;
             this.mealid = StringUtils.isBlank(mealid) ? "" : mealid;
-            List<CovListDTO> covListDTOS = getCovListDTOS(quoteVo);
+            List<CovListDTO> covListDTOS = getCovListDTOS(quoteVo, limitAmount);
             this.covListJSON = JSON.toJSONString(covListDTOS);
             //this.covListJSON = "[{'comcoveragecode':'B','limitamount':'2000000','insteadwalkdailylimitamount':'2000000','coverageclasscode':'1','startdate':'2026-06-26 00:00:00','enddate':'2027-06-25 23:59:59','coveragecode':'1251','isdedu':'1','independentvalue':'','comcoveragename':'机动车第三者责任保险','deductible':'','basedpremium':'','benchmarkpremium':'','kindpremiun':'','deductiblerate':'','billdate':'2026-06-26 00:00:00','serviceTimes':'','serviceCode':''},{'comcoveragecode':'D11','limitamount':'20000','insteadwalkdailylimitamount':'20000','coverageclasscode':'1','startdate':'2026-06-26 00:00:00','enddate':'2027-06-25 23:59:59','coveragecode':'1251','isdedu':'1','independentvalue':'','comcoveragename':'机动车车上人员责任险(司机)','deductible':'','basedpremium':'','benchmarkpremium':'','kindpremiun':'','deductiblerate':'','billdate':'2026-06-26 00:00:00','serviceTimes':'','serviceCode':''},{'comcoveragecode':'D12','limitamount':'20000','insteadwalkdailylimitamount':'20000','coverageclasscode':'1','startdate':'2026-06-26 00:00:00','enddate':'2027-06-25 23:59:59','coveragecode':'1251','isdedu':'1','independentvalue':'','comcoveragename':'机动车车上人员责任险(乘客)','deductible':'','basedpremium':'','benchmarkpremium':'','kindpremiun':'','deductiblerate':'','billdate':'2026-06-26 00:00:00','serviceTimes':'','serviceCode':''},{'comcoveragecode':'BZ','coveragecode':'1002','startdate':'2026-06-26 00:00:00','enddate':'2027-06-25 23:59:59','comcoveragename':'机动车交通事故责任强制保险','billdate':'2026-06-26 00:00:00','limitamount':'200000'}]";
             this.coveragecode = "1251";
@@ -200,7 +200,7 @@ public class HtCrawlerQuoteRequest implements Serializable {
          * @param quoteVo
          * @return
          */
-        public List<CovListDTO> getCovListDTOS(QuoteVo quoteVo) {
+        public List<CovListDTO> getCovListDTOS(QuoteVo quoteVo, String limitAmount) {
             List<CovListDTO> covListDTOS = new ArrayList<>();
             //商业险险种信息
             List<KindInfoVo> kindInfoVoList = quoteVo.getKindInfoVo();
@@ -219,9 +219,22 @@ public class HtCrawlerQuoteRequest implements Serializable {
                     covListDTO.setCoveragecode("1251");
                     covListDTO.setComcoveragecode(kindInfoVo.getKindCode());
                     covListDTO.setComcoveragename(kindInfoVo.getKindName());
-                    covListDTO.setLimitamount(kindInfoVo.getAmount());
-                    covListDTO.setInsteadwalkdailylimitamount(kindInfoVo.getAmount());
-                    covListDTO.setCoverageclasscode("1");
+                    // 机动车损失保险
+                    if(kindInfoVo.getKindCode().equals("A")) {
+                        covListDTO.setLimitamount(limitAmount);
+                        covListDTO.setInsteadwalkdailylimitamount(limitAmount);
+                    } else {
+                        covListDTO.setLimitamount(kindInfoVo.getAmount());
+                        covListDTO.setInsteadwalkdailylimitamount(kindInfoVo.getAmount());
+                    }
+                    // PB附加医保外医疗费用责任险(三者) PD11附加医保外医疗费用责任险(司机)
+                    // PD12附加医保外医疗费用责任险(乘客)SR附加机动车道路救援服务特约条款
+                    if(kindInfoVo.getKindCode().equals("PB") || kindInfoVo.getKindCode().equals("PD11") ||
+                            kindInfoVo.getKindCode().equals("PD12") || kindInfoVo.getKindCode().equals("SR")) {
+                        covListDTO.setCoverageclasscode("2");
+                    } else {
+                        covListDTO.setCoverageclasscode("1");
+                    }
                     covListDTO.setStartdate(ciRiskInfoVo.getStartDate());
                     covListDTO.setEnddate(ciRiskInfoVo.getEndDate());
                     covListDTO.setIsdedu("1");

+ 824 - 1174
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java

@@ -5,22 +5,21 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.jzg.commons.constants.ProductsType;
 import com.jzg.commons.constants.dict.InsOrderStatusEnum;
-import com.jzg.commons.core.page.HttpResult;
 import com.jzg.commons.entity.po.jyxSetting;
 import com.jzg.commons.entity.quote.vo.AccidentalDrivingVo;
 import com.jzg.commons.entity.quote.vo.AttributionInformationVo;
 import com.jzg.commons.entity.quote.vo.CarInfoVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.entity.quote.vo.aggregated.*;
+import com.jzg.commons.entity.quote.vo.base.LoginBase;
+import com.jzg.commons.entity.quote.vo.base.OrderBase;
+import com.jzg.commons.entity.quote.vo.image.InsuranceImageVo;
 import com.jzg.commons.entity.vo.JyxConfigVo;
 import com.jzg.commons.exception.SystemException;
 import com.jzg.commons.util.StringUtils;
 import com.jzg.quotation.commons.annotation.QuoteCrawlerService;
 import com.jzg.quotation.commons.client.PlatformClient;
 import com.jzg.quotation.commons.constant.InsuranceCode;
-import com.jzg.commons.entity.quote.vo.base.LoginBase;
-import com.jzg.commons.entity.quote.vo.base.OrderBase;
-import com.jzg.commons.entity.quote.vo.image.InsuranceImageVo;
 import com.jzg.quotation.commons.entity.DetectLoginRequest;
 import com.jzg.quotation.commons.entity.GetCaptchaRequest;
 import com.jzg.quotation.commons.entity.GetCaptchaResponse;
@@ -39,7 +38,6 @@ import com.jzg.quotation.huatai.crawler.entity.po.HtCrawlerOrder;
 import com.jzg.quotation.huatai.crawler.properties.HuaTaiCrawlerProperties;
 import com.jzg.quotation.huatai.crawler.service.HtCrawlerOrderService;
 import com.jzg.quotation.huatai.crawler.service.HuaTaiCrawlerRequest;
-import com.jzg.quotation.huatai.crawler.util.CookieUtils;
 import com.jzg.quotation.huatai.crawler.util.CryptoUtil;
 import com.jzg.quotation.huatai.crawler.util.UploadUtil;
 import lombok.RequiredArgsConstructor;
@@ -48,14 +46,20 @@ import org.jetbrains.annotations.NotNull;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.select.Elements;
-import org.springframework.http.*;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestTemplate;
 import org.springframework.web.util.UriComponentsBuilder;
 
-import javax.net.ssl.*;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.InetSocketAddress;
@@ -67,1426 +71,1072 @@ import java.security.cert.X509Certificate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- * &#064;description:  华泰财险 爬虫报价
- * &#064;author:  shenwd
- * &#064;date:  2025/4/15 08:56
- **/
+ * 华泰财险爬虫报价核心实现
+ */
 @Slf4j
 @QuoteCrawlerService(value = InsuranceCode.INSURANCE_HTIC)
 @RequiredArgsConstructor
 public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
-    // ====================== 常量定义 ======================
-    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
+    // ====================== 常量 ======================
+    private static final String UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
     private static final String ACCEPT_HTML = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7";
     private static final String ACCEPT_JSON = "application/json, text/javascript, */*; q=0.01";
-    private static final String ACCEPT_ALL = "*/*";
     private static final String ORIGIN = "https://cspxp.pc.ehuatai.com";
-    private static final String REFERER_START_PROCESS = ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=startProcess";
-    private static final String REFERER_SAVE_ONE_STEP = ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=saveOneStept";
-    private static final String REFERER_QUOTATION_LIST = ORIGIN + "/htcsp/QuotationQueryAction.do?cmd=quotationListAll";
-    private static final String REFERER_DOCUMENT_PRINT = ORIGIN + "/htcsp/DocumentPrintAction.do?cmd=queryDocumentPrintList";
-    private static final String REFERER_CHECK_INS = ORIGIN + "/htcsp/checkInsAction.do?cmd=welcome&recommendNo=&checkedThreeWriteFlagRecommend=Y";
     private static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded; charset=UTF-8";
-    private static final String CONTENT_TYPE_FORM_SIMPLE = "application/x-www-form-urlencoded";
-    private static final String X_REQUESTED_WITH = "XMLHttpRequest";
+    private static final String XHR = "XMLHttpRequest";
     private static final String BASE_URL = "https://cspxp.pc.ehuatai.com";
     private static final int MAX_RETRIES = 3;
     private static final long RETRY_DELAY_MS = 3000;
-    private static final long SHORT_RETRY_DELAY_MS = 100;
+    private static final long SHORT_DELAY_MS = 100;
     private static final String RISK_CODE = "1251";
     private static final String XBCOM_CODE = "927";
     private static final String SPECIAL_ACCOUNT_XN = "XN92709001";
-    private static final String ERROR_PREFIX = "报价失败:";
+    private static final String ERR_PREFIX = "报价失败:";
     private static final String CHINA = "中国";
-    private static final String END_DATE_PATTERN = "终保日期\\s*([^;]+)";
-    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
-    private static final String DATE_FORMAT_T = "yyyy-MM-dd'T'HH:mm:ss";
-    private static final String PROXY_HOST = "39.98.41.104";
-    private static final int PROXY_PORT = 2025;
-
-    // ====================== 预编译正则 ======================
-    private static final Pattern PATTERN_QUOTATION_NO_NORMAL = Pattern.compile("value=\"(.*?)\"value=\"\" id=\"quotationNo\"");
-    private static final Pattern PATTERN_QUOTATION_NO_SPECIAL = Pattern.compile("name=\"quotationNo\" value=\"(.*?)\"");
-    private static final Pattern PATTERN_MEAL_ID = Pattern.compile("\"mealid\":\"(.*?)\"");
-    private static final Pattern PATTERN_END_DATE = Pattern.compile(END_DATE_PATTERN);
-
-    // ====================== 特殊账号列表 ======================
+    private static final String DATE_FMT = "yyyy-MM-dd HH:mm:ss";
+    private static final String DATE_FMT_T = "yyyy-MM-dd'T'HH:mm:ss";
+
+    private static final Pattern PAT_QNO_NORMAL = Pattern.compile("value=\"(.*?)\"value=\"\" id=\"quotationNo\"");
+    private static final Pattern PAT_QNO_SPECIAL = Pattern.compile("name=\"quotationNo\" value=\"(.*?)\"");
+    private static final Pattern PAT_MEAL_ID = Pattern.compile("\"mealid\":\"(.*?)\"");
+    private static final Pattern LIMIT_AMOUNT = Pattern.compile("value=\"(.*?)\" id=\"js_actualvalue\"");
+    private static final Pattern PAT_END_DATE = Pattern.compile("终保日期\\s*([^;]+)");
+
     private static final Set<String> SPECIAL_USERS = Set.of(
             "EA27000180", "EA27000401", "EA27000098", "EA27000076",
             "EA27000385", "EA27000336", "EA27000393", "EA27000182",
             "EA27000381", "EA27000394", "XN9279003"
     );
 
-    // ====================== 依赖注入 ======================
-    private final HuaTaiCrawlerCacheComponent huaTaiCrawlerCacheComponent;
-    private final HtCrawlerRequestComponent htCrawlerRequestComponent;
-    private final HuaTaiCrawlerProperties huaTaiCrawlerProperties;
-    private final HtCrawlerOrderService htCrawlerOrderService;
+    // ====================== 依赖 ======================
+    private final HuaTaiCrawlerCacheComponent cache;
+    private final HtCrawlerRequestComponent req;
+    private final HuaTaiCrawlerProperties props;
+    private final HtCrawlerOrderService orderService;
     private final PlatformClient platformClient;
-    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
-    // ====================== 单例 RestTemplate ======================
-    private static final RestTemplate REST_TEMPLATE = createRestTemplate();
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+    private static final RestTemplate RT = createRestTemplate();
 
     private static RestTemplate createRestTemplate() {
         SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory() {
             @Override
-            protected void prepareConnection(@NotNull HttpURLConnection connection, @NotNull String httpMethod) throws IOException {
-                if (connection instanceof HttpsURLConnection) {
-                    try {
-                        TrustManager[] trustAllCerts = new TrustManager[]{
-                                new X509TrustManager() {
-                                    @Override
-                                    public X509Certificate[] getAcceptedIssuers() {
-                                        return new X509Certificate[0];
-                                    }
-
-                                    @Override
-                                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
-                                    }
-
-                                    @Override
-                                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
-                                    }
-                                }
-                        };
-                        SSLContext sslContext = SSLContext.getInstance("TLS");
-                        sslContext.init(null, trustAllCerts, new SecureRandom());
-                        ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory());
-                        ((HttpsURLConnection) connection).setHostnameVerifier((hostname, session) -> true);
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-                super.prepareConnection(connection, httpMethod);
+            protected void prepareConnection(@NotNull HttpURLConnection c, @NotNull String m) throws IOException {
+                if (c instanceof HttpsURLConnection) trustAll((HttpsURLConnection) c);
+                super.prepareConnection(c, m);
             }
         };
-        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
-        factory.setProxy(proxy);
+        factory.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("39.98.41.104", 2025)));
         return new RestTemplate(factory);
     }
 
-    private static RestTemplate getRestTemplate() {
-        return REST_TEMPLATE;
-    }
-
-    // ====================== 登录相关 ======================
-
-    @Override
-    public void login(LoginBase loginBase) {
-        // 空实现
-    }
-
-    @Override
-    public GetCaptchaResponse getCaptcha(GetCaptchaRequest sendCaptchaRequest) {
-        return null;
-    }
+    private static void trustAll(HttpsURLConnection c) {
+        try {
+            TrustManager[] tm = {new X509TrustManager() {
+                public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
+                public void checkClientTrusted(X509Certificate[] certs, String authType) { }
+                public void checkServerTrusted(X509Certificate[] certs, String authType) { }
+            }};
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(null, tm, new SecureRandom());
+            c.setSSLSocketFactory(ctx.getSocketFactory());
+            c.setHostnameVerifier((h, s) -> true);
+        } catch (Exception e) { throw new RuntimeException(e); }
+    }
+
+    // ====================== 登录 ======================
+    @Override public void login(LoginBase loginBase) { }
+    @Override public GetCaptchaResponse getCaptcha(GetCaptchaRequest r) { return null; }
 
     @Override
-    public boolean detectLogin(DetectLoginRequest detectLoginRequest) {
-        String cookie = htCrawlerRequestComponent.getCookie();
+    public boolean detectLogin(DetectLoginRequest r) {
+        String cookie = req.getCookie();
         if (!StringUtils.hasText(cookie)) {
-            cookie = htCrawlerRequestComponent.getHaTaiCookie(
-                    RequestUrl.NE_COOKIE.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()));
+            cookie = req.getHaTaiCookie(RequestUrl.NE_COOKIE.getRequestUrl(props.getBaseUrl()));
         }
         return StringUtils.hasText(cookie);
     }
 
     @Override
-    public Map<String, Object> login(LoginDTO loginDTO) {
-        return htCrawlerRequestComponent.authenticate(loginDTO, HtRequestConstant.REQUEST_NUMBER);
+    public Map<String, Object> login(LoginDTO dto) {
+        return req.authenticate(dto, HtRequestConstant.REQUEST_NUMBER);
     }
 
     // ====================== 重试工具 ======================
-
     @FunctionalInterface
-    public interface RetryableTask<T> {
-        T execute() throws Exception;
-    }
-
-    private <T> T executeWithRetry(RetryableTask<T> task, int maxRetries, long delayMs, String taskName) {
-        int retryCount = 0;
-        while (retryCount < maxRetries) {
-            try {
-                return task.execute();
-            } catch (Exception e) {
-                retryCount++;
-                log.warn("{} 失败,第 {} 次重试", taskName, retryCount);
-                if (retryCount >= maxRetries) {
-                    throw new SystemException(taskName + " 失败,已重试 " + maxRetries + " 次", e.getMessage());
-                }
-                sleepQuietly(delayMs);
+    public interface RetryableTask<T> { T execute() throws Exception; }
+
+    private <T> T retry(RetryableTask<T> task, int max, long delay, String name) {
+        for (int i = 0; i < max; i++) {
+            try { return task.execute(); }
+            catch (Exception e) {
+                log.warn("{} 失败,第 {} 次重试", name, i + 1);
+                if (i + 1 >= max) throw new SystemException(name + " 失败,已重试 " + max + " 次", e.getMessage());
+                sleep(delay);
             }
         }
-        throw new SystemException(taskName + " 失败,已达到最大重试次数");
+        throw new SystemException(name + " 失败,已达到最大重试次数");
     }
 
-    private void sleepQuietly(long millis) {
-        try {
-            Thread.sleep(millis);
-        } catch (InterruptedException ie) {
-            Thread.currentThread().interrupt();
-            throw new RuntimeException("重试被中断", ie);
-        }
+    private void sleep(long ms) {
+        try { Thread.sleep(ms); }
+        catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("重试被中断", ie); }
     }
 
     // ====================== 续保查询 ======================
-
-    public Map<String, Object> xbQuery(String username, String cookie, CarInfoVo carInfoVo) throws Exception {
+    public Map<String, Object> xbQuery(String username, String cookie, CarInfoVo car) throws Exception {
         log.info("开始续保查询");
-        String modelcodexh = "";
-        String quotationNo = "";
-        HttpHeaders headers = buildHtmlHeadersWithCookie(cookie);
-        headers.set("Referer", "https://cspxp.pc.ehuatai.com/htcsp/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=");
-
-        boolean isSpecial = SPECIAL_USERS.contains(username) || SPECIAL_ACCOUNT_XN.equals(username);
-
-        if (isSpecial) {
-            headers = buildSpecialHeaders(cookie);
-            HttpEntity<String> entity = new HttpEntity<>(headers);
-            getRestTemplate().exchange(
-                    huaTaiCrawlerProperties.getBaseUrl() + "/htcsp/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=",
-                    HttpMethod.POST, entity, String.class);
-            log.info("xbQuery:menuControl");
-            headers = buildSpecialHeaders2(cookie);
-        }
+        boolean special = SPECIAL_USERS.contains(username) || SPECIAL_ACCOUNT_XN.equals(username);
 
-        HttpEntity<String> getEntity = new HttpEntity<>(headers);
-        ResponseEntity<String> respStart = getRestTemplate().exchange(
-                huaTaiCrawlerProperties.getBaseUrl() + "/htcsp/VehicleInfoEntryAction.do?cmd=startProcess",
-                HttpMethod.GET, getEntity, String.class);
-        log.info("xbQuery:startProcess");
+        HttpHeaders headers = special ? buildSpecialHeaders(cookie, true) : buildHtmlHeaders(cookie);
+        if (special) {
+            get(ORIGIN + "/htcsp/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=", headers, null);
+            headers = buildSpecialHeaders(cookie, false);
+        }
 
-        quotationNo = extractQuotationNo(respStart.getBody(), isSpecial);
+        String body = get(props.getBaseUrl() + "/htcsp/VehicleInfoEntryAction.do?cmd=startProcess", headers, null);
+        String quotationNo = extractQuotationNo(body, special);
         log.info("quotationNo: {}", quotationNo);
 
-        headers = buildJsonHeadersWithCookie(cookie);
-        Map<String, Object> dataMap = buildXbQueryDataMap(carInfoVo, quotationNo);
+        Map<String, Object> dataMap = buildXbQueryMap(car, quotationNo);
         Map<String, Object> encrypted = encrypt(dataMap);
-        log.info("xbQuery:encrypted: {}", encrypted);
-
-        MultiValueMap<String, String> postData = buildEncryptedPostData(encrypted);
-        HttpEntity<MultiValueMap<String, String>> postEntity = new HttpEntity<>(postData, headers);
-        ResponseEntity<String> respXbquery = getRestTemplate().exchange(
-                huaTaiCrawlerProperties.getBaseUrl() + "/htcsp/VehicleInfoEntryAction.do?cmd=xbquery",
-                HttpMethod.POST, postEntity, String.class);
-        log.info("xbQuery:xbquery:response");
-
-        Map<String, Object> res = decrypt(OBJECT_MAPPER.readValue(respXbquery.getBody(), HashMap.class));
-        log.info("xbQuery:xbquery: {}", JSONObject.toJSONString(res));
-
-        Map<String, Object> carmodeldtojson = (Map<String, Object>) res.get("carmodeldtojson");
-        if (carmodeldtojson != null && !carmodeldtojson.isEmpty()) {
-            modelcodexh = (String) carmodeldtojson.get("modelcodexh");
-            log.info("modelcodexh: {}", modelcodexh);
-        }
+        log.info("xbQuery encrypted: {}", encrypted);
 
-        Map<String, Object> result = new HashMap<>();
-        result.put("quotationNo", quotationNo);
-        result.put("modelcodexh", modelcodexh);
-        return result;
-    }
-
-    private String extractQuotationNo(String body, boolean isSpecial) {
-        Pattern pattern = isSpecial ? PATTERN_QUOTATION_NO_SPECIAL : PATTERN_QUOTATION_NO_NORMAL;
-        Matcher matcher = pattern.matcher(body);
-        return matcher.find() ? matcher.group(1) : "";
-    }
-
-    private Map<String, Object> buildXbQueryDataMap(CarInfoVo carInfoVo, String quotationNo) {
-        Map<String, Object> dataMap = new HashMap<>();
-        dataMap.put("licenseplateno", carInfoVo.getLicenseNo());
-        dataMap.put("vin", carInfoVo.getVinNo());
-        dataMap.put("engineno", carInfoVo.getEngineNo());
-        dataMap.put("liceenseplatetype", carInfoVo.getLicenseKindCode());
-        dataMap.put("ecdemicvehicleflag", "0");
-        fillVehicleTypeCodes(carInfoVo, dataMap);
-        dataMap.put("xbcomcode", XBCOM_CODE);
-        dataMap.put("quotationNo", quotationNo);
-        dataMap.put("riskCode", RISK_CODE);
-        dataMap.put("engineno2", "");
-        dataMap.put("enrollDate", "");
-        dataMap.put("isJllxFlagType", "1");
-        dataMap.put("carModelCodexhQuery", "1");
-        dataMap.put("telemarketFlag", "");
-        return dataMap;
-    }
-
-    private MultiValueMap<String, String> buildEncryptedPostData(Map<String, Object> encrypted) {
         MultiValueMap<String, String> postData = new LinkedMultiValueMap<>();
         postData.add("sourceCode", String.valueOf(encrypted.get("sourceCode")));
         postData.add("accessToken", (String) encrypted.get("accessToken"));
         postData.add("data", (String) encrypted.get("data"));
         postData.add("insuredCode", "");
-        return postData;
+
+        Map<String, Object> res = decrypt(MAPPER.readValue(
+                post(props.getBaseUrl() + "/htcsp/VehicleInfoEntryAction.do?cmd=xbquery", buildJsonHeaders(cookie), postData),
+                HashMap.class));
+        log.info("xbQuery result: {}", JSONObject.toJSONString(res));
+
+        Map<String, Object> carmodel = (Map<String, Object>) res.get("carmodeldtojson");
+        String modelcodexh = carmodel != null ? (String) carmodel.get("modelcodexh") : "";
+        log.info("modelcodexh: {}", modelcodexh);
+
+        return Map.of("quotationNo", quotationNo, "modelcodexh", modelcodexh);
     }
 
-    // ====================== 报价主流程 ======================
+    private String extractQuotationNo(String body, boolean special) {
+        Matcher m = (special ? PAT_QNO_SPECIAL : PAT_QNO_NORMAL).matcher(body);
+        return m.find() ? m.group(1) : "";
+    }
+
+    private Map<String, Object> buildXbQueryMap(CarInfoVo car, String quotationNo) {
+        Map<String, Object> m = new HashMap<>();
+        m.put("licenseplateno", car.getLicenseNo());
+        m.put("vin", car.getVinNo());
+        m.put("engineno", car.getEngineNo());
+        m.put("liceenseplatetype", car.getLicenseKindCode());
+        m.put("ecdemicvehicleflag", "0");
+        fillVehicleTypeCodes(car, m);
+        m.put("xbcomcode", XBCOM_CODE);
+        m.put("quotationNo", quotationNo);
+        m.put("riskCode", RISK_CODE);
+        m.putAll(Map.of("engineno2", "", "enrollDate", "", "isJllxFlagType", "1",
+                "carModelCodexhQuery", "1", "telemarketFlag", ""));
+        return m;
+    }
 
+    // ====================== 报价主流程 ======================
     @Override
     public QuoteResultsVo quote(QuoteVo quoteVo) throws Exception {
-        AttributionInformationVo attributionInformationVo = quoteVo.getOrder().getAttributionInformationVo();
-        LoginDTO loginDTO = buildLoginDTO(attributionInformationVo);
-        Map<String, Object> mmap = login(loginDTO);
+        AttributionInformationVo attr = quoteVo.getOrder().getAttributionInformationVo();
+        LoginDTO dto = buildLoginDTO(attr);
+        Map<String, Object> mmap = login(dto);
+        //归属信息
         Map<String, Object> gsInfo = parseToMap(mmap.get("gsInfo").toString());
         log.info("gsInfo: {}", JSONObject.toJSONString(mmap));
 
         String cookie = extractCookie(mmap);
-        CarInfoVo carInfoVo = quoteVo.getCarInfoVo();
-
-        // 预报价(带重试)
-        Map<String, Object> xbResult = executeXbQueryWithRetry(loginDTO, cookie, carInfoVo);
-        String quotationNo = getStringValue(xbResult, "quotationNo");
-        String modelcodexh = getStringValue(xbResult, "modelcodexh");
-
-        // 报价前预处理
-        QuotePreResult preResult = prepareQuote(attributionInformationVo, mmap, carInfoVo, modelcodexh, cookie, quotationNo);
+        CarInfoVo car = quoteVo.getCarInfoVo();
 
-        // 报价核心流程(带重试)
-        QuoteCoreResult coreResult = executeQuoteWithRetry(quoteVo, quotationNo, loginDTO, cookie, preResult, gsInfo, attributionInformationVo);
-
-        if (!coreResult.success()) {
-            throw new RuntimeException("报价失败,已达到最大重试次数");
-        }
-
-        // 保存订单
-        saveCrawlerOrder(quoteVo, coreResult);
-
-        return HuaTaiCrawlerQuoteResultsVoBuild.buildQuoteResultsVo(quoteVo, coreResult.quoteResponseData(), coreResult.fcPrice());
-    }
+        //带重试的获取报价编号、车型型号
+        Map<String, Object> xbResult = retry(() -> {
+            try { return xbQuery(dto.getUsername(), cookie, car); }
+            catch (Exception e) { refreshLogin(dto); throw e; }
+        }, MAX_RETRIES, RETRY_DELAY_MS, "预报价获取登录信息");
 
-    private LoginDTO buildLoginDTO(AttributionInformationVo attributionInformationVo) {
-        LoginDTO loginDTO = new LoginDTO();
-        Map<String, String> configInfo = attributionInformationVo.getConfigInfo();
-        loginDTO.setComcode(configInfo.get("comCode"));
-        loginDTO.setUsername(attributionInformationVo.getUsername());
-        loginDTO.setPassword(attributionInformationVo.getPassword());
-        loginDTO.setUseridname(configInfo.get("userid_name"));
-        loginDTO.setDlrCode(configInfo.get("dlr_code"));
-        loginDTO.setDlxyCode(configInfo.get("dlxy_code"));
-        return loginDTO;
+        String quotationNo = str(xbResult, "quotationNo");
+        String modelcodexh = str(xbResult, "modelcodexh");
+        //报价前预处理-保存车辆信息、过滤车型等处理
+        PreResult pre = prepareQuote(attr, mmap, car, modelcodexh, cookie, quotationNo);
+        //带重试的报价
+        CoreResult core = executeQuoteWithRetry(quoteVo, quotationNo, dto, cookie, pre, gsInfo, attr);
+
+        if (!core.success()) throw new RuntimeException("报价失败,已达到最大重试次数");
+        //保存订单到MongoDB
+        saveOrder(quoteVo, core);
+        //组装费用
+        return HuaTaiCrawlerQuoteResultsVoBuild.buildQuoteResultsVo(quoteVo, core.data(), core.fcPrice());
+    }
+
+    private LoginDTO buildLoginDTO(AttributionInformationVo attr) {
+        Map<String, String> cfg = attr.getConfigInfo();
+        LoginDTO dto = new LoginDTO();
+        dto.setComcode(cfg.get("comCode"));
+        dto.setUsername(attr.getUsername());
+        dto.setPassword(attr.getPassword());
+        dto.setUseridname(cfg.get("userid_name"));
+        //代理人
+        dto.setDlrCode(cfg.get("dlr_code"));
+        //代理协议
+        dto.setDlxyCode(cfg.get("dlxy_code"));
+        return dto;
     }
 
     private String extractCookie(Map<String, Object> mmap) {
-        try {
-            String cookies = (String) mmap.get("cookies");
-            return StringUtils.isBlank(cookies) ? "" : cookies;
-        } catch (Exception e) {
-            throw new SystemException("获取必要信息失败,请稍后重试");
-        }
-    }
-
-    private Map<String, Object> executeXbQueryWithRetry(LoginDTO loginDTO, String cookie, CarInfoVo carInfoVo) {
-        return executeWithRetry(() -> {
-            try {
-                return xbQuery(loginDTO.getUsername(), cookie, carInfoVo);
-            } catch (Exception e) {
-                refreshLogin(loginDTO);
-                throw e;
-            }
-        }, MAX_RETRIES, RETRY_DELAY_MS, "预报价获取登录信息");
+        String cookies = (String) mmap.get("cookies");
+        return StringUtils.isBlank(cookies) ? "" : cookies;
     }
 
-    private void refreshLogin(LoginDTO loginDTO) {
-        huaTaiCrawlerCacheComponent.cacheCookie("");
-        huaTaiCrawlerCacheComponent.cacheGsInfo("");
-        huaTaiCrawlerCacheComponent.cacheComCode("");
-        Map<String, Object> mmap = login(loginDTO);
-        log.info("refreshLogin gsInfo: {}", JSONObject.toJSONString(mmap));
+    private void refreshLogin(LoginDTO dto) {
+        cache.cacheCookie("");
+        cache.cacheGsInfo("");
+        cache.cacheComCode("");
+        log.info("refreshLogin gsInfo: {}", JSONObject.toJSONString(login(dto)));
     }
 
-    private String getStringValue(Map<String, Object> map, String key) {
-        Object value = map.get(key);
-        return value == null ? "" : value.toString();
-    }
-
-    // ====================== 内部记录类 ======================
-
-    private record QuotePreResult(HtCrawlerConfigureParameters parameters,
-                                  HtCrawlerCarQueryResponse.DataDTO dataDTO,
-                                  HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO) {
+    private String str(Map<String, Object> m, String k) {
+        Object v = m.get(k);
+        return v == null ? "" : v.toString();
     }
 
-    private record QuoteCoreResult(String fcPrice,
-                                   HtCrawlerQuoteResponse.DataDTO quoteResponseData,
-                                   boolean success) {
-    }
+    // ====================== 内部记录 ======================
+    private record PreResult(HtCrawlerConfigureParameters params,
+                             HtCrawlerCarQueryResponse.DataDTO dataDTO,
+                             HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO) { }
 
-    private record QuoteStepResult(HtCrawlerQuoteResponse.DataDTO quoteResponseData, String personalInsuredJSONString) {
-    }
+    private record CoreResult(String fcPrice, HtCrawlerQuoteResponse.DataDTO data, boolean success) { }
 
-    // ====================== 私有辅助方法 ======================
+    private record StepResult(HtCrawlerQuoteResponse.DataDTO data, String personalInsuredJSON) { }
 
-    private void saveCrawlerOrder(QuoteVo quoteVo, QuoteCoreResult coreResult) {
-        HtCrawlerOrder htCrawlerOrder = new HtCrawlerOrder();
-        htCrawlerOrder.setOrdersNo(quoteVo.getOrder().getOrdersNo());
-        String ciCategory = coreResult.quoteResponseData().getApplicationdto().getCicategory();
-        htCrawlerOrder.setCiCategory(ciCategory);
-        String biCategory = coreResult.quoteResponseData().getApplicationdto().getBicategory();
-        htCrawlerOrder.setBiCategory(biCategory);
-        htCrawlerOrder.setQuotationRelaNo(coreResult.quoteResponseData().getQuotationRelaNo());
-        htCrawlerOrderService.save(htCrawlerOrder);
+    private void saveOrder(QuoteVo q, CoreResult c) {
+        HtCrawlerOrder o = new HtCrawlerOrder();
+        o.setOrdersNo(q.getOrder().getOrdersNo());
+        o.setCiCategory(c.data().getApplicationdto().getCicategory());
+        o.setBiCategory(c.data().getApplicationdto().getBicategory());
+        o.setQuotationRelaNo(c.data().getQuotationRelaNo());
+        orderService.save(o);
     }
 
     // ====================== 报价预处理 ======================
-
-    private QuotePreResult prepareQuote(AttributionInformationVo attributionInformationVo, Map<String, Object> mmap,
-                                        CarInfoVo carInfoVo, String modelcodexh, String cookie, String quotationNo) throws JsonProcessingException {
-        HtCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(
-                HtCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
-        parameters.setFullComCode(mmap.get("comCode").toString());
-
-        HttpHeaders headers = htCrawlerRequestComponent.getHeaders1();
-        carInfoVo.setPurchasePrice(carInfoVo.getPurchasePrice().replace(".00", ""));
-
-        HtCrawlerCarQueryRequest carQueryRequest = new HtCrawlerCarQueryRequest(carInfoVo, modelcodexh);
-        HtCrawlerCarQueryResponse.DataDTO dataDTO;
-        try {
-            dataDTO = htCrawlerRequestComponent.carQuery(carQueryRequest, headers, carInfoVo);
-        } catch (Exception e) {
-            throw new SystemException(e.getMessage());
-        }
-
-        HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO = CarModelsFilterUtils.to(dataDTO.getDatalist(), carInfoVo);
-
-        // savecarquery
-        executeSaveCarQuery(carDTO, dataDTO, carInfoVo, cookie);
-
-        // carModelCodexhQuery
-        executeCarModelCodexhQuery(carDTO, carInfoVo, cookie, quotationNo);
-
-        // messageCentreTimeBoom
+    private PreResult prepareQuote(AttributionInformationVo attr, Map<String, Object> mmap,
+                                   CarInfoVo car, String modelcodexh, String cookie, String quotationNo) throws Exception {
+        HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
+                HtCrawlerConfigureParameters.class, attr.getConfigInfo());
+        params.setFullComCode(mmap.get("comCode").toString());
+        //参数格式保持一致
+        car.setPurchasePrice(car.getPurchasePrice().replace(".00", ""));
+        //车辆查询
+        HtCrawlerCarQueryResponse.DataDTO dataDTO = req.carQuery(
+                new HtCrawlerCarQueryRequest(car, modelcodexh), req.getHeaders1(), car);
+        //过滤车型
+        HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO = CarModelsFilterUtils.to(dataDTO.getDatalist(), car);
+        //保存车辆信息
+        executeSaveCarQuery(carDTO, dataDTO, car, cookie);
+        //车型查询
+        executeCarModelCodexhQuery(carDTO, car, cookie, quotationNo);
+        //额外请求
         executeMessageCentreTimeBoom(cookie);
-
-        return new QuotePreResult(parameters, dataDTO, carDTO);
+        //以上请求模拟浏览器请求流程,否则报价失败
+        return new PreResult(params, dataDTO, carDTO);
     }
 
     private void executeSaveCarQuery(HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO,
-                                     HtCrawlerCarQueryResponse.DataDTO dataDTO, CarInfoVo carInfoVo, String cookie) throws JsonProcessingException {
-        String aa = OBJECT_MAPPER.writeValueAsString(carDTO);
-        String encodedAa = URLEncoder.encode(aa, StandardCharsets.UTF_8);
-        String bb = OBJECT_MAPPER.writeValueAsString(dataDTO.getDatalist2().get(0));
-        String encodedBb = URLEncoder.encode(bb, StandardCharsets.UTF_8);
-
-        HttpHeaders headers = buildCarQueryHeaders(cookie);
-        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-        formData.add("carmodelStr", encodedAa);
-        formData.add("commonoutputStr", encodedBb);
-        formData.add("transcodeStr", dataDTO.getTranscodeStr());
-        formData.add("pmquerynoStr", "");
-        formData.add("vin", carInfoVo.getVinNo());
-        formData.add("modelcodexh", carDTO.getModelcodexh());
-        formData.add("VehicleStyleUniqueId", "");
-
-        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
-        getRestTemplate().postForEntity(
-                BASE_URL + "/htcsp/VehicleInfoEntryAction.do?cmd=savecarquery",
-                requestEntity, String.class);
+                                     HtCrawlerCarQueryResponse.DataDTO dataDTO, CarInfoVo car, String cookie) throws JsonProcessingException {
+        String aa = URLEncoder.encode(MAPPER.writeValueAsString(carDTO), StandardCharsets.UTF_8);
+        String bb = URLEncoder.encode(MAPPER.writeValueAsString(dataDTO.getDatalist2().get(0)), StandardCharsets.UTF_8);
+
+        MultiValueMap<String, String> fd = new LinkedMultiValueMap<>();
+        fd.add("carmodelStr", aa);
+        fd.add("commonoutputStr", bb);
+        fd.add("transcodeStr", dataDTO.getTranscodeStr());
+        fd.add("pmquerynoStr", "");
+        fd.add("vin", car.getVinNo());
+        fd.add("modelcodexh", carDTO.getModelcodexh());
+        fd.add("VehicleStyleUniqueId", "");
+
+        post(BASE_URL + "/htcsp/VehicleInfoEntryAction.do?cmd=savecarquery", buildCarQueryHeaders(cookie), fd);
     }
 
     private void executeCarModelCodexhQuery(HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO,
-                                            CarInfoVo carInfoVo, String cookie, String quotationNo) {
-        HttpHeaders headers = buildCarModelCodexhHeaders(cookie);
-        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-        formData.add("modelCode", carDTO.getModelcodexh());
-        formData.add("licenseNo", carInfoVo.getLicenseNo());
-        formData.add("licenseKindCode", "02 小型汽车号牌");
-        formData.add("engineNo", carInfoVo.getEngineNo());
-        formData.add("frameNo", carInfoVo.getVinNo());
-        formData.add("useNatureCode", "8A 家庭自用");
-        formData.add("carKindCode", "A0 客车");
-        formData.add("enrollDate", carInfoVo.getRegisterDate());
-        formData.add("completeKerbMass", "1250");
-        formData.add("seatCount", carDTO.getLimitloadperson());
-        formData.add("noticeType", carDTO.getNoticetype());
-        formData.add("carName", carDTO.getModelname());
-        formData.add("purchasePrice", carDTO.getPurchasePrice());
-        formData.add("tonCount", carDTO.getLimitload());
-        formData.add("exhaustScale", carDTO.getDisplacement());
-        formData.add("carModelComCode", XBCOM_CODE);
-        formData.add("quotationNo", quotationNo);
-
-        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
-        getRestTemplate().postForEntity(
-                BASE_URL + "/htcsp/VehicleInfoEntryAction.do?cmd=carModelCodexhQuery",
-                requestEntity, String.class);
+                                            CarInfoVo car, String cookie, String quotationNo) {
+        MultiValueMap<String, String> fd = new LinkedMultiValueMap<>();
+        fd.add("modelCode", carDTO.getModelcodexh());
+        fd.add("licenseNo", car.getLicenseNo());
+        fd.add("licenseKindCode", "02 小型汽车号牌");
+        fd.add("engineNo", car.getEngineNo());
+        fd.add("frameNo", car.getVinNo());
+        fd.add("useNatureCode", "8A 家庭自用");
+        fd.add("carKindCode", "A0 客车");
+        fd.add("enrollDate", car.getRegisterDate());
+        fd.add("completeKerbMass", "1250");
+        fd.add("seatCount", carDTO.getLimitloadperson());
+        fd.add("noticeType", carDTO.getNoticetype());
+        fd.add("carName", carDTO.getModelname());
+        fd.add("purchasePrice", carDTO.getPurchasePrice());
+        fd.add("tonCount", carDTO.getLimitload());
+        fd.add("exhaustScale", carDTO.getDisplacement());
+        fd.add("carModelComCode", XBCOM_CODE);
+        fd.add("quotationNo", quotationNo);
+
+        post(BASE_URL + "/htcsp/VehicleInfoEntryAction.do?cmd=carModelCodexhQuery", buildCarModelCodexhHeaders(cookie), fd);
     }
 
     private void executeMessageCentreTimeBoom(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add("Accept", ACCEPT_ALL);
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("User-Agent", USER_AGENT);
-        headers.add(HtRequestConstant.COOKIE, cookie);
-
-        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-        formData.add("messageType", "0");
-
-        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
-        getRestTemplate().postForEntity(
-                BASE_URL + "/htcsp/MessageCentreAction.do?cmd=messageCentreTimeBoom",
-                requestEntity, String.class);
+        MultiValueMap<String, String> fd = new LinkedMultiValueMap<>();
+        fd.add("messageType", "0");
+        post(BASE_URL + "/htcsp/MessageCentreAction.do?cmd=messageCentreTimeBoom",
+                header(b -> { b.add(HtRequestConstant.COOKIE, cookie); b.add("Content-Type", CONTENT_TYPE_FORM); }), fd);
     }
 
-    // ====================== 报价核心流程 ======================
-
-    private QuoteCoreResult executeQuoteWithRetry(QuoteVo quoteVo, String quotationNo, LoginDTO loginDTO,
-                                                  String cookie, QuotePreResult preResult,
-                                                  Map<String, Object> gsInfo,
-                                                  AttributionInformationVo attributionInformationVo) {
-        int retryCount = 0;
-        boolean success = false;
+    // ====================== 报价核心 ======================
+    private CoreResult executeQuoteWithRetry(QuoteVo quoteVo, String quotationNo, LoginDTO dto,
+                                             String cookie, PreResult pre,
+                                             Map<String, Object> gsInfo, AttributionInformationVo attr) {
+        int retry = 0;
         String fcPrice = "";
-        HtCrawlerQuoteResponse.DataDTO quoteResponseData = null;
+        HtCrawlerQuoteResponse.DataDTO data = null;
 
-        while (retryCount < MAX_RETRIES && !success) {
+        while (retry < MAX_RETRIES) {
             try {
-                if (retryCount > 0) {
-                    log.info("第 {} 次重试,重新开始报价流程...", retryCount + 1);
-                    sleepQuietly(SHORT_RETRY_DELAY_MS);
-                }
-
-                QuoteStepResult stepResult = executeQuoteSteps(quoteVo, quotationNo, loginDTO, cookie, preResult, gsInfo);
-                quoteResponseData = stepResult.quoteResponseData();
+                if (retry > 0) { log.info("第 {} 次重试,重新开始报价流程...", retry + 1); sleep(SHORT_DELAY_MS); }
 
-                if (quoteResponseData.getApplicationdto() != null) {
-                    success = true;
+                StepResult step = executeQuoteSteps(quoteVo, quotationNo, dto, cookie, pre, gsInfo);
+                data = step.data();
 
-                    // 调用非车险保费接口(驾意险)
-                    if (StringUtils.isNotBlank(stepResult.personalInsuredJSONString())) {
-                        fcPrice = fetchNoCarPremium(cookie, quotationNo, attributionInformationVo, stepResult.personalInsuredJSONString());
-                    }
-                } else {
-                    retryCount++;
-                    log.warn("quoteResponseData.getApplicationdto() 为 null,第 {} 次重试...", retryCount);
-                    if (retryCount >= MAX_RETRIES) {
-                        throw new RuntimeException("报价重试次数已达上限,仍然无法获取数据");
+                if (data.getApplicationdto() != null) {
+                    if (StringUtils.isNotBlank(step.personalInsuredJSON())) {
+                        fcPrice = fetchNoCarPremium(cookie, quotationNo, attr, step.personalInsuredJSON());
                     }
+                    return new CoreResult(fcPrice, data, true);
                 }
+                log.warn("applicationdto 为 null,第 {} 次重试...", ++retry);
             } catch (Exception e) {
-                if (e.getMessage() != null && e.getMessage().contains("重试次数已达上限")) {
-                    throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
-                }
-                retryCount++;
-                log.warn("报价异常,第 {} 次重试... 异常信息: {}", retryCount, e.getMessage());
-                if (retryCount >= MAX_RETRIES) {
-                    throw new RuntimeException("报价重试次数已达上限: " + e.getMessage());
-                }
-                sleepQuietly(RETRY_DELAY_MS);
+                if (e.getMessage() != null && e.getMessage().contains("重试次数已达上限")) throw wrap(e);
+                log.warn("报价异常,第 {} 次重试... 异常: {}", ++retry, e.getMessage());
+                if (retry >= MAX_RETRIES) throw new RuntimeException("报价重试次数已达上限: " + e.getMessage());
+                sleep(RETRY_DELAY_MS);
             }
         }
-        return new QuoteCoreResult(fcPrice, quoteResponseData, success);
+        return new CoreResult(fcPrice, data, false);
+    }
+
+    private RuntimeException wrap(Exception e) {
+        return e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
     }
 
     private String fetchNoCarPremium(String cookie, String quotationNo,
-                                     AttributionInformationVo attributionInformationVo,
-                                     String personalInsuredJSONString) {
+                                     AttributionInformationVo attr, String personalInsuredJSON) {
         try {
-            HttpHeaders fcHeaders = buildNoCarPremiumHeaders(cookie);
-            MultiValueMap<String, String> fcData = new LinkedMultiValueMap<>();
-            fcData.add("noCarProductMes", personalInsuredJSONString);
-            fcData.add("quotationNo", quotationNo);
-            fcData.add("comCode", attributionInformationVo.getConfigInfo().get("comCode"));
-            fcData.add("handToCount43T3_903", "");
-            fcData.add("isCheckFlag", "1");
-
-            HttpEntity<MultiValueMap<String, String>> fcRequestEntity = new HttpEntity<>(fcData, fcHeaders);
-            ResponseEntity<String> fcResponse = getRestTemplate().postForEntity(
-                    BASE_URL + "/htcsp/QuotedPriceAction.do?cmd=getNoCarPreimum",
-                    fcRequestEntity, String.class);
-
-            log.info("fcResponse: {}", fcResponse.getBody());
-            Map<String, Object> res = OBJECT_MAPPER.readValue(fcResponse.getBody(), HashMap.class);
-            return getStringValue(res, "premium");
+            MultiValueMap<String, String> fd = new LinkedMultiValueMap<>();
+            fd.add("noCarProductMes", personalInsuredJSON);
+            fd.add("quotationNo", quotationNo);
+            fd.add("comCode", attr.getConfigInfo().get("comCode"));
+            fd.add("handToCount43T3_903", "");
+            fd.add("isCheckFlag", "1");
+
+            String body = post(BASE_URL + "/htcsp/QuotedPriceAction.do?cmd=getNoCarPreimum",
+                    buildNoCarPremiumHeaders(cookie), fd);
+            log.info("fcResponse: {}", body);
+            return str(MAPPER.readValue(body, HashMap.class), "premium");
         } catch (Exception e) {
             log.error("非车险保费获取失败: {}", e.getMessage());
             return "";
         }
     }
 
-    private HttpHeaders buildNoCarPremiumHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, cookie);
-        headers.add("Accept", "*/*");
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.add("Connection", "keep-alive");
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("Origin", ORIGIN);
-        headers.add("Referer", REFERER_SAVE_ONE_STEP);
-        headers.add("User-Agent", USER_AGENT);
-        headers.add("X-Requested-With", X_REQUESTED_WITH);
-        return headers;
-    }
-
-    private QuoteStepResult executeQuoteSteps(QuoteVo quoteVo, String quotationNo, LoginDTO loginDTO,
-                                              String cookie, QuotePreResult preResult,
-                                              Map<String, Object> gsInfo) throws Exception {
-        // 报价第一步 保存车辆信息
-        HttpHeaders headers2 = buildQuoteStep1Headers(cookie);
-        HtCrawlerSaveOneStepRequest saveOneStepRequest = new HtCrawlerSaveOneStepRequest(
-                quoteVo, preResult.carDTO(), preResult.parameters(), quotationNo,
-                preResult.dataDTO(), loginDTO, gsInfo);
-
-        ResponseEntity<Object> objectResponseEntity = htCrawlerRequestComponent.saveOneStept(saveOneStepRequest, headers2);
-        String body = objectResponseEntity.getBody().toString();
+    private StepResult executeQuoteSteps(QuoteVo q, String quotationNo, LoginDTO dto,
+                                         String cookie, PreResult pre, Map<String, Object> gsInfo) throws Exception {
+        HttpHeaders h = buildQuoteStep1Headers(cookie);
+        HtCrawlerSaveOneStepRequest r = new HtCrawlerSaveOneStepRequest(q, pre.carDTO(), pre.params(), quotationNo, pre.dataDTO(), dto, gsInfo);
+        String body = req.saveOneStept(r, h).getBody().toString();
         String mealid = extractMealId(body);
+        //机动车损失保险
+        String limitAmount = extractLimitAmount(body);
         log.info("mealid: {}", mealid);
-
-        // 驾意险处理
-        String personalInsuredJSONString = buildPersonalInsuredJSON(quoteVo);
-
-        // 报价第二步
-        headers2 = buildQuoteStep2Headers(cookie);
-        HtCrawlerQuoteRequest quoteRequest = new HtCrawlerQuoteRequest(quoteVo, mealid, "", personalInsuredJSONString);
-        String decryptData = htCrawlerRequestComponent.quote(quoteRequest, headers2);
-
-        // 错误处理
-        handleQuoteErrors(decryptData, quoteVo, mealid, headers2, loginDTO);
-
-        HtCrawlerQuoteResponse.DataDTO quoteResponseData = resolveQuoteResponse(quoteVo, decryptData, mealid, headers2, personalInsuredJSONString);
-        return new QuoteStepResult(quoteResponseData, personalInsuredJSONString);
+        log.info("limitAmount: {}", limitAmount);
+        //组装驾意险参数,没有就空字符串
+        String personalInsuredJSON = buildPersonalInsuredJSON(q);
+
+        h = buildQuoteStep2Headers(cookie);
+        //组装报价参数
+        HtCrawlerQuoteRequest qr = new HtCrawlerQuoteRequest(q, mealid, limitAmount, "", personalInsuredJSON);
+        //报价第一次
+        String decryptData = req.quote(qr, h);
+        //处理重复投保返回终保日期的
+        q = handleQuoteErrors(decryptData, q);
+        //处理可能的录入的校验码有误
+        HtCrawlerQuoteResponse.DataDTO data = resolveQuoteResponse(q, decryptData, mealid, limitAmount, h, personalInsuredJSON);
+        return new StepResult(data, personalInsuredJSON);
     }
 
     private String extractMealId(String body) {
-        Matcher matcher = PATTERN_MEAL_ID.matcher(body);
-        return matcher.find() ? matcher.group(1) : "";
+        Matcher m = PAT_MEAL_ID.matcher(body);
+        return m.find() ? m.group(1) : "";
     }
 
-    // ====================== 驾意险处理 ======================
+    private String extractLimitAmount(String body) {
+        Matcher m = LIMIT_AMOUNT.matcher(body);
+        return m.find() ? m.group(1) : "";
+    }
 
-    private String buildPersonalInsuredJSON(QuoteVo quoteVo) {
-        if (quoteVo.getAccidentalDrivings() == null || quoteVo.getAccidentalDrivings().isEmpty()) {
-            return "";
-        }
+    // ====================== 驾意险组装 ======================
+    private String buildPersonalInsuredJSON(QuoteVo q) {
+        if (q.getAccidentalDrivings() == null || q.getAccidentalDrivings().isEmpty()) return "";
 
-        List<Map<String, Object>> personalInsuredJSON = new ArrayList<>();
         List<Map<String, Object>> perinsuredList = new ArrayList<>();
-
-        JyxConfigVo lastConfigVo = null;
-        jyxSetting lastSetting = null;
-
-        for (AccidentalDrivingVo drivingDTO : quoteVo.getAccidentalDrivings()) {
-            HttpResult<JyxConfigVo> configResult = platformClient.jyxConfigGetById(drivingDTO.getId());
-            JyxConfigVo configVo = configResult.getData();
-            lastConfigVo = configVo;
-
-            int seatCount = Integer.parseInt(quoteVo.getCarInfoVo().getSeatCount());
-            jyxSetting setting = configVo.getSettingList().stream()
-                    .filter(s -> s.getSeats() == seatCount)
-                    .findFirst()
-                    .orElse(null);
-            lastSetting = setting;
-
-            String productCode = configVo.getParentProductCode();
-            perinsuredList.add(buildInsuredInfo(quoteVo, productCode));
-        }
-
-        personalInsuredJSON.add(buildAccidentalDriving(quoteVo, lastConfigVo, lastSetting, perinsuredList));
-        return JSONObject.toJSONString(personalInsuredJSON);
-    }
-
-    private Map<String, Object> buildInsuredInfo(QuoteVo quoteVo, String productCode) {
-        Map<String, Object> info = new HashMap<>();
-        info.put("productcode", StringUtils.isEmpty(productCode) ? "E007" : productCode);
-        info.put("mark", "1");
-        info.put("insuredname", quoteVo.getInsuredPersonInfo().getName());
-        info.put("certitype", "01");
-        info.put("certicode", quoteVo.getInsuredPersonInfo().getIdentifyNumber());
-        info.put("age", quoteVo.getInsuredPersonInfo().getBirthday().substring(0, 10));
-        info.put("gender", quoteVo.getInsuredPersonInfo().getGender());
-        info.put("perProWithCarStartDate", "");
-        info.put("perProWithCarEndDate", "");
-        info.put("relation", quoteVo.getOwnersInfo().getName().equals(quoteVo.getInsuredPersonInfo().getName()) ? "00" : "99");
-        info.put("job", "");
-        info.put("parentcode", "");
-        info.put("parentname", "");
-        info.put("parametercode", "");
-        info.put("parametername", "");
-        info.put("codecode", "");
-        info.put("codecname", "");
-        info.put("risktype", "");
-        info.put("jobtype", "");
-        info.put("customerno", "");
-        info.put("workname", "");
-        info.put("workphone", "");
-        info.put("yearmoney", "");
-        info.put("workaddress", "");
-        info.put("otherphone", "");
-        info.put("otheraddress", "");
-        info.put("mobile", "");
-        info.put("urgentname", "");
-        info.put("urgentmobile", "");
-        info.put("nationality", "CHN");
-        return info;
-    }
-
-    private Map<String, Object> buildAccidentalDriving(QuoteVo quoteVo, JyxConfigVo configVo,
-                                                       jyxSetting setting, List<Map<String, Object>> perinsuredList) {
-        Map<String, Object> driving = new HashMap<>();
-        driving.put("butouflag", "");
-        driving.put("amount", setting != null && setting.getCoverage() != null ?
-                setting.getCoverage().toString().replace(".00", "") : "");
-        driving.put("car_limitloadperson", StringUtils.isBlank(String.valueOf(setting.getSeats())) ? "" : setting.getSeats());
-        driving.put("isInsuredPassenger", "");
-        driving.put("productcode", configVo != null ? configVo.getParentProductCode() : "");
-        driving.put("productname", "");
-        driving.put("mealcode", configVo != null ? configVo.getProductCode() : "");
-        driving.put("mealname", "");
-        driving.put("startdate", quoteVo.getCiRiskInfoVo().getStartDate());
-        driving.put("enddate", quoteVo.getCiRiskInfoVo().getEndDate());
-        driving.put("num", "1");
-        driving.put("premium", setting != null && setting.getPremium() != null ?
-                setting.getPremium().toString().replace(".00", ".0") : "");
-        driving.put("formationJudiciary", "");
-        driving.put("argueJudiciary", "");
-        driving.put("newCarSalesProvince", "");
-        driving.put("newCarSalesCity", "");
-        driving.put("newCarSalesDistrict", "");
-        driving.put("buildYear", "");
-        driving.put("xiangxiAddress", "");
-        driving.put("postalCode", "");
-        driving.put("bulidTypeCode", "");
-        driving.put("bulidTypeQiTaMes", "");
-        driving.put("provinceName", "");
-        driving.put("cityName", "");
-        driving.put("regionName", "");
-        driving.put("isInsuredSizesFlag", "0");
-        driving.put("perinsuredList", perinsuredList);
-        driving.put("car_limitloadperson", quoteVo.getCarInfoVo().getSeatCount());
-        return driving;
-    }
-
-    private void handleQuoteErrors(String decryptData, QuoteVo quoteVo, String mealid,
-                                   HttpHeaders headers2, LoginDTO loginDTO) {
-        boolean hasError = decryptData.contains("erroeInfo") || decryptData.contains("errrorFlag");
-        if (!hasError) {
-            return;
+        JyxConfigVo lastCfg = null;
+        jyxSetting lastSet = null;
+
+        for (AccidentalDrivingVo d : q.getAccidentalDrivings()) {
+            JyxConfigVo cfg = platformClient.jyxConfigGetById(d.getId()).getData();
+            lastCfg = cfg;
+            int seats = Integer.parseInt(q.getCarInfoVo().getSeatCount());
+            jyxSetting set = cfg.getSettingList().stream().filter(s -> s.getSeats() == seats).findFirst().orElse(null);
+            lastSet = set;
+            perinsuredList.add(buildInsuredInfo(q, cfg.getParentProductCode()));
         }
-
-        boolean isDuplicateWithEndDate = decryptData.contains("发生重复投保") && decryptData.contains("终保日期");
-        boolean isCheckCodeError = decryptData.contains("录入的校验码有误");
-
-        if (isDuplicateWithEndDate || isCheckCodeError) {
-            handleDuplicateInsurance(decryptData, quoteVo, mealid, headers2, loginDTO);
-            return;
+        return JSONObject.toJSONString(List.of(buildAccidentalDriving(q, lastCfg, lastSet, perinsuredList)));
+    }
+
+    private Map<String, Object> buildInsuredInfo(QuoteVo q, String productCode) {
+        Map<String, Object> m = new HashMap<>();
+        m.put("productcode", StringUtils.isEmpty(productCode) ? "E007" : productCode);
+        m.put("mark", "1");
+        m.put("insuredname", q.getInsuredPersonInfo().getName());
+        m.put("certitype", "01");
+        m.put("certicode", q.getInsuredPersonInfo().getIdentifyNumber());
+        m.put("age", q.getInsuredPersonInfo().getBirthday().substring(0, 10));
+        m.put("gender", q.getInsuredPersonInfo().getGender());
+        m.put("relation", q.getOwnersInfo().getName().equals(q.getInsuredPersonInfo().getName()) ? "00" : "99");
+        m.put("nationality", "CHN");
+        // 其余字段均为空
+        for (String k : List.of("perProWithCarStartDate", "perProWithCarEndDate", "job", "parentcode", "parentname",
+                "parametercode", "parametername", "codecode", "codecname", "risktype", "jobtype", "customerno",
+                "workname", "workphone", "yearmoney", "workaddress", "otherphone", "otheraddress",
+                "mobile", "urgentname", "urgentmobile")) {
+            m.put(k, "");
         }
-
-        // 其余所有错误直接抛出
-        throwQuoteError(decryptData);
+        return m;
     }
 
-    private void throwQuoteError(String decryptData) {
-        HtCrawlerQuoteResponse.DataErrDTO dataErr = JSONObject.parseObject(decryptData, HtCrawlerQuoteResponse.DataErrDTO.class);
-        String errorInfo = dataErr.getErroeInfo();
-        int index = errorInfo.indexOf(CHINA);
-        String message = index >= 0 ? errorInfo.substring(index) : errorInfo;
-        throw new RuntimeException(ERROR_PREFIX + message);
-    }
+    private Map<String, Object> buildAccidentalDriving(QuoteVo q, JyxConfigVo cfg, jyxSetting set,
+                                                       List<Map<String, Object>> perinsuredList) {
+        Map<String, Object> m = new HashMap<>();
+        for (String k : List.of("butouflag", "isInsuredPassenger", "productname", "mealname", "formationJudiciary",
+                "argueJudiciary", "newCarSalesProvince", "newCarSalesCity", "newCarSalesDistrict", "buildYear", "xiangxiAddress",
+                "postalCode", "bulidTypeCode", "bulidTypeQiTaMes", "provinceName", "cityName", "regionName")) {
+            m.put(k, "");
+        }
+        m.put("amount", set != null && set.getCoverage() != null ? set.getCoverage().toString().replace(".00", "") : "");
+        m.put("car_limitloadperson", q.getCarInfoVo().getSeatCount());
+        m.put("productcode", cfg != null ? cfg.getParentProductCode() : "");
+        m.put("mealcode", cfg != null ? cfg.getProductCode() : "");
+        m.put("startdate", q.getCiRiskInfoVo().getStartDate());
+        m.put("enddate", q.getCiRiskInfoVo().getEndDate());
+        m.put("num", "1");
+        m.put("premium", set != null && set.getPremium() != null ? set.getPremium().toString().replace(".00", ".0") : "");
+        m.put("isInsuredSizesFlag", "0");
+        m.put("perinsuredList", perinsuredList);
+        return m;
+    }
+
+    // ====================== 报价错误处理 ======================
+    private QuoteVo handleQuoteErrors(String decryptData, QuoteVo q) {
+        boolean hasError = decryptData.contains("erroeInfo") || decryptData.contains("errrorFlag");
+        if (!hasError) return q;
 
-    private void handleDuplicateInsurance(String decryptData, QuoteVo quoteVo, String mealid,
-                                          HttpHeaders headers2, LoginDTO loginDTO) {
-        HtCrawlerQuoteResponse.DataErrDTO dataErrDTO = JSONObject.parseObject(decryptData, HtCrawlerQuoteResponse.DataErrDTO.class);
-        Matcher matcher = PATTERN_END_DATE.matcher(decryptData);
+        boolean dup = decryptData.contains("发生重复投保") && decryptData.contains("终保日期");
 
-        if (matcher.find()) {
-            String endDate = matcher.group(1).trim();
-            String startDate = endDate + ":00";
-            quoteVo.getCiRiskInfoVo().setStartDate(startDate);
-            try {
-                LocalDateTime dateTime = LocalDateTime.parse(
-                        startDate.replace(" ", "T"),
-                        DateTimeFormatter.ofPattern(DATE_FORMAT_T));
-                String newStartDate = dateTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
-                String newEndDate = dateTime.plusYears(1).minusDays(1).format(DateTimeFormatter.ofPattern(DATE_FORMAT));
-
-                quoteVo.getCiRiskInfoVo().setStartDate(newStartDate.substring(0, 10) + " 00:00:00");
-                quoteVo.getCiRiskInfoVo().setEndDate(newEndDate.substring(0, 10) + " 23:59:59");
-
-                log.info("发生重复投保,重新设置 起保日期: {}, 终保日期: {}",
-                        quoteVo.getCiRiskInfoVo().getStartDate(),
-                        quoteVo.getCiRiskInfoVo().getEndDate());
-            } catch (Exception e) {
-                log.error("日期解析失败: {}", e.getMessage());
-            }
-            // 递归重新报价 - 注意:这里存在潜在的无限递归风险,建议后续改为循环
-            // 为保持原有逻辑,保留递归调用
+        if (dup) {
+            q = handleDuplicateInsurance(decryptData, q);
+            return q;
         }
+        return q;
     }
 
-    private HtCrawlerQuoteResponse.DataDTO resolveQuoteResponse(QuoteVo quoteVo, String decryptData, String mealid,
-                                                                HttpHeaders headers2, String personalInsuredJSONString) throws Exception {
-        HtCrawlerQuoteResponse.DataDTO quoteResponseData = JSONObject.parseObject(decryptData, HtCrawlerQuoteResponse.DataDTO.class);
-
-        List<HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO> basePartList =
-                quoteResponseData.getBasepartdtolist() != null ? quoteResponseData.getBasepartdtolist().getBasepartdto() : null;
+    private QuoteVo handleDuplicateInsurance(String decryptData, QuoteVo q) {
+        Matcher m = PAT_END_DATE.matcher(decryptData);
+        if (!m.find()) return q;
 
-        if (quoteResponseData.getApplicationdto() == null && basePartList != null) {
-            int size = basePartList.size();
-            if (size >= 1 && size <= 3) {
-                String basePartJSON = buildBasePartJSON(basePartList, size);
-                HtCrawlerQuoteRequest quoteRequest = new HtCrawlerQuoteRequest(quoteVo, mealid, basePartJSON, personalInsuredJSONString);
-                decryptData = htCrawlerRequestComponent.quote(quoteRequest, headers2);
-                log.info("quoteResponseData:{}: {}", size, JSONObject.toJSONString(decryptData));
-                quoteResponseData = JSONObject.parseObject(decryptData, HtCrawlerQuoteResponse.DataDTO.class);
+        String end = m.group(1).trim();
+        String start = end + ":00";
+        q.getCiRiskInfoVo().setStartDate(start);
+        try {
+            LocalDateTime dt = LocalDateTime.parse(start.replace(" ", "T"), DateTimeFormatter.ofPattern(DATE_FMT_T));
+            String newStart = dt.format(DateTimeFormatter.ofPattern(DATE_FMT));
+            String newEnd = dt.plusYears(1).minusDays(1).format(DateTimeFormatter.ofPattern(DATE_FMT));
+            q.getCiRiskInfoVo().setStartDate(newStart.substring(0, 10) + " 00:00:00");
+            q.getCiRiskInfoVo().setEndDate(newEnd.substring(0, 10) + " 23:59:59");
+            log.info("重复投保,重新设置 起保: {}, 终保: {}", q.getCiRiskInfoVo().getStartDate(), q.getCiRiskInfoVo().getEndDate());
+            return q;
+        } catch (Exception e) { log.error("日期解析失败: {}", e.getMessage()); }
+        return q;
+    }
+
+    private HtCrawlerQuoteResponse.DataDTO resolveQuoteResponse(QuoteVo q, String decryptData, String mealid, String limitAmount,
+                                                              HttpHeaders h, String personalInsuredJSON) throws Exception {
+        HtCrawlerQuoteResponse.DataDTO data = JSONObject.parseObject(decryptData, HtCrawlerQuoteResponse.DataDTO.class);
+        if (data.getApplicationdto() != null) return data;
+
+        List<HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO> list =
+                data.getBasepartdtolist() != null ? data.getBasepartdtolist().getBasepartdto() : null;
+        if (list == null || list.isEmpty() || list.size() > 3) return data;
+
+        for (int i = 0; i < MAX_RETRIES; i++) {
+            String basePartJSON = buildBasePartJSON(list);
+            String response = req.quote(new HtCrawlerQuoteRequest(q, mealid, limitAmount, basePartJSON, personalInsuredJSON), h);
+            log.info("resolveQuoteResponse attempt {}/{}: {}", i + 1, MAX_RETRIES, response);
+
+            HtCrawlerQuoteResponse.DataDTO result = JSONObject.parseObject(response, HtCrawlerQuoteResponse.DataDTO.class);
+            if (result != null && result.getApplicationdto() != null) {
+                return result;
+            }
 
-                if (quoteResponseData == null || decryptData.contains("erroeInfo")) {
-                    HtCrawlerQuoteResponse.DataErrDTO dataErr = JSONObject.parseObject(decryptData, HtCrawlerQuoteResponse.DataErrDTO.class);
-                    throw new Exception(ERROR_PREFIX + dataErr.getErroeInfo());
+            // autocheckcode 可能不准确,重新获取 quote 刷新验证码
+            if (response.contains("录入的校验码有误") && i < MAX_RETRIES - 1) {
+                log.warn("autocheckcode 校验失败,重新获取 quote 刷新验证码 (attempt {}/{})", i + 1, MAX_RETRIES);
+                // 必须传空 basePart,接口才会返回新的 basePart 列表(含新 autocheckcode)
+                String fresh = req.quote(new HtCrawlerQuoteRequest(q, mealid, limitAmount, "", personalInsuredJSON), h);
+                result = JSONObject.parseObject(fresh, HtCrawlerQuoteResponse.DataDTO.class);
+                if (result != null && result.getApplicationdto() != null) {
+                    return result;
+                }
+                // 用新的 basePart 列表更新 list,下一轮循环会自动重新 buildBasePartJSON
+                list = result != null && result.getBasepartdtolist() != null
+                        ? result.getBasepartdtolist().getBasepartdto() : null;
+                if (list == null || list.isEmpty() || list.size() > 3) {
+                    return result != null ? result : data;
                 }
+                continue;
             }
-        }
-        return quoteResponseData;
-    }
-
-    private String buildBasePartJSON(List<HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO> basePartList, int size) {
-        StringBuilder sb = new StringBuilder("[");
-        for (int i = 0; i < size; i++) {
-            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO dto = basePartList.get(i);
-            String querrysequenceno = dto.getQuerrysequenceno();
-            String autocheckcode = dto.getAutocheckcode();
-            String riskfalg = i == 0 ? "0" : (i == 1 ? "1" : "2");
-            if (size == 1) {
-                riskfalg = "0".equals(querrysequenceno.substring(0, 1)) ? "0" : "1";
+
+            if (response.contains("erroeInfo")) {
+                throw new Exception(ERR_PREFIX + JSONObject.parseObject(response, HtCrawlerQuoteResponse.DataErrDTO.class).getErroeInfo());
             }
-            if (i > 0) sb.append(",");
-            sb.append(String.format("{\"riskfalg\":\"%s\",\"checktype\":2,\"querysequenceno\":\"%s\",\"checkcode\":\"%s\",\"answer\":\"\"}",
-                    riskfalg, querrysequenceno, autocheckcode));
+            return result != null ? result : data;
+        }
+        return data;
+    }
+
+    private String buildBasePartJSON(List<HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO> list) {
+        int size = list.size();
+        String basePartJSON = "";
+        if (size == 1) {
+            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO basePartDTO = list.get(0);
+            log.info("buildBasePartJSON0: {}, {}", size, JSONObject.toJSONString(basePartDTO));
+            String querrysequenceno = basePartDTO.getQuerrysequenceno();
+            String autocheckcode = basePartDTO.getAutocheckcode();
+            String riskfalg = "0".equals(querrysequenceno.substring(0, 1)) ? "0" : "1";
+            basePartJSON = "[{'riskfalg':'"+riskfalg+"','checktype':2,'querysequenceno':'"+querrysequenceno+"','checkcode':'"+autocheckcode+"','answer':''}]";
+        }
+        if (size == 2) {
+            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO basePartDTO = list.get(0);
+            log.info("buildBasePartJSON0: {}, {}", size, JSONObject.toJSONString(basePartDTO));
+            String querrysequenceno = basePartDTO.getQuerrysequenceno();
+            String autocheckcode = basePartDTO.getAutocheckcode();
+            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO basePartDTO1 = list.get(1);
+            log.info("buildBasePartJSON1: {}, {}", size, JSONObject.toJSONString(basePartDTO1));
+            String querrysequenceno1 = basePartDTO1.getQuerrysequenceno();
+            String autocheckcode1 = basePartDTO1.getAutocheckcode();
+            basePartJSON = "[{'riskfalg':'0','checktype':2,'querysequenceno':'"+querrysequenceno1+"','checkcode':'"+autocheckcode1+"','answer':''}," +
+                    "{'riskfalg':'1','checktype':2,'querysequenceno':'"+querrysequenceno+"','checkcode':'"+autocheckcode+"','answer':''}]";
+        }
+        if (size == 3) {
+            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO basePartDTO = list.get(0);
+            log.info("buildBasePartJSON0: {}, {}", size, JSONObject.toJSONString(basePartDTO));
+            String querrysequenceno = basePartDTO.getQuerrysequenceno();
+            String autocheckcode = basePartDTO.getAutocheckcode();
+            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO basePartDTO1 = list.get(1);
+            log.info("buildBasePartJSON1: {}, {}", size, JSONObject.toJSONString(basePartDTO1));
+            String querrysequenceno1 = basePartDTO1.getQuerrysequenceno();
+            String autocheckcode1 = basePartDTO1.getAutocheckcode();
+            HtCrawlerQuoteResponse.DataDTO.BasePartDTOListDTO.BasePartDTO basePartDTO2 = list.get(2);
+            log.info("buildBasePartJSON2: {}, {}", size, JSONObject.toJSONString(basePartDTO2));
+            String querrysequenceno2 = basePartDTO2.getQuerrysequenceno();
+            String autocheckcode2 = basePartDTO2.getAutocheckcode();
+            basePartJSON = "[{'riskfalg':'0','checktype':2,'querysequenceno':'"+querrysequenceno1+"','checkcode':'"+autocheckcode1+"','answer':''}," +
+                    "{'riskfalg':'1','checktype':2,'querysequenceno':'"+querrysequenceno+"','checkcode':'"+autocheckcode+"','answer':''}," +
+                    "{'riskfalg':'2','checktype':2,'querysequenceno':'"+querrysequenceno2+"','checkcode':'"+autocheckcode2+"','answer':''}]";
         }
-        sb.append("]");
-        return sb.toString();
+        return basePartJSON;
     }
 
     // ====================== 核保 ======================
-
     @Override
-    public UnderwritingResultsVo underwriting(UnderwritingVo underwritingVo) throws JsonProcessingException {
-        AttributionInformationVo attributionInformationVo = underwritingVo.getOrder().getAttributionInformationVo();
-        HtCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(
-                HtCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
-        doLoginCookie(attributionInformationVo);
-
-        HttpHeaders headers = htCrawlerRequestComponent.getHeaders();
-        HtCrawlerOrder order = htCrawlerOrderService.findByOrdersNo(underwritingVo.getOrder().getOrdersNo());
-        underwritingVo.getOrder().setQuoteNo(order.getQuotationRelaNo());
-
-        String ciCategory = order.getCiCategory();
-        String biCategory = order.getBiCategory();
-        HtCrawlerSubmitRequest submitRequest = new HtCrawlerSubmitRequest(underwritingVo, parameters, ciCategory, biCategory);
-        UnderwritingResultsVo result = new UnderwritingResultsVo();
-
-        String res = htCrawlerRequestComponent.submit2(submitRequest, headers);
-        Map<String, Object> map = OBJECT_MAPPER.readValue(res, Map.class);
-
-        // 评分不达标或其他错误
-        if (isUnderwritingFailed(ciCategory, map, res)) {
-            result.setUnderwritingStatus(InsOrderStatusEnum.WAIT_AUDIT);
-            throw new SystemException("核保失败" + "-------" + map.get("MSG"));
-        }
-        if (map.toString().contains("起保时间不正确")) {
-            throw new SystemException("起保时间不正确-------即时起保分钟数必须为00/15/30/45\n请重新选择起保时间!");
-        }
+    public UnderwritingResultsVo underwriting(UnderwritingVo vo) throws JsonProcessingException {
+        AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
+        HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
+                HtCrawlerConfigureParameters.class, attr.getConfigInfo());
+        doLoginCookie(attr);
 
-        if (map.toString().contains("需转人工核保")) {
-            return handleManualUnderwriting(order, parameters, result);
-        }
+        HtCrawlerOrder order = orderService.findByOrdersNo(vo.getOrder().getOrdersNo());
+        vo.getOrder().setQuoteNo(order.getQuotationRelaNo());
+
+        HtCrawlerSubmitRequest submit = new HtCrawlerSubmitRequest(vo, params, order.getCiCategory(), order.getBiCategory());
+        String res = req.submit2(submit, req.getHeaders());
+        Map<String, Object> map = MAPPER.readValue(res, Map.class);
 
-        if (res.contains("已通过核保未实收") || isPassHB(map)) {
-            return handlePassedUnderwriting(order, parameters, result, underwritingVo);
+        if (isUnderwritingFailed(order.getCiCategory(), map, res)) {
+            throw new SystemException("核保失败-------" + map.get("MSG"));
         }
+        if (res.contains("起保时间不正确")) {
+            throw new SystemException("起保时间不正确-------即时起保分钟数必须为00/15/30/45\n请重新选择起保时间!");
+        }
+        if (res.contains("需转人工核保")) return handleManualUnderwriting(order, params);
+        if (res.contains("已通过核保未实收") || isPassHB(map)) return handlePassedUnderwriting(order, params, vo);
 
-        result.setUnderwritingStatus(InsOrderStatusEnum.WAIT_AUDIT);
-        return result;
+        UnderwritingResultsVo r = new UnderwritingResultsVo();
+        r.setUnderwritingStatus(InsOrderStatusEnum.WAIT_AUDIT);
+        return r;
     }
 
-    private boolean isUnderwritingFailed(String ciCategory, Map<String, Object> map, String res) {
-        return !"1,2,3,4".contains(ciCategory)
-                || (map.get("isOver") != null && "Y".equals(map.get("isOver").toString()))
-                || (map.get("MSG") != null && "重新计算保费".equals(map.get("MSG").toString()))
+    private boolean isUnderwritingFailed(String ci, Map<String, Object> m, String res) {
+        return !"1,2,3,4".contains(ci)
+                || "Y".equals(str(m, "isOver"))
+                || "重新计算保费".equals(str(m, "MSG"))
                 || res.contains("强制下发修改意见");
     }
 
-    private boolean isPassHB(Map<String, Object> map) {
-        return map.get("isPassHB") != null && "Y".equals(map.get("isPassHB").toString());
-    }
+    private boolean isPassHB(Map<String, Object> m) { return "Y".equals(str(m, "isPassHB")); }
 
-    private UnderwritingResultsVo handleManualUnderwriting(HtCrawlerOrder order, HtCrawlerConfigureParameters parameters,
-                                                           UnderwritingResultsVo result) {
+    private UnderwritingResultsVo handleManualUnderwriting(HtCrawlerOrder order, HtCrawlerConfigureParameters params) {
         try {
-            HttpHeaders headers = buildManualUnderwritingHeaders();
-            String url = BASE_URL + "/htcsp/checkInsAction.do";
-            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
-            params.add("cmd", "submitZRG");
+            // 提交人工核保
             MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
-            data.add("zrgms", "");
-            data.add("zrgpriority", "1");
-
-            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(data, headers);
-            ResponseEntity<String> response = CookieUtils.getRestTemplate().exchange(
-                    UriComponentsBuilder.fromHttpUrl(url).queryParams(params).toUriString(),
-                    HttpMethod.POST, request, String.class);
-
-            Map<String, Object> body = OBJECT_MAPPER.readValue(response.getBody(), Map.class);
-            result.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
-            result.setCompanyOrderNo(body.get("quotationno") != null ? body.get("quotationno").toString() : "");
-            result.setJqApplyNo(body.get("proposal_no_jq") != null ? body.get("proposal_no_jq").toString() : "");
-            result.setSyApplyNo(body.get("proposal_no_sy") != null ? body.get("proposal_no_sy").toString() : "");
-
-            // 获取支付链接
-            headers = buildPaymentLinkHeaders();
-            url = BASE_URL + "/htcsp/checkInsAction.do?cmd=queryUnderwriterAdvice";
-            MultiValueMap<String, String> data2 = new LinkedMultiValueMap<>();
-            data2.add("businessNo", order.getQuotationRelaNo());
-            data2.add("excet", "");
-            request = new HttpEntity<>(data2, headers);
-            ResponseEntity<String> response2 = CookieUtils.getRestTemplate().exchange(
-                    UriComponentsBuilder.fromHttpUrl(url).toUriString(),
-                    HttpMethod.POST, request, String.class);
-
-            if (response2.getBody().contains("审核通过")) {
-                HtCrawlerPaymentLinkRequest paymentLinkRequest = new HtCrawlerPaymentLinkRequest(parameters, new PaymentLinkVo(), response2.getBody());
-                HtCrawlerPaymentLinkResponse paymentLinkResponse = htCrawlerRequestComponent.getPaymentLink(paymentLinkRequest, headers);
-                result.setPaymentLink(paymentLinkResponse.getImg());
-                result.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
-                return result;
-            } else {
-                result.setUnderwritingStatus(InsOrderStatusEnum.WAIT_AUDIT);
-                String body2 = response2.getBody();
-                int start = body2.indexOf("原因");
-                int end = body2.lastIndexOf("。");
-                String reason = (start >= 0 && end > start) ? body2.substring(start, end) : "审核未通过";
-                throw new SystemException("审核未通过:" + reason);
-            }
-        } catch (Exception e) {
-            result.setUnderwritingStatus(InsOrderStatusEnum.WAIT_AUDIT);
-            if (e instanceof SystemException) {
-                throw (SystemException) e;
+            data.add("zrgms", ""); data.add("zrgpriority", "1");
+            String url = BASE_URL + "/htcsp/checkInsAction.do";
+            Map<String, Object> body = exchangeForm(url, Map.of("cmd", "submitZRG"), data, buildManualUwHeaders());
+
+            UnderwritingResultsVo r = new UnderwritingResultsVo();
+            r.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
+            r.setCompanyOrderNo(str(body, "quotationno"));
+            r.setJqApplyNo(str(body, "proposal_no_jq"));
+            r.setSyApplyNo(str(body, "proposal_no_sy"));
+
+            // 查询支付链接
+            String url2 = BASE_URL + "/htcsp/checkInsAction.do?cmd=queryUnderwriterAdvice";
+            MultiValueMap<String, String> d2 = new LinkedMultiValueMap<>();
+            d2.add("businessNo", order.getQuotationRelaNo()); d2.add("excet", "");
+            String html = String.valueOf(exchangeForm(url2, null, d2, buildPaymentLinkHeaders()));
+
+            if (html.contains("审核通过")) {
+                HtCrawlerPaymentLinkResponse pl = req.getPaymentLink(
+                        new HtCrawlerPaymentLinkRequest(params, new PaymentLinkVo(), html), buildPaymentLinkHeaders());
+                r.setPaymentLink(pl.getImg());
+                r.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
+                return r;
             }
+            int s = html.indexOf("原因"), e = html.lastIndexOf("。");
+            throw new SystemException("审核未通过:" + (s >= 0 && e > s ? html.substring(s, e) : "审核未通过"));
+        } catch (SystemException se) { throw se; }
+        catch (Exception e) {
             log.error("人工核保处理异常", e);
-            return result;
+            UnderwritingResultsVo r = new UnderwritingResultsVo();
+            r.setUnderwritingStatus(InsOrderStatusEnum.WAIT_AUDIT);
+            return r;
         }
     }
 
-    private UnderwritingResultsVo handlePassedUnderwriting(HtCrawlerOrder order, HtCrawlerConfigureParameters parameters,
-                                                           UnderwritingResultsVo result, UnderwritingVo underwritingVo) {
-        HtCrawlerPaymentLinkResponse paymentLinkResponse = queryPaymentLink(order, parameters, new PaymentLinkVo());
-        log.info("paymentLinkResponse: {}", JSONObject.toJSONString(paymentLinkResponse));
-
-        result.setPaymentLink(paymentLinkResponse.getImg());
-        underwritingVo.getInsOrders().setPayLink(paymentLinkResponse.getImg());
-        underwritingVo.getInsOrders().setAuditTime(LocalDateTime.now());
-        underwritingVo.getInsOrders().setCompanyOrderNo(order.getQuotationRelaNo());
-        result.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
-        return result;
+    private UnderwritingResultsVo handlePassedUnderwriting(HtCrawlerOrder order, HtCrawlerConfigureParameters params, UnderwritingVo vo) {
+        HtCrawlerPaymentLinkResponse pl = queryPaymentLink(order, params, new PaymentLinkVo());
+        log.info("paymentLinkResponse: {}", JSONObject.toJSONString(pl));
+        vo.getInsOrders().setPayLink(pl.getImg());
+        vo.getInsOrders().setAuditTime(LocalDateTime.now());
+        vo.getInsOrders().setCompanyOrderNo(order.getQuotationRelaNo());
+        UnderwritingResultsVo r = new UnderwritingResultsVo();
+        r.setPaymentLink(pl.getImg());
+        r.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
+        return r;
     }
 
     // ====================== 支付链接 ======================
-
     @Override
-    public PaymentLinkResultsVo getPaymentLink(PaymentLinkVo paymentLinkVo) {
-        AttributionInformationVo attributionInformationVo = paymentLinkVo.getOrder().getAttributionInformationVo();
-        doLoginCookie(attributionInformationVo);
-        HtCrawlerOrder order = htCrawlerOrderService.findByOrdersNo(paymentLinkVo.getOrder().getOrdersNo());
-
-        HtCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(
-                HtCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
-
-        HtCrawlerPaymentLinkResponse paymentLinkResponse = queryPaymentLink(order, parameters, paymentLinkVo);
-        log.info("paymentLinkResponse: {}", JSONObject.toJSONString(paymentLinkResponse));
-        return new PaymentLinkResultsVo(true, paymentLinkResponse.getImg(), paymentLinkResponse.getErrorInfo());
-    }
-
-    private HtCrawlerPaymentLinkResponse queryPaymentLink(HtCrawlerOrder order,
-                                                          HtCrawlerConfigureParameters parameters,
-                                                          PaymentLinkVo paymentLinkVo) {
-        HttpHeaders headers = buildPaymentListHeaders(htCrawlerRequestComponent.getCookie());
-        String url = BASE_URL + "/htcsp/PaymentAction.do";
-
-        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
-        params.add("cmd", "queryPaymentList");
-        params.add("paytype", "jianfei");
-        params.add("Single_system", "10000");
-        params.add("checkStatus", "1");
-        params.add("quotationno", order.getQuotationRelaNo());
-
-        HttpEntity<Void> request = new HttpEntity<>(null, headers);
-        ResponseEntity<String> response = CookieUtils.getRestTemplate().exchange(
-                UriComponentsBuilder.fromHttpUrl(url).queryParams(params).toUriString(),
-                HttpMethod.GET, request, String.class);
-
-        String responseBody = response.getBody();
-        log.info("queryPaymentList response: {}", responseBody);
-        HtCrawlerPaymentLinkRequest paymentLinkRequest = new HtCrawlerPaymentLinkRequest(parameters, paymentLinkVo, responseBody);
-        return htCrawlerRequestComponent.getPaymentLink(paymentLinkRequest, headers);
-    }
-
-    // ====================== 登录Cookie检查 ======================
-
-    private void doLoginCookie(AttributionInformationVo attributionInformationVo) {
-        if (StringUtils.isBlank(htCrawlerRequestComponent.getCookie())) {
-            LoginDTO loginDTO = buildLoginDTO(attributionInformationVo);
-            Map<String, Object> mmap = login(loginDTO);
-            String cookie = (String) mmap.get("cookie");
-            htCrawlerRequestComponent.getHeaders().remove(HtRequestConstant.COOKIE);
-            htCrawlerRequestComponent.getHeaders().add(HtRequestConstant.COOKIE, cookie);
+    public PaymentLinkResultsVo getPaymentLink(PaymentLinkVo vo) {
+        AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
+        doLoginCookie(attr);
+        HtCrawlerOrder order = orderService.findByOrdersNo(vo.getOrder().getOrdersNo());
+        HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
+                HtCrawlerConfigureParameters.class, attr.getConfigInfo());
+        HtCrawlerPaymentLinkResponse pl = queryPaymentLink(order, params, vo);
+        log.info("paymentLinkResponse: {}", JSONObject.toJSONString(pl));
+        return new PaymentLinkResultsVo(true, pl.getImg(), pl.getErrorInfo());
+    }
+
+    private HtCrawlerPaymentLinkResponse queryPaymentLink(HtCrawlerOrder order, HtCrawlerConfigureParameters params, PaymentLinkVo vo) {
+        MultiValueMap<String, String> qp = new LinkedMultiValueMap<>();
+        qp.add("cmd", "queryPaymentList");
+        qp.add("paytype", "jianfei");
+        qp.add("Single_system", "10000");
+        qp.add("checkStatus", "1");
+        qp.add("quotationno", order.getQuotationRelaNo());
+        String body = get(BASE_URL + "/htcsp/PaymentAction.do", buildPaymentListHeaders(req.getCookie()), qp);
+        log.info("queryPaymentList response: {}", body);
+        return req.getPaymentLink(new HtCrawlerPaymentLinkRequest(params, vo, body), buildPaymentListHeaders(req.getCookie()));
+    }
+
+    // ====================== Cookie 检查 ======================
+    private void doLoginCookie(AttributionInformationVo attr) {
+        if (StringUtils.isBlank(req.getCookie())) {
+            Map<String, Object> m = login(buildLoginDTO(attr));
+            String cookie = (String) m.get("cookie");
+            req.getHeaders().remove(HtRequestConstant.COOKIE);
+            req.getHeaders().add(HtRequestConstant.COOKIE, cookie);
         }
     }
 
     // ====================== 影像上传 ======================
-
     @Override
-    public void uploadImage(InsuranceUploadImageVo insuranceUploadImageVo) {
-        OrderBase orderBase = insuranceUploadImageVo.getOrder();
-        AttributionInformationVo attributionInformationVo = orderBase.getAttributionInformationVo();
-        doLoginCookie(attributionInformationVo);
-        HtCrawlerOrder order = htCrawlerOrderService.findByOrdersNo(orderBase.getOrdersNo());
-
-        // 判断是否重复上传
-        String uploaded = huaTaiCrawlerCacheComponent.getYingXiangUploaded();
-        if (StringUtils.isNotBlank(uploaded) && uploaded.equals(order.getQuotationRelaNo())) {
-            return;
-        }
+    public void uploadImage(InsuranceUploadImageVo vo) {
+        OrderBase orderBase = vo.getOrder();
+        AttributionInformationVo attr = orderBase.getAttributionInformationVo();
+        doLoginCookie(attr);
+        HtCrawlerOrder order = orderService.findByOrdersNo(orderBase.getOrdersNo());
 
-        HtCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(
-                HtCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
-        HttpHeaders headers = buildUploadHeaders(htCrawlerRequestComponent.getCookie());
+        String uploaded = cache.getYingXiangUploaded();
+        if (StringUtils.isNotBlank(uploaded) && uploaded.equals(order.getQuotationRelaNo())) return;
+
+        HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
+                HtCrawlerConfigureParameters.class, attr.getConfigInfo());
         String quotationRelaNo = order.getQuotationRelaNo();
 
-        List<Map<String, String>> b64phoList = new ArrayList<>();
-        for (InsuranceImageVo insuranceImageVo : insuranceUploadImageVo.getImageVoList()) {
-            for (InsuranceImageVo.ImageDTO imageDTO : insuranceImageVo.getImageBase64List()) {
-                b64phoList.add(Map.of("imageBase64", imageDTO.getImageBase64Str(), "imgType", insuranceImageVo.getImageCode()));
+        List<Map<String, String>> b64List = new ArrayList<>();
+        for (InsuranceImageVo img : vo.getImageVoList()) {
+            for (InsuranceImageVo.ImageDTO dto : img.getImageBase64List()) {
+                b64List.add(Map.of("imageBase64", dto.getImageBase64Str(), "imgType", img.getImageCode()));
             }
         }
 
-        HtCrawlerGerUploadUrlRequest uploadUrlRequest = new HtCrawlerGerUploadUrlRequest(quotationRelaNo);
-        HtCrawlerGerUploadUrlResponse uploadUrlResponse = htCrawlerRequestComponent.getUploadUrl(uploadUrlRequest, headers);
+        HtCrawlerGerUploadUrlResponse up = req.getUploadUrl(new HtCrawlerGerUploadUrlRequest(quotationRelaNo), buildUploadHeaders(req.getCookie()));
         String token = "";
-        Map<String, Object> updataInfo = new LinkedHashMap<>();
-
-        if (HtRequestConstant.GET_UPLOAD_URL_SUCCESS.equals(uploadUrlResponse.getFlag())) {
-            token = uploadUrlResponse.getUrl().substring(uploadUrlResponse.getUrl().lastIndexOf("=") + 1);
-            updataInfo = fetchUploadInitParam(token);
+        Map<String, Object> updata = new LinkedHashMap<>();
+        if (HtRequestConstant.GET_UPLOAD_URL_SUCCESS.equals(up.getFlag())) {
+            token = up.getUrl().substring(up.getUrl().lastIndexOf("=") + 1);
+            updata = fetchUploadInitParam(token);
         }
 
         try {
-            new UploadUtil().upLoad(quotationRelaNo, b64phoList, parseToMap(htCrawlerRequestComponent.getGsInfo()),
-                    attributionInformationVo.getUsername(), parameters.getComCode(),
-                    convertCookieToMap(htCrawlerRequestComponent.getCookie()),
-                    quotationRelaNo + "_", token, updataInfo);
-            huaTaiCrawlerCacheComponent.cacheYingXiangUploaded(quotationRelaNo);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+            new UploadUtil().upLoad(quotationRelaNo, b64List, parseToMap(req.getGsInfo()),
+                    attr.getUsername(), params.getComCode(), convertCookieToMap(req.getCookie()),
+                    quotationRelaNo + "_", token, updata);
+            cache.cacheYingXiangUploaded(quotationRelaNo);
+        } catch (Exception e) { throw new RuntimeException(e); }
     }
 
     private Map fetchUploadInitParam(String token) {
-        HttpHeaders imageHeaders = new HttpHeaders();
-        imageHeaders.set(HtRequestConstant.COOKIE, htCrawlerRequestComponent.getCookie());
-        imageHeaders.set("Accept", "application/json, text/plain, */*");
-        imageHeaders.set("Referer", "https://img-web.pc.ehuatai.com/app/upload.img?token=" + token);
-        imageHeaders.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36");
-
+        HttpHeaders h = new HttpHeaders();
+        h.set(HtRequestConstant.COOKIE, req.getCookie());
+        h.set("Accept", "application/json, text/plain, */*");
+        h.set("Referer", "https://img-web.pc.ehuatai.com/app/upload.img?token=" + token);
+        h.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36");
         String url = "https://img-web.pc.ehuatai.com/app/ImgManager/initParam.cdo";
-        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
-                .queryParam("t", String.valueOf(System.currentTimeMillis()))
-                .queryParam("token", token);
-
-        return getRestTemplate().exchange(
-                builder.toUriString(),
-                HttpMethod.GET,
-                new HttpEntity<>(imageHeaders),
-                Map.class
-        ).getBody();
+        String s = get(url + "?t=" + System.currentTimeMillis() + "&token=" + token, h, null);
+        Map<String, Object> map = null;
+        try {
+            map = new ObjectMapper().readValue(s, Map.class);
+        } catch (JsonProcessingException e) {
+            log.error("json parse error: {}", e.getMessage());
+        }
+        return map;
     }
 
-    @Override
-    public void queryClauseData(ClauseDataVo clauseDataVo) {
-        // 空实现
-    }
+    @Override public void queryClauseData(ClauseDataVo vo) { }
 
     // ====================== 下载保单 ======================
-
     @Override
-    public DownloadPolicyResultVo downloadPolicy(DownloadPolicyVo downloadPolicyVo) {
-        AttributionInformationVo attributionInformationVo = downloadPolicyVo.getOrder().getAttributionInformationVo();
-        doLoginCookie(attributionInformationVo);
-        String productId = downloadPolicyVo.getOrder().getProductId();
-        String companyOrderNo = downloadPolicyVo.getOrder().getCompanyOrderNo();
-
-        if (ProductsType.PT_0330.getCode().equals(productId)) {
-            return downloadJqPolicy(companyOrderNo);
+    public DownloadPolicyResultVo downloadPolicy(DownloadPolicyVo vo) {
+        AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
+        doLoginCookie(attr);
+        String productId = vo.getOrder().getProductId();
+        String companyOrderNo = vo.getOrder().getCompanyOrderNo();
+        return ProductsType.PT_0330.getCode().equals(productId)
+                ? downloadPolicy(companyOrderNo, true) : downloadPolicy(companyOrderNo, false);
+    }
+
+    private DownloadPolicyResultVo downloadPolicy(String no, boolean jq) {
+        MultiValueMap<String, String> fd = new LinkedMultiValueMap<>();
+        fd.add("businessno", no);
+        fd.add("riskcode", jq ? "1002" : "1251");
+        fd.add("isbzdownload", "0");
+        fd.add("biztype", "P");
+        fd.add("elecProposalNoticeFlag", "");
+        fd.add("eleSymbolDownload", "1");
+        fd.add("tmpbiztype", "P");
+        fd.add("ownPrintElectronicFlag", "0");
+        fd.add("isbzdown", "1");
+        String body = post(BASE_URL + "/htcsp/DocumentPrintAction.do?cmd=electronicPolicyEndorseDownload",
+                buildDocumentHeaders(req.getCookie()), fd);
+        HtCrawlerDownloadPolicyResponse res = JSONObject.parseObject(body, HtCrawlerDownloadPolicyResponse.class);
+        DownloadPolicyResultVo r = new DownloadPolicyResultVo();
+        for (var ftp : res.getFtpdownloadJson()) {
+            if (jq) { r.setJqUrl(ftp.getDownurl()); r.setJqFlagUrl(ftp.getBzdownurl()); }
+            else { r.setSyUrl(ftp.getDownurl()); }
+            log.info("下载{}保单地址:{}", jq ? "交强" : "商业", ftp.getDownurl());
         }
-        return downloadSyPolicy(companyOrderNo);
-    }
-
-    private DownloadPolicyResultVo downloadJqPolicy(String companyOrderNo) {
-        HttpHeaders headers = buildDocumentHeaders(htCrawlerRequestComponent.getCookie());
-        MultiValueMap<String, String> formData = buildPolicyFormData(companyOrderNo, "1002");
-        return executePolicyDownload(headers, formData, true);
-    }
-
-    private DownloadPolicyResultVo downloadSyPolicy(String companyOrderNo) {
-        HttpHeaders headers = buildDocumentHeaders(htCrawlerRequestComponent.getCookie());
-        MultiValueMap<String, String> formData = buildPolicyFormData(companyOrderNo, "1251");
-        formData.add("elecProposalNoticeFlag", "");
-        return executePolicyDownload(headers, formData, false);
-    }
-
-    private MultiValueMap<String, String> buildPolicyFormData(String companyOrderNo, String riskCode) {
-        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-        formData.add("businessno", companyOrderNo);
-        formData.add("riskcode", riskCode);
-        formData.add("isbzdownload", "0");
-        formData.add("biztype", "P");
-        formData.add("eleSymbolDownload", "1");
-        formData.add("tmpbiztype", "P");
-        formData.add("ownPrintElectronicFlag", "0");
-        formData.add("isbzdown", "1");
-        return formData;
-    }
-
-    private DownloadPolicyResultVo executePolicyDownload(HttpHeaders headers, MultiValueMap<String, String> formData, boolean isJq) {
-        String url = BASE_URL + "/htcsp/DocumentPrintAction.do?cmd=electronicPolicyEndorseDownload";
-        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(formData, headers);
-        ResponseEntity<String> response = getRestTemplate().exchange(url, HttpMethod.POST, request, String.class);
-        String result = response.getBody();
-        HtCrawlerDownloadPolicyResponse policyResponse = JSONObject.parseObject(result, HtCrawlerDownloadPolicyResponse.class);
-
-        DownloadPolicyResultVo downloadPolicyResultVo = new DownloadPolicyResultVo();
-        for (HtCrawlerDownloadPolicyResponse.FtpdownloadJson ftpdownloadJson : policyResponse.getFtpdownloadJson()) {
-            if (isJq) {
-                log.info("下载交强保单地址:{}", ftpdownloadJson.getDownurl());
-                downloadPolicyResultVo.setJqUrl(ftpdownloadJson.getDownurl());
-                downloadPolicyResultVo.setJqFlagUrl(ftpdownloadJson.getBzdownurl());
-            } else {
-                log.info("下载商业保单地址:{}", ftpdownloadJson.getDownurl());
-                downloadPolicyResultVo.setSyUrl(ftpdownloadJson.getDownurl());
-            }
-        }
-        return downloadPolicyResultVo;
+        return r;
     }
 
     // ====================== 查询订单状态 ======================
-
     @Override
-    public OrderStatusResultVo queryOrderState(OrderStatusVo orderStatusVo) {
-        AttributionInformationVo attributionInformationVo = orderStatusVo.getOrder().getAttributionInformationVo();
-        HtCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(
-                HtCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
-        HttpHeaders headers = htCrawlerRequestComponent.getHeaders();
-
-        HtCrawlerOrder order = htCrawlerOrderService.findByOrdersNo(orderStatusVo.getOrder().getOrdersNo());
-        String quotationRelaNo = order.getQuotationRelaNo();
-
-        HtCrawlerOrderStateRequest stateRequest = new HtCrawlerOrderStateRequest(parameters, quotationRelaNo);
-        String html = htCrawlerRequestComponent.queryOrderStatus(stateRequest, headers);
-
-        Document document = Jsoup.parse(html);
-        Elements trElement = document.select("table[id='table']").select("tbody").select("tr");
-        Elements tdElements = trElement.select("td");
-
-        // TODO: 完善字段映射并返回非空结果
-        tdElements.get(1).firstElementChild().text(); // 客户名称
-        tdElements.get(2).firstElementChild().text(); // 保险起保日期
-        tdElements.get(3).firstElementChild().text(); // 车牌号
-        tdElements.get(4).firstElementChild().text(); // 报价单创建日期
-        tdElements.get(7).firstElementChild().text(); // 订单状态
+    public OrderStatusResultVo queryOrderState(OrderStatusVo vo) {
+        AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
+        HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
+                HtCrawlerConfigureParameters.class, attr.getConfigInfo());
+        HtCrawlerOrder order = orderService.findByOrdersNo(vo.getOrder().getOrdersNo());
+        String html = req.queryOrderStatus(new HtCrawlerOrderStateRequest(params, order.getQuotationRelaNo()), req.getHeaders());
+        Document doc = Jsoup.parse(html);
+        Elements tds = doc.select("table[id='table'] tbody tr td");
+        // TODO: 完善字段映射
+        tds.get(1).firstElementChild().text(); // 客户名称
+        tds.get(2).firstElementChild().text(); // 保险起保日期
+        tds.get(3).firstElementChild().text(); // 车牌号
+        tds.get(4).firstElementChild().text(); // 报价单创建日期
+        tds.get(7).firstElementChild().text(); // 订单状态
         return null;
     }
 
-    // ====================== Headers 工厂方法 ======================
+    // ====================== Headers 统一构建 ======================
+    private HttpHeaders header(Consumer<HttpHeaders> customizer) {
+        HttpHeaders h = new HttpHeaders();
+        h.add("User-Agent", UA);
+        h.add("Accept-Language", "zh-CN,zh;q=0.9");
+        h.add("Connection", "keep-alive");
+        customizer.accept(h);
+        return h;
+    }
 
-    private HttpHeaders buildHtmlHeadersWithCookie(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("User-Agent", USER_AGENT);
-        headers.set("Accept", ACCEPT_HTML);
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Pragma", "no-cache");
-        headers.set("Upgrade-Insecure-Requests", "1");
-        headers.set("Cookie", cookie);
-        return headers;
+    private HttpHeaders buildHtmlHeaders(String cookie) {
+        return header(h -> {
+            h.set("Accept", ACCEPT_HTML);
+            h.set("Cache-Control", "no-cache");
+            h.set("Pragma", "no-cache");
+            h.set("Upgrade-Insecure-Requests", "1");
+            h.set("Cookie", cookie);
+        });
     }
 
-    private HttpHeaders buildJsonHeadersWithCookie(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("Accept", ACCEPT_JSON);
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Connection", "keep-alive");
-        headers.set("Content-Type", CONTENT_TYPE_FORM);
-        headers.set("Origin", ORIGIN);
-        headers.set("Referer", REFERER_START_PROCESS);
-        headers.set("User-Agent", USER_AGENT);
-        headers.set("X-Requested-With", X_REQUESTED_WITH);
-        headers.set("Cookie", cookie);
-        return headers;
+    private HttpHeaders buildJsonHeaders(String cookie) {
+        return header(h -> {
+            h.set("Accept", ACCEPT_JSON);
+            h.set("Content-Type", CONTENT_TYPE_FORM);
+            h.set("Origin", ORIGIN);
+            h.set("Referer", ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=startProcess");
+            h.set("X-Requested-With", XHR);
+            h.set("Cookie", cookie);
+        });
     }
 
     private HttpHeaders buildQuoteStep1Headers(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, cookie);
-        headers.add("Accept", ACCEPT_HTML);
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.add("Cache-Control", "no-cache");
-        headers.add("Connection", "keep-alive");
-        headers.add("Content-Type", CONTENT_TYPE_FORM_SIMPLE);
-        headers.add("Origin", ORIGIN);
-        headers.add("Pragma", "no-cache");
-        headers.add("Referer", REFERER_START_PROCESS);
-        headers.add("Upgrade-Insecure-Requests", "1");
-        headers.add("User-Agent", USER_AGENT);
-        return headers;
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, cookie);
+            h.add("Accept", ACCEPT_HTML);
+            h.add("Cache-Control", "no-cache");
+            h.add("Content-Type", "application/x-www-form-urlencoded");
+            h.add("Origin", ORIGIN);
+            h.add("Pragma", "no-cache");
+            h.add("Referer", ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=startProcess");
+            h.add("Upgrade-Insecure-Requests", "1");
+        });
     }
 
     private HttpHeaders buildQuoteStep2Headers(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, cookie);
-        headers.add("Accept", ACCEPT_ALL);
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.add("Cache-Control", "no-cache");
-        headers.add("Connection", "keep-alive");
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("Origin", ORIGIN);
-        headers.add("Pragma", "no-cache");
-        headers.add("Referer", REFERER_SAVE_ONE_STEP);
-        headers.add("User-Agent", USER_AGENT);
-        headers.add("X-Requested-With", X_REQUESTED_WITH);
-        return headers;
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, cookie);
+            h.add("Accept", "*/*");
+            h.add("Cache-Control", "no-cache");
+            h.add("Content-Type", CONTENT_TYPE_FORM);
+            h.add("Origin", ORIGIN);
+            h.add("Pragma", "no-cache");
+            h.add("Referer", ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=saveOneStept");
+            h.add("X-Requested-With", XHR);
+        });
     }
 
     private HttpHeaders buildPaymentListHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, cookie);
-        headers.add("Accept", ACCEPT_HTML);
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.add("Connection", "keep-alive");
-        headers.add("Referer", REFERER_QUOTATION_LIST);
-        headers.add("Upgrade-Insecure-Requests", "1");
-        headers.add("User-Agent", USER_AGENT);
-        return headers;
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, cookie);
+            h.add("Accept", ACCEPT_HTML);
+            h.add("Referer", ORIGIN + "/htcsp/QuotationQueryAction.do?cmd=quotationListAll");
+            h.add("Upgrade-Insecure-Requests", "1");
+        });
     }
 
     private HttpHeaders buildDocumentHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set(HtRequestConstant.COOKIE, cookie);
-        headers.set("Accept", ACCEPT_JSON);
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-        headers.set("Origin", ORIGIN);
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", REFERER_DOCUMENT_PRINT);
-        headers.set("User-Agent", USER_AGENT);
-        headers.set("X-Requested-With", X_REQUESTED_WITH);
-        return headers;
+        return header(h -> {
+            h.set(HtRequestConstant.COOKIE, cookie);
+            h.set("Accept", ACCEPT_JSON);
+            h.set("Cache-Control", "no-cache");
+            h.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+            h.set("Origin", ORIGIN);
+            h.set("Pragma", "no-cache");
+            h.set("Referer", ORIGIN + "/htcsp/DocumentPrintAction.do?cmd=queryDocumentPrintList");
+            h.set("X-Requested-With", XHR);
+        });
     }
 
     private HttpHeaders buildUploadHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set(HtRequestConstant.COOKIE, cookie);
-        headers.set("Accept", ACCEPT_ALL);
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Content-Type", CONTENT_TYPE_FORM);
-        headers.set("Origin", ORIGIN);
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", REFERER_CHECK_INS);
-        headers.set("User-Agent", USER_AGENT);
-        headers.set("X-Requested-With", X_REQUESTED_WITH);
-        return headers;
-    }
-
-    private HttpHeaders buildSpecialHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("Accept", ACCEPT_HTML);
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", ORIGIN + "/htcsp/ShakeHandsAction.do?cmd=contentInit");
-        headers.set("Upgrade-Insecure-Requests", "1");
-        headers.set("User-Agent", USER_AGENT);
-        headers.set("Cookie", cookie);
-        return headers;
-    }
-
-    private HttpHeaders buildSpecialHeaders2(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("Accept", ACCEPT_HTML);
-        headers.set("Accept-Language", "zh-CN,zh;q=0.8");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", ORIGIN + "/htcsp/ShakeHandsAction.do?cmd=contentInit");
-        headers.set("Upgrade-Insecure-Requests", "1");
-        headers.set("User-Agent", USER_AGENT);
-        headers.set("Cookie", cookie);
-        return headers;
+        return header(h -> {
+            h.set(HtRequestConstant.COOKIE, cookie);
+            h.set("Accept", "*/*");
+            h.set("Cache-Control", "no-cache");
+            h.set("Content-Type", CONTENT_TYPE_FORM);
+            h.set("Origin", ORIGIN);
+            h.set("Pragma", "no-cache");
+            h.set("Referer", ORIGIN + "/htcsp/checkInsAction.do?cmd=welcome&recommendNo=&checkedThreeWriteFlagRecommend=Y");
+            h.set("X-Requested-With", XHR);
+        });
+    }
+
+    private HttpHeaders buildSpecialHeaders(String cookie, boolean first) {
+        return header(h -> {
+            h.set("Accept", ACCEPT_HTML);
+            h.set("Cache-Control", "no-cache");
+            h.set("Pragma", "no-cache");
+            h.set("Referer", ORIGIN + "/htcsp/ShakeHandsAction.do?cmd=contentInit");
+            h.set("Upgrade-Insecure-Requests", "1");
+            h.set("Cookie", cookie);
+            if (!first) h.set("Accept-Language", "zh-CN,zh;q=0.8");
+        });
     }
 
     private HttpHeaders buildCarQueryHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, cookie);
-        headers.add("Accept", ACCEPT_JSON);
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.add("Cache-Control", "no-cache");
-        headers.add("Connection", "keep-alive");
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("Origin", ORIGIN);
-        headers.add("Pragma", "no-cache");
-        headers.add("Referer", REFERER_START_PROCESS);
-        headers.add("User-Agent", USER_AGENT);
-        headers.add("X-Requested-With", X_REQUESTED_WITH);
-        return headers;
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, cookie);
+            h.add("Accept", ACCEPT_JSON);
+            h.add("Cache-Control", "no-cache");
+            h.add("Content-Type", CONTENT_TYPE_FORM);
+            h.add("Origin", ORIGIN);
+            h.add("Pragma", "no-cache");
+            h.add("Referer", ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=startProcess");
+            h.add("X-Requested-With", XHR);
+        });
     }
 
     private HttpHeaders buildCarModelCodexhHeaders(String cookie) {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add("Accept", ACCEPT_JSON);
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("User-Agent", USER_AGENT);
-        headers.add("X-Requested-With", X_REQUESTED_WITH);
-        headers.add(HtRequestConstant.COOKIE, cookie);
-        return headers;
-    }
-
-    private HttpHeaders buildManualUnderwritingHeaders() {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, htCrawlerRequestComponent.getCookie());
-        headers.add("Accept", ACCEPT_JSON);
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.add("Connection", "keep-alive");
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("User-Agent", USER_AGENT);
-        headers.add("X-Requested-With", X_REQUESTED_WITH);
-        return headers;
+        return header(h -> {
+            h.add("Accept", ACCEPT_JSON);
+            h.add("Content-Type", CONTENT_TYPE_FORM);
+            h.add("X-Requested-With", XHR);
+            h.add(HtRequestConstant.COOKIE, cookie);
+        });
+    }
+
+    private HttpHeaders buildManualUwHeaders() {
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, req.getCookie());
+            h.add("Accept", ACCEPT_JSON);
+            h.add("Content-Type", CONTENT_TYPE_FORM);
+            h.add("X-Requested-With", XHR);
+        });
     }
 
     private HttpHeaders buildPaymentLinkHeaders() {
-        HttpHeaders headers = new HttpHeaders();
-        headers.add(HtRequestConstant.COOKIE, htCrawlerRequestComponent.getCookie());
-        headers.add("Accept", "text/html, */*; q=0.01");
-        headers.add("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.add("Connection", "keep-alive");
-        headers.add("Content-Type", CONTENT_TYPE_FORM);
-        headers.add("Referer", REFERER_QUOTATION_LIST);
-        headers.add("Upgrade-Insecure-Requests", "1");
-        headers.add("User-Agent", USER_AGENT);
-        return headers;
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, req.getCookie());
+            h.add("Accept", "text/html, */*; q=0.01");
+            h.add("Content-Type", CONTENT_TYPE_FORM);
+            h.add("Referer", ORIGIN + "/htcsp/QuotationQueryAction.do?cmd=quotationListAll");
+            h.add("Upgrade-Insecure-Requests", "1");
+        });
     }
 
-    // ====================== 工具方法 ======================
+    private HttpHeaders buildNoCarPremiumHeaders(String cookie) {
+        return header(h -> {
+            h.add(HtRequestConstant.COOKIE, cookie);
+            h.add("Accept", "*/*");
+            h.add("Content-Type", CONTENT_TYPE_FORM);
+            h.add("Origin", ORIGIN);
+            h.add("Referer", ORIGIN + "/htcsp/VehicleInfoEntryAction.do?cmd=saveOneStept");
+            h.add("X-Requested-With", XHR);
+        });
+    }
 
-    private static Map<String, Object> parseToMap(String input) {
-        Map<String, Object> map = new HashMap<>();
-        if (StringUtils.isBlank(input)) {
-            return map;
-        }
-        String content = input.trim();
-        if (content.startsWith("{")) {
-            content = content.substring(1);
-        }
-        if (content.endsWith("}")) {
-            content = content.substring(0, content.length() - 1);
-        }
+    // ====================== HTTP 通用工具 ======================
+    private String get(String url, HttpHeaders headers, MultiValueMap<String, String> params) {
+        UriComponentsBuilder b = UriComponentsBuilder.fromHttpUrl(url);
+        if (params != null) b.queryParams(params);
+        return RT.exchange(b.toUriString(), HttpMethod.GET, new HttpEntity<>(headers), String.class).getBody();
+    }
 
-        String[] pairs = content.split(",\\s*");
-        for (String pair : pairs) {
-            String[] keyValue = pair.split("=", 2);
-            if (keyValue.length == 2) {
-                map.put(keyValue[0].trim(), keyValue[1].trim());
-            }
-        }
-        return map;
+    private String post(String url, HttpHeaders headers, MultiValueMap<String, String> data) {
+        return RT.postForEntity(url, new HttpEntity<>(data, headers), String.class).getBody();
     }
 
-    private static void fillVehicleTypeCodes(CarInfoVo carInfoVo, Map<String, Object> data) {
-        String useCode = carInfoVo.getVehicleUseCode();
-        String kindCode = carInfoVo.getCarKindCode();
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> exchangeForm(String url, Map<String, String> queryParams,
+                                               MultiValueMap<String, String> data, HttpHeaders headers) throws JsonProcessingException {
+        UriComponentsBuilder b = UriComponentsBuilder.fromHttpUrl(url);
+        if (queryParams != null) queryParams.forEach(b::queryParam);
+        String body = RT.exchange(b.toUriString(), HttpMethod.POST, new HttpEntity<>(data, headers), String.class).getBody();
+        return MAPPER.readValue(body, Map.class);
+    }
 
-        switch (useCode) {
-            case "04" -> {
-                data.put("motortypecode", "H0");
-                data.put("motorusagetypecode", "9D");
-            }
-            case "05" -> {
-                data.put("motortypecode", "A0");
-                data.put("motorusagetypecode", "8A");
-            }
+    // ====================== 工具方法 ======================
+    private static Map<String, Object> parseToMap(String input) {
+        Map<String, Object> m = new HashMap<>();
+        if (StringUtils.isBlank(input)) return m;
+        String c = input.trim();
+        if (c.startsWith("{")) c = c.substring(1);
+        if (c.endsWith("}")) c = c.substring(0, c.length() - 1);
+        for (String pair : c.split(",\\s*")) {
+            String[] kv = pair.split("=", 2);
+            if (kv.length == 2) m.put(kv[0].trim(), kv[1].trim());
+        }
+        return m;
+    }
+
+    private static void fillVehicleTypeCodes(CarInfoVo car, Map<String, Object> data) {
+        String use = car.getVehicleUseCode(), kind = car.getCarKindCode();
+        switch (use) {
+            case "04" -> data.putAll(Map.of("motortypecode", "H0", "motorusagetypecode", "9D"));
+            case "05" -> data.putAll(Map.of("motortypecode", "A0", "motorusagetypecode", "8A"));
             case "06" -> {
-                if ("A0".equals(kindCode)) {
-                    data.put("motortypecode", "A0");
-                    data.put("motorusagetypecode", "8C");
-                } else if ("A1".equals(kindCode)) {
-                    data.put("motortypecode", "H0");
-                    data.put("motorusagetypecode", "8D");
-                }
+                if ("A0".equals(kind)) data.putAll(Map.of("motortypecode", "A0", "motorusagetypecode", "8C"));
+                else if ("A1".equals(kind)) data.putAll(Map.of("motortypecode", "H0", "motorusagetypecode", "8D"));
             }
             case "07" -> {
-                if ("A0".equals(kindCode)) {
-                    data.put("motortypecode", "A0");
-                    data.put("motorusagetypecode", "8B");
-                } else if ("A1".equals(kindCode)) {
-                    data.put("motortypecode", "H0");
-                    data.put("motorusagetypecode", "8D");
-                }
-            }
-            case "08" -> {
-                data.put("motortypecode", "H0");
-                data.put("motorusagetypecode", "8D");
+                if ("A0".equals(kind)) data.putAll(Map.of("motortypecode", "A0", "motorusagetypecode", "8B"));
+                else if ("A1".equals(kind)) data.putAll(Map.of("motortypecode", "H0", "motorusagetypecode", "8D"));
             }
+            case "08" -> data.putAll(Map.of("motortypecode", "H0", "motorusagetypecode", "8D"));
         }
     }
 
     public Map<String, Object> encrypt(Map<String, Object> x) throws Exception {
         String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-        StringBuilder keyBuilder = new StringBuilder();
-        Random random = new Random();
-        for (int i = 0; i < 16; i++) {
-            keyBuilder.append(alphabet.charAt(random.nextInt(alphabet.length())));
-        }
-        String key = keyBuilder.toString();
-
-        Map<String, Object> makeInputData = new HashMap<>();
-        makeInputData.put("sourceCode", 8);
-        makeInputData.put("accessToken", CryptoUtil.rsaEncrypt(key));
-        makeInputData.put("data", CryptoUtil.aesEncrypt(OBJECT_MAPPER.writeValueAsString(x), key));
-        makeInputData.put("insuredCode", "");
-        return makeInputData;
+        StringBuilder kb = new StringBuilder();
+        Random r = new Random();
+        for (int i = 0; i < 16; i++) kb.append(alphabet.charAt(r.nextInt(alphabet.length())));
+        String key = kb.toString();
+        return Map.of("sourceCode", 8, "accessToken", CryptoUtil.rsaEncrypt(key),
+                "data", CryptoUtil.aesEncrypt(MAPPER.writeValueAsString(x), key), "insuredCode", "");
     }
 
     public Map<String, Object> decrypt(Map<String, Object> x) throws Exception {
-        String accessToken = (String) x.get("accessToken");
-        String data = (String) x.get("data");
-        String aesKey = CryptoUtil.rsaDecrypt(accessToken);
-        String decrypted = CryptoUtil.aesDecrypt(data, aesKey);
-        return OBJECT_MAPPER.readValue(decrypted, HashMap.class);
-    }
-
-    public static Map<String, String> convertCookieToMap(String cookieStr) {
-        Map<String, String> map = new HashMap<>();
-        if (StringUtils.isBlank(cookieStr)) {
-            return map;
-        }
-        String[] pairs = cookieStr.split(";");
-        for (String pair : pairs) {
-            pair = pair.trim();
-            if (pair.isEmpty()) {
-                continue;
-            }
-            int index = pair.indexOf("=");
-            if (index > 0) {
-                map.put(pair.substring(0, index), pair.substring(index + 1));
-            }
+        String aesKey = CryptoUtil.rsaDecrypt((String) x.get("accessToken"));
+        String decrypted = CryptoUtil.aesDecrypt((String) x.get("data"), aesKey);
+        return MAPPER.readValue(decrypted, HashMap.class);
+    }
+
+    public static Map<String, String> convertCookieToMap(String s) {
+        Map<String, String> m = new HashMap<>();
+        if (StringUtils.isBlank(s)) return m;
+        for (String p : s.split(";")) {
+            p = p.trim();
+            if (p.isEmpty()) continue;
+            int i = p.indexOf('=');
+            if (i > 0) m.put(p.substring(0, i), p.substring(i + 1));
         }
-        return map;
+        return m;
     }
 }

+ 1 - 1
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/async/AsyncGetPayLink.java

@@ -32,7 +32,7 @@ public class AsyncGetPayLink {
     @Async("asyncTaskExecutor")
     public void paymentLink(QuotationBaseService quoteService,OrderBase order, String companyCode ){
         try {
-            if (StringUtils.equals(CompanyConstants.TPBX.getCompanyCode(),companyCode) || StringUtils.equals(CompanyConstants.TKIC.getCompanyCode(),companyCode)
+            if (StringUtils.equals(CompanyConstants.TKIC.getCompanyCode(),companyCode)
                     || StringUtils.equals(CompanyConstants.TSBX.getCompanyCode(),companyCode) || StringUtils.equals(CompanyConstants.HTIC.getCompanyCode(),companyCode)
             ){
                 PaymentLinkVo paymentLinkVo = new PaymentLinkVo();

+ 17 - 2
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/controller/QrCodeController.java

@@ -9,6 +9,8 @@ import com.jzg.commons.core.page.HttpResult;
 import com.jzg.commons.entity.orders.po.*;
 import com.jzg.commons.entity.po.EsmInsCompany;
 import com.jzg.commons.entity.po.QrBarcodeConfig;
+import com.jzg.commons.entity.quote.vo.aggregated.PaymentLinkVo;
+import com.jzg.commons.entity.quote.vo.base.OrderBase;
 import com.jzg.commons.exception.SystemException;
 import com.jzg.commons.util.AssertionUtils;
 import com.jzg.commons.util.LocalDateTimeUtils;
@@ -51,6 +53,7 @@ public class QrCodeController extends BaseController {
     private final InsOrdersCarInfoService insOrdersCarInfoService;
     private final InsOrdersRiskService insOrdersRiskService;
     private final PlatformClient platformClient;
+    private final UnifyOrderManager unifyOrderManager;
     //二维码生成地址
     @Value("${qrcode.genAddress}")
     private String qrCodeGenAddress;
@@ -60,7 +63,7 @@ public class QrCodeController extends BaseController {
 
     @GetMapping(value = "/getQrCode")
     @Operation(summary = "获取二维码")
-    public ModelAndView getQrCode(@NotBlank(message = "订单号号不能为空") @RequestParam("orderNo") String orderNo) {
+    public ModelAndView getQrCode(@NotBlank(message = "订单号号不能为空") @RequestParam("orderNo") String orderNo) throws Exception {
         // 获取保存支付url
         InsOrders orders = insOrdersService.getById(orderNo);
         List<InsOrdersCarUserInfo> carUserInfoList = insOrdersCarUserInfoService.getByOrderId(orderNo);
@@ -96,6 +99,13 @@ public class QrCodeController extends BaseController {
 
         InsOrdersQrcode ordersQrcode = insOrdersQrcodeService.getOneOrSave(orders);
         if(ordersQrcode.getJumpUrl() == null || ordersQrcode.getJumpUrl().isEmpty()){
+            // 尝试获取支付连接
+            PaymentLinkVo paymentLinkVo = new PaymentLinkVo();
+            OrderBase orderBase = new OrderBase();
+            orderBase.setOrdersNo(orderNo);
+            paymentLinkVo.setOrder(orderBase);
+            HttpResult httpResult = unifyOrderManager.getPaymentLink(paymentLinkVo);
+
             throw new SystemException("订单不存在支付连接");
         }
 
@@ -203,7 +213,12 @@ public class QrCodeController extends BaseController {
                     }
                 } else {
                     // 本身就是 base64
-                    finalQrUrl = "data:image/png;base64," + jumpUrl;
+                    boolean isBase64Image = jumpUrl.startsWith("data:image/");
+                    if(!isBase64Image) {
+                        finalQrUrl = "data:image/png;base64," + jumpUrl;
+                    }else{
+                        finalQrUrl = jumpUrl;
+                    }
                 }
 
                 qrCodeVo.setQrCodeUrl(finalQrUrl);

+ 14 - 13
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsOrdersServiceImpl.java

@@ -42,10 +42,7 @@ import com.jzg.commons.entity.quote.vo.orders.InsOrdersVo;
 import com.jzg.commons.entity.rights.vo.InsOrderRightsInfoVo;
 import com.jzg.commons.entity.vo.*;
 import com.jzg.commons.exception.SystemException;
-import com.jzg.commons.util.AssertionUtils;
-import com.jzg.commons.util.DateTimeUtils;
-import com.jzg.commons.util.MinioUtils;
-import com.jzg.commons.util.StringUtils;
+import com.jzg.commons.util.*;
 import com.jzg.commons.util.idgen.IdGenerate;
 import com.jzg.quotation.commons.client.OrgClient;
 import com.jzg.quotation.commons.client.PartnerDistributionClient;
@@ -805,9 +802,9 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
                 ordersOther = new InsOrdersOther();
                 ordersOther.setOrderNo(ordersNo);
                 // 赔付率
-                ordersOther.setLossRation(new BigDecimal(quoteResponse.getScoreVo().getLossRation() != null ? quoteResponse.getScoreVo().getLossRation() : "0"));
-                ordersOther.setJqLossRation(new BigDecimal(quoteResponse.getScoreVo().getJqLossRation() != null ? quoteResponse.getScoreVo().getJqLossRation() : "0"));
-                ordersOther.setSyLossRation(new BigDecimal(quoteResponse.getScoreVo().getSyLossRation() != null ? quoteResponse.getScoreVo().getSyLossRation() : "0"));
+                ordersOther.setLossRation(new BigDecimal(StringUtils.isNotEmpty(quoteResponse.getScoreVo().getLossRation())  ? quoteResponse.getScoreVo().getLossRation() : "0"));
+                ordersOther.setJqLossRation(new BigDecimal(StringUtils.isNotEmpty(quoteResponse.getScoreVo().getJqLossRation()) ? quoteResponse.getScoreVo().getJqLossRation() : "0"));
+                ordersOther.setSyLossRation(new BigDecimal(StringUtils.isNotEmpty(quoteResponse.getScoreVo().getSyLossRation()) ? quoteResponse.getScoreVo().getSyLossRation() : "0"));
                 // 续保
                 ordersOther.setJqRenewal(quoteResponse.getScoreVo().getJqRenewal() != null ? Integer.parseInt(quoteResponse.getScoreVo().getJqRenewal()) : null);
                 ordersOther.setSyRenewal(quoteResponse.getScoreVo().getSyRenewal() != null ? Integer.parseInt(quoteResponse.getScoreVo().getSyRenewal()) : null);
@@ -1375,15 +1372,19 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
         // 费用信息
         InsFeeOrders insFeeOrders = insFeeOrdersMapper.selectOne(new LambdaQueryWrapper<InsFeeOrders>()
                 .eq(InsFeeOrders::getOrderNo, ordersNo));
-        String systemCode = getSystemCode();
         // 比例信息
         if (insFeeOrders != null) {
-            resultVo.setInsFeeOrders(new InsFeeOrdersVo(insFeeOrders));
-            // modify by lipf,  resultVo.setCostRatio(new PtlAgreementCostRatioVo(ptlAgreementCostTypeList, ptlAgreement)); 这一行代码被注视掉,那么上面两个查询也就没有必要调用了
-            // List<PtlAgreementCostTypeVo> ptlAgreementCostTypeList = orgClient.queryByCostId(insFeeOrders.getCostId());
-            //PtlAgreement ptlAgreement = orgClient.getByAgreementId(insOrdersVo.getInsOrders().getAgreementId());
-            //resultVo.setCostRatio(new PtlAgreementCostRatioVo(ptlAgreementCostTypeList, ptlAgreement));
+            // modify by lipf,
+            //resultVo.setCostRatio(new PtlAgreementCostRatioVo(ptlAgreementCostTypeList, ptlAgreement)); 这一行代码被注视掉,那么上面两个查询也就没有必要调用了
+//            List<PtlAgreementCostTypeVo> ptlAgreementCostTypeList = orgClient.queryByCostId(insFeeOrders.getCostId());
+//            PtlAgreement ptlAgreement = orgClient.getByAgreementId(insOrdersVo.getInsOrders().getAgreementId());
+//            resultVo.setCostRatio(new PtlAgreementCostRatioVo(ptlAgreementCostTypeList, ptlAgreement));
             resultVo.setCostRatio(new PtlAgreementCostRatioVo(insFeeOrders));
+            // add by lipf, 手动计算留点比例的费用 2026年6月11日11:31:39
+            insFeeOrders.setJqKeepPointPremium(BigDecimalUtil.divide( BigDecimalUtil.multiply(insFeeOrders.getJqPremium(),resultVo.getCostRatio().getJqKeepPointRatio()),new BigDecimal(100)));
+            insFeeOrders.setSyKeepPointPremium(BigDecimalUtil.divide(BigDecimalUtil.multiply(insFeeOrders.getSyPremium(),resultVo.getCostRatio().getSyKeepPointRatio()),new BigDecimal(100)));
+            insFeeOrders.setJyKeepPointPremium(BigDecimalUtil.divide(BigDecimalUtil.multiply(insFeeOrders.getJyPremium(),resultVo.getCostRatio().getJyKeepPointRatio()),new BigDecimal(100)));
+            resultVo.setInsFeeOrders(new InsFeeOrdersVo(insFeeOrders));
         }
         //权益信息
         InsOrderRightsInfoVo insOrderRightsInfoVo = insOrderRightsInfoService.queryByOrderNo(ordersNo);

+ 8 - 0
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/build/TkCrawlerQuoteResultsBuild.java

@@ -21,6 +21,7 @@ import com.jzg.quotation.taikang.crawler.model.entity.TKPremium;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
@@ -128,6 +129,13 @@ public class TkCrawlerQuoteResultsBuild {
         quoteResultsVo.getCosts().setVvTax(preciseInitResult.getCarTax().getSumPayment().toString());
         // 设置非车险(驾意险)保费
         quoteResultsVo.getCosts().setNiPremium(premium.getJyxPremium().toString());
+        // 设置总费用
+        BigDecimal ci = premium.getJqPremium() == null ? BigDecimal.ZERO : premium.getJqPremium();
+        BigDecimal bi = premium.getSyPremium() == null ? BigDecimal.ZERO : premium.getSyPremium();
+        BigDecimal vv = preciseInitResult.getCarTax().getSumPayment() == null ? BigDecimal.ZERO : preciseInitResult.getCarTax().getSumPayment();
+        BigDecimal total = ci.add(bi).add(vv);
+        quoteResultsVo.getCosts().setSumPremium(total.toPlainString());
+        // 设置订单状态
         quoteResultsVo.setQuoteStatusEnum(InsOrderStatusEnum.WAIT_AUDIT);
         // 缓存报价书据数据
         redisCacheUtil.setPreciseInitResult(preciseInitResult, quoteResultsVo.getOrdersNo());

+ 7 - 0
tenant/insurance/quotation-taiping/src/main/java/com/jzg/quotation/taiping/crawler/service/impl/TaipingCrawlerRequestImpl.java

@@ -7,6 +7,8 @@ import com.jzg.quotation.commons.constant.InsuranceCode;
 import com.jzg.commons.entity.quote.vo.base.LoginBase;
 import com.jzg.commons.entity.quote.vo.image.InsuranceImageVo;
 import com.jzg.quotation.commons.entity.DetectLoginRequest;
+import com.jzg.quotation.commons.entity.GetCaptchaRequest;
+import com.jzg.quotation.commons.entity.GetCaptchaResponse;
 import com.jzg.quotation.commons.filter.CarModelsFilterUtils;
 import com.jzg.quotation.taiping.crawler.build.TaiPingCrawlerQuoteResultsVoBuild;
 import com.jzg.quotation.taiping.crawler.component.TaiPingCrawlerCacheComponent;
@@ -40,6 +42,11 @@ public class TaipingCrawlerRequestImpl implements TaipingCrawlerRequest {
         taiPingCrawlerQuoteProcessService.login(loginBase.getUsername(), loginBase.getPassword());
     }
 
+    @Override
+    public GetCaptchaResponse getCaptcha(GetCaptchaRequest sendCaptchaRequest) {
+        return null;
+    }
+
     @Override
     public QuoteResultsVo quote(QuoteVo quoteVo) {
         HttpHeaders httpHeaders = taiPingCrawlerRequestComponent.getHeaders();

+ 32 - 11
tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/build/TaiShanCrawlerQuoteRequestVoBuild.java

@@ -17,6 +17,9 @@ import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 public class TaiShanCrawlerQuoteRequestVoBuild {
 
     public static TaiShanCrawlerQuotationRequest toQuotationDto(QuoteVo quoteVo, TaiShanCrawlerCarResponse.CarModelInfo modelsDTO, String currentValue, TaiShanCrawlerConfigureParameters parameters, TaiShanCrawlerDeptResponse taiShanCrawlerDeptResponse, AttributionInformationVo attributionInformationVo, String policyPersonId, String insuredPersonId) {
@@ -240,7 +243,17 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
         int seatNumber = Integer.parseInt(modelsDTO.getNewSeatNumber());
 
         for (KindInfoVo infoVo : kindInfoVoList) {
-            String kindCode = infoVo.getKindCode();
+            // 注意:kindCode是泰山编码(如030006),platformKindCode才是掌柜编码(如A)
+            // switch使用掌柜编码匹配,与Python insurance_Map的key一致
+            String kindCode = infoVo.getPlatformKindCode();
+            log.info("泰山报价-险种编码: kindCode(泰山)={}, amount={}, platformKindCode(掌柜)={}", infoVo.getKindCode(), infoVo.getAmount(), kindCode);
+
+            // 跳过不在商业险映射表中的险种(如交强险等),与Python insurance_Map一致
+            if (!BUSINESS_KIND_CODES.contains(kindCode)) {
+                log.info("泰山报价-跳过非商业险种: platformKindCode={}", kindCode);
+                continue;
+            }
+
             TaiShanCrawlerQuotationRequest.BusinessInsurance.ItemKind itemKind;
 
             // 获取对应险种的模板默认值(与Python insurance_Map对应)
@@ -249,6 +262,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                 case "A":
                     itemKind = buildDefaultItemKind("030006", "机动车损失保险", 1);
                     itemKind.setAmount(actualValue);
+                    itemKind.setIndemLmt(actualValue);
                     itemKind.setDeductible("375001");
                     break;
 
@@ -260,7 +274,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                     itemKind.setIndemLmtLvl("306006018"); // 默认200万以上
                     for (String[] limit : THIRD_LIMIT_AMT) {
                         double cnameVal = extractNumberFromWan(limit[0]);
-                        if (cnameVal == Double.parseDouble(infoVo.getAmount())) {
+                        if (Math.abs(cnameVal - Double.parseDouble(infoVo.getAmount())) < 0.01) {
                             itemKind.setIndemLmtLvl(limit[1]);
                             break;
                         }
@@ -270,12 +284,11 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                 // D3: 车上人员责任险(驾驶人)
                 case "D3":
                     itemKind = buildDefaultItemKind("030001", "车上人员责任险(驾驶人)", 3);
-                    itemKind.setAmount(Integer.parseInt(infoVo.getAmount()));
+                    itemKind.setAmount(infoVo.getAmount());
                     itemKind.setLiabDaysLmt(1);
                     // 匹配医保外医疗费用档次以获取indemLmtLvl(与Python一致)
                     for (String[] med : MEDICAL_EXPENSES_OUTSIDE) {
-                        if (!"保额共享".equals(med[0]) && Float.parseFloat(med[0]) == Float.parseFloat(infoVo.getAmount())) {
-                            itemKind.setAmount(infoVo.getAmount());
+                        if (!"保额共享".equals(med[0]) && Math.abs(Float.parseFloat(med[0]) - Float.parseFloat(infoVo.getAmount())) < 0.01) {
                             itemKind.setIndemLmt(actualValue);
                             itemKind.setIndemLmtLvl(med[1]);
                             break;
@@ -291,7 +304,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                     itemKind.setPerAmt(d4Amount);
                     itemKind.setLiabDaysLmt(seatNumber - 1);
                     for (String[] med : MEDICAL_EXPENSES_OUTSIDE) {
-                        if (!"保额共享".equals(med[0]) && Float.parseFloat(med[0]) == Float.parseFloat(infoVo.getAmount())) {
+                        if (!"保额共享".equals(med[0]) && Math.abs(Float.parseFloat(med[0]) - Float.parseFloat(infoVo.getAmount())) < 0.01) {
                             itemKind.setIndemLmt(actualValue);
                             itemKind.setIndemLmtLvl(med[1]);
                             break;
@@ -305,7 +318,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                     itemKind.setAmount(infoVo.getAmount());
                     itemKind.setIndemLmt(infoVo.getAmount());
                     for (String[] scratch : CAR_SCRATCH_LEVELS) {
-                        if (Float.parseFloat(scratch[0]) == Float.parseFloat(infoVo.getAmount())) {
+                        if (Math.abs(Float.parseFloat(scratch[0]) - Float.parseFloat(infoVo.getAmount())) < 0.01) {
                             itemKind.setIndemLmtLvl(scratch[1]);
                             break;
                         }
@@ -326,9 +339,9 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                     itemKind.setIndemLmt("500000");
                     itemKind.setIndemLmtLvl("390N27"); // 默认50万
                     for (String[] med : MEDICAL_EXPENSES_OUTSIDE) {
-                        if (!"保额共享".equals(med[0]) && Float.parseFloat(med[0]) == Float.parseFloat(infoVo.getAmount())) {
+                        if (!"保额共享".equals(med[0]) && Math.abs(Float.parseFloat(med[0]) - Float.parseFloat(infoVo.getAmount())) < 0.01) {
                             itemKind.setAmount(infoVo.getAmount());
-                            itemKind.setIndemLmt(infoVo.getAmount());
+                            itemKind.setIndemLmt(actualValue);
                             itemKind.setIndemLmtLvl(med[1]);
                             break;
                         }
@@ -342,7 +355,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                     itemKind.setIndemLmt("500000");
                     itemKind.setIndemLmtLvl("390N27"); // 默认50万
                     for (String[] med : MEDICAL_EXPENSES_OUTSIDE) {
-                        if (!"保额共享".equals(med[0]) && Float.parseFloat(med[0]) == Float.parseFloat(infoVo.getAmount())) {
+                        if (!"保额共享".equals(med[0]) && Math.abs(Float.parseFloat(med[0]) - Float.parseFloat(infoVo.getAmount())) < 0.01) {
                             itemKind.setIndemLmtLvl(med[1]);
                             itemKind.setAmount(infoVo.getAmount());
                             itemKind.setIndemLmt(infoVo.getAmount());
@@ -360,7 +373,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                     itemKind.setPerAmt("500000");
                     itemKind.setIndemLmtLvl("390N27"); // 默认50万
                     for (String[] med : MEDICAL_EXPENSES_OUTSIDE) {
-                        if (!"保额共享".equals(med[0]) && Float.parseFloat(med[0]) == Float.parseFloat(infoVo.getAmount())) {
+                        if (!"保额共享".equals(med[0]) && Math.abs(Float.parseFloat(med[0]) - Float.parseFloat(infoVo.getAmount())) < 0.01) {
                             itemKind.setAmount(Integer.parseInt(infoVo.getAmount()) * (seatNumber - 1));
                             itemKind.setIndemLmt(infoVo.getAmount());
                             itemKind.setLiabDaysLmt(seatNumber - 1);
@@ -435,6 +448,7 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
 
                 default:
                     // 未识别的险种,回退原有逻辑填充
+                    log.warn("泰山报价-未识别的险种编码: kindCode={}, 走fallback逻辑", kindCode);
                     itemKind = fallbackBuildItemKind(infoVo, kindList.size());
                     break;
             }
@@ -564,6 +578,13 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
 
     // ==================== Python 档位静态常量(与 tsbxitem.py 保持一致) ====================
 
+    /** 商业险种编码集合(与Python insurance_Map的key一致),用于过滤非商业险种 */
+    private static final Set<String> BUSINESS_KIND_CODES = new HashSet<>(Arrays.asList(
+            "A", "B", "D3", "D4", "G1", "L", "F",
+            "SY_FJ_YBW1", "SY_FJ_YBW2", "SY_FJ_YBW3",
+            "MJ1", "MJ2", "BD", "TY4"
+    ));
+
     /** 【第三者责任险】限额档次 */
     private static final List<String[]> THIRD_LIMIT_AMT = Arrays.asList(
             new String[]{"10万元", "306006005"},

+ 77 - 38
tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/service/impl/TaiShanCrawlerRequestImpl.java

@@ -233,6 +233,22 @@ public class TaiShanCrawlerRequestImpl implements TaiShanCrawlerRequest {
                         itemKind.setBefPremium(res.getBefPremium());
                         itemKind.setBefAnnPrm(res.getBefAnnPrm());
                         itemKind.setBasePrm(res.getBasePrm());
+                        itemKind.setPureRiskPremium(res.getPureRiskPremium());
+                        // 覆盖报价响应中的其他关键字段(与Python直接使用price_res['result']作为save数据一致)
+                        itemKind.setIndemLmtLvl(res.getIndemLmtLvl());
+                        itemKind.setDisCoef(res.getDisCoef());
+                        itemKind.setDuctDiscount(res.getDuctDiscount());
+                        itemKind.setDeductibleRatio(res.getDeductibleRatio());
+                        itemKind.setAfterPremiumYear(res.getAfterPremiumYear());
+                        itemKind.setCalcPrm(res.getCalcPrm());
+                        itemKind.setResvNumberRoad(res.getResvNumberRoad());
+                        itemKind.setResvNumberDriver(res.getResvNumberDriver());
+                        itemKind.setAbsoluteDeductible(res.getAbsoluteDeductible());
+                        itemKind.setResvNumAutonomy(res.getResvNumAutonomy());
+                        itemKind.setAmtVar(res.getAmtVar());
+                        itemKind.setPrmVar(res.getPrmVar());
+                        itemKind.setPkId(res.getPkId());
+                        itemKind.setSeqNo(res.getSeqNo());
                     }
                 });
             }
@@ -286,7 +302,7 @@ public class TaiShanCrawlerRequestImpl implements TaiShanCrawlerRequest {
      */
     @Override
     public UnderwritingResultsVo underwriting(UnderwritingVo underwritingVo) {
-        log.info("underwritingVo:{}", JSON.toJSONString(underwritingVo));
+        log.info("TaiShan-underwritingVo:{}", JSON.toJSONString(underwritingVo));
         // 1.0 获取认证信息和用户配置
         AttributionInformationVo attributionInformationVo = underwritingVo.getOrder().getAttributionInformationVo();
         TaiShanCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(TaiShanCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
@@ -302,16 +318,33 @@ public class TaiShanCrawlerRequestImpl implements TaiShanCrawlerRequest {
 
         UnderwritingResultsVo underwritingResultsVo = new UnderwritingResultsVo();
         if ("200".equals(taiShanCrawlerUnderwritingResponse.getCode())) {
-            //核保成功
-            underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
-            underwritingResultsVo.setJqApplyNo(taiShanCrawlerUnderwritingResponse.getResult().getJqAppNo());
-            underwritingResultsVo.setSyApplyNo(taiShanCrawlerUnderwritingResponse.getResult().getSyAppNo());
-
-            // 获取缴费地址
-            PaymentLinkVo paymentLinkVo = new PaymentLinkVo();
-            paymentLinkVo.setOrder(underwritingVo.getOrder());
-            PaymentLinkResultsVo paymentLinkResultsVo = getPaymentLink(paymentLinkVo);
-            underwritingResultsVo.setPaymentLink(paymentLinkResultsVo.getPaymentLink());
+            String retMsg = "";
+            TaiShanCrawlerUnderwritingResponse.Result result = taiShanCrawlerUnderwritingResponse.getResult();
+            if(result != null){
+                retMsg = result.getRetMsg();
+            }
+            log.info("TaiShan-retMsg:{}", JSON.toJSONString(retMsg));
+            if(org.apache.commons.lang3.StringUtils.isNotBlank(retMsg)){
+                if(retMsg.contains("重新提交")){
+                    underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.AUDIT_FAIL);
+//                    StringBuilder sb = new StringBuilder();
+//                    if (null != taiShanCrawlerUnderwritingResponse.getMessage()) {
+//                        sb.append(String.format("错误信息:%s", taiShanCrawlerUnderwritingResponse.getMessage()));
+//                    }
+                    underwritingResultsVo.setUnderwritingResult(retMsg);
+                }else if(retMsg.contains("核保通过")){
+                    //核保成功
+                    underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
+                    underwritingResultsVo.setJqApplyNo(taiShanCrawlerUnderwritingResponse.getResult().getJqAppNo());
+                    underwritingResultsVo.setSyApplyNo(taiShanCrawlerUnderwritingResponse.getResult().getSyAppNo());
+
+                    // 获取缴费地址
+                    PaymentLinkVo paymentLinkVo = new PaymentLinkVo();
+                    paymentLinkVo.setOrder(underwritingVo.getOrder());
+                    PaymentLinkResultsVo paymentLinkResultsVo = getPaymentLink(paymentLinkVo);
+                    underwritingResultsVo.setPaymentLink(paymentLinkResultsVo.getPaymentLink());
+                }
+            }
         } else {//核保失败
             underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.AUDIT_FAIL);
             StringBuilder sb = new StringBuilder();
@@ -320,6 +353,7 @@ public class TaiShanCrawlerRequestImpl implements TaiShanCrawlerRequest {
             }
             underwritingResultsVo.setUnderwritingResult(sb.toString());
         }
+        log.info("TaiShan-underwritingResultsVo:{}", JSON.toJSONString(underwritingResultsVo));
         return underwritingResultsVo;
     }
 
@@ -330,35 +364,40 @@ public class TaiShanCrawlerRequestImpl implements TaiShanCrawlerRequest {
      */
     @Override
     public void uploadImage(InsuranceUploadImageVo insuranceUploadImageVo) {
-        //获取归属信息
-        AttributionInformationVo attributionInformationVo = insuranceUploadImageVo.getOrder().getAttributionInformationVo();
-        TaiShanCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(TaiShanCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
-        HttpHeaders headers = taiShanCrawlerQuoteProcessService.getHeaders(attributionInformationVo.getUsername(), parameters.getComCode());
-        TaiShanCrawlerOrder taiShanCrawlerOrder = taiShanCrawlerOrderService.getByOrdersNo(insuranceUploadImageVo.getOrder().getOrdersNo());
-        // 上传影像文件请求参数
-        TaiShanCrawlerImageUploadRequest taiShanCrawlerImageUploadRequest = new TaiShanCrawlerImageUploadRequest();
-        taiShanCrawlerImageUploadRequest.setBiProposalNo(taiShanCrawlerOrder.getSyApplyNo());
-        taiShanCrawlerImageUploadRequest.setCiProposalNo(taiShanCrawlerOrder.getCiApplyNo());
-        taiShanCrawlerImageUploadRequest.setCompanyCode(parameters.getComCode());
-
-        List<TaiShanCrawlerImageUploadRequest.ImageNode> imageNodes = new LinkedList<>();
-        for (InsuranceImageVo imageVo : insuranceUploadImageVo.getImageVoList()) {
-            TaiShanCrawlerImageUploadRequest.ImageNode imageNode = new TaiShanCrawlerImageUploadRequest.ImageNode();
-            imageNode.setType(getImageType(imageVo.getImageCode()));
-            List<TaiShanCrawlerImageUploadRequest.ImageInfo> images = new ArrayList<>();
-            for (InsuranceImageVo.ImageDTO imageDTO : imageVo.getImageBase64List()) {
-                images.add(new TaiShanCrawlerImageUploadRequest.ImageInfo(imageDTO.getImageName(), imageDTO.getImageBase64Str()));
+        try {
+            //获取归属信息
+            AttributionInformationVo attributionInformationVo = insuranceUploadImageVo.getOrder().getAttributionInformationVo();
+            TaiShanCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(TaiShanCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
+            HttpHeaders headers = taiShanCrawlerQuoteProcessService.getHeaders(attributionInformationVo.getUsername(), parameters.getComCode());
+            TaiShanCrawlerOrder taiShanCrawlerOrder = taiShanCrawlerOrderService.getByOrdersNo(insuranceUploadImageVo.getOrder().getOrdersNo());
+            // 上传影像文件请求参数
+            TaiShanCrawlerImageUploadRequest taiShanCrawlerImageUploadRequest = new TaiShanCrawlerImageUploadRequest();
+            taiShanCrawlerImageUploadRequest.setBiProposalNo(taiShanCrawlerOrder.getSyApplyNo());
+            taiShanCrawlerImageUploadRequest.setCiProposalNo(taiShanCrawlerOrder.getCiApplyNo());
+            taiShanCrawlerImageUploadRequest.setCompanyCode(parameters.getComCode());
+
+            List<TaiShanCrawlerImageUploadRequest.ImageNode> imageNodes = new LinkedList<>();
+            for (InsuranceImageVo imageVo : insuranceUploadImageVo.getImageVoList()) {
+                TaiShanCrawlerImageUploadRequest.ImageNode imageNode = new TaiShanCrawlerImageUploadRequest.ImageNode();
+                imageNode.setType(getImageType(imageVo.getImageCode()));
+                List<TaiShanCrawlerImageUploadRequest.ImageInfo> images = new ArrayList<>();
+                for (InsuranceImageVo.ImageDTO imageDTO : imageVo.getImageBase64List()) {
+                    images.add(new TaiShanCrawlerImageUploadRequest.ImageInfo(imageDTO.getImageName(), imageDTO.getImageBase64Str()));
+                }
+                imageNode.setImages(images);
+                imageNodes.add(imageNode);
             }
-            imageNode.setImages(images);
-            imageNodes.add(imageNode);
+            taiShanCrawlerImageUploadRequest.setImageNodes(imageNodes);
+            taiShanCrawlerImageUploadRequest.setOperatorCode(parameters.getSaleCode());
+            taiShanCrawlerImageUploadRequest.setOperatorName(parameters.getSaleName());
+            taiShanCrawlerImageUploadRequest.setOrderNo(taiShanCrawlerOrder.getOrderNo());
+            taiShanCrawlerImageUploadRequest.setUploadFlag("D");
+            TaiShanCrawlerImageUploadResponse taiShanCrawlerImageUploadResponse = taiShanCrawlerRequestComponent.uploadImage(taiShanCrawlerImageUploadRequest, headers);
+            log.info("taiShanCrawlerImageUploadResponse对象的值:{}", JSON.toJSONString(taiShanCrawlerImageUploadResponse));
+        } catch (Exception e) {
+            log.error("泰山影像上传失败", e);
+            throw new RuntimeException("泰山影像上传失败: " + e.getMessage(), e);
         }
-        taiShanCrawlerImageUploadRequest.setImageNodes(imageNodes);
-        taiShanCrawlerImageUploadRequest.setOperatorCode(parameters.getSaleCode());
-        taiShanCrawlerImageUploadRequest.setOperatorName(parameters.getSaleName());
-        taiShanCrawlerImageUploadRequest.setOrderNo(taiShanCrawlerOrder.getOrderNo());
-        taiShanCrawlerImageUploadRequest.setUploadFlag("D");
-        TaiShanCrawlerImageUploadResponse taiShanCrawlerImageUploadResponse = taiShanCrawlerRequestComponent.uploadImage(taiShanCrawlerImageUploadRequest, headers);
-        log.info("taiShanCrawlerImageUploadResponse对象的值:{}", JSON.toJSONString(taiShanCrawlerImageUploadResponse));
     }
 
 

+ 3 - 0
tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/entity/Request/ZmCrawlerQuotedPriceRequest.java

@@ -192,6 +192,9 @@ public class ZmCrawlerQuotedPriceRequest extends BaseRequest {
                     coverageDTO.setQuantity(seatCount - 1);
                     coverageDTO.setAmount(String.valueOf(new BigDecimal(kindInfoVo.getUnitAmount()).multiply(new BigDecimal(coverageDTO.getQuantity()))));
                 }
+                if (kindInfoVo.getKindCode().equals("54") || kindInfoVo.getKindCode().equals("55") || kindInfoVo.getKindCode().equals("56") || kindInfoVo.getKindCode().equals("57")) {
+                    coverageDTO.setQuantity(Integer.parseInt(kindInfoVo.getServiceTimes()));
+                }
                 coverageDTO.setValidDate(biRiskInfoVo.getStartDate());
                 coverageDTO.setExpiryDate(biRiskInfoVo.getEndDate());
                 coverageDTOList.add(coverageDTO);

+ 1 - 1
tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/build/ZiJinCrawlerQuoteResultsVoBuild.java

@@ -205,7 +205,7 @@ public class ZiJinCrawlerQuoteResultsVoBuild {
         }
 
         // 计算总保费
-        // calculateSumPremium(quoteResultsVo);
+        calculateSumPremium(quoteResultsVo);
     }
 
     /**

+ 1 - 1
tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/component/ZiJinCrawlerCacheComponent.java

@@ -69,7 +69,7 @@ public class ZiJinCrawlerCacheComponent {
         String authorization = token;
         authorizationMap.put(AUTHORIZATION, authorization);
         authorizationMap.put(USER_CODE, userCode);
-        RMap<Object, Object> map = redissonClient.getMap(ZIJIN_AUTHORIZATION + userName);
+        RMap<Object, Object> map = redissonClient.getMap(ZIJIN_AUTHORIZATION + userCode);
         map.putAll(authorizationMap);
         map.expire(Duration.ofHours(ZIJIN_AUTHORIZATION_TIME));
         return authorization;

+ 90 - 60
tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/component/ZiJinCrawlerRequestComponent.java

@@ -2,18 +2,18 @@ package com.jzg.quotation.zijin.crawler.component;
 
 import com.alibaba.fastjson2.JSON;
 import com.google.zxing.NotFoundException;
-import com.jzg.commons.exception.SystemException;
-import com.jzg.quotation.zijin.crawler.enums.RequestUrl;
+import com.jzg.quotation.commons.component.RequestComponent;
+import com.jzg.quotation.commons.service.ExteriorUtilsRequest;
 import com.jzg.quotation.zijin.crawler.entity.request.*;
 import com.jzg.quotation.zijin.crawler.entity.response.*;
+import com.jzg.quotation.zijin.crawler.enums.RequestUrl;
 import com.jzg.quotation.zijin.crawler.properties.ZiJinCrawlerProperties;
-import com.jzg.quotation.zijin.crawler.util.*;
-import com.jzg.quotation.commons.component.RequestComponent;
+import com.jzg.quotation.zijin.crawler.util.DESEncryption;
+import com.jzg.quotation.zijin.crawler.util.RS6Item;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
 import org.springframework.util.LinkedMultiValueMap;
@@ -21,7 +21,10 @@ import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * @description: 紫金财险爬虫请求组件
@@ -39,9 +42,10 @@ public class ZiJinCrawlerRequestComponent {
 
     private final RequestComponent requestComponent;
 
-
     private final ZiJinCrawlerProperties ziJinCrawlerProperties;
 
+    private final ExteriorUtilsRequest exteriorUtilsRequest;
+
     /**
      * 获取验证码
      *
@@ -52,7 +56,7 @@ public class ZiJinCrawlerRequestComponent {
         ZiJinCrawlerCaptchaResponse ziJinCrawlerCaptchaResponse = null;
         try {
             if (frequency <= 0) {
-                //throw new SystemException("验证码请求频次异常");
+                // throw new SystemException("验证码请求频次异常");
                 ziJinCrawlerCaptchaResponse = new ZiJinCrawlerCaptchaResponse();
                 ziJinCrawlerCaptchaResponse.setRtnCode("500");
                 ziJinCrawlerCaptchaResponse.setRtnMsg("验证码请求频次异常");
@@ -69,9 +73,11 @@ public class ZiJinCrawlerRequestComponent {
             ziJinCrawlerCaptchaResponse = responseEntity.getBody();
             log.info("获取验证码接口响应内容:{}", JSON.toJSONString(ziJinCrawlerCaptchaResponse));
             String captchaImg = ziJinCrawlerCaptchaResponse.getCaptchaImg();
-            String captcha = captchaImg.split(",")[1].trim();
+            // String captcha = captchaImg.split(",")[1].trim();
             // 识别验证码的内容
-            String captchaContent = CaptchaUtil.performOCR(captcha);
+            // String captchaContent = CaptchaUtil.performOCR(captcha);
+            // 识别验证码
+            String captchaContent = exteriorUtilsRequest.captchaIdentify(captchaImg);
             log.info("验证码内容:{}", captchaContent);
             ziJinCrawlerCaptchaResponse.setCaptchaContent(captchaContent);
         } catch (Exception e) {
@@ -107,19 +113,19 @@ public class ZiJinCrawlerRequestComponent {
      */
     public ZiJinCrawlerCheckCaptchaResponse checkCaptchaAndGetToken(ZiJinCrawlerCheckCaptchaRequest ziJinCrawlerCheckCaptchaRequest, String token) throws IOException {
         log.info("ziJinCrawlerCheckCaptchaRequest请求参数的值:{}", JSON.toJSONString(ziJinCrawlerCheckCaptchaRequest));
-
         HttpHeaders httpHeaders = setHttpHeaders();
         httpHeaders.setContentLength(289);
-        //需要单独设置这个
+        // 需要单独设置这个
         httpHeaders.set("Authorization", token);
         httpHeaders.set("loginToken", token);
+
         String jsonString = JSON.toJSONString(ziJinCrawlerCheckCaptchaRequest);
         ResponseEntity<ZiJinCrawlerCheckCaptchaResponse> responseEntity = requestComponent.post(RequestUrl.CHECKCAPTCHAANDGETTOKEN.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), jsonString, ZiJinCrawlerCheckCaptchaResponse.class, httpHeaders);
         return responseEntity.getBody();
     }
 
     /**
-     * 校验token
+     * 校验token(Python没有这个接口)
      *
      * @param ziJinCrawlerCheckTokenRequest
      * @param token
@@ -127,12 +133,12 @@ public class ZiJinCrawlerRequestComponent {
      */
     public ZiJinCrawlerCheckTokenResponse checkToken(ZiJinCrawlerCheckTokenRequest ziJinCrawlerCheckTokenRequest, String token) throws IOException {
         log.info("ziJinCrawlerCheckTokenRequest请求参数的值:{}", JSON.toJSONString(ziJinCrawlerCheckTokenRequest));
-
         HttpHeaders httpHeaders = setHttpHeaders();
         httpHeaders.setContentLength(66);
-        //需要单独设置这个
+        // 需要单独设置这个
         httpHeaders.set("Authorization", token);
         httpHeaders.set("loginToken", token);
+
         String jsonString = JSON.toJSONString(ziJinCrawlerCheckTokenRequest);
         ResponseEntity<ZiJinCrawlerCheckTokenResponse> responseEntity = requestComponent.post(RequestUrl.CHECK_TOKEN.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), jsonString, ZiJinCrawlerCheckTokenResponse.class, httpHeaders);
         return responseEntity.getBody();
@@ -150,9 +156,9 @@ public class ZiJinCrawlerRequestComponent {
         ZiJinCrawlerCaptchaResponse captcha = captcha(CIRCULATE_NUMBER);
         // 登录存入缓存当中
         ZiJinCrawlerAuthenticateRequest request = new ZiJinCrawlerAuthenticateRequest(captcha, username, password);
-        //ZiJinCrawlerBaseRequest crawlerBaseRequest = new ZiJinCrawlerBaseRequest(request);
+        // ZiJinCrawlerBaseRequest crawlerBaseRequest = new ZiJinCrawlerBaseRequest(request);
         ZiJinCrawlerBaseRequest crawlerBaseRequest = null;
-        //return requestComponent.objectPost(RequestUrl.AUTHENTICATE.getRequestUrl(""), crawlerBaseRequest, ZiJinCrawlerBaseResponse.class);
+        // return requestComponent.objectPost(RequestUrl.AUTHENTICATE.getRequestUrl(""), crawlerBaseRequest, ZiJinCrawlerBaseResponse.class);
         return null;
     }
 
@@ -165,7 +171,7 @@ public class ZiJinCrawlerRequestComponent {
      * @return 获取渠道信息响应
      */
     public ZiJinCrawlerGetSaleInfoResponse getSaleInfo(String operatorCode, HttpHeaders headers) {
-        //String url = RequestUrl.GET_SALE_INFO.getRequestUrl("") + operatorCode;
+        // String url = RequestUrl.GET_SALE_INFO.getRequestUrl("") + operatorCode;
         String url = "";
         MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
         map.add("operatorCode", operatorCode);
@@ -181,8 +187,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerCarResponse
      */
     public ZiJinCrawlerCarResponse car(ZiJinCrawlerCarRequest ziJinCrawlerCarRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerCarRequest);
         log.info("ziJinCrawlerCarRequest请求参数的值:{}", jsonString);
@@ -198,8 +204,9 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerCarResponse
      */
     public ZiJinCrawlerGetOrderNoResponse getOrderNo(ZiJinCrawlerGetOrderNoRequest ziJinCrawlerGetOrderNoRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
+
         String jsonString = JSON.toJSONString(ziJinCrawlerGetOrderNoRequest);
         log.info("ziJinCrawlerGetOrderNoRequest请求参数的值:{}", jsonString);
         ResponseEntity<ZiJinCrawlerGetOrderNoResponse> responseEntity = requestComponent.post(RequestUrl.GET_ORDER_NUMBER.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), jsonString, ZiJinCrawlerGetOrderNoResponse.class, httpHeaders);
@@ -214,8 +221,9 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerGetCarActualValueResponse
      */
     public ZiJinCrawlerGetCarActualValueResponse getCarActualValue(ZiJinCrawlerGetCarActualValueRequest ziJinCrawlerGetCarActualValueRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
+
         String jsonString = JSON.toJSONString(ziJinCrawlerGetCarActualValueRequest);
         log.info("ziJinCrawlerGetCarActualValueRequest请求参数的值:{}", jsonString);
         ResponseEntity<ZiJinCrawlerGetCarActualValueResponse> responseEntity = requestComponent.post(RequestUrl.GET_ACTUAL_VALUE.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), jsonString, ZiJinCrawlerGetCarActualValueResponse.class, httpHeaders);
@@ -230,10 +238,17 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerGetTaxRegistryNumberResponse
      */
     public ZiJinCrawlerGetTaxRegistryNumberResponse getTaxRegistryNumber(String comCode, HttpHeaders headers) throws IOException {
-        HttpHeaders httpHeaders = setHttpHeaders();
-        String url = RequestUrl.GET_TAX_REGISTRY_NUMBER.getRequestUrl(ziJinCrawlerProperties.getBaseUrl());
-        log.info("获取税务登记证号请求URL:{}", url);
-        ZiJinCrawlerGetTaxRegistryNumberResponse response = requestComponent.get(url, new LinkedMultiValueMap<>(), "", ZiJinCrawlerGetTaxRegistryNumberResponse.class, httpHeaders);
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
+        httpHeaders.remove("Cache-Control");
+        httpHeaders.remove("Content-Length");
+        httpHeaders.remove("Origin");
+        httpHeaders.remove("Pragma");
+        httpHeaders.remove("content-type");
+
+        LinkedMultiValueMap<String, String> parameterMap = new LinkedMultiValueMap<>();
+        parameterMap.add("comCode", comCode);
+        ZiJinCrawlerGetTaxRegistryNumberResponse response = requestComponent.get(RequestUrl.GET_TAX_REGISTRY_NUMBER.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), parameterMap, "", ZiJinCrawlerGetTaxRegistryNumberResponse.class, httpHeaders);
         log.info("获取税务登记证号响应:{}", JSON.toJSONString(response));
         return response;
     }
@@ -246,8 +261,9 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerQuotationResponse
      */
     public ZiJinCrawlerQuotationResponse quotation(ZiJinCrawlerQuotationRequest ziJinCrawlerQuotationRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
+
         String jsonString = JSON.toJSONString(ziJinCrawlerQuotationRequest);
         log.info("ziJinCrawlerQuotationRequest请求参数的值:{}", jsonString);
         ResponseEntity<ZiJinCrawlerQuotationResponse> post = requestComponent.post(RequestUrl.PRICE.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), jsonString, ZiJinCrawlerQuotationResponse.class, httpHeaders);
@@ -258,13 +274,13 @@ public class ZiJinCrawlerRequestComponent {
      * 验证码(报价出现转保标志)
      *
      * @param ziJinCrawlerRecognizeCodesRequest
-     * @param headers 请求头
+     * @param headers                           请求头
      * @return ZiJinCrawlerRecognizeCodesResponse
      */
     public ZiJinCrawlerRecognizeCodesResponse recognizeCodes(List<ZiJinCrawlerRecognizeCodesRequest> ziJinCrawlerRecognizeCodesRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
-        httpHeaders.set("Content-Type", "application/json");
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
+
         String jsonString = JSON.toJSONString(ziJinCrawlerRecognizeCodesRequest);
         log.info("ziJinCrawlerRecognizeCodesRequest请求参数的值:{}", jsonString);
         ResponseEntity<ZiJinCrawlerRecognizeCodesResponse> post = requestComponent.post(RequestUrl.SKIP_SING.getRequestUrl(ziJinCrawlerProperties.getBaseUrl()), jsonString, ZiJinCrawlerRecognizeCodesResponse.class, httpHeaders);
@@ -279,8 +295,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerSaveQuoteInformationResponse
      */
     public ZiJinCrawlerSaveQuoteInfoResponse saveQuoteInformation(ZiJinCrawlerSaveQuoteInfoRequest ziJinCrawlerSaveQuoteInfoRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerSaveQuoteInfoRequest);
         log.info("ziJinCrawlerSaveQuoteInfoRequest请求参数的值:{}", jsonString);
@@ -288,7 +304,7 @@ public class ZiJinCrawlerRequestComponent {
         return responseEntity.getBody();
     }
 
-     /**
+    /**
      * 保存投保单
      *
      * @param ziJinCrawlerSaveProposalRequest
@@ -296,8 +312,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerSaveProposalResponse
      */
     public ZiJinCrawlerSaveProposalResponse saveProposal(ZiJinCrawlerSaveProposalRequest ziJinCrawlerSaveProposalRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerSaveProposalRequest);
         log.info("ziJinCrawlerSaveProposalRequest请求参数的值:{}", jsonString);
@@ -314,8 +330,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerUnderwritingResponse
      */
     public ZiJinCrawlerUnderwritingResponse underwriting(ZiJinCrawlerUnderwritingRequest ziJinCrawlerUnderwritingRequest, HttpHeaders headers) throws Exception {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerUnderwritingRequest);
         log.info("ziJinCrawlerUnderwritingRequest请求参数的值:{}", jsonString);
@@ -332,8 +348,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerImageUploadResponse
      */
     public ZiJinCrawlerImageUploadResponse uploadImage(ZiJinCrawlerImageUploadRequest ziJinCrawlerImageUploadRequest, HttpHeaders headers) throws Exception {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerImageUploadRequest);
         log.info("ziJinCrawlerImageUploadRequest请求参数的值:{}", jsonString);
@@ -348,8 +364,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerImageUploadResponse
      */
     public ZiJinCrawlerImageUploadInfoListResponse uploadImageInfoList(ZiJinCrawlerUploadImageInfoListRequest ziJinCrawlerUploadImageInfoListRequest, HttpHeaders headers) throws Exception {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerUploadImageInfoListRequest);
         log.info("ziJinCrawlerUploadImageInfoListRequest请求参数的值:{}", jsonString);
@@ -358,7 +374,6 @@ public class ZiJinCrawlerRequestComponent {
     }
 
 
-
     /**
      * 获取支付链接
      *
@@ -367,8 +382,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerPaymentLinkResponse
      */
     public ZiJinCrawlerPaymentLinkResponse payment(ZiJinCrawlerPaymentLinkRequest ziJinCrawlerPaymentLinkRequest, HttpHeaders headers) throws NotFoundException, IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerPaymentLinkRequest);
         log.info("ziJinCrawlerPaymentLinkRequest请求参数的值:{}", jsonString);
@@ -384,8 +399,8 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerQueryOrderStatusResponse
      */
     public ZiJinCrawlerQueryOrderStatusResponse queryOrderStatus(ZiJinCrawlerQueryOrderStatusRequest ziJinCrawlerQueryOrderStatusRequest, HttpHeaders headers) throws IOException {
-        //设置headers
-        HttpHeaders httpHeaders = setHttpHeaders();
+        String token = headers.getFirst("Authorization");
+        HttpHeaders httpHeaders = setHttpHeaders(token);
 
         String jsonString = JSON.toJSONString(ziJinCrawlerQueryOrderStatusRequest);
         log.info("ziJinCrawlerQueryOrderStatusRequest请求参数的值:{}", jsonString);
@@ -454,7 +469,7 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerQueryCarPolicyListResponse
      */
     public ZiJinCrawlerQueryCarPolicyListResponse queryCarPolicyList(ZiJinCrawlerQueryCarPolicyListRequest ziJinCrawlerQueryCarPolicyListRequest) throws IOException {
-        //设置headers
+        // 设置headers
         HttpHeaders httpHeaders = setHttpHeaders1();
 
         String jsonString = JSON.toJSONString(ziJinCrawlerQueryCarPolicyListRequest);
@@ -471,7 +486,7 @@ public class ZiJinCrawlerRequestComponent {
      * @return ZiJinCrawlerDownloadResponse
      */
     public ZiJinCrawlerDownloadPolicyResponse electronPolicyIODownload(ZiJinCrawlerDownloadPolicyRequest ziJinCrawlerDownloadPolicyRequest) throws IOException {
-        //设置headers
+        // 设置headers
         HttpHeaders httpHeaders = setHttpHeaders();
 
         String jsonString = JSON.toJSONString(ziJinCrawlerDownloadPolicyRequest);
@@ -501,17 +516,18 @@ public class ZiJinCrawlerRequestComponent {
      * @throws IOException
      */
     @NotNull
-    private static HttpHeaders setHttpHeaders() throws IOException {
+    private HttpHeaders setHttpHeaders() throws IOException {
+        return setHttpHeaders(null);
+    }
+
+    @NotNull
+    private HttpHeaders setHttpHeaders(String token) throws IOException {
         HttpHeaders headers = new HttpHeaders();
-        //headers.setContentType(MediaType.APPLICATION_JSON);
-        //获取cookie的值
         String cookie = getCookie();
         headers.set("Cookie", "csrfToken=CrwLEmSfUevAb8PcN7kXff-I; "+ cookie);
-        // 设置请求头
         headers.set("Accept", "application/json, text/plain, */*, application/problem+json");
-        //headers.set("Accept-Encoding", "gzip, deflate, br, zstd");
+        // headers.set("Accept-Encoding", "gzip, deflate, br, zstd");
         headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Authorization", "FD385C99C9B35B64EF84590AACA95380");
         headers.set("Cache-Control", "no-cache");
         headers.set("Connection", "keep-alive");
         headers.set("Content-Length", "23");
@@ -524,17 +540,31 @@ public class ZiJinCrawlerRequestComponent {
         headers.set("Sec-Fetch-Site", "same-origin");
         headers.set("Tokentype", "DEFULT");
         headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
-        headers.set("loginToken", "FD385C99C9B35B64EF84590AACA95380");
         headers.set("sec-ch-ua", "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"");
         headers.set("sec-ch-ua-mobile", "?0");
         headers.set("sec-ch-ua-platform", "\"Windows\"");
         headers.set("x-csrf-token", "CrwLEmSfUevAb8PcN7kXff-I");
         headers.set("content-type", "application/json");
+        if (StringUtils.hasText(token)) {
+            headers.set("Authorization", token);
+            headers.set("loginToken", token);
+        }
         return headers;
     }
 
+    /**
+     * 构建带有authorization的HTTP请求头
+     *
+     * @param authorization 认证token
+     * @return HttpHeaders
+     * @throws IOException
+     */
+    public HttpHeaders buildHttpHeaders(String authorization) throws IOException {
+        return setHttpHeaders(authorization);
+    }
+
     @NotNull
-    private HttpHeaders setHttpHeaders1(){
+    private HttpHeaders setHttpHeaders1() {
         try {
             HttpHeaders headers = new HttpHeaders();
             String cookie = getCookie();

+ 10 - 0
tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/entity/ZiJinCrawlerConfigureParameters.java

@@ -36,4 +36,14 @@ public class ZiJinCrawlerConfigureParameters implements ConfigureParameters {
      */
     private String comName;
 
+    /**
+     * 代理code
+     */
+    private String 代理code;
+
+    /**
+     * 渠道: 比如 个人代理
+     */
+    private String businessNatureName;
+
 }

+ 40 - 66
tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/service/impl/ZiJinCrawlerQuoteProcessServiceImpl.java

@@ -10,19 +10,18 @@ import com.jzg.quotation.zijin.crawler.entity.ZiJinCrawlerTokenInfo;
 import com.jzg.quotation.zijin.crawler.entity.request.ZiJinCrawlerCheckCaptchaRequest;
 import com.jzg.quotation.zijin.crawler.entity.request.ZiJinCrawlerCheckTokenRequest;
 import com.jzg.quotation.zijin.crawler.entity.request.ZiJinCrawlerCommonLoginRequest;
-import com.jzg.quotation.zijin.crawler.entity.response.*;
+import com.jzg.quotation.zijin.crawler.entity.response.ZiJinCrawlerCaptchaResponse;
+import com.jzg.quotation.zijin.crawler.entity.response.ZiJinCrawlerCheckCaptchaResponse;
+import com.jzg.quotation.zijin.crawler.entity.response.ZiJinCrawlerCheckTokenResponse;
+import com.jzg.quotation.zijin.crawler.entity.response.ZiJinCrawlerCommonLoginResponse;
 import com.jzg.quotation.zijin.crawler.service.ZiJinCrawlerQuoteProcessService;
-import com.jzg.quotation.zijin.crawler.util.DESEncryption;
-import com.jzg.quotation.zijin.crawler.util.RS6Item;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.http.HttpHeaders;
-import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 
 import java.io.IOException;
-import java.util.Map;
-import java.util.Optional;
 
 /**
  * @description: 紫金财险爬虫报价流程服务实现
@@ -68,15 +67,7 @@ public class ZiJinCrawlerQuoteProcessServiceImpl implements ZiJinCrawlerQuotePro
 
             // 3、校验验证码并获取token
             ZiJinCrawlerCheckCaptchaRequest ziJinCrawlerCheckCaptchaRequest = new ZiJinCrawlerCheckCaptchaRequest();
-            ZiJinCrawlerTokenInfo ziJinCrawlerTokenInfo = new ZiJinCrawlerTokenInfo();
-            ziJinCrawlerTokenInfo.setLoginComCode(ziJinCrawlerCommonLoginResponse.getData().getData().get(0).getComCode());
-            ziJinCrawlerTokenInfo.setUserCode(username);
-            ZiJinCrawlerTokenInfo.Attach attach = new ZiJinCrawlerTokenInfo.Attach();
-            attach.setLoginComName(ziJinCrawlerCommonLoginResponse.getData().getData().get(0).getComName());
-            attach.setLoginflag("common");
-            attach.setUserCode(username);
-            attach.setAccountType("9");
-            ziJinCrawlerTokenInfo.setAttach(attach);
+            ZiJinCrawlerTokenInfo ziJinCrawlerTokenInfo = getZiJinCrawlerTokenInfo(username, ziJinCrawlerCommonLoginResponse);
             log.info("ziJinCrawlerTokenInfo对象的值:{}", JSON.toJSONString(ziJinCrawlerTokenInfo));
             // 设置校验验证码请求参数
             ziJinCrawlerCheckCaptchaRequest.setTokenInfo(JSON.toJSONString(ziJinCrawlerTokenInfo));
@@ -91,23 +82,23 @@ public class ZiJinCrawlerQuoteProcessServiceImpl implements ZiJinCrawlerQuotePro
                 // 获取token
                 String token = ziJinCrawlerCheckCaptchaResponse.getData().getToken();
                 // 4、校验token
-                ZiJinCrawlerCheckTokenRequest ziJinCrawlerCheckTokenRequest = new ZiJinCrawlerCheckTokenRequest();
-                ziJinCrawlerCheckTokenRequest.setExpirationType("H5");
-                ziJinCrawlerCheckTokenRequest.setToken(token);
-
-                ZiJinCrawlerCheckTokenResponse ziJinCrawlerCheckTokenResponse = ziJinCrawlerRequestComponent.checkToken(ziJinCrawlerCheckTokenRequest, token);
-                log.info("校验token响应:{}", JSON.toJSONString(ziJinCrawlerCheckTokenResponse));
-                if (ziJinCrawlerCheckTokenResponse.isValid()) {
-                    String userName = ziJinCrawlerCheckTokenResponse.getUserName();
-                    String tokenInfo = ziJinCrawlerCheckTokenResponse.getTokenInfo();
-                    ZiJinCrawlerTokenInfo ziJinCrawlerTokenInfo1 = JSON.parseObject(tokenInfo, ZiJinCrawlerTokenInfo.class);
-                    String userCode = ziJinCrawlerTokenInfo1.getUserCode();
-                    // 缓存用户信息
-                    return ziJinCrawlerCacheComponent.cacheAuthorization(token, userCode, userName);
-                } else {
-                    log.error("校验token失败,响应:{}", JSON.toJSONString(ziJinCrawlerCheckTokenResponse));
-                    return ziJinCrawlerCheckTokenResponse.getMsg();
-                }
+                // ZiJinCrawlerCheckTokenRequest ziJinCrawlerCheckTokenRequest = new ZiJinCrawlerCheckTokenRequest();
+                // ziJinCrawlerCheckTokenRequest.setExpirationType("H5");
+                // ziJinCrawlerCheckTokenRequest.setToken(token);
+                // ZiJinCrawlerCheckTokenResponse ziJinCrawlerCheckTokenResponse = ziJinCrawlerRequestComponent.checkToken(ziJinCrawlerCheckTokenRequest, token);
+                // log.info("校验token响应:{}", JSON.toJSONString(ziJinCrawlerCheckTokenResponse));
+                // if (ziJinCrawlerCheckTokenResponse.isValid()) {
+                //     String userName = ziJinCrawlerCheckTokenResponse.getUserName();
+                //     String tokenInfo = ziJinCrawlerCheckTokenResponse.getTokenInfo();
+                //     ZiJinCrawlerTokenInfo ziJinCrawlerTokenInfo1 = JSON.parseObject(tokenInfo, ZiJinCrawlerTokenInfo.class);
+                //     String userCode = ziJinCrawlerTokenInfo1.getUserCode();
+                //     // 缓存用户信息
+                //     return ziJinCrawlerCacheComponent.cacheAuthorization(token, userCode, userName);
+                // } else {
+                //     log.error("校验token失败,响应:{}", JSON.toJSONString(ziJinCrawlerCheckTokenResponse));
+                //     return ziJinCrawlerCheckTokenResponse.getMsg();
+                // }
+                return ziJinCrawlerCacheComponent.cacheAuthorization(token, username, "");
             } else {
                 log.error("校验验证码并获取token失败,响应:{}", JSON.toJSONString(ziJinCrawlerCheckCaptchaResponse));
                 throw new SystemException("校验验证码并获取token失败");
@@ -118,6 +109,20 @@ public class ZiJinCrawlerQuoteProcessServiceImpl implements ZiJinCrawlerQuotePro
         }
     }
 
+    @NotNull
+    private ZiJinCrawlerTokenInfo getZiJinCrawlerTokenInfo(String username, ZiJinCrawlerCommonLoginResponse ziJinCrawlerCommonLoginResponse) {
+        ZiJinCrawlerTokenInfo ziJinCrawlerTokenInfo = new ZiJinCrawlerTokenInfo();
+        ziJinCrawlerTokenInfo.setLoginComCode(ziJinCrawlerCommonLoginResponse.getData().getData().get(0).getComCode());
+        ziJinCrawlerTokenInfo.setUserCode(username);
+        ZiJinCrawlerTokenInfo.Attach attach = new ZiJinCrawlerTokenInfo.Attach();
+        attach.setLoginComName(ziJinCrawlerCommonLoginResponse.getData().getData().get(0).getComName());
+        attach.setLoginflag("common");
+        attach.setUserCode(username);
+        attach.setAccountType("9");
+        ziJinCrawlerTokenInfo.setAttach(attach);
+        return ziJinCrawlerTokenInfo;
+    }
+
     /**
      * 获取请求头
      *
@@ -129,47 +134,16 @@ public class ZiJinCrawlerQuoteProcessServiceImpl implements ZiJinCrawlerQuotePro
     public HttpHeaders getHeaders(String username, String password) {
         String authorization = ziJinCrawlerCacheComponent.getAuthorization(username);
         log.info("authorization:{}", authorization);
-        /*if (authorization == null) {
+        if (authorization == null) {
             login(username, password);
             authorization = ziJinCrawlerCacheComponent.getAuthorization(username);
-        }*/
+        }
 
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("Authorization", authorization);
-        headers.set("Content-Type", "application/json");
-        headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
-        // 获取cookie的值
-        String cookie = null;
         try {
-            cookie = ZiJinCrawlerRequestComponent.getCookie();
+            return ziJinCrawlerRequestComponent.buildHttpHeaders(authorization);
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
-        headers.set("Cookie", "csrfToken=CrwLEmSfUevAb8PcN7kXff-I; " + cookie);
-        // 设置请求头
-        headers.set("Accept", "application/json, text/plain, */*, application/problem+json");
-        // headers.set("Accept-Encoding", "gzip, deflate, br, zstd");
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Authorization", "1B0234C742C12BD632AA5027EB3A501A");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Content-Length", "23");
-        headers.set("Host", "kscd.zking.com");
-        headers.set("Origin", "https://kscd.zking.com");
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", "https://kscd.zking.com/app/vehicle/proposal/commonlogin");
-        headers.set("Sec-Fetch-Dest", "empty");
-        headers.set("Sec-Fetch-Mode", "cors");
-        headers.set("Sec-Fetch-Site", "same-origin");
-        headers.set("Tokentype", "DEFULT");
-        // headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
-        headers.set("loginToken", "1B0234C742C12BD632AA5027EB3A501A");
-        headers.set("sec-ch-ua", "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"");
-        headers.set("sec-ch-ua-mobile", "?0");
-        headers.set("sec-ch-ua-platform", "\"Windows\"");
-        headers.set("x-csrf-token", "CrwLEmSfUevAb8PcN7kXff-I");
-
-        return headers;
     }
 
     @Override

+ 33 - 44
tenant/insurance/quotation-zijin/src/main/java/com/jzg/quotation/zijin/crawler/service/impl/ZiJinCrawlerRequestImpl.java

@@ -118,25 +118,15 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
         HttpHeaders headers = ziJinCrawlerQuoteProcessService.getHeaders(attributionInformationVo.getUsername(), attributionInformationVo.getPassword());
         log.info("headers参数的值:{}", JSON.toJSONString(headers));
 
-        String authorization = "FD385C99C9B35B64EF84590AACA95380";
-        headers.set("Authorization", authorization);
-
-        String userCode = ziJinCrawlerCacheComponent.getUserCode(attributionInformationVo.getUsername());
-        log.info("userCode:{}", userCode);
-
         // 1.1 解析配置参数
         ZiJinCrawlerConfigureParameters parameters = ConfigureParametersConversion.conversionEntity(ZiJinCrawlerConfigureParameters.class, attributionInformationVo.getConfigInfo());
         log.info("parameters参数的值:{}", JSON.toJSONString(parameters));
 
-        // 1.2 构建归属信息(机构、套餐、渠道信息)
-        // ProposalInfoDTO proposalInfoDTO = boHaiBuildingAttributionInformation.build(parameters, userCode, headers);
         try {
-            String comCode = "2140000601";
-
             // 查询车辆信息
             ZiJinCrawlerCarRequest ziJinCrawlerCarRequest = new ZiJinCrawlerCarRequest();
             ziJinCrawlerCarRequest.setChannelCode("KSCD_PROD");
-            ziJinCrawlerCarRequest.setComCode(comCode);
+            ziJinCrawlerCarRequest.setComCode(parameters.getComCode());
             ziJinCrawlerCarRequest.setLicenseNo(quoteVo.getCarInfoVo().getLicenseNo());
             ziJinCrawlerCarRequest.setVin(quoteVo.getCarInfoVo().getVinNo());
             ZiJinCrawlerCarResponse ziJinCrawlerCarResponse = ziJinCrawlerRequestComponent.car(ziJinCrawlerCarRequest, headers);
@@ -159,7 +149,7 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
             //  报价接口
             ZiJinCrawlerQuotationRequest ziJinCrawlerQuotationRequest = new ZiJinCrawlerQuotationRequest();
             // 设置基础信息
-            ziJinCrawlerQuotationRequest.setBaseInfo(buildBaseInfo(quoteVo));
+            ziJinCrawlerQuotationRequest.setBaseInfo(buildBaseInfo(quoteVo, parameters, attributionInformationVo.getUsername()));
 
             // 设置车主信息
             ZiJinCrawlerQuotationRequest.CarOwnerInfo carOwnerInfo = buildCarOwnerInfo(quoteVo);
@@ -183,8 +173,7 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
             ziJinCrawlerQuotationRequest.setProductName("机动车保险");
 
             // 设置减税信息
-            ziJinCrawlerQuotationRequest.setTaxRelifInfo(buildTaxRelifInfo(quoteVo, carOwnerInfo, comCode, headers));
-
+            ziJinCrawlerQuotationRequest.setTaxRelifInfo(buildTaxRelifInfo(quoteVo, carOwnerInfo, parameters.getComCode(), headers));
             // 设置险种列表
             Double actualValue = null;
             if (ziJinCrawlerGetCarActualValueResponse != null && StringUtils.hasLength(ziJinCrawlerGetCarActualValueResponse.getActualValue())) {
@@ -209,9 +198,9 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
             // 设置车载设备保费列表(空列表)
             ziJinCrawlerQuotationRequest.setCarDevicePremiumDtoList(new ArrayList<>());
             // 报价
-            log.info("ziJinCrawlerQuotationRequest参数的值:{}", JSON.toJSONString(ziJinCrawlerQuotationRequest));
+            log.info("ziJinCrawlerQuotationRequest参数:{}", JSON.toJSONString(ziJinCrawlerQuotationRequest));
             ZiJinCrawlerQuotationResponse ziJinCrawlerQuotationResponse = ziJinCrawlerRequestComponent.quotation(ziJinCrawlerQuotationRequest, headers);
-            log.info("ziJinCrawlerQuotationResponse参数的值:{}", JSON.toJSONString(ziJinCrawlerQuotationResponse));
+            log.info("ziJinCrawlerQuotationResponse响应:{}", JSON.toJSONString(ziJinCrawlerQuotationResponse));
             // 设置紫金财险爬虫订单号,后面保存到数据库中
             ziJinCrawlerQuotationResponse.setOrderNo(ziJinCrawlerGetOrderNoResponse.getOrderNo());
 
@@ -227,7 +216,7 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
                     // 修改交强险起保时间
                     ziJinCrawlerQuotationRequest.getBaseInfo().setStartDateCI(TimeProcessing.dateTimeToDate(startTime));
                     ziJinCrawlerQuotationRequest.getBaseInfo().setEndDateCI(TimeProcessing.minusDays(TimeProcessing.dateTimeToDate(newEndTime), 1));
-                    log.info("紫金报价重复投保参数: startTime={}, endTime={}", startTime, newEndTime);
+                    log.info("紫金报价重复投保日期参数: startTime={}, endTime={}", startTime, newEndTime);
                     ziJinCrawlerQuotationResponse = ziJinCrawlerRequestComponent.quotation(ziJinCrawlerQuotationRequest, headers);
                     log.info("紫金报价重复投保后响应: {}", JSON.toJSONString(ziJinCrawlerQuotationResponse));
                 }
@@ -237,11 +226,12 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
             if (ResponseCode.R_0001.getCode().equals(ziJinCrawlerQuotationResponse.getRtnCode())) {
                 if (ziJinCrawlerQuotationResponse.getBasePart() != null) {
                     ZiJinCrawlerRecognizeCodesResponse ziJinCrawlerRecognizeCodesResponse = ziJinCrawlerRequestComponent.recognizeCodes(ZiJinCrawlerQuoteRequestBuild.buildRecognizeCodesRequests(ziJinCrawlerQuotationResponse), headers);
-                    log.info("ziJinCrawlerRecognizeCodesResponse参数的值:{}", JSON.toJSONString(ziJinCrawlerRecognizeCodesResponse));
+                    log.info("ziJinCrawlerRecognizeCodesResponse响应:{}", JSON.toJSONString(ziJinCrawlerRecognizeCodesResponse));
                     // 验证码通过后再次调用报价接口
                     if (ResponseCode.R_0000.getCode().equals(ziJinCrawlerRecognizeCodesResponse.getRtnCode())) {
                         ZiJinCrawlerQuoteRequestBuild.buildReQuote(ziJinCrawlerQuotationResponse, ziJinCrawlerRecognizeCodesResponse, ziJinCrawlerQuotationRequest);
                         ziJinCrawlerQuotationResponse = ziJinCrawlerRequestComponent.quotation(ziJinCrawlerQuotationRequest, headers);
+                        log.info("紫金报价转保标志ziJinCrawlerQuotationResponse响应: {}", JSON.toJSONString(ziJinCrawlerQuotationResponse));
                     }
                 }
             }
@@ -254,9 +244,9 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
                 // 报价成功
                 // 保存报价信息
                 ZiJinCrawlerSaveQuoteInfoRequest ziJinCrawlerSaveQuoteInfoRequest = ziJinCrawlerSaveQuoteInfoRequestBuild.buildSaveQuoteInfoRequest(quoteVo, ziJinCrawlerQuotationRequest, ziJinCrawlerQuotationResponse);
-                log.info("ziJinCrawlerSaveQuoteInfoRequest参数的值:{}", JSON.toJSONString(ziJinCrawlerSaveQuoteInfoRequest));
+                log.info("ziJinCrawlerSaveQuoteInfoRequest参数:{}", JSON.toJSONString(ziJinCrawlerSaveQuoteInfoRequest));
                 ZiJinCrawlerSaveQuoteInfoResponse ziJinCrawlerSaveQuoteInfoResponse = ziJinCrawlerRequestComponent.saveQuoteInformation(ziJinCrawlerSaveQuoteInfoRequest, headers);
-                log.info("ziJinCrawlerSaveQuoteInfoResponse参数的值:{}", JSON.toJSONString(ziJinCrawlerSaveQuoteInfoResponse));
+                log.info("ziJinCrawlerSaveQuoteInfoResponse响应:{}", JSON.toJSONString(ziJinCrawlerSaveQuoteInfoResponse));
 
                 // 保存投保单
                 ZiJinCrawlerSaveProposalRequest ziJinCrawlerSaveProposalRequest = new ZiJinCrawlerSaveProposalRequest();
@@ -803,16 +793,16 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
      *
      * @return ZiJinCrawlerQuotationRequest.BaseInfo
      */
-    private ZiJinCrawlerQuotationRequest.BaseInfo buildBaseInfo(QuoteVo quoteVo) {
+    private ZiJinCrawlerQuotationRequest.BaseInfo buildBaseInfo(QuoteVo quoteVo, ZiJinCrawlerConfigureParameters parameters, String operatorCode) {
         ZiJinCrawlerQuotationRequest.BaseInfo baseInfo = new ZiJinCrawlerQuotationRequest.BaseInfo();
-        baseInfo.setAgentCode("P214001000622");
-        baseInfo.setAgreementNo("P214001000622-01");
+        baseInfo.setAgentCode(parameters.get代理code());
+        baseInfo.setAgreementNo(parameters.get代理code()+"-01");
         baseInfo.setBusinessNature("1");
-        baseInfo.setComCode("2140105601");
-        baseInfo.setComName("山西太原综改支公司直销业务一部");
-        baseInfo.setHandler1Code("214003263");
-        baseInfo.setHandler1Name("程涛");
-        baseInfo.setOperatorCode("214003053");
+        baseInfo.setComCode(parameters.getComCode());
+        baseInfo.setComName(parameters.getComName());
+        baseInfo.setHandler1Code(parameters.getUserCode());
+        baseInfo.setHandler1Name(parameters.getUserName());
+        baseInfo.setOperatorCode(operatorCode);
         baseInfo.setStartDate(quoteVo.getCiRiskInfoVo().getStartDate().substring(0, 10));
         baseInfo.setStartDateCI(quoteVo.getCiRiskInfoVo().getStartDate().substring(0, 10));
         baseInfo.setStartHour("00");
@@ -829,13 +819,13 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
         baseInfo.setXbFlag(quoteVo.getBiRiskInfoVo() == null ? "0" : null);
         baseInfo.setXbFlagCI(quoteVo.getBiRiskInfoVo() == null ? "0" : null);
         baseInfo.setGoalInsuredFlag("0");
-        baseInfo.setConfigName("天勤");
+        baseInfo.setConfigName("");
         baseInfo.setId("1dec8ac2ced74973842a5b098669813a");
-        baseInfo.setBusinessName("个人代理");
+        baseInfo.setBusinessName(parameters.getBusinessNatureName());
         baseInfo.setIdMongo("1dec8ac2ced74973842a5b098669813a");
-        baseInfo.setDesc("天勤");
-        baseInfo.setHanderCode("214003263");
-        baseInfo.setHanderName("程涛");
+        baseInfo.setDesc("");
+        baseInfo.setHanderCode(parameters.getUserCode());
+        baseInfo.setHanderName(parameters.getUserName());
         baseInfo.setDemandNo("01ZKIC140026051910017933666448");
         if (quoteVo.getBiRiskInfoVo() != null) {
             baseInfo.setDemandNoCI("01ZKIC140026051910017933666448");// 这个先放开看行不
@@ -1086,8 +1076,7 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
      * @param headers      请求头
      * @return ZiJinCrawlerQuotationRequest.TaxRelifInfo
      */
-    private ZiJinCrawlerQuotationRequest.TaxRelifInfo buildTaxRelifInfo(QuoteVo quoteVo,
-                                                                        ZiJinCrawlerQuotationRequest.CarOwnerInfo carOwnerInfo, String comCode, HttpHeaders headers) {
+    private ZiJinCrawlerQuotationRequest.TaxRelifInfo buildTaxRelifInfo(QuoteVo quoteVo, ZiJinCrawlerQuotationRequest.CarOwnerInfo carOwnerInfo, String comCode, HttpHeaders headers) {
         ZiJinCrawlerQuotationRequest.TaxRelifInfo taxRelifInfo = new ZiJinCrawlerQuotationRequest.TaxRelifInfo();
 
         // 只有投保交强险时才构建完整的税务信息
@@ -1095,15 +1084,15 @@ public class ZiJinCrawlerRequestImpl implements ZiJinCrawlerRequest {
         CarInfoVo carInfoVo = quoteVo.getCarInfoVo();
 
         // 获取税务登记证号
-        String taxRegistryNumber = "91140100MAE72CK690";
-        // try {
-        //     ZiJinCrawlerGetTaxRegistryNumberResponse taxResponse = ziJinCrawlerRequestComponent.getTaxRegistryNumber(comCode, headers);
-        //     if (taxResponse != null && StringUtils.hasLength(taxResponse.getData())) {
-        //         taxRegistryNumber = taxResponse.getData();
-        //     }
-        // } catch (Exception e) {
-        //     log.error("获取税务登记证号失败: {}", e.getMessage());
-        // }
+        String taxRegistryNumber = "";
+        try {
+            ZiJinCrawlerGetTaxRegistryNumberResponse taxResponse = ziJinCrawlerRequestComponent.getTaxRegistryNumber(comCode, headers);
+            if (taxResponse != null && StringUtils.hasLength(taxResponse.getData())) {
+                taxRegistryNumber = taxResponse.getData();
+            }
+        } catch (Exception e) {
+            log.error("获取税务登记证号失败: {}", e.getMessage());
+        }
 
         // 设置税务信息
         taxRelifInfo.setTaxRelifFlag(quoteVo.getVehicleAndVesselTaxVo().getTaxRelifFlag());// 1 正常缴税 2 免税 3 减税 4 已完税 5 不征

+ 18 - 0
tenant/organization/src/main/java/com/jzg/organization/controller/ReceivableController.java

@@ -663,6 +663,24 @@ public class ReceivableController {
         return receivableService.companySuperviseSettlementReport(settlementReportQueryVo);
     }
 
+    /**
+     * 设置指定发票为结算完成状态
+     *
+     * @param invoiceId 发票序号
+     * @author lipf
+     * @date 2026/6/11 9:33
+     */
+    @PostMapping("updateSettlementStatus")
+    public HttpResult<String> updateSettlementStatus(@RequestParam("id") String invoiceId){
+        int count = receivableService.updateSettlementStatus(invoiceId);
+        if(count>0){
+            return HttpResult.ok("订单被标记为结算完成");
+        }else{
+            // 可能已经被其他人标记为结算完成
+            return HttpResult.ok("操作完成");
+        }
+    }
+
     @PostMapping("receiverSettlementReport")
     @Operation(summary = "对接人结算报表")
     public HttpResult<Page<ReceiverSettlementReportDto>> receiverSettlementReport(@RequestBody SettlementReportQueryVo settlementReportQueryVo) {

+ 8 - 8
tenant/organization/src/main/java/com/jzg/organization/mapper/ReceivableMapper.java

@@ -3,10 +3,7 @@ package com.jzg.organization.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jzg.commons.entity.finance.dto.*;
-import com.jzg.commons.entity.finance.po.InsPlyIncome;
-import com.jzg.commons.entity.finance.po.InsPlyIncomeInvoiceLink;
-import com.jzg.commons.entity.finance.po.InsPlyIncomeInvoiceSettlement;
-import com.jzg.commons.entity.finance.po.InsPlyIncomeVO;
+import com.jzg.commons.entity.finance.po.*;
 import com.jzg.commons.entity.finance.vo.InvoiceRecordQueryVo;
 import com.jzg.commons.entity.finance.vo.InvoiceRecordResult;
 import com.jzg.commons.entity.finance.vo.InvoicingVo;
@@ -211,7 +208,8 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
      * @author lipf
      * @date 2026/6/1 16:57
      */
-    List<CompanySuperviseSettlementReportDto> queryCumulativeAccountsReceivable(@Param("systemCode") String systemCode);
+    List<CompanySuperviseSettlementReportDto> queryCumulativeAccountsReceivable(@Param("systemCode") String systemCode,
+                                                                                @Param("vo") SettlementReportQueryVo vo);
 
     /**
      * 查询已开票金额
@@ -226,7 +224,8 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
     List<CalRes> queryInvoicedAmount(@Param("systemCode") String systemCode,
                                      @Param("invoiceType") List<Integer> invoiceType,
                                      @Param("invoiceStartDate") String invoiceStartDate,
-                                     @Param("invoiceEndDate") String invoiceEndDate);
+                                     @Param("invoiceEndDate") String invoiceEndDate,
+                                     @Param("agreementType") String agreementType);
 
     /**
      * 查询已结算金额
@@ -235,8 +234,9 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
      * @return List<CalRes>
      */
     List<CalRes> querySettledAmount(@Param("systemCode") String systemCode,
-                                               @Param("settledStartDate") String settledStartDate,
-                                               @Param("settledEndDate") String settledEndDate);
+                                    @Param("settledStartDate") String settledStartDate,
+                                    @Param("settledEndDate") String settledEndDate,
+                                    @Param("agreementType") String agreementType);
 
     /**
      * 查询

+ 9 - 0
tenant/organization/src/main/java/com/jzg/organization/service/ReceivableService.java

@@ -457,4 +457,13 @@ public interface ReceivableService extends IService<InsPlyIncome> {
      * @return
      */
     Page<ExportSuperviseSettlementExcelDto> followSettlementExcel(Page page, SettlementQueryVo settlementQueryVo);
+
+    /**
+     * 设置指定发票为结算完成状态
+     *
+     * @param invoiceId 发票序号
+     * @author lipf
+     * @date 2026/6/11 9:33
+     */
+    int updateSettlementStatus(String invoiceId);
 }

+ 10 - 8
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlAgreementCostServiceImpl.java

@@ -26,10 +26,7 @@ import com.jzg.commons.util.idgen.IdGenerate;
 import com.jzg.organization.mapper.PtlAgreementCostMapper;
 import com.jzg.organization.mapper.PtlAgreementCostRuleMapper;
 import com.jzg.organization.mapper.PtlAgreementCostTypeMapper;
-import com.jzg.organization.service.PtlAgreementCostRuleService;
-import com.jzg.organization.service.PtlAgreementCostService;
-import com.jzg.organization.service.PtlAgreementCostTypeService;
-import com.jzg.organization.service.PtlAgreementUndwrtRulesAttrService;
+import com.jzg.organization.service.*;
 import com.jzg.organization.state.CostState;
 import com.jzg.organization.state.listener.CostStateListener;
 import io.seata.spring.annotation.GlobalTransactional;
@@ -89,6 +86,9 @@ public class PtlAgreementCostServiceImpl extends ServiceImpl<PtlAgreementCostMap
     @Autowired
     private PtlAgreementCostRuleMapper ptlAgreementCostRuleMapper;
 
+    @Autowired
+    private PtlAgreementService ptlAgreementService;
+
     /**
      * 报价配置/费用管理/新增费用
      *
@@ -117,6 +117,7 @@ public class PtlAgreementCostServiceImpl extends ServiceImpl<PtlAgreementCostMap
                     log.info("新增费用的时候,需要指定关联的协议信息。ptlAgreemenCostAdd=[{}]", JSONUtil.toJsonStr(ptlAgreemenCostAdd));
                     return HttpResult.error("新增费用的时候,需要指定关联的协议信息");
                 }
+                PtlAgreement agreement = ptlAgreementService.getById(costAdd.getPtlAgreementId());
                 ptlAgreementCostAdd.setPtlAgreementId(costAdd.getPtlAgreementId());
                 ptlAgreementCostAdd.setProductId(costAdd.getProductId());
                 ptlAgreementCostAdd.setProductName(costAdd.getProductName());
@@ -127,7 +128,7 @@ public class PtlAgreementCostServiceImpl extends ServiceImpl<PtlAgreementCostMap
 //                    throw new SystemException("已存在相同且有效的费用规则,请检查");
 //                }
                 PtlAgreementCost ptlAgreementCost = BeanUtil.copyProperties(ptlAgreementCostAdd, PtlAgreementCost.class);
-                ptlAgreementCost.setCostRules(buildDescString(ptlAgreementCostAdd.getRuleConditions()));
+                ptlAgreementCost.setCostRules(buildDescString(agreement.getCompanyId(), ptlAgreementCostAdd.getRuleConditions()));
                 if (baseMapper.insert(ptlAgreementCost) <= 0) {
                     log.error("插入操作失败");
                     throw new RuntimeException(ReturnInformation.FAILURE_MESSAGE);
@@ -167,10 +168,11 @@ public class PtlAgreementCostServiceImpl extends ServiceImpl<PtlAgreementCostMap
                 }
             }
         }
+        PtlAgreement agreement = ptlAgreementService.getById(ptlAgreementCostEnd.getPtlAgreementId());
         PtlAgreementCost ptlAgreementCost = BeanUtil.copyProperties(ptlAgreementCostEnd, PtlAgreementCost.class);
         // modify by lipf, 2026年5月22日11:28:34 设置为待审核状态
         ptlAgreementCost.setApproved(0);
-        ptlAgreementCost.setCostRules(buildDescString(ptlAgreementCostEnd.getRuleConditions()));
+        ptlAgreementCost.setCostRules(buildDescString(agreement.getCompanyId(), ptlAgreementCostEnd.getRuleConditions()));
         if (baseMapper.updateById(ptlAgreementCost) <= 0) {
             throw new RuntimeException(ReturnInformation.UPDATE_FAILED);
         }
@@ -196,11 +198,11 @@ public class PtlAgreementCostServiceImpl extends ServiceImpl<PtlAgreementCostMap
     }
 
 
-    public String buildDescString(List<?> list){
+    public String buildDescString(String companyId, List<?> list){
         if (CollectionUtils.isEmpty(list)){
             return null;
         }
-        return BuildDescUtils.buildDesc(BeanUtil.copyToList(list, PtlAgreementUndwrtRulesAttr.class), agreementUndwrtRulesAttrService.getUndwrtRulesAttrList());
+        return BuildDescUtils.buildDesc(companyId, BeanUtil.copyToList(list, PtlAgreementUndwrtRulesAttr.class), agreementUndwrtRulesAttrService.getUndwrtRulesAttrList());
     }
 
     public boolean verify(PtlAgreementCostAdd ptlAgreementCostAdd){

+ 2 - 2
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlAgreementUndwrtRulesServiceImpl.java

@@ -208,6 +208,7 @@ public class PtlAgreementUndwrtRulesServiceImpl extends ServiceImpl<PtlAgreement
         rules.setOrderNum(orderNum);
         rules.setStartTime(param.getStartTime());
         rules.setEndTime(param.getEndTime());
+        rules.setInputDescription(param.getInputDescription());
         rules.setAgreementId(param.getAgreementId());
         rules.setParentId(parentId);
         return rules;
@@ -356,8 +357,7 @@ public class PtlAgreementUndwrtRulesServiceImpl extends ServiceImpl<PtlAgreement
         // 获取费用因子属性(公共逻辑,仅查询一次)
         List<PtlAgreementUndwrtRulesAttr> attrList = agreementUndwrtRulesAttrService.getUndwrtRulesAttrList();
         Map<String, PtlAgreementUndwrtRulesAttr> attrMap = attrList.stream()
-                .filter(attr -> attr.getCompanyIds().equals("all"))
-                .filter(attr -> attr.getCompanyIds().equals(agreement.getCompanyId()))
+                .filter(attr -> "all".equals(attr.getCompanyIds()) || attr.getCompanyIds().equals(agreement.getCompanyId()))
                 .collect(Collectors.toMap(PtlAgreementUndwrtRulesAttr::getAttrCode, attr -> attr));
 
         // 确定需要处理的规则ID列表

+ 1 - 2
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlSchemeServiceImpl.java

@@ -302,8 +302,7 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
         // 获取费用因子属性
         List<PtlAgreementUndwrtRulesAttr> attrList = agreementUndwrtRulesAttrService.getUndwrtRulesAttrList(companyId);
         Map<String, PtlAgreementUndwrtRulesAttr> attrMap = attrList.stream()
-                .filter(attr -> attr.getCompanyIds().equals("all"))
-                .filter(attr -> attr.getCompanyIds().equals(companyId))
+                .filter(attr -> "all".equals(attr.getCompanyIds()) || attr.getCompanyIds().equals(companyId))
                 .collect(Collectors.toMap(PtlAgreementUndwrtRulesAttr::getAttrCode, attr -> attr));
         List<PtlAgreementUndwrtRulesAttr> newAttrList = new ArrayList<>();
         linkList.forEach(link -> {

+ 39 - 8
tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java

@@ -1458,6 +1458,16 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
     @Override
     public boolean deleteSettlementById(String id) {
         log.info("私有协议-结算记录-删除: id=[{}]",id);
+        InsPlyIncomeInvoiceSettlement settlement = insPlyIncomeInvoiceSettlementMapper.selectById(id);
+        if(settlement == null){
+            return false;
+        }
+        String invoiceId = settlement.getInvoiceId();
+        if(StrUtil.isNotEmpty(invoiceId)){
+            insPlyIncomeInvoiceMapper.update(null,
+                    new LambdaUpdateWrapper<InsPlyIncomeInvoice>()
+                            .set(InsPlyIncomeInvoice::getStatus,"0").eq(InsPlyIncomeInvoice::getId,invoiceId));
+        }
         return insPlyIncomeInvoiceSettlementMapper.deleteById(id) > 0;
     }
 
@@ -2786,7 +2796,8 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
      * @return
      */
     @Override
-    public void exportSuperviseSettlementExcel(Page page, SettlementQueryVo settlementQueryVo) {
+    public void exportSuperviseSettlementExcel(Page page, SettlementQueryVo settlementQueryVo) throws IOException {
+        verifyUploadPath();
         Page<ExportSuperviseSettlementExcelDto> exportSuperviseSettlementExcelDtos = baseMapper.superviseSettlementExcelDtos(page, settlementQueryVo);
         // 补充、转换列表的数据
         supplementaryListData(exportSuperviseSettlementExcelDtos);
@@ -2801,7 +2812,8 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
      * @return
      */
     @Override
-    public void exportPlatformSettlementExcel(Page page, SettlementQueryVo settlementQueryVo) {
+    public void exportPlatformSettlementExcel(Page page, SettlementQueryVo settlementQueryVo) throws IOException {
+        verifyUploadPath();
         Page<ExportSuperviseSettlementExcelDto> exportSuperviseSettlementExcelDtos = baseMapper.platformSettlementExcelDtos(page, settlementQueryVo);
         // 补充、转换列表的数据
         supplementaryListData(exportSuperviseSettlementExcelDtos);
@@ -3094,7 +3106,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         log.info("保险公司结算报表查询-区分租户并且按照日期进行过滤:systemCode=[{}],queryVo=[{}]", systemCode, JSONUtil.toJsonStr(queryVo));
         // 1. 先将有数据的保险公司进行汇总
         // 累计应收金额, 应收手续费(手续费), 应收手续费(手续费)
-        List<CompanySuperviseSettlementReportDto> allCompaniesIncomeDatas = baseMapper.queryCumulativeAccountsReceivable(systemCode);
+        List<CompanySuperviseSettlementReportDto> allCompaniesIncomeDatas = baseMapper.queryCumulativeAccountsReceivable(systemCode, queryVo);
         allCompaniesIncomeDatas = allCompaniesIncomeDatas.stream().peek(action-> action.setId(action.getCompanyId())).toList();
         // for test, 查看一家公司的数据,将不可变对象变成 可变对象
 //        allCompaniesIncomeDatas = allCompaniesIncomeDatas.stream().filter(action-> "1738811735599338932".equals(action.getCompanyId())).toList();
@@ -3102,7 +3114,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         if (CollUtil.isEmpty(allCompaniesIncomeDatas)) {
             String msg = String.format("租户%s下暂时没有手续费",systemCode);
             log.info(msg);
-            return HttpResult.error(msg, Collections.emptyList());
+            return HttpResult.ok(msg, Collections.emptyList());
         }
         // add by lipf, 前端保险公司的查询条件未生效,进行bug修复
         if(StrUtil.isNotEmpty(queryVo.getCompanyId())){
@@ -3153,15 +3165,15 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         // 已开票(跟单费)
 
         List<CalRes> calRes;
-        calRes = baseMapper.queryInvoicedAmount(systemCode, List.of(1, 3), queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime());
+        calRes = baseMapper.queryInvoicedAmount(systemCode, List.of(1, 3), queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime(), queryVo.getAgreementType());
         Map<String, BigDecimal> sxfInvoicedAmountMap = calRes.stream().collect(Collectors.toMap(CalRes::getCompanyId, CalRes::getAmount, (o1,o2)->o1));
-        calRes = baseMapper.queryInvoicedAmount(systemCode, List.of(2, 4), queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime());
+        calRes = baseMapper.queryInvoicedAmount(systemCode, List.of(2, 4), queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime(), queryVo.getAgreementType());
         Map<String, BigDecimal> gdfInvoicedAmountMap = calRes.stream().collect(Collectors.toMap(CalRes::getCompanyId, CalRes::getAmount, (o1,o2)->o1));
-        calRes = baseMapper.queryInvoicedAmount(systemCode, List.of(4, 6), queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime());
+        calRes = baseMapper.queryInvoicedAmount(systemCode, List.of(4, 6), queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime(), queryVo.getAgreementType());
         Map<String, BigDecimal> ptInvoicedAmountMap =calRes.stream().collect(Collectors.toMap(CalRes::getCompanyId, CalRes::getAmount, (o1,o2)->o1));
 
         // 结算费用 
-        calRes = baseMapper.querySettledAmount(systemCode, queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime());
+        calRes = baseMapper.querySettledAmount(systemCode, queryVo.getInvoiceStartTime(), queryVo.getInvoiceEndTime(), queryVo.getAgreementType());
         Map<String, BigDecimal> sxfSettledAmountMap = calRes.stream().collect(Collectors.toMap(CalRes::getCompanyId, CalRes::getSxfSettlement, (o1,o2)->o1));
         Map<String, BigDecimal> gdfSettledAmountMap = calRes.stream().collect(Collectors.toMap(CalRes::getCompanyId, CalRes::getGdfSettlement, (o1,o2)->o1));
 
@@ -3576,5 +3588,24 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         log.info(msg);
         return HttpResult.ok(msg);
     }
+
+    /**
+     * 设置指定发票为结算完成状态
+     *
+     * @param invoiceId 发票序号
+     * @author lipf
+     * @date 2026/6/11 9:33
+     */
+    @Override
+    public int updateSettlementStatus(String invoiceId) {
+        String userName = baseController.getUserName();
+        String systemCode = baseController.getSystemCode();
+        log.info("租户[{}]下的用户[{}]标识发票[{}]为结算完成", systemCode, userName, invoiceId);
+        return insPlyIncomeInvoiceMapper.update(null, new LambdaUpdateWrapper<InsPlyIncomeInvoice>()
+                .set(InsPlyIncomeInvoice::getStatus,"1")
+                .set(InsPlyIncomeInvoice::getUpdateBy,userName)
+                .set(InsPlyIncomeInvoice::getUpdateTime, LocalDateTime.now())
+                .eq(InsPlyIncomeInvoice::getId, invoiceId));
+    }
 }
 

+ 18 - 0
tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserAccountServiceImpl.java

@@ -1,7 +1,9 @@
 package com.jzg.organization.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.jzg.commons.core.base.BaseController;
@@ -25,6 +27,7 @@ import java.time.LocalDateTime;
 import java.time.YearMonth;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -263,6 +266,15 @@ public class SysUserAccountServiceImpl extends ServiceImpl<SysUserAccountMapper,
         
         List<SysAmountAuditing> sysAmountAuditingList = sysAmountAuditingService.list(auditingWrapper);
 
+        // 批量查询用户信息
+        Set<String> collect = sysAmountAuditingList.stream().map(SysAmountAuditing::getUserId).collect(Collectors.toSet());
+        QueryWrapper<SysUser> idSet = new QueryWrapper<SysUser>().in("id", collect);
+        List<SysUser> list = sysUserService.list(idSet);
+        Map<String, SysUser> userMap = new HashMap<>();
+        if (CollUtil.isNotEmpty(list)){
+            userMap = list.stream().collect(Collectors.toMap(SysUser::getId, Function.identity()));
+        }
+
         // 构建订单收入查询参数(手动设置时间,避免类型转换问题)
         InsFeeOrdersParam feeOrdersParam = new InsFeeOrdersParam();
         feeOrdersParam.setUserId(sysUserJzgInfo.getUserId());
@@ -295,6 +307,8 @@ public class SysUserAccountServiceImpl extends ServiceImpl<SysUserAccountMapper,
             item.setStatus(auditing.getAuditingStatus().toString());
             item.setRemark("提现支出");
             item.setBankName(auditing.getBankName());
+            item.setUserName(userMap.getOrDefault(auditing.getUserId(), new SysUser()).getUsername());
+            item.setBankCardNum(auditing.getBankCardNum());
             allRecords.add(item);
         }
         // 处理收入记录
@@ -307,6 +321,8 @@ public class SysUserAccountServiceImpl extends ServiceImpl<SysUserAccountMapper,
             item.setRemark("车险收入");
             item.setLicenseNo(insFeeOrders.getLicenseNo());
             item.setBankName(null);
+            item.setUserName(null);
+            item.setBankCardNum(null);
             allRecords.add(item);
         }
         // 处理分销收入记录
@@ -325,6 +341,8 @@ public class SysUserAccountServiceImpl extends ServiceImpl<SysUserAccountMapper,
             item.setRemark("分销收入");
             item.setLicenseNo(insFeeOrders.getLicenseNo());
             item.setBankName(null);
+            item.setUserName(null);
+            item.setBankCardNum(null);
             allRecords.add(item);
         }
         // 排序

+ 24 - 2
tenant/organization/src/main/resources/mapper/ReceivableMapper.xml

@@ -533,7 +533,6 @@
         ipii.invoice_type,
         ipii.invoice_party,
         ipii.tax_point,
-        ipi.order_no as orderNo,
         ipiis.actual_received_amount,
         ipii.receivable_supervise_premium,
         ipiis.receive_payment_date,
@@ -591,6 +590,19 @@
                 AND ipiis.create_time &lt;= date(#{settlementQueryVo.settlementDateEndStr})
             </if>
         </where>
+        group by
+        ipiis.id ,
+        ipiis.invoice_id,
+        io.company_id,
+        io.company_name,
+        ipii.invoice_type,
+        ipii.invoice_party,
+        ipii.tax_point,
+        ipiis.actual_received_amount,
+        ipii.receivable_supervise_premium,
+        ipiis.receive_payment_date,
+        ipiis.create_by ,
+        ipiis.create_time
         order by ipiis.create_time desc
     </select>
 
@@ -1612,6 +1624,9 @@
         <where>
             ipi.is_delete = 0
             and ipi.system_code = #{systemCode}
+            <if test="vo.agreementType != null and vo.agreementType != ''">
+                AND ipi.agreement_type = #{vo.agreementType}
+            </if>
         </where>
         group by
         eic.parent_id,
@@ -1647,7 +1662,11 @@
             ins_ply_income ipi
             where
             ipi.is_delete = 0
-            and ipi.system_code = #{systemCode})
+            and ipi.system_code = #{systemCode}
+            <if test="agreementType != null and agreementType != ''">
+                AND ipi.agreement_type = #{agreementType}
+            </if>
+            )
             <if test="invoiceType != null and !invoiceType.isEmpty()">
                 AND ipii.invoice_type IN
                 <foreach collection="invoiceType" item="item" open="(" separator="," close=")">
@@ -1694,6 +1713,9 @@
             <if test="settledEndDate != null and settledEndDate != ''">
                 AND ipi.settlement_time &lt; cast('${settledEndDate}' as datetime)
             </if>
+            <if test="agreementType != null and agreementType != ''">
+                AND ipi.agreement_type = #{agreementType}
+            </if>
         </where>
         ) a
         group by  a.company_id