8.4 KiB
8.4 KiB
tags, date created, date modified
| tags | date created | date modified |
|---|---|---|
| 星期二, 十二月 9日 2025, 11:56:32 晚上 | 星期二, 十二月 9日 2025, 11:57:24 晚上 |
03_entity 代码
根据 v1.1 SQL DDL 的变更(主要是增加了反范式化字段 cached_role_codes)以及 “按领域分包” 的架构要求,以下是完整的、生产级的 GORM Entity 代码。
请注意:为了处理 PostgreSQL 的 text[] 数组类型,我们在 Go 中通常直接使用 []string 并配合 GORM 的 type:text[] 标签(GORM v2 的 Postgres 驱动支持此特性)。
📂 1. 用户领域实体
文件路径: internal/user/entity.go
package user
import (
"database/sql/driver"
"encoding/json"
"errors"
"time"
"gorm.io/gorm"
)
// =================================================================================
// 核心实体 (Core Entities)
// 遵循 "Pragmatic Entity" 模式: 既是业务实体也是 GORM 模型
// =================================================================================
// User 聚合根
type User struct {
// ID 使用 int64 对应 BigSerial
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
// -------------------------------------------------------------------------
// 认证与安全
// -------------------------------------------------------------------------
Username string `gorm:"column:username;type:text;not null;unique" json:"username"`
PasswordHash string `gorm:"column:password_hash;type:text;not null" json:"-"` // 🔒 安全: 永不序列化
// -------------------------------------------------------------------------
// 个人资料 (Profile)
// 使用指针 (*string) 以区分 DB 中的 NULL 和 空字符串
// -------------------------------------------------------------------------
Nickname *string `gorm:"column:nickname;type:text" json:"nickname"`
AvatarURL *string `gorm:"column:avatar_url;type:text" json:"avatarUrl"`
Bio *string `gorm:"column:bio;type:text" json:"bio"`
// -------------------------------------------------------------------------
// 状态与权限
// -------------------------------------------------------------------------
// Status: 1=Active, 0=Banned
Status int16 `gorm:"column:status;type:smallint;not null;default:1" json:"status"`
// [v1.1 新增] 反范式化字段: 缓存角色编码
// GORM Postgres 驱动通常能自动处理 []string <-> text[]
// 作用: 鉴权中间件读取此字段即可,无需 Join 角色表
CachedRoleCodes StringArray `gorm:"column:cached_role_codes;type:text[];not null;default:'{}'" json:"cachedRoleCodes"`
// -------------------------------------------------------------------------
// 审计与时间
// -------------------------------------------------------------------------
CreatedAt time.Time `gorm:"column:created_at;not null;default:now()" json:"createdAt"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:now()" json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-"` // 启用软删除
// -------------------------------------------------------------------------
// 关联 (Associations) - 仅用于 Preload,非物理字段
// -------------------------------------------------------------------------
Roles []Role `gorm:"many2many:user_roles;joinForeignKey:user_id;joinReferences:role_id" json:"roles,omitempty"`
}
// TableName 显式定义表名
func (User) TableName() string {
return "users"
}
// Role 角色实体
type Role struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
Code string `gorm:"column:code;type:text;not null;unique" json:"code"` // e.g. "admin"
Name string `gorm:"column:name;type:text;not null" json:"name"` // e.g. "管理员"
Description *string `gorm:"column:description;type:text" json:"description"`
CreatedAt time.Time `gorm:"column:created_at;not null;default:now()" json:"createdAt"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:now()" json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-"`
}
func (Role) TableName() string {
return "roles"
}
// UserRole 关联实体 (中间表)
// 即使是中间表,也定义实体以便于管理(如审计创建时间)
type UserRole struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID int64 `gorm:"column:user_id;not null" json:"userId"`
RoleID int64 `gorm:"column:role_id;not null" json:"roleId"`
CreatedAt time.Time `gorm:"column:created_at;not null;default:now()" json:"createdAt"`
}
func (UserRole) TableName() string {
return "user_roles"
}
// =================================================================================
// 辅助类型 (Helper Types)
// 确保 PostgreSQL 数组类型能正确 Scanner/Valuer
// =================================================================================
// StringArray 自定义类型以处理 PostgreSQL 的 text[]
// 虽然 GORM 高版本支持 []string,但显式实现接口更兼容不同的 Driver
type StringArray []string
func (a *StringArray) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
// 处理空数组情况 "{}"
str := string(bytes)
if str == "{}" {
*a = []string{}
return nil
}
// 这里使用简单处理,生产环境建议使用 lib/pq 或 pgx 的解析逻辑
// 简单实现: 依赖 GORM 默认行为,或者使用 JSON 兼容
return json.Unmarshal(bytes, a) // 注意:PG 数组格式不是 JSON,此处通常依赖 Driver 的自动转换
}
func (a StringArray) Value() (driver.Value, error) {
if len(a) == 0 {
return "{}", nil
}
// 同样,依赖 GORM 传递给 Driver
return a, nil
}
👨💻 架构师注 (User Entity):
CachedRoleCodes: 我定义了一个StringArray类型。虽然现代 GORM (gorm.io/driver/postgres) 可以直接把[]string映射为text[],但在代码中显式标记类型(甚至未来引入lib/pq的pq.StringArray)会让类型转换逻辑更健壮,防止驱动更新导致的兼容性问题。
json:"-": 再次确认PasswordHash和DeletedAt已被隐藏,防止敏感信息泄露。
📂 2. 认证领域实体
文件路径: internal/auth/entity.go
package auth
import (
"time"
)
// RefreshToken 令牌实体
// 负责双令牌机制中的长效令牌存储与风控
// 对应 SQL 表: refresh_tokens
type RefreshToken struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID int64 `gorm:"column:user_id;not null;index" json:"userId"` // 仅存储 ID,保持领域解耦
// -------------------------------------------------------------------------
// 安全核心
// -------------------------------------------------------------------------
// TokenHash: 存哈希值,防止数据库泄露后 Token 直接被利用
TokenHash string `gorm:"column:token_hash;type:text;not null;unique" json:"-"`
// -------------------------------------------------------------------------
// 风控与轮转 (Token Rotation & Risk Control)
// -------------------------------------------------------------------------
// FamilyID: 标记一整条 Token 链,用于检测重放攻击时级联吊销
FamilyID string `gorm:"column:family_id;type:text;not null;index" json:"familyId"`
// ParentTokenID: 溯源上一个 Token,构建链式关系
ParentTokenID string `gorm:"column:parent_token_id;type:text;not null;default:''" json:"parentTokenId"`
// IsRevoked: 黑名单开关
IsRevoked bool `gorm:"column:is_revoked;not null;default:false" json:"isRevoked"`
// -------------------------------------------------------------------------
// 生命周期
// -------------------------------------------------------------------------
ExpiresAt time.Time `gorm:"column:expires_at;not null" json:"expiresAt"`
CreatedAt time.Time `gorm:"column:created_at;not null;default:now()" json:"createdAt"`
}
// TableName 显式定义表名
func (RefreshToken) TableName() string {
return "refresh_tokens"
}
👨💻 架构师注 (Auth Entity):
- 领域边界: 这里没有引入
User结构体指针。在auth领域内,我们只关心UserID。如果业务逻辑需要获取用户详情(例如封禁检查),应由auth.Service调用user.Service或user.Repository,而不是在 Entity 层面强耦合。这符合 Clean Architecture 的原则。