software copyright
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 自然写互动课堂教学管理云平台软件 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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user