/** * 自然写互动课堂教学管理云平台软件 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 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 devices = deviceRepository.findByClassroomId(classroomId); List devices = new ArrayList<>(); ClassroomTopology topology = new ClassroomTopology(); topology.setClassroomId(classroomId); // 按设备类型分组 Map> 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 checkOnlineStatus(List deviceIds) { Map 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 params) { // 构建MQTT消息 Map 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; } } }