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

九、附近门店

附近门店

新建模板share-rule

封装门店规则信息接口

FeeRuleApiController
java 复制代码
package com.share.rules.api;

import com.share.common.core.domain.R;
import com.share.rules.domain.vo.FeeRule;
import com.share.rules.service.IFeeRuleService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/feeRule")
@SuppressWarnings({"unchecked", "rawtypes"})
public class FeeRuleApiController {
    @Autowired
    private IFeeRuleService feeRuleService;

    //批量获取费用规则列表
    @Operation(summary = "批量获取费用规则信息")
    @PostMapping(value = "/getFeeRuleList")
    public R<List<FeeRule>> getFeeRuleList(@RequestBody List<Long> feeRuleIdList) {
        List<FeeRule> feeRules = feeRuleService.listByIds(feeRuleIdList);
        return R.ok(feeRules);
    }

    //根据id获取规则详情
    @Operation(summary = "获取费用规则详细信息")
    @GetMapping(value = "/getFeeRule/{id}")
    public R<FeeRule> getFeeRule(@PathVariable("id") Long id) {
        FeeRule byId = feeRuleService.getById(id);
        return R.ok(byId);
    }
}
搭建规则服务远程接口模块

在share-api模块下新建子模块share-api-rule

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-api</artifactId>
        <version>3.6.3</version>
    </parent>

    <artifactId>share-api-rule</artifactId>
    <description>
        share-api-rule规则接口模块
    </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>

        <!-- RuoYi Common Core-->
        <dependency>
            <groupId>com.share</groupId>
            <artifactId>share-common-core</artifactId>
        </dependency>

    </dependencies>
</project>
pom.xml

share-rule模块引入api依赖

java 复制代码
<dependency>
    <groupId>com.share</groupId>
    <artifactId>share-api-rule</artifactId>
    <version>3.6.3</version>
</dependency>
FeeRule

将FeeRule对象提升到share-api-rule模块com.share.rules.api.domain包下面,订单模块要使用该对象,share-rule的FeeRule删除

openFeign接口定义

操作模块:share-api-rule

ServiceNameConstants.java
java 复制代码
    /**
     * 规则服务
     */
    public static final String RULE_SERVICE = "share-rule";
RemoteFeeRuleService
java 复制代码
package com.share.rules.api;

import com.share.common.core.constant.ServiceNameConstants;
import com.share.common.core.domain.R;

import com.share.rules.domain.FeeRule;
import com.share.rules.factory.RemoteFeeRuleFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

/**
 * 用户服务
 *
 * @author share
 */
@FeignClient(contextId = "remoteFeeRuleService",
        value = ServiceNameConstants.RULE_SERVICE,
        fallbackFactory = RemoteFeeRuleFallbackFactory.class)
public interface RemoteFeeRuleService {

    @PostMapping(value = "/feeRule/getFeeRuleList")
    public R<List<FeeRule>> getFeeRuleList(@RequestBody List<Long> feeRuleIdList);

    @GetMapping(value = "/feeRule/getFeeRule/{id}")
    public R<FeeRule> getFeeRule(@PathVariable("id") Long id);
}
RemoteFeeRuleFallbackFactory
java 复制代码
package com.share.rules.factory;

import com.share.common.core.exception.ServiceException;

import com.share.rules.api.RemoteFeeRuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * 用户服务降级处理
 *
 * @author share
 */
@Component
public class RemoteFeeRuleFallbackFactory implements FallbackFactory<RemoteFeeRuleService>
{
    private static final Logger log = LoggerFactory.getLogger(RemoteFeeRuleFallbackFactory.class);

    @Override
    public RemoteFeeRuleService create(Throwable throwable)
    {
        log.error("用户服务调用失败:{}", throwable.getMessage());
        throw new ServiceException("调用出现错误");
    }
}
加载配置类

resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

java 复制代码
com.share.rules.api.factory.RemoteFeeRuleFallbackFactory

计算到门店的距离方法

IMapService
java 复制代码
package com.share.device.service;

public interface IMapService {

    //计算距离
    // 四个参数:开始经纬度, 目标经纬度
    Double calculateDistance(String startLongitude, String startLatitude,
                             String endLongitude, String endLatitude);
}
MapServiceImpl
java 复制代码
package com.share.device.service.impl;

import cn.hutool.json.JSONObject;
import com.share.common.core.exception.ServiceException;
import com.share.device.service.IMapService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class MapServiceImpl implements IMapService {

    @Autowired
    private RestTemplate restTemplate;

    private String key = "";

    //计算距离
    // 四个参数:开始经纬度, 目标经纬度
    @Override
    public Double calculateDistance(String startLongitude, String startLatitude,
                                    String endLongitude, String endLatitude) {
        String url = "https://apis.map.qq.com/ws/direction/v1/walking/?from={from}&to={to}&key={key}";

        Map<String, String> map = new HashMap<>();
        map.put("from", startLatitude + "," + startLongitude);
        map.put("to", endLatitude + "," + endLongitude);
        map.put("key", key);

        JSONObject result = restTemplate.getForObject(url, JSONObject.class, map);
        if (result.getInt("status") != 0) {
            String message = result.getStr("message");
            System.out.println("*****===============" + message);
            throw new ServiceException("地图服务调用失败");
        }

        //返回第一条最佳线路
        JSONObject route = result.getJSONObject("result").getJSONArray("routes").getJSONObject(0);
        // 单位:米
        return route.getBigDecimal("distance").doubleValue();
    }
}

搜索附近门店接口

模块share-device

创建包api

pom.xml
java 复制代码
        <dependency>
            <groupId>com.share</groupId>
            <artifactId>share-api-user</artifactId>
            <version>3.6.3</version>
        </dependency>
DeviceApiController
java 复制代码
package com.share.device.api;

