software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -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; }
}
}