diff --git a/src/main/java/com/yunhai/userhome/service/WidgetDataService.java b/src/main/java/com/yunhai/userhome/service/WidgetDataService.java
index 63a7d12..4dab6d8 100644
--- a/src/main/java/com/yunhai/userhome/service/WidgetDataService.java
+++ b/src/main/java/com/yunhai/userhome/service/WidgetDataService.java
@@ -1,7 +1,10 @@
package com.yunhai.userhome.service;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.yunhai.platform.openapi.core.YschOpenCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
@@ -11,15 +14,21 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
- * 小组件统一取数(方案 §3.4 已拍板:先按 L2 可溯源 mock)。
+ * 小组件统一取数。
*
- *
字段名逐一等于 design/widget-snapshots-old-bsch/*.js 的旧接口口径,接入真实数据源前不外扩;
- * 未知 code 返回 null 由调用方兜空数据,不抛异常。
+ * {@code syllabus}(我的课表)已对接 AcaSyllabu {@code GET /oapi/timetable/Get}(出站走 {@link YschOpenCall},
+ * 带 yh-app-auth 签名;目标 {@code acasyllabu} 配于 {@code yh.call.targets}),**原样透传** {@code data[]}
+ * 班级数组(含 {@code timetable.slots[].subjects[7]}),由前端按班级维度渲染。
+ *
+ * {@code evaluate} / {@code weekplan} 仍为 L2 可溯源 mock(字段名逐一等于
+ * {@code design/widget-snapshots-old-bsch/*.js} 的旧接口口径);未知 code 返回 null 由调用方兜空数据,不抛异常。
*/
@Service
public class WidgetDataService {
@@ -37,15 +46,36 @@ public class WidgetDataService {
/** 听评课旧接口 pageSize 默认值(evaluate_index.js 传 count=5) */
private static final int EVALUATE_DEFAULT_COUNT = 5;
+ /** 课表 oapi 目标名(见 yh.call.targets.acasyllabu) */
+ private static final String TARGET_ACASYLLABU = "acasyllabu";
+
+ /** 课表 oapi 路径(签名按目标 ctx 剥离,故此处不含 /acasyllabu) */
+ private static final String PATH_TIMETABLE = "/oapi/timetable/Get";
+
+ /** 课表缓存时长:课表低频变更,避免每次进桌面都打出站 */
+ private static final long SYLLABUS_TTL_MS = 90_000L;
+
+ /** 课表取数失败后的静默期(短 TTL 空结果,避免下游停摆时持续打出站) */
+ private static final long SYLLABUS_FAIL_BACKOFF_MS = 15_000L;
+
+ /** 课表缓存条目上限,超出时清理过期项 */
+ private static final int SYLLABUS_CACHE_MAX = 500;
+
+ @Autowired
+ private YschOpenCall openCall;
+
+ /** key = userId|roleId|schId → 缓存条目 */
+ private final ConcurrentHashMap syllabusCache = new ConcurrentHashMap<>();
+
/**
* 按数据键取数;params 为 urlencoded 查询串(对齐旧 otherParams 口径)。
*
* @return 数据结构由数据键决定;null = 该键未接入
*/
- public Object getData(String code, String params, Integer schId) {
+ public Object getData(String code, String params, Caller caller) {
if (CODE_EVALUATE.equals(code)) return evaluate(params);
- if (CODE_SYLLABUS.equals(code)) return syllabus();
- if (CODE_WEEKPLAN.equals(code)) return weekplan(params, schId);
+ if (CODE_SYLLABUS.equals(code)) return syllabus(caller);
+ if (CODE_WEEKPLAN.equals(code)) return weekplan(params, caller == null ? null : caller.getSchId());
log.warn("[widget] 未配置的数据键:{}", code);
return null;
}
@@ -76,29 +106,41 @@ public class WidgetDataService {
}
/**
- * 我的课表:数组,一格一行(节次 CourseTimes × 星期 Weeks)。
- * // TODO 接入 oapi: openCall.getList(TARGET_USERBASE, "/oapi/...", null, Vo.class)
+ * 我的课表:AcaSyllabu {@code GET /oapi/timetable/Get?userid=&roleId=&schId=}(SDK 出站,签名自动)。
+ * 原样返回 {@code data[]} 班级数组;无可见范围返回 {@code []};不可达 / 验签失败降级空数组(不抛 500)。
+ *
+ * 身份三参均来自服务端会话推导({@link Caller}),不接受前端传入;roleId<=0 时不透传
+ * (oapi 侧默认按普通教师处理)。
*/
- private List