体检中心
/*
体检中心小结:
前面就是检查项,检查组,套餐的CRUD,图片上传,后面是预约,spring security的权限认证,再就是报表相关和百度地图
*/
该项目在做的时候在数据库操作上有两种选择,一种是dao接口搭配xml映射文件,一种是纯注解方式,本人使用的是纯注解方式,所以以下代码关于dao部分都是注解代码,一般情况下,只需要在引号中写sql语句就可以了,但如果涉及到动态sql的话,简单方式是在引号中加,然后将原本的sql语句写到中间去就可以了,至于一些其它的标签比如SelectKey,具体操作看下面sql,不过一般是
@SelectKey(statement = “select last_insert_id()”, keyProperty = “id”, before = false, resultType = Integer.class)
第一天
重点
//环境搭建
/*
功能架构---SOA Service Oriented Architecture
*/
//element-ui
第二天
/*
1.检查项增加功能
*/
@Insert("insert into t_checkitem(code,name,sex,age,price,type,remark,attention)\n" +
" values\n" +
" (#{code},#{name},#{sex},#{age},#{price},#{type},#{remark},#{attention})")
public void add(CheckItem checkItem);
/*
2.检查项分页查询
分页查询的时候使用分页助手
需要的注意的是条件查询的时候返回的结果不是对象集合,而是Page类的对象
之后再从Page对象获取需要的值,代码如下:
*/
public PageResult pageQuery(QueryPageBean queryPageBean) {
Integer currentPage = queryPageBean.getCurrentPage();
Integer pageSize = queryPageBean.getPageSize();
String queryString = queryPageBean.getQueryString();
//调用分页助手
PageHelper.startPage(currentPage,pageSize);
//调用dao返回查询结果
Page<CheckItem> page = checkItemDao.selectByCondition(queryString);
long total = page.getTotal();
List<CheckItem> rows = page.getResult();
return new PageResult(total,rows);
}
@Select("<script>select * from t_checkitem <if test=\"value !=null and value.length > 0 \">where code = #{value} or name = #{value} </if></script>")
public Page<CheckItem> selectByCondition(String queryString);
/*
3.检查项编辑功能
编辑功能涉及两方面:
一方面:当点击编辑按钮时,显示编辑窗口,把需要的数据回显在窗口卡片上
需要发送3个请求:
1.根据id查询检查组基本信息
2.查询所有检查项信息
3.根据检查组id查询该检查组包含的检查项,并且显示在检查项卡片上
另一方面:点击编辑窗口的确定按钮触发handleEdit()方法,里面的ajax发送形式如下
axios.post("/checkgroup/edit.do?checkitemIds="+this.checkitemIds,this.formData)
.then((res) => {})
.finally(() => {})
注意看这里的checkitemIds参数直接跟在URL后面,表格参数放在逗号后面,在controller里面接收的时候
要注意formdata里的数据要封入的对象需要用@RequestBody修饰,而checkitemIds直接用同名
checkitemIds接收就可以了
*/
/*
4.检查项删除功能
删除检查项的时候得先判断该检查项是否与检查组有关联,如果有则不能删除
代码如下:
*/
//删除之前先判断检查项是否跟检查组相关联
long count = checkItemDao.findCountByCheckItemId(id);
if (count > 0) {
//有关联,不操作
throw new RuntimeException();
}
//无关联
checkItemDao.deleteById(id);
第三天
/*
1.checkGroup检查组的增加
增加检查组的时候,dao层可以用xml配置文件操作数据库,也可以用纯注解方式操作数据库
纯注解方式如下:
*/
@Insert("insert into t_checkgroup(code,name,sex,helpCode,remark,attention)\n" +
" values\n" +
" (#{code},#{name},#{sex},#{helpCode},#{remark},#{attention})")
@SelectKey(statement = "select last_insert_id()", keyProperty = "id", before = false, resultType = Integer.class)
public void add(CheckGroup checkGroup);
/*
2.分页查询
基本思想跟检查项分页一样,查的表不一样而已
*/
/*
3.检查组编辑---包含单个检查组查询,单个检查组查询涉及多对多关系,具体sql看下面
单个检查组查询是在前端页面回显数据时候用到
因为更新检查组涉及到与检查项的关联,所以在更新新的关系之前可以先把旧的关系都删掉,再来存入新的关系
最后更新的时候也涉及到动态语句
*/
@Update("<script>update t_checkgroup\n" +
" <set>\n" +
" <if test=\"name != null\">\n" +
" name = #{name},\n" +
" </if>\n" +
" <if test=\"sex != null\">\n" +
" sex = #{sex},\n" +
" </if>\n" +
" <if test=\"code != null\">\n" +
" code = #{code},\n" +
" </if>\n" +
" <if test=\"helpCode != null\">\n" +
" helpCode = #{helpCode},\n" +
" </if>\n" +
" <if test=\"attention != null\">\n" +
" attention = #{attention},\n" +
" </if>\n" +
" <if test=\"remark != null\">\n" +
" remark = #{remark},\n" +
" </if>\n" +
" </set>\n" +
" where id = #{id}</script>")
public void edit(CheckGroup checkGroup);
@Select("select * from t_checkgroup where id in (select checkgroup_id from t_setmeal_checkgroup where setmeal_id = #{setmeal_id})")
@Results({
@Result(id = true,column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "code",property = "code"),
@Result(column = "helpCode",property = "helpCode"),
@Result(column = "sex",property = "sex"),
@Result(column = "remark",property = "remark"),
@Result(column = "attention",property = "attention"),
@Result(
property = "checkItems",
column = "id",
javaType = List.class,
many = @Many(select = ("com.zz.dao.CheckItemDao.findCheckItemById"))
)
})
public List<CheckGroup> findCheckGroupById(int id);
第四天
//文件上传成功后的钩子,response为服务端返回的值,file为当前上传的文件封装成的js对象
handleAvatarSuccess(response, file) {
//上传成功后给imageUrl赋值
this.imageUrl = "http://q4754trxz.bkt.clouddn.com/" + response.data;
//弹出消息
this.$message({
type:response.flag ? 'success':'error',
message:response.message
});
this.formData.img = response.data;
}
//这里注意发送请求不是我们平时用的ajax,所有返回的response跟我们之前返回的值不同,不用res.data.data获取数据,直接response.data或者response.flag获取数据
//另外,这里的this.formData.img这个img属性不是在表格中与表格中的填空项绑定的,而是图片上传完成后返回的一个图片名,再作为一个参数跟随表格数据一起发送到controller里面,从这里可以看出,vue里的变量可以先定义一个变量名,比如formData:{},但是里面的属性名可以不在VUE的data变量里定义,可以在表格里赋予,也可以在方法里赋予
第五天
//套餐分页查询,操作大同小异
/*清理多余的图片
运用redis把上传成功的照片和存入数据库成功的图片名字存入redis,
视频中讲的是创建两个集合,一个PIC,一个DBPIC
但是实际上,只用一个集合也可以,并且理论上更加高效,不用对两个集合操作,也不用取两个集合差集,
只需要在点确定的时候把一个集合中的该图片名字删除,集合中剩下的就是需要清理的图片,接下来把清理交给定时
工具就可以了
*/
第六天
//使用POI对Excel文件读和写
/*
注意点:
1.点击下载按钮访问模板的请求路径
downloadTemplate(){
window.location.href="../../template/ordersetting_template.xlsx";
},
这里注意路径往前面退两层,即../../
2.预约数据的展示在完成serviceImpl功能时注意
根据月份查询对应的预约设置数据
public List<Map> getOrderSettingByMonth(String date) {//格式:yyyy-MM
String begin = date + "-1";//2019-6-1
String end = date + "-31";//2019-6-31
Map<String,String> map = new HashMap<>();
map.put("begin",begin);
map.put("end",end);
//调用DAO,根据日期范围查询预约设置数据
List<OrderSetting> list = orderSettingDao.getOrderSettingByMonth(map);
List<Map> result = new ArrayList<>();
if(list != null && list.size() > 0){
for (OrderSetting orderSetting : list) {
Map<String,Object> m = new HashMap<>();
m.put("date",orderSetting.getOrderDate().getDate());//获取日期数字(几号)
m.put("number",orderSetting.getNumber());
m.put("reservations",orderSetting.getReservations());
result.add(m);
}
}
return result;
}
这里结果map的date的值注入的时候,注意调用的是Date类的getDate()方法,可以获取日期数字
*/
第七天
前端判断无法文件类型问题
const isXLSX = file.type === 'application/vnd.openxmlformats- officedocument.spreadsheetml.sheet';
// return true;
if (isXLSX) {
return true;
}
//预约人数设置
/*
在进行预约设置的相关操作时,涉及到一个日期控件的使用,该控件会在页面一加载的时候进行日期初始化,
该方法是initData(cur),一开始cur是没有值的,我们调用的时候可以给它赋值,没赋值它自己内部会new 一个 日期出来,最后会查询出一个月的数据来
另外,在进行预约人数设置的时候,ajax请求会发送两个参数,一个是我们输入的预约人数值,一个是当前的日期,
可以看看这个弹出小窗口的相关代码
this.$prompt('请输入可预约人数', '预约设置', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[0-9]*[1-9][0-9]*$/,
inputErrorMessage: '只能输入正整数'
}).then(({ value }) => {
//发送ajax请求,将输入的数据提交到Controller
axios.post("/ordersetting/editNumberByDate.do",{
number:value,
orderDate:this.formatDate(day.getFullYear(),day.getMonth()+1,day.getDate()) //日期
}).then((res) => {
if(res.data.flag){
this.initData(this.formatDate(day.getFullYear(), day.getMonth() + 1, 1));
this.$message({
type:'success',
message:res.data.message
});
}else{
this.$message.error(res.data.message);
}
});
}).catch(() => {
this.$message({
type: 'info',
message: '取消输入'
});
});
*/
//移动端后台搭建+套餐展示
第八天
//体检套餐详情页
/*
前端部分:
healthmobile.js中的一个方法,用来获取url中的参数
--------------------------------------
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 "";
}
}
-----------------------------------
查询套餐详情的时候,案例用的是映射文件形式,自己用的是注解形式,这里把两种都写在这里,以作对比
*/
映射文件形式
---------------------SetmealDao.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.SetmealDao">
<resultMap id="baseResultMap" type="com.itheima.pojo.Setmeal">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="code" property="code"/>
<result column="helpCode" property="helpCode"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="price" property="price"/>
<result column="remark" property="remark"/>
<result column="attention" property="attention"/>
<result column="img" property="img"/>
</resultMap>
<resultMap id="findByIdResultMap" type="com.itheima.pojo.Setmeal" extends="baseResultMap">
<!--多对多映射-->
<collection
property="checkGroups"
ofType="com.itheima.pojo.CheckGroup"
select="com.itheima.dao.CheckGroupDao.findCheckGroupById"
column="id"
>
</collection>
</resultMap>
<!--根据套餐ID查询套餐详情(包含套餐基本信息、检查组信息、检查项信息)-->
<select id="findById" parameterType="int" resultMap="findByIdResultMap">
select * from t_setmeal where id = #{id}
</select>
</mapper>
------------------------------------CheckGroupDao.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.CheckGroupDao">
<resultMap id="baseResultMap" type="com.itheima.pojo.CheckGroup">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="code" property="code"/>
<result column="helpCode" property="helpCode"/>
<result column="sex" property="sex"/>
<result column="remark" property="remark"/>
<result column="attention" property="attention"/>
</resultMap>
<resultMap id="findByIdResultMap" type="com.itheima.pojo.CheckGroup" extends="baseResultMap">
<!--检查组和检查项多对多关联查询-->
<collection property="checkItems"
ofType="com.itheima.pojo.CheckItem"
column="id"
select="com.itheima.dao.CheckItemDao.findCheckItemById"
></collection>
</resultMap>
<!--根据套餐ID查询关联的检查组详情-->
<select id="findCheckGroupById" parameterType="int" resultMap="findByIdResultMap">
select * from t_checkgroup where id in (select checkgroup_id from t_setmeal_checkgroup where setmeal_id = #{setmeal_id})
</select>
</mapper>
------------------------------CheckItemDao.xml文件---------------------------
<select id="findCheckItemById" parameterType="int" resultType="com.itheima.pojo.CheckItem">
select * from t_checkitem
where id
in (select checkitem_id from t_checkgroup_checkitem where checkgroup_id=#{id})
</select>
注解形式
@Select("select * from t_setmeal where id = #{id}")
@Results({
@Result(id = true,column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "code",property = "code"),
@Result(column = "helpCode",property = "helpCode"),
@Result(column = "sex",property = "sex"),
@Result(column = "age",property = "age"),
@Result(column = "price",property = "price"),
@Result(column = "remark",property = "remark"),
@Result(column = "attention",property = "attention"),
@Result(column = "img",property = "img"),
@Result(
property = "checkGroups",
column = "id",
javaType = List.class,
many = @Many(select = ("com.zz.dao.CheckGroupDao.findCheckGroupById"))
)
})
public Setmeal findById(int id);
--------------------------------------------------------------------------------
@Select("select * from t_checkgroup where id in (select checkgroup_id from t_setmeal_checkgroup where setmeal_id = #{setmeal_id})")
@Results({
@Result(id = true,column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "code",property = "code"),
@Result(column = "helpCode",property = "helpCode"),
@Result(column = "sex",property = "sex"),
@Result(column = "remark",property = "remark"),
@Result(column = "attention",property = "attention"),
@Result(
property = "checkItems",
column = "id",
javaType = List.class,
many = @Many(select = ("com.zz.dao.CheckItemDao.findCheckItemById"))
)
})
public List<CheckGroup> findCheckGroupById(int id);
--------------------------------------------------------------------------
@Select("select * from t_checkitem\n" +
" where id\n" +
" \t in (select checkitem_id from t_checkgroup_checkitem where checkgroup_id=#{id})")
public List<CheckItem> findCheckItemById(int id);
//短信发送
//注册----> 创建短信签名---> 创建短信模板---->创建accessKey ----> 复制工具类----> 调用工具类的发短信方法完成短信发送.
第九天
实现体检预约的前台页面效果
//完成orderInfo.html的页面
--------------------------实现点击发送验证码后倒计时30秒效果-----------------------------
-----先检查手机号格式是否正确
-----再来实现倒计时效果
---先将jquery对象转换为dom对象----------------------------
|var doc2=$("#idDoc2")[0]; //转换jQuery对象为DOM对象 |
|doc2.innerHTML="这是jQuery的第一个DOM对象" |
|//使用jQuery对象本身提供的get函数来返回指定集合位置的DOM对象 |
|var doc2=$("#idDoc2").get(0); |
|doc2.innerHTML="这是jQuery的第二个DOM对象" |
-------------------------------------------------------
//在按钮上显示30秒倒计时效果
validateCodeButton = $("#validateCodeButton")[0];//锁定dom对象
clock = window.setInterval(doLoop,1000);//定时器方法,可以实现每隔指定的时间调用指定的方法
//发送ajax请求,为用户发送手机验证码
axios.post("/validateCode/send4Order.do?telephone=" + telephone).then((res) => {
if(!res.data.flag){
//短信验证码发送失败
this.$message.error(res.data.message);
}
});
},
-------------可以看看doLoop里面定义的方法-----------------------------------
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; //重置时间
}
}
---------------------下面看看选择时间的时候的那个日期控件------------------------
<input v-model="orderInfo.orderDate" type="text" class="picktime" readonly>
上面的class注意是picktime
---再在下面定义日期控件的初始化
<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>
关于发送预约成功短信无法发送的问题
//调用工具类SMSUtils的时候,最后会有一个sendSmsResponse,用它获得获得短信返回值状态,
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
// System.out.println(sendSmsResponse.getCode());
if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
// 请求成功
System.out.println("请求成功");
}
//发现这里获得的状态是 isv.SMS_SIGNATURE_SCENE_ILLEGAL
/*
原因是 我的签名创建时选择的是验证码,所以只能与验证码的模板一起使用,当与短信通知或者其他模板一起使用时,就会报错。而申请通用签名,则必须要上传一些公司、APP、或者其他证件资料,比较麻烦,有需要的可以了解一下
还有别想着用验证码接口,给code变量传你想要的内容,短信接口调用时,做了一系列的验证,比如不能传中文,不能传太长的内容,不能发送频繁,等等。反正验证码接口最终发送的内容就是几个数字或几个字母,别想着发送一句话啥的。
比如 发送 尊敬的某某女士,今天是520 ,最终只能用 zunjing_de_moumou_发送一条,jintian_shi_520,发送一条,比较尴尬,想推送消息,还是要申请通用的签名和模板
*/
第十天
体检预约的后台代码
//OrderController代码
@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");
//将用户输入的验证码和Redis中保存的验证码进行比对
if(validateCodeInRedis != null && validateCode != null && validateCode.equals(validateCodeInRedis)){
//如果比对成功,调用服务完成预约业务处理
map.put("orderType", Order.ORDERTYPE_WEIXIN);//设置预约类型,分为微信预约、电话预约
Result result = null;
try{
result = orderService.order(map);//通过Dubbo远程调用服务实现在线预约业务处理
}catch (Exception e){
e.printStackTrace();
return result;
}
if(result.isFlag()){
//预约成功,可以为用户发送短信
try{
SMSUtils.sendShortMessage(SMSUtils.ORDER_NOTICE,telephone,(String) map.get("orderDate"));
}catch (Exception e){
e.printStackTrace();
}
}
return result;
}else{
//如果比对不成功,返回结果给页面
return new Result(false, MessageConstant.VALIDATECODE_ERROR);
}
}
//OrderServiceImpl的代码-------比较复杂
/*
先理清步骤:
1.先判断当前预约日期是否有设置过预约设置,否则无法预约
2.判断当前预约日期的orderSetting的可预约人数是否小于等于已预约人数,是就无法预约
3.判断当前预约客户是否为会员,
如果是,判断该会员是否重复预约,是就无法在此预约
如果不是,自动把当前客户存入会员表
4.在订单表新增一条订单表,并在预约设置更新预约人数
*/
@Service(interfaceClass = OrderService.class)
@Transactional
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderSettingDao orderSettingDao;
@Autowired
private MemberDao memberDao;
@Autowired
private OrderDao orderDao;
//体检预约
public Result order(Map map) throws Exception{
//1、检查用户所选择的预约日期是否已经提前进行了预约设置,如果没有设置则无法进行预约
String orderDate = (String) map.get("orderDate");//预约日期
OrderSetting orderSetting= orderSettingDao.findByOrderDate(DateUtils.parseString2Date(orderDate));
if(orderSetting == null){
//指定日期没有进行预约设置,无法完成体检预约
return new Result(false, MessageConstant.SELECTED_DATE_CANNOT_ORDER);
}
//2、检查用户所选择的预约日期是否已经约满,如果已经约满则无法预约
int number = orderSetting.getNumber();//可预约人数
int reservations = orderSetting.getReservations();//已预约人数
if(reservations >= number){
//已经约满,无法预约
return new Result(false,MessageConstant.ORDER_FULL);
}
//3、检查当前用户是否为会员,如果是会员检查用户是否重复预约(同一个用户在同一天预约了同一个套 餐),如果是重复预约则无法完成再次预约
String telephone = (String) map.get("telephone");//获取用户页面输入的手机号
Member member = memberDao.findByTelephone(telephone);
if(member != null){
//判断是否在重复预约
Integer memberId = member.getId();//会员ID
Date order_Date = DateUtils.parseString2Date(orderDate);//预约日期
String setmealId = (String) map.get("setmealId");//套餐ID
Order order = new Order(memberId, order_Date, Integer.parseInt(setmealId));
//根据条件进行查询
List<Order> list = orderDao.findByCondition(order);
if(list != null && list.size() > 0){
//说明用户在重复预约,无法完成再次预约
return new Result(false,MessageConstant.HAS_ORDERED);
}
}else{
//4、如果不是会员则自动完成注册
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);//自动完成会员注册
}
//5、进行预约,预约成功,更新当日的已预约人数
Order order = new Order();
order.setMemberId(member.getId());//设置会员ID
order.setOrderDate(DateUtils.parseString2Date(orderDate));//预约日期
order.setOrderType((String) map.get("orderType"));//预约类型
order.setOrderStatus(Order.ORDERSTATUS_NO);//到诊状态
order.setSetmealId(Integer.parseInt((String) map.get("setmealId")));//套餐ID
orderDao.add(order);
orderSetting.setReservations(orderSetting.getReservations() + 1);//设置已预约人数+1
orderSettingDao.editReservationsByOrderDate(orderSetting);
return new Result(true,MessageConstant.ORDER_SUCCESS,order.getId());//这里返回order 的id,因为前端只要id就可以了
}
十一天
/*登录
这里在前台发送请求过来后,在controller需要完成以下步骤
1.从redis中取验证码和传过来的验证码做对比
2.判断是否是会员,如果不是,直接存入会员表里
3.把电话号码信息存入cookie中
4.把会员信息存入redis,key是telephone
不过这里要注意,这里只是存入了cookie,目前这个案例还没有取cookie,cookie的作用是把cookie带过来之后
取出号码再从redis中取member信息,判断是否最近登录过
*/
@RestController
@RequestMapping("/member")
public class MemberController {
@Autowired
private JedisPool jedisPool;
@Reference
private MemberService memberService;
//手机号快速登录
@RequestMapping("/login")
public Result login(HttpServletResponse response, @RequestBody Map map){
String telephone = (String) map.get("telephone");
String validateCode = (String) map.get("validateCode");
//从Redis中获取保存的验证码
String validateCodeInRedis = jedisPool.getResource().get(telephone + RedisMessageConstant.SENDTYPE_LOGIN);
if(validateCodeInRedis != null && validateCode != null && validateCode.equals(validateCodeInRedis)){
//验证码输入正确
//判断当前用户是否为会员(查询会员表来确定)
Member member = memberService.findByTelephone(telephone);
if(member == null){
//不是会员,自动完成注册(自动将当前用户信息保存到会员表),这里注意这里的member是null,需要new一个出来
member = new Member();
member.setRegTime(new Date());
member.setPhoneNumber(telephone);
memberService.add(member);
}
//向客户端浏览器写入Cookie,内容为手机号
Cookie cookie = new Cookie("login_member_telephone",telephone);
cookie.setPath("/");//这里意味着只要访问这个项目地址就把这个cookie带上,如果是“/page”说明只有在访问“/localhost:port/page/**”下的页面时候才带上这个cookie
cookie.setMaxAge(60*60*24*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);
}else{
//验证码输入错误
return new Result(false, MessageConstant.VALIDATECODE_ERROR);
}
}
}
---------------------memberServiceImpl--------------------------------------
//通过手机号查找会员信息
public Member findByTelephone(String telephone) {
return memberDao.findByTelephone(telephone);
}
//保存会员信息,这里要保证严谨,因为不知道前面是否存入过密码
public void add(Member member) {
String password = member.getPassword();
if(password != null){
//使用md5将明文密码进行加密
password = MD5Utils.md5(password);
member.setPassword(password);
}
memberDao.add(member);
}
十二天
Spring Security权限控制
第一步,引入坐标
<!-- 权限控制-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
第二步,配置spring-security.xml文件
<!--配置哪些资源匿名可以访问(不登录也可以访问)-->
<security:http security="none" pattern="/login.html"></security:http>
<security:http security="none" pattern="/css/**"></security:http>
<security:http security="none" pattern="/img/**"></security:http>
<security:http security="none" pattern="/js/**"></security:http>
<security:http security="none" pattern="/plugins/**"></security:http>
<!--
auto-config:自动配置,如果设置为true,表示自动应用一些默认配置,比如框架会提供一个默认的登录页 面
use-expressions:是否使用spring security提供的表达式来描述权限
-->
<security:http auto-config="true" use-expressions="true">
<security:headers>
<!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
<security:frame-options policy="SAMEORIGIN"></security:frame-options>
</security:headers>
<!--配置拦截规则,/** 表示拦截所有请求-->
<!--
pattern:描述拦截规则
asscess:指定所需的访问角色或者访问权限
-->
<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/pages/**" access="isAuthenticated()" />
<!--如果我们要使用自己指定的页面作为登录页面,必须配置登录表单.页面提交的登录表单请求是由框架负责 处理-->
<!--
login-page:指定登录页面访问URL
-->
<!--
login-processing-url="/login.do"
一般这个配置完才去登录页面设置action的地址,两者必须是一样的,即登录页面把登录表单数据提交到 action的地址之后,就会被拦截
-->
<security:form-login
login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/pages/main.html"
authentication-failure-url="/login.html"></security:form-login>
<!--
csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用 (403)
因为在默认登录页面上其实有一个隐藏域,提交表单的时候会把隐藏域中的csrf对应的一串值带过去,代表 这个页面是官方自带的,是安全的,而自己设置的登录页面没有这个值,security认为不安全,所以必须关 闭
-->
<security:csrf disabled="true"></security:csrf>
<!--
logout:退出登录
logout-url:退出登录操作对应的请求路径
logout-success-url:退出登录后的跳转页面
-->
<security:logout logout-url="/logout.do"
logout-success-url="/login.html" invalidate-session="true"/>
</security:http>
<!--配置认证管理器-->
<security:authentication-manager>
<!--配置认证提供者-->
<security:authentication-provider user-service-ref="springSecurityUserService">
<!--
配置一个具体的用户,后期需要从数据库查询用户
<security:user-service>
<security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
</security:user-service>
-->
<!--指定度密码进行加密的对象-->
<security:password-encoder ref="passwordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
<!--配置密码加密对象-->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<!-- 如果想要注解方式,需要在这里配置如下参数,但在这个项目,都可以省略,原因看解释方块
<context:annotation-config></context:annotation-config>
<mvc:annotation-driven></mvc:annotation-driven>
<context:component-scan base-package="com.itheima.controller"> </context:component-scan>
-->
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
解释方块
<!--下面这个注解是 开启spring注解使用-->
<context:annotation-config></context:annotation-config>
<!--这个注解是 会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两 个Bean,mvc三大组件中除视图解析器(InternalResourceViewResolver)的另外两大组件-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--这是配置扫描包,配置这个的时候,可将<context:annotation-config/>省去-->
<context:component-scan base-package="com.itheima.controller"> </context:component-scan>
<!--之所以在这个xml配置文件中上面这些参数都不用配置,是因为在springmvc.xml这个配置文件有代替这 些参数的配置。
springmvc.xml这个配置文件是配置dubbo相关设置的,里面有如下配置:
1.<mvc:annotation-driven></mvc:annotation-driven>
2.<dubbo:annotation package="com.zz" />批量扫描,代替<context:component-scan>
然后上面也说了,配置了扫描包设置就可以省略<context:annotation-config/>
所以上面这些参数设置在spring-security.xml中都可以省略
-->
第三步,配置web.xml文件
<!--委派过滤器,用于整合其他框架-->
<filter>
<!--整合spring security时,此过滤器的名称固定springSecurityFilterChain-->
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--这里省略了很多,比如解决post乱码问题的filter还有前端控制器,前端控制器加载resources里面的配置文件-->
第四步,使用数据库中用户信息进行权限控制,创建SpringSecurityUserService.java类,实现UserDetailsService接口,将这个类做为认证提供者
@Component
/*这里配置了Component之后,spring-security.xml文件中就不用配置<bean id="springSecurityUserService" class="com.zz.service.SpringSecurityUserService"></bean>
虽然xml文件中报红,但是实际没有问题
*/
public class SpringSecurityUserService implements UserDetailsService {
//使用dubbo通过网络远程调用服务提供方获取数据库中的用户信息
@Reference
private UserService userService;
//根据用户名查询数据库获取用户信息
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user == null){
//用户名不存在
return null;
}
List<GrantedAuthority> list = new ArrayList<>();
//动态为当前用户授权
Set<Role> roles = user.getRoles();
for (Role role : roles) {
//遍历角色集合,为用户授予角色
list.add(new SimpleGrantedAuthority(role.getKeyword()));
Set<Permission> permissions = role.getPermissions();
for (Permission permission : permissions) {
//遍历权限集合,为用户授权
list.add(new SimpleGrantedAuthority(permission.getKeyword()));
}
}
org.springframework.security.core.userdetails.User securityUser =
new org.springframework.security.core.userdetails.User(username,user.getPassword(),list);
return securityUser;
}
}
十三天
第五步,方法的权限控制
//删除检查项
@PreAuthorize("hasAuthority('CHECKITEM_DELETE')")//权限校验
@RequestMapping("/delete")
public Result delete(Integer id){
try{
checkItemService.deleteById(id);
}catch (Exception e){
e.printStackTrace();
//服务调用失败
return new Result(false, MessageConstant.DELETE_CHECKITEM_FAIL);
}
return new Result(true, MessageConstant.DELETE_CHECKITEM_SUCCESS);
}
退出登录
<!--前台-->
<el-dropdown-item divided>
<span style="display:block;">
<a href="/logout.do">退出</a>
</span>
</el-dropdown-item>
十四天&十五天
<!-- -->
<!--
这两天主要用百度的echart报表工具制作报表
使用步骤如下:
1.导入echart.js -->
<script src="../plugins/echarts/echarts.js"></script>
<!-- 2.把需要用到的报表图从官网copy代码过来,在自己的页面使用,具体如下使用
为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data:['销量']
},
xAxis: {
data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
报表的使用很简单,有难度的是把数据库中的数据按照需要查出来并封装,这里最常用到的是list集合以及map集合
不同json格式对应不同封装数据方式,返回不同的数据类型
ReportController
@RestController
@RequestMapping("/report")
public class ReportController {
@Reference
private MemberService memberService;
@Reference
private SetmealService setmealService;
/*
* {
* months:["xx","xx","xx"],
* memberCount:[xx,xx,xx]
* }
*
* */
//会员数量折线图数据
@RequestMapping("/getMemberReport")
public Result getMemberReport(){
Map<String,Object> map = new HashMap<>();
List<String> months = new ArrayList();
Calendar calendar = Calendar.getInstance();//获得日历对象,模拟时间就是当前时间
//计算过去一年的12个月
calendar.add(Calendar.MONTH,-12);//获得当前时间往前推12个月的时间
for(int i=0;i<12;i++){
calendar.add(Calendar.MONTH,1);//获得当前时间往后推一个月日期
Date date = calendar.getTime();
months.add(new SimpleDateFormat("yyyy.MM").format(date));
}
map.put("months",months);
List<Integer> memberCount = memberService.findMemberCountByMonths(months);
map.put("memberCount",memberCount);
return new Result(true, MessageConstant.GET_MEMBER_NUMBER_REPORT_SUCCESS,map);
}
//套餐预约占比饼形图
@RequestMapping("/getSetmealReport")
public Result getSetmealReport(){
//使用模拟数据测试使用什么样的java对象转为饼形图所需的json数据格式
/*
* {
* setmealCount:[
* {name:"",value:xx},
* {name:"",value:xx}
* ],
* setmealNames:["xx","xx","xx"]
* }
* */
Map<String,Object> data = new HashMap<>();
/*List<String> setmealNames = new ArrayList<>();
setmealNames.add("体检套餐");
setmealNames.add("孕前检查套餐");
data.put("setmealNames",setmealNames);*/
try{
List<Map<String,Object>> setmealCount = setmealService.findSetmealCount();
data.put("setmealCount",setmealCount);
List<String> setmealNames = new ArrayList<>();
for (Map<String, Object> map : setmealCount) {
String name = (String) map.get("name");//套餐名称
setmealNames.add(name);
}
data.put("setmealNames",setmealNames);
return new Result(true,MessageConstant.GET_SETMEAL_COUNT_REPORT_SUCCESS,data);
}catch (Exception e){
e.printStackTrace();
return new Result(false,MessageConstant.GET_SETMEAL_COUNT_REPORT_FAIL);
}
}
@Reference
private ReportService reportService;
//运营数据统计
@RequestMapping("/getBusinessReportData")
public Result getBusinessReportData(){
try{
Map<String,Object> data = reportService.getBusinessReportData();
return new Result(true,MessageConstant.GET_BUSINESS_REPORT_SUCCESS,data);
}catch (Exception e){
return new Result(false,MessageConstant.GET_BUSINESS_REPORT_FAIL);
}
}
//导出运营数据
@RequestMapping("/exportBusinessReport")
public Result exportBusinessReport(HttpServletRequest request, HttpServletResponse response){
try{
Map<String,Object> result = reportService.getBusinessReportData();
//取出返回结果数据,准备将报表数据写入到Excel文件中
String reportDate = (String) result.get("reportDate");
Integer todayNewMember = (Integer) result.get("todayNewMember");
Integer totalMember = (Integer) result.get("totalMember");
Integer thisWeekNewMember = (Integer) result.get("thisWeekNewMember");
Integer thisMonthNewMember = (Integer) result.get("thisMonthNewMember");
Integer todayOrderNumber = (Integer) result.get("todayOrderNumber");
Integer thisWeekOrderNumber = (Integer) result.get("thisWeekOrderNumber");
Integer thisMonthOrderNumber = (Integer) result.get("thisMonthOrderNumber");
Integer todayVisitsNumber = (Integer) result.get("todayVisitsNumber");
Integer thisWeekVisitsNumber = (Integer) result.get("thisWeekVisitsNumber");
Integer thisMonthVisitsNumber = (Integer) result.get("thisMonthVisitsNumber");
List<Map> hotSetmeal = (List<Map>) result.get("hotSetmeal");
/*
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| 这里怎么样获得文件全路径要记得方法 |
| |
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
*/
String filePath = request.getSession().getServletContext().getRealPath("template")+ File.separator+"report_template.xlsx";
//基于提供的Excel模板文件在内存中创建一个Excel表格对象
XSSFWorkbook excel = new XSSFWorkbook(new FileInputStream(new File(filePath)));
//读取第一个工作表
XSSFSheet sheet = excel.getSheetAt(0);
XSSFRow row = sheet.getRow(2);
row.getCell(5).setCellValue(reportDate);//日期
row = sheet.getRow(4);
row.getCell(5).setCellValue(todayNewMember);//新增会员数(本日)
row.getCell(7).setCellValue(totalMember);//总会员数
row = sheet.getRow(5);
row.getCell(5).setCellValue(thisWeekNewMember);//本周新增会员数
row.getCell(7).setCellValue(thisMonthNewMember);//本月新增会员数
row = sheet.getRow(7);
row.getCell(5).setCellValue(todayOrderNumber);//今日预约数
row.getCell(7).setCellValue(todayVisitsNumber);//今日到诊数
row = sheet.getRow(8);
row.getCell(5).setCellValue(thisWeekOrderNumber);//本周预约数
row.getCell(7).setCellValue(thisWeekVisitsNumber);//本周到诊数
row = sheet.getRow(9);
row.getCell(5).setCellValue(thisMonthOrderNumber);//本月预约数
row.getCell(7).setCellValue(thisMonthVisitsNumber);//本月到诊数
int rowNum = 12;
for(Map map : hotSetmeal){//热门套餐
String name = (String) map.get("name");
Long setmeal_count = (Long) map.get("setmeal_count");
BigDecimal proportion = (BigDecimal) map.get("proportion");
row = sheet.getRow(rowNum ++);
row.getCell(4).setCellValue(name);//套餐名称
row.getCell(5).setCellValue(setmeal_count);//预约数量
row.getCell(6).setCellValue(proportion.doubleValue());//占比
}
//使用输出流进行表格下载,基于浏览器作为客户端下载
/*
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| 如果直接用OutputStream代表着只是在服务器的磁盘上写文件,|
| 要想把输出流写回浏览器,就需要用response获得输出流, |
| 并且要记得设置ContentType和Header类型 |
| 然后用excel.write(输出流)就可把流文件写回去 |
| 关闭各种流不要忘了 |
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
*/
OutputStream out = response.getOutputStream();
response.setContentType("application/vnd.ms-excel");//代表的是Excel文件类型
response.setHeader("content-Disposition", "attachment;filename=report.xlsx");//指定以附件形式进行下载
excel.write(out);
out.flush();
out.close();
excel.close();
return null;
}catch (Exception e){
return new Result(false,MessageConstant.GET_BUSINESS_REPORT_FAIL);
}
}
}
dao文件的sql编写,很值得学习
<!--热门套餐查询仔细看看-->
@Select("select s.name, count(o.id) setmeal_count ,count(o.id)/(select count(id) from t_order) proportion\n" +
" from t_order o inner join t_setmeal s on s.id = o.setmeal_id\n" +
" group by o.setmeal_id\n" +
" order by setmeal_count desc limit 0,4")
public List<Map> findHotSetmeal();
自己模块阶段bug
百度地图模块知识点
//如何在html中嵌入地图其实很简单
/*
1.先去百度地图申请好密匙,要记得先认证好个人开发者
2.引入百度地图apiwenjian
<script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=您的密钥"> </script>
//给地图设置样式
<style>
#myMap{
height: 450px;
width: 100%;
overflow: hidden;
font-family: "微软雅黑";
border:1px solid green;
}
</style>
3.创建地图容器元素
<div id="myMap">hello</div>
4.定义一个初始化地图的方法
baiduMap() {
var map = new BMap.Map("myMap");//创建地图实例
//var point = new BMap.Point(114.31, 30.52);
//map.centerAndZoom(point, 15);另一种地图初始化方法
map.centerAndZoom("武汉市",11);//地图初始化,同时设置地图展示级别
map.enableScrollWheelZoom(true); //开启滑轮
var point = new BMap.Point(114.628,30.875);
var marker = new BMap.Marker(point); // 创建标注
map.addOverlay(marker); // 将标注添加到地图中
marker.setAnimation(BMAP_ANIMATION_BOUNCE); //跳动的动画
//给标注点加上文字
var label = new BMap.Label("好地方哦",{offset:new BMap.Size(20,-10)});
marker.setLabel(label);
}
5.加载页面的时候调用这个方法即可
*/
实现从数据库查找地址并且在地图上显示标注点
<!--
分析:要想从数据库拿数据并显示,说明要在初始化页面的时候就要发请求,并且在拿到了数据后才在地图上显示, 这里就要求这个请求是同步发送的,不然还没拿到数据地图已经初始化完成了,标注点也没有显示出来。
所以,使用$.ajax来发送请求,可以实现同步,而axios只能是异步发送
-->
<body onload="init()">
<div id="l-map"></div>
</body>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("l-map");
// var point = new BMap.Point(114.4060230, 30.7123300);
// map.centerAndZoom(point,10);
map.centerAndZoom("武汉市",11);
//根据浏览器定位当前位置
function init() {
load();
}
//加载分店位置
function load() {
map.enableScrollWheelZoom(true);
var index = 0;
var myGeo = new BMap.Geocoder();
var adds = [];
var addNames=[];
$.ajax({
type:"POST",
url:"/address/findAllMaps.do",
// data:JSON.stringify(param),
async: false,
contentType:"application/json;charset=utf-8",
success: function (data) {
// data = res.rows;
// data = res;
for(var x=0;x<data.length;x++){
adds.push(new BMap.Point(data[x].lng,data[x].lat));
addNames.push(data[x].address);
}
}
});
for(var i = 0; i<adds.length; i++){
var marker = new BMap.Marker(adds[i]);
map.addOverlay(marker);
// marker.setAnimation(BMAP_ANIMATION_BOUNCE); //跳动的动画
marker.setLabel(new BMap.Label(addNames[i],{offset:new BMap.Size(20,-10)}));
}
}
</script>
百度地图关键字提示
<div class="filter-container">
<el-input id="suggestId" name="address_detail" placeholder="请输入要查询的地址" v- model="address_detail" style="width: 400px;"></el-input>
</div>
<script>
<!--将下面这个方法定义在适当的位置-->
createMap(){
this.$nextTick(function () {
var th = this
// 创建Map实例
var map = new BMap.Map("allmap");
map.enableScrollWheelZoom();//准许滑轮
map.centerAndZoom("武汉市",11);
//建立一个自动完成的对象
var ac = new BMap.Autocomplete({"input": "suggestId", "location": map})
var myValue;
ac.addEventListener("onconfirm", function (e) {//鼠标点击下拉列表后的事件
console.log(e)
var _value = e.item.value;
myValue = _value.province + _value.city + _value.district + _value.street + _value.business;
th.address_detail = myValue;
setPlace();
});
function setPlace() {
map.clearOverlays(); //清除地图上所有覆盖物
function myFun() {
th.userlocation = local.getResults().getPoi(0).point; //获取第一个智能搜索的结果
temp = th.userlocation;
map.centerAndZoom(th.userlocation, 15);
map.addOverlay(new BMap.Marker(th.userlocation)); //添加标注
}
var local = new BMap.LocalSearch(map, { //智能搜索
onSearchComplete: myFun
});
// alert("666");
local.search(myValue);
}
// 百度地图API功能
function G(id) {
return document.getElementById(id);
}
})
},
</script>
设计地址表的时候参数类型
//因为经纬度都是精确到小数点后67位的,所以得用DECIMAL(10,7)类型,而POJO类用String类对应