9.java项目-尚医通(9)

前端代码: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中看到记录
注:使用阿里短信测试服务是不能发验证码以外内容的

猜你喜欢

转载自blog.csdn.net/hutc_Alan/article/details/126220350