import com.share.common.core.web.controller.BaseController;
import com.share.common.core.web.domain.AjaxResult;
import com.share.common.security.annotation.RequiresLogin;
import com.share.device.domain.StationVo;
import com.share.device.service.IDeviceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Tag(name = "站点接口管理")
@RestController
@RequestMapping("/device")
public class DeviceApiController extends BaseController {

    @Autowired
    private IDeviceService deviceService;
    
    @Operation(summary = "根据经纬度搜索附近门店(站点)")
    @RequiresLogin
    @GetMapping("/nearbyStation/{latitude}/{longitude}")
    public AjaxResult nearbyStation(@PathVariable String latitude,
                                    @PathVariable String longitude) {
        List<StationVo> stationVoList =
                deviceService.nearbyStation(latitude, longitude);
        return success(stationVoList);
    }

}
DeviceServiceImpl
java 复制代码
package com.share.device.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.common.core.constant.SecurityConstants;
import com.share.common.core.domain.R;
import com.share.device.domain.Cabinet;
import com.share.device.domain.Station;
import com.share.device.domain.StationLocation;
import com.share.device.domain.StationVo;
import com.share.device.service.ICabinetService;
import com.share.device.service.IDeviceService;
import com.share.device.service.IMapService;
import com.share.device.service.IStationService;
import com.share.rules.api.RemoteFeeRuleService;
import com.share.rules.domain.FeeRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DeviceServiceImpl implements IDeviceService {
    @Autowired
    private IStationService stationService;

    @Autowired
    private ICabinetService cabinetService;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IMapService mapService;

    @Autowired
    private RemoteFeeRuleService remoteFeeRuleService;

    @Override
    public List<StationVo> nearbyStation(String latitude, String longitude) {
        //坐标,确定中心点
        //GeoJsonPoint(double x, double y) x 表示经度,y 表示纬度。
        GeoJsonPoint geoJsonPoint =
                new GeoJsonPoint(Double.parseDouble(longitude)
                        , Double.parseDouble(latitude));
        //画圈的半径,50km范围
        Distance distance = new Distance(50, Metrics.KILOMETERS);
        //画了一个圆圈
        Circle circle = new Circle(geoJsonPoint, distance);
        //查询mongoDB
        Query query = Query.query(Criteria.where("location").withinSphere(circle));
        List<StationLocation> stationLocationList = mongoTemplate.find(query, StationLocation.class);
        //查询其他需要数据,进行封装
        //根据查询mongoDB的list集合里获取站点其他数据
        //从list获取所有站点id,组装数据
        List<Long> stationIdList = stationLocationList.stream().map(StationLocation::getStationId).collect(Collectors.toList());
        //根据所有站点id获取对应站点数量
        LambdaQueryWrapper<Station> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Station::getId, stationIdList);
//        queryWrapper.isNotNull(Station::getCabinetId);
        List<Station> stationList = stationService.list(queryWrapper);

        List<StationVo> stationVoList = new ArrayList<>();
        //遍历
        stationList.forEach(station -> {
            StationVo stationVo = new StationVo();
            BeanUtils.copyProperties(station, stationVo);
            // 计算距离
            Double distanceStation = mapService
                    .calculateDistance(longitude,
                            latitude,
                            station.getLongitude().toString(),
                            station.getLatitude().toString());
            stationVo.setDistance(distanceStation);

            // 获取柜机信息
            Long cabinetId = station.getCabinetId();
            Cabinet cabinet = cabinetService.getById(cabinetId);
            //可用充电宝数量大于0,可借用
            if (cabinet.getAvailableNum() > 0) {
                stationVo.setIsUsable("1");
            } else {
                stationVo.setIsUsable("0");
            }
            // 获取空闲插槽数量大于0,可归还
            if (cabinet.getFreeSlots() > 0) {
                stationVo.setIsReturn("1");
            } else {
                stationVo.setIsReturn("0");
            }
            // 获取费用规则
            Long feeRuleId = station.getFeeRuleId();
            R<FeeRule> feeRuleResult = remoteFeeRuleService.getFeeRule(feeRuleId);
            FeeRule feeRule = feeRuleResult.getData();
            String description = feeRule.getDescription();
            stationVo.setFeeRule(description);
            
            stationVoList.add(stationVo);
        });

        return stationVoList;
    }
}

测试过程中,可能出现超过调用次数限制,为了测试可以使用随机数

MapServiceImpl.java

java 复制代码
package com.share.device.service.impl;

import cn.hutool.json.JSONObject;
import com.share.common.core.exception.ServiceException;
import com.share.device.service.IMapService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class MapServiceImpl implements IMapService {

    @Autowired
    private RestTemplate restTemplate;

    private String key = "";

    //计算距离
    // 四个参数:开始经纬度, 目标经纬度
    @Override
    public Double calculateDistance(String startLongitude, String startLatitude,
                                    String endLongitude, String endLatitude) {
//        String url = "https://apis.map.qq.com/ws/direction/v1/walking/?from={from}&to={to}&key={key}";
//
//        Map<String, String> map = new HashMap<>();
//        map.put("from", startLatitude + "," + startLongitude);
//        map.put("to", endLatitude + "," + endLongitude);
//        map.put("key", key);
//
//        JSONObject result = restTemplate.getForObject(url, JSONObject.class, map);
//        if (result.getInt("status") != 0) {
//            String message = result.getStr("message");
//            System.out.println("*****===============" + message);
//            throw new ServiceException("地图服务调用失败");
//        }
//
//        //返回第一条最佳线路
//        JSONObject route = result.getJSONObject("result").getJSONArray("routes").getJSONObject(0);
//        // 单位:米
//        return route.getBigDecimal("distance").doubleValue();


        Random random = new Random();
        BigDecimal randomDouble = BigDecimal.valueOf(random.nextDouble(100));

        // 保留两位小数,并进行四舍五入
        BigDecimal roundedValue = randomDouble.setScale(1, RoundingMode.HALF_UP);
        double roundedDoubleValue = roundedValue.doubleValue();
        return roundedDoubleValue;
    }
}

