基于SpringBoot整合RocketMQ异步发送短信功能
文章目录
前言
上一篇文章记录了 RocketMQ 整体架构、安装部署、应用场景这三个内容。熟悉了 RocketMQ 相关核心概念后,本文记录基于 SpringBoot 整合 RocketMQ 异步发送短信功能,其中会引入阿里云短信服务相关内容。
一、整合RocketMQ
1.1 引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
1.2 编写配置
rocketmq: # RocketMQ配置
name-server: 192.168.57.129:9876
producer:
group: test-group
1.3 需求描述
平台系统有一键通知按钮,一键通知功能描述:用于发送短信给家长,引导家长去微信公众号关注他们孩子的视力,屈光,眼轴等信息。并且问卷倒计时内完成问卷信息的填写,可解锁未来预测功能,预测视力未来的发展情况。
查看查询当前任务中已经完成筛查的记录,若筛查项目全部无数据就需要过滤掉,并且需要创建学生对应的任务的问卷日期,用来记录问卷倒计时。以及家长需要在微信公众号绑定该学生后才能收到短信通知,没绑定则收不到短信通知。
/**
* 一键通知
*
* @param taskId
* @return
*/
@Login
@RequestMapping("/notice")
public Result notice(@RequestParam(value = "taskId") Integer taskId) {
ScreeningRecordQueryCondition queryCondition = new ScreeningRecordQueryCondition();
queryCondition.setTaskId(taskId);
queryCondition.setIsDelete(YNEnum.N);
queryCondition.setStatus(ScreeningStatus.DONE.getCode());
List<TScreeningRecord> screeningRecords = screeningRecordService.findByCondition(queryCondition);
// 过滤空数据->生成问卷倒计时->发送短信通知
taskService.filter(screeningRecords);
return Result.success();
}
/**
* 过滤空数据->发送通知->生成问卷日期
* @param screeningRecords
*/
public void filter(List<TScreeningRecord> screeningRecords) {
if (CollectionUtils.isEmpty(screeningRecords)) {
return ;
}
List<TScreeningRecord> records = screeningRecords.stream().
// 对于各个筛查项均无数据的学生,即使任务结束此处档案不显示,也不给家长发送通知
filter(tScreeningRecord -> StringUtils.isNotEmpty(tScreeningRecord.getVision()) ||
StringUtils.isNotEmpty(tScreeningRecord.getRefraction()) ||
StringUtils.isNotEmpty(tScreeningRecord.getAxis())
).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(records)) {
// 循环通知
records.forEach(screeningRecord -> screeningRecordHelper.notice(screeningRecord));
}
}
@Slf4j
@Service
public class ScreeningRecordHelper {
@Autowired
ParentRelationService parentRelationService;
@Autowired
ParentService parentService;
@Autowired
StudentService studentService;
@Autowired
GenerateQuestionnaireDateService GenerateQuestionnaireDateService;
@Autowired
SmsService smsService;
@Autowired
ScreeningRecordService screeningRecordService;
@Autowired
MQProducer mqProducer;
/**
* 将筛查记录发送通知给学生家长
*
* @param screeningRecord 筛查记录
*/
public void notice(TScreeningRecord screeningRecord) {
CompletableFuture.runAsync(() -> {
Integer studentId = screeningRecord.getStudentId();
Integer taskId = screeningRecord.getTaskId();
GenerateQuestionnaireDateQueryCondition queryCondition = new GenerateQuestionnaireDateQueryCondition();
queryCondition.setStudentId(studentId);
queryCondition.setTaskId(taskId);
List<TGenerateQuestionnaireDate> generateQuestionnaireDates = GenerateQuestionnaireDateService.findByCondition(queryCondition);
if (CollectionUtils.isEmpty(generateQuestionnaireDates)) {
// 创建生成问卷时间记录
GenerateQuestionnaireDateService.create(taskId, studentId, new Date());
}
// 查找学生对应的家长
List<TParentRelation> parentRelations = parentRelationService.findByBizTypeBizId(ParentRelationBizType.STUDENT.getCode(), String.valueOf(studentId));
if (CollectionUtils.isEmpty(parentRelations)) {
return;
}
TParentRelation relation = parentRelations.get(0);
TParent parent = parentService.findById(relation.getParentId());
// 获取学生信息
TStudent student = studentService.findById(studentId);
// 发送筛查记录通知模板消息
if (student != null) {
// 发送异步消息
ScreeningRecordSendStatusChangeMsg msg = new ScreeningRecordSendStatusChangeMsg();
msg.setRecordId(screeningRecord.getId());
msg.setPhone(parent.getPhone());
msg.setStudentName(student.getName());
msg.setOldSendStatus(screeningRecord.getSendStatus().intValue());
msg.setNewSendStatus(YNEnum.Y.getCode());
mqProducer.sendAsync(Topic.SCREENING_RECORD_SEND_STATUS_CHANGE.getTopic(), Topic.SCREENING_RECORD_SEND_STATUS_CHANGE.getTag(), JSON.toJSONString(msg));
}
});
}
}
1.4 Producer 代码
@Service
public class MQProducer {
private static final Logger log = LoggerFactory.getLogger(MQProducer.class);
/**
* 默认发送超时时间, 3000毫秒
*/
private static final int DEFAULT_TIMEOUT = 3000;
@Autowired
RocketMQTemplate rocketMQTemplate;
/**
* 发送异步消息(通过线程池执行发送到broker的消息任务, 执行完后回调)
*
* @param topic 话题
* @param tag
* @param msgBody 消息内容
*/
public void sendAsync(String topic, String tag, String msgBody) {
String destination = tag != null && !tag.isEmpty() ? topic + ":" + tag : topic;
rocketMQTemplate.asyncSend(destination, msgBody, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("sendAsync success, topic:{}, tag:{}, msg:{}", topic, tag, msgBody);
}
@Override
public void onException(Throwable throwable) {
log.error("sendAsync fail, topic:{}, tag:{}, msg:{}", topic, tag, msgBody, throwable);
}
});
}
}
1.5 Consumer 代码
@Component
@RocketMQMessageListener(topic = "screening-record-topic", consumerGroup = "screening-record-consumer_send-status-change", selectorExpression = "send-status-change")
public class ScreeningRecordSendStatusChangeConsumer implements RocketMQListener<ScreeningRecordSendStatusChangeMsg> {
private static final Logger log = LoggerFactory.getLogger(ScreeningRecordSendStatusChangeConsumer.class);
@Autowired
ScreeningRecordService screeningRecordService;
@Autowired
SmsService smsService;
@Override
public void onMessage(ScreeningRecordSendStatusChangeMsg message) {
log.info("接收到MQ消息, topic:screening-record-topic, tag:send-status-change, message:{}", message);
TScreeningRecord screeningRecord = screeningRecordService.findById(message.getRecordId());
if (screeningRecord != null && screeningRecord.getIsDelete().equals(YNEnum.N.getCode().byteValue())) {
smsService.sendNoticeMsg(message.getPhone(), message.getStudentName(), message.getStudentName());
screeningRecordService.updateSendStatus(YNEnum.Y.getCode(), message.getRecordId());
}
log.info("处理完成MQ消息, topic:screening-record-topic, tag:send-status-change, message:{}", message);
}
}
二、阿里云短信服务
2.1 申请短信模板
2.2 短信配置
sms:
base-url: ${sms.baseurl}
uri: /sms-api/sms/send
token-uri: /sms-api/authen/token/create?userName={userName}
user: ******
sign: ***
verification:
template: SMS_464********
param: '{"args0":"%s","args1":"%s"}'
2.3 短信业务层代码
@Service
public class SmsService {
private static final Logger log = LoggerFactory.getLogger(SmsService.class);
WebClient webClient;
@Autowired
SmsProperties smsProperties;
@PostConstruct
public void init() {
webClient = WebClient.create(smsProperties.getBaseUrl());
}
/**
* 一键通知
*
* @param phone 手机号
* @param name 学生姓名
* @param student 学生姓名
*/
public void sendNoticeMsg(String phone, String name, String student) {
String param = String.format(smsProperties.getNotice().getParam(), name, student);
send(phone, smsProperties.getNotice().getTemplate(), param, smsProperties.getSign());
}
/**
* 发送短信
*
* @param mobile 手机号码:国内短信:+/+86/0086/86或无任何前缀的11位手机号码;国际/港澳台消息:国际区号+号码
* @param smsTemplate 短信模板
* @param smsParm 短信参数
* @param signName 签名
*/
private void send(String mobile, String smsTemplate, String smsParm, String signName) {
// 请求体
SmsRequestDTO smsRequestDTO = new SmsRequestDTO();
smsRequestDTO.setMobile(mobile);
smsRequestDTO.setSmsTemplete(smsTemplate);
smsRequestDTO.setSmsParm(smsParm);
smsRequestDTO.setSignName(signName);
Mono<SmsResponseDTO> mono = webClient
.post()
.uri(smsProperties.getUri())
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, getSmsToken(smsProperties.getUser()))
.bodyValue(smsRequestDTO)
.retrieve()
.bodyToMono(SmsResponseDTO.class);
mono.subscribe(result -> log.info("发送短信完成, smsRequest:{}, smsResponse:{}", smsRequestDTO, result));
}
}
相关文章:

