zefeng_java 4 жил өмнө
parent
commit
df8bda53e8
42 өөрчлөгдсөн 1660 нэмэгдсэн , 72 устгасан
  1. 12 0
      pom.xml
  2. 73 0
      sql/ruoyi-vue-pro.sql
  3. 15 15
      src/main/java/cn/iocoder/dashboard/DashboardApplication.java
  4. 33 0
      src/main/java/cn/iocoder/dashboard/common/enums/SmsChannelEnum.java
  5. 28 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/SmsBody.java
  6. 27 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/SmsResult.java
  7. 67 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/SmsSender.java
  8. 48 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/config/SmsConfiguration.java
  9. 137 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/factory/DefaultSmsSenderFactory.java
  10. 86 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/impl/ali/AliSmsSender.java
  11. 38 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/AbstractSmsIntercepterChain.java
  12. 20 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/DefaultSmsIntercepterChain.java
  13. 41 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/SmsIntercepter.java
  14. 46 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/SmsLogIntercepter.java
  15. 41 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/proxy/DefaultSmsSenderProxy.java
  16. 34 0
      src/main/java/cn/iocoder/dashboard/framework/msg/sms/proxy/SmsSenderProxy.java
  17. 10 32
      src/main/java/cn/iocoder/dashboard/framework/redis/core/RedisKeyDefine.java
  18. 0 25
      src/main/java/cn/iocoder/dashboard/framework/redis/core/RedisKeyRegistry.java
  19. 54 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/SmsChannelController.java
  20. 84 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/SmsTemplateController.java
  21. 58 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/SmsChannelAllVO.java
  22. 30 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/SmsTemplateVO.java
  23. 44 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/req/SmsChannelCreateReqVO.java
  24. 29 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/req/SmsChannelPageReqVO.java
  25. 21 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/resp/SmsChannelEnumRespVO.java
  26. 43 0
      src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/resp/SmsChannelPageRespVO.java
  27. 34 0
      src/main/java/cn/iocoder/dashboard/modules/msg/convert/sms/SmsChannelConvert.java
  28. 26 0
      src/main/java/cn/iocoder/dashboard/modules/msg/convert/sms/SmsTemplateConvert.java
  29. 31 0
      src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/dao/sms/SmsChannelMapper.java
  30. 10 0
      src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/dao/sms/SmsLogMapper.java
  31. 39 0
      src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/dao/sms/SmsTemplateMapper.java
  32. 65 0
      src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/daoobject/sms/SmsChannelDO.java
  33. 71 0
      src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/daoobject/sms/SmsLog.java
  34. 81 0
      src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/daoobject/sms/SmsTemplateDO.java
  35. 6 0
      src/main/java/cn/iocoder/dashboard/modules/msg/package-info.java
  36. 34 0
      src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/SmsChannelService.java
  37. 10 0
      src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/SmsLogService.java
  38. 10 0
      src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/SmsTemplateService.java
  39. 88 0
      src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/impl/SmsChannelServiceImpl.java
  40. 15 0
      src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/impl/SmsLogServiceImpl.java
  41. 14 0
      src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/impl/SmsTemplateServiceImpl.java
  42. 7 0
      src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java

+ 12 - 0
pom.xml

@@ -222,6 +222,18 @@
             <version>${easyexcel.verion}</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.18</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
+            <version>2.1.0</version>
+        </dependency>
+
     </dependencies>
 
 

+ 73 - 0
sql/ruoyi-vue-pro.sql

@@ -884,4 +884,77 @@ INSERT INTO `sys_user_role` VALUES (5, 100, 1, '', NULL, '', NULL, b'0');
 INSERT INTO `sys_user_role` VALUES (6, 100, 2, '', NULL, '', NULL, b'0');
 COMMIT;
 
