Browse Source

mc poc订阅事件签到

wzy 4 months ago
parent
commit
c8dec33a2b

+ 102 - 0
mjava-mc/src/main/java/com/malk/mc/controller/McCallBackController.java

@@ -0,0 +1,102 @@
+package com.malk.mc.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.controller.DDCallbackController;
+import com.malk.mc.service.McYdService;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.crypto.DingCallbackCrypto;
+import com.malk.service.dingtalk.DDClient_Event;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@RestController
+@Component
+@RequestMapping("/callBack")
+public class McCallBackController extends DDCallbackController {
+    @Autowired
+    private DDConf ddConf;
+
+    @Autowired
+    private McYdService mcYdService;
+
+    @GetMapping("/test")
+    public String test(){
+        return "test";
+    }
+
+    /**
+     * 钉钉审批回调: [依赖包方案已弃用]
+     * -
+     * DingCallbackCrypto 方案: 官网案例 DingCallbackCrypto 不在钉钉架包, 需要单独引用
+     * 在钉钉开放平台重新保存回调地址后, 所有的注册事件会被关闭:: 通过代码注册不成功; 官方回复, 要么使用调用注册的方式 要么是后台的方式, 二选一
+     */
+    @SneakyThrows
+    public Map<String, String> invokeCallback(@RequestParam(value = "signature", required = false) String signature,
+                                              @RequestParam(value = "timestamp", required = false) String timestamp,
+                                              @RequestParam(value = "nonce", required = false) String nonce,
+                                              @RequestBody(required = false) JSONObject json) {
+        log.info("signature:{}",signature);
+        log.info("timestamp:{}",timestamp);
+        log.info("nonce:{}",nonce);
+        log.info("json:{}",json);
+
+        DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(ddConf.getToken(), ddConf.getAesKey(), ddConf.getAppKey());
+        final String decryptMsg = callbackCrypto.getDecryptMsg(signature, timestamp, nonce, json.getString("encrypt"));
+        JSONObject eventJson = JSON.parseObject(decryptMsg);
+        Map success = callbackCrypto.getEncryptedMap(DDConf.CALLBACK_RESPONSE, System.currentTimeMillis(), DingCallbackCrypto.Utils.getRandomStr(8));
+
+        String eventType = eventJson.getString("EventType");
+        if (DDConf.CALLBACK_CHECK.equals(eventType)) {
+            log.info("----- [DD]验证注册 -----");
+            return success;
+        }
+        if ("check_in".equals(eventType)) {
+            String eventId = eventJson.getString("eventId");//事件id
+
+            log.info("[DD]签到回调, {}", eventJson);
+
+            // 检查回调事件是否已经处理过,如果是,则忽略该回调
+            if (isCallbackProcessed(eventId)) {
+                log.info("----- [DD]该签到回调事件已处理过 忽略该回调 -----");
+                return success;
+            }else {
+                // 将回调事件和当前时间戳添加到已处理集合中
+                long currentTime = System.currentTimeMillis();
+                eventList.put(eventId, currentTime);
+            }
+
+            mcYdService.getCheckInData(eventJson);
+
+            return success;
+        }
+        log.info("----- [DD]已注册, 未处理的其它回调 -----, {}", eventJson);
+        return success;
+    }
+
+    //保存10s内已处理的回调事件
+    private Map<String, Long> eventList = new HashMap<>();
+
+    /**
+     * 检查该回调事件在10s内是否处理过,应对钉钉瞬间重复回调
+     *
+     * @param eventId 回调事件id
+     * @return 是否处理过
+     */
+    private boolean isCallbackProcessed(String eventId) {
+        // 清理超过10s的回调事件
+        long currentTime = System.currentTimeMillis();
+        long expirationTime = currentTime - TimeUnit.MINUTES.toMillis(10);
+        eventList.entrySet().removeIf(entry -> entry.getValue() < expirationTime);
+
+        return eventList.containsKey(eventId);
+    }
+}

+ 3 - 0
mjava-mc/src/main/java/com/malk/mc/service/McYdService.java

@@ -1,5 +1,6 @@
 package com.malk.mc.service;
 
+import com.alibaba.fastjson.JSONObject;
 import com.malk.server.common.McR;
 import org.checkerframework.checker.units.qual.A;
 import org.springframework.scheduling.annotation.Async;
@@ -33,4 +34,6 @@ public interface McYdService {
     McR syncYdAuth(Map map);
 
     McR delYdAuth(Map map);
+
+    void getCheckInData(JSONObject eventJson);
 }

+ 83 - 0
mjava-mc/src/main/java/com/malk/mc/service/impl/McYdServiceImpl.java

@@ -1,6 +1,7 @@
 package com.malk.mc.service.impl;
 
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.digest.MD5;
@@ -33,6 +34,7 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.util.*;
@@ -975,6 +977,76 @@ public class McYdServiceImpl implements McYdService {
         return McR.success();
     }
 
