Files
nl-blogs/server/handlers/post.go
2026-01-20 15:43:17 +08:00

406 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
// GetRecommendedPosts 获取推荐文章基于IP的协同过滤
func GetRecommendedPosts(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
utils.Error(c, 400, "Invalid post ID")
return
}
// 获取客户端IP
userIP := c.ClientIP()
// 获取推荐文章默认3篇
limit := 3
if limitStr := c.Query("limit"); limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 10 {
limit = l
}
}
posts, err := repositories.GetRecommendedPostsByIP(id, userIP, limit)
if err != nil {
utils.ServerError(c, err)
return
}
// 构建响应
responses := repositories.BuildPostsResponse(posts)
if responses == nil {
utils.Success(c, []interface{}{})
} else {
utils.Success(c, responses)
}
}