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

前端代码:https://github.com/wyj41/yygh_html.git
后端代码:https://github.com/wyj41/yygh_parent.git

1.用户订单管理

1.1 订单详情

1.1.1 api接口

操作模块:service_order

1.添加service接口及实现类
在OrderService类添加接口

//根据订单id查询订单详情
OrderInfo getOrder(String orderId);

在OrderServiceImpl类添加接口实现

@Override
public OrderInfo getOrder(String orderId) {
    
    
    OrderInfo orderInfo = baseMapper.selectById(orderId);

    return this.packOrderInfo(orderInfo);
}
private OrderInfo packOrderInfo(OrderInfo orderInfo){
    
    
    orderInfo.getParam().put("orderStatusString",OrderStatusEnum.getStatusNameByStatus(orderInfo.getOrderStatus()));
    return orderInfo;
}

2.添加controller
在OrderApiController类添加方法

//根据订单id查询订单详情
@GetMapping("auth/getOrders/{orderId}")
public Result getOrder(@PathVariable String orderId){
    
    
    OrderInfo orderInfo = orderService.getOrder(orderId);
    return Result.ok(orderInfo);
}

1.1.2 前端

操作:yygh-site

1.封装api请求
在/api/orderInfo.js添加方法

//订单详情
getOrders(orderId){
    
    
    return request({
    
    
      url: `${
      
      api_name}/auth/getOrders/${
      
      orderId}`,
      method: 'get'
    })
}

2.页面展示
创建/pages/order/show.vue组件

<template>
  <!-- header -->
  <div class="nav-container page-component">
    <!--左侧导航 #start -->
    <div class="nav left-nav">
      <div class="nav-item ">
        <span class="v-link clickable dark" οnclick="javascript:window.location='/user'">实名认证 </span>
      </div>
      <div class="nav-item selected">
        <span class="v-link selected dark" οnclick="javascript:window.location='/order'"> 挂号订单 </span>
      </div>
      <div class="nav-item ">
        <span class="v-link clickable dark" οnclick="javascript:window.location='/patient'"> 就诊人管理 </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="order-detail">
        <div class="title"> 挂号详情</div>
        <div class="status-bar">
          <div class="left-wrapper">
            <div class="status-wrapper BOOKING_SUCCESS">
              <span class="iconfont"></span> {
   
   { orderInfo.param.orderStatusString }}
            </div>
          </div>
          <div class="right-wrapper">
            <img src="//img.114yygh.com/static/web/code_order_detail.png" class="code-img">
            <div class="content-wrapper">
              <div> 微信<span class="iconfont"></span>关注“北京114预约挂号”</div>
              <div class="watch-wrapper"> 快速挂号,轻松就医</div>
            </div>
          </div>
        </div>
        <div class="info-wrapper">
          <div class="title-wrapper">
            <div class="block"></div>
            <div>挂号信息</div>
          </div>
          <div class="info-form">
            <el-form ref="form" :model="form">
              <el-form-item label="就诊人信息:">
                <div class="content"><span>{
   
   { orderInfo.patientName }}</span></div>
              </el-form-item>
              <el-form-item label="就诊日期:">
                <div class="content"><span>{
   
   { orderInfo.reserveDate }} {
   
   { orderInfo.reserveTime == 0 ? '上午' : '下午' }}</span></div>
              </el-form-item>
              <el-form-item label="就诊医院:">
                <div class="content"><span>{
   
   { orderInfo.hosname }} </span></div>
              </el-form-item>
              <el-form-item label="就诊科室:">
                <div class="content"><span>{
   
   { orderInfo.depname }} </span></div>
              </el-form-item>
              <el-form-item label="医生职称:">
                <div class="content"><span>{
   
   { orderInfo.title }} </span></div>
              </el-form-item>
              <el-form-item label="医事服务费:">
                <div class="content">
                  <div class="fee">{
   
   { orderInfo.amount }}元
                  </div>
                </div>
              </el-form-item>
              <el-form-item label="挂号单号:">
                <div class="content"><span>{
   
   { orderInfo.outTradeNo }} </span></div>
              </el-form-item>
              <el-form-item label="挂号时间:">
                <div class="content"><span>{
   
   { orderInfo.createTime }}</span></div>
              </el-form-item>
            </el-form>
          </div>
        </div>
        <div class="rule-wrapper mt40">
          <div class="rule-title"> 注意事项</div>
          <div>1、请确认就诊人信息是否准确,若填写错误将无法取号就诊,损失由本人承担;<br>
            <span style="color:red">2、【取号】就诊当天需在{
   
   { orderInfo.fetchTime }}在医院取号,未取号视为爽约,该号不退不换;</span><br>
            3、【退号】在{
   
   { orderInfo.quitTime }}前可在线退号 ,逾期将不可办理退号退费;<br>
            4、北京114预约挂号支持自费患者使用身份证预约,同时支持北京市医保患者使用北京社保卡在平台预约挂号。请于就诊当日,携带预约挂号所使用的有效身份证件到院取号;<br>
            5、请注意北京市医保患者在住院期间不能使用社保卡在门诊取号。
          </div>
        </div>
        <div class="bottom-wrapper mt60" v-if="orderInfo.orderStatus == 0 || orderInfo.orderStatus == 1">
          <div class="button-wrapper">
            <div class="v-button white" @click="cancelOrder()">取消预约</div>
          </div>
          <div class="button-wrapper ml20" v-if="orderInfo.orderStatus == 0">
            <div class="v-button" @click="pay()">支付</div>
          </div>
        </div>
      </div>
    </div>
    <!-- 右侧内容 #end -->
    <!-- 微信支付弹出框 -->
    <el-dialog :visible.sync="dialogPayVisible" style="text-align: left" :append-to-body="true" width="500px" @close="closeDialog">
      <div class="container">
        <div class="operate-view" style="height: 350px;">
          <div class="wrapper wechat">
            <div>
              <img src="images/weixin.jpg" alt="">

              <div style="text-align: center;line-height: 25px;margin-bottom: 40px;">
                请使用微信扫一扫<br/>
                扫描二维码支付
              </div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
  <!-- footer -->
</template>
<script>
import '~/assets/css/hospital_personal.css'
import '~/assets/css/hospital.css'
import orderInfoApi from '@/api/orderInfo'
export default {
  data() {
    return {
      orderId: null,
      orderInfo: {
        param: {}
      },
      dialogPayVisible: false,
      payObj: {},
      timer: null  // 定时器名称
    }
  },
  created() {
    this.orderId = this.$route.query.orderId
    this.init()
  },
  methods: {
    init() {
      orderInfoApi.getOrders(this.orderId).then(response => {
        console.log(response.data);
        this.orderInfo = response.data
      })
    }
  }
}
</script>
<style>
.info-wrapper {
  padding-left: 0;
  padding-top: 0;
}
.content-wrapper {
  color: #333;
  font-size: 14px;
  padding-bottom: 0;
}
.bottom-wrapper {
  width: 100%;
}
.button-wrapper {
  margin: 0;
}
.el-form-item {
  margin-bottom: 5px;
}
.bottom-wrapper .button-wrapper {
  margin-top: 0;
}
</style>

1.2 订单列表

操作模块:service_order

1.2.1 api接口

1.添加service接口及实现类
在OrderService类添加接口

//订单列表(条件查询带分页)
IPage<OrderInfo> selectPage(Page<OrderInfo> pageParam, OrderQueryVo orderQueryVo);

在OrderServiceImpl类添加接口实现

//订单列表(条件查询带分页)
@Override
public IPage<OrderInfo> selectPage(Page<OrderInfo> pageParam, OrderQueryVo orderQueryVo) {
    
    
    //orderQueryVo获取条件
    String name = orderQueryVo.getKeyword(); //医院名称
    Long patientId = orderQueryVo.getPatientId(); //就诊人id
    String orderStatus = orderQueryVo.getOrderStatus(); //订单状态
    String reserveDate = orderQueryVo.getReserveDate();//安排时间
    String createTimeBegin = orderQueryVo.getCreateTimeBegin();
    String createTimeEnd = orderQueryVo.getCreateTimeEnd();

    //对条件值进行非空判断
    QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
    wrapper.eq("user_id",orderQueryVo.getUserId());
    if(!StringUtils.isEmpty(name)) {
    
    
        wrapper.like("hosname",name);
    }
    if(!StringUtils.isEmpty(patientId)) {
    
    
        wrapper.eq("patient_id",patientId);
    }
    if(!StringUtils.isEmpty(orderStatus)) {
    
    
        wrapper.eq("order_status",orderStatus);
    }
    if(!StringUtils.isEmpty(reserveDate)) {
    
    
        wrapper.le("reserve_date",reserveDate);
    }
    if(!StringUtils.isEmpty(createTimeBegin)) {
    
    
        wrapper.ge("create_time",createTimeBegin);
    }
    if(!StringUtils.isEmpty(createTimeEnd)) {
    
    
        wrapper.le("create_time",createTimeEnd);
    }
    //调用mapper的方法
    Page<OrderInfo> pages = baseMapper.selectPage(pageParam, wrapper);
    //编号变成对应封装
    pages.getRecords().stream().forEach(item -> {
    
    
        this.packOrderInfo(item);
    });

    return pages;
}

2.添加controller
在OrderApiController类添加方法

//订单列表(条件查询带分页)
@GetMapping("auth/{page}/{limit}")
public Result list(@PathVariable Long page,
                   @PathVariable Long limit,
                   OrderQueryVo orderQueryVo,
                   HttpServletRequest request){
    
    
    //设置当前用户id
    orderQueryVo.setUserId(AuthContextHolder.getUserId(request));
    Page<OrderInfo> pageParam = new Page<>(page,limit);
    IPage<OrderInfo> pageModel = orderService.selectPage(pageParam,orderQueryVo);
    return Result.ok(pageModel);
}

@ApiOperation(value = "获取订单状态")
@GetMapping("auth/getStatusList")
public Result getStatusList() {
    
    
    return Result.ok(OrderStatusEnum.getStatusList());
}

1.2.2 前端

1.封装api请求
在/api/orderInfo.js添加方法

//订单列表
getPageList(page,limit,searchObj){
    
    
    return request({
    
    
      url: `${
      
      api_name}/auth/${
      
      page}/${
      
      limit}`,
      method: 'get',
      params: searchObj
    })
},
//查询订单状态
getStatusList() {
    
    
    return request({
    
    
      url: `${
      
      api_name}/auth/getStatusList`,
      method: 'get'
    })
}

2.页面展示
创建/pages/order/index.vue组件

<template>
  <!-- header -->
  <div class="nav-container page-component">
    <!--左侧导航 #start -->
    <div class="nav left-nav">
      <div class="nav-item ">
        <span class="v-link clickable dark" onclick="javascript:window.location='/user'">实名认证 </span>
      </div>
      <div class="nav-item selected">
        <span class="v-link selected dark" onclick="javascript:window.location='/order'"> 挂号订单 </span>
      </div>
      <div class="nav-item ">
        <span class="v-link clickable dark" onclick="javascript:window.location='/patient'"> 就诊人管理 </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="personal-order">
        <div class="title"> 挂号订单</div>
        <el-form :inline="true">
          <el-form-item label="就诊人:">
            <el-select v-model="searchObj.patientId" placeholder="请选择就诊人" class="v-select patient-select">
              <el-option
                v-for="item in patientList"
                :key="item.id"
                :label="item.name + '【' + item.certificatesNo + '】'"
                :value="item.id">
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="订单状态:" style="margin-left: 80px">
            <el-select v-model="searchObj.orderStatus" placeholder="全部" class="v-select patient-select" style="width: 200px;">
              <el-option
                v-for="item in statusList"
                :key="item.status"
                :label="item.comment"
                :value="item.status">
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="text" class="search-button v-link highlight clickable selected" @click="fetchData()">
              查询
            </el-button>
          </el-form-item>
        </el-form>
        <div class="table-wrapper table">
          <el-table
            :data="list"
            stripe
            style="width: 100%">
            <el-table-column
              label="就诊时间"
              width="120">
              <template slot-scope="scope">
                {
    
    {
    
     scope.row.reserveDate }} {
    
    {
    
     scope.row.reserveTime === 0 ? '上午' : '下午' }}
              </template>
            </el-table-column>
            <el-table-column
              prop="hosname"
              label="医院"
              width="100">
            </el-table-column>
            <el-table-column
              prop="depname"
              label="科室">
            </el-table-column>
            <el-table-column
              prop="title"
              label="医生">
            </el-table-column>
            <el-table-column
              prop="amount"
              label="医事服务费">
            </el-table-column>
            <el-table-column
              prop="patientName"
              label="就诊人">
            </el-table-column>
            <el-table-column
              prop="param.orderStatusString"
              label="订单状态">
            </el-table-column>
            <el-table-column label="操作">
              <template slot-scope="scope">
                <el-button type="text" class="v-link highlight clickable selected" @click="show(scope.row.id)">详情</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <!-- 分页 -->
        <el-pagination
          class="pagination"
          layout="prev, pager, next"
          :current-page="page"
          :total="total"
          :page-size="limit"
          @current-change="fetchData">
        </el-pagination>
      </div>
    </div>
    <!-- 右侧内容 #end -->
  </div>
  <!-- footer -->
</template>
<script>
import '~/assets/css/hospital_personal.css'
import '~/assets/css/hospital.css'
import orderInfoApi from '@/api/orderInfo'
import patientApi from '@/api/patient'
export default {
    
    
  data() {
    
    
    return {
    
    
      list: [], // banner列表
      total: 0, // 数据库中的总记录数
      page: 1, // 默认页码
      limit: 10, // 每页记录数
      searchObj: {
    
    }, // 查询表单对象
      patientList: [],
      statusList: []
    }
  },
  created() {
    
    
    this.orderId = this.$route.query.orderId
    this.fetchData()
    this.findPatientList()
    this.getStatusList()
  },
  methods: {
    
    
    fetchData(page = 1) {
    
    
      this.page = page
      orderInfoApi.getPageList(this.page, this.limit, this.searchObj).then(response => {
    
    
        console.log(response.data);
        this.list = response.data.records
        this.total = response.data.total
      })
    },
    findPatientList() {
    
    
      patientApi.findList().then(response => {
    
    
        this.patientList = response.data
      })
    },
    getStatusList() {
    
    
      orderInfoApi.getStatusList().then(response => {
    
    
        this.statusList = response.data
      })
    },
    changeSize(size) {
    
    
      console.log(size)
      this.limit = size
      this.fetchData(1)
    },
    show(id) {
    
    
      window.location.href = '/order/show?orderId=' + id
    }
  }
}
</script>