+    @Override
+    public void getCheckInData(JSONObject eventJson) {
+        MDC.put("MDC_KEY_PID","1000");
+
+        int retryCount = 0;
+        int maxRetries = 5;
+
+        long timeStamp = roundToNearestThousand(Long.parseLong(eventJson.getString("timeStamp")));//签到时间戳
+        String staffId = eventJson.getString("StaffId");//签到人userId
+        String eventId = eventJson.getString("eventId");//签到事件id
+
+        while (retryCount <= maxRetries) {
+            try {
+                //获取签到数据
+                Map body = new HashMap();
+                body.put("cursor",0);
+                body.put("size",100);
+                body.put("start_time",timeStamp);
+                body.put("end_time",timeStamp);
+                body.put("userid_list",staffId);
+
+                //查询用户详情
+                DDR_New ddrNew1 = (DDR_New) UtilHttp.doPost("https://oapi.dingtalk.com/topapi/v2/user/get", null, ddClient.initTokenParams(), UtilMap.map("userid", staffId), DDR_New.class);
+                Map result1 = (Map) ddrNew1.getResult();
+                String userName = UtilMap.getString(result1, "name");
+
+                //查询用户考勤数据
+                DDR_New ddrNew = (DDR_New) UtilHttp.doPost("https://oapi.dingtalk.com/topapi/checkin/record/get", null, ddClient.initTokenParams(), body, DDR_New.class);
+                Map result = (Map) ddrNew.getResult();
+                List list = UtilMap.getList(result, "page_list");
+
+                if (!list.isEmpty()){
+                    Map formData = new HashMap();
+                    Map checkInData = (Map) list.get(0);
+                    Long checkinTime = UtilMap.getLong(checkInData, "checkin_time");
+                    String detailPlace = UtilMap.getString(checkInData, "detail_place");
+                    String remark = UtilMap.getString(checkInData,"remark");
+                    formData.put("employeeField_mbypt8ro",Arrays.asList(staffId));//签到人
+                    formData.put("textField_mbyq1cji",userName);//签到人-文本
+                    formData.put("dateField_mbrb3qfy",checkinTime);//签到时间
+                    formData.put("textField_mbrb3qfx",detailPlace);//签到地点
+                    formData.put("textareaField_mbypt8rp",remark);//备注
+                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+                    String formattedDate = sdf.format(timeStamp);
+                    formData.put("textField_mbrbdyl4","签到人:" +userName + ",签到时间:" + formattedDate + ",签到地点:" + detailPlace);//标题
+
+                    ydClient.operateData(YDParam.builder()
+                            .formUuid("FORM-94F65C7405A24E20AE70987AD1C39082SLIB")
+                            .formDataJson(JSONObject.toJSONString(formData))
+                            .build(),YDConf.FORM_OPERATION.create);
+                }
+
+                break;
+            } catch (Exception e) {
+                if (retryCount < maxRetries){
+                    retryCount++;
+                    log.info("eventId:{},第{}次重试,异常信息:{}",eventId, retryCount, e.getMessage());
+                    try {
+                        Thread.sleep(5000);
+                    } catch (InterruptedException ie) {
+                        Thread.currentThread().interrupt();// 如果线程在等待期间被中断,恢复中断状态
+                        throw new RuntimeException("Request interrupted", ie);// 抛出运行时异常,终止重试
+                    }
+                    continue;
+                }
+                throw new RuntimeException("eventId:" + eventId + ",超出最大重试次数,异常信息:" + e.getMessage());
+            }
+        }
+    }
+
     public String sendNotification(String access_token, List<String> userid_list, List<String> dept_id_list, boolean to_all_user, Map msg, String agent_id) {
         Map body = UtilMap.map("agent_id, to_all_user, msg", new Object[]{agent_id, to_all_user, msg});
         if (UtilList.isNotEmpty(userid_list)) {
@@ -988,6 +1060,17 @@ public class McYdServiceImpl implements McYdService {
         return ddr.getTask_id();
     }
 
+    public long roundToNearestThousand(long timestamp) {
+        // 将时间戳除以1000,得到秒级部分(带毫秒的小数部分)
+        double seconds = timestamp / 1000.0;
+
+        // 对秒级部分的个位数进行四舍五入(即对毫秒的千位四舍五入)
+        long roundedSeconds = Math.round(seconds);
+
+        // 将四舍五入后的秒级部分转换回毫秒时间戳
+        return roundedSeconds * 1000;
+    }
+
     @Async
     @Override
     public void batchUpdateSpecifiedQuantityData(Map map) {

+ 2 - 2
mjava-mc/src/main/resources/application-dev.yml

@@ -22,8 +22,8 @@ dingtalk:
   appKey: dinghbynhnd2dbgypmsa
   appSecret: Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
   corpId: dingcc1b1ffad0d5ca1d
-  aesKey:
-  token:
+  aesKey: hUKD4QaLkGTvh86GyOHCmWZ4I1YvAlaUvKkGufXFdaD
+  token: TE0bPgJ1v9Fc3Svu8cfMKcoBd6q5TkljPHg
 # teambition
 teambition:
   AppID: 65956b5dd0ac095d62d0e592

+ 2 - 2
mjava-mc/src/main/resources/application-prod.yml

@@ -22,8 +22,8 @@ dingtalk:
   appKey: dinghbynhnd2dbgypmsa
   appSecret: Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
   corpId: dingcc1b1ffad0d5ca1d
-  aesKey:
-  token:
+  aesKey: hUKD4QaLkGTvh86GyOHCmWZ4I1YvAlaUvKkGufXFdaD
+  token: TE0bPgJ1v9Fc3Svu8cfMKcoBd6q5TkljPHg
 # teambition
 teambition:
   AppID: 65956b5dd0ac095d62d0e592