DingTalkServiceImpl.java 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. package com.malk.lanyun.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.malk.lanyun.service.DingTalkService;
  4. import com.malk.server.dingtalk.DDConf;
  5. import com.malk.server.dingtalk.DDR;
  6. import com.malk.service.dingtalk.DDClient;
  7. import com.malk.service.dingtalk.DDClient_Personnel;
  8. import com.malk.service.dingtalk.DDClient_Workflow;
  9. import com.malk.utils.UtilMap;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.springframework.stereotype.Service;
  12. import java.util.ArrayList;
  13. import java.util.Collections;
  14. import java.util.HashSet;
  15. import java.util.LinkedHashMap;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.Set;
  19. @Service
  20. @Slf4j
  21. public class DingTalkServiceImpl implements DingTalkService {
  22. private final DDClient ddClient;
  23. private final DDClient_Workflow ddClientWorkflow;
  24. private final DDClient_Personnel ddClientPersonnel;
  25. private final DDConf ddConf;
  26. public DingTalkServiceImpl(DDClient ddClient, DDClient_Workflow ddClientWorkflow,
  27. DDClient_Personnel ddClientPersonnel, DDConf ddConf) {
  28. this.ddClient = ddClient;
  29. this.ddClientWorkflow = ddClientWorkflow;
  30. this.ddClientPersonnel = ddClientPersonnel;
  31. this.ddConf = ddConf;
  32. }
  33. @Override
  34. public Map getAllUserIdInfo(String access_token, String status_list, Number offset, Number size) {
  35. Map param = UtilMap.map("access_token", access_token);
  36. Map body = UtilMap.map("status_list, offset, size", status_list, offset, size);
  37. return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob", null, param, body).getResult();
  38. }
  39. @Override
  40. public List<Map> getDDUserInfoById(String access_token, String userId, Number appAgentId) {
  41. Map param = UtilMap.map("access_token", access_token);
  42. Map body = UtilMap.map("userid_list, agentid", userId, appAgentId);
  43. return (List<Map>) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list", null, param, body).getResult();
  44. }
  45. /**
  46. * 审批通过后: 将审批单字段按名称回写到发起人的人员花名册
  47. *
  48. * @param processInstanceId 审批实例ID
  49. * @return 成功写入花名册的字段数量
  50. */
  51. @Override
  52. public int syncApprovalFormToRoster(String processInstanceId) {
  53. if (isBlank(processInstanceId)) {
  54. throw new IllegalArgumentException("processInstanceId不能为空");
  55. }
  56. // 审批应用凭证: 用于查询审批实例
  57. String approvalAccessToken = this.ddClient.getAccessToken(this.ddConf.getApprovalAppKey(), this.ddConf.getApprovalAppSecret());
  58. Map processData = this.ddClientWorkflow.getProcessInstanceId(approvalAccessToken, processInstanceId);
  59. if (processData == null || processData.isEmpty()) {
  60. throw new IllegalStateException("未查询到审批实例, processInstanceId=" + processInstanceId);
  61. }
  62. String userId = stringValue(processData.get("originatorUserId"));
  63. if (isBlank(userId)) {
  64. throw new IllegalStateException("审批实例缺少发起人, processInstanceId=" + processInstanceId);
  65. }
  66. // 花名册应用凭证: 用于读写智能人事花名册
  67. String personnelAccessToken = this.ddClient.getAccessToken(this.ddConf.getAppKey(), this.ddConf.getAppSecret());
  68. List<Map> formComponentValues = asMapList(processData.get("formComponentValues"));
  69. List<Map> metadataGroups = this.ddClientPersonnel.getPersonnelMeta(personnelAccessToken, this.ddConf.getAgentId());
  70. // 花名册字段: 按字段名称建索引, 名称重复或为派生字段时忽略
  71. Map<String, FieldMeta> metadataByName = indexMetadata(metadataGroups);
  72. Map<String, LinkedHashMap<String, String>> fieldsByGroup = new LinkedHashMap<>();
  73. int matchedCount = 0;
  74. for (Map component : formComponentValues) {
  75. String name = stringValue(component.get("name"));
  76. String value = fieldValue(component.get("value"));
  77. if (isBlank(name) || value == null) {
  78. continue;
  79. }
  80. FieldMeta metadata = metadataByName.get(name.trim());
  81. if (metadata == null) {
  82. continue;
  83. }
  84. LinkedHashMap<String, String> groupFields = fieldsByGroup.computeIfAbsent(metadata.groupId, key -> new LinkedHashMap<>());
  85. if (!groupFields.containsKey(metadata.fieldCode)) {
  86. matchedCount++;
  87. }
  88. groupFields.put(metadata.fieldCode, value);
  89. }
  90. if (matchedCount == 0) {
  91. log.info("审批单无匹配的花名册字段, processInstanceId={}", processInstanceId);
  92. return 0;
  93. }
  94. List<Map> groups = buildUpdateGroups(fieldsByGroup);
  95. boolean updated = this.ddClientPersonnel.updateEmployeeRoster(personnelAccessToken, userId, this.ddConf.getAgentId(), groups);
  96. if (!updated) {
  97. throw new IllegalStateException("花名册更新失败, processInstanceId=" + processInstanceId);
  98. }
  99. return matchedCount;
  100. }
  101. /**
  102. * 花名册字段索引: 字段名称 -> 分组ID + 字段编码
  103. */
  104. private Map<String, FieldMeta> indexMetadata(List<Map> metadataGroups) {
  105. Map<String, FieldMeta> metadataByName = new LinkedHashMap<>();
  106. Set<String> ambiguousNames = new HashSet<>();
  107. for (Map group : safeList(metadataGroups)) {
  108. String groupId = stringValue(group.get("group_id"));
  109. if (isBlank(groupId)) {
  110. continue;
  111. }
  112. for (Map field : metadataFields(group)) {
  113. String fieldName = stringValue(field.get("field_name"));
  114. String fieldCode = stringValue(field.get("field_code"));
  115. if (isBlank(fieldName) || isBlank(fieldCode) || isDerived(field.get("derived"))) {
  116. continue;
  117. }
  118. String normalizedName = fieldName.trim();
  119. FieldMeta existing = metadataByName.get(normalizedName);
  120. if (existing != null && (!existing.groupId.equals(groupId) || !existing.fieldCode.equals(fieldCode))) {
  121. ambiguousNames.add(normalizedName);
  122. continue;
  123. }
  124. if (existing == null) {
  125. metadataByName.put(normalizedName, new FieldMeta(groupId, fieldCode));
  126. }
  127. }
  128. }
  129. for (String ambiguousName : ambiguousNames) {
  130. metadataByName.remove(ambiguousName);
  131. log.warn("花名册字段名称存在歧义, 已忽略: {}", ambiguousName);
  132. }
  133. return metadataByName;
  134. }
  135. private List<Map> metadataFields(Map group) {
  136. Object detail = group.get("detail");
  137. if (detail instanceof Map) {
  138. return asMapList(((Map) detail).get("field_meta_info_list"));
  139. }
  140. if (detail instanceof List) {
  141. return asMapList(detail);
  142. }
  143. return asMapList(group.get("field_meta_info_list"));
  144. }
  145. /**
  146. * 组装花名册更新入参: groups[].sections[].section[].{field_code, value}
  147. */
  148. private List<Map> buildUpdateGroups(Map<String, LinkedHashMap<String, String>> fieldsByGroup) {
  149. List<Map> groups = new ArrayList<>();
  150. for (Map.Entry<String, LinkedHashMap<String, String>> groupEntry : fieldsByGroup.entrySet()) {
  151. List<Map> fields = new ArrayList<>();
  152. for (Map.Entry<String, String> fieldEntry : groupEntry.getValue().entrySet()) {
  153. fields.add(UtilMap.map("field_code, value", fieldEntry.getKey(), fieldEntry.getValue()));
  154. }
  155. Map section = UtilMap.map("section", fields);
  156. groups.add(UtilMap.map("group_id, sections", groupEntry.getKey(), Collections.singletonList(section)));
  157. }
  158. return groups;
  159. }
  160. private String fieldValue(Object value) {
  161. if (value == null) {
  162. return null;
  163. }
  164. if (value instanceof String) {
  165. String string = (String) value;
  166. return isBlank(string) ? null : string;
  167. }
  168. return JSON.toJSONString(value);
  169. }
  170. private boolean isDerived(Object value) {
  171. return (Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value)));
  172. }
  173. private List<Map> asMapList(Object value) {
  174. if (!(value instanceof List)) {
  175. return Collections.emptyList();
  176. }
  177. List<Map> result = new ArrayList<>();
  178. for (Object item : (List) value) {
  179. if (item instanceof Map) {
  180. result.add((Map) item);
  181. }
  182. }
  183. return result;
  184. }
  185. private List<Map> safeList(List<Map> value) {
  186. return (value == null) ? Collections.<Map>emptyList() : value;
  187. }
  188. private String stringValue(Object value) {
  189. return (value == null) ? null : String.valueOf(value);
  190. }
  191. private boolean isBlank(String value) {
  192. return (value == null || value.trim().isEmpty());
  193. }
  194. /**
  195. * 花名册字段元信息
  196. */
  197. private static final class FieldMeta {
  198. private final String groupId;
  199. private final String fieldCode;
  200. private FieldMeta(String groupId, String fieldCode) {
  201. this.groupId = groupId;
  202. this.fieldCode = fieldCode;
  203. }
  204. }
  205. }