注:平台订单管理和用户订单管理接口基本一样,因此不再操作

2.微信支付

2.1 微信支付简介

微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

申请步骤:(了解)
第一步:注册公众号(类型须为:服务号)
请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型。

第二步:认证公众号
公众号认证后才可申请微信支付,认证费:300元/年。

第三步:提交资料申请微信支付
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行验证
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:在线签署协议
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

开发文档
微信支付接口调用的整体思路:
按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html

  1. appid:微信公众账号或开放平台APP的唯一标识
  2. mch_id:商户号 (配置文件中的partner)
  3. partnerkey:商户密钥
  4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

2.2 微信支付开发

2.2.1 api接口

场景:用户扫描商户展示在各种场景的二维码进行支付
使用案例:
线下:家乐福超市、7-11便利店、上品折扣线下店等
线上:大众点评网站、携程网站、唯品会、美丽说网站等

开发模式:
模式一:商户在后台给你生成二维码,用户打开扫一扫
模式二:商户后台系统调用微信支付【统一下单API】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。
微信支付:生成xml发送请求

操作模块:service-order

1.引入依赖

<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

2.添加配置
在application.yml中添加商户信息

spring:
  redis:
    timeout: 1800000
    lettuce:
      pool:
        max-active: 20
        max-wait: -1
        max-idle: 5
        min-idle: 0
weixin:
  appid: wx74862e0dfcf69954
  partner: 1558950191
  partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

3.引入工具类
com.myproject.yygh.order.utils.ConstantPropertiesUtils

package com.myproject.yygh.order.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ConstantPropertiesUtils implements InitializingBean {
    
    

    @Value("${weixin.appid}")
    private String appid;

    @Value("${weixin.partner}")
    private String partner;

    @Value("${weixin.partnerkey}")
    private String partnerkey;

    public static String APPID;
    public static String PARTNER;
    public static String PARTNERKEY;
    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        APPID = appid;
        PARTNER = partner;
        PARTNERKEY = partnerkey;
    }
}

com.myproject.yygh.order.utils.HttpClient

package com.myproject.yygh.order.utils;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * http请求客户端
 */
