diff --git a/README.md b/README.md index 9a0e981..19cd5d9 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co - [x] 接入实时流计算平台(Flink),实时日志数据根据用户维度和消息模板维度清洗至Redis - [x] 通过AMIS低代码平台接入echarts图表展示实时聚合后的数据 - [x] 优雅停机、动态线程池参数配置 -- [ ] 企业微信渠道接入 +- [x] 企业微信渠道接入 - [ ] 钉钉渠道接入 - [ ] 优化代码 - [ ] 接入微信服务号渠道 @@ -123,9 +123,9 @@ curl -XPOST "127.0.0.1:8080/send" -H 'Content-Type: application/json' -d '{"co -**近期更新时间**:2022年3月15日 +**近期更新时间**:2022年3月22日 -**近期更新功能**:企业微信渠道接入(未完成) +**近期更新功能**:夜间屏蔽次日推送消息(未完成) ## 项目交流 diff --git a/austin-common/src/main/java/com/java3y/austin/common/domain/TaskInfo.java b/austin-common/src/main/java/com/java3y/austin/common/domain/TaskInfo.java index 68cf4f4..9001d6c 100644 --- a/austin-common/src/main/java/com/java3y/austin/common/domain/TaskInfo.java +++ b/austin-common/src/main/java/com/java3y/austin/common/domain/TaskInfo.java @@ -55,6 +55,11 @@ public class TaskInfo { */ private Integer msgType; + /** + * 屏蔽类型 + */ + private Integer shieldType; + /** * 发送文案模型 * message_template表存储的content是JSON(所有内容都会塞进去) diff --git a/austin-common/src/main/java/com/java3y/austin/common/enums/ShieldType.java b/austin-common/src/main/java/com/java3y/austin/common/enums/ShieldType.java new file mode 100644 index 0000000..ea00e3a --- /dev/null +++ b/austin-common/src/main/java/com/java3y/austin/common/enums/ShieldType.java @@ -0,0 +1,28 @@ +package com.java3y.austin.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * 屏蔽类型 + * + * @author 3y + */ +@Getter +@ToString +@AllArgsConstructor +public enum ShieldType { + + + NIGHT_NO_SHIELD(10, "夜间不屏蔽"), + NIGHT_SHIELD(20, "夜间屏蔽"), + NIGHT_SHIELD_BUT_NEXT_DAY_SEND(30, "夜间屏蔽(次日早上9点发送)"); + + private Integer code; + private String description; + + + + +} diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/handler/CronTaskHandler.java b/austin-cron/src/main/java/com/java3y/austin/cron/handler/CronTaskHandler.java index c3ec149..f824375 100644 --- a/austin-cron/src/main/java/com/java3y/austin/cron/handler/CronTaskHandler.java +++ b/austin-cron/src/main/java/com/java3y/austin/cron/handler/CronTaskHandler.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Service; /** - * 定时任务处理类 + * 后台提交的定时任务处理类 * @author 3y */ @Service @@ -27,7 +27,7 @@ public class CronTaskHandler { private DtpExecutor dtpExecutor = CronAsyncThreadPoolConfig.getXxlCronExecutor(); /** - * 处理所有的 austin 定时任务消息 + * 处理后台的 austin 定时任务消息 */ @XxlJob("austinJob") public void execute() { diff --git a/austin-cron/src/main/java/com/java3y/austin/cron/handler/NightShieldLazyPendingHandler.java b/austin-cron/src/main/java/com/java3y/austin/cron/handler/NightShieldLazyPendingHandler.java new file mode 100644 index 0000000..a52fa81 --- /dev/null +++ b/austin-cron/src/main/java/com/java3y/austin/cron/handler/NightShieldLazyPendingHandler.java @@ -0,0 +1,54 @@ +package com.java3y.austin.cron.handler; + +import cn.hutool.core.util.StrUtil; +import com.google.common.base.Throwables; +import com.java3y.austin.support.config.SupportThreadPoolConfig; +import com.java3y.austin.support.utils.KafkaUtils; +import com.java3y.austin.support.utils.RedisUtils; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + + +/** + * 夜间屏蔽的延迟处理类 + * + * example:当消息下发至austin平台时,已经是凌晨1点,业务希望此类消息在次日的早上9点推送 + * + * @author 3y + */ +@Service +@Slf4j +public class NightShieldLazyPendingHandler { + + private static final String NIGHT_SHIELD_BUT_NEXT_DAY_SEND_KEY = "night_shield_send"; + + @Autowired + private KafkaUtils kafkaUtils; + @Value("${austin.business.topic.name}") + private String topicName; + @Autowired + private RedisUtils redisUtils; + + /** + * 处理 夜间屏蔽(次日早上9点发送的任务) + */ + @XxlJob("nightShieldLazyJob") + public void execute() { + log.info("NightShieldLazyPendingHandler#execute!"); + SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> { + while (redisUtils.lLen(NIGHT_SHIELD_BUT_NEXT_DAY_SEND_KEY) > 0) { + String taskInfo = redisUtils.lPop(NIGHT_SHIELD_BUT_NEXT_DAY_SEND_KEY); + if (StrUtil.isNotBlank(taskInfo)) { + try { + kafkaUtils.send(topicName, taskInfo); + } catch (Exception e) { + log.error("nightShieldLazyJob send kafka fail! e:{},params:{}", Throwables.getStackTraceAsString(e), taskInfo); + } + } + } + }); + } +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/pending/Task.java b/austin-handler/src/main/java/com/java3y/austin/handler/pending/Task.java index 04b5eb9..9190040 100644 --- a/austin-handler/src/main/java/com/java3y/austin/handler/pending/Task.java +++ b/austin-handler/src/main/java/com/java3y/austin/handler/pending/Task.java @@ -6,6 +6,7 @@ import com.java3y.austin.common.domain.TaskInfo; import com.java3y.austin.handler.deduplication.DeduplicationRuleService; import com.java3y.austin.handler.discard.DiscardMessageService; import com.java3y.austin.handler.handler.HandlerHolder; +import com.java3y.austin.handler.shield.ShieldService; import lombok.Data; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; @@ -38,6 +39,9 @@ public class Task implements Runnable { @Autowired private DiscardMessageService discardMessageService; + @Autowired + private ShieldService shieldService; + private TaskInfo taskInfo; @@ -48,14 +52,17 @@ public class Task implements Runnable { if (discardMessageService.isDiscard(taskInfo)) { return; } + // 1. 屏蔽消息 + shieldService.shield(taskInfo); - // 1.平台通用去重 - deduplicationRuleService.duplication(taskInfo); + // 2.平台通用去重 + if (CollUtil.isNotEmpty(taskInfo.getReceiver())) { + deduplicationRuleService.duplication(taskInfo); + } - // 2. 真正发送消息 + // 3. 真正发送消息 if (CollUtil.isNotEmpty(taskInfo.getReceiver())) { - handlerHolder.route(taskInfo.getSendChannel()) - .doHandler(taskInfo); + handlerHolder.route(taskInfo.getSendChannel()).doHandler(taskInfo); } } diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/shield/ShieldService.java b/austin-handler/src/main/java/com/java3y/austin/handler/shield/ShieldService.java new file mode 100644 index 0000000..f9f7b39 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/shield/ShieldService.java @@ -0,0 +1,14 @@ +package com.java3y.austin.handler.shield; + +import com.java3y.austin.common.domain.TaskInfo; + +/** + * 屏蔽服务 + * + * @author 3y + */ +public interface ShieldService { + + + void shield(TaskInfo taskInfo); +} diff --git a/austin-handler/src/main/java/com/java3y/austin/handler/shield/impl/ShieldServiceImpl.java b/austin-handler/src/main/java/com/java3y/austin/handler/shield/impl/ShieldServiceImpl.java new file mode 100644 index 0000000..665f945 --- /dev/null +++ b/austin-handler/src/main/java/com/java3y/austin/handler/shield/impl/ShieldServiceImpl.java @@ -0,0 +1,63 @@ +package com.java3y.austin.handler.shield.impl; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSON; +import com.java3y.austin.common.domain.TaskInfo; +import com.java3y.austin.common.enums.ShieldType; +import com.java3y.austin.handler.shield.ShieldService; +import com.java3y.austin.support.utils.RedisUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.HashSet; + +/** + * 屏蔽服务 + */ +@Service +@Slf4j +public class ShieldServiceImpl implements ShieldService { + + private static final String NIGHT_SHIELD_BUT_NEXT_DAY_SEND_KEY = "night_shield_send"; + @Autowired + private RedisUtils redisUtils; + + @Override + public void shield(TaskInfo taskInfo) { + + /** + * example:当消息下发至austin平台时,已经是凌晨1点,业务希望此类消息在次日的早上9点推送 + * (配合 分布式任务定时任务框架搞掂) + */ + if (isNight() && isNightShieldType(taskInfo.getShieldType())) { + if (ShieldType.NIGHT_SHIELD_BUT_NEXT_DAY_SEND.getCode().equals(taskInfo.getShieldType())) { + redisUtils.lPush(NIGHT_SHIELD_BUT_NEXT_DAY_SEND_KEY, JSON.toJSONString(taskInfo), (DateUtil.offsetDay(new Date(), 1).getTime()) / 1000); + } + taskInfo.setReceiver(new HashSet<>()); + } + } + + + /** + * 根据code判断是否为夜间屏蔽类型 + */ + private boolean isNightShieldType(Integer code) { + if (ShieldType.NIGHT_SHIELD.getCode().equals(code) + || ShieldType.NIGHT_SHIELD_BUT_NEXT_DAY_SEND.getCode().equals(code)) { + return true; + } + return false; + } + + /** + * 小时 < 8 默认就认为是凌晨(夜晚) + * @return + */ + private boolean isNight() { + return Integer.valueOf(DateFormatUtils.format(new Date(), "HH")) < 8; + } + +} diff --git a/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java b/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java index f9ac5aa..9f7da3e 100644 --- a/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java +++ b/austin-service-api-impl/src/main/java/com/java3y/austin/service/api/impl/action/AssembleAction.java @@ -77,6 +77,7 @@ public class AssembleAction implements BusinessProcess { .sendChannel(messageTemplate.getSendChannel()) .templateType(messageTemplate.getTemplateType()) .msgType(messageTemplate.getMsgType()) + .shieldType(messageTemplate.getShieldType()) .sendAccount(messageTemplate.getSendAccount()) .contentModel(getContentModelValue(messageTemplate, messageParam)).build(); diff --git a/austin-support/src/main/java/com/java3y/austin/support/domain/MessageTemplate.java b/austin-support/src/main/java/com/java3y/austin/support/domain/MessageTemplate.java index 207199d..21d2c85 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/domain/MessageTemplate.java +++ b/austin-support/src/main/java/com/java3y/austin/support/domain/MessageTemplate.java @@ -75,6 +75,11 @@ public class MessageTemplate implements Serializable { */ private Integer templateType; + /** + * 屏蔽类型 + */ + private Integer shieldType; + /** * 消息类型 */ diff --git a/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java b/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java index 8247cae..9d7f905 100644 --- a/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java +++ b/austin-support/src/main/java/com/java3y/austin/support/utils/RedisUtils.java @@ -90,6 +90,48 @@ public class RedisUtils { } } + + /** + * lpush 方法 并指定 过期时间 + * + */ + public void lPush(String key, String value, Long seconds) { + try { + redisTemplate.executePipelined((RedisCallback) connection -> { + connection.lPush(key.getBytes(), value.getBytes()); + connection.expire(key.getBytes(), seconds); + return null; + }); + } catch (Exception e) { + log.error("RedisUtils#pipelineSetEx fail! e:{}", Throwables.getStackTraceAsString(e)); + } + } + + /** + * lLen 方法 + * + */ + public Long lLen(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + log.error("RedisUtils#pipelineSetEx fail! e:{}", Throwables.getStackTraceAsString(e)); + } + return 0L; + } + /** + * lPop 方法 + * + */ + public String lPop(String key) { + try { + return redisTemplate.opsForList().leftPop(key); + } catch (Exception e) { + log.error("RedisUtils#pipelineSetEx fail! e:{}", Throwables.getStackTraceAsString(e)); + } + return ""; + } + /** * pipeline 设置 key-value 并设置过期时间 * diff --git a/pom.xml b/pom.xml index 2f2083e..1005812 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ ${target.java.version} ${target.java.version} 2.17.1 - 4.1.0 + 4.1.0 @@ -157,7 +157,7 @@ com.github.binarywang weixin-java-mp - ${weixin-java-mp} + ${weixin-java} @@ -171,7 +171,7 @@ com.github.binarywang weixin-java-cp - ${weixin-java-mp} + ${weixin-java} diff --git a/sql/austin.sql b/sql/austin.sql index 638e4cb..ab18a58 100644 --- a/sql/austin.sql +++ b/sql/austin.sql @@ -17,6 +17,7 @@ CREATE TABLE `message_template` `send_channel` tinyint(4) NOT NULL DEFAULT '0' COMMENT '消息发送渠道:10.IM 20.Push 30.短信 40.Email 50.公众号 60.小程序 70.企业微信', `template_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '10.运营类 20.技术类接口调用', `msg_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '10.通知类消息 20.营销类消息 30.验证码类消息', + `shield_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '10.夜间不屏蔽 20.夜间屏蔽 30.夜间屏蔽(次日早上9点发送)', `msg_content` varchar(600) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '消息内容 占位符用{$var}表示', `send_account` tinyint(4) NOT NULL DEFAULT '0' COMMENT '发送账号 一个渠道下可存在多个账号', `creator` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建者',