健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))

一、体检预约流程

用户可以通过如下操作流程进行体检预约:
1、在移动端首页点击体检预约,页面跳转到套餐列表页面
2、在套餐列表页面点击要预约的套餐,页面跳转到套餐详情页面
3、在套餐详情页面点击立即预约,页面跳转到预约页面
4、在预约页面录入体检人信息,包括手机号,点击发送验证码
5、在预约页面录入收到的手机短信验证码,点击提交预约,完成体检预约

1.手机号校验
2.发送验证码之后30秒倒计时效果
3.调用ValidateCodeUtils生成四位数字验证码
4.调用SMSUtils向指定手机发送验证码
二、体检预约
1.在移动端的orderInfo.html根据套餐id获取套餐基本信息

1.1 前端执行流程
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第1张图片
1.2 后端执行逻辑:后端就是根据接收的id从数据库中查找出对应的套餐信息返回给前端

2.手机号校验

2.1 第一步:在页面导入的healthmobile.js文件中已经定义了校验手机号的方法

/**
 * 手机号校验
 1--以1为开头;
 2--第二位可为3,4,5,7,8,中的任意一位;
 3--最后以0-9的9个整数结尾。
 */
function checkTelephone(telephone) {
    var reg=/^[1][3,4,5,7,8][0-9]{9}$/;
    if (!reg.test(telephone)) {
        return false;
    } else {
        return true;
    }
}

2.2 第二步:为发送验证码按钮绑定事件sendValidateCode
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第2张图片

3.发送验证码之后30秒倒计时效果

3.1 前面在sendValidateCode方法中进行了手机号校验,如果校验通过,需要显示30秒倒计时效果
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第3张图片

4.发送ajax请求

健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第4张图片

5.后台请求之后调用ValidateCodeUtils生成四位数字验证码,调用SMSUtils向指定手机发送验证码
package com.oracle.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {
    public static final String VALIDATE_CODE = "SMS_193233375";//发送短信验证码
    public static final String ORDER_NOTICE = "SMS_193515443";//体检预约成功通知

    /**
     * 发送短信
     * @param phoneNumbers
     * @param param
     * @throws ClientException
     */
    public static void sendShortMessage(String templateCode,String phoneNumbers,String param) throws ClientException {
        // 设置超时时间-可自行调整
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        // 初始化ascClient需要的几个参数
        final String product = "Dysmsapi";// 短信API产品名称(短信产品名固定,无需修改)
        final String domain = "dysmsapi.aliyuncs.com";// 短信API产品域名(接口地址固定,无需修改)
        // 替换成你的AK
        final String accessKeyId = "LTAI5GEC5ULqFHxSsxXUPS4P";// 你的accessKeyId,参考本文档步骤2
        final String accessKeySecret = "QxCqQM6TXXyjv8ZbIEC4NgCDkMPdID";// 你的accessKeySecret,参考本文档步骤2
        // 初始化ascClient,暂时不支持多region(请勿修改)
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        // 组装请求对象
        SendSmsRequest request = new SendSmsRequest();
        // 使用post提交
        request.setMethod(MethodType.POST);
        // 必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
        request.setPhoneNumbers(phoneNumbers);
        // 必填:短信签名-可在短信控制台中找到
        request.setSignName("FlyHigh健康管理云");
        // 必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(templateCode);
        // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
        // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
        request.setTemplateParam("{\"code\":\""+param+"\"}");
        // 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
        // request.setSmsUpExtendCode("90997");
        // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        // request.setOutId("yourOutId");
        // 请求失败这里会抛ClientException异常
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
            // 请求成功
            System.out.println("请求成功");
        }
    }
}
package com.oracle.utils;

import java.util.Random;

/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}
package com.oracle.controller;

import com.aliyuncs.exceptions.ClientException;
import com.oracle.constant.MessageConstant;
import com.oracle.constant.RedisConstant;
import com.oracle.entity.Result;
import com.oracle.utils.SMSUtils;
import com.oracle.utils.ValidateCodeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.JedisPool;

@RestController
@RequestMapping("/validateCode")
public class ValidateCodeController {

    @Autowired
    JedisPool jedisPool;

