钉钉调整

This commit is contained in:
andy 2026-05-04 09:47:02 +08:00
parent f8a75bb671
commit 526bd7473c
18 changed files with 364 additions and 15 deletions

View File

@ -126,9 +126,16 @@ public class RzAttendanceController extends BaseController
return success(rzAttendanceDetailService.selectListByAttendanceId(id));
}
@Log(title = "考勤记录重新抓取", businessType = BusinessType.UPDATE)
@GetMapping(value = "/load/{id}")
public AjaxResult loadAttendance(@PathVariable("id") Long id)
{
public AjaxResult loadAttendance(@PathVariable("id") Long id){
return success(rzAttendanceService.loadAttendance(id));
}
@Log(title = "考勤记录全部重新抓取", businessType = BusinessType.UPDATE)
@PutMapping(value = "/load/all")
public AjaxResult loadAllAttendance(@RequestBody RzAttendance rzAttendance)
{
return rzAttendanceService.loadAllAttendance(rzAttendance);
}
}

View File

@ -1,5 +1,6 @@
package com.evo.attendance.service;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.evo.attendance.domain.RzAttendance;
import com.evo.attendance.domain.vo.RzAttendanceDetailVO;
@ -73,4 +74,6 @@ public interface IRzAttendanceService extends IService<RzAttendance>
public RzAttendance loadAttendance(Long id);
public AjaxResult loadAllAttendance(RzAttendance rzAttendance);
}

View File

