Просмотр исходного кода

Merge branch 'master' into wrq_dev

wangrq 3 месяцев назад
Родитель
Сommit
9a4631daa7
29 измененных файлов с 1086 добавлено и 917 удалено
  1. 28 1
      .gitignore
  2. 9 4
      commons/src/main/java/com/jzg/commons/entity/account/po/SysUserAccountChangeLog.java
  3. 2 0
      commons/src/main/java/com/jzg/commons/entity/finance/dto/CompanySuperviseSettlementReportDto.java
  4. 8 0
      commons/src/main/java/com/jzg/commons/entity/finance/dto/ExportSuperviseSettlementExcelDto.java
  5. 6 0
      commons/src/main/java/com/jzg/commons/entity/finance/dto/ReceiverSettlementReportDto.java
  6. 6 0
      commons/src/main/java/com/jzg/commons/entity/finance/po/InsPlyIncomeInvoice.java
  7. 10 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/FollowCompanyFeeVO.java
  8. 6 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/FollowInvoiceAndSettlementVo.java
  9. 38 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/InsPlyIncomeInvoiceSettlementVo.java
  10. 3 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/SettlementQueryVo.java
  11. 6 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/SettlementReportQueryVo.java
  12. 3 3
      commons/src/main/java/com/jzg/commons/entity/vo/AmountAuditVo.java
  13. 6 0
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/service/impl/HuaNongCrawlerQuoteProcessServiceImpl.java
  14. 14 42
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/component/HtCrawlerRequestComponent.java
  15. 4 0
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/properties/HuaTaiCrawlerProperties.java
  16. 11 52
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java
  17. 377 600
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/util/CookieUtils.java
  18. 86 156
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/util/UploadUtil.java
  19. 50 22
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/aspect/QuoteVerificationAspect.java
  20. 9 0
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsFeeAuditServiceImpl.java
  21. 9 9
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsOrdersServiceImpl.java
  22. 30 8
      tenant/insurance/quotation-zhongan/src/main/java/com/jzg/quotation/zhongan/crawler/build/ZhongAnCrawlerQuoteResultsVoBuild.java
  23. 19 0
      tenant/organization/src/main/java/com/jzg/organization/controller/ReceivableController.java
  24. 17 7
      tenant/organization/src/main/java/com/jzg/organization/mapper/ReceivableMapper.java
  25. 7 0
      tenant/organization/src/main/java/com/jzg/organization/service/ReceivableService.java
  26. 104 6
      tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java
  27. 4 4
      tenant/organization/src/main/resources/mapper/InsFeeFollowOrderMapper.xml
  28. 213 2
      tenant/organization/src/main/resources/mapper/ReceivableMapper.xml
  29. 1 1
      tenant/organization/src/main/resources/mapper/SysAmountAuditingMapper.xml

+ 28 - 1
.gitignore

@@ -56,4 +56,31 @@ pdoc/
 /tenant/organization/dependency-reduced-pom.xml
 /commons/src/main/java/com/jzg/commons/JwtAuthenticationTokenFilter.java
 
-pdoc/
+pdoc/
+outPath/
+tenant/costsorrule/outPath/
+tenant/insurance/outPath/
+tenant/insurance/quotation-ansheng/outPath/
+tenant/insurance/quotation-bohai/outPath/
+tenant/insurance/quotation-chengtai/outPath/
+tenant/insurance/quotation-commons/outPath/
+tenant/insurance/quotation-dajia/outPath/
+tenant/insurance/quotation-guoren/outPath/
+tenant/insurance/quotation-guoshou/outPath/
+tenant/insurance/quotation-hengbang/outPath/
+tenant/insurance/quotation-huanong/outPath/
+tenant/insurance/quotation-huatai/outPath/
+tenant/insurance/quotation-renbao/outPath/
+tenant/insurance/quotation-renbaonew/outPath/
+tenant/insurance/quotation-shenneng/outPath/
+tenant/insurance/quotation-summary/outPath/
+tenant/insurance/quotation-taikang/outPath/
+tenant/insurance/quotation-taiping/outPath/
+tenant/insurance/quotation-taipingyang/outPath/
+tenant/insurance/quotation-taishan/outPath/
+tenant/insurance/quotation-yangguang/outPath/
+tenant/insurance/quotation-yongcheng/outPath/
+tenant/insurance/quotation-zhongan/outPath/
+tenant/insurance/quotation-zhongmei/outPath/
+tenant/insurance/quotation-zijin/outPath/
+tenant/organization/outPath/

+ 9 - 4
commons/src/main/java/com/jzg/commons/entity/account/po/SysUserAccountChangeLog.java

@@ -4,13 +4,13 @@ import com.alibaba.excel.annotation.ExcelIgnore;
 import com.alibaba.excel.annotation.ExcelProperty;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.jzg.commons.core.base.BaseModel;
+import com.jzg.commons.util.BigDecimalUtil;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serial;
 import java.math.BigDecimal;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -56,10 +56,15 @@ public class SysUserAccountChangeLog extends BaseModel {
     private BigDecimal amount;
 
 
+    // 在实际运行中,可能因为并发的问题,操作前的金额和操作后的金额,很难得到要给准确的值(如果非要得到金额,需要加锁,影响其他模块操作)
+    @ExcelProperty("余额")
+    @Schema(description = "余额")
+    private BigDecimal amountAfter;
 
-//    在实际运行中,可能因为并发的问题,操作前的金额和操作后的金额,很难得到要给准确的值(如果非要得到金额,需要加锁,影响其他模块操作)
-//    @Schema(description = "操作后的金额")
-//    private BigDecimal amountAfter;
+    // 这个方法不能删除。 文件导出的时候,要用
+    public BigDecimal getAmountAfter() {
+        return BigDecimalUtil.add(this.amountBefore, this.amount);
+    }
 
     /**
      * 操作类型 1 保单费用 2 保单提成 3. 提现

+ 2 - 0
commons/src/main/java/com/jzg/commons/entity/finance/dto/CompanySuperviseSettlementReportDto.java

@@ -20,6 +20,8 @@ public class CompanySuperviseSettlementReportDto implements Serializable {
     private static final long serialVersionUID = -2428003756552348627L;
     @Schema(description = "保险公司Id")
     private String companyId;
+    @Schema(description = "对接人手机号")
+    private String contactPersonPhone;
 
     @Schema(description = "保险公司Id")
     private String id;

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

@@ -61,6 +61,14 @@ public class ExportSuperviseSettlementExcelDto {
     @ExcelProperty(value = "应收金额")
     private String receivableSupervisePremium;
 
+    @Schema(description = "回款差额")
+    @ExcelProperty(value = "回款差额")
+    private String settlementPaymentDifference;
+
+    @Schema(description = "回款原因")
+    @ExcelProperty(value = "回款原因")
+    private String settlementPaymentReason;
+
     @Schema(description = "收款日期")
     @ExcelProperty(value = "收款日期")
     private String receivePaymentDate;

+ 6 - 0
commons/src/main/java/com/jzg/commons/entity/finance/dto/ReceiverSettlementReportDto.java

@@ -3,6 +3,9 @@ package com.jzg.commons.entity.finance.dto;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
+
+
 /**
  * @author quchen
  * @date 2025/9/16 10:32
@@ -52,4 +55,7 @@ public class ReceiverSettlementReportDto {
 
     @Schema(description = "已结算发票的发票号")
     private String invoiceIds;
+
+    @Schema(description = "回款差额")
+    private BigDecimal totalPaymentDifference;
 }

+ 6 - 0
commons/src/main/java/com/jzg/commons/entity/finance/po/InsPlyIncomeInvoice.java

@@ -61,4 +61,10 @@ public class InsPlyIncomeInvoice extends BaseModel {
 
     @Schema(description = "租户代码")
     private String systemCode;
+
+    @Schema(description = "对接人名称")
+    private String contactPerson;
+
+    @Schema(description = "对接人手机号")
+    private String contactPersonPhone;
 }

+ 10 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/FollowCompanyFeeVO.java

@@ -1,6 +1,7 @@
 package com.jzg.commons.entity.finance.vo;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -15,6 +16,12 @@ public class FollowCompanyFeeVO {
 
     private String companyId;
 
+    @Schema(description = "对接人")
+    private String contactPerson;
+
+    @Schema(description = "对接人手机号")
+    private String contactPersonPhone;
+
     /** 分支保险公司ID partner_company_id */
     private String partnerCompanyId;
 
@@ -30,6 +37,9 @@ public class FollowCompanyFeeVO {
     /** 未结算总金额  totalInvoiced - totalSettlementAmount */
     private BigDecimal totalUnSettlementAmount;
 
+    /** 回款差额 */
+    private BigDecimal totalPaymentDifference;
+
     /** 累计应收  totalReceivable */
     private BigDecimal totalReceivable;
 

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

@@ -34,6 +34,12 @@ public class FollowInvoiceAndSettlementVo {
     @Schema(description = "差额原因")
     private String paymentReason;
 
+    @Schema(description = "回款差额")
+    private BigDecimal settlementPaymentDifference;
+
+    @Schema(description = "回款原因")
+    private String settlementPaymentReason;
+
     @Schema(description = "附加id")
     private List<String> attachmentIds;
 }

+ 38 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/InsPlyIncomeInvoiceSettlementVo.java

@@ -0,0 +1,38 @@
+package com.jzg.commons.entity.finance.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.jzg.commons.core.base.BaseModel;
+import com.jzg.commons.util.idgen.IdGenerate;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Schema(description = "财务应收开票结算表")
+public class InsPlyIncomeInvoiceSettlementVo extends BaseModel {
+    @Serial
+    private static final long serialVersionUID = -2019053289601744522L;
+
+    @Schema(description = "票据iD")
+    private String invoiceId;
+
+    @Schema(description = "实收金额")
+    private BigDecimal actualReceivedAmount;
+
+    @Schema(description = "收款日期")
+    private Date receivePaymentDate;
+
+    @Schema(description = "回款差额")
+    private BigDecimal settlementPaymentDifference;
+
+    @Schema(description = "回款原因")
+    private String settlementPaymentReason;
+
+
+}

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

@@ -25,6 +25,9 @@ public class SettlementQueryVo extends PageRequest {
     @Schema(description = "保险公司Id")
     private String companyId;
 
+    @Schema(description = "保险公司Id")
+    private String partnerCompanyId;
+
     @Schema(description = "应收项")
     private String invoiceRiskType;
 

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

@@ -16,6 +16,12 @@ public class SettlementReportQueryVo extends PageRequest {
     @Schema(description = "保险公司id")
     private String companyId;
 
+    @Schema(description = "对接人")
+    private String contactPerson;
+
+    @Schema(description = "对接人手机号")
+    private String contactPersonPhone;
+
     @Schema(description = "创建开始时间")
     private String startTime;
 

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

@@ -22,8 +22,8 @@ import java.util.Objects;
 public class AmountAuditVo  extends BaseModel {
 
     @ExcelIgnore
-    @Schema(description = "主键")
-    private int auditingStatus;
+    @Schema(description = "付款状态")
+    private Integer auditingStatus;
 
     @ExcelProperty("付款状态")
     @Schema(description = "付款状态")
@@ -35,8 +35,8 @@ public class AmountAuditVo  extends BaseModel {
         auditingStatusMap.put("0","待付款");
         auditingStatusMap.put("1","已付款");
         auditingStatusMap.put("2","拒绝付款");
-
     }
+    // 导出的时候使用,不能删除
     public String getAuditingStatusName() {
         return auditingStatusMap.getOrDefault(Objects.toString(this.auditingStatus),this.auditingStatus+"");
     }

+ 6 - 0
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/service/impl/HuaNongCrawlerQuoteProcessServiceImpl.java

@@ -223,6 +223,9 @@ public class HuaNongCrawlerQuoteProcessServiceImpl implements HuaNongCrawlerQuot
     public void uploadImage(LoginInfoBO loginInfoBO, String agreementId, String contractNo, List<InsuranceImageVo> imageVoList) {
         // 获取归属信息
         HuaNongCrawlerQuotedPriceRequestRequest.BusinessDataDTO.SaleInfoDTO saleInfoDTO = huaNongCrawlerCacheComponent.getSaleInfoDTO(agreementId);
+        if(saleInfoDTO == null){
+            throw new SystemException("报价归属信息获取失败,请重新报价!");
+        }
         // 跳转上传影像页面
         HuaNongCrawlerNewImageQueryRequestRequest huaNongCrawlerNewImageQueryRequest
                 = new HuaNongCrawlerNewImageQueryRequestRequest(loginInfoBO.getLoginCom(), loginInfoBO.getLoginComCode(), contractNo, loginInfoBO.getToken(), saleInfoDTO.getSalesman()
@@ -359,6 +362,9 @@ public class HuaNongCrawlerQuoteProcessServiceImpl implements HuaNongCrawlerQuot
     public void uploadImageToUnclassified(LoginInfoBO loginInfoBO, String agreementId, String contractNo,
                                           List<String> policyNumbers, List<InsuranceImageVo> imageVoList) {
         HuaNongCrawlerQuotedPriceRequestRequest.BusinessDataDTO.SaleInfoDTO saleInfoDTO = huaNongCrawlerCacheComponent.getSaleInfoDTO(agreementId);
+        if(saleInfoDTO == null){
+            throw new SystemException("报价归属信息获取失败,请重新报价!");
+        }
 
         HuaNongCrawlerNewImageQueryRequestRequest huaNongCrawlerNewImageQueryRequest
                 = new HuaNongCrawlerNewImageQueryRequestRequest(loginInfoBO.getLoginCom(), loginInfoBO.getLoginComCode(), contractNo, loginInfoBO.getToken(), saleInfoDTO.getSalesman()

+ 14 - 42
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/component/HtCrawlerRequestComponent.java

@@ -1,22 +1,17 @@
 package com.jzg.quotation.huatai.crawler.component;
 
-import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.jzg.commons.entity.quote.vo.CarInfoVo;
 import com.jzg.commons.util.StringUtils;
 import com.jzg.quotation.commons.component.RequestComponent;
-import com.jzg.quotation.commons.config.WebClientConfig;
-import com.jzg.quotation.commons.utils.RequestObjectConversion;
 import com.jzg.quotation.huatai.crawler.constant.HtRequestConstant;
 import com.jzg.quotation.huatai.crawler.constant.enums.RequestUrl;
 import com.jzg.quotation.huatai.crawler.entity.Request.*;
 import com.jzg.quotation.huatai.crawler.entity.Response.*;
 import com.jzg.quotation.huatai.crawler.entity.dto.LoginDTO;
 import com.jzg.quotation.huatai.crawler.properties.HuaTaiCrawlerProperties;
-import com.jzg.quotation.commons.utils.HtEncryptDecryptUtils;
 import com.jzg.quotation.huatai.crawler.util.CookieUtils;
 import com.jzg.quotation.huatai.crawler.util.CryptoUtil;
-import com.jzg.quotation.huatai.crawler.util.HuaTaiCryptUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RedissonClient;
@@ -24,28 +19,9 @@ 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;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.RestTemplate;
-import org.springframework.web.reactive.function.BodyInserters;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.reactive.function.client.WebClientResponseException;
 
 import javax.net.ssl.*;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
 import java.util.*;
-import java.util.regex.Pattern;
 
 /**
  * @description:
@@ -62,18 +38,14 @@ public class HtCrawlerRequestComponent {
 
     private final HuaTaiCrawlerCacheComponent huaTaiCrawlerCacheComponent;
 
-    private static final String proxyHostName = "172.24.144.226";//39.98.41.104
-
-    private static final int proxyPort = 2025;
-
-
+    private final CookieUtils cookieUtils;
     /**
      * 登录
      * @param loginDTO
      * @return
      */
     public Map<String, Object> authenticate(LoginDTO loginDTO, int loginsNumber) {
-        if(StringUtils.isNotBlank(huaTaiCrawlerCacheComponent.getCookie())) {
+        if(StringUtils.isNotBlank(huaTaiCrawlerCacheComponent.getCookie()) && StringUtils.isNotBlank(huaTaiCrawlerCacheComponent.getGsInfo())) {
             Map<String, Object> res = new HashMap<>();
             res.put("cookies", huaTaiCrawlerCacheComponent.getCookie());
             res.put("gsInfo", huaTaiCrawlerCacheComponent.getGsInfo());
@@ -82,14 +54,14 @@ public class HtCrawlerRequestComponent {
         }
         Map<String, Object> stringObjectMap = null;
         try {
-            stringObjectMap = new CookieUtils().mainFlow(loginDTO.getUsername(), loginDTO.getPassword(), loginDTO.getComcode(), loginDTO.getUseridname(), loginDTO.getDlrCode(), loginDTO.getDlxyCode(), 9999);
+            stringObjectMap = cookieUtils.mainFlow(loginDTO.getUsername(), loginDTO.getPassword(), loginDTO.getComcode(), loginDTO.getUseridname(), loginDTO.getDlrCode(), loginDTO.getDlxyCode(), 9999);
             if(stringObjectMap == null) return null;
             log.info("登录stringObjectMap:{}", JSONObject.toJSONString(stringObjectMap));
             huaTaiCrawlerCacheComponent.cacheCookie(stringObjectMap.get("cookies").toString());
             huaTaiCrawlerCacheComponent.cacheGsInfo(stringObjectMap.get("gsInfo").toString());
             huaTaiCrawlerCacheComponent.cacheComCode(stringObjectMap.get("comCode").toString());
         } catch (Exception e) {
-            throw new RuntimeException("无法登录华泰集成出单系统,获取登录信息失败"+e.getMessage());
+            throw new RuntimeException("抱歉,无法获取登录信息,请稍后重试");
         }
         return stringObjectMap;
     }
@@ -155,7 +127,7 @@ public class HtCrawlerRequestComponent {
     public HtCrawlerCarQueryResponse.DataDTO carQuery(HtCrawlerCarQueryRequest htCrawlerCarQueryRequest, HttpHeaders headers, CarInfoVo carInfoVo) {
         List<String> cookie = headers.get("Cookie");
         log.info("huatai_cookie:{}", cookie);
-        ResponseEntity<HtCrawlerCarQueryResponse> carQueryResponseEntity = requestComponent.objectPostWithProxy(RequestUrl.CAR_QUERY.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerCarQueryRequest, HtCrawlerCarQueryResponse.class, headers, proxyHostName, proxyPort);
+        ResponseEntity<HtCrawlerCarQueryResponse> carQueryResponseEntity = requestComponent.objectPostWithProxy(RequestUrl.CAR_QUERY.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerCarQueryRequest, HtCrawlerCarQueryResponse.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort());
         HtCrawlerCarQueryResponse carQueryResponse = carQueryResponseEntity.getBody();
         String decryptKey = CryptoUtil.rsaDecrypt(carQueryResponse.getAccessToken());
         String decryptData = CryptoUtil.aesDecrypt(carQueryResponse.getData(), decryptKey);
@@ -171,7 +143,7 @@ public class HtCrawlerRequestComponent {
      */
     public ResponseEntity<Object> saveOneStept(HtCrawlerSaveOneStepRequest htCrawlerSaveOneStepRequest, HttpHeaders headers) {
         log.info("htCrawlerSaveOneStepRequest:{}",JSONObject.toJSONString(htCrawlerSaveOneStepRequest));
-        ResponseEntity<Object> objectResponseEntity = requestComponent.objectPostWithProxy(RequestUrl.SAVE_ONE_STEP.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerSaveOneStepRequest, null, headers, proxyHostName, proxyPort);
+        ResponseEntity<Object> objectResponseEntity = requestComponent.objectPostWithProxy(RequestUrl.SAVE_ONE_STEP.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerSaveOneStepRequest, null, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort());
         return objectResponseEntity;
     }
 
@@ -185,7 +157,7 @@ public class HtCrawlerRequestComponent {
     public String quote(HtCrawlerQuoteRequest htCrawlerQuoteRequest, HttpHeaders headers) {
         headers.remove("Referer");
         headers.add("Referer", "https://cspxp.pc.ehuatai.com/htcsp/VehicleInfoEntryAction.do?cmd=saveOneStept");
-        ResponseEntity<HtCrawlerQuoteResponse> responseEntity = requestComponent.objectPostWithProxy(RequestUrl.QUOTE_PRICE.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerQuoteRequest, HtCrawlerQuoteResponse.class, headers, proxyHostName, proxyPort);
+        ResponseEntity<HtCrawlerQuoteResponse> responseEntity = requestComponent.objectPostWithProxy(RequestUrl.QUOTE_PRICE.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerQuoteRequest, HtCrawlerQuoteResponse.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort());
         HtCrawlerQuoteResponse quoteResponse = responseEntity.getBody();
         String decryptKey = CryptoUtil.rsaDecrypt(quoteResponse.getAccessToken());
         String decryptData = CryptoUtil.aesDecrypt(quoteResponse.getData(), decryptKey);
@@ -199,7 +171,7 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public HtCrawlerGerUploadUrlResponse getUploadUrl(HtCrawlerGerUploadUrlRequest htCrawlerGerUploadUrlRequest, HttpHeaders headers) {
-        return requestComponent.objectPostWithProxy(RequestUrl.GET_UPLOAD_URL.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerGerUploadUrlRequest, HtCrawlerGerUploadUrlResponse.class, headers, proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.GET_UPLOAD_URL.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerGerUploadUrlRequest, HtCrawlerGerUploadUrlResponse.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 
     /**
@@ -208,7 +180,7 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public String uploadImage(HtCrawlerUploadUrlRequest htCrawlerUploadUrlRequest) {
-        return requestComponent.objectPostWithProxy(RequestUrl.UPLOAD.getRequestUrl(huaTaiCrawlerProperties.getImgUrl()), htCrawlerUploadUrlRequest, String.class, null,proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.UPLOAD.getRequestUrl(huaTaiCrawlerProperties.getImgUrl()), htCrawlerUploadUrlRequest, String.class, null,huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 
     /**
@@ -218,7 +190,7 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public HtCrawlerSubmitResponse submit(HtCrawlerSubmitRequest htCrawlerSubmitRequest, HttpHeaders headers) {
-        return requestComponent.objectPostWithProxy(RequestUrl.SUBMIT.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerSubmitRequest, HtCrawlerSubmitResponse.class, headers, proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.SUBMIT.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerSubmitRequest, HtCrawlerSubmitResponse.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 
     /**
@@ -228,7 +200,7 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public String submit2(HtCrawlerSubmitRequest htCrawlerSubmitRequest, HttpHeaders headers) {
-        return requestComponent.objectPostWithProxy(RequestUrl.SUBMIT.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerSubmitRequest, String.class, headers, proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.SUBMIT.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerSubmitRequest, String.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 
     /**
@@ -238,7 +210,7 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public HtCrawlerDownloadPolicyResponse downloadPolicy(HtCrawlerDownloadPolicyRequest downloadPolicyRequest, HttpHeaders headers) {
-        return requestComponent.objectPostWithProxy(RequestUrl.DOWNLOAD_POLICY.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), downloadPolicyRequest, HtCrawlerDownloadPolicyResponse.class, headers, proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.DOWNLOAD_POLICY.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), downloadPolicyRequest, HtCrawlerDownloadPolicyResponse.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 
     /**
@@ -248,7 +220,7 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public String queryOrderStatus(HtCrawlerOrderStateRequest htCrawlerOrderStateRequest, HttpHeaders headers) {
-        return requestComponent.objectPostWithProxy(RequestUrl.ORDER_STATE.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerOrderStateRequest, String.class, headers,proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.ORDER_STATE.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), htCrawlerOrderStateRequest, String.class, headers,huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 
     /**
@@ -258,6 +230,6 @@ public class HtCrawlerRequestComponent {
      * @return
      */
     public HtCrawlerPaymentLinkResponse getPaymentLink(HtCrawlerPaymentLinkRequest paymentLinkRequest, HttpHeaders headers) {
-        return requestComponent.objectPostWithProxy(RequestUrl.PAYMENT_LINK.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), paymentLinkRequest, HtCrawlerPaymentLinkResponse.class, headers, proxyHostName, proxyPort).getBody();
+        return requestComponent.objectPostWithProxy(RequestUrl.PAYMENT_LINK.getRequestUrl(huaTaiCrawlerProperties.getBaseUrl()), paymentLinkRequest, HtCrawlerPaymentLinkResponse.class, headers, huaTaiCrawlerProperties.getProxyHostName(), huaTaiCrawlerProperties.getProxyPort()).getBody();
     }
 }

+ 4 - 0
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/properties/HuaTaiCrawlerProperties.java

@@ -23,4 +23,8 @@ public class HuaTaiCrawlerProperties {
      * https://img-web.pc.ehuatai.com
      */
     private String imgUrl;
+
+    private String proxyHostName;//39.98.41.104 172.24.144.226
+
+    private int proxyPort; //2025
 }

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

@@ -43,11 +43,11 @@ 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;
 import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.NotNull;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.select.Elements;
@@ -55,24 +55,12 @@ 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.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;
-import java.net.Proxy;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -103,7 +91,6 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
     private static final String XBCOM_CODE = "927";
     private static final String SPECIAL_ACCOUNT_XN = "XN92709001";
     private static final String ERR_PREFIX = "报价失败:";
-    private static final String CHINA = "中国";
     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";
 
@@ -128,34 +115,8 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
     private final HuaTaiCrawlerProperties props;
     private final HtCrawlerOrderService orderService;
     private final PlatformClient platformClient;
+    private final CookieUtils cookieUtils;
     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 c, @NotNull String m) throws IOException {
-                if (c instanceof HttpsURLConnection) trustAll((HttpsURLConnection) c);
-                super.prepareConnection(c, m);
-            }
-        };
-        factory.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("172.24.144.226", 2025)));//39.98.41.104
-        return new RestTemplate(factory);
-    }
-
-    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 SystemException(e.getMessage()); }
-    }
 
     // ====================== 登录 ======================
     @Override public void login(LoginBase loginBase) { }
@@ -180,7 +141,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("重试被中断", ie); }
     }
 
-    // ====================== 续保查询 ======================
+    // ====================== 续保查询(仅获取车型编码) ======================
     public Map<String, Object> xbQuery(String username, String cookie, CarInfoVo car) throws Exception {
         log.info("开始续保查询");
         boolean special = SPECIAL_USERS.contains(username) || SPECIAL_ACCOUNT_XN.equals(username);
@@ -268,7 +229,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         if (!core.success()) throw new SystemException("报价失败");
         // 保存订单到MongoDB
         saveOrder(quoteVo, core);
-        // 组装费用
+        // 组装费用、评分、险种信息、续保标志
         return HuaTaiCrawlerQuoteResultsVoBuild.buildQuoteResultsVo(quoteVo, core.data(), core.fcPrice());
     }
 
@@ -675,11 +636,9 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                     String chesun_startDate = m.group(1);
                     log.info("报价响应结果BI,终保日期{}", chesun_startDate);
                     duplQuoteResult.q().getBiRiskInfoVo().setStartDate(chesun_startDate);
-                    duplQuoteResult.q().getCiRiskInfoVo().setStartDate(chesun_startDate);
                     LocalDateTime dt = LocalDateTime.parse(chesun_startDate.replace(" ", "T"), DateTimeFormatter.ofPattern(DATE_FMT_T));
                     String newEnd = dt.plusYears(1).format(DateTimeFormatter.ofPattern(DATE_FMT));
                     duplQuoteResult.q().getBiRiskInfoVo().setEndDate(newEnd);
-                    duplQuoteResult.q().getCiRiskInfoVo().setEndDate(newEnd);
                     decryptData = req.quote(new HtCrawlerQuoteRequest(duplQuoteResult.q(), mealid, limitAmount, "", personalInsuredJSON), h);
                     return resolveQuoteResponse(duplQuoteResult, decryptData, mealid, limitAmount, h, personalInsuredJSON);
                 }
@@ -869,7 +828,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
         if (!"1,2,3,4,Y1,Y2,Y3".contains(order.getCiCategory()) || "Y".equals(str(map, "isOver")) || "Y".equals(str(map, "isOver1"))
                 || "重新计算保费".equals(str(map, "MSG")) || (res.contains("下发修改意见") && !res.contains("isPassHB")) || res.contains("转人工意见")) {
-            r.setUnderwritingResult("核保失败-------" + convertResponseMsg(res));
+            r.setUnderwritingResult("核保失败-------" + (StringUtils.isBlank(convertResponseMsg(res)) ? str(map, "MSG") : convertResponseMsg(res)));
             return r;
         }
         // 转人工处理
@@ -944,7 +903,8 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         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);
+        //返回的是html 页面,暂不打印
+        //log.info("queryPaymentList response: {}", body);
         return req.getPaymentLink(new HtCrawlerPaymentLinkRequest(params, vo, body), buildPaymentListHeaders(req.getCookie()));
     }
 
@@ -990,8 +950,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
         try {
             new UploadUtil().upLoad(quotationRelaNo, b64List, parseToMap(req.getGsInfo()),
-                    attr.getUsername(), params.getComCode(), convertCookieToMap(req.getCookie()),
-                    quotationRelaNo + "_", token, updata);
+                    attr.getUsername(), params.getComCode(), convertCookieToMap(req.getCookie()), token, updata, cookieUtils);
             cache.cacheYingXiangUploaded(quotationRelaNo);
         } catch (Exception e) { throw new SystemException(e.getMessage()); }
     }
@@ -1244,11 +1203,11 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
     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();
+        return cookieUtils.getRestTemplate().exchange(b.toUriString(), HttpMethod.GET, new HttpEntity<>(headers), String.class).getBody();
     }
 
     private String post(String url, HttpHeaders headers, MultiValueMap<String, String> data) {
-        return RT.postForEntity(url, new HttpEntity<>(data, headers), String.class).getBody();
+        return cookieUtils.getRestTemplate().postForEntity(url, new HttpEntity<>(data, headers), String.class).getBody();
     }
 
     @SuppressWarnings("unchecked")
@@ -1256,7 +1215,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                                              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();
+        String body = cookieUtils.getRestTemplate().exchange(b.toUriString(), HttpMethod.POST, new HttpEntity<>(data, headers), String.class).getBody();
         log.info("转人工响应结果,{}", body);
         return MAPPER.readValue(body, Map.class);
     }

