多短信模板&多短信供应商, 可拔插,可拓展,支持使用者在同一个项目中指定注解以使用不同的短信模板实现,本项目已提交至maven
**仓库,你可以直接在项目pom.xml
中引入使用,
找个最新版引入坐标即可使用:
<dependency>
<groupId>io.github.weasley-j</groupId>
<artifactId>multiple-sms-spring-boot-starter</artifactId>
<version>${multiple-email.verison}</version>
</dependency>
multiple-sms-spring-boot-starter
启动器解决的事:如何使用一个注解
@SMS
和一个模板类SmsTemplate
在多个短信供应商和多个短信模板之间优雅的切换
requirement
item | requirement | remark |
---|---|---|
SpringBoot | 2.2.0.RELEASE <= version <= 3.0.0-M3 | |
JDK | JDK1.8 or latest | |
Environment | Spring Web Application |
project tree map
我们先来看一张笔者一朋友张三所在的公司的短信模板图:
张三的苦恼是每新开一个业务模块他都要把之前发送短信的业务代码CV一遍,有没有优雅的一种方式呢?接下来,我们一同交流下这种问题。
通常规模稍大点的公司的业务板块比较多,对应的则是每一种业务的短信消息通知,通常情况下,短信通知分为三类:
- 验证码通知
- 营销通知
- 内容通知
从以上图片中张三所在的公司短信消息的模板已达到9
个,传统的XxxUtils
的CV
编码方式会大大增加代码的重复率,基于SpringBoot
极高的可拓展性,我们可以有更优雅的编码方式。
import cn.alphahub.multiple.sms.SmsTemplate;
import cn.alphahub.multiple.sms.annotation.EnableMultipleSmsSupport;
import cn.alphahub.multiple.sms.annotation.SMS;
import cn.alphahub.multiple.sms.enums.SmsSupplier;
import cn.alphahub.multiple.sms.demo.MyCustomSmsClientDemoImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.alphahub.multiple.sms.SmsTemplate.SmsParam;
/**
* SMS Service Test Controller
* <p>多短信供应商、多短信模板示例Controller</p>
*
* @author lwj
* @version 1.0
* @date 2021-09-29 14:14
*/
@Slf4j
@RestController
@RequestMapping("/sms/support/demo")
@ConditionalOnBean(annotation = {EnableMultipleSmsSupport.class})
public class SmsServiceDemoController {
@Autowired
private SmsTemplate smsTemplate;
/**
* 使用默认短信模板发送短信
* <p>默认模板可以注解{@code @SMS},也可以不加</p>
*
* @param smsParam 短信参数
* @return 发送结果
*/
@PostMapping("/sendWithDefaultTemplate")
public Object sendWithDefaultTemplate(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 使用阿里云短信模板1发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "促销短信模板")
@PostMapping("/sendWithAliCloud1")
public Object sendWithAliCloud1(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 使用阿里云短信模板2发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "秒杀短信模板")
@PostMapping("/sendWithAliCloud2")
public Object sendWithAliCloud2(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 使用华为云发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "验证码短信模板", supplier = SmsSupplier.HUAWEI)
@PostMapping("/sendWithHuaweiCloud")
public Object sendWithHuaweiCloud(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 使用京东云发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "京东云短信验证码模板", supplier = SmsSupplier.JINGDONG)
@PostMapping("/sendWithJingdongCloud")
public Object sendWithJingdongCloud(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 使用七牛云发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "验证码短信模板", supplier = SmsSupplier.QINIU)
@PostMapping("/sendWithQiniuCloud")
public Object sendWithQiniuCloud(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 使用腾讯云发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "内容短信模板", supplier = SmsSupplier.TENCENT)
@PostMapping("/sendWithTencentCloud")
public Object sendWithTencentCloud(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 自定义短信实现发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(invokeClass = MyCustomSmsClientDemoImpl.class)
@PostMapping("/sendCustomSmsClient")
public Object sendWithCustomSmsClient(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
}
说明:
- 上面上
8
个发送短信消息的方法都是调用SmsTemplate
类的send(SmsParam smsParam)
方法 - 通过注解
@SMS
动态切换短信模板 - 第
8
个方法支持自定义短信实现,通过注解@SMS(invokeClass = MyCustomSmsClientDemoImpl.class)
指定自定义短信实现类 - 通过注解
@SMS
在同一短信提供商的多个短信模板、不同短信提供上商多个短信模板之间自由切换短信模板 - 发送短信的方法只有一个
send()
方法 - 支持的短信提供商有
5
家:阿里云、腾讯云、华为云、京东云、七牛云
提示:同一短信供应商下面的多个模板的模板名称(
template-name
)不能重复。
以下是阿里云、腾讯云、华为云、京东云、七牛云等短信模板的配置示例,包含模板情景:同一短信供应商多个短信模板、多个短信供应商多个模板
spring:
sms:
#默认线程池配置,一下参数是默认值,可根据自己的业务调整大小
thread:
core-pool-size: 50
maximum-pool-size: 200
keep-alive-time: 10
time-unit: seconds
capacity: 2000
#默认短信模板配置,短信模板名称, 默认: DEFAULT, 无需修改
template-name: DEFAULT
#默认短信供应商,大小写不敏感,可选值: ali, huawei, jingdong, qiniu, tencent
sms-supplier: ALI
#短信配置
sms-properties:
#短信access-key
access-key: accessKey1
#短信secret-key
secret-key: secretKey1
#区域
region-id: regionId1
#短信签名
sign-name: signName1
#短信模板code、短信模板id
template-code: templateCode1
#短信sdk的appId,有就填,没有留空,如:腾讯云需要、阿里云不需要
app-id: null
#多供应商、多短信模块配置
multi-sms-templates:
template-properties:
- template-name: "促销短信模板"
sms-supplier: ALI
sms-properties:
access-key: accessKey1
secret-key: secretKey1
region-id: regionId1
sign-name: signName1
template-code: templateCode1
- template-name: "秒杀短信模板"
sms-supplier: ALI
sms-properties:
access-key: accessKey1
secret-key: secretKey1
region-id: regionId1
sign-name: signName1
template-code: templateCode1
- template-name: "验证码短信模板"
sms-supplier: HUAWEI
sms-properties:
access-key: accessKey2
secret-key: secretKey2
region-id: regionId2
sign-name: signName2
template-code: templateCode2
app-id: your-app-id
- template-name: "京东云短信验证码模板"
sms-supplier: JINGDONG
sms-properties:
access-key: your-ak
secret-key: your-sk
region-id: cn-north-1
sign-name: your-sign-name
template-code: your-template-id
- template-name: "验证码短信模板"
sms-supplier: QINIU
sms-properties:
access-key: accessKey4
secret-key: secretKey4
region-id: regionId4
sign-name: signName4
template-code: templateCode4
- template-name: "腾讯云内容短信模板"
sms-supplier: TENCENT
sms-properties:
access-key: accessKey4
secret-key: secretKey4
region-id: "ap-nanjing"
sign-name: "xxx的个人主页"
template-code: 1141766
app-id: "your-sms-sdk-app-id"
import cn.alphahub.multiple.sms.SmsClient;
import cn.alphahub.multiple.sms.enums.SmsSupplier;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static cn.alphahub.multiple.sms.SmsClient.DefaultSmsClientPlaceholder;
/**
* 多模板短信注解
*
* @author lwj
* @version 1.0
* @apiNote 基于此注解解析不同的短信模板, 使用注解{@code @SMS}指定以:短信供应商、短信模板发送短信
* @date 2021-09-24
*/
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SMS {
/**
* 默认模板名称
*/
String DEFAULT_TEMPLATE = "DEFAULT";
/**
* 短信模板名称,必须和{@code yml}配置文件里面的{@code template-name}一致,默认:DEFAULT
*
* @return 短信模板名称
*/
String name() default DEFAULT_TEMPLATE;
/**
* 短信供应商,默认短信供应商: 阿里云
*
* @apiNote 如果需要拓展其他短信供应商,见枚举{@code SmsSupplier}
* @see SmsSupplier
*/
SmsSupplier supplier() default SmsSupplier.ALI;
/**
* 自定义实现发送发送短信的实现类,必须显现或继承{@code SmsClient}接口
*
* @return 发送短信的实现类class
* @apiNote 当指定自定义短信发送类时将优先采用自定义短信发送实现完成发送短信的逻辑
*/
Class<? extends SmsClient> invokeClass() default DefaultSmsClientPlaceholder.class;
}
@SMS
可作用于类、方法上, 支持自定义发送短信逻辑实现,通过invokeClass
指定自定义实现类。
import cn.alphahub.multiple.sms.SmsTemplate;
import cn.alphahub.multiple.sms.aspect.SmsAspect;
import cn.alphahub.multiple.sms.config.SmsConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Enable Multiple SMS Support
*
* @author lwj
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({SmsConfig.class, SmsAspect.class, SmsTemplate.class})
public @interface EnableMultipleSmsSupport {
}
注解@EnableMultipleSmsSupport
作用于类上,用于需要发送短信的web应用启用短信支持,并自动装配配置文件,只需要在发送短信的服务的yml
配置文件中配置3.1
的短信AK
、SK
等数据即可,
目前只整合一下5种短信供应商的短信实现,读者可以自行增加其他短信产商的发送短信的实现
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.stream.Stream;
/**
* 短信供应商类型枚举枚举
*
* @author lwj
* @version 1.0
* @date 2021-09-24
*/
@Getter
@AllArgsConstructor
public enum SmsSupplier {
/**
* 阿里云
*/
ALI("ALI_CLOUD", "阿里云"),
/**
* 华为云
*/
HUAWEI("HUAWEI_CLOUD", "华为云"),
/**
* 京东云
*/
JINGDONG("JINGDONG_CLOUD", "京东云"),
/**
* 七牛云
*/
QINIU("QINIU_CLOUD", "七牛云"),
/**
* 腾讯云
*/
TENCENT("TENCENT_CLOUD", "腾讯云"),
;
/**
* 供应商名称编码
*/
private final String code;
/**
* 供应商名称
*/
private final String name;
/**
* 获取短信供应商枚举
*
* @param code 供应商名称编码
* @return 短信供应商枚举
*/
public static SmsSupplier getEnum(String code) {
return Stream.of(SmsSupplier.values())
.filter(smsSupplier -> smsSupplier.getCode().equals(code))
.findFirst().orElse(null);
}
}
自定义短信发送实现需实现cn.alphahub.multiple.sms.SmsClient
接口并覆写send
方法。
步骤:
- 新建一个
springboot
的web
项目,pom.xml
中引入multiple-sms-spring-boot-starter
的maven
坐标:
<!-- multiple-sms-spring-boot-starter -->
<dependency>
<groupId>io.github.weasley-j</groupId>
<artifactId>multiple-sms-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
- 在该服务的启动类上标注注解
@EnableMultipleSmsSupport
启用短信支持
这是从我另一个项目
里面拆离的基础组件,注解是使用本项目的的注解是: @EnableMultipleSmsSupport
- 在
Service
或Controller
层注入SmsTemplate
,并在业务方法或业务类上标注注解@SMS
发送短信,详见源码cn.alphahub.multiple.sms.test.controller.SmsServiceDemoController
推荐注解
@SMS
作用于方法,方法级别使用。
- 当注解
@SMS
同时作用类,和方法上时,方法上注解@SMS
的优先级高于类上@SMS
注解的优先级 - 当注解
@SMS
作用方法上时,该方法短信客户端的为注解@SMS
指定的短信客户端 - 当注解
@SMS
作用类上时,该类所有短信模板方法发送短信的客户端都以注解@SMS
指定为准客户端
import cn.alphahub.multiple.sms.SmsTemplate;
import cn.alphahub.multiple.sms.annotation.SMS;
import cn.alphahub.multiple.sms.demo.MyCustomSmsClientDemoImpl;
import cn.alphahub.multiple.sms.enums.SmsSupplier;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import javax.annotation.Resource;
import static cn.alphahub.multiple.sms.SmsTemplate.SmsParam;
/**
* 测试{@code @SMS}同时标注在方法类、类方法上面
*
* @author lwj
* @version 1.0
* @date 2021-10-02 18:18
*/
@SMS
@Service
public class SMSAnnotateWithClassAndMethod {
@Autowired
private SmsTemplate smsTemplate;
/**
* 使用腾讯云发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(name = "腾讯云内容短信模板", supplier = SmsSupplier.TENCENT)
public Object sendWithTencentCloud(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 自定义短信实现发送短信
*
* @param smsParam 短信参数
* @return 发送结果
*/
@SMS(invokeClass = MyCustomSmsClientDemoImpl.class)
public Object sendWithCustomSmsClient(@RequestBody SmsParam smsParam) {
return smsTemplate.send(smsParam);
}
/**
* 嵌套调用,使用Spring AOP代理当前对象
*
* @param smsParam 短信参数
*/
public void nested(@RequestBody SmsParam smsParam) {
((SMSAnnotateWithClassAndMethod) AopContext.currentProxy()).sendWithTencentCloud(smsParam);
((SMSAnnotateWithClassAndMethod) AopContext.currentProxy()).sendWithCustomSmsClient(smsParam);
}
}
以上多模板短信实例代码的应用场景:
当用户在乐璟商城下单成功后,平台方应当发送消息通知给用户和商家,告知用户物流情况,告知商家本单交易情况。
假设
sendWithTencentCloud()
是告诉用户的短信模板消息,sendWithCustomSmsClient()
是告诉商家的短信模板消息,为了完成以上业务场景,我们写了nested()
方法来完成这个业务场景。
nested()
方法一共调用两个本类里面被注解@SMS
标注的方法完成我们的业务需求:“当用户在乐璟商城下单成功后,平台方应当发送消息通知给用户和商家,告知用户物流情况,告知商家本单交易情况”,- 基于
CGLib
的动态代理调用本类方法完成业务时,使用的是增强代理类去执行业务方法,并不是本类this
自身,因此我们需要从线程变量中获取当前AOP
的真实代理对象,让真实代理对象调用本类(this
)的方法执行业务,执行前后AOP
能根据我们设定好的代理规则解析正确的业务参数完成业务需求。