Files
observer/server/common/base_dao.go
T

90 lines
3.1 KiB
Go

package common
import (
"context"
"database/sql"
"errors"
"os"
"path/filepath"
"regexp"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
// IsNoRows 判断 Scan 空结果(结构体 Scan 无行时返回 sql.ErrNoRows),
// dao 查询方法据此返回 nil 实体而非错误。
func IsNoRows(err error) bool {
return err != nil && errors.Is(err, sql.ErrNoRows)
}
// CacheTTL 查询缓存 TTL(秒),来自 config.yml database.cache.ttl,缺失或非法回退默认值。
func CacheTTL(ctx context.Context) time.Duration {
ttl := g.Cfg().MustGet(ctx, "database.cache.ttl", 30).Int()
if ttl <= 0 {
ttl = 30
}
return time.Duration(ttl) * time.Second
}
// CacheOption 构造带显式缓存键的查询缓存选项:键须含业务参数(如 license:设备号),
// 写操作后必须按同键调用 ClearCache,否则「库里已改、查询还是旧值」。
func CacheOption(ctx context.Context, key string) gdb.CacheOption {
return gdb.CacheOption{Duration: CacheTTL(ctx), Force: false, Name: key}
}
// gdb 的查询缓存键 = 固定前缀 + 自定义 Name(见 gdb.genSelectCacheKey,前缀常量未导出),
// 清除时必须拼上同一前缀,否则键对不上、缓存永远清不掉。
const selectCachePrefix = "SelectCache:"
// ClearCache 清除查询缓存(写操作后必须调用)。清除失败仅记录日志:缓存 TTL 自愈兜底,
// 不阻断业务主链路。
func ClearCache(ctx context.Context, keys ...string) {
if len(keys) == 0 {
return
}
for _, key := range keys {
if _, err := g.DB().GetCache().Remove(ctx, selectCachePrefix+key); err != nil {
g.Log().Warningf(ctx, "清除查询缓存 %q 失败: %+v", key, err)
}
}
}
// DropLegacyTableIfHasColumn 存量库迁移:表存在旧版本废弃列(账号体系上线前的 device_id)
// 时 DROP 整表(存量数据作废,用户决策),由 dao init 以新结构重建。
func DropLegacyTableIfHasColumn(ctx context.Context, table, column string) {
res, err := g.DB().GetAll(ctx, "PRAGMA table_info("+table+")")
if err != nil {
panic("检查表结构失败 " + table + ": " + err.Error())
}
for _, r := range res {
if gconv.String(r["name"]) == column {
if _, err := g.DB().Exec(ctx, "DROP TABLE "+table); err != nil {
panic("DROP 旧表失败 " + table + ": " + err.Error())
}
g.Log().Warningf(ctx, "存量表 %s 为旧结构(列 %s),已 DROP 由新结构重建(存量数据作废)", table, column)
return
}
}
}
var fileLinkRegexp = regexp.MustCompile(`@file\(([^)]+)\)`)
// init 确保 SQLite 数据文件所在目录存在(驱动打开文件前必须已创建,否则报 unable to open)。
// 必须在任何 g.DB() 调用(含 dao 包 init 建表)之前执行。
func init() {
ctx := context.Background()
link := g.Cfg().MustGet(ctx, "database.default.link", "").String()
file := link
if m := fileLinkRegexp.FindStringSubmatch(link); len(m) == 2 {
file = m[1]
}
if dir := filepath.Dir(file); dir != "" && dir != "." {
if err := os.MkdirAll(dir, 0o755); err != nil {
panic("创建数据目录失败: " + err.Error())
}
}
}