133 lines
4.1 KiB
Go
133 lines
4.1 KiB
Go
// Package dto 存放实时层与 REST 层共用的线上数据结构与纯转换函数。
|
||
//
|
||
// 放在这里而不是 httpapi:realtime 与 httpapi 是同层出口,互相 import 会造成分层倒置;
|
||
// 本包只依赖 domain 与标准库,不做任何 IO。
|
||
package dto
|
||
|
||
import (
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// EventSettings 是 GET/PUT /api/settings 与 snapshot.settings 的线上结构,
|
||
// 与 web/src/shared/types.ts 的 EventSettings 一一对应。
|
||
type EventSettings struct {
|
||
Event EventSettingsEvent `json:"event"`
|
||
Display EventSettingsDisplay `json:"display"`
|
||
Standings EventSettingsStandings `json:"standings"`
|
||
DefaultProfileID *int64 `json:"defaultProfileId"`
|
||
RequireApproval bool `json:"requireApproval"`
|
||
}
|
||
|
||
// EventSettingsEvent 是赛事基本信息。
|
||
type EventSettingsEvent struct {
|
||
Name string `json:"name"`
|
||
Subtitle *string `json:"subtitle"`
|
||
Host *string `json:"host"`
|
||
LogoPath *string `json:"logoPath"`
|
||
PrimaryColor *string `json:"primaryColor"`
|
||
}
|
||
|
||
// EventSettingsDisplay 是大屏展示配置。
|
||
type EventSettingsDisplay struct {
|
||
Background *string `json:"background"`
|
||
Logo *string `json:"logo"`
|
||
Title string `json:"title"`
|
||
Subtitle *string `json:"subtitle"`
|
||
PrimaryColor *string `json:"primaryColor"`
|
||
RotateSecond int `json:"rotateSeconds"`
|
||
}
|
||
|
||
// EventSettingsStandings 是榜单口径配置。
|
||
type EventSettingsStandings struct {
|
||
IndividualToTeamWeight float64 `json:"individualToTeamWeight"`
|
||
RankingMode string `json:"rankingMode"`
|
||
ShowIndividualBoard bool `json:"showIndividualBoard"`
|
||
}
|
||
|
||
// ParseEventSettings 把 settings 表的点号键聚合成嵌套对象。
|
||
//
|
||
// 默认值必须与 REST 的 getSettings 逐条一致:大屏首包走 snapshot、管理端走 /api/settings,
|
||
// 两边口径不同会让同一块屏刷新前后显示不一样的标题。
|
||
// defaultProfileId 需要查 scoring_profiles 反查 code,属于 IO,由调用方补填。
|
||
func ParseEventSettings(values map[string]string) EventSettings {
|
||
var out EventSettings
|
||
|
||
out.Event.Name = orDefault(values["event.name"], "未命名赛事")
|
||
out.Event.Subtitle = nilIfEmpty(values["event.subtitle"])
|
||
out.Event.Host = nilIfEmpty(values["event.organizer"])
|
||
out.Event.LogoPath = nilIfEmpty(values["event.logo_path"])
|
||
out.Event.PrimaryColor = nilIfEmpty(values["display.theme_color"])
|
||
|
||
out.Display.Background = nilIfEmpty(values["display.background"])
|
||
out.Display.Logo = nilIfEmpty(values["display.logo"])
|
||
if out.Display.Logo == nil {
|
||
out.Display.Logo = out.Event.LogoPath
|
||
}
|
||
out.Display.Title = orDefault(values["display.title"], out.Event.Name)
|
||
out.Display.Subtitle = nilIfEmpty(values["display.subtitle"])
|
||
if out.Display.Subtitle == nil {
|
||
out.Display.Subtitle = out.Event.Subtitle
|
||
}
|
||
out.Display.PrimaryColor = out.Event.PrimaryColor
|
||
out.Display.RotateSecond = atoiDefault(values["display.rotate_seconds"], 15)
|
||
|
||
out.Standings.IndividualToTeamWeight = atofDefault(values["standings.individual_to_team_weight"], 1)
|
||
out.Standings.RankingMode = orDefault(values["standings.mode"], "points")
|
||
out.Standings.ShowIndividualBoard = boolDefault(
|
||
firstNonEmpty(values["standings.show_individual_board"], values["standings.show_individual_medals"]), true)
|
||
|
||
out.RequireApproval = boolDefault(values["results.review_required"], false)
|
||
return out
|
||
}
|
||
|
||
func nilIfEmpty(s string) *string {
|
||
if s == "" {
|
||
return nil
|
||
}
|
||
return &s
|
||
}
|
||
|
||
func orDefault(v, def string) string {
|
||
if strings.TrimSpace(v) == "" {
|
||
return def
|
||
}
|
||
return v
|
||
}
|
||
|
||
func firstNonEmpty(values ...string) string {
|
||
for _, v := range values {
|
||
if strings.TrimSpace(v) != "" {
|
||
return v
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func atoiDefault(v string, def int) int {
|
||
n, err := strconv.Atoi(strings.TrimSpace(v))
|
||
if err != nil {
|
||
return def
|
||
}
|
||
return n
|
||
}
|
||
|
||
func atofDefault(v string, def float64) float64 {
|
||
n, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
|
||
if err != nil {
|
||
return def
|
||
}
|
||
return n
|
||
}
|
||
|
||
func boolDefault(v string, def bool) bool {
|
||
switch strings.ToLower(strings.TrimSpace(v)) {
|
||
case "true", "1", "yes":
|
||
return true
|
||
case "false", "0", "no":
|
||
return false
|
||
default:
|
||
return def
|
||
}
|
||
}
|