package web import ( "fmt" "net/url" "strings" "videoweb/app/service/playletser" "github.com/gin-gonic/gin" ) type searchTrend struct { Rank int Title string Heat string Badge string } // SearchMobile 移动端搜索入口。 // 热搜 + 猜你喜欢:GET /api/app/playlet/hot-like // 联想:前端调用 GET /api/web/search/suggest → 上游 /api/app/playlet/search/suggest // 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_hot_like func SearchMobile(c *gin.Context) { if hasLegacySearchQuery(c) { NotFound(c) return } raw := playletser.FetchHotLikeOrEmpty(c) trends := hotSearchTrends(raw.HotSearch) recommended := raw.GuessLike if len(recommended) > 15 { recommended = recommended[:15] } HTMLOK(c, "searchMobile.html", gin.H{ "title": "搜索 - 黄果短剧", "query": searchQueryFromRequest(c), "trends": trends, "recommended": recommended, "hotLikeDebug": gin.H{ "path": "/api/app/playlet/hot-like", "response": raw, }, }) } func hotSearchTrends(items []playletser.PlayletHotSearchItem) []searchTrend { out := make([]searchTrend, 0, len(items)) for i, item := range items { keyword := strings.TrimSpace(item.Keyword) if keyword == "" { continue } rank := i + 1 badge := "" switch rank { case 1: badge = "热" case 2: badge = "新" } out = append(out, searchTrend{ Rank: rank, Title: keyword, Heat: formatSearchHeat(item.Count), Badge: badge, }) } return out } func formatSearchHeat(n int64) string { if n <= 0 { return "0" } if n < 10000 { return fmt.Sprintf("%d", n) } v := float64(n) / 10000 if v >= 100 { return fmt.Sprintf("%.0f 万", v) } return fmt.Sprintf("%.1f 万", v) } // Search 搜索结果页:GET /api/app/playlet/search。 // 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_search // 无结果时「大家都在看」:GET /api/app/playlet/all/list(取 6 条最热)。 func Search(c *gin.Context) { if hasLegacySearchQuery(c) { NotFound(c) return } keyword := searchQueryFromRequest(c) pageStr := strings.TrimSpace(c.Param("page")) q := playletser.ParseSearchQuery(keyword, pageStr) list := []playletser.CommonData(nil) recommended := []playletser.CommonData(nil) total := int64(0) pageNum, pageSize := q.PageNum, q.PageSize var searchDebug any var watchingDebug any if q.Keyword != "" { raw := playletser.FetchSearchOrEmpty(c, q) searchDebug = gin.H{ "path": "/api/app/playlet/search", "keyword": q.Keyword, "pageNum": q.PageNum, "pageSize": q.PageSize, "response": raw, } if raw != nil { list = raw.List total = raw.TotalCount if raw.PageNum > 0 { pageNum = raw.PageNum } if raw.PageSize > 0 { pageSize = raw.PageSize } if strings.TrimSpace(raw.Keyword) != "" { keyword = raw.Keyword } } } else { searchDebug = gin.H{ "path": "/api/app/playlet/search", "keyword": "", "note": "empty keyword, skip upstream", } } if len(list) == 0 { watchingQ := playletser.AllListQuery{ PageNum: 1, PageSize: 6, SortType: 2, // 最热 UpdateStatus: 0, // 全部 } watching := playletser.FetchAllListOrEmpty(c, watchingQ) watchingDebug = gin.H{ "path": "/api/app/playlet/all/list", "pageNum": watchingQ.PageNum, "pageSize": watchingQ.PageSize, "sortType": watchingQ.SortType, "response": watching, } if watching != nil { recommended = watching.List if len(recommended) > 6 { recommended = recommended[:6] } } } HTMLOK(c, "search.html", gin.H{ "title": "搜索 - 黄果短剧", "query": keyword, "hasResults": len(list) > 0, "resultCount": total, "works": list, "recommended": recommended, "pageNum": pageNum, "pageSize": pageSize, "pagination": playletser.BuildSearchPagination(q, total), "searchDebug": searchDebug, "watchingDebug": watchingDebug, }) } type searchSuggestFeature struct { Title string `json:"title"` Cover string `json:"cover"` URL string `json:"url"` Meta string `json:"meta"` } type searchSuggestItem struct { Title string `json:"title"` Kind string `json:"kind"` Query string `json:"query"` URL string `json:"url"` } // SearchSuggest PC 顶栏输入联想:GET /api/web/search/suggest?keyword= // 上游:GET /api/app/playlet/search/suggest → proto.PlayletSearchSuggestResp // 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_search_suggest func SearchSuggest(c *gin.Context) { keyword := strings.TrimSpace(c.Query("keyword")) if keyword == "" { keyword = strings.TrimSpace(c.Query("q")) } if keyword == "" { c.JSON(200, gin.H{"code": 200, "keyword": "", "feature": nil, "items": []searchSuggestItem{}}) return } raw := playletser.FetchSearchSuggestOrEmpty(c, keyword) if kw := strings.TrimSpace(raw.Keyword); kw != "" { keyword = kw } var feature *searchSuggestFeature items := make([]searchSuggestItem, 0, len(raw.List)+1) for _, item := range raw.List { title := playletser.PlayletDisplayName(item.PlayName, item.Title) if title == "" { continue } href := playletser.PlayPath(item.Slug, item.ID) if feature == nil { feature = &searchSuggestFeature{ Title: title, Cover: strings.TrimSpace(item.Cover), URL: href, Meta: buildSearchSuggestMeta(item), } continue } items = append(items, searchSuggestItem{ Title: title, Kind: "短剧 剧集", Query: title, URL: href, }) } items = append(items, searchSuggestItem{ Title: keyword, Kind: "搜索", Query: keyword, URL: playletser.SearchResultsURL(keyword), }) c.JSON(200, gin.H{ "code": 200, "keyword": keyword, "feature": feature, "items": items, }) } func buildSearchSuggestMeta(item playletser.CommonData) string { parts := make([]string, 0, 4) for _, name := range item.ThemeNames { name = strings.TrimSpace(name) if name != "" { parts = append(parts, name) } } for _, name := range item.TagNames { name = strings.TrimSpace(name) if name != "" && len(parts) < 2 { parts = append(parts, name) } } if item.TotalEpisode > 0 { parts = append(parts, fmt.Sprintf("全 %d 集", item.TotalEpisode)) } else if item.UpdateStatus == 1 { parts = append(parts, "连载中") } if anchor := strings.TrimSpace(item.Anchor); anchor != "" && anchor != "暂无" && anchor != "原创" { parts = append(parts, "主演 "+anchor) } return strings.Join(parts, " ") } func hasLegacySearchQuery(c *gin.Context) bool { query := c.Request.URL.Query() return query.Has("q") || query.Has("page") } func searchQueryFromRequest(c *gin.Context) string { q := strings.TrimSpace(c.Param("q")) if q == "" { return "" } if decoded, err := url.PathUnescape(q); err == nil { return strings.TrimSpace(decoded) } return q }