feat(auth): 用户/角色模型与数据访问层
- constants: 用户状态、性别、角色代码常量定义 - model: User/Role/UserRole GORM 模型 - dao: UserDAO (CRUD + 账号查询) + RoleDAO (角色分配/查询/检查) - provider: Auth 模块 Wire Provider Set Made-with: Cursor
This commit is contained in:
119
backend/go-service/app/auth/dao/role_dao.go
Normal file
119
backend/go-service/app/auth/dao/role_dao.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/echochat/backend/app/auth/model"
|
||||||
|
"github.com/echochat/backend/pkg/logs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RoleDAO 角色数据访问对象
|
||||||
|
type RoleDAO struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoleDAO 创建 RoleDAO 实例
|
||||||
|
func NewRoleDAO(db *gorm.DB) *RoleDAO {
|
||||||
|
return &RoleDAO{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByCode 按角色代码查询角色
|
||||||
|
// 利用 code 唯一索引,精确匹配
|
||||||
|
func (d *RoleDAO) FindByCode(ctx context.Context, code string) (*model.Role, error) {
|
||||||
|
funcName := "dao.role_dao.FindByCode"
|
||||||
|
logs.Debug(ctx, funcName, "按代码查询角色", zap.String("code", code))
|
||||||
|
|
||||||
|
var role model.Role
|
||||||
|
err := d.db.WithContext(ctx).Where("code = ?", code).First(&role).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &role, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignRole 为用户分配角色
|
||||||
|
// 插入 user_id + role_id 关联记录
|
||||||
|
func (d *RoleDAO) AssignRole(ctx context.Context, userID int64, roleID int) error {
|
||||||
|
funcName := "dao.role_dao.AssignRole"
|
||||||
|
logs.Info(ctx, funcName, "分配角色",
|
||||||
|
zap.Int64("user_id", userID),
|
||||||
|
zap.Int("role_id", roleID),
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
logs.Error(ctx, funcName, "分配角色失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
userRole := model.UserRole{
|
||||||
|
UserID: userID,
|
||||||
|
RoleID: roleID,
|
||||||
|
}
|
||||||
|
err = d.db.WithContext(ctx).Create(&userRole).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRoles 获取用户的所有角色列表
|
||||||
|
// 通过 JOIN auth_roles 表获取角色完整信息
|
||||||
|
func (d *RoleDAO) GetUserRoles(ctx context.Context, userID int64) ([]model.Role, error) {
|
||||||
|
funcName := "dao.role_dao.GetUserRoles"
|
||||||
|
logs.Debug(ctx, funcName, "获取用户角色", zap.Int64("user_id", userID))
|
||||||
|
|
||||||
|
var roles []model.Role
|
||||||
|
err := d.db.WithContext(ctx).
|
||||||
|
Joins("JOIN auth_user_roles ON auth_user_roles.role_id = auth_roles.id").
|
||||||
|
Where("auth_user_roles.user_id = ?", userID).
|
||||||
|
Find(&roles).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return roles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRoleCodes 获取用户的角色代码列表(便于权限判断)
|
||||||
|
func (d *RoleDAO) GetUserRoleCodes(ctx context.Context, userID int64) ([]string, error) {
|
||||||
|
roles, err := d.GetUserRoles(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
codes := make([]string, 0, len(roles))
|
||||||
|
for _, r := range roles {
|
||||||
|
codes = append(codes, r.Code)
|
||||||
|
}
|
||||||
|
return codes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasRole 检查用户是否拥有指定角色
|
||||||
|
func (d *RoleDAO) HasRole(ctx context.Context, userID int64, roleCode string) (bool, error) {
|
||||||
|
funcName := "dao.role_dao.HasRole"
|
||||||
|
logs.Debug(ctx, funcName, "检查用户角色",
|
||||||
|
zap.Int64("user_id", userID),
|
||||||
|
zap.String("role_code", roleCode),
|
||||||
|
)
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
err := d.db.WithContext(ctx).
|
||||||
|
Model(&model.UserRole{}).
|
||||||
|
Joins("JOIN auth_roles ON auth_roles.id = auth_user_roles.role_id").
|
||||||
|
Where("auth_user_roles.user_id = ? AND auth_roles.code = ?", userID, roleCode).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUserRoles 移除用户的所有角色(用于重新分配)
|
||||||
|
func (d *RoleDAO) RemoveUserRoles(ctx context.Context, userID int64) error {
|
||||||
|
funcName := "dao.role_dao.RemoveUserRoles"
|
||||||
|
logs.Info(ctx, funcName, "移除用户所有角色", zap.Int64("user_id", userID))
|
||||||
|
|
||||||
|
return d.db.WithContext(ctx).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Delete(&model.UserRole{}).Error
|
||||||
|
}
|
||||||
131
backend/go-service/app/auth/dao/user_dao.go
Normal file
131
backend/go-service/app/auth/dao/user_dao.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// Package dao 提供 auth 模块的数据库访问操作
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/echochat/backend/app/auth/model"
|
||||||
|
"github.com/echochat/backend/pkg/logs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserDAO 用户数据访问对象
|
||||||
|
type UserDAO struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserDAO 创建 UserDAO 实例
|
||||||
|
func NewUserDAO(db *gorm.DB) *UserDAO {
|
||||||
|
return &UserDAO{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建用户
|
||||||
|
// 插入一条新用户记录,username 和 email 有唯一索引
|
||||||
|
func (d *UserDAO) Create(ctx context.Context, user *model.User) error {
|
||||||
|
funcName := "dao.user_dao.Create"
|
||||||
|
logs.Info(ctx, funcName, "创建用户",
|
||||||
|
zap.String("username", user.Username),
|
||||||
|
zap.String("email", logs.MaskEmail(user.Email)),
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
logs.Error(ctx, funcName, "创建用户失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logs.Info(ctx, funcName, "创建用户成功", zap.Int64("user_id", user.ID))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = d.db.WithContext(ctx).Create(user).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByEmail 按邮箱查询用户
|
||||||
|
// 精确匹配邮箱地址,利用 email 唯一索引
|
||||||
|
func (d *UserDAO) FindByEmail(ctx context.Context, email string) (*model.User, error) {
|
||||||
|
funcName := "dao.user_dao.FindByEmail"
|
||||||
|
logs.Debug(ctx, funcName, "按邮箱查询用户", zap.String("email", logs.MaskEmail(email)))
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
err := d.db.WithContext(ctx).Where("email = ?", email).First(&user).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername 按用户名查询用户
|
||||||
|
// 精确匹配用户名,利用 username 唯一索引
|
||||||
|
func (d *UserDAO) FindByUsername(ctx context.Context, username string) (*model.User, error) {
|
||||||
|
funcName := "dao.user_dao.FindByUsername"
|
||||||
|
logs.Debug(ctx, funcName, "按用户名查询用户", zap.String("username", username))
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
err := d.db.WithContext(ctx).Where("username = ?", username).First(&user).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByID 按 ID 查询用户
|
||||||
|
func (d *UserDAO) FindByID(ctx context.Context, id int64) (*model.User, error) {
|
||||||
|
funcName := "dao.user_dao.FindByID"
|
||||||
|
logs.Debug(ctx, funcName, "按ID查询用户", zap.Int64("id", id))
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
err := d.db.WithContext(ctx).First(&user, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新用户信息
|
||||||
|
// 仅更新非零值字段
|
||||||
|
func (d *UserDAO) Update(ctx context.Context, user *model.User) error {
|
||||||
|
funcName := "dao.user_dao.Update"
|
||||||
|
logs.Info(ctx, funcName, "更新用户信息", zap.Int64("user_id", user.ID))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
logs.Error(ctx, funcName, "更新用户失败", zap.Int64("user_id", user.ID), zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = d.db.WithContext(ctx).Save(user).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLastLogin 更新最后登录信息
|
||||||
|
func (d *UserDAO) UpdateLastLogin(ctx context.Context, userID int64, ip string) error {
|
||||||
|
funcName := "dao.user_dao.UpdateLastLogin"
|
||||||
|
logs.Debug(ctx, funcName, "更新登录信息", zap.Int64("user_id", userID))
|
||||||
|
|
||||||
|
err := d.db.WithContext(ctx).
|
||||||
|
Model(&model.User{}).
|
||||||
|
Where("id = ?", userID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"last_login_at": gorm.Expr("NOW()"),
|
||||||
|
"last_login_ip": ip,
|
||||||
|
}).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByAccount 按用户名或邮箱查找用户(登录时使用)
|
||||||
|
func (d *UserDAO) FindByAccount(ctx context.Context, account string) (*model.User, error) {
|
||||||
|
funcName := "dao.user_dao.FindByAccount"
|
||||||
|
logs.Debug(ctx, funcName, "按账号查询用户", zap.String("account", account))
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
err := d.db.WithContext(ctx).
|
||||||
|
Where("username = ? OR email = ?", account, account).
|
||||||
|
First(&user).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
29
backend/go-service/app/auth/model/role.go
Normal file
29
backend/go-service/app/auth/model/role.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Role 角色表模型,对应 auth_roles 表
|
||||||
|
type Role struct {
|
||||||
|
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Code string `json:"code" gorm:"uniqueIndex;size:50;not null"` // 角色代码: user/admin/super_admin
|
||||||
|
Name string `json:"name" gorm:"size:50;not null"` // 角色显示名称
|
||||||
|
Description string `json:"description" gorm:"size:200;default:''"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"not null;autoCreateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定数据库表名
|
||||||
|
func (Role) TableName() string {
|
||||||
|
return "auth_roles"
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRole 用户角色关联表模型,对应 auth_user_roles 表
|
||||||
|
type UserRole struct {
|
||||||
|
UserID int64 `json:"user_id" gorm:"primaryKey"`
|
||||||
|
RoleID int `json:"role_id" gorm:"primaryKey"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"not null;autoCreateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定数据库表名
|
||||||
|
func (UserRole) TableName() string {
|
||||||
|
return "auth_user_roles"
|
||||||
|
}
|
||||||
26
backend/go-service/app/auth/model/user.go
Normal file
26
backend/go-service/app/auth/model/user.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Package model 定义 auth 模块的数据库模型
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// User 用户主表模型,对应 auth_users 表
|
||||||
|
type User struct {
|
||||||
|
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Username string `json:"username" gorm:"uniqueIndex;size:50;not null"`
|
||||||
|
Email string `json:"email" gorm:"uniqueIndex;size:100;not null"`
|
||||||
|
PasswordHash string `json:"-" gorm:"column:password_hash;size:255;not null"` // JSON 序列化时隐藏密码
|
||||||
|
Nickname string `json:"nickname" gorm:"size:50;not null;default:''"`
|
||||||
|
Avatar string `json:"avatar" gorm:"size:500;not null;default:''"`
|
||||||
|
Gender int `json:"gender" gorm:"not null;default:0"` // 0=未知, 1=男, 2=女
|
||||||
|
Phone *string `json:"phone" gorm:"size:20"` // 可选字段
|
||||||
|
Status int `json:"status" gorm:"not null;default:1"` // 1=正常, 2=禁用, 3=注销
|
||||||
|
LastLoginAt *time.Time `json:"last_login_at"`
|
||||||
|
LastLoginIP *string `json:"last_login_ip" gorm:"column:last_login_ip;size:50"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"not null;autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"not null;autoUpdateTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定数据库表名
|
||||||
|
func (User) TableName() string {
|
||||||
|
return "auth_users"
|
||||||
|
}
|
||||||
13
backend/go-service/app/auth/provider.go
Normal file
13
backend/go-service/app/auth/provider.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Package auth 提供用户认证与授权模块
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/echochat/backend/app/auth/dao"
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthSet Auth 模块依赖注入 Provider Set
|
||||||
|
var AuthSet = wire.NewSet(
|
||||||
|
dao.NewUserDAO,
|
||||||
|
dao.NewRoleDAO,
|
||||||
|
)
|
||||||
15
backend/go-service/app/constants/role_code.go
Normal file
15
backend/go-service/app/constants/role_code.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 角色代码
|
||||||
|
const (
|
||||||
|
RoleUser = "user" // 普通用户
|
||||||
|
RoleAdmin = "admin" // 管理员
|
||||||
|
RoleSuperAdmin = "super_admin" // 超级管理员
|
||||||
|
)
|
||||||
|
|
||||||
|
// RoleNameMap 角色代码到中文名的映射
|
||||||
|
var RoleNameMap = map[string]string{
|
||||||
|
RoleUser: "普通用户",
|
||||||
|
RoleAdmin: "管理员",
|
||||||
|
RoleSuperAdmin: "超级管理员",
|
||||||
|
}
|
||||||
23
backend/go-service/app/constants/user_status.go
Normal file
23
backend/go-service/app/constants/user_status.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Package constants 定义系统全局常量
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// 用户账号状态
|
||||||
|
const (
|
||||||
|
UserStatusActive = 1 // 正常
|
||||||
|
UserStatusDisabled = 2 // 禁用(管理员封禁)
|
||||||
|
UserStatusDeleted = 3 // 注销(用户主动注销)
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserStatusMap 用户状态中文映射
|
||||||
|
var UserStatusMap = map[int]string{
|
||||||
|
UserStatusActive: "正常",
|
||||||
|
UserStatusDisabled: "禁用",
|
||||||
|
UserStatusDeleted: "注销",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户性别
|
||||||
|
const (
|
||||||
|
GenderUnknown = 0 // 未知
|
||||||
|
GenderMale = 1 // 男
|
||||||
|
GenderFemale = 2 // 女
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user