Ver Fonte

fix 恒邦报价遇到重复投保无法更改时间继续投保问题

liub há 3 meses atrás
pai
commit
56b5b144ba

+ 1 - 3
tenant/insurance/quotation-commons/src/main/java/com/jzg/quotation/commons/component/RequestComponent.java

@@ -32,8 +32,6 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
-import java.util.Map;
-import java.util.Optional;
 import java.time.Duration;
 import java.util.Map;
 import java.util.Optional;
@@ -393,7 +391,7 @@ public class RequestComponent {
     public <Response> ResponseEntity<Response> post(String url, String body, Class<Response> responseClass, HttpHeaders headers) {
         body = Optional.ofNullable(body).orElse("");
         log.info("URL的值:{},body的值:{}", url, body);
-        WebClient webClient = selectNoSSLWebClient();
+//        WebClient webClient = selectNoSSLWebClientWithProxy("127.0.0.1", 9001);
         try {
             // 创建一个不带Accept-Encoding的请求头,避免服务器返回压缩数据
             HttpHeaders requestHeaders = new HttpHeaders();

+ 36 - 23
tenant/insurance/quotation-commons/src/main/java/com/jzg/quotation/commons/utils/DateUtil.java

@@ -418,47 +418,60 @@ public class DateUtil {
 
     private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
     private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+    private static final ZoneId ASIA_SHANGHAI = ZoneId.of("Asia/Shanghai");
 
     /**
-     * 获取投保日期的下一天0点(北京时间)并转为UTC ISO8601格式
-     * 驾意险起保日期默认为投保日期的下一天的0点
+     * 获取北京时间下一天0点对应的UTC时间字符串
+     * 驾意险起保日期默认为投保日期的下一天的0点(北京时间)
      */
     public static String getNextDayMidnightUtc() {
-        LocalDate tomorrow = LocalDate.now().plusDays(1);
+        LocalDate tomorrow = LocalDate.now(ASIA_SHANGHAI).plusDays(1);
         String beijingTime = tomorrow.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 00:00:00";
         return toUtcDateTime(beijingTime);
     }
 
     /**
-     * 获取指定UTC日期时间的下一天0点(北京时间)并转为UTC ISO8601格式
-     * 驾意险不支持即时起保,需调整起保日期为主险投保日期的下一日0时
+     * 判断一个UTC时间是否对应北京时间的0点(用于驾意险起保时间判断)
      *
-     * @param utcDateTime 主险起保UTC时间(ISO8601格式,如 2026-07-04T16:00:00.000Z)
-     * @return 下一天北京时间0点对应的UTC ISO8601格式
+     * @param utcTime UTC时间字符串(ISO8601格式,如 2026-06-25T16:00:00.000Z 即北京06-26 00:00:00)
+     * @return true=北京时间0点
      */
-    public static String getNextDayMidnightUtc(String utcDateTime) {
-        ZonedDateTime utcZoned = ZonedDateTime.parse(utcDateTime, DateTimeFormatter.ISO_DATE_TIME);
-        ZonedDateTime beijingZoned = utcZoned.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
-        LocalDate nextDay = beijingZoned.toLocalDate().plusDays(1);
-        String beijingTime = nextDay.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 00:00:00";
+    public static boolean isBeijingMidnight(String utcTime) {
+        ZonedDateTime utcZdt = ZonedDateTime.parse(utcTime, DateTimeFormatter.ISO_DATE_TIME);
+        ZonedDateTime bjZdt = utcZdt.withZoneSameInstant(ASIA_SHANGHAI);
+        return "00:00:00".equals(bjZdt.toLocalTime().toString());
+    }
+
+    /**
+     * 获取驾意险起保时间对应的UTC时间字符串
+     * 驾意险不支持即时起保:
+     * - 调用方需先通过 isBeijingMidnight 判断,已是0点则直接复用原值,无需调用此方法
+     * - 若北京时间不是0点,则调整为下一日北京时间0点,再转为UTC
+     *
+     * @param utcTime 主险起保时间(ISO8601 UTC格式,如 2026-07-04T08:00:00.000Z 即北京16:00)
+     * @return UTC时间字符串
+     */
+    public static String getNextDayMidnightUtc(String utcTime) {
+        ZonedDateTime utcZdt = ZonedDateTime.parse(utcTime, DateTimeFormatter.ISO_DATE_TIME);
+        ZonedDateTime bjZdt = utcZdt.withZoneSameInstant(ASIA_SHANGHAI);
+        LocalDate targetDate = bjZdt.toLocalDate();
+        // 非0点则顺延至下一日0点
+        if (!"00:00:00".equals(bjZdt.toLocalTime().toString())) {
+            targetDate = targetDate.plusDays(1);
+        }
+        String beijingTime = targetDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 00:00:00";
         return toUtcDateTime(beijingTime);
     }
 
     /**
-     * 将北京时间(yyyy-MM-dd HH:mm:ss)转换为UTC时间(ISO8601格式)
+     * 将北京时间字符串(yyyy-MM-dd HH:mm:ss)转为实际UTC时间(yyyy-MM-dd'T'HH:mm:ss.SSS'Z')
+     * 如 "2026-06-25 16:00:00"(北京)→ "2026-06-25T08:00:00.000Z"(UTC)
      */
     public static String toUtcDateTime(String beijingTime) {
-        // 解析输入的北京时间
         LocalDateTime localDateTime = LocalDateTime.parse(beijingTime, INPUT_FORMAT);
-
-        // 指定时区为中国北京时间
-        ZonedDateTime beijingZoned = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
-
-        // 转为UTC时区
-        ZonedDateTime utcZoned = beijingZoned.withZoneSameInstant(ZoneId.of("UTC"));
-
-        // 格式化为ISO8601字符串
-        return utcZoned.format(OUTPUT_FORMAT);
+        ZonedDateTime bjZdt = localDateTime.atZone(ASIA_SHANGHAI);
+        ZonedDateTime utcZdt = bjZdt.withZoneSameInstant(ZoneOffset.UTC);
+        return utcZdt.format(OUTPUT_FORMAT);
     }
 
     public static void main(String[] args) throws ParseException {

+ 73 - 14
tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/component/HengBangCrawlerRequestComponent.java

@@ -2,6 +2,8 @@ package com.jzg.quotation.hengbang.crawler.component;
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -10,6 +12,7 @@ import com.jzg.commons.exception.SystemException;
 import com.jzg.quotation.commons.component.RequestComponent;
 import com.jzg.quotation.commons.constant.CacheConstant;
 import com.jzg.quotation.commons.service.ExteriorUtilsRequest;
+import com.jzg.quotation.commons.utils.DateUtil;
 import com.jzg.quotation.commons.utils.RequestObjectConversion;
 import com.jzg.quotation.hengbang.crawler.constant.RequestConstant;
 import com.jzg.quotation.hengbang.crawler.constant.enums.RequestUrl;
@@ -39,6 +42,8 @@ import javax.crypto.spec.SecretKeySpec;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
@@ -302,24 +307,78 @@ public class HengBangCrawlerRequestComponent {
             String json = mapper.writeValueAsString(calculationRequest);
             log.info("提交报价数据请求参数: {}", json);
             req.setHbEncD(HengBangAESUtil.encryptStr(json));
-            HbCrawlerSaleInfoResponse response = requestComponent.toStringPost(RequestUrl.CALCULATION.getRequestUrl(hbCrawlerProperties.getBaseUrl()), req, RequestUrl.CALCULATION.getRequestDescription(), HbCrawlerSaleInfoResponse.class, headers);
-            System.out.println(HengBangAESUtil.decryptStr(response.getHbEncD()));
+
+            String requestJson = mapper.writeValueAsString(req);
+            String url = RequestUrl.CALCULATION.getRequestUrl(hbCrawlerProperties.getBaseUrl());
+            log.info("恒邦calculation请求URL: {}, 请求体: {}", url, requestJson);
+
+            // 使用Hutool发送HTTP POST请求
+            HttpResponse httpResponse = HttpRequest.post(url)
+                    .header(headers)
+                    .body(requestJson)
+                    .timeout(60000)
+                    .execute();
+
+            String responseBody = httpResponse.body();
+            int status = httpResponse.getStatus();
+            log.info("恒邦calculation响应状态码: {}, 响应体(前500字符): {}", status,
+                    responseBody != null && responseBody.length() > 500 ? responseBody.substring(0, 500) : responseBody);
+
+            // 处理非2xx响应
+            if (!httpResponse.isOk()) {
+                if (status == 500) {
+                    JSONObject resObj = JSONObject.parseObject(responseBody);
+                    String detail = resObj.getString("detail");
+                    if (detail.contains("重复投保")) {
+                        String time = extractDuplicateEndTime(detail);
+                        if (time != null) {
+                            calculationRequest.setBegintimeCi(DateUtil.toUtcDateTime(time));
+                            calculationRequest.setBegintimeBi(DateUtil.toUtcDateTime(time));
+                        }
+                        return getCalculationResult(calculationRequest, headers);
+                    }
+                    return StrUtil.isNotBlank(detail) ? detail : responseBody;
+                } else if (status == 400) {
+                    JSONObject resObj = JSONObject.parseObject(responseBody);
+                    String title = resObj.getString("title");
+                    return StrUtil.isNotBlank(title) ? title : responseBody;
+                }
+                log.error("恒邦calculation接口返回非2xx: {}, 响应: {}", status, responseBody);
+            }
+
+            // Jackson解析响应
+            HbCrawlerSaleInfoResponse response = mapper.readValue(responseBody, HbCrawlerSaleInfoResponse.class);
+            if (response.getHbEncD() == null) {
+                throw new SystemException("恒邦calculation接口返回数据缺少hbEncD字段,原始响应: " + responseBody);
+            }
             String resJson = HengBangAESUtil.decryptStr(response.getHbEncD());
+            log.info("恒邦calculation解密后响应: {}", resJson);
             return resJson;
-        } catch (WebClientResponseException e) {
-            log.error("恒邦calculation接口调用失败[{}]: {}", e.getStatusCode(), e.getResponseBodyAsString());
-            final JSONObject jsonObject = JSONObject.parseObject(e.getResponseBodyAsString());
-            if (jsonObject.getInteger("status") == 500) {
-                String detail = jsonObject.getString("detail");
-                return StrUtil.isNotBlank(detail) ? detail : e.getResponseBodyAsString();
-            } else if (jsonObject.getInteger("status") == 400) {
-                return jsonObject.getString("title");
-            } else {
-                throw new SystemException(e.getMessage());
-            }
+        } catch (SystemException e) {
+            throw e;
         } catch (Exception e) {
-            throw new SystemException(e.getMessage());
+            log.error("恒邦calculation接口调用异常", e);
+            throw new SystemException("恒邦报价第二步异常: " + e.getMessage());
+        }
+    }
+
+
+    // 重复投保提取时间
+    private static final Pattern DUPLICATE_END_TIME_PATTERN = Pattern.compile("终保日期 (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})");
+
+    /**
+     * 提取系统自动分配的起保时间
+     *
+     * @param calculationResult 核心提示
+     * @return 提取到的时间字符串,未提取到返回 null
+     */
+    public String extractDuplicateEndTime(String calculationResult) {
+        Matcher matcher = DUPLICATE_END_TIME_PATTERN.matcher(calculationResult);
+        if (matcher.find()) {
+            String shortEndTime = matcher.group(1); // 提取到 2025-12-14 16:00
+            return shortEndTime + ":00"; // 补全秒数为 2025-12-14 16:00:00
         }
+        return null;
     }
 
     /**

+ 23 - 14
tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/entity/request/HbCrawlerCalculationRequest.java

@@ -1,7 +1,6 @@
 package com.jzg.quotation.hengbang.crawler.entity.request;
 
 import cn.hutool.core.collection.CollUtil;
-import com.alibaba.excel.util.StringUtils;
 import com.jzg.commons.entity.quote.vo.AccidentalDrivingVo;
 import com.jzg.commons.entity.quote.vo.KindInfoVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
@@ -88,16 +87,25 @@ public class HbCrawlerCalculationRequest extends HbBaseRequest {
         }
         if (CollUtil.isNotEmpty(kindInfoVoList)) {
             this.epersonliabilityList = getEpersonliabilityList(kindInfoVoList, depreciationPrice);
-            this.begintimeBi = DateUtil.toUtcDateTime(quoteVo.getBiRiskInfoVo().getStartDate());
+            // 防 NPE:BiRiskInfoVo 或 startDate 为空时跳过 begintimeBi 赋值
+            if (Objects.nonNull(quoteVo.getBiRiskInfoVo()) && Objects.nonNull(quoteVo.getBiRiskInfoVo().getStartDate())) {
+                this.begintimeBi = DateUtil.toUtcDateTime(quoteVo.getBiRiskInfoVo().getStartDate());
+            }
             this.risktype = "2";
         } else {
             this.epersonliabilityList = new ArrayList<>();
         }
-        // 驾意险不支持即时起保,起保日期为主险投保日期的下一日0时(北京时间)
+        // 驾意险不支持即时起保,若传入时间不是0点则自动改为下一日0点(北京时间),再转为UTC
         if ("2".equals(this.risktype) && this.begintimeBi != null) {
-            this.begintimeAccident = DateUtil.getNextDayMidnightUtc(this.begintimeBi);
+            // 商业险起始时间已是北京时间0点,驾意险直接复用,无需取下一日0时
+            this.begintimeAccident = DateUtil.isBeijingMidnight(this.begintimeBi)
+                    ? this.begintimeBi
+                    : DateUtil.getNextDayMidnightUtc(this.begintimeBi);
         } else if ("1".equals(this.risktype) && this.begintimeCi != null) {
-            this.begintimeAccident = DateUtil.getNextDayMidnightUtc(this.begintimeCi);
+            // 交强险起始时间已是北京时间0点,驾意险直接复用,无需取下一日0时
+            this.begintimeAccident = DateUtil.isBeijingMidnight(this.begintimeCi)
+                    ? this.begintimeCi
+                    : DateUtil.getNextDayMidnightUtc(this.begintimeCi);
         } else {
             this.begintimeAccident = DateUtil.getNextDayMidnightUtc();
         }
@@ -194,9 +202,9 @@ public class HbCrawlerCalculationRequest extends HbBaseRequest {
     public static AccidentInsuranceDTO convertToAccidentInsuranceDTO(RiskInfoVo biRiskInfoVo, AccidentalDrivingVo vo) {
         AccidentInsuranceDTO dto = new AccidentInsuranceDTO();
         dto.setNonriskcode(vo.getProductCode().split("_")[1]);
-        dto.setProgramcode(vo.getProductCode());
-        dto.setProgramname("1119险种5座非营运客车保额525000保费99.9(山西)");
         dto.setCompanycode("0107");
+        dto.setProgramcode(vo.getProductCode());
+        dto.setProgramname(vo.getProductName());
         dto.setIfdefault(true);
         dto.setBuynum(vo.getQuantity());
         List<GgriskprogramdetailsDTO> detailsList = getGgriskprogramdetailsList(vo.getProductCode().split("_")[1], vo.getProductCode());
@@ -331,6 +339,9 @@ public class HbCrawlerCalculationRequest extends HbBaseRequest {
     private String clauseType;
     private List<String> ecarChargingPosts;
     private List<String> carInsureDevices;
+    // 正确请求中 begintimeAccident/endtimeAccident 紧跟 carInsureDevices(位于 drivingtype 之前)
+    private String begintimeAccident;
+    private String endtimeAccident;
     private String effectfLag;
     private String drivingtype;
     private String relationCarOwner = "a";
@@ -347,8 +358,6 @@ public class HbCrawlerCalculationRequest extends HbBaseRequest {
     private String taxonomyCode;
     private String epolicyFlag;
     private String accidentEpolicyFlag;
-    private String begintimeAccident;
-    private String endtimeAccident;
     private HbCrawlerRecordResponse transferVehicle;
 
     @Data
@@ -425,19 +434,19 @@ public class HbCrawlerCalculationRequest extends HbBaseRequest {
         @Serial
         private static final long serialVersionUID = -9183755801236888529L;
 
-        private String createtime;
+        private String programcode;
         private String paramname;
         private String paramvalue;
-        private String programcode;
+        private String createtime;
         private String updatetime;
         private String validind;
 
         public GgriskprogramparamlDTO(String createtime, String paramname, String paramvalue, String programcode, String updatetime, String validind) {
-            this.createtime = createtime;
-            this.updatetime = updatetime;
+            this.programcode = programcode;
             this.paramname = paramname;
             this.paramvalue = paramvalue;
-            this.programcode = programcode;
+            this.createtime = createtime;
+            this.updatetime = updatetime;
             this.validind = validind;
         }
     }

+ 1 - 20
tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/service/Impl/HengBangCrawlerRequestImpl.java

@@ -53,8 +53,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * @description: 恒邦财险 爬虫请求
@@ -173,7 +171,7 @@ public class HengBangCrawlerRequestImpl implements HengBangCrawlerRequest {
         }
 
         if (errorMsg.contains("重复投保")) {
-            String time = extractDuplicateEndTime(errorMsg);
+            String time = hbCrawlerRequestComponent.extractDuplicateEndTime(errorMsg);
             if (time != null) {
                 calculationRequest.setBegintimeCi(DateUtil.toUtcDateTime(time));
                 calculationRequest.setBegintimeBi(DateUtil.toUtcDateTime(time));
@@ -243,23 +241,6 @@ public class HengBangCrawlerRequestImpl implements HengBangCrawlerRequest {
         calculationRequest.setAccidentEpolicyFlag(accidentList.isEmpty() ? "" : "Y");
     }
 
-    // 重复投保提取时间
-    private static final Pattern DUPLICATE_END_TIME_PATTERN = Pattern.compile("终保日期 (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})");
-
-    /**
-     * 提取系统自动分配的起保时间
-     *
-     * @param calculationResult 核心提示
-     * @return 提取到的时间字符串,未提取到返回 null
-     */
-    private String extractDuplicateEndTime(String calculationResult) {
-        Matcher matcher = DUPLICATE_END_TIME_PATTERN.matcher(calculationResult);
-        if (matcher.find()) {
-            String shortEndTime = matcher.group(1); // 提取到 2025-12-14 16:00
-            return shortEndTime + ":00"; // 补全秒数为 2025-12-14 16:00:00
-        }
-        return null;
-    }
 
     /**
      * 获取当前时间2小时以后的时间

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/utils/HengBangAESUtil.java


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff