Преглед изворни кода

feat(短信):
修复短信组件、阿里云短信通知
新增短信渠道配置、短信模板配置、短信日志
创建类型统一使用CreateEnum 枚举
新增 Spring Cloud Stream组件,进行业务消息通知
短信发送使用异步SCS组件完成
加入Rabbitmq作为通知组件

18339543638 пре 2 година
родитељ
комит
e9b9537636
81 измењених фајлова са 2799 додато и 493 уклоњено
  1. 16 9
      tr-dependencies/pom.xml
  2. 17 0
      tr-framework/src/main/java/cn/tr/core/enums/UserTypeEnum.java
  3. 1 1
      tr-framework/src/main/java/cn/tr/core/pojo/CommonResult.java
  4. 22 0
      tr-framework/src/main/java/cn/tr/core/pojo/KeyValue.java
  5. 3 2
      tr-modules/tr-module-gen/src/main/resources/backend/Controller.java.btl
  6. 4 3
      tr-modules/tr-module-gen/src/main/resources/backend/DTO.java.btl
  7. 15 0
      tr-modules/tr-module-system/pom.xml
  8. 3 2
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/controller/SysLogController.java
  9. 30 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mapper/sms/SysSmsChannelMapper.java
  10. 28 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mapper/sms/SysSmsTempMapper.java
  11. 41 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mq/consumer/sms/SmsConsumer.java
  12. 44 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mq/message/sms/SmsSendMessage.java
  13. 44 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mq/producer/sms/SmsSupplier.java
  14. 1 1
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/operator/OAuth2UserOperator.java
  15. 48 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SmsCallbackController.java
  16. 80 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SysSmsChannelController.java
  17. 59 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SysSmsLogController.java
  18. 94 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SysSmsTempController.java
  19. 25 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SmsTemplateSendDTO.java
  20. 63 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsChannelDTO.java
  21. 28 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsChannelQueryDTO.java
  22. 73 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsLogDTO.java
  23. 41 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsLogQueryDTO.java
  24. 81 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsTempDTO.java
  25. 31 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsTempQueryDTO.java
  26. 25 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsReceiveStatusEnum.java
  27. 45 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsSceneEnum.java
  28. 26 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsSendStatusEnum.java
  29. 24 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsTemplateTypeEnum.java
  30. 61 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/po/SysSmsChannelPO.java
  31. 125 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/po/SysSmsLogPO.java
  32. 64 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/po/SysSmsTempPO.java
  33. 16 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/repository/SysSmsChannelRepository.java
  34. 16 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/repository/SysSmsLogRepository.java
  35. 17 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/repository/SysSmsTempRepository.java
  36. 80 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISmsSendService.java
  37. 62 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISysSmsChannelService.java
  38. 83 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISysSmsLogService.java
  39. 72 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISysSmsTempService.java
  40. 178 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SmsSendServiceImpl.java
  41. 123 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SysSmsChannelServiceImpl.java
  42. 148 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SysSmsLogServiceImpl.java
  43. 134 0
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SysSmsTempServiceImpl.java
  44. 2 2
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/tenant/service/impl/SysTenantServiceImpl.java
  45. 3 7
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/controller/SysRoleController.java
  46. 4 4
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/enums/CreateEnum.java
  47. 0 15
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/enums/PortalEnum.java
  48. 0 5
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/po/SysPortalPO.java
  49. 2 3
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysMenuServiceImpl.java
  50. 2 2
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysPortalMenuServiceImpl.java
  51. 5 5
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysPortalServiceImpl.java
  52. 3 3
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysRoleMenuServiceImpl.java
  53. 5 5
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysRoleServiceImpl.java
  54. 2 0
      tr-plugins/tr-spring-boot-starter-plugin-mybatis/src/main/java/cn/tr/plugin/mybatis/pojo/TenantPO.java
  55. 2 8
      tr-plugins/tr-spring-boot-starter-plugin-sms/pom.xml
  56. 0 16
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsCodeMapping.java
  57. 6 17
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsCommonResult.java
  58. 49 0
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsReceiveRespBO.java
  59. 32 0
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsTemplateRespBO.java
  60. 67 7
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/AbstractSmsClient.java
  61. 99 0
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/DefaultSmsClientFactory.java
  62. 0 51
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/LocalSmsClientFactory.java
  63. 29 3
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClient.java
  64. 0 14
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClientConfig.java
  65. 8 4
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClientFactory.java
  66. 4 3
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsTempConfig.java
  67. 2 4
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsChannelProperties.java
  68. 132 32
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsClient.java
  69. 0 45
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsClientConfig.java
  70. 19 0
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsTempConfig.java
  71. 0 41
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliyunSmsCodeMapping.java
  72. 8 13
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/enums/SmsChannelEnum.java
  73. 0 55
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/enums/SmsErrorCode.java
  74. 0 58
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/enums/SmsFrameworkErrorCodeConstants.java
  75. 38 0
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/properties/SmsChannelProperties.java
  76. 1 0
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  77. 18 21
      tr-plugins/tr-spring-boot-starter-plugin-sms/src/test/java/cn/tr/plugin/sms/SmsTest.java
  78. 1 1
      tr-plugins/tr-spring-boot-starter-plugin-websocket/src/main/java/cn/tr/plugin/websocket/config/TrWsMsgHandler.java
  79. 40 0
      tr-test/src/main/resources/application-dev.yml
  80. 16 0
      tr-test/src/main/resources/application-stream.yml
  81. 9 31
      tr-test/src/main/resources/application.yml

+ 16 - 9
tr-dependencies/pom.xml

@@ -59,7 +59,7 @@
         <knife4j.verison>4.0.0</knife4j.verison>
 
         <!--阿里云短信-->
-        <ali.core.version>4.6.0</ali.core.version>
+        <ali.sms.version>2.0.23</ali.sms.version>
 
         <!--渲染模板引擎-->
         <beetl.version>1.2.40.Beetl.RELEASE</beetl.version>
@@ -67,7 +67,7 @@
         <!--硬件信息-->
         <oshi.core.version>6.2.2</oshi.core.version>
 
-        <ali.dysmsapi.version>2.2.1</ali.dysmsapi.version>
+        <stream-rabbit.version>3.2.6</stream-rabbit.version>
     </properties>
 
 
@@ -431,26 +431,33 @@
                 <version>${revision}</version>
             </dependency>
 
-            <!--阿里云短信-->
+            <!--短信模块-->
             <dependency>
-                <groupId>com.aliyun</groupId>
-                <artifactId>aliyun-java-sdk-core</artifactId>
-                <version>${ali.core.version}</version>
+                <groupId>cn.tr</groupId>
+                <artifactId>tr-spring-boot-starter-plugin-sms</artifactId>
+                <version>${revision}</version>
             </dependency>
 
+            <!--阿里云短信-->
             <dependency>
                 <groupId>com.aliyun</groupId>
-                <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
-                <version>${ali.dysmsapi.version}</version>
+                <artifactId>dysmsapi20170525</artifactId>
+                <version>${ali.sms.version}</version>
             </dependency>
-            <!--阿里云短信-->
 
+            <!--阿里云短信-->
             <dependency>
                 <groupId>com.ibeetl</groupId>
                 <artifactId>beetl-framework-starter</artifactId>
                 <version>${beetl.version}</version>
             </dependency>
 
+            <!--驱动式消息 订阅发布-->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
+                <version>${stream-rabbit.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 </project>

+ 17 - 0
tr-framework/src/main/java/cn/tr/core/enums/UserTypeEnum.java

@@ -0,0 +1,17 @@
+package cn.tr.core.enums;
+
+/**
+ * 全局用户类型枚举
+ */
+
+public interface UserTypeEnum {
+    /**
+     * 面向 b 端,管理后台
+     */
+    String ADMIN="admin";
+
+    /**
+     * 面向 c 端,普通用户
+     */
+    String MEMBER="member";
+}

+ 1 - 1
tr-framework/src/main/java/cn/tr/core/pojo/CommonResult.java

@@ -42,7 +42,7 @@ public class CommonResult<T> implements Serializable {
 
     public static <T> CommonResult<T> error(BaseCode excCode, String detailErrorMsg) {
         String errCode = excCode.getErrCode();
-        Assert.isTrue(!TRExcCode.SUCCESS.getErrCode().equals(errCode), "code 必须是错误的!");
+        Assert.isTrue(!TRExcCode.SUCCESS.getErrCode().equals(errCode), "type 必须是错误的!");
         CommonResult<T> result = new CommonResult<>();
         result.code = errCode;
         result.errorMsg = StrUtil.isNotBlank(detailErrorMsg)?detailErrorMsg:excCode.getErrMsg();

+ 22 - 0
tr-framework/src/main/java/cn/tr/core/pojo/KeyValue.java

@@ -0,0 +1,22 @@
+package cn.tr.core.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * Key Value 的键值对
+ *
+ * @author tr
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor(staticName = "of")
+public class KeyValue<K,V> implements Serializable {
+
+    private K key;
+    private V value;
+
+}

+ 3 - 2
tr-modules/tr-module-gen/src/main/resources/backend/Controller.java.btl

@@ -20,6 +20,7 @@ import java.util.*;
 import cn.tr.plugin.mybatis.base.BaseController;
 import org.springframework.web.bind.annotation.*;
 import cn.tr.plugin.operatelog.annotation.OperateLog;
+import cn.tr.core.pojo.TableDataInfo;
 /**
  * ${functionName}控制器
  *
@@ -37,9 +38,9 @@ public class ${className}Controller extends BaseController{
     @ApiOperationSupport(author = "${authorName}",order = 1)
     @ApiOperation(value="根据条件查询${functionName}",notes = "权限: 无")
     @PostMapping("/query/page")
-    public CommonResult<List<${className}DTO>> selectList(@RequestBody ${className}QueryDTO query) {
+    public TableDataInfo<${className}DTO> selectList(@RequestBody ${className}QueryDTO query) {
         startPage();
-        return CommonResult.success(${classNameFirstLower}Service.select${className}List(query));
+        return getDataTable(${classNameFirstLower}Service.select${className}List(query));
     }
 
     @ApiOperationSupport(author = "${authorName}",order = 2)

+ 4 - 3
tr-modules/tr-module-gen/src/main/resources/backend/DTO.java.btl

@@ -8,7 +8,7 @@ import cn.tr.core.validation.Update;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
-import javax.validation.constraints.NotNull;
+import javax.validation.constraints.*;
 import java.util.*;
 /**
  * ${functionName}传输对象
@@ -28,10 +28,11 @@ if(fieldNameCamelCase== "createTime" || fieldNameCamelCase == "createBy" || fiel
         continue ;} %>
     @ApiModelProperty(value = "${configList[i].fieldRemark}", position = ${i + 1})
     <% if(configList[i].needTableId ) { %>
-    @NotNull(message = "主键不能为空",groups = {Update.class})
+    <% if(configList[i].fieldJavaType == "String"){%> @NotBlank <% } else { %> @NotNull <% } %> (message = "主键不能为空",groups = {Update.class})
     <% } else if(configList[i].whetherRequired!=null && configList[i].whetherRequired){ %>
-    @NotNull(message = "configList[i].fieldRemark不能为空",groups = {Update.class,Insert.class})
+    <% if(configList[i].fieldJavaType == "String"){%> @NotBlank <% } else { %> @NotNull <% } %> (message = "${configList[i].fieldRemark}不能为空",groups = {Update.class,Insert.class})
     <% } %>
     private ${configList[i].fieldJavaType} ${configList[i].fieldNameCamelCase};
+
     <% } %>
 }

+ 15 - 0
tr-modules/tr-module-system/pom.xml

@@ -68,9 +68,24 @@
             <artifactId>tr-module-system-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-sms</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson-spring-boot-starter</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-desensitize</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 3 - 2
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/controller/SysLogController.java

@@ -1,5 +1,6 @@
 package cn.tr.module.sys.log.controller;
 
+import cn.tr.core.pojo.TableDataInfo;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.tr.core.validation.Insert;
@@ -36,9 +37,9 @@ public class SysLogController extends BaseController{
     @ApiOperationSupport(author = "lf",order = 1)
     @ApiOperation(value="根据条件查询系统日志",notes = "权限: 无")
     @PostMapping("/query/page")
-    public CommonResult<List<SysLogDTO>> selectList(@RequestBody SysLogQueryDTO query) {
+    public TableDataInfo<SysLogDTO> selectList(@RequestBody SysLogQueryDTO query) {
         startPage();
-        return CommonResult.success(sysLogService.selectSysLogList(query));
+        return getDataTable(sysLogService.selectSysLogList(query));
     }
 
     @ApiOperationSupport(author = "lf",order = 2)

+ 30 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mapper/sms/SysSmsChannelMapper.java

@@ -0,0 +1,30 @@
+package cn.tr.module.sys.mapper.sms;
+
+import cn.tr.module.sys.sms.po.SysSmsChannelPO;
+import cn.tr.module.sys.sms.dto.SysSmsChannelDTO;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+* 短信渠道映射工具
+*
+* @author lf
+* @date  2023/04/19 18:18
+**/
+@Mapper
+public interface SysSmsChannelMapper {
+    SysSmsChannelMapper INSTANCE = Mappers.getMapper(SysSmsChannelMapper.class);
+
+    SysSmsChannelPO convertPO(SysSmsChannelDTO source);
+
+    SysSmsChannelDTO convertDto(SysSmsChannelPO source);
+
+    List<SysSmsChannelDTO> convertDtoList(List<SysSmsChannelPO> source);
+
+    List<SysSmsChannelPO> convertPOList(List<SysSmsChannelDTO> source);
+
+    List<SmsChannelProperties> converProperties(List<SysSmsChannelPO> channels);
+}

+ 28 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mapper/sms/SysSmsTempMapper.java

@@ -0,0 +1,28 @@
+package cn.tr.module.sys.mapper.sms;
+
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+import cn.tr.module.sys.sms.po.SysSmsTempPO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+* 短信模板映射工具
+*
+* @author lf
+* @date  2023/04/21 10:29
+**/
+@Mapper
+public interface SysSmsTempMapper {
+    SysSmsTempMapper INSTANCE = Mappers.getMapper(SysSmsTempMapper.class);
+
+    SysSmsTempPO convertPO(SysSmsTempDTO source);
+
+    SysSmsTempDTO convertDto(SysSmsTempPO source);
+
+    List<SysSmsTempDTO> convertDtoList(List<SysSmsTempPO> source);
+
+    List<SysSmsTempPO> convertPOList(List<SysSmsTempDTO> source);
+
+}

+ 41 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mq/consumer/sms/SmsConsumer.java

@@ -0,0 +1,41 @@
+package cn.tr.module.sys.mq.consumer.sms;
+
+import cn.tr.module.sys.mq.message.sms.SmsSendMessage;
+import cn.tr.module.sys.sms.service.ISmsSendService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.function.Function;
+
+/**
+ * 针对 {@link SmsSendMessage} 的消费者
+ * @see  {https://docs.spring.io/spring-cloud-stream/docs/3.1.0/reference/html/spring-cloud-stream.html#_suppliers_sources}
+ *
+ * provider {@link cn.tr.module.sys.mq.producer.sms.SmsSupplier}
+ * 默认的主题名称是通过
+ * 输入:    <方法名> + -in- + <index>
+ * 输出:    <方法名> + -out- + <index>
+ * @author lf
+ */
+@Component
+@Slf4j
+@AllArgsConstructor
+public class SmsConsumer implements Function<Flux<SmsSendMessage>, Mono<Void>> {
+    private final ISmsSendService smsSendService;
+
+    @Override
+    public Mono<Void> apply(Flux<SmsSendMessage> flux) {
+        return flux
+                .log("smsConsumer")
+                .doOnNext(smsSendService::doSendSms)
+                .onErrorContinue((t,m)->{
+                    if(t!=null){
+                        log.error("[SmsConsumer] 信息发送失败,",t);
+                    }
+                })
+                .then();
+    }
+}

+ 44 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mq/message/sms/SmsSendMessage.java

@@ -0,0 +1,44 @@
+package cn.tr.module.sys.mq.message.sms;
+
+import cn.tr.core.pojo.KeyValue;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 短信发送消息
+ *
+ * @author 芋道源码
+ */
+@Data
+@Accessors(chain = true)
+public class SmsSendMessage {
+
+    /**
+     * 短信日志编号
+     */
+    @NotNull(message = "短信日志编号不能为空")
+    private String logId;
+    /**
+     * 手机号
+     */
+    @NotNull(message = "手机号不能为空")
+    private String mobile;
+    /**
+     * 短信渠道编号
+     */
+    @NotNull(message = "短信渠道编号不能为空")
+    private String channelId;
+    /**
+     * 短信 API 的模板编号
+     */
+    @NotNull(message = "短信 API 的模板编号不能为空")
+    private String apiTemplateId;
+    /**
+     * 短信模板参数
+     */
+    private List<KeyValue<String, Object>> templateParams;
+
+}

+ 44 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/mq/producer/sms/SmsSupplier.java

@@ -0,0 +1,44 @@
+package cn.tr.module.sys.mq.producer.sms;
+
+import cn.tr.core.pojo.KeyValue;
+import cn.tr.module.sys.mq.message.sms.SmsSendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Sinks;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Sms 短信相关消息的 Producer
+ *
+ * @author zzf
+ * @date 2021/3/9 16:35
+ */
+@Slf4j
+@Component
+public class SmsSupplier implements Supplier<Flux<SmsSendMessage>> {
+    private Sinks.Many<SmsSendMessage> sink = Sinks.many().unicast().onBackpressureBuffer();
+    /**
+     * 发送 {@link SmsSendMessage} 消息
+     *
+     * @param logId 短信日志编号
+     * @param mobile 手机号
+     * @param channelId 渠道编号
+     * @param apiTemplateId 短信模板编号
+     * @param templateParams 短信模板参数
+     */
+    public void sendSmsSendMessage(String logId, String mobile,
+                                   String channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
+        SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
+        message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);
+        sink.tryEmitNext(message);
+    }
+
+    @Override
+    public Flux<SmsSendMessage> get() {
+        return sink.asFlux().log();
+    }
+
+
+}

+ 1 - 1
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/operator/OAuth2UserOperator.java

@@ -46,7 +46,7 @@ public interface OAuth2UserOperator extends LoginTypeMatcher {
 
 	/**
 	 * @param source 认证参数
-	 * @return token 或 code
+	 * @return token 或 type
 	 */
 	String auth(OAuth2PswReqDTO source);
 

+ 48 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SmsCallbackController.java

@@ -0,0 +1,48 @@
+package cn.tr.module.sys.sms.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.extra.servlet.ServletUtil;
+import cn.tr.core.pojo.CommonResult;
+import cn.tr.module.sys.sms.service.ISmsSendService;
+import cn.tr.plugin.sms.enums.SmsChannelEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+@Api("短信回调")
+@ApiIgnore
+@RestController
+@RequestMapping("/sms/callback")
+@SaIgnore
+public class SmsCallbackController {
+
+    @Autowired
+    private ISmsSendService smsSendService;
+
+    @PostMapping("/aliyun")
+    @ApiOperation(value = "阿里云短信的回调", notes = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
+    public Map<Object,Object> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
+        String text = ServletUtil.getBody(request);
+        smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getChannel(), text);
+        return MapUtil.builder()
+                .put("code",0)
+                .put("msg","成功")
+                .build();
+    }
+
+    @PostMapping("/tencent")
+    @ApiOperation(value = "腾讯云短信的回调", notes = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
+    public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
+        String text = ServletUtil.getBody(request);
+        smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getChannel(), text);
+        return CommonResult.success(true);
+    }
+
+}

+ 80 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SysSmsChannelController.java

@@ -0,0 +1,80 @@
+package cn.tr.module.sys.sms.controller;
+
+import cn.tr.core.pojo.TableDataInfo;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import cn.tr.core.pojo.CommonResult;
+import lombok.AllArgsConstructor;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import cn.tr.module.sys.sms.dto.SysSmsChannelDTO;
+import cn.tr.module.sys.sms.service.ISysSmsChannelService;
+import cn.tr.module.sys.sms.dto.SysSmsChannelQueryDTO;
+import java.util.*;
+import cn.tr.plugin.mybatis.base.BaseController;
+import org.springframework.web.bind.annotation.*;
+import cn.tr.plugin.operatelog.annotation.OperateLog;
+/**
+ * 短信渠道控制器
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ */
+@Api(tags = "短信渠道")
+@RestController
+@RequestMapping("/sms/channel")
+@AllArgsConstructor
+public class SysSmsChannelController extends BaseController{
+
+    private final ISysSmsChannelService sysSmsChannelService;
+
+    @ApiOperationSupport(author = "lf",order = 1)
+    @ApiOperation(value="根据条件查询短信渠道",notes = "权限: 无")
+    @PostMapping("/query/page")
+    public TableDataInfo<SysSmsChannelDTO> selectList(@RequestBody SysSmsChannelQueryDTO query) {
+        startPage();
+        return getDataTable(sysSmsChannelService.selectSysSmsChannelList(query));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 2)
+    @ApiOperation(value = "根据id查询短信渠道",notes = "权限: sms:channel:query")
+    @GetMapping("/detail/{id}")
+    @SaCheckPermission("sms:channel:query")
+    public CommonResult<SysSmsChannelDTO> findById(@PathVariable("id") String id){
+        return CommonResult.success(sysSmsChannelService.selectSysSmsChannelById(id));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 3)
+    @ApiOperation(value="添加短信渠道",notes = "权限: sms:channel:add")
+    @PostMapping("/add")
+    @OperateLog
+    @SaCheckPermission("sms:channel:add")
+    public CommonResult<Boolean> add(@RequestBody@Validated(Insert.class) SysSmsChannelDTO source) {
+        return CommonResult.success(sysSmsChannelService.insertSysSmsChannel(source));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 4)
+    @ApiOperation(value="通过主键id编辑短信渠道",notes = "权限: sms:channel:edit")
+    @PostMapping("/edit")
+    @OperateLog
+    @SaCheckPermission("sms:channel:edit")
+    public CommonResult<Boolean> edit(@RequestBody@Validated(Update.class) SysSmsChannelDTO source) {
+        return CommonResult.success(sysSmsChannelService.updateSysSmsChannelById(source));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 5)
+    @ApiOperation(value="删除短信渠道",notes = "权限: sms:channel:remove")
+    @PostMapping("/removeByIds")
+    @OperateLog
+    @SaCheckPermission("sms:channel:remove")
+    public CommonResult<Integer> delete(@RequestBody Collection<String> ids) {
+        return CommonResult.success(sysSmsChannelService.removeSysSmsChannelByIds(ids));
+    }
+}

+ 59 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SysSmsLogController.java

@@ -0,0 +1,59 @@
+package cn.tr.module.sys.sms.controller;
+
+import cn.tr.core.pojo.TableDataInfo;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.tr.core.pojo.CommonResult;
+import lombok.AllArgsConstructor;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import cn.tr.module.sys.sms.dto.SysSmsLogDTO;
+import cn.tr.module.sys.sms.service.ISysSmsLogService;
+import cn.tr.module.sys.sms.dto.SysSmsLogQueryDTO;
+import java.util.*;
+import cn.tr.plugin.mybatis.base.BaseController;
+import org.springframework.web.bind.annotation.*;
+import cn.tr.plugin.operatelog.annotation.OperateLog;
+/**
+ * 短信日志控制器
+ *
+ * @author lf
+ * @date  2023/04/21 16:53
+ */
+@Api(tags = "短信日志")
+@RestController
+@RequestMapping("/sms/log")
+@AllArgsConstructor
+public class SysSmsLogController extends BaseController{
+
+    private final ISysSmsLogService sysSmsLogService;
+
+    @ApiOperationSupport(author = "lf",order = 1)
+    @ApiOperation(value="根据条件查询短信日志",notes = "权限: 无")
+    @PostMapping("/query/page")
+    public TableDataInfo<SysSmsLogDTO> selectList(@RequestBody SysSmsLogQueryDTO query) {
+        startPage();
+        return getDataTable(sysSmsLogService.selectSysSmsLogList(query));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 2)
+    @ApiOperation(value = "根据id查询短信日志",notes = "权限: sms:log:query")
+    @GetMapping("/detail/{id}")
+    @SaCheckPermission("sms:log:query")
+    public CommonResult<SysSmsLogDTO> findById(@PathVariable("id") String id){
+        return CommonResult.success(sysSmsLogService.selectSysSmsLogById(id));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 5)
+    @ApiOperation(value="删除短信日志",notes = "权限: sms:log:remove")
+    @PostMapping("/removeByIds")
+    @OperateLog
+    @SaCheckPermission("sms:log:remove")
+    public CommonResult<Integer> delete(@RequestBody Collection<String> ids) {
+        return CommonResult.success(sysSmsLogService.removeSysSmsLogByIds(ids));
+    }
+}

+ 94 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/controller/SysSmsTempController.java

@@ -0,0 +1,94 @@
+package cn.tr.module.sys.sms.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.tr.core.pojo.CommonResult;
+import cn.tr.core.pojo.TableDataInfo;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import cn.tr.module.sys.sms.dto.SmsTemplateSendDTO;
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+import cn.tr.module.sys.sms.dto.SysSmsTempQueryDTO;
+import cn.tr.module.sys.sms.service.ISmsSendService;
+import cn.tr.module.sys.sms.service.ISysSmsTempService;
+import cn.tr.module.sys.user.enums.CreateEnum;
+import cn.tr.plugin.mybatis.base.BaseController;
+import cn.tr.plugin.operatelog.annotation.OperateLog;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 短信模板控制器
+ *
+ * @author lf
+ * @date  2023/04/21 10:29
+ */
+@Api(tags = "短信模板")
+@RestController
+@RequestMapping("/sms/temp")
+@AllArgsConstructor
+public class SysSmsTempController extends BaseController{
+
+    private final ISysSmsTempService sysSmsTempService;
+    private final ISmsSendService smsSendService;
+
+    @ApiOperationSupport(author = "lf",order = 1)
+    @ApiOperation(value="根据条件查询短信模板",notes = "权限: 无")
+    @PostMapping("/query/page")
+    public TableDataInfo<SysSmsTempDTO> selectList(@RequestBody SysSmsTempQueryDTO query) {
+        startPage();
+        return getDataTable(sysSmsTempService.selectSysSmsTempList(query));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 2)
+    @ApiOperation(value = "根据id查询短信模板",notes = "权限: sms:temp:query")
+    @GetMapping("/detail/{id}")
+    @SaCheckPermission("sms:temp:query")
+    public CommonResult<SysSmsTempDTO> findById(@PathVariable("id") String id){
+        return CommonResult.success(sysSmsTempService.selectSysSmsTempById(id));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 3)
+    @ApiOperation(value="添加短信模板",notes = "权限: sms:temp:add")
+    @PostMapping("/add")
+    @OperateLog
+    @SaCheckPermission("sms:temp:add")
+    public CommonResult<Boolean> add(@RequestBody@Validated(Insert.class) SysSmsTempDTO source) {
+        source.setCreateType(CreateEnum.custom.name());
+        return CommonResult.success(sysSmsTempService.insertSysSmsTemp(source));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 4)
+    @ApiOperation(value="通过主键id编辑短信模板",notes = "权限: sms:temp:edit")
+    @PostMapping("/edit")
+    @OperateLog
+    @SaCheckPermission("sms:temp:edit")
+    public CommonResult<Boolean> edit(@RequestBody@Validated(Update.class) SysSmsTempDTO source) {
+        return CommonResult.success(sysSmsTempService.updateSysSmsTempById(source));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 5)
+    @ApiOperation(value="删除短信模板",notes = "权限: sms:temp:remove")
+    @PostMapping("/removeByIds")
+    @OperateLog
+    @SaCheckPermission("sms:temp:remove")
+    public CommonResult<Integer> delete(@RequestBody Collection<String> ids) {
+        return CommonResult.success(sysSmsTempService.removeSysSmsTempByIds(ids));
+    }
+
+    @PostMapping("/send/msg")
+    @ApiOperation(value="发送短信",notes = "权限: sms:temp:send")
+    @OperateLog
+    @SaCheckPermission(" sms:temp:send")
+    public CommonResult<String> sendSms(@Valid @RequestBody SmsTemplateSendDTO source) {
+        return CommonResult.success(smsSendService.sendSingleSmsToAdmin(source.getMobile(), null,
+                source.getTemplateCode(), source.getTemplateParams()));
+    }
+}

+ 25 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SmsTemplateSendDTO.java

@@ -0,0 +1,25 @@
+package cn.tr.module.sys.sms.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@ApiModel("短信发送对象")
+@Data
+public class SmsTemplateSendDTO {
+
+    @ApiModelProperty(value = "手机号", required = true, example = "15601691300")
+    @NotNull(message = "手机号不能为空")
+    private String mobile;
+
+    @ApiModelProperty(value = "模板编码", required = true, example = "test_01")
+    @NotNull(message = "模板编码不能为空")
+    private String templateCode;
+
+    @ApiModelProperty(value = "模板参数")
+    private Map<String, Object> templateParams;
+
+}

+ 63 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsChannelDTO.java

@@ -0,0 +1,63 @@
+package cn.tr.module.sys.sms.dto;
+
+import cn.tr.plugin.desensitize.config.slider.annotation.ChineseNameDesensitize;
+import cn.tr.plugin.desensitize.config.slider.annotation.PasswordDesensitize;
+import cn.tr.plugin.mybatis.pojo.BaseDTO;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 短信渠道传输对象
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ **/
+@Data
+@ApiModel("短信渠道传输对象")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysSmsChannelDTO extends BaseDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "主键", position = 1)
+    @NotBlank(message = "主键不能为空",groups = {Update.class})
+    private String id;
+
+    @ApiModelProperty(value = "渠道名称", position = 2)
+    @NotBlank(message = "渠道名称不能为空",groups = {Update.class,Insert.class})
+    private String name;
+
+    @ApiModelProperty(value = "短信签名", position = 3)
+    @NotBlank(message = "短信签名不能为空",groups = {Update.class,Insert.class})
+    private String signature;
+
+    @ApiModelProperty(value = "渠道类型", position = 4)
+    @NotBlank(message = "渠道类型不能为空",groups = {Update.class,Insert.class})
+    private String type;
+
+    @ApiModelProperty(value = "短信 API 的账号", position = 5)
+    @NotBlank(message = "短信 API 的账号不能为空",groups = {Update.class,Insert.class})
+    private String apiKey;
+
+    @ApiModelProperty(value = "短信 API 的秘钥", position = 6)
+    @NotBlank(message = "短信 API 的秘钥不能为空",groups = {Update.class,Insert.class})
+    @PasswordDesensitize(prefixKeep = 3,suffixKeep = 3)
+    private String apiSecret;
+
+    @ApiModelProperty(value = "短信发送回调 URL", position = 7)
+    private String callbackUrl;
+
+    @ApiModelProperty(value = "是否启用", position = 8)
+    @NotNull(message = "启用状态不能为空",groups = {Update.class,Insert.class})
+    private Boolean disable;
+
+    @ApiModelProperty(value = "备注", position = 9)
+    private String remark;
+}

+ 28 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsChannelQueryDTO.java

@@ -0,0 +1,28 @@
+package cn.tr.module.sys.sms.dto;
+
+import lombok.ToString;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 短信渠道查询参数
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ **/
+@Data
+@ApiModel("短信渠道查询参数")
+@ToString
+public class SysSmsChannelQueryDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "渠道名称", position = 2)
+    private String name;
+
+    @ApiModelProperty(value = "渠道类型", position = 4)
+    private String type;
+
+    @ApiModelProperty(value = "是否启用", position = 8)
+    private Boolean disable;
+
+}

+ 73 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsLogDTO.java

@@ -0,0 +1,73 @@
+package cn.tr.module.sys.sms.dto;
+
+import cn.tr.plugin.mybatis.pojo.BaseDTO;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import cn.tr.core.validation.Update;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import javax.validation.constraints.NotNull;
+import java.util.*;
+/**
+ * 短信日志传输对象
+ *
+ * @author lf
+ * @date  2023/04/21 17:06
+ **/
+@Data
+@ApiModel("短信日志传输对象")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@Builder
+public class SysSmsLogDTO extends BaseDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "id", position = 1)
+    @NotNull(message = "主键不能为空",groups = {Update.class})
+    private String id;
+    @ApiModelProperty(value = "渠道id", position = 2)
+    private String channelId;
+    @ApiModelProperty(value = "模板id", position = 3)
+    private String templateId;
+    @ApiModelProperty(value = "模板编码", position = 4)
+    private String templateCode;
+    @ApiModelProperty(value = "模板类型", position = 5)
+    private String templateType;
+    @ApiModelProperty(value = "模板参数", position = 6)
+    private Map<String,Object> templateParams;
+    @ApiModelProperty(value = "模板内容", position = 7)
+    private String templateContent;
+    @ApiModelProperty(value = "短信API的模板编号", position = 8)
+    private String apiTemplateId;
+    @ApiModelProperty(value = "手机号", position = 9)
+    private String mobile;
+    @ApiModelProperty(value = "接收用户id", position = 10)
+    private String userId;
+    @ApiModelProperty(value = "用户类型", position = 11)
+    private String userType;
+    @ApiModelProperty(value = "发送状态", position = 12)
+    private String sendStatus;
+    @ApiModelProperty(value = "发送时间", position = 13)
+    private Date sendTime;
+    @ApiModelProperty(value = "发送结果编码", position = 14)
+    private String sendCode;
+    @ApiModelProperty(value = "发送消息", position = 15)
+    private String sendMsg;
+    @ApiModelProperty(value = "API 结果编号", position = 16)
+    private String apiSendCode;
+    @ApiModelProperty(value = "API 错误信息", position = 17)
+    private String apiSendMsg;
+    @ApiModelProperty(value = "API 请求编号", position = 18)
+    private String apiRequestId;
+    @ApiModelProperty(value = "API 返回序号", position = 19)
+    private String apiSerialNo;
+    @ApiModelProperty(value = "接收状态", position = 20)
+    private String receiveStatus;
+    @ApiModelProperty(value = "接收时间", position = 21)
+    private Date receiveTime;
+    @ApiModelProperty(value = "API 接收结果编码", position = 22)
+    private String apiReceiveCode;
+    @ApiModelProperty(value = "API 接收结果提示", position = 23)
+    private String apiReceiveMsg;
+}

