chore: 初始化仓库,排除二进制文件和覆盖率文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Test / test (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / build (push) Has been cancelled

This commit is contained in:
lan
2025-11-28 23:30:49 +08:00
commit 4b4980820f
107 changed files with 20755 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
package model
import (
"time"
)
// AuditLog 审计日志模型
type AuditLog struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID *int64 `gorm:"column:user_id;type:bigint;index" json:"user_id,omitempty"`
Action string `gorm:"column:action;type:varchar(100);not null;index" json:"action"`
ResourceType string `gorm:"column:resource_type;type:varchar(50);not null;index:idx_audit_logs_resource" json:"resource_type"`
ResourceID string `gorm:"column:resource_id;type:varchar(50);index:idx_audit_logs_resource" json:"resource_id,omitempty"`
OldValues string `gorm:"column:old_values;type:jsonb" json:"old_values,omitempty"` // JSONB 格式
NewValues string `gorm:"column:new_values;type:jsonb" json:"new_values,omitempty"` // JSONB 格式
IPAddress string `gorm:"column:ip_address;type:inet;not null" json:"ip_address"`
UserAgent string `gorm:"column:user_agent;type:text" json:"user_agent,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;index:idx_audit_logs_created_at,sort:desc" json:"created_at"`
// 关联
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
}
// TableName 指定表名
func (AuditLog) TableName() string {
return "audit_logs"
}
// CasbinRule Casbin 权限规则模型
type CasbinRule struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
PType string `gorm:"column:ptype;type:varchar(100);not null;index;uniqueIndex:uk_casbin_rule" json:"ptype"`
V0 string `gorm:"column:v0;type:varchar(100);not null;default:'';index;uniqueIndex:uk_casbin_rule" json:"v0"`
V1 string `gorm:"column:v1;type:varchar(100);not null;default:'';index;uniqueIndex:uk_casbin_rule" json:"v1"`
V2 string `gorm:"column:v2;type:varchar(100);not null;default:'';uniqueIndex:uk_casbin_rule" json:"v2"`
V3 string `gorm:"column:v3;type:varchar(100);not null;default:'';uniqueIndex:uk_casbin_rule" json:"v3"`
V4 string `gorm:"column:v4;type:varchar(100);not null;default:'';uniqueIndex:uk_casbin_rule" json:"v4"`
V5 string `gorm:"column:v5;type:varchar(100);not null;default:'';uniqueIndex:uk_casbin_rule" json:"v5"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
}
// TableName 指定表名
func (CasbinRule) TableName() string {
return "casbin_rule"
}

63
internal/model/profile.go Normal file
View File

@@ -0,0 +1,63 @@
package model
import (
"time"
)
// Profile Minecraft 档案模型
type Profile struct {
UUID string `gorm:"column:uuid;type:varchar(36);primaryKey" json:"uuid"`
UserID int64 `gorm:"column:user_id;not null;index" json:"user_id"`
Name string `gorm:"column:name;type:varchar(16);not null;uniqueIndex" json:"name"` // Minecraft 角色名
SkinID *int64 `gorm:"column:skin_id;type:bigint" json:"skin_id,omitempty"`
CapeID *int64 `gorm:"column:cape_id;type:bigint" json:"cape_id,omitempty"`
RSAPrivateKey string `gorm:"column:rsa_private_key;type:text;not null" json:"-"` // RSA 私钥不返回给前端
IsActive bool `gorm:"column:is_active;not null;default:true;index" json:"is_active"`
LastUsedAt *time.Time `gorm:"column:last_used_at;type:timestamp" json:"last_used_at,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
// 关联
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Skin *Texture `gorm:"foreignKey:SkinID" json:"skin,omitempty"`
Cape *Texture `gorm:"foreignKey:CapeID" json:"cape,omitempty"`
}
// TableName 指定表名
func (Profile) TableName() string {
return "profiles"
}
// ProfileResponse 档案响应(包含完整的皮肤/披风信息)
type ProfileResponse struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Textures ProfileTexturesData `json:"textures"`
IsActive bool `json:"is_active"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// ProfileTexturesData Minecraft 材质数据结构
type ProfileTexturesData struct {
Skin *ProfileTexture `json:"SKIN,omitempty"`
Cape *ProfileTexture `json:"CAPE,omitempty"`
}
// ProfileTexture 单个材质信息
type ProfileTexture struct {
URL string `json:"url"`
Metadata *ProfileTextureMetadata `json:"metadata,omitempty"`
}
// ProfileTextureMetadata 材质元数据
type ProfileTextureMetadata struct {
Model string `json:"model,omitempty"` // "slim" or "classic"
}
type KeyPair struct {
PrivateKey string `json:"private_key" bson:"private_key"`
PublicKey string `json:"public_key" bson:"public_key"`
Expiration time.Time `json:"expiration" bson:"expiration"`
Refresh time.Time `json:"refresh" bson:"refresh"`
}

View File

@@ -0,0 +1,85 @@
package model
// Response 通用API响应结构
type Response struct {
Code int `json:"code"` // 业务状态码
Message string `json:"message"` // 响应消息
Data interface{} `json:"data,omitempty"` // 响应数据
}
// PaginationResponse 分页响应结构
type PaginationResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Total int64 `json:"total"` // 总记录数
Page int `json:"page"` // 当前页码
PerPage int `json:"per_page"` // 每页数量
}
// ErrorResponse 错误响应
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Error string `json:"error,omitempty"` // 详细错误信息(仅开发环境)
}
// 常用状态码
const (
CodeSuccess = 200 // 成功
CodeCreated = 201 // 创建成功
CodeBadRequest = 400 // 请求参数错误
CodeUnauthorized = 401 // 未授权
CodeForbidden = 403 // 禁止访问
CodeNotFound = 404 // 资源不存在
CodeConflict = 409 // 资源冲突
CodeServerError = 500 // 服务器错误
)
// 常用响应消息
const (
MsgSuccess = "操作成功"
MsgCreated = "创建成功"
MsgBadRequest = "请求参数错误"
MsgUnauthorized = "未授权,请先登录"
MsgForbidden = "权限不足"
MsgNotFound = "资源不存在"
MsgConflict = "资源已存在"
MsgServerError = "服务器内部错误"
MsgInvalidToken = "无效的令牌"
MsgTokenExpired = "令牌已过期"
MsgInvalidCredentials = "用户名或密码错误"
)
// NewSuccessResponse 创建成功响应
func NewSuccessResponse(data interface{}) *Response {
return &Response{
Code: CodeSuccess,
Message: MsgSuccess,
Data: data,
}
}
// NewErrorResponse 创建错误响应
func NewErrorResponse(code int, message string, err error) *ErrorResponse {
resp := &ErrorResponse{
Code: code,
Message: message,
}
if err != nil {
resp.Error = err.Error()
}
return resp
}
// NewPaginationResponse 创建分页响应
func NewPaginationResponse(data interface{}, total int64, page, perPage int) *PaginationResponse {
return &PaginationResponse{
Code: CodeSuccess,
Message: MsgSuccess,
Data: data,
Total: total,
Page: page,
PerPage: perPage,
}
}

View File

@@ -0,0 +1,257 @@
package model
import (
"errors"
"testing"
)
// TestNewSuccessResponse 测试创建成功响应
func TestNewSuccessResponse(t *testing.T) {
tests := []struct {
name string
data interface{}
}{
{
name: "字符串数据",
data: "success",
},
{
name: "map数据",
data: map[string]string{
"id": "1",
"name": "test",
},
},
{
name: "nil数据",
data: nil,
},
{
name: "数组数据",
data: []string{"a", "b", "c"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp := NewSuccessResponse(tt.data)
if resp == nil {
t.Fatal("NewSuccessResponse() 返回nil")
}
if resp.Code != CodeSuccess {
t.Errorf("Code = %d, want %d", resp.Code, CodeSuccess)
}
if resp.Message != MsgSuccess {
t.Errorf("Message = %q, want %q", resp.Message, MsgSuccess)
}
// 对于可比较类型直接比较对于不可比较类型只验证不为nil
switch v := tt.data.(type) {
case string, nil:
// 数组不能直接比较只验证不为nil
if tt.data != nil && resp.Data == nil {
t.Error("Data 不应为nil")
}
if tt.data == nil && resp.Data != nil {
t.Error("Data 应为nil")
}
case []string:
// 数组不能直接比较只验证不为nil
if resp.Data == nil {
t.Error("Data 不应为nil")
}
default:
// 对于map等不可比较类型只验证不为nil
if tt.data != nil && resp.Data == nil {
t.Error("Data 不应为nil")
}
_ = v
}
})
}
}
// TestNewErrorResponse 测试创建错误响应
func TestNewErrorResponse(t *testing.T) {
tests := []struct {
name string
code int
message string
err error
}{
{
name: "带错误信息",
code: CodeBadRequest,
message: "请求参数错误",
err: errors.New("具体错误信息"),
},
{
name: "无错误信息",
code: CodeUnauthorized,
message: "未授权",
err: nil,
},
{
name: "服务器错误",
code: CodeServerError,
message: "服务器内部错误",
err: errors.New("数据库连接失败"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp := NewErrorResponse(tt.code, tt.message, tt.err)
if resp == nil {
t.Fatal("NewErrorResponse() 返回nil")
}
if resp.Code != tt.code {
t.Errorf("Code = %d, want %d", resp.Code, tt.code)
}
if resp.Message != tt.message {
t.Errorf("Message = %q, want %q", resp.Message, tt.message)
}
if tt.err != nil {
if resp.Error != tt.err.Error() {
t.Errorf("Error = %q, want %q", resp.Error, tt.err.Error())
}
} else {
if resp.Error != "" {
t.Errorf("Error 应为空,实际为 %q", resp.Error)
}
}
})
}
}
// TestNewPaginationResponse 测试创建分页响应
func TestNewPaginationResponse(t *testing.T) {
tests := []struct {
name string
data interface{}
total int64
page int
perPage int
}{
{
name: "正常分页",
data: []string{"a", "b", "c"},
total: 100,
page: 1,
perPage: 20,
},
{
name: "空数据",
data: []string{},
total: 0,
page: 1,
perPage: 20,
},
{
name: "最后一页",
data: []string{"a", "b"},
total: 22,
page: 3,
perPage: 10,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp := NewPaginationResponse(tt.data, tt.total, tt.page, tt.perPage)
if resp == nil {
t.Fatal("NewPaginationResponse() 返回nil")
}
if resp.Code != CodeSuccess {
t.Errorf("Code = %d, want %d", resp.Code, CodeSuccess)
}
if resp.Message != MsgSuccess {
t.Errorf("Message = %q, want %q", resp.Message, MsgSuccess)
}
// 对于可比较类型直接比较对于不可比较类型只验证不为nil
switch v := tt.data.(type) {
case string, nil:
// 数组不能直接比较只验证不为nil
if tt.data != nil && resp.Data == nil {
t.Error("Data 不应为nil")
}
if tt.data == nil && resp.Data != nil {
t.Error("Data 应为nil")
}
case []string:
// 数组不能直接比较只验证不为nil
if resp.Data == nil {
t.Error("Data 不应为nil")
}
default:
// 对于map等不可比较类型只验证不为nil
if tt.data != nil && resp.Data == nil {
t.Error("Data 不应为nil")
}
_ = v
}
if resp.Total != tt.total {
t.Errorf("Total = %d, want %d", resp.Total, tt.total)
}
if resp.Page != tt.page {
t.Errorf("Page = %d, want %d", resp.Page, tt.page)
}
if resp.PerPage != tt.perPage {
t.Errorf("PerPage = %d, want %d", resp.PerPage, tt.perPage)
}
})
}
}
// TestResponseConstants 测试响应常量
func TestResponseConstants(t *testing.T) {
// 测试状态码常量
statusCodes := map[string]int{
"CodeSuccess": CodeSuccess,
"CodeCreated": CodeCreated,
"CodeBadRequest": CodeBadRequest,
"CodeUnauthorized": CodeUnauthorized,
"CodeForbidden": CodeForbidden,
"CodeNotFound": CodeNotFound,
"CodeConflict": CodeConflict,
"CodeServerError": CodeServerError,
}
expectedCodes := map[string]int{
"CodeSuccess": 200,
"CodeCreated": 201,
"CodeBadRequest": 400,
"CodeUnauthorized": 401,
"CodeForbidden": 403,
"CodeNotFound": 404,
"CodeConflict": 409,
"CodeServerError": 500,
}
for name, code := range statusCodes {
expected := expectedCodes[name]
if code != expected {
t.Errorf("%s = %d, want %d", name, code, expected)
}
}
// 测试消息常量不为空
messages := []string{
MsgSuccess,
MsgCreated,
MsgBadRequest,
MsgUnauthorized,
MsgForbidden,
MsgNotFound,
MsgConflict,
MsgServerError,
MsgInvalidToken,
MsgTokenExpired,
MsgInvalidCredentials,
}
for _, msg := range messages {
if msg == "" {
t.Error("响应消息常量不应为空")
}
}
}

View File

@@ -0,0 +1,41 @@
package model
import (
"time"
)
// ConfigType 配置类型
type ConfigType string
const (
ConfigTypeString ConfigType = "STRING"
ConfigTypeInteger ConfigType = "INTEGER"
ConfigTypeBoolean ConfigType = "BOOLEAN"
ConfigTypeJSON ConfigType = "JSON"
)
// SystemConfig 系统配置模型
type SystemConfig struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
Key string `gorm:"column:key;type:varchar(100);not null;uniqueIndex" json:"key"`
Value string `gorm:"column:value;type:text;not null" json:"value"`
Description string `gorm:"column:description;type:varchar(255);not null;default:''" json:"description"`
Type ConfigType `gorm:"column:type;type:varchar(50);not null;default:'STRING'" json:"type"` // STRING, INTEGER, BOOLEAN, JSON
IsPublic bool `gorm:"column:is_public;not null;default:false;index" json:"is_public"` // 是否可被前端获取
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
}
// TableName 指定表名
func (SystemConfig) TableName() string {
return "system_config"
}
// SystemConfigPublicResponse 公开配置响应
type SystemConfigPublicResponse struct {
SiteName string `json:"site_name"`
SiteDescription string `json:"site_description"`
RegistrationEnabled bool `json:"registration_enabled"`
MaintenanceMode bool `json:"maintenance_mode"`
Announcement string `json:"announcement"`
}

76
internal/model/texture.go Normal file
View File

@@ -0,0 +1,76 @@
package model
import (
"time"
)
// TextureType 材质类型
type TextureType string
const (
TextureTypeSkin TextureType = "SKIN"
TextureTypeCape TextureType = "CAPE"
)
// Texture 材质模型
type Texture struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UploaderID int64 `gorm:"column:uploader_id;not null;index" json:"uploader_id"`
Name string `gorm:"column:name;type:varchar(100);not null;default:''" json:"name"`
Description string `gorm:"column:description;type:text" json:"description,omitempty"`
Type TextureType `gorm:"column:type;type:varchar(50);not null" json:"type"` // SKIN, CAPE
URL string `gorm:"column:url;type:varchar(255);not null" json:"url"`
Hash string `gorm:"column:hash;type:varchar(64);not null;uniqueIndex" json:"hash"` // SHA-256
Size int `gorm:"column:size;type:integer;not null;default:0" json:"size"`
IsPublic bool `gorm:"column:is_public;not null;default:false;index:idx_textures_public_type_status" json:"is_public"`
DownloadCount int `gorm:"column:download_count;type:integer;not null;default:0;index:idx_textures_download_count,sort:desc" json:"download_count"`
FavoriteCount int `gorm:"column:favorite_count;type:integer;not null;default:0;index:idx_textures_favorite_count,sort:desc" json:"favorite_count"`
IsSlim bool `gorm:"column:is_slim;not null;default:false" json:"is_slim"` // Alex(细) or Steve(粗)
Status int16 `gorm:"column:status;type:smallint;not null;default:1;index:idx_textures_public_type_status" json:"status"` // 1:正常, 0:审核中, -1:已删除
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
// 关联
Uploader *User `gorm:"foreignKey:UploaderID" json:"uploader,omitempty"`
}
// TableName 指定表名
func (Texture) TableName() string {
return "textures"
}
// UserTextureFavorite 用户材质收藏
type UserTextureFavorite struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID int64 `gorm:"column:user_id;not null;index;uniqueIndex:uk_user_texture" json:"user_id"`
TextureID int64 `gorm:"column:texture_id;not null;index;uniqueIndex:uk_user_texture" json:"texture_id"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;index" json:"created_at"`
// 关联
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Texture *Texture `gorm:"foreignKey:TextureID" json:"texture,omitempty"`
}
// TableName 指定表名
func (UserTextureFavorite) TableName() string {
return "user_texture_favorites"
}
// TextureDownloadLog 材质下载记录
type TextureDownloadLog struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
TextureID int64 `gorm:"column:texture_id;not null;index" json:"texture_id"`
UserID *int64 `gorm:"column:user_id;type:bigint;index" json:"user_id,omitempty"`
IPAddress string `gorm:"column:ip_address;type:inet;not null;index" json:"ip_address"`
UserAgent string `gorm:"column:user_agent;type:text" json:"user_agent,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;index:idx_download_logs_created_at,sort:desc" json:"created_at"`
// 关联
Texture *Texture `gorm:"foreignKey:TextureID" json:"texture,omitempty"`
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
}
// TableName 指定表名
func (TextureDownloadLog) TableName() string {
return "texture_download_logs"
}

