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

二、体检预约
1. 页面调整
在预约页面(/pages/orderInfo.html)进行调整
1.1 展示预约的套餐信息
第一步:从请求路径中获取当前套餐的id
<script> var id = getUrlParam("id");//套餐id </script>
第二步:定义模型数据setmeal,用于套餐数据展示
var vue = new Vue({
el:'#app',
data:{
setmeal:{},//套餐信息
orderInfo:{
setmealId:id,
sex:'1'
}//预约信息
}
});
<div class="card"> <div class=""> <img :src="'http://pqjroc654.bkt.clouddn.com/'+setmeal.img" width="100%" height="100%" /> </div> <div class="project-text"> <h4 class="tit">{{setmeal.name}}</h4> <p class="subtit">{{setmeal.remark}}</p> <p class="keywords">
<span>{{setmeal.sex == '0' ? '性别不限' : setmeal.sex == '1' ? '男':'女'}}</span> <span>{{setmeal.age}}</span> </p> </div> <div class="project-know"> <a href="orderNotice.html" class="link-page"> <i class="icon-ask-circle"><span class="path1"></span><span class="path2"></span></i> <span class="word">预约须知</span> <span class="arrow"><i class="icon-rit-arrow"></i></span> </a> </div> </div>
第三步:在VUE的钩子函数中发送ajax请求,根据id查询套餐信息
mounted(){
axios.post("/setmeal/findById.do?id=" + id).then((response) => {
if(response.data.flag){
this.setmeal = response.data.data;
}
});
}
1.2 手机号校验
第一步:在页面导入的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;
}
}
第二步:为发送验证码按钮绑定事件sendValidateCode
<div class="input-row"> <label>手机号</label> <input v-model="orderInfo.telephone" type="text" class="input-clear" placeholder="请输入手机号"> <input style="font-size: x-small;" id="validateCodeButton" @click="sendValidateCode()" type="button" value="发送验证码"> </div>
//发送验证码
sendValidateCode(){
//获取用户输入的手机号
var telephone = this.orderInfo.telephone;
//校验手机号输入是否正确
if (!checkTelephone(telephone)) {
this.$message.error('请输入正确的手机号');
return false;
}
}
1.3 30秒倒计时效果
前面在sendValidateCode方法中进行了手机号校验,如果校验通过,需要显示30秒倒计时效果
//发送验证码
sendValidateCode(){
//获取用户输入的手机号
var telephone = this.orderInfo.telephone;
//校验手机号输入是否正确
if (!checkTelephone(telephone)) {
this.$message.error('请输入正确的手机号');
return false;
}
validateCodeButton = $("#validateCodeButton")[0];
clock = window.setInterval(doLoop, 1000); //一秒执行一次
}
其中,validateCodeButton和clock是在healthmobile.js文件中定义的变量,doLoop是在 healthmobile.js文件中定义的方法
var clock = '';//定时器对象,用于页面30秒倒计时效果
var nums = 30;
var validateCodeButton;//发送验证码按钮对象
//基于定时器实现30秒倒计时效果
function doLoop() {
validateCodeButton.disabled = true;//将按钮置为不可点击
nums--;
if (nums > 0) {
validateCodeButton.value = nums + '秒后重新获取';
} else {
clearInterval(clock); //清除js定时器
validateCodeButton.disabled = false;
validateCodeButton.value = '重新获取验证码';
nums = 30; //重置时间
}
}
1.4 发送ajax请求
在按钮上显示30秒倒计时效果的同时,需要发送ajax请求,在后台给用户发送手机验证码
//发送验证码
sendValidateCode(){
//获取用户输入的手机号
var telephone = this.orderInfo.telephone;
//校验手机号输入是否正确
if (!checkTelephone(telephone)) {
this.$message.error('请输入正确的手机号');
return false;
}
validateCodeButton = $("#validateCodeButton")[0];
clock = window.setInterval(doLoop, 1000); //一秒执行一次
//发送ajax请求,发送手机验证码
axios.post("/validateCode/send4Order.do?telephone=" + telephone).then((response) => {
if(!response.data.flag){
//验证码发送失败
this.$message.error('验证码发送失败,请检查手机号输入是否正确');
}
});
}
创建ValidateCodeController,提供方法发送短信验证码,并将验证码保存到redis
package com.itheima.controller;
import com.aliyuncs.exceptions.ClientException;
import com.itheima.constant.MessageConstant;
import com.itheima.constant.RedisConstant;
import com.itheima.constant.RedisMessageConstant;
import com.itheima.entity.Result;
import com.itheima.utils.JedisUtils;
import com.itheima.utils.SMSUtils;
import com.itheima.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.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Random;
/**
* 短信验证码
*/
@RestController
@RequestMapping("/validateCode")
public class ValidateCodeController {
@Autowired
private JedisPool jedisPool;
//体检预约时发送手机验证码
@RequestMapping("/send4Order")
public Result send4Order(String telephone){
Integer code = ValidateCodeUtils.generateValidateCode(4);//生成4位数字验证码
try {
//发送短信
SMSUtils.sendShortMessage(SMSUtils.VALIDATE_CODE,telephone,code.toString());
} catch (ClientException e) {
e.printStackTrace();
//验证码发送失败
return new Result(false, MessageConstant.SEND_VALIDATECODE_FAIL);
}
System.out.println("发送的手机验证码为:" + code);
//将生成的验证码缓存到redis,5分钟失效
jedisPool.getResource().setex(telephone + RedisMessageConstant.SENDTYPE_ORDER,5 * 60,code.toString());
//验证码发送成功
return new Result(true,MessageConstant.SEND_VALIDATECODE_SUCCESS);
}
}
1.5 日历展示
页面中使用DatePicker控件来展示日历。根据需求,最多可以提前一个月进行体检预约,所以日历控件只展示未来一个月的日期
<div class="date"> <label>体检日期</label> <i class="icon-date" class="picktime"></i><!--日历图标--> <input v-model="orderInfo.orderDate" type="text" class="picktime" readonly> </div>
<script> //日期控件 var calendar = new datePicker(); calendar.init({ 'trigger': '.picktime',/*按钮选择器,用于触发弹出插件*/ 'type': 'date',/*模式:date日期;datetime日期时间;time时间;ym年月;*/ 'minDate': getSpecifiedDate(new Date(),1),/*最小日期*/ 'maxDate': getSpecifiedDate(new Date(),30),/*最大日期*/ 'onSubmit': function() { /*确认时触发事件*/}, 'onClose': function() { /*取消时触发事件*/ } }); </script>
其中getSpecifiedDate方法定义在healthmobile.js文件中
//获得指定日期后指定天数的日期
function getSpecifiedDate(date,days) {
date.setDate(date.getDate() + days);//获取指定天之后的日期
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
return (year + "-" + month + "-" + day);
}
1.6 提交预约请求
为提交预约按钮绑定事件
<div class="box-button"> <button @click="submitOrder()" type="button" class="btn order-btn">提交预约</button> </div>
//提交预约 submitOrder(){ //校验身份证号格式 if(!checkIdCard(this.orderInfo.idCard)){ this.$message.error('身份证号码输入错误,请重新输入'); return ; } axios.post("/order/submit.do",this.orderInfo).then((response) => { if(response.data.flag){ //预约成功,跳转到预约成功页面 window.location.href="orderSuccess.html?orderId=" + response.data.data; }else{ //预约失败,提示预约失败信息 this.$message.error(response.data.message); } }); }
其中checkIdCard方法是在healthmobile.js文件中定义的
/**
* 身份证号码校验
* 身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X
*/
function checkIdCard(idCard){
var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
if(reg.test(idCard)){
return true;
}else{
return false;
}
}
2. 后台代码
2.1 Controller
在health_mobile工程中创建OrderController并提供submitOrder方法
package com.itheima.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.aliyuncs.exceptions.ClientException;
import com.itheima.constant.MessageConstant;
import com.itheima.constant.RedisConstant;
import com.itheima.constant.RedisMessageConstant;
import com.itheima.entity.Result;
import com.itheima.pojo.Member;
import com.itheima.pojo.Order;
import com.itheima.pojo.Setmeal;
import com.itheima.service.OrderService;
import com.itheima.utils.JedisUtils;
import com.itheima.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.HashMap;
import java.util.Map;
/**
* 体检预约
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Reference
private OrderService orderService;
@Autowired
private JedisPool jedisPool;
/**
* 体检预约
* @param map
* @return
*/
@RequestMapping("/submit")
public Result submitOrder(@RequestBody Map map){
String telephone = (String) map.get("telephone");
//从Redis中获取缓存的验证码,key为手机号+RedisConstant.SENDTYPE_ORDER
String codeInRedis = jedisPool.getResource().get(
telephone + RedisMessageConstant.SENDTYPE_ORDER);
String validateCode = (String) map.get("validateCode");
//校验手机验证码
if(codeInRedis == null || !codeInRedis.equals(validateCode)){
return new Result(false, MessageConstant.VALIDATECODE_ERROR);
}
Result result =null;
//调用体检预约服务
try{
map.put("orderType", Order.ORDERTYPE_WEIXIN);
result = orderService.order(map);
}catch (Exception e){
e.printStackTrace();
//预约失败
return result;
}
if(result.isFlag()){
//预约成功,发送短信通知
String orderDate = (String) map.get("orderDate");
try {
SMSUtils.sendShortMessage(SMSUtils.ORDER_NOTICE,telephone,orderDate);
} catch (ClientException e) {
e.printStackTrace();
}
}
return result;
}
}
2.2 服务接口
在health_interface工程中创建体检预约服务接口OrderService并提供预约方法
package com.itheima.service;
import com.itheima.entity.Result;
import java.util.Map;
/**
* 体检预约服务接口
*/
public interface OrderService {
//体检预约
public Result order(Map map) throws Exception;
}
2.3 服务实现类
在health_service_provider工程中创建体检预约服务实现类OrderServiceImpl并实现体检预约方法。
体检预约方法处理逻辑比较复杂,需要进行如下业务处理:
1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约
2、检查用户所选择的预约日期是否已经约满,如果已经约满则无法预约
3、检查当前用户是否为会员,如果是会员则检查用户是否重复预约,如果不是会员则自动完成注册并进行预约
4、检查用户是否重复预约(同一个用户在同一天预约了同一个套餐),如果是重复预约则无法完成再次预约
5、预约成功,更新当日的已预约人数
实现代码如下:
package com.itheima.service.impl; import com.alibaba.dubbo.config.annotation.Service; import com.itheima.constant.MessageConstant; import com.itheima.dao.MemberDao; import com.itheima.dao.OrderDao; import com.itheima.dao.OrderSettingDao; import com.itheima.entity.Result; import com.itheima.pojo.Member; import com.itheima.pojo.Order; import com.itheima.pojo.OrderSetting; import com.itheima.service.OrderService; import com.itheima.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; import java.util.Map; @Service(interfaceClass = OrderService.class) @Transactional public class OrderServiceImpl implements OrderService { @Autowired private OrderSettingDao orderSettingDao; @Autowired private OrderDao orderDao; @Autowired private MemberDao memberDao; @Override public Result order(Map map) throws Exception { //检查预约日期是否进行了预约设置,没有设置则无法进行预约 Date orderDate = DateUtils.parseString2Date((String) map.get("orderDate")); OrderSetting orderSetting = orderSettingDao.findByOrderDate(orderDate); if(orderSetting == null){ System.out.println("orderSetting is null and "+MessageConstant.SELECTED_DATE_CANNOT_ORDER); return new Result(false, MessageConstant.SELECTED_DATE_CANNOT_ORDER); } //检查预约日期是否已经约满,已经约满则无法预约 if(orderSetting.getReservations() >= orderSetting.getNumber()){ return new Result(false,MessageConstant.ORDER_FULL); } //检查当前用户是否为会员,根据手机号判断,如果是会员则直接完成预约,如果不是会员则自动完成注册并进行预约 String telephone = (String) map.get("telephone"); Member member = memberDao.findByTelephone(telephone); Integer setmealId = Integer.parseInt((String) map.get("setmealId")); if(member != null){//用户是会员 //判断是否在重复预约(同一个用户在同一天预约了同一个套餐),重复预约则无法完成再次预约 Integer memberId = member.getId(); Order order = new Order(memberId,orderDate,null,null,setmealId); List<Order> list = orderDao.findByCondition(order); if(list != null && list.size() > 0){ return new Result(false,MessageConstant.HAS_ORDERED); } }else{ //用户不是会员,需要添加到会员表,完成注册 member = new Member(); member.setName((String) map.get("name")); member.setSex((String) map.get("sex")); member.setIdCard((String) map.get("idCard")); member.setRegTime(new Date()); member.setPhoneNumber(telephone); memberDao.add(member); } //保存预约信息到预约表 Order order = new Order(member.getId(),orderDate,(String)map.get("orderType"),Order.ORDERSTATUS_NO,setmealId); orderDao.add(order); //预约成功,设置预约人数加一 //可能会出现线程安全问题,两个人同时预约,最后已预约人数+1,但是实际上是2个人预约了 //使用redis.setnx(key,value),两个人同时写redis,成功返回1表示抢到锁,不成功返回0会一直请求锁,那么两个人只能成功一个。这是分布式锁 //使用zookeeper,两个人往zookeeper里面写,请求按顺序写,第一个人写完会通知下一个人,不会一直去写,而是会通知下一个人去写。这是zookeeper实现分布式共享锁 orderSetting.setReservations(orderSetting.getReservations()+1); orderSettingDao.editReservationsByOrderDate(orderSetting); return new Result(true,MessageConstant.ORDER_SUCCESS,order.getId()); } }
2.4 Dao接口
OrderSettingDao
package com.itheima.dao;
import com.itheima.pojo.OrderSetting;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface OrderSettingDao {
//根据预约日期查询预约设置信息
public OrderSetting findByOrderDate(Date orderDate);
//更新已预约人数
public void editReservationsByOrderDate(OrderSetting orderSetting);
}
MemberDao
package com.itheima.dao;
import com.itheima.pojo.Member;
public interface MemberDao {
public Member findByTelephone(String telephone);
public void add(Member member);
}
OrderDao
package com.itheima.dao; import com.itheima.pojo.Order; import java.util.List; import java.util.Map; public interface OrderDao { public List<Order> findByCondition(Order order); public void add(Order order); }
2.5 Mapper映射文件
OrderSettingDao.xml
<!--根据日期查询预约设置信息--> <select id="findByOrderDate" parameterType="date" resultType="com.itheima.pojo.OrderSetting"> select * from t_ordersetting where orderDate = #{orderDate} </select> <!--根据预约日期更新已预约人数--> <update id="editReservationsByOrderDate" parameterType="com.itheima.pojo.OrderSetting"> update t_ordersetting set reservations = #{reservations} where orderDate = #{orderDate} </update>
MemberDao.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.itheima.dao.MemberDao" > <!--新增会员--> <insert id="add" parameterType="com.itheima.pojo.Member"> <selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id"> SELECT LAST_INSERT_ID() </selectKey> 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}) </insert> <!--根据id查询会员--> <select id="findByTelephone" parameterType="string" resultType="com.itheima.pojo.Member"> select * from t_member where phoneNumber = #{phoneNumber} </select> </mapper>
OrderDao.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.itheima.dao.OrderDao" > <resultMap id="baseResultMap" type="com.itheima.pojo.Order"> <id column="id" property="id"/> <result column="member_id" property="memberId"/> <result column="orderDate" property="orderDate"/> <result column="orderType" property="orderType"/> <result column="orderStatus" property="orderStatus"/> <result column="setmeal_id" property="setmealId"/> </resultMap> <!--新增--> <insert id="add" parameterType="com.itheima.pojo.Order"> <selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id"> SELECT LAST_INSERT_ID() </selectKey> insert into t_order(member_id,orderDate,orderType,orderStatus,setmeal_id) values (#{memberId},#{orderDate},#{orderType},#{orderStatus},#{setmealId}) </insert> <!--动态条件查询--> <select id="findByCondition" parameterType="com.itheima.pojo.Order" resultMap="baseResultMap"> select * from t_order <where> <if test="id != null"> and id = #{id} </if> <if test="memberId != null"> and member_id = #{memberId} </if> <if test="orderDate != null"> and orderDate = #{orderDate} </if> <if test="orderType != null"> and orderType = #{orderType} </if> <if test="orderStatus != null"> and orderStatus = #{orderStatus} </if> <if test="setmealId != null"> and setmeal_id = #{setmealId} </if> </where> </select> </mapper>
三、预约成功页面展示
前面已经完成了体检预约,预约成功后页面会跳转到成功提示页面(orderSuccess.html)并展示预约的相关信息(体检人、体检套餐、体检时间等)。
1. 页面调整
提供orderSuccess.html页面,展示预约成功后相关信息
<div class="info-title"> <span class="name">体检预约成功</span></div> <div class="notice-item"> <div class="item-title">预约信息</div> <div class="item-content"> <p>体检人:{{orderInfo.member}}</p> <p>体检套餐:{{orderInfo.setmeal}}</p>
<p>体检日期:{{orderInfo.orderDate}}</p>
<p>预约类型:{{orderInfo.orderType}}</p> </div> </div>
<script> //从请求URL根据参数名获取对应值,orderId为预约id var id = getUrlParam("orderId"); </script>
<script> var vue = new Vue({ el:'#app', data:{ orderInfo:{} }, mounted(){ axios.post("/order/findById.do?id=" + id).then((response) => { if(response.data.flag){ this.orderInfo = response.data.data; }else { this.$message.error(response.data.data); } }); } }); </script>
2. 后台代码
2.1 Controller
在OrderController中提供findById方法,根据预约id查询预约相关信息
//根据预约id查询预约相关信息,包括套餐信息和会员信息
@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){
return new Result(false, MessageConstant.QUERY_ORDER_FAIL);
}
}
2.2 服务接口
在OrderService服务接口中扩展findById方法
//根据id查询预约信息,包括体检人信息、套餐信息
public Map findById(Integer id) throws Exception;
2.3 服务实现类
在OrderServiceImpl服务实现类中实现findById方法
//根据预约id查询预约相关信息(体检人姓名、预约日期、套餐名称、预约类型)
@Override
public Map findById(Integer id) throws Exception {
Map map = orderDao.findById4Detail(id);
if(map != null){
Date orderDate = (Date) map.get("orderDate");
map.put("orderDate",DateUtils.parseDate2String(orderDate));
}
return map;
}
2.4 Dao接口
在OrderDao接口中扩展findById4Detail方法
public Map findById4Detail(Integer id);
2.5 Mapper映射文件
在OrderDao.xml映射文件中提供SQL语句
<!--根据预约id查询预约信息,包括体检人信息、套餐信息--> <select id="findById4Detail" parameterType="int" resultType="map">
select m.name member ,s.name setmeal,o.orderDate orderDate,o.orderType orderType from t_order o, t_member m, t_setmeal s where o.member_id=m.id and o.setmeal_id=s.id and o.id=#{id} </select>
浙公网安备 33010602011771号