1. 图片存储方案

1.1 介绍

在实际开发中,我们会有很多处理不同功能的服务器。例如:

应用服务器:负责部署我们的应用

数据库服务器:运行我们的数据库

文件服务器:负责存储用户上传文件的服务器

分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

常见的图片存储方案:

方案一:使用nginx搭建图片服务器

方案二:使用开源的分布式文件存储系统,例如Fastdfs、HDFS等

方案三:使用云存储,例如阿里云、七牛云等

1.2 七牛云存储

七牛云(隶属于上海七牛信息技术有限公司)是国内领先的以视觉智能和数据智能为核心的企业级云计 算服务商,同时也是国内知名智能视频云服务商,累计为 70 多万家企业提供服务,覆盖了国内80%网 民。围绕富媒体场景推出了对象存储、融合 CDN 加速、容器云、大数据平台、深度学习平台等产品、 并提供一站式智能视频云解决方案。为各行业及应用提供可持续发展的智能视频云生态,帮助企业快速 上云,创造更广阔的商业价值。

官网:https://www.qiniu.com/

通过七牛云官网介绍我们可以知道其提供了多种服务,我们主要使用的是七牛云提供的对象存储服务来 存储图片。

1.2.1 注册、登录

要使用七牛云的服务,首先需要注册成为会员。地址:https://portal.qiniu.com/signup

注册完成后就可以使用刚刚注册的邮箱和密码登录到七牛云:

登录成功后根据提示进行实名认证,实名认证成功后点击页面右上角管理控制台:

1.2.2 新建存储空间

要进行图片存储,我们需要在七牛云管理控制台新建存储空间。点击管理控制台首页对象存储下的立即 添加按钮,页面跳转到新建存储空间页面:

可以创建多个存储空间,各个存储空间是相互独立的。

1.2.3 查看存储空间信息

存储空间创建后,会在左侧的空间管理列表菜单中展示创建的存储空间名称,点击存储空间名称可以查看当前存储空间的相关信息

1.2.4 开发者中心

可以通过七牛云提供的开发者中心学习如何操作七牛云服务,地址:https://developer.qiniu.com/

点击对象存储,跳转到对象存储开发页面,地址:https://developer.qiniu.com/kodo

七牛云提供了多种方式操作对象存储服务,本项目采用ava SDK方式,地址:https://developer.qiniu.com/kodo/1239/java

使用Java SDK操作七牛云需要导入如下maven坐标:

<dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>7.7.0</version>
</dependency>

1.2.5 鉴权

