feat: 添加每日签到功能,新增帖子排序与社区面板优化

本次提交完成了多个核心功能迭代:
1.  新增用户每日签到系统,包含签到状态查询、连续天数统计、积分累计逻辑
2.  新增"新帖子"排序选项,替换原有的latest排序为新评论排序
3.  重构首页信息流布局,优化移动端与桌面端展示样式
4.  新增最新评论、最新注册用户模块,完善社区面板数据展示
5.  新增静态友情链接配置模块
6.  提取通用时间格式化工具函数,统一全站相对时间展示
7.  补充数据库签到模型与后端API接口,完善前后端交互链路
This commit is contained in:
2026-09-13 06:03:40 +08:00
parent 95921d24fa
commit 9bae6f2513
19 changed files with 790 additions and 280 deletions

View File

@@ -1,6 +1,7 @@
package service
import (
"strings"
"time"
"github.com/freefire/jiang13-bbs/model"
@@ -39,12 +40,51 @@ type BoardCount struct {
PostCount int64 `json:"post_count"`
}
// LatestCommentItem 首页"最新评论"条目(纯文本摘要 + 所属帖子)
type LatestCommentItem struct {
ID uint `json:"id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
User struct {
ID uint `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
} `json:"user"`
Post struct {
ID uint `json:"id"`
Title string `json:"title"`
} `json:"post"`
}
// NewUserItem 最新注册成员
type NewUserItem struct {
ID uint `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
CreatedAt time.Time `json:"created_at"`
}
// OverviewData 首页聚合数据
type OverviewData struct {
Stats OverviewStats `json:"stats"`
Hot []PostListItem `json:"hot"`
ActiveUsers []ActiveUser `json:"active_users"`
Boards []BoardCount `json:"boards"`
Stats OverviewStats `json:"stats"`
Hot []PostListItem `json:"hot"`
ActiveUsers []ActiveUser `json:"active_users"`
Boards []BoardCount `json:"boards"`
LatestComments []LatestCommentItem `json:"latest_comments"`
NewUsers []NewUserItem `json:"new_users"`
Checkin *CheckinStatus `json:"checkin,omitempty"`
}
// truncateText 折叠空白并按 rune 截断(评论为纯文本,无 HTML 注入面)
func truncateText(s string, n int) string {
s = strings.Join(strings.Fields(s), " ")
r := []rune(s)
if len(r) > n {
return string(r[:n]) + "…"
}
return s
}
const activeUserWindow = 14 * 24 * time.Hour
@@ -52,9 +92,11 @@ const activeUserWindow = 14 * 24 * time.Hour
// Get 聚合首页数据。hot 的 liked 态由 handler 层按当前用户填充。
func (s *OverviewService) Get() (*OverviewData, error) {
data := &OverviewData{
Hot: []PostListItem{},
ActiveUsers: []ActiveUser{},
Boards: []BoardCount{},
Hot: []PostListItem{},
ActiveUsers: []ActiveUser{},
Boards: []BoardCount{},
LatestComments: []LatestCommentItem{},
NewUsers: []NewUserItem{},
}
now := time.Now()
@@ -163,5 +205,65 @@ LIMIT 5`
data.Boards = append(data.Boards, BoardCount{Board: b, PostCount: countMap[b.ID]})
}
// 最新评论:JOIN 未封禁用户与已发布帖,一条 SQL 取齐
type latestCommentRow struct {
ID uint
Content string
CreatedAt time.Time
UID uint
Username string
Nickname string
Avatar string
PID uint
Title string
}
var commentRows []latestCommentRow
commentSQL := `
SELECT c.id, c.content, c.created_at,
u.id AS uid, u.username AS username, u.nickname AS nickname, u.avatar AS avatar,
p.id AS pid, p.title AS title
FROM comments c
JOIN users u ON u.id = c.user_id AND u.banned = ? AND u.deleted_at IS NULL
JOIN posts p ON p.id = c.post_id AND p.status = ? AND p.deleted_at IS NULL
WHERE c.status = ? AND c.deleted_at IS NULL
ORDER BY c.created_at DESC
LIMIT 6`
if err := s.db.Raw(commentSQL,
false, model.ContentStatusPublished, model.ContentStatusPublished).
Scan(&commentRows).Error; err != nil {
return nil, err
}
for _, r := range commentRows {
item := LatestCommentItem{
ID: r.ID,
Content: truncateText(r.Content, 60),
CreatedAt: r.CreatedAt,
}
item.User.ID = r.UID
item.User.Username = r.Username
item.User.Nickname = r.Nickname
item.User.Avatar = r.Avatar
item.Post.ID = r.PID
item.Post.Title = r.Title
data.LatestComments = append(data.LatestComments, item)
}
// 最新注册成员
var newUsers []model.User
if err := s.db.Select("id, username, nickname, avatar, created_at").
Where("banned = ?", false).
Order("created_at DESC").Limit(8).Find(&newUsers).Error; err != nil {
return nil, err
}
for _, u := range newUsers {
data.NewUsers = append(data.NewUsers, NewUserItem{
ID: u.ID,
Username: u.Username,
Nickname: u.Nickname,
Avatar: u.Avatar,
CreatedAt: u.CreatedAt,
})
}
return data, nil
}