    /**
     * 提供方法发送短信验证码,并将验证码保存到redis中
     */
    @RequestMapping("/send4ValidateCode")
    public Result getAllSetmeal(String telephone){
        //生成四位数的验证码
        Integer validateCode = ValidateCodeUtils.generateValidateCode(4);
        //发送手机验证码至指定的手机
        try {
            SMSUtils.sendShortMessage(SMSUtils.VALIDATE_CODE,telephone,validateCode.toString());
        } catch (ClientException e) {
            e.printStackTrace();
            //发送验证码失败
            return new Result(false,MessageConstant.SEND_VALIDATECODE_FAIL);
        }
        //在控制台打印验证码
        System.out.println(validateCode);
        //将手机验证码保存至redis中,5分钟后过期
        jedisPool.getResource().setex(telephone+ RedisConstant.SENDTYPE_ORDER,5*60,validateCode.toString());
        return new Result(true,MessageConstant.SEND_VALIDATECODE_SUCCESS);
    }
}
6.日历展示

6.1 页面中使用DatePicker控件来展示日历。根据需求,最多可以提前一个月进行体检预约,所以日历控件只展示未来一个月的日期。
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第5张图片

7.提交预约请求

7.1 前台提交预约
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第6张图片
7.2 后台接收请求之后的处理过程

package com.oracle.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.aliyuncs.exceptions.ClientException;
import com.oracle.constant.MessageConstant;
import com.oracle.constant.RedisConstant;
import com.oracle.constant.RedisMessageConstant;
import com.oracle.entity.Result;
import com.oracle.pojo.Order;
import com.oracle.service.OrderService;
import com.oracle.utils.SMSUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.JedisPool;