门店详情

DeviceApiController.java

java 复制代码
@Operation(summary = "根据id获取门店详情")
@RequiresLogin
@GetMapping("/getStation/{id}/{latitude}/{longitude}")
public AjaxResult getStation(@PathVariable Long id, @PathVariable String latitude, @PathVariable String longitude)
{
    return success(deviceService.getStation(id, latitude, longitude));
}

在"IDeviceService'中创建方法'getStation

java 复制代码
    //门店详情
    StationVo getStation(Long id, String latitude, String longitude);

实现方法

java 复制代码
    //门店详情
    @Override
    public StationVo getStation(Long id, String latitude, String longitude) {
        Station station = stationService.getById(id);
        StationVo stationVo = new StationVo();
        BeanUtils.copyProperties(station, stationVo);
        // 计算距离
        Double distance = mapService.calculateDistance(longitude, latitude, station.getLongitude().toString(), station.getLatitude().toString());
        stationVo.setDistance(distance);

        // 获取柜机信息
        Cabinet cabinet = cabinetService.getById(station.getCabinetId());
        //可用充电宝数量大于0,可借用
        if (cabinet.getAvailableNum() > 0) {
            stationVo.setIsUsable("1");
        } else {
            stationVo.setIsUsable("0");
        }
        // 获取空闲插槽数量大于0,可归还
        if (cabinet.getFreeSlots() > 0) {
            stationVo.setIsReturn("1");
        } else {
            stationVo.setIsReturn("0");
        }

        // 获取费用规则
        FeeRule feeRule =
                remoteFeeRuleService.getFeeRule(station.getFeeRuleId()).getData();
        stationVo.setFeeRule(feeRule.getDescription());

        //返回数据
        return stationVo;
    }

十、MQTT消息服务

系统消息处理

操作模块:share-device

连接测试

pom.xml
java 复制代码
<dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
测试类MqttDemo
java 复制代码
package com.share.device.emqx;

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class MqttDemo {

    public static void main(String[] args) {
        String subTopic = "testtopic/#";
        String pubTopic = "testtopic/1";
        String content = "Hello World";
        int qos = 2;
        String broker = "tcp://192.168.3.137:1883";
        String clientId = "emqx_test";
        MemoryPersistence persistence = new MemoryPersistence();

        try {
            //创建mqtt客户端
            MqttClient client = new MqttClient(broker, clientId, persistence);

            //连接emqx
            MqttConnectOptions connectOptions = new MqttConnectOptions();
            connectOptions.setUserName("emqx_test");
            connectOptions.setPassword("emqx_test_password".toCharArray());
            // 保留会话
            connectOptions.setCleanSession(true);

            //进行连接
            System.out.println("Connecting to broker: " + broker);
            client.connect(connectOptions);

            System.out.println("Connected");
            System.out.println("Publishing message: " + content);
            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    // 连接丢失后,一般在这里面进行重连
                    System.out.println("连接断开,可以做重连");
                }

                @Override
                public void messageArrived(String topic, MqttMessage message) throws Exception {
                    // subscribe后得到的消息会执行到这里面
                    System.out.println("接收消息主题:" + topic);
                    System.out.println("接收消息Qos:" + message.getQos());
                    System.out.println("接收消息内容:" + new String(message.getPayload()));
                }

                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    System.out.println("deliveryComplete---------" + token.isComplete());
                }
            });
            //订阅topic
            client.subscribe(subTopic);

            //发送消息
            MqttMessage message = new MqttMessage(content.getBytes());
            message.setQos(2);
            client.publish(pubTopic, message);
            System.out.println("Message published");

            client.disconnect();
            client.close();
            System.exit(0);
        } catch (MqttException e) {
            throw new RuntimeException(e);
        }


    }
}

封装mqtt

share-device-dev.yml
EmqxProperties
java 复制代码
package com.share.device.emqx.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "emqx.client")
public class EmqxProperties {

    private String clientId;
    private String username;
    private String password;
    private String serverURI;
    private int keepAliveInterval;
    private int connectionTimeout;
}
EmqxConstants

系统使用到的Topic信息

java 复制代码
package com.share.device.emqx.constant;

/**
 * Emqx常量信息
 *
 */
public class EmqxConstants {


    //充电宝插入,订阅设备端Topic, 服务器监听
    public final static String TOPIC_POWERBANK_CONNECTED = "/sys/powerBank/connected";

    /** 扫码提交指令Topic,柜机监听  */
    public final static String TOPIC_SCAN_SUBMIT = "/sys/scan/submit/%s";

    /** 柜机解锁充电宝,服务器监听  */
    public final static String TOPIC_POWERBANK_UNLOCK = "/sys/powerBank/unlock";

    /** 柜机属性上报,服务器监听  */
    public final static String TOPIC_PROPERTY_POST = "/sys/property/post";
}
EmqxClientWrapper

封装mqtt连接、订阅与发布接口

java 复制代码
package com.share.device.emqx;//package com.atguigu.iot.mqtt.emqx;

import com.share.device.emqx.callback.OnMessageCallback;
import com.share.device.emqx.config.EmqxProperties;
import com.share.device.emqx.constant.EmqxConstants;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * https://docs.emqx.com/zh/emqx/v5.0/connect-emqx/java.html
 */
@Slf4j
@Component
public class EmqxClientWrapper {

    @Autowired
    private EmqxProperties emqxProperties;

    private MqttClient client;

    @Autowired
    private OnMessageCallback onMessageCallback;

