首页 >> 大全

传智健康2.0-8、9-移动端开发/体检预约/手机快速登录、权限控制

2023-11-23 大全 41 作者:考证青年

第8章 移动端开发-体检预约 1. 体检预约流程

用户可以通过如下操作流程进行体检预约:

1、在移动端首页点击体检预约,页面跳转到套餐列表页面

2、在套餐列表页面点击要预约的套餐,页面跳转到套餐详情页面

3、在套餐详情页面点击立即预约,页面跳转到预约页面

4、在预约页面录入体检人信息,包括手机号,点击发送验证码

5、在预约页面录入收到的手机短信验证码,点击提交预约,完成体检预约

2. 体检预约 2.1 页面调整

在预约页面(/pages/.html)进行调整

2.1.1 展示预约的套餐信息

第一步:从请求路径中获取当前套餐的id

<script>var id = getUrlParam("id");//套餐id
</script>

第二步:定义模型数据,用于套餐数据展示

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) => {this.setmeal = response.data.data;});
}

2.1.2 手机号校验

第一步:在页面导入的.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;}
}

第二步:为发送验证码按钮绑定事件

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

2.1.3 30秒倒计时效果

前面在方法中进行了手机号校验,如果校验通过,需要显示30秒倒计时效果

//发送验证码
sendValidateCode(){//获取用户输入的手机号var telephone = this.orderInfo.telephone;//校验手机号输入是否正确if (!checkTelephone(telephone)) {this.$message.error('请输入正确的手机号');return false;}validateCodeButton = $("#validateCodeButton")[0];clock = window.setInterval(doLoop, 1000); //一秒执行一次
}

其中,和clock是在.js文件中定义的变量,是在.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; //重置时间}
}

2.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); //一秒执行一次axios.post("/validateCode/send4Order.do?telephone=" + telephone).then((response) => {if(!response.data.flag){//验证码发送失败this.$message.error('验证码发送失败,请检查手机号输入是否正确');}});
}

创建er,提供方法发送短信验证码,并将验证码保存到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 {@Autowiredprivate 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);//将生成的验证码缓存到redisjedisPool.getResource().setex(telephone + RedisMessageConstant.SENDTYPE_ORDER,5 * 60,code.toString());//验证码发送成功return new Result(true,MessageConstant.SEND_VALIDATECODE_SUCCESS);}
}

2.1.5 日历展示

页面中使用控件来展示日历。根据需求,最多可以提前一个月进行体检预约,所以日历控件只展示未来一个月的日期

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

其中方法定义在.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(){//校验身份证号格式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);}});
}

其中方法是在.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

