網站中接入手機驗證碼和定時任務(含原始碼)

2023-06-25 09:00:21

頁面預覽

繫結手機號

未繫結手機號

已係結手機號

第01章-簡訊傳送

1、雲市場-簡訊API

1.1、開通三網106簡訊

在阿里云云市場搜尋「簡訊」,一般都可用,選擇一個即可,例如如下:點選「立即購買」開通

這裡開通的是【簡訊驗證碼- 快速報備簽名】

1.2、獲取開發引數

登入雲市場控制檯,在已購買的服務中可以檢視到所有購買成功的API商品情況,下圖紅框中的就是AppKey/AppSecret,AppCode的資訊。

1.3、API方式使用雲市場服務

將工具類放入service-yun微服務的utils包中:資料:資料>簡訊傳送>工具類

參考如下例子,複製程式碼在test目錄進行測試

2、傳送短息

2.1、Controller

建立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("簡訊傳送成功");
    }
}

2.2、Service

介面: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(), "簡訊傳送失敗");
        }
    }
}

3、繫結手機號

3.1、Controller

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("繫結成功");
}

3.2、Service

介面: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);
}

4、前端整合

4.1、api

建立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`
    })
},

4.2、頁面元件

複製頁面到專案pages目錄中

資料:資料>手機號繫結頁面

第02章-引入MQ

預約或取消預約成功後我們要 更新預約數 以及 傳送簡訊提醒,為了實現使用者下單和取消訂單的快速響應,這部分邏輯我們就交給MQ完成。

1、引入RabbitMQ

1.1、安裝RabbitMQ

#拉取映象
docker pull rabbitmq:3.8-management
#建立容器啟動
docker run -d --restart=always -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:3.8-management

1.2、存取管理後臺

http://192.168.100.101:15672

登入:guest / guest

1.3、建立rabbit-utils模組

在common模組中建立rabbit-utils模組

1.4、引入依賴

在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>

1.5、建立RabbitService類

新增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;
    }
}

1.6、設定MQ訊息轉換器

package com.atguigu.syt.rabbit.config;

@Configuration
public class MQConfig {

    @Bean
    public MessageConverter messageConverter(){
        //設定json字串轉換器,預設使用SimpleMessageConverter
        return new Jackson2JsonMessageConverter();
    }
}

1.7、新增常數類

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";
}

2、service-yun中傳送簡訊

2.1、引入依賴

<!--MQ-->
<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>rabbit-utils</artifactId>
    <version>1.0</version>
</dependency>

2.2、新增MQ設定

spring:
  rabbitmq:
    host: 192.168.100.101
    port: 5672
    username: guest
    password: guest

2.3、封裝MQ監聽器

在監聽器中傳送簡訊:

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);
    }
}

3、service-hosp中更新排班數量

3.1、引入依賴

<!--MQ-->
<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>rabbit-utils</artifactId>
    <version>1.0</version>
</dependency>

3.2、新增MQ設定

spring:
  rabbitmq:
    host: 192.168.100.101
    port: 5672
    username: guest
    password: guest

3.3、封裝MQ監聽器

在監聽器中更新排班數量:

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);
}

4、完善service-orde訂單介面

4.1、引入依賴

<!--MQ-->
<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>rabbit-utils</artifactId>
    <version>1.0</version>
</dependency>

4.2、新增MQ設定

spring:
  rabbitmq:
    host: 192.168.100.101
    port: 5672
    username: guest
    password: guest

4.3、修改下單方法

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);

4.4、修改取消訂單方法

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);

5、就診人提醒

5.1、新增定時任務

在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 執行");
    }
}

5.2、新增就診人提醒定時任務

@Resource
private OrderInfoService orderInfoService;

@Scheduled(cron = "0 0 18 * * ?")
public void patientAdviceTask(){

    log.info("執行定時任務");
    orderInfoService.patientAdvice();
}

5.3、Service

需求:就診前一天晚六點向用戶傳送就醫提醒簡訊

介面: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