diff --git a/es/es_search.go b/es/es_search.go index 2a98514..35c2fa3 100644 --- a/es/es_search.go +++ b/es/es_search.go @@ -70,6 +70,49 @@ type ESBookResponse struct { Other map[string]interface{} `json:"other"` // 扩展字段,用于兼容未来新增字段 } +// ESBookResponseByPSI 用于返回给Java客户端的格式,ID为简单的int64 +type ESBookResponseByPSI struct { + ID int64 `json:"id"` + BookName string `json:"book_name"` + BookPic map[string]interface{} `json:"book_pic"` + BookPicS map[string]interface{} `json:"book_pic_s"` + BookPicB string `json:"book_pic_b"` + BookPicW map[string]interface{} `json:"book_pic_w"` + BookDefPic map[string]interface{} `json:"book_def_pic"` // 自制官图,临时用 + ISBN string `json:"isbn"` + Author string `json:"author"` + Category string `json:"category"` + Publisher string `json:"publisher"` + PublicationTime string `json:"publication_time"` + BindingLayout string `json:"binding_layout"` + FixPrice float64 `json:"fix_price"` + Content string `json:"content"` + IsSuit int `json:"is_suit"` + DaySale7 int `json:"day_sale_7"` + DaySale15 int `json:"day_sale_15"` + DaySale30 int `json:"day_sale_30"` + DaySale60 int `json:"day_sale_60"` + DaySale90 int `json:"day_sale_90"` + DaySale180 int `json:"day_sale_180"` + DaySale365 int `json:"day_sale_365"` + ThisYearSale int `json:"this_year_sale"` + LastYearSale int `json:"last_year_sale"` + TotalSale int `json:"total_sale"` + BuyCounts int64 `json:"buy_counts"` + SellCounts int64 `json:"sell_counts"` + BookPicObj map[string]interface{} `json:"book_pic_obj"` + BookPicObjS map[string]interface{} `json:"book_pic_obj_s"` + UpdateTime NumberOrString `json:"update_time"` + IsIllegal int `json:"is_illegal"` // 是否非法 示例 000000 + IsReturn int `json:"is_return"` // 是否为驳回 示例 0 否 1 是 + IsFilter string `json:"is_filter"` // 过滤字段 + PageCount NumberOrString `json:"page_count"` // 页数 + WordCount NumberOrString `json:"word_count"` // 字数 + BookFormat NumberOrString `json:"book_format"` // 多少开 + CatId request.CatIdObject `json:"cat_id"` // 类目 + Other map[string]interface{} `json:"other"` // 扩展字段,用于兼容未来新增字段 +} + // FlexibleString 处理可能是字符串或数组的字段 type FlexibleString struct { Value string @@ -210,6 +253,71 @@ func (book *ESBook) ConvertToResponse() ESBookResponse { } } +// ConvertToResponseByPsi 将ESBook转换为ESBookResponse,处理ID字段转换 +func (book *ESBook) ConvertToResponseByPsi() ESBookResponseByPSI { + bookPicMap := map[string]interface{}{ + "localPath": book.BookPic.LocalPath, + "pddPath": book.BookPic.PddPath, + } + bookPicSMap := map[string]interface{}{ + "localPath": book.BookPicS.LocalPath, + "pddResponse": book.BookPicS.PddResponse, + } + bookDefPic := map[string]interface{}{ + "localPath": book.BookDefPic.LocalPath, + "pddPath": book.BookDefPic.PddPath, + } + publicationTime := book.PublicationTime + if publicationTime == "" || publicationTime == "0" || publicationTime == "0000-00-00" { + publicationTime = time.Unix(0, 0).Format("2006-01") + } else { + if timeVal, err := strconv.ParseInt(publicationTime, 10, 64); err == nil { + publicationTime = strconv.FormatInt(timeVal-5364000000, 10) + } + } + return ESBookResponseByPSI{ + ID: book.ID, + BookName: book.BookName.Value, + BookPic: bookPicMap, + BookPicS: bookPicSMap, + BookPicB: book.BookPicB, + BookPicW: book.BookPicW, + BookDefPic: bookDefPic, + ISBN: book.ISBN, + Author: book.Author, + Category: book.Category, + Publisher: book.Publisher, + PublicationTime: publicationTime, + BindingLayout: book.BindingLayout, + FixPrice: float64(book.FixPrice), + Content: book.Content, + IsSuit: book.IsSuit, + DaySale7: book.DaySale7, + DaySale15: book.DaySale15, + DaySale30: book.DaySale30, + DaySale60: book.DaySale60, + DaySale90: book.DaySale90, + DaySale180: book.DaySale180, + DaySale365: book.DaySale365, + ThisYearSale: book.ThisYearSale, + LastYearSale: book.LastYearSale, + TotalSale: book.TotalSale, + BuyCounts: book.BuyCounts, + SellCounts: book.SellCounts, + BookPicObj: book.BookPicObj, + BookPicObjS: book.BookPicObjS, + UpdateTime: book.UpdateTime, + IsIllegal: book.IsIllegal, + IsReturn: book.IsReturn, + IsFilter: book.IsFilter, + PageCount: book.PageCount, + WordCount: book.WordCount, + BookFormat: book.BookFormat, + CatId: book.CatId, + Other: book.Other, + } +} + // 用于 book_pic type BookPicObj struct { LocalPath string `json:"localPath"` @@ -322,8 +430,10 @@ type AddBookFullRequest struct { } type ESSearchService struct { - ES *ESClient + ES *ESClient + SyncRedisByISBN func(isbn string, act string) error } + type NumberOrString string func (n *NumberOrString) UnmarshalJSON(data []byte) error { @@ -2344,6 +2454,110 @@ func (svc *ESSearchService) SearchBookByISBNHandler(c *gin.Context) { }) } +func (svc *ESSearchService) SearchBookByISBNHandlerToPsi(c *gin.Context) { + isbn := c.Query("isbn") + if isbn == "" { + log.Printf("[SearchBookByISBNHandler] 缺少 isbn 参数") + c.JSON(400, gin.H{"error": "缺少 isbn 参数"}) + return + } + + log.Printf("[SearchBookByISBNHandler] 查询 ISBN: %s", isbn) + + ctx := context.Background() + endpoint := c.FullPath() + + // Redis 查询(使用监控) + db1Client, err := redisClient.GetClientByName("db1") + if err == nil { + monitoredRedis := monitor.NewMonitoredRedisClient(db1Client, endpoint) + val, _, err := monitoredRedis.Get(ctx, isbn) + if err == nil && val != "" { + log.Printf("[SearchBookByISBNHandler] 从 Redis db1 查询到数据:%s", isbn) + var redisBook request.BookInfoByPsi + if err := json.Unmarshal([]byte(val), &redisBook); err == nil { + esBook := ConvertRedisBookToESBookByPsi(&redisBook) + if esBook != nil { + responseData := esBook.ConvertToResponseByPsi() + c.JSON(200, gin.H{ + "data": responseData, + }) + return + } + } else { + log.Printf("[SearchBookByISBNHandler] Redis 数据解析失败:%v", err) + } + } + } + + // ES 查询(使用监控) + query := map[string]interface{}{ + "query": map[string]interface{}{ + "term": map[string]interface{}{ + "isbn": isbn, + }, + }, + "_source": true, + } + + body, err := json.Marshal(query) + if err != nil { + log.Printf("[SearchBookByISBNHandler] 构建查询 JSON 失败:%v", err) + c.JSON(500, gin.H{"error": "构建查询失败"}) + return + } + + req := esapi.SearchRequest{ + Index: []string{ESIndex}, + Body: bytes.NewReader(body), + TrackTotalHits: true, + Pretty: true, + } + + // 创建监控客户端并执行查询 + monitoredES := monitor.NewMonitoredESClient(svc.ES.Client, endpoint) + resp, duration, err := monitoredES.Search(ctx, &req) + + log.Printf("[SearchBookByISBNHandler] ES 查询耗时:%dms", duration.Milliseconds()) + + if err != nil { + log.Printf("[SearchBookByISBNHandler] ES 查询失败:%v", err) + c.JSON(500, gin.H{"error": "ES 查询失败:" + err.Error()}) + return + } + defer resp.Body.Close() + + if resp.IsError() { + log.Printf("[SearchBookByISBNHandler] ES 返回错误:%s", resp.String()) + c.JSON(500, gin.H{"error": "ES 返回错误:" + resp.String()}) + return + } + + var parsed esHitsWrapper + if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil { + log.Printf("[SearchBookByISBNHandler] 解析 ES 响应失败:%v", err) + c.JSON(500, gin.H{"error": "解析响应失败"}) + return + } + + var result *ESBook + if len(parsed.Hits.Hits) > 0 { + result = &parsed.Hits.Hits[0].Source + } + + if result == nil { + c.JSON(200, gin.H{ + "data": nil, + }) + return + } + + responseData := result.ConvertToResponseByPsi() + c.JSON(200, gin.H{ + "data": responseData, + }) +} + // ConvertKongfzToESBook 将第三方接口返回的数据转换为 ESBook 结构 func ConvertKongfzToESBook(apiBook *kongfz.BookResponse) *ESBook { if apiBook == nil || apiBook.Data.ISBN == "" { @@ -3512,6 +3726,10 @@ func (svc *ESSearchService) AddBookToES(ctx context.Context, req *ESBook) (*ESBo return nil, fmt.Errorf("ES返回错误: %s", res.String()) } + // 同步 Redis + if svc.SyncRedisByISBN != nil { + _ = svc.SyncRedisByISBN(req.ISBN, "update") + } return &newBook, nil } @@ -4152,3 +4370,77 @@ func ConvertRedisBookToESBook(redisBook *request.BookInfo) *ESBook { IsFilter: "000000", } } + +// ConvertRedisBookToESBookByPsi 将 Redis 中的 BookInfo 转换为 ESBook +func ConvertRedisBookToESBookByPsi(redisBook *request.BookInfoByPsi) *ESBook { + if redisBook == nil || redisBook.Isbn == "" { + return nil + } + + // 构建图片对象 + carouselUrls := []string{} + if len(redisBook.ImageObject.CarouselUrlArray) > 0 { + carouselUrls = redisBook.ImageObject.CarouselUrlArray + } + + liveShootingUrls := []string{} + if len(redisBook.ImageObject.DetailUrlObject.LiveShootingUrl) > 0 { + liveShootingUrls = redisBook.ImageObject.DetailUrlObject.LiveShootingUrl + } + + // 提取第一张轮播图作为 book_pic + bookPicPath := "" + if len(carouselUrls) > 0 { + bookPicPath = carouselUrls[0] + } + + // 提取第一张实拍图作为 book_pic_s + bookPicSResponse := "" + if len(liveShootingUrls) > 0 { + bookPicSResponse = liveShootingUrls[0] + } + + return &ESBook{ + ISBN: redisBook.Isbn, + BookName: FlexibleString{Value: redisBook.BookName}, + Author: redisBook.Author, + Publisher: redisBook.Publishing, + PublicationTime: redisBook.PublicationDate, + BindingLayout: redisBook.Binding, + PageCount: NumberOrString(strconv.FormatInt(redisBook.PagesCount, 10)), + WordCount: NumberOrString(strconv.FormatInt(redisBook.WordsCount, 10)), + BookFormat: NumberOrString(strconv.FormatInt(redisBook.Format, 10)), + FixPrice: Float64OrString(redisBook.Price), + BookPic: BookPicObj{ + LocalPath: "", + PddPath: bookPicPath, + }, + BookPicS: BookPicSObj{ + LocalPath: "", + PddResponse: bookPicSResponse, + }, + BookDefPic: BookDefPicObj{ + LocalPath: "", + PddPath: redisBook.ImageObject.DefaultImageUrl, + }, + BookPicB: redisBook.ImageObject.WhiteBackgroundUrl, + CatId: redisBook.CatIdObject, + // 销量等字段默认为 0 + DaySale7: 0, + DaySale15: 0, + DaySale30: 0, + DaySale60: 0, + DaySale90: 0, + DaySale180: 0, + DaySale365: 0, + ThisYearSale: 0, + LastYearSale: 0, + TotalSale: 0, + BuyCounts: 0, + SellCounts: 0, + IsSuit: redisBook.IsSuit, + IsIllegal: 0, + IsReturn: 0, + IsFilter: "000000", + } +} diff --git a/main.go b/main.go index 2b88169..8313ba4 100644 --- a/main.go +++ b/main.go @@ -311,6 +311,8 @@ func main() { r.GET("/api/es/searchByISBNLike", esService.SearchBooksHandler) //监控 // ISBN 精确搜索 r.GET("/api/es/searchByISBN", esService.SearchBookByISBNHandler) //监控 + // ISBN 精确搜索 为psi提供 + r.GET("/api/es/searchByISBNtoPsi", esService.SearchBookByISBNHandlerToPsi) //监控 // 书名搜索 r.GET("/api/es/searchByBookName", esService.SearchBookByBookNameHandler) // 全字段搜索 @@ -321,6 +323,7 @@ func main() { //------------------------------------------------------------------------ // 初始化控制器 新 bookSearchService := service.NewBookService(esClient) + esService.SyncRedisByISBN = bookSearchService.SyncRedisByISBN bookController := controller.NewBookController(bookSearchService) // 根据条件查询 ES 图书信息 r.GET("/api/es/getBookBaseInfoES", bookController.SearchBookBaseInfoHandler) diff --git a/model/request/book.go b/model/request/book.go index 963bb5f..e2bc92f 100644 --- a/model/request/book.go +++ b/model/request/book.go @@ -70,6 +70,23 @@ type BookInfo struct { CatIdObject CatIdObject `json:"cat_id"` // 分类 } +// BookInfoByPsi 书籍信息结构 +type BookInfoByPsi struct { + Isbn string `json:"isbn"` // ISBN + BookName string `json:"book_name"` // 书名 + Author string `json:"author"` // 作者 + Publishing string `json:"publishing"` // 出版社 + PublicationDate string `json:"publication_date"` // 出版时间 + Binding string `json:"binding"` // 装帧 + PagesCount int64 `json:"pages_count"` // 页数 + WordsCount int64 `json:"words_count"` // 字数 + Format int64 `json:"format"` // 开本 + ImageObject *ImageObject `json:"image_object"` // 图片 + Price int64 `json:"price"` // 售价 + CatIdObject CatIdObject `json:"cat_id"` // 分类 + IsSuit int `json:"is_suit"` +} + // ImageObject 图片对象结构 type ImageObject struct { CarouselUrlArray []string `json:"carousel_url_array"` // 轮播图