文章和文件建立关联

This commit is contained in:
2026-08-19 17:39:23 +08:00
parent f1a6c5abaa
commit 4a9ac54963
13 changed files with 256 additions and 28 deletions
+1 -1
View File
@@ -38,12 +38,12 @@ var adminMenus = []AdminMenuGroup{
{Key: "posts", Name: "文章管理"},
{Key: "categories", Name: "分类管理"},
{Key: "tags", Name: "标签管理"},
{Key: "files", Name: "文件管理"},
{Key: "pages", Name: "页面管理"},
{Key: "comments", Name: "评论管理"},
}},
{Group: "系统管理", Items: []AdminMenuItem{
{Key: "users", Name: "用户管理"},
{Key: "files", Name: "文件管理"},
{Key: "themes", Name: "主题管理"},
{Key: "settings", Name: "基础设置"},
}},
+49 -3
View File
@@ -2,6 +2,8 @@ package backend
import (
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@@ -34,6 +36,7 @@ type CreatePostRequest struct {
Tags []string `json:"tags"`
Status string `json:"status"`
IsTop bool `json:"is_top"`
FileIDs []uint `json:"file_ids"` // 正文中使用的文件ID列表(按出现顺序)
}
// 更新文章请求
@@ -46,6 +49,7 @@ type UpdatePostRequest struct {
Tags []string `json:"tags"`
Status string `json:"status"`
IsTop bool `json:"is_top"`
FileIDs []uint `json:"file_ids"` // 正文中使用的文件ID列表(按出现顺序)
}
// Markdown 预览请求
@@ -221,6 +225,15 @@ func CreatePost(c *gin.Context) {
return
}
// 处理文件关联(包含封面)
if len(req.FileIDs) > 0 {
var files []models.File
database.DB.Where("id IN ?", req.FileIDs).Find(&files)
if len(files) > 0 {
database.DB.Model(&post).Association("Files").Replace(files)
}
}
c.JSON(http.StatusCreated, gin.H{"data": post})
}
@@ -260,9 +273,8 @@ func UpdatePost(c *gin.Context) {
if req.Summary != "" {
updates["summary"] = req.Summary
}
if req.Cover != "" {
updates["cover"] = req.Cover
}
// 封面始终更新(空字符串表示移除封面)
updates["cover"] = req.Cover
if req.CategoryID > 0 {
updates["category_id"] = req.CategoryID
}
@@ -297,6 +309,20 @@ func UpdatePost(c *gin.Context) {
return
}
// 处理文件关联(包含封面)
if req.FileIDs != nil {
if len(req.FileIDs) > 0 {
var files []models.File
database.DB.Where("id IN ?", req.FileIDs).Find(&files)
if len(files) > 0 {
database.DB.Model(&post).Association("Files").Replace(files)
}
} else {
// 空数组,清除所有关联
database.DB.Model(&post).Association("Files").Clear()
}
}
c.JSON(http.StatusOK, gin.H{"data": post})
}
@@ -319,6 +345,26 @@ func DeletePost(c *gin.Context) {
}
}
// 获取关联文件,删除物理文件和数据库记录
var files []models.File
database.DB.Model(&post).Association("Files").Find(&files)
for _, f := range files {
// 检查文件是否还被其他文章使用
var count int64
database.DB.Table("post_files").Where("file_id = ? AND post_id != ?", f.ID, post.ID).Count(&count)
if count == 0 {
// 没有其他文章使用,删除物理文件
physPath := filepath.Join("./static/uploads", f.FilePath)
if _, err := os.Stat(physPath); err == nil {
os.Remove(physPath)
}
// 删除 files 表记录
database.DB.Delete(&f)
}
}
// 清除关联关系
database.DB.Model(&post).Association("Files").Clear()
if err := database.DB.Delete(&post).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除文章失败"})
return
+44 -2
View File
@@ -156,6 +156,7 @@ func UploadImage(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"id": fileRecord.ID,
"url": uploadURLPrefix + strings.ReplaceAll(relPath, "\\", "/"),
"name": filename,
"size": file.Size,
@@ -189,11 +190,38 @@ func ListUploads(c *gin.Context) {
MimeType string `json:"mime_type"`
UserID uint `json:"user_id"`
CreatedAt string `json:"created_at"`
PostTitle string `json:"post_title"` // 关联文章标题,为空表示未关联
PostID uint `json:"post_id"` // 关联文章ID
}
// 批量查询文件关联信息
fileIDs := make([]uint, 0, len(files))
for _, f := range files {
fileIDs = append(fileIDs, f.ID)
}
// 查询关联关系(带文章标题)
type PostFileResult struct {
FileID uint `json:"file_id"`
PostID uint `json:"post_id"`
PostTitle string `json:"post_title"`
}
var postFileResults []PostFileResult
database.DB.Table("post_files").
Select("post_files.file_id, post_files.post_id, posts.title as post_title").
Joins("LEFT JOIN posts ON posts.id = post_files.post_id").
Where("post_files.file_id IN ?", fileIDs).
Find(&postFileResults)
// 构建 fileID -> PostFileResult 映射
postFileMap := make(map[uint]PostFileResult)
for _, pf := range postFileResults {
postFileMap[pf.FileID] = pf
}
items := make([]FileItem, 0, len(files))
for _, f := range files {
items = append(items, FileItem{
item := FileItem{
ID: f.ID,
Name: f.FileName,
OrigName: f.OrigName,
@@ -202,7 +230,13 @@ func ListUploads(c *gin.Context) {
MimeType: f.MimeType,
UserID: f.UserID,
CreatedAt: f.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
// 填充关联信息
if pf, ok := postFileMap[f.ID]; ok {
item.PostTitle = pf.PostTitle
item.PostID = pf.PostID
}
items = append(items, item)
}
c.JSON(http.StatusOK, gin.H{"data": items})
@@ -230,6 +264,14 @@ func DeleteUpload(c *gin.Context) {
return
}
// 检查文件是否被文章使用
var count int64
database.DB.Table("post_files").Where("file_id = ?", fileID).Count(&count)
if count > 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "该文件已被文章使用,无法删除"})
return
}
// 删除物理文件
physPath := filepath.Join(uploadDir, file.FilePath)
if _, err := os.Stat(physPath); err == nil {