mirror of
https://github.com/limitcool/starter.git
synced 2025-09-27 04:36:18 +08:00

- Consolidated user ID retrieval and permission checks into helper functions. - Updated UserHandler to utilize BaseHandler for common database and configuration access. - Enhanced logging for user-related operations, including login, registration, and password changes. - Removed redundant context handling in middleware and improved readability. - Introduced FileUtil for file URL generation and management, encapsulating file-related logic. - Refactored FileRepo and UserRepo to streamline database operations and error handling. - Deleted unused request_id middleware and integrated its functionality into request_logger. - Removed legacy test runner script to simplify testing process.
182 lines
5.4 KiB
Go
182 lines
5.4 KiB
Go
package model
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
|
||
"github.com/limitcool/starter/internal/pkg/errorx"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// User 用户模型
|
||
// 可以作为普通用户,也可以在合并模式下同时作为管理员用户
|
||
type User struct {
|
||
SnowflakeModel
|
||
|
||
Username string `json:"username" gorm:"size:50;not null;unique;comment:用户名"`
|
||
Password string `json:"-" gorm:"size:100;not null;comment:密码"`
|
||
Nickname string `json:"nickname" gorm:"size:50;comment:昵称"`
|
||
AvatarFileID int64 `json:"-" gorm:"comment:头像文件ID"`
|
||
AvatarURL string `json:"avatar" gorm:"-"` // 头像URL,不存储到数据库
|
||
AvatarFile *File `json:"avatar_file" gorm:"foreignKey:AvatarFileID"` // 关联的头像文件
|
||
Email string `json:"email" gorm:"size:100;comment:邮箱"`
|
||
Mobile string `json:"mobile" gorm:"size:20;comment:手机号"`
|
||
Enabled bool `json:"enabled" gorm:"default:true;comment:是否启用"`
|
||
Remark string `json:"remark" gorm:"size:500;comment:备注"`
|
||
LastLogin *time.Time `json:"last_login" gorm:"comment:最后登录时间"`
|
||
LastIP string `json:"last_ip" gorm:"size:50;comment:最后登录IP"`
|
||
|
||
// 普通用户特有字段
|
||
Gender string `json:"gender" gorm:"size:10;comment:性别"`
|
||
Birthday *time.Time `json:"birthday" gorm:"comment:生日"`
|
||
Address string `json:"address" gorm:"size:255;comment:地址"`
|
||
RegisterIP string `json:"register_ip" gorm:"size:50;comment:注册IP"`
|
||
|
||
// 管理员字段
|
||
IsAdmin bool `json:"is_admin" gorm:"default:false;comment:是否管理员"`
|
||
}
|
||
|
||
func (User) TableName() string {
|
||
return "user"
|
||
}
|
||
|
||
func NewUser() *User {
|
||
return &User{}
|
||
}
|
||
|
||
// UserRepo 用户仓库
|
||
type UserRepo struct {
|
||
*GenericRepo[User]
|
||
}
|
||
|
||
// NewUserRepo 创建用户仓库
|
||
func NewUserRepo(db *gorm.DB) *UserRepo {
|
||
genericRepo := NewGenericRepo[User](db)
|
||
genericRepo.ErrorCode = errorx.ErrorUserNotFoundCode
|
||
|
||
return &UserRepo{
|
||
GenericRepo: genericRepo,
|
||
}
|
||
}
|
||
|
||
// GetByID 根据ID获取用户
|
||
func (r *UserRepo) GetByID(ctx context.Context, id int64) (*User, error) {
|
||
user, err := r.Get(ctx, id, nil)
|
||
if err != nil {
|
||
return nil, errorx.WrapError(err, "查询用户失败")
|
||
}
|
||
return user, nil
|
||
}
|
||
|
||
// GetUserWithAvatar 获取用户信息,包括头像
|
||
func (r *UserRepo) GetUserWithAvatar(ctx context.Context, id int64) (*User, error) {
|
||
// 先查询用户基本信息
|
||
user, err := r.GenericRepo.Get(ctx, id, nil)
|
||
if err != nil {
|
||
return nil, errorx.WrapError(err, "查询用户失败")
|
||
}
|
||
|
||
// 如果用户有头像,再预加载头像
|
||
if user.AvatarFileID > 0 {
|
||
user, err = r.Get(ctx, id, &QueryOptions{
|
||
Preloads: []string{"AvatarFile"},
|
||
})
|
||
if err != nil {
|
||
return nil, errorx.WrapError(err, "查询用户头像失败")
|
||
}
|
||
|
||
// 设置头像URL
|
||
if user.AvatarFile != nil {
|
||
user.AvatarURL = user.AvatarFile.URL
|
||
}
|
||
}
|
||
|
||
return user, nil
|
||
}
|
||
|
||
// GetByUsername 根据用户名获取用户
|
||
func (r *UserRepo) GetByUsername(ctx context.Context, username string) (*User, error) {
|
||
user, err := r.Get(ctx, nil, &QueryOptions{
|
||
Condition: "username = ?",
|
||
Args: []any{username},
|
||
})
|
||
if err != nil {
|
||
if errors.Is(err, errorx.ErrNotFound) {
|
||
return nil, errorx.ErrUserNotFound
|
||
}
|
||
return nil, errorx.WrapError(err, "查询用户失败")
|
||
}
|
||
return user, nil
|
||
}
|
||
|
||
// IsExist 检查用户是否存在
|
||
func (r *UserRepo) IsExist(ctx context.Context, username string) (bool, error) {
|
||
count, err := r.Count(ctx, &QueryOptions{
|
||
Condition: "username = ?",
|
||
Args: []any{username},
|
||
})
|
||
if err != nil {
|
||
return false, errorx.WrapError(err, "检查用户是否存在失败")
|
||
}
|
||
return count > 0, nil
|
||
}
|
||
|
||
// ListUsers 获取用户列表
|
||
func (r *UserRepo) ListUsers(ctx context.Context, page, pageSize int, keyword string) ([]User, int64, error) {
|
||
var opts *QueryOptions
|
||
|
||
// 如果有关键字,添加模糊查询条件
|
||
if keyword != "" {
|
||
opts = &QueryOptions{
|
||
Condition: "username LIKE ? OR nickname LIKE ? OR email LIKE ?",
|
||
Args: []any{"%" + keyword + "%", "%" + keyword + "%", "%" + keyword + "%"},
|
||
Preloads: []string{"AvatarFile"},
|
||
}
|
||
} else {
|
||
opts = &QueryOptions{
|
||
Preloads: []string{"AvatarFile"},
|
||
}
|
||
}
|
||
|
||
// 获取用户列表
|
||
users, err := r.List(ctx, page, pageSize, opts)
|
||
if err != nil {
|
||
return nil, 0, errorx.WrapError(err, "查询用户列表失败")
|
||
}
|
||
|
||
// 设置头像URL
|
||
for i := range users {
|
||
if users[i].AvatarFile != nil {
|
||
users[i].AvatarURL = users[i].AvatarFile.URL
|
||
}
|
||
}
|
||
|
||
// 获取总数
|
||
total, err := r.Count(ctx, opts)
|
||
if err != nil {
|
||
return nil, 0, errorx.WrapError(err, "查询用户总数失败")
|
||
}
|
||
|
||
return users, total, nil
|
||
}
|
||
|
||
// UpdateAvatar 更新用户头像
|
||
func (r *UserRepo) UpdateAvatar(ctx context.Context, userID int64, fileID int64) error {
|
||
return r.DB.WithContext(ctx).Model(&User{}).Where("id = ?", userID).Update("avatar_file_id", fileID).Error
|
||
}
|
||
|
||
// UpdatePassword 更新用户密码
|
||
func (r *UserRepo) UpdatePassword(ctx context.Context, userID int64, password string) error {
|
||
return r.DB.WithContext(ctx).Model(&User{}).Where("id = ?", userID).Update("password", password).Error
|
||
}
|
||
|
||
// UpdateLastLogin 更新最后登录信息
|
||
func (r *UserRepo) UpdateLastLogin(ctx context.Context, userID int64, ip string) error {
|
||
now := time.Now()
|
||
return r.DB.WithContext(ctx).Model(&User{}).Where("id = ?", userID).Updates(map[string]any{
|
||
"last_login": now,
|
||
"last_ip": ip,
|
||
}).Error
|
||
}
|