Files
谢宇宁andClaude Opus 5.5 0fbdba5ccf Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:47:47 +07:00

285 lines
6.8 KiB
Go

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
}