ArNotifyServiceImpl.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package com.malk.taisen.service.impl;
  2. import cn.hutool.http.HttpUtil;
  3. import com.alibaba.fastjson.JSON;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.malk.server.aliwork.YDConf;
  6. import com.malk.server.aliwork.YDParam;
  7. import com.malk.server.dingtalk.DDR_New;
  8. import com.malk.service.aliwork.YDClient;
  9. import com.malk.service.dingtalk.DDClient;
  10. import com.malk.service.dingtalk.DDClient_Notice;
  11. import com.malk.service.dingtalk.DDClient_NoticeResult;
  12. import com.malk.taisen.dto.ArNotifyRecord;
  13. import com.malk.taisen.service.ArNotifyService;
  14. import com.malk.utils.UtilList;
  15. import com.malk.utils.UtilMap;
  16. import lombok.extern.slf4j.Slf4j;
  17. import org.apache.commons.lang3.StringUtils;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.beans.factory.annotation.Value;
  20. import org.springframework.stereotype.Service;
  21. import java.text.ParseException;
  22. import java.text.SimpleDateFormat;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.Date;
  26. import java.util.HashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. @Slf4j
  30. @Service
  31. public class ArNotifyServiceImpl implements ArNotifyService {
  32. private static final String APP_TYPE = "APP_N9NPHVTQLPBPO8MR6WFG";
  33. private static final String FORM_UUID = "FORM-A7AA1BE41C354D5C879925006D3A3F06K6ZY";
  34. private static final int MAX_ATTEMPTS = 6;
  35. @Autowired
  36. private YDClient ydClient;
  37. @Autowired
  38. private DDClient ddClient;
  39. @Autowired
  40. private DDClient_Notice ddClientNotice;
  41. @Autowired
  42. private DDClient_NoticeResult ddClientNoticeResult;
  43. @Value("${ar.notify.user-ids:}")
  44. private String notifyUserIds;
  45. /**
  46. * Persist an AR batch and send one DingTalk work notification.
  47. *
  48. * @param records AR automatic posting records
  49. * @return true only after DingTalk confirms delivery and Yida is updated
  50. */
  51. @Override
  52. public boolean notify(List<ArNotifyRecord> records) {
  53. try {
  54. for (ArNotifyRecord record : records) {
  55. validate(record);
  56. }
  57. final String instanceId = createRecord(records);
  58. if (instanceId == null) {
  59. return false;
  60. }
  61. List<String> recipients = recipients();
  62. if (UtilList.isEmpty(recipients)) {
  63. markFailure(instanceId, 0, "未配置AR_NOTIFY_USER_IDS");
  64. return false;
  65. }
  66. return sendAndConfirm(records, instanceId, recipients);
  67. } catch (Exception e) {
  68. log.error("AR自动入账通知执行失败", e);
  69. return false;
  70. }
  71. }
  72. private String createRecord(List<ArNotifyRecord> records) {
  73. Map formData = UtilMap.map("tableField_3ycv10yjn", detailRows(records));
  74. formData.put("selectField_89a55tbg2", "处理中");
  75. formData.put("numberField_89a56h485", 0);
  76. Object result = retry("创建宜搭实例", new Action() {
  77. @Override
  78. public Object execute() {
  79. return ydClient.createData(YDParam.builder().appType(APP_TYPE).formUuid(FORM_UUID)
  80. .formDataJson(JSON.toJSONString(formData)).build());
  81. }
  82. });
  83. return instanceId(result);
  84. }
  85. private boolean sendAndConfirm(List<ArNotifyRecord> records, String instanceId, List<String> recipients) {
  86. String lastError = "";
  87. String taskId = null;
  88. for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
  89. try {
  90. if (StringUtils.isBlank(taskId)) {
  91. taskId = ddClientNotice.sendNotification(ddClient.getAccessToken(), recipients, null, false,
  92. notificationMessage(records, instanceId));
  93. if (StringUtils.isBlank(taskId)) {
  94. throw new IllegalStateException("钉钉未返回工作通知任务编号");
  95. }
  96. update(instanceId, UtilMap.map("textField_89a545m8z, numberField_89a56h485",
  97. taskId, attempt - 1));
  98. }
  99. Map result = ddClientNoticeResult.getSendResult(ddClient.getAccessToken(), taskId);
  100. if (!isSent(result)) {
  101. throw new IllegalStateException("钉钉工作通知尚未发送成功: " + JSON.toJSONString(result));
  102. }
  103. Date now = new Date();
  104. update(instanceId, UtilMap.map(
  105. "textField_89a41cak4, dateField_89a42ww2f, textField_89a53tgha, selectField_89a55tbg2, numberField_89a56h485, textareaField_89a575hku",
  106. "X", now.getTime(), new SimpleDateFormat("HHmmss").format(now), "成功", attempt - 1, "钉钉工作通知发送成功"));
  107. return true;
  108. } catch (Exception e) {
  109. lastError = e.getMessage();
  110. log.warn("AR自动入账通知第{}次尝试失败, instanceId={}, taskId={}, error={}",
  111. attempt, instanceId, taskId, lastError);
  112. if (attempt < MAX_ATTEMPTS) {
  113. sleep(attempt);
  114. }
  115. }
  116. }
  117. log.error("AR自动入账通知重试耗尽, instanceId={}, error={}", instanceId, lastError);
  118. markFailure(instanceId, MAX_ATTEMPTS - 1, lastError);
  119. return false;
  120. }
  121. private void markFailure(final String instanceId, final int retryCount, final String message) {
  122. retry("回写宜搭失败状态", new Action() {
  123. @Override
  124. public Object execute() {
  125. update(instanceId, UtilMap.map("selectField_89a55tbg2, numberField_89a56h485, textareaField_89a575hku",
  126. "失败", retryCount, StringUtils.abbreviate(message, 1900)));
  127. return Boolean.TRUE;
  128. }
  129. });
  130. }
  131. private void update(String instanceId, Map formData) {
  132. ydClient.operateData(YDParam.builder().appType(APP_TYPE).formInstanceId(instanceId)
  133. .updateFormDataJson(JSON.toJSONString(formData)).build(), YDConf.FORM_OPERATION.update);
  134. }
  135. private Object retry(String actionName, Action action) {
  136. for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
  137. try {
  138. return action.execute();
  139. } catch (Exception e) {
  140. log.warn("{}第{}次尝试失败: {}", actionName, attempt, e.getMessage());
  141. if (attempt < MAX_ATTEMPTS) {
  142. sleep(attempt);
  143. }
  144. }
  145. }
  146. log.error("{}重试耗尽", actionName);
  147. return null;
  148. }
  149. private void sleep(int retryNumber) {
  150. try {
  151. Thread.sleep((1L << (retryNumber - 1)) * 1000L);
  152. } catch (InterruptedException e) {
  153. Thread.currentThread().interrupt();
  154. throw new IllegalStateException("重试等待被中断", e);
  155. }
  156. }
  157. private Map notificationMessage(List<ArNotifyRecord> records, String instanceId) {
  158. String detailUrl = "https://www.aliwork.com/" + APP_TYPE + "/formDetail/" + FORM_UUID
  159. + "?formInstId=" + instanceId + "&corpid=dinge61fe69900ea236b35c2f4657eb6378f";
  160. ArNotifyRecord first = records.get(0);
  161. String text = "本次自动入账记录数:" + records.size() + "\n公司代码:" + first.getBUKRS()
  162. + "\n首条客户:" + first.getKUNNR() + " " + StringUtils.defaultString(first.getZNAME())
  163. + "\n首条凭证:" + first.getBELNR() + "\n请点击查看明细。";
  164. Map<String, Object> link = new HashMap<>();
  165. link.put("picUrl", "@lALOACZwe2Rk");
  166. link.put("title", "AR自动入账通知");
  167. link.put("text", text);
  168. link.put("messageUrl", detailUrl);
  169. Map<String, Object> message = new HashMap<>();
  170. message.put("msgtype", "link");
  171. message.put("link", link);
  172. return message;
  173. }
  174. private boolean isSent(Map result) {
  175. if (result == null) {
  176. return false;
  177. }
  178. return !hasFailure(result, "invalid_user_id_list")
  179. && !hasFailure(result, "forbidden_list")
  180. && !hasFailure(result, "failed_user_id_list")
  181. && !hasFailure(result, "invalid_dept_id_list")
  182. && !hasFailure(result, "forbidden_dept_id_list")
  183. && !hasFailure(result, "failed_dept_id_list");
  184. }
  185. private boolean hasFailure(Map result, String key) {
  186. Object value = result.get(key);
  187. if (value == null) {
  188. return false;
  189. }
  190. if (value instanceof Collection) {
  191. return !((Collection) value).isEmpty();
  192. }
  193. if (value.getClass().isArray()) {
  194. return java.lang.reflect.Array.getLength(value) > 0;
  195. }
  196. return StringUtils.isNotBlank(String.valueOf(value));
  197. }
  198. private String instanceId(Object result) {
  199. if (result instanceof DDR_New) {
  200. DDR_New response = (DDR_New) result;
  201. if (StringUtils.isNotBlank(response.getFormInstId())) {
  202. return response.getFormInstId();
  203. }
  204. if (StringUtils.isNotBlank(response.getInstanceId())) {
  205. return response.getInstanceId();
  206. }
  207. result = response.getResult();
  208. }
  209. if (result instanceof String && StringUtils.isNotBlank((String) result)) {
  210. return (String) result;
  211. }
  212. if (!(result instanceof Map)) {
  213. log.error("宜搭创建响应未包含实例ID, response={}", JSON.toJSONString(result));
  214. return null;
  215. }
  216. Map map = (Map) result;
  217. Object id = map.get("formInstId");
  218. if (id == null) {
  219. id = map.get("formInstanceId");
  220. }
  221. if (id == null) {
  222. id = map.get("instanceId");
  223. }
  224. return id == null ? null : String.valueOf(id);
  225. }
  226. private List<String> recipients() {
  227. List<String> result = new ArrayList<>();
  228. String[] userIds = StringUtils.split(StringUtils.defaultString(notifyUserIds), ',');
  229. if (userIds == null) {
  230. return result;
  231. }
  232. for (String userId : userIds) {
  233. if (StringUtils.isNotBlank(userId)) {
  234. result.add(userId.trim());
  235. }
  236. }
  237. return result;
  238. }
  239. private List<Map> detailRows(List<ArNotifyRecord> records) {
  240. List<Map> rows = new ArrayList<>();
  241. for (ArNotifyRecord record : records) {
  242. rows.add(UtilMap.map(
  243. "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",
  244. record.getBUKRS(), record.getKUNNR(), record.getZNAME(), record.getBELNR(), record.getGJAHR(),
  245. dateMillis(record.getBUDAT()), dateMillis(record.getBLDAT()), dateMillis(record.getCPUDT()), record.getWRBTR(),
  246. record.getWAERS(), "", null, "", "", "", ""));
  247. }
  248. return rows;
  249. }
  250. private static long dateMillis(String date) {
  251. try {
  252. SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
  253. formatter.setLenient(false);
  254. return formatter.parse(date).getTime();
  255. } catch (ParseException e) {
  256. throw new IllegalArgumentException("日期必须为yyyyMMdd格式: " + date, e);
  257. }
  258. }
  259. private void validate(ArNotifyRecord record) {
  260. if (record == null || StringUtils.isAnyBlank(record.getBUKRS(), record.getKUNNR(), record.getBELNR(),
  261. record.getGJAHR(), record.getBUDAT(), record.getBLDAT(), record.getCPUDT()) || record.getWRBTR() == null) {
  262. throw new IllegalArgumentException("BUKRS、KUNNR、BELNR、GJAHR、BUDAT、BLDAT、CPUDT、WRBTR不能为空");
  263. }
  264. }
  265. private interface Action {
  266. Object execute();
  267. }
  268. public static void main(String[] args) {
  269. String url = "https://etl-nonprod-tasks.tysondt.com:443/api/1/rest/feed/run/task/TysonNonProd/projects/10-QA-team-cash-to-order/tk_dingtalk_to_sap_otc_inquiry?bearer_token=Rs8eIy614To-g4UAQTsYTt@G9bRMhQrl";
  270. // String url2 = "https://etl-nonprod-tasks.tysondt.com:443/api/1/rest/feed/run/task/TysonNonProd/projects/00-STG-team-cash-to-order/tk_dingtalk_to_sap_otc_unlocked_orders_inquiry?bearer_token=i2AZ93FBOD42aPNLyWACGxXRyK0LdY8U";
  271. String result= HttpUtil.createPost(url).body(JSONObject.toJSONString(UtilMap.map("CUSTOMER_NUMBER","3000045"))).form(JSONObject.toJSONString(UtilMap.map("requestID, interface_no","12345, OTC002"))).execute().body();
  272. System.out.println(result);
  273. // String result2= HttpUtil.createPost(url2).body(JSONObject.toJSONString(UtilMap.map("CUSTOMER_NUMBER","1000253"))).form(JSONObject.toJSONString(UtilMap.map("requestID","1234"))).execute().body();
  274. // System.out.println(result2);
  275. }
  276. }