数据结构优化

This commit is contained in:
李琦
2026-01-19 13:53:32 +08:00
parent e6edf7210b
commit 3443c10c92
38 changed files with 1556 additions and 2153 deletions

View File

@@ -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()
}
}
}

View File

@@ -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

View File

@@ -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=

View File

@@ -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()

BIN
server/ip2region.xdb Normal file

Binary file not shown.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 操作日志响应模型

View File

@@ -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 权限响应模型

View File

@@ -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 文章历史记录响应模型

View File

@@ -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 角色响应模型

View File

@@ -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
}

View File

@@ -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 系统配置响应模型

View File

@@ -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 代码片段响应模型

View File

@@ -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
}

View File

@@ -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 用户响应模型

View File

@@ -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
}

View File

@@ -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 作品响应模型,包含关联数据

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 构建角色响应

View File

@@ -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

View File

@@ -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
})
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 构建用户响应

View File

@@ -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
}