初始化

This commit is contained in:
李琦
2026-01-15 15:37:34 +08:00
parent 8e28549ad5
commit f39e37e534
8 changed files with 681 additions and 169 deletions

View File

@@ -70,6 +70,18 @@
{{ tech }}
</span>
</div>
<h3 class="text-xl font-bold text-white mb-6 flex items-center gap-2">
<i data-lucide="briefcase" class="w-5 h-5 text-art-accent"></i> 经历
</h3>
<div class="space-y-6 relative border-l border-white/10 ml-2 pl-6">
<div v-for="(exp, index) in profile.experiences" :key="index" class="relative">
<div class="absolute -left-[29px] top-1.5 w-3 h-3 rounded-full" :class="index === 0 ? 'bg-art-accent' : 'bg-white/20'"></div>
<div class="text-sm font-mono mb-1" :class="index === 0 ? 'text-art-accent' : 'text-art-muted'">{{ exp.year }}</div>
<div class="text-white font-bold">{{ exp.role }}</div>
<div class="text-xs text-art-muted">{{ exp.company }}</div>
</div>
</div>
</div>
</div>
</div>
@@ -78,10 +90,17 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeMount } from 'vue'
import { ref, onMounted, onUpdated, nextTick } from 'vue'
import { useScrollAnimation } from '../composables/useScrollAnimation'
import { fetchAboutProfile } from '../services/api'
// 定义类型
interface Experience {
year: string
role: string
company: string
}
interface Profile {
id: string
name: string
@@ -93,19 +112,21 @@ interface Profile {
wechat: string
}
techStack: string[]
experiences: Experience[]
}
const profile = ref<Profile>({
id: '',
name: '年糕崽崽',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4',
location: '中国 · 浙江杭州',
bio: '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。目前,我专注于高性能 B 端应用的体验升级。',
name: '',
avatar: '',
location: '',
bio: '',
contact: {
email: 'hello@niangao.dev',
wechat: 'Niangao_Dev'
email: '',
wechat: ''
},
techStack: ['Vue 3', 'React', 'TypeScript', 'Three.js', 'Golang', 'Tailwind CSS', 'Vite', 'Gin']
techStack: [],
experiences: []
})
const loading = ref(false)
@@ -113,45 +134,48 @@ const error = ref('')
const { initObserver } = useScrollAnimation()
// 模拟API调用实际项目中应替换为真实API
const refreshIcons = () => {
if ((window as any).lucide) {
(window as any).lucide.createIcons()
}
}
const fetchProfileData = async () => {
loading.value = true
error.value = ''
try {
// 实际项目中应调用真实API
// const response = await fetch(`${API_BASE}/profile`)
// const data = await response.json()
// profile.value = data
// 使用模拟数据作为备选
const data = await fetchAboutProfile()
profile.value = {
id: '1',
name: '年糕崽崽',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4',
location: '中国 · 浙江杭州',
bio: '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。目前,我专注于高性能 B 端应用的体验升级。',
id: String(data.id),
name: data.name,
avatar: data.avatar,
location: data.location,
bio: data.bio,
contact: {
email: 'hello@niangao.dev',
wechat: 'Niangao_Dev'
email: data.email,
wechat: data.wechat
},
techStack: ['Vue 3', 'React', 'TypeScript', 'Three.js', 'Golang', 'Tailwind CSS', 'Vite', 'Gin']
techStack: data.techStack,
experiences: data.experiences || []
}
// 初始化动画
initObserver()
} catch (err) {
console.error('Error fetching profile data:', err)
error.value = '获取个人资料失败,请稍后重试'
// 保持原有静态数据
} finally {
loading.value = false
nextTick(() => {
initObserver()
refreshIcons()
})
}
}
onMounted(() => {
fetchProfileData()
// 初始化Lucide图标
if (window.lucide) {
window.lucide.createIcons()
}
refreshIcons()
})
onUpdated(() => {
refreshIcons()
})
</script>

View File

@@ -854,3 +854,40 @@ export const deleteTag = async (id: number): Promise<void> => {
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<AboutProfile> => {
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
}
}

16
server/about_page.sql Normal file
View File

@@ -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 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级以及探索生成式艺术Generative Art在网页中的应用。闲暇之余我喜欢在杭州的西湖边散步或者捣腾我的客制化机械键盘。', 'hello@niangao.dev', 'Niangao_Dev', '["Vue 3", "React", "TypeScript", "Three.js", "Golang", "Tailwind CSS", "Rust", "Wails"]', TRUE);

92
server/handlers/about.go Normal file
View File

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

View File

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

28
server/models/about.go Normal file
View File

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

View File

@@ -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 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 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 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 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', '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>', 0, 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14');
INSERT INTO `posts` VALUES ('shader', '着色器魔法:从零开始写一个噪声生成器', '图形渲染', '2025-12-08', '深入了解WebGL着色器学习如何从零开始实现一个高性能的噪声生成器为你的3D作品增添独特的视觉效果。', '<h2>WebGL着色器基础</h2><p>WebGL着色器是运行在GPU上的小程序,用于处理图形渲染...</p>', 0, 1, '2026-01-13 16:10:14', '2026-01-13 16:10:14');
INSERT INTO `posts` VALUES ('refactor', '重构的艺术:如何优雅地处理遗留代码', '工程化', '2026-01-12', '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>', 1, 1, '2026-01-13 16:10:14', '2026-01-15 15:01:35');
INSERT INTO `posts` VALUES ('shader', '着色器魔法:从零开始写一个噪声生成器', '图形渲染', '2025-12-08', '深入了解WebGL着色器学习如何从零开始实现一个高性能的噪声生成器为你的3D作品增添独特的视觉效果。', ' <p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>\r\n <h2>什么是柏林噪声?</h2>\r\n <p>柏林噪声Perlin Noise是一种梯度噪声它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>\r\n <h2>Three.js 中的实现</h2>\r\n <p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>\r\n <pre><code><span class=\"code-comment\">// 简单的顶点着色器</span>\r\n<span class=\"code-keyword\">varying</span> <span class=\"code-keyword\">vec2</span> vUv;\r\n<span class=\"code-keyword\">void</span> <span class=\"code-func\">main</span>() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}</code></pre>\r\n <p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>\r\n ', 1, 1, '2026-01-13 16:10:14', '2026-01-15 15:03:42');
INSERT INTO `posts` VALUES ('ux', '用户体验设计:从认知心理学到交互实践', '设计思维', '2025-11-20', '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '<h2>认知心理学在UX设计中的应用</h2><p>了解用户的认知过程是设计良好用户体验的基础...</p>', 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;
SET FOREIGN_KEY_CHECKS = 1;

View File

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