gcli2api / internal /upstream /device_token.go
a3216's picture
chore: 同步到上游 1.12.0-panel + 凭证同步/独立启动器/保活
6d60378 verified
Raw History Blame Contribute Delete
3.09 kB
// device_token.go X-Device-Token 的文件兜底读取(与桌面端共用状态文件)。
//
// 容器内无桌面端 Turing Shield SDK,无法像 Python fork(xiaofan6ya/converter.py)那样
// 现取 token。这里提供另一条路径:宿主把桌面端生成的 device token 落 /app/data/device_token
// (或任意挂载路径),网关定期读取注入。读取频率限 5 分钟一次缓存,>1KB 或读失败则忽略
// (优雅降级不注入,不影响主流程)。
package upstream
import (
"errors"
"os"
"strings"
"sync"
"time"
)
var errDeviceTokenTooLarge = errors.New("device token file too large")
// deviceTokenFile 文件读取缓存 TTL(秒)。桌面端 SDK 自身也有缓存,这里再兜一层
// 避免每次出站请求都 stat+read 文件。
const deviceTokenFileTTL = 5 * time.Minute
// deviceTokenFileMaxLen token 文件最大字节数。token 通常几百字节;超过 1KB
// 视为异常(非 token 内容 / 文件被误用),忽略不注入。
const deviceTokenFileMaxLen = 1024
// deviceTokenFileCache 缓存 device token 文件读取结果(path → token+读取时刻)。
type deviceTokenFileCache struct {
mu sync.Mutex
path string
token string
readAt time.Time
lastErr error
}
var dtFileCache = &deviceTokenFileCache{}
// readDeviceTokenFile 读取并缓存 device token 文件;5 分钟内复用上次结果。
// 返回空串表示无可用 token(文件未配置 / 读失败 / 内容过长 / 空白)。
func readDeviceTokenFile(path string) string {
if path == "" {
return ""
}
// 快路径:缓存命中且未过期,直接返回缓存值。注意全程持锁(defer Unlock)——
// 含 5 分钟一次的过期重读(锁内读文件)。调用频率极低(每 5min 最多一次
// 文件 IO,文件上限 1KB),锁内 IO 可接受;若未来出现 NFS 挂载 + 高并发的
// 部署形态,再上 singleflight 包住重读段(YAGNI,现在不做)。
dtFileCache.mu.Lock()
defer dtFileCache.mu.Unlock()
if path == dtFileCache.path && time.Since(dtFileCache.readAt) < deviceTokenFileTTL {
return dtFileCache.token
}
// 缓存未命中或过期:重新读文件。
dtFileCache.path = path
tok, err := readTrimmedFile(path, deviceTokenFileMaxLen)
if err != nil {
// 读失败:清空缓存 token,避免注入过期/错误的值。
dtFileCache.token = ""
dtFileCache.lastErr = err
dtFileCache.readAt = time.Now()
return ""
}
dtFileCache.token = tok
dtFileCache.lastErr = nil
dtFileCache.readAt = time.Now()
return tok
}
// readTrimmedFile 读文件并 trim 首尾空白,超过 maxLen 返回错误(拒绝过长内容)。
func readTrimmedFile(path string, maxLen int) (string, error) {
info, err := os.Stat(path)
if err != nil {
return "", err
}
if info.Size() > int64(maxLen) {
return "", errDeviceTokenTooLarge
}
raw, err := os.ReadFile(path)
if err != nil {
return "", err
}
return strings.TrimSpace(string(raw)), nil
}