✨ feat(api): 新增榜单书架历史与搜索续传并重构正文缓存链路

- 新增 /rank 免签名榜单接口(多域名容错)、/bookshelf 与 /read-history(sessionid 登录态,书架支持分组过滤)
- 搜索重写:翻页透传上游 passback/search_id/next_offset/corrected_query 并按 book_id 去重,15~20 位纯数字关键词直查详情(ID 直搜)
- 删除 /login/check 登录校验端点(login.go、路由与文档同步移除,sessionid 说明改为直接供书架/历史使用)
- 正文链路重构:singleflight 合并同章节并发回源防击穿,缓存与解析解耦统一在出口处理,decodeContentBody 按 crypt_status 分支并兜底 500003 明文
- 缓存层:SQL 持久层 TTL 与业务 TTL 解耦(不可变内容过期后由 SQL 回填),动态短缓存只走 Redis,withCache 合并并发 miss
- 密钥池:KeyTs 随正文请求上传降低密钥不匹配重试,密钥超 24 小时异步刷新并防刷新风暴,last_used_at 批量落库
- 签名补 X-Helios(与 X-Ladon 同值,对齐书源实现);设备注册版本号统一走 ClientVersionCode/Name
- 分类 landing 透传新版标签 selected_items;multi-detail 批量详情供书架/历史复用
- README 与 openapi.yaml 补充新接口文档
This commit is contained in:
chenxin committed 2026-10-12 14:49:14 +08:00
1 parent 8ca372050b
commit 84bd3238ab
28 files changed
+1180 -270

No files matched your search

+17 -15
View File
@@ -12,15 +12,13 @@ import (
"fmt"
"math/rand"
"regexp"
"sort"
"strings"
"time"
)
// KeyInfo 密钥信息
type KeyInfo struct {
Key string `json:"key"`
Ver int `json:"ver"`
Ts int64 `json:"ts"` // 密钥注册时间戳(key_register_ts),随正文请求上传可降低密钥不匹配重试
}
var (
@@ -154,25 +152,22 @@ func GetKey(header string) (*KeyInfo, error) {
keyLogger.Info("正在获取密钥...")
// 构建参数
// 构建参数(form 编码:机型含空格等字符时 URL 与签名一致才不会注册失败)
params := []string{
"aid=1967",
"app_name=novelapp",
"channel=0",
"device_platform=android",
"os_version=0",
"version_code=5.8.9.32",
"version_code=" + ClientVersionCode,
header,
}
allParams := strings.Join(params, "&")
paramList := strings.Split(allParams, "&")
sort.Strings(paramList)
sortedParams := strings.Join(paramList, "&")
sortedParams := buildSortedQuery(params...)
// 准备请求体
requestBody := map[string]string{
"content": content,
"keyver": "1",
}
bodyJSON, _ := json.Marshal(requestBody)
@@ -203,6 +198,12 @@ func GetKey(header string) (*KeyInfo, error) {
var respData map[string]interface{}
json.Unmarshal(resp.Content, &respData)
// 上游判定该设备无需解密(正文明文下发),返回空密钥信息由调用方分支处理
if code, ok := respData["code"].(float64); ok && int(code) == 500003 {
keyLogger.Info("上游标记无需解密(code=500003)")
return &KeyInfo{}, nil
}
dataObj, ok := respData["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("响应格式错误")
@@ -231,12 +232,17 @@ func GetKey(header string) (*KeyInfo, error) {
finalKey := base64.StdEncoding.EncodeToString(decryptedKey)
keyVer, _ := dataObj["keyver"].(float64)
keyTs, _ := dataObj["key_register_ts"].(float64)
if keyTs == 0 {
keyTs, _ = dataObj["register_time"].(float64)
}
keyInfo = &KeyInfo{
Key: finalKey,
Ver: int(keyVer),
Ts: int64(keyTs),
}
keyLogger.Info(fmt.Sprintf("密钥获取成功!"))
keyLogger.Info("密钥获取成功!")
return keyInfo, nil
}
@@ -248,7 +254,3 @@ func GetKey(header string) (*KeyInfo, error) {
func GetKeyInfo() *KeyInfo {
return keyInfo
}
func init() {
rand.Seed(time.Now().UnixNano())
}