优化页面、修复BUG
This commit is contained in:
@@ -11,6 +11,29 @@ import (
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
|
||||
func trySavePostHistory(c *gin.Context, postID uint) bool {
|
||||
userID, _ := c.Get("userID")
|
||||
modifiedBy, ok := userID.(uint)
|
||||
if !ok {
|
||||
log.Printf("Error saving post history for post %d: invalid userID", postID)
|
||||
return false
|
||||
}
|
||||
saved, err := repositories.GetPostByIDAdmin(postID)
|
||||
if err != nil {
|
||||
log.Printf("Error saving post history for post %d: reload failed: %v", postID, err)
|
||||
return false
|
||||
}
|
||||
if saved == nil {
|
||||
log.Printf("Error saving post history for post %d: post not found after save", postID)
|
||||
return false
|
||||
}
|
||||
if err := repositories.SavePostHistory(saved, modifiedBy); err != nil {
|
||||
log.Printf("Error saving post history for post %d: %v", postID, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 获取博客文章列表 (前台)
|
||||
func GetPosts(c *gin.Context) {
|
||||
// 获取查询参数
|
||||
@@ -252,15 +275,9 @@ func AdminCreatePost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存历史记录
|
||||
userID, _ := c.Get("userID")
|
||||
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)
|
||||
}
|
||||
}
|
||||
historySaved := trySavePostHistory(c, post.ID)
|
||||
|
||||
utils.SuccessWithMsg(c, "Post created successfully", gin.H{"id": post.ID})
|
||||
utils.SuccessWithMsg(c, "Post created successfully", gin.H{"id": post.ID, "historySaved": historySaved})
|
||||
}
|
||||
|
||||
// 更新文章
|
||||
@@ -287,15 +304,9 @@ func AdminUpdatePost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存历史记录
|
||||
userID, _ := c.Get("userID")
|
||||
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)
|
||||
}
|
||||
}
|
||||
historySaved := trySavePostHistory(c, postID)
|
||||
|
||||
utils.SuccessWithMsg(c, "Post updated successfully", nil)
|
||||
utils.SuccessWithMsg(c, "Post updated successfully", gin.H{"historySaved": historySaved})
|
||||
}
|
||||
|
||||
// 更新文章关联关系(只更新分类、专栏、标签,不更新内容)
|
||||
@@ -323,12 +334,9 @@ 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))
|
||||
}
|
||||
historySaved := trySavePostHistory(c, postID)
|
||||
|
||||
utils.SuccessWithMsg(c, "Post relations updated successfully", nil)
|
||||
utils.SuccessWithMsg(c, "Post relations updated successfully", gin.H{"historySaved": historySaved})
|
||||
}
|
||||
|
||||
// 切换文章发布状态
|
||||
@@ -353,12 +361,9 @@ 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))
|
||||
}
|
||||
historySaved := trySavePostHistory(c, postID)
|
||||
|
||||
utils.SuccessWithMsg(c, "Post status updated successfully", nil)
|
||||
utils.SuccessWithMsg(c, "Post status updated successfully", gin.H{"historySaved": historySaved})
|
||||
}
|
||||
|
||||
// 删除文章
|
||||
|
||||
@@ -9,6 +9,36 @@ import (
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
|
||||
func parseSnippetID(c *gin.Context) (uint, bool) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil || id == 0 {
|
||||
utils.Error(c, 400, "Invalid snippet ID")
|
||||
return 0, false
|
||||
}
|
||||
return uint(id), true
|
||||
}
|
||||
|
||||
type snippetPayload struct {
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
CodeTypeID uint `json:"codeTypeId"`
|
||||
Description string `json:"description"`
|
||||
PostIDs []uint `json:"postIds"`
|
||||
}
|
||||
|
||||
func (p *snippetPayload) toSnippet(id uint) models.Snippet {
|
||||
return models.Snippet{
|
||||
ID: id,
|
||||
Title: p.Title,
|
||||
Code: p.Code,
|
||||
Type: p.Type,
|
||||
CodeTypeID: p.CodeTypeID,
|
||||
Description: p.Description,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSnippets 获取所有代码片段
|
||||
func GetSnippets(c *gin.Context) {
|
||||
snippets, err := repositories.GetSnippets()
|
||||
@@ -17,7 +47,6 @@ func GetSnippets(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
responses := repositories.BuildSnippetsResponse(snippets)
|
||||
if responses == nil {
|
||||
utils.Success(c, []interface{}{})
|
||||
@@ -27,8 +56,11 @@ func GetSnippets(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetSnippet(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// 从数据库获取代码片段
|
||||
id, ok := parseSnippetID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
snippet, err := repositories.GetSnippetByID(id)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
@@ -40,10 +72,7 @@ func GetSnippet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := repositories.BuildSnippetResponse(snippet)
|
||||
|
||||
utils.Success(c, response)
|
||||
utils.Success(c, repositories.BuildSnippetResponse(snippet))
|
||||
}
|
||||
|
||||
// AdminGetSnippets 获取代码片段列表 (后台)
|
||||
@@ -73,12 +102,13 @@ func AdminGetSnippets(c *gin.Context) {
|
||||
|
||||
// AdminCreateSnippet 创建代码片段
|
||||
func AdminCreateSnippet(c *gin.Context) {
|
||||
var snippet models.Snippet
|
||||
if err := c.ShouldBindJSON(&snippet); err != nil {
|
||||
var payload snippetPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
snippet := payload.toSnippet(0)
|
||||
syncSnippetTypeFromCodeType(&snippet)
|
||||
|
||||
if err := repositories.CreateSnippet(&snippet); err != nil {
|
||||
@@ -86,20 +116,28 @@ func AdminCreateSnippet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.ReplaceSnippetPosts(snippet.ID, payload.PostIDs); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Snippet created successfully", gin.H{"id": snippet.ID})
|
||||
}
|
||||
|
||||
// AdminUpdateSnippet 更新代码片段
|
||||
func AdminUpdateSnippet(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, ok := parseSnippetID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var snippet models.Snippet
|
||||
if err := c.ShouldBindJSON(&snippet); err != nil {
|
||||
var payload snippetPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
snippet.ID = idStr
|
||||
snippet := payload.toSnippet(id)
|
||||
syncSnippetTypeFromCodeType(&snippet)
|
||||
|
||||
if err := repositories.UpdateSnippet(&snippet); err != nil {
|
||||
@@ -107,6 +145,11 @@ func AdminUpdateSnippet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.ReplaceSnippetPosts(snippet.ID, payload.PostIDs); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Snippet updated successfully", nil)
|
||||
}
|
||||
|
||||
@@ -122,9 +165,12 @@ func syncSnippetTypeFromCodeType(snippet *models.Snippet) {
|
||||
|
||||
// AdminDeleteSnippet 删除代码片段
|
||||
func AdminDeleteSnippet(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, ok := parseSnippetID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.DeleteSnippet(idStr); err != nil {
|
||||
if err := repositories.DeleteSnippet(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/handlers"
|
||||
"github.com/niangaodev/art-code/middleware"
|
||||
"github.com/niangaodev/art-code/repositories"
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,8 @@ func main() {
|
||||
|
||||
// 运行数据库迁移 (Convert Datetime to BigInt)
|
||||
// repositories.MigrateToBigInt() // 已禁用自动迁移
|
||||
repositories.MigratePostHistoryFields()
|
||||
repositories.MigratePostSnippets()
|
||||
|
||||
// 初始化ip2region (如果文件不存在,将降级为普通IP记录)
|
||||
// 函数会自动从环境变量或可执行文件目录查找 ip2region.xdb
|
||||
|
||||
@@ -54,20 +54,21 @@ func (p *Post) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// PostResponse 博客文章响应模型
|
||||
type PostResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName"`
|
||||
CategorySlug string `json:"categorySlug"`
|
||||
ColumnID *uint `json:"columnId,omitempty"`
|
||||
ColumnName string `json:"columnName,omitempty"`
|
||||
ColumnSlug string `json:"columnSlug,omitempty"`
|
||||
Date string `json:"date"`
|
||||
Excerpt string `json:"excerpt,omitempty"`
|
||||
Content *string `json:"content,omitempty"` // 使用指针类型,当 includeContent=true 时总是设置
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
IsPublished int `json:"isPublished,omitempty"`
|
||||
ReadCount uint `json:"readCount,omitempty"`
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName"`
|
||||
CategorySlug string `json:"categorySlug"`
|
||||
ColumnID *uint `json:"columnId,omitempty"`
|
||||
ColumnName string `json:"columnName,omitempty"`
|
||||
ColumnSlug string `json:"columnSlug,omitempty"`
|
||||
Date string `json:"date"`
|
||||
Excerpt string `json:"excerpt,omitempty"`
|
||||
Content *string `json:"content,omitempty"` // 使用指针类型,当 includeContent=true 时总是设置
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
IsPublished int `json:"isPublished,omitempty"`
|
||||
ReadCount uint `json:"readCount,omitempty"`
|
||||
Snippets []SnippetBrief `json:"snippets,omitempty"`
|
||||
}
|
||||
|
||||
// PostHistory 文章历史记录模型
|
||||
|
||||
27
server/models/post_snippet.go
Normal file
27
server/models/post_snippet.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
// PostSnippet 文章与代码片段关联
|
||||
type PostSnippet struct {
|
||||
PostID uint `json:"postId" gorm:"primaryKey;column:post_id"`
|
||||
SnippetID uint `json:"snippetId" gorm:"primaryKey;column:snippet_id"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at;default:0"`
|
||||
}
|
||||
|
||||
func (PostSnippet) TableName() string {
|
||||
return "post_snippets"
|
||||
}
|
||||
|
||||
// PostBrief 文章简要信息
|
||||
type PostBrief struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// SnippetBrief 代码片段简要信息
|
||||
type SnippetBrief struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
CodeType *CodeTypeResponse `json:"codeType,omitempty"`
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// Snippet 代码片段模型
|
||||
type Snippet struct {
|
||||
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id;autoIncrement"`
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
Code string `json:"code" gorm:"column:code;type:text"`
|
||||
Type string `json:"type" gorm:"column:type"`
|
||||
@@ -48,7 +48,7 @@ func (s *Snippet) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// SnippetResponse 代码片段响应模型
|
||||
type SnippetResponse struct {
|
||||
ID string `json:"id"`
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
@@ -56,4 +56,5 @@ type SnippetResponse struct {
|
||||
CodeType *CodeTypeResponse `json:"codeType,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ViewCount uint `json:"viewCount,omitempty"`
|
||||
Posts []PostBrief `json:"posts,omitempty"`
|
||||
}
|
||||
|
||||
@@ -31,12 +31,12 @@ CREATE TABLE `about_profiles` (
|
||||
`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',
|
||||
`experiences` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'JSON string of experience list',
|
||||
`is_primary` tinyint(1) NULL DEFAULT 0,
|
||||
`is_primary` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否主展示资料(0否 1是)',
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '关于页个人资料表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for access_logs
|
||||
@@ -310,8 +310,8 @@ CREATE TABLE `posts` (
|
||||
`column_id` int UNSIGNED NULL DEFAULT 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 '文章内容',
|
||||
`read_count` int UNSIGNED NULL DEFAULT 0 COMMENT '阅读量',
|
||||
`is_published` tinyint(1) NULL DEFAULT 1 COMMENT '是否已发布(0:草稿,1:已发布)',
|
||||
`read_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '阅读量',
|
||||
`is_published` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否已发布(0:草稿,1:已发布)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
@@ -388,7 +388,7 @@ CREATE TABLE `settings` (
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `code_types`;
|
||||
CREATE TABLE `code_types` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`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,
|
||||
@@ -403,13 +403,13 @@ CREATE TABLE `code_types` (
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `snippets`;
|
||||
CREATE TABLE `snippets` (
|
||||
`id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码片段唯一标识',
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '代码片段ID',
|
||||
`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 '查看次数',
|
||||
`view_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '查看次数',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
@@ -549,4 +549,18 @@ CREATE TABLE `works` (
|
||||
INDEX `idx_is_featured`(`is_featured` ASC) USING BTREE COMMENT '按精选状态查询索引'
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for post_snippets
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `post_snippets`;
|
||||
CREATE TABLE `post_snippets` (
|
||||
`post_id` int UNSIGNED NOT NULL COMMENT '文章ID',
|
||||
`snippet_id` bigint UNSIGNED NOT NULL COMMENT '代码片段ID',
|
||||
`sort_order` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序(越小越靠前)',
|
||||
`created_at` bigint NOT NULL DEFAULT 0 COMMENT '创建时间(Unix秒)',
|
||||
PRIMARY KEY (`post_id`, `snippet_id`) USING BTREE,
|
||||
INDEX `idx_snippet_id`(`snippet_id` ASC) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章代码片段关联表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@@ -112,3 +112,47 @@ func execSQL(query string) {
|
||||
log.Printf("SQL Error: %v | Query: %s", err, query)
|
||||
}
|
||||
}
|
||||
|
||||
// MigratePostHistoryFields adds column_id and tag_ids to post_history if missing.
|
||||
func MigratePostHistoryFields() {
|
||||
if !columnExists("post_history", "column_id") {
|
||||
log.Printf("Adding post_history.column_id...")
|
||||
execSQL("ALTER TABLE `post_history` ADD COLUMN `column_id` INT UNSIGNED NULL AFTER `category_id`")
|
||||
}
|
||||
if !columnExists("post_history", "tag_ids") {
|
||||
log.Printf("Adding post_history.tag_ids...")
|
||||
execSQL("ALTER TABLE `post_history` ADD COLUMN `tag_ids` JSON NULL COMMENT '标签ID快照' AFTER `column_id`")
|
||||
}
|
||||
}
|
||||
|
||||
func tableExists(tableName string) bool {
|
||||
var count int64
|
||||
err := config.DB.Raw(`
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
`, tableName).Scan(&count).Error
|
||||
if err != nil {
|
||||
log.Printf("Error checking table %s: %v", tableName, err)
|
||||
return false
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// MigratePostSnippets creates post_snippets junction table if missing.
|
||||
func MigratePostSnippets() {
|
||||
if tableExists("post_snippets") {
|
||||
return
|
||||
}
|
||||
log.Printf("Creating post_snippets table...")
|
||||
execSQL(`CREATE TABLE post_snippets (
|
||||
post_id INT UNSIGNED NOT NULL COMMENT '文章ID',
|
||||
snippet_id BIGINT UNSIGNED NOT NULL COMMENT '代码片段ID',
|
||||
sort_order INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序(越小越靠前)',
|
||||
created_at BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间(Unix秒)',
|
||||
PRIMARY KEY (post_id, snippet_id),
|
||||
INDEX idx_snippet_id (snippet_id),
|
||||
INDEX idx_sort_order (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章代码片段关联表'`)
|
||||
}
|
||||
|
||||
@@ -391,6 +391,10 @@ func BuildPostResponse(post *models.Post, includeContent bool) *models.PostRespo
|
||||
response.Content = &content
|
||||
}
|
||||
|
||||
if snippets, err := GetSnippetsByPostID(post.ID); err == nil && len(snippets) > 0 {
|
||||
response.Snippets = snippets
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
132
server/repositories/post_snippet_repository.go
Normal file
132
server/repositories/post_snippet_repository.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ReplaceSnippetPosts 全量替换代码片段关联的文章
|
||||
func ReplaceSnippetPosts(snippetID uint, postIDs []uint) error {
|
||||
return config.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("snippet_id = ?", snippetID).Delete(&models.PostSnippet{}).Error; err != nil {
|
||||
log.Printf("Error clearing snippet posts: %v", err)
|
||||
return err
|
||||
}
|
||||
if len(postIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
for i, postID := range postIDs {
|
||||
row := models.PostSnippet{
|
||||
PostID: postID,
|
||||
SnippetID: snippetID,
|
||||
SortOrder: uint(i),
|
||||
CreatedAt: now,
|
||||
}
|
||||
if err := tx.Create(&row).Error; err != nil {
|
||||
log.Printf("Error creating post_snippet: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetPostsBySnippetID 获取代码片段关联的文章
|
||||
func GetPostsBySnippetID(snippetID uint) ([]models.PostBrief, error) {
|
||||
var posts []models.Post
|
||||
err := config.DB.Table("posts").
|
||||
Select("posts.id, posts.title").
|
||||
Joins("INNER JOIN post_snippets ps ON ps.post_id = posts.id").
|
||||
Where("ps.snippet_id = ? AND posts.deleted_at = ?", snippetID, 0).
|
||||
Order("ps.sort_order ASC, ps.created_at ASC").
|
||||
Find(&posts).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting posts by snippet ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
result := make([]models.PostBrief, 0, len(posts))
|
||||
for _, p := range posts {
|
||||
result = append(result, models.PostBrief{ID: p.ID, Title: p.Title})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetSnippetsByPostID 获取文章关联的代码片段(不含完整 code)
|
||||
func GetSnippetsByPostID(postID uint) ([]models.SnippetBrief, error) {
|
||||
var snippets []models.Snippet
|
||||
err := config.DB.Table("snippets").
|
||||
Select("snippets.id, snippets.title, snippets.type, snippets.code_type_id").
|
||||
Joins("INNER JOIN post_snippets ps ON ps.snippet_id = snippets.id").
|
||||
Where("ps.post_id = ? AND snippets.deleted_at = ?", postID, 0).
|
||||
Order("ps.sort_order ASC, ps.created_at ASC").
|
||||
Find(&snippets).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting snippets by post ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
result := make([]models.SnippetBrief, 0, len(snippets))
|
||||
for _, s := range snippets {
|
||||
brief := models.SnippetBrief{
|
||||
ID: s.ID,
|
||||
Title: s.Title,
|
||||
Type: s.Type,
|
||||
}
|
||||
if ct := GetCodeTypeForSnippet(&s); ct != nil {
|
||||
r := BuildCodeTypeResponse(ct)
|
||||
brief.CodeType = &r
|
||||
if brief.Type == "" {
|
||||
brief.Type = ct.Name
|
||||
}
|
||||
}
|
||||
result = append(result, brief)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPostsBySnippetIDs 批量获取多个片段关联的文章
|
||||
func GetPostsBySnippetIDs(snippetIDs []uint) map[uint][]models.PostBrief {
|
||||
result := make(map[uint][]models.PostBrief)
|
||||
if len(snippetIDs) == 0 {
|
||||
return result
|
||||
}
|
||||
type row struct {
|
||||
SnippetID uint
|
||||
PostID uint
|
||||
Title string
|
||||
SortOrder uint
|
||||
}
|
||||
var rows []row
|
||||
err := config.DB.Table("post_snippets ps").
|
||||
Select("ps.snippet_id, ps.post_id, p.title, ps.sort_order").
|
||||
Joins("INNER JOIN posts p ON p.id = ps.post_id").
|
||||
Where("ps.snippet_id IN ? AND p.deleted_at = ?", snippetIDs, 0).
|
||||
Order("ps.sort_order ASC, ps.created_at ASC").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
log.Printf("Error batch getting posts by snippet IDs: %v", err)
|
||||
return result
|
||||
}
|
||||
for _, r := range rows {
|
||||
result[r.SnippetID] = append(result[r.SnippetID], models.PostBrief{ID: r.PostID, Title: r.Title})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetPostIDsBySnippetID 获取片段关联的文章 ID 列表(后台编辑用)
|
||||
func GetPostIDsBySnippetID(snippetID uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := config.DB.Model(&models.PostSnippet{}).
|
||||
Where("snippet_id = ?", snippetID).
|
||||
Order("sort_order ASC, created_at ASC").
|
||||
Pluck("post_id", &ids).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting post IDs by snippet ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func GetSnippets() ([]models.Snippet, error) {
|
||||
}
|
||||
|
||||
// GetSnippetByID 根据ID获取代码片段
|
||||
func GetSnippetByID(id string) (*models.Snippet, error) {
|
||||
func GetSnippetByID(id uint) (*models.Snippet, error) {
|
||||
var snippet models.Snippet
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
@@ -47,6 +47,10 @@ func GetSnippetByID(id string) (*models.Snippet, error) {
|
||||
|
||||
// BuildSnippetResponse 构建代码片段响应
|
||||
func BuildSnippetResponse(snippet *models.Snippet) *models.SnippetResponse {
|
||||
return BuildSnippetResponseWithPosts(snippet, nil)
|
||||
}
|
||||
|
||||
func BuildSnippetResponseWithPosts(snippet *models.Snippet, posts []models.PostBrief) *models.SnippetResponse {
|
||||
resp := &models.SnippetResponse{
|
||||
ID: snippet.ID,
|
||||
Title: snippet.Title,
|
||||
@@ -63,14 +67,27 @@ func BuildSnippetResponse(snippet *models.Snippet) *models.SnippetResponse {
|
||||
resp.Type = ct.Name
|
||||
}
|
||||
}
|
||||
if posts != nil {
|
||||
resp.Posts = posts
|
||||
} else if posts, err := GetPostsBySnippetID(snippet.ID); err == nil && len(posts) > 0 {
|
||||
resp.Posts = posts
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// BuildSnippetsResponse 构建代码片段列表响应
|
||||
func BuildSnippetsResponse(snippets []models.Snippet) []models.SnippetResponse {
|
||||
if len(snippets) == 0 {
|
||||
return []models.SnippetResponse{}
|
||||
}
|
||||
ids := make([]uint, len(snippets))
|
||||
for i, s := range snippets {
|
||||
ids[i] = s.ID
|
||||
}
|
||||
postsMap := GetPostsBySnippetIDs(ids)
|
||||
var responses []models.SnippetResponse
|
||||
for _, snippet := range snippets {
|
||||
responses = append(responses, *BuildSnippetResponse(&snippet))
|
||||
responses = append(responses, *BuildSnippetResponseWithPosts(&snippet, postsMap[snippet.ID]))
|
||||
}
|
||||
return responses
|
||||
}
|
||||
@@ -105,7 +122,7 @@ func UpdateSnippet(snippet *models.Snippet) error {
|
||||
}
|
||||
|
||||
// DeleteSnippet 删除代码片段 (Soft Delete)
|
||||
func DeleteSnippet(id string) error {
|
||||
func DeleteSnippet(id uint) error {
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
|
||||
Reference in New Issue
Block a user