# 第3章 预约管理-套餐管理
回顾

今日内容介绍

学习目标
- 掌握新增套餐实现过程
- 定时清理垃圾图片实现
- 掌握Quartz使用方式
- 了解什么是Apache POI
- 掌握Apache POI的使用方法
- 掌握批量导入预约设置信息的实现过程
- 掌握日历方式展示预约设置信息的实现过程
- 掌握基于日历实现预约设置信息的实现过程
1. 新增套餐
【分析】
-
需求分析:
点击“新建”弹出新增套餐窗口,查询所有检查组列表数据,填充套餐数据以及勾选检组项,并点击提交按钮。
点击“上传图片”,将图片存入七牛云,回显图片,并图片名称保存表中。
-
数据库表:t_setmeal 新增 t_checkgroup 查询 t_setmeal_checkgroup新增
-
编码分析
3.1. 前端编码分析
3.1.1. 点击“新增”弹出窗口,清空表单,查询所有检查组列表数据
3.1.2. 填充表单内容以及勾选检查组,点击提交。校验表单内容(省略。。。)
3.1.3. 校验成功,发送请求 /setmeal/add.do 参数表单对象 检查组ids POST方式 3.1.4. 得到结果,提示成功或失败 刷新页面 关闭窗口
3.1.5. 点击“上传图片”发送上传图片请求,回显图片,并将图片名称设置formData对象中
注意:修改页面代码后,右击重新加载框架
3.2. 后端编码分析
3.2.1. controller service dao 查询所有检查组列表数据
3.2.2. controller 接收图片上传请求,将图片保存七牛云,**并返回图片名称** 3.2.3. controller接收请求 @RequestBody Setmeal setmeal,Integer[] ids
3.2.4.controller调用service业务处理
a.保存套餐表
b.获取套餐id
c.将检查组id 跟 套餐id 循环插入中间表
3.2.5.dao持久层接口 映射
【目标】
新增套餐
【路径】
1:需求分析
2:前台代码
(1)弹出新增窗口
(2)动态展示检查组列表
(3)图片上传并预览
- 使用七牛云存储图片
(4)提交请求
- 使用数据库存储图片名称
- 使用springmvc的文件上传技术
3:后台代码
业务:
- 新增套餐
(1)SetmealController.java(Controller)
(2)SetmealService.java(服务接口)
(3)SetmealServiceImpl.java(服务实现类)
(4)SetmealDao.java(Dao接口)
(5)SetmealDao.xml(Mapper映射文件)
4:完善文件上传,Redis存储图片名称(一会说)
【讲解】
1.1. 需求分析
套餐其实就是检查组的集合,例如有一个套餐为“入职体检套餐”,这个检查组可以包括多个检查组:一般检查、血常规、尿常规、肝功三项等。
所以在添加套餐时需要选择这个套餐包括的检查组。
套餐对应的实体类为Setmeal,
public class Setmeal implements Serializable {
private Integer id;
private String name;
private String code;
private String helpCode;
private String sex;//套餐适用性别:0不限 1男 2女
private String age;//套餐适用年龄
private Float price;//套餐价格
private String remark;
private String attention;
private String img;//套餐对应图片名称(用于存放七牛云上的图片名称-唯一)
private List<CheckGroup> checkGroups;//体检套餐对应的检查组,多对多关系
}
其中img字段表示套餐对应图片存储路径(用于存放七牛云上的图片名称)
对应的数据表为t_setmeal。套餐和检查组为多对多关系,所以需要中间表t_setmeal_checkgroup进行关联。
t_setmeal表

t_setmeal_checkgroup表

1.2. 前台代码
套餐管理页面对应的是setmeal.html页面,根据产品设计的原型已经完成了页面基本结构的编写,现在需要完善页面动态效果。

1.2.1. 弹出新增窗口
页面中已经提供了新增窗口,只是出于隐藏状态。只需要将控制展示状态的属性dialogFormVisible改为true接口显示出新增窗口。点击新建按钮时绑定的方法为handleCreate,所以在handleCreate方法中修改dialogFormVisible属性的值为true即可。同时为了增加用户体验度,需要每次点击新建按钮时清空表单输入项。
由于新增套餐时还需要选择此套餐包含的检查组,所以新增套餐窗口分为两部分信息:基本信息和检查组信息,如下图:


