This commit is contained in:
2026-07-29 13:08:49 +08:00
parent 29ecbf321f
commit 6be6d5d333
59 changed files with 1396 additions and 304 deletions

View File

@@ -0,0 +1,20 @@
package com.maibu.constant;
public class CommandConstant {
public static byte remoteControl = (byte) 0x00;
public static byte heartbeat = (byte) 0xff;
public static byte path = (byte) 0x01;
public static byte status = (byte) 0x02;
public static byte connect = (byte) 0x03;
public static byte interaction = (byte) 0x12; //主要用于推送状态消息等
public static byte readConfig = (byte) 0x05; //读取配置的参数
public static byte writeConfig = (byte) 0x06; //写配置参数
}

View File

@@ -0,0 +1,25 @@
package com.maibu.constant;
import com.maibu.core.business.DeviceRunParam;
import com.maibu.core.enums.ConnectionCloseReason;
import io.netty.util.AttributeKey;
import lombok.Data;
import java.util.concurrent.CompletableFuture;
@Data
public class Constant {
public static final AttributeKey<String> ATT_DEVICE_ID = AttributeKey.valueOf("deviceId");
public static final AttributeKey<CompletableFuture<DeviceRunParam>> READ_CONFIG_KEY =
AttributeKey.valueOf("READ_CONFIG");
public static final AttributeKey<ConnectionCloseReason> ATT_CLOSE_REASON = AttributeKey.valueOf("closeReason");
public static final AttributeKey<String> ATT_ORG_ID = AttributeKey.valueOf("orgId");
public static final AttributeKey<String> ATT_SITE_ID = AttributeKey.valueOf("siteId");
public static final String WheelGainCoefficients = "WheelGainCoefficients";
}

View File

@@ -20,21 +20,23 @@ public class DevicePlanTask extends OrgBaseDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long planId; //归属于某个计划任务
private Long planId; // 归属于某个计划任务
private String deviceId;
private DeviceTaskStaus taskStaus; //任务状态
private DeviceTaskStaus taskStaus; // 任务状态
@TableField(exist = false)
private String taskStausTranslate; //任务状态
private String taskStausTranslate; // 任务状态
private Long routeId; // 线路id
private LocalDateTime finishTime;
private LocalDateTime startTime; //开始时间 时分秒
private LocalDateTime startTime; // 开始时间 时分秒
private String taskType; //任务类型分类
private String taskType; // 任务类型分类
private LocalDateTime canExecuteTime; // 这个时间之前都不能执行
}

View File