在工程中创建并提供方法

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 {@Referenceprivate OrderService orderService;@Autowiredprivate JedisPool jedisPool;/*** 体检预约* @param map* @return*/@RequestMapping("/submit")public Result submitOrder(@RequestBody Map map){String telephone = (String) map.get("telephone");//从Redis中获取缓存的验证码,key为手机号+RedisConstant.SENDTYPE_ORDERString 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.2 服务接口

在工程中创建体检预约服务接口并提供预约方法

package com.itheima.service;import com.itheima.entity.Result;
import java.util.Map;
/*** 体检预约服务接口*/
public interface OrderService {//体检预约public Result order(Map map) throws Exception;
}

2.2.3 服务实现类

在der工程中创建体检预约服务实现类并实现体检预约方法。

体检预约方法处理逻辑比较复杂,需要进行如下业务处理:

1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约

2、检查用户所选择的预约日期是否已经约满,如果已经约满则无法预约

3、检查用户是否重复预约(同一个用户在同一天预约了同一个套餐),如果是重复预约则无法完成再次预约

4、检查当前用户是否为会员,如果是会员则直接完成预约,如果不是会员则自动完成注册并进行预约

5、预约成功,更新当日的已预约人数

实现代码如下:

package com.itheima.service;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.dao.SetmealDao;
import com.itheima.entity.Result;
import com.itheima.pojo.Member;
import com.itheima.pojo.Order;
import com.itheima.pojo.OrderSetting;
import com.itheima.pojo.Setmeal;
import com.itheima.utils.DateUtils;
import org.apache.poi.ss.usermodel.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 体检预约服务*/
@Service(interfaceClass = OrderService.class)
@Transactional
public class OrderServiceImpl implements OrderService{@Autowiredprivate OrderSettingDao orderSettingDao;@Autowiredprivate MemberDao memberDao;@Autowiredprivate OrderDao orderDao;//体检预约public Result order(Map map) throws Exception {//检查当前日期是否进行了预约设置String orderDate = (String) map.get("orderDate");Date date = DateUtils.parseString2Date(orderDate);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(reservations >= number){//预约已满,不能预约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();int setmealId = Integer.parseInt((String) map.get("setmealId"));Order order = new Order(memberId,date,null,null,setmealId);List<Order> list = orderDao.findByCondition(order);if(list != null && list.size() > 0){//已经完成了预约,不能重复预约return new Result(false,MessageConstant.HAS_ORDERED);}}//可以预约,设置预约人数加一orderSetting.setReservations(orderSetting.getReservations()+1);orderSettingDao.editReservationsByOrderDate(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.add(member);}//保存预约信息到预约表Order order = new Order(member.getId(),date,(String)map.get("orderType"),Order.ORDERSTATUS_NO,Integer.parseInt((String) map.get("setmealId")));orderDao.add(order);return new Result(true,MessageConstant.ORDER_SUCCESS,order.getId());}
}

2.2.4 Dao接口

package com.itheima.dao;import com.itheima.pojo.OrderSetting;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public interface OrderSettingDao {public void add(OrderSetting orderSetting);//更新可预约人数public void editNumberByOrderDate(OrderSetting orderSetting);//更新已预约人数public void editReservationsByOrderDate(OrderSetting orderSetting);public long findCountByOrderDate(Date orderDate);//根据日期范围查询预约设置信息public List<OrderSetting> getOrderSettingByMonth(Map date);//根据预约日期查询预约设置信息public OrderSetting findByOrderDate(Date orderDate);
}

package com.itheima.dao;import com.github.pagehelper.Page;
import com.itheima.pojo.Member;
import java.util.List;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();
}

package com.itheima.dao;import com.itheima.pojo.Order;
import java.util.List;
import java.util.Map;public interface OrderDao {public void add(Order order);public List<Order> findByCondition(Order order);
}

2.2.5 映射文件

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

.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.dao.MemberDao" ><select id="findAll" resultType="com.itheima.pojo.Member">select * from t_memberselect><select id="selectByCondition" parameterType="string" resultType="com.itheima.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.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><delete id="deleteById" parameterType="int">delete from t_member where id = #{id}delete><select id="findById" parameterType="int" resultType="com.itheima.pojo.Member">select * from t_member where id = #{id}select><select id="findByTelephone" parameterType="string" resultType="com.itheima.pojo.Member">select * from t_member where phoneNumber = #{phoneNumber}select><update id="edit" parameterType="com.itheima.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_memberselect>
mapper>

.xml


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>

3. 预约成功页面展示

前面已经完成了体检预约,预约成功后页面会跳转到成功提示页面(.html)并展示预约的相关信息(体检人、体检套餐、体检时间等)。

3.1 页面调整

提供.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为预约idvar id = getUrlParam("orderId");
</script>
<script>var vue = new Vue({el:'#app',data:{orderInfo:{}},mounted(){axios.post("/order/findById.do?id=" + id).then((response) => {this.orderInfo = response.data.data;});}});
</script>

3.2 后台代码 3.2.1

在中提供方法,根据预约id查询预约相关信息

/*** 根据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);}
}

3.2.2 服务接口

在服务接口中扩展方法

//根据id查询预约信息,包括体检人信息、套餐信息
public Map findById(Integer id) throws Exception;

3.2.3 服务实现类

在服务实现类中实现方法

//根据id查询预约信息,包括体检人信息、套餐信息
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;
}

3.2.4 Dao接口

在接口中扩展方法

public Map findById4Detail(Integer id);

3.2.5 映射文件

在.xml映射文件中提供SQL语句


<select id="findById4Detail" parameterType="int" resultType="map">select m.name member ,s.name setmeal,o.orderDate orderDate,o.orderType orderTypefromt_order o,t_member m,t_setmeal swhere o.member_id=m.id and o.setmeal_id=s.id and o.id=#{id}
select>

第9章 移动端开发-手机快速登录权限控制 1. 需求分析

手机快速登录功能,就是通过短信验证码的方式进行登录。这种方式相对于用户名密码登录方式,用户不需要记忆自己的密码,只需要通过输入手机号并获取验证码就可以完成登录,是目前比较流行的登录方式。

2. 手机快速登录 2.1 页面调整

登录页面为/pages/login.html

2.1.1 发送验证码

为获取验证码按钮绑定事件,并在事件对应的处理函数中校验手机号,如果手机号输入正确则显示30秒倒计时效果并发送ajax请求,发送短信验证码

在er中提供方法,调用短信服务发送验证码并将验证码保存到redis

//手机快速登录时发送手机验证码
@RequestMapping("/send4Login")
public Result send4Login(String telephone){Integer code = ValidateCodeUtils.generateValidateCode(6);//生成6位数字验证码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);//将生成的验证码缓存到redisjedisPool.getResource().setex(telephone+RedisMessageConstant.SENDTYPE_LOGIN,5 * 60,code.toString());//验证码发送成功return new Result(true,MessageConstant.SEND_VALIDATECODE_SUCCESS);
}

2.1.2 提交登录请求

为登录按钮绑定事件


//登录
login(){var telephone = this.loginInfo.telephone;if (!checkTelephone(telephone)) {this.$message.error('请输入正确的手机号');return false;}axios.post("/member/login.do",this.loginInfo).then((response) => {if(response.data.flag){//登录成功,跳转到会员页面window.location.href="member.html";}else{//失败,提示失败信息this.$message.error(response.data.message);}});
}

2.2 后台代码 2.2.1

在工程中创建并提供login方法进行登录检查,处理逻辑为:

1、校验用户输入的短信验证码是否正确,如果验证码错误则登录失败

2、如果验证码正确,则判断当前用户是否为会员,如果不是会员则自动完成会员注册

3、向客户端写入,内容为用户手机号

4、将会员信息保存到Redis,使用手机号作为key,保存时长为30分钟

package com.itheima.controller;
import com.alibaba.fastjson.JSON;
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.service.MemberService;
import com.itheima.utils.JedisUtils;
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 javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Map;
/*** 会员登录*/
@RestController
@RequestMapping("/member")
public class MemberController {@Referenceprivate MemberService memberService;@Autowiredprivate JedisPool jedisPool;//使用手机号和验证码登录@RequestMapping("/login")public Result login(HttpServletResponse response,@RequestBody Map map){String telephone = (String) map.get("telephone");String validateCode = (String) map.get("validateCode");//从Redis中获取缓存的验证码String codeInRedis = jedisPool.getResource().get(telephone+RedisMessageConstant.SENDTYPE_LOGIN);if(codeInRedis == null || !codeInRedis.equals(validateCode)){//验证码输入错误return new Result(false,MessageConstant.VALIDATECODE_ERROR);}else{//验证码输入正确//判断当前用户是否为会员Member member = memberService.findByTelephone(telephone);if(member == null){//当前用户不是会员,自动完成注册member = new Member();member.setPhoneNumber(telephone);member.setRegTime(new Date());memberService.add(member);}//登录成功//写入Cookie,跟踪用户Cookie cookie = new Cookie("login_member_telephone",telephone);cookie.setPath("/");//路径cookie.setMaxAge(60*60*24*30);//有效期30天response.addCookie(cookie);//保存会员信息到Redis中String json = JSON.toJSON(member).toString();jedisPool.getResource().setex(telephone,60*30,json);return new Result(true,MessageConstant.LOGIN_SUCCESS);}}
}

2.2.2 服务接口

在服务接口中提供和add方法

public void add(Member member);
public Member findByTelephone(String telephone);

2.2.3 服务实现类

在服务实现类中实现和add方法

//根据手机号查询会员
public Member findByTelephone(String telephone) {return memberDao.findByTelephone(telephone);
}
//新增会员
public void add(Member member) {if(member.getPassword() != null){member.setPassword(MD5Utils.md5(member.getPassword()));}memberDao.add(member);
}

2.2.4 Dao接口

在接口中声明和add方法

public Member findByTelephone(String telephone);
public void add(Member member);

2.2.5 映射文件

在.xml映射文件中定义SQL语句


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


3. 权限控制 3.1 认证和授权概念

前面我们已经完成了传智健康后台管理系统的部分功能,例如检查项管理、检查组管理、套餐管理、预约设置等。接下来我们需要思考2个问题:

问题1:在生产环境下我们如果不登录后台系统就可以完成这些功能操作吗?

答案显然是否定的,要操作这些功能必须首先登录到系统才可以。

问题2:是不是所有用户,只要登录成功就都可以操作所有功能呢?

答案是否定的,并不是所有的用户都可以操作这些功能。不同的用户可能拥有不同的权限,这就需要进行授权了。

认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。

授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。

本章节就是要对后台系统进行权限控制,其本质就是对用户进行认证和授权。

3.2 权限模块数据模型

前面已经分析了认证和授权的概念,要实现最终的权限控制,需要有一套表结构支撑:

用户表、权限表、角色表、菜单表、用户角色关系表、角色权限关系表、角色菜单关系表。

表之间关系如下图:

通过上图可以看到,权限模块共涉及到7张表。在这7张表中,角色表起到了至关重要的作用,其处于核心位置,因为用户、权限、菜单都和角色是多对多关系。

接下来我们可以分析一下在认证和授权过程中分别会使用到哪些表:

认证过程:只需要用户表就可以了,在用户登录时可以查询用户表进行校验,判断用户输入的用户名和密码是否正确。

授权过程:用户必须完成认证之后才可以进行授权,首先可以根据用户查询其角色,再根据角色查询对应的菜单,这样就确定了用户能够看到哪些菜单。然后再根据用户的角色查询对应的权限,这样就确定了用户拥有哪些权限。所以授权过程会用到上面7张表。

3.3 简介

是 提供的安全认证服务的框架。 使用 可以帮助我们来简化认证和授权的过程。官网:

对应的maven坐标:

org.springframework.securityspring-security-web5.0.5.RELEASE

org.springframework.securityspring-security-config5.0.5.RELEASE

常用的权限框架除了 ,还有的shiro框架。

3.4 入门案例 3.4.1 工程搭建

创建maven工程,打包方式为war,为了方便起见我们可以让入门案例工程依赖,这样相关的依赖都继承过来了。

pom.xml

4.0.0com.itheimaspringsecuritydemo1.0-SNAPSHOTwarspringsecuritydemo Maven Webapphttp://www.example.comUTF-81.81.8com.itheimahealth_interface1.0-SNAPSHOTorg.apache.tomcat.maventomcat7-maven-plugin85/

提供index.html页面,内容为hello !!

3.4.2 配置web.xml

在web.xml中主要配置的和用于整合第三方框架的y,用于整合 。

Archetype Created Web ApplicationspringSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxyspringSecurityFilterChain/*springmvcorg.springframework.web.servlet.DispatcherServletcontextConfigLocationclasspath:spring-security.xml1springmvc*.do

3.4.3 配置-.xml

在-.xml中主要配置 的拦截规则和认证管理器。




智慧体检平台app__智慧体检怎么预约

3.5 对入门案例改进

前面我们已经完成了 的入门案例,通过入门案例我们可以看到, 将我们项目中的所有资源都保护了起来,要访问这些资源必须要完成认证而且需要具有角色。

但是入门案例中的使用方法离我们真实生产环境还差很远,还存在如下一些问题:

1、项目中我们将所有的资源(所有请求URL)都保护起来,实际环境下往往有一些资源不需要认证也可以访问,也就是可以匿名访问。

2、登录页面是由框架生成的,而我们的项目往往会使用自己的登录页面。

3、直接将用户名和密码配置在了配置文件中,而真实生产环境下的用户名和密码往往保存在数据库中。

4、在配置文件中配置的密码使用明文,这非常不安全,而真实生产环境下密码需要进行加密。

本章节需要对这些问题进行改进。

3.5.1 配置可匿名访问的资源

第一步:在项目中创建pages目录,在pages目录中创建a.html和b.html

第二步:在-.xml文件中配置,指定哪些资源可以匿名访问





通过上面的配置可以发现,pages目录下的文件可以在没有认证的情况下任意访问。

3.5.2 使用指定的登录页面

第一步:提供login.html作为项目的登录页面


登录

username:
password:

第二步:修改-.xml文件,指定login.html页面可以匿名访问


第三步:修改-.xml文件,加入表单登录信息的配置



第四步:修改-.xml文件,关闭过滤器



3.5.3 从数据库查询用户信息

如果我们要从数据库动态查询用户信息,就必须按照 框架的要求提供一个实现接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。

实现类代码:

package com.itheima.security;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class UserService implements UserDetailsService {//模拟数据库中的用户数据public  static  Map map = new HashMap<>();static {com.itheima.pojo.User user1 = new com.itheima.pojo.User();user1.setUsername("admin");user1.setPassword("admin");com.itheima.pojo.User user2 = new com.itheima.pojo.User();user2.setUsername("xiaoming");user2.setPassword("1234");map.put(user1.getUsername(),user1);map.put(user2.getUsername(),user2);}/*** 根据用户名加载用户信息* @param username* @return* @throws UsernameNotFoundException*/public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("username:" + username);com.itheima.pojo.User userInDb = map.get(username);//模拟根据用户名查询数据库if(userInDb == null){//根据用户名没有查询到用户return null;}//模拟数据库中的密码,后期需要查询数据库String passwordInDb = "{noop}" + userInDb.getPassword();List list = new ArrayList<>();//授权,后期需要改为查询数据库动态获得用户拥有的权限和角色list.add(new SimpleGrantedAuthority("add"));list.add(new SimpleGrantedAuthority("delete"));list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));UserDetails user = new User(username,passwordInDb,list);return user;}
}

