1
0

Merge remote-tracking branch 'origin/main'

# Conflicts:
#	fys-admin/src/main/java/com/fuyuanshen/app/service/AppDeviceBizService.java
#	fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppDeviceBindRecord.java
This commit is contained in:
2025-08-01 09:06:03 +08:00
45 changed files with 2743 additions and 230 deletions

View File

@ -1,7 +1,11 @@
package com.fuyuanshen.app.controller;
import cn.hutool.json.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.APPReNameDTO;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.APPDeviceTypeVo;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.app.service.AppDeviceBizService;
@ -18,8 +22,10 @@ import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
/**
* APP设备信息管理
@ -38,7 +44,6 @@ public class AppDeviceController extends BaseController {
*/
@GetMapping("/list")
public TableDataInfo<AppDeviceVo> list(DeviceQueryCriteria bo, PageQuery pageQuery) {
return appDeviceService.queryAppDeviceList(bo,pageQuery);
}
@ -105,4 +110,60 @@ public class AppDeviceController extends BaseController {
public R<Void> sendMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(appDeviceService.sendMessage(bo));
}
/**
* 上传设备logo图片
*/
@PostMapping("/uploadLogo")
public R<Void> upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
appDeviceService.uploadDeviceLogo(bo);
return R.ok();
}
/**
* 灯光模式
* 0关灯1强光模式2弱光模式, 3爆闪模式, 4泛光模式
*/
@PostMapping("/lightModeSettings")
public R<Void> lightModeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
appDeviceService.lightModeSettings(params);
return R.ok();
}
/**
* 灯光亮度设置
*
*/
@PostMapping("/lightBrightnessSettings")
public R<Void> lightBrightnessSettings(@RequestBody DeviceInstructDto params) {
appDeviceService.lightBrightnessSettings(params);
return R.ok();
}
/**
* 激光模式设置
*
*/
@PostMapping("/laserModeSettings")
public R<Void> laserModeSettings(@RequestBody DeviceInstructDto params) {
appDeviceService.laserModeSettings(params);
return R.ok();
}
/**
* 地图逆解析
*
*/
@PostMapping("/mapReverseGeocoding")
public R<Void> mapReverseGeocoding(@RequestBody DeviceInstructDto params) {
String mapJson = appDeviceService.mapReverseGeocoding(params);
return R.ok(mapJson);
}
}

View File

@ -0,0 +1,16 @@
package com.fuyuanshen.app.domain.dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class AppDeviceLogoUploadDto {
private Long deviceId;
/**
* 文件
*/
private MultipartFile file;
}

View File

@ -0,0 +1,13 @@
package com.fuyuanshen.app.domain.dto;
import lombok.Data;
@Data
public class AppLightModeDto {
private Long deviceId;
//0关灯1强光模式2弱光模式, 3爆闪模式, 4泛光模式
private Integer mode;
}

View File

