优化页面、修复BUG
This commit is contained in:
83
server/handlers/code_type.go
Normal file
83
server/handlers/code_type.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"github.com/niangaodev/art-code/repositories"
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
|
||||
func GetCodeTypes(c *gin.Context) {
|
||||
list, err := repositories.GetCodeTypes()
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildCodeTypesResponse(list))
|
||||
}
|
||||
|
||||
func AdminGetCodeTypes(c *gin.Context) {
|
||||
GetCodeTypes(c)
|
||||
}
|
||||
|
||||
func AdminCreateCodeType(c *gin.Context) {
|
||||
var ct models.CodeType
|
||||
if err := c.ShouldBindJSON(&ct); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
if ct.Name == "" {
|
||||
utils.Error(c, 400, "Name is required")
|
||||
return
|
||||
}
|
||||
if err := repositories.CreateCodeType(&ct); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.SuccessWithMsg(c, "Code type created", repositories.BuildCodeTypeResponse(&ct))
|
||||
}
|
||||
|
||||
func AdminUpdateCodeType(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
|
||||
utils.Error(c, 400, "Invalid ID")
|
||||
return
|
||||
}
|
||||
var ct models.CodeType
|
||||
if err := c.ShouldBindJSON(&ct); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
ct.ID = id
|
||||
if err := repositories.UpdateCodeType(&ct); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.SuccessWithMsg(c, "Code type updated", nil)
|
||||
}
|
||||
|
||||
func AdminDeleteCodeType(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
|
||||
utils.Error(c, 400, "Invalid ID")
|
||||
return
|
||||
}
|
||||
count, err := repositories.CountSnippetsByCodeTypeID(id)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
utils.Error(c, 400, "Cannot delete: snippets are using this code type")
|
||||
return
|
||||
}
|
||||
if err := repositories.DeleteCodeType(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.SuccessWithMsg(c, "Code type deleted", nil)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func AdminGetRecentActivities(c *gin.Context) {
|
||||
// 获取最近10条操作日志
|
||||
logs, _, err := repositories.GetOperationLogs(1, 10)
|
||||
logs, _, err := repositories.GetOperationLogs(1, 10, repositories.OperationLogFilter{})
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
@@ -58,11 +58,9 @@ func AdminGetRecentActivities(c *gin.Context) {
|
||||
|
||||
// 获取操作日志列表
|
||||
func AdminGetOperationLogs(c *gin.Context) {
|
||||
// 获取分页参数
|
||||
page := 1
|
||||
pageSize := 10
|
||||
|
||||
// 从查询参数中获取分页信息
|
||||
if c.Query("page") != "" {
|
||||
if p, err := strconv.Atoi(c.Query("page")); err == nil {
|
||||
page = p
|
||||
@@ -75,8 +73,24 @@ func AdminGetOperationLogs(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取操作日志
|
||||
logs, total, err := repositories.GetOperationLogs(page, pageSize)
|
||||
filter := repositories.OperationLogFilter{
|
||||
Action: c.Query("action"),
|
||||
Method: c.Query("method"),
|
||||
StartDate: c.Query("startDate"),
|
||||
EndDate: c.Query("endDate"),
|
||||
}
|
||||
if statusStr := c.Query("status"); statusStr != "" {
|
||||
if s, err := strconv.Atoi(statusStr); err == nil {
|
||||
filter.Status = s
|
||||
}
|
||||
}
|
||||
if userIDStr := c.Query("userId"); userIDStr != "" {
|
||||
if uid, err := strconv.ParseUint(userIDStr, 10, 32); err == nil {
|
||||
filter.UserID = uint(uid)
|
||||
}
|
||||
}
|
||||
|
||||
logs, total, err := repositories.GetOperationLogs(page, pageSize, filter)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
@@ -145,8 +159,15 @@ func AdminGetAccessLogs(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
filter := repositories.AccessLogFilter{
|
||||
Path: c.Query("path"),
|
||||
Region: c.Query("region"),
|
||||
StartDate: c.Query("startDate"),
|
||||
EndDate: c.Query("endDate"),
|
||||
}
|
||||
|
||||
// 获取访问日志
|
||||
logs, total, err := repositories.GetAccessLogs(page, pageSize, nil)
|
||||
logs, total, err := repositories.GetAccessLogs(page, pageSize, nil, filter)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -254,8 +254,10 @@ func AdminCreatePost(c *gin.Context) {
|
||||
|
||||
// 保存历史记录
|
||||
userID, _ := c.Get("userID")
|
||||
if err := repositories.SavePostHistory(&post, userID.(uint)); err != nil {
|
||||
log.Printf("Error saving post history: %v", err)
|
||||
if saved, err := repositories.GetPostByIDAdmin(post.ID); err == nil && saved != nil {
|
||||
if err := repositories.SavePostHistory(saved, userID.(uint)); err != nil {
|
||||
log.Printf("Error saving post history: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Post created successfully", gin.H{"id": post.ID})
|
||||
@@ -287,8 +289,10 @@ func AdminUpdatePost(c *gin.Context) {
|
||||
|
||||
// 保存历史记录
|
||||
userID, _ := c.Get("userID")
|
||||
if err := repositories.SavePostHistory(&post, userID.(uint)); err != nil {
|
||||
log.Printf("Error saving post history: %v", err)
|
||||
if saved, err := repositories.GetPostByIDAdmin(postID); err == nil && saved != nil {
|
||||
if err := repositories.SavePostHistory(saved, userID.(uint)); err != nil {
|
||||
log.Printf("Error saving post history: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Post updated successfully", nil)
|
||||
@@ -319,6 +323,11 @@ func AdminUpdatePostRelations(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := c.Get("userID")
|
||||
if saved, err := repositories.GetPostByIDAdmin(postID); err == nil && saved != nil {
|
||||
_ = repositories.SavePostHistory(saved, userID.(uint))
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Post relations updated successfully", nil)
|
||||
}
|
||||
|
||||
@@ -344,6 +353,11 @@ func AdminTogglePostStatus(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := c.Get("userID")
|
||||
if saved, err := repositories.GetPostByIDAdmin(postID); err == nil && saved != nil {
|
||||
_ = repositories.SavePostHistory(saved, userID.(uint))
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Post status updated successfully", nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ func AdminCreateSnippet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
syncSnippetTypeFromCodeType(&snippet)
|
||||
|
||||
if err := repositories.CreateSnippet(&snippet); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
@@ -98,6 +100,7 @@ func AdminUpdateSnippet(c *gin.Context) {
|
||||
}
|
||||
|
||||
snippet.ID = idStr
|
||||
syncSnippetTypeFromCodeType(&snippet)
|
||||
|
||||
if err := repositories.UpdateSnippet(&snippet); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
@@ -107,6 +110,16 @@ func AdminUpdateSnippet(c *gin.Context) {
|
||||
utils.SuccessWithMsg(c, "Snippet updated successfully", nil)
|
||||
}
|
||||
|
||||
func syncSnippetTypeFromCodeType(snippet *models.Snippet) {
|
||||
if snippet.CodeTypeID == 0 {
|
||||
return
|
||||
}
|
||||
ct, err := repositories.GetCodeTypeByID(snippet.CodeTypeID)
|
||||
if err == nil && ct != nil {
|
||||
snippet.Type = ct.Name
|
||||
}
|
||||
}
|
||||
|
||||
// AdminDeleteSnippet 删除代码片段
|
||||
func AdminDeleteSnippet(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
|
||||
@@ -15,7 +15,12 @@ func AdminGetUsers(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
|
||||
users, total, err := repositories.GetUsers(page, pageSize)
|
||||
keyword := c.Query("keyword")
|
||||
if keyword == "" {
|
||||
keyword = c.Query("q")
|
||||
}
|
||||
|
||||
users, total, err := repositories.GetUsers(page, pageSize, keyword)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -71,6 +71,7 @@ func main() {
|
||||
// 代码片段路由
|
||||
api.GET("/snippets", handlers.GetSnippets)
|
||||
api.GET("/snippets/:id", handlers.GetSnippet)
|
||||
api.GET("/code-types", handlers.GetCodeTypes)
|
||||
|
||||
// 标签路由
|
||||
api.GET("/tags", handlers.GetTags)
|
||||
@@ -135,6 +136,12 @@ func main() {
|
||||
authAdmin.PUT("/snippets/:id", middleware.PermissionMiddleware("snippets", "update"), handlers.AdminUpdateSnippet)
|
||||
authAdmin.DELETE("/snippets/:id", middleware.PermissionMiddleware("snippets", "delete"), handlers.AdminDeleteSnippet)
|
||||
|
||||
// 代码类型管理
|
||||
authAdmin.GET("/code-types", middleware.PermissionMiddleware("snippets", "read"), handlers.AdminGetCodeTypes)
|
||||
authAdmin.POST("/code-types", middleware.PermissionMiddleware("snippets", "create"), handlers.AdminCreateCodeType)
|
||||
authAdmin.PUT("/code-types/:id", middleware.PermissionMiddleware("snippets", "update"), handlers.AdminUpdateCodeType)
|
||||
authAdmin.DELETE("/code-types/:id", middleware.PermissionMiddleware("snippets", "delete"), handlers.AdminDeleteCodeType)
|
||||
|
||||
// 系统配置管理
|
||||
authAdmin.GET("/settings", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetSettings)
|
||||
authAdmin.POST("/settings", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateSetting)
|
||||
|
||||
@@ -70,13 +70,16 @@ func OperationLogMiddleware() gin.HandlerFunc {
|
||||
log.Printf("Creating operation log: UserID=%d, IP=%s, Region=%s, Path=%s", userID.(uint), ip, region, c.Request.URL.Path)
|
||||
|
||||
// 构建操作日志
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
operationLog := &models.OperationLog{
|
||||
UserID: userID.(uint),
|
||||
Username: username.(string),
|
||||
IP: ip,
|
||||
Region: region,
|
||||
Path: c.Request.URL.Path,
|
||||
Method: c.Request.Method,
|
||||
Path: path,
|
||||
Method: method,
|
||||
Action: utils.GetOperationAction(path, method),
|
||||
Params: string(requestBody),
|
||||
Status: c.Writer.Status(),
|
||||
Duration: duration,
|
||||
|
||||
43
server/models/code_type.go
Normal file
43
server/models/code_type.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CodeType 代码类型
|
||||
type CodeType struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Category int `json:"category" gorm:"column:category"` // 0前端 1后端 2其他
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
func (CodeType) TableName() string {
|
||||
return "code_types"
|
||||
}
|
||||
|
||||
func (c *CodeType) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if c.CreatedAt == 0 {
|
||||
c.CreatedAt = now
|
||||
}
|
||||
if c.UpdatedAt == 0 {
|
||||
c.UpdatedAt = now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CodeType) BeforeUpdate(tx *gorm.DB) error {
|
||||
c.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
type CodeTypeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Category int `json:"category"`
|
||||
}
|
||||
@@ -15,6 +15,7 @@ type OperationLog struct {
|
||||
Region string `json:"region" gorm:"column:region"` // IP归属地
|
||||
Path string `json:"path" gorm:"column:path;index"`
|
||||
Method string `json:"method" gorm:"column:method"`
|
||||
Action string `json:"action" gorm:"column:action;index"`
|
||||
Params string `json:"params" gorm:"column:params;type:text"`
|
||||
Status int `json:"status" gorm:"column:status"`
|
||||
Duration int `json:"duration" gorm:"column:duration"`
|
||||
|
||||
@@ -118,21 +118,24 @@ func (ph *PostHistory) BeforeCreate(tx *gorm.DB) error {
|
||||
|
||||
// PostHistoryResponse 文章历史记录响应模型
|
||||
type PostHistoryResponse struct {
|
||||
ID uint `json:"id"`
|
||||
PostID uint `json:"postId"`
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName,omitempty"`
|
||||
ColumnID *uint `json:"columnId,omitempty"`
|
||||
TagIDs []uint `json:"tagIds,omitempty"`
|
||||
Excerpt string `json:"excerpt,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Date string `json:"date"`
|
||||
IsPublished int `json:"isPublished"`
|
||||
ModifiedBy uint `json:"modifiedBy"`
|
||||
ModifiedAt string `json:"modifiedAt"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
ID uint `json:"id"`
|
||||
PostID uint `json:"postId"`
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName,omitempty"`
|
||||
ColumnID *uint `json:"columnId,omitempty"`
|
||||
TagIDs []uint `json:"tagIds,omitempty"`
|
||||
Excerpt string `json:"excerpt,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Date string `json:"date"`
|
||||
IsPublished int `json:"isPublished"`
|
||||
ModifiedBy uint `json:"modifiedBy"`
|
||||
ModifiedByName string `json:"modifiedByName,omitempty"`
|
||||
ColumnName string `json:"columnName,omitempty"`
|
||||
TagNames []string `json:"tagNames,omitempty"`
|
||||
ModifiedAt string `json:"modifiedAt"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
// PostHistoryFieldDiff 字段对比结果
|
||||
|
||||
@@ -12,6 +12,7 @@ type Snippet struct {
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
Code string `json:"code" gorm:"column:code;type:text"`
|
||||
Type string `json:"type" gorm:"column:type"`
|
||||
CodeTypeID uint `json:"codeTypeId" gorm:"column:code_type_id"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
ViewCount uint `json:"viewCount" gorm:"column:view_count;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
@@ -47,8 +48,12 @@ func (s *Snippet) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// SnippetResponse 代码片段响应模型
|
||||
type SnippetResponse struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
CodeTypeID uint `json:"codeTypeId,omitempty"`
|
||||
CodeType *CodeTypeResponse `json:"codeType,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ViewCount uint `json:"viewCount,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
Navicat Premium Dump SQL
|
||||
|
||||
Source Server : 我的mysql8
|
||||
Source Server : 开发环境-本地
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80407 (8.4.7)
|
||||
Source Host : 101.43.12.11:3306
|
||||
Source Server Version : 80408 (8.4.8)
|
||||
Source Host : localhost:3306
|
||||
Source Schema : nl_blog
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80407 (8.4.7)
|
||||
Target Server Version : 80408 (8.4.8)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 24/06/2026 16:07:16
|
||||
Date: 24/06/2026 16:52:32
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
@@ -36,7 +36,7 @@ CREATE TABLE `about_profiles` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for access_logs
|
||||
@@ -56,7 +56,7 @@ CREATE TABLE `access_logs` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_path`(`path` ASC) USING BTREE COMMENT '按访问路径查询索引',
|
||||
INDEX `idx_status_code`(`status_code` ASC) USING BTREE COMMENT '按状态码查询索引'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 25100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访问日志表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 27003 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访问日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachment_categories
|
||||
@@ -72,7 +72,7 @@ CREATE TABLE `attachment_categories` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件分类表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachments
|
||||
@@ -98,7 +98,7 @@ CREATE TABLE `attachments` (
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_file_type`(`file_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 74 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 76 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for categories
|
||||
@@ -116,7 +116,7 @@ CREATE TABLE `categories` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_slug`(`slug` ASC) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章分类表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for column_posts
|
||||
@@ -129,7 +129,7 @@ CREATE TABLE `column_posts` (
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`column_id`, `post_id`) USING BTREE,
|
||||
INDEX `idx_post_id`(`post_id` ASC) USING BTREE
|
||||
) 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;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for columns
|
||||
@@ -147,7 +147,7 @@ CREATE TABLE `columns` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '专栏表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '专栏表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for email_suffixes
|
||||
@@ -163,7 +163,7 @@ CREATE TABLE `email_suffixes` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_suffix`(`suffix` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱后缀配置表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱后缀配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for inquiries
|
||||
@@ -182,7 +182,7 @@ CREATE TABLE `inquiries` (
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作咨询表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作咨询表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for operation_logs
|
||||
@@ -196,14 +196,16 @@ CREATE TABLE `operation_logs` (
|
||||
`region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 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方法',
|
||||
`action` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作描述',
|
||||
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数',
|
||||
`status` int NOT NULL COMMENT '响应状态码',
|
||||
`duration` int NOT NULL COMMENT '响应时间(毫秒)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 487 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = Dynamic;
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_action`(`action` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 678 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oss_configs
|
||||
@@ -225,7 +227,7 @@ CREATE TABLE `oss_configs` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_is_active`(`is_active` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OSS配置表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OSS配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for partners
|
||||
@@ -242,7 +244,7 @@ CREATE TABLE `partners` (
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作伙伴表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作伙伴表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for permissions
|
||||
@@ -258,7 +260,7 @@ CREATE TABLE `permissions` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `unique_resource_action`(`resource` ASC, `action` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for post_history
|
||||
@@ -270,7 +272,7 @@ CREATE TABLE `post_history` (
|
||||
`version` int UNSIGNED NOT NULL DEFAULT 1 COMMENT '版本号',
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章标题',
|
||||
`category_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '文章分类ID',
|
||||
`column_id` int UNSIGNED NULL DEFAULT NULL COMMENT '专栏ID',
|
||||
`column_id` int UNSIGNED NULL DEFAULT NULL COMMENT '专栏ID快照',
|
||||
`tag_ids` json NULL COMMENT '标签ID快照',
|
||||
`excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文章摘要',
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章内容',
|
||||
@@ -282,7 +284,7 @@ CREATE TABLE `post_history` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_post_id`(`post_id` ASC) USING BTREE,
|
||||
INDEX `idx_version`(`version` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 60 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章历史记录表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 60 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章历史记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for post_tags
|
||||
@@ -294,7 +296,7 @@ CREATE TABLE `post_tags` (
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`post_id`, `tag_id`) USING BTREE,
|
||||
INDEX `idx_tag_id`(`tag_id` ASC) USING BTREE
|
||||
) 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;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for posts
|
||||
@@ -319,7 +321,7 @@ CREATE TABLE `posts` (
|
||||
INDEX `idx_is_published`(`is_published` ASC) USING BTREE COMMENT '按发布状态查询索引',
|
||||
INDEX `idx_original_id`(`original_id` ASC) USING BTREE,
|
||||
FULLTEXT INDEX `idx_title_content`(`title`, `content`) COMMENT '标题和内容全文索引,用于搜索'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '博客文章表(新结构)' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '博客文章表(新结构)' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for role_permissions
|
||||
@@ -330,7 +332,7 @@ CREATE TABLE `role_permissions` (
|
||||
`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
|
||||
) 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;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for roles
|
||||
@@ -345,7 +347,7 @@ CREATE TABLE `roles` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `name`(`name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for search_logs
|
||||
@@ -362,7 +364,7 @@ CREATE TABLE `search_logs` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_search_type`(`search_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '搜索记录表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '搜索记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for settings
|
||||
@@ -379,7 +381,22 @@ 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 = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '网站配置表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '网站配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for code_types
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `code_types`;
|
||||
CREATE TABLE `code_types` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型名称',
|
||||
`category` tinyint NOT NULL DEFAULT 0 COMMENT '0前端 1后端 2其他',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_name`(`name` ASC, `deleted_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码类型表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for snippets
|
||||
@@ -390,6 +407,7 @@ CREATE TABLE `snippets` (
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码片段标题',
|
||||
`code` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码内容',
|
||||
`type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码类型(如:javascript、css、html等)',
|
||||
`code_type_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '代码类型ID',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '代码片段描述',
|
||||
`view_count` int UNSIGNED NULL DEFAULT 0 COMMENT '查看次数',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
@@ -398,7 +416,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;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for tags
|
||||
@@ -415,7 +433,7 @@ 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 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;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for testimonials
|
||||
@@ -433,7 +451,7 @@ CREATE TABLE `testimonials` (
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客户评价表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客户评价表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_access_logs
|
||||
@@ -445,15 +463,12 @@ CREATE TABLE `user_access_logs` (
|
||||
`user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户IP地址',
|
||||
`user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户归属地',
|
||||
`article_id` int NOT NULL COMMENT '访问的文章ID',
|
||||
`visitor_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '匿名访客标识',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`access_time` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_article_id`(`article_id` ASC) USING BTREE,
|
||||
INDEX `idx_dedup_hour`(`article_id` ASC, `visitor_key` ASC, `access_time` ASC) USING BTREE,
|
||||
INDEX `idx_dedup_user_hour`(`article_id` ASC, `user_id` ASC, `access_time` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户访问记录表' ROW_FORMAT = Dynamic;
|
||||
INDEX `idx_article_id`(`article_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户访问记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for users
|
||||
@@ -477,7 +492,7 @@ CREATE TABLE `users` (
|
||||
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
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_gallery
|
||||
@@ -494,7 +509,7 @@ CREATE TABLE `work_gallery` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_work_id`(`work_id` ASC) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_tech_stack
|
||||
@@ -510,7 +525,7 @@ CREATE TABLE `work_tech_stack` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_work_id`(`work_id` ASC) USING BTREE,
|
||||
INDEX `idx_category`(`category` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for works
|
||||
@@ -532,6 +547,6 @@ 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;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
9
server/repositories/access_log_filter.go
Normal file
9
server/repositories/access_log_filter.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package repositories
|
||||
|
||||
// AccessLogFilter 访问日志筛选条件
|
||||
type AccessLogFilter struct {
|
||||
Path string
|
||||
Region string
|
||||
StartDate string
|
||||
EndDate string
|
||||
}
|
||||
89
server/repositories/code_type_repository.go
Normal file
89
server/repositories/code_type_repository.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func GetCodeTypes() ([]models.CodeType, error) {
|
||||
var list []models.CodeType
|
||||
err := config.DB.Model(&models.CodeType{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("category ASC, name ASC").
|
||||
Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
func GetCodeTypeByID(id uint) (*models.CodeType, error) {
|
||||
var ct models.CodeType
|
||||
err := config.DB.Model(&models.CodeType{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&ct).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &ct, nil
|
||||
}
|
||||
|
||||
func CreateCodeType(ct *models.CodeType) error {
|
||||
return config.DB.Create(ct).Error
|
||||
}
|
||||
|
||||
func UpdateCodeType(ct *models.CodeType) error {
|
||||
return config.DB.Model(&models.CodeType{}).
|
||||
Where("id = ? AND deleted_at = ?", ct.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": ct.Name,
|
||||
"category": ct.Category,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func DeleteCodeType(id uint) error {
|
||||
return config.DB.Model(&models.CodeType{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
}
|
||||
|
||||
func CountSnippetsByCodeTypeID(id uint) (int64, error) {
|
||||
var count int64
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("code_type_id = ? AND deleted_at = ?", id, 0).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func BuildCodeTypeResponse(ct *models.CodeType) models.CodeTypeResponse {
|
||||
return models.CodeTypeResponse{
|
||||
ID: ct.ID,
|
||||
Name: ct.Name,
|
||||
Category: ct.Category,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildCodeTypesResponse(list []models.CodeType) []models.CodeTypeResponse {
|
||||
res := make([]models.CodeTypeResponse, 0, len(list))
|
||||
for _, ct := range list {
|
||||
res = append(res, BuildCodeTypeResponse(&ct))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetCodeTypeForSnippet(snippet *models.Snippet) *models.CodeType {
|
||||
if snippet.CodeTypeID == 0 {
|
||||
return nil
|
||||
}
|
||||
ct, err := GetCodeTypeByID(snippet.CodeTypeID)
|
||||
if err != nil {
|
||||
log.Printf("Error loading code type %d: %v", snippet.CodeTypeID, err)
|
||||
return nil
|
||||
}
|
||||
return ct
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func GetDailyUV(startDate, endDate string) ([]UVTrendData, error) {
|
||||
}
|
||||
|
||||
// GetAccessLogs 获取访问日志列表(支持分页和筛选)
|
||||
func GetAccessLogs(page, pageSize int, postID *int) ([]models.AccessLog, int64, error) {
|
||||
func GetAccessLogs(page, pageSize int, postID *int, filter AccessLogFilter) ([]models.AccessLog, int64, error) {
|
||||
query := config.DB.Model(&models.AccessLog{}).
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
@@ -168,6 +168,25 @@ func GetAccessLogs(page, pageSize int, postID *int) ([]models.AccessLog, int64,
|
||||
)
|
||||
}
|
||||
|
||||
if filter.Path != "" {
|
||||
query = query.Where("path LIKE ?", "%"+filter.Path+"%")
|
||||
}
|
||||
if filter.Region != "" {
|
||||
query = query.Where("region LIKE ?", "%"+filter.Region+"%")
|
||||
}
|
||||
if filter.StartDate != "" {
|
||||
startUnix := parseDateToUnix(filter.StartDate, false)
|
||||
if startUnix > 0 {
|
||||
query = query.Where("created_at >= ?", startUnix)
|
||||
}
|
||||
}
|
||||
if filter.EndDate != "" {
|
||||
endUnix := parseDateToUnix(filter.EndDate, true)
|
||||
if endUnix > 0 {
|
||||
query = query.Where("created_at <= ?", endUnix)
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
@@ -186,7 +205,7 @@ func GetAccessLogs(page, pageSize int, postID *int) ([]models.AccessLog, int64,
|
||||
// GetPostAccessLogsFromAccessLogs 从 access_logs 表获取指定文章的访问记录(已废弃,应使用 user_access_logs)
|
||||
// 保留此函数以保持向后兼容,但建议使用 user_access_log_repository.GetPostAccessLogs
|
||||
func GetPostAccessLogsFromAccessLogs(postID int, page, pageSize int) ([]models.AccessLog, int64, error) {
|
||||
return GetAccessLogs(page, pageSize, &postID)
|
||||
return GetAccessLogs(page, pageSize, &postID, AccessLogFilter{})
|
||||
}
|
||||
|
||||
// ExtractProvinceFromRegion 从归属地字符串中提取省份信息
|
||||
|
||||
@@ -14,7 +14,7 @@ func MigrateToBigInt() {
|
||||
"about_profiles", "partners", "testimonials", "inquiries",
|
||||
"email_suffixes", "access_logs", "user_access_logs",
|
||||
"operation_logs", "permissions", "roles",
|
||||
"work_tech_stack", "work_gallery", "post_tags", "post_history",
|
||||
"work_tech_stack", "work_gallery", "post_tags", "post_history", "code_types",
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
|
||||
11
server/repositories/operation_log_filter.go
Normal file
11
server/repositories/operation_log_filter.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package repositories
|
||||
|
||||
// OperationLogFilter 操作日志筛选条件
|
||||
type OperationLogFilter struct {
|
||||
Action string
|
||||
Method string
|
||||
Status int // 0 表示不筛选
|
||||
UserID uint
|
||||
StartDate string
|
||||
EndDate string
|
||||
}
|
||||
@@ -20,25 +20,45 @@ func CreateOperationLog(operationLog *models.OperationLog) error {
|
||||
}
|
||||
|
||||
// GetOperationLogs 获取操作日志列表
|
||||
func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error) {
|
||||
func GetOperationLogs(page, pageSize int, filter OperationLogFilter) ([]models.OperationLog, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var logs []models.OperationLog
|
||||
var total int64
|
||||
query := config.DB.Model(&models.OperationLog{}).
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
// 获取总记录数
|
||||
err := config.DB.Model(&models.OperationLog{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
if filter.Action != "" {
|
||||
query = query.Where("action LIKE ?", "%"+filter.Action+"%")
|
||||
}
|
||||
if filter.Method != "" {
|
||||
query = query.Where("method = ?", filter.Method)
|
||||
}
|
||||
if filter.Status > 0 {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.UserID > 0 {
|
||||
query = query.Where("user_id = ?", filter.UserID)
|
||||
}
|
||||
if filter.StartDate != "" {
|
||||
startUnix := parseDateToUnix(filter.StartDate, false)
|
||||
if startUnix > 0 {
|
||||
query = query.Where("created_at >= ?", startUnix)
|
||||
}
|
||||
}
|
||||
if filter.EndDate != "" {
|
||||
endUnix := parseDateToUnix(filter.EndDate, true)
|
||||
if endUnix > 0 {
|
||||
query = query.Where("created_at <= ?", endUnix)
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
log.Printf("Error counting operation logs: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
err = config.DB.Model(&models.OperationLog{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
var logs []models.OperationLog
|
||||
err := query.Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&logs).Error
|
||||
@@ -52,8 +72,10 @@ func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error)
|
||||
|
||||
// BuildOperationLogResponse 构建操作日志响应
|
||||
func BuildOperationLogResponse(log *models.OperationLog) *models.OperationLogResponse {
|
||||
// 生成操作描述
|
||||
action := utils.GetOperationAction(log.Path, log.Method)
|
||||
action := log.Action
|
||||
if action == "" {
|
||||
action = utils.GetOperationAction(log.Path, log.Method)
|
||||
}
|
||||
|
||||
return &models.OperationLogResponse{
|
||||
ID: log.ID,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
@@ -558,18 +559,22 @@ func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, er
|
||||
// BuildPostHistoryResponse 构建历史记录响应
|
||||
func BuildPostHistoryResponse(h *models.PostHistory, includeFull bool) *models.PostHistoryResponse {
|
||||
resp := &models.PostHistoryResponse{
|
||||
ID: h.ID,
|
||||
PostID: h.PostID,
|
||||
Version: h.Version,
|
||||
Title: h.Title,
|
||||
CategoryID: h.CategoryID,
|
||||
ColumnID: h.ColumnID,
|
||||
TagIDs: h.GetTagIDList(),
|
||||
Date: time.Unix(h.CreatedAt, 0).Format("2006-01-02"),
|
||||
IsPublished: h.IsPublished,
|
||||
ModifiedBy: h.ModifiedBy,
|
||||
ModifiedAt: time.Unix(h.ModifiedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
CreatedAt: time.Unix(h.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
ID: h.ID,
|
||||
PostID: h.PostID,
|
||||
Version: h.Version,
|
||||
Title: h.Title,
|
||||
CategoryID: h.CategoryID,
|
||||
CategoryName: lookupCategoryName(h.CategoryID),
|
||||
ColumnID: h.ColumnID,
|
||||
ColumnName: lookupColumnName(h.ColumnID),
|
||||
TagIDs: h.GetTagIDList(),
|
||||
TagNames: lookupTagNames(h.GetTagIDList()),
|
||||
Date: time.Unix(h.CreatedAt, 0).Format("2006-01-02"),
|
||||
IsPublished: h.IsPublished,
|
||||
ModifiedBy: h.ModifiedBy,
|
||||
ModifiedByName: lookupUsername(h.ModifiedBy),
|
||||
ModifiedAt: time.Unix(h.ModifiedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
CreatedAt: time.Unix(h.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
if includeFull {
|
||||
@@ -580,6 +585,53 @@ func BuildPostHistoryResponse(h *models.PostHistory, includeFull bool) *models.P
|
||||
return resp
|
||||
}
|
||||
|
||||
func lookupCategoryName(id uint) string {
|
||||
if id == 0 {
|
||||
return ""
|
||||
}
|
||||
var name string
|
||||
config.DB.Model(&models.Category{}).Where("id = ?", id).Pluck("name", &name)
|
||||
return name
|
||||
}
|
||||
|
||||
func lookupColumnName(id *uint) string {
|
||||
if id == nil || *id == 0 {
|
||||
return ""
|
||||
}
|
||||
var name string
|
||||
config.DB.Model(&models.Column{}).Where("id = ?", *id).Pluck("name", &name)
|
||||
return name
|
||||
}
|
||||
|
||||
func lookupTagNames(ids []uint) []string {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
var tags []models.Tag
|
||||
config.DB.Model(&models.Tag{}).Where("id IN ?", ids).Find(&tags)
|
||||
names := make([]string, 0, len(tags))
|
||||
for _, t := range tags {
|
||||
names = append(names, t.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func lookupUsername(id uint) string {
|
||||
if id == 0 {
|
||||
return ""
|
||||
}
|
||||
var username string
|
||||
config.DB.Model(&models.User{}).Where("id = ?", id).Pluck("username", &username)
|
||||
return username
|
||||
}
|
||||
|
||||
func formatPublishedLabel(v int) string {
|
||||
if v == 1 {
|
||||
return "已发布"
|
||||
}
|
||||
return "草稿"
|
||||
}
|
||||
|
||||
// BuildPostHistoryResponses 构建历史记录列表响应
|
||||
func BuildPostHistoryResponses(history []models.PostHistory) []models.PostHistoryResponse {
|
||||
var responses []models.PostHistoryResponse
|
||||
@@ -621,17 +673,14 @@ func GetPostHistoryDiff(postID uint, fromVersion, toVersion uint) (*models.PostH
|
||||
return nil, fmt.Errorf("to version not found")
|
||||
}
|
||||
|
||||
fromTags, _ := json.Marshal(fromHistory.GetTagIDList())
|
||||
toTags, _ := json.Marshal(toHistory.GetTagIDList())
|
||||
|
||||
fields := map[string]models.PostHistoryFieldDiff{
|
||||
"title": buildFieldDiff(fromHistory.Title, toHistory.Title, false),
|
||||
"excerpt": buildFieldDiff(fromHistory.Excerpt, toHistory.Excerpt, false),
|
||||
"content": buildFieldDiff(fromHistory.Content, toHistory.Content, true),
|
||||
"categoryId": buildFieldDiff(strconv.FormatUint(uint64(fromHistory.CategoryID), 10), strconv.FormatUint(uint64(toHistory.CategoryID), 10), false),
|
||||
"columnId": buildFieldDiff(formatOptionalUint(fromHistory.ColumnID), formatOptionalUint(toHistory.ColumnID), false),
|
||||
"tagIds": buildFieldDiff(string(fromTags), string(toTags), false),
|
||||
"isPublished": buildFieldDiff(strconv.Itoa(fromHistory.IsPublished), strconv.Itoa(toHistory.IsPublished), false),
|
||||
"category": buildFieldDiff(lookupCategoryName(fromHistory.CategoryID), lookupCategoryName(toHistory.CategoryID), false),
|
||||
"column": buildFieldDiff(lookupColumnName(fromHistory.ColumnID), lookupColumnName(toHistory.ColumnID), false),
|
||||
"tags": buildFieldDiff(strings.Join(lookupTagNames(fromHistory.GetTagIDList()), ", "), strings.Join(lookupTagNames(toHistory.GetTagIDList()), ", "), false),
|
||||
"isPublished": buildFieldDiff(formatPublishedLabel(fromHistory.IsPublished), formatPublishedLabel(toHistory.IsPublished), false),
|
||||
}
|
||||
|
||||
return &models.PostHistoryDiffResponse{
|
||||
|
||||
@@ -47,12 +47,23 @@ func GetSnippetByID(id string) (*models.Snippet, error) {
|
||||
|
||||
// BuildSnippetResponse 构建代码片段响应
|
||||
func BuildSnippetResponse(snippet *models.Snippet) *models.SnippetResponse {
|
||||
return &models.SnippetResponse{
|
||||
ID: snippet.ID,
|
||||
Title: snippet.Title,
|
||||
Code: snippet.Code,
|
||||
Type: snippet.Type,
|
||||
resp := &models.SnippetResponse{
|
||||
ID: snippet.ID,
|
||||
Title: snippet.Title,
|
||||
Code: snippet.Code,
|
||||
Type: snippet.Type,
|
||||
CodeTypeID: snippet.CodeTypeID,
|
||||
Description: snippet.Description,
|
||||
ViewCount: snippet.ViewCount,
|
||||
}
|
||||
if ct := GetCodeTypeForSnippet(snippet); ct != nil {
|
||||
r := BuildCodeTypeResponse(ct)
|
||||
resp.CodeType = &r
|
||||
if resp.Type == "" {
|
||||
resp.Type = ct.Name
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// BuildSnippetsResponse 构建代码片段列表响应
|
||||
@@ -79,11 +90,12 @@ func UpdateSnippet(snippet *models.Snippet) error {
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ? AND deleted_at = ?", snippet.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"title": snippet.Title,
|
||||
"code": snippet.Code,
|
||||
"type": snippet.Type,
|
||||
"description": snippet.Description,
|
||||
"updated_at": time.Now().Unix(),
|
||||
"title": snippet.Title,
|
||||
"code": snippet.Code,
|
||||
"type": snippet.Type,
|
||||
"code_type_id": snippet.CodeTypeID,
|
||||
"description": snippet.Description,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating snippet: %v", err)
|
||||
|
||||
@@ -49,27 +49,28 @@ func GetUserByID(id uint) (*models.User, error) {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUsers 获取所有用户 (分页)
|
||||
func GetUsers(page, pageSize int) ([]models.User, int, error) {
|
||||
// GetUsers 获取所有用户 (分页,支持 keyword 搜索)
|
||||
func GetUsers(page, pageSize int, keyword string) ([]models.User, int, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var users []models.User
|
||||
var total int64
|
||||
query := config.DB.Model(&models.User{}).
|
||||
Where("users.deleted_at = ?", 0)
|
||||
|
||||
// 获取总数
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
if keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
query = query.Where("users.username LIKE ? OR users.email LIKE ?", like, like)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
log.Printf("Error getting user count: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
err = config.DB.Model(&models.User{}).
|
||||
var users []models.User
|
||||
err := query.
|
||||
Select("users.*, COALESCE(roles.name, users.role) as role").
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id").
|
||||
Where("users.deleted_at = ?", 0).
|
||||
Order("users.created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
|
||||
@@ -133,6 +133,22 @@ func GetOperationAction(path, method string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// 代码类型管理
|
||||
if strings.Contains(pathLower, "/api/admin/code-types") {
|
||||
if methodUpper == "GET" {
|
||||
return "查看代码类型列表"
|
||||
}
|
||||
if methodUpper == "POST" {
|
||||
return "创建代码类型"
|
||||
}
|
||||
if methodUpper == "PUT" {
|
||||
return "更新代码类型"
|
||||
}
|
||||
if methodUpper == "DELETE" {
|
||||
return "删除代码类型"
|
||||
}
|
||||
}
|
||||
|
||||
// 代码片段管理
|
||||
if strings.Contains(pathLower, "/api/admin/snippets") {
|
||||
if methodUpper == "GET" {
|
||||
|
||||
Reference in New Issue
Block a user