Java SDK的所有的功能,都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的Access Key 和Secret Key,这对密钥可以在七牛云管理控制台的个人中心(https://portal.qiniu.com/user/key)获得,如下图:

1.2.6 Java SDK操作七牛云

本章节我们就需要使用七牛云提供的Java SDK完成图片上传和删除,我们可以参考官方提供的例子。

//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String accessKey = "your access key";
String secretKey = "your secret key";
String bucket = "your bucket name";
//如果是Windows情况下,格式是 D:\\qiniu\\test.png
String localFilePath = "/home/qiniu/test.png";
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = null;
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {Response response = uploadManager.put(localFilePath, key, upToken);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);System.out.println(putRet.key);System.out.println(putRet.hash);
} catch (QiniuException ex) {Response r = ex.response;System.err.println(r.toString());try {System.err.println(r.bodyString());} catch (QiniuException ex2) {//ignore}
}
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
//...其他参数参考类注释
String accessKey = "your access key";
String secretKey = "your secret key";
String bucket = "your bucket name";
String key = "your file key";
Auth auth = Auth.create(accessKey, secretKey);
BucketManager bucketManager = new BucketManager(auth, cfg);
try {bucketManager.delete(bucket, key);
} catch (QiniuException ex) {//如果遇到异常,说明删除失败System.err.println(ex.code());System.err.println(ex.response.toString());
}

1.2.7 封装工具类

为了方便操作七牛云存储服务,我们可以将官方提供的案例简单改造成一个工具类,在我们的项目中直接使用此工具类来操作就可以:

package com.itterence.utils;import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;/*** 七牛云工具类*/
public class QiniuUtils {public static String accessKey = "G78D_qC2JG3V9WBW9lxysK9OZeslRJtJZI85orM_";public static String secretKey = "-ptaWzGugFgUbFNBu8OWoSqbaTfiAPvdz9EJaLtr";public static String bucket = "itterence-health";public static void upload2Qiniu(String filePath,String fileName){//构造一个带指定Zone对象的配置类Configuration cfg = new Configuration(Zone.zone1());UploadManager uploadManager = new UploadManager(cfg);Auth auth = Auth.create(accessKey, secretKey);String upToken = auth.uploadToken(bucket);try {Response response = uploadManager.put(filePath, fileName, upToken);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);} catch (QiniuException ex) {Response r = ex.response;try {System.err.println(r.bodyString());} catch (QiniuException ex2) {//ignore}}}//上传文件public static void upload2Qiniu(byte[] bytes, String fileName){//构造一个带指定Zone对象的配置类Configuration cfg = new Configuration(Zone.zone1());//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//默认不指定key的情况下,以文件内容的hash值作为文件名String key = fileName;Auth auth = Auth.create(accessKey, secretKey);String upToken = auth.uploadToken(bucket);try {Response response = uploadManager.put(bytes, key, upToken);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);System.out.println(putRet.key);System.out.println(putRet.hash);} catch (QiniuException ex) {Response r = ex.response;System.err.println(r.toString());try {System.err.println(r.bodyString());} catch (QiniuException ex2) {//ignore}}}//删除文件public static void deleteFileFromQiniu(String fileName){//构造一个带指定Zone对象的配置类Configuration cfg = new Configuration(Zone.zone1());String key = fileName;Auth auth = Auth.create(accessKey, secretKey);BucketManager bucketManager = new BucketManager(auth, cfg);try {bucketManager.delete(bucket, key);} catch (QiniuException ex) {//如果遇到异常,说明删除失败System.err.println(ex.code());System.err.println(ex.response.toString());}}
}

注:此处我遇到了maven依赖的问题,在test目录下import com.google.gson.Gson;没问题,在java目录中报红色错误,提示没有依赖,然后我查看maven工具内,这个包的状态为runtime,这就是无法编译问题所在

最终在父工程pom.xml中 添加了com.google.code.gson包

<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version><scope>compile</scope>
</dependency>

将此工具类放在health_common工程中,后续会使用到。

2. 新增套餐

2.1 需求分析

套餐其实就是检查组的集合,例如有一个套餐为“入职体检套餐”,这个体检套餐可以包括多个检查组:一般检查、血常规、尿常规、肝功三项等。所以在添加套餐时需要选择这个套餐包括的检查组。

套餐对应的实体类为Setmeal,对应的数据表为t_setmeal。套餐和检查组为多对多关系,所以需要中间表t_setmeal_checkgroup进行关联。

2.2 完善页面

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

2.2.1 弹出新增窗口

页面中已经提供了新增窗口,只是出于隐藏状态。只需要将控制展示状态的属性dialogFormVisible改为true接口显示出新增窗口。点击新建按钮时绑定的方法为handleCreate,所以在handleCreate方法中修改dialogFormVisible属性的值为true即可。同时为了增加用户体验度,需要每次点击新建按钮时清空表单输入项。

由于新增套餐时还需要选择此套餐包含的检查组,所以新增套餐窗口分为两部分信息:基本信息和检查 组信息,如下图:

新建按钮绑定单击事件,对应的处理函数为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.itterence.pojo.CheckGroup">select * from t_checkgroup
</select>

2.2.3 图片上传并预览

此处使用的是ElementUI提供的上传组件el-upload,提供了多种不同的上传效果,上传成功后可以进行预览。

实现步骤:

(1)定义模型数据,用于后面上传文件的图片预览:

imageUrl:null,//模型数据,用于上传图片完成后图片预览

(2)定义上传组件:

<!--el-upload:上传组件action:上传的提交地址auto-upload:选中文件后是否自动上传name:上传文件的名称,服务端可以根据名称获得上传的文件对象show-file-list:是否显示已上传文件列表on-success:文件上传成功时的钩子before-upload:上传文件之前的钩子
--><el-uploadclass="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="data:imageUrl" class="avatar"><i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>

(3)定义对应的钩子函数:

//文件上传成功后的钩子,response为服务端返回的值,file为当前上传的文件封装成的js对象
handleAvatarSuccess(response, file) {//为模型数据imageUrl赋值,用于页面图片预览this.imageUrl = 'http://r5u9a5ajg.hb-bkt.clouddn.com/' + response.data;this.$message({type:response.flag ? 'success':'error',message:response.message});//设置模型数据(图片名称),后续提交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.itterence.controller;import com.itterence.constant.MessageConstant;
import com.itterence.entity.Result;
import com.itterence.utils.QiniuUtils;
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 java.io.IOException;
import java.util.UUID;/*** 体检套餐管理*/@RestController
@RequestMapping("/setmeal")
public class SetmealController {//文件上传@RequestMapping("/upload")public Result upload(@RequestParam("imgFile") MultipartFile imgFile){System.out.println(imgFile);String originalFilename = imgFile.getOriginalFilename();//原始文件名 3bd90d2c-4e82-42a1-a401-882c88b06a1a2.jpgint lastIndex= originalFilename.lastIndexOf(".");String extention = originalFilename.substring(lastIndex);//.jpgString fileName = UUID.randomUUID().toString() + extention;//    FuM1Sa5TtL_ekLsdkYWcf5pyjKGu.jpgtry {//将文件上传到七牛云服务器QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);} catch (IOException e) {e.printStackTrace();return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);}return new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS,fileName);}}

注意:别忘了在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 () {//发送ajax请求,将表单数据(套餐基本信息、检查组ID)提交到后台进行处理axios.post("/setmeal/add.do?checkgroupIds=" + this.checkgroupIds,this.formData).then((res) => {//关闭新增窗口this.dialogFormVisible = false;if(res.data.flag){//执行成功this.$message({type:'success',message:res.data.message});}else{//执行失败this.$message.error(res.data.message);}}).finally(() => {this.findPage();});
},

2.3 后台代码

2.3.1 Controller

在SetmealController中增加方法

@Reference
private SetmealService setmealService;//新增套餐
@RequestMapping("/add")
public Result add(@RequestBody Setmeal setmeal, Integer[] checkgroupIds){try{setmealService.add(setmeal,checkgroupIds);}catch (Exception e){e.printStackTrace();return new Result(false,MessageConstant.ADD_SETMEAL_FAIL);}return new Result(true,MessageConstant.ADD_SETMEAL_SUCCESS);
}

2.3.2 服务接口

创建SetmealService接口并提供新增方法

package com.itterence.service;import com.itterence.pojo.Setmeal;/*** 体检套餐服务接口*/
public interface SetmealService {public void add(Setmeal setmeal, Integer[] checkgroupIds);
}

2.3.3 服务实现类

创建SetmealServiceImpl服务实现类并实现新增方法

package com.itterence.service.impl;import com.alibaba.dubbo.config.annotation.Service;
import com.itterence.dao.SetmealDao;
import com.itterence.pojo.Setmeal;
import com.itterence.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;/*** 体检套餐服务*/
@Service(interfaceClass = SetmealService.class)
@Transactional
public class SetmealServiceImpl implements SetmealService{@Autowiredprivate SetmealDao setmealDao;//新增套餐信息,同时需要关联检查组public void add(Setmeal setmeal, Integer[] checkgroupIds) {setmealDao.add(setmeal);this.setSetmealAndCheckGroup(setmeal.getId(),checkgroupIds);}//设置套餐和检查组多对多关系,操作t_setmeal_checkgrouppublic void setSetmealAndCheckGroup(Integer setmealId,Integer[] checkgroupIds){if(checkgroupIds != null && checkgroupIds.length > 0){for (Integer checkgroupId : checkgroupIds) {Map<String,Integer> map = new HashMap<>();map.put("setmealId",setmealId);map.put("checkgroupId",checkgroupId);setmealDao.setSetmealAndCheckGroup(map);}}}
}

2.3.4 Dao接口

创建SetmealDao接口并提供相关方法

package com.itterence.dao;import com.itterence.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.itterence.dao.SetmealDao"><!--插入套餐数据--><insert id="add" parameterType="com.itterence.pojo.Setmeal"><!--通过mybatis框架提供的selectKey标签获得自增产生的ID值--><selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">select LAST_INSERT_ID()</selectKey>insert into t_setmeal(code,name,sex,helpCode,remark,attention,age,price,img)values(#{code},#{name},#{sex},#{helpCode},#{remark},#{attention},#{age},#{price},#{img})</insert><!--设置套餐和检查组多对多关系--><insert id="setSetmealAndCheckGroup" parameterType="map">insert into t_setmeal_checkgroup(setmeal_id,checkgroup_id)values(#{setmealId},#{checkgroupId})</insert></mapper>

2.4 完善文件上传

前面我们已经完成了文件上传,将图片存储在了七牛云服务器中。但是这个过程存在一个问题,就是如果用户只上传了图片而没有最终保存套餐信息到我们的数据库,这时我们上传的图片就变为了垃圾图片。对于这些垃圾图片我们需要定时清理来释放磁盘空间。这就需要我们能够区分出来哪些是垃圾图片,哪些不是垃圾图片。如何实现呢?

方案就是利用redis来保存图片名称,具体做法为:

1、当用户上传图片后,将图片名称保存到redis的一个Set集合中,例如集合名称为 setmealPicResources

2、当用户添加套餐后,将图片名称保存到redis的另一个Set集合中,例如集合名称为 setmealPicDbResources

3、计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称集合,清理这些图片即可 本小节我们先来完成前面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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsdhttp://www.springframework.org/schema/contexthttp://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>

在health_backend项目中spring-mvc.xml中引用spring-redis.xml

<import resource="spring-redis.xml"/>

(2)在health_common工程中提供Redis常量类

package com.itterence.constant;public class RedisConstant {//套餐图片所有图片名称public static final String SETMEAL_PIC_RESOURCES = "setmealPicResources";//套餐图片保存在数据库中的图片名称public static final String SETMEAL_PIC_DB_RESOURCES = "setmealPicDbResources";
}

(3)完善SetmealController,在文件上传成功后将图片名称保存到redis集合中

//使用JedisPool操作Redis服务
@Autowired
private JedisPool jedisPool;//文件上传
@RequestMapping("/upload")
public Result upload(@RequestParam("imgFile") MultipartFile imgFile){System.out.println(imgFile);String originalFilename = imgFile.getOriginalFilename();//原始文件名 3bd90d2c-4e82-42a1-a401-882c88b06a1a2.jpgint lastIndex = originalFilename.lastIndexOf(".");String extention = originalFilename.substring(lastIndex);//.jpgString fileName = UUID.randomUUID().toString() + extention;//  FuM1Sa5TtL_ekLsdkYWcf5pyjKGu.jpgtry {//将文件上传到七牛云服务器QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_RESOURCES,fileName);} catch (IOException e) {e.printStackTrace();return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);}return new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS,fileName);
}

(4)在health_service_provider项目中提供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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsdhttp://www.springframework.org/schema/contexthttp://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);}//将图片名称保存到RedissavePic2Redis(setmeal.getImg());
}
//将图片名称保存到Redis
private void savePic2Redis(String pic){jedisPool.getResource().sadd(RedisConstant.SETMEAL_PIC_DB_RESOURCES,pic);
}

3. 体检套餐分页

3.1 完善页面

3.1.1 定义分页相关模型数据

pagination: {//分页相关属性currentPage: 1,pageSize:10,total:100,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-paginationclass="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){return setmealService.pageQuery(queryPageBean);
}

3.2.2 服务接口

在SetmealService服务接口中扩展分页查询方法

public PageResult pageQuery(QueryPageBean queryPageBean);

3.2.3 服务实现类

在SetmealServiceImpl服务实现类中实现分页查询方法,基于Mybatis分页助手插件实现分页

@Override
public PageResult pageQuery(QueryPageBean queryPageBean) {Integer currentPage = queryPageBean.getCurrentPage();Integer pageSize = queryPageBean.getPageSize();String queryString = queryPageBean.getQueryString();PageHelper.startPage(currentPage,pageSize);Page<Setmeal> page = setmealDao.findByCondition(queryString);return new PageResult(page.getTotal(),page.getResult());
}

3.2.4 Dao接口

在SetmealDao接口中扩展分页查询方法

public Page<Setmeal> findByCondition(String queryString);

3.2.5 Mapper映射文件

在SetmealDao.xml文件中增加SQL定义

<!--根据条件进行查询-->
<select id="findByCondition" parameterType="string" resultType="com.itterence.pojo.Setmeal">select * from t_setmeal<if test="value != null and value != '' and value.length > 0">where code = #{value} or name = #{value} or helpCode = #{value}</if>
</select>

4. 定时任务组件Quartz

4.1 Quartz介绍

Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。

官网:http://www.quartz-scheduler.org/

maven坐标:

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

4.2 Quartz入门案例

本案例基于Quartz和spring整合的方式使用。具体步骤:

(1)创建maven工程quartzdemo,导入Quartz和spring相关坐标,pom.xml文件如下

<?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.itterence</groupId><artifactId>quartz_demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><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.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>
</project>

(2)自定义一个Job

package com.itterence.jobs;public class JobDemo {public void run(){System.out.println("job execute...");}
}

(3)提供Spring配置文件spring-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等

<?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"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册自定义Job --><bean id="jobDemo" class="com.itterence.jobs.JobDemo"></bean><!-- 注册JobDetail,作用是负责通过反射调用指定的Job --><bean id="jobDetail"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><!-- 注入目标对象 --><property name="targetObject" ref="jobDemo"/><!-- 注入目标方法 --><property name="targetMethod" value="run"/></bean><!-- 注册一个触发器,指定任务触发的时间 --><bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"><!-- 注入JobDetail --><property name="jobDetail" ref="jobDetail"/><!-- 指定触发的时间,基于Cron表达式 --><property name="cronExpression"><value>0/10 * * * * ?</value></property></bean><!-- 注册一个统一的调度工厂,通过这个调度工厂调度任务 --><bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><!-- 注入多个触发器 --><property name="triggers"><list><ref bean="myTrigger"/></list></property></bean>
</beans>

(4)编写main方法进行测试

package com.itterence;import org.springframework.context.support.ClassPathXmlApplicationContext;public class App {public static void main(String[] args) {new ClassPathXmlApplicationContext("spring-jobs.xml");}
}

执行上面main方法观察控制台,可以发现每隔10秒会输出一次,说明每隔10秒自定义Job被调用一次。

4.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 字符代表着工作日 (星期一到星期五),只能用在日域上,它用来指定离指定日的最近的一个工作日

4.4 cron表达式在线生成器

前面介绍了cron表达式,但是自己编写表达式还是有一些困难的,我们可以借助一些cron表达式在线生 成器来根据我们的需求生成表达式即可。

http://qqe2.com/cron

5. 定时清理垃圾图片

前面我们已经完成了体检套餐的管理,在新增套餐时套餐的基本信息和图片是分两次提交到后台进行操作的。也就是用户首先将图片上传到七牛云服务器,然后再提交新增窗口中录入的其他信息。如果用户 只是上传了图片而没有提交录入的其他信息,此时的图片就变为了垃圾图片,因为在数据库中并没有记 录它的存在。此时我们要如何处理这些垃圾图片呢?

解决方案就是通过定时任务组件定时清理这些垃圾图片。为了能够区分出来哪些图片是垃圾图片,我们 在文件上传成功后将图片保存到了一个redis集合中,当套餐数据插入到数据库后我们又将图片名称保存 到了另一个redis集合中,通过计算这两个集合的差值就可以获得所有垃圾图片的名称。

本章节我们就会基于Quartz定时任务,通过计算redis两个集合的差值找出所有的垃圾图片,就可以将 垃圾图片清理掉。 操作步骤:

(1)创建maven工程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.itterence</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>health_jobs</artifactId><packaging>war</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>com.itterence</groupId><artifactId>health_common</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency><dependency><groupId>com.itterence</groupId><artifactId>health_interface</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>83</port><!-- 请求路径 --><path>/</path></configuration></plugin></plugins></build>
</project>

(2)配置web.xml

<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!-- 加载spring容器 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath*:applicationContext*.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
</web-app>

(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)配置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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsdhttp://www.springframework.org/schema/contexthttp://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)配置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"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--开启spring注解使用--><context:annotation-config></context:annotation-config><!--注册自定义Job--><bean id="clearImgJob" class="com.itterence.jobs.ClearImgJob"></bean><bean id="jobDetail"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><!-- 注入目标对象 --><property name="targetObject" ref="clearImgJob"/><!-- 注入目标方法 --><property name="targetMethod" value="clearImg"/></bean><!-- 注册一个触发器,指定任务触发的时间 --><bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"><!-- 注入JobDetail --><property name="jobDetail" ref="jobDetail"/><!-- 指定触发的时间,基于Cron表达式 --><property name="cronExpression"><!--<value>0 0 2 * * ?</value>--><value>0/10 * * * * ?</value></property></bean><!-- 注册一个统一的调度工厂,通过这个调度工厂调度任务 --><bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><!-- 注入多个触发器 --><property name="triggers"><list><ref bean="myTrigger"/></list></property></bean>
</beans>

(6)创建ClearImgJob定时任务类

package com.itterence.jobs;import com.itterence.constant.RedisConstant;
import com.itterence.utils.QiniuUtils;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisPool;import java.util.Set;/*** 自定义Job,实现定时清理垃圾图片*/
public class ClearImgJob {@Autowiredprivate JedisPool jedisPool;public void clearImg(){//根据Redis中保存的两个set集合进行差值计算,获得垃圾图片名称集合Set<String> set =jedisPool.getResource().sdiff(RedisConstant.SETMEAL_PIC_RESOURCES,RedisConstant.SETMEAL_PIC_DB_RESOURCES);if(set != null){for (String picName : set) {//删除七牛云服务器上的图片QiniuUtils.deleteFileFromQiniu(picName);//从Redis集合中删除图片名称jedisPool.getResource().srem(RedisConstant.SETMEAL_PIC_RESOURCES,picName);}}}}

传智健康day04 预约管理-套餐管理相关推荐

  1. Day_04 传智健康项目-预约管理-套餐管理

    第4章 预约管理-套餐管理 1. 图片存储方案 1.1 介绍 在实际开发中,我们会有很多处理不同功能的服务器.例如: 应用服务器:负责部署我们的应用 数据库服务器:运行我们的数据库 文件服务器:负责存 ...

  2. 传智健康day05 预约管理-预约设置

    1. 需求分析 前面我们已经完成了检查项管理.检查组管理.套餐管理等.接下来我们需要进行预约设置,其实就是设置每一天的体检预约最大数量.客户可以通过微信端在线预约,在线预约时需要选择体检的时间,如果客 ...

  3. Day_05 传智健康项目-预约管理-预约设置

    第5章 预约管理-预约设置 1. 需求分析 前面我们已经完成了检查项管理.检查组管理.套餐管理等.接下来我们需要进行预约设置,其实就是设置每一天的体检预约最大数量.客户可以通过微信端在线预约,在线预约 ...

  4. 传智健康2.0-2-预约管理-检查项管理

    第2章 预约管理-检查项管理 1. 需求分析 传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化.患者管理专业化.健康评估数字化.健康干预流程化.知识库集成化,从而提高 ...

  5. 【项目一】医疗实战-传智健康

    传智健康 一.项目概述 1.1 项目介绍 1.2 技术架构 1.3 功能架构 二.环境搭建 2.1 项目结构 2.2 maven项目搭建 三.Power Designer和ElementUI 3.1 ...

  6. 【医疗健康项目】传智健康项目(二)

    第2章 预约管理-检查项管理 1. 需求分析 传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化.患者管理专业化.健康评估数字化.健康干预流程化.知识库集成化,从而提高 ...

  7. 传智健康项目day02

    第2章 预约管理-检查项管理 1. 需求分析 传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化.患者管理专业化.健康评估数字化.健康干预流程化.知识库集成化,从而提高 ...

  8. 传智健康——五、预约套餐管理(详细功能实现)

    文章目录 1. 预约套餐管理 1.0 前端页面准备 1.1 套餐分页 1.1.1 代码思路分析 1.1.2 前台代码 1.1.3 后台代码 1.1.4 套餐分页效果展示 1.2 新增套餐 1.2.1 ...

  9. 传智健康2.0-4-预约管理-套餐管理

    第4章 预约管理-套餐管理 1. 图片存储方案 1.1 介绍 在实际开发中,我们会有很多处理不同功能的服务器.例如: 应用服务器:负责部署我们的应用 数据库服务器:运行我们的数据库 文件服务器:负责存 ...

最新文章

  1. 【CVPR 2020】CVPR2020 最新论文下载!看计算机视觉2020在研究什么?
  2. openGL入门3 --- rasterization pipeline
  3. InputFlinger崩溃问题分析报告
  4. 拼多多面试真题:如何用 Redis 统计独立用户访问量!
  5. python项目开发案例-Python项目开发案例集锦 PDF 全彩超清版
  6. request、response 中文乱码问题与解决方式
  7. 单例-单例设计模式代码实现
  8. WinSock重叠I/O模型
  9. Codeigniter中创建LeanCloud云函数实现微信支付
  10. 看到这一幕,我甚至都想戒烟了。。 | 今日最佳
  11. 吃奶酪(洛谷-P1433)
  12. o型圈沟槽设计软件_265 电机壳体上轴承室和轴承外圈增加的O型圈工艺对轴承外圈(防蠕动)作用有多大?...
  13. 主定理(Master Theorem)与时间复杂度
  14. 普通人学python有啥用-普通人为什么要学习Python
  15. Leetcode 64. 最小路径和 -- DP算法
  16. 【渝粤教育】电大中专门店销售与服务技巧 (2)作业 题库
  17. Atitit 常用比较复杂的图像滤镜 attilax大总结
  18. tensorflow目标检测--识别赵丽颖
  19. 基于C++的递归和回溯国际象棋女王安全算法
  20. 12、Tello 进行起飞命令执行和视频显示

热门文章

  1. matlab字符模板在哪,新人求助!车牌识别系统里的字符模版存放路径是哪里
  2. Android NDK 使用skia
  3. gcc安装教程(windows版本)
  4. SAP 响应时间 性能察看
  5. 洗牌算法具体指的是什么
  6. Psins代码解析之线性误差模型精度验证(test_SINS_error_model_verify.m)
  7. C语言写一个函数,可以逆序一个字符串的内容。
  8. DNS服务器的原理及搭建
  9. 浏览器中Vimium 插件快捷键
  10. 两个向量组的秩相等说明什么_若两个向量组等价,它们的秩是否相等?