@ -0,0 +1,16 @@
package com.fuyuanshen.app.domain.dto;
import lombok.Data;
@Data
public class DeviceInstructDto {
private Long deviceId;
private String deviceImei;
/**
* 下发指令
*/
private Object instructValue;
}

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.app.service;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@ -122,27 +123,6 @@ public class AppDeviceBizService {
if (device.getBindingStatus() != null && device.getBindingStatus() == BindingStatusEnum.BOUND.getCode()) {
throw new RuntimeException("设备已绑定");
}
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
if (appDeviceBindRecord != null) {
UpdateWrapper<AppDeviceBindRecord> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_id", device.getId())
.set("binding_status", BindingStatusEnum.BOUND.getCode())
.set("binding_user_id", userId)
.set("update_time", new Date())
.set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
}else{
AppDeviceBindRecord bindRecord = new AppDeviceBindRecord();
bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date());
bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord);
}
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("id", device.getId())
.set("binding_status", BindingStatusEnum.BOUND.getCode())
@ -191,30 +171,48 @@ public class AppDeviceBizService {
}
public int unBindDevice(Long id) {
return unBindDevice(id, null, 1);
}
public int unBindDevice(Long id, Long userId, int type) {
Device device = deviceMapper.selectById(id);
if (device == null) {
throw new RuntimeException("请先将设备入库!!!");
}
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("id", device.getId())
.set("binding_user_id", null)
.set("binding_status", BindingStatusEnum.UNBOUND.getCode())
.set("binding_user_id", null)
.set("binding_time", null);
deviceMapper.update(null, deviceUpdateWrapper);
Long userId = AppLoginHelper.getUserId();
if (userId == null) {
userId = AppLoginHelper.getUserId();
}
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
bindRecordQueryWrapper.eq("binding_user_id", userId);
AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
if (appDeviceBindRecord != null) {
return appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId());
// 设备端解绑 0:设备端解绑 1:web端解绑
if (type == 1) {
bindRecordQueryWrapper.eq("binding_user_id", userId);
}
// AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
// if (appDeviceBindRecord != null) {
// return appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId());
// }
List<AppDeviceBindRecord> appDeviceBindRecordList = appDeviceBindRecordMapper.selectList(bindRecordQueryWrapper);
if (CollectionUtil.isNotEmpty(appDeviceBindRecordList)) {
appDeviceBindRecordList.forEach(appDeviceBindRecord ->
appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId()));
}
return 1;
}
public AppDeviceDetailVo getInfo(Long id) {
Device device = deviceMapper.selectById(id);
if (device == null) {
@ -228,7 +226,7 @@ public class AppDeviceBizService {
vo.setDeviceMac(device.getDeviceMac());
vo.setDeviceStatus(device.getDeviceStatus());
DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
if(deviceType!=null){
if (deviceType != null) {
vo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode()));
vo.setTypeName(deviceType.getTypeName());
}
@ -239,7 +237,7 @@ public class AppDeviceBizService {
QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
.eq("device_id", device.getId());
AppPersonnelInfo appPersonnelInfo = appPersonnelInfoMapper.selectOne(qw);
if(appPersonnelInfo != null){
if (appPersonnelInfo != null) {
AppPersonnelInfoVo personnelInfoVo = MapstructUtils.convert(appPersonnelInfo, AppPersonnelInfoVo.class);
vo.setPersonnelInfo(personnelInfoVo);
}
@ -269,19 +267,21 @@ public class AppDeviceBizService {
mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY+deviceObj.getDeviceImei(), 1 ,personnelInfo);
log.info("发送点阵数据到设备消息:{}", bo);
if(ObjectUtils.length(appPersonnelInfoVos) == 0){
if (ObjectUtils.length(appPersonnelInfoVos) == 0) {
AppPersonnelInfo appPersonnelInfo = MapstructUtils.convert(bo, AppPersonnelInfo.class);
return appPersonnelInfoMapper.insertOrUpdate(appPersonnelInfo);
}else {
} else {
UpdateWrapper<AppPersonnelInfo> uw = new UpdateWrapper<>();
uw.eq("device_id", deviceId)
.set("name", bo.getName())
.set("position", bo.getPosition())
.set("unit_name", bo.getUnitName())
.set("code",bo.getCode());
return appPersonnelInfoMapper.update(null, uw)>0;
.set("code", bo.getCode());
return appPersonnelInfoMapper.update(null, uw) > 0;
}
}
}

View File

@ -0,0 +1,26 @@
package com.fuyuanshen.global.mqtt.base;
/**
* MQTT消息处理接口
*/
public interface MqttMessageRule {
/**
* 获取命令类型
* @return 命令类型
*/
String getCommandType();
/**
* 执行处理
* @param context 处理上下文
*/
void execute(MqttRuleContext context);
/**
* 获取优先级,数值越小优先级越高
* @return 优先级
*/
default int getPriority() {
return 0;
}
}

View File

@ -0,0 +1,32 @@
package com.fuyuanshen.global.mqtt.base;
import lombok.Data;
import java.util.Map;
/**
* MQTT消息处理上下文
*/
@Data
public class MqttRuleContext {
/**
* 命令类型
*/
private byte commandType;
/**
* 转换后的参数数组
*/
private Object[] convertArr;
/**
* 设备IMEI
*/
private String deviceImei;
/**
* 数据来源Redis
*/
private String dataFromRedis;
/**
* MQTT消息负载字典
*/
private Map<String, Object> payloadDict;
}

View File

@ -0,0 +1,39 @@
package com.fuyuanshen.global.mqtt.base;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
/**
* MQTT消息引擎
*/
@Component
public class MqttRuleEngine {
private final LinkedHashMap<String, MqttMessageRule> rulesMap = new LinkedHashMap<>();
public MqttRuleEngine(List<MqttMessageRule> rules) {
// 按优先级排序
rules.sort(Comparator.comparing(MqttMessageRule::getPriority));
rules.forEach(rule -> rulesMap.put(rule.getCommandType(), rule)
);
}
/**
* 执行匹配
* @param context 处理上下文
* @return
*/
public boolean executeRule(MqttRuleContext context) {
int commandType = context.getCommandType();
MqttMessageRule mqttMessageRule = rulesMap.get("Light_"+commandType);
if (mqttMessageRule != null) {
mqttMessageRule.execute(context);
return true;
}
return false;
}
}

View File

@ -0,0 +1,31 @@
package com.fuyuanshen.global.mqtt.config;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
@Configuration
public class MqttConfiguration {
@Autowired
private MqttPropertiesConfig mqttPropertiesConfig;
/** 创建连接工厂 **/
@Bean
public MqttPahoClientFactory mqttPahoClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true); //设置新会话
options.setUserName(mqttPropertiesConfig.getUsername());
options.setPassword(mqttPropertiesConfig.getPassword().toCharArray());
options.setServerURIs(new String[]{mqttPropertiesConfig.getUrl()});
options.setAutomaticReconnect(true); // 启用自动重连
options.setConnectionTimeout(10); // 设置连接超时时间
options.setKeepAliveInterval(60); // 设置心跳间隔
factory.setConnectionOptions(options);
return factory;
}
}

View File

@ -0,0 +1,17 @@
package com.fuyuanshen.global.mqtt.config;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
/**
* @Author: HarryLin
* @Date: 2025/3/20 17:06
* @Company: 北京红山信息科技研究院有限公司
* @Email: linyun@***.com.cn
**/
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGateway {
public abstract void sendMsgToMqtt(@Header(value = MqttHeaders.TOPIC) String topic, String payload);
public abstract void sendMsgToMqtt(@Header(value = MqttHeaders.TOPIC) String topic, @Header(value = MqttHeaders.QOS) int qos, String payload );
}

View File

@ -0,0 +1,58 @@
package com.fuyuanshen.global.mqtt.config;
import cn.hutool.core.lang.UUID;
import com.fuyuanshen.global.mqtt.receiver.ReceiverMessageHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@Configuration
public class MqttInboundConfiguration {
@Autowired
private MqttPropertiesConfig mqttPropertiesConfig;
@Autowired
private MqttPahoClientFactory mqttPahoClientFactory;
@Autowired
private ReceiverMessageHandler receiverMessageHandler;
//消息通道
@Bean
public MessageChannel messageInboundChannel(){
return new DirectChannel();
}
/**
* 配置入站适配器
* 作用: 设置订阅主题,以及指定消息的通道 等相关属性
* */
@Bean
public MessageProducer messageProducer(){
// 生成一个不重复的随机数
String clientId = mqttPropertiesConfig.getSubClientId() + "_" + UUID.fastUUID();
MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter = new MqttPahoMessageDrivenChannelAdapter(
mqttPropertiesConfig.getUrl(),
clientId,
mqttPahoClientFactory,
mqttPropertiesConfig.getSubTopic().split(",")
);
mqttPahoMessageDrivenChannelAdapter.setQos(1);
mqttPahoMessageDrivenChannelAdapter.setConverter(new DefaultPahoMessageConverter());
mqttPahoMessageDrivenChannelAdapter.setOutputChannel(messageInboundChannel());
return mqttPahoMessageDrivenChannelAdapter;
}
/** 指定处理消息来自哪个通道 */
@Bean
@ServiceActivator(inputChannel = "messageInboundChannel")
public MessageHandler messageHandler(){
return receiverMessageHandler;
}
}

View File

@ -0,0 +1,46 @@
package com.fuyuanshen.global.mqtt.config;
import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@Configuration
@Slf4j
public class MqttOutboundConfiguration {
@Autowired
private MqttPropertiesConfig mqttPropertiesConfig;
@Autowired
private MqttPahoClientFactory mqttPahoClientFactory;
// 消息通道
@Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
/** 配置出站消息处理器 */
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel") // 指定处理器针对哪个通道的消息进行处理
public MessageHandler mqttOutboundMessageHandler(){
String clientId = mqttPropertiesConfig.getPubClientId() + "_" + UUID.fastUUID();
MqttPahoMessageHandler mqttPahoMessageHandler = new MqttPahoMessageHandler(
mqttPropertiesConfig.getUrl(),
clientId,
mqttPahoClientFactory
);
mqttPahoMessageHandler.setDefaultQos(1);
mqttPahoMessageHandler.setDefaultTopic("B/#");
mqttPahoMessageHandler.setAsync(true);
return mqttPahoMessageHandler;
}
}

View File

@ -0,0 +1,19 @@
package com.fuyuanshen.global.mqtt.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "mqtt")
@Component
public class MqttPropertiesConfig {
private String username;
private String password;
private String url;
private String subClientId;
private String subTopic;
private String pubClientId;
private String pubTopic;
}

