#init
This commit is contained in:
@@ -11,6 +11,41 @@
|
||||
|
||||
<artifactId>maibu-netty-server</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.56.Final</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-iot-service</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-common</artifactId>
|
||||
<version>3.8.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.20.0</version> <!-- 使用最新版 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.20.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>1.5.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.maibu;
|
||||
|
||||
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
|
||||
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
|
||||
// to see how IntelliJ IDEA suggests fixing it.
|
||||
System.out.printf("Hello and welcome!");
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
|
||||
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
|
||||
System.out.println("i = " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.fastbee.common;
|
||||
|
||||
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; //主要用于推送状态消息等
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.fastbee.common;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Constant {
|
||||
|
||||
public static final AttributeKey<String> ATT_DEVICE_ID = AttributeKey.valueOf("deviceId");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.fastbee.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
public class NettyCacheKey {
|
||||
|
||||
public static final String deviceRunningStatusKey = "running_status:";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.fastbee.controller;
|
||||
|
||||
import com.fastbee.common.CommandConstant;
|
||||
import com.fastbee.common.core.controller.BaseController;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
import com.fastbee.dto.DeviceTaskQueryDTO;
|
||||
import com.fastbee.dto.NettyDevice;
|
||||
import com.fastbee.dto.ObstacleData;
|
||||
import com.fastbee.iot.domain.DevicePlan;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import com.fastbee.service.DeviceTaskService;
|
||||
import com.fastbee.utils.CommandUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/iot/api")
|
||||
public class DeviceExternalApiController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager deviceSessionManager;
|
||||
|
||||
@PostMapping("/obstacleData")
|
||||
public AjaxResult obstacleData(@RequestBody ObstacleData obstacleData) {
|
||||
Map<String, Object> data = obstacleData.getData();
|
||||
String deviceId = (String) data.get("deviceId");
|
||||
if (StringUtils.isEmpty(deviceId)) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
// Map<String, Object> obstacle = (Map<String, Object>) data.get("obstacle");
|
||||
// Boolean isSecure = (Boolean) data.get("isSecure");
|
||||
|
||||
List<NettyDevice> devices = deviceSessionManager.getSlaveControl(deviceId);
|
||||
if (!CollectionUtils.isEmpty(devices)) {
|
||||
devices.forEach(x -> {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
CommandUtils.buildCommand(buf, obstacleData, CommandConstant.interaction);
|
||||
String content = buf.toString(CharsetUtil.UTF_8);
|
||||
logger.info("推送设备:{}障碍物消息:{}到:{}", deviceId, content, x.getConnectorId());
|
||||
if (x.getChannel() != null && x.getChannel().isActive()) {
|
||||
x.getChannel().writeAndFlush(buf);
|
||||
}
|
||||
});
|
||||
}
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.fastbee.controller;
|
||||
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/memory/device")
|
||||
public class DeviceMemoryController {
|
||||
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager deviceSessionManager;
|
||||
|
||||
@Autowired
|
||||
private GlobalMemory globalMemory;
|
||||
|
||||
@GetMapping("getDeviceMemory")
|
||||
public Map<String, Object> getDeviceMemory() {
|
||||
Map<String, Object> res = new HashMap<>();
|
||||
res.put("deviceMap", deviceSessionManager.getDeviceMap());
|
||||
res.put("channelToDeviceIdMap", deviceSessionManager.getChannelToDeviceIdMap());
|
||||
res.put("channelMap", deviceSessionManager.getChannelMap());
|
||||
return res;
|
||||
}
|
||||
//
|
||||
// @GetMapping("getGlobalMemory")
|
||||
// public Map<String, Object> getGlobalMemory() {
|
||||
// Map<String, Object> res = new HashMap<>();
|
||||
// res.put("sysLoginMap", globalMemory.getSysLoginMap());
|
||||
// res.put("sysLoginDataMap", globalMemory.getSysLoginDataMap());
|
||||
// return res;
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.fastbee.controller;
|
||||
|
||||
import com.fastbee.common.core.controller.BaseController;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.dto.DeviceTaskQueryDTO;
|
||||
import com.fastbee.iot.domain.DevicePlan;
|
||||
import com.fastbee.service.DeviceTaskService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/iot/deviceTask")
|
||||
public class DeviceTaskController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private DeviceTaskService deviceTaskService;
|
||||
|
||||
@PostMapping("/save")
|
||||
public AjaxResult insertOrUpdate(@RequestBody DevicePlan devicePlan) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
return AjaxResult.success(deviceTaskService.insertOrUpdate(devicePlan, loginUser));
|
||||
}
|
||||
|
||||
@GetMapping("/getAllDeviceTaskByUser")
|
||||
public AjaxResult getAllDeviceTaskByUser(@RequestParam Long userId) {
|
||||
return AjaxResult.success(deviceTaskService.getDeviceTaskByUser(userId));
|
||||
}
|
||||
|
||||
@PostMapping("/getDevicePlan")
|
||||
public AjaxResult getDevicePlan(@RequestBody DeviceTaskQueryDTO dto) {
|
||||
return AjaxResult.success(deviceTaskService.getDeviceTask(dto));
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
public AjaxResult delete(@RequestParam Long id) {
|
||||
return AjaxResult.success(deviceTaskService.delete(id));
|
||||
}
|
||||
|
||||
@GetMapping("/start")
|
||||
public AjaxResult start(@RequestParam Long id) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
deviceTaskService.start(id, loginUser);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@GetMapping("/devicePlanStatistics")
|
||||
public AjaxResult devicePlanStatistics(@RequestParam Long userId) {
|
||||
return AjaxResult.success(deviceTaskService.getDevicePlanStatistics(userId));
|
||||
}
|
||||
|
||||
@GetMapping("/devicePlanTaskStatistics")
|
||||
public AjaxResult devicePlanTaskStatistics(@RequestParam Long userId) {
|
||||
|
||||
return AjaxResult.success(deviceTaskService.getDevicePlanTaskStatistics(userId));
|
||||
}
|
||||
|
||||
@GetMapping("/concurrentDeviceRoute")
|
||||
public AjaxResult concurrentDeviceRoute(@RequestParam String deviceId) {
|
||||
return AjaxResult.success(deviceTaskService.concurrentDevicePlanTask(deviceId));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/workStatistics")
|
||||
public AjaxResult workStatistics(@RequestParam Long userId) {
|
||||
return AjaxResult.success(deviceTaskService.workStatistics(userId));
|
||||
}
|
||||
|
||||
@PostMapping("/deviceTaskPool")
|
||||
public AjaxResult deviceTaskPool(@RequestBody DeviceTaskQueryDTO dto) {
|
||||
return AjaxResult.success(deviceTaskService.deviceTaskPool(dto));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.fastbee.controller;
|
||||
|
||||
import com.fastbee.common.core.controller.BaseController;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.page.TableDataInfo;
|
||||
import com.fastbee.iot.domain.DeviceRunningStatusHistory;
|
||||
import com.fastbee.entity.ErrorIdentificationStandard;
|
||||
import com.fastbee.service.NettyDeviceService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/netty/device")
|
||||
public class NettyDeviceController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private NettyDeviceService nettyDeviceService;
|
||||
|
||||
@GetMapping("/statusHistory")
|
||||
public TableDataInfo statusHistory(DeviceRunningStatusHistory history)
|
||||
{
|
||||
startPage();
|
||||
List<DeviceRunningStatusHistory> list = nettyDeviceService.statusHistory(history);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@GetMapping("/getErrorIdentification")
|
||||
public TableDataInfo getErrorIdentification()
|
||||
{
|
||||
startPage();
|
||||
List<ErrorIdentificationStandard> list = nettyDeviceService.allErrorIdentificationStandard();
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PostMapping("/updateErrorIdentification")
|
||||
public AjaxResult updateErrorIdentification(@RequestBody ErrorIdentificationStandard standard)
|
||||
{
|
||||
int i = nettyDeviceService.updateErrorIdentification(standard);
|
||||
return AjaxResult.success(i);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.fastbee.controller;
|
||||
|
||||
import com.fastbee.common.annotation.Log;
|
||||
import com.fastbee.common.core.controller.BaseController;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.enums.BusinessType;
|
||||
import com.fastbee.common.utils.MessageUtils;
|
||||
import com.fastbee.dto.DeviceBindDTO;
|
||||
import com.fastbee.service.TransferDeviceService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/forward/device")
|
||||
public class TransferDeviceController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private TransferDeviceService transferDeviceService;
|
||||
|
||||
@PostMapping("bind")
|
||||
@ApiOperation("绑定设备")
|
||||
@Log(title = "绑定设备", businessType = BusinessType.OTHER)
|
||||
public AjaxResult bind(@RequestBody DeviceBindDTO bindDTO) {
|
||||
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceId())) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
return transferDeviceService.bind(bindDTO, user);
|
||||
}
|
||||
|
||||
@PostMapping("unbind")
|
||||
@ApiOperation("解绑设备")
|
||||
@Log(title = "解绑设备", businessType = BusinessType.OTHER)
|
||||
public AjaxResult unbind(@RequestBody DeviceBindDTO bindDTO) {
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceId())) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
return transferDeviceService.unbind(bindDTO, user);
|
||||
}
|
||||
|
||||
@PostMapping("updateDeviceAlias")
|
||||
@ApiOperation("更新设备别名")
|
||||
@Log(title = "更新设备别名", businessType = BusinessType.OTHER)
|
||||
public AjaxResult updateDeviceAlias(@RequestBody DeviceBindDTO bindDTO) {
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceId())) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceAlias())) {
|
||||
return AjaxResult.error("设备别名为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
return transferDeviceService.updateDeviceAlias(bindDTO, user);
|
||||
}
|
||||
|
||||
@PostMapping("switchDevice")
|
||||
@ApiOperation("切换设备")
|
||||
@Log(title = "切换设备", businessType = BusinessType.OTHER)
|
||||
public AjaxResult switchDevice(@RequestBody DeviceBindDTO bindDTO) {
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceId())) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
|
||||
return AjaxResult.error("平台为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
if (user == null) {
|
||||
return AjaxResult.error("当前token用户不存在!");
|
||||
}
|
||||
return AjaxResult.success(transferDeviceService.switchDevice(bindDTO, user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的控制权在哪 能否控制
|
||||
*/
|
||||
@PostMapping("remoteControl")
|
||||
@ApiOperation("获取当前设备控制权")
|
||||
@Log(title = "获取当前设备控制权", businessType = BusinessType.OTHER)
|
||||
public AjaxResult remoteControl(@RequestBody DeviceBindDTO bindDTO) {
|
||||
try {
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceId())) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
|
||||
return AjaxResult.error("平台为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
return AjaxResult.success(transferDeviceService.remoteControl(bindDTO, user));
|
||||
} catch (Exception e) {
|
||||
logger.error("获取设备控制权出错", e);
|
||||
return AjaxResult.error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换控制请求
|
||||
*/
|
||||
@PostMapping("switchControl")
|
||||
@ApiOperation("切换控制请求")
|
||||
@Log(title = "切换控制请求", businessType = BusinessType.OTHER)
|
||||
public AjaxResult switchControl(@RequestBody DeviceBindDTO bindDTO) {
|
||||
if (StringUtils.isEmpty(bindDTO.getDeviceId())) {
|
||||
return AjaxResult.error("deviceId为空!");
|
||||
}
|
||||
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
|
||||
return AjaxResult.error("平台为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
return AjaxResult.success(transferDeviceService.switchControl(bindDTO, user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放控制权
|
||||
*/
|
||||
@PostMapping("releaseControl")
|
||||
@ApiOperation("释放控制权")
|
||||
@Log(title = "释放控制权", businessType = BusinessType.OTHER)
|
||||
public AjaxResult releaseControl(@RequestBody DeviceBindDTO bindDTO) {
|
||||
if (StringUtils.isEmpty(bindDTO.getPlatform())) {
|
||||
return AjaxResult.error("平台为空!");
|
||||
}
|
||||
LoginUser user = getLoginUser();
|
||||
transferDeviceService.releaseControl(bindDTO, user);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import com.fastbee.enums.ConnectorStatus;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Data
|
||||
public class Connector {
|
||||
// 获取设备ID
|
||||
private final String connectorId;
|
||||
// 设备ID
|
||||
private ConnectorStatus connectorStatus; // 设备状态(可用、禁言、不可用)
|
||||
// 获取连接的设备列表
|
||||
private Set<Connector> connectedConnectors; // 连接的设备列表 主机绑定从机 从机无绑定
|
||||
|
||||
private Channel channel;
|
||||
|
||||
// 无参构造函数,解决Lombok需要默认构造函数的问题
|
||||
public Connector() {
|
||||
this.connectorId = "";
|
||||
this.connectorStatus = ConnectorStatus.ACTIVE;
|
||||
this.connectedConnectors = new HashSet<>();
|
||||
}
|
||||
|
||||
// 构造器
|
||||
public Connector(String connectorId) {
|
||||
this.connectorId = connectorId;
|
||||
this.connectorStatus = ConnectorStatus.ACTIVE; // 默认状态为完全可用
|
||||
this.connectedConnectors = new HashSet<>();
|
||||
}
|
||||
|
||||
//
|
||||
public void addConnectedDevice(Connector connector) {
|
||||
connectedConnectors.add(connector);
|
||||
}
|
||||
|
||||
// 移除连接的设备,确保双向移除
|
||||
public void removeConnectedDevice(Connector connector) {
|
||||
// 从当前设备的连接列表中移除
|
||||
connectedConnectors.remove(connector);
|
||||
|
||||
}
|
||||
|
||||
public boolean isOnline() {
|
||||
return channel != null && channel.isActive();
|
||||
}
|
||||
|
||||
public boolean canSend() {
|
||||
return connectorStatus == ConnectorStatus.ACTIVE;
|
||||
}
|
||||
|
||||
public boolean canReceive() {
|
||||
return connectorStatus != ConnectorStatus.DISABLED;
|
||||
}
|
||||
|
||||
// 打印设备信息
|
||||
public void printConnectorInfo() {
|
||||
System.out.println("Device ID: " + connectorId);
|
||||
System.out.println("Device Status: " + connectorStatus);
|
||||
System.out.println("Connected Devices: ");
|
||||
for (Connector connectedDevice : connectedConnectors) {
|
||||
System.out.println("- " + connectedDevice.getConnectorId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceBindDTO {
|
||||
/**
|
||||
* 主机名
|
||||
*/
|
||||
private String platform;
|
||||
/**
|
||||
* 设备id
|
||||
*/
|
||||
private String deviceId;
|
||||
/**
|
||||
* 设备别名
|
||||
*/
|
||||
private String deviceAlias;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceControlDTO {
|
||||
private boolean remoteControl;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private String reason;
|
||||
|
||||
private String owner;// app/pc 具体被占用方
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import com.fastbee.enums.RespondCode;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DeviceErrorPushDTO {
|
||||
|
||||
private List<DeviceErrorPushDetail> details;
|
||||
|
||||
private long time;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private RespondCode event;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceErrorPushDetail {
|
||||
|
||||
private String errorName;
|
||||
|
||||
private String description;
|
||||
|
||||
private String range;
|
||||
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import com.fastbee.enums.RespondCode;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceLoginResponseDTO {
|
||||
private RespondCode respond;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DevicePlanStatisticsDTO {
|
||||
|
||||
private String name;
|
||||
private Integer planned;
|
||||
private int completed;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DevicePlanTaskStatisticsDTO {
|
||||
|
||||
private String date;
|
||||
private Integer success;
|
||||
private Integer paused;
|
||||
private Integer failed;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import com.fastbee.enums.CommandRequestType;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceRequestDTO {
|
||||
private CommandRequestType request;
|
||||
private String deviceId;
|
||||
private String userId;
|
||||
private String platform;
|
||||
private String token;
|
||||
private DeviceRespondDTO respond;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceRespondDTO {
|
||||
private Boolean switchResult;
|
||||
private String deviceId;
|
||||
private String holder;
|
||||
private String reason;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceRunningStatus {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
/** 产品分类ID */
|
||||
private String deviceId;
|
||||
|
||||
private String voltage;
|
||||
|
||||
private String leftTargetSpeed;
|
||||
|
||||
private String rightTargetSpeed;
|
||||
|
||||
private String leftMeasureSpeed;
|
||||
|
||||
private String rightMeasureSpeed;
|
||||
|
||||
private String leftCurrent;
|
||||
|
||||
private String rightCurrent;
|
||||
|
||||
private String leftMotorTemp;
|
||||
|
||||
private String rightMotorTemp;
|
||||
|
||||
private String chipTemp;
|
||||
|
||||
private String Q0;
|
||||
|
||||
private String Q1;
|
||||
|
||||
private String Q2;
|
||||
|
||||
private String Q3;
|
||||
|
||||
private String latitude;
|
||||
|
||||
private String longitude;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import com.fastbee.enums.RespondCode;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceStatusChangeDTO {
|
||||
|
||||
private RespondCode event;
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private String status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class DeviceSwitchControlDTO {
|
||||
private boolean switchControl;
|
||||
|
||||
private String reason;
|
||||
|
||||
private String holder;
|
||||
|
||||
|
||||
public DeviceSwitchControlDTO(boolean switchControl) {
|
||||
this.switchControl = switchControl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import com.fastbee.iot.enums.DeviceTaskStaus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class DeviceTaskQueryDTO {
|
||||
|
||||
private Long userId;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
private DeviceTaskStaus taskStaus;
|
||||
private String planName;
|
||||
private Long planId;
|
||||
private String deviceId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceWorkStatisticsDTO {
|
||||
|
||||
private String label;
|
||||
private Integer value;
|
||||
private Integer unit;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
|
||||
import com.fastbee.entity.path.LatAndLngEntity;
|
||||
import com.fastbee.enums.ConnectorType;
|
||||
import com.fastbee.iot.domain.DeviceRunStatistics;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class NettyDevice extends Connector {
|
||||
|
||||
private ConnectorType deviceType; // 设备角色类型:MASTER、SLAVE、GENERAL
|
||||
|
||||
private String currentSlaveId;// 当前对应的下位机 master
|
||||
|
||||
private Long currentTaskId; //当前执行的任务;
|
||||
|
||||
private Integer onlineStatus; //在线状态 0 离线 1在线
|
||||
|
||||
private Queue<LatAndLngEntity> locationQueue = new LinkedList<>();
|
||||
|
||||
private DeviceRunStatistics latestStatus;
|
||||
|
||||
private Long latestLoginTime;
|
||||
|
||||
public NettyDevice(String connectorId) {
|
||||
super(connectorId);
|
||||
this.deviceType = ConnectorType.MASTER; // 默认上位机角色
|
||||
}
|
||||
|
||||
public NettyDevice(String connectorId, ConnectorType deviceType) {
|
||||
super(connectorId);
|
||||
this.deviceType = deviceType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.fastbee.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ObstacleData {
|
||||
|
||||
private String type;
|
||||
private Map<String,Object> data;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.fastbee.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fastbee.enums.CompareEnum;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("error_identification")
|
||||
public class ErrorIdentificationStandard {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String field;
|
||||
private String compareValues;
|
||||
private CompareEnum compareType;
|
||||
private String errorDescription;
|
||||
private String errorName;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.fastbee.entity.path;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LatAndLngEntity {
|
||||
private double lat;
|
||||
private double lng;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + lat + "," + lng + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
package com.fastbee.entity.path;
|
||||
|
||||
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class MowingPathGenerator {
|
||||
public static final double M_PI = 3.14159265358979323846;
|
||||
public static final double Earth_Radius = 6378137.0;
|
||||
public static final double EPSILON = 1e-6;
|
||||
public static final double REL_EPSILON = 1e-8;
|
||||
public static final double ABS_EPSILON = 1e-12;
|
||||
|
||||
public static class Point {
|
||||
public double x, y;
|
||||
|
||||
public Point(double x, double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PathPoint {
|
||||
public double lat, lon;
|
||||
|
||||
public PathPoint(double lat, double lon) {
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Polygon {
|
||||
public Point[] vertices;
|
||||
public int num_vertices;
|
||||
|
||||
public Polygon(Point[] vertices, int num_vertices) {
|
||||
this.vertices = vertices;
|
||||
this.num_vertices = num_vertices;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PolygonWithHoles {
|
||||
public Polygon outer;
|
||||
public Polygon[] holes;
|
||||
public int num_holes;
|
||||
|
||||
public PolygonWithHoles(Polygon outer, Polygon[] holes, int num_holes) {
|
||||
this.outer = outer;
|
||||
this.holes = holes;
|
||||
this.num_holes = num_holes;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProjectedPoint {
|
||||
public Point pt;
|
||||
public double proj;
|
||||
|
||||
public ProjectedPoint(Point pt, double proj) {
|
||||
this.pt = pt;
|
||||
this.proj = proj;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEqual(double a, double b) {
|
||||
double diff = Math.abs(a - b);
|
||||
double max_ab = Math.max(Math.abs(a), Math.abs(b));
|
||||
double tolerance = REL_EPSILON * max_ab + ABS_EPSILON;
|
||||
return diff <= tolerance;
|
||||
}
|
||||
|
||||
public static double cross(Point a, Point b) {
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
public static boolean getLineIntersection(Point p1, Point p2, Point q1, Point q2, Point intersection) {
|
||||
Point r = new Point(p2.x - p1.x, p2.y - p1.y);
|
||||
Point s = new Point(q2.x - q1.x, q2.y - q1.y);
|
||||
double rxs = cross(r, s);
|
||||
Point qp = new Point(q1.x - p1.x, q1.y - p1.y);
|
||||
double q_p_cross_r = cross(qp, r);
|
||||
|
||||
if (Math.abs(rxs) < EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double t = cross(qp, s) / rxs;
|
||||
double u = q_p_cross_r / rxs;
|
||||
|
||||
if (t >= 0.0 - EPSILON && t <= 1.0 + EPSILON && u >= 0.0 - EPSILON && u <= 1.0 + EPSILON) {
|
||||
intersection.x = p1.x + t * r.x;
|
||||
intersection.y = p1.y + t * r.y;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isPointOnSegment(Point pt, Point p1, Point p2) {
|
||||
if (pt.x < Math.min(p1.x, p2.x) - EPSILON || pt.x > Math.max(p1.x, p2.x) + EPSILON) {
|
||||
return false;
|
||||
}
|
||||
if (pt.y < Math.min(p1.y, p2.y) - EPSILON || pt.y > Math.max(p1.y, p2.y) + EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double cross = (p2.x - p1.x) * (pt.y - p1.y) - (p2.y - p1.y) * (pt.x - p1.x);
|
||||
if (Math.abs(cross) > EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void makeScanline(PolygonWithHoles field, Point lineStart, Point lineEnd, Point normal, double min_proj, Point dir) {
|
||||
double min_x = Double.POSITIVE_INFINITY, max_x = Double.NEGATIVE_INFINITY;
|
||||
double min_y = Double.POSITIVE_INFINITY, max_y = Double.NEGATIVE_INFINITY;
|
||||
|
||||
for (int i = 0; i < field.outer.num_vertices; i++) {
|
||||
Point p = field.outer.vertices[i];
|
||||
if (p.x < min_x) min_x = p.x;
|
||||
if (p.x > max_x) max_x = p.x;
|
||||
if (p.y < min_y) min_y = p.y;
|
||||
if (p.y > max_y) max_y = p.y;
|
||||
}
|
||||
|
||||
double dx = max_x - min_x, dy = max_y - min_y;
|
||||
double len_scan = Math.sqrt(dx * dx + dy * dy) * 1.1;
|
||||
|
||||
Point lineOrigin = new Point(normal.x * min_proj, normal.y * min_proj);
|
||||
|
||||
lineStart.x = lineOrigin.x - dir.x * len_scan;
|
||||
lineStart.y = lineOrigin.y - dir.y * len_scan;
|
||||
|
||||
lineEnd.x = lineOrigin.x + dir.x * len_scan;
|
||||
lineEnd.y = lineOrigin.y + dir.y * len_scan;
|
||||
}
|
||||
|
||||
public static void collectHoleIntersections(PolygonWithHoles poly, Point q1, Point q2, List<ProjectedPoint> intersections, Point dir) {
|
||||
intersections.clear();
|
||||
|
||||
for (int h = 0; h < poly.num_holes; h++) {
|
||||
for (int i = 0; i < poly.holes[h].num_vertices; i++) {
|
||||
Point p1 = poly.holes[h].vertices[i];
|
||||
Point p2 = poly.holes[h].vertices[(i + 1) % poly.holes[h].num_vertices];
|
||||
Point inter = new Point(0, 0);
|
||||
|
||||
if (getLineIntersection(p1, p2, q1, q2, inter)) {
|
||||
double proj = inter.x * dir.x + inter.y * dir.y;
|
||||
intersections.add(new ProjectedPoint(new Point(inter.x, inter.y), proj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void collectOuterIntersections(PolygonWithHoles poly, Point q1, Point q2, List<ProjectedPoint> intersections, Point dir) {
|
||||
intersections.clear();
|
||||
|
||||
for (int i = 0; i < poly.outer.num_vertices; i++) {
|
||||
Point p1 = poly.outer.vertices[i];
|
||||
Point p2 = poly.outer.vertices[(i + 1) % poly.outer.num_vertices];
|
||||
Point inter = new Point(0, 0);
|
||||
|
||||
if (getLineIntersection(p1, p2, q1, q2, inter)) {
|
||||
double proj = inter.x * dir.x + inter.y * dir.y;
|
||||
intersections.add(new ProjectedPoint(new Point(inter.x, inter.y), proj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateMowingPath(PolygonWithHoles field, double width, Point scanStart, Point scanEnd, List<Point> pathPoints) {
|
||||
pathPoints.clear();
|
||||
|
||||
Point dir = new Point(scanEnd.x - scanStart.x, scanEnd.y - scanStart.y);
|
||||
double len = Math.sqrt(dir.x * dir.x + dir.y * dir.y);
|
||||
dir.x /= len;
|
||||
dir.y /= len;
|
||||
Point normal = new Point(-dir.y, dir.x);
|
||||
|
||||
double min_proj = Double.POSITIVE_INFINITY, max_proj = Double.NEGATIVE_INFINITY;
|
||||
for (int i = 0; i < field.outer.num_vertices; i++) {
|
||||
Point p = field.outer.vertices[i];
|
||||
double proj = p.x * normal.x + p.y * normal.y;
|
||||
if (proj < min_proj) min_proj = proj;
|
||||
if (proj > max_proj) max_proj = proj;
|
||||
}
|
||||
|
||||
int lines = (int) ((max_proj - min_proj) / width + 1);
|
||||
int max_outer_points = lines * field.outer.num_vertices * 2;
|
||||
int max_hole_points = lines * field.num_holes * 10 * 2;
|
||||
|
||||
System.out.printf("dir = (%.4f, %.4f)\n", dir.x, dir.y);
|
||||
System.out.printf("normal = (%.4f, %.4f)\n", normal.x, normal.y);
|
||||
System.out.printf("Projection range: %.4f ~ %.4f (%.4f)\n", min_proj, max_proj, max_proj - min_proj);
|
||||
System.out.printf("lines = %d\n", lines);
|
||||
|
||||
Point lineStart = new Point(0, 0);
|
||||
Point lineEnd = new Point(0, 0);
|
||||
makeScanline(field, lineStart, lineEnd, normal, min_proj, dir);
|
||||
int direction = 1;
|
||||
|
||||
List<List<ProjectedPoint>> outerIntersections = new ArrayList<>();
|
||||
List<List<ProjectedPoint>> holeIntersections = new ArrayList<>();
|
||||
List<Integer> numHole = new ArrayList<>();
|
||||
List<Integer> numOuter = new ArrayList<>();
|
||||
|
||||
for (int l = 0; l < lines; l++) {
|
||||
List<ProjectedPoint> outerInts = new ArrayList<>();
|
||||
collectOuterIntersections(field, lineStart, lineEnd, outerInts, dir);
|
||||
|
||||
List<ProjectedPoint> holeInts = new ArrayList<>();
|
||||
collectHoleIntersections(field, lineStart, lineEnd, holeInts, dir);
|
||||
|
||||
holeInts.sort(Comparator.comparingDouble(p -> p.proj));
|
||||
|
||||
if (direction == -1) {
|
||||
Collections.reverse(holeInts);
|
||||
}
|
||||
|
||||
outerIntersections.add(outerInts);
|
||||
holeIntersections.add(holeInts);
|
||||
numHole.add(holeInts.size());
|
||||
numOuter.add(outerInts.size());
|
||||
|
||||
lineStart.x += normal.x * width;
|
||||
lineStart.y += normal.y * width;
|
||||
lineEnd.x += normal.x * width;
|
||||
lineEnd.y += normal.y * width;
|
||||
direction *= -1;
|
||||
}
|
||||
|
||||
direction = 1;
|
||||
|
||||
for (int l = 0; l < lines; l++) {
|
||||
List<ProjectedPoint> intersections = new ArrayList<>();
|
||||
intersections.addAll(outerIntersections.get(l));
|
||||
intersections.addAll(holeIntersections.get(l));
|
||||
|
||||
intersections.sort(Comparator.comparingDouble(p -> p.proj));
|
||||
|
||||
if (direction == -1) {
|
||||
Collections.reverse(intersections);
|
||||
}
|
||||
|
||||
boolean isHoleStart = false;
|
||||
int layerHoleNum = numHole.get(l);
|
||||
|
||||
if (layerHoleNum > 0) {
|
||||
ProjectedPoint holeStart = holeIntersections.get(l).get(0);
|
||||
ProjectedPoint holeEnd = holeIntersections.get(l).get(layerHoleNum - 1);
|
||||
|
||||
for (int i = 0; i < intersections.size(); i++) {
|
||||
ProjectedPoint testPoint = intersections.get(i);
|
||||
|
||||
if (isEqual(testPoint.proj, holeStart.proj)) {
|
||||
isHoleStart = true;
|
||||
}
|
||||
|
||||
if (isHoleStart) {
|
||||
pathPoints.add(holeStart.pt);
|
||||
|
||||
int holeId = -1;
|
||||
int startIndex = -1, endIndex = -1;
|
||||
boolean isHoleVertices = false;
|
||||
|
||||
for (int h = 0; h < field.num_holes; h++) {
|
||||
Polygon hole = field.holes[h];
|
||||
|
||||
for (int j = 0; j < hole.num_vertices; j++) {
|
||||
Point p1 = hole.vertices[j];
|
||||
Point p2 = hole.vertices[(j + 1) % hole.num_vertices];
|
||||
|
||||
if (isEqual(holeStart.pt.x, p1.x) && isEqual(holeStart.pt.y, p1.y)) {
|
||||
isHoleVertices = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isPointOnSegment(holeStart.pt, p1, p2)) {
|
||||
holeId = h;
|
||||
startIndex = (j + 1) % hole.num_vertices;
|
||||
}
|
||||
|
||||
if (isPointOnSegment(holeEnd.pt, p1, p2)) {
|
||||
endIndex = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isHoleVertices) {
|
||||
if (holeId >= 0 && startIndex >= 0 && endIndex >= 0) {
|
||||
Polygon hole = field.holes[holeId];
|
||||
int index = startIndex;
|
||||
int indexHoleIntersections = 1;
|
||||
boolean forward = true;
|
||||
|
||||
for (int step = 0; step < hole.num_vertices; step++) {
|
||||
if (indexHoleIntersections < layerHoleNum - 1) {
|
||||
Point current = hole.vertices[index];
|
||||
Point next = hole.vertices[(index + 1) % hole.num_vertices];
|
||||
Point intersectionPt = holeIntersections.get(l).get(indexHoleIntersections).pt;
|
||||
|
||||
if (forward && !isPointOnSegment(intersectionPt, current, next)) {
|
||||
forward = false;
|
||||
index = (index - 1 + hole.num_vertices) % hole.num_vertices;
|
||||
endIndex = (endIndex + 1) % hole.num_vertices;
|
||||
System.out.println("is_point_on_segment");
|
||||
}
|
||||
|
||||
if (indexHoleIntersections % 2 != 0) {
|
||||
pathPoints.add(hole.vertices[index]);
|
||||
}
|
||||
|
||||
pathPoints.add(holeIntersections.get(l).get(indexHoleIntersections).pt);
|
||||
indexHoleIntersections++;
|
||||
i++;
|
||||
} else {
|
||||
pathPoints.add(hole.vertices[index]);
|
||||
}
|
||||
|
||||
if (index == endIndex) break;
|
||||
|
||||
if (forward) {
|
||||
index = (index + 1) % hole.num_vertices;
|
||||
} else {
|
||||
index = (index - 1 + hole.num_vertices) % hole.num_vertices;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathPoints.add(holeEnd.pt);
|
||||
i++;
|
||||
isHoleStart = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
pathPoints.add(intersections.get(i).pt);
|
||||
}
|
||||
} else {
|
||||
for (ProjectedPoint pp : intersections) {
|
||||
pathPoints.add(pp.pt);
|
||||
}
|
||||
}
|
||||
|
||||
direction *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void latlonToXY(double lat0, double lon0, double lat, double lon, Point result) {
|
||||
double d_lat = Math.toRadians(lat - lat0);
|
||||
double d_lon = Math.toRadians(lon - lon0);
|
||||
double lat0_rad = Math.toRadians(lat0);
|
||||
|
||||
result.x = Earth_Radius * d_lon * Math.cos(lat0_rad);
|
||||
result.y = Earth_Radius * d_lat;
|
||||
}
|
||||
|
||||
public static void xyToLatlon(double lat0, double lon0, double x, double y, PathPoint result) {
|
||||
double lat0_rad = Math.toRadians(lat0);
|
||||
|
||||
result.lat = lat0 + (y / Earth_Radius) * (180.0 / M_PI);
|
||||
result.lon = lon0 + (x / (Earth_Radius * Math.cos(lat0_rad))) * (180.0 / M_PI);
|
||||
}
|
||||
|
||||
public static void getCoord(PathPoint[] gps, Point[] points, PathPoint start, int cnt) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
latlonToXY(start.lat, start.lon, gps[i].lat, gps[i].lon, points[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void getLatlon(PathPoint[] gps, Point[] points, PathPoint start, int cnt) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
xyToLatlon(start.lat, start.lon, points[i].x, points[i].y, gps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void parseJson(String json) {
|
||||
RPPathData data = JsonUtils.parseObject(json,RPPathData.class);
|
||||
|
||||
PathPoint refPoint = data.refPoint;
|
||||
PathPoint[] outer = data.Polygon.outer.toArray(new PathPoint[0]);
|
||||
PathPoint start = data.scanLine.start;
|
||||
PathPoint end = data.scanLine.end;
|
||||
PathPoint[] holes = data.Polygon.holes.toArray(new PathPoint[0]);
|
||||
// 打印验证
|
||||
System.out.println("Ref Point: " + refPoint);
|
||||
System.out.println("Start: " + start);
|
||||
System.out.println("End: " + end);
|
||||
System.out.println("Outer:");
|
||||
for (PathPoint p : outer) {
|
||||
System.out.println(" " + p);
|
||||
}
|
||||
System.out.println("Holes:");
|
||||
for (PathPoint p : holes) {
|
||||
System.out.println(" " + p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static List<LatAndLngEntity> generateMowingPath(String json) {
|
||||
RPPathData data = JsonUtils.parseObject(json,RPPathData.class);
|
||||
|
||||
PathPoint refPoint = data.refPoint;
|
||||
PathPoint[] outer = data.Polygon.outer.toArray(new PathPoint[0]);
|
||||
PathPoint start = data.scanLine.start;
|
||||
PathPoint end = data.scanLine.end;
|
||||
List<PathPoint> allHolePoints = new ArrayList<>();
|
||||
for (List<PathPoint> hole : data.Polygon.holes) {
|
||||
allHolePoints.addAll(hole);
|
||||
}
|
||||
PathPoint[] holes = allHolePoints.toArray(new PathPoint[0]);
|
||||
|
||||
|
||||
Point[] outerPoint = new Point[outer.length];
|
||||
Point startPoint = new Point(0, 0);
|
||||
Point endPoint = new Point(0, 0);
|
||||
Point[] holesPoint = new Point[holes.length];
|
||||
|
||||
latlonToXY(refPoint.lat, refPoint.lon, start.lat, start.lon, startPoint);
|
||||
latlonToXY(refPoint.lat, refPoint.lon, end.lat, end.lon, endPoint);
|
||||
|
||||
for (int i = 0; i < outer.length; i++) {
|
||||
outerPoint[i] = new Point(0, 0);
|
||||
latlonToXY(refPoint.lat, refPoint.lon, outer[i].lat, outer[i].lon, outerPoint[i]);
|
||||
System.out.printf("(%.14f, %.14f)\n", outerPoint[i].x, outerPoint[i].y);
|
||||
// Log.d("MowingPathGenerator", "Outer Point: " + "(" + outerPoint[i].x + ", " + outerPoint[i].y + " )");
|
||||
}
|
||||
|
||||
if (holes.length > 0) {
|
||||
for (int i = 0; i < holes.length; i++) {
|
||||
holesPoint[i] = new Point(0, 0);
|
||||
latlonToXY(refPoint.lat, refPoint.lon, holes[i].lat, holes[i].lon, holesPoint[i]);
|
||||
// Log.d("MowingPathGenerator", "holes Point: " + "(" + holesPoint[i].x + ", " + holesPoint[i].y + " )");
|
||||
System.out.printf("(%.14f, %.14f)\n", holesPoint[i].x, holesPoint[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Polygon outerPolygon = new Polygon(outerPoint, outer.length);
|
||||
|
||||
// Polygon holesPolygon[] = {new Polygon(holesPoint, holesPoint.length)};
|
||||
// PolygonWithHoles field = new PolygonWithHoles(outerPolygon, holesPolygon,holesPoint.length);
|
||||
|
||||
Polygon[] holesPolygons = new Polygon[data.Polygon.holes.size()];
|
||||
for (int i = 0; i < data.Polygon.holes.size(); i++) {
|
||||
List<PathPoint> hole = data.Polygon.holes.get(i);
|
||||
Point[] holePoints = new Point[hole.size()];
|
||||
for (int j = 0; j < hole.size(); j++) {
|
||||
holePoints[j] = new Point(0, 0);
|
||||
latlonToXY(refPoint.lat, refPoint.lon, hole.get(j).lat, hole.get(j).lon, holePoints[j]);
|
||||
}
|
||||
holesPolygons[i] = new Polygon(holePoints, holePoints.length);
|
||||
}
|
||||
PolygonWithHoles field = new PolygonWithHoles(outerPolygon, holesPolygons, holesPolygons.length);
|
||||
|
||||
|
||||
// PolygonWithHoles field = new PolygonWithHoles(outerPolygon, null,0);
|
||||
|
||||
double width = 0.8;
|
||||
List<Point> path = new ArrayList<>();
|
||||
|
||||
generateMowingPath(field, width, startPoint, endPoint, path);
|
||||
|
||||
PathPoint[] gpsout = new PathPoint[path.size()];
|
||||
|
||||
for (int i = 0; i < gpsout.length; i++) {
|
||||
gpsout[i] = new PathPoint(0, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
if (i >= gpsout.length) break;
|
||||
xyToLatlon(start.lat, start.lon, path.get(i).x, path.get(i).y, gpsout[i]);
|
||||
}
|
||||
List<LatAndLngEntity> Points = new ArrayList<>();
|
||||
|
||||
System.out.printf("Generated %d path points:\n", path.size());
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
if (i >= gpsout.length) break;
|
||||
System.out.printf("[%.14f, %.14f]\n", gpsout[i].lon, gpsout[i].lat);
|
||||
// Log.d("MowingPathGenerator", "gpsout Point: " + "(" + gpsout[i].lon + ", " + gpsout[i].lat + " )");
|
||||
LatAndLngEntity latAndLngEntity = new LatAndLngEntity();
|
||||
latAndLngEntity.setLat(gpsout[i].lat);
|
||||
latAndLngEntity.setLng(gpsout[i].lon);
|
||||
Points.add(latAndLngEntity);
|
||||
}
|
||||
|
||||
return Points;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// public static void main(String[] args) {
|
||||
//
|
||||
// PathPoint start = new PathPoint(32.08599985199932, 120.77586450935124);
|
||||
// PathPoint[] gpsout = new PathPoint[1024];
|
||||
// PathPoint[] gpsin = {
|
||||
// new PathPoint(32.08599985199932, 120.77586450935124),
|
||||
// new PathPoint(32.08624992154597, 120.77670803771835),
|
||||
// new PathPoint(32.08568874374706, 120.77710241458807),
|
||||
// new PathPoint(32.0854398968189, 120.77615838649545)
|
||||
// };
|
||||
//
|
||||
// PathPoint scanStart = new PathPoint(32.08599985199932, 120.77586450935124);
|
||||
// PathPoint scanEnd = new PathPoint(32.08624992154597, 120.77670803771835);
|
||||
// Point startPoint = new Point(0,0);
|
||||
// Point endPoint = new Point(0,0);
|
||||
//
|
||||
// latlonToXY(start.lat, start.lon, scanStart.lat, scanStart.lon, startPoint);
|
||||
// latlonToXY(start.lat, start.lon, scanEnd.lat, scanEnd.lon, endPoint);
|
||||
//
|
||||
// Point[] outerVertices = new Point[4];
|
||||
// for (int i = 0; i < 4; i++) {
|
||||
// outerVertices[i] = new Point(0, 0);
|
||||
// latlonToXY(start.lat, start.lon, gpsin[i].lat, gpsin[i].lon, outerVertices[i]);
|
||||
// System.out.printf("(%.14f, %.14f)\n", outerVertices[i].x, outerVertices[i].y);
|
||||
// }
|
||||
//
|
||||
// Polygon outer = new Polygon(outerVertices, 4);
|
||||
// Point[] hole1Vertices = {
|
||||
// new Point(40, -10), new Point(80, -10), new Point(80, -40),
|
||||
// new Point(60, -28), new Point(40, -40)
|
||||
// };
|
||||
// Polygon[] holes = {
|
||||
// new Polygon(hole1Vertices, 5)
|
||||
// };
|
||||
// PolygonWithHoles field = new PolygonWithHoles(outer, holes, 1);
|
||||
//
|
||||
// double width = 0.8;
|
||||
// List<Point> path = new ArrayList<>();
|
||||
//
|
||||
// generateMowingPath(field, width, startPoint, endPoint, path);
|
||||
//
|
||||
// for (int i = 0; i < gpsout.length; i++) {
|
||||
// gpsout[i] = new PathPoint(0, 0);
|
||||
// }
|
||||
//
|
||||
// for (int i = 0; i < path.size(); i++) {
|
||||
// if (i >= gpsout.length) break;
|
||||
// xyToLatlon(start.lat, start.lon, path.get(i).x, path.get(i).y, gpsout[i]);
|
||||
// }
|
||||
//
|
||||
// System.out.printf("Generated %d path points:\n", path.size());
|
||||
// for (int i = 0; i < path.size(); i++) {
|
||||
// if (i >= gpsout.length) break;
|
||||
// System.out.printf("{%.14f, %.14f}\n", gpsout[i].lon, gpsout[i].lat);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.fastbee.entity.path;
|
||||
import com.fastbee.entity.path.MowingPathGenerator.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RPPathData {
|
||||
public ScanLine scanLine;
|
||||
public PathPoint refPoint;
|
||||
public PolygonData Polygon;
|
||||
|
||||
public static class ScanLine {
|
||||
public PathPoint start;
|
||||
public PathPoint end;
|
||||
}
|
||||
|
||||
public static class PolygonData {
|
||||
public List<PathPoint> outer;
|
||||
public List<List<PathPoint>> holes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.fastbee.entity.path;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RoutePlanRecEntity {
|
||||
private byte header1;
|
||||
private byte header2;
|
||||
private byte commandType;
|
||||
private short pointNum;
|
||||
private byte status;
|
||||
private short crc16;
|
||||
private byte endFlag1;
|
||||
private byte endFlag2;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RoutePlanRecEntity{" +
|
||||
"header1=" + header1 +
|
||||
", header2=" + header2 +
|
||||
", commandType=" + commandType +
|
||||
", pointNum=" + pointNum +
|
||||
", status=" + status +
|
||||
", crc16=" + crc16 +
|
||||
", endFlag1=" + endFlag1 +
|
||||
", endFlag2=" + endFlag2 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.fastbee.entity.path;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
@Data
|
||||
public class RoutePlanSendEntity {
|
||||
|
||||
public RoutePlanSendEntity() {
|
||||
header1 = (byte) 0xAB;
|
||||
header2 = (byte) 0xAA;
|
||||
commandType = 0x01;
|
||||
pointCounts = 1;
|
||||
targetLatitude = 0;
|
||||
targetLongitude = 0;
|
||||
speed = 0x3E8;
|
||||
crc16 = 0;
|
||||
endFlag1 = (byte) 0xAA;
|
||||
endFlag2 = (byte) 0xAB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RoutePlanSendEntity{" +
|
||||
"header1=" + header1 +
|
||||
", header2=" + header2 +
|
||||
", commandType=" + commandType +
|
||||
", pointCounts=" + pointCounts +
|
||||
", targetLatitude=" + targetLatitude +
|
||||
", targetLongitude=" + targetLongitude +
|
||||
", speed=" + speed +
|
||||
", crc16=" + crc16 +
|
||||
", endFlag1=" + endFlag1 +
|
||||
", endFlag2=" + endFlag2 +
|
||||
'}';
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(27); // 修改为27字节
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buffer.put((byte) 0xAB);
|
||||
buffer.put((byte) 0xAA);
|
||||
|
||||
buffer.put((byte) 0x01);
|
||||
|
||||
buffer.putShort(pointCounts);
|
||||
|
||||
|
||||
long latitudeBits = Double.doubleToLongBits(targetLatitude);
|
||||
buffer.putLong(latitudeBits);
|
||||
|
||||
long longitudeBits = Double.doubleToLongBits(targetLongitude);
|
||||
buffer.putLong(longitudeBits);
|
||||
|
||||
buffer.putShort(speed);
|
||||
buffer.putShort(crc16);
|
||||
|
||||
buffer.put((byte) 0xAA);
|
||||
buffer.put((byte) 0xAB);
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private byte header1;
|
||||
private byte header2;
|
||||
private byte commandType;
|
||||
private short pointCounts;
|
||||
private double targetLatitude; // 改为 double
|
||||
private double targetLongitude; // 改为 double
|
||||
private short speed;
|
||||
private short crc16;
|
||||
private byte endFlag1;
|
||||
private byte endFlag2;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
public enum CommandRequestType {
|
||||
switch_control
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 比较操作符枚举类
|
||||
* 用于标准化管理比较类型,如>、<、between等
|
||||
* 基于项目现有的使用模式实现
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CompareEnum {
|
||||
|
||||
/** 等于 */
|
||||
EQ("=", "等于", "=", true),
|
||||
|
||||
/** 不等于 */
|
||||
NEQ("!=", "不等于", "!=", true),
|
||||
|
||||
/** 大于 */
|
||||
GT(">", "大于", ">", true),
|
||||
|
||||
/** 小于 */
|
||||
LT("<", "小于", "<", true),
|
||||
|
||||
/** 大于等于 */
|
||||
GTE(">=", "大于等于", ">=", true),
|
||||
|
||||
/** 小于等于 */
|
||||
LTE("<=", "小于等于", "<=", true),
|
||||
|
||||
/** 在之间 */
|
||||
BETWEEN("between", "在之间", "between", false),
|
||||
|
||||
/** 不在之间 */
|
||||
NOT_BETWEEN("notBetween", "不在之间", "notBetween", false),
|
||||
|
||||
/** 包含 */
|
||||
CONTAIN("contain", "包含", "contain", true),
|
||||
|
||||
/** 不包含 */
|
||||
NOT_CONTAIN("notContain", "不包含", "notContain", true);
|
||||
|
||||
/** 操作符值(存储在数据库中) */
|
||||
private final String value;
|
||||
|
||||
/** 操作符名称(用于前端显示) */
|
||||
private final String name;
|
||||
|
||||
/** 表达式符号(用于计算表达式) */
|
||||
private final String symbol;
|
||||
|
||||
/** 是否为单值比较(true:单值, false:范围值) */
|
||||
private final boolean singleValue;
|
||||
|
||||
/**
|
||||
* 根据值获取比较操作符枚举
|
||||
* @param value 操作符值
|
||||
* @return 比较操作符枚举
|
||||
*/
|
||||
public static CompareEnum getByValue(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (CompareEnum operator : CompareEnum.values()) {
|
||||
if (operator.getValue().equals(value)) {
|
||||
return operator;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有比较操作符列表
|
||||
* @return 比较操作符列表
|
||||
*/
|
||||
public static List<CompareEnum> getAll() {
|
||||
return Arrays.asList(CompareEnum.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为范围比较操作符
|
||||
* @param value 操作符值
|
||||
* @return 是否为范围比较
|
||||
*/
|
||||
public static boolean isRangeOperator(String value) {
|
||||
CompareEnum operator = getByValue(value);
|
||||
return operator != null && !operator.isSingleValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
public enum ConnectionCloseReason {
|
||||
NROMAL,
|
||||
TIMEOUT,
|
||||
REPEAT
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
public enum ConnectorStatus {
|
||||
ACTIVE, // 完全可用
|
||||
RECEIVER, // 禁言(不可发送但可以接收)
|
||||
DISABLED // 完全不可用
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
public enum ConnectorType {
|
||||
MASTER, // 上位机(主动绑定别人)
|
||||
SLAVE, // 下位机(只能被动绑定)
|
||||
GENERAL // 不区分角色(将来扩展用)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DeviceStatus {
|
||||
|
||||
|
||||
online(1, "online", "在线"),
|
||||
offline(0, "offline", "离线");
|
||||
|
||||
private int type;
|
||||
private String code;
|
||||
private String description;
|
||||
|
||||
public static DeviceStatus convert(int type) {
|
||||
for (DeviceStatus value : DeviceStatus.values()) {
|
||||
if (value.type == type) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DeviceStatus convert(String code) {
|
||||
for (DeviceStatus value : DeviceStatus.values()) {
|
||||
if (value.code.equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.fastbee.enums;
|
||||
|
||||
public enum RespondCode {
|
||||
/**
|
||||
* 账号已登录
|
||||
*/
|
||||
have_logged_in,
|
||||
|
||||
/**
|
||||
* 设备状态改变
|
||||
*/
|
||||
device_status_changed,
|
||||
/**
|
||||
* 设备错误推送
|
||||
*/
|
||||
device_error_push
|
||||
}
|
||||
103
maibu-netty-server/src/main/java/com/maibu/init/InitThread.java
Normal file
103
maibu-netty-server/src/main/java/com/maibu/init/InitThread.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.fastbee.init;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.domain.entity.UserClient;
|
||||
import com.fastbee.framework.mybatis.LambdaQueryWrapperX;
|
||||
import com.fastbee.iot.domain.DevicePlan;
|
||||
import com.fastbee.iot.domain.DevicePlanTask;
|
||||
import com.fastbee.iot.enums.DeviceTaskStaus;
|
||||
import com.fastbee.iot.mapper.DevicePlanMapper;
|
||||
import com.fastbee.iot.mapper.DevicePlanTaskMapper;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import com.fastbee.service.DevicePlanTaskMonitorService;
|
||||
import com.fastbee.service.DeviceThreadService;
|
||||
import com.fastbee.system.mapper.SysUserClientMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(20)
|
||||
public class InitThread implements ApplicationRunner {
|
||||
|
||||
|
||||
@Resource(name = FastBeeConstant.TASK.DEVICE_ERROR_MONITOR)
|
||||
private Executor deviceErrorMonitorExecutor;
|
||||
|
||||
@Resource(name = FastBeeConstant.TASK.DEVICE_TASK_HANDLER)
|
||||
private Executor deviceTaskHandlerExecutor;
|
||||
|
||||
@Autowired
|
||||
private DeviceThreadService deviceThreadService;
|
||||
|
||||
@Autowired
|
||||
private DevicePlanTaskMonitorService devicePlanTaskMonitorService;
|
||||
|
||||
@Autowired
|
||||
private DevicePlanMapper devicePlanMapper;
|
||||
|
||||
@Autowired
|
||||
private SysUserClientMapper sysUserClientMapper;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
recoverMemory();
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
private void init() {
|
||||
//设备错误监听线程
|
||||
deviceErrorMonitorExecutor.execute(() -> {
|
||||
try {
|
||||
deviceThreadService.deviceErrorMonitor();
|
||||
} catch (InterruptedException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
//设备任务处理线程
|
||||
deviceTaskHandlerExecutor.execute(() -> {
|
||||
//任务下发
|
||||
try {
|
||||
devicePlanTaskMonitorService.generatePlanTask();
|
||||
//执行准备队列
|
||||
devicePlanTaskMonitorService.doLoop();
|
||||
//执行任务
|
||||
devicePlanTaskMonitorService.doExecute();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复内存
|
||||
*/
|
||||
public void recoverMemory(){
|
||||
//todo 启动时清除当前登录记录
|
||||
// System.out.println("开始清除登录信息");
|
||||
// sysUserClientMapper.clearDeviceName();
|
||||
// 未执行完的重复计划
|
||||
LambdaQueryWrapperX<DevicePlan> query = new LambdaQueryWrapperX<>();
|
||||
List<String> list = Arrays.asList(DeviceTaskStaus.NEW.getCode(),DeviceTaskStaus.EXECUTING.getCode(),DeviceTaskStaus.PAUSE.getCode());
|
||||
query.in(DevicePlan::getTaskStaus, list);
|
||||
List<DevicePlan> devicePlanList = devicePlanMapper.selectList(query);
|
||||
if(!CollectionUtils.isEmpty(devicePlanList)){
|
||||
devicePlanList.forEach(GlobalMemory::addDevicePlan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.fastbee.manager;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fastbee.dto.Connector;
|
||||
import com.fastbee.dto.NettyDevice;
|
||||
import com.fastbee.enums.ConnectorStatus;
|
||||
import com.fastbee.enums.ConnectorType;
|
||||
import com.fastbee.netty.handler.CommandForwardHandler;
|
||||
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 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.fastbee.mapper;
|
||||
|
||||
import com.fastbee.entity.ErrorIdentificationStandard;
|
||||
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* 设备历史状态
|
||||
*/
|
||||
@Repository
|
||||
public interface ErrorIdentificationStandardMapper extends BaseMapperX<ErrorIdentificationStandard> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.fastbee.memory;
|
||||
|
||||
import com.fastbee.entity.ErrorIdentificationStandard;
|
||||
import com.fastbee.iot.domain.DevicePlan;
|
||||
import com.fastbee.iot.domain.DevicePlanTask;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@Data
|
||||
public class GlobalMemory {
|
||||
|
||||
public List<ErrorIdentificationStandard> standard = null;
|
||||
|
||||
//key deviceId
|
||||
public static ConcurrentHashMap<String, List<DevicePlan>> devicePlanMap = new ConcurrentHashMap<>();
|
||||
//key deviceId prepare
|
||||
public static ConcurrentHashMap<String, List<DevicePlanTask>> devicePlanTaskPrepareMap = new ConcurrentHashMap<>();
|
||||
//key deviceId execute
|
||||
public static ConcurrentHashMap<String, DevicePlanTask> devicePlanTaskExecuteMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 设备当前控制权在哪里key->deviceId value:userName:app/web/pc:token
|
||||
// public static final ConcurrentHashMap<String, String> controlLockMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public static void addDevicePlan(DevicePlan devicePlan) {
|
||||
if (devicePlan != null && !StringUtils.isEmpty(devicePlan.getDeviceId())) {
|
||||
String deviceId = devicePlan.getDeviceId();
|
||||
List<DevicePlan> list = devicePlanMap.get(deviceId);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
boolean exists = list.stream()
|
||||
.anyMatch(l -> Objects.equals(l.getId(), devicePlan.getId()));
|
||||
if (!exists) {
|
||||
list.add(devicePlan);
|
||||
}
|
||||
devicePlanMap.put(deviceId, list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void removeDevicePlan(DevicePlan devicePlan) {
|
||||
if (devicePlan != null && !StringUtils.isEmpty(devicePlan.getDeviceId())) {
|
||||
String deviceId = devicePlan.getDeviceId();
|
||||
List<DevicePlan> list = devicePlanMap.get(deviceId);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
list.removeIf(x -> x.getId().equals(devicePlan.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addDevicePlanPrepare(DevicePlanTask devicePlanTask) {
|
||||
if (devicePlanTask != null && !StringUtils.isEmpty(devicePlanTask.getDeviceId())) {
|
||||
String deviceId = devicePlanTask.getDeviceId();
|
||||
List<DevicePlanTask> list = devicePlanTaskPrepareMap.get(deviceId);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
list.add(devicePlanTask);
|
||||
devicePlanTaskPrepareMap.put(deviceId, list);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeDevicePlanPrepare(DevicePlanTask devicePlanTask) {
|
||||
if (devicePlanTask != null && !StringUtils.isEmpty(devicePlanTask.getDeviceId())) {
|
||||
String deviceId = devicePlanTask.getDeviceId();
|
||||
List<DevicePlanTask> list = devicePlanTaskPrepareMap.get(deviceId);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
list.removeIf(x -> x.getId().equals(devicePlanTask.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void addDevicePlanExecute(DevicePlanTask devicePlanTask) {
|
||||
if (devicePlanTask != null && !StringUtils.isEmpty(devicePlanTask.getDeviceId())) {
|
||||
String deviceId = devicePlanTask.getDeviceId();
|
||||
devicePlanTaskExecuteMap.put(deviceId, devicePlanTask);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeDevicePlanExecute(String deviceId) {
|
||||
if (!StringUtils.isEmpty(deviceId)) {
|
||||
devicePlanTaskExecuteMap.remove(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.fastbee.netty;
|
||||
|
||||
import com.fastbee.netty.handler.*;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class NettyServer {
|
||||
|
||||
private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
private EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
|
||||
@Autowired
|
||||
private CommandForwardHandler commandForwardHandler;
|
||||
|
||||
@Autowired
|
||||
private HeartbeatHandler heartbeatHandler;
|
||||
|
||||
@Autowired
|
||||
private DeviceConnectHandler deviceConnectHandler;
|
||||
|
||||
@Autowired
|
||||
private DataToDataBaseHandler dataToDataBaseHandler;
|
||||
|
||||
public void start(){
|
||||
try {
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(ChannelOption.SO_BACKLOG, 1024)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
|
||||
pipeline.addLast(new HeaderFooterDecoder());
|
||||
pipeline.addLast(new HexDecoder());
|
||||
pipeline.addLast(new HexEncoder());
|
||||
pipeline.addLast(deviceConnectHandler);
|
||||
pipeline.addLast(heartbeatHandler);
|
||||
pipeline.addLast(dataToDataBaseHandler);
|
||||
pipeline.addLast(commandForwardHandler);
|
||||
}
|
||||
});
|
||||
System.out.println("聊天室服务器启动了...");
|
||||
ChannelFuture channelFuture = serverBootstrap.bind(9001).sync();
|
||||
channelFuture.channel().closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
import com.fastbee.common.CommandConstant;
|
||||
import com.fastbee.common.Constant;
|
||||
import com.fastbee.dto.Connector;
|
||||
import com.fastbee.dto.NettyDevice;
|
||||
import com.fastbee.enums.ConnectorType;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class CommandForwardHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandForwardHandler.class);
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager deviceSessionManager;
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
Channel currentChannel = ctx.channel();
|
||||
byte[] bytes = Hex.decodeHex(msg.toCharArray());
|
||||
if (bytes.length > 3 && (bytes[2] == CommandConstant.remoteControl || bytes[2] == CommandConstant.path || bytes[2] == CommandConstant.status)) {
|
||||
String senderDeviceId = deviceSessionManager.getDeviceIdByChannel(currentChannel);
|
||||
NettyDevice senderNettyDevice =
|
||||
senderDeviceId != null ? deviceSessionManager.getDevice(senderDeviceId) : null;
|
||||
|
||||
if (senderNettyDevice == null) {
|
||||
logger.debug("发送拒绝,设备:{}未发送上线指令", senderDeviceId);
|
||||
ByteBuf buf = ctx.alloc().buffer();
|
||||
buf.writeShort(0xABAA);
|
||||
buf.writeByte(0x03);
|
||||
buf.writeShort(0x00);
|
||||
buf.writeShort(0x00);
|
||||
buf.writeShort(0xAAAB);
|
||||
currentChannel.writeAndFlush(buf);
|
||||
logger.debug("已重新请求设备连接");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!senderNettyDevice.canSend()) {
|
||||
logger.debug("发送拒绝:设备:{}已处于禁止发送状态", senderDeviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConnectorType.MASTER.equals(senderNettyDevice.getDeviceType())) {
|
||||
Set<Connector> connectedDevices = senderNettyDevice.getConnectedConnectors();
|
||||
if (connectedDevices == null || connectedDevices.isEmpty()) {
|
||||
logger.debug("发送拒绝master:{},该设备没有关联的设备", senderDeviceId);
|
||||
return;
|
||||
}
|
||||
for (Connector target : connectedDevices) {
|
||||
if (!target.isOnline()) {
|
||||
logger.debug("发送拒绝master:{},消息到达的salve设备:{},已离线", senderDeviceId, target.getConnectorId());
|
||||
continue;
|
||||
}
|
||||
if (!target.canReceive()) {
|
||||
logger.debug("发送拒绝master:{},消息到达的slave设备:{},处于禁止接收状态", senderDeviceId, target.getConnectorId());
|
||||
continue;
|
||||
}
|
||||
Channel targetChannel = target.getChannel();
|
||||
if (targetChannel != null && targetChannel.isActive()) {
|
||||
targetChannel.writeAndFlush(msg);
|
||||
logger.debug("从master:{},向slave:{}, 转发消息:{}", senderDeviceId, target.getConnectorId(), msg);
|
||||
}
|
||||
}
|
||||
} else if (ConnectorType.SLAVE.equals(senderNettyDevice.getDeviceType())) {
|
||||
List<NettyDevice> masters = deviceSessionManager.getMastersBySlaveId(senderDeviceId);
|
||||
if (CollectionUtils.isEmpty(masters)) {
|
||||
logger.debug("发送拒绝slave:{},该设备没有关联的master设备", senderDeviceId);
|
||||
return;
|
||||
}
|
||||
for (NettyDevice master : masters) {
|
||||
if (!master.isOnline()) {
|
||||
logger.debug("发送拒绝slave:{},消息到达的master{},设备已离线 ", senderDeviceId, master.getConnectorId());
|
||||
continue;
|
||||
}
|
||||
if (!master.canReceive()) {
|
||||
logger.debug("发送拒绝slave:{},消息到达的master{},处于禁止接收状态 ", senderDeviceId, master.getConnectorId());
|
||||
continue;
|
||||
}
|
||||
Channel targetChannel = master.getChannel();
|
||||
if (targetChannel != null && targetChannel.isActive()) {
|
||||
targetChannel.writeAndFlush(msg);
|
||||
logger.debug("从slave:{},向master:{}, 转发消息:{}", senderDeviceId, master.getConnectorId(), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.fireChannelRead(msg); // 不是控制指令,继续传递给其他Handler
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
import com.fastbee.common.CommandConstant;
|
||||
import com.fastbee.common.NettyCacheKey;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.dto.NettyDevice;
|
||||
import com.fastbee.iot.domain.DeviceRunStatistics;
|
||||
import com.fastbee.iot.domain.DeviceRunningStatusHistory;
|
||||
import com.fastbee.iot.domain.DeviceStatusRecordDTO;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class DataToDataBaseHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager deviceSessionManager;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
private static final Long interval = 5000L;
|
||||
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
Channel channel = ctx.channel();
|
||||
String deviceID = deviceSessionManager.getDeviceIdByChannel(channel);
|
||||
if (StringUtils.isEmpty(deviceID)) return;
|
||||
|
||||
byte[] bytes = Hex.decodeHex(msg.toCharArray());
|
||||
if (bytes[2] == CommandConstant.status) {
|
||||
String key = NettyCacheKey.deviceRunningStatusKey + deviceID;
|
||||
DeviceStatusRecordDTO recordDTO = redisCache.getCacheObject(key);
|
||||
boolean record = false;
|
||||
List<DeviceRunningStatusHistory> histories;
|
||||
if (recordDTO == null) {
|
||||
recordDTO = new DeviceStatusRecordDTO();
|
||||
histories = new ArrayList<>();
|
||||
record = true;
|
||||
} else {
|
||||
long lastRecordTime = recordDTO.getLastRecordTime();
|
||||
histories = recordDTO.getHistories();
|
||||
if (System.currentTimeMillis() - lastRecordTime >= interval) {
|
||||
record = true;
|
||||
}
|
||||
}
|
||||
if (record) {
|
||||
String deviceStatus = new String(bytes, 2, bytes.length - 4, StandardCharsets.UTF_8).trim();
|
||||
String[] split = deviceStatus.split(",");
|
||||
|
||||
DeviceRunningStatusHistory deviceRunningStatus = getDeviceRunningStatusHistory(deviceID, split);
|
||||
if (histories == null) {
|
||||
histories = new ArrayList<>();
|
||||
}
|
||||
histories.add(deviceRunningStatus);
|
||||
recordDTO.setHistories(histories);
|
||||
recordDTO.setLastRecordTime(System.currentTimeMillis());
|
||||
|
||||
redisCache.setCacheObject(key, recordDTO);
|
||||
// 判断整点 记录到数据库
|
||||
// deviceStatusHistoryMapper.insert(deviceRunningStatus);
|
||||
NettyDevice nettyDevice = deviceSessionManager.getDevice(deviceID);
|
||||
String workArea = deviceRunningStatus.getWorkingArea();
|
||||
DeviceRunStatistics latestStatus;
|
||||
if(nettyDevice.getLatestStatus() == null){
|
||||
latestStatus = new DeviceRunStatistics();
|
||||
latestStatus.setStartTime(LocalDateTime.now());
|
||||
latestStatus.setDeviceId(deviceID);
|
||||
}else {
|
||||
latestStatus = nettyDevice.getLatestStatus();
|
||||
// 使用Duration.between计算两个时间点之间的持续时间
|
||||
Duration duration = Duration.between(latestStatus.getStartTime(), LocalDateTime.now());
|
||||
long minutes = duration.toMinutes();
|
||||
latestStatus.setTime(minutes);
|
||||
}
|
||||
latestStatus.setWorkArea(Double.valueOf(workArea));
|
||||
nettyDevice.setLatestStatus(latestStatus);
|
||||
}
|
||||
// else{
|
||||
// System.out.println(false);
|
||||
// }
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
private DeviceRunningStatusHistory getDeviceRunningStatusHistory(String deviceID, String[] split) {
|
||||
DeviceRunningStatusHistory deviceRunningStatus = new DeviceRunningStatusHistory();
|
||||
deviceRunningStatus.setDeviceId(deviceID);
|
||||
deviceRunningStatus.setVoltage(split[0]);
|
||||
deviceRunningStatus.setLeftTargetSpeed(split[1]);
|
||||
deviceRunningStatus.setRightTargetSpeed(split[2]);
|
||||
deviceRunningStatus.setLeftMeasureSpeed(split[3]);
|
||||
deviceRunningStatus.setRightMeasureSpeed(split[4]);
|
||||
deviceRunningStatus.setLeftCurrent(split[5]);
|
||||
deviceRunningStatus.setRightCurrent(split[6]);
|
||||
deviceRunningStatus.setLeftMotorTemp(split[7]);
|
||||
deviceRunningStatus.setRightMotorTemp(split[8]);
|
||||
deviceRunningStatus.setChipTemp(split[9]);
|
||||
deviceRunningStatus.setYaw(split[10]);
|
||||
deviceRunningStatus.setPitch(split[11]);
|
||||
deviceRunningStatus.setRoll(split[12]);
|
||||
deviceRunningStatus.setSatelliteCnt(split[13]);
|
||||
deviceRunningStatus.setQual(split[14]);
|
||||
deviceRunningStatus.setHeadingStatus(split[15]);
|
||||
deviceRunningStatus.setLatitude(split[16]);
|
||||
deviceRunningStatus.setLongitude(split[17]);
|
||||
|
||||
//18 时间暂未给值
|
||||
|
||||
deviceRunningStatus.setCuttingSpeed(split[19]);
|
||||
deviceRunningStatus.setControlMode(split[20]);
|
||||
deviceRunningStatus.setBattery(split[21]);
|
||||
deviceRunningStatus.setWorkingArea(split[22]);
|
||||
deviceRunningStatus.setObstacleSign(split[23]);
|
||||
|
||||
deviceRunningStatus.setCreateTime(new Date());
|
||||
return deviceRunningStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
import com.fastbee.common.CommandConstant;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.domain.entity.UserClient;
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
import com.fastbee.dto.*;
|
||||
import com.fastbee.entity.path.LatAndLngEntity;
|
||||
import com.fastbee.enums.*;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.mapper.DeviceRunStatisticsMapper;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import com.fastbee.system.service.ISysUserClientService;
|
||||
import com.fastbee.system.service.ISysUserService;
|
||||
import com.fastbee.utils.CommandUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class DeviceConnectHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DeviceConnectHandler.class);
|
||||
|
||||
public static final AttributeKey<String> ATT_DEVICE_ID = AttributeKey.valueOf("deviceId");
|
||||
public static final AttributeKey<ConnectionCloseReason> ATT_CLOSE_REASON = AttributeKey.valueOf("closeReason");
|
||||
|
||||
//记录切换控制权请求来源 key 是请求的masterId,value是控制当前设备的主机
|
||||
private final ConcurrentHashMap<String, String> controlRequestMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private final DeviceSessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private ISysUserClientService sysUserClientService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private DeviceRunStatisticsMapper deviceRunStatisticsMapper;
|
||||
|
||||
public DeviceConnectHandler(DeviceSessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
logger.info("Channel active: {}", ctx.channel().remoteAddress());
|
||||
ctx.fireChannelActive();
|
||||
|
||||
//todo 更新单次统计消息
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
Channel channel = ctx.channel();
|
||||
ConnectionCloseReason reason = channel.attr(ATT_CLOSE_REASON).get();
|
||||
String deviceId = channel.attr(ATT_DEVICE_ID).get();
|
||||
|
||||
if (reason == ConnectionCloseReason.REPEAT) {
|
||||
logger.warn("重复连接断开: {}", channel.remoteAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
if (deviceId != null) {
|
||||
NettyDevice nettyDevice = sessionManager.getDevice(deviceId);
|
||||
if (nettyDevice != null && nettyDevice.getDeviceType() == ConnectorType.MASTER) {
|
||||
nettyDevice.setConnectorStatus(ConnectorStatus.DISABLED);
|
||||
nettyDevice.setCurrentSlaveId(null);
|
||||
sysUserClientService.updateAliveStatus(deviceId, "", 0);
|
||||
logger.info("{}: 设备断开 status: {}", deviceId, ConnectorStatus.DISABLED);
|
||||
// updateLogoutSingle(deviceId);
|
||||
//todo 自动切换到其他?
|
||||
}
|
||||
if (nettyDevice != null && nettyDevice.getDeviceType() == ConnectorType.SLAVE) {
|
||||
nettyDevice.setOnlineStatus(0);
|
||||
//推送json 找到设备绑定的主机进行推送
|
||||
List<NettyDevice> controlMasters = sessionManager.getAllSlaveControl(deviceId);
|
||||
if (!CollectionUtils.isEmpty(controlMasters)) {
|
||||
controlMasters.forEach(x -> {
|
||||
DeviceStatusChangeDTO changeDTO = new DeviceStatusChangeDTO();
|
||||
changeDTO.setDeviceId(deviceId);
|
||||
changeDTO.setStatus(DeviceStatus.offline.getCode());
|
||||
changeDTO.setEvent(RespondCode.device_status_changed);
|
||||
ByteBuf buf = ctx.alloc().buffer();
|
||||
CommandUtils.buildCommand(buf, changeDTO, CommandConstant.interaction);
|
||||
String content = buf.toString(CharsetUtil.UTF_8);
|
||||
logger.info("推送下位机离线状态消息:{}", content);
|
||||
if (x.getChannel() != null && x.getChannel().isActive()) {
|
||||
x.getChannel().writeAndFlush(buf);
|
||||
}
|
||||
});
|
||||
}
|
||||
Device device = deviceService.selectDeviceBySerialNumber(deviceId);
|
||||
if (device != null) {
|
||||
device.setOnlineStatus(DeviceStatus.offline.getType());
|
||||
device.setUpdateTime(new Date());
|
||||
deviceService.updateDeviceBySelf(device);
|
||||
//todo 更新历史作业消息
|
||||
}
|
||||
if (nettyDevice.getLatestStatus() != null) {
|
||||
nettyDevice.getLatestStatus().setEndTime(LocalDateTime.now());
|
||||
deviceRunStatisticsMapper.insert(nettyDevice.getLatestStatus());
|
||||
}
|
||||
}
|
||||
sessionManager.removeDevice(deviceId);
|
||||
logger.info("{}: 设备断开连接,地址: {}", deviceId, channel.remoteAddress());
|
||||
} else {
|
||||
logger.warn("未知设备断开连接: {}", channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
try {
|
||||
byte[] bytes = Hex.decodeHex(msg.toCharArray());
|
||||
|
||||
if (bytes.length < 5) {
|
||||
logger.warn("收到无效数据包,长度过短");
|
||||
return;
|
||||
}
|
||||
byte cmdType = bytes[2];
|
||||
if (cmdType == CommandConstant.connect) {
|
||||
// 下位机连接
|
||||
String data = new String(bytes, 2, bytes.length - 4, StandardCharsets.UTF_8).trim();
|
||||
logger.info("{}: 03指令", data);
|
||||
if (!StringUtils.isEmpty(data)) {
|
||||
if (data.contains(":")) {
|
||||
logger.info("上位机连接 data:{}", data);
|
||||
//判断 是否更新连接了 更新连接就返回失败
|
||||
String[] parts = data.split(":");
|
||||
if (parts.length == 3) {
|
||||
String deviceId = parts[0] + ":" + parts[1];
|
||||
String sessionId = parts[2];
|
||||
String userName = parts[0];
|
||||
logger.info("上位机连接 deviceId: {}", deviceId);
|
||||
logger.info("上位机连接 sessionId: {}", sessionId);
|
||||
logger.info("上位机连接 userName: {}", userName);
|
||||
logger.info("上位机连接 type: {}", parts[1]);
|
||||
SysUser sysUser = userService.selectUserByUserName(userName);
|
||||
if (sysUser != null) {
|
||||
sysUserClientService.insertUserClient(sysUser.getUserId(), userName, deviceId, "", 0);
|
||||
}
|
||||
//如果 当前角色存在sessionId同时和当前的穿过来不同则下发剔除指令
|
||||
boolean t = false;
|
||||
if (StringUtils.isEmpty(sessionId) || sysUser == null) {
|
||||
t = true;
|
||||
}
|
||||
if (sysUser != null && !StringUtils.isEmpty(sessionId) && !StringUtils.isEmpty(sysUser.getSessionId())) {
|
||||
if (!sysUser.getSessionId().equals(sessionId)) {
|
||||
t = true;
|
||||
}
|
||||
}
|
||||
if (t) {
|
||||
//发送已登录状态到客户端
|
||||
if ("web".equals(parts[1])) {
|
||||
NettyDevice device = sessionManager.getDevice(deviceId);
|
||||
logger.info("web 上位机连接重连: {}", JsonUtils.toJsonString(device));
|
||||
if (device != null && !StringUtils.isEmpty(device.getCurrentSlaveId())) {
|
||||
logger.info("上位机连接重连: {}", deviceId);
|
||||
} else {
|
||||
//判断是否账号在控制空 如果在控制设置账号为非active
|
||||
List<UserClient> userClients = sysUserClientService.selectUserClientByUserName(userName);
|
||||
ConnectorStatus status = ConnectorStatus.ACTIVE;
|
||||
logger.info("web 上位机连接重连2: {}", JsonUtils.toJsonString(userClients));
|
||||
UserClient nowClient = null;
|
||||
if (!CollectionUtils.isEmpty(userClients)) {
|
||||
long count = userClients.stream().filter(x ->
|
||||
x.getIsAlive() == 1 && !deviceId.equals(x.getClientName())).count();
|
||||
if (count > 0) {
|
||||
status = ConnectorStatus.RECEIVER;
|
||||
}
|
||||
nowClient = userClients.stream().filter(x ->
|
||||
x.getIsAlive() == 1 && deviceId.equals(x.getClientName())).findFirst().orElse(null);
|
||||
}
|
||||
logger.info("web 上位机连接重连3: {}", JsonUtils.toJsonString(status));
|
||||
if (!ConnectorStatus.ACTIVE.equals(status)) {
|
||||
handleMasterConnect(ctx, deviceId, null, status);
|
||||
} else {
|
||||
logger.info("web 上位机连接重连4: {}", JsonUtils.toJsonString(nowClient));
|
||||
if (nowClient != null) {
|
||||
handleMasterConnect(ctx, deviceId, nowClient.getDeviceName(), status);
|
||||
} else {
|
||||
handleMasterConnect(ctx, deviceId, null, status);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DeviceLoginResponseDTO responseDTO = new DeviceLoginResponseDTO();
|
||||
responseDTO.setRespond(RespondCode.have_logged_in);
|
||||
ByteBuf buf = ctx.alloc().buffer();
|
||||
CommandUtils.buildCommand(buf, responseDTO, CommandConstant.interaction);
|
||||
ctx.channel().writeAndFlush(buf);
|
||||
}
|
||||
} else {
|
||||
//判断是否账号在控制空 如果在控制设置账号为非active
|
||||
List<UserClient> userClients = sysUserClientService.selectUserClientByUserName(userName);
|
||||
ConnectorStatus status = ConnectorStatus.ACTIVE;
|
||||
if (!CollectionUtils.isEmpty(userClients)) {
|
||||
long count = userClients.stream().filter(x ->
|
||||
x.getIsAlive() == 1 && !deviceId.equals(x.getClientName())).count();
|
||||
if (count > 0) {
|
||||
status = ConnectorStatus.RECEIVER;
|
||||
}
|
||||
}
|
||||
logger.info("上位机上线:{}", status);
|
||||
// if (ConnectorStatus.ACTIVE.equals(status)) {
|
||||
// //todo 设置当前控制的token
|
||||
// sysUser.setSessionId(sessionId);
|
||||
// userService.updateUserProfile(sysUser);
|
||||
// }
|
||||
handleMasterConnect(ctx, deviceId, null, status);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleSlaveConnect(ctx, data);
|
||||
logger.info("{}: 下位机连接", data);
|
||||
Device device = deviceService.selectDeviceBySerialNumber(data);
|
||||
if (device == null) {
|
||||
device = new Device();
|
||||
device.setStatus(3);
|
||||
device.setOnlineStatus(DeviceStatus.online.getType());
|
||||
device.setSerialNumber(data);
|
||||
device.setDeviceName(data);
|
||||
device.setProductId(137L);
|
||||
device.setTenantId(-1L);
|
||||
device.setFirmwareVersion(BigDecimal.valueOf(1.0));
|
||||
device.setProductName("割草机产品MC700");
|
||||
device.setCreateTime(new Date());
|
||||
device.setUpdateTime(new Date());
|
||||
deviceService.insertDeviceBySelf(device);
|
||||
} else {
|
||||
device.setStatus(3);
|
||||
device.setOnlineStatus(DeviceStatus.online.getType());
|
||||
device.setUpdateTime(new Date());
|
||||
deviceService.updateDeviceBySelf(device);
|
||||
}
|
||||
NettyDevice slaveDevice = sessionManager.getDevice(data);
|
||||
if (slaveDevice != null) {
|
||||
slaveDevice.setOnlineStatus(1);
|
||||
}
|
||||
//推送json 找到设备绑定的主机进行推送
|
||||
List<NettyDevice> controlMasters = sessionManager.getAllSlaveControl(device.getSerialNumber());
|
||||
if (!CollectionUtils.isEmpty(controlMasters)) {
|
||||
controlMasters.forEach(x -> {
|
||||
DeviceStatusChangeDTO changeDTO = new DeviceStatusChangeDTO();
|
||||
changeDTO.setDeviceId(data);
|
||||
changeDTO.setStatus(DeviceStatus.online.getCode());
|
||||
changeDTO.setEvent(RespondCode.device_status_changed);
|
||||
ByteBuf buf = ctx.alloc().buffer();
|
||||
CommandUtils.buildCommand(buf, changeDTO, CommandConstant.interaction);
|
||||
String content = buf.toString(CharsetUtil.UTF_8);
|
||||
logger.info("推送下位机登录状态消息:{}", content);
|
||||
if (x.getChannel() != null && x.getChannel().isActive()) {
|
||||
x.getChannel().writeAndFlush(buf);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cmdType == CommandConstant.interaction) {
|
||||
String data = new String(bytes, 2, bytes.length - 4, StandardCharsets.UTF_8).trim();
|
||||
logger.info("12指令 :{}", data);
|
||||
DeviceRequestDTO requestDTO = JsonUtils.parseObject(data, DeviceRequestDTO.class);
|
||||
logger.info("12指令解析结果 :{}", JsonUtils.toJsonString(requestDTO));
|
||||
if (requestDTO != null) {
|
||||
if (requestDTO.getRequest() != null) {
|
||||
logger.info("12指令解析,请求转发");
|
||||
requestDispatch(requestDTO, bytes, ctx);
|
||||
} else {
|
||||
if (requestDTO.getRespond() != null) {
|
||||
logger.info("12指令解析,切换权限");
|
||||
responseDispatch(requestDTO.getRespond(), bytes);
|
||||
} else {
|
||||
logger.info("权限切换参数错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// else if (cmdType == CommandConstant.path) {
|
||||
// Channel channel = ctx.channel();
|
||||
// String deviceId = channel.attr(ATT_DEVICE_ID).get();
|
||||
// NettyDevice device = sessionManager.getDevice(deviceId);
|
||||
// if(CollectionUtils.isEmpty(device.getLocationQueue())) return;
|
||||
// Queue<LatAndLngEntity> locationQueue = device.getLocationQueue();
|
||||
//
|
||||
// }
|
||||
ctx.fireChannelRead(msg); // 如有其他 handler 需要处理
|
||||
} catch (Exception e) {
|
||||
logger.error("deviceConnect channelRead:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理反馈 待扩展
|
||||
*/
|
||||
private void responseDispatch(DeviceRespondDTO respond, byte[] bytes) {
|
||||
Boolean switchResult = respond.getSwitchResult();
|
||||
String slaveId = respond.getDeviceId();
|
||||
//找到请求的 设备 他这个是先记录切换请求做个绑定关系,在同意或者反对时能直接找到对应 请求的设备 直接发送报文
|
||||
String masterId = getRequestMaster(slaveId);
|
||||
if (switchResult) {
|
||||
List<NettyDevice> controlList = sessionManager.getSlaveControl(slaveId);
|
||||
NettyDevice masterDevice = sessionManager.getDevice(masterId);
|
||||
NettyDevice slaveDevice = sessionManager.getDevice(slaveId);
|
||||
if (!CollectionUtils.isEmpty(controlList)) {
|
||||
//如果当前设备有控制
|
||||
controlList.forEach(c -> {
|
||||
logger.info("12指令切换权限,c:{}", c.getConnectorId());
|
||||
if (!c.getConnectorId().equals(masterId)) {
|
||||
// c.setCurrentSlaveId(null);
|
||||
c.removeConnectedDevice(slaveDevice);
|
||||
c.setConnectorStatus(ConnectorStatus.RECEIVER);
|
||||
sysUserClientService.updateAliveStatus(c.getConnectorId(), slaveId, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
logger.info("12指令解析,切换权限,slaveId:{},masterId :{}", slaveId, masterId);
|
||||
masterDevice.setCurrentSlaveId(slaveId);
|
||||
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
|
||||
masterDevice.addConnectedDevice(slaveDevice);
|
||||
sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
|
||||
} else {
|
||||
logger.info("权限切换请求被拒绝");
|
||||
}
|
||||
NettyDevice masterDevice = sessionManager.getDevice(masterId);
|
||||
controlRequestMap.remove(masterId);
|
||||
Channel channel = masterDevice.getChannel();
|
||||
channel.writeAndFlush(Unpooled.wrappedBuffer(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求目标设备的主机
|
||||
*/
|
||||
private String getRequestMaster(String slaveId) {
|
||||
return controlRequestMap.keySet().stream().filter(x -> controlRequestMap.get(x).equals(slaveId)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理转发0x12请求命令
|
||||
*/
|
||||
private void requestDispatch(DeviceRequestDTO requestDTO, byte[] bytes, ChannelHandlerContext ctx) {
|
||||
CommandRequestType request = requestDTO.getRequest();
|
||||
switch (request) {
|
||||
case switch_control:
|
||||
// 权限切换
|
||||
handlePermissionRequest(requestDTO, bytes, ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到当前设备控制的主机 转发控制请求
|
||||
*/
|
||||
private void handlePermissionRequest(DeviceRequestDTO requestDTO, byte[] bytes, ChannelHandlerContext ctx) {
|
||||
logger.info("转发控制请求 :{}", JsonUtils.toJsonString(requestDTO));
|
||||
String slaveId = requestDTO.getDeviceId();
|
||||
List<NettyDevice> controlList = sessionManager.getSlaveControl(slaveId);
|
||||
String masterId = requestDTO.getUserId() + ":" + requestDTO.getPlatform();
|
||||
logger.info("转发控制请求 controlList :{}", JsonUtils.toJsonString(controlList));
|
||||
if (!CollectionUtils.isEmpty(controlList)) {
|
||||
//如果当前设备有控制
|
||||
logger.info("转发控制请求 发送报文");
|
||||
NettyDevice controlDevice = controlList.get(0);
|
||||
if (controlDevice != null && controlDevice.getChannel() != null && controlDevice.getChannel().isActive()) {
|
||||
controlDevice.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
|
||||
logger.info("转发控制请求 发送报文结束 deviceId :{}", controlDevice.getConnectorId());
|
||||
controlRequestMap.put(masterId, slaveId);
|
||||
logger.info("转发控制请求 controlRequestMap:{}", JsonUtils.toJsonString(controlRequestMap));
|
||||
}
|
||||
|
||||
} else {
|
||||
//判断直接给权限 还是 判断是否有该账号其他端的的控制有权限
|
||||
NettyDevice activeDevice = sessionManager.getUserControl(requestDTO.getUserId());
|
||||
boolean sendMes = true;
|
||||
if (activeDevice == null) {
|
||||
sendMes = false;
|
||||
} else {
|
||||
if (activeDevice.getConnectorId().equals(masterId)) {
|
||||
sendMes = false;
|
||||
}
|
||||
}
|
||||
if (sendMes) {
|
||||
if (activeDevice.getChannel() != null && activeDevice.getChannel().isActive()) {
|
||||
activeDevice.getChannel().writeAndFlush(Unpooled.wrappedBuffer(bytes));
|
||||
logger.info("转发控制请求2 发送报文结束 deviceId :{}", activeDevice.getConnectorId());
|
||||
controlRequestMap.put(masterId, slaveId);
|
||||
logger.info("转发控制请求2 controlRequestMap:{}", JsonUtils.toJsonString(controlRequestMap));
|
||||
}
|
||||
} else {
|
||||
NettyDevice masterDevice = sessionManager.getDevice(masterId);
|
||||
if (masterDevice != null) {
|
||||
NettyDevice slaveDevice = sessionManager.getDevice(slaveId);
|
||||
masterDevice.setCurrentSlaveId(slaveId);
|
||||
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
|
||||
masterDevice.addConnectedDevice(slaveDevice);
|
||||
|
||||
sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
|
||||
if (masterDevice.getChannel() != null && masterDevice.getChannel().isActive()) {
|
||||
DeviceRequestDTO reDto = new DeviceRequestDTO();
|
||||
DeviceRespondDTO respond = new DeviceRespondDTO();
|
||||
respond.setSwitchResult(true);
|
||||
respond.setHolder("you");
|
||||
respond.setDeviceId(slaveId);
|
||||
reDto.setRespond(respond);
|
||||
ByteBuf buf = ctx.alloc().buffer();
|
||||
CommandUtils.buildCommand(buf, reDto, CommandConstant.interaction);
|
||||
String content = buf.toString(CharsetUtil.UTF_8);
|
||||
masterDevice.getChannel().writeAndFlush(buf);
|
||||
logger.info("转发控制请求设置当前控制2 发送报文:{} deviceId :{}", content, masterId);
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSlaveConnect(ChannelHandlerContext ctx, String slaveId) {
|
||||
ctx.channel().attr(ATT_DEVICE_ID).set(slaveId);
|
||||
sessionManager.registerDevice(slaveId, ctx.channel(), ConnectorType.SLAVE, ConnectorStatus.ACTIVE, null);
|
||||
}
|
||||
|
||||
private void handleMasterConnect(ChannelHandlerContext ctx, String masterId, String slaveId, ConnectorStatus status) {
|
||||
ctx.channel().attr(ATT_DEVICE_ID).set(masterId);
|
||||
sessionManager.registerDevice(masterId, ctx.channel(), ConnectorType.MASTER, status, slaveId);
|
||||
// sessionManager.bindConnector(masterId, slaveId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
String deviceId = ctx.channel().attr(ATT_DEVICE_ID).get();
|
||||
logger.error("异常设备 {} 出现错误: {}", deviceId, cause.getMessage(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
public class HeaderFooterDecoder extends ByteToMessageDecoder {
|
||||
private static final byte[] HEADER = {(byte) 0xAB, (byte) 0xAA};
|
||||
private static final byte[] FOOTER = {(byte) 0xAA, (byte) 0xAB};
|
||||
private static final int MAX_FRAME_LENGTH = 4096;
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
while (true) {
|
||||
int readerIndex = in.readerIndex();
|
||||
int writerIndex = in.writerIndex();
|
||||
|
||||
int headerPos = findPattern(in, HEADER, readerIndex);
|
||||
if (headerPos == -1) {
|
||||
// 没有包头,丢弃所有数据(防止残留字节误判)
|
||||
in.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
int searchStart = headerPos + HEADER.length;
|
||||
int footerPos = findPattern(in, FOOTER, searchStart);
|
||||
if (footerPos == -1) {
|
||||
// 没有完整帧,保留到下次
|
||||
in.readerIndex(headerPos);
|
||||
return;
|
||||
}
|
||||
|
||||
int frameLength = footerPos + FOOTER.length - headerPos;
|
||||
if (frameLength > MAX_FRAME_LENGTH) {
|
||||
in.readerIndex(footerPos + FOOTER.length);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 读取完整帧
|
||||
in.readerIndex(headerPos);
|
||||
ByteBuf frame = in.readRetainedSlice(frameLength);
|
||||
|
||||
byte[] frameBytes = new byte[frame.readableBytes()];
|
||||
frame.getBytes(frame.readerIndex(), frameBytes);
|
||||
|
||||
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
|
||||
+ " 收到原始数据:" + Hex.encodeHexString(frameBytes).toUpperCase());
|
||||
|
||||
out.add(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int findPattern(ByteBuf buf, byte[] pattern, int fromIndex) {
|
||||
for (int i = fromIndex; i <= buf.writerIndex() - pattern.length; i++) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; j++) {
|
||||
if (buf.getByte(i + j) != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
|
||||
import com.fastbee.common.Constant;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager sessionManager;
|
||||
|
||||
private static final byte[] HEARTBEAT_PACKET = {(byte) 0xAB, (byte) 0xAA, (byte) 0xFF, (byte) 0xAA, (byte) 0xAB};
|
||||
private static final int HEARTBEAT_INTERVAL = 2; // 5秒发一次心跳
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HeartbeatHandler.class);
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// 连接建立后启动定时任务,主动发心跳
|
||||
scheduleHeartbeat(ctx);
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
private void scheduleHeartbeat(ChannelHandlerContext ctx) {
|
||||
ctx.executor().scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (ctx.channel().isActive()) {
|
||||
String deviceId = ctx.channel().attr(Constant.ATT_DEVICE_ID).get();
|
||||
if (!StringUtils.isEmpty(deviceId)) {
|
||||
ByteBuf heartbeat = Unpooled.wrappedBuffer(HEARTBEAT_PACKET);
|
||||
ctx.writeAndFlush(heartbeat);
|
||||
logger.debug("发送心跳包给客户端:{},deviceId:{}", ctx.channel().remoteAddress(), deviceId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("heartbeat error", e);
|
||||
}
|
||||
}, 0, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof ByteBuf) {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
if (buf.readableBytes() == HEARTBEAT_PACKET.length) {
|
||||
byte[] data = new byte[HEARTBEAT_PACKET.length];
|
||||
buf.getBytes(0, data);
|
||||
if (Arrays.equals(data, HEARTBEAT_PACKET)) {
|
||||
String deviceId = ctx.channel().attr(Constant.ATT_DEVICE_ID).get();
|
||||
logger.debug("收到客户端心跳回复 deviceId:{}", deviceId);
|
||||
return; // 过滤心跳包
|
||||
}
|
||||
}
|
||||
}
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
// 处理 IdleState 事件
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent idleEvt = (IdleStateEvent) evt;
|
||||
if (idleEvt.state() == IdleState.READER_IDLE) {
|
||||
String deviceId = ctx.channel().attr(Constant.ATT_DEVICE_ID).get();
|
||||
logger.debug("超时未收到客户端心跳回复,断开连接,deviceId:{}", deviceId);
|
||||
String currentDeviceId = sessionManager.getDeviceIdByChannel(ctx.channel());
|
||||
if (currentDeviceId != null) {
|
||||
sessionManager.removeDevice(currentDeviceId);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
} else {
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
import com.fastbee.common.CommandConstant;
|
||||
import com.fastbee.common.Constant;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
// 解码器:ByteBuf → 16进制字符串
|
||||
@Slf4j
|
||||
public class HexDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HexDecoder.class);
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
|
||||
byte[] bytes = new byte[in.readableBytes()];
|
||||
in.readBytes(bytes);
|
||||
logger.debug("收到报文:{}", toHexString(bytes));
|
||||
out.add(Hex.encodeHexString(bytes)); // 转为16进制字符串
|
||||
// String now = LocalDateTime.now().format(FORMATTER);
|
||||
|
||||
Channel channel = ctx.channel();
|
||||
String deviceId = channel.attr(Constant.ATT_DEVICE_ID).get();
|
||||
if (bytes[2] == CommandConstant.connect) {
|
||||
logger.debug("收到连接请求:{}", Hex.encodeHexString(bytes));
|
||||
} else if (bytes[2] == CommandConstant.remoteControl) {
|
||||
logger.debug("收到:{}控制请求:{}", deviceId, Hex.encodeHexString(bytes));
|
||||
} else if (bytes[2] == CommandConstant.heartbeat) {
|
||||
logger.debug("收到:{}心跳包", deviceId);
|
||||
} else if (bytes[2] == CommandConstant.status) {
|
||||
logger.debug("收到:{}状态信息:{}", deviceId, Hex.encodeHexString(bytes));
|
||||
} else if (bytes[2] == CommandConstant.interaction) {
|
||||
logger.debug("收到:{}0x12状态消息推送:{}", deviceId, Hex.encodeHexString(bytes));
|
||||
} else if (bytes[2] == CommandConstant.path) {
|
||||
logger.debug("收到:{}路径规划消息推送:{}", deviceId, Hex.encodeHexString(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
private static String toHexString(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.fastbee.netty.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// 编码器:16进制字符串 → ByteBuf
|
||||
public class HexEncoder extends MessageToByteEncoder<String> {
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
|
||||
byte[] bytes = Hex.decodeHex(msg.toCharArray()); // 16进制字符串转字节数组
|
||||
out.writeBytes(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.fastbee.scheduled;
|
||||
|
||||
import com.fastbee.common.NettyCacheKey;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.iot.domain.DevicePlan;
|
||||
import com.fastbee.iot.domain.DeviceStatusRecordDTO;
|
||||
import com.fastbee.iot.mapper.DevicePlanMapper;
|
||||
import com.fastbee.iot.mapper.DeviceStatusHistoryMapper;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class ScheduledTask {
|
||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private DeviceStatusHistoryMapper deviceStatusHistoryMapper;
|
||||
|
||||
@Autowired
|
||||
private GlobalMemory globalMemory;
|
||||
|
||||
@Autowired
|
||||
private DevicePlanMapper devicePlanMapper;
|
||||
|
||||
|
||||
//整点更新设备状态
|
||||
@Scheduled(cron = "0 0 * * * ?")
|
||||
// @Scheduled(cron = "0 * * * * ?")
|
||||
public void executeHourlyTask() {
|
||||
System.out.println("整点定时任务执行时间: " + dateFormat.format(new Date()));
|
||||
String key = NettyCacheKey.deviceRunningStatusKey;
|
||||
// Collection<String> keys = redisCache.keys(key);
|
||||
Collection<String> keys = redisCache.getListKeyByPrefix(key);
|
||||
if (!CollectionUtils.isEmpty(keys)) {
|
||||
keys.forEach(k -> {
|
||||
DeviceStatusRecordDTO recordDTO = redisCache.getCacheObject(k);
|
||||
if (recordDTO != null && !CollectionUtils.isEmpty(recordDTO.getHistories())) {
|
||||
// recordDTO.getHistories().forEach(x -> {
|
||||
// deviceStatusHistoryMapper.insert(x);
|
||||
// });
|
||||
deviceStatusHistoryMapper.insertBatch(recordDTO.getHistories());
|
||||
}
|
||||
redisCache.deleteObject(k);
|
||||
});
|
||||
}
|
||||
System.out.println("任务执行完成");
|
||||
}
|
||||
|
||||
|
||||
//0点刷新任务创建状态
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
public void refreshDevicePlanTask() {
|
||||
System.out.println("0点定时刷新状态");
|
||||
Map<String, List<DevicePlan>> map = GlobalMemory.devicePlanMap;
|
||||
if (!CollectionUtils.isEmpty(map.keySet())) {
|
||||
map.values().forEach(x -> {
|
||||
if (!CollectionUtils.isEmpty(x)) {
|
||||
x.forEach(y -> {
|
||||
y.setDayTriggerFlag(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
System.out.println("0点定时刷新状态执行完成");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.fastbee.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
@Service
|
||||
public class DevicePathService {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package com.fastbee.service;
|
||||
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.NettyCacheKey;
|
||||
import com.fastbee.dto.DeviceErrorPushDTO;
|
||||
import com.fastbee.dto.DeviceErrorPushDetail;
|
||||
import com.fastbee.dto.NettyDevice;
|
||||
import com.fastbee.enums.RespondCode;
|
||||
import com.fastbee.iot.domain.*;
|
||||
import com.fastbee.iot.enums.DeviceTaskPlanRepeatType;
|
||||
import com.fastbee.iot.enums.DeviceTaskStaus;
|
||||
import com.fastbee.iot.mapper.DevicePlanMapper;
|
||||
import com.fastbee.iot.mapper.DevicePlanTaskMapper;
|
||||
import com.fastbee.iot.mapper.WorkRecordMapper;
|
||||
import com.fastbee.iot.model.WorkRecord;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DevicePlanTaskMonitorService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private GlobalMemory globalMemory;
|
||||
|
||||
@Autowired
|
||||
private DevicePlanTaskMapper devicePlanTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private DevicePlanMapper devicePlanMapper;
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager deviceSessionManager;
|
||||
|
||||
@Autowired
|
||||
private WorkRecordMapper workRecordMapper;
|
||||
|
||||
|
||||
/**
|
||||
* 计划迟的任务生成机器执行任务
|
||||
*/
|
||||
public void generatePlanTask() throws InterruptedException {
|
||||
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
Map<String, List<DevicePlan>> planMap = GlobalMemory.devicePlanMap;
|
||||
if (!CollectionUtils.isEmpty(planMap.values())) {
|
||||
Iterator<String> iterator = planMap.keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
String key = iterator.next();
|
||||
List<DevicePlan> list = planMap.get(key);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
list.forEach(this::createPlanTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("执行任务生成线程错误", e);
|
||||
}
|
||||
}, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void createPlanTask(DevicePlan devicePlan) {
|
||||
if (devicePlan.isDayTriggerFlag()) return; //当天应该创建的全部创建完
|
||||
DeviceTaskPlanRule rules = devicePlan.getDeviceTaskPlanRules();
|
||||
if (rules != null) {
|
||||
DeviceTaskPlanRepeatType type = rules.getType();
|
||||
switch (type) {
|
||||
case DAY:
|
||||
dayPlan(devicePlan);
|
||||
break;
|
||||
case WEEK:
|
||||
weekPlan(devicePlan);
|
||||
break;
|
||||
case MONTH:
|
||||
monthPlan(devicePlan);
|
||||
break;
|
||||
// case ONCE:
|
||||
// oncePlan(devicePlan);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dayPlan(DevicePlan devicePlan) {
|
||||
DeviceTaskPlanRule rules = devicePlan.getDeviceTaskPlanRules();
|
||||
LocalTime nowTime = LocalTime.now();
|
||||
LocalTime dayTime = LocalTime.parse(rules.getDayTime());
|
||||
if (nowTime.isAfter(dayTime)) {
|
||||
DevicePlanTask devicePlanTask = new DevicePlanTask();
|
||||
devicePlanTask.setPlanId(devicePlan.getId());
|
||||
devicePlanTask.setDeviceId(devicePlan.getDeviceId());
|
||||
devicePlanTask.setTaskStaus(DeviceTaskStaus.NEW);
|
||||
devicePlanTask.setRouteId(devicePlan.getRouteId());
|
||||
devicePlanTask.setCreateTime(LocalDateTime.now());
|
||||
devicePlanTask.setTaskType("");//待拓展任务类型
|
||||
devicePlanTaskMapper.insert(devicePlanTask);
|
||||
globalMemory.addDevicePlanPrepare(devicePlanTask);
|
||||
//啥时候重置?每日0点?
|
||||
devicePlan.setDayTriggerFlag(true);
|
||||
devicePlan.setUpdateTime(LocalDateTime.now());
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.EXECUTING);
|
||||
//todo 怎么判断任务全部排完了?排完了就删除
|
||||
LocalDate today = LocalDate.now(); // 获取当前日期(系统时区)
|
||||
LocalDate endTime = rules.getEndTime();
|
||||
if (endTime != null && endTime.isEqual(today)) {
|
||||
devicePlan.setFinishTime(LocalDateTime.now());
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.FINISH);
|
||||
GlobalMemory.removeDevicePlan(devicePlan);
|
||||
}
|
||||
devicePlanMapper.updateById(devicePlan);
|
||||
}
|
||||
}
|
||||
|
||||
public void weekPlan(DevicePlan devicePlan) {
|
||||
DeviceTaskPlanRule rules = devicePlan.getDeviceTaskPlanRules();
|
||||
LocalTime nowTime = LocalTime.now();
|
||||
List<DayOfWeek> days = devicePlan.getDeviceTaskPlanRules().getDays();
|
||||
// 1. 获取当前日期
|
||||
LocalDate today = LocalDate.now();
|
||||
DayOfWeek dayOfWeek = today.getDayOfWeek();
|
||||
LocalTime dayTime = LocalTime.parse(rules.getDayTime());
|
||||
if (!CollectionUtils.isEmpty(days) && days.contains(dayOfWeek) && nowTime.isAfter(dayTime)) {
|
||||
DevicePlanTask devicePlanTask = new DevicePlanTask();
|
||||
devicePlanTask.setPlanId(devicePlan.getId());
|
||||
devicePlanTask.setDeviceId(devicePlan.getDeviceId());
|
||||
devicePlanTask.setTaskStaus(DeviceTaskStaus.NEW);
|
||||
devicePlanTask.setRouteId(devicePlan.getRouteId());
|
||||
devicePlanTask.setCreateTime(LocalDateTime.now());
|
||||
devicePlanTask.setTaskType("");//待拓展任务类型
|
||||
devicePlanTaskMapper.insert(devicePlanTask);
|
||||
globalMemory.addDevicePlanPrepare(devicePlanTask);
|
||||
//啥时候重置?每日0点?
|
||||
devicePlan.setDayTriggerFlag(true);
|
||||
devicePlan.setUpdateTime(LocalDateTime.now());
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.EXECUTING);
|
||||
//todo 怎么判断任务全部排完了?排完了就删除
|
||||
LocalDate endTime = rules.getEndTime();
|
||||
if (endTime != null && endTime.isEqual(today)) {
|
||||
devicePlan.setFinishTime(LocalDateTime.now());
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.FINISH);
|
||||
GlobalMemory.removeDevicePlan(devicePlan);
|
||||
}
|
||||
devicePlanMapper.updateById(devicePlan);
|
||||
}
|
||||
}
|
||||
|
||||
public void monthPlan(DevicePlan devicePlan) {
|
||||
DeviceTaskPlanRule rules = devicePlan.getDeviceTaskPlanRules();
|
||||
List<Integer> dayOfMonthList = rules.getDayOfMonth();
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalTime nowTime = LocalTime.now();
|
||||
int dayOfMonth = today.getDayOfMonth();
|
||||
LocalTime dayTime = LocalTime.parse(rules.getDayTime());
|
||||
if (!CollectionUtils.isEmpty(dayOfMonthList) && dayOfMonthList.contains(dayOfMonth) && nowTime.isAfter(dayTime)) {
|
||||
DevicePlanTask devicePlanTask = new DevicePlanTask();
|
||||
devicePlanTask.setPlanId(devicePlan.getId());
|
||||
devicePlanTask.setDeviceId(devicePlan.getDeviceId());
|
||||
devicePlanTask.setTaskStaus(DeviceTaskStaus.NEW);
|
||||
devicePlanTask.setRouteId(devicePlan.getRouteId());
|
||||
devicePlanTask.setCreateTime(LocalDateTime.now());
|
||||
devicePlanTask.setTaskType("");//待拓展任务类型
|
||||
devicePlanTaskMapper.insert(devicePlanTask);
|
||||
globalMemory.addDevicePlanPrepare(devicePlanTask);
|
||||
//啥时候重置?每日0点?
|
||||
devicePlan.setDayTriggerFlag(true);
|
||||
devicePlan.setUpdateTime(LocalDateTime.now());
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.EXECUTING);
|
||||
//todo 怎么判断任务全部排完了?排完了就删除
|
||||
LocalDate endTime = rules.getEndTime();
|
||||
if (endTime != null && endTime.isEqual(today)) {
|
||||
devicePlan.setFinishTime(LocalDateTime.now());
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.FINISH);
|
||||
GlobalMemory.removeDevicePlan(devicePlan);
|
||||
}
|
||||
devicePlanMapper.updateById(devicePlan);
|
||||
}
|
||||
}
|
||||
|
||||
public void oncePlan(DevicePlan devicePlan) {
|
||||
// DeviceTaskPlanRule rules = devicePlan.getDeviceTaskPlanRules();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void doLoop() throws InterruptedException {
|
||||
//prepare 进执行execute的逻辑
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
Map<String, List<DevicePlanTask>> planMap = GlobalMemory.devicePlanTaskPrepareMap;
|
||||
if (!CollectionUtils.isEmpty(planMap.keySet())) {
|
||||
planMap.keySet().forEach(key -> {
|
||||
NettyDevice device = deviceSessionManager.getDevice(key);
|
||||
//判断是否能进execute
|
||||
if (device != null && device.getCurrentTaskId() == null && device.getOnlineStatus() != null && device.getOnlineStatus() == 1) {
|
||||
List<DevicePlanTask> list = planMap.get(key);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
list.removeIf(Objects::isNull);
|
||||
list.sort(
|
||||
Comparator.comparing(
|
||||
DevicePlanTask::getStartTime,
|
||||
Comparator.nullsLast(Comparator.naturalOrder())
|
||||
)
|
||||
);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
DevicePlanTask devicePlanTask = list.get(0);
|
||||
String deviceId = devicePlanTask.getDeviceId();
|
||||
if (!StringUtils.isEmpty(deviceId) && deviceId.equals(key)) {
|
||||
GlobalMemory.addDevicePlanExecute(devicePlanTask);
|
||||
device.setCurrentTaskId(devicePlanTask.getId());
|
||||
GlobalMemory.removeDevicePlanPrepare(devicePlanTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("执行任务prepare线程错误", e);
|
||||
}
|
||||
}, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void doExecute() {
|
||||
//prepare 进执行execute的逻辑
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
Map<String, DevicePlanTask> planMap = GlobalMemory.devicePlanTaskExecuteMap;
|
||||
if (!CollectionUtils.isEmpty(planMap.keySet())) {
|
||||
planMap.keySet().forEach(key -> {
|
||||
NettyDevice device = deviceSessionManager.getDevice(key);
|
||||
if (device != null && device.getCurrentTaskId() == null && device.getOnlineStatus() != null && device.getOnlineStatus() == 1) {
|
||||
DevicePlanTask devicePlanTask = planMap.get(key);
|
||||
if (devicePlanTask != null && DeviceTaskStaus.NEW.equals(devicePlanTask.getTaskStaus())) {
|
||||
devicePlanTask.setTaskStaus(DeviceTaskStaus.EXECUTING);
|
||||
//todo 任务结束的逻辑,执行完路线规划更新 任务完成
|
||||
Long routId = devicePlanTask.getRouteId();
|
||||
WorkRecord workRecord = workRecordMapper.selectWorkRecordById(routId);
|
||||
if (workRecord != null) {
|
||||
//todo 执行转化 并更新到nettyDevice
|
||||
//todo 更新到device
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("执行任务execute线程错误", e);
|
||||
}
|
||||
}, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
package com.fastbee.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.dto.DevicePlanStatisticsDTO;
|
||||
import com.fastbee.dto.DevicePlanTaskStatisticsDTO;
|
||||
import com.fastbee.dto.DeviceTaskQueryDTO;
|
||||
import com.fastbee.dto.DeviceWorkStatisticsDTO;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.domain.DevicePlan;
|
||||
import com.fastbee.iot.domain.DevicePlanTask;
|
||||
import com.fastbee.iot.domain.DeviceTaskPlanRule;
|
||||
import com.fastbee.iot.enums.DeviceTaskStaus;
|
||||
import com.fastbee.iot.mapper.DeviceMapper;
|
||||
import com.fastbee.iot.mapper.DevicePlanMapper;
|
||||
import com.fastbee.iot.mapper.DevicePlanTaskMapper;
|
||||
import com.fastbee.iot.mapper.WorkRecordMapper;
|
||||
import com.fastbee.iot.model.WorkRecord;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DeviceTaskService {
|
||||
|
||||
@Autowired
|
||||
private DevicePlanMapper devicePlanMapper;
|
||||
|
||||
@Autowired
|
||||
private DevicePlanTaskMapper devicePlanTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private DeviceMapper deviceMapper;
|
||||
|
||||
@Autowired
|
||||
private WorkRecordMapper workRecordMapper;
|
||||
|
||||
|
||||
public int insertOrUpdate(DevicePlan devicePlan, LoginUser loginUser) {
|
||||
Long id = devicePlan.getId();
|
||||
if (id != null) {
|
||||
devicePlan.setUpdateBy(loginUser.getUsername());
|
||||
devicePlan.setUpdateTime(LocalDateTime.now());
|
||||
transferDays(devicePlan);
|
||||
int i = devicePlanMapper.updateById(devicePlan);
|
||||
GlobalMemory.addDevicePlan(devicePlan);
|
||||
return i;
|
||||
} else {
|
||||
devicePlan.setTaskStaus(DeviceTaskStaus.NEW);
|
||||
devicePlan.setCreateTime(LocalDateTime.now());
|
||||
devicePlan.setCreateBy(loginUser.getUsername());
|
||||
transferDays(devicePlan);
|
||||
int i = devicePlanMapper.insert(devicePlan);
|
||||
GlobalMemory.addDevicePlan(devicePlan);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
public void transferDays(DevicePlan devicePlan) {
|
||||
DeviceTaskPlanRule deviceTaskPlanRules = devicePlan.getDeviceTaskPlanRules();
|
||||
if (deviceTaskPlanRules != null && !CollectionUtils.isEmpty(deviceTaskPlanRules.getDays())) {
|
||||
List<String> days = new ArrayList<>();
|
||||
deviceTaskPlanRules.getDays().forEach(x -> {
|
||||
String day = null;
|
||||
switch (x) {
|
||||
case MONDAY:
|
||||
day = "周一";
|
||||
break;
|
||||
case TUESDAY:
|
||||
day = "周二";
|
||||
break;
|
||||
case WEDNESDAY:
|
||||
day = "周三";
|
||||
break;
|
||||
case THURSDAY:
|
||||
day = "周四";
|
||||
break;
|
||||
case FRIDAY:
|
||||
day = "周五";
|
||||
break;
|
||||
case SATURDAY:
|
||||
day = "周六";
|
||||
break;
|
||||
case SUNDAY:
|
||||
day = "周日";
|
||||
break;
|
||||
}
|
||||
days.add(day);
|
||||
});
|
||||
devicePlan.getDeviceTaskPlanRules().setDaysTranslate(days);
|
||||
}
|
||||
}
|
||||
|
||||
public List<DevicePlan> getDeviceTaskByUser(Long userId) {
|
||||
if (userId != null) {
|
||||
LambdaQueryWrapper<DevicePlan> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(DevicePlan::getUserId, userId);
|
||||
return transferDevicePlanData(devicePlanMapper.selectList(queryWrapper));
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public List<DevicePlan> getDeviceTask(DeviceTaskQueryDTO dto) {
|
||||
if (dto != null) {
|
||||
LambdaQueryWrapper<DevicePlan> queryWrapper = new LambdaQueryWrapper<>();
|
||||
if (dto.getUserId() != null) {
|
||||
queryWrapper.eq(DevicePlan::getUserId, dto.getUserId());
|
||||
}
|
||||
if (dto.getDeviceId() != null) {
|
||||
queryWrapper.eq(DevicePlan::getDeviceId, dto.getDeviceId());
|
||||
}
|
||||
if (dto.getTaskStaus() != null) {
|
||||
queryWrapper.eq(DevicePlan::getTaskStaus, dto.getTaskStaus());
|
||||
}
|
||||
if (dto.getPlanName() != null) {
|
||||
queryWrapper.like(DevicePlan::getPlanName, dto.getPlanName());
|
||||
}
|
||||
queryWrapper.eq(DevicePlan::getDelFlag, 0);
|
||||
List<DevicePlan> list = devicePlanMapper.selectList(queryWrapper);
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
list.forEach(x -> {
|
||||
Device device = deviceMapper.selectDeviceBySerialNumber(x.getDeviceId());
|
||||
if (device != null) {
|
||||
x.setDeviceAlias(device.getDeviceAlias());
|
||||
}
|
||||
});
|
||||
}
|
||||
return transferDevicePlanData(list);
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public int delete(Long id) {
|
||||
DevicePlan planTask = devicePlanMapper.selectById(id);
|
||||
if (planTask != null) {
|
||||
planTask.setDelFlag(1);
|
||||
GlobalMemory.removeDevicePlan(planTask);
|
||||
return devicePlanMapper.updateById(planTask);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void start(Long id, LoginUser loginUser) {
|
||||
if (id != null) {
|
||||
DevicePlan planTask = devicePlanMapper.selectById(id);
|
||||
if (planTask != null) {
|
||||
planTask.setTaskStaus(DeviceTaskStaus.EXECUTING);
|
||||
planTask.setUpdateTime(LocalDateTime.now());
|
||||
planTask.setUpdateBy(loginUser.getUsername());
|
||||
devicePlanMapper.updateById(planTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<DevicePlanStatisticsDTO> getDevicePlanStatistics(Long userId) {
|
||||
List<DevicePlanStatisticsDTO> list = new ArrayList<>();
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDateTime monthStart = LocalDateTime.of(
|
||||
today.getYear(), // 当前年
|
||||
today.getMonth(), // 当前月
|
||||
1, // 当月第一天
|
||||
0, 0, 0, 0 // 时:分:秒:纳秒(00:00:00.000)
|
||||
);
|
||||
LocalDate lastDayOfMonth = today.withDayOfMonth(today.lengthOfMonth()); // 获取当月最后一天
|
||||
LocalDateTime monthEnd = LocalDateTime.of(
|
||||
lastDayOfMonth.getYear(),
|
||||
lastDayOfMonth.getMonth(),
|
||||
lastDayOfMonth.getDayOfMonth(),
|
||||
23, 59, 59, 999_999_999 // 时:分:秒:纳秒(23:59:59.999)
|
||||
);
|
||||
LambdaQueryWrapper<DevicePlan> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.ge(DevicePlan::getCreateTime, monthStart)
|
||||
.le(DevicePlan::getCreateTime, monthEnd)
|
||||
.eq(DevicePlan::getUserId,userId);
|
||||
List<DevicePlan> devicePlans = transferDevicePlanData(devicePlanMapper.selectList(queryWrapper));
|
||||
if (!CollectionUtils.isEmpty(devicePlans)) {
|
||||
List<LocalDate> dates = getMonthDays();
|
||||
for (LocalDate date : dates) {
|
||||
List<DevicePlan> plans = devicePlans.stream().filter(x -> x.getCreateTime().toLocalDate().equals(date)).collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(plans)) {
|
||||
DevicePlanStatisticsDTO dto = new DevicePlanStatisticsDTO();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M.d");
|
||||
dto.setName(formatter.format(date));
|
||||
long count = plans.stream().filter(x -> DeviceTaskStaus.FINISH.equals(x.getTaskStaus())).count();
|
||||
dto.setCompleted((int) count);
|
||||
dto.setPlanned(plans.size());
|
||||
list.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<LocalDate> getMonthDays() {
|
||||
LocalDate today = LocalDate.now();
|
||||
int totalDays = today.lengthOfMonth();
|
||||
LocalDate firstDayOfMonth = LocalDate.of(today.getYear(), today.getMonth(), 1);
|
||||
List<LocalDate> allDaysOfCurrentMonth = new ArrayList<>();
|
||||
for (int i = 0; i < totalDays; i++) {
|
||||
// 每次累加1天,获取当月每一天
|
||||
LocalDate currentDay = firstDayOfMonth.plusDays(i);
|
||||
allDaysOfCurrentMonth.add(currentDay);
|
||||
}
|
||||
return allDaysOfCurrentMonth;
|
||||
}
|
||||
|
||||
public List<DevicePlanTaskStatisticsDTO> getDevicePlanTaskStatistics(Long userId) {
|
||||
List<DevicePlanTaskStatisticsDTO> list = new ArrayList<>();
|
||||
// 1. 获取当前日期
|
||||
LocalDate today = LocalDate.now();
|
||||
// 2. 构造当前月份的起始时间:当月第一天 00:00:00
|
||||
LocalDateTime monthStart = LocalDateTime.of(
|
||||
today.getYear(), // 当前年
|
||||
today.getMonth(), // 当前月
|
||||
1, // 当月第一天
|
||||
0, 0, 0, 0 // 时:分:秒:纳秒(00:00:00.000)
|
||||
);
|
||||
// 3. 构造当前月份的结束时间:当月最后一天 23:59:59.999
|
||||
LocalDate lastDayOfMonth = today.withDayOfMonth(today.lengthOfMonth()); // 获取当月最后一天
|
||||
LocalDateTime monthEnd = LocalDateTime.of(
|
||||
lastDayOfMonth.getYear(),
|
||||
lastDayOfMonth.getMonth(),
|
||||
lastDayOfMonth.getDayOfMonth(),
|
||||
23, 59, 59, 999_999_999 // 时:分:秒:纳秒(23:59:59.999)
|
||||
);
|
||||
// 4. 构建 LambdaQueryWrapper 进行范围查询
|
||||
LambdaQueryWrapper<DevicePlan> query = new LambdaQueryWrapper<>();
|
||||
query.eq(DevicePlan::getUserId,userId);
|
||||
List<DevicePlan> devicePlans = transferDevicePlanData(devicePlanMapper.selectList(query));
|
||||
if(CollectionUtils.isEmpty(devicePlans)) return new ArrayList<>();
|
||||
List<Long> planIds = devicePlans.stream().map(DevicePlan::getId).collect(Collectors.toList());
|
||||
if(CollectionUtils.isEmpty(planIds)) return new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<DevicePlanTask> queryWrapper = new LambdaQueryWrapper<>();
|
||||
// 核心:筛选 createTime 大于等于起始时间,且小于等于结束时间
|
||||
queryWrapper.ge(DevicePlanTask::getCreateTime, monthStart)
|
||||
.le(DevicePlanTask::getCreateTime, monthEnd)
|
||||
.in(DevicePlanTask::getPlanId,planIds);
|
||||
List<DevicePlanTask> devicePlanTasks = transferDeviceTaskData(devicePlanTaskMapper.selectList(queryWrapper));
|
||||
if (!CollectionUtils.isEmpty(devicePlanTasks)) {
|
||||
List<LocalDate> dates = getMonthDays();
|
||||
for (LocalDate date : dates) {
|
||||
List<DevicePlanTask> planTasks = devicePlanTasks.stream().filter(x -> x.getCreateTime().toLocalDate().equals(date)).collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(planTasks)) {
|
||||
DevicePlanTaskStatisticsDTO dto = new DevicePlanTaskStatisticsDTO();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M.d");
|
||||
dto.setDate(formatter.format(date));
|
||||
long finishCount = planTasks.stream().filter(x -> DeviceTaskStaus.FINISH.equals(x.getTaskStaus())).count();
|
||||
dto.setSuccess((int) finishCount);
|
||||
long failedCount = planTasks.stream().filter(x -> DeviceTaskStaus.FAILED.equals(x.getTaskStaus())).count();
|
||||
dto.setFailed((int) failedCount);
|
||||
long pauseCount = planTasks.stream().filter(x -> DeviceTaskStaus.PAUSE.equals(x.getTaskStaus())).count();
|
||||
dto.setPaused((int) pauseCount);
|
||||
list.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public WorkRecord concurrentDevicePlanTask(String deviceId) {
|
||||
LambdaQueryWrapper<DevicePlanTask> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(DevicePlanTask::getDeviceId, deviceId)
|
||||
.eq(DevicePlanTask::getTaskStaus, DeviceTaskStaus.EXECUTING);
|
||||
List<DevicePlanTask> devicePlanTasks = devicePlanTaskMapper.selectList(queryWrapper);
|
||||
Long routId = CollectionUtils.isEmpty(devicePlanTasks) ? null : devicePlanTasks.get(0).getRouteId();
|
||||
return routId == null ? null : workRecordMapper.selectWorkRecordById(routId);
|
||||
}
|
||||
|
||||
|
||||
public List<DeviceWorkStatisticsDTO> workStatistics(Long userId) {
|
||||
//todo update
|
||||
|
||||
List<DeviceWorkStatisticsDTO> result = new ArrayList<>();
|
||||
Random random = new Random();
|
||||
int times = random.nextInt(100);
|
||||
int s = random.nextInt(1000);
|
||||
DeviceWorkStatisticsDTO dto = new DeviceWorkStatisticsDTO();
|
||||
dto.setLabel("割草次数");
|
||||
dto.setValue(times);
|
||||
DeviceWorkStatisticsDTO dto2 = new DeviceWorkStatisticsDTO();
|
||||
dto2.setLabel("割草时长(s)");
|
||||
dto2.setValue(s);
|
||||
DeviceWorkStatisticsDTO dto3 = new DeviceWorkStatisticsDTO();
|
||||
dto3.setLabel("割草面积(m2)");
|
||||
dto3.setValue(s * 5);
|
||||
result.add(dto);
|
||||
result.add(dto2);
|
||||
result.add(dto3);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public List<DevicePlanTask> deviceTaskPool(DeviceTaskQueryDTO dto) {
|
||||
List<DevicePlanTask> list = new ArrayList<>();
|
||||
GlobalMemory.devicePlanTaskPrepareMap.values().forEach(list::addAll);
|
||||
list.addAll(GlobalMemory.devicePlanTaskExecuteMap.values());
|
||||
if (dto != null) {
|
||||
if (dto.getTaskStaus() != null) {
|
||||
list = list.stream().filter(x -> x.getTaskStaus().equals(dto.getTaskStaus())).collect(Collectors.toList());
|
||||
}
|
||||
if (!StringUtils.isEmpty(dto.getDeviceId())) {
|
||||
list = list.stream().filter(x -> x.getDeviceId().equals(dto.getDeviceId())).collect(Collectors.toList());
|
||||
}
|
||||
if (dto.getPlanId() != null) {
|
||||
list = list.stream().filter(x -> x.getPlanId().equals(dto.getPlanId())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
return transferDeviceTaskData(list);
|
||||
}
|
||||
|
||||
|
||||
public List<DevicePlan> transferDevicePlanData(List<DevicePlan> list) {
|
||||
if (CollectionUtils.isEmpty(list)) return list;
|
||||
list.forEach(x -> {
|
||||
DeviceTaskStaus taskStaus = x.getTaskStaus();
|
||||
x.setTaskStausTranslate(taskStaus.getDescription());
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<DevicePlanTask> transferDeviceTaskData(List<DevicePlanTask> list) {
|
||||
if (CollectionUtils.isEmpty(list)) return list;
|
||||
list.forEach(x -> {
|
||||
DeviceTaskStaus taskStaus = x.getTaskStaus();
|
||||
x.setTaskStausTranslate(taskStaus.getDescription());
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.fastbee.service;
|
||||
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.CommandConstant;
|
||||
import com.fastbee.common.NettyCacheKey;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.dto.DeviceErrorPushDTO;
|
||||
import com.fastbee.dto.DeviceErrorPushDetail;
|
||||
import com.fastbee.dto.NettyDevice;
|
||||
import com.fastbee.iot.domain.DeviceRunningStatusHistory;
|
||||
import com.fastbee.entity.ErrorIdentificationStandard;
|
||||
import com.fastbee.enums.CompareEnum;
|
||||
import com.fastbee.enums.RespondCode;
|
||||
import com.fastbee.iot.domain.DeviceStatusRecordDTO;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import com.fastbee.mapper.ErrorIdentificationStandardMapper;
|
||||
import com.fastbee.memory.GlobalMemory;
|
||||
import com.fastbee.netty.handler.DeviceConnectHandler;
|
||||
import com.fastbee.utils.CommandUtils;
|
||||
import com.fastbee.utils.CompareUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DeviceThreadService {
|
||||
|
||||
@Autowired
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
@Autowired
|
||||
private GlobalMemory globalMemory;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private ErrorIdentificationStandardMapper standardMapper;
|
||||
|
||||
|
||||
public void deviceErrorMonitor() throws InterruptedException, IOException {
|
||||
initStandard();
|
||||
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
String key = NettyCacheKey.deviceRunningStatusKey;
|
||||
Collection<String> keys = redisCache.getListKeyByPrefix(key);
|
||||
if (!CollectionUtils.isEmpty(keys)) {
|
||||
keys.forEach(k -> {
|
||||
DeviceStatusRecordDTO recordDTO = redisCache.getCacheObject(k);
|
||||
if (recordDTO != null && !CollectionUtils.isEmpty(recordDTO.getHistories())) {
|
||||
recordDTO.getHistories().sort(Comparator.comparing(DeviceRunningStatusHistory::getCreateTime).reversed());
|
||||
DeviceRunningStatusHistory history = recordDTO.getHistories().get(0);
|
||||
List<DeviceErrorPushDetail> compared = compareErrorStandard(history, globalMemory.standard);
|
||||
|
||||
String[] parts = k.split(":");
|
||||
String deviceId = parts[1];
|
||||
|
||||
DeviceErrorPushDTO pushDTO = new DeviceErrorPushDTO();
|
||||
pushDTO.setDeviceId(deviceId);
|
||||
pushDTO.setTime(System.currentTimeMillis());
|
||||
pushDTO.setDetails(compared);
|
||||
pushDTO.setEvent(RespondCode.device_error_push);
|
||||
//todo push 推送到前端
|
||||
pushErrorMessage(deviceId, JSON.toJSONString(pushDTO));
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("执行监测错误线程错误:{}", e.getMessage());
|
||||
}
|
||||
}, 0, 2, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public void initStandard() throws IOException {
|
||||
if (globalMemory.standard == null) {
|
||||
List<ErrorIdentificationStandard> standards = standardMapper.selectList();
|
||||
if (CollectionUtils.isEmpty(standards)) {
|
||||
// 读取resource目录下的device-config.json文件
|
||||
Resource resource = resourceLoader.getResource("classpath:errorStandard.json");
|
||||
// 使用fastjson2转换为DeviceConfig对象
|
||||
String jsonContent = IoUtil.readUtf8(resource.getInputStream());
|
||||
globalMemory.standard = JSON.parseArray(jsonContent, ErrorIdentificationStandard.class);
|
||||
} else {
|
||||
globalMemory.standard = standards;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void pushErrorMessage(String deviceId, String dto) {
|
||||
NettyDevice device = sessionManager.getDevice(deviceId);
|
||||
if (device != null) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
CommandUtils.buildCommand(buf, dto, CommandConstant.interaction);
|
||||
if (device.getChannel() != null && device.getChannel().isActive()) {
|
||||
device.getChannel().writeAndFlush(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<DeviceErrorPushDetail> compareErrorStandard(DeviceRunningStatusHistory history, List<ErrorIdentificationStandard> standards) {
|
||||
if (history == null || CollectionUtils.isEmpty(standards)) return null;
|
||||
List<DeviceErrorPushDetail> list = new ArrayList<>();
|
||||
standards.forEach(standard -> {
|
||||
String fieldName = standard.getField();
|
||||
String compareValues = standard.getCompareValues();
|
||||
CompareEnum compareType = standard.getCompareType();
|
||||
String targetValue = knownClassFieldFinding(fieldName, history);
|
||||
if (!StringUtils.isEmpty(compareValues) && !StringUtils.isEmpty(targetValue)) {
|
||||
boolean result = CompareUtils.compare(compareType, compareValues, targetValue);
|
||||
if (!result) {
|
||||
DeviceErrorPushDetail detail = new DeviceErrorPushDetail();
|
||||
detail.setErrorName(standard.getErrorName());
|
||||
detail.setDescription(standard.getErrorDescription());
|
||||
detail.setValue(targetValue);
|
||||
detail.setRange(compareValues);
|
||||
list.add(detail);
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
private static String knownClassFieldFinding(String fieldName, DeviceRunningStatusHistory history) {
|
||||
try {
|
||||
// 1. 获取类的Class对象
|
||||
Class<?> historyClass = DeviceRunningStatusHistory.class;
|
||||
Field field = historyClass.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(history);
|
||||
return (String) value;
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
System.out.println("获取字段失败: " + e.getMessage());
|
||||
log.error("获取属性值失败:{}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.fastbee.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.iot.domain.DeviceRunningStatusHistory;
|
||||
import com.fastbee.entity.ErrorIdentificationStandard;
|
||||
import com.fastbee.iot.mapper.DeviceStatusHistoryMapper;
|
||||
import com.fastbee.mapper.ErrorIdentificationStandardMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class NettyDeviceService {
|
||||
|
||||
@Autowired
|
||||
private DeviceStatusHistoryMapper deviceStatusHistoryMapper;
|
||||
|
||||
@Autowired
|
||||
private ErrorIdentificationStandardMapper errorIdentificationStandardMapper;
|
||||
|
||||
public List<DeviceRunningStatusHistory> statusHistory(DeviceRunningStatusHistory history) {
|
||||
|
||||
LambdaQueryWrapper<DeviceRunningStatusHistory> queryWrapper = new LambdaQueryWrapper<>();
|
||||
if (!StringUtils.isEmpty(history.getDeviceId())) {
|
||||
queryWrapper.eq(DeviceRunningStatusHistory::getDeviceId, history.getDeviceId());
|
||||
}
|
||||
return deviceStatusHistoryMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
public List<ErrorIdentificationStandard> allErrorIdentificationStandard() {
|
||||
return errorIdentificationStandardMapper.selectList(null);
|
||||
}
|
||||
|
||||
public int updateErrorIdentification(ErrorIdentificationStandard standard) {
|
||||
return errorIdentificationStandardMapper.updateById(standard);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package com.fastbee.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.domain.entity.UserClient;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.MessageUtils;
|
||||
import com.fastbee.dto.*;
|
||||
import com.fastbee.enums.ConnectorStatus;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.mapper.DeviceMapper;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.manager.DeviceSessionManager;
|
||||
import com.fastbee.system.mapper.SysUserClientMapper;
|
||||
import com.fastbee.system.service.ISysUserClientService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class TransferDeviceService {
|
||||
|
||||
@Autowired
|
||||
private IDeviceService iDeviceService;
|
||||
|
||||
@Autowired
|
||||
private ISysUserClientService sysUserClientService;
|
||||
|
||||
@Autowired
|
||||
private DeviceSessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private SysUserClientMapper userClientMapper;
|
||||
|
||||
@Autowired
|
||||
private DeviceMapper deviceMapper;
|
||||
|
||||
|
||||
public AjaxResult bind(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
String deviceId = bindDTO.getDeviceId();
|
||||
String deviceAlias = bindDTO.getDeviceAlias();
|
||||
//判断当前设备是否存
|
||||
Device device = iDeviceService.selectDeviceBySerialNumber(deviceId);
|
||||
if (device == null) {
|
||||
return AjaxResult.error("设备不存在");
|
||||
} else {
|
||||
Long tenantId = device.getTenantId();
|
||||
if (tenantId != -1) {
|
||||
if (!tenantId.equals(user.getUserId())) {
|
||||
return AjaxResult.error("设备有其他用户绑定");
|
||||
}
|
||||
}
|
||||
device.setTenantId(user.getUserId());
|
||||
device.setTenantName(user.getUsername());
|
||||
device.setUpdateTime(new Date());
|
||||
device.setDeviceAlias(deviceAlias);
|
||||
device.setActiveTime(new Date());
|
||||
iDeviceService.updateDeviceBySelf(device);
|
||||
// deviceMapper.updateById(device);
|
||||
return AjaxResult.success(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("设备绑定失败: {}", e.getMessage(), e);
|
||||
return AjaxResult.error("设备绑定失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public AjaxResult unbind(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
String deviceId = bindDTO.getDeviceId();
|
||||
//判断当前设备是否存
|
||||
Device device = iDeviceService.selectDeviceBySerialNumber(deviceId);
|
||||
if (device == null) {
|
||||
return AjaxResult.error("设备不存在");
|
||||
} else {
|
||||
Long tenantId = device.getTenantId();
|
||||
if (tenantId == -1) {
|
||||
return AjaxResult.error("设备未绑定");
|
||||
}
|
||||
if (!tenantId.equals(user.getUserId())) {
|
||||
return AjaxResult.error("无解绑权限");
|
||||
}
|
||||
device.setTenantId(-1L);
|
||||
device.setTenantName("");
|
||||
device.setUpdateTime(new Date());
|
||||
device.setDeviceAlias("");
|
||||
// deviceMapper.updateById(device);
|
||||
iDeviceService.updateDeviceBySelf(device);
|
||||
//判断是否正在控制 如果正在控制,删除控制状态
|
||||
LambdaQueryWrapper<UserClient> lambdaWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaWrapper.eq(UserClient::getDeviceName, deviceId);
|
||||
List<UserClient> users = userClientMapper.selectList(lambdaWrapper);
|
||||
log.info("users:{}", users);
|
||||
if (!CollectionUtils.isEmpty(users)) {
|
||||
users.forEach(userClient -> {
|
||||
log.info("userClient:{}", userClient);
|
||||
if (userClient != null) {
|
||||
String masterId = userClient.getClientName();
|
||||
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
|
||||
if (deviceMaster != null) {
|
||||
String oldSlave = userClient.getDeviceName();
|
||||
//删除主的连接关系
|
||||
deviceMaster.getConnectedConnectors().removeIf(x -> x.getConnectorId().equals(oldSlave));
|
||||
//删除从的连接关系
|
||||
NettyDevice oldSlaveDevice = sessionManager.getDevice(oldSlave);
|
||||
if (oldSlaveDevice != null && !CollectionUtils.isEmpty(oldSlaveDevice.getConnectedConnectors())) {
|
||||
Set<Connector> connectedDevices = oldSlaveDevice.getConnectedConnectors();
|
||||
connectedDevices.removeIf(x -> x.getConnectorId().equals(masterId));
|
||||
}
|
||||
}
|
||||
sysUserClientService.updateAliveStatus(masterId, "", 0);
|
||||
log.info("updateById:{}", userClient.getClientName());
|
||||
}
|
||||
});
|
||||
}
|
||||
return AjaxResult.success(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("设备解绑失败: {}", e.getMessage(), e);
|
||||
return AjaxResult.error("设备解绑失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public AjaxResult updateDeviceAlias(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
String deviceId = bindDTO.getDeviceId();
|
||||
//判断当前设备是否存
|
||||
Device device = iDeviceService.selectDeviceBySerialNumber(deviceId);
|
||||
if (device == null) {
|
||||
return AjaxResult.error("设备不存在");
|
||||
} else {
|
||||
Long tenantId = device.getTenantId();
|
||||
if (tenantId == -1) {
|
||||
return AjaxResult.error("设备未绑定");
|
||||
}
|
||||
if (!tenantId.equals(user.getUserId())) {
|
||||
return AjaxResult.error("无操作权限");
|
||||
}
|
||||
device.setDeviceAlias(bindDTO.getDeviceAlias());
|
||||
device.setUpdateTime(new Date());
|
||||
device.setCreateBy(user.getUsername());
|
||||
iDeviceService.updateDeviceBySelf(device);
|
||||
return AjaxResult.success(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("设备更新失败: {}", e.getMessage(), e);
|
||||
return AjaxResult.error("设备更新失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean switchDevice(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
String slaveId = bindDTO.getDeviceId();
|
||||
String platform = bindDTO.getPlatform();
|
||||
String userName = user.getUsername();
|
||||
String masterId = userName + ":" + platform;
|
||||
//todo 判断是否有权限 正在控制才能切
|
||||
//todo 离线没有控制权的怎么切
|
||||
log.info("switchDevice:{}", masterId);
|
||||
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
|
||||
log.info("deviceMaster:{}", deviceMaster);
|
||||
if (deviceMaster != null) {
|
||||
log.info("ConnectorStatus:{}", deviceMaster.getConnectorStatus());
|
||||
if (ConnectorStatus.RECEIVER.equals(deviceMaster.getConnectorStatus())) {
|
||||
//当前账号没有控制则可以切换为控制?
|
||||
NettyDevice activeDevice = sessionManager.getUserControl(userName);
|
||||
deviceMaster.getConnectedConnectors().clear();
|
||||
if (activeDevice == null) {
|
||||
log.info("bindConnector ACTIVE :{}", masterId);
|
||||
sessionManager.bindConnector(masterId, slaveId);
|
||||
deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
|
||||
deviceMaster.setCurrentSlaveId(slaveId);
|
||||
sysUserClientService.updateAliveStatus(masterId, slaveId, 1);
|
||||
} else {
|
||||
log.info("bindConnector RECEIVER :{}", masterId);
|
||||
sessionManager.bindConnector(masterId, slaveId);
|
||||
deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
|
||||
deviceMaster.setCurrentSlaveId(slaveId);
|
||||
sysUserClientService.updateAliveStatus(masterId, slaveId, 2);
|
||||
}
|
||||
} else {
|
||||
//从关联其他设备切换到 当前禁言 假如有控制且不是当前更新为2
|
||||
List<NettyDevice> controls = sessionManager.getSlaveControl(slaveId);
|
||||
if (CollectionUtils.isEmpty(controls)) {
|
||||
deviceMaster.getConnectedConnectors().clear();
|
||||
log.info("bindConnector 1:{}", masterId);
|
||||
sessionManager.bindConnector(masterId, slaveId);
|
||||
deviceMaster.setConnectorStatus(ConnectorStatus.ACTIVE);
|
||||
deviceMaster.setCurrentSlaveId(slaveId);
|
||||
sysUserClientService.updateAliveStatus(masterId, slaveId, 1);
|
||||
} else {
|
||||
NettyDevice controlDevice = controls.get(0);
|
||||
if (!masterId.equals(controlDevice.getConnectorId())) {
|
||||
//当前的设备有控制 的主机
|
||||
deviceMaster.getConnectedConnectors().clear();
|
||||
log.info("bindConnector RECEIVER 2:{}", masterId);
|
||||
sessionManager.bindConnector(masterId, slaveId);
|
||||
deviceMaster.setConnectorStatus(ConnectorStatus.RECEIVER);
|
||||
deviceMaster.setCurrentSlaveId(slaveId);
|
||||
sysUserClientService.updateAliveStatus(masterId, slaveId, 2);
|
||||
}
|
||||
//当前 切当前不做操作
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("切换设备失败: {}", e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// public DeviceControlDTO remoteControl(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
// try {
|
||||
// String slaveId = bindDTO.getDeviceId();
|
||||
// String platform = bindDTO.getPlatform();
|
||||
// String userName = user.getUsername();
|
||||
// String masterId = userName + ":" + platform;
|
||||
// DeviceControlDTO dto = new DeviceControlDTO();
|
||||
// NettyDevice deviceMaster = sessionManager.getDevice(masterId);
|
||||
// log.info("remoteControl deviceMaster:{}", masterId);
|
||||
// if (deviceMaster != null) {
|
||||
// if (ConnectorStatus.ACTIVE.equals(deviceMaster.getConnectorStatus())) {
|
||||
// if (!StringUtils.isEmpty(deviceMaster.getCurrentSlaveId())) {
|
||||
// if (deviceMaster.getCurrentSlaveId().equalsIgnoreCase(slaveId)) {
|
||||
// dto.setRemoteControl(true);
|
||||
// dto.setOwner(platform);
|
||||
// } else {
|
||||
// dto.setRemoteControl(false);
|
||||
// dto.setReason("occupied");
|
||||
// List<NettyDevice> list = sessionManager.getSlaveControl(slaveId);
|
||||
// if (!CollectionUtils.isEmpty(list)) {
|
||||
// dto.setOwner(list.get(0).getConnectorId());
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// dto.setRemoteControl(false);
|
||||
// dto.setReason("no control");
|
||||
// List<NettyDevice> list = sessionManager.getSlaveControl(slaveId);
|
||||
// if (!CollectionUtils.isEmpty(list)) {
|
||||
// dto.setOwner(list.get(0).getConnectorId());
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// dto.setRemoteControl(false);
|
||||
// dto.setReason("not active");
|
||||
// List<NettyDevice> list = sessionManager.getSlaveControl(slaveId);
|
||||
// if (!CollectionUtils.isEmpty(list)) {
|
||||
// dto.setOwner(list.get(0).getConnectorId());
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// dto.setRemoteControl(false);
|
||||
// dto.setReason("not exist");
|
||||
// List<NettyDevice> list = sessionManager.getSlaveControl(slaveId);
|
||||
// if (!CollectionUtils.isEmpty(list)) {
|
||||
// dto.setOwner(list.get(0).getConnectorId());
|
||||
// }
|
||||
// }
|
||||
// return dto;
|
||||
// } catch (Exception e) {
|
||||
// log.error(e.getMessage());
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
|
||||
public DeviceControlDTO remoteControl(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
// String slaveId = bindDTO.getDeviceId();
|
||||
String platform = bindDTO.getPlatform();
|
||||
String userName = user.getUsername();
|
||||
String masterId = userName + ":" + platform;
|
||||
DeviceControlDTO dto = new DeviceControlDTO();
|
||||
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
|
||||
log.info("remoteControl deviceMaster:{}", masterId);
|
||||
if (deviceMaster != null) {
|
||||
if (ConnectorStatus.ACTIVE.equals(deviceMaster.getConnectorStatus())) {
|
||||
dto.setRemoteControl(true);
|
||||
dto.setOwner(platform);
|
||||
} else {
|
||||
dto.setRemoteControl(false);
|
||||
dto.setReason("occupied");
|
||||
NettyDevice activeDevice = sessionManager.getUserControl(userName);
|
||||
if (activeDevice != null) {
|
||||
String[] l = activeDevice.getConnectorId().split(":");
|
||||
dto.setOwner(l[1]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dto.setRemoteControl(false);
|
||||
dto.setReason("not exist");
|
||||
NettyDevice activeDevice = sessionManager.getUserControl(userName);
|
||||
if (activeDevice != null) {
|
||||
String[] l = activeDevice.getConnectorId().split(":");
|
||||
dto.setOwner(l[1]);
|
||||
}
|
||||
}
|
||||
return dto;
|
||||
} catch (Exception e) {
|
||||
log.error("获取设备权限失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public DeviceSwitchControlDTO switchControl(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
String slaveId = bindDTO.getDeviceId();
|
||||
String platform = bindDTO.getPlatform();
|
||||
String userName = user.getUsername();
|
||||
String masterId = userName + ":" + platform;
|
||||
|
||||
DeviceSwitchControlDTO dto = new DeviceSwitchControlDTO();
|
||||
//收到明确的切换设备请求了 从哪边切换到另一边
|
||||
//判断当前是否有控制的设备
|
||||
NettyDevice masterDevice = sessionManager.getDevice(masterId);
|
||||
if (masterDevice == null || !masterDevice.getChannel().isActive()) {
|
||||
//主机已离线
|
||||
dto.setSwitchControl(false);
|
||||
dto.setReason("主机已离线");
|
||||
return dto;
|
||||
}
|
||||
|
||||
NettyDevice slaveDevice = sessionManager.getDevice(slaveId);
|
||||
if (slaveDevice == null || !masterDevice.getChannel().isActive()) {
|
||||
//从机已离线
|
||||
//主机已离线
|
||||
dto.setSwitchControl(false);
|
||||
dto.setReason("从机已离线");
|
||||
return dto;
|
||||
}
|
||||
|
||||
List<NettyDevice> controlList = sessionManager.getSlaveControl(slaveId);
|
||||
|
||||
if (!CollectionUtils.isEmpty(controlList)) {
|
||||
//如果当前设备有控制
|
||||
controlList.forEach(c -> {
|
||||
if (!c.getConnectorId().equals(masterId)) {
|
||||
c.setCurrentSlaveId(null);
|
||||
c.removeConnectedDevice(slaveDevice);
|
||||
c.setConnectorStatus(ConnectorStatus.RECEIVER);
|
||||
sysUserClientService.updateAliveStatus(c.getConnectorId(), slaveId, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
masterDevice.setCurrentSlaveId(slaveId);
|
||||
masterDevice.setConnectorStatus(ConnectorStatus.ACTIVE);
|
||||
sysUserClientService.updateAliveStatus(masterDevice.getConnectorId(), slaveId, 1);
|
||||
|
||||
dto.setSwitchControl(true);
|
||||
dto.setHolder(platform);
|
||||
return dto;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
return new DeviceSwitchControlDTO(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseControl(DeviceBindDTO bindDTO, LoginUser user) {
|
||||
try {
|
||||
String platform = bindDTO.getPlatform();
|
||||
String userName = user.getUsername();
|
||||
String masterId = userName + ":" + platform;
|
||||
NettyDevice deviceMaster = sessionManager.getDevice(masterId);
|
||||
if (deviceMaster != null) {
|
||||
sysUserClientService.updateAliveStatus(masterId, "", 0);
|
||||
deviceMaster.setCurrentSlaveId(null);
|
||||
deviceMaster.getConnectedConnectors().clear();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.fastbee.utils;
|
||||
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
public class CommandUtils {
|
||||
|
||||
|
||||
public static void buildCommand(ByteBuf buf, Object json, byte command) {
|
||||
buf.writeShort(0xABAA);
|
||||
buf.writeByte(command);//推送tcp消息指令
|
||||
if (json != null) {
|
||||
String jsonStr = JsonUtils.toJsonString(json);
|
||||
byte[] jsonBytes = jsonStr.getBytes(StandardCharsets.UTF_8);
|
||||
//写入JSON数据的字节数组
|
||||
buf.writeBytes(jsonBytes);
|
||||
}
|
||||
buf.writeByte(0x00);
|
||||
buf.writeByte(0x00);
|
||||
buf.writeShort(0xAAAB);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.fastbee.utils;
|
||||
|
||||
import com.fastbee.enums.CompareEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 比较工具类
|
||||
* 提供基于比较操作符的通用比较功能
|
||||
*/
|
||||
@Slf4j
|
||||
public class CompareUtils {
|
||||
|
||||
/**
|
||||
* 比较两个值是否满足指定的操作符条件
|
||||
*
|
||||
* @param operator 比较操作符
|
||||
* @param targetValue 目标值(用于比较的值)
|
||||
* @param compareValue 比较值(要比较的值)
|
||||
* @return 是否满足条件
|
||||
*/
|
||||
public static boolean compare(CompareEnum operator, String targetValue, String compareValue) {
|
||||
if (operator == null || targetValue == null || compareValue == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 根据操作符执行不同的比较逻辑
|
||||
switch (operator) {
|
||||
case EQ:
|
||||
return compareValue.equals(targetValue);
|
||||
case NEQ:
|
||||
return !compareValue.equals(targetValue);
|
||||
case GT:
|
||||
return isNumeric(compareValue) && isNumeric(targetValue) &&
|
||||
Double.parseDouble(compareValue) > Double.parseDouble(targetValue);
|
||||
case LT:
|
||||
return isNumeric(compareValue) && isNumeric(targetValue) &&
|
||||
Double.parseDouble(compareValue) < Double.parseDouble(targetValue);
|
||||
case GTE:
|
||||
return isNumeric(compareValue) && isNumeric(targetValue) &&
|
||||
Double.parseDouble(compareValue) >= Double.parseDouble(targetValue);
|
||||
case LTE:
|
||||
return isNumeric(compareValue) && isNumeric(targetValue) &&
|
||||
Double.parseDouble(compareValue) <= Double.parseDouble(targetValue);
|
||||
case BETWEEN:
|
||||
// 范围值用英文中划线分割
|
||||
String[] rangeValues = targetValue.split("-");
|
||||
if (rangeValues.length != 2) {
|
||||
return false;
|
||||
}
|
||||
return isNumeric(compareValue) && isNumeric(rangeValues[0]) && isNumeric(rangeValues[1]) &&
|
||||
Double.parseDouble(compareValue) >= Double.parseDouble(rangeValues[0]) &&
|
||||
Double.parseDouble(compareValue) <= Double.parseDouble(rangeValues[1]);
|
||||
case NOT_BETWEEN:
|
||||
// 范围值用英文中划线分割
|
||||
String[] notRangeValues = targetValue.split("-");
|
||||
if (notRangeValues.length != 2) {
|
||||
return false;
|
||||
}
|
||||
return isNumeric(compareValue) && isNumeric(notRangeValues[0]) && isNumeric(notRangeValues[1]) &&
|
||||
(Double.parseDouble(compareValue) < Double.parseDouble(notRangeValues[0]) ||
|
||||
Double.parseDouble(compareValue) > Double.parseDouble(notRangeValues[1]));
|
||||
case CONTAIN:
|
||||
return compareValue.contains(targetValue);
|
||||
case NOT_CONTAIN:
|
||||
return !compareValue.contains(targetValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("比较操作失败,操作符: {}, 目标值: {}, 比较值: {}", operator.getValue(), targetValue, compareValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为数字
|
||||
*
|
||||
* @param str 要判断的字符串
|
||||
* @return 是否为数字
|
||||
*/
|
||||
private static boolean isNumeric(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 使用正则表达式判断是否为数字
|
||||
return str.matches("^[+-]?\\d*(\\.\\d+)?$");
|
||||
}
|
||||
}
|
||||
16
maibu-netty-server/src/main/resources/errorStandard.json
Normal file
16
maibu-netty-server/src/main/resources/errorStandard.json
Normal file
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"field": "voltage",
|
||||
"compareValues": 58,
|
||||
"compareType": "GT",
|
||||
"errorName" : "电压过高",
|
||||
"errorDescription" : "当前电池电压过高,超过正常范围"
|
||||
},
|
||||
{
|
||||
"field": "battery",
|
||||
"compareValues": 20,
|
||||
"compareType": "LT",
|
||||
"errorName" : "电量过低",
|
||||
"errorDescription" : "当前电池电量过低,请及时充电"
|
||||
}
|
||||
]
|
||||
58
maibu-netty-server/src/main/resources/log4j2.xml
Normal file
58
maibu-netty-server/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<!-- 定义每个日志级别对应的文件输出 -->
|
||||
<File name="TraceFile" fileName="logs/trace.log">
|
||||
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %c{1} - %msg%n"/>
|
||||
<Filters>
|
||||
<ThresholdFilter level="TRACE" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</Filters>
|
||||
</File>
|
||||
|
||||
<File name="DebugFile" fileName="logs/debug.log">
|
||||
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %c{1} - %msg%n"/>
|
||||
<Filters>
|
||||
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</Filters>
|
||||
</File>
|
||||
|
||||
<File name="InfoFile" fileName="logs/info.log">
|
||||
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %c{1} - %msg%n"/>
|
||||
<Filters>
|
||||
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</Filters>
|
||||
</File>
|
||||
|
||||
<File name="WarnFile" fileName="logs/warn.log">
|
||||
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %c{1} - %msg%n"/>
|
||||
<Filters>
|
||||
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</Filters>
|
||||
</File>
|
||||
|
||||
<File name="ErrorFile" fileName="logs/error.log">
|
||||
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %c{1} - %msg%n"/>
|
||||
<Filters>
|
||||
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</Filters>
|
||||
</File>
|
||||
|
||||
<File name="FatalFile" fileName="logs/fatal.log">
|
||||
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %c{1} - %msg%n"/>
|
||||
<Filters>
|
||||
<ThresholdFilter level="FATAL" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</Filters>
|
||||
</File>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="TraceFile"/>
|
||||
<AppenderRef ref="DebugFile"/>
|
||||
<AppenderRef ref="InfoFile"/>
|
||||
<AppenderRef ref="WarnFile"/>
|
||||
<AppenderRef ref="ErrorFile"/>
|
||||
<AppenderRef ref="FatalFile"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
96
maibu-netty-server/src/main/resources/logback-spring.xml
Normal file
96
maibu-netty-server/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<property name="LOG_HOME" value="logs"/>
|
||||
|
||||
<appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/trace.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/trace.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>TRACE</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/debug.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/info.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/warn.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 控制台输出可选 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>[%d{HH:mm:ss}] [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="trace">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="TRACE_FILE"/>
|
||||
<appender-ref ref="DEBUG_FILE"/>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="WARN_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user