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/")) }