View File

@ -0,0 +1,76 @@
package com.fuyuanshen.global.mqtt.constants;
/**
* 设备命令类型常量
* Device Command Type Constants
*/
public class LightingCommandTypeConstants {
/**
* 灯光模式 (Light Mode)
*/
public static final String LIGHT_MODE = "Light_1";
/**
* 人员信息 (Personnel Information)
*/
public static final String PERSONNEL_INFO = "Light_2";
/**
* 开机LOGO (Boot Logo)
*/
public static final String BOOT_LOGO = "Light_3";
/**
* 激光灯 (Laser Light)
*/
public static final String LASER_LIGHT = "Light_4";
/**
* 主灯亮度 (Main Light Brightness)
*/
public static final String MAIN_LIGHT_BRIGHTNESS = "Light_5";
/**
* 定位数据 (Location Data)
*/
public static final String LOCATION_DATA = "Light_11";
/**
* 主动上报设备数据 (Active Reporting Device Data)
*/
public static final String ACTIVE_REPORTING_DEVICE_DATA = "Light_12";
/**
* 获取命令类型描述
*
* @param commandType 命令类型
* @return 命令类型描述
*/
public static String getCommandTypeDescription(String commandType) {
return switch (commandType) {
case LIGHT_MODE -> "灯光模式 (Light Mode)";
case PERSONNEL_INFO -> "人员信息 (Personnel Information)";
case BOOT_LOGO -> "开机LOGO (Boot Logo)";
case LASER_LIGHT -> "激光灯 (Laser Light)";
case MAIN_LIGHT_BRIGHTNESS -> "主灯亮度 (Main Light Brightness)";
case LOCATION_DATA -> "定位数据 (Location Data)";
default -> "未知命令类型 (Unknown Command Type)";
};
}
/**
* 检查是否为有效命令类型
*
* @param commandType 命令类型
* @return 是否有效
*/
public static boolean isValidCommandType(String commandType) {
return commandType.equals(LIGHT_MODE) ||
commandType.equals(PERSONNEL_INFO) ||
commandType.equals(BOOT_LOGO) ||
commandType.equals(LASER_LIGHT) ||
commandType.equals(MAIN_LIGHT_BRIGHTNESS) ||
commandType.equals(LOCATION_DATA);
}
}