spring.factories文件的作用
即spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器中。在Spring Boot启动时,它会扫描classpath下所有的spring.factories文件,加载其中的自动配置类,并将它们注入到Spring ApplicationContext中,使得项目能够自动运行。spring.factories文件是Spring Boot自动配置的核心文件之一,它的作用是。

7min到40s:SpringBoot 启动优化实践
然后重点排查这些阶段的代码。先看下。

SpringBoot系列教程之Bean之指定初始化顺序的若干姿势
之前介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢?本文将介绍几种可行的方式来控制 bean 之间的加载顺序。

SpringBoot接口防抖(防重复提交)的一些实现方案
作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。啥是防抖所谓防抖,一是防用户手抖,二是防网络抖动。

SpringBoot请求转发与重定向
但是可能由于B网址相对于A网址过于复杂,这样搜索引擎就会觉得网址A对用户更加友好,因而在重定向之后任然显示旧的网址A,但是显示网址B的内容。在平常使用手机的过程当中,有时候会发现网页上会有浮动的窗口,或者访问的页面不是正常的页面,这就可能是运营商通过某种方式篡改了用户正常访问的页面。重定向,是指在Nginx中,重定向是指通过修改URL地址,将客户端的请求重定向到另一个URL地址的过程,Nginx中实现重定向的方式有多种,比如使用rewrite模块、return指令等。使用场景:在返回视图的前面加上。

