119 lines
3.9 KiB
Go
119 lines
3.9 KiB
Go
// Copyright 2026 tamakyi. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package conf
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// announcementConfig 站内公告([announcement] 段)。
|
|
//
|
|
// Content 留空即关闭公告;Start / End 留空表示该侧不限。
|
|
// 展示窗口是左闭右开 [Start, End):到 end 那一刻公告消失,
|
|
// 运维不必为了「准时下线」把时间掐到秒后还担心多挂一分钟。
|
|
type announcementConfig struct {
|
|
Content string `ini:"content"`
|
|
Start string `ini:"start"`
|
|
End string `ini:"end"`
|
|
|
|
// 解析后的时间点,零值表示该侧不限。
|
|
// ini tag 为 "-":既不参与 ini 解析,也不会被未知键检测登记为合法键名。
|
|
StartAt time.Time `ini:"-"`
|
|
EndAt time.Time `ini:"-"`
|
|
}
|
|
|
|
// 支持的时间格式。裸时间(不带时区偏移)按服务器本地时区解释。
|
|
var announcementTimeLayouts = []string{
|
|
"2006-01-02 15:04",
|
|
"2006-01-02 15:04:05",
|
|
"2006-01-02",
|
|
time.RFC3339,
|
|
}
|
|
|
|
// Active 判断给定时刻是否处于公告展示窗口,并返回公告正文。
|
|
// 窗口为左闭右开 [Start, End),两侧留空表示不限。
|
|
func (a announcementConfig) Active(now time.Time) (string, bool) {
|
|
if a.Content == "" {
|
|
return "", false
|
|
}
|
|
if !a.StartAt.IsZero() && now.Before(a.StartAt) {
|
|
return "", false
|
|
}
|
|
if !a.EndAt.IsZero() && !now.Before(a.EndAt) {
|
|
return "", false
|
|
}
|
|
return a.Content, true
|
|
}
|
|
|
|
// sanitizeAnnouncement 解析公告时间窗。
|
|
//
|
|
// 时间写错只告警并关闭公告,不阻断启动:公告是展示性配置,拼错一个日期不该让整站
|
|
// 502;但必须留下能定位的日志 —— 否则现象就是「配了却不显示」,和未知键被静默
|
|
// 忽略属于同一类坑(见 unknown.go)。
|
|
func sanitizeAnnouncement() {
|
|
Announcement.Content = strings.TrimSpace(Announcement.Content)
|
|
if Announcement.Content == "" {
|
|
return
|
|
}
|
|
|
|
disable := func(key, value, reason string) {
|
|
logrus.WithFields(logrus.Fields{
|
|
"section": "announcement",
|
|
"key": key,
|
|
"value": value,
|
|
"reason": reason,
|
|
}).Warn("公告配置无效,已关闭公告(站点其它功能不受影响)")
|
|
Announcement = announcementConfig{}
|
|
}
|
|
|
|
var ok bool
|
|
if Announcement.StartAt, ok = parseAnnouncementTime(Announcement.Start); !ok {
|
|
disable("start", Announcement.Start, "支持 2006-01-02 15:04 / 2006-01-02 15:04:05 / 2006-01-02 / RFC3339")
|
|
return
|
|
}
|
|
if Announcement.EndAt, ok = parseAnnouncementTime(Announcement.End); !ok {
|
|
disable("end", Announcement.End, "支持 2006-01-02 15:04 / 2006-01-02 15:04:05 / 2006-01-02 / RFC3339")
|
|
return
|
|
}
|
|
if !Announcement.StartAt.IsZero() && !Announcement.EndAt.IsZero() && !Announcement.EndAt.After(Announcement.StartAt) {
|
|
disable("end", Announcement.End, "end 必须晚于 start,否则公告永远不会展示")
|
|
return
|
|
}
|
|
|
|
_, activeNow := Announcement.Active(time.Now())
|
|
logrus.WithFields(logrus.Fields{
|
|
"start": announcementBoundLabel(Announcement.StartAt),
|
|
"end": announcementBoundLabel(Announcement.EndAt),
|
|
"zone": time.Local.String(),
|
|
"active_now": activeNow,
|
|
}).Info("站内公告已配置")
|
|
}
|
|
|
|
// parseAnnouncementTime 解析单个时间点;空串返回零值(表示不限)且视为合法。
|
|
func parseAnnouncementTime(raw string) (time.Time, bool) {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return time.Time{}, true
|
|
}
|
|
for _, layout := range announcementTimeLayouts {
|
|
if t, err := time.ParseInLocation(layout, raw, time.Local); err == nil {
|
|
return t, true
|
|
}
|
|
}
|
|
return time.Time{}, false
|
|
}
|
|
|
|
// announcementBoundLabel 把时间点格式化成带时区偏移的字符串,供启动日志核对
|
|
// 「我写的 10:00 到底被当成了几点」——裸时间按服务器本地时区解释,容器多为 UTC。
|
|
func announcementBoundLabel(t time.Time) string {
|
|
if t.IsZero() {
|
|
return "不限"
|
|
}
|
|
return t.Format("2006-01-02 15:04:05 -07:00")
|
|
}
|