前端代码:https://github.com/wyj41/yygh_html.git
后端代码:https://github.com/wyj41/yygh_parent.git
1.预约挂号详情
需求分析
1、接口分析
(1)根据预约周期,展示可预约日期数据,按分页展示
(2)选择日期展示当天可预约列表(该接口后台已经实现过)
2、页面展示分析
(1)分页展示可预约日期,根据有号、无号、约满等状态展示不同颜色,以示区分
(2)可预约最后一个日期为即将放号日期,根据放号时间页面展示倒计时
1.1 api接口
操作模块service_hosp
1.1.1 添加service接口
在ScheduleService类添加接口
//获取可预约排班数据
Map<String,Object> getBookingScheduleRule(Integer page, Integer limit, String hoscode, String depcode);
在ScheduleServiceImpl类实现接口
//获取可预约排班数据
@Override
public Map<String,Object> getBookingScheduleRule(Integer page, Integer limit, String hoscode, String depcode) {
Map<String,Object> result = new HashMap<>();
//获取预约规则
//根据医院编号获取预约规则
Hospital hospital = hospitalService.getByHoscode(hoscode);
if(hospital == null){
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> aggregateResult =
mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
List<BookingScheduleRuleVo> scheduleVoList = aggregateResult.getMappedResults();
//合并数据 map集合 key日期 value预约规则和剩余数量等
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);
//从map集合根据key日期获取value值
BookingScheduleRuleVo bookingScheduleRuleVo = scheduleVoMap.get(date);
//如果当天没有排班医生
if(bookingScheduleRuleVo == null){
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.getByHoscode(hoscode).getHosname());
//科室
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 getListDate(Integer page, Integer limit, BookingRule bookingRule) {
//获取当天放号时间 年 月 日 时 分
DateTime releaseTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
//获取预约周期
Integer 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天数据,超过7天分页
List<Date> pageDateList = new ArrayList<>();
int start = (page-1) * limit;
int end = page * limit;
//如果可以显式数据小于7,直接显示
if(end > dateList.size()){
end = dateList.size();
}
for(int i = start;i < end;i++){
pageDateList.add(dateList.get(i));
}
//如果可以显式数据大于7,进行分页
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;
}
1.1.2 获取科室信息
在DepartmentService类添加接口
//根据科室编号和医院编号,查询科室
Department getDepartment(String hoscode, String depcode);
在DepartmentImpl类实现接口
//根据科室编号和医院编号,查询科室
@Override
public Department getDepartment(String hoscode, String depcode) {
return departmentRepository.getDepartmentByHoscodeAndDepcode(hoscode,depcode);
}
1.1.3 添加controller方法
在HospApiController类添加方法
@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.2 前端
操作:yygh_site
1.2.1 封装api请求
在/api/hosp.js添加方法
//获取可预约信息(分页)
getBookingScheduleRule(page, limit, hoscode, depcode) {
return request({
url: `${
api_name}/auth/getBookingScheduleRule/${
page}/${
limit}/${
hoscode}/${
depcode}`,
method: 'get'
})
},
//获取排班信息
findScheduleList(hoscode, depcode, workDate) {
return request({
url: `${
api_name}/auth/findScheduleList/${
hoscode}/${
depcode}/${
workDate}`,
method: 'get'
})
}
1.2.2 页面展示
修改路由跳转
修改/page/hosp/_hoscode.vue第155行
window.location.href = '/hosp/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
创建/pages/hosp/schedule.vue组件
<template>
<!-- header -->
<div class="nav-container page-component">
<!--左侧导航 #start -->
<div class="nav left-nav">
<div class="nav-item selected">
<span class="v-link selected dark" :οnclick="'javascript:window.location=\'/hosp/'+hoscode+'\''">预约挂号 </span>
</div>
<div class="nav-item ">
<span class="v-link clickable dark" :οnclick="'javascript:window.location=\'/hosp/detail/'+hoscode+'\''"> 医院详情 </span>
</div>
<div class="nav-item">
<span class="v-link clickable dark" :οnclick="'javascript:window.location=\'/hosp/notice/'+hoscode+'\''"> 预约须知 </span>
</div>
<div class="nav-item "><span
class="v-link clickable dark"> 停诊信息 </span>
</div>
<div class="nav-item "><span
class="v-link clickable dark"> 查询/取消 </span>
</div>
</div>
<!-- 左侧导航 #end -->
<!-- 右侧内容 #start -->
<div class="page-container">
<div class="hospital-source-list">
<div class="header-wrapper" style="justify-content:normal">
<span class="v-link clickable" @click="show()">{
{ baseMap.hosname}}</span>
<div class="split"></div>
<div>{
{ baseMap.bigname }}</div>
</div>
<div class="title mt20"> {
{ baseMap.depname }}</div>
<!-- 号源列表 #start -->
<div class="mt60">
<div class="title-wrapper">{
{ baseMap.workDateString }}</div>
<div class="calendar-list-wrapper">
<!-- item.depNumber == -1 ? 'gray space' : item.depNumber == 0 ? 'gray' : 'small small-space'-->
<!-- selected , index == activeIndex ? 'selected' : ''-->
<div :class="'calendar-item '+item.curClass" style="width: 124px;"
v-for="(item, index) in bookingScheduleList" :key="item.id"
@click="selectDate(item, index)">
<div class="date-wrapper"><span>{
{ item.workDate }}</span><span class="week">{
{ item.dayOfWeek }}</span></div>
<div class="status-wrapper" v-if="item.status == 0">{
{ item.availableNumber == -1 ? '无号' : item.availableNumber == 0 ? '约满' : '有号' }}</div>
<div class="status-wrapper" v-if="item.status == 1">即将放号</div>
<div class="status-wrapper" v-if="item.status == -1">停止挂号</div>
</div>
</div>
<!-- 分页 -->
<el-pagination
class="pagination"
layout="prev, pager, next"
:current-page="page"
:total="total"
:page-size="limit"
@current-change="getPage">
</el-pagination>
</div>
<!-- 即将放号 #start-->
<div class="countdown-wrapper mt60" v-if="!tabShow">
<div class="countdonw-title"> {
{ time }}<span class="v-link selected">{
{ baseMap.releaseTime }} </span>放号</div>
<div class="countdown-text"> 倒 计 时
<div>
<span class="number">{
{ timeString }}</span>
</div>
</div>
</div>
<!-- 即将放号 #end-->
<!-- 号源列表 #end -->
<!-- 上午号源 #start -->
<div class="mt60" v-if="tabShow">
<div class="">
<div class="list-title">
<div class="block"></div>
上午号源
</div>
<div v-for="item in scheduleList" :key="item.id" v-if="item.workTime == 0">
<div class="list-item">
<div class="item-wrapper">
<div class="title-wrapper">
<div class="title">{
{ item.title }}</div>
<div class="split"></div>
<div class="name"> {
{ item.docname }}</div>
</div>
<div class="special-wrapper">{
{ item.skill }}</div>
</div>
<div class="right-wrapper">
<div class="fee"> ¥{
{ item.amount }}
</div>
<div class="button-wrapper">
<div class="v-button" @click="booking(item.id, item.availableNumber)" :style="item.availableNumber == 0 || pageFirstStatus == -1 ? 'background-color: #7f828b;' : ''">
<span>剩余<span class="number">{
{ item.availableNumber }}</span></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 上午号源 #end -->
<!-- 下午号源 #start -->
<div class="mt60" v-if="tabShow">
<div class="">
<div class="list-title">
<div class="block"></div>
下午号源
</div>
<div v-for="item in scheduleList" :key="item.id" v-if="item.workTime == 1">
<div class="list-item">
<div class="item-wrapper">
<div class="title-wrapper">
<div class="title">{
{ item.title }}</div>
<div class="split"></div>
<div class="name"> {
{ item.docname }}</div>
</div>
<div class="special-wrapper">{
{ item.skill }}</div>
</div>
<div class="right-wrapper">
<div class="fee"> ¥{
{ item.amount }}
</div>
<div class="button-wrapper">
<div class="v-button" @click="booking(item.id, item.availableNumber)" :style="item.availableNumber == 0 || pageFirstStatus == -1 ? 'background-color: #7f828b;' : ''">
<span>剩余<span class="number">{
{ item.availableNumber }}</span></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 下午号源 #end -->
</div>
</div>
<!-- 右侧内容 #end -->
</div>
<!-- footer -->
</template>
<script>
import '~/assets/css/hospital_personal.css'
import '~/assets/css/hospital.css'
import hospitalApi from '@/api/hosp'
export default {
data() {
return {
hoscode: null,
depcode: null,
workDate: null,
bookingScheduleList: [],
scheduleList : [],
baseMap : {},
nextWorkDate: null, // 下一页第一个日期
preWorkDate: null, // 上一页第一个日期
tabShow: true, //挂号列表与即将挂号切换
activeIndex: 0,
page: 1, // 当前页
limit: 7, // 每页个数
total: 1, // 总页码
timeString: null,
time: '今天',
timer: null,
pageFirstStatus: 0 // 第一页第一条数据状态
}
},
created() {
this.hoscode = this.$route.query.hoscode
this.depcode = this.$route.query.depcode
this.workDate = this.getCurDate()
this.getBookingScheduleRule()
},
methods: {
getPage(page = 1) {
this.page = page
this.workDate = null
this.activeIndex = 0
this.getBookingScheduleRule()
},
getBookingScheduleRule() {
hospitalApi.getBookingScheduleRule(this.page, this.limit, this.hoscode, this.depcode).then(response => {
this.bookingScheduleList = response.data.bookingScheduleList
this.total = response.data.total
this.baseMap = response.data.baseMap
this.dealClass()
// 分页后workDate=null,默认选中第一个
if (this.workDate == null) {
this.workDate = this.bookingScheduleList[0].workDate
}
//判断当天是否停止预约 status == -1 停止预约
if(this.workDate == this.getCurDate()) {
this.pageFirstStatus = this.bookingScheduleList[0].status
} else {
this.pageFirstStatus = 0
}
this.findScheduleList()
})
},
findScheduleList() {
hospitalApi.findScheduleList(this.hoscode, this.depcode, this.workDate).then(response => {
this.scheduleList = response.data
})
},
selectDate(item, index) {
this.workDate = item.workDate
this.activeIndex = index
//清理定时
if(this.timer != null) clearInterval(this.timer)
// 是否即将放号
if(item.status == 1) {
this.tabShow = false
// 放号时间
let releaseTime = new Date(this.getCurDate() + ' ' + this.baseMap.releaseTime).getTime()
let nowTime = new Date().getTime();
this.countDown(releaseTime, nowTime)
this.dealClass();
} else {
this.tabShow = true
this.getBookingScheduleRule()
}
},
dealClass() {
//处理样式
for (let i = 0; i < this.bookingScheduleList.length; i++) {
// depNumber -1:无号 0:约满 >0:有号
let curClass = this.bookingScheduleList[i].availableNumber == -1 ? 'gray space' : this.bookingScheduleList[i].availableNumber == 0 ? 'gray' : 'small small-space'
curClass += i == this.activeIndex ? ' selected' : ''
this.bookingScheduleList[i].curClass = curClass
}
},
getCurDate() {
let datetime = new Date()
let year = datetime.getFullYear()
let month = datetime.getMonth() + 1 < 10 ? '0' + (datetime.getMonth() + 1) : datetime.getMonth() + 1
let date = datetime.getDate() < 10 ? '0' + datetime.getDate() : datetime.getDate()
return year + '-' + month + '-' + date
},
countDown(releaseTime, nowTime) {
//计算倒计时时长
let secondes = 0;
if(releaseTime > nowTime) {
this.time = '今天'
//当前时间到放号时间的时长
secondes = Math.floor((releaseTime - nowTime) / 1000);
} else {
this.time = '明天'
//计算明天放号时间
let releaseDate = new Date(releaseTime)
releaseTime = new Date(releaseDate.setDate(releaseDate.getDate() + 1)).getTime()
//当前时间到明天放号时间的时长
secondes = Math.floor((releaseTime - nowTime) / 1000);
}
//定时任务
this.timer = setInterval(() => {
secondes = secondes - 1
if(secondes <= 0) {
clearInterval(timer);
this.init()
}
this.timeString = this.convertTimeString(secondes)
}, 1000);
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once('hook:beforeDestroy', () => {
clearInterval(timer);
})
},
convertTimeString(allseconds) {
if(allseconds <= 0) return '00:00:00'
// 计算天数
let days = Math.floor(allseconds / (60 * 60 * 24));
// 小时
let hours = Math.floor((allseconds - (days * 60 * 60 * 24)) / (60 * 60));
// 分钟
let minutes = Math.floor((allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60)) / 60);
// 秒
let seconds = allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60) - (minutes * 60);
//拼接时间
let timString = "";
if (days > 0) {
timString = days + "天:";
}
return timString += hours + " 时 " + minutes + " 分 " + seconds + " 秒 ";
},
show() {
window.location.href = '/hospital/' + this.hoscode
},
booking(scheduleId, availableNumber) {
debugger
if(availableNumber == 0 || this.pageFirstStatus == -1) {
this.$message.error('不能预约')
} else {
window.location.href = '/hospital/booking?scheduleId=' + scheduleId
}
}
}
}
</script>
测试
http://localhost:3000/hosp/schedule?hoscode=1000_0&depcode=200040878
问题:没有当前时间的排班数据
解决方案:添加部分数据
1.修改workDate为你当前时间及后面几天的时间
2.启动hospital-manage项目
访问:http://localhost:9998/
在排班列表中将下面修改好的排班信息添加
[
{
"hoscode":"1000_0","depcode":"200040878","title":"医师","docname":"邵迎红","skill":"内分泌科常见病。","workDate":"2022-08-04","workTime":0,"reservedNumber":33,"availableNumber":22,"amount":"100","status":1,"hosScheduleId":"115"},
{
"hoscode":"1000_0","depcode":"200040878","title":"副主任医师","docname":"裴育","skill":"骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病。","workDate":"2022-08-04","workTime":0,"reservedNumber":40,"availableNumber":6,"amount":"100","status":1,"hosScheduleId":"116"},
{
"hoscode":"1000_0","depcode":"200040878","title":"副主任医师","docname":"邵迎红","skill":"内分泌与代谢性疾病。","workDate":"2022-08-04","workTime":1,"reservedNumber":27,"availableNumber":10,"amount":"100","status":1,"hosScheduleId":"117"},
{
"hoscode":"1000_0","depcode":"200040878","title":"医师","docname":"邵迎红","skill":"内分泌科常见病。","workDate":"2022-08-05","workTime":0,"reservedNumber":33,"availableNumber":22,"amount":"100","status":1,"hosScheduleId":"118"},
{
"hoscode":"1000_0","depcode":"200040878","title":"副主任医师","docname":"裴育","skill":"骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病。","workDate":"2022-08-05","workTime":0,"reservedNumber":40,"availableNumber":6,"amount":"100","status":1,"hosScheduleId":"118"},
{
"hoscode":"1000_0","depcode":"200040878","title":"副主任医师","docname":"邵迎红","skill":"内分泌与代谢性疾病。","workDate":"2022-08-05","workTime":1,"reservedNumber":27,"availableNumber":10,"amount":"100","status":1,"hosScheduleId":"120"},
{
"hoscode":"1000_0","depcode":"200040878","title":"医师","docname":"邵迎红","skill":"内分泌科常见病。","workDate":"2022-08-06","workTime":0,"reservedNumber":33,"availableNumber":22,"amount":"100","status":1,"hosScheduleId":"115"},
{
"hoscode":"1000_0","depcode":"200040878","title":"副主任医师","docname":"裴育","skill":"骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病。","workDate":"2022-08-06","workTime":0,"reservedNumber":40,"availableNumber":6,"amount":"100","status":1,"hosScheduleId":"121"},
{
"hoscode":"1000_0","depcode":"200040878","title":"副主任医师","docname":"邵迎红","skill":"内分泌与代谢性疾病。","workDate":"2022-08-06","workTime":1,"reservedNumber":27,"availableNumber":10,"amount":"100","status":1,"hosScheduleId":"122"},
]
2.预约确认
1、根据排班id获取排班信息,在页面展示
2、选择就诊人
3、预约下单
2.1 api接口
操作模块:service_hosp
2.1.1 添加service接口
在ScheduleService类添加接口
//根据排班id获取排班数据
Schedule getScheduleById(String scheduleId);
在ScheduleServiceImpl类添加实现
//根据排班id获取排班数据
@Override
public Schedule getScheduleById(String scheduleId) {
Schedule schedule = scheduleRepository.findById(scheduleId).get();
this.packageSchedule(schedule);
return schedule;
}
2.1.2 添加controller方法
在HospApiController类添加方法
@ApiOperation(value = "根据排班id获取排班数据")
@GetMapping("getSchedule/{scheduleId}")
public Result getSchedule(@PathVariable String scheduleId){
Schedule schedule = scheduleService.getScheduleById(scheduleId);
return Result.ok(schedule);
}
2.2 前端
操作模块:yygh-site
1.在/api/hosp.js添加方法
//根据排班id获取排班信息
getScheduleById(id) {
return request({
url: `${
api_name}/getSchedule/${
id}`,
method: 'get'
})
}
2.修改路由跳转路径
修改/pages/hosp/schedule.vue的第297行
window.location.href = '/hosp/booking?scheduleId=' + scheduleId
3.创建/pages/hospital/booking.vue组件
<template>
<!-- header -->
<div class="nav-container page-component">
<!--左侧导航 #start -->
<div class="nav left-nav">
<div class="nav-item selected">
<span class="v-link selected dark" :οnclick="'javascript:window.location=\'/hospital/'+schedule.hoscode+'\''">预约挂号 </span>
</div>
<div class="nav-item ">
<span class="v-link clickable dark" :οnclick="'javascript:window.location=\'/hospital/detail/'+schedule.hoscode+'\''"> 医院详情 </span>
</div>
<div class="nav-item">
<span class="v-link clickable dark" :οnclick="'javascript:window.location=\'/hospital/notice/'+schedule.hoscode+'\''"> 预约须知 </span>
</div>
<div class="nav-item "><span
class="v-link clickable dark"> 停诊信息 </span>
</div>
<div class="nav-item "><span
class="v-link clickable dark"> 查询/取消 </span>
</div>
</div>
<!-- 左侧导航 #end -->
<!-- 右侧内容 #start -->
<div class="page-container">
<div class="hospital-order">
<div class="header-wrapper">
<div class="title mt20"> 确认挂号信息</div>
<div>
<div class="sub-title">
<div class="block"></div>
选择就诊人:
</div>
<div class="patient-wrapper">
<div >
<div class="v-card clickable item ">
<div class="inline" v-for="(item,index) in patientList" :key="item.id"
@click="selectPatient(index)" style="margin-right: 10px;">
<!-- 选中 selected 未选中去掉selected-->
<div :class="activeIndex == index ? 'item-wrapper selected' : 'item-wrapper'">
<div>
<div class="item-title">{
{ item.name }}</div>
<div>{
{ item.param.certificatesTypeString }}</div>
<div>{
{ item.certificatesNo }}</div>
</div>
<img src="//img.114yygh.com/static/web/checked.png" class="checked">
</div>
</div>
</div>
</div>
<div class="item space add-patient v-card clickable">
<div class="">
<div class="item-add-wrapper" @click="addPatient()"> +
添加就诊人
</div>
</div>
</div>
<div class="el-loading-mask" style="display: none;">
<div class="el-loading-spinner">
<svg viewBox="25 25 50 50" class="circular">
<circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
</svg>
</div>
</div>
</div>
<!-- 就诊人,选中显示 -->
<div class="sub-title" v-if="patientList.length > 0">
<div class="block"></div>
选择就诊卡: <span class="card-tips"><span
class="iconfont"></span> 如您持社保卡就诊,请务必选择医保预约挂号,以保证正常医保报销</span>
</div>
<el-card class="patient-card" shadow="always" v-if="patientList.length > 0">
<div slot="header" class="clearfix">
<div><span class="name"> {
{ patient.name }} {
{ patient.certificatesNo }} 居民身份证</span></div>
</div>
<div class="card SELF_PAY_CARD">
<div class="info"><span class="type">{
{ patient.isInsure == 0 ? '自费' : '医保'}}</span><span class="card-no">{
{ patient.certificatesNo }}</span><span
class="card-view">居民身份证</span></div>
<span class="operate"></span></div>
<div class="card">
<div class="text bind-card"></div>
</div>
</el-card>
<div class="sub-title">
<div class="block"></div>
挂号信息
</div>
<div class="content-wrapper">
<el-form ref="form">
<el-form-item label="就诊日期:">
<div class="content"><span>{
{ schedule.workDate }} {
{ schedule.param.dayOfWeek }} {
{ schedule.workTime == 0 ? '上午' : '下午' }}</span></div>
</el-form-item>
<el-form-item label="就诊医院:">
<div class="content"><span>{
{ schedule.param.hosname }} </span></div>
</el-form-item>
<el-form-item label="就诊科室:">
<div class="content"><span>{
{ schedule.param.depname }} </span></div>
</el-form-item>
<el-form-item label="医生姓名:">
<div class="content"><span>{
{ schedule.docname }} </span></div>
</el-form-item>
<el-form-item label="医生职称:">
<div class="content"><span>{
{ schedule.title }} </span></div>
</el-form-item>
<el-form-item label="医生专长:">
<div class="content"><span>{
{ schedule.skill }}</span></div>
</el-form-item>
<el-form-item label="医事服务费:">
<div class="content">
<div class="fee">{
{ schedule.amount }}元</div>
</div>
</el-form-item>
</el-form>
</div>
<!-- 用户信息 #start-->
<div>
<div class="sub-title">
<div class="block"></div>
用户信息
</div>
<div class="content-wrapper">
<el-form ref="form" :model="form">
<el-form-item class="form-item" label="就诊人手机号:">
{
{ patient.phone }}
</el-form-item>
</el-form>
</div>
</div>
<!-- 用户信息 #end -->
<div class="bottom-wrapper">
<div class="button-wrapper">
<div class="v-button" @click="submitOrder()">{
{ submitBnt }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧内容 #end -->
</div>
<!-- footer -->
</template>
<script>
import '~/assets/css/hospital_personal.css'
import '~/assets/css/hospital.css'
import hospitalApi from '@/api/hosp'
import patientApi from '@/api/patient'
export default {
data() {
return {
scheduleId: null,
schedule: {
param: {}
},
patientList: [],
patient: {},
activeIndex: 0,
submitBnt: '确认挂号'
}
},
created() {
this.scheduleId = this.$route.query.scheduleId
this.init()
},
methods: {
init() {
this.getSchedule()
this.findPatientList()
},
getSchedule() {
hospitalApi.getScheduleById(this.scheduleId).then(response => {
this.schedule = response.data
})
},
findPatientList() {
patientApi.findList().then(response => {
this.patientList = response.data
if(this.patientList.length > 0) {
this.patient = this.patientList[0]
}
})
},
selectPatient(index) {
this.activeIndex = index;
this.patient = this.patientList[index]
},
submitOrder() {
},
addPatient() {
window.location.href = '/patient/add'
}
}
}
</script>
<style>
.hospital-order .header-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: block !important;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.hospital-order .sub-title {
letter-spacing: 1px;
color: #999;
margin-top: 60px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.hospital-order .content-wrapper .content {
color: #333;
}
.el-form-item {
margin-bottom: 5px;
}
.hospital-order .content-wrapper {
margin-left: 140px;
margin-top: 20px;
}
</style>
4.测试
选择一个有号的,有剩余号的点击,查看是否显示信息
3.预约下单
需求分析
参考《尚医通API接口文档.docx》业务接口5.1预约下单
下单参数:就诊人id与排班id
1、下单我们要获取就诊人信息
2、获取排班下单信息与规则信息
3、获取医院签名信息,然后通过接口去医院预约下单
4、下单成功更新排班信息与发送短信
3.1 的搭建service_order模块
在service模块下创建service_order子模块
3.1.1 修改配置
修改pom.xml,引入依赖
<dependencies>
<dependency>
<groupId>com.myproject</groupId>
<artifactId>service_cmn_client</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
添加配置文件application.yml
server:
port: 8206
spring:
application:
name: service-order
profiles:
active: dev
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.50.224:3306/yygh_order?characterEncoding=utf-8&useSSL=false
username: root
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
data:
mongodb:
uri: mongodb://192.168.50.224:27017/yygh_hosp
cloud:
nacos:
server-addr: 127.0.0.1:8848
3.1.2 启动类
com.myproject.yygh.order.ServiceOrderApplication
package com.myproject.yygh.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {
"com.myproject"})
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {
"com.myproject"})
public class ServiceOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOrderApplication.class, args);
}
}
3.1.3 配置网关
- id: service-order
uri: lb://service-order
predicates:
- name: Path
args:
- /*/order/**
3.2 添加订单基础类
1.添加model
说明:由于实体对象没有逻辑,我们已经统一导入
model模块中的com.myproject.yygh.model.order.OrderInfo
2.添加Mapper
com.myproject.yygh.order.mapper.OrderMapper
package com.myproject.yygh.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.myproject.yygh.model.order.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper extends BaseMapper<OrderInfo> {
}
3.添加service接口及实现类
添加com.myproject.yygh.order.service.OrderService接口
package com.myproject.yygh.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.myproject.yygh.model.order.OrderInfo;
public interface OrderService extends IService<OrderInfo> {
//生成挂号订单
Long saveOrder(String scheduleId, Long patientId);
}
添加com.myproject.yygh.order.service.impl.OrderServiceImpl实现类
package com.myproject.yygh.order.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myproject.yygh.model.order.OrderInfo;
import com.myproject.yygh.order.mapper.OrderMapper;
import com.myproject.yygh.order.service.OrderService;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService {
//生成挂号订单
@Override
public Long saveOrder(String scheduleId, Long patientId) {
return null;
}
}
4.添加controller
com.myproject.yygh.order.api.OrderApiController
package com.myproject.yygh.order.api;
import com.myproject.yygh.common.result.Result;
import com.myproject.yygh.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/order/orderInfo")
public class OrderApiController {
@Autowired
private OrderService orderService;
//生成挂号订单
@PostMapping("auth/submitOrder/{scheduleId}/{patientId}")
public Result saveOrder(@PathVariable String scheduleId,
@PathVariable Long patientId){
Long orderId = orderService.saveOrder(scheduleId,patientId);
return Result.ok(orderId);
}
}
3.3 封装Feign调用获取就诊人接口
3.3.1 获取就诊人信息api接口
操作模块:service_user
在PatientApiController类添加方法
//根据就诊人id获取就诊人信息
@GetMapping("inner/get/{id}")
public Patient getPatientOrder(@PathVariable Long id){
Patient patient = patientService.getPatientById(id);
return patient;
}
3.3.2 搭建service_user_client模块
在service_client模块下创建service-user_client子模块
1.添加依赖
在pom.xml中添加
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
<dependency>
<groupId>com.myproject</groupId>
<artifactId>model</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
2.添加Feign接口类
package com.myproject.yygh.user.client;
import com.myproject.yygh.model.user.Patient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-user")
@Repository
public interface PatientFeignClient {
//获取就诊人信息
@GetMapping("/api/user/patient/inner/get/{id}")
public Patient getPatientOrder(@PathVariable("id") Long id);
}
3.4 封装Feign调用获取排班下单信息接口
3.4.1 获取排班下单信息api接口
操作模块:service-hosp
1.添加service接口与实现
在ScheduleService类添加接口
//根据排班id获取预约下单数据
ScheduleOrderVo getScheduleOrderVo(String scheduleId);
在ScheduleServiceImpl类添加实现
//根据排班id获取预约下单数据
@Override
public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
//获取排班信息
Schedule schedule = scheduleRepository.findById(scheduleId).get();
if(schedule == null){
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//获取预约规则信息
Hospital hospital = hospitalService.getByHoscode(schedule.getHoscode());
if(hospital == null){
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
BookingRule bookingRule = hospital.getBookingRule();
if(bookingRule == null){
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//把获取数据设置到scheduleOrderVo中
scheduleOrderVo.setHoscode(schedule.getHoscode());
scheduleOrderVo.setHosname(hospital.getHosname());
scheduleOrderVo.setDepcode(schedule.getDepcode());
scheduleOrderVo.setDepname((String) 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(stopTime.toDate());
return scheduleOrderVo;
}
2.添加controller方法
在HospApiController类添加方法
@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);
}
3.4.2 获取下单引用签名信息接口
操作模块:service-hosp
1.添加service接口与实现
在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;
}
2.添加controller方法
@Autowired
private HospitalSetService hospitalSetService;
@ApiOperation(value = "获取医院签名信息")
@GetMapping("inner/getSignInfoVo/{hoscode}")
public SignInfoVo getSignInfoVo(
@ApiParam(name = "hoscode", value = "医院code", required = true)
@PathVariable("hoscode") String hoscode) {
return hospitalSetService.getSignInfoVo(hoscode);
}
3.4.3 搭建service_hosp_client模块
在service_client模块中创建service_hosp_client子模块
1.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
<dependency>
<groupId>com.myproject</groupId>
<artifactId>model</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
2.添加Feign接口类
com.myproject.yygh.hosp.client.HospitalFeignClient
package com.myproject.yygh.hosp.client;
import com.myproject.yygh.vo.hosp.ScheduleOrderVo;
import com.myproject.yygh.vo.order.SignInfoVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@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);
}
3.5 实现下单接口
操作模块:service_order
1.引入依赖
<dependency>
<groupId>com.myproject</groupId>
<artifactId>service_user_client</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.myproject</groupId>
<artifactId>service_hosp_client</artifactId>
<version>1.0</version>
</dependency>
2.实现下单接口
修改OrderServiceImpl类下单方法
@Autowired
private PatientFeignClient patientFeignClient;
@Autowired
private HospitalFeignClient hospitalFeignClient;
//生成挂号订单
@Override
public Long saveOrder(String scheduleId, Long patientId) {
//获取就诊人信息
Patient patient = patientFeignClient.getPatientOrder(patientId);
//获取排班相关信息
ScheduleOrderVo scheduleOrderVo = hospitalFeignClient.getScheduleOrderVo(scheduleId);
//判断当前时间是否还可以预约
if(new DateTime(scheduleOrderVo.getStartTime()).isAfterNow()
|| new DateTime(scheduleOrderVo.getEndTime()).isBeforeNow()){
throw new YyghException(ResultCodeEnum.TIME_NO);
}
//获取签名信息
SignInfoVo signInfoVo = hospitalFeignClient.getSignInfoVo(scheduleOrderVo.getHoscode());
//添加到订单表
OrderInfo orderInfo = new OrderInfo();
//scheduleOrderVo 数据复制到 orderInfo
BeanUtils.copyProperties(scheduleOrderVo,orderInfo);
//向orderInfo设置其他数据
String outTradeNo = System.currentTimeMillis() + ""+ new Random().nextInt(100);
orderInfo.setOutTradeNo(outTradeNo);
orderInfo.setScheduleId(scheduleOrderVo.getHosScheduleId());
orderInfo.setUserId(patient.getUserId());
orderInfo.setPatientId(patientId);
orderInfo.setPatientName(patient.getName());
orderInfo.setPatientPhone(patient.getPhone());
orderInfo.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());
baseMapper.insert(orderInfo);
//调用医院接口,实现预约挂号操作
//设置调用医院接口需要参数,参数放到map集合
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();
}
3.6 预约成功后处理逻辑
预约成功后我们要更新预约数和短信提醒预约成功,为了提高下单的并发性,这部分逻辑我们就交给mq为我们完成,预约成功发送消息即可
3.6.1 RabbitMQ
RabbitMQ简介
以商品订单场景为例,
如果商品服务和订单服务是两个不同的微服务,在下单的过程中订单服务需要调用商品服务进行扣库存操作。按照传统的方式,下单过程要等到调用完毕之后才能返回下单成功,如果网络产生波动等原因使得商品服务扣库存延迟或者失败,会带来较差的用户体验,如果在高并发的场景下,这样的处理显然是不合适的,那怎么进行优化呢?这就需要消息队列登场了。
消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通的时候在将消息转发给相应的应用程序或者服务,当然前提是这些服务订阅了该队列。如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。
RabbitMQ就是这样一款消息队列。RabbitMQ是一个开源的消息代理的队列服务器,用来通过普通协议在完全不同的应用之间共享数据。
典型应用场景
1.异步处理。把消息放入消息中间件中,等到需要的时候再去处理。
2.流量削峰。例如秒杀活动,在短时间内访问量急剧增加,使用消息队列,当消息队列满了就拒绝响应,跳转到错误页面,这样就可以使得系统不会因为超负载而崩溃。
3.日志处理
4.应用解耦
Debian安装RabbitMQ
官网参考:https://rabbitmq.com/install-debian.html#apt-packagecloud
rabbitmq安装脚本(不是国内镜像,因此安装有点慢,而且容易报错,多试几次)
#!/usr/bin/sh
sudo apt-get install curl gnupg apt-transport-https -y
## Team RabbitMQ's main signing key
curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.rabbitmq.team.gpg > /dev/null
## Launchpad PPA that provides modern Erlang releases
curl -1sLf "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xf77f1eda57ebb1cc" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg > /dev/null
## PackageCloud RabbitMQ repository
curl -1sLf "https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/io.packagecloud.rabbitmq.gpg > /dev/null
## Add apt repositories maintained by Team RabbitMQ
sudo tee /etc/apt/sources.list.d/rabbitmq.list <<EOF
## Provides modern Erlang/OTP releases
##
deb [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.E495BB49CC4BBE5B.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/deb/debian buster main
deb-src [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.E495BB49CC4BBE5B.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/deb/debian buster main
## Provides RabbitMQ
##
deb [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.9F4587F226208342.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/deb/debian buster main
deb-src [signed-by=/usr/share/keyrings/io.cloudsmith.rabbitmq.9F4587F226208342.gpg] https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/deb/debian buster main
EOF
## Update package indices
sudo apt-get update -y
## Install Erlang packages
sudo apt-get install -y erlang-base \
erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
erlang-runtime-tools erlang-snmp erlang-ssl \
erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl
## Install rabbitmq-server and its dependencies
sudo apt-get install rabbitmq-server -y --fix-missing
#启动rabbitmq-server
systemctl start rabbitmq-server.service
#查看rabbitmq-server状态
systemctl status rabbitmq-server.service
#安装管理插件
rabbitmq-plugins enable rabbitmq_management
# 为RabbitMQ添加用户并授权
#sudo rabbitmqctl add_user [username] [password]
#sudo rabbitmqctl set_user_tags [username] administrator
#sudo rabbitmqctl set_permissions -p / [username] '.*' '.*' '.*'
rabbitmqctl add_user admin admin
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin '.*' '.*' '.*'
# 启用stomp和management插件
sudo rabbitmq-plugins enable rabbitmq_management
sudo rabbitmq-plugins enable rabbitmq_web_stomp
sudo rabbitmq-plugins enable rabbitmq_web_stomp_examples
管理后台(IP为安装rabbitmq的服务器IP):http://IP:15672
3.6.2 rabbit-util模块封装
由于后续可能多个模块都会使用mq,所以我们把它封装成一个模块,需要的地方直接引用即可
在common模块中创建rabbit_util子模块
1.修改pom.xml
添加依赖
<dependencies>
<!--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>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
2.封装service方法
package com.myproject.common.rabbit.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.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;
}
}
3.配置mq消息转换器
package com.myproject.common.rabbit.config;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MQConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
3.6.3 封装短信接口
操作模块:service-msm
1.引入依赖
<dependency>
<groupId>com.myproject</groupId>
<artifactId>rabbit_util</artifactId>
<version>1.0</version>
</dependency>
2.添加配置
spring:
rabbitmq:
host: 192.168.50.224
port: 5672
username: admin
password: admin
3.添加常量配置
在rabbit-util模块com.myproject.common.rabbit.constant.MqConst类添加
package com.myproject.common.rabbit.constant;
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";
}
4.在model模块封装短信实体
model模块中:已统一导入
com.myproject.yygh.vo.msm.MsmVo
5.封装service接口
在MsmService类添加接口
//mq发送短信
boolean send(MsmVo msmVo);
在MsmServiceImpl类添加接口实现
//mq发送短信
@Override
public boolean send(MsmVo msmVo) {
if(!StringUtils.isEmpty(msmVo.getPhone())){
String code = (String) msmVo.getParam().get("code");
boolean issend = this.send(msmVo.getPhone(),code);
return issend;
}
return false;
}
6.封装mq监听器
com.myproject.yygh.msm.receiver.MsmReceiver
package com.myproject.yygh.msm.receiver;
import com.myproject.common.rabbit.constant.MqConst;
import com.myproject.yygh.msm.service.MsmService;
import com.myproject.yygh.vo.msm.MsmVo;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MsmReceiver {
@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);
}
}
3.6.4 封装更新排班数量
操作模块:service-hosp
1.引入依赖
<dependency>
<groupId>com.myproject</groupId>
<artifactId>rabbit_util</artifactId>
<version>1.0</version>
</dependency>
2.添加配置
spring:
rabbitmq:
host: 192.168.50.224
port: 5672
username: admin
password: admin
3.在model模块封装更新排班实体
com.myproject.yygh.vo.order.OrderMqVo
说明:已统一引入,该对象放一个短信实体,预约下单成功后,我们发送一条消息,让mq来保证两个消息都发送成功
4.封装service接口
在ScheduleService类添加接口
//更新排班数据,用于mq
void update(Schedule schedule);
在ScheduleServiceImpl类添加接口实现
//更新排班数据,用于mq
@Override
public void update(Schedule schedule) {
schedule.setUpdateTime(new Date());
scheduleRepository.save(schedule);
}
5.封装mq监听器
com.myproject.yygh.hosp.receiver.HospitalReceiver
package com.myproject.yygh.hosp.receiver;
import com.myproject.common.rabbit.constant.MqConst;
import com.myproject.common.rabbit.service.RabbitService;
import com.myproject.yygh.hosp.service.ScheduleService;
import com.myproject.yygh.model.hosp.Schedule;
import com.myproject.yygh.vo.msm.MsmVo;
import com.myproject.yygh.vo.order.OrderMqVo;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@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.getScheduleById(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);
}
}
}
3.6.5 调整下单接口
操作模块:service-order
1.引入依赖
<dependency>
<groupId>com.myproject</groupId>
<artifactId>rabbit_util</artifactId>
<version>1.0</version>
</dependency>
2.添加配置
spring:
rabbitmq:
host: 192.168.50.224
port: 5672
username: admin
password: admin
3.修改下单接口
@Autowired
private RabbitService rabbitService;
//生成挂号订单
@Override
public Long saveOrder(String scheduleId, Long patientId) {
...
//请求医院系统接口
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信息,更新号源和短信通知
//发送mq号源更新
OrderMqVo orderMqVo = new OrderMqVo();
orderMqVo.setScheduleId(scheduleId);
orderMqVo.setReservedNumber(reservedNumber);
orderMqVo.setAvailableNumber(availableNumber);
//短信提示
MsmVo msmVo = new MsmVo();
msmVo.setPhone(orderInfo.getPatientPhone());
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();
}
4.修改MsmServiceImpl的send方法
//mq发送短信
@Override
public boolean send(MsmVo msmVo) {
if(!StringUtils.isEmpty(msmVo.getPhone())){
boolean isdend = this.send(msmVo.getPhone(),msmVo.getParam());
return isdend;
}
return false;
}
//mq发送短信封装
private boolean send(String phone, Map<String,Object> param) {
//判断手机号是否为空
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_154950909");
//验证码 使用json格式 {"code":"123456"}
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;
}
3.7 前端整合
操作:yygh-site
1.封装api请求
添加/api/orderInfo.js文件,定义下单接口
import request from '@/utils/request'
const api_name = `/api/order/orderInfo`
export default {
saveOrders(scheduleId, patientId) {
return request({
url: `${
api_name}/auth/submitOrder/${
scheduleId}/${
patientId}`,
method: 'post'
})
}
}
2.页面修改
在/pages/hosp/booking.vue组件完善下单方法
引入接口
import orderApi from '@/api/orderInfo'
修改submitOrder方法
//生成订单
submitOrder() {
if(this.submitBnt == "正在提交"){
this.$message.error("不能重复提交")
return
}
this.submitBnt = "正在提交"
orderApi.saveOrders(this.scheduleId,this.patient.id)
.then(response => {
let orderId = response.data //订单id
window.location.href='/order/show?orderId='+orderId
}).catch(e => {
this.submitBnt = "确认挂号"
})
}
修改bug
1.修改model模块中的com.myproject.yygh.model.order.OrderInfo
命名错误,数据库的表中字段名为“hos_schedule_id”
@ApiModelProperty(value = "排班id")
@TableField("hos_schedule_id")
private String scheduleId;
2.修改加密方式
service_util模块中
com.myproject.yygh.common.helper.HttpRequestHelper
修改getSign方法
public static String getSign(Map<String, Object> paramMap, String signKey) {
if(paramMap.containsKey("sign")) {
paramMap.remove("sign");
}
log.info("加密前:" + signKey.toString());
String md5Str = MD5.encrypt(signKey.toString());
log.info("加密后:" + md5Str);
return md5Str;
}
3.修改数据库的数据
将mysql中yygh_hosp数据库的hospital_set表的“北京协和医院的apu_url”改为“http://localhost:9998”
4.修改hospital_manage的配置文件,将IP修改为自己服务器的IP
测试提交订单功能
测试提交功能是否报错,若不报错并跳转到空白页面则成功
并且可以在http://IP:15672/#/queues
中看到记录
注:使用阿里短信测试服务是不能发验证码以外内容的