✨ 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:
1 parent
8ca372050b
commit
84bd3238ab
28 files changed
+1180
-270
No files matched your search
@@ -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())
|
||||
}
|
||||
Reference in new issue
Block a user