|
|
@@ -0,0 +1,357 @@
|
|
|
+package com.malk.minglei.service.impl;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.malk.minglei.config.YidaApplicationsProperties;
|
|
|
+import com.malk.minglei.dto.MingleiDocumentQueryRequest;
|
|
|
+import com.malk.minglei.service.MingleiDocumentService;
|
|
|
+import com.malk.server.aliwork.YDConf;
|
|
|
+import com.malk.server.aliwork.YDParam;
|
|
|
+import com.malk.server.dingtalk.DDR_New;
|
|
|
+import com.malk.server.dingtalk.DDConf;
|
|
|
+import com.malk.service.aliwork.YDClient;
|
|
|
+import com.malk.service.dingtalk.DDClient;
|
|
|
+import com.malk.service.dingtalk.DDClient_Contacts;
|
|
|
+import org.springframework.beans.factory.annotation.Qualifier;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class MingleiDocumentServiceImpl implements MingleiDocumentService {
|
|
|
+
|
|
|
+ private static final String MINGLEI_DOCUMENT_FORM_UUID = "FORM-8F527F696C5B4AF7AB7A53979527FBA23USO";
|
|
|
+ private static final String DEPARTMENT_ID_FIELD_ID = "textField_msztwhs2";
|
|
|
+ private static final String DEPARTMENT_NAME_FIELD_ID = "textField_mszr6tuz";
|
|
|
+ private static final String FIRST_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrl8";
|
|
|
+ private static final String SECOND_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrl9";
|
|
|
+ private static final String THIRD_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrla";
|
|
|
+ private static final String FOURTH_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrlb";
|
|
|
+ private static final String DEPARTMENT_LEADER_FIELD_ID = "employeeField_mszdqrl3";
|
|
|
+ private static final String[] DEPARTMENT_MANAGER_FIELD_IDS = {
|
|
|
+ FIRST_LEVEL_DEPARTMENT_MANAGER_FIELD_ID,
|
|
|
+ SECOND_LEVEL_DEPARTMENT_MANAGER_FIELD_ID,
|
|
|
+ THIRD_LEVEL_DEPARTMENT_MANAGER_FIELD_ID,
|
|
|
+ FOURTH_LEVEL_DEPARTMENT_MANAGER_FIELD_ID,
|
|
|
+ DEPARTMENT_LEADER_FIELD_ID
|
|
|
+ };
|
|
|
+ private static final int PRECISE_QUERY_PAGE_SIZE = 100;
|
|
|
+
|
|
|
+ private final YDClient yidaClient;
|
|
|
+ private final DDClient dingTalkClient;
|
|
|
+ private final DDClient_Contacts dingTalkContactsClient;
|
|
|
+ private final DDConf dingTalkConfiguration;
|
|
|
+ private final YidaApplicationsProperties.ApplicationCredentials masterDataConfiguration;
|
|
|
+ private final ObjectMapper objectMapper;
|
|
|
+
|
|
|
+ public MingleiDocumentServiceImpl(@Qualifier("masterDataYidaClient") YDClient yidaClient,
|
|
|
+ DDClient dingTalkClient,
|
|
|
+ DDClient_Contacts dingTalkContactsClient,
|
|
|
+ DDConf dingTalkConfiguration,
|
|
|
+ YidaApplicationsProperties applications,
|
|
|
+ ObjectMapper objectMapper) {
|
|
|
+ this.yidaClient = yidaClient;
|
|
|
+ this.dingTalkClient = dingTalkClient;
|
|
|
+ this.dingTalkContactsClient = dingTalkContactsClient;
|
|
|
+ this.dingTalkConfiguration = dingTalkConfiguration;
|
|
|
+ this.masterDataConfiguration = applications.getMasterData();
|
|
|
+ this.objectMapper = objectMapper;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用明磊表单 UUID 组装查询条件,并通过 malk 的普通表单查询接口返回分页单据。
|
|
|
+ *
|
|
|
+ * @param page 页码,从 1 开始
|
|
|
+ * @param size 每页记录数
|
|
|
+ * @return 包含记录列表、总数和页码的分页查询结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> queryDocuments(int page, int size) {
|
|
|
+ return executeQuery(null, null, page, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据请求参数的优先级生成精准筛选条件:部门编号、用户所属部门、部门名称。
|
|
|
+ *
|
|
|
+ * @param request 包含 user_id、dept_id、dept_name 的查询参数
|
|
|
+ * @return 仅包含各级部门主管和部门分管领导用户 ID 的单层结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> queryDocuments(MingleiDocumentQueryRequest request) {
|
|
|
+ if (request == null) {
|
|
|
+ throw new IllegalArgumentException("Query request must not be null");
|
|
|
+ }
|
|
|
+
|
|
|
+ String departmentId = trimToNull(request.getDeptId());
|
|
|
+ if (departmentId != null) {
|
|
|
+ return extractDepartmentManagers(executeQuery(DEPARTMENT_ID_FIELD_ID, departmentId, 1, PRECISE_QUERY_PAGE_SIZE));
|
|
|
+ }
|
|
|
+
|
|
|
+ String userId = trimToNull(request.getUserId());
|
|
|
+ if (userId != null) {
|
|
|
+ return extractDepartmentManagers(executeQuery(DEPARTMENT_ID_FIELD_ID, resolveDepartmentId(userId), 1, PRECISE_QUERY_PAGE_SIZE));
|
|
|
+ }
|
|
|
+
|
|
|
+ String departmentName = trimToNull(request.getDeptName());
|
|
|
+ if (departmentName != null) {
|
|
|
+ return extractDepartmentManagers(executeQuery(DEPARTMENT_NAME_FIELD_ID, departmentName, 1, PRECISE_QUERY_PAGE_SIZE));
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new IllegalArgumentException("One of dept_id, user_id or dept_name must be provided");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按查询范围读取部门成员 ID,并按成员首次出现顺序去重。
|
|
|
+ *
|
|
|
+ * @param departmentId 钉钉部门 ID
|
|
|
+ * @param type 查询范围,0 表示包含子部门,1 表示仅当前部门
|
|
|
+ * @return 去重后的钉钉 userid 列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<String> listDepartmentUserIds(long departmentId, int type) {
|
|
|
+ String accessToken = dingTalkClient.getAccessToken();
|
|
|
+ require(accessToken, "Unable to obtain DingTalk access token");
|
|
|
+
|
|
|
+ Set<Long> departmentIds = new LinkedHashSet<Long>();
|
|
|
+ departmentIds.add(departmentId);
|
|
|
+
|
|
|
+ // 仅 type 为 0 时查询子部门,type 为 1 时保留当前部门。
|
|
|
+ if (type == 0) {
|
|
|
+ List<Long> childDepartmentIds = dingTalkContactsClient.getDepartmentId_all(accessToken, false, departmentId);
|
|
|
+ if (childDepartmentIds != null) {
|
|
|
+ departmentIds.addAll(childDepartmentIds);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<String> userIds = new LinkedHashSet<String>();
|
|
|
+ for (Long currentDepartmentId : departmentIds) {
|
|
|
+ if (currentDepartmentId == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<String> departmentUserIds = dingTalkContactsClient.listDepartmentUserId(accessToken, currentDepartmentId.longValue());
|
|
|
+ if (departmentUserIds == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (String userId : departmentUserIds) {
|
|
|
+ String normalizedUserId = trimToNull(userId);
|
|
|
+ if (normalizedUserId != null) {
|
|
|
+ userIds.add(normalizedUserId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new ArrayList<String>(userIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> executeQuery(String fieldId, String fieldValue, int page, int size) {
|
|
|
+ validateConfiguration();
|
|
|
+
|
|
|
+ YDParam parameter = new YDParam();
|
|
|
+ parameter.setAppType(masterDataConfiguration.getAppType());
|
|
|
+ parameter.setSystemToken(masterDataConfiguration.getSystemToken());
|
|
|
+ parameter.setFormUuid(MINGLEI_DOCUMENT_FORM_UUID);
|
|
|
+ parameter.setCurrentPage(page);
|
|
|
+ parameter.setPageSize(size);
|
|
|
+ if (fieldId != null) {
|
|
|
+ parameter.setSearchFieldJson(createExactSearchFieldJson(fieldId, fieldValue));
|
|
|
+ }
|
|
|
+
|
|
|
+ DDR_New response = yidaClient.queryData(parameter, YDConf.FORM_QUERY.retrieve_search_form);
|
|
|
+ if (response == null) {
|
|
|
+ throw new IllegalStateException("Yida form query returned an empty response");
|
|
|
+ }
|
|
|
+ response.assertSuccess();
|
|
|
+
|
|
|
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
|
|
|
+ Object records = response.getRecords();
|
|
|
+ if (records == null) {
|
|
|
+ records = response.getResult();
|
|
|
+ }
|
|
|
+ if (records == null) {
|
|
|
+ records = response.getData();
|
|
|
+ }
|
|
|
+ result.put("records", records);
|
|
|
+ result.put("totalCount", response.getTotalCount());
|
|
|
+ result.put("pageNumber", response.getPageNumber());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从精准查询命中的首条单据提取五个主管字段对应的用户 ID,并直接返回到顶层。
|
|
|
+ *
|
|
|
+ * @param queryResult 宜搭列表查询结果
|
|
|
+ * @return 仅包含一级至四级部门主管和部门分管领导的单层结果
|
|
|
+ */
|
|
|
+ private Map<String, Object> extractDepartmentManagers(Map<String, Object> queryResult) {
|
|
|
+ Map formData = firstFormData(queryResult.get("records"));
|
|
|
+ if (formData == null) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
|
|
|
+ for (String fieldId : DEPARTMENT_MANAGER_FIELD_IDS) {
|
|
|
+ result.put(fieldId, firstEmployeeUserId(formData.get(fieldId + "_id")));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map firstFormData(Object records) {
|
|
|
+ if (!(records instanceof Iterable)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (Object record : (Iterable<?>) records) {
|
|
|
+ if (!(record instanceof Map)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ Object formData = ((Map) record).get("formData");
|
|
|
+ return formData instanceof Map ? (Map) formData : null;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object firstValue(Object values) {
|
|
|
+ if (values instanceof Iterable) {
|
|
|
+ for (Object value : (Iterable<?>) values) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return values;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从宜搭成员字段对应的 ID 数组中提取首个钉钉 userid。
|
|
|
+ *
|
|
|
+ * @param employeeIds 宜搭成员字段对应的 ID 数组
|
|
|
+ * @return 钉钉 userid;字段未填写或无法识别时为 null
|
|
|
+ */
|
|
|
+ private String firstEmployeeUserId(Object employeeIds) {
|
|
|
+ Object employee = firstValue(employeeIds);
|
|
|
+ if (employee == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (!(employee instanceof Map)) {
|
|
|
+ return toStringValue(employee);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map employeeInfo = (Map) employee;
|
|
|
+ String userId = toStringValue(employeeInfo.get("userid"));
|
|
|
+ if (userId == null) {
|
|
|
+ userId = toStringValue(employeeInfo.get("userId"));
|
|
|
+ }
|
|
|
+ if (userId == null) {
|
|
|
+ userId = toStringValue(employeeInfo.get("user_id"));
|
|
|
+ }
|
|
|
+ return userId == null ? toStringValue(employeeInfo.get("id")) : userId;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调用钉钉通讯录接口读取员工所属部门,并取返回列表中的首个部门编号作为查询条件。
|
|
|
+ *
|
|
|
+ * @param userId 钉钉用户编号
|
|
|
+ * @return 员工所属的部门编号
|
|
|
+ */
|
|
|
+ private String resolveDepartmentId(String userId) {
|
|
|
+ Map userInfo = dingTalkContactsClient.getUserInfoById(dingTalkClient.getAccessToken(), userId);
|
|
|
+ if (userInfo == null) {
|
|
|
+ throw new IllegalArgumentException("No DingTalk user information was found for user_id");
|
|
|
+ }
|
|
|
+
|
|
|
+ String mainDepartmentId = toStringValue(userInfo.get("main_dept_id"));
|
|
|
+ if (mainDepartmentId != null) {
|
|
|
+ return mainDepartmentId;
|
|
|
+ }
|
|
|
+
|
|
|
+ String departmentId = firstDepartmentId(userInfo.get("department"));
|
|
|
+ if (departmentId == null) {
|
|
|
+ departmentId = firstDepartmentId(userInfo.get("dept_id_list"));
|
|
|
+ }
|
|
|
+ if (departmentId == null) {
|
|
|
+ throw new IllegalArgumentException("No department was found for user_id");
|
|
|
+ }
|
|
|
+ return departmentId;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按宜搭普通文本字段的等值查询格式生成 searchFieldJson。
|
|
|
+ *
|
|
|
+ * @param fieldId 宜搭字段 ID
|
|
|
+ * @param value 需要精确匹配的字段值
|
|
|
+ * @return 宜搭搜索条件 JSON
|
|
|
+ */
|
|
|
+ private String createExactSearchFieldJson(String fieldId, String value) {
|
|
|
+ Map<String, String> condition = new LinkedHashMap<String, String>();
|
|
|
+ condition.put("key", fieldId);
|
|
|
+ condition.put("value", value);
|
|
|
+ condition.put("type", "TEXT");
|
|
|
+ condition.put("operator", "eq");
|
|
|
+ condition.put("componentName", "TextField");
|
|
|
+ try {
|
|
|
+ return objectMapper.writeValueAsString(Collections.singletonList(condition));
|
|
|
+ } catch (JsonProcessingException exception) {
|
|
|
+ throw new IllegalStateException("Unable to create Yida search condition", exception);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String firstDepartmentId(Object departmentIds) {
|
|
|
+ if (departmentIds instanceof Iterable) {
|
|
|
+ for (Object departmentId : (Iterable<?>) departmentIds) {
|
|
|
+ String value = toStringValue(departmentId);
|
|
|
+ if (value != null) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (departmentIds != null && departmentIds.getClass().isArray()) {
|
|
|
+ Object[] values = (Object[]) departmentIds;
|
|
|
+ for (Object departmentId : values) {
|
|
|
+ String value = toStringValue(departmentId);
|
|
|
+ if (value != null) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return toStringValue(departmentIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String toStringValue(Object value) {
|
|
|
+ return value == null ? null : trimToNull(String.valueOf(value));
|
|
|
+ }
|
|
|
+
|
|
|
+ private String trimToNull(String value) {
|
|
|
+ if (value == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String trimmedValue = value.trim();
|
|
|
+ return trimmedValue.isEmpty() ? null : trimmedValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验调用宜搭所需的应用级鉴权配置是否完整。
|
|
|
+ */
|
|
|
+ private void validateConfiguration() {
|
|
|
+ require(dingTalkConfiguration.getAppKey(), "DINGTALK_APP_KEY is not configured");
|
|
|
+ require(dingTalkConfiguration.getAppSecret(), "DINGTALK_APP_SECRET is not configured");
|
|
|
+ require(masterDataConfiguration.getAppType(), "YIDA_MASTER_DATA_APP_TYPE is not configured");
|
|
|
+ require(masterDataConfiguration.getSystemToken(), "YIDA_MASTER_DATA_SYSTEM_TOKEN is not configured");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验单个配置值非空,避免以不完整配置调用远端接口。
|
|
|
+ *
|
|
|
+ * @param value 待校验配置值
|
|
|
+ * @param message 配置缺失时抛出的错误信息
|
|
|
+ */
|
|
|
+ private void require(String value, String message) {
|
|
|
+ if (value == null || value.trim().isEmpty()) {
|
|
|
+ throw new IllegalStateException(message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|