一、项目介绍
尚医通即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号!不用排长队!
二、技术选型
- SpringBoot:简化新Spring应用的初始搭建以及开发过程
- SpringCloud:基于Spring Boot实现的云原生应用开发工具,SpringCloud使用的技术:(SpringCloudGateway、Spring Cloud Alibaba Nacos、Spring Cloud Alibaba Sentinel、SpringCloud Task和SpringCloudFeign等)
- MyBatis-Plus:持久层框架
- Redis:内存缓存
- RabbitMQ:消息中间件
- HTTPClient: Http协议客户端
- Swagger2:Api接口文档工具
- Nginx:负载均衡
- Lombok
- Mysql:关系型数据库
- MongoDB:面向文档的NoSQL数据库
- Vue.js:web 界面的渐进式框架
- Node.js: JavaScript 运行环境
- Axios:Axios 是一个基于 promise 的 HTTP 库
- NPM:包管理器
- Babel:转码器
- Webpack:打包工具
- Docker :容器技术
- Git:代码管理工具
- 使用Nginx为外部负载,将请求转发代理到JWT进行统一的网关认证,使用SpringCloud微服务一站式解决方案对各个服务模块进行处理。
- 使用ELK日志系统对项目日志进行实时地采集,同时对象的文件存储方式使用OSS,此外使用Redis,MongoDB,RabbitMQ一整套集群 对大量用户数据进行处理。
- 最后通过Docker对整个项目进行自动化的一站式部署。
三、项目架构
1. 管理员系统
- 数据管理模块
- 医院管理模块
- 会员管理模块
- 订单管理模块
- 统计管理模块
2. 用户系统
- 首页数据显示
- 预约挂号
- 支付挂号订单
- 手机号登录以及微信登录。
四、业务流程
- 管理端主要功能分为数据管理,会员管理,订单管理,医院管理与统计管理五大模块。
- 数据管理模块是对共通数据进行的封装处理,相当于数据字典。会员管理模块是对会员用户的管理,会员权限审批等。
- 订单管理模块是对客户预约挂号订单的管理。医院管理就是管理相关医院的信息,统计管理是对某一时间段中挂号预约流量的统计。
- 简单介绍一下此项目的主要业务流程,客户通过网页登录到医院的门户网站进行预约挂号。
- 可以浏览医院的基本信息,预约方式,科室信息等,选择相关医室进行挂号预约,确认成功之后进行支付。
- 支付完成后可以选择取号,也可以取消挂号退款。
五、开发环境搭建
- 搭建父工程hospital。
- pom的意思是项目里没有java代码,也不执行任何代码,只是为了聚合工程或传递依赖用的。所以并不会寻找配置文件。
<packaging>pom</packaging>
- 父工程pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules> <module>common</module>
<module>model</module>
<module>service</module>
<module>server-gateway</module>
<module>service-client</module>
<module>hospital-manage</module>
</modules>
<parent> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>pers.jl</groupId>
<artifactId>hospital</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<properties> <java.version>1.8</java.version>
<cloud.version>Hoxton.RELEASE</cloud.version>
<alibaba.version>2.2.0.RELEASE</alibaba.version>
<mybatis-plus.version>3.3.1</mybatis-plus.version>
<mysql.version>8.0</mysql.version>
<swagger.version>2.7.0</swagger.version>
<jwt.version>0.7.0</jwt.version>
<fastjson.version>1.2.29</fastjson.version>
<httpclient.version>4.5.1</httpclient.version>
<easyexcel.version>2.2.0-beta2</easyexcel.version>
<aliyun.version>4.1.1</aliyun.version>
<oss.version>3.9.1</oss.version>
<jodatime.version>2.10.1</jodatime.version>
</properties>
<!--配置dependencyManagement锁定依赖的版本-->
<dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency> <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency> <!--swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency> <groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<dependency> <groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun.version}</version>
</dependency>
<dependency> <groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${oss.version}</version>
</dependency>
<!--日期时间工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime.version}</version>
</dependency> </dependencies> </dependencyManagement></project>
- 创建common子模块
- 要删除src目录。
- common模块pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent> <artifactId>hospital</artifactId>
<groupId>pers.jl</groupId>
<version>1.0</version>
</parent> <artifactId>common</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency> <dependency> <groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency> </dependencies>
</project>
- 创建common-util子模块,pom配置文件如下
<?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>common</artifactId>
<groupId>pers.jl</groupId>
<version>1.0</version>
</parent> <modelVersion>4.0.0</modelVersion>
<artifactId>common-util</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>common-util</name>
<description>common-util</description>
<dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency> <groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<!-- 日期工具栏依赖 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency> </dependencies></project>
- 然后后在common-util中添加工具类
- 创建service-util子模块,pom配置文件如下
<?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>common</artifactId>
<groupId>pers.jl</groupId>
<version>1.0</version>
</parent> <modelVersion>4.0.0</modelVersion>
<artifactId>service-util</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>service-util</name>
<description>service-util</description>
<dependencies> <dependency> <groupId>pers.jl</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
</dependency> <!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> <!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency> </dependencies>
</project>
- 然后后在service-util中添加工具类
- 创建model子模块
- model的pom文件
<?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>hospital</artifactId>
<groupId>pers.jl</groupId>
<version>1.0</version>
</parent> <modelVersion>4.0.0</modelVersion>
<artifactId>model</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>model</name>
<description>model</description>
<dependencies> <dependency> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> <!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency> <!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
</dependency> <dependency> <groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<scope>provided </scope>
</dependency> <dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<scope>provided </scope>
</dependency> <dependency> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<scope>provided </scope>
</dependency> </dependencies></project>
- 引入实体类
- 创建service子模块
- service的pom文件
<?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>hospital</artifactId>
<groupId>pers.jl</groupId>
<version>1.0</version>
</parent> <modelVersion>4.0.0</modelVersion>
<artifactId>service</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<dependencies> <dependency> <groupId>pers.jl</groupId>
<artifactId>service-util</artifactId>
<version>1.0</version>
</dependency> <dependency> <groupId>pers.jl</groupId>
<artifactId>model</artifactId>
<version>1.0</version>
</dependency> <!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency> <!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency> <!--开发者工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency> <!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> <!-- 服务注册 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency> <!-- 流量控制 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin> </plugins> <resources> <resource> <directory>src/main/java</directory>
<includes> <include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes> <filtering>false</filtering>
</resource> <resource> <directory>src/main/resources</directory>
<includes> <include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes> <filtering>false</filtering>
</resource> </resources> </build></project>
六、业务模块开发
一、医院设置模块
初始化
- 医院设置模块搭建
- 整合Swagger,通过Swagger的ui界面测试接口。
- 导入service-util依赖,service-util依赖中有Swagger的配置类,然后如果包不同,扫一下包即可整合Swagger.
功能
- 查询所有医院设置
- 删除医院设置
- 条件查询带分页医院设置
- 记得controller接口接收参数需要当前页码、当前页条数、vo对象,vo对象用来封装条件值。
- 接口使用POST方式,然后vo对象添加@RequestBody(require =false)可以方便前端传值,传值是json格式的。
- 添加医院设置
- 新增医院设置时,有些字段无法自动赋值,需要我们手动赋值,如医院状态、签名密钥。
- 根据id获取医院设置
- 修改医院设置
- 批量删除医院设置
- 医院设置锁定和解锁
- 通过短信验证码发送签名密钥。
全局异常处理
- 自定义异常
- 全局异常处理类
统一日志处理
- SpringBoot内部使用Logback作为日志实现的框架,Logback和log4j非常类似。
- Logback使用非常简单,只需导入一个配置文件即可,记得把application中对日志级别的设置注销掉。
二、数据字典模块
列表展示
- 根据id查询子数据列表。
// 根据id查询子数据
@Override
public List<Dict> getDictById(int id) {
LambdaQueryWrapper<Dict> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 父id等于当前id的数据全部查出来
lambdaQueryWrapper.eq(Dict::getParentId,id);
List<Dict> list = this.list(lambdaQueryWrapper);
// 对集合中的每个子数据做判断,判断是否含有子数据,给hasChildren赋值
list.stream().map(item -> {
boolean res = check(item.getId());
item.setHasChildren(res);
return item;
}).collect(Collectors.toList());
return list;
}
- 根据id查询数据是否有子数据,将布尔值赋值给数据的hashChildren字段。
// 判断是否含有子节点
public boolean check(long id){
LambdaQueryWrapper<Dict> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Dict::getParentId,id);
// 统计子数据个数
long count = this.count(lambdaQueryWrapper);
return count > 0;
}
数据字典导出
- 无需返回值,直接通过响应体通知浏览器下载。
- 封装成DictEeVo对象,可以给用户展示我们想展示的数据,更安全。
service层
// 导出成excel文件
@Override
public void exportExcel(HttpServletResponse response) {
// 1. 设置下载信息
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = "dict";// 设置文件名
// 在头信息中设置,以下载方式打开
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 2. 查询数据
List<Dict> dictList = this.list();
// 将数据替换成DictEeVo对象封装到响应体,安全。
List<DictEeVo> dictEeVoList = dictList.stream().map(item -> {
DictEeVo dictEeVo = new DictEeVo();
BeanUtils.copyProperties(item, dictEeVo);
return dictEeVo;
}).collect(Collectors.toList());
// 3. 导出
try {
EasyExcel.write(response.getOutputStream(),DictEeVo.class)
.sheet("数据字典")
.doWrite(dictEeVoList);
} catch (IOException e) {
// 导出失败抛出异常
throw new YyghException("数据导出失败!",404);
}
}
数据字典导入
- excel文件导出需要一个监听器来将每一行数据映射成Dict对象。
监听器
public class DictListener extends AnalysisEventListener<DictEeVo> {
private IDictService dictService;
public DictListener(IDictService dictService) {
this.dictService = dictService;
}
//一行一行读取
@Override
public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {
//调用方法添加数据库
Dict dict = new Dict();
BeanUtils.copyProperties(dictEeVo,dict);
dictService.save(dict);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
service层
// 导入excel文件
@Override
public void importData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(this))
.sheet().doRead();
} catch (IOException e) {
throw new YyghException("数据导入失败!",404);
}
}
数据字典添加缓存
- 由于管理端数据字典是一些固定数据,我们可以采用缓存策略优化。
- 添加相关依赖
- 编写配置类
- 配置redis地址等配置信息。
- 添加注解。
三、医院接口模块
上传医院接口
- request传过来的数据是map集合,我们需要获取request的数据然后转成map集合,然后使用工具类将map数据中的值类型转成Object类型方便操作的map数据
- 凡是外部医院想告诉我医院那个医生、科室时间可以看病,都要在医药挂号的医院设置登记,然后给这个医院一个签名,医药挂号有相应的接口可以上传医院的服务,上传之前都要验证签名是否正确,如果不正确,即该医院未在医药挂号系统登记,不受医药挂号系统管制,无法发布医院的服务。
- 医药挂号系统的医院设置采用mysql数据库,而医药挂号系统针对医院的api接口服务采用Mongodb数据库。
- 医院系统是已经存在的系统,为了接入我们的医药挂号系统,需要在医院设置那里登记,并分配一个签名。
- 添加了外接的医院系统。
- 编写接口
controller层
/**
* 医院上传接口
*
* @return
*/
@PostMapping("/saveHospital")
public Result saveHosp(HttpServletRequest request) {
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询医院对象,然后比较密钥是否正确
// 获取请求体中的医院编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据编码查出医院对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)){
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 正确将数据中的图片logo数据使用base64转码,然后继续处理
String logoData = (String)resultMap.get("logoData");
logoData = logoData.replaceAll(" ","+");
resultMap.put("logoData",logoData);
// 4. 将map数据传给service层接口处理,保存或者更新数据
hospitalService.saveOrUpdate(resultMap);
// 5. 响应成功
return Result.ok();
}
service层
// 保存或更新医院上传数据
@Override
public void saveOrUpdate(Map<String, Object> resultMap) {
// 1. 使用JSONObject将map数据转换字符串,然后转成成医院对象
String toJSONString = JSONObject.toJSONString(resultMap);
Hospital hospital = JSONObject.parseObject(toJSONString, Hospital.class);
// 根据医院编码在Mongodb中获取医院对象
Hospital hospitalInMongo = hospitalRepository.findByHoscode(hospital.getHoscode());
// 2. 判断医院对象是否存在,然后设置相应的值
if (hospitalInMongo == null) {
// 医院对象不存在,新增操作
hospital.setStatus(1);
hospital.setIsDeleted(0);
hospital.setCreateTime(new Date());
hospital.setUpdateTime(new Date());
hospitalRepository.save(hospital);
}else {
// 存在,更新医院数据
// 设置id,这样调用save方法,才能更新
hospital.setId(hospitalInMongo.getId());
hospital.setStatus(1);
hospital.setIsDeleted(0);
hospital.setCreateTime(new Date());
hospital.setUpdateTime(new Date());
// hospital中含有id,方法自动更新
hospitalRepository.save(hospital);
}
}
repository层
/**
* 采用mongodb的MongoRepository方式操作数据库
*
*/@Repository
public interface HospitalRepository extends MongoRepository<Hospital,String> {
// Spring Data对Mongodb提供了数据访问的支持。
// 我们可以按照Spring Data 规范定义方法就可以简单实现对数据库的访问
// 根据医院编码查询Mongodb中是否含有该医院对象
Hospital findByHoscode(String hoscode);
}
查询医院接口
- 外接医院系统每次发送请求都会携带医院编码签名等信息,这些是医院系统写好的。
controller层
/**
* 查询医院信息
* @param request
* @return
*/
@PostMapping("/hospital/show")
public Result getHospital(HttpServletRequest request){
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询医院对象,然后比较密钥是否正确
// 获取请求体中的医院编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据编码查出医院对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)){
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 执行查询操作
Hospital hospital = hospitalService.getByHoscode(hoscode);
// 返回结果
return Result.ok(hospital);
}
service层
// 查询医院对象
@Override
public Hospital getByHoscode(String hoscode) {
Hospital hospital = hospitalRepository.findByHoscode(hoscode);
return hospital;
}
上传科室接口
- 难点在查询那里,需要使用MongoRepository分页查询,构造条件
controller层
/**
* 科室上传接口
*
* @return
*/
@PostMapping("/saveDepartment")
public Result saveDepartment(HttpServletRequest request) {
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询科室对象,然后比较密钥是否正确
// 获取请求体中的医院编码、科室编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据医院编码查出科室对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)) {
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 将map数据传给service层接口处理,保存或者更新数据
departmentService.saveOrUpdate(resultMap);
// 5. 响应成功
return Result.ok();
}
service层
// 保存或更新科室信息
@Override
public void saveOrUpdate(Map<String, Object> resultMap) {
// 1. 使用JSONObject将map数据转换字符串,然后转成成医院对象
String toJSONString = JSONObject.toJSONString(resultMap);
Department department = JSONObject.parseObject(toJSONString, Department.class);
// 根据医院编码和科室编码在Mongodb中获取医院对象
Department departmentInMongo = departmentRepository.findDepartmentByHoscodeAndDepcode(department.getHoscode(),department.getDepcode());
// 2. 判断科室对象是否存在,然后设置相应的值
if (departmentInMongo == null) {
// 科室对象不存在,新增操作
department.setIsDeleted(0);
department.setCreateTime(new Date());
department.setUpdateTime(new Date());
departmentRepository.save(department);
}else {
// 存在,更新科室数据
// 设置id,这样调用save方法,才能更新
department.setId(departmentInMongo.getId());
department.setIsDeleted(0);
department.setCreateTime(new Date());
department.setUpdateTime(new Date());
// hospital中含有id,方法自动更新
departmentRepository.save(department);
}
}
repository层
// 科室Repository
@Repository
public interface DepartmentRepository extends MongoRepository<Department,String> {
// 根据医院编码和科室编码查询科室对象
Department findDepartmentByHoscodeAndDepcode(String hoscode, String depcode);
}
查询科室接口
- 采用mongodb的MongoRepository方式操作数据库,条件查询
controller层
/**
* 查询科室信息(分页查询)
* * @param request
* @return
*/
@PostMapping("/department/list")
public Result departmentList(HttpServletRequest request) {
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询科室对象,然后比较密钥是否正确
// 获取请求体中的医院编码、科室编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据医院编码查出科室对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)) {
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 获取科室分页数据
// 查询对象(使用DepartmentQueryVo的分页对象,可以保证数据安全,控制数据的展示)
Page<Department> departmentPage = departmentService.list(resultMap);
return Result.ok(departmentPage);
}
service层
// 查询科室分页信息
@Override
public Page<Department> list(Map<String, Object> resultMap) {
// 1.创建Pageable对象,设置当前页和每页记录数
// 获取第几页和当前页数(做了空值验证,如果为空默认赋值为1)
int page = StringUtils.isEmpty( resultMap.get("page")) ? 1 : Integer.parseInt((String) resultMap.get("page"));
int limit = StringUtils.isEmpty( resultMap.get("limit")) ? 1 : Integer.parseInt((String) resultMap.get("limit"));
Pageable pageable = PageRequest.of(page-1,limit);//0是第一页
// 2. 设置条件
String hoscode = (String) resultMap.get("hoscode");
Department department = new Department();
department.setIsDeleted(0);
department.setHoscode(hoscode);
// 使用匹配器,模糊查询设置条件
ExampleMatcher matcher = ExampleMatcher.matching()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)// 字符串包含
.withIgnoreCase(true);// 忽略大小写
// 创建Example对象,条件查询
Example<Department> example = Example.of(department,matcher);
// 3. 查询
Page<Department> all = departmentRepository.findAll(example, pageable);
return all;
}
删除科室接口
controller层
/**
* 删除科室
* @param request
* @return
*/
@PostMapping("/department/remove")
public Result DepartmentRemove(HttpServletRequest request){
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询科室对象,然后比较密钥是否正确
// 获取请求体中的医院编码、科室编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据医院编码查出科室对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)) {
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 根据医院编码和科室编码删除科室
String depcode = (String) resultMap.get("depcode");
departmentService.remove(hoscode,depcode);
return Result.ok();
}
service层
// 根据医院编码和科室编码删除科室
@Override
public void remove(String hoscode, String depcode) {
// 根据医院编码和科室编码查询科室对象
Department department = departmentRepository.findDepartmentByHoscodeAndDepcode(hoscode, depcode);
// 判断科室对象是否存在
if (department != null) {
// 科室对象存在,根据id删除(如果不存在,则不用删除)
departmentRepository.deleteById(department.getId());
}
}
上传排班接口
controller层
/**
* 上传排班信息
* @param request
* @return
*/
@PostMapping("/saveSchedule")
public Result saveSchedule(HttpServletRequest request){
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询科室对象,然后比较密钥是否正确
// 获取请求体中的医院编码、科室编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据医院编码查出科室对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)) {
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 将map数据传给service层接口处理,保存或者更新数据
scheduleService.saveOrUpdate(resultMap);
// 5. 响应成功
return Result.ok();
}
service层
// 保存或者更新排班
@Override
public void saveOrUpdate(Map<String, Object> resultMap) {
// 1. 使用JSONObject将map数据转换字符串,然后转成成医院对象
String toJSONString = JSONObject.toJSONString(resultMap);
Schedule schedule = JSONObject.parseObject(toJSONString, Schedule.class);
// 根据医院编码和排班编码在Mongodb中获取排班对象
Schedule scheduleInMongo = scheduleRepository.findScheduleByHoscodeAndHosScheduleId(schedule.getHoscode(),schedule.getHosScheduleId());
// 2. 判断科室对象是否存在,然后设置相应的值
if (scheduleInMongo == null) {
// 科室对象不存在,新增操作
schedule.setIsDeleted(0);
schedule.setStatus(1);
schedule.setCreateTime(new Date());
schedule.setUpdateTime(new Date());
scheduleRepository.save(schedule);
}else {
// 存在,更新排班数据
// 设置id,这样调用save方法,才能更新
schedule.setId(scheduleInMongo.getId());
schedule.setIsDeleted(0);
schedule.setStatus(1);
schedule.setCreateTime(new Date());
schedule.setUpdateTime(new Date());
// schedule中含有id,方法自动更新
scheduleRepository.save(schedule);
}
}
repository层
// 排班接口
@Repository
public interface ScheduleRepository extends MongoRepository<Schedule,String> {
// 根据医院编码和排班编码获取排班对象
Schedule findScheduleByHoscodeAndHosScheduleId(String hoscode, String hosScheduleId);
}
查询排班接口
controller层
/**
* 查询排班信息(分页查询)
* @param request
* @return
*/
@PostMapping("/schedule/list")
public Result scheduleList(HttpServletRequest request){
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询科室对象,然后比较密钥是否正确
// 获取请求体中的医院编码、科室编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据医院编码查出科室对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)) {
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 获取排班分页数据
// 查询对象
Page<Schedule> schedulePage = scheduleService.list(resultMap);
return Result.ok(schedulePage);
}
service层
// 获取排班分页数据
@Override
public Page<Schedule> list(Map<String, Object> resultMap) {
// 1.创建Pageable对象,设置当前页和每页记录数
// 获取第几页和当前页数(做了空值验证,如果为空默认赋值为1)
int page = StringUtils.isEmpty( resultMap.get("page")) ? 1 : Integer.parseInt((String) resultMap.get("page"));
int limit = StringUtils.isEmpty( resultMap.get("limit")) ? 1 : Integer.parseInt((String) resultMap.get("limit"));
Pageable pageable = PageRequest.of(page-1,limit);//0是第一页
// 2. 设置条件
Schedule schedule = new Schedule();
schedule.setIsDeleted(0);
schedule.setStatus(1);
// 使用匹配器,模糊查询设置条件
ExampleMatcher matcher = ExampleMatcher.matching()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)// 字符串包含
.withIgnoreCase(true);// 忽略大小写
// 创建Example对象,条件查询
Example<Schedule> example = Example.of(schedule,matcher);
// 3. 查询
Page<Schedule> all = scheduleRepository.findAll(example, pageable);
return all;
}
删除排班接口
controller层
/**
* 删除排班信息
* @param request
* @return
*/
@PostMapping("/schedule/remove")
public Result scheduleRemove(HttpServletRequest request){
// 1. 请求发送过来中,含有一个map数据,获取请求中的map数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2. 使用工具类将map数据中的值类型转成Object类型方便操作的map数据
Map<String, Object> resultMap = HttpRequestHelper.switchMap(parameterMap);
// 3. 根据医院编码查询科室对象,然后比较密钥是否正确
// 获取请求体中的医院编码、科室编码和签名密钥
String hoscode = (String) resultMap.get("hoscode");
String sign = (String) resultMap.get("sign");
// 根据医院编码查出科室对象的签名,并使用md5加密
String signKey = MD5.encrypt(hospitalSetService.getSignKey(hoscode));
if (!sign.equals(signKey)) {
// 不正确,抛出签名异常,
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
// 4. 根据医院编码和排班编码删除科室
String hosScheduleId = (String) resultMap.get("hosScheduleId");
scheduleService.remove(hoscode,hosScheduleId);
return Result.ok();
}
service层
// 根据医院编码和排班编码删除科室
@Override
public void remove(String hoscode, String hosScheduleId) {
// 根据医院编码和排班编码查询排班对象
Schedule schedule = scheduleRepository.findScheduleByHoscodeAndHosScheduleId(hoscode,hosScheduleId);
// 判断科室对象是否存在
if (schedule != null) {
// 科室对象存在,根据id删除(如果不存在,则不用删除)
scheduleRepository.deleteById(schedule.getId());
}
}
四、医院管理模块
医院列表分页查询(难点)
基本业务逻辑
1, 新建医院管理的controller,包含一个分页查询医院数据的接口,传入当前页、每页显示数和一个医院vo对象(用来封装用户的条件查询)。(查询Mongodb)
2. 在service层编写查询逻辑,构造Pageable对象用来封装分页数据,matcher对象封装我们自定义的查询规则,拷贝vo对象到一个新的hospital对象中,example对象封装matcher和hospital,相当于封装所有查询条件,最后构造page对象,传入参数example条件对象和Pageable分页对象。
3. 分页数据是查出来了,但是我们需要给分页数据中的每个医院对象设置医院等级等信息,而这个信息保存在字典数据中。
4. 这个时候就需要远程调用字典模块的接口,根据医院对象的value值和字典编码查询医院等级,这两个接口很简单,基于mp简单查询即可。
5. 然后我们需要一个封装一个远程调用模块,内置一个子模块用来专门处理发起字典数据的远程调用。
6. 最后我们在service层中需要根据医院对象的value值和字典编码查询医院等级,然后封装到医院对象中,返回新的分页对象。
7. 最后在数据字典中写一个根据dictcode查出省id,然后根据省id查询所有省的接口用于省市联动。
五、排班管理模块
科室列表
controller层
@RestController
@RequestMapping("/admin/hosp/department")
@Api(tags = "科室相关接口")
@CrossOrigin
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
// 根据医院编号查询所有科室信息
@GetMapping("/getAllDepartment/{hoscode}")
@ApiOperation(value = "根据医院编号查询所有科室信息")
public Result getAllDepartment( @PathVariable String hoscode){
// 查询结果放在科室vo对象里,内涵下级结点字段
List<DepartmentVo> departmentVoList = departmentService.getAll(hoscode);
return Result.ok(departmentVoList);
}
}
service层
// 根据医院编号查询所有科室信息
@Override
public List<DepartmentVo> getAll(String hoscode) {
// 1. 封装返回的list集合
ArrayList<DepartmentVo> list = new ArrayList<>();
// 2. 查询所有科室信息
// 设置查询条件对象
Department department = new Department();
department.setHoscode(hoscode);
// 封装查询案例
Example<Department> example = Example.of(department);
// 调用repository接口查询
List<Department> departmentList = departmentRepository.findAll(example);
// 3. 将所有科室按照bigname分组
Map<String, List<Department>> map = departmentList
.stream()
.collect(Collectors.groupingBy(Department::getBigname));
// 4. 遍历分组数据的每一个键值对,封装成科室vo对象
for (Map.Entry<String, List<Department>> entry : map.entrySet()) {
// 封装大科室(科室编码,科室名称)
DepartmentVo bigDepartmentVo = new DepartmentVo();
bigDepartmentVo.setDepcode(entry.getValue().get(0).getBigcode());
bigDepartmentVo.setDepname(entry.getKey());
// 遍历大科室下的小科室数据,然后封装成小科室(科室编码,科室名称)
// 把每个科室对象都转换成科室的vo对象,只有科室编码和科室名称两个属性
ArrayList<DepartmentVo> smallList = new ArrayList<>();
for(Department smallDepartment:entry.getValue()){
DepartmentVo departmentVo = new DepartmentVo();
departmentVo.setDepcode(smallDepartment.getDepcode());
departmentVo.setDepname(smallDepartment.getDepname());
// 存入小科室集合
smallList.add(departmentVo);
}
// 将小科室集合封装到大科室中
bigDepartmentVo.setChildren(smallList);
// 将大科室存入集合
list.add(bigDepartmentVo);
}
// 4. 返回科室vo对象的集合
return list;
}
排版日期分页列表
- 根据医院编号和科室编号,查询排班规则数据(分页查询),返回map集合
根据排版日期获取排班详情列表
- 根据医院编号 、科室编号和工作日期,查询排班详细信息
整合网关
- 目前我们已经在网关做了跨域处理,那么service服务就不需要再做跨域处理了,将之前在controller类上添加过@CrossOrigin标签的去掉,防止程序异常,可以使用快捷键ctrl + shift + r快速替换。
- 具体实现看项目模块,其他项目需要可以直接拿来用,修改一下匹配路径即可。
- 新建maven模块
- 导入依赖
- 添加启动类
- 编写配置文件设置请求转发
- 配置类允许跨域访问