package store import ( "database/sql" "errors" "strconv" "strings" ) // Settings 键白名单(§7.1 SettingsDTO 同源;admin_password_hash 永不进入 API 响应)。 const ( KeyAdminPasswordHash = "admin_password_hash" KeySiteTitle = "site_title" KeySiteDesc = "site_desc" KeyPageSize = "page_size" KeyBeianNo = "beian_no" KeySiteLogo = "site_logo" ) // GetSetting 读取单个设置。 func (s *Store) GetSetting(key string) (string, bool, error) { var v string err := s.db.QueryRow(`SELECT value FROM settings WHERE key=?`, key).Scan(&v) if errors.Is(err, sql.ErrNoRows) { return "", false, nil } return v, err == nil, err } // SetSetting 写入单个设置(INSERT OR REPLACE)。 func (s *Store) SetSetting(key, value string) error { _, err := s.db.Exec(`INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`, key, value) return err } // SiteSettings 对外的站点设置白名单视图。 type SiteSettings struct { SiteTitle string `json:"site_title"` SiteDesc string `json:"site_desc"` PageSize int `json:"page_size"` BeianNo string `json:"beian_no"` SiteLogo string `json:"site_logo"` } // Defaults,未初始化时兜底。 const ( DefaultSiteTitle = "Pure Note" DefaultSiteDesc = "" DefaultPageSize = 10 DefaultBeianNo = "" ) // ValidPageSize 每页条数可选项(前端选择器同源:10/20/30/50)。 func ValidPageSize(n int) bool { switch n { case 10, 20, 30, 50: return true } return false } // GetSiteSettings 读取站点设置(白名单键,带默认值)。 func (s *Store) GetSiteSettings() (*SiteSettings, error) { ss := &SiteSettings{SiteTitle: DefaultSiteTitle, SiteDesc: DefaultSiteDesc, PageSize: DefaultPageSize, BeianNo: DefaultBeianNo} if v, ok, err := s.GetSetting(KeySiteTitle); err != nil { return nil, err } else if ok && v != "" { ss.SiteTitle = v } if v, ok, err := s.GetSetting(KeySiteDesc); err != nil { return nil, err } else if ok { ss.SiteDesc = v } if v, ok, err := s.GetSetting(KeyPageSize); err != nil { return nil, err } else if ok { // strconv.Atoi 全文解析:拒绝 "10abc" 类部分解析的脏值(评审 round2 P2-13); // 脏值/越界值回退默认,而非落入任意 [1,100] if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && ValidPageSize(n) { ss.PageSize = n } } if v, ok, err := s.GetSetting(KeyBeianNo); err != nil { return nil, err } else if ok { ss.BeianNo = strings.TrimSpace(v) } if v, ok, err := s.GetSetting(KeySiteLogo); err != nil { return nil, err } else if ok { ss.SiteLogo = strings.TrimSpace(v) } return ss, nil } // logoImageID 从 site_logo 设置解析受 GC 保护的图片 id(无 logo 或非本站图片路径返回 0)。 func (s *Store) logoImageID() int64 { v, ok, err := s.GetSetting(KeySiteLogo) if err != nil || !ok || v == "" { return 0 } if m := imageRefRe.FindStringSubmatch(v); m != nil { var id int64 if _, err := fmtSscanInt(m[1], &id); err == nil && id > 0 { return id } } return 0 }