    @PostConstruct
    private void init() {
        MqttClientPersistence mqttClientPersistence = new MemoryPersistence();
        try {
            //新建客户端 参数:MQTT服务的地址,客户端名称,持久化
            client = new MqttClient(emqxProperties.getServerURI(), emqxProperties.getClientId(), mqttClientPersistence);

            // 设置回调
            client.setCallback(onMessageCallback);

            // 建立连接
            connect();

        } catch (MqttException e) {
            log.info("MqttClient创建失败");
            throw new RuntimeException(e);
        }
    }

    public Boolean connect() {
        // 设置连接的配置
        try {
            client.connect(mqttConnectOptions());
            log.info("连接成功");

            // 订阅
            String[] topics = {EmqxConstants.TOPIC_POWERBANK_CONNECTED, EmqxConstants.TOPIC_POWERBANK_UNLOCK, EmqxConstants.TOPIC_PROPERTY_POST};
            client.subscribe(topics);
            return true;
        } catch (MqttException e) {
            log.info("连接失败");
            e.printStackTrace();
        }
        return false;
    }

    /*创建MQTT配置类*/
    private MqttConnectOptions mqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(emqxProperties.getUsername());
        options.setPassword(emqxProperties.getPassword().toCharArray());
        options.setAutomaticReconnect(true);//是否自动重新连接
        options.setCleanSession(true);//是否清除之前的连接信息
        options.setConnectionTimeout(emqxProperties.getConnectionTimeout());//连接超时时间
        options.setKeepAliveInterval(emqxProperties.getKeepAliveInterval());//心跳
        return options;
    }

    /**
     * 发布消息
     * @param topic
     * @param data
     */
    public void publish(String topic, String data) {
        try {
            MqttMessage message = new MqttMessage(data.getBytes());
            message.setQos(2);
            client.publish(topic, message);
        } catch (MqttException e) {
            log.info("消息发布失败");
            e.printStackTrace();
        }
    }

}
OnMessageCallback

回调消息处理类

java 复制代码
package com.share.device.emqx.callback;

import com.alibaba.fastjson2.JSONObject;
import com.share.device.emqx.ProtocolConvertUtil;
import com.share.device.emqx.factory.MessageHandlerFactory;
import com.share.device.emqx.handler.MassageHandler;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OnMessageCallback implements MqttCallback {

    @Autowired
    private MessageHandlerFactory messageHandlerFactory;

//    @Autowired
//    private EmqxClientWrapper emqxClientWrapper;


    @Override
    public void connectionLost(Throwable cause) {
        // 连接丢失后,一般在这里面进行重连
        System.out.println("连接断开,可以做重连");
       // emqxClientWrapper.connect();
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        // subscribe后得到的消息会执行到这里面
        System.out.println("接收消息主题:" + topic);
        System.out.println("接收消息Qos:" + message.getQos());
        System.out.println("接收消息内容:" + new String(message.getPayload()));

        try {
            // 根据主题选择不同的处理逻辑
            MassageHandler massageHandler = messageHandlerFactory.getMassageHandler(topic);
            if(null != massageHandler) {
                JSONObject jsonMessage = ProtocolConvertUtil.convertJson(new String(message.getPayload()));
                massageHandler.handleMessage(jsonMessage);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("mqtt消息异常:{}", new String(message.getPayload()));
        }
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        System.out.println("deliveryComplete---------" + token.isComplete());
    }
}
MassageHandler

定义策略接口

java 复制代码
package com.share.device.emqx.handler;

import com.alibaba.fastjson2.JSONObject;

public interface MassageHandler {

    /**
     * 策略接口
     * @param message
     */
    void handleMessage(JSONObject message);
}
具体Handler处理
GuiguEmqx

自定义注解

java 复制代码
package com.share.device.emqx.annotation;

import java.lang.annotation.*;

// 自定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GuiguEmqx {

    String topic();

}
PowerBankConnectedHandler

充电宝插入处理类

java 复制代码
package com.share.device.emqx.handler.impl;

import com.alibaba.fastjson2.JSONObject;
import com.share.device.emqx.annotation.GuiguEmqx;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.emqx.handler.MassageHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;


@Slf4j
@Component
@GuiguEmqx(topic = EmqxConstants.TOPIC_POWERBANK_CONNECTED)
public class PowerBankConnectedHandler implements MassageHandler {

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handleMessage(JSONObject message) {
        log.info("handleMessage: {}", message.toJSONString());
    }
}
PowerBankUnlockHandler

充电宝弹出处理类

java 复制代码
package com.share.device.emqx.handler.impl;

import com.alibaba.fastjson2.JSONObject;
import com.share.device.emqx.annotation.GuiguEmqx;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.emqx.handler.MassageHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;


@Slf4j
@Component
@GuiguEmqx(topic = EmqxConstants.TOPIC_POWERBANK_UNLOCK)
public class PowerBankUnlockHandler implements MassageHandler {


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handleMessage(JSONObject message) {
        log.info("handleMessage: {}", message.toJSONString());
    }
}
PropertyPostHandler

充电宝属性上报处理类,上报电量等信息

java 复制代码
package com.share.device.emqx.handler.impl;

import com.alibaba.fastjson2.JSONObject;
import com.share.device.emqx.annotation.GuiguEmqx;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.emqx.handler.MassageHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Component
@GuiguEmqx(topic = EmqxConstants.TOPIC_PROPERTY_POST)
public class PropertyPostHandler implements MassageHandler {

    /**
     * 处理消息:
     *
     * @param message
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handleMessage(JSONObject message) {
        log.info("handleMessage: {}", message.toJSONString());
    }
}
创建Handler工厂类
MessageHandlerFactory
java 复制代码
package com.share.device.emqx.factory;


import com.share.device.emqx.handler.MassageHandler;

/**
 * 平台消息工厂
 */
public interface MessageHandlerFactory {

