spark-server/internal/module/user/model/user_device.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

82 lines
2.6 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"
"gorm.io/gorm/clause"
"spark-server/internal/module/user/entity"
)
// UserDevice maps table user_devices: devices that have logged in.
type UserDevice struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)"`
UserID int64 `gorm:"column:user_id;comment:所属用户"`
DeviceID uint64 `gorm:"column:device_id;comment:客户端设备 id,写入 JWT did"`
DeviceType string `gorm:"column:platform;comment:设备类型:ios/android/harmony/windows/macos/web"`
DeviceName string `gorm:"column:device_name;comment:设备展示名"`
AppVersion string `gorm:"column:app_version;comment:最近一次登录的客户端版本"`
Language string `gorm:"column:language;comment:最近一次登录语言,如 zh-CN"`
LastIP string `gorm:"column:last_ip;comment:最近一次登录的 PROXY RealIP,禁止用环回地址"`
LastLoginAt time.Time `gorm:"column:last_login_at;comment:最近一次登录时间"`
CreatedAt time.Time `gorm:"column:created_at;comment:该设备首次登录时间"`
}
func (UserDevice) TableName() string { return "user_devices" }
func (row *UserDevice) entity() *entity.Device {
if row == nil {
return nil
}
return &entity.Device{
ID: row.ID,
UserID: row.UserID,
DeviceID: row.DeviceID,
DeviceType: row.DeviceType,
DeviceName: row.DeviceName,
AppVersion: row.AppVersion,
Language: row.Language,
LastIP: row.LastIP,
LastLoginAt: row.LastLoginAt,
CreatedAt: row.CreatedAt,
}
}
func (m *Repo) UpsertDevice(ctx context.Context, d *entity.Device) error {
db, err := m.session(ctx)
if err != nil {
return err
}
row := UserDevice{
UserID: d.UserID,
DeviceID: d.DeviceID,
DeviceType: d.DeviceType,
DeviceName: d.DeviceName,
AppVersion: d.AppVersion,
Language: d.Language,
LastIP: d.LastIP,
LastLoginAt: d.LastLoginAt,
CreatedAt: d.CreatedAt,
}
err = db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}, {Name: "device_id"}},
DoUpdates: clause.AssignmentColumns([]string{
"platform", "device_name", "app_version", "language", "last_ip", "last_login_at",
}),
}).Create(&row).Error
return mapDBErr(err)
}
func (m *Repo) GetDevice(ctx context.Context, userID int64, deviceID uint64) (*entity.Device, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var row UserDevice
if err := db.Where("user_id = ? AND device_id = ?", userID, deviceID).Take(&row).Error; err != nil {
return nil, mapDBErr(err)
}
return row.entity(), nil
}