package handlers import ( "fmt" "log" "strconv" "github.com/gin-gonic/gin" "github.com/niangaodev/art-code/models" "github.com/niangaodev/art-code/repositories" "github.com/niangaodev/art-code/utils" ) // 获取博客文章列表 (前台) func GetPosts(c *gin.Context) { // 获取查询参数 keyword := c.Query("q") categoryIDStr := c.Query("category") tagIDStr := c.Query("tag") columnIDStr := c.Query("column") var categoryID uint if categoryIDStr != "" { if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil { categoryID = uint(id) } } var tagID uint if tagIDStr != "" { if id, err := strconv.ParseUint(tagIDStr, 10, 32); err == nil { tagID = uint(id) } } var columnID uint if columnIDStr != "" { if id, err := strconv.ParseUint(columnIDStr, 10, 32); err == nil { columnID = uint(id) } } // 从数据库获取所有博客文章 posts, err := repositories.GetPosts(keyword, categoryID, tagID, columnID) if err != nil { utils.ServerError(c, err) return } // 异步记录搜索日志 userIP := c.ClientIP() userLocation := utils.GetRegion(userIP) if keyword != "" { LogSearch(keyword, "keyword", userIP, userLocation) } if categoryID > 0 { LogSearch(strconv.FormatUint(uint64(categoryID), 10), "category", userIP, userLocation) } if tagID > 0 { LogSearch(strconv.FormatUint(uint64(tagID), 10), "tag", userIP, userLocation) } if columnID > 0 { LogSearch(strconv.FormatUint(uint64(columnID), 10), "column", userIP, userLocation) } // 构建响应 responses := repositories.BuildPostsResponse(posts) // Ensure not nil if responses == nil { // We need to return [] utils.Success(c, []interface{}{}) } else { utils.Success(c, responses) } } func GetPost(c *gin.Context) { idStr := c.Param("id") // 转换ID var id uint if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil { utils.Error(c, 400, "Invalid post ID") return } // 从数据库获取博客文章 post, err := repositories.GetPostByID(id) if err != nil { utils.ServerError(c, err) return } if post == nil { // User requested empty object for details if not found? // "Details return empty object... otherwise frontend errors" // If I return 404, frontend api.ts might throw. // If I return 200 with empty result, frontend might handle it better if it checks result. // But empty object {} is safer than null. // Let's return error for now as it's more standard, but user asked for "empty object". // Actually, let's look at api.ts: `fetchPost` returns `Post` object. // If it gets null, it might crash access properties. // If it gets {}, it's fine (properties undefined). // But usually we want 404. // Let's stick to Error for Not Found, but ensure api.ts handles it or returns default object. // My api.ts update handles errors by returning default object for details! utils.Error(c, 404, "Post not found") return } // 构建响应,包含内容 response := repositories.BuildPostResponse(post, true) // 记录用户访问日志 (异步执行,不阻塞响应) go func() { // 添加 panic recover 保护 defer func() { if r := recover(); r != nil { log.Printf("Panic in user access log goroutine for post %d: %v", post.ID, r) } }() // 获取客户端IP ip := c.ClientIP() // 获取归属地,使用 recover 保护 var location string func() { defer func() { if r := recover(); r != nil { log.Printf("Panic in GetRegion for IP %s (post %d): %v", ip, post.ID, r) location = "Unknown" } }() location = utils.GetRegion(ip) }() // 确保 location 不为空,如果为空则设置为 "Unknown" if location == "" { location = "Unknown" } // 记录调试信息 log.Printf("Creating user access log: PostID=%d, IP=%s, Location=%s", post.ID, ip, location) // 获取用户ID (如果已登录) var userID uint = 0 if uid, exists := c.Get("userID"); exists { userID = uid.(uint) } logEntry := &models.UserAccessLog{ UserID: userID, UserIP: ip, UserLocation: location, ArticleID: post.ID, } if err := repositories.CreateUserAccessLog(logEntry); err != nil { log.Printf("Failed to create user access log for PostID=%d, IP=%s, Location=%s: %v", post.ID, ip, location, err) } else { log.Printf("Successfully created user access log: PostID=%d, IP=%s, Location=%s", post.ID, ip, location) } }() utils.Success(c, response) } // 获取所有文章(包括未发布的,后台用) func AdminGetPosts(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "1000")) // Default to 1000 to mimic "all" for now posts, total, err := repositories.GetAllPosts(page, pageSize) if err != nil { utils.ServerError(c, err) return } list := repositories.BuildPostsResponse(posts) res := gin.H{ "list": list, "total": total, "page": page, "size": pageSize, } if list == nil { res["list"] = []interface{}{} } utils.Success(c, res) } // 创建文章 func AdminCreatePost(c *gin.Context) { var post models.Post if err := c.ShouldBindJSON(&post); err != nil { utils.Error(c, 400, "Invalid request") return } // 创建文章 if err := repositories.CreatePost(&post); err != nil { utils.ServerError(c, err) return } // 保存历史记录 userID, _ := c.Get("userID") if err := repositories.SavePostHistory(&post, userID.(uint)); err != nil { log.Printf("Error saving post history: %v", err) } utils.SuccessWithMsg(c, "Post created successfully", gin.H{"id": post.ID}) } // 更新文章 func AdminUpdatePost(c *gin.Context) { postIDStr := c.Param("id") var postID uint if _, err := fmt.Sscanf(postIDStr, "%d", &postID); err != nil { utils.Error(c, 400, "Invalid post ID") return } var post models.Post if err := c.ShouldBindJSON(&post); err != nil { utils.Error(c, 400, "Invalid request") return } // 设置文章ID post.ID = postID // 更新文章 if err := repositories.UpdatePost(&post); err != nil { utils.ServerError(c, err) return } // 保存历史记录 userID, _ := c.Get("userID") if err := repositories.SavePostHistory(&post, userID.(uint)); err != nil { log.Printf("Error saving post history: %v", err) } utils.SuccessWithMsg(c, "Post updated successfully", nil) } // 切换文章发布状态 func AdminTogglePostStatus(c *gin.Context) { postIDStr := c.Param("id") var postID uint if _, err := fmt.Sscanf(postIDStr, "%d", &postID); err != nil { utils.Error(c, 400, "Invalid post ID") return } var req struct { IsPublished int `json:"isPublished"` } if err := c.ShouldBindJSON(&req); err != nil { utils.Error(c, 400, "Invalid request") return } if err := repositories.UpdatePostStatus(postID, req.IsPublished); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Post status updated successfully", nil) } // 删除文章 func AdminDeletePost(c *gin.Context) { postIDStr := c.Param("id") var postID uint if _, err := fmt.Sscanf(postIDStr, "%d", &postID); err != nil { utils.Error(c, 400, "Invalid post ID") return } // 删除文章 if err := repositories.DeletePost(postID); err != nil { utils.ServerError(c, err) return } utils.SuccessWithMsg(c, "Post deleted successfully", nil) } // 获取文章历史记录 func AdminGetPostHistory(c *gin.Context) { postIDStr := c.Param("id") var postID uint if _, err := fmt.Sscanf(postIDStr, "%d", &postID); err != nil { utils.Error(c, 400, "Invalid post ID") return } history, err := repositories.GetPostHistory(postID) if err != nil { utils.ServerError(c, err) return } res := repositories.BuildPostHistoryResponses(history) if res == nil { utils.Success(c, []interface{}{}) } else { utils.Success(c, res) } } // 获取指定版本的文章历史记录 func AdminGetPostHistoryByVersion(c *gin.Context) { postIDStr := c.Param("id") var postID uint if _, err := fmt.Sscanf(postIDStr, "%d", &postID); err != nil { utils.Error(c, 400, "Invalid post ID") return } version := c.Param("version") // 转换版本号为uint var versionUint uint _, err := fmt.Sscanf(version, "%d", &versionUint) if err != nil { utils.Error(c, 400, "Invalid version") return } history, err := repositories.GetPostHistoryByVersion(postID, versionUint) if err != nil { utils.ServerError(c, err) return } if history == nil { utils.Error(c, 404, "History not found") return } utils.Success(c, repositories.BuildPostHistoryResponse(history)) } // GetPostsByTagID 根据标签ID获取文章 func GetPostsByTagID(c *gin.Context) { tagIDStr := c.Param("id") var tagID uint if _, err := fmt.Sscanf(tagIDStr, "%d", &tagID); err != nil { utils.Error(c, 400, "Invalid tag ID") return } posts, err := repositories.GetPosts("", 0, tagID, 0) if err != nil { utils.ServerError(c, err) return } res := repositories.BuildPostsResponse(posts) if res == nil { utils.Success(c, []interface{}{}) } else { utils.Success(c, res) } }