Files
ny-exam-platform/docs/guide/settings.md
T
nxxy335 a3be81b261 fix(security): 修复上传名单文件可被 URL 直连访问(nginx 下可 getshell)
严重漏洞:roster_import 使用 $_FILES 的原始扩展名拼 roster_YYYYMMDD_HHMMSS.<ext>
落在 Web 根目录内的 data/uploads/,既无扩展名白名单(可上传 .php),文件名又
可按秒枚举;而 bootstrap 只写了 data/.htaccess,nginx 不读 .htaccess,
生产环境(nginx + PHP-FPM)下可直接远程代码执行。

修复(api.php roster_import 四层防护):
- 扩展名白名单:仅 xlsx / xlsm / csv / txt
- 随机文件名:bin2hex(random_bytes(16)),杜绝枚举
- 解析完成后 @unlink 立即删除,Web 目录下不留存任何上传文件
- 20MB 大小限制 + UPLOAD_ERR 检查

加固(bootstrap.php):data 及 cache/uploads/snapshots 每个目录都写
.htaccess(Deny from all)+ index.html 占位;README 与文档补充 nginx
location ^~ /data/ { deny all; return 404; } 规则。

测试:新增 e2e tools/test-upload-guard.js(11 断言,覆盖 .php/.phtml 被拒、
无残留、合法 csv 导入成功且即删、无可枚举文件名),并入 tests/run-all.sh。
全套 90 项通过。

docs: 新增 VitePress 文档站(18 页)
- 使用指南 12 页:简介/部署/快速上手/系统设置/名单管理/统计口径/试卷设置/
  快照趋势异常/导出打印/数据看板/随机抽取/常见问题
- 安全 2 页:安全总览(含本次漏洞复盘)与部署加固清单(P0-P2)
- 开发 4 页:架构与目录/接口一览/SQLite 存储/测试
- 配图迁移至 docs/public/images/;.gitignore 忽略文档构建产物
2026-09-10 22:42:13 +08:00

71 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 系统设置
点击右上角 **设置** 打开。设置保存到 `data/ny.db`(SQLite);无 `pdo_sqlite` 时落到 `data/config.json`,两者始终双写。
## 连接
| 配置项 | 默认值 | 说明 |
| --- | --- | --- |
| 卷王系统地址 | `https://exam.nxxy335.top` | 结尾不带 `/` |
| 接口前缀 | `/api` | 社区版 `/api`;企业版可能用 `/admin-api` |
| 登录账号 | `admin` | 卷王后台管理员账号 |
| 登录密码 | `123456` | 不回显;留空表示不修改 |
| 校验 HTTPS 证书 | 勾选 | 自签名证书取消勾选 |
| 超时(秒) | `20` | 单次 HTTP 超时 |
::: tip 登录流程(社区版 v1.13)
`GET /api/system` 取 RSA 公钥 → 公钥加密密码 → `POST /api/public/login` 换取 JWT。
注意 v1.13 服务端的 `JwtTokenFilter` **只从 Cookie `sk-token` 读取令牌,不读 `Authorization` 头**,
所以客户端会把登录响应 `Set-Cookie` 中的 `sk-token` 原样带在后续每个请求的 `Cookie` 头里
(同时带 `Authorization` 头以兼容旧版)。平台已内置该流程与令牌缓存,并向下兼容
`/system/auth/login`、`/admin-api/oauth2/token` 两种登录方式。
这也是"自己写的客户端明明拿到 JWT 却仍返回 401 *Full authentication is required*"的根因。
:::
点击 **测试连接** 会立即做一次登录 + 项目列表探测,返回明确的成功 / 失败原因。
::: warning 保存时会自动重新登录的情况
仅当 **地址、接口前缀、账号、密码、证书校验** 五项中任意一项发生变化时,保存才会清除本地令牌缓存
(`data/cache/token.json`)并重新登录卷王。其余项(满分、合格率、刷新间隔、布局等)保存不会打断刷新。
:::
## 数据
| 配置项 | 默认值 | 说明 |
| --- | --- | --- |
| 姓名题关键词 | `姓名,名字,学生姓名,…` | 逗号分隔,用于自动识别答卷中的姓名题 |
| 全局默认满分 | `100` | 试卷未单独设置时使用;每卷满分在主页面「试卷设置」设置 |
| 全局默认合格率 | `60` | 百分比。60 表示「成绩 ≥ 满分 × 60%」记合格 |
| 同一学号去重 | 勾选 | 决定**代表成绩**取最新还是最高分;不影响考试次数与历史完整性 |
| 包含暂存答卷 | 不勾选 | 是否统计未正式提交的答卷 |
| 每页拉取条数 | `200` | 拉取卷王答卷的分页大小,内部会夹在 20–200 之间 |
| 结果缓存(秒) | `15` | 0 = 每次实时拉取。点「立即刷新」会带 `force=1` 跳过缓存 |
::: details 去重到底影响什么
- **代表成绩**:勾选去重后,同一学号只展示一行,取「最新」或「最高」(由去重策略决定);
- **考试次数**:始终是**全部提交次数**,与去重开关无关;
- **历史**:始终完整,点击任意一行可查看该学生的每一次提交。
:::
## 界面
| 配置项 | 默认值 | 说明 |
| --- | --- | --- |
| 默认刷新间隔 | `15` 秒 | 可选 5 / 10 / 15 / 30 / 60 / 120 秒 |
| 布局 | `default` | 成绩明细与侧栏面板的排列方式,适配手机 / 平板 / 桌面三端 |
| 历史成绩展示 | `click` | `click` = 点击成员弹窗查看;`inline` = 成绩明细内直接展开每次考试 |
| 成绩导出排序 | `time` | `time` 提交时间 / `sid` 学号 / `score_desc` 成绩降序 / `score_asc` 成绩升序 |
## 保存与诊断
- 保存后会返回明确结果;若连接信息变化,会附带 `ping` 结果告诉你是否真的连上了卷王;
- 接口 `action=diag` 与 `action=ping` 可用于排障(需已登录);
- 若页面提示 `storage=file`,说明 `pdo_sqlite` 未启用,当前使用 JSON 文件存储,功能不受影响但并发写安全性略低。
## 相关
- [试卷设置(每卷独立)](./project-config.md)
- [数据存储(SQLite)](../dev/database.md)