diff --git a/shuili-system/pom.xml b/shuili-system/pom.xml index 5d4579c3..f79d2ab6 100644 --- a/shuili-system/pom.xml +++ b/shuili-system/pom.xml @@ -28,6 +28,12 @@ spring-boot-starter-web + + org.redisson + redisson-spring-boot-starter + 3.16.4 + + mysql @@ -76,7 +82,7 @@ cn.hutool - hutool-core + hutool-all diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/config/RedissonConfig.java b/shuili-system/src/main/java/com/kms/yxgh/common/config/RedissonConfig.java new file mode 100644 index 00000000..2cc4b174 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/config/RedissonConfig.java @@ -0,0 +1,39 @@ +package com.kms.yxgh.common.config; + + +import com.shuili.common.utils.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RedissonConfig { + + + @Value("${spring.redis.host}") + private String redisHost; + + @Value("${spring.redis.port}") + private int redisPort; + + @Value("${spring.redis.password}") + private String redisPassword; + + @Value("${spring.redis.database:0}") + private int redisDatabase; + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redisHost + ":" + redisPort) + .setDatabase(redisDatabase); + if (StringUtils.isNotBlank(redisPassword)) { + config.useSingleServer().setPassword(redisPassword); + } + return Redisson.create(config); + } +} \ No newline at end of file diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/domain/JobTask.java b/shuili-system/src/main/java/com/kms/yxgh/common/domain/JobTask.java new file mode 100644 index 00000000..66039452 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/domain/JobTask.java @@ -0,0 +1,59 @@ +package com.kms.yxgh.common.domain; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.kms.yxgh.base.SyBaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.ToString; + +import java.util.Date; + +/** + * 定时任务实体类对应 bs_sgc_job_task 表 + */ +@TableName("bs_sgc_job_task") +@Data +@ApiModel("定时任务") +@ToString(callSuper = true) +public class JobTask extends SyBaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("任务名称") + private String name; + + @ApiModelProperty("业务类型") + private String businessType; + + @ApiModelProperty("业务id") + private String businessId; + + @ApiModelProperty("任务状态") + private String status; + + @ApiModelProperty("任务参数") + @TableField(value = "params") + private String jobParams; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("上次开始时间") + private Date lastStartTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("上次结束时间") + private Date lastEndTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("下次开始时间") + private Date nextStartTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("任务结束时间") + private Date taskEndTime; + + @TableField(exist = false) + private String remark; +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/JobScheduled.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/JobScheduled.java new file mode 100644 index 00000000..dc7f95b0 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/JobScheduled.java @@ -0,0 +1,78 @@ +package com.kms.yxgh.common.job; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.event.StartJobEvent; +import com.kms.yxgh.common.service.JobTaskService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Calendar; +import java.util.Date; + +@EnableAsync +@EnableScheduling +@Slf4j +@Component +@AllArgsConstructor +public class JobScheduled { + + public final JobTaskService jobTaskService; + private final ApplicationContext applicationContext; + + @Scheduled(fixedRate = 1000 * 60) + public void run() { + log.info("start to run job"); + + Date currentTime = new Date(); + int page = 1; + IPage jobTaskList = getJobTaskList(page, currentTime); + while (!jobTaskList.getRecords().isEmpty()) { + jobTaskList.getRecords().forEach(this::addTask); + jobTaskList = getJobTaskList(page + 1, currentTime); + } + log.info("finish run job"); + } + + private void addTask(JobTask jobTask) { + applicationContext.publishEvent(new StartJobEvent(this, jobTask.getId())); + } + + + private IPage getJobTaskList(int page, Date currentTime) { + IPage pageQuery = new Page<>(page, 100); + Wrapper wrapper = Wrappers.lambdaQuery() + .eq(JobTask::getStatus, JobStatus.RUNNING.getCode()) + .le(JobTask::getNextStartTime, currentTime) + .gt(JobTask::getNextStartTime, getCurrentMidnight()) + .select(JobTask::getId) + .orderByAsc(JobTask::getNextStartTime); + return jobTaskService.page(pageQuery, wrapper); + } + + private Date getCurrentMidnight() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + //每天凌晨1点执行,清理过期任务 + @Scheduled(cron = "0 0 1 * * ?") + public void clearExpireJob() { + log.info("start to clear expire job"); + jobTaskService.clearExpireJob(); + log.info("finish clear expire job"); + } + +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/JobStatus.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/JobStatus.java new file mode 100644 index 00000000..47d56a66 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/JobStatus.java @@ -0,0 +1,33 @@ +package com.kms.yxgh.common.job; + +import lombok.Getter; + +@Getter +public enum JobStatus { + /** + * 任务状态 + */ + INIT("0", "初始化"), + RUNNING("1", "运行中"), + STOP("2", "停止"), + PAUSE("3", "暂停"), + FINISH("4", "完成"), + ERROR("5", "异常"); + + private final String code; + private final String desc; + + JobStatus(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static JobStatus getByCode(String code) { + for (JobStatus value : values()) { + if (value.code.equals(code)) { + return value; + } + } + return null; + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/event/StartJobEvent.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/event/StartJobEvent.java new file mode 100644 index 00000000..cf1aeeda --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/event/StartJobEvent.java @@ -0,0 +1,18 @@ +package com.kms.yxgh.common.job.event; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.context.ApplicationEvent; + +@Getter +@ToString +public class StartJobEvent extends ApplicationEvent { + + private final String jobId; + + public StartJobEvent(Object source, String jobId) { + super(source); + this.jobId = jobId; + } + +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/event/StartJobEventListener.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/event/StartJobEventListener.java new file mode 100644 index 00000000..2eed9f97 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/event/StartJobEventListener.java @@ -0,0 +1,26 @@ +package com.kms.yxgh.common.job.event; + +import com.kms.yxgh.common.service.JobTaskService; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@AllArgsConstructor +public class StartJobEventListener implements ApplicationListener { + + public final JobTaskService jobTaskService; + + @Async + @SneakyThrows + @Override + public void onApplicationEvent(StartJobEvent event) { + log.info("start to run job, job id: {}", event.getJobId()); + jobTaskService.runJob(event.getJobId()); + log.info("finish run job, job id: {}", event.getJobId()); + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/AbstractSendMessageHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/AbstractSendMessageHandler.java new file mode 100644 index 00000000..88b7aa31 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/AbstractSendMessageHandler.java @@ -0,0 +1,159 @@ +package com.kms.yxgh.common.job.handler; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.kms.system.service.SysUserService; +import com.kms.yxgh.common.domain.JobTask; +import com.shuili.common.core.domain.entity.SysUser; +import com.shuili.common.exception.CustomException; +import com.shuili.common.utils.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import java.util.Date; +import java.util.List; + +@Slf4j +public abstract class AbstractSendMessageHandler implements JobHandler { + + //网关 + @Value("${water.gateway.paasToken:}") + private String gatewayPaasToken; + @Value("${water.gateway.paasId:}") + private String gatewayPaasId; + + //应用中台 + @Value("${water.center.paasToken:}") + private String centerPaasToken; + @Value("${water.center.paasId:}") + private String centerPaasId; + + @Value("${water.message.url:}") + private String messageUrl; + + @Autowired + private SysUserService sysUserService; + + public abstract List getMessagesAndUpdateJobTask(JobTask jobTask); + + @Override + public JobTask runJob(JobTask jobTask) { + List messages = getMessagesAndUpdateJobTask(jobTask); + sendMessage(messages); + return jobTask; + } + + + @Data + public static class MsgVo { + private String bizType;// 业务类型(由调用方选填,可填任意字符串,仅作为查询条件使用) + private String content; // 消息内容 + private String contentType; // 内容类型(由调用方选填,可填任意字符串,仅作为查询条件使用) + // description 粤政易消息类型为textcard时的描述,不超过512个字节,超过会自动截断 + //msgType 粤政易消息类型:text、文本,textcard、文本卡片 + //params 参数 + private String receiveNo; // 接收号码:类型为短信则为手机号,粤政易则为统一身份用户id,邮箱则为邮箱号码,站内信为用户主键id + private String receiver; // 接收用户姓名 + // recordId 消息记录id + //sendNo 发送号码:类型为短信则为手机号,粤政易则为统一身份用户id,邮箱则为邮箱号码,站内信为用户主键id + private String sender; // 发送用户姓名 + //templateId 模板id//; + // title 粤政易消息类型为textcard时或者消息类型为email的标题,不超过128个字节,超过会自动截断 + private String type = "inMail";// 消息类型:sms-短信,zwwx-旧粤政易,yzy-新粤政易,email-邮箱,inMail-站内信 + //url 粤政易消息类型为textcard时点击后跳转的链接 + //pcUrl PC端跳转URL + //mobileUrl 移动端跳转URL + // sendType 消息发送类型(关联订阅,数据字典:msg_send_type) + } + + protected void setReceiver(MsgVo msgVo, String userId) { + if (StringUtils.isNotBlank(userId)) { + SysUser user = sysUserService.getById(userId); + if (user == null) { + log.error("用户不存在[{}]", userId); + return; + } + msgVo.setReceiveNo(user.getSingleUserId()); + msgVo.setReceiver(user.getNickName()); + } + } + + + private void sendMessage(List messages) { + if (StringUtils.isNotBlank(messageUrl)) { + log.warn("消息发送地址为空"); + return; + } + if (CollectionUtil.isEmpty(messages)) { + log.warn("消息为空"); + return; + } + HttpRequest httpRequest = HttpRequest.post(messageUrl).body(JSON.toJSONString(messages)); + setHead(httpRequest); + try (HttpResponse response = httpRequest.execute()) { + if (!response.isOk()) { + String body = response.body(); + WaterResult waterResult = JSONObject.parseObject(body, WaterResult.class); + isSuccess(waterResult); + } else { + log.error("消息发送失败"); + } + } catch (Exception e) { + log.error("消息发送失败", e); + } + } + + private void isSuccess(WaterResult waterResult) { + if (waterResult == null) { + throw new CustomException("请求异常"); + } + if (waterResult.getCode() != 200 || !waterResult.getSuccess()) { + throw new CustomException("请求失败,原因:" + waterResult.getMessage()); + } + } + + private void setHead(HttpRequest httpRequest) { + String timestamp = String.valueOf(new Date().getTime()); + String nonce = IdUtil.fastSimpleUUID(); + String gatewaySignature = timestamp + gatewayPaasToken + nonce + timestamp; + String centerSignature = timestamp + centerPaasToken + nonce + timestamp; + try { + gatewaySignature = DigestUtils.sha256Hex(gatewaySignature).toUpperCase(); + centerSignature = DigestUtils.sha256Hex(centerSignature).toUpperCase(); + } catch (Exception e) { + log.error("签名失败", e); + } + httpRequest + .header("x-tif-paasid", gatewayPaasId) + .header("x-tif-signature", gatewaySignature) + .header("x-tif-timestamp", timestamp) + .header("x-tif-nonce", nonce) + .header("x-tsp-paasid", centerPaasId) + .header("x-tsp-signature", centerSignature) + .header("x-tsp-timestamp", timestamp) + .header("x-tsp-nonce", nonce); + + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class WaterResult { + + private int code; + private String message; + private Boolean success; + private String data; + private String timestamp; + } + +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/CycleType.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/CycleType.java new file mode 100644 index 00000000..b0c9fd95 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/CycleType.java @@ -0,0 +1,27 @@ +package com.kms.yxgh.common.job.handler; + +import lombok.Getter; + +@Getter +public enum CycleType { + DAY("0", "每天"), + WEEK("1", "每周"), + MONTH("2", "每月"); + + private final String code; + private final String desc; + + CycleType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static CycleType getByCode(String code) { + for (CycleType value : values()) { + if (value.code.equals(code)) { + return value; + } + } + return null; + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfAnimalPlanSendMessageHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfAnimalPlanSendMessageHandler.java new file mode 100644 index 00000000..61e2fad1 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfAnimalPlanSendMessageHandler.java @@ -0,0 +1,89 @@ +package com.kms.yxgh.common.job.handler; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.JobStatus; +import com.kms.yxgh.df.dto.DfAnimalPlanDto; +import com.kms.yxgh.df.service.DfAnimalPlanService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class DfAnimalPlanSendMessageHandler extends AbstractSendMessageHandler { + + @Autowired() + @Lazy + private DfAnimalPlanService dfAnimalPlanService; + + @Override + public List getMessagesAndUpdateJobTask(JobTask jobTask) { + DfAnimalPlanDto dto = dfAnimalPlanService.getDetailById(jobTask.getBusinessId()); + if (dto == null || dto.getIsReminder() != 1 || CollectionUtil.isEmpty(dto.getOperators())) { + jobTask.setTaskEndTime(new Date()); + jobTask.setStatus(JobStatus.ERROR.getCode()); + return Collections.emptyList(); + } + + if (isExpired(dto, new Date())) { + jobTask.setTaskEndTime(new Date()); + jobTask.setStatus(JobStatus.FINISH.getCode()); + return Collections.emptyList(); + } + + jobTask.setNextStartTime(getNextTime(dto, jobTask.getNextStartTime())); + boolean isFinish = isExpired(dto, jobTask.getNextStartTime()); + if (isFinish) { + jobTask.setTaskEndTime(new Date()); + jobTask.setStatus(JobStatus.FINISH.getCode()); + } + + return dto.getOperators().stream().map(operator -> { + MsgVo msgVo = new MsgVo(); + msgVo.setBizType(jobTask.getBusinessType()); + msgVo.setContent("害堤动物防治计划<<" + dto.getName() + ">>即将开始,请及时执行!"); + msgVo.setContentType(jobTask.getBusinessId()); + setReceiver(msgVo, operator.getUid()); + return msgVo; + }).collect(Collectors.toList()); + } + + //计算下次执行时间 + private Date getNextTime(DfAnimalPlanDto dto, Date targetTime) { + CycleType cycleType = CycleType.getByCode(dto.getCycleType()); + if (cycleType == null) { + return targetTime; + } + switch (cycleType) { + case DAY: + return DateUtil.offsetDay(targetTime, 1); + case WEEK: + return DateUtil.offsetWeek(targetTime, 1); + case MONTH: + return DateUtil.offsetMonth(targetTime, 1); + default: + return targetTime; + } + + } + + //计划是否过期 + private boolean isExpired(DfAnimalPlanDto dto, Date targetTime) { + String currentYearMonth = DateUtil.format(targetTime, "yyyy-MM"); + String planYearMonth = DateUtil.format(dto.getPlanTime(), "yyyy-MM"); + return !currentYearMonth.equals(planYearMonth); + } + + @Override + public String type() { + return SendMessageType.DF_ANIMAL_PLAN.name(); + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfCheckingPlanSendMessageHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfCheckingPlanSendMessageHandler.java new file mode 100644 index 00000000..94fe723b --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfCheckingPlanSendMessageHandler.java @@ -0,0 +1,133 @@ +package com.kms.yxgh.common.job.handler; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.JobStatus; +import com.kms.yxgh.df.dto.DfCheckingPlanContentDto; +import com.kms.yxgh.df.dto.DfPlanDetailDto; +import com.kms.yxgh.df.service.DfPlanService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class DfCheckingPlanSendMessageHandler extends AbstractSendMessageHandler { + + @Autowired() + @Lazy + private DfPlanService dfPlanService; + + @Override + public List getMessagesAndUpdateJobTask(JobTask jobTask) { + DfPlanDetailDto dto = dfPlanService.getDetailById(jobTask.getBusinessId()); + if (dto == null || CollectionUtil.isEmpty(dto.getContents())) { + log.error("DfCheckingPlanSendMessageHandler getMessagesAndUpdateJobTask error, dto is null, jobTask:{}", jobTask); + jobTask.setStatus(JobStatus.ERROR.getCode()); + return Collections.emptyList(); + } + Date currentTime = new Date(); + Optional optional = dto.getContents().stream().filter(content -> content.getStartDate().before(currentTime) && content.getEndDate().after(currentTime)) + .findFirst(); + if (optional.isPresent()) { + DfCheckingPlanContentDto content = optional.get(); + if (CollectionUtil.isEmpty(content.getOperator())) { + return Collections.emptyList(); + } + Date nextTime = getNextTime(dto, jobTask.getNextStartTime()); + if (dto.getContents().stream().anyMatch(c -> c.getEndDate().after(nextTime))) { + jobTask.setNextStartTime(nextTime); + } else { + jobTask.setStatus(JobStatus.FINISH.getCode()); + } + return content.getOperator().stream() + .map(operator -> { + MsgVo msgVo = new MsgVo(); + msgVo.setBizType(jobTask.getBusinessType()); + msgVo.setContent("堤防巡视检查计划<<" + content.getName() + ">>即将开始,请及时执行!\n 计划开始时间:" + content.getStartDate() + " \n计划结束时间:" + content.getEndDate()); + msgVo.setContentType(jobTask.getBusinessId()); + setReceiver(msgVo, operator.getUid()); + return msgVo; + }).collect(Collectors.toList()); + } else { + if (dto.getContents().stream().anyMatch(content -> content.getEndDate().after(currentTime))) { + jobTask.setStatus(JobStatus.FINISH.getCode()); + } + return Collections.emptyList(); + } + + } + + private Date getNextTime(DfPlanDetailDto dto, Date nextStartTime) { + CycleType cycleType = CycleType.getByCode(dto.getCycleType()); + if (cycleType == null) { + return nextStartTime; + } + Date currentTime = new Date(); + switch (cycleType) { + case DAY: + return DateUtil.offsetDay(nextStartTime, 1); + case WEEK: + //获取现在是周几 + int currentWeek = DateUtil.dayOfWeek(currentTime); + if (dto.getOtherConfig() == null) { + return nextStartTime; + } + List weeks = dto.getOtherConfig().getReminderWeeks(); + if (weeks == null) { + return nextStartTime; + } + weeks = weeks.stream().sorted().collect(Collectors.toList()); + int netWeek = weeks.stream().filter(w -> w > currentWeek).findFirst().orElse(weeks.get(0)); + if (netWeek > currentWeek) { + return DateUtil.offsetDay(nextStartTime, netWeek - currentWeek); + } else { + return DateUtil.offsetDay(nextStartTime, 7 - currentWeek + netWeek); + } + + case MONTH: + Date currentDate = new Date(); + int currentMonthDays = getMonthDays(currentDate); + int currentDay = DateUtil.dayOfMonth(currentDate); + if (dto.getOtherConfig() == null) { + return nextStartTime; + } + List days = dto.getOtherConfig().getReminderDays(); + if (days == null) { + return nextStartTime; + } + days = days.stream().sorted().collect(Collectors.toList()); + int nextDay = days.stream().filter(d -> d > currentDay && d <= currentMonthDays).findFirst().orElse(days.get(0)); + if (nextDay > currentDay) { + return DateUtil.offsetDay(nextStartTime, nextDay - currentDay); + } else { + return DateUtil.offsetDay(nextStartTime, currentMonthDays - currentDay + nextDay); + } + default: + return nextStartTime; + } + } + + //获取指定时间月份的天数 + private int getMonthDays(Date date) { + //当前是否闰年 + boolean isLeapYear = DateUtil.isLeapYear(DateUtil.year(date)); + //获取当前月份 + int currentMonth = DateUtil.month(date) + 1; + //获取当前月的天数 + return DateUtil.lengthOfMonth(currentMonth, isLeapYear); + } + + @Override + public String type() { + return SendMessageType.DF_CHECKING_PLAN.name(); + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfYhPlanSendMessageHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfYhPlanSendMessageHandler.java new file mode 100644 index 00000000..5f878653 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/DfYhPlanSendMessageHandler.java @@ -0,0 +1,54 @@ +package com.kms.yxgh.common.job.handler; + +import com.kms.yxgh.common.ApprovalStatusEnum; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.JobStatus; +import com.kms.yxgh.df.dto.DfYhPlanDetailDto; +import com.kms.yxgh.df.service.DfYhPlanService; +import com.shuili.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@Slf4j +@Component +public class DfYhPlanSendMessageHandler extends AbstractSendMessageHandler { + + @Autowired() + @Lazy + private DfYhPlanService dfYhPlanService; + + @Override + public List getMessagesAndUpdateJobTask(JobTask jobTask) { + DfYhPlanDetailDto dto = dfYhPlanService.getDetailById(jobTask.getBusinessId()); + jobTask.setStatus(JobStatus.FINISH.getCode()); + jobTask.setTaskEndTime(new Date()); + if (dto == null || dto.getResponsiblePerson() == null || StringUtils.isBlank(dto.getResponsiblePerson().getUid())) { + log.error("DfYhPlanSendMessageHandler getMessagesAndUpdateJobTask error, dto or responsiblePerson is null, jobTask:{}", jobTask); + jobTask.setStatus(JobStatus.ERROR.getCode()); + return Collections.emptyList(); + } + + long currentTime = System.currentTimeMillis(); + if (dto.getStartDate().getTime() > currentTime || dto.getEndDate().getTime() < currentTime || !dto.getStatus().equals(ApprovalStatusEnum.PASS.getValue())) { + return Collections.emptyList(); + } + + MsgVo msgVo = new MsgVo(); + msgVo.setBizType(jobTask.getBusinessType()); + msgVo.setContent("堤防维修养护计划<<" + dto.getName() + ">>即将开始,请及时执行!\n 计划开始时间:" + dto.getStartDate() + " \n计划结束时间:" + dto.getEndDate()); + msgVo.setContentType(jobTask.getBusinessId()); + setReceiver(msgVo, dto.getResponsiblePerson().getUid()); + return Collections.singletonList(msgVo); + } + + @Override + public String type() { + return SendMessageType.DF_YH_PLAN.name(); + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/JobHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/JobHandler.java new file mode 100644 index 00000000..65fdbed0 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/JobHandler.java @@ -0,0 +1,10 @@ +package com.kms.yxgh.common.job.handler; + +import com.kms.yxgh.common.domain.JobTask; + +public interface JobHandler { + + String type(); + + JobTask runJob(JobTask jobTask); +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SendMessageType.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SendMessageType.java new file mode 100644 index 00000000..8c84dbdc --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SendMessageType.java @@ -0,0 +1,12 @@ +package com.kms.yxgh.common.job.handler; + +import lombok.Getter; + +@Getter +public enum SendMessageType { + DF_YH_PLAN, + SZ_YH_PLAN, + DF_ANIMAL_PLAN, + DF_CHECKING_PLAN, + SZ_CHECKING_PLAN, +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SzCheckingPlanSendMessageHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SzCheckingPlanSendMessageHandler.java new file mode 100644 index 00000000..6c58d9be --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SzCheckingPlanSendMessageHandler.java @@ -0,0 +1,133 @@ +package com.kms.yxgh.common.job.handler; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.JobStatus; +import com.kms.yxgh.sz.dto.SzCheckingPlanContentDto; +import com.kms.yxgh.sz.dto.SzPlanDetailDto; +import com.kms.yxgh.sz.service.SzPlanService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class SzCheckingPlanSendMessageHandler extends AbstractSendMessageHandler { + + @Autowired() + @Lazy + private SzPlanService dfPlanService; + + @Override + public List getMessagesAndUpdateJobTask(JobTask jobTask) { + SzPlanDetailDto dto = dfPlanService.getDetailById(jobTask.getBusinessId()); + if (dto == null || CollectionUtil.isEmpty(dto.getContents())) { + log.error("SzCheckingPlanSendMessageHandler getMessagesAndUpdateJobTask error, dto is null, jobTask:{}", jobTask); + jobTask.setStatus(JobStatus.ERROR.getCode()); + return Collections.emptyList(); + } + Date currentTime = new Date(); + Optional optional = dto.getContents().stream().filter(content -> content.getStartDate().before(currentTime) && content.getEndDate().after(currentTime)) + .findFirst(); + if (optional.isPresent()) { + SzCheckingPlanContentDto content = optional.get(); + if (CollectionUtil.isEmpty(content.getOperator())) { + return Collections.emptyList(); + } + Date nextTime = getNextTime(dto, jobTask.getNextStartTime()); + if (dto.getContents().stream().anyMatch(c -> c.getEndDate().after(nextTime))) { + jobTask.setNextStartTime(nextTime); + } else { + jobTask.setStatus(JobStatus.FINISH.getCode()); + } + return content.getOperator().stream() + .map(operator -> { + MsgVo msgVo = new MsgVo(); + msgVo.setBizType(jobTask.getBusinessType()); + msgVo.setContent("水闸巡视检查计划<<" + content.getName() + ">>即将开始,请及时执行!\n 计划开始时间:" + content.getStartDate() + " \n计划结束时间:" + content.getEndDate()); + msgVo.setContentType(jobTask.getBusinessId()); + setReceiver(msgVo, operator.getUid()); + return msgVo; + }).collect(Collectors.toList()); + } else { + if (dto.getContents().stream().anyMatch(content -> content.getEndDate().after(currentTime))) { + jobTask.setStatus(JobStatus.FINISH.getCode()); + } + return Collections.emptyList(); + } + + } + + private Date getNextTime(SzPlanDetailDto dto, Date nextStartTime) { + CycleType cycleType = CycleType.getByCode(dto.getCycleType()); + if (cycleType == null) { + return nextStartTime; + } + Date currentTime = new Date(); + switch (cycleType) { + case DAY: + return DateUtil.offsetDay(nextStartTime, 1); + case WEEK: + //获取现在是周几 + int currentWeek = DateUtil.dayOfWeek(currentTime); + if (dto.getOtherConfig() == null) { + return nextStartTime; + } + List weeks = dto.getOtherConfig().getReminderWeeks(); + if (weeks == null) { + return nextStartTime; + } + weeks = weeks.stream().sorted().collect(Collectors.toList()); + int netWeek = weeks.stream().filter(w -> w > currentWeek).findFirst().orElse(weeks.get(0)); + if (netWeek > currentWeek) { + return DateUtil.offsetDay(nextStartTime, netWeek - currentWeek); + } else { + return DateUtil.offsetDay(nextStartTime, 7 - currentWeek + netWeek); + } + + case MONTH: + Date currentDate = new Date(); + int currentMonthDays = getMonthDays(currentDate); + int currentDay = DateUtil.dayOfMonth(currentDate); + if (dto.getOtherConfig() == null) { + return nextStartTime; + } + List days = dto.getOtherConfig().getReminderDays(); + if (days == null) { + return nextStartTime; + } + days = days.stream().sorted().collect(Collectors.toList()); + int nextDay = days.stream().filter(d -> d > currentDay && d <= currentMonthDays).findFirst().orElse(days.get(0)); + if (nextDay > currentDay) { + return DateUtil.offsetDay(nextStartTime, nextDay - currentDay); + } else { + return DateUtil.offsetDay(nextStartTime, currentMonthDays - currentDay + nextDay); + } + default: + return nextStartTime; + } + } + + //获取指定时间月份的天数 + private int getMonthDays(Date date) { + //当前是否闰年 + boolean isLeapYear = DateUtil.isLeapYear(DateUtil.year(date)); + //获取当前月份 + int currentMonth = DateUtil.month(date) + 1; + //获取当前月的天数 + return DateUtil.lengthOfMonth(currentMonth, isLeapYear); + } + + @Override + public String type() { + return SendMessageType.SZ_CHECKING_PLAN.name(); + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SzYhPlanSendMessageHandler.java b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SzYhPlanSendMessageHandler.java new file mode 100644 index 00000000..905cc8a8 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/job/handler/SzYhPlanSendMessageHandler.java @@ -0,0 +1,54 @@ +package com.kms.yxgh.common.job.handler; + +import com.kms.yxgh.common.ApprovalStatusEnum; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.JobStatus; +import com.kms.yxgh.sz.dto.SzYhPlanDetailDto; +import com.kms.yxgh.sz.service.SzYhPlanService; +import com.shuili.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@Slf4j +@Component +public class SzYhPlanSendMessageHandler extends AbstractSendMessageHandler { + + @Autowired() + @Lazy + private SzYhPlanService szYhPlanService; + + @Override + public List getMessagesAndUpdateJobTask(JobTask jobTask) { + SzYhPlanDetailDto dto = szYhPlanService.getDetailById(jobTask.getBusinessId()); + jobTask.setStatus(JobStatus.FINISH.getCode()); + jobTask.setTaskEndTime(new Date()); + if (dto == null || dto.getResponsiblePerson() == null || StringUtils.isBlank(dto.getResponsiblePerson().getUid())) { + log.error("SzYhPlanSendMessageHandler getMessagesAndUpdateJobTask error, dto or responsiblePerson is null, jobTask:{}", jobTask); + jobTask.setStatus(JobStatus.ERROR.getCode()); + return Collections.emptyList(); + } + + long currentTime = System.currentTimeMillis(); + if (dto.getStartDate().getTime() > currentTime || dto.getEndDate().getTime() < currentTime || !dto.getStatus().equals(ApprovalStatusEnum.PASS.getValue())) { + return Collections.emptyList(); + } + + MsgVo msgVo = new MsgVo(); + msgVo.setBizType(jobTask.getBusinessType()); + msgVo.setContent("水闸维修养护计划<<" + dto.getName() + ">>即将开始,请及时执行!\n 计划开始时间:" + dto.getStartDate() + " \n计划结束时间:" + dto.getEndDate()); + msgVo.setContentType(jobTask.getBusinessId()); + setReceiver(msgVo, dto.getResponsiblePerson().getUid()); + return Collections.singletonList(msgVo); + } + + @Override + public String type() { + return SendMessageType.SZ_YH_PLAN.name(); + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/mapper/JobTaskMapper.java b/shuili-system/src/main/java/com/kms/yxgh/common/mapper/JobTaskMapper.java new file mode 100644 index 00000000..7c9d91df --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/mapper/JobTaskMapper.java @@ -0,0 +1,11 @@ +package com.kms.yxgh.common.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.kms.yxgh.common.domain.JobTask; +import org.springframework.stereotype.Repository; + + +@Repository +public interface JobTaskMapper extends BaseMapper { + +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/common/service/JobTaskService.java b/shuili-system/src/main/java/com/kms/yxgh/common/service/JobTaskService.java new file mode 100644 index 00000000..126ba566 --- /dev/null +++ b/shuili-system/src/main/java/com/kms/yxgh/common/service/JobTaskService.java @@ -0,0 +1,109 @@ +package com.kms.yxgh.common.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.kms.yxgh.common.domain.JobTask; +import com.kms.yxgh.common.job.JobStatus; +import com.kms.yxgh.common.job.handler.JobHandler; +import com.kms.yxgh.common.mapper.JobTaskMapper; +import com.shuili.common.core.service.BaseService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +@RequiredArgsConstructor +public class JobTaskService extends BaseService { + private final RedissonClient redissonClient; + private final List handlers; + + public void runJob(String jobId) { + JobTask jobTask = getById(jobId); + if (jobTask == null) { + return; + } + if (Objects.equals(jobTask.getStatus(), JobStatus.RUNNING.getCode())) { + log.info("job [{}] is in running status [{}]", jobId, JobStatus.getByCode(jobTask.getStatus())); + // Use Redisson to lock the job + RLock lock = redissonClient.getLock(jobId); + try { + // Try to acquire the lock with a wait time of 10 seconds and lease time of 60 seconds + if (lock.tryLock(10, 60, TimeUnit.SECONDS)) { + try { + jobTask.setLastStartTime(new Date()); + JobHandler jobHandler = getHandler(jobTask.getBusinessType()); + if (jobHandler != null) { + jobHandler.runJob(jobTask); + } else { + jobTask.setStatus(JobStatus.ERROR.getCode()); + log.error("No job handler found for job [{},{}]", jobTask.getBusinessType(), jobId); + } + jobTask.setLastEndTime(new Date()); + this.baseMapper.updateById(jobTask); + } finally { + lock.unlock(); + log.info("Released lock for job [{}]", jobId); + } + } else { + log.info("Could not acquire lock for job [{}]", jobId); + } + } catch (InterruptedException e) { + log.error("Error while trying to acquire lock for job [{}]", jobId, e); + Thread.currentThread().interrupt(); + } + + } else { + log.info("job [{}] is not in running status [{}]", jobId, JobStatus.getByCode(jobTask.getStatus())); + } + + } + + @Transactional(rollbackFor = Exception.class) + public void updateOrInsertJobTask(JobTask jobTask, boolean isCancel) { + if (jobTask.getNextStartTime() == null) { + log.error("jobTask nextStartTime is null, jobTask:{}", jobTask); + } + JobTask old = this.baseMapper.selectOne(new QueryWrapper() + .eq("business_id", jobTask.getBusinessId()) + .eq("business_type", jobTask.getBusinessType())); + if (old == null) { + if (!isCancel) { + jobTask.setStatus(JobStatus.RUNNING.getCode()); + this.baseMapper.insert(jobTask); + } + } else { + if (isCancel) { + old.setStatus(JobStatus.STOP.getCode()); + this.baseMapper.updateById(old); + } else { + old.setNextStartTime(jobTask.getNextStartTime()); + old.setStatus(JobStatus.RUNNING.getCode()); + this.baseMapper.updateById(old); + } + } + + } + + private JobHandler getHandler(String handlerType) { + return handlers.stream().filter(handler -> handler.type().equals(handlerType)).findFirst().orElse(null); + } + + + public void clearExpireJob() { + //删除lastEndTime是3个月前的任务 + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MONTH, -3); + Date expireTime = calendar.getTime(); + this.baseMapper.delete(new QueryWrapper().lt("last_end_time", expireTime)); + + } +} diff --git a/shuili-system/src/main/java/com/kms/yxgh/df/service/DfAnimalPlanService.java b/shuili-system/src/main/java/com/kms/yxgh/df/service/DfAnimalPlanService.java index 94fbad1b..718bffb4 100644 --- a/shuili-system/src/main/java/com/kms/yxgh/df/service/DfAnimalPlanService.java +++ b/shuili-system/src/main/java/com/kms/yxgh/df/service/DfAnimalPlanService.java @@ -9,7 +9,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.kms.yg.df.domain.BsSgcDfSafeJbxx; import com.kms.yg.df.mapper.BsSgcDfSafeJbxxMapper; import com.kms.yxgh.base.DfException; +import com.kms.yxgh.common.domain.JobTask; import com.kms.yxgh.common.dto.OperatorDto; +import com.kms.yxgh.common.job.handler.CycleType; +import com.kms.yxgh.common.job.handler.SendMessageType; +import com.kms.yxgh.common.service.JobTaskService; import com.kms.yxgh.df.domain.DfAnimalOperator; import com.kms.yxgh.df.domain.DfAnimalPlan; import com.kms.yxgh.df.domain.DfAnimalPlanDetail; @@ -27,6 +31,8 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Calendar; +import java.util.Date; import java.util.function.Consumer; /** @@ -42,6 +48,7 @@ public class DfAnimalPlanService extends BaseService maxDay) { + day = maxDay; + } + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + + return calendar.getTime(); + } + + private Date getNextWeekDate(DfAnimalPlanDto dto, int week) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(dto.getReminderTime()); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + + // 设置为下周几 + calendar.setTime(new Date()); + int currentWeek = calendar.get(Calendar.DAY_OF_WEEK); + int daysUntilNextWeek = (week - currentWeek + 7) % 7; + if (daysUntilNextWeek == 0) { + daysUntilNextWeek = 7; + } + calendar.add(Calendar.DAY_OF_MONTH, daysUntilNextWeek); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + + return calendar.getTime(); + } + + private Date getNextDate(DfAnimalPlanDto dto) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(dto.getReminderTime()); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + // 设置为明天的日期 + calendar.setTime(new Date()); + calendar.add(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + return calendar.getTime(); + } + @Transactional(rollbackFor = Exception.class) public Boolean deleteById(String id) { diff --git a/shuili-system/src/main/java/com/kms/yxgh/df/service/DfPlanService.java b/shuili-system/src/main/java/com/kms/yxgh/df/service/DfPlanService.java index a3c288a1..cad9cbc9 100644 --- a/shuili-system/src/main/java/com/kms/yxgh/df/service/DfPlanService.java +++ b/shuili-system/src/main/java/com/kms/yxgh/df/service/DfPlanService.java @@ -1,5 +1,6 @@ package com.kms.yxgh.df.service; +import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -8,7 +9,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.kms.common.utils.UserUtils; import com.kms.system.service.SysUserService; import com.kms.yxgh.base.DfException; +import com.kms.yxgh.common.domain.JobTask; import com.kms.yxgh.common.dto.OperatorDto; +import com.kms.yxgh.common.job.handler.CycleType; +import com.kms.yxgh.common.job.handler.SendMessageType; +import com.kms.yxgh.common.service.JobTaskService; import com.kms.yxgh.df.domain.*; import com.kms.yxgh.df.dto.*; import com.kms.yxgh.df.mapper.DfCheckingPlanContentMapper; @@ -47,6 +52,7 @@ public class DfPlanService extends BaseService { private final DfCheckingPlanContentMapper dfCheckingPlanContentMapper; private final SysUserService userService; private final DfPlanOperatorService dfPlanOperatorService; + private final JobTaskService jobTaskService; public IPage search(SearchParam sp) { Page page = new Page<>(sp.getPageNum(), sp.getPageSize()); @@ -99,7 +105,9 @@ public class DfPlanService extends BaseService { getBaseMapper().insert(dfPlan); String id = dfPlan.getId(); saveOrUpdateContent(id, dto.getContents()); - return this.getDetailById(id); + DfPlanDetailDto planDetailDto = this.getDetailById(id); + sendMsg(planDetailDto); + return planDetailDto; } else { throw new DfException("该名称已存在"); } @@ -120,7 +128,9 @@ public class DfPlanService extends BaseService { getBaseMapper().updateById(dfPlan); String id = dfPlan.getId(); saveOrUpdateContent(id, dto.getContents()); - return this.getDetailById(id); + DfPlanDetailDto planDetailDto = this.getDetailById(id); + sendMsg(planDetailDto); + return planDetailDto; } else { throw new DfException("该名称已存在"); } @@ -130,6 +140,113 @@ public class DfPlanService extends BaseService { } + private void sendMsg(DfPlanDetailDto dto) { + if (dto == null) { + return; + } + JobTask jobTask = new JobTask(); + jobTask.setBusinessId(dto.getId()); + jobTask.setBusinessType(SendMessageType.DF_CHECKING_PLAN.name()); + jobTask.setNextStartTime(getNextTime(dto)); + jobTaskService.updateOrInsertJobTask(jobTask, false); + } + + private Date getNextTime(DfPlanDetailDto dto) { + CycleType cycleType = CycleType.getByCode(dto.getCycleType()); + if (cycleType == null || dto.getOtherConfig() == null || dto.getReminderTime() == null) { + return null; + } + switch (cycleType) { + case DAY: + return getNextDate(dto); + case WEEK: + return getNextWeekDate(dto); + case MONTH: + return getNextMonthDate(dto); + default: + return null; + } + } + + private Date getNextMonthDate(DfPlanDetailDto dto) { + if (dto.getOtherConfig() == null) { + return null; + } + List days = dto.getOtherConfig().getReminderDays(); + if (days == null) { + return null; + } + Date currentTime = new Date(); + int currentMonthDays = getMonthDays(currentTime); + int currentDay = DateUtil.dayOfMonth(currentTime); + Date nextStartTime = getCurrentDate(dto); + days = days.stream().sorted().collect(Collectors.toList()); + int nextDay = days.stream().filter(w -> w > currentDay && w <= currentMonthDays).findFirst().orElse(days.get(0)); + if (nextDay > currentDay) { + return DateUtil.offsetDay(nextStartTime, nextDay - currentDay); + } else { + return DateUtil.offsetDay(nextStartTime, currentMonthDays - currentDay + nextDay); + } + } + + private int getMonthDays(Date date) { + //当前是否闰年 + boolean isLeapYear = DateUtil.isLeapYear(DateUtil.year(date)); + //获取当前月份 + int currentMonth = DateUtil.month(date) + 1; + //获取当前月的天数 + return DateUtil.lengthOfMonth(currentMonth, isLeapYear); + } + + private Date getNextWeekDate(DfPlanDetailDto dto) { + if (dto.getOtherConfig() == null) { + return null; + } + List weeks = dto.getOtherConfig().getReminderWeeks(); + if (weeks == null) { + return null; + } + Date currentTime = new Date(); + int currentWeek = DateUtil.dayOfWeek(currentTime); + Date nextStartTime = getCurrentDate(dto); + weeks = weeks.stream().sorted().collect(Collectors.toList()); + int netWeek = weeks.stream().filter(w -> w > currentWeek).findFirst().orElse(weeks.get(0)); + if (netWeek > currentWeek) { + return DateUtil.offsetDay(nextStartTime, netWeek - currentWeek); + } else { + return DateUtil.offsetDay(nextStartTime, 7 - currentWeek + netWeek); + } + } + + private Date getCurrentDate(DfPlanDetailDto dto) { + Calendar calendar = Calendar.getInstance(); + setCalendarTime(calendar, dto.getReminderTime()); + setCalendarToTomorrow(calendar); + return calendar.getTime(); + } + + private Date getNextDate(DfPlanDetailDto dto) { + Calendar calendar = Calendar.getInstance(); + setCalendarTime(calendar, dto.getReminderTime()); + setCalendarToTomorrow(calendar); + calendar.add(Calendar.DAY_OF_MONTH, 1); + return calendar.getTime(); + } + + private void setCalendarTime(Calendar calendar, Date date) { + calendar.setTime(date); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + } + + private void setCalendarToTomorrow(Calendar calendar) { + calendar.setTime(new Date()); + } + @Transactional(rollbackFor = Exception.class) public Boolean deleteById(String[] ids) { if (ids.length == 1) { diff --git a/shuili-system/src/main/java/com/kms/yxgh/df/service/DfYhPlanService.java b/shuili-system/src/main/java/com/kms/yxgh/df/service/DfYhPlanService.java index 8f917efa..6e0956cd 100644 --- a/shuili-system/src/main/java/com/kms/yxgh/df/service/DfYhPlanService.java +++ b/shuili-system/src/main/java/com/kms/yxgh/df/service/DfYhPlanService.java @@ -6,11 +6,13 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.kms.system.service.SysUserService; import com.kms.yxgh.base.DfException; import com.kms.yxgh.common.ApprovalStatusEnum; +import com.kms.yxgh.common.domain.JobTask; import com.kms.yxgh.common.dto.*; +import com.kms.yxgh.common.job.handler.SendMessageType; import com.kms.yxgh.common.service.DefaultApprovalBusinessService; +import com.kms.yxgh.common.service.JobTaskService; import com.kms.yxgh.df.domain.DfYhPlan; import com.kms.yxgh.df.dto.DfYhPlanDetailDto; import com.kms.yxgh.df.dto.DfYhPlanSearchDto; @@ -35,7 +37,7 @@ import java.util.Optional; @AllArgsConstructor public class DfYhPlanService extends DefaultApprovalBusinessService { - private final SysUserService sysUserService; + private final JobTaskService jobTaskService; public DfYhPlanDetailDto getDetailById(String id) { DfYhPlan dfPlan = this.getById(id); @@ -130,12 +132,20 @@ public class DfYhPlanService extends DefaultApprovalBusinessService wp = Wrappers.lambdaUpdate() .eq(DfYhPlan::getId, formId) .set(DfYhPlan::getStatus, status.getValue()); getBaseMapper().update(null, wp); + DfYhPlan dfPlan = this.getBaseMapper().selectById(formId); + + JobTask jobTask = new JobTask(); + jobTask.setBusinessId(formId); + jobTask.setBusinessType(SendMessageType.DF_YH_PLAN.name()); + jobTask.setNextStartTime(dfPlan.getReminderTime()); + jobTaskService.updateOrInsertJobTask(jobTask, !ApprovalStatusEnum.PASS.equals(status)); } @Override diff --git a/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzPlanService.java b/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzPlanService.java index 98f8ad02..ff5cf368 100644 --- a/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzPlanService.java +++ b/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzPlanService.java @@ -1,5 +1,6 @@ package com.kms.yxgh.sz.service; +import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -8,7 +9,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.kms.common.utils.UserUtils; import com.kms.system.service.SysUserService; import com.kms.yxgh.base.SzException; +import com.kms.yxgh.common.domain.JobTask; import com.kms.yxgh.common.dto.OperatorDto; +import com.kms.yxgh.common.job.handler.CycleType; +import com.kms.yxgh.common.job.handler.SendMessageType; +import com.kms.yxgh.common.service.JobTaskService; import com.kms.yxgh.sz.domain.*; import com.kms.yxgh.sz.dto.*; import com.kms.yxgh.sz.mapper.SzCheckingPlanContentMapper; @@ -47,6 +52,8 @@ public class SzPlanService extends BaseService { private final SzCheckingPlanContentMapper szCheckingPlanContentMapper; private final SysUserService userService; private final SzPlanOperatorService szPlanOperatorService; + private final JobTaskService jobTaskService; + public IPage search(SearchParam sp) { Page page = new Page<>(sp.getPageNum(), sp.getPageSize()); @@ -99,7 +106,9 @@ public class SzPlanService extends BaseService { getBaseMapper().insert(szPlan); String id = szPlan.getId(); saveOrUpdateContent(id, dto.getContents()); - return this.getDetailById(id); + SzPlanDetailDto szPlanDetailDto = this.getDetailById(id); + sendMsg(szPlanDetailDto); + return szPlanDetailDto; } else { throw new SzException("该名称已存在"); } @@ -120,7 +129,9 @@ public class SzPlanService extends BaseService { getBaseMapper().updateById(szPlan); String id = szPlan.getId(); saveOrUpdateContent(id, dto.getContents()); - return this.getDetailById(id); + SzPlanDetailDto szPlanDetailDto = this.getDetailById(id); + sendMsg(szPlanDetailDto); + return szPlanDetailDto; } else { throw new SzException("该名称已存在"); } @@ -144,6 +155,113 @@ public class SzPlanService extends BaseService { return rt; } + private void sendMsg(SzPlanDetailDto dto) { + if (dto == null) { + return; + } + JobTask jobTask = new JobTask(); + jobTask.setBusinessId(dto.getId()); + jobTask.setBusinessType(SendMessageType.DF_CHECKING_PLAN.name()); + jobTask.setNextStartTime(getNextTime(dto)); + jobTaskService.updateOrInsertJobTask(jobTask, false); + } + + private Date getNextTime(SzPlanDetailDto dto) { + CycleType cycleType = CycleType.getByCode(dto.getCycleType()); + if (cycleType == null || dto.getOtherConfig() == null || dto.getReminderTime() == null) { + return null; + } + switch (cycleType) { + case DAY: + return getNextDate(dto); + case WEEK: + return getNextWeekDate(dto); + case MONTH: + return getNextMonthDate(dto); + default: + return null; + } + } + + private Date getNextMonthDate(SzPlanDetailDto dto) { + if (dto.getOtherConfig() == null) { + return null; + } + List days = dto.getOtherConfig().getReminderDays(); + if (days == null) { + return null; + } + Date currentTime = new Date(); + int currentMonthDays = getMonthDays(currentTime); + int currentDay = DateUtil.dayOfMonth(currentTime); + Date nextStartTime = getCurrentDate(dto); + days = days.stream().sorted().collect(Collectors.toList()); + int nextDay = days.stream().filter(w -> w > currentDay && w <= currentMonthDays).findFirst().orElse(days.get(0)); + if (nextDay > currentDay) { + return DateUtil.offsetDay(nextStartTime, nextDay - currentDay); + } else { + return DateUtil.offsetDay(nextStartTime, currentMonthDays - currentDay + nextDay); + } + } + + private int getMonthDays(Date date) { + //当前是否闰年 + boolean isLeapYear = DateUtil.isLeapYear(DateUtil.year(date)); + //获取当前月份 + int currentMonth = DateUtil.month(date) + 1; + //获取当前月的天数 + return DateUtil.lengthOfMonth(currentMonth, isLeapYear); + } + + private Date getNextWeekDate(SzPlanDetailDto dto) { + if (dto.getOtherConfig() == null) { + return null; + } + List weeks = dto.getOtherConfig().getReminderWeeks(); + if (weeks == null) { + return null; + } + Date currentTime = new Date(); + int currentWeek = DateUtil.dayOfWeek(currentTime); + Date nextStartTime = getCurrentDate(dto); + weeks = weeks.stream().sorted().collect(Collectors.toList()); + int netWeek = weeks.stream().filter(w -> w > currentWeek).findFirst().orElse(weeks.get(0)); + if (netWeek > currentWeek) { + return DateUtil.offsetDay(nextStartTime, netWeek - currentWeek); + } else { + return DateUtil.offsetDay(nextStartTime, 7 - currentWeek + netWeek); + } + } + + private Date getCurrentDate(SzPlanDetailDto dto) { + Calendar calendar = Calendar.getInstance(); + setCalendarTime(calendar, dto.getReminderTime()); + setCalendarToTomorrow(calendar); + return calendar.getTime(); + } + + private Date getNextDate(SzPlanDetailDto dto) { + Calendar calendar = Calendar.getInstance(); + setCalendarTime(calendar, dto.getReminderTime()); + setCalendarToTomorrow(calendar); + calendar.add(Calendar.DAY_OF_MONTH, 1); + return calendar.getTime(); + } + + private void setCalendarTime(Calendar calendar, Date date) { + calendar.setTime(date); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + } + + private void setCalendarToTomorrow(Calendar calendar) { + calendar.setTime(new Date()); + } + private void planeTime(SzPlan szPlan, List contents) { if (CollectionUtils.isEmpty(contents)) { diff --git a/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzYhPlanService.java b/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzYhPlanService.java index ff3c6945..bbc19b97 100644 --- a/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzYhPlanService.java +++ b/shuili-system/src/main/java/com/kms/yxgh/sz/service/SzYhPlanService.java @@ -6,13 +6,14 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.kms.system.service.SysUserService; -import com.kms.yg.sz.service.BsSgcSzSafeJbxxService; import com.kms.yxgh.base.DfException; import com.kms.yxgh.base.SzException; import com.kms.yxgh.common.ApprovalStatusEnum; +import com.kms.yxgh.common.domain.JobTask; import com.kms.yxgh.common.dto.*; +import com.kms.yxgh.common.job.handler.SendMessageType; import com.kms.yxgh.common.service.DefaultApprovalBusinessService; +import com.kms.yxgh.common.service.JobTaskService; import com.kms.yxgh.sz.domain.SzYhPlan; import com.kms.yxgh.sz.dto.SzYhPlanDetailDto; import com.kms.yxgh.sz.dto.SzYhPlanSearchDto; @@ -35,8 +36,7 @@ import java.util.List; @Service @AllArgsConstructor public class SzYhPlanService extends DefaultApprovalBusinessService { - private final BsSgcSzSafeJbxxService bsSgcSzSafeJbxxService; - private final SysUserService sysUserService; + private final JobTaskService jobTaskService; public SzYhPlanDetailDto getDetailById(String id) { SzYhPlan szPlan = this.getById(id); @@ -130,12 +130,21 @@ public class SzYhPlanService extends DefaultApprovalBusinessService wp = Wrappers.lambdaUpdate() .eq(SzYhPlan::getId, formId) .set(SzYhPlan::getStatus, status.getValue()); getBaseMapper().update(null, wp); + + SzYhPlan szPlan = this.getBaseMapper().selectById(formId); + + JobTask jobTask = new JobTask(); + jobTask.setBusinessId(formId); + jobTask.setBusinessType(SendMessageType.SZ_YH_PLAN.name()); + jobTask.setNextStartTime(szPlan.getReminderTime()); + jobTaskService.updateOrInsertJobTask(jobTask, !ApprovalStatusEnum.PASS.equals(status)); } @Override diff --git a/sql/sy/v1.4.0/全量脚本/v1.4.0-all.sql b/sql/sy/v1.4.0/全量脚本/v1.4.0-all.sql index 8c8fadf7..46ece23b 100644 --- a/sql/sy/v1.4.0/全量脚本/v1.4.0-all.sql +++ b/sql/sy/v1.4.0/全量脚本/v1.4.0-all.sql @@ -1103,6 +1103,24 @@ CREATE TABLE `bs_sgc_sz_bxgcnrgl` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='水闸病险工程项目任务关联表'; +CREATE TABLE `bs_sgc_job_task`( + `ID` int NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `NAME` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `BUSINESS_TYPE` int NOT NULL COMMENT '业务类型', + `BUSINESS_Id` int NOT NULL COMMENT '业务ID', + `STATUS` int NOT NULL COMMENT '任务状态', + `PARAMS` text COLLATE utf8mb4_general_ci COMMENT '任务参数', + `LAST_START_TIME` datetime DEFAULT NULL COMMENT '上次开始时间', + `LAST_END_TIME` datetime DEFAULT NULL COMMENT '上次结束时间', + `NEXT_START_TIME` datetime DEFAULT NULL COMMENT '下次开始时间', + `TASK_END_TIME` datetime DEFAULT NULL COMMENT '任务结束时间', + `CREATE_UID` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', + `CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间', + `UPDATE_UID` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最近修改人', + `UPDATE_TIME` datetime DEFAULT NULL COMMENT '最近修改时间', + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务表'; + CREATE OR REPLACE VIEW `v_bs_sgc_dtsbr` AS SELECT df.`ID`, df.`DIKE_CODE` AS `CODE`,gc.`DIKE_NAME` AS `NAME`,'1' AS `TYPE`,df.`STATUS` AS `STATUS`,df.`DEVICE_ID`,df.`DEVICE_NAME`,gc.`ADCD` AS ADCD, df.`WARNING_TYPE` AS `WARNING_TYPE`, df.`WARNING_LEVEL` AS `WARNING_LEVEL`, df.`WARNING_TIME` AS `WARNING_TIME` FROM `bs_sgc_df_dtsbr` AS df diff --git a/sql/sy/v1.4.0/增量脚本/v1.4.0-update.sql b/sql/sy/v1.4.0/增量脚本/v1.4.0-update.sql index 84b18548..34b86bb8 100644 --- a/sql/sy/v1.4.0/增量脚本/v1.4.0-update.sql +++ b/sql/sy/v1.4.0/增量脚本/v1.4.0-update.sql @@ -151,17 +151,20 @@ CREATE TABLE `bs_sgc_sz_xsjhxq` ( CREATE TABLE `bs_sgc_job_task`( `ID` int NOT NULL AUTO_INCREMENT COMMENT '任务ID', `NAME` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', - `TYPE` int NOT NULL COMMENT '任务类型', + `BUSINESS_TYPE` int NOT NULL COMMENT '业务类型', + `BUSINESS_Id` int NOT NULL COMMENT '业务ID', `STATUS` int NOT NULL COMMENT '任务状态', - `START_DATE` datetime DEFAULT NULL COMMENT '开始时间', - `END_DATE` datetime DEFAULT NULL COMMENT '结束时间', - `REMINDER_TIME` datetime DEFAULT NULL COMMENT '提醒时间', + `PARAMS` text COLLATE utf8mb4_general_ci COMMENT '任务参数', + `LAST_START_TIME` datetime DEFAULT NULL COMMENT '上次开始时间', + `LAST_END_TIME` datetime DEFAULT NULL COMMENT '上次结束时间', + `NEXT_START_TIME` datetime DEFAULT NULL COMMENT '下次开始时间', + `TASK_END_TIME` datetime DEFAULT NULL COMMENT '任务结束时间', `CREATE_UID` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', `CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_UID` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最近修改人', `UPDATE_TIME` datetime DEFAULT NULL COMMENT '最近修改时间', PRIMARY KEY (`ID`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='任务表'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务表'; delete from sys_menu where id in (