Files
system-design/software-copyright/01-writech-cloud-platform/controller/DeviceController.java
T
2026-03-22 15:24:40 +08:00

392 lines
16 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 自然写互动课堂教学管理云平台软件 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<DeviceRegisterResponse> 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<Void> 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<Void> unbindDevice(@RequestBody DeviceUnbindRequest request) {
deviceService.unbindDevice(request.getDeviceId());
return ApiResponse.success();
}
/**
* 查询设备列表
* GET /api/v1/device/list
*
* 按学校/教室/设备类型/状态等条件分页查询设备
*/
@GetMapping("/list")
public ApiResponse<Page<Device>> 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<Device> devices = deviceService.queryDevices(
schoolId, classroomId, deviceType, status,
PageRequest.of(page, size));
return ApiResponse.success(devices);
}
/**
* 查询单个设备详情
* GET /api/v1/device/{id}
*/
@GetMapping("/{id}")
public ApiResponse<DeviceDetailResponse> 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<Void> 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<ClassroomTopology> 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<Device> gateways;
private List<Device> edgeBoxes;
private List<Device> terminals;
private List<Device> 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<Device> getGateways() { return gateways; }
public void setGateways(List<Device> g) { this.gateways = g; }
public List<Device> getEdgeBoxes() { return edgeBoxes; }
public void setEdgeBoxes(List<Device> e) { this.edgeBoxes = e; }
public List<Device> getTerminals() { return terminals; }
public void setTerminals(List<Device> t) { this.terminals = t; }
public List<Device> getPens() { return pens; }
public void setPens(List<Device> p) { this.pens = p; }
public int getTotalDeviceCount() { return totalDeviceCount; }
public void setTotalDeviceCount(int c) { this.totalDeviceCount = c; }
}
}