fix(clients,client,web): 远控指令的两个修复 —— 执行失败不再谎报成功 + 「上次指令」让"点了没反应"可见(1.3.2)

背景:用户报「下发锁屏等等指令不生效」。先把服务端那条链路单独验了一遍
(模拟客户端:登记 → 下发 → 心跳带回 → 设备列表可见 → 回执 → 清除),**整条是通的**;
真正的问题在两处。

客户端:执行失败会谎报成功
- `spawn` 找不到可执行文件 / 没权限时**不抛异常**,而是在下一个 tick 发一个异步 `error` 事件 ——
  只包 `try/catch` 抓不到,代码一路走到 `return { ok: true }`,回执写着「已下达系统命令」,
  Web 端看起来一切正常而机器纹丝不动
- 现在等一个 200ms 的窗口确认没有立刻报错,失败如实回执(`client-agent.ts` 的 `executeCommand`)

Web 端:点了没生效,界面上什么都不说
- 一条命令被**取消 / 过期**之后就从 `pendingCommand` 里消失,页面留白 ——
  而最常见的两种"没反应"恰恰是「本机没人确认(客户端 60 秒自动按取消)」与「下达时机器离线」
- `GET /clients/devices` 增加 `lastCommand`(最近一条,不论状态);
  「客户端」页的设备表多一列「上次指令」,由 `commandStateText()` 翻成一句人话
  (已执行 / 本机用户取消 / 已过期 —— 下达时机器不在线 / 待执行)
- 下发成功的提示补上「60 秒内没人点确认就按取消处理」

文档
- AGENTS.md §5 第 69 条记下这两条,并写明排查同类反馈的顺序:**先看那台机器的客户端版本是否 ≥ 1.3.0**
  (远控命令的处理 `client-agent.ts` 是 1.3.0 才有的,旧客户端连心跳都不发)
- docs/reference.md 4.15 的表格补上「二次确认」的准确口径与新增的「上次指令」列

验收:`lint` / `typecheck` / `check:icons` / `docs:check` 全绿;
后端端到端 **247/247**、联动链路 **44/44**、插件静态契约 **76/76**、
Web 真实点击 **34/34**、客户端冒烟 **90/90**;
远控链路另用模拟客户端跑通,并确认新增的「上次指令」在待执行与已回执两种状态下都显示正确。
This commit is contained in:
LoveChengke committed 2026-10-11 17:00:11 +08:00
1 parent 227117ccb3
commit b7cdc7c9ea
32 files changed
+192 -175

No files matched your search

