Commit d16f873521d24892f1148cfa9b43f01e94514e04

Authored by xrcoder
1 parent bb78a580

站内信模板功能完善。

yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
... ... @@ -144,6 +144,7 @@ public interface ErrorCodeConstants {
144 144  
145 145 // ========== 站内信模版 1002023000 ==========
146 146 ErrorCode NOTIFY_TEMPLATE_NOT_EXISTS = new ErrorCode(1002023000, "站内信模版不存在");
  147 + ErrorCode NOTIFY_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002023001, "已经存在编码为【{}】的站内信模板");
147 148  
148 149 // ========== 站内信 1002024000 ==========
149 150 ErrorCode NOTIFY_MESSAGE_NOT_EXISTS = new ErrorCode(1002024000, "站内信不存在");
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notify/vo/template/NotifyTemplateRespVO.java
... ... @@ -13,6 +13,9 @@ public class NotifyTemplateRespVO extends NotifyTemplateBaseVO {
13 13 @ApiModelProperty(value = "ID", required = true)
14 14 private Long id;
15 15  
  16 + @ApiModelProperty(value = "参数数组", example = "name,code")
  17 + private List<String> params;
  18 +
16 19 @ApiModelProperty(value = "创建时间", required = true)
17 20 private Date createTime;
18 21  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/notify/NotifyTemplateDO.java
1 1 package cn.iocoder.yudao.module.system.dal.dataobject.notify;
2 2  
3   -import lombok.*;
4   -import java.util.*;
5   -import com.baomidou.mybatisplus.annotation.*;
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  10 +import lombok.*;
  11 +
  12 +import java.util.List;
7 13  
8 14 /**
9 15 * 站内信模版 DO
10 16 *
11 17 * @author xrcoder
12 18 */
13   -@TableName("system_notify_template")
  19 +@TableName(value = "system_notify_template", autoResultMap = true)
14 20 @KeySequence("system_notify_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
15 21 @Data
16 22 @EqualsAndHashCode(callSuper = true)
... ... @@ -25,28 +31,35 @@ public class NotifyTemplateDO extends BaseDO {
25 31 */
26 32 @TableId
27 33 private Long id;
  34 +
28 35 /**
29 36 * 模版编码
30 37 */
31 38 private String code;
  39 +
32 40 /**
33 41 * 模版标题
34 42 */
35 43 private String title;
  44 +
36 45 /**
37 46 * 模版内容
38 47 */
39 48 private String content;
  49 +
40 50 /**
41 51 * 参数数组
42 52 */
43   - private String params;
  53 + @TableField(typeHandler = JacksonTypeHandler.class)
  54 + private List<String> params;
  55 +
44 56 /**
45 57 * 状态:1-启用 0-禁用
46   - *
47   - * 枚举 {@link TODO common_status 对应的类}
  58 + * <p>
  59 + * 枚举 {@link CommonStatusEnum}
48 60 */
49 61 private String status;
  62 +
50 63 /**
51 64 * 备注
52 65 */
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/notify/NotifyTemplateMapper.java
... ... @@ -8,7 +8,9 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
8 8 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateExportReqVO;
9 9 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
11 12 import org.apache.ibatis.annotations.Mapper;
  13 +import org.apache.ibatis.annotations.Select;
12 14  
13 15 /**
14 16 * 站内信模版 Mapper
... ... @@ -18,6 +20,13 @@ import org.apache.ibatis.annotations.Mapper;
18 20 @Mapper
19 21 public interface NotifyTemplateMapper extends BaseMapperX<NotifyTemplateDO> {
20 22  
  23 + @Select("SELECT COUNT(*) FROM system_notify_template WHERE update_time > #{maxUpdateTime}")
  24 + Long selectCountByUpdateTimeGt(Date maxUpdateTime);
  25 +
  26 + default NotifyTemplateDO selectByCode(String code) {
  27 + return selectOne(NotifyTemplateDO::getCode, code);
  28 + }
  29 +
21 30 default PageResult<NotifyTemplateDO> selectPage(NotifyTemplatePageReqVO reqVO) {
22 31 return selectPage(reqVO, new LambdaQueryWrapperX<NotifyTemplateDO>()
23 32 .eqIfPresent(NotifyTemplateDO::getCode, reqVO.getCode())
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/notify/NotifyTemplateRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.consumer.notify;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
  5 +import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link NotifyTemplateRefreshMessage} 的消费者
  13 + *
  14 + * @author xrcoder
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class NotifyTemplateRefreshConsumer extends AbstractChannelMessageListener<NotifyTemplateRefreshMessage> {
  19 +
  20 + @Resource
  21 + private NotifyTemplateService notifyTemplateService;
  22 +
  23 + @Override
  24 + public void onMessage(NotifyTemplateRefreshMessage message) {
  25 + log.info("[onMessage][收到 NotifyTemplate 刷新消息]");
  26 + notifyTemplateService.initLocalCache();
  27 + }
  28 +
  29 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/notify/NotifyTemplateRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.message.notify;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +/**
  8 + * 站内信模板的数据刷新 Message
  9 + *
  10 + * @author xrcoder
  11 + */
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +public class NotifyTemplateRefreshMessage extends AbstractChannelMessage {
  15 +
  16 + @Override
  17 + public String getChannel() {
  18 + return "system.notify-template.refresh";
  19 + }
  20 +
  21 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/notify/NotifyProducer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.producer.notify;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
  4 +import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * Notify 站内信相关消息的 Producer
  12 + *
  13 + * @author xrcoder
  14 + * @since 2022-08-06
  15 + */
  16 +@Slf4j
  17 +@Component
  18 +public class NotifyProducer {
  19 +
  20 + @Resource
  21 + private RedisMQTemplate redisMQTemplate;
  22 +
  23 +
  24 + /**
  25 + * 发送 {@link NotifyTemplateRefreshMessage} 消息
  26 + */
  27 + public void sendNotifyTemplateRefreshMessage() {
  28 + NotifyTemplateRefreshMessage message = new NotifyTemplateRefreshMessage();
  29 + redisMQTemplate.send(message);
  30 + }
  31 +
  32 +
  33 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateService.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.Notify
9 9 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
11 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  12 +import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
12 13  
13 14 /**
14 15 * 站内信模版 Service 接口
... ... @@ -18,6 +19,29 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
18 19 public interface NotifyTemplateService {
19 20  
20 21 /**
  22 + * 初始化站内信模板的本地缓存
  23 + */
  24 + void initLocalCache();
  25 +
  26 + /**
  27 + * 获得站内信模板,从缓存中
  28 + *
  29 + * @param code 模板编码
  30 + * @return 站内信模板
  31 + */
  32 + NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
  33 +
  34 +
  35 + /**
  36 + * 格式化站内信内容
  37 + *
  38 + * @param content 站内信模板的内容
  39 + * @param params 站内信内容的参数
  40 + * @return 格式化后的内容
  41 + */
  42 + String formatNotifyTemplateContent(String content, Map<String, Object> params);
  43 +
  44 + /**
21 45 * 创建站内信模版
22 46 *
23 47 * @param createReqVO 创建信息
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.notify;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ReUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
3 8 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateCreateReqVO;
4 9 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateExportReqVO;
5 10 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
6 11 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
  12 +import cn.iocoder.yudao.module.system.convert.notify.NotifyTemplateConvert;
  13 +import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
  14 +import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
  15 +import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
  16 +import com.google.common.annotations.VisibleForTesting;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.scheduling.annotation.Scheduled;
7 19 import org.springframework.stereotype.Service;
8   -import javax.annotation.Resource;
9 20 import org.springframework.validation.annotation.Validated;
10 21  
11   -import java.util.*;
12   -
13   -import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
14   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
15   -
16   -import cn.iocoder.yudao.module.system.convert.notify.NotifyTemplateConvert;
17   -import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
  22 +import javax.annotation.Resource;
  23 +import java.util.Collection;
  24 +import java.util.Date;
  25 +import java.util.List;
  26 +import java.util.Map;
  27 +import java.util.regex.Pattern;
18 28  
19 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
20   -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  30 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE;
  31 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
21 32  
22 33 /**
23 34 * 站内信模版 Service 实现类
... ... @@ -26,16 +37,123 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
26 37 */
27 38 @Service
28 39 @Validated
  40 +@Slf4j
29 41 public class NotifyTemplateServiceImpl implements NotifyTemplateService {
30 42  
  43 + /**
  44 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  45 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  46 + */
  47 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  48 +
  49 + /**
  50 + * 正则表达式,匹配 {} 中的变量
  51 + */
  52 + private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
  53 +
31 54 @Resource
32 55 private NotifyTemplateMapper notifyTemplateMapper;
33 56  
  57 + @Resource
  58 + private NotifyProducer notifyProducer;
  59 +
  60 + /**
  61 + * 站内信模板缓存
  62 + * key:站内信模板编码 {@link NotifyTemplateDO#getCode()}
  63 + * <p>
  64 + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
  65 + */
  66 + private volatile Map<String, NotifyTemplateDO> notifyTemplateCache;
  67 +
  68 + /**
  69 + * 缓存站内信模板的最大更新时间,用于后续的增量轮询,判断是否有更新
  70 + */
  71 + private volatile Date maxUpdateTime;
  72 +
  73 + /**
  74 + * 初始化站内信模板的本地缓存
  75 + */
  76 + @Override
  77 + public void initLocalCache() {
  78 + // 获取站内信模板列表,如果有更新
  79 + List<NotifyTemplateDO> notifyTemplateList = this.loadNotifyTemplateIfUpdate(maxUpdateTime);
  80 + if (CollUtil.isEmpty(notifyTemplateList)) {
  81 + return;
  82 + }
  83 +
  84 + // 写入缓存
  85 + notifyTemplateCache = CollectionUtils.convertMap(notifyTemplateList, NotifyTemplateDO::getCode);
  86 + maxUpdateTime = CollectionUtils.getMaxValue(notifyTemplateList, NotifyTemplateDO::getUpdateTime);
  87 + log.info("[initLocalCache][初始化 NotifyTemplate 数量为 {}]", notifyTemplateList.size());
  88 +
  89 + }
  90 +
  91 + /**
  92 + * 如果站内信模板发生变化,从数据库中获取最新的全量站内信模板。
  93 + * 如果未发生变化,则返回空
  94 + *
  95 + * @param maxUpdateTime 当前站内信模板的最大更新时间
  96 + * @return 站内信模板列表
  97 + */
  98 + private List<NotifyTemplateDO> loadNotifyTemplateIfUpdate(Date maxUpdateTime) {
  99 + // 第一步,判断是否要更新。
  100 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  101 + log.info("[loadNotifyTemplateIfUpdate][首次加载全量站内信模板]");
  102 + } else { // 判断数据库中是否有更新的站内信模板
  103 + if (notifyTemplateMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  104 + return null;
  105 + }
  106 + log.info("[loadNotifyTemplateIfUpdate][增量加载全量站内信模板]");
  107 + }
  108 + // 第二步,如果有更新,则从数据库加载所有站内信模板
  109 + return notifyTemplateMapper.selectList();
  110 + }
  111 +
  112 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  113 + public void schedulePeriodicRefresh() {
  114 + initLocalCache();
  115 + }
  116 +
  117 +
  118 + /**
  119 + * 获得站内信模板,从缓存中
  120 + *
  121 + * @param code 模板编码
  122 + * @return 站内信模板
  123 + */
  124 + @Override
  125 + public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
  126 + return notifyTemplateCache.get(code);
  127 + }
  128 +
  129 + /**
  130 + * 格式化站内信内容
  131 + *
  132 + * @param content 站内信模板的内容
  133 + * @param params 站内信内容的参数
  134 + * @return 格式化后的内容
  135 + */
  136 + @Override
  137 + public String formatNotifyTemplateContent(String content, Map<String, Object> params) {
  138 + return StrUtil.format(content, params);
  139 + }
  140 +
  141 + @VisibleForTesting
  142 + public List<String> parseTemplateContentParams(String content) {
  143 + return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
  144 + }
  145 +
34 146 @Override
35 147 public Long createNotifyTemplate(NotifyTemplateCreateReqVO createReqVO) {
  148 + // 校验站内信编码是否重复
  149 + checkNotifyTemplateCodeDuplicate(null, createReqVO.getCode());
  150 +
36 151 // 插入
37 152 NotifyTemplateDO notifyTemplate = NotifyTemplateConvert.INSTANCE.convert(createReqVO);
  153 + notifyTemplate.setParams(parseTemplateContentParams(notifyTemplate.getContent()));
38 154 notifyTemplateMapper.insert(notifyTemplate);
  155 + // 发送刷新消息
  156 + notifyProducer.sendNotifyTemplateRefreshMessage();
39 157 // 返回
40 158 return notifyTemplate.getId();
41 159 }
... ... @@ -44,9 +162,16 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
44 162 public void updateNotifyTemplate(NotifyTemplateUpdateReqVO updateReqVO) {
45 163 // 校验存在
46 164 this.validateNotifyTemplateExists(updateReqVO.getId());
  165 + // 校验站内信编码是否重复
  166 + checkNotifyTemplateCodeDuplicate(updateReqVO.getId(), updateReqVO.getCode());
  167 +
47 168 // 更新
48 169 NotifyTemplateDO updateObj = NotifyTemplateConvert.INSTANCE.convert(updateReqVO);
  170 + updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
  171 +
49 172 notifyTemplateMapper.updateById(updateObj);
  173 + // 发送刷新消息
  174 + notifyProducer.sendNotifyTemplateRefreshMessage();
50 175 }
51 176  
52 177 @Override
... ... @@ -55,6 +180,8 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
55 180 this.validateNotifyTemplateExists(id);
56 181 // 删除
57 182 notifyTemplateMapper.deleteById(id);
  183 + // 发送刷新消息
  184 + notifyProducer.sendNotifyTemplateRefreshMessage();
58 185 }
59 186  
60 187 private void validateNotifyTemplateExists(Long id) {
... ... @@ -83,4 +210,18 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
83 210 return notifyTemplateMapper.selectList(exportReqVO);
84 211 }
85 212  
  213 + @VisibleForTesting
  214 + public void checkNotifyTemplateCodeDuplicate(Long id, String code) {
  215 + NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code);
  216 + if (template == null) {
  217 + return;
  218 + }
  219 + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型
  220 + if (id == null) {
  221 + throw exception(NOTIFY_TEMPLATE_CODE_DUPLICATE, code);
  222 + }
  223 + if (!template.getId().equals(id)) {
  224 + throw exception(NOTIFY_TEMPLATE_CODE_DUPLICATE, code);
  225 + }
  226 + }
86 227 }
... ...