@@ -1,13 +1,19 @@
package com.maibu.core.business;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.maibu.constant.CommandConstant;
import com.maibu.core.domain.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName(value = "iot_device_run_param", autoResultMap = true)
@@ -22,6 +28,432 @@ public class DeviceRunParam extends BaseDO {
private Long orgId;
private Double runSpeed;
private Integer runSpeed; //不下发到设备自己用
@TableField(exist = false)
public byte header1;
@TableField(exist = false)
public byte header2;
@TableField(exist = false)
public byte cmd;
public byte chipUidSign;//芯片UID固化标志位
public String chipUid;//芯片UID
public byte[] remoteConfig;//遥控器通道配置
public int knifeMotorMode;//割刀电机模式
public int walkMotorMode;//行走电机模式
public int leftMotorReverse;//左轮电机方向极性
public int rightMotorReverse;//右轮电机方向极性
public int swapChannel;//左右轮通道互换标志位
public int use4G;//联网标志位
public short forwardSpeedLimit;//前进速度限制
public short turnSpeedLimit;//转向速度限制
public int knifePolarity;//割刀通道极性
public int fanPolarity;//风门通道极性
public int throttlePolarity;//油门通道极性
public int liftProtectTime;//底盘升降电机保护时间
public int dualRtk;//RTK配置
public int knifeChannel;//割刀通道配置
public int fanChannel;//风门通道配置
public int throttleChannel;//油门通道配置
public int remoteType;//遥控器类型
public boolean relayBoard;//是否搭载继电器板
public int liftChannel;//底盘升通道配置
public int chassisChannel;//底盘降通道配置
public int armChannel;//抱闸通道配置
public int fuelPumpChannel;//燃油励磁通道配置
public String wifiName;//wifi名称
public String wifiPassword;//wifi密码
public int batteryType;//电池类型
public int driveType;//行走驱动配置
public float gearRatio;//转速比
public float robotLength;//机器人长度
public float robotWidth;//机器人宽度
public float robotHeight;//机器人高度
public float knifeWidth;//割刀宽度
public float tyreSize;//轮胎尺寸
public float leftForwardGain;//左轮前进增益
public float leftBackwardGain;//左轮后退增益
public float rightForwardGain;//右轮前进增益
public float rightBackwardGain;//右轮后退增益
public String firmwareVersion;//固件版本
@TableField(exist = false)
public int crc16;
@TableField(exist = false)
public byte tail1;
@TableField(exist = false)
public byte tail2;
@TableField(exist = false)
private byte[] originalData;
public static byte[] toReadBytes() {
ByteBuffer buffer = ByteBuffer.allocate(7); // 修改为27字节
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put((byte) 0xAB);
buffer.put((byte) 0xAA);
buffer.put(CommandConstant.readConfig);
buffer.putShort((short) 0);
buffer.put((byte) 0xAA);
buffer.put((byte) 0xAB);
return buffer.array();
}
// public static byte[] toWriteBytes(DeviceRunParam p) {
//
// ByteBuffer buffer = ByteBuffer.allocate(178);
// buffer.order(ByteOrder.LITTLE_ENDIAN);
//
// // 包头
// buffer.put((byte) 0xAB);
// buffer.put((byte) 0xAA);
//
// // 指令
// buffer.put(CommandConstant.writeConfig);
//
// // UID固化标志
// buffer.put(p.getChipUidSign());
//
// // UID
// putFixedString(buffer, p.getChipUid(), 45);
//
// // Byte68
// byte remote = 0;
// remote |= (p.getKnifeMotorMode() & 0x03);
// remote |= (p.getWalkMotorMode() & 0x03) << 2;
// remote |= (p.getLeftMotorReverse() & 0x01) << 4;
// remote |= (p.getRightMotorReverse() & 0x01) << 5;
// remote |= (p.getSwapChannel() & 0x01) << 6;
// remote |= (p.getUse4G() & 0x01) << 7;
// buffer.put(remote);
//
// // 69~72
// buffer.putShort((short) p.getForwardSpeedLimit());
// buffer.putShort((short) p.getTurnSpeedLimit());
//
// //73
// byte b73 = 0;
// b73 |= (p.getKnifePolarity() & 1);
// b73 |= (p.getFanPolarity() & 1) << 1;
// b73 |= (p.getThrottlePolarity() & 1) << 2;
// b73 |= (p.getLiftProtectTime() & 0x0F) << 3;
// b73 |= (p.getDualRtk() & 1) << 7;
// buffer.put(b73);
//
// //74
// byte b74 = 0;
// b74 |= (p.getKnifeChannel() & 0x0F);
// b74 |= (p.getFanChannel() & 0x0F) << 4;
// buffer.put(b74);
//
// //75
// byte b75 = 0;
// b75 |= (p.getThrottleChannel() & 0x0F);
// b75 |= (p.getRemoteType() & 0x07) << 4;
// if (p.isRelayBoard()) {
// b75 |= 1 << 7;
// }
// buffer.put(b75);
//
// //76
// byte b76 = 0;
// b76 |= (p.getLiftChannel() & 0x0F);
// b76 |= (p.getChassisChannel() & 0x0F) << 4;
// buffer.put(b76);
//
// //77
// byte b77 = 0;
// b77 |= (p.getArmChannel() & 0x0F);
// b77 |= (p.getFuelPumpChannel() & 0x0F) << 4;
// buffer.put(b77);
//
// //wifi
// putFixedString(buffer, p.getWifiName(), 20);
// putFixedString(buffer, p.getWifiPassword(), 20);
//
// //118
// byte b118 = 0;
// b118 |= (p.getBatteryType() & 0x03);
// b118 |= (p.getDriveType() & 0x03) << 2;
// buffer.put(b118);
//
// //119~158 float
// buffer.putFloat(p.getGearRatio());
// buffer.putFloat(p.getRobotLength());
// buffer.putFloat(p.getRobotWidth());
// buffer.putFloat(p.getRobotHeight());
// buffer.putFloat(p.getKnifeWidth());
// buffer.putFloat(p.getTyreSize());
// buffer.putFloat(p.getLeftForwardGain());
// buffer.putFloat(p.getLeftBackwardGain());
// buffer.putFloat(p.getRightForwardGain());
// buffer.putFloat(p.getRightBackwardGain());
//
// //159~173
// putFixedString(buffer, p.getFirmwareVersion(), 15);
//
// //CRC
//
// / / byte[] bytes = buffer.array();
// / / int crc = CRC16Util.crc16(bytes, 0, 174);
// buffer.putShort((short) 0);
//
// //Tail
// buffer.put((byte) 0xAA);
// buffer.put((byte) 0xAB);
//
// return buffer.array();
// }
public byte[] toWriteBytes() {
// 如果没有原始数据,使用静态方法重新构建
if (originalData == null || originalData.length < 178) {
return null;
}
// 克隆原始数据,避免修改原始数据
byte[] result = originalData.clone();
ByteBuffer buffer = ByteBuffer.wrap(result);
buffer.order(ByteOrder.LITTLE_ENDIAN);
// 修改指令为写配置(字节2)
buffer.put(2, CommandConstant.writeConfig);
// 修改字节68:遥控器配置
byte remote = 0;
remote |= (getKnifeMotorMode() & 0x03);
remote |= (getWalkMotorMode() & 0x03) << 2;
remote |= (getLeftMotorReverse() & 0x01) << 4;
remote |= (getRightMotorReverse() & 0x01) << 5;
remote |= (getSwapChannel() & 0x01) << 6;
remote |= (getUse4G() & 0x01) << 7;
buffer.put(68, remote);
// 修改字节69-72:速度限制
buffer.putShort(69, getForwardSpeedLimit());
buffer.putShort(71, getTurnSpeedLimit());
// 修改字节73:通道极性
byte b73 = 0;
b73 |= (getKnifePolarity() & 1);
b73 |= (getFanPolarity() & 1) << 1;
b73 |= (getThrottlePolarity() & 1) << 2;
b73 |= (getLiftProtectTime() & 0x0F) << 3;
b73 |= (getDualRtk() & 1) << 7;
buffer.put(73, b73);
// 修改字节74-77:通道配置
byte b74 = 0;
b74 |= (getKnifeChannel() & 0x0F);
b74 |= (getFanChannel() & 0x0F) << 4;
buffer.put(74, b74);
byte b75 = 0;
b75 |= (getThrottleChannel() & 0x0F);
b75 |= (getRemoteType() & 0x07) << 4;
if (isRelayBoard()) b75 |= 1 << 7;
buffer.put(75, b75);
byte b76 = 0;
b76 |= (getLiftChannel() & 0x0F);
b76 |= (getChassisChannel() & 0x0F) << 4;
buffer.put(76, b76);
byte b77 = 0;
b77 |= (getArmChannel() & 0x0F);
b77 |= (getFuelPumpChannel() & 0x0F) << 4;
buffer.put(77, b77);
// // 修改字节78-117:WiFi信息
// byte[] wifiNameBytes = new byte[20];
// if (getWifiName() != null) {
// byte[] src = getWifiName().getBytes(StandardCharsets.UTF_8);
// System.arraycopy(src, 0, wifiNameBytes, 0, Math.min(src.length, 20));
// }
// System.arraycopy(wifiNameBytes, 0, result, 78, 20);
//
// byte[] wifiPwdBytes = new byte[20];
// if (getWifiPassword() != null) {
// byte[] src = getWifiPassword().getBytes(StandardCharsets.UTF_8);
// System.arraycopy(src, 0, wifiPwdBytes, 0, Math.min(src.length, 20));
// }
// System.arraycopy(wifiPwdBytes, 0, result, 98, 20);
// // 修改字节118:电池和驱动配置
// byte b118 = 0;
// b118 |= (getBatteryType() & 0x03);
// b118 |= (getDriveType() & 0x03) << 2;
// buffer.put(118, b118);
// 修改字节119-158:float参数
buffer.putFloat(119, getGearRatio());
buffer.putFloat(123, getRobotLength());
buffer.putFloat(127, getRobotWidth());
buffer.putFloat(131, getRobotHeight());
buffer.putFloat(135, getKnifeWidth());
buffer.putFloat(139, getTyreSize());
buffer.putFloat(143, getLeftForwardGain());
buffer.putFloat(147, getLeftBackwardGain());
buffer.putFloat(151, getRightForwardGain());
buffer.putFloat(155, getRightBackwardGain());
// 修改字节159-173:固件版本
// byte[] fwBytes = new byte[15];
// if (getFirmwareVersion() != null) {
// byte[] src = getFirmwareVersion().getBytes(StandardCharsets.UTF_8);
// System.arraycopy(src, 0, fwBytes, 0, Math.min(src.length, 15));
// }
// System.arraycopy(fwBytes, 0, result, 159, 15);
// 保留原始的 CRC 和尾部,不强制修改
// 移除以下强制修改:
// buffer.putShort(174, (short) 0);
// buffer.put(176, (byte) 0xAA);
// buffer.put(177, (byte) 0xAB);
return result;
}
public static void putFixedString(ByteBuffer buffer, String value, int len) {
byte[] bytes = new byte[len];
if (value != null) {
byte[] src = value.getBytes(StandardCharsets.UTF_8);
System.arraycopy(src, 0, bytes, 0, Math.min(src.length, len));
}
buffer.put(bytes);
}
public static DeviceRunParam fromBytes(byte[] data) {
// 183
if (data == null || data.length < 178) {
throw new IllegalArgumentException("数据长度不足");
}
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.LITTLE_ENDIAN);
DeviceRunParam p = new DeviceRunParam();
p.setOriginalData(data);
p.setHeader1(buffer.get());
p.setHeader2(buffer.get());
p.setCmd(buffer.get());
p.setChipUidSign(buffer.get());
p.setChipUid(readString(buffer, 45));
// 跳过字节49-67(共19字节,预留不用)
buffer.position(buffer.position() + 19);
byte remote = buffer.get();
// p.setRemoteConfig(remote);
p.setKnifeMotorMode(remote & 0x03);
p.setWalkMotorMode((remote >> 2) & 0x03);
p.setLeftMotorReverse((remote >> 4) & 0x01);
p.setRightMotorReverse((remote >> 5) & 0x01);
p.setSwapChannel((remote >> 6) & 0x01);
p.setUse4G((remote >> 7) & 0x01);
p.setForwardSpeedLimit(buffer.getShort());
p.setTurnSpeedLimit(buffer.getShort());
byte b73 = buffer.get();
p.setKnifePolarity(b73 & 1);
p.setFanPolarity((b73 >> 1) & 1);
p.setThrottlePolarity((b73 >> 2) & 1);
p.setLiftProtectTime((b73 >> 3) & 0x0F);
p.setDualRtk((b73 >> 7) & 1);
byte b74 = buffer.get();
p.setKnifeChannel(b74 & 0x0F);
p.setFanChannel((b74 >> 4) & 0x0F);
byte b75 = buffer.get();
p.setThrottleChannel(b75 & 0x0F);
p.setRemoteType((b75 >> 4) & 0x07);
p.setRelayBoard(((b75 >> 7) & 1) == 1);
byte b76 = buffer.get();
p.setLiftChannel(b76 & 0x0F);
p.setChassisChannel((b76 >> 4) & 0x0F);
byte b77 = buffer.get();
p.setArmChannel(b77 & 0x0F);
p.setFuelPumpChannel((b77 >> 4) & 0x0F);
p.setWifiName(readString(buffer, 20));
p.setWifiPassword(readString(buffer, 20));
byte b118 = buffer.get();
p.setBatteryType(b118 & 0x03);
p.setDriveType((b118 >> 2) & 0x03);
// float
p.setGearRatio(buffer.getFloat());
p.setRobotLength(buffer.getFloat());
p.setRobotWidth(buffer.getFloat());
p.setRobotHeight(buffer.getFloat());
p.setKnifeWidth(buffer.getFloat());
p.setTyreSize(buffer.getFloat());
p.setLeftForwardGain(buffer.getFloat());
p.setLeftBackwardGain(buffer.getFloat());
p.setRightForwardGain(buffer.getFloat());
p.setRightBackwardGain(buffer.getFloat());
p.setFirmwareVersion(readString(buffer, 15));
p.setCrc16(Short.toUnsignedInt(buffer.getShort()));
p.setTail1(buffer.get());
p.setTail2(buffer.get());
return p;
}
public static String readString(ByteBuffer buffer, int len) {
byte[] bytes = new byte[len];
buffer.get(bytes);
int end = 0;
while (end < bytes.length && bytes[end] != 0) {
end++;
}
return new String(bytes, 0, end, StandardCharsets.UTF_8);
}
}

