jinshihui 6 месяцев назад
Родитель
Сommit
27d361290f
14 измененных файлов с 793 добавлено и 285 удалено
  1. 2 0
      commons/src/main/java/com/jzg/commons/entity/quote/vo/VehicleAndVesselTaxVo.java
  2. 12 9
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/component/BoHaiCrawlerRequestComponent.java
  3. 20 114
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/request/BoHaiCrawlerCarRequest.java
  4. 2 2
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/request/BoHaiCrawlerQuotationRequest2.java
  5. 35 0
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/response/BoHaiCrawlerCarApiResponse.java
  6. 1 1
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/response/BoHaiCrawlerCarResponse.java
  7. 6 0
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/service/BoHaiCrawlerOrderService.java
  8. 76 3
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/service/impl/BoHaiCrawlerQuoteProcessServiceImpl.java
  9. 67 61
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/service/impl/BoHaiCrawlerRequestImpl.java
  10. 18 0
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/DateTimeUtils.java
  11. 324 0
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/PythonExecutor.java
  12. 135 0
      tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py
  13. 58 64
      tenant/insurance/quotation-commons/src/main/java/com/jzg/quotation/commons/component/RequestComponent.java
  14. 37 31
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/factory/decorator/quote/QuoteDispatchDecorator.java

+ 2 - 0
commons/src/main/java/com/jzg/commons/entity/quote/vo/VehicleAndVesselTaxVo.java