+ 41 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsLogQueryDTO.java

@@ -0,0 +1,41 @@
+package cn.tr.module.sys.sms.dto;
+
+import lombok.ToString;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import cn.tr.plugin.mybatis.pojo.BetweenQuery;
+import java.util.*;
+/**
+ * 短信日志查询参数
+ *
+ * @author lf
+ * @date  2023/04/21 17:06
+ **/
+@Data
+@ApiModel("短信日志查询参数")
+@ToString
+public class SysSmsLogQueryDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "渠道id", position = 2)
+    private String channelId;
+
+    @ApiModelProperty(value = "模板id", position = 3)
+    private String templateId;
+
+    @ApiModelProperty(value = "模板编码", position = 4)
+    private String templateCode;
+
+    @ApiModelProperty(value = "手机号", position = 9)
+    private String mobile;
+
+    @ApiModelProperty(value = "发送状态", position = 12)
+    private String sendStatus;
+
+    @ApiModelProperty(value = "发送时间", position = 13)
+    private BetweenQuery<Date> sendTime;
+
+    @ApiModelProperty(value = "接收状态", position = 20)
+    private String receiveStatus;
+
+}

+ 81 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsTempDTO.java

@@ -0,0 +1,81 @@
+package cn.tr.module.sys.sms.dto;
+
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.tr.plugin.mybatis.pojo.BaseDTO;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * 短信模板传输对象
+ *
+ * @author lf
+ * @date  2023/04/21 19:05
+ **/
+@Data
+@ApiModel("短信模板传输对象")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysSmsTempDTO extends BaseDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "主键", position = 1)
+    @NotNull(message = "主键不能为空",groups = {Update.class})
+    private String id;
+
+    @ApiModelProperty(value = "短信渠道id", position = 2,required = true)
+    @NotBlank(message = "短信渠道不能为空",groups = {Update.class,Insert.class})
+    private String channelId;
+
+    @ApiModelProperty(value = "创建类型", position = 3,readOnly = true)
+    private String createType;
+
+    @ApiModelProperty(value = "模板类型", position = 4,required = true)
+    @NotBlank(message = "模板类型不能为空",groups = {Update.class,Insert.class})
+    private String tempType;
+
+    @ApiModelProperty(value = "模板内容", position = 5)
+    private String content;
+
+
+    @ApiModelProperty(value = "模板编码", position = 6,required = true)
+    @NotBlank(message = "模板编码不能为空",groups = {Update.class,Insert.class})
+    private String code;
+
+    @ApiModelProperty(value = "模板名称", position = 7,required = true)
+    @NotBlank(message = "模板名称不能为空",groups = {Update.class,Insert.class})
+    private String name;
+
+    @ApiModelProperty(value = "短信 API 模板编号", position = 8)
+    @NotBlank(message = "短信 API 模板编号不能为空",groups = {Update.class,Insert.class})
+    private String apiTempCode;
+
+    @ApiModelProperty(value = "状态", position = 9,required = true)
+    @NotNull(message = "状态不能为空",groups = {Update.class,Insert.class})
+    private Boolean disable;
+
+    @ApiModelProperty(value = "备注", position = 10)
+    private String remark;
+
+
+    private static final java.util.regex.Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
+
+
+
+    public List<String> getTemplateParams(){
+
+        if(StrUtil.isEmpty(content)){
+            return Collections.emptyList();
+        }
+        return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
+    }
+}

+ 31 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/dto/SysSmsTempQueryDTO.java

@@ -0,0 +1,31 @@
+package cn.tr.module.sys.sms.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 短信模板查询参数
+ *
+ * @author lf
+ * @date  2023/04/21 10:29
+ **/
+@Data
+@ApiModel("短信模板查询参数")
+@ToString
+public class SysSmsTempQueryDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "创建类型", position = 3)
+    private String createType;
+
+    @ApiModelProperty(value = "模板类型", position = 4)
+    private String tempType;
+
+    @ApiModelProperty(value = "模板编码", position = 5)
+    private String code;
+
+    @ApiModelProperty(value = "状态", position = 8)
+    private Boolean disable;
+
+}

+ 25 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsReceiveStatusEnum.java

@@ -0,0 +1,25 @@
+package cn.tr.module.sys.sms.enums;
+
+import cn.tr.core.enums.IEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信的接收状态枚举
+ *
+ * @author 芋道源码
+ * @date 2021/2/1 13:39
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsReceiveStatusEnum implements IEnum<String> {
+
+    INIT("init","初始化"),
+    SUCCESS("success","接收成功"),
+    FAILURE("fail","接收失败"),
+    ;
+
+    private String value;
+    private String label;
+
+}

+ 45 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsSceneEnum.java

@@ -0,0 +1,45 @@
+package cn.tr.module.sys.sms.enums;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.tr.core.enums.IEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 用户短信验证码发送场景的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsSceneEnum implements IEnum<String> {
+
+    MEMBER_LOGIN(1, "user-sms-login", "会员用户 - 手机号登陆"),
+    MEMBER_UPDATE_MOBILE(2, "user-sms-reset-password", "会员用户 - 修改手机"),
+    MEMBER_FORGET_PASSWORD(3, "user-sms-update-mobile", "会员用户 - 忘记密码"),
+
+    ADMIN_MEMBER_LOGIN(21, "admin-sms-login", "后台用户 - 手机号登录");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsSceneEnum::getScene).toArray();
+
+    /**
+     * 验证场景的编号
+     */
+    private final Integer scene;
+    /**
+     * 模版编码
+     */
+    private final String value;
+    /**
+     * 描述
+     */
+    private final String label;
+
+    public static SmsSceneEnum getCodeByScene(Integer scene) {
+        return ArrayUtil.firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene),
+                values());
+    }
+
+}

+ 26 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsSendStatusEnum.java

@@ -0,0 +1,26 @@
+package cn.tr.module.sys.sms.enums;
+
+import cn.tr.core.enums.IEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信的发送状态枚举
+ *
+ * @author zzf
+ * @date 2021/2/1 13:39
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsSendStatusEnum implements IEnum<String> {
+
+    INIT("init","初始化"),
+    SUCCESS("success","发送成功"),
+    FAILURE("fail","发送失败"),
+    IGNORE("ignore","不发送");
+
+
+    private String value;
+    private String label;
+
+}

+ 24 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/enums/SmsTemplateTypeEnum.java

@@ -0,0 +1,24 @@
+package cn.tr.module.sys.sms.enums;
+
+import cn.tr.core.enums.IEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信的模板类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsTemplateTypeEnum implements IEnum<String> {
+
+    VERIFICATION_CODE("1","验证码"),
+    NOTICE("2","通知"),
+    PROMOTION("3","营销"),
+    ;
+
+    private String value;
+    private String label;
+
+}

+ 61 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/po/SysSmsChannelPO.java

@@ -0,0 +1,61 @@
+package cn.tr.module.sys.sms.po;
+
+
+import cn.tr.plugin.mybatis.pojo.BasePO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 短信渠道实体
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ **/
+@Data
+@TableName("sys_sms_channel")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysSmsChannelPO extends BasePO {
+
+    /** 主键 */
+    @TableId
+    @ApiModelProperty(value = "主键", position = 1)
+    private String id;
+
+    /** 渠道名称 */
+    @ApiModelProperty(value = "渠道名称", position = 2)
+    private String name;
+
+    /** 短信签名 */
+    @ApiModelProperty(value = "短信签名", position = 3)
+    private String signature;
+
+    /** 渠道类型 */
+    @ApiModelProperty(value = "渠道类型", position = 4)
+    private String type;
+
+    /** 短信 API 的账号 */
+    @ApiModelProperty(value = "短信 API 的账号", position = 5)
+    private String apiKey;
+
+    /** 短信 API 的秘钥 */
+    @ApiModelProperty(value = "短信 API 的秘钥", position = 6)
+    private String apiSecret;
+
+    /** 短信发送回调 URL */
+    @ApiModelProperty(value = "短信发送回调 URL", position = 7)
+    private String callbackUrl;
+
+    /** 是否启用 */
+    @ApiModelProperty(value = "是否启用", position = 8)
+    private Boolean disable;
+
+    /** 备注 */
+    @ApiModelProperty(value = "备注", position = 9)
+    private String remark;
+
+}

+ 125 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/po/SysSmsLogPO.java

@@ -0,0 +1,125 @@
+package cn.tr.module.sys.sms.po;
+
+
+import cn.tr.plugin.mybatis.pojo.BasePO;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import java.util.*;
+/**
+ * 短信日志实体
+ *
+ * @author lf
+ * @date  2023/04/21 17:06
+ **/
+@Data
+@TableName("sys_sms_log")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysSmsLogPO extends BasePO {
+
+    /** id */
+    @TableId
+    @ApiModelProperty(value = "id", position = 1)
+    private String id;
+
+    /** 渠道id */
+    @ApiModelProperty(value = "渠道id", position = 2)
+    private String channelId;
+
+    /***********模板部分***********/
+
+    /** 模板id */
+    @ApiModelProperty(value = "模板id", position = 3)
+    private String templateId;
+
+    /** 模板编码 */
+    @ApiModelProperty(value = "模板编码", position = 4)
+    private String templateCode;
+
+    /** 模板类型 */
+    @ApiModelProperty(value = "模板类型", position = 5)
+    private String templateType;
+
+    /** 模板参数 */
+    @ApiModelProperty(value = "模板参数", position = 6)
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Map<String,Object> templateParams;
+
+    /** 模板内容 */
+    @ApiModelProperty(value = "模板内容", position = 7)
+    private String templateContent;
+
+    /** 短信API的模板编号 */
+    @ApiModelProperty(value = "短信API的模板编号", position = 8)
+    private String apiTemplateId;
+
+    /** 手机号 */
+    @ApiModelProperty(value = "手机号", position = 9)
+    private String mobile;
+
+    /** 接收用户id */
+    @ApiModelProperty(value = "接收用户id", position = 10)
+    private String userId;
+
+    /** 用户类型 */
+    @ApiModelProperty(value = "用户类型", position = 11)
+    private String userType;
+
+    /** 发送状态 */
+    @ApiModelProperty(value = "发送状态", position = 12)
+    private String sendStatus;
+
+    /** 发送时间 */
+    @ApiModelProperty(value = "发送时间", position = 13)
+    private Date sendTime;
+
+    /** 发送结果编码 */
+    @ApiModelProperty(value = "发送结果编码", position = 14)
+    private String sendCode;
+
+    /** 发送消息 */
+    @ApiModelProperty(value = "发送消息", position = 15)
+    private String sendMsg;
+
+    /***********服务器 -》 第三方服务 的响应数据***********/
+
+    /** API 结果编号 */
+    @ApiModelProperty(value = "API 结果编号", position = 16)
+    private String apiSendCode;
+
+    /** API 错误信息 */
+    @ApiModelProperty(value = "API 错误信息", position = 17)
+    private String apiSendMsg;
+
+    /** API 请求编号 */
+    @ApiModelProperty(value = "API 请求编号", position = 18)
+    private String apiRequestId;
+
+    /** API 返回序号 */
+    @ApiModelProperty(value = "API 返回序号", position = 19)
+    private String apiSerialNo;
+
+    /***********第三方服务 -》 用户的响应数据***********/
+    /** 接收状态 */
+    @ApiModelProperty(value = "接收状态", position = 20)
+    private String receiveStatus;
+
+    /** 接收时间 */
+    @ApiModelProperty(value = "接收时间", position = 21)
+    private Date receiveTime;
+
+    /** API 接收结果编码 */
+    @ApiModelProperty(value = "API 接收结果编码", position = 22)
+    private String apiReceiveCode;
+
+    /** API 接收结果提示 */
+    @ApiModelProperty(value = "API 接收结果提示", position = 23)
+    private String apiReceiveMsg;
+
+}

+ 64 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/po/SysSmsTempPO.java

@@ -0,0 +1,64 @@
+package cn.tr.module.sys.sms.po;
+
+
+import cn.tr.plugin.mybatis.pojo.BasePO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 短信模板实体
+ *
+ * @author lf
+ * @date  2023/04/21 10:29
+ **/
+@Data
+@TableName("sys_sms_temp")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysSmsTempPO extends BasePO {
+
+    /** 主键 */
+    @TableId
+    @ApiModelProperty(value = "主键", position = 1)
+    private String id;
+
+    /** 短信渠道id */
+    @ApiModelProperty(value = "短信渠道id", position = 2)
+    private String channelId;
+
+    /** 创建类型 */
+    @ApiModelProperty(value = "创建类型", position = 3)
+    private String createType;
+
+    /** 模板类型 */
+    @ApiModelProperty(value = "模板类型", position = 4)
+    private String tempType;
+
+    @ApiModelProperty(value = "模板内容", position = 5)
+    private String content;
+
+    /** 模板编码 */
+    @ApiModelProperty(value = "模板编码", position = 6)
+    private String code;
+
+    /** 模板名称 */
+    @ApiModelProperty(value = "模板名称", position = 7)
+    private String name;
+
+    /** 短信 API 模板编号 */
+    @ApiModelProperty(value = "短信 API 模板编号", position = 8)
+    private String apiTempCode;
+
+    /** 状态 */
+    @ApiModelProperty(value = "状态", position = 9)
+    private Boolean disable;
+
+    /** 备注 */
+    @ApiModelProperty(value = "备注", position = 10)
+    private String remark;
+
+}

+ 16 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/repository/SysSmsChannelRepository.java

@@ -0,0 +1,16 @@
+package cn.tr.module.sys.sms.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+import cn.tr.module.sys.sms.po.SysSmsChannelPO;
+/**
+ * 短信渠道Mapper接口
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ **/
+@Repository
+@Mapper
+public interface SysSmsChannelRepository extends BaseMapper<SysSmsChannelPO> {
+}

+ 16 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/repository/SysSmsLogRepository.java

@@ -0,0 +1,16 @@
+package cn.tr.module.sys.sms.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+import cn.tr.module.sys.sms.po.SysSmsLogPO;
+/**
+ * 短信日志Mapper接口
+ *
+ * @author lf
+ * @date  2023/04/21 16:53
+ **/
+@Repository
+@Mapper
+public interface SysSmsLogRepository extends BaseMapper<SysSmsLogPO> {
+}

+ 17 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/repository/SysSmsTempRepository.java

@@ -0,0 +1,17 @@
+package cn.tr.module.sys.sms.repository;
+
+import cn.tr.module.sys.sms.po.SysSmsTempPO;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 短信模板Mapper接口
+ *
+ * @author lf
+ * @date  2023/04/21 10:29
+ **/
+@Repository
+@Mapper
+public interface SysSmsTempRepository extends BaseMapper<SysSmsTempPO> {
+}

+ 80 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISmsSendService.java

@@ -0,0 +1,80 @@
+package cn.tr.module.sys.sms.service;
+
+import cn.tr.module.sys.mq.message.sms.SmsSendMessage;
+
+import java.util.Map;
+
+/**
+ * @ClassName : ISmsSendService
+ * @Description : 短信发送服务
+ * @Author : LF
+ * @Date: 2023年04月21日
+ */
+public interface ISmsSendService {
+
+    /**
+     * 发送单条短信给管理后台的用户
+     *
+     * 在 mobile 为空时,使用 userId 加载对应管理员的手机号
+     *
+     * @param mobile 手机号
+     * @param userId 户编号
+     * @param templateCode 短信模板编号
+     * @param templateParams 短信模板参数
+     * @return 发送日志编号
+     */
+    String sendSingleSmsToAdmin(String mobile, String userId,
+                                String templateCode, Map<String, Object> templateParams);
+
+    default String sendSingleSmsToAdmin(String userId, String templateCode, Map<String, Object> templateParams){
+        return sendSingleSmsToAdmin(null,userId,templateCode,templateParams);
+    };
+
+    /**
+     * 发送单条短信给用户 APP 的用户
+     *
+     * 在 mobile 为空时,使用 userId 加载对应会员的手机号
+     *
+     * @param mobile 手机号
+     * @param userId 用户编号
+     * @param templateCode 短信模板编号
+     * @param templateParams 短信模板参数
+     * @return 发送日志编号
+     */
+    String sendSingleSmsToMember(String mobile, String userId,
+                                 String templateCode, Map<String, Object> templateParams);
+
+    default String sendSingleSmsToMember(String userId, String templateCode, Map<String, Object> templateParams){
+        return sendSingleSmsToMember(null,userId,templateCode,templateParams);
+    };
+
+    /**
+     * 发送单条短信给用户
+     *
+     * @param mobile 手机号
+     * @param userId 用户编号
+     * @param userType 用户类型 {@link cn.tr.core.enums.UserTypeEnum}
+     * @param templateCode 短信模板编号
+     * @param templateParams 短信模板参数
+     * @return 发送日志编号
+     */
+    String sendSingleSms(String mobile, String userId, String userType,
+                         String templateCode, Map<String, Object> templateParams);
+
+    /**
+     * 执行真正的短信发送
+     * 注意,该方法仅仅提供给 MQ Consumer 使用
+     *
+     * @param message 短信
+     */
+    void doSendSms(SmsSendMessage message);
+
+    /**
+     * 接收短信的接收结果
+     *
+     * @param channelCode 渠道编码
+     * @param text 结果内容
+     * @throws Throwable 处理失败时,抛出异常
+     */
+    void receiveSmsStatus(String channelCode, String text) throws Throwable;
+}

