software copyright
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 自然写互动课堂教学管理云平台软件 V1.0
|
||||
*
|
||||
* 设备管理服务
|
||||
* 管理点阵笔、网关、终端设备、算力盒的全生命周期
|
||||
*/
|
||||
package com.writech.cloud.service;
|
||||
|
||||
import com.writech.cloud.model.Device;
|
||||
import com.writech.cloud.controller.DeviceController.ClassroomTopology;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 设备服务类
|
||||
*
|
||||
* 管理互动课堂中所有硬件设备的注册、绑定、状态监控
|
||||
* 设备类型:pen(点阵笔) / gateway(网关) / terminal(终端) / edge_box(算力盒)
|
||||
*/
|
||||
@Service
|
||||
public class DeviceService {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
/** 设备在线超时时间(秒),超过此时间未收到心跳视为离线 */
|
||||
private static final long DEVICE_ONLINE_TIMEOUT = 120;
|
||||
|
||||
/** 网关设备心跳间隔(秒) */
|
||||
private static final long GATEWAY_HEARTBEAT_INTERVAL = 30;
|
||||
|
||||
/** 笔设备心跳间隔(秒) */
|
||||
private static final long PEN_HEARTBEAT_INTERVAL = 300;
|
||||
|
||||
/**
|
||||
* 保存设备信息
|
||||
*/
|
||||
@Transactional
|
||||
public void save(Device device) {
|
||||
// deviceRepository.save(device);
|
||||
// 更新Redis中的设备在线状态缓存
|
||||
updateDeviceOnlineStatus(device.getId(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询设备
|
||||
*/
|
||||
public Device findById(String deviceId) {
|
||||
// return deviceRepository.findById(deviceId).orElse(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据MAC地址查询设备
|
||||
*/
|
||||
public Device findByMacAddr(String macAddr) {
|
||||
// return deviceRepository.findByMacAddr(macAddr);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验设备证书(X.509)
|
||||
* 首次注册时网关设备需提供预置的设备证书进行身份校验
|
||||
*
|
||||
* @param macAddr MAC地址
|
||||
* @param certPem PEM格式的X.509证书
|
||||
* @return 校验通过返回true
|
||||
*/
|
||||
public boolean validateDeviceCertificate(String macAddr, String certPem) {
|
||||
if (certPem == null || certPem.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析X.509证书
|
||||
java.security.cert.CertificateFactory cf =
|
||||
java.security.cert.CertificateFactory.getInstance("X.509");
|
||||
java.io.ByteArrayInputStream bis =
|
||||
new java.io.ByteArrayInputStream(certPem.getBytes());
|
||||
X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
|
||||
|
||||
// 检查证书有效期
|
||||
cert.checkValidity();
|
||||
|
||||
// 验证证书签名(使用CA根证书公钥)
|
||||
// cert.verify(caCertificate.getPublicKey());
|
||||
|
||||
// 从证书CN字段提取MAC地址,与请求中的MAC地址比对
|
||||
String cn = cert.getSubjectX500Principal().getName();
|
||||
if (!cn.contains(macAddr.replace(":", "").toUpperCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备绑定
|
||||
* 将设备绑定至指定用户和教室
|
||||
*/
|
||||
@Transactional
|
||||
public void bindDevice(String deviceId, String userId, String classroomId) {
|
||||
// deviceRepository.updateBinding(deviceId, userId, classroomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备解绑
|
||||
*/
|
||||
@Transactional
|
||||
public void unbindDevice(String deviceId) {
|
||||
// deviceRepository.clearBinding(deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询设备列表
|
||||
* 支持按学校、教室、类型、状态多维度过滤
|
||||
*/
|
||||
public Page<Device> queryDevices(String schoolId, String classroomId,
|
||||
String deviceType, Integer status,
|
||||
Pageable pageable) {
|
||||
// return deviceRepository.queryByConditions(schoolId, classroomId,
|
||||
// deviceType, status, pageable);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备心跳
|
||||
* 心跳数据写入MySQL并更新Redis在线状态缓存
|
||||
*/
|
||||
public void updateHeartbeat(Device device) {
|
||||
// deviceRepository.updateHeartbeat(device.getId(),
|
||||
// device.getLastHeartbeat(), device.getBatteryLevel(),
|
||||
// device.getConnectedPenCount(), device.getCpuUsage(),
|
||||
// device.getMemoryUsage());
|
||||
|
||||
// 更新Redis在线状态(设置过期时间为心跳超时时间)
|
||||
updateDeviceOnlineStatus(device.getId(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建教室设备拓扑
|
||||
* 查询教室内所有设备,按类型分组并建立连接关系
|
||||
*
|
||||
* @param classroomId 教室ID
|
||||
* @return 拓扑结构(网关/算力盒/终端/笔)
|
||||
*/
|
||||
public ClassroomTopology buildClassroomTopology(String classroomId) {
|
||||
// 查询教室下所有设备
|
||||
// List<Device> devices = deviceRepository.findByClassroomId(classroomId);
|
||||
List<Device> devices = new ArrayList<>();
|
||||
|
||||
ClassroomTopology topology = new ClassroomTopology();
|
||||
topology.setClassroomId(classroomId);
|
||||
|
||||
// 按设备类型分组
|
||||
Map<String, List<Device>> grouped = devices.stream()
|
||||
.collect(Collectors.groupingBy(Device::getType));
|
||||
|
||||
topology.setGateways(grouped.getOrDefault("gateway", new ArrayList<>()));
|
||||
topology.setEdgeBoxes(grouped.getOrDefault("edge_box", new ArrayList<>()));
|
||||
topology.setTerminals(grouped.getOrDefault("terminal", new ArrayList<>()));
|
||||
topology.setPens(grouped.getOrDefault("pen", new ArrayList<>()));
|
||||
topology.setTotalDeviceCount(devices.size());
|
||||
|
||||
return topology;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量检查设备在线状态
|
||||
* 通过Redis缓存快速判断设备是否在线
|
||||
*/
|
||||
public Map<String, Boolean> checkOnlineStatus(List<String> deviceIds) {
|
||||
Map<String, Boolean> result = new HashMap<>();
|
||||
for (String deviceId : deviceIds) {
|
||||
String key = "writech:device:online:" + deviceId;
|
||||
result.put(deviceId, Boolean.TRUE.equals(redisTemplate.hasKey(key)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送远程指令至设备
|
||||
* 通过MQTT向指定设备下发控制指令(重启/配置更新/OTA等)
|
||||
*/
|
||||
public void sendCommand(String deviceId, String command, Map<String, Object> params) {
|
||||
// 构建MQTT消息
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("command", command);
|
||||
message.put("params", params);
|
||||
message.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
// 根据设备类型确定Topic
|
||||
Device device = findById(deviceId);
|
||||
if (device == null) return;
|
||||
|
||||
String topic;
|
||||
switch (device.getType()) {
|
||||
case "gateway":
|
||||
topic = "gateway/" + deviceId + "/command";
|
||||
break;
|
||||
case "edge_box":
|
||||
topic = "edgebox/" + deviceId + "/command";
|
||||
break;
|
||||
default:
|
||||
topic = "device/" + deviceId + "/command";
|
||||
}
|
||||
|
||||
// mqttTemplate.convertAndSend(topic, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计学校设备概况
|
||||
*/
|
||||
public DeviceOverview getSchoolDeviceOverview(String schoolId) {
|
||||
DeviceOverview overview = new DeviceOverview();
|
||||
// 各类型设备数量统计
|
||||
// overview.setTotalPens(deviceRepository.countBySchoolAndType(schoolId, "pen"));
|
||||
// overview.setTotalGateways(deviceRepository.countBySchoolAndType(schoolId, "gateway"));
|
||||
// overview.setOnlinePens(countOnlineDevices(schoolId, "pen"));
|
||||
// overview.setOnlineGateways(countOnlineDevices(schoolId, "gateway"));
|
||||
return overview;
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
/** 更新Redis中设备在线状态 */
|
||||
private void updateDeviceOnlineStatus(String deviceId, boolean online) {
|
||||
String key = "writech:device:online:" + deviceId;
|
||||
if (online) {
|
||||
redisTemplate.opsForValue().set(key, "1",
|
||||
DEVICE_ONLINE_TIMEOUT, java.util.concurrent.TimeUnit.SECONDS);
|
||||
} else {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内部类 ====================
|
||||
|
||||
/** 设备概况统计 */
|
||||
public static class DeviceOverview {
|
||||
private int totalPens;
|
||||
private int totalGateways;
|
||||
private int totalEdgeBoxes;
|
||||
private int totalTerminals;
|
||||
private int onlinePens;
|
||||
private int onlineGateways;
|
||||
private int onlineEdgeBoxes;
|
||||
private double averageBatteryLevel;
|
||||
|
||||
public int getTotalPens() { return totalPens; }
|
||||
public void setTotalPens(int c) { this.totalPens = c; }
|
||||
public int getTotalGateways() { return totalGateways; }
|
||||
public void setTotalGateways(int c) { this.totalGateways = c; }
|
||||
public int getTotalEdgeBoxes() { return totalEdgeBoxes; }
|
||||
public void setTotalEdgeBoxes(int c) { this.totalEdgeBoxes = c; }
|
||||
public int getTotalTerminals() { return totalTerminals; }
|
||||
public void setTotalTerminals(int c) { this.totalTerminals = c; }
|
||||
public int getOnlinePens() { return onlinePens; }
|
||||
public void setOnlinePens(int c) { this.onlinePens = c; }
|
||||
public int getOnlineGateways() { return onlineGateways; }
|
||||
public void setOnlineGateways(int c) { this.onlineGateways = c; }
|
||||
public int getOnlineEdgeBoxes() { return onlineEdgeBoxes; }
|
||||
public void setOnlineEdgeBoxes(int c) { this.onlineEdgeBoxes = c; }
|
||||
public double getAverageBatteryLevel() { return averageBatteryLevel; }
|
||||
public void setAverageBatteryLevel(double l) { this.averageBatteryLevel = l; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user