我是3y,一年CRUD教训用十年的markdown程序员长年被誉为优质八股文选手

明天要做的就是实现austin-apiaustin-api-impl模块的局部代码,这块实现了之后模块之间的一整条链路就买通咯


austin我的项目外围性能:发送音讯

我的项目呈现意义:只有公司内有发送音讯的需要,都应该要有相似austin的我的项目,对各类音讯进行对立发送解决。这有利于对性能的收拢,以及进步业务需要开发的效率

不多BB,开始明天的正题

01、接口设计

austini-api模块下定义发送音讯的接口,在austin-api-impl下实现具体的逻辑。我的接口实现定义:

public interface SendService {    /**     * 单模板单文案发送接口     * @param sendRequest     * @return     */    SendResponse send(SendRequest sendRequest);    /**     * 单模板多文案发送接口     * @param batchSendRequest     * @return     */    SendResponse batchSend(BatchSendRequest batchSendRequest);}

对外提供的接口,除了须要提供Single接口,最好还提供个Batch接口。因为很有可能业务方是须要一次批量执行的(如果只有Single接口,那就须要屡次近程调用,这样对业务而言就不太适合了)

我所定义的接口参数如下:

public class SendRequest {    /**     * 执行业务类型     */    private String code;    /**     * 音讯模板Id     */    private Long messageTemplateId;    /**     * 音讯相干的参数     */    private MessageParam messageParam;    }

通过messageTemplateId能够去数据库查出整个模板的信息,而MessageParam则是业务自行传入的参数(重要的是接收者以及文案的参数信息),而code则代表着以后申请要执行什么业务类型的(可基于该code扩大,前面会持续聊到)

02、代码实现

从流程能够看到,austin-api接管到申请之后,是把音讯发到MQ

这样做有什么益处呢?假如某音讯的服务超时,austin-api如果是间接调用下发接口服务,那可能会存在超时危险,拖垮整个接口性能。MQ在这是为了做异步和解耦,并且在肯定水平上抗住业务流量。

对于绝大多数发送的音讯而言,业务方也不太关怀是不是能在接口调用时就晓得发送后果,并且某些渠道在发送的时候也不晓得发送的后果(最初的后果是异步告知的,比方短信和PUSH推送)

基于以上的起因,引入MQ来承载接口的流量以及做异步,是十分正当的事。

前两天我在博客平台上发了一篇文章《面试官:零碎需要多变时如何设计? 》,有网友评论了一把:

面试官:我懂了,回去等告诉吧。 …… leader:小王,咱们那个可变零碎的重构打算写的怎么样了? 小王:没问题了,首先按找咱们的业务辨别出责任链,而后在每个具体的步骤中部署脚本,下层再减少一个服务编排的接口对立治理…… leader:听起来有点意思,明天的候选人怎么样? 小王:别提了,嘴上说5年教训有大型零碎设计,连redis都没用过。这不是快招聘季了吗,招两个实习生工具人进来给我打打下手就够了。 leader:好,把工夫节点和里程碑划分一下,confluence上立项开干吧。 小王:好嘞。

在这次实现中,我也是用了责任链模式,具体残缺的代码大家就去Gitee拉就好了。很多同学拉完代码发现看不懂了,大家能够依照上面的图去梳理下责任链的各个角色。如果切实看不懂,倡议翻下我以前写过的责任链文章(曾经投稿过两篇了)

回到代码实现吧,这次我实现的业务是:参数前置查看->参数拼装->发送音讯

呀,都画了这么多图了,先点个赞,关注一波先咯。

在这几个流程中,可能你下次拉代码的时候,会看到有“后置查看”,或者别的什么的。但不论怎么样,加这种逻辑我再也不必在同一个类上写各种if else啦。只有在某个节点处增加一个Action就完事了。

(注:这是第一版实现,前面必定会在根底上增加逻辑或正文的,其实曾经在写了,但我个别是有个小阶段再push代码,所以记得star下gitee不便看最新的代码)

先来说前置查看吧,次要就判断模板ID是否有传入,音讯参数是否有传入(对参数的惯例查看,如果有问题,间接break掉链路,返回通知调用方有问题)

接着来看参数拼装,这块次要就是通过模板ID去查整个模板的内容,而后依据业务入参拼装出本人的TaskInfo(工作音讯)。

可能有同学会有疑难❓:为什么不能间接用模板的POJO呢?反而须要拼装成TaskInfo?

其实还是比拟好了解的,模板是作为给用户去配置该音讯的信息,这是最最原始的信息。然而咱们发送的时候是须要做解决的。比方,我要在用户写好的URL链接上拼接参数,我要对占位符进行替换实在的值,我要在模板的根底上减少业务ID进而追踪数据 等等等。

说白了,TaskInfo是基于模板的,在模板的根底上增加了某些平台性的字段(businessId),解析出用户设置的模板而想要发送的实在内容等等。

在这里,值得要阐明的是msgContent该字段的阐明。在模板中,该字段我在数据库正文所下的定义是(这个字段存入数据库肯定是JSON格局的):

`msg_content`        varchar(600) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '音讯内容 占位符用{$var}示意',

不同的渠道的JSON构造还不一样:

  • 短信:{"content":"","url":""}
  • 邮件:{"content":"","subTitle":""}
  • Push:{"content":"","subTitle":"","phoneImgUrl":""}
  • 小程序:{"content":"","pagePath":"" .......}

第一反馈,我是想把所有渠道可能用到的字段都定义在TaskInfo下。起初感觉这样不太好看,于是我就定义了各种Model(不同的发送渠道领有着本人的内容模型)

于是,我在组装TaskInfo的时候利用反射来进行映射,替换占位符则借助的是PropertyPlaceholderHelper

而发送则很简略了,我是间接把TaskInfo序列化为JSON,而后读取的时候再反序列化就好了。

值得注意的是,因为TaskInfo用的是ContentModel来存储着内容模型,所以咱们在序列化JSON的时候须要把"类信息"写进去,不然在反序列的时候是拿不到子类的数据的。

03、总结

对于有源码的我的项目,其实我是不太违心每一步解说我写的代码的。因为我认为我自身写得也没那么简单,也没有炫技的成分在内。

但自从push了代码当前,在群里揭示各位跟着做我的项目的小伙伴后,有好几位向我反馈看不太懂,所以这篇我就独自拎进去讲讲。

再回过头看,其实在austin-api层接管到申请之后,在发送音讯至MQ之前,在这里的操作都是非常简单。其实是能够把通用业务做在这(比如说通用去重的性能),但经我思考之后,还是不太适合。

austin-api算是一个接入层,到目前为止它只是通过id去数据库读取配置,就没有耗时的操作(这意味着他能承载的并发是极大的)。假如通过ID去数据库读取未来存在瓶颈,咱们还能够思考将配置从Redis甚至本地内存里取。

这是由业务能够决定的:一个模板的变更往往并不多,即使缓存存在强一致性的问题,但就那点点工夫是齐全可承受的。

Question :为什么发个音讯须要MQ?

Answer:发送音讯实际上是调用各个服务提供的API,假如某音讯的服务超时,austin-api如果是间接调用服务,那存在超时危险,拖垮整个接口性能。MQ在这是为了做异步和解耦,并且在肯定水平上抗住业务流量。

Question:能简略说下接入层做了什么事吗?

Answer

关注我的微信公众号【Java3y】除了技术我还会聊点日常,有些话只能轻轻说~ 【对线面试官+从零编写Java我的项目】 继续高强度更新中!求star!!原创不易!!求三连!!

Gitee链接:https://gitee.com/austin

GitHub链接:https://github.com/austin