View File

@ -0,0 +1,16 @@
package com.fuyuanshen.global.mqtt.constants;
public interface MqttConstants {
/**
* 全局发布消息的key
*/
String GLOBAL_PUB_KEY = "B/";
/**
* 全局订阅消息的key
*/
String GLOBAL_SUB_KEY = "A/";
}

View File

@ -0,0 +1,23 @@
package com.fuyuanshen.global.mqtt.publish;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/")
@Slf4j
public class DeviceDataController {
@Autowired
private MqttClientTest mqttClientTest;
// @PostMapping("/{deviceId}/command")
public ResponseEntity<String> sendCommand() {
mqttClientTest.sendMsg();
return ResponseEntity.ok("success");
}
}

View File

@ -0,0 +1,22 @@
package com.fuyuanshen.global.mqtt.publish;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class MqttClientTest {
@Autowired
private MqttGateway mqttGateway;
public void sendMsg() {
mqttGateway.sendMsgToMqtt("worker/location/1", "hello mqtt spring boot");
log.info("message is send");
mqttGateway.sendMsgToMqtt("worker/alert/2", "hello mqtt spring boot2");
log.info("message is send2");
}
}

View File

@ -0,0 +1,25 @@
package com.fuyuanshen.global.mqtt.publish;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;
/**
* @Author: HarryLin
* @Date: 2025/3/20 16:16
* @Company: 北京红山信息科技研究院有限公司
* @Email: linyun@***.com.cn
**/
@Service
public class MqttMessageSender {
@Autowired
private MqttGateway mqttGateway;
public void sendMsg(@Header(value = MqttHeaders.TOPIC) String topic, String payload) {
mqttGateway.sendMsgToMqtt(topic,payload);
}
public void sendMsg(@Header(value = MqttHeaders.TOPIC) String topic, @Header(value = MqttHeaders.QOS) int qos, String payload) {
mqttGateway.sendMsgToMqtt(topic,qos,payload);
}
}