+ 62 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISysSmsChannelService.java

@@ -0,0 +1,62 @@
+package cn.tr.module.sys.sms.service;
+
+import cn.tr.module.sys.sms.dto.SysSmsChannelDTO;
+import cn.tr.module.sys.sms.dto.SysSmsChannelQueryDTO;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+
+/**
+ * 短信渠道Service接口
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ **/
+public interface ISysSmsChannelService{
+
+
+    /**
+     * 刷新短信渠道本地缓存
+     */
+    void initLocalCache();
+
+    /**
+     * 根据条件查询短信渠道
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/04/19 18:18
+     */
+    List<SysSmsChannelDTO> selectSysSmsChannelList(SysSmsChannelQueryDTO query);
+
+    /**
+     * 根据id查询短信渠道
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/04/19 18:18
+     */
+    SysSmsChannelDTO selectSysSmsChannelById(String id);
+
+    /**
+     * 编辑短信渠道
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/04/19 18:18
+     */
+    boolean updateSysSmsChannelById(SysSmsChannelDTO source);
+
+    /**
+     * 新增短信渠道
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/04/19 18:18
+     */
+    boolean insertSysSmsChannel(SysSmsChannelDTO source);
+
+    /**
+     * 删除短信渠道详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/04/19 18:18
+     */
+    int removeSysSmsChannelByIds(Collection<String> ids);
+}

+ 83 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISysSmsLogService.java

@@ -0,0 +1,83 @@
+package cn.tr.module.sys.sms.service;
+
+import cn.tr.module.sys.sms.dto.SysSmsLogDTO;
+import cn.tr.module.sys.sms.dto.SysSmsLogQueryDTO;
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * 短信日志Service接口
+ *
+ * @author lf
+ * @date  2023/04/21 16:53
+ **/
+public interface ISysSmsLogService{
+
+    /**
+     * 根据条件查询短信日志
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/04/21 16:53
+     */
+    List<SysSmsLogDTO> selectSysSmsLogList(SysSmsLogQueryDTO query);
+
+    /**
+     * 根据id查询短信日志
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/04/21 16:53
+     */
+    SysSmsLogDTO selectSysSmsLogById(String id);
+
+    /**
+     * 删除短信日志详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/04/21 16:53
+     */
+    int removeSysSmsLogByIds(Collection<String> ids);
+
+
+    /**
+     * 创建短信日志
+     *
+     * @param mobile 手机号
+     * @param userId 用户编号
+     * @param userType 用户类型
+     * @param isSend 是否发送
+     * @param template 短信模板
+     * @param templateContent 短信内容
+     * @param templateParams 短信参数
+     * @return 发送日志编号
+     */
+    String createSmsLog(String mobile, String userId, String userType, Boolean isSend,
+                      SysSmsTempDTO template, String templateContent, Map<String, Object> templateParams);
+
+    /**
+     * 更新日志的发送结果
+     *
+     * @param id 日志编号
+     * @param sendCode 发送结果的编码
+     * @param sendMsg 发送结果的提示
+     * @param apiSendCode 短信 API 发送结果的编码
+     * @param apiSendMsg 短信 API 发送失败的提示
+     * @param apiRequestId 短信 API 发送返回的唯一请求 ID
+     * @param apiSerialNo 短信 API 发送返回的序号
+     */
+    void updateSmsSendResult(String id, String sendCode, String sendMsg,
+                             String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo);
+
+    /**
+     * 更新日志的接收结果
+     *
+     * @param id 日志编号
+     * @param success 是否接收成功
+     * @param receiveTime 用户接收时间
+     * @param apiReceiveCode API 接收结果的编码
+     * @param apiReceiveMsg API 接收结果的说明
+     */
+    void updateSmsReceiveResult(String id, Boolean success, Date receiveTime, String apiReceiveCode, String apiReceiveMsg);
+
+}

+ 72 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/ISysSmsTempService.java

@@ -0,0 +1,72 @@
+package cn.tr.module.sys.sms.service;
+
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+import cn.tr.module.sys.sms.dto.SysSmsTempQueryDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 短信模板Service接口
+ *
+ * @author lf
+ * @date  2023/04/21 10:29
+ **/
+public interface ISysSmsTempService{
+
+    /**
+     * 根据条件查询短信模板
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/04/21 10:29
+     */
+    List<SysSmsTempDTO> selectSysSmsTempList(SysSmsTempQueryDTO query);
+
+    /**
+     * 根据id查询短信模板
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/04/21 10:29
+     */
+    SysSmsTempDTO selectSysSmsTempById(String id);
+
+    /**
+     * 编辑短信模板
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/04/21 10:29
+     */
+    boolean updateSysSmsTempById(SysSmsTempDTO source);
+
+    /**
+     * 新增短信模板
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/04/21 10:29
+     */
+    boolean insertSysSmsTemp(SysSmsTempDTO source);
+
+    /**
+     * 删除短信模板详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/04/21 10:29
+     */
+    int removeSysSmsTempByIds(Collection<String> ids);
+
+    /**
+     * 格式化模板内容
+     * @param content           模板内容
+     * @param templateParams    模板参数内容
+     * @return  格式化信息
+     */
+    String formatSmsTemplateContent(String content, Map<String, Object> templateParams);
+
+    /**
+     * 根据模板编码获取模板信息
+     * @param templateCode 模板编码
+     * @return 模板信息
+     */
+    SysSmsTempDTO selectSysSmsTempByTemplateCode(String templateCode);
+}

+ 178 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SmsSendServiceImpl.java

@@ -0,0 +1,178 @@
+package cn.tr.module.sys.sms.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.tr.core.enums.UserTypeEnum;
+import cn.tr.core.exception.ServiceException;
+import cn.tr.core.exception.TRExcCode;
+import cn.tr.core.pojo.KeyValue;
+import cn.tr.module.sys.mq.message.sms.SmsSendMessage;
+import cn.tr.module.sys.mq.producer.sms.SmsSupplier;
+import cn.tr.module.sys.sms.dto.SysSmsChannelDTO;
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+import cn.tr.module.sys.sms.service.ISmsSendService;
+import cn.tr.module.sys.sms.service.ISysSmsChannelService;
+import cn.tr.module.sys.sms.service.ISysSmsLogService;
+import cn.tr.module.sys.sms.service.ISysSmsTempService;
+import cn.tr.module.sys.user.dto.SysUserDTO;
+import cn.tr.module.sys.user.service.ISysUserService;
+import cn.tr.plugin.sms.bo.SmsCommonResult;
+import cn.tr.plugin.sms.bo.SmsReceiveRespBO;
+import cn.tr.plugin.sms.bo.SmsSendRespBO;
+import cn.tr.plugin.sms.config.SmsClient;
+import cn.tr.plugin.sms.config.SmsClientFactory;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @ClassName : SmsSendServiceImpl
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年04月21日
+ */
+@Service
+@Slf4j
+@AllArgsConstructor
+public class SmsSendServiceImpl implements ISmsSendService {
+    private final ISysUserService userService;
+
+    private final ISysSmsTempService smsTempService;
+
+    private final ISysSmsChannelService smsChannelService;
+
+    private final ISysSmsLogService smsLogService;
+
+    private final SmsClientFactory smsClientFactory;
+
+    private final SmsSupplier smsSupplier;
+    @Override
+    public String sendSingleSmsToAdmin(String mobile, String userId, String templateCode, Map<String, Object> templateParams) {
+        if (StrUtil.isEmpty(mobile)) {
+            SysUserDTO user = userService.selectSysUserById(userId);
+            if(Objects.isNull(user)){
+                throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"用户不存在,信息发送失败");
+            }
+            mobile=user.getPhone();
+        }
+        return sendSingleSms(mobile,userId,UserTypeEnum.ADMIN,templateCode, templateParams);
+    }
+
+    @Override
+    public String sendSingleSmsToMember(String mobile, String userId, String templateCode, Map<String, Object> templateParams) {
+        //todo
+        return null;
+    }
+
+    @Override
+    public String sendSingleSms(String mobile, String userId, String userType, String templateCode, Map<String, Object> templateParams) {
+        // 校验短信模板是否合法
+        SysSmsTempDTO template = validateSmsTemplate(templateCode);
+        // 校验短信渠道是否合法
+        SysSmsChannelDTO smsChannel = validateSmsChannel(template.getChannelId());
+
+        // 校验手机号码是否存在
+        mobile = validateMobile(mobile);
+
+        // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
+        List<KeyValue<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);
+
+        // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
+        boolean isSend =Boolean.FALSE.equals(template.getDisable())
+                &&  Boolean.FALSE.equals(smsChannel.getDisable());
+
+        String content = smsTempService.formatSmsTemplateContent(template.getContent(), templateParams);
+        String sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);
+        // 发送 MQ 消息,异步执行发送短信
+        if (isSend) {
+            smsSupplier.sendSmsSendMessage(sendLogId, mobile, template.getChannelId(),
+                    template.getApiTempCode(), newTemplateParams);
+        }
+        return sendLogId;
+    }
+
+    @Override
+    public void doSendSms(SmsSendMessage message) {
+        // 获得渠道对应的 SmsClient 客户端
+        SmsClient smsClient = smsClientFactory.getSmsClientByChannelId(message.getChannelId());
+        if (Objects.isNull(smsClient)) {
+            smsLogService.updateSmsSendResult(message.getLogId(), null, "短信客户端不存在",
+                    null,null, null, null);
+        }else {
+            // 发送短信
+            SmsCommonResult<SmsSendRespBO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(),
+                    message.getApiTemplateId(), message.getTemplateParams());
+            smsLogService.updateSmsSendResult(message.getLogId(), sendResult.getCode(), sendResult.getErrorMsg(),
+                    sendResult.getApiCode(), sendResult.getApiMsg(), sendResult.getApiRequestId(),
+                    sendResult.getData() != null ? sendResult.getData().getSerialNo() : null);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void receiveSmsStatus(String channelCode, String text) throws Throwable {
+        // 获得渠道对应的 SmsClient 客户端
+        SmsClient smsClient = smsClientFactory.getSmsClientByChannelType(channelCode);
+        if(smsClient==null){
+            return;
+        }
+        // 解析内容
+        List<SmsReceiveRespBO> receiveResults = smsClient.parseSmsReceiveStatus(text);
+        if (CollUtil.isEmpty(receiveResults)) {
+            return;
+        }
+        // 更新短信日志的接收结果. 因为量一般不大,所以先使用 for 循环更新
+        receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(),
+                result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorMsg()));
+    }
+
+    private SysSmsTempDTO validateSmsTemplate(String templateCode) {
+        // 获得短信模板
+        SysSmsTempDTO template = smsTempService.selectSysSmsTempByTemplateCode(templateCode);
+        // 短信模板不存在
+        if (template == null) {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"短信模板不存在");
+        }
+        return template;
+    }
+
+    private SysSmsChannelDTO validateSmsChannel(String channelId) {
+        // 获得短信渠道
+        SysSmsChannelDTO channelDO = smsChannelService.selectSysSmsChannelById(channelId);
+        // 短信模板不存在
+        if (channelDO == null) {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"短信渠道不存在");
+        }
+        return channelDO;
+    }
+
+
+    private String validateMobile(String mobile) {
+        if (StrUtil.isEmpty(mobile)) {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"手机号不存在");
+        }
+        return mobile;
+    }
+
+    private List<KeyValue<String, Object>> buildTemplateParams(SysSmsTempDTO template, Map<String, Object> templateParams) {
+        List<String> paramTemplate = template.getTemplateParams();
+        if(CollectionUtil.isEmpty(paramTemplate)){
+            return Collections.emptyList();
+        }
+        return paramTemplate.stream().map(key -> {
+            Object value = templateParams.get(key);
+            if (value == null) {
+                throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,String.format("模板参数({%s})缺失",key));
+            }
+            return KeyValue.of(key, value);
+        }).collect(Collectors.toList());
+    }
+
+
+}

+ 123 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SysSmsChannelServiceImpl.java

