十二、RabbitMQ
RabbitMQ模块搭建
搭建share-common-rabbit模块
pom.xml
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.share</groupId>
<artifactId>share-common</artifactId>
<version>3.6.3</version>
</parent>
<artifactId>share-common-rabbit</artifactId>
<description>
share-common-rabbit服务
</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--rabbitmq消息队列-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 缓存服务 -->
<dependency>
<groupId>com.share</groupId>
<artifactId>share-common-redis</artifactId>
</dependency>
</dependencies>
</project>
RabbitService
java
package com.share.common.rabbit.service;
import com.alibaba.fastjson2.JSON;
import com.share.common.rabbit.entity.GuiguCorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class RabbitService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisTemplate redisTemplate;
/**
* 发送消息
* @param exchange 交换机
* @param routingKey 路由键
* @param message 消息
*/
public boolean sendMessage(String exchange, String routingKey, Object message) {
//1.创建自定义相关消息对象-包含业务数据本身,交换器名称,路由键,队列类型,延迟时间,重试次数
GuiguCorrelationData correlationData = new GuiguCorrelationData();
String uuid = "mq:" + UUID.randomUUID().toString().replaceAll("-", "");
correlationData.setId(uuid);
correlationData.setMessage(message);
correlationData.setExchange(exchange);
correlationData.setRoutingKey(routingKey);
//2.将相关消息封装到发送消息方法中
rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);
//3.将相关消息存入Redis Key:UUID 相关消息对象 10 分钟
redisTemplate.opsForValue().set(uuid, JSON.toJSONString(correlationData), 10, TimeUnit.MINUTES);
return true;
}
/**
* 发送延迟消息方法
* @param exchange 交换机
* @param routingKey 路由键
* @param message 消息数据
* @param delayTime 延迟时间,单位为:秒
*/
public boolean sendDealyMessage(String exchange, String routingKey, Object message, int delayTime) {
//1.创建自定义相关消息对象-包含业务数据本身,交换器名称,路由键,队列类型,延迟时间,重试次数
GuiguCorrelationData correlationData = new GuiguCorrelationData();
String uuid = "mq:" + UUID.randomUUID().toString().replaceAll("-", "");
correlationData.setId(uuid);
correlationData.setMessage(message);
correlationData.setExchange(exchange);
correlationData.setRoutingKey(routingKey);
correlationData.setDelay(true);
correlationData.setDelayTime(delayTime);
//2.将相关消息封装到发送消息方法中
rabbitTemplate.convertAndSend(exchange, routingKey, message,message1 -> {
message1.getMessageProperties().setDelay(delayTime*1000);
return message1;
}, correlationData);
//3.将相关消息存入Redis Key:UUID 相关消息对象 10 分钟
redisTemplate.opsForValue().set(uuid, JSON.toJSONString(correlationData), 10, TimeUnit.MINUTES);
return true;
}
}
加载配置类
java
com.share.common.rabbit.service.RabbitService
com.share.common.rabbit.config.RabbitInitConfigApplicationListener
MqConst
提供常量类 MqConst
java
package com.share.common.rabbit.constant;
public class MqConst {
/**
* 测试
*/
public static final String EXCHANGE_TEST = "spzx.test";
public static final String ROUTING_TEST = "spzx.test";
public static final String ROUTING_CONFIRM = "spzx.confirm";
//队列
public static final String QUEUE_TEST = "spzx.test";
public static final String QUEUE_CONFIRM = "spzx.confirm";
/**
* 订单
*/
public static final String EXCHANGE_ORDER = "share.order";
public static final String ROUTING_SUBMIT_ORDER = "share.submit.order";
public static final String ROUTING_END_ORDER = "share.end.order";
//队列
public static final String QUEUE_SUBMIT_ORDER = "share.submit.order";
public static final String QUEUE_END_ORDER = "share.end.order";
/**
* 支付
*/
public static final String EXCHANGE_PAYMENT_PAY = "share.payment";
public static final String ROUTING_PAYMENT_PAY = "share.payment.pay";
public static final String QUEUE_PAYMENT_PAY = "share.payment.pay";
/**
* 取消订单延迟消息
*/
public static final String EXCHANGE_DEVICE = "share.device";
public static final String ROUTING_UNLOCK_SLOT = "share.unlock.slot";
public static final String QUEUE_UNLOCK_SLOT = "share.unlock.slot";
public static final Integer CANCEL_UNLOCK_SLOT_DELAY_TIME = 1 * 5;
}
创建订单
发送消息
PowerBankUnlockHandler
java
@Autowired
private RabbitService rabbitService;
@Transactional(rollbackFor = Exception.class)
@Override
public void handleMessage(JSONObject message) {
log.info("handleMessage: {}", message.toJSONString());
...
//构建订单对象
SubmitOrderVo submitOrderVo = new SubmitOrderVo();
submitOrderVo.setMessageNo(messageNo);
submitOrderVo.setUserId(userId);
submitOrderVo.setPowerBankNo(powerBankNo);
submitOrderVo.setStartStationId(station.getId());
submitOrderVo.setStartStationName(station.getName());
submitOrderVo.setStartCabinetNo(cabinetNo);
submitOrderVo.setFeeRuleId(station.getFeeRuleId());
log.info("构建订单对象: {}", JSONObject.toJSONString(submitOrderVo));
//发送信息
rabbitService.sendMessage(MqConst.EXCHANGE_ORDER, MqConst.ROUTING_SUBMIT_ORDER, JSONObject.toJSONString(submitOrderVo));
}
SubmitOrderVo
java
package com.share.order.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class SubmitOrderVo {
@Schema(description = "消息编号")
private String messageNo;
@Schema(description = "用户Id")
private Long UserId;
//送货地址id
@Schema(description = "充电宝编号")
private String powerBankNo;
/** 借用站点id */
@Schema(description = "借用站点id")
private Long startStationId;
/** 借用地点名称 */
@Schema(description = "借用地点名称")
private String startStationName;
/** 借用柜机编号 */
@Schema(description = "借用柜机编号")
private String startCabinetNo;
@Schema(description = "费用规则id")
private Long feeRuleId;
}
接收消息
OrderReceiver
java
package com.share.order.receiver;
@Slf4j
@Component
public class OrderReceiver {
@Autowired
private IOrderInfoService orderInfoService;
@Autowired
private RedisTemplate redisTemplate;
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = MqConst.EXCHANGE_ORDER, durable = "true"),
value = @Queue(value = MqConst.QUEUE_SUBMIT_ORDER, durable = "true"),
key = MqConst.ROUTING_SUBMIT_ORDER
))
public void submitOrder(String content, Message message, Channel channel) {
log.info("[订单服务]租借充电宝消息:{}", content);
SubmitOrderVo orderForm = JSONObject.parseObject(content, SubmitOrderVo.class);
String messageNo = orderForm.getMessageNo();
//防止重复请求
String key = "order:submit:" + messageNo;
boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, messageNo, 1, TimeUnit.HOURS);
if (!isExist) {
log.info("重复请求: {}", content);
return;
}
try {
orderInfoService.saveOrder(orderForm);
//手动应答
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
log.error("订单服务:订单归还失败,订单编号:{}", messageNo, e);
redisTemplate.delete(key);
// 消费异常,重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
IOrderInfoService
在 "OrderlnfoService'中创建方法'saveOrder'
java
/**
* 保存订单信息的方法
*
* @param submitOrderVo 提交订单的视图对象,包含订单相关的所有必要信息
* @return Long 返回新创建订单的唯一标识符(ID)
*/
Long saveOrder(SubmitOrderVo submitOrderVo);
OrderInfoServiceI
实现方法
java
@Autowired
private RemoteFeeRuleService remoteFeeRuleService;
@Transactional(rollbackFor = Exception.class)
@Override
public Long saveOrder(SubmitOrderVo orderForm) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setUserId(orderForm.getUserId());
orderInfo.setOrderNo(RandomUtil.randomString(8));
orderInfo.setPowerBankNo(orderForm.getPowerBankNo());
orderInfo.setStartTime(new Date());
orderInfo.setStartStationId(orderForm.getStartStationId());
orderInfo.setStartStationName(orderForm.getStartStationName());
orderInfo.setStartCabinetNo(orderForm.getStartCabinetNo());
// 费用规则
FeeRule feeRule = remoteFeeRuleService.getFeeRule(orderForm.getFeeRuleId(), SecurityConstants.INNER).getData();
orderInfo.setFeeRuleId(orderForm.getFeeRuleId());
orderInfo.setFeeRule(feeRule.getDescription());
orderInfo.setStatus("0");
orderInfo.setCreateTime(new Date());
orderInfo.setCreateBy(SecurityUtils.getUsername());
//用户昵称
UserInfo userInfo = remoteUserInfoService.getInfo(orderInfo.getUserId()).getData();
//orderInfo.setNickname(userInfo.getNickname());
orderInfoMapper.insert(orderInfo);
return orderInfo.getId();
}
十三、充电宝归还
充电宝计费规则
| 时间段收费 |
|---|
| 前5分钟免费 |
| 5分钟后,每小时3元,24小时35元,不足1小时按1小时算 |
| 超过24小时99元 |
| 时间与费用可以动态调整 |
集成Drools
操作模块:share-rule
pom.xml
引入依赖,版本:8.41.0.Final,父级模块已加入版本管理
java
<!-- drools lib -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
</dependency>
DroolsConfig
Drools配置类
java
package com.share.rules.config;
@Slf4j
@Configuration
public class DroolsConfig {
// 制定规则文件的路径
private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";
@Bean
public KieContainer kieContainer() {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kieContainer;
}
}
FeeRule.drl
创建规则文件resources/rules/FeeRule.drl
java
package com.share.rules
import com.share.rules.domain.vo.FeeRuleRequest;
global com.share.rules.domain.vo.FeeRuleResponse feeRuleResponse;
rule "前5分钟免费"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(durations >= 0)
then
feeRuleResponse.setFreeDescription("前5分钟免费");
feeRuleResponse.setTotalAmount(0.0);
feeRuleResponse.setFreePrice(0.0);
feeRuleResponse.setExceedPrice(0.0);
System.out.println("前5分钟免费");
end
rule "每1小时3元,24小时35"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(durations > 5 && (durations - 5) <= 24*60)
then
int hour = ($rule.getDurations() - 5)/60;
double exceedPrice = (hour + 1) * 3.0;
if(exceedPrice > 35.0) {
exceedPrice = 35.0;
}
feeRuleResponse.setFreeDescription("前5分钟免费");
feeRuleResponse.setTotalAmount(exceedPrice);
feeRuleResponse.setFreePrice(0.0);
feeRuleResponse.setExceedPrice(exceedPrice);
int minute = $rule.getDurations() - 5;
feeRuleResponse.setExceedDescription("去除免费时长5分钟,计费时长:"+ minute + "分钟");
System.out.println("24小时内费用:" + exceedPrice + "元");
end
rule "超24小时99元"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest((durations - 5) > 24*60)
then
feeRuleResponse.setFreeDescription("前5分钟免费");
feeRuleResponse.setTotalAmount(99.0);
feeRuleResponse.setFreePrice(0.0);
feeRuleResponse.setExceedPrice(99.0);
int minute = $rule.getDurations() - 5;
feeRuleResponse.setExceedDescription("去除免费时长5分钟,计费时长:"+ minute + "分钟,超24小时");
System.out.println("超24小时99元");
end
费用计算接口
FeeRuleRequest
封装输入对象
java
package com.share.rules.domain;
@Data
public class FeeRuleRequest {
@Schema(description = "借用时长")
private Integer durations;
@Schema(description = "超出免费时长的小时数")
private Integer exceedHours;
}
FeeRuleResponse
封装输出对象
java
package com.share.rules.domain;
@Data
public class FeeRuleResponse {
@Schema(description = "总金额")
private Double totalAmount;
@Schema(description = "免费价格")
private Double freePrice;
@Schema(description = "免费描述")
private String freeDescription;
@Schema(description = "超出免费分钟的价格")
private Double exceedPrice;
@Schema(description = "超出免费分钟描述")
private String exceedDescription;
}
封装微服务接口
FeeRuleApiController
java
//计算订单费用
@PostMapping("/calculateOrderFee")
public R<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm feeRuleRequestForm) {
FeeRuleResponseVo feeRuleResponseVo =
feeRuleService.calculateOrderFee(feeRuleRequestForm);
return R.ok(feeRuleResponseVo);
}
IFeeRuleService
在'IFeeRuleService'中创建方法'calculateOrderFee'
java
FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm feeRuleRequestForm);
FeeRuleServiceImpl
实现方法
java
//计算订单费用
@Override
public FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm feeRuleRequestForm) {
//1 开启会话
KieSession kieSession = kieContainer.newKieSession();
//2 创建传入数据对象 设置数据
FeeRuleRequest feeRuleRequest = new FeeRuleRequest();
feeRuleRequest.setDurations(feeRuleRequestForm.getDuration());
//3 创建返回数据对象
FeeRuleResponse feeRuleResponse = new FeeRuleResponse();
kieSession.setGlobal("feeRuleResponse", feeRuleResponse);
//4 对象传入到会话对象里面
kieSession.insert(feeRuleRequest);
//5 触发规则
kieSession.fireAllRules();
//6 中止会话
kieSession.dispose();
//7 封装返回需要数据
FeeRuleResponseVo feeRuleResponseVo = new FeeRuleResponseVo();
feeRuleResponseVo.setTotalAmount(new BigDecimal(feeRuleResponse.getTotalAmount()));
feeRuleResponseVo.setFreePrice(new BigDecimal(feeRuleResponse.getFreePrice()));
feeRuleResponseVo.setFreeDescription(feeRuleResponse.getFreeDescription());
feeRuleResponseVo.setExceedDescription(feeRuleResponse.getExceedDescription());
feeRuleResponseVo.setExceedPrice(new BigDecimal(feeRuleResponse.getExceedPrice()));
return feeRuleResponseVo;
}
FeeRuleRequestForm
远程调用的传入参数
java
package com.share.rules.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class FeeRuleRequestForm {
@Schema(description = "费用规则id")
private Long FeeRuleId;
@Schema(description = "借用时长")
private Integer duration;
}
FeeRuleResponseVo
远程调用的返回值
java
package com.share.rules.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class FeeRuleResponseVo {
@Schema(description = "总金额")
private BigDecimal totalAmount;
@Schema(description = "免费价格")
private BigDecimal freePrice;
@Schema(description = "免费描述")
private String freeDescription;
@Schema(description = "超出免费分钟的价格")
private BigDecimal exceedPrice;
@Schema(description = "超出免费分钟描述")
private String exceedDescription;
}
封装Feign接口
RemoteFeeRuleService
java
/**
* 计算订单费用的接口方法
*
* @param feeRuleRequestForm 请求参数,包含计算订单费用所需的信息
* @return 返回一个R对象,其中包含FeeRuleResponseVo类型的响应数据
* @PostMapping 指定请求方式为POST,请求路径为"/feeRule/calculateOrderFee"
*/
@PostMapping("/feeRule/calculateOrderFee")
public R<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm feeRuleRequestForm);
RemoteFeeRuleFallbackFactory
java
@Override
public R<FeeRuleResponseVo> calculateOrderFee(FeeRuleRequestForm calculateOrderFeeForm) {
return R.fail("费用计算失败:" + throwable.getMessage());
}
规则接口改造
充电宝归还
业务接口实现
EndOrderVo
java
package com.share.order.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
@Data
public class EndOrderVo {
@Schema(description = "消息编号")
private String messageNo;
@Schema(description = "归还时间")
private Date endTime;
@Schema(description = "归还站点id")
private Long endStationId;
@Schema(description = "归还地点名称")
private String endStationName;
@Schema(description = "归还柜机编号")
private String endCabinetNo;
@Schema(description = "充电宝编号")
private String powerBankNo;
}
PowerBankConnectedHandler
java
package com.share.device.emqx.handler.impl;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.common.core.constant.DeviceConstants;
import com.share.common.core.utils.StringUtils;
import com.share.common.rabbit.constant.MqConst;
import com.share.common.rabbit.service.RabbitService;
import com.share.device.domain.Cabinet;
import com.share.device.domain.CabinetSlot;
import com.share.device.domain.PowerBank;
import com.share.device.domain.Station;
import com.share.device.emqx.annotation.GuiguEmqx;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.emqx.handler.MassageHandler;
import com.share.device.service.ICabinetService;
import com.share.device.service.ICabinetSlotService;
import com.share.device.service.IPowerBankService;
import com.share.device.service.IStationService;
import com.share.order.api.RemoteOrderInfoService;
import com.share.order.domain.EndOrderVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@GuiguEmqx(topic = EmqxConstants.TOPIC_POWERBANK_CONNECTED)
public class PowerBankConnectedHandler implements MassageHandler {
@Autowired
private ICabinetService cabinetService;
@Autowired
private IPowerBankService powerBankService;
@Autowired
private ICabinetSlotService cabinetSlotService;
@Autowired
private IStationService stationService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RemoteOrderInfoService remoteOrderInfoService;
@Autowired
private RabbitService rabbitService;
@Transactional(rollbackFor = Exception.class)
@Override
public void handleMessage(JSONObject message) {
//1 获取messageNo,防止重复提交
String messageNo = message.getString("mNo");
String key = "powerBank:connected:" + messageNo;
Boolean ifAbsent =
redisTemplate.opsForValue().setIfAbsent(key, messageNo, 1, TimeUnit.HOURS);
if (!ifAbsent) {
return;
}
//2 获取cabinetNo powerBankNo slotNo electricity
String cabinetNo = message.getString("cNo");
//充电宝编号
String powerBankNo = message.getString("pNo");
//插槽编号
String slotNo = message.getString("sNo");
//当前电量
BigDecimal electricity = message.getBigDecimal("ety");
//3 非空判断
if (StringUtils.isEmpty(cabinetNo)
|| StringUtils.isEmpty(powerBankNo)
|| StringUtils.isEmpty(slotNo)
|| null == electricity) {
log.info("参数为空: {}", message.toJSONString());
return;
}
//4 获取相关数据 柜机、充电宝、插槽数据
Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);
// 获取充电宝
PowerBank powerBank = powerBankService.getByPowerBankNo(powerBankNo);
// 获取插槽
CabinetSlot cabinetSlot = cabinetSlotService.getBtSlotNo(cabinet.getId(), slotNo);
//5 更新充电宝数据(电量、状态)
powerBank.setElectricity(electricity);
//电量大于可用最低值
// 状态(0:未投放 1:可用 2:已租用 3:充电中 4:故障)
if (electricity.subtract(DeviceConstants.ELECTRICITY_MIN).doubleValue() > 0) {
//可以借用
powerBank.setStatus("1");
} else {
//充电中
powerBank.setStatus("3");
}
powerBankService.updateById(powerBank);
//6 更新柜机、插槽
//更新插槽状态
cabinetSlot.setPowerBankId(powerBank.getId());
cabinetSlot.setStatus("1");
cabinetSlot.setUpdateTime(new Date());
cabinetSlotService.updateById(cabinetSlot);
//更新柜机信息
int freeSlots = cabinet.getFreeSlots() - 1;
cabinet.setFreeSlots(freeSlots);
int usedSlots = cabinet.getUsedSlots() + 1;
cabinet.setUsedSlots(usedSlots);
//可以借用
if ("1".equals(powerBank.getStatus())) {
int availableNum = cabinet.getAvailableNum() + 1;
cabinet.setAvailableNum(availableNum);
}
cabinet.setUpdateTime(new Date());
cabinetService.updateById(cabinet);
//7 发送mq消息结束订单(封装数据)
EndOrderVo endOrderVo = new EndOrderVo();
endOrderVo.setMessageNo(messageNo);
endOrderVo.setEndTime(new Date());
endOrderVo.setEndCabinetNo(cabinetNo);
LambdaQueryWrapper<Station> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Station::getCabinetId, cabinet.getId());
Station station = stationService.getOne(wrapper);
endOrderVo.setEndStationId(station.getId());
endOrderVo.setEndStationName(station.getName());
endOrderVo.setPowerBankNo(powerBankNo);
rabbitService.sendMessage(MqConst.EXCHANGE_ORDER,
MqConst.ROUTING_END_ORDER,
JSONObject.toJSONString(endOrderVo));
}
}
OrderReceiver
java
//充电宝插入,接收消息结束订单
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = MqConst.EXCHANGE_ORDER,durable = "true"),
value = @Queue(value = MqConst.QUEUE_END_ORDER, durable = "true"),
key = MqConst.ROUTING_END_ORDER
))
public void endOrder(String content, Message message , Channel channel) {
EndOrderVo endOrderVo = JSONObject.parseObject(content, EndOrderVo.class);
//重复消费
String messageNo = endOrderVo.getMessageNo();
String key = "order:endorder:"+messageNo;
Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, messageNo, 1, TimeUnit.HOURS);
if(!ifAbsent) {
return;
}
try {
//调用方法,结束订单
orderInfoService.endOrder(endOrderVo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (IOException e) {
redisTemplate.delete(key);
//消息重新回到队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}
}
IOrderInfoService
java
/**
* 结束订单的方法
*
* @param endOrderVo 结束订单的数据传输对象,包含结束订单所需的所有信息
*/
void endOrder(EndOrderVo endOrderVo);
pom.xml
java
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
OrderInfoServiceImpl
实现方法
java
@Override
public void endOrder(EndOrderVo endOrderVo) {
//1 根据充电宝编号 + 订单状态(充电中)查询是否存在,如果不存在订单,直接返回
//如果存在正在充电的订单,把订单结束
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getPowerBankNo, endOrderVo.getPowerBankNo());
wrapper.eq(OrderInfo::getStatus, "0");
OrderInfo orderInfo = baseMapper.selectOne(wrapper);
//判断
if (orderInfo == null) { //如果不存在订单,直接返回
return;
}
//2 设置订单相关数据,进行更新
orderInfo.setEndTime(endOrderVo.getEndTime());
orderInfo.setEndStationId(endOrderVo.getEndStationId());
orderInfo.setEndStationName(endOrderVo.getEndStationName());
orderInfo.setEndCabinetNo(endOrderVo.getEndCabinetNo());
// 包含充电时长 以分钟单位
Date startTime = orderInfo.getStartTime();
Date endTime = orderInfo.getEndTime();
int duration = Minutes.minutesBetween(new DateTime(startTime), new DateTime(endTime)).getMinutes();
orderInfo.setDuration(duration);
//远程调用:规则引擎进行费用计算
//封装参数
FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();
feeRuleRequestForm.setDuration(duration);
feeRuleRequestForm.setFeeRuleId(orderInfo.getFeeRuleId());
//远程调用
R<FeeRuleResponseVo> feeRuleResponseVoR = remoteFeeRuleService.calculateOrderFee(feeRuleRequestForm);
FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoR.getData();
//设置费用
orderInfo.setTotalAmount(feeRuleResponseVo.getTotalAmount());
orderInfo.setDeductAmount(new BigDecimal("0"));
orderInfo.setRealAmount(feeRuleResponseVo.getTotalAmount());
if (orderInfo.getRealAmount().subtract(new BigDecimal(0)).doubleValue() == 0) {
orderInfo.setStatus("2");
} else {
orderInfo.setStatus("1");
}
baseMapper.updateById(orderInfo);
//3 插入免费账单数据
OrderBill freeOrderBill = new OrderBill();
freeOrderBill.setOrderId(orderInfo.getId());
freeOrderBill.setBillItem(feeRuleResponseVo.getFreeDescription());
freeOrderBill.setBillAmount(new BigDecimal(0));
orderBillMapper.insert(freeOrderBill);
//4 插入收费账单数据(超过免费时间账单数据)
BigDecimal exceedPrice = feeRuleResponseVo.getExceedPrice();
if (exceedPrice.doubleValue() > 0) {
OrderBill exceedOrderBill = new OrderBill();
exceedOrderBill.setOrderId(orderInfo.getId());
exceedOrderBill.setBillItem(feeRuleResponseVo.getExceedDescription());
exceedOrderBill.setBillAmount(feeRuleResponseVo.getExceedPrice());
orderBillMapper.insert(exceedOrderBill);
}
}
属性上报
PropertyPostHandler
java
@Autowired
private ICabinetService cabinetService;
@Autowired
private IPowerBankService powerBankService;
@Autowired
private ICabinetSlotService cabinetSlotService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 处理消息:
*
* @param message
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void handleMessage(JSONObject message) {
log.info("handleMessage: {}", message.toJSONString());
//消息编号
String messageNo = message.getString("mNo");
//防止重复请求
String key = "property:post:" + messageNo;
boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, messageNo, 1, TimeUnit.HOURS);
if (!isExist) {
log.info("重复请求: {}", message.toJSONString());
return;
}
//柜机编号
String cabinetNo = message.getString("cNo");
//充电宝编号
String powerBankNo = message.getString("pNo");
//插槽编号
String slotNo = message.getString("sNo");
//当前电量
BigDecimal electricity = message.getBigDecimal("ety");
if (StringUtils.isEmpty(cabinetNo)
|| StringUtils.isEmpty(powerBankNo)
|| StringUtils.isEmpty(slotNo)
|| null == electricity) {
log.info("参数为空: {}", message.toJSONString());
return;
}
//获取柜机
Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);
// 获取充电宝
PowerBank powerBank = powerBankService.getByPowerBankNo(powerBankNo);
//更新充电宝状态
// 状态(0:未投放 1:可用 2:已租用 3:充电中 4:故障)
//更新充电宝电量与状态
powerBank.setElectricity(electricity);
//电量大于可用最低值
// 状态(0:未投放 1:可用 2:已租用 3:充电中 4:故障)
if (electricity.subtract(DeviceConstants.ELECTRICITY_MIN).doubleValue() > 0) {
//可以借用
powerBank.setStatus("1");
} else {
//充电中
powerBank.setStatus("3");
}
powerBankService.updateById(powerBank);
//更新柜机可用充电宝数量
// 获取柜机插槽列表
List<CabinetSlot> cabinetSlotList = cabinetSlotService.list(new LambdaQueryWrapper<CabinetSlot>()
.eq(CabinetSlot::getStatus, "1")
.eq(CabinetSlot::getCabinetId, cabinet.getId())
.select(CabinetSlot::getPowerBankId)
);
// 获取可用充电宝id列表
List<Long> powerBankIdList = cabinetSlotList.stream().map(CabinetSlot::getPowerBankId).collect(Collectors.toList());
// 获取可用充电宝数量
Long availableNum = powerBankService.count(new LambdaQueryWrapper<PowerBank>().in(PowerBank::getId, powerBankIdList).eq(PowerBank::getStatus, "1"));
cabinet.setAvailableNum(availableNum.intValue());
cabinet.setUpdateTime(new Date());
cabinetService.updateById(cabinet);
}
十四、订单支付
订单列表
IOrderInfoService
在"IOrderlnfoService' 中创建方法'selectOrderListByUserld'
java
/**
* 根据用户ID查询订单列表
*
* @param userId 用户ID
* @return 返回该用户的所有订单信息列表
*/
List<OrderInfo> selectOrderListByUserId(Long userId);
OrderInfoServiceImpl
实现方法
java
@Override
public List<OrderInfo> selectOrderListByUserId(Long userId) {
//1 根据用户id查询用户所有订单列表,返回list集合
LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderInfo::getUserId, userId)
.orderByDesc(OrderInfo::getId);
List<OrderInfo> list = baseMapper.selectList(queryWrapper);
//2 遍历list集合,得到每个orderInfo对象
for (OrderInfo orderInfo : list) {
//3 判断订单状态是0(充电中),实时计算充电时间和金额
if ("0".equals(orderInfo.getStatus())) {
//计算当前充电时间
//当前时间 - 开始时间 = 充电时间
int minutes = Minutes.minutesBetween(new DateTime(orderInfo.getStartTime()),
new DateTime()).getMinutes();
//如果充电时长大于0
if (minutes > 0) {
orderInfo.setDuration(minutes);
FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();
feeRuleRequestForm.setDuration(minutes);
feeRuleRequestForm.setFeeRuleId(orderInfo.getFeeRuleId());
R<FeeRuleResponseVo> feeRuleResponseVoR =
remoteFeeRuleService.calculateOrderFee(feeRuleRequestForm);
FeeRuleResponseVo responseVo = feeRuleResponseVoR.getData();
//设置到orderInfo里面
orderInfo.setTotalAmount(responseVo.getTotalAmount());
orderInfo.setDeductAmount(new BigDecimal(0));
orderInfo.setRealAmount(responseVo.getTotalAmount());
} else {
orderInfo.setDuration(0);
orderInfo.setTotalAmount(new BigDecimal(0));
orderInfo.setDeductAmount(new BigDecimal(0));
orderInfo.setRealAmount(new BigDecimal(0));
}
}
}
return list;
}
订单详情
充电中的订单计算使用时间与费用金额
OrderInfoApiController
java
@Operation(summary = "获取订单详细信息")
@RequiresLogin
@GetMapping(value = "/getOrderInfo/{id}")
public AjaxResult getOrderInfo(@PathVariable("id") Long id)
{
return success(orderInfoService.selectOrderInfoById(id));
}
IOrderInfoService.java
java
/**
* 根据ID查询订单信息
*
* @param id 订单ID
* @return 返回订单信息对象
*/
Object selectOrderInfoById(Long id);
OrderInfoServiceImpl
实现方法
java
//获取订单详细信息
@Override
public Object selectOrderInfoById(Long id) {
//根据id查询订单信息
OrderInfo orderInfo = baseMapper.selectById(id);
//判断订单状态充电中 0
if ("0".equals(orderInfo.getStatus())) {
//计算充电时间
// 计算从订单开始时间到现在经过的分钟数
// 使用Minutes类的minutesBetween方法计算两个DateTime对象之间的分钟差
int minutes = Minutes.minutesBetween(new DateTime(orderInfo.getStartTime()), // 订单的开始时间
new DateTime()).getMinutes(); // 当前时间
//充电时间大于0
if (minutes > 0) {
orderInfo.setDuration(minutes);
FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();
feeRuleRequestForm.setDuration(minutes);
feeRuleRequestForm.setFeeRuleId(orderInfo.getFeeRuleId());
R<FeeRuleResponseVo> feeRuleResponseVoR = remoteFeeRuleService.calculateOrderFee(feeRuleRequestForm);
FeeRuleResponseVo responseVo = feeRuleResponseVoR.getData();
//设置到orderInfo里面
orderInfo.setTotalAmount(responseVo.getTotalAmount());
orderInfo.setDeductAmount(new BigDecimal(0));
orderInfo.setRealAmount(responseVo.getTotalAmount());
} else {
orderInfo.setDuration(0);
orderInfo.setTotalAmount(new BigDecimal(0));
orderInfo.setDeductAmount(new BigDecimal(0));
orderInfo.setRealAmount(new BigDecimal(0));
}
}
//OrderBill
List<OrderBill> orderBillList = orderBillMapper.selectList(new LambdaQueryWrapper<OrderBill>().eq(OrderBill::getOrderId, id));
orderInfo.setOrderBillList(orderBillList);
R<UserInfo> userInfoR = remoteUserInfoService.getInfo(orderInfo.getUserId());
UserInfo userInfo = userInfoR.getData();
UserInfoVo userInfoVo = new UserInfoVo();
BeanUtils.copyProperties(userInfo, userInfoVo);
orderInfo.setUserInfoVo(userInfoVo);
//返回对象
return orderInfo;
}
微信支付
新建share-payment模块
java
<dependencies>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- RuoYi Common DataScope -->
<dependency>
<groupId>com.share</groupId>
<artifactId>share-common-datascope</artifactId>
</dependency>
<!-- RuoYi Common Log -->
<dependency>
<groupId>com.share</groupId>
<artifactId>share-common-log</artifactId>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
</dependency>
</dependencies>
banner.txt、bootstrap.yml、logback.xml
share-payment-dev.yml

