尚医通-预约挂号
# 尚医通-预约挂号
# 预约挂号详情
# 需求分析
- 接口分析
- 根据预约周期,展示可预约日期数据,按分页展示
- 选择日期展示当天可预约列表(该接口后台已经实现过)
- 页面展示分析
- 分页展示可预约日期,根据有号、无号、约满等状态展示不同颜色,以示区分
- 可预约最后一个日期为即将放号日期,根据放号时间页面展示倒计时
# api 接口
# 添加 service 接口以及实现类
- 在 ScheduleService 类添加接口
//获取可预约的排版数据
Map<String,Object> getBookingScheduleRule(Integer page, Integer limit, String hoscode, String depcode);
2
- 在 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> scheduleRuleVoList = aggregateResult.getMappedResults();
//合并数据 map集合 key日期 value预约规则和数量信息
Map<Date, BookingScheduleRuleVo> scheduleVoMap = new HashMap<>();
if(!CollectionUtils.isEmpty(scheduleRuleVoList)){
scheduleVoMap = scheduleRuleVoList.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(0);
} 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.getHospName(hoscode));
//科室
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 currentDateTime = new DateTime().plusDays(i);
String dataSting = currentDateTime.toString("yyyy-MM-dd");
dateList.add(new DateTime(dataSting).toDate());
}
//因为预约周期是不同的,每页显示的日期最多七天的数据,超过七天分页
List<Date> pageDateList = new ArrayList<>();
int start = (page-1)*limit;
int end = (page-1)*limit+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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 获取科室信息
# 添加 service 接口以及实现类
- 在DepartmentService类添加接口
//根绝科室编号,和医院编号,查询科室
Department getDepartment(String hoscode, String depcode);
2
- 在DepartmentImpl类实现接口
@Override
public Department getDepartment(String hoscode, String depcode) {
return departmentRepository.getDepartemntByHoscodeAndDepcode(hoscode,depcode);
}
2
3
4
# 添加 Controller 接口
在HospitalApiController类添加方法
//获取可预约的排班数据
@ApiOperation(value = "获取可预约排版数据")
@GetMapping("/auth/getBookingScheduleRule/{page}/{limit}/{hoscode}/{depcode}")
public Result getBookingScheduleRule(@PathVariable Integer page,
@PathVariable Integer limit,
@PathVariable String hoscode,
@PathVariable String depcode){
return Result.ok(scheduleService.getBookingScheduleRule(page,limit,hoscode,depcode));
}
//获取排班的具体数据
@ApiOperation(value = "获取排班数据")
@GetMapping("/auth/findScheduleList/{hoscode}/{depcode}/{workData}")
public Result findScheduleList(@PathVariable String hoscode,
@PathVariable String depcode,
@PathVariable String workData){
return Result.ok(scheduleService.getDetailSchedule(hoscode,depcode,workData));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 前端访问结果
# 预约确认
- 根据排班id获取排班信息,在页面展示
- 选择就诊人
- 预约下单
# api 接口
# 添加 service 接口以及实现类
- 在ScheduleService类添加接口
//根绝排班id获取排班数据
Schedule getScheduleById(String scheduleId);
2
- 在ScheduleServiceImpl类实现接口
//根绝排班id获取排班数据
@Override
public Schedule getScheduleById(String scheduleId) {
Schedule schedule = scheduleRepository.findById(scheduleId).get();
return this.packageSchedule(schedule);
}
2
3
4
5
6
# 添加 Controller 接口
//根绝排班id获取排班数据
@ApiOperation(value = "根绝排班id获取排班数据")
@GetMapping("/getSchedule/{scheduleId}")
public Result getSchedule(@PathVariable String scheduleId){
Schedule schedule = scheduleService.getScheduleById(scheduleId);
return Result.ok(schedule);
}
2
3
4
5
6
7
- 前端访问测试
# 预约下单
由于预约下单后台api接口相对复杂,我们先实现前端,前端配合调试api接口。
# 需求分析
# 订单表结构
# 下单分析
下单参数:就诊人id与排班id
- 下单我们要获取就诊人信息
- 获取排班下单信息与规则信息
- 获取医院签名信息,然后通过接口去医院预约下单
- 下单成功更新排班信息与发送短信
# 搭建 service-order 模块
# 修改配置
- 修改 pom.xml,引入依赖
<dependencies>
<dependency>
<groupId>com.frx01</groupId>
<artifactId>service-cmn-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
2
3
4
5
6
7
- 添加配置文件 application.properties
# 服务端口
server.port=8206
# 服务名
spring.application.name=service-order
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/yygh_hosp?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=hsp
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.data.mongodb.uri=mongodb://192.168.91.166:27017/yygh_hosp
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 启动类
@SpringBootApplication
@ComponentScan(basePackages = {"com.frx01"})
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.frx01"})
public class ServiceOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOrderApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
# 配置网关
#设置路由id
spring.cloud.gateway.routes[6].id=service-order
#设置路由的uri
spring.cloud.gateway.routes[6].uri=lb://service-order
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[6].predicates= Path=/*/order/**
2
3
4
5
6
# 添加订单基础类
# 添加 model
说明:由于实体对象没有逻辑,我们已经统一导入
com.frx01.yygh.model.order.OrderInfo
# 添加 Mapper
添加com.frx01.yygh.order.mapper.OrderInfoMapper,添加配置类,扫描mapper包
public interface OrderInfoMapper extends BaseMapper<OrderInfo> {
}
2
# 添加 service 接口及实现类
- 添加com.frx01.yygh.order.service.OrderService接口
public interface OrderService extends IService<OrderInfo> {
//生成挂号的订单
Long saveOrder(String scheduleId, String patientId);
}
2
3
4
5
6
- 添加com.frx01.yygh.order.service.impl.OrderServiceImpl接口实现
@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderService {
@Override
public Long saveOrder(String scheduleId, String patientId) {
return null;
}
}
2
3
4
5
6
7
8
# 添加 Controller
@RestController
@RequestMapping("/api/order/orderInfo")
public class OrderApiController {
@Autowired
private OrderService orderService;
//生成挂号的订单
@PostMapping("/auth/submitOrder/{scheduleId}/{patientId}")
public Result saveOrders(@PathVariable String scheduleId,
@PathVariable String patientId){
Long orderId = orderService.saveOrder(scheduleId,patientId);
return Result.ok(orderId);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 封装 Feign 调用获取就诊人接口
# 获取就诊人信息 api 接口
操作模块:service-user
在PatientApiController类添加方法
//根绝就诊人id获取就诊人信息
@GetMapping("/inner/get/{id}")
public Patient getPatientOrder(@PathVariable Long id){
Patient patient = patientService.getPatientId(id);
return patient;
}
2
3
4
5
6
# 搭建 service-user-client 模块
# 添加 Feign 接口类
@FeignClient(value = "service-user")
@Repository
public interface PatientFeignClient {
//根绝就诊人id获取就诊人信息
@GetMapping("/api/user/patient/inner/get/{id}")
public Patient getPatientOrder(@PathVariable("id") Long id);
}
2
3
4
5
6
7
8
9
# 封装Feign调用获取排班下单信息接口
# 获取排班下单信息api接口
# 添加 service 接口与实现
- 在 ScheduleService 类添加接口
//根据排班id获取预约下单数据
ScheduleOrderVo getScheduleOrderVo(String scheduleId);
2
- 在ScheduleServiceImpl类 添加实现
@Override
public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
//获取排班的信息
Schedule schedule = baseMapper.selectById(scheduleId);
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(hospitalService.getHospName(schedule.getHoscode()));
scheduleOrderVo.setDepcode(schedule.getDepcode());
scheduleOrderVo.setDepname(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.setStopTime(stopTime.toDate());
return scheduleOrderVo;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 添加 Controller 方法
@ApiOperation(value = "根据排班id获取预约下单数据")
@GetMapping("/inner/getScheduleOrderVo/{scheduleId}")
public ScheduleOrderVo getScheduleOrderVo(@PathVariable("scheduleId") String scheduleId){
return scheduleService.getScheduleOrderVo(scheduleId);
}
2
3
4
5
# 添加 Feign 接口类
@FeignClient(value = "service-hosp")
@Repository
public interface HospitalFeignClient {
/**
* 根据排班id获取预约下单数据
* @param scheduleId
* @return
*/
@ApiOperation(value = "根据排班id获取预约下单数据")
@GetMapping("/api/hosp/hospital/inner/getScheduleOrderVo/{scheduleId}")
ScheduleOrderVo getScheduleOrderVo(@PathVariable("scheduleId") String scheduleId);
}
2
3
4
5
6
7
8
9
10
11
12
13
# 获取下单引用签名信息接口
# 添加 service 接口以及实现类
- 在HospitalSetService类添加接口
//获取医院签名信息
SignInfoVo getSignInfoVo(SignInfoVo hoscode);
2
- 在 HospitalSetServiceImpl 类实现
//根据医院编号获取医院签名信息
@Override
public SignInfoVo getSignInfoVo(SignInfoVo hoscode) {
QueryWrapper<HospitalSet> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("hoscode",hoscode);
HospitalSet hospitalSet = baseMapper.selectOne(queryWrapper);
if(null == hospitalSet){
throw new YyghException(ResultCodeEnum.HOSPITAL_OPEN);
}
SignInfoVo signInfoVo = new SignInfoVo();
signInfoVo.setSignKey(hospitalSet.getSignKey());
signInfoVo.setApiUrl(hospitalSet.getApiUrl());
return signInfoVo;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 添加 Controller 方法
@ApiOperation(value = "获取医院签名信息")
@GetMapping("/inner/getSignInfoVo/{hoscode}")
public SignInfoVo getSignInfoVo(@PathVariable("hoscode") SignInfoVo hoscode){
return hospitalSetService.getSignInfoVo(hoscode);
}
2
3
4
5
# 添加 Feign 接口类
/**
* 获取医院签名信息
* @param hoscode
* @return
*/
@ApiOperation(value = "获取医院签名信息")
@GetMapping("/api/hosp/hospital/inner/getSignInfoVo/{hoscode}")
SignInfoVo getSignInfoVo(@PathVariable("hoscode") String hoscode);
2
3
4
5
6
7
8
# 实现下单接口
操作模块:service-order
# 引入依赖
<dependencies>
<dependency>
<groupId>com.frx01</groupId>
<artifactId>service-cmn-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.frx01</groupId>
<artifactId>service-hosp-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.frx01</groupId>
<artifactId>service-user-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 封装下单工具类
封装HttpRequestHelper类,添加签名请求方法
@Slf4j
public class HttpRequestHelper {
/**
*
* @param paramMap
* @return
*/
public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
resultMap.put(param.getKey(), param.getValue()[0]);
}
return resultMap;
}
/**
* 请求数据获取签名
* @param paramMap
* @param signKey
* @return
*/
public static String getSign(Map<String, Object> paramMap, String signKey) {
if(paramMap.containsKey("sign")) {
paramMap.remove("sign");
}
TreeMap<String, Object> sorted = new TreeMap<>(paramMap);
StringBuilder str = new StringBuilder();
for (Map.Entry<String, Object> param : sorted.entrySet()) {
str.append(param.getValue()).append("|");
}
str.append(signKey);
log.info("加密前:" + str.toString());
String md5Str = MD5.encrypt(str.toString());
log.info("加密后:" + md5Str);
return md5Str;
}
/**
* 签名校验
* @param paramMap
* @param signKey
* @return
*/
public static boolean isSignEquals(Map<String, Object> paramMap, String signKey) {
String sign = (String)paramMap.get("sign");
String md5Str = getSign(paramMap, signKey);
if(!sign.equals(md5Str)) {
return false;
}
return true;
}
/**
* 获取时间戳
* @return
*/
public static long getTimestamp() {
return new Date().getTime();
}
/**
* 封装同步请求
*/
public static JSONObject sendRequest(Map<String, Object> paramMap, String url){
String result = "";
try {
//封装post参数
StringBuilder postdata = new StringBuilder();
for (Map.Entry<String, Object> param : paramMap.entrySet()) {
postdata.append(param.getKey()).append("=")
.append(param.getValue()).append("&");
}
log.info(String.format("--> 发送请求:post data %1s", postdata));
byte[] reqData = postdata.toString().getBytes("utf-8");
byte[] respdata = HttpUtil.doPost(url,reqData);
result = new String(respdata);
log.info(String.format("--> 应答结果:result data %1s", result));
} catch (Exception ex) {
ex.printStackTrace();
}
return JSONObject.parseObject(result);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 预约成功后处理逻辑
预约成功后我们要更新预约数和短信提醒预约成功,为了提高下单的并发性,这部分逻辑我们就交给mq为我们完成,预约成功发送消息即可。
- RabbitMQ简介
以商品订单场景为例,
如果商品服务和订单服务是两个不同的微服务,在下单的过程中订单服务需要调用商品服务进行扣库存操作。按照传统的方式,下单过程要等到调用完毕之后才能返回下单成功,如果网络产生波动等原因使得商品服务扣库存延迟或者失败,会带来较差的用户体验,如果在高并发的场景下,这样的处理显然是不合适的,那怎么进行优化呢?这就需要消息队列登场了。
消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通的时候在将消息转发给相应的应用程序或者服务,当然前提是这些服务订阅了该队列。如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。
RabbitMQ就是这样一款消息队列。RabbitMQ是一个开源的消息代理的队列服务器,用来通过普通协议在完全不同的应用之间共享数据。
- 典型应用场景:
异步处理。把消息放入消息中间件中,等到需要的时候再去处理。
流量削峰。例如秒杀活动,在短时间内访问量急剧增加,使用消息队列,当消息队列满了就拒绝响应,跳转到错误页面,这样就可以使得系统不会因为超负载而崩溃。
日志处理
应用解耦
- 安装RabbitMQ
docker pull rabbitmq:management
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
2
管理后台:http://IP:15672
- 访问
# rabbit-util 模块封装
由于后续可能多个模块都会使用mq,所以我们把它封装成一个模块,需要的地方直接引用即可
# 修改 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
3
4
5
6
7
8
9
10
11
12
13
14
15
# 封装 service 方法
@Service
public class RabbitService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param exchange 交换机
* @param rootingKey 路由键
* @param message 消息
* @return
*/
public boolean sendMessage(String exchange,String rootingKey,Object message){
rabbitTemplate.convertAndSend(exchange,rootingKey,message);
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 配置 mq 消息转换器
@Configuration
public class MQconfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
2
3
4
5
6
7
8
说明:默认是字符串转换器
# 封装短信接口
操作模块:service-msm
# 引入依赖
<dependency>
<groupId>com.frx01</groupId>
<artifactId>rabbit-util</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2
3
4
5
# 添加配置
在resources/application.properties添加
#rabbitmq地址
spring.rabbitmq.host=192.168.91.166
spring.rabbitmq.port=15672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
2
3
4
5
# 添加常量配置
在rabbit-util模块com.frx01.yygh.common.constant.MqConst类添加
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";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 在model模块封装短信实体
@Data
@ApiModel(description = "短信实体")
public class MsmVo {
@ApiModelProperty(value = "phone")
private String phone;
@ApiModelProperty(value = "短信模板code")
private String templateCode;
@ApiModelProperty(value = "短信模板参数")
private Map<String,Object> param;
}
2
3
4
5
6
7
8
9
10
11
12
13
说明:已统一引入
# 封装 service 接口
- 在 MsmService 类添加接口
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;
}
2
3
4
5
6
7
8
9
10
# 封装 mq 监听器
@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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 封装更新排班数量
操作模块:service-hosp
# 引入依赖
<dependency>
<groupId>com.frx01</groupId>
<artifactId>rabbit-util</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2
3
4
5
# 添加配置
在resources/application.properties添加
#rabbitmq地址
spring.rabbitmq.host=192.168.91.166
spring.rabbitmq.port=15672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
2
3
4
5
# 添加常量配置
在rabbit-util模块com.frx01.yygh.common.constant.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";
2
3
4
5
6
7
# 在model模块封装更新排班实体
@Data
@ApiModel(description = "短信实体")
public class MsmVo {
@ApiModelProperty(value = "phone")
private String phone;
@ApiModelProperty(value = "短信模板code")
private String templateCode;
@ApiModelProperty(value = "短信模板参数")
private Map<String,Object> param;
}
2
3
4
5
6
7
8
9
10
11
12
13
说明:已统一引入,该对象放一个短信实体,预约下单成功后,我们发送一条消息,让mq来保证两个消息都发送成功。
# 封装 service 接口
- 在ScheduleService类添加接口
//更新排版数据
void update(Schedule schedule);
2
- 在ScheduleServiceImpl类添加接口实现
//更新排班信息,用于mq的操作
@Override
public void update(Schedule schedule) {
schedule.setUpdateTime(new Date());
scheduleRepository.save(schedule);
}
2
3
4
5
6
# 封装mq监听器
@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);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 调整下单接口
操作模块:service-order
# 引入依赖
<dependency>
<groupId>com.frx01</groupId>
<artifactId>service-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2
3
4
5
# 添加配置
在resources/application.properties添加
#rabbitmq地址
spring.rabbitmq.host=192.168.91.166
spring.rabbitmq.port=15672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
2
3
4
5
# 修改下单接口
//生成挂号订单
@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(scheduleId);
orderInfo.setUserId(patient.getUserId());
orderInfo.setPatientId(patientId);
orderInfo.setPatientName(patient.getName());
orderInfo.setPatientPhone(patient.getPhone());
orderInfo.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());
this.save(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消息,号源更新和短信通知
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();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
- 前端测试访问
- 得到就诊人信息
- 得到排班信息
- 得到相关信息
- 调用医院接口
2022-11-13 08:48:29.151 INFO 7572 --- [nio-8206-exec-1] com.frx01.helper.HttpRequestHelper : --> 应答结果:result data {"code":200,"message":"成功","data":{"number":12,"fetchAddress":"一层114窗口","reservedNumber":33,"availableNumber":21,"resultCode":"0000","fetchTime":"2022-11-1409:00前","hosRecordId":11,"resultMsg":"预约成功"}}
2022-11-13 08:48:29.437 INFO 7572 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: service-user.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2022-11-13 08:48:29.593 INFO 7572 --- [erListUpdater-1] c.netflix.config.ChainedDynamicProperty : Flipping property: service-hosp.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2
3
# 订单详情功能
# 添加 servcie 接口以及实现类
- 在OrderInfoService类添加接口
//根据订单的id查询订单详情
OrderInfo getOrder(String orderId);
2
- 在OrderInfoServiceImpl类添加接口实现
//根据订单id查询订单详情
@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
3
4
5
6
7
8
9
10
11
# 添加 Controller
//根据订单的id查询订单详情
@GetMapping("/auth/getOrders/{orderId}")
public Result getOrders(@PathVariable String orderId){
OrderInfo orderInfo = orderService.getOrder(orderId);
return Result.ok(orderInfo);
}
2
3
4
5
6
- 前端测试访问
# 订单列表功能
# 添加 service 接口以及实现
- 在 OrderInfoSerice 接口及实现类
//订单列表(条件查询带分页)
IPage<OrderInfo> selectPage(Page<OrderInfo> pageParam, OrderQueryVo orderQueryVo);
2
- 在 OrderInfoServiceImpl 类添加接口实现
//订单列表(条件查询带分页)
@Override
public IPage<OrderInfo> selectPage(Page<OrderInfo> pageParam, OrderQueryVo orderQueryVo) {
//OrderQueryVo获取条件值
String name = orderQueryVo.getKeyword();//医院名称
String patientName = orderQueryVo.getPatientName();//就诊人的名称
String orderStatus = orderQueryVo.getOrderStatus();//订单的状态
String reserveDate = orderQueryVo.getReserveDate();//安排日期
String createTimeBegin = orderQueryVo.getCreateTimeBegin();//开始时间
String createTimeEnd = orderQueryVo.getCreateTimeEnd();//结束时间
//对条件值进行非空的判断
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
if(!StringUtils.isEmpty(name)){
queryWrapper.like("hosname",name);
}
if(!StringUtils.isEmpty(patientName)){
queryWrapper.eq("patient_name",patientName);
}
if(!StringUtils.isEmpty(orderStatus)){
queryWrapper.eq("order_status",orderStatus);
}
if(!StringUtils.isEmpty(reserveDate)){
queryWrapper.ge("reserce_date",reserveDate);
}
if(!StringUtils.isEmpty(createTimeBegin)){
queryWrapper.ge("create_time",createTimeBegin);
}
if(!StringUtils.isEmpty(createTimeEnd)){
queryWrapper.le("create_time",createTimeEnd);
}
//调用mapper的方法
Page<OrderInfo> pages = baseMapper.selectPage(pageParam, queryWrapper);
pages.getRecords().stream().forEach(item -> {
this.packOrderInfo(item);
});
return pages;
}
private OrderInfo packOrderInfo(OrderInfo orderInfo){
orderInfo.getParam().put("orderStatusString",OrderStatusEnum.getStatusNameByStatus(orderInfo.getOrderStatus()));
return orderInfo;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 添加 Controller
//订单列表(条件查询带分页)
@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);
}
//获取订单状态
@GetMapping("/auth/getStatusList")
public Result getStatusList(){
return Result.ok(OrderStatusEnum.getStatusList());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
说明:订单状态我们是封装到枚举中的,页面搜索需要一个下拉列表展示,所以我们通过接口返回页面
- 前端访问测试
# 订单支付(生成二维码)
# 微信支付介绍
# 微信扫码支付申请
微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
申请步骤:(了解)
第一步:注册公众号(类型须为:服务号)
请根据营业执照类型选择以下主体注册:个体工商户 (opens new window)| 企业/公司 (opens new window)| 政府 (opens new window)| 媒体 (opens new window)| 其他类型 (opens new window)。
第二步:认证公众号
公众号认证后才可申请微信支付,认证费:300元/年。
第三步:提交资料申请微信支付
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。
第四步:开户成功,登录商户平台进行验证
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。
第五步:在线签署协议
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。
# 开发文档
微信支付接口调用的整体思路:
按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。
在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html
- appid:微信公众账号或开放平台APP的唯一标识
- mch_id:商户号 (配置文件中的partner)
- partnerkey:商户密钥
- sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性
# 微信支付SDK
添加依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2
3
4
5
我们主要会用到微信支付SDK的以下功能
- 获取随机字符串
WXPayUtil.generateNonceStr()
- MAP转换为XML字符串(自动添加签名)
WXPayUtil.generateSignedXml(param, partnerkey)
- XML字符串转换为MAP
WXPayUtil.xmlToMap(result)
# 微信支付开发
# api 接口
场景:用户扫描商户展示在各种场景的二维码进行支付
使用案例:
线下:家乐福超市、7-11便利店、上品折扣线下店等
线上:大众点评网站、携程网站、唯品会、美丽说网站等
开发模式:
模式一:商户在后台给你生成二维码,用户打开扫一扫
模式二:商户后台系统调用微信支付【统一下单API (opens new window)】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:**该模式的预付单有效期为2小时,**过期后无法支付。
微信支付:生成xml发送请求
操作模块:service-order
# 引入依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2
3
4
5
# 添加配置
在application.properties中添加商户信息
spring.redis.host=192.168.91.166
spring.redis.port=6379
spring.redis.database= 0
spring.redis.password=mima
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#关联的公众号appid
weixin.pay.appid=wx74862e0dfcf69954
#商户号
weixin.pay.partner=1558950191
#商户key
weixin.pay.partnerkey=T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 引入工具类
@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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 添加交易记录接口
# 添加 Mapper
public interface PaymentMapper extends BaseMapper<PaymentInfo> {
}
2
# 添加service接口与实现
- 添加PaymentService 类
public interface PaymentService extends IService<PaymentInfo> {
//向支付记录表添加信息
void savePaymentInfo(OrderInfo orderInfo, Integer status);
}
2
3
4
5
- 添加PaymentServiceImpl实现类
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, PaymentInfo> implements PaymentService {
//向支付记录表添加信息
@Override
public void savePaymentInfo(OrderInfo orderInfo, Integer status) {
//根据订单id和支付类型,查询支付记录表里面是否存在相同的订单
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_id",orderInfo.getId());
queryWrapper.eq("payment_type",status);
Integer count = baseMapper.selectCount(queryWrapper);
if(count>0){
return;
}
//添加记录
PaymentInfo paymentInfo = new PaymentInfo();
paymentInfo.setCreateTime(new Date());
paymentInfo.setOrderId(orderInfo.getId());
paymentInfo.setPaymentType(status);
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 添加支付service接口与实现
- 添加com.frx01.yygh.order.service.WeixinService类
public interface WeiXinService {
//生成微信支付扫描的二维码
Map createNative(Long orderId);
}
2
3
4
5
- 添加com.frx01.yygh.order.service.impl.WeixinServiceImpl类
@Service
public class WeiXinServiceImpl implements WeiXinService {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@Autowired
private RedisTemplate redisTemplate;
//生成微信支付扫描的二维码
@Override
public Map createNative(Long orderId) {
//1.根据orderId获取订单信息
OrderInfo orderInfo = orderService.getById(orderId);
//2.向支付记录表添加信息
paymentService.savePaymentInfo(orderInfo, PaymentTypeEnum.WEIXIN.getStatus());
try {
//从redis获取数据
Map payMap = (Map)redisTemplate.opsForValue().get(orderId.toString());
if(payMap!=null){
return payMap;
}
//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 client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
//设置map参数
client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantPropertiesUtils.PARTNERKEY));
client.setHttps(true);
client.post();
//5.返回相关的数据
String xml = client.getContent();
//转换成map集合
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
System.out.println("resultMap=:" + resultMap);
//6.封装返回结果集
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"));//二维码地址
if(resultMap.get("result_code")!=null){
redisTemplate.opsForValue().set(orderId.toString(),map,120, TimeUnit.MINUTES);
}
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 添加 Controller 方法
@Api(tags = "微信支付接口")
@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
3
4
5
6
7
8
9
10
11
12
13
14
15
- 前端支付测试
# 处理支付结果
# 添加 service 接口以及实现
- 在PaymentService类添加接口
//更新订单状态
void paySucess(String out_trade_no, Map<String, String> resultMap);
2
- 在PaymentServiceImpl类添加实现
//更新订单状态
@Override
public void paySucess(String out_trade_no, Map<String, String> resultMap) {
//1.根据订单编号得到支付记录
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("out_trade_no",out_trade_no);
queryWrapper.eq("payment_type", PaymentTypeEnum.WEIXIN.getStatus());
PaymentInfo paymentInfo = baseMapper.selectOne(queryWrapper);
//2.更新支付记录信息
paymentInfo.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());
paymentInfo.setCallbackTime(new Date());
paymentInfo.setTradeNo(resultMap.get("transaction_id"));
paymentInfo.setCallbackContent(resultMap.toString());
baseMapper.updateById(paymentInfo);
//3.根据订单号得到订单信息
//4.更新订单信息
OrderInfo orderInfo = orderService.getById(paymentInfo.getOrderId());
orderInfo.setOrderStatus(OrderStatusEnum.PAID.getStatus());
orderService.updateById(orderInfo);
//5.调用医院接口,更新订单支付信息
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 更新医院支付状态
//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
4
5
6
7
8
9
10
11
- 前端访问测试
- 查看控制台
支付状态resultMap:{transaction_id=4200001670202211168362613899, nonce_str=mDMDU1ap2Lqlg9QR, trade_state=SUCCESS, bank_type=OTHERS, openid=oHwsHuL_t99-ri2ZsWbBVi4CNGXI, sign=6BB001DF5E713892D5D5142615D56718, return_msg=OK, fee_type=CNY, mch_id=1558950191, cash_fee=1, out_trade_no=16685284490991, cash_fee_type=CNY, appid=wx74862e0dfcf69954, total_fee=1, trade_state_desc=支付成功, trade_type=NATIVE, result_code=SUCCESS, attach=, time_end=20221116000748, is_subscribe=N, return_code=SUCCESS}
- 查看数据库
# 取消预约
# 需求描述
取消订单分两种情况:
- 未支付取消订单,直接通知医院更新取消预约状态
- 已支付取消订单,先退款给用户,然后通知医院更新取消预约状态
# 开发微信退款接口
该接口需要使用证书,详情参考文档并下载证书
# 配置证书
请下载的证书放在service-order模块/resources/cert文件夹下
在application.properties文件配置证书路径
#退款证书
weixin.cert=cert/apiclient_cert.p12
2
# 添加获取支付记录接口
退款我们是根据支付记录发起退款的
- 在PaymentService类添加接口
//获取支付记录
PaymentInfo getPaymentInfo(Long orderId,Integer paymentType);
2
- 在PaymentServiceImpl类添加实现
//获取支付记录
@Override
public PaymentInfo getPaymentInfo(Long orderId, Integer paymentType) {
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_id",orderId);
queryWrapper.eq("payment_type",paymentType);
PaymentInfo paymentInfo = baseMapper.selectOne(queryWrapper);
return paymentInfo;
}
2
3
4
5
6
7
8
9
# 添加退款记录
# 添加 mapper
public interface RefundInfoMapper extends BaseMapper<RefundInfo> {
}
2
# 添加service接口与实现
- 添加service接口
public interface RefundInfoService extends IService<RefundInfo> {
//保存退款记录
RefundInfo saveRefundInfo(PaymentInfo paymentInfo);
}
2
3
4
5
- 添加service接口实现
@Service
public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundInfo> implements RefundInfoService {
//保存退款记录
@Override
public RefundInfo saveRefundInfo(PaymentInfo paymentInfo) {
//判断是否有重复数据添加
QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_id",paymentInfo.getOrderId());
queryWrapper.eq("payment_type",paymentInfo.getPaymentType());
RefundInfo refundInfo = baseMapper.selectOne(queryWrapper);
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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 添加微信退款接口
- 在WeixinService添加接口
//退款
Boolean refund(Long orderId);
2
- 在WeixinServiceImpl添加实现
//微信退款
@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<>();
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;
}
return false;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 完成取消预约
# 添加service接口与实现
- 在OrderService添加接口
//取消预约
boolean cancelOrder(Long orderId);
2
- 在OrderServiceImpl添加实现
//取消预约
@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, "http://localhost:9998/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());
msmVo.setTemplateCode("SMS_194640722");
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 添加 Controller 方法
//取消预约
@GetMapping("/auth/cancelOrder/{orderId}")
public Result cancelOrder(@PathVariable Long orderId){
boolean isOrder = orderService.cancelOrder(orderId);
return Result.ok(isOrder);
}
2
3
4
5
6