第一章(项目概述和环境搭建)
项目概述
技术架构
功能架构
环境搭建
项目结构
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模块下的依赖可以全部继承过来
对于health_common、health_interface: 都是打jar包(maven默认打包方式)
对于health_service_provider、 health_backend: 都是要选择打war包(因为要单独启动)
对于health_service_provider:
将spring的配置文件拆解为spring-dao、spring-service、spring-tx三部分,方便管理
对于health_backend:
Power Designer
物理数据模型--->sql语句
sql语句--->物理数据模型(逆向工程)
ElementUI与Vue2.0
注意: 不是elementUIPlus与vue3.0
(cdn方式)引入elementUI与vue2:
(本地文件的方式)引入elementUI与vue2:(未完成)
第二章(预约管理——检查项管理)
需求分析
预约管理包括: 检查项管理、检查组管理、体验套餐管理、预约设置等
基础环境搭建
导入表
导入实体类
实体类都放在health_common模块中,因为各个模块都会用到实体类
导入项目所需公共资源
公共资源也都放在health_common模块中
导入静态资源
静态资源放在health_backend模块中(负责mvc)
正式开发
项目启动
项目要依赖zookeeper,需要先启动zookeeper服务
zookeeper是dubbo服务的注册中心
启动zookeeper:
maven clean后利用maven install打包(health_parent中):
启动tomcat(利用插件)(health_backend中):
访问静态页面:
分析页面布局:
右侧iframe中的页面应该是这样控制的:
前端的一些注意细节
1.做校验时:
上面的@click="handleAdd('dataAddForm')"中的dataAddForm要与这里的ref里的保持一致:
2.做校验时的bug:
规则校验后有个bug: 上一次表单上弹出的红字,下一次打开表单还是能看见
解决办法:
3. 关于 this.$refs['']
所有像这样在标签中以ref='name'注册过的dom元素,都可以用this.$refs['name'].function()来调用其方法
因此:
即使这里不传参数:
这里也能直接调用dom元素的方法:
前提是dom元素的ref名字要处处一致
4. 关于web.xml中的servlet-mapping配置
作用体现在:
而真正的controller中并没有.do:
controller层的一些注意细节
serviceImpl层的一些注意细节
dao层的一些注意细节
测试
先启动zookeeper,再启动health_service_provider,最后启动health_backend
基础架构:
架构的具体实现:
- health_backend中指定zookeeper:
- health_backend中消费服务:
-
zookeeper配置文件修改好,制定好端口后启动
-
编写health_interface,提供服务接口:
- 编写health_service_provider,实现服务接口:
- 编写health_service_provider,实现dao层:
测试过程遇到的一些问题:
1. 关于注册zookeeper的配置文件:
health_service_provider中:
health_backend中:
如果上面两处不指定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分页插件
debug中的第一个查询语句,select count(0)...,为什么永远返回的是1,我是真的不理解...
查询中遇到的一个问题
问题描述:在检查项和检查组页面进行分页查询时,如果不是在第一页,此时在查询输入框中输入过滤 条件并点击查询按钮,无法查询到对应数据。
问题原因: 利用mybatis分页插件之后,每次分页查询的sql后都自动加上了limit '',''
不管查询框中是否有过滤条件,每次查询的sql语句后面都有limit '',''
那么当查询框中有过滤条件的时候,若查询到的内容小于一页,而limit '',''表示的是大于1页的内容,就会导致无法查询到对应数据
解决办法:
办法1(现在用的, 不太好):
办法2(更好):
删除检查项
编辑检查项
第三章(预约管理——检查组管理)
新增检查组
检查组分页
404 NOT FOUND
上面的url的前面必须要加一个'/', 否则对应的请求就发到了:
而本来期望发送到:
原因:
可见前端中的'/'相当于'/pages', 必须要加上
编辑检查组
删除检查组
第四章(预约管理——套餐管理)
图片存储方案
没有用视频中的七牛云,用的是阿里云OSS
导入阿里云OSS依赖的时候出现了问题
不管怎么样导入依赖都爆红,然后发现是在dependencyManagement标签中导入的,难怪会爆红。
项目刚搭建导入全部依赖的时候就出现这个问题了,解决方法是:
先将要导入的依赖放入单独的dependencies标签下(而不是dependencyManagement标签的dependencies标签下),然后reload dependency,就会从网上将依赖下载到本地maven仓库。然后就可以将依赖移到dependencyManagement标签的dependencies标签下了
归根结底的原因就是: dependencyManagement标签只能读取本地maven仓库中的依赖,如果本地maven仓库中没有,那么就会爆红
使用阿里云OSS
安装SDK
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
创建AccessKey(用于鉴权)
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();
}
}
}
}
下面这个url是可以访问的
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();
}
}
}
}
封装工具类
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();
}
}
}
}
新增套餐
前端文件上传相关:
action用于直接发送ajax请求controller
name表示ajax请求携带的参数名
:on-success表示上传成功后回调的方法: 这个上传成功,应该指的是后端返回的响应码为200
:before-upload表示上传之前调用的方法: 一般用来做校验
v-if="imageUrl" :src="imageUrl表示如果imageUrl不是空,则显示图片,图片路径为imageUrl
可以看看前端发送给controller的请求:
前端处理的逻辑如下:
用户从前端上传图片后,前端通过ajax请求将图片发送到controller,controller将图片上传到阿里云OSS,上传完毕后,返回图片的名称(与阿里云OSS中的一致)到前端。前端收到响应后调用回调函数(钩子函数)handleAvatarSuccess(response, file), 在函数中将图片名称提取出来,与固定的imageUrlPre拼接成阿里云OSS中该图片的url,将该url赋值给imageUrl,图片便可回显给用户;并且将图片名称赋值给formData.img,提交表单时,便可将图片名一起提交,保存到数据库中。
后端文件上传相关
需要springMVC的文件上传组件,用途是接收前端传来的文件的二进制数据:
后端要将前端传来的文件上传到阿里云OSS,并返回给前端文件名:
完善文件上传(关于阿里云OSS中的垃圾图片)
之前的图片存储有一个问题:
由于图片设置为autoUpload,导致会出现用户只上传了图片到阿里云OSS而没有最终保存套餐信息到我们的数据库(即只上传图片却没有点确定按钮提交表单),这时我们上传的图片就变味了垃圾图片。对于这些垃圾图片我们需要定时清理来释放磁盘空间。这就需要我们能够区分哪些是垃圾图片,哪些不是垃圾图片。方案就是利用redis来保存图片名称,具体做法为:
-
当用户上传图片后,将图片名称保存到redis的一个Set集合中,例如集合名称为 setmealPicResources
-
当用户添加套餐后,将图片名称保存到redis的另一个Set集合中,例如集合名称为 setmealPicDbResources
-
计算setmealPicResources集合与setmealPicDbResources集合的差值,结果就是垃圾图片的名称 集合,清理这些图片即可 本小节我们先来完成前面2个环节,第3个环节(清理图片环节)在后面会通过定时任务再实现。
(1) 先启动redis服务
(2) 在backend模块与service_provider模块中添加spring-redis.xml配置文件
(3) 加载配置文件
(4) 在common模块下提供redis常量类
(5) 完善SetmealController,在文件上传成功后将图片名称保存到redis集合中
service_provider模块同样如此:
由于web.xml中已经配置了扫描,则不再需要加载配置文件
套餐分页查询
跟检查组一样
然后我把“不在第一页时,查询不到结果”这个bug在前端解决了
定时任务组件Quartz
用来定时清理阿里云OSS上的垃圾图片
提供Quartz的Spring配置文件: spring-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等
cron表达式
0/10 * * * * ?
cron表达式在线生成器
网址: cron.qqe2.com
定时清理垃圾图片
操作步骤:
(1) 创建maven工程health_jobs, 打包方式为war(因为要借助tomcat),导入Quartz等相关坐标
(2) 配置web.xml
(3) 配置log4j.properties
(4) 配置applicationContext-redis.xml
(5) 配置applicationContext-jobs.xml
(6) 创建ClearImgJob定时类
(7) 测试是否将redis中的垃圾图片与阿里云OSS中的垃圾图片删除
编辑套餐
写编辑套餐的时候还是考虑了一些东西了的:
关于编辑时的图片修改
有两种情况:
- 修改图片后没有点击提交:后果就是有两处记录下了imgName,一是OSS,二是redis的"setmealPicOSSResources"。由于redis的两个集合间存在差值,所以这两处垃圾可由定时任务清理
- 修改图片后点击了提交:后果就是有三处记录下了imgName,一是OSS,二是redis的"setmealPicOSSResources"与"setmealPicDbResources"。由此便出现问题: redis的两个集合间不存在该imgName的差值,那么定时清理则无法清理掉这三处的垃圾imgName。解决办法:每次提交编辑表单后,先不急着去数据库中update setmeal,而是先从数据库中取出原来的该setmeal,让它的imgName与现有的imgName对比。如果一样,则证明没有垃圾产生,一切照常;如果不一样,则证明修改了图片,有三处垃圾产生,则根据原来的imgName来清理掉三处垃圾。然后再将新的imgName存入redis的"setmealPicDbResources"(OSS与redis的"setmealPicOSSResources"在编辑上传图片时就存入了)。
这段代码后面绝对是可以优化的
删除套餐
没啥问题
第五章(预约管理——预约设置)
需求分析
前面我们已经完成了检查项管理、检查组管理、套餐管理等。接下来我们需要进行预约设置,其实就是 设置每一天的体检预约最大数量。客户可以通过微信端在线预约,在线预约时需要选择体检的时间,如 果客户选择的时间已经预约满则无法进行预约。
Apache POI
Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。