public class HttpClient {
    
    
    private String url;
    private Map<String, String> param;
    private int statusCode;
    private String content;
    private String xmlParam;
    private boolean isHttps;
    private boolean isCert = false;
    //证书密码 微信商户号(mch_id)
    private String certPassword;
    public boolean isHttps() {
    
    
        return isHttps;
    }
    public void setHttps(boolean isHttps) {
    
    
        this.isHttps = isHttps;
    }
    public boolean isCert() {
    
    
        return isCert;
    }
    public void setCert(boolean cert) {
    
    
        isCert = cert;
    }
    public String getXmlParam() {
    
    
        return xmlParam;
    }
    public void setXmlParam(String xmlParam) {
    
    
        this.xmlParam = xmlParam;
    }
    public HttpClient(String url, Map<String, String> param) {
    
    
        this.url = url;
        this.param = param;
    }
    public HttpClient(String url) {
    
    
        this.url = url;
    }
    public String getCertPassword() {
    
    
        return certPassword;
    }
    public void setCertPassword(String certPassword) {
    
    
        this.certPassword = certPassword;
    }
    public void setParameter(Map<String, String> map) {
    
    
        param = map;
    }
    public void addParameter(String key, String value) {
    
    
        if (param == null)
            param = new HashMap<String, String>();
        param.put(key, value);
    }
    public void post() throws ClientProtocolException, IOException {
    
    
        HttpPost http = new HttpPost(url);
        setEntity(http);
        execute(http);
    }
    public void put() throws ClientProtocolException, IOException {
    
    
        HttpPut http = new HttpPut(url);
        setEntity(http);
        execute(http);
    }
    public void get() throws ClientProtocolException, IOException {
    
    
        if (param != null) {
    
    
            StringBuilder url = new StringBuilder(this.url);
            boolean isFirst = true;
            for (String key : param.keySet()) {
    
    
                if (isFirst)
                    url.append("?");
                else
                    url.append("&");
                url.append(key).append("=").append(param.get(key));
            }
            this.url = url.toString();
        }
        HttpGet http = new HttpGet(url);
        execute(http);
    }
    /**
     * set http post,put param
     */
    private void setEntity(HttpEntityEnclosingRequestBase http) {
    
    
        if (param != null) {
    
    
            List<NameValuePair> nvps = new LinkedList<NameValuePair>();
            for (String key : param.keySet())
                nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
            http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
        }
        if (xmlParam != null) {
    
    
            http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
        }
    }
    private void execute(HttpUriRequest http) throws ClientProtocolException,
            IOException {
    
    
        CloseableHttpClient httpClient = null;
        try {
    
    
            if (isHttps) {
    
    
                if(isCert) {
    
    
                    //TODO 需要完善
                    FileInputStream inputStream = new FileInputStream(new File(""));
                    KeyStore keystore = KeyStore.getInstance("PKCS12");
                    char[] partnerId2charArray = certPassword.toCharArray();
                    keystore.load(inputStream, partnerId2charArray);
                    SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
                    SSLConnectionSocketFactory sslsf =
                            new SSLConnectionSocketFactory(sslContext,
                                    new String[] {
    
     "TLSv1" },
                                    null,
                                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
                    httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
                } else {
    
    
                    SSLContext sslContext = new SSLContextBuilder()
                            .loadTrustMaterial(null, new TrustStrategy() {
    
    
                                // 信任所有
                                public boolean isTrusted(X509Certificate[] chain,
                                                         String authType)
                                        throws CertificateException {
    
    
                                    return true;
                                }
                            }).build();
                    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                            sslContext);
                    httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
                            .build();
                }
            } else {
    
    
                httpClient = HttpClients.createDefault();
            }
            CloseableHttpResponse response = httpClient.execute(http);
            try {
    
    
                if (response != null) {
    
    
                    if (response.getStatusLine() != null)
                        statusCode = response.getStatusLine().getStatusCode();
                    HttpEntity entity = response.getEntity();
                    // 响应内容
                    content = EntityUtils.toString(entity, Consts.UTF_8);
                }
            } finally {
    
    
                response.close();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            httpClient.close();
        }
    }
    public int getStatusCode() {
    
    
        return statusCode;
    }
    public String getContent() throws ParseException, IOException {
    
    
        return content;
    }
}

2.2.2 添加交易记录接口

1.添加Mapper
com.myproject.yygh.order.mapper.PaymentMapper

package com.myproject.yygh.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.myproject.yygh.model.order.PaymentInfo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface PaymentMapper extends BaseMapper<PaymentInfo> {
    
    
}

2.添加service接口与实现
com.myproject.yygh.order.service.PaymentService

package com.myproject.yygh.order.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.myproject.yygh.model.order.OrderInfo;
import com.myproject.yygh.model.order.PaymentInfo;

public interface PaymentService extends IService<PaymentInfo> {
    
    
    //向支付记录表添加信息
    void savePaymentInfo(OrderInfo orderInfo, Integer status);
}

com.myproject.yygh.order.service.impl.PaymentServiceImpl

package com.myproject.yygh.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myproject.yygh.enums.PaymentStatusEnum;
import com.myproject.yygh.model.order.OrderInfo;
import com.myproject.yygh.model.order.PaymentInfo;
import com.myproject.yygh.order.mapper.PaymentMapper;
import com.myproject.yygh.order.service.PaymentService;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, PaymentInfo> implements PaymentService {
    
    
    //向支付记录表添加信息
    @Override
    public void savePaymentInfo(OrderInfo orderInfo, Integer paymentType) {
    
    
        //根据订单id和支付类型,查询支付记录表是否存在相同订单
        QueryWrapper<PaymentInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("order_id",orderInfo.getId());
        wrapper.eq("payment_type",paymentType);
        Long count = baseMapper.selectCount(wrapper);
        if(count > 0){
    
    
            return;
        }
        //添加记录
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setCreateTime(new Date());
        paymentInfo.setOrderId(orderInfo.getId());
        paymentInfo.setPaymentType(paymentType);
        paymentInfo.setOutTradeNo(orderInfo.getOutTradeNo());
        paymentInfo.setPaymentStatus(PaymentStatusEnum.UNPAID.getStatus());
        String subject = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")+"|"+orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle();
        paymentInfo.setSubject(subject);
        paymentInfo.setTotalAmount(orderInfo.getAmount());
        baseMapper.insert(paymentInfo);
    }
}

*3.添加支付service接口与实现
com.myproject.yygh.order.service.WeixinService

package com.myproject.yygh.order.service;

import java.util.Map;

public interface WeixinService {
    
    
    //生成微信支付扫描的二维码
    Map createNative(Long orderId);
}

com.myproject.yygh.order.service.impl.WeixinServiceImpl

package com.myproject.yygh.order.service.impl;

import com.github.wxpay.sdk.WXPayUtil;
import com.myproject.yygh.enums.PaymentTypeEnum;
import com.myproject.yygh.model.order.OrderInfo;
import com.myproject.yygh.order.service.OrderService;
import com.myproject.yygh.order.service.PaymentService;
import com.myproject.yygh.order.service.WeixinService;
import com.myproject.yygh.order.utils.ConstantPropertiesUtils;
import com.myproject.yygh.order.utils.HttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class WeixinServiceImpl implements WeixinService {
    
    
    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private RedisTemplate redisTemplate;

    //生成微信支付扫描的二维码
    @Override
    public Map createNative(Long orderId) {
    
    
        try {
    
    
            //从redis获取数据
            Map payMap = (Map) redisTemplate.opsForValue().get(orderId.toString());
            if(payMap != null){
    
    
                return payMap;
            }

            //1.根据orderId获取订单信息
            OrderInfo orderInfo = orderService.getById(orderId);
            //2.向支付记录表添加信息
            paymentService.savePaymentInfo(orderInfo, PaymentTypeEnum.WEIXIN.getStatus());

            //3.设置参数
            //吧参数转换xml格式,使用商户key进行加密
            Map paramMap = new HashMap<>();
            paramMap.put("appid", ConstantPropertiesUtils.APPID);
            paramMap.put("mch_id", ConstantPropertiesUtils.PARTNER);
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
            String body = orderInfo.getReserveDate() + "就诊"+ orderInfo.getDepname();
            paramMap.put("body", body);
            paramMap.put("out_trade_no", orderInfo.getOutTradeNo());
            //paramMap.put("total_fee", order.getAmount().multiply(new BigDecimal("100")).longValue()+"");
            paramMap.put("total_fee", "1");//为了测试,统一携程这个值
            paramMap.put("spbill_create_ip", "127.0.0.1");
            paramMap.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify");
            paramMap.put("trade_type", "NATIVE");

            //4.调用微信生成二维码接口,httpclient调用
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            //设置map参数
            client.setXmlParam(WXPayUtil.generateSignedXml(paramMap,ConstantPropertiesUtils.PARTNERKEY));
            client.setHttps(true);
            client.post();

            //6.返回相关数据
            String xml = client.getContent();
            //转换成map集合
            Map<String,String> resultMap = WXPayUtil.xmlToMap(xml);
            System.out.println("resultMap:" + resultMap);

            //4、封装返回结果集
            Map map = new HashMap<>();
            map.put("orderId", orderId);
            map.put("totalFee", orderInfo.getAmount());
            map.put("resultCode", resultMap.get("result_code"));
            map.put("codeUrl", resultMap.get("code_url"));//二维码地址

            //微信支付二维码2小时过期,可采取2小时未支付取消订单
            if(resultMap.get("resultCode") != null){
    
    
                redisTemplate.opsForValue().set(orderId.toString(),map,120, TimeUnit.MINUTES);
            }

            return map;

        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }
}

4.添加controller方法
com.myproject.yygh.order.api.WeiXinController

package com.myproject.yygh.order.api;

import com.myproject.yygh.common.result.Result;
import com.myproject.yygh.order.service.WeixinService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/order/weixin")
public class WeiXinController {
    
    
    @Autowired
    private WeixinService weixinService;

    //生成微信支付扫描的二维码
    @GetMapping("createNative/{orderId}")
    public Result createNative(@PathVariable Long orderId){
    
    
        Map map = weixinService.createNative(orderId);
        return Result.ok(map);
    }
}

2.2 前端

2.2.1 封装api请求

添加/api/weixin.js文件

//生成微信支付的二维码
createNative(orderId) {
    
    
    return request({
    
    
      url: `/api/order/weixin/createNative/${
      
      orderId}`,
      method: 'get'
    })
},
//查询支付状态的接口
queryPayStatus(orderId) {
    
    
    return request({
    
    
      url: `/api/order/weixin/queryPayStatus/${
      
      orderId}`,
      method: 'get'
    })
}

2.2.2 引入vue-qriously生成二维码

安装vue-qriously

npm install vue-qriously

引入
在/plugins/myPlugin.js文件添加引入

import VueQriously from 'vue-qriously'
Vue.use(VueQriously)

2.2.3 页面展示

修改/pages/order/show.vue组件

1.将104行的微信图片改为显示二维码

<qriously :value="payObj.codeUrl" :size="220"/>

2.在script中引入api接口

import weixinApi from '@/api/weixin'

3.添加方法

//生成支付二维码
pay(){
  //支付二维码弹框显示
  this.dialogPayVisible = true
  weixinApi.createNative(this.orderId)
    .then(response => {
      this.payObj = response.data
      if(this.payObj.codeUrl == ''){//生成失败
        this.dialogPayVisible = false
        this.$message.error('支付错误')
      }else{
        //每个3秒调用查询支付状态接口
        this.timer = setInterval(() => {
          this.queryPayStatus(this.orderId)
        },3000);
      }
    })
},
//查询支付状态的方法
queryPayStatus(orderId) {
  weixinApi.queryPayStatus(orderId).then(response => {
    if(response.message == '支付中'){
      return;
    }
    //清除定时器
    clearInterval(this.timer)
    window.location.reload()
  })
},
closeDialog(){
  if(this.timer){
    clearInterval(this.timer);
  }
}

说明:我们只有轮询查看支付状态,接下来我们处理支付查询接口

测试功能,查看订单点击“支付”后是否显示二维码

2.3 处理支付结果

操作模块:yygh_order

2.3.1 支付查询

1.添加service接口与实现
在WeixinService类添加接口

//调用微信接口实现支付状态查询
Map<String, String> queryPayStatus(Long orderId);

在WeixinServiceImpl类添加实现

//调用微信接口实现支付状态查询
@Override
public Map<String, String> queryPayStatus(Long orderId) {
    
    
    try{
    
    
        //1.根据orderId获取订单信息
        OrderInfo orderInfo = orderService.getById(orderId);

        //2.封装提交参数
        Map paramMap = new HashMap<>();
        paramMap.put("appid", ConstantPropertiesUtils.APPID);
        paramMap.put("mch_id", ConstantPropertiesUtils.PARTNER);
        paramMap.put("out_trade_no", orderInfo.getOutTradeNo());
        paramMap.put("nonce_str", WXPayUtil.generateNonceStr());

        //3.设置请求内容
        HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
        client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantPropertiesUtils.PARTNERKEY));
        client.setHttps(true);
        client.post();

        //4.得到微信接口返回数据
        String xml = client.getContent();
        Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
        System.out.println("支付状态resultMap:" + resultMap);

        //5.把接口数据返回
        return resultMap;
    }catch (Exception e){
    
    
        return null;
    }
}

