七牛云 阿里云图片存储 新增套餐 分页 定时任务Quartz(作业:编辑和删除功能)

@TOC

第4章 预约管理-套餐管理

今日目标:

  • 熟悉图片存储方案
  • 掌握七牛云图片上传
  • 掌握新增套餐并图片上传到七牛云
  • 掌握体检套餐分页展示
  • 熟悉定时调度任务Quartz

1. 图片存储方案

1.1 介绍

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

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

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

文件服务器:负责存储用户上传文件的服务器
在这里插入图片描述
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

常见的图片存储方案:

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

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

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

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/

在这里插入图片描述

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

在这里插入图片描述
使用Java SDK操作七牛云需要导入如下maven坐标:

<dependency>
  <groupId>com.qiniu</groupId>
  <artifactId>qiniu-java-sdk</artifactId>
  <version>7.2.25</version>
</dependency>
1.2.5 鉴权

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

1.2.6 上传策略与流程

文件上传分为客户端上传(主要是指网页端和移动端等面向终端用户的场景)和服务端上传两种场景,具体可以参考文档业务流程。服务端SDK在上传方面主要提供两种功能,一种是生成客户端上传所需要的上传凭证,另外一种是直接上传文件到云端。

  • 客户端上传

客户端(移动端或者Web端)上传文件的时候,需要从客户自己的业务服务器获取上传凭证,而这些上传凭证是通过服务端的SDK来生成的,然后通过客户自己的业务API分发给客户端使用。根据上传的业务需求不同,七牛云Java SDK支持丰富的上传凭证生成方式。
在这里插入图片描述

  • 服务端直传

服务直传是指客户利用七牛服务端SDK从服务端直接上传文件到七牛云,交互的双方一般都在机房里面,所以服务端可以自己生成上传凭证,然后利用SDK中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义returnBody来获取自定义的回复内容。
在这里插入图片描述

1.2.7 Java SDK操作七牛云

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

官方文档:

https://developer.qiniu.com/kodo/sdk/1239/java

  • 上传
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";
//默认不指定key的情况下,以文件内容的hash值作为文件名
String key = null;
try {
    byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
    ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);
    Auth auth = Auth.create(accessKey, secretKey);
    String upToken = auth.uploadToken(bucket);
    try {
        Response response = uploadManager.put(byteInputStream,key,upToken,null, null);
        //解析上传成功的结果
        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
        }
    }
} catch (UnsupportedEncodingException ex) {
    //ignore
}
  • 删除
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone0());
//...其他参数参考类注释

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.8 封装工具类

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

  • 在health_common模块中创建com.itheima.health.utils.QiniuUtils类
/**
 * @description 七牛云工具类
 **/
@Slf4j
public class QiniuUtils {
    public static final String CONFIG_FILE = "qiniu-config.properties";
    public static String QINIU_IMG_URL_PRE = "";
    public static String ACCESS_KEY = "";
    public static String SECRET_KEY = "";
    public static String BUCKET = "";
    static {
        Properties prop = new Properties();
        // 加载配置
        try(InputStream is = QiniuUtils.class.getClassLoader().getResourceAsStream("qiniu-config.properties")) {
            prop.load(is);
        } catch (IOException e) {
            log.error("[七牛云工具类-初始化]失败", e);
        }
        // 读取配置
        QINIU_IMG_URL_PRE = prop.getProperty("img.url.prefix","");
        ACCESS_KEY = prop.getProperty("access.key","");
        SECRET_KEY = prop.getProperty("secret.key","");
        BUCKET = prop.getProperty("bucket","");
        log.info("[七牛云配置]加载成功");
    }

    /**
     * 上传到七牛云
     *
     * @param is             输入流
     * @param uploadFileName 文件名
     */
    public static void upload2Qiniu(InputStream is, String uploadFileName) {
        //构造一个带指定Zone对象的配置类
        Configuration cfg = new Configuration(Region.autoRegion());
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);

        //默认不指定key的情况下,以文件内容的hash值作为文件名
        String key = uploadFileName;
        Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
        String upToken = auth.uploadToken(BUCKET);
        try {
            Response response = uploadManager.put(is, key, upToken, null, null);
            //解析上传成功的结果
            log.info(response.bodyString());
            // 访问路径
            log.info(QINIU_IMG_URL_PRE + "/{}", uploadFileName);
        } catch (QiniuException ex) {
            log.error("", ex);
            Response r = ex.response;
            log.error(r.toString());
            try {
                log.error(r.bodyString());
            } catch (QiniuException ex2) {
                //ignore
                log.error("", ex2);
            }
        }
    }
	/**
     * 删除文件
     *
     * @param fileName 文件名
     */
    public static void deleteFileFromQiniu(String fileName) {
        //构造一个带指定Zone对象的配置类
        Configuration cfg = new Configuration(Region.autoRegion());
        String key = fileName;
        Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
        BucketManager bucketManager = new BucketManager(auth, cfg);
        try {
            bucketManager.delete(BUCKET, key);
        } catch (QiniuException ex) {
            //如果遇到异常,说明删除失败
            log.error("", ex);
            log.error("code:{}", ex.code());
            log.error(ex.response.toString());
        }
    }

    // 测试上传
    public static void main(String args[]) throws IOException {
        // 测试上传
//        String localFilePath = "C:\\Users\\zhang\\Desktop\\qiniu_test.png";
//        FileInputStream fileInputStream = new FileInputStream(localFilePath);
//        String saveFileName = UUID.randomUUID().toString().replace("-","");
//        QiniuUtils.upload2Qiniu(fileInputStream,saveFileName);
        
        // 测试删除
//        deleteFileFromQiniu("f7414cc9312b4c17b40523eab97b1f57");
    }

}

  • 在需要使用QiniuUtils的项目(health_backend)中加入配置文件qiniu-config.properties
  img.url.prefix=http://**************
  access.key=************
  secret.key=************
  bucket=***********
  

2. 新增套餐

需求分析

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

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

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

页面中已经提供了新增窗口,只是出于隐藏状态。只需要将控制展示状态的属性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.1 展示检查组列表

现在虽然已经完成了新增窗口的弹出,但是在检查组信息标签页中需要动态展示所有的检查组信息列表数据,并且可以进行勾选。具体操作步骤如下:

(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(this.backend_url+"/checkgroup/findAll.do").then((res)=> {
        if(res.data.flag){
            this.tableData = res.data.data;
        }else{
            this.$message.error(res.data.message);
        }
    }).catch((error)=>{
        console.log(error);
        this.$message.error("网络异常");
    });
},

(4)分别在CheckGroupController、CheckGroupService、CheckGroupServiceImpl、CheckGroupDao、CheckGroupDao.xml中扩展方法查询所有检查组数据

  • CheckGroupService(health_interface工程):
	/**
	 * 获取所有检查组
	 * @return
	 */
	public List<CheckGroup> findAll();
  • CheckGroupController(health_oms_backend工程):
	 /**
     * 查询所有检查组
     * @return
     */
    @RequestMapping("/findAll")
    public Result findAll(){
        try {
            //RPC调用查询数据
             List<CheckGroup> checkGroups = checkGroupService.findAll();
             return new Result(true,MessageConst.QUERY_CHECKGROUP_SUCCESS,checkGroups);
        } catch (RuntimeException e) {
            log.error("",e);
            return new Result(false,MessageConst.QUERY_CHECKGROUP_FAIL);
        }
    }

CheckGroupDao (health_service_provider工程):

	/**
	 * 获取所有检查组数据
	 * @return
	 */
	List<CheckGroup> selectAll();

CheckGroupDao.xml (health_service_provider工程):

<select id="selectAll" resultType="com.itheima.pojo.CheckGroup">
  select * from t_checkgroup
</select>

CheckGroupServiceImpl (health_service_provider工程):

public List<CheckGroup> findAll() {
  return checkGroupDao.selectAll();
}

2.2 图片上传并预览

此处使用的是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="http://localhost:9002/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 = 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,接收上传的文件

​ health_oms_backend工程

  • 修改pom.xml文件

    当前工程pom.xml,需要增加文件上传及七牛上传依赖

    <dependency>
        <groupId>com.qiniu</groupId>
        <artifactId>qiniu-java-sdk</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
    </dependency>
    
  • 修改spring-mvc.xml配置上传组件

    在spring-mvc.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>
    
  • 增加图片上传工具类(health_oms_backend工程,com.itheima.health.utils)

    详见本文档1.2.8

  • 增加上传控制器

@RestController
@RequestMapping("/setmeal")
public class SetmealController {
    /**
     * 上传套餐图片
     *
     * @param multipartFile
     * @return
     */
    @PostMapping("/upload")
    public Result upload(@RequestParam("imgFile") MultipartFile multipartFile) {
        log.info("文件上传,name:{},size:{}"
                 ,multipartFile.getOriginalFilename(),multipartFile.getSize());
         //原始文件名
        String originalFileName = multipartFile.getOriginalFilename();
        //使用UUID构造不重复的文件名
        String fileName = UUID.randomUUID().toString().replace("-", "") + "_" + originalFileName;

        //获取输入流并上传
        try (InputStream is = multipartFile.getInputStream()) {
            QiniuUtils.upload2Qiniu(is, fileName);
        } catch (IOException e) {
            log.error("", e);
            return new Result(false, MessageConst.PIC_UPLOAD_FAIL);
        }

        //构造返回值
        String pic = QiniuUtils.QINIU_IMG_URL_PRE + fileName;
        return new Result(true, MessageConst.PIC_UPLOAD_SUCCESS, pic);
    }

}

2.3 新增套餐

当用户点击新增窗口中的确定按钮时发送ajax请求将数据提交到后台进行数据库操作。提交到后台的数据分为两部分:套餐基本信息(对应的模型数据为formData)和检查组id数组(对应的模型数据为checkgroupIds)。

为确定按钮绑定单击事件,对应的处理函数为handleAdd

<el-button type="primary" @click="handleAdd()">确定</el-button>

完善handleAdd方法

//添加
handleAdd () {
    axios.post(this.backend_url+"/setmeal/add.do?checkgroupIds=" + this.checkgroupIds,this.formData).then((response)=> {
        this.dialogFormVisible = false;
        if(response.data.flag){
            this.$message.success( response.data.message);
        }else{
            this.$message.error(response.data.message);
        }
    }).catch((error)=>{
        console.log(error);
        this.$message.error("网络异常");
    }).finally(()=> {
        this.findPage();
    });
},
2.3.1 服务接口

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

/**
 * 套餐SERVICE
 **/
public interface SetMealService {

    /**
     * 添加套餐
     * @param setmeal
     * @param checkgroupIds
     */
    void add(Setmeal setmeal, Integer[] checkgroupIds);
}
2.3.2 Controller

在SetmealController中增加方法

	/**
	 * 新增套餐
	 * @param setmeal 套餐基本信息
	 * @param checkgroupIds 选择的检查组列表
	 * @return
	 */
	 @RequestMapping("/add")
    public Result add(@RequestBody Setmeal setmeal, Integer[] checkgroupIds) {
        log.info("[套餐-添加]data:{},checkgroupIds:{}", setmeal, checkgroupIds);
        if(!StringUtils.isEmpty(setmeal.getImg())) {
            String img =setmeal.getImg().replace(QiniuUtils.QINIU_IMG_URL_PRE,"");
            setmeal.setImg(img);
        }
        try {
            setMealService.add(setmeal, checkgroupIds);
            return new Result(true, MessageConst.ADD_SETMEAL_SUCCESS);
        } catch (RuntimeException e) {
           log.error("",e);
           return new Result(false,MessageConst.ADD_SETMEAL_FAIL+":"+e.getMessage());
        }
    }
2.3.3 Dao接口

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

public interface SetmealDao {
    /**
     * 插入
     * @param setmeal 套餐
     */
    void insert(Setmeal setmeal);

