From 31f481a78be9677cf84cdcdfac2adbfda74267c7 Mon Sep 17 00:00:00 2001 From: andy <1042025947@qq.com> Date: Sat, 7 Mar 2026 13:23:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=92=89=E9=92=89=E7=9B=B8=E5=85=B3=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- evo-admin/pom.xml | 19 + .../src/main/java/com/evo/EvoApplication.java | 2 + .../impl/KQDeviceExchangeProcessor.java | 1 - .../service/IRzAttendanceService.java | 2 + .../service/impl/RzAttendanceServiceImpl.java | 118 +++++- .../com/evo/common/async/AsyncConfig.java | 52 +++ .../common/controller/CallbackController.java | 95 +++++ .../evo/common/controller/TestController.java | 17 + .../java/com/evo/ding/DingCallbackCrypto.java | 400 ++++++++++++++++++ .../java/com/evo/ding/DingGlobalParams.java | 91 ++++ .../src/main/java/com/evo/ding/DingUtils.java | 207 +++++++++ .../java/com/evo/ding/domain/DingGroup.java | 25 ++ .../java/com/evo/ding/domain/DingShift.java | 31 ++ .../com/evo/ding/domain/DingShiftGroup.java | 29 ++ .../evo/ding/mapper/DingShiftGroupMapper.java | 19 + .../com/evo/ding/mapper/DingShiftMapper.java | 17 + .../evo/framework/config/SecurityConfig.java | 1 + .../java/com/evo/system/domain/SysStaff.java | 2 + .../com/evo/system/mapper/SysStaffMapper.java | 8 + .../src/main/resources/application-local.yml | 2 +- evo-admin/src/main/resources/application.yml | 13 + .../mapper/ding/DingShiftGroupMapper.xml | 90 ++++ .../mapper/system/SysStaffMapper.xml | 8 +- 23 files changed, 1242 insertions(+), 7 deletions(-) create mode 100644 evo-admin/src/main/java/com/evo/common/async/AsyncConfig.java create mode 100644 evo-admin/src/main/java/com/evo/common/controller/CallbackController.java create mode 100644 evo-admin/src/main/java/com/evo/ding/DingCallbackCrypto.java create mode 100644 evo-admin/src/main/java/com/evo/ding/DingGlobalParams.java create mode 100644 evo-admin/src/main/java/com/evo/ding/DingUtils.java create mode 100644 evo-admin/src/main/java/com/evo/ding/domain/DingGroup.java create mode 100644 evo-admin/src/main/java/com/evo/ding/domain/DingShift.java create mode 100644 evo-admin/src/main/java/com/evo/ding/domain/DingShiftGroup.java create mode 100644 evo-admin/src/main/java/com/evo/ding/mapper/DingShiftGroupMapper.java create mode 100644 evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java create mode 100644 evo-admin/src/main/resources/mapper/ding/DingShiftGroupMapper.xml diff --git a/evo-admin/pom.xml b/evo-admin/pom.xml index 170d4fc..3cc7edd 100644 --- a/evo-admin/pom.xml +++ b/evo-admin/pom.xml @@ -17,6 +17,19 @@ + + + com.aliyun + dingtalk + 2.2.43 + + + + com.aliyun + alibaba-dingtalk-service-sdk + 2.0.0 + + com.baomidou mybatis-plus-boot-starter @@ -241,6 +254,12 @@ org.springframework.boot spring-boot-starter-thymeleaf + + org.springframework.boot + spring-boot-test + 3.3.3 + test + diff --git a/evo-admin/src/main/java/com/evo/EvoApplication.java b/evo-admin/src/main/java/com/evo/EvoApplication.java index d9284f8..d39027e 100644 --- a/evo-admin/src/main/java/com/evo/EvoApplication.java +++ b/evo-admin/src/main/java/com/evo/EvoApplication.java @@ -4,6 +4,7 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; /** @@ -14,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) @EnableScheduling @MapperScan({"com.evo.**.mapper.**.**"}) +@EnableAsync // 开启异步支持 public class EvoApplication { public static void main(String[] args) diff --git a/evo-admin/src/main/java/com/evo/attendance/processor/impl/KQDeviceExchangeProcessor.java b/evo-admin/src/main/java/com/evo/attendance/processor/impl/KQDeviceExchangeProcessor.java index ef44732..3ea8187 100644 --- a/evo-admin/src/main/java/com/evo/attendance/processor/impl/KQDeviceExchangeProcessor.java +++ b/evo-admin/src/main/java/com/evo/attendance/processor/impl/KQDeviceExchangeProcessor.java @@ -149,7 +149,6 @@ public class KQDeviceExchangeProcessor implements PunchTheClockStrategyExchangeP return initMessage(1, "当天已经打过加班卡"); } - //员工考勤记录 RzAttendance attendance = rzAttendanceMapper.queryNowDayAttendanceByStatisticalIdAndDate(Long.valueOf(userId),date); //如果当前打卡是下班卡, 并且当前员工考勤上班时间为空, 则判定为隔天打下班卡 diff --git a/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java b/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java index 34938e6..acfca3e 100644 --- a/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java +++ b/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java @@ -62,4 +62,6 @@ public interface IRzAttendanceService extends IService RzAttendance selectRzAttendanceBySfIdAndNameAndTime(Long userId, String employeeName, String replacementTime,Boolean isAttendance); public void sendAbnormalAttendance(); + + public void dingDing(String dingUserId, String bizId); } diff --git a/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java b/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java index 4b69aef..8e4b220 100644 --- a/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java @@ -1,5 +1,8 @@ package com.evo.attendance.service.impl; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -13,10 +16,12 @@ import com.evo.attendance.service.RzAttendanceDetailService; import com.evo.common.annotation.DataScope; import com.evo.common.core.domain.AjaxResult; import com.evo.common.core.domain.entity.SysDept; -import com.evo.common.utils.Collections; -import com.evo.common.utils.DateUtils; -import com.evo.common.utils.SecurityUtils; -import com.evo.common.utils.StringUtils; +import com.evo.common.utils.*; +import com.evo.ding.DingUtils; +import com.evo.ding.domain.DingShift; +import com.evo.ding.domain.DingShiftGroup; +import com.evo.ding.mapper.DingShiftGroupMapper; +import com.evo.ding.mapper.DingShiftMapper; import com.evo.equipment.service.IEqImagesService; import com.evo.system.domain.SysStaff; import com.evo.system.mapper.SysDeptMapper; @@ -31,6 +36,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Date; import java.util.List; import java.util.Map; @@ -56,6 +62,10 @@ public class RzAttendanceServiceImpl extends ServiceImpl"+result); + JSONObject resultJson = JSON.parseObject(result); + if(Integer.valueOf(0).equals(resultJson.getInteger("errcode"))) try { + { + JSONArray jsonArray = resultJson.getJSONArray("recordresult"); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject ob = jsonArray.getJSONObject(i); + if(bizId.equals(ob.getString("bizId"))){ + //打卡类型 + String checkType = ob.getString("checkType"); + //考勤组 + String groupId = ob.getString("groupId"); + //排班时间 + Long planCheckTime = ob.getLong("planCheckTime"); + // 格式化:先转Date对象,再格式化 + String formatPlanCheckTime = DateUtils.parseDateToStr("HH:mm:ss", new Date(planCheckTime)); + DingShiftGroup dingsShiftGroup = dingsShiftGroupMapper.selectByGroupIdAndTypeAndCheckTime(groupId, checkType, formatPlanCheckTime); + if(dingsShiftGroup != null){ + //用户打卡时间 + Long userCheckTime = ob.getLong("userCheckTime"); + //工作时间 (这里指的是哪一天的卡) + Long workDate = ob.getLong("workDate"); + /*** + * 打卡结果 + * Normal:正常 + * Early:早退 + * Late:迟到 + * SeriousLate:严重迟到 + * Absenteeism:旷工迟到 + * NotSigned:未打卡 + */ + String timeResult = ob.getString("timeResult"); + Date formatWorkDate = Date.from(Instant.ofEpochMilli(workDate)); + Date formatUserCheckTime = Date.from(Instant.ofEpochMilli(userCheckTime)); + + //通过打卡类型, classId, 和排班打卡时间, 获取工作时长 + //通过班次id拿到工作时长 + DingShift dingsShift = dingsShiftMapper.selectOne(new LambdaQueryWrapper().eq(DingShift::getDingId, dingsShiftGroup.getShiftId())); + //通过workDate 获取哪一天的卡 + RzAttendance attendance = getBaseMapper().queryNowDayAttendanceByStatisticalIdAndDate(sysStaff.getUserId(),formatWorkDate); + //生成打卡记录 + rzAttendanceDetailService.addDetail(attendance, ("OnDuty".equals(checkType) ? "上班卡" : "下班卡"), "/", formatUserCheckTime, "钉钉打卡", ""); + //上班 + if("OnDuty".equals(checkType)){ + attendance.setRules("上班卡"); + attendance.setWorkStartTime(formatUserCheckTime); + if(!"Normal".equals(timeResult)){ + attendance.setYcsFlag("1"); + } + }else if("OffDuty".equals(checkType)){ + attendance.setWorkEndTime(formatUserCheckTime); + if("Normal".equals(timeResult)){ + attendance.setWorkSum(BigDecimal.valueOf(dingsShift.getWorkHour())); + }else{ + //早退需要计算工时 + Long hours = (userCheckTime - attendance.getWorkStartTime().getTime())/1000/60/60; + attendance.setWorkSum(BigDecimal.valueOf(hours)); + attendance.setYcxFlag("1"); + } + }else{ + log.error("钉钉打卡=====>>>>>>>>>>>>>>>>>> 出现未知打卡类型, 当前打卡类型"); + //终止继续执行 + return; + } + //判断打卡时间, 添加夜班次数 打卡时间在晚上7点以后 + if(dingsShift.getWorkHour() == 12 && attendance.getWorkSum().intValue() >= 12 && attendance.getWorkStartTime().getHours() > 12){ + attendance.setNightNumber(1); + //必须是8小时倒班制or单班制, 并且工作时长>=8小时, 并且 上班打开时间在12点至21点 算作中班 -- 2026-02-04 因1月份张克龙中班考勤问题, 刘杰面谈 + }else if(dingsShift.getWorkHour() == 8 && attendance.getWorkSum().intValue() >= 8 && attendance.getWorkStartTime().getHours() >= 12 && attendance.getWorkStartTime().getHours() < 21){ + attendance.setMiddleShiftNumber(1); + } + + getBaseMapper().updateRzAttendance(attendance); + //检查是否开启加班 + if(dingsShift.getOvertimeWork()){ + //如果开启了加班 + Long hours = (userCheckTime - attendance.getWorkStartTime().getTime())/1000/60/60; + new BigDecimal(hours).subtract(new BigDecimal(dingsShift.getWorkHour())).subtract(BigDecimal.valueOf(1)).subtract(new BigDecimal(String.valueOf(DataUtils.findDefaultValue(dingsShift.getDeductionHour(), 0)))); + } + + }else{ + log.info("未找到相关考勤班次数据, 疑似加班打卡=====>>>>> {}", result); + } + break; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + log.error("处理钉钉打卡出现异常, 异常原因是==>>{}, 打卡结果是=====>>>>> {}", e.getMessage(), result); + } + } + } @Async diff --git a/evo-admin/src/main/java/com/evo/common/async/AsyncConfig.java b/evo-admin/src/main/java/com/evo/common/async/AsyncConfig.java new file mode 100644 index 0000000..ab7a14d --- /dev/null +++ b/evo-admin/src/main/java/com/evo/common/async/AsyncConfig.java @@ -0,0 +1,52 @@ +package com.evo.common.async; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; +/** + * AsyncConfig + * + * @author andy.shi + * @ClassName:AsyncConfig + * @date: 2026年03月05日 8:48 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Configuration +//@EnableAsync +public class AsyncConfig { + + @Value("${yt.async.corePoolSize:'5'}") + private Integer corePoolSize; + @Value("${yt.async.maxPoolSize:'10'}") + private Integer maxPoolSize; + @Value("${yt.async.queueCapacity:'20'}") + private Integer queueCapacity; + @Value("${yt.async.keepAliveSeconds:'60'}") + private Integer keepAliveSeconds; + + + @Bean("asyncExecutor") // 自定义线程池名称 + public Executor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 核心线程数 + executor.setCorePoolSize(corePoolSize); + // 最大线程数 + executor.setMaxPoolSize(maxPoolSize); + // 队列容量 + executor.setQueueCapacity(queueCapacity); + // 线程空闲超时时间 + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程名称前缀 + executor.setThreadNamePrefix("async-thread-"); + // 拒绝策略(队列满+最大线程数满时的处理方式) + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 初始化 + executor.initialize(); + return executor; + } +} diff --git a/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java b/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java new file mode 100644 index 0000000..691a5ed --- /dev/null +++ b/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java @@ -0,0 +1,95 @@ +package com.evo.common.controller; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.evo.attendance.processor.impl.KQDeviceExchangeProcessor; +import com.evo.attendance.service.IRzAttendanceService; +import com.evo.common.core.controller.BaseController; +import com.evo.common.utils.StringUtils; +import com.evo.ding.DingGlobalParams; +import com.evo.ding.DingCallbackCrypto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.Map; + +/** + * CallbackController + * + * @author andy.shi + * @ClassName:CallbackController + * @date: 2026年03月04日 11:27 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@RestController +@RequestMapping("/callback") +@Slf4j +public class CallbackController extends BaseController { + + + @Autowired + IRzAttendanceService rzAttendanceService; + + @PostMapping("/ding/clock") + public Map dingDingClock(@RequestBody Map params){ + System.out.println(JSONObject.toJSONString(params)); + try { + // 1. 从http请求中获取加解密参数 + + // 2. 使用加解密类型 + // 2、调用订阅事件接口订阅的事件为企业级事件推送,此时OWNER_KEY为:开发者后台应用的Client ID(原企业内部应用 appKey ) + DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(DingGlobalParams.getToken(), DingGlobalParams.getAesKey(), DingGlobalParams.getClientId()); + String encryptMsg = String.valueOf(params.get("encrypt")); + String decryptMsg = callbackCrypto.getDecryptMsg(String.valueOf(params.get("msg_signature")), String.valueOf(params.get("timestamp")), String.valueOf(params.get("nonce")), encryptMsg); + + System.out.println("==============>"+decryptMsg); + // 3. 反序列化回调事件json数据 + JSONObject eventJson = JSON.parseObject(decryptMsg); + String eventType = eventJson.getString("EventType"); + + // 4. 根据EventType分类处理 + if ("check_url".equals(eventType)) { + // 测试回调url的正确性 + log.info("测试回调url的正确性"); + } else if ("attendance_check_record".equals(eventType)) { //打卡推送 + JSONArray dataList = eventJson.getJSONArray("DataList"); + if(dataList!= null && dataList.size() > 0){ + JSONObject jsonObject = dataList.getJSONObject(0); + String userId = jsonObject.getString("userId"); + if(StringUtils.isNotEmpty(userId)){ + rzAttendanceService.dingDing(userId, jsonObject.getString("bizId")); + } + } + //加班审批订阅 + }else if ("bpms_instance_change".equals(eventType)) { + + + // 处理通讯录用户增加事件 + }else if ("user_add_org".equals(eventType)) { + // 处理通讯录用户增加事件 + log.info("发生了:" + eventType + "事件"); + + + + } else { + // 添加其他已注册的 + log.info("发生了:" + eventType + "事件"); + } + + // 5. 返回success的加密数据 + Map successMap = callbackCrypto.getEncryptedMap("success"); + return successMap; + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/evo-admin/src/main/java/com/evo/common/controller/TestController.java b/evo-admin/src/main/java/com/evo/common/controller/TestController.java index cb862c8..4cff262 100644 --- a/evo-admin/src/main/java/com/evo/common/controller/TestController.java +++ b/evo-admin/src/main/java/com/evo/common/controller/TestController.java @@ -11,6 +11,7 @@ import com.evo.common.config.Global; import com.evo.common.constant.Constants; import com.evo.common.core.domain.AjaxResult; import com.evo.common.utils.Collections; +import com.evo.ding.DingUtils; import com.evo.personnelMatters.mapper.RzOverTimeDetailMapper; import com.evo.personnelMatters.mapper.RzOverTimeMapper; import com.evo.restaurant.service.IRzRestaurantStatisticsService; @@ -68,6 +69,22 @@ public class TestController { @Resource private GZHAccessTokenService gzhAccessTokenService; + /** + * 清洗加班 + */ + @GetMapping("/dept") + public AjaxResult dept(Long deptId) throws ParseException { + return AjaxResult.success(""); + } + + /** + * 清洗加班 + */ + @GetMapping("/user") + public AjaxResult user(String phone) throws ParseException { + return AjaxResult.success(DingUtils.getUserIdByPhoto(phone)); + } + /** * 清洗加班 */ diff --git a/evo-admin/src/main/java/com/evo/ding/DingCallbackCrypto.java b/evo-admin/src/main/java/com/evo/ding/DingCallbackCrypto.java new file mode 100644 index 0000000..9de042a --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/DingCallbackCrypto.java @@ -0,0 +1,400 @@ +package com.evo.ding; + +/** + * DingCallbackCrypto + * + * @author andy.shi + * @ClassName:DingCallbackCrypto + * @date: 2026年03月04日 11:55 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ + +import com.alibaba.fastjson2.JSON; +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Security; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class DingCallbackCrypto { + + private static final Charset CHARSET = Charset.forName("utf-8"); + private static final Base64 base64 = new Base64(); + private byte[] aesKey; + private String token; + private String corpId; + /** + * ask getPaddingBytes key固定长度 + **/ + private static final Integer AES_ENCODE_KEY_LENGTH = 43; + /** + * 加密随机字符串字节长度 + **/ + private static final Integer RANDOM_LENGTH = 16; + + /** + * 构造函数 + * + * @param token 钉钉开放平台上,开发者设置的token + * @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey + * @param corpId 企业自建应用-事件订阅, 使用appKey + * 企业自建应用-注册回调地址, 使用corpId + * 第三方企业应用, 使用suiteKey + * + * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public DingCallbackCrypto(String token, String encodingAesKey, String corpId) throws DingTalkEncryptException { + if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) { + throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL); + } + this.token = token; + this.corpId = corpId; + aesKey = Base64.decodeBase64(encodingAesKey + "="); + } + + public Map getEncryptedMap(String plaintext) throws DingTalkEncryptException { + return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16)); + } + + /** + * 将和钉钉开放平台同步的消息体加密,返回加密Map + * + * @param plaintext 传递的消息体明文 + * @param timeStamp 时间戳 + * @param nonce 随机字符串 + * @return + * @throws DingTalkEncryptException + */ + public Map getEncryptedMap(String plaintext, Long timeStamp, String nonce) + throws DingTalkEncryptException { + if (null == plaintext) { + throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL); + } + if (null == timeStamp) { + throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL); + } + if (null == nonce) { + throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL); + } + // 加密 + String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext); + String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt); + Map resultMap = new HashMap(); + resultMap.put("msg_signature", signature); + resultMap.put("encrypt", encrypt); + resultMap.put("timeStamp", String.valueOf(timeStamp)); + resultMap.put("nonce", nonce); + return resultMap; + } + + /** + * 密文解密 + * + * @param msgSignature 签名串 + * @param timeStamp 时间戳 + * @param nonce 随机串 + * @param encryptMsg 密文 + * @return 解密后的原文 + * @throws DingTalkEncryptException + */ + public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg) + throws DingTalkEncryptException { + //校验签名 + String signature = getSignature(token, timeStamp, nonce, encryptMsg); + if (!signature.equals(msgSignature)) { + throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR); + } + // 解密 + String result = decrypt(encryptMsg); + return result; + } + + /* + * 对明文加密. + * @param text 需要加密的明文 + * @return 加密后base64编码的字符串 + */ + private String encrypt(String random, String plaintext) throws DingTalkEncryptException { + try { + byte[] randomBytes = random.getBytes(CHARSET); + byte[] plainTextBytes = plaintext.getBytes(CHARSET); + byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length); + byte[] corpidBytes = corpId.getBytes(CHARSET); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + byteStream.write(randomBytes); + byteStream.write(lengthByte); + byteStream.write(plainTextBytes); + byteStream.write(corpidBytes); + byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size()); + byteStream.write(padBytes); + byte[] unencrypted = byteStream.toByteArray(); + byteStream.close(); + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + byte[] encrypted = cipher.doFinal(unencrypted); + String result = base64.encodeToString(encrypted); + return result; + } catch (Exception e) { + throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR); + } + } + + /* + * 对密文进行解密. + * @param text 需要解密的密文 + * @return 解密得到的明文 + */ + private String decrypt(String text) throws DingTalkEncryptException { + byte[] originalArr; + try { + // 设置解密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); + // 使用BASE64对密文进行解码 + byte[] encrypted = Base64.decodeBase64(text); + // 解密 + originalArr = cipher.doFinal(encrypted); + } catch (Exception e) { + throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR); + } + + String plainText; + String fromCorpid; + try { + // 去除补位字符 + byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr); + // 分离16位随机字符串,网络字节序和corpId + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + int plainTextLegth = Utils.bytes2int(networkOrder); + plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET); + fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET); + } catch (Exception e) { + throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR); + } + + // corpid不相同的情况 + if (!fromCorpid.equals(corpId)) { + throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR); + } + return plainText; + } + + /** + * 数字签名 + * + * @param token isv token + * @param timestamp 时间戳 + * @param nonce 随机串 + * @param encrypt 加密文本 + * @return + * @throws DingTalkEncryptException + */ + public String getSignature(String token, String timestamp, String nonce, String encrypt) + throws DingTalkEncryptException { + try { + String[] array = new String[] {token, timestamp, nonce, encrypt}; + Arrays.sort(array); + System.out.println(JSON.toJSONString(array)); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 4; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + System.out.println(str); + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuffer hexstr = new StringBuffer(); + String shaHex = ""; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR); + } + } + + public static class Utils { + public Utils() { + } + + public static String getRandomStr(int count) { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < count; ++i) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + + return sb.toString(); + } + + public static byte[] int2Bytes(int count) { + byte[] byteArr = new byte[] {(byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255), + (byte)(count & 255)}; + return byteArr; + } + + public static int bytes2int(byte[] byteArr) { + int count = 0; + + for (int i = 0; i < 4; ++i) { + count <<= 8; + count |= byteArr[i] & 255; + } + + return count; + } + } + + public static class PKCS7Padding { + private static final Charset CHARSET = Charset.forName("utf-8"); + private static final int BLOCK_SIZE = 32; + + public PKCS7Padding() { + } + + public static byte[] getPaddingBytes(int count) { + int amountToPad = 32 - count % 32; + if (amountToPad == 0) { + amountToPad = 32; + } + + char padChr = chr(amountToPad); + String tmp = new String(); + + for (int index = 0; index < amountToPad; ++index) { + tmp = tmp + padChr; + } + + return tmp.getBytes(CHARSET); + } + + public static byte[] removePaddingBytes(byte[] decrypted) { + int pad = decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } + + private static char chr(int a) { + byte target = (byte)(a & 255); + return (char)target; + } + } + + public static class DingTalkEncryptException extends Exception { + public static final int SUCCESS = 0; + public static final int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001; + public static final int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002; + public static final int ENCRYPTION_NONCE_ILLEGAL = 900003; + public static final int AES_KEY_ILLEGAL = 900004; + public static final int SIGNATURE_NOT_MATCH = 900005; + public static final int COMPUTE_SIGNATURE_ERROR = 900006; + public static final int COMPUTE_ENCRYPT_TEXT_ERROR = 900007; + public static final int COMPUTE_DECRYPT_TEXT_ERROR = 900008; + public static final int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009; + public static final int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010; + private static Map msgMap = new HashMap(); + private Integer code; + + static { + msgMap.put(0, "成功"); + msgMap.put(900001, "加密明文文本非法"); + msgMap.put(900002, "加密时间戳参数非法"); + msgMap.put(900003, "加密随机字符串参数非法"); + msgMap.put(900005, "签名不匹配"); + msgMap.put(900006, "签名计算失败"); + msgMap.put(900004, "不合法的aes key"); + msgMap.put(900007, "计算加密文字错误"); + msgMap.put(900008, "计算解密文字错误"); + msgMap.put(900009, "计算解密文字长度不匹配"); + msgMap.put(900010, "计算解密文字corpid不匹配"); + } + + public Integer getCode() { + return this.code; + } + + public DingTalkEncryptException(Integer exceptionCode) { + super((String)msgMap.get(exceptionCode)); + this.code = exceptionCode; + } + } + static { + try { + Security.setProperty("crypto.policy", "limited"); + RemoveCryptographyRestrictions(); + } catch (Exception var1) { + } + + } + private static void RemoveCryptographyRestrictions() throws Exception { + Class jceSecurity = getClazz("javax.crypto.JceSecurity"); + Class cryptoPermissions = getClazz("javax.crypto.CryptoPermissions"); + Class cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission"); + if (jceSecurity != null) { + setFinalStaticValue(jceSecurity, "isRestricted", false); + PermissionCollection defaultPolicy = (PermissionCollection)getFieldValue(jceSecurity, "defaultPolicy", (Object)null, PermissionCollection.class); + if (cryptoPermissions != null) { + Map map = (Map)getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class); + map.clear(); + } + + if (cryptoAllPermission != null) { + Permission permission = (Permission)getFieldValue(cryptoAllPermission, "INSTANCE", (Object)null, Permission.class); + defaultPolicy.add(permission); + } + } + + } + private static Class getClazz(String className) { + Class clazz = null; + + try { + clazz = Class.forName(className); + } catch (Exception var3) { + } + + return clazz; + } + private static void setFinalStaticValue(Class srcClazz, String fieldName, Object newValue) throws Exception { + Field field = srcClazz.getDeclaredField(fieldName); + field.setAccessible(true); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & -17); + field.set((Object)null, newValue); + } + private static T getFieldValue(Class srcClazz, String fieldName, Object owner, Class dstClazz) throws Exception { + Field field = srcClazz.getDeclaredField(fieldName); + field.setAccessible(true); + return dstClazz.cast(field.get(owner)); + } + +} \ No newline at end of file diff --git a/evo-admin/src/main/java/com/evo/ding/DingGlobalParams.java b/evo-admin/src/main/java/com/evo/ding/DingGlobalParams.java new file mode 100644 index 0000000..f2f3283 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/DingGlobalParams.java @@ -0,0 +1,91 @@ +package com.evo.ding; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * DingBase + * + * @author andy.shi + * @ClassName:DingBase + * @date: 2026年03月02日 8:48 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Data +@Component +@ConfigurationProperties(prefix = "ding") +public class DingGlobalParams { + + public static String baseUrl; + + public static String url; + + public static String clientId; + + public static String clientSecret; + + public static String aesKey; + + public static String token; + + public static String corpId; + + + public static String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + DingGlobalParams.baseUrl = baseUrl; + } + + public static String getCorpId() { + return corpId; + } + + public void setCorpId(String corpId) { + DingGlobalParams.corpId = corpId; + } + + public static String getAesKey() { + return aesKey; + } + + public void setAesKey(String aesKey) { + DingGlobalParams.aesKey = aesKey; + } + + public static String getToken() { + return token; + } + + public void setToken(String token) { + DingGlobalParams.token = token; + } + + public static String getUrl() { + return url; + } + + public void setUrl(String url) { + DingGlobalParams.url = url; + } + + public static String getClientId() { + return DingGlobalParams.clientId; + } + + public void setClientId(String clientId) { + DingGlobalParams.clientId = clientId; + } + + public static String getClientSecret() { + return clientSecret; + } + + + public void setClientSecret(String clientSecret) { + DingGlobalParams.clientSecret = clientSecret; + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/DingUtils.java b/evo-admin/src/main/java/com/evo/ding/DingUtils.java new file mode 100644 index 0000000..ff0cc0f --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/DingUtils.java @@ -0,0 +1,207 @@ +package com.evo.ding; + +import com.aliyun.dingtalkoauth2_1_0.Client; +import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest; +import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse; +import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; +import com.aliyun.tea.TeaException; +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.*; +import com.dingtalk.api.response.*; +import com.evo.common.core.redis.RedisCache; +import com.evo.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * DingUtils + * + * @author andy.shi + * @ClassName:DingUtils + * @date: 2026年03月02日 8:54 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Service +@Slf4j +public class DingUtils { + + private static RedisCache redisCache; + + private static String ACCESS_TOKEN_KEY = "ding_access_token"; + + @Autowired + public DingUtils(RedisCache redisCache) { + this.redisCache = redisCache; + } + + private static Client createClient() throws Exception { + com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config(); + config.protocol = "https"; + config.regionId = "central"; + return new Client(config); + } + + public static String getAccessToken() throws Exception { + String token = redisCache.getCacheObject(ACCESS_TOKEN_KEY); + if(StringUtils.isEmpty(token)){ + Client client = createClient(); + GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest().setAppKey(DingGlobalParams.getClientId()).setAppSecret(DingGlobalParams.getClientSecret()); + try { + GetAccessTokenResponse res = client.getAccessToken(getAccessTokenRequest); + token = res.getBody().getAccessToken(); + //7200s- 10s,防止出现读取不超时, 请求超时 + redisCache.setCacheObject(ACCESS_TOKEN_KEY, token, res.getBody().getExpireIn()-10); + } catch (TeaException err) { + if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + log.error("获取token出现 TeaException 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); + } + } catch (Exception _err) { + TeaException err = new TeaException(_err.getMessage(), _err); + if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + log.error("获取token出现 Exception 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); + } + } + + } + return token; + } + +// public static String getDeptList(Long deptId){ +// try { +// DingTalkClient client = new DefaultDingTalkClient(DingBase.getUrl()+"v2/department/listsub"); +// OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest(); +// req.setDeptId(deptId); +// req.setLanguage("zh_CN"); +// OapiV2DepartmentListsubResponse rsp = client.execute(req, getAccessToken()); +// return rsp.getBody(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return ""; +// } +// +// public static String getUserList(Long deptId){ +// try { +// DingTalkClient client = new DefaultDingTalkClient(DingBase.getUrl()+"user/listid"); +// OapiUserListidRequest req = new OapiUserListidRequest(); +// req.setDeptId(deptId); +// OapiUserListidResponse rsp = client.execute(req, getAccessToken()); +// return rsp.getBody(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return ""; +// } + //根据手机号获取用户id + public static String getUserIdByPhoto(String photo){ + try { + DingTalkClient client = new DefaultDingTalkClient(DingGlobalParams.getUrl()+"v2/user/getbymobile"); + OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest(); + req.setMobile(photo); + OapiV2UserGetbymobileResponse rsp = client.execute(req, getAccessToken()); + return rsp.getBody(); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + /*** + * 获取班次信息 + * + */ + public static String getShift(){ + try { + DingTalkClient client = new DefaultDingTalkClient(DingGlobalParams.getUrl()+"attendance/shift/list"); + OapiAttendanceShiftListRequest req = new OapiAttendanceShiftListRequest(); + req.setOpUserId("03491015494921375480"); + OapiAttendanceShiftListResponse rsp = client.execute(req, getAccessToken()); + return rsp.getBody(); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + /*** + * 获取考勤组信息 + * + */ + public static String getAttendanceGroupList(){ + try { + DingTalkClient client = new DefaultDingTalkClient(DingGlobalParams.getUrl()+"attendance/getsimplegroups"); + OapiAttendanceGetsimplegroupsRequest req = new OapiAttendanceGetsimplegroupsRequest(); + OapiAttendanceGetsimplegroupsResponse rsp = client.execute(req, getAccessToken()); + return rsp.getBody(); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + + /*** + * 获取打卡详情 + * @param dingUserIds 钉钉userId + * @param checkDateFrom 开始时间 yyyy-MM-dd HH:mm:ss + * @param checkDateTo 结束时间 yyyy-MM-dd HH:mm:ss + */ + public static String getListRecord(List dingUserIds, String checkDateFrom, String checkDateTo){ + try { + DingTalkClient client = new DefaultDingTalkClient(DingGlobalParams.getBaseUrl()+"attendance/listRecord"); + OapiAttendanceListRecordRequest req = new OapiAttendanceListRecordRequest(); + req.setUserIds(dingUserIds); + req.setCheckDateFrom(checkDateFrom); + req.setCheckDateTo(checkDateTo); + req.setIsI18n(false); + OapiAttendanceListRecordResponse rsp = client.execute(req, getAccessToken()); + System.out.println(rsp.getBody()); + return rsp.getBody(); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + public static com.aliyun.dingtalkworkflow_1_0.Client createClient1() throws Exception { + com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config(); + config.protocol = "https"; + config.regionId = "central"; + return new com.aliyun.dingtalkworkflow_1_0.Client(config); + } + + /*** + * 获取审批详情 + * @param processInstanceId 审批示例id + */ + public static GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult getProcessInstanceInfo(String processInstanceId) throws Exception { + + com.aliyun.dingtalkworkflow_1_0.Client client = DingUtils.createClient1(); + com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders(); + getProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken(); + com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest(); + getProcessInstanceRequest.setProcessInstanceId(processInstanceId); + try { + return client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new com.aliyun.teautil.models.RuntimeOptions()).getBody().getResult(); + } catch (TeaException err) { + if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + log.error("获取token出现 TeaException 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); + } + } catch (Exception _err) { + TeaException err = new TeaException(_err.getMessage(), _err); + if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + log.error("获取token出现 Exception 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); + } + + } + return null; + } + +} diff --git a/evo-admin/src/main/java/com/evo/ding/domain/DingGroup.java b/evo-admin/src/main/java/com/evo/ding/domain/DingGroup.java new file mode 100644 index 0000000..0816658 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/domain/DingGroup.java @@ -0,0 +1,25 @@ +package com.evo.ding.domain; + +import lombok.Data; + +import java.io.Serializable; + +/** + * DingsShift + * + * @author andy.shi + * @ClassName:DingsShift + * @date: 2026年03月04日 13:50 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Data +public class DingGroup implements Serializable { + + /** 主键 */ + private Long id; + /** 班次名称 */ + public String name; + /** 钉钉的班次id */ + public String dingId; + +} diff --git a/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java b/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java new file mode 100644 index 0000000..677c5f5 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java @@ -0,0 +1,31 @@ +package com.evo.ding.domain; + +import lombok.Data; + +import java.io.Serializable; + +/** + * DingsShift + * + * @author andy.shi + * @ClassName:DingsShift + * @date: 2026年03月04日 13:50 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Data +public class DingShift implements Serializable { + + /** 主键 */ + private Long id; + /** 班次名称 */ + public String name; + /** 钉钉的班次id */ + public String dingId; + /** 工作时长 */ + public Integer workHour; + /** 是否加班 */ + public Boolean overtimeWork; + /** 扣减时长 */ + public Double deductionHour; + +} diff --git a/evo-admin/src/main/java/com/evo/ding/domain/DingShiftGroup.java b/evo-admin/src/main/java/com/evo/ding/domain/DingShiftGroup.java new file mode 100644 index 0000000..9e6ebe8 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/domain/DingShiftGroup.java @@ -0,0 +1,29 @@ +package com.evo.ding.domain; + +import lombok.Data; + +import java.io.Serializable; + +/** + * DingsShift + * + * @author andy.shi + * @ClassName:DingsShift + * @date: 2026年03月04日 13:50 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Data +public class DingShiftGroup implements Serializable { + + /** 主键 */ + private Long id; + /** 钉钉的groupId */ + public String groupId; + /** 钉钉的班次id */ + public String shiftId; + /** type 打卡类型 */ + public String type; + /** type 打卡类型 */ + public String checkTime; + +} diff --git a/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftGroupMapper.java b/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftGroupMapper.java new file mode 100644 index 0000000..ae21f00 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftGroupMapper.java @@ -0,0 +1,19 @@ +package com.evo.ding.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.evo.ding.domain.DingShiftGroup; +import org.apache.ibatis.annotations.Param; + +/** + * DingsShiftGroupMapper + * + * @author andy.shi + * @ClassName:DingsShiftGroupMapper + * @date: 2026年03月05日 14:47 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +public interface DingShiftGroupMapper extends BaseMapper { + + + DingShiftGroup selectByGroupIdAndTypeAndCheckTime(@Param("groupId") String groupId, @Param("type") String type, @Param("checkTime") String checkTime); +} diff --git a/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java b/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java new file mode 100644 index 0000000..975b3d3 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java @@ -0,0 +1,17 @@ +package com.evo.ding.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.evo.ding.domain.DingShift; + +/** + * DingsShiftGroupMapper + * + * @author andy.shi + * @ClassName:DingsShiftGroupMapper + * @date: 2026年03月05日 14:47 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +public interface DingShiftMapper extends BaseMapper { + + +} diff --git a/evo-admin/src/main/java/com/evo/framework/config/SecurityConfig.java b/evo-admin/src/main/java/com/evo/framework/config/SecurityConfig.java index 254268a..68a9ce9 100644 --- a/evo-admin/src/main/java/com/evo/framework/config/SecurityConfig.java +++ b/evo-admin/src/main/java/com/evo/framework/config/SecurityConfig.java @@ -119,6 +119,7 @@ public class SecurityConfig .antMatchers("/wechat/**").permitAll() .antMatchers("/websocket/**").permitAll() .antMatchers("/test/**").permitAll() + .antMatchers("/callback/**/**").permitAll() .antMatchers("/api/v1/verify_user").permitAll() .antMatchers("/api/v1/record/face").permitAll() .antMatchers("/api/v2/verify_user_yt").permitAll() diff --git a/evo-admin/src/main/java/com/evo/system/domain/SysStaff.java b/evo-admin/src/main/java/com/evo/system/domain/SysStaff.java index edb0339..e084e96 100644 --- a/evo-admin/src/main/java/com/evo/system/domain/SysStaff.java +++ b/evo-admin/src/main/java/com/evo/system/domain/SysStaff.java @@ -215,6 +215,8 @@ public class SysStaff extends BaseEntity private String extended; + private String dingUserId; + public List getSubsidyList() { if(Collections.isEmpty(subsidyList) && StringUtils.isNotEmpty(subsidys)){ subsidyList = Collections.asList(subsidys.split(",")).stream().map(Long::valueOf).collect(Collectors.toList()); diff --git a/evo-admin/src/main/java/com/evo/system/mapper/SysStaffMapper.java b/evo-admin/src/main/java/com/evo/system/mapper/SysStaffMapper.java index 2bb6608..e9b16c3 100644 --- a/evo-admin/src/main/java/com/evo/system/mapper/SysStaffMapper.java +++ b/evo-admin/src/main/java/com/evo/system/mapper/SysStaffMapper.java @@ -88,4 +88,12 @@ s * @return */ SysStaff selectLeaderSysStaffByDeptId(Long deptId); + /** + * 查询员工管理 + * + * @param dingUserId 员工管理主键 + * @return 员工管理 + */ + public SysStaff selectSysStaffByDingUserId(String dingUserId); + } diff --git a/evo-admin/src/main/resources/application-local.yml b/evo-admin/src/main/resources/application-local.yml index d839287..bd781f9 100644 --- a/evo-admin/src/main/resources/application-local.yml +++ b/evo-admin/src/main/resources/application-local.yml @@ -32,7 +32,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://localhost:3306/evo_cw_dev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3306/evo_cw_dev_ding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: hbyt123456 #username: root diff --git a/evo-admin/src/main/resources/application.yml b/evo-admin/src/main/resources/application.yml index c8aa6aa..125763f 100644 --- a/evo-admin/src/main/resources/application.yml +++ b/evo-admin/src/main/resources/application.yml @@ -115,3 +115,16 @@ yt: appid: wx268e32962db19f5f app-secret: 84a6065165ec82862c5e03a010a6dc6c abnormal-attendance-template-id: z9sy-38K-iC5MAWHbxcxwg1c-9oNTFWeCOoy6B6zdKY + async: + corePoolSize: 15 + maxPoolSize: 30 + queueCapacity: 60 + keepAliveSeconds: 60 +ding: + base_url: https://oapi.dingtalk.com/ + url: ${ding.base_url}topapi/ + client_id: dingoilfezillv76k3mm + client_secret: _UsKmEIidy8U-VDxhb11-qxSGwg4JZqretet4zV5YYTF39lH1DLig5kIlQx4pMfd + aes_key: gCywKzw8AyGfnnh5SkIaRNnVSLzKka0mYjzrODqRzqD + token: J0kSLkXOVMqI3PSSrF1YS16r2pwpExzyEFDGl68SNbf + corp_id: dinga5c6eeb2f797ecdbf5bf40eda33b7ba0 diff --git a/evo-admin/src/main/resources/mapper/ding/DingShiftGroupMapper.xml b/evo-admin/src/main/resources/mapper/ding/DingShiftGroupMapper.xml new file mode 100644 index 0000000..1ea3a38 --- /dev/null +++ b/evo-admin/src/main/resources/mapper/ding/DingShiftGroupMapper.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + select id, group_id, shift_id, type, check_time from ding_shift_group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/evo-admin/src/main/resources/mapper/system/SysStaffMapper.xml b/evo-admin/src/main/resources/mapper/system/SysStaffMapper.xml index 358ba09..049ad30 100644 --- a/evo-admin/src/main/resources/mapper/system/SysStaffMapper.xml +++ b/evo-admin/src/main/resources/mapper/system/SysStaffMapper.xml @@ -50,6 +50,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -58,7 +59,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" quit_date, contract_start, contract_end, contract_type, social_type, seniority, is_overtime_pay, zs_flag, secrecy, injury, insurance, introducer, clock_in, status, wages_ratio_date, remarks, del_flag, create_by, create_time, update_by, - update_time, image_url,time_clock,subsidys, job_code, extended, openid + update_time, image_url,time_clock,subsidys, job_code, extended, openid, ding_user_id from sys_staff @@ -88,6 +89,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + +