spark-server/internal/module/user/model/user.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

192 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
import (
"context"
"time"
"spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
)
// User maps table users: account row, Snowflake uid, mutable spark_id.
type User struct {
ID int64 `gorm:"column:id;primaryKey;comment:Snowflake 用户 id(uid),时间有序,非自增"`
SparkID string `gorm:"column:spark_id;comment:对外展示的账号,用户可改,全局唯一"`
Nickname string `gorm:"column:nickname;comment:昵称"`
Avatar string `gorm:"column:avatar;comment:头像对象存储 key 或 URL,不存图片二进制"`
QRCode string `gorm:"column:qrcode;comment:个人二维码扫描票据,随机生成,不含 uid 或 spark_id"`
Birthday *time.Time `gorm:"column:birthday;type:date;comment:生日,未填为空"`
Gender int `gorm:"column:gender;comment:0=未知 1=男 2=女"`
Status int `gorm:"column:status;comment:1=正常 0=禁用"`
LastLoginAt *time.Time `gorm:"column:last_login_at;comment:最近一次登录时间"`
LastActiveAt *time.Time `gorm:"column:last_active_at;comment:最近一次活跃时间(登录、心跳、发消息等)"`
CreatedAt time.Time `gorm:"column:created_at;comment:注册时间(create_time)"`
UpdatedAt time.Time `gorm:"column:updated_at;comment:资料最近更新时间(update_time)"`
}
func (User) TableName() string { return "users" }
func (r *User) entity() *entity.User {
if r == nil {
return nil
}
return &entity.User{
ID: r.ID,
SparkID: r.SparkID,
Nickname: r.Nickname,
Avatar: r.Avatar,
QRCode: r.QRCode,
Birthday: timeOrZero(r.Birthday),
Gender: r.Gender,
Status: r.Status,
LastLoginAt: timeOrZero(r.LastLoginAt),
LastActiveAt: timeOrZero(r.LastActiveAt),
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
}
}
func fromEntity(u *entity.User) User {
row := User{
ID: u.ID,
SparkID: u.SparkID,
Nickname: u.Nickname,
Avatar: u.Avatar,
QRCode: u.QRCode,
Gender: u.Gender,
Status: u.Status,
CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt,
}
row.Birthday = timePtr(u.Birthday)
row.LastLoginAt = timePtr(u.LastLoginAt)
row.LastActiveAt = timePtr(u.LastActiveAt)
return row
}
func timeOrZero(t *time.Time) time.Time {
if t == nil {
return time.Time{}
}
return *t
}
func timePtr(t time.Time) *time.Time {
if t.IsZero() {
return nil
}
return &t
}
func (m *Repo) GetByID(ctx context.Context, id int64) (*entity.User, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var row User
if err := db.Where("id = ?", id).Take(&row).Error; err != nil {
return nil, mapDBErr(err)
}
return row.entity(), nil
}
func (m *Repo) GetBySparkID(ctx context.Context, sparkID string) (*entity.User, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var row User
if err := db.Where("spark_id = ?", sparkID).Take(&row).Error; err != nil {
return nil, mapDBErr(err)
}
return row.entity(), nil
}
func (m *Repo) GetByQRCode(ctx context.Context, qrcode string) (*entity.User, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var row User
if err := db.Where("qrcode = ?", qrcode).Take(&row).Error; err != nil {
return nil, mapDBErr(err)
}
return row.entity(), nil
}
func (m *Repo) UpdateSparkID(ctx context.Context, userID int64, sparkID string) error {
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Model(&User{}).Where("id = ?", userID).Updates(map[string]any{
"spark_id": sparkID,
"updated_at": time.Now(),
})
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}
func (m *Repo) UpdateQRCode(ctx context.Context, userID int64, qrcode string) error {
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Model(&User{}).Where("id = ?", userID).Update("qrcode", qrcode)
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}
func (m *Repo) UpdateProfile(ctx context.Context, u *entity.User) error {
if u == nil || u.ID <= 0 {
return errs.ErrInvalidArgument
}
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Model(&User{}).Where("id = ?", u.ID).Select("spark_id", "nickname", "avatar", "gender", "birthday", "updated_at").Updates(map[string]any{
"spark_id": u.SparkID,
"nickname": u.Nickname,
"avatar": u.Avatar,
"gender": u.Gender,
"birthday": timePtr(u.Birthday),
"updated_at": u.UpdatedAt,
})
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}
func (m *Repo) TouchLogin(ctx context.Context, userID int64, at time.Time) error {
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Model(&User{}).Where("id = ?", userID).Updates(map[string]any{
"last_login_at": at,
"last_active_at": at,
})
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}