package fqapi import ( "encoding/json" "fanqie/internal/common" "fmt" "strings" "sync" ) var multiDetailLogger = common.NewLogger("multi_detail", "INFO", "") // singleDetail 获取单个书籍详情 func singleDetail(bookID string) (map[string]interface{}, error) { reqURL := BuildURL("https://reading.snssdk.com/reading/bookapi/detail/v/", "book_id="+bookID) resp, err := GorgonRequest(reqURL, map[string]interface{}{ "method": "GET", "headers": map[string]string{ "User-Agent": "com.dragon.read", }, }) if err != nil { return map[string]interface{}{}, err } respJSON, err := resp.JSON() if err != nil { return map[string]interface{}{}, err } if code, ok := respJSON["code"].(float64); ok && code == 0 { if data, ok := respJSON["data"].(map[string]interface{}); ok { delete(data, "content") return data, nil } } return map[string]interface{}{}, nil } // multiDetail 批量获取书籍详情 func multiDetail(bookIDs string) ([]map[string]interface{}, error) { reqURL := BuildURL("https://reading.snssdk.com/reading/bookapi/multi-detail/v/", "book_id="+bookIDs) resp, err := GorgonRequest(reqURL, map[string]interface{}{ "method": "GET", "headers": map[string]string{ "User-Agent": "com.dragon.read", }, }) if err != nil { // 返回空列表 count := len(strings.Split(bookIDs, ",")) result := make([]map[string]interface{}, count) for i := range result { result[i] = map[string]interface{}{} } return result, err } respJSON, err := resp.JSON() if err != nil { count := len(strings.Split(bookIDs, ",")) result := make([]map[string]interface{}, count) for i := range result { result[i] = map[string]interface{}{} } return result, err } if code, ok := respJSON["code"].(float64); ok && code == 0 { if data, ok := respJSON["data"].([]interface{}); ok && len(data) > 0 { result := make([]map[string]interface{}, len(data)) for i, item := range data { if itemMap, ok := item.(map[string]interface{}); ok { result[i] = itemMap } else { result[i] = map[string]interface{}{} } } return result, nil } } // 返回空列表 count := len(strings.Split(bookIDs, ",")) result := make([]map[string]interface{}, count) for i := range result { result[i] = map[string]interface{}{} } return result, nil } // chunkArray 分块数组 func chunkArray(arr []string, size int) [][]string { chunks := make([][]string, 0) for i := 0; i < len(arr); i += size { end := i + size if end > len(arr) { end = len(arr) } chunks = append(chunks, arr[i:end]) } return chunks } // HandleGetMultiDetail 处理批量获取书籍详情,详情数据基本不变,缓存 24 小时 func HandleGetMultiDetail(bookID interface{}) (string, error) { return withCache(fmt.Sprintf("multi_detail_%v", bookID), 86400, func() (string, error) { return fetchMultiDetail(bookID) }) } func fetchMultiDetail(bookID interface{}) (string, error) { if bookID == nil { return BuildErrorResponse("需要参数 book_id"), nil } var bookIDList []string switch v := bookID.(type) { case string: if v == "" { return BuildErrorResponse("需要参数 book_id"), nil } bookIDList = strings.Split(v, ",") case []string: bookIDList = v default: return BuildErrorResponse("book_id 参数格式错误"), nil } // 分块处理,每批100个 chunks := chunkArray(bookIDList, 100) // 并发请求 type result struct { data []map[string]interface{} err error } results := make([]result, len(chunks)) var wg sync.WaitGroup for i, chunk := range chunks { wg.Add(1) go func(idx int, ch []string) { defer wg.Done() data, err := multiDetail(strings.Join(ch, ",")) results[idx] = result{data: data, err: err} }(i, chunk) } wg.Wait() // 合并结果 res := make([]map[string]interface{}, 0) for _, r := range results { if r.err == nil { res = append(res, r.data...) } else { multiDetailLogger.Error(fmt.Sprintf("批量获取详情失败: %v", r.err)) } } // 处理下架书籍 offShelfBookIDs := make([]string, 0) offShelfIndexes := make([]int, 0) for i, item := range res { if status, ok := item["tomato_book_status"].(string); ok && status == "3" { if bid, ok := item["book_id"].(string); ok { offShelfBookIDs = append(offShelfBookIDs, bid) offShelfIndexes = append(offShelfIndexes, i) } } } // 单独获取下架书籍信息 if len(offShelfBookIDs) > 0 { type offshelfResult struct { data map[string]interface{} idx int } offshelfResults := make([]offshelfResult, len(offShelfBookIDs)) var wg2 sync.WaitGroup for i, bid := range offShelfBookIDs { wg2.Add(1) go func(idx int, bookID string, originalIdx int) { defer wg2.Done() data, _ := singleDetail(bookID) offshelfResults[idx] = offshelfResult{data: data, idx: originalIdx} }(i, bid, offShelfIndexes[i]) } wg2.Wait() // 更新结果 for _, r := range offshelfResults { if r.idx < len(res) { res[r.idx] = r.data } } } // 序列化结果 response := map[string]interface{}{ "code": 0, "message": "SUCCESS", "data": res, } resultBytes, _ := json.Marshal(response) return string(resultBytes), nil }