(1):新建按钮绑定单击事件,对应的处理函数为handleCreate
<el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
(2):handleCreate()方法:
// 重置表单
resetForm() {
// 清空套餐基本信息
this.formData = {};
// 选项卡设置成第一个
this.activeName='first';
// 重置检查项的复选框
this.checkgroupIds = [];
// 重置上传的图片路径
this.imageUrl = null;
},
// 弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
1.2.2. 动态展示检查组列表
现在虽然已经完成了新增窗口的弹出,但是在检查组信息标签页中需要动态展示所有的检查组信息列表数据,并且可以进行勾选。具体操作步骤如下:
(1)定义模型数据
tableData:[],//添加表单窗口中检查组列表数据
checkgroupIds:[],//添加表单窗口中检查组复选框对应id
(2)动态展示检查组列表数据,数据来源于上面定义的tableData模型数据
<el-tab-pane label="检查组信息" name="second">
<div class="checkScrol">
<table class="datatable">
<thead>
<tr>
<th>选择</th>
<th>项目编码</th>
<th>项目名称</th>
<th>项目说明</th>
</tr>
</thead>
<tbody>
<!--循环遍历tableData-->
<tr v-for="c in tableData">
<td>
<!--复选框绑定checkgroupIds,存放到值是id-->
<input :id="c.id" v-model="checkgroupIds" type="checkbox" :value="c.id">
</td>
<td><label :for="c.id">{{c.code}}</label></td>
<td><label :for="c.id">{{c.name}}</label></td>
<td><label :for="c.id">{{c.remark}}</label></td>
</tr>
</tbody>
</table>
</div>
</el-tab-pane>
其中:v-model="checkgroupIds",用于回显复选框。
(3)完善handleCreate方法,发送ajax请求查询所有检查组数据并将结果赋值给tableData模型数据用于页面表格展示
// 弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
// 3.1.1. 点击“新增”弹出窗口,清空表单,查询所有检查组列表数据
this.formData = {}; //null 单个值 []展示集合数据 {}展示对象
axios.get("/checkgroup/findAll.do").then(res=>{
this.tableData = res.data.data;
this.$message({
message: res.data.message,
type: res.data.flag ? 'success':'error'
});
})
},
(4)分别在CheckGroupController、CheckGroupService、CheckGroupServiceImpl、CheckGroupDao、CheckGroupDao.xml中扩展方法查询所有检查组数据
1:CheckGroupController:
//查询所有
@RequestMapping("/findAll")
public Result findAll(){
// 查询所有的检查组
List<CheckGroup> checkGroupList = checkGroupService.findAll();
if(checkGroupList != null && checkGroupList.size() > 0){
Result result = new Result(true, MessageConstant.QUERY_CHECKGROUP_SUCCESS,checkGroupList);
return result;
}
return new Result(false,MessageConstant.QUERY_CHECKGROUP_FAIL);
}
2:CheckGroupService:
List<CheckGroup> findAll();
3:CheckGroupServiceImpl:
// 查询所有检查组
@Override
public List<CheckGroup> findAll() {
return checkGroupDao.findAll();
}
4:CheckGroupDao:
List<CheckGroup> findAll();
5:CheckGroupDao.xml:
<select id="findAll" resultType="com.itheima.pojo.CheckGroup">
select * from t_checkgroup
</select>
1.2.3. 图片上传并预览
此处使用的是ElementUI提供的上传组件el-upload,提供了多种不同的上传效果,上传成功后可以进行预览。
实现步骤:
(1)定义模型数据,用于后面上传文件的图片预览:
imageUrl:null,//模型数据,用于上传图片完成后图片预览
(2)定义ElementUI上传组件:
<!--
el-upload:上传组件
action:上传的提交地址(七牛云服务器)
auto-upload:选中文件后是否自动上传
name:上传文件的名称,服务端可以根据名称获得上传的文件对象
show-file-list:是否显示已上传文件列表
on-success:文件上传成功时的钩子
before-upload:上传文件之前的钩子
-->
<el-upload
class="avatar-uploader"
action="/setmeal/upload.do"
:auto-upload="autoUpload"
name="imgFile"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<!--用于上传图片预览-->
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<!--用于展示上传图标-->
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
(3)定义对应的钩子函数:
//文件上传成功后的钩子,response为服务端返回的值,file为当前上传的文件封装成的js对象
handleAvatarSuccess(response, file) { //response == response.data
// 3.1.5. 点击“上传图片”发送上传图片请求,回显图片,并将图片名称设置formData对象中
this.imageUrl = "http://r0sybje64.hn-bkt.clouddn.com/"+response.data;
this.formData.img=response.data;
},
//上传图片之前执行
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传套餐图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传套餐图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
},
(4)创建SetmealController,接收上传的文件
/**
* 图片上传
* 方式一:MultipartFile imgFile
* 方式二:@RequestParam("imgFile") MultipartFile aaaa
*/
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public Result upload(MultipartFile imgFile){
try {
//获取原始文件名称 进行后缀截取
String originalFilename = imgFile.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")) ;
//将图片保存七牛云
String uuid = UUID.randomUUID().toString();
String newFileName= uuid+suffix;
QiniuUtils.upload2Qiniu(imgFile.getBytes(),newFileName);
//并返回图片名称
return new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS,newFileName);
} catch (Exception e) {
e.printStackTrace();
return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);
}
}
注意:别忘了在spring配置文件中配置文件上传组件
已在springmvc.xml中配置
<!--文件上传组件-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600" /><!--最大上传文件大小-->
<property name="maxInMemorySize" value="4096" />
<property name="defaultEncoding" value="UTF-8"/>
</bean>
1.2.4. 提交请求
当用户点击新增窗口中的确定按钮时发送ajax请求将数据提交到后台进行数据库操作。提交到后台的数据分为两部分:套餐基本信息(对应的模型数据为formData)和检查组id数组(对应的模型数据为checkgroupIds)。
(1)为确定按钮绑定单击事件,对应的处理函数为handleAdd
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="handleAdd()">确定</el-button>
</div>
(2)完善handleAdd方法
//添加
handleAdd() {
//3.1.2. 填充表单内容以及勾选检查组,点击提交。校验表单内容(省略。。。)
//3.1.3. 校验成功,发送请求 /setmeal/add.do 参数表单对象 检查组ids POST方式
//3.1.4. 得到结果,提示成功或失败 刷新页面 关闭窗口
axios.post("/setmeal/add.do?checkGroupIds="+this.checkgroupIds,this.formData).then(res=>{
this.$message({
message: res.data.message,
type: res.data.flag ? 'success':'error'
});
//刷新页面
this.findPage();
//关闭窗口
this.dialogFormVisible = false;
})
},
1.3. 后台代码
1.3.1. Controller
在SetmealController中增加方法
/**
* 新增套餐
*/
@RequestMapping(value = "/add",method = RequestMethod.POST)
public Result add(@RequestBody Setmeal setmeal, Integer[] checkGroupIds){
try {
setmealService.add(setmeal,checkGroupIds);
return new Result(true, MessageConstant.ADD_SETMEAL_SUCCESS);
} catch (Exception e) {
e.printStackTrace();
return new Result(false, MessageConstant.ADD_SETMEAL_FAIL);
}
}
1.3.2. Service服务实现类
创建SetmealService接口并提供新增方法
package com.itheima.service;
import com.itheima.pojo.Setmeal;
/**
* 套餐管理业务处理层
*/
public interface SetmealService {
/**
* 新增套餐
*/
void add(Setmeal setmeal, Integer[] checkGroupIds);
}
创建SetmealServiceImpl服务实现类并实现新增方法
package com.itheima.service.impl;
import com.itheima.dao.SetmealDao;
import com.itheima.pojo.Setmeal;
import com.itheima.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
/**
* 套餐业务处理接口实现类
*/
@Service
@Transactional
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealDao setmealDao;
/**
* 新增套餐
*/
@Override
public void add(Setmeal setmeal, Integer[] checkGroupIds) {
//a.保存套餐表
setmealDao.add(setmeal);
//b.获取套餐id
Integer setmealId = setmeal.getId();
//c.将检查组id 跟 套餐id 循环插入中间表
setGroupAndSetmeal(setmealId,checkGroupIds);
}
/**
* 往套餐检查组中间表插入数据
* @param setmealId
* @param checkGroupIds
*/
private void setGroupAndSetmeal(Integer setmealId, Integer[] checkGroupIds) {
if(checkGroupIds != null && checkGroupIds.length>0){
for (Integer checkGroupId : checkGroupIds) {
Map map = new HashMap();
map.put("setmealId",setmealId);
map.put("checkGroupId",checkGroupId);
setmealDao.setGroupAndSetmeal(map);
}
}
}
}
1.3.4. Dao接口
创建SetmealDao接口并提供相关方法
package com.itheima.dao;
import com.itheima.pojo.Setmeal;
import java.util.Map;
/**
* 套餐持久层接口
*/
public interface SetmealDao {
/**
* 保存套餐表
* @param setmeal
*/
void add(Setmeal setmeal);
/**
* 往套餐检查组中间表插入数据
* @param map
*/
void setGroupAndSetmeal(Map map);
}
1.3.5. Mapper映射文件
创建SetmealDao.xml文件并定义相关SQL语句
<?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">
<!--保存套餐表-->
<insert id="add" parameterType="com.itheima.pojo.Setmeal">
/*resultType="int"返回值类型 order="AFTER"执行插入完成后执行 keyProperty="id" 将获取的id值设置到id属性上*/
<selectKey resultType="int" order="AFTER" keyProperty="id" >
SELECT LAST_INSERT_ID()
</selectKey>
insert into t_setmeal(name,code,helpCode,sex,age,price,remark,attention,img)
values(#{name},#{code},#{helpCode},#{sex},#{age},#{price},#{remark},#{attention},#{img})
</insert>
<!--往套餐检查组中间表插入数据-->
<insert id="setGroupAndSetmeal" parameterType="map">
insert into t_setmeal_checkgroup(setmeal_id,checkgroup_id) values (#{setmealId},#{checkGroupId})
</insert>
</mapper>
【小结】
1:需求分析
2:前台代码
(1)弹出新增窗口
(2)动态展示检查组列表
(3)图片上传并预览
- 使用七牛云存储图片
(4)提交请求
-
使用数据库存储图片名称
-
使用springmvc的文件上传技术
第一步:页面定义上传组件
第二步:使用springmvc跳转Controller,接收文件参数
第三步:springmvc.xml 配置文件上传组件
3:后台代码
- 新增套餐
(1)SetmealController.java(Controller)
(1)SetmealService.java(服务接口)
(1)SetmealServiceImpl.java(服务实现类)
(1)SetmealDao.java(Dao接口)
(1)SetmealDao.xml(Mapper映射文件)
2. 定时任务组件Quartz
【目标】
定时任务组件Quartz
- 清除文件上传所产生的垃圾图片
【路径】
1:Quart介绍
- 掌握场景(定时任务)
2:Quartz入门案例
- spring整合Quartz(spring中配置)
3:cron表达式
4:cron表达式在线生成器
【讲解】
3.1. Quartz介绍
Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。
maven坐标:
<!--quartz的基础包-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
<!--spring整合Quartz-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
3.2. Quartz入门案例
【路径】
1:创建maven工程quartzdemo,打包方式为war,导入jar包
2:自定义一个Job
3:提供Spring配置文件application-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等
4:web.xml中定义
5:启动tomcat完成测试
【讲解】
本案例基于Quartz和spring整合的方式使用。具体步骤:
(1)创建maven工程quartzdemo,打包方式为war,导入Quartz和spring相关坐标,pom.xml文件如下

导入jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>quartzdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>8080</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
(2)自定义一个Job
package com.itheima.jobs;
import java.util.Date;
/**
* 自定义任务类Job
*/
public class HelloJob {
/**
* 任务方法
*/
public void sayHello(){
System.out.println("任务运行了时间::"+new Date());
}
/**
* 任务方法
*/
public void sayHello2(){
System.out.println("任务运行了时间::"+new Date());
}
}
(3)提供Spring配置文件application-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等
【路径】
1:创建JobDetail对象,作用是负责通过反射调用指定的Job,注入目标对象,注入目标方法
2:注册一个触发器,指定任务触发的时间
3:注册一个统一的调度工厂,通过这个调度工厂调度任务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--1注册任务类-->
<bean id="helloJob" class="com.itheima.jobs.HelloJob"/>
<!--2.任务工厂对象配置-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--注入任务-->
<property name="targetObject" ref="helloJob"/>
<property name="targetMethod" value="sayHello"/>
</bean>
<!--3.触发器配置-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!--注入任务工厂-->
<property name="jobDetail" ref="jobDetail"/>
<!--时间配置-->
<property name="cronExpression">
<!--每隔5秒执行一次-->
<value>0/5 * * * * ?</value>
</property>
</bean>
<!--4.quartz调度工厂统一调度运行-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
</list>
</property>
</bean>
</beans>
(4)web.xml中定义
启动web,自动加载spring容器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-jobs.xml</param-value>
</context-param>
</web-app>

或者:
【2】编写main方法进行测试
package com.itheima.main;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("application-jobs.xml");
}
}
执行上面main方法观察控制台,可以发现每隔10秒会输出一次,说明每隔10秒自定义Job被调用一次。

