feat: 交付官方 Docker 运行时,外观改背景图并下线自定义 CSS/JS
站点/后台分轨背景与用户列表排序一并落地;生产 CORS 改走 SITE_URL,健康检查带版本号。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
167
backend/service/bg.go
Normal file
167
backend/service/bg.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 站点背景图(前台 / 后台分轨)
|
||||
const (
|
||||
SettingKeyBgSiteURL = "bg_site_url"
|
||||
SettingKeyBgSiteMode = "bg_site_mode"
|
||||
SettingKeyBgAdminURL = "bg_admin_url"
|
||||
SettingKeyBgAdminMode = "bg_admin_mode"
|
||||
|
||||
BgSurfaceSite = "site"
|
||||
BgSurfaceAdmin = "admin"
|
||||
|
||||
BgModeCover = "cover"
|
||||
BgModeContain = "contain"
|
||||
BgModeRepeat = "repeat"
|
||||
BgModeRepeatX = "repeat-x"
|
||||
BgModeRepeatY = "repeat-y"
|
||||
BgModeStretch = "stretch"
|
||||
BgModeCenter = "center"
|
||||
DefaultBgMode = BgModeCover
|
||||
)
|
||||
|
||||
// ErrBgFileMissing 设置了合法 URL 但磁盘上没有对应文件
|
||||
var ErrBgFileMissing = errors.New("背景图文件不存在")
|
||||
|
||||
// 仅接受本站上传目录下的 32 位 hex 文件名,禁止外链与路径穿越(写入 CSS url())
|
||||
var bgUploadURLRe = regexp.MustCompile(`^/uploads/backgrounds/[0-9a-f]{32}\.(jpg|jpeg|png|webp)$`)
|
||||
|
||||
var bgModes = map[string]struct{}{
|
||||
BgModeCover: {},
|
||||
BgModeContain: {},
|
||||
BgModeRepeat: {},
|
||||
BgModeRepeatX: {},
|
||||
BgModeRepeatY: {},
|
||||
BgModeStretch: {},
|
||||
BgModeCenter: {},
|
||||
}
|
||||
|
||||
// NormalizeBgMode 校验铺放模式;空串视为默认 cover
|
||||
func NormalizeBgMode(raw string) (string, bool) {
|
||||
m := strings.ToLower(strings.TrimSpace(raw))
|
||||
if m == "" {
|
||||
return DefaultBgMode, true
|
||||
}
|
||||
if _, ok := bgModes[m]; !ok {
|
||||
return "", false
|
||||
}
|
||||
return m, true
|
||||
}
|
||||
|
||||
// NormalizeBgURL 校验背景图 URL;空串表示清除(走默认画布)
|
||||
func NormalizeBgURL(raw string) (string, bool) {
|
||||
u := strings.TrimSpace(raw)
|
||||
if u == "" {
|
||||
return "", true
|
||||
}
|
||||
u = strings.ToLower(u)
|
||||
if !bgUploadURLRe.MatchString(u) {
|
||||
return "", false
|
||||
}
|
||||
return u, true
|
||||
}
|
||||
|
||||
// NormalizeBgSurface 前台 site / 后台 admin
|
||||
func NormalizeBgSurface(raw string) (string, bool) {
|
||||
s := strings.ToLower(strings.TrimSpace(raw))
|
||||
if s == BgSurfaceSite || s == BgSurfaceAdmin {
|
||||
return s, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (s *SettingService) BgSiteURL() (string, error) {
|
||||
return s.bgURL(SettingKeyBgSiteURL)
|
||||
}
|
||||
|
||||
func (s *SettingService) BgAdminURL() (string, error) {
|
||||
return s.bgURL(SettingKeyBgAdminURL)
|
||||
}
|
||||
|
||||
func (s *SettingService) BgSiteMode() (string, error) {
|
||||
return s.bgMode(SettingKeyBgSiteMode)
|
||||
}
|
||||
|
||||
func (s *SettingService) BgAdminMode() (string, error) {
|
||||
return s.bgMode(SettingKeyBgAdminMode)
|
||||
}
|
||||
|
||||
func (s *SettingService) bgURL(key string) (string, error) {
|
||||
v, found, err := s.getValue(key)
|
||||
if err != nil || !found {
|
||||
return "", err
|
||||
}
|
||||
u, ok := NormalizeBgURL(v)
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) bgMode(key string) (string, error) {
|
||||
v, found, err := s.getValue(key)
|
||||
if err != nil || !found {
|
||||
return DefaultBgMode, err
|
||||
}
|
||||
m, ok := NormalizeBgMode(v)
|
||||
if !ok {
|
||||
return DefaultBgMode, nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SetBgSiteURL 保存前台背景;空串删除键
|
||||
func (s *SettingService) SetBgSiteURL(url string) error {
|
||||
return s.setBgURL(SettingKeyBgSiteURL, url)
|
||||
}
|
||||
|
||||
// SetBgAdminURL 保存后台背景;空串删除键
|
||||
func (s *SettingService) SetBgAdminURL(url string) error {
|
||||
return s.setBgURL(SettingKeyBgAdminURL, url)
|
||||
}
|
||||
|
||||
func (s *SettingService) setBgURL(key, url string) error {
|
||||
u, ok := NormalizeBgURL(url)
|
||||
if !ok {
|
||||
return ErrInvalidSiteSetting
|
||||
}
|
||||
if u == "" {
|
||||
return s.deleteKey(key)
|
||||
}
|
||||
return s.putValue(key, u)
|
||||
}
|
||||
|
||||
func (s *SettingService) SetBgSiteMode(mode string) error {
|
||||
return s.setBgMode(SettingKeyBgSiteMode, mode)
|
||||
}
|
||||
|
||||
func (s *SettingService) SetBgAdminMode(mode string) error {
|
||||
return s.setBgMode(SettingKeyBgAdminMode, mode)
|
||||
}
|
||||
|
||||
func (s *SettingService) setBgMode(key, mode string) error {
|
||||
m, ok := NormalizeBgMode(mode)
|
||||
if !ok {
|
||||
return ErrInvalidSiteSetting
|
||||
}
|
||||
if m == DefaultBgMode {
|
||||
return s.deleteKey(key)
|
||||
}
|
||||
return s.putValue(key, m)
|
||||
}
|
||||
|
||||
// BgRelPath 把合法 URL 转成 uploads 根下的相对路径;非法返回空
|
||||
func BgRelPath(url string) string {
|
||||
u, ok := NormalizeBgURL(url)
|
||||
if !ok || u == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.FromSlash(strings.TrimPrefix(u, "/uploads/"))
|
||||
}
|
||||
Reference in New Issue
Block a user