<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> </dependency>
(一)MQTT协议配置属性@Component @ConfigurationProperties(prefix = "spring.mqtt") public class MqttProperties { private String username = "source"; private String password = "link"; private String hostUrl = "tcp://XXXX.XXXX.XXXX.XXXX:端口号"; private String clientId = "consumerClient"; private String defaultTopic = "monitor"; /**监测上报主题*/ private String upTopic = "monitorUp"; /**下发的主题*/ private String downTopic = "displayDown";
(二)MQTT协议的配置类
@Configuration
@IntegrationComponentScan
@Slf4j
public class MqttConfig {
@Autowired
private MqttProperties mqttProperties;
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName(mqttProperties.getUsername());
factory.setPassword(mqttProperties.getPassword());
factory.setServerURIs(new String[]{mqttProperties.getHostUrl()});
log.info("mqtt服务器地址:{}", mqttProperties.getHostUrl());
return factory;
}
@Bean
public MessageProducer inbound() {
log.info("上报的主题:{}", Arrays.asList(mqttProperties.getUpTopic().split(",")));
MqttPahoMessageDrivenChannelAdapter adapter = new
MqttPahoMessageDrivenChannelAdapter(
mqttProperties.getClientId()+new Random().nextInt(),
mqttClientFactory(),
mqttProperties.getUpTopic().split(","));
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
/**
* 发送消息和消费消息Channel可以使用相同MqttPahoClientFactory
* @return
*/
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler outbound() {
// 在这里进行mqttOutboundChannel的相关设置
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("publishClient", mqttClientFactory());
//如果设置成true,发送消息时将不会阻塞。
messageHandler.setAsync(true);
messageHandler.setDefaultTopic(mqttProperties.getDownTopic());
log.info("下报的主题:{}", mqttProperties.getDownTopic());
return messageHandler;
}
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
(三)用于发送基于MQTT协议的消息服务类
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttSendMessageService {
/**
* 用于发送 基于主题的消息
* @Date 2019/10/14 16:08
* @param topic 主题
* @param payload 真实的消息负载
* @throws
* @return
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
(四)处理上报消息服务类
@Service
@Slf4j
public class MqttAcceptMessageServiceImpl implements MqttAcceptMessageService {
@Autowired
private MqttProperties mqttProperties;
@Autowired
private MqttDao mqttDao;
/**
* ServiceActivator注解表明当前方法用于处理MQTT消息,
* inputChannel参数指定了用于接收消息信息的channel
* 处理所有接收到的MQTT消息
*
* */
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handlerInput() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
log.info("接收的消息:{}" ,message);
if (!ObjectUtils.isEmpty(message)) {
String topic = message.getHeaders().get("mqtt_topic").toString();
final boolean isMonitorUp = mqttProperties.getUpTopic().equals(topic);
if (isMonitorUp) {
parseMonitorUpPayLoad(message.getPayload().toString());
}
}
}
};
}
/**
* 根据上报负荷信息解析监测对象信息 并且插入数据
* @Date 2019/10/17 14:43
* @param payload 真实的上报的负荷数据
* @throws
* @return
*/
private void parseMonitorUpPayLoad(String payload) {
try {
MqttFrame frame = FrameUtil.parseFrame(payload.getBytes());
insertMonitorObjects(frame.getImei(),frame.getMonitorObjects());
} catch (ParseFrameException e) {
log.error("解析MQTT协议出错", e);
throw new BussinessException(BizExceptionEnum.MQTT_PARSE_ERROR);
} catch (Exception e) {
log.error("插入上报数值出错", e);
throw new BussinessException(BizExceptionEnum.MQTT_ACCEPT_INSERT_ERROR);
}
}
/**
* 插入上报的监测对象的数据
* @Date 2019/10/21 8:41
* @param imei 设备唯一标识ID
* @param objectList 上报的监测对象列表
* @throws
* @return
*/
@Transactional(rollbackFor = BussinessException.class)
private void insertMonitorObjects(String imei, List<MonitorObject> objectList) {
if (!CollectionUtils.isEmpty(objectList)) {
for (MonitorObject monitorObject : objectList) {
insertMonitor(imei, monitorObject);
}
}
}
/**
* 根据设备唯一标识IMEi 插入上报的数据
* @Date 2019/10/17 16:23
* @param imei 设备唯一标识
* @param monitorObject 上报数据对象
* @throws
* @return
*/
private void insertMonitor(String imei, MonitorObject monitorObject) {
Byte id = monitorObject.getId();
String value = monitorObject.getValue();
ObjectType type = ObjectType.valueOf(id);
if (!ObjectType.isNone(type) && !StringUtils.isEmpty(imei)) {
String quotaName = type.getName();
Long quotaId = mqttDao.getQuotaId(quotaName);
MMonitorPlace place = mqttDao.getMonitorPlace(imei);
if (quotaId != null && place != null) {
insertDeviceData(quotaId, place.getId(), place.getAreaId(), value);
}
/**判断设备监测点是否是正常*/
if (place != null && !DeviceStatus.isNormal(place.getStatus())) {
place.setStatus(DeviceStatus.NORMAL.getCode());
place.setModifiedTime(new Date());
place.updateById();
}
/**判断发布屏是否正常 监测点的采集设备和发布屏显示设备IMEI是一致的*/
MScreen screen = mqttDao.getScreen(imei);
if (screen != null && !DeviceStatus.isNormal(screen.getStatus())) {
screen.setStatus(DeviceStatus.NORMAL.getCode());
screen.setModifiedTime(new Date());
screen.updateById();
}
}
}
/**
* 插入上报的数据
* @Date 2019/10/18 8:35
* @param quotaId 指标ID
* @param placeId 监测点ID
* @param areaId 景区ID
* @param value 上报数据
* @throws
* @return
*/
private static void insertDeviceData(Long quotaId, Long placeId, Long areaId, String value) {
MDeviceData data = new MDeviceData();
data.setQuotaId(quotaId);
data.setPlaceId(placeId);
data.setAreaId(areaId);
data.setData(new BigDecimal(value));
data.setCreatedTime(new Date());
data.setModifiedTime(new Date());
data.setIsDeleted(new Integer(0));
data.insert();
}
/**
* 检查上报的数据是否满足配置的指标范围之内
* @Date 2019/11/14 9:01
* @param quotaId 指标ID
* @param value 上报的数据
* @throws
* @return
*/
private void checkQuotaValueRange(Long quotaId, String value) {
}
(五)MQTT协议的帧处理工具类
@Slf4j
public class FrameUtil {
/**最小解析帧长度*/
private final static int MIN_PARSE_ARRAY_LENGTH = 24;
/**上报的监测对象占比字节数*/
private final static int MONITOR_OBJECT_BYTE_SIZE = 9;
/**
* @Description 获取帧的组成的字节数组
* 帧头 0x7a7a7a7a(4byte)
* 设备IMEI(15byte)
* 对象序列号(1byte)
* 对象值分为2类 数值类型和文本类型 上报只有数值类型,下发才有数值类型和文本类型
* 数值类型为 8个字节 一个监测对象上报占9个字节
* 对象序列号结束符(1byte, 0x7e,对象结束)
* 帧尾 0x61616161 (4byte)
* @Date 2019/10/16 10:55
* @param frame 帧对象
* @throws
* @return
*/
public static byte[] getFrameBytes(MqttFrame frame) {
if (frame == null) {
return new byte[0];
}
/**帧头*/
byte[] top = getTopBytes(frame.getTop());
/**IMEI 设备唯一标识*/
byte[] imei = getImeiBytes(frame.getImei());
/**监测对象列表 包括 数值类型 和 文本类型*/
byte[] monitorObjects = getMonitorObjectBytes(frame.getMonitorObjects());
/**对象结束符 默认是 单字节最大值 =126*/
byte[] objectEnd = getObjectEndBytes(frame.getObjectEnd());
/**帧尾*/
byte[] tail = getTailBytes(frame.getTail());
List<byte[]> frameByteList = new ArrayList<>();
frameByteList.add(top);
frameByteList.add(imei);
if (!ArrayUtils.isEmpty(monitorObjects)) {
frameByteList.add(monitorObjects);
}
frameByteList.add(objectEnd);
frameByteList.add(tail);
return ByteUtil.getBytes(frameByteList);
}
/**
* @Description 获取 监测对象列表 转成 字节数组
* @Date 2019/10/16 13:40
* @param objects 监测对象列表
* @throws
* @return
*/
private static byte[] getMonitorObjectBytes(List<MonitorObject> objects) {
if (CollectionUtils.isEmpty(objects)) {
return new byte[0];
}
List<byte[]> monitorBytes = new ArrayList<>();
for (MonitorObject object : objects) {
Byte id = object.getId();
String value = object.getValue();
/**判断监测对象类型是文本还是数值类型*/
final boolean isText = ObjectType.isText(id);
if (isText) {
/**文本类型并且需要排除空文本*/
final boolean isEmptyText = StringUtils.isEmpty(value);
if (!isEmptyText) {
monitorBytes.add(getTextMonitorObjectBytes(ByteUtil.getByteBytes(id), value));
}
} else { /**数值类型*/
monitorBytes.add(getValueMonitorObjectBytes(ByteUtil.getByteBytes(id), value));
}
}//end for
return ByteUtil.getBytes(monitorBytes);
}
/**
* @Description 将数值类型的监测对象 转化为 字节数组
* 策略 是 监测对象数组 + 数值字节数组(8个字节)=9个字节
* @Date 2019/10/16 15:21
* @param idbytes 监测对象ID 字节数组
* @param value 文本值
* @throws
* @return
*/
private static byte[] getValueMonitorObjectBytes(byte[] idbytes, String value) {
byte[] valueBytes = ByteUtil.getDoubleStringBytes(new Double(value));
List<byte[]> byteList = Arrays.asList(new byte[][]{idbytes, valueBytes});
return ByteUtil.getBytes(byteList);
}
/**
* @Description 将文本类型的监测对象 转化为 字节数组
* 策略 是 监测对象ID字节数组 + 编码之后的数组长度字节数组(1个字节) + 字符字节数组
* 字节数组长度 不能超过 64个字节
* @Date 2019/10/16 15:21
* @param idbytes 监测对象ID 字节数组
* @param textValue 文本值
* @throws
* @return
*/
private static byte[] getTextMonitorObjectBytes(byte[] idbytes, String textValue) {
byte[] textValueBytes = ByteUtil.getStringBytes(textValue);
/**文本类型长度 用一个字节表示*/
Integer textLen = textValueBytes.length;
byte[] textLenBytes = ByteUtil.getByteBytes(textLen.byteValue());
List<byte[]> byteList = Arrays.asList(new byte[][]{idbytes, textLenBytes, textValueBytes});
return ByteUtil.getBytes(byteList);
}
/**
* @Description 解析监测对象 字节数组
* 上传的都是 数值类型 每一个监测对象 占9个字节
* 第1个字节对象ID 后面8个对象值
* @Date 2019/10/17 9:44
* @param bytes 字节数组
* @throws
* @return
*/
private static List<MonitorObject> parseMonitors(byte[] bytes) {
if (ArrayUtils.isEmpty(bytes)) {
return new ArrayList<>();
}
/**保证字节数组长度是9的倍数*/
int total = bytes.length/9;
List<MonitorObject> monitorObjects = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
/**对象ID*/
byte id = ByteUtil.getByte(new byte[]{bytes[i*9]});
String value = new String(bytes, 9*i+1,8);
monitorObjects.add(new MonitorObject(id, value));
}//end for
return monitorObjects;
}
/**
* @Description 获取 设备唯一标识IMEI 字节数组
* 占比 15个字节
* @Date 2019/10/16 10:31
* @param imei 设备唯一标识IMEI
* @throws
* @return
*/
private static byte[] getImeiBytes(String imei) {
return ByteUtil.getStringBytes(imei);
}
/**
* @Description 解析 设备唯一标识IMEI 字节数组
* 占比 15个字节
* @Date 2019/10/16 10:31
* @param bytes imei组成的字节数组
* @throws
* @return
*/
private static String parseImei(byte[] bytes) throws ParseFrameException {
try {
return ByteUtil.getString(bytes, "ascii");
} catch (UnsupportedEncodingException e) {
log.error("解析设备IMEI出错, 出现不支持编码", e);
throw new ParseFrameException("解析设备IMEI出错, 出现不支持编码字符");
}
}
/**
* @Description 解析帧组成的字节数组
* 帧头 0x7a7a7a7a(4byte)
* 设备IMEI(15byte)
* 对象序列号(1byte)
* 对象值分为2类 数值类型和文本类型 上报只有数值类型,下发才有数值类型和文本类型
* 数值类型为 8个字节 一个监测对象上报占9个字节
* 对象序列号结束符(1byte, 0x7e,对象结束)
* 帧尾 0x61616161 (4byte)
* @Date 2019/10/16 10:50
* @param bytes 帧组成的字节数组
* @throws
* @return
*/
public static MqttFrame parseFrame(byte[] bytes) throws ParseFrameException {
checkParseLength(bytes);
checkTopAndTail(bytes);
checkObjectEnd(bytes);
checkMonitorObject(bytes);
MqttFrame frame = new MqttFrame();
byte[] imeiBytes = ArrayUtils.subarray(bytes, 4,19);
frame.setImei(parseImei(imeiBytes));
int oLen = bytes.length-MIN_PARSE_ARRAY_LENGTH;
if (oLen > 0) {
byte[] monitors = ArrayUtils.subarray(bytes, 19, bytes.length-5);
frame.setMonitorObjects(parseMonitors(monitors));
}
return frame;
}
private static void checkParseLength(byte[] bytes) throws ParseFrameException {
if (ArrayUtils.isEmpty(bytes) || bytes.length < MIN_PARSE_ARRAY_LENGTH) {
log.error("解析MQTT帧长度:{}", bytes.length);
throw new ParseFrameException("解析MQTT帧长度不正确");
}
}
private static void checkTopAndTail(byte[] bytes) throws ParseFrameException {
byte[] topBytes = ArrayUtils.subarray(bytes, 0,4);
byte[] tailBytes = ArrayUtils.subarray(bytes, bytes.length-4, bytes.length);
Integer top = parseTop(topBytes);
Integer tail = parseTail(tailBytes);
if (!MqttFrame.DEFAULT_TOP.equals(top) || !MqttFrame.DEFAULT_TAIL.equals(tail)) {
log.error("解析MQTT帧头和帧尾:{}, {}", Integer.toHexString(top), Integer.toHexString(tail));
throw new ParseFrameException("解析MQTT帧头帧尾不正确");
}
}
private static void checkMonitorObject(byte[] bytes) throws ParseFrameException {
int oLen = bytes.length-MIN_PARSE_ARRAY_LENGTH;
/**对象长度必须9的倍数*/
if (oLen < 0 || (oLen % MONITOR_OBJECT_BYTE_SIZE) != 0) {
log.error("解析MQTT的监测对象长度:{}", oLen);
throw new ParseFrameException("解析MQTT的监测对象长度不正确");
}
}
private static void checkObjectEnd(byte[] bytes) throws ParseFrameException {
byte[] objectEndBytes = ArrayUtils.subarray(bytes,bytes.length-5, bytes.length-4);
Byte objectEnd = parseObjectEnd(objectEndBytes);
if (!MqttFrame.DEFAULT_OBJECT_END.equals(objectEnd)) {
log.error("解析MQTT的对象终止符:{}", Integer.toHexString(objectEnd));
throw new ParseFrameException("解析MQTT的对象终止符不正确");
}
}
/**
* @Description 获取帧头字节数组
* 枕头占比 4个字节
* @Date 2019/10/16 10:31
* @param
* @throws
* @return
*/
private static byte[] getTopBytes(Integer top) {
return ByteUtil.getIntBytes(top);
}
/**
* @Description 解析枕头字节数组
* 占比 4个字节
* @Date 2019/10/16 10:33
* @param bytes 枕头字节数组
* @throws
* @return
*/
private static Integer parseTop(byte[] bytes) {
assert (bytes.length == 4);
return ByteUtil.getInt(bytes);
}
/**
* @Description 解析对象结束符 默认是 0x4e
* @Date 2019/10/16 10:33
* @param bytes 对象结束符字节数组 数组长度为1
* @throws
* @return
*/
private static Byte parseObjectEnd(byte[] bytes) {
assert (bytes.length == 1);
return ByteUtil.getByte(bytes);
}
/**
* @Description 获取对象结束符数组 默认是
* 对象结束符 占比 1个字节
* @Date 2019/10/16 10:31
* @param
* @throws
* @return
*/
private static byte[] getObjectEndBytes(Byte objectEnd) {
return ByteUtil.getByteBytes(objectEnd);
}
/**
* @Description 获取帧尾字节数组
* 帧尾占比 4个字节
* @Date 2019/10/16 10:31
* @param
* @throws
* @return
*/
private static byte[] getTailBytes(Integer tail) {
return ByteUtil.getIntBytes(tail);
}
/**
* @Description 解析帧尾字节数组
* 帧尾占比 4个字节
* @Date 2019/10/16 10:33
* @param bytes 帧尾字节数组
* @throws
* @return
*/
private static Integer parseTail(byte[] bytes) {
assert (bytes.length == 4);
return ByteUtil.getInt(bytes);
}
(六)字节工具类
public class ByteUtil {
/**
* @Description 将 int 转化为 4个字节数组
* 数组中存储的 就是 真实的int值
* @Date 2019/10/16 9:24
* @param data 数据
* @throws
* @return
*/
public static byte[] getIntBytes(int data) {
return ByteBuffer.allocate(4).putInt(data).array();
}
/**
* @Description 将 4个字节数组 转化为 int
* 数组中存储的就是真实的int值
* @Date 2019/10/16 9:24
* @param bytes 字节数组
* @throws
* @return
*/
public static int getInt(byte[] bytes) {
return ByteBuffer.wrap(bytes).getInt();
}
/**
* @Description 将 byte 转化为 1个字节数组
* 数组中存储的是字符 因此加上字符‘0'
* @Date 2019/10/16 9:24
* @param data 数据
* @throws
* @return
*/
public static byte[] getByteBytes(byte data) {
byte[] bytes = new byte[1];
bytes[0] = (byte)(data + '0');
return bytes;
}
/**
* @Description 将 1个字节数组 转化为 byte
* 数组中存储的是 字符 因此将原来传递的字符减去字符 '0' 等于真实的数值
* @Date 2019/10/16 9:24
* @param bytes 字节数组
* @throws
* @return
*/
public static byte getByte(byte[] bytes) {
return (byte)(bytes[0] -'0');
}
/**
* @Description 将double类型的数值当成字符串传递
* 字符串长度固定为 8个字节并且包括小数点
* @Date 2019/10/17 9:05
* @param data 数值
* @throws
* @return
*/
public static byte[] getDoubleStringBytes(Double data) {
byte[] src = getStringBytes(data.toString());
byte[] des = new byte[8];
Arrays.fill(des, (byte)'0');
for (int i = 0; i < src.length && i < des.length; i++) {
des[i] = src[i];
}//end for
return des;
}
/**
* @Description 将double类型的数值组成的字符串 转变为double
* 字符串长度固定为 8个字节并且包括小数点
* @Date 2019/10/17 9:05
* @param bytes
* @throws
* @return
*/
public static Double parseDoubleString(byte[] bytes) {
String data = getString(bytes);
return new Double(data);
}
/**
* @Description 将字符串 转化为 字节数组
* @Date 2019/10/16 10:27
* @param data 字符串数据
* @throws
* @return
*/
public static byte[] getStringBytes(String data) {
return data.getBytes();
}
/**
* @Description 将字符串 转化为 特定的编码 字节数组
* @Date 2019/10/16 10:27
* @param data 字符串数据
* @param charsetName 编码名称
* @throws
* @return
*/
public static byte[] getStringBytes(String data, String charsetName) throws UnsupportedEncodingException {
return data.getBytes(charsetName);
}
/**
* @Description 字节数组 转化为 字符串 默认编码是 ASCII编码
* @Date 2019/10/16 10:28
* @param bytes 字节数组
* @throws
* @return
*/
public static String getString(byte[] bytes) {
return new String(bytes);
}
/**
* @Description 字节数组 转化为 特定的编码 字符串
* @Date 2019/10/16 10:28
* @param bytes 字节数组
* @param charsetName 编码名称
* @throws
* @return
*/
public static String getString(byte[] bytes, String charsetName) throws UnsupportedEncodingException {
return new String(bytes, charsetName);
}
/**
* @Description 将字节数组 合并为一个字节数组
* @Date 2019/10/16 15:12
* @param
* @throws
* @return
*/
public static byte[] getBytes(List<byte[]> byteList) {
if (CollectionUtils.isEmpty(byteList)) {
return new byte[0];
}
Integer len = 0;
for (byte[] bytes : byteList) {
len += bytes.length;
}
ByteBuffer byteBuffer = ByteBuffer.allocate(len);
for (byte[] bytes : byteList) {
byteBuffer.put(bytes);
}
return byteBuffer.array();
}
/**
* @Description 将字节数组转化为 十六进制字符串
* @Date 2019/10/18 9:32
* @param bytes 字节数组
* @throws
* @return
*/
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if(hex.length() < 2){
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}