3.3. cron表达式
上面的入门案例中我们指定了一个表达式:0/10 * * * * ?
这种表达式称为cron表达式,通过cron表达式可以灵活的定义出符合要求的程序执行的时间。本小节我们就来学习一下cron表达式的使用方法。如下图:

cron表达式分为七个域,之间使用空格分隔。其中最后一个域(年)可以为空。每个域都有自己允许的值和一些特殊字符构成。使用这些特殊字符可以使我们定义的表达式更加灵活。
下面是对这些特殊字符的介绍:
逗号(,):指定一个值列表,例如使用在月域上1,4,5,7表示1月、4月、5月和7月
横杠(-):指定一个范围,例如在时域上3-6表示3点到6点(即3点、4点、5点、6点)
星号(*):表示这个域上包含所有合法的值。例如,在月份域上使用星号意味着每个月都会触发
斜线(/):表示递增,例如使用在秒域上0/15表示每15秒
问号(?):只能用在日和周域上,但是不能在这两个域上同时使用。表示不指定
井号(#):只能使用在周域上,用于指定月份中的第几周的哪一天,例如6#3,意思是某月的第三个周五 (6=星期五,3意味着月份中的第三周)
L:某域上允许的最后一个值。只能使用在日和周域上。当用在日域上,表示的是在月域上指定的月份的最后一天。用于周域上时,表示周的最后一天,就是星期六
W:W 字符代表着工作日 (星期一到星期五),只能用在日域上,它用来指定离指定日的最近的一个工作日
3.4. cron表达式在线生成器
前面介绍了cron表达式,但是自己编写表达式还是有一些困难的,我们可以借助一些cron表达式在线生成器来根据我们的需求生成表达式即可。

【小结】
1:Quart介绍
- 掌握场景(定时任务)
2:Quartz入门案例
- spring整合Quartz(spring中配置)
3:cron表达式
4:cron表达式在线生成器
4. 定时清理垃圾图片
【分析】
-
需求分析:
新增套餐的时候,上传图片,但未提交表单,则产生了垃圾图片,将垃圾图片从七牛云删除掉。

-
操作七牛云删除图片
-
编码分析

4.3. SetmealController中 add upload最后将操作记录写入redis集合中
【目标】
Quartz整合项目,完成定时清理垃圾图片
【路径】
1:创建maven聚合工程health_jobs,打包方式为war,导入Quartz等相关坐标
2:配置web.xml
- web容器启动,加载spring容器
3:配置log4j.properties
4:配置applicationContext-redis.xml
- spring整合redis
5:配置applicationContext-jobs.xml
- spring整合Quartz
6:创建ClearImgJob定时任务类
- 使用Quartz清理垃圾图片
【讲解】
前面我们已经完成了体检套餐的管理,在新增套餐时套餐的基本信息和图片是分两次提交到后台进行操作的。也就是用户首先将图片上传到七牛云服务器,然后再提交新增窗口中录入的其他信息。如果用户只是上传了图片而没有提交录入的其他信息,此时的图片就变为了垃圾图片,因为在数据库中并没有记录它的存在。此时我们要如何处理这些垃圾图片呢?
解决方案就是通过定时任务组件定时清理这些垃圾图片。为了能够区分出来哪些图片是垃圾图片,我们在文件上传成功后将图片保存到了一个redis集合中,当套餐数据插入到数据库后我们又将图片名称保存到了另一个redis集合中,通过计算这两个集合的差值就可以获得所有垃圾图片的名称。
本章节我们就会基于Quartz定时任务,通过计算redis两个集合的差值找出所有的垃圾图片,就可以将垃圾图片清理掉。
4.1. 清理垃圾图片分析
前面我们已经完成了文件上传,将图片存储在了七牛云服务器中。但是这个过程存在一个问题,就是如果用户只上传了图片而没有最终保存套餐信息到我们的数据库,这时我们上传的图片就变为了垃圾图片。对于这些垃圾图片我们需要定时清理来释放磁盘空间。这就需要我们能够区分出来哪些是垃圾图片,哪些不是垃圾图片。如何实现呢?
方案就是利用redis来保存图片名称,具体做法为:
1、当用户上传图片后,将图片名称保存到redis的一个Set集合中,例如集合名称为setmealPicResources
2、当用户添加套餐后,将图片名称保存到redis的另一个Set集合中,例如集合名称为setmealPicDbResources
3、计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称集合,清理这些图片即可
4.2. 清理垃圾图片实现
4.2.1 health_web中配置修改
修改spring-redis.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Jedis连接池的相关配置-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal">
<value>200</value>
</property>
<property name="maxIdle">
<value>50</value>
</property>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="6379" type="int" />
<constructor-arg name="timeout" value="30000" type="int" />
</bean>
</beans>
同时在springmvc.xml中使用
<import resource="classpath:spring-redis.xml"></import>
4.2.2 health_common常量类
提供Redis常量类
package com.itheima.constant;
public class RedisConstant {
//套餐图片所有图片名称
public static final String SETMEAL_PIC_RESOURCES = "setmealPicResources";
//套餐图片保存在数据库中的图片名称
public static final String SETMEAL_PIC_DB_RESOURCES = "setmealPicDbResources";
}
4.2.3 SetmealController完善
在文件上传成功后将图片名称保存到redis集合中
@Autowired
private JedisPool jedisPool;
/**
* 图片上传
* 方式一:MultipartFile imgFile
* 方式二:@RequestParam("imgFile") MultipartFile aaaa
*/
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public Result upload(MultipartFile imgFile){
try {
//获取原始文件名称 进行后缀截取
String originalFilename = imgFile.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")) ;
//将图片保存七牛云
String uuid = UUID.randomUUID().toString();
String newFileName= uuid+suffix;
QiniuUtils.upload2Qiniu(imgFile.getBytes(),newFileName);
//并返回图片名称
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,newFileName);
return new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS,newFileName);
} catch (Exception e) {
e.printStackTrace();
return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);
}
}
/**
* 新增套餐
*/
@RequestMapping(value = "/add",method = RequestMethod.POST)
public Result add(@RequestBody Setmeal setmeal, Integer[] checkGroupIds){
try {
setmealService.add(setmeal,checkGroupIds);
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_DB_RESOURCES,setmeal.getImg());
return new Result(true, MessageConstant.ADD_SETMEAL_SUCCESS);
} catch (Exception e) {
e.printStackTrace();
return new Result(false, MessageConstant.ADD_SETMEAL_FAIL);
}
}
添加:
//将上传图片名称存入Redis,基于Redis的Set集合存储
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,fileName);
4.2. 清理垃圾图片定时实现
4.2.1.创建工程health_jobs
打包方式为war,导入Quartz等相关坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>health_parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>health_jobs</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>health_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>8889</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2.2. 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--applicationContext*.xml:以applicationContext开头的配置文件进行加载-->
<!--classpath*:加载依赖的工程的配置文件 此处加上*-->
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
4.2.3. 配置log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:\\mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
4.2.3. 配置applicationContext-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Jedis连接池的相关配置-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal">
<value>200</value>
</property>
<property name="maxIdle">
<value>50</value>
</property>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="6379" type="int" />
<constructor-arg name="timeout" value="30000" type="int" />
</bean>
</beans>
4.2.4. 配置applicationContext-jobs.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描-->
<context:component-scan base-package="com.itheima.jobs"/>
<!--1注册任务类-->
<bean id="clearImgJob" class="com.itheima.jobs.ClearImgJob"/>
<!--2.任务工厂对象配置-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--注入任务-->
<property name="targetObject" ref="clearImgJob"/>
<property name="targetMethod" value="clearImg"/>
</bean>
<!--3.触发器配置-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!--注入任务工厂-->
<property name="jobDetail" ref="jobDetail"/>
<!--时间配置-->
<property name="cronExpression">
<!--每隔10秒执行一次-->
<value>0/10 * * * * ?</value>
</property>
</bean>
<!--4.quartz调度工厂统一调度运行-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
</list>
</property>
</bean>
</beans>
4.2.5. 创建ClearImgJob任务类
package com.itheima.jobs;
import com.itheima.constant.RedisConstant;
import com.itheima.utils.QiniuUtils;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisPool;
import java.util.Set;
/**
* 定时清理垃圾图片任务类
*/
public class ClearImgJob {
@Autowired
private JedisPool jedisPool;
/**
* 清理垃圾图片
*/
public void clearImg(){
//1.将集合1(上传成功图片集合) 减去 集合2(保存套餐成功集合) == 集合3(垃圾图片)
Set<String> imgFileNames = jedisPool.getResource().sdiff(RedisConstant.SETMEAL_PIC_RESOURCES, RedisConstant.SETMEAL_PIC_DB_RESOURCES);
System.out.println("********清理垃圾图片*********垃圾图片条数************"+imgFileNames.size()+"*****************");
if(imgFileNames != null && imgFileNames.size()>0){
for (String imgFileName : imgFileNames) {
//2.调用七牛云删除图片
QiniuUtils.deleteFileFromQiniu(imgFileName);
//3.jedisPool将集合1中垃圾图片记录删除
jedisPool.getResource().srem(RedisConstant.SETMEAL_PIC_RESOURCES,imgFileName);
}
}
}
}
测试:

