✨ 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
@@ -20,9 +20,14 @@ type DeviceKeyItem struct {
|
||||
DeviceHeader string `json:"device_header"`
|
||||
Key string `json:"key"`
|
||||
KeyVer int `json:"key_ver"`
|
||||
KeyTs int64 `json:"key_ts"` // 密钥注册时间戳(key_register_ts)
|
||||
IsValid bool `json:"is_valid"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastUsedAt time.Time `json:"last_used_at"`
|
||||
|
||||
// 仅内存字段(不落库)
|
||||
KeyFetchedAt time.Time `json:"-"` // 密钥获取时间,超 24h 异步刷新
|
||||
KeyRefreshAt time.Time `json:"-"` // 上次刷新尝试时间,防刷新风暴
|
||||
}
|
||||
|
||||
// DeviceKeyPool 设备密钥池
|
||||
@@ -77,6 +82,10 @@ func (p *DeviceKeyPool) Initialize() error {
|
||||
}
|
||||
|
||||
poolLogger.Info(fmt.Sprintf("设备密钥池初始化完成,当前有效设备数: %d", p.getValidCount()))
|
||||
|
||||
// last_used_at 批量落库(GetNext 不再同步写库)
|
||||
p.StartDevicePoolFlusher()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,11 +119,22 @@ func (p *DeviceKeyPool) GetNext() (*DeviceKeyItem, error) {
|
||||
item := validItems[p.currentIndex%len(validItems)]
|
||||
p.currentIndex++
|
||||
|
||||
// 更新最后使用时间
|
||||
// 只更新内存中的最后使用时间,由后台 flusher 定期批量落库
|
||||
// (此前每次取设备同步 UPDATE,SQLite 单写锁下是吞吐瓶颈)
|
||||
item.LastUsedAt = time.Now()
|
||||
p.updateDeviceInDB(item)
|
||||
|
||||
// poolLogger.Debug(fmt.Sprintf("使用设备 ID=%d", item.ID))
|
||||
// 密钥超 24h 未刷新:异步换新(本次请求仍用旧密钥,下次生效)
|
||||
if time.Since(item.KeyFetchedAt) > 20*time.Hour && time.Since(item.KeyRefreshAt) > 10*time.Minute {
|
||||
item.KeyRefreshAt = time.Now()
|
||||
header := item.DeviceHeader
|
||||
go func() {
|
||||
if ki, err := GetKey(header); err == nil {
|
||||
UpdateKeyInPool(header, ki)
|
||||
} else {
|
||||
poolLogger.Warning(fmt.Sprintf("刷新设备密钥失败: %v", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 异步检查是否需要补充
|
||||
go p.checkAndRefill()
|
||||
@@ -316,9 +336,11 @@ func (p *DeviceKeyPool) addNewDevice() error {
|
||||
DeviceHeader: deviceHeader,
|
||||
Key: keyInfo.Key,
|
||||
KeyVer: keyInfo.Ver,
|
||||
KeyTs: keyInfo.Ts,
|
||||
IsValid: true,
|
||||
CreatedAt: time.Now(),
|
||||
LastUsedAt: time.Now(),
|
||||
KeyFetchedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
@@ -344,8 +366,8 @@ func (p *DeviceKeyPool) loadFromDB() error {
|
||||
}
|
||||
|
||||
rows, err := cacheDB.Query(`
|
||||
SELECT id, device_header, key, key_ver, is_valid, created_at, last_used_at
|
||||
FROM device_pool
|
||||
SELECT id, device_header, key, key_ver, COALESCE(key_ts, 0), is_valid, created_at, last_used_at
|
||||
FROM device_pool
|
||||
ORDER BY id
|
||||
`)
|
||||
if err != nil {
|
||||
@@ -363,6 +385,7 @@ func (p *DeviceKeyPool) loadFromDB() error {
|
||||
&item.DeviceHeader,
|
||||
&item.Key,
|
||||
&item.KeyVer,
|
||||
&item.KeyTs,
|
||||
&item.IsValid,
|
||||
&createdAt,
|
||||
&lastUsedAt,
|
||||
@@ -374,6 +397,7 @@ func (p *DeviceKeyPool) loadFromDB() error {
|
||||
|
||||
item.CreatedAt = time.Unix(createdAt, 0)
|
||||
item.LastUsedAt = time.Unix(lastUsedAt, 0)
|
||||
item.KeyFetchedAt = time.Now() // 旧设备的密钥年龄未知,从加载起算
|
||||
items = append(items, &item)
|
||||
}
|
||||
|
||||
@@ -401,10 +425,10 @@ func (p *DeviceKeyPool) saveDeviceToDB(item *DeviceKeyItem) error {
|
||||
|
||||
// RETURNING id 同时兼容 SQLite(3.35+) 与 PostgreSQL,替代 LastInsertId()
|
||||
err := cacheDB.QueryRow(q(`
|
||||
INSERT INTO device_pool (device_header, key, key_ver, is_valid, created_at, last_used_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO device_pool (device_header, key, key_ver, key_ts, is_valid, created_at, last_used_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING id
|
||||
`), item.DeviceHeader, item.Key, item.KeyVer, boolToInt(item.IsValid),
|
||||
`), item.DeviceHeader, item.Key, item.KeyVer, item.KeyTs, boolToInt(item.IsValid),
|
||||
item.CreatedAt.Unix(), item.LastUsedAt.Unix(), item.CreatedAt.Unix()).Scan(&item.ID)
|
||||
|
||||
return err
|
||||
@@ -418,14 +442,39 @@ func (p *DeviceKeyPool) updateDeviceInDB(item *DeviceKeyItem) error {
|
||||
|
||||
_, err := cacheDB.Exec(q(`
|
||||
UPDATE device_pool
|
||||
SET device_header = ?, key = ?, key_ver = ?, is_valid = ?, last_used_at = ?, updated_at = ?
|
||||
SET device_header = ?, key = ?, key_ver = ?, key_ts = ?, is_valid = ?, last_used_at = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`), item.DeviceHeader, item.Key, item.KeyVer, boolToInt(item.IsValid),
|
||||
`), item.DeviceHeader, item.Key, item.KeyVer, item.KeyTs, boolToInt(item.IsValid),
|
||||
item.LastUsedAt.Unix(), item.LastUsedAt.Unix(), item.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// StartDevicePoolFlusher 启动 last_used_at 批量落库任务(GetNext 已改为只写内存)
|
||||
func (p *DeviceKeyPool) StartDevicePoolFlusher() {
|
||||
go func() {
|
||||
for range time.Tick(time.Minute) {
|
||||
FlushDevicePool()
|
||||
}
|
||||
}()
|
||||
poolLogger.Info("设备池 last_used_at 批量落库已启动(每分钟)")
|
||||
}
|
||||
|
||||
// FlushDevicePool 将内存中的设备使用时间批量刷入数据库
|
||||
// ponytail: 进程退出时最多丢 1 分钟使用记录(仅统计用途),无需精确停表
|
||||
func FlushDevicePool() {
|
||||
p := GetDeviceKeyPool()
|
||||
if !p.enabled || cacheDB == nil {
|
||||
return
|
||||
}
|
||||
p.mu.RLock()
|
||||
items := append([]*DeviceKeyItem(nil), p.items...)
|
||||
p.mu.RUnlock()
|
||||
for _, it := range items {
|
||||
p.updateDeviceInDB(it)
|
||||
}
|
||||
}
|
||||
|
||||
// removeInvalidFromDB 从数据库移除无效设备
|
||||
func (p *DeviceKeyPool) removeInvalidFromDB() error {
|
||||
if cacheDB == nil {
|
||||
@@ -483,10 +532,10 @@ func GetKeyInfoFromPool(deviceHeader string) *KeyInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateKeyInPool 更新池中设备的密钥
|
||||
func UpdateKeyInPool(deviceHeader string, key string, ver int) error {
|
||||
// UpdateKeyInPool 更新池中设备的密钥(含注册时间戳)
|
||||
func UpdateKeyInPool(deviceHeader string, ki *KeyInfo) error {
|
||||
pool := GetDeviceKeyPool()
|
||||
if !pool.IsEnabled() {
|
||||
if !pool.IsEnabled() || ki == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -495,8 +544,10 @@ func UpdateKeyInPool(deviceHeader string, key string, ver int) error {
|
||||
|
||||
for _, item := range pool.items {
|
||||
if item.DeviceHeader == deviceHeader {
|
||||
item.Key = key
|
||||
item.KeyVer = ver
|
||||
item.Key = ki.Key
|
||||
item.KeyVer = ki.Ver
|
||||
item.KeyTs = ki.Ts
|
||||
item.KeyFetchedAt = time.Now()
|
||||
return pool.updateDeviceInDB(item)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user