View File

@ -0,0 +1,63 @@
package com.fuyuanshen.global.mqtt.receiver;
import cn.hutool.core.lang.Dict;
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.base.MqttRuleEngine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
@Slf4j
public class ReceiverMessageHandler implements MessageHandler {
@Autowired
private MqttRuleEngine ruleEngine;
@Override
public void handleMessage(Message<?> message) throws MessagingException {
Object payload = message.getPayload();
MessageHeaders headers = message.getHeaders();
String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString();
String receivedQos = Objects.requireNonNull(headers.get("mqtt_receivedQos")).toString();
String timestamp = Objects.requireNonNull(headers.get("timestamp")).toString();
log.info("MQTT payload= {} \n receivedTopic = {} \n receivedQos = {} \n timestamp = {}",
payload, receivedTopic, receivedQos, timestamp);
Dict payloadDict = JsonUtils.parseMap(payload.toString());
if (receivedTopic == null || payloadDict == null) {
return;
}
String state = payloadDict.getStr("state");
Object[] convertArr = ImageToCArrayConverter.convertByteStringToMixedObjectArray(state);
if (convertArr.length > 0) {
Byte val1 = (Byte) convertArr[0];
String[] subStr = receivedTopic.split("/");
System.out.println("收到设备id: " + subStr[1]);
String deviceImei = subStr[1];
MqttRuleContext context = new MqttRuleContext();
context.setCommandType(val1);
context.setConvertArr(convertArr);
context.setDeviceImei(deviceImei);
context.setPayloadDict(payloadDict);
boolean ruleExecuted = ruleEngine.executeRule(context);
if (!ruleExecuted) {
log.warn("未找到匹配的规则来处理命令类型: {}", val1);
}
}
}
}

View File

@ -0,0 +1,95 @@
package com.fuyuanshen.global.mqtt.rule;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* 主动上报设备数据命令处理
* "第1位为12表示设备主动上报设备硬件状态第2为表示当时设备主灯档位第3位表示当时激光灯档位第4位电量百分比第5位为充电状态0没有充电1正在充电2为已充满
* 第6位200代表电池剩余续航时间200分钟"
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class ActiveReportingDeviceDataRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
@Override
public String getCommandType() {
return LightingCommandTypeConstants.ACTIVE_REPORTING_DEVICE_DATA;
}
@Override
public void execute(MqttRuleContext context) {
try {
Object[] convertArr = context.getConvertArr();
// Latitude, longitude
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间
String mainLightMode = convertArr[1].toString();
String laserLightMode = convertArr[2].toString();
String batteryPercentage = convertArr[3].toString();
String chargeState = convertArr[4].toString();
String batteryRemainingTime = convertArr[5].toString();
// 异步发送设备状态和位置信息到Redis
asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(), mainLightMode, laserLightMode,
batteryPercentage, chargeState, batteryRemainingTime);
} catch (Exception e) {
log.error("处理定位数据命令时出错", e);
}
}
/**
* 异步发送设备状态信息和位置信息到Redis使用CompletableFuture
*
* @param deviceImei 设备IMEI
* @param mainLightMode 主灯档位
* @param laserLightMode 激光灯档位
* @param batteryPercentage 电量百分比
* @param chargeState 充电状态
* @param batteryRemainingTime 电池剩余续航时间
*/
public void asyncSendDeviceDataToRedisWithFuture(String deviceImei, String mainLightMode, String laserLightMode,
String batteryPercentage, String chargeState, String batteryRemainingTime) {
CompletableFuture.runAsync(() -> {
try {
// 构造设备状态信息对象
Map<String, Object> deviceInfo = new LinkedHashMap<>();
deviceInfo.put("deviceImei", deviceImei);
deviceInfo.put("mainLightMode", mainLightMode);
deviceInfo.put("laserLightMode", laserLightMode);
deviceInfo.put("batteryPercentage", batteryPercentage);
deviceInfo.put("chargeState", chargeState);
deviceInfo.put("batteryRemainingTime", batteryRemainingTime);
deviceInfo.put("timestamp", System.currentTimeMillis());
// 将设备状态信息存储到Redis中
String deviceRedisKey = "device:status:" + deviceImei;
String deviceInfoJson = JsonUtils.toJsonString(deviceInfo);
// 存储到Redis设置过期时间例如24小时
RedisUtils.setCacheObject(deviceRedisKey, deviceInfoJson, Duration.ofSeconds(24 * 60 * 60));
log.info("设备状态信息已异步发送到Redis: device={}, mainLightMode={}, laserLightMode={}, batteryPercentage={}",
deviceImei, mainLightMode, laserLightMode, batteryPercentage);
} catch (Exception e) {
log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e);
}
});
}
}

