// Package user 提供用户数据访问。所有查询强制按 mini_id 过滤, // 从数据层保证各小程序用户体系相互独立。 package user import ( "context" "gorm.io/gorm" "portal-mini-go/internal/model" "portal-mini-go/internal/pkg/errors" ) type Repository struct { db *gorm.DB } func New(db *gorm.DB) *Repository { return &Repository{db: db} } // UpsertByOpenID 按 mini_id + openid 创建或返回已有用户。 func (r *Repository) UpsertByOpenID(ctx context.Context, miniID uint64, openID, unionID, nickname, avatar string) (*model.User, error) { var u model.User err := r.db.WithContext(ctx). Where("mini_id = ? AND openid = ?", miniID, openID). Attrs(model.User{MiniID: miniID, OpenID: openID, UnionID: unionID, Nickname: nickname, Avatar: avatar}). FirstOrCreate(&u).Error if err != nil { return nil, errors.Wrap(50000, "upsert user", err) } return &u, nil } // GetByID 按 mini_id + id 查询用户,跨小程序查询直接返回未找到。 func (r *Repository) GetByID(ctx context.Context, miniID uint64, id uint64) (*model.User, error) { var u model.User err := r.db.WithContext(ctx). Where("mini_id = ? AND id = ?", miniID, id). First(&u).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.ErrNotFound } return nil, errors.Wrap(50000, "query user", err) } return &u, nil } // UpdateProfile 更新用户资料,限定在 mini_id 范围内。 // nickname / avatar 为空字符串时视为不更新该字段,便于部分更新。 func (r *Repository) UpdateProfile(ctx context.Context, miniID uint64, id uint64, nickname, avatar string) error { cols := make(map[string]interface{}) if nickname != "" { cols["nickname"] = nickname } if avatar != "" { cols["avatar"] = avatar } if len(cols) == 0 { return nil } res := r.db.WithContext(ctx). Model(&model.User{}). Where("mini_id = ? AND id = ?", miniID, id). Updates(cols) if res.Error != nil { return errors.Wrap(50000, "update user", res.Error) } if res.RowsAffected == 0 { return errors.ErrNotFound } return nil } // List 分页查询某小程序下的用户,按 ID 倒序。 func (r *Repository) List(ctx context.Context, miniID uint64, page, size int) ([]model.User, int64, error) { q := r.db.WithContext(ctx).Model(&model.User{}).Where("mini_id = ?", miniID) var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, errors.Wrap(50000, "count users", err) } var list []model.User if err := q.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { return nil, 0, errors.Wrap(50000, "list users", err) } return list, total, nil } // AdminListFilter 后台用户列表过滤条件。MiniID 为 0 表示不限小程序。 type AdminListFilter struct { MiniID uint64 Keyword string Page int Size int } // AdminList 后台分页查询用户,支持按小程序与关键字(昵称 / OpenID)过滤。 func (r *Repository) AdminList(ctx context.Context, f AdminListFilter) ([]model.User, int64, error) { q := r.db.WithContext(ctx).Model(&model.User{}) if f.MiniID != 0 { q = q.Where("mini_id = ?", f.MiniID) } if f.Keyword != "" { kw := "%" + f.Keyword + "%" q = q.Where("nickname LIKE ? OR openid LIKE ?", kw, kw) } var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, errors.Wrap(50000, "count users", err) } var list []model.User if err := q.Order("id DESC").Offset((f.Page - 1) * f.Size).Limit(f.Size).Find(&list).Error; err != nil { return nil, 0, errors.Wrap(50000, "list users", err) } return list, total, nil } // AdminGet 按 ID 查询用户(后台视角,不限小程序)。 func (r *Repository) AdminGet(ctx context.Context, id uint64) (*model.User, error) { var u model.User err := r.db.WithContext(ctx).Where("id = ?", id).First(&u).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.ErrNotFound } return nil, errors.Wrap(50000, "query user", err) } return &u, nil } // AdminUpdate 后台按 ID 更新用户资料与状态。nil 字段表示不更新该字段。 func (r *Repository) AdminUpdate(ctx context.Context, id uint64, nickname, avatar *string, status *int8) error { cols := map[string]interface{}{} if nickname != nil { cols["nickname"] = *nickname } if avatar != nil { cols["avatar"] = *avatar } if status != nil { cols["status"] = *status } if len(cols) == 0 { return nil } res := r.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Updates(cols) if res.Error != nil { return errors.Wrap(50000, "update user", res.Error) } if res.RowsAffected == 0 { return errors.ErrNotFound } return nil }