Bläddra i källkod

fix(benteler): 修复员工更新与宜搭回写

malk 1 vecka sedan
förälder
incheckning
7c0d22fe7a

+ 81 - 6
mjava-benteler/src/main/java/com/malk/benteler/service/BentelerYidaSyncService.java

@@ -27,6 +27,8 @@ import java.util.Map;
 @Service
 public class BentelerYidaSyncService {
 
+    private static final String YIDA_VERSION_CONFLICT_MESSAGE = "实例数据已修改";
+
     private final YDClient_Form ydClientForm;
     private final YDService ydService;
     private final YDConf ydConf;
@@ -129,6 +131,22 @@ public class BentelerYidaSyncService {
     private EiamFormSyncResult writeback(String formInstanceId, String tableFieldId,
                                          List<Map<String, Object>> rows,
                                          EiamBatchResult batchResult) {
+        Map<String, Object> update = buildWritebackUpdate(tableFieldId, rows, batchResult);
+        try {
+            updateForm(formInstanceId, update);
+            return EiamFormSyncResult.of(formInstanceId, batchResult, true, "回写成功");
+        } catch (RuntimeException ex) {
+            if (isVersionConflict(ex)) {
+                return retryWriteback(formInstanceId, tableFieldId, rows, batchResult);
+            }
+            log.error("宜搭实例执行结果回写失败, formInstanceId={}", formInstanceId, ex);
+            return writebackFailure(formInstanceId, batchResult, ex);
+        }
+    }
+
+    private Map<String, Object> buildWritebackUpdate(String tableFieldId,
+                                                     List<Map<String, Object>> rows,
+                                                     EiamBatchResult batchResult) {
         Map<String, Object> update = new HashMap<>();
         update.put(tableFieldId, mapper.rebuildRows(tableFieldId, rows,
                 batchResult.getItems()));
@@ -137,17 +155,74 @@ public class BentelerYidaSyncService {
         update.put(conf.getTotalFieldId(), batchResult.getTotal());
         update.put(conf.getSuccessFieldId(), batchResult.getSuccessCount());
         update.put(conf.getFailedFieldId(), batchResult.getFailedCount());
+        return update;
+    }
+
+    private void updateForm(String formInstanceId, Map<String, Object> update) {
         Map<String, Object> bodyExt = new HashMap<>();
         bodyExt.put("useLatestVersion", true);
         bodyExt.put("ignoreEmpty", false);
+        ydClientForm.updateForm(auth(), formInstanceId, JSON.toJSONString(update), bodyExt);
+    }
+
+    private EiamFormSyncResult retryWriteback(String formInstanceId, String tableFieldId,
+                                              List<Map<String, Object>> originalRows,
+                                              EiamBatchResult batchResult) {
+        log.warn("宜搭实例版本冲突,重新读取后重试回写, formInstanceId={}", formInstanceId);
         try {
-            ydClientForm.updateForm(auth(), formInstanceId, JSON.toJSONString(update), bodyExt);
-            return EiamFormSyncResult.of(formInstanceId, batchResult, true, "回写成功");
-        } catch (RuntimeException ex) {
-            log.error("宜搭实例执行结果回写失败, formInstanceId={}", formInstanceId, ex);
-            return EiamFormSyncResult.of(formInstanceId, batchResult, false,
-                    StringUtils.defaultIfBlank(ex.getMessage(), "宜搭回写失败"));
+            Map<String, Object> latestInstance = ydClientForm.getForm(auth(), formInstanceId, null);
+            Map<String, Object> latestFormData = formData(latestInstance);
+            List<Map<String, Object>> latestRows = resolveDetailRows(formInstanceId, tableFieldId,
+                    latestFormData.get(tableFieldId));
+            if (!samePeople(originalRows, latestRows, batchResult.getOperation())) {
+                throw new McException("YIDA_FORM_ROWS_CHANGED",
+                        "宜搭人员子表已变化,停止自动重试回写");
+            }
+            updateForm(formInstanceId, buildWritebackUpdate(
+                    tableFieldId, latestRows, batchResult));
+            return EiamFormSyncResult.of(formInstanceId, batchResult, true, "冲突重试回写成功");
+        } catch (RuntimeException retryEx) {
+            log.error("宜搭实例冲突重试回写失败, formInstanceId={}", formInstanceId, retryEx);
+            return writebackFailure(formInstanceId, batchResult, retryEx);
+        }
+    }
+
+    private boolean samePeople(List<Map<String, Object>> originalRows,
+                               List<Map<String, Object>> latestRows,
+                               EiamBatchOperation operation) {
+        if (originalRows.size() != latestRows.size()) {
+            return false;
+        }
+        String identityFieldId = identityFieldId(operation);
+        for (int index = 0; index < originalRows.size(); index++) {
+            if (!StringUtils.equals(
+                    firstText(originalRows.get(index).get(identityFieldId)),
+                    firstText(latestRows.get(index).get(identityFieldId)))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private String identityFieldId(EiamBatchOperation operation) {
+        if (operation == EiamBatchOperation.CREATE) {
+            return conf.getOnboardingPhoneFieldId();
+        }
+        if (operation == EiamBatchOperation.UPDATE) {
+            return conf.getUpdateEmployeeFieldId() + "_id";
         }
+        return conf.getOffboardingEmployeeFieldId() + "_id";
+    }
+
+    private boolean isVersionConflict(RuntimeException ex) {
+        return StringUtils.contains(ex.getMessage(), YIDA_VERSION_CONFLICT_MESSAGE);
+    }
+
+    private EiamFormSyncResult writebackFailure(String formInstanceId,
+                                                EiamBatchResult batchResult,
+                                                RuntimeException ex) {
+        return EiamFormSyncResult.of(formInstanceId, batchResult, false,
+                StringUtils.defaultIfBlank(ex.getMessage(), "宜搭回写失败"));
     }
 
     @SuppressWarnings("unchecked")

+ 27 - 0
mjava-benteler/src/test/java/com/malk/benteler/service/BentelerYidaSyncServiceTest.java

@@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -111,6 +112,32 @@ public class BentelerYidaSyncServiceTest {
         assertFalse(result.isWritebackSuccess());
         assertEquals(1, result.getSuccessCount());
         verify(eiamLocalService).batchCreate(any());
+        verify(ydClientForm, times(1)).updateForm(any(YDAuth.class), anyString(),
+                anyString(), anyMap());
+    }
+
+    @Test
+    public void syncCreate_versionConflictRetriesWritebackWithoutRetryingEiam() {
+        Map<String, Object> formData = new HashMap<>();
+        formData.put(conf.getOnboardingOrganizationalUnitIdFieldId(), "ou_technical");
+        formData.put(conf.getOnboardingTableFieldId(), Collections.singletonList(
+                createRow("人员一", "13800000001")));
+        when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull()))
+                .thenReturn(instance(formData), instance(formData));
+        when(eiamLocalService.batchCreate(any())).thenReturn(batchResult(
+                EiamBatchOperation.CREATE, true));
+        when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap()))
+                .thenThrow(new McException("YIDA_ERROR",
+                        "宜搭服务内部异常信息:实例数据已修改,请刷新当前页面"))
+                .thenReturn(Collections.emptyMap());
+
+        EiamFormSyncResult result = service.syncCreate("form_version_conflict");
+
+        assertTrue(result.isWritebackSuccess());
+        assertEquals("冲突重试回写成功", result.getWritebackMessage());
+        verify(eiamLocalService, times(1)).batchCreate(any());
+        verify(ydClientForm, times(2)).updateForm(any(YDAuth.class), anyString(),
+                anyString(), anyMap());
     }
 
     @Test

+ 5 - 1
mjava/pom.xml

@@ -20,7 +20,11 @@
     </properties>
 
     <dependencies>
-
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.14</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 53 - 1
mjava/src/main/java/com/malk/utils/UtilHttp.java

@@ -12,8 +12,18 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.malk.server.common.VenR;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPatch;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 
@@ -194,7 +204,49 @@ public abstract class UtilHttp {
     /*** ------------ 创建PATCH请求 ------------ ***/
 
     public static String doPatch(String url, Map header, Map param, Map body) {
-        return doRequest(METHOD.PATCH, url, header, param, body);
+        long startMs = System.currentTimeMillis();
+        String vendor = UtilHttpAudit.vendor(url);
+        String path = HttpUtil.urlWithForm(url, param, CharsetUtil.CHARSET_UTF_8, true);
+        String endpoint = UtilHttpAudit.endpoint(path);
+        log.debug("请求入参, url = {}, header = {}, param = {}, body = {}",
+                url,
+                UtilHttpAudit.sanitize(header),
+                UtilHttpAudit.sanitize(param),
+                UtilHttpAudit.sanitize(body));
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+            HttpPatch request = new HttpPatch(path);
+            addPatchHeaders(request, header);
+            if (ObjectUtil.isNotNull(body)) {
+                String json = JSON.toJSONString(body, SerializerFeature.WriteMapNullValue);
+                request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
+            }
+            try (CloseableHttpResponse response = client.execute(request)) {
+                HttpEntity entity = response.getEntity();
+                String responseBody = entity == null
+                        ? "" : EntityUtils.toString(entity, StandardCharsets.UTF_8);
+                log.debug("请求响应, {}, {}", response.getStatusLine().getStatusCode(), responseBody);
+                UtilHttpAudit.logSuccess(vendor, METHOD.PATCH.name(), endpoint,
+                        System.currentTimeMillis() - startMs, responseBody.length());
+                return responseBody;
+            }
+        } catch (IOException ex) {
+            UtilHttpAudit.logError(vendor, METHOD.PATCH.name(), endpoint,
+                    System.currentTimeMillis() - startMs, ex.getMessage());
+            throw new cn.hutool.http.HttpException(ex);
+        }
+    }
+
+    private static void addPatchHeaders(HttpPatch request, Map header) {
+        if (header == null) {
+            return;
+        }
+        for (Object item : header.entrySet()) {
+            Map.Entry entry = (Map.Entry) item;
+            if (entry.getKey() != null && entry.getValue() != null) {
+                request.addHeader(String.valueOf(entry.getKey()),
+                        String.valueOf(entry.getValue()));
+            }
+        }
     }
 
     public static VenR doPatch(String url, Map header, Map param, Map body, Class rClass) {