【小结】
1:创建maven聚合工程health_jobs,打包方式为war,导入Quartz等相关坐标
2:配置web.xml
- web容器启动,加载spring容器
3:配置log4j.properties
4:配置applicationContext-redis.xml
- spring整合redis
5:配置applicationContext-jobs.xml
- spring整合Quartz
6:创建ClearImgJob定时任务类
- 使用Quartz清理垃圾图片
5. 预约设置需求分析
【目标】
- 掌握预约设置的需求
【路径】
- 预约设置需求分析
- 对应的表结构和实体类
【讲解】
5.1. 预约设置需求分析
前面我们已经完成了检查项管理、检查组管理、套餐管理等。接下来我们需要进行预约设置,其实就是设置每一天的体检预约最大数量。客户可以通过微信端在线预约,在线预约时需要选择体检的时间,使得选择体检时间的已预约人数加1,如果客户选择的时间已经预约满则无法进行预约。
5.2. t_ordersetting表结构

orderDate:预约日期
number:最多可预约人数
reservations:已预约人数
【小结】
- 预约设置 就是设置一天最大预约体检人数(number字段)
- 客户预约的时候,需要更新当前预约人数+1(reservations字段)
6. Apache POI
【目标】
了解什么是Apache POI
掌握Apache POI的使用方法
【路径】
-
POI介绍
-
POI入门案例
(1)从Excel文件读取数据
(2)向Excel文件写入数据
-
POI工具类的介绍
【讲解】
6.1. POI介绍
Apache POI是用Java编写的免费开源的跨平台的Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能,其中使用最多的就是使用POI操作Excel文件。
jxl:专门操作Excel
maven坐标:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
POI结构:
HSSF - 提供读写Microsoft Excel XLS格式档案的功能 xls
XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能(我们使用)xlsx
HSSF - 提供读写Microsoft Excel XLS格式档案的功能
XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能(我们使用)
HWPF - 提供读写Microsoft Word DOC格式档案的功能
HSLF - 提供读写Microsoft PowerPoint格式档案的功能
HDGF - 提供读Microsoft Visio格式档案的功能
HPBF - 提供读Microsoft Publisher格式档案的功能
HSMF - 提供读Microsoft Outlook格式档案的功能
我们使用:XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能
6.2. 入门案例
6.2.1. 从Excel文件读取数据
【需求】
使用POI可以从一个已经存在的Excel文件中读取数据(数据读取并保存数据库表中)
【路径】
1:创建工作簿对象
2:获得工作表对象
3:遍历工作表对象 获得行对象
4:遍历行对象 获得单元格(列)对象
5:获得数据
6:关闭
【讲解】
- 实现
使用POI可以从一个已经存在的Excel文件中读取数据
在health_common中建立测试类,TestPoi.java
/**
* 方式一:读取excel文件
*/
@Test
public void readExcel1() throws IOException {
//1:创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook("C:\\Users\\Administrator\\Desktop\\read.xlsx");
//2:获得工作表对象
//XSSFSheet sheet1 = xssfWorkbook.getSheet("Sheet1");
XSSFSheet sheet1 = xssfWorkbook.getSheetAt(0);
//3:遍历工作表对象 获得行对象
for (Row row : sheet1) {
//4:遍历行对象 获得单元格(列)对象
for (Cell cell : row) {
//5:获得数据
System.out.println(cell.getStringCellValue());
}
System.out.println("*******************************************");
}
//6:关闭
xssfWorkbook.close();
}
通过上面的入门案例可以看到,POI操作Excel表格封装了几个核心对象:
XSSFWorkbook:工作簿
XSSFSheet:工作表
XSSFRow:行
XSSFCell:单元格
上面案例是通过遍历工作表获得行,遍历行获得单元格,最终获取单元格中的值。
还有一种方式就是获取工作表最后一个行号,从而根据行号获得行对象,通过行获取最后一个单元格索引,从而根据单元格索引获取每行的一个单元格对象,代码如下:
/**
* 方式二:读取excel文件(推荐使用)
*/
@Test
public void readExcel2() throws IOException {
//1:创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook("C:\\Users\\Administrator\\Desktop\\read.xlsx");
//2:获得工作表对象
XSSFSheet sheet1 = xssfWorkbook.getSheetAt(0);
int lastRowNum = sheet1.getLastRowNum();//最后一行行号 从0开始
//3:遍历工作表对象 获得行对象
for (int i = 0;i<=lastRowNum;i++) {
XSSFRow row = sheet1.getRow(i);
short lastCellNum = row.getLastCellNum();//最后一列列号 从0开始
//4:遍历行对象 获得单元格(列)对象
for (int j = 0;j<lastCellNum;j++){
//5:获得数据
System.out.println(row.getCell(j).getStringCellValue());
}
System.out.println("*******************************************");
}
//6:关闭
xssfWorkbook.close();
}
6.2.2. 向Excel文件写入数据
【需求】
使用POI可以在内存中创建一个Excel文件并将数据写入到这个文件,最后通过输出流将内存中的Excel文件下载到磁盘(将数据库表中数据导出excel文件)
【路径】
1.创建工作簿对象
2.创建工作表对象
3.创建行对象
4.创建列(单元格)对象, 设置内容
5.通过输出流将workbook对象下载到磁盘
【实现】
使用POI可以在内存中创建一个Excel文件并将数据写入到这个文件,最后通过输出流将内存中的Excel文件下载到磁盘
/**
* 向Excel文件写入数据
*/
@Test
public void writeExcel() throws IOException {
//1.创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
//2.创建工作表对象
XSSFSheet sheet = xssfWorkbook.createSheet();
//3.创建行对象(标题行)
XSSFRow titleRow = sheet.createRow(0);
//4.创建列(单元格)对象, 设置内容
titleRow.createCell(0).setCellValue("编号");
titleRow.createCell(1).setCellValue("姓名");
titleRow.createCell(2).setCellValue("年龄");
//5.创建行对象(数据行)
XSSFRow dataRow = sheet.createRow(1);
//6.创建列(单元格)对象, 设置内容
dataRow.createCell(0).setCellValue("001");
dataRow.createCell(1).setCellValue("老王");
dataRow.createCell(2).setCellValue("18");
//7.通过输出流将workbook对象下载到磁盘
OutputStream outPutStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\hello.xlsx");
xssfWorkbook.write(outPutStream);
//8.释放资源
outPutStream.flush();
outPutStream.close();
xssfWorkbook.close();
}

