- HTML 提取至 templates/status.html、templates/review.html(go:embed 内嵌) - status.go 1022→271 行、review.go 1281→438 行,动态数据全部经 html/template 按上下文自动转义 - 评论循环/作者徽章/贴纸/图片条件渲染翻译为模板 range/if;emojiMap 以 template.JS 注入 - 与旧版渲染输出实测对比:22 条评论结构完全一致;差异仅为 JS 上下文安全转义(& 等)与 html/template 剥除 CSS 注释,均无功能影响 - 其余文件为 gofmt 顺手格式化
221 lines
5.1 KiB
Go
221 lines
5.1 KiB
Go
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
|
|
}
|