From f39e37e534b4516c2599956980e28771f222b7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Thu, 15 Jan 2026 15:37:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/About.vue | 84 ++++-- client/src/services/api.ts | 37 +++ server/about_page.sql | 16 ++ server/handlers/about.go | 92 +++++++ server/main.go | 9 + server/models/about.go | 28 ++ server/nl_blog.sql | 352 ++++++++++++++---------- server/repositories/about_repository.go | 232 ++++++++++++++++ 8 files changed, 681 insertions(+), 169 deletions(-) create mode 100644 server/about_page.sql create mode 100644 server/handlers/about.go create mode 100644 server/models/about.go create mode 100644 server/repositories/about_repository.go diff --git a/client/src/pages/About.vue b/client/src/pages/About.vue index 797bd2b..60357ec 100644 --- a/client/src/pages/About.vue +++ b/client/src/pages/About.vue @@ -70,6 +70,18 @@ {{ tech }} + +

+ 经历 +

+
+
+
+
{{ exp.year }}
+
{{ exp.role }}
+
{{ exp.company }}
+
+
@@ -78,10 +90,17 @@ \ No newline at end of file diff --git a/client/src/services/api.ts b/client/src/services/api.ts index 60df565..cfcb0b4 100644 --- a/client/src/services/api.ts +++ b/client/src/services/api.ts @@ -854,3 +854,40 @@ export const deleteTag = async (id: number): Promise => { throw error } } + +// 关于页面相关类型 +export interface Experience { + year: string + role: string + company: string +} + +export interface AboutProfile { + id: number + name: string + avatar: string + location: string + bio: string + email: string + wechat: string + techStack: string[] + experiences: Experience[] + isPrimary: boolean + createdAt: string + updatedAt: string +} + +// 关于页面API +export const fetchAboutProfile = async (): Promise => { + try { + const response = await fetch(`${API_BASE}/about`) + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error || '获取个人资料失败') + } + return await response.json() + } catch (error) { + console.error('Fetch about profile error:', error) + throw error + } +} diff --git a/server/about_page.sql b/server/about_page.sql new file mode 100644 index 0000000..5a9f559 --- /dev/null +++ b/server/about_page.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS `about_profiles` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `avatar` VARCHAR(255) DEFAULT '', + `location` VARCHAR(255) DEFAULT '', + `bio` TEXT, + `email` VARCHAR(255) DEFAULT '', + `wechat` VARCHAR(255) DEFAULT '', + `tech_stack` TEXT COMMENT 'JSON string or comma separated list', + `is_primary` BOOLEAN DEFAULT FALSE, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT INTO `about_profiles` (`name`, `avatar`, `location`, `bio`, `email`, `wechat`, `tech_stack`, `is_primary`) VALUES +('年糕崽崽', '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"]', TRUE); diff --git a/server/handlers/about.go b/server/handlers/about.go new file mode 100644 index 0000000..939ba21 --- /dev/null +++ b/server/handlers/about.go @@ -0,0 +1,92 @@ +package handlers + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/niangaodev/art-code/models" + "github.com/niangaodev/art-code/repositories" +) + +// GetAboutProfile 获取公开的关于页面信息(主页资料) +func GetAboutProfile(c *gin.Context) { + profile, err := repositories.GetPrimaryAboutProfile() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get about profile"}) + return + } + if profile == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "About profile not found"}) + return + } + + c.JSON(http.StatusOK, profile) +} + +// AdminGetAboutProfiles 管理员获取所有资料列表 +func AdminGetAboutProfiles(c *gin.Context) { + profiles, err := repositories.GetAllAboutProfiles() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get profiles"}) + return + } + c.JSON(http.StatusOK, profiles) +} + +// AdminCreateAboutProfile 创建资料 +func AdminCreateAboutProfile(c *gin.Context) { + var profile models.AboutProfile + if err := c.ShouldBindJSON(&profile); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + return + } + + if err := repositories.CreateAboutProfile(&profile); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create profile"}) + return + } + + c.JSON(http.StatusOK, profile) +} + +// AdminUpdateAboutProfile 更新资料 +func AdminUpdateAboutProfile(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + var profile models.AboutProfile + if err := c.ShouldBindJSON(&profile); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + return + } + profile.ID = uint(id) + + if err := repositories.UpdateAboutProfile(&profile); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update profile"}) + return + } + + c.JSON(http.StatusOK, profile) +} + +// AdminDeleteAboutProfile 删除资料 +func AdminDeleteAboutProfile(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + if err := repositories.DeleteAboutProfile(uint(id)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete profile"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Profile deleted successfully"}) +} diff --git a/server/main.go b/server/main.go index bdfc33d..78984ed 100644 --- a/server/main.go +++ b/server/main.go @@ -74,6 +74,9 @@ func main() { // 代码执行路由 api.POST("/run", handlers.RunCode) + + // 关于页面路由 + api.GET("/about", handlers.GetAboutProfile) } // 管理员API路由组 @@ -143,6 +146,12 @@ func main() { authAdmin.POST("/tags", middleware.PermissionMiddleware("tags", "create"), handlers.AdminCreateTag) authAdmin.PUT("/tags/:id", middleware.PermissionMiddleware("tags", "update"), handlers.AdminUpdateTag) authAdmin.DELETE("/tags/:id", middleware.PermissionMiddleware("tags", "delete"), handlers.AdminDeleteTag) + + // 关于页面管理 (复用 settings 权限) + authAdmin.GET("/about", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetAboutProfiles) + authAdmin.POST("/about", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateAboutProfile) + authAdmin.PUT("/about/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateAboutProfile) + authAdmin.DELETE("/about/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteAboutProfile) } } diff --git a/server/models/about.go b/server/models/about.go new file mode 100644 index 0000000..48b5eab --- /dev/null +++ b/server/models/about.go @@ -0,0 +1,28 @@ +package models + +import ( + "time" +) + +type Experience struct { + Year string `json:"year"` + Role string `json:"role"` + Company string `json:"company"` +} + +type AboutProfile struct { + ID uint `json:"id"` + Name string `json:"name"` + Avatar string `json:"avatar"` + Location string `json:"location"` + Bio string `json:"bio"` + Email string `json:"email"` + Wechat string `json:"wechat"` + TechStack string `json:"-"` // Stored as string in DB + TechList []string `json:"techStack"` // Exposed as array in JSON + ExperiencesStr string `json:"-"` // Stored as string in DB + ExperienceList []Experience `json:"experiences"` // Exposed as array in JSON + IsPrimary bool `json:"isPrimary"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/server/nl_blog.sql b/server/nl_blog.sql index 0eeb60d..c82e522 100644 --- a/server/nl_blog.sql +++ b/server/nl_blog.sql @@ -11,12 +11,37 @@ Target Server Version : 80407 (8.4.7) File Encoding : 65001 - Date: 14/01/2026 13:01:23 + Date: 15/01/2026 15:27:24 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; +-- ---------------------------- +-- Table structure for about_profiles +-- ---------------------------- +DROP TABLE IF EXISTS `about_profiles`; +CREATE TABLE `about_profiles` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '', + `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '', + `bio` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL, + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '', + `wechat` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '', + `tech_stack` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'JSON string or comma separated 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, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 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\"]', 1, '2026-01-15 15:22:08', '2026-01-15 15:22:08'); +INSERT INTO `about_profiles` VALUES (2, '年糕崽崽', '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\"]', 1, '2026-01-15 15:24:18', '2026-01-15 15:24:18'); + -- ---------------------------- -- Table structure for access_logs -- ---------------------------- @@ -34,12 +59,100 @@ CREATE TABLE `access_logs` ( 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 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访问日志表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of access_logs -- ---------------------------- +-- ---------------------------- +-- Table structure for operation_logs +-- ---------------------------- +DROP TABLE IF EXISTS `operation_logs`; +CREATE TABLE `operation_logs` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` bigint UNSIGNED NOT NULL COMMENT '操作用户ID', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作用户名', + `ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作IP地址', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作路径', + `method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'HTTP方法', + `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 '操作时间', + 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 = 16 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'); + +-- ---------------------------- +-- Table structure for permissions +-- ---------------------------- +DROP TABLE IF EXISTS `permissions`; +CREATE TABLE `permissions` ( + `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `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 '更新时间', + 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; + +-- ---------------------------- +-- 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'); + -- ---------------------------- -- Table structure for post_tags -- ---------------------------- @@ -53,7 +166,7 @@ CREATE TABLE `post_tags` ( 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, CONSTRAINT `post_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章标签关联表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章标签关联表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of post_tags @@ -79,15 +192,96 @@ CREATE TABLE `posts` ( 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 '标题和内容全文索引,用于搜索' -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '博客文章表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '博客文章表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of posts -- ---------------------------- -INSERT INTO `posts` VALUES ('refactor', '重构的艺术:如何优雅地处理遗留代码', '工程化', '2026-01-12', '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '

什么是代码重构?

代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...

', 0, 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -INSERT INTO `posts` VALUES ('shader', '着色器魔法:从零开始写一个噪声生成器', '图形渲染', '2025-12-08', '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', '

WebGL着色器基础

WebGL着色器是运行在GPU上的小程序,用于处理图形渲染...

', 0, 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14'); +INSERT INTO `posts` VALUES ('refactor', '重构的艺术:如何优雅地处理遗留代码', '工程化', '2026-01-12', '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '

什么是代码重构?

代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...

', 1, 1, '2026-01-13 16:10:14', '2026-01-15 15:01:35'); +INSERT INTO `posts` VALUES ('shader', '着色器魔法:从零开始写一个噪声生成器', '图形渲染', '2025-12-08', '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', '

GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。

\r\n

什么是柏林噪声?

\r\n

柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。

\r\n

Three.js 中的实现

\r\n

在 Three.js 中,我们可以通过 ShaderMaterial 直接编写 GLSL 代码。

\r\n
// 简单的顶点着色器\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 15:03:42'); INSERT INTO `posts` VALUES ('ux', '用户体验设计:从认知心理学到交互实践', '设计思维', '2025-11-20', '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '

认知心理学在UX设计中的应用

了解用户的认知过程是设计良好用户体验的基础...

', 0, 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14'); +-- ---------------------------- +-- Table structure for role_permissions +-- ---------------------------- +DROP TABLE IF EXISTS `role_permissions`; +CREATE TABLE `role_permissions` ( + `role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID', + `permission_id` bigint UNSIGNED NOT NULL COMMENT '权限ID', + PRIMARY KEY (`role_id`, `permission_id`) USING BTREE, + INDEX `role_permissions_ibfk_2`(`permission_id` ASC) USING BTREE, + CONSTRAINT `role_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, + CONSTRAINT `role_permissions_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色权限关联表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of role_permissions +-- ---------------------------- +INSERT INTO `role_permissions` VALUES (1, 1); +INSERT INTO `role_permissions` VALUES (1, 2); +INSERT INTO `role_permissions` VALUES (3, 2); +INSERT INTO `role_permissions` VALUES (1, 3); +INSERT INTO `role_permissions` VALUES (1, 4); +INSERT INTO `role_permissions` VALUES (1, 5); +INSERT INTO `role_permissions` VALUES (1, 6); +INSERT INTO `role_permissions` VALUES (3, 6); +INSERT INTO `role_permissions` VALUES (1, 7); +INSERT INTO `role_permissions` VALUES (1, 8); +INSERT INTO `role_permissions` VALUES (1, 9); +INSERT INTO `role_permissions` VALUES (2, 9); +INSERT INTO `role_permissions` VALUES (1, 10); +INSERT INTO `role_permissions` VALUES (2, 10); +INSERT INTO `role_permissions` VALUES (3, 10); +INSERT INTO `role_permissions` VALUES (1, 11); +INSERT INTO `role_permissions` VALUES (2, 11); +INSERT INTO `role_permissions` VALUES (1, 12); +INSERT INTO `role_permissions` VALUES (2, 12); +INSERT INTO `role_permissions` VALUES (1, 13); +INSERT INTO `role_permissions` VALUES (1, 14); +INSERT INTO `role_permissions` VALUES (3, 14); +INSERT INTO `role_permissions` VALUES (1, 15); +INSERT INTO `role_permissions` VALUES (1, 16); +INSERT INTO `role_permissions` VALUES (1, 17); +INSERT INTO `role_permissions` VALUES (1, 18); +INSERT INTO `role_permissions` VALUES (3, 18); +INSERT INTO `role_permissions` VALUES (1, 19); +INSERT INTO `role_permissions` VALUES (1, 20); +INSERT INTO `role_permissions` VALUES (1, 21); +INSERT INTO `role_permissions` VALUES (1, 22); +INSERT INTO `role_permissions` VALUES (3, 22); +INSERT INTO `role_permissions` VALUES (1, 23); +INSERT INTO `role_permissions` VALUES (1, 24); +INSERT INTO `role_permissions` VALUES (1, 25); +INSERT INTO `role_permissions` VALUES (1, 26); +INSERT INTO `role_permissions` VALUES (3, 26); +INSERT INTO `role_permissions` VALUES (1, 27); +INSERT INTO `role_permissions` VALUES (1, 28); +INSERT INTO `role_permissions` VALUES (1, 29); +INSERT INTO `role_permissions` VALUES (3, 29); +INSERT INTO `role_permissions` VALUES (1, 30); +INSERT INTO `role_permissions` VALUES (3, 30); + +-- ---------------------------- +-- Table structure for roles +-- ---------------------------- +DROP TABLE IF EXISTS `roles`; +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 '更新时间', + 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; + +-- ---------------------------- +-- 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'); + -- ---------------------------- -- Table structure for settings -- ---------------------------- @@ -102,7 +296,7 @@ CREATE TABLE `settings` ( PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `key_name`(`key_name` ASC) USING BTREE, INDEX `idx_key_name`(`key_name` ASC) USING BTREE COMMENT '按键名查询索引' -) 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 settings @@ -131,7 +325,7 @@ CREATE TABLE `snippets` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_type`(`type` ASC) USING BTREE COMMENT '按代码类型查询索引', INDEX `idx_view_count`(`view_count` ASC) USING BTREE COMMENT '按查看次数查询索引' -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码片段表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码片段表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of snippets @@ -152,113 +346,12 @@ CREATE TABLE `tags` ( 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 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of tags -- ---------------------------- --- ---------------------------- --- Table structure for roles --- ---------------------------- -DROP TABLE IF EXISTS `roles`; -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 COMMENT '角色描述', - `created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `name`(`name` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of roles --- ---------------------------- -INSERT INTO `roles` (`name`, `description`) VALUES ('admin', '系统管理员'); -INSERT INTO `roles` (`name`, `description`) VALUES ('editor', '内容编辑'); -INSERT INTO `roles` (`name`, `description`) VALUES ('viewer', '普通访客'); - --- ---------------------------- --- Table structure for permissions --- ---------------------------- -DROP TABLE IF EXISTS `permissions`; -CREATE TABLE `permissions` ( - `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `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 '更新时间', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `unique_resource_action`(`resource`, `action`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of permissions --- ---------------------------- --- User permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create User', 'users', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read User', 'users', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update User', 'users', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete User', 'users', 'delete'); --- Role permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create Role', 'roles', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Role', 'roles', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update Role', 'roles', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete Role', 'roles', 'delete'); --- Post permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create Post', 'posts', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Post', 'posts', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update Post', 'posts', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete Post', 'posts', 'delete'); --- Work permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create Work', 'works', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Work', 'works', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update Work', 'works', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete Work', 'works', 'delete'); --- Snippet permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create Snippet', 'snippets', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Snippet', 'snippets', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update Snippet', 'snippets', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete Snippet', 'snippets', 'delete'); --- Setting permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create Setting', 'settings', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Setting', 'settings', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update Setting', 'settings', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete Setting', 'settings', 'delete'); --- Tag permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Create Tag', 'tags', 'create'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Tag', 'tags', 'read'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Update Tag', 'tags', 'update'); -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Delete Tag', 'tags', 'delete'); --- Log permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Operation Log', 'operation_logs', 'read'); --- Dashboard permissions -INSERT INTO `permissions` (`name`, `resource`, `action`) VALUES ('Read Dashboard', 'dashboard', 'read'); - --- ---------------------------- --- Table structure for role_permissions --- ---------------------------- -DROP TABLE IF EXISTS `role_permissions`; -CREATE TABLE `role_permissions` ( - `role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID', - `permission_id` bigint UNSIGNED NOT NULL COMMENT '权限ID', - PRIMARY KEY (`role_id`, `permission_id`) USING BTREE, - CONSTRAINT `role_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT, - CONSTRAINT `role_permissions_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色权限关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of role_permissions --- ---------------------------- --- Admin has all permissions (assuming ids 1-12) -INSERT INTO `role_permissions` SELECT 1, id FROM permissions; --- Editor has Post permissions (ids 9-12) -INSERT INTO `role_permissions` SELECT 2, id FROM permissions WHERE resource = 'posts'; --- Viewer has read permissions (assuming read action) -INSERT INTO `role_permissions` SELECT 3, id FROM permissions WHERE action = 'read'; - -- ---------------------------- -- Table structure for users -- ---------------------------- @@ -268,7 +361,7 @@ CREATE TABLE `users` ( `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱地址', `password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码哈希值', - `role_id` bigint UNSIGNED NULL COMMENT '角色ID', + `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 '创建时间', @@ -279,34 +372,15 @@ CREATE TABLE `users` ( INDEX `idx_username`(`username` ASC) USING BTREE COMMENT '按用户名查询索引', INDEX `idx_email`(`email` ASC) USING BTREE COMMENT '按邮箱查询索引', INDEX `idx_role`(`role` ASC) USING BTREE COMMENT '按角色查询索引', + INDEX `users_ibfk_1`(`role_id` ASC) USING BTREE, CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for operation_logs --- ---------------------------- -DROP TABLE IF EXISTS `operation_logs`; -CREATE TABLE `operation_logs` ( - `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `user_id` bigint UNSIGNED NOT NULL COMMENT '操作用户ID', - `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作用户名', - `ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作IP地址', - `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作路径', - `method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'HTTP方法', - `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 '操作时间', - 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 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of users -- ---------------------------- -INSERT INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `created_at`, `updated_at`) VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 1, 'admin', 1, '2026-01-13 16:10:14', '2026-01-14 12:57:55'); -INSERT INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `created_at`, `updated_at`) VALUES (2, 'editor', 'editor@example.com', '$2a$10$J9q3NfJ2X8H7Q5z5Q7z5Q7z5Q7z5Q7z5Q7z5Q7z5Q7z5Q7z5Q', 2, 'editor', 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14'); +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$J9q3NfJ2X8H7Q5z5Q7z5Q7z5Q7z5Q7z5Q7z5Q7z5Q7z5Q7z5Q', 2, 'editor', 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14'); -- ---------------------------- -- Table structure for work_gallery @@ -323,7 +397,7 @@ CREATE TABLE `work_gallery` ( INDEX `idx_work_id`(`work_id` ASC) USING BTREE, INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE, CONSTRAINT `work_gallery_ibfk_1` FOREIGN KEY (`work_id`) REFERENCES `works` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of work_gallery @@ -347,7 +421,7 @@ CREATE TABLE `work_tech_stack` ( INDEX `idx_work_id`(`work_id` ASC) USING BTREE, INDEX `idx_category`(`category` ASC) USING BTREE, CONSTRAINT `work_tech_stack_ibfk_1` FOREIGN KEY (`work_id`) REFERENCES `works` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT -) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of work_tech_stack @@ -380,12 +454,12 @@ CREATE TABLE `works` ( INDEX `idx_category`(`category` ASC) USING BTREE COMMENT '按分类查询索引', INDEX `idx_year`(`year` ASC) USING BTREE COMMENT '按年份查询索引', INDEX `idx_is_featured`(`is_featured` ASC) USING BTREE COMMENT '按精选状态查询索引' -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品表' ROW_FORMAT = Dynamic; +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- 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-1611974765270-ca12586343bb?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 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'); -SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file +SET FOREIGN_KEY_CHECKS = 1; diff --git a/server/repositories/about_repository.go b/server/repositories/about_repository.go new file mode 100644 index 0000000..699f40d --- /dev/null +++ b/server/repositories/about_repository.go @@ -0,0 +1,232 @@ +package repositories + +import ( + "database/sql" + "encoding/json" + "log" + + "github.com/niangaodev/art-code/config" + "github.com/niangaodev/art-code/models" +) + +// GetPrimaryAboutProfile 获取主页个人资料 +func GetPrimaryAboutProfile() (*models.AboutProfile, error) { + query := ` + SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at + FROM about_profiles + WHERE is_primary = TRUE + LIMIT 1 + ` + row := config.DB.QueryRow(query) + + var profile models.AboutProfile + if err := row.Scan( + &profile.ID, + &profile.Name, + &profile.Avatar, + &profile.Location, + &profile.Bio, + &profile.Email, + &profile.Wechat, + &profile.TechStack, + &profile.ExperiencesStr, + &profile.IsPrimary, + &profile.CreatedAt, + &profile.UpdatedAt, + ); err != nil { + if err == sql.ErrNoRows { + // If no primary profile, try to get the first one + return GetFirstAboutProfile() + } + log.Printf("Error scanning primary about profile: %v", err) + return nil, err + } + + // Unmarshal JSON + if profile.TechStack != "" { + _ = json.Unmarshal([]byte(profile.TechStack), &profile.TechList) + } else { + profile.TechList = []string{} + } + + if profile.ExperiencesStr != "" { + _ = json.Unmarshal([]byte(profile.ExperiencesStr), &profile.ExperienceList) + } else { + profile.ExperienceList = []models.Experience{} + } + + return &profile, nil +} + +// GetFirstAboutProfile 获取第一个个人资料(备用) +func GetFirstAboutProfile() (*models.AboutProfile, error) { + query := ` + SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at + FROM about_profiles + ORDER BY id ASC + LIMIT 1 + ` + row := config.DB.QueryRow(query) + + var profile models.AboutProfile + if err := row.Scan( + &profile.ID, + &profile.Name, + &profile.Avatar, + &profile.Location, + &profile.Bio, + &profile.Email, + &profile.Wechat, + &profile.TechStack, + &profile.ExperiencesStr, + &profile.IsPrimary, + &profile.CreatedAt, + &profile.UpdatedAt, + ); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + log.Printf("Error scanning first about profile: %v", err) + return nil, err + } + + if profile.TechStack != "" { + _ = json.Unmarshal([]byte(profile.TechStack), &profile.TechList) + } else { + profile.TechList = []string{} + } + + if profile.ExperiencesStr != "" { + _ = json.Unmarshal([]byte(profile.ExperiencesStr), &profile.ExperienceList) + } else { + profile.ExperienceList = []models.Experience{} + } + + return &profile, nil +} + +// 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" + rows, err := config.DB.Query(query) + if err != nil { + log.Printf("Error querying about profiles: %v", err) + return nil, err + } + defer rows.Close() + + var profiles []models.AboutProfile + for rows.Next() { + var p models.AboutProfile + if err := rows.Scan( + &p.ID, + &p.Name, + &p.Avatar, + &p.Location, + &p.Bio, + &p.Email, + &p.Wechat, + &p.TechStack, + &p.ExperiencesStr, + &p.IsPrimary, + &p.CreatedAt, + &p.UpdatedAt, + ); err != nil { + continue + } + if p.TechStack != "" { + _ = json.Unmarshal([]byte(p.TechStack), &p.TechList) + } else { + p.TechList = []string{} + } + if p.ExperiencesStr != "" { + _ = json.Unmarshal([]byte(p.ExperiencesStr), &p.ExperienceList) + } else { + p.ExperienceList = []models.Experience{} + } + profiles = append(profiles, p) + } + return profiles, nil +} + +// CreateAboutProfile 创建个人资料 +func CreateAboutProfile(profile *models.AboutProfile) error { + // Marshal JSON + techBytes, _ := json.Marshal(profile.TechList) + profile.TechStack = string(techBytes) + + expBytes, _ := json.Marshal(profile.ExperienceList) + profile.ExperiencesStr = string(expBytes) + + query := ` + INSERT INTO about_profiles (name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + ` + result, err := config.DB.Exec( + query, + profile.Name, + profile.Avatar, + profile.Location, + profile.Bio, + profile.Email, + profile.Wechat, + profile.TechStack, + profile.ExperiencesStr, + profile.IsPrimary, + ) + if err != nil { + log.Printf("Error creating about profile: %v", err) + return err + } + + id, err := result.LastInsertId() + if err != nil { + return err + } + profile.ID = uint(id) + return nil +} + +// UpdateAboutProfile 更新个人资料 +func UpdateAboutProfile(profile *models.AboutProfile) error { + techBytes, _ := json.Marshal(profile.TechList) + profile.TechStack = string(techBytes) + + expBytes, _ := json.Marshal(profile.ExperienceList) + profile.ExperiencesStr = string(expBytes) + + query := ` + UPDATE about_profiles + SET name = ?, avatar = ?, location = ?, bio = ?, email = ?, wechat = ?, tech_stack = ?, experiences = ?, is_primary = ?, updated_at = NOW() + WHERE id = ? + ` + _, err := config.DB.Exec( + query, + profile.Name, + profile.Avatar, + profile.Location, + profile.Bio, + profile.Email, + profile.Wechat, + profile.TechStack, + profile.ExperiencesStr, + profile.IsPrimary, + profile.ID, + ) + if err != nil { + log.Printf("Error updating about profile: %v", err) + return err + } + return nil +} + +// DeleteAboutProfile 删除个人资料 +func DeleteAboutProfile(id uint) error { + query := "DELETE FROM about_profiles WHERE id = ?" + _, err := config.DB.Exec(query, id) + if err != nil { + log.Printf("Error deleting about profile: %v", err) + return err + } + return nil +}