    /**
     * 插入套餐和检查组关联关系
     * @param setmealId 套餐ID
     * @param checkgroupId 检查组ID
     */
    void insertSetMealAndCheckGroup(@Param("setmealId") Integer setmealId, @Param("checkgroupId") Integer checkgroupId);

}
2.3.4 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.health.dao.SetmealDao">
    <insert id="insert" parameterType="Setmeal">
        INSERT t_setmeal
        VALUES (null, #{name}, #{code}, #{helpCode},#{sex},#{age},#{price}, #{remark}, #{attention}, #{img})
        <selectKey resultType="integer" order="AFTER" keyProperty="id">
            Select LAST_INSERT_ID()
        </selectKey>
    </insert>
	<insert id="insertSetMealAndCheckGroup">
        INSERT  INTO t_setmeal_checkgroup(setmeal_id, checkgroup_id)
        VALUES (#{setmealId},#{checkgroupId})
    </insert>
</mapper>
2.3.5 服务实现类

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

@Service
public class SetmealServiceImpl implements SetmealService {
	
	@Autowired
	private SetmealDao setmealDao;

	@Transactional
	@Override
	public void add(Setmeal setmeal, Integer[] checkgroupIds) {
        log.info("[套餐-添加]data:{},checkgroupIds:{}", setmeal, checkgroupIds);
        // 插入基础数据
        setMealDao.insert(setmeal);
        // 循环插入关联数据
        for (Integer checkgroupId : checkgroupIds) {
            setMealDao.insertSetMealAndCheckGroup(setmeal.getId(), checkgroupId);
        }
	}
}

2.4 完善文件上传

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

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

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

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

3、计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称集合,清理这些图片即可

本小节我们先来完成前面2个环节,第3个环节(清理图片环节)在后面会通过定时任务再实现。

实现步骤:

  1. 在health_common工程中提供Redis常量类
  2. 修订health_oms_backend工程
    1. 引入jedis库
    2. 增加spring-redis.xml文件
    3. 修改控制器,使用jedis
  3. 修订health_service_provider工程
    1. 引入jedis库
    2. 增加spring-redis.xml文件
    3. 修改Service,使用jedis
1、health_common工程

提供Redis常量类

public class RedisConst {
    //套餐图片所有图片名称
    public static final String SETMEAL_PIC_RESOURCES = "setmealPicResources";
    //套餐图片保存在数据库中的图片名称
    public static final String SETMEAL_PIC_DB_RESOURCES = "setmealPicDbResources";
}
2、health_oms_backend工程

​ (1) pom.xml文件引入jedis库

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

​ (2) 在health_oms_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" />
	</bean>
</beans>

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

@Autowired
private JedisPool jedisPool;

@PostMapping("/upload")
public Result upload(@RequestParam("imgFile") MultipartFile multipartFile) {
    log.info("文件上传,name:{},size:{}", multipartFile.getOriginalFilename(), multipartFile.getSize());
    //原始文件名
    String originalFileName = multipartFile.getOriginalFilename();
    //使用UUID构造不重复的文件名
    String fileName = UUID.randomUUID().toString().replace("-", "") + "_" + originalFileName;

    //获取输入流并上传
    try (InputStream is = multipartFile.getInputStream()) {
        QiniuUtils.upload2Qiniu(is, fileName);
    } catch (IOException e) {
        log.error("", e);
        return new Result(false, MessageConst.PIC_UPLOAD_FAIL);
    }
    
    //开始写入redis
    try(Jedis jedis = jedisPool.getResource()){
        jedis.sadd(RedisConst.SETMEAL_PIC_RESOURCES,fileName);
    }
    /写入redis结束
    
    //构造返回值
    String pic = QiniuUtils.QINIU_IMG_URL_PRE + fileName;
    return new Result(true, MessageConst.PIC_UPLOAD_SUCCESS, pic);
}
3、health_service_provider工程

​ (1)jedis库

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

​ (2)在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/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>

​ (3)完善SetmealServiceImpl服务类,在保存完成套餐信息后将图片名称存储到redis集合中

@Autowired
private JedisPool jedisPool;
@Transactional
@Override
public void add(Setmeal setmeal, Integer[] checkgroupIds) {
    log.info("[套餐-添加]data:{},checkgroupIds:{}", setmeal, checkgroupIds);
    // 插入基础数据
    setMealDao.insert(setmeal);
    // 循环插入关联数据
    for (Integer checkgroupId : checkgroupIds) {
        setMealDao.insertSetMealAndCheckGroup(setmeal.getId(), checkgroupId);
    }
    //  img记入redis
    try (Jedis jedis = jedisPool.getResource()) {
        jedis.sadd(RedisConst.SETMEAL_PIC_DB_RESOURCES, setmeal.getImg());
    }
}

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(currentPage) {
    if(currentPage){
        this.pagination.currentPage = currentPage;
    }
    //分页条件
    let param = {
        currentPage:this.pagination.currentPage,
        pageSize:this.pagination.pageSize,
        queryString:this.pagination.queryString
    };
    axios.get(this.backend_url+"/setmeal/findPage.do",{params:param}).then((response)=> {
        this.dataList = response.data.rows;
        this.pagination.total = response.data.total;
    }).catch((error)=>{
        console.log(error);
        this.$message.error("网络异常");
    });
},
3.1.3 完善分页方法执行时机

除了在created钩子函数中调用findPage方法查询分页数据之外,当用户点击查询按钮或者点击分页条中的页码时也需要调用findPage方法重新发起查询请求。

为查询按钮绑定单击事件,调用findPage方法,并且制定currentPage为第1页

<el-button @click="findPage(1)" 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 服务接口

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

	/**
     * 分页查询
     * @param queryPageBean
     * @return
     */
    PageResult pageQuery(QueryPageBean queryPageBean);
3.2.2 Controller

在SetmealController中增加分页查询方法

	/**
	 * 分页获取套餐数据
	 * @param queryPageBean 查询参数
	 * @return
	 */
 	@GetMapping("/findPage")
    public PageResult findPage(QueryPageBean queryPageBean) {
        try {
            return setMealService.pageQuery(queryPageBean);
        } catch (RuntimeException e) {
            log.error("", e);
            return new PageResult(0L, Collections.emptyList());
        }
    }
3.2.4 Dao接口

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

	/**
	 * 基于条件查询数据
	 * @param queryString 条件
	 * @return
	 */
	Page<Setmeal> selectByCondition(@Param("queryString") String queryString);
3.2.5 Mapper映射文件

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

<!--根据条件查询-->
    <select id="selectByCondition" resultType="com.itheima.health.pojo.Setmeal">
        SELECT * FROM t_setmeal
        <if test=" queryString!=null and queryString.length > 0">
            WHERE code LIKE CONCAT('%',#{queryString},'%') or name LIKE CONCAT('%',#{queryString},'%') or helpCode like CONCAT('%',#{queryString},'%')
        </if>
    </select>
3.2.3 服务实现类

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

   @Override
    public PageResult pageQuery(QueryPageBean queryPageBean) {
        //设置分页参数
        PageHelper.startPage(queryPageBean.getCurrentPage(),queryPageBean.getPageSize());
        //调用DAO查询数据
        Page<Setmeal> page = setMealDao.selectByCondition(queryPageBean.getQueryString());
        //封装返回结果
        return new PageResult(page.getTotal(),page.getResult());
    }

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>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

4.2 Quartz整合

基本步骤:

  1. 创建子模块 health-jobs

    webapp骨架

  2. 修改pom.xml文件

    引入需要的三方库

  3. 自定义一个任务类

    调用任务类,普通类普通方法

  4. 增加spring-jobs.xml文件

    配置任务对象、调度对象,调度工厂

  5. 修改web.xml文件

    容器启动,加载spring配置文件

  6. 加入log4j.properties
    用于输入日志

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

(1)创建maven子模块工程health-jobs,骨架webapp
在这里插入图片描述
名字:health-jobs 模块:health-jobs
(2)修改pom.xml文件

【加入lombok和log4j的依赖】

打包方式为war,导入health_common、spring-webmvc、spring-tx、spring-context-support、Quartz、jedis相关坐标

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">
    <parent>
        <artifactId>itcast_health</artifactId>
        <groupId>com.itheima.health</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>health-jobs</artifactId>
    <packaging>war</packaging>

    <name>health-jobs</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>com.itheima.health</groupId>
            <artifactId>health_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
        </dependency>
         <!--log4j slf4j-->
        <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
        </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>health-jobs</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <path>/</path>
                    <port>9003</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

(3)自定义一个Job

main下创建java目录 (刷新maven工程),然后创建com.itheima.health.jobs包,然后新建一个任务调度类ClearImageJob,任务内容暂时做普通打印即可。

public class ClearImageJob {
	/**
	 * 定义清理图片的任务
	 */	
	public void clearImageJob(){
		System.out.println("clearImageJob......");
	}
}

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

  • 自动扫包
  • 注册任务对象
  • 注册JobDetail
  • 触发器
  • 调度工厂

​ 在main下创建resources目录,刷新maven后,在目录下创建spring-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">

    <!--自动扫包,后续注入jedis到任务调度类-->
    <context:component-scan base-package="com.itheima.health"/>
    <!--注册一个定义任务对象-->
    <bean id = "clearImgJob" class="com.itheima.health.jobs.ClearImageJob"/>
    <!-- 注册JobDetail,作用是负责通过反射调用指定的Job -->
    <bean id="clearImgJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <!--注入对象-->
        <property name="targetObject" ref="clearImgJob"/>
        <!--注入方法-->
        <property name="targetMethod" value="clearImageJob"/>
    </bean>
    <!--注册一个触发器,指定任务触发的时间(间隔)-->
    <bean id="jobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="clearImgJobDetail"/>
        <property name="cronExpression">
            <!-- 每隔10秒执行一次任务 0/10 * * * * ? -->
            <!-- 每隔2分钟执行一次任务  0 0/2 * * * ? -->
            <!-- 每天凌晨2点执行一次任务 0 0 2 * * ?  -->
            <value>0/10 * * * * ?</value>
        </property>
    </bean>
    <!--注册一个统一调用工厂,通过这个调度工厂调度任务-->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="jobTrigger"/>
            </list>
        </property>
    </bean>
</beans>

(5)修改web.xml,容器启动时,加载spring配置文件

<!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> Jobs Web Application</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-*.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

(6)加入log4j.properties

log4j.rootLogger=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#[%-5p] %t %l %d %rms:%m%n
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n
# file
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=log/out.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n

使用tomcat插件启动项目,可以发现每隔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表达式在线生成器来根据我们的需求生成表达式即可。

https://www.matools.com/cron

http://cron.qqe2.com/
在这里插入图片描述
周和日不能同时指定

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.itheima</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>health_jobs</artifactId>
    <packaging>war</packaging>
    <name>health_jobs Maven Webapp</name>
    <url>http://www.example.com</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.itheima</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=info, 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/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)配置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/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">
	<context:annotation-config></context:annotation-config>
	<bean id="clearImgJob" class="com.itheima.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>
		</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.itheima.jobs;

import com.itheima.constant.RedisConstant;
import com.itheima.utils.QiniuUtils;
import com.sun.jersey.api.client.Client;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisPool;
import java.util.Iterator;
import java.util.Set;
/**
 * 定时任务:清理垃圾图片
 */
public class ClearImgJob {
    @Autowired
    private JedisPool jedisPool;
    //清理图片
    public void clearImg(){
        //计算redis中两个集合的差值,获取垃圾图片名称
        Set<String> set = jedisPool.getResource().sdiff(
                                                      RedisConstant.SETMEAL_PIC_RESOURCES, 
                                                      RedisConstant.SETMEAL_PIC_DB_RESOURCES);
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            String pic = iterator.next();
            //删除图片服务器中的图片文件
            QiniuUtils.deleteFileFromQiniu(pic);
            //删除redis中的数据
            jedisPool.getResource().srem(RedisConstant.SETMEAL_PIC_RESOURCES,pic);
        }
    }
}

总的代码+自己拓展的阿里云oss

有一点redis这个需要自己新建一个spring-redis,不要写在同一个spring中,不然会有问题
html

<!DOCTYPE html>
<html>
    <head>
        <!-- 页面meta -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>传智健康</title>
        <meta name="description" content="传智健康">
        <meta name="keywords" content="传智健康">
        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
        <!-- 引入样式 -->
        <link rel="stylesheet" href="../plugins/elementui/index.css">
        <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
        <link rel="stylesheet" href="../css/style.css">
        <!-- 引入组件库 -->
        <script type="text/javascript" src="../js/jquery.min.js"></script>
        <script src="../js/vue.js"></script>
        <script src="../plugins/elementui/index.js"></script>
        <script src="../js/axios-0.18.0.js"></script>
        <style>
            .avatar-uploader .el-upload {
                border: 1px dashed #d9d9d9;
                border-radius: 6px;
                cursor: pointer;
                position: relative;
                overflow: hidden;
            }
            .avatar-uploader .el-upload:hover {
                border-color: #409EFF;
            }
            .avatar-uploader-icon {
                font-size: 28px;
                color: #8c939d;
                width: 178px;
                height: 178px;
                line-height: 178px;
                text-align: center;
            }
            .avatar {
                width: 178px;
                height: 178px;
                display: block;
            }
            .datatable {
                position: relative;
                box-sizing: border-box;
                -webkit-box-flex: 1;
                width: 100%;
                max-width: 100%;
                font-size: 14px;
                color: rgb(96, 98, 102);
                overflow: hidden;
                flex: 1 1 0%;
            }
            .datatable td, .datatable th {
                padding: 12px 0;
                min-width: 0;
                -webkit-box-sizing: border-box;
                box-sizing: border-box;
                text-overflow: ellipsis;
                vertical-align: middle;
                position: relative;
                text-align: left;
            }
        </style>
    </head>
    <body class="hold-transition">
        <div id="app">
            <div class="content-header">
                <h1>预约管理<small>套餐管理</small></h1>
                <el-breadcrumb separator-class="el-icon-arrow-right" class="breadcrumb">
                    <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
                    <el-breadcrumb-item>预约管理</el-breadcrumb-item>
                    <el-breadcrumb-item>套餐管理</el-breadcrumb-item>
                </el-breadcrumb>
            </div>
            <div class="app-container">
                <div class="box">
                    <div class="filter-container">
                        <el-input placeholder="编码/名称/助记码" v-model="pagination.queryString" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"></el-input>
                        <el-button @click="findPage()" class="dalfBut">查询</el-button>
                        <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
                    </div>
                    <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
                        <el-table-column type="index" align="center" label="序号"></el-table-column>
                        <el-table-column prop="code" label="套餐编码" align="center"></el-table-column>
                        <el-table-column prop="name" label="套餐名称" align="center"></el-table-column>
                        <el-table-column label="适用性别" align="center">
                            <template slot-scope="scope">
                                <span>{{ scope.row.sex == '0' ? '不限' : scope.row.sex == '1' ? '男' : '女'}}</span>
                            </template>
                        </el-table-column>
                        <el-table-column prop="age" label="适用年龄" align="center"></el-table-column>
                        <el-table-column prop="helpCode" label="助记码" align="center"></el-table-column>
                        <el-table-column prop="remark" label="说明" align="center"></el-table-column>
                        <el-table-column label="操作" align="center">
                            <template slot-scope="scope">
                                <el-button type="primary" size="mini">编辑</el-button>
                                <el-button size="mini" type="danger">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                    <div class="pagination-container">
                        <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>
                    </div>
                    <!-- 新增标签弹层 -->
                    <div class="add-form">
                        <el-dialog title="新增套餐" :visible.sync="dialogFormVisible">
                            <template>
                                <el-tabs v-model="activeName" type="card">
                                    <el-tab-pane label="基本信息" name="first">
                                        <el-form label-position="right" label-width="100px">
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="编码">
                                                        <el-input v-model="formData.code"/>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="名称">
                                                        <el-input v-model="formData.name"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="适用性别">
                                                        <el-select v-model="formData.sex">
                                                            <el-option label="不限" value="0"></el-option>
                                                            <el-option label="" value="1"></el-option>
                                                            <el-option label="" value="2"></el-option>
                                                        </el-select>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="助记码">
                                                        <el-input v-model="formData.helpCode"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="套餐价格">
                                                        <el-input v-model="formData.price"/>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="适用年龄">
                                                        <el-input v-model="formData.age"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="上传图片">
                                                        <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>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="说明">
                                                        <el-input v-model="formData.remark" type="textarea"></el-input>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="注意事项">
                                                        <el-input v-model="formData.attention" type="textarea"></el-input>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                        </el-form>
                                    </el-tab-pane>
                                    <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>
												<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>
										</div>
                                    </el-tab-pane>
                                </el-tabs>
                            </template>
                            <div slot="footer" class="dialog-footer">
                                <el-button @click="dialogFormVisible = false">取消</el-button>
                                <el-button type="primary" @click="handleAdd()">确定</el-button>
                            </div>
                        </el-dialog>
                    </div>
                </div>
            </div>
        </div>
    </body>

    <script>
        var vue = new Vue({
            el: '#app',
            data:{
                autoUpload:true,//自动上传
                imageUrl:null,//模型数据,用于上传图片完成后图片预览
                activeName:'first',//添加/编辑窗口Tab标签名称
                pagination: {//分页相关属性
                    currentPage: 1,
                    pageSize:10,
                    total:100,
                    queryString:null,
                },
                dataList: [],//列表数据
                formData: {},//表单数据
                tableData:[],//添加表单窗口中检查组列表数据
                checkgroupIds:[],//添加表单窗口中检查组复选框对应id
                dialogFormVisible: false//控制添加窗口显示/隐藏
            },
            created() {
                this.findPage();
            },
            methods: {
                //文件上传成功后的钩子,response为服务端返回的值,file为当前上传的文件封装成的js对象
                handleAvatarSuccess(response, file) {
                    //我模型数据ImgaeUrl赋值,用于预览
                  /*  this.imageUrl='http://qfjm1t11q.hd-bkt.clouddn.com/'+response.data;*/
                    this.imageUrl=response.data;
                    this.$message({
                        type:response.flag?'success':'error',
                        message:response.message
                    });
                    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;
                },
                //添加
                handleAdd () {
                    this.dialogFormVisible=false;
                    //发送请求,把表单数据发送到后台
                    axios.post("/setmeal/add.do?checkgroupIds="+this.checkitemIds,this.formData).then((resp)=>{
                        if (resp.data.flag){
                            this.$message({
                                type:'success',
                                message:resp.data.message
                            })
                        }else {
                            this.$message.error(resp.dada.message)
                        }
                    }).finally(()=>{
                        this.findPage();
                    })
                },
                //分页查询
                findPage() {
                    if (this.pagination.queryString!=null&&this.pagination.queryString!==""){
                        let param={
                            currentPage: 1,//页码
                            pageSize: this.pagination.pageSize,
                            queryString: this.pagination.queryString
                        };
                        axios.post("/setmeal/findPage.do",param).then((response)=>{
                            this.dataList=response.data.rows;
                            this.pagination.total=response.data.total;
                        });
                    }else {
                        let param = {
                            currentPage: this.pagination.currentPage,//页码
                            pageSize: this.pagination.pageSize,
                            queryString: this.pagination.queryString
                        };
                        axios.post("/setmeal/findPage.do",param).then((response)=>{
                            this.dataList=response.data.rows;
                            this.pagination.total=response.data.total;
                        });
                    }


                },
                // 重置表单
                resetForm() {
                    this.activeName='first';
                    this.formData={};
                    this.imageUrl=null;
                    this.checkitemIds=[];
                },
                // 弹出添加窗口
                handleCreate() {
                    this.resetForm();
                    this.dialogFormVisible=true;

                    axios.get("/checkgroup/findAll.do").then((resp)=>{
                        if (resp.data.flag){
                            this.tableData=resp.data.data;
                        }else {
                            this.$message.error(resp.data.message)
                        }

                    })
                },
                //切换页码
                handleCurrentChange(currentPage) {
                    this.pagination.currentPage=currentPage;
                    this.findPage();
                }
            }
        })
    </script>
</html>

dao

package com.ybb.dao;

import com.github.pagehelper.Page;
import com.ybb.entity.QueryPageBean;
import com.ybb.pojo.Setmeal;

import java.util.Map;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
public interface SetmealDao {

    void add(Setmeal setmeal);

    void setSetmealAndCheckGroup(Map map);

    Page<Setmeal> findByCondition(String querayString);
}

dao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ybb.dao.SetmealDao">

    <!--新增,获取(自增)产生的最后一个id,封装到id属性中-->
    <insert id="add" parameterType="com.ybb.pojo.Setmeal">
   <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>

<!--需要改成foreach添加-->
    <insert id="setSetmealAndCheckgroup" parameterType="map">
        insert into t_setmeal_checkgroup(setmeal_id,checkgroup_id)
        values
        <foreach collection="checkgroup_id" separator="," item="groupId">
            (#{setmeal_id},#{groupId})
        </foreach>
    </insert>




    <select id="findByCondition" parameterType="string" resultType="com.ybb.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>


</mapper>

service

package com.ybb.service;

import com.ybb.entity.PageResult;
import com.ybb.entity.QueryPageBean;
import com.ybb.entity.Result;
import com.ybb.pojo.Setmeal;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
public interface SetmealService {

    Result add(Setmeal setmeal, Integer[]checkgroupIds);

    PageResult findPage(QueryPageBean queryPageBean);

    void setSetmealAndCheckgroup(Integer id,Integer[]checkgroupIds);
}

serviceImpl

package com.ybb.service.Impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.ybb.dao.SetmealDao;
import com.ybb.entity.PageResult;
import com.ybb.entity.QueryPageBean;
import com.ybb.entity.Result;
import com.ybb.pojo.Setmeal;
import com.ybb.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.JedisPool;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
@Service(interfaceClass = SetmealService.class)
@Transactional
public class SetmealServiceImpl implements SetmealService {

    @Autowired
    private SetmealDao setmealDao;



    //新增套餐信息,同时需要关联检查组
    @Override
    public Result add(Setmeal setmeal, Integer[] checkgroupIds) {
      //校验
        setmealDao.add(setmeal);
     //数据库生成的id
        Integer id = setmeal.getId();
      this.setSetmealAndCheckgroup(id,checkgroupIds);


        return null;
    }

    @Override
    public PageResult findPage(QueryPageBean queryPageBean) {

        Integer currentPage = queryPageBean.getCurrentPage();
        Integer pageSize = queryPageBean.getPageSize();
        String queryString = queryPageBean.getQueryString();

        PageHelper.startPage(currentPage,pageSize);
        Page<Setmeal>page=setmealDao.findByCondition(queryPageBean.getQueryString());
        return new PageResult(page.getTotal(),page.getResult());
    }


    //设置套餐和检查组多对多关系,操作t_setmeal_checkgroup
    @Override
    public void setSetmealAndCheckgroup(Integer id,Integer[]checkgroupIds){
        if (checkgroupIds!=null&&checkgroupIds.length>0){
            Map<String,Object>map=new HashMap<>();
            map.put("setmealId",id);
            map.put("checkgroupId",checkgroupIds);
            setmealDao.setSetmealAndCheckGroup(map);
        }
    }
}

controller

package com.ybb.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.aliyun.oss.model.ObjectMetadata;
import com.ybb.constant.MessageConstant;
import com.ybb.entity.PageResult;
import com.ybb.entity.QueryPageBean;
import com.ybb.entity.Result;
import com.ybb.pojo.Setmeal;
import com.ybb.service.SetmealService;


import com.ybb.utils.ALiYunUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import redis.clients.jedis.JedisPool;


import java.io.IOException;

import java.util.UUID;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :体检套餐管理
 * Version :1.0
 */
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
//使用JedisPool操作Redis服务
    @Autowired
    private JedisPool jedisPool;

    //文件上传
    @RequestMapping("/upload")
    public Result upload(MultipartFile imgFile) throws IOException {
        //TODO 记得做数据校验,校验数据的大小,类型,还有文件名拼接时注意长度


//处理后缀,uuid+.jpg拼接
        String originalFilename = imgFile.getOriginalFilename();//原始文件名
        int index = originalFilename.lastIndexOf(".");
        String extention = originalFilename.substring(index ); //.jpg

        String fileName = UUID.randomUUID().toString()+extention;
        try {
//     七牛云的       QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);
            String url = ALiYunUtils.add(imgFile.getBytes(), fileName);
            System.out.println();
     jedisPool.getResource().sadd("health-img",fileName);
            return new Result(true,MessageConstant.PIC_UPLOAD_SUCCESS,url);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);
        }
    }

    @Reference
    private SetmealService setmealService;

    //文件上传
    @RequestMapping("/add")
    public Result add(@RequestBody Setmeal setmeal, Integer[]checkgroupIds){
      try {
          setmealService.add(setmeal,checkgroupIds);

          jedisPool.getResource().srem("health-img",setmeal.getImg());

          return new Result(true,MessageConstant.ADD_SETMEAL_SUCCESS);
      }catch (Exception e){
          e.printStackTrace();
          return new Result(false,MessageConstant.ADD_SETMEAL_FAIL);
      }
    }





    @RequestMapping("/findPage")
    public PageResult findPage(@RequestBody QueryPageBean queryPageBean){
      return setmealService.findPage(queryPageBean);
    }
}

在这里插入图片描述
poxm.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">
    <parent>
        <artifactId>health_parent</artifactId>
        <groupId>com.ybb</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>health_jobs</artifactId>
    <packaging>war</packaging>
    <name>health_jobs Maven Webapp</name>
    <url>http://www.example.com</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.ybb</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>

spring.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/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="192.168.23.129" />
        <constructor-arg name="port" value="6379" type="int" />
        <constructor-arg name="timeout" value="30000" type="int" />
    </bean>


    <context:annotation-config/>
    <bean id="clearImgJob" class="com.ybb.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表达式       0 0 2 * * ?-->
        <property name="cronExpression">
            <value>5 * * * * ? *</value>
        </property>
    </bean>
    <!-- 注册一个统一的调度工厂,通过这个调度工厂调度任务 -->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <!-- 注入多个触发器 -->
        <property name="triggers">
            <list>
                <ref bean="myTrigger"/>
            </list>
        </property>
    </bean>

</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


        <display-name>Archetype Created Web Application</display-name>
        <!-- 加载spring容器 -->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring.xml</param-value>
        </context-param>
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>

</web-app>

ClearImgJob

package com.ybb.jobs;

import com.ybb.utils.ALiYunUtils;
import com.ybb.utils.QiniuUtils;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisPool;

import java.util.Iterator;
import java.util.Set;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */

public class ClearImgJob {
      @Autowired
      private JedisPool jedisPool;

    public void clearImg(){
          Set<String> smembers = jedisPool.getResource().smembers("health-img");
          for (String smember : smembers) {
                QiniuUtils.deleteFileFromQiniu(smember);
              ALiYunUtils.delete(smember);
          }
          jedisPool.getResource().del("health-img");
        System.out.println("执行了!!!!");
    }
}

作业套餐编辑和修改,全代码

html

<!DOCTYPE html>
<html>
    <head>
        <!-- 页面meta -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>传智健康</title>
        <meta name="description" content="传智健康">
        <meta name="keywords" content="传智健康">
        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
        <!-- 引入样式 -->
        <link rel="stylesheet" href="../plugins/elementui/index.css">
        <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min(1).css">
        <link rel="stylesheet" href="../css/style.css">
        <!-- 引入组件库 -->
        <script type="text/javascript" src="../js/jquery.min.js"></script>
        <script src="../js/vue.js"></script>
        <script src="../plugins/elementui/index.js"></script>
        <script src="../js/axios-0.18.0.js"></script>
        <style>
            .avatar-uploader .el-upload {
                border: 1px dashed #d9d9d9;
                border-radius: 6px;
                cursor: pointer;
                position: relative;
                overflow: hidden;
            }
            .avatar-uploader .el-upload:hover {
                border-color: #409EFF;
            }
            .avatar-uploader-icon {
                font-size: 28px;
                color: #8c939d;
                width: 178px;
                height: 178px;
                line-height: 178px;
                text-align: center;
            }
            .avatar {
                width: 178px;
                height: 178px;
                display: block;
            }
            .datatable {
                position: relative;
                box-sizing: border-box;
                -webkit-box-flex: 1;
                width: 100%;
                max-width: 100%;
                font-size: 14px;
                color: rgb(96, 98, 102);
                overflow: hidden;
                flex: 1 1 0%;
            }
            .datatable td, .datatable th {
                padding: 12px 0;
                min-width: 0;
                -webkit-box-sizing: border-box;
                box-sizing: border-box;
                text-overflow: ellipsis;
                vertical-align: middle;
                position: relative;
                text-align: left;
            }
        </style>
    </head>
    <body class="hold-transition">
        <div id="app">
            <div class="content-header">
                <h1>预约管理<small>套餐管理</small></h1>
                <el-breadcrumb separator-class="el-icon-arrow-right" class="breadcrumb">
                    <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
                    <el-breadcrumb-item>预约管理</el-breadcrumb-item>
                    <el-breadcrumb-item>套餐管理</el-breadcrumb-item>
                </el-breadcrumb>
            </div>
            <div class="app-container">
                <div class="box">
                    <div class="filter-container">
                        <el-input placeholder="编码/名称/助记码" v-model="pagination.queryString" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"></el-input>
                        <el-button @click="findPage()" class="dalfBut">查询</el-button>
                        <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
                    </div>
                    <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
                        <el-table-column type="index" align="center" label="序号"></el-table-column>
                        <el-table-column prop="code" label="套餐编码" align="center"></el-table-column>
                        <el-table-column prop="name" label="套餐名称" align="center"></el-table-column>
                        <el-table-column label="适用性别" align="center">
                            <template slot-scope="scope">
                                <span>{{ scope.row.sex == '0' ? '不限' : scope.row.sex == '1' ? '男' : '女'}}</span>
                            </template>
                        </el-table-column>
                        <el-table-column prop="age" label="适用年龄" align="center"></el-table-column>
                        <el-table-column prop="helpCode" label="助记码" align="center"></el-table-column>
                        <el-table-column prop="remark" label="说明" align="center"></el-table-column>
                        <el-table-column label="操作" align="center">
                            <template slot-scope="scope">
                                <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
                                <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                    <div class="pagination-container">
                        <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>
                    </div>
                    <!-- 新增标签弹层 -->
                    <div class="add-form">
                        <el-dialog title="新增套餐" :visible.sync="dialogFormVisible">
                            <template>
                                <el-tabs v-model="activeName" type="card">
                                    <el-tab-pane label="基本信息" name="first">
                                        <el-form label-position="right" label-width="100px">
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="编码">
                                                        <el-input v-model="formData.code"/>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="名称">
                                                        <el-input v-model="formData.name"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="适用性别">
                                                        <el-select v-model="formData.sex">
                                                            <el-option label="不限" value="0"></el-option>
                                                            <el-option label="" value="1"></el-option>
                                                            <el-option label="" value="2"></el-option>
                                                        </el-select>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="助记码">
                                                        <el-input v-model="formData.helpCode"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="套餐价格">
                                                        <el-input v-model="formData.price"/>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="适用年龄">
                                                        <el-input v-model="formData.age"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="上传图片">
                                                        <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>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="说明">
                                                        <el-input v-model="formData.remark" type="textarea"></el-input>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="注意事项">
                                                        <el-input v-model="formData.attention" type="textarea"></el-input>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                        </el-form>
                                    </el-tab-pane>
                                    <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>
												<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>
										</div>
                                    </el-tab-pane>
                                </el-tabs>
                            </template>
                            <div slot="footer" class="dialog-footer">
                                <el-button @click="dialogFormVisible = false">取消</el-button>
                                <el-button type="primary" @click="handleAdd()">确定</el-button>
                            </div>
                        </el-dialog>
                    </div>

                    <!-- 编辑标签弹层 -->
                    <div class="add-form">
                        <el-dialog title="编辑套餐" :visible.sync="dialogFormVisible4Edit">
                            <template>
                                <el-tabs v-model="activeName" type="card">
                                    <el-tab-pane label="基本信息" name="first">
                                        <el-form label-position="right" label-width="100px">
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="编码">
                                                        <el-input v-model="formData.code"/>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="名称">
                                                        <el-input v-model="formData.name"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="适用性别">
                                                        <el-select v-model="formData.sex">
                                                            <el-option label="不限" value="0"></el-option>
                                                            <el-option label="" value="1"></el-option>
                                                            <el-option label="" value="2"></el-option>
                                                        </el-select>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="助记码">
                                                        <el-input v-model="formData.helpCode"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="12">
                                                    <el-form-item label="套餐价格">
                                                        <el-input v-model="formData.price"/>
                                                    </el-form-item>
                                                </el-col>
                                                <el-col :span="12">
                                                    <el-form-item label="适用年龄">
                                                        <el-input v-model="formData.age"/>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="上传图片">
                                                        <el-upload
                                                                class="avatar-uploader"
                                                                action="/setmeal/upload.do"
                                                                :auto-upload="autoUpload"
                                                                name="imgFile"
                                                                :show-file-list="false"
                                                                :on-success="handleAvatarSuccesss"
                                                                :before-upload="beforeAvatarUpload">
                                                            <img v-if="imageUrl" :src="imageUrl" class="avatar">
                                                            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                                                        </el-upload>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="说明">
                                                        <el-input v-model="formData.remark" type="textarea"></el-input>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                            <el-row>
                                                <el-col :span="24">
                                                    <el-form-item label="注意事项">
                                                        <el-input v-model="formData.attention" type="textarea"></el-input>
                                                    </el-form-item>
                                                </el-col>
                                            </el-row>
                                        </el-form>
                                    </el-tab-pane>
                                    <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>
                                                <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>
                                        </div>
                                    </el-tab-pane>
                                </el-tabs>
                            </template>
                            <div slot="footer" class="dialog-footer">
                                <el-button @click="dialogFormVisible4Edit = false">取消</el-button>
                                <el-button type="primary" @click="handleEdit()">确定</el-button>
                            </div>
                        </el-dialog>
                    </div>



                </div>
            </div>
        </div>
    </body>

    <script>
        var vue = new Vue({
            el: '#app',
            data:{
                autoUpload:true,//自动上传
                imageUrl:null,//模型数据,用于上传图片完成后图片预览

                activeName:'first',//添加/编辑窗口Tab标签名称
                pagination: {//分页相关属性
                    currentPage: 1,
                    pageSize:10,
                    total:100,
                    queryString:null,
                },
                dataList: [],//列表数据
                formData: {},//表单数据
                tableData:[],//添加表单窗口中检查组列表数据
                checkgroupIds:[],//添加表单窗口中检查组复选框对应id
                dialogFormVisible: false,//控制添加窗口显示/隐藏
                dialogFormVisible4Edit:false//控制编辑窗口显示/隐藏
            },
            created() {
                this.findPage();
            },
            methods: {
                // 弹出编辑窗口
                handleUpdate(row) {
                    this.imageUrl=null;
                    this.dialogFormVisible4Edit = true;
                    this.activeName='first'

                    axios.get("/setmeal/findById.do?id="+row.id).then((resp)=>{
                        if (resp.data.flag){
                            this.formData=resp.data.data;
                            this.imageUrl='http://qfjm1t11q.hd-bkt.clouddn.com/'+resp.data.data.img;
                            //发送ajax请求,查询所有的检查项数据,用于展示检查项列表
                            axios.get("/checkgroup/findAll.do").then((resp) => {
                                if (resp.data.flag) {
                                    this.tableData = resp.data.data;
                                    //查询成功后,需要根据查组的ID查询中间表,用于页面复选框中
                                    axios.get("/setmeal/findcheckIdBySetMealId.do?id=" + row.id).then((resp) => {
                                        this.checkgroupIds = resp.data.data;
                                    })
                                }
                            })
                        }else {
                            this.$message.error(resp.data.message)
                        }
                    })
                },

                //编辑
                handleEdit() {

                    axios.post("/setmeal/edit.do",this.formData).then((resp)=>{

                        this.dialogFormVisible4Edit=false;
                        if (resp.data.flag){
                            this.$message().success(resp.data.message)
                        }else {
                            this.$message().error(resp.data.message)
                        }
                    }).finally(()=>{
                        this.findPage();
                    })
                },


                //文件上传成功后的钩子,response为服务端返回的值,file为当前上传的文件封装成的js对象
                handleAvatarSuccess(response, file) {
                    //我模型数据ImgaeUrl赋值,用于预览
                    this.imageUrl='http://qfjm1t11q.hd-bkt.clouddn.com/'+response.data;
                    /*this.imageUrl=response.data;*/
                    this.$message({
                        type:response.flag?'success':'error',
                        message:response.message
                    });
                    this.formData.img=response.data;
                    },
     /*编辑的钩子函数*/
                handleAvatarSuccesss(response, file) {
                    //我模型数据ImgaeUrl赋值,用于预览
                    /*  this.imageUrl='http://qfjm1t11q.hd-bkt.clouddn.com/'+response.data;*/
                    this.imageUrl=response.data;
                    this.$message({
                        type:response.flag?'success':'error',
                        message:response.message
                    });
                    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;
                },
                //添加
                handleAdd () {
                    this.dialogFormVisible=false;
                    //发送请求,把表单数据发送到后台
                    axios.post("/setmeal/add.do?checkgroupIds="+this.checkitemIds,this.formData).then((resp)=>{
                        if (resp.data.flag){
                            this.$message({
                                type:'success',
                                message:resp.data.message
                            })
                        }else {
                            this.$message.error(resp.dada.message)
                        }
                    }).finally(()=>{
                        this.findPage();
                    })
                },
                //分页查询
                findPage() {
                    if (this.pagination.queryString!=null&&this.pagination.queryString!==""){
                        let param={
                            currentPage: 1,//页码
                            pageSize: this.pagination.pageSize,
                            queryString: this.pagination.queryString
                        };
                        axios.post("/setmeal/findPage.do",param).then((response)=>{
                            this.dataList=response.data.rows;
                            this.pagination.total=response.data.total;
                        });
                    }else {
                        let param = {
                            currentPage: this.pagination.currentPage,//页码
                            pageSize: this.pagination.pageSize,
                            queryString: this.pagination.queryString
                        };
                        axios.post("/setmeal/findPage.do",param).then((response)=>{
                            this.dataList=response.data.rows;
                            this.pagination.total=response.data.total;
                        });
                    }
                },
                // 删除
                handleDelete(row) {
                    this.$confirm("你确定要删除当前数据吗?","提示",{
                        type:'warning'
                    }).then(()=>{
                        axios.get("/setmeal/deleteById.do?id="+row.id).then((resp)=>{
                            if (resp.data.flag){
                                this.$message({
                                    type:'success',
                                    message:res.data.message
                                });
                            }else {
                                this.$message.error(resp.data.message);
                            }
                        })
                    }).catch(()=>{
                        this.$message({
                            type:'info',
                            message:"操作取消"
                        })
                    }).finally(()=>{
                        this.findPage();
                    })
                },
                // 重置表单
                resetForm() {
                    this.activeName='first';
                    this.formData={};
                    this.imageUrl=null;
                    this.checkitemIds=[];
                },
                // 弹出添加窗口
                handleCreate() {
                    this.resetForm();
                    this.dialogFormVisible=true;

                    axios.get("/checkgroup/findAll.do").then((resp)=>{
                        if (resp.data.flag){
                            this.tableData=resp.data.data;
                        }else {
                            this.$message.error(resp.data.message)
                        }

                    })
                },
                //切换页码
                handleCurrentChange(currentPage) {
                    this.pagination.currentPage=currentPage;
                    this.findPage();
                }
            }
        })
    </script>
</html>

dao

package com.ybb.dao;

import com.github.pagehelper.Page;
import com.ybb.entity.QueryPageBean;
import com.ybb.pojo.CheckGroup;
import com.ybb.pojo.Setmeal;

import java.util.List;
import java.util.Map;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
public interface SetmealDao {

    void add(Setmeal setmeal);

    void setSetmealAndCheckGroup(Map map);

    Page<Setmeal> findByCondition(String querayString);



    public void edit(Setmeal setmeal);

    void setRelationShip(Integer id);

    void deleteById(Integer id);

    void setSetMealIdAndCheckGroupId(Map<String, Object> map);

    Setmeal findById(Integer id);

    List<Integer> findcheckIdBySetMealId(Integer id);
}

dao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ybb.dao.SetmealDao">

    <!--新增,获取(自增)产生的最后一个id,封装到id属性中-->
    <insert id="add" parameterType="com.ybb.pojo.Setmeal">
   <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>

<!--需要改成foreach添加-->
    <insert id="setSetmealAndCheckgroup" parameterType="map">
        insert into t_setmeal_checkgroup(setmeal_id,checkgroup_id)
        values
        <foreach collection="checkgroup_id" separator="," item="groupId">
            (#{setmeal_id},#{groupId})
        </foreach>
    </insert>




    <select id="findByCondition" parameterType="string" resultType="com.ybb.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>





    <!--需要改成foreach添加-->
    <insert id="setSetMealIdAndCheckGroupId" parameterType="map">
        insert into t_setmeal_checkgroup(setmeal_id,checkgroup_id)
        values
        <foreach collection="checkgroupIds" separator="," item="groupId">
            (#{setmeal},#{groupId})
        </foreach>
    </insert>

    <!--根据id来动态修改检查组相关字段-->
    <update id="edit" parameterType="com.ybb.pojo.Setmeal">
        update t_setmeal
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="sex != null">
                sex = #{sex},
            </if>
            <if test="code != null">
                code = #{code},
            </if>
            <if test="helpCode != null">
                helpCode = #{helpCode},
            </if>
            <if test="attention != null">
                attention = #{attention},
            </if>
            <if test="remark != null">
                remark = #{remark},
            </if>
            <if test="age != null">
                age = #{age},
            </if>
            <if test="price != null">
                price = #{price},
            </if>
            <if test="img != null">
                img = #{img},
            </if>
        </set>
        where id = #{id}
    </update>

    <delete id="setRelationShip" parameterType="int">
        delete from t_setmeal_checkgroup where setmeal_id=#{id}
    </delete>

    <delete id="deleteById" parameterType="int">
        delete from t_setmeal where id=#{id}
    </delete>

    <select id="findById" resultType="com.ybb.pojo.Setmeal">
         select * from t_setmeal where id=#{id}
    </select>

    <select id="findcheckIdBySetMealId" parameterType="int" resultType="int">
        select checkgroup_id from t_setmeal_checkgroup where setmeal_id =#{id}
    </select>

</mapper>

service

package com.ybb.service;

import com.ybb.entity.PageResult;
import com.ybb.entity.QueryPageBean;
import com.ybb.entity.Result;
import com.ybb.pojo.CheckGroup;
import com.ybb.pojo.Setmeal;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
public interface SetmealService {

    Result add(Setmeal setmeal, Integer[]checkgroupIds);

    PageResult findPage(QueryPageBean queryPageBean);

    void setSetmealAndCheckgroup(Integer id,Integer[]checkgroupIds);

    void edit(Setmeal setmeal, Integer[] checkgoupIds);

    void deleteById(Integer id);

    Setmeal findById(Integer id);

    List<Integer> findcheckIdBySetMealId(Integer id);
}

Impl

package com.ybb.service.Impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.ybb.dao.SetmealDao;
import com.ybb.entity.PageResult;
import com.ybb.entity.QueryPageBean;
import com.ybb.entity.Result;
import com.ybb.pojo.CheckGroup;
import com.ybb.pojo.Setmeal;
import com.ybb.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.JedisPool;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
@Service(interfaceClass = SetmealService.class)
@Transactional
public class SetmealServiceImpl implements SetmealService {

    @Autowired
    private SetmealDao setmealDao;



    //新增套餐信息,同时需要关联检查组
    @Override
    public Result add(Setmeal setmeal, Integer[] checkgroupIds) {
      //校验
        setmealDao.add(setmeal);
     //数据库生成的id
        Integer id = setmeal.getId();
      this.setSetmealAndCheckgroup(id,checkgroupIds);


        return null;
    }

    @Override
    public PageResult findPage(QueryPageBean queryPageBean) {

        Integer currentPage = queryPageBean.getCurrentPage();
        Integer pageSize = queryPageBean.getPageSize();
        String queryString = queryPageBean.getQueryString();

        PageHelper.startPage(currentPage,pageSize);
        Page<Setmeal>page=setmealDao.findByCondition(queryPageBean.getQueryString());
        return new PageResult(page.getTotal(),page.getResult());
    }


    //设置套餐和检查组多对多关系,操作t_setmeal_checkgroup
    @Override
    public void setSetmealAndCheckgroup(Integer id,Integer[]checkgroupIds){
        if (checkgroupIds!=null&&checkgroupIds.length>0){
            Map<String,Object>map=new HashMap<>();
            map.put("setmealId",id);
            map.put("checkgroupId",checkgroupIds);
            setmealDao.setSetmealAndCheckGroup(map);
        }
    }



    @Override
    public void edit(Setmeal Setmeal, Integer[] checkgroupIds) {
        //操作检查组基本信息,操作检查组t_checkgroup表
        setmealDao.edit(Setmeal);
        //中间表关系清除
        setmealDao.setRelationShip(Setmeal.getId());
        //重新建立关系
        Integer id = Setmeal.getId();
        if (checkgroupIds != null && checkgroupIds.length > 0) {
            Map<String, Object> map = new HashMap<>();
            map.put("setmeal", id);
            map.put("checkgroupIds", checkgroupIds);
            setmealDao.setSetMealIdAndCheckGroupId(map);
        }
    }

    @Override
    public void deleteById(Integer id) {
        //先清理关系,再删除
        setmealDao.setRelationShip(id);

        setmealDao.deleteById(id);
    }



    @Override
    public Setmeal findById(Integer id) {
        if (id < 0) {
            return null;
        } else {
            return setmealDao.findById(id);
        }
    }

    @Override
    public List<Integer> findcheckIdBySetMealId(Integer id) {
        return setmealDao.findcheckIdBySetMealId(id);
    }

}

controller

package com.ybb.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.aliyun.oss.model.ObjectMetadata;
import com.ybb.constant.MessageConstant;
import com.ybb.entity.PageResult;
import com.ybb.entity.QueryPageBean;
import com.ybb.entity.Result;
import com.ybb.pojo.CheckGroup;
import com.ybb.pojo.Setmeal;
import com.ybb.service.SetmealService;


import com.ybb.utils.ALiYunUtils;
import com.ybb.utils.QiniuUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import redis.clients.jedis.JedisPool;


import java.io.IOException;

import java.util.List;
import java.util.UUID;

import static com.ybb.utils.QiniuUtils.upload2Qiniu;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :体检套餐管理
 * Version :1.0
 */
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
//使用JedisPool操作Redis服务
    @Autowired
    private JedisPool jedisPool;

    //文件上传
    @RequestMapping("/upload")
    public Result upload(MultipartFile imgFile) throws IOException {
        //TODO 记得做数据校验,校验数据的大小,类型,还有文件名拼接时注意长度


//处理后缀,uuid+.jpg拼接
        String originalFilename = imgFile.getOriginalFilename();//原始文件名
        int index = originalFilename.lastIndexOf(".");
        String extention = originalFilename.substring(index ); //.jpg

        String fileName = UUID.randomUUID().toString()+extention;
        try {
           QiniuUtils.upload2Qiniu(imgFile.getBytes(),fileName);
      /*   String url = ALiYunUtils.add(imgFile.getBytes(), fileName);*/
            System.out.println();
    /* jedisPool.getResource().sadd("health-img",fileName);*/
            return new Result(true,MessageConstant.PIC_UPLOAD_SUCCESS,fileName);
           /* return new Result(true,MessageConstant.PIC_UPLOAD_SUCCESS,url);*/
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);
        }
    }

    @Reference
    private SetmealService setmealService;

    //文件上传
    @RequestMapping("/add")
    public Result add(@RequestBody Setmeal setmeal, Integer[]checkgroupIds){
      try {
          setmealService.add(setmeal,checkgroupIds);

        /*  jedisPool.getResource().srem("health-img",setmeal.getImg());*/

          return new Result(true,MessageConstant.ADD_SETMEAL_SUCCESS);
      }catch (Exception e){
          e.printStackTrace();
          return new Result(false,MessageConstant.ADD_SETMEAL_FAIL);
      }
    }





    @RequestMapping("/findPage")
    public PageResult findPage(@RequestBody QueryPageBean queryPageBean){
      return setmealService.findPage(queryPageBean);
    }



    @RequestMapping("/edit")
    public Result edit(@RequestBody Setmeal setmeal, Integer[]checkgroupIds){
        try {
            setmealService.edit(setmeal,checkgroupIds);
            return new Result(true, "编辑套餐项成功");
        }catch (Exception e){
            e.printStackTrace();
            return new Result(false, "编辑套餐项失败");
        }
    }



    @RequestMapping("/findById")
    public Result findById(Integer id) {
        try {
            Setmeal setmeal= setmealService.findById(id);
            return new Result(true,MessageConstant.QUERY_SETMEAL_SUCCESS,setmeal);
        }catch (Exception e){
            e.printStackTrace();
            return new Result(false,MessageConstant.QUERY_SETMEAL_FAIL);
        }
    }




    @RequestMapping("/findcheckIdBySetMealId")
    public Result findcheckIdBySetMealId(Integer id) {
        try {
            List<Integer> checkgroups= setmealService.findcheckIdBySetMealId(id);
            return new Result(false,MessageConstant.QUERY_SETMEAL_SUCCESS,checkgroups);
        }catch (Exception e){
            e.printStackTrace();
            return new Result(false,MessageConstant.QUERY_SETMEAL_FAIL);
        }
    }


    @RequestMapping("/deleteById")
    public Result deleteById(Integer id){
        try {
            setmealService.deleteById(id);
        }catch (Exception e){
            e.printStackTrace();
            return new Result(false,"删除套餐成功");
        }
        return new Result(true,"删除套餐失败");
    }


}

阿里云工具类

package com.ybb.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URL;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by Administrator
 * Date :2020/8/24
 * Description :
 * Version :1.0
 */
public class ALiYunUtils {
    public static   String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    public static   String accessKeyId = "xxx";
    public static   String accessKeySecret = "xxx";
    public static   String bucketName = "xxx";

    public static void delete(String fileName){

        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 删除文件。如需删除文件夹,请将ObjectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。
        ossClient.deleteObject(bucketName, fileName);
// 关闭OSSClient。
        ossClient.shutdown();
    }


    public static String add(byte[] bytes, String fileName){

        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
//这一步很关键!不然图片会一直下载不显示
        ObjectMetadata meta = new ObjectMetadata();
        meta.setContentType("image/jpg");
// 上传Byte数组。
        ossClient.putObject(bucketName, fileName, new ByteArrayInputStream(bytes));

        // 设置URL过期时间为1小时。
        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000*365);
        // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。
        URL url = ossClient.generatePresignedUrl(bucketName, fileName, expiration);
        ossClient.shutdown();
        System.out.println(url.toString());
        return url.toString();
    }
}

七牛工具类

package com.ybb.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;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * 七牛云工具类
 */
public class QiniuUtils {
    public  static String accessKey = "xxx";
    public  static String secretKey = "xxx";
    public  static String bucket = "xxx";

    public static void upload2Qiniu(String filePath,String fileName){
        //构造一个带指定Zone对象的配置类
        Configuration cfg = new Configuration(Zone.zone0());
        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.zone0());
        //...其他参数参考类注释
        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.zone0());
        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());
        }
    }
}

web层
重点在于图片的回写,要先上传到七牛,然后用钩子函数回调的方式展示在界面上,编辑如此
controller
会出现上传完成,点取消不上传的情况,七牛会产生垃圾图片,这个怎么清理呢,用redis和定时任务组件Quartz配合使用来实现。存储redis时用set集合的形式存入,如果添加图片,在redis,七牛会出现一份,而提交时把图片添加到数据库中,同时把redis中的图片名给删除掉,这样redis中只会保存垃圾图片(冗余),定时任务根据redis中的图片信息定时清理redis和七牛即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值