✨ feat(cache): 新增 Redis 缓存与 PostgreSQL 后端支持

- 新增 cache.redis 配置(独立 host/port/password/db 字段),启用后正文缓存与音频代理缓存走 Redis,键前缀 fq:cache:/fq:audio:,TTL 自动过期
- 新增 cache.backend: sqlite|postgres 切换,PG 连接用 net/url 构造 DSN,密码特殊字符自动转义;SQLite 顺手启用 WAL 与 busy_timeout
- SQL 方言收敛:q() 将 ? 占位符转换为 $n、INSERT OR IGNORE 与查询再插入统一为 ON CONFLICT、LastInsertId 改为 RETURNING id、建表 DDL 双版本分派
- PG 下 VACUUM 走 autovacuum 说明、状态页大小改查 pg_database_size
- 修复 PG 下设备保存失败:is_valid 列为 INTEGER,写入点补 boolToInt 转换
- 新增 .env 支持:敏感配置(密码/主机/访问密钥)由环境变量覆盖 config.yaml,.env 进 gitignore,仓库附 .env.example 模板
- 新增 openapi.yaml(OpenAPI 3.1):覆盖全部 24 个路径,含参数、响应结构与鉴权方案,README 增加指引
- 删除死代码 InitDevicePoolTable;新增 rebind_test.go 单测
- 更新 README 与 config.yaml 配置示例,go.mod 新增 go-redis/v9 与 pgx/v5 依赖
This commit is contained in:
chenxin committed 2026-09-29 20:33:37 +08:00
1 parent d710d382ee
commit 1eccebedf9
15 files changed
+1406 -210

No files matched your search

+17 -46
View File
@@ -385,6 +385,14 @@ func (p *DeviceKeyPool) loadFromDB() error {
return nil
}
// boolToInt 布尔转整数存储(is_valid 列为 INTEGER,PostgreSQL 严格要求类型匹配)
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
// saveDeviceToDB 保存设备到数据库
func (p *DeviceKeyPool) saveDeviceToDB(item *DeviceKeyItem) error {
if cacheDB == nil {
@@ -394,23 +402,15 @@ func (p *DeviceKeyPool) saveDeviceToDB(item *DeviceKeyItem) error {
cacheMutex.Lock()
defer cacheMutex.Unlock()
result, err := cacheDB.Exec(`
// 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)
VALUES (?, ?, ?, ?, ?, ?)
`, item.DeviceHeader, item.Key, item.KeyVer, item.IsValid,
item.CreatedAt.Unix(), item.LastUsedAt.Unix())
if err != nil {
return err
}
RETURNING id
`), item.DeviceHeader, item.Key, item.KeyVer, boolToInt(item.IsValid),
item.CreatedAt.Unix(), item.LastUsedAt.Unix()).Scan(&item.ID)
// 获取插入的ID
id, err := result.LastInsertId()
if err == nil {
item.ID = int(id)
}
return nil
return err
}
// updateDeviceInDB 更新设备信息
@@ -422,11 +422,11 @@ func (p *DeviceKeyPool) updateDeviceInDB(item *DeviceKeyItem) error {
cacheMutex.Lock()
defer cacheMutex.Unlock()
_, err := cacheDB.Exec(`
UPDATE device_pool
_, err := cacheDB.Exec(q(`
UPDATE device_pool
SET device_header = ?, key = ?, key_ver = ?, is_valid = ?, last_used_at = ?
WHERE id = ?
`, item.DeviceHeader, item.Key, item.KeyVer, item.IsValid,
`), item.DeviceHeader, item.Key, item.KeyVer, boolToInt(item.IsValid),
item.LastUsedAt.Unix(), item.ID)
return err
@@ -454,35 +454,6 @@ func (p *DeviceKeyPool) removeInvalidFromDB() error {
return nil
}
// InitDevicePoolTable 初始化设备池表
func InitDevicePoolTable() error {
if cacheDB == nil {
return fmt.Errorf("数据库未初始化")
}
_, err := cacheDB.Exec(`CREATE TABLE IF NOT EXISTS device_pool (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_header TEXT NOT NULL,
key TEXT NOT NULL,
key_ver INTEGER NOT NULL,
is_valid INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
last_used_at INTEGER NOT NULL
)`)
if err != nil {
return err
}
// 创建索引
_, err = cacheDB.Exec(`CREATE INDEX IF NOT EXISTS idx_device_pool_valid ON device_pool(is_valid)`)
if err != nil {
poolLogger.Warning("创建索引失败: " + err.Error())
}
return nil
}
// GetDeviceHeaderFromPool 从池中获取设备header(兼容旧接口)
func GetDeviceHeaderFromPool() string {
pool := GetDeviceKeyPool()