    MassageHandler getMassageHandler(String topic);
}
Handler工厂初始化
MessageHandlerFactoryImpl
java 复制代码
package com.share.device.emqx.factory.impl;

import com.share.device.emqx.annotation.GuiguEmqx;
import com.share.device.emqx.factory.MessageHandlerFactory;
import com.share.device.emqx.handler.MassageHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;


@Service
public class MessageHandlerFactoryImpl implements MessageHandlerFactory, ApplicationContextAware {


    private Map<String, MassageHandler> handlerMap = new HashMap<>();

    /**
     * 初始化bean对象
     *
     * @param ioc
     */
    @Override
    public void setApplicationContext(ApplicationContext ioc) {
        // 获取对象
        Map<String, MassageHandler> beanMap = ioc.getBeansOfType(MassageHandler.class);
        for (MassageHandler massageHandler : beanMap.values()) {
            GuiguEmqx guiguEmqx = AnnotatedElementUtils.findAllMergedAnnotations(massageHandler.getClass(), GuiguEmqx.class).iterator().next();
            if (null != guiguEmqx) {
                String topic = guiguEmqx.topic();
                // 初始化到map
                handlerMap.put(topic, massageHandler);
            }
        }
    }

    @Override
    public MassageHandler getMassageHandler(String topic) {
        return handlerMap.get(topic);
    }
}
OnMessageCallback

补充handler处理逻辑

java 复制代码
package com.share.device.emqx.callback;

import com.alibaba.fastjson2.JSONObject;
import com.share.device.emqx.ProtocolConvertUtil;
import com.share.device.emqx.factory.MessageHandlerFactory;
import com.share.device.emqx.handler.MassageHandler;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OnMessageCallback implements MqttCallback {

    @Autowired
    private MessageHandlerFactory messageHandlerFactory;

    @Override
    public void connectionLost(Throwable cause) {
        // 连接丢失后,一般在这里面进行重连
        System.out.println("连接断开,可以做重连");
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        // subscribe后得到的消息会执行到这里面
        System.out.println("接收消息主题:" + topic);
        System.out.println("接收消息Qos:" + message.getQos());
        System.out.println("接收消息内容:" + new String(message.getPayload()));

        try {
            // 根据主题选择不同的处理逻辑
            MassageHandler massageHandler = messageHandlerFactory.getMassageHandler(topic);
            if(null != massageHandler) {
                JSONObject jsonMessage = ProtocolConvertUtil.convertJson(new String(message.getPayload()));
                massageHandler.handleMessage(jsonMessage);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("mqtt消息异常:{}", new String(message.getPayload()));
        }
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        System.out.println("deliveryComplete---------" + token.isComplete());
    }
}

十一、扫码

扫码接口

扫码前判断:是否免押金

java 复制代码
@Operation(summary = "是否免押金")
@RequiresLogin
@GetMapping("/isFreeDeposit")
public AjaxResult isFreeDeposit() {
    return success(userInfoService.isFreeDeposit());
}

在'UserlnfoService'中创建方法'isFreeDeposit'

java 复制代码
    //扫码前判断:是否免押金
    Boolean isFreeDeposit();

实现方法

java 复制代码
    //扫码前判断:是否免押金
    @Override
    public Boolean isFreeDeposit() {
        // 默认免押金,模拟实现
        //根据用户id查询用户信息
        UserInfo userInfo = userInfoMapper.selectById(SecurityContextHolder.getUserId());
        //设置押金状态
        userInfo.setDepositStatus("1");
        this.updateById(userInfo);
        return true;
    }

扫码前判断:未完成订单

新建模块share-order、share-api-order

远程接口
OrderInfoApiController
java 复制代码
package com.share.order.api;

import com.share.common.core.domain.R;
import com.share.common.core.web.controller.BaseController;
import com.share.common.core.web.domain.AjaxResult;
import com.share.common.security.annotation.RequiresLogin;
import com.share.common.security.utils.SecurityUtils;
import com.share.order.domain.OrderInfo;
import com.share.order.service.IOrderInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 订单Controller
 *
 * @author atguigu
 * @date 2024-02-22
 */
@Tag(name = "订单接口管理")
@RestController
@RequestMapping("/orderInfo")
public class OrderInfoApiController extends BaseController
{
    @Autowired
    private IOrderInfoService orderInfoService;

    @Operation(summary = "获取未完成订单")
    @RequiresLogin
    @GetMapping("getNoFinishOrder")
    public AjaxResult getNoFinishOrder() {
        return success(orderInfoService.getNoFinishOrder(SecurityUtils.getUserId()));
    }

    @Operation(summary = "获取未完成订单")
    @GetMapping("getNoFinishOrder/{userId}")
    public R<OrderInfo> getNoFinishOrder(@PathVariable Long userId) {
        return R.ok(orderInfoService.getNoFinishOrder(userId));
    }

}
IOrderInfoService
java 复制代码
    //获取未完成订单
    OrderInfo getNoFinishOrder(Long userId);
OrderInfoServiceImpl
java 复制代码
package com.share.order.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.share.order.domain.OrderInfo;
import com.share.order.mapper.OrderInfoMapper;
import com.share.order.service.IOrderInfoService;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 订单Service业务层处理
 *
 * @author atguigu
 * @date 2024-02-22
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService {
    //获取未完成订单
    @Override
    public OrderInfo getNoFinishOrder(Long userId) {
        // 查询用户是否有使用中与未支付订单
        LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderInfo::getUserId, userId)
                .in(OrderInfo::getStatus, Arrays.asList("0", "1"))// 订单状态:0:充电中 1:未支付 2:已支付
                .orderByDesc(OrderInfo::getId)
                .last("limit 1");

        OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
        return orderInfo;
    }

}
搭建订单服务远程接口模块

操作模块:share-api-order

pom.xml
java 复制代码
<dependencies>

        <!-- RuoYi Common Core-->
        <dependency>
            <groupId>com.share</groupId>
            <artifactId>share-common-core</artifactId>
        </dependency>

