// Package gueststore 游客(免登录模块)通用存储。 // // 一条业务条目 = yz_guest_data 一行 JSON 文档,按 guest_id + module 隔离; // 删除只做软删(is_deleted=1)永久保留,合并进正式账号后置 is_deleted=2。 // 业务控制器通过 identity.IsGuest 分流到这里,响应结构与登录态接口保持一致。 package gueststore import ( "encoding/json" "errors" "strings" "time" "server/models" "github.com/beego/beego/v2/client/orm" ) // ErrNotFound 条目不存在(或不属于该游客 / 已被删除)。 var ErrNotFound = errors.New("记录不存在") // Row 一条游客数据文档(对外统一用表 ID 作为条目 ID,payload 内的 id 仅冗余)。 type Row struct { ID uint64 Payload json.RawMessage CreateTime time.Time UpdateTime *time.Time } // Upsert 按手机号定位/创建游客身份,并刷新 openid / 活跃时间。 // 已合并(merged_uid>0)的旧记录会被复位为新的游客周期: // 历史数据行在合并时已置 is_deleted=2,不会再次出现在列表里。 func Upsert(phone, openid, unionid string) (*models.GuestUser, error) { phone = strings.TrimSpace(phone) if phone == "" { return nil, errors.New("手机号不能为空") } now := time.Now() var row models.GuestUser err := models.Orm.QueryTable(new(models.GuestUser)).Filter("phone", phone).One(&row) if err == orm.ErrNoRows { row = models.GuestUser{Phone: phone, OpenID: openid, UnionID: unionid, LastActiveTime: &now} id, ierr := models.Orm.Insert(&row) if ierr != nil { return nil, ierr } row.ID = uint64(id) return &row, nil } if err != nil { return nil, err } upd := map[string]interface{}{"last_active_time": now} if openid != "" { upd["openid"] = openid } if unionid != "" { upd["unionid"] = unionid } if row.MergedUID > 0 { // 复位游客周期,允许同一手机号再次以游客身份产生新数据 upd["merged_uid"] = uint64(0) upd["merged_time"] = nil row.MergedUID = 0 } if _, err = models.Orm.QueryTable(new(models.GuestUser)).Filter("id", row.ID).Update(upd); err != nil { return nil, err } row.LastActiveTime = &now return &row, nil } // Insert 写入一条业务条目,返回行 ID(即条目 ID)。 func Insert(guestID uint64, module string, payload interface{}) (uint64, error) { b, err := json.Marshal(payload) if err != nil { return 0, err } now := time.Now() id, err := models.Orm.Insert(&models.GuestData{ GuestID: guestID, Module: module, Payload: string(b), CreateTime: now, UpdateTime: &now, }) if err != nil { return 0, err } return uint64(id), nil } // Get 读取单条(限本游客 + 本模块 + 未删除)。 func Get(guestID uint64, module string, id uint64) (Row, error) { var d models.GuestData err := models.Orm.QueryTable(new(models.GuestData)). Filter("id", id). Filter("guest_id", guestID). Filter("module", module). Filter("is_deleted", 0). One(&d) if err == orm.ErrNoRows { return Row{}, ErrNotFound } if err != nil { return Row{}, err } return toRow(d), nil } // List 列出某游客某模块的全部有效条目(新→旧)。 func List(guestID uint64, module string) ([]Row, error) { var rows []models.GuestData _, err := models.Orm.QueryTable(new(models.GuestData)). Filter("guest_id", guestID). Filter("module", module). Filter("is_deleted", 0). OrderBy("-id"). All(&rows) if err != nil && err != orm.ErrNoRows { return nil, err } out := make([]Row, 0, len(rows)) for _, d := range rows { out = append(out, toRow(d)) } return out, nil } // Update 覆盖一条条目的 payload。 func Update(guestID uint64, module string, id uint64, payload interface{}) error { b, err := json.Marshal(payload) if err != nil { return err } n, err := models.Orm.QueryTable(new(models.GuestData)). Filter("id", id). Filter("guest_id", guestID). Filter("module", module). Filter("is_deleted", 0). Update(map[string]interface{}{"payload": string(b), "update_time": time.Now()}) if err != nil { return err } if n == 0 { return ErrNotFound } return nil } // Delete 软删除一条条目(保留数据行,永久不清理)。 func Delete(guestID uint64, module string, id uint64) error { n, err := models.Orm.QueryTable(new(models.GuestData)). Filter("id", id). Filter("guest_id", guestID). Filter("module", module). Filter("is_deleted", 0). Update(map[string]interface{}{"is_deleted": 1, "update_time": time.Now()}) if err != nil { return err } if n == 0 { return ErrNotFound } return nil } // MarkMerged 游客转正后:该游客全部有效数据行置 is_deleted=2(保留行本身)。 func MarkMerged(guestID uint64) error { _, err := models.Orm.QueryTable(new(models.GuestData)). Filter("guest_id", guestID). Filter("is_deleted", 0). Update(map[string]interface{}{"is_deleted": 2, "update_time": time.Now()}) return err } func toRow(d models.GuestData) Row { return Row{ID: d.ID, Payload: json.RawMessage(d.Payload), CreateTime: d.CreateTime, UpdateTime: d.UpdateTime} }