@ -16,6 +16,7 @@ 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.handler.thread.RzAttendanceBatchHandler;
import com.evo.common.utils.*;
import com.evo.ding.DingUtils;
import com.evo.ding.domain.DingShift;
@ -56,8 +57,6 @@ public class RzAttendanceServiceImpl extends ServiceImpl<RzAttendanceMapper, RzA
@Resource
private ISysStaffService sysStaffService;
@Resource
private IRzOverTimeService rzOverTimeService;
@Resource
private IEqImagesService eqImagesService;
@Resource
private RzAttendanceDetailService rzAttendanceDetailService;
@ -69,6 +68,9 @@ public class RzAttendanceServiceImpl extends ServiceImpl<RzAttendanceMapper, RzA
private DingShiftGroupMapper dingsShiftGroupMapper;
@Autowired
private DingShiftMapper dingsShiftMapper;
@Resource
private RzAttendanceBatchHandler dataBatchHandler;
private static final String MORNING_CARD_SINGLE = "上班卡(单班制)";
private static final String MORNING_CARD_DOUBLE = "上班卡(双班制)";
@ -666,6 +668,23 @@ public class RzAttendanceServiceImpl extends ServiceImpl<RzAttendanceMapper, RzA
return attendance;
}
@Override
public AjaxResult loadAllAttendance(RzAttendance rzAttendance) {
if (StringUtils.isNull(rzAttendance.getAttendanceDate())) {
return AjaxResult.error("请选择需要重新抓取的月份!!");
}
dataBatchHandler.batchProcess(rzAttendance.getAttendanceDate(), (result, sysStaff, ob, attendance) -> {
// 调用你当前 ServiceImpl 里的方法
try {
this.calculationAddRzOverTime(result, sysStaff, ob, attendance);
} catch (Exception e) {
e.printStackTrace();
log.error("刷新用户===>>{}, 出现异常, 异常原因为====>{}", JSONObject.toJSONString(sysStaff), e.getMessage());
}
});
return null;
}
public void calculationAddRzOverTime(String result ,SysStaff sysStaff, JSONObject ob, RzAttendance attendance){
//打卡类型
String checkType = ob.getString("checkType");

View File

@ -0,0 +1,73 @@
package com.evo.common.config.thread;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
/**
* SpringBoot 动态安全线程池生产级
* @ClassName:DynamicThreadPoolConfig
* @date: 2026年04月25日 15:00
* @author: andy.shi
* @contact: 17330188597
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@Configuration
public class DynamicThreadPoolConfig {
private final ThreadPoolProperties poolProperties;
public DynamicThreadPoolConfig(ThreadPoolProperties poolProperties) {
this.poolProperties = poolProperties;
}
@Bean(name = "dataBatchThreadPool")
public ThreadPoolTaskExecutor dataBatchThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 基础配置
executor.setCorePoolSize(poolProperties.getCorePoolSize());
executor.setMaxPoolSize(poolProperties.getMaxPoolSize());
executor.setQueueCapacity(poolProperties.getQueueCapacity());
executor.setKeepAliveSeconds(poolProperties.getKeepAliveSeconds());
executor.setThreadNamePrefix(poolProperties.getThreadNamePrefix());
// 核心线程允许超时回收
executor.setAllowCoreThreadTimeOut(true);
// 自定义线程工厂异常捕获
executor.setThreadFactory(new ThreadFactory() {
private final AtomicLong num = new AtomicLong(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName(poolProperties.getThreadNamePrefix() + num.getAndIncrement());
thread.setDaemon(true);
thread.setUncaughtExceptionHandler((t, e) ->
System.err.println("线程异常:" + t.getName() + "" + e.getMessage())
);
return thread;
}
});
// 拒绝策略阻塞入队不丢任务
executor.setRejectedExecutionHandler((r, e) -> {
if (!e.isShutdown()) {
try {
e.getQueue().put(r);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
});
// 优雅关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}

View File

@ -0,0 +1,39 @@
package com.evo.common.config.thread;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 线程池工具类静态调用
* @ClassName:ThreadPoolManager
* @date: 2026年04月25日 15:02
* @author: andy.shi
* @contact: 17330188597
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@Component
public class ThreadPoolManager {
private static ThreadPoolTaskExecutor executor;
@Resource(name = "dataBatchThreadPool")
public void setExecutor(ThreadPoolTaskExecutor executor) {
ThreadPoolManager.executor = executor;
}
// 提交任务
public static void execute(Runnable task) {
executor.execute(task);
}
// 动态修改核心线程
public static void setCorePoolSize(int size) {
executor.setCorePoolSize(size);
}
// 动态修改最大线程
public static void setMaxPoolSize(int size) {
executor.setMaxPoolSize(size);
}
}

View File

@ -0,0 +1,26 @@
package com.evo.common.config.thread;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @desc:
* @ClassName:ThreadPoolProperties
* @date: 2026年04月25日 14:57
* @author: andy.shi
* @contact: 17330188597
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@Data
@Component
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties {
private Integer corePoolSize;
private Integer maxPoolSize;
private Integer keepAliveSeconds;
private Integer queueCapacity;
private String threadNamePrefix;
private Integer batchSize; // 每线程处理条数
}

View File

@ -224,7 +224,8 @@ public class TestController {
dataMap.put("robotCode", "dingwoaeaozawfochwoo");
dataMap.put("clientId", "dingwoaeaozawfochwoo");
dataMap.put("clientSecret", "rGrAQ7cReWeYzIK5j71l8d2qOLgUTyc-qnlacQcl3n0dKG3M8bgbbaSoc9AbKeUy");
dataMap.put("users", JSON.toJSONString(Collections.asList("史国臣","田志阳")));
dataMap.put("recipientType", "code");//00146
dataMap.put("users", JSON.toJSONString(Collections.asList("00272")));
dataMap.put("parameterTypes", JSON.toJSONString(Collections.asList("java.lang.String","java.lang.String")));
dataMap.put("parameters", JSON.toJSONString(Collections.asList("test测试",markdownMsg.toString())));

View File

@ -0,0 +1,14 @@
package com.evo.common.function;
/**
* @desc: 四参数
* @ClassName:FourParamFunction
* @date: 2026年04月27日 11:46
* @author: andy.shi
* @contact: 17330188597
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@FunctionalInterface
public interface FourParamFunction<A, B, C, D> {
void apply(A a, B b, C c, D d);
}

View File

@ -0,0 +1,119 @@
package com.evo.common.handler.thread;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.evo.attendance.domain.RzAttendance;
import com.evo.common.config.thread.ThreadPoolManager;
import com.evo.common.config.thread.ThreadPoolProperties;
import com.evo.common.function.FourParamFunction;
import com.evo.common.utils.Collections;
import com.evo.common.utils.DateUtils;
import com.evo.common.utils.StringUtils;
import com.evo.ding.DingUtils;
import com.evo.system.domain.SysStaff;
import com.evo.system.service.ISysStaffService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 考勤 批量数据处理器 每个线程 固定处理 50 条数据
* @ClassName:DataBatchHandler
* @date: 2026年04月25日 15:01
* @author: andy.shi
* @contact: 17330188597
* @remark: 开发人员联系方式 1042025947@qq.com/微信同步
*/
@Slf4j
@Component
public class RzAttendanceBatchHandler {
private final ThreadPoolProperties poolProperties;
@Autowired
private ISysStaffService sysStaffService;
public RzAttendanceBatchHandler(ThreadPoolProperties poolProperties) {
this.poolProperties = poolProperties;
}
/**
* 批量处理任务自动分片每片50条
*/
public void batchProcess(Date date, FourParamFunction<String, SysStaff, JSONObject, RzAttendance> calculationAddRzOverTime) {
int batchSize = poolProperties.getBatchSize(); // 50条/线程
AtomicInteger index = new AtomicInteger(0);
List<SysStaff> sysStaffList = sysStaffService.selectSysStaffListAll();
Date firstDate = DateUtils.getMonthFirst(date);
Date endDate = DateUtils.getMonthEnd(date);
// 获取有多少天
List<String> dateTimeList = new ArrayList<>();
while (firstDate.compareTo(endDate)<=0) {
dateTimeList.add(DateUtils.parseDateToStr("yyyy-MM-dd", firstDate));
firstDate = DateUtils.addDays(firstDate, 1);
}
// 循环分片提交任务
while (index.get() < sysStaffList.size()) {
int start = index.getAndAdd(batchSize);
int end = Math.min(start + batchSize, sysStaffList.size());
List<SysStaff> batchData = sysStaffList.subList(start, end);
// 提交到线程池一个线程处理这 50
ThreadPoolManager.execute(() -> {
try {
handleBatchData(batchData, dateTimeList, calculationAddRzOverTime);
} catch (Exception e) {
System.err.println("批量处理异常:" + e.getMessage());
}
});
}
}
/**
* 真正业务单个线程处理 50 条数据
*/
private void handleBatchData(List<SysStaff> batchData, List<String> dateTimeList, FourParamFunction<String, SysStaff, JSONObject, RzAttendance> calculationAddRzOverTime) {
String threadName = Thread.currentThread().getName();
System.out.println("========================================");
System.out.println(threadName + " 开始处理,条数:" + batchData.size());
//将数据的钉钉钉钉list获取
Map<String, SysStaff> map = batchData.stream().filter(data -> StringUtils.isNotEmpty(data.getDingUserId())).collect(Collectors.toMap(SysStaff::getDingUserId, data -> data));
List<String> dingUserIds = map.keySet().stream().collect(Collectors.toList());
System.out.println("==================="+JSONObject.toJSONString(dingUserIds));
for (String date : dateTimeList){
String result = DingUtils.getListRecord(dingUserIds, date+" 00:00:00", date+" 23:59:59");
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 object = jsonArray.getJSONObject(i);
calculationAddRzOverTime.apply(result, map.get(object.getString("userId")), object, null);
}
} catch (Exception e) {
e.printStackTrace();
log.error("重新抓取钉钉考勤结果异常, 抓取用户为====>>>{}, 异常原因是==>>{}, 打卡结果是=====>>>>> {}", JSONObject.toJSONString(dingUserIds), e.getMessage(), result);
}
}
}
System.out.println(threadName + " 处理完成");
}
}

