feat(integrations,classisland,server,web,desktop): ClassIsland 联动插件——上报课表与上课状态、下发提醒并回执、教室端推送通道
教室机器上的 ClassIsland 现在可以直接和班级小助手互通:插件走 /api/integrations/classisland/*,用设备令牌(X-ClassIsland-Token,库里只存 sha256)独立鉴权,不占用 JWT 通道。 - 服务端:新增 integrations 模块(上报 / 待弹出 / 回执 / 课表镜像 / 回写),两个迁移(设备令牌表、Class.notificationChannel);通知、叫人、联动页下发统一复用 pushToClassIsland,避免"某个页面发的通知不联动" - 插件:packages/classisland-plugin(.NET 8,独立于 pnpm workspace)——BridgeService 上报与提醒轮询、ScheduleMapper/ClassPlanWriter 课表镜像、Avalonia 设置页、ClassHelperNotificationProvider 在 ClassIsland 上弹出提醒;本地/内网地址强制直连,躲开系统代理返回的假 404 - 客户端:设置页可选提醒通道(both / client / classisland),并订阅相应实时事件 - Web 端:新增「ClassIsland 联动」页(设备管理与提醒下发) - 校验与打包:verify:classisland(需后端)、verify:classisland-plugin(静态契约 54 项)、build/dist:classisland-plugin 产出 .cipx - 文档:README 与 AGENTS.md 同步新增联动说明,docs/screenshots/classisland 留三张真机截图 门禁:pnpm lint 0 error、pnpm typecheck 四包通过、verify:classisland-plugin 54/54 通过。运行期仍需在装了 ClassIsland 的机器上实测(步骤见 AGENTS.md §8)。
This commit is contained in:
1 parent
fe41ebb7d6
commit
d0830f6830
61 files changed
+7026
-93
No files matched your search
@@ -0,0 +1,158 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace ClassHelper.ClassIslandPlugin.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 插件设置。
|
||||
///
|
||||
/// 存储由 ClassIsland 负责:插件入口把它保存在 <c>PluginConfigFolder/Settings.json</c>
|
||||
/// (**不要**放在插件安装目录,那里不会随档案备份、升级时还会被清掉)。
|
||||
/// 因此这里必须继承 <see cref="ObservableObject"/> 并让每个属性都走 <c>SetProperty</c>:
|
||||
/// ClassIsland 通过监听 <c>PropertyChanged</c> 来即时保存。
|
||||
/// </summary>
|
||||
public class PluginSettings : ObservableObject
|
||||
{
|
||||
private string _serverUrl = "http://127.0.0.1:4000";
|
||||
private string _deviceToken = "";
|
||||
private string _deviceKey = "";
|
||||
private bool _autoReport = true;
|
||||
private int _reportIntervalSeconds = 60;
|
||||
private bool _uploadSchedule = true;
|
||||
private bool _mirrorSchedule = false;
|
||||
private bool _receiveNotifications = true;
|
||||
private int _notificationDurationSeconds = 8;
|
||||
private int _notificationPollSeconds = 10;
|
||||
private bool _speechEnabled = false;
|
||||
private bool _showDebugInfo = false;
|
||||
|
||||
/// <summary>
|
||||
/// 班级小助手后端地址,例如 <c>http://127.0.0.1:4000</c>。
|
||||
/// </summary>
|
||||
public string ServerUrl
|
||||
{
|
||||
get => _serverUrl;
|
||||
set => SetProperty(ref _serverUrl, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备令牌(形如 <c>chci_...</c>):在班级小助手 Web 端「ClassIsland 联动」页面生成。
|
||||
/// </summary>
|
||||
public string DeviceToken
|
||||
{
|
||||
get => _deviceToken;
|
||||
set => SetProperty(ref _deviceToken, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本机标识(首次生成后固定):服务端据此把同一台机器识别为同一设备,重装插件不会产生重复设备。
|
||||
/// </summary>
|
||||
public string DeviceKey
|
||||
{
|
||||
get => _deviceKey;
|
||||
set => SetProperty(ref _deviceKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动上报:启动后、课表变化时、以及每隔 <see cref="ReportIntervalSeconds"/> 秒各上报一次。
|
||||
/// </summary>
|
||||
public bool AutoReport
|
||||
{
|
||||
get => _autoReport;
|
||||
set => SetProperty(ref _autoReport, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时上报间隔(秒),最小 15 秒。
|
||||
/// </summary>
|
||||
public int ReportIntervalSeconds
|
||||
{
|
||||
get => _reportIntervalSeconds;
|
||||
set => SetProperty(ref _reportIntervalSeconds, Math.Clamp(value, 15, 3600));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否把 ClassIsland 的课表同步到班级小助手。
|
||||
/// 关闭后仍会上报"当前上什么课"的状态,只是不再写课表。
|
||||
/// </summary>
|
||||
public bool UploadSchedule
|
||||
{
|
||||
get => _uploadSchedule;
|
||||
set => SetProperty(ref _uploadSchedule, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否把班级小助手上排好的课表镜像回 ClassIsland
|
||||
/// (新建一份名为「班级小助手-<班级名>」的档案课表,不覆盖老师原有的课表)。
|
||||
/// </summary>
|
||||
public bool MirrorSchedule
|
||||
{
|
||||
get => _mirrorSchedule;
|
||||
set => SetProperty(ref _mirrorSchedule, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否接收老师在班级小助手上发布的提醒,并在 ClassIsland 上弹出。
|
||||
/// </summary>
|
||||
public bool ReceiveNotifications
|
||||
{
|
||||
get => _receiveNotifications;
|
||||
set => SetProperty(ref _receiveNotifications, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提醒轮询间隔(秒),最小 5 秒。
|
||||
///
|
||||
/// 插件与后端之间没有长连接(刻意不引入 Socket 依赖),提醒是"轮询取回"的,
|
||||
/// 因此这个值就是**老师按下发送到教室弹出之间的最大延迟**。
|
||||
/// 单独一个轻量轮询(只查待提醒,不上传课表)比把上报间隔改小划算得多。
|
||||
/// </summary>
|
||||
public int NotificationPollSeconds
|
||||
{
|
||||
get => _notificationPollSeconds;
|
||||
set => SetProperty(ref _notificationPollSeconds, Math.Clamp(value, 5, 600));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认提醒显示时长(秒);老师在下发时可以单独指定更长的时长。
|
||||
/// </summary>
|
||||
public int NotificationDurationSeconds
|
||||
{
|
||||
get => _notificationDurationSeconds;
|
||||
set => SetProperty(ref _notificationDurationSeconds, Math.Clamp(value, 1, 120));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提醒弹出时是否语音朗读(沿用 ClassIsland 的语音设置与语音提供方)。
|
||||
/// </summary>
|
||||
public bool SpeechEnabled
|
||||
{
|
||||
get => _speechEnabled;
|
||||
set => SetProperty(ref _speechEnabled, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否在设置页显示调试信息(最后一次上报结果、课表条目数等),排查问题时打开。
|
||||
/// </summary>
|
||||
public bool ShowDebugInfo
|
||||
{
|
||||
get => _showDebugInfo;
|
||||
set => SetProperty(ref _showDebugInfo, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 兼容旧配置:反序列化后把 DeviceKey 补齐(首次运行时由服务生成)。
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
{
|
||||
ServerUrl = (ServerUrl ?? "").Trim().TrimEnd('/');
|
||||
DeviceToken = (DeviceToken ?? "").Trim();
|
||||
DeviceKey = (DeviceKey ?? "").Trim();
|
||||
ReportIntervalSeconds = Math.Clamp(ReportIntervalSeconds, 15, 3600);
|
||||
NotificationDurationSeconds = Math.Clamp(NotificationDurationSeconds, 1, 120);
|
||||
NotificationPollSeconds = Math.Clamp(NotificationPollSeconds, 5, 600);
|
||||
}
|
||||
|
||||
/// <summary>是否已经配置好(地址 + 令牌都填了)。</summary>
|
||||
public bool IsConfigured =>
|
||||
Uri.TryCreate(ServerUrl, UriKind.Absolute, out _) && DeviceToken.StartsWith("chci_", StringComparison.Ordinal);
|
||||
}
|
||||
Reference in new issue
Block a user