View File

@@ -2,10 +2,14 @@ package com.maibu.core.business;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.maibu.core.domain.OrgNoUserDO;
import com.maibu.core.enums.ErrorLevel;
import com.maibu.core.enums.ErrorSource;
@@ -15,10 +19,11 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("iot_work_order")
@TableName(value = "iot_work_order", autoResultMap = true)
@ApiModel("工单记录")
public class IOTWorkOrder extends OrgNoUserDO {
@@ -64,21 +69,31 @@ public class IOTWorkOrder extends OrgNoUserDO {
private String collaboratorNames;
@ApiModelProperty("计划开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime planStartTime;
@ApiModelProperty("计划结束时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime planEndTime;
@ApiModelProperty("截止时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime deadlineTime;
@ApiModelProperty("实际开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime actualStartTime;
@ApiModelProperty("实际完成时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime actualEndTime;
@ApiModelProperty("工单状态:1-待处理 2-已派发 3-执行中 4-已完成 5-已挂起")
@ApiModelProperty("工单状态:1-待处理 2-执行中 3-已完成 4-已挂起")
private Integer orderStatus;
@ApiModelProperty("关联告警 ID")
@@ -105,7 +120,23 @@ public class IOTWorkOrder extends OrgNoUserDO {
@ApiModelProperty("处理备注")
private String handleRemark;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> imgUrl; //创建时候的照片
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> videoUrl;//创建时候的视频
private Long modelId;
private Long taskId;
private String taskId;
private boolean autoCreate = false; //自动创建
private String failureCause; //故障原因
private String handleMeasures; //处理措施
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> images; //现场图片 处理后的照片
}

View File

@@ -3,6 +3,8 @@ package com.maibu.core.business.alarm_center;
import com.baomidou.mybatisplus.annotation.*;
import com.maibu.annotation.Excel;
import com.maibu.core.domain.TenantBaseDO;
import com.maibu.core.enums.AlarmHandleResult;
import com.maibu.core.enums.AlarmType;
import com.maibu.core.enums.ErrorLevel;
import com.maibu.core.enums.ErrorSource;
@@ -34,7 +36,7 @@ public class Alarm extends TenantBaseDO {
@ApiModelProperty("告警类型:1-设备故障 2-数据异常 3-通信中断 4-性能告警 5-安全告警 6-环境告警")
@Excel(name = "告警类型", readConverterExp = "1=设备故障,2=数据异常,3=通信中断,4=性能告警,5=安全告警,6=环境告警")
private Integer alarmType;
private AlarmType alarmType;
@ApiModelProperty("告警标题")
@Excel(name = "告警标题")
@@ -44,6 +46,8 @@ public class Alarm extends TenantBaseDO {
@Excel(name = "告警内容")
private String alarmContent;
private String timeValues; //告警时上报的值
@ApiModelProperty("设备ID")
@Excel(name = "设备ID", cellType = Excel.ColumnType.NUMERIC)
private String deviceId;
@@ -68,8 +72,8 @@ public class Alarm extends TenantBaseDO {
@Excel(name = "持续时长(秒)", cellType = Excel.ColumnType.NUMERIC)
private Integer duration;
@ApiModelProperty("处理状态:1-待处理 2-处理中 3-已关闭 4-已忽略")
@Excel(name = "处理状态", readConverterExp = "1=待处理,2=处理中,3=已关闭,4=已忽略")
@ApiModelProperty("处理状态:1-待处理 2已关闭")
@Excel(name = "处理状态", readConverterExp = "1=待处理,2=已关闭")
private Integer handleStatus;
@ApiModelProperty("处理人ID")
@@ -111,4 +115,6 @@ public class Alarm extends TenantBaseDO {
private Long siteId;
private Long orgId;
private AlarmHandleResult handleResult;
}

View File

@@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.maibu.core.enums.ErrorLevel;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@Data
public class AlarmMessage {
@@ -21,6 +22,7 @@ public class AlarmMessage {
/** 告警时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime time;
/** 设备名称 */
@@ -45,4 +47,6 @@ public class AlarmMessage {
private Long orgId;
private String timeValues;
}

View File

@@ -1,9 +1,12 @@
package com.maibu.core.business.device;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.Queue;
import com.maibu.utils.json.JsonUtils;
import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -74,7 +77,7 @@ public class NettyDevice extends Connector {
this.deviceType = deviceType;
}
// todo 重发
// todo 如何确定命令已收到?
public void sendNextPoint() {
if (task.getTaskStaus().equals(DeviceTaskStaus.PAUSE))
return;
@@ -139,9 +142,9 @@ public class NettyDevice extends Connector {
return true;
}
public void sendPathCommand(byte coommadnType, short pointCounts, double lat, double lng, short speed) {
public void sendPathCommand(byte commandType, short pointCounts, double lat, double lng, short speed) {
RoutePlanSendEntity routePlanSendEntity = new RoutePlanSendEntity();
routePlanSendEntity.setCommandType(coommadnType);
routePlanSendEntity.setCommandType(commandType);
routePlanSendEntity.setPointCounts(pointCounts);
routePlanSendEntity.setTargetLatitude(lat);
routePlanSendEntity.setTargetLongitude(lng);
@@ -153,6 +156,57 @@ public class NettyDevice extends Connector {
}
}
//配置左右轮增益系数
public void sendWheelGainCoefficientsCommand(double leftForwardGain, double leftBackwardGain, double rightForwardGain, double rightBackwardGain) {
ByteBuffer buffer = ByteBuffer.allocate(39);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put((byte) 0xAB);
buffer.put((byte) 0xAA);
buffer.put((byte) 0x13);
long leftForward = Double.doubleToLongBits(leftForwardGain);
buffer.putLong(leftForward);
long leftBackward = Double.doubleToLongBits(leftBackwardGain);
buffer.putLong(leftBackward);
long rightForward = Double.doubleToLongBits(rightForwardGain);
buffer.putLong(rightForward);
long rightBackward = Double.doubleToLongBits(rightBackwardGain);
buffer.putLong(rightBackward);
buffer.putShort((short) 0);
buffer.put((byte) 0xAA);
buffer.put((byte) 0xAB);
log.debug("下发设置左右轮增益系数报文:{}", toHexString(buffer.array()));
if (this.getChannel() != null && this.getChannel().isActive()) {
this.getChannel().writeAndFlush(Unpooled.wrappedBuffer(buffer.array()));
}
}
public void readConfigCommand() {
byte[] bytes = DeviceRunParam.toReadBytes();
log.debug("下发读取配置的报文:{}", toHexString(bytes));
if (this.getChannel() != null && this.getChannel().isActive()) {
this.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
}
}
public void writeConfigCommand(DeviceRunParam deviceRunParam) {
log.debug("下发修改配置的属性:{}", JsonUtils.toJsonString(deviceRunParam));
byte[] bytes = deviceRunParam.toWriteBytes();
log.debug("下发修改配置的报文:{}", toHexString(bytes));
if (this.getChannel() != null && this.getChannel().isActive()) {
this.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
}
}
// todo 优化
public synchronized boolean pauseTask() {
if (executingTask) {

View File

@@ -1,5 +1,6 @@
package com.maibu.core.business.dto;
import com.maibu.core.enums.AlarmHandleResult;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -11,7 +12,7 @@ public class AlarmHandleDTO {
@ApiModelProperty("告警ID")
private Long alarmId;
@ApiModelProperty("处理状态:2-处理中 3-已关闭 4-已忽略")
@ApiModelProperty("处理状态:1-待处理 2已关闭")
private Integer handleStatus;
@ApiModelProperty("处理备注")
@@ -19,4 +20,7 @@ public class AlarmHandleDTO {
@ApiModelProperty("根因分析")
private String rootCause;
@ApiModelProperty("处理结果")
private AlarmHandleResult handleResult;
}

View File

@@ -39,4 +39,6 @@ public class AlarmQueryDTO extends BaseEntity {
@ApiModelProperty("场站ID")
private Long siteId;
private Integer handleStatus;
}

View File

@@ -6,8 +6,10 @@ import lombok.Data;
import java.time.LocalDateTime;
import javax.lang.model.type.ErrorType;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.maibu.core.enums.ErrorLevel;
import com.maibu.core.enums.OrderType;
@Data
@@ -27,7 +29,7 @@ public class WorkOrderQueryDTO {
private OrderType orderType;
@ApiModelProperty("优先级")
private ErrorType priorityLevel;
private ErrorLevel priorityLevel;
@ApiModelProperty("负责人 ID")
private Long assigneeId;
@@ -44,10 +46,12 @@ public class WorkOrderQueryDTO {
@ApiModelProperty("工单状态")
private Integer orderStatus;
@ApiModelProperty("开始时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
@ApiModelProperty("结束时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endTime;
@ApiModelProperty("搜索关键词")

View File

@@ -15,12 +15,14 @@ public class MowerCommandDispather {
@Resource
private ApplicationContext applicationContext;
public void dispathDeviceTask(DeviceTaskCommandDTO dto) {
public Long dispathDeviceTask(DeviceTaskCommandDTO dto) {
Map<String, MowerCommandInter> beanMap = applicationContext.getBeansOfType(MowerCommandInter.class);
// 遍历
beanMap.values().forEach(x -> {
x.createDeviceTask(dto);
});
Long id = null;
for (MowerCommandInter value : beanMap.values()) {
id = value.createDeviceTask(dto);
}
return id;
}
}

View File

@@ -4,6 +4,6 @@ import com.maibu.core.business.dto.DeviceTaskCommandDTO;
public interface MowerCommandInter {
void createDeviceTask(DeviceTaskCommandDTO dto);
Long createDeviceTask(DeviceTaskCommandDTO dto);
}

View File

@@ -15,12 +15,14 @@ public class UAVCommandDispather {
@Resource
private ApplicationContext applicationContext;
public void dispathFightTask(FlightTaskCreateDTO dto) {
public String dispathFightTask(FlightTaskCreateDTO dto) {
Map<String, UAVCommandInter> beanMap = applicationContext.getBeansOfType(UAVCommandInter.class);
// 遍历
beanMap.values().forEach(x -> {
x.createFlightTask(dto);
});
String taskId = null;
for (UAVCommandInter value : beanMap.values()) {
taskId = value.createFlightTask(dto);
}
return taskId;
}
}

View File

@@ -4,6 +4,6 @@ import com.maibu.core.business.dto.uav.FlightTaskCreateDTO;
public interface UAVCommandInter {
void createFlightTask(FlightTaskCreateDTO dto);
String createFlightTask(FlightTaskCreateDTO dto);
}

View File

@@ -41,4 +41,6 @@ public class SysSite extends BaseDO {
private String dingTalkToken; // 钉钉推送token
private boolean dingTalkOrderPush = false;// 工单是否推送
}

View File

@@ -0,0 +1,31 @@
package com.maibu.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AlarmHandleResult {
Handled("Handled", "已处理"),
Restored("Restored", "已恢复"),
Ignored("Ignored", "已忽略"),
UnableToHandle("UnableToHandle", "无法处理"),
ManufacturerHandling("ManufacturerHandling", "厂家处理中"),
FalseAlarm("FalseAlarm", "误报");
/**
* 错误来源值(存储在数据库中)
*/
private final String value;
/**
* 错误来源名称(用于前端显示)
*/
private final String name;
}

View File

@@ -0,0 +1,27 @@
package com.maibu.core.enums;
import com.maibu.annotation.Excel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AlarmType {
UAV_FAULT("UAV_FAULT", "无人机故障"),
MOWER_FAULT("MOWER_FAULT", "割草机故障"),
OTHER_DEVICE_FAULT("OTHER_DEVICE_FAULT", "其他设备故障"),
SERVER_FAILURE("SERVER_FAILURE", "服务器故障"),
SYSTEM_ERROR("SYSTEM_ERROR", "系统错误");
/** 错误来源值(存储在数据库中) */
private final String value;
/** 错误来源名称(用于前端显示) */
private final String name;
}

View File

@@ -0,0 +1,235 @@
package com.maibu.memory;
import cn.hutool.core.util.ObjectUtil;
import com.maibu.core.business.device.Connector;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.enums.ConnectorStatus;
import com.maibu.core.enums.ConnectorType;
import io.netty.channel.Channel;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 设备会话管理器
* 负责管理设备信息、连接通道、双向绑定关系、状态管理和消息转发
*/
@Component
@Data
public class DeviceSessionManager {
private static final Logger logger = LoggerFactory.getLogger(DeviceSessionManager.class);
// 设备ID -> 设备对象(业务实体)
private final ConcurrentHashMap<String, NettyDevice> deviceMap = new ConcurrentHashMap<>();
// 设备ID -> Channel(通信通道)
private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
// Channel -> 设备ID(反查)
private final ConcurrentHashMap<Channel, String> channelToDeviceIdMap = new ConcurrentHashMap<>();
public void clearChannelToDeviceIdMap(String deviceId) {
if (StringUtils.isEmpty(deviceId) || CollectionUtils.isEmpty(channelToDeviceIdMap)) {
return;
}
// 获取 entrySet 迭代器,安全遍历+删除
Iterator<Map.Entry<Channel, String>> iterator = channelToDeviceIdMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Channel, String> entry = iterator.next();
Channel channel = entry.getKey();
String value = entry.getValue();
// 匹配目标 deviceId
if (deviceId.equals(value)) {
// 关闭 Channel:捕获 IO 异常,避免单个 Channel 关闭失败影响整体遍历
if (channel != null && channel.isActive()) {
try {
channel.close().syncUninterruptibly(); // 优雅关闭,等待关闭完成
} catch (Exception e) {
// 记录日志,不中断后续操作
logger.error("关闭 Channel 失败", e);
}
}
// 关键:使用迭代器的 remove() 方法,安全移除键值对,不会触发并发修改异常
iterator.remove();
}
}
}
/**
* 注册设备连接(设备上线)
*
* @param deviceId 设备ID
* @param channel 通信通道
* @param deviceType 设备类型(MASTER/SLAVE)
*/
public void registerDevice(String deviceId, Channel channel, ConnectorType deviceType, ConnectorStatus status, String slaveId) {
NettyDevice nettyDevice = deviceMap.computeIfAbsent(deviceId, id -> new NettyDevice(id, deviceType));
nettyDevice.setChannel(channel);
nettyDevice.setConnectorStatus(status); // 默认上线即ACTIVE
nettyDevice.setCurrentSlaveId(slaveId);
nettyDevice.setLatestLoginTime(System.currentTimeMillis());
deviceMap.put(deviceId, nettyDevice);
channelMap.put(deviceId, channel);
channelToDeviceIdMap.put(channel, deviceId);
System.out.println("Registered device: " + deviceId + " type: " + deviceType);
}
/**
* 上位机绑定下位机,建立双向绑定关系
*
* @param masterId 上位机设备ID
* @param slaveId 下位机设备ID
*/
public void bindConnector(String masterId, String slaveId) {
NettyDevice master = deviceMap.get(masterId);
if (master == null) return;
NettyDevice slave = deviceMap.computeIfAbsent(slaveId, id -> new NettyDevice(id, ConnectorType.SLAVE));
master.addConnectedDevice(slave);
System.out.println("Bind connectors for master " + masterId + " and slave " + slaveId);
}
/**
* 根据设备ID获取设备对象
*/
public NettyDevice getDevice(String deviceId) {
return deviceMap.get(deviceId);
}
// 模糊匹配
public List<NettyDevice> getDevicesLike(String query) {
return deviceMap.values().stream()
.filter(device -> device.getConnectorId().contains(query))
.collect(Collectors.toList());
}
public List<NettyDevice> getSlaveControl(String slaveId) {
// 获取所有连接的主机 且是active状态的
return deviceMap.values().stream()
.filter(device -> ObjectUtil.equals(device.getCurrentSlaveId(), slaveId)
&& ConnectorStatus.ACTIVE.equals(device.getConnectorStatus()))
.collect(Collectors.toList());
}
public List<NettyDevice> getAllSlaveControl(String slaveId) {
// 获取所有连接的主机 不区分状态
return deviceMap.values().stream()
.filter(device -> ObjectUtil.equals(device.getCurrentSlaveId(), slaveId))
.collect(Collectors.toList());
}
public NettyDevice getCurrentControl(String slaveId) {
// 获取所有连接的主机 不区分状态
return deviceMap.values().stream()
.filter(device -> ObjectUtil.equals(device.getCurrentSlaveId(), slaveId)
&& ConnectorStatus.ACTIVE.equals(device.getConnectorStatus()))
.findFirst().orElse(null);
}
/// 模糊匹配
public NettyDevice getUserControl(String userName) {
return deviceMap.values().stream()
.filter(device -> device.getConnectorId().contains(userName)
&& ConnectorStatus.ACTIVE.equals(device.getConnectorStatus()))
.findFirst().orElse(null);
}
/**
* 根据设备ID获取Channel
*/
public Channel getChannel(String deviceId) {
return channelMap.get(deviceId);
}
/**
* 根据Channel反查设备ID
*/
public String getDeviceIdByChannel(Channel channel) {
return channelToDeviceIdMap.get(channel);
}
/**
* 根据从机查控制的主机
*/
public NettyDevice getMasterBySlaveId(String slaveId) {
return deviceMap.values().stream().filter(x -> Objects.equals(x.getCurrentSlaveId(), slaveId)
&& ConnectorStatus.ACTIVE.equals(x.getConnectorStatus())).findFirst().orElse(null);
}
/**
* 根据从机查所有主机
*/
public List<NettyDevice> getMastersBySlaveId(String slaveId) {
return deviceMap.values().stream().filter(x -> Objects.equals(x.getCurrentSlaveId(), slaveId)).collect(Collectors.toList());
}
/**
* 移除设备连接(设备下线)
*/
public void removeDevice(String deviceId) {
NettyDevice nettyDevice = deviceMap.remove(deviceId);
// Channel channel = channelMap.remove(deviceId);
clearChannelToDeviceIdMap(deviceId);
// if (channel != null) {
// channelToDeviceIdMap.remove(channel);
// channel.close();
// }
if (nettyDevice != null) {
// 仅当设备是上位机时,才解绑绑定关系
if (nettyDevice.getDeviceType() == ConnectorType.MASTER) {
for (Connector connected : new HashSet<>(nettyDevice.getConnectedConnectors())) {
nettyDevice.removeConnectedDevice(connected);
}
} else {
// 如果是下位机,则保留绑定关系,只清理channel和状态
nettyDevice.setChannel(null);
nettyDevice.setConnectorStatus(ConnectorStatus.DISABLED); // 或者设置为离线状态
// 注意:此时device仍留在deviceMap中
nettyDevice.setLatestStatus(null);
deviceMap.put(deviceId, nettyDevice);
}
System.out.println("Removed device: " + deviceId);
}
}
/**
* 判断设备是否在线
*/
public boolean isOnline(String deviceId) {
Channel ch = channelMap.get(deviceId);
return ch != null && ch.isActive();
}
/**
* 判断设备是否允许发送消息(ACTIVE状态才允许)
*/
public boolean canSend(String deviceId) {
NettyDevice nettyDevice = deviceMap.get(deviceId);
return nettyDevice != null && nettyDevice.canSend();
}
/**
* 判断设备是否允许接收消息(非DISABLED状态才允许)
*/
public boolean canReceive(String deviceId) {
NettyDevice nettyDevice = deviceMap.get(deviceId);
return nettyDevice != null && nettyDevice.canReceive();
}
}

View File

@@ -35,6 +35,8 @@ public class GlobalMemory {
public static MqttClientUtil mqttClientUtil;
public static SiteMemory getSiteMemory(Long orgId, Long siteId) {
if (orgId != null && siteId != null) {
Map<Long, SiteMemory> siteMemories = orgAttrs.get(orgId);

View File

@@ -35,7 +35,7 @@ public class SiteMemory {
public SysSite sysSite;
public static final ConcurrentHashMap<Long, OrderModel> ordrModelMap = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<Long, OrderModel> orderModelMap = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<String, AlarmOrderConfig> orderConfigMap = new ConcurrentHashMap<>();
@@ -87,7 +87,19 @@ public class SiteMemory {
if (CollectionUtils.isEmpty(list)) {
list = new ArrayList<>();
}
list.add(errorIdentificationStandard);
// 替换已存在的记录,兼容后续保存
int index = -1;
for (int i = 0; i < list.size(); i++) {
if (Objects.equals(list.get(i).getErrorCode(), errorIdentificationStandard.getErrorCode())){
index = i;
break;
}
}
if (index >= 0) {
list.set(index, errorIdentificationStandard);
} else {
list.add(errorIdentificationStandard);
}
standards.put(errorIdentificationStandard.getErrorSource(), list);
}
@@ -108,19 +120,19 @@ public class SiteMemory {
}
public void saveOrderModel(OrderModel orderModel) {
ordrModelMap.put(orderModel.getId(), orderModel);
orderModelMap.put(orderModel.getId(), orderModel);
}
public OrderModel getOrderModel(Long id) {
if (id != null) {
return ordrModelMap.get(id);
return orderModelMap.get(id);
}
return null;
}
public void removeOrderModel(Long id) {
if (id != null) {
ordrModelMap.remove(id);
orderModelMap.remove(id);
}
}

View File

@@ -1,15 +1,108 @@
package com.maibu.mqtt;
public class MqttTopic {
public static final String DEVICE_STATUS_TOPIC = "device/%s/realTimeMessage"; // 实时消息
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
public static final String DEVICE_STATUS_TOPIC = "device/%s/realTimeMessage";
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
public static final String DEVICE_TASK_STATUS_TOPIC = "task/%s/status";
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
public static final String DEVICE_TASK_ARRIVE_TOPIC = "task/%s/arrive";
/** @deprecated 通用设备主题,建议使用 MOWER_* 系列主题 */
public static final String DEVICE_ERROR_PUSH_TOPIC = "device/%s/error";
public static final String DEVICE_TASK_STATUS_TOPIC = "task/%s/status"; // 任务状态 完成、取消、暂停、继续
/** 产品标识:割草机 */
public static final String MOWER_PRODUCT = "mower";
public static final String DEVICE_TASK_ARRIVE_TOPIC = "task/%s/arrive"; // 任务到达
// ==================== 设备连接与状态 ====================
/** 设备上线/下线事件上报(设备->平台),对应 TCP 0x03 connect */
public static final String MOWER_STATUS_POST = "mower/%s/event/status/post";
/** 状态变更确认回复(平台->设备) */
// public static final String MOWER_STATUS_REPLY = "mower/%s/event/status/reply";
public static final String DEVICE_ERROR_PUSH_TOPIC = "device/%s/error"; // 设备错误推送
// ==================== 实时运行数据 ====================
/** 实时运行数据上报:电压/速度/温度/位置等(设备->平台),对应 TCP 0x02 status */
public static final String MOWER_REALTIME_POST = "mower/%s/property/realtime/post";
// ==================== 心跳保活 ====================
/** 设备心跳上报(设备->平台),对应 TCP 0xFF heartbeat */
public static final String MOWER_HEARTBEAT_POST = "mower/%s/event/heartbeat/post";
/** 心跳确认回复(平台->设备) */
public static final String MOWER_HEARTBEAT_REPLY = "mower/%s/event/heartbeat/reply";
}
// ==================== 参数配置 ====================
/** 下发读取配置请求(平台->设备),对应 TCP 0x05 readConfig */
public static final String MOWER_CONFIG_GET = "mower/%s/action/config/get";
/** 下发配置修改指令(平台->设备),对应 TCP 0x06 writeConfig */
public static final String MOWER_CONFIG_SET = "mower/%s/action/config/set";
/** 配置读取/修改响应(设备->平台) */
public static final String MOWER_CONFIG_REPLY = "mower/%s/action/config/reply";
// ==================== 远程控制 ====================
/** 远程控制指令下发:前进/后退/转向/割草等(平台->设备),对应 TCP 0x00 remoteControl */
public static final String MOWER_CONTROL_SET = "mower/%s/action/control/set";
// /** 控制指令响应(设备->平台) */
// public static final String MOWER_CONTROL_REPLY = "mower/%s/action/control/reply";
// ==================== 任务管理 ====================
/** 路径规划下发(平台->设备),对应 TCP 0x01 path */
public static final String MOWER_TASK_ROUTE_SET = "mower/%s/task/route/set";
public static final String MOWER_TASK_ROUTE_POST = "mower/%s/task/route/post";
/** 启动任务执行(平台->设备),对应 TCP 0x12 interaction */
public static final String MOWER_TASK_STATUS_SET = "mower/%s/task/status/set";
// /** 暂停任务(平台->设备) */
// public static final String MOWER_TASK_PAUSE_SET = "mower/%s/task/pause/set";
// /** 继续任务(平台->设备) */
// public static final String MOWER_TASK_RESUME_SET = "mower/%s/task/resume/set";
// /** 取消任务(平台->设备) */
// public static final String MOWER_TASK_CANCEL_SET = "mower/%s/task/cancel/set";
// /** 到达路径点上报(设备->平台) */
public static final String MOWER_TASK_POINT_POST = "mower/%s/task/point/post";
/** 任务状态变更上报:执行中/暂停/完成等(设备->平台) */
public static final String MOWER_TASK_STATUS_POST = "mower/%s/task/status/post";
/** 任务完成上报(设备->平台) */
public static final String MOWER_TASK_FINISH_POST = "mower/%s/task/finish/post";
// ==================== 错误告警 ====================
/** 设备错误告警上报:电机故障/传感器异常等(设备->平台) */
public static final String MOWER_ERROR_POST = "mower/%s/event/error/post";
// ==================== OTA 固件升级 ====================
// /** 下发 OTA 升级指令(平台->设备) */
// public static final String MOWER_OTA_UPGRADE_SET = "mower/%s/ota/upgrade/set";
// /** OTA 升级进度上报(设备->平台) */
// public static final String MOWER_OTA_PROGRESS_POST = "mower/%s/ota/progress/post";
// /** OTA 升级结果上报(设备->平台) */
// public static final String MOWER_OTA_RESULT_POST = "mower/%s/ota/result/post";
// ==================== 设备信息 ====================
// /** 查询设备信息(平台->设备) */
// public static final String MOWER_INFO_DEVICE_GET = "mower/%s/info/device/get";
// /** 设备信息响应(设备->平台) */
// public static final String MOWER_INFO_DEVICE_REPLY = "mower/%s/info/device/reply";
/** 设备版本信息上报(设备->平台) */
// public static final String MOWER_INFO_VERSION_POST = "mower/%s/info/version/post";
// ==================== 通配符订阅 ====================
/** 订阅所有设备的状态变更事件 */
public static final String MOWER_WILDCARD_STATUS = "mower/+/event/status/post";
/** 订阅所有设备的实时运行数据 */
public static final String MOWER_WILDCARD_REALTIME = "mower/+/property/realtime/post";
/** 订阅所有设备的错误告警 */
public static final String MOWER_WILDCARD_ERROR = "mower/+/event/error/post";
/** 订阅所有设备的任务状态变更 */
public static final String MOWER_WILDCARD_TASK_STATUS = "mower/+/task/status/post";
/**
* 格式化 Topic 模板,将 %s 替换为设备ID
*
* @param template Topic 模板,如 {@link #MOWER_REALTIME_POST}
* @param deviceId 设备ID
* @return 格式化后的完整 Topic
*/
public static String formatTopic(String template, String deviceId) {
return String.format(template, deviceId);
}
}

View File

@@ -17,6 +17,7 @@ import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* JSON 工具类
@@ -87,10 +88,11 @@ public class JsonUtils {
/**
* 将字符串解析成指定类型的对象
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use =
* JsonTypeInfo.Id.CLASS) 的场景下,
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
*
* @param text 字符串
* @param text 字符串
* @param clazz 类型
* @return 对象
*/
@@ -127,7 +129,8 @@ public class JsonUtils {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);