View File

@ -345,6 +345,16 @@ public class ParamUtils {
return Collections.asList(param.getParamValue().split(",")).stream().filter(StringUtils::isNotEmpty).map(Long::valueOf).collect(Collectors.toList());
}
/***
* 获取基本工资全新的假期
* @return
*/
public static List<Long> getBasicFullPaidLeave(){
RzSysParam param= paramService.getRzSysParam("基本薪全薪发放的假期", "basic_full_paid_leave","58","基本薪全薪发放的假期; 58-陪产假");
return Collections.asList(param.getParamValue().split(",")).stream().filter(StringUtils::isNotEmpty).map(Long::valueOf).collect(Collectors.toList());
}
public static List<String> getWebCompany(){
RzSysParam param= paramService.getRzSysParam("外包公司名称", "web_company","WB,LWWB","外包公司, 不扣个税)");
return Collections.asList(param.getParamValue().split(",")).stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());

View File

@ -198,6 +198,9 @@ public class RzSalaryDetail extends BaseEntity
@TableField(exist = false)
private BigDecimal bjAmount;
//基本工资全薪
@TableField(exist = false)
private BigDecimal basicAmount;
/** 删除标识 */
private String delFlag;
@ -640,6 +643,14 @@ public class RzSalaryDetail extends BaseEntity
this.bjAmount = bjAmount;
}
public BigDecimal getBasicAmount() {
return basicAmount;
}
public void setBasicAmount(BigDecimal basicAmount) {
this.basicAmount = basicAmount;
}
public BigDecimal getSocialSecurityDeduction() {
return socialSecurityDeduction;
}

