概述

针对作业批改系统(Spring Boot 4.0.4 + OpenClaw Gateway)进行了一轮系统优化,涵盖定时任务、插件稳定性、接口文档、缓存性能四个维度。


一、OpenClaw Cron 定时播报

需求

每周五在 QQ 群自动播报未完成作业的学生名单。

架构

1
2
3
4
5
OpenClaw cron(每周五 20:00)
→ web_fetch → 触发 Spring Boot /api/homework/tasks/remind-all
→ TaskReminderService.sendRecurringStatusReminder()
→ 查 class_student + submission → 未交名单
→ OneBotHttpService → Napcat HTTP → QQ 群消息

关键代码

HomeworkTaskMapper.java — 查活跃作业:

1
2
3
@Select("SELECT * FROM homework_task WHERE status != 'closed' " +
"AND deadline IS NOT NULL AND deadline > NOW() ORDER BY deadline ASC")
List<HomeworkTask> selectActiveWithDeadline();

TaskReminderService.java — 定期播报方法:

1
2
3
4
5
6
7
8
9
public void sendRecurringStatusReminder(Long taskId) {
// 查未提交学生 + 已交/总人数
List<Map<String, Object>> unsubmitted = classStudentMapper
.selectUnsubmittedByTaskId(task.getClassId(), taskId);
Integer submittedCount = classStudentMapper
.countSubmittedByTaskId(task.getClassId(), taskId);
Integer totalCount = classStudentMapper.countByClassId(task.getClassId());
// 构建消息通过 OneBotHttpService 发到 QQ 群
}

HomeworkController.java — 触发端点:

1
2
3
4
5
6
7
8
@GetMapping("/tasks/remind-all")
public ResponseEntity<?> remindAllTasks() {
List<HomeworkTask> activeTasks = taskMapper.selectActiveWithDeadline();
for (HomeworkTask task : activeTasks) {
taskReminderService.sendRecurringStatusReminder(task.getId());
}
return ResponseEntity.ok(Map.of("code", 200, "message", "已处理"));
}

OpenClaw cron 配置

1
2
3
4
5
6
7
8
openclaw cron add \
--name "作业完成情况定时播报" \
--cron "0 20 * * 5" \
--tz "Asia/Shanghai" \
--session isolated \
--agent jarvis \
--light-context \
--message "用 web_fetch 请求 http://localhost:8080/api/homework/tasks/remind-all..."

二、OneBot 插件稳定性修复

问题

QQ 群消息偶尔发不出去,日志显示 bufferLen=0, chunks=0,同时 OneBot 插件反复重载。

根因链

1
2
3
4
5
memory_search 工具卡 126s
→ event loop 延迟飙到 90s
→ 插件健康检查触发重载
→ dispatcher 回调被销毁
→ 消息丢失

修复

jarvis agent 设置 skills: ["homework-grader"],移除 memory-core 技能:

1
2
3
4
5
6
7
8
9
10
{
"agents": {
"list": [
{
"id": "jarvis",
"skills": ["homework-grader"]
}
]
}
}

QQ 群消息收发走 onebot channel plugin,与 skills 无关,不影响消息功能。


三、Dashboard 数据定时上报 RAG

需求

教师端控制台「上传数据库」功能改为每天自动执行,无需手动触发。

实现

DemoApplication.java — 启用调度:

1
@EnableScheduling

DashboardUploadScheduler.java — 定时调度器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@RequiredArgsConstructor
public class DashboardUploadScheduler {
private final ClassInfoMapper classInfoMapper;
private final DashboardService dashboardService;
private final DashboardRagService dashboardRagService;
private final VectorStoreService vectorStoreService;

@Scheduled(cron = "0 0 2 * * ?", zone = "Asia/Shanghai")
public void scheduledUpload() {
List<ClassInfo> classes = classInfoMapper.selectList(null);
for (ClassInfo cls : classes) {
if (vectorStoreService.existsToday("dashboard_" + cls.getId())) {
continue; // 今天已上传则跳过
}
DashboardUploadDTO data = buildUploadData(cls);
dashboardRagService.uploadDashboard(data);
}
}
}

四、Swagger API 文档

技术选型

Spring Boot 4.x 使用 Spring Framework 7.x,knife4j 4.5.0 内置的 springdoc 2.3.0 不兼容。最终使用 springdoc-openapi 3.0.0

依赖

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.0</version>
</dependency>

配置

OpenAPI Bean — 添加 JWT Bearer 认证方案:

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info().title("作业批改系统 API").version("1.0"))
.addSecurityItem(new SecurityRequirement().addList("Bearer"))
.components(new Components()
.addSecuritySchemes("Bearer", new SecurityScheme()
.name("Bearer")
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}

SecurityConfig.java — 放行 Swagger 路径:

1
2
3
.requestMatchers("/api/auth/**", "/api/homework/**",
"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/doc.html", "/webjars/**")
.permitAll()

访问

  • API 文档 JSON:http://localhost:8080/v3/api-docs
  • Swagger UI:http://localhost:8080/swagger-ui.html
  • 右上角 Authorize → 输入 Bearer <JWT Token> → 在线调试

五、Spring Cache 缓存优化

技术选型

Caffeine 本地缓存(内存缓存,零网络开销,适合单机部署)。

依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>

缓存配置

1
2
3
4
5
6
7
8
9
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(200)
.expireAfterWrite(30, TimeUnit.MINUTES)
.recordStats());
return manager;
}

缓存注解

DashboardServiceImpl.java — 查询缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
@CacheConfig(cacheNames = "dashboard")

@Cacheable(key = "'metrics:' + #classId")
public DashboardMetricsDTO getMetrics(Long classId) { ... }

@Cacheable(key = "'scoreDist:' + #classId")
public List<ScoreDistributionDTO> getScoreDistribution(Long classId) { ... }

@Cacheable(key = "'knowledge:' + #classId")
public List<KnowledgeMasteryDTO> getKnowledgeMastery(Long classId) { ... }

@Cacheable(key = "'errors:' + #classId")
public List<FrequentErrorDTO> getFrequentErrors(Long classId) { ... }

缓存清除

学生提交作业和批改完成时清除对应班级缓存:

1
2
3
4
5
6
7
8
// HomeworkController.submitHomework() + GradingStreamConsumer.processOne()
var cache = cacheManager.getCache("dashboard");
if (cache != null) {
cache.evict("metrics:" + classId);
cache.evict("scoreDist:" + classId);
cache.evict("knowledge:" + classId);
cache.evict("errors:" + classId);
}

总结

模块 技术点 效果
定时播报 OpenClaw cron + Spring API 每周五自动推送未交名单
插件稳定 skills 白名单 解决 OneBot 重载丢消息
自动上报 @Scheduled + Caffeine 每日凌晨自动同步 RAG
API 文档 springdoc 3.0.0 + JWT Swagger UI 可视化调试
查询加速 @Cacheable + Caffeine Dashboard 响应毫秒级