传智健康项目——基于SSM+Zookeeper+Dubbo+Spring Security

476 阅读13分钟

第一章(项目概述和环境搭建)

项目概述

技术架构

image.png

功能架构

image.png

环境搭建

项目结构

image.png

image.png

maven项目搭建

在project structure中:

先创建父maven模块(主要是配置好pom文件,控制依赖version): health_parent

😡父maven模块中的pom.xml爆红问题解决: 先将依赖移出 dependencyManagement 标签,刷新让它们下载完毕后,再将它们放回dependencyManagement标签中

再创建子maven模块(主要是实际导入依赖,此时便不需要再声明version了!): health_common 、 health_interface 、 health_service_provider 、 health_backend (health_mobile)

对于health_interface、health_service_provider等模块,直接依赖health_common模块,则health_common模块下的依赖可以全部继承过来

image.png

对于health_common、health_interface: 都是打jar包(maven默认打包方式)

对于health_service_provider、 health_backend: 都是要选择打war包(因为要单独启动)

对于health_service_provider:

image.png

将spring的配置文件拆解为spring-dao、spring-service、spring-tx三部分,方便管理

image.png

对于health_backend:

image.png

Power Designer

物理数据模型--->sql语句

sql语句--->物理数据模型(逆向工程)

ElementUI与Vue2.0

注意: 不是elementUIPlus与vue3.0

(cdn方式)引入elementUI与vue2:

image.png

image.png

(本地文件的方式)引入elementUI与vue2:(未完成)

第二章(预约管理——检查项管理)

image.png

需求分析

预约管理包括: 检查项管理、检查组管理、体验套餐管理、预约设置等

基础环境搭建

导入表

image.png

导入实体类

实体类都放在health_common模块中,因为各个模块都会用到实体类

image.png

导入项目所需公共资源

公共资源也都放在health_common模块中

image.png

导入静态资源

静态资源放在health_backend模块中(负责mvc)

image.png

正式开发

项目启动

项目要依赖zookeeper,需要先启动zookeeper服务

zookeeper是dubbo服务的注册中心

image.png

启动zookeeper: image.png

maven clean后利用maven install打包(health_parent中):

image.png

启动tomcat(利用插件)(health_backend中):

image.png

访问静态页面:

image.png

image.png

image.png

分析页面布局:

image.png

右侧iframe中的页面应该是这样控制的:

image.png image.png

前端的一些注意细节

image.png

1.做校验时:

~8ER9H94)R@%UL6D(8C82A7.png

image.png

上面的@click="handleAdd('dataAddForm')"中的dataAddForm要与这里的ref里的保持一致:

image.png

2.做校验时的bug:

规则校验后有个bug: 上一次表单上弹出的红字,下一次打开表单还是能看见

解决办法:

image.png

3. 关于 this.$refs['']

所有像这样在标签中以ref='name'注册过的dom元素,都可以用this.$refs['name'].function()来调用其方法 image.png

因此:

即使这里不传参数: image.png

这里也能直接调用dom元素的方法: image.png

前提是dom元素的ref名字要处处一致

4. 关于web.xml中的servlet-mapping配置

image.png image.png

作用体现在:

image.png

而真正的controller中并没有.do:

image.png

image.png

controller层的一些注意细节

image.png

serviceImpl层的一些注意细节

dao层的一些注意细节

测试

先启动zookeeper,再启动health_service_provider,最后启动health_backend

image.png

基础架构:

F5EFF0C6567A09F4F7C37FAD42408B64.png

架构的具体实现:

  1. health_backend中指定zookeeper:

image.png

  1. health_backend中消费服务:

image.png

  1. zookeeper配置文件修改好,制定好端口后启动

  2. 编写health_interface,提供服务接口:

image.png

  1. 编写health_service_provider,实现服务接口:

image.png

  1. 编写health_service_provider,实现dao层:

image.png

image.png

测试过程遇到的一些问题:

1. 关于注册zookeeper的配置文件:

health_service_provider中:

image.png

health_backend中:

image.png

如果上面两处不指定file属性的话,启动tomcat的时候会有如下错误:

[DUBBO] Failed to save registry store file, cause: Invalid file path, dubbo version: 2.6.0, current host: 10.30.110.250
java.io.IOException: Invalid file path 疯狂刷屏

新增检查项

检查项分页查询

利用了mybatis分页插件

image.png

image.png

image.png

image.png debug中的第一个查询语句,select count(0)...,为什么永远返回的是1,我是真的不理解...

查询中遇到的一个问题

问题描述:在检查项和检查组页面进行分页查询时,如果不是在第一页,此时在查询输入框中输入过滤 条件并点击查询按钮,无法查询到对应数据。

问题原因: 利用mybatis分页插件之后,每次分页查询的sql后都自动加上了limit '',''

不管查询框中是否有过滤条件,每次查询的sql语句后面都有limit '',''

那么当查询框中有过滤条件的时候,若查询到的内容小于一页,而limit '',''表示的是大于1页的内容,就会导致无法查询到对应数据

解决办法:

办法1(现在用的, 不太好): image.png

办法2(更好):

image.png

删除检查项

编辑检查项

第三章(预约管理——检查组管理)

新增检查组

检查组分页

404 NOT FOUND

image.png

上面的url的前面必须要加一个'/', 否则对应的请求就发到了:

image.png

而本来期望发送到:

image.png

原因:

image.png

可见前端中的'/'相当于'/pages', 必须要加上

编辑检查组

删除检查组

第四章(预约管理——套餐管理)

图片存储方案

image.png

没有用视频中的七牛云,用的是阿里云OSS

导入阿里云OSS依赖的时候出现了问题

不管怎么样导入依赖都爆红,然后发现是在dependencyManagement标签中导入的,难怪会爆红。

image.png

项目刚搭建导入全部依赖的时候就出现这个问题了,解决方法是:

先将要导入的依赖放入单独的dependencies标签下(而不是dependencyManagement标签的dependencies标签下),然后reload dependency,就会从网上将依赖下载到本地maven仓库。然后就可以将依赖移到dependencyManagement标签的dependencies标签下了

image.png

归根结底的原因就是: dependencyManagement标签只能读取本地maven仓库中的依赖,如果本地maven仓库中没有,那么就会爆红

image.png

image.png

使用阿里云OSS

安装SDK

        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.1</version>
        </dependency>

创建AccessKey(用于鉴权)

image.png

java SDK操作阿里云OSS(文件上传)


import org.junit.Test;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import java.io.File;

public class AliyunOSSTest {

    //使用阿里云提供的SDK实现将本地图片上传到阿里云OSS服务器
    @Test
    public void test1(){
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "yourAccessKeyId";
        String accessKeySecret = "yourAccessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。 //objectName就是上传后的文件名(也可包括bucket中存放该文件的目录)
        String objectName = "exampledir/exampleobject.txt";
        // 填写本地文件的完整路径,例如D:\localpath\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String filePath= "D:\localpath\examplefile.txt";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

image.png

下面这个url是可以访问的

image.png

java SDK操作阿里云OSS(文件删除)


import com.aliyun.oss.ClientException;
        import com.aliyun.oss.OSS;
        import com.aliyun.oss.OSSClientBuilder;
        import com.aliyun.oss.OSSException;

public class Demo {
    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "yourAccessKeyId";
        String accessKeySecret = "yourAccessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写文件完整路径。文件完整路径中不能包含Bucket名称。
        String objectName = "exampleobject.txt";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 删除文件或目录。如果要删除目录,目录必须为空。
            ossClient.deleteObject(bucketName, objectName);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}            

封装工具类

image.png


import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;

import java.io.ByteArrayInputStream;
import java.io.File;

public class AliyunOSSUtil {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    public static String endpoint = "your endpoint";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    public static String accessKeyId = "your accessKeyId";
    public static String accessKeySecret = "your accessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    public static String bucketName = "your bucketName";

    /**
     * 上传本地文件到阿里云OSS
     * @param filePath:本地文件路径,例如D:\localpath\examplefile.txt
     * @param objectName: 上传后的文件名(也可包括bucket中存放该文件的目录)
     */
    public static void upload(String filePath,String objectName){
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 上传文件到阿里云OSS,以字节数组的方式
     * @param bytes:文件的字节流
     * @param objectName:上传后的文件名(也可包括bucket中存放该文件的目录)
     */
    public static void upload(byte[] bytes, String objectName){
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    public static void delete(String objectName){
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 删除文件或目录。如果要删除目录,目录必须为空。
            ossClient.deleteObject(bucketName, objectName);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

新增套餐

前端文件上传相关:

image.png

image.png

action用于直接发送ajax请求controller

name表示ajax请求携带的参数名

:on-success表示上传成功后回调的方法: 这个上传成功,应该指的是后端返回的响应码为200

:before-upload表示上传之前调用的方法: 一般用来做校验

v-if="imageUrl" :src="imageUrl表示如果imageUrl不是空,则显示图片,图片路径为imageUrl

可以看看前端发送给controller的请求:

image.png

image.png

image.png

image.png

前端处理的逻辑如下:

用户从前端上传图片后,前端通过ajax请求将图片发送到controller,controller将图片上传到阿里云OSS,上传完毕后,返回图片的名称(与阿里云OSS中的一致)到前端。前端收到响应后调用回调函数(钩子函数)handleAvatarSuccess(response, file), 在函数中将图片名称提取出来,与固定的imageUrlPre拼接成阿里云OSS中该图片的url,将该url赋值给imageUrl,图片便可回显给用户;并且将图片名称赋值给formData.img,提交表单时,便可将图片名一起提交,保存到数据库中。

后端文件上传相关

需要springMVC的文件上传组件,用途是接收前端传来的文件的二进制数据:

image.png

image.png

后端要将前端传来的文件上传到阿里云OSS,并返回给前端文件名:

image.png

完善文件上传(关于阿里云OSS中的垃圾图片)

之前的图片存储有一个问题:

image.png

由于图片设置为autoUpload,导致会出现用户只上传了图片到阿里云OSS而没有最终保存套餐信息到我们的数据库(即只上传图片却没有点确定按钮提交表单),这时我们上传的图片就变味了垃圾图片。对于这些垃圾图片我们需要定时清理来释放磁盘空间。这就需要我们能够区分哪些是垃圾图片,哪些不是垃圾图片。方案就是利用redis来保存图片名称,具体做法为:

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

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

  3. 计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称 集合,清理这些图片即可 本小节我们先来完成前面2个环节,第3个环节(清理图片环节)在后面会通过定时任务再实现。

(1) 先启动redis服务

(2) 在backend模块与service_provider模块中添加spring-redis.xml配置文件

image.png

(3) 加载配置文件

image.png

(4) 在common模块下提供redis常量类

image.png

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

image.png

image.png

service_provider模块同样如此:

image.png

由于web.xml中已经配置了扫描,则不再需要加载配置文件 image.png

image.png

套餐分页查询

跟检查组一样

然后我把“不在第一页时,查询不到结果”这个bug在前端解决了

image.png

image.png

定时任务组件Quartz

用来定时清理阿里云OSS上的垃圾图片

image.png

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

cron表达式

0/10 * * * * ?

image.png

image.png

cron表达式在线生成器

网址: cron.qqe2.com

image.png

定时清理垃圾图片

操作步骤:

(1) 创建maven工程health_jobs, 打包方式为war(因为要借助tomcat),导入Quartz等相关坐标

image.png

(2) 配置web.xml

image.png

(3) 配置log4j.properties

(4) 配置applicationContext-redis.xml

(5) 配置applicationContext-jobs.xml

(6) 创建ClearImgJob定时类

image.png

image.png

(7) 测试是否将redis中的垃圾图片与阿里云OSS中的垃圾图片删除

image.png

image.png

编辑套餐

写编辑套餐的时候还是考虑了一些东西了的:

关于编辑时的图片修改

有两种情况:

  1. 修改图片后没有点击提交:后果就是有两处记录下了imgName,一是OSS,二是redis的"setmealPicOSSResources"。由于redis的两个集合间存在差值,所以这两处垃圾可由定时任务清理
  2. 修改图片后点击了提交:后果就是有三处记录下了imgName,一是OSS,二是redis的"setmealPicOSSResources"与"setmealPicDbResources"。由此便出现问题: redis的两个集合间不存在该imgName的差值,那么定时清理则无法清理掉这三处的垃圾imgName。解决办法:每次提交编辑表单后,先不急着去数据库中update setmeal,而是先从数据库中取出原来的该setmeal,让它的imgName与现有的imgName对比。如果一样,则证明没有垃圾产生,一切照常;如果不一样,则证明修改了图片,有三处垃圾产生,则根据原来的imgName来清理掉三处垃圾。然后再将新的imgName存入redis的"setmealPicDbResources"(OSS与redis的"setmealPicOSSResources"在编辑上传图片时就存入了)。

这段代码后面绝对是可以优化的 image.png

删除套餐

没啥问题

第五章(预约管理——预约设置)

需求分析

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

Apache POI

Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。