2.3.2 处理支付成功逻辑

1.添加service接口与实现
在PaymentService类添加接口

//更新订单状态
void paySuccess(String out_trade_no, Map<String, String> resultMap);

在PaymentServiceImpl类添加实现

@Autowired
private OrderService orderService;

@Autowired
private HospitalFeignClient hospitalFeignClient;

//更新订单状态
@Override
public void paySuccess(String out_trade_no, Map<String, String> resultMap) {
    
    
    //1.根据订单编号得到支付记录
    QueryWrapper<PaymentInfo> wrapper = new QueryWrapper<>();
    wrapper.eq("out_trade_no", out_trade_no);
    wrapper.eq("payment_type", PaymentTypeEnum.WEIXIN.getStatus());
    PaymentInfo paymentInfo = baseMapper.selectOne(wrapper);

    //2.更新支付记录信息
    paymentInfo.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());
    paymentInfo.setTradeNo(resultMap.get("transaction_id"));
    paymentInfo.setCallbackTime(new Date());
    paymentInfo.setCallbackContent(resultMap.toString());
    baseMapper.updateById(paymentInfo);

    //3.根据订单号得到订单信息
    //4.更新订单信息
    OrderInfo orderInfo = orderService.getById(paymentInfo.getOrderId());
    orderInfo.setOrderStatus(OrderStatusEnum.PAID.getStatus());
    orderService.updateById(orderInfo);

    //5.调用医院接口,更新订单信息
    SignInfoVo signInfoVo = hospitalFeignClient.getSignInfoVo(orderInfo.getHoscode());
    Map<String,Object> reqMap = new HashMap<>();
    reqMap.put("hoscode",orderInfo.getHoscode());
    reqMap.put("hosRecordId",orderInfo.getHosRecordId());
    reqMap.put("timestamp", HttpRequestHelper.getTimestamp());
    String sign = HttpRequestHelper.getSign(reqMap, signInfoVo.getSignKey());
    reqMap.put("sign", sign);

    JSONObject result = HttpRequestHelper.sendRequest(reqMap, signInfoVo.getApiUrl() + "/order/updatePayStatus");
}

2.3.3 添加controller方法

在WeixinController类添加方法

@Autowired
private PaymentService paymentService;

//查询支付状态
@GetMapping("queryPayStatus/{orderId}")
public Result queryPayStatus(@PathVariable Long orderId){
    
    
    //调用微信接口实现支付状态查询
    Map<String,String> resultMap = weixinService.queryPayStatus(orderId);
    if(resultMap == null){
    
    
        return Result.fail().message("支付出错");
    }
    if("SUCCESS".equals(resultMap.get("trade_state"))){
    
    //支付成功
        //更新订单状态
        String out_trade_no = resultMap.get("out_trade_no");//订单编号
        paymentService.paySuccess(out_trade_no,resultMap);
        return Result.ok().message("支付成功");
    }
    return Result.ok().message("支付中");
}

3.取消预约

需求分析
取消订单分两种情况:
1、未支付取消订单,直接通知医院更新取消预约状态
2、已支付取消订单,先退款给用户,然后通知医院更新取消预约状态

3.1 开发微信退款接口

参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
该接口需要使用证书,详情参考文档并下载证书

3.1.1 配置证书

请下载的证书放在service-order模块/resources/cert文件夹下
(链接:https://pan.baidu.com/s/1xPL02-ncdEFSz2nyyjlOpA 提取码:odj8)

1.在application.yml文件配置证书路径

weixin:
  cert: service/service_order/src/main/java/com/myproject/yygh/order/cert/apiclient_cert.p12

2.修改ConstantPropertiesUtils工具类

package com.myproject.yygh.order.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ConstantPropertiesUtils implements InitializingBean {
    
    

    @Value("${weixin.appid}")
    private String appid;

    @Value("${weixin.partner}")
    private String partner;

    @Value("${weixin.partnerkey}")
    private String partnerkey;

    @Value("${weixin.cert}")
    private String cert;

    public static String APPID;
    public static String PARTNER;
    public static String PARTNERKEY;
    public static String CERT;
    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        APPID = appid;
        PARTNER = partner;
        PARTNERKEY = partnerkey;
        CERT = cert;
    }
}

3.修改HttpClient工具类
修改第131行

FileInputStream inputStream = new FileInputStream(new File(ConstantPropertiesUtils.CERT));

3.1.2 添加获取支付记录接口

退款我们是根据支付记录发起退款的

1.在PaymentService类添加接口

//获取支付记录
PaymentInfo getPaymentInfo(Long orderId, Integer paymentType);

2.在PaymentServiceImpl类添加实现

//获取支付记录
@Override
public PaymentInfo getPaymentInfo(Long orderId, Integer paymentType) {
    
    
    QueryWrapper<PaymentInfo> wrapper = new QueryWrapper<>();
    wrapper.eq("order_id",orderId);
    wrapper.eq("payment_type",paymentType);
    PaymentInfo paymentInfo = baseMapper.selectOne(wrapper);
    return paymentInfo;
}

3.1.3 添加退款记录

1.添加mapper
com.myproject.yygh.order.mapper.RefundInfoMapper

package com.myproject.yygh.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.myproject.yygh.model.order.RefundInfo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface RefundInfoMapper extends BaseMapper<RefundInfo> {
    
    
}

2.添加service接口与实现
添加service接口
com.myproject.yygh.order.service.RefundInfoService

package com.myproject.yygh.order.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.myproject.yygh.model.order.PaymentInfo;
import com.myproject.yygh.model.order.RefundInfo;

public interface RefundInfoService extends IService<RefundInfo> {
    
    
    //保存退款记录
    RefundInfo saveRefundInfo(PaymentInfo paymentInfo);
}

