7 changed files with 345 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||
|
package com.project.exam.domain.service; |
||||
|
|
||||
|
import com.project.exam.domain.dto.ExamRecordDTO; |
||||
|
|
||||
|
public interface NotifyExamRecordDomainService { |
||||
|
|
||||
|
void notifyExamRecord(ExamRecordDTO dto); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
package com.project.exam.domain.service.impl; |
||||
|
|
||||
|
import com.github.tingyugetc520.ali.dingtalk.api.DtService; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMsgSendResult; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; |
||||
|
import com.project.ding.utils.SecurityUtils; |
||||
|
import com.project.exam.domain.dto.ExamRecordDTO; |
||||
|
import com.project.exam.domain.service.NotifyExamRecordDomainService; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class NotifyExamRecordDomainServiceImpl implements NotifyExamRecordDomainService { |
||||
|
|
||||
|
@Autowired |
||||
|
private DtService dtService; |
||||
|
|
||||
|
@Override |
||||
|
public void notifyExamRecord(ExamRecordDTO dto) { |
||||
|
String userId = SecurityUtils.getUserId(); |
||||
|
|
||||
|
// 不阻碍正常业务
|
||||
|
try { |
||||
|
String markdown = String.format("### 【%s】考试成绩已公布,请留意查收\n\n", dto.getTaskDTO().getName()) + |
||||
|
String.format("考试成绩:%.2f\n\n", dto.getScore()) + |
||||
|
String.format("考试结果:%s\n\n", dto.getPass() ? "考试通过" : "考试不通过") + |
||||
|
String.format("%s\n\n", dto.getPass() ? "恭喜考试通过,请继续保持!可以自行查看考试结果" : |
||||
|
"遗憾考试不通过,请再接再厉!可以查看考试结果或尝试重新补考"); |
||||
|
DtCorpConversationMessage message = DtCorpConversationMessage.builder() |
||||
|
.agentId(dtService.getDtConfigStorage().getAgentId()) |
||||
|
.userIds(Collections.singletonList(userId)) |
||||
|
.msg(DtMessage.ACTIONCARD() |
||||
|
.title("产品知识考核") |
||||
|
.markdown(markdown) |
||||
|
.singleTitle("查看详情") |
||||
|
// todo 修改为对应页面
|
||||
|
.singleUrl("https://www.baidu.com/").build()) |
||||
|
.build(); |
||||
|
DtCorpConversationMsgSendResult result = dtService.getCorpConversationMsgService().send(message); |
||||
|
if (result.getErrCode() == 0) { |
||||
|
log.info(">>> [成绩公布通知] 成功发送通知 ,考试任务名:{} ,用户:{}, ExamRecord:{}", dto.getTaskDTO().getName() , userId , dto.getId() |
||||
|
); |
||||
|
} else { |
||||
|
log.error(">>> [成绩公布通知] 发送失败.考试任务名:{} ,用户:{}, ExamRecord:{}, 错误码: {}, 错误信息: {}", dto.getTaskDTO().getName() , userId , dto.getId() , |
||||
|
result.getErrCode(), result.getErrMsg()); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error(">>> [成绩公布通知] 钉钉接口调用异常, 考试任务名:{} ,用户:{}, ExamRecord:{}, 原因: {}", dto.getTaskDTO().getName() , userId , |
||||
|
dto.getId() , e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
package com.project.task.domain.job; |
||||
|
|
||||
|
|
||||
|
import com.project.base.config.ScheduledTaskProperties; |
||||
|
import com.project.task.domain.service.NotifyTaskDomainService; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.scheduling.annotation.Scheduled; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class TaskNotifyJob { |
||||
|
@Autowired |
||||
|
private ScheduledTaskProperties taskProps; |
||||
|
|
||||
|
@Autowired |
||||
|
private NotifyTaskDomainService notifyTaskDomainService; |
||||
|
|
||||
|
/** |
||||
|
* 每天上午 9:00 执行通知 |
||||
|
*/ |
||||
|
@Scheduled(cron = "0 0 9 * * ?") |
||||
|
public void run() { |
||||
|
if ("dev".equalsIgnoreCase(taskProps.getOwner())) { |
||||
|
return; |
||||
|
} |
||||
|
log.info(">>> [每日通知任务] 启动执行..."); |
||||
|
Date today = new Date(); |
||||
|
try { |
||||
|
// 处理新开始的任务
|
||||
|
notifyTaskDomainService.notifyNewTasks(today); |
||||
|
|
||||
|
// 处理7天截止的任务
|
||||
|
notifyTaskDomainService.notifyDeadlineTasks(today); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error(">>> [每日通知任务] 执行异常", e); |
||||
|
} |
||||
|
log.info(">>> [每日通知任务] 执行完毕。"); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
package com.project.task.domain.service; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
public interface NotifyTaskDomainService { |
||||
|
void notifyNewTasks(Date today); |
||||
|
void notifyDeadlineTasks(Date today); |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
package com.project.task.domain.service.impl; |
||||
|
|
||||
|
import cn.hutool.core.collection.CollUtil; |
||||
|
import cn.hutool.core.collection.ListUtil; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.api.DtService; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMsgSendResult; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; |
||||
|
import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; |
||||
|
import com.project.task.domain.dto.TaskDTO; |
||||
|
import com.project.task.domain.entity.TaskEntity; |
||||
|
import com.project.task.domain.entity.TaskUserEntity; |
||||
|
import com.project.task.domain.enums.TaskUserStatusEnum; |
||||
|
import com.project.task.domain.service.NotifyTaskDomainService; |
||||
|
import com.project.task.mapper.TaskMapper; |
||||
|
import com.project.task.mapper.TaskUserMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Date; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class NotifyTaskDomainServiceImpl implements NotifyTaskDomainService { |
||||
|
@Autowired |
||||
|
private TaskMapper taskMapper; |
||||
|
|
||||
|
@Autowired |
||||
|
private TaskUserMapper taskUserMapper; |
||||
|
@Autowired |
||||
|
private DtService dtService; |
||||
|
|
||||
|
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public void notifyNewTasks(Date today) { |
||||
|
// 1. 查找今天 00:00:00 到 23:59:59 之间开始的任务
|
||||
|
Date startOfDay = DateUtil.beginOfDay(today); |
||||
|
Date endOfDay = DateUtil.endOfDay(today); |
||||
|
|
||||
|
List<TaskDTO> startTasks = taskMapper.selectList(new LambdaQueryWrapper<TaskEntity>() |
||||
|
.between(TaskEntity::getStartTime, startOfDay, endOfDay)) |
||||
|
.stream().map(entity -> entity.toDTO(TaskDTO::new)).toList(); |
||||
|
|
||||
|
for (TaskDTO task : startTasks) { |
||||
|
// 获取该任务下所有考生
|
||||
|
List<String> userIds = taskUserMapper.selectList(new LambdaQueryWrapper<TaskUserEntity>() |
||||
|
.select(TaskUserEntity::getUserId) |
||||
|
.eq(TaskUserEntity::getTaskId, task.getId())) |
||||
|
.stream().map(TaskUserEntity::getUserId).toList(); |
||||
|
|
||||
|
sendBatchNewTasks(userIds , task); |
||||
|
} |
||||
|
} |
||||
|
@Override |
||||
|
public void notifyDeadlineTasks(Date today) { |
||||
|
Date startOfDay = DateUtil.beginOfDay(DateUtil.offsetDay(today , -6)); |
||||
|
Date endOfDay = DateUtil.endOfDay(DateUtil.offsetDay(today , -6)); |
||||
|
List<TaskDTO> startTasks = taskMapper.selectList(new LambdaQueryWrapper<TaskEntity>() |
||||
|
.between(TaskEntity::getStartTime, startOfDay, endOfDay)) |
||||
|
.stream().map(entity -> entity.toDTO(TaskDTO::new)).toList(); |
||||
|
for (TaskDTO task : startTasks) { |
||||
|
// 获取该任务下所有未通过考生
|
||||
|
List<String> userIds = taskUserMapper.selectList(new LambdaQueryWrapper<TaskUserEntity>() |
||||
|
.select(TaskUserEntity::getUserId) |
||||
|
.in(TaskUserEntity::getStatus , Arrays.asList(TaskUserStatusEnum.Not_Start.getValue() , TaskUserStatusEnum.Fail.getValue())) |
||||
|
.eq(TaskUserEntity::getTaskId, task.getId())) |
||||
|
.stream().map(TaskUserEntity::getUserId).toList(); |
||||
|
|
||||
|
sendBatchDeadlineTasks(userIds , task); |
||||
|
} |
||||
|
} |
||||
|
private void sendBatchNewTasks(List<String> userIds , TaskDTO taskDTO) { |
||||
|
if (CollUtil.isEmpty(userIds)) { |
||||
|
return; |
||||
|
} |
||||
|
StringBuilder markdown = new StringBuilder(); |
||||
|
markdown.append(String.format("### 【%s】已经<font color=#00b500>开始</font>,请按时完成\n\n" , taskDTO.getName())) |
||||
|
.append(String.format("产品线:%s\n\n" , taskDTO.getLineName())) |
||||
|
.append(String.format("开考时间:%s\n\n" ,sdf.format(taskDTO.getStartTime()))) |
||||
|
.append(String.format("截止时间:%s\n\n" , sdf.format(taskDTO.getEndTime()))) |
||||
|
.append(String.format("注意事项:%s" , taskDTO.getNote())); |
||||
|
|
||||
|
ListUtil.partition(userIds, 100).forEach(batch -> { |
||||
|
DtCorpConversationMessage message = DtCorpConversationMessage.builder() |
||||
|
.agentId(dtService.getDtConfigStorage().getAgentId()) |
||||
|
.userIds(batch) |
||||
|
.msg(DtMessage.ACTIONCARD() |
||||
|
.title("产品知识考核") |
||||
|
.markdown(markdown.toString()) |
||||
|
.singleTitle("查看详情") |
||||
|
// todo 修改为对应页面
|
||||
|
.singleUrl("https://www.baidu.com/").build()) |
||||
|
.build(); |
||||
|
try { |
||||
|
DtCorpConversationMsgSendResult result = dtService.getCorpConversationMsgService().send(message); |
||||
|
|
||||
|
if (result.getErrCode() == 0) { |
||||
|
log.info(">>> [考试任务开始通知] 成功发送新任务通知. 任务名: {}, 批次人数: {}, 任务ID: {}", |
||||
|
taskDTO.getName(), batch.size(), result.getTaskId()); |
||||
|
} else { |
||||
|
log.warn(">>> [考试任务开始通知] 发送失败. 错误码: {}, 错误信息: {}", result.getErrCode(), result.getErrMsg()); |
||||
|
} |
||||
|
|
||||
|
} catch (DtErrorException e) { |
||||
|
log.error(">>> [考试任务开始通知] 钉钉接口调用异常, 任务名: {}, 原因: {}", taskDTO.getName(), e.getMessage()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
private void sendBatchDeadlineTasks(List<String> userIds , TaskDTO taskDTO) { |
||||
|
if (CollUtil.isEmpty(userIds)) { |
||||
|
return; |
||||
|
} |
||||
|
StringBuilder markdown = new StringBuilder(); |
||||
|
markdown.append(String.format("### 【%s】即将<font color=#FF0000>结束</font>,请按时完成\n\n" , taskDTO.getName())) |
||||
|
.append(String.format("产品线:%s\n\n" , taskDTO.getLineName())) |
||||
|
.append(String.format("开考时间:%s\n\n" ,sdf.format(taskDTO.getStartTime()))) |
||||
|
.append(String.format("截止时间:%s\n\n" , sdf.format(taskDTO.getEndTime()))) |
||||
|
.append(String.format("注意事项:%s" , taskDTO.getNote())); |
||||
|
|
||||
|
ListUtil.partition(userIds, 100).forEach(batch -> { |
||||
|
DtCorpConversationMessage message = DtCorpConversationMessage.builder() |
||||
|
.agentId(dtService.getDtConfigStorage().getAgentId()) |
||||
|
.userIds(batch) |
||||
|
.msg(DtMessage.ACTIONCARD() |
||||
|
.title("产品知识考核") |
||||
|
.markdown(markdown.toString()) |
||||
|
.singleTitle("查看详情") |
||||
|
// todo 修改为对应页面
|
||||
|
.singleUrl("https://www.baidu.com/").build()) |
||||
|
.build(); |
||||
|
try { |
||||
|
DtCorpConversationMsgSendResult result = dtService.getCorpConversationMsgService().send(message); |
||||
|
|
||||
|
if (result.getErrCode() == 0) { |
||||
|
log.info(">>> [考试任务即将结束通知] 成功发送新任务通知. 任务名: {}, 批次人数: {}, 任务ID: {}", |
||||
|
taskDTO.getName(), batch.size(), result.getTaskId()); |
||||
|
} else { |
||||
|
log.warn(">>> [考试任务即将结束通知] 发送失败. 错误码: {}, 错误信息: {}", result.getErrCode(), result.getErrMsg()); |
||||
|
} |
||||
|
|
||||
|
} catch (DtErrorException e) { |
||||
|
log.error(">>> [考试任务即将结束通知] 钉钉接口调用异常, 任务名: {}, 原因: {}", taskDTO.getName(), e.getMessage()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<configuration> |
||||
|
<!-- 引入 Spring Boot 默认的日志配置(保留控制台输出等) --> |
||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml" /> |
||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml" /> |
||||
|
|
||||
|
<!-- 定义定时任务专用的 Appender --> |
||||
|
<appender name="REPLENISH_JOB_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||
|
<!-- 日志文件路径 --> |
||||
|
<file>logs/replenish-inventory.log</file> |
||||
|
|
||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
||||
|
<!-- 每天产生一个日志文件,并进行 GZ 压缩 --> |
||||
|
<fileNamePattern>logs/archive/replenish-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> |
||||
|
|
||||
|
<!-- 单个文件最大 10MB --> |
||||
|
<maxFileSize>10MB</maxFileSize> |
||||
|
|
||||
|
<!-- 只保留最近 3 天的日志--> |
||||
|
<maxHistory>3</maxHistory> |
||||
|
|
||||
|
<!-- 该类日志总大小不得超过 100MB --> |
||||
|
<totalSizeCap>100MB</totalSizeCap> |
||||
|
</rollingPolicy> |
||||
|
|
||||
|
<encoder> |
||||
|
<pattern>${FILE_LOG_PATTERN}</pattern> |
||||
|
<charset>UTF-8</charset> |
||||
|
</encoder> |
||||
|
</appender> |
||||
|
|
||||
|
<!-- 将特定的类绑定到上述 Appender --> |
||||
|
<logger name="com.project.question.domain.job.InventoryCheckJob" level="INFO" additivity="false"> |
||||
|
<appender-ref ref="REPLENISH_JOB_FILE" /> |
||||
|
</logger> |
||||
|
|
||||
|
<appender name="TASK_NOTICE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||
|
<file>logs/exam-notice.log</file> |
||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
||||
|
<fileNamePattern>logs/archive/exam-notice-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> |
||||
|
<maxFileSize>5MB</maxFileSize> |
||||
|
<maxHistory>3</maxHistory> |
||||
|
<totalSizeCap>50MB</totalSizeCap> |
||||
|
</rollingPolicy> |
||||
|
<encoder><pattern>${FILE_LOG_PATTERN}</pattern></encoder> |
||||
|
</appender> |
||||
|
<logger name="com.project.task.domain.job.TaskNotifyJob" level="INFO" additivity="false"> |
||||
|
<appender-ref ref="TASK_NOTICE_FILE" /> |
||||
|
</logger> |
||||
|
<!-- 根日志配置(保持原有逻辑) --> |
||||
|
<root level="INFO"> |
||||
|
<appender-ref ref="CONSOLE" /> |
||||
|
</root> |
||||
|
</configuration> |
||||
Loading…
Reference in new issue