+ 377 - 600
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/util/CookieUtils.java

@@ -1,687 +1,464 @@
 package com.jzg.quotation.huatai.crawler.util;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.jzg.quotation.huatai.crawler.properties.HuaTaiCrawlerProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.http.*;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestTemplate;
+import com.jzg.commons.util.StringUtils;
 
 import javax.net.ssl.*;
+import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.InetSocketAddress;
 import java.net.Proxy;
+import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-
+/**
+ * 华泰爬虫登录工具类(单例+ThreadLocal线程隔离)
+ * 负责登录、Cookie维护、归属信息拉取、前置接口请求
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
 public class CookieUtils {
-    private final ObjectMapper objectMapper = new ObjectMapper();
-
-    private final String baseUrl = "https://cspxp.pc.ehuatai.com/htcsp";
-
-    // 车辆信息
-    private String licenseNo;
-    private String vin;
-    private String registerDate;
-    private String energyType;
-    private String modelCName;
-    private String engineNo;
-    private String seat;
-    private String purchasePrice;
-    private String vehicleUse;
-    private String limitLoad;
-    private String cartype;
-    private String completeKerbMass;
-    private String cimodelclass;
-    private String motorTypeCode;
-    private String motorUsageTypeCode;
-    private String motortypecode;
-
-    // 归属信息
-    private String ywlyName;
-    private String xstxCode;
-    private String dlrCode;
-    private String dlxyCode;
-    private Map<String, Object> gsInfo = new HashMap<>();
-
-    // cookies
-    private String jsessionId = "";
-    private String bigIpCookie = "";
-
-    // 特殊账号列表
-    private final List<String> specialUsers = Arrays.asList(
-            "EA27000180", "EA27000401", "EA27000098", "EA27000076",
-            "EA27000385", "EA27000336", "EA27000393", "EA27000182",
-            "EA27000381", "EA27000394", "XN9279003"
-    );
+    private final HuaTaiCrawlerProperties crawlerProp;
+    private final ObjectMapper objectMapper;
+
+    // ====================== 全局常量统一管理 ======================
+    private static final String BASE_DOMAIN = "https://cspxp.pc.ehuatai.com/htcsp";
+    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/125.0.0.0 Safari/537.36";
+    private static final String COOKIE_JSESSIONID = "JSESSIONID";
+    private static final String COOKIE_BIGIP = "BIGipServerypKFOfHGLAALYYraCSdVMw";
+
+    // 接口地址常量
+    private static final String URL_INDEX = BASE_DOMAIN + "/";
+    private static final String URL_CHECK_WEB = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=checkWeb";
+    private static final String URL_GET_BRANCH = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=getBranch";
+    private static final String URL_JUDGE_FORWARD = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=judgeCanForwardNewPage";
+    private static final String URL_GET_MAC = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=getMac";
+    private static final String URL_VERIFY_FLAG = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=verificationFlag";
+    private static final String URL_GET_PROMPT = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=getPrompt";
+    private static final String URL_CHECK_USER = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=checkUser";
+    private static final String URL_LOGIN_WELCOME = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=loginWelcom";
+    private static final String URL_MENU_CONTROL = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=";
+    private static final String URL_INIT_SALE_USER = BASE_DOMAIN + "/SaleInfoAction.do?cmd=initSaledUser";
+    private static final String URL_INIT_SALE_SYS = BASE_DOMAIN + "/SaleInfoAction.do?cmd=initSalesys";
+    private static final String URL_INIT_SELECT_SALE_ALL = BASE_DOMAIN + "/SaleInfoAction.do?cmd=initSelectSalesysall";
+    private static final String URL_CONFIRM_USER = BASE_DOMAIN + "/ShakeHandsAction.do?cmd=confirmUserVoByInitBySale";
+
+    // 正则常量
+    private static final Pattern PAT_COOKIE = Pattern.compile("([^;]+)=([^;]+)");
+    private static final Pattern PAT_COMPANY_JSON = Pattern.compile("var comPanyJson= (.*?);");
+
+    // ====================== 线程上下文封装(替代零散ThreadLocal变量) ======================
+    private static class CrawlContext {
+        // Cookie
+        String jsessionId = "";
+        String bigIpCookie = "";
+        // 归属信息
+        String ywlyName;
+        String xstxCode;
+        String dlrCode;
+        String dlxyCode;
+        Map<String, Object> gsInfo = new HashMap<>();
+    }
 
-    private String quotationNo = "";
+    // 单一线程上下文容器
+    private final ThreadLocal<CrawlContext> threadCtx = ThreadLocal.withInitial(CrawlContext::new);
 
+    // ====================== 对外主入口 ======================
     public Map<String, Object> mainFlow(String username, String pwd, String comCode,
                                         String useridName, String dlrCode, String dlxyCode, int maxQuoteNum) {
-
-        RestTemplate restTemplate = getRestTemplate();
-
-
+        CrawlContext ctx = threadCtx.get();
+        Map<String, Object> result = new HashMap<>();
         try {
-            // 初始化车辆信息(示例数据)
-            initCarInfo();
-
-            // 登录
-            Map<String, Object> loginResult = login(username, pwd, comCode);
-            if (loginResult.get("code") != null && (int) loginResult.get("code") != 200) {
-                System.out.println("华泰[" + username + "] 登录失败");
-                Map<String, Object> result1 = new HashMap<>();
-                result1.put("code", 400);
-                result1.put("message", "用户名、密码错误");
-                Map<String, Object> other = new HashMap<>();
-                other.put("maxQuoteNum", maxQuoteNum);
-                result1.put("other", other);
-                return result1;
+            // 登录流程
+            Map<String, Object> loginResp = doLoginFlow(ctx, username, pwd, comCode);
+            if (!"200".equals(String.valueOf(loginResp.get("code")))) {
+                log.error("华泰账号[{}]登录失败:{}", username, loginResp.get("message"));
+                result.put("code", 400);
+                result.put("message", loginResp.get("message"));
+                result.put("other", Map.of("maxQuoteNum", maxQuoteNum));
+                return result;
             }
-
-            Map<String, Object> cookies = (Map<String, Object>) loginResult.get("cookies");
-            this.jsessionId = (String) cookies.get("JSESSIONID");
-            this.bigIpCookie = (String) cookies.get("BIGipServerypKFOfHGLAALYYraCSdVMw");
-
-            // 如果不是特殊账号,获取归属信息
-            //if (!specialUsers.contains(username)) {
-            //    try {
-            //        getGuishu(comCode, useridName, dlrCode, dlxyCode);
-            //    } catch (Exception e) {
-            //        e.printStackTrace();
-            //    }
-            //}
-            getGuishu(comCode, useridName, dlrCode, dlxyCode);
-
-            Map<String, Object> resultMap = new HashMap<>();
-            resultMap.put("code", 200);
-            resultMap.put("message", "success");
-            resultMap.put("cookies", "JSESSIONID="+this.jsessionId+"; BIGipServerypKFOfHGLAALYYraCSdVMw="+this.bigIpCookie+";");
-            resultMap.put("comCode", loginResult.get("comcode"));
-            resultMap.put("gsInfo", gsInfo);
-            return resultMap;
+            // 缓存当前线程Cookie
+            ctx.jsessionId = (String) loginResp.get("JSESSIONID");
+            ctx.bigIpCookie = (String) loginResp.get(COOKIE_BIGIP);
+            // 拉取归属树gsInfo
+            fetchGsInfo(ctx, comCode, useridName, dlrCode, dlxyCode);
+            // 组装返回
+            String cookieStr = String.format("%s=%s; %s=%s",
+                    COOKIE_JSESSIONID, ctx.jsessionId,
+                    COOKIE_BIGIP, ctx.bigIpCookie);
+            result.put("code", 200);
+            result.put("message", "success");
+            result.put("cookies", cookieStr);
+            result.put("comCode", loginResp.get("comcode"));
+            result.put("gsInfo", ctx.gsInfo);
+            log.info("华泰账号[{}]登录前置流程完成", username);
+            return result;
         } catch (Exception e) {
-            Map<String, Object> result2 = new HashMap<>();
-            result2.put("code", 500);
-            result2.put("message", e.getMessage());
-            return result2;
+            log.error("华泰爬虫mainFlow异常", e);
+            result.put("code", 500);
+            result.put("message", e.getMessage());
+            return result;
+        } finally {
+            // 必须销毁,线程池防内存泄漏
+            threadCtx.remove();
         }
     }
 
+    // ====================== 公共工具方法 ======================
+    /** 统一构建带代理、忽略SSL的RestTemplate */
     @NotNull