14
internal/model/token.go Normal file
View File

@@ -0,0 +1,14 @@
package model
import "time"
type Token struct {
AccessToken string `json:"_id"`
UserID int64 `json:"user_id"`
ClientToken string `json:"client_token"`
ProfileId string `json:"profile_id"`
Usable bool `json:"usable"`
IssueDate time.Time `json:"issue_date"`
}
func (Token) TableName() string { return "token" }

70
internal/model/user.go Normal file
View File

@@ -0,0 +1,70 @@
package model
import (
"time"
)
// User 用户模型
type User struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
Username string `gorm:"column:username;type:varchar(255);not null;uniqueIndex" json:"username"`
Password string `gorm:"column:password;type:varchar(255);not null" json:"-"` // 密码不返回给前端
Email string `gorm:"column:email;type:varchar(255);not null;uniqueIndex" json:"email"`
Avatar string `gorm:"column:avatar;type:varchar(255);not null;default:''" json:"avatar"`
Points int `gorm:"column:points;type:integer;not null;default:0" json:"points"`
Role string `gorm:"column:role;type:varchar(50);not null;default:'user'" json:"role"`
Status int16 `gorm:"column:status;type:smallint;not null;default:1" json:"status"` // 1:正常, 0:禁用, -1:删除
Properties string `gorm:"column:properties;type:jsonb" json:"properties"` // JSON字符串存储为PostgreSQL的JSONB类型
LastLoginAt *time.Time `gorm:"column:last_login_at;type:timestamp" json:"last_login_at,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
}
// TableName 指定表名
func (User) TableName() string {
return "user"
}
// UserPointLog 用户积分变更记录
type UserPointLog struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID int64 `gorm:"column:user_id;not null;index" json:"user_id"`
ChangeType string `gorm:"column:change_type;type:varchar(50);not null" json:"change_type"` // EARN, SPEND, ADMIN_ADJUST
Amount int `gorm:"column:amount;type:integer;not null" json:"amount"`
BalanceBefore int `gorm:"column:balance_before;type:integer;not null" json:"balance_before"`
BalanceAfter int `gorm:"column:balance_after;type:integer;not null" json:"balance_after"`
Reason string `gorm:"column:reason;type:varchar(255);not null" json:"reason"`
ReferenceType string `gorm:"column:reference_type;type:varchar(50)" json:"reference_type,omitempty"`
ReferenceID *int64 `gorm:"column:reference_id;type:bigint" json:"reference_id,omitempty"`
OperatorID *int64 `gorm:"column:operator_id;type:bigint" json:"operator_id,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;index:idx_point_logs_created_at,sort:desc" json:"created_at"`
// 关联
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Operator *User `gorm:"foreignKey:OperatorID" json:"operator,omitempty"`
}
// TableName 指定表名
func (UserPointLog) TableName() string {
return "user_point_logs"
}
// UserLoginLog 用户登录日志
type UserLoginLog struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID int64 `gorm:"column:user_id;not null;index" json:"user_id"`
IPAddress string `gorm:"column:ip_address;type:inet;not null;index" json:"ip_address"`
UserAgent string `gorm:"column:user_agent;type:text" json:"user_agent,omitempty"`
LoginMethod string `gorm:"column:login_method;type:varchar(50);not null;default:'PASSWORD'" json:"login_method"`
IsSuccess bool `gorm:"column:is_success;not null;index" json:"is_success"`
FailureReason string `gorm:"column:failure_reason;type:varchar(255)" json:"failure_reason,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP;index:idx_login_logs_created_at,sort:desc" json:"created_at"`
// 关联
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
}
// TableName 指定表名
func (UserLoginLog) TableName() string {
return "user_login_logs"
}

View File

@@ -0,0 +1,48 @@
package model
import (
"fmt"
"gorm.io/gorm"
"math/rand"
"time"
)
// 定义随机字符集
const passwordChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
// Yggdrasil ygg密码与用户id绑定
type Yggdrasil struct {
ID int64 `gorm:"column:id;primaryKey;not null" json:"id"`
Password string `gorm:"column:password;not null" json:"password"`
// 关联 - Yggdrasil的ID引用User的ID但不自动创建外键约束避免循环依赖
User *User `gorm:"foreignKey:ID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE" json:"user,omitempty"`
}
func (Yggdrasil) TableName() string { return "Yggdrasil" }
// AfterCreate User创建后自动同步生成GeneratePassword记录
func (u *User) AfterCreate(tx *gorm.DB) error {
randomPwd := GenerateRandomPassword(16)
// 创建GeneratePassword记录
gp := Yggdrasil{
ID: u.ID, // 关联User的ID
Password: randomPwd, // 16位随机密码
}
if err := tx.Create(&gp).Error; err != nil {
// 若同步失败,可记录日志或回滚事务(根据业务需求处理)
return fmt.Errorf("同步生成密码失败: %w", err)
}
return nil
}
// GenerateRandomPassword 生成指定长度的随机字符串
func GenerateRandomPassword(length int) string {
rand.Seed(time.Now().UnixNano()) // 初始化随机数种子
b := make([]byte, length)
for i := range b {
b[i] = passwordChars[rand.Intn(len(passwordChars))]
}
return string(b)
}