feat: M3-M7 全模块落地 + 集成接线
- M3 认证/权限/审计/限流 + 全部 REST 接口 - M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制) - M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配) - M6 WebSocket 实时同步中心 - M7 xlsx 导出 + 赛事包 zip + 备份 - 规则包 YAML 导入导出(差异预览) - 字体 vendor 子集化(4 套 x2 字重) - 集成:realtime/export/rules 接入 main+httpapi;backup 子命令 - 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
This commit is contained in:
1 parent
d0e3b93e76
commit
bc8b1ee6c0
145 files changed
+25159
-321
No files matched your search
@@ -0,0 +1,155 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.yeij.top/AskaEth/Scoreboard/server/internal/domain"
|
||||
)
|
||||
|
||||
// 赛事库 settings 表里的键名;与 migrations/event/0011_seed.sql 保持一致。
|
||||
const (
|
||||
settingIndividualToTeamWeight = "standings.individual_to_team_weight"
|
||||
settingTieBreak = "standings.tie_break"
|
||||
settingMedalCountsTeamEvents = "standings.medal_counts_team_events"
|
||||
settingShowIndividualBoard = "standings.show_individual_board"
|
||||
settingShowIndividualMedals = "standings.show_individual_medals"
|
||||
settingRecordBonusEnabled = "record_bonus.enabled"
|
||||
settingRecordBonusMode = "record_bonus.mode"
|
||||
settingRecordBonusLevels = "record_bonus.levels"
|
||||
settingManualEnabled = "manual_adjustment.enabled"
|
||||
settingManualReasonRequired = "manual_adjustment.reason_required"
|
||||
)
|
||||
|
||||
// settingsFromPack 把规则包里能落到 settings 表的部分拍平成键值对。
|
||||
// 破纪录档位库里没有表,用 JSON 存在 record_bonus.levels 里,避免导入时丢配置。
|
||||
func settingsFromPack(pack *RulePack) map[string]string {
|
||||
levels, err := json.Marshal(pack.RecordBonus.Levels)
|
||||
if err != nil {
|
||||
levels = []byte("[]")
|
||||
}
|
||||
return map[string]string{
|
||||
settingIndividualToTeamWeight: formatFloat(pack.Standings.IndividualToTeamWeight),
|
||||
settingTieBreak: strings.Join(pack.Standings.TieBreak, ","),
|
||||
settingMedalCountsTeamEvents: formatBool(pack.Standings.MedalCountsTeamEventsEnabled()),
|
||||
settingShowIndividualBoard: formatBool(pack.Standings.ShowIndividualBoardEnabled()),
|
||||
settingShowIndividualMedals: formatBool(pack.Standings.ShowIndividualMedalsEnabled()),
|
||||
settingRecordBonusEnabled: formatBool(pack.RecordBonus.Enabled),
|
||||
settingRecordBonusMode: string(pack.RecordBonus.Mode),
|
||||
settingRecordBonusLevels: string(levels),
|
||||
settingManualEnabled: formatBool(pack.ManualAdjustment.Enabled),
|
||||
settingManualReasonRequired: formatBool(pack.ManualAdjustment.ReasonRequired),
|
||||
}
|
||||
}
|
||||
|
||||
// settingsKeys 返回固定顺序的键名,保证差异预览与落库顺序稳定。
|
||||
func settingsKeys() []string {
|
||||
keys := []string{
|
||||
settingIndividualToTeamWeight,
|
||||
settingTieBreak,
|
||||
settingMedalCountsTeamEvents,
|
||||
settingShowIndividualBoard,
|
||||
settingShowIndividualMedals,
|
||||
settingRecordBonusEnabled,
|
||||
settingRecordBonusMode,
|
||||
settingRecordBonusLevels,
|
||||
settingManualEnabled,
|
||||
settingManualReasonRequired,
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func standingsFromSettings(m map[string]string) Standings {
|
||||
s := Standings{
|
||||
IndividualToTeamWeight: settingFloat(m, settingIndividualToTeamWeight, 1),
|
||||
TieBreak: splitList(m[settingTieBreak]),
|
||||
MedalCountsTeamEvents: BoolPtr(settingBool(m, settingMedalCountsTeamEvents, true)),
|
||||
ShowIndividualBoard: BoolPtr(settingBool(m, settingShowIndividualBoard, true)),
|
||||
ShowIndividualMedals: BoolPtr(settingBool(m, settingShowIndividualMedals, true)),
|
||||
}
|
||||
if len(s.TieBreak) == 0 {
|
||||
s.TieBreak = append([]string(nil), DefaultTieBreak...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func recordBonusFromSettings(m map[string]string) RecordBonus {
|
||||
rb := RecordBonus{
|
||||
Enabled: settingBool(m, settingRecordBonusEnabled, false),
|
||||
Mode: modeOrDefault(m[settingRecordBonusMode]),
|
||||
}
|
||||
if raw := strings.TrimSpace(m[settingRecordBonusLevels]); raw != "" {
|
||||
var levels []RecordLevel
|
||||
if err := json.Unmarshal([]byte(raw), &levels); err == nil {
|
||||
rb.Levels = levels
|
||||
}
|
||||
}
|
||||
return rb
|
||||
}
|
||||
|
||||
func manualAdjustmentFromSettings(m map[string]string) ManualAdjustment {
|
||||
return ManualAdjustment{
|
||||
Enabled: settingBool(m, settingManualEnabled, false),
|
||||
ReasonRequired: settingBool(m, settingManualReasonRequired, true),
|
||||
}
|
||||
}
|
||||
|
||||
func settingFloat(m map[string]string, key string, def float64) float64 {
|
||||
raw, ok := m[key]
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
v, err := strconv.ParseFloat(strings.TrimSpace(raw), 64)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func settingBool(m map[string]string, key string, def bool) bool {
|
||||
raw, ok := m[key]
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "true", "1", "yes", "on":
|
||||
return true
|
||||
case "false", "0", "no", "off":
|
||||
return false
|
||||
default:
|
||||
return def
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat(v float64) string { return strconv.FormatFloat(v, 'g', -1, 64) }
|
||||
|
||||
func formatBool(v bool) string {
|
||||
if v {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func splitList(raw string) []string {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func modeOrDefault(raw string) domain.RecordBonusMode {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return domain.RecordBonusMax
|
||||
}
|
||||
return domain.RecordBonusMode(strings.TrimSpace(raw))
|
||||
}
|
||||
Reference in new issue
Block a user