Files
Scoreboard/console/main/config.js
T
AskaEth 7f9701c465 fix: 审查修复波次(P0x4 + P1x30+)
- domain/rules: 计分与排名解耦、破纪录按组别、tieBreak 生效、调整分不丢、移除 aggregate 假能力
- realtime: 新建 internal/dto,快照三项契约对齐,/ws 只认会话身份
- httpapi: 流水幂等、standings 整表载荷、广播接线、强制改密、XFF 防伪造、records 写入、审计、赛事包内容、发布前备份、result.points、实例列表
- web: FormData 上传、YAML 解析、快照归一化、大屏解析层、bootstrap 降级、访客判定、主题切换
- console: 优雅关闭握手、中文化、免验证改管理员账号、选屏与日志轮转修复
- docs: 用户文档三份 + README
- 主代理: 破纪录加分接线(levels 未加载导致恒 0)
2026-09-09 21:06:40 +08:00

106 lines
3.4 KiB
JavaScript
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.
'use strict'
const fs = require('fs')
const path = require('path')
// 默认值写死在代码里:config.json 只覆盖要改的项。
// 这样升级新增字段时老配置依然能用,现场也不容易配出半成品。
const DEFAULTS = {
server: {
exe: 'scoreboard.exe',
args: ['-config', 'config.yaml'],
host: '127.0.0.1',
port: 19540,
autoStart: true,
healthIntervalMs: 5000,
restartDelayMs: 3000,
// 0 表示无限重启;服务端是现场唯一进程,崩了必须拉起来
maxRestarts: 0,
// 优雅关闭握手:服务端需实现该回环接口,未实现时回退到进程信号
shutdownPath: '/api/shutdown',
shutdownTimeoutMs: 12000,
// 连续健康这么久后清零崩溃计数,避免历史崩溃耗尽 maxRestarts
healthyResetMs: 60000
},
display: {
// all-secondary | all | specified
selection: 'all-secondary',
displays: [],
// 只能写大屏实际支持的页名(见 web/src/display/types.ts 的 PAGE_ALIASES)
pages: ['standings', 'medals', 'individual', 'results'],
pageParam: 'page',
kiosk: true,
ignoreMouse: true,
reloadEveryHours: 24,
// 服务端迟迟没就绪时的兜底:超时后也把大屏开出来,避免现场一片黑
waitForServerMs: 20000,
backgroundColor: '#0b0f1a'
},
startup: {
// 打包后随系统启动(doc 10 §4.3);开发态不注册
openAtLogin: true
},
admin: {
width: 1040,
height: 760
},
log: {
maxLines: 2000,
file: 'logs/console.log',
maxFileBytes: 4 * 1024 * 1024
}
}
function isPlainObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}
function merge(base, override) {
const out = isPlainObject(base) ? { ...base } : {}
if (!isPlainObject(override)) return out
for (const [key, value] of Object.entries(override)) {
if (value === undefined) continue
out[key] = isPlainObject(value) && isPlainObject(base[key]) ? merge(base[key], value) : value
}
return out
}
function readJson(file) {
try {
const raw = fs.readFileSync(file, 'utf8')
return { values: JSON.parse(raw), warning: null }
} catch (err) {
if (err.code === 'ENOENT') return { values: {}, warning: `配置文件不存在,使用内置默认值:${file}` }
return { values: {}, warning: `配置文件无法解析,使用内置默认值:${file}(${err.message})` }
}
}
function loadConfig(configPath) {
const { values, warning } = readJson(configPath)
return {
path: configPath,
values: merge(DEFAULTS, values),
warning
}
}
// 相对路径的 exe 需要按「部署包根目录」理解,而部署包根目录在不同形态下不同:
// 开发时是 console/ 的上级,打包后是 exe 所在目录。逐个候选位置探测,避免写死。
function resolveExecutable(exe, configPath) {
if (!exe) return { path: null, candidates: [] }
if (path.isAbsolute(exe)) return { path: exe, candidates: [exe] }
const configDir = path.dirname(configPath)
const candidates = [
configDir,
process.cwd(),
path.resolve(configDir, '..'),
path.resolve(configDir, '..', 'dist')
].map((dir) => path.resolve(dir, exe))
const found = candidates.find((candidate) => fs.existsSync(candidate))
return { path: found || candidates[0], candidates: [...new Set(candidates)] }
}
module.exports = { DEFAULTS, loadConfig, merge, resolveExecutable }