+1 -1
View File
@@ -139,7 +139,7 @@ export const apiModules: ApiModule[] = [
## 几条贯穿全局的硬约定
这些都是踩过坑之后固化下来的,改代码前值得先看(完整版在 `AGENTS.md`):
这些都是长期演进中固化下来的,改代码前值得先看(完整版在 `AGENTS.md`):
| 约定 | 为什么 |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ |
+5 -2
View File
@@ -112,14 +112,17 @@ pnpm verify:classisland-plugin # 静态契约校验(不需要 .NET,不需
## 部署到教室机器
把编出来的 DLL 覆盖到这台机器的 ClassIsland 数据目录(默认 `%USERPROFILE%\ClassIsland\data`,
便携版则是安装目录下的 `data\`)。ClassIsland **运行时会锁住 DLL,必须先完全退出它**:
```powershell
# 1) 完全退出 ClassIsland(它会锁住 DLL)
Get-Process ClassIsland.Desktop | Stop-Process -Force
# 2) 覆盖插件 DLL(**不要覆盖 Settings.json**,那里存着服务器地址与设备令牌)
Copy-Item packages\classisland-plugin\bin\Release\ClassHelper.ClassIslandPlugin.dll `
D:\Classisland\data\Plugins\classhelper.classisland.bridge\ -Force
"$env:USERPROFILE\ClassIsland\data\Plugins\classhelper.classisland.bridge\" -Force
# 3) 重新启动
Start-Process D:\Classisland\ClassIsland.exe -WorkingDirectory D:\Classisland
Start-Process "$env:USERPROFILE\ClassIsland\ClassIsland.exe"
```
改配置要在 **ClassIsland 退出时**改:运行中改会被内存里的副本随时回写覆盖。
+1 -1
View File
@@ -141,7 +141,7 @@ add_permission_groups_and_clients
## 开发时的模型改动
1. 改 `schema.prisma`;
2. 在 `prisma/migrations/` 下加一个迁移目录(本机用
2. 在 `prisma/migrations/` 下加一个迁移目录(用
`pnpm --filter @classhelper/server db:deploy` 应用,**别用 `pnpm db:migrate`** ——
它在 Windows 上不可靠)。要改已有表结构而不只是加列时,可以先用
`pnpm exec prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --script`
+4 -4
View File
@@ -48,8 +48,8 @@ ClassIsland(教室机器)──► 联动插件 ──► /api/integrations/
## 仓库里另外两份文档
- **[完整参考](../reference.md)** —— 产品说明书 + 验收对照表(原 README 全文),
功能行为的**权威来源**;仓库首页那份 README 是精简后的门面页,细节都在这里。
- **[完整参考](../reference.md)** —— 产品说明书(原 README 全文),功能行为的**权威来源**;
仓库首页那份 README 是精简后的门面页,细节都在这里。
- **[`AGENTS.md`](https://github.com/LoveChengke/classhelper/blob/master/AGENTS.md)** ——
写给 AI 编码代理的工程约定与本机踩坑记录,**改代码前必读**。
里面每一条硬约定都对应一次真实的翻车,比代码注释更值得先看一遍。
工程约定与开发环境注意事项,**改代码前必读**。里面每一条硬约定都对应一处容易踩空的地方,
比代码注释更值得先看一遍。
+2 -2
View File
@@ -191,8 +191,8 @@ gh workflow run release-linux-server.yml -f tag=v1.1.3 -f attach=true
> **CI 是按 tag 的 ref 检出源码的。** 发完版又发现构建脚本(或任何影响出包的代码)要修时,
> **光推 master 不够** —— CI 仍会拿旧 tag 的源码跑,得把 tag 挪到修复后的提交:
> `git tag -f v<版本> <提交> && git push --force origin v<版本>`,tag 一变会自动重跑。
> 1.3.0 实测踩过:tag 指的那个提交里打包脚本有个 bug,CI 红了、Release 上少一个 Linux 包,
> 而本地 `pnpm dist:server` 一直是绿的(Windows 侧走另一条分支)。
> 别拿「Windows 侧绿不绿」当判据:`pnpm dist:server` 走的是另一条分支,
> Linux 那一步红的时候本地完全看不出来。
>
> 另外,本页第 8 步的「官网与文档站」是**另一条**流水线(master 推送触发)——
> 它绿不代表 Linux 包出来了,推完 tag 要去 `gh run list --limit 5` 看一眼。
+11 -10
View File
@@ -53,21 +53,22 @@ pnpm build && pnpm verify:web
| `verify:web`「课表科目为全校统一目录」 | 种子给每个班建满了全部科目,下拉里就没有可自动建课的「统一科目」分组 |
| `verify:desktop`「课表今天时间轴」 | 凌晨约 00:00–03:00 时「已结束」探针被夹紧成 0 长度而被过滤 |
| `verify:desktop`「作业看板全屏自适应」 | 演示数据某天有 6 条长作业、而冒烟窗口只有约 543px 高时,缩放被下限 0.45 钳住 → 必然溢出 |
| 岛交互 / 岛动画 / 岛截图留档 | **AI 会话里会间歇性失败**:置顶透明小窗的 rAF 被限流、形变采样抓不到帧。**先原样重跑一次再动手改** |
| 岛交互 / 岛动画 / 岛截图留档 | 窗口被遮挡或跑在远程会话里时会间歇性失败:置顶透明小窗的 rAF 被限流、形变采样抓不到帧。**先原样重跑一次再动手改** |
## 大面积报「登录失败 / 401」时
先核对本机 `teacher1` 的密码 —— 本机实测可能被改成了 `123456`,不是种子默认的 `teacher123`
(原因是「修改密码」弹窗留空会落到默认初始密码)。**处理方式是用环境变量传实际凭据,不要去改数据。**
先确认演示账号的**当前**口令。种子脚本硬编码的是 `teacher123`,但「修改密码」弹窗留空会落到
默认初始密码(`DEFAULT_TEACHER_PASSWORD`,默认 `123456`),任何一次误操作都会把它改掉 ——
而**两个演示账号不一定一起漂**,所以不能简单地全局替换。
两个演示账号的口令**不一定一起漂**,所以不能简单地全局替换。
**处理方式是用环境变量传实际凭据,不要去改数据。**
| 脚本 | 覆盖凭据的方式 |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `verify:desktop` | `set "ELECTRON_SMOKE_USER=teacher1" && set "ELECTRON_SMOKE_PASSWORD=123456" && pnpm verify:desktop` |
| `verify:packaged` | 同上(`ELECTRON_SMOKE_*`) |
| `verify:web` | `set "UI_SMOKE_USER=teacher1" && set "UI_SMOKE_PASS=123456" && pnpm verify:web`(另有 `UI_SMOKE_ADMIN*`) |
| `verify:e2e` | **不支持环境变量**,需用临时副本替换(且副本必须放在 `packages/server/scripts/` 里,否则 `import 'socket.io-client'` 会失败) |
| 脚本 | 覆盖凭据的方式 |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verify:desktop` | `set "ELECTRON_SMOKE_USER=teacher1" && set "ELECTRON_SMOKE_PASSWORD=123456" && pnpm verify:desktop` |
| `verify:packaged` | 同上(`ELECTRON_SMOKE_*`) |
| `verify:web` | `set "UI_SMOKE_USER=teacher1" && set "UI_SMOKE_PASS=123456" && pnpm verify:web`(另有 `UI_SMOKE_ADMIN*`) |
| `verify:e2e` | **不支持环境变量**(口令硬编码在脚本里),需自己用改过口令的临时副本跑;副本必须放在 `packages/server/scripts/` 里,否则 `import 'socket.io-client'` 会失败 |
## 冒烟会真的动你的屏幕
+1 -1
View File
@@ -1,6 +1,6 @@
# WinIsland 视觉设计系统(Design Token 规格)
来源:`D:\classhelper\.cache\winisland-ref\WinIsland-master`(只读 checkout)。
来源:[WinIsland](https://github.com/WinIslandProject/WinIsland) 的源码(`WinIsland-master`,只读 checkout)。
所有数值均取自代码常量,标注了「常量名 = 值 @ 文件」。未在代码中找到的项明确写「未找到」。
关键前提:**岛内所有几何尺寸都乘以 `compact_scale`(默认 1.0)或 `expanded_scale`(默认 1.0)**,