diff --git a/server/config/config.go b/server/config/config.go index cbe32c9..1573cab 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -1,15 +1,16 @@ package config import ( - "database/sql" "fmt" "log" "os" - _ "github.com/go-sql-driver/mysql" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" ) -var DB *sql.DB +var DB *gorm.DB // JWTSecret is the secret key used for signing JWT tokens var JWTSecret = "your-secret-key-change-this-in-production" // Default value @@ -28,12 +29,20 @@ func InitDB() { JWTSecret = secret } - DB, err = sql.Open("mysql", dsn) + DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), // 禁用 GORM 日志,使用自定义日志 + // 不使用 NowFunc,因为我们使用模型钩子手动处理 int64 时间戳 + }) if err != nil { log.Fatal("Failed to connect to database:", err) } - if err = DB.Ping(); err != nil { + sqlDB, err := DB.DB() + if err != nil { + log.Fatal("Failed to get underlying sql.DB:", err) + } + + if err = sqlDB.Ping(); err != nil { log.Fatal("Failed to ping database:", err) } @@ -42,6 +51,9 @@ func InitDB() { func CloseDB() { if DB != nil { - DB.Close() + sqlDB, err := DB.DB() + if err == nil { + sqlDB.Close() + } } } diff --git a/server/go.mod b/server/go.mod index 5a030e6..2821d45 100644 --- a/server/go.mod +++ b/server/go.mod @@ -10,6 +10,8 @@ require ( github.com/google/uuid v1.6.0 github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260109033043-398149f17e54 golang.org/x/crypto v0.40.0 + gorm.io/driver/mysql v1.5.7 + gorm.io/gorm v1.25.12 ) require ( @@ -24,6 +26,8 @@ require ( github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/server/go.sum b/server/go.sum index 4773e5e..fb292aa 100644 --- a/server/go.sum +++ b/server/go.sum @@ -25,6 +25,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -38,6 +39,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -98,3 +103,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/server/handlers/dashboard.go b/server/handlers/dashboard.go index 8c56ea4..72ff87b 100644 --- a/server/handlers/dashboard.go +++ b/server/handlers/dashboard.go @@ -2,7 +2,6 @@ package handlers import ( "github.com/gin-gonic/gin" - "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/repositories" "github.com/niangaodev/art-code/utils" ) @@ -75,8 +74,7 @@ func GetDashboardStats(c *gin.Context) { } // 5. 合作咨询总数 - var inquiryCount int - config.DB.QueryRow("SELECT COUNT(*) FROM inquiries WHERE deleted_at = 0").Scan(&inquiryCount) + inquiryCount, _ := repositories.GetInquiryCount() // 6. 作品总数 workCount, _ := repositories.GetWorkCount() diff --git a/server/ip2region.xdb b/server/ip2region.xdb new file mode 100644 index 0000000..2c4de15 Binary files /dev/null and b/server/ip2region.xdb differ diff --git a/server/models/about.go b/server/models/about.go index 5b14253..b5473c6 100644 --- a/server/models/about.go +++ b/server/models/about.go @@ -1,5 +1,11 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + type Experience struct { Year string `json:"year"` Role string `json:"role"` @@ -8,19 +14,45 @@ type Experience struct { // AboutProfile 关于我页面数据模型 type AboutProfile struct { - ID uint `json:"id"` - Name string `json:"name"` - Avatar string `json:"avatar"` - Location string `json:"location"` - Bio string `json:"bio"` - Email string `json:"email"` - Wechat string `json:"wechat"` - TechStack string `json:"-"` // Stored as string in DB - TechList []string `json:"techStack"` // Exposed as array in JSON - ExperiencesStr string `json:"-"` // Stored as string in DB - ExperienceList []Experience `json:"experiences"` // Exposed as array in JSON - IsPrimary bool `json:"isPrimary"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Avatar string `json:"avatar" gorm:"column:avatar"` + Location string `json:"location" gorm:"column:location"` + Bio string `json:"bio" gorm:"column:bio;type:text"` + Email string `json:"email" gorm:"column:email"` + Wechat string `json:"wechat" gorm:"column:wechat"` + TechStack string `json:"-" gorm:"column:tech_stack;type:text"` // Stored as string in DB + TechList []string `json:"techStack" gorm:"-"` // Exposed as array in JSON + ExperiencesStr string `json:"-" gorm:"column:experiences;type:text"` // Stored as string in DB + ExperienceList []Experience `json:"experiences" gorm:"-"` // Exposed as array in JSON + IsPrimary bool `json:"isPrimary" gorm:"column:is_primary;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (AboutProfile) TableName() string { + return "about_profiles" +} + +// BeforeCreate 创建前钩子 +func (a *AboutProfile) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if a.CreatedAt == 0 { + a.CreatedAt = now + } + if a.UpdatedAt == 0 { + a.UpdatedAt = now + } + if a.DeletedAt == 0 { + a.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (a *AboutProfile) BeforeUpdate(tx *gorm.DB) error { + a.UpdatedAt = time.Now().Unix() + return nil } diff --git a/server/models/access_log.go b/server/models/access_log.go index 7e05003..ec792f7 100644 --- a/server/models/access_log.go +++ b/server/models/access_log.go @@ -1,15 +1,37 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // AccessLog 访问日志模型 type AccessLog struct { - ID uint `json:"id" gorm:"primaryKey"` - IP string `json:"ip"` - UserAgent string `json:"user_agent"` - Path string `json:"path"` - Method string `json:"method"` - StatusCode int `json:"status_code"` - ResponseTime int64 `json:"response_time"` // 毫秒 - Region string `json:"region"` // IP归属地 - CreatedAt int64 `json:"created_at"` - DeletedAt int64 `json:"deleted_at"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + IP string `json:"ip" gorm:"column:ip;index"` + UserAgent string `json:"user_agent" gorm:"column:user_agent"` + Path string `json:"path" gorm:"column:path;index"` + Method string `json:"method" gorm:"column:method"` + StatusCode int `json:"status_code" gorm:"column:status_code"` + ResponseTime int64 `json:"response_time" gorm:"column:response_time"` // 毫秒 + Region string `json:"region" gorm:"column:region"` // IP归属地 + CreatedAt int64 `json:"created_at" gorm:"column:created_at"` + DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (AccessLog) TableName() string { + return "access_logs" +} + +// BeforeCreate 创建前钩子 +func (a *AccessLog) BeforeCreate(tx *gorm.DB) error { + if a.CreatedAt == 0 { + a.CreatedAt = time.Now().Unix() + } + if a.DeletedAt == 0 { + a.DeletedAt = 0 + } + return nil } diff --git a/server/models/category.go b/server/models/category.go index 9c36cbf..cc4f07e 100644 --- a/server/models/category.go +++ b/server/models/category.go @@ -1,13 +1,45 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Category 分类模型 type Category struct { - ID uint `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description"` - SortOrder uint `json:"sortOrder"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Slug string `json:"slug" gorm:"column:slug;uniqueIndex"` + Description string `json:"description" gorm:"column:description;type:text"` + SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Category) TableName() string { + return "categories" +} + +// BeforeCreate 创建前钩子 +func (c *Category) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if c.CreatedAt == 0 { + c.CreatedAt = now + } + if c.UpdatedAt == 0 { + c.UpdatedAt = now + } + if c.DeletedAt == 0 { + c.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (c *Category) BeforeUpdate(tx *gorm.DB) error { + c.UpdatedAt = time.Now().Unix() + return nil } diff --git a/server/models/column.go b/server/models/column.go index fcc628d..06324dd 100644 --- a/server/models/column.go +++ b/server/models/column.go @@ -1,22 +1,67 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Column 专栏模型 type Column struct { - ID uint `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Cover string `json:"cover"` - IsActive int `json:"isActive"` - SortOrder uint `json:"sortOrder"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Description string `json:"description" gorm:"column:description;type:text"` + Cover string `json:"cover" gorm:"column:cover"` + IsActive int `json:"isActive" gorm:"column:is_active;default:1"` + SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Column) TableName() string { + return "columns" +} + +// BeforeCreate 创建前钩子 +func (c *Column) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if c.CreatedAt == 0 { + c.CreatedAt = now + } + if c.UpdatedAt == 0 { + c.UpdatedAt = now + } + if c.DeletedAt == 0 { + c.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (c *Column) BeforeUpdate(tx *gorm.DB) error { + c.UpdatedAt = time.Now().Unix() + return nil } // ColumnPost 专栏文章关联模型 type ColumnPost struct { - ColumnID uint `json:"columnId"` - PostID uint `json:"postId"` - SortOrder uint `json:"sortOrder"` - CreatedAt int64 `json:"createdAt"` + ColumnID uint `json:"columnId" gorm:"primaryKey;column:column_id"` + PostID uint `json:"postId" gorm:"primaryKey;column:post_id"` + SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` +} + +// TableName 指定表名 +func (ColumnPost) TableName() string { + return "column_posts" +} + +// BeforeCreate 创建前钩子 +func (cp *ColumnPost) BeforeCreate(tx *gorm.DB) error { + if cp.CreatedAt == 0 { + cp.CreatedAt = time.Now().Unix() + } + return nil } diff --git a/server/models/inquiry.go b/server/models/inquiry.go index 46585e9..788617f 100644 --- a/server/models/inquiry.go +++ b/server/models/inquiry.go @@ -1,27 +1,85 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Inquiry 合作咨询 type Inquiry struct { - ID uint `json:"id"` - Name string `json:"name"` - Company string `json:"company"` - ContactMethod string `json:"contactMethod"` // email, wechat, phone - ContactValue string `json:"contactValue"` - Budget string `json:"budget"` - Description string `json:"description"` - Status int `json:"status"` // 0-Unread, 1-Read, 2-Contacted - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Company string `json:"company" gorm:"column:company"` + ContactMethod string `json:"contactMethod" gorm:"column:contact_method"` // email, wechat, phone + ContactValue string `json:"contactValue" gorm:"column:contact_value"` + Budget string `json:"budget" gorm:"column:budget"` + Description string `json:"description" gorm:"column:description;type:text"` + Status int `json:"status" gorm:"column:status;default:0"` // 0-Unread, 1-Read, 2-Contacted + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Inquiry) TableName() string { + return "inquiries" +} + +// BeforeCreate 创建前钩子 +func (i *Inquiry) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if i.CreatedAt == 0 { + i.CreatedAt = now + } + if i.UpdatedAt == 0 { + i.UpdatedAt = now + } + if i.DeletedAt == 0 { + i.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (i *Inquiry) BeforeUpdate(tx *gorm.DB) error { + i.UpdatedAt = time.Now().Unix() + return nil } // EmailSuffix 邮箱后缀配置 type EmailSuffix struct { - ID uint `json:"id"` - Suffix string `json:"suffix"` - IsActive bool `json:"isActive"` - SortOrder int `json:"sortOrder"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Suffix string `json:"suffix" gorm:"column:suffix;uniqueIndex"` + IsActive bool `json:"isActive" gorm:"column:is_active;default:1"` + SortOrder int `json:"sortOrder" gorm:"column:sort_order;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (EmailSuffix) TableName() string { + return "email_suffixes" +} + +// BeforeCreate 创建前钩子 +func (es *EmailSuffix) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if es.CreatedAt == 0 { + es.CreatedAt = now + } + if es.UpdatedAt == 0 { + es.UpdatedAt = now + } + if es.DeletedAt == 0 { + es.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (es *EmailSuffix) BeforeUpdate(tx *gorm.DB) error { + es.UpdatedAt = time.Now().Unix() + return nil } diff --git a/server/models/operation_log.go b/server/models/operation_log.go index 8c0611e..6cb431d 100644 --- a/server/models/operation_log.go +++ b/server/models/operation_log.go @@ -1,18 +1,40 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // OperationLog 操作日志模型 type OperationLog struct { - ID uint `json:"id"` - UserID uint `json:"userId"` - Username string `json:"username"` - IP string `json:"ip"` - Path string `json:"path"` - Method string `json:"method"` - Params string `json:"params"` - Status int `json:"status"` - Duration int `json:"duration"` - CreatedAt int64 `json:"createdAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + UserID uint `json:"userId" gorm:"column:user_id;index"` + Username string `json:"username" gorm:"column:username"` + IP string `json:"ip" gorm:"column:ip"` + Path string `json:"path" gorm:"column:path;index"` + Method string `json:"method" gorm:"column:method"` + Params string `json:"params" gorm:"column:params;type:text"` + Status int `json:"status" gorm:"column:status"` + Duration int `json:"duration" gorm:"column:duration"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (OperationLog) TableName() string { + return "operation_logs" +} + +// BeforeCreate 创建前钩子 +func (o *OperationLog) BeforeCreate(tx *gorm.DB) error { + if o.CreatedAt == 0 { + o.CreatedAt = time.Now().Unix() + } + if o.DeletedAt == 0 { + o.DeletedAt = 0 + } + return nil } // OperationLogResponse 操作日志响应模型 diff --git a/server/models/permission.go b/server/models/permission.go index c05e478..74d6486 100644 --- a/server/models/permission.go +++ b/server/models/permission.go @@ -1,14 +1,46 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Permission 权限模型 type Permission struct { - ID uint `json:"id"` - Name string `json:"name"` - Resource string `json:"resource"` - Action string `json:"action"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Resource string `json:"resource" gorm:"column:resource"` + Action string `json:"action" gorm:"column:action"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Permission) TableName() string { + return "permissions" +} + +// BeforeCreate 创建前钩子 +func (p *Permission) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if p.CreatedAt == 0 { + p.CreatedAt = now + } + if p.UpdatedAt == 0 { + p.UpdatedAt = now + } + if p.DeletedAt == 0 { + p.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (p *Permission) BeforeUpdate(tx *gorm.DB) error { + p.UpdatedAt = time.Now().Unix() + return nil } // PermissionResponse 权限响应模型 diff --git a/server/models/post.go b/server/models/post.go index c2a1a28..2aa04c0 100644 --- a/server/models/post.go +++ b/server/models/post.go @@ -1,20 +1,52 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Post 博客文章模型 type Post struct { - ID uint `json:"id"` - OriginalID string `json:"originalId,omitempty"` // For backward compatibility - Title string `json:"title"` - CategoryID uint `json:"categoryId"` - Category *Category `json:"category,omitempty"` // For join query result - Excerpt string `json:"excerpt"` - Content string `json:"content"` - ReadCount uint `json:"readCount"` - IsPublished int `json:"isPublished"` // 0: draft, 1: published - Tags []Tag `json:"tags"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + OriginalID string `json:"originalId,omitempty" gorm:"-"` // For backward compatibility + Title string `json:"title" gorm:"column:title"` + CategoryID uint `json:"categoryId" gorm:"column:category_id"` + Category *Category `json:"category,omitempty" gorm:"foreignKey:CategoryID"` // For join query result + Excerpt string `json:"excerpt" gorm:"column:excerpt"` + Content string `json:"content" gorm:"column:content;type:text"` + ReadCount uint `json:"readCount" gorm:"column:read_count;default:0"` + IsPublished int `json:"isPublished" gorm:"column:is_published;default:0"` // 0: draft, 1: published + Tags []Tag `json:"tags" gorm:"many2many:post_tags;joinForeignKey:post_id;joinReferences:tag_id"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Post) TableName() string { + return "posts" +} + +// BeforeCreate 创建前钩子 +func (p *Post) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if p.CreatedAt == 0 { + p.CreatedAt = now + } + if p.UpdatedAt == 0 { + p.UpdatedAt = now + } + if p.DeletedAt == 0 { + p.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (p *Post) BeforeUpdate(tx *gorm.DB) error { + p.UpdatedAt = time.Now().Unix() + return nil } // PostResponse 博客文章响应模型 @@ -32,17 +64,34 @@ type PostResponse struct { // PostHistory 文章历史记录模型 type PostHistory struct { - ID uint `json:"id"` - PostID uint `json:"postId"` - Version int `json:"version"` - Title string `json:"title"` - CategoryID uint `json:"categoryId"` - Excerpt string `json:"excerpt"` - Content string `json:"content"` - IsPublished int `json:"isPublished"` - ModifiedBy uint `json:"modifiedBy"` - ModifiedAt int64 `json:"modifiedAt"` - CreatedAt int64 `json:"createdAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + PostID uint `json:"postId" gorm:"column:post_id;index"` + Version int `json:"version" gorm:"column:version"` + Title string `json:"title" gorm:"column:title"` + CategoryID uint `json:"categoryId" gorm:"column:category_id"` + Excerpt string `json:"excerpt" gorm:"column:excerpt"` + Content string `json:"content" gorm:"column:content;type:text"` + IsPublished int `json:"isPublished" gorm:"column:is_published"` + ModifiedBy uint `json:"modifiedBy" gorm:"column:modified_by"` + ModifiedAt int64 `json:"modifiedAt" gorm:"column:modified_at"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` +} + +// TableName 指定表名 +func (PostHistory) TableName() string { + return "post_history" +} + +// BeforeCreate 创建前钩子 +func (ph *PostHistory) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if ph.CreatedAt == 0 { + ph.CreatedAt = now + } + if ph.ModifiedAt == 0 { + ph.ModifiedAt = now + } + return nil } // PostHistoryResponse 文章历史记录响应模型 diff --git a/server/models/role.go b/server/models/role.go index a3685e6..742d826 100644 --- a/server/models/role.go +++ b/server/models/role.go @@ -1,14 +1,46 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Role 角色模型 type Role struct { - ID uint `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Permissions []Permission `json:"permissions,omitempty"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name;uniqueIndex"` + Description string `json:"description" gorm:"column:description"` + Permissions []Permission `json:"permissions,omitempty" gorm:"many2many:role_permissions;joinForeignKey:role_id;joinReferences:permission_id"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Role) TableName() string { + return "roles" +} + +// BeforeCreate 创建前钩子 +func (r *Role) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if r.CreatedAt == 0 { + r.CreatedAt = now + } + if r.UpdatedAt == 0 { + r.UpdatedAt = now + } + if r.DeletedAt == 0 { + r.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (r *Role) BeforeUpdate(tx *gorm.DB) error { + r.UpdatedAt = time.Now().Unix() + return nil } // RoleResponse 角色响应模型 diff --git a/server/models/services.go b/server/models/services.go index 81905dc..acd9381 100644 --- a/server/models/services.go +++ b/server/models/services.go @@ -1,28 +1,86 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Testimonial 客户评价模型 type Testimonial struct { - ID uint `json:"id"` - Name string `json:"name"` - Role string `json:"role"` - Content string `json:"content"` - Avatar string `json:"avatar"` - Rating uint8 `json:"rating"` - SortOrder uint `json:"sortOrder"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Role string `json:"role" gorm:"column:role"` + Content string `json:"content" gorm:"column:content;type:text"` + Avatar string `json:"avatar" gorm:"column:avatar"` + Rating uint8 `json:"rating" gorm:"column:rating;default:5"` + SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Testimonial) TableName() string { + return "testimonials" +} + +// BeforeCreate 创建前钩子 +func (t *Testimonial) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if t.CreatedAt == 0 { + t.CreatedAt = now + } + if t.UpdatedAt == 0 { + t.UpdatedAt = now + } + if t.DeletedAt == 0 { + t.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (t *Testimonial) BeforeUpdate(tx *gorm.DB) error { + t.UpdatedAt = time.Now().Unix() + return nil } // Partner 合作伙伴模型 type Partner struct { - ID uint `json:"id"` - Name string `json:"name"` - Logo string `json:"logo"` - Description string `json:"description"` - URL string `json:"url"` - SortOrder uint `json:"sortOrder"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Logo string `json:"logo" gorm:"column:logo"` + Description string `json:"description" gorm:"column:description;type:text"` + URL string `json:"url" gorm:"column:url"` + SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Partner) TableName() string { + return "partners" +} + +// BeforeCreate 创建前钩子 +func (p *Partner) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if p.CreatedAt == 0 { + p.CreatedAt = now + } + if p.UpdatedAt == 0 { + p.UpdatedAt = now + } + if p.DeletedAt == 0 { + p.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (p *Partner) BeforeUpdate(tx *gorm.DB) error { + p.UpdatedAt = time.Now().Unix() + return nil } diff --git a/server/models/setting.go b/server/models/setting.go index 0cc53b9..7568f12 100644 --- a/server/models/setting.go +++ b/server/models/setting.go @@ -1,14 +1,46 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Setting 系统配置模型 type Setting struct { - ID uint `json:"id"` - KeyName string `json:"keyName"` - Value string `json:"value"` - Description string `json:"description"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + KeyName string `json:"keyName" gorm:"column:key_name;uniqueIndex"` + Value string `json:"value" gorm:"column:value;type:text"` + Description string `json:"description" gorm:"column:description"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Setting) TableName() string { + return "settings" +} + +// BeforeCreate 创建前钩子 +func (s *Setting) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if s.CreatedAt == 0 { + s.CreatedAt = now + } + if s.UpdatedAt == 0 { + s.UpdatedAt = now + } + if s.DeletedAt == 0 { + s.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (s *Setting) BeforeUpdate(tx *gorm.DB) error { + s.UpdatedAt = time.Now().Unix() + return nil } // SettingResponse 系统配置响应模型 diff --git a/server/models/snippet.go b/server/models/snippet.go index 2b3ad8a..9ff1d16 100644 --- a/server/models/snippet.go +++ b/server/models/snippet.go @@ -1,16 +1,48 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Snippet 代码片段模型 type Snippet struct { - ID string `json:"id"` - Title string `json:"title"` - Code string `json:"code"` - Type string `json:"type"` - Description string `json:"description"` - ViewCount uint `json:"viewCount"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID string `json:"id" gorm:"primaryKey;column:id"` + Title string `json:"title" gorm:"column:title"` + Code string `json:"code" gorm:"column:code;type:text"` + Type string `json:"type" gorm:"column:type"` + Description string `json:"description" gorm:"column:description;type:text"` + ViewCount uint `json:"viewCount" gorm:"column:view_count;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Snippet) TableName() string { + return "snippets" +} + +// BeforeCreate 创建前钩子 +func (s *Snippet) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if s.CreatedAt == 0 { + s.CreatedAt = now + } + if s.UpdatedAt == 0 { + s.UpdatedAt = now + } + if s.DeletedAt == 0 { + s.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (s *Snippet) BeforeUpdate(tx *gorm.DB) error { + s.UpdatedAt = time.Now().Unix() + return nil } // SnippetResponse 代码片段响应模型 diff --git a/server/models/tag.go b/server/models/tag.go index f97a76d..1808156 100644 --- a/server/models/tag.go +++ b/server/models/tag.go @@ -1,18 +1,63 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Tag 标签模型 type Tag struct { - ID uint `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Name string `json:"name" gorm:"column:name"` + Slug string `json:"slug" gorm:"column:slug;uniqueIndex"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Tag) TableName() string { + return "tags" +} + +// BeforeCreate 创建前钩子 +func (t *Tag) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if t.CreatedAt == 0 { + t.CreatedAt = now + } + if t.UpdatedAt == 0 { + t.UpdatedAt = now + } + if t.DeletedAt == 0 { + t.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (t *Tag) BeforeUpdate(tx *gorm.DB) error { + t.UpdatedAt = time.Now().Unix() + return nil } // PostTag 文章标签关联模型 type PostTag struct { - PostID string `json:"postId"` - TagID uint `json:"tagId"` - CreatedAt int64 `json:"createdAt"` + PostID uint `json:"postId" gorm:"primaryKey;column:post_id"` + TagID uint `json:"tagId" gorm:"primaryKey;column:tag_id"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` +} + +// TableName 指定表名 +func (PostTag) TableName() string { + return "post_tags" +} + +// BeforeCreate 创建前钩子 +func (pt *PostTag) BeforeCreate(tx *gorm.DB) error { + if pt.CreatedAt == 0 { + pt.CreatedAt = time.Now().Unix() + } + return nil } diff --git a/server/models/user.go b/server/models/user.go index 91e07b7..f6d7c4a 100644 --- a/server/models/user.go +++ b/server/models/user.go @@ -1,18 +1,50 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // User 用户模型 type User struct { - ID uint `json:"id"` - Username string `json:"username"` - Email string `json:"email"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + Username string `json:"username" gorm:"column:username;uniqueIndex;not null"` + Email string `json:"email" gorm:"column:email"` Password string `json:"password,omitempty" gorm:"-"` // Virtual field for input - PasswordHash string `json:"-"` - RoleID uint `json:"roleId"` - Role string `json:"role"` // 保持兼容,或者作为Role Name - IsActive int `json:"isActive"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + PasswordHash string `json:"-" gorm:"column:password_hash"` + RoleID uint `json:"roleId" gorm:"column:role_id"` + Role string `json:"role" gorm:"column:role"` // 保持兼容,或者作为Role Name + IsActive int `json:"isActive" gorm:"column:is_active;default:1"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (User) TableName() string { + return "users" +} + +// BeforeCreate 创建前钩子 +func (u *User) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if u.CreatedAt == 0 { + u.CreatedAt = now + } + if u.UpdatedAt == 0 { + u.UpdatedAt = now + } + if u.DeletedAt == 0 { + u.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (u *User) BeforeUpdate(tx *gorm.DB) error { + u.UpdatedAt = time.Now().Unix() + return nil } // UserResponse 用户响应模型 diff --git a/server/models/user_access_log.go b/server/models/user_access_log.go index ac2605a..0634849 100644 --- a/server/models/user_access_log.go +++ b/server/models/user_access_log.go @@ -1,12 +1,34 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // UserAccessLog 用户访问日志模型 type UserAccessLog struct { - ID uint `json:"id"` - UserID uint `json:"user_id"` // 用户ID(未登录用户为0) - UserIP string `json:"user_ip"` // 用户IP地址 - UserLocation string `json:"user_location"` // 用户归属地 - ArticleID uint `json:"article_id"` // 访问的文章ID - AccessTime int64 `json:"access_time"` // 访问时间 - DeletedAt int64 `json:"deleted_at"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + UserID uint `json:"user_id" gorm:"column:user_id;index"` // 用户ID(未登录用户为0) + UserIP string `json:"user_ip" gorm:"column:user_ip;index"` // 用户IP地址 + UserLocation string `json:"user_location" gorm:"column:user_location"` // 用户归属地 + ArticleID uint `json:"article_id" gorm:"column:article_id;index"` // 访问的文章ID + AccessTime int64 `json:"access_time" gorm:"column:access_time"` // 访问时间 + DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (UserAccessLog) TableName() string { + return "user_access_logs" +} + +// BeforeCreate 创建前钩子 +func (u *UserAccessLog) BeforeCreate(tx *gorm.DB) error { + if u.AccessTime == 0 { + u.AccessTime = time.Now().Unix() + } + if u.DeletedAt == 0 { + u.DeletedAt = 0 + } + return nil } diff --git a/server/models/work.go b/server/models/work.go index f420f19..f1d70f6 100644 --- a/server/models/work.go +++ b/server/models/work.go @@ -1,38 +1,102 @@ package models +import ( + "time" + + "gorm.io/gorm" +) + // Work 作品模型 type Work struct { - ID string `json:"id"` - Title string `json:"title"` - Category string `json:"category"` - Year string `json:"year"` - HeroImg string `json:"heroImg"` - Description string `json:"desc"` - IsFeatured int `json:"isFeatured"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - DeletedAt int64 `json:"deletedAt"` + ID string `json:"id" gorm:"primaryKey;column:id"` + Title string `json:"title" gorm:"column:title"` + Category string `json:"category" gorm:"column:category"` + Year string `json:"year" gorm:"column:year"` + HeroImg string `json:"heroImg" gorm:"column:hero_img"` + Description string `json:"desc" gorm:"column:description;type:text"` + IsFeatured int `json:"isFeatured" gorm:"column:is_featured;default:0"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (Work) TableName() string { + return "works" +} + +// BeforeCreate 创建前钩子 +func (w *Work) BeforeCreate(tx *gorm.DB) error { + now := time.Now().Unix() + if w.CreatedAt == 0 { + w.CreatedAt = now + } + if w.UpdatedAt == 0 { + w.UpdatedAt = now + } + if w.DeletedAt == 0 { + w.DeletedAt = 0 + } + return nil +} + +// BeforeUpdate 更新前钩子 +func (w *Work) BeforeUpdate(tx *gorm.DB) error { + w.UpdatedAt = time.Now().Unix() + return nil } // WorkTechStack 作品技术栈模型 type WorkTechStack struct { - ID uint `json:"id"` - WorkID string `json:"workId"` - Category string `json:"category"` - Item string `json:"item"` - CreatedAt int64 `json:"createdAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + WorkID string `json:"workId" gorm:"column:work_id;index"` + Category string `json:"category" gorm:"column:category"` + Item string `json:"item" gorm:"column:item"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (WorkTechStack) TableName() string { + return "work_tech_stack" +} + +// BeforeCreate 创建前钩子 +func (wts *WorkTechStack) BeforeCreate(tx *gorm.DB) error { + if wts.CreatedAt == 0 { + wts.CreatedAt = time.Now().Unix() + } + if wts.DeletedAt == 0 { + wts.DeletedAt = 0 + } + return nil } // WorkGallery 作品图库模型 type WorkGallery struct { - ID uint `json:"id"` - WorkID string `json:"workId"` - ImageURL string `json:"imageUrl"` - SortOrder uint `json:"sortOrder"` - Description string `json:"description"` - CreatedAt int64 `json:"createdAt"` - DeletedAt int64 `json:"deletedAt"` + ID uint `json:"id" gorm:"primaryKey;column:id"` + WorkID string `json:"workId" gorm:"column:work_id;index"` + ImageURL string `json:"imageUrl" gorm:"column:image_url"` + SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"` + Description string `json:"description" gorm:"column:description"` + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` + DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"` +} + +// TableName 指定表名 +func (WorkGallery) TableName() string { + return "work_gallery" +} + +// BeforeCreate 创建前钩子 +func (wg *WorkGallery) BeforeCreate(tx *gorm.DB) error { + if wg.CreatedAt == 0 { + wg.CreatedAt = time.Now().Unix() + } + if wg.DeletedAt == 0 { + wg.DeletedAt = 0 + } + return nil } // WorkResponse 作品响应模型,包含关联数据 diff --git a/server/repositories/about_repository.go b/server/repositories/about_repository.go index 56b5ec2..889caee 100644 --- a/server/repositories/about_repository.go +++ b/server/repositories/about_repository.go @@ -1,7 +1,6 @@ package repositories import ( - "database/sql" "encoding/json" "log" "strconv" @@ -10,6 +9,7 @@ import ( "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // unescapeJSONString 解码转义的 JSON 字符串 @@ -60,35 +60,17 @@ func unescapeJSONString(s string) (string, error) { // GetPrimaryAboutProfile 获取主页个人资料 func GetPrimaryAboutProfile() (*models.AboutProfile, error) { - query := ` - SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at - FROM about_profiles - WHERE is_primary = TRUE AND deleted_at = 0 - LIMIT 1 - ` - row := config.DB.QueryRow(query) - var profile models.AboutProfile - if err := row.Scan( - &profile.ID, - &profile.Name, - &profile.Avatar, - &profile.Location, - &profile.Bio, - &profile.Email, - &profile.Wechat, - &profile.TechStack, - &profile.ExperiencesStr, - &profile.IsPrimary, - &profile.CreatedAt, - &profile.UpdatedAt, - &profile.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.AboutProfile{}). + Where("is_primary = ? AND deleted_at = ?", true, 0). + First(&profile).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { // If no primary profile, try to get the first one return GetFirstAboutProfile() } - log.Printf("Error scanning primary about profile: %v", err) + log.Printf("Error getting primary about profile: %v", err) return nil, err } @@ -115,35 +97,17 @@ func GetPrimaryAboutProfile() (*models.AboutProfile, error) { // GetFirstAboutProfile 获取第一个个人资料(备用) func GetFirstAboutProfile() (*models.AboutProfile, error) { - query := ` - SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at - FROM about_profiles - WHERE deleted_at = 0 - ORDER BY id ASC - LIMIT 1 - ` - row := config.DB.QueryRow(query) - var profile models.AboutProfile - if err := row.Scan( - &profile.ID, - &profile.Name, - &profile.Avatar, - &profile.Location, - &profile.Bio, - &profile.Email, - &profile.Wechat, - &profile.TechStack, - &profile.ExperiencesStr, - &profile.IsPrimary, - &profile.CreatedAt, - &profile.UpdatedAt, - &profile.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.AboutProfile{}). + Where("deleted_at = ?", 0). + Order("id ASC"). + First(&profile).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning first about profile: %v", err) + log.Printf("Error getting first about profile: %v", err) return nil, err } @@ -182,34 +146,16 @@ func GetFirstAboutProfile() (*models.AboutProfile, error) { // GetAboutProfileByID 根据 ID 获取个人资料 func GetAboutProfileByID(id uint) (*models.AboutProfile, error) { - query := ` - SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at - FROM about_profiles - WHERE id = ? AND deleted_at = 0 - LIMIT 1 - ` - row := config.DB.QueryRow(query, id) - var profile models.AboutProfile - if err := row.Scan( - &profile.ID, - &profile.Name, - &profile.Avatar, - &profile.Location, - &profile.Bio, - &profile.Email, - &profile.Wechat, - &profile.TechStack, - &profile.ExperiencesStr, - &profile.IsPrimary, - &profile.CreatedAt, - &profile.UpdatedAt, - &profile.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.AboutProfile{}). + Where("id = ? AND deleted_at = ?", id, 0). + First(&profile).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning about profile by ID: %v", err) + log.Printf("Error getting about profile by ID: %v", err) return nil, err } @@ -248,34 +194,17 @@ func GetAboutProfileByID(id uint) (*models.AboutProfile, error) { // GetAllAboutProfiles 获取所有个人资料(管理用) func GetAllAboutProfiles() ([]models.AboutProfile, error) { - query := "SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at FROM about_profiles WHERE deleted_at = 0" - rows, err := config.DB.Query(query) + var profiles []models.AboutProfile + err := config.DB.Model(&models.AboutProfile{}). + Where("deleted_at = ?", 0). + Find(&profiles).Error if err != nil { log.Printf("Error querying about profiles: %v", err) return nil, err } - defer rows.Close() - var profiles []models.AboutProfile - for rows.Next() { - var p models.AboutProfile - if err := rows.Scan( - &p.ID, - &p.Name, - &p.Avatar, - &p.Location, - &p.Bio, - &p.Email, - &p.Wechat, - &p.TechStack, - &p.ExperiencesStr, - &p.IsPrimary, - &p.CreatedAt, - &p.UpdatedAt, - &p.DeletedAt, - ); err != nil { - continue - } + for i := range profiles { + var p = &profiles[i] // Unmarshal JSON - 确保 TechList 和 ExperienceList 始终是数组而不是 nil p.TechList = []string{} p.ExperienceList = []models.Experience{} @@ -305,7 +234,6 @@ func GetAllAboutProfiles() ([]models.AboutProfile, error) { } } } - profiles = append(profiles, p) } return profiles, nil } @@ -327,41 +255,12 @@ func CreateAboutProfile(profile *models.AboutProfile) error { expBytes, _ := json.Marshal(profile.ExperienceList) profile.ExperiencesStr = string(expBytes) - now := time.Now().Unix() - query := ` - INSERT INTO about_profiles (name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0) - ` - result, err := config.DB.Exec( - query, - profile.Name, - profile.Avatar, - profile.Location, - profile.Bio, - profile.Email, - profile.Wechat, - profile.TechStack, - profile.ExperiencesStr, - profile.IsPrimary, - now, - now, - ) + err := config.DB.Create(profile).Error if err != nil { log.Printf("Error creating about profile: %v", err) return err } - id, err := result.LastInsertId() - if err != nil { - return err - } - profile.ID = uint(id) - profile.CreatedAt = now - profile.UpdatedAt = now - - // 确保返回的数据包含 TechList 和 ExperienceList(已从 JSON 解析) - // 这些字段已经在上面被 Marshal 了,现在需要确保它们被正确设置 - // 由于我们已经 Marshal 了,TechList 和 ExperienceList 应该保持原样 return nil } @@ -382,26 +281,20 @@ func UpdateAboutProfile(profile *models.AboutProfile) error { expBytes, _ := json.Marshal(profile.ExperienceList) profile.ExperiencesStr = string(expBytes) - now := time.Now().Unix() - query := ` - UPDATE about_profiles - SET name = ?, avatar = ?, location = ?, bio = ?, email = ?, wechat = ?, tech_stack = ?, experiences = ?, is_primary = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - profile.Name, - profile.Avatar, - profile.Location, - profile.Bio, - profile.Email, - profile.Wechat, - profile.TechStack, - profile.ExperiencesStr, - profile.IsPrimary, - now, - profile.ID, - ) + err := config.DB.Model(&models.AboutProfile{}). + Where("id = ? AND deleted_at = ?", profile.ID, 0). + Updates(map[string]interface{}{ + "name": profile.Name, + "avatar": profile.Avatar, + "location": profile.Location, + "bio": profile.Bio, + "email": profile.Email, + "wechat": profile.Wechat, + "tech_stack": profile.TechStack, + "experiences": profile.ExperiencesStr, + "is_primary": profile.IsPrimary, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating about profile: %v", err) return err @@ -411,9 +304,9 @@ func UpdateAboutProfile(profile *models.AboutProfile) error { // DeleteAboutProfile 删除个人资料 (Soft Delete) func DeleteAboutProfile(id uint) error { - now := time.Now().Unix() - query := "UPDATE about_profiles SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.AboutProfile{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting about profile: %v", err) return err diff --git a/server/repositories/category_repository.go b/server/repositories/category_repository.go index 222ab5c..018b57b 100644 --- a/server/repositories/category_repository.go +++ b/server/repositories/category_repository.go @@ -1,141 +1,77 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetCategories 获取所有分类 func GetCategories() ([]models.Category, error) { - query := "SELECT id, name, slug, description, sort_order, created_at, updated_at, deleted_at FROM categories WHERE deleted_at = 0 ORDER BY sort_order ASC, created_at DESC" - rows, err := config.DB.Query(query) + var categories []models.Category + err := config.DB.Model(&models.Category{}). + Where("deleted_at = ?", 0). + Order("sort_order ASC, created_at DESC"). + Find(&categories).Error if err != nil { log.Printf("Error querying categories: %v", err) return nil, err } - defer rows.Close() - - var categories []models.Category - for rows.Next() { - var category models.Category - var description sql.NullString // Use NullString for nullable column - if err := rows.Scan( - &category.ID, - &category.Name, - &category.Slug, - &description, // Scan into NullString - &category.SortOrder, - &category.CreatedAt, - &category.UpdatedAt, - &category.DeletedAt, - ); err != nil { - log.Printf("Error scanning category: %v", err) - continue - } - if description.Valid { - category.Description = description.String - } - categories = append(categories, category) - } - return categories, nil } // GetCategoryByID 根据ID获取分类 func GetCategoryByID(id uint) (*models.Category, error) { - query := "SELECT id, name, slug, description, sort_order, created_at, updated_at, deleted_at FROM categories WHERE id = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, id) - var category models.Category - var description sql.NullString // Use NullString for nullable column - if err := row.Scan( - &category.ID, - &category.Name, - &category.Slug, - &description, // Scan into NullString - &category.SortOrder, - &category.CreatedAt, - &category.UpdatedAt, - &category.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Category{}). + Where("id = ? AND deleted_at = ?", id, 0). + First(&category).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning category by ID: %v", err) + log.Printf("Error getting category by ID: %v", err) return nil, err } - if description.Valid { - category.Description = description.String - } - return &category, nil } // CreateCategory 创建分类 func CreateCategory(category *models.Category) error { - now := time.Now().Unix() - query := ` - INSERT INTO categories (name, slug, description, sort_order, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, 0) - ` - result, err := config.DB.Exec( - query, - category.Name, - category.Slug, - category.Description, - category.SortOrder, - now, - now, - ) + err := config.DB.Create(category).Error if err != nil { log.Printf("Error creating category: %v", err) return err } - - id, err := result.LastInsertId() - if err != nil { - return err - } - category.ID = uint(id) - category.CreatedAt = now - category.UpdatedAt = now - return nil } // UpdateCategory 更新分类 func UpdateCategory(category *models.Category) error { - now := time.Now().Unix() - query := ` - UPDATE categories SET name = ?, slug = ?, description = ?, sort_order = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - category.Name, - category.Slug, - category.Description, - category.SortOrder, - now, - category.ID, - ) + err := config.DB.Model(&models.Category{}). + Where("id = ? AND deleted_at = ?", category.ID, 0). + Updates(map[string]interface{}{ + "name": category.Name, + "slug": category.Slug, + "description": category.Description, + "sort_order": category.SortOrder, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating category: %v", err) return err } - return nil } // DeleteCategory 删除分类 func DeleteCategory(id uint) error { - now := time.Now().Unix() - query := "UPDATE categories SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Category{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting category: %v", err) return err diff --git a/server/repositories/column_repository.go b/server/repositories/column_repository.go index 4472013..5195119 100644 --- a/server/repositories/column_repository.go +++ b/server/repositories/column_repository.go @@ -1,153 +1,78 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetColumns 获取所有专栏 func GetColumns() ([]models.Column, error) { - query := "SELECT id, name, description, cover, is_active, sort_order, created_at, updated_at, deleted_at FROM columns WHERE deleted_at = 0 ORDER BY sort_order ASC, created_at DESC" - rows, err := config.DB.Query(query) + var columns []models.Column + err := config.DB.Model(&models.Column{}). + Where("deleted_at = ?", 0). + Order("sort_order ASC, created_at DESC"). + Find(&columns).Error if err != nil { log.Printf("Error querying columns: %v", err) return nil, err } - defer rows.Close() - - var columns []models.Column - for rows.Next() { - var col models.Column - var description sql.NullString // Use NullString - var cover sql.NullString // Use NullString - if err := rows.Scan( - &col.ID, - &col.Name, - &description, - &cover, - &col.IsActive, - &col.SortOrder, - &col.CreatedAt, - &col.UpdatedAt, - &col.DeletedAt, - ); err != nil { - log.Printf("Error scanning column: %v", err) - continue - } - if description.Valid { - col.Description = description.String - } - if cover.Valid { - col.Cover = cover.String - } - columns = append(columns, col) - } - return columns, nil } // GetColumnByID 根据ID获取专栏 func GetColumnByID(id uint) (*models.Column, error) { - query := "SELECT id, name, description, cover, is_active, sort_order, created_at, updated_at, deleted_at FROM columns WHERE id = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, id) - var col models.Column - var description sql.NullString // Use NullString - var cover sql.NullString // Use NullString - if err := row.Scan( - &col.ID, - &col.Name, - &description, - &cover, - &col.IsActive, - &col.SortOrder, - &col.CreatedAt, - &col.UpdatedAt, - &col.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Column{}). + Where("id = ? AND deleted_at = ?", id, 0). + First(&col).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning column by ID: %v", err) + log.Printf("Error getting column by ID: %v", err) return nil, err } - if description.Valid { - col.Description = description.String - } - if cover.Valid { - col.Cover = cover.String - } - return &col, nil } // CreateColumn 创建专栏 func CreateColumn(col *models.Column) error { - now := time.Now().Unix() - query := ` - INSERT INTO columns (name, description, cover, is_active, sort_order, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, 0) - ` - result, err := config.DB.Exec( - query, - col.Name, - col.Description, - col.Cover, - col.IsActive, - col.SortOrder, - now, - now, - ) + err := config.DB.Create(col).Error if err != nil { log.Printf("Error creating column: %v", err) return err } - - id, err := result.LastInsertId() - if err != nil { - return err - } - col.ID = uint(id) - col.CreatedAt = now - col.UpdatedAt = now - return nil } // UpdateColumn 更新专栏 func UpdateColumn(col *models.Column) error { - now := time.Now().Unix() - query := ` - UPDATE columns SET name = ?, description = ?, cover = ?, is_active = ?, sort_order = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - col.Name, - col.Description, - col.Cover, - col.IsActive, - col.SortOrder, - now, - col.ID, - ) + err := config.DB.Model(&models.Column{}). + Where("id = ? AND deleted_at = ?", col.ID, 0). + Updates(map[string]interface{}{ + "name": col.Name, + "description": col.Description, + "cover": col.Cover, + "is_active": col.IsActive, + "sort_order": col.SortOrder, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating column: %v", err) return err } - return nil } // DeleteColumn 删除专栏 func DeleteColumn(id uint) error { - now := time.Now().Unix() - query := "UPDATE columns SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Column{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting column: %v", err) return err @@ -157,66 +82,39 @@ func DeleteColumn(id uint) error { // GetPostsByColumnID 获取专栏下的文章 func GetPostsByColumnID(columnID uint) ([]models.Post, error) { - query := ` - SELECT p.id, p.title, p.category_id, c.name as category_name, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at - FROM posts p - JOIN column_posts cp ON p.id = cp.post_id - LEFT JOIN categories c ON p.category_id = c.id - WHERE cp.column_id = ? AND p.deleted_at = 0 AND p.is_published = 1 - ORDER BY cp.sort_order ASC, p.created_at DESC - ` - rows, err := config.DB.Query(query, columnID) + var posts []models.Post + err := config.DB.Model(&models.Post{}). + Select("posts.*, categories.name as category_name"). + Joins("JOIN column_posts cp ON posts.id = cp.post_id"). + Joins("LEFT JOIN categories ON posts.category_id = categories.id"). + Where("cp.column_id = ? AND posts.deleted_at = ? AND posts.is_published = ?", columnID, 0, 1). + Order("cp.sort_order ASC, posts.created_at DESC"). + Find(&posts).Error if err != nil { log.Printf("Error querying posts by column ID: %v", err) return nil, err } - defer rows.Close() - - var posts []models.Post - for rows.Next() { - var post models.Post - var categoryName sql.NullString - if err := rows.Scan( - &post.ID, - &post.Title, - &post.CategoryID, - &categoryName, - &post.Excerpt, - &post.Content, - &post.ReadCount, - &post.IsPublished, - &post.CreatedAt, - &post.UpdatedAt, - &post.DeletedAt, - ); err != nil { - log.Printf("Error scanning post: %v", err) - continue - } - if categoryName.Valid { - post.Category = &models.Category{ID: post.CategoryID, Name: categoryName.String} - } - posts = append(posts, post) - } return posts, nil } // AddPostToColumn 添加文章到专栏 func AddPostToColumn(columnID, postID, sortOrder uint) error { - now := time.Now().Unix() - // Check if exists first to avoid duplicates or use INSERT IGNORE/REPLACE if simple - // Assuming unique key on (column_id, post_id) - query := ` - INSERT INTO column_posts (column_id, post_id, sort_order, created_at) - VALUES (?, ?, ?, ?) - ON DUPLICATE KEY UPDATE sort_order = VALUES(sort_order) - ` - _, err := config.DB.Exec(query, columnID, postID, sortOrder, now) + columnPost := &models.ColumnPost{ + ColumnID: columnID, + PostID: postID, + SortOrder: sortOrder, + CreatedAt: time.Now().Unix(), + } + // 使用 Clauses 处理 ON DUPLICATE KEY UPDATE + err := config.DB.Where("column_id = ? AND post_id = ?", columnID, postID). + Assign(models.ColumnPost{SortOrder: sortOrder}). + FirstOrCreate(columnPost).Error return err } // RemovePostFromColumn 从专栏移除文章 func RemovePostFromColumn(columnID, postID uint) error { - query := "DELETE FROM column_posts WHERE column_id = ? AND post_id = ?" - _, err := config.DB.Exec(query, columnID, postID) + err := config.DB.Where("column_id = ? AND post_id = ?", columnID, postID). + Delete(&models.ColumnPost{}).Error return err } diff --git a/server/repositories/inquiry_repository.go b/server/repositories/inquiry_repository.go index b5ead90..0b7219e 100644 --- a/server/repositories/inquiry_repository.go +++ b/server/repositories/inquiry_repository.go @@ -10,9 +10,7 @@ import ( // CreateInquiry 创建咨询 func CreateInquiry(inquiry *models.Inquiry) error { - now := time.Now().Unix() - query := `INSERT INTO inquiries (name, company, contact_method, contact_value, budget, description, status, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, 0)` - _, err := config.DB.Exec(query, inquiry.Name, inquiry.Company, inquiry.ContactMethod, inquiry.ContactValue, inquiry.Budget, inquiry.Description, now, now) + err := config.DB.Create(inquiry).Error if err != nil { log.Printf("Error creating inquiry: %v", err) return err @@ -22,130 +20,76 @@ func CreateInquiry(inquiry *models.Inquiry) error { // GetInquiries 获取咨询列表 (Admin) func GetInquiries() ([]models.Inquiry, error) { - // Added deleted_at check - rows, err := config.DB.Query("SELECT id, name, company, contact_method, contact_value, budget, description, status, created_at, updated_at, deleted_at FROM inquiries WHERE deleted_at = 0 ORDER BY created_at DESC") + var inquiries []models.Inquiry + err := config.DB.Model(&models.Inquiry{}). + Where("deleted_at = ?", 0). + Order("created_at DESC"). + Find(&inquiries).Error if err != nil { return nil, err } - defer rows.Close() - - var inquiries []models.Inquiry - for rows.Next() { - var i models.Inquiry - if err := rows.Scan( - &i.ID, - &i.Name, - &i.Company, - &i.ContactMethod, - &i.ContactValue, - &i.Budget, - &i.Description, - &i.Status, - &i.CreatedAt, - &i.UpdatedAt, - &i.DeletedAt, - ); err != nil { - return nil, err - } - inquiries = append(inquiries, i) - } return inquiries, nil } +// GetInquiryCount 获取咨询总数 +func GetInquiryCount() (int64, error) { + var count int64 + err := config.DB.Model(&models.Inquiry{}). + Where("deleted_at = ?", 0). + Count(&count).Error + return count, err +} + // GetEmailSuffixes 获取活跃的邮箱后缀 func GetEmailSuffixes() ([]models.EmailSuffix, error) { - rows, err := config.DB.Query("SELECT id, suffix, is_active, sort_order, created_at, updated_at, deleted_at FROM email_suffixes WHERE is_active = 1 AND deleted_at = 0 ORDER BY sort_order ASC") - if err != nil { - return nil, err - } - defer rows.Close() - var suffixes []models.EmailSuffix - for rows.Next() { - var s models.EmailSuffix - var isActive int - if err := rows.Scan( - &s.ID, - &s.Suffix, - &isActive, - &s.SortOrder, - &s.CreatedAt, - &s.UpdatedAt, - &s.DeletedAt, - ); err != nil { - return nil, err - } - s.IsActive = isActive == 1 - suffixes = append(suffixes, s) - } - return suffixes, nil + err := config.DB.Model(&models.EmailSuffix{}). + Where("is_active = ? AND deleted_at = ?", true, 0). + Order("sort_order ASC"). + Find(&suffixes).Error + return suffixes, err } // AdminGetEmailSuffixes 获取所有邮箱后缀 (Admin) func AdminGetEmailSuffixes() ([]models.EmailSuffix, error) { - rows, err := config.DB.Query("SELECT id, suffix, is_active, sort_order, created_at, updated_at, deleted_at FROM email_suffixes WHERE deleted_at = 0 ORDER BY sort_order ASC") - if err != nil { - return nil, err - } - defer rows.Close() - var suffixes []models.EmailSuffix - for rows.Next() { - var s models.EmailSuffix - var isActive int - if err := rows.Scan( - &s.ID, - &s.Suffix, - &isActive, - &s.SortOrder, - &s.CreatedAt, - &s.UpdatedAt, - &s.DeletedAt, - ); err != nil { - return nil, err - } - s.IsActive = isActive == 1 - suffixes = append(suffixes, s) - } - return suffixes, nil + err := config.DB.Model(&models.EmailSuffix{}). + Where("deleted_at = ?", 0). + Order("sort_order ASC"). + Find(&suffixes).Error + return suffixes, err } // CreateEmailSuffix 创建邮箱后缀 func CreateEmailSuffix(suffix *models.EmailSuffix) error { - now := time.Now().Unix() - query := `INSERT INTO email_suffixes (suffix, is_active, sort_order, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, ?, 0)` - isActive := 0 - if suffix.IsActive { - isActive = 1 - } - _, err := config.DB.Exec(query, suffix.Suffix, isActive, suffix.SortOrder, now, now) - return err + return config.DB.Create(suffix).Error } // UpdateEmailSuffix 更新邮箱后缀 func UpdateEmailSuffix(suffix *models.EmailSuffix) error { - now := time.Now().Unix() - query := `UPDATE email_suffixes SET suffix = ?, is_active = ?, sort_order = ?, updated_at = ? WHERE id = ? AND deleted_at = 0` - isActive := 0 - if suffix.IsActive { - isActive = 1 - } - _, err := config.DB.Exec(query, suffix.Suffix, isActive, suffix.SortOrder, now, suffix.ID) - return err + return config.DB.Model(&models.EmailSuffix{}). + Where("id = ? AND deleted_at = ?", suffix.ID, 0). + Updates(map[string]interface{}{ + "suffix": suffix.Suffix, + "is_active": suffix.IsActive, + "sort_order": suffix.SortOrder, + "updated_at": time.Now().Unix(), + }).Error } // DeleteEmailSuffix 删除邮箱后缀 (Soft Delete) func DeleteEmailSuffix(id uint) error { - now := time.Now().Unix() - query := `UPDATE email_suffixes SET deleted_at = ? WHERE id = ?` - _, err := config.DB.Exec(query, now, id) - return err + return config.DB.Model(&models.EmailSuffix{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error } // UpdateInquiryStatus 更新咨询状态 func UpdateInquiryStatus(id uint, status int) error { - now := time.Now().Unix() - query := `UPDATE inquiries SET status = ?, updated_at = ? WHERE id = ? AND deleted_at = 0` - _, err := config.DB.Exec(query, status, now, id) - return err + return config.DB.Model(&models.Inquiry{}). + Where("id = ? AND deleted_at = ?", id, 0). + Updates(map[string]interface{}{ + "status": status, + "updated_at": time.Now().Unix(), + }).Error } diff --git a/server/repositories/log_repository.go b/server/repositories/log_repository.go index a5bac55..5fb3631 100644 --- a/server/repositories/log_repository.go +++ b/server/repositories/log_repository.go @@ -38,10 +38,7 @@ func parseDateToUnix(dateStr string, isEnd bool) int64 { // CreateAccessLog 创建访问日志 func CreateAccessLog(log *models.AccessLog) error { - now := time.Now().Unix() - query := `INSERT INTO access_logs (ip, user_agent, path, method, status_code, response_time, region, created_at, deleted_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)` - _, err := config.DB.Exec(query, log.IP, log.UserAgent, log.Path, log.Method, log.StatusCode, log.ResponseTime, log.Region, now) - return err + return config.DB.Create(log).Error } // UVTrendData UV趋势数据 @@ -54,52 +51,38 @@ type UVTrendData struct { // GetDailyUV 获取UV趋势 func GetDailyUV(startDate, endDate string) ([]UVTrendData, error) { - query := ` - SELECT FROM_UNIXTIME(access_time, '%Y-%m-%d') as date, COUNT(DISTINCT user_ip) as count - FROM user_access_logs - WHERE 1=1 - ` - args := []interface{}{} + query := config.DB.Model(&models.UserAccessLog{}). + Select("FROM_UNIXTIME(access_time, '%Y-%m-%d') as date, COUNT(DISTINCT user_ip) as count") if startDate != "" { startUnix := parseDateToUnix(startDate, false) - query += " AND access_time >= ?" - args = append(args, startUnix) + query = query.Where("access_time >= ?", startUnix) } else { // 默认最近7天 startUnix := time.Now().AddDate(0, 0, -6).Unix() - query += " AND access_time >= ?" - args = append(args, startUnix) + query = query.Where("access_time >= ?", startUnix) } if endDate != "" { endUnix := parseDateToUnix(endDate, true) - query += " AND access_time <= ?" - args = append(args, endUnix) + query = query.Where("access_time <= ?", endUnix) } - query += ` - GROUP BY date - ORDER BY date ASC - ` + var results []UVTrendData + err := query.Group("date"). + Order("date ASC"). + Scan(&results).Error - rows, err := config.DB.Query(query, args...) if err != nil { return nil, err } - defer rows.Close() - var results []UVTrendData - for rows.Next() { - var r UVTrendData - if err := rows.Scan(&r.Date, &r.Count); err != nil { - return nil, err - } - // 暂未实现真实的同比环比计算逻辑,设为0 - r.YoY = 0 - r.MoM = 0 - results = append(results, r) + // 暂未实现真实的同比环比计算逻辑,设为0 + for i := range results { + results[i].YoY = 0 + results[i].MoM = 0 } + return results, nil } @@ -108,52 +91,27 @@ func GetUserRegions(startDate, endDate string) ([]struct { Region string Count int }, error) { - query := ` - SELECT - COALESCE(NULLIF(user_location, ''), 'Unknown') as region, - COUNT(DISTINCT user_ip) as count - FROM user_access_logs - WHERE 1=1 - ` - args := []interface{}{} + query := config.DB.Model(&models.UserAccessLog{}). + Select("COALESCE(NULLIF(user_location, ''), 'Unknown') as region, COUNT(DISTINCT user_ip) as count") if startDate != "" { startUnix := parseDateToUnix(startDate, false) - query += " AND access_time >= ?" - args = append(args, startUnix) + query = query.Where("access_time >= ?", startUnix) } if endDate != "" { endUnix := parseDateToUnix(endDate, true) - query += " AND access_time <= ?" - args = append(args, endUnix) + query = query.Where("access_time <= ?", endUnix) } - query += ` - GROUP BY region - ORDER BY count DESC - LIMIT 20 - ` - - rows, err := config.DB.Query(query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - var results []struct { Region string Count int } - for rows.Next() { - var r struct { - Region string - Count int - } - if err := rows.Scan(&r.Region, &r.Count); err != nil { - return nil, err - } - results = append(results, r) - } - return results, nil + err := query.Group("region"). + Order("count DESC"). + Limit(20). + Scan(&results).Error + + return results, err } diff --git a/server/repositories/migration.go b/server/repositories/migration.go index 8db4cac..96bccb9 100644 --- a/server/repositories/migration.go +++ b/server/repositories/migration.go @@ -51,15 +51,14 @@ func MigrateToBigInt() { } func columnExists(tableName, colName string) bool { - query := ` + var count int64 + err := config.DB.Raw(` SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? - ` - var count int - err := config.DB.QueryRow(query, tableName, colName).Scan(&count) + `, tableName, colName).Scan(&count).Error if err != nil { log.Printf("Error checking column %s.%s: %v", tableName, colName, err) return false @@ -68,15 +67,14 @@ func columnExists(tableName, colName string) bool { } func isBigInt(tableName, colName string) bool { - query := ` + var dataType string + err := config.DB.Raw(` SELECT DATA_TYPE FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? - ` - var dataType string - err := config.DB.QueryRow(query, tableName, colName).Scan(&dataType) + `, tableName, colName).Scan(&dataType).Error if err != nil { return false } @@ -108,7 +106,7 @@ func migrateColumn(table, col string) { } func execSQL(query string) { - _, err := config.DB.Exec(query) + err := config.DB.Exec(query).Error if err != nil { // Log but continue (might fail if column doesn't exist etc) log.Printf("SQL Error: %v | Query: %s", err, query) diff --git a/server/repositories/operation_log_repository.go b/server/repositories/operation_log_repository.go index 4d7d54d..2f8d09a 100644 --- a/server/repositories/operation_log_repository.go +++ b/server/repositories/operation_log_repository.go @@ -10,80 +10,41 @@ import ( // CreateOperationLog 创建操作日志 func CreateOperationLog(operationLog *models.OperationLog) error { - now := time.Now().Unix() - query := ` - INSERT INTO operation_logs (user_id, username, ip, path, method, params, status, duration, created_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0) - ` - _, err := config.DB.Exec( - query, - operationLog.UserID, - operationLog.Username, - operationLog.IP, - operationLog.Path, - operationLog.Method, - operationLog.Params, - operationLog.Status, - operationLog.Duration, - now, - ) + err := config.DB.Create(operationLog).Error if err != nil { log.Printf("Error creating operation log: %v", err) return err } - return nil } // GetOperationLogs 获取操作日志列表 func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error) { - // 计算偏移量 offset := (page - 1) * pageSize - // 获取总记录数 + var logs []models.OperationLog var total int64 - countQuery := "SELECT COUNT(*) FROM operation_logs WHERE deleted_at = 0" - if err := config.DB.QueryRow(countQuery).Scan(&total); err != nil { + + // 获取总记录数 + err := config.DB.Model(&models.OperationLog{}). + Where("deleted_at = ?", 0). + Count(&total).Error + if err != nil { log.Printf("Error counting operation logs: %v", err) return nil, 0, err } // 获取分页数据 - query := ` - SELECT id, user_id, username, ip, path, method, params, status, duration, created_at, deleted_at - FROM operation_logs - WHERE deleted_at = 0 - ORDER BY created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := config.DB.Query(query, pageSize, offset) + err = config.DB.Model(&models.OperationLog{}). + Where("deleted_at = ?", 0). + Order("created_at DESC"). + Limit(pageSize). + Offset(offset). + Find(&logs).Error if err != nil { log.Printf("Error querying operation logs: %v", err) return nil, 0, err } - defer rows.Close() - - var logs []models.OperationLog - for rows.Next() { - var operationLog models.OperationLog - if err := rows.Scan( - &operationLog.ID, - &operationLog.UserID, - &operationLog.Username, - &operationLog.IP, - &operationLog.Path, - &operationLog.Method, - &operationLog.Params, - &operationLog.Status, - &operationLog.Duration, - &operationLog.CreatedAt, - &operationLog.DeletedAt, - ); err != nil { - log.Printf("Error scanning operation log: %v", err) - continue - } - logs = append(logs, operationLog) - } return logs, total, nil } diff --git a/server/repositories/permission_repository.go b/server/repositories/permission_repository.go index 09a9858..db43487 100644 --- a/server/repositories/permission_repository.go +++ b/server/repositories/permission_repository.go @@ -10,53 +10,28 @@ import ( // GetPermissions 获取所有权限 func GetPermissions() ([]models.Permission, error) { - // Filter deleted_at = 0 - query := "SELECT id, name, resource, action, created_at, updated_at, deleted_at FROM permissions WHERE deleted_at = 0" - rows, err := config.DB.Query(query) + var permissions []models.Permission + err := config.DB.Model(&models.Permission{}). + Where("deleted_at = ?", 0). + Find(&permissions).Error if err != nil { log.Printf("Error getting permissions: %v", err) return nil, err } - defer rows.Close() - - var permissions []models.Permission - for rows.Next() { - var permission models.Permission - if err := rows.Scan(&permission.ID, &permission.Name, &permission.Resource, &permission.Action, &permission.CreatedAt, &permission.UpdatedAt, &permission.DeletedAt); err != nil { - log.Printf("Error scanning permission: %v", err) - continue - } - permissions = append(permissions, permission) - } - return permissions, nil } // GetPermissionsByRoleID 获取指定角色的权限 func GetPermissionsByRoleID(roleID uint) ([]models.Permission, error) { - query := ` - SELECT p.id, p.name, p.resource, p.action, p.created_at, p.updated_at, p.deleted_at - FROM permissions p - JOIN role_permissions rp ON p.id = rp.permission_id - WHERE rp.role_id = ? AND p.deleted_at = 0 - ` - rows, err := config.DB.Query(query, roleID) + var permissions []models.Permission + err := config.DB.Model(&models.Permission{}). + Joins("JOIN role_permissions rp ON permissions.id = rp.permission_id"). + Where("rp.role_id = ? AND permissions.deleted_at = ?", roleID, 0). + Find(&permissions).Error if err != nil { log.Printf("Error getting permissions by role id: %v", err) return nil, err } - defer rows.Close() - - var permissions []models.Permission - for rows.Next() { - var permission models.Permission - if err := rows.Scan(&permission.ID, &permission.Name, &permission.Resource, &permission.Action, &permission.CreatedAt, &permission.UpdatedAt, &permission.DeletedAt); err != nil { - log.Printf("Error scanning permission: %v", err) - continue - } - permissions = append(permissions, permission) - } - return permissions, nil } diff --git a/server/repositories/post_repository.go b/server/repositories/post_repository.go index 7228d62..3aa9e69 100644 --- a/server/repositories/post_repository.go +++ b/server/repositories/post_repository.go @@ -1,12 +1,12 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // TrendData 趋势数据 @@ -19,143 +19,57 @@ type TrendData struct { // GetPosts 获取所有博客文章(支持搜索、分类、标签筛选) func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error) { - query := ` - SELECT p.id, p.title, p.category_id, c.name, c.slug, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at - FROM posts p - LEFT JOIN categories c ON p.category_id = c.id - ` - - whereClause := " WHERE p.is_published = 1 AND p.deleted_at = 0" - args := []interface{}{} + var posts []models.Post + query := config.DB.Model(&models.Post{}). + Preload("Category"). + Preload("Tags"). + Where("is_published = ? AND deleted_at = ?", 1, 0) if tagID > 0 { - query += " JOIN post_tags pt ON p.id = pt.post_id" - whereClause += " AND pt.tag_id = ?" - args = append(args, tagID) + query = query.Joins("JOIN post_tags pt ON posts.id = pt.post_id"). + Where("pt.tag_id = ?", tagID) } if categoryID > 0 { - whereClause += " AND p.category_id = ?" - args = append(args, categoryID) + query = query.Where("category_id = ?", categoryID) } if keyword != "" { - whereClause += ` AND ( - MATCH(p.title, p.content) AGAINST(? IN BOOLEAN MODE) OR - p.title LIKE ? OR - p.content LIKE ? - )` likeKeyword := "%" + keyword + "%" - args = append(args, keyword, likeKeyword, likeKeyword) + query = query.Where("(MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ?)", + keyword, likeKeyword, likeKeyword) } - query += whereClause + " ORDER BY p.created_at DESC" - - rows, err := config.DB.Query(query, args...) + err := query.Order("created_at DESC").Find(&posts).Error if err != nil { log.Printf("Error querying posts: %v", err) return nil, err } - defer rows.Close() - - var posts []models.Post - for rows.Next() { - var post models.Post - var catID sql.NullInt64 - var catName sql.NullString - var catSlug sql.NullString - - if err := rows.Scan( - &post.ID, - &post.Title, - &catID, - &catName, - &catSlug, - &post.Excerpt, - &post.Content, - &post.ReadCount, - &post.IsPublished, - &post.CreatedAt, - &post.UpdatedAt, - &post.DeletedAt, - ); err != nil { - log.Printf("Error scanning post: %v", err) - continue - } - - if catID.Valid { - post.CategoryID = uint(catID.Int64) - post.Category = &models.Category{ - ID: uint(catID.Int64), - Name: catName.String, - Slug: catSlug.String, - } - } - - // TODO: Fetch tags if needed, or lazy load - - posts = append(posts, post) - } return posts, nil } // GetPostByID 根据ID获取博客文章 func GetPostByID(id uint) (*models.Post, error) { - query := ` - SELECT p.id, p.title, p.category_id, c.name, c.slug, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at - FROM posts p - LEFT JOIN categories c ON p.category_id = c.id - WHERE p.id = ? AND p.is_published = 1 AND p.deleted_at = 0 - ` - row := config.DB.QueryRow(query, id) - var post models.Post - var catID sql.NullInt64 - var catName sql.NullString - var catSlug sql.NullString + err := config.DB.Model(&models.Post{}). + Preload("Category"). + Preload("Tags"). + Where("id = ? AND is_published = ? AND deleted_at = ?", id, 1, 0). + First(&post).Error - if err := row.Scan( - &post.ID, - &post.Title, - &catID, - &catName, - &catSlug, - &post.Excerpt, - &post.Content, - &post.ReadCount, - &post.IsPublished, - &post.CreatedAt, - &post.UpdatedAt, - &post.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning post by ID: %v", err) + log.Printf("Error getting post by ID: %v", err) return nil, err } - if catID.Valid { - post.CategoryID = uint(catID.Int64) - post.Category = &models.Category{ - ID: uint(catID.Int64), - Name: catName.String, - Slug: catSlug.String, - } - } - - // 获取标签 - tags, err := GetTagsByPostID(post.ID) - if err == nil { - post.Tags = tags - } - // 更新阅读量 - updateReadCountQuery := "UPDATE posts SET read_count = read_count + 1 WHERE id = ?" - if _, err := config.DB.Exec(updateReadCountQuery, id); err != nil { - log.Printf("Error updating post read count: %v", err) - } + config.DB.Model(&models.Post{}). + Where("id = ?", id). + UpdateColumn("read_count", gorm.Expr("read_count + ?", 1)) return &post, nil } @@ -164,100 +78,49 @@ func GetPostByID(id uint) (*models.Post, error) { func GetAllPosts(page, pageSize int) ([]models.Post, int64, error) { offset := (page - 1) * pageSize - // Count total + var posts []models.Post var total int64 - config.DB.QueryRow("SELECT COUNT(*) FROM posts WHERE deleted_at = 0").Scan(&total) - query := ` - SELECT p.id, p.title, p.category_id, c.name, c.slug, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at - FROM posts p - LEFT JOIN categories c ON p.category_id = c.id - WHERE p.deleted_at = 0 - ORDER BY p.created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := config.DB.Query(query, pageSize, offset) + // Count total + err := config.DB.Model(&models.Post{}). + Where("deleted_at = ?", 0). + Count(&total).Error + if err != nil { + log.Printf("Error counting posts: %v", err) + return nil, 0, err + } + + // Get posts + err = config.DB.Model(&models.Post{}). + Preload("Category"). + Where("deleted_at = ?", 0). + Order("created_at DESC"). + Limit(pageSize). + Offset(offset). + Find(&posts).Error + if err != nil { log.Printf("Error querying all posts: %v", err) return nil, 0, err } - defer rows.Close() - - var posts []models.Post - for rows.Next() { - var post models.Post - var catID sql.NullInt64 - var catName sql.NullString - var catSlug sql.NullString - - if err := rows.Scan( - &post.ID, - &post.Title, - &catID, - &catName, - &catSlug, - &post.Excerpt, - &post.Content, - &post.ReadCount, - &post.IsPublished, - &post.CreatedAt, - &post.UpdatedAt, - &post.DeletedAt, - ); err != nil { - log.Printf("Error scanning post: %v", err) - continue - } - - if catID.Valid { - post.CategoryID = uint(catID.Int64) - post.Category = &models.Category{ - ID: uint(catID.Int64), - Name: catName.String, - Slug: catSlug.String, - } - } - posts = append(posts, post) - } return posts, total, nil } // CreatePost 创建博客文章 func CreatePost(post *models.Post) error { - now := time.Now().Unix() - - // Insert Post - query := ` - INSERT INTO posts (title, category_id, excerpt, content, is_published, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, 0) - ` - result, err := config.DB.Exec( - query, - post.Title, - post.CategoryID, - post.Excerpt, - post.Content, - post.IsPublished, - now, - now, - ) + err := config.DB.Create(post).Error if err != nil { log.Printf("Error creating post: %v", err) return err } - id, err := result.LastInsertId() - if err != nil { - return err - } - post.ID = uint(id) - post.CreatedAt = now - post.UpdatedAt = now - // Insert Tags if len(post.Tags) > 0 { - for _, tag := range post.Tags { - AddTagToPost(post.ID, tag.ID) + err = config.DB.Model(post).Association("Tags").Replace(post.Tags) + if err != nil { + log.Printf("Error associating tags: %v", err) + return err } } @@ -266,32 +129,35 @@ func CreatePost(post *models.Post) error { // UpdatePost 更新博客文章 func UpdatePost(post *models.Post) error { - now := time.Now().Unix() - query := ` - UPDATE posts SET title = ?, category_id = ?, excerpt = ?, content = ?, is_published = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - post.Title, - post.CategoryID, - post.Excerpt, - post.Content, - post.IsPublished, - now, - post.ID, - ) + err := config.DB.Model(&models.Post{}). + Where("id = ? AND deleted_at = ?", post.ID, 0). + Updates(map[string]interface{}{ + "title": post.Title, + "category_id": post.CategoryID, + "excerpt": post.Excerpt, + "content": post.Content, + "is_published": post.IsPublished, + "updated_at": time.Now().Unix(), + }).Error + if err != nil { log.Printf("Error updating post: %v", err) return err } - // Update Tags: Delete all and re-insert - // Note: This is a simple approach. Better approach is to diff. - config.DB.Exec("DELETE FROM post_tags WHERE post_id = ?", post.ID) + // Update Tags if len(post.Tags) > 0 { - for _, tag := range post.Tags { - AddTagToPost(post.ID, tag.ID) + err = config.DB.Model(&models.Post{ID: post.ID}).Association("Tags").Replace(post.Tags) + if err != nil { + log.Printf("Error updating tags: %v", err) + return err + } + } else { + // Clear all tags + err = config.DB.Model(&models.Post{ID: post.ID}).Association("Tags").Clear() + if err != nil { + log.Printf("Error clearing tags: %v", err) + return err } } @@ -300,17 +166,20 @@ func UpdatePost(post *models.Post) error { // UpdatePostStatus 更新文章状态 func UpdatePostStatus(id uint, status int) error { - now := time.Now().Unix() - query := "UPDATE posts SET is_published = ?, updated_at = ? WHERE id = ? AND deleted_at = 0" - _, err := config.DB.Exec(query, status, now, id) + err := config.DB.Model(&models.Post{}). + Where("id = ? AND deleted_at = ?", id, 0). + Updates(map[string]interface{}{ + "is_published": status, + "updated_at": time.Now().Unix(), + }).Error return err } // DeletePost 删除博客文章 (Soft Delete) func DeletePost(id uint) error { - now := time.Now().Unix() - query := "UPDATE posts SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Post{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting post: %v", err) return err @@ -320,17 +189,15 @@ func DeletePost(id uint) error { // GetPostCount 获取文章总数 func GetPostCount() (int, error) { - var count int - query := "SELECT COUNT(*) FROM posts WHERE deleted_at = 0" - row := config.DB.QueryRow(query) - - err := row.Scan(&count) + var count int64 + err := config.DB.Model(&models.Post{}). + Where("deleted_at = ?", 0). + Count(&count).Error if err != nil { log.Printf("Error getting post count: %v", err) return 0, err } - - return count, nil + return int(count), nil } // BuildPostResponse 构建博客文章响应 @@ -375,33 +242,28 @@ func BuildPostsResponse(posts []models.Post) []models.PostResponse { // SavePostHistory 保存文章历史记录 func SavePostHistory(post *models.Post, modifiedBy uint) error { // 获取当前最大版本号 - var maxVersion uint - query := "SELECT COALESCE(MAX(version), 0) FROM post_history WHERE post_id = ?" - if err := config.DB.QueryRow(query, post.ID).Scan(&maxVersion); err != nil { + var maxVersion int + err := config.DB.Model(&models.PostHistory{}). + Where("post_id = ?", post.ID). + Select("COALESCE(MAX(version), 0)"). + Scan(&maxVersion).Error + if err != nil { log.Printf("Error getting max version: %v", err) return err } - now := time.Now().Unix() - insertQuery := ` - INSERT INTO post_history ( - post_id, version, title, category_id, excerpt, content, - is_published, modified_by, modified_at, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ` - _, err := config.DB.Exec( - insertQuery, - post.ID, - maxVersion+1, - post.Title, - post.CategoryID, - post.Excerpt, - post.Content, - post.IsPublished, - modifiedBy, - now, - now, - ) + history := &models.PostHistory{ + PostID: post.ID, + Version: maxVersion + 1, + Title: post.Title, + CategoryID: post.CategoryID, + Excerpt: post.Excerpt, + Content: post.Content, + IsPublished: post.IsPublished, + ModifiedBy: modifiedBy, + } + + err = config.DB.Create(history).Error if err != nil { log.Printf("Error saving post history: %v", err) return err @@ -412,119 +274,79 @@ func SavePostHistory(post *models.Post, modifiedBy uint) error { // GetTopPosts 获取热门文章 (按阅读量) func GetTopPosts(limit int) ([]models.Post, error) { - // Simple query without category join for dashboard to avoid complexity if not needed - // Or join if needed. Dashboard usually needs Title. - query := "SELECT id, title, read_count FROM posts WHERE is_published = 1 AND deleted_at = 0 ORDER BY read_count DESC LIMIT ?" - rows, err := config.DB.Query(query, limit) + var posts []models.Post + err := config.DB.Model(&models.Post{}). + Select("id, title, read_count"). + Where("is_published = ? AND deleted_at = ?", 1, 0). + Order("read_count DESC"). + Limit(limit). + Find(&posts).Error if err != nil { return nil, err } - defer rows.Close() - - var posts []models.Post - for rows.Next() { - var post models.Post - if err := rows.Scan(&post.ID, &post.Title, &post.ReadCount); err != nil { - continue - } - posts = append(posts, post) - } return posts, nil } // GetNewPostsTrend 获取新增文章趋势 func GetNewPostsTrend(startDate, endDate string) ([]TrendData, error) { - // Same as before - query := ` - SELECT FROM_UNIXTIME(created_at, '%Y-%m-%d') as date, COUNT(*) as count - FROM posts - WHERE deleted_at = 0 - ` - args := []interface{}{} + query := config.DB.Model(&models.Post{}). + Select("FROM_UNIXTIME(created_at, '%Y-%m-%d') as date, COUNT(*) as count"). + Where("deleted_at = ?", 0) if startDate != "" { startUnix := parseDateToUnix(startDate, false) - query += " AND created_at >= ?" - args = append(args, startUnix) + query = query.Where("created_at >= ?", startUnix) } else { startUnix := time.Now().AddDate(0, 0, -6).Unix() - query += " AND created_at >= ?" - args = append(args, startUnix) + query = query.Where("created_at >= ?", startUnix) } if endDate != "" { endUnix := parseDateToUnix(endDate, true) - query += " AND created_at <= ?" - args = append(args, endUnix) + query = query.Where("created_at <= ?", endUnix) } - query += ` - GROUP BY date - ORDER BY date ASC - ` + var results []TrendData + err := query.Group("date"). + Order("date ASC"). + Scan(&results).Error - rows, err := config.DB.Query(query, args...) if err != nil { return nil, err } - defer rows.Close() - var results []TrendData - for rows.Next() { - var r TrendData - if err := rows.Scan(&r.Date, &r.Count); err != nil { - return nil, err - } - r.YoY = 0 - r.MoM = 0 - results = append(results, r) + // Set YoY and MoM to 0 + for i := range results { + results[i].YoY = 0 + results[i].MoM = 0 } + return results, nil } // GetPostHistory 获取文章修改历史 func GetPostHistory(postID uint) ([]models.PostHistory, error) { - query := ` - SELECT id, post_id, version, title, category_id, excerpt, content, is_published, modified_by, modified_at, created_at - FROM post_history - WHERE post_id = ? - ORDER BY version DESC - ` - rows, err := config.DB.Query(query, postID) + var history []models.PostHistory + err := config.DB.Model(&models.PostHistory{}). + Where("post_id = ?", postID). + Order("version DESC"). + Find(&history).Error if err != nil { return nil, err } - defer rows.Close() - - var history []models.PostHistory - for rows.Next() { - var h models.PostHistory - if err := rows.Scan( - &h.ID, &h.PostID, &h.Version, &h.Title, &h.CategoryID, - &h.Excerpt, &h.Content, &h.IsPublished, - &h.ModifiedBy, &h.ModifiedAt, &h.CreatedAt, - ); err != nil { - continue - } - history = append(history, h) - } return history, nil } // GetPostHistoryByVersion 获取特定版本的历史记录 func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, error) { - query := ` - SELECT id, post_id, version, title, category_id, excerpt, content, is_published, modified_by, modified_at, created_at - FROM post_history - WHERE post_id = ? AND version = ? - ` var h models.PostHistory - err := config.DB.QueryRow(query, postID, version).Scan( - &h.ID, &h.PostID, &h.Version, &h.Title, &h.CategoryID, - &h.Excerpt, &h.Content, &h.IsPublished, - &h.ModifiedBy, &h.ModifiedAt, &h.CreatedAt, - ) + err := config.DB.Model(&models.PostHistory{}). + Where("post_id = ? AND version = ?", postID, version). + First(&h).Error if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } return nil, err } return &h, nil diff --git a/server/repositories/role_repository.go b/server/repositories/role_repository.go index 3744207..8b565e1 100644 --- a/server/repositories/role_repository.go +++ b/server/repositories/role_repository.go @@ -6,94 +6,75 @@ import ( "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetRoles 获取所有角色 func GetRoles() ([]models.Role, error) { - query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE deleted_at = 0" - rows, err := config.DB.Query(query) + var roles []models.Role + err := config.DB.Model(&models.Role{}). + Preload("Permissions"). + Where("deleted_at = ?", 0). + Find(&roles).Error if err != nil { log.Printf("Error getting roles: %v", err) return nil, err } - defer rows.Close() - - var roles []models.Role - for rows.Next() { - var role models.Role - if err := rows.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil { - log.Printf("Error scanning role: %v", err) - continue - } - - // 获取权限 - permissions, _ := GetPermissionsByRoleID(role.ID) - role.Permissions = permissions - - roles = append(roles, role) - } - return roles, nil } // GetRoleByID 根据ID获取角色 func GetRoleByID(id uint) (*models.Role, error) { - query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE id = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, id) - var role models.Role - if err := row.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil { + err := config.DB.Model(&models.Role{}). + Preload("Permissions"). + Where("id = ? AND deleted_at = ?", id, 0). + First(&role).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } log.Printf("Error getting role by ID: %v", err) return nil, err } - - // 获取权限 - permissions, _ := GetPermissionsByRoleID(role.ID) - role.Permissions = permissions - return &role, nil } // GetRoleByName 根据名称获取角色 func GetRoleByName(name string) (*models.Role, error) { - query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE name = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, name) - var role models.Role - if err := row.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil { + err := config.DB.Model(&models.Role{}). + Where("name = ? AND deleted_at = ?", name, 0). + First(&role).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } log.Printf("Error getting role by name: %v", err) return nil, err } - return &role, nil } // CreateRole 创建角色 func CreateRole(role *models.Role) error { - now := time.Now().Unix() - query := "INSERT INTO roles (name, description, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, 0)" - result, err := config.DB.Exec(query, role.Name, role.Description, now, now) + err := config.DB.Create(role).Error if err != nil { log.Printf("Error creating role: %v", err) return err } - - id, err := result.LastInsertId() - if err != nil { - return err - } - role.ID = uint(id) - role.CreatedAt = now - role.UpdatedAt = now - return nil } // UpdateRole 更新角色 func UpdateRole(role *models.Role) error { - now := time.Now().Unix() - query := "UPDATE roles SET name = ?, description = ?, updated_at = ? WHERE id = ? AND deleted_at = 0" - _, err := config.DB.Exec(query, role.Name, role.Description, now, role.ID) + err := config.DB.Model(&models.Role{}). + Where("id = ? AND deleted_at = ?", role.ID, 0). + Updates(map[string]interface{}{ + "name": role.Name, + "description": role.Description, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating role: %v", err) return err @@ -103,9 +84,9 @@ func UpdateRole(role *models.Role) error { // DeleteRole 删除角色 (Soft Delete) func DeleteRole(id uint) error { - now := time.Now().Unix() - query := "UPDATE roles SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Role{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting role: %v", err) return err @@ -115,36 +96,25 @@ func DeleteRole(id uint) error { // AssignPermissionsToRole 给角色分配权限 func AssignPermissionsToRole(roleID uint, permissionIDs []uint) error { - // 开启事务 - tx, err := config.DB.Begin() - if err != nil { - return err - } - - // 先删除旧权限 - _, err = tx.Exec("DELETE FROM role_permissions WHERE role_id = ?", roleID) - if err != nil { - tx.Rollback() - return err - } - - // 插入新权限 - stmt, err := tx.Prepare("INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)") - if err != nil { - tx.Rollback() - return err - } - defer stmt.Close() - - for _, permID := range permissionIDs { - _, err = stmt.Exec(roleID, permID) + return config.DB.Transaction(func(tx *gorm.DB) error { + // 先删除旧权限 + err := tx.Exec("DELETE FROM role_permissions WHERE role_id = ?", roleID).Error if err != nil { - tx.Rollback() return err } - } - return tx.Commit() + // 插入新权限 + if len(permissionIDs) > 0 { + for _, permID := range permissionIDs { + err = tx.Exec("INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)", roleID, permID).Error + if err != nil { + return err + } + } + } + + return nil + }) } // BuildRoleResponse 构建角色响应 diff --git a/server/repositories/services_repository.go b/server/repositories/services_repository.go index 8ca3fe1..3181e90 100644 --- a/server/repositories/services_repository.go +++ b/server/repositories/services_repository.go @@ -2,6 +2,7 @@ package repositories import ( "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -9,52 +10,21 @@ import ( // GetTestimonials 获取所有客户评价 func GetTestimonials() ([]models.Testimonial, error) { - query := "SELECT id, name, role, content, avatar, rating, sort_order, created_at, updated_at FROM testimonials ORDER BY sort_order ASC, created_at DESC" - rows, err := config.DB.Query(query) + var testimonials []models.Testimonial + err := config.DB.Model(&models.Testimonial{}). + Where("deleted_at = ?", 0). + Order("sort_order ASC, created_at DESC"). + Find(&testimonials).Error if err != nil { log.Printf("Error querying testimonials: %v", err) return nil, err } - defer rows.Close() - - var testimonials []models.Testimonial - for rows.Next() { - var t models.Testimonial - if err := rows.Scan( - &t.ID, - &t.Name, - &t.Role, - &t.Content, - &t.Avatar, - &t.Rating, - &t.SortOrder, - &t.CreatedAt, - &t.UpdatedAt, - ); err != nil { - log.Printf("Error scanning testimonial: %v", err) - continue - } - testimonials = append(testimonials, t) - } - return testimonials, nil } // CreateTestimonial 创建客户评价 func CreateTestimonial(t *models.Testimonial) error { - query := ` - INSERT INTO testimonials (name, role, content, avatar, rating, sort_order, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW()) - ` - _, err := config.DB.Exec( - query, - t.Name, - t.Role, - t.Content, - t.Avatar, - t.Rating, - t.SortOrder, - ) + err := config.DB.Create(t).Error if err != nil { log.Printf("Error creating testimonial: %v", err) return err @@ -64,20 +34,17 @@ func CreateTestimonial(t *models.Testimonial) error { // UpdateTestimonial 更新客户评价 func UpdateTestimonial(t *models.Testimonial) error { - query := ` - UPDATE testimonials SET name = ?, role = ?, content = ?, avatar = ?, rating = ?, sort_order = ?, updated_at = NOW() - WHERE id = ? - ` - _, err := config.DB.Exec( - query, - t.Name, - t.Role, - t.Content, - t.Avatar, - t.Rating, - t.SortOrder, - t.ID, - ) + err := config.DB.Model(&models.Testimonial{}). + Where("id = ?", t.ID). + Updates(map[string]interface{}{ + "name": t.Name, + "role": t.Role, + "content": t.Content, + "avatar": t.Avatar, + "rating": t.Rating, + "sort_order": t.SortOrder, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating testimonial: %v", err) return err @@ -87,8 +54,9 @@ func UpdateTestimonial(t *models.Testimonial) error { // DeleteTestimonial 删除客户评价 func DeleteTestimonial(id uint) error { - query := "DELETE FROM testimonials WHERE id = ?" - _, err := config.DB.Exec(query, id) + err := config.DB.Model(&models.Testimonial{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting testimonial: %v", err) return err @@ -98,50 +66,21 @@ func DeleteTestimonial(id uint) error { // GetPartners 获取所有合作伙伴 func GetPartners() ([]models.Partner, error) { - query := "SELECT id, name, logo, description, url, sort_order, created_at, updated_at FROM partners ORDER BY sort_order ASC, created_at DESC" - rows, err := config.DB.Query(query) + var partners []models.Partner + err := config.DB.Model(&models.Partner{}). + Where("deleted_at = ?", 0). + Order("sort_order ASC, created_at DESC"). + Find(&partners).Error if err != nil { log.Printf("Error querying partners: %v", err) return nil, err } - defer rows.Close() - - var partners []models.Partner - for rows.Next() { - var p models.Partner - if err := rows.Scan( - &p.ID, - &p.Name, - &p.Logo, - &p.Description, - &p.URL, - &p.SortOrder, - &p.CreatedAt, - &p.UpdatedAt, - ); err != nil { - log.Printf("Error scanning partner: %v", err) - continue - } - partners = append(partners, p) - } - return partners, nil } // CreatePartner 创建合作伙伴 func CreatePartner(p *models.Partner) error { - query := ` - INSERT INTO partners (name, logo, description, url, sort_order, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, NOW(), NOW()) - ` - _, err := config.DB.Exec( - query, - p.Name, - p.Logo, - p.Description, - p.URL, - p.SortOrder, - ) + err := config.DB.Create(p).Error if err != nil { log.Printf("Error creating partner: %v", err) return err @@ -151,19 +90,16 @@ func CreatePartner(p *models.Partner) error { // UpdatePartner 更新合作伙伴 func UpdatePartner(p *models.Partner) error { - query := ` - UPDATE partners SET name = ?, logo = ?, description = ?, url = ?, sort_order = ?, updated_at = NOW() - WHERE id = ? - ` - _, err := config.DB.Exec( - query, - p.Name, - p.Logo, - p.Description, - p.URL, - p.SortOrder, - p.ID, - ) + err := config.DB.Model(&models.Partner{}). + Where("id = ?", p.ID). + Updates(map[string]interface{}{ + "name": p.Name, + "logo": p.Logo, + "description": p.Description, + "url": p.URL, + "sort_order": p.SortOrder, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating partner: %v", err) return err @@ -173,8 +109,9 @@ func UpdatePartner(p *models.Partner) error { // DeletePartner 删除合作伙伴 func DeletePartner(id uint) error { - query := "DELETE FROM partners WHERE id = ?" - _, err := config.DB.Exec(query, id) + err := config.DB.Model(&models.Partner{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting partner: %v", err) return err diff --git a/server/repositories/setting_repository.go b/server/repositories/setting_repository.go index d6779e7..2f48862 100644 --- a/server/repositories/setting_repository.go +++ b/server/repositories/setting_repository.go @@ -1,135 +1,79 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetSettings 获取所有系统配置 func GetSettings() ([]models.Setting, error) { - query := "SELECT id, key_name, value, description, created_at, updated_at, deleted_at FROM settings WHERE deleted_at = 0 ORDER BY key_name" - rows, err := config.DB.Query(query) + var settings []models.Setting + err := config.DB.Model(&models.Setting{}). + Where("deleted_at = ?", 0). + Order("key_name"). + Find(&settings).Error if err != nil { log.Printf("Error querying settings: %v", err) return nil, err } - defer rows.Close() - - var settings []models.Setting - for rows.Next() { - var setting models.Setting - if err := rows.Scan( - &setting.ID, - &setting.KeyName, - &setting.Value, - &setting.Description, - &setting.CreatedAt, - &setting.UpdatedAt, - &setting.DeletedAt, - ); err != nil { - log.Printf("Error scanning setting: %v", err) - continue - } - settings = append(settings, setting) - } - return settings, nil } // GetSettingByKey 根据键名获取系统配置 func GetSettingByKey(keyName string) (*models.Setting, error) { - query := "SELECT id, key_name, value, description, created_at, updated_at, deleted_at FROM settings WHERE key_name = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, keyName) - var setting models.Setting - if err := row.Scan( - &setting.ID, - &setting.KeyName, - &setting.Value, - &setting.Description, - &setting.CreatedAt, - &setting.UpdatedAt, - &setting.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Setting{}). + Where("key_name = ? AND deleted_at = ?", keyName, 0). + First(&setting).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning setting by key: %v", err) + log.Printf("Error getting setting by key: %v", err) return nil, err } - return &setting, nil } // UpdateSetting 更新系统配置 func UpdateSetting(setting *models.Setting) error { - now := time.Now().Unix() - query := ` - UPDATE settings SET value = ?, description = ?, updated_at = ? - WHERE key_name = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - setting.Value, - setting.Description, - now, - setting.KeyName, - ) + err := config.DB.Model(&models.Setting{}). + Where("key_name = ? AND deleted_at = ?", setting.KeyName, 0). + Updates(map[string]interface{}{ + "value": setting.Value, + "description": setting.Description, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating setting: %v", err) return err } - return nil } // CreateSetting 创建系统配置 func CreateSetting(setting *models.Setting) error { - now := time.Now().Unix() - query := ` - INSERT INTO settings (key_name, value, description, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, 0) - ` - result, err := config.DB.Exec( - query, - setting.KeyName, - setting.Value, - setting.Description, - now, - now, - ) + err := config.DB.Create(setting).Error if err != nil { log.Printf("Error creating setting: %v", err) return err } - - // 获取自增ID - id, err := result.LastInsertId() - if err != nil { - log.Printf("Error getting last insert ID: %v", err) - return err - } - setting.ID = uint(id) - setting.CreatedAt = now - setting.UpdatedAt = now - return nil } // DeleteSetting 删除系统配置 (Soft Delete) func DeleteSetting(keyName string) error { - now := time.Now().Unix() - query := "UPDATE settings SET deleted_at = ? WHERE key_name = ?" - _, err := config.DB.Exec(query, now, keyName) + err := config.DB.Model(&models.Setting{}). + Where("key_name = ?", keyName). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting setting: %v", err) return err } - return nil } @@ -161,22 +105,20 @@ func GetAllSettings() (map[string]string, error) { // UpdateSettings 批量更新系统配置 func UpdateSettings(settings map[string]string) error { - tx, err := config.DB.Begin() - if err != nil { - return err - } - - now := time.Now().Unix() - query := "UPDATE settings SET value = ?, updated_at = ? WHERE key_name = ? AND deleted_at = 0" - - for key, value := range settings { - _, err := tx.Exec(query, value, now, key) - if err != nil { - tx.Rollback() - log.Printf("Error updating setting %s: %v", key, err) - return err + return config.DB.Transaction(func(tx *gorm.DB) error { + now := time.Now().Unix() + for key, value := range settings { + err := tx.Model(&models.Setting{}). + Where("key_name = ? AND deleted_at = ?", key, 0). + Updates(map[string]interface{}{ + "value": value, + "updated_at": now, + }).Error + if err != nil { + log.Printf("Error updating setting %s: %v", key, err) + return err + } } - } - - return tx.Commit() + return nil + }) } diff --git a/server/repositories/snippet_repository.go b/server/repositories/snippet_repository.go index cfce394..8b9a726 100644 --- a/server/repositories/snippet_repository.go +++ b/server/repositories/snippet_repository.go @@ -1,76 +1,46 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetSnippets 获取所有代码片段 func GetSnippets() ([]models.Snippet, error) { - query := "SELECT id, title, code, type, description, view_count, created_at, updated_at, deleted_at FROM snippets WHERE deleted_at = 0 ORDER BY created_at DESC" - rows, err := config.DB.Query(query) + var snippets []models.Snippet + err := config.DB.Model(&models.Snippet{}). + Where("deleted_at = ?", 0). + Order("created_at DESC"). + Find(&snippets).Error if err != nil { log.Printf("Error querying snippets: %v", err) return nil, err } - defer rows.Close() - - var snippets []models.Snippet - for rows.Next() { - var snippet models.Snippet - if err := rows.Scan( - &snippet.ID, - &snippet.Title, - &snippet.Code, - &snippet.Type, - &snippet.Description, - &snippet.ViewCount, - &snippet.CreatedAt, - &snippet.UpdatedAt, - &snippet.DeletedAt, - ); err != nil { - log.Printf("Error scanning snippet: %v", err) - continue - } - snippets = append(snippets, snippet) - } - return snippets, nil } // GetSnippetByID 根据ID获取代码片段 func GetSnippetByID(id string) (*models.Snippet, error) { - query := "SELECT id, title, code, type, description, view_count, created_at, updated_at, deleted_at FROM snippets WHERE id = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, id) - var snippet models.Snippet - if err := row.Scan( - &snippet.ID, - &snippet.Title, - &snippet.Code, - &snippet.Type, - &snippet.Description, - &snippet.ViewCount, - &snippet.CreatedAt, - &snippet.UpdatedAt, - &snippet.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Snippet{}). + Where("id = ? AND deleted_at = ?", id, 0). + First(&snippet).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning snippet by ID: %v", err) + log.Printf("Error getting snippet by ID: %v", err) return nil, err } // 更新查看次数 - updateViewCountQuery := "UPDATE snippets SET view_count = view_count + 1 WHERE id = ?" - if _, err := config.DB.Exec(updateViewCountQuery, id); err != nil { - log.Printf("Error updating snippet view count: %v", err) - } + config.DB.Model(&models.Snippet{}). + Where("id = ?", id). + UpdateColumn("view_count", gorm.Expr("view_count + ?", 1)) return &snippet, nil } @@ -96,63 +66,41 @@ func BuildSnippetsResponse(snippets []models.Snippet) []models.SnippetResponse { // CreateSnippet 创建代码片段 func CreateSnippet(snippet *models.Snippet) error { - now := time.Now().Unix() - query := ` - INSERT INTO snippets (id, title, code, type, description, view_count, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, 0, ?, ?, 0) - ` - _, err := config.DB.Exec( - query, - snippet.ID, - snippet.Title, - snippet.Code, - snippet.Type, - snippet.Description, - now, - now, - ) + err := config.DB.Create(snippet).Error if err != nil { log.Printf("Error creating snippet: %v", err) return err } - return nil } // UpdateSnippet 更新代码片段 func UpdateSnippet(snippet *models.Snippet) error { - now := time.Now().Unix() - query := ` - UPDATE snippets SET title = ?, code = ?, type = ?, description = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - snippet.Title, - snippet.Code, - snippet.Type, - snippet.Description, - now, - snippet.ID, - ) + err := config.DB.Model(&models.Snippet{}). + Where("id = ? AND deleted_at = ?", snippet.ID, 0). + Updates(map[string]interface{}{ + "title": snippet.Title, + "code": snippet.Code, + "type": snippet.Type, + "description": snippet.Description, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating snippet: %v", err) return err } - return nil } // DeleteSnippet 删除代码片段 (Soft Delete) func DeleteSnippet(id string) error { - now := time.Now().Unix() - query := "UPDATE snippets SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Snippet{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting snippet: %v", err) return err } - return nil } @@ -160,49 +108,29 @@ func DeleteSnippet(id string) error { func GetAdminSnippets(page, pageSize int) ([]models.Snippet, int, error) { offset := (page - 1) * pageSize + var snippets []models.Snippet + var total int64 + // 获取总数 - var total int - countQuery := "SELECT COUNT(*) FROM snippets WHERE deleted_at = 0" - err := config.DB.QueryRow(countQuery).Scan(&total) + err := config.DB.Model(&models.Snippet{}). + Where("deleted_at = ?", 0). + Count(&total).Error if err != nil { log.Printf("Error getting snippet count: %v", err) return nil, 0, err } // 获取列表 - query := ` - SELECT id, title, code, type, description, view_count, created_at, updated_at, deleted_at - FROM snippets - WHERE deleted_at = 0 - ORDER BY created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := config.DB.Query(query, pageSize, offset) + err = config.DB.Model(&models.Snippet{}). + Where("deleted_at = ?", 0). + Order("created_at DESC"). + Limit(pageSize). + Offset(offset). + Find(&snippets).Error if err != nil { log.Printf("Error querying admin snippets: %v", err) return nil, 0, err } - defer rows.Close() - var snippets []models.Snippet - for rows.Next() { - var snippet models.Snippet - if err := rows.Scan( - &snippet.ID, - &snippet.Title, - &snippet.Code, - &snippet.Type, - &snippet.Description, - &snippet.ViewCount, - &snippet.CreatedAt, - &snippet.UpdatedAt, - &snippet.DeletedAt, - ); err != nil { - log.Printf("Error scanning snippet: %v", err) - continue - } - snippets = append(snippets, snippet) - } - - return snippets, total, nil + return snippets, int(total), nil } diff --git a/server/repositories/tag_repository.go b/server/repositories/tag_repository.go index 8aea8d8..74e9358 100644 --- a/server/repositories/tag_repository.go +++ b/server/repositories/tag_repository.go @@ -1,172 +1,101 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetTags 获取所有标签 func GetTags() ([]models.Tag, error) { - query := "SELECT id, name, slug, created_at, updated_at, deleted_at FROM tags WHERE deleted_at = 0 ORDER BY name ASC" - rows, err := config.DB.Query(query) + var tags []models.Tag + err := config.DB.Model(&models.Tag{}). + Where("deleted_at = ?", 0). + Order("name ASC"). + Find(&tags).Error if err != nil { log.Printf("Error querying tags: %v", err) return nil, err } - defer rows.Close() - - var tags []models.Tag - for rows.Next() { - var tag models.Tag - if err := rows.Scan( - &tag.ID, - &tag.Name, - &tag.Slug, - &tag.CreatedAt, - &tag.UpdatedAt, - &tag.DeletedAt, - ); err != nil { - log.Printf("Error scanning tag: %v", err) - continue - } - tags = append(tags, tag) - } - return tags, nil } // GetTagByID 根据ID获取标签 func GetTagByID(id uint) (*models.Tag, error) { - query := "SELECT id, name, slug, created_at, updated_at, deleted_at FROM tags WHERE id = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, id) - var tag models.Tag - if err := row.Scan( - &tag.ID, - &tag.Name, - &tag.Slug, - &tag.CreatedAt, - &tag.UpdatedAt, - &tag.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Tag{}). + Where("id = ? AND deleted_at = ?", id, 0). + First(&tag).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning tag by ID: %v", err) + log.Printf("Error getting tag by ID: %v", err) return nil, err } - return &tag, nil } // GetTagBySlug 根据Slug获取标签 func GetTagBySlug(slug string) (*models.Tag, error) { - query := "SELECT id, name, slug, created_at, updated_at, deleted_at FROM tags WHERE slug = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, slug) - var tag models.Tag - if err := row.Scan( - &tag.ID, - &tag.Name, - &tag.Slug, - &tag.CreatedAt, - &tag.UpdatedAt, - &tag.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Tag{}). + Where("slug = ? AND deleted_at = ?", slug, 0). + First(&tag).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning tag by slug: %v", err) + log.Printf("Error getting tag by slug: %v", err) return nil, err } - return &tag, nil } // CreateTag 创建标签 func CreateTag(tag *models.Tag) error { - now := time.Now().Unix() - query := ` - INSERT INTO tags (name, slug, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, 0) - ` - result, err := config.DB.Exec( - query, - tag.Name, - tag.Slug, - now, - now, - ) + err := config.DB.Create(tag).Error if err != nil { log.Printf("Error creating tag: %v", err) return err } - - // 获取自增ID - id, err := result.LastInsertId() - if err != nil { - log.Printf("Error getting last insert ID: %v", err) - return err - } - tag.ID = uint(id) - tag.CreatedAt = now - tag.UpdatedAt = now - return nil } // UpdateTag 更新标签 func UpdateTag(tag *models.Tag) error { - now := time.Now().Unix() - query := ` - UPDATE tags SET name = ?, slug = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - tag.Name, - tag.Slug, - now, - tag.ID, - ) + err := config.DB.Model(&models.Tag{}). + Where("id = ? AND deleted_at = ?", tag.ID, 0). + Updates(map[string]interface{}{ + "name": tag.Name, + "slug": tag.Slug, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating tag: %v", err) return err } - return nil } // DeleteTag 删除标签 (Soft Delete) func DeleteTag(id uint) error { - now := time.Now().Unix() // 软删除标签 - query := "UPDATE tags SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Tag{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting tag: %v", err) return err } - // 注意:post_tags 关联表通常不做软删除,或者可以级联删除,或者在查询时过滤。 - // 为了保持数据一致性,我们可以物理删除 post_tags 中的关联,或者也软删除(如果支持)。 - // 这里选择物理删除关联,因为关联关系是“从属”的,标签没了,关系也没意义。 - // 但如果是“软删除”,也许想保留恢复可能? - // 如果保留恢复可能,post_tags 也应该软删除。 - // 但 post_tags 没有 deleted_at。 - // 所以:物理删除关联,或者不处理关联(查询时 JOIN tags 会过滤掉)。 - // 最好是物理删除关联,或者保留关联但因为 tag 被软删除而不可见。 - // 这里保留原有逻辑:物理删除关联。 - - deleteRelQuery := "DELETE FROM post_tags WHERE tag_id = ?" - _, err = config.DB.Exec(deleteRelQuery, id) + // 物理删除关联关系 + err = config.DB.Where("tag_id = ?", id).Delete(&models.PostTag{}).Error if err != nil { log.Printf("Error deleting post-tag relationships: %v", err) - // Continue even if relation delete fails? No, return error. return err } @@ -175,105 +104,58 @@ func DeleteTag(id uint) error { // GetTagsByPostID 根据文章ID获取标签 func GetTagsByPostID(postID uint) ([]models.Tag, error) { - query := ` - SELECT t.id, t.name, t.slug, t.created_at, t.updated_at, t.deleted_at - FROM tags t - JOIN post_tags pt ON t.id = pt.tag_id - WHERE pt.post_id = ? AND t.deleted_at = 0 - ORDER BY t.name ASC - ` - rows, err := config.DB.Query(query, postID) + var tags []models.Tag + err := config.DB.Model(&models.Tag{}). + Joins("JOIN post_tags pt ON tags.id = pt.tag_id"). + Where("pt.post_id = ? AND tags.deleted_at = ?", postID, 0). + Order("tags.name ASC"). + Find(&tags).Error if err != nil { log.Printf("Error querying tags by post ID: %v", err) return nil, err } - defer rows.Close() - - var tags []models.Tag - for rows.Next() { - var tag models.Tag - if err := rows.Scan( - &tag.ID, - &tag.Name, - &tag.Slug, - &tag.CreatedAt, - &tag.UpdatedAt, - &tag.DeletedAt, - ); err != nil { - log.Printf("Error scanning tag: %v", err) - continue - } - tags = append(tags, tag) - } - return tags, nil } // AddTagToPost 为文章添加标签 func AddTagToPost(postID uint, tagID uint) error { - now := time.Now().Unix() - query := ` - INSERT IGNORE INTO post_tags (post_id, tag_id, created_at) - VALUES (?, ?, ?) - ` - _, err := config.DB.Exec(query, postID, tagID, now) + postTag := &models.PostTag{ + PostID: postID, + TagID: tagID, + CreatedAt: time.Now().Unix(), + } + err := config.DB.Where("post_id = ? AND tag_id = ?", postID, tagID). + FirstOrCreate(postTag).Error if err != nil { log.Printf("Error adding tag to post: %v", err) return err } - return nil } // RemoveTagFromPost 从文章移除标签 func RemoveTagFromPost(postID uint, tagID uint) error { - query := "DELETE FROM post_tags WHERE post_id = ? AND tag_id = ?" - _, err := config.DB.Exec(query, postID, tagID) + err := config.DB.Where("post_id = ? AND tag_id = ?", postID, tagID). + Delete(&models.PostTag{}).Error if err != nil { log.Printf("Error removing tag from post: %v", err) return err } - return nil } // GetPostsByTagID 根据标签ID获取文章 func GetPostsByTagID(tagID uint) ([]models.Post, error) { - query := ` - SELECT p.id, p.title, p.category, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at - FROM posts p - JOIN post_tags pt ON p.id = pt.post_id - WHERE pt.tag_id = ? AND p.is_published = 1 AND p.deleted_at = 0 - ORDER BY p.created_at DESC - ` - rows, err := config.DB.Query(query, tagID) + var posts []models.Post + err := config.DB.Model(&models.Post{}). + Joins("JOIN post_tags pt ON posts.id = pt.post_id"). + Where("pt.tag_id = ? AND posts.is_published = ? AND posts.deleted_at = ?", tagID, 1, 0). + Order("posts.created_at DESC"). + Find(&posts).Error if err != nil { log.Printf("Error querying posts by tag ID: %v", err) return nil, err } - defer rows.Close() - - var posts []models.Post - for rows.Next() { - var post models.Post - if err := rows.Scan( - &post.ID, - &post.Title, - &post.Category, - &post.Excerpt, - &post.Content, - &post.ReadCount, - &post.IsPublished, - &post.CreatedAt, - &post.UpdatedAt, - &post.DeletedAt, - ); err != nil { - log.Printf("Error scanning post: %v", err) - continue - } - posts = append(posts, post) - } - return posts, nil } diff --git a/server/repositories/user_access_log_repository.go b/server/repositories/user_access_log_repository.go index 67bc094..449b026 100644 --- a/server/repositories/user_access_log_repository.go +++ b/server/repositories/user_access_log_repository.go @@ -2,7 +2,7 @@ package repositories import ( "log" - // "time" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -10,11 +10,10 @@ import ( // CreateUserAccessLog 创建用户访问日志 func CreateUserAccessLog(logEntry *models.UserAccessLog) error { - query := ` - INSERT INTO user_access_logs (user_id, user_ip, user_location, article_id, access_time) - VALUES (?, ?, ?, ?, NOW()) - ` - _, err := config.DB.Exec(query, logEntry.UserID, logEntry.UserIP, logEntry.UserLocation, logEntry.ArticleID) + if logEntry.AccessTime == 0 { + logEntry.AccessTime = time.Now().Unix() + } + err := config.DB.Create(logEntry).Error if err != nil { log.Printf("Error creating user access log: %v", err) return err @@ -30,42 +29,24 @@ type AccessStats struct { // GetArticleAccessTrend 获取文章访问趋势 func GetArticleAccessTrend(startDate, endDate string) ([]AccessStats, error) { - query := ` - SELECT DATE_FORMAT(access_time, '%Y-%m-%d') as date, COUNT(*) as count - FROM user_access_logs - WHERE 1=1 - ` - args := []interface{}{} + query := config.DB.Model(&models.UserAccessLog{}). + Select("FROM_UNIXTIME(access_time, '%Y-%m-%d') as date, COUNT(*) as count") if startDate != "" { - query += " AND access_time >= ?" - args = append(args, startDate) + startUnix := parseDateToUnix(startDate, false) + query = query.Where("access_time >= ?", startUnix) } if endDate != "" { - query += " AND access_time <= ?" - args = append(args, endDate+" 23:59:59") + endUnix := parseDateToUnix(endDate, true) + query = query.Where("access_time <= ?", endUnix) } - query += ` - GROUP BY date - ORDER BY date ASC - ` - - rows, err := config.DB.Query(query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - var results []AccessStats - for rows.Next() { - var s AccessStats - if err := rows.Scan(&s.Date, &s.Count); err != nil { - return nil, err - } - results = append(results, s) - } - return results, nil + err := query.Group("date"). + Order("date ASC"). + Scan(&results).Error + + return results, err } // GetTopArticlesByAccess 获取访问量最高的文章 @@ -74,35 +55,18 @@ func GetTopArticlesByAccess(limit int) ([]struct { Title string `json:"title"` Count int `json:"count"` }, error) { - query := ` - SELECT l.article_id, p.title, COUNT(*) as count - FROM user_access_logs l - JOIN posts p ON l.article_id = p.id - GROUP BY l.article_id, p.title - ORDER BY count DESC - LIMIT ? - ` - rows, err := config.DB.Query(query, limit) - if err != nil { - return nil, err - } - defer rows.Close() - var results []struct { ArticleID int `json:"article_id"` Title string `json:"title"` Count int `json:"count"` } - for rows.Next() { - var r struct { - ArticleID int `json:"article_id"` - Title string `json:"title"` - Count int `json:"count"` - } - if err := rows.Scan(&r.ArticleID, &r.Title, &r.Count); err != nil { - return nil, err - } - results = append(results, r) - } - return results, nil + err := config.DB.Model(&models.UserAccessLog{}). + Select("user_access_logs.article_id, posts.title, COUNT(*) as count"). + Joins("JOIN posts ON user_access_logs.article_id = posts.id"). + Group("user_access_logs.article_id, posts.title"). + Order("count DESC"). + Limit(limit). + Scan(&results).Error + + return results, err } diff --git a/server/repositories/user_repository.go b/server/repositories/user_repository.go index d031d01..97bd156 100644 --- a/server/repositories/user_repository.go +++ b/server/repositories/user_repository.go @@ -1,97 +1,51 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetUserByUsername 根据用户名获取用户 func GetUserByUsername(username string) (*models.User, error) { - query := ` - SELECT u.id, u.username, u.email, u.password_hash, u.role_id, COALESCE(r.name, u.role), u.is_active, u.created_at, u.updated_at, u.deleted_at - FROM users u - LEFT JOIN roles r ON u.role_id = r.id - WHERE u.username = ? AND u.deleted_at = 0 - ` - row := config.DB.QueryRow(query, username) - var user models.User - var roleID sql.NullInt64 // Handle nullable role_id - var roleName sql.NullString // Handle nullable role name + err := config.DB.Model(&models.User{}). + Select("users.*, COALESCE(roles.name, users.role) as role"). + Joins("LEFT JOIN roles ON users.role_id = roles.id"). + Where("users.username = ? AND users.deleted_at = ?", username, 0). + First(&user).Error - if err := row.Scan( - &user.ID, - &user.Username, - &user.Email, - &user.PasswordHash, - &roleID, - &roleName, - &user.IsActive, - &user.CreatedAt, - &user.UpdatedAt, - &user.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning user by username: %v", err) + log.Printf("Error getting user by username: %v", err) return nil, err } - if roleID.Valid { - user.RoleID = uint(roleID.Int64) - } - if roleName.Valid { - user.Role = roleName.String - } - return &user, nil } // GetUserByID 根据ID获取用户 func GetUserByID(id uint) (*models.User, error) { - query := ` - SELECT u.id, u.username, u.email, u.password_hash, u.role_id, COALESCE(r.name, u.role), u.is_active, u.created_at, u.updated_at, u.deleted_at - FROM users u - LEFT JOIN roles r ON u.role_id = r.id - WHERE u.id = ? AND u.deleted_at = 0 - ` - row := config.DB.QueryRow(query, id) - var user models.User - var roleID sql.NullInt64 - var roleName sql.NullString + err := config.DB.Model(&models.User{}). + Select("users.*, COALESCE(roles.name, users.role) as role"). + Joins("LEFT JOIN roles ON users.role_id = roles.id"). + Where("users.id = ? AND users.deleted_at = ?", id, 0). + First(&user).Error - if err := row.Scan( - &user.ID, - &user.Username, - &user.Email, - &user.PasswordHash, - &roleID, - &roleName, - &user.IsActive, - &user.CreatedAt, - &user.UpdatedAt, - &user.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning user by ID: %v", err) + log.Printf("Error getting user by ID: %v", err) return nil, err } - if roleID.Valid { - user.RoleID = uint(roleID.Int64) - } - if roleName.Valid { - user.Role = roleName.String - } - return &user, nil } @@ -99,63 +53,34 @@ func GetUserByID(id uint) (*models.User, error) { func GetUsers(page, pageSize int) ([]models.User, int, error) { offset := (page - 1) * pageSize + var users []models.User + var total int64 + // 获取总数 - var total int - countQuery := "SELECT COUNT(*) FROM users WHERE deleted_at = 0" - err := config.DB.QueryRow(countQuery).Scan(&total) + err := config.DB.Model(&models.User{}). + Where("deleted_at = ?", 0). + Count(&total).Error if err != nil { log.Printf("Error getting user count: %v", err) return nil, 0, err } - query := ` - SELECT u.id, u.username, u.email, u.password_hash, u.role_id, COALESCE(r.name, u.role), u.is_active, u.created_at, u.updated_at, u.deleted_at - FROM users u - LEFT JOIN roles r ON u.role_id = r.id - WHERE u.deleted_at = 0 - ORDER BY u.created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := config.DB.Query(query, pageSize, offset) + // 获取用户列表 + err = config.DB.Model(&models.User{}). + Select("users.*, COALESCE(roles.name, users.role) as role"). + Joins("LEFT JOIN roles ON users.role_id = roles.id"). + Where("users.deleted_at = ?", 0). + Order("users.created_at DESC"). + Limit(pageSize). + Offset(offset). + Find(&users).Error + if err != nil { log.Printf("Error querying users: %v", err) return nil, 0, err } - defer rows.Close() - var users []models.User - for rows.Next() { - var user models.User - var roleID sql.NullInt64 - var roleName sql.NullString - - if err := rows.Scan( - &user.ID, - &user.Username, - &user.Email, - &user.PasswordHash, - &roleID, - &roleName, - &user.IsActive, - &user.CreatedAt, - &user.UpdatedAt, - &user.DeletedAt, - ); err != nil { - log.Printf("Error scanning user: %v", err) - continue - } - - if roleID.Valid { - user.RoleID = uint(roleID.Int64) - } - if roleName.Valid { - user.Role = roleName.String - } - - users = append(users, user) - } - - return users, total, nil + return users, int(total), nil } // CreateUser 创建用户 @@ -168,45 +93,12 @@ func CreateUser(user *models.User) error { } } - now := time.Now().Unix() - query := ` - INSERT INTO users (username, email, password_hash, role_id, role, is_active, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0) - ` - - var roleID interface{} - if user.RoleID != 0 { - roleID = user.RoleID - } else { - roleID = nil - } - - result, err := config.DB.Exec( - query, - user.Username, - user.Email, - user.PasswordHash, - roleID, - user.Role, // Fallback legacy column - user.IsActive, - now, - now, - ) + err := config.DB.Create(user).Error if err != nil { log.Printf("Error creating user: %v", err) return err } - // 获取自增ID - id, err := result.LastInsertId() - if err != nil { - log.Printf("Error getting last insert ID: %v", err) - return err - } - user.ID = uint(id) - user.CreatedAt = now - user.UpdatedAt = now - return nil } @@ -220,29 +112,23 @@ func UpdateUser(user *models.User) error { } } - now := time.Now().Unix() - query := ` - UPDATE users SET username = ?, email = ?, role_id = ?, role = ?, is_active = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - - var roleID interface{} - if user.RoleID != 0 { - roleID = user.RoleID - } else { - roleID = nil + updates := map[string]interface{}{ + "username": user.Username, + "email": user.Email, + "role": user.Role, + "is_active": user.IsActive, + "updated_at": time.Now().Unix(), } - _, err := config.DB.Exec( - query, - user.Username, - user.Email, - roleID, - user.Role, - user.IsActive, - now, - user.ID, - ) + if user.RoleID != 0 { + updates["role_id"] = user.RoleID + } else { + updates["role_id"] = nil + } + + err := config.DB.Model(&models.User{}). + Where("id = ? AND deleted_at = ?", user.ID, 0). + Updates(updates).Error if err != nil { log.Printf("Error updating user: %v", err) return err @@ -253,12 +139,12 @@ func UpdateUser(user *models.User) error { // UpdateUserPassword 更新用户密码 func UpdateUserPassword(id uint, passwordHash string) error { - now := time.Now().Unix() - query := ` - UPDATE users SET password_hash = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec(query, passwordHash, now, id) + err := config.DB.Model(&models.User{}). + Where("id = ? AND deleted_at = ?", id, 0). + Updates(map[string]interface{}{ + "password_hash": passwordHash, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating user password: %v", err) return err @@ -269,9 +155,9 @@ func UpdateUserPassword(id uint, passwordHash string) error { // DeleteUser 删除用户 (Soft Delete) func DeleteUser(id uint) error { - now := time.Now().Unix() - query := "UPDATE users SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.User{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting user: %v", err) return err @@ -282,17 +168,16 @@ func DeleteUser(id uint) error { // GetUserCount 获取用户总数 func GetUserCount() (int, error) { - var count int - query := "SELECT COUNT(*) FROM users WHERE deleted_at = 0" - row := config.DB.QueryRow(query) - - err := row.Scan(&count) + var count int64 + err := config.DB.Model(&models.User{}). + Where("deleted_at = ?", 0). + Count(&count).Error if err != nil { log.Printf("Error getting user count: %v", err) return 0, err } - return count, nil + return int(count), nil } // BuildUserResponse 构建用户响应 diff --git a/server/repositories/work_repository.go b/server/repositories/work_repository.go index 2fa6b3b..c15530c 100644 --- a/server/repositories/work_repository.go +++ b/server/repositories/work_repository.go @@ -1,136 +1,67 @@ package repositories import ( - "database/sql" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" + "gorm.io/gorm" ) // GetWorks 获取所有作品 func GetWorks() ([]models.Work, error) { - query := "SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at FROM works WHERE deleted_at = 0" - rows, err := config.DB.Query(query) + var works []models.Work + err := config.DB.Model(&models.Work{}). + Where("deleted_at = ?", 0). + Find(&works).Error if err != nil { log.Printf("Error querying works: %v", err) return nil, err } - defer rows.Close() - - var works []models.Work - for rows.Next() { - var work models.Work - if err := rows.Scan( - &work.ID, - &work.Title, - &work.Category, - &work.Year, - &work.HeroImg, - &work.Description, - &work.IsFeatured, - &work.CreatedAt, - &work.UpdatedAt, - &work.DeletedAt, - ); err != nil { - log.Printf("Error scanning work: %v", err) - continue - } - works = append(works, work) - } - return works, nil } // GetWorkByID 根据ID获取作品 func GetWorkByID(id string) (*models.Work, error) { - query := "SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at FROM works WHERE id = ? AND deleted_at = 0" - row := config.DB.QueryRow(query, id) - var work models.Work - if err := row.Scan( - &work.ID, - &work.Title, - &work.Category, - &work.Year, - &work.HeroImg, - &work.Description, - &work.IsFeatured, - &work.CreatedAt, - &work.UpdatedAt, - &work.DeletedAt, - ); err != nil { - if err == sql.ErrNoRows { + err := config.DB.Model(&models.Work{}). + Where("id = ? AND deleted_at = ?", id, 0). + First(&work).Error + if err != nil { + if err == gorm.ErrRecordNotFound { return nil, nil } - log.Printf("Error scanning work by ID: %v", err) + log.Printf("Error getting work by ID: %v", err) return nil, err } - return &work, nil } // GetWorkTechStack 获取作品的技术栈 func GetWorkTechStack(workID string) ([]models.WorkTechStack, error) { - // work_tech_stack has deleted_at? I added it to all tables. - query := "SELECT id, work_id, category, item, created_at, deleted_at FROM work_tech_stack WHERE work_id = ? AND deleted_at = 0" - rows, err := config.DB.Query(query, workID) + var techStacks []models.WorkTechStack + err := config.DB.Model(&models.WorkTechStack{}). + Where("work_id = ? AND deleted_at = ?", workID, 0). + Find(&techStacks).Error if err != nil { log.Printf("Error querying work tech stack: %v", err) return nil, err } - defer rows.Close() - - var techStacks []models.WorkTechStack - for rows.Next() { - var techStack models.WorkTechStack - if err := rows.Scan( - &techStack.ID, - &techStack.WorkID, - &techStack.Category, - &techStack.Item, - &techStack.CreatedAt, - &techStack.DeletedAt, - ); err != nil { - log.Printf("Error scanning work tech stack: %v", err) - continue - } - techStacks = append(techStacks, techStack) - } - return techStacks, nil } // GetWorkGallery 获取作品的图库 func GetWorkGallery(workID string) ([]models.WorkGallery, error) { - // work_gallery has deleted_at - query := "SELECT id, work_id, image_url, sort_order, description, created_at, deleted_at FROM work_gallery WHERE work_id = ? AND deleted_at = 0 ORDER BY sort_order" - rows, err := config.DB.Query(query, workID) + var galleries []models.WorkGallery + err := config.DB.Model(&models.WorkGallery{}). + Where("work_id = ? AND deleted_at = ?", workID, 0). + Order("sort_order"). + Find(&galleries).Error if err != nil { log.Printf("Error querying work gallery: %v", err) return nil, err } - defer rows.Close() - - var galleries []models.WorkGallery - for rows.Next() { - var gallery models.WorkGallery - if err := rows.Scan( - &gallery.ID, - &gallery.WorkID, - &gallery.ImageURL, - &gallery.SortOrder, - &gallery.Description, - &gallery.CreatedAt, - &gallery.DeletedAt, - ); err != nil { - log.Printf("Error scanning work gallery: %v", err) - continue - } - galleries = append(galleries, gallery) - } - return galleries, nil } @@ -194,22 +125,14 @@ func BuildWorkResponse(work *models.Work) (*models.WorkResponse, error) { // GetNextWorkID 获取下一个作品ID(简单实现,实际可能需要更复杂的逻辑) func GetNextWorkID(currentID string) (string, error) { - // 获取所有作品ID - query := "SELECT id FROM works WHERE deleted_at = 0" - rows, err := config.DB.Query(query) + var ids []string + err := config.DB.Model(&models.Work{}). + Select("id"). + Where("deleted_at = ?", 0). + Pluck("id", &ids).Error if err != nil { return "", err } - defer rows.Close() - - var ids []string - for rows.Next() { - var id string - if err := rows.Scan(&id); err != nil { - continue - } - ids = append(ids, id) - } // 找到当前ID的索引 index := -1 @@ -234,67 +157,43 @@ func GetNextWorkID(currentID string) (string, error) { // CreateWork 创建作品 func CreateWork(work *models.Work) error { - now := time.Now().Unix() - query := ` - INSERT INTO works (id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0) - ` - _, err := config.DB.Exec( - query, - work.ID, - work.Title, - work.Category, - work.Year, - work.HeroImg, - work.Description, - work.IsFeatured, - now, - now, - ) + err := config.DB.Create(work).Error if err != nil { log.Printf("Error creating work: %v", err) return err } - return nil } // UpdateWork 更新作品 func UpdateWork(work *models.Work) error { - now := time.Now().Unix() - query := ` - UPDATE works SET title = ?, category = ?, year = ?, hero_img = ?, description = ?, is_featured = ?, updated_at = ? - WHERE id = ? AND deleted_at = 0 - ` - _, err := config.DB.Exec( - query, - work.Title, - work.Category, - work.Year, - work.HeroImg, - work.Description, - work.IsFeatured, - now, - work.ID, - ) + err := config.DB.Model(&models.Work{}). + Where("id = ? AND deleted_at = ?", work.ID, 0). + Updates(map[string]interface{}{ + "title": work.Title, + "category": work.Category, + "year": work.Year, + "hero_img": work.HeroImg, + "description": work.Description, + "is_featured": work.IsFeatured, + "updated_at": time.Now().Unix(), + }).Error if err != nil { log.Printf("Error updating work: %v", err) return err } - return nil } // DeleteWork 删除作品 (Soft Delete) func DeleteWork(id string) error { - now := time.Now().Unix() - query := "UPDATE works SET deleted_at = ? WHERE id = ?" - _, err := config.DB.Exec(query, now, id) + err := config.DB.Model(&models.Work{}). + Where("id = ?", id). + Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting work: %v", err) return err } - return nil } @@ -302,51 +201,31 @@ func DeleteWork(id string) error { func GetAdminWorks(page, pageSize int) ([]models.Work, int, error) { offset := (page - 1) * pageSize + var works []models.Work + var total int64 + // 获取总数 - var total int - countQuery := "SELECT COUNT(*) FROM works WHERE deleted_at = 0" - err := config.DB.QueryRow(countQuery).Scan(&total) + err := config.DB.Model(&models.Work{}). + Where("deleted_at = ?", 0). + Count(&total).Error if err != nil { log.Printf("Error getting work count: %v", err) return nil, 0, err } - query := ` - SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at - FROM works - WHERE deleted_at = 0 - ORDER BY created_at DESC - LIMIT ? OFFSET ? - ` - rows, err := config.DB.Query(query, pageSize, offset) + // 获取列表 + err = config.DB.Model(&models.Work{}). + Where("deleted_at = ?", 0). + Order("created_at DESC"). + Limit(pageSize). + Offset(offset). + Find(&works).Error if err != nil { log.Printf("Error querying admin works: %v", err) return nil, 0, err } - defer rows.Close() - var works []models.Work - for rows.Next() { - var work models.Work - if err := rows.Scan( - &work.ID, - &work.Title, - &work.Category, - &work.Year, - &work.HeroImg, - &work.Description, - &work.IsFeatured, - &work.CreatedAt, - &work.UpdatedAt, - &work.DeletedAt, - ); err != nil { - log.Printf("Error scanning work: %v", err) - continue - } - works = append(works, work) - } - - return works, total, nil + return works, int(total), nil } // BuildWorksResponse 构建作品列表响应 @@ -368,15 +247,13 @@ func BuildWorksResponse(works []models.Work) []models.WorkResponse { // GetWorkCount 获取作品总数 func GetWorkCount() (int, error) { - var count int - query := "SELECT COUNT(*) FROM works WHERE deleted_at = 0" - row := config.DB.QueryRow(query) - - err := row.Scan(&count) + var count int64 + err := config.DB.Model(&models.Work{}). + Where("deleted_at = ?", 0). + Count(&count).Error if err != nil { log.Printf("Error getting work count: %v", err) return 0, err } - - return count, nil + return int(count), nil }