/** * 自然写互动课堂教学管理云平台软件 V1.0 * * 设备管理控制器 * 负责点阵笔、网关、终端设备的注册、绑定、状态查询等接口 */ package com.writech.cloud.controller; import com.writech.cloud.WritechCloudApplication.ApiResponse; import com.writech.cloud.WritechCloudApplication.BusinessException; import com.writech.cloud.model.Device; import com.writech.cloud.service.DeviceService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import java.time.LocalDateTime; import java.util.*; /** * 设备控制器 - /api/v1/device * * 管理互动课堂中涉及的所有智能硬件设备: * - 点阵笔(pen):学生书写工具,通过BLE连接网关 * - 网关设备(gateway):教室中枢,管理多支笔的连接与数据转发 * - 终端设备(terminal):黑板、PC、电视、平板等显示终端 * - 算力盒(edge_box):教室端AI推理设备 */ @RestController @RequestMapping("/api/v1/device") public class DeviceController { @Autowired private DeviceService deviceService; /** * 设备注册接口 * POST /api/v1/device/register * * 将新设备注册到云平台,绑定至指定用户和学校 * 注册时校验设备MAC地址唯一性和设备证书有效性 * * @param request 注册请求(MAC地址、设备类型、序列号等) * @return 注册成功后的设备信息 */ @PostMapping("/register") public ApiResponse registerDevice( @Valid @RequestBody DeviceRegisterRequest request) { // 校验设备MAC地址格式 if (!isValidMacAddress(request.getMacAddr())) { throw new BusinessException(400, "无效的MAC地址格式"); } // 检查设备是否已注册 Device existing = deviceService.findByMacAddr(request.getMacAddr()); if (existing != null) { throw new BusinessException(409, "设备已注册,MAC地址: " + request.getMacAddr()); } // 校验设备证书(X.509) boolean certValid = deviceService.validateDeviceCertificate( request.getMacAddr(), request.getDeviceCert()); if (!certValid) { throw new BusinessException(403, "设备证书校验失败,拒绝注册"); } // 创建设备记录 Device device = new Device(); device.setId(UUID.randomUUID().toString().replace("-", "")); device.setType(request.getDeviceType()); device.setMacAddr(request.getMacAddr()); device.setSerialNumber(request.getSerialNumber()); device.setFirmwareVersion(request.getFirmwareVersion()); device.setBindUserId(request.getUserId()); device.setSchoolId(request.getSchoolId()); device.setClassroomId(request.getClassroomId()); device.setStatus(1); // 1=在线 device.setRegisterTime(LocalDateTime.now()); device.setLastHeartbeat(LocalDateTime.now()); deviceService.save(device); // 返回注册结果 DeviceRegisterResponse response = new DeviceRegisterResponse(); response.setDeviceId(device.getId()); response.setMacAddr(device.getMacAddr()); response.setDeviceType(device.getType()); response.setRegisteredAt(device.getRegisterTime()); return ApiResponse.success(response); } /** * 设备绑定接口 * POST /api/v1/device/bind * * 将已注册设备绑定至指定用户(教师/学生) * 一支笔只能绑定一个用户,一个用户可绑定多支笔 */ @PostMapping("/bind") public ApiResponse bindDevice(@Valid @RequestBody DeviceBindRequest request) { Device device = deviceService.findById(request.getDeviceId()); if (device == null) { throw new BusinessException(404, "设备不存在"); } // 检查笔是否已被其他用户绑定 if ("pen".equals(device.getType()) && device.getBindUserId() != null && !device.getBindUserId().equals(request.getUserId())) { throw new BusinessException(409, "该笔已绑定其他用户,请先解绑"); } deviceService.bindDevice(request.getDeviceId(), request.getUserId(), request.getClassroomId()); return ApiResponse.success(); } /** * 设备解绑接口 * POST /api/v1/device/unbind */ @PostMapping("/unbind") public ApiResponse unbindDevice(@RequestBody DeviceUnbindRequest request) { deviceService.unbindDevice(request.getDeviceId()); return ApiResponse.success(); } /** * 查询设备列表 * GET /api/v1/device/list * * 按学校/教室/设备类型/状态等条件分页查询设备 */ @GetMapping("/list") public ApiResponse> listDevices( @RequestParam(required = false) String schoolId, @RequestParam(required = false) String classroomId, @RequestParam(required = false) String deviceType, @RequestParam(required = false) Integer status, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { Page devices = deviceService.queryDevices( schoolId, classroomId, deviceType, status, PageRequest.of(page, size)); return ApiResponse.success(devices); } /** * 查询单个设备详情 * GET /api/v1/device/{id} */ @GetMapping("/{id}") public ApiResponse getDevice(@PathVariable String id) { Device device = deviceService.findById(id); if (device == null) { throw new BusinessException(404, "设备不存在"); } DeviceDetailResponse detail = new DeviceDetailResponse(); detail.setDeviceId(device.getId()); detail.setType(device.getType()); detail.setMacAddr(device.getMacAddr()); detail.setSerialNumber(device.getSerialNumber()); detail.setFirmwareVersion(device.getFirmwareVersion()); detail.setStatus(device.getStatus()); detail.setBindUserId(device.getBindUserId()); detail.setSchoolId(device.getSchoolId()); detail.setClassroomId(device.getClassroomId()); detail.setBatteryLevel(device.getBatteryLevel()); detail.setLastHeartbeat(device.getLastHeartbeat()); detail.setRegisterTime(device.getRegisterTime()); return ApiResponse.success(detail); } /** * 设备心跳上报接口 * POST /api/v1/device/heartbeat * * 设备定期上报在线状态、电量、连接笔数等信息 * 网关设备每30秒上报一次,笔设备每5分钟上报一次 */ @PostMapping("/heartbeat") public ApiResponse heartbeat(@Valid @RequestBody HeartbeatRequest request) { Device device = deviceService.findById(request.getDeviceId()); if (device == null) { throw new BusinessException(404, "设备不存在"); } // 更新设备状态 device.setStatus(1); // 在线 device.setLastHeartbeat(LocalDateTime.now()); device.setBatteryLevel(request.getBatteryLevel()); if (request.getConnectedPenCount() != null) { device.setConnectedPenCount(request.getConnectedPenCount()); } if (request.getCpuUsage() != null) { device.setCpuUsage(request.getCpuUsage()); } if (request.getMemoryUsage() != null) { device.setMemoryUsage(request.getMemoryUsage()); } deviceService.updateHeartbeat(device); return ApiResponse.success(); } /** * 批量查询教室设备拓扑 * GET /api/v1/device/topology/{classroomId} * * 返回指定教室中所有设备的连接拓扑关系 * 包括网关、笔、算力盒、黑板等设备的层级关系 */ @GetMapping("/topology/{classroomId}") public ApiResponse getTopology(@PathVariable String classroomId) { ClassroomTopology topology = deviceService.buildClassroomTopology(classroomId); return ApiResponse.success(topology); } // ==================== 内部方法 ==================== /** MAC地址格式校验(支持 XX:XX:XX:XX:XX:XX 和 XX-XX-XX-XX-XX-XX) */ private boolean isValidMacAddress(String mac) { if (mac == null) return false; return mac.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"); } // ==================== DTO 定义 ==================== /** 设备注册请求 */ public static class DeviceRegisterRequest { @NotBlank(message = "设备类型不能为空") private String deviceType; // pen/gateway/terminal/edge_box @NotBlank(message = "MAC地址不能为空") private String macAddr; private String serialNumber; private String firmwareVersion; private String userId; private String schoolId; private String classroomId; private String deviceCert; // X.509设备证书 public String getDeviceType() { return deviceType; } public void setDeviceType(String t) { this.deviceType = t; } public String getMacAddr() { return macAddr; } public void setMacAddr(String m) { this.macAddr = m; } public String getSerialNumber() { return serialNumber; } public void setSerialNumber(String s) { this.serialNumber = s; } public String getFirmwareVersion() { return firmwareVersion; } public void setFirmwareVersion(String v) { this.firmwareVersion = v; } public String getUserId() { return userId; } public void setUserId(String id) { this.userId = id; } public String getSchoolId() { return schoolId; } public void setSchoolId(String id) { this.schoolId = id; } public String getClassroomId() { return classroomId; } public void setClassroomId(String id) { this.classroomId = id; } public String getDeviceCert() { return deviceCert; } public void setDeviceCert(String c) { this.deviceCert = c; } } /** 设备注册响应 */ public static class DeviceRegisterResponse { private String deviceId; private String macAddr; private String deviceType; private LocalDateTime registeredAt; public String getDeviceId() { return deviceId; } public void setDeviceId(String id) { this.deviceId = id; } public String getMacAddr() { return macAddr; } public void setMacAddr(String m) { this.macAddr = m; } public String getDeviceType() { return deviceType; } public void setDeviceType(String t) { this.deviceType = t; } public LocalDateTime getRegisteredAt() { return registeredAt; } public void setRegisteredAt(LocalDateTime t) { this.registeredAt = t; } } /** 设备绑定请求 */ public static class DeviceBindRequest { @NotBlank private String deviceId; @NotBlank private String userId; private String classroomId; public String getDeviceId() { return deviceId; } public void setDeviceId(String id) { this.deviceId = id; } public String getUserId() { return userId; } public void setUserId(String id) { this.userId = id; } public String getClassroomId() { return classroomId; } public void setClassroomId(String id) { this.classroomId = id; } } /** 设备解绑请求 */ public static class DeviceUnbindRequest { private String deviceId; public String getDeviceId() { return deviceId; } public void setDeviceId(String id) { this.deviceId = id; } } /** 心跳请求 */ public static class HeartbeatRequest { @NotBlank private String deviceId; private Integer batteryLevel; private Integer connectedPenCount; private Double cpuUsage; private Double memoryUsage; public String getDeviceId() { return deviceId; } public void setDeviceId(String id) { this.deviceId = id; } public Integer getBatteryLevel() { return batteryLevel; } public void setBatteryLevel(Integer l) { this.batteryLevel = l; } public Integer getConnectedPenCount() { return connectedPenCount; } public void setConnectedPenCount(Integer c) { this.connectedPenCount = c; } public Double getCpuUsage() { return cpuUsage; } public void setCpuUsage(Double u) { this.cpuUsage = u; } public Double getMemoryUsage() { return memoryUsage; } public void setMemoryUsage(Double u) { this.memoryUsage = u; } } /** 设备详情响应 */ public static class DeviceDetailResponse { private String deviceId; private String type; private String macAddr; private String serialNumber; private String firmwareVersion; private int status; private String bindUserId; private String schoolId; private String classroomId; private Integer batteryLevel; private LocalDateTime lastHeartbeat; private LocalDateTime registerTime; public String getDeviceId() { return deviceId; } public void setDeviceId(String id) { this.deviceId = id; } public String getType() { return type; } public void setType(String t) { this.type = t; } public String getMacAddr() { return macAddr; } public void setMacAddr(String m) { this.macAddr = m; } public String getSerialNumber() { return serialNumber; } public void setSerialNumber(String s) { this.serialNumber = s; } public String getFirmwareVersion() { return firmwareVersion; } public void setFirmwareVersion(String v) { this.firmwareVersion = v; } public int getStatus() { return status; } public void setStatus(int s) { this.status = s; } public String getBindUserId() { return bindUserId; } public void setBindUserId(String id) { this.bindUserId = id; } public String getSchoolId() { return schoolId; } public void setSchoolId(String id) { this.schoolId = id; } public String getClassroomId() { return classroomId; } public void setClassroomId(String id) { this.classroomId = id; } public Integer getBatteryLevel() { return batteryLevel; } public void setBatteryLevel(Integer l) { this.batteryLevel = l; } public LocalDateTime getLastHeartbeat() { return lastHeartbeat; } public void setLastHeartbeat(LocalDateTime t) { this.lastHeartbeat = t; } public LocalDateTime getRegisterTime() { return registerTime; } public void setRegisterTime(LocalDateTime t) { this.registerTime = t; } } /** 教室拓扑结构 */ public static class ClassroomTopology { private String classroomId; private String classroomName; private List gateways; private List edgeBoxes; private List terminals; private List pens; private int totalDeviceCount; public String getClassroomId() { return classroomId; } public void setClassroomId(String id) { this.classroomId = id; } public String getClassroomName() { return classroomName; } public void setClassroomName(String n) { this.classroomName = n; } public List getGateways() { return gateways; } public void setGateways(List g) { this.gateways = g; } public List getEdgeBoxes() { return edgeBoxes; } public void setEdgeBoxes(List e) { this.edgeBoxes = e; } public List getTerminals() { return terminals; } public void setTerminals(List t) { this.terminals = t; } public List getPens() { return pens; } public void setPens(List p) { this.pens = p; } public int getTotalDeviceCount() { return totalDeviceCount; } public void setTotalDeviceCount(int c) { this.totalDeviceCount = c; } } }