# 部署指南 > 面向现场操作人员与管理员。本系统完全离线运行,不需要联网,不需要安装任何运行时。 --- ## 1. 运行环境要求 | 项目 | 要求 | | --- | --- | | 服务端主机 | Windows 10 / 11,64 位(amd64) | | 大屏 | 通过 HDMI 接入主机;显示模式设为**扩展**;推荐 16:9、1920×1080 或 3840×2160 | | 网络 | 完全离线即可运行。若需要手机/平板/电视访问,用独立交换机或路由器组成局域网 | | 磁盘 | 建议预留 ≥ 10GB | | 运行时 | 无需安装 Go / Node / 数据库等任何运行时,前端已内嵌进可执行文件 | | 权限 | 首次运行会在 `%LOCALAPPDATA%\ScoreboardConsole\` 下建数据目录并写入配置/规则(见 3.5),并在其下 `data\logs\` 写服务端日志(见第 10 节);Console 自己还在部署目录的 `logs\` 写 `console.log`,请确保这两处可写 | > 服务端只构建 Windows amd64 一个版本。Linux / 其他平台不在交付范围内。 --- ## 2. 获取安装包 在**构建机**(具备 Go、Node.js、pnpm、git 环境,并能访问依赖源)上执行: ```bat deploy\build.bat ``` 脚本依次完成:拉取代码 → 构建前端 → 构建服务端 → 打包桌面宿主。产物落在仓库的 `dist\` 目录: | 产物 | 类型 | 说明 | | --- | --- | --- | | `dist\scoreboard.exe` | 服务端 | 前端已通过内嵌方式打进可执行文件,单独运行也能提供全部页面 | | `dist\console\Scoreboard Console-<版本>-x64.exe` | 安装包 | NSIS 安装程序,双击按向导安装 | | `dist\console\Scoreboard Console-<版本>-x64.zip` | 免安装包 | 解压即用 | | `dist\console\win-unpacked\` | 目录 | 未压缩的运行目录,便于现场调试 | > 版本号来自仓库根的 `VERSION` 文件:构建时由 `deploy\build.bat` 注入服务端二进制(`-ldflags -X main.version`,运行期不再读任何文件),并同步到 `console\package.json` 决定安装包/免安装包的文件名。**本文不写死版本号——以仓库根 `VERSION` 与 `dist\console\` 下的实际文件名为准**,运行期可在管理端「关于」页与 `GET /api/health` 里看到(没走构建脚本直接 `go build` 时兜底显示 `dev`,不编造版本号)。升级版本只改 `VERSION` 一处。 > > 目标机器**不需要** Go / Node / pnpm,也不联网;上述工具只用于构建机。 > > 安装包与免安装包解出来的根目录里,除程序与配置模板外还平铺着两份用户文档 `使用说明.txt`、`报名名单导入规范.txt`(由构建脚本从 `docs\` 暂存,见 3.3)。 --- ## 3. 安装与首次启动 ### 3.1 使用安装包 1. 双击 `Scoreboard Console-<版本>-x64.exe`(版本以 `dist\console\` 下实际文件名为准)。 2. 按向导选择安装目录(可自定义)。 3. 安装完成后,桌面会出现名为**「赛事积分榜」**的快捷方式。 4. 双击「赛事积分榜」启动。 > 赛事数据、配置与规则都存放在 `%LOCALAPPDATA%\ScoreboardConsole\`(不随安装目录走,见 3.4、3.5);服务端日志在数据目录的 `data\logs\` 下,Console 自己的 `console.log` 在安装目录的 `logs\` 下(见第 10 节);安装目录本身只要当前用户可写即可。 ### 3.2 使用免安装包 1. 把 `Scoreboard Console-<版本>-x64.zip`(版本以 `dist\console\` 下实际文件名为准)解压到任意目录(建议使用纯英文路径)。 2. 双击目录里的 `Scoreboard_Console.exe` 启动。 ### 3.3 首次启动会自动完成 - 拉起并守护服务端 `scoreboard.exe`; - 在数据目录(默认 `%LOCALAPPDATA%\ScoreboardConsole\data`)下自动建库、建目录; - 打开服务端管理面板窗口; - 把大屏页面投到扩展屏(只接一块屏时开成窗口化窗口,按 F11 可切全屏;见第 7 节); - 启用防熄屏; - 注册开机自启登录项但**不启用**(任务管理器「启动」里可见、状态为「已禁用」);需要随系统启动时,在服务管理窗口的「开机自启」开关里打开(仅打包后的 Windows 版本生效)。 部署目录结构(安装或解压后): ``` 安装目录\ ├── Scoreboard_Console.exe # 桌面宿主,双击启动 ├── resources\app.asar # Console 自身资源 ├── scoreboard.exe # Go 服务端 ├── scoreboard.png # 应用图标 ├── config.json # Console 配置模板(首次启动复制到用户配置目录) ├── config.yaml # 服务端配置模板(同上) ├── rules\ # 规则包模板(同上) ├── 使用说明.txt # 用户手册,记事本直接打开 ├── 报名名单导入规范.txt # 报名表填写规范,可直接转发给各单位填表人 └── logs\console.log # 首次运行生成:运行日志 ``` > 根目录里那两份 `.txt` 与 `scoreboard.exe`、`config.yaml`、`config.json`、`scoreboard.png`、`rules\` 平铺在同一层,是构建时从 `docs\` 暂存进包的用户文档:`使用说明.txt` 即使用手册,`报名名单导入规范.txt` 可直接发给填表人(两者都按纯文本存放,用记事本打开即可,中文不会乱码)。改动了仓库里的 `docs\` 文档后需要**重新打包**才会更新到包内。 配置、规则与数据**都不在安装目录里**,各是独立的一处(详见 3.4、3.5): ``` %LOCALAPPDATA%\ScoreboardConsole\ # 用户数据目录(与安装目录无关) ├── data\ # 数据库、备份、上传的图片、服务端日志(data\logs\) ├── console\config.json # Console 自己的配置(实际生效的那份) ├── config.yaml # 服务端配置(实际生效的那份) └── rules\ # 规则包(实际生效的那份) ``` > 服务端日志默认落在 `data\logs\` 下(`operations\` 放行类、`interceptions\` 拦截类,见第 10 节),所以它同样在安装目录之外、覆盖安装不会丢。 ### 3.4 数据放在哪里(升级/卸载都不会删) 覆盖安装时安装程序会**先静默运行旧版卸载器**,而卸载器会把整个安装目录删掉,所以数据库、规则、用户、上传的图片与字体都不能放在安装目录里。现在的规则: | 场景 | 数据目录 | | --- | --- | | 默认(`console\config.json` 的 `server.dataDir` 留空,该文件见 3.5) | `%LOCALAPPDATA%\ScoreboardConsole\data`,即 `C:\Users\<用户名>\AppData\Local\ScoreboardConsole\data` | | 想放到别处,例如 D 盘 | 在 `%LOCALAPPDATA%\ScoreboardConsole\console\config.json` 里把 `server.dataDir` 改成 `"D:\\scoreboard-data"`(绝对路径),或相对安装目录的路径 | | 直接双击 `scoreboard.exe`(不经 Console) | 服务端默认行为不变:用工作目录下的 `data\`,**这种用法在覆盖安装时仍会丢数据** | - Console 启动时会把这个目录用 `-data` 参数交给服务端,界面上方「运行日志」里能看到 `数据目录:...` 这一行; - **服务端日志也在数据目录下**:`<数据目录>\logs\` 里分 `operations\`(放行类)与 `interceptions\`(拦截类)两个子目录,按天 + 按大小轮转、自动清理旧文件,可用 `config.yaml` 的 `log:` 段改位置与保留份数(见第 10 节)。它在安装目录之外,覆盖安装不会丢; - **恢复未完成标记**:数据目录根部的 `pending_restore.json` 只在「一键恢复」**中途异常中断**时留下(正常走完会被清掉),里面记着备份文件名、目标库、恢复前的安全副本与操作员。服务端下次启动会读它并在运行日志里给一条 WARN(含处置建议),**只读不删**——它是现场判断「这台机器上发生过一次没走完的恢复」的唯一线索。平时这个文件不存在,看到它就该按日志提示核对数据库; - **上传资源随赛事项目走**:赛事会标、大屏背景图、单位 logo,以及「赛事信息 → 字体」里上传的**字体文件**,都在 `<数据目录>\event_projs\<项目名>\uploads\` 里(字体在 `uploads\fonts\`)。它们是文件而不是数据库记录,**不在 `backups\` 的 `.db` 快照里**(`.db` 快照与「一键恢复」都只动数据库,不含这些文件);备份 / 迁移时要连这个目录一起拷(见 8.5、8.6)。构建/开发机上的 `deploy\backup.bat` 会额外把每个项目的 `uploads\` 打成 `*-uploads-<时间戳>.zip`,但那份 zip 不在服务端的 20 份滚动保留里,要手工清理; - **一次性迁移**:如果新数据目录还是空的,而旧的 `<安装目录>\data` 里有数据,Console 会在启动时把旧目录**整体改名**过去(同盘很快),日志里会写「已把旧数据从 X 迁移到 Y」。任何失败(跨盘、权限不足等)都只记日志,旧数据原地保留,不会被删; - 如果两个目录里**都有数据**,Console 不做任何动作,只写一条警告日志,由现场人工决定保留哪一份; - 迁移只在安装包版本(打包后)执行一次,`pnpm start` 之类的开发运行不会碰仓库里的 `data\`; - 迁移的**旧目录只认 `<安装目录>\data`**。如果你已经把数据迁到默认位置、之后又想改成 D 盘等其他目录,Console **不会**自动搬运:请先停服务,把 `%LOCALAPPDATA%\ScoreboardConsole\data` 整个目录复制到新路径,再改 `server.dataDir` 并重启 Console。 > 升级到本版本后第一次启动,请打开 Console 的「运行日志」确认是否有迁移记录,并核对管理端里的赛事/账号数据是否完整。 > > **升级前先看 [《升级说明》](./升级说明.md)**:里面按版本写了「这一版改了什么、升级后要补做什么、分数或奖牌为什么会变化」。本版(dev_0.1.8)有一条**需要人工补做的事**(「个人+团体」项目的团体名次)。 **升级后奖牌榜可能短暂是空的(正常,会自己补齐)** 本版给赛事库加了一条新迁移(**0020**),并新增了一张存"通道名次"的表与三个奖牌开关列。迁移**只建表、不回填**历史名次,所以**老库升级后、服务端做第一次重算之前,奖牌榜(金 / 银 / 铜)会是 0**——这不是数据丢了。 服务端**下一次启动时会自动做一次全量重算**把名次补齐(这次重算在开始对外提供服务之前跑完,所以大屏不会先亮出一帧全 0)。在 Console「运行日志」或 `<数据目录>\logs\operations\` 的当天日志里能看到这两行,**是正常的,不是报错**: ``` 检测到通道名次为空但已有已发布成绩,正在执行一次性全量重算 一次性全量重算完成,通道名次与奖牌榜已补齐 ``` - 通常**几秒内**跑完(成绩多时稍久,日志里会写处理了几个小项与耗时);跑完后刷新管理端 / 大屏,奖牌就回来了; - 这是**一次性**的:表里已经有名次(说明重算过)之后不会再触发; - 若日志里出现 `通道名次自愈失败,奖牌榜可能仍为空;可重新发布成绩或导入规则包触发重算`,说明这次重算没做完(不影响服务启动):**随便发布一次成绩**,或**重新导入一次规则包**(会全量重算),奖牌同样会回来。升级前如果做过备份,先确认榜单数字与升级前一致再开赛(见《上线检查清单》)。 - 升级命令与首次启动的完整流程见 3.4 开头;这条迁移是**加列 / 建表**,可重复执行,不会改动任何既有数据行。 ### 3.5 配置与规则放在哪里(升级/卸载也不会丢) `config.json`、`config.yaml`、`rules\` 以前跟着安装包落在安装目录里,而**安装目录会被卸载器整个删掉**(覆盖安装会先静默跑一遍旧卸载器),所以用户改过的配置和规则包会一起没。本版本起它们也搬到**用户数据目录**(与数据目录同一处,安装目录里那三份只当模板): | 内容 | 实际生效的位置 | 管什么 | | --- | --- | --- | | Console 配置 | `%LOCALAPPDATA%\ScoreboardConsole\console\config.json` | 端口、选屏策略、开机自启等(见 6.2) | | 服务端配置 | `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` | 监听地址/端口、备份间隔、日志级别与日志目录(见 6.1) | | 规则目录 | `%LOCALAPPDATA%\ScoreboardConsole\rules\` | 规则包 YAML;管理面板「导出 → 规则包 → 选择 YAML」时从这里选 | **怎么改**:直接编辑上表里的文件。`config.json` 与 `config.yaml` 都是 Console 启动时读一次,改完要**退出并重启 Console**(托盘 → 退出);服务管理窗口的「配置文件」一行显示的就是 `console\config.json` 的实际路径,可用来确认。`rules\` 只是规则包存放目录,往里放文件不需要重启,但规则要在管理面板「导出 → 规则包 → 选择 YAML → 预览差异 → 确认导入并全量重算」里导入后才生效(服务端不会自动加载)。安装目录里那三份是模板,改了不生效。 升级/覆盖安装时: - **首次运行会复制**:用户目录里还没有、安装目录里有 → 原样复制一份过去,安装目录那份留着当模板; - **已存在就绝不覆盖**:用户目录里已经有一份时一律用它,Console 既不覆盖也不删除,之后升级只换程序不换配置; - Console 拉起服务端时传**绝对路径** `-config <用户目录>\config.yaml`,`passwd` 子命令用同一份(见 5.5),不会两边各读各的; - 规则目录只在首次复制一次,之后你往里面放的规则包不会被模板顶掉; - 某一处建不出来或不可写(权限、杀毒软件拦截等)时**只记日志并回落到安装目录里那份**,绝不因此启动失败;代价是那份改动仍会在覆盖安装时丢失,日志里会明确写出来; - 开发运行(`pnpm start`)不接管这三项,仍用仓库里的 `console\config.json`、`config.yaml`、`rules\`; - 不用 Console、直接双击 `scoreboard.exe` 时,服务端仍按工作目录下的 `config.yaml` 读配置,**不受这套保护**; - 上传的图片与字体不在这三项里,位置见 3.4:它们随赛事项目存放在数据目录的 `event_projs\<项目名>\uploads\`(字体在 `uploads\fonts\`),同样在安装目录之外,覆盖安装不会动它们;**但它们不在 `backups\` 的数据库快照里**(「一键恢复」也只换数据库文件,不会带回图片),备份 / 迁移时要连这个目录一起拷(见 8.6)。 > 从老版本覆盖升级上来的第一次启动:安装目录里那三份在覆盖安装时**就已经被旧卸载器删过一遍**(新装进去的是新版模板),Console 只能把模板复制到用户目录,升级前改在安装目录里的配置需要重做一遍。**从本版本起**(也就是改动落在上表位置之后),配置与规则才会跨升级保留。 > 日志分两处,别找错:Console 自己的排查日志留在安装目录的 `logs\console.log`(每次运行都会重写,不值得搬,卸载后会消失);**服务端的运行 / 放行 / 拦截日志在数据目录的 `logs\` 下**(`operations\` 与 `interceptions\` 两类,随数据目录跨升级保留,见第 10 节)。 首次启动后看 Console「运行日志」里这几行核对: | 日志 | 含义 | | --- | --- | | `已把 Console 配置文件 复制到 …` | 三项各自的首选复制记录,各一条,属正常 | | `用户数据目录:<路径>` | 本次实际使用的用户数据目录 | | `警告:准备 … 失败…` | 该项回落到安装目录里那份,按日志里的路径手工复制过去再重启 Console | | `警告:拿不到 %LOCALAPPDATA%…` | 系统环境异常,三项都还在安装目录里,覆盖安装会丢改动 | --- ## 4. 服务管理窗口(Scoreboard Console) 启动后首先看到的是「Scoreboard Console · 启动管理」窗口。 ### 4.1 窗口按钮 | 位置 | 按钮 / 项 | 作用 | | --- | --- | --- | | 服务端状态 | **启动服务** | 手动拉起服务端 | | 服务端状态 | **停止服务** | 优雅停止服务端(会先请求服务端关闭,超时后强制结束) | | 服务端状态 | **重启服务** | 停止后重新启动 | | 服务端状态 | 状态行 | 已停止 / 启动中 / 运行中 / 停止中 / 异常退出 | | 服务端状态 | 服务地址、进程 PID、自动重启、防熄屏 | 实时运行信息 | | 服务端状态 | 服务端程序、配置文件 | 实际使用的可执行文件与配置路径 | | 大屏窗口 | **重新排布** | 重新枚举显示器并重开大屏窗口 | | 大屏窗口 | **打开服务端管理面板** | 用内置窗口打开 `http://127.0.0.1:<端口>/admin`,无需再开浏览器 | | 运行日志 | **自动滚动** | 日志是否跟随最新一行 | | 运行日志 | **清空** | 清空窗口内显示(不影响日志文件) | | 已连接的大屏实例 | — | 显示在线大屏窗口的数量与各自页面,5 秒刷新;需先在管理面板登录过(记录员及以上)才能读到 | | 管理员账号 | **重置账号** | 本机直接调用同目录的 `scoreboard.exe passwd`,自动带上用户目录里的 `config.yaml` 与数据目录,**不需要登录**;会作废该用户的全部登录会话(见 5.5) | | 标题栏 | 最小化 / 最大化 / 关闭 | 关闭只是**隐藏到托盘**,服务端继续运行 | ### 4.2 托盘用法 - 托盘菜单显示当前服务端状态(如「服务端:运行中 · 就绪」); - 菜单项:**启动服务**、**停止服务**、**重启服务**、**打开管理端**、**打开服务端管理面板**、**退出**; - 双击托盘图标 = 打开启动管理窗口; - **要真正退出程序,必须用托盘菜单的「退出」**;直接关窗口不会停服务端。 > 同一台机器只允许运行一个 Console 实例,重复启动会自动退出,避免抢占端口和大屏。 --- ## 5. 首次登录 ### 5.1 访问地址 | 用途 | 地址 | | --- | --- | | 本机管理面板 | `http://127.0.0.1:19540/admin` | | 局域网管理面板 | `http://<主机IP>:19540/admin` | | 大屏 | `http://<主机IP>:19540/display` | | 导航页 | `http://<主机IP>:19540/` | > 端口默认 **19540**,以 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` 为准(该文件在哪见 3.5)。 ### 5.2 初始账号 - 用户名:`admin` - 初始密码:**首次启动时随机生成,只打印一次**,数据库里只保存加密后的哈希,无法反查。 ### 5.3 从日志里找初始密码 用 Console 启动时,服务端输出会同时显示在「运行日志」面板并写入 `logs\console.log`。初始密码所在的一段输出形如: ``` [server] 初始密码: xxxxxxxxxxxxxxxx [server] 首次登录后请立即修改密码 [server] === INITIAL ADMIN CREATED (password above) === ``` 在部署目录下执行以下命令即可找到(搜索纯 ASCII 标记行,中文 `findstr` 在 chcp 936/65001 下不可靠): ```bat findstr /C:"INITIAL ADMIN CREATED" logs\console.log ``` 搜到这一行后,初始密码就在它上面几行(形如 `初始密码: xxxxxxxx`)。也可以直接打开 Console 的「运行日志」面板往上翻。 > 如果不用 Console、直接双击 `scoreboard.exe`,密码只会打印在终端窗口里,不会落文件。建议始终通过 Console 启动。 ### 5.4 修改密码 - 首次登录后会自动跳到**「修改密码」**页,必须改密才能继续使用; - 新密码至少 8 位,需输入两次确认; - 修改成功后所有会话作废,需用新密码重新登录; - 管理员也可以在「系统设置 → 用户管理 → 重置密码」为其他用户重置。 ### 5.5 忘记密码 / 没记下初始密码 **首选:服务管理窗口的「管理员账号」卡片。** 在卡片里填「当前用户名」(默认 `admin`),「新用户名」「新密码」留空即「不改名 / 随机生成」,点「重置账号」,命令输出会直接显示在卡片里。这条路径不需要登录,Console 会自动用上用户目录里的 `config.yaml` 与数据目录。 **命令行方式**:在 Console 里先**停止服务**,然后在安装目录(`scoreboard.exe` 所在目录)执行: ```bat scoreboard.exe passwd -config "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" -data "%LOCALAPPDATA%\ScoreboardConsole\data" ``` > **`-config` 与 `-data` 都不能省**:`-config` 指到 Console 实际使用的用户配置(安装目录里那份只是模板,见 3.5);`-data` 不加的话服务端会去用工作目录下的 `data\`,改的是另一个库里的账号(那个库是空的,看起来像「密码重置成功但登录不上」)。数据目录改过配置的,把这里换成用户配置里 `server.dataDir` 实际生效的那个路径;实在不确定,可以先看 `logs\console.log` 里 `数据目录:` 那一行。 该命令会为用户 `admin` 生成一个新的随机密码并打印出来,同时要求下次登录强制改密。可选参数: | 参数 | 说明 | | --- | --- | | `-user <用户名>` | 指定要重置的用户,默认 `admin` | | `-password <新密码>` | 直接指定新密码;留空则随机生成并打印 | | `-new-username <新用户名>` | 同时修改用户名 | | `-data <数据目录>` | 数据目录;通过 Console 启动时必须带上同一个值(见第 3.4 节) | --- ## 6. 端口与配置修改 ### 6.1 服务端配置 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` > 路径说明见 3.5;安装目录里的 `config.yaml` 只是模板,改它不生效。 | 项 | 当前默认值 | 说明 | | --- | --- | --- | | `server.host` | `0.0.0.0` | 监听地址;`0.0.0.0` 表示局域网内其他设备也能访问 | | `server.port` | `19540` | HTTP 端口 | | `dataDir` | `data` | 数据目录,相对服务端工作目录解析;**Console 启动时会用 `-data <绝对路径>` 覆盖它**,配置项本身只对直接运行 `scoreboard.exe` 时生效 | | `log.level` | `info` | 控制台与 `operations\` 放行类日志的采集级别:`debug` / `info` / `warn` / `error`;拦截类日志固定按 `debug` 采集,不受它影响(见第 10 节) | | `log.dir` | 空 | 日志根目录(其下分 `operations\` / `interceptions\`);留空 = `<数据目录>\logs` | | `log.maxSizeMB` | `20` | 单个日志文件大小上限(MB);`<=0` 用内置默认 | | `log.keepFiles` | `10` | 每类日志保留份数(含当前文件);`<=0` 用内置默认 | | `backup.intervalMinutes` | `5` | 自动备份间隔(分钟),`<=0` 关闭自动备份 | | `backup.keep` | `20` | 每个库滚动保留的备份份数 | | `server.allowedOrigins` | `[]` | 额外放行的 WebSocket Origin 白名单;默认只放行同源与不带 Origin 的客户端 | ### 6.2 Console 配置 `%LOCALAPPDATA%\ScoreboardConsole\console\config.json` > 路径说明见 3.5;安装目录里的 `config.json` 只是模板,改它不生效。服务管理窗口的「配置文件」一行显示的就是这份实际路径。 | 项 | 当前默认值 | 说明 | | --- | --- | --- | | `server.exe` | `scoreboard.exe` | 服务端可执行文件;相对路径会在部署目录(安装目录)、当前目录、上级目录、`dist\` 依次查找 | | `server.args` | `["-config","config.yaml"]` | 传给服务端的启动参数;其中 `-config` 的**值**会被换成 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` 的绝对路径,其余参数(如 `-project`)按原样传(见 3.5) | | `server.dataDir` | `""` | 数据目录;留空 = 用默认位置 `%LOCALAPPDATA%\ScoreboardConsole\data`。填了就按它来:绝对路径直接用,相对路径以安装目录为基准。Console 会把它作为 `-data <绝对路径>` 追加到启动参数后(见第 3.4 节) | | `server.host` / `server.port` | `127.0.0.1` / `19540` | 健康检查与打开管理面板用的地址;配成 `0.0.0.0` 时本机探测自动改走回环地址 | | `server.autoStart` | `true` | Console 启动后是否自动拉起服务端 | | `server.healthIntervalMs` | `5000` | 健康检查间隔(毫秒) | | `server.restartDelayMs` | `3000` | 崩溃重启的初始退避,之后逐次翻倍 | | `server.maxRestarts` | `0` | `0` = 无限重启 | | `server.shutdownTimeoutMs` | `5000` | 等待服务端优雅退出的上限 | | `server.healthyResetMs` | `60000` | 连续健康多久后清零崩溃计数 | | `display.selection` | `all-secondary` | 选屏策略,见第 7 节 | | `display.displays` | `[]` | 仅 `specified` 策略生效,填显示器 id 或 0 起序号 | | `display.pages` | `["standings","medals","individual","results"]` | 多屏时按屏序轮转锁定的页面;可加入 `live`(实时赛况)等页面值,见 7.4 | | `display.pageParam` | `page` | 页面 URL 参数名 | | `display.kiosk` | `true` | 多屏时是否 kiosk 全屏(单屏恒为窗口化) | | `display.allowClickThrough` | `false` | 多屏 kiosk 窗口是否**允许点击穿透**。默认 `false` = 窗口吞掉鼠标点击:点击不会漏到大屏后面的窗口 / 桌面,被别的窗口盖住后点一下就能回到最前。显式写 `true` 才穿透,代价是**误触会点到后面别的东西**、大屏也点不活。本键是 `display.ignoreMouse` **改名**来的 | | `display.reloadEveryHours` | `24` | 定时重载兜底,`0` = 关闭 | | `display.waitForServerMs` | `20000` | 服务端未就绪时,最多等这么久也先把大屏开出来 | | `display.backgroundColor` | `#0b0f1a` | 大屏窗口底色,减少加载白闪 | | `startup.openAtLogin` | `false` | 默认注册但不启用(任务管理器可见、已禁用);在服务管理窗口「开机自启」开关里控制 | | `admin.width` / `admin.height` | `1280` / `860` | 启动管理窗口初始尺寸(服务管理窗口与服务端管理面板共用) | | `log.maxLines` | `2000` | 内存中保留的日志行数 | | `log.file` | `logs/console.log` | 日志文件,相对部署目录(安装目录)解析 —— 日志不随配置搬到用户目录 | | `log.maxFileBytes` | `4194304` | 单个日志文件上限,超出后轮转为 `.1` | > 配置缺项会自动使用内置默认值;JSON 语法错误时回落到全默认值,并在窗口顶部提示。 > > 配置文件里若还留着**已废弃的** `display.ignoreMouse`(旧版模板会写 `ignoreMouse: true`),这个键现在**完全不读**,启动时会在 `logs\console.log` 里记一条中文警告(只在启动记一次,不随大屏重排重复刷):旧键已废弃、现在默认不穿透、想穿透改用 `display.allowClickThrough: true`。旧键的值一律忽略,也**不会**被自动改写进你的配置文件。 ### 6.3 修改端口 端口必须**两处一致**: 1. 关闭 Console(托盘 → 退出); 2. 修改 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` 的 `server.port`; 3. 修改 `%LOCALAPPDATA%\ScoreboardConsole\console\config.json` 的 `server.port`(保持一致,否则 Console 健康检查和管理面板地址会失效); 4. 重新启动 Console; 5. 之后管理面板地址变为 `http://<主机IP>:<新端口>/admin`。 --- ## 7. 大屏接线 ### 7.1 主方案:Console 驱动 HDMI 1. 用 HDMI 线连接主机与大屏; 2. 在 Windows「显示设置」里把显示模式设为**扩展**(不要「复制」); 3. 确认分辨率与缩放(推荐 1920×1080 或 3840×2160,缩放按现场效果调整); 4. 启动 Console,大屏窗口会自动打开。 ### 7.2 选屏策略(`display.selection`) | 取值 | 行为 | | --- | --- | | `all-secondary`(默认) | 除内置屏外的每块屏各开一个大屏窗口;若所有屏都报告为非内置屏(纯台式机),退回「排除主屏」 | | `all` | 所有显示器都开大屏窗口 | | `specified` | 只对 `display.displays` 里列出的显示器开窗;可填显示器 id 或 0 起序号 | ### 7.3 单屏:窗口化 + F11 - 只有一块屏时,大屏以窗口化方式打开(屏幕 80% 大小、居中),并显示自绘标题栏; - 在窗口内按 **F11** 切换全屏 / 窗口:全屏时自绘标题栏与「实例 · 屏」标识一起隐藏、画面铺满整块屏;再按一次恢复; - 单屏模式不跟随显示器变化重排; - 窗口化且未全屏时右上角显示一行「实例 … · 屏 …」供现场核对(数据版本号角标已移除,见 7.7)。 ### 7.4 多屏:每屏锁一页 - 除内置屏外的每块屏各开一个 kiosk 全屏窗口; - 每块屏**锁定一个页面**,按 `display.pages` 的顺序轮转分配; - 多屏 kiosk 不响应 F11,也不显示任何调试标识;窗口**默认吞掉鼠标点击、不穿透**——点击不会漏到大屏后面的窗口或桌面,被别的窗口盖住后点一下就能回到最前,只有显式配 `display.allowClickThrough: true` 才会让点击穿透(代价是误触会点到后面的东西、大屏也点不活);kiosk 窗口固定置顶在 `screen-saver` 层级,**压得住任务栏**,别的窗口拖到这块屏上也盖不住它; - 可用的页面值:`standings`(总积分榜)、`medals`(单位奖牌榜)、`individual`(个人积分奖牌榜)、`results`(分项成绩)、`live`(实时赛况)、`announcement`(公告);管理端切页用的是同一批页面的规范名 `team-medals` / `athlete-standings` / `event-results`,两种写法大屏都认。把 `live` 加进 `display.pages`,就能把某块屏固定成「实时赛况」(`live` 也认 `live-results` / `实时赛况` / `实时` / `赛况` 这些别名,与其它页一样)。 - 这一页现在叫 **「单位奖牌榜」**(旧称「团体奖牌榜」容易和「团体项目的奖牌」混淆,已改名);**旧名 `团体奖牌榜` 仍作为别名被接受**,老配置里的 `display.pages`、大屏地址里 `page=团体奖牌榜` 这类老链接都照常生效,不需要改。 ### 7.5 在管理端单独控制某块屏 管理端「大屏控制」页有**显示器**卡片,可以给指定显示器单独开窗或收窗。这张卡片只在**从服务管理窗口点「打开服务端管理面板」打开的那个窗口**里可用;用浏览器直接打开管理面板时,卡片会提示此功能不可用。 同一页右侧还有「**局域网大屏**」卡片:把本机可用的局域网地址拼成完整链接(可点开、可一键复制),第一个地址还带二维码,别的设备扫码即可当大屏用。卡片需**录入员及以上**登录才看得到,用法见《使用手册》第 9 节。 - 列出本机每块显示器(名称、分辨率、缩放、是否主显示器、当前有没有大屏窗口、是全屏还是窗口化); - 先在「窗口页面」里选页面,再对某块屏点「创建全屏窗口」或「创建窗口(窗口化)」;已经开着窗口的屏显示「聚焦窗口」与「关闭窗口」; - **全屏只对「多显示器 + 非主显示器」开放**:只接一块屏、或选中的是主显示器时只能创建窗口化窗口(全屏会把桌面整个盖住又退不出来)。请求被降级时窗口照开,提示里会写明原因; - 「重新排布」按 Console 配置的选屏策略重建全部窗口,不在策略内的窗口会被关掉。 副屏的全屏窗口被误关时,用这里的「创建全屏窗口」就能恢复,不必重启 Console。 **「局域网大屏」卡片:怎么拿到地址(两条路)** 1. **卡片自动检测**:「大屏控制 → 局域网大屏」卡片会列出主机上可用的地址,直接点开或扫码;第一个地址(也就是二维码那一条)就是现场最可能要用的; 2. **`ipconfig` 手填**:自动检测不到、或者列出来的不是你那张网卡时,在管理员 cmd / PowerShell 里跑 `ipconfig`,把对应网卡的 **IPv4 地址**抄进卡片下方的「手动添加地址」再点「添加」——手填的排在第一条、带「手动」徽标,记在浏览器本地,刷新不丢(最多 10 个,可「删除」)。 **三类地址的含义**(卡片按类别判定,只排除回环 / 未指定 / 组播 / 广播这四类对本机之外没有意义的地址): | 类别 | 形如 | 现场含义 | 排序 | | --- | --- | --- | --- | | 内网地址 | `192.168.x.x`、`10.x.x.x`、`172.16–31.x.x` | 路由器 / 交换机分配或手工设的地址,最常用的现场形态 | 最前 | | 全局地址 | 其它网段(运营商专线等) | 能用就照常列出,但多半要经中间设备才通,不一定平板能直连 | 居中 | | **自动配置地址** | **`169.254.x.x`** | **本网段没有 DHCP**,地址是设备自己配的;在独立小网络(没路由器、设备直连或只接交换机)里是**正常用法**,同网段设备可互相访问 | 最后 | 另外两种来源会各带一句提示、**照常能用**:网卡状态读不到时提示「网卡状态读不到(可能是驱动或虚拟网卡),地址本身可用,打不开就换下面一条」;按系统默认路由推断出来时提示「这条地址是按系统默认路由推断出来的(网卡枚举里没有它),地址本身可用,打不开就换下面一条」。 **防火墙要放行入站 19540**(默认端口,改过端口就换成实际端口)。安装包会替你放行,分两种情况: - 安装时选了**「为所有用户安装」**:安装程序会弹 UAC 提权,**提权那一趟就顺手把入站 19540 放行了**(规则名 `Scoreboard Console (TCP 19540)`),装完不用再管;卸载时会自动把这条规则删掉。 - 安装时选了**「仅为我安装」**:全程不申请提权,装不了防火墙规则,**必须手工放行**。也可以重新跑一次安装程序、这次选「为所有用户安装」,让它自动放行。 手工兜底(给「仅为我安装」的机器,以及改过端口、装了才发现要放行的机器)——在**管理员** cmd 里跑: ```bat netsh advfirewall firewall add rule name="Scoreboard 19540" dir=in action=allow protocol=TCP localport=19540 ``` 注意这条手工规则的名字(`Scoreboard 19540`)与安装程序自动加的那条(`Scoreboard Console (TCP 19540)`)**不一样**:卸载时安装程序只会删掉自己加的那条,手工加的这条要自己 `netsh advfirewall firewall delete rule name="Scoreboard 19540"` 删;两条同时存在只是重复放行,不影响使用。 临时关掉防火墙只能用来**验证**「是不是防火墙挡的」,验证完要恢复;正式开赛前一定用上面这条命令(或改用「为所有用户安装」重跑一次安装程序)放行,而不是一直关着防火墙。 **排障常识**:Windows 里「网线插着、网卡显示 Up」**不等于拿到了 IP**;`ipconfig` 里看到 `169.254.x.x` 说明**本网段没有 DHCP**(地址是设备自配的)——这在**独立小网络**里是正常用法,**不是故障**,同网段的平板 / 手机照样能访问。 ### 7.6 公告 管理端「大屏控制 → 公告」推送后: - 大屏先**全屏**显示这条公告,时长取「滚动时长」(5–120 秒,实际就是全屏展示的秒数),到时自动退回推送前的那一页; - 公告正文随后留在**大屏底部的公告栏**(底栏固定的「公告」槽位)常驻显示,直到被下一条公告替换或点了「清除公告」才消失; - 公告正文存在服务端设置里:断线重连、重开大屏窗口都会重新拿到它(新开的窗口会先全屏放 15 秒再落回底栏);点「清除公告」后底栏回到「暂无公告」。 ### 7.7 大屏画面里的调试标识与单位 logo - **调试标识**:只有**窗口化且没有全屏**的大屏窗口才会显示一行「实例 … · 屏 …」;**多屏 kiosk 全屏完全不带调试标识**,单屏窗口化窗口按 F11 全屏后它也会连同自绘标题栏一起隐藏。数据版本号角标 `v` 已移除,任何情况下都不再显示(要核对某块屏是否跟上服务端,看管理端仪表盘「实时连接」里的「版本 N」)。要一个完全干净的展示画面,就用多屏 kiosk; - **单位 logo**:参赛单位的 logo 会显示在总积分榜、单位奖牌榜、分项成绩表与个人积分榜上(个人榜显示在姓名前);没上传 logo 的单位不占位。logo 在「基础数据 → 参赛单位」里上传,随赛事项目包一起导出/导入(见 8.5)。 ### 7.8 大屏窗口的自动行为 | 场景 | 行为 | | --- | --- | | 防熄屏 | Console 启动即生效,前后台都算 | | 服务端就绪 | 首次健康检查通过后开大屏 | | 服务端迟迟不就绪 | 等待超过 `waitForServerMs`(默认 20 秒)也先开,避免只见桌面 | | 服务端崩溃 | 按退避策略自动重启,恢复后大屏自动重载 | | 长时间运行 | 每 `reloadEveryHours`(默认 24 小时)自动重载一次 | ### 7.9 备选方案:安卓 TV / 设备自带浏览器 如果不用 Console 驱动 HDMI,也可以让大屏设备用自己的浏览器访问: ``` http://<主机IP>:19540/display ``` - 这个地址不用手敲:管理端「大屏控制 → 局域网大屏」卡片里有**可复制的完整链接**与**二维码**(第一个地址),别的设备扫码或在浏览器里粘贴即可打开(该卡片需录入员及以上登录才看得到); - 需要主机设置固定 IP,且设备与主机在同一局域网; - **主机防火墙要放行入站 19540**(默认端口):装的时候选**「为所有用户安装」**,安装程序提权时会自动放行,不用再管;选了「仅为我安装」就得手工放行一次,命令与说明见 7.5; - 安卓电视一般常亮,无需额外防熄屏; - 浏览器方案**不受** Console 的 kiosk、防熄屏、选屏控制,字体与动画表现以实际设备为准; - 现场以 Console + HDMI 为主方案,浏览器访问作为备用。 --- ## 8. 备份与恢复 ### 8.1 备份目录 所有备份都在**数据目录**下的 `backups\` 里,主体是 `.db` 快照;用 `deploy\backup.bat` 时还会多出 `*-uploads-*.zip`(上传图片与字体)。默认数据目录是 `%LOCALAPPDATA%\ScoreboardConsole\data`,所以备份在: ``` %LOCALAPPDATA%\ScoreboardConsole\data\backups\ ``` 数据目录改过 `server.dataDir` 的,按改后的路径找(同目录下的 `backups\`)。该目录不进版本库。 ### 8.2 如何备份 | 方式 | 操作 | 产物 | | --- | --- | --- | | 自动 | 服务端启动后按 `backup.intervalMinutes`(默认 5 分钟) | `system-auto-<时间戳>.db` 与 `event-auto-<时间戳>.db` | | 管理端 | 「系统设置 → 备份 → 立即备份」 | `system-manual-<时间戳>.db` 与 `event-manual-<时间戳>.db` | | 命令行 | 在安装目录执行 `scoreboard.exe backup -config "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" -data "%LOCALAPPDATA%\ScoreboardConsole\data"` | `system-manual-<时间戳>.db` 与 `event-manual-<时间戳>.db` | | 快捷脚本 | `deploy\backup.bat`(原因记 `manual`)或 `deploy\backup.bat publish`(原因记 `publish`) | 同上(`publish` 时原因为 `publish`);**额外**把每个赛事项目的 `uploads\`(图片 + 上传字体)打成 `<项目名>-uploads-<时间戳>.zip` | | 发布前 | 每次发布成绩时自动 | `event-publish-<时间戳>.db` | - 备份使用 SQLite 的 `VACUUM INTO`,服务运行中也能安全执行,不需要停服务,也不依赖外部 `sqlite3.exe`; - 自动、手动、命令行三种方式都按 `backup.keep`(默认 20 份)滚动保留,`system` 库与赛事库各自计算,超出的旧备份自动删除; - ⚠️ **`*-uploads-*.zip` 不在 20 份滚动保留范围内**(滚动只认 `.db`):它不会被自动清理,需要手工删旧文件(建议每个项目至少留最新一份)。`deploy\backup.bat` 不归档日志(`logs\`),排查材料要单独拷;空的项目 `uploads\` 会跳过,不产空包; - 同一时刻只允许一个备份在进行,自动备份与手动、发布前备份不会互相踩; - `deploy\backup.bat` 会自动用和 Console 相同的配置与数据目录(配置优先 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml`、数据优先 `%LOCALAPPDATA%\ScoreboardConsole\data`,即用户数据目录里那两份;都不存在时回退到脚本上级目录 `..\config.yaml` 与 `..\data`),并分别作为 `-config` / `-data` 传给服务端,所以从哪里运行都不会备份到别的库; - 现场建议:开幕前、每次发布高峰前、每天结束时各手动备份一次(自动备份只是兜底)。 ### 8.3 如何恢复 **首选:管理端「一键恢复」(仅管理员)。** 到「系统设置 → 备份」,在备份列表里找到要退回到的那一份(列表会显示时间、原因、大小;`pre-restore-…` 是恢复前自动留的安全副本),点该行的**「恢复」**: 1. 先过危险确认框(会写明要覆盖的是系统库还是赛事库、会丢弃备份时间点之后的改动); 2. 服务端先**给当前库拍一份「恢复前安全副本」**(`pre-restore-<库>-<时间戳>.db`,不参与 20 份滚动清理,选错时间点还能用它退回;**每类库只保留最近 5 份**,更旧的会在后续恢复时被自动清理),再替换库文件,然后**优雅退出并由 Console 自动拉起**; 3. 页面显示「正在重启服务端…」并自动等待刷新(最多 60 秒;超时就打开服务管理窗口确认状态后手动刷新); 4. **恢复是覆盖式的**:备份时间点之后的所有改动都会丢。恢复系统库还会连**账号与会话**一起换掉,重启后需要用那个时间点的账号重新登录; 5. ⚠️ **恢复不会带回上传的图片与字体**(它们在 `uploads\` 里是文件,不在 `.db` 内);需要一起回退时见 8.6。 **没有 Console 或恢复不了时的兜底:停机手工替换文件。** 1. 在 Console 里**停止服务**,避免恢复过程中继续写入; 2. 把当前的 `<数据目录>\event_projs\<项目名>\event.db` 改名留档,确认恢复失败还能退回; 3. 选一份目标备份复制为 `event.db`(覆盖原文件); 4. 同时删除同目录下的 `event.db-wal` 与 `event.db-shm`; 5. 重新启动服务,核对积分榜与最近一次发布记录。 > `<数据目录>\system.db` 保存账号与会话信息,一般不需要回退;如需一并回退,替换它的方法与 `event.db` 相同(同时删除 `system.db-wal` / `system.db-shm`)。 > > 现场若还有备机,优先切换备机比原地恢复更快。 ### 8.4 整机迁移 用户数据目录(`%LOCALAPPDATA%\ScoreboardConsole\`,里面是数据 + 配置 + 规则)与**安装目录**(`Scoreboard_Console.exe`、`scoreboard.exe` 等)一起拷到另一台同版本 Windows 机器: - 用户数据目录:整个 `%LOCALAPPDATA%\ScoreboardConsole\` 拷过去即可(含 `data\`、`console\config.json`、`config.yaml`、`rules\`;同一用户名下路径一致,最省事)——这样配置、规则和赛事数据原样接管,无需重新配置; - 安装目录:按同样路径放好,或直接用新机器上的安装包装一遍(安装目录里那三份只是模板,拷不拷都行); - 启动 Console 即可;若数据目录放到了别处,记得同步改 `console\config.json` 的 `server.dataDir`(在用户数据目录里那份)。 ### 8.5 赛事项目包导出 / 导入 服务端**一次只打开一个赛事项目**,页面上看到的数据全部来自标记为「当前」的那一项,其余项目只是躺在数据目录里的数据。导入赛事包**不会自动切换**当前赛事,导入后要再点一次「切换为当前赛事」才生效(见下面的步骤)。 | 操作 | 位置 | 说明 | | --- | --- | --- | | 导出 | 「导出 → 赛事项目包 → 导出 zip」 | 包内含 `meta.yaml`、`event.db`、`uploads/`、`rules.yaml`;整包拷走即可在另一台机器复现 | | 导入 | 「导出 → 赛事项目包 → 选择 .zip → 导入」 | **仅管理员**可操作;可填「重命名」,勾选「覆盖同名项目」会先二次确认 | | 切换 | 「导出 → 赛事项目包 → 赛事项目列表 → 切换为当前赛事」 | 只有管理员可操作;会让服务端重启一次 | 包内含四类内容: - `meta.yaml`:包格式标识、项目名、赛事名称/副标题/主办方、导出时间,以及每个文件的 SHA-256 清单(导入时逐个校验,对不上就拒绝); - `event.db`:赛事库快照(用 `VACUUM INTO` 生成,导出时服务端可以继续写入),成绩、基础数据、积分规则都在里面; - `uploads/`:**全部上传资源**——赛事会标、大屏会标、大屏背景图、各参赛单位 logo,以及**上传的字体**(放在 `uploads/fonts/` 下,见使用手册 14.3)。导入时会连文件一起落到新项目目录的 `uploads/`,切换过去后图片直接显示、字体直接可用,不用重新上传; - `rules.yaml`:当前规则包的一份 YAML 副本,便于单独复用到别的赛事;规则本身存在赛事库里,导入后即刻生效,这份只是副本。 导出界面同一页的「结果导出」标签可以把最终结果导成 xlsx:**总表**(总积分榜 + 单位奖牌榜 + 个人积分奖牌榜,一个工作簿)、**全部**(总表 + 全部分项)、**全部(单页)**(所有成绩平铺在一张表)、**分项成绩表**(按小项)、**单位积分明细**(按单位)各自可单独导出打印,另有可筛选(小项 / 组别 / 单位 / 批次)的**报名名单**;「积分榜」页三个榜单的卡片头还各有明细导出(**导出总明细** / **导出奖牌明细** / **导出个人明细**,见使用手册 8.3)。基础数据(单位 / 大项 / 小项 / 组别)另有单独的基础数据包可导出 / 导入,见 8.7。 导入与切换: 1. 导入把包解到 `<数据目录>\event_projs\<项目名>\`(项目名可用「重命名」覆盖)。**导入只是把数据落盘,不会切换当前赛事**,所以导入完成后页面会提示「已导入为 xxx,但它还不是当前赛事」;导入期间请勿停止服务端进程; 2. 在导入结果框里点「立即切换并重启服务端」,或稍后在**赛事项目列表**里点该项目的「切换为当前赛事」(列表会显示每个项目的项目名、更新时间、占用大小,当前打开的那项带「当前」徽标,缺 `event.db` 的项目不可切换); 3. 切换 = 服务端把项目名写进 `<数据目录>\active_project.txt` 后优雅退出,由 Console 自动拉起并打开新赛事(页面会显示「正在重启服务端…」,恢复后自动刷新,最多等 60 秒)。**所以切换需要 Console 在运行**;如果是不经 Console、直接双击 `scoreboard.exe` 跑的,切换后要自己把服务端重新启动一次; 4. 之后即使不带任何启动参数,服务端也仍然打开这个赛事(记忆文件生效);换了别的赛事再切一次即可。 - **导出前先看一眼赛事名称**:名称里含 `/ \ : * ? " < > |`、超过 64 个字、或以点 / 空格结尾时**导不出来**——导出包的目录名取自赛事名称。现在这种非法名会返回一句明确的提示(不是「服务端异常」),按提示把「赛事信息 → 赛事名称」改掉再导即可; - **切换后怎么确认真的切过去了**:看服务端日志里有没有 `msg=当前赛事项目 project=<项目名> source="记忆文件 active_project.txt"` 这一行。看到它才算切过去,再去核对榜单、成绩、单位 logo、大屏背景图与字体(图片有长缓存,必要时在大屏窗口按一次强制刷新); - 不能覆盖**正在运行**的赛事项目:对当前赛事的同名包,导入会被直接拒绝(先切到别的赛事再导); - 勾选「覆盖同名项目」时,旧目录会先改名保留、新项目落盘成功后才删除它; - 参赛单位 logo、会标、背景图与上传的字体都在 `uploads\` 里,所以**备份 / 迁移时要连 `event_projs\<项目名>\uploads\` 一起拷**(见 8.6); - **赛事包上传上限是 512MB**(名单 CSV 与基础数据包那一档是 32MB,这里刻意放宽):赛事包是唯一带二进制资源的包——上传字体单个就允许 20MB(且可以传多个),再加上背景图、会标与各单位 logo,「两个字体 + 一张背景图」的正常包很容易超过 32MB,按 32MB 卡会让正常包导不进来。真的超过 512MB 时导入会被拒绝并提示「解析上传失败」,先到「系统设置 → 赛事信息」删掉用不到的图片 / 字体,再重新导出一份包; - **不要用 `-project` 启动参数来切赛事**:显式传 `-project` 时,服务端会顺手把它写回「上次打开的赛事」记忆文件,于是以后每次启动都优先打开这个项目——在页面里切完、重启后又被拉回去。只有在「刻意把一台机器固定成只开某一个项目」时才这么用:在 Console 的用户配置 `config.json` 里给 `server.args` 加 `"-project","<项目名>"` 后重启 Console,或直接 `scoreboard.exe -config "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" -project <项目名> -data "%LOCALAPPDATA%\ScoreboardConsole\data"` 启动(见 6.2)。 ### 8.6 手工备份 / 迁移整个数据目录 快照备份之外,数据库文件、上传的图片与字体、备份都可以整体拷走: 1. 在 Console 里**停止服务**(托盘 → 停止服务),避免拷贝过程中继续写入; 2. 把整个数据目录 `%LOCALAPPDATA%\ScoreboardConsole\data` 复制到 U 盘或其他盘(里面含 `system.db`、`event_projs\`(数据库 + `uploads\` 图片与字体)、`backups\`); 3. 恢复时把整个目录拷回去,然后: - 拷回**原路径**:直接启动 Console 即可; - 拷到**别的路径**:先建好目标目录并放入数据,再在用户配置 `console\config.json` 里把 `server.dataDir` 指向它,重启 Console; 4. 恢复完成后在管理端核对赛事、账号、上传图片与字体是否齐全。 > 只拷 `backups\` 里的 `.db` 快照也能救回成绩数据,但上传的背景图/会标/单位 logo **与自定义字体**在 `event_projs\<项目名>\uploads\` 里(字体在 `uploads\fonts\`),不拷这个目录会丢这些文件,大屏上表现为图片不显示、字体回落成默认字体。`deploy\backup.bat` 会自动把每个项目的 `uploads\` 打成 `*-uploads-*.zip` 放在 `backups\` 里(见 8.2),可以用它替代手工拷贝——但那个 zip **不参与 20 份滚动清理**,解压后请把项目目录里的 `uploads\` 覆盖回去,并定期手工删旧的 zip。 ### 8.7 基础数据包导出 / 导入(单位 / 大项 / 小项 / 组别) 赛前批量维护基础数据,或换一台机器时把基础数据整体搬过去,用「导出 → 基础数据」页签: | 操作 | 位置 | 说明 | | --- | --- | --- | | 导出 | 「导出 → 基础数据 → 导出 xlsx」 | 一个工作簿四张工作表:单位 / 大项 / 小项 / 组别(组别表带所属小项编码) | | 导入 | 「导出 → 基础数据 → 选择 .xlsx → 预览差异 → 确认写入」 | **仅管理员**;只认 `.xlsx`(四张表装不进一个 CSV) | - **按编码匹配,存在就更新、不存在就新建;绝不删除**:文件里没有的行不会被删,也不会被停用; - **两步走**:先「预览差异」(新增 / 更新 / 无变化 / 错误 + 逐行变化,这一步不写库),确认后再点「**确认写入**」;**有错的行会跳过,合法的行照常写入**,行级错误带**真实 Excel 行号**(如 `单位 第 3 行:单位编码必填`); - **列不在表里 = 不动该字段**(**布尔列也一样**:缺整列不等于「取默认值」,否则把「启用」列删掉再导回,会把所有停用的单位/大项静默改成启用);**列在表里但格子空**:文本列与年龄上下限清空,权重按 1、布尔列按「是」,**排序留空保持原值**(**四张表口径一致**,单位 / 大项留空同样不会被改写); - **枚举列例外:列在表里但格子空 = 不改动**(不是「取默认值」)。小项的类型 / 结果模式 / 结果类型 / 比较 / 状态 / 平均口径,以及组别的性别,留空都沿用原值;只有**新建的行**才用默认值(新建组别时性别留空 = 不限)。这是为了防手滑——误清一格不会把已发布小项打回「待录入」、也不会把「团体」改成「个人」; - 表头行在第 4 行(系统导出件,上面是标题与副标题)或第 1 行(手工整理的表)都能识别;枚举列要填英文规范值(如 `individual` / `time` / `published`),布尔列填「是 / 否」; - 被「彻底删除」的单位不在导出件里,导入也**不会**把它翻出来:命中它的编码那一行会**整行跳过**(不更新它的任何字段),并在导入结果里报 `单位 第 N 行:单位 XX 已被彻底删除,本行跳过(导入不会把隐藏的单位翻出来);要放回列表请先恢复该单位`,其余合法的行照常写入。要让这个单位回来,先到「基础数据 → 参赛单位」用「恢复」把它放回列表(见使用手册 10.1); - **这个包只含基础数据**,不含成绩、报名与规则;整场赛事的搬迁仍用赛事项目包(见 8.5)。赛前导出一份留底,现场改乱了可以导回对照。 --- ## 9. 故障排查 ### 9.1 端口被占用,服务端起不来 1. 查看占用进程(Windows 命令提示符): ```bat netstat -ano | findstr :19540 ``` 2. 根据最后一列的 PID 结束进程: ```bat taskkill /PID /F ``` 3. 或按第 6.3 节把端口改到其他值(两处同时改)。 > 同一台机器不要启动两个 Console,否则会互相抢占端口和大屏。 ### 9.2 大屏不亮 / 只有桌面 按顺序检查: 1. HDMI 线是否插好,大屏电源是否打开; 2. Windows 显示设置是否为**扩展**模式; 3. Console「大屏窗口」卡片是否显示已打开;点「重新排布」; 4. 在服务管理窗口点「打开服务端管理面板」,到「大屏控制 → 显示器」里看该块屏的状态,点它的「创建全屏窗口」重新开出来(副屏全屏窗口被误关时最有效,见 7.5); 5. 确认 `display.selection` 选屏策略是否漏掉了目标屏; 6. 服务端未就绪时大屏最多等 20 秒才打开,稍等即可; 7. 查看 Console 运行日志里的「大屏排布完成」「大屏加载」记录; 8. 单屏窗口化时按 **F11** 可切全屏。 > 「大屏控制」页的显示器卡片只在**从服务管理窗口点「打开服务端管理面板」打开的那个窗口**里可用;用浏览器直接打开管理面板看不到这组按钮(页面会给出说明)。 ### 9.3 管理面板打不开 1. 确认 Console 里服务端状态为「运行中」; 2. 本机访问 `http://127.0.0.1:19540/admin`,其他设备访问 `http://<主机IP>:19540/admin`; 3. 确认用户配置 `config.yaml` 与 `console\config.json` 的端口一致(都在 `%LOCALAPPDATA%\ScoreboardConsole\` 下,见 3.5); 4. 检查 Windows 防火墙是否拦截了该端口; 5. 用托盘菜单的「打开服务端管理面板」直接打开。 ### 9.4 服务反复重启 - 打开 `logs\console.log`,搜索 `[server!]`,这是服务端错误输出; - 检查数据目录 `%LOCALAPPDATA%\ScoreboardConsole\data` 是否可写、磁盘是否已满; - 检查用户配置 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` 的语法与端口是否合法。 ### 9.5 页面显示「示例数据」 - 表示前端连不上后端或接口未就绪,此时录入、发布等操作**不会写入真实赛事**; - 确认服务端状态为「运行中」、实时连接显示「实时在线」; - 恢复连接后刷新页面即可切换到真实数据。 ### 9.6 大屏时间不对 - 系统没有外网 NTP,**以主机时间为准**; - 大屏顶部时钟精确到秒、每秒刷新,改完 Windows 系统时间与时区后立刻跟随; - 建议在开幕前校准主机时间。 ### 9.7 安卓 TV 备选方案打不开 1. 确认电视与主机在同一局域网,主机 IP 为固定 IP; 2. 电视浏览器地址填 `http://<主机IP>:19540/display`; 3. 确认用户配置 `%LOCALAPPDATA%\ScoreboardConsole\config.yaml` 的 `server.host` 为 `0.0.0.0`; 4. 部分电视浏览器对 CSS 动画、字体支持有限,显示效果以实际设备为准。 ### 9.8 忘记管理员密码 见第 5.5 节,使用 `scoreboard.exe passwd -config "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" -data "%LOCALAPPDATA%\ScoreboardConsole\data"` 重置。 ### 9.9 升级后数据/配置不见了 / 日志里有迁移警告 覆盖安装时安装程序会先跑旧卸载器删掉安装目录,所以本版本起数据放在 `%LOCALAPPDATA%\ScoreboardConsole\data`、配置与规则放在 `%LOCALAPPDATA%\ScoreboardConsole\` 下的另外三处(见 3.4、3.5)。首次启动后先看 Console「运行日志」或 `logs\console.log`: | 日志 | 含义 | | --- | --- | | `数据目录:<路径>` | 本次实际使用的数据目录,先确认它是不是你期望那个 | | `用户数据目录:<路径>` | 本次实际使用的用户数据目录(配置与规则都在它下面) | | `已把 复制到 ` | 首次运行把安装目录里的配置/规则复制到用户目录,属正常 | | `已把旧数据从 X 迁移到 Y` | 旧数据已整体搬过去,属正常 | | `警告:...都有数据...` | 两个目录都有数据,Console 没有动任何一边;请人工把要保留的那份拷到 `数据目录:` 指向的位置 | | `警告:迁移旧数据失败...` | 跨盘或权限问题;旧数据仍在日志里写的 `X`,手工拷过去再重启即可 | | `警告:准备 失败...` | 配置/规则那一项回落到安装目录里那份,改动仍会在覆盖安装时丢;按日志里的路径手工复制到用户目录再重启 | | `警告:数据目录仍在安装目录内` | `server.dataDir` 配置成了安装目录里的路径,覆盖安装会丢数据,请改到安装目录之外 | 若安装目录里的 `data\` 已被删掉,优先用 `backups\` 里最新的 `.db` 快照按第 8.3 节恢复;配置与规则从老版本升上来时只能由 Console 复制新版模板过去(原因见 3.5 的说明),升级后确认一遍端口等设置即可。 --- ## 10. 服务端日志与拦截复盘 服务端会把日志写到**数据目录**下的 `logs\`(默认 `%LOCALAPPDATA%\ScoreboardConsole\data\logs\`),按用途分成**两类**目录,各自按天 + 按大小轮转: ``` <数据目录>\logs\ ├── operations\ # 放行类:放行的写操作 + 常规运行日志(启动、迁移…) └── interceptions\ # 拦截类:我们主动拦截的报错 + 服务端异常 ``` > 这与 Console 自己的 `logs\console.log`(在安装目录里,只装 Console 与服务端的控制台输出)不是一回事:要复盘「某次操作为什么被拒绝」,看的是数据目录 `logs\interceptions\`。 ### 10.1 两类日志各记什么 | 目录 | 内容 | 采集级别 | | --- | --- | --- | | `operations\` | **放行类**:放行的写操作(`操作已放行`,含 action / 实体 / 操作人 / requestId)+ 常规运行日志(启动、数据库迁移…) | 按 `log.level` 过滤(默认 `info`) | | `interceptions\` | **拦截类**:我们主动拦截的报错(WARN,含中文原因 + 拦截点 `文件:行 函数名` + method/path + 操作人/角色 + requestId)+ 拦截上下文(DEBUG,含 detail 与来源 IP)+ **服务端异常(ERROR)** | **固定按 `debug` 采集,不受 `log.level` 影响** | 拦截类固定采集是刻意的:默认级别是 `info`,跟着过滤就看不到现场最需要的那份详细拦截日志。中间件层的拦截(未登录 401 / 无权限 403 / CSRF 校验失败 / 未改初始密码 / 操作过于频繁 429)同样进这里,一样带中文原因与拦截点。 ### 10.2 文件命名与轮转 - 文件名按天:`operations-YYYYMMDD.log`、`interceptions-YYYYMMDD.log`;同一天写满一个文件后接着写 `.1`、`.2`…,例如 `interceptions-20260911.1.log`; - **保留份数含当前文件**,超出后自动删最旧的(每开一个新文件都会清理一遍); - 单文件大小上限 `log.maxSizeMB`(默认 20 MB),每类保留份数 `log.keepFiles`(默认 10)。两类合计最多约 `2 × keepFiles × maxSizeMB`,默认约 400 MB,正常跑一整天不会把盘写满; - 万一日志目录不可写(权限、盘满),服务端只把告警打一次,之后降级为只写控制台,**不影响业务请求**。 ### 10.3 配置(`config.yaml` 的 `log:` 段) | 项 | 默认值 | 说明 | | --- | --- | --- | | `log.level` | `info` | 控制台与 `operations\` 的采集级别:`debug` / `info` / `warn` / `error`;**拦截类不受它影响** | | `log.dir` | 空 | 日志根目录,其下分 `operations\` 与 `interceptions\`;留空 = `<数据目录>\logs` | | `log.maxSizeMB` | `20` | 单个日志文件大小上限(MB);`<=0` 用内置默认 | | `log.keepFiles` | `10` | 每类保留份数(含当前文件);`<=0` 用内置默认 | `dir` / `maxSizeMB` / `keepFiles` 三个键都有默认值,不配也能跑;仓库根的 `config.yaml` 里已带注释,照抄即可。改完要重启 Console(配置只在启动时读一次,见 3.5)。 ### 10.4 现场复盘:某次操作被拒了怎么看 1. 打开 `<数据目录>\logs\interceptions\`,找当天的 `interceptions-YYYYMMDD.log`(有续写文件时按 `.1`、`.2` 往后看); 2. 搜 `请求被拦截`(WARN)那一行,它带: - `reason`:中文原因(与页面上看到的提示同源); - `origin`:**拦截点**,形如 `internal/httpapi/repo_events.go:231 normalizeAvgMode`——直接告诉你是哪个函数的哪一行拦下的; - `code` / `method` / `path`:错误码与请求地址; - `actor`(`用户名#id`,未登录记 `guest`)/ `role` / `requestId`; 3. 想定位得更准:在浏览器开发者工具的 **Network** 面板点开那条失败请求,复制响应头 `X-Request-Id`,在日志文件里搜这个值;同值的 DEBUG 行「拦截上下文」会再补上 `detail` 与来源 IP; 4. 想对照同一时间有没有成功的操作,去 `operations\` 里搜同一个 `requestId`(放行日志带 action / 实体 / 操作人)。 > 导入类操作的行级报错也留痕:`导入行被拦截`(DEBUG)会记第几行、原因与拦截点。日志不记请求体,证件号这类个人信息不会因为日志多一处泄露面;`detail` 字段与页面响应同源。 > 服务端自身异常(5xx)也在 `interceptions\` 里,是 `请求失败`(ERROR)并同样带 `origin`。 --- ## 11. 上线检查清单 - [ ] 服务端开机自启、崩溃自动拉起已验证 - [ ] 「运行日志」里的 `数据目录:` 指向 `%LOCALAPPDATA%\ScoreboardConsole\data`(不是安装目录),且没有迁移警告 - [ ] 「运行日志」里的 `用户数据目录:` 指向 `%LOCALAPPDATA%\ScoreboardConsole`,且没有「准备 … 失败」警告(配置与规则也在用户目录里,见 3.5) - [ ] 数据目录的 `backups\` 可写,已生成至少一份备份 - [ ] **服务端日志目录可写**:数据目录下已出现 `logs\operations\` 与 `logs\interceptions\` 两个目录(见第 10 节);知道操作被拒时去 `interceptions\` 搜原因与拦截点 - [ ] 已把数据目录整个拷一份到 U 盘(含 `event_projs\<项目名>\uploads\` 里的图片与字体) - [ ] **当前赛事正确**:「导出 → 赛事项目包 → 赛事项目列表」里带「当前」徽标的是本次要用的那个项目(刚导入的包要切换过去才算生效,见 8.5) - [ ] 大屏 HDMI 输出定位正确、分辨率正确、字体正常 - [ ] **显示器菜单可用**:在服务管理窗口打开的「大屏控制」页里能看到每块屏并「创建全屏窗口」,副屏被误关时能重开(见 7.5) - [ ] **单位 logo 已上传且在榜上显示**(总积分榜 / 单位奖牌榜 / 分项成绩表 / 个人积分榜,见 7.7) - [ ] **大屏字体已选好并核对**:「系统设置 → 赛事信息 → 字体」里选了标题字体与正文字体,大屏上显示正常;上传的字体在数据目录 `event_projs\<项目名>\uploads\fonts\` 里,导出赛事项目包会一并带走(见 8.5) - [ ] **公告可用**:推送一条测试公告,全屏到时后底栏仍能看到正文,点「清除公告」后底栏回到「暂无公告」(见 7.6) - [ ] 管理端各角色账号可登录,权限正确 - [ ] 主机 IP 固定,管理端 / 大屏地址已打印或做成二维码贴在操作台 - [ ] 防熄屏、屏保、休眠、系统通知已关闭 - [ ] 主机时间已校准 - [ ] 磁盘剩余空间充足 - [ ] 安卓 TV / 浏览器备选通道实测可打开页面