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