    </dependencies>
pom.xml

share-order模块引入api依赖

java 复制代码
<dependency>
    <groupId>com.share</groupId>
    <artifactId>share-api-order</artifactId>
    <version>3.6.3</version>
</dependency>

实体类OrderInfo、OrderBill、UserInfoVo

openFeign接口定义
RemoteOrderInfoService
java 复制代码
package com.share.order.api;

import com.share.common.core.domain.R;
import com.share.order.domain.OrderInfo;
import com.share.order.factory.RemoteOrderInfoFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;


/**
 * 用户服务
 *
 * @author share
 */
@FeignClient(contextId = "remoteOrderInfoService",
        value = "share-order",
        fallbackFactory = RemoteOrderInfoFallbackFactory.class)
public interface RemoteOrderInfoService {
    /**
     * 获取用户未完成订单信息
     *
     * @param userId 用户ID,通过路径变量传递
     * @return 返回一个Result对象,其中包含OrderInfo类型的未完成订单数据
     */
    @GetMapping("/orderInfo/getNoFinishOrder/{userId}")
    public R<OrderInfo> getNoFinishOrder(@PathVariable("userId") Long userId);

}
ServiceNameConstants
java 复制代码
public static final String ORDER_SERVICE = "share-order";
RemoteOrderInfoFallbackFactory
java 复制代码
package com.share.order.factory;

import com.share.common.core.exception.ServiceException;
import com.share.order.api.RemoteOrderInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * 用户服务降级处理
 *
 * @author share
 */
@Component
public class RemoteOrderInfoFallbackFactory implements FallbackFactory<RemoteOrderInfoService>
{
    private static final Logger log = LoggerFactory.getLogger(RemoteOrderInfoFallbackFactory.class);

    @Override
    public RemoteOrderInfoService create(Throwable throwable)
    {
        log.error("订单服务调用失败:{}", throwable.getMessage());
        throw new ServiceException("获取用户未完成订单失败");
    }
}
加载配置类

resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

java 复制代码
com.share.order.api.factory.RemoteOrderInfoFallbackFactory

基础接口封装

根据柜号获取对应的柜体对象getBtCabinetNo
ICabinetService
java 复制代码
    /**
     * 根据柜号获取对应的柜体对象
     */
    Cabinet getBtCabinetNo(String cabinetNo);
CabinetServiceImpl

实现方法

java 复制代码
    @Override
    /**
     * 根据柜号获取柜子信息
     * @param cabinetNo 柜号
     * @return Cabinet 返回匹配的柜子对象,如果没有找到则返回null
     */
    public Cabinet getBtCabinetNo(String cabinetNo) {
        LambdaQueryWrapper<Cabinet> queryWrapper = new LambdaQueryWrapper<>();
        // 设置查询条件:柜号等于传入的cabinetNo
        queryWrapper.eq(Cabinet::getCabinetNo, cabinetNo);
        // 执行查询并返回结果
        Cabinet cabinet = cabinetMapper.selectOne(queryWrapper);
        return cabinet;
    }
根据机柜ID和槽位号获取槽位信息getBtSlotNo
ICabinetSlotService
java 复制代码
CabinetSlot getBtSlotNo(Long cabinetId, String slotNo);
CabinetSlotServiceImpl
java 复制代码
package com.share.device.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.share.device.domain.CabinetSlot;
import com.share.device.mapper.CabinetSlotMapper;
import com.share.device.service.ICabinetSlotService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 柜机插槽Service业务层处理
 *
 * @author atguigu
 * @date 2024-10-22
 */
@Service
public class CabinetSlotServiceImpl extends ServiceImpl<CabinetSlotMapper, CabinetSlot> implements ICabinetSlotService {
    @Autowired
    private CabinetSlotMapper cabinetSlotMapper;

    @Override
    public CabinetSlot getBtSlotNo(Long cabinetId, String slotNo) {
        LambdaQueryWrapper<CabinetSlot> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CabinetSlot::getCabinetId, cabinetId)
                .eq(CabinetSlot::getSlotNo, slotNo);
        CabinetSlot cabinetSlot = cabinetSlotMapper.selectOne(queryWrapper);
        return cabinetSlot;
    }
}
根据充电宝编号获取充电宝信息getByPowerBankNo
IPowerBankService
java 复制代码
    //根据充电宝编号获取充电宝信息
    PowerBank getByPowerBankNo(String powerBankNo);
PowerBankServiceImpl
java 复制代码
    /**
     * 根据充电宝编号查询充电宝信息
     *
     * @param powerBankNo 充电宝编号
     * @return 返回查询到的充电宝对象,如果未找到则返回null
     */
    @Override
    public PowerBank getByPowerBankNo(String powerBankNo) {
        LambdaQueryWrapper<PowerBank> queryWrapper = new LambdaQueryWrapper<>();
        // 设置查询条件:充电宝编号等于传入的powerBankNo
        queryWrapper.eq(PowerBank::getPowerBankNo, powerBankNo);
        // 执行查询并返回结果
        PowerBank powerBank = powerBankMapper.selectOne(queryWrapper);
        return powerBank;
    }

扫码业务接口

DeviceApiController
java 复制代码
    //扫码充电接口
    @Operation(summary = "扫码充电")
    @RequiresLogin
    @GetMapping("scanCharge/{cabinetNo}")
    public AjaxResult scanCharge(@PathVariable String cabinetNo) {
        return success(deviceService.scanCharge(cabinetNo));
    }
ScanChargeVo
java 复制代码
package com.share.device.domain;

@Data
@Schema(description = "扫码充电返回对象")
public class ScanChargeVo
{

    /** 状态(1是 0否) */
    @Schema(description = "状态:1:成功 2:有未归还充电宝 3:有未支付订单")
    private String status;

    @Schema(description = "消息")
    private String message;

}
IDeviceService