+
+-- ----------------------------
+-- Table structure for sms_channel
+-- ----------------------------
+DROP TABLE IF EXISTS `sms_channel`;
+CREATE TABLE `sms_channel` (
+    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
+    `code` varchar(50) not null COMMENT '编码(来自枚举类 阿里、华为、七牛等)',
+    `api_key` varchar(100) NOT NULL COMMENT '账号id',																					-- add
+    `api_secret` varchar(100) NOT NULL COMMENT '账号秘钥',																			-- add
+    `priority` tinyint(8) NOT NULL default 1 COMMENT '优先级(存在多个签名时,选择值最小的,渠道不可用时,按优先级从小到大切换)',			-- add
+    `api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识',
+    `name` varchar(50) not null COMMENT '名称',
+    `signature` varchar(50) not null COMMENT '签名值',
+    `remark` varchar(200) NOT NULL COMMENT '备注',
+
+    `status` tinyint(4) NOT NULL default 0 COMMENT '启用状态(0正常 1停用)',
+    `create_by` varchar(64) not null DEFAULT '' COMMENT '创建者',
+    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+    `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
+    `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+    `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短信渠道';
+/*
+	优先级值一样时,按照id顺序取值
+*/
+
+-- ----------------------------
+-- Table structure for sms_template
+-- ----------------------------
+DROP TABLE IF EXISTS `sms_template`;
+CREATE TABLE `sms_template` (
+    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
+    `channel_code` varchar(50) not null COMMENT '短信渠道编码(来自枚举类)',
+    `channel_id` bigint(20) not null COMMENT '短信渠道id (对于前端来说就是绑定一个签名)',												-- add
+    `type` tinyint(4) NOT NULL default 1 COMMENT '消息类型 [0验证码 1短信通知 2推广短信 3国际/港澳台消息]',
+    `biz_code` varchar(50) not null COMMENT '业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)',						-- add
+    `priority` tinyint(8) NOT NULL default 1 COMMENT '优先级(默认直接继承渠道表的,逻辑也与渠道表的一致,可以针对每个biz_code进行修改)',-- add
+    `code` varchar(50) not null COMMENT '编码',
+    `name` varchar(50) not null COMMENT '名称',
+    `api_template_id` varchar(100) NOT NULL COMMENT '实际渠道模板唯一标识',
+    `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
+    `params` varchar(200) NOT NULL DEFAULT '' COMMENT '参数数组(自动根据内容生成)',													-- add
+    `remark` varchar(200) NOT NULL COMMENT '备注',
+
+    `status` tinyint(4) NOT NULL default 0 COMMENT '启用状态(0正常 1停用)',
+    `create_by` varchar(64) not null DEFAULT '' COMMENT '创建者',
+    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+    `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
+    `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+    `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短信模板';
+
+-- ----------------------------
+-- Table structure for sms_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sms_log`;
+CREATE TABLE `sms_log` (
+    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
+    `channel_code` varchar(50) not null COMMENT '短信渠道编码(来自枚举类)',
+    `api_sms_id` varchar(50) not null COMMENT '实际渠道短信唯一标识',
+    `template_id` bigint(20) NOT NULL COMMENT '模板id',
+    `phone` char(11) not null COMMENT '手机号',
+    `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
+    `remark` varchar(200) NOT NULL COMMENT '备注',
+    `send_status` tinyint(4) NOT NULL default 0 COMMENT '发送状态(0发送中 1成功 2失败)',
+    `create_by` varchar(64) not null DEFAULT '' COMMENT '创建者',
+    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短信日志';
+
 SET FOREIGN_KEY_CHECKS = 1;

+ 15 - 15
src/main/java/cn/iocoder/dashboard/DashboardApplication.java

@@ -1,15 +1,15 @@
-package cn.iocoder.dashboard;
-
-import de.codecentric.boot.admin.server.config.EnableAdminServer;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-@EnableAdminServer
-public class DashboardApplication {
-
-    public static void main(String[] args) {
-        SpringApplication.run(DashboardApplication.class, args);
-    }
-
-}
+//package cn.iocoder.dashboard;
+//
+//import de.codecentric.boot.admin.server.config.EnableAdminServer;
+//import org.springframework.boot.SpringApplication;
+//import org.springframework.boot.autoconfigure.SpringBootApplication;
+//
+//@SpringBootApplication
+//@EnableAdminServer
+//public class DashboardApplication {
+//
+//    public static void main(String[] args) {
+//        SpringApplication.run(DashboardApplication.class, args);
+//    }
+//
+//}

+ 33 - 0
src/main/java/cn/iocoder/dashboard/common/enums/SmsChannelEnum.java

@@ -0,0 +1,33 @@
+package cn.iocoder.dashboard.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信渠道枚举
+ *
+ * @author zzf
+ * @date 2021/1/25 10:56
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsChannelEnum {
+
+    ALI("ALI", "阿里"),
+    HUA_WEI("HUA_WEI", "华为"),
+    QI_NIU("QI_NIU", "七牛"),
+    TEN_XUN("TEN_XUN", "腾讯");
+
+    private final String code;
+
+    private final String name;
+
+    public static SmsChannelEnum getByCode(String code) {
+        for (SmsChannelEnum value : SmsChannelEnum.values()) {
+            if (value.getCode().equals(code)) {
+                return value;
+            }
+        }
+        return null;
+    }
+}

+ 28 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/SmsBody.java

@@ -0,0 +1,28 @@
+package cn.iocoder.dashboard.framework.msg.sms;
+
+import cn.iocoder.dashboard.util.json.JsonUtils;
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 消息内容实体类
+ */
+@Data
+public class SmsBody {
+
+    /**
+     * 模板编码
+     */
+    private String code;
+
+    /**
+     * 参数列表
+     */
+    private Map<String, String> params;
+
+    public String getParamsStr() {
+        return JsonUtils.toJsonString(params);
+    }
+
+}

+ 27 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/SmsResult.java

@@ -0,0 +1,27 @@
+package cn.iocoder.dashboard.framework.msg.sms;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 消息内容实体类
+ */
+@Data
+public class SmsResult<T> implements Serializable {
+
+    /**
+     * 是否成功
+     */
+    private Boolean success;
+
+    /**
+     * 提示
+     */
+    private String message;
+
+    /**
+     * 返回值
+     */
+    private T result;
+}

+ 67 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/SmsSender.java

@@ -0,0 +1,67 @@
+package cn.iocoder.dashboard.framework.msg.sms;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * 短信父接口
+ *
+ * @author zzf
+ * @date 2021/1/25 14:14
+ */
+public interface SmsSender<R> {
+
+    /**
+     * 发送通知
+     *
+     * @param msgBody 通知内容
+     * @param targets 发送对象列表
+     * @return 是否发送成功
+     */
+    SmsResult<R> send(SmsBody msgBody, Collection<String> targets);
+
+    /**
+     * 发送通知
+     *
+     * @param msgBody 通知内容
+     * @param target  发送对象列表
+     * @return 是否发送成功
+     */
+    default SmsResult<R> send(SmsBody msgBody, String target) {
+        if (StringUtils.isBlank(target)) {
+            return failResult();
+        }
+
+        return send(msgBody, Collections.singletonList(target));
+    }
+
+    /**
+     * 发送通知
+     *
+     * @param msgBody 通知内容
+     * @param targets 发送对象列表
+     * @return 是否发送成功
+     */
+    default SmsResult<R> send(SmsBody msgBody, String... targets) {
+        if (targets == null) {
+            return failResult();
+        }
+
+        return send(msgBody, Arrays.asList(targets));
+    }
+
+    default SmsResult<R> failResult() {
+        SmsResult<R> resultBody = new SmsResult<>();
+        resultBody.setSuccess(false);
+        return resultBody;
+    }
+
+    default SmsResult<R> failResult(String message) {
+        SmsResult<R> resultBody = failResult();
+        resultBody.setMessage(message);
+        return resultBody;
+    }
+}

+ 48 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/config/SmsConfiguration.java

@@ -0,0 +1,48 @@
+package cn.iocoder.dashboard.framework.msg.sms.config;
+
+import cn.iocoder.dashboard.framework.msg.sms.factory.DefaultSmsSenderFactory;
+import cn.iocoder.dashboard.framework.msg.sms.intercepter.AbstractSmsIntercepterChain;
+import cn.iocoder.dashboard.framework.msg.sms.intercepter.DefaultSmsIntercepterChain;
+import cn.iocoder.dashboard.framework.msg.sms.intercepter.SmsLogIntercepter;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
+import cn.iocoder.dashboard.modules.msg.service.sms.SmsChannelService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 短信服务配置
+ *
+ * @author guer
+ */
+@Configuration
+@ConditionalOnProperty("sms.enabled")
+public class SmsConfiguration {
+
+    @Resource
+    private SmsChannelService channelService;
+
+
+    @Bean
+    public AbstractSmsIntercepterChain smsIntercepterChain() {
+        DefaultSmsIntercepterChain intercepterChain = new DefaultSmsIntercepterChain();
+        //添加拦截器
+        intercepterChain.addSmsIntercepter(new SmsLogIntercepter());
+        return intercepterChain;
+    }
+
+    @Bean
+    public DefaultSmsSenderFactory smsSenderFactory(AbstractSmsIntercepterChain intercepterChain) {
+        DefaultSmsSenderFactory defaultSmsSenderFactory = new DefaultSmsSenderFactory();
+        List<SmsChannelAllVO> smsChannelAllVOList = channelService.listChannelAllEnabledInfo();
+        //初始化渠道、模板信息
+        defaultSmsSenderFactory.init(smsChannelAllVOList);
+        //注入拦截器链
+        defaultSmsSenderFactory.setIntercepterChain(intercepterChain);
+        return defaultSmsSenderFactory;
+    }
+
+}

+ 137 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/factory/DefaultSmsSenderFactory.java

@@ -0,0 +1,137 @@
+package cn.iocoder.dashboard.framework.msg.sms.factory;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.common.exception.ServiceException;
+import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
+import cn.iocoder.dashboard.framework.msg.sms.impl.ali.AliSmsSender;
+import cn.iocoder.dashboard.framework.msg.sms.intercepter.AbstractSmsIntercepterChain;
+import cn.iocoder.dashboard.framework.msg.sms.proxy.DefaultSmsSenderProxy;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsTemplateVO;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
+
+/**
+ * 短信发送者工厂
+ *
+ * @author zzf
+ * @date 2021/1/25 16:18
+ */
+public class DefaultSmsSenderFactory {
+
+    /**
+     * sender索引
+     * key: {@link SmsTemplateVO#getBizCode()}
+     * value: {@link SmsSender}
+     */
+    private final ConcurrentHashMap<String, SmsSender<?>> bizCode2SenderMap = new ConcurrentHashMap<>(8);
+
+    /**
+     * sender索引
+     * key: {@link SmsTemplateVO#getCode()}
+     * value: {@link SmsSender}
+     */
+    private final ConcurrentHashMap<String, SmsSender<?>> templateCode2SenderMap = new ConcurrentHashMap<>(8);
+
+
+    @Setter
+    private AbstractSmsIntercepterChain intercepterChain;
+
+    /**
+     * 读写锁
+     */
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private final Lock readLock = lock.readLock();
+    private final Lock writeLock = lock.writeLock();
+
+
+    public void init(List<SmsChannelAllVO> smsChannelAllVOList) {
+        if (ObjectUtil.isEmpty(smsChannelAllVOList)) {
+            throw new ServiceException(SMS_CHANNEL_NOT_FOUND);
+        }
+        try {
+            writeLock.lock();
+            addSender(smsChannelAllVOList);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public SmsSender<?> getSenderByBizCode(String bizCode) {
+        return getSmsSender(bizCode, bizCode2SenderMap);
+    }
+
+    public SmsSender<?> getSenderByTemplateCode(String templateCode) {
+        return getSmsSender(templateCode, templateCode2SenderMap);
+    }
+
+    private SmsSender<?> getSmsSender(String templateCode, ConcurrentHashMap<String, SmsSender<?>> cacheMap) {
+        try {
+            readLock.lock();
+            SmsSender<?> smsSender = cacheMap.get(templateCode);
+            if (smsSender == null) {
+                throw new ServiceException(SMS_SENDER_NOT_FOUND);
+            }
+            return smsSender;
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public void flush(List<SmsChannelAllVO> smsChannelAllVOList) {
+        try {
+            writeLock.lock();
+            bizCode2SenderMap.clear();
+            templateCode2SenderMap.clear();
+            addSender(smsChannelAllVOList);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+
+    private void addSender(List<SmsChannelAllVO> smsChannelAllVOList) {
+        smsChannelAllVOList.forEach(channelAllVO -> addSender(SmsChannelEnum.getByCode(channelAllVO.getCode()), channelAllVO));
+    }
+
+    private void addSender(SmsChannelEnum channelEnum, SmsChannelAllVO channelAllVO) {
+        if (channelEnum == null) {
+            throw new ServiceException(INVALID_CHANNEL_CODE);
+        }
+        List<SmsTemplateVO> templateList = channelAllVO.getTemplateList();
+        if (ObjectUtil.isEmpty(templateList)) {
+            throw new ServiceException(SMS_TEMPLATE_NOT_FOUND);
+        }
+
+        SmsSender<?> aliSmsSender = getSender(channelEnum, channelAllVO);
+
+
+        templateList.forEach(smsTemplateVO -> {
+            bizCode2SenderMap.put(smsTemplateVO.getBizCode(), aliSmsSender);
+            templateCode2SenderMap.put(smsTemplateVO.getCode(), aliSmsSender);
+        });
+    }
+
+    private SmsSender<?> getSender(SmsChannelEnum channelEnum, SmsChannelAllVO channelAllVO) {
+        switch (channelEnum) {
+            case ALI:
+                return new DefaultSmsSenderProxy<>(new AliSmsSender(channelAllVO), intercepterChain);
+            // TODO fill more channel
+            default:
+                break;
+        }
+        throw new ServiceException(SMS_SENDER_NOT_FOUND);
+    }
+
+
+
+
+}

+ 86 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/impl/ali/AliSmsSender.java

@@ -0,0 +1,86 @@
+package cn.iocoder.dashboard.framework.msg.sms.impl.ali;
+
+import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
+import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
+import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
+import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 阿里短信实现类
+ *
+ * @author zzf
+ * @date 2021/1/25 14:17
+ */
+@Slf4j
+public class AliSmsSender implements SmsSender<SendSmsResponse> {
+
+    private static final String OK = "OK";
+
+    private static final String PRODUCT = "Dysmsapi";
+
+    private static final String DOMAIN = "dysmsapi.aliyuncs.com";
+
+    private static final String ENDPOINT = "cn-hangzhou";
+
+    private final SmsChannelAllVO channelVO;
+
+    private final IAcsClient acsClient;
+
+    /**
+     * 构造阿里云短信发送处理
+     *
+     * @param channelVO 阿里云短信配置
+     */
+    public AliSmsSender(SmsChannelAllVO channelVO) {
+
+        this.channelVO = channelVO;
+
+        String accessKeyId = channelVO.getApiKey();
+        String accessKeySecret = channelVO.getApiSecret();
+
+        IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, accessKeyId, accessKeySecret);
+        DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
+
+        acsClient = new DefaultAcsClient(profile);
+    }
+
+
+    @Override
+    public SmsResult<SendSmsResponse> send(SmsBody msgBody, Collection<String> targets) {
+        SendSmsRequest request = new SendSmsRequest();
+        request.setSysMethod(MethodType.POST);
+        request.setPhoneNumbers(StringUtils.join(targets, ","));
+        request.setSignName(channelVO.getApiSignatureId());
+        request.setTemplateCode(channelVO.getTemplateByTemplateCode(msgBody.getCode()).getApiTemplateId());
+        request.setTemplateParam(msgBody.getParamsStr());
+
+        try {
+            SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
+
+            boolean result = OK.equals(sendSmsResponse.getCode());
+            if (!result) {
+                log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
+            }
+            SmsResult<SendSmsResponse> resultBody = new SmsResult<>();
+            resultBody.setSuccess(result);
+            resultBody.setResult(sendSmsResponse);
+            return resultBody;
+        } catch (Exception e) {
+            log.debug(e.getMessage(), e);
+        }
+
+        return failResult();
+    }
+
+}

+ 38 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/AbstractSmsIntercepterChain.java

@@ -0,0 +1,38 @@
+package cn.iocoder.dashboard.framework.msg.sms.intercepter;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 消息父接口
+ *
+ * @author zzf
+ * @date 2021/1/22 15:46
+ */
+public abstract class AbstractSmsIntercepterChain {
+
+    @Getter
+    protected final List<SmsIntercepter> intercepterList = new ArrayList<>(8);
+
+
+    /**
+     * 添加短信拦截器
+     *
+     * @param smsIntercepter 短信拦截器
+     */
+    public void addSmsIntercepter(SmsIntercepter smsIntercepter) {
+        addSmsIntercepter(Collections.singletonList(smsIntercepter));
+    }
+
+    /**
+     * 添加短信拦截器
+     *
+     * @param smsIntercepterList 短信拦截器数组
+     */
+    abstract void addSmsIntercepter(List<SmsIntercepter> smsIntercepterList);
+
+
+}

+ 20 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/DefaultSmsIntercepterChain.java

@@ -0,0 +1,20 @@
+package cn.iocoder.dashboard.framework.msg.sms.intercepter;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * 消息父接口
+ *
+ * @author zzf
+ * @date 2021/1/22 15:46
+ */
+public class DefaultSmsIntercepterChain extends AbstractSmsIntercepterChain {
+
+    @Override
+    public void addSmsIntercepter(List<SmsIntercepter> smsIntercepterList) {
+        intercepterList.addAll(smsIntercepterList);
+        //排序
+        intercepterList.sort(Comparator.comparingInt(SmsIntercepter::getOrder));
+    }
+}

+ 41 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/SmsIntercepter.java

@@ -0,0 +1,41 @@
+package cn.iocoder.dashboard.framework.msg.sms.intercepter;
+
+import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
+import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
+
+import java.util.Collection;
+
+/**
+ * 消息父接口
+ *
+ * @author zzf
+ * @date 2021/1/22 15:46
+ */
+public interface SmsIntercepter {
+
+    /**
+     * 监听发送前
+     *
+     * @param msgBody 消息体
+     * @param targets 发送对象数组
+     */
+    void beforeSender(SmsBody msgBody, Collection<String> targets);
+
+    /**
+     * 监听发送后
+     *
+     * @param msgBody    消息体
+     * @param targets    发送对象数组
+     * @param resultBody 返回对象
+     */
+    <T> void afterSender(SmsBody msgBody, Collection<String> targets, SmsResult<T> resultBody);
+
+    /**
+     * 排序值,拦截器根据order值顺序执行
+     * <p>
+     * 值越小,越早执行
+     *
+     * @return 排序值
+     */
+    int getOrder();
+}

+ 46 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/intercepter/SmsLogIntercepter.java

@@ -0,0 +1,46 @@
+package cn.iocoder.dashboard.framework.msg.sms.intercepter;
+
+import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
+import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms.SmsLogMapper;
+import cn.iocoder.dashboard.util.json.JsonUtils;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collection;
+
+/**
+ * 短信日志拦截器
+ *
+ * @author zzf
+ * @date 2021/1/22 15:46
+ */
+@Slf4j
+public class SmsLogIntercepter implements SmsIntercepter {
+
+
+    @Override
+    public void beforeSender(SmsBody msgBody, Collection<String> targets) {
+        log.debug("ready send sms, body: {}, target: {}", JsonUtils.toJsonString(msgBody), targets);
+
+    }
+
+    @Override
+    public <T> void afterSender(SmsBody msgBody, Collection<String> targets, SmsResult<T> resultBody) {
+        if (resultBody.getSuccess()) {
+            //
+        } else {
+            log.warn("send sms fail, body: {}, target: {}, resultBody: {}",
+                    JsonUtils.toJsonString(msgBody),
+                    targets,
+                    JsonUtils.toJsonString(resultBody)
+            );
+        }
+
+    }
+
+    @Override
+    public int getOrder() {
+        return 0;
+    }
+}

+ 41 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/proxy/DefaultSmsSenderProxy.java

@@ -0,0 +1,41 @@
+package cn.iocoder.dashboard.framework.msg.sms.proxy;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
+import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
+import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
+import cn.iocoder.dashboard.framework.msg.sms.intercepter.AbstractSmsIntercepterChain;
+
+import java.util.Collection;
+
+/**
+ * 消息父接口
+ *
+ * @author zzf
+ * @date 2021/1/22 15:46
+ */
+public class DefaultSmsSenderProxy<R> implements SmsSender<R> {
+
+    private final SmsSender<R> smsSender;
+    private final AbstractSmsIntercepterChain chain;
+
+    @Override
+    public SmsResult<R> send(SmsBody msgBody, Collection<String> targets) {
+        if (ObjectUtil.isNotNull(chain) && ObjectUtil.isNotEmpty(chain.getIntercepterList())) {
+            chain.getIntercepterList().forEach(s -> s.beforeSender(msgBody, targets));
+        }
+
+        SmsResult<R> resultBody = smsSender.send(msgBody, targets);
+
+        if (ObjectUtil.isNotNull(chain) && ObjectUtil.isNotEmpty(chain.getIntercepterList())) {
+            chain.getIntercepterList().forEach(s -> s.afterSender(msgBody, targets, resultBody));
+        }
+        return resultBody;
+    }
+
+    public DefaultSmsSenderProxy(SmsSender<R> smsSender,
+                                 AbstractSmsIntercepterChain chain) {
+        this.smsSender = smsSender;
+        this.chain = chain;
+    }
+}

+ 34 - 0
src/main/java/cn/iocoder/dashboard/framework/msg/sms/proxy/SmsSenderProxy.java

@@ -0,0 +1,34 @@
+package cn.iocoder.dashboard.framework.msg.sms.proxy;
+
+import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
+import cn.iocoder.dashboard.framework.msg.sms.intercepter.SmsIntercepter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 消息父接口
+ *
+ * @author zzf
+ * @date 2021/1/22 15:46
+ */
+public interface SmsSenderProxy<R> extends SmsSender<R> {
+
+    /**
+     * 添加短信拦截器
+     *
+     * @param smsIntercepter 短信拦截器
+     */
+    default void addSmsIntercepter(SmsIntercepter smsIntercepter) {
+        addSmsIntercepter(Collections.singletonList(smsIntercepter));
+    }
+
+    /**
+     * 添加短信拦截器
+     *
+     * @param smsIntercepterList 短信拦截器数组
+     */
+    void addSmsIntercepter(List<SmsIntercepter> smsIntercepterList);
+
+
+}

+ 10 - 32
src/main/java/cn/iocoder/dashboard/framework/redis/core/RedisKeyDefine.java

@@ -1,8 +1,6 @@
 package cn.iocoder.dashboard.framework.redis.core;
 
-import lombok.AllArgsConstructor;
 import lombok.Data;
-import lombok.Getter;
 
 import java.time.Duration;
 
@@ -26,20 +24,15 @@ public class RedisKeyDefine {
 
     }
 
-    @Getter
-    @AllArgsConstructor
-    public enum TimeoutTypeEnum {
-
-        FOREVER(1), // 永不超时
-        DYNAMIC(2), // 动态超时
-        FIXED(3); // 固定超时
-
-        /**
-         * 类型
-         */
-        private final Integer type;
+    /**
+     * 过期时间 - 永不过期
+     */
+    public static final Duration TIMEOUT_FOREVER = null;
 
-    }
+    /**
+     * 过期时间 - 动态,通过参数传入
+     */
+    public static final Duration TIMEOUT_DYNAMIC = null;
 
     /**
      * Key 模板
@@ -55,12 +48,10 @@ public class RedisKeyDefine {
      * 如果是使用分布式锁,设置为 {@link java.util.concurrent.locks.Lock} 类型
      */
     private final Class<?> valueType;
-    /**
-     * 超时类型
-     */
-    private final TimeoutTypeEnum timeoutType;
     /**
      * 过期时间
+     *
+     * 为空时,表示永不过期 {@link #TIMEOUT_FOREVER}
      */
     private final Duration timeout;
 
@@ -68,20 +59,7 @@ public class RedisKeyDefine {
         this.keyTemplate = keyTemplate;
         this.keyType = keyType;
         this.valueType = valueType;
-        this.timeoutType = TimeoutTypeEnum.FIXED;
         this.timeout = timeout;
-        // 添加注册表
-        RedisKeyRegistry.add(this);
-    }
-
-    public RedisKeyDefine(String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
-        this.keyTemplate = keyTemplate;
-        this.keyType = keyType;
-        this.valueType = valueType;
-        this.timeoutType = timeoutType;
-        this.timeout = Duration.ZERO;
-        // 添加注册表
-        RedisKeyRegistry.add(this);
     }
 
 }

+ 0 - 25
src/main/java/cn/iocoder/dashboard/framework/redis/core/RedisKeyRegistry.java

@@ -1,25 +0,0 @@
-package cn.iocoder.dashboard.framework.redis.core;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * {@link RedisKeyDefine} 注册表
- */
-public class RedisKeyRegistry {
-
-    private static final List<RedisKeyDefine> defines = new ArrayList<>();
-
-    public static void add(RedisKeyDefine define) {
-        defines.add(define);
-    }
-
-    public static List<RedisKeyDefine> list() {
-        return defines;
-    }
-
-    public static int size() {
-        return defines.size();
-    }
-
-}

+ 54 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/SmsChannelController.java

@@ -0,0 +1,54 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms;
+
+import cn.iocoder.dashboard.common.pojo.CommonResult;
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
+import cn.iocoder.dashboard.modules.msg.service.sms.SmsChannelService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
+
+@Api("短信 渠道/签名 API")
+@RestController
+@RequestMapping("/sms/channel")
+public class SmsChannelController {
+
+    @Resource
+    private SmsChannelService service;
+
+    @ApiOperation("获取渠道/签名分页")
+    @GetMapping("/page")
+    public CommonResult<PageResult<SmsChannelDO>> getPermissionInfo(@Validated SmsChannelPageReqVO reqVO) {
+        return success(service.pageChannels(reqVO));
+    }
+
+    @ApiOperation("获取渠道枚举")
+    @GetMapping("/list/channel-enum")
+    public CommonResult<List<SmsChannelEnumRespVO>> getChannelEnums() {
+        return success(service.getChannelEnums());
+    }
+
+
+    @ApiOperation("添加消息渠道")
+    @PostMapping("/create")
+    public CommonResult<Long> add(@Validated @RequestBody SmsChannelCreateReqVO reqVO) {
+        return success(service.createChannel(reqVO));
+    }
+
+    @ApiOperation("刷新消息渠道信息")
+    @PutMapping("/flush")
+    public CommonResult<Boolean> flushChannel() {
+        return success(service.flushChannel());
+    }
+
+
+}

+ 84 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/SmsTemplateController.java

@@ -0,0 +1,84 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms;
+
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+import cn.iocoder.dashboard.common.pojo.CommonResult;
+import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
+import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginReqVO;
+import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginRespVO;
+import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthMenuRespVO;
+import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthPermissionInfoRespVO;
+import cn.iocoder.dashboard.modules.system.convert.auth.SysAuthConvert;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
+import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
+import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService;
+import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
+import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService;
+import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
+import cn.iocoder.dashboard.util.collection.SetUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
+import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId;
+import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds;
+
+@Api("认证 API")
+@RestController
+@RequestMapping("/sms/template")
+public class SmsTemplateController {
+
+    @Resource
+    private SysAuthService authService;
+    @Resource
+    private SysUserService userService;
+    @Resource
+    private SysRoleService roleService;
+    @Resource
+    private SysPermissionService permissionService;
+
+    @ApiOperation("使用账号密码登录")
+    @PostMapping("/login")
+    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+    public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
+        String token = authService.login(reqVO.getUsername(), reqVO.getPassword(), reqVO.getUuid(), reqVO.getCode());
+        // 返回结果
+        return success(SysAuthLoginRespVO.builder().token(token).build());
+    }
+
+    @ApiOperation("获取登陆用户的权限信息")
+    @GetMapping("/get-permission-info")
+    public CommonResult<SysAuthPermissionInfoRespVO> getPermissionInfo() {
+        // 获得用户信息
+        SysUserDO user = userService.getUser(getLoginUserId());
+        if (user == null) {
+            return null;
+        }
+        // 获得角色列表
+        List<SysRoleDO> roleList = roleService.listRolesFromCache(getLoginUserRoleIds());
+        // 获得菜单列表
+        List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache(getLoginUserRoleIds(),
+                SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
+                SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus()));
+        // 拼接结果返回
+        return success(SysAuthConvert.INSTANCE.convert(user, roleList, menuList));
+    }
+
+    @ApiOperation("获得登陆用户的菜单列表")
+    @GetMapping("list-menus")
+    public CommonResult<List<SysAuthMenuRespVO>> listMenus() {
+        // 获得用户拥有的菜单列表
+        List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache(getLoginUserRoleIds(),
+                SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
+                SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
+        // 转换成 Tree 结构返回
+        return success(SysAuthConvert.INSTANCE.buildMenuTree(menuList));
+    }
+
+}

+ 58 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/SmsChannelAllVO.java

@@ -0,0 +1,58 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 渠道(包含模板)信息VO类
+ *
+ * @author zzf
+ * @date 2021/1/25 17:01
+ */
+@Data
+@EqualsAndHashCode
+public class SmsChannelAllVO implements Serializable {
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 编码(来自枚举类 阿里、华为、七牛等)
+     */
+    private String code;
+
+    /**
+     * 渠道账号id
+     */
+    private String apiKey;
+
+    /**
+     * 渠道账号秘钥
+     */
+    private String apiSecret;
+
+    /**
+     * 实际渠道签名唯一标识
+     */
+    private String apiSignatureId;
+
+    /**
+     * 签名值
+     */
+    private String signature;
+
+    /**
+     * 该渠道名下的短信模板集合
+     */
+    private List<SmsTemplateVO> templateList;
+
+    public SmsTemplateVO getTemplateByTemplateCode(String tempCode) {
+        return templateList.stream().filter(s -> s.getCode().equals(tempCode)).findFirst().get();
+    }
+
+}

+ 30 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/SmsTemplateVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 渠道模板VO类
+ *
+ * @author zzf
+ * @date 2021/1/25 17:03
+ */
+@Data
+@EqualsAndHashCode
+public class SmsTemplateVO {
+
+    /**
+     * 业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)
+     */
+    private String bizCode;
+    /**
+     * 编码
+     */
+    private String code;
+
+    /**
+     * 实际渠道模板唯一标识
+     */
+    private String apiTemplateId;
+
+}

+ 44 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/req/SmsChannelCreateReqVO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms.vo.req;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+
+@ApiModel("消息渠道创建 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode
+public class SmsChannelCreateReqVO implements Serializable {
+
+    @ApiModelProperty("编码(来自枚举类 阿里、华为、七牛等)")
+    private String code;
+
+    @ApiModelProperty("渠道账号id")
+    private String apiKey;
+
+    @ApiModelProperty("渠道账号秘钥")
+    private String apiSecret;
+
+    @ApiModelProperty("优先级(存在多个签名时,选择值最小的,渠道不可用时,按优先级从小到大切换)")
+    private Integer priority;
+
+    @ApiModelProperty("名称")
+    private String name;
+
+    @ApiModelProperty("签名值")
+    private String signature;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty("启用状态(0正常 1停用)")
+    private Integer status;
+
+}

+ 29 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/req/SmsChannelPageReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms.vo.req;
+
+import cn.iocoder.dashboard.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("消息渠道分页 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class SmsChannelPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "渠道名", example = "阿里", notes = "模糊匹配")
+    private String name;
+
+    @ApiModelProperty(value = "签名值", example = "源码", notes = "模糊匹配")
+    private String signature;
+
+}

+ 21 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/resp/SmsChannelEnumRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+
+@ApiModel("用户分页 Request VO")
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode
+public class SmsChannelEnumRespVO implements Serializable {
+
+    private String code;
+
+    private String name;
+
+}

+ 43 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/controller/sms/vo/resp/SmsChannelPageRespVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp;
+
+import cn.iocoder.dashboard.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("用户分页 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class SmsChannelPageRespVO extends PageParam {
+
+    @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配")
+    private String username;
+
+    @ApiModelProperty(value = "手机号码", example = "yudao", notes = "模糊匹配")
+    private String mobile;
+
+    @ApiModelProperty(value = "展示状态", example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
+    private Integer status;
+
+    @ApiModelProperty(value = "开始时间", example = "2020-10-24")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date beginTime;
+
+    @ApiModelProperty(value = "结束时间", example = "2020-10-24")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date endTime;
+
+    @ApiModelProperty(value = "部门编号", example = "1024", notes = "同时筛选子部门")
+    private Long deptId;
+
+}

+ 34 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/convert/sms/SmsChannelConvert.java

@@ -0,0 +1,34 @@
+package cn.iocoder.dashboard.modules.msg.convert.sms;
+
+import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
+import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+@Mapper
+public interface SmsChannelConvert {
+
+    SmsChannelConvert INSTANCE = Mappers.getMapper(SmsChannelConvert.class);
+
+    @Mapping(source = "records", target = "list")
+    PageResult<SmsChannelDO> convertPage(IPage<SmsChannelDO> page);
+
+    SmsChannelDO convert(SmsChannelCreateReqVO bean);
+
+    SmsChannelDO convert(SysUserUpdateReqVO bean);
+
+    List<SmsChannelEnumRespVO> convertEnum(List<SmsChannelEnum> bean);
+
+    List<SmsChannelAllVO> convert(List<SmsChannelDO> bean);
+
+
+}

+ 26 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/convert/sms/SmsTemplateConvert.java

@@ -0,0 +1,26 @@
+package cn.iocoder.dashboard.modules.msg.convert.sms;
+
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsTemplateVO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsTemplateDO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+@Mapper
+public interface SmsTemplateConvert {
+
+    SmsTemplateConvert INSTANCE = Mappers.getMapper(SmsTemplateConvert.class);
+
+    @Mapping(source = "records", target = "list")
+    PageResult<SmsChannelDO> convertPage(IPage<SmsChannelDO> page);
+
+    List<SmsTemplateVO> convert(List<SmsTemplateDO> bean);
+
+    SmsTemplateVO convert(SmsTemplateDO bean);
+
+}

+ 31 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/dao/sms/SmsChannelMapper.java

@@ -0,0 +1,31 @@
+package cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface SmsChannelMapper extends BaseMapper<SmsChannelDO> {
+
+    default IPage<SmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
+        return selectPage(MyBatisUtils.buildPage(reqVO), new LambdaQueryWrapper<SmsChannelDO>()
+                .like(StrUtil.isNotBlank(reqVO.getName()), SmsChannelDO::getName, reqVO.getName())
+                .like(StrUtil.isNotBlank(reqVO.getSignature()), SmsChannelDO::getName, reqVO.getSignature())
+        );
+    }
+
+    default List<SmsChannelDO> selectEnabledList() {
+        return selectList(new LambdaQueryWrapper<SmsChannelDO>()
+                .eq(SmsChannelDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
+                .orderByAsc(SmsChannelDO::getId)
+        );
+    }
+}

+ 10 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/dao/sms/SmsLogMapper.java

@@ -0,0 +1,10 @@
+package cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms;
+
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SmsLogMapper extends BaseMapper<SmsLog> {
+
+}

+ 39 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/dao/sms/SmsTemplateMapper.java

@@ -0,0 +1,39 @@
+package cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms;
+
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsTemplateDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface SmsTemplateMapper extends BaseMapper<SmsTemplateDO> {
+
+    /**
+     * 根据短信渠道id查询短信模板集合
+     *
+     * @param channelId 渠道id
+     * @return 模板集合
+     */
+    default List<SmsTemplateDO> selectListByChannelId(Long channelId) {
+        return selectList(new LambdaQueryWrapper<SmsTemplateDO>()
+                .eq(SmsTemplateDO::getChannelId, channelId)
+                .eq(SmsTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
+                .orderByAsc(SmsTemplateDO::getId)
+        );
+    }
+
+    /**
+     * 查询有效短信模板集合
+     *
+     * @return 有效短信模板集合
+     */
+    default List<SmsTemplateDO> selectEnabledList() {
+        return selectList(new LambdaQueryWrapper<SmsTemplateDO>()
+                .eq(SmsTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
+                .orderByAsc(SmsTemplateDO::getId)
+        );
+    }
+}

+ 65 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/daoobject/sms/SmsChannelDO.java

@@ -0,0 +1,65 @@
+package cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms;
+
+import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+/**
+ * 短信渠道
+ *
+ * @author zzf
+ * @since 2021-01-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "sms_channel", autoResultMap = true)
+public class SmsChannelDO extends BaseDO {
+
+    /**
+     * 自增编号
+     */
+    private Long id;
+
+    /**
+     * 编码(来自枚举类 阿里、华为、七牛等)
+     */
+    private String code;
+
+    /**
+     * 渠道账号id
+     */
+    private String apiKey;
+
+    /**
+     * 渠道账号秘钥
+     */
+    private String apiSecret;
+
+    /**
+     * 实际渠道签名唯一标识
+     */
+    private String apiSignatureId;
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * 签名值
+     */
+    private String signature;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 启用状态(0正常 1停用)
+     */
+    private Integer status;
+
+}

+ 71 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/daoobject/sms/SmsLog.java

@@ -0,0 +1,71 @@
+package cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 短信日志
+ *
+ * @author zzf
+ * @since 2021-01-25
+ */
+@Data
+@EqualsAndHashCode
+@TableName(value = "sms_log", autoResultMap = true)
+public class SmsLog implements Serializable {
+
+    /**
+     * 自增编号
+     */
+    private Long id;
+
+    /**
+     * 短信渠道编码(来自枚举类)
+     */
+    private String channelCode;
+
+    /**
+     * 实际渠道短信唯一标识
+     */
+    private String apiSmsId;
+
+    /**
+     * 模板id
+     */
+    private Long templateId;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 内容
+     */
+    private String content;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 发送状态(0发送中 1成功 2失败)
+     */
+    private Integer sendStatus;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 81 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/dal/mysql/daoobject/sms/SmsTemplateDO.java

@@ -0,0 +1,81 @@
+package cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms;
+
+import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 短信模板
+ *
+ * @author zzf
+ * @since 2021-01-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "sms_template", autoResultMap = true)
+public class SmsTemplateDO extends BaseDO {
+
+    /**
+     * 自增编号
+     */
+    private Long id;
+
+    /**
+     * 短信渠道编码(来自枚举类)
+     */
+    private String channelCode;
+
+    /**
+     * 短信渠道id (对于前端来说就是绑定一个签名)
+     */
+    private Long channelId;
+
+    /**
+     * 消息类型 [0验证码 1短信通知 2推广短信 3国际/港澳台消息]
+     */
+    private Integer type;
+
+    /**
+     * 业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)
+     */
+    private String bizCode;
+
+    /**
+     * 编码
+     */
+    private String code;
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * 实际渠道模板唯一标识
+     */
+    private String apiTemplateId;
+
+    /**
+     * 内容
+     */
+    private String content;
+
+    /**
+     * 参数数组(自动根据内容生成)
+     */
+    private String params;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 启用状态(0正常 1停用)
+     */
+    private Integer status;
+
+}

+ 6 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * msg 包,专门专门用于发送消息的功能,支撑上层的通用与核心业务。
+ * 例如说:短信、邮件、app通知等等
+ *
+ */
+package cn.iocoder.dashboard.modules.msg;

+ 34 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/SmsChannelService.java

@@ -0,0 +1,34 @@
+package cn.iocoder.dashboard.modules.msg.service.sms;
+
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
+
+import java.util.List;
+
+/**
+ * 短信渠道Service接口
+ *
+ * @author zzf
+ * @date 2021/1/25 9:24
+ */
+public interface SmsChannelService {
+
+    PageResult<SmsChannelDO> pageChannels(SmsChannelPageReqVO reqVO);
+
+    Long createChannel(SmsChannelCreateReqVO reqVO);
+
+    List<SmsChannelEnumRespVO> getChannelEnums();
+
+    /**
+     * 查询渠道(包含名下模块)信息集合
+     *
+     * @return 渠道(包含名下模块)信息集合
+     */
+    List<SmsChannelAllVO> listChannelAllEnabledInfo();
+
+    boolean flushChannel();
+}

+ 10 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/SmsLogService.java

@@ -0,0 +1,10 @@
+package cn.iocoder.dashboard.modules.msg.service.sms;
+
+/**
+ * 短信渠道Service接口
+ *
+ * @author zzf
+ * @date 2021/1/25 9:24
+ */
+public interface SmsLogService {
+}

+ 10 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/SmsTemplateService.java

@@ -0,0 +1,10 @@
+package cn.iocoder.dashboard.modules.msg.service.sms;
+
+/**
+ * 短信渠道Service接口
+ *
+ * @author zzf
+ * @date 2021/1/25 9:24
+ */
+public interface SmsTemplateService {
+}

+ 88 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/impl/SmsChannelServiceImpl.java

@@ -0,0 +1,88 @@
+package cn.iocoder.dashboard.modules.msg.service.sms.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.msg.sms.factory.AbstractSmsSenderFactory;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
+import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
+import cn.iocoder.dashboard.modules.msg.convert.sms.SmsChannelConvert;
+import cn.iocoder.dashboard.modules.msg.convert.sms.SmsTemplateConvert;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms.SmsChannelMapper;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms.SmsTemplateMapper;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
+import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsTemplateDO;
+import cn.iocoder.dashboard.modules.msg.service.sms.SmsChannelService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 短信渠道Service实现类
+ *
+ * @author zzf
+ * @date 2021/1/25 9:25
+ */
+@Service
+public class SmsChannelServiceImpl implements SmsChannelService {
+
+    @Resource
+    private SmsChannelMapper mapper;
+
+    @Resource
+    private SmsTemplateMapper templateMapper;
+
+    @Override
+    public PageResult<SmsChannelDO> pageChannels(SmsChannelPageReqVO reqVO) {
+        return SmsChannelConvert.INSTANCE.convertPage(mapper.selectChannelPage(reqVO));
+    }
+
+    @Override
+    public Long createChannel(SmsChannelCreateReqVO reqVO) {
+        SmsChannelDO channelDO = SmsChannelConvert.INSTANCE.convert(reqVO);
+        mapper.insert(channelDO);
+        return channelDO.getId();
+    }
+
+    @Override
+    public List<SmsChannelEnumRespVO> getChannelEnums() {
+        return SmsChannelConvert.INSTANCE.convertEnum(Arrays.asList(SmsChannelEnum.values()));
+    }
+
+    @Override
+    public List<SmsChannelAllVO> listChannelAllEnabledInfo() {
+        List<SmsChannelDO> channelDOList = mapper.selectEnabledList();
+        if (ObjectUtil.isNull(channelDOList)) {
+            return null;
+        }
+        List<SmsChannelAllVO> channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList);
+
+        channelAllVOList.forEach(smsChannelDO -> {
+
+            List<SmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(smsChannelDO.getId());
+            if (ObjectUtil.isNull(templateDOList)) {
+                templateDOList = new ArrayList<>();
+            }
+            smsChannelDO.setTemplateList(SmsTemplateConvert.INSTANCE.convert(templateDOList));
+        });
+        return channelAllVOList;
+    }
+
+    @Override
+    public boolean flushChannel() {
+        AbstractSmsSenderFactory smsSenderFactory = SpringUtil.getBean(AbstractSmsSenderFactory.class);
+        if (smsSenderFactory == null) {
+            return false;
+        }
+
+        smsSenderFactory.flush(listChannelAllEnabledInfo());
+
+        return true;
+    }
+}

+ 15 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/impl/SmsLogServiceImpl.java

@@ -0,0 +1,15 @@
+package cn.iocoder.dashboard.modules.msg.service.sms.impl;
+
+import cn.iocoder.dashboard.modules.msg.service.sms.SmsLogService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 短信日志Service实现类
+ *
+ * @author zzf
+ * @date 2021/1/25 9:25
+ */
+@Service
+public class SmsLogServiceImpl implements SmsLogService {
+
+}

+ 14 - 0
src/main/java/cn/iocoder/dashboard/modules/msg/service/sms/impl/SmsTemplateServiceImpl.java

@@ -0,0 +1,14 @@
+package cn.iocoder.dashboard.modules.msg.service.sms.impl;
+
+import cn.iocoder.dashboard.modules.msg.service.sms.SmsTemplateService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 短信模板Service实现类
+ *
+ * @author zzf
+ * @date 2021/1/25 9:25
+ */
+@Service
+public class SmsTemplateServiceImpl implements SmsTemplateService {
+}

+ 7 - 0
src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java

@@ -75,4 +75,11 @@ public interface SysErrorCodeConstants {
     // ========== 文件 1002009000 ==========
     ErrorCode FILE_PATH_EXISTS = new ErrorCode(1002009001, "文件路径已经存在");
 
+
+    // ========== 消息 1003001000 ==========
+    ErrorCode SMS_CHANNEL_NOT_FOUND = new ErrorCode(1003001001, "没有短信渠道信息, 请初始化sms_channel表数据。");
+    ErrorCode SMS_TEMPLATE_NOT_FOUND = new ErrorCode(1003001002, "没有短信模板信息, 请初始化sms_template表数据。");
+    ErrorCode SMS_SENDER_NOT_FOUND = new ErrorCode(1003001003, "没有找到对应的短信发送对象,请检查sms_channel表和sms_template表数据");
+    ErrorCode INVALID_CHANNEL_CODE = new ErrorCode(1003001004, "非法的短信渠道code,请检查sms_channel表的code值是否与SmsChannelEnum中的code值一致。");
+
 }