SpringBoot 中实现订单30分钟自动取消的策略
在电商和其他涉及到在线支付的应用中,通常需要实现一个功能:如果用户在生成订单后的一定时间内未完成支付,系统将自动取消该订单。本文将详细介绍基于Spring Boot框架实现订单30分钟内未支付自动取消的几种方案,并提供实例代码。

SpringBoot 优雅实现超大文件上传,通用方案
通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了。分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。

Springboot + oauth2 单点登录 - 原理篇
OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。授权码模式(authorization code)密码模式(resource owner password credentials)客户端模式(client credentials) 不常用。

Springboot整合HBase——大数据技术之HBase2.x
Apache HBase 是以hdfs为数据存储的,一种分布式、可扩展的noSql数据库。是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase使用与BigTable(BigTable是一个稀疏的、分布式的、持久化的多维排序map)非常相似的数据模型。用户将数据行存储在带标签的表中。数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列。

Docker部署SpringBoot项目详细部署过程
Docker可比喻成一个装应用的容器,将应用及其依赖文件、数据等打包在容器内,直接运行容器即可把应用运行起来,而无需关心环境配置问题。 本文记录个人学习Docker的总结内容,安装、配置和部署等内容,在过程中,应注意命令不要写错,加上Docker插件等问题,若出现理解不到位的地方,请多指出。

spring和springboot、springMVC有什么区别?
今天来聊一下,刚在面试中被问到的一个经典问题Spring 提供了广泛的功能用于企业级应用开发Spring Boot 简化了 Spring 应用的开发和部署Spring MVC 则是专注于构建 Web 应用的 MVC 框架在使用时,你可以根据项目需求选择合适的组件或组合使用它们。在很多现代的 Spring 应用中,特别是微服务架构中,Spring Boot 和 Spring MVC 经常一起使用。好了,以上就是本文的全部内容,如有问题欢迎留言讨论。

Spring Boot整合日期转换器(Converter)和拦截器(HandlerInterceptor)
配置文件形式针对框架进行个性化定制,例如:拦截器,类型转化器等等。WebMvcConfigurer配置类其实是。内部的一种配置方式,采用。
SpringBoot 使用过滤器、拦截器、切面(AOP),及其之间的区别和执行顺序
Servlet(Server Applet),全称是Java Servlet,是提供基于协议请求/响应服务的Java类。在JavaEE中是Servlet规范,即是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的Java类,一般人们理解是后者。是什么。

【总结】SpringBoot 中过滤器、拦截器、监听器的基本使用
拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法。是基于JAVA的反射机制。1)预处理preHandle()方法用户发送请求时,先执行preHandle()方法。会先按照顺序执行所有拦截器的preHandle方法,一直遇到return false为止,比如第二个preHandle方法是return false,则第三个以及以后所有拦截器都不会执行。若都是return true,则执行用户请求的url方法。2)后处理postHandle()方法。

SpringBoot--过滤器/拦截器/AOP--区别/使用/顺序
本文介绍SpringMVC(SpringBoot)中的过滤器、拦截器、AOP的区别及其用法。 如果监听器、过滤器、 拦截器、 AOP都存在,则它们的执行顺序为:监听器 => 过滤器=> 拦截器=> AOP。

