这一天以来真的是有种劫后余生的感觉,体会到了为什么电视剧里那些人在经历一些事情之后,点根烟连打火机都点不着的感觉了,就是还没缓过来!
大体流程是这样的
1、用户系统选中某一个科室下的某个排班,点击剩余按钮,跳转到一个页面然后选择就诊人,点击确认挂号。
2、现在状态就变为预约成功,未支付状态。我们点击支付,之后就是扫描二维码付款了。
rabbitmq的使用
mq在这里充当了异步调用的作用,可以提高下单的响应速度。
1.1 调用医院端 的程序在医院端保存一条订单和在医院端扣减号源。
//使用httpclient发送请求,请求医院接口 //医院端 1.扣减号源 2.保存医院端自己的订单 JSONObject result = HttpRequestHelper.sendRequest(paramMap, "http://localhost:9998/order/submitOrder");1.2 在我们的平台端也保存一条我们自己的订单,之后发送消息到mq,异步处理。
1.3 mq在本项目的异步处理的体现:
平台端的医院服务更新mongodb中的号源(数据来自医院端的返回)
平台端医院服务再发送消息到mq,短信服务消费这些消息发送短信通知用户挂号成功。
OrderMqVo orderMqVo = new OrderMqVo(); //这2个数量都是医院端返给我们平台端的 orderMqVo.setReservedNumber(reservedNumber);//总的号源数量 orderMqVo.setAvailableNumber(availableNumber);//剩余可预约数量 orderMqVo.setScheduleId(scheduleId);//mongondb中排班id MsmVo msmVo = new MsmVo(); msmVo.setPhone(orderInfo.getPatientPhone()); Mapmap = new HashMap (){{ put("message","请于"+orderInfo.getReserveDate()+"到" +orderInfo.getHosname()+orderInfo.getDepname()+"就诊"); }}; msmVo.setParam(map); orderMqVo.setMsmVo(msmVo); //发送消息到mq,异步处理 rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER,MqConst.ROUTING_ORDER,orderMqVo); 发送短信的话,我们模拟一下,在控制台输出。上一篇我们已经讲过了如何调用阿里云发送短信,在短信服务控制台中输出如下:
在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html
--------------------------------------
生成二维码
使用Redis判断5分钟内是否生成过二维码
- Map m = (Map)redisTemplate.opsForValue().get(orderId.toString());
- //防止5分钟之内重复向微信端发请求获取支付链接
- if(m!=null){
- return m;
- }
1、创建支付记录,处于未支付状态
2、向微信端“ 统一下单 ” 发请求,拿到二维码连接,并把map存储到Redis
paramMap参数的设置就多了去了,,,
//2、HTTPClient来根据URL访问第三方接口并且传递参数 HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder"); //client设置参数 client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantPropertiesUtils.PARTNERKEY)); client.setHttps(true); client.post(); //3、微信端返回数据xml格式数据 String xml = client.getContent(); Mapresult = WXPayUtil.xmlToMap(xml); String return_code = result.get("return_code"); String result_code = result.get("result_code"); if (return_code.equals("SUCCESS") && result_code.equals("SUCCESS")) { Map map = new HashMap(); map.put("orderId", orderId); map.put("totalFee", order.getAmount()); map.put("resultCode", result.get("result_code"));//返回状态码 SUCCESS/FAIL map.put("codeUrl", result.get("code_url"));//二维码链接 redisTemplate.opsForValue().set(orderId.toString(),map,5, TimeUnit.MINUTES); return map; }
----------------------------------------------
查询支付状态
打开二维码后,前端每隔3秒去调用查询支付状态接口
1、查询微信端“查询订单”接口
代码如下,没啥可说的。写法很固定!
代码里面有很多工具类,比如发送请求的、map和xml格式互转的。
//根据订单号去微信第三方查询支付状态 @Override public Map queryPayStatus(Long orderId) { OrderInfo orderInfo = orderService.getById(orderId); //1、封装参数 Map paramMap = new HashMap<>(); paramMap.put("appid", ConstantPropertiesUtils.APPID); paramMap.put("mch_id", ConstantPropertiesUtils.PARTNER);//商户号 paramMap.put("out_trade_no", orderInfo.getOutTradeNo());//订单的out_trade_no paramMap.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串 try { //2、设置请求,调用微信端“查询订单”接口 HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery"); //微信端都是xml作为参数和返回值 //generateSignedXml会自动添加签名 client.setXmlParam(WXPayUtil .generateSignedXml(paramMap, ConstantPropertiesUtils.PARTNERKEY));//商户号签名 client.setHttps(true); //是否支持指定协议 client.post(); //3、获取返回值,转成Map String xml = client.getContent(); MapresultMap = WXPayUtil.xmlToMap(xml); //4、返回 return resultMap; } catch (Exception e) { e.printStackTrace(); return null; } }
2、支付成功,需要做的事情有修改订单的状态为已支付,修改或填充支付记录中的4个字段
比较重要的就是 trade_no,这个是微信端返回给我们的支付流水号。另外一个out_trade_no是我们订单表中自己的交易记录号,out_trade_no会伴随着整个支付流程
paymentInfo.setTradeNo(paramMap.get("transaction_id"))
取消预约 有2种情况
(1)未支付取消订单,直接通知医院更新取消预约状态
(2)已支付取消订单,先退款给用户,然后通知医院更新取消预约状态
取消预约需要配置证书:
拷贝证书到c盘下:
在配置文件中:
weixin.cert=C:\\apiclient_cert.p12
1、根据支付记录创建退款记录
2、调用微信端接口“申请退款”
- 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);//商户id
- client.post();
- //返回第三方的数据
- String xml = client.getContent();
- Map
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;
- }
- return false;
3、查询退款成功则修改第一步创建的退款记录,并把平台端的订单状态也要改为-1
4、mq异步实现
- //4.向mq发送消息,完成mongondb中号源数量+1,短信通知就诊人
- OrderMqVo orderMqVo = new OrderMqVo();
- orderMqVo.setScheduleId(orderInfo.getScheduleId());
- MsmVo msmVo =new MsmVo();
- msmVo.setPhone(orderInfo.getPatientPhone());
- Map
param=new HashMap<>(); - param.put("message","退号成功");
- msmVo.setParam(param);
- orderMqVo.setMsmVo(msmVo);
- rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER,MqConst.ROUTING_ORDER,msmVo);
请夸我灵魂画手,低调低调哈哈
这个消息可以是空的,本来就是起一个提示的作用。
要注意的点就是 @Scheduled(cron = "0/50 * * * * ?") //等步增长
/** * 每天8点执行 提醒就诊 */ //@Scheduled(cron = "0 0 8 * * ?") @Scheduled(cron = "0/5 * * * * ?") //等步增长 public void task() { System.out.println(new Date().toLocaleString()); rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK, MqConst.ROUTING_TASK_8, ""); }RabbitService是我们自己封装好的一个操作rabbitmq的服务
@Component 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; } }
2.1、去查询当天的就诊人,实际上是查询 就诊日期是当天的订单,
2.2、然后在订单服务遍历当天的这些订单,给“msm队列”里发送消息。
public void patientTips() { //1、查询当天已支付所有的订单 QueryWrapperqueryWrapper = new QueryWrapper<>(); queryWrapper.eq("reserve_date", new DateTime().toString("yyyy-MM-dd")); queryWrapper.eq("order_status",1); ListorderInfos = baseMapper.selectList(queryWrapper); //2、循环遍历发送消息到mq for (OrderInfo orderInfo : orderInfos) { //短信提示 MsmVo msmVo = new MsmVo(); //就诊人手机号 msmVo.setPhone(orderInfo.getPatientPhone()); //短信内容 Mapparam = new HashMap (){{ put("message", orderInfo.getPatientName()+"请于今天就诊"); }}; msmVo.setParam(param); //发送消息到msm队列 rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo); } }
@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) { Mapparam = msmVo.getParam(); System.out.println("[尚医通]: "+param.get("message")); }控制台打印如下:当然你还可以让提示更加丰富,我这里就只取了“就诊人的名字”
在实际的生产环境中,有很多种各式统计,数据来源于各个服务模块,我们得有一个统计模块来专门管理。
ECharts是百度的一个项目,后来百度把Echart捐给apache,用于图表展示,提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。
官方网站:https://echarts.apache.org/zh/index.html
需求:统计每天平台预约数据(即订单数量)
实际上返回给前端的就是横坐标集合和纵坐标集合。横坐标表示日期,纵坐标表示预约数量
npm install --save echarts@4.1.0
我们要做的事,对应到mysql,差不多就是这样
SELECT `reserve_date`,COUNT(0) AS 'count' FROM `order_info` GROUP BY `reserve_date` ORDER BY `reserve_date`
核心代码就是
1、调用mapper接口查询
<select id="selectOrderCount" resultType="com.atguigu.yygh.vo.order.OrderCountVo"> select reserve_date as reserveDate, count(reserve_date) as count from order_info <where> <if test="hosname != null and hosname != ''"> and hosname like CONCAT('%',#{hosname},'%') if> <if test="reserveDateBegin != null and reserveDateBegin != ''"> and reserve_date >= #{reserveDateBegin} if> <if test="reserveDateEnd != null and reserveDateEnd != ''"> and reserve_date <= #{reserveDateEnd} if> and is_deleted = 0 where> group by reserve_date order by reserve_date select>2、把查询的结果封装横坐标集合和纵坐标集合
//获取订单统计数据 @Override public MapgetCountMap(OrderCountQueryVo orderCountQueryVo) { ListorderCountVos = orderInfoMapper.selectOrderCount(orderCountQueryVo); ListdateList= orderCountVos.stream() .map(OrderCountVo::getReserveDate).collect(Collectors.toList()); ListcountList= orderCountVos.stream() .map(OrderCountVo::getCount).collect(Collectors.toList()); HashMapmap = new HashMap<>(); //给前端返回横坐标集合(日期)和纵坐标集合(预约数量) map.put("dateList",dateList); map.put("countList",countList); return map; }
最后,就可以在页面看到好看的效果了,当然省略了前端的代码
医院端
医院设置管理
医院管理
数据字典
用户管理
统计管理