@@ -0,0 +1,123 @@
+package cn.tr.module.sys.sms.service.impl;
+
+import cn.tr.core.utils.JsonUtils;
+import cn.tr.plugin.sms.config.SmsClientFactory;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import cn.tr.module.sys.sms.repository.SysSmsChannelRepository;
+import cn.tr.module.sys.sms.po.SysSmsChannelPO;
+import cn.tr.module.sys.sms.dto.SysSmsChannelDTO;
+import cn.tr.module.sys.sms.dto.SysSmsChannelQueryDTO;
+import java.util.*;
+import cn.tr.module.sys.sms.service.ISysSmsChannelService;
+import cn.tr.module.sys.mapper.sms.SysSmsChannelMapper;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 短信渠道Service接口实现类
+ *
+ * @author lf
+ * @date  2023/04/19 18:18
+ **/
+@Service
+@Slf4j
+public class SysSmsChannelServiceImpl implements ISysSmsChannelService {
+    @Autowired
+    private SysSmsChannelRepository baseRepository;
+
+    @Autowired
+    private SmsClientFactory smsClientFactory;
+    @Override
+    @PostConstruct
+    public void initLocalCache() {
+        // 第一步:查询数据
+        List<SysSmsChannelPO> channels = baseRepository.selectList(new LambdaQueryWrapper<>());
+        log.info("[initLocalCache][缓存短信渠道,数量为:{}]", channels.size());
+
+        // 第二步:构建缓存:创建或更新短信 Client
+        List<SmsChannelProperties> propertiesList = SysSmsChannelMapper.INSTANCE.converProperties(channels);
+        propertiesList.forEach(properties -> {
+            try {
+                smsClientFactory.createOrUpdateSmsClient(properties);
+            } catch (Exception e) {
+                log.error("[initLocalCache] 缓存短信渠道失败,properties:{},e:{}", JsonUtils.toJsonString(properties),e);
+            }
+        });
+    }
+
+    /**
+     * 根据条件查询短信渠道
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/04/19 18:18
+     */
+    @Override
+    public List<SysSmsChannelDTO> selectSysSmsChannelList(SysSmsChannelQueryDTO query){
+        return SysSmsChannelMapper.INSTANCE.convertDtoList(
+                baseRepository.selectList(new LambdaQueryWrapper<SysSmsChannelPO>()
+                        .like(Objects.nonNull(query.getName()),SysSmsChannelPO::getName,
+                                query.getName())
+                        .eq(Objects.nonNull(query.getType()),SysSmsChannelPO::getType,query.getType())
+                        .eq(Objects.nonNull(query.getDisable()),SysSmsChannelPO::getDisable,query.getDisable())
+                )
+        );
+    };
+
+    /**
+     * 根据id查询短信渠道
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/04/19 18:18
+     */
+    @Override
+    public SysSmsChannelDTO selectSysSmsChannelById(String id){
+        return SysSmsChannelMapper.INSTANCE.convertDto(baseRepository.selectById(id));
+    };
+
+    /**
+     * 编辑短信渠道
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/04/19 18:18
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public boolean updateSysSmsChannelById(SysSmsChannelDTO source){
+        return baseRepository.updateById(SysSmsChannelMapper.INSTANCE.convertPO(source))!=0;
+    };
+
+    /**
+     * 新增短信渠道
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/04/19 18:18
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean insertSysSmsChannel(SysSmsChannelDTO source){
+        return baseRepository.insert(SysSmsChannelMapper.INSTANCE.convertPO(source))!=0;
+    };
+
+    /**
+     * 删除短信渠道详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/04/19 18:18
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int removeSysSmsChannelByIds(Collection<String> ids){
+        return baseRepository.deleteBatchIds(ids);
+    };
+
+
+    private void testConfig(SysSmsChannelDTO smsChannel){
+        //校验配置
+
+    }
+}

+ 148 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SysSmsLogServiceImpl.java

@@ -0,0 +1,148 @@
+package cn.tr.module.sys.sms.service.impl;
+
+import cn.tr.core.pojo.CommonResult;
+import cn.tr.module.sys.mapper.sms.SysSmsLogMapper;
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+import cn.tr.module.sys.sms.enums.SmsReceiveStatusEnum;
+import cn.tr.module.sys.sms.enums.SmsSendStatusEnum;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import cn.tr.module.sys.sms.repository.SysSmsLogRepository;
+import cn.tr.module.sys.sms.po.SysSmsLogPO;
+import cn.tr.module.sys.sms.dto.SysSmsLogDTO;
+import cn.tr.module.sys.sms.dto.SysSmsLogQueryDTO;
+import java.util.*;
+import cn.tr.module.sys.sms.service.ISysSmsLogService;
+/**
+ * 短信日志Service接口实现类
+ *
+ * @author lf
+ * @date  2023/04/21 17:06
+ **/
+@Service
+public class SysSmsLogServiceImpl implements ISysSmsLogService {
+    @Autowired
+    private SysSmsLogRepository baseRepository;
+
+
+    /**
+     * 根据条件查询短信日志
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/04/21 17:06
+     */
+    @Override
+    public List<SysSmsLogDTO> selectSysSmsLogList(SysSmsLogQueryDTO query){
+        return SysSmsLogMapper.INSTANCE.convertDtoList(
+                baseRepository.selectList(new LambdaQueryWrapper<SysSmsLogPO>()
+                        .eq(Objects.nonNull(query.getChannelId()),SysSmsLogPO::getChannelId,query.getChannelId())
+                        .eq(Objects.nonNull(query.getTemplateId()),SysSmsLogPO::getTemplateId,query.getTemplateId())
+                        .eq(Objects.nonNull(query.getTemplateCode()),SysSmsLogPO::getTemplateCode,query.getTemplateCode())
+                        .like(Objects.nonNull(query.getMobile()),SysSmsLogPO::getMobile,
+                                query.getMobile())
+                        .eq(Objects.nonNull(query.getSendStatus()),SysSmsLogPO::getSendStatus,query.getSendStatus())
+                        .le(Objects.nonNull(query.getSendTime())&&Objects.nonNull(query.getSendTime().getMax()),SysSmsLogPO::getSendTime,query.getSendTime().getMax())
+                        .ge(Objects.nonNull(query.getSendTime())&&Objects.nonNull(query.getSendTime().getMin()),SysSmsLogPO::getSendTime,query.getSendTime().getMin())
+                        .eq(Objects.nonNull(query.getReceiveStatus()),SysSmsLogPO::getReceiveStatus,query.getReceiveStatus())
+                )
+        );
+    };
+
+    /**
+     * 根据id查询短信日志
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/04/21 17:06
+     */
+    @Override
+    public SysSmsLogDTO selectSysSmsLogById(String id){
+        return SysSmsLogMapper.INSTANCE.convertDto(baseRepository.selectById(id));
+    };
+
+    /**
+     * 新增短信日志
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/04/21 17:06
+     * @return 返回新增数据id
+     */
+    private String insertSysSmsLog(SysSmsLogDTO source){
+        SysSmsLogPO insertSource = SysSmsLogMapper.INSTANCE.convertPO(source);
+        baseRepository.insert(insertSource);
+        return insertSource.getId();
+    };
+
+    /**
+     * 删除短信日志详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/04/21 17:06
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int removeSysSmsLogByIds(Collection<String> ids){
+        return baseRepository.deleteBatchIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String createSmsLog(String mobile, String userId, String userType, Boolean isSend, SysSmsTempDTO template, String templateContent, Map<String, Object> templateParams) {
+        SysSmsLogDTO log = SysSmsLogDTO.builder()
+                // 根据是否要发送,设置状态
+                .sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getValue()
+                        : SmsSendStatusEnum.IGNORE.getValue())
+                // 设置手机相关字段
+                .mobile(mobile)
+                .userId(userId)
+                .userType(userType)
+                // 设置模板相关字段
+                .templateId(template.getId())
+                .templateCode(template.getCode())
+                .templateType(template.getTempType())
+                .templateContent(templateContent)
+                .templateParams(templateParams)
+                .apiTemplateId(template.getApiTempCode())
+                // 设置渠道相关字段
+                .channelId(template.getChannelId())
+                // 设置接收相关字段
+                .receiveStatus(SmsReceiveStatusEnum.INIT.getValue())
+                .build();
+        // 插入数据库
+        return this.insertSysSmsLog(log);
+    }
+
+    @Override
+    public void updateSmsSendResult(String id, String sendCode, String sendMsg, String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo) {
+        SmsSendStatusEnum sendStatus = CommonResult.isSuccess(sendCode) ?
+                SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;
+        SysSmsLogDTO logDTO = SysSmsLogDTO.builder().id(id)
+                .sendStatus(sendStatus.getValue())
+                .sendTime(new Date())
+                .sendCode(sendCode)
+                .sendMsg(sendMsg)
+                .apiSendCode(apiSendCode)
+                .apiSendMsg(apiSendMsg)
+                .apiRequestId(apiRequestId)
+                .apiSerialNo(apiSerialNo)
+                .build();
+        baseRepository.updateById(SysSmsLogMapper.INSTANCE.convertPO(logDTO));
+    }
+
+    @Override
+    public void updateSmsReceiveResult(String id, Boolean success, Date receiveTime, String apiReceiveCode, String apiReceiveMsg) {
+        SmsReceiveStatusEnum receiveStatus = Objects.equals(success, true) ?
+                SmsReceiveStatusEnum.SUCCESS : SmsReceiveStatusEnum.FAILURE;
+        SysSmsLogDTO logDTO = SysSmsLogDTO.builder()
+                .id(id)
+                .receiveStatus(receiveStatus.getValue())
+                .receiveTime(receiveTime)
+                .apiReceiveCode(apiReceiveCode)
+                .apiReceiveMsg(apiReceiveMsg)
+                .build();
+        baseRepository.updateById(SysSmsLogMapper.INSTANCE.convertPO(logDTO));
+    }
+
+    ;
+}

+ 134 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/sms/service/impl/SysSmsTempServiceImpl.java

@@ -0,0 +1,134 @@
+package cn.tr.module.sys.sms.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.tr.core.exception.ServiceException;
+import cn.tr.core.exception.TRExcCode;
+import cn.tr.module.sys.mapper.sms.SysSmsTempMapper;
+import cn.tr.module.sys.sms.dto.SysSmsTempDTO;
+import cn.tr.module.sys.sms.dto.SysSmsTempQueryDTO;
+import cn.tr.module.sys.sms.po.SysSmsTempPO;
+import cn.tr.module.sys.sms.repository.SysSmsTempRepository;
+import cn.tr.module.sys.sms.service.ISysSmsTempService;
+import cn.tr.module.sys.user.enums.CreateEnum;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 短信模板Service接口实现类
+ *
+ * @author lf
+ * @date  2023/04/21 10:29
+ **/
+@Service
+public class SysSmsTempServiceImpl implements ISysSmsTempService {
+    @Autowired
+    private SysSmsTempRepository baseRepository;
+
+
+    /**
+     * 根据条件查询短信模板
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/04/21 10:29
+     */
+    @Override
+    public List<SysSmsTempDTO> selectSysSmsTempList(SysSmsTempQueryDTO query){
+        return SysSmsTempMapper.INSTANCE.convertDtoList(
+                baseRepository.selectList(new LambdaQueryWrapper<SysSmsTempPO>()
+                        .eq(Objects.nonNull(query.getCreateType()), SysSmsTempPO::getCreateType,query.getCreateType())
+                        .eq(Objects.nonNull(query.getTempType()), SysSmsTempPO::getTempType,query.getTempType())
+                        .like(Objects.nonNull(query.getCode()), SysSmsTempPO::getCode, query.getCode())
+                        .eq(Objects.nonNull(query.getDisable()), SysSmsTempPO::getDisable,query.getDisable())
+                )
+        );
+    };
+
+    /**
+     * 根据id查询短信模板
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/04/21 10:29
+     */
+    @Override
+    public SysSmsTempDTO selectSysSmsTempById(String id){
+        return SysSmsTempMapper.INSTANCE.convertDto(baseRepository.selectById(id));
+    };
+
+    /**
+     * 编辑短信模板
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/04/21 10:29
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public boolean updateSysSmsTempById(SysSmsTempDTO source){
+        validateSmsTempSource(source);
+        return baseRepository.updateById(SysSmsTempMapper.INSTANCE.convertPO(source))!=0;
+    };
+
+    /**
+     * 新增短信模板
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/04/21 10:29
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean insertSysSmsTemp(SysSmsTempDTO source){
+        validateSmsTempSource(source);
+        return baseRepository.insert(SysSmsTempMapper.INSTANCE.convertPO(source))!=0;
+    };
+
+    /**
+     * 删除短信模板详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/04/21 10:29
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int removeSysSmsTempByIds(Collection<String> ids){
+        List<SysSmsTempPO> temps = baseRepository.selectBatchIds(ids);
+        for (SysSmsTempPO temp : temps) {
+            if (StrUtil.equals(temp.getCreateType(), CreateEnum.sys.name())) {
+                throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法删除系统模板");
+            }
+        }
+        return baseRepository.deleteBatchIds(ids);
+    }
+
+    @Override
+    public String formatSmsTemplateContent(String content, Map<String, Object> templateParams) {
+        return StrUtil.format(content, templateParams);
+    }
+
+    @Override
+    public SysSmsTempDTO selectSysSmsTempByTemplateCode(String templateCode) {
+        return SysSmsTempMapper.INSTANCE.convertDto(baseRepository.selectOne(new LambdaQueryWrapper<SysSmsTempPO>()
+                .eq(SysSmsTempPO::getCode,templateCode)
+                .last("limit 1")));
+    }
+
+    ;
+
+    /**
+     * 检验资源中的模板编码是否重复
+     * @param source
+     */
+    private void validateSmsTempSource(SysSmsTempDTO source){
+        SysSmsTempPO temp = baseRepository.selectOne(new LambdaQueryWrapper<SysSmsTempPO>()
+                .eq(SysSmsTempPO::getCode, source.getCode())
+                .last("limit 1"));
+        if(temp!=null&& !StrUtil.equals(temp.getId(),source.getId())){
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,String.format("模板编码{%s}不能重复",source.getCode()));
+        }
+    }
+}

+ 2 - 2
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/tenant/service/impl/SysTenantServiceImpl.java

@@ -9,7 +9,7 @@ import cn.tr.module.sys.tenant.dto.SysTenantCommonDTO;
 import cn.tr.module.sys.tenant.dto.SysTenantQueryDTO;
 import cn.tr.module.sys.tenant.service.ISysTenantPackageMenuService;
 import cn.tr.module.sys.user.dto.*;
-import cn.tr.module.sys.user.enums.RoleEnum;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.enums.UserStatusEnum;
 import cn.tr.module.sys.tenant.po.SysTenantPO;
 import cn.tr.module.sys.tenant.repository.SysTenantRepository;
@@ -131,7 +131,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
         role.setRemark("系统角色");
         role.setDisable(false);
         role.setSort(1);
-        role.setType(RoleEnum.sys.name());
+        role.setType(CreateEnum.sys.name());
         return role;
     }
 

+ 3 - 7
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/controller/SysRoleController.java

@@ -3,15 +3,12 @@ package cn.tr.module.sys.user.controller;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.tr.core.pojo.CommonResult;
 import cn.tr.core.pojo.TableDataInfo;
-import cn.tr.core.utils.TreeUtil;
 import cn.tr.core.validation.Insert;
 import cn.tr.core.validation.Update;
 import cn.tr.module.sys.user.dto.*;
-import cn.tr.module.sys.user.enums.RoleEnum;
-import cn.tr.module.sys.user.service.ISysMenuService;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.service.ISysRoleMenuService;
 import cn.tr.module.sys.user.service.ISysRoleService;
-import cn.tr.module.sys.user.vo.RouteItemVO;
 import cn.tr.plugin.mybatis.base.BaseController;
 import cn.tr.plugin.operatelog.annotation.OperateLog;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