Spring boot 3 集成rocketmq-spring-boot-starter解决版本不一致问题
根据上篇文章使用Docker安装RocketMQ并启动之后,有个隐患详情见下文。如何解决rocketmq 和spring boot 3.x集成问题

RocketMQ5-03RocketMQ-Dashboard和Java客户端访问示例
接上篇已经完成 RocketMQ5.0 环境的部署,就需要对这个环境进行测试,查看集群、写入消息、读取消息等。Docker部署 Dashboard:获取镜像并下载,部署服务。客户端连接:pom文件,生产者代码,消费者代码,接口测试,问题: broker资源不足无法提供服务

基于SpringBoot的校园二手闲置交易平台
基于SpringBoot的校园二手闲置交易平台的设计与实现~

SpringBoot 中获取 Request 的四种方法
Controller中获取request对象后,如果要在其他方法中(如service方法、工具类方法等)使用request对象,需要在调用这些方法时将request对象作为参数传入。如果其他方法(如工具类中static方法)需要使用request对象,则需要在调用这些方法时将request参数传递进去。下面介绍的方法4,则可以直接在诸如工具类中的static方法中使用request对象(当然在各种Bean中也可以使用)。该方法实现的原理是,在Controller方法开始处理请求时,对象是方法参数,相当于。

springboot对接WebSocket实现消息推送
项目上线需要https请求,把请求地址换成wss,需要通过nginx配置转发,再443端口加上如下配置。请求地址更换成wss://域名/wss/websocket/或者播放指定的音频文件,播放音频需要浏览器设置可以发放声音。2.增加配置WebSocketConfig.java。7.增加消息推送语音播放_文本转语音。3.创建一个WebSocket实例。6.前端对接WebSocket。4.修改启动类,添加注解。8.如何修改成wss请求。5.测试一下推送消息。

利用systemd设置springboot微服务服务在linux重启后自启动
要使 Spring Boot 服务的 JAR 包在 Linux 重启后自启动,您可以使用systemd。

【Spring boot】RedisTemplate中String、Hash、List设置过期时间
putIfAbsent 指的是如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null。如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null。下面这两句话,可以实现向Redis插入Hash数据,并且设置整个Hash的过期时间。TimeUnit.MILLISECONDS:毫秒。TimeUnit.MILLISECONDS:微秒。TimeUnit.MINUTES:分。

SpringBoot进行自然语言处理,利用Hanlp进行文本情感分析
自然语言处理,或简称NLP,是处理和转换文本的计算机科学学科。它由几个任务组成,这些任务从标记化开始,将文本分成单独的意义单位,应用句法和语义分析来生成抽象的知识表示,然后再次将该表示转换为文本,用于翻译、问答或对话等目的。

Spring-Boot---项目创建和使用
Spring的诞生是为了简化Java程序开发的;而Spring-Boot的诞生是为了简化Spring程序开发的。快速集成框架:Spring-Boot提供了启动添加依赖的功能,用于秒级集成各种框架内置运行容器:内置了Tomcat等Web容器,无需配置可以直接运行和部署快速部署项目:更方便的把项目部署到云服务器上完全抛弃繁琐的XML:使用注解和配置的方式进行开发支持更多的监控指标:可以更好的了解项目的运行情况我们自己要创建的类要放在和启动类的同级目录下,如果不在同级运行时会报错。

微信小程序完整实现微信支付功能(SpringBoot和小程序)
然后到提供前端调用支付路由的类,WechatController类,注意我这里路由拼接的有/wechat/pay/notify,这个要和之前配置yml文件的支付回调函数一样,要不然不行。不久前给公司实现支付功能,折腾了一阵子,终于实现了,微信支付对于小白来说真的很困难,特别是没有接触过企业级别开发的大学生更不用说,因此尝试写一篇我如何从小白实现微信小程序支付功能的吧,使用的后端是SpringBoot。效果如下,这里因为我的手机不能截图支付页面,所以用的开发者工具支付的效果,都是一样的。4.前端(小程序端)

SpringBoot整合Kafka (二)
Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景,比如基于hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源 项目。

SpringBoot整合Kafka (一)
Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景,比如基于hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源 项目。

MyBatis-Plus 实战教程四 idea插件
文章浏览阅读269次。在未引入分页插件的情况下,MybatisPlus是不支持分页功能的,IService和BaseMapper中的分页方法都无法正常起效。其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。在刚才的代码中,从PageQuery到MybatisPlus的Page之间转换的过程还是比较麻烦的。这里用到了分页参数,Page,即可以支持分页参数,也可以支持排序参数。在查询出分页结果后,数据的非空校验,数据的vo转换都是模板代码,编写起来很麻烦。