SharePaymentApplication
java
package com.share.payment;
import com.share.common.security.annotation.EnableCustomConfig;
import com.share.common.security.annotation.EnableRyFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableCustomConfig
@EnableRyFeignClients
@SpringBootApplication
public class SharePaymentApplication
{
public static void main(String[] args)
{
SpringApplication.run(SharePaymentApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 支付模块启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
根据订单号获取订单接口
订单微服务接口
java
@Operation(summary = "根据订单号获取订单信息")
@GetMapping("getByOrderNo/{orderNo}")
public R<OrderInfo> getByOrderNo(@PathVariable String orderNo) {
OrderInfo orderInfo = orderInfoService.getByOrderNo(orderNo);
return R.ok(orderInfo);
}
IOrderInfoService
在"IOrderlnfoService' 中创建方法'getByOrderNo'
java
/**
* 根据订单号获取订单信息
*
* @param orderNo 订单号,用于唯一标识一个订单
* @return 返回对应的OrderInfo对象,包含订单的详细信息
*/
OrderInfo getByOrderNo(String orderNo);
OrderInfoServiceImpl
实现方法
java
@Override
public OrderInfo getByOrderNo(String orderNo) {
LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderInfo::getOrderNo, orderNo);
OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
return orderInfo;
}
Feign接
RemoteOrderInfoService
java
/**
* 根据订单号获取订单信息
*
* @param orderNo 订单号
* @return 返回订单信息对象,使用统一响应格式R包装
*/
@GetMapping("/orderInfo/getByOrderNo/{orderNo}")
public R<OrderInfo> getByOrderNo(@PathVariable("orderNo") String orderNo);
封装保存支付信息方法
PaymentInfo
java
package com.share.payment.domain;
import com.share.common.core.web.domain.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
@Schema(description = "PaymentInfo")
public class PaymentInfo extends BaseEntity {
private static final long serialVersionUID = 1L;
@Schema(description = "用户id")
private Long userId;
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "付款方式:1-微信")
private Integer payWay;
@Schema(description = "交易编号(微信或支付)")
private String transactionId;
@Schema(description = "支付金额")
private BigDecimal amount;
@Schema(description = "交易内容")
private String content;
@Schema(description = "支付状态:0-未支付 1-已支付 -1-关闭交易")
private Integer paymentStatus;
@Schema(description = "回调时间")
private Date callbackTime;
@Schema(description = "回调信息")
private String callbackContent;
}
PaymentInfoMapper
java
package com.share.payment.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.share.payment.domain.PaymentInfo;
public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {
}
IPaymentInfoService
java
package com.share.payment.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.share.payment.domain.PaymentInfo;
import com.wechat.pay.java.service.payments.model.Transaction;
public interface IPaymentService extends IService<PaymentInfo> {
PaymentInfo savePaymentInfo(String orderNo);
}
PaymentInfoServiceImpl
java
@Override
public PaymentInfo savePaymentInfo(String orderNo) {
// 根据订单编号查询支付记录
PaymentInfo paymentInfo =
baseMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>()
.eq(PaymentInfo::getOrderNo, orderNo));
//2 如果支付记录不存在,进行添加
if(paymentInfo == null) {
//3 远程调用:根据订单编号查询订单数据
R<OrderInfo> result = remoteOrderInfoService.getByOrderNo(orderNo);
OrderInfo orderInfo = result.getData();
//添加数据
paymentInfo = new PaymentInfo();
paymentInfo.setUserId(orderInfo.getUserId());
paymentInfo.setContent("共享充电宝租借");
paymentInfo.setAmount(orderInfo.getTotalAmount());
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPaymentStatus(0);
baseMapper.insert(paymentInfo);
}
return paymentInfo;
}
微信下单
WxPayApiController
java
package com.share.payment.api;
@Tag(name = "微信支付接口")
@RestController
@RequestMapping("/wxPay")
@Slf4j
public class WxPayApiController extends BaseController {
@Autowired
private IWxPayService wxPayService;
@Autowired
private IPaymentInfoService paymentInfoService;
@RequiresLogin
@Operation(summary = "微信下单")
@PostMapping("/createWxPayment")
public AjaxResult createWxPayment(@RequestBody CreateWxPaymentForm createWxPaymentForm) {
WxPrepayVo wxPrepayVo = wxPayService.createWxPayment(createWxPaymentForm);
return success(wxPrepayVo);
}
}
CreateWxPaymentForm
java
package com.share.payment.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class CreateWxPaymentForm {
@Schema(description = "订单号")
private String orderNo;
}
WxPrepayVo
java
package com.share.payment.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class WxPrepayVo {
@Schema(description = "公众号ID")
private String appId;
@Schema(description = "时间戳,自1970年以来的秒数")
private String timeStamp;
@Schema(description = "随机串")
private String nonceStr;
@Schema(description = "预支付交易会话标识")
private String packageVal;
@Schema(description = "微信签名方式")
private String signType;
@Schema(description = "微信签名")
private String paySign;
}
IWxPayService
java
/**
* 创建微信支付订单的方法
*
* @param createWxPaymentForm 创建微信支付所需的表单数据对象
* @return WxPrepayVo 返回微信预支付订单信息对象
*/
WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm);
WxPayServiceImpl
java
package com.share.payment.service.impl;
@Service
@Slf4j
public class WxPayServiceImpl implements IWxPayService {
@Autowired
private IPaymentInfoService paymentInfoService;
@Autowired
private RemoteUserInfoService remoteUserInfoService;
@Autowired
private WxPayV3Properties wxPayV3Properties;
@Autowired
private RSAAutoCertificateConfig rsaAutoCertificateConfig;
@Override
public WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm) {
try {
//保存支付记录
PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(createWxPaymentForm.getOrderNo());
//获取用户信息
R<UserInfo> userInfoResult = remoteUserInfoService.getUserInfo(paymentInfo.getUserId(), SecurityConstants.INNER);
if (StringUtils.isNull(userInfoResult) || StringUtils.isNull(userInfoResult.getData())) {
throw new com.share.common.core.exception.ServiceException("获取用户信息失败");
}
if (R.FAIL == userInfoResult.getCode()) {
throw new com.share.common.core.exception.ServiceException(userInfoResult.getMsg());
}
String openid = userInfoResult.getData().getWxOpenId();
// 构建service
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(paymentInfo.getAmount().multiply(new BigDecimal(100)).intValue());
request.setAmount(amount);
request.setAppid(wxPayV3Properties.getAppid());
request.setMchid(wxPayV3Properties.getMerchantId());
request.setDescription(paymentInfo.getContent());
request.setNotifyUrl(wxPayV3Properties.getNotifyUrl());
request.setOutTradeNo(paymentInfo.getOrderNo());
//获取用户信息
Payer payer = new Payer();
payer.setOpenid(openid);
request.setPayer(payer);
// 调用下单方法,得到应答
// response包含了调起支付所需的所有参数,可直接用于前端调起支付
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
log.info("微信支付下单返回参数:{}", JSON.toJSONString(response));
WxPrepayVo wxPrepayVo = new WxPrepayVo();
BeanUtils.copyProperties(response, wxPrepayVo);
wxPrepayVo.setTimeStamp(response.getTimeStamp());
return wxPrepayVo;
} catch (ServiceException e) {
e.printStackTrace();
throw new com.share.common.core.exception.ServiceException(e.getErrorMessage());
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new com.share.common.core.exception.ServiceException("订单号不存在");
} catch (Exception e) {
e.printStackTrace();
throw new com.share.common.core.exception.ServiceException("微信下单异常");
}
}
}
WxPayV3Properties
java
package com.share.payment.config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix="wx.v3pay") //读取节点
@Data
public class WxPayV3Properties {
private String appid;
/** 商户号 */
public String merchantId;
/** 商户API私钥路径 */
public String privateKeyPath;
/** 商户证书序列号 */
public String merchantSerialNumber;
/** 商户APIV3密钥 */
public String apiV3key;
/** 回调地址 */
private String notifyUrl;
@Bean
public RSAAutoCertificateConfig getConfig(){
return new RSAAutoCertificateConfig.Builder()
.merchantId(this.getMerchantId())
.privateKeyFromPath(this.getPrivateKeyPath())
.merchantSerialNumber(this.getMerchantSerialNumber())
.apiV3Key(this.getApiV3key())
.build();
}
}
支付结果通知
pom.xml
java
<dependency>
<groupId>com.share</groupId>
<artifactId>share-common-rabbit</artifactId>
<version>3.6.3</version>
</dependency>
WxPayApiController
java
@Operation(summary = "微信支付异步通知接口")
@PostMapping("/notify")
public Map<String, Object> notify(HttpServletRequest request) {
try {
wxPayService.wxnotify(request);
//返回成功
Map<String, Object> result = new HashMap<>();
result.put("code", "SUCCESS");
result.put("message", "成功");
return result;
} catch (Exception e) {
e.printStackTrace();
}
//返回失败
Map<String, Object> result = new HashMap<>();
result.put("code", "FAIL");
result.put("message", "失败");
return result;
}
IWxPayService
java
/**
* 处理微信通知回调的接口方法
* 该方法用于接收并处理来自微信服务器的通知请求
*
* @param request HttpServletRequest对象,包含微信服务器发送的所有请求参数和相关信息
*/
void wxnotify(HttpServletRequest request);
RequestUtils
java
package com.share.payment.utils;
import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
public class RequestUtils {
/**
* 将通知参数转化为字符串
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
WxPayServiceImpl
实现方法
java
@Override
public void wxnotify(HttpServletRequest request) {
//1.回调通知的验签与解密
//HTTP 头 Wechatpay-Signature
// HTTP 头 Wechatpay-Nonce
//HTTP 头 Wechatpay-Timestamp
//HTTP 头 Wechatpay-Serial
//HTTP 头 Wechatpay-Signature-Type
//HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String signature = request.getHeader("Wechatpay-Signature");
String requestBody = RequestUtils.readData(request);
//2.构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(requestBody)
.build();
//3.初始化 NotificationParser
NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
//4.以支付通知回调为例,验签、解密并转换成 Transaction
Transaction transaction = parser.parse(requestParam, Transaction.class);
if (null != transaction &&
transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
//5.处理支付业务
IPaymentService.updatePaymentStatus(transaction);
}
}
IPaymentInfoService
java
void updatePaymentStatus(Transaction transaction);
PaymentInfoServiceImpl
实现方法
java
//支付成功后,后续处理
@Override
public void updatePaymentStatus(Transaction transaction) {
//1 更新支付记录状态:已经支付
String outTradeNo = transaction.getOutTradeNo();
//根据订单编号查询支付记录
PaymentInfo paymentInfo =
baseMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, outTradeNo));
//判断如果支付记录状态已经修改了,不需要操作
Integer paymentStatus = paymentInfo.getPaymentStatus();
if(paymentStatus.intValue()==1) { //已经修改状态
return;
}
//没有修改,再进行修改
paymentInfo.setPaymentStatus(1); //修改为已经支付
paymentInfo.setOrderNo(transaction.getOutTradeNo());
paymentInfo.setTransactionId(transaction.getTransactionId());
paymentInfo.setCallbackTime(new Date());
paymentInfo.setCallbackContent(com.alibaba.fastjson.JSON.toJSONString(transaction));
baseMapper.updateById(paymentInfo);
//2 基于MQ通知订单系统,修改订单状态
rabbitService.sendMessage(MqConst.EXCHANGE_PAYMENT_PAY,
MqConst.ROUTING_PAYMENT_PAY,
paymentInfo.getOrderNo());
}
支付查询
WxPayApiController
java
@RequiresLogin
@Operation(summary = "支付状态查询")
@GetMapping("/queryPayStatus/{orderNo}")
public AjaxResult queryPayStatus(@PathVariable String orderNo) {
//根据订单编号调用微信服务接口,查询支付相关状态信息
Transaction transaction = wxPayService.queryPayStatus(orderNo);
//根据微信服务返回结果,判断支付是否成功
if(transaction != null
&& transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
paymentService.updatePaymentStatus(transaction);
return success(true);
}
return success(false);
}
IWxPayService
java
//根据订单编号调用微信服务接口,查询支付相关状态信息
Transaction queryPayStatus(String orderNo);
WxPayServiceImpl
java
//根据订单编号调用微信服务接口,查询支付相关状态信息
@Override
public Transaction queryPayStatus(String orderNo) {
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wxPayV3Properties.getMerchantId());
queryRequest.setOutTradeNo(orderNo);
Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
return transaction;
}
支付成功订单业务处理
OrderReceiver
java
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = MqConst.EXCHANGE_PAYMENT_PAY, durable = "true"),
value = @Queue(value = MqConst.QUEUE_PAYMENT_PAY, durable = "true"),
key = MqConst.ROUTING_PAYMENT_PAY
))
public void processPaySucess(String orderNo, Message message, Channel channel) {
//业务处理
if (StringUtils.isNotEmpty(orderNo)) {
//更改订单支付状态
orderInfoService.processPaySucess(orderNo);
}
//手动应答
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
IOrderInfoService
java
/**
* 处理支付成功后的业务逻辑
* 该方法用于在支付成功后执行相关的业务处理,例如更新订单状态、发送通知等
*
* @param orderNo 订单编号,用于唯一标识一个订单
*/
void processPaySucess(String orderNo);
OrderInfoServiceImpl
java
/**
* 处理支付成功的订单
*
* @param orderNo 订单编号
*/
@Override
public void processPaySucess(String orderNo) {
// 创建Lambda查询条件构造器
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
// 设置查询条件:根据订单编号进行精确匹配
wrapper.eq(OrderInfo::getOrderNo, orderNo);
// 执行查询,获取订单信息
OrderInfo orderInfo = baseMapper.selectOne(wrapper);
// 判断订单状态是否为"1"(可能是待支付状态)
if ("1".equals(orderInfo.getStatus())) {
// 更新订单状态为"2"(可能是已支付状态)
orderInfo.setStatus("2");
// 设置支付时间为当前时间
orderInfo.setPayTime(new Date());
// 更新订单信息到数据库
baseMapper.updateById(orderInfo);
}
}
测试
mqtt.html
java
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"
type="text/javascript"></script>
<!-- <script type="text/javascript"
src="http://sph.atguigu.cn/js/plugins/jquery/jquery.min.js"></script> -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"
type="text/javascript"></script>
device与柜机都连接上去了

点击归还充电宝后
docker restart share_rabbitmq
docker start emqx