1. 图片存储方案
七牛云存储图片服务器使用:https://blog.csdn.net/ZGL_cyy/article/details/111935958
2. 新增套餐
2.1 需求分析
套餐其实就是检查组的集合,例如有一个套餐为“入职体检套餐”,这个体检套餐可以包括多个检查组:一般检查、血常规、尿常规、肝功三项等。所以在添加套餐时需要选择这个套餐包括的检查组。
套餐对应的实体类为Setmeal,对应的数据表为t_setmeal。套餐和检查组为多对多关系,所以需要中间表t_setmeal_checkgroup进行关联。
一个套餐 有多个检查组,一个检查组又有多个检查项
2.2 完善页面
套餐管理页面对应的是setmeal.html页面,根据产品设计的原型已经完成了页面基本结构的编写,现在需要完善页面动态效果。
2.2.1 弹出新增窗口
页面中已经提供了新增窗口,只是出于隐藏状态。只需要将控制展示状态的属性dialogFormVisible改为true接口显示出新增窗口。点击新建按钮时绑定的方法为handleCreate,所以在handleCreate方法中修改dialogFormVisible属性的值为true即可。同时为了增加用户体验度,需要每次点击新建按钮时清空表单输入项。
由于新增套餐时还需要选择此套餐包含的检查组,所以新增套餐窗口分为两部分信息:基本信息和检查组信息,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AJ2RFCAw-1609427434542)(img/3.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g10YjD93-1609427434554)(img/4.png)]
新建按钮绑定单击事件,对应的处理函数为handleCreate
<el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
// 重置表单
resetForm() {
this.formData = {};
this.activeName='first';
this.checkgroupIds = [];
this.imageUrl = null;
}
// 弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
}
2.2.2 动态展示检查组列表
现在虽然已经完成了新增窗口的弹出,但是在检查组信息标签页中需要动态展示所有的检查组信息列表数据,并且可以进行勾选。具体操作步骤如下:
(1)定义模型数据
tableData:[],//添加表单窗口中检查组列表数据
checkgroupIds:[],//添加表单窗口中检查组复选框对应id
(2)动态展示检查组列表数据,数据来源于上面定义的tableData模型数据
<table class="datatable">
<thead>
<tr>
<th>选择</th>
<th>项目编码</th>
<th>项目名称</th>
<th>项目说明</th>
</tr>
</thead>
<tbody>
<tr v-for="c in tableData">
<td>
<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>
(3)完善handleCreate方法,发送ajax请求查询所有检查组数据并将结果赋值给tableData模型数据用于页面表格展示
// 弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
axios.get("/checkgroup/findAll.do").then((res)=> {
if(res.data.flag){
this.tableData = res.data.data;
}else{
this.$message.error(res.data.message);
}
});
}
(4)分别在CheckGroupController、CheckGroupService、CheckGroupServiceImpl、CheckGroupDao、CheckGroupDao.xml中扩展方法查询所有检查组数据
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);
result.setData(checkGroupList);
return result;
}
return new Result(false,MessageConstant.QUERY_CHECKGROUP_FAIL);
}
CheckGroupService:
List<CheckGroup> findAll();
CheckGroupServiceImpl:
public List<CheckGroup> findAll() {
return checkGroupDao.findAll();
}
CheckGroupDao:
List<CheckGroup> findAll();
CheckGroupDao.xml:
<select id="findAll" resultType="com.itheima.pojo.CheckGroup">
select * from t_checkgroup
</select>
大作业:
1. 查询所有检查组
1. redis
2. 新增 编辑检查组的时候,更新redis中的数据
2. 查询所有检查项
1. redis
2. 新增 编辑检查组的时候,更新redis中的数据
2.2.3 图片上传并预览
此处使用的是ElementUI提供的上传组件el-upload,提供了多种不同的上传效果,上传成功后可以进行预览。
实现步骤:
(1)定义模型数据,用于后面上传文件的图片预览:
imageUrl:null,//模型数据,用于上传图片完成后图片预览
(2)定义上传组件:
<!--
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) {
this.imageUrl = "http://pqjroc654.bkt.clouddn.com/"+response.data;
this.$message({
message: response.message,
type: response.flag ? 'success' : 'error'
});
//设置模型数据(图片名称),后续提交ajax请求时会提交到后台最终保存到数据库
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,接收上传的文件
package com.itheima.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.itheima.constant.MessageConstant;
import com.itheima.entity.PageResult;
import com.itheima.entity.QueryPageBean;
import com.itheima.entity.Result;
import com.itheima.pojo.CheckGroup;
import com.itheima.pojo.Setmeal;
import com.itheima.service.CheckGroupService;
import com.itheima.service.SetmealService;
import com.itheima.utils.QiniuUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.List;
import java.util.UUID;
/**
* 套餐管理
*/
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Reference
private SetmealService setmealService;
//图片上传
@RequestMapping("/upload")
public Result upload(@RequestParam("imgFile")MultipartFile imgFile){
try{
//获取原始文件名
String originalFilename = imgFile.getOriginalFilename();
int lastIndexOf = originalFilename.lastIndexOf(".");
//获取文件后缀
String suffix = originalFilename.substring(lastIndexOf);
//使用UUID随机产生文件名称,防止同名文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);
//图片上传成功
Result result = new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS);
result.setData(fileName);
return result;
}catch (Exception e){
e.printStackTrace();
//图片上传失败
return new Result(false,MessageConstant.PIC_UPLOAD_FAIL);
}
}
}
注意:别忘了在spring配置文件中配置文件上传组件
<!--文件上传组件-->
<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>
2.2.4 提交请求
当用户点击新增窗口中的确定按钮时发送ajax请求将数据提交到后台进行数据库操作。提交到后台的数据分为两部分:套餐基本信息(对应的模型数据为formData)和检查组id数组(对应的模型数据为checkgroupIds)。
为确定按钮绑定单击事件,对应的处理函数为handleAdd
<el-button type="primary" @click="handleAdd()">确定</el-button>
完善handleAdd方法
//添加
handleAdd () {
axios.post("/setmeal/add.do?checkgroupIds=" + this.checkgroupIds,this.formData).
then((response)=> {
this.dialogFormVisible = false;
if(response.data.flag){
this.$message({
message: response.data.message,
type: 'success'
});
}else{
this.$message.error(response.data.message);
}
}).finally(()=> {
this.findPage();
});
}
2.3 后台代码
2.3.1 Controller
在SetmealController中增加方法
//新增
@RequestMapping("/add")
public Result add(@RequestBody Setmeal setmeal, Integer[] checkgroupIds){
try {
setmealService.add(setmeal,checkgroupIds);
}catch (Exception e){
//新增套餐失败
return new Result(false,MessageConstant.ADD_SETMEAL_FAIL);
}
//新增套餐成功
return new Result(true,MessageConstant.ADD_SETMEAL_SUCCESS);
}
2.3.2 服务接口
创建SetmealService接口并提供新增方法
package com.itheima.service;
import com.itheima.entity.PageResult;
import com.itheima.pojo.CheckGroup;
import com.itheima.pojo.Setmeal;
import java.util.List;
/**
* 体检套餐服务接口
*/
public interface SetmealService {
public void add(Setmeal setmeal, Integer[] checkgroupIds);
}
2.3.3 服务实现类
创建SetmealServiceImpl服务实现类并实现新增方法
package com.itheima.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.itheima.dao.SetmealDao;
import com.itheima.entity.PageResult;
import com.itheima.pojo.Setmeal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 体检套餐服务实现类
*/
@Service(interfaceClass = SetmealService.class)
@Transactional
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealDao setmealDao;
//新增套餐
public void add(Setmeal setmeal, Integer[] checkgroupIds) {
setmealDao.add(setmeal);
if(checkgroupIds != null && checkgroupIds.length > 0){
//绑定套餐和检查组的多对多关系
setSetmealAndCheckGroup(setmeal.getId(),checkgroupIds);
}
}
//绑定套餐和检查组的多对多关系
private void setSetmealAndCheckGroup(Integer id, Integer[] checkgroupIds) {
for (Integer checkgroupId : checkgroupIds) {
Map<String,Integer> map = new HashMap<>();
map.put("setmeal_id",id);
map.put("checkgroup_id",checkgroupId);
setmealDao.setSetmealAndCheckGroup(map);
}
}
}
2.3.4 Dao接口
创建SetmealDao接口并提供相关方法
package com.itheima.dao;
import com.itheima.pojo.Setmeal;
import java.util.Map;
public interface SetmealDao {
public void add(Setmeal setmeal);
public void setSetmealAndCheckGroup(Map<String, Integer> map);
}
2.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">
<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
SELECT LAST_INSERT_ID()
</selectKey>
insert into t_setmeal
(code,name,sex,age,helpCode,price,remark,attention,img)
values
(#{code},#{name},#{sex},#{age},#{helpCode},#{price},#{remark},#{attention},#{img})
</insert>
<!--绑定套餐和检查组多对多关系-->
<insert id="setSetmealAndCheckGroup" parameterType="hashmap">
insert into t_setmeal_checkgroup
(setmeal_id,checkgroup_id)
values
(#{setmeal_id},#{checkgroup_id})
</insert>
</mapper>
2.4 完善文件上传
前面我们已经完成了文件上传,将图片存储在了七牛云服务器中。但是这个过程存在一个问题,就是如果用户只上传了图片而没有最终保存套餐信息到我们的数据库,这时我们上传的图片就变为了垃圾图片。对于这些垃圾图片我们需要定时清理来释放磁盘空间。这就需要我们能够区分出来哪些是垃圾图片,哪些不是垃圾图片。如何实现呢?
方案就是利用redis来保存图片名称,具体做法为:
1、当用户上传图片后,将图片名称保存到redis的一个Set集合中,例如集合名称为setmealPicResources
2、当用户添加套餐后,将图片名称保存到redis的另一个Set集合中,例如集合名称为setmealPicDbResources
3、计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称集合,清理这些图片即可
diff
redis set diff(多的集合,少的集合)= 垃圾图片的集合
本小节我们先来完成前面2个环节,第3个环节(清理图片环节)在后面会通过定时任务再实现。
实现步骤:
(1)在health_backend项目中提供Spring配置文件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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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>
(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";
}
(3)完善SetmealController,在文件上传成功后将图片名称保存到redis集合中
@Autowired
private JedisPool jedisPool;
//图片上传
@RequestMapping("/upload")
public Result upload(@RequestParam("imgFile")MultipartFile imgFile){
try{
//获取原始文件名
String originalFilename = imgFile.getOriginalFilename();
int lastIndexOf = originalFilename.lastIndexOf(".");
//获取文件后缀
String suffix = originalFilename.substring(lastIndexOf - 1);
//使用UUID随机产生文件名称,防止同名文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);
//图片上传成功
Result result = new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS);
result.setData(fileName);
//将上传图片名称存入Redis,基于Redis的Set集合存储
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,fileName);
return result;
}catch (Exception e){
e.printStackTrace();
//图片上传失败
return new Result(false,MessageConstant.PIC_UPLOAD_FAIL);
}
}
(4)在health_service_provider项目中提供Spring配置文件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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.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>
(5)完善SetmealServiceImpl服务类,在保存完成套餐信息后将图片名称存储到redis集合中
@Autowired
private JedisPool jedisPool;
//新增套餐
public void add(Setmeal setmeal, Integer[] checkgroupIds) {
setmealDao.add(setmeal);
if(checkgroupIds != null && checkgroupIds.length > 0){
setSetmealAndCheckGroup(setmeal.getId(),checkgroupIds);
}
//将图片名称保存到Redis
savePic2Redis(setmeal.getImg());
}
//将图片名称保存到Redis
private void savePic2Redis(String pic){
jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_DB_RESOURCES,pic);
}
小结
-
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>
-
MultipartFile接收文件
-
dubbo服务不能接受MultipartFile作为参数
-
redis的使用,一般使用jedis来操作redis
-
jedisPool.getResource().sadd(), redis的数据类型:Set集合,去重
-
redis 127.0.0.1:6379> SADD myset "hello" (integer) 1 redis 127.0.0.1:6379> SADD myset "foo" (integer) 1 redis 127.0.0.1:6379> SADD myset "hello" (integer) 0 redis 127.0.0.1:6379> SMEMBERS myset 1) "hello" 2) "foo"
-
-
3. 体检套餐分页
3.1 完善页面
3.1.1 定义分页相关模型数据
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:10,//每页显示的记录数
total:0,//总记录数
queryString:null//查询条件
},
dataList: [],//当前页要展示的分页列表数据
3.1.2 定义分页方法
在页面中提供了findPage方法用于分页查询,为了能够在setmeal.html页面加载后直接可以展示分页数据,可以在VUE提供的钩子函数created中调用findPage方法
//钩子函数,VUE对象初始化完成后自动执行
created() {
this.findPage();
}
//分页查询
findPage() {
//分页参数
var param = {
currentPage:this.pagination.currentPage,//页码
pageSize:this.pagination.pageSize,//每页显示的记录数
queryString:this.pagination.queryString//查询条件
};
//请求后台
axios.post("/setmeal/findPage.do",param).then((response)=> {
//为模型数据赋值,基于VUE的双向绑定展示到页面
this.dataList = response.data.rows;
this.pagination.total = response.data.total;
});
}
3.1.3 完善分页方法执行时机
除了在created钩子函数中调用findPage方法查询分页数据之外,当用户点击查询按钮或者点击分页条中的页码时也需要调用findPage方法重新发起查询请求。
为查询按钮绑定单击事件,调用findPage方法
<el-button @click="findPage()" class="dalfBut">查询</el-button>
为分页条组件绑定current-change事件,此事件是分页条组件自己定义的事件,当页码改变时触发,对应的处理函数为handleCurrentChange
<el-pagination
class="pagiantion"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
layout="total, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
定义handleCurrentChange方法
//切换页码
handleCurrentChange(currentPage) {
//currentPage为切换后的页码
this.pagination.currentPage = currentPage;
this.findPage();
}
3.2 后台代码
3.2.1 Controller
在SetmealController中增加分页查询方法
//分页查询
@RequestMapping("/findPage")
public PageResult findPage(@RequestBody QueryPageBean queryPageBean){
PageResult pageResult = setmealService.pageQuery(
queryPageBean.getCurrentPage(),
queryPageBean.getPageSize(),
queryPageBean.getQueryString()
);
return pageResult;
}
3.2.2 服务接口
在SetmealService服务接口中扩展分页查询方法
public PageResult pageQuery(Integer currentPage, Integer pageSize, String queryString);
3.2.3 服务实现类
在SetmealServiceImpl服务实现类中实现分页查询方法,基于Mybatis分页助手插件实现分页
public PageResult pageQuery(Integer currentPage, Integer pageSize, String queryString) {
PageHelper.startPage(currentPage,pageSize);
Page<CheckItem> page = checkGroupDao.selectByCondition(queryString);
return new PageResult(page.getTotal(),page.getResult());
}
3.2.4 Dao接口
在SetmealDao接口中扩展分页查询方法
public Page<Setmeal> selectByCondition(String queryString);
public Page<Setmeal> selectByCondition(@Param("queryString") String queryString);
3.2.5 Mapper映射文件
在SetmealDao.xml文件中增加SQL定义
<!--根据条件查询-->
<select id="selectByCondition" parameterType="string" resultType="com.itheima.pojo.Setmeal">
select * from t_setmeal
<if test="value != null and value.length > 0">
where code = #{value} or name = #{value} or helpCode = #{value}
</if>
</select>
4. 定时任务组件Quartz
Quartz定时任务组件:https://blog.csdn.net/ZGL_cyy/article/details/111935988