package handler import ( "errors" "net/http" "github.com/freefire/jiang13-bbs/model" "github.com/freefire/jiang13-bbs/realtime" "github.com/freefire/jiang13-bbs/service" "github.com/gin-gonic/gin" ) // GET /api/settings func (h *Handlers) PublicSettings(c *gin.Context) { s, err := h.Setting.Public() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取站点设置失败"}) return } c.JSON(http.StatusOK, s) } // updateSettingsRequest 字段均为可选指针:只更新请求里出现的项,避免外观页覆盖其它设置 type updateSettingsRequest struct { Accent *string `json:"accent"` TrustReviewedPublish *bool `json:"trust_reviewed_publish"` PublishWithoutReview *bool `json:"publish_without_review"` SiteName *string `json:"site_name"` SiteDescription *string `json:"site_description"` AllowRegister *bool `json:"allow_register"` AllowComments *bool `json:"allow_comments"` CommentsRequireLogin *bool `json:"comments_require_login"` AllowMessages *bool `json:"allow_messages"` PostCooldownHours *int `json:"post_cooldown_hours"` CodeBlockAutoFold *bool `json:"code_block_auto_fold"` CodeBlockFoldLines *int `json:"code_block_fold_lines"` UIAnimations *bool `json:"ui_animations"` AnimCodeFold *bool `json:"anim_code_fold"` AnimSmoothScroll *bool `json:"anim_smooth_scroll"` AnimChrome *bool `json:"anim_chrome"` PostLinkNewTab *bool `json:"post_link_new_tab"` AttachmentExtLimit *bool `json:"attachment_ext_limit"` AttachmentExts *[]string `json:"attachment_exts"` AttachmentMaxMB *int `json:"attachment_max_mb"` AttachmentMaxCount *int `json:"attachment_max_count"` ImageMaxMB *int `json:"image_max_mb"` BgSiteURL *string `json:"bg_site_url"` BgSiteMode *string `json:"bg_site_mode"` BgAdminURL *string `json:"bg_admin_url"` BgAdminMode *string `json:"bg_admin_mode"` SiteWordmark *string `json:"site_wordmark"` SiteSlogan *string `json:"site_slogan"` SiteKeywords *[]string `json:"site_keywords"` LogoLightURL *string `json:"logo_light_url"` LogoDarkURL *string `json:"logo_dark_url"` FaviconURL *string `json:"favicon_url"` BrandMark *string `json:"brand_mark"` BrandLogoSize *string `json:"brand_logo_size"` BrandLogoFit *string `json:"brand_logo_fit"` FooterLinks *[]service.FooterLink `json:"footer_links"` PointsCheckinBase *int `json:"points_checkin_base"` PointsStreakEveryDays *int `json:"points_streak_every_days"` PointsStreakBonus *int `json:"points_streak_bonus"` PointsPostReward *int `json:"points_post_reward"` PointsPostDailyCap *int `json:"points_post_daily_cap"` PointsReplyReward *int `json:"points_reply_reward"` PointsReplyDailyCap *int `json:"points_reply_daily_cap"` PointsRecommendReward *int `json:"points_recommend_reward"` PostEditLockHours *int `json:"post_edit_lock_hours"` CommentEditLockHours *int `json:"comment_edit_lock_hours"` NecroReplyAfterHours *int `json:"necro_reply_after_hours"` NecroReplyPenalty *int `json:"necro_reply_penalty"` Levels *[]model.LevelDef `json:"levels"` LevelsFx *bool `json:"levels_fx"` UrlStyle *string `json:"url_style"` MobileTabs *[]string `json:"mobile_tabs"` HeaderNav *[]service.HeaderNavItem `json:"header_nav"` } func settingsPayload(saved service.PublicSiteSettings) gin.H { return gin.H{ "accent": saved.Accent, "trust_reviewed_publish": saved.TrustReviewedPublish, "publish_without_review": saved.PublishWithoutReview, "site_name": saved.SiteName, "site_description": saved.SiteDescription, "allow_register": saved.AllowRegister, "allow_comments": saved.AllowComments, "comments_require_login": saved.CommentsRequireLogin, "allow_messages": saved.AllowMessages, "post_cooldown_hours": saved.PostCooldownHours, "code_block_auto_fold": saved.CodeBlockAutoFold, "code_block_fold_lines": saved.CodeBlockFoldLines, "ui_animations": saved.UIAnimations, "anim_code_fold": saved.AnimCodeFold, "anim_smooth_scroll": saved.AnimSmoothScroll, "anim_chrome": saved.AnimChrome, "post_link_new_tab": saved.PostLinkNewTab, "attachment_ext_limit": saved.AttachmentExtLimit, "attachment_exts": saved.AttachmentExts, "attachment_max_mb": saved.AttachmentMaxMB, "attachment_max_count": saved.AttachmentMaxCount, "image_max_mb": saved.ImageMaxMB, "bg_site_url": saved.BgSiteURL, "bg_site_mode": saved.BgSiteMode, "bg_admin_url": saved.BgAdminURL, "bg_admin_mode": saved.BgAdminMode, "site_wordmark": saved.SiteWordmark, "site_slogan": saved.SiteSlogan, "site_keywords": saved.SiteKeywords, "logo_light_url": saved.LogoLightURL, "logo_dark_url": saved.LogoDarkURL, "favicon_url": saved.FaviconURL, "brand_mark": saved.BrandMark, "brand_logo_size": saved.BrandLogoSize, "brand_logo_fit": saved.BrandLogoFit, "footer_links": saved.FooterLinks, "points_checkin_base": saved.PointsCheckinBase, "points_streak_every_days": saved.PointsStreakEveryDays, "points_streak_bonus": saved.PointsStreakBonus, "points_post_reward": saved.PointsPostReward, "points_post_daily_cap": saved.PointsPostDailyCap, "points_reply_reward": saved.PointsReplyReward, "points_reply_daily_cap": saved.PointsReplyDailyCap, "points_recommend_reward": saved.PointsRecommendReward, "post_edit_lock_hours": saved.PostEditLockHours, "comment_edit_lock_hours": saved.CommentEditLockHours, "necro_reply_after_hours": saved.NecroReplyAfterHours, "necro_reply_penalty": saved.NecroReplyPenalty, "levels": saved.Levels, "levels_fx": saved.LevelsFx, "url_style": saved.UrlStyle, "mobile_tabs": saved.MobileTabs, "header_nav": saved.HeaderNav, } } func (req *updateSettingsRequest) hasAny() bool { return req.Accent != nil || req.TrustReviewedPublish != nil || req.PublishWithoutReview != nil || req.SiteName != nil || req.SiteDescription != nil || req.AllowRegister != nil || req.AllowComments != nil || req.CommentsRequireLogin != nil || req.AllowMessages != nil || req.PostCooldownHours != nil || req.CodeBlockAutoFold != nil || req.CodeBlockFoldLines != nil || req.UIAnimations != nil || req.AnimCodeFold != nil || req.AnimSmoothScroll != nil || req.AnimChrome != nil || req.PostLinkNewTab != nil || req.AttachmentExtLimit != nil || req.AttachmentExts != nil || req.AttachmentMaxMB != nil || req.AttachmentMaxCount != nil || req.ImageMaxMB != nil || req.BgSiteURL != nil || req.BgSiteMode != nil || req.BgAdminURL != nil || req.BgAdminMode != nil || req.SiteWordmark != nil || req.SiteSlogan != nil || req.SiteKeywords != nil || req.LogoLightURL != nil || req.LogoDarkURL != nil || req.FaviconURL != nil || req.BrandMark != nil || req.BrandLogoSize != nil || req.BrandLogoFit != nil || req.FooterLinks != nil || req.PointsCheckinBase != nil || req.PointsStreakEveryDays != nil || req.PointsStreakBonus != nil || req.PointsPostReward != nil || req.PointsPostDailyCap != nil || req.PointsReplyReward != nil || req.PointsReplyDailyCap != nil || req.PointsRecommendReward != nil || req.Levels != nil || req.LevelsFx != nil || req.PostEditLockHours != nil || req.CommentEditLockHours != nil || req.NecroReplyAfterHours != nil || req.NecroReplyPenalty != nil || req.UrlStyle != nil || req.MobileTabs != nil || req.HeaderNav != nil } // AdminGetSettings 超管读取站点设置(与公开 payload 字段一致) func (h *Handlers) AdminGetSettings(c *gin.Context) { saved, err := h.Setting.Public() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取站点设置失败"}) return } c.JSON(http.StatusOK, settingsPayload(saved)) } // PUT /api/admin/settings func (h *Handlers) UpdateSettings(c *gin.Context) { var req updateSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } if !req.hasAny() { c.JSON(http.StatusBadRequest, gin.H{"error": "未提供任何可更新的设置项"}) return } if req.AllowRegister != nil || req.AttachmentExtLimit != nil || req.AttachmentExts != nil || req.AttachmentMaxMB != nil || req.AttachmentMaxCount != nil || req.ImageMaxMB != nil { c.JSON(400, gin.H{"error": "请在访问与安全或文件与存储页通过版本校验保存这些设置"}) return } if req.Accent != nil { if err := h.Setting.SetAccent(*req.Accent); err != nil { if errors.Is(err, service.ErrInvalidAccent) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.TrustReviewedPublish != nil { if err := h.Setting.SetTrustReviewedPublish(*req.TrustReviewedPublish); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.PublishWithoutReview != nil { if err := h.Setting.SetPublishWithoutReview(*req.PublishWithoutReview); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.SiteName != nil { if err := h.Setting.SetSiteName(*req.SiteName); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "站点名称过长或无效"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.SiteDescription != nil { if err := h.Setting.SetSiteDescription(*req.SiteDescription); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "站点简介过长"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AllowRegister != nil { if err := h.Setting.SetAllowRegister(*req.AllowRegister); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AllowComments != nil { if err := h.Setting.SetAllowComments(*req.AllowComments); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.CommentsRequireLogin != nil { if err := h.Setting.SetCommentsRequireLogin(*req.CommentsRequireLogin); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AllowMessages != nil { if err := h.Setting.SetAllowMessages(*req.AllowMessages); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.PostCooldownHours != nil { if err := h.Setting.SetPostCooldownHours(*req.PostCooldownHours); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "冷静期须为 0–168 小时"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } lockRules := []struct { req *int set func(int) error hint string }{ {req.PostEditLockHours, h.Setting.SetPostEditLockHours, "帖子可编辑时长须为 0–8760 小时"}, {req.CommentEditLockHours, h.Setting.SetCommentEditLockHours, "评论可编辑时长须为 0–8760 小时"}, {req.NecroReplyAfterHours, h.Setting.SetNecroReplyAfterHours, "旧帖回复阈值须为 0–8760 小时"}, {req.NecroReplyPenalty, h.Setting.SetNecroReplyPenalty, "旧帖回复扣分须为 0–100"}, } for _, r := range lockRules { if r.req == nil { continue } if err := r.set(*r.req); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": r.hint}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } pointsRules := []struct { req *int set func(int) error hint string }{ {req.PointsCheckinBase, h.Setting.SetPointsCheckinBase, "签到基础分须为 0–100"}, {req.PointsStreakEveryDays, h.Setting.SetPointsStreakEveryDays, "连续签到触发天数须为 0–100"}, {req.PointsStreakBonus, h.Setting.SetPointsStreakBonus, "连续签到加成分须为 0–100"}, {req.PointsPostReward, h.Setting.SetPointsPostReward, "发帖奖励须为 0–100"}, {req.PointsPostDailyCap, h.Setting.SetPointsPostDailyCap, "发帖奖励每日上限须为 0–100"}, {req.PointsReplyReward, h.Setting.SetPointsReplyReward, "回复奖励须为 0–100"}, {req.PointsReplyDailyCap, h.Setting.SetPointsReplyDailyCap, "回复奖励每日上限须为 0–100"}, {req.PointsRecommendReward, h.Setting.SetPointsRecommendReward, "推荐奖励须为 0–100"}, } for _, r := range pointsRules { if r.req == nil { continue } if err := r.set(*r.req); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": r.hint}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.Levels != nil { if err := h.Setting.SetLevels(*req.Levels); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "等级配置无效(2–12 级,阈值须非负且严格递增、首级为 0,颜色须在色板内)"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.LevelsFx != nil { if err := h.Setting.SetLevelsFx(*req.LevelsFx); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.CodeBlockAutoFold != nil { if err := h.Setting.SetCodeBlockAutoFold(*req.CodeBlockAutoFold); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.CodeBlockFoldLines != nil { if err := h.Setting.SetCodeBlockFoldLines(*req.CodeBlockFoldLines); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "折叠行数须为 0–100"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.UIAnimations != nil { if err := h.Setting.SetUIAnimations(*req.UIAnimations); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AnimCodeFold != nil { if err := h.Setting.SetAnimCodeFold(*req.AnimCodeFold); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AnimSmoothScroll != nil { if err := h.Setting.SetAnimSmoothScroll(*req.AnimSmoothScroll); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AnimChrome != nil { if err := h.Setting.SetAnimChrome(*req.AnimChrome); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.PostLinkNewTab != nil { if err := h.Setting.SetPostLinkNewTab(*req.PostLinkNewTab); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AttachmentExtLimit != nil { if err := h.Setting.SetAttachmentExtLimit(*req.AttachmentExtLimit); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AttachmentExts != nil { if err := h.Setting.SetAttachmentExts(*req.AttachmentExts); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "扩展名无效或数量超限(最多 80 个,仅字母数字)"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AttachmentMaxMB != nil { if err := h.Setting.SetAttachmentMaxMB(*req.AttachmentMaxMB); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "附件上限须至少 1MB"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.AttachmentMaxCount != nil { if err := h.Setting.SetAttachmentMaxCount(*req.AttachmentMaxCount); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "每帖附件数须为 1–20"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.ImageMaxMB != nil { if err := h.Setting.SetImageMaxMB(*req.ImageMaxMB); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "插图上限须至少 1MB"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.BgSiteURL != nil { if err := h.applyBgURL(service.BgSurfaceSite, *req.BgSiteURL); err != nil { writeBgSettingError(c, err) return } } if req.BgAdminURL != nil { if err := h.applyBgURL(service.BgSurfaceAdmin, *req.BgAdminURL); err != nil { writeBgSettingError(c, err) return } } if req.BgSiteMode != nil { if err := h.Setting.SetBgSiteMode(*req.BgSiteMode); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的背景铺放模式"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.BgAdminMode != nil { if err := h.Setting.SetBgAdminMode(*req.BgAdminMode); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的背景铺放模式"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.SiteWordmark != nil { if err := h.Setting.SetSiteWordmark(*req.SiteWordmark); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "字标过长(最多 16 字)"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.SiteSlogan != nil { if err := h.Setting.SetSiteSlogan(*req.SiteSlogan); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "标语过长(最多 32 字)"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.SiteKeywords != nil { if err := h.Setting.SetSiteKeywords(*req.SiteKeywords); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "关键词过多或单个过长(最多 16 个,每个 20 字)"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.LogoLightURL != nil { if err := h.applyBrandURL(service.BrandSlotLight, *req.LogoLightURL); err != nil { writeBrandSettingError(c, err) return } } if req.LogoDarkURL != nil { if err := h.applyBrandURL(service.BrandSlotDark, *req.LogoDarkURL); err != nil { writeBrandSettingError(c, err) return } } if req.FaviconURL != nil { if err := h.applyBrandURL(service.BrandSlotFavicon, *req.FaviconURL); err != nil { writeBrandSettingError(c, err) return } } if req.BrandMark != nil { if err := h.Setting.SetBrandMark(*req.BrandMark); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "页眉形式无效"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.BrandLogoFit != nil { if err := h.Setting.SetBrandLogoFit(*req.BrandLogoFit); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "铺法无效"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.BrandLogoSize != nil { if err := h.Setting.SetBrandLogoSize(*req.BrandLogoSize); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "画幅尺寸无效"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.FooterLinks != nil { if err := h.Setting.SetFooterLinks(*req.FooterLinks); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "页脚链接无效(最多 8 条,文案 32 字,地址须为站内路径或 http(s))"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.UrlStyle != nil { if err := h.Setting.SetUrlStyle(*req.UrlStyle); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "链接风格无效"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.MobileTabs != nil { if err := h.Setting.SetMobileTabs(*req.MobileTabs); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "移动端底栏配置无效(至少保留一项,且取值须在允许列表内)"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } if req.HeaderNav != nil { if err := h.Setting.SetHeaderNav(*req.HeaderNav); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "顶部导航配置无效(最多 8 项,内置项须在允许列表内,自定义项名称 1–16 字、地址须为站内路径或 http(s))"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) return } } saved, err := h.Setting.Public() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取站点设置失败"}) return } payload := settingsPayload(saved) h.Hub.BroadcastAll(realtime.Envelope{ Type: realtime.EventSettingsChanged, Data: payload, }) out := settingsPayload(saved) out["ok"] = true c.JSON(http.StatusOK, out) } func writeBgSettingError(c *gin.Context, err error) { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "背景图地址无效"}) return } if errors.Is(err, service.ErrBgFileMissing) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) } // applyBgURL 写入对应轨的背景 URL,并删除不再被引用的旧文件 func (h *Handlers) applyBgURL(surface, url string) error { u, ok := service.NormalizeBgURL(url) if !ok { return service.ErrInvalidSiteSetting } if u != "" && !h.Upload.BackgroundFileExists(u) { return service.ErrBgFileMissing } oldSite, err := h.Setting.BgSiteURL() if err != nil { return err } oldAdmin, err := h.Setting.BgAdminURL() if err != nil { return err } if surface == service.BgSurfaceAdmin { if err := h.Setting.SetBgAdminURL(u); err != nil { return err } h.Upload.RemoveBackgroundIfUnused(oldAdmin, oldSite, u) return nil } if err := h.Setting.SetBgSiteURL(u); err != nil { return err } h.Upload.RemoveBackgroundIfUnused(oldSite, u, oldAdmin) return nil } func writeBrandSettingError(c *gin.Context, err error) { if errors.Is(err, service.ErrInvalidSiteSetting) { c.JSON(http.StatusBadRequest, gin.H{"error": "品牌图地址无效"}) return } if errors.Is(err, service.ErrBrandFileMissing) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) } // applyBrandURL 写入对应槽位,并删除不再被引用的旧文件 func (h *Handlers) applyBrandURL(slot, url string) error { u, ok := service.NormalizeBrandURL(slot, url) if !ok { return service.ErrInvalidSiteSetting } if u != "" && !h.Upload.BrandFileExists(u) { return service.ErrBrandFileMissing } oldLight, err := h.Setting.LogoLightURL() if err != nil { return err } oldDark, err := h.Setting.LogoDarkURL() if err != nil { return err } oldFav, err := h.Setting.FaviconURL() if err != nil { return err } var old string switch slot { case service.BrandSlotDark: old = oldDark if err := h.Setting.SetLogoDarkURL(u); err != nil { return err } oldDark = u case service.BrandSlotFavicon: old = oldFav if err := h.Setting.SetFaviconURL(u); err != nil { return err } oldFav = u default: old = oldLight if err := h.Setting.SetLogoLightURL(u); err != nil { return err } oldLight = u } h.Upload.RemoveBrandIfUnused(old, oldLight, oldDark, oldFav) return nil }