在'IDeviceService'中创建方法'scanCharge

java 复制代码
    /**
     * 扫描充电操作的方法
     */
    ScanChargeVo scanCharge(String cabinetNo);
添加依赖pom.xml
java 复制代码
        <dependency>
            <groupId>com.share</groupId>
            <artifactId>share-api-order</artifactId>
            <version>3.6.3</version>
        </dependency>

RemoteUserService.java

java 复制代码
    /**
     * 根据用户ID获取用户信息的接口方法
     *
     * @param id 用户ID,通过路径变量传递
     * @return 返回一个R对象,其中包含UserInfo类型的用户数据
     */
    @GetMapping(value = "/userInfo/getUserInfo/{id}")
    public R<UserInfo> getInfo(@PathVariable("id") Long id);
DeviceServiceImpl

实现方法

java 复制代码
@Autowired
    private RemoteUserService remoteUserService;

    @Autowired
    private RemoteOrderInfoService remoteOrderInfoService;

    @Autowired
    private EmqxClientWrapper emqxClientWrapper;

    @Override
    public ScanChargeVo scanCharge(String cabinetNo) {
        //1 远程调用:根据当前登录用户id查询用户信息,
        // 从用户信息获取是否支持免押金充电
        R<UserInfo> userInfoR = remoteUserService.getInfo(SecurityContextHolder.getUserId());
        UserInfo userInfo = userInfoR.getData();

        //判断用户信息
        if(userInfo == null) {
            throw new ServiceException("获取用户信息失败");
        }
        //判断是否免押金
        if("0".equals(userInfo.getDepositStatus())) {
            throw new ServiceException("未申请免押金使用");
        }

        ScanChargeVo scanChargeVo = new ScanChargeVo();
        //2 远程调用:判断用户是否有未完成订单
        R<OrderInfo> orderInfoR = remoteOrderInfoService.getNoFinishOrder(SecurityUtils.getUserId());
        OrderInfo orderInfo = orderInfoR.getData();
        if(orderInfo != null) {//有 未完成订单
            String status = userInfo.getStatus();
            if("0".equals(status)) {
                scanChargeVo.setStatus("2");
                scanChargeVo.setMessage("有未归还充电宝,请归还后使用");
                return scanChargeVo;
            }
            if("1".equals(status)) {
                scanChargeVo.setStatus("3");
                scanChargeVo.setMessage("有未支付订单,去支付");
                return scanChargeVo;
            }
        }


        //3 从柜机里面获取最优充电宝
        AvailableProwerBankVo availableProwerBankVo =
                this.checkAvailableProwerBank(cabinetNo);
        if(null == availableProwerBankVo) {
            throw new ServiceException("无可用充电宝");
        }
        if(!StringUtils.isEmpty(availableProwerBankVo.getErrMessage())) {
            throw new ServiceException(availableProwerBankVo.getErrMessage());
        }

        //4 把选择最优充电宝弹出
        // 使用MQTT弹出充电宝
        // 生成借取指令,弹出充电宝
        JSONObject object = new JSONObject();
        object.put("uId", SecurityContextHolder.getUserId());//SecurityUtils.getUserId()
        object.put("mNo", "mm"+ RandomUtil.randomString(8));
        object.put("cNo", cabinetNo);
        object.put("pNo", availableProwerBankVo.getPowerBankNo());
        object.put("sNo", availableProwerBankVo.getSlotNo());
        String topic = String.format(EmqxConstants.TOPIC_SCAN_SUBMIT, cabinetNo);
        String message = ProtocolConvertUtil.convertString(object);
        emqxClientWrapper.publish(topic, message);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //5 返回封装需要数据
        scanChargeVo.setStatus("1");
        return scanChargeVo;
    }
    @Autowired
    private ICabinetSlotService cabinetSlotService;

    @Autowired
    private IPowerBankService powerBankService;

    //获取柜机的充电宝信息
    private AvailableProwerBankVo checkAvailableProwerBank(String cabinetNo) {
        //1 创建AvailableProwerBankVo对象
        AvailableProwerBankVo availableProwerBankVo = new AvailableProwerBankVo();

        //2 根据cabinetNo柜机编号查询柜机信息
        Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);

        //3 判断柜机可用充电宝数量是否大于0
        Integer availableNum = cabinet.getAvailableNum();
        if(availableNum == 0) {
            availableProwerBankVo.setErrMessage("无可用充电宝");
            return availableProwerBankVo;
        }

        //4 根据柜机id查询插槽列表,返回list集合
        LambdaQueryWrapper<CabinetSlot> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CabinetSlot::getCabinetId,cabinet.getId());
        wrapper.eq(CabinetSlot::getStatus, "1");
        List<CabinetSlot> cabinetSlotList = cabinetSlotService.list(wrapper);

        //5 从第四步返回插槽列表list集合 获取对应充电宝id集合
        List<Long> powerBankIdList = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId())
                .map(CabinetSlot::getPowerBankId).collect(Collectors.toList());

        //6 根据充电宝id列表查询对应充电宝信息
        LambdaQueryWrapper<PowerBank> wrapperPowerBank = new LambdaQueryWrapper<>();
        wrapperPowerBank.in(PowerBank::getId,powerBankIdList);
        wrapperPowerBank.eq(PowerBank::getStatus,"1");
        List<PowerBank> powerBankList = powerBankService.list(wrapperPowerBank);
        //判断集合不为空
        if(CollectionUtils.isEmpty(powerBankList)) {
            availableProwerBankVo.setErrMessage("无可用充电宝");
            return availableProwerBankVo;
        }

        //7 把上一步获取充电宝信息集合进行排序(根据电量降序)
        if(powerBankList.size()>1) {
            Collections.sort(powerBankList,
                    (o1,o2)->o2.getElectricity().compareTo(o1.getElectricity()));
        }

        //8 获取电量最多充电宝信息
        PowerBank powerBank = powerBankList.get(0);

        //9 获取电量最多的充电宝对应插槽信息
        CabinetSlot cabinetSlot = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId()
                        && item.getPowerBankId().equals(powerBank.getId())).collect(Collectors.toList()).get(0);

        //10 锁定插槽(更新插槽状态)
        cabinetSlot.setStatus("2");
        cabinetSlotService.updateById(cabinetSlot);

        //11 返回需要vo数据
        availableProwerBankVo.setPowerBankNo(powerBank.getPowerBankNo());
        availableProwerBankVo.setSlotNo(cabinetSlot.getSlotNo());
        return availableProwerBankVo;
    }