View File

@ -105,9 +105,10 @@ public interface SalaryCalculationStrategyExchangeProcessor {
rzSalaryDetail.setSalary(rzSalaryDetail.getMonthSalary().add(isMonth ? rzSalaryDetail.getOvertimeSalary() : DataUtils.DEFAULT_VALUE).add(rzSalaryDetail.getNightSubsidies()).add(rzSalaryDetail.getMiddleSubsidies()).add(rzSalaryDetail.getDinnerSubsidies())
.add(DataUtils.findDefaultValue(rzSalaryDetail.getFullSubsidies(), DataUtils.DEFAULT_VALUE)).add(rzSalaryDetail.getLevelSubsidies()).add(rzSalaryDetail.getContractSubsidies()).add(rzSalaryDetail.getSocialSubsidies())
.add(rzSalaryDetail.getSenioritySalary()).add(rzSalaryDetail.getSubsidyOrBonus()).add(rzSalaryDetail.getSalesCommission())
.subtract(rzSalaryDetail.getAbsenteeismSubsidies()).subtract(rzSalaryDetail.getDeductions()).subtract(rzSalaryDetail.getAbsenteeismSalary()).add(DataUtils.findDefaultValue(rzSalaryDetail.getBjAmount(), DataUtils.DEFAULT_VALUE)));
.subtract(rzSalaryDetail.getAbsenteeismSubsidies()).subtract(rzSalaryDetail.getDeductions()).subtract(rzSalaryDetail.getAbsenteeismSalary()).add(DataUtils.findDefaultValue(rzSalaryDetail.getBjAmount(), DataUtils.DEFAULT_VALUE))
.add(DataUtils.findDefaultValue(rzSalaryDetail.getBasicAmount(), DataUtils.DEFAULT_VALUE)));
//.subtract(rzSalaryDetail.getMealFee())
//如果当前人员不打卡 则需要扣减缺勤工资
//如果当前人员不打卡 则需要扣减缺勤工资setBasicAmount
// if("".equals(sysStaff.getClockIn())){
// rzSalaryDetail.setSalary(rzSalaryDetail.getSalary());
// }

View File