View File

@ -0,0 +1,149 @@
package com.fuyuanshen.global.mqtt.rule;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil;
import com.fuyuanshen.equipment.utils.map.LngLonUtil;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* 定位数据命令处理
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class LocationDataRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
@Override
public String getCommandType() {
return LightingCommandTypeConstants.LOCATION_DATA;
}
@Override
public void execute(MqttRuleContext context) {
try {
Object[] convertArr = context.getConvertArr();
// Latitude, longitude
String latitude = convertArr[1].toString();
String longitude = convertArr[2].toString();
// 异步发送经纬度到Redis
asyncSendLocationToRedisWithFuture(context.getDeviceImei(), latitude, longitude);
Map<String, Object> map = buildLocationDataMap(latitude, longitude);
mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map));
log.info("发送定位数据到设备=>topic:{},payload:{}",
MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(),
JsonUtils.toJsonString(map));
} catch (Exception e) {
log.error("处理定位数据命令时出错", e);
}
}
/**
* 异步发送位置信息到Redis
*
* @param deviceImei 设备IMEI
* @param latitude 纬度
* @param longitude 经度
* @param timestamp 时间戳
*/
// @Async
// public void asyncSendLocationToRedis(String deviceImei, String latitude, String longitude, long timestamp) {
// try {
// // 构造位置信息对象
// Map<String, Object> locationInfo = new HashMap<>();
// locationInfo.put("deviceImei", deviceImei);
// locationInfo.put("latitude", latitude);
// locationInfo.put("longitude", longitude);
// locationInfo.put("timestamp", timestamp);
//
// // 将位置信息存储到Redis中使用设备IMEI作为key的一部分
// String redisKey = "device:location:" + deviceImei;
// String locationJson = JsonUtils.toJsonString(locationInfo);
//
// // 存储到Redis设置过期时间例如24小时
// RedisUtils.setCacheObject(redisKey, locationJson, Duration.ofSeconds(24 * 60 * 60));
//
// // 也可以存储到一个列表中,保留历史位置信息
// String locationHistoryKey = "device:location:history:" + deviceImei;
// RedisUtils.lPush(locationHistoryKey, locationJson, 24 * 60 * 60);
//
// log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
// } catch (Exception e) {
// log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e);
// }
// }
/**
* 异步发送位置信息到Redis使用CompletableFuture
*
* @param deviceImei 设备IMEI
* @param latitude 纬度
* @param longitude 经度
*/
public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) {
CompletableFuture.runAsync(() -> {
try {
if(StringUtils.isNotBlank(latitude) || StringUtils.isNotBlank(longitude)){
return;
}
// 构造位置信息对象
Map<String, Object> locationInfo = new LinkedHashMap<>();
double[] doubles = LngLonUtil.gps84_To_Gcj02(Double.parseDouble(latitude), Double.parseDouble(longitude));
locationInfo.put("deviceImei", deviceImei);
locationInfo.put("latitude", doubles[0]);
locationInfo.put("longitude", doubles[1]);
String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0]));
locationInfo.put("address", address);
locationInfo.put("timestamp", System.currentTimeMillis());
// 将位置信息存储到Redis中
String redisKey = "device:location:" + deviceImei;
String locationJson = JsonUtils.toJsonString(locationInfo);
// 存储到Redis
RedisUtils.setCacheObject(redisKey, locationJson, Duration.ofSeconds(24 * 60 * 60));
log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
} catch (Exception e) {
log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e);
}
});
}
private Map<String, Object> buildLocationDataMap(String latitude, String longitude) {
String[] latArr = latitude.split("\\.");
String[] lonArr = longitude.split("\\.");
ArrayList<Integer> intData = new ArrayList<>();
intData.add(11);
intData.add(Integer.parseInt(latArr[0]));
intData.add(Integer.parseInt(latArr[1]));
intData.add(Integer.parseInt(lonArr[0]));
intData.add(Integer.parseInt(lonArr[1]));
Map<String, Object> map = new HashMap<>();
map.put("instruct", intData);
return map;
}
}