-.xml:




本章节我们提供了实现类,并且按照框架的要求实现了接口。在配置文件中注册,指定其作为认证过程中根据用户名查询用户信息的处理类。当我们进行登录操作时, 框架会调用的方法查询用户信息,并根据此方法中提供的密码和用户页面输入的密码进行比对来实现认证操作。

3.5.4 对密码进行加密

前面我们使用的密码都是明文的,这是非常不安全的。一般情况下用户的密码需要进行加密后再保存到数据库中。

常见的密码加密方式有:

3DES、AES、DES:使用对称加密算法,可以通过解密来还原出原始密码

MD5、SHA1:使用单向HASH算法,无法通过计算还原出原始密码,但是可以建立彩虹表进行查表破解

:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题

加密后的格式一般为:

$2a$10$/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa

加密后字符串的长度为固定的60位。其中:$是分割符,无意义;2a是加密版本号;10是cost的值;而后的前22位是salt值;再然后的字符串就是密码的密文了。

实现步骤:

第一步:在-.xml文件中指定密码加密对象






第二步:修改实现类

package com.itheima.security;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class UserService implements UserDetailsService {@Autowiredprivate BCryptPasswordEncoder passwordEncoder;public  Map map = new HashMap<>();//模拟数据库中的用户数据public void initData(){com.itheima.pojo.User user1 = new com.itheima.pojo.User();user1.setUsername("admin");user1.setPassword(passwordEncoder.encode("admin"));com.itheima.pojo.User user2 = new com.itheima.pojo.User();user2.setUsername("xiaoming");user2.setPassword(passwordEncoder.encode("1234"));map.put(user1.getUsername(),user1);map.put(user2.getUsername(),user2);}/*** 根据用户名加载用户信息* @param username* @return* @throws UsernameNotFoundException*/public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {initData();System.out.println("username:" + username);com.itheima.pojo.User userInDb = map.get(username);//模拟根据用户名查询数据库if(userInDb == null){//根据用户名没有查询到用户return null;}String passwordInDb = userInDb.getPassword();//模拟数据库中的密码,后期需要查询数据库List list = new ArrayList<>();//授权,后期需要改为查询数据库动态获得用户拥有的权限和角色list.add(new SimpleGrantedAuthority("add"));list.add(new SimpleGrantedAuthority("delete"));list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));UserDetails user = new User(username,passwordInDb,list);return user;}
}

