学习: 尚硅谷Java项目之小谷充电宝(5)

十二、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

相关推荐
zhouping@2 小时前
JAVA的学习笔记day05
java·笔记·学习
小陈phd2 小时前
多模态大模型学习笔记(十二)——transformer学习之Embedding
笔记·学习·transformer
格鸰爱童话2 小时前
向AI学习项目技能(三)
java·人工智能·python·学习
头疼的程序员2 小时前
计算机网络:自顶向下方法(第七版)第三章 学习分享(三)
网络·学习·计算机网络
观书喜夜长2 小时前
OpenClaw 本地安装与 Ollama 大模型接入实战教程-实现无限tokens使用
学习·网络安全
kisshuan123962 小时前
Ghost卷积瓶颈轻量化改进YOLOv26双阶段压缩与残差学习协同突破
学习·yolo
EnglishJun2 小时前
ARM嵌入式学习(一) --- 入门51
arm开发·学习
技术小黑2 小时前
TensorFlow学习系列07 | 实现咖啡豆识别
人工智能·学习·tensorflow
invicinble3 小时前
学习视频的全域理解
学习