View File

@ -0,0 +1,76 @@
package com.fuyuanshen.global.mqtt.rule;
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
/**
* 人员信息命令处理
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class PersonnelInfoRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
@Override
public String getCommandType() {
return LightingCommandTypeConstants.PERSONNEL_INFO;
}
@Override
public void execute(MqttRuleContext context) {
try {
Byte val2 = (Byte) context.getConvertArr()[1];
if (val2 == 100) {
return;
}
String data = RedisUtils.getCacheObject("894078:app_logo_data:" + context.getDeviceImei());
if (StringUtils.isEmpty(data)) {
return;
}
byte[] arr = ImageToCArrayConverter.convertStringToByteArray(data);
byte[] specificChunk = ImageToCArrayConverter.getChunk(arr, (val2 - 1), 512);
System.out.println("" + val2 + "块数据大小: " + specificChunk.length + " 字节");
System.out.println("" + val2 + "块数据: " + Arrays.toString(specificChunk));
ArrayList<Integer> intData = new ArrayList<>();
intData.add(3);
intData.add((int) val2);
ImageToCArrayConverter.buildArr(convertHexToDecimal(specificChunk), intData);
intData.add(0);
intData.add(0);
intData.add(0);
intData.add(0);
Map<String, Object> map = new HashMap<>();
map.put("instruct", intData);
mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map));
log.info("发送人员信息点阵数据到设备消息=>topic:{},payload:{}",
MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(),
JsonUtils.toJsonString(map));
} catch (Exception e) {
log.error("处理人员信息命令时出错", e);
}
}
}

View File

@ -0,0 +1,46 @@
package com.fuyuanshen.web.controller.device;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.web.service.WEBDeviceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: WY
* @Date: 2025/5/16
**/
@Slf4j
@Tag(name = "web:设备管理", description = "web:设备管理")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/device")
public class WEBDeviceController extends BaseController {
private final WEBDeviceService deviceService;
/**
* @param id
* @return
* @ModelAttribute 主要用于将请求参数绑定到 Java 对象上,它会从 HTTP 请求的查询参数Query Parameters
* 或表单数据Form Data中提取值并自动填充到指定的对象属性中。
*/
// @Log("解绑设备")
@Operation(summary = "WEB端解绑设备")
@DeleteMapping(value = "/unbind")
public R<Void> unbindDevice(Long id, Long userId) {
return toAjax(deviceService.webUnBindDevice(id, userId));
}
}

View File

@ -0,0 +1,32 @@
package com.fuyuanshen.web.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.dto.AppDeviceBo;
import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
import com.fuyuanshen.equipment.domain.vo.AppDeviceVo;
import com.fuyuanshen.equipment.domain.vo.CustomerVo;
import java.io.IOException;
import java.util.List;
/**
* @Description:
* @Author: WY
* @Date: 2025/5/16
**/
public interface WEBDeviceService extends IService<Device> {
/**
* WEB端解绑设备
*
* @param id
* @return
*/
int webUnBindDevice(Long id, Long userId);
}

View File

@ -0,0 +1,51 @@
package com.fuyuanshen.web.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuyuanshen.app.service.AppDeviceBizService;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceAssignments;
import com.fuyuanshen.equipment.mapper.DeviceAssignmentsMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.web.service.WEBDeviceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @Description:
* @Author: WY
* @Date: 2025/5/16
**/
@Slf4j
@Service
@RequiredArgsConstructor
public class WEBDeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements WEBDeviceService {
private final AppDeviceBizService appDeviceService;
private final DeviceAssignmentsMapper deviceAssignmentsMapper;
/**
* WEB端解绑设备
*
* @param id
* @return
*/
@Override
public int webUnBindDevice(Long id, Long userId) {
// 设备端解绑 0:设备端解绑 1:web端解绑
int type = 1;
if (userId == null) {
DeviceAssignments deviceAssignments = deviceAssignmentsMapper.selectById(id);
if (deviceAssignments == null) {
throw new RuntimeException("请先将设备入库!!!");
}
id = deviceAssignments.getDeviceId();
type = 0;
}
return appDeviceService.unBindDevice(id, userId, type);
}
}