|
|
@@ -0,0 +1,288 @@
|
|
|
+package com.malk.taisen.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.malk.server.aliwork.YDConf;
|
|
|
+import com.malk.server.aliwork.YDParam;
|
|
|
+import com.malk.server.dingtalk.DDR_New;
|
|
|
+import com.malk.service.aliwork.YDClient;
|
|
|
+import com.malk.service.dingtalk.DDClient;
|
|
|
+import com.malk.service.dingtalk.DDClient_Notice;
|
|
|
+import com.malk.service.dingtalk.DDClient_NoticeResult;
|
|
|
+import com.malk.taisen.dto.ArNotifyRecord;
|
|
|
+import com.malk.taisen.service.ArNotifyService;
|
|
|
+import com.malk.utils.UtilList;
|
|
|
+import com.malk.utils.UtilMap;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.text.ParseException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class ArNotifyServiceImpl implements ArNotifyService {
|
|
|
+
|
|
|
+ private static final String APP_TYPE = "APP_N9NPHVTQLPBPO8MR6WFG";
|
|
|
+ private static final String FORM_UUID = "FORM-A7AA1BE41C354D5C879925006D3A3F06K6ZY";
|
|
|
+ private static final int MAX_ATTEMPTS = 6;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private YDClient ydClient;
|
|
|
+ @Autowired
|
|
|
+ private DDClient ddClient;
|
|
|
+ @Autowired
|
|
|
+ private DDClient_Notice ddClientNotice;
|
|
|
+ @Autowired
|
|
|
+ private DDClient_NoticeResult ddClientNoticeResult;
|
|
|
+
|
|
|
+ @Value("${ar.notify.user-ids:}")
|
|
|
+ private String notifyUserIds;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Persist an AR batch and send one DingTalk work notification.
|
|
|
+ *
|
|
|
+ * @param records AR automatic posting records
|
|
|
+ * @return true only after DingTalk confirms delivery and Yida is updated
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean notify(List<ArNotifyRecord> records) {
|
|
|
+ try {
|
|
|
+ for (ArNotifyRecord record : records) {
|
|
|
+ validate(record);
|
|
|
+ }
|
|
|
+ final String instanceId = createRecord(records);
|
|
|
+ if (instanceId == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ List<String> recipients = recipients();
|
|
|
+ if (UtilList.isEmpty(recipients)) {
|
|
|
+ markFailure(instanceId, 0, "未配置AR_NOTIFY_USER_IDS");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return sendAndConfirm(records, instanceId, recipients);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("AR自动入账通知执行失败", e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String createRecord(List<ArNotifyRecord> records) {
|
|
|
+ Map formData = UtilMap.map("tableField_3ycv10yjn", detailRows(records));
|
|
|
+ formData.put("selectField_89a55tbg2", "处理中");
|
|
|
+ formData.put("numberField_89a56h485", 0);
|
|
|
+ Object result = retry("创建宜搭实例", new Action() {
|
|
|
+ @Override
|
|
|
+ public Object execute() {
|
|
|
+ return ydClient.createData(YDParam.builder().appType(APP_TYPE).formUuid(FORM_UUID)
|
|
|
+ .formDataJson(JSON.toJSONString(formData)).build());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return instanceId(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean sendAndConfirm(List<ArNotifyRecord> records, String instanceId, List<String> recipients) {
|
|
|
+ String lastError = "";
|
|
|
+ String taskId = null;
|
|
|
+ for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
|
+ try {
|
|
|
+ if (StringUtils.isBlank(taskId)) {
|
|
|
+ taskId = ddClientNotice.sendNotification(ddClient.getAccessToken(), recipients, null, false,
|
|
|
+ notificationMessage(records, instanceId));
|
|
|
+ if (StringUtils.isBlank(taskId)) {
|
|
|
+ throw new IllegalStateException("钉钉未返回工作通知任务编号");
|
|
|
+ }
|
|
|
+ update(instanceId, UtilMap.map("textField_89a545m8z, numberField_89a56h485",
|
|
|
+ taskId, attempt - 1));
|
|
|
+ }
|
|
|
+ Map result = ddClientNoticeResult.getSendResult(ddClient.getAccessToken(), taskId);
|
|
|
+ if (!isSent(result)) {
|
|
|
+ throw new IllegalStateException("钉钉工作通知尚未发送成功: " + JSON.toJSONString(result));
|
|
|
+ }
|
|
|
+ Date now = new Date();
|
|
|
+ update(instanceId, UtilMap.map(
|
|
|
+ "textField_89a41cak4, dateField_89a42ww2f, textField_89a53tgha, selectField_89a55tbg2, numberField_89a56h485, textareaField_89a575hku",
|
|
|
+ "X", now.getTime(), new SimpleDateFormat("HHmmss").format(now), "成功", attempt - 1, "钉钉工作通知发送成功"));
|
|
|
+ return true;
|
|
|
+ } catch (Exception e) {
|
|
|
+ lastError = e.getMessage();
|
|
|
+ log.warn("AR自动入账通知第{}次尝试失败, instanceId={}, taskId={}, error={}",
|
|
|
+ attempt, instanceId, taskId, lastError);
|
|
|
+ if (attempt < MAX_ATTEMPTS) {
|
|
|
+ sleep(attempt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.error("AR自动入账通知重试耗尽, instanceId={}, error={}", instanceId, lastError);
|
|
|
+ markFailure(instanceId, MAX_ATTEMPTS - 1, lastError);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void markFailure(final String instanceId, final int retryCount, final String message) {
|
|
|
+ retry("回写宜搭失败状态", new Action() {
|
|
|
+ @Override
|
|
|
+ public Object execute() {
|
|
|
+ update(instanceId, UtilMap.map("selectField_89a55tbg2, numberField_89a56h485, textareaField_89a575hku",
|
|
|
+ "失败", retryCount, StringUtils.abbreviate(message, 1900)));
|
|
|
+ return Boolean.TRUE;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void update(String instanceId, Map formData) {
|
|
|
+ ydClient.operateData(YDParam.builder().appType(APP_TYPE).formInstanceId(instanceId)
|
|
|
+ .updateFormDataJson(JSON.toJSONString(formData)).build(), YDConf.FORM_OPERATION.update);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object retry(String actionName, Action action) {
|
|
|
+ for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
|
+ try {
|
|
|
+ return action.execute();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("{}第{}次尝试失败: {}", actionName, attempt, e.getMessage());
|
|
|
+ if (attempt < MAX_ATTEMPTS) {
|
|
|
+ sleep(attempt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.error("{}重试耗尽", actionName);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void sleep(int retryNumber) {
|
|
|
+ try {
|
|
|
+ Thread.sleep((1L << (retryNumber - 1)) * 1000L);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new IllegalStateException("重试等待被中断", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map notificationMessage(List<ArNotifyRecord> records, String instanceId) {
|
|
|
+ String detailUrl = "https://www.aliwork.com/" + APP_TYPE + "/formDetail/" + FORM_UUID
|
|
|
+ + "?formInstId=" + instanceId + "&corpid=dinge61fe69900ea236b35c2f4657eb6378f";
|
|
|
+ ArNotifyRecord first = records.get(0);
|
|
|
+ String text = "本次自动入账记录数:" + records.size() + "\n公司代码:" + first.getBUKRS()
|
|
|
+ + "\n首条客户:" + first.getKUNNR() + " " + StringUtils.defaultString(first.getZNAME())
|
|
|
+ + "\n首条凭证:" + first.getBELNR() + "\n请点击查看明细。";
|
|
|
+ Map<String, Object> link = new HashMap<>();
|
|
|
+ link.put("picUrl", "@lALOACZwe2Rk");
|
|
|
+ link.put("title", "AR自动入账通知");
|
|
|
+ link.put("text", text);
|
|
|
+ link.put("messageUrl", detailUrl);
|
|
|
+ Map<String, Object> message = new HashMap<>();
|
|
|
+ message.put("msgtype", "link");
|
|
|
+ message.put("link", link);
|
|
|
+ return message;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isSent(Map result) {
|
|
|
+ if (result == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !hasFailure(result, "invalid_user_id_list")
|
|
|
+ && !hasFailure(result, "forbidden_list")
|
|
|
+ && !hasFailure(result, "failed_user_id_list")
|
|
|
+ && !hasFailure(result, "invalid_dept_id_list")
|
|
|
+ && !hasFailure(result, "forbidden_dept_id_list")
|
|
|
+ && !hasFailure(result, "failed_dept_id_list");
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasFailure(Map result, String key) {
|
|
|
+ Object value = result.get(key);
|
|
|
+ if (value == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (value instanceof Collection) {
|
|
|
+ return !((Collection) value).isEmpty();
|
|
|
+ }
|
|
|
+ if (value.getClass().isArray()) {
|
|
|
+ return java.lang.reflect.Array.getLength(value) > 0;
|
|
|
+ }
|
|
|
+ return StringUtils.isNotBlank(String.valueOf(value));
|
|
|
+ }
|
|
|
+
|
|
|
+ private String instanceId(Object result) {
|
|
|
+ if (result instanceof DDR_New) {
|
|
|
+ DDR_New response = (DDR_New) result;
|
|
|
+ if (StringUtils.isNotBlank(response.getFormInstId())) {
|
|
|
+ return response.getFormInstId();
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(response.getInstanceId())) {
|
|
|
+ return response.getInstanceId();
|
|
|
+ }
|
|
|
+ result = response.getResult();
|
|
|
+ }
|
|
|
+ if (result instanceof String && StringUtils.isNotBlank((String) result)) {
|
|
|
+ return (String) result;
|
|
|
+ }
|
|
|
+ if (!(result instanceof Map)) {
|
|
|
+ log.error("宜搭创建响应未包含实例ID, response={}", JSON.toJSONString(result));
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ Map map = (Map) result;
|
|
|
+ Object id = map.get("formInstId");
|
|
|
+ if (id == null) {
|
|
|
+ id = map.get("formInstanceId");
|
|
|
+ }
|
|
|
+ if (id == null) {
|
|
|
+ id = map.get("instanceId");
|
|
|
+ }
|
|
|
+ return id == null ? null : String.valueOf(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<String> recipients() {
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+ String[] userIds = StringUtils.split(StringUtils.defaultString(notifyUserIds), ',');
|
|
|
+ if (userIds == null) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ for (String userId : userIds) {
|
|
|
+ if (StringUtils.isNotBlank(userId)) {
|
|
|
+ result.add(userId.trim());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map> detailRows(List<ArNotifyRecord> records) {
|
|
|
+ List<Map> rows = new ArrayList<>();
|
|
|
+ for (ArNotifyRecord record : records) {
|
|
|
+ rows.add(UtilMap.map(
|
|
|
+ "textField_3ycw2w508, textField_3ycw3wu79, textField_3ycw4j3ot, textField_3ycw539ws, textField_3ycw6yohj, dateField_3ycw7ql0n, dateField_3ycw8qqry, dateField_3ycw9wnsf, numberField_3ycwa25z7, textField_3ycwbocwu, textField_3ycwcntzu, dateField_3ycwdnk73, textField_3ycwes8k4, textField_3ycwfk3zf, textField_3ycwgmpib, textField_3ycwhz5dp",
|
|
|
+ record.getBUKRS(), record.getKUNNR(), record.getZNAME(), record.getBELNR(), record.getGJAHR(),
|
|
|
+ dateMillis(record.getBUDAT()), dateMillis(record.getBLDAT()), dateMillis(record.getCPUDT()), record.getWRBTR(),
|
|
|
+ record.getWAERS(), "", null, "", "", "", ""));
|
|
|
+ }
|
|
|
+ return rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long dateMillis(String date) {
|
|
|
+ try {
|
|
|
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
|
|
|
+ formatter.setLenient(false);
|
|
|
+ return formatter.parse(date).getTime();
|
|
|
+ } catch (ParseException e) {
|
|
|
+ throw new IllegalArgumentException("日期必须为yyyyMMdd格式: " + date, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validate(ArNotifyRecord record) {
|
|
|
+ if (record == null || StringUtils.isAnyBlank(record.getBUKRS(), record.getKUNNR(), record.getBELNR(),
|
|
|
+ record.getGJAHR(), record.getBUDAT(), record.getBLDAT(), record.getCPUDT()) || record.getWRBTR() == null) {
|
|
|
+ throw new IllegalArgumentException("BUKRS、KUNNR、BELNR、GJAHR、BUDAT、BLDAT、CPUDT、WRBTR不能为空");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private interface Action {
|
|
|
+ Object execute();
|
|
|
+ }
|
|
|
+}
|