- httpapi:新增 POST /api/shutdown(admin 会话或 X-Shutdown-Token 常量时间比对) 与 GET /api/rules/switches(recorder 可读,只回布尔开关);settings/standings 的 重复 DTO 收敛到 internal/dto;standings.showIndividualMedals 贯通到设置读写与前端 - export:抽出 export.Service,新增自动备份定时任务(backup.intervalMinutes/keep), 自动、手动、发布前三类备份共用同一把互斥锁与滚动清理 - realtime:/ws 去掉 InsecureSkipVerify,改用 OriginPatterns(同源与无 Origin 放行, 跨站需命中 server.allowedOrigins) - console:停止服务改走 POST /api/shutdown 优雅关闭(5 秒宽限后回退强杀), 服务管理器新增「已连接的大屏实例」卡片 - deploy:build.bat 改用 for /f 读内部署名——set /p 在 chcp 65001 下返回空, 导致最近这次构建的 exe 里根本没注入署名 - docs:备份策略、关闭超时、Origin 配置的说明与实现对齐
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
// Package config 负责加载服务端启动配置。
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Config 是 config.yaml 的映射。
|
|
type Config struct {
|
|
Server Server `yaml:"server"`
|
|
DataDir string `yaml:"dataDir"`
|
|
Backup Backup `yaml:"backup"`
|
|
Log Log `yaml:"log"`
|
|
}
|
|
|
|
// Backup 是自动备份配置。
|
|
type Backup struct {
|
|
// IntervalMinutes 是自动备份间隔(分钟);<=0 表示关闭自动备份。
|
|
IntervalMinutes int `yaml:"intervalMinutes"`
|
|
// Keep 是每个库滚动保留的份数;<=0 时用 export.DefaultBackupKeep。
|
|
Keep int `yaml:"keep"`
|
|
}
|
|
|
|
// Server 是 HTTP 监听配置。
|
|
type Server struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
// AllowedOrigins 是 WebSocket 跨站 Origin 白名单;为空时只放行同源与不带 Origin 的客户端。
|
|
AllowedOrigins []string `yaml:"allowedOrigins"`
|
|
}
|
|
|
|
// Log 是日志配置。
|
|
type Log struct {
|
|
Level string `yaml:"level"`
|
|
}
|
|
|
|
// Default 返回一份可直接使用的默认配置。
|
|
func Default() *Config {
|
|
return &Config{
|
|
Server: Server{Host: "0.0.0.0", Port: 19540},
|
|
DataDir: "data",
|
|
Backup: Backup{IntervalMinutes: 5, Keep: 20},
|
|
Log: Log{Level: "info"},
|
|
}
|
|
}
|
|
|
|
// Load 读取配置文件;文件不存在时返回默认配置。
|
|
func Load(path string) (*Config, error) {
|
|
cfg := Default()
|
|
|
|
raw, err := os.ReadFile(path)
|
|
switch {
|
|
case err == nil:
|
|
if err := yaml.Unmarshal(raw, cfg); err != nil {
|
|
return nil, fmt.Errorf("解析配置文件 %s: %w", path, err)
|
|
}
|
|
case os.IsNotExist(err):
|
|
// 没有配置文件时使用默认值
|
|
default:
|
|
return nil, fmt.Errorf("读取配置文件 %s: %w", path, err)
|
|
}
|
|
|
|
if err := cfg.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (c *Config) validate() error {
|
|
if c.Server.Port <= 0 || c.Server.Port > 65535 {
|
|
return fmt.Errorf("server.port 非法: %d", c.Server.Port)
|
|
}
|
|
if strings.TrimSpace(c.DataDir) == "" {
|
|
return fmt.Errorf("dataDir 不能为空")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Addr 返回 http.Server 使用的监听地址。
|
|
func (s Server) Addr() string {
|
|
return fmt.Sprintf("%s:%d", s.Host, s.Port)
|
|
}
|
|
|
|
// SlogLevel 把配置里的日志级别映射为 slog 级别。
|
|
func (l Log) SlogLevel() slog.Level {
|
|
switch strings.ToLower(strings.TrimSpace(l.Level)) {
|
|
case "debug":
|
|
return slog.LevelDebug
|
|
case "warn", "warning":
|
|
return slog.LevelWarn
|
|
case "error":
|
|
return slog.LevelError
|
|
default:
|
|
return slog.LevelInfo
|
|
}
|
|
}
|