手机验证码
1. 体检预约流程
用户可以通过如下操作流程进行体检预约:
1、在移动端首页点击体检预约,页面跳转到套餐列表页面
2、在套餐列表页面点击要预约的套餐,页面跳转到套餐详情页面
3、在套餐详情页面点击立即预约,页面跳转到预约页面
4、在预约页面录入体检人信息,包括手机号,点击发送验证码
5、在预约页面录入收到的手机短信验证码,点击提交预约,完成体检预约
2.体检预约
2.1页面调整
在预约页面(/pages/orderInfo.html)进行调整
2.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="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.get("/setmeal/find.do?id="+id).then((res)=>{
if (res.data.flag){
this.setmeal=res.data.data
}
})
}
2.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
//发送验证码
sendValidateCode(){
let telephone=this.orderInfo.telephone
if (!checkTelephone(telephone)){
//校验不通过,提示错误信息
this.$message.error("请输入正确的手机号");
return ;
}
2.1.3 30秒倒计时效果
前面在sendValidateCode方法中进行了手机号校验,如果校验通过,需要显示30秒倒计时效果
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; //重置时间
}
}
//id选择器,#(), jquery和js对象相互转化
// 转jquery $(js对象) 转js jquery对象.get(0)
validateCodeButton=$("#validateCodeButton")[0];
//在按钮上显示30秒倒计时效果
window.setInterval(doLoop,1000);//定时器方法
2.1.4 发送ajax请求
在按钮上显示30秒倒计时效果的同时,需要发送ajax请求,在后台给用户发送手机验证码
//发送验证码
sendValidateCode(){
let telephone=this.orderInfo.telephone
if (!checkTelephone(telephone)){
//校验不通过,提示错误信息
this.$message.error("请输入正确的手机号");
return ;
}
//id选择器,#(), jquery和js对象相互转化
// 转jquery $(js对象) 转js jquery对象.get(0)
validateCodeButton=$("#validateCodeButton")[0];
//在按钮上显示30秒倒计时效果
window.setInterval(doLoop,1000);//定时器方法
axios.get("/validateCode/send4Order.do?telephone="+telephone).then((res)=>{
if (!res.data.flag){
//短信验证码发送失败
this.$message.error(res.data.message);
}
})
}
创建ValidateCodeController,提供方法发送短信验证码,并将验证码保存到redis
package com.ybb.controller;
import com.aliyuncs.exceptions.ClientException;
import com.ybb.constant.MessageConstant;
import com.ybb.constant.RedisMessageConstant;
import com.ybb.entity.Result;
import com.ybb.utils.SMSUtils;
import com.ybb.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;
/**
* Description :
* Version :1.0
*/
@RestController
@RequestMapping("/validateCode")
public class ValidateCodeController {
@Autowired
private JedisPool jedisPool;
//发送验证码 用户在线体检预约的
@RequestMapping("/send4Order")
public Result send4Order(String telephone){
//给用户发送验证码
Integer validateCode = ValidateCodeUtils.generateValidateCode(4);
String value = String.valueOf(validateCode);
System.out.println(validateCode);
try {
/* SMSUtils.sendShortMessage(SMSUtils.VALIDATE_CODE,telephone,value);
*/ //根据业务用手机号+业务的形式保存
//将验证码保存到redis(定时间)
jedisPool.getResource().setex(telephone+ RedisMessageConstant.SENDTYPE_ORDER,500,value);
return new Result(true,MessageConstant.SEND_VALIDATECODE_SUCCESS);
} catch (Exception e) {
e.printStackTrace();
return new Result(false, MessageConstant.SEND_VALIDATECODE_FAIL);
}
}
}
2.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() { /*确认时触发事件*/
//var theSelectData = calendar.value;
},
'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);
}
2.1.6 提交预约请求
为提交预约按钮绑定事件
<div class="box-button">
<button @click="submitOrder()" type="button" class="btn order-btn">提交预约</button>
</div>
//提交预约
submitOrder(){
//先对身份证号进行校验
let IdCard=this.orderInfo.idCard;
if (!checkIdCard(IdCard)){
this.$message.error("请输入正确的身份证号")
return;
};
axios.post("/order/submit.do",this.orderInfo).then((res)=>{
if (res.data.flag){
//预约成功,跳转到成功页面
window.location.href="orderSuccess.html?orderId="+res.data.data;
}else {
this.$message.error(res.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 后台代码
2.2.1 Controller
在health_mobile工程中创建OrderController并提供submitOrder方法
package com.ybb.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.ybb.constant.MessageConstant;
import com.ybb.constant.RedisMessageConstant;
import com.ybb.entity.Result;
import com.ybb.pojo.Order;
import com.ybb.service.OrderService;
import com.ybb.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;
/**
* Description :体检预约处理
* Version :1.0
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private JedisPool jedisPool;
@Reference
private OrderService orderService;
@RequestMapping("/submit")
public Result submit(@RequestBody Map map) {
String telephone = (String) map.get("telephone");
//从redis缓存中拿到具体验证码
String validateCodeInRedis = jedisPool.getResource().get(telephone + RedisMessageConstant.SENDTYPE_ORDER);
String validateCode = (String) map.get("validateCode");
//验证码比对
if (validateCodeInRedis != null && validateCode != null && validateCode.equals(validateCodeInRedis)) {
Result result=null;
try {
result = orderService.order(map);
/* SMSUtils.sendShortMessage(SMSUtils.ORDER_NOTICE,telephone,(String) map.get("orderDate"));*/
return result;
} catch (Exception e) {
e.printStackTrace();
return result;
}
}else {
//比对失败
return new Result(false, MessageConstant.VALIDATECODE_ERROR);
}
}
@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);
}
}
}
在health_interface工程中创建体检预约服务接口OrderService并提供预约方法
public interface OrderService {
public Result order(Map map) throws Exception;
Map findById(Integer id);
}
2.2.3 服务实现类
在health_service_provider工程中创建体检预约服务实现类OrderServiceImpl并实现体检预约方法。
体检预约方法处理逻辑比较复杂,需要进行如下业务处理:
1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约
2、检查用户所选择的预约日期是否已经约满,如果已经约满则无法预约
3、检查用户是否重复预约(同一个用户在同一天预约了同一个套餐),如果是重复预约则无法完成再
次预约
4、检查当前用户是否为会员,如果是会员则直接完成预约,如果不是会员则自动完成注册并进行预约
5、预约成功,更新当日的已预约人数
实现代码如下:
package com.ybb.service.Impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.ybb.constant.MessageConstant;
import com.ybb.dao.MemberDao;
import com.ybb.dao.OrderDao;
import com.ybb.dao.OrderSettingDao;
import com.ybb.entity.Result;
import com.ybb.pojo.Member;
import com.ybb.pojo.Order;
import com.ybb.pojo.OrderSetting;
import com.ybb.service.OrderService;
import com.ybb.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Description :体检预约服务
* Version :1.0
*/
@Service(interfaceClass = OrderService.class)
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderSettingDao orderSettingDao;
@Autowired
private MemberDao memberDao;
@Autowired
private OrderDao orderDao;
@Override
public Result order(Map map) throws Exception {
//检查用户锁选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约
String orderDate = (String) map.get("orderDate");
Date date = DateUtils.parseString2Date(orderDate);
OrderSetting orderSetting= orderSettingDao.findCountByOrderDate(date);
if (orderSetting==null){
return new Result(false, MessageConstant.SELECTED_DATE_CANNOT_ORDER);
}
//检查用户预约的日期是否已经预约满了,如果已经约满则无法预约
int number = orderSetting.getNumber();//可预约人数
int reservations = orderSetting.getReservations();//已预约人数
if (reservations>=number){
//已经约满,无法预约
return new Result(false,MessageConstant.ORDERSETTING_FAIL);
}
//检查用户是否重复预约(同一用户同一时间同一套餐),如果是重复预约那就无法完成再次预约
String telephone = (String) map.get("telephone");
Member member = memberDao.findByTelephone(telephone);
if (member!=null){
//判断是否在重复预约
Integer id = member.getId();
Date order_date = DateUtils.parseString2Date(orderDate);
String setmealId = (String) map.get("setmealId");
//调方法查询有没有这个对象,有则报错
Member member1= orderDao.findByCondition(id,order_date,setmealId);
if (member1!=null) {
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.setPhoneNumber(telephone);
member.setRegTime(new Date());
memberDao.add(member);
}
//保存信息
Map map1=new HashMap();
map1.put("member_id",member.getId());
map1.put("orderDate",date);
map1.put("orderType",Order.ORDERTYPE_WEIXIN);
map1.put("orderStatus",Order.ORDERSTATUS_NO);
map1.put("setmeal_id",Integer.parseInt((String) map.get("setmealId")));
orderDao.add(map1);
//预约成功,更新当日的已预约人数
orderSetting.setReservations(orderSetting.getReservations()+1);
orderSettingDao.editNumberByOrderDate(orderSetting);
//检查
return new Result(true,MessageConstant.ORDER_SUCCESS,member.getId());
}
/**
* @param id 根据预约ID查询预约相关信息(体检人姓名,预约日期,套餐名称,名称类型)
* @return java.util.Map
*/
@Override
public Map findById(Integer id) {
Map map= orderDao.findById4Detail(id);
System.out.println(map);
return map;
}
}
2.2.4 Dao接口
package com.ybb.dao;
import com.ybb.pojo.OrderSetting;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
**OrderSettingDao**
/**
* Created by Administrator
* Date :2020/8/25
* Description :
* Version :1.0
*/
public interface OrderSettingDao {
void add(OrderSetting orderSetting);
void editNumberByOrderDate(OrderSetting orderSetting);
public OrderSetting findCountByOrderDate(Date orderDate);
List<Map> getOrderSettingByMonth(@Param("begin") String begin,@Param("end") String end);
public OrderSetting findByOrderDate(Date orderDate)throws Exception;
}
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.ybb.dao.OrderSettingDao">
<insert id="add" parameterType="com.ybb.pojo.OrderSetting">
insert into t_ordersetting(orderDate,number,reservations) values (#{orderDate},#{number},#{reservations})
</insert>
<update id="editNumberByOrderDate" parameterType="com.ybb.pojo.OrderSetting">
update t_ordersetting set reservations=#{reservations} where orderDate=#{orderDate}
</update>
<select id="findCountByOrderDate" parameterType="date" resultType="com.ybb.pojo.OrderSetting">
select * from t_ordersetting where orderDate=#{orderDate}
</select>
<!--根据日期范围查询-->
<select id="getOrderSettingByMonth" parameterType="string" resultType="map">
select day(orderDate)date,number,reservations from t_ordersetting where orderDate between #{begin} and #{end}
</select>
<select id="findByOrderDate" resultType="com.ybb.pojo.OrderSetting" parameterType="date">
select * from
</select>
</mapper>
memberDao
public interface MemberDao {
public List<Member> findAll();
public Page<Member> selectByCondition(String queryString);
public void add(Member member);
public void deleteById(Integer id);
public Member findById(Integer id);
public Member findByTelephone(String telephone);
public void edit(Member member);
public Integer findMemberCountBeforeDate(String date);
public Integer findMemberCountByDate(String date);
public Integer findMemberCountAfterDate(String date);
public Integer findMemberTotalCount();
}
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.ybb.dao.MemberDao" >
<select id="findAll" resultType="com.ybb.pojo.Member">
select * from t_member
</select>
<!--根据条件查询-->
<select id="selectByCondition" parameterType="string" resultType="com.ybb.pojo.Member">
select * from t_member
<if test="value != null and value.length > 0">
where fileNumber = #{value} or phoneNumber = #{value} or name = #{value}
</if>
</select>
<!--新增会员-->
<insert id="add" parameterType="com.ybb.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>
<!--删除会员-->
<delete id="deleteById" parameterType="int">
delete from t_member where id = #{id}
</delete>
<!--根据id查询会员-->
<select id="findById" parameterType="int" resultType="com.ybb.pojo.Member">
select * from t_member where id = #{id}
</select>
<!--根据id查询会员-->
<select id="findByTelephone" parameterType="string" resultType="com.ybb.pojo.Member">
select * from t_member where phoneNumber = #{phoneNumber}
</select>
<!--编辑会员-->
<update id="edit" parameterType="com.ybb.pojo.Member">
update t_member
<set>
<if test="fileNumber != null">
fileNumber = #{fileNumber},
</if>
<if test="name != null">
name = #{name},
</if>
<if test="sex != null">
sex = #{sex},
</if>
<if test="idCard != null">
idCard = #{idCard},
</if>
<if test="phoneNumber != null">
phoneNumber = #{phoneNumber},
</if>
<if test="regTime != null">
regTime = #{regTime},
</if>
<if test="password != null">
password = #{password},
</if>
<if test="email != null">
email = #{email},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
<if test="remark != null">
remark = #{remark},
</if>
</set>
where id = #{id}
</update>
<!--根据日期统计会员数,统计指定日期之前的会员数-->
<select id="findMemberCountBeforeDate" parameterType="string" resultType="int">
select count(id) from t_member where regTime <= #{value}
</select>
<!--根据日期统计会员数-->
<select id="findMemberCountByDate" parameterType="string" resultType="int">
select count(id) from t_member where regTime = #{value}
</select>
<!--根据日期统计会员数,统计指定日期之后的会员数-->
<select id="findMemberCountAfterDate" parameterType="string" resultType="int">
select count(id) from t_member where regTime >= #{value}
</select>
<!--总会员数-->
<select id="findMemberTotalCount" resultType="int">
select count(id) from t_member
</select>
</mapper>
OrderDao
public interface OrderDao {
Map findById4Detail(Integer id);
Member findByCondition(@Param("id") Integer id, @Param("orderDate") Date order_date, @Param("setmeal_id") String setmealId);
void add(Map map);
}
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.ybb.dao.OrderDao">
<insert id="add" parameterType="map">
insert into t_order (member_id,orderDate,orderType,orderStatus,setmeal_id)
values
(#{member_id},#{orderDate},#{orderType},#{orderStatus},#{setmeal_id})
</insert>
<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.member_id=#{id}
</select>
<select id="findByCondition" resultType="com.ybb.pojo.Member">
select * from t_order where id=#{id} and orderDate =#{orderDate} and setmeal_id =#{setmeal_id}
</select>
</mapper>
3. 预约成功页面展示
前面已经完成了体检预约,预约成功后页面会跳转到成功提示页面(orderSuccess.html)并展示预约
的相关信息(体检人、体检套餐、体检时间等)。
3.1 页面调整
提供orderSuccess.html页面,展示预约成功后相关信息
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,user-scalable=no,minimal-ui">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../img/asset-favico.ico">
<title>预约须知</title>
<link rel="stylesheet" href="../css/page-health-orderNotice.css" />
<script src="../plugins/jquery/dist/jquery.min.js"></script>
<script src="../plugins/healthmobile.js"></script>
<script src="../plugins/vue/vue.js"></script>
<script src="../plugins/vue/axios-0.18.0.js"></script>
<script>
var id = getUrlParam("orderId");
</script>
</head>
<body data-spy="scroll" data-target="#myNavbar" data-offset="150">
<div id="app" class="app">
<!-- 页面头部 -->
<div class="top-header">
<span class="f-left"><i class="icon-back" onclick="history.go(-1)"></i></span>
<span class="center">传智健康</span>
<span class="f-right"><i class="icon-more"></i></span>
</div>
<!-- 页面内容 -->
<div class="contentBox">
<div class="notice-article">
<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>
<div class="notice-item">
<div class="item-title">注意事项</div>
<div class="item-content">
<p>1、体检前三天饮食不宜有太大变化,尽量保证清淡饮食,避免油腻、过甜、过咸的食物,避免饮食不均暴饮暴食,以免影响检查结果。</p>
<p>2、体检前要注意好好休息,尽量不要做剧烈运动以及情绪也不宜激动,要保证充足的睡眠时间,调整好自己的身体状态。</p>
<p>3、体检前不要饮酒,酒精会影响到检查的准确性,如导致甘油三酯、转氨酶等检查结果出现异常,干扰到很多检查项目。</p>
<p>4、体检当天应该保证穿着轻便、简单,另外女士要注意不要穿连裙、连裤袜,以及不要佩戴饰品等,以免影响检查。</p>
<p>5、女士朋友要注意避开经期,以防经期影响到相关的检查项目。</p>
<p>6、如果有前列腺或妇科B超检查,需要憋尿,检前可以适当饮水,以保证膀胱充盈后再进行检查。</p>
<p>7、如果有妇科检查,要注意检前不宜进行夫妻生活,夫妻生活会影响到阴道检查,干扰检查的结果。</p>
<p>8、做妇科检查前也不宜过度清洁,不宜使用阴道药物,避免过度清洁或是药物干扰,影响到检查的结果。</p>
<p>9、积极配合医师的检查,保持良好的心态,告知医师自己的真实情况,有助于医师帮助判断身体健康状况。</p>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
var vue = new Vue({
el:'#app',
data:{
orderInfo:{}
},
mounted(){
axios.post("/order/findById.do?id=" + id).then((response) => {
console.log(response.data.data)
if (response.data.flag){
this.orderInfo = response.data.data;
}else {
this.$message.error(response.data.message)
}
});
}
});
</script>
</html>
3.2 后台代码
3.2.1 Controller
在OrderController中提供findById方法,根据预约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);
}
}
3.2.2 服务接口
在OrderService服务接口中扩展findById方法
/**
* @param id 根据预约ID查询预约相关信息(体检人姓名,预约日期,套餐名称,名称类型)
* @return java.util.Map
*/
@Override
public Map findById(Integer id) {
Map map= orderDao.findById4Detail(id);
System.out.println(map);
return map;
}
3.2.4 Dao接口
在OrderDao接口中扩展findById4Detail方法
Map findById4Detail(Integer id);
3.2.5 Mapper映射文件
在OrderDao.xml映射文件中提供SQL语句
<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.member_id=#{id}
</select>
工具类
生成验证码工具类
package com.ybb.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.ybb.utils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
public class POIUtils {
private final static String xls = "xls";
private final static String xlsx = "xlsx";
private final static String DATE_FORMAT = "yyyy/MM/dd";
/**
* 读入excel文件,解析后返回
* @param file
* @throws IOException
*/
public static List<String[]> readExcel(MultipartFile file) throws IOException {
//检查文件
checkFile(file);
//获得Workbook工作薄对象
Workbook workbook = getWorkBook(file);
//创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回
List<String[]> list = new ArrayList<String[]>();
if(workbook != null){
for(int sheetNum = 0;sheetNum < workbook.getNumberOfSheets();sheetNum++){
//获得当前sheet工作表
Sheet sheet = workbook.getSheetAt(sheetNum);
if(sheet == null){
continue;
}
//获得当前sheet的开始行
int firstRowNum = sheet.getFirstRowNum();
//获得当前sheet的结束行
int lastRowNum = sheet.getLastRowNum();
//循环除了第一行的所有行
for(int rowNum = firstRowNum+1;rowNum <= lastRowNum;rowNum++){
//获得当前行
Row row = sheet.getRow(rowNum);
if(row == null){
continue;
}
//获得当前行的开始列
int firstCellNum = row.getFirstCellNum();
//获得当前行的列数
int lastCellNum = row.getPhysicalNumberOfCells();
String[] cells = new String[row.getPhysicalNumberOfCells()];
//循环当前行
for(int cellNum = firstCellNum; cellNum < lastCellNum;cellNum++){
Cell cell = row.getCell(cellNum);
cells[cellNum] = getCellValue(cell);
}
list.add(cells);
}
}
workbook.close();
}
return list;
}
//校验文件是否合法
public static void checkFile(MultipartFile file) throws IOException{
//判断文件是否存在
if(null == file){
throw new FileNotFoundException("文件不存在!");
}
//获得文件名
String fileName = file.getOriginalFilename();
//判断文件是否是excel文件
if(!fileName.endsWith(xls) && !fileName.endsWith(xlsx)){
throw new IOException(fileName + "不是excel文件");
}
}
public static Workbook getWorkBook(MultipartFile file) {
//获得文件名
String fileName = file.getOriginalFilename();
//创建Workbook工作薄对象,表示整个excel
Workbook workbook = null;
try {
//获取excel文件的io流
InputStream is = file.getInputStream();
//根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象
if(fileName.endsWith(xls)){
//2003
workbook = new HSSFWorkbook(is);
}else if(fileName.endsWith(xlsx)){
//2007
workbook = new XSSFWorkbook(is);
}
} catch (IOException e) {
e.printStackTrace();
}
return workbook;
}
public static String getCellValue(Cell cell){
String cellValue = "";
if(cell == null){
return cellValue;
}
//如果当前单元格内容为日期类型,需要特殊处理
String dataFormatString = cell.getCellStyle().getDataFormatString();
if(dataFormatString.equals("m/d/yy")){
cellValue = new SimpleDateFormat(DATE_FORMAT).format(cell.getDateCellValue());
return cellValue;
}
//把数字当成String来读,避免出现1读成1.0的情况
if(cell.getCellType() == Cell.CELL_TYPE_NUMERIC){
cell.setCellType(Cell.CELL_TYPE_STRING);
}
//判断数据的类型
switch (cell.getCellType()){
case Cell.CELL_TYPE_NUMERIC: //数字
cellValue = String.valueOf(cell.getNumericCellValue());
break;
case Cell.CELL_TYPE_STRING: //字符串
cellValue = String.valueOf(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_BOOLEAN: //Boolean
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case Cell.CELL_TYPE_FORMULA: //公式
cellValue = String.valueOf(cell.getCellFormula());
break;
case Cell.CELL_TYPE_BLANK: //空值
cellValue = "";
break;
case Cell.CELL_TYPE_ERROR: //故障
cellValue = "非法字符";
break;
default:
cellValue = "未知类型";
break;
}
return cellValue;
}
}
js正则校验工具类
//获取指定的URL参数值 http://localhost/pages/setmeal_detail.html?id=3&name=jack
function getUrlParam(paraName) {
var url = document.location.toString();
//alert(url);
var arrObj = url.split("?");
if (arrObj.length > 1) {
var arrPara = arrObj[1].split("&");
var arr;
for (var i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split("=");
if (arr != null && arr[0] == paraName) {
return arr[1];
}
}
return "";
}
else {
return "";
}
}
//获得当前日期,返回字符串
function getToday() {
var today = new Date();
var year = today.getFullYear();
var month = today.getMonth() + 1;//0表示1月,1表示2月
var day = today.getDate();
return (year + "-" + month + "-" + day);
}
//获得指定日期后指定天数的日期
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--以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;
}
}
/**
* 身份证号码校验
* 身份证号码为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;
}
}
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; //重置时间
}
}
4.总结
昨天完成了页面的静态,今天要进行具体的预约功能实现了
redis中保存key时最好要遵循下面的规则
-
找唯一且非空的字段
-
表明:主键:主键值:字段名称 字段值
-
项目名:模块名称:业务名称:唯一值 值
-
页面信息的回写,通过网页中传递id来获取对应的对象参数,从而回写
-
手机号校验时引入工具类中的正则方法,输入后点击发送验证码之前会触发正则的校验,如果通过再
30秒倒计时,也是用到工具类,用window提供的定时器每秒触发,同时发送axios请求到后端,后端根据传入的telephone和随机生成的数字,组合起来放入redis中。
预约的日历控件,根据业务控制预约的时间,这里以一个月的日期为准,只能显示当前日期的后面一天到30天的时间去预约。
身份证也是一个正则的校验,也是在提交之前去校验,然后把整体的数据提交到后端,根据后端传来的flag的true or false来判断是否需要成功跳转,或者报错误信息。
接下来是重点的业务了
因为传过来的值是组合起来的,用所拥有的对象没有能完美封装的,所有用map最为好。然后根据手机号和业务值,去redis中拿具体的验证码信息,和输入的验证码信息做对比,先判空,在判是否相等,这样在controller层把这个简单校验完成掉,再去调用service(dubbo注入)的业务会更好。
Service层
- 判断用户选择的预约日期是否在预约设置的范围内(1-30)天
- 检查用户预约的日期是否已经预约满了
- 检查用户是否重复预约(同一用户同一时间同一套餐)
String orderDate = (String) map.get("orderDate");
Date date = DateUtils.parseString2Date(orderDate);
OrderSetting orderSetting= orderSettingDao.findCountByOrderDate(date);
if (orderSetting==null){
return new Result(false, MessageConstant.SELECTED_DATE_CANNOT_ORDER);
}
1.比较简单,根据传入的orderDate去查输入的日期值是否在这个时间范围内,有则继续往下走,没有retrun(前面有写过)
int number = orderSetting.getNumber();//可预约人数
int reservations = orderSetting.getReservations();//已预约人数
if (reservations>=number){
//已经约满,无法预约
return new Result(false,MessageConstant.ORDERSETTING_FAIL);
}
2.上面已经拿到了那一天的对象了,直接调用两个方法,然后对比下即可。
String telephone = (String) map.get("telephone");
Member member = memberDao.findByTelephone(telephone);
if (member!=null){
//判断是否在重复预约
Integer id = member.getId();
Date order_date = DateUtils.parseString2Date(orderDate);
String setmealId = (String) map.get("setmealId");
//调方法查询有没有这个对象,有则报错
Member member1= orderDao.findByCondition(id,order_date,setmealId);
if (member1!=null) {
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.setPhoneNumber(telephone);
member.setRegTime(new Date());
memberDao.add(member);
}
3.因为这里涉及到会员表,需要先根据电话号码去查询具体的member有没有,如果有,再进行进一步的判断里面的id,预约日期和预约的套餐id,如果还是能查到说明有重复预约,没有则没事。前面会员表查询时为了另一个业务,如果该用户还没有注册,就帮该用户注册一下。
//保存信息
Map map1=new HashMap();
map1.put("member_id",member.getId());
map1.put("orderDate",date);
map1.put("orderType",Order.ORDERTYPE_WEIXIN);
map1.put("orderStatus",Order.ORDERSTATUS_NO);
map1.put("setmeal_id",Integer.parseInt((String) map.get("setmealId")));
orderDao.add(map1);
//预约成功,更新当日的已预约人数
orderSetting.setReservations(orderSetting.getReservations()+1);
orderSettingDao.editNumberByOrderDate(orderSetting);
//检查
return new Result(true,MessageConstant.ORDER_SUCCESS,member.getId());
4.帮用户注册后,需要保存到预约表中,这里因为数据库字段和类属性不匹配,就用了map封装,同时需要更新一个当日预约的人数,返回的时候返还id即可。
5.最后是预约成功后的数据回写,因为要涉及到三张表的字段,所以还是不匹配,只能用map封装,然后select语句查出来后记得起别名和前端名字保持一致。