diff --git a/server/go.mod b/server/go.mod
index bf9ca7e..0cd845f 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -7,6 +7,7 @@ require (
github.com/go-sql-driver/mysql v1.9.3
github.com/golang-jwt/jwt/v5 v5.3.0
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
)
@@ -25,7 +26,6 @@ require (
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
- github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260109033043-398149f17e54 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
diff --git a/server/handlers/dashboard.go b/server/handlers/dashboard.go
index 9f4b2e7..8c56ea4 100644
--- a/server/handlers/dashboard.go
+++ b/server/handlers/dashboard.go
@@ -34,45 +34,55 @@ func GetDashboardStats(c *gin.Context) {
}
// 4. 热门文章 Top 5
- // 优先使用访问日志统计,如果没有则回退到posts表的read_count
+ // 优先使用访问日志统计,不足5条则用posts表的read_count补齐
topPosts, err := repositories.GetTopArticlesByAccess(5)
- if err != nil || len(topPosts) == 0 {
- // Fallback to post.read_count
- dbPosts, err := repositories.GetTopPosts(5)
+ if err != nil {
+ topPosts = []struct {
+ ArticleID int `json:"article_id"`
+ Title string `json:"title"`
+ Count int `json:"count"`
+ }{}
+ }
+
+ if len(topPosts) < 5 {
+ // Fetch more than needed to ensure we find unique ones, or just fetch top 5
+ dbPosts, err := repositories.GetTopPosts(10)
if err == nil {
- // Convert models.Post to the struct structure
- // Create a temporary structure slice
- topPosts = make([]struct {
- ArticleID int `json:"article_id"`
- Title string `json:"title"`
- Count int `json:"count"`
- }, 0)
+ // Create a map of existing IDs to avoid duplicates
+ existingIDs := make(map[int]bool)
+ for _, p := range topPosts {
+ existingIDs[p.ArticleID] = true
+ }
+
for _, p := range dbPosts {
- topPosts = append(topPosts, struct {
- ArticleID int `json:"article_id"`
- Title string `json:"title"`
- Count int `json:"count"`
- }{
- ArticleID: int(p.ID),
- Title: p.Title,
- Count: int(p.ReadCount),
- })
+ if len(topPosts) >= 5 {
+ break
+ }
+ if !existingIDs[int(p.ID)] {
+ topPosts = append(topPosts, struct {
+ ArticleID int `json:"article_id"`
+ Title string `json:"title"`
+ Count int `json:"count"`
+ }{
+ ArticleID: int(p.ID),
+ Title: p.Title,
+ Count: int(p.ReadCount),
+ })
+ existingIDs[int(p.ID)] = true
+ }
}
}
}
// 5. 合作咨询总数
var inquiryCount int
- config.DB.QueryRow("SELECT COUNT(*) FROM inquiries").Scan(&inquiryCount)
+ config.DB.QueryRow("SELECT COUNT(*) FROM inquiries WHERE deleted_at = 0").Scan(&inquiryCount)
// 6. 作品总数
- var workCount int
- // 假设 works 表存在,如果没有则返回 0
- config.DB.QueryRow("SELECT COUNT(*) FROM works").Scan(&workCount)
+ workCount, _ := repositories.GetWorkCount()
// 7. 文章总数
- var postCount int
- config.DB.QueryRow("SELECT COUNT(*) FROM posts").Scan(&postCount)
+ postCount, _ := repositories.GetPostCount()
utils.Success(c, gin.H{
"postsTrend": postsTrend,
diff --git a/server/handlers/log.go b/server/handlers/log.go
index 9b1884c..378e4d3 100644
--- a/server/handlers/log.go
+++ b/server/handlers/log.go
@@ -3,6 +3,7 @@ package handlers
import (
"fmt"
"net/http"
+ "time"
"github.com/gin-gonic/gin"
"github.com/niangaodev/art-code/repositories"
@@ -43,7 +44,7 @@ func AdminGetRecentActivities(c *gin.Context) {
"id": log.ID,
"icon": icon,
"text": text,
- "time": log.CreatedAt.Format("2006-01-02 15:04:05"),
+ "time": time.Unix(log.CreatedAt, 0).Format("2006-01-02 15:04:05"),
})
}
diff --git a/server/main.go b/server/main.go
index bde3e7f..cadeb70 100644
--- a/server/main.go
+++ b/server/main.go
@@ -7,6 +7,7 @@ import (
"github.com/niangaodev/art-code/config"
"github.com/niangaodev/art-code/handlers"
"github.com/niangaodev/art-code/middleware"
+ "github.com/niangaodev/art-code/repositories"
"github.com/niangaodev/art-code/utils"
)
@@ -15,6 +16,9 @@ func main() {
config.InitDB()
defer config.CloseDB()
+ // 运行数据库迁移 (Convert Datetime to BigInt)
+ repositories.MigrateToBigInt()
+
// 初始化ip2region (如果文件不存在,将降级为普通IP记录)
// 请确保在server根目录或合适位置放入 ip2region.xdb
utils.InitIP2Region("ip2region.xdb")
diff --git a/server/models/about.go b/server/models/about.go
index 48b5eab..5b14253 100644
--- a/server/models/about.go
+++ b/server/models/about.go
@@ -1,15 +1,12 @@
package models
-import (
- "time"
-)
-
type Experience struct {
Year string `json:"year"`
Role string `json:"role"`
Company string `json:"company"`
}
+// AboutProfile 关于我页面数据模型
type AboutProfile struct {
ID uint `json:"id"`
Name string `json:"name"`
@@ -23,6 +20,7 @@ type AboutProfile struct {
ExperiencesStr string `json:"-"` // Stored as string in DB
ExperienceList []Experience `json:"experiences"` // Exposed as array in JSON
IsPrimary bool `json:"isPrimary"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ CreatedAt int64 `json:"createdAt"`
+ UpdatedAt int64 `json:"updatedAt"`
+ DeletedAt int64 `json:"deletedAt"`
}
diff --git a/server/models/access_log.go b/server/models/access_log.go
index 3944e2f..7e05003 100644
--- a/server/models/access_log.go
+++ b/server/models/access_log.go
@@ -1,16 +1,15 @@
package models
-import "time"
-
-// AccessLog 访问日志 (用于统计流量)
+// 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 time.Time `json:"created_at"`
+ 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"`
}
diff --git a/server/models/inquiry.go b/server/models/inquiry.go
index dd7e152..46585e9 100644
--- a/server/models/inquiry.go
+++ b/server/models/inquiry.go
@@ -1,27 +1,27 @@
package models
-import "time"
-
// 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 time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
// EmailSuffix 邮箱后缀配置
type EmailSuffix struct {
- ID uint `json:"id"`
- Suffix string `json:"suffix"`
- IsActive bool `json:"isActive"`
- SortOrder int `json:"sortOrder"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
diff --git a/server/models/operation_log.go b/server/models/operation_log.go
index fa24aa0..8c0611e 100644
--- a/server/models/operation_log.go
+++ b/server/models/operation_log.go
@@ -1,21 +1,18 @@
package models
-import (
- "time"
-)
-
// 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 time.Time `json:"createdAt"`
+ 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"`
}
// OperationLogResponse 操作日志响应模型
diff --git a/server/models/permission.go b/server/models/permission.go
index 94d92ae..c05e478 100644
--- a/server/models/permission.go
+++ b/server/models/permission.go
@@ -1,17 +1,14 @@
package models
-import (
- "time"
-)
-
// Permission 权限模型
type Permission struct {
- ID uint `json:"id"`
- Name string `json:"name"`
- Resource string `json:"resource"`
- Action string `json:"action"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
// PermissionResponse 权限响应模型
diff --git a/server/models/post.go b/server/models/post.go
index 39e4e07..500b65c 100644
--- a/server/models/post.go
+++ b/server/models/post.go
@@ -1,23 +1,20 @@
package models
-import (
- "time"
-)
-
// Post 博客文章模型
type Post struct {
- ID uint `json:"id"`
- OriginalID string `json:"originalId,omitempty"` // For backward compatibility
- Title string `json:"title"`
- Category string `json:"category"`
- Date string `json:"date"` // YYYY-MM-DD
- 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 time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ ID uint `json:"id"`
+ OriginalID string `json:"originalId,omitempty"` // For backward compatibility
+ Title string `json:"title"`
+ Category string `json:"category"`
+ // Date Removed from DB
+ 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"`
}
// PostResponse 博客文章响应模型
@@ -32,34 +29,35 @@ type PostResponse struct {
// Tag 标签模型
type Tag struct {
- ID uint `json:"id"`
- Name string `json:"name"`
- Slug string `json:"slug"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ ID uint `json:"id"`
+ Name string `json:"name"`
+ Slug string `json:"slug"`
+ CreatedAt int64 `json:"createdAt"`
+ UpdatedAt int64 `json:"updatedAt"`
+ DeletedAt int64 `json:"deletedAt"`
}
// PostTag 文章标签关联模型
type PostTag struct {
- PostID string `json:"postId"`
- TagID uint `json:"tagId"`
- CreatedAt time.Time `json:"createdAt"`
+ PostID string `json:"postId"`
+ TagID uint `json:"tagId"`
+ CreatedAt int64 `json:"createdAt"`
}
// PostHistory 文章历史记录模型
type PostHistory struct {
- ID uint `json:"id"`
- PostID uint `json:"postId"`
- Version int `json:"version"`
- Title string `json:"title"`
- Category string `json:"category"`
- Date string `json:"date"`
- Excerpt string `json:"excerpt"`
- Content string `json:"content"`
- IsPublished int `json:"isPublished"`
- ModifiedBy uint `json:"modifiedBy"`
- ModifiedAt time.Time `json:"modifiedAt"`
- CreatedAt time.Time `json:"createdAt"`
+ ID uint `json:"id"`
+ PostID uint `json:"postId"`
+ Version int `json:"version"`
+ Title string `json:"title"`
+ Category string `json:"category"`
+ // Date Removed
+ Excerpt string `json:"excerpt"`
+ Content string `json:"content"`
+ IsPublished int `json:"isPublished"`
+ ModifiedBy uint `json:"modifiedBy"`
+ ModifiedAt int64 `json:"modifiedAt"`
+ CreatedAt int64 `json:"createdAt"`
}
// PostHistoryResponse 文章历史记录响应模型
diff --git a/server/models/role.go b/server/models/role.go
index 5d69a73..a3685e6 100644
--- a/server/models/role.go
+++ b/server/models/role.go
@@ -1,17 +1,14 @@
package models
-import (
- "time"
-)
-
// Role 角色模型
type Role struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permissions []Permission `json:"permissions,omitempty"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ CreatedAt int64 `json:"createdAt"`
+ UpdatedAt int64 `json:"updatedAt"`
+ DeletedAt int64 `json:"deletedAt"`
}
// RoleResponse 角色响应模型
diff --git a/server/models/services.go b/server/models/services.go
index 8675be3..81905dc 100644
--- a/server/models/services.go
+++ b/server/models/services.go
@@ -1,30 +1,28 @@
package models
-import (
- "time"
-)
-
// 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 time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
// 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 time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
diff --git a/server/models/setting.go b/server/models/setting.go
index 9e60d5a..0cc53b9 100644
--- a/server/models/setting.go
+++ b/server/models/setting.go
@@ -1,17 +1,14 @@
package models
-import (
- "time"
-)
-
// Setting 系统配置模型
type Setting struct {
- ID uint `json:"id"`
- KeyName string `json:"keyName"`
- Value string `json:"value"`
- Description string `json:"description"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
// SettingResponse 系统配置响应模型
diff --git a/server/models/snippet.go b/server/models/snippet.go
index 980d177..2b3ad8a 100644
--- a/server/models/snippet.go
+++ b/server/models/snippet.go
@@ -1,19 +1,16 @@
package models
-import (
- "time"
-)
-
// 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 time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
// SnippetResponse 代码片段响应模型
diff --git a/server/models/user.go b/server/models/user.go
index 416af64..5e3dd65 100644
--- a/server/models/user.go
+++ b/server/models/user.go
@@ -1,20 +1,17 @@
package models
-import (
- "time"
-)
-
// User 用户模型
type User struct {
- ID uint `json:"id"`
- Username string `json:"username"`
- Email string `json:"email"`
- PasswordHash string `json:"-"`
- RoleID uint `json:"roleId"`
- Role string `json:"role"` // 保持兼容,或者作为Role Name
- IsActive int `json:"isActive"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ ID uint `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ 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"`
}
// UserResponse 用户响应模型
diff --git a/server/models/user_access_log.go b/server/models/user_access_log.go
index 66daec1..ac2605a 100644
--- a/server/models/user_access_log.go
+++ b/server/models/user_access_log.go
@@ -1,13 +1,12 @@
package models
-import "time"
-
-// UserAccessLog 用户访问记录模型
+// 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 time.Time `json:"access_time"` // 访问时间
+ 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"`
}
diff --git a/server/models/work.go b/server/models/work.go
index 528acdb..f420f19 100644
--- a/server/models/work.go
+++ b/server/models/work.go
@@ -1,39 +1,38 @@
package models
-import (
- "time"
-)
-
// 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 time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ 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"`
}
// WorkTechStack 作品技术栈模型
type WorkTechStack struct {
- ID uint `json:"id"`
- WorkID string `json:"workId"`
- Category string `json:"category"`
- Item string `json:"item"`
- CreatedAt time.Time `json:"createdAt"`
+ ID uint `json:"id"`
+ WorkID string `json:"workId"`
+ Category string `json:"category"`
+ Item string `json:"item"`
+ CreatedAt int64 `json:"createdAt"`
+ DeletedAt int64 `json:"deletedAt"`
}
// 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 time.Time `json:"createdAt"`
+ 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"`
}
// WorkResponse 作品响应模型,包含关联数据
diff --git a/server/nl_blog.sql b/server/nl_blog.sql
index ffb53cf..d625f0c 100644
--- a/server/nl_blog.sql
+++ b/server/nl_blog.sql
@@ -11,7 +11,7 @@
Target Server Version : 80407 (8.4.7)
File Encoding : 65001
- Date: 16/01/2026 12:21:22
+ Date: 16/01/2026 13:00:57
*/
SET NAMES utf8mb4;
@@ -32,15 +32,16 @@ CREATE TABLE `about_profiles` (
`tech_stack` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'JSON string or comma separated list',
`experiences` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'JSON string of experience list',
`is_primary` tinyint(1) NULL DEFAULT 0,
- `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
- `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `created_at` bigint NOT NULL DEFAULT 0,
+ `updated_at` bigint NOT NULL DEFAULT 0,
+ `deleted_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of about_profiles
-- ----------------------------
-INSERT INTO `about_profiles` VALUES (1, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:技术是骨架,艺术是灵魂。
目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'hello@niangao.dev', 'Niangao_Dev', '[\"Vue 3\",\"React\",\"TypeScript\",\"Three.js\",\"Golang\",\"Tailwind CSS\",\"Rust\",\"Wails\"]', '[{\"year\":\"2024 - 至今\",\"role\":\"技术负责人\",\"company\":\"某医疗平台公司\"},{\"year\":\"2020 - 2024\",\"role\":\"PHP开发工程师\",\"company\":\"某电商公司\"}]', 1, '2026-01-15 15:22:08', '2026-01-15 15:41:51');
+INSERT INTO `about_profiles` VALUES (3, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:技术是骨架,艺术是灵魂。
目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'liqiworker@gmail.com', 'ngzz_0218', '[\\\"Vue 3\\\",\\\"React\\\",\\\"TypeScript\\\",\\\"Three.js\\\",\\\"Golang\\\",\\\"Tailwind CSS\\\",\\\"Rust\\\",\\\"Wails\\\"]', '[{\\\"year\\\":\\\"2024 - 至今\\\",\\\"role\\\":\\\"技术负责人\\\",\\\"company\\\":\\\"某医疗平台公司\\\"},{\\\"year\\\":\\\"2020 - 2024\\\",\\\"role\\\":\\\"PHP开发工程师\\\",\\\"company\\\":\\\"某电商公司\\\"}]', 0, 0, 0, 0);
-- ----------------------------
-- Table structure for access_logs
@@ -54,12 +55,12 @@ CREATE TABLE `access_logs` (
`method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'HTTP方法',
`status_code` int UNSIGNED NOT NULL COMMENT 'HTTP状态码',
`response_time` int UNSIGNED NOT NULL COMMENT '响应时间(毫秒)',
- `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '访问时间',
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_path`(`path` ASC) USING BTREE COMMENT '按访问路径查询索引',
- INDEX `idx_created_at`(`created_at` ASC) USING BTREE COMMENT '按访问时间查询索引',
INDEX `idx_status_code`(`status_code` ASC) USING BTREE COMMENT '按状态码查询索引'
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访问日志表' ROW_FORMAT = DYNAMIC;
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访问日志表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of access_logs
@@ -74,22 +75,23 @@ CREATE TABLE `email_suffixes` (
`suffix` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱后缀',
`is_active` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序',
- `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
+ `updated_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_suffix`(`suffix` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱后缀配置表' ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱后缀配置表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of email_suffixes
-- ----------------------------
-INSERT INTO `email_suffixes` VALUES (1, '@gmail.com', 1, 1, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
-INSERT INTO `email_suffixes` VALUES (2, '@163.com', 1, 2, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
-INSERT INTO `email_suffixes` VALUES (3, '@qq.com', 1, 3, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
-INSERT INTO `email_suffixes` VALUES (4, '@outlook.com', 1, 4, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
-INSERT INTO `email_suffixes` VALUES (5, '@foxmail.com', 1, 5, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
-INSERT INTO `email_suffixes` VALUES (6, '@sina.com', 1, 6, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
-INSERT INTO `email_suffixes` VALUES (7, '@126.com', 1, 7, '2026-01-16 08:37:46', '2026-01-16 08:37:46');
+INSERT INTO `email_suffixes` VALUES (1, '@gmail.com', 1, 1, 0, 1768523866, 1768538954);
+INSERT INTO `email_suffixes` VALUES (2, '@163.com', 1, 2, 0, 1768523866, 1768538954);
+INSERT INTO `email_suffixes` VALUES (3, '@qq.com', 1, 3, 0, 1768523866, 1768538954);
+INSERT INTO `email_suffixes` VALUES (4, '@outlook.com', 1, 4, 0, 1768523866, 1768538954);
+INSERT INTO `email_suffixes` VALUES (5, '@foxmail.com', 1, 5, 0, 1768523866, 1768538954);
+INSERT INTO `email_suffixes` VALUES (6, '@sina.com', 1, 6, 0, 1768523866, 1768538954);
+INSERT INTO `email_suffixes` VALUES (7, '@126.com', 1, 7, 0, 1768523866, 1768538954);
-- ----------------------------
-- Table structure for inquiries
@@ -104,15 +106,16 @@ CREATE TABLE `inquiries` (
`budget` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '预算范围',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '需求描述',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态: 0-未读, 1-已读, 2-已联系',
- `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
+ `updated_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作咨询表' ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作咨询表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of inquiries
-- ----------------------------
-INSERT INTO `inquiries` VALUES (1, '李先生', '萧康云医', 'wechat', 'ngzz_9527', '1w-5w', '我需要做一个诊所小程序', 2, '2026-01-16 08:52:45', '2026-01-16 09:01:19');
+INSERT INTO `inquiries` VALUES (1, '李先生', '萧康云医', 'wechat', 'ngzz_9527', '1w-5w', '我需要做一个诊所小程序', 2, 0, 1768524765, 1768538954);
-- ----------------------------
-- Table structure for operation_logs
@@ -128,294 +131,312 @@ CREATE TABLE `operation_logs` (
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数',
`status` int NOT NULL COMMENT '响应状态码',
`duration` int NOT NULL COMMENT '响应时间(毫秒)',
- `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
- INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
- INDEX `idx_created_at`(`created_at` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 280 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;
+ INDEX `idx_user_id`(`user_id` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of operation_logs
-- ----------------------------
-INSERT INTO `operation_logs` VALUES (1, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 403, 5, '2026-01-15 12:54:55');
-INSERT INTO `operation_logs` VALUES (2, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, '2026-01-15 13:34:45');
-INSERT INTO `operation_logs` VALUES (3, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 57, '2026-01-15 13:34:45');
-INSERT INTO `operation_logs` VALUES (4, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 51, '2026-01-15 13:34:46');
-INSERT INTO `operation_logs` VALUES (5, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-15 13:34:50');
-INSERT INTO `operation_logs` VALUES (6, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 140, '2026-01-15 13:34:53');
-INSERT INTO `operation_logs` VALUES (7, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-15 13:34:54');
-INSERT INTO `operation_logs` VALUES (8, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 236, '2026-01-15 13:34:55');
-INSERT INTO `operation_logs` VALUES (9, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, '2026-01-15 13:34:55');
-INSERT INTO `operation_logs` VALUES (10, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, '2026-01-15 13:34:56');
-INSERT INTO `operation_logs` VALUES (11, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-15 13:34:57');
-INSERT INTO `operation_logs` VALUES (12, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, '2026-01-15 13:35:01');
-INSERT INTO `operation_logs` VALUES (13, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, '2026-01-15 13:35:01');
-INSERT INTO `operation_logs` VALUES (14, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 54, '2026-01-15 13:37:17');
-INSERT INTO `operation_logs` VALUES (15, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 51, '2026-01-15 13:37:17');
-INSERT INTO `operation_logs` VALUES (16, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 59, '2026-01-15 15:28:19');
-INSERT INTO `operation_logs` VALUES (17, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 50, '2026-01-15 15:28:19');
-INSERT INTO `operation_logs` VALUES (18, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, '2026-01-15 15:28:20');
-INSERT INTO `operation_logs` VALUES (19, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 48, '2026-01-15 15:28:22');
-INSERT INTO `operation_logs` VALUES (20, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-15 15:28:23');
-INSERT INTO `operation_logs` VALUES (21, 1, 'lq', '::1', '/api/admin/users', 'POST', '{\"username\":\"cs\",\"email\":\"cs@nailaoyun.cn\",\"role\":\"viewer\",\"isActive\":1}', 200, 101, '2026-01-15 15:28:35');
-INSERT INTO `operation_logs` VALUES (22, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-15 15:28:35');
-INSERT INTO `operation_logs` VALUES (23, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 46, '2026-01-15 15:28:40');
-INSERT INTO `operation_logs` VALUES (24, 1, 'lq', '::1', '/api/admin/users/2', 'PUT', '{\"username\":\"editor\",\"email\":\"editor@example.com\",\"role\":\"viewer\",\"isActive\":1}', 200, 97, '2026-01-15 15:28:44');
-INSERT INTO `operation_logs` VALUES (25, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, '2026-01-15 15:28:44');
-INSERT INTO `operation_logs` VALUES (26, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 46, '2026-01-15 15:28:47');
-INSERT INTO `operation_logs` VALUES (27, 1, 'lq', '::1', '/api/admin/users/2', 'PUT', '{\"username\":\"editor\",\"email\":\"editor@example.com\",\"role\":\"editor\",\"isActive\":1}', 200, 98, '2026-01-15 15:28:49');
-INSERT INTO `operation_logs` VALUES (28, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, '2026-01-15 15:28:49');
-INSERT INTO `operation_logs` VALUES (29, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 132, '2026-01-15 15:28:50');
-INSERT INTO `operation_logs` VALUES (30, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-15 15:28:51');
-INSERT INTO `operation_logs` VALUES (31, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 237, '2026-01-15 15:28:52');
-INSERT INTO `operation_logs` VALUES (32, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, '2026-01-15 15:28:53');
-INSERT INTO `operation_logs` VALUES (33, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, '2026-01-15 15:28:54');
-INSERT INTO `operation_logs` VALUES (34, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, '2026-01-15 15:28:54');
-INSERT INTO `operation_logs` VALUES (35, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, '2026-01-15 15:28:55');
-INSERT INTO `operation_logs` VALUES (36, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, '2026-01-15 15:28:55');
-INSERT INTO `operation_logs` VALUES (37, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 47, '2026-01-15 15:28:55');
-INSERT INTO `operation_logs` VALUES (38, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 55, '2026-01-15 15:35:04');
-INSERT INTO `operation_logs` VALUES (39, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 54, '2026-01-15 15:35:04');
-INSERT INTO `operation_logs` VALUES (40, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 241, '2026-01-15 15:35:07');
-INSERT INTO `operation_logs` VALUES (41, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, '2026-01-15 15:35:08');
-INSERT INTO `operation_logs` VALUES (42, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, '2026-01-15 15:35:09');
-INSERT INTO `operation_logs` VALUES (43, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, '2026-01-15 15:35:09');
-INSERT INTO `operation_logs` VALUES (44, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, '2026-01-15 15:35:12');
-INSERT INTO `operation_logs` VALUES (45, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, '2026-01-15 15:35:14');
-INSERT INTO `operation_logs` VALUES (46, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, '2026-01-15 15:35:24');
-INSERT INTO `operation_logs` VALUES (47, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, '2026-01-15 15:35:26');
-INSERT INTO `operation_logs` VALUES (48, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, '2026-01-15 15:35:27');
-INSERT INTO `operation_logs` VALUES (49, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 143, '2026-01-15 15:36:36');
-INSERT INTO `operation_logs` VALUES (50, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 49, '2026-01-15 15:40:41');
-INSERT INTO `operation_logs` VALUES (51, 1, 'lq', '::1', '/api/admin/about/2', 'DELETE', '', 200, 42, '2026-01-15 15:40:47');
-INSERT INTO `operation_logs` VALUES (52, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 42, '2026-01-15 15:40:47');
-INSERT INTO `operation_logs` VALUES (53, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 40, '2026-01-15 15:40:48');
-INSERT INTO `operation_logs` VALUES (54, 1, 'lq', '::1', '/api/admin/about/1', 'PUT', '{\"name\":\"年糕崽崽\",\"avatar\":\"https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4\",\"location\":\"中国 · 浙江杭州\",\"bio\":\"嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:技术是骨架,艺术是灵魂。
目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。\",\"email\":\"hello@niangao.dev\",\"wechat\":\"Niangao_Dev\",\"isPrimary\":true,\"experiences\":[{\"year\":\"2024 - 至今\",\"role\":\"技术负责人\",\"company\":\"某医疗平台公司\"},{\"year\":\"2020 - 2024\",\"role\":\"PHP开发工程师\",\"company\":\"某电商公司\"}],\"techStack\":[\"Vue 3\",\"React\",\"TypeScript\",\"Three.js\",\"Golang\",\"Tailwind CSS\",\"Rust\",\"Wails\"]}', 200, 55, '2026-01-15 15:41:51');
-INSERT INTO `operation_logs` VALUES (55, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, '2026-01-15 15:41:51');
-INSERT INTO `operation_logs` VALUES (56, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 45, '2026-01-15 15:41:59');
-INSERT INTO `operation_logs` VALUES (57, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 221, '2026-01-15 15:41:59');
-INSERT INTO `operation_logs` VALUES (58, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 45, '2026-01-15 15:42:01');
-INSERT INTO `operation_logs` VALUES (59, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 131, '2026-01-15 15:42:02');
-INSERT INTO `operation_logs` VALUES (60, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 44, '2026-01-15 15:42:03');
-INSERT INTO `operation_logs` VALUES (61, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 42, '2026-01-15 15:42:04');
-INSERT INTO `operation_logs` VALUES (62, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, '2026-01-15 15:42:04');
-INSERT INTO `operation_logs` VALUES (63, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 42, '2026-01-15 15:42:07');
-INSERT INTO `operation_logs` VALUES (64, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, '2026-01-15 16:15:53');
-INSERT INTO `operation_logs` VALUES (65, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 51, '2026-01-15 16:15:53');
-INSERT INTO `operation_logs` VALUES (66, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, '2026-01-15 16:15:56');
-INSERT INTO `operation_logs` VALUES (67, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 51, '2026-01-15 16:16:31');
-INSERT INTO `operation_logs` VALUES (68, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, '2026-01-15 16:16:31');
-INSERT INTO `operation_logs` VALUES (69, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-15 16:16:33');
-INSERT INTO `operation_logs` VALUES (70, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, '2026-01-15 16:16:33');
-INSERT INTO `operation_logs` VALUES (71, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, '2026-01-15 16:16:34');
-INSERT INTO `operation_logs` VALUES (72, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 239, '2026-01-15 16:16:35');
-INSERT INTO `operation_logs` VALUES (73, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, '2026-01-15 16:16:36');
-INSERT INTO `operation_logs` VALUES (74, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 51, '2026-01-15 16:16:36');
-INSERT INTO `operation_logs` VALUES (75, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, '2026-01-15 16:16:37');
-INSERT INTO `operation_logs` VALUES (76, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 50, '2026-01-15 16:16:37');
-INSERT INTO `operation_logs` VALUES (77, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-15 16:16:38');
-INSERT INTO `operation_logs` VALUES (78, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, '2026-01-15 16:16:38');
-INSERT INTO `operation_logs` VALUES (79, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 48, '2026-01-15 16:16:39');
-INSERT INTO `operation_logs` VALUES (80, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, '2026-01-15 16:18:36');
-INSERT INTO `operation_logs` VALUES (81, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 54, '2026-01-15 16:18:36');
-INSERT INTO `operation_logs` VALUES (82, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, '2026-01-15 16:18:40');
-INSERT INTO `operation_logs` VALUES (83, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 52, '2026-01-15 16:19:16');
-INSERT INTO `operation_logs` VALUES (84, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, '2026-01-15 16:19:17');
-INSERT INTO `operation_logs` VALUES (85, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, '2026-01-15 16:19:17');
-INSERT INTO `operation_logs` VALUES (86, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-15 16:32:17');
-INSERT INTO `operation_logs` VALUES (87, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, '2026-01-15 16:32:18');
-INSERT INTO `operation_logs` VALUES (88, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, '2026-01-15 21:17:31');
-INSERT INTO `operation_logs` VALUES (89, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 93, '2026-01-15 21:17:31');
-INSERT INTO `operation_logs` VALUES (90, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 240, '2026-01-15 21:17:46');
-INSERT INTO `operation_logs` VALUES (91, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 50, '2026-01-15 21:17:54');
-INSERT INTO `operation_logs` VALUES (92, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, '2026-01-16 08:52:54');
-INSERT INTO `operation_logs` VALUES (93, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 47, '2026-01-16 08:52:54');
-INSERT INTO `operation_logs` VALUES (94, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, '2026-01-16 08:53:04');
-INSERT INTO `operation_logs` VALUES (95, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 50, '2026-01-16 09:01:14');
-INSERT INTO `operation_logs` VALUES (96, 1, 'lq', '::1', '/api/admin/inquiries/1/status', 'PUT', '{\"status\":1}', 200, 49, '2026-01-16 09:01:16');
-INSERT INTO `operation_logs` VALUES (97, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, '2026-01-16 09:01:16');
-INSERT INTO `operation_logs` VALUES (98, 1, 'lq', '::1', '/api/admin/inquiries/1/status', 'PUT', '{\"status\":2}', 200, 49, '2026-01-16 09:01:20');
-INSERT INTO `operation_logs` VALUES (99, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 45, '2026-01-16 09:01:20');
-INSERT INTO `operation_logs` VALUES (100, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 47, '2026-01-16 09:02:57');
-INSERT INTO `operation_logs` VALUES (101, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, '2026-01-16 09:03:49');
-INSERT INTO `operation_logs` VALUES (102, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, '2026-01-16 09:03:49');
-INSERT INTO `operation_logs` VALUES (103, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, '2026-01-16 09:03:55');
-INSERT INTO `operation_logs` VALUES (104, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, '2026-01-16 09:03:57');
-INSERT INTO `operation_logs` VALUES (105, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-16 09:05:16');
-INSERT INTO `operation_logs` VALUES (106, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, '2026-01-16 09:05:22');
-INSERT INTO `operation_logs` VALUES (107, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, '2026-01-16 09:05:22');
-INSERT INTO `operation_logs` VALUES (108, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, '2026-01-16 09:13:46');
-INSERT INTO `operation_logs` VALUES (109, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 227, '2026-01-16 09:13:48');
-INSERT INTO `operation_logs` VALUES (110, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, '2026-01-16 09:13:49');
-INSERT INTO `operation_logs` VALUES (111, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 43, '2026-01-16 09:13:50');
-INSERT INTO `operation_logs` VALUES (112, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, '2026-01-16 09:13:51');
-INSERT INTO `operation_logs` VALUES (113, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 50, '2026-01-16 09:13:59');
-INSERT INTO `operation_logs` VALUES (114, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 45, '2026-01-16 09:14:06');
-INSERT INTO `operation_logs` VALUES (115, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 55, '2026-01-16 09:15:10');
-INSERT INTO `operation_logs` VALUES (116, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, '2026-01-16 09:15:16');
-INSERT INTO `operation_logs` VALUES (117, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, '2026-01-16 09:15:19');
-INSERT INTO `operation_logs` VALUES (118, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 138, '2026-01-16 09:15:20');
-INSERT INTO `operation_logs` VALUES (119, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, '2026-01-16 09:15:23');
-INSERT INTO `operation_logs` VALUES (120, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, '2026-01-16 09:15:29');
-INSERT INTO `operation_logs` VALUES (121, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-16 09:15:30');
-INSERT INTO `operation_logs` VALUES (122, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 47, '2026-01-16 09:15:33');
-INSERT INTO `operation_logs` VALUES (123, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 45, '2026-01-16 09:15:33');
-INSERT INTO `operation_logs` VALUES (124, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, '2026-01-16 09:25:08');
-INSERT INTO `operation_logs` VALUES (125, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 50, '2026-01-16 09:25:08');
-INSERT INTO `operation_logs` VALUES (126, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 52, '2026-01-16 09:25:23');
-INSERT INTO `operation_logs` VALUES (127, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 46, '2026-01-16 09:25:23');
-INSERT INTO `operation_logs` VALUES (128, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 53, '2026-01-16 09:26:23');
-INSERT INTO `operation_logs` VALUES (129, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, '2026-01-16 09:26:23');
-INSERT INTO `operation_logs` VALUES (130, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, '2026-01-16 09:29:33');
-INSERT INTO `operation_logs` VALUES (131, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, '2026-01-16 09:31:48');
-INSERT INTO `operation_logs` VALUES (132, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 55, '2026-01-16 09:36:19');
-INSERT INTO `operation_logs` VALUES (133, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, '2026-01-16 09:37:05');
-INSERT INTO `operation_logs` VALUES (134, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-16 09:37:05');
-INSERT INTO `operation_logs` VALUES (135, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 51, '2026-01-16 09:38:22');
-INSERT INTO `operation_logs` VALUES (136, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 54, '2026-01-16 09:39:55');
-INSERT INTO `operation_logs` VALUES (137, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 57, '2026-01-16 09:39:55');
-INSERT INTO `operation_logs` VALUES (138, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 45, '2026-01-16 09:40:05');
-INSERT INTO `operation_logs` VALUES (139, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 44, '2026-01-16 09:40:08');
-INSERT INTO `operation_logs` VALUES (140, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, '2026-01-16 09:40:14');
-INSERT INTO `operation_logs` VALUES (141, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, '2026-01-16 09:40:25');
-INSERT INTO `operation_logs` VALUES (142, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 45, '2026-01-16 09:40:53');
-INSERT INTO `operation_logs` VALUES (143, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, '2026-01-16 09:40:58');
-INSERT INTO `operation_logs` VALUES (144, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 53, '2026-01-16 09:44:40');
-INSERT INTO `operation_logs` VALUES (145, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, '2026-01-16 09:44:45');
-INSERT INTO `operation_logs` VALUES (146, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 45, '2026-01-16 09:44:46');
-INSERT INTO `operation_logs` VALUES (147, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, '2026-01-16 09:44:47');
-INSERT INTO `operation_logs` VALUES (148, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 47, '2026-01-16 09:47:16');
-INSERT INTO `operation_logs` VALUES (149, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 51, '2026-01-16 09:48:11');
-INSERT INTO `operation_logs` VALUES (150, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 142, '2026-01-16 09:57:36');
-INSERT INTO `operation_logs` VALUES (151, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 153, '2026-01-16 09:57:49');
-INSERT INTO `operation_logs` VALUES (152, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 144, '2026-01-16 09:57:51');
-INSERT INTO `operation_logs` VALUES (153, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 132, '2026-01-16 09:57:52');
-INSERT INTO `operation_logs` VALUES (154, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, '2026-01-16 09:57:54');
-INSERT INTO `operation_logs` VALUES (155, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 144, '2026-01-16 09:57:54');
-INSERT INTO `operation_logs` VALUES (156, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, '2026-01-16 09:57:55');
-INSERT INTO `operation_logs` VALUES (157, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, '2026-01-16 09:57:56');
-INSERT INTO `operation_logs` VALUES (158, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 57, '2026-01-16 09:58:02');
-INSERT INTO `operation_logs` VALUES (159, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 97, '2026-01-16 09:58:02');
-INSERT INTO `operation_logs` VALUES (160, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 145, '2026-01-16 09:58:04');
-INSERT INTO `operation_logs` VALUES (161, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-16 09:58:04');
-INSERT INTO `operation_logs` VALUES (162, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 242, '2026-01-16 09:58:05');
-INSERT INTO `operation_logs` VALUES (163, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, '2026-01-16 09:58:07');
-INSERT INTO `operation_logs` VALUES (164, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 50, '2026-01-16 09:58:08');
-INSERT INTO `operation_logs` VALUES (165, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, '2026-01-16 09:58:10');
-INSERT INTO `operation_logs` VALUES (166, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 245, '2026-01-16 09:58:13');
-INSERT INTO `operation_logs` VALUES (167, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-16 09:58:14');
-INSERT INTO `operation_logs` VALUES (168, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 99, '2026-01-16 09:58:14');
-INSERT INTO `operation_logs` VALUES (169, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 220, '2026-01-16 09:58:18');
-INSERT INTO `operation_logs` VALUES (170, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, '2026-01-16 09:58:19');
-INSERT INTO `operation_logs` VALUES (171, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, '2026-01-16 09:59:33');
-INSERT INTO `operation_logs` VALUES (172, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, '2026-01-16 09:59:33');
-INSERT INTO `operation_logs` VALUES (173, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 139, '2026-01-16 09:59:34');
-INSERT INTO `operation_logs` VALUES (174, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, '2026-01-16 10:02:12');
-INSERT INTO `operation_logs` VALUES (175, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 141, '2026-01-16 10:02:15');
-INSERT INTO `operation_logs` VALUES (176, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, '2026-01-16 10:02:17');
-INSERT INTO `operation_logs` VALUES (177, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, '2026-01-16 10:02:17');
-INSERT INTO `operation_logs` VALUES (178, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, '2026-01-16 10:02:21');
-INSERT INTO `operation_logs` VALUES (179, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 138, '2026-01-16 10:02:22');
-INSERT INTO `operation_logs` VALUES (180, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, '2026-01-16 10:02:23');
-INSERT INTO `operation_logs` VALUES (181, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, '2026-01-16 10:02:31');
-INSERT INTO `operation_logs` VALUES (182, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-16 10:03:27');
-INSERT INTO `operation_logs` VALUES (183, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, '2026-01-16 10:03:27');
-INSERT INTO `operation_logs` VALUES (184, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, '2026-01-16 10:03:32');
-INSERT INTO `operation_logs` VALUES (185, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, '2026-01-16 10:03:32');
-INSERT INTO `operation_logs` VALUES (186, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, '2026-01-16 10:03:33');
-INSERT INTO `operation_logs` VALUES (187, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 200, '2026-01-16 10:09:24');
-INSERT INTO `operation_logs` VALUES (188, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 193, '2026-01-16 10:09:33');
-INSERT INTO `operation_logs` VALUES (189, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 183, '2026-01-16 10:10:59');
-INSERT INTO `operation_logs` VALUES (190, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 138, '2026-01-16 10:13:38');
-INSERT INTO `operation_logs` VALUES (191, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, '2026-01-16 10:13:40');
-INSERT INTO `operation_logs` VALUES (192, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 141, '2026-01-16 10:16:30');
-INSERT INTO `operation_logs` VALUES (193, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, '2026-01-16 10:17:47');
-INSERT INTO `operation_logs` VALUES (194, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, '2026-01-16 10:17:47');
-INSERT INTO `operation_logs` VALUES (195, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, '2026-01-16 10:17:49');
-INSERT INTO `operation_logs` VALUES (196, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, '2026-01-16 10:21:44');
-INSERT INTO `operation_logs` VALUES (197, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, '2026-01-16 10:21:44');
-INSERT INTO `operation_logs` VALUES (198, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, '2026-01-16 10:21:44');
-INSERT INTO `operation_logs` VALUES (199, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 53, '2026-01-16 10:59:13');
-INSERT INTO `operation_logs` VALUES (200, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 103, '2026-01-16 10:59:13');
-INSERT INTO `operation_logs` VALUES (201, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 93, '2026-01-16 10:59:25');
-INSERT INTO `operation_logs` VALUES (202, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, '2026-01-16 10:59:25');
-INSERT INTO `operation_logs` VALUES (203, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, '2026-01-16 10:59:30');
-INSERT INTO `operation_logs` VALUES (204, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, '2026-01-16 10:59:31');
-INSERT INTO `operation_logs` VALUES (205, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 237, '2026-01-16 10:59:32');
-INSERT INTO `operation_logs` VALUES (206, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, '2026-01-16 10:59:35');
-INSERT INTO `operation_logs` VALUES (207, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, '2026-01-16 10:59:35');
-INSERT INTO `operation_logs` VALUES (208, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, '2026-01-16 10:59:40');
-INSERT INTO `operation_logs` VALUES (209, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 45, '2026-01-16 10:59:45');
-INSERT INTO `operation_logs` VALUES (210, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, '2026-01-16 10:59:46');
-INSERT INTO `operation_logs` VALUES (211, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, '2026-01-16 11:00:05');
-INSERT INTO `operation_logs` VALUES (212, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 53, '2026-01-16 11:00:07');
-INSERT INTO `operation_logs` VALUES (213, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-16 11:00:09');
-INSERT INTO `operation_logs` VALUES (214, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 147, '2026-01-16 11:00:10');
-INSERT INTO `operation_logs` VALUES (215, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, '2026-01-16 11:00:40');
-INSERT INTO `operation_logs` VALUES (216, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-16 11:00:41');
-INSERT INTO `operation_logs` VALUES (217, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, '2026-01-16 11:00:43');
-INSERT INTO `operation_logs` VALUES (218, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, '2026-01-16 11:00:46');
-INSERT INTO `operation_logs` VALUES (219, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 150, '2026-01-16 11:01:00');
-INSERT INTO `operation_logs` VALUES (220, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, '2026-01-16 11:01:15');
-INSERT INTO `operation_logs` VALUES (221, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, '2026-01-16 11:01:15');
-INSERT INTO `operation_logs` VALUES (222, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 148, '2026-01-16 11:02:00');
-INSERT INTO `operation_logs` VALUES (223, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, '2026-01-16 11:03:05');
-INSERT INTO `operation_logs` VALUES (224, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, '2026-01-16 11:03:05');
-INSERT INTO `operation_logs` VALUES (225, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 93, '2026-01-16 11:04:41');
-INSERT INTO `operation_logs` VALUES (226, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, '2026-01-16 11:04:41');
-INSERT INTO `operation_logs` VALUES (227, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, '2026-01-16 11:05:25');
-INSERT INTO `operation_logs` VALUES (228, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 100, '2026-01-16 11:05:25');
-INSERT INTO `operation_logs` VALUES (229, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, '2026-01-16 11:05:38');
-INSERT INTO `operation_logs` VALUES (230, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, '2026-01-16 11:05:38');
-INSERT INTO `operation_logs` VALUES (231, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 94, '2026-01-16 11:08:30');
-INSERT INTO `operation_logs` VALUES (232, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 50, '2026-01-16 11:08:30');
-INSERT INTO `operation_logs` VALUES (233, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 49, '2026-01-16 11:08:32');
-INSERT INTO `operation_logs` VALUES (234, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, '2026-01-16 11:08:34');
-INSERT INTO `operation_logs` VALUES (235, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, '2026-01-16 11:08:35');
-INSERT INTO `operation_logs` VALUES (236, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, '2026-01-16 11:08:37');
-INSERT INTO `operation_logs` VALUES (237, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-16 11:08:39');
-INSERT INTO `operation_logs` VALUES (238, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 51, '2026-01-16 11:10:52');
-INSERT INTO `operation_logs` VALUES (239, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 246, '2026-01-16 11:12:34');
-INSERT INTO `operation_logs` VALUES (240, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-16 11:12:39');
-INSERT INTO `operation_logs` VALUES (241, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, '2026-01-16 11:12:47');
-INSERT INTO `operation_logs` VALUES (242, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, '2026-01-16 11:12:53');
-INSERT INTO `operation_logs` VALUES (243, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, '2026-01-16 11:13:22');
-INSERT INTO `operation_logs` VALUES (244, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, '2026-01-16 11:13:22');
-INSERT INTO `operation_logs` VALUES (245, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 94, '2026-01-16 11:13:32');
-INSERT INTO `operation_logs` VALUES (246, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, '2026-01-16 11:13:32');
-INSERT INTO `operation_logs` VALUES (247, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, '2026-01-16 11:13:34');
-INSERT INTO `operation_logs` VALUES (248, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, '2026-01-16 11:13:42');
-INSERT INTO `operation_logs` VALUES (249, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, '2026-01-16 11:13:46');
-INSERT INTO `operation_logs` VALUES (250, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, '2026-01-16 11:13:49');
-INSERT INTO `operation_logs` VALUES (251, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, '2026-01-16 11:13:49');
-INSERT INTO `operation_logs` VALUES (252, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, '2026-01-16 11:13:58');
-INSERT INTO `operation_logs` VALUES (253, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 48, '2026-01-16 11:14:02');
-INSERT INTO `operation_logs` VALUES (254, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, '2026-01-16 11:14:04');
-INSERT INTO `operation_logs` VALUES (255, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, '2026-01-16 11:14:25');
-INSERT INTO `operation_logs` VALUES (256, 5, 'cs', '::1', '/api/admin/operation-logs', 'GET', '', 200, 51, '2026-01-16 11:14:25');
-INSERT INTO `operation_logs` VALUES (257, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 142, '2026-01-16 11:14:30');
-INSERT INTO `operation_logs` VALUES (258, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 233, '2026-01-16 11:14:31');
-INSERT INTO `operation_logs` VALUES (259, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 45, '2026-01-16 11:14:32');
-INSERT INTO `operation_logs` VALUES (260, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 219, '2026-01-16 11:16:01');
-INSERT INTO `operation_logs` VALUES (261, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 221, '2026-01-16 11:16:06');
-INSERT INTO `operation_logs` VALUES (262, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 48, '2026-01-16 11:16:10');
-INSERT INTO `operation_logs` VALUES (263, 5, 'cs', '::1', '/api/admin/tags', 'GET', '', 200, 44, '2026-01-16 11:16:11');
-INSERT INTO `operation_logs` VALUES (264, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 45, '2026-01-16 11:16:12');
-INSERT INTO `operation_logs` VALUES (265, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 139, '2026-01-16 11:16:13');
-INSERT INTO `operation_logs` VALUES (266, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 153, '2026-01-16 11:22:15');
-INSERT INTO `operation_logs` VALUES (267, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 183, '2026-01-16 11:24:06');
-INSERT INTO `operation_logs` VALUES (268, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 182, '2026-01-16 11:24:10');
-INSERT INTO `operation_logs` VALUES (269, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 180, '2026-01-16 11:24:11');
-INSERT INTO `operation_logs` VALUES (270, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 193, '2026-01-16 11:24:12');
-INSERT INTO `operation_logs` VALUES (271, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 192, '2026-01-16 11:24:14');
-INSERT INTO `operation_logs` VALUES (272, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 197, '2026-01-16 11:25:30');
-INSERT INTO `operation_logs` VALUES (273, 5, 'cs', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 46, '2026-01-16 11:40:51');
-INSERT INTO `operation_logs` VALUES (274, 5, 'cs', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 47, '2026-01-16 11:48:15');
-INSERT INTO `operation_logs` VALUES (275, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 189, '2026-01-16 11:57:59');
-INSERT INTO `operation_logs` VALUES (276, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 190, '2026-01-16 11:59:06');
-INSERT INTO `operation_logs` VALUES (277, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 190, '2026-01-16 11:59:08');
-INSERT INTO `operation_logs` VALUES (278, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 189, '2026-01-16 11:59:11');
-INSERT INTO `operation_logs` VALUES (279, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 196, '2026-01-16 11:59:13');
+INSERT INTO `operation_logs` VALUES (1, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 403, 5, 0, 1768452895);
+INSERT INTO `operation_logs` VALUES (2, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768455285);
+INSERT INTO `operation_logs` VALUES (3, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 57, 0, 1768455285);
+INSERT INTO `operation_logs` VALUES (4, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 51, 0, 1768455286);
+INSERT INTO `operation_logs` VALUES (5, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768455290);
+INSERT INTO `operation_logs` VALUES (6, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 140, 0, 1768455293);
+INSERT INTO `operation_logs` VALUES (7, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768455294);
+INSERT INTO `operation_logs` VALUES (8, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 236, 0, 1768455295);
+INSERT INTO `operation_logs` VALUES (9, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768455295);
+INSERT INTO `operation_logs` VALUES (10, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768455296);
+INSERT INTO `operation_logs` VALUES (11, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768455297);
+INSERT INTO `operation_logs` VALUES (12, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, 0, 1768455301);
+INSERT INTO `operation_logs` VALUES (13, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768455301);
+INSERT INTO `operation_logs` VALUES (14, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 54, 0, 1768455437);
+INSERT INTO `operation_logs` VALUES (15, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 51, 0, 1768455437);
+INSERT INTO `operation_logs` VALUES (16, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 59, 0, 1768462099);
+INSERT INTO `operation_logs` VALUES (17, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 50, 0, 1768462099);
+INSERT INTO `operation_logs` VALUES (18, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768462100);
+INSERT INTO `operation_logs` VALUES (19, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 48, 0, 1768462102);
+INSERT INTO `operation_logs` VALUES (20, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768462103);
+INSERT INTO `operation_logs` VALUES (21, 1, 'lq', '::1', '/api/admin/users', 'POST', '{\"username\":\"cs\",\"email\":\"cs@nailaoyun.cn\",\"role\":\"viewer\",\"isActive\":1}', 200, 101, 0, 1768462115);
+INSERT INTO `operation_logs` VALUES (22, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768462115);
+INSERT INTO `operation_logs` VALUES (23, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 46, 0, 1768462120);
+INSERT INTO `operation_logs` VALUES (24, 1, 'lq', '::1', '/api/admin/users/2', 'PUT', '{\"username\":\"editor\",\"email\":\"editor@example.com\",\"role\":\"viewer\",\"isActive\":1}', 200, 97, 0, 1768462124);
+INSERT INTO `operation_logs` VALUES (25, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, 0, 1768462124);
+INSERT INTO `operation_logs` VALUES (26, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 46, 0, 1768462127);
+INSERT INTO `operation_logs` VALUES (27, 1, 'lq', '::1', '/api/admin/users/2', 'PUT', '{\"username\":\"editor\",\"email\":\"editor@example.com\",\"role\":\"editor\",\"isActive\":1}', 200, 98, 0, 1768462129);
+INSERT INTO `operation_logs` VALUES (28, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, 0, 1768462129);
+INSERT INTO `operation_logs` VALUES (29, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 132, 0, 1768462130);
+INSERT INTO `operation_logs` VALUES (30, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768462131);
+INSERT INTO `operation_logs` VALUES (31, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 237, 0, 1768462132);
+INSERT INTO `operation_logs` VALUES (32, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768462133);
+INSERT INTO `operation_logs` VALUES (33, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768462134);
+INSERT INTO `operation_logs` VALUES (34, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768462134);
+INSERT INTO `operation_logs` VALUES (35, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768462135);
+INSERT INTO `operation_logs` VALUES (36, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, 0, 1768462135);
+INSERT INTO `operation_logs` VALUES (37, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 47, 0, 1768462135);
+INSERT INTO `operation_logs` VALUES (38, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 55, 0, 1768462504);
+INSERT INTO `operation_logs` VALUES (39, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 54, 0, 1768462504);
+INSERT INTO `operation_logs` VALUES (40, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 241, 0, 1768462507);
+INSERT INTO `operation_logs` VALUES (41, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, 0, 1768462508);
+INSERT INTO `operation_logs` VALUES (42, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768462509);
+INSERT INTO `operation_logs` VALUES (43, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768462509);
+INSERT INTO `operation_logs` VALUES (44, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768462512);
+INSERT INTO `operation_logs` VALUES (45, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768462514);
+INSERT INTO `operation_logs` VALUES (46, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768462524);
+INSERT INTO `operation_logs` VALUES (47, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768462526);
+INSERT INTO `operation_logs` VALUES (48, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768462527);
+INSERT INTO `operation_logs` VALUES (49, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 143, 0, 1768462596);
+INSERT INTO `operation_logs` VALUES (50, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 49, 0, 1768462841);
+INSERT INTO `operation_logs` VALUES (51, 1, 'lq', '::1', '/api/admin/about/2', 'DELETE', '', 200, 42, 0, 1768462847);
+INSERT INTO `operation_logs` VALUES (52, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 42, 0, 1768462847);
+INSERT INTO `operation_logs` VALUES (53, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 40, 0, 1768462848);
+INSERT INTO `operation_logs` VALUES (54, 1, 'lq', '::1', '/api/admin/about/1', 'PUT', '{\"name\":\"年糕崽崽\",\"avatar\":\"https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4\",\"location\":\"中国 · 浙江杭州\",\"bio\":\"嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:技术是骨架,艺术是灵魂。
目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。\",\"email\":\"hello@niangao.dev\",\"wechat\":\"Niangao_Dev\",\"isPrimary\":true,\"experiences\":[{\"year\":\"2024 - 至今\",\"role\":\"技术负责人\",\"company\":\"某医疗平台公司\"},{\"year\":\"2020 - 2024\",\"role\":\"PHP开发工程师\",\"company\":\"某电商公司\"}],\"techStack\":[\"Vue 3\",\"React\",\"TypeScript\",\"Three.js\",\"Golang\",\"Tailwind CSS\",\"Rust\",\"Wails\"]}', 200, 55, 0, 1768462911);
+INSERT INTO `operation_logs` VALUES (55, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768462911);
+INSERT INTO `operation_logs` VALUES (56, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 45, 0, 1768462919);
+INSERT INTO `operation_logs` VALUES (57, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 221, 0, 1768462919);
+INSERT INTO `operation_logs` VALUES (58, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768462921);
+INSERT INTO `operation_logs` VALUES (59, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 131, 0, 1768462922);
+INSERT INTO `operation_logs` VALUES (60, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 44, 0, 1768462923);
+INSERT INTO `operation_logs` VALUES (61, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 42, 0, 1768462924);
+INSERT INTO `operation_logs` VALUES (62, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768462924);
+INSERT INTO `operation_logs` VALUES (63, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 42, 0, 1768462927);
+INSERT INTO `operation_logs` VALUES (64, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768464953);
+INSERT INTO `operation_logs` VALUES (65, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 51, 0, 1768464953);
+INSERT INTO `operation_logs` VALUES (66, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768464956);
+INSERT INTO `operation_logs` VALUES (67, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 51, 0, 1768464991);
+INSERT INTO `operation_logs` VALUES (68, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768464991);
+INSERT INTO `operation_logs` VALUES (69, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768464993);
+INSERT INTO `operation_logs` VALUES (70, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768464993);
+INSERT INTO `operation_logs` VALUES (71, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768464994);
+INSERT INTO `operation_logs` VALUES (72, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 239, 0, 1768464995);
+INSERT INTO `operation_logs` VALUES (73, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768464996);
+INSERT INTO `operation_logs` VALUES (74, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 51, 0, 1768464996);
+INSERT INTO `operation_logs` VALUES (75, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768464997);
+INSERT INTO `operation_logs` VALUES (76, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 50, 0, 1768464997);
+INSERT INTO `operation_logs` VALUES (77, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768464998);
+INSERT INTO `operation_logs` VALUES (78, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768464998);
+INSERT INTO `operation_logs` VALUES (79, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 48, 0, 1768464999);
+INSERT INTO `operation_logs` VALUES (80, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768465116);
+INSERT INTO `operation_logs` VALUES (81, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 54, 0, 1768465116);
+INSERT INTO `operation_logs` VALUES (82, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768465120);
+INSERT INTO `operation_logs` VALUES (83, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 52, 0, 1768465156);
+INSERT INTO `operation_logs` VALUES (84, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768465157);
+INSERT INTO `operation_logs` VALUES (85, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, 0, 1768465157);
+INSERT INTO `operation_logs` VALUES (86, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768465937);
+INSERT INTO `operation_logs` VALUES (87, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768465938);
+INSERT INTO `operation_logs` VALUES (88, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768483051);
+INSERT INTO `operation_logs` VALUES (89, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 93, 0, 1768483051);
+INSERT INTO `operation_logs` VALUES (90, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 240, 0, 1768483066);
+INSERT INTO `operation_logs` VALUES (91, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 50, 0, 1768483074);
+INSERT INTO `operation_logs` VALUES (92, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768524774);
+INSERT INTO `operation_logs` VALUES (93, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 47, 0, 1768524774);
+INSERT INTO `operation_logs` VALUES (94, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768524784);
+INSERT INTO `operation_logs` VALUES (95, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 50, 0, 1768525274);
+INSERT INTO `operation_logs` VALUES (96, 1, 'lq', '::1', '/api/admin/inquiries/1/status', 'PUT', '{\"status\":1}', 200, 49, 0, 1768525276);
+INSERT INTO `operation_logs` VALUES (97, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768525276);
+INSERT INTO `operation_logs` VALUES (98, 1, 'lq', '::1', '/api/admin/inquiries/1/status', 'PUT', '{\"status\":2}', 200, 49, 0, 1768525280);
+INSERT INTO `operation_logs` VALUES (99, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 45, 0, 1768525280);
+INSERT INTO `operation_logs` VALUES (100, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 47, 0, 1768525377);
+INSERT INTO `operation_logs` VALUES (101, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768525429);
+INSERT INTO `operation_logs` VALUES (102, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768525429);
+INSERT INTO `operation_logs` VALUES (103, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768525435);
+INSERT INTO `operation_logs` VALUES (104, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768525437);
+INSERT INTO `operation_logs` VALUES (105, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768525516);
+INSERT INTO `operation_logs` VALUES (106, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768525522);
+INSERT INTO `operation_logs` VALUES (107, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768525522);
+INSERT INTO `operation_logs` VALUES (108, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768526026);
+INSERT INTO `operation_logs` VALUES (109, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 227, 0, 1768526028);
+INSERT INTO `operation_logs` VALUES (110, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768526029);
+INSERT INTO `operation_logs` VALUES (111, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 43, 0, 1768526030);
+INSERT INTO `operation_logs` VALUES (112, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768526031);
+INSERT INTO `operation_logs` VALUES (113, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 50, 0, 1768526039);
+INSERT INTO `operation_logs` VALUES (114, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 45, 0, 1768526046);
+INSERT INTO `operation_logs` VALUES (115, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 55, 0, 1768526110);
+INSERT INTO `operation_logs` VALUES (116, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, 0, 1768526116);
+INSERT INTO `operation_logs` VALUES (117, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, 0, 1768526119);
+INSERT INTO `operation_logs` VALUES (118, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 138, 0, 1768526120);
+INSERT INTO `operation_logs` VALUES (119, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, 0, 1768526123);
+INSERT INTO `operation_logs` VALUES (120, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768526129);
+INSERT INTO `operation_logs` VALUES (121, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768526130);
+INSERT INTO `operation_logs` VALUES (122, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 47, 0, 1768526133);
+INSERT INTO `operation_logs` VALUES (123, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 45, 0, 1768526133);
+INSERT INTO `operation_logs` VALUES (124, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, 0, 1768526708);
+INSERT INTO `operation_logs` VALUES (125, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 50, 0, 1768526708);
+INSERT INTO `operation_logs` VALUES (126, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 52, 0, 1768526723);
+INSERT INTO `operation_logs` VALUES (127, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 46, 0, 1768526723);
+INSERT INTO `operation_logs` VALUES (128, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 53, 0, 1768526783);
+INSERT INTO `operation_logs` VALUES (129, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768526783);
+INSERT INTO `operation_logs` VALUES (130, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768526973);
+INSERT INTO `operation_logs` VALUES (131, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768527108);
+INSERT INTO `operation_logs` VALUES (132, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 55, 0, 1768527379);
+INSERT INTO `operation_logs` VALUES (133, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768527425);
+INSERT INTO `operation_logs` VALUES (134, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768527425);
+INSERT INTO `operation_logs` VALUES (135, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 51, 0, 1768527502);
+INSERT INTO `operation_logs` VALUES (136, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 54, 0, 1768527595);
+INSERT INTO `operation_logs` VALUES (137, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 57, 0, 1768527595);
+INSERT INTO `operation_logs` VALUES (138, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 45, 0, 1768527605);
+INSERT INTO `operation_logs` VALUES (139, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 44, 0, 1768527608);
+INSERT INTO `operation_logs` VALUES (140, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768527614);
+INSERT INTO `operation_logs` VALUES (141, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768527625);
+INSERT INTO `operation_logs` VALUES (142, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768527653);
+INSERT INTO `operation_logs` VALUES (143, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768527658);
+INSERT INTO `operation_logs` VALUES (144, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 53, 0, 1768527880);
+INSERT INTO `operation_logs` VALUES (145, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768527885);
+INSERT INTO `operation_logs` VALUES (146, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 45, 0, 1768527886);
+INSERT INTO `operation_logs` VALUES (147, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768527887);
+INSERT INTO `operation_logs` VALUES (148, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 47, 0, 1768528036);
+INSERT INTO `operation_logs` VALUES (149, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 51, 0, 1768528091);
+INSERT INTO `operation_logs` VALUES (150, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 142, 0, 1768528656);
+INSERT INTO `operation_logs` VALUES (151, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 153, 0, 1768528669);
+INSERT INTO `operation_logs` VALUES (152, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 144, 0, 1768528671);
+INSERT INTO `operation_logs` VALUES (153, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 132, 0, 1768528672);
+INSERT INTO `operation_logs` VALUES (154, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768528674);
+INSERT INTO `operation_logs` VALUES (155, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 144, 0, 1768528674);
+INSERT INTO `operation_logs` VALUES (156, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768528675);
+INSERT INTO `operation_logs` VALUES (157, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, 0, 1768528676);
+INSERT INTO `operation_logs` VALUES (158, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 57, 0, 1768528682);
+INSERT INTO `operation_logs` VALUES (159, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 97, 0, 1768528682);
+INSERT INTO `operation_logs` VALUES (160, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 145, 0, 1768528684);
+INSERT INTO `operation_logs` VALUES (161, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768528684);
+INSERT INTO `operation_logs` VALUES (162, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 242, 0, 1768528685);
+INSERT INTO `operation_logs` VALUES (163, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768528687);
+INSERT INTO `operation_logs` VALUES (164, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 50, 0, 1768528688);
+INSERT INTO `operation_logs` VALUES (165, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768528690);
+INSERT INTO `operation_logs` VALUES (166, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 245, 0, 1768528693);
+INSERT INTO `operation_logs` VALUES (167, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768528694);
+INSERT INTO `operation_logs` VALUES (168, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 99, 0, 1768528694);
+INSERT INTO `operation_logs` VALUES (169, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 220, 0, 1768528698);
+INSERT INTO `operation_logs` VALUES (170, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768528699);
+INSERT INTO `operation_logs` VALUES (171, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768528773);
+INSERT INTO `operation_logs` VALUES (172, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, 0, 1768528773);
+INSERT INTO `operation_logs` VALUES (173, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 139, 0, 1768528774);
+INSERT INTO `operation_logs` VALUES (174, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, 0, 1768528932);
+INSERT INTO `operation_logs` VALUES (175, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 141, 0, 1768528935);
+INSERT INTO `operation_logs` VALUES (176, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768528937);
+INSERT INTO `operation_logs` VALUES (177, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768528937);
+INSERT INTO `operation_logs` VALUES (178, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, 0, 1768528941);
+INSERT INTO `operation_logs` VALUES (179, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 138, 0, 1768528942);
+INSERT INTO `operation_logs` VALUES (180, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768528943);
+INSERT INTO `operation_logs` VALUES (181, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, 0, 1768528951);
+INSERT INTO `operation_logs` VALUES (182, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768529007);
+INSERT INTO `operation_logs` VALUES (183, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768529007);
+INSERT INTO `operation_logs` VALUES (184, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768529012);
+INSERT INTO `operation_logs` VALUES (185, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, 0, 1768529012);
+INSERT INTO `operation_logs` VALUES (186, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768529013);
+INSERT INTO `operation_logs` VALUES (187, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 200, 0, 1768529364);
+INSERT INTO `operation_logs` VALUES (188, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 193, 0, 1768529373);
+INSERT INTO `operation_logs` VALUES (189, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 183, 0, 1768529459);
+INSERT INTO `operation_logs` VALUES (190, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 138, 0, 1768529618);
+INSERT INTO `operation_logs` VALUES (191, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768529620);
+INSERT INTO `operation_logs` VALUES (192, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 141, 0, 1768529790);
+INSERT INTO `operation_logs` VALUES (193, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768529867);
+INSERT INTO `operation_logs` VALUES (194, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, 0, 1768529867);
+INSERT INTO `operation_logs` VALUES (195, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768529869);
+INSERT INTO `operation_logs` VALUES (196, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768530104);
+INSERT INTO `operation_logs` VALUES (197, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, 0, 1768530104);
+INSERT INTO `operation_logs` VALUES (198, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, 0, 1768530104);
+INSERT INTO `operation_logs` VALUES (199, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 53, 0, 1768532353);
+INSERT INTO `operation_logs` VALUES (200, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 103, 0, 1768532353);
+INSERT INTO `operation_logs` VALUES (201, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 93, 0, 1768532365);
+INSERT INTO `operation_logs` VALUES (202, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, 0, 1768532365);
+INSERT INTO `operation_logs` VALUES (203, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, 0, 1768532370);
+INSERT INTO `operation_logs` VALUES (204, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768532371);
+INSERT INTO `operation_logs` VALUES (205, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 237, 0, 1768532372);
+INSERT INTO `operation_logs` VALUES (206, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768532375);
+INSERT INTO `operation_logs` VALUES (207, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768532375);
+INSERT INTO `operation_logs` VALUES (208, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768532380);
+INSERT INTO `operation_logs` VALUES (209, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 45, 0, 1768532385);
+INSERT INTO `operation_logs` VALUES (210, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768532386);
+INSERT INTO `operation_logs` VALUES (211, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768532405);
+INSERT INTO `operation_logs` VALUES (212, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 53, 0, 1768532407);
+INSERT INTO `operation_logs` VALUES (213, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768532409);
+INSERT INTO `operation_logs` VALUES (214, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 147, 0, 1768532410);
+INSERT INTO `operation_logs` VALUES (215, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, 0, 1768532440);
+INSERT INTO `operation_logs` VALUES (216, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768532441);
+INSERT INTO `operation_logs` VALUES (217, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768532443);
+INSERT INTO `operation_logs` VALUES (218, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, 0, 1768532446);
+INSERT INTO `operation_logs` VALUES (219, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 150, 0, 1768532460);
+INSERT INTO `operation_logs` VALUES (220, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768532475);
+INSERT INTO `operation_logs` VALUES (221, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, 0, 1768532475);
+INSERT INTO `operation_logs` VALUES (222, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 148, 0, 1768532520);
+INSERT INTO `operation_logs` VALUES (223, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768532585);
+INSERT INTO `operation_logs` VALUES (224, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768532585);
+INSERT INTO `operation_logs` VALUES (225, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 93, 0, 1768532681);
+INSERT INTO `operation_logs` VALUES (226, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768532681);
+INSERT INTO `operation_logs` VALUES (227, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768532725);
+INSERT INTO `operation_logs` VALUES (228, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 100, 0, 1768532725);
+INSERT INTO `operation_logs` VALUES (229, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768532738);
+INSERT INTO `operation_logs` VALUES (230, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, 0, 1768532738);
+INSERT INTO `operation_logs` VALUES (231, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 94, 0, 1768532910);
+INSERT INTO `operation_logs` VALUES (232, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 50, 0, 1768532910);
+INSERT INTO `operation_logs` VALUES (233, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 49, 0, 1768532912);
+INSERT INTO `operation_logs` VALUES (234, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768532914);
+INSERT INTO `operation_logs` VALUES (235, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768532915);
+INSERT INTO `operation_logs` VALUES (236, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768532917);
+INSERT INTO `operation_logs` VALUES (237, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768532919);
+INSERT INTO `operation_logs` VALUES (238, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 51, 0, 1768533052);
+INSERT INTO `operation_logs` VALUES (239, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 246, 0, 1768533154);
+INSERT INTO `operation_logs` VALUES (240, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768533159);
+INSERT INTO `operation_logs` VALUES (241, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768533167);
+INSERT INTO `operation_logs` VALUES (242, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, 0, 1768533173);
+INSERT INTO `operation_logs` VALUES (243, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768533202);
+INSERT INTO `operation_logs` VALUES (244, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, 0, 1768533202);
+INSERT INTO `operation_logs` VALUES (245, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 94, 0, 1768533212);
+INSERT INTO `operation_logs` VALUES (246, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, 0, 1768533212);
+INSERT INTO `operation_logs` VALUES (247, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768533214);
+INSERT INTO `operation_logs` VALUES (248, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768533222);
+INSERT INTO `operation_logs` VALUES (249, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, 0, 1768533226);
+INSERT INTO `operation_logs` VALUES (250, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, 0, 1768533229);
+INSERT INTO `operation_logs` VALUES (251, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768533229);
+INSERT INTO `operation_logs` VALUES (252, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768533238);
+INSERT INTO `operation_logs` VALUES (253, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 48, 0, 1768533242);
+INSERT INTO `operation_logs` VALUES (254, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, 0, 1768533244);
+INSERT INTO `operation_logs` VALUES (255, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768533265);
+INSERT INTO `operation_logs` VALUES (256, 5, 'cs', '::1', '/api/admin/operation-logs', 'GET', '', 200, 51, 0, 1768533265);
+INSERT INTO `operation_logs` VALUES (257, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 142, 0, 1768533270);
+INSERT INTO `operation_logs` VALUES (258, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 233, 0, 1768533271);
+INSERT INTO `operation_logs` VALUES (259, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768533272);
+INSERT INTO `operation_logs` VALUES (260, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 219, 0, 1768533361);
+INSERT INTO `operation_logs` VALUES (261, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 221, 0, 1768533366);
+INSERT INTO `operation_logs` VALUES (262, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768533370);
+INSERT INTO `operation_logs` VALUES (263, 5, 'cs', '::1', '/api/admin/tags', 'GET', '', 200, 44, 0, 1768533371);
+INSERT INTO `operation_logs` VALUES (264, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768533372);
+INSERT INTO `operation_logs` VALUES (265, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 139, 0, 1768533373);
+INSERT INTO `operation_logs` VALUES (266, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 153, 0, 1768533735);
+INSERT INTO `operation_logs` VALUES (267, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 183, 0, 1768533846);
+INSERT INTO `operation_logs` VALUES (268, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 182, 0, 1768533850);
+INSERT INTO `operation_logs` VALUES (269, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 180, 0, 1768533851);
+INSERT INTO `operation_logs` VALUES (270, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 193, 0, 1768533852);
+INSERT INTO `operation_logs` VALUES (271, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 192, 0, 1768533854);
+INSERT INTO `operation_logs` VALUES (272, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 197, 0, 1768533930);
+INSERT INTO `operation_logs` VALUES (273, 5, 'cs', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 46, 0, 1768534851);
+INSERT INTO `operation_logs` VALUES (274, 5, 'cs', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 47, 0, 1768535295);
+INSERT INTO `operation_logs` VALUES (275, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 189, 0, 1768535879);
+INSERT INTO `operation_logs` VALUES (276, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 190, 0, 1768535946);
+INSERT INTO `operation_logs` VALUES (277, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 190, 0, 1768535948);
+INSERT INTO `operation_logs` VALUES (278, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 189, 0, 1768535951);
+INSERT INTO `operation_logs` VALUES (279, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 196, 0, 1768535953);
+INSERT INTO `operation_logs` VALUES (280, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768537859);
+INSERT INTO `operation_logs` VALUES (281, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768537860);
+INSERT INTO `operation_logs` VALUES (282, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 250, 0, 1768538179);
+INSERT INTO `operation_logs` VALUES (283, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768538180);
+INSERT INTO `operation_logs` VALUES (284, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 224, 0, 1768538193);
+INSERT INTO `operation_logs` VALUES (285, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768538193);
+INSERT INTO `operation_logs` VALUES (286, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 245, 0, 1768539279);
+INSERT INTO `operation_logs` VALUES (287, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768539282);
+INSERT INTO `operation_logs` VALUES (288, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768539301);
+INSERT INTO `operation_logs` VALUES (289, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 236, 0, 1768539305);
+INSERT INTO `operation_logs` VALUES (290, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 43, 0, 1768539306);
+INSERT INTO `operation_logs` VALUES (291, 5, 'cs', '::1', '/api/admin/tags', 'GET', '', 200, 45, 0, 1768539308);
+INSERT INTO `operation_logs` VALUES (292, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768539309);
+INSERT INTO `operation_logs` VALUES (293, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 53, 0, 1768539311);
+INSERT INTO `operation_logs` VALUES (294, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 306, 0, 1768539314);
+INSERT INTO `operation_logs` VALUES (295, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 299, 0, 1768539320);
+INSERT INTO `operation_logs` VALUES (296, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768539435);
+INSERT INTO `operation_logs` VALUES (297, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 62, 0, 1768539635);
-- ----------------------------
-- Table structure for partners
@@ -428,19 +449,20 @@ CREATE TABLE `partners` (
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '合作伙伴介绍',
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '合作伙伴官网链接',
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序权重',
- `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
+ `updated_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作伙伴表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of partners
-- ----------------------------
-INSERT INTO `partners` VALUES (1, 'Vercel', 'https://upload.wikimedia.org/wikipedia/commons/5/5e/Vercel_logo_black.svg', '前端部署平台', 'https://vercel.com', 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33');
-INSERT INTO `partners` VALUES (2, 'Supabase', 'https://seeklogo.com/images/S/supabase-logo-DCC676FFE2-seeklogo.com.png', '开源 Firebase 替代方案', 'https://supabase.com', 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33');
-INSERT INTO `partners` VALUES (3, 'Stripe', 'https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg', '在线支付基础设施', 'https://stripe.com', 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33');
-INSERT INTO `partners` VALUES (4, 'Algolia', 'https://upload.wikimedia.org/wikipedia/commons/6/69/Algolia-logo.svg', '搜索即服务 API', 'https://algolia.com', 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33');
-INSERT INTO `partners` VALUES (5, 'Prisma', 'https://seeklogo.com/images/P/prisma-logo-3805665B69-seeklogo.com.png', '下一代 ORM', 'https://prisma.io', 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33');
+INSERT INTO `partners` VALUES (1, 'Vercel', 'https://upload.wikimedia.org/wikipedia/commons/5/5e/Vercel_logo_black.svg', '前端部署平台', 'https://vercel.com', 0, 0, 1768482993, 1768538953);
+INSERT INTO `partners` VALUES (2, 'Supabase', 'https://seeklogo.com/images/S/supabase-logo-DCC676FFE2-seeklogo.com.png', '开源 Firebase 替代方案', 'https://supabase.com', 0, 0, 1768482993, 1768538953);
+INSERT INTO `partners` VALUES (3, 'Stripe', 'https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg', '在线支付基础设施', 'https://stripe.com', 0, 0, 1768482993, 1768538953);
+INSERT INTO `partners` VALUES (4, 'Algolia', 'https://upload.wikimedia.org/wikipedia/commons/6/69/Algolia-logo.svg', '搜索即服务 API', 'https://algolia.com', 0, 0, 1768482993, 1768538953);
+INSERT INTO `partners` VALUES (5, 'Prisma', 'https://seeklogo.com/images/P/prisma-logo-3805665B69-seeklogo.com.png', '下一代 ORM', 'https://prisma.io', 0, 0, 1768482993, 1768538953);
-- ----------------------------
-- Table structure for permissions
@@ -451,8 +473,9 @@ CREATE TABLE `permissions` (
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名称',
`resource` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '资源名称',
`action` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作名称',
- `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
+ `updated_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `unique_resource_action`(`resource` ASC, `action` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = DYNAMIC;
@@ -460,36 +483,36 @@ CREATE TABLE `permissions` (
-- ----------------------------
-- Records of permissions
-- ----------------------------
-INSERT INTO `permissions` VALUES (1, 'Create User', 'users', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (2, 'Read User', 'users', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (3, 'Update User', 'users', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (4, 'Delete User', 'users', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (5, 'Create Role', 'roles', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (6, 'Read Role', 'roles', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (7, 'Update Role', 'roles', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (8, 'Delete Role', 'roles', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (9, 'Create Post', 'posts', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (10, 'Read Post', 'posts', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (11, 'Update Post', 'posts', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (12, 'Delete Post', 'posts', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (13, 'Create Work', 'works', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (14, 'Read Work', 'works', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (15, 'Update Work', 'works', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (16, 'Delete Work', 'works', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (17, 'Create Snippet', 'snippets', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (18, 'Read Snippet', 'snippets', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (19, 'Update Snippet', 'snippets', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (20, 'Delete Snippet', 'snippets', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (21, 'Create Setting', 'settings', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (22, 'Read Setting', 'settings', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (23, 'Update Setting', 'settings', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (24, 'Delete Setting', 'settings', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (25, 'Create Tag', 'tags', 'create', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (26, 'Read Tag', 'tags', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (27, 'Update Tag', 'tags', 'update', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (28, 'Delete Tag', 'tags', 'delete', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (29, 'Read Operation Log', 'operation_logs', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
-INSERT INTO `permissions` VALUES (30, 'Read Dashboard', 'dashboard', 'read', '2026-01-15 12:54:52', '2026-01-15 12:54:52');
+INSERT INTO `permissions` VALUES (1, 'Create User', 'users', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (2, 'Read User', 'users', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (3, 'Update User', 'users', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (4, 'Delete User', 'users', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (5, 'Create Role', 'roles', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (6, 'Read Role', 'roles', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (7, 'Update Role', 'roles', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (8, 'Delete Role', 'roles', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (9, 'Create Post', 'posts', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (10, 'Read Post', 'posts', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (11, 'Update Post', 'posts', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (12, 'Delete Post', 'posts', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (13, 'Create Work', 'works', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (14, 'Read Work', 'works', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (15, 'Update Work', 'works', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (16, 'Delete Work', 'works', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (17, 'Create Snippet', 'snippets', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (18, 'Read Snippet', 'snippets', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (19, 'Update Snippet', 'snippets', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (20, 'Delete Snippet', 'snippets', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (21, 'Create Setting', 'settings', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (22, 'Read Setting', 'settings', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (23, 'Update Setting', 'settings', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (24, 'Delete Setting', 'settings', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (25, 'Create Tag', 'tags', 'create', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (26, 'Read Tag', 'tags', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (27, 'Update Tag', 'tags', 'update', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (28, 'Delete Tag', 'tags', 'delete', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (29, 'Read Operation Log', 'operation_logs', 'read', 0, 1768452892, 1768538956);
+INSERT INTO `permissions` VALUES (30, 'Read Dashboard', 'dashboard', 'read', 0, 1768452892, 1768538956);
-- ----------------------------
-- Table structure for post_tags
@@ -497,8 +520,8 @@ INSERT INTO `permissions` VALUES (30, 'Read Dashboard', 'dashboard', 'read', '20
DROP TABLE IF EXISTS `post_tags`;
CREATE TABLE `post_tags` (
`tag_id` bigint UNSIGNED NOT NULL COMMENT '关联的标签ID',
- `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`post_id` int UNSIGNED NOT NULL COMMENT '关联的文章ID',
+ `created_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`post_id`, `tag_id`) USING BTREE,
INDEX `idx_tag_id`(`tag_id` ASC) USING BTREE,
CONSTRAINT `post_tags_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
@@ -518,16 +541,15 @@ CREATE TABLE `posts` (
`original_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '原始字符串ID备份',
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章标题',
`category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章分类',
- `date` date NOT NULL COMMENT '发布日期',
`excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文章摘要',
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章内容',
`read_count` int UNSIGNED NULL DEFAULT 0 COMMENT '阅读量',
`is_published` tinyint(1) NULL DEFAULT 1 COMMENT '是否已发布(0:草稿,1:已发布)',
- `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted_at` bigint NOT NULL DEFAULT 0,
+ `created_at` bigint NOT NULL DEFAULT 0,
+ `updated_at` bigint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_category`(`category` ASC) USING BTREE COMMENT '按分类查询索引',
- INDEX `idx_date`(`date` ASC) USING BTREE COMMENT '按发布日期查询索引',
INDEX `idx_is_published`(`is_published` ASC) USING BTREE COMMENT '按发布状态查询索引',
FULLTEXT INDEX `idx_title_content`(`title`, `content`) COMMENT '标题和内容全文索引,用于搜索',
INDEX `idx_original_id`(`original_id` ASC) USING BTREE
@@ -536,12 +558,12 @@ CREATE TABLE `posts` (
-- ----------------------------
-- Records of posts
-- ----------------------------
-INSERT INTO `posts` VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', '工程化', '2026-01-13', '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '
代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...
', 4, 1, '2026-01-13 16:10:14', '2026-01-15 17:01:44'); -INSERT INTO `posts` VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', '图形渲染', '2026-01-13', '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', 'GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。
\r\n柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。
\r\n在 Three.js 中,我们可以通过 ShaderMaterial 直接编写 GLSL 代码。
// 简单的顶点着色器\r\nvarying vec2 vUv;\r\nvoid main() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}\r\n 通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。
\r\n ', 1, 1, '2026-01-13 16:10:14', '2026-01-15 17:01:44'); -INSERT INTO `posts` VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', '设计思维', '2026-01-13', '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '了解用户的认知过程是设计良好用户体验的基础...
', 0, 1, '2026-01-13 16:10:14', '2026-01-15 17:01:44'); -INSERT INTO `posts` VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 'Go语言', '2026-01-15', '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, '2026-01-15 16:32:12', '2026-01-16 11:08:41'); -INSERT INTO `posts` VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 'Go语言', '2026-01-15', '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 900, 1, '2026-01-15 16:32:12', '2026-01-16 11:12:49'); -INSERT INTO `posts` VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 'Go语言', '2026-01-15', '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\n\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\n\n## 配置数据库\n\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\n\n```env\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=goravel\nDB_USERNAME=root\nDB_PASSWORD=password\n```\n\n## 定义模型\n\n使用 `knit` 生成模型:\n\n```bash\nknit make:model Post\n```\n\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\n\n```go\npackage models\n\nimport (\n \"github.com/goravel/framework/database/orm\"\n)\n\ntype Post struct {\n orm.Model\n Title string `gorm:\"size:255;not null\"`\n Content string `gorm:\"type:text\"`\n UserID uint\n}\n```\n\n## 数据库迁移\n\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\n\n```bash\nknit make:migration create_posts_table\n```\n\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\n\n```bash\nknit migrate\n```\n\n## CRUD 操作\n\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\n\n### 创建 (Create)\n\n```go\npost := models.Post{\n Title: \"My First Post\",\n Content: \"Content goes here...\",\n}\nerr := facades.Orm().Query().Create(&post)\n```\n\n### 查询 (Read)\n\n```go\nvar post models.Post\n// 根据主键查询\nfacades.Orm().Query().Find(&post, 1)\n\n// 条件查询\nvar posts []models.Post\nfacades.Orm().Query().Where(\"title\", \"My First Post\").Get(&posts)\n```\n\n### 更新 (Update)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Find(&post, 1)\n\npost.Title = \"Updated Title\"\nfacades.Orm().Query().Save(&post)\n```\n\n### 删除 (Delete)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Delete(&post, 1)\n```\n\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1561, 1, '2026-01-15 16:32:12', '2026-01-16 11:14:59'); +INSERT INTO `posts` VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', '工程化', '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...
', 5, 1, 0, 1768291814, 1768538949); +INSERT INTO `posts` VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', '图形渲染', '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', 'GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。
\r\n柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。
\r\n在 Three.js 中,我们可以通过 ShaderMaterial 直接编写 GLSL 代码。
// 简单的顶点着色器\r\nvarying vec2 vUv;\r\nvoid main() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}\r\n 通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。
\r\n ', 1, 1, 0, 1768291815, 1768538949); +INSERT INTO `posts` VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', '设计思维', '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '了解用户的认知过程是设计良好用户体验的基础...
', 0, 1, 0, 1768291816, 1768538949); +INSERT INTO `posts` VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 'Go语言', '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, 0, 1768465932, 1768538949); +INSERT INTO `posts` VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 'Go语言', '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 901, 1, 0, 1768465933, 1768538949); +INSERT INTO `posts` VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 'Go语言', '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\n\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\n\n## 配置数据库\n\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\n\n```env\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=goravel\nDB_USERNAME=root\nDB_PASSWORD=password\n```\n\n## 定义模型\n\n使用 `knit` 生成模型:\n\n```bash\nknit make:model Post\n```\n\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\n\n```go\npackage models\n\nimport (\n \"github.com/goravel/framework/database/orm\"\n)\n\ntype Post struct {\n orm.Model\n Title string `gorm:\"size:255;not null\"`\n Content string `gorm:\"type:text\"`\n UserID uint\n}\n```\n\n## 数据库迁移\n\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\n\n```bash\nknit make:migration create_posts_table\n```\n\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\n\n```bash\nknit migrate\n```\n\n## CRUD 操作\n\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\n\n### 创建 (Create)\n\n```go\npost := models.Post{\n Title: \"My First Post\",\n Content: \"Content goes here...\",\n}\nerr := facades.Orm().Query().Create(&post)\n```\n\n### 查询 (Read)\n\n```go\nvar post models.Post\n// 根据主键查询\nfacades.Orm().Query().Find(&post, 1)\n\n// 条件查询\nvar posts []models.Post\nfacades.Orm().Query().Where(\"title\", \"My First Post\").Get(&posts)\n```\n\n### 更新 (Update)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Find(&post, 1)\n\npost.Title = \"Updated Title\"\nfacades.Orm().Query().Save(&post)\n```\n\n### 删除 (Delete)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Delete(&post, 1)\n```\n\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1562, 1, 0, 1768465934, 1768538949); -- ---------------------------- -- Table structure for role_permissions @@ -611,8 +633,9 @@ CREATE TABLE `roles` ( `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称', `description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色描述', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `name`(`name` ASC) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC; @@ -620,9 +643,9 @@ CREATE TABLE `roles` ( -- ---------------------------- -- Records of roles -- ---------------------------- -INSERT INTO `roles` VALUES (1, 'admin', '系统管理员', '2026-01-15 12:54:52', '2026-01-15 12:54:52'); -INSERT INTO `roles` VALUES (2, 'editor', '内容编辑', '2026-01-15 12:54:52', '2026-01-15 12:54:52'); -INSERT INTO `roles` VALUES (3, 'viewer', '普通访客', '2026-01-15 12:54:52', '2026-01-15 12:54:52'); +INSERT INTO `roles` VALUES (1, 'admin', '系统管理员', 0, 1768452892, 1768538956); +INSERT INTO `roles` VALUES (2, 'editor', '内容编辑', 0, 1768452892, 1768538956); +INSERT INTO `roles` VALUES (3, 'viewer', '普通访客', 0, 1768452892, 1768538956); -- ---------------------------- -- Table structure for settings @@ -633,8 +656,9 @@ CREATE TABLE `settings` ( `key_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置项键名', `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '配置项值', `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '配置项描述', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `key_name`(`key_name` ASC) USING BTREE, INDEX `idx_key_name`(`key_name` ASC) USING BTREE COMMENT '按键名查询索引' @@ -643,13 +667,13 @@ CREATE TABLE `settings` ( -- ---------------------------- -- Records of settings -- ---------------------------- -INSERT INTO `settings` VALUES (1, 'site_title', '年糕博客', '网站标题', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `settings` VALUES (2, 'site_description', '分享前端技术、交互设计以及数字艺术的深度思考', '网站描述', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `settings` VALUES (3, 'site_author', '年糕崽崽', '网站作者', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `settings` VALUES (4, 'site_keywords', '前端, 设计, 技术博客', '网站关键词', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `settings` VALUES (5, 'posts_per_page', '10', '每页显示的文章数量', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `settings` VALUES (6, 'works_per_page', '6', '每页显示的作品数量', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `settings` VALUES (7, 'snippets_per_page', '8', '每页显示的代码片段数量', '2026-01-13 16:10:14', '2026-01-13 16:10:14'); +INSERT INTO `settings` VALUES (1, 'site_title', '年糕博客', '网站标题', 0, 1768291814, 1768538952); +INSERT INTO `settings` VALUES (2, 'site_description', '分享前端技术、交互设计以及数字艺术的深度思考', '网站描述', 0, 1768291814, 1768538952); +INSERT INTO `settings` VALUES (3, 'site_author', '年糕崽崽', '网站作者', 0, 1768291814, 1768538952); +INSERT INTO `settings` VALUES (4, 'site_keywords', '前端, 设计, 技术博客', '网站关键词', 0, 1768291814, 1768538952); +INSERT INTO `settings` VALUES (5, 'posts_per_page', '10', '每页显示的文章数量', 0, 1768291814, 1768538952); +INSERT INTO `settings` VALUES (6, 'works_per_page', '6', '每页显示的作品数量', 0, 1768291814, 1768538952); +INSERT INTO `settings` VALUES (7, 'snippets_per_page', '8', '每页显示的代码片段数量', 0, 1768291814, 1768538952); -- ---------------------------- -- Table structure for snippets @@ -662,8 +686,9 @@ CREATE TABLE `snippets` ( `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码类型(如:javascript、css、html等)', `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '代码片段描述', `view_count` int UNSIGNED NULL DEFAULT 0 COMMENT '查看次数', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, INDEX `idx_type`(`type` ASC) USING BTREE COMMENT '按代码类型查询索引', INDEX `idx_view_count`(`view_count` ASC) USING BTREE COMMENT '按查看次数查询索引' @@ -672,7 +697,7 @@ CREATE TABLE `snippets` ( -- ---------------------------- -- Records of snippets -- ---------------------------- -INSERT INTO `snippets` VALUES ('1', 'React 鼠标追踪 Hook', 'import { useState, useEffect } from \'react\';\r\n\r\nexport const useMousePosition = () => {\r\n const [pos, setPos] = useState({ x: 0, y: 0 });\r\n useEffect(() => {\r\n const update = (e) => setPos({ x: e.clientX, y: e.clientY });\r\n window.addEventListener(\'mousemove\', update);\r\n return () => window.removeEventListener(\'mousemove\', update);\r\n }, []);\r\n return pos;\r\n};', 'mouse', '这是一个鼠标追踪', 1, '2026-01-14 08:32:19', '2026-01-16 11:10:53'); +INSERT INTO `snippets` VALUES ('1', 'React 鼠标追踪 Hook', 'import { useState, useEffect } from \'react\';\r\n\r\nexport const useMousePosition = () => {\r\n const [pos, setPos] = useState({ x: 0, y: 0 });\r\n useEffect(() => {\r\n const update = (e) => setPos({ x: e.clientX, y: e.clientY });\r\n window.addEventListener(\'mousemove\', update);\r\n return () => window.removeEventListener(\'mousemove\', update);\r\n }, []);\r\n return pos;\r\n};', 'mouse', '这是一个鼠标追踪', 1, 0, 1768350739, 1768538952); -- ---------------------------- -- Table structure for tags @@ -682,13 +707,14 @@ CREATE TABLE `tags` ( `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标签名称', `slug` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标签别名,用于URL', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `name`(`name` ASC) USING BTREE, UNIQUE INDEX `slug`(`slug` ASC) USING BTREE, INDEX `idx_slug`(`slug` ASC) USING BTREE COMMENT '按别名查询索引' -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签表' ROW_FORMAT = DYNAMIC; +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of tags @@ -706,19 +732,20 @@ CREATE TABLE `testimonials` ( `avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户头像URL', `rating` tinyint UNSIGNED NULL DEFAULT 5 COMMENT '评分(1-5)', `sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序权重', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客户评价表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of testimonials -- ---------------------------- -INSERT INTO `testimonials` VALUES (1, 'Alex Chen', 'Product Owner @ TechFlow', '年糕不仅技术过硬,对设计细节的把控更是令人惊叹。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alex', 5, 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33'); -INSERT INTO `testimonials` VALUES (2, 'Sarah Wu', 'Design Director @ ArtSpace', '很少见到能把代码写得像诗一样的工程师,合作非常愉快!', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah', 5, 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33'); -INSERT INTO `testimonials` VALUES (3, 'Mike Zhang', 'CTO @ FutureWave', '交付质量远超预期,特别是在性能优化方面做得非常出色。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Mike', 5, 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33'); -INSERT INTO `testimonials` VALUES (4, 'Jessica Li', 'Founder @ ZenMode', '从交互动效到整体架构,都体现了极高的专业水准。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Jessica', 5, 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33'); -INSERT INTO `testimonials` VALUES (5, 'David Wang', 'Tech Lead @ Innovate', '代码结构清晰,注释完善,后续维护非常轻松。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=David', 5, 0, '2026-01-15 21:16:33', '2026-01-15 21:16:33'); +INSERT INTO `testimonials` VALUES (1, 'Alex Chen', 'Product Owner @ TechFlow', '年糕不仅技术过硬,对设计细节的把控更是令人惊叹。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alex', 5, 0, 0, 1768482993, 1768538954); +INSERT INTO `testimonials` VALUES (2, 'Sarah Wu', 'Design Director @ ArtSpace', '很少见到能把代码写得像诗一样的工程师,合作非常愉快!', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah', 5, 0, 0, 1768482993, 1768538954); +INSERT INTO `testimonials` VALUES (3, 'Mike Zhang', 'CTO @ FutureWave', '交付质量远超预期,特别是在性能优化方面做得非常出色。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Mike', 5, 0, 0, 1768482993, 1768538954); +INSERT INTO `testimonials` VALUES (4, 'Jessica Li', 'Founder @ ZenMode', '从交互动效到整体架构,都体现了极高的专业水准。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Jessica', 5, 0, 0, 1768482993, 1768538954); +INSERT INTO `testimonials` VALUES (5, 'David Wang', 'Tech Lead @ Innovate', '代码结构清晰,注释完善,后续维护非常轻松。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=David', 5, 0, 0, 1768482993, 1768538954); -- ---------------------------- -- Table structure for user_access_logs @@ -730,24 +757,27 @@ CREATE TABLE `user_access_logs` ( `user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户IP地址', `user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户归属地', `article_id` int NOT NULL COMMENT '访问的文章ID', - `access_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '访问时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `access_time` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, INDEX `idx_user_id`(`user_id` ASC) USING BTREE, - INDEX `idx_article_id`(`article_id` ASC) USING BTREE, - INDEX `idx_access_time`(`access_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户访问记录表' ROW_FORMAT = Dynamic; + INDEX `idx_article_id`(`article_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户访问记录表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of user_access_logs -- ---------------------------- -INSERT INTO `user_access_logs` VALUES (1, 0, '::1', 'Unknown', 5, '2026-01-16 10:11:55'); -INSERT INTO `user_access_logs` VALUES (2, 0, '::1', 'Unknown', 4, '2026-01-16 10:13:10'); -INSERT INTO `user_access_logs` VALUES (3, 0, '::1', 'Unknown', 5, '2026-01-16 10:13:24'); -INSERT INTO `user_access_logs` VALUES (4, 0, '::1', 'Unknown', 4, '2026-01-16 10:13:34'); -INSERT INTO `user_access_logs` VALUES (5, 0, '::1', 'Unknown', 5, '2026-01-16 10:13:35'); -INSERT INTO `user_access_logs` VALUES (6, 0, '::1', 'Unknown', 4, '2026-01-16 11:08:41'); -INSERT INTO `user_access_logs` VALUES (7, 0, '::1', 'Unknown', 5, '2026-01-16 11:12:49'); -INSERT INTO `user_access_logs` VALUES (8, 0, '::1', 'Unknown', 6, '2026-01-16 11:14:59'); +INSERT INTO `user_access_logs` VALUES (1, 0, '::1', 'Unknown', 5, 0, 1768529515); +INSERT INTO `user_access_logs` VALUES (2, 0, '::1', 'Unknown', 4, 0, 1768529590); +INSERT INTO `user_access_logs` VALUES (3, 0, '::1', 'Unknown', 5, 0, 1768529604); +INSERT INTO `user_access_logs` VALUES (4, 0, '::1', 'Unknown', 4, 0, 1768529614); +INSERT INTO `user_access_logs` VALUES (5, 0, '::1', 'Unknown', 5, 0, 1768529615); +INSERT INTO `user_access_logs` VALUES (6, 0, '::1', 'Unknown', 4, 0, 1768532921); +INSERT INTO `user_access_logs` VALUES (7, 0, '::1', 'Unknown', 5, 0, 1768533169); +INSERT INTO `user_access_logs` VALUES (8, 0, '::1', 'Unknown', 6, 0, 1768533299); +INSERT INTO `user_access_logs` VALUES (9, 0, '::1', 'Unknown', 1, 0, 1768538210); +INSERT INTO `user_access_logs` VALUES (10, 0, '::1', 'Unknown', 6, 0, 20260116125547); +INSERT INTO `user_access_logs` VALUES (11, 0, '::1', 'Unknown', 5, 0, 20260116125614); -- ---------------------------- -- Table structure for users @@ -761,8 +791,9 @@ CREATE TABLE `users` ( `role_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '角色ID', `role` enum('admin','editor','viewer') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'viewer' COMMENT '用户角色(兼容旧版)', `is_active` tinyint(1) NULL DEFAULT 1 COMMENT '是否激活(0:禁用,1:激活)', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `username`(`username` ASC) USING BTREE, UNIQUE INDEX `email`(`email` ASC) USING BTREE, @@ -776,9 +807,9 @@ CREATE TABLE `users` ( -- ---------------------------- -- Records of users -- ---------------------------- -INSERT INTO `users` VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 1, 'admin', 1, '2026-01-13 16:10:14', '2026-01-15 13:22:46'); -INSERT INTO `users` VALUES (2, 'editor', 'editor@example.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 2, 'editor', 1, '2026-01-13 16:10:14', '2026-01-16 11:14:14'); -INSERT INTO `users` VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 3, 'viewer', 1, '2026-01-15 15:28:35', '2026-01-16 11:14:17'); +INSERT INTO `users` VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 1, 'admin', 1, 0, 1768291814, 1768538951); +INSERT INTO `users` VALUES (2, 'editor', 'editor@example.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 2, 'editor', 1, 0, 1768291814, 1768538951); +INSERT INTO `users` VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 3, 'viewer', 1, 0, 1768462115, 1768538951); -- ---------------------------- -- Table structure for work_gallery @@ -790,7 +821,8 @@ CREATE TABLE `work_gallery` ( `image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片URL', `sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序顺序,数值越小越靠前', `description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '图片描述', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, INDEX `idx_work_id`(`work_id` ASC) USING BTREE, INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE, @@ -800,10 +832,10 @@ CREATE TABLE `work_gallery` ( -- ---------------------------- -- Records of work_gallery -- ---------------------------- -INSERT INTO `work_gallery` VALUES (1, 'nova', 'https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070', 1, 'Nova 交易平台首页', '2026-01-13 16:12:17'); -INSERT INTO `work_gallery` VALUES (2, 'nova', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070', 2, 'Nova 交易平台交易界面', '2026-01-13 16:12:17'); -INSERT INTO `work_gallery` VALUES (3, 'archdaily', 'https://images.unsplash.com/photo-1503387762-592deb58ef4e?q=80&w=2089', 1, 'ArchDaily 网站首页', '2026-01-13 16:12:17'); -INSERT INTO `work_gallery` VALUES (4, 'archdaily', 'https://images.unsplash.com/photo-1518005020951-ecc859466abc?q=80&w=1920', 2, 'ArchDaily 文章详情页', '2026-01-13 16:12:17'); +INSERT INTO `work_gallery` VALUES (1, 'nova', 'https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070', 1, 'Nova 交易平台首页', 0, 1768291937); +INSERT INTO `work_gallery` VALUES (2, 'nova', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070', 2, 'Nova 交易平台交易界面', 0, 1768291937); +INSERT INTO `work_gallery` VALUES (3, 'archdaily', 'https://images.unsplash.com/photo-1503387762-592deb58ef4e?q=80&w=2089', 1, 'ArchDaily 网站首页', 0, 1768291937); +INSERT INTO `work_gallery` VALUES (4, 'archdaily', 'https://images.unsplash.com/photo-1518005020951-ecc859466abc?q=80&w=1920', 2, 'ArchDaily 文章详情页', 0, 1768291937); -- ---------------------------- -- Table structure for work_tech_stack @@ -814,7 +846,8 @@ CREATE TABLE `work_tech_stack` ( `work_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联的作品ID', `category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '技术分类(如:前端、后端、数据库等)', `item` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '具体技术项(如:Vue 3、Golang、MySQL等)', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, INDEX `idx_work_id`(`work_id` ASC) USING BTREE, INDEX `idx_category`(`category` ASC) USING BTREE, @@ -824,15 +857,15 @@ CREATE TABLE `work_tech_stack` ( -- ---------------------------- -- Records of work_tech_stack -- ---------------------------- -INSERT INTO `work_tech_stack` VALUES (1, 'nova', '前端层', 'React 18', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (2, 'nova', '前端层', 'TypeScript', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (3, 'nova', '前端层', 'D3.js', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (4, 'nova', '后端服务', 'Golang', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (5, 'nova', '后端服务', 'gRPC', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (6, 'archdaily', '核心前端', 'Vue 3', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (7, 'archdaily', '核心前端', 'Nuxt.js', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (8, 'archdaily', '核心前端', 'GSAP', '2026-01-13 16:12:17'); -INSERT INTO `work_tech_stack` VALUES (9, 'archdaily', 'CMS', 'Strapi', '2026-01-13 16:12:17'); +INSERT INTO `work_tech_stack` VALUES (1, 'nova', '前端层', 'React 18', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (2, 'nova', '前端层', 'TypeScript', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (3, 'nova', '前端层', 'D3.js', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (4, 'nova', '后端服务', 'Golang', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (5, 'nova', '后端服务', 'gRPC', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (6, 'archdaily', '核心前端', 'Vue 3', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (7, 'archdaily', '核心前端', 'Nuxt.js', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (8, 'archdaily', '核心前端', 'GSAP', 0, 1768291937); +INSERT INTO `work_tech_stack` VALUES (9, 'archdaily', 'CMS', 'Strapi', 0, 1768291937); -- ---------------------------- -- Table structure for works @@ -846,8 +879,9 @@ CREATE TABLE `works` ( `hero_img` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品主图URL', `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品详细描述', `is_featured` tinyint(1) NULL DEFAULT 0 COMMENT '是否为精选作品(0:否,1:是)', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` bigint NOT NULL DEFAULT 0, + `created_at` bigint NOT NULL DEFAULT 0, + `updated_at` bigint NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE, INDEX `idx_category`(`category` ASC) USING BTREE COMMENT '按分类查询索引', INDEX `idx_year`(`year` ASC) USING BTREE COMMENT '按年份查询索引', @@ -857,7 +891,7 @@ CREATE TABLE `works` ( -- ---------------------------- -- Records of works -- ---------------------------- -INSERT INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 1, '2026-01-13 16:10:14', '2026-01-15 15:04:52'); +INSERT INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', 1, 0, 1768291814, 1768538952); +INSERT INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 1, 0, 1768291814, 1768538952); SET FOREIGN_KEY_CHECKS = 1; diff --git a/server/repositories/about_repository.go b/server/repositories/about_repository.go index 699f40d..94f9a94 100644 --- a/server/repositories/about_repository.go +++ b/server/repositories/about_repository.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -12,9 +13,9 @@ import ( // GetPrimaryAboutProfile 获取主页个人资料 func GetPrimaryAboutProfile() (*models.AboutProfile, error) { query := ` - SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at + 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 + WHERE is_primary = TRUE AND deleted_at = 0 LIMIT 1 ` row := config.DB.QueryRow(query) @@ -33,6 +34,7 @@ func GetPrimaryAboutProfile() (*models.AboutProfile, error) { &profile.IsPrimary, &profile.CreatedAt, &profile.UpdatedAt, + &profile.DeletedAt, ); err != nil { if err == sql.ErrNoRows { // If no primary profile, try to get the first one @@ -61,8 +63,9 @@ 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 + 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 ` @@ -82,6 +85,7 @@ func GetFirstAboutProfile() (*models.AboutProfile, error) { &profile.IsPrimary, &profile.CreatedAt, &profile.UpdatedAt, + &profile.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -107,7 +111,7 @@ func GetFirstAboutProfile() (*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 FROM about_profiles" + 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) if err != nil { log.Printf("Error querying about profiles: %v", err) @@ -131,6 +135,7 @@ func GetAllAboutProfiles() ([]models.AboutProfile, error) { &p.IsPrimary, &p.CreatedAt, &p.UpdatedAt, + &p.DeletedAt, ); err != nil { continue } @@ -158,9 +163,10 @@ 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) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + 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, @@ -173,6 +179,8 @@ func CreateAboutProfile(profile *models.AboutProfile) error { profile.TechStack, profile.ExperiencesStr, profile.IsPrimary, + now, + now, ) if err != nil { log.Printf("Error creating about profile: %v", err) @@ -184,6 +192,8 @@ func CreateAboutProfile(profile *models.AboutProfile) error { return err } profile.ID = uint(id) + profile.CreatedAt = now + profile.UpdatedAt = now return nil } @@ -195,10 +205,11 @@ 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 = NOW() - WHERE id = ? + 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, @@ -211,6 +222,7 @@ func UpdateAboutProfile(profile *models.AboutProfile) error { profile.TechStack, profile.ExperiencesStr, profile.IsPrimary, + now, profile.ID, ) if err != nil { @@ -220,10 +232,11 @@ func UpdateAboutProfile(profile *models.AboutProfile) error { return nil } -// DeleteAboutProfile 删除个人资料 +// DeleteAboutProfile 删除个人资料 (Soft Delete) func DeleteAboutProfile(id uint) error { - query := "DELETE FROM about_profiles WHERE id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := "UPDATE about_profiles SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { log.Printf("Error deleting about profile: %v", err) return err diff --git a/server/repositories/inquiry_repository.go b/server/repositories/inquiry_repository.go index 7dd373b..b5ead90 100644 --- a/server/repositories/inquiry_repository.go +++ b/server/repositories/inquiry_repository.go @@ -2,6 +2,7 @@ package repositories import ( "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -9,8 +10,9 @@ import ( // CreateInquiry 创建咨询 func CreateInquiry(inquiry *models.Inquiry) error { - query := `INSERT INTO inquiries (name, company, contact_method, contact_value, budget, description, status) VALUES (?, ?, ?, ?, ?, ?, 0)` - _, err := config.DB.Exec(query, inquiry.Name, inquiry.Company, inquiry.ContactMethod, inquiry.ContactValue, inquiry.Budget, inquiry.Description) + 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) if err != nil { log.Printf("Error creating inquiry: %v", err) return err @@ -20,7 +22,8 @@ func CreateInquiry(inquiry *models.Inquiry) error { // GetInquiries 获取咨询列表 (Admin) func GetInquiries() ([]models.Inquiry, error) { - rows, err := config.DB.Query("SELECT id, name, company, contact_method, contact_value, budget, description, status, created_at FROM inquiries ORDER BY created_at DESC") + // 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") if err != nil { return nil, err } @@ -29,15 +32,21 @@ func GetInquiries() ([]models.Inquiry, error) { var inquiries []models.Inquiry for rows.Next() { var i models.Inquiry - var createdAtStr string - if err := rows.Scan(&i.ID, &i.Name, &i.Company, &i.ContactMethod, &i.ContactValue, &i.Budget, &i.Description, &i.Status, &createdAtStr); err != nil { + 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 } - // 解析时间字符串 (MySQL driver default behavior might differ based on parseTime param) - // Assuming config.DB has parseTime=true, if not we scan to string. - // For safety, let's scan to string or handle time parsing if needed, but standard scan to time.Time works if parseTime=true - // Based on previous files, I'll assume standard scan or ignore strict parsing for now, or scan to string and not parse to keep simple display. - // Let's assume standard behavior. If error, I'll fix. inquiries = append(inquiries, i) } return inquiries, nil @@ -45,7 +54,7 @@ func GetInquiries() ([]models.Inquiry, error) { // GetEmailSuffixes 获取活跃的邮箱后缀 func GetEmailSuffixes() ([]models.EmailSuffix, error) { - rows, err := config.DB.Query("SELECT id, suffix, is_active, sort_order FROM email_suffixes WHERE is_active = 1 ORDER BY sort_order ASC") + 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 } @@ -55,7 +64,15 @@ func GetEmailSuffixes() ([]models.EmailSuffix, error) { for rows.Next() { var s models.EmailSuffix var isActive int - if err := rows.Scan(&s.ID, &s.Suffix, &isActive, &s.SortOrder); err != nil { + 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 @@ -66,7 +83,7 @@ func GetEmailSuffixes() ([]models.EmailSuffix, error) { // AdminGetEmailSuffixes 获取所有邮箱后缀 (Admin) func AdminGetEmailSuffixes() ([]models.EmailSuffix, error) { - rows, err := config.DB.Query("SELECT id, suffix, is_active, sort_order, created_at FROM email_suffixes ORDER BY sort_order ASC") + 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 } @@ -76,8 +93,15 @@ func AdminGetEmailSuffixes() ([]models.EmailSuffix, error) { for rows.Next() { var s models.EmailSuffix var isActive int - var createdAt []uint8 // Handle potential []byte - if err := rows.Scan(&s.ID, &s.Suffix, &isActive, &s.SortOrder, &createdAt); err != nil { + 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 @@ -88,36 +112,40 @@ func AdminGetEmailSuffixes() ([]models.EmailSuffix, error) { // CreateEmailSuffix 创建邮箱后缀 func CreateEmailSuffix(suffix *models.EmailSuffix) error { - query := `INSERT INTO email_suffixes (suffix, is_active, sort_order) VALUES (?, ?, ?)` + 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) + _, err := config.DB.Exec(query, suffix.Suffix, isActive, suffix.SortOrder, now, now) return err } // UpdateEmailSuffix 更新邮箱后缀 func UpdateEmailSuffix(suffix *models.EmailSuffix) error { - query := `UPDATE email_suffixes SET suffix = ?, is_active = ?, sort_order = ? WHERE id = ?` + 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, suffix.ID) + _, err := config.DB.Exec(query, suffix.Suffix, isActive, suffix.SortOrder, now, suffix.ID) return err } -// DeleteEmailSuffix 删除邮箱后缀 +// DeleteEmailSuffix 删除邮箱后缀 (Soft Delete) func DeleteEmailSuffix(id uint) error { - query := `DELETE FROM email_suffixes WHERE id = ?` - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := `UPDATE email_suffixes SET deleted_at = ? WHERE id = ?` + _, err := config.DB.Exec(query, now, id) return err } // UpdateInquiryStatus 更新咨询状态 func UpdateInquiryStatus(id uint, status int) error { - query := `UPDATE inquiries SET status = ? WHERE id = ?` - _, err := config.DB.Exec(query, status, id) + 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 } diff --git a/server/repositories/log_repository.go b/server/repositories/log_repository.go index 030968c..a5bac55 100644 --- a/server/repositories/log_repository.go +++ b/server/repositories/log_repository.go @@ -8,39 +8,39 @@ import ( "github.com/niangaodev/art-code/models" ) -// Helper to parse date strings flexibly -func parseDateString(dateStr string, isEnd bool) string { +// Helper to parse date string to unix timestamp +func parseDateToUnix(dateStr string, isEnd bool) int64 { if dateStr == "" { - return "" + return 0 } // Try parsing with time first t, err := time.ParseInLocation("2006-01-02 15:04", dateStr, time.Local) if err == nil { if isEnd { - // If it's end time, go to end of that minute - return t.Format("2006-01-02 15:04") + ":59" + // HH:mm:59 + return t.Add(59 * time.Second).Unix() } - return t.Format("2006-01-02 15:04:05") + return t.Unix() } // Try parsing just date t, err = time.ParseInLocation("2006-01-02", dateStr, time.Local) if err == nil { if isEnd { - // If it's end date, go to end of day - return t.Format("2006-01-02") + " 23:59:59" + // 23:59:59 + return t.Add(24*time.Hour - 1*time.Second).Unix() } - return t.Format("2006-01-02") + " 00:00:00" + return t.Unix() } - // Return original if parsing fails (fallback) - return dateStr + return 0 } // CreateAccessLog 创建访问日志 func CreateAccessLog(log *models.AccessLog) error { - query := `INSERT INTO access_logs (ip, user_agent, path, method, status_code, response_time, region) VALUES (?, ?, ?, ?, ?, ?, ?)` - _, err := config.DB.Exec(query, log.IP, log.UserAgent, log.Path, log.Method, log.StatusCode, log.ResponseTime, log.Region) + 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 } @@ -55,25 +55,27 @@ type UVTrendData struct { // GetDailyUV 获取UV趋势 func GetDailyUV(startDate, endDate string) ([]UVTrendData, error) { query := ` - SELECT DATE_FORMAT(access_time, '%Y-%m-%d') as date, COUNT(DISTINCT user_ip) as count + 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{}{} if startDate != "" { - formattedStart := parseDateString(startDate, false) + startUnix := parseDateToUnix(startDate, false) query += " AND access_time >= ?" - args = append(args, formattedStart) + args = append(args, startUnix) } else { // 默认最近7天 - query += " AND access_time >= DATE_SUB(CURDATE(), INTERVAL 6 DAY)" + startUnix := time.Now().AddDate(0, 0, -6).Unix() + query += " AND access_time >= ?" + args = append(args, startUnix) } if endDate != "" { - formattedEnd := parseDateString(endDate, true) + endUnix := parseDateToUnix(endDate, true) query += " AND access_time <= ?" - args = append(args, formattedEnd) + args = append(args, endUnix) } query += ` @@ -116,15 +118,15 @@ func GetUserRegions(startDate, endDate string) ([]struct { args := []interface{}{} if startDate != "" { - formattedStart := parseDateString(startDate, false) + startUnix := parseDateToUnix(startDate, false) query += " AND access_time >= ?" - args = append(args, formattedStart) + args = append(args, startUnix) } if endDate != "" { - formattedEnd := parseDateString(endDate, true) + endUnix := parseDateToUnix(endDate, true) query += " AND access_time <= ?" - args = append(args, formattedEnd) + args = append(args, endUnix) } query += ` diff --git a/server/repositories/migration.go b/server/repositories/migration.go new file mode 100644 index 0000000..8db4cac --- /dev/null +++ b/server/repositories/migration.go @@ -0,0 +1,116 @@ +package repositories + +import ( + "fmt" + "log" + + "github.com/niangaodev/art-code/config" +) + +// MigrateToBigInt performs schema migration to BigInt timestamps +func MigrateToBigInt() { + tables := []string{ + "posts", "users", "tags", "works", "snippets", "settings", + "about_profiles", "partners", "testimonials", "inquiries", + "email_suffixes", "access_logs", "user_access_logs", + "operation_logs", "permissions", "roles", + "work_tech_stack", "work_gallery", "post_tags", "post_history", + } + + for _, table := range tables { + log.Printf("Migrating table: %s", table) + + // 1. Add deleted_at if not exists + if !columnExists(table, "deleted_at") && table != "post_tags" { // post_tags doesn't need deleted_at in my previous plan? I added to all in SQL file? Yes. + // Actually post_tags in SQL file I modified: `created_at` bigint. No `deleted_at`. + // So skip deleted_at for post_tags. + if table != "post_tags" { + execSQL(fmt.Sprintf("ALTER TABLE `%s` ADD COLUMN `deleted_at` BIGINT NOT NULL DEFAULT 0", table)) + } + } + + // 2. Migrate created_at + migrateColumn(table, "created_at") + + // 3. Migrate updated_at (if exists) + if columnExists(table, "updated_at") { + migrateColumn(table, "updated_at") + } + + // 4. Migrate access_time (for user_access_logs) + if table == "user_access_logs" && columnExists(table, "access_time") { + migrateColumn(table, "access_time") + } + + // 5. Remove 'date' from posts if exists + if table == "posts" && columnExists(table, "date") { + execSQL("ALTER TABLE `posts` DROP COLUMN `date`") + // Drop index idx_date if exists? MySQL drops index on column drop usually. + } + } +} + +func columnExists(tableName, colName string) bool { + query := ` + 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) + if err != nil { + log.Printf("Error checking column %s.%s: %v", tableName, colName, err) + return false + } + return count > 0 +} + +func isBigInt(tableName, colName string) bool { + query := ` + 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) + if err != nil { + return false + } + return dataType == "bigint" || dataType == "int" +} + +func migrateColumn(table, col string) { + if !columnExists(table, col) { + return + } + if isBigInt(table, col) { + return // Already migrated + } + + log.Printf("Converting %s.%s to BIGINT...", table, col) + + // Rename old + oldCol := col + "_old_dt" + execSQL(fmt.Sprintf("ALTER TABLE `%s` CHANGE `%s` `%s` DATETIME", table, col, oldCol)) // Ensure it's treated as datetime for rename + + // Add new + execSQL(fmt.Sprintf("ALTER TABLE `%s` ADD COLUMN `%s` BIGINT NOT NULL DEFAULT 0", table, col)) + + // Copy and Convert + execSQL(fmt.Sprintf("UPDATE `%s` SET `%s` = UNIX_TIMESTAMP(`%s`) WHERE `%s` IS NOT NULL", table, col, oldCol, oldCol)) + + // Drop old + execSQL(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN `%s`", table, oldCol)) +} + +func execSQL(query string) { + _, err := config.DB.Exec(query) + if err != nil { + // Log but continue (might fail if column doesn't exist etc) + log.Printf("SQL Error: %v | Query: %s", err, query) + } +} diff --git a/server/repositories/operation_log_repository.go b/server/repositories/operation_log_repository.go index c2e1bd3..4d7d54d 100644 --- a/server/repositories/operation_log_repository.go +++ b/server/repositories/operation_log_repository.go @@ -2,6 +2,7 @@ package repositories import ( "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -9,9 +10,10 @@ 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) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW()) + INSERT INTO operation_logs (user_id, username, ip, path, method, params, status, duration, created_at, deleted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0) ` _, err := config.DB.Exec( query, @@ -23,6 +25,7 @@ func CreateOperationLog(operationLog *models.OperationLog) error { operationLog.Params, operationLog.Status, operationLog.Duration, + now, ) if err != nil { log.Printf("Error creating operation log: %v", err) @@ -39,7 +42,7 @@ func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error) // 获取总记录数 var total int64 - countQuery := "SELECT COUNT(*) FROM operation_logs" + countQuery := "SELECT COUNT(*) FROM operation_logs WHERE deleted_at = 0" if err := config.DB.QueryRow(countQuery).Scan(&total); err != nil { log.Printf("Error counting operation logs: %v", err) return nil, 0, err @@ -47,8 +50,9 @@ func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error) // 获取分页数据 query := ` - SELECT id, user_id, username, ip, path, method, params, status, duration, created_at + 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 ? ` @@ -73,6 +77,7 @@ func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error) &operationLog.Status, &operationLog.Duration, &operationLog.CreatedAt, + &operationLog.DeletedAt, ); err != nil { log.Printf("Error scanning operation log: %v", err) continue @@ -95,7 +100,7 @@ func BuildOperationLogResponse(log *models.OperationLog) *models.OperationLogRes Params: log.Params, Status: log.Status, Duration: log.Duration, - CreatedAt: log.CreatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: time.Unix(log.CreatedAt, 0).Format("2006-01-02 15:04:05"), } } diff --git a/server/repositories/permission_repository.go b/server/repositories/permission_repository.go index 7a40a94..09a9858 100644 --- a/server/repositories/permission_repository.go +++ b/server/repositories/permission_repository.go @@ -2,6 +2,7 @@ package repositories import ( "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -9,7 +10,8 @@ import ( // GetPermissions 获取所有权限 func GetPermissions() ([]models.Permission, error) { - query := "SELECT id, name, resource, action, created_at, updated_at FROM permissions" + // 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) if err != nil { log.Printf("Error getting permissions: %v", err) @@ -20,7 +22,7 @@ func GetPermissions() ([]models.Permission, error) { 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); err != nil { + 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 } @@ -33,10 +35,10 @@ func GetPermissions() ([]models.Permission, error) { // GetPermissionsByRoleID 获取指定角色的权限 func GetPermissionsByRoleID(roleID uint) ([]models.Permission, error) { query := ` - SELECT p.id, p.name, p.resource, p.action, p.created_at, p.updated_at + 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 = ? + WHERE rp.role_id = ? AND p.deleted_at = 0 ` rows, err := config.DB.Query(query, roleID) if err != nil { @@ -48,7 +50,7 @@ func GetPermissionsByRoleID(roleID uint) ([]models.Permission, error) { 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); err != nil { + 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 } @@ -65,16 +67,23 @@ func BuildPermissionResponse(permission *models.Permission) *models.PermissionRe Name: permission.Name, Resource: permission.Resource, Action: permission.Action, - CreatedAt: permission.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: permission.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: time.Unix(permission.CreatedAt, 0).Format("2006-01-02 15:04:05"), + UpdatedAt: time.Unix(permission.UpdatedAt, 0).Format("2006-01-02 15:04:05"), } } // BuildPermissionsResponse 构建权限列表响应 func BuildPermissionsResponse(permissions []models.Permission) []models.PermissionResponse { var responses []models.PermissionResponse - for _, permission := range permissions { - responses = append(responses, *BuildPermissionResponse(&permission)) + for _, p := range permissions { + responses = append(responses, models.PermissionResponse{ + ID: p.ID, + Name: p.Name, + Resource: p.Resource, + Action: p.Action, + CreatedAt: time.Unix(p.CreatedAt, 0).Format("2006-01-02 15:04:05"), + UpdatedAt: time.Unix(p.UpdatedAt, 0).Format("2006-01-02 15:04:05"), + }) } return responses } diff --git a/server/repositories/post_repository.go b/server/repositories/post_repository.go index 8e999ab..18c7ead 100644 --- a/server/repositories/post_repository.go +++ b/server/repositories/post_repository.go @@ -9,44 +9,20 @@ import ( "github.com/niangaodev/art-code/models" ) -// Helper to parse date strings flexibly (duplicated to avoid circular dependency if moved to utils, or just keep simple) -// Ideally this should be in a utils package, but for now we'll keep it local to avoid refactoring everything -func parsePostDateString(dateStr string, isEnd bool) string { - if dateStr == "" { - return "" - } - // Try parsing with time first - t, err := time.ParseInLocation("2006-01-02 15:04", dateStr, time.Local) - if err == nil { - if isEnd { - return t.Format("2006-01-02 15:04") + ":59" - } - return t.Format("2006-01-02 15:04:05") - } - - // Try parsing just date - t, err = time.ParseInLocation("2006-01-02", dateStr, time.Local) - if err == nil { - if isEnd { - return t.Format("2006-01-02") + " 23:59:59" - } - return t.Format("2006-01-02") + " 00:00:00" - } - - return dateStr -} - // GetPosts 获取所有博客文章(支持搜索) func GetPosts(keyword string) ([]models.Post, error) { var rows *sql.Rows var err error + // Common select fields (removed date) + selectFields := "id, title, category, excerpt, content, read_count, is_published, created_at, updated_at, deleted_at" + if keyword != "" { // 使用全文搜索 query := ` - SELECT id, title, category, date, excerpt, content, read_count, is_published, created_at, updated_at + SELECT ` + selectFields + ` FROM posts - WHERE is_published = 1 AND ( + WHERE is_published = 1 AND deleted_at = 0 AND ( MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ? @@ -57,7 +33,7 @@ func GetPosts(keyword string) ([]models.Post, error) { rows, err = config.DB.Query(query, keyword, likeKeyword, likeKeyword) } else { // 默认查询 - query := "SELECT id, title, category, date, excerpt, content, read_count, is_published, created_at, updated_at FROM posts WHERE is_published = 1 ORDER BY created_at DESC" + query := "SELECT " + selectFields + " FROM posts WHERE is_published = 1 AND deleted_at = 0 ORDER BY created_at DESC" rows, err = config.DB.Query(query) } @@ -74,13 +50,13 @@ func GetPosts(keyword string) ([]models.Post, error) { &post.ID, &post.Title, &post.Category, - &post.Date, &post.Excerpt, &post.Content, &post.ReadCount, &post.IsPublished, &post.CreatedAt, &post.UpdatedAt, + &post.DeletedAt, ); err != nil { log.Printf("Error scanning post: %v", err) continue @@ -93,7 +69,8 @@ func GetPosts(keyword string) ([]models.Post, error) { // GetPostByID 根据ID获取博客文章 func GetPostByID(id uint) (*models.Post, error) { - query := "SELECT id, title, category, date, excerpt, content, read_count, is_published, created_at, updated_at FROM posts WHERE id = ? AND is_published = 1" + selectFields := "id, title, category, excerpt, content, read_count, is_published, created_at, updated_at, deleted_at" + query := "SELECT " + selectFields + " FROM posts WHERE id = ? AND is_published = 1 AND deleted_at = 0" row := config.DB.QueryRow(query, id) var post models.Post @@ -101,13 +78,13 @@ func GetPostByID(id uint) (*models.Post, error) { &post.ID, &post.Title, &post.Category, - &post.Date, &post.Excerpt, &post.Content, &post.ReadCount, &post.IsPublished, &post.CreatedAt, &post.UpdatedAt, + &post.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -127,7 +104,8 @@ func GetPostByID(id uint) (*models.Post, error) { // GetAllPosts 获取所有博客文章(包括未发布的) func GetAllPosts() ([]models.Post, error) { - query := "SELECT id, title, category, date, excerpt, content, read_count, is_published, created_at, updated_at FROM posts ORDER BY created_at DESC" + selectFields := "id, title, category, excerpt, content, read_count, is_published, created_at, updated_at, deleted_at" + query := "SELECT " + selectFields + " FROM posts WHERE deleted_at = 0 ORDER BY created_at DESC" rows, err := config.DB.Query(query) if err != nil { log.Printf("Error querying all posts: %v", err) @@ -142,13 +120,13 @@ func GetAllPosts() ([]models.Post, error) { &post.ID, &post.Title, &post.Category, - &post.Date, &post.Excerpt, &post.Content, &post.ReadCount, &post.IsPublished, &post.CreatedAt, &post.UpdatedAt, + &post.DeletedAt, ); err != nil { log.Printf("Error scanning post: %v", err) continue @@ -161,18 +139,20 @@ func GetAllPosts() ([]models.Post, error) { // CreatePost 创建博客文章 func CreatePost(post *models.Post) error { + now := time.Now().Unix() query := ` - INSERT INTO posts (title, category, date, excerpt, content, is_published, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW()) + INSERT INTO posts (title, category, excerpt, content, is_published, created_at, updated_at, deleted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, 0) ` result, err := config.DB.Exec( query, post.Title, post.Category, - post.Date, post.Excerpt, post.Content, post.IsPublished, + now, + now, ) if err != nil { log.Printf("Error creating post: %v", err) @@ -184,24 +164,27 @@ func CreatePost(post *models.Post) error { return err } post.ID = uint(id) + post.CreatedAt = now + post.UpdatedAt = now return nil } // UpdatePost 更新博客文章 func UpdatePost(post *models.Post) error { + now := time.Now().Unix() query := ` - UPDATE posts SET title = ?, category = ?, date = ?, excerpt = ?, content = ?, is_published = ?, updated_at = NOW() - WHERE id = ? + UPDATE posts SET title = ?, category = ?, excerpt = ?, content = ?, is_published = ?, updated_at = ? + WHERE id = ? AND deleted_at = 0 ` _, err := config.DB.Exec( query, post.Title, post.Category, - post.Date, post.Excerpt, post.Content, post.IsPublished, + now, post.ID, ) if err != nil { @@ -212,10 +195,11 @@ func UpdatePost(post *models.Post) error { return nil } -// DeletePost 删除博客文章 +// DeletePost 删除博客文章 (Soft Delete) func DeletePost(id uint) error { - query := "DELETE FROM posts WHERE id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := "UPDATE posts SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { log.Printf("Error deleting post: %v", err) return err @@ -227,7 +211,7 @@ func DeletePost(id uint) error { // GetPostCount 获取文章总数 func GetPostCount() (int, error) { var count int - query := "SELECT COUNT(*) FROM posts" + query := "SELECT COUNT(*) FROM posts WHERE deleted_at = 0" row := config.DB.QueryRow(query) err := row.Scan(&count) @@ -241,11 +225,14 @@ func GetPostCount() (int, error) { // BuildPostResponse 构建博客文章响应 func BuildPostResponse(post *models.Post, includeContent bool) *models.PostResponse { + // Format CreatedAt to Date string + dateStr := time.Unix(post.CreatedAt, 0).Format("2006-01-02") + response := &models.PostResponse{ ID: post.ID, Title: post.Title, Category: post.Category, - Date: post.Date, + Date: dateStr, Excerpt: post.Excerpt, } @@ -275,24 +262,55 @@ func SavePostHistory(post *models.Post, modifiedBy uint) error { return err } - // 插入新的历史记录 + now := time.Now().Unix() + // 插入新的历史记录 (PostHistory struct updated to int64) + // Note: post_history table also needs to be updated to support bigint timestamps if not already. + // Assuming user wanted ALL tables updated, but I missed checking post_history structure explicitly in sql file scan. + // But assuming I applied "all tables" logic if it existed. + // Wait, post_history wasn't in the SQL file dump I read earlier? + // I will double check. If it's missing, I might get errors. + // The SQL dump showed `posts`, `users` etc. `post_history` was NOT in the dump I read? + // Let me check the Read output again. + // It wasn't there! `post_tags` was there. `post_history` is missing from the SQL dump provided by the user? + // Or maybe I missed it. + // If it doesn't exist, this code will fail. + // But `GetPostHistory` exists in the repo, so the table MUST exist. + // I will assume it exists and uses the same convention. + + // PostHistory model has Date string? + // Check models/post.go: + // type PostHistory struct { ... Date string ... } + // The struct I updated earlier removed Date? + // No, I checked PostHistory in models/post.go, it had Date string. + // And I updated it to: + // Date string (removed?) + // Let's check my model update for PostHistory. + // I removed `Date string` from PostHistory? + // `type PostHistory struct { ... Title string; Category string; Excerpt string ... }` + // Yes, I removed Date. + // So I should remove `date` from Insert too. + insertQuery := ` INSERT INTO post_history ( - post_id, version, title, category, date, excerpt, content, + post_id, version, title, category, excerpt, content, is_published, modified_by, modified_at, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` + // Date string generation? Post history usually snapshots the post state. + // If post has no date column, history shouldn't either. + _, err := config.DB.Exec( insertQuery, post.ID, maxVersion+1, post.Title, post.Category, - post.Date, post.Excerpt, post.Content, post.IsPublished, modifiedBy, + now, + now, ) if err != nil { log.Printf("Error saving post history: %v", err) @@ -305,7 +323,7 @@ func SavePostHistory(post *models.Post, modifiedBy uint) error { // GetPostHistory 获取文章历史记录 func GetPostHistory(postID uint) ([]models.PostHistory, error) { query := ` - SELECT id, post_id, version, title, category, date, excerpt, content, + SELECT id, post_id, version, title, category, excerpt, content, is_published, modified_by, modified_at, created_at FROM post_history WHERE post_id = ? @@ -327,7 +345,6 @@ func GetPostHistory(postID uint) ([]models.PostHistory, error) { &h.Version, &h.Title, &h.Category, - &h.Date, // Scan date directly into h.Date &h.Excerpt, &h.Content, &h.IsPublished, @@ -347,7 +364,7 @@ func GetPostHistory(postID uint) ([]models.PostHistory, error) { // GetPostHistoryByVersion 获取指定版本的文章历史记录 func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, error) { query := ` - SELECT id, post_id, version, title, category, date, excerpt, content, + SELECT id, post_id, version, title, category, excerpt, content, is_published, modified_by, modified_at, created_at FROM post_history WHERE post_id = ? AND version = ? @@ -361,7 +378,6 @@ func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, er &h.Version, &h.Title, &h.Category, - &h.Date, &h.Excerpt, &h.Content, &h.IsPublished, @@ -387,11 +403,11 @@ func BuildPostHistoryResponse(history *models.PostHistory) *models.PostHistoryRe Version: history.Version, Title: history.Title, Category: history.Category, - Date: history.Date, + Date: time.Unix(history.CreatedAt, 0).Format("2006-01-02"), // Compute date IsPublished: history.IsPublished, ModifiedBy: history.ModifiedBy, - ModifiedAt: history.ModifiedAt.Format("2006-01-02 15:04:05"), - CreatedAt: history.CreatedAt.Format("2006-01-02 15:04:05"), + ModifiedAt: time.Unix(history.ModifiedAt, 0).Format("2006-01-02 15:04:05"), + CreatedAt: time.Unix(history.CreatedAt, 0).Format("2006-01-02 15:04:05"), } } @@ -415,36 +431,29 @@ type TrendData struct { // GetNewPostsTrend 获取新增文章趋势 (带同比环比) // 支持按日/周/月/年维度统计 func GetNewPostsTrend(startDate, endDate string) ([]TrendData, error) { - // 默认按日统计 - dateFormat := "%Y-%m-%d" - - // 根据时间范围自动调整粒度 (简化逻辑:如果跨度大于3个月则按月,大于3年则按年) - // 这里为了简化,暂时保留前端传递的日期范围,后端可以根据startDate和endDate计算跨度 - // 但SQL中动态GROUP BY比较复杂,这里先默认按日,前端可以自行聚合或者我们根据需求扩展 - - // 如果需要更智能的粒度,可以解析startDate和endDate - // ... - + // Use FROM_UNIXTIME to format timestamp query := ` - SELECT DATE_FORMAT(created_at, ?) as date, COUNT(*) as count + SELECT FROM_UNIXTIME(created_at, '%Y-%m-%d') as date, COUNT(*) as count FROM posts - WHERE 1=1 + WHERE deleted_at = 0 ` - args := []interface{}{dateFormat} + args := []interface{}{} if startDate != "" { - formattedStart := parsePostDateString(startDate, false) + startUnix := parseDateToUnix(startDate, false) query += " AND created_at >= ?" - args = append(args, formattedStart) + args = append(args, startUnix) } else { - // 默认最近7天 - query += " AND created_at >= DATE_SUB(CURDATE(), INTERVAL 6 DAY)" + // Default 7 days + startUnix := time.Now().AddDate(0, 0, -6).Unix() + query += " AND created_at >= ?" + args = append(args, startUnix) } if endDate != "" { - formattedEnd := parsePostDateString(endDate, true) + endUnix := parseDateToUnix(endDate, true) query += " AND created_at <= ?" - args = append(args, formattedEnd) + args = append(args, endUnix) } query += ` @@ -474,13 +483,8 @@ func GetNewPostsTrend(startDate, endDate string) ([]TrendData, error) { // GetTopPosts 获取热门文章 (按阅读量) func GetTopPosts(limit int) ([]models.Post, error) { - query := ` - SELECT id, title, category, date, excerpt, content, read_count, is_published, created_at, updated_at - FROM posts - WHERE is_published = 1 - ORDER BY read_count DESC - LIMIT ? - ` + selectFields := "id, title, category, excerpt, content, read_count, is_published, created_at, updated_at, deleted_at" + query := "SELECT " + selectFields + " FROM posts WHERE is_published = 1 AND deleted_at = 0 ORDER BY read_count DESC LIMIT ?" rows, err := config.DB.Query(query, limit) if err != nil { return nil, err @@ -494,13 +498,13 @@ func GetTopPosts(limit int) ([]models.Post, error) { &post.ID, &post.Title, &post.Category, - &post.Date, &post.Excerpt, &post.Content, &post.ReadCount, &post.IsPublished, &post.CreatedAt, &post.UpdatedAt, + &post.DeletedAt, ); err != nil { continue } diff --git a/server/repositories/role_repository.go b/server/repositories/role_repository.go index 7a2b122..3744207 100644 --- a/server/repositories/role_repository.go +++ b/server/repositories/role_repository.go @@ -10,7 +10,7 @@ import ( // GetRoles 获取所有角色 func GetRoles() ([]models.Role, error) { - query := "SELECT id, name, description, created_at, updated_at FROM roles" + query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE deleted_at = 0" rows, err := config.DB.Query(query) if err != nil { log.Printf("Error getting roles: %v", err) @@ -21,7 +21,7 @@ func GetRoles() ([]models.Role, error) { 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); err != nil { + 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 } @@ -38,11 +38,11 @@ func GetRoles() ([]models.Role, error) { // GetRoleByID 根据ID获取角色 func GetRoleByID(id uint) (*models.Role, error) { - query := "SELECT id, name, description, created_at, updated_at FROM roles WHERE id = ?" + 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); err != nil { + if err := row.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil { log.Printf("Error getting role by ID: %v", err) return nil, err } @@ -56,11 +56,11 @@ func GetRoleByID(id uint) (*models.Role, error) { // GetRoleByName 根据名称获取角色 func GetRoleByName(name string) (*models.Role, error) { - query := "SELECT id, name, description, created_at, updated_at FROM roles WHERE name = ?" + 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); err != nil { + if err := row.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil { log.Printf("Error getting role by name: %v", err) return nil, err } @@ -70,8 +70,9 @@ func GetRoleByName(name string) (*models.Role, error) { // CreateRole 创建角色 func CreateRole(role *models.Role) error { - query := "INSERT INTO roles (name, description, created_at, updated_at) VALUES (?, ?, NOW(), NOW())" - result, err := config.DB.Exec(query, role.Name, role.Description) + 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) if err != nil { log.Printf("Error creating role: %v", err) return err @@ -82,16 +83,17 @@ func CreateRole(role *models.Role) error { return err } role.ID = uint(id) - role.CreatedAt = time.Now() - role.UpdatedAt = time.Now() + role.CreatedAt = now + role.UpdatedAt = now return nil } // UpdateRole 更新角色 func UpdateRole(role *models.Role) error { - query := "UPDATE roles SET name = ?, description = ?, updated_at = NOW() WHERE id = ?" - _, err := config.DB.Exec(query, role.Name, role.Description, role.ID) + 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) if err != nil { log.Printf("Error updating role: %v", err) return err @@ -99,10 +101,11 @@ func UpdateRole(role *models.Role) error { return nil } -// DeleteRole 删除角色 +// DeleteRole 删除角色 (Soft Delete) func DeleteRole(id uint) error { - query := "DELETE FROM roles WHERE id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := "UPDATE roles SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { log.Printf("Error deleting role: %v", err) return err @@ -151,8 +154,8 @@ func BuildRoleResponse(role *models.Role) *models.RoleResponse { Name: role.Name, Description: role.Description, Permissions: BuildPermissionsResponse(role.Permissions), - CreatedAt: role.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: role.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: time.Unix(role.CreatedAt, 0).Format("2006-01-02 15:04:05"), + UpdatedAt: time.Unix(role.UpdatedAt, 0).Format("2006-01-02 15:04:05"), } } diff --git a/server/repositories/setting_repository.go b/server/repositories/setting_repository.go index 3e4cf08..a6914de 100644 --- a/server/repositories/setting_repository.go +++ b/server/repositories/setting_repository.go @@ -3,6 +3,7 @@ package repositories import ( "database/sql" "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -10,7 +11,7 @@ import ( // GetSettings 获取所有系统配置 func GetSettings() ([]models.Setting, error) { - query := "SELECT id, key_name, value, description, created_at, updated_at FROM settings ORDER BY key_name" + 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) if err != nil { log.Printf("Error querying settings: %v", err) @@ -28,6 +29,7 @@ func GetSettings() ([]models.Setting, error) { &setting.Description, &setting.CreatedAt, &setting.UpdatedAt, + &setting.DeletedAt, ); err != nil { log.Printf("Error scanning setting: %v", err) continue @@ -40,7 +42,7 @@ func GetSettings() ([]models.Setting, error) { // GetSettingByKey 根据键名获取系统配置 func GetSettingByKey(keyName string) (*models.Setting, error) { - query := "SELECT id, key_name, value, description, created_at, updated_at FROM settings WHERE key_name = ?" + 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 @@ -51,6 +53,7 @@ func GetSettingByKey(keyName string) (*models.Setting, error) { &setting.Description, &setting.CreatedAt, &setting.UpdatedAt, + &setting.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -64,14 +67,16 @@ func GetSettingByKey(keyName string) (*models.Setting, error) { // UpdateSetting 更新系统配置 func UpdateSetting(setting *models.Setting) error { + now := time.Now().Unix() query := ` - UPDATE settings SET value = ?, description = ?, updated_at = NOW() - WHERE key_name = ? + 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, ) if err != nil { @@ -84,15 +89,18 @@ func UpdateSetting(setting *models.Setting) error { // CreateSetting 创建系统配置 func CreateSetting(setting *models.Setting) error { + now := time.Now().Unix() query := ` - INSERT INTO settings (key_name, value, description, created_at, updated_at) - VALUES (?, ?, ?, NOW(), NOW()) + 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, ) if err != nil { log.Printf("Error creating setting: %v", err) @@ -106,14 +114,17 @@ func CreateSetting(setting *models.Setting) error { return err } setting.ID = uint(id) + setting.CreatedAt = now + setting.UpdatedAt = now return nil } -// DeleteSetting 删除系统配置 +// DeleteSetting 删除系统配置 (Soft Delete) func DeleteSetting(keyName string) error { - query := "DELETE FROM settings WHERE key_name = ?" - _, err := config.DB.Exec(query, keyName) + now := time.Now().Unix() + query := "UPDATE settings SET deleted_at = ? WHERE key_name = ?" + _, err := config.DB.Exec(query, now, keyName) if err != nil { log.Printf("Error deleting setting: %v", err) return err @@ -129,8 +140,8 @@ func BuildSettingResponse(setting *models.Setting) *models.SettingResponse { KeyName: setting.KeyName, Value: setting.Value, Description: setting.Description, - CreatedAt: setting.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: setting.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: time.Unix(setting.CreatedAt, 0).Format("2006-01-02 15:04:05"), + UpdatedAt: time.Unix(setting.UpdatedAt, 0).Format("2006-01-02 15:04:05"), } } diff --git a/server/repositories/snippet_repository.go b/server/repositories/snippet_repository.go index 6dfc7f5..7012e80 100644 --- a/server/repositories/snippet_repository.go +++ b/server/repositories/snippet_repository.go @@ -3,6 +3,7 @@ package repositories import ( "database/sql" "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -10,7 +11,7 @@ import ( // GetSnippets 获取所有代码片段 func GetSnippets() ([]models.Snippet, error) { - query := "SELECT id, title, code, type, description, view_count, created_at, updated_at FROM snippets ORDER BY created_at DESC" + 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) if err != nil { log.Printf("Error querying snippets: %v", err) @@ -30,6 +31,7 @@ func GetSnippets() ([]models.Snippet, error) { &snippet.ViewCount, &snippet.CreatedAt, &snippet.UpdatedAt, + &snippet.DeletedAt, ); err != nil { log.Printf("Error scanning snippet: %v", err) continue @@ -42,7 +44,7 @@ func GetSnippets() ([]models.Snippet, error) { // GetSnippetByID 根据ID获取代码片段 func GetSnippetByID(id string) (*models.Snippet, error) { - query := "SELECT id, title, code, type, description, view_count, created_at, updated_at FROM snippets WHERE id = ?" + 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 @@ -55,6 +57,7 @@ func GetSnippetByID(id string) (*models.Snippet, error) { &snippet.ViewCount, &snippet.CreatedAt, &snippet.UpdatedAt, + &snippet.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -93,9 +96,10 @@ 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) - VALUES (?, ?, ?, ?, ?, 0, NOW(), NOW()) + INSERT INTO snippets (id, title, code, type, description, view_count, created_at, updated_at, deleted_at) + VALUES (?, ?, ?, ?, ?, 0, ?, ?, 0) ` _, err := config.DB.Exec( query, @@ -104,6 +108,8 @@ func CreateSnippet(snippet *models.Snippet) error { snippet.Code, snippet.Type, snippet.Description, + now, + now, ) if err != nil { log.Printf("Error creating snippet: %v", err) @@ -115,9 +121,10 @@ func CreateSnippet(snippet *models.Snippet) error { // UpdateSnippet 更新代码片段 func UpdateSnippet(snippet *models.Snippet) error { + now := time.Now().Unix() query := ` - UPDATE snippets SET title = ?, code = ?, type = ?, description = ?, updated_at = NOW() - WHERE id = ? + UPDATE snippets SET title = ?, code = ?, type = ?, description = ?, updated_at = ? + WHERE id = ? AND deleted_at = 0 ` _, err := config.DB.Exec( query, @@ -125,6 +132,7 @@ func UpdateSnippet(snippet *models.Snippet) error { snippet.Code, snippet.Type, snippet.Description, + now, snippet.ID, ) if err != nil { @@ -135,10 +143,11 @@ func UpdateSnippet(snippet *models.Snippet) error { return nil } -// DeleteSnippet 删除代码片段 +// DeleteSnippet 删除代码片段 (Soft Delete) func DeleteSnippet(id string) error { - query := "DELETE FROM snippets WHERE id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := "UPDATE snippets SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { log.Printf("Error deleting snippet: %v", err) return err @@ -150,7 +159,7 @@ func DeleteSnippet(id string) error { // GetSnippetCount 获取代码片段总数 func GetSnippetCount() (int, error) { var count int - query := "SELECT COUNT(*) FROM snippets" + query := "SELECT COUNT(*) FROM snippets WHERE deleted_at = 0" row := config.DB.QueryRow(query) err := row.Scan(&count) diff --git a/server/repositories/tag_repository.go b/server/repositories/tag_repository.go index bbf1663..9c08751 100644 --- a/server/repositories/tag_repository.go +++ b/server/repositories/tag_repository.go @@ -3,6 +3,7 @@ package repositories import ( "database/sql" "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -10,7 +11,7 @@ import ( // GetTags 获取所有标签 func GetTags() ([]models.Tag, error) { - query := "SELECT id, name, slug, created_at, updated_at FROM tags ORDER BY name ASC" + 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) if err != nil { log.Printf("Error querying tags: %v", err) @@ -27,6 +28,7 @@ func GetTags() ([]models.Tag, error) { &tag.Slug, &tag.CreatedAt, &tag.UpdatedAt, + &tag.DeletedAt, ); err != nil { log.Printf("Error scanning tag: %v", err) continue @@ -39,7 +41,7 @@ func GetTags() ([]models.Tag, error) { // GetTagByID 根据ID获取标签 func GetTagByID(id uint) (*models.Tag, error) { - query := "SELECT id, name, slug, created_at, updated_at FROM tags WHERE id = ?" + 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 @@ -49,6 +51,7 @@ func GetTagByID(id uint) (*models.Tag, error) { &tag.Slug, &tag.CreatedAt, &tag.UpdatedAt, + &tag.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -62,7 +65,7 @@ func GetTagByID(id uint) (*models.Tag, error) { // GetTagBySlug 根据Slug获取标签 func GetTagBySlug(slug string) (*models.Tag, error) { - query := "SELECT id, name, slug, created_at, updated_at FROM tags WHERE slug = ?" + 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 @@ -72,6 +75,7 @@ func GetTagBySlug(slug string) (*models.Tag, error) { &tag.Slug, &tag.CreatedAt, &tag.UpdatedAt, + &tag.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -85,14 +89,17 @@ func GetTagBySlug(slug string) (*models.Tag, error) { // CreateTag 创建标签 func CreateTag(tag *models.Tag) error { + now := time.Now().Unix() query := ` - INSERT INTO tags (name, slug, created_at, updated_at) - VALUES (?, ?, NOW(), NOW()) + 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, ) if err != nil { log.Printf("Error creating tag: %v", err) @@ -106,20 +113,24 @@ func CreateTag(tag *models.Tag) error { 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 = NOW() - WHERE id = ? + 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, ) if err != nil { @@ -130,21 +141,32 @@ func UpdateTag(tag *models.Tag) error { return nil } -// DeleteTag 删除标签 +// DeleteTag 删除标签 (Soft Delete) func DeleteTag(id uint) error { - // 先删除关联的文章标签关系 - query := "DELETE FROM post_tags WHERE tag_id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + // 软删除标签 + query := "UPDATE tags SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { - log.Printf("Error deleting post-tag relationships: %v", err) + log.Printf("Error deleting tag: %v", err) return err } - // 再删除标签 - query = "DELETE FROM tags WHERE id = ?" - _, err = config.DB.Exec(query, id) + // 注意: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) if err != nil { - log.Printf("Error deleting tag: %v", err) + log.Printf("Error deleting post-tag relationships: %v", err) + // Continue even if relation delete fails? No, return error. return err } @@ -154,10 +176,10 @@ func DeleteTag(id uint) error { // GetTagsByPostID 根据文章ID获取标签 func GetTagsByPostID(postID string) ([]models.Tag, error) { query := ` - SELECT t.id, t.name, t.slug, t.created_at, t.updated_at + 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 = ? + WHERE pt.post_id = ? AND t.deleted_at = 0 ORDER BY t.name ASC ` rows, err := config.DB.Query(query, postID) @@ -176,6 +198,7 @@ func GetTagsByPostID(postID string) ([]models.Tag, error) { &tag.Slug, &tag.CreatedAt, &tag.UpdatedAt, + &tag.DeletedAt, ); err != nil { log.Printf("Error scanning tag: %v", err) continue @@ -188,11 +211,12 @@ func GetTagsByPostID(postID string) ([]models.Tag, error) { // AddTagToPost 为文章添加标签 func AddTagToPost(postID string, tagID uint) error { + now := time.Now().Unix() query := ` INSERT IGNORE INTO post_tags (post_id, tag_id, created_at) - VALUES (?, ?, NOW()) + VALUES (?, ?, ?) ` - _, err := config.DB.Exec(query, postID, tagID) + _, err := config.DB.Exec(query, postID, tagID, now) if err != nil { log.Printf("Error adding tag to post: %v", err) return err @@ -216,11 +240,11 @@ func RemoveTagFromPost(postID string, tagID uint) error { // GetPostsByTagID 根据标签ID获取文章 func GetPostsByTagID(tagID uint) ([]models.Post, error) { query := ` - SELECT p.id, p.title, p.category, p.date, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at + 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 - ORDER BY p.date DESC + 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) if err != nil { @@ -236,13 +260,13 @@ func GetPostsByTagID(tagID uint) ([]models.Post, error) { &post.ID, &post.Title, &post.Category, - &post.Date, &post.Excerpt, &post.Content, &post.ReadCount, &post.IsPublished, &post.CreatedAt, &post.UpdatedAt, + &post.DeletedAt, ); err != nil { log.Printf("Error scanning post: %v", err) continue diff --git a/server/repositories/user_repository.go b/server/repositories/user_repository.go index 7824461..dfed988 100644 --- a/server/repositories/user_repository.go +++ b/server/repositories/user_repository.go @@ -3,6 +3,7 @@ package repositories import ( "database/sql" "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -11,10 +12,10 @@ import ( // 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 + 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 = ? + WHERE u.username = ? AND u.deleted_at = 0 ` row := config.DB.QueryRow(query, username) @@ -32,6 +33,7 @@ func GetUserByUsername(username string) (*models.User, error) { &user.IsActive, &user.CreatedAt, &user.UpdatedAt, + &user.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -53,10 +55,10 @@ func GetUserByUsername(username string) (*models.User, error) { // 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 + 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 = ? + WHERE u.id = ? AND u.deleted_at = 0 ` row := config.DB.QueryRow(query, id) @@ -74,6 +76,7 @@ func GetUserByID(id uint) (*models.User, error) { &user.IsActive, &user.CreatedAt, &user.UpdatedAt, + &user.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -95,9 +98,10 @@ func GetUserByID(id uint) (*models.User, error) { // GetUsers 获取所有用户 func GetUsers() ([]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 + 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 ` rows, err := config.DB.Query(query) @@ -123,6 +127,7 @@ func GetUsers() ([]models.User, error) { &user.IsActive, &user.CreatedAt, &user.UpdatedAt, + &user.DeletedAt, ); err != nil { log.Printf("Error scanning user: %v", err) continue @@ -151,9 +156,10 @@ 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) - VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW()) + INSERT INTO users (username, email, password_hash, role_id, role, is_active, created_at, updated_at, deleted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0) ` var roleID interface{} @@ -171,6 +177,8 @@ func CreateUser(user *models.User) error { roleID, user.Role, // Fallback legacy column user.IsActive, + now, + now, ) if err != nil { log.Printf("Error creating user: %v", err) @@ -184,6 +192,8 @@ func CreateUser(user *models.User) error { return err } user.ID = uint(id) + user.CreatedAt = now + user.UpdatedAt = now return nil } @@ -198,9 +208,10 @@ func UpdateUser(user *models.User) error { } } + now := time.Now().Unix() query := ` - UPDATE users SET username = ?, email = ?, role_id = ?, role = ?, is_active = ?, updated_at = NOW() - WHERE id = ? + UPDATE users SET username = ?, email = ?, role_id = ?, role = ?, is_active = ?, updated_at = ? + WHERE id = ? AND deleted_at = 0 ` var roleID interface{} @@ -217,6 +228,7 @@ func UpdateUser(user *models.User) error { roleID, user.Role, user.IsActive, + now, user.ID, ) if err != nil { @@ -229,11 +241,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 = NOW() - WHERE id = ? + UPDATE users SET password_hash = ?, updated_at = ? + WHERE id = ? AND deleted_at = 0 ` - _, err := config.DB.Exec(query, passwordHash, id) + _, err := config.DB.Exec(query, passwordHash, now, id) if err != nil { log.Printf("Error updating user password: %v", err) return err @@ -242,10 +255,11 @@ func UpdateUserPassword(id uint, passwordHash string) error { return nil } -// DeleteUser 删除用户 +// DeleteUser 删除用户 (Soft Delete) func DeleteUser(id uint) error { - query := "DELETE FROM users WHERE id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := "UPDATE users SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { log.Printf("Error deleting user: %v", err) return err @@ -257,7 +271,7 @@ func DeleteUser(id uint) error { // GetUserCount 获取用户总数 func GetUserCount() (int, error) { var count int - query := "SELECT COUNT(*) FROM users" + query := "SELECT COUNT(*) FROM users WHERE deleted_at = 0" row := config.DB.QueryRow(query) err := row.Scan(&count) @@ -278,8 +292,8 @@ func BuildUserResponse(user *models.User) *models.UserResponse { RoleID: user.RoleID, Role: user.Role, IsActive: user.IsActive, - CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: time.Unix(user.CreatedAt, 0).Format("2006-01-02 15:04:05"), + UpdatedAt: time.Unix(user.UpdatedAt, 0).Format("2006-01-02 15:04:05"), } } diff --git a/server/repositories/work_repository.go b/server/repositories/work_repository.go index ae7b098..a83c036 100644 --- a/server/repositories/work_repository.go +++ b/server/repositories/work_repository.go @@ -3,6 +3,7 @@ package repositories import ( "database/sql" "log" + "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" @@ -10,7 +11,7 @@ import ( // GetWorks 获取所有作品 func GetWorks() ([]models.Work, error) { - query := "SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at FROM works" + 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) if err != nil { log.Printf("Error querying works: %v", err) @@ -31,6 +32,7 @@ func GetWorks() ([]models.Work, error) { &work.IsFeatured, &work.CreatedAt, &work.UpdatedAt, + &work.DeletedAt, ); err != nil { log.Printf("Error scanning work: %v", err) continue @@ -43,7 +45,7 @@ func GetWorks() ([]models.Work, error) { // GetWorkByID 根据ID获取作品 func GetWorkByID(id string) (*models.Work, error) { - query := "SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at FROM works WHERE id = ?" + 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 @@ -57,6 +59,7 @@ func GetWorkByID(id string) (*models.Work, error) { &work.IsFeatured, &work.CreatedAt, &work.UpdatedAt, + &work.DeletedAt, ); err != nil { if err == sql.ErrNoRows { return nil, nil @@ -70,7 +73,8 @@ func GetWorkByID(id string) (*models.Work, error) { // GetWorkTechStack 获取作品的技术栈 func GetWorkTechStack(workID string) ([]models.WorkTechStack, error) { - query := "SELECT id, work_id, category, item, created_at FROM work_tech_stack WHERE work_id = ?" + // 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) if err != nil { log.Printf("Error querying work tech stack: %v", err) @@ -87,6 +91,7 @@ func GetWorkTechStack(workID string) ([]models.WorkTechStack, error) { &techStack.Category, &techStack.Item, &techStack.CreatedAt, + &techStack.DeletedAt, ); err != nil { log.Printf("Error scanning work tech stack: %v", err) continue @@ -99,7 +104,8 @@ func GetWorkTechStack(workID string) ([]models.WorkTechStack, error) { // GetWorkGallery 获取作品的图库 func GetWorkGallery(workID string) ([]models.WorkGallery, error) { - query := "SELECT id, work_id, image_url, sort_order, description, created_at FROM work_gallery WHERE work_id = ? ORDER BY sort_order" + // 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) if err != nil { log.Printf("Error querying work gallery: %v", err) @@ -117,6 +123,7 @@ func GetWorkGallery(workID string) ([]models.WorkGallery, error) { &gallery.SortOrder, &gallery.Description, &gallery.CreatedAt, + &gallery.DeletedAt, ); err != nil { log.Printf("Error scanning work gallery: %v", err) continue @@ -188,7 +195,7 @@ func BuildWorkResponse(work *models.Work) (*models.WorkResponse, error) { // GetNextWorkID 获取下一个作品ID(简单实现,实际可能需要更复杂的逻辑) func GetNextWorkID(currentID string) (string, error) { // 获取所有作品ID - query := "SELECT id FROM works" + query := "SELECT id FROM works WHERE deleted_at = 0" rows, err := config.DB.Query(query) if err != nil { return "", err @@ -227,9 +234,10 @@ 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) - VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + 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, @@ -240,6 +248,8 @@ func CreateWork(work *models.Work) error { work.HeroImg, work.Description, work.IsFeatured, + now, + now, ) if err != nil { log.Printf("Error creating work: %v", err) @@ -251,9 +261,10 @@ func CreateWork(work *models.Work) error { // 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 = NOW() - WHERE id = ? + UPDATE works SET title = ?, category = ?, year = ?, hero_img = ?, description = ?, is_featured = ?, updated_at = ? + WHERE id = ? AND deleted_at = 0 ` _, err := config.DB.Exec( query, @@ -263,6 +274,7 @@ func UpdateWork(work *models.Work) error { work.HeroImg, work.Description, work.IsFeatured, + now, work.ID, ) if err != nil { @@ -273,10 +285,11 @@ func UpdateWork(work *models.Work) error { return nil } -// DeleteWork 删除作品 +// DeleteWork 删除作品 (Soft Delete) func DeleteWork(id string) error { - query := "DELETE FROM works WHERE id = ?" - _, err := config.DB.Exec(query, id) + now := time.Now().Unix() + query := "UPDATE works SET deleted_at = ? WHERE id = ?" + _, err := config.DB.Exec(query, now, id) if err != nil { log.Printf("Error deleting work: %v", err) return err @@ -288,7 +301,7 @@ func DeleteWork(id string) error { // GetWorkCount 获取作品总数 func GetWorkCount() (int, error) { var count int - query := "SELECT COUNT(*) FROM works" + query := "SELECT COUNT(*) FROM works WHERE deleted_at = 0" row := config.DB.QueryRow(query) err := row.Scan(&count)