@ -115,7 +115,6 @@ public class MonthlySalaryStrategyExchangeProcessor implements SalaryCalculation
BigDecimal gs = rzSalaryDetail.getBasicSalary().multiply(new BigDecimal(Constants.SUBSIDY_PERIOD)).divide(new BigDecimal(ParamUtils.getMonthWorkDayNum(DateUtils.getYear(rzSalaryDetail.getMonth()), DateUtils.getMonth(rzSalaryDetail.getMonth()))),2, RoundingMode.HALF_UP).multiply(new BigDecimal(total8Hour).divide(Constants.DAY_WORK_HOUR,2,RoundingMode.HALF_UP));
// rzSalaryDetail.setMonthSalary(rzSalaryDetail.getMonthSalary().add(gs));
rzSalaryDetail.setBjAmount(gs);
}
BigDecimal gsj = new BigDecimal(0);
Long totalAllHour = 0l;
@ -128,6 +127,20 @@ public class MonthlySalaryStrategyExchangeProcessor implements SalaryCalculation
}
}
}
//获取所有的基本薪资
Long totalBasicHour = 0l;
for (Long type: ParamUtils.getBasicFullPaidLeave()) {
Long hour = rzLeaveDetailMapper.selectLeaveHourByUserIdAndDateAndType(sysStaff.getUserId(), rzSalaryDetail.getMonth(), type);
if(hour.compareTo(0l)> 0){
totalBasicHour = totalBasicHour+hour;
}
}
if(totalBasicHour.compareTo(0l)> 0){
//存在工伤假, 需要计算
BigDecimal basicAmount = rzSalaryDetail.getBasicSalary().divide(new BigDecimal(ParamUtils.getMonthWorkDayNum(DateUtils.getYear(rzSalaryDetail.getMonth()), DateUtils.getMonth(rzSalaryDetail.getMonth()))),2, RoundingMode.HALF_UP).multiply(new BigDecimal(totalBasicHour).divide(Constants.DAY_WORK_HOUR,2,RoundingMode.HALF_UP));
rzSalaryDetail.setBasicAmount(basicAmount);
}
// if(totalAllHour.compareTo(0l)> 0){
// //存在工伤假, 需要计算
// rzSalaryDetail.setMonthSalary(rzSalaryDetail.getMonthSalary().add(workHourPrice.multiply(new BigDecimal(totalAllHour))));

View File

@ -102,6 +102,6 @@ s * @return
*/
public SysStaff selectSysStaffByDingUserId(String dingUserId);
String getDingUserId(@Param("userName") String userName);
SysStaff getDingUserId(@Param("userName") String userName);
}

View File

@ -1019,11 +1019,11 @@ public class SysStaffServiceImpl extends ServiceImpl<SysStaffMapper, SysStaff> i
@Override
public AjaxResult getDingUserId(String userName) {
Object dingUserId = baseMapper.getDingUserId(userName);
if(ObjectUtils.isEmpty(dingUserId)){
SysStaff sysStaff = baseMapper.getDingUserId(userName);
if(sysStaff == null){
return AjaxResult.error("钉钉UserId不存在");
}
return AjaxResult.success(dingUserId);
return AjaxResult.success(JSONObject.parseObject(JSONObject.toJSONString(sysStaff)));
}

View File

@ -32,7 +32,7 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.5.232:3306/evo_cw_dev_ding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.5.230:3306/evo_cw_dev_ding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: hbyt123456
#username: root

View File

@ -128,3 +128,15 @@ ding:
aes_key: gCywKzw8AyGfnnh5SkIaRNnVSLzKka0mYjzrODqRzqD
token: J0kSLkXOVMqI3PSSrF1YS16r2pwpExzyEFDGl68SNbf
corp_id: dinga5c6eeb2f797ecdbf5bf40eda33b7ba0
# 动态线程池配置
thread:
pool:
core-pool-size: 5 # 核心线程
max-pool-size: 20 # 最大线程
keep-alive-seconds: 60 # 空闲回收时间
queue-capacity: 500 # 队列容量
thread-name-prefix: "data-handle-pool-"
batch-size: 50 # 每个线程处理50条数据

View File

@ -314,8 +314,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where del_flag = '0' and status != '-1' and dept_id = #{deptId} and is_leader='是' limit 1
</select>
<select id="getDingUserId" resultType="java.lang.String">
select ding_user_id from sys_staff where name=#{userName}
<select id="getDingUserId" resultMap="SysStaffResult">
<include refid="selectSysStaffVo"/>
where (name=#{userName} or code = #{userName})
</select>