package handler import ( "net/http" "strconv" "strings" "sync" "git.iioio.com/freefire/jiang13-forum/model" "git.iioio.com/freefire/jiang13-forum/service" "github.com/gin-gonic/gin" ) // homeBootPayload 与前端 HomeBootPayload 对齐(Vike +data 冷启动灌缓存)。 type homeBootPayload struct { BoardID uint `json:"board_id"` Sort string `json:"sort"` Keyword string `json:"keyword"` Tag string `json:"tag"` Author string `json:"author"` TitleOnly bool `json:"title_only"` Posts []service.PostListItem `json:"posts"` PostTotal int64 `json:"post_total"` Page int `json:"page"` Boards []service.BoardWithStats `json:"boards"` Stats homeBootStats `json:"stats"` RecentComments []service.RecentCommentItem `json:"recent_comments"` RecentUsers []service.RecentUserItem `json:"recent_users"` Tags []service.TagCount `json:"tags"` // omitempty:侧栏关闭或未拉取时不写字段,避免 SPA 把 [] 当成有效会话快照 Showcase []service.CommunityShowcaseItem `json:"showcase,omitempty"` Pages []service.SitePageSummary `json:"pages"` Limits service.ForumLimitsPublic `json:"limits"` Branding service.SiteBranding `json:"branding"` User *model.UserSelf `json:"user"` UnreadMessages int `json:"unread_messages"` CheckIn *service.CheckInStatus `json:"check_in"` } type homeBootStats struct { Users int64 `json:"users"` Posts int64 `json:"posts"` Boards int64 `json:"boards"` Comments int64 `json:"comments"` } // APIHomeBoot 首页/板块/壳层聚合数据(Vike +data 取数与 SPA 冷启动预热共用,与前端 HomeBootPayload 对齐)。 // 并发聚合:帖子列表、板块、统计、侧栏 widget、登录态、签到等。 func (h *Handlers) APIHomeBoot(c *gin.Context) { boardID := uint(parseUintOrZero(c.Query("board_id"))) boot, err := h.gatherHomeBoot(c, boardID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取数据失败"}) return } c.JSON(http.StatusOK, boot) } // gatherHomeBoot 纯数据聚合(JSON),不做任何 HTML 渲染。 func (h *Handlers) gatherHomeBoot(c *gin.Context, boardID uint) (*homeBootPayload, error) { limits := h.Settings.PublicLimits() brand := h.Settings.SiteBranding() brand.SiteURL = h.publicBaseURL(c) defSort := h.Settings.DefaultFeedSort() sort := strings.TrimSpace(c.Query("sort")) if sort == "" { sort = defSort } keyword := strings.TrimSpace(c.Query("keyword")) tag := strings.TrimSpace(c.Query("tag")) author := strings.TrimSpace(c.Query("author")) titleOnly := c.Query("title_only") == "1" || strings.EqualFold(c.Query("title_only"), "true") if tag != "" { keyword = "" author = "" titleOnly = false } pageSize := h.Settings.PageSizeDefault() out := &homeBootPayload{ BoardID: boardID, Sort: sort, Keyword: keyword, Tag: tag, Author: author, TitleOnly: titleOnly, Page: 1, Posts: []service.PostListItem{}, Boards: []service.BoardWithStats{}, Pages: []service.SitePageSummary{}, Limits: limits, Branding: brand, } var ( postsErr, boardsErr error wg sync.WaitGroup ) wg.Add(1) go func() { defer wg.Done() q := service.PostListQuery{ BoardID: boardID, Page: 1, Size: pageSize, Sort: sort, Keyword: keyword, Tag: tag, Author: author, TitleOnly: titleOnly, ViewerID: h.currentUserID(c), ViewerIsAdmin: h.isAdmin(c), } items, total, err := h.Post.ListItems(q) if err != nil { postsErr = err return } if items == nil { items = []service.PostListItem{} } if h.Badge != nil { users := make([]*model.User, 0, len(items)) for i := range items { if items[i].User.ID > 0 { users = append(users, &items[i].User) } } h.Badge.AttachBadgeSummaries(users, 2) } out.Posts = items out.PostTotal = total }() wg.Add(1) go func() { defer wg.Done() boards, err := h.Board.ListWithStats() if err != nil { boardsErr = err return } if boards == nil { boards = []service.BoardWithStats{} } out.Boards = boards }() wg.Add(1) go func() { defer wg.Done() var users, posts, boardsN, comments int64 model.DB.Model(&model.User{}).Count(&users) model.DB.Model(&model.Post{}).Where("status = ?", model.ContentStatusPublished).Count(&posts) model.DB.Model(&model.Board{}).Count(&boardsN) model.DB.Model(&model.Comment{}).Where("status = ?", model.ContentStatusPublished).Count(&comments) out.Stats = homeBootStats{Users: users, Posts: posts, Boards: boardsN, Comments: comments} }() wg.Add(1) go func() { defer wg.Done() pages, err := h.SitePage.ListPublished() if err != nil || pages == nil { pages = []service.SitePageSummary{} } out.Pages = pages }() widgetEnabled := map[string]bool{} for _, w := range limits.AsideWidgets { widgetEnabled[w.ID] = w.Enabled } if widgetEnabled[service.AsideWidgetRecentComments] { wg.Add(1) go func() { defer wg.Done() items, err := h.Comment.ListRecentPublic(8) if err != nil || items == nil { items = []service.RecentCommentItem{} } out.RecentComments = items }() } else { out.RecentComments = []service.RecentCommentItem{} } if widgetEnabled[service.AsideWidgetRecentUsers] { wg.Add(1) go func() { defer wg.Done() items, err := h.User.ListRecentRegistered(8) if err != nil || items == nil { items = []service.RecentUserItem{} } out.RecentUsers = items }() } else { out.RecentUsers = []service.RecentUserItem{} } if widgetEnabled[service.AsideWidgetTagCloud] { wg.Add(1) go func() { defer wg.Done() tags, err := h.Post.PopularTags(40) if err != nil || tags == nil { tags = []service.TagCount{} } out.Tags = tags }() } else { out.Tags = []service.TagCount{} } if widgetEnabled[service.AsideWidgetShowcase] && h.Community != nil { wg.Add(1) go func() { defer wg.Done() items, err := h.Community.ListShowcase(c.Request.Host) if err != nil { // 失败不写 showcase,让前端自行请求,避免假空粘死 return } if items == nil { items = []service.CommunityShowcaseItem{} } out.Showcase = items }() } uid := h.currentUserID(c) if uid > 0 { wg.Add(1) go func() { defer wg.Done() user, err := h.User.GetByID(uid) if err != nil || user == nil { return } view := user.ToSelf() if h.Badge != nil { _ = h.Badge.EvaluateAuto(user.ID) if badges, bErr := h.Badge.ListUserBadges(user.ID); bErr == nil { view.Badges = service.BadgeViews(badges, 0) } } out.User = &view }() if h.Message != nil { wg.Add(1) go func() { defer wg.Done() total, _, _, err := h.Message.UnreadCounts(uid) if err == nil { out.UnreadMessages = int(total) } }() } if h.Points != nil { wg.Add(1) go func() { defer wg.Done() st, err := h.Points.GetCheckInStatus(uid) if err == nil { out.CheckIn = &st } }() } } wg.Wait() if postsErr != nil { return nil, postsErr } if boardsErr != nil { return nil, boardsErr } return out, nil } func parseUintOrZero(s string) uint64 { n, _ := strconv.ParseUint(s, 10, 64) return n }