第2版维护补充
- 项目目录:
/var/www/quiz。 - 已建立本地 Git 版本管理,主分支为
main。 - 当前基线标签:
v2.0-baseline-20260619。 - 数据库备份脚本:
./scripts/backup-db.sh。 - 数据库迁移记录目录:
db/migrations/,用于记录已执行过的表结构变更。 - 公共样式文件:
public/common.css,后续通用页面样式优先放这里。 - 图片压缩工具:
sharp,发布任务时会自动压缩工程 JSON 内嵌的data:image图片。 - 随机题目/随机选项:教师端发布时写入
project.settings,后端保存到assignments.settings_json,学生端按设置自动随机显示。 - 分数段设置:发布任务、单任务修改、批量应用都会写入
assignments.settings_json.score_ranges,并同步到assignments.project_json.settings.score_ranges。 - 重复提交次数上限:数据库字段
assignments.max_submit_count;空值表示不限,1表示仅一次,正整数表示最多提交次数。 - 学生自删除记录:数据库字段
assignments.allow_student_delete;教师端任务列表可用“可自删 / 禁自删”切换,开启后学生只能删除本人提交记录。 - 每题正确率/易错题排序区域已增加滚动容器,适合二三十题以上试卷查看。
- 修改代码前建议先执行:
git status。 - 修改数据库结构前必须先执行数据库备份脚本。
- 完成一个小功能并测试正常后,使用
git add .和git commit -m "说明"提交。 - 单个文件回滚可用:
git restore 文件名。 - 重启服务:
pm2 restart quiz --update-env。
一、当前服务器与系统信息
| 项目 | 当前值 |
|---|---|
| 云服务商 | 腾讯云 |
| 实例名称 | hofor |
| 系统 | Ubuntu Server 24.04 LTS 64bit |
| 公网 IP | 124.223.201.198 |
| SSH 用户 | ubuntu |
| 域名 | https://hofor881.cn |
| www 域名 | https://www.hofor881.cn |
| 项目目录 | /var/www/quiz |
| Node 监听 | 127.0.0.1:3000 |
| 公网访问 | Nginx 通过 443 HTTPS 反向代理到 Node |
| 数据库 | PostgreSQL,数据库名 quiz |
| Node 守护 | PM2,应用名 quiz |
二、整体架构
当前系统采用经典的单机部署结构:
用户浏览器 | | HTTPS 443 v Nginx | | 反向代理到 127.0.0.1:3000 v Node.js / Express | | SQL v PostgreSQL
外部用户只访问:
https://hofor881.cnhttps://www.hofor881.cn
Node 程序只监听本机:
127.0.0.1:3000
三、主要页面入口
| 页面 | 地址 | 用途 |
|---|---|---|
| 教师登录 | /login.html | 教师输入账号密码登录。 |
| 教师端 | /teacher.html | 发布任务、复制学生链接、查看提交记录。 |
| 学生端 | /student.html?id=任务ID |
学生填写班级、姓名、学号并答题。 |
| 使用说明 | /help.html | 给教师和学生看的操作说明。 |
| 维护说明 | /admin-guide.html | 服务器和代码维护说明。 |
四、项目目录结构
项目主目录:
cd /var/www/quiz
主要文件和目录:
| 路径 | 作用 |
|---|---|
server.js | Node / Express 主程序,提供 API、静态页面访问、登录、发布、提交等功能。 |
.env | 环境变量配置文件,包含数据库连接、教师账号、JWT 密钥等。 |
public/login.html | 教师登录页。 |
public/teacher.html | 教师端页面。 |
public/student.html | 学生答题页面。 |
public/help.html | 普通使用说明。 |
public/admin-guide.html | 本维护说明文档。 |
db/init.sql | 数据库初始化 SQL。 |
db/pool.js | PostgreSQL 连接池配置。 |
utils/projectParser.js | 解析 v1.3.3 教师工程 JSON。 |
utils/grading.js | 判分相关工具函数。 |
package.json | Node 依赖和项目脚本说明。 |
五、环境变量 .env
查看环境变量配置:
cd /var/www/quiz cat .env
常见配置项:
| 变量名 | 用途 |
|---|---|
PORT | Node 服务端口,当前通常为 3000。 |
HOST | Node 监听地址,建议为 127.0.0.1。 |
DATABASE_URL | PostgreSQL 数据库连接字符串。 |
TEACHER_USERNAME | 教师用户名。 |
TEACHER_PASSWORD | 教师密码。 |
JWT_SECRET | JWT 登录令牌密钥。 |
六、PM2 管理 Node 服务
1. 查看运行状态
pm2 status
正常情况下可以看到:
quiz online
2. 重启服务
cd /var/www/quiz pm2 restart quiz --update-env
3. 查看日志
pm2 logs quiz --nostream --lines 50
4. 保存 PM2 进程列表
pm2 save
5. 开机自启状态
systemctl status pm2-ubuntu
pm2 restart quiz --update-env 即可。
七、Nginx 与 HTTPS
1. 当前 Nginx 站点配置文件
/etc/nginx/sites-available/hofor881.cn
2. 查看站点启用情况
ls -l /etc/nginx/sites-enabled
3. 查看当前 Nginx 配置摘要
sudo nginx -T | grep -nE "server_name|listen 443|listen 80|proxy_pass|ssl_certificate"
4. 测试 Nginx 配置
sudo nginx -t
5. 重载 Nginx
sudo systemctl reload nginx
6. 当前核心反向代理逻辑
location / {
proxy_pass http://127.0.0.1:3000;
}
八、PostgreSQL 数据库
1. 数据库信息
| 项目 | 值 |
|---|---|
| 数据库名 | quiz |
| 业务用户 | quiz_user |
| 主要业务表 | assignments、submissions |
2. 进入数据库
sudo -u postgres psql -d quiz
3. 查看表列表
\dt
4. 查看 assignments 表结构
\d assignments
4-1. 旧库补充学生自删除字段
如果是从旧版本升级,需确认 assignments 表已有学生自删除字段:
ALTER TABLE assignments ADD COLUMN IF NOT EXISTS allow_student_delete boolean DEFAULT false;
5. 查看 submissions 表结构
\d submissions
6. 查看任务列表
sudo -u postgres psql -d quiz -c "SELECT id,title,mode,access_code,created_at FROM assignments ORDER BY id DESC;"
7. 查看提交记录
sudo -u postgres psql -d quiz -c "SELECT id,assignment_id,class_name,name,student_id,score,created_at FROM submissions ORDER BY id DESC LIMIT 20;"
九、核心数据表说明
1. assignments:任务表
每发布一次任务,就会在 assignments 表中生成一条记录。
| 字段 | 说明 |
|---|---|
id | 任务 ID,学生链接中使用。 |
title | 任务标题或试卷名称。 |
mode | 任务模式,practice 为练习,test 为测试。 |
access_code | 访问码,可用于扩展短链接或二维码。 |
settings_json | 任务设置,包含随机题目/选项、分数段 score_ranges 等设置。 |
questions_json | 题目数据。 |
project_json | 原始教师工程 JSON;分数段也会同步写入 project_json.settings.score_ranges,便于导出和迁移。 |
is_active | 任务是否启用。 |
allow_repeat_submit | 是否允许重复提交。 |
max_submit_count | 最多提交次数;为空表示不限次数。 |
allow_student_delete | 是否允许学生删除本人提交记录。 |
created_at | 创建时间。 |
2. submissions:提交表
学生每提交一次答案,就会在 submissions 表中生成一条记录。
| 字段 | 说明 |
|---|---|
assignment_id | 对应任务 ID。 |
class_name | 学生填写的班级。 |
name | 学生填写的姓名。 |
student_id | 学生填写的学号。 |
answers_json | 学生答案。 |
score | 得分。 |
result_json | 判分详情。 |
created_at | 提交时间。 |
十、主要 API 流程
1. 教师登录
POST /api/login
教师输入用户名和密码,后端验证后返回登录结果和 token。
2. 教师发布任务
POST /api/assignments
教师上传 v1.3.3 教师工程 JSON,后端解析并写入 assignments 表。发布参数包括任务模式、随机设置、是否允许重复提交、最多提交次数和答题码名单。
3. 教师获取任务列表
GET /api/assignments
教师端显示已经发布的任务。
4. 学生获取任务详情
GET /api/assignments/:id
学生打开链接后,根据任务 ID 加载题目。
5. 学生提交答案
POST /api/assignments/:id/submissions POST /api/submissions POST /api/student/submit
学生提交班级、姓名、学号和答案,后端先按答题码或“班级+姓名+学号”检查提交次数上限,再判分并写入 submissions 表。
6. 教师查看提交记录
GET /api/assignments/:id/submissions
教师端点击“记录”,显示该任务的学生提交情况,并可查看单个学生作答详情。
6-1. 教师设置分数段
教师端支持三种分数段设置入口:发布任务时设置、任务列表单个任务“分数段”按钮、任务列表右上角“批量分数段”按钮。
PATCH /api/assignments/:id/score-ranges:修改单个任务分数段。PATCH /api/assignments/score-ranges-all:把分数段应用到全部任务。- 两个接口都会更新
settings_json.score_ranges,并同步更新project_json.settings.score_ranges。
7. 教师切换学生自删除权限
PATCH /api/assignments/:id/student-delete
教师端任务列表中的“可自删 / 禁自删”按钮会调用此接口,写入 assignments.allow_student_delete。
8. 学生查看和删除本人记录
GET /api/assignments/:id/my-submissions DELETE /api/assignments/:id/my-submissions/:submissionId
学生输入班级、姓名、学号后,只能查看和删除本人在当前任务下的提交记录;删除接口还会检查教师是否已允许自删除。
十一、主要代码文件说明
1. server.js
系统后端主文件,包含:
- Express 服务启动;
- 静态页面服务;
- 教师登录接口;
- 任务发布接口;
- 任务列表接口;
- 学生获取任务接口;
- 学生提交答案接口;
- 教师查看提交记录接口。
- 单任务分数段修改接口和全部任务分数段批量应用接口。
2. public/login.html
教师登录页。登录成功后保存 token,并跳转到教师端。
3. public/teacher.html
教师管理页。主要功能:
- 选择 v1.3.3 教师工程 JSON;
- 选择练习模式或测试模式;
- 发布任务;
- 复制学生链接;
- 查看学生提交记录。
- 发布任务分数段设置、单任务分数段修改和批量分数段应用。
4. public/student.html
学生答题页。主要功能:
- 根据任务 ID 加载题目;
- 填写班级、姓名、学号;
- 选择答案;
- 提交答案;
- 练习模式显示答案解析;
- 测试模式只显示成绩。
5. utils/projectParser.js
用于解析 v1.3.3 教师工程 JSON,把原始工程数据转换为系统发布任务所需的数据。
6. utils/grading.js
用于答案比对和计算成绩。
十二、日常维护常用命令
1. 进入项目目录
cd /var/www/quiz
2. 查看 Node 服务
pm2 status
3. 重启 Node 服务
pm2 restart quiz --update-env
4. 查看最近日志
pm2 logs quiz --nostream --lines 50
5. 查看 3000 监听
ss -lntp | grep 3000
6. 查看健康接口
curl -s https://hofor881.cn/api/health
7. 查看页面状态
curl -I https://hofor881.cn/login.html curl -I https://hofor881.cn/teacher.html curl -I https://hofor881.cn/student.html curl -I https://hofor881.cn/help.html
8. 查看 Nginx 状态
systemctl status nginx
9. 重载 Nginx
sudo nginx -t sudo systemctl reload nginx
十三、修改代码的推荐流程
- 进入项目目录:
cd /var/www/quiz
- 修改前先备份目标文件:
cp server.js server.js.bak.$(date +%Y%m%d%H%M%S) cp public/teacher.html public/teacher.html.bak.$(date +%Y%m%d%H%M%S) cp public/student.html public/student.html.bak.$(date +%Y%m%d%H%M%S)
- 修改文件。
- 如果修改的是前端 HTML,一般刷新页面即可。
- 如果修改的是 server.js 或 .env,需要重启 PM2:
pm2 restart quiz --update-env
- 保存 PM2 状态:
pm2 save
十四、数据库备份与恢复
1. 创建备份目录
mkdir -p /var/www/quiz/backups
2. 备份数据库
sudo -u postgres pg_dump quiz > /var/www/quiz/backups/quiz_$(date +%Y%m%d_%H%M%S).sql
3. 查看备份文件
ls -lh /var/www/quiz/backups
4. 恢复数据库的基本形式
sudo -u postgres psql -d quiz < /var/www/quiz/backups/备份文件名.sql
十五、代码备份建议
1. 备份整个项目目录
sudo tar -czf /var/www/quiz_backup_$(date +%Y%m%d_%H%M%S).tar.gz /var/www/quiz
2. 查看备份包
ls -lh /var/www | grep quiz_backup
3. 单文件修改前备份
cp public/student.html public/student.html.bak.$(date +%Y%m%d%H%M%S)
十六、一次完整业务流程对应的数据变化
1. 教师发布任务
教师上传 JSON 并点击发布后:
- 后端解析教师工程 JSON;
- 写入 assignments 表,包括
settings_json、project_json、allow_repeat_submit、max_submit_count、allow_student_delete等字段;其中分数段保存在settings_json.score_ranges和project_json.settings.score_ranges; - 返回任务 ID;
- 教师端生成学生链接。
2. 学生打开链接
学生访问:
/student.html?id=任务ID
学生端调用:
GET /api/assignments/:id
3. 学生提交答案
学生点击提交后:
- 前端提交班级、姓名、学号、答案和作答用时;
- 后端读取任务题目和提交次数限制;
- 如果达到提交次数上限,返回错误提示,不写入新记录;
- 未达到上限时,后端比对正确答案;
- 写入 submissions 表;
- 返回成绩和判分详情。
4. 教师查看记录
教师点击“记录”后:
GET /api/assignments/:id/submissions
教师端显示该任务所有提交记录。教师可查看单个学生作答详情,也可删除提交记录;学生自删除权限由任务列表中的“可自删 / 禁自删”按钮控制。
十七、练习模式与测试模式
| 模式 | mode 值 | 学生提交后 |
|---|---|---|
| 练习模式 | practice | 显示成绩、答案、解析。 |
| 测试模式 | test | 只显示成绩。 |
模式是在教师发布任务时确定的,并保存在 assignments 表里。随机题目/选项来自 settings_json;重复提交策略由 allow_repeat_submit 与 max_submit_count 共同决定;学生是否可删除本人记录由 allow_student_delete 决定。
十八、后期改进设想
- 为已发布任务增加更完整的“编辑设置”面板,集中修改随机、限时、重复提交次数、答题码等选项。
- 学生端可增加提示语,说明本任务是否随机题目/随机选项。
- 将数据库结构变更整理为正式迁移脚本,记录
assignments.max_submit_count等字段。
十九、更新教师账号密码
教师账号密码保存在:
/var/www/quiz/.env
修改方式:
cd /var/www/quiz nano .env
修改类似以下两项:
TEACHER_USERNAME=新的用户名 TEACHER_PASSWORD=新的密码
修改后重启:
pm2 restart quiz --update-env
十九、系统扩展方向
1. 多教师账号
未来可以把教师账号从 .env 移到 teachers 表中,每个教师拥有自己的任务列表和提交记录。
2. 企业版部署
未来可以为不同学校或机构增加 tenant_id,实现多机构隔离。
3. 二维码页面
可以给每个任务增加二维码展示页面,方便课堂投屏扫码。
4. 成绩导出
可以在教师端增加导出 CSV 或 Excel 的按钮。
5. 任务管理
可以增加任务停用、删除、重命名、复制任务等功能。
二十一、备案信息添加与维护
当前网站已添加 ICP 备案和公安备案底部信息。为了便于维护,备案信息使用公共脚本统一生成。
1. 当前备案信息
- 工信部备案号:
闽ICP备2026023379号 - 公安备案号:
闽公网安备35012102550517号 - ICP备案链接:
https://beian.miit.gov.cn/ - 公安备案链接:
https://beian.mps.gov.cn/#/query/webSearch?code=35012102550517
2. 涉及文件
public/beian-footer.js:公共备案底部脚本。public/beian.png:公安备案图标。public/login.html、public/teacher.html、public/student.html、public/help.html、public/admin-guide.html:页面底部引入备案脚本。
3. 关键注意事项
给 HTML 页面插入 <script src="/beian-footer.js"></script> 时,必须插入最后一个 </body> 前面,不要替换第一个 </body>。
原因:教师端和学生端中存在 w.document.write(...</body></html>) 弹窗字符串,错误插入会破坏 JavaScript,导致页面报 Invalid or unexpected token。
4. 检查命令
cd /var/www/quiz ls -lh public/beian.png public/beian-footer.js file public/beian.png grep -n "beian-footer.js" public/login.html public/teacher.html public/student.html public/help.html public/admin-guide.html grep -n "闽ICP备2026023379号\|闽公网安备35012102550517号" public/beian-footer.js curl -I http://127.0.0.1:3000/beian.png curl -s "http://127.0.0.1:3000/teacher.html?v=beian-check" | tail -20 curl -s "http://127.0.0.1:3000/student.html?id=66&v=beian-check" | tail -20
5. 离线保留版
已生成独立离线说明文件:
二十二、查看导入后在线教师工程大小
在线版教师工程主要保存在 PostgreSQL 的 assignments.project_json 字段中。可用以下 SQL 查看在线压缩后的工程大小,并与本地离线版工程 JSON 比较。
1. 查看最近任务工程大小
cd /var/www/quiz
sudo -u postgres psql -d quiz -c "
SELECT
id,
title,
created_at,
pg_size_pretty(pg_column_size(project_json)::bigint) AS db_size,
pg_size_pretty(octet_length(project_json::text)::bigint) AS text_size,
((length(project_json::text) - length(replace(project_json::text, 'data:image', ''))) / length('data:image')) AS image_count
FROM assignments
ORDER BY id DESC
LIMIT 30;
"
2. 按工程大小从大到小排序
sudo -u postgres psql -d quiz -c " SELECT id, title, created_at, pg_size_pretty(pg_column_size(project_json)::bigint) AS db_size, pg_size_pretty(octet_length(project_json::text)::bigint) AS text_size FROM assignments ORDER BY pg_column_size(project_json) DESC LIMIT 20; "
3. 查看所有工程总大小
sudo -u postgres psql -d quiz -c " SELECT COUNT(*) AS assignment_count, pg_size_pretty(SUM(pg_column_size(project_json))::bigint) AS total_project_json_db_size, pg_size_pretty(SUM(octet_length(project_json::text))::bigint) AS total_project_json_text_size, pg_size_pretty(SUM(pg_column_size(questions_json))::bigint) AS total_questions_json_db_size FROM assignments; "
4. 导出某个在线工程 JSON 与本地比较
sudo -u postgres psql -d quiz -Atc "SELECT project_json::text FROM assignments WHERE id=66;" > /tmp/assignment_66_project.json ls -lh /tmp/assignment_66_project.json wc -c /tmp/assignment_66_project.json gzip -c /tmp/assignment_66_project.json > /tmp/assignment_66_project.json.gz ls -lh /tmp/assignment_66_project.json /tmp/assignment_66_project.json.gz
5. 查看 API 实际返回大小
curl -s -o /tmp/assignment_66_api.json -w "download_size_bytes=%{size_download}\n" "http://127.0.0.1:3000/api/assignments/66"
ls -lh /tmp/assignment_66_api.json
wc -c /tmp/assignment_66_api.json
6. 离线保留版
已生成独立离线说明文件:
二十三、本轮教师端任务列表与弹窗模式维护记录
1. 本轮主要提交
a7a6dc9 feat: show submission records below assignment row 721896d feat: show answer code management below assignment row d6d6326 feat: show QR code below assignment row 98a0714 feat: add row detail display mode option 3e546ea feat: support question stats in submission popup cbc6473 feat: show submission records in popup mode db2bc35 feat: show answer code management in popup mode eadb4c6 fix: allow blank student id in answer code validation 39ccb0f feat: show assignment QR code in popup mode 2755728 feat: add teacher detail display mode toggle 22b533b feat: add assignment export options 3a165ac feat: show display sequence number in assignment list
2. 教师端任务列表显示编号
- 任务列表显示编号为前端显示层编号,不改变数据库
assignments.id。 - 当前列表中最早任务显示
#1,最新任务显示最大编号。 - 真实数据库 ID 仍显示为
ID:xx,学生链接、二维码、提交记录、删除等接口仍使用真实 ID。
3. 任务导出功能
- 教师端任务操作区新增“导出”按钮。
- 支持
txt、word、json三种格式。 json导出的是可重新导入的教师工程结构,不是外包一层的统计包。word实际为 Word/WPS 可打开的 HTML.doc文件;导出图片统一限制为约560px宽。- TXT 和 Word 导出会清理图片编辑菜单残留文字,例如“图片池挂接图片”“选择外部图片替换”等。
4. 详情显示模式
- 教师端任务列表右上角新增
详情显示下拉框。 - 当前可选三种模式:
页面下方、任务下方、弹窗显示,选择保存在浏览器localStorage中。 页面下方保留原有布局,记录、二维码、答题码显示在任务列表下方。任务下方会在当前任务行下方插入一个详情行,已覆盖二维码、答题码管理、提交记录和每题统计/易错题。弹窗显示已覆盖二维码、答题码管理、提交记录和每题统计/易错题。- 任务下方模式同一时间只保留一个详情区,点击其他任务或其他详情按钮时会先清理旧详情区。
5. 弹窗模式维护注意事项
- 弹窗使用
window.open。为避免浏览器拦截,应在用户点击按钮后立即打开窗口,再异步加载数据。 - 弹窗内按钮调用主页面函数时,一般使用
opener.xxx()。 - 涉及确认框的操作,建议在弹窗内先执行
confirm(),再调用主窗口函数。 - 主窗口函数可增加
skipConfirm参数,避免弹窗确认后主窗口再次确认。 - 提交记录弹窗删除记录后,主窗口成功提示应在
skipConfirm时跳过,避免主页面再弹“提交记录已删除”。 - 教师端文件中存在多个
w.document.write(...</body></html>)弹窗字符串,批量插入脚本时仍须避免误替换字符串里的</body>。
5-1. 任务下方模式维护注意事项
- 任务下方模式通过任务行
id="assignment-row-任务ID"定位当前任务。 - 详情区使用统一行
id="assignment-detail-row",插入到当前任务行后面。 - 每次显示新的任务下方详情前,会先调用
clearRowDetail(),因此同一时间只显示一个详情区。 - 详情区内容容器使用
class="row-detail-content",提交记录的每题统计会插入到该容器内。 - 如果后续给任务列表增加或减少列,需要同步检查任务下方详情行的
colspan。 - 任务下方模式属于主页面 DOM,不需要
opener;弹窗模式才需要opener.xxx()。
6. 答题码空学号规则
答题码名单现在支持学号为空的格式:
班级,姓名,学号,答题码 班级,姓名,,答题码
parseAnswerCodesText()已允许student_id为空,只要有答题码且班级/姓名/学号至少有一项即可保存。verifyAssignmentCode()中,如果数据库保存的student_id为空,则忽略学生端输入的学号。- 因此教师只知道班级、姓名时,也可以提前启用答题码;学生端仍填写自己的学号或座号,但不会参与答题码匹配。
7. 分数段设置维护记录
- 发布任务区域新增“分数段设置”输入框,默认值为
0-34,35-40,41-46,47-50,51-55,56。 - 分数段格式支持区间和单点:例如
0-34、35-40、56。 - 任务列表每条任务新增“分数段”按钮,对应接口
PATCH /api/assignments/:id/score-ranges。 - 任务列表右上角新增“批量分数段”按钮,对应接口
PATCH /api/assignments/score-ranges-all。 - 相关提交:
f7725e9、73939ea、420c84e、a3edcb0。
8. 相关检查命令
cd /var/www/quiz git log --oneline -10 git status --short pm2 status sudo -u postgres psql -d quiz -c "SELECT id, assignment_id, class_name, name, student_id, code, created_at FROM assignment_codes ORDER BY id DESC LIMIT 10;"