目录
一、取消预约 (微信退款)
1、准备工作
2、保存退款记录接口
3、实现微信退款
4、取消预约接口
5、前端实现
二、就医提醒
1、准备工作,搭建模块
2、在orders模块实现功能
三、预约统计功能
1、开发统计每天预约数据接口
一、取消预约 (微信退款)
1、准备工作
1. 确认需求
(1)未支付取消订单,直接通知医院更新取消预约状态
(2)已支付取消订单,先退款给用户,然后通知医院更新取消预约状态
参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
该接口需要使用证书,详情参考文档并下载证书
2. 确认证书
3. 添加配置证书地址,解开相关注释
#双向证书 weixin.cert=E:\apiclient_cert.p12
4. 获取支付记录方法
PaymentService
//获取支付记录
PaymentInfo getPaymentInfo(Long orderId, Integer paymentType);
//获取支付记录
@Override
public PaymentInfo getPaymentInfo(Long orderId, Integer paymentType) {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("order_id", orderId);
wrapper.eq("payment_type", paymentType);
PaymentInfo paymentInfo = baseMapper.selectOne(wrapper);
return paymentInfo;
}
2、保存退款记录接口
1. 确认库表 refund_info
2. 创建相关接口、类
3. 接口实现
public interface RefundInfoService extends IService{ //保存退款记录 RefundInfo saveRefundInfo(PaymentInfo paymentInfo); } @Service public class RefundInfoServiceImpl extends ServiceImpl implements RefundInfoService { //保存退款记录 @Override public RefundInfo saveRefundInfo(PaymentInfo paymentInfo) { //1.查询退款记录,如果存在直接返回 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("order_id", paymentInfo.getOrderId()); queryWrapper.eq("payment_type", paymentInfo.getPaymentType()); RefundInfo refundInfo = baseMapper.selectOne(queryWrapper); if(null != refundInfo) return refundInfo; //2.如果不存在,新增退款记录 refundInfo = new RefundInfo(); refundInfo.setCreateTime(new Date()); refundInfo.setOrderId(paymentInfo.getOrderId()); refundInfo.setPaymentType(paymentInfo.getPaymentType()); refundInfo.setOutTradeNo(paymentInfo.getOutTradeNo()); refundInfo.setRefundStatus(RefundStatusEnum.UNREFUND.getStatus());//1 退款中 refundInfo.setSubject(paymentInfo.getSubject()); //paymentInfo.setSubject("test"); refundInfo.setTotalAmount(paymentInfo.getTotalAmount()); baseMapper.insert(refundInfo); return refundInfo; } }
3、实现微信退款
1. 接口实现
weixinService
//退款
Boolean refund(Long orderId);
//退款
@Override
public Boolean refund(Long orderId) {
try {
//1.根据参数查询交易记录
PaymentInfo paymentInfo = paymentService.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());
if (paymentInfo == null) {
throw new YyghException(20001, "交易记录有误");
}
//2.根据交易记录添加退款记录,确认退款状态
RefundInfo refundInfo = refundInfoService.saveRefundInfo(paymentInfo);
if (refundInfo.getRefundStatus() ==
RefundStatusEnum.REFUND.getStatus()) {
return true; //已完成退款
}
//3.封装调用接口参数
Map 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"); //退款多少,小于总金额
//4.创建客户端(设置url) 参考文档
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/secapi/pay/refund");
//5.设置参数 (map=>xml),开启读取证书开关
String paramXml = WXPayUtil.generateSignedXml(paramMap,ConstantPropertiesUtils.PARTNERKEY);
client.setXmlParam(paramXml);
client.setHttps(true);
client.setCert(true); //开启读取证书开关
client.setCertPassword(ConstantPropertiesUtils.PARTNER); //整证密码(商户编号)
//6.客户端发送请求
client.post();
//7.获取响应,转化响应类型(xml=>map)
String xml = client.getContent();
System.out.println("退款xml = " + xml);
Map resultMap = WXPayUtil.xmlToMap(xml);
//8.如果退款成功,更新退款记录信息
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;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
4、取消预约接口
1. OrderController 方法
*参数:orderId
*返回值:R.ok()
@ApiOperation(value = "取消预约")
@GetMapping("auth/cancelOrder/{orderId}")
public R cancelOrder(
@ApiParam(name = "orderId", value = "订单id", required = true)
@PathVariable("orderId") Long orderId) {
Boolean flag = orderService.cancelOrder(orderId);
return R.ok().data("flag",flag);
}
2. service
注释掉医院取消预约 校验签名
//取消预约
@Override
public Boolean cancelOrder(Long orderId) {
//1.查询订单信息
OrderInfo orderInfo = baseMapper.selectById(orderId);
if(orderInfo==null){
throw new YyghException(20001,"订单信息有误");
}
//2.判断是否已过退号时间
DateTime quitDateTime = new DateTime(orderInfo.getQuitTime());
if(quitDateTime.isBeforeNow()){
throw new YyghException(20001,"已过取消预约截止时间");
}
//3.调用医院系统接口,取消预约
Map reqMap = new HashMap<>();
reqMap.put("hoscode",orderInfo.getHoscode());
reqMap.put("hosRecordId",orderInfo.getHosRecordId());
reqMap.put("timestamp", HttpRequestHelper.getTimestamp());
reqMap.put("sign", "");
JSONObject result = HttpRequestHelper.sendRequest(reqMap, "http://localhost:9998/order/updateCancelStatus");
//4.医院取消预约成功
if(result.getInteger("code")!=200){
throw new YyghException(20001,"取消预约失败");
}else {
//判断是否已支付
if(orderInfo.getOrderStatus() == OrderStatusEnum.PAID.getStatus()) {
//5.如果已支付,调用微信退款
Boolean refund = weixinService.refund(orderId);
if(!refund){
throw new YyghException(20001,"微信退款失败");
}
}
//6.更新订单状态
orderInfo.setOrderStatus(OrderStatusEnum.CANCLE.getStatus());
this.updateById(orderInfo);
//7.发送MQ消息,更新号源,通知就诊人
OrderMqVo orderMqVo = new OrderMqVo();
orderMqVo.setScheduleId(orderInfo.getHosScheduleId());
orderMqVo.setHoscode(orderInfo.getHoscode());
//短信提示
MsmVo msmVo = new MsmVo();
msmVo.setPhone(orderInfo.getPatientPhone());
orderMqVo.setMsmVo(msmVo);
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER, MqConst.ROUTING_ORDER, orderMqVo);
return true;
}
}
3. 改造HospitalReceiver 监听器,判断取消预约还是创建订单
@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 {
//1.取出参数
String hoscode = orderMqVo.getHoscode();
String hosScheduleId = orderMqVo.getScheduleId();
Integer reservedNumber = orderMqVo.getReservedNumber();
Integer availableNumber = orderMqVo.getAvailableNumber();
MsmVo msmVo = orderMqVo.getMsmVo();
//2.根据参数查询排班信息
Schedule schedule = scheduleService.getScheduleByIds(hoscode, hosScheduleId);
//2.5判断创建订单,还是取消预约 (剩余预约数是否为空 取消订单医院接口没有返回值)
if(StringUtils.isEmpty(availableNumber)){
//取消预约,更新号源
availableNumber = schedule.getAvailableNumber().intValue() + 1;
schedule.setAvailableNumber(availableNumber);
}else {
//创建订单,更新号源
schedule.setReservedNumber(reservedNumber);
schedule.setAvailableNumber(availableNumber);
}
//3.更新排班信息
schedule.setUpdateTime(new Date()); //MongoDB不会自动更新时间
scheduleService.update(schedule);
//4.发送短信相关MQ消息
if(msmVo!=null){
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
}
}
}
5、前端实现
1. wx.js 创建API接口方法
//取消预约订单
cancelOrder(orderId) {
return request({
url: `/api/order/orderInfo/auth/cancelOrder/${orderId}`,
method: 'get'
})
},
2. JS实现
//取消预约
cancelOrder() {
this.$confirm('此操作将取消预约,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return weixinApi.cancelOrder(this.orderId).then(response => {
this.$message({
type: "success",
message: "取消预约成功!"
})
window.location.reload()
})
}).catch(() => {
this.$message({
type: "info",
message: "已取消操作"
})
})
}
5. 测试,收到微信退款、短信
二、就医提醒 (定时调度)
我们通过定时任务,每天8点执行,提醒就诊
1、准备工作,搭建模块
1. 新建service_task 模块
com.atguigu rabbit_util0.0.1-SNAPSHOT
# 服务端口 server.port=8208 # 服务名 spring.application.name=service-task # 环境设置:dev、test、prod spring.profiles.active=dev # nacos服务地址 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 #rabbitmq地址 spring.rabbitmq.host=192.168.86.86 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.atguigu"})
public class ServiceTaskApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceTaskApplication.class, args);
}
}
2. 添加常量配置
在rabbit-util模块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";
3. 七域表达式 (七个时间区域)
在线Cron表达式生成器
4. ScheduledTask 实现
@Component
@EnableScheduling //开启定时任务
public class ScheduledTask {
@Autowired
private RabbitService rabbitService;
//@Scheduled(cron = "0 0 8 * * ?")
@Scheduled(cron = "0/5 * * * * ?")
public void test() {
System.out.println("定时任务启动");
}
//每天8点执行 提醒就诊
@Scheduled(cron = "0 0 8 * * ?")
public void task1() {
System.out.println(new Date().toLocaleString());
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK, MqConst.ROUTING_TASK_8, "");
}
}
2、在orders模块实现功能
1. OrderService 添加方法 就诊提醒
//就诊提醒
void patientTips();
//就诊提醒
@Override
public void patientTips() {
//1.查询符合条件订单集合
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("reserve_date",
new DateTime().toString("yyyy-MM-dd"));
List orderInfoList = baseMapper.selectList(queryWrapper);
//2.遍历集合,拼写短信,发送mq消息
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 param = new HashMap(){{
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. 创建监听器
receiver/OrderReceiver
@ComponentScan
public class OrderReceiver {
@Autowired
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();
}
}
三、预约统计功能
我们统计医院每天的预约情况,通过图表的形式展示,统计的数据都来自订单模块,因此我们在该模块封装好数据,在统计模块通过feign的形式获取数据。
1、分析统计分析方案
(1) 创建远程接口统计数据
优势:实时查询
劣势:影响各个模块使用的性能 (占用服务器资源)
(2) 每天固定时间,统计分析各个指标数据,生成前一天统计报表
优势:各个模块使用影响相对小
劣势:不是实时数据
(3) 使用脚本语言、数据库编程、数据统计分析
优势: 数据统计块,对资源占用少
劣势:不能使用JAVA实现 (学习成本高)
2、开发统计每天预约数据接口
1. 查询数据sql
SELECT o.`reserve_date`,COUNT(o.`id`) FROM order_info o
WHERe XXXXX
GROUP BY o.`reserve_date`;
2. 接口分析
*参数:OrderCountQueryVo
*返回值:Map (x轴,y轴数据)
3. OrderApiController 新增方法
@ApiOperation(value = "获取订单统计数据")
@PostMapping("inner/getCountMap")
public Map getCountMap(@RequestBody OrderCountQueryVo orderCountQueryVo) {
Map map = orderService.getCountMap(orderCountQueryVo);
return map;
}
4. 实现mapper
由于MP(MybatisPlus) 无法实现统计sql,需要自行实现
public interface OrderInfoMapper extends BaseMapper{ //统计每天平台预约数据 List selectOrderCount(OrderCountQueryVo orderCountQueryVo); }
创建 mapper/xml/OrderInfoMapper.xml
5. 添加配置
*service.pom.xml 添加 maven插件,部署xml文件
org.springframework.boot
spring-boot-maven-plugin
src/main/java
***.properties
***.yml
***.xml
false
*order 配置文件添加相关配置
#mapper xml文件扫描 mybatis-plus.mapper-locations=classpath:com/atguigu/yygh/order/mapper/xmlstatistics/**
2. 实现 controller
@Api(tags = "统计管理接口")
@RestController
@RequestMapping("/admin/statistics")
public class StatisticsController {
@Autowired
private OrderFeignClient orderFeignClient;
@ApiOperation(value = "获取订单统计数据")
@GetMapping("getCountMap")
public R getCountMap(OrderCountQueryVo orderCountQueryVo) {
Map map = orderFeignClient.getCountMap(orderCountQueryVo);
return R.ok().data(map);
}
}
5、统计功能前端 (ECharts)
ECharts是百度的一个项目,后来百度把Echart捐给apache,用于图表展示,提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。
官网:Apache ECharts
1. 后台前端 安装echarts
npm install --save echarts@4.1.0
2. 添加路由、创建页面
{
path: '/statistics',
component: Layout,
redirect: '/statistics/order/index',
name: 'BasesInfo',
meta: { title: '统计管理', icon: 'table' },
alwaysShow: true,
children: [
{
path: 'order/index',
name: '预约统计',
component: () => import('@/views/yygh/sta/order/index'),
meta: { title: '预约统计' }
}
]
},
3. 添加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
})
}
}
4. 实现相关页面
查询
5. 实现相关页面