@@ -74,11 +74,13 @@ public class VehicleAndVesselTaxVo implements Serializable {
      */
     @Schema(description = "纳税人证件号码 正常缴税时必传")
     private String identifyNumber;
+
     /**
      * 车船税标志 1-正常交税; 2-免税; 3-减税; 4-完税
      */
     @Schema(description = "车船税标志 1-正常交税; 2-免税; 3-减税; 4-完税")
     private String taxRelifFlag;
+
     /**
      * 组织机构代码
      */

+ 12 - 9
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/component/BoHaiCrawlerRequestComponent.java

@@ -178,7 +178,6 @@ public class BoHaiCrawlerRequestComponent {
                     String jsonString = JSON.toJSONString(request);
                     log.info("请求参数:{}", jsonString);
                     // 请求参数加密
-                    // 参数加密
                     String encrypt = DESUtil.des_encrypt(jsonString);
                     log.info("请求参数加密:{}", encrypt);
                     //生成md5值
@@ -188,27 +187,31 @@ public class BoHaiCrawlerRequestComponent {
                     boHaiCrawlerEncryptionRequest.setEncryptionData(encrypt);
                     boHaiCrawlerEncryptionRequest.setMd5(md5Value);
 
+                    String boHaiCrawlerEncryptionRequestJsonString = JSON.toJSONString(boHaiCrawlerEncryptionRequest);
+                    log.info("加密之后的请求参数:{}", boHaiCrawlerEncryptionRequestJsonString);
                     CrawlerRequestUrlStandard standard = (CrawlerRequestUrlStandard) urlOrStandard;
-                    ResponseEntity<String> encryptedResponse = requestComponent.objectPost2(
-                            standard.getRequestUrl(boHaiCrawlerProperties.getBaseUrl2()),
-                            boHaiCrawlerEncryptionRequest,
+                    ResponseEntity<String> encryptedResponse = requestComponent.objectPost(
+                            standard.getRequestUrl(boHaiCrawlerProperties.getBaseUrl()),
+                            boHaiCrawlerEncryptionRequestJsonString,
                             String.class,
                             headers
                     );
 
                     String responseString = encryptedResponse.getBody();
                     log.info("请求返回参数:{}", responseString);
-                    AssertionUtils.isEmpty(responseString, "请求异常请重试");
+                    AssertionUtils.isEmpty(responseString, "请求返回参数为空");
                     // 参数解密
                     String decrypt = DESUtil.des_decrypt(responseString);
                     log.info("请求返回参数解密:{}", decrypt);
                     return JSON.parseObject(decrypt, responseClass);
                 // 请求参数不加密
                 case UNENCRYPTED:
+                    String jsonString2 = JSON.toJSONString(request);
+                    log.info("请求参数:{}", jsonString2);
                     CrawlerRequestUrlStandard unencryptedStandard = (CrawlerRequestUrlStandard) urlOrStandard;
                     ResponseEntity<BoHaiResponse> unencryptedResponse = requestComponent.objectPost(
                             unencryptedStandard.getRequestUrl(boHaiCrawlerProperties.getBaseUrl2()),
-                            request,
+                            jsonString2,
                             responseClass,
                             headers
                     );
@@ -351,10 +354,10 @@ public class BoHaiCrawlerRequestComponent {
      *
      * @param boHaiCrawlerCarRequest 车型查询请求体
      * @param headers                请求头
-     * @return BoHaiCrawlerCarResponse 车型列表
+     * @return BoHaiCrawlerCarApiResponse 车型列表
      */
-    public BoHaiCrawlerCarResponse car(BoHaiCrawlerCarRequest boHaiCrawlerCarRequest, HttpHeaders headers) {
-        return executeRequest(RequestType.ENCRYPTED, RequestUrl.CAR, boHaiCrawlerCarRequest, BoHaiCrawlerCarResponse.class, headers);
+    public BoHaiCrawlerCarApiResponse car(BoHaiCrawlerCarRequest boHaiCrawlerCarRequest, HttpHeaders headers) {
+        return executeRequest(RequestType.UNENCRYPTED, RequestUrl.CAR, boHaiCrawlerCarRequest, BoHaiCrawlerCarApiResponse.class, headers);
     }
 
     /**

+ 20 - 114
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/request/BoHaiCrawlerCarRequest.java

@@ -1,7 +1,9 @@
 package com.jzg.quotation.bohai.crawler.entity.request;
 
+import com.alibaba.fastjson2.annotation.JSONField;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.jzg.commons.entity.quote.vo.CarInfoVo;
+import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -12,6 +14,7 @@ import java.io.Serializable;
  * @author system
  * @date 2025/09/28
  */
+@Data
 public class BoHaiCrawlerCarRequest implements Serializable {
 
     @Serial
@@ -21,16 +24,16 @@ public class BoHaiCrawlerCarRequest implements Serializable {
      * 请求头信息
      */
     @JsonProperty("head")
+    @JSONField(ordinal = 1)
     private Head head;
 
     /**
      * 请求数据
      */
     @JsonProperty("data")
+    @JSONField(ordinal = 2)
     private Data data;
 
-    public BoHaiCrawlerCarRequest() {
-    }
 
     /**
      * 构造函数
@@ -55,200 +58,103 @@ public class BoHaiCrawlerCarRequest implements Serializable {
         this.data.setPlateNoType(carInfoVo != null && carInfoVo.getLicenseKindCode() != null ? carInfoVo.getLicenseKindCode() : "02");
         this.data.setPlateNoColor("01");
         this.data.setVehicleModels(carInfoVo != null ? carInfoVo.getBrandName() : "");
-        this.data.setOperatorCode(userCode != null ? userCode : "");
-    }
-
-    public Head getHead() {
-        return head;
-    }
-
-    public void setHead(Head head) {
-        this.head = head;
-    }
-
-    public Data getData() {
-        return data;
+        this.data.setOperatorCode("114050281");
     }
 
-    public void setData(Data data) {
-        this.data = data;
-    }
 
     /**
      * 请求头信息
      */
+    @lombok.Data
     public static class Head {
         /**
          * 渠道代码
          */
         @JsonProperty("channelCode")
+        @JSONField(ordinal = 1)
         private String channelCode;
 
         /**
          * 公司代码
          */
         @JsonProperty("comCode")
+        @JSONField(ordinal = 2)
         private String comCode;
 
         /**
          * 交易日期
          */
         @JsonProperty("transDate")
+        @JSONField(ordinal = 3)
         private String transDate;
-
-        public String getChannelCode() {
-            return channelCode;
-        }
-
-        public void setChannelCode(String channelCode) {
-            this.channelCode = channelCode;
-        }
-
-        public String getComCode() {
-            return comCode;
-        }
-
-        public void setComCode(String comCode) {
-            this.comCode = comCode;
-        }
-
-        public String getTransDate() {
-            return transDate;
-        }
-
-        public void setTransDate(String transDate) {
-            this.transDate = transDate;
-        }
     }
 
     /**
      * 请求数据
      */
+    @lombok.Data
     public static class Data {
         /**
          * 公司代码
          */
         @JsonProperty("comCode")
+        @JSONField(ordinal = 1)
         private String comCode;
 
         /**
          * 资源类型代码
          */
         @JsonProperty("resourceTypeCode")
+        @JSONField(ordinal = 2)
         private String resourceTypeCode;
 
         /**
          * 车架号
          */
         @JsonProperty("vin")
+        @JSONField(ordinal = 3)
         private String vin;
 
         /**
          * 车牌号
          */
         @JsonProperty("plateNumber")
+        @JSONField(ordinal = 4)
         private String plateNumber;
 
         /**
          * 新车标志 0-否 1-是
          */
         @JsonProperty("newCarFlag")
+        @JSONField(ordinal = 5)
         private String newCarFlag;
 
         /**
          * 号牌种类
          */
         @JsonProperty("plateNoType")
+        @JSONField(ordinal = 6)
         private String plateNoType;
 
         /**
          * 号牌颜色
          */
         @JsonProperty("plateNoColor")
+        @JSONField(ordinal = 7)
         private String plateNoColor;
 
         /**
          * 车辆型号
          */
         @JsonProperty("vehicleModels")
+        @JSONField(ordinal = 8)
         private String vehicleModels;
 
         /**
          * 操作员代码
          */
         @JsonProperty("operatorCode")
+        @JSONField(ordinal = 9)
         private String operatorCode;
-
-        public String getComCode() {
-            return comCode;
-        }
-
-        public void setComCode(String comCode) {
-            this.comCode = comCode;
-        }
-
-        public String getResourceTypeCode() {
-            return resourceTypeCode;
-        }
-
-        public void setResourceTypeCode(String resourceTypeCode) {
-            this.resourceTypeCode = resourceTypeCode;
-        }
-
-        public String getVin() {
-            return vin;
-        }
-
-        public void setVin(String vin) {
-            this.vin = vin;
-        }
-
-        public String getPlateNumber() {
-            return plateNumber;
-        }
-
-        public void setPlateNumber(String plateNumber) {
-            this.plateNumber = plateNumber;
-        }
-
-        public String getNewCarFlag() {
-            return newCarFlag;
-        }
-
-        public void setNewCarFlag(String newCarFlag) {
-            this.newCarFlag = newCarFlag;
-        }
-
-        public String getPlateNoType() {
-            return plateNoType;
-        }
-
-        public void setPlateNoType(String plateNoType) {
-            this.plateNoType = plateNoType;
-        }
-
-        public String getPlateNoColor() {
-            return plateNoColor;
-        }
-
-        public void setPlateNoColor(String plateNoColor) {
-            this.plateNoColor = plateNoColor;
-        }
-
-        public String getVehicleModels() {
-            return vehicleModels;
-        }
-
-        public void setVehicleModels(String vehicleModels) {
-            this.vehicleModels = vehicleModels;
-        }
-
-        public String getOperatorCode() {
-            return operatorCode;
-        }
-
-        public void setOperatorCode(String operatorCode) {
-            this.operatorCode = operatorCode;
-        }
     }
 }

+ 2 - 2
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/request/BoHaiCrawlerQuotationRequest2.java

@@ -112,7 +112,7 @@ public class BoHaiCrawlerQuotationRequest2 {
         private String goalInsuredFlag;
         /** 承保单位/核算单位 */
         private String underUnit;
-        /** 核保识别类型 */
+        /** 身份类型 */
         private String identifyType;
         /** 核保识别明细 */
         private String identifyDetail;
@@ -236,7 +236,7 @@ public class BoHaiCrawlerQuotationRequest2 {
         private String useYearsBI;
         /** 使用年限(交强险口径) */
         private Integer useYearsCI;
-        /** 使用年限(设备口径) */
+        /** 使用年限 */
         private String useYearsDI;
         /** 过户标志 */
         private String chgOwnerFlag;

+ 35 - 0
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/response/BoHaiCrawlerCarApiResponse.java

@@ -0,0 +1,35 @@
+package com.jzg.quotation.bohai.crawler.entity.response;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+@Data
+public class BoHaiCrawlerCarApiResponse implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 响应码
+     */
+    @JsonProperty("code")
+    private Integer code;
+
+    /**
+     * 响应消息
+     */
+    @JsonProperty("msg")
+    private String msg;
+
+    /**
+     * 响应时间戳
+     */
+    @JsonProperty("time")
+    private Long time;
+
+    /**
+     * 响应数据
+     */
+    @JsonProperty("data")
+    private BoHaiCrawlerCarResponse data;
+}

+ 1 - 1
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/entity/response/BoHaiCrawlerCarResponse.java

@@ -602,7 +602,7 @@ public class BoHaiCrawlerCarResponse implements Serializable {
         private String resourceTypeCode;
 
         /**
-         * 车牌颜色
+         * 车牌颜色(01:蓝牌)
          */
         @JsonProperty("plateNoColor")
         private String plateNoColor;

+ 6 - 0
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/service/BoHaiCrawlerOrderService.java

@@ -11,6 +11,12 @@ import com.jzg.quotation.bohai.crawler.entity.response.BoHaiCrawlerQuotationResp
  **/
 public interface BoHaiCrawlerOrderService {
 
+    /**
+     *
+     * @param quoteVo
+     * @param boHaiCrawlerQuotationResponse
+     * @param success
+     */
     void save(QuoteVo quoteVo, BoHaiCrawlerQuotationResponse boHaiCrawlerQuotationResponse, boolean success);
 
     BoHaiCrawlerOrder getById(String ordersNo);

+ 76 - 3
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/service/impl/BoHaiCrawlerQuoteProcessServiceImpl.java

@@ -9,11 +9,16 @@ import com.jzg.quotation.bohai.crawler.entity.response.BoHaiCrawlerBaseResponse;
 import com.jzg.quotation.bohai.crawler.entity.response.BoHaiCrawlerCheckTokenResponse;
 import com.jzg.quotation.bohai.crawler.service.BoHaiCrawlerQuoteProcessService;
 import com.jzg.quotation.bohai.crawler.util.DESEncryption;
+import com.jzg.quotation.bohai.crawler.util.PythonExecutor;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import java.time.Duration;
 import java.util.Optional;
 
 /**
@@ -21,14 +26,42 @@ import java.util.Optional;
  * @author:
  * @date:
  **/
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class BoHaiCrawlerQuoteProcessServiceImpl implements BoHaiCrawlerQuoteProcessService {
 
+    /**
+     * Redis中sessionId的缓存key
+     */
+    private static final String BOHAI_SESSION_ID_KEY = "bohai:crawler:sessionId";
+
+    /**
+     * sessionId缓存过期时间(秒)- 设置为2小时
+     */
+    private static final long SESSION_ID_EXPIRE_SECONDS = 2 * 60 * 60;
+
+    /**
+     * Python脚本路径
+     */
+    private static final String PYTHON_SCRIPT_PATH = "e:/project/jzg/tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py";
+
+    /**
+     * Python解释器路径
+     */
+    private static final String PYTHON_EXECUTABLE_PATH = "E:\\project\\memorize_backend\\venv\\Scripts\\python.exe";
+
+    /**
+     * Python脚本执行超时时间(秒)
+     */
+    private static final int PYTHON_EXECUTION_TIMEOUT = 120;
+
     private final BoHaiCrawlerCacheComponent boHaiCrawlerCacheComponent;
 
     private final BoHaiCrawlerRequestComponent boHaiCrawlerRequestComponent;
 
+    private final RedissonClient redissonClient;
+
     // 登陆次数
     public static final int LOGINS_NUMBER = 3;
 
@@ -90,7 +123,10 @@ public class BoHaiCrawlerQuoteProcessServiceImpl implements BoHaiCrawlerQuotePro
         return headers;*/
         HttpHeaders httpHeaders = new HttpHeaders();
         httpHeaders.set("Cookie", "HTTPOnly; skin-color=skin-db; Cookie=!uEW94o2278iwLg21yv2qnldjEk8Kb5RsZkOl0FDX8+Mqxb0EGroyfXXQVmrGlnxevDyUYDwsWh2B5iY=; JSESSIONID=0001TTPD95X9DLJB1il0R_O_NhM:3T0NUMVN3E");
-        httpHeaders.set("sessionId","114050281_ys1Joy8Fnl-SL4D515lH1uJ1761182282670");
+
+        // 从Redis获取sessionId
+        String sessionId = getOrRefreshSessionId();
+        httpHeaders.set("sessionId", sessionId);
         return httpHeaders;
     }
 
@@ -107,7 +143,44 @@ public class BoHaiCrawlerQuoteProcessServiceImpl implements BoHaiCrawlerQuotePro
         String tokenId = boHaiCrawlerRequestComponent.createToken(userCode, httpHeaders);
         BoHaiCrawlerCheckTokenRequest boHaiCrawlerCheckTokenRequest = new BoHaiCrawlerCheckTokenRequest(tokenId, userCode);
         // 校验token
-        BoHaiCrawlerCheckTokenResponse daJiaCrawlerCheckTokenResponse = boHaiCrawlerRequestComponent.checkToken(boHaiCrawlerCheckTokenRequest, httpHeaders);
-        return  daJiaCrawlerCheckTokenResponse.getIdToken();
+        BoHaiCrawlerCheckTokenResponse boHaiCrawlerCheckTokenResponse = boHaiCrawlerRequestComponent.checkToken(boHaiCrawlerCheckTokenRequest, httpHeaders);
+        return  boHaiCrawlerCheckTokenResponse.getIdToken();
+    }
+
+    /**
+     * 获取或刷新sessionId
+     * 优先从Redis缓存获取,如果缓存不存在则调用Python脚本获取并缓存
+     *
+     * @return sessionId字符串
+     */
+    private String getOrRefreshSessionId() {
+        // 从Redis获取sessionId
+        RBucket<String> bucket = redissonClient.getBucket(BOHAI_SESSION_ID_KEY);
+        String sessionId = bucket.get();
+
+        // 如果Redis中存在sessionId,直接返回
+        if (StringUtils.hasLength(sessionId)) {
+            log.info("从Redis缓存获取sessionId: {}", sessionId);
+            return sessionId;
+        }
+
+        // Redis中不存在,调用Python脚本获取sessionId
+        log.info("Redis缓存中sessionId不存在,开始调用Python脚本获取");
+        sessionId = PythonExecutor.executeAndGetSessionId(
+                PYTHON_SCRIPT_PATH,
+                PYTHON_EXECUTABLE_PATH,
+                PYTHON_EXECUTION_TIMEOUT
+        );
+
+        // 校验获取结果
+        if (!StringUtils.hasLength(sessionId)) {
+            log.error("调用Python脚本获取sessionId失败");
+            throw new SystemException("获取sessionId失败");
+        }
+
+        // 将sessionId存入Redis并设置过期时间
+        bucket.set(sessionId, Duration.ofSeconds(SESSION_ID_EXPIRE_SECONDS));
+        log.info("sessionId已存入Redis缓存,过期时间: {}秒", SESSION_ID_EXPIRE_SECONDS);
+        return sessionId;
     }
 }

+ 67 - 61
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/service/impl/BoHaiCrawlerRequestImpl.java

@@ -23,6 +23,8 @@ import com.jzg.quotation.bohai.crawler.component.BoHaiBuildingAttributionInforma
 import com.jzg.quotation.bohai.crawler.constant.enums.CertifyType;
 import com.jzg.quotation.bohai.crawler.entity.request.BoHaiCrawlerQuotationRequest;
 import com.jzg.quotation.bohai.crawler.entity.request.BoHaiCrawlerPaymentCertiNoQueryRequest;
+import com.jzg.quotation.bohai.crawler.util.DateTimeUtils;
+import com.jzg.quotation.bohai.crawler.util.PythonExecutor;
 import com.jzg.quotation.commons.annotation.QuoteCrawlerService;
 import com.jzg.quotation.commons.constant.InsuranceCode;
 import com.jzg.quotation.commons.standard.conversion.ConfigureParametersConversion;
@@ -72,7 +74,9 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
 
     private final BoHaiCrawlerQuoteProcessService boHaiCrawlerQuoteProcessService;
 
-    private static final String TOKEN = "114050281-ab12387d7bb7bd319ac3b7d4ddd22090";
+    private static final String TOKEN = PythonExecutor.executeAndGetToken(
+            "E:/project/jzg/tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py",
+            "E:\\project\\memorize_backend\\venv\\Scripts\\python.exe", 120);
 
     /**
      * 登录
@@ -109,11 +113,11 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         //String orderNo = generateOrderNo(parameters, headers);
 
         // 2.1 车辆查询
-        BoHaiCrawlerCarResponse boHaiCrawlerCarResponse = null;
+        BoHaiCrawlerCarApiResponse boHaiCrawlerCarApiResponse = null;
         try {
             BoHaiCrawlerCarRequest boHaiCrawlerCarRequest = new BoHaiCrawlerCarRequest(quoteVo.getCarInfoVo(), parameters.getOrgName(), parameters.getCDptCde(), userCode);
-            boHaiCrawlerCarResponse = boHaiCrawlerRequestComponent.car(boHaiCrawlerCarRequest, headers);
-            log.info("boHaiCrawlerCarResponse参数的值:{}", JSON.toJSONString(boHaiCrawlerCarResponse));
+            boHaiCrawlerCarApiResponse = boHaiCrawlerRequestComponent.car(boHaiCrawlerCarRequest, headers);
+            log.info("boHaiCrawlerCarApiResponse参数的值:{}", JSON.toJSONString(boHaiCrawlerCarApiResponse));
         } catch (Exception e) {
             e.printStackTrace();
             throw new RuntimeException(e);
@@ -122,17 +126,16 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         //2.2 车辆确认
         String currentValue = null;
         try {
-            Map<String, Object> vehicleConfirmationResult = getVehicleConfirmationResponse(quoteVo, boHaiCrawlerCarResponse, parameters, headers);
+            BoHaiCrawlerVehicleConfirmationResponse2 boHaiCrawlerVehicleConfirmationResponse = getVehicleConfirmationResponse(quoteVo, boHaiCrawlerCarApiResponse, parameters, headers);
             //获取车辆当前价值
-            currentValue = (String) vehicleConfirmationResult.get("currentValue");
-            log.info("车辆当前价值:{}", currentValue);
+            currentValue =boHaiCrawlerVehicleConfirmationResponse.getVehiclePriceList().get(0).getActualValue();
+            log.info("[车辆确认]车辆当前价值:{}", currentValue);
         } catch (Exception e) {
             e.printStackTrace();
             throw new RuntimeException(e);
         }
 
         // 3.0 报价接口
-        /*BoHaiCrawlerQuotationRequest boHaiCrawlerQuotationRequest = new BoHaiCrawlerQuotationRequest(parameters, quoteVo, currentValue, "", quoteVo.getAccidentalDrivings(), "");*/
         BoHaiCrawlerQuotationRequest2 boHaiCrawlerQuotationRequest2 = new BoHaiCrawlerQuotationRequest2();
         BoHaiCrawlerQuotationRequest2.Head head = new BoHaiCrawlerQuotationRequest2.Head();
         head.setTransType("Req");
@@ -145,7 +148,7 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
 
         // 业务主信息
         BoHaiCrawlerQuotationRequest2.BusinessMainInfo mainInfo = new BoHaiCrawlerQuotationRequest2.BusinessMainInfo();
-        mainInfo.setOrderNo("O140703302025063172");
+        mainInfo.setOrderNo("");
         mainInfo.setFixPriceType("Y2");
         mainInfo.setRiskControlFlag("");
         mainInfo.setUuid("");
@@ -163,7 +166,7 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         mainInfo.setAgreementNo("C142024004345_2");
         mainInfo.setCityCode("140700");
         mainInfo.setCountyCode("140781");
-        mainInfo.setOperateDate("2025-10-09");
+        mainInfo.setOperateDate(DateTimeUtils.getCurrentDate());
         mainInfo.setGoalInsuredFlag("2");
         mainInfo.setUnderUnit("");
         mainInfo.setIdentifyType("006");
@@ -196,34 +199,40 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         item.setCertificateType("");
         item.setOtherEnergyTypeDesc("");
         item.setPureRange("");
+        //燃油类型:A=汽油
         item.setPmFuelType("A");
-        item.setCarCheckStatus("");
+        // 验车状态:4=已验
+        item.setCarCheckStatus("4");
         //实际价值
-        item.setActualValue("15380.00");
+        item.setActualValue(currentValue);
+        //其他性质标志
         item.setOtherNature7BI("0");
         //协商价值
-        item.setNegotiatedValue("15380.00");
+        item.setNegotiatedValue(currentValue);
+        // 新车标志:0=非新车
         item.setNewVehicleFlag("0");
-        item.setLicenseNo("晋K38V85");
-        item.setFrameNo("LJDLAA197D0029125");
-        item.setEngineNo("D5685636");
-        item.setVinNo("LJDLAA197D0029125");
+        // 车牌号
+        item.setLicenseNo(quoteVo.getCarInfoVo().getLicenseNo());
+        item.setFrameNo(quoteVo.getCarInfoVo().getVinNo());
+        item.setEngineNo(quoteVo.getCarInfoVo().getEngineNo());
+        item.setVinNo(quoteVo.getCarInfoVo().getVinNo());
         item.setNoLicenseFlag("");
-        item.setLicenseColorCode("01");
-        item.setUseNatureCode("8A");
+        //号牌颜色 01:蓝牌
+        item.setLicenseColorCode(boHaiCrawlerCarApiResponse.getData().getJyCarModelFill().getPlateNoColor());
+        item.setUseNatureCode(quoteVo.getCarInfoVo().getCarNatureCode());
         item.setMainCarKindCode("A");
-        item.setCarKindCode("A0");
-        item.setBrandName("起亚YQZ7143A轿车");
-        item.setSeatCount("5");
-        item.setTonCount("0.0000");
-        item.setPurchasePrice("76900");
-        item.setBasePurchasePrice("76900");
-        item.setExhaustScale(1.396);
-        item.setEnrollDate("2013-10-14");
-        item.setUseYearsBI("12");
-        item.setUseYearsCI(11);
+        item.setCarKindCode(quoteVo.getCarInfoVo().getCarKindCode());
+        item.setBrandName(quoteVo.getCarInfoVo().getBrandName());
+        item.setSeatCount(quoteVo.getCarInfoVo().getSeatCount());
+        item.setTonCount(quoteVo.getCarInfoVo().getTonCount());
+        item.setPurchasePrice(quoteVo.getCarInfoVo().getPurchasePrice());
+        item.setBasePurchasePrice(quoteVo.getCarInfoVo().getPurchasePrice());
+        item.setExhaustScale(Double.parseDouble(boHaiCrawlerCarApiResponse.getData().getPrpDCarModels().get(0).getExhaustScale()));
+        item.setEnrollDate(quoteVo.getCarInfoVo().getRegisterDate());
+        item.setUseYearsBI("");
+        item.setUseYearsCI(0);
         item.setUseYearsDI("");
-        item.setChgOwnerFlag("1");
+        item.setChgOwnerFlag(quoteVo.getCarInfoVo().getChgOwnerFlag());
         item.setChgTypeFlag("0");
         item.setChgOwnerDate("2024-11-05");
         item.setCountryNature("C");
@@ -232,7 +241,7 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         item.setRunAreaCode("02");
         item.setTrafficViolationRecords("0");
         item.setColorCode("99");
-        item.setMadeFactory("东风悦达起亚汽车有限公司");
+        item.setMadeFactory("");
         item.setMakeDate("");
         item.setVehicleBrand("起亚");
         item.setCompleteKerbMass(1087);
@@ -931,13 +940,12 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
      * 车辆确认
      *
      * @param quoteVo
-     * @param boHaiCrawlerCarResponse
+     * @param boHaiCrawlerCarApiResponse
      * @param parameters
-     * @param orderNo
      * @param headers
      * @return Map<String, Object>
      */
-    private Map<String, Object> getVehicleConfirmationResponse(QuoteVo quoteVo, BoHaiCrawlerCarResponse boHaiCrawlerCarResponse, BoHaiCrawlerConfigureParameters parameters, HttpHeaders headers) {
+    private BoHaiCrawlerVehicleConfirmationResponse2 getVehicleConfirmationResponse(QuoteVo quoteVo, BoHaiCrawlerCarApiResponse boHaiCrawlerCarApiResponse, BoHaiCrawlerConfigureParameters parameters, HttpHeaders headers) {
         // 车主信息
         CustomerInfoVo ownerInfo = quoteVo.getOwnersInfo();
         PersonAreaVo ownerInfoArea = new PersonAreaVo(ownerInfo);
@@ -962,37 +970,38 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         bodyDTO.setBasicRateCode("");
         // 登录机构编码,先写死
         bodyDTO.setComCode("14070201");
-        bodyDTO.setEffectiveDate("2025-09-30");
-        bodyDTO.setExpireDate("2026-09-29");
+        bodyDTO.setEffectiveDate(quoteVo.getCiRiskInfoVo().getStartDate());
+        bodyDTO.setExpireDate(quoteVo.getCiRiskInfoVo().getEndDate());
         bodyDTO.setEndHour("24");
         bodyDTO.setStartHour("0");
-        bodyDTO.setRiskCode("0380");
-        bodyDTO.setBusinessNature("2");
-        bodyDTO.setEngineNo(boHaiCrawlerCarResponse.getJyCarModelFill().getEngineNumber());
-        bodyDTO.setVin(boHaiCrawlerCarResponse.getJyCarModelFill().getVinCode());
-        bodyDTO.setLicensePlateNo(boHaiCrawlerCarResponse.getJyCarModelFill().getPlateNumber());
+        bodyDTO.setRiskCode(quoteVo.getProductId());
+        bodyDTO.setBusinessNature(quoteVo.getCarInfoVo().getCarNatureCode());
+        bodyDTO.setEngineNo(boHaiCrawlerCarApiResponse.getData().getJyCarModelFill().getEngineNumber());
+        bodyDTO.setVin(boHaiCrawlerCarApiResponse.getData().getJyCarModelFill().getVinCode());
+        bodyDTO.setLicensePlateNo(boHaiCrawlerCarApiResponse.getData().getJyCarModelFill().getPlateNumber());
         //车牌类型
-        bodyDTO.setLicensePlateType("02");
-        bodyDTO.setFirstRegisterDate(boHaiCrawlerCarResponse.getJyCarModelFill().getRegisterDate());
-        bodyDTO.setModelCode(boHaiCrawlerCarResponse.getPrpDCarModels().get(0).getIndustryCarTypeCode());
+        bodyDTO.setLicensePlateType(quoteVo.getCarInfoVo().getLicenseKindCode());
+        bodyDTO.setFirstRegisterDate(boHaiCrawlerCarApiResponse.getData().getJyCarModelFill().getRegisterDate());
+        bodyDTO.setModelCode(boHaiCrawlerCarApiResponse.getData().getPrpDCarModels().get(0).getIndustryCarTypeCode());
         //平台车型代码
-        bodyDTO.setPlatFormModelCode(boHaiCrawlerCarResponse.getPrpDCarModels().get(0).getIndustryCarTypeCode());
+        bodyDTO.setPlatFormModelCode(boHaiCrawlerCarApiResponse.getData().getPrpDCarModels().get(0).getIndustryCarTypeCode());
         bodyDTO.setNoticeType("");
-        bodyDTO.setVehicleModel(boHaiCrawlerCarResponse.getPrpDCarModels().get(0).getModelCode());
-        bodyDTO.setVehicleStyleDesc("K33");
+        bodyDTO.setVehicleModel(boHaiCrawlerCarApiResponse.getData().getPrpDCarModels().get(0).getModelCode());
+        bodyDTO.setVehicleStyleDesc(quoteVo.getCarInfoVo().getCarTypeCode());
         bodyDTO.setCarName("");
         bodyDTO.setVehicleBrand("");
         bodyDTO.setMadeFactory("");
 
         //车辆动力参数
         //排量
-        bodyDTO.setExhaustScale(1.373);
+        bodyDTO.setExhaustScale(Double.parseDouble(quoteVo.getCarInfoVo().getDisplacement()));
         bodyDTO.setDisplacement("");
-        bodyDTO.setPower("103");
-        bodyDTO.setModelEnergyType("0");
+        bodyDTO.setPower(quoteVo.getCarInfoVo().getPowerScale());
+        bodyDTO.setModelEnergyType(quoteVo.getCarInfoVo().getEnergyTypeCode());
         bodyDTO.setPureRange("");
 
         //车辆类型分类
+        //主车型代码
         bodyDTO.setMainCarKindCode("A");
         bodyDTO.setMotorTypeCode("A0");
         bodyDTO.setMotorMainTypeCode("A");
@@ -1000,15 +1009,15 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
 
         //车辆载重/载客信息
         //核定载客量
-        bodyDTO.setRatedPassengerCapacity("5");
-        bodyDTO.setLimitLoadPerson("5");
+        bodyDTO.setRatedPassengerCapacity(quoteVo.getCarInfoVo().getSeatCount());
+        bodyDTO.setLimitLoadPerson(quoteVo.getCarInfoVo().getSeatCount());
         bodyDTO.setTonnage("0.0000");
-        bodyDTO.setLimitLoad("0.0000");
-        bodyDTO.setWholeWeight("");
+        bodyDTO.setLimitLoad(quoteVo.getCarInfoVo().getLimitLoad());
+        bodyDTO.setWholeWeight(quoteVo.getCarInfoVo().getCompleteKerbMass());
 
         //价格与价值信息
-        log.info("购买价值:{}", boHaiCrawlerCarResponse.getPrpDCarModels().get(0).getPurchasePrice());
-        bodyDTO.setReplacementValue(boHaiCrawlerCarResponse.getPrpDCarModels().get(0).getPurchasePrice());
+        log.info("购买价值:{}", boHaiCrawlerCarApiResponse.getData().getPrpDCarModels().get(0).getPurchasePrice());
+        bodyDTO.setReplacementValue(boHaiCrawlerCarApiResponse.getData().getPrpDCarModels().get(0).getPurchasePrice());
 
         //车辆状态标志
         bodyDTO.setNewVehicleFlag("");
@@ -1024,14 +1033,11 @@ public class BoHaiCrawlerRequestImpl implements BoHaiCrawlerRequest {
         bodyDTO.setSalesChannelRefCode("");
         bodyDTO.setPrecisePriceComCode(true);
         boHaiCrawlerVehicleConfirmationReqeust1.setBody(bodyDTO);
-
+        log.info("车辆确认请求参数:{}", JSON.toJSONString(boHaiCrawlerVehicleConfirmationReqeust1));
         //调用车辆确认接口
         BoHaiCrawlerVehicleConfirmationResponse2 boHaiCrawlerVehicleConfirmationResponse = boHaiCrawlerRequestComponent.vehicleConfirmation(boHaiCrawlerVehicleConfirmationReqeust1, headers);
         log.info("boHaiCrawlerVehicleConfirmationResponse参数的值:{}", JSON.toJSONString(boHaiCrawlerVehicleConfirmationResponse));
-
-        Map<String, Object> map = new HashMap<>();
-        map.put("currentValue", boHaiCrawlerVehicleConfirmationResponse.getVehiclePriceList().get(0).getActualValue());
-        return map;
+        return boHaiCrawlerVehicleConfirmationResponse;
     }
 
     /**

+ 18 - 0
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/DateTimeUtils.java

@@ -0,0 +1,18 @@
+package com.jzg.quotation.bohai.crawler.util;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+public class DateTimeUtils {
+
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    /**
+     * 获取当前日期
+     *
+     * @return 当前日期字符串,格式:yyyy-MM-dd
+     */
+    public static String getCurrentDate() {
+        return LocalDate.now().format(DATE_FORMATTER);
+    }
+}

+ 324 - 0
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/PythonExecutor.java

@@ -0,0 +1,324 @@
+package com.jzg.quotation.bohai.crawler.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Python脚本执行工具类
+ * 用于调用Python脚本并解析返回结果中的sessionId
+ *
+ * @author jzg
+ */
+public class PythonExecutor {
+
+    private static final Logger log = LoggerFactory.getLogger(PythonExecutor.class);
+
+    /**
+     * 默认Python解释器路径(可根据实际环境配置)
+     */
+    private static final String DEFAULT_PYTHON_PATH = "python";
+
+    /**
+     * 默认脚本执行超时时间(秒)
+     */
+    private static final int DEFAULT_TIMEOUT_SECONDS = 60;
+
+    /**
+     * 执行Python脚本并获取sessionId
+     *
+     * @param scriptPath Python脚本的绝对路径
+     * @return sessionId字符串,执行失败返回null
+     */
+    public static String executeAndGetSessionId(String scriptPath) {
+        return executeAndGetSessionId(scriptPath, DEFAULT_PYTHON_PATH, DEFAULT_TIMEOUT_SECONDS);
+    }
+
+    /**
+     * 执行Python脚本并获取sessionId
+     *
+     * @param scriptPath   Python脚本的绝对路径
+     * @param pythonPath   Python解释器路径
+     * @param timeoutSeconds 超时时间(秒)
+     * @return sessionId字符串,执行失败返回null
+     */
+    public static String executeAndGetSessionId(String scriptPath, String pythonPath, int timeoutSeconds) {
+        String output = executePythonScript(scriptPath, pythonPath, timeoutSeconds);
+        if (output == null || output.isEmpty()) {
+            log.error("Python脚本执行结果为空");
+            return null;
+        }
+        return parseSessionId(output);
+    }
+
+    /**
+     * 执行Python脚本并获取token
+     *
+     * @param scriptPath   Python脚本的绝对路径
+     * @param pythonPath   Python解释器路径
+     * @param timeoutSeconds 超时时间(秒)
+     * @return token字符串,执行失败返回null
+     */
+    public static String executeAndGetToken(String scriptPath, String pythonPath, int timeoutSeconds) {
+        String output = executePythonScript(scriptPath, pythonPath, timeoutSeconds);
+        if (output == null || output.isEmpty()) {
+            log.error("Python脚本执行结果为空");
+            return null;
+        }
+        return parseToken(output);
+    }
+
+    /**
+     * 执行Python脚本并返回完整输出
+     *
+     * @param scriptPath     Python脚本的绝对路径
+     * @param pythonPath     Python解释器路径
+     * @param timeoutSeconds 超时时间(秒)
+     * @return 脚本的完整输出内容,执行失败返回null
+     */
+    public static String executePythonScript(String scriptPath, String pythonPath, int timeoutSeconds) {
+        // 参数校验
+        if (scriptPath == null || scriptPath.trim().isEmpty()) {
+            log.error("Python脚本路径不能为空");
+            return null;
+        }
+
+        File scriptFile = new File(scriptPath);
+        if (!scriptFile.exists()) {
+            log.error("Python脚本文件不存在: {}", scriptPath);
+            return null;
+        }
+
+        if (pythonPath == null || pythonPath.trim().isEmpty()) {
+            pythonPath = DEFAULT_PYTHON_PATH;
+        }
+
+        log.info("开始执行Python脚本: {}", scriptPath);
+
+        ProcessBuilder processBuilder = new ProcessBuilder(pythonPath, scriptPath);
+        processBuilder.environment().put("PYTHONIOENCODING", "UTF-8");
+        // 将错误流合并到标准输出流
+        processBuilder.redirectErrorStream(true);
+
+        StringBuilder output = new StringBuilder();
+        Process process = null;
+
+        try {
+            process = processBuilder.start();
+
+            // 读取输出流
+            try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    output.append(line).append("\n");
+                    log.debug("Python输出: {}", line);
+                }
+            }
+
+            // 等待进程完成
+            boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
+            if (!finished) {
+                log.error("Python脚本执行超时,已强制终止");
+                process.destroyForcibly();
+                return null;
+            }
+
+            int exitCode = process.exitValue();
+            if (exitCode != 0) {
+                log.error("Python脚本执行失败,退出码: {}", exitCode);
+                return null;
+            }
+
+            log.info("Python脚本执行完成");
+            return output.toString();
+
+        } catch (Exception e) {
+            log.error("执行Python脚本时发生异常", e);
+            return null;
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+        }
+    }
+
+    /**
+     * 从Python脚本输出中解析sessionId
+     * 输出格式示例:
+     * 核心登录数据:localStorage.setItem("cherry.cherry.user",'{"sessionId": "xxx", ...}')
+     *
+     * @param output Python脚本的输出内容
+     * @return sessionId字符串,解析失败返回null
+     */
+    public static String parseSessionId(String output) {
+        if (output == null || output.isEmpty()) {
+            log.error("输出内容为空,无法解析sessionId");
+            return null;
+        }
+
+        try {
+            // 查找包含sessionId的JSON字符串
+            // 格式:localStorage.setItem("cherry.cherry.user",'JSON内容')
+            int startIndex = output.indexOf("localStorage.setItem");
+            if (startIndex == -1) {
+                log.error("未找到localStorage.setItem,无法解析sessionId");
+                return null;
+            }
+
+            // 找到第一个单引号的位置(JSON内容开始)
+            int jsonStart = output.indexOf("'", startIndex);
+            if (jsonStart == -1) {
+                log.error("未找到JSON内容起始位置");
+                return null;
+            }
+
+            // 找到对应的结束单引号
+            int jsonEnd = output.lastIndexOf("'");
+            if (jsonEnd == -1 || jsonEnd <= jsonStart) {
+                log.error("未找到JSON内容结束位置");
+                return null;
+            }
+
+            // 提取JSON字符串
+            String jsonStr = output.substring(jsonStart + 1, jsonEnd);
+            // 解析JSON并获取sessionId
+            JSONObject jsonObject = JSON.parseObject(jsonStr);
+            String sessionId = jsonObject.getString("sessionId");
+
+            if (sessionId == null || sessionId.isEmpty()) {
+                log.error("JSON中未找到sessionId字段");
+                return null;
+            }
+            return sessionId;
+        } catch (Exception e) {
+            log.error("解析sessionId时发生异常", e);
+            return null;
+        }
+    }
+
+
+    /**
+     * 从Python脚本输出中解析token
+     * 输出格式示例:
+     * 核心登录数据:localStorage.setItem("cherry.cherry.user",'{"token": "xxx", ...}')
+     *
+     * @param output Python脚本的输出内容
+     * @return token字符串,解析失败返回null
+     */
+    public static String parseToken(String output) {
+        if (output == null || output.isEmpty()) {
+            log.error("输出内容为空,无法解析token");
+            return null;
+        }
+
+        try {
+            // 查找包含token的JSON字符串
+            // 格式:localStorage.setItem("cherry.cherry.user",'JSON内容')
+            int startIndex = output.indexOf("localStorage.setItem");
+            if (startIndex == -1) {
+                log.error("未找到localStorage.setItem,无法解析token");
+                return null;
+            }
+
+            // 找到第一个单引号的位置(JSON内容开始)
+            int jsonStart = output.indexOf("'", startIndex);
+            if (jsonStart == -1) {
+                log.error("未找到JSON内容起始位置");
+                return null;
+            }
+
+            // 找到对应的结束单引号
+            int jsonEnd = output.lastIndexOf("'");
+            if (jsonEnd == -1 || jsonEnd <= jsonStart) {
+                log.error("未找到JSON内容结束位置");
+                return null;
+            }
+
+            // 提取JSON字符串
+            String jsonStr = output.substring(jsonStart + 1, jsonEnd);
+            // 解析JSON并获取sessionId
+            JSONObject jsonObject = JSON.parseObject(jsonStr);
+            String sessionId = jsonObject.getString("token");
+
+            if (sessionId == null || sessionId.isEmpty()) {
+                log.error("JSON中未找到token字段");
+                return null;
+            }
+            return sessionId;
+        } catch (Exception e) {
+            log.error("解析token时发生异常", e);
+            return null;
+        }
+    }
+
+    /**
+     * 从Python脚本输出中解析完整的登录数据JSON对象
+     *
+     * @param output Python脚本的输出内容
+     * @return 登录数据JSON对象,解析失败返回null
+     */
+    public static JSONObject parseFullLoginData(String output) {
+        if (output == null || output.isEmpty()) {
+            log.error("输出内容为空,无法解析登录数据");
+            return null;
+        }
+
+        try {
+            int startIndex = output.indexOf("localStorage.setItem");
+            if (startIndex == -1) {
+                log.error("未找到localStorage.setItem");
+                return null;
+            }
+
+            int jsonStart = output.indexOf("'", startIndex);
+            int jsonEnd = output.lastIndexOf("'");
+
+            if (jsonStart == -1 || jsonEnd == -1 || jsonEnd <= jsonStart) {
+                log.error("JSON内容位置解析失败");
+                return null;
+            }
+
+            String jsonStr = output.substring(jsonStart + 1, jsonEnd);
+            return JSON.parseObject(jsonStr);
+
+        } catch (Exception e) {
+            log.error("解析登录数据时发生异常", e);
+            return null;
+        }
+    }
+
+    public static void main(String[] args) {
+        /*String scriptPath = "e:/project/jzg/tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py";
+        String sessionId = PythonExecutor.executeAndGetSessionId(scriptPath);
+        System.out.println("sessionId: " + sessionId);*/
+
+        // 方式2:获取sessionId的值
+        /*String sessionId = PythonExecutor.executeAndGetSessionId(
+                "e:/project/jzg/tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py",
+                "E:\\project\\memorize_backend\\venv\\Scripts\\python.exe",  // Python解释器路径
+                120         // 超时时间(秒)
+        );
+        if (sessionId != null) {
+            System.out.println("获取到的sessionId: " + sessionId);
+        } else {
+            System.out.println("获取sessionId失败");
+        }*/
+        // 获取token的值
+        String token = PythonExecutor.executeAndGetToken(
+                "e:/project/jzg/tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py",
+                "E:\\project\\memorize_backend\\venv\\Scripts\\python.exe", 120);
+        if (token != null) {
+            System.out.println("获取到的token: " + token);
+        } else {
+            System.out.println("获取token失败");
+        }
+    }
+}

+ 135 - 0
tenant/insurance/quotation-bohai/src/main/java/com/jzg/quotation/bohai/crawler/util/novel.py

@@ -0,0 +1,135 @@
+import json
+import hashlib
+from Crypto.Cipher import DES
+from Crypto.Util.Padding import pad, unpad
+import base64
+import requests
+from loguru import logger
+from datetime import datetime
+def md5_hash(text):
+    """计算字符串的MD5哈希值"""
+    # 创建md5对象
+    md5 = hashlib.md5()
+
+    # 更新哈希对象(需要将字符串编码为bytes)
+    md5.update(text.encode('utf-8'))
+
+    # 获取十六进制哈希值
+    return md5.hexdigest()
+
+class DES_py():
+    def __init__(self, key = "c2lub3NvZnQuY2hhbm5lbC55ZHBpYw=="):
+        self.key = key
+        self.mode = DES.MODE_ECB
+        # 注意:这里直接使用base64字符串作为密钥,而不是解码base64
+        key_utf8 = key.encode('utf-8')[:8]
+        self.cipher = DES.new(key_utf8, DES.MODE_ECB)
+
+    def des_encrypt(self,plaintext):
+
+        padded_data = pad(plaintext.encode('utf-8'), DES.block_size)
+        encrypted_data = self.cipher.encrypt(padded_data)
+        return base64.b64encode(encrypted_data).decode('utf-8')
+
+    def des_decrypt(self,ciphertext):
+        encrypted_data = base64.b64decode(ciphertext)
+        decrypted_data = self.cipher.decrypt(encrypted_data)
+        unpadded_data = unpad(decrypted_data, DES.block_size)
+        return unpadded_data.decode('utf-8')
+
+class BHBX_login():
+    def __init__(self,username,pwd,openId):
+        self.username =  username
+        self.pwd = pwd
+        self.des = DES_py()
+        self.openId = openId
+        self.requests = requests.Session()
+        self.requests.headers.update({
+            "Accept": "application/json, text/plain, */*",
+            "Accept-Language": "zh-CN,zh;q=0.9",
+            "Cache-Control": "no-cache",
+            "Connection": "keep-alive",
+            "Content-Type": "application/json;charset=UTF-8",
+            "Origin": "https://biz.bpic.com.cn",
+            "Pragma": "no-cache",
+            "Referer": "https://biz.bpic.com.cn/motor/pspcc/app/",
+            "Sec-Fetch-Dest": "empty",
+            "Sec-Fetch-Mode": "cors",
+            "Sec-Fetch-Site": "same-origin",
+            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
+            "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
+            "sec-ch-ua-mobile": "?0",
+            "sec-ch-ua-platform": "\"Windows\"",
+            "sessionId": ""
+        })
+    def login(self):
+        url = "https://biz.bpic.com.cn/motor/pspcs/Channel/channelTask/channelTaskMain.do"
+        data = {
+            "head": {
+                "transCode": "Login",
+                "transType": "Req",
+                "token": ""
+            },
+            "openId": self.openId,
+            "userCode": self.username,
+            "pwd": self.pwd,
+            "verification": "",
+            "loginSystemCode": "",
+            "isTelemarketing": "0",
+            "forceLoginFlag": "1", # 1
+            "clientIP": ""
+        }
+        encryptionData =  self.des.des_encrypt(json.dumps(data,ensure_ascii=False))
+        md5_ =  md5_hash(encryptionData + self.des.key)
+        en_data = {
+            "encryptionData": encryptionData,
+            "md5": md5_
+        }
+        res = self.requests.post(url,json=en_data)
+        logger.info("登录返回值 ==> " + res.text)
+        try:
+            res_txt = self.des.des_decrypt(res.text)
+            logger.info("登录返回值 解密结果 ==> " + res_txt)
+            res_json = json.loads(res_txt)
+            # 获取当前时间
+            formatted_time = str(datetime.now().strftime("%Y-%m-%d"))
+            localstorage = {
+                "ip": "",
+                "openId": self.openId,
+                "timeDifference": 10965,
+                "time": formatted_time,
+                "loginComList": res_json['loginComInfoList'],
+                "userCode": self.username,
+                "token": res_json['head']['token'],
+                "userName": res_json['userName'],
+                "rememberMe": True,
+                "sessionId": res_json['head']['sessionId'],
+                "loginSystem": {}
+            }
+            dddd = """localStorage.setItem("cherry.cherry.user",'localstorage_')"""
+            dddd = dddd.replace("localstorage_",json.dumps(localstorage,ensure_ascii=False))
+            logger.info("核心登录数据:"+dddd)
+        except Exception as e:
+            logger.error("des 解密失败")
+            res_json = {}
+        return res_json
+# 账号:114050281   密码:Bhcx1234  openId  odviKt0g-CP5NVNnZKiHEStPbRoU
+
+usernamelist = [
+    {"username":"114050281","passward":"Bhcx1234","openId":"odviKt0g-CP5NVNnZKiHEStPbRoU"} # odviKt0g-CP5NVNnZKiHEStPbRoU
+]
+username = "114050281"
+password = ""
+openId = ""
+for item in usernamelist:
+    if item['username'] == username:
+        password = item['passward']
+        openId = item['openId']
+        break
+if openId == "":
+    print(f"账号 {username} 暂未配置 账号信息")
+    exit()
+bhbx = BHBX_login(username,password,openId)
+print(bhbx.login())
+
+

+ 58 - 64
tenant/insurance/quotation-commons/src/main/java/com/jzg/quotation/commons/component/RequestComponent.java

@@ -412,7 +412,6 @@ public class RequestComponent {
      * @return <Response> ResponseEntity<Response>
      */
     public <Response> ResponseEntity<Response> post(String url, Object body, Class<Response> responseClass, HttpHeaders headers) {
-        body = Optional.ofNullable(body).orElse("");
         log.info("URL的值:{},body的值:{}", url, body);
         WebClient webClient = selectNoSSLWebClient2();
         try {
@@ -432,62 +431,35 @@ public class RequestComponent {
                     .retrieve()
                     .toEntity(String.class)
                     .block();
-
             if (rawResponse == null) {
-                throw new WebClientResponseException(
-                        500,
-                        "No response received",
-                        HttpHeaders.EMPTY,
-                        "No response received".getBytes(StandardCharsets.UTF_8),
-                        null
-                );
+                throw new WebClientResponseException(500, "No response received", HttpHeaders.EMPTY, "No response received".getBytes(StandardCharsets.UTF_8), null);
             }
 
             String responseBody = rawResponse.getBody();
             if (responseBody == null) {
                 responseBody = "";
             }
-
             // 处理响应:过滤非法字符并解析JSON
-            String filteredResponse = filterIllegalCharacters(responseBody);
+            //String filteredResponse = filterIllegalCharacters(responseBody);
             Response result;
-            try {
-                // 使用FastJSON手动解析JSON
-                result = JSON.parseObject(filteredResponse, responseClass);
-            } catch (Exception e) {
-                log.error("JSON解析失败,过滤后的响应内容: {}", filteredResponse, e);
-                throw new WebClientResponseException(
-                        500,
-                        "JSON parsing failed after filtering: " + e.getMessage(),
-                        rawResponse.getHeaders(),
-                        filteredResponse.getBytes(StandardCharsets.UTF_8),
-                        null
-                );
+            // 判断返回的字符串是否为有效的JSON格式
+            if (isValidJson(responseBody)) {
+                result = JSON.parseObject(responseBody, responseClass);
+            }else{
+                result = (Response)responseBody;
             }
-
             // 构建返回的ResponseEntity
             HttpHeaders responseHeaders = new HttpHeaders();
             responseHeaders.setContentType(MediaType.APPLICATION_JSON);
-
-            return ResponseEntity.status(rawResponse.getStatusCode())
-                    .headers(responseHeaders)
-                    .body(result);
-
+            return ResponseEntity.status(rawResponse.getStatusCode()).headers(responseHeaders).body(result);
         } catch (WebClientResponseException e) {
             // 如果是WebClientResponseException,记录详细信息
-            log.error("WebClient请求失败,状态码: {}, 响应头: {}",
-                    e.getStatusCode(), e.getHeaders(), e);
+            log.error("WebClient请求失败,状态码: {}, 响应头: {}", e.getStatusCode(), e.getHeaders(), e);
             throw e;
         } catch (Exception e) {
             // 处理其他异常
             log.error("请求失败", e);
-            throw new WebClientResponseException(
-                    500,
-                    "Request failed: " + e.getMessage(),
-                    HttpHeaders.EMPTY,
-                    e.getMessage().getBytes(StandardCharsets.UTF_8),
-                    null
-            );
+            throw new WebClientResponseException(500, "Request failed: " + e.getMessage(), HttpHeaders.EMPTY, e.getMessage().getBytes(StandardCharsets.UTF_8), null);
         }
     }
 
@@ -516,22 +488,21 @@ public class RequestComponent {
                     .body(BodyInserters.fromValue(body))
                     .retrieve()
                     .bodyToMono(String.class);
-
             // 处理响应:过滤非法字符并解析JSON
             Mono<Response> resultMono = responseMono.flatMap(rawResponse -> {
                 // 过滤非法字符
-                String filteredResponse = filterIllegalCharacters(rawResponse);
+                //String filteredResponse = filterIllegalCharacters(rawResponse);
                 try {
                     // 使用FastJSON手动解析JSON
-                    Response result = JSON.parseObject(filteredResponse, responseClass);
+                    Response result = JSON.parseObject(rawResponse, responseClass);
                     return Mono.just(result);
                 } catch (Exception e) {
                     // 如果JSON解析失败,抛出异常
                     return Mono.error(new WebClientResponseException(
                             500,
-                            "JSON parsing failed after filtering: " + e.getMessage(),
+                            "JSON parsing failed: " + e.getMessage(),
                             HttpHeaders.EMPTY,
-                            filteredResponse.getBytes(StandardCharsets.UTF_8),
+                            rawResponse.getBytes(StandardCharsets.UTF_8),
                             null
                     ));
                 }
@@ -540,32 +511,14 @@ public class RequestComponent {
             Response result = resultMono.block();
             HttpHeaders responseHeaders = new HttpHeaders();
             responseHeaders.setContentType(MediaType.APPLICATION_JSON);
-            // 获取原始响应状态码
-            ResponseEntity<String> rawResponse = webClient.post()
-                    .uri(url)
-                    .headers(setHeaders(headers))
-                    .header(HttpHeaders.ACCEPT, "application/json,text/html,text/json,*/*")
-                    .header(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.9")
-                    .header(HttpHeaders.CACHE_CONTROL, "no-cache")
-                    .body(BodyInserters.fromValue(body))
-                    .retrieve()
-                    .toEntity(String.class)
-                    .block();
-
-            int statusCode = rawResponse != null ? rawResponse.getStatusCode().value() : 200;
-            return ResponseEntity.status(statusCode).headers(responseHeaders).body(result);
+            return ResponseEntity.status(HttpStatus.OK).headers(responseHeaders).body(result);
         } catch (WebClientResponseException e) {
             e.printStackTrace();
             throw e;
         } catch (Exception e) {
+            e.printStackTrace();
             // 处理其他异常
-            throw new WebClientResponseException(
-                    500,
-                    "Request failed: " + e.getMessage(),
-                    HttpHeaders.EMPTY,
-                    e.getMessage().getBytes(StandardCharsets.UTF_8),
-                    null
-            );
+            throw new WebClientResponseException(500, "Request failed: " + e.getMessage(), HttpHeaders.EMPTY, e.getMessage().getBytes(StandardCharsets.UTF_8), null);
         }
     }
 
@@ -964,4 +917,45 @@ public class RequestComponent {
                 .block();
     }
 
+    /**
+     * 判断字符串是否为有效的JSON格式
+     * 支持JSON对象({})和JSON数组([])
+     *
+     * @param str 待检测的字符串
+     * @return 如果是有效的JSON格式返回true,否则返回false
+     */
+    private boolean isValidJson(String str) {
+        // 空字符串或null不是有效JSON
+        if (str == null || str.isEmpty()) {
+            return false;
+        }
+
+        // 去除首尾空白字符
+        String trimmed = str.trim();
+        if (trimmed.isEmpty()) {
+            return false;
+        }
+
+        // 检查首尾字符是否符合JSON格式
+        char firstChar = trimmed.charAt(0);
+        char lastChar = trimmed.charAt(trimmed.length() - 1);
+
+        // JSON对象以{}包裹,JSON数组以[]包裹
+        boolean isJsonObject = (firstChar == '{' && lastChar == '}');
+        boolean isJsonArray = (firstChar == '[' && lastChar == ']');
+
+        if (!isJsonObject && !isJsonArray) {
+            return false;
+        }
+
+        // 使用FastJSON尝试解析,确保是合法的JSON结构
+        try {
+            JSON.parse(trimmed);
+            return true;
+        } catch (Exception e) {
+            log.debug("JSON格式校验失败: {}", e.getMessage());
+            return false;
+        }
+    }
+
 }

+ 37 - 31
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/factory/decorator/quote/QuoteDispatchDecorator.java

@@ -52,40 +52,46 @@ public class QuoteDispatchDecorator {
     }
 
     public QuoteResultsVo quote(QuoteVo quoteVo) throws Exception {
-        String agreementId = quoteVo.getOrder().getAgreementId();
-        log.info("agreementId参数的值:{}", agreementId);
+        try {
+            String agreementId = quoteVo.getOrder().getAgreementId();
+            log.info("agreementId参数的值:{}", agreementId);
 
-        String token = baseController.getToken();
-        String systemCode = "";
-        //如果是APP登录的话 则传递SystemCode的头部字段
-        if(baseController.getUserSystemCode().equals("APP")){
-            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
-            systemCode = request.getHeader("system_code");
-        }else{
-            //设置固定的信息
-            systemCode = "WWW001";
-        }
-        HttpResult<PtlAgreementAttribution> agreementAttribution = platformClient.getAgreementAttribution(agreementId, token, systemCode);
-        AssertionUtils.isFail(agreementAttribution.getCode() == 500, "获取协议属性失败");
-        PtlAgreementAttribution ptlAgreementAttribution = agreementAttribution.getData();
+            String token = baseController.getToken();
+            String systemCode = "";
+            //如果是APP登录的话 则传递SystemCode的头部字段
+            if(baseController.getUserSystemCode().equals("APP")){
+                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+                systemCode = request.getHeader("system_code");
+            }else{
+                //设置固定的信息
+                systemCode = "WWW001";
+            }
+            log.info("systemCode参数的值:{}", systemCode);
+            HttpResult<PtlAgreementAttribution> agreementAttribution = platformClient.getAgreementAttribution(agreementId, token, systemCode);
+            AssertionUtils.isFail(agreementAttribution.getCode() == 500, "获取协议属性失败");
+            PtlAgreementAttribution ptlAgreementAttribution = agreementAttribution.getData();
 
-        // API类型设置
-        quoteVo.getOrder().setApiType(Integer.parseInt(ptlAgreementAttribution.getApiType()));
-        Map<String, String> fieldMap = agreementAttribution.getData().getFields().stream()
-                .map(obj -> new JSONObject((Map<String, Object>) obj))  // 转换为 JSONObject
-                .collect(Collectors.toMap(
-                        json -> json.getString("field"),    // 获取 key
-                        json -> json.getString("value") == null ? "" : json.getString("value"),
-                        (existing, replacement) -> existing + replacement // 获取 value
-                ));
+            // API类型设置
+            quoteVo.getOrder().setApiType(Integer.parseInt(ptlAgreementAttribution.getApiType()));
+            Map<String, String> fieldMap = agreementAttribution.getData().getFields().stream()
+                    .map(obj -> new JSONObject((Map<String, Object>) obj))  // 转换为 JSONObject
+                    .collect(Collectors.toMap(
+                            json -> json.getString("field"),    // 获取 key
+                            json -> json.getString("value") == null ? "" : json.getString("value"),
+                            (existing, replacement) -> existing + replacement // 获取 value
+                    ));
 
-        AttributionInformationVo attributionInformationVo = new AttributionInformationVo();
-        attributionInformationVo.setConfigInfo(fieldMap);
-        attributionInformationVo.setUsername(ptlAgreementAttribution.getUsername());
-        attributionInformationVo.setPassword(ptlAgreementAttribution.getPassword());
-        quoteVo.getOrder().setAttributionInformationVo(attributionInformationVo);
+            AttributionInformationVo attributionInformationVo = new AttributionInformationVo();
+            attributionInformationVo.setConfigInfo(fieldMap);
+            attributionInformationVo.setUsername(ptlAgreementAttribution.getUsername());
+            attributionInformationVo.setPassword(ptlAgreementAttribution.getPassword());
+            quoteVo.getOrder().setAttributionInformationVo(attributionInformationVo);
 
-        //协议调度
-        return target.quote(quoteVo);
+            //协议调度
+            return target.quote(quoteVo);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
     }
 }