1.为什么要设计SMS短信服务?

a.在大多数项目中都存在对接短信的需求且短信的执行流程都是一样的;
b.新设计的通用短信平台具有高可用、高可扩、集群容错、流量削峰、负载均衡、渠道路由等功能。

2.设计SMS短信服务的的困难点以及如何解决的呢?

困难点 解决方案
短信模板和签名如何申请? 需要事先在三方短信平台(如:阿里云、腾讯云、百度云)开通短信账号,在平台中申请签名和模板,通过审核后才可使用
在高并发情况下如何保证系统的稳定性? 使用spring-cloud-stream-rabbitMQ来实现异步发送【流量削峰】来保证其发送稳定性
如何提高后续对接新渠道的扩展性? 需要使用工厂+代理模式构建易于扩展的平台
如何做到渠道自动路由,路由中负载均衡如何实现 工厂+代理实现,轮询、随机、加权轮询、加权随机、一致hash
短信内容安全校验怎么做? 由三方短信平台来保证

3.运营商在短信平台前需要做些什么?

运营商在各个三方短信平台申请对应的秘钥ID和秘钥后,配置到短信平台。

4.商户在发送短信时,如何获取当前的短信配置?

每个平台的短信配置都不一样,需要从各个平台的SDK文档查看。
a.**model-basic-producer**模块在项目启动时会从mysql中加载所有短信配置到redis中:

  1. 1. **根据阿里云的短信标识,在数据库中查询到阿里云SMS配置信息;**
  2. 1. **根据业务前缀创建桶对象,把数据库中的aliyun短信配置存到桶对象中。**

b.用户发送短信请求,携带短信标识从redis中获得对应的短信配置,使用应用私钥对明文字符串进行加密;
代码实现(以阿里云为例):

  1. /**
  2. * @ClassName AlipayConfig.java
  3. * @Description 支付宝配置类
  4. */
  5. @Slf4j
  6. @Configuration
  7. public class AliyunSmsConfig {
  8. @Autowired
  9. ISmsChannelService smsChannelService;
  10. @Autowired
  11. RedissonClient redissonClient;
  12. /***
  13. * @description 初始化短信配置
  14. */
  15. @PostConstruct
  16. public void initSmsConfig() {
  17. //查询阿里云SMS的配置信息
  18. SmsChannel smsChannel = smsChannelService.findChannelByChannelLabel(SuperConstant.ALIYUN_SMS);
  19. if (EmptyUtil.isNullOrEmpty(smsChannel)){
  20. log.warn("阿里云SMS的未配置");
  21. return;
  22. }
  23. RBucket<SmsChannel> aliyunSmsClient = redissonClient.getBucket("sms:aliyunSmsChannel");
  24. aliyunSmsClient.set(smsChannel);
  25. }
  26. public Client createOrUpdateClient(SmsChannel smsChannel){
  27. RBucket<SmsChannel> aliyunSmsClient = redissonClient.getBucket("sms:aliyunSmsChannel");
  28. aliyunSmsClient.set(smsChannel);
  29. Config config = new Config()
  30. // 阿里云AccessKey ID
  31. .setAccessKeyId(smsChannel.getAccessKeyId())
  32. // 阿里云AccessKey Secret
  33. .setAccessKeySecret(smsChannel.getAccessKeySecret());
  34. // 访问的域名
  35. config.endpoint = smsChannel.getDomain();
  36. try {
  37. return new Client(config);
  38. } catch (Exception e) {
  39. log.error("阿里云SMS的配置信息出错:{}", ExceptionsUtil.getStackTraceAsString(e));
  40. return null;
  41. }
  42. }
  43. /***
  44. * @description 移除配置
  45. * @return
  46. */
  47. public void removeClient(){
  48. RBucket<SmsChannel> aliyunSmsClient = redissonClient.getBucket("sms:aliyunSmsChannel");
  49. aliyunSmsClient.delete();
  50. }
  51. /***
  52. * @description 获得配置
  53. * @return
  54. */
  55. public Client queryClient(){
  56. RBucket<SmsChannel> aliyunSmsClient = redissonClient.getBucket("sms:aliyunSmsChannel");
  57. return this.createOrUpdateClient(aliyunSmsClient.get());
  58. }
  59. }

5.短信通道的功能需求及表结构设计?

