🔧 chore: 初始化番茄小说 API 项目结构
新增项目基础框架,包含核心 API 实现、算法模块、HTTP 处理器等完整功能模块
This commit is contained in:
commit
6981148727
107 files changed
+12463
No files matched your search
@@ -0,0 +1,215 @@
|
||||
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 处理批量获取书籍详情
|
||||
func HandleGetMultiDetail(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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user