此提交完成了以下核心功能与优化: 1. 新增站点公告全流程支持:包含公告模型、CRUD接口、管理后台页面、公告详情页与首页展示 2. 实现评论楼层化与回复嵌套:重构评论系统为树状结构,支持回复评论与层级限制 3. 添加用户在线状态追踪:异步更新用户最后活跃时间并做SQL限频 4. 优化用户体验:移动端浮动操作条、统一板块标签样式、用户页签到积分展示、注册即登录优化 5. 其他细节优化:SEO站点地图新增公告页、评论分页与统计、登录/注册表单增强等
150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
|
||
"github.com/freefire/jiang13-bbs/model"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// 公告标签允许使用的预设色(服务端白名单,禁止透传任意颜色值)
|
||
var allowedTagColors = map[string]bool{
|
||
"blue": true, "green": true, "orange": true,
|
||
"red": true, "purple": true, "gray": true,
|
||
}
|
||
|
||
// AnnouncementService 站点公告服务
|
||
type AnnouncementService struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
func NewAnnouncementService(db *gorm.DB) *AnnouncementService {
|
||
return &AnnouncementService{db: db}
|
||
}
|
||
|
||
// AnnouncementInput 创建/更新公告的入参
|
||
type AnnouncementInput struct {
|
||
Title string `json:"title"`
|
||
Content string `json:"content"`
|
||
Tag string `json:"tag"`
|
||
TagColor string `json:"tag_color"`
|
||
Published *bool `json:"published"`
|
||
}
|
||
|
||
// normalize 清洗并校验入参,返回可直接落库的字段
|
||
func (in *AnnouncementInput) normalize(existing *model.Announcement) error {
|
||
in.Title = strings.TrimSpace(in.Title)
|
||
if in.Title == "" {
|
||
return errors.New("标题不能为空")
|
||
}
|
||
if len([]rune(in.Title)) > 200 {
|
||
return errors.New("标题不能超过 200 字")
|
||
}
|
||
in.Content = strings.TrimSpace(in.Content)
|
||
if in.Content == "" {
|
||
return errors.New("内容不能为空")
|
||
}
|
||
|
||
in.Tag = strings.TrimSpace(in.Tag)
|
||
if in.Tag == "" {
|
||
in.Tag = "公告"
|
||
}
|
||
if len([]rune(in.Tag)) > 32 {
|
||
return errors.New("标签不能超过 32 字")
|
||
}
|
||
|
||
in.TagColor = strings.ToLower(strings.TrimSpace(in.TagColor))
|
||
if in.TagColor == "" {
|
||
if existing != nil && existing.TagColor != "" {
|
||
in.TagColor = existing.TagColor
|
||
} else {
|
||
in.TagColor = "blue"
|
||
}
|
||
}
|
||
if !allowedTagColors[in.TagColor] {
|
||
return errors.New("不支持的标签颜色")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// applyTo 把入参写入模型(Published 为指针以区分"未传"与"显式 false")
|
||
func (in *AnnouncementInput) applyTo(a *model.Announcement) {
|
||
a.Title = in.Title
|
||
a.Content = in.Content
|
||
a.Tag = in.Tag
|
||
a.TagColor = in.TagColor
|
||
if in.Published != nil {
|
||
a.Published = *in.Published
|
||
}
|
||
}
|
||
|
||
// ListPublished 已发布公告(首页右栏 / 公开列表),按发布时间倒序
|
||
func (s *AnnouncementService) ListPublished(limit int) ([]model.Announcement, error) {
|
||
if limit <= 0 {
|
||
limit = 5
|
||
}
|
||
var list []model.Announcement
|
||
err := s.db.Where("published = ?", true).
|
||
Order("created_at DESC, id DESC").Limit(limit).Find(&list).Error
|
||
return list, err
|
||
}
|
||
|
||
// GetPublished 取单条已发布公告(详情页);草稿/不存在返回 gorm.ErrRecordNotFound
|
||
func (s *AnnouncementService) GetPublished(id uint) (*model.Announcement, error) {
|
||
var a model.Announcement
|
||
err := s.db.Where("published = ?", true).First(&a, id).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &a, nil
|
||
}
|
||
|
||
// ListAll 管理后台:含草稿,按更新时间倒序
|
||
func (s *AnnouncementService) ListAll() ([]model.Announcement, error) {
|
||
var list []model.Announcement
|
||
err := s.db.Order("created_at DESC, id DESC").Limit(100).Find(&list).Error
|
||
return list, err
|
||
}
|
||
|
||
// Create 创建公告
|
||
func (s *AnnouncementService) Create(in *AnnouncementInput) (*model.Announcement, error) {
|
||
if err := in.normalize(nil); err != nil {
|
||
return nil, err
|
||
}
|
||
a := &model.Announcement{Published: true}
|
||
in.applyTo(a)
|
||
if err := s.db.Create(a).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return a, nil
|
||
}
|
||
|
||
// Update 更新公告
|
||
func (s *AnnouncementService) Update(id uint, in *AnnouncementInput) (*model.Announcement, error) {
|
||
var a model.Announcement
|
||
if err := s.db.First(&a, id).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
if err := in.normalize(&a); err != nil {
|
||
return nil, err
|
||
}
|
||
in.applyTo(&a)
|
||
if err := s.db.Save(&a).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &a, nil
|
||
}
|
||
|
||
// Delete 删除公告(软删除)
|
||
func (s *AnnouncementService) Delete(id uint) error {
|
||
result := s.db.Delete(&model.Announcement{}, id)
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return nil
|
||
}
|