jingquan #8
@ -0,0 +1,96 @@
|
|||||||
|
package com.fuyuanshen.app.controller.device;
|
||||||
|
|
||||||
|
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
|
||||||
|
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
|
||||||
|
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
|
||||||
|
import com.fuyuanshen.common.core.domain.R;
|
||||||
|
import com.fuyuanshen.common.core.validate.AddGroup;
|
||||||
|
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
|
||||||
|
import com.fuyuanshen.common.web.core.BaseController;
|
||||||
|
import com.fuyuanshen.web.service.device.DeviceBJQBizService;
|
||||||
|
import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HBY670设备控制类
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/app/xinghan/device")
|
||||||
|
public class AppDeviceXinghanController extends BaseController {
|
||||||
|
|
||||||
|
private final DeviceXinghanBizService appDeviceService;
|
||||||
|
/**
|
||||||
|
* 人员信息登记
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/registerPersonInfo")
|
||||||
|
// @FunctionAccessAnnotation("registerPersonInfo")
|
||||||
|
public R<Void> registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) {
|
||||||
|
return toAjax(appDeviceService.registerPersonInfo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传设备logo图片
|
||||||
|
*/
|
||||||
|
@PostMapping("/uploadLogo")
|
||||||
|
@FunctionAccessAnnotation("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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静电预警档位
|
||||||
|
* 3,2,1,0,分别表示高档/中档/低挡/关闭
|
||||||
|
*/
|
||||||
|
@PostMapping("/DetectGradeSettings")
|
||||||
|
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) {
|
||||||
|
// params 转 JSONObject
|
||||||
|
appDeviceService.upDetectGradeSettings(params);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 照明档位
|
||||||
|
* 照明档位,2,1,0,分别表示弱光/强光/关闭
|
||||||
|
*/
|
||||||
|
@PostMapping("/LightGradeSettings")
|
||||||
|
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) {
|
||||||
|
// params 转 JSONObject
|
||||||
|
appDeviceService.upLightGradeSettings(params);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SOS档位
|
||||||
|
* SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭
|
||||||
|
*/
|
||||||
|
@PostMapping("/SOSGradeSettings")
|
||||||
|
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) {
|
||||||
|
// params 转 JSONObject
|
||||||
|
appDeviceService.upSOSGradeSettings(params);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静止报警状态
|
||||||
|
* 静止报警状态,0-未静止报警,1-正在静止报警。
|
||||||
|
*/
|
||||||
|
@PostMapping("/ShakeBitSettings")
|
||||||
|
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) {
|
||||||
|
// params 转 JSONObject
|
||||||
|
appDeviceService.upShakeBitSettings(params);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.fuyuanshen.global.mqtt.base;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Dict;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public final class MqttXinghanCommandType {
|
||||||
|
private MqttXinghanCommandType() {}
|
||||||
|
|
||||||
|
public enum XinghanCommandTypeEnum {
|
||||||
|
/**
|
||||||
|
* 星汉设备主动上报数据
|
||||||
|
*/
|
||||||
|
GRADE_INFO(101),
|
||||||
|
/**
|
||||||
|
* 星汉开机LOGO
|
||||||
|
*/
|
||||||
|
PIC_TRANS(102),
|
||||||
|
/**
|
||||||
|
* 星汉设备发送消息 (XingHan send msg)
|
||||||
|
*/
|
||||||
|
TEX_TRANS(103),
|
||||||
|
BREAK_NEWS(104),
|
||||||
|
UNKNOWN(0);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
XinghanCommandTypeEnum(int value) { this.value = value; }
|
||||||
|
public int getValue() { return value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<String, XinghanCommandTypeEnum> KEY_TO_TYPE;
|
||||||
|
static {
|
||||||
|
LinkedHashMap<String, XinghanCommandTypeEnum> map = new LinkedHashMap<>();
|
||||||
|
map.put("sta_DetectGrade", XinghanCommandTypeEnum.GRADE_INFO);
|
||||||
|
map.put("sta_PowerTime", XinghanCommandTypeEnum.GRADE_INFO);
|
||||||
|
map.put("sta_longitude", XinghanCommandTypeEnum.GRADE_INFO);
|
||||||
|
map.put("sta_latitude", XinghanCommandTypeEnum.GRADE_INFO);
|
||||||
|
map.put("sta_PicTrans", XinghanCommandTypeEnum.PIC_TRANS);
|
||||||
|
map.put("sta_TexTrans", XinghanCommandTypeEnum.TEX_TRANS);
|
||||||
|
map.put("sta_BreakNews", XinghanCommandTypeEnum.BREAK_NEWS);
|
||||||
|
KEY_TO_TYPE = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int computeVirtualCommandType(Dict payloadDict) {
|
||||||
|
if (payloadDict == null) {
|
||||||
|
return XinghanCommandTypeEnum.UNKNOWN.getValue();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (String key : KEY_TO_TYPE.keySet()) {
|
||||||
|
if (payloadDict.containsKey(key)) {
|
||||||
|
return KEY_TO_TYPE.get(key).getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return XinghanCommandTypeEnum.UNKNOWN.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return XinghanCommandTypeEnum.UNKNOWN.getValue();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.fuyuanshen.global.mqtt.base;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class MqttXinghanJson {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第一键值对,静电预警档位:3,2,1,0,分别表示高档/中档/低挡/关闭.
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_DetectGrade")
|
||||||
|
private Integer staDetectGrade;
|
||||||
|
/**
|
||||||
|
* 第二键值对,照明档位,2,1,0,分别表示弱光/强光/关闭
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_LightGrade")
|
||||||
|
private Integer staLightGrade;
|
||||||
|
/**
|
||||||
|
* 第三键值对,SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_SOSGrade")
|
||||||
|
public Integer staSOSGrade;
|
||||||
|
/**
|
||||||
|
* 第四键值对,剩余照明时间,0-5999,单位分钟。
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_PowerTime")
|
||||||
|
public Integer staPowerTime;
|
||||||
|
/**
|
||||||
|
* 第五键值对,剩余电量百分比,0-100
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_PowerPercent")
|
||||||
|
public Integer staPowerPercent;
|
||||||
|
/**
|
||||||
|
* 第六键值对, 近电预警级别, 0-无预警,1-弱预警,2-中预警,3-强预警,4-非常强预警。
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_DetectResult")
|
||||||
|
public Integer staDetectResult;
|
||||||
|
/**
|
||||||
|
* 第七键值对, 静止报警状态,0-未静止报警,1-正在静止报警。
|
||||||
|
*/
|
||||||
|
@JsonProperty("staShakeBit")
|
||||||
|
public Integer sta_ShakeBit;
|
||||||
|
/**
|
||||||
|
* 第八键值对, 4G信号强度,0-32,数值越大,信号越强。
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_4gSinal")
|
||||||
|
public Integer sta4gSinal;
|
||||||
|
/**
|
||||||
|
* 第九键值对,IMIE卡号
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_imei")
|
||||||
|
public String staimei;
|
||||||
|
/**
|
||||||
|
* 第十键值对,经度
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_longitude")
|
||||||
|
public String stalongitude;
|
||||||
|
/**
|
||||||
|
* 第十一键值对,纬度
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_latitude")
|
||||||
|
public String stalatitude;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.fuyuanshen.global.mqtt.constants;
|
||||||
|
|
||||||
|
public class XingHanCommandTypeConstants {
|
||||||
|
/**
|
||||||
|
* 星汉设备主动上报数据 (XingHan Device Data)
|
||||||
|
*/
|
||||||
|
public static final String XingHan_DEVICE_DATA = "Light_101";
|
||||||
|
/**
|
||||||
|
* 星汉开机LOGO (XingHan Boot Logo)
|
||||||
|
*/
|
||||||
|
public static final String XingHan_BOOT_LOGO = "Light_102";
|
||||||
|
/**
|
||||||
|
* 星汉设备发送消息 (XingHan send msg)
|
||||||
|
*/
|
||||||
|
public static final String XingHan_ESEND_MSG = "Light_103";
|
||||||
|
/**
|
||||||
|
* 星汉设备发送紧急通知 (XingHan break news)
|
||||||
|
*/
|
||||||
|
public static final String XingHan_BREAK_NEWS = "Light_104";
|
||||||
|
}
|
@ -8,6 +8,7 @@ import com.fuyuanshen.common.json.utils.JsonUtils;
|
|||||||
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||||
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
|
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
|
||||||
import com.fuyuanshen.global.mqtt.base.MqttRuleEngine;
|
import com.fuyuanshen.global.mqtt.base.MqttRuleEngine;
|
||||||
|
import com.fuyuanshen.global.mqtt.base.MqttXinghanCommandType;
|
||||||
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
|
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -69,5 +70,19 @@ public class ReceiverMessageHandler implements MessageHandler {
|
|||||||
log.warn("未找到匹配的规则来处理命令类型: {}", val1);
|
log.warn("未找到匹配的规则来处理命令类型: {}", val1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 追加:根据报文内容识别格式并统一解析 ===== */
|
||||||
|
int intType = MqttXinghanCommandType.computeVirtualCommandType(payloadDict);
|
||||||
|
if (intType > 0) {
|
||||||
|
MqttRuleContext newCtx = new MqttRuleContext();
|
||||||
|
newCtx.setCommandType((byte) intType);
|
||||||
|
newCtx.setDeviceImei(deviceImei);
|
||||||
|
newCtx.setPayloadDict(payloadDict);
|
||||||
|
|
||||||
|
boolean ok = ruleEngine.executeRule(newCtx);
|
||||||
|
if (!ok) {
|
||||||
|
log.warn("新规则引擎未命中, imei={}", deviceImei);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,143 @@
|
|||||||
|
package com.fuyuanshen.global.mqtt.rule.xinghan;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
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 com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
|
||||||
|
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
|
||||||
|
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
||||||
|
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_BOOT_LOGO_KEY_PREFIX;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 星汉设备开机 LOGO 下发规则:
|
||||||
|
* <p>
|
||||||
|
* 1. 设备上行 sta_PicTarns=great! => 仅标记成功<br>
|
||||||
|
* 2. 设备上行 sta_PicTarns=数字 => 下发第 N 块数据(256B/块,带 CRC32)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class XinghanBootLogoRule implements MqttMessageRule {
|
||||||
|
|
||||||
|
private final MqttGateway mqttGateway;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandType() {
|
||||||
|
return XingHanCommandTypeConstants.XingHan_BOOT_LOGO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(MqttRuleContext ctx) {
|
||||||
|
final String functionAccessKey = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
|
||||||
|
try {
|
||||||
|
MqttXinghanLogoJson payload = objectMapper.convertValue(
|
||||||
|
ctx.getPayloadDict(), MqttXinghanLogoJson.class);
|
||||||
|
|
||||||
|
String respText = payload.getStaPicTrans();
|
||||||
|
log.warn("设备上报LOGO:{}", respText);
|
||||||
|
|
||||||
|
// 1. great! —— 成功标记
|
||||||
|
if ("great!".equalsIgnoreCase(respText)) {
|
||||||
|
RedisUtils.setCacheObject(functionAccessKey,
|
||||||
|
FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
|
||||||
|
log.info("设备 {} 开机 LOGO 写入成功", ctx.getDeviceImei());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 数字 —— 下发数据块
|
||||||
|
int blockIndex;
|
||||||
|
try {
|
||||||
|
blockIndex = Integer.parseInt(respText);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
log.warn("设备 {} LOGO 上报非法块号:{}", ctx.getDeviceImei(), respText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String hexImage = RedisUtils.getCacheObject(
|
||||||
|
GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX);
|
||||||
|
if (StringUtils.isEmpty(hexImage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fullBin = ImageToCArrayConverter.convertStringToByteArray(hexImage);
|
||||||
|
byte[] chunk = ImageToCArrayConverter.getChunk(fullBin, blockIndex - 1, CHUNK_SIZE);
|
||||||
|
log.info("设备 {} 第 {} 块数据长度: {} bytes", ctx.getDeviceImei(), blockIndex, chunk.length);
|
||||||
|
|
||||||
|
// 组装下发数据
|
||||||
|
ArrayList<Integer> dataFrame = new ArrayList<>();
|
||||||
|
dataFrame.add(blockIndex); // 块号
|
||||||
|
ImageToCArrayConverter.buildArr(convertHexToDecimal(chunk), dataFrame);
|
||||||
|
dataFrame.addAll(crc32AsList(chunk)); // CRC32
|
||||||
|
|
||||||
|
Map<String, Object> pub = new HashMap<>();
|
||||||
|
pub.put("ins_PicTrans", dataFrame);
|
||||||
|
|
||||||
|
String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei();
|
||||||
|
String json = JsonUtils.toJsonString(pub);
|
||||||
|
mqttGateway.sendMsgToMqtt(topic, 1, json);
|
||||||
|
|
||||||
|
log.info("下发开机 LOGO 数据 => topic:{}, payload:{}", topic, json);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理设备 {} 开机 LOGO 失败", ctx.getDeviceImei(), e);
|
||||||
|
RedisUtils.setCacheObject(functionAccessKey,
|
||||||
|
FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 内部工具 ---------- */
|
||||||
|
|
||||||
|
private static final int CHUNK_SIZE = 256;
|
||||||
|
|
||||||
|
private static ArrayList<Integer> crc32AsList(byte[] data) {
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
crc.update(data);
|
||||||
|
byte[] crcBytes = ByteBuffer.allocate(4)
|
||||||
|
.order(ByteOrder.BIG_ENDIAN)
|
||||||
|
.putInt((int) crc.getValue())
|
||||||
|
.array();
|
||||||
|
ArrayList<Integer> list = new ArrayList<>(4);
|
||||||
|
for (byte b : crcBytes) {
|
||||||
|
list.add(Byte.toUnsignedInt(b));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- DTO ---------- */
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class MqttXinghanLogoJson {
|
||||||
|
/**
|
||||||
|
* 设备上行:
|
||||||
|
* 数字 -> 请求对应块号
|
||||||
|
* great! -> 写入成功
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_PicTrans")
|
||||||
|
private String staPicTrans;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,212 @@
|
|||||||
|
package com.fuyuanshen.global.mqtt.rule.xinghan;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fuyuanshen.common.core.constant.GlobalConstants;
|
||||||
|
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.base.MqttXinghanJson;
|
||||||
|
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
|
||||||
|
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
|
||||||
|
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动上报设备数据命令处理
|
||||||
|
* 第一键值对,静电预警档位:3,2,1,0,分别表示高档/中档/低挡/关闭.
|
||||||
|
* 第二键值对,照明档位,2,1,0,分别表示弱光/强光/关闭
|
||||||
|
* 第三键值对,SOS档位,2,1,0, 分别表示红蓝模式/爆闪模式/关闭
|
||||||
|
* 第四键值对,剩余照明时间,0-5999,单位分钟。
|
||||||
|
* 第五键值对, 剩余电量百分比,0-100。
|
||||||
|
* 第六键值对, 近电预警级别, 0-无预警,1-弱预警,2-中预警,3-强预警,4-非常强预警。
|
||||||
|
* 第七键值对, 静止报警状态,0-未静止报警,1-正在静止报警。
|
||||||
|
* 第八键值对, 4G信号强度,0-32,数值越大,信号越强。
|
||||||
|
* 第九键值对,IMIE卡号
|
||||||
|
* 第十键值对,经度
|
||||||
|
* 第十一键值对,纬度
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandType() {
|
||||||
|
return XingHanCommandTypeConstants.XingHan_DEVICE_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(MqttRuleContext context) {
|
||||||
|
try {
|
||||||
|
// Latitude, longitude
|
||||||
|
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间
|
||||||
|
MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class);
|
||||||
|
|
||||||
|
// 发送设备状态和位置信息到Redis
|
||||||
|
asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理上报数据命令时出错", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送设备状态信息和位置信息到Redis
|
||||||
|
*
|
||||||
|
* @param deviceImei 设备IMEI
|
||||||
|
* @param deviceStatus 机器主动上报的状态信息
|
||||||
|
*/
|
||||||
|
public void asyncSendDeviceDataToRedisWithFuture(String deviceImei, MqttXinghanJson deviceStatus) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 将设备状态信息存储到Redis中
|
||||||
|
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_STATUS_KEY_PREFIX;
|
||||||
|
String deviceInfoJson = JsonUtils.toJsonString(deviceStatus);
|
||||||
|
// 存储到Redis
|
||||||
|
RedisUtils.setCacheObject(deviceRedisKey, deviceInfoJson);
|
||||||
|
|
||||||
|
log.info("设备状态信息已异步发送到Redis: device={}, deviceInfoJson={}",
|
||||||
|
deviceImei, deviceInfoJson);
|
||||||
|
|
||||||
|
//设备坐标缓存KEY
|
||||||
|
String functionAccess = FUNCTION_ACCESS_KEY + deviceImei;
|
||||||
|
// 异步发送经纬度到Redis
|
||||||
|
asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude());
|
||||||
|
|
||||||
|
} 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.isBlank(latitude) || StringUtils.isBlank(longitude)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] latArr = latitude.split("\\.");
|
||||||
|
String[] lonArr = longitude.split("\\.");
|
||||||
|
// 将位置信息存储到Redis中
|
||||||
|
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX;
|
||||||
|
String redisObj = RedisUtils.getCacheObject(redisKey);
|
||||||
|
JSONObject jsonOBj = JSONObject.parseObject(redisObj);
|
||||||
|
if(jsonOBj != null){
|
||||||
|
String str1 = latArr[0] +"."+ latArr[1].substring(0,4);
|
||||||
|
String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4);
|
||||||
|
|
||||||
|
String cacheLatitude = jsonOBj.getString("wgs84_latitude");
|
||||||
|
String cacheLongitude = jsonOBj.getString("wgs84_longitude");
|
||||||
|
String[] latArr1 = cacheLatitude.split("\\.");
|
||||||
|
String[] lonArr1 = cacheLongitude.split("\\.");
|
||||||
|
|
||||||
|
String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4);
|
||||||
|
String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4);
|
||||||
|
if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){
|
||||||
|
log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, 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]);
|
||||||
|
locationInfo.put("wgs84_latitude", latitude);
|
||||||
|
locationInfo.put("wgs84_longitude", longitude);
|
||||||
|
String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0]));
|
||||||
|
locationInfo.put("address", address);
|
||||||
|
locationInfo.put("timestamp", System.currentTimeMillis());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
String locationJson = JsonUtils.toJsonString(locationInfo);
|
||||||
|
|
||||||
|
// 存储到Redis
|
||||||
|
RedisUtils.setCacheObject(redisKey, locationJson);
|
||||||
|
|
||||||
|
// 存储到一个列表中,保留历史位置信息
|
||||||
|
// String locationHistoryKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX + deviceImei;
|
||||||
|
// RedisUtils.addCacheList(locationHistoryKey, locationJson);
|
||||||
|
// RedisUtils.expire(locationHistoryKey, Duration.ofDays(90));
|
||||||
|
storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson);
|
||||||
|
log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储设备30天历史轨迹到Redis (使用Sorted Set)
|
||||||
|
*/
|
||||||
|
public void storeDeviceTrajectoryWithSortedSet(String deviceImei, String locationJson) {
|
||||||
|
try {
|
||||||
|
String trajectoryKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX;
|
||||||
|
// String trajectoryKey = "device:trajectory:zset:" + deviceImei;
|
||||||
|
// String locationJson = JsonUtils.toJsonString(locationInfo);
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 添加到Sorted Set,使用时间戳作为score
|
||||||
|
RedisUtils.zAdd(trajectoryKey, locationJson, timestamp);
|
||||||
|
|
||||||
|
// // 设置30天过期时间
|
||||||
|
// RedisUtils.expire(trajectoryKey, Duration.ofDays(30));
|
||||||
|
|
||||||
|
// 清理30天前的数据(冗余保护)
|
||||||
|
long thirtyDaysAgo = System.currentTimeMillis() - (7L * 24 * 60 * 60 * 1000);
|
||||||
|
RedisUtils.zRemoveRangeByScore(trajectoryKey, 0, thirtyDaysAgo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("存储设备轨迹到Redis(ZSet)失败: 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]));
|
||||||
|
String str1 = latArr[1];
|
||||||
|
intData.add(Integer.parseInt(str1.substring(0,4)));
|
||||||
|
String str2 = lonArr[1];
|
||||||
|
intData.add(Integer.parseInt(lonArr[0]));
|
||||||
|
intData.add(Integer.parseInt(str2.substring(0,4)));
|
||||||
|
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("instruct", intData);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package com.fuyuanshen.global.mqtt.rule.xinghan;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
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.MqttConstants;
|
||||||
|
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
|
||||||
|
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
|
||||||
|
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 星汉设备发送消息 下发规则:
|
||||||
|
* <p>
|
||||||
|
* 1. 设备上行 sta_TexTarns=genius! => 仅标记成功<br>
|
||||||
|
* 2. 设备上行 sta_TexTarns=数字 => GBK编码,每行文字为一包,一共4包,第一字节为包序号
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class XinghanSendMsgRule implements MqttMessageRule {
|
||||||
|
|
||||||
|
private final MqttGateway mqttGateway;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandType() {
|
||||||
|
return XingHanCommandTypeConstants.XingHan_ESEND_MSG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(MqttRuleContext ctx) {
|
||||||
|
String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
|
||||||
|
try {
|
||||||
|
XinghanSendMsgRule.MqttXinghanMsgJson payload = objectMapper.convertValue(
|
||||||
|
ctx.getPayloadDict(), XinghanSendMsgRule.MqttXinghanMsgJson.class);
|
||||||
|
|
||||||
|
String respText = payload.getStaTexTrans();
|
||||||
|
log.info("设备上报人员信息: {} ", respText);
|
||||||
|
|
||||||
|
// 1. genius! —— 成功标记
|
||||||
|
if ("genius!".equalsIgnoreCase(respText)) {
|
||||||
|
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
|
||||||
|
log.info("设备 {} 发送消息完成", ctx.getDeviceImei());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 2. 数字 —— 下发数据块
|
||||||
|
int blockIndex;
|
||||||
|
try {
|
||||||
|
blockIndex = Integer.parseInt(respText);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
log.warn("设备 {} 消息上报非法块号:{}", ctx.getDeviceImei(), respText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 将发送的信息原文本以List<String>形式存储在Redis中
|
||||||
|
List<String> data = RedisUtils.getCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + ":app_send_message_data");
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
ArrayList<Integer> intData = new ArrayList<>();
|
||||||
|
intData.add(blockIndex);
|
||||||
|
// 获取块原内容 转成GBK 再转成无符号十进制整数
|
||||||
|
String blockTxt = data.get(blockIndex-1);
|
||||||
|
// 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数
|
||||||
|
for (byte b : blockTxt.getBytes(GBK)) {
|
||||||
|
intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("ins_TexTrans", intData);
|
||||||
|
|
||||||
|
String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei();
|
||||||
|
String json = JsonUtils.toJsonString(map);
|
||||||
|
mqttGateway.sendMsgToMqtt(topic, 1, json);
|
||||||
|
log.info("发送设备信息数据到设备消息=>topic:{},payload:{}",
|
||||||
|
MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(),
|
||||||
|
JsonUtils.toJsonString(map));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理发送设备信息时出错", e);
|
||||||
|
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Charset GBK = Charset.forName("GBK");
|
||||||
|
|
||||||
|
/* ---------- DTO ---------- */
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class MqttXinghanMsgJson {
|
||||||
|
/**
|
||||||
|
* 设备上行:
|
||||||
|
* 数字 -> 请求对应块号
|
||||||
|
* genius! -> 写入成功
|
||||||
|
*/
|
||||||
|
@JsonProperty("sta_TexTrans")
|
||||||
|
private String staTexTrans;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,269 @@
|
|||||||
|
package com.fuyuanshen.web.service.device;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.fuyuanshen.app.domain.AppPersonnelInfo;
|
||||||
|
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
|
||||||
|
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
|
||||||
|
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
|
||||||
|
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
|
||||||
|
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
|
||||||
|
import com.fuyuanshen.common.core.constant.GlobalConstants;
|
||||||
|
import com.fuyuanshen.common.core.exception.ServiceException;
|
||||||
|
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
|
||||||
|
import com.fuyuanshen.common.core.utils.MapstructUtils;
|
||||||
|
import com.fuyuanshen.common.core.utils.ObjectUtils;
|
||||||
|
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||||
|
import com.fuyuanshen.common.json.utils.JsonUtils;
|
||||||
|
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||||
|
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
|
||||||
|
import com.fuyuanshen.equipment.domain.Device;
|
||||||
|
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
|
||||||
|
import com.fuyuanshen.equipment.enums.LightModeEnum;
|
||||||
|
import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
|
||||||
|
import com.fuyuanshen.equipment.mapper.DeviceMapper;
|
||||||
|
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
|
||||||
|
import com.fuyuanshen.global.mqtt.config.MqttGateway;
|
||||||
|
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
|
||||||
|
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
||||||
|
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr;
|
||||||
|
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.generateFixedBitmapData;
|
||||||
|
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_BOOT_LOGO_KEY_PREFIX;
|
||||||
|
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DeviceXinghanBizService {
|
||||||
|
|
||||||
|
private final DeviceMapper deviceMapper;
|
||||||
|
private final AppPersonnelInfoMapper appPersonnelInfoMapper;
|
||||||
|
private final DeviceTypeMapper deviceTypeMapper;
|
||||||
|
private final MqttGateway mqttGateway;
|
||||||
|
private final DeviceLogMapper deviceLogMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有档位的描述表
|
||||||
|
* key : 指令类型,如 "ins_DetectGrade"、"ins_LightGrade" ……
|
||||||
|
* value : Map<Integer,String> 值 -> 描述
|
||||||
|
*/
|
||||||
|
private static final Map<String, Map<Integer, String>> GRADE_DESC = Map.of(
|
||||||
|
"ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"),
|
||||||
|
"ins_LightGrade", Map.of(1, "强光", 2, "弱光"),
|
||||||
|
"ins_SOSGrade", Map.of(1, "爆闪模式", 2, "红蓝模式"),
|
||||||
|
"ins_ShakeBit", Map.of(0, "未静止报警", 1, "正在静止报警")
|
||||||
|
// 再加 4、5、6…… 档,直接往 Map 里塞即可
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据指令类型和值,返回中文描述
|
||||||
|
*/
|
||||||
|
private static String resolveGradeDesc(String type, int value) {
|
||||||
|
return GRADE_DESC.getOrDefault(type, Map.of())
|
||||||
|
.getOrDefault(value, "关闭");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置静电预警档位
|
||||||
|
*/
|
||||||
|
public void upDetectGradeSettings(DeviceInstructDto dto) {
|
||||||
|
sendCommand(dto, "ins_DetectGrade","静电预警档位");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置照明档位
|
||||||
|
*/
|
||||||
|
public void upLightGradeSettings(DeviceInstructDto dto) {
|
||||||
|
sendCommand(dto, "ins_LightGrade","照明档位");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置SOS档位
|
||||||
|
*/
|
||||||
|
public void upSOSGradeSettings(DeviceInstructDto dto) {
|
||||||
|
sendCommand(dto, "ins_SOSGrade","SOS档位");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置强制报警
|
||||||
|
*/
|
||||||
|
public void upShakeBitSettings(DeviceInstructDto dto) {
|
||||||
|
sendCommand(dto, "ins_ShakeBit","强制报警");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传设备logo
|
||||||
|
*/
|
||||||
|
public void uploadDeviceLogo(AppDeviceLogoUploadDto bo) {
|
||||||
|
try {
|
||||||
|
Device device = deviceMapper.selectById(bo.getDeviceId());
|
||||||
|
if (device == null) {
|
||||||
|
throw new ServiceException("设备不存在");
|
||||||
|
}
|
||||||
|
if (isDeviceOffline(device.getDeviceImei())) {
|
||||||
|
throw new ServiceException("设备已断开连接:" + device.getDeviceName());
|
||||||
|
}
|
||||||
|
MultipartFile file = bo.getFile();
|
||||||
|
|
||||||
|
byte[] largeData = ImageToCArrayConverter.convertImageToCArray(file.getInputStream(), 160, 80, 25600);
|
||||||
|
log.info("长度:" + largeData.length);
|
||||||
|
|
||||||
|
log.info("原始数据大小: {} 字节", largeData.length);
|
||||||
|
|
||||||
|
int[] ints = convertHexToDecimal(largeData);
|
||||||
|
RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() +DEVICE_BOOT_LOGO_KEY_PREFIX, Arrays.toString(ints), Duration.ofSeconds(5 * 60L));
|
||||||
|
|
||||||
|
Map<String, Object> payload = Map.of("ins_PicTrans",
|
||||||
|
Collections.singletonList(0));
|
||||||
|
String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei();
|
||||||
|
String json = JsonUtils.toJsonString(payload);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mqttGateway.sendMsgToMqtt(topic, 1, json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("上传开机画面失败, topic={}, payload={}", topic, json, e);
|
||||||
|
throw new ServiceException("上传LOGO失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
log.info("发送上传开机画面到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json);
|
||||||
|
|
||||||
|
recordDeviceLog(device.getId(), device.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId());
|
||||||
|
} catch (Exception e){
|
||||||
|
throw new ServiceException("发送指令失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人员登记
|
||||||
|
* @param bo
|
||||||
|
*/
|
||||||
|
public boolean registerPersonInfo(AppPersonnelInfoBo bo) {
|
||||||
|
Long deviceId = bo.getDeviceId();
|
||||||
|
Device deviceObj = deviceMapper.selectById(deviceId);
|
||||||
|
if (deviceObj == null) {
|
||||||
|
throw new RuntimeException("请先将设备入库!!!");
|
||||||
|
}
|
||||||
|
if (isDeviceOffline(deviceObj.getDeviceImei())) {
|
||||||
|
throw new ServiceException("设备已断开连接:" + deviceObj.getDeviceName());
|
||||||
|
}
|
||||||
|
QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
|
||||||
|
.eq("device_id", deviceId);
|
||||||
|
List<AppPersonnelInfoVo> appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw);
|
||||||
|
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.add(bo.getUnitName());
|
||||||
|
list.add(bo.getName());
|
||||||
|
list.add(bo.getPosition());
|
||||||
|
list.add(bo.getCode());
|
||||||
|
RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list);
|
||||||
|
|
||||||
|
Map<String, Object> payload = Map.of("ins_TexTrans",
|
||||||
|
Collections.singletonList(0));
|
||||||
|
String topic = MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei();
|
||||||
|
String json = JsonUtils.toJsonString(payload);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mqttGateway.sendMsgToMqtt(topic, 1, json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("人员信息登记失败, topic={}, payload={}", topic, json, e);
|
||||||
|
throw new ServiceException("人员信息登记失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
log.info("发送人员信息登记到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), bo);
|
||||||
|
|
||||||
|
recordDeviceLog(deviceId, deviceObj.getDeviceName(), "人员信息登记", JSON.toJSONString(bo), AppLoginHelper.getUserId());
|
||||||
|
if (ObjectUtils.length(appPersonnelInfoVos) == 0) {
|
||||||
|
AppPersonnelInfo appPersonnelInfo = MapstructUtils.convert(bo, AppPersonnelInfo.class);
|
||||||
|
return appPersonnelInfoMapper.insertOrUpdate(appPersonnelInfo);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------- 私有通用方法 ---------------------------------- */
|
||||||
|
|
||||||
|
private void sendCommand(DeviceInstructDto dto,
|
||||||
|
String payloadKey,String deviceAction) {
|
||||||
|
long deviceId = dto.getDeviceId();
|
||||||
|
Device device = deviceMapper.selectById(deviceId);
|
||||||
|
if (device == null) {
|
||||||
|
throw new ServiceException("设备不存在");
|
||||||
|
}
|
||||||
|
if (isDeviceOffline(device.getDeviceImei())) {
|
||||||
|
throw new ServiceException("设备已断开连接:" + device.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer value = Integer.parseInt(dto.getInstructValue());
|
||||||
|
|
||||||
|
Map<String, Object> payload = Map.of(payloadKey,
|
||||||
|
Collections.singletonList(value));
|
||||||
|
|
||||||
|
String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei();
|
||||||
|
String json = JsonUtils.toJsonString(payload);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mqttGateway.sendMsgToMqtt(topic, 1, json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送指令失败, topic={}, payload={}", topic, json, e);
|
||||||
|
throw new ServiceException("发送指令失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("发送指令成功 => topic:{}, payload:{}", topic, json);
|
||||||
|
String content = resolveGradeDesc("ins_DetectGrade", value);
|
||||||
|
recordDeviceLog(device.getId(),
|
||||||
|
device.getDeviceName(),
|
||||||
|
deviceAction,
|
||||||
|
content,
|
||||||
|
AppLoginHelper.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDeviceOffline(String imei) {
|
||||||
|
// 原方法名语义相反,这里取反,使含义更清晰
|
||||||
|
return getDeviceStatus(imei);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录设备操作日志
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param content 日志内容
|
||||||
|
* @param operator 操作人
|
||||||
|
*/
|
||||||
|
private void recordDeviceLog(Long deviceId,String deviceName, String deviceAction, String content, Long operator) {
|
||||||
|
try {
|
||||||
|
// 创建设备日志实体
|
||||||
|
com.fuyuanshen.equipment.domain.DeviceLog deviceLog = new com.fuyuanshen.equipment.domain.DeviceLog();
|
||||||
|
deviceLog.setDeviceId(deviceId);
|
||||||
|
deviceLog.setDeviceAction(deviceAction);
|
||||||
|
deviceLog.setContent(content);
|
||||||
|
deviceLog.setCreateBy(operator);
|
||||||
|
deviceLog.setDeviceName(deviceName);
|
||||||
|
deviceLog.setCreateTime(new Date());
|
||||||
|
|
||||||
|
// 插入日志记录
|
||||||
|
deviceLogMapper.insert(deviceLog);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("记录设备操作日志失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getDeviceStatus(String deviceImei) {
|
||||||
|
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
|
||||||
|
return StringUtils.isBlank(deviceOnlineStatusRedisKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -65,4 +65,9 @@ public class DeviceQueryCriteria extends BaseEntity {
|
|||||||
|
|
||||||
/* app绑定用户id */
|
/* app绑定用户id */
|
||||||
private Long bindingUserId;
|
private Long bindingUserId;
|
||||||
|
|
||||||
|
|
||||||
|
/* 是否为管理员 */
|
||||||
|
private Boolean isAdmin = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,10 @@ public class DeviceTypeQueryCriteria extends BaseEntity implements Serializable
|
|||||||
|
|
||||||
@Schema(name = "每页数据量", example = "10")
|
@Schema(name = "每页数据量", example = "10")
|
||||||
private Integer pageSize = 10;
|
private Integer pageSize = 10;
|
||||||
|
|
||||||
|
|
||||||
|
/* 是否为管理员 */
|
||||||
|
private Boolean isAdmin = false;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,13 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
|||||||
criteria.setDeviceType(deviceTypeGrant.getDeviceTypeId());
|
criteria.setDeviceType(deviceTypeGrant.getDeviceTypeId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理员
|
||||||
|
String username = LoginHelper.getUsername();
|
||||||
|
if (username.equals("admin")) {
|
||||||
|
criteria.setIsAdmin(true);
|
||||||
|
}
|
||||||
|
|
||||||
IPage<Device> devices = deviceMapper.findAll(criteria, page);
|
IPage<Device> devices = deviceMapper.findAll(criteria, page);
|
||||||
|
|
||||||
List<Device> records = devices.getRecords();
|
List<Device> records = devices.getRecords();
|
||||||
|
@ -53,8 +53,12 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public TableDataInfo<DeviceType> queryAll(DeviceTypeQueryCriteria criteria, Page<DeviceType> page) {
|
public TableDataInfo<DeviceType> queryAll(DeviceTypeQueryCriteria criteria, Page<DeviceType> page) {
|
||||||
criteria.setCustomerId(LoginHelper.getUserId());
|
// 管理员
|
||||||
// return
|
String username = LoginHelper.getUsername();
|
||||||
|
if (!username.equals("admin")) {
|
||||||
|
criteria.setCustomerId(LoginHelper.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
IPage<DeviceType> deviceTypeIPage = deviceTypeMapper.findAll(criteria, page);
|
IPage<DeviceType> deviceTypeIPage = deviceTypeMapper.findAll(criteria, page);
|
||||||
return new TableDataInfo<DeviceType>(deviceTypeIPage.getRecords(), deviceTypeIPage.getTotal());
|
return new TableDataInfo<DeviceType>(deviceTypeIPage.getRecords(), deviceTypeIPage.getTotal());
|
||||||
}
|
}
|
||||||
@ -74,8 +78,16 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
|
|||||||
@Override
|
@Override
|
||||||
public List<DeviceType> queryDeviceTypes() {
|
public List<DeviceType> queryDeviceTypes() {
|
||||||
DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria();
|
DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria();
|
||||||
Long userId = LoginHelper.getUserId();
|
|
||||||
criteria.setCustomerId(userId);
|
// 管理员
|
||||||
|
String username = LoginHelper.getUsername();
|
||||||
|
if (!username.equals("admin")) {
|
||||||
|
criteria.setCustomerId(LoginHelper.getUserId());
|
||||||
|
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
criteria.setCustomerId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
return deviceTypeMapper.findAll(criteria);
|
return deviceTypeMapper.findAll(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +74,11 @@
|
|||||||
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
||||||
and da.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
and da.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
||||||
</if>
|
</if>
|
||||||
AND da.assignee_id = #{criteria.currentOwnerId}
|
<!-- 管理员可以查看所有设备,普通用户只能查看自己的设备 -->
|
||||||
AND dg.customer_id = #{criteria.currentOwnerId}
|
<if test="criteria.isAdmin != true">
|
||||||
|
AND da.assignee_id = #{criteria.currentOwnerId}
|
||||||
|
AND dg.customer_id = #{criteria.currentOwnerId}
|
||||||
|
</if>
|
||||||
</where>
|
</where>
|
||||||
) AS ranked
|
) AS ranked
|
||||||
WHERE rn = 1
|
WHERE rn = 1
|
||||||
@ -213,18 +216,20 @@
|
|||||||
WHERE original_device_id = #{originalDeviceId}
|
WHERE original_device_id = #{originalDeviceId}
|
||||||
</select>
|
</select>
|
||||||
<select id="getDeviceInfo" resultType="com.fuyuanshen.equipment.domain.vo.AppDeviceVo">
|
<select id="getDeviceInfo" resultType="com.fuyuanshen.equipment.domain.vo.AppDeviceVo">
|
||||||
select d.id, d.device_name, d.device_name,
|
select d.id,
|
||||||
d.device_name,
|
d.device_name,
|
||||||
d.device_mac,
|
d.device_name,
|
||||||
d.device_sn,
|
d.device_name,
|
||||||
d.device_imei,
|
d.device_mac,
|
||||||
d.device_pic,
|
d.device_sn,
|
||||||
dt.type_name,
|
d.device_imei,
|
||||||
dt.communication_mode,
|
d.device_pic,
|
||||||
d.bluetooth_name,
|
dt.type_name,
|
||||||
dt.model_dictionary detailPageUrl
|
dt.communication_mode,
|
||||||
|
d.bluetooth_name,
|
||||||
|
dt.model_dictionary detailPageUrl
|
||||||
from device d
|
from device d
|
||||||
inner join device_type dt on d.device_type = dt.id
|
inner join device_type dt on d.device_type = dt.id
|
||||||
where d.device_mac = #{deviceMac}
|
where d.device_mac = #{deviceMac}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user