-    public static RestTemplate getRestTemplate() {
+    public RestTemplate getRestTemplate() {
         SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory() {
             @Override
-            protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws java.io.IOException {
-                if (connection instanceof HttpsURLConnection) {
+            protected void prepareConnection(HttpURLConnection conn, String method) throws IOException {
+                if (conn instanceof HttpsURLConnection httpsConn) {
                     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 java.security.SecureRandom());
-                        ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory());
-                        ((HttpsURLConnection) connection).setHostnameVerifier(new HostnameVerifier() {
-                            @Override
-                            public boolean verify(String hostname, SSLSession session) {
-                                return true;
-                            }
-                        });
-
+                        trustAllSsl(httpsConn);
                     } catch (Exception e) {
                         throw new RuntimeException(e);
                     }
                 }
-                super.prepareConnection(connection, httpMethod);
+                super.prepareConnection(conn, method);
             }
         };
-
-        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("172.24.144.226", 2025));//39.98.41.104
+        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(crawlerProp.getProxyHostName(), crawlerProp.getProxyPort()));
         factory.setProxy(proxy);
-
-        RestTemplate restTemplate = new RestTemplate(factory);
-        return restTemplate;
+        return new RestTemplate(factory);
     }
 
-    private void initCarInfo() {
-        Map<String, Object> dddd = new HashMap<>();
-        dddd.put("frameNo", "LM8F7J3B0SC078844");
-        dddd.put("transferFlagBi", false);
-        dddd.put("transferFlag", false);
-        dddd.put("cartype", "K31");
-        dddd.put("licenseNo", "晋AFS6726");
-        dddd.put("newEnergy", true);
-        dddd.put("modelcname", "赛力斯SKE6520SSHEVA6S插电式增程混合动力多用途乘用车");
-        dddd.put("purchasePrice", "469800");
-        dddd.put("vinNo", "LM8F7J3B0SC078844");
-        dddd.put("noLicenseFlag", false);
-        dddd.put("enginedesc", "1.496");
-        dddd.put("powerScale", "118");
-        dddd.put("modelCode", "AIAACD0022");
-        dddd.put("completeKerbMass", "2690");
-        dddd.put("familyName", "问界M9");
-        dddd.put("importFlag", "越野车类");
-        dddd.put("loanStatus", false);
-        dddd.put("property", "1");
-        dddd.put("caryear", "202503");
-        dddd.put("issueDate", "2025-06-19");
-        dddd.put("registerDate", "2025-06-19");
-        dddd.put("engineNo", "25634769");
-        dddd.put("brandName", "AITO问界");
-        dddd.put("usedCar", false);
-        dddd.put("licenseTypeCode", "02");
-        dddd.put("vehicleUse", "05");
-        dddd.put("carnature", "02");
-        dddd.put("seatCount", "5");
-        dddd.put("energyType", "1");
-        dddd.put("cimodelclass", "A0");
-        dddd.put("outOfInsurance", false);
-        dddd.put("vehicleweight", "3165");
-
-        this.licenseNo = (String) dddd.get("licenseNo");
-        this.vin = (String) dddd.get("vinNo");
-        this.registerDate = (String) dddd.get("registerDate");
-        this.energyType = (String) dddd.get("energyType");
-        this.modelCName = (String) dddd.get("modelcname");
-        this.engineNo = (String) dddd.get("engineNo");
-        this.seat = (String) dddd.get("seatCount");
-        this.purchasePrice = (String) dddd.get("purchasePrice");
-        this.vehicleUse = (String) dddd.get("vehicleUse");
-        this.limitLoad = (String) dddd.get("limitLoad");
-        this.cartype = (String) dddd.get("cartype");
-        this.completeKerbMass = (String) dddd.get("completeKerbMass");
-        this.cimodelclass = (String) dddd.get("cimodelclass");
-
-        if ("04".equals(this.vehicleUse)) {
-            this.motorTypeCode = "H0";
-            this.motorUsageTypeCode = "9D";
-        } else if ("05".equals(this.vehicleUse)) {
-            this.motorTypeCode = "A0";
-            this.motorUsageTypeCode = "8A";
-        } else if ("06".equals(this.vehicleUse)) {
-            if ("A0".equals(this.cimodelclass)) {
-                this.motorTypeCode = "A0";
-                this.motorUsageTypeCode = "8C";
-            } else if ("A1".equals(this.cimodelclass)) {
-                this.motorTypeCode = "H0";
-                this.motorUsageTypeCode = "8D";
-            }
-        } else if ("07".equals(this.vehicleUse)) {
-            if ("A0".equals(this.cimodelclass)) {
-                this.motorTypeCode = "A0";
-                this.motorUsageTypeCode = "8B";
-            } else if ("A1".equals(this.cimodelclass)) {
-                this.motorTypeCode = "H0";
-                this.motorUsageTypeCode = "8D";
-            }
-        } else if ("08".equals(this.vehicleUse)) {
-            this.motorTypeCode = "H0";
-            this.motorUsageTypeCode = "8D";
-        }
-        this.motortypecode = this.motorTypeCode;
+    /** 全局信任SSL工具,抽取公共 */
+    private void trustAllSsl(HttpsURLConnection conn) throws Exception {
+        TrustManager[] trustAll = 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 sslCtx = SSLContext.getInstance("TLS");
+        sslCtx.init(null, trustAll, new SecureRandom());
+        conn.setSSLSocketFactory(sslCtx.getSocketFactory());
+        conn.setHostnameVerifier((host, session) -> true);
     }
 
-    private HttpHeaders buildHeadersWithCookie() {
+    /** 通用构建基础请求头(无Cookie) */
+    private HttpHeaders buildBaseHeaders() {
         HttpHeaders headers = new HttpHeaders();
-        headers.set("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");
-        headers.set("Accept", "*/*");
+        headers.set("User-Agent", USER_AGENT);
         headers.set("Accept-Language", "zh-CN,zh;q=0.9");
         headers.set("Connection", "keep-alive");
-        headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
-        headers.set("Origin", "https://cspxp.pc.ehuatai.com");
-        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=");
+        headers.set("Cache-Control", "no-cache");
+        headers.set("Pragma", "no-cache");
+        return headers;
+    }
+
+    /** 表单JSON请求头(XHR) */
+    private HttpHeaders buildJsonHeaders(CrawlContext ctx) {
+        HttpHeaders headers = buildBaseHeaders();
+        headers.set("Accept", "application/json, text/javascript, */*; q=0.01");
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.set("Origin", BASE_DOMAIN);
+        headers.set("Referer", BASE_DOMAIN + "/");
         headers.set("X-Requested-With", "XMLHttpRequest");
-        String cookieStr = "JSESSIONID=" + jsessionId;
-        if (!bigIpCookie.isEmpty()) {
-            cookieStr += "; BIGipServerypKFOfHGLAALYYraCSdVMw=" + bigIpCookie;
-        }
-        headers.set("Cookie", cookieStr);
+        setCookieHeader(headers, ctx);
         return headers;
     }
 
-    private HttpHeaders buildHtmlHeadersWithCookie() {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("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");
-        headers.set("Accept", "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");
-        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");
+    /** HTML页面请求头 */
+    private HttpHeaders buildHtmlHeaders(CrawlContext ctx) {
+        HttpHeaders headers = buildBaseHeaders();
+        headers.set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
         headers.set("Upgrade-Insecure-Requests", "1");
-        String cookieStr = "JSESSIONID=" + jsessionId;
-        if (!bigIpCookie.isEmpty()) {
-            cookieStr += "; BIGipServerypKFOfHGLAALYYraCSdVMw=" + bigIpCookie;
-        }
-        headers.set("Cookie", cookieStr);
+        setCookieHeader(headers, ctx);
         return headers;
     }
 
-    private void getGuishu(String comCode, String useridName, String dlrCode, String dlxyCode) throws Exception {
-        System.out.println("开始获取归属树==================");
-        RestTemplate restTemplate = getRestTemplate();
-        HttpHeaders headers = buildHeadersWithCookie();
-        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
-        data.add("comcode", comCode);
-        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(data, headers);
-        ResponseEntity<String> response = restTemplate.exchange(
-                baseUrl + "/SaleInfoAction.do?cmd=initSaledUser", HttpMethod.POST, entity, String.class);
-        System.out.println("归属树用户列表:initSaledUser:");
-        String useridValue = "";
-        String foundUseridName = "";
-        List<Map<String, Object>> userList = objectMapper.readValue(response.getBody(), List.class);
-        System.out.println("归属树用户列表:" + userList);
-        for (Map<String, Object> item : userList) {
-            if (useridName.equals(item.get("codecname"))) {
-                foundUseridName = (String) item.get("codecname");
-                useridValue = (String) item.get("codecode");
-                break;
-            }
+    /** 统一填充Cookie到Header */
+    private void setCookieHeader(HttpHeaders headers, CrawlContext ctx) {
+        List<String> cookieList = new ArrayList<>();
+        if (StringUtils.hasText(ctx.jsessionId)) {
+            cookieList.add(COOKIE_JSESSIONID + "=" + ctx.jsessionId);
         }
-
-        headers = buildHeadersWithCookie();
-        data = new LinkedMultiValueMap<>();
-        data.add("comcode", comCode);
-        entity = new HttpEntity<>(data, headers);
-        ResponseEntity<String> respSalesys = restTemplate.exchange(
-                baseUrl + "/SaleInfoAction.do?cmd=initSalesys", HttpMethod.POST, entity, String.class);
-        Map<String, Object> salesysMap = objectMapper.readValue(respSalesys.getBody(), HashMap.class);
-        System.out.println("归属树用户列表:salesysMap" + salesysMap);
-        String channelOrign = (String) salesysMap.get("codecode");
-        String newChannelOrign = (String) salesysMap.get("newcodecode");
-
-        headers = buildHtmlHeadersWithCookie();
-        HttpEntity<String> getEntity = new HttpEntity<>(headers);
-        ResponseEntity<String> respMenu = restTemplate.exchange(
-                baseUrl + "/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=",
-                HttpMethod.GET, getEntity, String.class);
-        System.out.println("归属树用户列表:menuControl:");
-        String comcodeGbdistrictcode = "";
-        String comcodeName = "";
-        Pattern pattern = Pattern.compile("var comPanyJson= (.*?);");
-        Matcher matcher = pattern.matcher(respMenu.getBody());
-        if (matcher.find()) {
-            String jsonStr = matcher.group(1);
-            List<Map<String, Object>> comPanyJsonList = objectMapper.readValue(jsonStr, List.class);
-            for (Map<String, Object> zzz : comPanyJsonList) {
-                if (comCode.equals(zzz.get("codecode"))) {
-                    comcodeGbdistrictcode = (String) zzz.get("codetype");
-                    comcodeName = (String) zzz.get("codecname");
-                    break;
-                }
-            }
+        if (StringUtils.hasText(ctx.bigIpCookie)) {
+            cookieList.add(COOKIE_BIGIP + "=" + ctx.bigIpCookie);
         }
+        headers.set("Cookie", String.join("; ", cookieList));
+    }
 
-        headers = buildHeadersWithCookie();
-        data = new LinkedMultiValueMap<>();
-        data.add("comcode", comCode);
-        data.add("codeCode", channelOrign);
-        entity = new HttpEntity<>(data, headers);
-        restTemplate.exchange(baseUrl + "/SaleInfoAction.do?cmd=initSelectSalesysall", HttpMethod.POST, entity, String.class);
-        System.out.println("归属树用户列表:initSelectSalesysall");
-        headers = buildHtmlHeadersWithCookie();
-        getEntity = new HttpEntity<>(headers);
-        restTemplate.exchange(
-                baseUrl + "/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=",
-                HttpMethod.GET, getEntity, String.class);
-
-        this.ywlyName = "代理业务";
-        this.xstxCode = "0401";
-        List<Map<String, Object>> codelist = new ArrayList<>();
-        codelist.add(createCodeItem("直接业务", "0"));
-        codelist.add(createCodeItem("代理业务", "1"));
-        codelist.add(createCodeItem("经纪业务", "4"));
-        codelist.add(createCodeItem("电销/网销", "6"));
-        for (Map<String, Object> zzz : codelist) {
-            if (this.ywlyName.equals(zzz.get("codecname"))) {
-                this.ywlyName = (String) zzz.get("codecode");
-                break;
+    /** 从Set-Cookie响应头解析并存入上下文 */
+    private void parseSetCookie(ResponseEntity<?> resp, CrawlContext ctx) {
+        List<String> setCookieList = resp.getHeaders().get("Set-Cookie");
+        if (CollectionUtils.isEmpty(setCookieList)) return;
+        for(String cookieRaw : setCookieList) {
+            Matcher m = PAT_COOKIE.matcher(cookieRaw);
+            if (!m.find()) continue;
+            String key = m.group(1);
+            String val = m.group(2);
+            switch (key) {
+                case COOKIE_JSESSIONID: ctx.jsessionId = val; break;
+                case COOKIE_BIGIP: ctx.bigIpCookie = val; break;
             }
         }
-
-        this.dlrCode = dlrCode;
-        this.dlxyCode = dlxyCode;
-
-        headers = new HttpHeaders();
-        headers.set("Accept", "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");
-        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", "application/x-www-form-urlencoded");
-        headers.set("Origin", "https://cspxp.pc.ehuatai.com");
-        headers.set("Pragma", "no-cache");
-        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=");
-        headers.set("Upgrade-Insecure-Requests", "1");
-        headers.set("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");
-        String cookieStr = "JSESSIONID=" + jsessionId;
-        if (!bigIpCookie.isEmpty()) {
-            cookieStr += "; BIGipServerypKFOfHGLAALYYraCSdVMw=" + bigIpCookie;
-        }
-        headers.set("Cookie", cookieStr);
-
-        data = new LinkedMultiValueMap<>();
-        data.add("isTeamCarFlag", "");
-        data.add("teamcarmodelcode", "");
-        data.add("teamcarmodelname", "");
-        data.add("salemdea", "");
-        data.add("PRPDCODJson", "");
-        data.add("state", "");
-        data.add("comCodeInAgentsFlag", "0");
-        data.add("wangdianNameFlag", "0");
-        data.add("wangdianNameHid", "0");
-        data.add("licenseNo", "");
-        data.add("policyBI", "");
-        data.add("policyCI", "");
-        data.add("policyStatusBI", "");
-        data.add("policyStatusCI", "");
-        data.add("showEmploy", "");
-        data.add("showEmployControl", "");
-        data.add("businessature", "");
-        data.add("comcode", comCode);
-        data.add("comcode_name", comcodeName);
-        data.add("comcode_gbcode", comcodeGbdistrictcode);
-        data.add("userid", useridValue);
-        data.add("userid_name", foundUseridName);
-        data.add("businessNature", this.ywlyName);
-        data.add("selectsalesys", "");
-        data.add("salesys", this.xstxCode);
-        data.add("channel", "04");
-        data.add("channel_orign", channelOrign);
-        data.add("newChannel", "saleSysDl");
-        data.add("newChannel_orign", newChannelOrign);
-        data.add("agentno_dl", this.dlrCode);
-        data.add("agreementno_dl", this.dlxyCode);
-        data.add("agentno_fdl", "");
-        data.add("agreementno_fdl", "");
-        data.add("agentno_jj", "");
-        data.add("agreementno_jj", "");
-        data.add("teamcode_value", "");
-        data.add("teamcode_name", "");
-        data.add("systemfor", "");
-        data.add("usercodelifedot_text", "");
-        data.add("usercodelife_value", "");
-        data.add("usercodelife_name", "");
-        data.add("usercodelife_text", "");
-        data.add("phone_value", "");
-        data.add("phone_name", "");
-        data.add("carType", "baojia");
-        data.add("quotationNo", "");
-
-        entity = new HttpEntity<>(data, headers);
-        restTemplate.exchange(baseUrl + "/ShakeHandsAction.do?cmd=confirmUserVoByInitBySale", HttpMethod.POST, entity, String.class);
-        System.out.println("归属树用户列表:confirmUserVoByInitBySale");
-        gsInfo.put("userid_name", foundUseridName);
-        gsInfo.put("userid_value", useridValue);
-        gsInfo.put("comcode_gbdistrictcode", comcodeGbdistrictcode);
-        gsInfo.put("comcode_name", comcodeName);
-        gsInfo.put("businessnature_value", this.ywlyName);
-        gsInfo.put("salesys_value", this.xstxCode);
-        gsInfo.put("channel_orign", channelOrign);
-        gsInfo.put("newChannel_orign", newChannelOrign);
-        System.out.println("归属树用户列表结束==============");
-    }
-
-    private Map<String, Object> createCodeItem(String name, String code) {
-        Map<String, Object> item = new HashMap<>();
-        item.put("codecname", name);
-        item.put("codecode", code);
-        return item;
     }
 
-    private Map<String, Object> login(String uname, String pwd, String comCode) {
+    /** AES+RSA统一解密 */
+    private Map<String, Object> decryptResp(String body) {
         try {
-            getCk();
-
-            String comcode = getComcode(uname, comCode);
-            if (comcode == null) {
-                Map<String, Object> result = new HashMap<>();
-                result.put("code", 400);
-                result.put("message", "获取机构失败");
-                return result;
-            }
-
-            return doLogin(uname, pwd, comcode);
+            Map<String, Object> map = objectMapper.readValue(body, Map.class);
+            String accessToken = (String) map.get("accessToken");
+            String data = (String) map.get("data");
+            String aesKey = HuaTaiCryptUtil.RSADecrypt(accessToken);
+            String realData = HuaTaiCryptUtil.AESDecrypt(data, aesKey);
+            return objectMapper.readValue(realData, Map.class);
         } catch (Exception e) {
-            Map<String, Object> result = new HashMap<>();
-            result.put("code", 500);
-            result.put("message", e.getMessage());
-            return result;
+            log.warn("解密响应失败", e);
+            return new HashMap<>();
         }
     }
 
-    private HttpHeaders buildCommonHeaders() {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("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");
-        headers.set("Accept", "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");
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Pragma", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Upgrade-Insecure-Requests", "1");
-        return headers;
+    /** 构造下拉编码项 */
+    private Map<String, Object> buildCodeItem(String name, String code) {
+        return Map.of("codecname", name, "codecode", code);
     }
 
-    private HttpHeaders buildJsonHeaders() {
-        HttpHeaders headers = new HttpHeaders();
-        headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36");
-        headers.set("Accept", "application/json, text/javascript, */*; q=0.01");
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Pragma", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
-        headers.set("Origin", "https://cspxp.pc.ehuatai.com");
-        headers.set("Referer", "https://cspxp.pc.ehuatai.com/htcsp/");
-        headers.set("X-Requested-With", "XMLHttpRequest");
-        headers.set("Sec-Fetch-Dest", "empty");
-        headers.set("Sec-Fetch-Mode", "cors");
-        headers.set("Sec-Fetch-Site", "same-origin");
-        headers.set("sec-ch-ua", "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand)\";v=\"24\"");
-        headers.set("sec-ch-ua-mobile", "?0");
-        headers.set("sec-ch-ua-platform", "\"Windows\"");
-        return headers;
+    // ====================== 内部流程拆分 ======================
+
+    /** 完整登录前置流程(获取初始Cookie+机构) */
+    private Map<String, Object> doLoginFlow(CrawlContext ctx, String username, String pwd, String inputComCode) {
+        RestTemplate rt = getRestTemplate();
+        // 1. 初始化页面Cookie
+        initBaseCookie(ctx, rt);
+        // 2. 获取机构编码
+        String fullComCode = fetchComCode(ctx, rt, username, inputComCode);
+        if (StringUtils.isBlank(fullComCode)) {
+            return Map.of("code", "400", "message", "获取机构信息失败");
+        }
+        // 3. 提交账号密码正式登录
+        return submitLogin(ctx, rt, username, pwd, fullComCode);
     }
 
-    private void getCk() {
-        System.out.println("获取Cookie开始==========");
-        RestTemplate restTemplate = getRestTemplate();
-        HttpHeaders headers = buildCommonHeaders();
-        HttpEntity<String> entity = new HttpEntity<>(headers);
-        ResponseEntity<String> response = restTemplate.exchange(
-                baseUrl + "/", HttpMethod.GET, entity, String.class);
-        extractCookies(response);
-
-        headers = buildCommonHeaders();
-        headers.set("Content-Type", "application/x-www-form-urlencoded");
-        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
-        data.add("ip", "cspxp.pc.ehuatai.com");
-        HttpEntity<MultiValueMap<String, String>> postEntity = new HttpEntity<>(data, headers);
-        ResponseEntity<String> postResp = restTemplate.exchange(
-                baseUrl + "/ShakeHandsAction.do?cmd=checkWeb", HttpMethod.POST, postEntity, String.class);
-        extractCookies(postResp);
-        System.out.println("获取Cookie结束==========");
+    /** 访问首页、checkWeb 预拉取基础Cookie */
+    private void initBaseCookie(CrawlContext ctx, RestTemplate rt) {
+        log.info("开始拉取基础页面Cookie");
+        HttpHeaders baseHtml = buildHtmlHeaders(ctx);
+        // 访问首页
+        ResponseEntity<String> indexResp = rt.getForEntity(URL_INDEX, String.class, new HttpEntity<>(baseHtml));
+        parseSetCookie(indexResp, ctx);
+        // checkWeb提交
+        MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
+        param.add("ip", "cspxp.pc.ehuatai.com");
+        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(param, baseHtml);
+        ResponseEntity<String> webResp = rt.postForEntity(URL_CHECK_WEB, entity, String.class);
+        parseSetCookie(webResp, ctx);
+        log.info("基础Cookie获取完成");
     }
 
-    private String getComcode(String uname, String comCode) {
-        System.out.println("获取机构开始==========");
-        RestTemplate restTemplate = getRestTemplate();
-        HttpHeaders headers = buildJsonHeaders();
-        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
-        data.add("username", uname);
-        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(data, headers);
-        ResponseEntity<String> response = restTemplate.exchange(
-                baseUrl + "/ShakeHandsAction.do?cmd=getBranch", HttpMethod.POST, entity, String.class);
-        extractCookies(response);
-        Map<String, Object> decrypted = htDecrypt(response.getBody());
-        List<Map<String, Object>> comCodeList = (List<Map<String, Object>>) decrypted.get("comCodeList");
-
-        String comcode = null;
-        if (comCodeList != null) {
-            for (Map<String, Object> item : comCodeList) {
-                if (comCode.equals(item.get("comcode"))) {
-                    comcode = item.get("comcode") + "-" + item.get("comcname");
-                    break;
-                }
-            }
-            if (comcode == null && !comCodeList.isEmpty()) {
-                comcode = comCodeList.get(0).get("comcode") + "-" + comCodeList.get(0).get("comcname");
+    /** 获取机构编码comcode */
+    private String fetchComCode(CrawlContext ctx, RestTemplate rt, String username, String inputComCode) {
+        log.info("开始拉取机构信息");
+        HttpHeaders jsonHeader = buildJsonHeaders(ctx);
+        MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
+        param.add("username", username);
+        ResponseEntity<String> branchResp = rt.postForEntity(URL_GET_BRANCH, new HttpEntity<>(param, jsonHeader), String.class);
+        parseSetCookie(branchResp, ctx);
+        Map<String, Object> decryptData = decryptResp(branchResp.getBody());
+        List<Map<String, Object>> comList = (List<Map<String, Object>>) decryptData.get("comCodeList");
+        if (CollectionUtils.isEmpty(comList)) return null;
+        String matchCode = null;
+        for (Map<String, Object> item : comList) {
+            String code = (String) item.get("comcode");
+            if (inputComCode.equals(code)) {
+                matchCode = code + "-" + item.get("comcname");
+                break;
             }
         }
-
-        headers = buildJsonHeaders();
-        HttpEntity<String> emptyEntity = new HttpEntity<>(headers);
-        restTemplate.exchange(baseUrl + "/ShakeHandsAction.do?cmd=judgeCanForwardNewPage",
-                HttpMethod.POST, emptyEntity, String.class);
-
-        headers = buildJsonHeaders();
-        data = new LinkedMultiValueMap<>();
-        data.add("username", uname);
-        entity = new HttpEntity<>(data, headers);
-        restTemplate.exchange(baseUrl + "/ShakeHandsAction.do?cmd=getMac", HttpMethod.POST, entity, String.class);
-
-        headers = buildJsonHeaders();
-        data = new LinkedMultiValueMap<>();
-        data.add("username", uname);
-        data.add("customerIp", "cspxp.pc.ehuatai.com");
-        data.add("messageType", "01");
-        data.add("comcode", comcode);
-        entity = new HttpEntity<>(data, headers);
-        restTemplate.exchange(baseUrl + "/ShakeHandsAction.do?cmd=verificationFlag", HttpMethod.POST, entity, String.class);
-
-        headers = buildJsonHeaders();
-        data = new LinkedMultiValueMap<>();
-        data.add("userName", uname);
-        entity = new HttpEntity<>(data, headers);
-        restTemplate.exchange(baseUrl + "/ShakeHandsAction.do?cmd=getPrompt", HttpMethod.POST, entity, String.class);
-
-        System.out.println("获取机构结束==========");
-        return comcode;
-    }
-
-    private Map<String, Object> doLogin(String uname, String pwd, String comcode) throws Exception {
-        System.out.println("登录开始==========");
-        RestTemplate restTemplate = getRestTemplate();
-        HttpHeaders headers = buildJsonHeaders();
-        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
-        data.add("username", uname);
-        data.add("password", pwd);
-        data.add("ip", "csp.pc.ehuatai.com");
-        data.add("comcode", comcode);
-        data.add("mobile", "");
-        data.add("verificationCode", "");
-        data.add("weChatFlag", "");
-        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(data, headers);
-        ResponseEntity<String> response = restTemplate.exchange(
-                baseUrl + "/ShakeHandsAction.do?cmd=checkUser", HttpMethod.POST, entity, String.class);
-        extractCookies(response);
-
-        headers = new HttpHeaders();
-        headers.set("Accept", "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");
-        headers.set("Accept-Language", "zh-CN,zh;q=0.9");
-        headers.set("Cache-Control", "no-cache");
-        headers.set("Connection", "keep-alive");
-        headers.set("Content-Type", "application/x-www-form-urlencoded");
-        headers.set("Origin", "https://cspxp.pc.ehuatai.com");
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", "https://cspxp.pc.ehuatai.com/htcsp/ShakeHandsAction.do?cmd=exitLogin");
-        headers.set("Sec-Fetch-Dest", "document");
-        headers.set("Sec-Fetch-Mode", "navigate");
-        headers.set("Sec-Fetch-Site", "same-origin");
-        headers.set("Sec-Fetch-User", "?1");
-        headers.set("Upgrade-Insecure-Requests", "1");
-        headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36");
-        headers.set("sec-ch-ua", "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand)\";v=\"24\"");
-        headers.set("sec-ch-ua-mobile", "?0");
-        headers.set("sec-ch-ua-platform", "\"Windows\"");
-
-        data = new LinkedMultiValueMap<>();
-        data.add("username", uname);
-        data.add("password", pwd);
-        data.add("comcode", comcode);
-        data.add("verificationCode", "");
-        data.add("yanzhengtupianflag", "");
-        data.add("brownVerb", "143.0.0.0");
-        data.add("brownVern", "Chrome");
-        data.add("bindflag", "");
-        data.add("customerIp", "127.0.0.1");
-        data.add("customerCity", "未知");
-        data.add("userIsNowFlag", "");
-        data.add("sendNecessityIpt", "X");
-        data.add("wwaUserCode", "");
-        data.add("weChatFlag", "");
-        data.add("validStatus", "1");
-        entity = new HttpEntity<>(data, headers);
-        ResponseEntity<String> loginResp = restTemplate.exchange(
-                baseUrl + "/ShakeHandsAction.do?cmd=loginWelcom", HttpMethod.POST, entity, String.class);
-        extractCookies(loginResp);
-
-        Map<String, Object> result = new HashMap<>();
-        if (!loginResp.getBody().contains("请填写正确的用户名和密码")) {
-            Map<String, Object> cookies = new HashMap<>();
-            cookies.put("JSESSIONID", jsessionId);
-            cookies.put("BIGipServerypKFOfHGLAALYYraCSdVMw", bigIpCookie);
-            result.put("code", 200);
-            result.put("cookies", cookies);
-            result.put("comcode", comcode);
-            result.put("message", "登录成功");
-        } else {
-            result.put("code", 400);
-            result.put("message", "账号密码错误");
+        // 未匹配取第一条
+        if (matchCode == null) {
+            Map<String, Object> first = comList.get(0);
+            matchCode = first.get("comcode") + "-" + first.get("comcname");
         }
-        System.out.println("登录结束==========");
-        return result;
+        // 前置一系列空接口
+        callEmptyJsonApi(ctx, rt, URL_JUDGE_FORWARD, null);
+        callEmptyJsonApi(ctx, rt, URL_GET_MAC, Map.of("username", username));
+        callEmptyJsonApi(ctx, rt, URL_VERIFY_FLAG, Map.of("username", username, "customerIp", "cspxp.pc.ehuatai.com", "messageType", "01", "comcode", matchCode));
+        callEmptyJsonApi(ctx, rt, URL_GET_PROMPT, Map.of("username", username));
+        log.info("机构信息获取完成:{}", matchCode);
+        return matchCode;
     }
 
-    private void extractCookies(ResponseEntity<String> response) {
-        List<String> cookies = response.getHeaders().get("Set-Cookie");
-        if (cookies != null) {
-            for (String cookie : cookies) {
-                if (cookie.contains("JSESSIONID=")) {
-                    jsessionId = extractCookieValue(cookie, "JSESSIONID");
-                }
-                if (cookie.contains("BIGipServerypKFOfHGLAALYYraCSdVMw=")) {
-                    bigIpCookie = extractCookieValue(cookie, "BIGipServerypKFOfHGLAALYYraCSdVMw");
-                }
-            }
+    /** 无返回空接口统一调用 */
+    private void callEmptyJsonApi(CrawlContext ctx, RestTemplate rt, String url, Map<String, String> params) {
+        HttpHeaders jsonHeader = buildJsonHeaders(ctx);
+        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
+        if (params != null) {
+            params.forEach(form::add);
         }
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(form, jsonHeader);
+        ResponseEntity<String> responseEntity = rt.postForEntity(url, requestEntity, String.class);
+        // 从本次请求的响应实体中获取Set-Cookie
+        parseSetCookie(responseEntity, ctx);
     }
 
-    private String extractCookieValue(String cookieHeader, String name) {
-        Pattern pattern = Pattern.compile(name + "=([^;]+)");
-        Matcher matcher = pattern.matcher(cookieHeader);
-        if (matcher.find()) {
-            return matcher.group(1);
+    /** 提交账号密码登录 */
+    private Map<String, Object> submitLogin(CrawlContext ctx, RestTemplate rt, String user, String pwd, String fullComCode) {
+        log.info("正式登录提交:{}", user);
+        HttpHeaders jsonHeader = buildJsonHeaders(ctx);
+        MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
+        param.add("username", user);
+        param.add("password", pwd);
+        param.add("ip", "csp.pc.ehuatai.com");
+        param.add("comcode", fullComCode);
+        param.add("mobile", "");
+        param.add("verificationCode", "");
+        param.add("weChatFlag", "");
+        ResponseEntity<String> checkUserResp = rt.postForEntity(URL_CHECK_USER, new HttpEntity<>(param, jsonHeader), String.class);
+        parseSetCookie(checkUserResp, ctx);
+        // 跳转登录页
+        HttpHeaders htmlHeader = buildHtmlHeaders(ctx);
+        MultiValueMap<String, String> loginForm = new LinkedMultiValueMap<>();
+        loginForm.add("username", user);
+        loginForm.add("password", pwd);
+        loginForm.add("comcode", fullComCode);
+        loginForm.add("verificationCode", "");
+        loginForm.add("yanzhengtupianflag", "");
+        loginForm.add("brownVerb", "143.0.0.0");
+        loginForm.add("brownVern", "Chrome");
+        loginForm.add("bindflag", "");
+        loginForm.add("customerIp", "127.0.0.1");
+        loginForm.add("customerCity", "未知");
+        loginForm.add("userIsNowFlag", "");
+        loginForm.add("sendNecessityIpt", "X");
+        loginForm.add("wwaUserCode", "");
+        loginForm.add("weChatFlag", "");
+        loginForm.add("validStatus", "1");
+        ResponseEntity<String> loginPageResp = rt.postForEntity(URL_LOGIN_WELCOME, new HttpEntity<>(loginForm, htmlHeader), String.class);
+        parseSetCookie(loginPageResp, ctx);
+        String body = loginPageResp.getBody();
+        if (body != null && body.contains("请填写正确的用户名和密码")) {
+            return Map.of("code", "400", "message", "账号或密码错误");
         }
-        return "";
+        return Map.of(
+                "code", "200",
+                "comcode", fullComCode,
+                COOKIE_JSESSIONID, ctx.jsessionId,
+                COOKIE_BIGIP, ctx.bigIpCookie,
+                "message", "登录成功"
+        );
     }
 
-    private Map<String, Object> htDecrypt(String jsonBody) {
-        try {
-            Map<String, Object> map = objectMapper.readValue(jsonBody, HashMap.class);
-            String accessToken = (String) map.get("accessToken");
-            String data = (String) map.get("data");
-            String aesKey = HuaTaiCryptUtil.RSADecrypt(accessToken);
-            String decrypted = HuaTaiCryptUtil.AESDecrypt(data, aesKey);
-            return objectMapper.readValue(decrypted, HashMap.class);
-        } catch (Exception e) {
-            return new HashMap<>();
+    /** 拉取归属树gsInfo核心逻辑 */
+    private void fetchGsInfo(CrawlContext ctx, String comCode, String useridName, String dlrCode, String dlxyCode) throws Exception {
+        log.info("开始拉取归属信息");
+        RestTemplate rt = getRestTemplate();
+        HttpHeaders jsonHeader = buildJsonHeaders(ctx);
+        // 1. initSaledUser
+        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
+        form.add("comcode", comCode);
+        ResponseEntity<String> userListResp = rt.postForEntity(URL_INIT_SALE_USER, new HttpEntity<>(form, jsonHeader), String.class);
+        List<Map<String, Object>> userList = objectMapper.readValue(userListResp.getBody(), List.class);
+        String targetUserId = "";
+        String targetUserName = "";
+        for (Map<String, Object> item : userList) {
+            if (useridName.equals(item.get("codecname"))) {
+                targetUserName = (String) item.get("codecname");
+                targetUserId = (String) item.get("codecode");
+                break;
+            }
+        }
+        // 2. initSalesys
+        form.clear();
+        form.add("comcode", comCode);
+        ResponseEntity<String> saleSysResp = rt.postForEntity(URL_INIT_SALE_SYS, new HttpEntity<>(form, jsonHeader), String.class);
+        Map<String, Object> saleSysMap = objectMapper.readValue(saleSysResp.getBody(), Map.class);
+        String channelOrig = (String) saleSysMap.get("codecode");
+        String newChannelOrig = (String) saleSysMap.get("newcodecode");
+        // 3. 菜单页面拿机构编码
+        HttpHeaders htmlHeader = buildHtmlHeaders(ctx);
+        ResponseEntity<String> menuResp = rt.getForEntity(URL_MENU_CONTROL, String.class, new HttpEntity<>(htmlHeader));
+        String comGbCode = "";
+        String comName = "";
+        Matcher m = PAT_COMPANY_JSON.matcher(menuResp.getBody());
+        if (m.find()) {
+            List<Map<String, Object>> comList = objectMapper.readValue(m.group(1), List.class);
+            for (Map<String, Object> item : comList) {
+                if (comCode.equals(item.get("codecode"))) {
+                    comGbCode = (String) item.get("codetype");
+                    comName = (String) item.get("codecname");
+                    break;
+                }
+            }
+        }
+        // 4. initSelectSalesysall
+        form.clear();
+        form.add("comcode", comCode);
+        form.add("codeCode", channelOrig);
+        rt.postForEntity(URL_INIT_SELECT_SALE_ALL, new HttpEntity<>(form, jsonHeader), String.class);
+        // 5. 二次菜单访问
+        rt.getForEntity(URL_MENU_CONTROL, String.class, new HttpEntity<>(htmlHeader));
+        // 固定业务渠道编码
+        String ywCode = "1";
+        String xstx = "0401";
+        List<Map<String, Object>> channelList = List.of(
+                buildCodeItem("直接业务", "0"),
+                buildCodeItem("代理业务", "1"),
+                buildCodeItem("经纪业务", "4"),
+                buildCodeItem("电销/网销", "6")
+        );
+        for (Map<String, Object> ch : channelList) {
+            if ("代理业务".equals(ch.get("codecname"))) {
+                ywCode = (String) ch.get("codecode");
+                break;
+            }
         }
+        // 6. 提交确认用户信息
+        MultiValueMap<String, String> submitForm = new LinkedMultiValueMap<>();
+        submitForm.add("comcode", comCode);
+        submitForm.add("comcode_name", comName);
+        submitForm.add("comcode_gbcode", comGbCode);
+        submitForm.add("userid", targetUserId);
+        submitForm.add("userid_name", targetUserName);
+        submitForm.add("businessNature", ywCode);
+        submitForm.add("salesys", xstx);
+        submitForm.add("channel_orign", channelOrig);
+        submitForm.add("newChannel_orign", newChannelOrig);
+        submitForm.add("agentno_dl", dlrCode);
+        submitForm.add("agreementno_dl", dlxyCode);
+        submitForm.add("carType", "baojia");
+        // 其余空参数统一填充
+        List<String> emptyKeys = List.of("isTeamCarFlag","teamcarmodelcode","quotationNo","policyBI","policyCI");
+        emptyKeys.forEach(k -> submitForm.add(k, ""));
+        rt.postForEntity(URL_CONFIRM_USER, new HttpEntity<>(submitForm, buildHtmlHeaders(ctx)), String.class);
+        // 拉取渠道编码后赋值到上下文
+        ctx.ywlyName = ywCode;
+        ctx.xstxCode = xstx;
+        ctx.dlrCode = dlrCode;
+        ctx.dlxyCode = dlxyCode;
+
+        // 回填gs
+        Map<String, Object> gs = new HashMap<>();
+        gs.put("userid_name", targetUserName);
+        gs.put("userid_value", targetUserId);
+        gs.put("comcode_gbdistrictcode", comGbCode);
+        gs.put("comcode_name", comName);
+        gs.put("businessnature_value", ctx.ywlyName);
+        gs.put("salesys_value", ctx.xstxCode);
+        gs.put("channel_orign", channelOrig);
+        gs.put("newChannel_orign", newChannelOrig);
+        ctx.gsInfo = gs;
+        log.info("归属信息拉取完成");
     }
-}
+}

+ 86 - 156
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/util/UploadUtil.java

@@ -1,12 +1,8 @@
 package com.jzg.quotation.huatai.crawler.util;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.core.io.ByteArrayResource;
-import org.springframework.core.io.FileSystemResource;
 import org.springframework.http.*;
-import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.util.UriComponentsBuilder;
@@ -15,9 +11,10 @@ import javax.imageio.IIOImage;
 import javax.imageio.ImageIO;
 import javax.imageio.ImageWriteParam;
 import javax.imageio.ImageWriter;
-import javax.imageio.stream.FileImageOutputStream;
 import java.awt.image.BufferedImage;
-import java.io.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.*;
 
 /**
@@ -25,44 +22,50 @@ import java.util.*;
  * 负责图片上传、OCR 识别及相关影像处理
  */
 public class UploadUtil {
-
     private final ObjectMapper objectMapper = new ObjectMapper();
 
-    private String imagesPath;
+    // ===================== 新增:线程上下文=====================
+    private static class UploadContext {
+        String orderNo;
+        List<Map<String, String>> b64phoList;
+        String username;
+        Map<String, String> cookies;
+        String comCode;
+        Map<String, Object> gsInfo;
+        String token;
+        Map<String, Object> updataInfo;
+        CookieUtils cookieUtils;
+    }
 
-    // ============ 类字段(对应 Python 中的 self 属性) ============
-    private String orderNo;
-    private List<Map<String, String>> b64phoList;
-    private String username;
-    private Map<String, String> cookies;
-    private String comCode;
-    private Map<String, Object> gsInfo;
-    private String token;
-    private Map<String, Object> updataInfo;
+    private final ThreadLocal<UploadContext> ctxHolder = ThreadLocal.withInitial(UploadContext::new);
+
+    // ===================== 对外主入口(仅改上下文赋值,内部逻辑完全不变) =====================
+    public Map<String, Object> upLoad(String orderNo, List<Map<String, String>> b64phoList, Map<String, Object> gsInfo,
+                                      String username, String comCode, Map<String, String> cookies, String token,
+                                      Map<String, Object> updataInfo, CookieUtils cookieUtils) throws Exception {
+        UploadContext ctx = ctxHolder.get();
+        // 赋值上下文,替代原来this.xxx
+        ctx.orderNo = orderNo;
+        ctx.b64phoList = b64phoList;
+        ctx.gsInfo = gsInfo;
+        ctx.username = username;
+        ctx.comCode = comCode;
+        ctx.cookies = cookies;
+        ctx.token = token;
+        ctx.updataInfo = updataInfo;
+        ctx.cookieUtils = cookieUtils;
 
-    /**
-     * 入口方法:对应 Python 中的 photo_up.main()
-     */
-    public Map<String, Object> upLoad(String orderNo, List<Map<String, String>> b64phoList, Map<String, Object> gsInfo, String username, String comCode, Map<String, String> cookies, String imagesPath, String token, Map<String, Object> updataInfo) throws Exception {
-        this.orderNo = orderNo;
-        this.b64phoList = b64phoList;
-        this.gsInfo = gsInfo;
-        this.username = username;
-        this.comCode = comCode;
-        this.cookies = cookies;
-        this.imagesPath = imagesPath;
-        this.token = token;
-        this.updataInfo = updataInfo;
         uploads();
-
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("code", 200);
         result.put("message", "影像上传成功");
+
+        // 释放ThreadLocal,防止线程池内存泄漏
+        ctxHolder.remove();
         return result;
     }
 
-    // ============ UUID / 文件名生成(替代 JS 的 get_uuid) ============
-
+    // ============ UUID / 文件名生成(原代码原样) ============
     private String getFileName() {
         return UUID.randomUUID().toString().replace("-", "");
     }
@@ -76,77 +79,58 @@ public class UploadUtil {
     public String ysPhoto(String base64Image) throws Exception {
         // 1. Base64 解码成字节数组
         byte[] imageBytes = Base64.getDecoder().decode(base64Image);
-
         // 2. 从内存流直接读取图片(不创建文件)
         ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
         BufferedImage img = ImageIO.read(bais);
         bais.close();
-
         if (img == null) {
             throw new IOException("无法读取图片:字节流解析失败");
         }
-
         // 3. 转 RGB 格式(去掉透明通道,原逻辑保留)
         BufferedImage rgbImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
         rgbImg.createGraphics().drawImage(img, 0, 0, null);
-
         // 4. 【重点】内存流压缩,不写文件!
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
         ImageWriteParam param = writer.getDefaultWriteParam();
-
         // 保留压缩
         param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
         param.setCompressionQuality(0.4f);
-
         // 输出到内存流,不写文件
         writer.setOutput(ImageIO.createImageOutputStream(baos));
         writer.write(null, new IIOImage(rgbImg, null, null), param);
-
         // 5. 清理
         writer.dispose();
         img.flush();
         rgbImg.flush();
-
         // 6. 压缩后的字节 -> 转 Base64 返回
         byte[] resultBytes = baos.toByteArray();
         baos.close();
-
         return Base64.getEncoder().encodeToString(resultBytes);
     }
 
-    /**
-     * 保存图片并返回尺寸和大小(对应 Python 中的 ys_photo_new)
-     * 【已改造】:不创建任何文件,纯流处理,解决服务器文件权限问题
-     */
     public double[] ysPhotoNew(byte[] imageBytes, String imgName) throws Exception {
         // ===================== 1. 直接从字节数组读取图片(零文件) =====================
         ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
         BufferedImage img = ImageIO.read(bais);
         bais.close();
-
         if (img == null) {
             throw new IOException("无法读取图片:字节流解析失败");
         }
-
-        // ===================== 2. 处理透明通道(不变) =====================
+        // ===================== 2. 处理透明通道 =====================
         if (img.getColorModel().hasAlpha() || img.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
             BufferedImage rgbImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
             rgbImg.createGraphics().drawImage(img, 0, 0, null);
             img = rgbImg;
         }
-
-        // ===================== 3. 缩放逻辑(你的原有逻辑,完全不变) =====================
+        // ===================== 3. 缩放逻辑(原样不动) =====================
         String width = "";
         String height = "";
-
         if (width != null && !width.isEmpty() || height != null && !height.isEmpty()) {
             int originalWidth = img.getWidth();
             int originalHeight = img.getHeight();
-
             int targetWidth;
             int targetHeight;
-
             if (width != null && !width.isEmpty() && height != null && !height.isEmpty()) {
                 targetWidth = Integer.parseInt(width);
                 targetHeight = Integer.parseInt(height);
@@ -159,44 +143,32 @@ public class UploadUtil {
                 double ratio = (double) targetHeight / originalHeight;
                 targetWidth = (int) (originalWidth * ratio);
             }
-
             BufferedImage resizedImg = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
             resizedImg.createGraphics().drawImage(
                     img.getScaledInstance(targetWidth, targetHeight, java.awt.Image.SCALE_SMOOTH),
                     0, 0, null);
             img = resizedImg;
         }
-
-        // ===================== 4. 【关键】纯流获取文件大小,不写磁盘 =====================
+        // ===================== 4. 纯流获取大小 =====================
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ImageIO.write(img, "jpg", baos);
         byte[] outputBytes = baos.toByteArray();
         baos.close();
-
-        // 宽、高、大小(KB)—— 完全不需要文件
+        // 宽、高、大小KB
         int newWidth = img.getWidth();
         int newHeight = img.getHeight();
         double sizeKb = outputBytes.length / 1024.0;
-
-        // ===================== 5. 释放资源 =====================
         img.flush();
-
-        // 返回和原来完全一样:[宽度,高度,大小KB]
         return new double[]{newWidth, newHeight, sizeKb};
     }
 
-    // ============ HTTP 请求方法 ============
-
-    /**
-     * 主上传逻辑(对应 Python 中的 upload)
-     */
+    // ============ HTTP 请求主上传逻辑 所有判断原样,仅把this.xxx换成ctx.xxx ============
     public void uploads() throws Exception {
-        for (Map<String, String> zz : this.b64phoList) {
+        UploadContext ctx = ctxHolder.get();
+        for (Map<String, String> zz : ctx.b64phoList) {
             String imgType = zz.get("imgType");
             String imageBase64 = zz.get("imageBase64");
-
             if ("C01".equals(imgType) || "D01".equals(imgType)) {
-                // OCR 识别 + 影像上传
                 if ("C01".equals(imgType)) {
                     doOcrUpload(imageBase64, "carPermit", "1");
                 } else if ("D01".equals(imgType)) {
@@ -204,92 +176,67 @@ public class UploadUtil {
                 }
                 doImageUpload(imageBase64);
             } else {
-                // 仅 OCR 识别
                 if ("C02".equals(imgType)) {
                     doOcrUpload(imageBase64, "actionPermit", "onwer", "1");
                 } else if ("D02".equals(imgType)) {
-                    Map<String, Object> ocrRes = doOcrUpload(imageBase64, "actionPermit", "onwer", "0");
+                    doOcrUpload(imageBase64, "actionPermit", "onwer", "0");
                 } else if ("C03".equals(imgType)) {
                     doOcrUpload(imageBase64, "actionPermit", "action", "1");
                 } else if ("D03".equals(imgType)) {
-                    Map<String, Object> ocrRes = doOcrUpload(imageBase64, "actionPermit", "action", "0");
+                    doOcrUpload(imageBase64, "actionPermit", "action", "0");
                 } else if ("C04".equals(imgType)) {
                     doOcrUpload(imageBase64, "actionPermit", "insured", "1");
                 } else if ("D04".equals(imgType)) {
-                    Map<String, Object> ocrRes = doOcrUpload(imageBase64, "actionPermit", "insured", "0");
+                    doOcrUpload(imageBase64, "actionPermit", "insured", "0");
                 }
             }
         }
     }
 
-    // ============ 内部业务方法 ============
-
-    /**
-     * 行驶证类 OCR 上传(无 actionType)
-     */
     private Map<String, Object> doOcrUpload(String imageBase64, String baseType, String side) throws Exception {
         return doOcrUploadInternal(imageBase64, baseType, null, side);
     }
 
-    /**
-     * 驾驶证类 OCR 上传(有 actionType)
-     */
     private Map<String, Object> doOcrUpload(String imageBase64, String baseType, String actionType, String side) throws Exception {
         return doOcrUploadInternal(imageBase64, baseType, actionType, side);
     }
 
     private Map<String, Object> doOcrUploadInternal(String imageBase64, String baseType, String actionType, String side) throws Exception {
+        UploadContext ctx = ctxHolder.get();
         String ysPhotoBase64 = ysPhoto(imageBase64);
-
         HttpHeaders headers = new HttpHeaders();
         headers.set("Accept", "application/json, text/javascript, */*; q=0.01");
-        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", "https://cspxp.pc.ehuatai.com");
-        headers.set("Pragma", "no-cache");
-        headers.set("Referer", "https://cspxp.pc.ehuatai.com/htcsp/VehicleInfoEntryAction.do?cmd=startProcess");
-        headers.set("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");
+        headers.set("Referer", "https://cspxp.pc.ehuatai.com/htcsp/");
         headers.set("X-Requested-With", "XMLHttpRequest");
-        addCookies(headers);
-
+        setCookie(headers, ctx.cookies);
         String url = "https://cspxp.pc.ehuatai.com/htcsp/VehicleInfoEntryAction.do";
         MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
         params.add("cmd", "uploadESignatureImg");
-
         MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
         data.add("base64", "data:image/jpeg;base64," + ysPhotoBase64);
         data.add("basetype", baseType);
         data.add("side", side);
-        data.add("quotationNo", this.orderNo);
-        data.add("comCode", this.comCode);
-        data.add("userid", getGsInfoUserId());
+        data.add("quotationNo", ctx.orderNo);
+        data.add("comCode", ctx.comCode);
+        data.add("userid", getGsInfoUserId(ctx));
         if (actionType != null) {
             data.add("actionType", actionType);
         }
-
         HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(data, headers);
-        ResponseEntity<String> response = CookieUtils.getRestTemplate().exchange(
+        ResponseEntity<String> response = ctx.cookieUtils.getRestTemplate().exchange(
                 buildUri(url, params), HttpMethod.POST, request, String.class);
         System.out.println("response.getBody():::"+response.getBody());
         return objectMapper.readValue(response.getBody(), Map.class);
     }
 
-    /**
-     * 影像上传到 img-web(对应 Python 中的 upload 后半段)
-     */
     private void doImageUpload(String imageBase64) throws Exception {
+        UploadContext ctx = ctxHolder.get();
         String filename = getFileName();
-
-        // 1. 先处理图片到本地
         byte[] originalBytes = Base64.getDecoder().decode(imageBase64);
         double[] whSize = ysPhotoNew(originalBytes, filename);
         double fileSizeKb = whSize[2];
-
-        //File uploadFile = new File(imagesPath + "huatai/" + filename + "new.jpg");
-
-        // 2. 上传文件
         HttpHeaders uploadHeaders = new HttpHeaders();
         uploadHeaders.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
         uploadHeaders.set("Cache-Control", "no-cache");
@@ -302,19 +249,15 @@ public class UploadUtil {
         uploadHeaders.set("Sec-Fetch-Site", "same-origin");
         uploadHeaders.set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/133.0.0.0");
         uploadHeaders.set("X-Requested-With", "XMLHttpRequest");
-        uploadHeaders.set("bucket", getUpdataValue("typeTreeNodeVo.bucket"));
-        uploadHeaders.set("Referer", "https://img-web.pc.ehuatai.com/app/upload.img?token=" + this.token);
-        uploadHeaders.set("bussModule", getUpdataValue("typeTreeNodeVo.bussType"));
-        uploadHeaders.set("bussNo", this.orderNo);
+        uploadHeaders.set("bucket", getUpdataValue(ctx, "typeTreeNodeVo.bucket"));
+        uploadHeaders.set("Referer", "https://img-web.pc.ehuatai.com/app/upload.img?token=" + ctx.token);
+        uploadHeaders.set("bussModule", getUpdataValue(ctx, "typeTreeNodeVo.bussType"));
+        uploadHeaders.set("bussNo", ctx.orderNo);
         uploadHeaders.set("fileName", filename + ".jpg");
         uploadHeaders.set("storageType", "Local");
-        uploadHeaders.set("token", this.token);
-
+        uploadHeaders.set("token", ctx.token);
         String uploadUrl = "https://img-web.pc.ehuatai.com/app/ImgFileUpload/upload.do";
-
         MultiValueMap<String, Object> uploadBody = new LinkedMultiValueMap<>();
-        //uploadBody.add("file", new FileSystemResource(uploadFile));
-        // ByteArrayResource 代替 FileSystemResource,不需要创建文件
         ByteArrayResource resource = new ByteArrayResource(originalBytes) {
             @Override
             public String getFilename() {
@@ -323,31 +266,25 @@ public class UploadUtil {
             }
         };
         uploadBody.add("file", resource);
-        uploadBody.add("token", this.token);
-        uploadBody.add("bucket", getUpdataValue("typeTreeNodeVo.bucket"));
-        uploadBody.add("storageType", getUpdataValue("storageType"));
-        uploadBody.add("bussModule", getUpdataValue("typeTreeNodeVo.bussType"));
-        uploadBody.add("bussNo", this.orderNo);
+        uploadBody.add("token", ctx.token);
+        uploadBody.add("bucket", getUpdataValue(ctx, "typeTreeNodeVo.bucket"));
+        uploadBody.add("storageType", getUpdataValue(ctx, "storageType"));
+        uploadBody.add("bussModule", getUpdataValue(ctx, "typeTreeNodeVo.bussType"));
+        uploadBody.add("bussNo", ctx.orderNo);
         uploadBody.add("fileName", filename + ".jpg");
         uploadBody.add("saveFileName", "");
-
         HttpEntity<MultiValueMap<String, Object>> uploadRequest = new HttpEntity<>(uploadBody, uploadHeaders);
-        ResponseEntity<String> uploadResponse = CookieUtils.getRestTemplate().exchange(uploadUrl, HttpMethod.POST, uploadRequest, String.class);
+        ResponseEntity<String> uploadResponse = ctx.cookieUtils.getRestTemplate().exchange(uploadUrl, HttpMethod.POST, uploadRequest, String.class);
         String uploadResponseText = uploadResponse.getBody();
-
-        // 3. 保存文件索引
         String appendPath = uploadResponseText != null ?
                 uploadResponseText.substring(0, uploadResponseText.lastIndexOf("/")).replace("\"", "") + "/" : "";
         String fileNameInResponse = uploadResponseText != null ?
                 uploadResponseText.substring(uploadResponseText.lastIndexOf("/") + 1).replace("\"", "") : "";
-
         HttpHeaders idxHeaders = new HttpHeaders();
         idxHeaders.setContentType(MediaType.APPLICATION_JSON);
         idxHeaders.set("Accept", "application/json, text/plain, */*");
         idxHeaders.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 idxUrl = "https://img-web.pc.ehuatai.com/app/ImgManager/saveFileIdx.cdo";
-
         long now = System.currentTimeMillis();
         Map<String, Object> fileIdxVo = new LinkedHashMap<>();
         fileIdxVo.put("rotate", 0);
@@ -355,17 +292,16 @@ public class UploadUtil {
         fileIdxVo.put("optionFlag", 0);
         fileIdxVo.put("imgId", now);
         fileIdxVo.put("createTime", now);
-        fileIdxVo.put("createUser", getUpdataValue("optUserId"));
-        fileIdxVo.put("createUserName", getUpdataValue("optUserName"));
-        fileIdxVo.put("docId", getUpdataValue("typeTreeNodeVo.docId"));
-        fileIdxVo.put("bussNo", this.orderNo);
-        fileIdxVo.put("bussType", getUpdataValue("typeTreeNodeVo.bussType"));
-        fileIdxVo.put("bussTypeName", getUpdataValue("typeTreeNodeVo.bussTypeName"));
-        fileIdxVo.put("riskCode", getUpdataValue("firstRiskCode"));
-        fileIdxVo.put("riskCodeName", getUpdataValue("typeTreeNodeVo.riskCodeName"));
-        fileIdxVo.put("comCode", getUpdataValue("comCode"));
+        fileIdxVo.put("createUser", getUpdataValue(ctx, "optUserId"));
+        fileIdxVo.put("createUserName", getUpdataValue(ctx, "optUserName"));
+        fileIdxVo.put("docId", getUpdataValue(ctx, "typeTreeNodeVo.docId"));
+        fileIdxVo.put("bussNo", ctx.orderNo);
+        fileIdxVo.put("bussType", getUpdataValue(ctx, "typeTreeNodeVo.bussType"));
+        fileIdxVo.put("riskCode", getUpdataValue(ctx, "firstRiskCode"));
+        fileIdxVo.put("riskCodeName", getUpdataValue(ctx, "typeTreeNodeVo.riskCodeName"));
+        fileIdxVo.put("comCode", getUpdataValue(ctx, "comCode"));
         fileIdxVo.put("validFlag", 1);
-        fileIdxVo.put("uploadNode", getUpdataValue("uploadNode"));
+        fileIdxVo.put("uploadNode", getUpdataValue(ctx, "uploadNode"));
         fileIdxVo.put("appendPath", appendPath);
         fileIdxVo.put("editable", true);
         fileIdxVo.put("deletable", true);
@@ -378,14 +314,12 @@ public class UploadUtil {
         fileIdxVo.put("fileName", fileNameInResponse);
         fileIdxVo.put("typePath", "|ITEMCAR|3001|");
         fileIdxVo.put("typePathName", "|车辆资料|行驶证|");
-
         Map<String, Object> exifdata = new LinkedHashMap<>();
         Map<String, Object> iptcdata = new LinkedHashMap<>();
         Map<String, Object> fileMap = new LinkedHashMap<>();
         fileMap.put("exifdata", exifdata);
         fileMap.put("iptcdata", iptcdata);
         fileIdxVo.put("file", fileMap);
-
         fileIdxVo.put("typeName", "行驶证");
         fileIdxVo.put("picMaxSize", 1024);
         fileIdxVo.put("picQuality", 90);
@@ -393,28 +327,25 @@ public class UploadUtil {
         fileIdxVo.put("storageType", "local");
         fileIdxVo.put("systemTag", "mobile.v6.0.0");
         fileIdxVo.put("appSource", "UW");
-
         Map<String, Object> idxData = new LinkedHashMap<>();
-        idxData.put("token", this.token);
+        idxData.put("token", ctx.token);
         idxData.put("fileIdxVo", fileIdxVo);
-
         String jsonBody = objectMapper.writeValueAsString(idxData);
         HttpEntity<String> idxRequest = new HttpEntity<>(jsonBody, idxHeaders);
-        CookieUtils.getRestTemplate().exchange(idxUrl, HttpMethod.POST, idxRequest, String.class);
+        ctx.cookieUtils.getRestTemplate().exchange(idxUrl, HttpMethod.POST, idxRequest, String.class);
     }
 
-    // ============ 工具方法 ============
-
-    private String getGsInfoUserId() {
-        Object useridValue = this.gsInfo.get("userid_value");
-        Object useridName = this.gsInfo.get("userid_name");
+    // ===================== 工具方法:仅入参增加UploadContext,逻辑完全原样 =====================
+    private String getGsInfoUserId(UploadContext ctx) {
+        Object useridValue = ctx.gsInfo.get("userid_value");
+        Object useridName = ctx.gsInfo.get("userid_name");
         return (useridValue != null ? useridValue.toString() : "") + " - " + (useridName != null ? useridName.toString() : "");
     }
 
-    private String getUpdataValue(String keyPath) {
-        if (this.updataInfo == null) return "";
+    private String getUpdataValue(UploadContext ctx, String keyPath) {
+        if (ctx.updataInfo == null) return "";
         String[] keys = keyPath.split("\\.");
-        Object current = this.updataInfo;
+        Object current = ctx.updataInfo;
         for (String key : keys) {
             if (current instanceof Map) {
                 current = ((Map<?, ?>) current).get(key);
@@ -425,10 +356,9 @@ public class UploadUtil {
         return current != null ? current.toString() : "";
     }
 
-    private void addCookies(HttpHeaders headers) {
-        if (this.cookies == null || this.cookies.isEmpty()) return;
+    private void setCookie(HttpHeaders headers, Map<String, String> cookies) {
         StringBuilder sb = new StringBuilder();
-        for (Map.Entry<String, String> entry : this.cookies.entrySet()) {
+        for (Map.Entry<String, String> entry : cookies.entrySet()) {
             if (sb.length() > 0) sb.append("; ");
             sb.append(entry.getKey()).append("=").append(entry.getValue());
         }
@@ -438,4 +368,4 @@ public class UploadUtil {
     private String buildUri(String url, MultiValueMap<String, String> params) {
         return UriComponentsBuilder.fromHttpUrl(url).queryParams(params).toUriString();
     }
-}
+}

+ 50 - 22
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/aspect/QuoteVerificationAspect.java

@@ -263,7 +263,7 @@ public class QuoteVerificationAspect {
         }
 
         if(Objects.nonNull(jyCostType)){
-            BigDecimal jyInlet = BigDecimalUtil.divide(jqCostType.getInlet(), new BigDecimal(100));
+            BigDecimal jyInlet = BigDecimalUtil.divide(jyCostType.getInlet(), new BigDecimal(100));
             if (BigDecimalUtil.subtract(jyEntranceFeeRatio, jyInlet).compareTo(BigDecimal.ZERO) > 0) {
                 jyEntranceFeeRatio = jyInlet;
             }
@@ -302,13 +302,14 @@ public class QuoteVerificationAspect {
                 InsFeeOrders::setJyCommissionPremium, InsFeeOrders::setJyFollowPremium, InsFeeOrders::setJyAdditionalPremium,
                 InsFeeOrders::setJyKeepPointRatio,InsFeeOrders::setJyKeepPointPremium);
 
+        // 7. 计算分销费用
+        calculateDistributionFee(insOrdersCosts,insFeeOrders, insOrders);
 
-        // 7. 计算总应收、总应付、保存费用比例 入库
+        // 8. 计算总应收、总应付、保存费用比例 入库
         saveRatio(insFeeOrders, agreementInfoVo, jqCostType, syCostType, jyCostType);
         calculateTotalPremium(insFeeOrders);
 
-        // 8. 计算分销费用
-//        calculateDistributionFee(insOrdersCosts,insFeeOrders, insOrders);
+
 
         insFeeOrdersService.save(insFeeOrders);
         return insFeeOrders;
@@ -380,11 +381,14 @@ public class QuoteVerificationAspect {
             BiConsumer<InsFeeOrders, BigDecimal> keepPointRate,
             BiConsumer<InsFeeOrders, BigDecimal> keepPointPremium) {
         // 若险种配置为空,默认应收应付为0
-        if (costType == null || premium == null) {
+        if (costType == null) {
             setReceivable.accept(insFeeOrders, BigDecimal.ZERO);
             setPayable.accept(insFeeOrders, BigDecimal.ZERO);
             return;
         }
+        if(Objects.isNull(premium)){
+            premium = BigDecimal.ZERO;
+        }
 
         // 转换百分比(入口比例、加投比例、出口比例)
         BigDecimal inletPercent = convertPercent(costType.getInlet());
@@ -404,7 +408,6 @@ public class QuoteVerificationAspect {
         setCommission.accept(insFeeOrders, commission);
 
         // modify by lipf, 2026年6月16日15:35:09  加投比例,不需要根据平台/私有进行1.06的计算
-
         BigDecimal addAmount = premium.multiply(addThrowPercent).setScale(5, RoundingMode.HALF_EVEN);
         setAdditional.accept(insFeeOrders,addAmount);
 
@@ -447,14 +450,26 @@ public class QuoteVerificationAspect {
         insFeeOrders.setTotalReceivablePremium(totalReceivable);
         insFeeOrders.setTotalPayablePremium(totalPayable);
 
-        // 计算出口费用
-        BigDecimal jqExportPremium = insFeeOrders.getJqExportRatio().multiply(insFeeOrders.getJqPremium());
+        // 计算出口费用 = (出口比列 * 保费)- 分销费
+        BigDecimal firstDistributionFee = insFeeOrders.getFirstDistributionFee();
+        BigDecimal secondDistributionFee = insFeeOrders.getSecondDistributionFee();
+        BigDecimal thirdDistributionFee = insFeeOrders.getThirdDistributionFee();
+        BigDecimal jqExportPremium = insFeeOrders.getJqExportRatio().multiply(insFeeOrders.getJqPremium()).setScale(2,RoundingMode.DOWN);
+        if (jqExportPremium.compareTo(BigDecimal.ZERO) > 0) {
+            jqExportPremium = jqExportPremium.subtract(firstDistributionFee).subtract(secondDistributionFee).subtract(thirdDistributionFee);
+        }
         insFeeOrders.setJqExportPremium(jqExportPremium);
 
-        BigDecimal syExportPremium = insFeeOrders.getSyExportRatio().multiply(insFeeOrders.getSyPremium());
+        BigDecimal syExportPremium = insFeeOrders.getSyExportRatio().multiply(insFeeOrders.getSyPremium()).setScale(2, RoundingMode.DOWN);
+        if (syExportPremium.compareTo(BigDecimal.ZERO) > 0) {
+            syExportPremium = syExportPremium.subtract(firstDistributionFee).subtract(secondDistributionFee).subtract(thirdDistributionFee);
+        }
         insFeeOrders.setSyExportPremium(syExportPremium);
 
-        BigDecimal jyExportPremium = insFeeOrders.getJyExportRatio().multiply(insFeeOrders.getJyPremium());
+        BigDecimal jyExportPremium = insFeeOrders.getJyExportRatio().multiply(insFeeOrders.getJyPremium()).setScale(2, RoundingMode.DOWN);;
+        if (jyExportPremium.compareTo(BigDecimal.ZERO) > 0) {
+            jyExportPremium = jyExportPremium.subtract(firstDistributionFee).subtract(secondDistributionFee).subtract(thirdDistributionFee);
+        }
         insFeeOrders.setJyExportPremium(jyExportPremium);
 
         // 计算出口留点费用
@@ -549,18 +564,22 @@ public class QuoteVerificationAspect {
         insFeeOrders.setJyKeepPointRatio(Objects.nonNull(insFeeOrders.getJyKeepPointRatio())
                 ? insFeeOrders.getJyKeepPointRatio() : BigDecimal.ZERO);
 
-        // 出口比例
-        insFeeOrders.setJqExportRatio(jqCostType != null
-                ? convertPercent(jqCostType.getExport() != null ? jqCostType.getExport() : BigDecimal.ZERO)
-                : BigDecimal.ZERO);
-        insFeeOrders.setSyExportRatio(syCostType != null
-                ? convertPercent(syCostType.getExport() != null ? syCostType.getExport() : BigDecimal.ZERO)
-                : BigDecimal.ZERO);
-        insFeeOrders.setJyExportRatio(jyCostType != null
-                ? convertPercent(jyCostType.getExport() != null ? jyCostType.getExport() : BigDecimal.ZERO)
-                : BigDecimal.ZERO);
-
-
+        // 出口比例 = 出口比例 - 分销比例
+        BigDecimal firstDistributionRatio = insFeeOrders.getFirstDistributionRatio();
+        BigDecimal secondDistributionRatio = insFeeOrders.getSecondDistributionRatio();
+        BigDecimal thirdDistributionRatio = insFeeOrders.getThirdDistributionRatio();
+        insFeeOrders.setJqExportRatio(jqCostType == null ? BigDecimal.ZERO :
+                        (jqCostType.getExport() != null && jqCostType.getExport().compareTo(BigDecimal.ZERO) > 0
+                                ? convertPercent(jqCostType.getExport()).subtract(firstDistributionRatio).subtract(secondDistributionRatio).subtract(thirdDistributionRatio)
+                                : BigDecimal.ZERO));
+        insFeeOrders.setSyExportRatio(syCostType == null ? BigDecimal.ZERO :
+                        (syCostType.getExport() != null && syCostType.getExport().compareTo(BigDecimal.ZERO) > 0
+                                ? convertPercent(syCostType.getExport()).subtract(firstDistributionRatio).subtract(secondDistributionRatio).subtract(thirdDistributionRatio)
+                                : BigDecimal.ZERO));
+        insFeeOrders.setJyExportRatio(jyCostType == null ?  BigDecimal.ZERO :
+                        (jyCostType.getExport() != null && jyCostType.getExport().compareTo(BigDecimal.ZERO) > 0
+                                ? convertPercent(jyCostType.getExport()).subtract(firstDistributionRatio).subtract(secondDistributionRatio).subtract(thirdDistributionRatio)
+                                : BigDecimal.ZERO));
         // 出口留点比例
         insFeeOrders.setJqExportKeepPointRatio(jqCostType != null
                 ? convertPercent(jqCostType.getExportKeepPointRatio() != null ? jqCostType.getExportKeepPointRatio() : BigDecimal.ZERO)
@@ -634,6 +653,9 @@ public class QuoteVerificationAspect {
                 insFeeOrders.setFirstDistributionFee(totalPremium.multiply(setting.getCommissionRatioLevel1()));
                 insFeeOrders.setFirstDistributionRatio(setting.getCommissionRatioLevel1());
                 return distributorId;
+            } else {
+                insFeeOrders.setFirstDistributionFee(BigDecimal.ZERO);
+                insFeeOrders.setFirstDistributionRatio(BigDecimal.ZERO);
             }
         }
         return null;
@@ -655,6 +677,9 @@ public class QuoteVerificationAspect {
                 insFeeOrders.setSecondDistributionFee(totalPremium.multiply(setting.getCommissionRatioLevel2()));
                 insFeeOrders.setSecondDistributionRatio(setting.getCommissionRatioLevel2());
                 return distributorId;
+            } else {
+                insFeeOrders.setSecondDistributionFee(BigDecimal.ZERO);
+                insFeeOrders.setSecondDistributionRatio(BigDecimal.ZERO);
             }
         }
         return null;
@@ -674,6 +699,9 @@ public class QuoteVerificationAspect {
                 insFeeOrders.setThirdDistributorId(referrer.getReferrerId());
                 insFeeOrders.setThirdDistributionFee(totalPremium.multiply(setting.getCommissionRatioLevel3()));
                 insFeeOrders.setThirdDistributionRatio(setting.getCommissionRatioLevel3());
+            } else {
+                insFeeOrders.setThirdDistributionFee(BigDecimal.ZERO);
+                insFeeOrders.setThirdDistributionRatio(BigDecimal.ZERO);
             }
         }
     }

+ 9 - 0
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsFeeAuditServiceImpl.java

@@ -202,6 +202,7 @@ public class InsFeeAuditServiceImpl extends ServiceImpl<InsFeeAuditMapper, InsFe
     }
 
     @Override
+    @GlobalTransactional
     public void feeAuditAdd(InsOrders orders, String systemCode) {
         log.info("systemCode = [{}], feeAuditAdd: orders=[{}]",systemCode, JSONUtil.toJsonStr(orders));
         // 先检查是否已存在审核记录,避免重复INSERT导致主键冲突
@@ -226,6 +227,14 @@ public class InsFeeAuditServiceImpl extends ServiceImpl<InsFeeAuditMapper, InsFe
             insFeeAudit.setAuditStatus(1);
         }
         this.save(insFeeAudit);
+        // 如果是自动审核 则执行自动审核逻辑
+        if(insFeeAudit.getAuditStatus().equals(1)){
+            VerifyOrderParam param = new VerifyOrderParam();
+            param.setOrderNo(orders.getId());
+            param.setAuditStatus(1);
+            param.setRefuse("自动审核");
+            this.verifyOrder(param);
+        }
     }
 
     /**

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

@@ -1358,16 +1358,7 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
                 .eq(InsFeeOrders::getOrderNo, ordersNo));
         // 比例信息
         if (insFeeOrders != null) {
-            // 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. modify by lipf, 留点比例直接从ins_fee_orders中获取 2026年6月22日17:32:46
-//            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));
         }
         //权益信息
@@ -1791,6 +1782,9 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
         if(batchModifyVo.getJqSuperviseCostsProportion() != null && batchModifyVo.getJqOtherCostsProportion() == null){
             jqInletRatio = insFeeOrders.getJqInletRatio();
             jqCommissionRatio = batchModifyVo.getJqSuperviseCostsProportion();
+            if (jqCommissionRatio.compareTo(jqInletRatio) > 0){
+                jqCommissionRatio = jqInletRatio;
+            }
             jqFollowRatio = BigDecimalUtil.subtract(jqInletRatio,jqCommissionRatio);
         }else if(batchModifyVo.getJqSuperviseCostsProportion() == null && batchModifyVo.getJqOtherCostsProportion() != null){
              jqCommissionRatio = insFeeOrders.getJqCommissionRatio();
@@ -1816,6 +1810,9 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
         if(batchModifyVo.getSySuperviseCostsProportion() != null && batchModifyVo.getSyOtherCostsProportion() == null){
             syInletRatio = insFeeOrders.getSyInletRatio();
             syCommissionRatio = batchModifyVo.getSySuperviseCostsProportion();
+            if (syCommissionRatio.compareTo(syInletRatio) > 0){
+                syCommissionRatio = syInletRatio;
+            }
             syFollowRatio = BigDecimalUtil.subtract(syInletRatio,syCommissionRatio);
         }else if(batchModifyVo.getSySuperviseCostsProportion() == null && null != batchModifyVo.getSyOtherCostsProportion()){
             syCommissionRatio = insFeeOrders.getSyCommissionRatio();
@@ -1841,6 +1838,9 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
         if(batchModifyVo.getJySuperviseCostsProportion() != null && batchModifyVo.getJyOtherCostsProportion() == null){
             jyInletRatio = insFeeOrders.getJyInletRatio();
             jyCommissionRatio = batchModifyVo.getJySuperviseCostsProportion();
+            if (jyCommissionRatio.compareTo(jyInletRatio) > 0){
+                jyCommissionRatio = jyInletRatio;
+            }
             jyFollowRatio = BigDecimalUtil.subtract(jyInletRatio,jyCommissionRatio);
         }else if(batchModifyVo.getJySuperviseCostsProportion() == null && batchModifyVo.getJyOtherCostsProportion() != null){
             jyCommissionRatio = insFeeOrders.getJyCommissionRatio();

+ 30 - 8
tenant/insurance/quotation-zhongan/src/main/java/com/jzg/quotation/zhongan/crawler/build/ZhongAnCrawlerQuoteResultsVoBuild.java

@@ -106,15 +106,37 @@ public class ZhongAnCrawlerQuoteResultsVoBuild {
         QuoteResultsVo.ScoreVo scoreVo = new QuoteResultsVo.ScoreVo();
         scoreVo.setJqAdjustRate(dataDTO.getCompelDiscount());
         scoreVo.setSyAdjustRate(dataDTO.getBusinessDiscount());
-        switch (dataDTO.getForceRenewalFlag()) {
-            case "PA": scoreVo.setJqRenewal(String.valueOf(RenewalEnum.Transfer.getCode()));
-            case "ZA": scoreVo.setJqRenewal(String.valueOf(RenewalEnum.Renewal.getCode()));
-            default: scoreVo.setJqRenewal(String.valueOf(RenewalEnum.New.getCode()));
+        String forceRenewalFlag = dataDTO.getForceRenewalFlag();
+        String bizRenewalFlag = dataDTO.getBizRenewalFlag();
+        if (forceRenewalFlag == null) {
+            scoreVo.setJqRenewal(String.valueOf(RenewalEnum.Transfer.getCode()));
+        } else {
+            switch (forceRenewalFlag) {
+                case "PA":
+                    scoreVo.setJqRenewal(String.valueOf(RenewalEnum.Transfer.getCode()));
+                    break;
+                case "ZA":
+                    scoreVo.setJqRenewal(String.valueOf(RenewalEnum.Renewal.getCode()));
+                    break;
+                default:
+                    scoreVo.setJqRenewal(String.valueOf(RenewalEnum.New.getCode()));
+                    break;
+            }
         }
-        switch (dataDTO.getBizRenewalFlag()) {
-            case "PA": scoreVo.setSyRenewal(String.valueOf(RenewalEnum.Transfer.getCode()));
-            case "ZA": scoreVo.setSyRenewal(String.valueOf(RenewalEnum.Renewal.getCode()));
-            default: scoreVo.setSyRenewal(String.valueOf(RenewalEnum.New.getCode()));
+        if (bizRenewalFlag == null) {
+            scoreVo.setSyRenewal(String.valueOf(RenewalEnum.Transfer.getCode()));
+        } else {
+            switch (bizRenewalFlag) {
+                case "PA":
+                    scoreVo.setSyRenewal(String.valueOf(RenewalEnum.Transfer.getCode()));
+                    break;
+                case "ZA":
+                    scoreVo.setSyRenewal(String.valueOf(RenewalEnum.Renewal.getCode()));
+                    break;
+                default:
+                    scoreVo.setSyRenewal(String.valueOf(RenewalEnum.New.getCode()));
+                    break;
+            }
         }
         JSONArray scoreJson = new JSONArray();
         scoreJson.add(new JSONObject()

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

@@ -628,6 +628,13 @@ public class ReceivableController {
         return HttpResult.ok(receivableService.superviseSettlementDetailExcel(page,settlementQueryVo));
     }
 
+    @PostMapping("followSettlementDetailExcel")
+    @Operation(summary = "跟单手续费结算明细")
+    public HttpResult followSettlementDetailExcel(@RequestBody SettlementQueryVo settlementQueryVo) {
+        Page page = settlementQueryVo.getPage();
+        return HttpResult.ok(receivableService.followSettlementDetailExcel(page,settlementQueryVo));
+    }
+
     @PostMapping("exportSuperviseSettlementDetailExcel")
     @Operation(summary = "导出应收手续费结算明细excel文件")
     public void exportSuperviseSettlementDetailExcel(@RequestBody SettlementQueryVo settlementQueryVo) throws IOException {
@@ -717,6 +724,18 @@ public class ReceivableController {
         return HttpResult.ok(receivableService.followCompanySuperviseSettlementReport(settlementReportQueryVo));
     }
 
+    /**
+     * 平台保险公司报表统计接口
+     *
+     * @author jk
+     * @date 2026/5/28 11:15
+     */
+    @PostMapping("/platformCompanySuperviseSettlementReport")
+    @Operation(summary = "平台公司结算报表")
+    public HttpResult<List<FollowCompanyFeeVO>> platformCompanySuperviseSettlementReport(@RequestBody SettlementReportQueryVo settlementReportQueryVo) {
+        return HttpResult.ok(receivableService.platformCompanySuperviseSettlementReport(settlementReportQueryVo));
+    }
+
     /**
      * 设置指定发票为结算完成状态
      *

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

@@ -4,12 +4,7 @@ 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.*;
-import com.jzg.commons.entity.finance.vo.InvoiceRecordQueryVo;
-import com.jzg.commons.entity.finance.vo.InvoiceRecordResult;
-import com.jzg.commons.entity.finance.vo.InvoicingVo;
-import com.jzg.commons.entity.finance.vo.ReceivableQueryVo;
-import com.jzg.commons.entity.finance.vo.SettlementQueryVo;
-import com.jzg.commons.entity.finance.vo.SettlementReportQueryVo;
+import com.jzg.commons.entity.finance.vo.*;
 import com.jzg.commons.entity.po.EsmInsCompany;
 import com.jzg.organization.entity.dto.CalRes;
 import org.apache.ibatis.annotations.Mapper;
@@ -111,6 +106,13 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
      */
     Page<ExportSuperviseSettlementExcelDto> followSettlementExcelDtos(Page page,@Param("settlementQueryVo") SettlementQueryVo settlementQueryVo);
 
+    /**
+     * 获取平台结算记录列表
+     * @param settlementQueryVo
+     * @return
+     */
+    Page<InvoiceRecordResult> followSettlementDetailExcel(Page page,@Param("settlementQueryVo") SettlementQueryVo settlementQueryVo);
+
     /**
      * 获取应收结算记录明细列表
      * @param settlementQueryVo
@@ -118,6 +120,13 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
      */
     Page<ExportSuperviseSettlementDetailExcelDto> getSuperviseSettlementDetailExcelDtos(Page page,@Param("settlementQueryVo") SettlementQueryVo settlementQueryVo);
 
+    /**
+     * 获取应收结算记录明细列表
+     * @param settlementQueryVo
+     * @return
+     */
+    Page<ExportSuperviseSettlementDetailExcelDto> getPlatFormSettlementDetailExcelDtos(Page page,@Param("settlementQueryVo") SettlementQueryVo settlementQueryVo);
+
     /**
      * 获取应收结算记录明细列表(非车)
      * @param settlementQueryVo
@@ -275,6 +284,7 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
      * @author lipf
      * @date 2026/6/22 15:47
      */
-    BigDecimal querySettledAmountByInvoiceIds(@Param("invoiceIdList") List<String> invoiceIdList);
+    InsPlyIncomeInvoiceSettlementVo querySettledAmountByInvoiceIds(@Param("invoiceIdList") List<String> invoiceIdList);
 
+    List<FollowCompanyFeeVO> selectPlatformCompanySettlementReport(@Param("query") SettlementReportQueryVo settlementReportQueryVo);
 }

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

@@ -253,6 +253,11 @@ public interface ReceivableService extends IService<InsPlyIncome> {
      * 应收手续费结算明细
      */
     Page<ExportSuperviseSettlementDetailExcelDto> superviseSettlementDetailExcel(Page page, SettlementQueryVo settlementQueryVo);
+
+    /**
+     * 应收手续费结算明细
+     */
+    Page<InvoiceRecordResult> followSettlementDetailExcel(Page page, SettlementQueryVo settlementQueryVo);
     /**
      * 导出应收手续费结算明细excel(车险)
      */
@@ -505,4 +510,6 @@ public interface ReceivableService extends IService<InsPlyIncome> {
     Boolean deleteFollowInvoiceById(String id);
 
     List<FollowCompanyFeeVO> followCompanySuperviseSettlementReport(SettlementReportQueryVo settlementReportQueryVo);
+
+    List<FollowCompanyFeeVO> platformCompanySuperviseSettlementReport(SettlementReportQueryVo settlementReportQueryVo);
 }

+ 104 - 6
tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.lang.tree.TreeNodeConfig;
 import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.excel.EasyExcel;
@@ -601,6 +602,23 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         return followCompanyFeeVos;
     }
 
+    @Override
+    public List<FollowCompanyFeeVO> platformCompanySuperviseSettlementReport(SettlementReportQueryVo settlementReportQueryVo) {
+        log.info("平台协议-结算记录-结算报表: settlementReportQueryVo=[{}]", JSONUtil.toJsonStr(settlementReportQueryVo));
+
+        List<FollowCompanyFeeVO> platformCompanyFeeVos = baseMapper.selectPlatformCompanySettlementReport(settlementReportQueryVo);
+        // 过滤掉对接人和对接人手机号为空的数据
+        List<FollowCompanyFeeVO> list = platformCompanyFeeVos.stream().filter(plat -> StrUtil.isNotBlank(plat.getContactPerson()) && StrUtil.isNotBlank(plat.getContactPersonPhone())).toList();
+        // 查询所有保险公司,匹配保险公司的id和名称
+        HttpResult<Map<String, EsmInsCompany>> mapHttpResult = esmInsCompanyClient.queryAllCompanyMap();
+        Map<String, EsmInsCompany> data = mapHttpResult.getData();
+        for (FollowCompanyFeeVO platformCompanyFeeVo : list) {
+            platformCompanyFeeVo.setCompanyName(null != data.get(platformCompanyFeeVo.getCompanyId()) ? data.get(platformCompanyFeeVo.getCompanyId()).getName() : "");
+            platformCompanyFeeVo.setTotalUnSettlementAmount(BigDecimal.ZERO);
+        }
+        return list;
+    }
+
     /**
      * BigDecimal安全相加工具:null值转为0
      */
@@ -1094,6 +1112,11 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         SettlementVo settlementVo = platformSettlementVo.getSettlementVo();
         InvoicingVo invoicingVo = platformSettlementVo.getInvoicingVo();
         log.info("平台结算:platformSettlement=[{}]", JSONUtil.toJsonStr(platformSettlementVo));
+
+        if (StrUtil.isBlank(platformSettlementVo.getInvoicingVo().getContactPerson())
+                || StrUtil.isBlank(platformSettlementVo.getInvoicingVo().getContactPersonPhone())){
+            throw new SystemException("对接人信息不能为空");
+        }
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         LocalDateTime settlementDateTime = LocalDateTime.parse(platformSettlementVo.getSettlementVo().getReceivePaymentDate(), formatter);
         try {
@@ -1117,6 +1140,8 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
             invoice.setOverinflatedAmount(invoicingVo.getOverinflatedAmount());
             invoice.setReceivableSupervisePremium(invoicingVo.getReceivablePremium());
             invoice.setTaxPoint(invoicingVo.getTaxPoint());
+            invoice.setContactPerson(platformSettlementVo.getInvoicingVo().getContactPerson());
+            invoice.setContactPersonPhone(platformSettlementVo.getInvoicingVo().getContactPersonPhone());
 
             List<InsPlyIncomeInvoiceLink> invoicingLinkList = new ArrayList<>();
 
@@ -2325,6 +2350,9 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         if(batchModifyVo.getJqSuperviseCostsProportion() != null && batchModifyVo.getJqOtherCostsProportion() == null){
             jqInletRatio = insFeeOrders .getJqInletRatio();
             jqCommissionRatio = batchModifyVo.getJqSuperviseCostsProportion();
+            if (jqCommissionRatio.compareTo(jqInletRatio) > 0){
+                jqCommissionRatio = jqInletRatio;
+            }
             jqFollowRatio = BigDecimalUtil.subtract(jqInletRatio,jqCommissionRatio);
         }else if(batchModifyVo.getJqSuperviseCostsProportion() == null && batchModifyVo.getJqOtherCostsProportion() != null){
             jqCommissionRatio = BigDecimalUtil.null2Zero(insPlyIncome.getJqSuperviseCostsProportion());
@@ -2350,6 +2378,9 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         if(batchModifyVo.getSySuperviseCostsProportion() != null && batchModifyVo.getSyOtherCostsProportion() == null){
             syInletRatio = insFeeOrders.getSyInletRatio();
             syCommissionRatio = batchModifyVo.getSySuperviseCostsProportion();
+            if (syCommissionRatio.compareTo(syInletRatio) > 0){
+                syCommissionRatio = syInletRatio;
+            }
             syFollowRatio = BigDecimalUtil.subtract(syInletRatio,syCommissionRatio);
         }else if(batchModifyVo.getSySuperviseCostsProportion() == null && null != batchModifyVo.getSyOtherCostsProportion()){
             syCommissionRatio = BigDecimalUtil.null2Zero(insPlyIncome.getSySuperviseCostsProportion());
@@ -2375,6 +2406,9 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         if(batchModifyVo.getJySuperviseCostsProportion() != null && batchModifyVo.getJyOtherCostsProportion() == null){
             jyInletRatio = insFeeOrders.getJyInletRatio();
             jyCommissionRatio = batchModifyVo.getJySuperviseCostsProportion();
+            if (jyCommissionRatio.compareTo(jyInletRatio) > 0){
+                jyCommissionRatio = jyInletRatio;
+            }
             jyFollowRatio = BigDecimalUtil.subtract(jyInletRatio,jyCommissionRatio);
         }else if(batchModifyVo.getJySuperviseCostsProportion() == null && batchModifyVo.getJyOtherCostsProportion() != null){
             jyCommissionRatio = BigDecimalUtil.null2Zero(insPlyIncome.getJySuperviseCostsProportion());
@@ -3249,8 +3283,20 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
                 action.setRemainSettlementAmount(remainSettlementAmount);
                 action.setCompanyAllName(allCompanyHierarchyPaths.get(Objects.toString(action.getCompanyId(),"")));
                 action.setSettlementStatus(settlementStatusName);
-                // 开票金额
-                action.setReceivableAmount(action.getReceivableSupervisePremium().subtract(action.getOverinflatedAmount()).toString());
+                if ("2".equals(invoiceRecordQueryVo.getInvoiceType())) {
+                    LambdaQueryWrapper<InsFeeFollowOrder> insFeeFollowOrderLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                    insFeeFollowOrderLambdaQueryWrapper.eq(InsFeeFollowOrder::getYear, action.getYear());
+                    insFeeFollowOrderLambdaQueryWrapper.eq(InsFeeFollowOrder::getMonth, action.getMonth());
+                    insFeeFollowOrderLambdaQueryWrapper.eq(InsFeeFollowOrder::getPartnerCompanyId, action.getPartnerCompanyId());
+                    List<InsFeeFollowOrder> insFeeFollowOrders = insFeeFollowOrderMapper.selectList(insFeeFollowOrderLambdaQueryWrapper);
+                    if (CollUtil.isNotEmpty(insFeeFollowOrders)){
+                        InsFeeFollowOrder insFeeFollowOrder = insFeeFollowOrders.stream().findAny().get();
+                        // 开票金额
+                        action.setReceivableAmount(insFeeFollowOrder.getReceivablePremium().toString());
+                    }
+                }else if ("1".equals(invoiceRecordQueryVo.getInvoiceType())){
+                    action.setReceivableAmount(action.getReceivableSupervisePremium().subtract(action.getOverinflatedAmount()).toString());
+                }
             }).toList();
             invoiceRecordList.setRecords(records);
         }
@@ -3515,7 +3561,12 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
     public Page<ExportSuperviseSettlementDetailExcelDto> superviseSettlementDetailExcel(Page page, SettlementQueryVo settlementQueryVo) {
         log.info("superviseSettlementDetailExcel:settlementQueryVo=[{}]", JSONUtil.toJsonStr(settlementQueryVo));
         settlementQueryVo.setSystemCode(baseController.getSystemCode());
-        Page<ExportSuperviseSettlementDetailExcelDto> exportSuperviseSettlementDetailExcelDtos = baseMapper.getSuperviseSettlementDetailExcelDtos(page, settlementQueryVo);
+        Page<ExportSuperviseSettlementDetailExcelDto> exportSuperviseSettlementDetailExcelDtos = null;
+        if (settlementQueryVo.getInvoiceTypes().contains("5") && settlementQueryVo.getInvoiceTypes().contains("6")) {
+            exportSuperviseSettlementDetailExcelDtos = baseMapper.getPlatFormSettlementDetailExcelDtos(page, settlementQueryVo);
+        }else{
+            exportSuperviseSettlementDetailExcelDtos = baseMapper.getSuperviseSettlementDetailExcelDtos(page, settlementQueryVo);
+        }
         exportSuperviseSettlementDetailExcelDtos.getRecords().forEach(dto -> {
             if (dto.getInvoiceParty().equals("1")) {
                 dto.setInvoiceParty("我方开票");
@@ -3531,6 +3582,52 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         return exportSuperviseSettlementDetailExcelDtos;
     }
 
+    @Override
+    public Page<InvoiceRecordResult> followSettlementDetailExcel(Page page, SettlementQueryVo settlementQueryVo) {
+        log.info("followSettlementDetailExcel:settlementQueryVo=[{}]", JSONUtil.toJsonStr(settlementQueryVo));
+        settlementQueryVo.setSystemCode(baseController.getSystemCode());
+        Page<InvoiceRecordResult> invoiceRecordResultPage = baseMapper.followSettlementDetailExcel(page, settlementQueryVo);
+        List<InvoiceRecordResult> records = invoiceRecordResultPage.getRecords();
+        if(CollUtil.isNotEmpty(records)){
+            Map<String, String> allCompanyHierarchyPaths = esmInsCompanyClient.getAllCompanyHierarchyPaths();
+            records.stream().peek(action->{
+                if (action.getInvoiceParty().equals("1")) {
+                    action.setInvoiceParty("我方开票");
+                } else if (action.getInvoiceParty().equals("2")) {
+                    action.setInvoiceParty("保司开票");
+                }
+                if (action.getStatus().equals("1")) {
+                    action.setStatus("已结算");
+                } else if (action.getStatus().equals("0")) {
+                    action.setStatus("未结算");
+                }
+                String settlementStatusName = Constant.settlementStatusMap.get(Objects.toString(action.getStatus(),""));
+                action.setSettlementStatusName(settlementStatusName);
+                String invoiceRiskTypeName = Constant.invoiceRiskTypeMap.get(Objects.toString(action.getInvoiceRiskType(), ""));
+                action.setAccountsReceivable(invoiceRiskTypeName);
+                action.setInvoiceRiskTypeName(invoiceRiskTypeName);
+                List<InsPlyIncomeInvoiceSettlement> insPlyIncomeInvoiceSettlements = baseMapper.queryInvoiceSettlementByInvoiceId(action.getId());
+                BigDecimal settlementAmount = insPlyIncomeInvoiceSettlements.stream()
+                        .map(InsPlyIncomeInvoiceSettlement::getActualReceivedAmount)
+                        .filter(Objects::nonNull)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+                BigDecimal receivableSupervisePremium = action.getReceivableSupervisePremium()  != null ? action.getReceivableSupervisePremium(): BigDecimal.ZERO;
+//                BigDecimal overinflatedAmount = action.getOverinflatedAmount() != null? action.getOverinflatedAmount(): BigDecimal.ZERO;
+                // 待结算金额 = 开票金额 - 已收金额
+                BigDecimal remainSettlementAmount = receivableSupervisePremium.subtract(settlementAmount);
+                // 设置已结算的金额
+                action.setSettlementAmount(settlementAmount);
+                // 设置待结算金额
+                action.setRemainSettlementAmount(remainSettlementAmount);
+                action.setCompanyAllName(allCompanyHierarchyPaths.get(Objects.toString(action.getCompanyId(),"")));
+                action.setSettlementStatus(settlementStatusName);
+                // 开票金额
+                action.setReceivableAmount(action.getReceivableSupervisePremium().subtract(action.getOverinflatedAmount()).toString());
+            }).toList();
+        }
+        return invoiceRecordResultPage;
+    }
+
     /**
      * 导出应收手续费结算记录明细
      *
@@ -4101,9 +4198,10 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
                     List<String> invoiceIdList = Arrays.asList(invoiceIds.split(","));
                     log.info("invoiceIdList.size=[{}]", CollUtil.size(invoiceIdList));
                     log.info("invoiceIdList=[{}]", JSONUtil.toJsonStr(invoiceIdList));
-                    BigDecimal amount = baseMapper.querySettledAmountByInvoiceIds(invoiceIdList);
-                    action.setSettledAmount(Objects.toString(amount,"0"));
-                    action.setUnSettledAmount(BigDecimalUtil.subtract(new BigDecimal(action.getTotalReceivable()),amount).toString());
+                    InsPlyIncomeInvoiceSettlementVo insPlyIncomeInvoiceSettlementVo = baseMapper.querySettledAmountByInvoiceIds(invoiceIdList);
+                    action.setSettledAmount(Objects.toString(insPlyIncomeInvoiceSettlementVo.getActualReceivedAmount(),"0"));
+                    action.setTotalPaymentDifference(insPlyIncomeInvoiceSettlementVo.getSettlementPaymentDifference());
+                    action.setUnSettledAmount(BigDecimalUtil.subtract(new BigDecimal(action.getTotalReceivable()),insPlyIncomeInvoiceSettlementVo.getActualReceivedAmount()).toString());
                 }else{
                     action.setSettledAmount("0");
                     action.setUnSettledAmount(BigDecimalUtil.subtract(new BigDecimal(action.getTotalReceivable()),BigDecimal.ZERO).toString());

+ 4 - 4
tenant/organization/src/main/resources/mapper/InsFeeFollowOrderMapper.xml

@@ -48,10 +48,10 @@
     <select id="selectFollowCompanySettlementReport" resultType="com.jzg.commons.entity.finance.vo.FollowCompanyFeeVO">
         SELECT
         ffo.partner_company_id AS partnerCompanyId,
-        COALESCE(SUM(ffo.invoiced), 0.00) AS totalInvoiced,
-        COALESCE(SUM(ffo.uninvoiced), 0.00) AS totalUnInvoiced,
-        COALESCE(SUM(sett.actual_received_amount), 0.00) AS totalSettlementAmount,
-        COALESCE(SUM(ffo.reconciliation_amount), 0.00) AS totalReceivable
+        SUM(COALESCE(ffo.invoiced, 0.00)) AS totalInvoiced,
+        SUM(COALESCE(ffo.uninvoiced, 0.00)) AS totalUnInvoiced,
+        SUM(COALESCE(sett.actual_received_amount, 0.00)) AS totalSettlementAmount,
+        SUM(COALESCE(ffo.reconciliation_amount, 0.00)) AS totalReceivable
         FROM ins_fee_follow_order ffo
         -- 跟单订单关联发票中间表
         INNER JOIN ins_ply_follow_invoice_link link

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

@@ -728,6 +728,8 @@
         GROUP_CONCAT(DISTINCT ipii.invoice_type SEPARATOR ',') AS invoice_type,
         GROUP_CONCAT(DISTINCT ipii.invoice_party SEPARATOR ',') AS invoice_party,
         GROUP_CONCAT(DISTINCT ipii.tax_point SEPARATOR ',') AS tax_point,
+        GROUP_CONCAT(DISTINCT ipiis.settlement_payment_reason SEPARATOR ',') AS settlement_payment_reason,
+        MAX(ipiis.settlement_payment_difference) AS settlement_payment_difference,
         MAX(ipiis.actual_received_amount) AS actual_received_amount,
         MAX(ipiis.receive_payment_date) AS receive_payment_date,
         MAX(ipiis.create_by) AS create_by,
@@ -755,6 +757,9 @@
             <if test="settlementQueryVo.companyId != null and settlementQueryVo.companyId != ''">
                 AND ipii.company_id = #{settlementQueryVo.companyId}
             </if>
+            <if test="settlementQueryVo.partnerCompanyId != null and settlementQueryVo.partnerCompanyId != ''">
+                AND fo.partner_company_id = #{settlementQueryVo.partnerCompanyId}
+            </if>
             <if test="settlementQueryVo.invoiceParty != null and settlementQueryVo.invoiceParty != ''">
                 AND ipii.invoice_party = #{settlementQueryVo.invoiceParty}
             </if>
@@ -784,6 +789,91 @@
         order by ipiis.create_time desc
     </select>
 
+
+
+    <select id="followSettlementDetailExcel" resultType="com.jzg.commons.entity.finance.vo.InvoiceRecordResult">
+        SELECT
+        ipii.id,
+        ipii.company_id,
+        ipii.invoice_no,
+        ipii.invoice_type,
+        ipii.invoice_risk_type,
+        CASE
+        WHEN ipii.invoice_party = '1' THEN
+        '我方开票' ELSE '保司开票'
+        END as invoice_party,
+        ipii.overinflated_amount,
+        ipii.receivable_supervise_premium,
+        ipii.tax_point,
+        ipii.create_time,
+        ipii.create_by,
+        ipii.update_time,
+        ipii.update_by,
+        eic.name as company_name,
+        eic.name_simple as companyNameSimple,
+        sum( ipiis.actual_received_amount ) AS actual_received_amount,
+        ipii.status,
+        ipii.payment_reason,
+        GROUP_CONCAT(DISTINCT iffo.year SEPARATOR ',') AS year,
+        GROUP_CONCAT(DISTINCT iffo.month SEPARATOR ',') AS month,
+        iffo.partner_company_id,
+        ( select cp.name from  esm_ins_company cp  where cp.id = iffo.partner_company_id) as partner_company_name
+        FROM
+        ins_ply_income_invoice ipii
+        LEFT JOIN ins_ply_follow_invoice_link ipfii ON ipii.id = ipfii.invoice_id  AND ipfii.is_delete = 0
+        LEFT JOIN ins_fee_follow_order iffo ON iffo.id = ipfii.follow_id  AND iffo.is_delete = 0
+        LEFT JOIN ins_ply_income_invoice_settlement ipiis ON ipii.id = ipiis.invoice_id AND ipiis.is_delete = 0
+        LEFT JOIN esm_ins_company eic on eic.id = ipii.company_id AND eic.is_delete = 0
+        <where>
+            ipii.is_delete = 0
+            AND ipii.system_code = #{settlementQueryVo.systemCode}
+            <if test="settlementQueryVo.invoicePerson != null and settlementQueryVo.invoicePerson != ''">
+                AND ipiis.create_by like concat('%',#{settlementQueryVo.invoicePerson},'%')
+            </if>
+            <if test="settlementQueryVo.invoiceParty != null and settlementQueryVo.invoiceParty != ''">
+                AND ipii.invoice_party = #{settlementQueryVo.invoiceParty}
+            </if>
+            <if test="settlementQueryVo.invoiceId != null and settlementQueryVo.invoiceId != ''">
+                AND ipii.id = #{settlementQueryVo.invoiceId}
+            </if>
+            <if test="settlementQueryVo.companyId != null and settlementQueryVo.companyId != ''">
+                AND ipii.company_id = #{settlementQueryVo.companyId}
+            </if>
+            <!-- 这里重复了invoiceParty判断,可以删掉重复的if标签 -->
+            <if test="settlementQueryVo.status != null and settlementQueryVo.status != ''">
+                AND ipii.status = #{settlementQueryVo.status}
+            </if>
+            <if test="settlementQueryVo.partnerCompanyId != null and settlementQueryVo.partnerCompanyId != ''">
+                AND iffo.partner_company_id = #{settlementQueryVo.partnerCompanyId}
+            </if>
+            <!-- 严重逻辑错误:时间字段对比了金额字段 -->
+            <if test="settlementQueryVo.settlementTimeStart != null and settlementQueryVo.settlementTimeStart != '' and settlementQueryVo.settlementTimeEnd != null and settlementQueryVo.settlementTimeEnd != '' ">
+                AND ipiis.create_time between #{settlementQueryVo.settlementTimeStart} and #{settlementQueryVo.settlementTimeEnd}
+            </if>
+            <if test="settlementQueryVo.invoiceTypes != null and settlementQueryVo.invoiceTypes != ''">
+                AND ipii.invoice_type IN
+                <foreach collection="settlementQueryVo.invoiceTypes" item="invoiceType" open="(" separator="," close=")">
+                    #{invoiceType}
+                </foreach>
+            </if>
+        </where>
+        <if test="settlementQueryVo.settlementStatus != null and settlementQueryVo.settlementStatus != ''">
+            HAVING CASE
+            WHEN ipii.receivable_supervise_premium = SUM(ipiis.actual_received_amount) THEN '1'
+            ELSE '0'
+            END = #{settlementQueryVo.settlementStatus}
+        </if>
+        GROUP BY
+        ipii.id,
+        ipii.receivable_supervise_premium,
+        ipii.invoice_type,
+        ipii.company_id,
+        ipii.invoice_party,
+        iffo.partner_company_id
+        ORDER BY ipii.create_time DESC
+    </select>
+
+
     <select id="getSuperviseSettlementDetailExcelDtos" resultType="com.jzg.commons.entity.finance.dto.ExportSuperviseSettlementDetailExcelDto">
         select ipi.contact_person, ipi.contact_person_phone,
         ipiis.id as settlementId,
@@ -855,6 +945,78 @@
             </if>
         </where>
     </select>
+    <select id="getPlatFormSettlementDetailExcelDtos" resultType="com.jzg.commons.entity.finance.dto.ExportSuperviseSettlementDetailExcelDto">
+        select ipi.contact_person, ipi.contact_person_phone,
+        ipiis.id as settlementId,
+        ipiis.invoice_id as invoiceId,
+        io.company_name as companyName,
+        io.signing_time as signingTime,
+        ipii.status as status,
+        ipi.license_no as licenseNo,
+        io.product_name as productName,
+        ipii.invoice_type as invoiceType,
+        ipii.invoice_party as invoiceParty,
+        ipii.tax_point as taxPoint,
+        COALESCE(ipi.jq_supervise_costs_premiums, 0) + COALESCE(ipi.sy_supervise_costs_premiums, 0) + COALESCE(ipi.jy_supervise_costs_premiums, 0) + COALESCE(ipi.jq_other_costs_premiums, 0) + COALESCE(ipi.sy_other_costs_premiums, 0) + COALESCE(ipi.jy_other_costs_premiums, 0) AS receivableSuperviseCosts,
+        ipi.jq_premium as jqPremium,
+        ipi.jq_supervise_costs_proportion as jqSuperviseCostsProportion,
+        ipi.jq_policy_no as jqPolicyNo,
+        ipi.sy_premium as syPremium,
+        ipi.sy_supervise_costs_proportion as sySuperviseCostsProportion,
+        ipi.sy_policy_no as syPolicyNo,
+        ipi.jy_premium as jyPremium,
+        ipi.jy_supervise_costs_proportion as jySuperviseCostsProportion,
+        ipi.jy_policy_no as jyPolicyNo,
+        ipi.tax_premium as taxPremium,
+        ipii.create_by as invoicePerson,
+        ipii.create_time as invoiceTime,
+        ipiis.create_by as settlementPerson,
+        ipiis.create_time as settlementTime
+        FROM
+        ins_ply_income_invoice ipii
+        LEFT JOIN ins_ply_income_invoice_settlement ipiis ON ipiis.invoice_id = ipii.id AND ipiis.is_delete = 0
+        LEFT JOIN ins_ply_income_invoice_link ipil ON ipil.invoice_id = ipiis.invoice_id AND ipil.is_delete = 0
+        LEFT JOIN ins_ply_income ipi ON ipi.id = ipil.income_id AND ipi.is_delete = 0
+        LEFT JOIN ins_orders io ON ipi.order_no = io.id AND io.is_delete = 0
+        <where>
+            ipii.is_delete = 0
+            AND ipii.system_code = #{settlementQueryVo.systemCode}
+            <if test="settlementQueryVo.settlementId != null and settlementQueryVo.settlementId != ''">
+                AND ipiis.id = #{settlementQueryVo.settlementId}
+            </if>
+            <if test="settlementQueryVo.settlementIds != null and settlementQueryVo.settlementIds != ''">
+                AND ipiis.id IN
+                <foreach collection="settlementQueryVo.settlementIds" item="settlementId" open="(" separator="," close=")">
+                    #{settlementId}
+                </foreach>
+            </if>
+            <if test="settlementQueryVo.invoiceTypes != null and settlementQueryVo.invoiceTypes != ''">
+                AND ipii.invoice_type IN
+                <foreach collection="settlementQueryVo.invoiceTypes" item="invoiceType" open="(" separator="," close=")">
+                    #{invoiceType}
+                </foreach>
+            </if>
+            <if test="settlementQueryVo.invoiceId != null and settlementQueryVo.invoiceId != ''">
+                AND ipiis.invoice_id = #{settlementQueryVo.invoiceId}
+            </if>
+            <if test="settlementQueryVo.companyId != null and settlementQueryVo.companyId != ''">
+                AND ipi.company_id = #{settlementQueryVo.companyId}
+            </if>
+            <if test="settlementQueryVo.invoiceParty != null and settlementQueryVo.invoiceParty != ''">
+                AND ipii.invoice_party = #{settlementQueryVo.invoiceParty}
+            </if>
+            <if test="settlementQueryVo.settlementPerson != null and settlementQueryVo.settlementPerson != ''">
+                AND ipiis.create_by = #{settlementQueryVo.settlementPerson}
+            </if>
+            <if test="settlementQueryVo.settlementTimeStart != null and settlementQueryVo.settlementTimeStart != ''">
+                AND ipiis.create_time between #{settlementQueryVo.signingTimeStart} and #{settlementQueryVo.signingTimeEnd}
+            </if>
+            <if test="settlementQueryVo.agreementType != null and settlementQueryVo.agreementType != ''">
+                AND ipi.agreement_type = #{settlementQueryVo.agreementType}
+            </if>
+        </where>
+    </select>
+
 
     <select id="getSuperviseSettlementDetailJyExcelDtos" resultType="com.jzg.commons.entity.finance.dto.ExportSuperviseSettlementDetailJyExcelDto">
         select
@@ -1908,8 +2070,12 @@
         </where>
     </select>
     <!-- 查询发票结算的金额 2026年6月22日15:48:33 lipf  -->
-    <select id="querySettledAmountByInvoiceIds" resultType="java.math.BigDecimal">
-        select sum(ipiis.actual_received_amount ) from ins_ply_income_invoice_settlement ipiis
+    <select id="querySettledAmountByInvoiceIds" resultType="com.jzg.commons.entity.finance.vo.InsPlyIncomeInvoiceSettlementVo">
+        select
+            sum(ipiis.actual_received_amount ) as actual_received_amount,
+            sum(ipiis.settlement_payment_difference ) as settlement_payment_difference
+        from
+        ins_ply_income_invoice_settlement ipiis
         <where>
             <if test="invoiceIdList != null and invoiceIdList.size() > 0">
                 and ipiis.invoice_id in
@@ -1920,4 +2086,49 @@
         </where>
     </select>
 
+
+    <select id="selectPlatformCompanySettlementReport" resultType="com.jzg.commons.entity.finance.vo.FollowCompanyFeeVO">
+        SELECT
+        ipi.company_id,
+        ipii.contact_person,
+        ipii.contact_person_phone,
+        COALESCE(SUM(sett.actual_received_amount), 0.00) AS totalSettlementAmount,
+        COALESCE(SUM(ipii.receivable_supervise_premium), 0.00) AS totalReceivable,
+        COALESCE(SUM(sett.settlement_payment_difference), 0.00) AS totalPaymentDifference
+        FROM ins_ply_income ipi
+        LEFT JOIN ins_ply_income_invoice_link ipil ON ipi.id = ipil.income_id AND ipil.is_delete = 0
+        LEFT JOIN ins_ply_income_invoice ipii ON ipil.invoice_id = ipii.id AND ipii.is_delete = 0
+        LEFT JOIN ins_ply_income_invoice_settlement sett ON ipii.id = sett.invoice_id AND sett.is_delete = 0
+        <where>
+            ipi.is_delete = 0
+            <if test="query.companyId != null and query.companyId != ''">
+                AND ipi.company_id = #{query.companyId}
+            </if>
+            <if test="query.contactPersonPhone != null and query.contactPersonPhone != ''">
+                AND ipii.contact_person_phone LIKE CONCAT('%', #{query.contactPersonPhone}, '%')
+            </if>
+            <if test="query.contactPerson != null and query.contactPerson != ''">
+                AND ipii.contact_person LIKE CONCAT('%', #{query.contactPerson}, '%')
+            </if>
+            -- 跟单订单创建时间范围
+            <if test="query.startTime != null and query.startTime != '' and query.endTime != null and query.endTime != ''">
+                AND ipii.create_time BETWEEN #{query.startTime} AND #{query.endTime}
+            </if>
+            -- 发票状态筛选
+            <if test="query.status != null">
+                AND ipii.status = #{query.status}
+            </if>
+            -- 开票时间范围
+            <if test="query.invoiceStartTime != null and query.invoiceStartTime != '' and query.invoiceEndTime != null and query.invoiceEndTime != ''">
+                AND ipii.invoice_time BETWEEN #{query.invoiceStartTime} AND #{query.invoiceEndTime}
+            </if>
+            -- 结算创建时间范围(子查询过滤结算时间)
+            <if test="query.settlementStartTime != null and query.settlementStartTime != '' and query.settlementEndTime != null and query.settlementEndTime != ''">
+                AND sett.create_time BETWEEN #{query.settlementStartTime} AND #{query.settlementEndTime}
+            </if>
+        </where>
+        GROUP BY ipii.contact_person_phone, ipii.contact_person, ipi.company_id
+    </select>
+
+
 </mapper>

+ 1 - 1
tenant/organization/src/main/resources/mapper/SysAmountAuditingMapper.xml

@@ -101,7 +101,7 @@
         sujim.name as managerName,sum.mobile as managerMobile,saa.create_time as createTime,
         saa.amount, saa.balance_amount as balanceAmount,saa.account_name AS accountName,
         saa.bank_card_num as bankCardNum, saa.bank_name as bankName, saa.bank_num as bankNum, suji.id as userJzgInfoId,
-        saa.payment_time as paymentTime
+        saa.payment_time as paymentTime,saa.auditing_status
         FROM sys_amount_auditing saa
         LEFT JOIN sys_user_jzg_info suji on saa.user_id = suji.user_id AND suji.is_delete = 0
         LEFT JOIN sys_user su on su.id = saa.user_id AND su.is_delete = 0