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

323 lines
13 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
*
* 笔迹数据控制器
* 负责笔迹数据的批量上传、查询、回放等接口
* 数据流向:点阵笔 → 网关/算力盒 → 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; }
}
}