package services import ( "errors" "strings" "git.iioio.com/freefire/jiang13-forum/models" ) var ( ErrSitePageNotFound = errors.New("单页不存在") ErrSitePageSlugUsed = errors.New("slug 已被占用") ) // SitePageService 自定义单页 type SitePageService struct { filter *SensitiveFilter } func NewSitePageService(filter *SensitiveFilter) *SitePageService { return &SitePageService{filter: filter} } // SitePageSummary 公开列表摘要 type SitePageSummary struct { ID uint `json:"id"` Title string `json:"title"` Slug string `json:"slug"` ShowInFooter bool `json:"show_in_footer"` ShowInNav bool `json:"show_in_nav"` SortOrder int `json:"sort_order"` } func (s *SitePageService) ListPublished() ([]SitePageSummary, error) { var rows []models.SitePage err := models.DB.Where("published = ?", true). Order("sort_order ASC, id ASC"). Find(&rows).Error if err != nil { return nil, err } out := make([]SitePageSummary, len(rows)) for i, p := range rows { out[i] = SitePageSummary{ ID: p.ID, Title: p.Title, Slug: p.Slug, ShowInFooter: p.ShowInFooter, ShowInNav: p.ShowInNav, SortOrder: p.SortOrder, } } return out, nil } func (s *SitePageService) ListAll() ([]models.SitePage, error) { var rows []models.SitePage err := models.DB.Order("sort_order ASC, id ASC").Find(&rows).Error if err != nil { return nil, err } return rows, nil } func (s *SitePageService) GetBySlug(slug string, allowUnpublished bool) (*models.SitePage, error) { slug, ok := NormalizePageSlug(slug) if !ok { return nil, ErrSitePageNotFound } var page models.SitePage q := models.DB.Where("slug = ?", slug) if !allowUnpublished { q = q.Where("published = ?", true) } if err := q.First(&page).Error; err != nil { return nil, ErrSitePageNotFound } page.Content = SanitizePostHTML(UnwrapContentGateTags(page.Content)) return &page, nil } func (s *SitePageService) GetByID(id uint) (*models.SitePage, error) { var page models.SitePage if err := models.DB.First(&page, id).Error; err != nil { return nil, ErrSitePageNotFound } page.Content = SanitizePostHTML(UnwrapContentGateTags(page.Content)) return &page, nil } type SitePageInput struct { Title string `json:"title"` Slug string `json:"slug"` Content string `json:"content"` Published bool `json:"published"` SortOrder int `json:"sort_order"` ShowInFooter bool `json:"show_in_footer"` ShowInNav bool `json:"show_in_nav"` } func (s *SitePageService) Create(in SitePageInput) (*models.SitePage, error) { page, err := s.normalizeInput(in) if err != nil { return nil, err } var exists int64 models.DB.Model(&models.SitePage{}).Where("slug = ?", page.Slug).Count(&exists) if exists > 0 { return nil, ErrSitePageSlugUsed } if err := models.DB.Create(page).Error; err != nil { return nil, err } return page, nil } func (s *SitePageService) Update(id uint, in SitePageInput) error { page, err := s.GetByID(id) if err != nil { return err } next, err := s.normalizeInput(in) if err != nil { return err } var exists int64 models.DB.Model(&models.SitePage{}).Where("slug = ? AND id <> ?", next.Slug, id).Count(&exists) if exists > 0 { return ErrSitePageSlugUsed } return models.DB.Model(page).Updates(map[string]interface{}{ "title": next.Title, "slug": next.Slug, "content": next.Content, "published": next.Published, "sort_order": next.SortOrder, "show_in_footer": next.ShowInFooter, "show_in_nav": next.ShowInNav, }).Error } func (s *SitePageService) Delete(id uint) error { res := models.DB.Delete(&models.SitePage{}, id) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return ErrSitePageNotFound } return nil } // SetPublished 仅切换发布状态(列表快捷操作) func (s *SitePageService) SetPublished(id uint, published bool) error { page, err := s.GetByID(id) if err != nil { return err } return models.DB.Model(page).Update("published", published).Error } func (s *SitePageService) ListSitemap(limit int) ([]models.SitePage, error) { if limit <= 0 { limit = 500 } var rows []models.SitePage err := models.DB.Where("published = ?", true). Order("updated_at DESC").Limit(limit).Find(&rows).Error return rows, err } func (s *SitePageService) normalizeInput(in SitePageInput) (*models.SitePage, error) { title := s.filter.Filter(strings.TrimSpace(in.Title)) slug, ok := NormalizePageSlug(in.Slug) if !ok { return nil, errors.New("slug 格式无效(2-64 位小写字母、数字、连字符)") } // 单页不支持登录/回复/积分可见:保存前剥离外壳,保留内部正文 content := s.filter.Filter(SanitizePostHTML(UnwrapContentGateTags(in.Content))) if title == "" { return nil, errors.New("标题不能为空") } if content == "" { return nil, errors.New("正文不能为空") } return &models.SitePage{ Title: title, Slug: slug, Content: content, Published: in.Published, SortOrder: in.SortOrder, ShowInFooter: in.ShowInFooter, ShowInNav: in.ShowInNav, }, nil }