在阿里云云市场搜索“短信”,一般都可用,选择一个即可,例如如下:点击“立即购买”开通
这里开通的是【短信验证码- 快速报备签名】
登录云市场控制台,在已购买的服务中可以查看到所有购买成功的API商品情况,下图红框中的就是AppKey/AppSecret,AppCode的信息。
将工具类放入service-yun微服务的utils包中:资料:资料>短信发送>工具类
参考如下例子,复制代码在test目录进行测试
创建FrontSmsController
package com.atguigu.syt.yun.controller.front; @Api(tags = "短信接口") @RestController @RequestMapping("/front/yun/sms") public class FrontSmsController { @Resource private SmsService smsService; @Resource private RedisTemplate redisTemplate; @ApiOperation("发送短信") @ApiImplicitParam(name = "phone",value = "手机号") @PostMapping("/send/{phone}") public Result send(@PathVariable String phone) { //生成验证码 int minutes = 5; //验证码5分钟有效 String code = RandomUtil.getFourBitRandom(); //创建短信发送对象 SmsVo smsVo = new SmsVo(); smsVo.setPhone(phone); smsVo.setTemplateCode("CST_qozfh101"); Map<String,Object> paramsMap = new HashMap<String, Object>(){{ put("code", code); put("expire_at", 5); }}; smsVo.setParam(paramsMap); //发送短信 smsService.send(smsVo); //验证码存入redis redisTemplate.opsForValue().set("code:" + phone, code, minutes, TimeUnit.MINUTES); return Result.ok().message("短信发送成功"); } }
接口:SmsService
package com.atguigu.syt.yun.service; public interface SmsService { /** * 发送短信 * @param smsVo */ void send(SmsVo smsVo); }
实现:SmsServiceImpl
package com.atguigu.syt.yun.service.impl; @Service @Slf4j public class SmsServiceImpl implements SmsService { @Override public void send(SmsVo smsVo) { String host = "https://dfsns.market.alicloudapi.com"; String path = "/data/send_sms"; String method = "POST"; String appcode = "你的appcode"; Map<String, String> headers = new HashMap<>(); //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105 headers.put("Authorization", "APPCODE " + appcode); //根据API的要求,定义相对应的Content-Type headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); Map<String, String> querys = new HashMap<>(); Map<String, String> bodys = new HashMap<>(); StringBuffer contentBuffer = new StringBuffer(); smsVo.getParam().entrySet().forEach( item -> { contentBuffer.append(item.getKey()).append(":").append(item.getValue()).append(","); }); String content = contentBuffer.substring(0, contentBuffer.length() - 1); bodys.put("content", content); bodys.put("phone_number", smsVo.getPhone()); bodys.put("template_id", smsVo.getTemplateCode()); try { /** * 重要提示如下: * HttpUtils请从 * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java * 下载 * * 相应的依赖请参照 * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml */ HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys); //获取response的body String data = EntityUtils.toString(response.getEntity()); HashMap<String, String> resultMap = JSONObject.parseObject(data, HashMap.class); String status = resultMap.get("status"); if(!"OK".equals(status)){ String reason = resultMap.get("reason"); log.error("短信发送失败:status = " + status + ", reason = " + reason); throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "短信发送失败"); } } catch (Exception e) { log.error(ExceptionUtils.getStackTrace(e)); throw new GuiguException(ResultCodeEnum.FAIL.getCode(), "短信发送失败"); } } }
service-user微服务FrontUserInfoController中添加接口方法
@ApiOperation("绑定手机号") @ApiImplicitParams({ @ApiImplicitParam(name = "phone",value = "手机号", required = true), @ApiImplicitParam(name = "code",value = "验证码", required = true)}) @PostMapping("/auth/bindPhone/{phone}/{code}") public Result bindPhone(@PathVariable String phone, @PathVariable String code, HttpServletRequest request, HttpServletResponse response) { Long userId = authContextHolder.checkAuth(request, resposne); userInfoService.bindPhone(userId, phone, code); return Result.ok().message("绑定成功"); }
接口:UserInfoService
/** * 绑定当前用户的手机号 * @param userId * @param phone * @param code */ void bindPhone(Long userId, String phone, String code);
实现:UserInfoServiceImpl
@Resource private RedisTemplate redisTemplate; @Override public void bindPhone(Long userId, String phone, String code) { //校验参数 if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) { throw new GuiguException(ResultCodeEnum.PARAM_ERROR); } //校验验证码 String phoneCode = (String)redisTemplate.opsForValue().get("code:" + phone); if(!code.equals(phoneCode)) { throw new GuiguException(ResultCodeEnum.CODE_ERROR); } //根据手机号查找会员 LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>(); //手机号没有被其他人绑定过 queryWrapper.eq(UserInfo::getPhone, phone).ne(UserInfo::getId, userId); UserInfo userInfo = baseMapper.selectOne(queryWrapper); //手机号已存在 if(userInfo != null) { throw new GuiguException(ResultCodeEnum.REGISTER_MOBILE_ERROR); } //设置绑定手机号 userInfo = new UserInfo(); userInfo.setId(userId); userInfo.setPhone(phone); baseMapper.updateById(userInfo); }
创建sms.js
import request from '~/utils/request' export default { sendCode(phone) { return request({ url: `/front/yun/sms/send/${phone}`, method: `post` }) } }
在userInfo.js中添加方法
bindPhone(phone, code) { return request({ url: `/front/user/userInfo/auth/bindPhone/${phone}/${code}`, method: `post` }) },
复制页面到项目pages目录中
资料:资料>手机号绑定页面
预约或取消预约成功后我们要 更新预约数 以及 发送短信提醒,为了实现用户下单和取消订单的快速响应,这部分逻辑我们就交给MQ完成。
#拉取镜像 docker pull rabbitmq:3.8-management #创建容器启动 docker run -d --restart=always -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:3.8-management
http://192.168.100.101:15672
登录:guest / guest
在common模块中创建rabbit-utils模块
在rabbit-utils中引入依赖
<dependencies> <!--rabbitmq消息队列--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
添加sendMessage方法
package com.atguigu.syt.rabbit; @Service @Slf4j public class RabbitService { @Resource private RabbitTemplate rabbitTemplate; /** * 发送消息 * @param exchange 交换机 * @param routingKey 路由 * @param message 消息 */ public boolean sendMessage(String exchange, String routingKey, Object message) { log.info("发送消息..........."); rabbitTemplate.convertAndSend(exchange, routingKey, message); return true; } }
package com.atguigu.syt.rabbit.config; @Configuration public class MQConfig { @Bean public MessageConverter messageConverter(){ //配置json字符串转换器,默认使用SimpleMessageConverter return new Jackson2JsonMessageConverter(); } }
package com.atguigu.syt.rabbit.config; public class MQConst { /** * 预约/取消订单 */ public static final String EXCHANGE_DIRECT_ORDER = "exchange.direct.order"; public static final String ROUTING_ORDER = "routing.order"; public static final String QUEUE_ORDER = "queue.order"; /** * 短信 */ public static final String EXCHANGE_DIRECT_SMS = "exchange.direct.sms"; public static final String ROUTING_SMS = "routing.sms"; public static final String QUEUE_SMS = "queue.sms"; }
<!--MQ--> <dependency> <groupId>com.atguigu</groupId> <artifactId>rabbit-utils</artifactId> <version>1.0</version> </dependency>
spring: rabbitmq: host: 192.168.100.101 port: 5672 username: guest password: guest
在监听器中发送短信:
package com.atguigu.syt.yun.receiver; @Component @Slf4j public class SmsReceiver { @Resource private SmsService smsService; /** * 监听MQ中的消息 * @param smsVo */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = MQConst.QUEUE_SMS, durable = "true"), //消息队列,并持久化 exchange = @Exchange(value = MQConst.EXCHANGE_DIRECT_SMS), //交换机 key = {MQConst.ROUTING_SMS} //路由 )) public void receive(SmsVo smsVo){ log.info("SmsReceiver 监听器监听到消息......"); smsService.send(smsVo); } }
<!--MQ--> <dependency> <groupId>com.atguigu</groupId> <artifactId>rabbit-utils</artifactId> <version>1.0</version> </dependency>
spring: rabbitmq: host: 192.168.100.101 port: 5672 username: guest password: guest
在监听器中更新排班数量:
package com.atguigu.syt.hosp.receiver; @Component @Slf4j public class HospReceiver { @Resource private ScheduleService scheduleService; /** * 监听MQ中的消息 * @param orderMqVo */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = MQConst.QUEUE_ORDER, durable = "true"), //消息队列,并持久化 exchange = @Exchange(value = MQConst.EXCHANGE_DIRECT_ORDER), //交换机 key = {MQConst.ROUTING_ORDER} //路由 )) public void receive(OrderMqVo orderMqVo){ //修改排班信息 log.info("HospReceiver 监听器监听到消息......"); scheduleService.updateByOrderMqVo(orderMqVo); } }
接口:ScheduleService
/** * 更新排班数量 * @param orderMqVo */ void updateByOrderMqVo(OrderMqVo orderMqVo);
实现:ScheduleServiceImpl
@Override public void updateByOrderMqVo(OrderMqVo orderMqVo) { //下单成功更新预约数 ObjectId objectId = new ObjectId(orderMqVo.getScheduleId()); //id是objectId Schedule schedule = scheduleRepository.findById(objectId).get(); //id是string // Schedule schedule = scheduleRepository.findById(orderMqVo.getScheduleId()).get(); schedule.setReservedNumber(orderMqVo.getReservedNumber()); schedule.setAvailableNumber(orderMqVo.getAvailableNumber()); //主键一致就是更新 scheduleRepository.save(schedule); }
<!--MQ--> <dependency> <groupId>com.atguigu</groupId> <artifactId>rabbit-utils</artifactId> <version>1.0</version> </dependency>
spring: rabbitmq: host: 192.168.100.101 port: 5672 username: guest password: guest
OrderInfoServiceImpl类:
@Resource private RabbitService rabbitService;
submitOrder方法中添加发送消息的业务逻辑:
//使用这两个数据更新平台端的最新的排班数量 Integer reservedNumber = data.getInteger("reservedNumber"); Integer availableNumber = data.getInteger("availableNumber"); //目的1:更新mongodb数据库中的排班数量 //组装数据同步消息对象 OrderMqVo orderMqVo = new OrderMqVo(); orderMqVo.setAvailableNumber(availableNumber); orderMqVo.setReservedNumber(reservedNumber); orderMqVo.setScheduleId(scheduleId); //发消息 rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_ORDER, MQConst.ROUTING_ORDER, orderMqVo); //目的2:给就诊人发短信 //组装短信消息对象 SmsVo smsVo = new SmsVo(); smsVo.setPhone(orderInfo.getPatientPhone()); //亲爱的用户:您已预约%{hosname}%{hosdepname}%{date}就诊 //请您于%{time}至%{address}取号, //您的就诊号码是%{number},请您及时就诊 smsVo.setTemplateCode("和客服申请模板编号"); Map<String,Object> paramsSms = new HashMap<String, Object>(){{ put("hosname", orderInfo.getHosname()); put("hosdepname", orderInfo.getDepname()); put("date", new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")); put("time", orderInfo.getFetchTime()); put("address", orderInfo.getFetchAddress()); put("number", orderInfo.getNumber()); }}; smsVo.setParam(paramsSms); //向MQ发消息 rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_SMS, MQConst.ROUTING_SMS, smsVo);
cancelOrder方法中添加发送消息的业务逻辑:
//获取返回数据 JSONObject jsonObject = jsonResult.getJSONObject("data"); Integer reservedNumber = jsonObject.getInteger("reservedNumber"); Integer availableNumber = jsonObject.getInteger("availableNumber"); //目的1:更新mongodb数据库中的排班数量 //组装数据同步消息对象 OrderMqVo orderMqVo = new OrderMqVo(); orderMqVo.setAvailableNumber(availableNumber); orderMqVo.setReservedNumber(reservedNumber); orderMqVo.setScheduleId(orderInfo.getScheduleId()); //发消息 rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_ORDER, MQConst.ROUTING_ORDER, orderMqVo); //目的2:给就诊人发短信 //组装短信消息对象 SmsVo smsVo = new SmsVo(); smsVo.setPhone(orderInfo.getPatientPhone()); //亲爱的用户:您已取消%{hosname}%{hosdepname}%{date}就诊 smsVo.setTemplateCode("和客服申请模板编号"); Map<String,Object> paramsSms = new HashMap<String, Object>(){{ put("hosname", orderInfo.getHosname()); put("hosdepname", orderInfo.getDepname()); put("date", new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")); }}; smsVo.setParam(paramsSms); //向MQ发消息 rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_SMS, MQConst.ROUTING_SMS, smsVo);
在service-order微服务中添加定时任务:创建ScheduledTask类
cron表达式参考:https://qqe2.com/cron
package com.atguigu.syt.order.schedule; @Component @EnableScheduling //开启定时任务 @Slf4j public class ScheduledTask { /** * 测试 * (cron="秒 分 时 日 月 周") * *:每隔一秒执行 * 0/3:从第0秒开始,每隔3秒执行一次 * 1-3: 从第1秒开始执行,到第3秒结束执行 * 1,2,3:第1、2、3秒执行 * ?:不指定,若指定日期,则不指定周,反之同理 */ @Scheduled(cron="0/3 * * * * ?") public void task1() { log.info("task1 执行"); } }
@Resource private OrderInfoService orderInfoService; @Scheduled(cron = "0 0 18 * * ?") public void patientAdviceTask(){ log.info("执行定时任务"); orderInfoService.patientAdvice(); }
需求:就诊前一天晚六点向用户发送就医提醒短信
接口:OrderInfoService
/** * 就诊人提醒 */ void patientAdvice();
实现:OrderInfoServiceImpl
@Override public void patientAdvice() { //查询明天的预约信息 LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>(); //明天 String tomorrow = new DateTime().plusDays(1).toString("yyyy-MM-dd"); queryWrapper.eq(OrderInfo::getReserveDate, tomorrow); //未取消 queryWrapper.ne(OrderInfo::getOrderStatus, OrderStatusEnum.CANCLE.getStatus()); List<OrderInfo> orderInfoList = baseMapper.selectList(queryWrapper); for (OrderInfo orderInfo : orderInfoList) { //短信对象 SmsVo smsVo = new SmsVo(); smsVo.setPhone(orderInfo.getPatientPhone()); //就诊提醒:您已预约%{hosname}%{depname}的号源,就诊时间:%{date},就诊人%{name},请您合理安排出行时间 smsVo.setTemplateCode("和客服申请模板编号"); Map<String,Object> paramsSms = new HashMap<String, Object>(){{ put("hosname", orderInfo.getHosname()); put("hosdepname", orderInfo.getDepname()); put("date", new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")); put("name", orderInfo.getPatientName()); }}; smsVo.setParam(paramsSms); //发消息 rabbitService.sendMessage(MQConst.EXCHANGE_DIRECT_SMS, MQConst.ROUTING_SMS, smsVo); } }
源码:https://gitee.com/dengyaojava/guigu-syt-parent