406 lines
9.7 KiB
Go
406 lines
9.7 KiB
Go
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)
|
||
}
|
||
}
|