@@ -21,7 +18,6 @@ import lombok.AllArgsConstructor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.validation.constraints.NotNull;
 import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -88,7 +84,7 @@ public class SysRoleController extends BaseController {
     @ApiOperation(value = "根据id更新角色",notes = "权限: sys:role:edit")
     @OperateLog
     public CommonResult<Boolean> edit(@RequestBody@Validated(Update.class) SysRoleDTO source){
-        source.setType(RoleEnum.custom.name());
+        source.setType(CreateEnum.custom.name());
         return CommonResult.success(roleService.updateSysRoleById(source));
     }
 
@@ -98,7 +94,7 @@ public class SysRoleController extends BaseController {
     @OperateLog
     @ApiOperation(value = "新增角色",notes = "权限: sys:role:add")
     public CommonResult<Boolean> add(@RequestBody@Validated(Insert.class) SysRoleDTO source){
-        source.setType(RoleEnum.custom.name());
+        source.setType(CreateEnum.custom.name());
         return CommonResult.success(roleService.insertSysRole(source));
     }
 

+ 4 - 4
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/enums/RoleEnum.java → tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/enums/CreateEnum.java

@@ -1,13 +1,13 @@
 package cn.tr.module.sys.user.enums;
 
 /**
- * @ClassName : RoleEnum
- * @Description : 角色类型
+ * @ClassName : CreateEnum
+ * @Description :
  * @Author : LF
- * @Date: 2023年04月05
+ * @Date: 2023年04月21
  */
 
-public enum  RoleEnum {
+public enum  CreateEnum {
     //系统类型
     sys,
     //自定义类型

+ 0 - 15
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/enums/PortalEnum.java

@@ -1,15 +0,0 @@
-package cn.tr.module.sys.user.enums;
-
-/**
- * @ClassName : RoleEnum
- * @Description : 角色类型
- * @Author : LF
- * @Date: 2023年04月05日
- */
-
-public enum PortalEnum {
-    //系统类型
-    sys,
-    //自定义类型
-    custom
-}

+ 0 - 5
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/po/SysPortalPO.java

@@ -1,15 +1,10 @@
 package cn.tr.module.sys.user.po;
 
 import cn.tr.core.annotation.Comment;
-import cn.tr.plugin.mybatis.config.handler.StringListTypeHandler;
 import cn.tr.plugin.mybatis.pojo.TenantPO;
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
-import org.apache.ibatis.type.JdbcType;
-
-import java.util.List;
 
 /**
  * @ClassName : SysRolePO

+ 2 - 3
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysMenuServiceImpl.java

@@ -13,7 +13,7 @@ import cn.tr.module.sys.tenant.service.ISysTenantService;
 import cn.tr.module.sys.user.dto.SysMenuDTO;
 import cn.tr.module.sys.user.dto.SysMenuQueryDTO;
 import cn.tr.module.sys.user.dto.SysRoleDTO;
-import cn.tr.module.sys.user.enums.RoleEnum;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.po.SysMenuPO;
 import cn.tr.module.sys.user.repository.SysMenuRepository;
 import cn.tr.module.sys.user.service.*;
@@ -26,7 +26,6 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * @ClassName : SysMenuServiceImpl
@@ -151,7 +150,7 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuRepository,SysMenuPO>
             return new HashSet<>();
         }
         for (SysRoleDTO role : roles) {
-            if(StrUtil.equals(role.getType(), RoleEnum.sys.name())){
+            if(StrUtil.equals(role.getType(), CreateEnum.sys.name())){
                 return tenantService.currentTenantMenus();
             }
         }

+ 2 - 2
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysPortalMenuServiceImpl.java

@@ -7,7 +7,7 @@ import cn.tr.module.sys.tenant.service.ISysTenantService;
 import cn.tr.module.sys.user.dto.SysMenuDTO;
 import cn.tr.module.sys.user.dto.SysPortalDTO;
 import cn.tr.module.sys.user.dto.SysPortalMenuDTO;
-import cn.tr.module.sys.user.enums.PortalEnum;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.po.SysPortalMenuPO;
 import cn.tr.module.sys.user.repository.SysMenuRepository;
 import cn.tr.module.sys.user.repository.SysPortalMenuRepository;
@@ -64,7 +64,7 @@ public class SysPortalMenuServiceImpl extends ServiceImpl<SysPortalMenuRepositor
         if (Objects.isNull(portal)) {
             return new ArrayList<>();
         }
-        if(StrUtil.equals(portal.getType(), PortalEnum.sys.name())){
+        if(StrUtil.equals(portal.getType(), CreateEnum.sys.name())){
             return new ArrayList<>(tenantService.currentTenantMenus());
         }
         return SysMenuMapper.INSTANCE.toSysMenuDTOList(menuRepository.findAllMenuByPortalId(portalId));

+ 5 - 5
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysPortalServiceImpl.java

@@ -7,7 +7,7 @@ import cn.tr.core.exception.TRExcCode;
 import cn.tr.module.sys.mapper.user.SysPortalMapper;
 import cn.tr.module.sys.user.dto.SysPortalDTO;
 import cn.tr.module.sys.user.dto.SysPortalQueryDTO;
-import cn.tr.module.sys.user.enums.PortalEnum;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.po.SysPortalMenuPO;
 import cn.tr.module.sys.user.po.SysPortalPO;
 import cn.tr.module.sys.user.repository.SysPortalMenuRepository;
@@ -70,7 +70,7 @@ public class SysPortalServiceImpl implements ISysPortalService {
     public int deleteSysPortalByIds(Collection<String> ids) {
         List<SysPortalPO> portals = portalRepository.selectBatchIds(ids);
         for (SysPortalPO portal : portals) {
-            if (StrUtil.equals(portal.getType(), PortalEnum.sys.name())) {
+            if (StrUtil.equals(portal.getType(), CreateEnum.sys.name())) {
                 throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统门户进行操作");
             }
         }
@@ -88,7 +88,7 @@ public class SysPortalServiceImpl implements ISysPortalService {
     @Override
     public Collection<SysPortalDTO> findAllSysPortals() {
         return SysPortalMapper.INSTANCE.toSysPortalDTOList(portalRepository.selectList(new LambdaQueryWrapper<SysPortalPO>()
-                .eq(SysPortalPO::getType, PortalEnum.sys.name())));
+                .eq(SysPortalPO::getType, CreateEnum.sys.name())));
     }
 
 
@@ -99,7 +99,7 @@ public class SysPortalServiceImpl implements ISysPortalService {
         if(role!=null&& !StrUtil.equals(role.getId(),source.getId())){
             throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,String.format("门户编码{%s}不能重复",source.getCode()));
         }
-        if(StrUtil.equals(source.getType(), PortalEnum.sys.name())){
+        if(StrUtil.equals(source.getType(), CreateEnum.sys.name())){
             throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统级门户进行操作");
         }
         if(StrUtil.isNotEmpty(source.getId())){
@@ -107,7 +107,7 @@ public class SysPortalServiceImpl implements ISysPortalService {
             if(sysRolePO==null){
                 return;
             }
-            if(StrUtil.equals(sysRolePO.getType(), PortalEnum.sys.name())){
+            if(StrUtil.equals(sysRolePO.getType(), CreateEnum.sys.name())){
                 throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统角色进行操作");
             }
         }

+ 3 - 3
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysRoleMenuServiceImpl.java

@@ -10,7 +10,7 @@ import cn.tr.module.sys.tenant.service.ISysTenantService;
 import cn.tr.module.sys.user.dto.SysMenuDTO;
 import cn.tr.module.sys.user.dto.SysRoleDTO;
 import cn.tr.module.sys.user.dto.SysRoleMenuDTO;
-import cn.tr.module.sys.user.enums.RoleEnum;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.po.SysRoleMenuPO;
 import cn.tr.module.sys.user.po.SysRolePO;
 import cn.tr.module.sys.user.repository.SysMenuRepository;
@@ -60,7 +60,7 @@ public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuRepository,Sy
         Map<String, List<SysRoleMenuPO>> roleIdMap = roleMenus.stream()
                 .collect(Collectors.groupingBy(SysRoleMenuPO::getRoleId));
         List<SysRolePO> sysRoles = roleRepository.selectList(new LambdaQueryWrapper<SysRolePO>().in(SysRolePO::getId, roleIdMap.keySet())
-                .eq(SysRolePO::getType, RoleEnum.sys.name()));
+                .eq(SysRolePO::getType, CreateEnum.sys.name()));
         if(CollectionUtil.isNotEmpty(sysRoles)){
             throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统角色进行操作");
         }
@@ -75,7 +75,7 @@ public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuRepository,Sy
         if (Objects.isNull(roleDTO)) {
             return new ArrayList<>();
         }
-        if(StrUtil.equals(roleDTO.getType(), RoleEnum.sys.name())){
+        if(StrUtil.equals(roleDTO.getType(), CreateEnum.sys.name())){
             //当前租户的所有菜单
             return new ArrayList<>(tenantService.currentTenantMenus());
         }

+ 5 - 5
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/service/impl/SysRoleServiceImpl.java

@@ -8,7 +8,7 @@ import cn.tr.core.exception.TRExcCode;
 import cn.tr.module.sys.mapper.user.SysRoleMapper;
 import cn.tr.module.sys.user.dto.SysRoleDTO;
 import cn.tr.module.sys.user.dto.SysRoleQueryDTO;
-import cn.tr.module.sys.user.enums.RoleEnum;
+import cn.tr.module.sys.user.enums.CreateEnum;
 import cn.tr.module.sys.user.po.SysRoleMenuPO;
 import cn.tr.module.sys.user.po.SysRolePO;
 import cn.tr.module.sys.user.repository.SysRoleMenuRepository;
@@ -54,7 +54,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
     @Override
     public Collection<SysRoleDTO> findAllSysRoles() {
         return SysRoleMapper.INSTANCE.toSysRoleDTOList( roleRepository.selectList(new LambdaQueryWrapper<SysRolePO>()
-                .eq(SysRolePO::getType,RoleEnum.sys.name())));
+                .eq(SysRolePO::getType,CreateEnum.sys.name())));
     }
 
     ;
@@ -86,7 +86,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
     public int deleteSysRoleByIds(Collection<String> ids) {
         List<SysRolePO> roles = roleRepository.selectBatchIds(ids);
         for (SysRolePO role : roles) {
-            if (StrUtil.equals(role.getType(), RoleEnum.sys.name())) {
+            if (StrUtil.equals(role.getType(), CreateEnum.sys.name())) {
                 throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统角色进行操作");
             }
         }
@@ -108,7 +108,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
         if(role!=null&& !StrUtil.equals(role.getId(),source.getId())){
             throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,String.format("角色编码{%s}不能重复",source.getCode()));
         }
-        if(StrUtil.equals(source.getType(), RoleEnum.sys.name())){
+        if(StrUtil.equals(source.getType(), CreateEnum.sys.name())){
             throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统角色进行操作");
         }
         if(StrUtil.isNotEmpty(source.getId())){
@@ -116,7 +116,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
             if(sysRolePO==null){
                 return;
             }
-            if(StrUtil.equals(sysRolePO.getType(), RoleEnum.sys.name())){
+            if(StrUtil.equals(sysRolePO.getType(), CreateEnum.sys.name())){
                 throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"无法对系统角色进行操作");
             }
         }

+ 2 - 0
tr-plugins/tr-spring-boot-starter-plugin-mybatis/src/main/java/cn/tr/plugin/mybatis/pojo/TenantPO.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.TableField;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import org.apache.ibatis.type.JdbcType;
 
 /**
@@ -14,6 +15,7 @@ import org.apache.ibatis.type.JdbcType;
  * @Date: 2023年04月01日
  */
 @Data
+@EqualsAndHashCode(callSuper = true)
 public class TenantPO extends BasePO {
     @Comment("租户id")
     @TableField(updateStrategy = FieldStrategy.NEVER,fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)

+ 2 - 8
tr-plugins/tr-spring-boot-starter-plugin-sms/pom.xml

@@ -32,16 +32,10 @@
             <scope>provided</scope>
         </dependency>
 
-
-        <dependency>
-            <groupId>com.aliyun</groupId>
-            <artifactId>aliyun-java-sdk-core</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>com.aliyun</groupId>
-            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.23</version>
         </dependency>
-
     </dependencies>
 </project>

+ 0 - 16
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsCodeMapping.java

@@ -1,16 +0,0 @@
-package cn.tr.plugin.sms.bo;
-
-import cn.tr.core.exception.BaseCode;
-
-import java.util.function.Function;
-
-/**
- * 将 API 的错误码,转换为通用的错误码
- *
- * @see SmsCommonResult
- * @see
- *
- * @author 芋道源码
- */
-public interface SmsCodeMapping extends Function<String, BaseCode> {
-}

+ 6 - 17
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsCommonResult.java

@@ -1,10 +1,8 @@
 package cn.tr.plugin.sms.bo;
 
 import cn.hutool.core.exceptions.ExceptionUtil;
-import cn.hutool.core.lang.Assert;
-import cn.tr.core.exception.BaseCode;
+import cn.tr.core.exception.TRExcCode;
 import cn.tr.core.pojo.CommonResult;
-import cn.tr.plugin.sms.enums.SmsFrameworkErrorCodeConstants;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
@@ -13,7 +11,7 @@ import lombok.experimental.Accessors;
 /**
  * 短信的 CommonResult 拓展类
  *
- * 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
+ * 考虑到不同的平台,返回的 type 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
  *
  * 另外,一些短信平台(例如说阿里云、腾讯云)会返回一个请求编号,用于排查请求失败的问题,我们设置到 {@link #apiRequestId} 字段
  *
@@ -44,28 +42,19 @@ public class SmsCommonResult<T> extends CommonResult<T> {
     private SmsCommonResult() {
     }
 
-    public static <T> SmsCommonResult<T> build(String apiCode, String apiMsg, String apiRequestId,
-                                               T data, SmsCodeMapping codeMapping) {
-        Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
+    public static <T> SmsCommonResult<T> build(String code,String apiCode, String apiMsg, String apiRequestId,
+                                               T data) {
         SmsCommonResult<T> result = new SmsCommonResult<T>().setApiCode(apiCode);
+        result.setCode(code);
         result.setErrorMsg(apiMsg);
         result.setApiRequestId(apiRequestId);
         result.setData(data);
-        // 翻译错误码
-        if (codeMapping != null) {
-            BaseCode errorCode = codeMapping.apply(apiCode);
-            if (errorCode == null) {
-                errorCode = SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
-            }
-            result.setCode(errorCode.getErrCode());
-            result.setErrorMsg(errorCode.getErrCode());
-        }
         return result;
     }
 
     public static <T> SmsCommonResult<T> error(Throwable ex) {
         SmsCommonResult<T> result = new SmsCommonResult<>();
-        result.setCode(SmsFrameworkErrorCodeConstants.EXCEPTION.getErrCode());
+        result.setCode(TRExcCode.SERVICE_ERROR_C0001.getErrCode());
         result.setErrorMsg(ExceptionUtil.getRootCauseMessage(ex));
         return result;
     }

+ 49 - 0
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsReceiveRespBO.java

@@ -0,0 +1,49 @@
+package cn.tr.plugin.sms.bo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import java.util.Date;
+
+/**
+ * 消息接收 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Accessors(chain = true)
+public class SmsReceiveRespBO {
+
+    /**
+     * 是否接收成功
+     */
+    private Boolean success;
+    /**
+     * API 接收结果的编码
+     */
+    private String errorCode;
+    /**
+     * API 接收结果的说明
+     */
+    private String errorMsg;
+
+    /**
+     * 手机号
+     */
+    private String mobile;
+    /**
+     * 用户接收时间
+     */
+    private Date receiveTime;
+
+    /**
+     * 短信 API 发送返回的序号
+     */
+    private String serialNo;
+    /**
+     * 短信日志编号
+     *
+     * 对应 SysSmsLogDO 的编号
+     */
+    private String logId;
+
+}

+ 32 - 0
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/bo/SmsTemplateRespBO.java

@@ -0,0 +1,32 @@
+package cn.tr.plugin.sms.bo;
+
+import lombok.Data;
+
+/**
+ * 短信模板 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class SmsTemplateRespBO {
+
+    /**
+     * 模板编号
+     */
+    private String id;
+    /**
+     * 短信内容
+     */
+    private String content;
+    /**
+     * 审核状态
+     *
+     * 枚举 {@link SmsTemplateAuditStatusEnum}
+     */
+    private Integer auditStatus;
+    /**
+     * 审核未通过的理由
+     */
+    private String auditReason;
+
+}

+ 67 - 7
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/AbstractSmsClient.java

@@ -1,9 +1,17 @@
 package cn.tr.plugin.sms.config;
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.tr.core.pojo.KeyValue;
+import cn.tr.plugin.sms.bo.SmsCommonResult;
+import cn.tr.plugin.sms.bo.SmsReceiveRespBO;
+import cn.tr.plugin.sms.bo.SmsSendRespBO;
+import cn.tr.plugin.sms.bo.SmsTemplateRespBO;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 
+import java.util.List;
+
 /**
  * @ClassName : AbstractSmsClient
  * @Description :
@@ -12,29 +20,28 @@ import lombok.extern.slf4j.Slf4j;
  */
 @Slf4j
 @Getter
-public abstract class AbstractSmsClient<ClientConfig extends SmsClientConfig,SendConfig extends SmsClientSendConfig>  implements SmsClient<SendConfig> {
+public abstract class AbstractSmsClient<ClientConfig extends SmsTempConfig>  implements SmsClient {
     /**
+     *
      * 客户端id
      */
     private String id;
     /**
      * 客户端配置
      */
-    private ClientConfig config;
+    private SmsChannelProperties config;
 
-    public AbstractSmsClient(String id, ClientConfig clientConfig) {
+    public AbstractSmsClient(String id, SmsChannelProperties clientConfig) {
         this.id = id;
         this.config = clientConfig;
     }
 
-    public void init(){
+    public void init() throws Exception {
         doInit(this.config);
         log.info("Sms Client[init][配置({}) 初始化完成],configClass:({})", this.config,this.getClass().getCanonicalName());
     }
 
-    public abstract void doInit(ClientConfig config);
-
-    public void refresh(ClientConfig config){
+    public void refresh(SmsChannelProperties config) throws Exception {
         // 判断是否更新
         if (ObjectUtil.equal(config,this.config)) {
             return;
@@ -44,4 +51,57 @@ public abstract class AbstractSmsClient<ClientConfig extends SmsClientConfig,Sen
         // 初始化
         this.init();
     }
+
+    public abstract void doInit(SmsChannelProperties config) throws Exception;
+
+
+    @Override
+    public final SmsCommonResult<SmsSendRespBO> sendSms(String logId, String mobile, String apiTemplateId,
+                                                        List<KeyValue<String, Object>> templateParams) {
+        // 执行短信发送
+        SmsCommonResult<SmsSendRespBO> result;
+        try {
+            result = doSendSms(logId, mobile, apiTemplateId, templateParams);
+        } catch (Throwable ex) {
+            // 打印异常日志
+            log.error("[sendSms][发送短信异常,sendLogId({}) mobile({}) apiTemplateId({}) templateParams({})]",
+                    logId, mobile, apiTemplateId, templateParams, ex);
+            // 封装返回
+            return SmsCommonResult.error(ex);
+        }
+        return result;
+    }
+
+    protected abstract SmsCommonResult<SmsSendRespBO> doSendSms(String sendLogId, String mobiles,
+                                                                String apiTemplateId, List<KeyValue<String, Object>> templateParams)
+            throws Throwable;
+
+    @Override
+    public List<SmsReceiveRespBO> parseSmsReceiveStatus(String text) throws Throwable {
+        try {
+            return doParseSmsReceiveStatus(text);
+        } catch (Throwable ex) {
+            log.error("[parseSmsReceiveStatus][text({}) 解析发生异常]", text, ex);
+            throw ex;
+        }
+    }
+
+    protected abstract List<SmsReceiveRespBO> doParseSmsReceiveStatus(String text) throws Throwable;
+
+    @Override
+    public SmsCommonResult<SmsTemplateRespBO> getSmsTemplate(String apiTemplateId) {
+        // 执行短信发送
+        SmsCommonResult<SmsTemplateRespBO> result;
+        try {
+            result = doGetSmsTemplate(apiTemplateId);
+        } catch (Throwable ex) {
+            // 打印异常日志
+            log.error("[getSmsTemplate][获得短信模板({}) 发生异常]", apiTemplateId, ex);
+            // 封装返回
+            return SmsCommonResult.error(ex);
+        }
+        return result;
+    }
+
+    protected abstract SmsCommonResult<SmsTemplateRespBO> doGetSmsTemplate(String apiTemplateId) throws Throwable;
 }

+ 99 - 0
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/DefaultSmsClientFactory.java

@@ -0,0 +1,99 @@
+package cn.tr.plugin.sms.config;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.tr.plugin.sms.config.ali.AliSmsClient;
+import cn.tr.plugin.sms.enums.SmsChannelEnum;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.Assert;
+
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * @ClassName : LocalFileClientFactory
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月13日
+ */
+@Configuration
+@Slf4j
+public class DefaultSmsClientFactory implements SmsClientFactory{
+
+    /**
+     * 短信客户端 Map
+     * key:渠道编码,使用 {@link SmsChannelProperties#getType()} ()}
+     *
+     * 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。
+     * 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的
+     */
+    private final ConcurrentMap<String, AbstractSmsClient> channelCodeClients = new ConcurrentHashMap<>();
+
+    /**
+     * 短信客户端 Map
+     * key:配置编号
+     */
+    private final ConcurrentMap<String, AbstractSmsClient<?>> clients = new ConcurrentHashMap<>();
+
+    public DefaultSmsClientFactory() {
+        // 初始化 channelCodeClients 集合
+        Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
+            // 创建一个空的 SmsChannelProperties 对象
+            SmsChannelProperties properties = new SmsChannelProperties()
+                    .setType(channel.getChannel())
+                    .setApiKey("default default")
+                    .setApiSecret("default");
+            // 创建 Sms 客户端
+            AbstractSmsClient smsClient = createSmsClient(properties);
+            channelCodeClients.put(channel.getChannel(), smsClient);
+        });
+    }
+
+    @Override
+    public SmsClient getSmsClientByChannelId(String id) {
+        return clients.get(id);
+    }
+
+    @Override
+    public SmsClient getSmsClientByChannelType(String channelType) {
+        return channelCodeClients.get(channelType);
+    }
+
+    @Override
+    public <TempConfig extends SmsTempConfig> SmsClient createOrUpdateSmsClient(SmsChannelProperties config) throws Exception {
+        AbstractSmsClient<TempConfig> client = (AbstractSmsClient<TempConfig>) clients.get(config.getId());
+        if (client == null) {
+            client = this.creatSmsClient(config.getId(), config.getType(), config);
+            client.init();
+            clients.put(client.getId(), client);
+        } else {
+            client.refresh(config);
+        }
+        return client;
+    }
+
+    private <TempConfig extends SmsTempConfig> AbstractSmsClient<TempConfig> creatSmsClient(String configId, String channel, SmsChannelProperties config) {
+        SmsChannelEnum channelEnum = SmsChannelEnum.getByChannel(channel);
+        if(channelEnum==null){
+            throw new IllegalArgumentException(String.format("[channel]:{%s}类型不存在",channel));
+        }
+        // 创建客户端
+        return ReflectUtil.newInstance(channelEnum.getClientClass(), configId, config);
+    }
+
+
+    private AbstractSmsClient createSmsClient(SmsChannelProperties properties) {
+        SmsChannelEnum channelEnum = SmsChannelEnum.getByChannel(properties.getType());
+        Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
+        // 创建客户端
+        switch (channelEnum) {
+            case ALIYUN: return new AliSmsClient(properties.getId(),properties);
+            case TENCENT: return new AliSmsClient(properties.getId(),properties);
+        }
+        // 创建失败,错误日志 + 抛出异常
+        log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
+        throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties));
+    }
+}

+ 0 - 51
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/LocalSmsClientFactory.java

@@ -1,51 +0,0 @@
-package cn.tr.plugin.sms.config;
-
-import cn.hutool.core.util.ReflectUtil;
-import cn.tr.plugin.sms.enums.SmsChannelEnum;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-/**
- * @ClassName : LocalFileClientFactory
- * @Description :
- * @Author : LF
- * @Date: 2023年03月13日
- */
-@Slf4j
-public class LocalSmsClientFactory implements SmsClientFactory{
-    /**
-     * 短信客户端 Map
-     * key:配置编号
-     */
-    private final ConcurrentMap<String, AbstractSmsClient<?,?>> clients = new ConcurrentHashMap<>();
-
-    @Override
-    public SmsClient getSmsClient(String id) {
-        return clients.get(id);
-    }
-
-    @Override
-    public <ClientConfig extends SmsClientConfig,SendClientConfig extends SmsClientSendConfig> SmsClient createOrUpdateFileClient(String configId, String channel,ClientConfig config) {
-        AbstractSmsClient<ClientConfig,SendClientConfig> client = (AbstractSmsClient<ClientConfig,SendClientConfig>) clients.get(configId);
-        if (client == null) {
-            client = this.creatSmsClient(configId, channel, config);
-            client.init();
-            clients.put(client.getId(), client);
-        } else {
-            client.refresh(config);
-        }
-        return client;
-    }
-
-    private <ClientConfig extends SmsClientConfig,SendClientConfig extends SmsClientSendConfig> AbstractSmsClient<ClientConfig,SendClientConfig> creatSmsClient(
-            String configId, String storage, ClientConfig config) {
-        SmsChannelEnum channelEnum = SmsChannelEnum.getByChannel(storage);
-        if(channelEnum==null){
-            throw new IllegalArgumentException(String.format("[channel]:{%s}类型不存在",storage));
-        }
-        // 创建客户端
-        return (AbstractSmsClient<ClientConfig,SendClientConfig>) ReflectUtil.newInstance(channelEnum.getClientClass(), configId, config);
-    }
-}

+ 29 - 3
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClient.java

@@ -1,7 +1,12 @@
 package cn.tr.plugin.sms.config;
 
+import cn.tr.core.pojo.KeyValue;
 import cn.tr.plugin.sms.bo.SmsCommonResult;
+import cn.tr.plugin.sms.bo.SmsReceiveRespBO;
 import cn.tr.plugin.sms.bo.SmsSendRespBO;
+import cn.tr.plugin.sms.bo.SmsTemplateRespBO;
+
+import java.util.List;
 
 /**
  * @ClassName : SmsClient
@@ -10,7 +15,7 @@ import cn.tr.plugin.sms.bo.SmsSendRespBO;
  * @Date: 2023年03月15日
  */
 
-public interface SmsClient<SendConfig extends SmsClientSendConfig> {
+public interface SmsClient{
     /**
      * @return 当前客户端id
      */
@@ -20,8 +25,29 @@ public interface SmsClient<SendConfig extends SmsClientSendConfig> {
     /**
      * 发送消息
      *
-     * @param sendConfig 发送配置
+     * @param logId 日志编号
+     * @param mobile 手机号
+     * @param apiTemplateId 短信 API 的模板编号
+     * @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
      * @return 短信发送结果
      */
-    SmsCommonResult<SmsSendRespBO> sendSms(SendConfig sendConfig);
+    SmsCommonResult<SmsSendRespBO> sendSms(String logId, String mobile, String apiTemplateId,
+                                            List<KeyValue<String, Object>> templateParams);
+
+    /**
+     * 解析接收短信的接收结果
+     *
+     * @param text 结果
+     * @return 结果内容
+     * @throws Throwable 当解析 text 发生异常时,则会抛出异常
+     */
+    List<SmsReceiveRespBO> parseSmsReceiveStatus(String text) throws Throwable;
+
+    /**
+     * 查询指定的短信模板
+     *
+     * @param apiTemplateId 短信 API 的模板编号
+     * @return 短信模板
+     */
+    SmsCommonResult<SmsTemplateRespBO> getSmsTemplate(String apiTemplateId);
 }

+ 0 - 14
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClientConfig.java

@@ -1,14 +0,0 @@
-package cn.tr.plugin.sms.config;
-
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-
-/**
- * @ClassName : SmsClientConfig
- * @Description : 短信发送客户端配置
- * @Author : LF
- * @Date: 2023年03月15日
- */
-@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
-public interface SmsClientConfig {
-
-}

+ 8 - 4
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClientFactory.java

@@ -1,6 +1,6 @@
 package cn.tr.plugin.sms.config;
 
-import cn.tr.plugin.sms.enums.SmsChannelEnum;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
 
 /**
  * @Interface : SmsClientFactory
@@ -15,14 +15,18 @@ public interface SmsClientFactory {
      * @param id 客户端id
      * @return 短信发送客户端
      */
-    SmsClient  getSmsClient(String id);
+    SmsClient getSmsClientByChannelId(String id);
 
     /**
      *
      * @param id 客户端id
-     * @param channel 短信客户端类型 {@link SmsChannelEnum#getChannel()} ()} ()}
+     * @return 短信发送客户端
+     */
+    SmsClient getSmsClientByChannelType(String id);
+
+    /**
      * @param config 短信发送客户配置
      * @return
      */
-    <ClientConfig extends SmsClientConfig,SendConfig extends SmsClientSendConfig> SmsClient createOrUpdateFileClient(String id, String channel,ClientConfig config);
+    <ClientConfig extends SmsTempConfig> SmsClient createOrUpdateSmsClient(SmsChannelProperties config) throws Exception;
 }

+ 4 - 3
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsClientSendConfig.java → tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/SmsTempConfig.java

@@ -3,11 +3,12 @@ package cn.tr.plugin.sms.config;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 /**
- * @ClassName : SmsClientSendConfig
- * @Description : 短信发送配置
+ * @ClassName : SmsTempConfig
+ * @Description : 短信发送模板配置
  * @Author : LF
  * @Date: 2023年03月15日
  */
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
-public interface SmsClientSendConfig {
+public interface SmsTempConfig {
+
 }

+ 2 - 4
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsClientSendConfig.java → tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsChannelProperties.java

@@ -1,6 +1,6 @@
 package cn.tr.plugin.sms.config.ali;
 
-import cn.tr.plugin.sms.config.SmsClientSendConfig;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
 import lombok.Data;
 import javax.validation.constraints.NotBlank;
 
@@ -14,9 +14,7 @@ import java.util.Map;
  * @Date: 2023年03月15日
  */
 @Data
-public class
-
-AliSmsClientSendConfig implements SmsClientSendConfig {
+public class AliSmsChannelProperties extends SmsChannelProperties {
 
     //接收短信的手机号码
     @NotBlank

+ 132 - 32
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsClient.java

@@ -1,17 +1,25 @@
 package cn.tr.plugin.sms.config.ali;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.exceptions.ExceptionUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.tr.core.exception.TRExcCode;
+import cn.tr.core.pojo.KeyValue;
 import cn.tr.core.utils.JsonUtils;
 import cn.tr.plugin.sms.bo.SmsCommonResult;
+import cn.tr.plugin.sms.bo.SmsReceiveRespBO;
 import cn.tr.plugin.sms.bo.SmsSendRespBO;
+import cn.tr.plugin.sms.bo.SmsTemplateRespBO;
 import cn.tr.plugin.sms.config.AbstractSmsClient;
-import com.aliyuncs.DefaultAcsClient;
-import com.aliyuncs.IAcsClient;
-import com.aliyuncs.exceptions.ClientException;
-import com.aliyuncs.profile.DefaultProfile;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @ClassName : AliSmsClient
@@ -20,44 +28,136 @@ import lombok.extern.slf4j.Slf4j;
  * @Date: 2023年03月15日
  */
 @Slf4j
-public class AliSmsClient extends AbstractSmsClient<AliSmsClientConfig,AliSmsClientSendConfig> {
-    private IAcsClient client;
-    private final AliyunSmsCodeMapping smsCodeMapping;
-    public AliSmsClient(String id, AliSmsClientConfig clientConfig) {
+public class AliSmsClient extends AbstractSmsClient<AliSmsTempConfig> {
+    private Client client;
+
+    public AliSmsClient(String id, SmsChannelProperties clientConfig) {
         super(id, clientConfig);
-        this.smsCodeMapping=new AliyunSmsCodeMapping();
     }
 
     @Override
-    public void doInit(AliSmsClientConfig config) {
-        DefaultProfile profile = DefaultProfile.getProfile(config.getRegionId(), config.getAccessKeyId(), config.getAccessKeySecret());
-        client = new DefaultAcsClient(profile);
+    public void doInit(SmsChannelProperties config) throws Exception {
+        client = createClient(config.getApiKey(), config.getApiSecret());
     }
 
     @Override
-    public SmsCommonResult<SmsSendRespBO> sendSms(AliSmsClientSendConfig sendConfig) {
-        SendSmsRequest sendSmsRequest =new SendSmsRequest();
-        sendSmsRequest.setPhoneNumbers(sendConfig.getPhoneNumber());
-        sendSmsRequest.setSignName(sendConfig.getSignName());
-        sendSmsRequest.setTemplateCode(sendConfig.getTemplateCode());
-        sendSmsRequest.setTemplateParam(JsonUtils.toJsonString(sendConfig.getTemplateParam()));
-        sendSmsRequest.setSmsUpExtendCode(sendConfig.getSmsUpExtendCode());
-        sendSmsRequest.setOutId(sendConfig.getOutId());
-        SendSmsResponse response=null;
+    protected SmsCommonResult<SmsSendRespBO> doSendSms(String sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
+        HashMap<String, Object>  templateParamMap= new HashMap<>();
+        if (CollectionUtil.isNotEmpty(templateParams)) {
+            for (KeyValue<String, Object> templateParam : templateParams) {
+                templateParamMap.put(templateParam.getKey(),templateParam.getValue());
+            }
+        }
+        SendSmsRequest request = new SendSmsRequest();
+        request.setSignName(getConfig().getSignature());
+        request.setTemplateParam(JsonUtils.toJsonString(templateParamMap));
+        request.setTemplateCode(apiTemplateId);
+        request.setPhoneNumbers(mobile);
+        request.setOutId(sendLogId);
+        SendSmsResponse response;
         try {
-            response = client.getAcsResponse(sendSmsRequest);
-        } catch (ClientException e) {
-            return SmsCommonResult.build(e.getErrCode(), formatResultMsg(e), e.getRequestId(), null, smsCodeMapping);
+            response = client.sendSms(request);
+            if(log.isDebugEnabled()){
+                log.debug("[AliSmsClient] logId:{} ,mobile:{},responseCode:{},body:{}",sendLogId,mobile,response.statusCode,StrUtil.toString(response.body));
+            }
+            SmsSendRespBO sendResp = new SmsSendRespBO().setSerialNo(response.getBody().bizId);
+            if(StrUtil.equals(response.body.code,"OK")){
+                return SmsCommonResult.build(TRExcCode.SUCCESS.getErrCode(),response.getBody().code, response.getBody().getMessage(), response.getBody().getRequestId(), sendResp);
+            }
+            return SmsCommonResult.build(TRExcCode.SERVICE_ERROR_C0001.getErrCode(),response.getBody().code, response.getBody().getMessage(), response.getBody().getRequestId(), sendResp);
+        }catch (Exception ex){
+            return SmsCommonResult.build(TRExcCode.SERVICE_ERROR_C0001.getErrCode(),null, ExceptionUtil.getMessage(ex), null, null);
+        }
+    }
+
+    @Override
+    protected List<SmsReceiveRespBO> doParseSmsReceiveStatus(String text) throws Throwable {
+        List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
+        if(CollectionUtil.isEmpty(statuses)){
+            return Collections.emptyList();
         }
+        return statuses.stream().map(status -> {
+            SmsReceiveRespBO resp = new SmsReceiveRespBO();
+            resp.setSuccess(status.getSuccess());
+            resp.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg());
+            resp.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime());
+            resp.setSerialNo(status.getBizId()).setLogId(status.getOutId());
+            return resp;
+        }).collect(Collectors.toList());
+    }
 
-        return SmsCommonResult.build(response.getCode(), response.getMessage(), response.getRequestId(), new SmsSendRespBO().setSerialNo(response.getBizId()), smsCodeMapping);
+    @Override
+    protected SmsCommonResult<SmsTemplateRespBO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
+        return null;
+    }
 
+    private Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+                .setAccessKeyId(accessKeyId)
+                .setAccessKeySecret(accessKeySecret);
+        // 访问的域名
+        config.endpoint = "dysmsapi.aliyuncs.com";
+        return new Client(config);
     }
 
-    private static String formatResultMsg(ClientException ex) {
-        if (StrUtil.isEmpty(ex.getErrorDescription())) {
-            return ex.getErrMsg();
-        }
-        return ex.getErrMsg() + " => " + ex.getErrorDescription();
+    /**
+     * 短信接收状态
+     *
+     * 参见 https://help.aliyun.com/document_detail/101867.html 文档
+     *
+     * @author 芋道源码
+     */
+    @Data
+    public static class SmsReceiveStatus {
+
+        /**
+         * 手机号
+         */
+        @JsonProperty("phone_number")
+        private String phoneNumber;
+        /**
+         * 发送时间
+         */
+        @JsonProperty("send_time")
+        private Date sendTime;
+        /**
+         * 状态报告时间
+         */
+        @JsonProperty("report_time")
+        private Date reportTime;
+        /**
+         * 是否接收成功
+         */
+        private Boolean success;
+        /**
+         * 状态报告说明
+         */
+        @JsonProperty("err_msg")
+        private String errMsg;
+        /**
+         * 状态报告编码
+         */
+        @JsonProperty("err_code")
+        private String errCode;
+        /**
+         * 发送序列号
+         */
+        @JsonProperty("biz_id")
+        private String bizId;
+        /**
+         * 用户序列号
+         *
+         * 这里我们传递的是 SysSmsLogDO 的日志编号
+         */
+        @JsonProperty("out_id")
+        private String outId;
+        /**
+         * 短信长度,例如说 1、2、3
+         *
+         * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
+         */
+        @JsonProperty("sms_size")
+        private Integer smsSize;
+
     }
 }

+ 0 - 45
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsClientConfig.java

@@ -1,45 +0,0 @@
-package cn.tr.plugin.sms.config.ali;
-
-import cn.tr.plugin.sms.config.SmsClientConfig;
-import lombok.Data;
-import org.hibernate.validator.constraints.NotBlank;
-
-/**
- * @ClassName : AliSmsClientConfig
- * @Description :
- * @Author : LF
- * @Date: 2023年03月15日
- */
-@Data
-public class AliSmsClientConfig implements SmsClientConfig {
-
-    private String regionId;
-    private String accessKeyId;
-
-    private String accessKeySecret;
-
-    public String getRegionId() {
-        return regionId;
-    }
-
-    public void setRegionId(String regionId) {
-        this.regionId = regionId;
-    }
-
-    public String getAccessKeyId() {
-        return accessKeyId;
-    }
-
-    public void setAccessKeyId(String accessKeyId) {
-        this.accessKeyId = accessKeyId;
-    }
-
-    public String getAccessKeySecret() {
-        return accessKeySecret;
-    }
-
-    public void setAccessKeySecret(String accessKeySecret) {
-        this.accessKeySecret = accessKeySecret;
-    }
-
-}

+ 19 - 0
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliSmsTempConfig.java

@@ -0,0 +1,19 @@
+package cn.tr.plugin.sms.config.ali;
+
+import cn.tr.plugin.sms.config.SmsTempConfig;
+import lombok.Data;
+
+/**
+ * @ClassName : AliSmsClientConfig
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月15日
+ */
+@Data
+public class AliSmsTempConfig implements SmsTempConfig {
+
+    private String regionId;
+    private String accessKeyId;
+
+    private String accessKeySecret;
+}

+ 0 - 41
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/config/ali/AliyunSmsCodeMapping.java

@@ -1,41 +0,0 @@
-package cn.tr.plugin.sms.config.ali;
-import cn.tr.core.exception.BaseCode;
-import cn.tr.core.exception.TRExcCode;
-import cn.tr.plugin.sms.bo.SmsCodeMapping;
-import cn.tr.plugin.sms.enums.SmsFrameworkErrorCodeConstants;
-
-/**
- * 阿里云的 SmsCodeMapping 实现类
- *
- * 参见 https://help.aliyun.com/document_detail/101346.htm 文档
- *
- * @author 芋道源码
- */
-public class AliyunSmsCodeMapping implements SmsCodeMapping {
-
-    @Override
-    public BaseCode apply(String apiCode) {
-        switch (apiCode) {
-            case "OK": return TRExcCode.SUCCESS;
-            case "isv.ACCOUNT_NOT_EXISTS":
-            case "isv.ACCOUNT_ABNORMAL":
-            case "MissingAccessKeyId": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID;
-            case "isp.RAM_PERMISSION_DENY": return SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY;
-            case "isv.INVALID_JSON_PARAM":
-            case "isv.INVALID_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR;
-            case "isv.BUSINESS_LIMIT_CONTROL": return SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL;
-            case "isv.DAY_LIMIT_CONTROL": return SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL;
-            case "isv.SMS_CONTENT_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID;
-            case "isv.SMS_TEMPLATE_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID;
-            case "isv.SMS_SIGNATURE_ILLEGAL":
-            case "isv.SIGN_NAME_ILLEGAL":
-            case "isv.SMS_SIGN_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID;
-            case "isv.AMOUNT_NOT_ENOUGH":
-            case "isv.OUT_OF_SERVICE": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
-            case "isv.MOBILE_NUMBER_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID;
-            case "isv.TEMPLATE_MISSING_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
-            default: return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
-        }
-    }
-
-}

+ 8 - 13
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/enums/SmsChannelEnum.java

@@ -1,9 +1,10 @@
 package cn.tr.plugin.sms.enums;
 
 import cn.hutool.core.util.ArrayUtil;
-import cn.tr.core.enums.IEnum;
-import cn.tr.plugin.sms.config.SmsClientConfig;
-import cn.tr.plugin.sms.config.SmsClientSendConfig;
+import cn.tr.plugin.sms.config.AbstractSmsClient;
+import cn.tr.plugin.sms.config.SmsTempConfig;
+import cn.tr.plugin.sms.config.ali.AliSmsClient;
+import cn.tr.plugin.sms.config.ali.AliSmsTempConfig;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -16,20 +17,14 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum SmsChannelEnum{
-
-    /**
-     * 企业微信
-     */
-    DEBUG_EWX("DEBUG_WX_ENTER",null,null ),
     /**
      * 阿里云
      */
-    ALIYUN("ALIYUN", null,null),
+    ALIYUN("ALIYUN", AliSmsTempConfig.class, AliSmsClient.class),
     /**
      * 腾讯云
      */
-    TENCENT("TENCENT", null,null),
-//    HUA_WEI("HUA_WEI", "华为云"),
+    TENCENT("TENCENT", null,null)
     ;
 
     /**
@@ -40,11 +35,11 @@ public enum SmsChannelEnum{
     /**
      * 客户端配置类
      */
-    private final Class<? extends SmsClientConfig> clientConfigClass;
+    private final Class<? extends SmsTempConfig> tempConfig;
     /**
      * 客户端发送消息类
      */
-    private final Class<? extends SmsClientSendConfig> clientClass;
+    private final Class<? extends AbstractSmsClient> clientClass;
 
     public static SmsChannelEnum getByChannel(String channel) {
         return ArrayUtil.firstMatch(o -> o.getChannel().equals(channel), values());

+ 0 - 55
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/enums/SmsErrorCode.java

@@ -1,55 +0,0 @@
-package cn.tr.plugin.sms.enums;
-
-import cn.tr.core.exception.BaseCode;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 短信框架的错误码枚举
-
- *
- * @author tr
- */
-@AllArgsConstructor
-@Getter
-public enum  SmsErrorCode implements BaseCode {
-
-    SMS_UNKNOWN("S0001", "未知错误,需要解析"),
-    // ========== 权限 / 限流等相关 S2开头 ==========
-
-    SMS_PERMISSION_DENY("S2001", "没有发送短信的权限"),
-    SMS_IP_DENY("S2002", "IP 不允许发送短信"),
-
-    // 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
-    SMS_SEND_BUSINESS_LIMIT_CONTROL("S2003", "指定手机的发送限流"),
-    // 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
-    SMS_SEND_DAY_LIMIT_CONTROL ("S2004", "每天的发送限流"),
-
-    SMS_SEND_CONTENT_INVALID ("S2005", "短信内容有敏感词"),
-
-    // 腾讯云:为避免骚扰用户,营销短信只允许在8点到22点发送。
-    SMS_SEND_MARKET_LIMIT_CONTROL("S2006", "营销短信发送时间限制"),
-
-    // ========== 模板相关 2001000200 ==========
-    SMS_TEMPLATE_INVALID ("S2007", "短信模板不合法"), // 包括短信模板不存在
-    SMS_TEMPLATE_PARAM_ERROR("S2008", "模板参数不正确"),
-
-    // ========== 签名相关 2001000300 ==========
-    SMS_SIGN_INVALID ("S2009", "短信签名不可用"),
-
-    // ========== 账户相关 2001000400 ==========
-    SMS_ACCOUNT_MONEY_NOT_ENOUGH ("S2010", "账户余额不足"),
-    SMS_ACCOUNT_INVALID("S2011", "apiKey 不存在"),
-
-    // ========== 其它相关 2001000900 开头 ==========
-    SMS_API_PARAM_ERROR ("S2012", "请求参数缺失"),
-    SMS_MOBILE_INVALID("S2013", "手机格式不正确"),
-    SMS_MOBILE_BLACK ("S2014", "手机号在黑名单中"),
-    SMS_APP_ID_INVALID("S2015", "SdkAppId不合法"),
-
-    EXCEPTION("S2016", "调用异常");;
-    @Getter
-    private String errCode;
-    @Getter
-    private String errMsg;
-}

+ 0 - 58
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/enums/SmsFrameworkErrorCodeConstants.java

@@ -1,58 +0,0 @@
-package cn.tr.plugin.sms.enums;
-
-
-import cn.tr.core.exception.BaseCode;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 短信框架的错误码枚举
- *
- * 短信框架,使用 2-001-000-000 段
- *
- * @author 芋道源码
- */
-@Getter
-@AllArgsConstructor
-public enum  SmsFrameworkErrorCodeConstants implements BaseCode {
-
-    SMS_UNKNOWN ("2001000000", "未知错误,需要解析"),
-
-    // ========== 权限 / 限流等相关 2001000100 ==========
-
-    SMS_PERMISSION_DENY("2001000100", "没有发送短信的权限"),
-     SMS_IP_DENY ("2001000100", "IP 不允许发送短信"),
-
-    // 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
-     SMS_SEND_BUSINESS_LIMIT_CONTROL("2001000102", "指定手机的发送限流"),
-    // 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
-     SMS_SEND_DAY_LIMIT_CONTROL("2001000103", "每天的发送限流"),
-
-     SMS_SEND_CONTENT_INVALID("2001000104", "短信内容有敏感词"),
-
-    // 腾讯云:为避免骚扰用户,营销短信只允许在8点到22点发送。
-     SMS_SEND_MARKET_LIMIT_CONTROL("2001000105", "营销短信发送时间限制"),
-
-    // ========== 模板相关 2001000200 ==========
-     SMS_TEMPLATE_INVALID ("2001000200", "短信模板不合法"), // 包括短信模板不存在
-     SMS_TEMPLATE_PARAM_ERROR("2001000201", "模板参数不正确"),
-
-    // ========== 签名相关 2001000300 ==========
-     SMS_SIGN_INVALID ("2001000300", "短信签名不可用"),
-
-    // ========== 账户相关 2001000400 ==========
-     SMS_ACCOUNT_MONEY_NOT_ENOUGH ("2001000400", "账户余额不足"),
-     SMS_ACCOUNT_INVALID ("2001000401", "apiKey 不存在"),
-
-    // ========== 其它相关 2001000900 开头 ==========
-     SMS_API_PARAM_ERROR ("2001000900", "请求参数缺失"),
-     SMS_MOBILE_INVALID("2001000901", "手机格式不正确"),
-     SMS_MOBILE_BLACK("2001000902", "手机号在黑名单中"),
-    SMS_APP_ID_INVALID("2001000903", "SdkAppId不合法"),
-
-    EXCEPTION ("2001000999", "调用异常");
-
-    private String errCode;
-    private String errMsg;
-
-}

+ 38 - 0
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/java/cn/tr/plugin/sms/properties/SmsChannelProperties.java

@@ -0,0 +1,38 @@
+package cn.tr.plugin.sms.properties;
+
+
+import cn.tr.plugin.sms.enums.SmsChannelEnum;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @ClassName : SmsClientSendConfig
+ * @Description : 短信渠道配置
+ * @Author : LF
+ * @Date: 2023年03月15日
+ */
+@Data
+@Accessors(chain = true)
+public class SmsChannelProperties {
+    /** 渠道id */
+    private String id;
+
+    /** 短信签名 */
+    private String signature;
+
+    /**
+     * 渠道编码
+     *
+     * 枚举 {@link SmsChannelEnum}
+     */
+    private String type;
+
+    /** 短信 API 的账号 */
+    private String apiKey;
+
+    /** 短信 API 的秘钥 */
+    private String apiSecret;
+
+    /** 短信发送回调 URL */
+    private String callbackUrl;
+}

+ 1 - 0
tr-plugins/tr-spring-boot-starter-plugin-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.tr.plugin.sms.config.DefaultSmsClientFactory

+ 18 - 21
tr-plugins/tr-spring-boot-starter-plugin-sms/src/test/java/cn/tr/plugin/sms/SmsTest.java

@@ -1,19 +1,16 @@
 package cn.tr.plugin.sms;
 
-import cn.hutool.core.map.MapUtil;
-import cn.tr.core.pojo.CommonResult;
+import cn.tr.core.pojo.KeyValue;
 import cn.tr.core.utils.JsonUtils;
 import cn.tr.plugin.sms.bo.SmsCommonResult;
 import cn.tr.plugin.sms.bo.SmsSendRespBO;
+import cn.tr.plugin.sms.properties.SmsChannelProperties;
 import cn.tr.plugin.sms.config.ali.AliSmsClient;
-import cn.tr.plugin.sms.config.ali.AliSmsClientConfig;
-import cn.tr.plugin.sms.config.ali.AliSmsClientSendConfig;
+import cn.tr.plugin.sms.config.ali.AliSmsChannelProperties;
 import cn.tr.plugin.test.ut.BaseMockitoUnitTest;
 import org.junit.jupiter.api.Test;
 
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.*;
 
 /**
  * @ClassName : SmsTest
@@ -25,23 +22,23 @@ import java.util.Map;
 public class SmsTest extends BaseMockitoUnitTest {
 
     @Test
-    public void aliSmsTest(){
-        AliSmsClientConfig aliSmsClientConfig = new AliSmsClientConfig();
-        aliSmsClientConfig.setAccessKeyId("LTAI4G7FA9ytMc76oNkJ45YJ");
-        aliSmsClientConfig.setAccessKeySecret("R7hOvMfiHb0PYroDqUDXAYgB9htQss");
-        aliSmsClientConfig.setRegionId("cn-hangzhou");
-        AliSmsClient aliSmsClient = new AliSmsClient("123",aliSmsClientConfig);
-        aliSmsClient.doInit(aliSmsClientConfig);
-        AliSmsClientSendConfig sendConfig = new AliSmsClientSendConfig();
+    public void aliSmsTest() throws Exception {
+        SmsChannelProperties smsChannelProperties = new SmsChannelProperties();
+        smsChannelProperties.setApiKey("LTAI4G7FA9ytMc76oNkJ45YJ");
+        smsChannelProperties.setApiSecret("R7hOvMfiHb0PYroDqUDXAYgB9htQss");
+        smsChannelProperties.setSignature("驼人医疗");
+        AliSmsClient aliSmsClient = new AliSmsClient("123", smsChannelProperties);
+        aliSmsClient.doInit(smsChannelProperties);
+
+
+        AliSmsChannelProperties sendConfig = new AliSmsChannelProperties();
         sendConfig.setPhoneNumber("18339543638");
         sendConfig.setSignName("驼人医疗");
         sendConfig.setTemplateCode("SMS_247815102");
-        Map<String, String> map = new HashMap<>();
-        map.put("patientCode","1234");
-        map.put("code","1234");
-        sendConfig.setTemplateParam(map);
-
-        SmsCommonResult<SmsSendRespBO> result = aliSmsClient.sendSms(sendConfig);
+        List<KeyValue<String,Object>> objects = new ArrayList<>();
+        objects.add(KeyValue.of("patientCode","1234"));
+        objects.add(KeyValue.of("type","1234"));
+        SmsCommonResult<SmsSendRespBO> result = aliSmsClient.sendSms("123", "18339543638", "SMS_247815102", objects);
         System.out.println(JsonUtils.toJsonString(result));
     }
 }

+ 1 - 1
tr-plugins/tr-spring-boot-starter-plugin-websocket/src/main/java/cn/tr/plugin/websocket/config/TrWsMsgHandler.java

@@ -51,7 +51,7 @@ public class TrWsMsgHandler implements IWsMsgHandler {
     @Override
     public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
         WsAuthResult authResult = authHandler.afterHandshakeAuth(httpRequest, httpResponse, channelContext);
-        log.warn("websocket 握手成功后, code : {},errorMsg: {}",authResult.getCode(),authResult.getErrorMsg());
+        log.warn("websocket 握手成功后, type : {},errorMsg: {}",authResult.getCode(),authResult.getErrorMsg());
         WsUtil.send(channelContext, WsSignalBuilder.buildHelloSignal(authResult));
         if (TRExcCode.SUCCESS.getErrCode().equals(authResult.getCode())) {
             WsHeartbeatManage.register(channelContext);

+ 40 - 0
tr-test/src/main/resources/application-dev.yml

@@ -0,0 +1,40 @@
+spring:
+  application:
+    name: tr-dev
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://192.168.100.32:3306/tr-footstone?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&useInformationSchema=true
+    username: root
+    password: 123456
+  redis:
+    # 地址
+    host: 192.168.100.32
+    # 端口,默认为6379
+    port: 9736
+    # 数据库索引
+    database: 8
+    # 密码
+    password: 6E6985E1F7CB40F24A\.
+    # 连接超时时间
+    timeout: 30s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 16
+        # 连接池中的最大空闲连接
+        max-idle: 16
+        # 连接池的最大数据库连接数
+        max-active: 16
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: -1ms
+  rabbitmq:
+    host: 192.168.100.32
+    port: 5672
+    username: guest
+    password: guest
+    listener:
+      simple:
+        acknowledge-mode: auto
+    #    publisher-confirms: true
+    publisher-returns: true

+ 16 - 0
tr-test/src/main/resources/application-stream.yml

@@ -0,0 +1,16 @@
+spring:
+  cloud:
+    stream:
+      rabbit:
+        binder:
+          connection-name-prefix: ${spring.application.name}
+      bindings:
+        #短信发送通道名称
+        smsSupplier-out-0:
+          #交换机名称
+          destination: smsSend
+        smsConsumer-in-0:
+          destination: smsSend
+          group: sms               #分组
+    function:
+      definition: smsSupplier;smsConsumer

+ 9 - 31
tr-test/src/main/resources/application.yml

@@ -14,45 +14,18 @@ mybatis-plus:
       logic-delete-value: 1
       logic-delete-field: deleted
 spring:
-  datasource:
-    type: com.alibaba.druid.pool.DruidDataSource
-    driverClassName: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://192.168.100.32:3306/tr-footstone?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&useInformationSchema=true
-    username: root
-    password: 123456
-  redis:
-    # 地址
-    host: 192.168.100.32
-    # 端口,默认为6379
-    port: 9736
-    # 数据库索引
-    database: 8
-    # 密码
-    password: 6E6985E1F7CB40F24A\.
-    # 连接超时时间
-    timeout: 30s
-    lettuce:
-      pool:
-        # 连接池中的最小空闲连接
-        min-idle: 16
-        # 连接池中的最大空闲连接
-        max-idle: 16
-        # 连接池的最大数据库连接数
-        max-active: 16
-        # #连接池最大阻塞等待时间(使用负值表示没有限制)
-        max-wait: -1ms
   cache:
     type: redis
     redis:
 #      7天
       time-to-live: 604800000
   profiles:
-    include: doc,sys
+    include: doc,sys,stream
+    active: dev
   jackson:
     date-format: yyyy-MM-dd HH:mm:ss
     time-zone: GMT-8
-  application:
-    name: tr
+
 
 
 tr:
@@ -78,15 +51,20 @@ tr:
       - gen_config
       - gen_basic
       - sys_menu
+      - sys_role_menu
       - sys_dict
       - sys_dict_item
+#      租户模块
       - sys_tenant
       - sys_tenant_package
       - sys_tenant_package_menu
-      - sys_role_menu
       - sys_user_role
       - sys_portal_menu
       - sys_user_portal
+#      短信模块
+      - sys_sms_temp
+      - sys_sms_channel
+      - sys_sms_log
 
 sa-token:
   is-read-header: true