废话不多说,直接上图
博客目录结构
工程目录结构
yygh-parent根目录
common公共模块父节点
common-util公共工具类
rabbit-util业务封装RabbitMQ
service-util服务工具类
hospital-manage医院接口模拟端(已开发,直接使用)
model实体类
service接口服务父节点
service-hosp医院api接口服务
service-cmn公共api接口服务
service-user用户api接口服务
service-order订单api接口服务
service-oss文件api接口服务
service-msm短信api接口服务
service-statistics统计api接口服务
service-client服务调用feign父节点
service-cmn-client公共api接口
service-hosp-client医院api接口
service-order-client订单api接口
service-user-client用户api接口
service-gateway服务网关
工程目录结构细分版:https://blog.csdn.net/tufhgddty/article/details/123523530
一、common公共模块
common-util公共的工具类模块
工具类模块,所有模块都可以依赖于它
从课程资料文件夹直接粘贴工具类到project中
总共 5个工具类 包括:
YyghException:自定义全局异常
GlobalExceptionHandler:全局异常处理Handler类
JwtHelper:Json Web Token,认证帮助类
Result:API统一返回结果封装类
ResultCodeEnum:API统一返回结果状态信息枚举类
AuthContextHolder:从header获取当前用户工具类
service-util service服务的工具类模块
service服务的工具类模块,包含service服务的公共配置类,所有service模块依赖于它
工具类 包括:
MybatisPlusConfig:MybatisPlus配置类
CommonMetaObjectHandler:Mybatis plus Handler配置类
RedisConfig:Redis配置类
Swagger2Config:Swagger2配置类
HttpRequestHelper【【【【【【【】】】】】】】
HttpUtil
MD5
其中MybatisPlusConfig.java配置了MybatisPlus的分页插件和乐观锁
package com.atguigu.yygh.common.config;
/**
* MybatisPlus配置类
*/
@EnableTransactionManagement //事务处理
@Configuration
@MapperScan("com.atguigu.yygh.*.mapper")
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// paginationInterceptor.setLimit(你的最大单页限制数量,默认 500 条,小于 0 如 -1 不受限制);
return paginationInterceptor;
}
/**
* 乐观锁配置
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
CommonMetaObjectHandler配置了创建时间和修改时间字段的自动插入
/**
* Mybatis plus Handler配置类
*/
@Component
public class CommonMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
rabbit-util service服务的工具类模块
MqConfig RabbitMQ的配置类
MqConst 【【【【【【【】】】】】】】
RabbitService
二、model实体类模块
从课程资料文件夹直接复制到project中
model实体类
enums
AuthStatusEnum
DictEnum
OrderStatusEnum
PaymentStatusEnum
PaymentTypeEnum
RefundStatusEnum
model
acl
Permission
Role
RolePermission
User
UserRole
base
BaseEntity
BaseMongoEntity
cmn
Dict
cms
Banner
hosp
BookingRule
Department
Hospital
HospitalSet
Schedule
order
OrderInfo
PaymentInfo
RefundInfo
user
Patient
UserInfo
RefundInfo
vo
acl
AssignVo
RoleQueryVo
UserQueryVo
cmn
DictEeVo
hosp
BookingScheduleRuleVo
DepartmentQueryVo
DepartmentVo
HospitalQueryVo
HospitalSetQueryVo
ScheduleOrderVo
ScheduleQueryVo
msm
MsmVo
order
OrderCountQueryVo
OrderCountVo
OrderMqVo
OrderQueryVo
SignInfoVo
user
LoginVo
RegisterVo
UserAuthVo
UserInfoQueryVo
三、 hospital-manage医院接口模拟端(已开发,直接使用)
四、service接口服务父模块
4.1service-hosp医院服务接口模块
配置文件application.properties
【配置文件的参数值记得根据自己的实际情况进行更改】
# 服务端口
server.port=8201
# 服务名
spring.application.name=service-hosp
# 环境设置:dev、test、prod (开发、测试、生产环境)
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.44.163:3306/yygh_hosp?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/yygh/mapper/xml/ *.xml
#nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#开启sentinel
feign.sentinel.enabled=true
#设置sentinel地址
spring.cloud.sentinel.transport.dashboard=http://127.0.0.1:8858
#mongodb地址
spring.data.mongodb.host=192.168.44.163
spring.data.mongodb.port=27017
spring.data.mongodb.database=yygh_hosp
#rabbitmq地址
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
系统管理后台–医院设置信息页面
页面效果:系统管理后台–医院设置信息页面
需求:医院基本信息接口主要是用来保存开通医院的一些基本信息,每个医院一条信息,保存了医院编号(平台分配,全局唯一)和接口调用相关的签名key等信息,是整个流程的第一步,只有开通了医院设置信息,才可以上传医院相关信息。
共需开发三个接口:①基于单表的医院CRUD接口②医院锁定/解锁接口③医院发送签名信息接口
表结构:
hosname:医院名称
hoscode:医院编号(平台分配,全局唯一,api接口必填信息)
api_url:医院回调的基础url(如:预约下单,我们要调用该地址去医院下单)
sign_key:双方api接口调用的签名key,有平台生成
contacts_name:医院联系人姓名
contacts_phone:医院联系人手机
status:状态(锁定/解锁)
①医院基本信息CRUD接口
实际上为Mybatis Plus CRUD接口
Mybatis Plus的CRUD步骤为:
1、 参考配置文件application.properties中的配置
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.44.163:3306/yygh_hosp?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
2、添加启动类:ServiceHospApplication
package com.atguigu.yygh;
@SpringBootApplication
public class ServiceHospApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHospApplication.class, args);
}
}
3、使用model:HospitalSet
@Data
@ApiModel(description = "医院基本信息")
@TableName("hospital_set") 【表名】
public class HospitalSet extends BaseEntity {
private static final long serialVersionUID = 1L; 【自定义序列版本用户标识符,详细看https://blog.csdn.net/tufhgddty/article/details/123063761】
@ApiModelProperty(value = "医院名称")
@TableField("hosname") 【字段名】
private String hosname;
@ApiModelProperty(value = "医院编号")
@TableField("hoscode")
private String hoscode;
@ApiModelProperty(value = "api基础路径")
@TableField("api_url")
private String apiUrl;
@ApiModelProperty(value = "签名秘钥")
@TableField("sign_key")
private String signKey;
@ApiModelProperty(value = "联系人姓名")
@TableField("contacts_name")
private String contactsName;
@ApiModelProperty(value = "联系人手机")
@TableField("contacts_phone")
private String contactsPhone;
@ApiModelProperty(value = "状态")
@TableField("status")
private Integer status;
}
BaseEntity:在此entity存储一些通用的字段,节省子类代码量
@Data
public class BaseEntity implements Serializable {
@ApiModelProperty(value = "id")
@TableId(type = IdType.AUTO) 【@TableId:主键 type=IdType.AUTO:主键策略为自增】
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 【@JsonFormat:Json格式 pattern:模板】
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false) 【这个注解的意思是,虽然实体类有这个属性但是数据库表中没有这个字段,也就是实体类有这个属性,但是没有用来放在数据库中存储】
private Map<String,Object> param = new HashMap<>();
}
4、添加mapper:
@Mapper
public interface HospitalSetMapper extends BaseMapper<HospitalSet>【Mybatis Plus的Mapper需要继承BaseMapper< XXX>,接口的泛型为要操作的实体类】 {
}
5、在mapper/xml下添加HospitalSetMapper.xml------->【说明一下:这里集成了Mybatis-Plus作为ORM框架,一般情况下是否创建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.atguigu.yygh.hosp.mapper.HospitalSetMapper">
</mapper>
6、添加service接口及实现类
接口 HospitalSetService
public interface HospitalSetService extends IService<HospitalSetService> 【Mybatis Plus的Service需要继承IService< XXX>接口,接口的泛型为要操作的实体类】{
}
实现类 HospitalSetServiceImpl
@Service
public class HospitalSetServiceImpl extends ServiceImpl<HospitalSetMapper, HospitalSet> implements HospitalSetService【Mybatis Plus的ServiceImpl需要继承ServiceImpl< XXX,XXX>类,接口的泛型为对应的Mapper和实体类】 {
@Autowired
private HospitalSetMapper hospitalSetMapper;
}
7、添加controller------->【说明一下:原项目中老师这样写是不符合规范的,业务逻辑代码应该尽量都放在Service层,不要放在Controller层,最好尽可能的把代码放在Service层,然后在Controller层调用Service层的方法实现功能】
Controller com.atguigu.yygh.hosp.controller.HospitalSetController
@Api(tags = "医院基本信息管理") 【Swagger2的注解】
@RestController 【相当于@Controller + @ResponseBody】
@RequestMapping("/admin/hosp/hospitalSet") 【用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径】
public class HospitalSetController {
//注入service
@Autowired
private HospitalSetService hospitalSetService;
//1 查询医院基本信息表所有信息
@ApiOperation(value = "获取所有医院基本信息")
@GetMapping("findAll")
public Result findAllHospitalSet() {
//调用service的方法
List<HospitalSet> list = hospitalSetService.list();
return Result.ok(list); 【自定义结果返回类型】
}
//2 逻辑删除医院设置
@ApiOperation(value = "逻辑删除医院基本信息")
@DeleteMapping("{id}") 【/admin/hosp/hospitalSet/XXX XXX为id参数的值】
public Result removeHospSet(@PathVariable Long id) 【@PathVariable 路径变量,加此注解可以获取到路径变量的值作为入参】 {
boolean flag = hospitalSetService.removeById(id);
if(flag) {
return Result.ok();
} else {
return Result.fail();
}
}
//3 条件查询带分页
@PostMapping("findPageHospSet/{current}/{limit}")
public Result findPageHospSet(@PathVariable long current,
@PathVariable long limit,
@RequestBody
(required = false) HospitalSetQueryVo hospitalSetQueryVo)【@RequestBody是PostMapping用来传递Json格式参数的注解,required=false表示此参数可为空】 {
//创建page对象,传递当前页,每页记录数
Page<HospitalSet> page = new Page<>(current,limit);
//构建条件
【Wrapper条件构造器,详细请看这里https://blog.csdn.net/tufhgddty/article/details/123332559?spm=1001.2014.3001.5501】
QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>();
String hosname = hospitalSetQueryVo.getHosname();//医院名称
String hoscode = hospitalSetQueryVo.getHoscode();//医院编号
if(!StringUtils.isEmpty(hosname)) {
wrapper.like("hosname",hospitalSetQueryVo.getHosname());
}
if(!StringUtils.isEmpty(hoscode)) {
wrapper.eq("hoscode",hospitalSetQueryVo.getHoscode());
}
//调用方法实现分页查询
【分页查询,详细请看这里https://blog.csdn.net/tufhgddty/article/details/123332559?spm=1001.2014.3001.5501】
Page<HospitalSet> pageHospitalSet = hospitalSetService.page(page, wrapper);
//返回结果
return Result.ok(pageHospitalSet);
}
//4 添加医院基本信息
@PostMapping("saveHospitalSet")
public Result saveHospitalSet(@RequestBody HospitalSet hospitalSet) {
//设置状态 1 使用 0 不能使用
hospitalSet.setStatus(1);
//签名秘钥
Random random = new Random();
hospitalSet.setSignKey(MD5.encrypt(System.currentTimeMillis()+""+random.nextInt(1000)));【System.currentTimeMillis() 获取时间戳 MD5.encrypt() MD5工具类的加密方法,和MD4一样生成128位的哈希值,三大成熟哈希算法包括:MD SHA SM3】
//调用service
boolean save = hospitalSetService.save(hospitalSet);
if(save) {
return Result.ok();
} else {
return Result.fail(); 【Result.ok()和Result.fail()是自定义结果返回工具类里针对不同返回结果定义的结果返回方法】
}
}
//5 根据id获取医院基本信息
@GetMapping("getHospSet/{id}")
public Result getHospSet(@PathVariable Long id) {
HospitalSet hospitalSet = hospitalSetService.getById(id);
return Result.ok(hospitalSet);
}
//6 修改医院基本信息
@PostMapping("updateHospitalSet")
public Result updateHospitalSet(@RequestBody HospitalSet hospitalSet) {
boolean flag = hospitalSetService.updateById(hospitalSet);【修改成功与否的布尔类型返回值】
if(flag) {
return Result.ok();
} else {
return Result.fail();
}
}
//7 批量删除医院基本信息
@DeleteMapping("batchRemove")
public Result batchRemoveHospitalSet(@RequestBody List<Long> idList) 【@RequestBody也是可以传列表的】{
hospitalSetService.removeByIds(idList);
return Result.ok();
}
}
②医院基本信息锁定与解锁接口
//8 医院基本信息锁定和解锁
@PutMapping("lockHospitalSet/{id}/{status}")
public Result lockHospitalSet(@PathVariable Long id,
@PathVariable Integer status) {
//根据id查询医院基本信息
HospitalSet hospitalSet = hospitalSetService.getById(id);
//设置状态
hospitalSet.setStatus(status);
//调用方法
hospitalSetService.updateById(hospitalSet);
return Result.ok();
}
③医院发送签名Key接口
/9 发送签名秘钥
@PutMapping("sendKey/{id}")
public Result lockHospitalSet(@PathVariable Long id) {
HospitalSet hospitalSet = hospitalSetService.getById(id);
String signKey = hospitalSet.getSignKey();
String hoscode = hospitalSet.getHoscode();
//TODO 发送短信,后续集成阿里云短信服务实现
return Result.ok();
}
*Swagger2模块集成
*自定义全局异常处理
*LogBack模块集成
这三个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
4.2service-cmn数据字典服务接口模块
数据字典介绍
何为数据字典?数据字典就是管理系统常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理,方便管理系统数据,一般系统基本都会做数据管理。
系统管理后台–数据字典页面
共需开发一个接口:①数据字典服务的CRUD接口
页面效果:系统管理后台–数据字典页面
表设计
数据分析
parent_id:
上级id,通过id与parent_id构建上下级关系,例如:我们要获取所有行业数据,那么只需要查询parent_id=20000的数据
name:名称,例如:填写用户信息,我们要select标签选择民族,“汉族”就是数据字典的名称
value:值,例如:填写用户信息,我们要select标签选择民族,“1”(汉族的标识)就是数据字典的值
dict_code:编码,编码是我们自定义的,全局唯一,例如:我们要获取行业数据,我们可以通过parent_id获取,但是parent_id是不确定的,所以我们可以根据编码来获取行业数据
service-cmn模块搭建
配置文件application.properties
# 服务端口
server.port=8202
# 服务名
spring.application.name=service-cmn
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.44.165:3306/yygh_cmn?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root123
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
①数据字典服务接口
实际上为Mybatis Plus CRUD接口【前面已经包含的知识点不再展示】
1、启动类
2、实体类 model模块中的:com.atguigu.yygh.model.cmn.Dict
实体类其中有一个字段:
@ApiModelProperty(value = "是否包含子节点")
@TableField(exist = false)
private boolean hasChildren; 【hasChildren为前端elementUI树形组件所需字段】
根据element组件要求,返回列表数据必须包含hasChildren字段
3、添加数据字典mapper:com.atguigu.yygh.cmn.mapper.DictMapper
4、添加数据字典Service接口及其实现类
接口
public interface DictService extends IService<Dict> {
//根据数据id查询子数据列表
List<Dict> findChlidData(Long id);
}
实现类 【【【【【】】】】】
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {
//根据数据id查询子数据列表
@Override
public List<Dict> findChlidData(Long id) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
List<Dict> dictList = baseMapper.selectList(wrapper);
//向list集合每个dict对象中设置hasChildren
for (Dict dict:dictList) {
Long dictId = dict.getId();
boolean isChild = this.isChildren(dictId);
dict.setHasChildren(isChild);
}
return dictList;
}
//判断id下面是否有子节点
private boolean isChildren(Long id) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
Integer count = baseMapper.selectCount(wrapper);
// 0>0 1>0
return count>0;
}
}
5、添加数据字典controller:com.atguigu.yygh.cmn.controller.DictController
*EasyExcel模块集成
这个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
*MongoDB模块集成
MongoDB的CRUD操作请看另一篇博客
https://blog.csdn.net/tufhgddty/article/details/123540812
4.1service-hosp医院服务接口模块
医院管理后台–医院设置页面
共需开发两个接口①上传医院接口②查看医院信息接口
①上传医院接口
实际上为MongoDB CRUD接口
页面效果:医院设置页面–添加医院页面
MongoDB的CRUD分为MongoTemplate和MongoRepository两种,详细请看https://blog.csdn.net/tufhgddty/article/details/123540812
接口数据分析
{
"hoscode": "1000_0",
"hosname": "北京协和医院",
"hostype": "1",
"provinceCode": "110000",
"cityCode": "110100",
"districtCode": "110102",
"address": "大望路",
"intro": "北京协和医院是集医疗、教学、科研于一体的大型三级甲等综合医院,是国家卫生计生委...目标而继续努力。",
"route": "东院区乘车路线:106、...更多乘车路线详见须知。",
"logoData": "iVBORw0KGgoAAAA...NSUhEUg==",
"bookingRule": {
"cycle": "1",
"releaseTime": "08:30",
"stopTime": "11:30",
"quitDay": "-1",
"quitTime": "15:30",
"rule": [
"西院区预约号取号地点:西院区门诊楼一层大厅挂号窗口取号",
"东院区预约号取号地点:东院区老门诊楼一层大厅挂号窗口或新门诊楼各楼层挂号/收费窗口取号"
]
}
}
1,医院编号是平台分配的,全局唯一,上传医院接口可以多次调用,如果存在相同编号的为更新操作
2,数据分为医院基本信息与预约规则信息
3,医院logo转换为base64字符串
4,预约规则信息属于医院基本信息的一个属性
5,预约规则rule,以数组形式传递
6,数据传递过来我们还要验证签名,只允许平台开通的医院可以上传数据,保证数据安全性
MongoDB的CRUD步骤为:
1、 参考配置文件application.properties中的配置
#mongodb地址
spring.data.mongodb.uri=mongodb://IP27017/yygh_hosp
或第二种配置方式
spring.data.mongodb.host=IP
spring.data.mongodb.port=27017
spring.data.mongodb.database=yygh_hosp
2、添加启动类:ServiceHospApplication 【前面位于同一个service-hosp模块的医院设置CRUD接口已添加过】
3、使用model:com.atguigu.yygh.model.hosp.Hospital【【【【【】】】】】
【省略了此实体类中一些重复性或此博客前面学习过的代码】
@Document("Hospital") 【@Document文档注解,注解里的"Hospital"为对应的数据库集合名Hospital,如果不填,默认为实体类的小写:hospital】
public class Hospital extends BaseMongoEntity {
@ApiModelProperty(value = "医院编号")
@Indexed(unique = true) //唯一索引
private String hoscode;
@ApiModelProperty(value = "医院名称")
@Indexed //普通索引
private String hosname;
@ApiModelProperty(value = "医院类型")
private String hostype;
@ApiModelProperty(value = "状态 0:未上线 1:已上线")
private Integer status;
//预约规则
@ApiModelProperty(value = "预约规则")
private BookingRule bookingRule;
public void setBookingRule(String bookingRule) {
this.bookingRule = JSONObject.parseObject(bookingRule, BookingRule.class);
}
}
BaseMongoEntity:自定义的序列化基础实体类,和之前自定义的BaseEntity一样:在此entity存储一些通用的字段,节省子类代码量
4、添加Repository:
@Repository 【MongoDB的Repository注解,标志DAO层】
public interface HospitalRepository extends MongoRepository<Hospital,String>【MongoDB的Repository需要继承MongoRepository<XXX,XXX>第一个参数为操作的实体类,第二个参数为实体类Id字段的数据类型】
{
/**
* 上传医院信息
* @param hoscode
*/
Hospital getHospitalByHoscode(String hoscode);
}
5、添加service接口及实现类
接口 HospitalService
public interface HospitalService {
/**
* 上传医院信息
* @param paramMap
*/
void save(Map<String, Object> paramMap); 【【参数使用Map,减少对象封装,有利于签名校验,后续会体验到】】
}
实现类 HospitalServiceImpl
@Service
public class HospitalServiceImpl implements HospitalService {
@Autowired
private HospitalRepository hospitalRepository;
--------------------------------------------------------------------------------
/**
* 上传医院信息
* @param paramMap
*/
@Override
public void save(Map<String, Object> paramMap) {
log.info(JSONObject.toJSONString(paramMap));
Hospital hospital = JSONObject.parseObject(JSONObject.toJSONString(paramMap),Hospital.class);
//判断是否存在
Hospital targetHospital = hospitalRepository.getHospitalByHoscode(hospital.getHoscode());
if(null != targetHospital) {
hospital.setStatus(targetHospital.getStatus());
hospital.setCreateTime(targetHospital.getCreateTime());
hospital.setUpdateTime(new Date());
hospital.setIsDeleted(0);
hospitalRepository.save(hospital);
} else {
//0:未上线 1:已上线
hospital.setStatus(0);
hospital.setCreateTime(new Date());
hospital.setUpdateTime(new Date());
hospital.setIsDeleted(0);
hospitalRepository.save(hospital);
}
}
---
说明:
Hospital hospital = JSONObject.parseObject(JSONObject.toJSONString(paramMap),Hospital.class);
Map转换为Hospital对象时,预约规则bookingRule为一个对象属性,rule为一个数组属性,因此在转换时我们要重新对应的set方法,不然转换不会成功
---
public class Hospital extends BaseMongoEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "医院编号")
private String hoscode;
...
//预约规则
@ApiModelProperty(value = "预约规则")
private BookingRule bookingRule;
public void setBookingRule(String bookingRule) {
this.bookingRule = JSONObject.parseObject(bookingRule, BookingRule.class);
}
}
---
public class BookingRule {
@ApiModelProperty(value = "预约周期")
private Integer cycle;
...
@ApiModelProperty(value = "预约规则")
private List<String>rule;
/**
*
* @param rule
*/
public void setRule(String rule) {
if(!StringUtils.isEmpty(rule)) {
this.rule = JSONArray.parseArray(rule, String.class);
}
}
}
-------------------------------------------------------------------------------
}
6、在controller添加上传医院方法saveHospital():com.atguigu.yygh.hosp.api.ApiController
@Api(tags = "医院管理API接口")
@RestController
@RequestMapping("/api/hosp")
public class ApiController {
@Autowired
private HospitalService hospitalService;
@ApiOperation(value = "上传医院") 【上传医院接口】
@PostMapping("saveHospital")
public Result saveHospital(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
hospitalService.save(paramMap);
return Result.ok();
}
}
7、添加帮助类【【【【【】】】】】:
在service-util模块添加HttpRequestHelper帮助类:com.atguigu.yygh.common.helper.HttpRequestHelper
@Slf4j
public class HttpRequestHelper {
-------------------------------------------------------------------------------
/**
*
* @param paramMap
* @return
*/
public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
resultMap.put(param.getKey(), param.getValue()[0]);
}
return resultMap;
}
}
8、整合参数签名
8.1在HttpRequestHelper帮助类添加封装签名的方法
封装签名方法:
public static void main(String[] args) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("d", "4");
paramMap.put("b", "2");
paramMap.put("c", "3");
paramMap.put("a", "1");
paramMap.put("timestamp", getTimestamp());
log.info(getSign(paramMap, "111111111"));
}
/**
* 请求数据获取签名
* @param paramMap
* @param signKey
* @return
*/
public static String getSign(Map<String, Object> paramMap, String signKey) {
if(paramMap.containsKey("sign")) {
paramMap.remove("sign");
}
TreeMap<String, Object> sorted = new TreeMap<>(paramMap);
StringBuilder str = new StringBuilder();
for (Map.Entry<String, Object> param : sorted.entrySet()) {
str.append(param.getValue()).append("|");
}
str.append(signKey);
log.info("加密前:"+ str.toString());
String md5Str = MD5.encrypt(str.toString());
log.info("加密后:"+ md5Str);
return md5Str;
}
/**
* 签名校验
* @param paramMap
* @param signKey
* @return
*/
public static boolean isSignEquals(Map<String, Object> paramMap, String signKey) {
String sign = (String)paramMap.get("sign");
String md5Str = getSign(paramMap, signKey);
if(!sign.equals(md5Str)) {
return false;
}
return true;
}
/**
* 获取时间戳
* @return
*/
public static long getTimestamp() {
return new Date().getTime();
}
8.2给上传医院接口整合签名校验功能
我们在医院设置的时候,为每个医院生成了医院编码与签名key,因此我在验证签名时要根据医院编码去动态获取签名key,然后再做签名校验
动态获取签名key接口
接口:在HospitalSetService接口添加方法
/**
* 获取签名key
* @param hoscode
* @return
*/
String getSignKey(String hoscode);
实现类:在HospitalSetServiceImpl实现类添加方法
@Override
public String getSignKey(String hoscode) {
HospitalSet hospitalSet = this.getByHoscode(hoscode);
if(null == hospitalSet) {
throw new YyghException(ResultCodeEnum.HOSPITAL_OPEN);
}
if(hospitalSet.getStatus().intValue() == 0) {
throw new YyghException(ResultCodeEnum.HOSPITAL_LOCK);
}
return hospitalSet.getSignKey();
}
/**
* 根据hoscode获取医院设置
* @param hoscode
* @return
*/
private HospitalSet getByHoscode(String hoscode) {
return hospitalSetMapper.selectOne(new QueryWrapper<HospitalSet>().eq("hoscode", hoscode));
}
8.3修改Controller里的上传医院方法:ApiController类的saveHospital()方法
//上传医院接口
@PostMapping("/saveHospital")
public Result saveHosp(HttpServletRequest request){
//获取传递过来 的医院信息
Map<String, String[]> requestMap = request.getParameterMap();
Map<String, Object> paramMap = HttpRequestHelper.switchMap(requestMap);
//获取传递过来的签名密钥(签名已被MD5加密)
String hospSign = (String)paramMap.get("sign");
//根据传过来的医院信息中的医院编码,去平台对应的数据库yygh_hosp中查询此医院编码对应的签名密钥并MD5加密
String hoscode =(String)paramMap.get("hoscode");
String signKey=hospitalSetService.getSignKey(hoscode);
String signKeyMd5 = MD5.encrypt(signKey);
//判断
if(!hospSign.equals(signKeyMd5)){
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
//"+" ""转换
String logoData = (String) paramMap.get("logoData");
logoData = logoData.replaceAll(" ", "+");
paramMap.put("logoData",logoData);
hospitalService.save(paramMap);
return Result.ok();
}
②查看医院信息接口
实际上为MongoDB CRUD接口
页面效果:医院设置页面–查看医院信息页面
添加service接口、实现类、Controller
接口:HospitalService
/**
* 查询医院
* @param hoscode
* @return
*/
Hospital getByHoscode(String hoscode);
实现类:HospitalServiceImpl
@Override
public Hospital getByHoscode(String hoscode) {
return hospitalRepository.getHospitalByHoscode(hoscode);
}
Controller:api包的ApiController
//查询医院
@PostMapping("hospital/show")
public Result getHospital(HttpServletRequest request){
//上传医院接口
Map<String, String[]> requestMap = request.getParameterMap();
Map<String, Object> paramMap = HttpRequestHelper.switchMap(requestMap);
String hoscode = (String) paramMap.get("hoscode");
//获取传递过来的签名密钥(签名已被MD5加密)
String hospSign = (String)paramMap.get("sign");
//根据传过来的医院信息中的医院编码,去平台对应的数据库yygh_hosp中查询此医院编码对应的签名密钥并MD5加密
String signKey=hospitalSetService.getSignKey(hoscode);
String signKeyMd5 = MD5.encrypt(signKey);
//判断
if(!hospSign.equals(signKeyMd5)){
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
//查询
Hospital hospital=hospitalService.getByHoscode(hoscode);
return Result.ok(hospital);
}
医院管理后台–科室设置页面
共需开发三个接口:①上传科室接口②查看科室信息接口③删除科室接口
①上传科室接口
实际上为MongoDB CRUD接口
页面效果:科室设置页面–添加科室页面
医院编号是平台分配的,全局唯一,科室编号为医院自己的编号,相对医院唯一,上传科室接口可以多次调用,如果医院编号与科室编号组合唯一为更新操作
接口数据分析
{
"hoscode": "1000_0",
"depcode": "200050923",
"depname": "门诊部核酸检测门诊(东院)",
"intro": "门诊部核酸检测门诊(东院)",
"bigcode": "44f162029abb45f9ff0a5f743da0650d",
"bigname": "体检科"
}
说明:一个大科室下可以有多个小科室,如图:
![image-20220326084548039](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220326084548039.png
MongoDB的CRUD步骤为:
1、model:com.atguigu.yygh.model.hosp.Department
2、Repository:com.atguigu.yygh.hosp.repository.DepartmentRepository
在DepartmentRepository添加方法:
Department getDepartmentByHoscodeAndDepcode(String hoscode, String depcode);
3、Service接口以及实现类
接口
public interface DepartmentService {
/**
* 上传科室信息
* @param paramMap
*/
void save(Map<String, Object> paramMap);
}
实现类
@Service
@Slf4j
public class DepartmentServiceImpl implements DepartmentService {
@Autowired
private DepartmentRepository departmentRepository;
@Override
public void save(Map<String, Object> paramMap) {
Department department = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Department.class);
Department targetDepartment = departmentRepository.getDepartmentByHoscodeAndDepcode(department.getHoscode(), department.getDepcode());
if(null != targetDepartment) {
//copy不为null的值,该方法为自定义方法
BeanUtils.copyBean(department, targetDepartment, Department.class);
departmentRepository.save(targetDepartment);
} else {
department.setCreateTime(new Date());
department.setUpdateTime(new Date());
department.setIsDeleted(0);
departmentRepository.save(department);
}
}
}
4、controller:在ApiController添加上传科室方法
@Autowired
private DepartmentService departmentService;
@ApiOperation(value = "上传科室")
@PostMapping("saveDepartment")
public Result saveDepartment(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
//必须参数校验 略
String hoscode = (String)paramMap.get("hoscode");
if(StringUtils.isEmpty(hoscode)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//签名校验
if(!HttpRequestHelper.isSignEquals(paramMap, hospitalSetService.getSignKey(hoscode))) {
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
departmentService.save(paramMap);
return Result.ok();
}
②查看科室信息接口
实际上为MongoDB CRUD接口
页面效果:科室设置页面–查看科室信息页面
3、Service接口以及实现类:在DepartmentService接口以及实现类添加以下方法
接口 com.atguigu.yygh.hosp.service.DepartmentService
/**
* 分页查询
* @param page 当前页码
* @param limit 每页记录数
* @param departmentQueryVo 查询条件
* @return
*/
Page<Department> selectPage(Integer page, Integer limit, DepartmentQueryVo departmentQueryVo);
实现类 com.atguigu.yygh.hosp.service.impl.DepartmentServiceImpl
@Override
public Page<Department> selectPage(Integer page, Integer limit, DepartmentQueryVo departmentQueryVo) {
Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
//0为第一页
Pageable pageable = PageRequest.of(page-1, limit, sort);
Department department = new Department();
BeanUtils.copyProperties(departmentQueryVo, department);
department.setIsDeleted(0);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
//创建实例
Example<Department> example = Example.of(department, matcher);
Page<Department> pages = departmentRepository.findAll(example, pageable);
return pages;
}
4、controller:在ApiController类添加查询科室接口方法
controller:com.atguigu.yygh.hosp.api.ApiController
@ApiOperation(value = "获取分页列表")
@PostMapping("department/list")
public Result department(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
//必须参数校验 略
String hoscode = (String)paramMap.get("hoscode");
//非必填
String depcode = (String)paramMap.get("depcode");
int page = StringUtils.isEmpty(paramMap.get("page")) ? 1 : Integer.parseInt((String)paramMap.get("page"));
int limit = StringUtils.isEmpty(paramMap.get("limit")) ? 10 : Integer.parseInt((String)paramMap.get("limit"));
if(StringUtils.isEmpty(hoscode)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//签名校验
if(!HttpRequestHelper.isSignEquals(paramMap, hospitalSetService.getSignKey(hoscode))) {
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
DepartmentQueryVo departmentQueryVo = new DepartmentQueryVo();
departmentQueryVo.setHoscode(hoscode);
departmentQueryVo.setDepcode(depcode);
Page<Department> pageModel = departmentService.selectPage(page, limit, departmentQueryVo);
return Result.ok(pageModel);
}
③删除科室接口
实际上为MongoDB CRUD接口
页面效果:科室设置页面–科室删除页面
3、Service接口以及实现类:在DepartmentService接口以及实现类添加以下方法
接口 com.atguigu.yygh.hosp.service.DepartmentService
/**
* 删除科室
* @param hoscode
* @param depcode
*/
void remove(String hoscode, String depcode);
实现类 com.atguigu.yygh.hosp.service.impl.DepartmentServiceImpl
@Override
public void remove(String hoscode, String depcode) {
Department department = departmentRepository.getDepartmentByHoscodeAndDepcode(hoscode, depcode);
if(null != department) {
//departmentRepository.delete(department);
departmentRepository.deleteById(department.getId());
}
}
4、controller:在ApiController类添加查询科室接口方法
controller:com.atguigu.yygh.hosp.api.ApiController
@ApiOperation(value = "删除科室")
@PostMapping("department/remove")
public Result removeDepartment(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
//必须参数校验 略
String hoscode = (String)paramMap.get("hoscode");
//必填
String depcode = (String)paramMap.get("depcode");
if(StringUtils.isEmpty(hoscode)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//签名校验
if(!HttpRequestHelper.isSignEquals(paramMap, hospitalSetService.getSignKey(hoscode))) {
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
departmentService.remove(hoscode, depcode);
return Result.ok();
}
医院管理后台–排班设置页面
共需开发三个接口:①上传排班接口②查看排班信息接口③删除排班接口
①上传排班接口
实际上为MongoDB CRUD接口
页面效果:排班设置页面–上传排班页面
医院编号是平台分配的,全局唯一,排班编号为医院自己的编号,相对医院唯一,上传排班接口可以多次调用,如果医院编号与排班编号组合唯一为更新操作
接口数据分析
{
"hoscode": "1000_0",
"depcode": "200040878",
"title": "医师",
"docname": "",
"skill": "内分泌科常见病。",
"workDate": "2020-06-22",
"workTime": 0,
"reservedNumber": 33,
"availableNumber": 22,
"amount": "100",
"status": 1,
"hosScheduleId": "1"
}
MongoDB的CRUD步骤为:
1、model:com.atguigu.yygh.model.hosp.Schedule
2、Repository:com.atguigu.yygh.hosp.repository.ScheduleRepository
在ScheduleRepository添加方法:
Schedule getScheduleByHoscodeAndHosScheduleId(String hoscode, String hosScheduleId);
3、Service接口以及实现类
接口
public interface ScheduleService {
/**
* 上传排班信息
* @param paramMap
*/
void save(Map<String, Object> paramMap); //参数使用Map,减少对象封装,有利于签名校验,后续会体验到
}
实现类
@Service
@Slf4j
public class ScheduleServiceImpl implements ScheduleService {
@Autowired
private ScheduleRepository scheduleRepository;
@Override
public void save(Map<String, Object> paramMap) {
Schedule schedule = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Schedule.class);
Schedule targetSchedule = scheduleRepository.getScheduleByHoscodeAndHosScheduleId(schedule.getHoscode(), schedule.getHosScheduleId());
if(null != targetSchedule) {
//值copy不为null的值,该方法为自定义方法
BeanUtils.copyBean(schedule, targetSchedule, Schedule.class);
scheduleRepository.save(targetSchedule);
} else {
schedule.setCreateTime(new Date());
schedule.setUpdateTime(new Date());
schedule.setIsDeleted(0);
scheduleRepository.save(schedule);
}
}
}
4、controller:在ApiController添加上传科室方法
@Autowired
private ScheduleService scheduleService;
@ApiOperation(value = "上传排班")
@PostMapping("saveSchedule")
public Result saveSchedule(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
//必须参数校验 略
String hoscode = (String)paramMap.get("hoscode");
if(StringUtils.isEmpty(hoscode)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//签名校验
if(!HttpRequestHelper.isSignEquals(paramMap, hospitalSetService.getSignKey(hoscode))) {
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
scheduleService.save(paramMap);
return Result.ok();
}
②查看排班信息接口
实际上为MongoDB CRUD接口
页面效果:排班设置页面–查看排班信息页面
一个科室有多个科室,因此我们采取分页查询方式查询排班
3、Service接口以及实现类:在ScheduleService接口以及实现类添加以下方法
接口 com.atguigu.yygh.hosp.service.ScheduleService
/**
* 分页查询
* @param page 当前页码
* @param limit 每页记录数
* @param scheduleQueryVo 查询条件
* @return
*/
Page<Schedule> selectPage(Integer page, Integer limit, ScheduleQueryVo scheduleQueryVo);
实现类 com.atguigu.yygh.hosp.service.impl.ScheduleServiceImpl
@Override
public Page<Schedule> selectPage(Integer page, Integer limit, ScheduleQueryVo scheduleQueryVo) {
Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
//0为第一页
Pageable pageable = PageRequest.of(page-1, limit, sort);
Schedule schedule = new Schedule();
BeanUtils.copyProperties(scheduleQueryVo, schedule);
schedule.setIsDeleted(0);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
//创建实例
Example<Schedule> example = Example.of(schedule, matcher);
Page<Schedule> pages = scheduleRepository.findAll(example, pageable);
return pages;
}
4、controller:在ApiController类添加查询排班接口方法
controller:com.atguigu.yygh.hosp.api.ApiController
@ApiOperation(value = "获取排班分页列表")
@PostMapping("schedule/list")
public Result schedule(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
//必须参数校验 略
String hoscode = (String)paramMap.get("hoscode");
//非必填
String depcode = (String)paramMap.get("depcode");
int page = StringUtils.isEmpty(paramMap.get("page")) ? 1 : Integer.parseInt((String)paramMap.get("page"));
int limit = StringUtils.isEmpty(paramMap.get("limit")) ? 10 : Integer.parseInt((String)paramMap.get("limit"));
if(StringUtils.isEmpty(hoscode)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//签名校验
if(!HttpRequestHelper.isSignEquals(paramMap, hospitalSetService.getSignKey(hoscode))) {
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
ScheduleQueryVo scheduleQueryVo = new ScheduleQueryVo();
scheduleQueryVo.setHoscode(hoscode);
scheduleQueryVo.setDepcode(depcode);
Page<Schedule> pageModel = scheduleService.selectPage(page , limit, scheduleQueryVo);
return Result.ok(pageModel);
}
③删除排班接口
实际上为MongoDB CRUD接口
页面效果:排班设置页面–删除排班页面
根据医院编号与排班编号删除科室
3、Service接口以及实现类:在ScheduleService接口以及实现类添加以下方法
接口 com.atguigu.yygh.hosp.service.ScheduleService
/**
* 删除科室
* @param hoscode
* @param hosScheduleId
*/
void remove(String hoscode, String hosScheduleId);
实现类 com.atguigu.yygh.hosp.service.impl.ScheduleServiceImpl
@Override
public void remove(String hoscode, String hosScheduleId) {
Schedule schedule = scheduleRepository.getScheduleByHoscodeAndHosScheduleId(hoscode, hosScheduleId);
if(null != schedule) {
scheduleRepository.deleteById(schedule.getId());
}
}
4、controller:在ApiController类添加查询排班接口方法
controller:com.atguigu.yygh.hosp.api.ApiController
@ApiOperation(value = "删除科室")
@PostMapping("schedule/remove")
public Result removeSchedule(HttpServletRequest request) {
Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
//必须参数校验 略
String hoscode = (String)paramMap.get("hoscode");
//必填
String hosScheduleId = (String)paramMap.get("hosScheduleId");
if(StringUtils.isEmpty(hoscode)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//签名校验
if(!HttpRequestHelper.isSignEquals(paramMap, hospitalSetService.getSignKey(hoscode))) {
throw new YyghException(ResultCodeEnum.SIGN_ERROR);
}
scheduleService.remove(hoscode, hosScheduleId);
return Result.ok();
}
*图片Bash64编码工具类模块集成
*SpringCloud Nacos服务注册配置管理中心模块集成
这两个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
系统管理后台–医院信息列表页面
①医院列表接口
添加接口和实现类
接口
/**
* 分页查询
* @param page 当前页码
* @param limit 每页记录数
* @param hospitalQueryVo 查询条件
* @return
*/
Page<Hospital> selectPage(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo);
实现类
@Override
public Page<Hospital> selectPage(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo) {
Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
//0为第一页
Pageable pageable = PageRequest.of(page-1, limit, sort);
Hospital hospital = new Hospital();
BeanUtils.copyProperties(hospitalQueryVo, hospital);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
//创建实例
Example<Hospital> example = Example.of(hospital, matcher);
Page<Hospital> pages = hospitalRepository.findAll(example, pageable);
return pages;
}
创建新的HospitalController :com.atguigu.yygh.hosp.controller.HospitalController
@Api(tags = "医院管理接口")
@RestController
@RequestMapping("/admin/hosp/hospital")
public class HospitalController {
@Autowired
private HospitalService hospitalService;
@ApiOperation(value = "获取分页列表")
@GetMapping("{page}/{limit}")
public Result index(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Integer page,
@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Integer limit,
@ApiParam(name = "hospitalQueryVo", value = "查询对象", required = false)
HospitalQueryVo hospitalQueryVo) {
return Result.ok(hospitalService.selectPage(page, limit, hospitalQueryVo));
}
}
service-cmn需要提供的接口:
由于之前开发的医院详情信息接口返回的医院等级、省市区地址得值都是数据字典的value值而不是数据字典的名称,因此我们在医院管理页面列表显示医院等级与医院地址时要根据数据字典value值去查数据字典名称
根据上级编码与value值可以获取唯一对应的数据字典名称,但如果value值本身就是全局唯一的,我们也可以直接通过value值去获取数据字典名称,就不需要上级编码了
在之前的DictService和DictServiceImpl添加方法
接口
/**
* 根据上级编码与值获取数据字典名称
* @param parentDictCode
* @param value
* @return
*/
String getNameByParentDictCodeAndValue(String parentDictCode, String value);
实现类
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
@Override
public String getNameByParentDictCodeAndValue(String parentDictCode, String value) {
//如果value能唯一定位数据字典,parentDictCode可以传空,例如:省市区的value值能够唯一确定
if(StringUtils.isEmpty(parentDictCode)) {
Dict dict = dictMapper.selectOne(new QueryWrapper<Dict>().eq("value", value));
if(null != dict) {
return dict.getName();
}
} else {
Dict parentDict = this.getByDictsCode(parentDictCode);
if(null == parentDict) return "";
Dict dict = dictMapper.selectOne(new QueryWrapper<Dict>().eq("parent_id", parentDict.getId()).eq("value", value));
if(null != dict) {
return dict.getName();
}
}
return "";
}
在之前的DictController添加方法
@ApiOperation(value = "获取数据字典名称")
@GetMapping(value = "/getName/{parentDictCode}/{value}")
public String getName(
@ApiParam(name = "parentDictCode", value = "上级编码", required = true)
@PathVariable("parentDictCode") String parentDictCode,
@ApiParam(name = "value", value = "值", required = true)
@PathVariable("value") String value) {
return dictService.getNameByParentDictCodeAndValue(parentDictCode, value);
}
@ApiOperation(value = "获取数据字典名称")
@ApiImplicitParam(name = "value", value = "值", required = true, dataType = "Long", paramType = "path")
@GetMapping(value = "/getName/{value}")
public String getName(
@ApiParam(name = "value", value = "值", required = true)
@PathVariable("value") String value) {
return dictService.getNameByParentDictCodeAndValue("", value);
}
//说明:提供两个api接口,是因为有的数据字典名称只需要value就可以查询,有的需要value和上级编码才可以查询,如省市区不需要上级编码,医院等级需要上级编码
*Feign远程服务调用功能模块集成
Feign的翻译为假装、佯装。举例理解:比如service-hosp模块佯装调用service-cmn-client模块的接口方法,但是从本质上来说其实调用的是service-cmn模块的接口方法
创建service-client模块
引入依赖
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
在service-client模块创建service-cmn-client模块
添加Feign被远程调用接口类
/**
* 数据字典API接口
*/
@FeignClient("service-cmn")
public interface DictFeignClient {
/**
* 获取数据字典名称
* @param parentDictCode
* @param value
* @return
*/
@GetMapping(value = "/admin/cmn/dict/getName/{parentDictCode}/{value}")
String getName(@PathVariable("parentDictCode") String parentDictCode, @PathVariable("value") String value);
/**
* 获取数据字典名称
* @param value
* @return
*/
@GetMapping(value = "/admin/cmn/dict/getName/{value}")
String getName(@PathVariable("value") String value);
}
service-hosp模块远程调用service-cmn模块的接口
在service父模块引入依赖
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在service-hosp模块引入service-cmn-client(也就是service-cmn模块的client)依赖
<dependency>
<groupId>com.atguigu.yygh</groupId>
<artifactId>service-cmn-client</artifactId>
<version>1.0</version>
</dependency>
在service-hosp模块的启动类上添加开启服务调用的注解 @EnableFeignClients(basePackages = “com.atguigu”)
@SpringBootApplication
@ComponentScan(basePackages = "com.atguigu")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu")
public class ServiceHospApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHospApplication.class, args);
}
}
修改HospitalServiceImpl实现类方法,将远程调用接口返回的数据整合进去
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@Autowired 【注入字典的Feignclient】
private DictFeignClient dictFeignClient;
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
@Override
public Page<Hospital> selectPage(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo) {
Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
//0为第一页
Pageable pageable = PageRequest.of(page-1, limit, sort);
Hospital hospital = new Hospital();
BeanUtils.copyProperties(hospitalQueryVo, hospital);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
//创建实例
Example<Hospital> example = Example.of(hospital, matcher);
Page<Hospital> pages = hospitalRepository.findAll(example, pageable);
>>>>>>>>>>>>>>>>>>>>>>>
【调用封装数据方法】
pages.getContent().stream().forEach(item -> {this.packHospital(item);});<<<<<<<<<<<<<<<<<<<<<<
return pages;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
【这个方法其实是把远程调用方法的返回数据整合原返回数据一块封装到实体类中进行返回,所以这个方法叫做整合封装数据方法其实更合适】
/**
* 封装数据
* @param hospital
* @return
*/
private Hospital packHospital(Hospital hospital) {
String hostypeString = dictFeignClient.getName(DictEnum.HOSTYPE.getDictCode(),hospital.getHostype());
String provinceString = dictFeignClient.getName(hospital.getProvinceCode());
String cityString = dictFeignClient.getName(hospital.getCityCode());
String districtString = dictFeignClient.getName(hospital.getDistrictCode());
hospital.getParam().put("hostypeString", hostypeString);
hospital.getParam().put("fullAddress", provinceString + cityString + districtString + hospital.getAddress());
return hospital;
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
②添加数据字典筛选接口
根据dicode查询下层节点的方法
Service添加根据dicode查询下层节点的方法
@Override
public List<Dict> findByDictCode(String dictCode) {
Dict codeDict = this.getDictByDictCode(dictCode);
if(null == codeDict) return null;
return this.findChlidData(codeDict.getId());
}
Controller添加根据dicode查询下层节点的方法
@ApiOperation(value = "根据dictCode获取下级节点")
@GetMapping(value = "/findByDictCode/{dictCode}")
public Result<List<Dict>> findByDictCode(
@ApiParam(name = "dictCode", value = "节点编码", required = true)
@PathVariable String dictCode) {
List<Dict> list = dictService.findByDictCode(dictCode);
return Result.ok(list);
}
【数据字典筛选接口的业务逻辑在前端框架中通过整合调用这个‘根据dictCode查询子节点方法’以及之前写过的一些接口方法实现】
③医院详情接口
接口 HospitalService
/**
* 医院详情
* @param id
* @return
*/
Map<String, Object> show(String id);
实现类 HospitalServiceImpl
@Override
public Map<String, Object> show(String id) {
Map<String, Object> result = new HashMap<>();
Hospital hospital = this.packHospital(this.getById(id));
result.put("hospital", hospital);
//单独处理更直观
result.put("bookingRule", hospital.getBookingRule());
//不需要重复返回
hospital.setBookingRule(null);
return result;
}
Controller:HospitalController
@ApiOperation(value = "获取医院详情")
@GetMapping("show/{id}")
public Result show(
@ApiParam(name = "id", value = "医院id", required = true)
@PathVariable String id) {
return Result.ok(hospitalService.show(id));
}
④排班详情接口
页面效果:系统管理后台–医院信息列表页面–排班详情页面
页面效果
排班分成三部分显示:
1 科室信息(大科室与小科室树形展示)
2 排班日期,分页显示,根据上传排班数据聚合统计产生
3 排班日期对应的就诊医生信息
接口分析
1 科室数据使用Element-ui el-tree组件渲染展示,需要将医院上传的科室数据封装成两层父子级数据;
2 聚合所有排班数据,按日期分页展示,并统计号源数据展示
3 根据排班日期获取排班详情数据
实现分析
虽然是一个页面展示所有内容,但是页面相对复杂,我们分步骤实现
1 先实现左侧科室树形展示
2 其次排班日期分页展示
3 最后根据排班日期获取排班详情数据
④.1科室列表接口
页面效果为在图片左边框内的大科室和小科室列表
接口:在DepartmentService添加
//根据医院编号,查询医院所有科室列表
List<DepartmentVo> findDeptTree(String hoscode);
实现类:在DepartmentServiceImpl添加
//根据医院编号,查询医院所有科室列表
@Override
public List<DepartmentVo> findDeptTree(String hoscode) {
//创建list集合,用于最终数据封装
List<DepartmentVo> result = new ArrayList<>();
//根据医院编号,查询医院所有科室信息
Department departmentQuery = new Department();
departmentQuery.setHoscode(hoscode);
Example example = Example.of(departmentQuery);
//所有科室列表 departmentList
List<Department> departmentList = departmentRepository.findAll(example);
//根据大科室编号 bigcode 分组,获取每个大科室里面下级子科室
Map<String, List<Department>> deparmentMap =
departmentList.stream().collect(Collectors.groupingBy(Department::getBigcode));
//遍历map集合 deparmentMap
for(Map.Entry<String,List<Department>> entry : deparmentMap.entrySet()) {
//大科室编号
String bigcode = entry.getKey();
//大科室编号对应的全局数据
List<Department> deparment1List = entry.getValue();
//封装大科室
DepartmentVo departmentVo1 = new DepartmentVo();
departmentVo1.setDepcode(bigcode);
departmentVo1.setDepname(deparment1List.get(0).getBigname());
//封装小科室
List<DepartmentVo> children = new ArrayList<>();
for(Department department: deparment1List) {
DepartmentVo departmentVo2 = new DepartmentVo();
departmentVo2.setDepcode(department.getDepcode());
departmentVo2.setDepname(department.getDepname());
//封装到list集合
children.add(departmentVo2);
}
//把小科室list集合放到大科室children里面
departmentVo1.setChildren(children);
//放到最终result里面
result.add(departmentVo1);
}
//返回
return result;
}
Controller:在DepartmentController添加
@RestController
@RequestMapping("/admin/hosp/department")
@CrossOrigin 【【【【【】】】】】
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
//根据医院编号,查询医院所有科室列表
@ApiOperation(value = "查询医院所有科室列表")
@GetMapping("getDeptList/{hoscode}")
public Result getDeptList(@PathVariable String hoscode) {
List<DepartmentVo> list = departmentService.findDeptTree(hoscode);
return Result.ok(list);
}
}
④.2排班日期分页列表接口
页面效果为在图片上方框内的日期时间列表
接口:在ScheduleService添加
//根据医院编号 和 科室编号 ,查询排班规则数据
Map<String, Object> getRuleSchedule(long page, long limit, String hoscode, String depcode);
实现类:在ScheduleServiceImpl添加
-----------------------------------------------
@Autowired
private ScheduleRepository scheduleRepository;
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private HospitalService hospitalService;
-----------------------------------------------
//根据医院编号 和 科室编号 ,查询排班规则数据
@Override
public Map<String, Object> getRuleSchedule(long page, long limit, String hoscode, String depcode) {
//1 根据医院编号 和 科室编号 查询
Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);
//2 根据工作日workDate期进行分组
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(criteria),//匹配条件
Aggregation.group("workDate")//分组字段
.first("workDate").as("workDate")
//3 统计号源数量
.count().as("docCount")
.sum("reservedNumber").as("reservedNumber")
.sum("availableNumber").as("availableNumber"),
//排序
Aggregation.sort(Sort.Direction.DESC,"workDate"),
//4 实现分页
Aggregation.skip((page-1)*limit),
Aggregation.limit(limit)
);
//调用方法,最终执行
AggregationResults<BookingScheduleRuleVo> aggResults =
mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
List<BookingScheduleRuleVo> bookingScheduleRuleVoList = aggResults.getMappedResults();
//分组查询的总记录数
Aggregation totalAgg = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.group("workDate")
);
AggregationResults<BookingScheduleRuleVo> totalAggResults =
mongoTemplate.aggregate(totalAgg,
Schedule.class, BookingScheduleRuleVo.class);
int total = totalAggResults.getMappedResults().size();
//把日期对应星期获取
for(BookingScheduleRuleVo bookingScheduleRuleVo:bookingScheduleRuleVoList) {
Date workDate = bookingScheduleRuleVo.getWorkDate();
String dayOfWeek = this.getDayOfWeek(new DateTime(workDate));
bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
}
//设置最终数据,进行返回
Map<String, Object> result = new HashMap<>();
result.put("bookingScheduleRuleList",bookingScheduleRuleVoList);
result.put("total",total);
//获取医院名称
String hosName = hospitalService.getHospName(hoscode);
//其他基础数据
Map<String, String> baseMap = new HashMap<>();
baseMap.put("hosname",hosName);
result.put("baseMap",baseMap);
return result;
}
----------------------------------------------
/**
* 根据日期获取周几数据
* @param dateTime
* @return
*/
private String getDayOfWeek(DateTime dateTime) {
String dayOfWeek = "";
switch (dateTime.getDayOfWeek()) {
case DateTimeConstants.SUNDAY:
dayOfWeek = "周日";
break;
case DateTimeConstants.MONDAY:
dayOfWeek = "周一";
break;
case DateTimeConstants.TUESDAY:
dayOfWeek = "周二";
break;
case DateTimeConstants.WEDNESDAY:
dayOfWeek = "周三";
break;
case DateTimeConstants.THURSDAY:
dayOfWeek = "周四";
break;
case DateTimeConstants.FRIDAY:
dayOfWeek = "周五";
break;
case DateTimeConstants.SATURDAY:
dayOfWeek = "周六";
default:
break;
}
return dayOfWeek;
}
---------------------------------------------
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
在HospitalService添加根据医院编号获取医院名称接口
接口 HospitalService
/**
* 根据医院编号获取医院名称接口
* @param hoscode
* @return
*/
String getName(String hoscode);
实现类 HospitalServiceImpl
@Override
public String getName(String hoscode) {
Hospital hospital = hospitalRepository.getHospitalByHoscode(hoscode);
if(null != hospital) {
return hospital.getHosname();
}
return "";
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Controller:在ScheduleController添加
//根据医院编号 和 科室编号 ,查询排班规则数据
@ApiOperation(value ="查询排班规则数据")
@GetMapping("getScheduleRule/{page}/{limit}/{hoscode}/{depcode}")
public Result getScheduleRule(@PathVariable long page,
@PathVariable long limit,
@PathVariable String hoscode,
@PathVariable String depcode) {
Map<String,Object> map
= scheduleService.getRuleSchedule(page,limit,hoscode,depcode);
return Result.ok(map);
}
④.3根据排班日期获取排班详情列表接口
页面效果为在图片下方框内的排班详细信息列表
DAO:在ScheduleRepository添加
//根据医院编号 、科室编号和工作日期,查询排班详细信息
List<Schedule> findScheduleByHoscodeAndDepcodeAndWorkDate(String hoscode, String depcode, Date toDate);
接口:在ScheduleService添加
//根据医院编号 、科室编号和工作日期,查询排班详细信息
List<Schedule> getDetailSchedule(String hoscode, String depcode, String workDate);
实现类:在ScheduleServiceImpl添加
//根据医院编号 、科室编号和工作日期,查询排班详细信息
@Override
public List<Schedule> getDetailSchedule(String hoscode, String depcode, String workDate) {
//根据参数查询mongodb
List<Schedule> scheduleList =
scheduleRepository.findScheduleByHoscodeAndDepcodeAndWorkDate(hoscode,depcode,new DateTime(workDate).toDate());
//把得到list集合遍历,向设置其他值:医院名称、科室名称、日期对应星期
scheduleList.stream().forEach(item->{
this.packageSchedule(item);
});
return scheduleList;
}
//封装排班详情其他值 医院名称、科室名称、日期对应星期
private void packageSchedule(Schedule schedule) {
//设置医院名称
schedule.getParam().put("hosname",hospitalService.getHospName(schedule.getHoscode()));
//设置科室名称
schedule.getParam().put("depname",departmentService.getDepName(schedule.getHoscode(),schedule.getDepcode()));
//设置日期对应星期
schedule.getParam().put("dayOfWeek",this.getDayOfWeek(new DateTime(schedule.getWorkDate())));
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
在DepartmentService添加根据部门编码获取部门名称方法
接口 DepartmentService
//根据科室编号,和医院编号,查询科室名称
String getDepName(String hoscode, String depcode);
实现类 DepartmentServiceImpl
//根据科室编号,和医院编号,查询科室名称
@Override
public String getDepName(String hoscode, String depcode) {
Department department = departmentRepository.getDepartmentByHoscodeAndDepcode(hoscode, depcode);
if(department != null) {
return department.getDepname();
}
return null;
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Controller ScheduleController
//根据医院编号 、科室编号和工作日期,查询排班详细信息
@ApiOperation(value = "查询排班详细信息")
@GetMapping("getScheduleDetail/{hoscode}/{depcode}/{workDate}")
public Result getScheduleDetail( @PathVariable String hoscode,
@PathVariable String depcode,
@PathVariable String workDate) {
List<Schedule> list = scheduleService.getDetailSchedule(hoscode,depcode,workDate);
return Result.ok(list);
}
*SpringCloud GateWay 服务网关模块集成
这个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
用户使用前台–首页医院列表页面
区分一下
前端:Vue等开发的前端代码逻辑集合
后端:Java开发的后端代码逻辑集合
前台:业务用户前台使用平台
后台:业务管理后台平台
页面效果:用户使用前台–首页医院数据显示页面
图1
图2
图3
共需开发四个接口:
①获取医院等级接口(根据数据字典编码获取) 已开发,在DictController
②获取地区接口(根据数据字典编码获取) 已开发,在DictController
③医院分页查询列表接口
④根据医院名称关键字搜索医院列表接口(模糊查询)
③医院分页查询列表接口
@Api(tags = "医院管理接口")
@RestController
@RequestMapping("/api/hosp/hospital")
public class HospitalApiController {
@Autowired
private HospitalService hospitalService;
@ApiOperation(value = "获取分页列表")
@GetMapping("{page}/{limit}")
public Result index(
@PathVariable Integer page,
@PathVariable Integer limit,
HospitalQueryVo hospitalQueryVo) {
//显示上线的医院
//hospitalQueryVo.setStatus(1);
Page<Hospital> pageModel = hospitalService.selectHospPage(page, limit, hospitalQueryVo);
return Result.ok(pageModel);
}
}
④根据医院名称关键字搜索医院列表接口(模糊查询)
DAO层:接HospitalRepository.java
List<Hospital> findHospitalByHosnameLike(String hosname);
接口
/**
* 根据医院名称获取医院列表
*/
List<Hospital> findByHosname(String hosname);
实现类
@Override
public List<Hospital> findByHosname(String hosname) {
return hospitalRepository.findHospitalByHosnameLike(hosname);
}
Controller:在HospitalApiController.java添加方法【注意不是HospitalController】
@ApiOperation(value = "根据医院名称获取医院列表")
@GetMapping("findByHosname/{hosname}")
public Result findByHosname(
@ApiParam(name = "hosname", value = "医院名称", required = true)
@PathVariable String hosname) {
return Result.ok(hospitalService.findByHosname(hosname));
}
用户使用前台–医院详情页面
页面效果:医院详情页面–预约挂号页面
页面效果:医院详情页面–医院详情页面
页面效果:医院详情页面–预约须知页面
共需开发三个接口:①预约挂号接口②医院详情接口(已开发)③预约须知接口(前端实现)
①预约挂号接口
需求分析:需要获取医院信息(医院基本信息、预约信息)和科室信息
接口方法、实现类方法、controller方法
接口:HospitalService
/**
* 医院预约挂号详情
*/
Map<String, Object> item(String hoscode);
实现类:HospitalServiceImpl
@Autowired
private DepartmentService departmentService;
@Override
public Map<String, Object> item(String hoscode) {
Map<String, Object> result = new HashMap<>();
//医院详情
Hospital hospital = this.setHospitalHosType(this.getByHoscode(hoscode));
result.put("hospital", hospital);
//预约规则
result.put("bookingRule", hospital.getBookingRule());
//不需要重复返回
hospital.setBookingRule(null);
return result;
}
Controller:HospitalApiController
@ApiOperation(value = "获取科室列表")
@GetMapping("department/{hoscode}")
public Result index(
@ApiParam(name = "hoscode", value = "医院code", required = true)
@PathVariable String hoscode) {
return Result.ok(departmentService.findTree(hoscode));
}
@ApiOperation(value = "医院预约挂号详情")
@GetMapping("{hoscode}")
public Result item(
@ApiParam(name = "hoscode", value = "医院code", required = true)
@PathVariable String hoscode) {
return Result.ok(hospitalService.item(hoscode));
}
4.3service-user用户模块
用户使用前台–登录页面
页面效果:用户使用前台–登录页面
共需开发两个接口:①手机验证码登录接口②微信扫码登录接口
登录需求
- 登录页面使用弹出层形式
- 登录方式:①手机号+手机验证码②微信扫描
- 无注册界面,第一次登录根据手机号判断系统是否存在,如果不存在则自动注册
- 微信扫描登录成功必须绑定手机号码,即:第一次扫描成功后绑定手机号,以后登录扫描直接登录成功
- 网关统一判断登录状态,如何需要登录,页面弹出登录层
①手机验证码登录接口
搭建service-user模块
依赖导入:pom.xml
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_cmn_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
配置文件application.properties
# 服务端口
server.port=8203
# 服务名
spring.application.name=service-user
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.44.165:3306/yygh_user?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root123
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/yygh/user/mapper/xml/*.xml
启动类
@SpringBootApplication
@ComponentScan(basePackages = "com.atguigu")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu")
public class ServiceUserApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceUserApplication.class, args);
}
}
配置网关:在service-gateway的application.properties文件添加
#设置路由id
spring.cloud.gateway.routes[2].id=service-user
#设置路由的uri
spring.cloud.gateway.routes[2].uri=lb://service-user
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[2].predicates= Path=/*/user/**
model:UserInfo实体类 com.atguigu.yygh.model.user.UserInfo
DAO:①添加UserInfoMapper类 com.atguigu.yygh.user.UserInfoMapper
②添加UserInfoMapper.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.atguigu.yygh.user.mapper.UserInfoMapper">
</mapper>
接口方法以及实现类方法
接口:UserInfoService
public interface UserInfoService extends IService<UserInfo> {
//会员登录
Map<String, Object> login(LoginVo loginVo);
}
实现类:UserInfoServiceImpl
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Override
public Map<String, Object> login(LoginVo loginVo) {
String phone = loginVo.getPhone();
String code = loginVo.getCode();
//校验参数
if(StringUtils.isEmpty(phone) ||
StringUtils.isEmpty(code)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//TODO 验证码校验
//手机号已被使用
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("phone", phone);
//获取会员
UserInfo userInfo = baseMapper.selectOne(queryWrapper);
if(null == userInfo) {
userInfo = new UserInfo();
userInfo.setName("");
userInfo.setPhone(phone);
userInfo.setStatus(1);
this.save(userInfo);
}
//校验是否被禁用
if(userInfo.getStatus() == 0) {
throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
}
//TODO 记录登录
//返回页面显示名称
Map<String, Object> map = new HashMap<>();
String name = userInfo.getName();
if(StringUtils.isEmpty(name)) {
name = userInfo.getNickName();
}
if(StringUtils.isEmpty(name)) {
name = userInfo.getPhone();
}
map.put("name", name);
map.put("token", "");
return map;
}
}
//验证码校验功能,后续再开发
//登录成功生成token,请看后续JWT集成
Controller:UserInfoApiController
@RestController
@RequestMapping("/api/user")
public class UserInfoApiController {
@Autowired
private UserInfoService userInfoService;
@ApiOperation(value = "会员登录")
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo, HttpServletRequest request) {
loginVo.setIp(IpUtil.getIpAddr(request));
Map<String, Object> info = userInfoService.login(loginVo);
return Result.ok(info);
}
}
添加IpUtil工具类
public class IpUtil {
private static final String UNKNOWN = "unknown";
private static final String LOCALHOST = "127.0.0.1";
private static final String SEPARATOR = ",";
public static String getIpAddr(HttpServletRequest request) {
System.out.println(request);
String ipAddress;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (LOCALHOST.equals(ipAddress)) {
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
// "***.***.***.***".length()
if (ipAddress != null && ipAddress.length() >15) {
if (ipAddress.indexOf(SEPARATOR) >0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
}
*JWT(Json Web Token)模块集成
这个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
*阿里云短信服务模块集成
阿里云短信服务申请不下来,这个我自己改成了短信验证码发送到控制台显示,也实现了验证码登录
4.4service-msm短信服务模块
用户使用前台–登录页面
①手机验证码登录接口
搭建service-msm模块
依赖导入:pom.xml
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
配置文件application.properties
# 服务端口
server.port=8204
# 服务名
spring.application.name=service-msm
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.redis.host=192.168.44.165
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
aliyun.sms.regionId=default
aliyun.sms.accessKeyId=LT6I0Y5633pX89qC
aliyun.sms.secret=jX8D04Dm12I3gGKj345FYSzu0fq8mT
启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@EnableDiscoveryClient
public class ServiceMsmApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceMsmApplication.class, args);
}
}
配置网关:在service-gateway的application.properties文件添加
#设置路由id
spring.cloud.gateway.routes[3].id=service-msm
#设置路由的uri
spring.cloud.gateway.routes[3].uri=lb://service-msm
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[3].predicates= Path=/*/msm/**
封装注册短信验证码接口
添加配置类
@Component
public class ConstantPropertiesUtils implements InitializingBean {
@Value("${aliyun.sms.regionId}")
private String regionId;
@Value("${aliyun.sms.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.sms.secret}")
private String secret;
public static String REGION_Id;
public static String ACCESS_KEY_ID;
public static String SECRECT;
@Override
public void afterPropertiesSet() throws Exception {
REGION_Id=regionId;
ACCESS_KEY_ID=accessKeyId;
SECRECT=secret;
}
}
接口方法以及实现类方法
接口:MsmService
public interface MsmService {
//发送手机验证码
boolean send(String phone, String code);
}
实现类:MsmServiceImpl
@Service
public class MsmServiceImpl implements MsmService {
@Override
public boolean send(String phone, String code) {
//判断手机号是否为空
if(StringUtils.isEmpty(phone)) {
return false;
}
//整合阿里云短信服务
//设置相关参数
DefaultProfile profile = DefaultProfile.
getProfile(ConstantPropertiesUtils.REGION_Id,
ConstantPropertiesUtils.ACCESS_KEY_ID,
ConstantPropertiesUtils.SECRECT);
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
//request.setProtocol(ProtocolType.HTTPS);
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");
request.setVersion("2017-05-25");
request.setAction("SendSms");
//手机号
request.putQueryParameter("PhoneNumbers", phone);
//签名名称
request.putQueryParameter("SignName", "我的谷粒在线教育网站");
//模板code
request.putQueryParameter("TemplateCode", "SMS_180051135");
//验证码 使用json格式 {"code":"123456"}
Map<String,Object> param = new HashMap();
param.put("code",code);
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
//调用方法进行短信发送
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
}
Controller:MsmApiController
@RestController
@RequestMapping("/api/msm")
public class MsmApiController {
@Autowired
private MsmService msmService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
//发送手机验证码
@GetMapping("send/{phone}")
public Result sendCode(@PathVariable String phone) {
//从redis获取验证码,如果获取获取到,返回ok
// key 手机号 value 验证码
String code = redisTemplate.opsForValue().get(phone);
if(!StringUtils.isEmpty(code)) {
return Result.ok();
}
//如果从redis获取不到,
// 生成验证码,
code = RandomUtil.getSixBitRandom();
//调用service方法,通过整合短信服务进行发送
boolean isSend = msmService.send(phone,code);
//生成验证码放到redis里面,设置有效时间
if(isSend) {
redisTemplate.opsForValue().set(phone,code,2, TimeUnit.MINUTES);
return Result.ok();
} else {
return Result.fail().message("发送短信失败");
}
}
}
添加RandomUtil工具类
public class RandomUtil {
private static final Random random = new Random();
private static final DecimalFormat fourdf = new DecimalFormat("0000");
private static final DecimalFormat sixdf = new DecimalFormat("000000");
public static String getFourBitRandom() {
return fourdf.format(random.nextInt(10000));
}
public static String getSixBitRandom() {
return sixdf.format(random.nextInt(1000000));
}
/**
* 给定数组,抽取n个数据
* @param list
* @param n
* @return
*/
public static ArrayList getRandom(List list, int n) {
Random random = new Random();
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
// 生成随机数字并存入HashMap
for (int i = 0; i < list.size(); i++) {
int number = random.nextInt(100) + 1;
hashMap.put(number, i);
}
// 从HashMap导入数组
Object[] robjs = hashMap.values().toArray();
ArrayList r = new ArrayList();
// 遍历数组并打印数据
for (int i = 0; i < n; i++) {
r.add(list.get((int) robjs[i]));
System.out.print(list.get((int) robjs[i]) + "\t");
}
System.out.print("\n");
return r;
}
}
完善验证码校验接口
修改UserInfoServiceImpl类的登录方法,添加验证码校验方法
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//验证码校验
String mobleCode = redisTemplate.opsForValue().get(phone);
if(!code.equals(mobleCode)) {
throw new YyghException(ResultCodeEnum.CODE_ERROR);
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
*用户认证与网关整合模块集成
这个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
4.3service-user用户模块
用户使用前台–登录页面
②微信扫码登录接口
OAuth2 微信登录模块集成*
这个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
4.5service-oss文件服务模块
*阿里云OSS对象存储服务
这个模块集成在一个汇总博客里面:https://blog.csdn.net/tufhgddty/article/details/123492674
4.3service-user用户模块
用户使用前台–实名认证页面
共需开发一个接口:①用户实名认证接口
①用户实名认证接口
需求分析:用户登录成功后都要进行身份认证,认证通过后才可以预约挂号
认证过程:用户填写信息(姓名、证件类型、证件号码和证件照片)==> 平台审批
接口功能:①提交认证②上传证件图片③获取提交的认证信息
页面效果
接口、实现类
接口:在之前创建的UserInfoService接口中添加
//用户认证
void userAuth(Long userId, UserAuthVo userAuthVo);
实现类:在之前创建的UserInfoServiceImpl实现类中添加
//用户认证
@Override
public void userAuth(Long userId, UserAuthVo userAuthVo) {
//根据用户id查询用户信息
UserInfo userInfo = baseMapper.selectById(userId);
//设置认证信息
//认证人姓名
userInfo.setName(userAuthVo.getName());
//其他认证信息
userInfo.setCertificatesType(userAuthVo.getCertificatesType());
userInfo.setCertificatesNo(userAuthVo.getCertificatesNo());
userInfo.setCertificatesUrl(userAuthVo.getCertificatesUrl());
userInfo.setAuthStatus(AuthStatusEnum.AUTH_RUN.getStatus());
//进行信息更新
baseMapper.updateById(userInfo);
}
Controller:在之前创建的UserInfoApiController中添加
//用户认证接口
@PostMapping("auth/userAuth")
public Result userAuth(@RequestBody UserAuthVo userAuthVo, HttpServletRequest request) {
//传递两个参数,第一个参数用户id,第二个参数认证数据vo对象
userInfoService.userAuth(AuthContextHolder.getUserId(request),userAuthVo);
return Result.ok();
}
//获取用户id信息接口
@GetMapping("auth/getUserInfo")
public Result getUserInfo(HttpServletRequest request) {
Long userId = AuthContextHolder.getUserId(request);
UserInfo userInfo = userInfoService.getById(userId);
return Result.ok(userInfo);
}
获取当前用户的工具类:在common-util模块添加工具类AuthContextHolder
//获取当前用户信息工具类
public class AuthContextHolder {
//获取当前用户id
public static Long getUserId(HttpServletRequest request) {
//从header获取token
String token = request.getHeader("token");
//jwt从token获取userid
Long userId = JwtHelper.getUserId(token);
return userId;
}
//获取当前用户名称
public static String getUserName(HttpServletRequest request) {
//从header获取token
String token = request.getHeader("token");
//jwt从token获取userid
String userName = JwtHelper.getUserName(token);
return userName;
}
}
用户使用前台–就诊人管理页面
共需开发一个接口:①就诊人管理接口
①就诊人管理接口
实际上是MybatisPlus的CRUD接口
需求分析:预约下单需要选择就诊人,因此我们要实现就诊人管理,前端就诊人管理其实就是要实现一个完整的增删改查
引入依赖
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_cmn_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
DAO、接口、实现类、Controller
DAO:com.atguigu.yygh.user.mapper.PatientMapper
public interface PatientMapper extends BaseMapper<Patient> {
}
接口 PatientService:com.atguigu.yygh.user.service.PatientService
public interface PatientService extends IService<Patient> {
//获取就诊人列表
List<Patient> findAllUserId(Long userId);
//根据id获取就诊人信息
Patient getPatientId(Long id);
}
实现类 PatientServiceImpl:com.atguigu.yygh.user.service.impl.PatientServiceImpl
@Service
public class PatientServiceImpl extends
ServiceImpl<PatientMapper, Patient> implements PatientService {
@Autowired
private DictFeignClient dictFeignClient;
//获取就诊人列表
@Override
public List<Patient> findAllUserId(Long userId) {
//根据userid查询所有就诊人信息列表
QueryWrapper<Patient> wrapper = new QueryWrapper<>();
wrapper.eq("user_id",userId);
List<Patient> patientList = baseMapper.selectList(wrapper);
//通过远程调用,得到编码对应具体内容,查询数据字典表内容
patientList.stream().forEach(item -> {
//其他参数封装
this.packPatient(item);
});
return patientList;
}
@Override
public Patient getPatientId(Long id) {
return this.packPatient(baseMapper.selectById(id));
}
//Patient对象里面其他参数封装
private Patient packPatient(Patient patient) {
//根据证件类型编码,获取证件类型具体指
String certificatesTypeString =
dictFeignClient.getName(DictEnum.CERTIFICATES_TYPE.getDictCode(), patient.getCertificatesType());//联系人证件
//联系人证件类型
String contactsCertificatesTypeString =
dictFeignClient.getName(DictEnum.CERTIFICATES_TYPE.getDictCode(),patient.getContactsCertificatesType());
//省
String provinceString = dictFeignClient.getName(patient.getProvinceCode());
//市
String cityString = dictFeignClient.getName(patient.getCityCode());
//区
String districtString = dictFeignClient.getName(patient.getDistrictCode());
patient.getParam().put("certificatesTypeString", certificatesTypeString);
patient.getParam().put("contactsCertificatesTypeString", contactsCertificatesTypeString);
patient.getParam().put("provinceString", provinceString);
patient.getParam().put("cityString", cityString);
patient.getParam().put("districtString", districtString);
patient.getParam().put("fullAddress", provinceString + cityString + districtString + patient.getAddress());
return patient;
}
}
Controller:com.atguigu.yygh.user.api.PatientApiController
//就诊人管理接口
@RestController
@RequestMapping("/api/user/patient")
public class PatientApiController {
@Autowired
private PatientService patientService;
//获取就诊人列表
@GetMapping("auth/findAll")
public Result findAll(HttpServletRequest request) {
//获取当前登录用户id
Long userId = AuthContextHolder.getUserId(request);
List<Patient> list = patientService.findAllUserId(userId);
return Result.ok(list);
}
//添加就诊人
@PostMapping("auth/save")
public Result savePatient(@RequestBody Patient patient,HttpServletRequest request) {
//获取当前登录用户id
Long userId = AuthContextHolder.getUserId(request);
patient.setUserId(userId);
patientService.save(patient);
return Result.ok();
}
//根据id获取就诊人信息
@GetMapping("auth/get/{id}")
public Result getPatient(@PathVariable Long id) {
Patient patient = patientService.getPatientId(id);
return Result.ok(patient);
}
//修改就诊人
@PostMapping("auth/update")
public Result updatePatient(@RequestBody Patient patient) {
patientService.updateById(patient);
return Result.ok();
}
//删除就诊人
@DeleteMapping("auth/remove/{id}")
public Result removePatient(@PathVariable Long id) {
patientService.removeById(id);
return Result.ok();
}
}
系统管理后台–用户管理页面
需求分析:前面我们做了用户登录、用户认证与就诊人,现在我们需要把这些信息在我们的平台的后台管理系统做一个统一管理
页面效果一(用户管理页面–用户列表页面):
共需开发三个接口:①用户列表接口②用户锁定接口③用户详情接口
页面效果二(用户管理页面–用户认证审批页面)
共需开发两个接口:④用户认证列表接口⑤用户认证审批接口
①用户列表接口
添加接口方法和实现类方法以及创建controller包和UserController.java
接口:UserInfoService
//用户列表(条件查询带分页)
IPage<UserInfo> selectPage(Page<UserInfo> pageParam, UserInfoQueryVo userInfoQueryVo);
实现类:UserInfoServiceImpl
//用户列表(条件查询带分页)
@Override
public IPage<UserInfo> selectPage(Page<UserInfo> pageParam, UserInfoQueryVo userInfoQueryVo) {
//UserInfoQueryVo获取条件值
String name = userInfoQueryVo.getKeyword(); //用户名称
Integer status = userInfoQueryVo.getStatus();//用户状态
Integer authStatus = userInfoQueryVo.getAuthStatus(); //认证状态
String createTimeBegin = userInfoQueryVo.getCreateTimeBegin(); //开始时间
String createTimeEnd = userInfoQueryVo.getCreateTimeEnd(); //结束时间
//对条件值进行非空判断
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
if(!StringUtils.isEmpty(name)) {
wrapper.like("name",name);
}
if(!StringUtils.isEmpty(status)) {
wrapper.eq("status",status);
}
if(!StringUtils.isEmpty(authStatus)) {
wrapper.eq("auth_status",authStatus);
}
if(!StringUtils.isEmpty(createTimeBegin)) {
wrapper.ge("create_time",createTimeBegin);
}
if(!StringUtils.isEmpty(createTimeEnd)) {
wrapper.le("create_time",createTimeEnd);
}
//调用mapper的方法
IPage<UserInfo> pages = baseMapper.selectPage(pageParam, wrapper);
//编号变成对应值封装
pages.getRecords().stream().forEach(item -> {
this.packageUserInfo(item);
});
return pages;
}
//编号变成对应值封装
private UserInfo packageUserInfo(UserInfo userInfo) {
//处理认证状态编码
userInfo.getParam().put("authStatusString",AuthStatusEnum.getStatusNameByStatus(userInfo.getAuthStatus()));
//处理用户状态 0 1
String statusString = userInfo.getStatus().intValue()==0 ?"锁定" : "正常";
userInfo.getParam().put("statusString",statusString);
return userInfo;
}
Controller:controller包下的UserController【注意不是api包下的UserInfoController,api代表用户前台接口,不带api的代表管理后台接口】
@RestController
@RequestMapping("/admin/user")
public class UserController {
@Autowired
private UserInfoService userInfoService;
//用户列表(条件查询带分页)
@GetMapping("{page}/{limit}")
public Result list(@PathVariable Long page,
@PathVariable Long limit,
UserInfoQueryVo userInfoQueryVo) {
Page<UserInfo> pageParam = new Page<>(page,limit);
IPage<UserInfo> pageModel =
userInfoService.selectPage(pageParam,userInfoQueryVo);
return Result.ok(pageModel);
}
}
②用户锁定接口
添加接口方法、实现类方法、Controller方法
接口:UserInfoService
/**
* 用户锁定
* @param userId
* @param status 0:锁定 1:正常
*/
void lock(Long userId, Integer status);
实现类:UserInfoServiceImpl
@Override
public void lock(Long userId, Integer status) {
if(status.intValue() == 0 || status.intValue() == 1) {
UserInfo userInfo = this.getById(userId);
userInfo.setStatus(status);
this.updateById(userInfo);
}
}
Controller:UserController
@ApiOperation(value = "锁定")
@GetMapping("lock/{userId}/{status}")
public Result lock(
@PathVariable("userId") Long userId,
@PathVariable("status") Integer status){
userInfoService.lock(userId, status);
return Result.ok();
}
③用户详情接口
详情展示用户信息、用户就诊人信息和登录日志信息
页面效果
添加接口方法、实现类方法、Controller方法
接口:UserInfoService
/**
* 详情
* @param userId
* @return
*/
Map<String, Object> show(Long userId);
实现类:UserInfoServiceImpl
@Autowired
private PatientService patientService;
//用户详情
@Override
public Map<String, Object> show(Long userId) {
Map<String,Object> map = new HashMap<>();
//根据userid查询用户信息
UserInfo userInfo = this.packageUserInfo(baseMapper.selectById(userId));
map.put("userInfo",userInfo);
//根据userid查询就诊人信息
List<Patient> patientList = patientService.findAllUserId(userId);
map.put("patientList",patientList);
return map;
}
Controller:UserController
//用户详情
@GetMapping("show/{userId}")
public Result show(@PathVariable Long userId) {
Map<String,Object> map = userInfoService.show(userId);
return Result.ok(map);
}
④用户认证列表
api接口与用户列表一致,只是默认加了一个认证状态搜索条件:authStatus【具体逻辑在前端实现,不再阐述】
⑤用户认证审批
添加接口方法、实现类方法、Controller方法
接口:UserInfoService
/**
* 认证审批
* @param userId
* @param authStatus 2:通过 -1:不通过
*/
void approval(Long userId, Integer authStatus);
实现类:UserInfoServiceImpl
//认证审批 2通过 -1不通过
@Override
public void approval(Long userId, Integer authStatus) {
if(authStatus.intValue()==2 || authStatus.intValue()==-1) {
UserInfo userInfo = baseMapper.selectById(userId);
userInfo.setAuthStatus(authStatus);
baseMapper.updateById(userInfo);
}
}
Controller:UserController
//认证审批
@GetMapping("approval/{userId}/{authStatus}")
public Result approval(@PathVariable Long userId,@PathVariable Integer authStatus) {
userInfoService.approval(userId,authStatus);
return Result.ok();
}
4.1service-hosp医院服务接口模块
用户使用前台–预约挂号详情页面
页面效果:用户使用前台–医院详情页面–预约挂号页面–预约挂号详情页面
页面展示分析
-
分页展示可预约日期,根据有号、无号、约满等状态展示不同颜色,以示区分
-
可预约最后一个日期为即将放号日期,根据放号时间页面展示倒计时
共需开发两个接口:①查询可预约排班列表接口②排班预约详情接口(已经实现过)③预约确认接口
①查询可预约排班列表接口
根据预约周期,展示可预约日期数据,按分页展示
接口方法、实现类方法、Controller方法
接口方法:ScheduleService
/**
* 获取排班可预约日期数据
* @param page
* @param limit
* @param hoscode
* @param depcode
* @return
*/
Map<String, Object> getBookingScheduleRule(int page, int limit, String hoscode, String depcode);
实现类方法:ScheduleServiceImpl
@Override
public Map<String, Object> getBookingScheduleRule(int page, int limit, String hoscode, String depcode) {
Map<String, Object> result = new HashMap<>();
//获取预约规则
Hospital hospital = hospitalService.getByHoscode(hoscode);
if(null == hospital) {
throw new YyghException(ResultCodeEnum.DATA_ERROR);
}
BookingRule bookingRule = hospital.getBookingRule();
//获取可预约日期分页数据
IPage iPage = this.getListDate(page, limit, bookingRule);
//当前页可预约日期
List<Date> dateList = iPage.getRecords();
//获取可预约日期科室剩余预约数
Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(dateList);
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.group("workDate")//分组字段
.first("workDate").as("workDate")
.count().as("docCount")
.sum("availableNumber").as("availableNumber")
.sum("reservedNumber").as("reservedNumber")
);
AggregationResults<BookingScheduleRuleVo> aggregationResults = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
List<BookingScheduleRuleVo> scheduleVoList = aggregationResults.getMappedResults();
//获取科室剩余预约数
//合并数据 将统计数据ScheduleVo根据“安排日期”合并到BookingRuleVo
Map<Date, BookingScheduleRuleVo> scheduleVoMap = new HashMap<>();
if(!CollectionUtils.isEmpty(scheduleVoList)) {
scheduleVoMap = scheduleVoList.stream().collect(Collectors.toMap(BookingScheduleRuleVo::getWorkDate, BookingScheduleRuleVo -> BookingScheduleRuleVo));
}
//获取可预约排班规则
List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<>();
for(int i=0, len=dateList.size(); i<len; i++) {
Date date = dateList.get(i);
BookingScheduleRuleVo bookingScheduleRuleVo = scheduleVoMap.get(date);
if(null == bookingScheduleRuleVo) { // 说明当天没有排班医生
bookingScheduleRuleVo = new BookingScheduleRuleVo();
//就诊医生人数
bookingScheduleRuleVo.setDocCount(0);
//科室剩余预约数 -1表示无号
bookingScheduleRuleVo.setAvailableNumber(-1);
}
bookingScheduleRuleVo.setWorkDate(date);
bookingScheduleRuleVo.setWorkDateMd(date);
//计算当前预约日期为周几
String dayOfWeek = this.getDayOfWeek(new DateTime(date));
bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
//最后一页最后一条记录为即将预约 状态 0:正常 1:即将放号 -1:当天已停止挂号
if(i == len-1 && page == iPage.getPages()) {
bookingScheduleRuleVo.setStatus(1);
} else {
bookingScheduleRuleVo.setStatus(0);
}
//当天预约如果过了停号时间, 不能预约
if(i == 0 && page == 1) {
DateTime stopTime = this.getDateTime(new Date(), bookingRule.getStopTime());
if(stopTime.isBeforeNow()) {
//停止预约
bookingScheduleRuleVo.setStatus(-1);
}
}
bookingScheduleRuleVoList.add(bookingScheduleRuleVo);
}
//可预约日期规则数据
result.put("bookingScheduleList", bookingScheduleRuleVoList);
result.put("total", iPage.getTotal());
//其他基础数据
Map<String, String> baseMap = new HashMap<>();
//医院名称
baseMap.put("hosname", hospitalService.getHospName(hoscode));
//科室
Department department =departmentService.getDepartment(hoscode, depcode);
//大科室名称
baseMap.put("bigname", department.getBigname());
//科室名称
baseMap.put("depname", department.getDepname());
//月
baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
//放号时间
baseMap.put("releaseTime", bookingRule.getReleaseTime());
//停号时间
baseMap.put("stopTime", bookingRule.getStopTime());
result.put("baseMap", baseMap);
return result;
}
/**
* 获取可预约日期分页数据
*/
private IPage<Date> getListDate(int page, int limit, BookingRule bookingRule) {
//当天放号时间
DateTime releaseTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
//预约周期
int cycle = bookingRule.getCycle();
//如果当天放号时间已过,则预约周期后一天为即将放号时间,周期加1
if(releaseTime.isBeforeNow()) cycle += 1;
//可预约所有日期,最后一天显示即将放号倒计时
List<Date> dateList = new ArrayList<>();
for (int i = 0; i < cycle; i++) {
//计算当前预约日期
DateTime curDateTime = new DateTime().plusDays(i);
String dateString = curDateTime.toString("yyyy-MM-dd");
dateList.add(new DateTime(dateString).toDate());
}
//日期分页,由于预约周期不一样,页面一排最多显示7天数据,多了就要分页显示
List<Date> pageDateList = new ArrayList<>();
int start = (page-1)*limit;
int end = (page-1)*limit+limit;
if(end >dateList.size()) end = dateList.size();
for (int i = start; i < end; i++) {
pageDateList.add(dateList.get(i));
}
IPage<Date> iPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(page, 7, dateList.size());
iPage.setRecords(pageDateList);
return iPage;
}
/**
* 将Date日期(yyyy-MM-dd HH:mm)转换为DateTime
*/
private DateTime getDateTime(Date date, String timeString) {
String dateTimeString = new DateTime(date).toString("yyyy-MM-dd") + " "+ timeString;
DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(dateTimeString);
return dateTime;
}
获取科室信息
接口方法、实现类方法
接口:DepartmentService
/**
* 获取部门
*/
Department getDepartment(String hoscode, String depcode);
实现类:DepartmentServiceImpl
@Override
public Department getDepartment(String hoscode, String depcode) {
return departmentRepository.getDepartmentByHoscodeAndDepcode(hoscode, depcode);
}
Controller:HospitalApiController
@Autowired
private ScheduleService scheduleService;
@ApiOperation(value = "获取可预约排班数据")
@GetMapping("auth/getBookingScheduleRule/{page}/{limit}/{hoscode}/{depcode}")
public Result getBookingSchedule(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Integer page,
@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Integer limit,
@ApiParam(name = "hoscode", value = "医院code", required = true)
@PathVariable String hoscode,
@ApiParam(name = "depcode", value = "科室code", required = true)
@PathVariable String depcode) {
return Result.ok(scheduleService.getBookingScheduleRule(page, limit, hoscode, depcode));
}
@ApiOperation(value = "获取排班数据")
@GetMapping("auth/findScheduleList/{hoscode}/{depcode}/{workDate}")
public Result findScheduleList(
@ApiParam(name = "hoscode", value = "医院code", required = true)
@PathVariable String hoscode,
@ApiParam(name = "depcode", value = "科室code", required = true)
@PathVariable String depcode,
@ApiParam(name = "workDate", value = "排班日期", required = true)
@PathVariable String workDate) {
return Result.ok(scheduleService.getDetailSchedule(hoscode, depcode, workDate));
}
③预约挂号确认接口
页面效果:预约挂号确认页面
预约步骤:
1 根据排班id获取排班信息,在页面展示
2 选择就诊人
3 预约下单
接口方法、实现类方法、Controller
接口:ScheduleService
/**
* 根据id获取排班
* @param id
* @return
*/
Schedule getById(String id);
实现类:ScheduleServiceImpl
@Override
public Schedule getById(String id) {
Schedule schedule = scheduleRepository.findById(id).get();
return this.packSchedule(schedule);
}
Controller:HospitalApiController
@ApiOperation(value = "根据排班id获取排班数据")
@GetMapping("getSchedule/{scheduleId}")
public Result getSchedule(
@ApiParam(name = "scheduleId", value = "排班id", required = true)
@PathVariable String scheduleId) {
return Result.ok(scheduleService.getById(scheduleId));
}
4.6service-order模块
用户使用前台–预约挂号下单页面
页面效果:用户使用前台–预约下单页面
需求分析
订单表结构
下单分析
下单参数:就诊人id与排班id
1、下单我们要获取就诊人信息
2、获取排班下单信息与规则信息
3、获取医院签名信息,然后通过接口去医院预约下单
4、下单成功更新排班信息与发送短信
①预约挂号下单接口(创建)
搭建service-order模块
引入依赖
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_cmn_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
application.properties
# 服务端口
server.port=8206
# 服务名
spring.application.name=service-order
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.44.165:3306/yygh_hosp?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root123
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.data.mongodb.uri=mongodb://192.168.44.165:27017/yygh_hosp
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#rabbitmq地址
spring.rabbitmq.host=192.168.44.165
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
配置网关:service-gateway的application.properties
#设置路由id
spring.cloud.gateway.routes[6].id=service-order
#设置路由的uri
spring.cloud.gateway.routes[6].uri=lb://service-order
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[6].predicates= Path=/*/order/**
启动类
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.atguigu"})
public class ServiceOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOrderApplication.class, args);
}
}
model:使用com.atguigu.yygh.model.order.OrderInfo
DAO、接口、实现类、Controller
DAO OrderInfoMapper
public interface OrderInfoMapper extends BaseMapper<OrderInfo> {
}
接口 OrderService
public interface OrderService extends IService<OrderInfo> {
//保存订单
Long saveOrder(String scheduleId, Long patientId);
}
实现类 OrderServiceImpl
@Service
public class OrderServiceImpl extends
ServiceImpl<OrderMapper, OrderInfo> implements OrderService {
//保存订单
@Override
public Long saveOrder(String scheduleId, Long patientId) {
return null;
}
}
Controller:OrderApiController
@Api(tags = "订单接口")
@RestController
@RequestMapping("/api/order/orderInfo")
public class OrderApiController {
@Autowired
private OrderService orderService;
@ApiOperation(value = "创建订单")
@PostMapping("auth/submitOrder/{scheduleId}/{patientId}")
public Result submitOrder(
@ApiParam(name = "scheduleId", value = "排班id", required = true)
@PathVariable String scheduleId,
@ApiParam(name = "patientId", value = "就诊人id", required = true)
@PathVariable Long patientId) {
return Result.ok(orderService.saveOrder(scheduleId, patientId));
}
}
②封装Feign调用获取就诊人接口
获取就诊人信息api接口
操作模块:service-user
在PatientApiController类添加方法
@ApiOperation(value = "获取就诊人")
@GetMapping("inner/get/{id}")
public Patient getPatientOrder(
@ApiParam(name = "id", value = "就诊人id", required = true)
@PathVariable("id") Long id) {
return patientService.getById(id);
}
搭建service-user-client模块
添加Feign接口类
@FeignClient(value = "service-user")
@Repository
public interface PatientFeignClient {
//获取就诊人
@GetMapping("/api/user/patient/inner/get/{id}")
Patient getPatient(@PathVariable("id") Long id);
}
③封装Feign调用获取排班下单信息接口
获取排班下单信息api接口
操作模块:service-hosp
接口、实现类
接口 ScheduleService
//根据排班id获取预约下单数据
ScheduleOrderVo getScheduleOrderVo(String scheduleId);
实现类 ScheduleServiceImpl
//根据排班id获取预约下单数据
@Override
public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
//排班信息
Schedule schedule = baseMapper.selectById(scheduleId);
if(null == schedule) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//获取预约规则信息
Hospital hospital = hospitalService.getByHoscode(schedule.getHoscode());
if(null == hospital) {
throw new YyghException(ResultCodeEnum.DATA_ERROR);
}
BookingRule bookingRule = hospital.getBookingRule();
if(null == bookingRule) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
scheduleOrderVo.setHoscode(schedule.getHoscode());
scheduleOrderVo.setHosname(hospitalService.getHospName(schedule.getHoscode()));
scheduleOrderVo.setDepcode(schedule.getDepcode());
scheduleOrderVo.setDepname(departmentService.getDepName(schedule.getHoscode(), schedule.getDepcode()));
scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());
scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());
scheduleOrderVo.setTitle(schedule.getTitle());
scheduleOrderVo.setReserveDate(schedule.getWorkDate());
scheduleOrderVo.setReserveTime(schedule.getWorkTime());
scheduleOrderVo.setAmount(schedule.getAmount());
//退号截止天数(如:就诊前一天为-1,当天为0)
int quitDay = bookingRule.getQuitDay();
DateTime quitTime = this.getDateTime(new DateTime(schedule.getWorkDate()).plusDays(quitDay).toDate(), bookingRule.getQuitTime());
scheduleOrderVo.setQuitTime(quitTime.toDate());
//预约开始时间
DateTime startTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
scheduleOrderVo.setStartTime(startTime.toDate());
//预约截止时间
DateTime endTime = this.getDateTime(new DateTime().plusDays(bookingRule.getCycle()).toDate(), bookingRule.getStopTime());
scheduleOrderVo.setEndTime(endTime.toDate());
//当天停止挂号时间
DateTime stopTime = this.getDateTime(new Date(), bookingRule.getStopTime());
scheduleOrderVo.setStartTime(startTime.toDate());
return scheduleOrderVo;
}
Controller:在HospitalApiController添加方法
@ApiOperation(value = "根据排班id获取预约下单数据")
@GetMapping("inner/getScheduleOrderVo/{scheduleId}")
public ScheduleOrderVo getScheduleOrderVo(
@ApiParam(name = "scheduleId", value = "排班id", required = true)
@PathVariable("scheduleId") String scheduleId) {
return scheduleService.getScheduleOrderVo(scheduleId);
}
获取下单引用签名信息接口
接口、实现类
接口 HospitalSetService
//获取医院签名信息
SignInfoVo getSignInfoVo(String hoscode);
实现类 HospitalSetServiceImpl
//获取医院签名信息
@Override
public SignInfoVo getSignInfoVo(String hoscode) {
QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>();
wrapper.eq("hoscode",hoscode);
HospitalSet hospitalSet = baseMapper.selectOne(wrapper);
if(null == hospitalSet) {
throw new YyghException(ResultCodeEnum.HOSPITAL_OPEN);
}
SignInfoVo signInfoVo = new SignInfoVo();
signInfoVo.setApiUrl(hospitalSet.getApiUrl());
signInfoVo.setSignKey(hospitalSet.getSignKey());
return signInfoVo;
}
Controller:在HospitalApiController类添加方法
@ApiOperation(value = "获取医院签名信息")
@GetMapping("inner/getSignInfoVo/{hoscode}")
public SignInfoVo getSignInfoVo(
@ApiParam(name = "hoscode", value = "医院code", required = true)
@PathVariable("hoscode") String hoscode) {
return hospitalSetService.getSignInfoVo(hoscode);
}
搭建service-hosp-client模块
添加Feign接口类
@FeignClient(value = "service-hosp")
@Repository
public interface HospitalFeignClient {
/**
* 根据排班id获取预约下单数据
*/
@GetMapping("/api/hosp/hospital/inner/getScheduleOrderVo/{scheduleId}")
ScheduleOrderVo getScheduleOrderVo(@PathVariable("scheduleId") String scheduleId);
/**
* 获取医院签名信息
*/
@GetMapping("/api/hosp/hospital/inner/getSignInfoVo/{hoscode}")
SignInfoVo getSignInfoVo(@PathVariable("hoscode") String hoscode);
}
①预约挂号下单接口(实现)
引入依赖
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_cmn_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_hosp_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_user_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
封装下单工具类
封装HttpRequestHelper类,添加签名请求方法
public class HttpRequestHelper {
/**
*
* @param paramMap
* @return
*/
public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
resultMap.put(param.getKey(), param.getValue()[0]);
}
return resultMap;
}
/**
* 请求数据获取签名
* @param paramMap
* @param signKey
* @return
*/
public static String getSign(Map<String, Object> paramMap, String signKey) {
if(paramMap.containsKey("sign")) {
paramMap.remove("sign");
}
TreeMap<String, Object> sorted = new TreeMap<>(paramMap);
StringBuilder str = new StringBuilder();
for (Map.Entry<String, Object> param : sorted.entrySet()) {
str.append(param.getValue()).append("|");
}
str.append(signKey);
log.info("加密前:" + str.toString());
String md5Str = MD5.encrypt(str.toString());
log.info("加密后:" + md5Str);
return md5Str;
}
/**
* 签名校验
* @param paramMap
* @param signKey
* @return
*/
public static boolean isSignEquals(Map<String, Object> paramMap, String signKey) {
String sign = (String)paramMap.get("sign");
String md5Str = getSign(paramMap, signKey);
if(!sign.equals(md5Str)) {
return false;
}
return true;
}
/**
* 获取时间戳
* @return
*/
public static long getTimestamp() {
return new Date().getTime();
}
/**
* 封装同步请求
*/
public static JSONObject sendRequest(Map<String, Object> paramMap, String url){
String result = "";
try {
//封装post参数
StringBuilder postdata = new StringBuilder();
for (Map.Entry<String, Object> param : paramMap.entrySet()) {
postdata.append(param.getKey()).append("=")
.append(param.getValue()).append("&");
}
log.info(String.format("--> 发送请求:post data %1s", postdata));
byte[] reqData = postdata.toString().getBytes("utf-8");
byte[] respdata = HttpUtil.doPost(url,reqData);
result = new String(respdata);
log.info(String.format("--> 应答结果:result data %1s", result));
} catch (Exception ex) {
ex.printStackTrace();
}
return JSONObject.parseObject(result);
}
}
实现下单接口(修改实现类方法)
@Service
public class OrderServiceImpl extends
ServiceImpl<OrderMapper, OrderInfo> implements OrderService {
@Autowired
private PatientFeignClient patientFeignClient;
@Autowired
private HospitalFeignClient hospitalFeignClient;
//保存订单
@Override
public Long saveOrder(String scheduleId, Long patientId) {
Patient patient = patientFeignClient.getPatient(patientId);
if(null == patient) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
ScheduleOrderVo scheduleOrderVo = hospitalFeignClient.getScheduleOrderVo(scheduleId);
if(null == scheduleOrderVo) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//当前时间不可以预约
if(new DateTime(scheduleOrderVo.getStartTime()).isAfterNow()
|| new DateTime(scheduleOrderVo.getEndTime()).isBeforeNow()) {
throw new YyghException(ResultCodeEnum.TIME_NO);
}
SignInfoVo signInfoVo = hospitalFeignClient.getSignInfoVo(scheduleOrderVo.getHoscode());
if(null == scheduleOrderVo) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
if(scheduleOrderVo.getAvailableNumber() <= 0) {
throw new YyghException(ResultCodeEnum.NUMBER_NO);
}
OrderInfo orderInfo = new OrderInfo();
BeanUtils.copyProperties(scheduleOrderVo, orderInfo);
String outTradeNo = System.currentTimeMillis() + ""+ new Random().nextInt(100);
orderInfo.setOutTradeNo(outTradeNo);
orderInfo.setScheduleId(scheduleId);
orderInfo.setUserId(patient.getUserId());
orderInfo.setPatientId(patientId);
orderInfo.setPatientName(patient.getName());
orderInfo.setPatientPhone(patient.getPhone());
orderInfo.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());
this.save(orderInfo);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("hoscode",orderInfo.getHoscode());
paramMap.put("depcode",orderInfo.getDepcode());
paramMap.put("hosScheduleId",orderInfo.getScheduleId());
paramMap.put("reserveDate",new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd"));
paramMap.put("reserveTime", orderInfo.getReserveTime());
paramMap.put("amount",orderInfo.getAmount());
paramMap.put("name", patient.getName());
paramMap.put("certificatesType",patient.getCertificatesType());
paramMap.put("certificatesNo", patient.getCertificatesNo());
paramMap.put("sex",patient.getSex());
paramMap.put("birthdate", patient.getBirthdate());
paramMap.put("phone",patient.getPhone());
paramMap.put("isMarry", patient.getIsMarry());
paramMap.put("provinceCode",patient.getProvinceCode());
paramMap.put("cityCode", patient.getCityCode());
paramMap.put("districtCode",patient.getDistrictCode());
paramMap.put("address",patient.getAddress());
//联系人
paramMap.put("contactsName",patient.getContactsName());
paramMap.put("contactsCertificatesType", patient.getContactsCertificatesType());
paramMap.put("contactsCertificatesNo",patient.getContactsCertificatesNo());
paramMap.put("contactsPhone",patient.getContactsPhone());
paramMap.put("timestamp", HttpRequestHelper.getTimestamp());
String sign = HttpRequestHelper.getSign(paramMap, signInfoVo.getSignKey());
paramMap.put("sign", sign);
JSONObject result = HttpRequestHelper.sendRequest(paramMap, signInfoVo.getApiUrl()+"/order/submitOrder");
if(result.getInteger("code") == 200) {
JSONObject jsonObject = result.getJSONObject("data");
//预约记录唯一标识(医院预约记录主键)
String hosRecordId = jsonObject.getString("hosRecordId");
//预约序号
Integer number = jsonObject.getInteger("number");;
//取号时间
String fetchTime = jsonObject.getString("fetchTime");;
//取号地址
String fetchAddress = jsonObject.getString("fetchAddress");;
//更新订单
orderInfo.setHosRecordId(hosRecordId);
orderInfo.setNumber(number);
orderInfo.setFetchTime(fetchTime);
orderInfo.setFetchAddress(fetchAddress);
baseMapper.updateById(orderInfo);
//排班可预约数
Integer reservedNumber = jsonObject.getInteger("reservedNumber");
//排班剩余预约数
Integer availableNumber = jsonObject.getInteger("availableNumber");
//发送mq信息更新号源和短信通知
} else {
throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());
}
return orderInfo.getId();
}
}
预约成功后处理逻辑
预约成功后我们要更新预约数和短信提醒预约成功,为了提高下单的并发性,这部分逻辑我们就交给mq为我们完成,预约成功发送消息即可
*RabbitMQ功能模块集成
(1)简介:
以商品订单场景为例,
如果商品服务和订单服务是两个不同的微服务,在下单的过程中订单服务需要调用商品服务进行扣库存操作。按照传统的方式,下单过程要等到调用完毕之后才能返回下单成功,如果网络产生波动等原因使得商品服务扣库存延迟或者失败,会带来较差的用户体验,如果在高并发的场景下,这样的处理显然是不合适的,那怎么进行优化呢?这就需要消息队列登场了。
消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通的时候在将消息转发给相应的应用程序或者服务,当然前提是这些服务订阅了该队列。如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。
RabbitMQ就是这样一款消息队列。RabbitMQ是一个开源的消息代理的队列服务器,用来通过普通协议在完全不同的应用之间共享数据。
(2)典型应用场景:
异步处理:把消息放入消息中间件中,等到需要的时候再去处理。
流量削峰:例如秒杀活动,在短时间内访问量急剧增加,使用消息队列,当消息队列满了就拒绝响应,跳转到错误页面,这样就可以使得系统不会因为超负载而崩溃。
日志处理
应用解耦
安装RabbitMQ
docker pull rabbitmq:management
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
搭建rabbit-util模块:在common模块下搭建,搭建过程如:common-util
引入依赖
<!--rabbitmq消息队列-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
封装Service方法
@Service
public class RabbitService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param exchange 交换机
* @param routingKey 路由键
* @param message 消息
*/
public boolean sendMessage(String exchange, String routingKey, Object message) {
rabbitTemplate.convertAndSend(exchange, routingKey, message);
return true;
}
}
创建RabbitMQ配置类,配置mq消息转换器
@Configuration
public class MQConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
//说明:默认是字符串转换器
封装短信接口
操作模块:service-msm
引入依赖
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>rabbit_util</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
添加配置
#rabbitmq地址
spring.rabbitmq.host=192.168.44.165
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
添加常量配置:在rabbit-util模块com.atguigu.yygh.common.constant.MqConst类添加
public class MqConst {
/**
* 预约下单
*/
public static final String EXCHANGE_DIRECT_ORDER
= "exchange.direct.order";
public static final String ROUTING_ORDER = "order";
//队列
public static final String QUEUE_ORDER = "queue.order";
/**
* 短信
*/
public static final String EXCHANGE_DIRECT_MSM = "exchange.direct.msm";
public static final String ROUTING_MSM_ITEM = "msm.item";
//队列
public static final String QUEUE_MSM_ITEM = "queue.msm.item";
}
使用在model模块封装的短信实体类MsmVo
@Data
@ApiModel(description = "短信实体")
public class MsmVo {
@ApiModelProperty(value = "phone")
private String phone;
@ApiModelProperty(value = "短信模板code")
private String templateCode;
@ApiModelProperty(value = "短信模板参数")
private Map<String,Object> param;
}
接口方法和实现类方法
接口 MsmService
boolean send(MsmVo msmVo);
实现类 MsmServiceImpl
@Override
public boolean send(MsmVo msmVo) {
if(!StringUtils.isEmpty(msmVo.getPhone())) {
String code = (String)msmVo.getParam().get("code");
return this.send(msmVo.getPhone(),code);
}
return false;
}
封装mq监听器
@Component
public class SmsReceiver {
@Autowired
private MsmService msmService;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = MqConst.QUEUE_MSM_ITEM, durable = "true"),
exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_MSM),
key = {MqConst.ROUTING_MSM_ITEM}
))
public void send(MsmVo msmVo, Message message, Channel channel) {
msmService.send(msmVo);
}
}
封装更新排班数量接口
操作模块:service-hosp
引入依赖
<!--rabbitmq消息队列-->
<dependency>
<groupId>com.atguigu.yygh</groupId>
<artifactId>rabbit-util</artifactId>
<version>1.0</version>
</dependency>
添加配置:在service-hosp的application.properties中添加
#rabbitmq地址
spring.rabbitmq.host=192.168.44.165
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
添加常量配置 constant:常量
在rabbit-util模块的constant包的MqConst类添加
/**
* 预约下单
*/
public static final String EXCHANGE_DIRECT_ORDER = "exchange.direct.order";
public static final String ROUTING_ORDER = "order";
//队列
public static final String QUEUE_ORDER = "queue.order";
使用model模块封装的更新排班实体类
@Data
@ApiModel(description = "OrderMqVo")
public class OrderMqVo {
@ApiModelProperty(value = "可预约数")
private Integer reservedNumber;
@ApiModelProperty(value = "剩余预约数")
private Integer availableNumber;
@ApiModelProperty(value = "排班id")
private String scheduleId;
@ApiModelProperty(value = "短信实体")
private MsmVo msmVo;
}
接口方法和实现类方法
接口 ScheduleService
/**
* 修改排班
*/
void update(Schedule schedule);
实现类 ScheduleServiceImpl
@Override
public void update(Schedule schedule) {
schedule.setUpdateTime(new Date());
//主键一致就是更新
scheduleRepository.save(schedule);
}
封装mq监听器:在service-hosp模块
@Component
public class HospitalReceiver {
@Autowired
private ScheduleService scheduleService;
@Autowired
private RabbitService rabbitService;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = MqConst.QUEUE_ORDER, durable = "true"),
exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_ORDER),
key = {MqConst.ROUTING_ORDER}
))
public void receiver(OrderMqVo orderMqVo, Message message, Channel channel) throws IOException {
//下单成功更新预约数
Schedule schedule = scheduleService.getScheduleId(orderMqVo.getScheduleId());
schedule.setReservedNumber(orderMqVo.getReservedNumber());
schedule.setAvailableNumber(orderMqVo.getAvailableNumber());
scheduleService.update(schedule);
//发送短信
MsmVo msmVo = orderMqVo.getMsmVo();
if(null != msmVo) {
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
}
}
}
调整下单接口
操作模块:service-order
引入依赖
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>rabbit_util</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
添加配置
#rabbitmq地址
spring.rabbitmq.host=192.168.44.165
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
修改下单接口实现类方法
@Autowired
private RabbitService rabbitService;
@Transactional(rollbackFor = Exception.class)
@Override
public Long saveOrder(String scheduleId, Long patientId) {
.......
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//排班可预约数
Integer reservedNumber = jsonObject.getInteger("reservedNumber");
//排班剩余预约数
Integer availableNumber = jsonObject.getInteger("availableNumber");
//发送mq信息更新号源和短信通知
//发送mq信息更新号源
OrderMqVo orderMqVo = new OrderMqVo();
orderMqVo.setScheduleId(scheduleId);
orderMqVo.setReservedNumber(reservedNumber);
orderMqVo.setAvailableNumber(availableNumber);
//短信提示
MsmVo msmVo = new MsmVo();
msmVo.setPhone(orderInfo.getPatientPhone());
msmVo.setTemplateCode("SMS_194640721");
String reserveDate =
new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")
+ (orderInfo.getReserveTime()==0 ? "上午": "下午");
Map<String,Object> param = new HashMap<String,Object>(){{
put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
put("amount", orderInfo.getAmount());
put("reserveDate", reserveDate);
put("name", orderInfo.getPatientName());
put("quitTime", new DateTime(orderInfo.getQuitTime()).toString("yyyy-MM-dd HH:mm"));
}};
msmVo.setParam(param);
orderMqVo.setMsmVo(msmVo);
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER, MqConst.ROUTING_ORDER, orderMqVo);
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
} else {
throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());
}
return orderInfo.getId();
}
用户使用前台–订单管理页面
页面效果:用户使用前台–订单管理页面
共需开发两个接口:①订单列表接口②订单详情接口
①订单列表接口 OrderApiController
页面效果:订单列表接口
接口、实现类、Controller
接口 OrderInfoService
/**
* 分页列表
*/
IPage<OrderInfo> selectPage(Page<OrderInfo> pageParam, OrderQueryVo orderQueryVo);
实现类 OrderInfoServiceImpl
//订单列表(条件查询带分页)
@Override
public IPage<OrderInfo> selectPage(Page<OrderInfo> pageParam, OrderQueryVo orderQueryVo) {
//orderQueryVo获取条件值
String name = orderQueryVo.getKeyword(); //医院名称
Long patientId = orderQueryVo.getPatientId(); //就诊人名称
String orderStatus = orderQueryVo.getOrderStatus(); //订单状态
String reserveDate = orderQueryVo.getReserveDate();//安排时间
String createTimeBegin = orderQueryVo.getCreateTimeBegin();
String createTimeEnd = orderQueryVo.getCreateTimeEnd();
//对条件值进行非空判断
QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
if(!StringUtils.isEmpty(name)) {
wrapper.like("hosname",name);
}
if(!StringUtils.isEmpty(patientId)) {
wrapper.eq("patient_id",patientId);
}
if(!StringUtils.isEmpty(orderStatus)) {
wrapper.eq("order_status",orderStatus);
}
if(!StringUtils.isEmpty(reserveDate)) {
wrapper.ge("reserve_date",reserveDate);
}
if(!StringUtils.isEmpty(createTimeBegin)) {
wrapper.ge("create_time",createTimeBegin);
}
if(!StringUtils.isEmpty(createTimeEnd)) {
wrapper.le("create_time",createTimeEnd);
}
//调用mapper的方法
IPage<OrderInfo> pages = baseMapper.selectPage(pageParam, wrapper);
//编号变成对应值封装
pages.getRecords().stream().forEach(item -> {
this.packOrderInfo(item);
});
return pages;
}
private OrderInfo packOrderInfo(OrderInfo orderInfo) {
orderInfo.getParam().put("orderStatusString", OrderStatusEnum.getStatusNameByStatus(orderInfo.getOrderStatus()));
return orderInfo;
}
Controller:OrderApiController
//订单列表(条件查询带分页)
@GetMapping("auth/{page}/{limit}")
public Result list(@PathVariable Long page,
@PathVariable Long limit,
OrderQueryVo orderQueryVo, HttpServletRequest request) {
//设置当前用户id
orderQueryVo.setUserId(AuthContextHolder.getUserId(request));
Page<OrderInfo> pageParam = new Page<>(page,limit);
IPage<OrderInfo> pageModel =
orderService.selectPage(pageParam,orderQueryVo);
return Result.ok(pageModel);
}
@ApiOperation(value = "获取订单状态")
@GetMapping("auth/getStatusList")
public Result getStatusList() {
return Result.ok(OrderStatusEnum.getStatusList());
}
//说明:订单状态我们是封装到枚举中的,页面搜索需要一个下拉列表展示,所以我们通过接口返回页面
②订单详情接口 OrderApiController
页面详情:订单详情接口
接口、实现类、Controller
接口 OrderInfoService
/**
* 获取订单详情
*/
OrderInfo getOrderInfo(Long id);
实现类 OrderInfoServiceImpl
//根据订单id查询订单详情
@Override
public OrderInfo getOrder(String orderId) {
OrderInfo orderInfo = baseMapper.selectById(orderId);
return this.packOrderInfo(orderInfo);
}
Controller:OrderApiController
//根据订单id查询订单详情
@GetMapping("auth/getOrders/{orderId}")
public Result getOrders(@PathVariable String orderId) {
OrderInfo orderInfo = orderService.getOrder(orderId);
return Result.ok(orderInfo);
}
③订单列表接口 OrderController
Controller:在OrderController添加
@Api(tags = "订单接口")
@RestController
@RequestMapping("/admin/order/orderInfo")
public class OrderController {
@Autowired
private OrderService orderService;
@ApiOperation(value = "获取分页列表")
@GetMapping("{page}/{limit}")
public Result index(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Long page,
@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Long limit,
@ApiParam(name = "orderCountQueryVo", value = "查询对象", required = false) OrderQueryVo orderQueryVo) {
Page<OrderInfo> pageParam = new Page<>(page, limit);
IPage<OrderInfo> pageModel = orderService.selectPage(pageParam, orderQueryVo);
return Result.ok(pageModel);
}
@ApiOperation(value = "获取订单状态")
@GetMapping("getStatusList")
public Result getStatusList() {
return Result.ok(OrderStatusEnum.getStatusList());
}
}
④订单详情接口(OrderController)
接口、实现类、Controller
接口 OrderInfoService
/**
* 订单详情
* @param orderId
* @return
*/
Map<String,Object> show(Long orderId);
实现类 OrderInfoServiceImpl
@Override
public Map<String, Object> show(Long orderId) {
Map<String, Object> map = new HashMap<>();
OrderInfo orderInfo = this.packOrderInfo(this.getById(orderId));
map.put("orderInfo", orderInfo);
Patient patient
= patientFeignClient.getPatient(orderInfo.getPatientId());
map.put("patient", patient);
return map;
}
Controller:OrderController
@ApiOperation(value = "获取订单")
@GetMapping("show/{id}")
public Result get(
@ApiParam(name = "orderId", value = "订单id", required = true)
@PathVariable Long id) {
return Result.ok(orderService.show(id));
}
参考资料:B站-尚硅谷-尚医通项目 https://www.bilibili.com/video/BV1V5411K7rT?p=1