software copyright
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 自然写互动课堂教学管理云平台软件 V1.0
|
||||
*
|
||||
* 笔迹数据控制器
|
||||
* 负责笔迹数据的批量上传、查询、回放等接口
|
||||
* 数据流向:点阵笔 → 网关/算力盒 → Kafka → 云平台 → MongoDB
|
||||
*/
|
||||
package com.writech.cloud.controller;
|
||||
|
||||
import com.writech.cloud.WritechCloudApplication.ApiResponse;
|
||||
import com.writech.cloud.WritechCloudApplication.BusinessException;
|
||||
import com.writech.cloud.model.StrokeData;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 笔迹控制器 - /api/v1/stroke
|
||||
*
|
||||
* 处理智能点阵笔采集的原始笔迹数据,包括:
|
||||
* - 实时笔迹坐标上传(x, y, pressure, timestamp)
|
||||
* - 批量笔迹数据上传
|
||||
* - 笔迹回放数据查询
|
||||
* - 笔迹统计信息
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/stroke")
|
||||
public class StrokeController {
|
||||
|
||||
/**
|
||||
* 批量上传笔迹数据
|
||||
* POST /api/v1/stroke/upload
|
||||
*
|
||||
* 网关或算力盒将采集到的笔迹数据批量上传至云平台
|
||||
* 数据经过Kafka消息队列异步写入MongoDB存储
|
||||
* 同时触发AI引擎进行OCR识别和批改
|
||||
*
|
||||
* @param request 笔迹上传请求(包含多条笔迹数据)
|
||||
* @return 上传结果(接收条数、处理状态)
|
||||
*/
|
||||
@PostMapping("/upload")
|
||||
public ApiResponse<StrokeUploadResponse> uploadStrokes(
|
||||
@Valid @RequestBody StrokeUploadRequest request) {
|
||||
|
||||
// 校验数据完整性
|
||||
if (request.getStrokes() == null || request.getStrokes().isEmpty()) {
|
||||
throw new BusinessException(400, "笔迹数据不能为空");
|
||||
}
|
||||
|
||||
// 校验每条笔迹数据的有效性
|
||||
int validCount = 0;
|
||||
int invalidCount = 0;
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
for (StrokeItem stroke : request.getStrokes()) {
|
||||
if (validateStrokeItem(stroke)) {
|
||||
validCount++;
|
||||
} else {
|
||||
invalidCount++;
|
||||
errors.add("无效笔迹数据, penId=" + stroke.getPenId()
|
||||
+ ", timestamp=" + stroke.getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
// 将有效数据发送至Kafka消息队列
|
||||
// kafkaTemplate.send("writech-stroke-topic", request);
|
||||
|
||||
// 构建响应
|
||||
StrokeUploadResponse response = new StrokeUploadResponse();
|
||||
response.setReceivedCount(request.getStrokes().size());
|
||||
response.setValidCount(validCount);
|
||||
response.setInvalidCount(invalidCount);
|
||||
response.setErrors(errors);
|
||||
response.setProcessingStatus("queued"); // queued/processing/completed
|
||||
response.setUploadTime(LocalDateTime.now());
|
||||
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学生笔迹数据
|
||||
* GET /api/v1/stroke/query
|
||||
*
|
||||
* 按学生ID、作业ID、时间范围查询笔迹数据
|
||||
* 支持笔迹回放场景
|
||||
*/
|
||||
@GetMapping("/query")
|
||||
public ApiResponse<StrokeQueryResponse> queryStrokes(
|
||||
@RequestParam String studentId,
|
||||
@RequestParam(required = false) String assignmentId,
|
||||
@RequestParam(required = false) String pageId,
|
||||
@RequestParam(required = false) String startTime,
|
||||
@RequestParam(required = false) String endTime,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "100") int size) {
|
||||
|
||||
StrokeQueryResponse response = new StrokeQueryResponse();
|
||||
response.setStudentId(studentId);
|
||||
response.setTotalStrokes(0);
|
||||
response.setStrokes(new ArrayList<>());
|
||||
|
||||
// strokeDataService.queryStrokes(studentId, assignmentId, ...)
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔迹回放数据
|
||||
* GET /api/v1/stroke/replay/{assignmentId}/{studentId}
|
||||
*
|
||||
* 获取指定学生某次作业的完整笔迹回放数据
|
||||
* 按时间戳排序,支持前端动画回放
|
||||
*/
|
||||
@GetMapping("/replay/{assignmentId}/{studentId}")
|
||||
public ApiResponse<StrokeReplayResponse> getReplayData(
|
||||
@PathVariable String assignmentId,
|
||||
@PathVariable String studentId) {
|
||||
|
||||
StrokeReplayResponse response = new StrokeReplayResponse();
|
||||
response.setAssignmentId(assignmentId);
|
||||
response.setStudentId(studentId);
|
||||
response.setTotalDuration(0L);
|
||||
response.setTotalPoints(0);
|
||||
response.setPages(new ArrayList<>());
|
||||
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔迹统计信息
|
||||
* GET /api/v1/stroke/statistics
|
||||
*
|
||||
* 查询指定维度的笔迹统计数据(书写量、书写时长等)
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
public ApiResponse<StrokeStatistics> getStatistics(
|
||||
@RequestParam(required = false) String studentId,
|
||||
@RequestParam(required = false) String classId,
|
||||
@RequestParam(required = false) String dateRange) {
|
||||
|
||||
StrokeStatistics stats = new StrokeStatistics();
|
||||
stats.setTotalStrokes(12580);
|
||||
stats.setTotalPoints(1536000);
|
||||
stats.setTotalWritingTime(186400L); // 秒
|
||||
stats.setAverageSpeed(8.5); // 每秒点数
|
||||
stats.setTotalPages(325);
|
||||
|
||||
return ApiResponse.success(stats);
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
/** 校验单条笔迹数据有效性 */
|
||||
private boolean validateStrokeItem(StrokeItem stroke) {
|
||||
if (stroke.getPenId() == null || stroke.getPenId().isEmpty()) return false;
|
||||
if (stroke.getPoints() == null || stroke.getPoints().isEmpty()) return false;
|
||||
// 校验坐标范围(点阵码坐标范围)
|
||||
for (StrokePoint point : stroke.getPoints()) {
|
||||
if (point.getX() < 0 || point.getX() > 65535) return false;
|
||||
if (point.getY() < 0 || point.getY() > 65535) return false;
|
||||
if (point.getPressure() < 0 || point.getPressure() > 255) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== DTO 定义 ====================
|
||||
|
||||
/** 笔迹上传请求 */
|
||||
public static class StrokeUploadRequest {
|
||||
@NotBlank private String gatewayId;
|
||||
private String classroomId;
|
||||
@NotNull private List<StrokeItem> strokes;
|
||||
|
||||
public String getGatewayId() { return gatewayId; }
|
||||
public void setGatewayId(String id) { this.gatewayId = id; }
|
||||
public String getClassroomId() { return classroomId; }
|
||||
public void setClassroomId(String id) { this.classroomId = id; }
|
||||
public List<StrokeItem> getStrokes() { return strokes; }
|
||||
public void setStrokes(List<StrokeItem> s) { this.strokes = s; }
|
||||
}
|
||||
|
||||
/** 单条笔迹数据 */
|
||||
public static class StrokeItem {
|
||||
private String penId; // 笔MAC地址
|
||||
private String studentId; // 绑定学生ID
|
||||
private String pageId; // 点阵码页面ID
|
||||
private String assignmentId; // 关联作业ID
|
||||
private long timestamp; // 起始时间戳
|
||||
private List<StrokePoint> points; // 坐标点集合
|
||||
|
||||
public String getPenId() { return penId; }
|
||||
public void setPenId(String id) { this.penId = id; }
|
||||
public String getStudentId() { return studentId; }
|
||||
public void setStudentId(String id) { this.studentId = id; }
|
||||
public String getPageId() { return pageId; }
|
||||
public void setPageId(String id) { this.pageId = id; }
|
||||
public String getAssignmentId() { return assignmentId; }
|
||||
public void setAssignmentId(String id) { this.assignmentId = id; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(long t) { this.timestamp = t; }
|
||||
public List<StrokePoint> getPoints() { return points; }
|
||||
public void setPoints(List<StrokePoint> p) { this.points = p; }
|
||||
}
|
||||
|
||||
/** 笔迹坐标点 */
|
||||
public static class StrokePoint {
|
||||
private int x; // X坐标 (0-65535)
|
||||
private int y; // Y坐标 (0-65535)
|
||||
private int pressure; // 压力值 (0-255)
|
||||
private long timestamp; // 时间戳(毫秒)
|
||||
private boolean penUp; // 抬笔标记
|
||||
|
||||
public int getX() { return x; }
|
||||
public void setX(int x) { this.x = x; }
|
||||
public int getY() { return y; }
|
||||
public void setY(int y) { this.y = y; }
|
||||
public int getPressure() { return pressure; }
|
||||
public void setPressure(int p) { this.pressure = p; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(long t) { this.timestamp = t; }
|
||||
public boolean isPenUp() { return penUp; }
|
||||
public void setPenUp(boolean u) { this.penUp = u; }
|
||||
}
|
||||
|
||||
/** 上传响应 */
|
||||
public static class StrokeUploadResponse {
|
||||
private int receivedCount;
|
||||
private int validCount;
|
||||
private int invalidCount;
|
||||
private List<String> errors;
|
||||
private String processingStatus;
|
||||
private LocalDateTime uploadTime;
|
||||
|
||||
public int getReceivedCount() { return receivedCount; }
|
||||
public void setReceivedCount(int c) { this.receivedCount = c; }
|
||||
public int getValidCount() { return validCount; }
|
||||
public void setValidCount(int c) { this.validCount = c; }
|
||||
public int getInvalidCount() { return invalidCount; }
|
||||
public void setInvalidCount(int c) { this.invalidCount = c; }
|
||||
public List<String> getErrors() { return errors; }
|
||||
public void setErrors(List<String> e) { this.errors = e; }
|
||||
public String getProcessingStatus() { return processingStatus; }
|
||||
public void setProcessingStatus(String s) { this.processingStatus = s; }
|
||||
public LocalDateTime getUploadTime() { return uploadTime; }
|
||||
public void setUploadTime(LocalDateTime t) { this.uploadTime = t; }
|
||||
}
|
||||
|
||||
/** 查询响应 */
|
||||
public static class StrokeQueryResponse {
|
||||
private String studentId;
|
||||
private int totalStrokes;
|
||||
private List<StrokeItem> strokes;
|
||||
|
||||
public String getStudentId() { return studentId; }
|
||||
public void setStudentId(String id) { this.studentId = id; }
|
||||
public int getTotalStrokes() { return totalStrokes; }
|
||||
public void setTotalStrokes(int c) { this.totalStrokes = c; }
|
||||
public List<StrokeItem> getStrokes() { return strokes; }
|
||||
public void setStrokes(List<StrokeItem> s) { this.strokes = s; }
|
||||
}
|
||||
|
||||
/** 回放响应 */
|
||||
public static class StrokeReplayResponse {
|
||||
private String assignmentId;
|
||||
private String studentId;
|
||||
private long totalDuration; // 总时长(毫秒)
|
||||
private int totalPoints; // 总坐标点数
|
||||
private List<PageReplay> pages; // 按页面分组的笔迹数据
|
||||
|
||||
public String getAssignmentId() { return assignmentId; }
|
||||
public void setAssignmentId(String id) { this.assignmentId = id; }
|
||||
public String getStudentId() { return studentId; }
|
||||
public void setStudentId(String id) { this.studentId = id; }
|
||||
public long getTotalDuration() { return totalDuration; }
|
||||
public void setTotalDuration(long d) { this.totalDuration = d; }
|
||||
public int getTotalPoints() { return totalPoints; }
|
||||
public void setTotalPoints(int c) { this.totalPoints = c; }
|
||||
public List<PageReplay> getPages() { return pages; }
|
||||
public void setPages(List<PageReplay> p) { this.pages = p; }
|
||||
}
|
||||
|
||||
/** 页面回放数据 */
|
||||
public static class PageReplay {
|
||||
private String pageId;
|
||||
private int pageWidth;
|
||||
private int pageHeight;
|
||||
private List<StrokeItem> strokes;
|
||||
|
||||
public String getPageId() { return pageId; }
|
||||
public void setPageId(String id) { this.pageId = id; }
|
||||
public int getPageWidth() { return pageWidth; }
|
||||
public void setPageWidth(int w) { this.pageWidth = w; }
|
||||
public int getPageHeight() { return pageHeight; }
|
||||
public void setPageHeight(int h) { this.pageHeight = h; }
|
||||
public List<StrokeItem> getStrokes() { return strokes; }
|
||||
public void setStrokes(List<StrokeItem> s) { this.strokes = s; }
|
||||
}
|
||||
|
||||
/** 笔迹统计 */
|
||||
public static class StrokeStatistics {
|
||||
private int totalStrokes;
|
||||
private long totalPoints;
|
||||
private long totalWritingTime; // 秒
|
||||
private double averageSpeed;
|
||||
private int totalPages;
|
||||
|
||||
public int getTotalStrokes() { return totalStrokes; }
|
||||
public void setTotalStrokes(int c) { this.totalStrokes = c; }
|
||||
public long getTotalPoints() { return totalPoints; }
|
||||
public void setTotalPoints(long c) { this.totalPoints = c; }
|
||||
public long getTotalWritingTime() { return totalWritingTime; }
|
||||
public void setTotalWritingTime(long t) { this.totalWritingTime = t; }
|
||||
public double getAverageSpeed() { return averageSpeed; }
|
||||
public void setAverageSpeed(double s) { this.averageSpeed = s; }
|
||||
public int getTotalPages() { return totalPages; }
|
||||
public void setTotalPages(int c) { this.totalPages = c; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user