添加service接口实现
com.myproject.yygh.order.service.impl.RefundInfoServiceImpl

package com.myproject.yygh.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myproject.yygh.enums.RefundStatusEnum;
import com.myproject.yygh.model.order.PaymentInfo;
import com.myproject.yygh.model.order.RefundInfo;
import com.myproject.yygh.order.mapper.RefundInfoMapper;
import com.myproject.yygh.order.service.RefundInfoService;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundInfo> implements RefundInfoService {
    
    

    //保存退款记录
    @Override
    public RefundInfo saveRefundInfo(PaymentInfo paymentInfo) {
    
    
        //判断是否有重复数据添加
        QueryWrapper<RefundInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("order_id", paymentInfo.getOrderId());
        wrapper.eq("payment_type", paymentInfo.getPaymentType());
        RefundInfo refundInfo = baseMapper.selectOne(wrapper);
        if(refundInfo != null){
    
    
            return refundInfo;
        }
        //添加记录
        refundInfo = new RefundInfo();
        refundInfo.setCreateTime(new Date());
        refundInfo.setOrderId(paymentInfo.getOrderId());
        refundInfo.setPaymentType(paymentInfo.getPaymentType());
        refundInfo.setOutTradeNo(paymentInfo.getOutTradeNo());
        refundInfo.setRefundStatus(RefundStatusEnum.UNREFUND.getStatus());
        refundInfo.setSubject(paymentInfo.getSubject());
        //paymentInfo.setSubject("test");
        refundInfo.setTotalAmount(paymentInfo.getTotalAmount());
        baseMapper.insert(refundInfo);
        return refundInfo;
    }
}

3.1.4 添加微信退款接口

在WeixinService添加接口

//退款
Boolean refund(Long orderId);

在WeixinServiceImpl添加实现

@Autowired
private RefundInfoService refundInfoService;

//微信退款
@Override
public Boolean refund(Long orderId) {
    
    
    try {
    
    
        //获取支付记录信息
        PaymentInfo paymentInfo = paymentService.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());
        //添加信息到退款记录表
        RefundInfo refundInfo = refundInfoService.saveRefundInfo(paymentInfo);
        //判断当前订单数据是否已经退款
        if(refundInfo.getRefundStatus().intValue() == RefundStatusEnum.REFUND.getStatus().intValue()) {
    
    
            return true;
        }
        //调用微信接口实现退款
        //封装需要参数
        Map<String,String> paramMap = new HashMap<>(8);
        paramMap.put("appid",ConstantPropertiesUtils.APPID);       //公众账号ID
        paramMap.put("mch_id",ConstantPropertiesUtils.PARTNER);   //商户编号
        paramMap.put("nonce_str",WXPayUtil.generateNonceStr());
        paramMap.put("transaction_id",paymentInfo.getTradeNo()); //微信订单号
        paramMap.put("out_trade_no",paymentInfo.getOutTradeNo()); //商户订单编号
        paramMap.put("out_refund_no","tk"+paymentInfo.getOutTradeNo()); //商户退款单号
//            paramMap.put("total_fee",paymentInfoQuery.getTotalAmount().multiply(new BigDecimal("100")).longValue()+"");
//            paramMap.put("refund_fee",paymentInfoQuery.getTotalAmount().multiply(new BigDecimal("100")).longValue()+"");
        paramMap.put("total_fee","1");
        paramMap.put("refund_fee","1");
        String paramXml = WXPayUtil.generateSignedXml(paramMap,ConstantPropertiesUtils.PARTNERKEY);

        //设置调用接口内容
        HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/secapi/pay/refund");
        client.setXmlParam(paramXml);
        client.setHttps(true);
        //设置整数信息
        client.setCert(true);
        client.setCertPassword(ConstantPropertiesUtils.PARTNER);
        client.post();

        //接受返回数据
        String xml = client.getContent();
        Map<String,String > resultMap = WXPayUtil.xmlToMap(xml);
        if (null != resultMap && WXPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("result_code"))) {
    
    
            refundInfo.setCallbackTime(new Date());
            refundInfo.setTradeNo(resultMap.get("refund_id"));
            refundInfo.setRefundStatus(RefundStatusEnum.REFUND.getStatus());
            refundInfo.setCallbackContent(JSONObject.toJSONString(resultMap));
            refundInfoService.updateById(refundInfo);
            return true;
        }
    } catch (Exception e) {
    
    
        throw new RuntimeException(e);
    }
    return null;
}

3.2 完成取消预约

参考《尚医通API接口文档.docx》业务接口5.3.取消预约

3.2.1 添加service接口与实现

在OrderService添加接口

//取消预约
Boolean cancelOrder(Long orderId);

在OrderServiceImpl添加实现

@Autowired
private WeixinService weixinService;

//取消预约
@Override
public Boolean cancelOrder(Long orderId) {
    
    
    //获取订单信息
    OrderInfo orderInfo = baseMapper.selectById(orderId);
    //判断是否取消
    DateTime quitTime = new DateTime(orderInfo.getQuitTime());
    if(quitTime.isBeforeNow()){
    
    
        throw new YyghException(ResultCodeEnum.CANCEL_ORDER_NO);
    }
    //调用医院接口实现预约取消
    SignInfoVo signInfoVo = hospitalFeignClient.getSignInfoVo(orderInfo.getHoscode());
    if(null == signInfoVo) {
    
    
        throw new YyghException(ResultCodeEnum.PARAM_ERROR);
    }
    Map<String, Object> reqMap = new HashMap<>();
    reqMap.put("hoscode",orderInfo.getHoscode());
    reqMap.put("hosRecordId",orderInfo.getHosRecordId());
    reqMap.put("timestamp", HttpRequestHelper.getTimestamp());
    String sign = HttpRequestHelper.getSign(reqMap, signInfoVo.getSignKey());
    reqMap.put("sign", sign);

    JSONObject result = HttpRequestHelper.sendRequest(reqMap, signInfoVo.getApiUrl()+"/order/updateCancelStatus");

    //根据医院接口返回数据
    if(result.getInteger("code") != 200){
    
    
        throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());
    }else {
    
    
        //判断当前订单是否可以取消
        if(orderInfo.getOrderStatus().intValue() == OrderStatusEnum.PAID.getStatus().intValue()) {
    
    
            //已支付 退款
            boolean isRefund = weixinService.refund(orderId);
            if(!isRefund) {
    
    
                throw new YyghException(ResultCodeEnum.CANCEL_ORDER_FAIL);
            }
            //更新订单状态
            orderInfo.setOrderStatus(OrderStatusEnum.CANCLE.getStatus());
            baseMapper.updateById(orderInfo);

            //发送mq更新预约数量
            OrderMqVo orderMqVo = new OrderMqVo();
            orderMqVo.setScheduleId(orderInfo.getScheduleId());
            //短信提示
            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("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
            }};
            msmVo.setParam(param);
            orderMqVo.setMsmVo(msmVo);
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER, MqConst.ROUTING_ORDER, orderMqVo);
            return true;
        }
    }
    return false;
}

3.2.2 添加controller方法

在OrderApiController添加方法

//取消预约
@GetMapping("auth/cancelOrder/{orderId}")
public Result cancelOrder(@PathVariable Long orderId){
    
    
    Boolean isOrder = orderService.cancelOrder(orderId);
    return Result.ok(isOrder);
}

