项目开源地址:https://github.com/tigerZZY/Classroom-Instant-Messaging-Notification-System

编写背景:

本人为成都某不知名高中的高三学生,上学期我们班主任看见其他班有一个消息通知系统,便让本牛马 编写一个我们班自己的通知系统,于是便有了这个程序的诞生。

所实现的功能:

目录

  1. 🚀 快速入门
  2. 👤 用户角色与权限
  3. 🔐 注册与登录
  4. 📨 消息通知系统
  5. ⏰ 上课/课间消息策略
  6. 🎙️ 语音消息
  7. 🎂 生日祝福系统
  8. 🌤️ 天气模块
  9. 📋 课表管理
  10. ⚙️ 系统设置
  11. 👥 用户管理(管理员)
  12. 💻 ClassIsland 桌面插件
  13. 📱 确认消息专用页面
  14. ❓ 常见问题 FAQ

🚀 快速入门

这是什么?

强基五班班级管理系统是一个专为班级打造的 Web 消息平台,帮助老师高效发送通知、叫人和公告。支持网页端 + ClassIsland 桌面插件,消息到达即时弹窗 + 语音播报,课间自动提醒未确认消息。

核心功能一览

功能 说明
📢 消息通知 三种类型(通知 / 叫人 / 公告),支持置顶、星标、附件
🎙️ 语音消息 网页端直接录音发送,课间自动语音播报
🔔 课间提醒 课间自动轮播未确认消息,弹窗 + TTS 语音
🎂 生日祝福 自动提醒当天生日同学,ClassIsland 插件实时显示
📋 课表管理 多课表轮换支持(单/双周),教室大屏轮播
💻 桌面插件 ClassIsland 插件:状态栏实时显示消息和生日

👤 用户角色与权限

系统有三种角色,各司其职:

👑 管理员(Admin)

  • 权限范围
    • ✅ 查看所有消息(不限发送者)
    • ✅ 发布消息、置顶、星标
    • ✅ 管理用户(激活、禁用、删除)
    • ✅ 修改系统设置(上下学时间、课间时段等)
    • ✅ 管理课表、生日数据
    • ✅ 批量删除消息
    • 🔇 不播放语音播报(管理员端静默接收)

