Files
jiang13-bbs/backend/service/site_page.go

264 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"errors"
"regexp"
"strings"
"time"
"unicode/utf8"
"github.com/freefire/jiang13-bbs/model"
"gorm.io/gorm"
)
var sitePageSlugRe = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
// 写入时显式列出字段(须为独立 Select 参数;逗号拼成一串会被 GORM 当成单个非法字段名)
var sitePageWriteFields = []string{
"Slug", "Title", "Content", "Excerpt",
"Published", "ShowInSidebar", "SortOrder", "AllowComments",
}
// SitePageService 站点单页服务
type SitePageService struct {
db *gorm.DB
}
func NewSitePageService(db *gorm.DB) *SitePageService {
return &SitePageService{db: db}
}
// SitePageInput 创建/更新单页入参
type SitePageInput struct {
Slug string `json:"slug"`
Title string `json:"title"`
Content string `json:"content"`
Excerpt string `json:"excerpt"`
Published *bool `json:"published"`
ShowInSidebar *bool `json:"show_in_sidebar"`
SortOrder *int `json:"sort_order"`
AllowComments *bool `json:"allow_comments"`
}
// SidebarPageItem 首页右栏「关于本站」入口
type SidebarPageItem struct {
ID uint `json:"id"`
Slug string `json:"slug"`
Title string `json:"title"`
Excerpt string `json:"excerpt"`
}
// PublishedPageItem 公开单页目录(全部页,含未进侧栏的已发布页)
type PublishedPageItem struct {
ID uint `json:"id"`
Slug string `json:"slug"`
Title string `json:"title"`
Excerpt string `json:"excerpt"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (in *SitePageInput) normalize(existing *model.SitePage) error {
in.Slug = strings.ToLower(strings.TrimSpace(in.Slug))
if in.Slug == "" {
return errors.New("slug 不能为空")
}
if len(in.Slug) > 64 {
return errors.New("slug 不能超过 64 字符")
}
if !sitePageSlugRe.MatchString(in.Slug) {
return errors.New("slug 仅允许小写字母、数字与连字符")
}
in.Title = strings.TrimSpace(in.Title)
if in.Title == "" {
return errors.New("标题不能为空")
}
if utf8.RuneCountInString(in.Title) > 200 {
return errors.New("标题不能超过 200 字")
}
in.Content = strings.TrimSpace(in.Content)
if in.Content == "" {
return errors.New("内容不能为空")
}
if err := rejectHideBlocks(in.Content, ErrSitePageHideNotAllowed); err != nil {
return err
}
in.Excerpt = strings.TrimSpace(in.Excerpt)
if utf8.RuneCountInString(in.Excerpt) > 300 {
return errors.New("摘要不能超过 300 字")
}
_ = existing
return nil
}
func (in *SitePageInput) applyTo(p *model.SitePage) {
p.Slug = in.Slug
p.Title = in.Title
p.Content = in.Content
p.Excerpt = in.Excerpt
if in.Published != nil {
p.Published = *in.Published
}
if in.ShowInSidebar != nil {
p.ShowInSidebar = *in.ShowInSidebar
}
if in.SortOrder != nil {
p.SortOrder = *in.SortOrder
}
if in.AllowComments != nil {
p.AllowComments = *in.AllowComments
}
}
func (s *SitePageService) slugTaken(slug string, excludeID uint) (bool, error) {
q := s.db.Model(&model.SitePage{}).Where("slug = ?", slug)
if excludeID > 0 {
q = q.Where("id <> ?", excludeID)
}
var n int64
if err := q.Count(&n).Error; err != nil {
return false, err
}
return n > 0, nil
}
// ListSidebarPublished 首页右栏:已发布且勾选侧栏展示
func (s *SitePageService) ListSidebarPublished() ([]SidebarPageItem, error) {
var pages []model.SitePage
err := s.db.Where("published = ? AND show_in_sidebar = ?", true, true).
Order("sort_order ASC, id ASC").
Select("id", "slug", "title", "excerpt").
Find(&pages).Error
if err != nil {
return nil, err
}
out := make([]SidebarPageItem, 0, len(pages))
for _, p := range pages {
out = append(out, SidebarPageItem{
ID: p.ID, Slug: p.Slug, Title: p.Title, Excerpt: p.Excerpt,
})
}
return out, nil
}
// ListPublished 已发布单页目录(全部页;含未勾选侧栏的)
func (s *SitePageService) ListPublished() ([]PublishedPageItem, error) {
var pages []model.SitePage
err := s.db.Where("published = ?", true).
Order("sort_order ASC, id ASC").
Select("id", "slug", "title", "excerpt", "created_at", "updated_at").
Limit(100).
Find(&pages).Error
if err != nil {
return nil, err
}
out := make([]PublishedPageItem, 0, len(pages))
for _, p := range pages {
out = append(out, PublishedPageItem{
ID: p.ID, Slug: p.Slug, Title: p.Title, Excerpt: p.Excerpt,
CreatedAt: p.CreatedAt, UpdatedAt: p.UpdatedAt,
})
}
return out, nil
}
// GetPublishedBySlug 公开详情(仅已发布)
func (s *SitePageService) GetPublishedBySlug(slug string) (*model.SitePage, error) {
slug = strings.ToLower(strings.TrimSpace(slug))
var p model.SitePage
err := s.db.Where("slug = ? AND published = ?", slug, true).First(&p).Error
if err != nil {
return nil, err
}
return &p, nil
}
// ListAll 管理后台全部单页
func (s *SitePageService) ListAll() ([]model.SitePage, error) {
var list []model.SitePage
err := s.db.Order("sort_order ASC, id ASC").Limit(100).Find(&list).Error
return list, err
}
// Get 管理后台按 ID 取单页
func (s *SitePageService) Get(id uint) (*model.SitePage, error) {
var p model.SitePage
if err := s.db.First(&p, id).Error; err != nil {
return nil, err
}
return &p, nil
}
// Create 新建单页
func (s *SitePageService) Create(in *SitePageInput) (*model.SitePage, error) {
if err := in.normalize(nil); err != nil {
return nil, err
}
taken, err := s.slugTaken(in.Slug, 0)
if err != nil {
return nil, err
}
if taken {
return nil, errors.New("slug 已被占用")
}
p := &model.SitePage{
Published: false,
ShowInSidebar: false,
AllowComments: false,
SortOrder: 0,
}
in.applyTo(p)
if err := s.db.Select(sitePageWriteFields).Create(p).Error; err != nil {
return nil, err
}
return p, nil
}
// Update 更新单页
func (s *SitePageService) Update(id uint, in *SitePageInput) (*model.SitePage, error) {
var p model.SitePage
if err := s.db.First(&p, id).Error; err != nil {
return nil, err
}
if err := in.normalize(&p); err != nil {
return nil, err
}
taken, err := s.slugTaken(in.Slug, id)
if err != nil {
return nil, err
}
if taken {
return nil, errors.New("slug 已被占用")
}
in.applyTo(&p)
// 用 map 更新,确保 false / 空串也会写入;勿用逗号拼接的 Select+Save(只会改 updated_at)
if err := s.db.Model(&model.SitePage{}).Where("id = ?", p.ID).Updates(map[string]interface{}{
"slug": p.Slug,
"title": p.Title,
"content": p.Content,
"excerpt": p.Excerpt,
"published": p.Published,
"show_in_sidebar": p.ShowInSidebar,
"sort_order": p.SortOrder,
"allow_comments": p.AllowComments,
}).Error; err != nil {
return nil, err
}
return &p, nil
}
// Delete 软删除单页
func (s *SitePageService) Delete(id uint) error {
result := s.db.Delete(&model.SitePage{}, id)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}