小结:POI技术(向Excel文件写入数据)
- 创建工作簿的时候, 不需要传入参数(excel不存在的)
- 使用输出流,输出excel
应用场景:1:从excel中读取数据,写入到数据库(导入);2:从数据库查询数据,写入到excel文件(报表技术,导出)
6.2.3.POI工具类的介绍
将资料中的POIUtils工具类复制到health_common工程

【小结】
1.POI介绍
POI就是一个工具, 用来操作Office, 主要用来操作Excel
2.POI入门案例
(1)从Excel文件读取数据(导入到数据库)
(2)向Excel文件写入数据(导出报表)
3.POI工具类的介绍
使用工具类, 读取Excel, 每一行读取到了String[] 里面, 多行就是多个String[] , 最终封装到List
7. 批量导入预约设置信息
【分析】
-
需求分析:
点击“模板下载”,修改模板中数据。点击“上传文件”将填充好的模板文件,批量导入后数据库表中。
-
数据库表:先查询 如果存在更新 不存在t_ordersetting 新增
-
编码分析
3.1. 前端编码分析
3.1.1. 点击“模板下载”将模板文件下载本地(webapp目录下template目录放模板文件)
3.1.2. 点击“上传文件”发送请求/ordersetting/upload.do post 参数名excelFile
3.1.3. 批量导入预约设置成功或失败提示
注意:修改页面代码后,右击重新加载框架
3.2. 后端编码分析
3.2.1. controller接收批量导入预约设置文件,MultipartFile excelFile
a.调用POIUtils工具类 解析excel得到结果List<String[]>
b.将List<String[]> 转换为 List<OrderSetting> c.调用service方法传入List
3.2.2. service中业务处理 a.先根据预约日期查询t_ordersetting表记录是否存在
b.如果预约记录不存在,则保存预约设置数据
c.如果预约记录存在,则更新预约日期更新最大可预约人数
【目标】
批量导入预约设置信息
【路径】
1:前台代码
(1)提供模板文件
(2)实现模板文件下载(填写数据)
(3)文件上传(读取excel的数据,批量导入到数据库)
2:后台代码
业务:
- 从excel中读取预约设置信息,批量导入到数据库
(1)OrderSettingController.java(Controller)
(2)OrderSettingService.java(服务接口)
(3)OrderSettingServiceImpl.java(服务实现类)
(4)OrderSettingDao.java(Dao接口)
(5)OrderSettingDao.xml(Mapper映射文件)
【讲解】
【需求】
从Excel读取预约设置的数据,批量导入到数据库

【设计】
预约设置信息对应的数据表为t_ordersetting,预约设置操作对应的页面为ordersetting.html
1:t_ordersetting表结构:

orderDate:预约日期
number:最多可预约人数
reservations:已预约人数
【路径】
批量导入预约设置信息操作过程:
第一步、点击模板下载按钮下载Excel模板文件【文件下载】
第二步、将预约设置信息录入到模板文件中
第三步、点击上传文件按钮将录入完信息的模板文件上传到服务器【文件上传】
第四步、通过POI读取上传文件的数据并保存到数据库【poi导入】
2:将ordersetting.html放置到health_web中

