Files
jiang13-bbs/backend/service/announcement.go
freefire 996a870015 feat: 新增站点公告系统与多项体验优化
此提交完成了以下核心功能与优化:
1. 新增站点公告全流程支持:包含公告模型、CRUD接口、管理后台页面、公告详情页与首页展示
2. 实现评论楼层化与回复嵌套:重构评论系统为树状结构,支持回复评论与层级限制
3. 添加用户在线状态追踪:异步更新用户最后活跃时间并做SQL限频
4. 优化用户体验:移动端浮动操作条、统一板块标签样式、用户页签到积分展示、注册即登录优化
5. 其他细节优化:SEO站点地图新增公告页、评论分页与统计、登录/注册表单增强等
2026-09-13 21:38:01 +08:00

150 lines
3.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"
"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
}