表结构设计:
channel_name:通道名称
channel_label:通道唯一标记
channel_type:通道类型,1代表文字,2代表语音,3代表推送
domain:域名,各个三方短信平台的域名
access_key_id:秘钥id
access_key_secret:秘钥值
other_config:其他配置,这里采用了表中表的设计,就是把各个三方平台不一样的短信配置,都放到这个字段中,使用JSON格式存储起来。
level:优先级,数字越大代表优先级越高
remark:说明
代码实现简单的单表CRUD实现,需要注意的是,增删改操作一定要添加事务。

6.短信签名的功能需求及表结构设计?

表结构设计:
sign_name:签名名称
sign_code:三方签名码
sign_type:签名类型
document_type:证明类型
international:是否是港澳台/国际短信
sign_purpose:签名用途,0表示自用,1表示他用
remark:短信签名申请说明
accept_status:是否受理成功
accept_msg:受理返回的信息
audit_status:审核状态
audit_msg:审核信息
sign_no:应用签名编号:(多签名编号相同则认为是一个签名多个通道公用)
困难点:
客户端发送短信签名申请后在后端要同时发送给对应的三方短信平台,但是三方的短信平台接口是相互不兼容的,需要考虑的是如何把这些相互不兼容的接口给兼容。实现方案是采用适配器模式。
代码实现:

  1. 1. **短信签名dubbo接口实现类依赖短信签名适配器接口定义;**
  2. 1. **短信签名适配器接口实现类实现了短信签名适配器接口,在该实现类做了以下几件事:**
  3. 1. **创建一个Map集合的成员变量;**
  4. 1. **在一个静态内部类里,map集合调用put方法,key是短信渠道,value是三方短信平台的签名处理器接口的实现类存到Spring IOC容器的Bean的名称。**
  5. 1. **在申请签名/删除签名/修改签名/查询签名方法里直接使Map集合的对象调用get( )方法传入客户端给的短信渠道参数,得到对应的三方短信平台的签名处理器接口的实现类存到Spring IOC容器的Bean的名称;**
  6. 1. **根据这个名称在Spring IOC容器找到对应的三方短信平台签名处理器接口的实现类对象**
  7. 3. **三方短信平台的签名处理器接口的实现类依赖了三方短信平台的短信配置类,在该实现类有以下几个方法:(代码参考三方短信平台提供的SDK文档)**
  8. 1. **申请签名:**
  9. 1. **删除签名;**
  10. 1. **修改签名;**
  11. 1. **查询签名;**

7.短信模块的功能开发以及表结构设计

表结构设计:
channel_label :短信通道唯一标识
template_name:模板名称
sms_type:短信类型
template_no:应用模板编号:(多个模板编号相同则认为是一个签名多个通道公用)
template_code:三方应用模板code
content:模板内容
other_config:其他配置,这里采用了表中表的设计,就是把各个三方平台不一样的模板变量配置,都放到这个字段中,使用JSON格式存储起来。
international:是否国际/港澳台短信
remark:模板说明
accept_status:是否受理成功
accept_msg:受理返回信息
audit_status:审核状态
audit_msg:审核信息
实现细节参考短信签名**

8.短信发送流程是怎样的?怎么实现的?有什么困难点?

困难点:
a.流量削峰怎么设计?通过spring-cloud-stream rabbitmq组件来进行处理,最初发送的短信不是直接调用SmsSendAdapter进行处理,而是通过SmsSource接口推送到SmsSink中,通过监听SmsListen进行发送
b.负载均衡怎么设计?在SmsSendAdapter中进行短信发送时,优先通过registerBeanHandler到SendLoadBalancer的负载均衡实现,从而找到合适的渠道进行发送。
短信发送的执行流程.png
执行流程:
1.客户端把发短信请求到服务器的短信业务系统中;
2.短信业务系统把短信信息发送到rabbitmq(实现异步解耦和流量削峰功能);
3.短信监听系统从rabbitmq拉取短信信息;
4.过滤黑名单即判断前端发短信的号码是否在黑名单中;
5.通过负载均衡选择对应的通道;
6.查询短信渠道;
7.查询签名;
8.模板参数兑换;
9.把短信信息发送到三方短信平台;
10.轮询查询发送短信发送的结果;
11.将结果同步到短信业务系统;

9.负载均衡算法设计的优化