BentelerYidaSyncService.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package com.malk.benteler.service;
  2. import com.alibaba.fastjson.JSON;
  3. import com.malk.benteler.config.BentelerYidaConf;
  4. import com.malk.benteler.dto.EiamBatchOperation;
  5. import com.malk.benteler.dto.EiamBatchResult;
  6. import com.malk.benteler.dto.EiamFormSyncResult;
  7. import com.malk.server.aliwork.YDAuth;
  8. import com.malk.server.aliwork.YDConf;
  9. import com.malk.server.aliwork.YDParam;
  10. import com.malk.server.common.McException;
  11. import com.malk.service.aliwork.YDClient_Form;
  12. import com.malk.service.aliwork.YDService;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.springframework.stereotype.Service;
  16. import java.util.ArrayList;
  17. import java.util.HashMap;
  18. import java.util.List;
  19. import java.util.Map;
  20. /**
  21. * 基于一个宜搭实例 ID 完成查询、批量 EIAM 执行和全量回写。
  22. */
  23. @Slf4j
  24. @Service
  25. public class BentelerYidaSyncService {
  26. private final YDClient_Form ydClientForm;
  27. private final YDService ydService;
  28. private final YDConf ydConf;
  29. private final BentelerYidaConf conf;
  30. private final BentelerYidaFormMapper mapper;
  31. private final EiamLocalService eiamLocalService;
  32. public BentelerYidaSyncService(YDClient_Form ydClientForm, YDService ydService,
  33. YDConf ydConf, BentelerYidaConf conf,
  34. BentelerYidaFormMapper mapper,
  35. EiamLocalService eiamLocalService) {
  36. this.ydClientForm = ydClientForm;
  37. this.ydService = ydService;
  38. this.ydConf = ydConf;
  39. this.conf = conf;
  40. this.mapper = mapper;
  41. this.eiamLocalService = eiamLocalService;
  42. }
  43. /**
  44. * 查询入职实例并批量创建 EIAM 用户。
  45. *
  46. * @param formInstanceId 宜搭实例 ID
  47. * @return 执行及回写结果
  48. */
  49. public EiamFormSyncResult syncCreate(String formInstanceId) {
  50. return sync(formInstanceId, EiamBatchOperation.CREATE,
  51. conf.getOnboardingFormUuid(), conf.getOnboardingTableFieldId());
  52. }
  53. /**
  54. * 查询人员部门更新实例并批量更新 EIAM 用户。
  55. *
  56. * @param formInstanceId 宜搭实例 ID
  57. * @return 执行及回写结果
  58. */
  59. public EiamFormSyncResult syncUpdate(String formInstanceId) {
  60. return sync(formInstanceId, EiamBatchOperation.UPDATE,
  61. conf.getUpdateFormUuid(), conf.getUpdateTableFieldId());
  62. }
  63. /**
  64. * 查询离职实例并批量删除 EIAM 用户。
  65. *
  66. * @param formInstanceId 宜搭实例 ID
  67. * @return 执行及回写结果
  68. */
  69. public EiamFormSyncResult syncDelete(String formInstanceId) {
  70. return sync(formInstanceId, EiamBatchOperation.DELETE,
  71. conf.getOffboardingFormUuid(), conf.getOffboardingTableFieldId());
  72. }
  73. private EiamFormSyncResult sync(String formInstanceId, EiamBatchOperation operation,
  74. String expectedFormUuid, String tableFieldId) {
  75. validateRequest(formInstanceId, tableFieldId);
  76. Map<String, Object> instance = ydClientForm.getForm(auth(), formInstanceId, null);
  77. validateForm(instance, expectedFormUuid);
  78. Map<String, Object> formData = formData(instance);
  79. List<Map<String, Object>> rows = resolveDetailRows(formInstanceId, tableFieldId,
  80. formData.get(tableFieldId));
  81. if (rows.isEmpty()) {
  82. throw new McException("YIDA_FORM_EMPTY", "宜搭人员子表不能为空");
  83. }
  84. EiamBatchResult batchResult = execute(operation, formInstanceId, formData, rows);
  85. return writeback(formInstanceId, tableFieldId, rows, batchResult);
  86. }
  87. private EiamBatchResult execute(EiamBatchOperation operation, String formInstanceId,
  88. Map<String, Object> formData,
  89. List<Map<String, Object>> rows) {
  90. if (operation == EiamBatchOperation.CREATE) {
  91. String organizationalUnitId = text(formData.get(
  92. conf.getOnboardingOrganizationalUnitIdFieldId()));
  93. if (!StringUtils.startsWith(organizationalUnitId, "ou_")) {
  94. throw new McException("EIAM_ORG_INVALID", "iDaaS 部门 ID 无效");
  95. }
  96. return eiamLocalService.batchCreate(mapper.mapCreateItems(
  97. formInstanceId, formData, rows));
  98. }
  99. if (operation == EiamBatchOperation.UPDATE) {
  100. List<String> updateContents = texts(formData.get(conf.getUpdateContentFieldId()));
  101. if (updateContents.isEmpty()) {
  102. throw new McException("EIAM_UPDATE_CONTENT_EMPTY", "更新内容不能为空");
  103. }
  104. String organizationalUnitId = null;
  105. if (requiresOrganizationalUnit(updateContents)) {
  106. organizationalUnitId = firstText(formData.get(
  107. conf.getUpdateOrganizationalUnitIdFieldId()));
  108. if (!StringUtils.startsWith(organizationalUnitId, "ou_")) {
  109. throw new McException("EIAM_ORG_INVALID", "iDaaS 部门 ID 无效");
  110. }
  111. }
  112. return eiamLocalService.batchUpdate(mapper.mapUpdateItems(
  113. formInstanceId, rows, organizationalUnitId, updateContents));
  114. }
  115. return eiamLocalService.batchDelete(mapper.mapDeleteItems(formInstanceId, rows));
  116. }
  117. private EiamFormSyncResult writeback(String formInstanceId, String tableFieldId,
  118. List<Map<String, Object>> rows,
  119. EiamBatchResult batchResult) {
  120. Map<String, Object> update = new HashMap<>();
  121. update.put(tableFieldId, mapper.rebuildRows(tableFieldId, rows,
  122. batchResult.getItems()));
  123. update.put(conf.getFailureMessageFieldId(), mapper.buildFailureMessage(
  124. tableFieldId, rows, batchResult.getItems()));
  125. update.put(conf.getTotalFieldId(), batchResult.getTotal());
  126. update.put(conf.getSuccessFieldId(), batchResult.getSuccessCount());
  127. update.put(conf.getFailedFieldId(), batchResult.getFailedCount());
  128. Map<String, Object> bodyExt = new HashMap<>();
  129. bodyExt.put("useLatestVersion", true);
  130. bodyExt.put("ignoreEmpty", false);
  131. try {
  132. ydClientForm.updateForm(auth(), formInstanceId, JSON.toJSONString(update), bodyExt);
  133. return EiamFormSyncResult.of(formInstanceId, batchResult, true, "回写成功");
  134. } catch (RuntimeException ex) {
  135. log.error("宜搭实例执行结果回写失败, formInstanceId={}", formInstanceId, ex);
  136. return EiamFormSyncResult.of(formInstanceId, batchResult, false,
  137. StringUtils.defaultIfBlank(ex.getMessage(), "宜搭回写失败"));
  138. }
  139. }
  140. @SuppressWarnings("unchecked")
  141. private Map<String, Object> formData(Map<String, Object> instance) {
  142. if (instance == null || instance.get("formData") == null) {
  143. throw new McException("YIDA_FORM_DATA_EMPTY", "宜搭实例数据为空");
  144. }
  145. Object value = instance.get("formData");
  146. if (value instanceof Map) {
  147. return (Map<String, Object>) value;
  148. }
  149. Map<String, Object> parsed = JSON.parseObject(String.valueOf(value), Map.class);
  150. if (parsed == null) {
  151. throw new McException("YIDA_FORM_DATA_INVALID", "宜搭实例 formData 格式错误");
  152. }
  153. return parsed;
  154. }
  155. @SuppressWarnings("unchecked")
  156. private List<Map<String, Object>> resolveDetailRows(String formInstanceId,
  157. String tableFieldId,
  158. Object inlineValue) {
  159. List<Map<String, Object>> inlineRows = inlineValue instanceof List
  160. ? (List<Map<String, Object>>) inlineValue : new ArrayList<>();
  161. // fixme 详情接口最多内联 50 行;只有恰好 50 行时才可能被截断,必须单独分页查询子表。
  162. if (inlineRows.size() == YDConf.PAGE_SIZE_DETAILS) {
  163. try {
  164. List<Map> details = ydService.queryDetails(YDParam.builder()
  165. .appType(ydConf.getAppType())
  166. .systemToken(ydConf.getSystemToken())
  167. .formInstanceId(formInstanceId)
  168. .tableFieldId(tableFieldId)
  169. .pageNumber(1)
  170. .build());
  171. return details == null ? inlineRows : (List<Map<String, Object>>) (List<?>) details;
  172. } catch (NullPointerException ex) {
  173. log.warn("宜搭子表分页查询空数据,退回内联 50 行, formInstanceId={}, tableFieldId={}",
  174. formInstanceId, tableFieldId);
  175. }
  176. }
  177. return inlineRows;
  178. }
  179. private void validateRequest(String formInstanceId, String tableFieldId) {
  180. if (StringUtils.isBlank(formInstanceId)) {
  181. throw new McException("YIDA_FORM_INSTANCE_ID_EMPTY", "formInstanceId 不能为空");
  182. }
  183. if (StringUtils.isBlank(tableFieldId)) {
  184. throw new McException("YIDA_FIELD_CONFIG_EMPTY", "人员子表 fieldId 未配置");
  185. }
  186. }
  187. private void validateForm(Map<String, Object> instance, String expectedFormUuid) {
  188. if (instance == null) {
  189. throw new McException("YIDA_FORM_EMPTY", "宜搭实例查询结果为空");
  190. }
  191. String actualFormUuid = text(instance.get("modelUuid"));
  192. if (StringUtils.isNotBlank(expectedFormUuid) && StringUtils.isNotBlank(actualFormUuid)
  193. && !StringUtils.equals(expectedFormUuid, actualFormUuid)) {
  194. throw new McException("YIDA_FORM_MISMATCH", "宜搭实例与当前同步场景不匹配");
  195. }
  196. }
  197. private YDAuth auth() {
  198. return YDAuth.ofGlobal(ydConf);
  199. }
  200. private String firstText(Object value) {
  201. if (value instanceof Iterable) {
  202. for (Object item : (Iterable<?>) value) {
  203. return text(item);
  204. }
  205. return null;
  206. }
  207. return text(value);
  208. }
  209. private boolean requiresOrganizationalUnit(List<String> updateContents) {
  210. return updateContents.contains(BentelerYidaFormMapper.UPDATE_PRIMARY_ORG)
  211. || updateContents.contains(BentelerYidaFormMapper.ADD_ORG);
  212. }
  213. private List<String> texts(Object value) {
  214. List<String> values = new ArrayList<>();
  215. if (value instanceof Iterable) {
  216. for (Object item : (Iterable<?>) value) {
  217. String itemText = text(item);
  218. if (StringUtils.isNotBlank(itemText)) {
  219. values.add(itemText);
  220. }
  221. }
  222. return values;
  223. }
  224. String itemText = text(value);
  225. if (StringUtils.isNotBlank(itemText)) {
  226. values.add(itemText);
  227. }
  228. return values;
  229. }
  230. private String text(Object value) {
  231. return value == null ? null : StringUtils.trimToNull(String.valueOf(value));
  232. }
  233. }