diff --git a/pom.xml b/pom.xml index 4365dae..7f3882c 100644 --- a/pom.xml +++ b/pom.xml @@ -197,6 +197,10 @@ spring-boot-starter-data-redis + + org.springframework.boot + spring-boot-starter-quartz + redis.clients diff --git a/src/main/java/com/subsidy/common/configure/JobFactory.java b/src/main/java/com/subsidy/common/configure/JobFactory.java new file mode 100644 index 0000000..9a0bdcb --- /dev/null +++ b/src/main/java/com/subsidy/common/configure/JobFactory.java @@ -0,0 +1,31 @@ +package com.subsidy.common.configure; + +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.scheduling.quartz.AdaptableJobFactory; +import org.springframework.stereotype.Component; + +/** + *

+ * JobFactory实例 + *

+ * + * @author DengMin + * @since 2020/12/9 + */ +@Component +public class JobFactory extends AdaptableJobFactory { + + private AutowireCapableBeanFactory factory; + + public JobFactory(AutowireCapableBeanFactory factory) { + this.factory = factory; + } + + @Override + protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { + Object job = super.createJobInstance(bundle); + factory.autowireBean(job); + return job; + } +} diff --git a/src/main/java/com/subsidy/common/configure/QuartzConfig.java b/src/main/java/com/subsidy/common/configure/QuartzConfig.java new file mode 100644 index 0000000..cdb7008 --- /dev/null +++ b/src/main/java/com/subsidy/common/configure/QuartzConfig.java @@ -0,0 +1,45 @@ +package com.subsidy.common.configure; + +import org.quartz.Scheduler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import javax.sql.DataSource; + +/** + *

+ * Quartz配置 + *

+ * + * @author DengMin + * @since 2020/12/8 + */ +@Configuration +public class QuartzConfig { + + @Autowired + private JobFactory jobFactory; + + @Autowired + private DataSource dataSource; + + @Bean + public SchedulerFactoryBean schedulerFactoryBean() { + SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); + schedulerFactoryBean.setOverwriteExistingJobs(true); + schedulerFactoryBean.setStartupDelay(60); + schedulerFactoryBean.setJobFactory(jobFactory); + schedulerFactoryBean.setOverwriteExistingJobs(true); + schedulerFactoryBean.setStartupDelay(1); + schedulerFactoryBean.setDataSource(dataSource); + schedulerFactoryBean.setConfigLocation(new ClassPathResource("/application-quartz.properties")); + return schedulerFactoryBean; + } + + @Bean + public Scheduler scheduler() { + return schedulerFactoryBean().getScheduler(); + } +} diff --git a/src/main/java/com/subsidy/common/constant/CourseNotification.java b/src/main/java/com/subsidy/common/constant/CourseNotification.java new file mode 100644 index 0000000..449a57a --- /dev/null +++ b/src/main/java/com/subsidy/common/constant/CourseNotification.java @@ -0,0 +1,14 @@ +package com.subsidy.common.constant; + +public class CourseNotification { + + public static final String UNSENT = "待发送"; + + public static final String SENT = "已发送"; + + public static final String ALL = "全部成员"; + + public static final String NOT_SIGNED_IN = "未签到成员"; + + public static final String UNFINISHED = "未完课成员"; +} diff --git a/src/main/java/com/subsidy/controller/ClassNoticeController.java b/src/main/java/com/subsidy/controller/ClassNoticeController.java index bbe911f..8097ff0 100644 --- a/src/main/java/com/subsidy/controller/ClassNoticeController.java +++ b/src/main/java/com/subsidy/controller/ClassNoticeController.java @@ -3,6 +3,7 @@ package com.subsidy.controller; import com.subsidy.common.ResponseData; import com.subsidy.common.ResponseVO; +import com.subsidy.dto.classNotice.SendNotificationDTO; import com.subsidy.model.ClassNoticeDO; import com.subsidy.service.ClassNoticeService; import io.swagger.annotations.ApiOperation; @@ -23,7 +24,7 @@ import io.swagger.annotations.Api; * @since 2022-01-21 */ @RestController -@Api(tags = "") +@Api(tags = "课程通知") @RequestMapping("/classNotice") public class ClassNoticeController { @@ -31,7 +32,7 @@ public class ClassNoticeController { private ClassNoticeService classNoticeService; @PostMapping("addNotice") - @ApiOperation("新增一个通知 classId noticeType isAuto noticeTime") + @ApiOperation("新增一个通知 classId noticeType isAuto noticeTime") public ResponseVO addNotice(@RequestBody ClassNoticeDO classNoticeDO){ return ResponseData.generateCreatedResponse(0,classNoticeService.addNotice(classNoticeDO)); } @@ -49,12 +50,15 @@ public class ClassNoticeController { } @PostMapping("queryClassNotices") - @ApiOperation("查看某个课程的通知提醒 classId 课程id") + @ApiOperation("查看某个课程的通知提醒 classId 课程id status 状态(待发送/已发送)") public ResponseVO queryClassNotices(ClassNoticeDO classNoticeDO){ return ResponseData.generateCreatedResponse(0,classNoticeService.queryClassNotices(classNoticeDO)); } - - - + @PostMapping("sendClassNotices") + @ApiOperation("发送通知:classId/课程Id sendType/发送类型(全部成员/未签到成员/未完课成员)") + public ResponseVO sendClassNotices(@RequestBody SendNotificationDTO sendNotificationDTO) { + classNoticeService.sendNotification(sendNotificationDTO); + return ResponseData.generateCreatedResponse(0); + } } diff --git a/src/main/java/com/subsidy/dto/classNotice/SendNotificationDTO.java b/src/main/java/com/subsidy/dto/classNotice/SendNotificationDTO.java new file mode 100644 index 0000000..bfa506d --- /dev/null +++ b/src/main/java/com/subsidy/dto/classNotice/SendNotificationDTO.java @@ -0,0 +1,11 @@ +package com.subsidy.dto.classNotice; + +import lombok.Data; + +@Data +public class SendNotificationDTO { + + private Long classId; + + private String sendType; +} diff --git a/src/main/java/com/subsidy/jobs/CourseNotificationJob.java b/src/main/java/com/subsidy/jobs/CourseNotificationJob.java new file mode 100644 index 0000000..3388b6b --- /dev/null +++ b/src/main/java/com/subsidy/jobs/CourseNotificationJob.java @@ -0,0 +1,58 @@ +package com.subsidy.jobs; + +import com.subsidy.common.constant.CourseNotification; +import com.subsidy.mapper.ClassNoticeMapper; +import com.subsidy.mapper.MemberMapper; +import com.subsidy.model.ClassNoticeDO; +import com.subsidy.model.MemberDO; +import com.subsidy.util.SMSUtils; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 课程通知 + *

+ * + * @author DengMin + * @since 2022/2/14 + */ +@Component +public class CourseNotificationJob implements Job { + + @Autowired + private MemberMapper memberMapper; + + @Autowired + private ClassNoticeMapper classNoticeMapper; + + @Autowired + private SMSUtils smsUtils; + + @Override + public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { + Map map = jobExecutionContext.getJobDetail().getJobDataMap(); + Map params = (Map) map.get("params"); + if(params != null) { + List list = memberMapper.getMemberList((Long) params.get("classId")); + if(list != null && list.size() > 0) { + for (MemberDO memberDO : list) { + if(memberDO != null) { + // smsUtils.send(memberDO.getTelephone(), ""); + } + } + + ClassNoticeDO classNoticeDO = new ClassNoticeDO(); + classNoticeDO.setId((Long) params.get("id")); + classNoticeDO.setStatus(CourseNotification.SENT); + classNoticeMapper.updateById(classNoticeDO); + } + } + } +} diff --git a/src/main/java/com/subsidy/jobs/Scheduler.java b/src/main/java/com/subsidy/jobs/SchedulerJob.java similarity index 99% rename from src/main/java/com/subsidy/jobs/Scheduler.java rename to src/main/java/com/subsidy/jobs/SchedulerJob.java index a685f28..157379e 100644 --- a/src/main/java/com/subsidy/jobs/Scheduler.java +++ b/src/main/java/com/subsidy/jobs/SchedulerJob.java @@ -31,7 +31,7 @@ import java.util.Set; */ @Component -public class Scheduler { +public class SchedulerJob { @Autowired private OprAdmDictMapper oprAdmDictMapper; diff --git a/src/main/java/com/subsidy/mapper/MemberMapper.java b/src/main/java/com/subsidy/mapper/MemberMapper.java index 33f39d1..8e229dc 100644 --- a/src/main/java/com/subsidy/mapper/MemberMapper.java +++ b/src/main/java/com/subsidy/mapper/MemberMapper.java @@ -55,4 +55,9 @@ public interface MemberMapper extends BaseMapper { */ IPage manageMember(IPage iPage,Long companyId,String userName); + List getMemberList(Long classId); + + List getMemberListBySignInRecord(Long classId); + + List getUnfinishedMemberList(Long classId); } diff --git a/src/main/java/com/subsidy/service/ClassNoticeService.java b/src/main/java/com/subsidy/service/ClassNoticeService.java index 98d24cf..58f800c 100644 --- a/src/main/java/com/subsidy/service/ClassNoticeService.java +++ b/src/main/java/com/subsidy/service/ClassNoticeService.java @@ -1,5 +1,6 @@ package com.subsidy.service; +import com.subsidy.dto.classNotice.SendNotificationDTO; import com.subsidy.model.ClassNoticeDO; import com.baomidou.mybatisplus.extension.service.IService; @@ -22,4 +23,6 @@ public interface ClassNoticeService extends IService { String deleteNotice(ClassNoticeDO classNoticeDO); List queryClassNotices(ClassNoticeDO classNoticeDO); + + void sendNotification(SendNotificationDTO sendNotificationDTO); } diff --git a/src/main/java/com/subsidy/service/impl/ClassNoticeServiceImpl.java b/src/main/java/com/subsidy/service/impl/ClassNoticeServiceImpl.java index 9391ae3..c8631e4 100644 --- a/src/main/java/com/subsidy/service/impl/ClassNoticeServiceImpl.java +++ b/src/main/java/com/subsidy/service/impl/ClassNoticeServiceImpl.java @@ -1,14 +1,29 @@ package com.subsidy.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.subsidy.common.constant.CourseNotification; +import com.subsidy.common.exception.HttpException; +import com.subsidy.dto.classNotice.SendNotificationDTO; +import com.subsidy.jobs.CourseNotificationJob; +import com.subsidy.mapper.MemberMapper; +import com.subsidy.model.ClassDictDO; import com.subsidy.model.ClassNoticeDO; import com.subsidy.mapper.ClassNoticeMapper; +import com.subsidy.model.MemberDO; +import com.subsidy.service.ClassDictService; import com.subsidy.service.ClassNoticeService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.subsidy.util.ConstantUtils; +import com.subsidy.util.DateFormatUtil; +import com.subsidy.util.QuartzUtil; +import com.subsidy.util.SMSUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; - +import org.springframework.transaction.annotation.Transactional; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** *

@@ -21,8 +36,34 @@ import java.util.List; @Service public class ClassNoticeServiceImpl extends ServiceImpl implements ClassNoticeService { + @Autowired + private QuartzUtil quartzUtil; + + @Autowired + private ClassDictService classDictService; + + @Autowired + private MemberMapper memberMapper; + + @Autowired + private SMSUtils smsUtils; + + @Transactional(rollbackFor = Exception.class) + @Override public String addNotice(ClassNoticeDO classNoticeDO) { + if(DateFormatUtil.parse(classNoticeDO.getNoticeTime(),"yyyy-MM-dd").before(new Date())) { + throw new HttpException(70001); + } + + classNoticeDO.setStatus(CourseNotification.UNSENT); this.baseMapper.insert(classNoticeDO); + + ClassDictDO classDictDO = classDictService.getById(classNoticeDO.getClassId()); + Map params = new HashMap<>(); + params.put("classId", classNoticeDO.getClassId()); + params.put("id", classNoticeDO.getId()); + String name = classDictDO.getClassName()+"-"+classNoticeDO.getNoticeType()+"-"+classNoticeDO.getNoticeTime(); + quartzUtil.addSimpleJob(CourseNotificationJob.class,DateFormatUtil.parse(classNoticeDO.getNoticeTime(), "yyyy-MM-dd") , params, name, "CourseNotificationJob"); return ConstantUtils.ADD_SUCCESS; } @@ -31,15 +72,48 @@ public class ClassNoticeServiceImpl extends ServiceImpl queryClassNotices(ClassNoticeDO classNoticeDO){ return this.baseMapper.selectList(new QueryWrapper() .lambda() + .eq(ClassNoticeDO::getStatus, classNoticeDO.getStatus()) .eq(ClassNoticeDO::getClassId,classNoticeDO.getClassId())); } + @Override + public void sendNotification(SendNotificationDTO sendNotificationDTO) { + if(sendNotificationDTO.getSendType().equals(CourseNotification.ALL)) { + List list = memberMapper.getMemberList(sendNotificationDTO.getClassId()); + if(list != null) { + for (MemberDO memberDO : list) { + smsUtils.send(memberDO.getTelephone(), ""); + } + } + } else if(sendNotificationDTO.getSendType().equals(CourseNotification.NOT_SIGNED_IN)) { + List list = memberMapper.getMemberListBySignInRecord(sendNotificationDTO.getClassId()); + if(list != null) { + for (MemberDO memberDO : list) { + smsUtils.send(memberDO.getTelephone(), ""); + } + } + } else if(sendNotificationDTO.getSendType().equals(CourseNotification.UNFINISHED)) { + List list = memberMapper.getUnfinishedMemberList(sendNotificationDTO.getClassId()); + if(list != null) { + for (MemberDO memberDO : list) { + smsUtils.send(memberDO.getTelephone(), ""); + } + } + } + } } diff --git a/src/main/java/com/subsidy/util/QuartzUtil.java b/src/main/java/com/subsidy/util/QuartzUtil.java new file mode 100644 index 0000000..d60aeca --- /dev/null +++ b/src/main/java/com/subsidy/util/QuartzUtil.java @@ -0,0 +1,119 @@ +package com.subsidy.util; + +import com.subsidy.common.exception.HttpException; +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.DateBuilder; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SimpleTrigger; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static org.quartz.DateBuilder.futureDate; + +@Component +public class QuartzUtil { + + @Autowired + private Scheduler scheduler; + + /** + * 添加定时任务,只执行一次的定时任务 + * + * @param cls 执行类 + * @param date 时间 + * @param params 参数 + * @param name 定时器名称 + * @param group 定时器组名 + */ + public void addSimpleJob(Class cls, Date date, Map params, String name, String group) { + try { + JobKey key = new JobKey(name, group); + List triggers = (List) scheduler.getTriggersOfJob(key); + if (triggers.size() == 0) { + int time = (int) (date.getTime() - System.currentTimeMillis()) / 1000; + JobDataMap jobDataMap = new JobDataMap(); + jobDataMap.put("params", params); + JobDetail jobDetail = JobBuilder.newJob(cls) + .withIdentity(name, group) + .usingJobData(jobDataMap) + .build(); + SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() + .withIdentity(name, group) + .startAt(futureDate(time, DateBuilder.IntervalUnit.SECOND)) + .build(); + scheduler.scheduleJob(jobDetail, trigger); + if (!scheduler.isShutdown()) { + scheduler.start(); + } + } + } catch (Exception e) { + throw new HttpException(70002); + } + } + + /** + * 添加定时任务,循环不断执行的定时任务 + * + * @param cls 执行类 + * @param cron cron 表达式 + * @param params 参数 + * @param name 定时器名称 + * @param group 定时器组名 + */ + public void addCronJob(Class cls, String cron, Map params, String name, String group) { + try { + JobKey key = new JobKey(name, group); + List triggers = (List) scheduler.getTriggersOfJob(key); + if (triggers.size() == 0) { + JobDataMap jobDataMap = new JobDataMap(); + jobDataMap.put("params", params); + JobDetail jobDetail = JobBuilder.newJob(cls) + .withIdentity(name, group) + .usingJobData(jobDataMap) + .build(); + CronTrigger trigger = TriggerBuilder.newTrigger() + .withIdentity(name, group) + .withSchedule(CronScheduleBuilder.cronSchedule(cron)) + .build(); + scheduler.scheduleJob(jobDetail, trigger); + if (!scheduler.isShutdown()) { + scheduler.start(); + } + } + } catch (Exception e) { + throw new HttpException(70002); + } + } + + /** + * 删除定时器 + * + * @param name 定时器名称 + * @param group 定时器组名 + */ + public void deleteJob(String name, String group) { + try { + JobKey key = new JobKey(name, group); + List triggers = (List) scheduler.getTriggersOfJob(key); + if (triggers.size() > 0) { + TriggerKey triggerKey = TriggerKey.triggerKey(name, group); + scheduler.pauseTrigger(triggerKey); + scheduler.unscheduleJob(triggerKey); + scheduler.deleteJob(JobKey.jobKey(name, group)); + } + } catch (Exception e) { + throw new HttpException(70003); + } + } +} \ No newline at end of file diff --git a/src/main/resources/application-quartz.properties b/src/main/resources/application-quartz.properties new file mode 100755 index 0000000..0d1f6b6 --- /dev/null +++ b/src/main/resources/application-quartz.properties @@ -0,0 +1,7 @@ +spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX +spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ +spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool +spring.quartz.properties.org.quartz.threadPool.threadCount=15 +spring.quartz.properties.org.quartz.threadPool.threadPriority=5 +spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true \ No newline at end of file diff --git a/src/main/resources/code.properties b/src/main/resources/code.properties index 6e59202..abbb579 100644 --- a/src/main/resources/code.properties +++ b/src/main/resources/code.properties @@ -44,4 +44,8 @@ meishu.code-message[10001]=导入失败 meishu.code-message[11001]=该行业已存在 +meishu.code-message[70001]=添加通知失败,通知时间不能小于今天时间 +meishu.code-message[70002]=定时任务创建失败 +meishu.code-message[70003]=定时任务删除失败 + meishu.code-message[12001]=该职级已存在 diff --git a/src/main/resources/mapper/MemberMapper.xml b/src/main/resources/mapper/MemberMapper.xml index 1a7fcc0..754148c 100644 --- a/src/main/resources/mapper/MemberMapper.xml +++ b/src/main/resources/mapper/MemberMapper.xml @@ -206,4 +206,60 @@ + + + + +