7.1. 前台代码
7.1.1. 提供模板文件
资料中已经提供了Excel模板文件ordersetting_template.xlsx,将文件放在health_web工程的template目录下

7.1.2. 实现模板文件下载
(71)为模板下载按钮绑定事件实现模板文件下载
<el-button style="margin-bottom: 20px;margin-right: 20px" type="primary" @click="downloadTemplate()">模板下载</el-button>
(2)downloadTemplate()
//下载模板文件
downloadTemplate(){
window.location.href="../template/ordersetting_template.xlsx";
},
7.1.3. 文件上传
(1)使用ElementUI的上传组件实现文件上传并绑定相关事件
<el-upload action="/ordersetting/upload.do"
name="excelFile"
:show-file-list="false"
:on-success="handleSuccess"
:before-upload="beforeUpload">
<el-button type="primary">上传文件</el-button>
</el-upload>
(2)handleSuccess方法:用于显示上传成功或者失败信息。
//上传成功提示
handleSuccess(response, file) {
if(response.flag){
this.$message({
message: response.message,
type: 'success'
});
}else{
this.$message.error(response.message);
}
},
(3)beforeUpload方法:用于校验上传的文件是否是excel文件
//上传之前进行文件格式校验
beforeUpload(file){
const isXLS = file.type = 'application/vnd.ms-excel';
if(isXLS){
return true;
}
const isXLSX = file.type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
if (isXLSX) {
return true;
}
this.$message.error('上传文件只能是xls或者xlsx格式!');
return false;
},
7.2. 后台代码
7.2.1. Controller
【路径】
1.点击上传文件, 把编写好的Excel上传到OrderSettingController
2.创建OrderSettingController, 创建upload()方法
//1.使用POI解析文件 得到List<String[]> list
//2.把List<String[]> list转成 List<OrderSetting> list
//3.调用业务 进行保存
3.创建OrderSettingService
//1.遍历List<OrderSetting> list
//2.判断当前的日期之前是否设置过
//2.1 如果设置过, 更新数量
//2.2 没有设置过, 保存
4.创建OrderSettingDao
//根据日期查询
//根据日期更新number
//保存OrderSetting
在health_web工程创建OrderSettingController并提供upload方法
package com.itheima.controller;
import com.itheima.constant.MessageConstant;
import com.itheima.entity.Result;
import com.itheima.pojo.OrderSetting;
import com.itheima.service.OrderSettingService;
import com.itheima.utils.POIUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 预约设置控制层
*/
@RestController
@RequestMapping("/ordersetting")
public class OrderSettingController {
@Autowired
private OrderSettingService orderSettingService;
/**
* 批量导入预约设置信息
* @param excelFile
* @return
*/
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public Result upload(MultipartFile excelFile){
try {
//a.调用POIUtils工具类 解析excel得到结果List<String[]>
List<String[]> listStr = POIUtils.readExcel(excelFile);
//b.将List<String[]> 转换为 List<OrderSetting>
if(!CollectionUtils.isEmpty(listStr)) {
List<OrderSetting> orderSettingList = new ArrayList<>();
for (String[] str : listStr) {
OrderSetting orderSetting = new OrderSetting();
orderSetting.setOrderDate(new Date(str[0]));//预约日期
orderSetting.setNumber(Integer.parseInt(str[1]));//最大可预约人数
orderSetting.setReservations(0);//已经预约人数 默认0
orderSettingList.add(orderSetting);
}
//c.调用service方法传入List<OrderSetting>
orderSettingService.batchImport(orderSettingList);
}
return new Result(true, MessageConstant.IMPORT_ORDERSETTING_SUCCESS);
} catch (Exception e) {
e.printStackTrace();
return new Result(false, MessageConstant.IMPORT_ORDERSETTING_FAIL);
}
}
}
7.2.2. Service实现服务类
创建OrderSettingService服务接口并提供新增方法
package com.itheima.service;
import com.itheima.pojo.OrderSetting;
import java.util.List;
/**
* 预约设置业务层接口
*/
public interface OrderSettingService {
/**
* 批量导入预约设置信息
*/
void batchImport(List<OrderSetting> orderSettingList);
}
创建服务实现类OrderSettingServiceImpl并实现新增方法
package com.itheima.service.impl;
import com.itheima.dao.OrderSettingDao;
import com.itheima.pojo.OrderSetting;
import com.itheima.service.OrderSettingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 预约设置业务层接口
*/
@Service
@Transactional
public class OrderSettingServiceImpl implements OrderSettingService {
@Autowired
private OrderSettingDao orderSettingDao;
/**
* 批量导入预约设置信息
*/
@Override
public void batchImport(List<OrderSetting> orderSettingList) {
for (OrderSetting os : orderSettingList) {
//a.先根据预约日期查询t_ordersetting表记录是否存在
int count = orderSettingDao.findByOrderDate(os.getOrderDate());
//b.如果预约记录存在,则更新预约日期更新最大可预约人数
if(count >0){
orderSettingDao.updateNumberByOrderDate(os);
}else {
//c.如果预约记录不存在,则保存预约设置数据
orderSettingDao.add(os);
}
}
}
}
7.2.3. Dao接口
创建Dao接口OrderSettingDao并提供更新和新增方法
package com.itheima.dao;
import com.itheima.pojo.OrderSetting;
import java.util.Date;
/**
* 预约设置持久层接口
*/
public interface OrderSettingDao {
/**
* 先根据预约日期查询t_ordersetting表记录是否存在
* @param orderDate
* @return
*/
int findByOrderDate(Date orderDate);
/**
*则更新预约日期更新最大可预约人数
* @param os
*/
void updateNumberByOrderDate(OrderSetting os);
/**
* 则保存预约设置数据
* @param os
*/
void add(OrderSetting os);
}
7.2.5. Mapper映射文件
创建Mapper映射文件OrderSettingDao.xml并提供相关SQL
<?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.OrderSettingDao">
<!--先根据预约日期查询t_ordersetting表记录是否存在-->
<select id="findByOrderDate" parameterType="date" resultType="int">
select count(*) from t_ordersetting where orderDate = #{orderDate}
</select>
<!--则更新预约日期更新最大可预约人数-->
<update id="updateNumberByOrderDate" parameterType="com.itheima.pojo.OrderSetting">
update t_ordersetting set number = #{number} where orderDate = #{orderDate}
</update>
<!--则保存预约设置数据-->
<insert id="add" parameterType="com.itheima.pojo.OrderSetting">
insert into t_ordersetting(orderDate,number,reservations) values(#{orderDate},#{number},#{reservations})
</insert>
</mapper>
【小结】
1:前台代码
(1)提供模板文件
(2)实现模板文件下载
(3)文件上传
2:后台代码
- 从excel中读取预约设置信息,批量导入到数据库
(1)OrderSettingController.java(Controller)
(2)OrderSettingService.java(服务接口)
(3)OrderSettingServiceImpl.java(服务实现类)
(4)OrderSettingDao.java(Dao接口)
(5)OrderSettingDao.xml(Mapper映射文件)
8. 日历展示预约设置信息-作业
【目标】
日历展示预约设置信息
【路径】
1:前台代码
(1)使用静态数据调试
(2)发送ajax获取动态数据
2:后台代码
业务:
- 在页面上,使用日历展示预约设置信息
(1)OrderSettingController.java(Controller)
(2)OrderSettingService.java(服务接口)
(3)OrderSettingServiceImpl.java(服务实现类)
(4)OrderSettingDao.java(Dao接口)
(5)OrderSettingDao.xml(Mapper映射文件)
3:初始化下个月,上个月数据
【讲解】
【需求】
前面已经完成了预约设置功能,现在就需要通过日历的方式展示出来每天设置的预约人数。
在页面中已经完成了日历的动态展示,我们只需要查询当前月份的预约设置信息并展示到日历中即可,同时在日历中还需要展示已经预约的人数,效果如下:

8.1. 前台代码
8.1.1. 使用静态数据调试
为了能够快速看到效果,我们可以先使用静态数据模拟,然后再改为发送ajax请求查询数据库。
实现步骤:
(1)预约设置数据对应的模型数据为leftobj,在initData方法最后为leftobj模型数据赋值:
this.leftobj = [
{ date: 1, number: 120, reservations: 1 },
{ date: 3, number: 120, reservations: 1 },
{ date: 4, number: 120, reservations: 120 },
{ date: 6, number: 120, reservations: 1 },
{ date: 8, number: 120, reservations: 1 }
];
其中date表示日期,number表示可预约人数,reservations表示已预约人数
(2)使用VUE的v-for标签遍历上面的leftobj模型数据,展示到日历上:
<template>
<template v-for="obj in leftobj">
<template v-if="obj.date dayobject.day.getDate()">
<template v-if="obj.number > obj.reservations">
<div class="usual">
<p>可预约{{obj.number}}人</p>
<p>已预约{{obj.reservations}}人</p>
</div>
</template>
<template v-else>
<div class="fulled">
<p>可预约{{obj.number}}人</p>
<p>已预约{{obj.reservations}}人</p>
<p>已满</p>
</div>
</template>
</template>
</template>
<button v-if="dayobject.day > today" @click="handleOrderSet(dayobject.day)" class="orderbtn">设置</button>
</template>

8.1.2. 发送ajax获取动态数据
将上面的静态模拟数据去掉,改为发送ajax请求,根据当前页面对应的月份查询数据库获取预约设置信息,将查询结果赋值给leftobj模型数据
(1)在钩子函数created中添加:
created: function () {//在vue初始化时调用
this.initData(null);
this.createdData();
},
(2)创建函数createData()
组织this.leftobj的数据,返回List
//发送ajax请求,根据当前页面对应的月份查询预约设置信息
methods: {
createdData(){
axios.post("/ordersetting/getOrderSettingByMonth.do?date="+this.currentYear+"-"+this.currentMonth).then((response)=>{
if(response.data.flag){
this.leftobj = response.data.data;
this.$message({
message:response.data.message,
type:"success"
})
}else{
this.$message.error(response.data.message);
}
})
},
...
}
8.2. 后台代码
【路径】
1.OrderSettingController.java
2.OrderSettingServiceImpl.java
// 1.组织查询Map,dateBegin表示月份开始时间,dateEnd月份结束时间
// 2.查询当前月份的预约设置
// 3.将List<OrderSetting>,组织成List<Map>
3.OrderSettingDao.java
查询当前月份的预约设置
4.OrderSettingDao.xml
查询当前月份的预约设置(使用between and)
8.2.1. Controller
在OrderSettingController中提供getOrderSettingByMonth方法,根据月份查询预约设置信息
/**
* 根据日期查询预约设置数据(获取指定日期所在月份的预约设置数据)
* @param date
* @return
*/
@RequestMapping("/getOrderSettingByMonth")
public Result getOrderSettingByMonth(String date){//参数格式为:2019-03
try{
List<Map> list = orderSettingService.getOrderSettingByMonth(date);
//获取预约设置数据成功
return new Result(true,MessageConstant.GET_ORDERSETTING_SUCCESS,list);
}catch (Exception e){
e.printStackTrace();
//获取预约设置数据失败
return new Result(false,MessageConstant.GET_ORDERSETTING_FAIL);
}
}
8.2.2. 服务接口
在OrderSettingService服务接口中扩展方法getOrderSettingByMonth
List<Map> getOrderSettingByMonth(String date); //参数格式为:2019-03
8.2.3. 服务实现类
在OrderSettingServiceImpl服务实现类中实现方法getOrderSettingByMonth
//根据日期查询预约设置数据
public List<Map> getOrderSettingByMonth(String date) {//2019-03
// 1.组织查询Map,dateBegin表示月份开始时间,dateEnd月份结束时间
String dateBegin = date + "-1";//2019-03-1
String dateEnd = date + "-31";//2019-03-31
Map map = new HashMap();
map.put("dateBegin",dateBegin);
map.put("dateEnd",dateEnd);
// 2.查询当前月份的预约设置
List<OrderSetting> list = orderSettingDao.getOrderSettingByMonth(map);
List<Map> data = new ArrayList<>();
// 3.将List<OrderSetting>,组织成List<Map>
for (OrderSetting orderSetting : list) {
Map orderSettingMap = new HashMap();
orderSettingMap.put("date",orderSetting.getOrderDate().getDate());//获得日期(几号)
orderSettingMap.put("number",orderSetting.getNumber());//可预约人数
orderSettingMap.put("reservations",orderSetting.getReservations());//已预约人数
data.add(orderSettingMap);
}
return data;
}
8.2.4. Dao接口
在OrderSettingDao接口中扩展方法getOrderSettingByMonth
List<OrderSetting> getOrderSettingByMonth(Map map);
8.2.5. Mapper映射文件
在OrderSettingDao.xml文件中扩展SQL
<!--根据月份查询预约设置信息-->
<select id="getOrderSettingByMonth"
parameterType="hashmap"
resultType="com.itheima.pojo.OrderSetting">
select * from t_ordersetting where orderDate between #{dateBegin} and #{dateEnd}
</select>
也可以使用sql语句:SELECT * FROM t_ordersetting WHERE orderDate LIKE '2019-08-%'
8.3. 初始化下个月,上个月数据

(1)点击事件
<div class="choose">
<span @click="goCurrentMonth(currentYear,currentMonth)" class="gotoday">今天</span>
<span @click="pickPre(currentYear,currentMonth)">❮</span>
<span @click="pickNext(currentYear,currentMonth)">❯</span>
</div>
(2)初始化日期数据(今天、上个月、下个月):
//切换到当前月份
goCurrentMonth: function (year, month) {
var d = new Date();
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
this.createdData();
},
//向前一个月
pickPre: function (year, month) {
// setDate(0); 上月最后一天
// setDate(-1); 上月倒数第二天
// setDate(dx) 参数dx为 上月最后一天的前后dx天
var d = new Date(this.formatDate(year, month, 1));
d.setDate(0);
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
this.createdData();
},
//向后一个月
pickNext: function (year, month) {
var d = new Date(this.formatDate(year, month, 1));
d.setDate(35);////获取指定天之后的日期
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
this.createdData();
},
分别执行this.createDate();表示初始化数据。
【小结】
1:前台代码
(1)使用静态数据调试
预约设置数据对应的模型数据为leftobj,在initData方法最后为leftobj模型数据赋值:
this.leftobj = [
{ date: 1, number: 120, reservations: 1 },
{ date: 3, number: 120, reservations: 1 },
{ date: 4, number: 120, reservations: 120 },
{ date: 6, number: 120, reservations: 1 },
{ date: 8, number: 120, reservations: 1 }
];
其中date表示日期,number表示可预约人数,reservations表示已预约人数
使用VUE的v-for标签遍历上面的leftobj模型数据,展示到日历上:
<template>
<template v-for="obj in leftobj">
<template v-if="obj.date dayobject.day.getDate()">
<template v-if="obj.number > obj.reservations">
<div class="usual">
<p>可预约{{obj.number}}人</p>
<p>已预约{{obj.reservations}}人</p>
</div>
</template>
<template v-else>
<div class="fulled">
<p>可预约{{obj.number}}人</p>
<p>已预约{{obj.reservations}}人</p>
<p>已满</p>
</div>
</template>
</template>
</template>
<button v-if="dayobject.day > today" @click="handleOrderSet(dayobject.day)" class="orderbtn">设置</button>
</template>
(2)发送ajax获取动态数据
2:后台代码
业务:
- 在页面上,使用日历展示预约设置信息
(1)OrderSettingController.java(Controller)
(2)OrderSettingService.java(服务接口)
(3)OrderSettingServiceImpl.java(服务实现类)
(4)OrderSettingDao.java(Dao接口)
(5)OrderSettingDao.xml(Mapper映射文件)
查询当前月份的预约设置(前端传递当前的年-月: 2019-06)
SELECT * FROM t_ordersetting WHERE orderDate LIKE '2019-06-%'
或者
SELECT * FROM t_ordersetting WHERE orderDate BETWEEN '2019-06-01' AND '2019-06-31'
页面需要的数据,使用List
[
{date: 1, number: 120, reservations: 1},
{date: 3, number: 120, reservations: 1},
{date: 4, number: 120, reservations: 120},
{date: 6, number: 120, reservations: 1},
{date: 8, number: 120, reservations: 1}
]
3:初始化下个月,上个月数据
9.基于日历实现预约设置-作业
【目标】
日历展示预约设置信息,点击【设置】按钮完成针对当前时间进行设置,设置最多可预约的人数
【路径】
1:前台代码
(1)为设置按钮绑定事件
(2)弹出预约设置窗口,并发送ajax请求
2:后台代码
业务:
- 在页面上,基于日历实现预约设置
(1)OrderSettingController.java(Controller)
(2)OrderSettingService.java(服务接口)
(3)OrderSettingServiceImpl.java(服务实现类)
(4)OrderSettingDao.java(Dao接口)
(5)OrderSettingDao.xml(Mapper映射文件)
【讲解】
【需求】
本章节要完成的功能为通过点击日历中的设置按钮来设置对应日期的可预约人数。效果如下:

9.1. 前台代码
9.1.1. 为设置按钮绑定事件
(1)为日历中的设置按钮绑定单击事件,当前日期作为参数
<button v-if="dayobject.day > today" @click="handleOrderSet(dayobject.day)" class="orderbtn">设置</button>
(2)handleOrderset()方法
//预约设置
handleOrderSet(day){
alert(day);
},
9.1.2. 弹出预约设置窗口并发送ajax请求
完善handleOrderSet方法,弹出预约设置窗口,用户点击确定按钮则发送ajax请求
参考:$prompt

//预约设置
handleOrderSet(day){
//alert(day);
this.$prompt('请输入可预约人数', '预约设置', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[0-9]*[1-9][0-9]*$/,
inputErrorMessage: '只能输入正整数'
}).then(({ value }) => {
//发送ajax请求根据日期修改可预约人数
axios.post("/ordersetting/editNumberByDate.do",{
orderDate:this.formatDate(day.getFullYear(),day.getMonth()+1,day.getDate()), //日期
number:value //可预约人数
}).then((response)=>{
if(response.data.flag){
this.createdData();
this.$message({
type: 'success',
message: response.data.message
});
}else{
this.$message.error(response.data.message);
}
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消'
});
});
},
9.2. 后台代码
9.2.1. Controller
在OrderSettingController中提供方法editNumberByDate
/**
* 根据指定日期修改可预约人数
* @param orderSetting
* @return
*/
@RequestMapping("/editNumberByDate")
public Result editNumberByDate(@RequestBody OrderSetting orderSetting){
try{
orderSettingService.editNumberByDate(orderSetting);
//预约设置成功
return new Result(true,MessageConstant.ORDERSETTING_SUCCESS);
}catch (Exception e){
e.printStackTrace();
//预约设置失败
return new Result(false,MessageConstant.ORDERSETTING_FAIL);
}
}
9.2.2. Service服务实现类
在OrderSettingService服务接口中提供方法editNumberByDate
void editNumberByDate(OrderSetting orderSetting);
在OrderSettingServiceImpl服务实现类中实现editNumberByDate
//根据日期修改可预约人数
public void editNumberByDate(OrderSetting orderSetting) {
long count = orderSettingDao.findCountByOrderDate(orderSetting.getOrderDate());
if(count > 0){
//当前日期已经进行了预约设置,需要进行修改操作
orderSettingDao.editNumberByOrderDate(orderSetting);
}else{
//当前日期没有进行预约设置,进行添加操作
orderSettingDao.add(orderSetting);
}
}
9.2.3. Dao接口
在OrderSettingDao接口中提供方法(上面已经完成)
long findCountByOrderDate(Date orderDate);
void editNumberByOrderDate(OrderSetting orderSetting);
void add(OrderSetting orderSetting);
9.2.4. Mapper映射文件
在OrderSettingDao.xml映射文件中提供SQL(上面已经完成)
<!--根据日期更新预约人数-->
<update id="editNumberByOrderDate" parameterType="com.itheima.pojo.OrderSetting">
update t_ordersetting set number = #{number} where orderDate = #{orderDate}
</update>
<!--根据预约日期查询-->
<select id="findCountByOrderDate" parameterType="java.util.Date" resultType="long">
select count(*) from t_ordersetting where orderDate = #{orderDate}
</select>
<!--新增-->
<insert id="add" parameterType="com.itheima.pojo.OrderSetting">
insert into t_ordersetting
(orderDate,number,reservations)
values
(#{orderDate},#{number},#{reservations})
</insert>
【小结】
1:前台代码
(1)为设置按钮绑定事件
(2)弹出预约设置窗口,并发送ajax请求
2:后台代码
业务:
- 在页面上,基于日历实现预约设置
(1)OrderSettingController.java(Controller)
(2)OrderSettingService.java(服务接口)
(3)OrderSettingServiceImpl.java(服务实现类)
(4)OrderSettingDao.java(Dao接口)
(5)OrderSettingDao.xml(Mapper映射文件)
作业
1.套餐编辑、删除、分页查询
2.日历展示预约设置信息-作业
3.基于日历实现预约设置-作业
总结
1.跳过测试

2.新增套餐-重点关注 图片回显 以及 提交表单时候需要 图片名称-重点掌握
3.Quartz定时任务调度框架-重点掌握
4.批量预约设置=重点掌握
浙公网安备 33010602011771号