3.2.3 修改监听

操作:service-hosp模块
修改HospitalReceiver 类

@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 {
    
    
    if(orderMqVo.getAvailableNumber() != null){
    
    
        //下单成功更新预约数
        Schedule schedule = scheduleService.getScheduleById(orderMqVo.getScheduleId());
        schedule.setReservedNumber(orderMqVo.getReservedNumber());
        schedule.setAvailableNumber(orderMqVo.getAvailableNumber());
        scheduleService.update(schedule);
    }else {
    
    
        Schedule schedule = scheduleService.getScheduleById(orderMqVo.getScheduleId());
        int availableNumber = schedule.getAvailableNumber().intValue() + 1;
        schedule.setAvailableNumber(availableNumber);
        scheduleService.update(schedule);
    }
    //发送短信
    MsmVo msmVo = orderMqVo.getMsmVo();
    if(null != msmVo) {
    
    
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
    }
}

3.3 前端

3.3.1 封装api请求

添加/api/orderInfo.js文件

//取消订单
cancelOrder(orderId) {
    
    
    return request({
    
    
      url: `${
      
      api_name}/auth/cancelOrder/${
      
      orderId}`,
      method: 'get'
    })
}

3.3.2 页面展示

修改/pages/order/show.vue组件

//取消预约
cancelOrder() {
    
    
  this.$confirm('确定取消预约吗?', '提示', {
    
    
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    
     // promise
    // 点击确定,远程调用
    return orderInfoApi.cancelOrder(this.orderId)
  }).then((response) => {
    
    
    this.$message.success('取消成功')
    this.init()
  }).catch(() => {
    
    
    this.$message.info('已取消取消预约')
  })
}

4.就医提醒功能

我们通过定时任务,每天8点执行,提醒就诊

4.1 搭建定时任务模块service-task

在service模块中创建service-task子模块

1.修改配置pom.xml

<dependencies>
    <dependency>
        <groupId>com.myproject</groupId>
        <artifactId>rabbit_util</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

2.添加配置文件
application.yml

server:
  port: 8207

spring:
  application:
    name: service-task
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
  rabbitmq:
    host: 192.168.50.224
    port: 5672
    username: admin
    password: admin

3. 添加启动类

package com.myproject.yygh.task;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@EnableDiscoveryClient
@ComponentScan(basePackages = {
    
    "com.myproject"})
public class ServiceTaskApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ServiceTaskApplication.class, args);
    }
}

4.添加常量配置
在rabbit-util模块com.myproject.common.rabbit.constant.MqConst类添加

public static final String EXCHANGE_DIRECT_TASK = "exchange.direct.task";
public static final String ROUTING_TASK_8 = "task.8";
//队列
public static final String QUEUE_TASK_8 = "queue.task.8";

5.添加定时任务
com.myproject.yygh.task.scheduled.ScheduledTask

package com.myproject.yygh.task.scheduled;

import com.myproject.common.rabbit.constant.MqConst;
import com.myproject.common.rabbit.service.RabbitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class ScheduledTask {
    
    
    @Autowired
    private RabbitService rabbitService;

    //每天8点执行方法,提醒就医
    //cron表达式,执行时间间隔
    //每天8点:0 0 8 * * ?
    @Scheduled(cron = "0/30 * * * * ?")
    public void taskPatient(){
    
    
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK,MqConst.ROUTING_TASK_8,"");
    }
}

4.2 添加就医提醒处理

操作:service_order

1.添加service接口和接口实现
在OrderService类添加接口

//就诊人通知
void patientTips();

在OrderServiceImpl类添加接口实现

//就诊人通知
@Override
public void patientTips() {
    
    
    QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
    wrapper.eq("reserve_date",new DateTime().toString("yyy-MM-dd"));
    wrapper.ne("order_status",OrderStatusEnum.CANCLE.getStatus());
    List<OrderInfo> orderInfoList = baseMapper.selectList(wrapper);
    for (OrderInfo orderInfo : orderInfoList) {
    
    
        //短信提示
        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("reserveDate", reserveDate);
            put("name", orderInfo.getPatientName());
        }};
        msmVo.setParam(param);
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
    }
}

2.添加mq监听
com.myproject.yygh.order.receiver.OrderReceiver

package com.myproject.yygh.order.receiver;

import com.myproject.common.rabbit.constant.MqConst;
import com.myproject.yygh.order.service.OrderService;
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 OrderReceiver {
    
    
    @Autowired
    private OrderService orderService;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConst.QUEUE_TASK_8, durable = "true"),
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK),
            key = {
    
    MqConst.ROUTING_TASK_8}
    ))
    public void patientTips(Message message, Channel channel) throws IOException {
    
    
        orderService.patientTips();
    }
}

5.预约统计

我们统计医院每天的预约情况,通过图表的形式展示,统计的数据都来自订单模块,因此我们在该模块封装好数据,在统计模块通过feign的形式获取数据。

我们为什么需要一个统计模块呢,因为在实际的生成环境中,有很多种各式统计,数据来源于各个服务模块,我们得有一个统计模块来专门管理

5.1 ECharts

简介
ECharts是百度的一个项目,后来百度把Echart捐给apache,用于图表展示,提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。

官方网站:https://echarts.apache.org/zh/index.html

5.1.1 基本使用

echarts.min.js(链接:https://pan.baidu.com/s/1s-Az_JMZl7uj3ggDgAfzLg 提取码:pyrg)

1.引入ECharts

<!--引入 EChars 文件-->
<script src="../js/echarts.min.js"></script>

2.定义图表区域

<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>

3.渲染图表(折线图)

<script>
    var myChart = echarts.init(document.getElementById('main'));
    var option = {
      
      
        //x轴是类目轴(离散数据),必须通过data设置类目数据
        xAxis: {
      
      
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
        },
        //y轴是数据轴(连续数据)
        yAxis: {
      
      
            type: 'value'
        },
        //系列列表。每个系列通过 type 决定自己的图表类型
        series: [{
      
      
            //系列中的数据内容数组
            data: [820, 932, 901, 934, 1290, 1330, 1320],
            //折线图
            type: 'line'
        }]
    };
    myChart.setOption(option);
</script>

4.渲染图表(柱状图)

<script>
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));
    // 指定图表的配置项和数据
    var option = {
      
      
        title: {
      
      
            text: 'ECharts 入门示例'
        },
        tooltip: {
      
      },
        legend: {
      
      
            data:['销量']
        },
        xAxis: {
      
      
            data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
        },
        yAxis: {
      
      },
        series: [{
      
      
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
        }]
    };
    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
</script>

4.项目中集成ECharts
在yygh-page中安装

npm install --save [email protected]

5.2 获取医院每天平台预约数据接口

操作模块:service-order

5.2.1 添加Mapper接口

1.在OrderMapper类添加接口

//查询预约统计数据的方法
List<OrderCountVo> selectOrderCount(@Param("vo") OrderCountQueryVo orderCountQueryVo);