👨‍🏫 教师(Teacher)

  • 用户名格式teacher_xxx(如 teacher_何建伟
  • 权限范围
    • ✅ 发布消息到本班
    • ✅ 查看自己发送的消息
    • ❌ 不能查看其他教师的消息(保护隐私)
    • ❌ 不能管理用户和系统设置
    • 🔇 不播放语音播报
    • ⚠️ 注册后需管理员激活

👩‍🎓 班级用户(Class)

  • 用户名格式class_xxx(如 class_5
  • 权限范围
    • ✅ 接收所有通知(管理员 + 教师发出的)
    • ✅ 可以发布消息(仅管理员可见,用于反馈)
    • ✅ 确认消息(标记已读)
    • 🔊 播放语音播报(非自己发的消息)
    • ❌ 不能管理后台功能

权限速查表

操作 Admin Teacher Class
查看所有消息 ❌ 仅自己
发布消息
语音播报接收 🔇 🔇 🔊
管理用户
修改系统设置
确认消息
批量操作

🔐 注册与登录

注册流程

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ 填写信息  │ ──▶ │ 提交注册  │ ──▶ │ 等待审核  │ ──▶ │ 审核通过  │
│          │     │          │     │ (pending) │     │ (active)  │
└──────────┘     └──────────┘     └──────────┘     └──────────┘

注册注意事项

  1. 用户名格式要求

    • 教师:必须以 teacher_ 开头。例如 teacher_张三 ✅ | 张三
    • 班级用户:必须以 class_ 开头。例如 class_5 ✅ | 5班
  2. 必填信息

    • 用户名(格式见上)
    • 密码(至少 6 位)
    • 确认密码(必须与密码一致)
    • 显示名称(会显示在消息列表中)
    • 角色选择(教师 / 班级用户)
  3. 审核机制:注册成功后账号状态为"待激活(pending)",管理员审核通过后方可登录。这是为了防止无关人员随意注册。

📨 消息通知系统

三种消息类型

类型 图标 用途 课间表现
📢 通知 🔔 日常通知(作业、活动、安排等) 弹窗 + TTS 语音播报
📞 叫人 📞 紧急召唤(叫某位同学来办公室等) 弹窗 + 特殊提示音
📣 公告 📣 重要公告(全班须知事项) 弹窗 + 公告提示音

发送消息

  1. 点击首页顶部"发布消息"按钮
  2. 填写消息内容:
    • 标题:消息标题(必填)
    • 内容:消息正文
    • 类型:选择通知 / 叫人 / 公告
    • 优先级:普通 / 紧急(紧急消息红色标记)
    • 附件:支持图片、文档、音频(最多 10 个附件,单文件 ≤ 50MB)
    • 置顶:勾选后消息始终排在列表最前面
  3. 点击"发布" → 系统自动创建回执并推送

消息接收与确认

  • 教室端(Class 角色):收到消息 → 弹窗 + 语音播报
  • 消息列表:未读消息有红色标记,已确认消息灰色标记
  • 确认操作:点击消息旁的"确认"按钮 → 标记已读
  • 待确认统计:首页统计卡片显示"待确认 X 条"

消息列表功能

功能 说明
📌 置顶 重要消息固定在列表顶部(管理员操作)
⭐ 星标 收藏常用消息
🗑️ 删除 单条删除或批量勾选后删除
✅ 批量确认 勾选多条消息后一次性确认
📎 附件预览 点击附件图标预览/下载
🔊 语音播放 消息含语音附件可在线播放

消息列表横向滚动

首页消息区域采用横向卡片布局,最近 5 条消息一行展示:

  • 📌 置顶消息红色边框
  • 🔴 紧急消息红色标签
  • 📞 叫人消息特殊图标
  • 🔔 未读消息闪烁提醒

⏰ 上课/课间消息策略

系统会根据当前时间智能判断消息的推送方式,避免教学干扰

课间 = 上学时间内但不在上课时段中。例如 09:40-09:55、16:30-16:40 等。

不同时段的推送策略

│ 上课时段
│ 📨 新消息 → 缓存到 pending 队列 │
│ 💬 页面提示"当前为上课时段,消息将在课间时通知" │
│ 🔇 不弹窗、不播放 TTS
│ 课间时段 │
│ 📨 新消息 → 立即弹窗 + TTS 语音播报 │
│ 🔄 课间轮播:逐条播放所有未确认消息 │
│ 🔔 课间提醒弹窗 + 语音提示 │
│ ⏱️ 轮播冷却:播完一轮后等待 2 分钟再重播 │
│ 周末/假期 │
│ 📨 新消息 → 立即弹窗播报(不缓存) │
│ 🏠 课间轮播不启用

课间提醒机制

  1. 每分钟检测:后端 Cron 每分钟检测当前是否处于课间
  2. 弹出提醒:课间开始时 → 弹窗"🔔 课间提醒:您有 X 条未确认的通知"
  3. TTS 语音:提醒后 2 秒开始逐条语音播报
  4. 防重复:每个课间只完整播报一轮,2 分钟冷却

周末特殊处理

周末不触发课间轮播,但新消息立即播报(与工作日上课缓存策略不同)。


🎙️ 语音消息

网页端录音

  1. 点击发送消息面板的"🎤 录音"按钮
  2. 浏览器请求麦克风权限 → 允许
  3. 开始录音(最长 60 秒),再次点击停止
  4. 发送后,音频随消息一起推送

语音播报(TTS)

  • 触发条件:Class 角色 + 课间/周末 + 非自己发出的消息
  • 播报内容:“来自 XXX 的消息:标题…”
  • 提示音区分
    • 📢 通知 → 标准提示音
    • 📞 叫人 → 紧急提示音
    • 📣 公告 → 公告提示音
  • 管理端:不播报(静默接收)

音频附件播放

接收端点击消息中音频附件可在线播放,使用浏览器原生音频控件。


🎂 生日祝福系统

生日录入

管理员可在后台管理页面的「生日管理」中:

  • 逐条添加:姓名 + 生日日期
  • 批量导入:表格粘贴(推荐导入全班数据)
  • 当前系统已有 40+ 条生日数据

生日提醒

  • 每日 8:00:后端自动检测当天生日
  • WebSocket 推送:教室端弹窗"🎂 今天是 XXX 的生日!祝生日快乐!"
  • ClassIsland 插件:状态栏显示"🎂 祝XXX同学N岁生日快乐!"(红色文字)
  • 无人过生日:显示"🎂 今日无寿星"

多人生日

如当天有多个同学生日,显示"🎂 祝张三、李四同学生日快乐!"


🌤️ 天气模块

  • 数据来源:小米天气 API(后端代理,无跨域问题)
  • 默认城市:成都 · 武侯区
  • 预警系统
    • 🟥 红色预警 → 红色横幅 + 闪烁圆点
    • 🟧 橙色预警 → 橙色横幅
    • 🟨 黄色预警 → 黄色横幅
    • 🟦 蓝色预警 → 蓝色横幅
  • 预警弹窗:0.8 秒后自动弹出(仅弹一次)
  • 教室大屏模式:首页大屏轮播中展示天气卡片

📋 课表管理

系统支持多课表轮换(例如单周/双周不同课表)。

课表操作(管理员)

  • 添加/编辑每节课:星期几、节次、科目、教师、时间
  • 批量导入课表
  • 课表名称管理(如 default、单周、双周)
  • 课表周次映射(指定第几周用哪个课表)

课表查看

  • 网页端按星期筛选查看
  • 教室大屏轮播展示当天课表
  • ClassIsland 插件可同步课表数据

⚙️ 系统设置

管理员可在「系统设置」页面配置以下参数:

设置项 说明 默认值
学校名称 显示在学校标题处
上学时间 早读/第一节课开始时间 07:30
放学时间 最后一节课结束时间 21:50
上课时段 所有上课时间段(逗号分隔) 见上文列表
学期开始 学期起始日期 2026-03-01
学期周数 学期总周数 20
考试日期 考试倒计时目标日期 由管理员设置
语音播报 是否启用 TTS 语音 开启
课间提醒 是否启用课间提醒轮播 开启
提醒间隔 未确认消息检查间隔(分钟) 5

⚠️ 重要提醒

break_times 字段存在历史命名问题

  • 字段名叫 break_times(课间),但实际存的是上课时段
  • 修改时格式:HH:MM-HH:MM,HH:MM-HH:MM,...
  • 课间 = 上学时间内但不在 break_times 中的空档

👥 用户管理(管理员)

管理员登录后在「用户管理」页面可以:

用户列表

展示所有注册用户,包含:

  • 用户名、显示名称、角色、所属班级
  • 状态(pending 待激活 / active 已激活)
  • 是否启用(is_active)

用户操作

操作 说明
✅ 激活 pending → active,用户可登录
✏️ 编辑 修改用户信息(角色、科目、手机号等)
🚫 禁用 暂禁止登录(可恢复)
🗑️ 删除 彻底删除用户(⚠️ 管理员不可删除)

待激活提醒

首页统计卡片实时显示"待激活 X 人",提醒管理员及时审核。


💻 ClassIsland 桌面插件

ClassIsland 是教室大屏使用的桌面课表软件。Qj5Notifier 插件为 ClassIsland 增加了班级消息推送能力。

插件功能

组件 功能
🔔 消息通知 状态栏单行显示:图标 + 最新消息标题 + 未读徽章
🎂 生日祝福 今天生日显示祝福;无人过生日显示"今日无寿星"
🌐 WebSocket 实时连接服务器,秒级接收到新消息

课间行为

  • 上课时段:新消息缓存,不打扰课堂
  • 课间来临:WebSocket 收到 break_start → 立即弹窗 + TTS + 逐条播报缓存消息
  • 防重复:每个课间只播一次(_drainedThisBreak 防护)
  • 缓存上限:最多 50 条消息

消息通知组件样式

🔔 📢 关于明天考试的通知… [2 条未确认] 🟢在线

  • 标题白色加粗(13px)
  • 未读数量红色徽章
  • 在线状态绿点
  • 一行紧凑布局,适配状态栏窄空间

📱 确认消息专用页面

专为教室端设计的未确认消息集中处理页

  • 访问路径/confirm 或从首页"待确认"卡片跳转
  • 自动语音:进入页面后自动语音播报每条未确认消息
  • 逐条确认:点击"确认"按钮逐条标记已读
  • 课间提醒:WebSocket 实时监听 break_start,课间自动弹窗提醒
  • 轮询兜底:每 10 秒检查一次(防止 WebSocket 断连)

使用场景

教室电脑常驻打开此页面,上课时静默,课间自动弹出提醒 → 班长/值日生查看确认。

编写时的经验总结

从 2026-04-25 项目启动到 2026-08-06 审计完成,完整技术栈:Node.js + Express + SQLite + WebSocket + PM2 + Electron


一、编码损坏 — 最反复出现的硬伤

1.1 edit 工具破坏 UTF-8(3 次)

现象:对 app.js 使用 edit 工具后,中文字符被替换为 ??(U+FFFD),触发 JavaScript SyntaxError,页面按钮全无响应。

根因edit 工具的文本替换未保持 UTF-8 多字节字符边界,在多字节序列中间插入/替换字节。

教训

  • 禁止对含中文的 JS/HTML 文件使用 edit 工具。改用 write 整体写入,或写成 Python 脚本在服务器端做字符串替换。
  • 每次修改前必须备份(cp file.js file.js.bak_$(date +%Y%m%d_%H%M%S))。

1.2 PowerShell GBK 编码损坏(多次)

现象:通过 PowerShell SSH/scp 传输到服务器的文件,中文字节被替换为 ?(0x3F),Node.js 语法检查失败。

根因:PowerShell 终端默认 GBK 编码,SSH 命令中的 UTF-8 多字节序列被截断。

教训

  • 一律使用「本地写脚本 → scp 上传到 /tmp → ssh bash/python3 执行」工作流
  • 绕过 PowerShell 的引号解析,不在 PowerShell 命令行中写含中文/特殊字符的内联脚本。
  • Python 脚本是安全的中间格式(UTF-8 天然兼容,scp 二进制传输)。

1.3 PowerShell heredoc 陷阱

现象ssh root@host "cat > file.py << 'EOF' ... EOF" 被 PowerShell 解析为本地命令,引号层层崩解。

根因:PowerShell 先解析命令行再传递给 ssh,heredoc 的 << 被当作本地重定向。

教训

  • 永远不在 PowerShell 命令行中写 heredoc。
  • 需要远程创建脚本时:scp local_script.sh root@host:/tmp/ && ssh root@host "bash /tmp/script.sh"

二、服务崩溃 — 根因诊断与止血

2.1 TDZ(Temporal Dead Zone)导致间歇性崩溃

现象/api/schedule/break-now 始终返回 500 → 上课时段消息缓存逻辑全失效 → PM2 重启 48 次。

根因schedule.js 第 148 行 inClass 箭头函数捕获 inSchoolHours,但 inSchoolHours 在第 156 行才用 const 声明。V8 的 TDZ 检查在闭包创建时就触发 ReferenceError。

教训

  • 箭头函数隐式闭包可能触发 TDZ,const/let 声明的位置非常敏感。
  • 关键 API 必须加单元级烟雾测试:启动后 curl /api/schedule/break-now 检查返回 200。
  • 使用 var 声明提升变量作为 TDZ 的快速绕过手段(语义上不优雅但有效)。

2.2 JSON 解析崩溃 — 未捕获异常直接崩进程

现象:PM2 日志频繁 SyntaxError: Bad escaped character in JSON → SIGKILL。

根因:接收到畸形 JSON 请求体时,Express 的 express.json() 中间件抛出异常未被捕获。外加缺少 uncaughtException/unhandledRejection 全局处理器,任何未捕获异常直接崩进程 → PM2 重启 → 内存 session 全部丢失 → 用户被踢回登录页。

教训

  • server.js 第一行就应加 process.on('uncaughtException', ...)process.on('unhandledRejection', ...),记录日志但不退出。
  • 所有异步路由内部必须 try-catch。
  • PM2 重启计数是最重要的健康指标,应该定期监控(pm2 infopm2 monit)。

2.3 bcrypt 模块缺失 — 部署后依赖不完整

现象/api/users/:id/reset-password 必 500,Cannot find module 'bcrypt'

根因package.json 有 bcrypt 但 node_modules 中没有。可能是某次部署时 npm install 被中断或跳过了。

教训

  • 每次部署后用 node -e "require('bcrypt')" 验证关键 native 模块可加载。
  • pm2 restart 不等于 npm install。换服务器或清 node_modules 后必须重新 npm install --production

2.4 SESSION_SECRET 每次都变 — 全员掉登录

现象:每次 PM2 重启 / 进程崩溃恢复后所有用户被踢回登录页。

根因server.jssecret: process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex'),环境变量未设,每次生成新密钥 → 旧 session cookie 全部失效。

教训

  • session 密钥必须固定。用 ecosystem.config.js 写入 env 或直接硬编码生成一次的固定值。
  • 绝对不要用随机值作为 fallback。

三、远程操作 — PowerShell 是最大的障碍

3.1 问题矩阵

操作 PowerShell 直接执行 脚本化(scp → ssh 执行)
SSH 单行命令 引号嵌套易出错 ✅ 安全
SSH heredoc ❌ 被本地解析 ✅ bash 执行
Python 修复脚本 ❌ 中文被破坏 ✅ scp 二进制传输
node --check 校验 ❌ 工作目录错误 ✅ 脚本内 cd 到正确目录
base64 编码 ❌ 编码不完整 ✅ Python 处理
文件 grep/搜索 ❌ 通配符被 PowerShell 展开 ✅ 写成 bash 脚本

3.2 标准操作流程

# 1. 本地创建脚本
# C:\Users\zengz\.qclaw\workspace-agent-d3b9cb40\tmp_fix_xxx.sh (或 .py)

# 2. scp 上传
scp tmp_fix_xxx.sh root@47.108.162.103:/tmp/

# 3. ssh 执行
ssh root@47.108.162.103 "bash /tmp/fix_xxx.sh"

# 4. (如果是前端修改) 手动升级缓存版本号
# index.html 中 v=YYYYMMDDvN → v=YYYYMMDDvN+1

3.3 SSH 连接不稳定

现象(2026-05-30):从助手环境 22 端口多次不通(ping 超时、SIGKILL),但用户本地可连。HTTPS 正常。

应对:备选方案 — 请用户手动 SSH 执行简单命令。


四、前端 JS — 纯 SPA 的坑

4.1 innerHTML = XSS 入口

app.js 中 20 处 innerHTML =,含通知标题/内容、生日名字等直接拼接服务器数据。若有人注册后发送 <script>alert(1)</script> 作为消息标题,会触发 XSS。

建议:对用户可控字段用 textContent,或用 DOMPurify 过滤。

4.2 setInterval 泄漏

23 个 setInterval,部分无对应 clearInterval。SPA 内多次切换页面不清理 → 定时器堆积 → 内存泄漏。

4.3 函数名不匹配(幽灵引用)

  • openModal() — 不存在,实际是 openBirthdayModal() / openPublishModal() 等具体函数
  • statUsers — 不存在,引用它的代码会导致静默失败
  • loadMyNotifs() — 已删除但调用未清理

教训:删除功能时,用 grep 全局搜索函数名确保没有残留引用。

4.4 HTML ID 不匹配

课间时段输入框的 HTML id 和 JS 中引用的 id 不一致 → 设置页保存功能静默失效。

4.5 缓存版本号机制

<script src="js/app.js?v=YYYYMMDDvN"> 手动递增版本号。
每次前端修改后必须升级版本号,否则浏览器缓存旧 JS → 新功能不生效。


五、数据库 — SQLite 的实践经验

5.1 WAL 模式注意点

  • SQLite WAL 文件会持续增长,需定期 PRAGMA wal_checkpoint(TRUNCATE)
  • 出现过 4MB WAL 配 64KB 主库的情况 — 不影响功能,但影响启动速度。

5.2 UTC vs 本地时间

SQLite 的 CURRENT_TIMESTAMP 返回 UTC。需用 datetime('now','localtime') 才能拿到北京时间。历史数据需要手动 +8 小时修复。

5.3 参数化查询 100% 覆盖

21 处数据库操作全部参数化(db.run(sql, [params], ...)),零 SQL 注入。这是做得最好的部分。

5.4 数据库路径混淆

曾存在根目录 0 字节空文件 class_system.dbdata/class_system.db 同时存在 → 排查困难。


六、部署与运维

6.1 跨平台部署铁律

Windows 编译的 sqlite3 不能在 Linux 上运行(invalid ELF header)。
→ 永远在目标服务器上执行 npm install

6.2 PM2 重启计数

累计 89 次重启。每次非预期的重启都是 bug 信号。健康的服务不应频繁重启。

6.3 备份清单

  • class-system-backup-20260605_204832 — 2026-06-05 全量
  • audit_backup_20260802 — 2026-08-02 全量 (注意:本地下载包排除了!)
  • data/class_system.db.bak / .bak_utcfix — 数据库备份
  • 多次 app.js.bak_*index.html.bak_*style.css.bak_* 单文件备份

6.4 npm 安全漏洞未修复

31 个漏洞(2 critical / 21 high / 5 moderate / 3 low),npm audit fix --force 有 breaking change 风险,需先 npm audit 看具体包再决定。


七、快速参考卡

场景 ✅ 正确做法 ❌ 错误做法
修改服务器文件 本地写 Python 脚本 → scp → ssh python3 PowerShell SSH 内联代码
修改含中文的 JS write 工具整体写入,或服务器端 Python edit 工具
部署到 Linux 目标机器 npm install 复制 Windows 的 node_modules
前端修改后 升版本号 ?v=YYYYMMDDvN+1 + PM2 重启 直接覆盖不升版本
Session 密钥 固定写入 ecosystem.config.js crypto.randomBytes() fallback
API 接口 try-catch 包裹 + 错误脱敏 async (req, res)
删除函数 grep 全局搜索引用 只删定义不管调用
PM2 重启 用备份验证后再重启 无备份直接改
SQLite 操作 参数化查询 db.run(sql, [p1, p2]) 字符串拼接 SQL

尾声:一行代码里的少年气

从四月暮春到八月流火,一百多个深夜的键盘敲击,把一个高三学生的“临时任务”,锻造成了一套真正跑在教室大屏上的系统。它不只是 冷冰冰的代码,更是为班级刻下的一段时光。

在这个系统里,消息不再是冰冷的数据包,而是老师的一声叮咛,是同学生日的烛光,是课间十分钟里准时响起的、属于我们的声音。它打破了空间的隔阂,最终让冰冷的机器学会了在恰当的时刻保持沉默,又在需要的瞬间大声说话。让老师在炎热的夏季能够坐在办公室中就将消息传达到位,而不用遭受高温的“炙烤”

编程的过程,像极了高三的备考。你会遇到解不开的 TDZ(暂时性死区),会遇到编码错乱的乱码(Bug),也会在 PM2 一次次重启中怀疑人生。但正如《黑客与画家》中所说:“软件正在吞噬世界,但首先,它在喂养那些创造它的人。”​ 每一次修复,都是对逻辑的重新梳理;每一次部署,都是对耐心的极限考验。

这不仅是一次技术的试炼,更是一次关于“责任”的预演。我守着服务器,就像守着我们班的秩序。哪怕只是为了让那条“祝某某同学生日快乐”的弹窗准点出现,所有的熬夜都变得值得。

最后,借用诗人里尔克在《给青年诗人的信》中的话作结:

“哪有什么胜利可言,挺住意味着一切。”

愿未来的某一天,当北二外成都附中高2024级5班的同学们回想起高三,除了试卷和分数,还能记得大屏上那个红色的未读徽章,记得那句准时响起的“祝生日快乐”。那是我们在数字世界里,共同度过的一段滚烫岁月。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