package dao import ( "context" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "observer-server/biz/consts" "observer-server/biz/model/entity" "observer-server/common" ) // License 账号授权表 DAO:一手机号一记录(账号+授权合一);查询走缓存,写后必须清缓存。 type licenseDao struct{} var License = &licenseDao{} func init() { ctx := context.Background() common.DropLegacyTableIfHasColumn(ctx, consts.TableLicense, "device_id") _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS license ( phone_num TEXT PRIMARY KEY, password TEXT NOT NULL, expires_at TEXT, remark TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL )`) if err != nil { panic(err) } common.EnsureColumn(ctx, consts.TableLicense, "remark", "remark TEXT") } // GetByPhone 按手机号查询(走缓存,键含手机号) func (d *licenseDao) GetByPhone(ctx context.Context, phone string) (*entity.License, error) { return d.getByPhone(ctx, nil, phone, true) } // GetByPhoneNoCache 按手机号查询(绕过缓存:登录校验密码必须即时读库) func (d *licenseDao) GetByPhoneNoCache(ctx context.Context, phone string) (*entity.License, error) { return d.getByPhone(ctx, nil, phone, false) } // GetByPhoneInTx 事务内按手机号查询:读改写链路内必须用此方法(绕过缓存, // 防止并发写后缓存回填旧值导致续费叠加错乱) func (d *licenseDao) GetByPhoneInTx(ctx context.Context, tx gdb.TX, phone string) (*entity.License, error) { return d.getByPhone(ctx, tx, phone, false) } func (d *licenseDao) getByPhone(ctx context.Context, tx gdb.TX, phone string, cache bool) (*entity.License, error) { m := g.DB().Model(consts.TableLicense).Ctx(ctx) if tx != nil { m = m.TX(tx) } else if cache { m = m.Cache(common.CacheOption(ctx, "license:"+phone)) } var e entity.License if err := m.Where("phone_num", phone).Scan(&e); err != nil { if common.IsNoRows(err) { return nil, nil } return nil, err } return &e, nil } // Register 注册账号:插入 license 行(无授权);冲突由 service 先查后插保证。 func (d *licenseDao) Register(ctx context.Context, tx gdb.TX, phone, passwordHash string) error { now := gtime.Now() _, err := g.DB().Model(consts.TableLicense).Ctx(ctx).TX(tx).Insert(g.Map{ "phone_num": phone, "password": passwordHash, "expires_at": nil, "created_at": now, "updated_at": now, }) return err } // SetPasswordInTx 补写密码(注册时已有运营发卡占位行:只补密码,不覆盖授权) func (d *licenseDao) SetPasswordInTx(ctx context.Context, tx gdb.TX, phone, passwordHash string) error { _, err := g.DB().Model(consts.TableLicense).Ctx(ctx).TX(tx). Where("phone_num", phone). Data(gdb.Map{"password": passwordHash, "updated_at": gtime.Now()}). Update() return err } // UpsertAuthInTx 落授权(支付回调/手动授权):INSERT 占位(password 空串,运营发卡场景) // 或 ON CONFLICT 只更新授权字段——password/created_at 一律不动,防止覆盖已注册账号密码。 func (d *licenseDao) UpsertAuthInTx(ctx context.Context, tx gdb.TX, phone string, expiresAt any, updatedAt any) error { _, err := tx.Exec(`INSERT INTO license (phone_num, password, expires_at, created_at, updated_at) VALUES (?, '', ?, ?, ?) ON CONFLICT(phone_num) DO UPDATE SET expires_at = excluded.expires_at, updated_at = excluded.updated_at`, phone, expiresAt, updatedAt, updatedAt) return err } // ClearAuthInTx 事务内撤销授权(管理端 revoke):清空授权字段、保留账号行(密码不动) func (d *licenseDao) ClearAuthInTx(ctx context.Context, tx gdb.TX, phone string) error { _, err := g.DB().Model(consts.TableLicense).Ctx(ctx).TX(tx). Where("phone_num", phone). Data(gdb.Map{"expires_at": nil, "updated_at": gtime.Now()}). Update() return err } // SetRemarkInTx 事务内写备注(管理端 remark,空串即清空):不覆盖密码/授权字段 func (d *licenseDao) SetRemarkInTx(ctx context.Context, tx gdb.TX, phone, remark string) error { _, err := g.DB().Model(consts.TableLicense).Ctx(ctx).TX(tx). Where("phone_num", phone). Data(gdb.Map{"remark": remark, "updated_at": gtime.Now()}). Update() return err } // PageByPhone 管理端账号分页查询:phone 模糊筛选,按更新时间倒序; // 列表不缓存(单点 GetByPhone 仍走缓存,写后清缓存不变)。 func (d *licenseDao) PageByPhone(ctx context.Context, phone string, page, size int) ([]*entity.License, int64, error) { base := func() *gdb.Model { m := g.DB().Model(consts.TableLicense).Ctx(ctx) if phone != "" { m = m.WhereLike("phone_num", "%"+phone+"%") } return m } total, err := base().Count() if err != nil { return nil, 0, err } var list []*entity.License err = base().OrderDesc("updated_at").Limit((page-1)*size, size).Scan(&list) if err != nil { if common.IsNoRows(err) { return []*entity.License{}, int64(total), nil } return nil, 0, err } return list, int64(total), nil }