2.在OrderMapper.xml文件添加方法
com/myproject/yygh/order/mapper/xml/OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.myproject.yygh.order.mapper.OrderMapper">
    <select id="selectOrderCount" resultType="com.myproject.yygh.vo.order.OrderCountVo">
        select reserve_date as reserveDate, count(reserve_date) as count
        from order_info
        <where>
            <if test="vo.hosname != null and vo.hosname != ''">
                and hosname like CONCAT('%',#{vo.hosname},'%')
            </if>
            <if test="vo.reserveDateBegin != null and vo.reserveDateBegin != ''">
                and reserve_date >= #{vo.reserveDateBegin}
            </if>
            <if test="vo.reserveDateEnd != null and vo.reserveDateEnd != ''">
                and reserve_date &lt;= #{vo.reserveDateEnd}
            </if>
            and is_deleted = 0
        </where>
        group by reserve_date
        order by reserve_date
    </select>
</mapper>

3.添加application.yml配置

mybatis-plus:
  mapper-locations: classpath:com/myproject/yygh/order/mapper/xml/*.xml

4.在yygh_parent的pom.xml中添加

<!--项目打包时会将java目录中的*.xml文件也进行打包-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

5.2.2 添加service接口和接口实现

在OrderService类添加接口

//预约统计
Map<String,Object> getCountMap(OrderCountQueryVo orderCountQueryVo);

在OrderServiceImpl类添加实现

//预约统计
@Override
public Map<String, Object> getCountMap(OrderCountQueryVo orderCountQueryVo) {
    
    
    //调用mapper方法得到数据
    List<OrderCountVo> orderCountVoList = baseMapper.selectOrderCount(orderCountQueryVo);

    //获取x需要数据,日期数据 list集合
    List<String> dateList = orderCountVoList.stream().map(OrderCountVo::getReserveDate).collect(Collectors.toList());

    //获取y需要数据,具体数量 list集合
    List<Integer> countList = orderCountVoList.stream().map(OrderCountVo::getCount).collect(Collectors.toList());

    Map<String, Object> map = new HashMap<>();
    map.put("dateList", dateList);
    map.put("countList", countList);
    return map;
}

5.2.3 添加controller方法

在OrderApiController类添加方法

//获取订单统计数据
@PostMapping("inner/getCountMap")
public Map<String, Object> getCountMap(@RequestBody OrderCountQueryVo orderCountQueryVo) {
    
    
    return orderService.getCountMap(orderCountQueryVo);
}

5.2.4 添加feign方法

创建模块:service-order-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.order.client.OrderFeignClient

package com.myproject.yygh.order.client;

import com.myproject.yygh.vo.order.OrderCountQueryVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Map;

@FeignClient(value = "service-order")
@Repository
public interface OrderFeignClient {
    
    
    /**
     * 获取订单统计数据
     */
    @PostMapping("/api/order/orderInfo/inner/getCountMap")
    Map<String, Object> getCountMap(@RequestBody OrderCountQueryVo orderCountQueryVo);
}

5.3 搭建service-statistics

创建service_statistics子模块

1.修改配置pom.xml

<dependencies>
    <dependency>
        <groupId>com.myproject</groupId>
        <artifactId>service_order_client</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

2.添加配置文件
application.yml

server:
  port: 8208
spring:
  application:
    name: service-statistics
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848

3.添加启动类
com.myproject.yygh.sta.ServiceStatisticsApplication

package com.myproject.yygh.sta;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {
    
    "com.myproject"})
@ComponentScan(basePackages = {
    
    "com.myproject"})
public class ServiceStatisticsApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ServiceStatisticsApplication.class, args);
    }
}

4.添加controller方法
com.myproject.yygh.sta.controller.StatisticsController

package com.myproject.yygh.sta.controller;

import com.myproject.yygh.common.result.Result;
import com.myproject.yygh.order.client.OrderFeignClient;
import com.myproject.yygh.vo.order.OrderCountQueryVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@Api(tags = "统计管理接口")
@RestController
@RequestMapping("/admin/statistics")
public class StatisticsController {
    
    

    @Autowired
    private OrderFeignClient orderFeignClient;

   //获取预约统计数据
    @GetMapping("getCountMap")
    public Result getCountMap(OrderCountQueryVo orderCountQueryVo) {
    
    
        Map<String,Object> countMap = orderFeignClient.getCountMap(orderCountQueryVo);
        return Result.ok(countMap);
    }
}

5.添加网关

- id: service-statistics
  uri: lb://service-statistics
  predicates:
    - name: Path
      args:
        - /*/statistics/**

5.4 前端展示

操作:yygh_page

1.添加路由
在 src/router/index.js 文件添加路由

{
    
    
    path: '/statistics',
    component: Layout,
    redirect: '/statistics/order/index',
    name: 'BasesInfo',
    meta: {
    
     title: '统计管理', icon: 'table' },
    alwaysShow: true,
    children: [
      {
    
    
        path: 'order/index',
        name: '预约统计',
        component: () =>import('@/views/statistics/order/index'),
        meta: {
    
     title: '预约统计' }
      }
    ]
},

2.封装api请求
创建/api/sta.js

import request from '@/utils/request'

const api_name = '/admin/statistics'

export default {
    
    

  getCountMap(searchObj) {
    
    
    return request({
    
    
      url: `${
      
      api_name}/getCountMap`,
      method: 'get',
      params: searchObj
    })
  }
}

3.添加组件
创建/views/statistics/order/index.vue组件

<template>
  <div class="app-container">
    <!--表单-->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="searchObj.hosname" placeholder="点击输入医院名称"/>
      </el-form-item>

      <el-form-item>
        <el-date-picker
          v-model="searchObj.reserveDateBegin"
          type="date"
          placeholder="选择开始日期"
          value-format="yyyy-MM-dd"/>
      </el-form-item>
      <el-form-item>
        <el-date-picker
          v-model="searchObj.reserveDateEnd"
          type="date"
          placeholder="选择截止日期"
          value-format="yyyy-MM-dd"/>
      </el-form-item>
      <el-button
        :disabled="btnDisabled"
        type="primary"
        icon="el-icon-search"
        @click="showChart()">查询</el-button>
    </el-form>

    <div class="chart-container">
      <div id="chart" ref="chart"
           class="chart" style="height:500px;width:100%"/>
    </div>
  </div>
</template>

<script>
import echarts from 'echarts'
import statisticsApi from '@/api/sta'

export default {

  data() {
    return {
      searchObj: {
        hosname: '',
        reserveDateBegin: '',
        reserveDateEnd: ''
      },
      btnDisabled: false,
      chart: null,
      title: '',
      xData: [], // x轴数据
      yData: [] // y轴数据
    }
  },

  methods: {
    // 初始化图表数据
    showChart() {
      statisticsApi.getCountMap(this.searchObj).then(response => {
        this.yData = response.data.countList
        this.xData = response.data.dateList
        this.setChartData()
      })
    },

    setChartData() {
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('chart'))
      // 指定图表的配置项和数据
      var option = {
        title: {
          text: this.title + '挂号量统计'
        },
        tooltip: {},
        legend: {
          data: [this.title]
        },
        xAxis: {
          data: this.xData
        },
        yAxis: {
          minInterval: 1
        },
        series: [{
          name: this.title,
          type: 'line',
          data: this.yData
        }]
      }

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option)
    },
  }
}
</script>

猜你喜欢

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