3.5.5 配置多种校验规则

为了测试方便,首先在项目中创建a.html、b.html、c.html、d.html几个页面

修改-.xml文件:







3.5.6 注解方式权限控制

除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如中的某个方法要求必须具有某个权限才可以访问,此时就可以使用 框架提供的注解方式进行控制。

实现步骤:

第一步:在-.xml文件中配置组件扫描,用于扫描



第二步:在-.xml文件中开启权限注解支持



第三步:创建类并在的方法上加入注解进行权限控制

package com.itheima.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;@RestController
@RequestMapping("/hello")
public class HelloController {@RequestMapping("/add")@PreAuthorize("hasAuthority('add')")//表示用户必须拥有add权限才能调用当前方法public String add(){System.out.println("add...");return "success";}@RequestMapping("/delete")@PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法public String delete(){System.out.println("delete...");return "success";}
}

3.5.7 退出登录

用户完成登录后 框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。那用户如何退出登录呢?我们可以在-.xml文件中进行如下配置:


通过上面的配置可以发现,如果用户要退出登录,只需要请求/.do这个URL地址就可以,同时会将当前失效,最后页面会跳转到login.html页面。

ccess=“(‘ADMIN’)” />


#### 3.5.6 注解方式权限控制Spring Security除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如Controller中的某个方法要求必须具有某个权限才可以访问,此时就可以使用Spring Security框架提供的注解方式进行控制。实现步骤:第一步:在spring-security.xml文件中配置组件扫描,用于扫描Controller

mvc:-


第二步:在spring-security.xml文件中开启权限注解支持


第三步:创建Controller类并在Controller的方法上加入注解进行权限控制

com..;

org.....;

org..web.bind..;

org..web.bind..;

@

@(“/hello”)

class {

@(“/add”)

@(“(‘add’)”)//表示用户必须拥有add权限才能调用当前方法

add(){

.out.(“add…”);

“”;

@RequestMapping("/delete")
@PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法
public String delete(){System.out.println("delete...");return "success";
}


#### 3.5.7 退出登录用户完成登录后Spring Security框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。那用户如何退出登录呢?我们可以在spring-security.xml文件中进行如下配置:

--url=“/login.html” -=“true”/>


通过上面的配置可以发现,如果用户要退出登录,只需要请求/logout.do这个URL地址就可以,同时会将当前session失效,最后页面会跳转到login.html页面。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了