fuyuchuan пре 1 дан
родитељ
комит
01d5b3c8fc

+ 21 - 2
service-issue/service-issue-biz/src/main/java/com/usky/issue/controller/api/CloudIntegrationApiController.java

@@ -7,6 +7,7 @@ import com.usky.issue.service.CloudSyncReceiveService;
 import com.usky.issue.service.constant.CloudIntegrationConstants;
 import com.usky.issue.service.constant.CloudIntegrationConstants;
 import com.usky.issue.service.vo.CloudConnectionTestResponse;
 import com.usky.issue.service.vo.CloudConnectionTestResponse;
 import com.usky.issue.service.vo.SyncPacket;
 import com.usky.issue.service.vo.SyncPacket;
+import com.usky.issue.service.vo.SyncPollRequest;
 import com.usky.issue.service.vo.SyncResponse;
 import com.usky.issue.service.vo.SyncResponse;
 import com.usky.issue.service.util.CredentialMaskUtil;
 import com.usky.issue.service.util.CredentialMaskUtil;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -128,7 +129,18 @@ public class CloudIntegrationApiController {
     }
     }
 
 
     /**
     /**
-     * 下发数据轮询(云端→本地)
+     * 下发数据轮询(云端→本地),POST 供网关/offline-api 路由(与 testConnection 一致)
+     */
+    @PostMapping("/sync/poll")
+    public ApiResult<SyncPacket> pollPost(
+            @RequestHeader(value = CloudIntegrationConstants.HEADER_API_KEY, required = false) String apiKey,
+            @RequestBody SyncPollRequest request) {
+        return doPoll(apiKey, request.getTenantId(), request.getTableName(),
+                request.getLastVersion(), request.isForceBackfill());
+    }
+
+    /**
+     * 下发数据轮询(GET 兼容旧调用)
      */
      */
     @GetMapping("/sync/poll")
     @GetMapping("/sync/poll")
     public ApiResult<SyncPacket> poll(
     public ApiResult<SyncPacket> poll(
@@ -137,12 +149,19 @@ public class CloudIntegrationApiController {
             @RequestParam String tableName,
             @RequestParam String tableName,
             @RequestParam(required = false) Long lastVersion,
             @RequestParam(required = false) Long lastVersion,
             @RequestParam(required = false, defaultValue = "false") boolean forceBackfill) {
             @RequestParam(required = false, defaultValue = "false") boolean forceBackfill) {
+        return doPoll(apiKey, tenantId, tableName, lastVersion, forceBackfill);
+    }
 
 
+    private ApiResult<SyncPacket> doPoll(String apiKey, String tenantId, String tableName,
+                                         Long lastVersion, boolean forceBackfill) {
         if (!cloudAuthService.validateToken(apiKey, Integer.valueOf(tenantId))) {
         if (!cloudAuthService.validateToken(apiKey, Integer.valueOf(tenantId))) {
             log.warn("[poll] 认证失败 tenantId={}, tableName={}", tenantId, tableName);
             log.warn("[poll] 认证失败 tenantId={}, tableName={}", tenantId, tableName);
             return ApiResult.error(CloudIntegrationConstants.MSG_POLL_AUTH_FAILED);
             return ApiResult.error(CloudIntegrationConstants.MSG_POLL_AUTH_FAILED);
         }
         }
-        return ApiResult.success(cloudSyncReceiveService.pollDownQueue(tenantId, tableName, lastVersion, forceBackfill));
+        log.info("[poll] tenantId={}, tableName={}, lastVersion={}, forceBackfill={}",
+                tenantId, tableName, lastVersion, forceBackfill);
+        return ApiResult.success(cloudSyncReceiveService.pollDownQueue(
+                tenantId, tableName, lastVersion, forceBackfill));
     }
     }
 
 
     /**
     /**

+ 33 - 22
service-issue/service-issue-biz/src/main/java/com/usky/issue/service/client/CloudPlatformClient.java

@@ -11,7 +11,9 @@ import com.usky.issue.service.util.HmacSignUtil;
 import com.usky.issue.service.vo.CloudConnectionTestResult;
 import com.usky.issue.service.vo.CloudConnectionTestResult;
 import com.usky.issue.service.vo.CloudPollResult;
 import com.usky.issue.service.vo.CloudPollResult;
 import com.usky.issue.service.vo.SyncPacket;
 import com.usky.issue.service.vo.SyncPacket;
+import com.usky.issue.service.vo.SyncPollRequest;
 import com.usky.issue.service.vo.SyncResponse;
 import com.usky.issue.service.vo.SyncResponse;
+import com.usky.issue.service.util.ApiResultParser;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpEntity;
@@ -136,46 +138,55 @@ public class CloudPlatformClient {
         try {
         try {
             String credential = aesGcmCipher.decrypt(config.getCredentialKey());
             String credential = aesGcmCipher.decrypt(config.getCredentialKey());
             HttpHeaders headers = buildAuthHeaders(config, credential);
             HttpHeaders headers = buildAuthHeaders(config, credential);
-            String url = UriComponentsBuilder
-                    .fromHttpUrl(resolveIntegrationBaseUrl(config) + CloudIntegrationConstants.PATH_SYNC_POLL)
-                    .queryParam("tenantId", tenantId)
-                    .queryParam("tableName", tableName)
-                    .queryParam("lastVersion", lastVersion == null ? 0L : lastVersion)
-                    .queryParam("forceBackfill", forceBackfill)
-                    .toUriString();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+
+            SyncPollRequest pollRequest = new SyncPollRequest();
+            pollRequest.setTenantId(String.valueOf(tenantId));
+            pollRequest.setTableName(tableName);
+            pollRequest.setLastVersion(lastVersion == null ? 0L : lastVersion);
+            pollRequest.setForceBackfill(forceBackfill);
+            String requestBody = OBJECT_MAPPER.writeValueAsString(pollRequest);
 
 
-            log.info("[pollCloud] 请求 url={}, tenantId={}, tableName={}, lastVersion={}, forceBackfill={}, hasToken={}",
+            String url = resolveIntegrationBaseUrl(config) + CloudIntegrationConstants.PATH_SYNC_POLL;
+            log.info("[pollCloud] POST url={}, tenantId={}, tableName={}, lastVersion={}, forceBackfill={}, hasToken={}",
                     url, tenantId, tableName, lastVersion, forceBackfill,
                     url, tenantId, tableName, lastVersion, forceBackfill,
                     StringUtils.hasText(CloudEntitySupport.resolveAccessToken(config)));
                     StringUtils.hasText(CloudEntitySupport.resolveAccessToken(config)));
 
 
             ResponseEntity<String> response = restTemplate.exchange(
             ResponseEntity<String> response = restTemplate.exchange(
-                    url, HttpMethod.GET, new HttpEntity<>(headers), String.class);
-            String body = response.getBody();
-            if (!StringUtils.hasText(body)) {
-                return CloudPollResult.builder().requestFailed(true).errorMessage("云端返回空响应").build();
-            }
-            log.info("[pollCloud] 响应 status={}, bodyLength={}", response.getStatusCodeValue(), body.length());
+                    url, HttpMethod.POST, new HttpEntity<>(requestBody, headers), String.class);
+            return parsePollResponse(response.getBody());
+        } catch (Exception e) {
+            log.error("[pollCloud] 轮询云端失败 tenantId={}, tableName={}", tenantId, tableName, e);
+            return CloudPollResult.builder().requestFailed(true).errorMessage(e.getMessage()).build();
+        }
+    }
 
 
+    private CloudPollResult parsePollResponse(String body) {
+        if (!StringUtils.hasText(body)) {
+            return CloudPollResult.builder().requestFailed(true).errorMessage("云端返回空响应").build();
+        }
+        log.info("[pollCloud] 响应 body={}", body);
+        try {
             JsonNode json = OBJECT_MAPPER.readTree(body);
             JsonNode json = OBJECT_MAPPER.readTree(body);
-            int code = json.has("code") ? json.get("code").asInt(200) : 200;
-            String msg = json.has("msg") ? json.get("msg").asText() : null;
-            if (code != 200) {
-                boolean authFailed = msg != null && msg.contains("认证");
+            if (!ApiResultParser.isSuccess(json)) {
+                String msg = ApiResultParser.resolveMessage(json);
+                log.warn("[pollCloud] 业务失败 msg={}", msg);
+                boolean authFailed = msg != null && (msg.contains("认证") || msg.contains("令牌"));
                 return CloudPollResult.builder()
                 return CloudPollResult.builder()
                         .authFailed(authFailed)
                         .authFailed(authFailed)
                         .requestFailed(true)
                         .requestFailed(true)
-                        .errorMessage(StringUtils.hasText(msg) ? msg : "poll 失败 code=" + code)
+                        .errorMessage(StringUtils.hasText(msg) ? msg : "poll 调用失败")
                         .build();
                         .build();
             }
             }
-            JsonNode dataNode = json.has("data") ? json.get("data") : json;
+            JsonNode dataNode = json.has("data") ? json.get("data") : null;
             if (dataNode == null || dataNode.isNull()) {
             if (dataNode == null || dataNode.isNull()) {
                 return CloudPollResult.builder().packet(null).build();
                 return CloudPollResult.builder().packet(null).build();
             }
             }
             SyncPacket packet = OBJECT_MAPPER.treeToValue(dataNode, SyncPacket.class);
             SyncPacket packet = OBJECT_MAPPER.treeToValue(dataNode, SyncPacket.class);
             return CloudPollResult.builder().packet(packet).build();
             return CloudPollResult.builder().packet(packet).build();
         } catch (Exception e) {
         } catch (Exception e) {
-            log.error("[pollCloud] 轮询云端失败 tenantId={}, tableName={}", tenantId, tableName, e);
-            return CloudPollResult.builder().requestFailed(true).errorMessage(e.getMessage()).build();
+            log.error("[pollCloud] 响应解析失败 body={}", body, e);
+            return CloudPollResult.builder().requestFailed(true).errorMessage("响应解析失败: " + e.getMessage()).build();
         }
         }
     }
     }
 
 

+ 59 - 0
service-issue/service-issue-biz/src/main/java/com/usky/issue/service/util/ApiResultParser.java

@@ -0,0 +1,59 @@
+package com.usky.issue.service.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.util.StringUtils;
+
+/**
+ * ApiResult 响应解析
+ *
+ * @author fyc
+ * @date 2026-06-29
+ */
+public final class ApiResultParser {
+
+    private ApiResultParser() {
+    }
+
+    /** 本项目 ApiResult 成功:status=SUCCESS 或 code=0/"0"/200 */
+    public static boolean isSuccess(JsonNode json) {
+        if (json == null || json.isNull()) {
+            return false;
+        }
+        if (json.has("status")) {
+            String status = json.get("status").asText("");
+            if ("SUCCESS".equalsIgnoreCase(status)) {
+                return true;
+            }
+            if ("FAIL".equalsIgnoreCase(status) || "ERROR".equalsIgnoreCase(status)) {
+                return false;
+            }
+        }
+        if (json.has("code")) {
+            JsonNode codeNode = json.get("code");
+            if (codeNode.isTextual()) {
+                String code = codeNode.asText();
+                return "0".equals(code) || "200".equals(code);
+            }
+            if (codeNode.isNumber()) {
+                int code = codeNode.asInt();
+                return code == 0 || code == 200;
+            }
+        }
+        return true;
+    }
+
+    public static String resolveMessage(JsonNode json) {
+        if (json == null) {
+            return null;
+        }
+        String msg = json.path("msg").asText(null);
+        if (StringUtils.hasText(msg)) {
+            return msg;
+        }
+        msg = json.path("message").asText(null);
+        if (StringUtils.hasText(msg)) {
+            return msg;
+        }
+        return json.path("errorSummary").asText(null);
+    }
+}

+ 21 - 0
service-issue/service-issue-biz/src/main/java/com/usky/issue/service/vo/SyncPollRequest.java

@@ -0,0 +1,21 @@
+package com.usky.issue.service.vo;
+
+import lombok.Data;
+
+/**
+ * 云端下发轮询请求
+ *
+ * @author fyc
+ * @date 2026-06-29
+ */
+@Data
+public class SyncPollRequest {
+
+    private String tenantId;
+
+    private String tableName;
+
+    private Long lastVersion;
+
+    private boolean forceBackfill;
+}