功能级准入改为「权限键」,`requireRole` 退成默认模板。 权限组 - 权限目录(39 键:键 / 中文名 / 分类 / 默认角色)收口到 shared,路由挂 requirePermission / requireAnyPermission - 有效权限每请求实时算,改完分组立刻生效:ADMIN 全集且 permission:manage、system:manage 不可剥夺; CLASS_DEVICE 空集(不受分组影响);有分组 = 并集(因此也能用来**收回**权限);无分组 = 角色默认模板 - 新增「权限组」页:组增删改、成员分配、生效权限预览 - 班级维度的科目规则(assertCanManageSubjectContent)故意不可被权限组覆盖 客户端设备(ClassHelper 桌面端) - 设备身份是 deviceKey(首次启动生成的 UUID,saveConfig 拒绝改写、退出登录也保留) - 心跳走「DB 记状态 + 客户端定时拉」,不引入新的长连接:30 秒一次,在线窗口 90 秒 - 远控命令 shutdown / restart / logoff / lock:服务端有效期 10 分钟,客户端弹默认选中「取消」的对话框、 60 秒未确认按取消处理,执行后必须回执(重复回执幂等),同一台机器同一条命令只弹一次 - 新增「客户端设备」页:版本一览、一键推送更新、下发远控 客户端安装包由服务端下载并缓存(教室机常常没有外网,浏览器也受 CSP 限制) - POST /api/update/client/fetch 由服务端下载到 data/client-releases/<版本>/ 并登记 sha256 + size - 自动安装三条护栏:没有 sha256 就拒收、必须等到非上课且主窗口隐藏满 5 分钟、冒烟模式永不安装 大模型助手:只解析、不落库 - POST /api/ai/interpret 返回**待确认**的结构化动作(建作业 / 发通知 / 叫人), 真正落库仍调既有端点 —— 权限断言、上课时段紧急通知保护、ClassIsland 推送全部自动复用 - 模型给的 params 用与手动入口**同一份** zod 再校验(模型输出不可信) - API Key 走 AES-256-GCM(密钥由 JWT_SECRET 经 scrypt 派生)加密存 AppSetting,只进不出 - provider=mock 是离线确定性解析,自动化验收一律用它;每次解析写一条 AiAudit(不含密钥) 系统与运维 - 开发者模式(服务端内存缓存开关,热路径不查库;开启后 500 才附 error.stack)、运行诊断(绝不回传密钥) - 开机自启动运行期可改:Windows 改 HKCU\...\Run、Linux systemctl enable/disable, **非安装形态一律返回 supported:false**,不往开发机的注册表里写自启项 - 服务端侧栏折叠只用 CSS 收拢(不重建 DOM),关怀模式只放大字号与控件、不动任何几何令牌 客户端 - 崩溃自愈:渲染进程挂掉 / 超 10 秒无响应先静默重建主窗口(10 分钟最多 3 次), 未捕获异常只在「没在上课、没人看着」时才重启进程;冒烟模式绝不重启(会杀掉验证实例) - 上线补弹离线期间收到的消息只做一次:起点只在「可达 → 不可达」的边界写,恢复时取最近 5 条按时间升序推 发布内容批量 - POST /notifications/bulk、POST /homeworks/bulk:**单班失败不影响整批**,逐班返回结果 - 作业的科目名跨班**按名匹配**已有课程,找不到就整班记失败,绝不自动建课 (自动建课会把创建者写成任课老师,等于越权改任教关系) - 通知与作业同口径按「所属日期」归档,新增 /notifications/days 与 /notifications/creators BREAKING CHANGE: 能不能做某件事不再看角色名,而是看权限键(GET /api/permissions/me)。 未分组的账号按角色默认模板生效,行为与 1.2.0 一致;分组之后生效集合只由组的并集决定。 服务端首次启动会自动应用 1 个数据库迁移(权限组 3 表 / AppSetting / 客户端设备·命令·安装包 / AiAudit / Notification.assignDate / User.careMode,历史通知按本地日期回填),升级前请先备份。
177 lines
7.8 KiB
NSIS
177 lines
7.8 KiB
NSIS
; ClassHelper 服务端 + Web 管理端 Windows 安装程序
|
||
; 由 scripts/dist-server.mjs 生成(占位符在打包时替换)
|
||
;
|
||
; 设计要点:
|
||
; - 当前用户级安装($LOCALAPPDATA\Programs),无需管理员权限即可完成安装
|
||
; - 内置 Node 运行时,目标机无需安装 Node.js
|
||
; - 快捷方式:启动/停止/重启服务、打开管理端、卸载
|
||
; - 默认加入当前用户开机自启(注册表 HKCU\...\Run),随时可用
|
||
; - 卸载时保留 data 目录(数据库)以避免误删数据
|
||
|
||
Unicode true
|
||
SetCompressor /SOLID lzma
|
||
|
||
!include "MUI2.nsh"
|
||
!include "FileFunc.nsh"
|
||
|
||
!define PRODUCT "@PRODUCT@"
|
||
!define VERSION "@VERSION@"
|
||
!define STAGING "@STAGING@"
|
||
!define OUT_FILE "@OUTFILE@"
|
||
!define ICON_FILE "@ICON@"
|
||
!define APP_PORT "@PORT@"
|
||
|
||
Name "${PRODUCT} ${VERSION}"
|
||
OutFile "${OUT_FILE}"
|
||
InstallDir "$LOCALAPPDATA\Programs\ClassHelperServer"
|
||
InstallDirRegKey HKCU "Software\ClassHelperServer" "InstallDir"
|
||
RequestExecutionLevel user
|
||
ShowInstDetails show
|
||
ShowUninstDetails show
|
||
|
||
!define MUI_ICON "${ICON_FILE}"
|
||
!define MUI_UNICON "${ICON_FILE}"
|
||
!define MUI_ABORTWARNING
|
||
!define MUI_FINISHPAGE_TITLE "安装完成"
|
||
!define MUI_FINISHPAGE_TEXT "ClassHelper 服务端已安装完成。$\r$\n$\r$\n若勾选了「开机自启动」,服务会在登录 Windows 后自动运行(可随时在管理端「系统设置」里关闭)。浏览器打开 http://127.0.0.1:${APP_PORT}/ 即可使用管理端。$\r$\n$\r$\n默认账号:admin / admin123(首次登录后请立即修改密码)"
|
||
!define MUI_FINISHPAGE_RUN
|
||
!define MUI_FINISHPAGE_RUN_TEXT "立即启动服务并打开管理端"
|
||
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchService
|
||
!define MUI_LICENSEPAGE_TEXT_TOP "请阅读以下说明后继续安装。"
|
||
|
||
!insertmacro MUI_PAGE_WELCOME
|
||
!insertmacro MUI_PAGE_LICENSE "${STAGING}\LICENSE.txt"
|
||
!insertmacro MUI_PAGE_DIRECTORY
|
||
; 组件页:让"开机自启动"成为**可选**项(需求:「支持选择开机自启动」)。
|
||
; 默认勾选(保持既有行为),取消勾选则完全不写注册表 Run 项 ——
|
||
; 装完之后仍然可以在 Web 端「系统设置」里随时开关(PUT /api/system/autostart)。
|
||
!insertmacro MUI_PAGE_COMPONENTS
|
||
!insertmacro MUI_PAGE_INSTFILES
|
||
!insertmacro MUI_PAGE_FINISH
|
||
|
||
!insertmacro MUI_UNPAGE_CONFIRM
|
||
!insertmacro MUI_UNPAGE_INSTFILES
|
||
|
||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||
|
||
Function LaunchService
|
||
ExecShell "open" "$INSTDIR\start.cmd" "" SW_SHOWMINIMIZED
|
||
Sleep 3000
|
||
ExecShell "open" "http://127.0.0.1:${APP_PORT}/"
|
||
FunctionEnd
|
||
|
||
; ------------------------------------------------------------------ 安装
|
||
|
||
Section "主程序" SecMain
|
||
SectionIn RO
|
||
SetOutPath "$INSTDIR"
|
||
|
||
; 若目标目录已有旧版本,先停止正在运行的服务,避免文件占用
|
||
IfFileExists "$INSTDIR\data\server.pid" 0 +3
|
||
nsExec::ExecToLog '"$INSTDIR\stop.cmd"'
|
||
Sleep 1500
|
||
|
||
; 升级安装时保留原有配置(含 JWT 密钥与端口设置),避免用户被强制重新登录
|
||
IfFileExists "$INSTDIR\.env" 0 +2
|
||
Rename "$INSTDIR\.env" "$INSTDIR\.env.previous"
|
||
|
||
; 先清掉旧版本的代码目录再铺新文件:
|
||
; 否则 web/assets 里的旧 hash 分片、server/dist 旧模块、node_modules 旧依赖会残留,
|
||
; 出现"新旧版本混装"(例如 shared/dist 是旧的,服务启动直接报找不到导出)。
|
||
RMDir /r "$INSTDIR\web"
|
||
RMDir /r "$INSTDIR\server"
|
||
RMDir /r "$INSTDIR\node_modules"
|
||
|
||
File /r "${STAGING}\*.*"
|
||
|
||
IfFileExists "$INSTDIR\.env.previous" 0 +3
|
||
Delete "$INSTDIR\.env"
|
||
Rename "$INSTDIR\.env.previous" "$INSTDIR\.env"
|
||
|
||
WriteRegStr HKCU "Software\ClassHelperServer" "InstallDir" "$INSTDIR"
|
||
WriteRegStr HKCU "Software\ClassHelperServer" "Version" "${VERSION}"
|
||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||
|
||
; 开始菜单
|
||
CreateDirectory "$SMPROGRAMS\${PRODUCT}"
|
||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\启动服务.lnk" "$INSTDIR\start.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
|
||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\停止服务.lnk" "$INSTDIR\stop.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
|
||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\重启服务.lnk" "$INSTDIR\restart.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
|
||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\打开管理端.lnk" "http://127.0.0.1:${APP_PORT}/"
|
||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\使用说明.lnk" "$INSTDIR\README.txt"
|
||
CreateShortCut "$SMPROGRAMS\${PRODUCT}\卸载.lnk" "$INSTDIR\uninstall.exe"
|
||
|
||
; 桌面快捷方式(管理端进浏览器)
|
||
CreateShortCut "$DESKTOP\${PRODUCT}.lnk" "http://127.0.0.1:${APP_PORT}/"
|
||
|
||
; 开机自启改到**组件页里的可选 Section**(见下面的 SecAutostart):
|
||
; 这里不再无条件写注册表,由用户在组件页勾选决定(需求:「支持选择开机自启动」)。
|
||
|
||
; 注册到"应用和功能"列表,便于通过系统设置卸载
|
||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayName" "${PRODUCT} ${VERSION}"
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayIcon" "$INSTDIR\uninstall.exe"
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayVersion" "${VERSION}"
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "Publisher" "Class Helper"
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "InstallLocation" "$INSTDIR"
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "EstimatedSize" "$0"
|
||
|
||
; 首次启动即初始化数据库并开始提供服务
|
||
ExecShell "open" "$INSTDIR\start.cmd" "" SW_SHOWMINIMIZED
|
||
SectionEnd
|
||
|
||
; ------------------------------------------------------------------ 开机自启动
|
||
;
|
||
; 独立成一个 Section,于是它会出现在组件页上(默认勾选 = 与历史行为一致),
|
||
; 用户可以取消勾选、装完之后也能在 Web 端「系统设置」里随时开关
|
||
; (服务端接口 `PUT /api/system/autostart`,Windows 走的就是这个注册表 Run 项)。
|
||
Section "开机自启动(登录 Windows 后自动启动服务,推荐)" SecAutostart
|
||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "ClassHelperServer" '"$INSTDIR\start.cmd"'
|
||
SectionEnd
|
||
|
||
; ------------------------------------------------------------------ 卸载
|
||
|
||
Section "Uninstall"
|
||
; 停止服务
|
||
IfFileExists "$INSTDIR\data\server.pid" 0 +3
|
||
nsExec::ExecToLog '"$INSTDIR\stop.cmd"'
|
||
Sleep 1200
|
||
|
||
; 兜底:按命令行特征结束残留进程
|
||
nsExec::ExecToLog 'taskkill /F /FI "WINDOWTITLE eq ClassHelperServer*"'
|
||
|
||
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "ClassHelperServer"
|
||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer"
|
||
DeleteRegKey HKCU "Software\ClassHelperServer"
|
||
|
||
Delete "$DESKTOP\${PRODUCT}.lnk"
|
||
RMDir /r "$SMPROGRAMS\${PRODUCT}"
|
||
|
||
; 保留数据目录,避免误删数据库:重命名为 data.backup
|
||
IfFileExists "$INSTDIR\data\classhelper.db" 0 +3
|
||
RMDir /r "$INSTDIR\data.backup"
|
||
Rename "$INSTDIR\data" "$INSTDIR\data.backup"
|
||
|
||
RMDir /r "$INSTDIR\server"
|
||
RMDir /r "$INSTDIR\web"
|
||
RMDir /r "$INSTDIR\node_modules"
|
||
RMDir /r "$INSTDIR\logs"
|
||
Delete "$INSTDIR\node.exe"
|
||
Delete "$INSTDIR\package.json"
|
||
Delete "$INSTDIR\package-lock.json"
|
||
Delete "$INSTDIR\LICENSE.txt"
|
||
Delete "$INSTDIR\start.cmd"
|
||
Delete "$INSTDIR\stop.cmd"
|
||
Delete "$INSTDIR\restart.cmd"
|
||
Delete "$INSTDIR\status.cmd"
|
||
Delete "$INSTDIR\install-service.cmd"
|
||
Delete "$INSTDIR\uninstall-service.cmd"
|
||
Delete "$INSTDIR\README.txt"
|
||
Delete "$INSTDIR\uninstall.exe"
|
||
|
||
; 若用户未保留 .env,则一并清理目录
|
||
Delete "$INSTDIR\.env"
|
||
RMDir "$INSTDIR"
|
||
SectionEnd
|