import java.util.Map;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Reference
    OrderService orderService;

    @Autowired
    JedisPool jedisPool;

    /**
     * 由于实体类中没有一个叫OrderInfo的实体类,所以采用map接收数据
     * 提交用户的预约信息
     * @return Result
     */
    @RequestMapping("/submit")
    public Result submitOrder(@RequestBody Map map){
        //获取用户的手机号码
        String telephone = (String) map.get("telephone");
        //从redis里面取出发送的验证码
        String codeInRedis  = jedisPool.getResource().get(telephone + RedisMessageConstant.SENDTYPE_ORDER);
        //获取用户输入的验证码
        String validateCode = (String)map.get("validateCode");
        //校验用户输入的验证码是否等于redis中保存的验证码
        if(codeInRedis==null||!codeInRedis.equalsIgnoreCase(validateCode)){
            //如果redis中的验证码过期了,如果用户输入的验证码和redis中的验证码不一致
            return new Result(false, MessageConstant.VALIDATECODE_ERROR);
        }
        //如果验证码输入正确,则调用体检预约的服务
        Result result=null;
        try {
            map.put("orderType", Order.ORDERTYPE_WEIXIN);
            result = orderService.addOrder(map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //预约成功  发送短信通知
        if(result.isFlag()){
            String orderDate = (String) map.get("orderDate");
            try {
                //发送用户的预约日期至用户手机
                SMSUtils.sendShortMessage(SMSUtils.ORDER_NOTICE,telephone,orderDate);
            } catch (ClientException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 根据id查询预约信息,包括套餐信息和会员信息
     * @param id
     * @return
     */
    @RequestMapping("/findById")
    public Result findById(Integer id){
        try {
            Map map = orderService.findById(id);
            //查询预约信息成功
            return new Result(true,MessageConstant.QUERY_ORDER_SUCCESS,map);
        } catch (Exception e) {
            e.printStackTrace();
            //查询预约信息失败
            return new Result(false,MessageConstant.QUERY_ORDER_FAIL);
        }
    }
}

7.3 完善OrderService

package com.oracle.service;

import com.oracle.entity.Result;

import java.util.Map;

public interface OrderService {
    //添加体检预约
    Result addOrder(Map map);
    //通过orderID查找Order信息、Setmeal信息等
    Map findById(Integer id);
}

7.4 体检预约方法处理逻辑比较复杂,业务流程如下所示

在health_service_provider工程中创建体检预约服务实现类OrderServiceImpl并实现体检预约方法。 
体检预约方法处理逻辑比较复杂,需要进行如下业务处理: 
1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约 
2、检查用户所选择的预约日期是否已经约满,如果已经约满则无法预约
3、检查用户是否重复预约(同一个用户在同一天预约了同一个套餐),如果是重复预约,则无法完成再次预约 
4、检查当前用户是否为会员,如果是会员则直接完成预约,如果不是会员则自动完成注册并进行预约 
5、预约成功,更新当日的已预约人数,实现代码如下:
package com.oracle.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.oracle.constant.MessageConstant;
import com.oracle.dao.MemberDao;
import com.oracle.dao.OrderDao;
import com.oracle.dao.OrderSettingDao;
import com.oracle.entity.Result;
import com.oracle.pojo.Member;
import com.oracle.pojo.Order;
import com.oracle.pojo.OrderSetting;
import com.oracle.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.Map;

@Service(interfaceClass = OrderService.class)
public class OrderServiceImpl implements OrderService{

    @Autowired
    OrderDao orderDao;
    @Autowired
    OrderSettingDao orderSettingDao;
    @Autowired
    MemberDao memberDao;

    /**
     * 体检预约方法处理逻辑比较复杂,需要进行如下业务处理:
     * 1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约
     * 2、检查用户所选择的预约日期是否已经约满,如果已经约满则无法预约
     * 3、检查用户是否重复预约(同一个用户在同一天预约了同一个套餐),如果是重复预约 则无法完成再次预约
     * 4、检查当前用户是否为会员,如果是会员则直接完成预约,如果不是会员则自动完成注册并进行预约
     * 5、预约成功,更新当日的已预约人数
     * @param map
     * @return
     */
    @Override
    public Result addOrder(Map map) {
        //1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约
        String orderDate = (String) map.get("orderDate");
        //将字符串类型的日期类转换成Date类型
        Date date = DateUtils.parseString2Date(orderDate);
        //根据预约日期查询t_ordersetting表是否存在对应的日期,如果存在才可以预约,如果不存在就不能预约
        OrderSetting orderSetting = orderSettingDao.findByOrderDate(date);
        if(orderSetting==null){
            //预约日期不存在,不能预约
            return new Result(false, MessageConstant.SELECTED_DATE_CANNOT_ORDER);
        }
        //如果预约日期存在,那么接着判断预约日期是否满员
        int number = orderSetting.getNumber();  //可预约人数
        int reservations = orderSetting.getReservations();   //已预约人数
        if(number==reservations){
            //预约人数已满,不能进行预约
            return new Result(false,MessageConstant.ORDER_FULL);
        }
        //如果预约日期没有满员,可以进行预约,那么这时会判断该用户是否是会员
        //根据手机号码判断该用户是否是会员
        String telephone = (String) map.get("telephone");
        Member member = memberDao.findByTelephone(telephone);
        //如果存在该会员,那么为了防止重复预约 需要查询对应会员是否已经预约
        if(member!=null){
            Integer memberId = member.getId();//会员ID
            Integer setmealId = Integer.valueOf((String) map.get("setmealId")); //套餐ID
            Order order = new Order(memberId,date,null,null,setmealId);
            Integer orderDaoByCondition = orderDao.findByCondition(order);
            if(orderDaoByCondition>0){
                //该会员已经当天预约过,不能再次预约
                return new Result(false,MessageConstant.HAS_ORDERED);
            }
        }
        //用户可以进行预约,设置预约人数+1
        orderSetting.setReservations(orderSetting.getReservations()+1);
        //更新已预约人数
        orderSettingDao.upateOrderSetting(orderSetting);
        //如果该用户不是会员,需要添加到会员列表
        if(member==null){
            member = new Member();
            member.setName((String) map.get("name"));
            member.setPhoneNumber(telephone);
            member.setIdCard((String) map.get("idCard"));
            member.setSex((String) map.get("sex"));
            member.setRegTime(new Date());
            memberDao.addMember(member);
        }
        //将预约信息保存到预约表里面
        Order order = new Order(member.getId(),date,(String)map.get("orderType"),Order.ORDERSTATUS_NO,Integer.parseInt((String)map.get("setmealId")));
        orderDao.addOrder(order);
        return new Result(true,MessageConstant.ORDER_SUCCESS,order.getId());
    }

    @Override
    public Map findById(Integer id) {
        Map map = orderDao.findDetailsById(id);
        System.out.println("MAP:"+map);
        if(map!=null){
            Date orderDate = (Date)map.get("orderDate");
            //由于从数据库中查找出来的类型是date类型,但是在前台页面展示的是String类型的,所以需要进行转换
            map.put("orderDate",DateUtils.parseDate2String(orderDate));
        }
        return map;
    }
}

7.5 Dao接口

OrderDao

package com.oracle.dao;

import com.oracle.pojo.Order;
import com.oracle.pojo.OrderSetting;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Repository
public interface OrderDao {
    //根据前台传过来的体检预约信息在订单表中判断是否存在
    Integer findByCondition(Order order);
    //添加预约信息到预约表里面
    void addOrder(Order order);
}

OrderSettingDao

package com.oracle.dao;

import com.oracle.pojo.OrderSetting;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Repository
public interface OrderSettingDao {
    //提交体检预约的时候根据date判断该日期是否存在
    OrderSetting findByOrderDate(Date date);
    //更新已预约人数
    void upateOrderSetting(OrderSetting orderSetting);
}

memberDao

package com.oracle.dao;

import com.oracle.pojo.Member;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberDao {
    //根据用户手机号码判断用户是否为会员
    Member findByTelephone(String telephone);
    //添加用户
    void addMember(Member member);
}

7.6 Mapper映射文件

OrderSettingDao.xml



    
    
    
    
        UPDATE t_ordersetting SET reservations=#{reservations} where orderDate=#{orderDate}
    

OrderDao.xml



    
    
    
    
        
            SELECT LAST_INSERT_ID()
        
        insert into  t_order(member_id,orderDate,orderType,orderStatus,setmeal_id)
        values(#{memberId},#{orderDate},#{orderType},#{orderStatus},#{setmealId})
    

memberDao.xml



    
    
    
    
        
            SELECT LAST_INSERT_ID()
        
        insert into t_member(fileNumber,name,sex,idCard,phoneNumber,regTime,password,email,birthday,remark)
        values(#{fileNumber},#{name},#{sex},#{idCard},#{phoneNumber},#{regTime},#{password},#{email},#{birthday},#{remark})
    

八、预约成功页面展示

前面已经完成了体检预约,预约成功后页面会跳转到成功提示页面(orderSuccess.html)并展示预约的相关信息(体检人、体检套餐、体检时间等)。
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第7张图片
8.1 完善OrderController

/**
 * 根据id查询预约信息,包括套餐信息和会员信息
 * @param id
 * @return
 */
  @RequestMapping("/findById")
  public Result findById(Integer id){
      try {
          Map map = orderService.findById(id);
          //查询预约信息成功
          return new Result(true,MessageConstant.QUERY_ORDER_SUCCESS,map);
      } catch (Exception e) {
          e.printStackTrace();
          //查询预约信息失败
          return new Result(false,MessageConstant.QUERY_ORDER_FAIL);
      }
  }

8.2 OrderService服务接口

package com.oracle.service;

import com.oracle.entity.Result;

import java.util.Map;

public interface OrderService {
    //通过orderID查找Order信息、Setmeal信息等
    Map findById(Integer id);
}

8.3 OrderServiceImpl服务实现类

/**
 * 根据id查询预约信息,包括套餐信息和会员信息
 * @param id
 * @return
 */
@Override
public Map findById(Integer id) throws Exception{
    Map map = orderDao.findDetailsById(id);
    System.out.println("MAP:"+map);
    if(map!=null){
        Date orderDate = (Date)map.get("orderDate");
        map.put("orderDate",DateUtils.parseDate2String(orderDate));
    }
    return map;
}

8.4 在OrderDao接口中扩展findDetailsById方法

public interface OrderDao {
    Map findDetailsById(Integer id);
}

8.5 OrderDao.xml映射文件



9.启动测试

健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第8张图片
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第9张图片
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第10张图片
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第11张图片
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第12张图片
健康管理系统第七天(移动端_体检预约(手机号校验、发送验证码之后30秒倒计时效果、生成验证码、向手机发送验证码))_第13张图片

你可能感兴趣的:(Java学习资源和工具)