充电宝弹出业务处理

PowerBankUnlockHandler
java 复制代码
package com.share.device.emqx.handler.impl;

import com.alibaba.fastjson2.JSONObject;
import com.share.common.core.utils.StringUtils;
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 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.util.Date;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
@GuiguEmqx(topic = EmqxConstants.TOPIC_POWERBANK_UNLOCK)
public class PowerBankUnlockHandler implements MassageHandler {

    @Autowired
    private ICabinetService cabinetService;

    @Autowired
    private IPowerBankService powerBankService;

    @Autowired
    private ICabinetSlotService cabinetSlotService;

    @Autowired
    private IStationService stationService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handleMessage(JSONObject message) {
        log.info("handleMessage: {}", message.toJSONString());
        //消息编号
        String messageNo = message.getString("mNo");
        //防止重复请求
        String key = "powerBank:unlock:" + 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");
        //用户id
        Long userId = message.getLong("uId");
        if (StringUtils.isEmpty(cabinetNo)
                || StringUtils.isEmpty(powerBankNo)
                || StringUtils.isEmpty(slotNo)
                || null == userId) {
            log.info("参数为空: {}", message.toJSONString());
            return;
        }
        //获取柜机
        Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);
        // 获取充电宝
        PowerBank powerBank = powerBankService.getByPowerBankNo(powerBankNo);
        // 获取插槽
        CabinetSlot cabinetSlot = cabinetSlotService.getBtSlotNo(cabinet.getId(), slotNo);
        // 获取站点
        Station station = stationService.getByCabinetId(cabinet.getId());

        //更新充电宝状态
        // 状态(0:未投放 1:可用 2:已租用 3:充电中 4:故障)
        powerBank.setStatus("2");
        powerBankService.updateById(powerBank);

        //更新插槽状态
        // 状态(1:占用 0:空闲)
        cabinetSlot.setStatus("0");
        cabinetSlot.setPowerBankId(null);
        cabinetSlot.setUpdateTime(new Date());
        cabinetSlotService.updateById(cabinetSlot);

        //更新柜机信息
        int freeSlots = cabinet.getFreeSlots() + 1;
        cabinet.setFreeSlots(freeSlots);
        int usedSlots = cabinet.getUsedSlots() - 1;
        cabinet.setUsedSlots(usedSlots);
        //可以借用
        int availableNum = cabinet.getAvailableNum() - 1;
        cabinet.setAvailableNum(availableNum);
        cabinet.setUpdateTime(new Date());
        cabinetService.updateById(cabinet);

        //发送消息构建订单
        //TODO
    }
    
}
IStationService

在'StationService'中创建方法'getByCabinetld'

java 复制代码
    /**
     * 根据机柜ID获取站点信息
     *
     * @param id 机柜ID,类型为Long
     * @return 返回对应的站点对象
     */
    Station getByCabinetId(Long id);
StationServiceI

实现方法

java 复制代码
    /**
     * 根据柜子ID获取站点信息
     *
     * @param id 柜子ID
     * @return 返回对应的站点对象,如果不存在则返回null
     */
    @Override
    public Station getByCabinetId(Long id) {
        LambdaQueryWrapper<Station> queryWrapper = new LambdaQueryWrapper<>();
        // 设置查询条件:柜子ID等于传入的id
        queryWrapper.eq(Station::getCabinetId, id);
        // 执行查询,获取符合条件的站点信息
        Station station = stationMapper.selectOne(queryWrapper);
        // 返回查询结果
        return station;
    }

订单详情接口

OrderInfoApiController
java 复制代码
@Operation(summary = "获取订单详细信息")
@RequiresLogin
@GetMapping(value = "/getOrderInfo/{id}")
public AjaxResult getOrderInfo(@PathVariable("id") Long id)
{
    return success(orderInfoService.selectOrderInfoById(id));
}
IOrderInfoService

在'IOrderlnfoService'中创建方法'selectOrderlnfoByld'

java 复制代码
    /**
     * 根据ID查询订单信息
     *
     * @param id 订单ID
     * @return 返回订单信息对象
     */
    Object selectOrderInfoById(Long id);

实现方法

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;
    }
相关推荐
71-32 小时前
VMware没网的处理方式
笔记·学习
微露清风2 小时前
系统性学习Linux-第五讲-基础IO
linux·运维·学习
風清掦2 小时前
【江科大STM32学习笔记-08】DMA直接存储器存取
笔记·stm32·单片机·嵌入式硬件·学习
Nan_Shu_6142 小时前
学习: 尚硅谷Java项目之小谷充电宝(5)
学习
zhouping@2 小时前
JAVA的学习笔记day05
java·笔记·学习
小陈phd2 小时前
多模态大模型学习笔记(十二)——transformer学习之Embedding
笔记·学习·transformer
格鸰爱童话2 小时前
向AI学习项目技能(三)
java·人工智能·python·学习
头疼的程序员3 小时前
计算机网络:自顶向下方法(第七版)第三章 学习分享(三)
网络·学习·计算机网络
观书喜夜长3 小时前
OpenClaw 本地安装与 Ollama 大模型接入实战教程-实现无限tokens使用
学习·网络安全