feat: 添加微语功能
- 新增微语页面,类似 Twitter/QQ 空间的短内容发布平台 - 添加 GitHub 风格热力图组件展示发布活动 - 支持发布微语、图片上传、标签、Emoji - 支持点赞、评论功能 - 右侧栏显示统计数据和热门标签 - 支持按标签筛选微语 - 后端新增微语相关 API(CRUD、点赞、评论、标签) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -33,6 +33,7 @@ func main() {
|
||||
authHandler := handlers.NewAuthHandler(jwtManager)
|
||||
commentHandler := handlers.NewCommentHandler()
|
||||
likeHandler := handlers.NewLikeHandler()
|
||||
microHandler := handlers.NewMicroHandler()
|
||||
|
||||
// 创建路由
|
||||
r := gin.New()
|
||||
@@ -70,6 +71,15 @@ func main() {
|
||||
api.GET("/likes", likeHandler.GetLikeStatus)
|
||||
api.POST("/likes", likeHandler.ToggleLike) // 允许访客点赞(基于 IP Hash)
|
||||
|
||||
// 微语公开接口
|
||||
api.GET("/micro", microHandler.GetMicros)
|
||||
api.GET("/micro/stats", microHandler.GetMicroStats)
|
||||
api.GET("/micro/heatmap", microHandler.GetMicroHeatmap)
|
||||
api.GET("/micro/tags", microHandler.GetMicroTags)
|
||||
api.GET("/micro/:id", microHandler.GetMicro)
|
||||
api.POST("/micro/:id/like", microHandler.ToggleMicroLike) // 允许访客点赞
|
||||
api.GET("/micro-comments", microHandler.GetMicroComments) // 获取微语评论
|
||||
|
||||
// 需要认证的接口
|
||||
authGroup := api.Group("")
|
||||
authGroup.Use(middleware.AuthMiddleware(jwtManager))
|
||||
@@ -81,6 +91,14 @@ func main() {
|
||||
// 评论相关(需要登录才能评论)
|
||||
authGroup.POST("/comments", commentHandler.CreateComment)
|
||||
authGroup.DELETE("/comments/:id", commentHandler.DeleteComment)
|
||||
|
||||
// 微语相关(需要登录才能发布和删除)
|
||||
authGroup.POST("/micro", microHandler.CreateMicro)
|
||||
authGroup.DELETE("/micro/:id", microHandler.DeleteMicro)
|
||||
|
||||
// 微语评论相关
|
||||
authGroup.POST("/micro-comments", microHandler.CreateMicroComment)
|
||||
authGroup.DELETE("/micro-comments/:id", microHandler.DeleteMicroComment)
|
||||
}
|
||||
|
||||
// 管理员接口
|
||||
|
||||
@@ -54,6 +54,9 @@ func autoMigrate() error {
|
||||
&models.Like{},
|
||||
&models.LikeCount{},
|
||||
&models.PostMeta{},
|
||||
&models.Micro{},
|
||||
&models.MicroLike{},
|
||||
&models.MicroComment{},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
595
server/internal/handlers/micro.go
Normal file
595
server/internal/handlers/micro.go
Normal file
@@ -0,0 +1,595 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/novablog/server/internal/database"
|
||||
"github.com/novablog/server/internal/middleware"
|
||||
"github.com/novablog/server/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MicroHandler 微语处理器
|
||||
type MicroHandler struct{}
|
||||
|
||||
// NewMicroHandler 创建微语处理器
|
||||
func NewMicroHandler() *MicroHandler {
|
||||
return &MicroHandler{}
|
||||
}
|
||||
|
||||
// CreateMicroRequest 创建微语请求
|
||||
type CreateMicroRequest struct {
|
||||
Content string `json:"content" binding:"required,min=1,max=2000"`
|
||||
Images []string `json:"images"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// CreateMicro 创建微语
|
||||
func (h *MicroHandler) CreateMicro(c *gin.Context) {
|
||||
userID, isLoggedIn := middleware.GetUserID(c)
|
||||
if !isLoggedIn || userID == 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请登录后再发布"})
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateMicroRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 将图片数组转为 JSON 字符串
|
||||
var imagesJSON string
|
||||
if len(req.Images) > 0 {
|
||||
imagesBytes, _ := json.Marshal(req.Images)
|
||||
imagesJSON = string(imagesBytes)
|
||||
}
|
||||
|
||||
// 将标签数组转为 JSON 字符串
|
||||
var tagsJSON string
|
||||
if len(req.Tags) > 0 {
|
||||
tagsBytes, _ := json.Marshal(req.Tags)
|
||||
tagsJSON = string(tagsBytes)
|
||||
}
|
||||
|
||||
micro := models.Micro{
|
||||
UserID: userID,
|
||||
Content: req.Content,
|
||||
Images: imagesJSON,
|
||||
Tags: tagsJSON,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(µ).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "发布失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 加载用户信息
|
||||
database.DB.Preload("User").First(µ, micro.ID)
|
||||
|
||||
c.JSON(http.StatusCreated, micro)
|
||||
}
|
||||
|
||||
// GetMicros 获取微语列表
|
||||
func (h *MicroHandler) GetMicros(c *gin.Context) {
|
||||
// 分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 50 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 标签过滤
|
||||
tag := c.Query("tag")
|
||||
|
||||
var micros []models.Micro
|
||||
var total int64
|
||||
|
||||
query := database.DB.Model(&models.Micro{}).Where("deleted_at IS NULL")
|
||||
|
||||
// 如果有标签过滤
|
||||
if tag != "" {
|
||||
query = query.Where("tags LIKE ?", "%\""+tag+"\"%")
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
if err := query.
|
||||
Preload("User").
|
||||
Order("created_at DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Find(µs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户的点赞状态
|
||||
userID, isLoggedIn := middleware.GetUserID(c)
|
||||
userLikes := make(map[uint]bool)
|
||||
if isLoggedIn && userID > 0 {
|
||||
var likes []models.MicroLike
|
||||
microIDs := make([]uint, len(micros))
|
||||
for i, m := range micros {
|
||||
microIDs[i] = m.ID
|
||||
}
|
||||
database.DB.Where("micro_id IN ? AND user_id = ?", microIDs, userID).Find(&likes)
|
||||
for _, like := range likes {
|
||||
userLikes[like.MicroID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
type MicroResponse struct {
|
||||
models.Micro
|
||||
IsLiked bool `json:"is_liked"`
|
||||
}
|
||||
|
||||
responses := make([]MicroResponse, len(micros))
|
||||
for i, m := range micros {
|
||||
responses[i] = MicroResponse{
|
||||
Micro: m,
|
||||
IsLiked: userLikes[m.ID],
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": responses,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetMicro 获取单条微语
|
||||
func (h *MicroHandler) GetMicro(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var micro models.Micro
|
||||
if err := database.DB.Preload("User").First(µ, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取点赞状态
|
||||
userID, isLoggedIn := middleware.GetUserID(c)
|
||||
isLiked := false
|
||||
if isLoggedIn && userID > 0 {
|
||||
var like models.MicroLike
|
||||
if err := database.DB.Where("micro_id = ? AND user_id = ?", micro.ID, userID).First(&like).Error; err == nil {
|
||||
isLiked = true
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": micro,
|
||||
"is_liked": isLiked,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMicro 删除微语
|
||||
func (h *MicroHandler) DeleteMicro(c *gin.Context) {
|
||||
userID, _ := middleware.GetUserID(c)
|
||||
role, _ := c.Get("role")
|
||||
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var micro models.Micro
|
||||
if err := database.DB.First(µ, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查权限:本人或管理员可删除
|
||||
if micro.UserID != userID && role != "admin" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权限删除"})
|
||||
return
|
||||
}
|
||||
|
||||
// 软删除
|
||||
if err := database.DB.Delete(µ).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
// ToggleMicroLike 切换点赞状态
|
||||
func (h *MicroHandler) ToggleMicroLike(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查微语是否存在
|
||||
var micro models.Micro
|
||||
if err := database.DB.First(µ, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
|
||||
return
|
||||
}
|
||||
|
||||
userID, isLoggedIn := middleware.GetUserID(c)
|
||||
|
||||
var existingLike models.MicroLike
|
||||
var likeErr error
|
||||
|
||||
if isLoggedIn && userID > 0 {
|
||||
// 登录用户:按 user_id 查找
|
||||
likeErr = database.DB.Where("micro_id = ? AND user_id = ?", id, userID).First(&existingLike).Error
|
||||
} else {
|
||||
// 访客:按 IP Hash 查找
|
||||
ipHash := getIPHash(c)
|
||||
likeErr = database.DB.Where("micro_id = ? AND ip_hash = ?", id, ipHash).First(&existingLike).Error
|
||||
}
|
||||
|
||||
if likeErr == gorm.ErrRecordNotFound {
|
||||
// 创建点赞
|
||||
newLike := models.MicroLike{
|
||||
MicroID: uint(id),
|
||||
}
|
||||
if isLoggedIn && userID > 0 {
|
||||
newLike.UserID = &userID
|
||||
} else {
|
||||
newLike.IPHash = getIPHash(c)
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&newLike).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "点赞失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新点赞计数
|
||||
database.DB.Model(µ).Update("like_count", gorm.Expr("like_count + 1"))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"liked": true,
|
||||
"like_count": micro.LikeCount + 1,
|
||||
})
|
||||
} else if likeErr == nil {
|
||||
// 取消点赞
|
||||
if err := database.DB.Delete(&existingLike).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "取消点赞失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新点赞计数
|
||||
newCount := micro.LikeCount - 1
|
||||
if newCount < 0 {
|
||||
newCount = 0
|
||||
}
|
||||
database.DB.Model(µ).Update("like_count", newCount)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"liked": false,
|
||||
"like_count": newCount,
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetMicroHeatmap 获取热力图数据
|
||||
func (h *MicroHandler) GetMicroHeatmap(c *gin.Context) {
|
||||
yearStr := c.DefaultQuery("year", strconv.Itoa(time.Now().Year()))
|
||||
year, err := strconv.Atoi(yearStr)
|
||||
if err != nil {
|
||||
year = time.Now().Year()
|
||||
}
|
||||
|
||||
// 计算年份的起止日期
|
||||
startDate := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
endDate := time.Date(year, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
|
||||
// 按日期分组统计
|
||||
type DayCount struct {
|
||||
Date string `json:"date"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
var results []DayCount
|
||||
|
||||
database.DB.Model(&models.Micro{}).
|
||||
Select("DATE(created_at) as date, COUNT(*) as count").
|
||||
Where("created_at >= ? AND created_at <= ? AND deleted_at IS NULL", startDate, endDate).
|
||||
Group("DATE(created_at)").
|
||||
Find(&results)
|
||||
|
||||
// 转换为 map
|
||||
data := make(map[string]int)
|
||||
for _, r := range results {
|
||||
data[r.Date] = r.Count
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"year": year,
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMicroStats 获取统计数据
|
||||
func (h *MicroHandler) GetMicroStats(c *gin.Context) {
|
||||
var totalMicros int64
|
||||
var totalLikes int64
|
||||
var totalComments int64
|
||||
var monthMicros int64
|
||||
|
||||
// 总微语数
|
||||
database.DB.Model(&models.Micro{}).Count(&totalMicros)
|
||||
|
||||
// 总点赞数
|
||||
database.DB.Model(&models.MicroLike{}).Count(&totalLikes)
|
||||
|
||||
// 总评论数
|
||||
database.DB.Model(&models.MicroComment{}).Count(&totalComments)
|
||||
|
||||
// 本月发布数
|
||||
now := time.Now()
|
||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
database.DB.Model(&models.Micro{}).Where("created_at >= ? AND deleted_at IS NULL", monthStart).Count(&monthMicros)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"total_micros": totalMicros,
|
||||
"total_likes": totalLikes,
|
||||
"total_comments": totalComments,
|
||||
"month_micros": monthMicros,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMicroTags 获取热门标签
|
||||
func (h *MicroHandler) GetMicroTags(c *gin.Context) {
|
||||
// 获取所有微语的标签
|
||||
var micros []models.Micro
|
||||
database.DB.Where("tags IS NOT NULL AND tags != '' AND deleted_at IS NULL").Select("tags").Find(µs)
|
||||
|
||||
// 统计标签使用次数
|
||||
tagCount := make(map[string]int)
|
||||
for _, micro := range micros {
|
||||
if micro.Tags == "" {
|
||||
continue
|
||||
}
|
||||
var tags []string
|
||||
if err := json.Unmarshal([]byte(micro.Tags), &tags); err == nil {
|
||||
for _, tag := range tags {
|
||||
tagCount[tag]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为排序后的列表
|
||||
type TagItem struct {
|
||||
Name string `json:"name"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
var tags []TagItem
|
||||
for name, count := range tagCount {
|
||||
tags = append(tags, TagItem{Name: name, Count: count})
|
||||
}
|
||||
|
||||
// 按使用次数排序
|
||||
for i := 0; i < len(tags); i++ {
|
||||
for j := i + 1; j < len(tags); j++ {
|
||||
if tags[j].Count > tags[i].Count {
|
||||
tags[i], tags[j] = tags[j], tags[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 只返回前20个
|
||||
if len(tags) > 20 {
|
||||
tags = tags[:20]
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"tags": tags,
|
||||
})
|
||||
}
|
||||
|
||||
// getIPHash 获取 IP 的哈希值
|
||||
func getIPHash(c *gin.Context) string {
|
||||
ip := c.ClientIP()
|
||||
hash := sha256.Sum256([]byte(ip + "micro-salt"))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// CreateMicroCommentRequest 创建微语评论请求
|
||||
type CreateMicroCommentRequest struct {
|
||||
MicroID uint `json:"micro_id" binding:"required"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
Content string `json:"content" binding:"required,min=1,max=2000"`
|
||||
}
|
||||
|
||||
// CreateMicroComment 创建微语评论
|
||||
func (h *MicroHandler) CreateMicroComment(c *gin.Context) {
|
||||
userID, isLoggedIn := middleware.GetUserID(c)
|
||||
if !isLoggedIn || userID == 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请登录后再评论"})
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateMicroCommentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查微语是否存在
|
||||
var micro models.Micro
|
||||
if err := database.DB.First(µ, req.MicroID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
comment := models.MicroComment{
|
||||
MicroID: req.MicroID,
|
||||
UserID: userID,
|
||||
ParentID: req.ParentID,
|
||||
Content: req.Content,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&comment).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "评论失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新评论计数
|
||||
database.DB.Model(µ).Update("comment_count", gorm.Expr("comment_count + 1"))
|
||||
|
||||
// 加载用户信息
|
||||
database.DB.Preload("User").First(&comment, comment.ID)
|
||||
|
||||
c.JSON(http.StatusCreated, comment)
|
||||
}
|
||||
|
||||
// GetMicroComments 获取微语评论列表
|
||||
func (h *MicroHandler) GetMicroComments(c *gin.Context) {
|
||||
microIDStr := c.Query("micro_id")
|
||||
if microIDStr == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "micro_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
microID, err := strconv.ParseUint(microIDStr, 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid micro_id"})
|
||||
return
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取顶级评论(非回复)
|
||||
var comments []models.MicroComment
|
||||
var total int64
|
||||
|
||||
query := database.DB.Model(&models.MicroComment{}).
|
||||
Where("micro_id = ? AND parent_id IS NULL", microID)
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
if err := query.
|
||||
Preload("User").
|
||||
Preload("Replies.User").
|
||||
Order("created_at DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Find(&comments).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取评论失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 手动加载每个评论的用户信息
|
||||
for i := range comments {
|
||||
if comments[i].UserID > 0 {
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, comments[i].UserID).Error; err == nil {
|
||||
comments[i].User = user
|
||||
}
|
||||
}
|
||||
// 加载回复的用户信息
|
||||
for j := range comments[i].Replies {
|
||||
if comments[i].Replies[j].UserID > 0 {
|
||||
var replyUser models.User
|
||||
if err := database.DB.First(&replyUser, comments[i].Replies[j].UserID).Error; err == nil {
|
||||
comments[i].Replies[j].User = replyUser
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": comments,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMicroComment 删除微语评论
|
||||
func (h *MicroHandler) DeleteMicroComment(c *gin.Context) {
|
||||
userID, _ := middleware.GetUserID(c)
|
||||
role, _ := c.Get("role")
|
||||
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid comment id"})
|
||||
return
|
||||
}
|
||||
|
||||
var comment models.MicroComment
|
||||
if err := database.DB.First(&comment, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查权限:本人或管理员可删除
|
||||
if comment.UserID != userID && role != "admin" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
|
||||
return
|
||||
}
|
||||
|
||||
// 软删除
|
||||
if err := database.DB.Delete(&comment).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新评论计数
|
||||
database.DB.Model(&models.Micro{}).Where("id = ?", comment.MicroID).
|
||||
Update("comment_count", gorm.Expr("comment_count - 1"))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "评论已删除"})
|
||||
}
|
||||
@@ -62,3 +62,41 @@ type PostMeta struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Micro 微语模型
|
||||
type Micro struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
Content string `json:"content" gorm:"type:text;not null"`
|
||||
Images string `json:"images" gorm:"type:text"` // JSON array of image URLs
|
||||
Tags string `json:"tags" gorm:"type:text"` // JSON array of tags
|
||||
LikeCount int `json:"like_count" gorm:"default:0"`
|
||||
CommentCount int `json:"comment_count" gorm:"default:0"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
// MicroLike 微语点赞模型
|
||||
type MicroLike struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
MicroID uint `json:"micro_id" gorm:"uniqueIndex:idx_micro_user;not null"`
|
||||
UserID *uint `json:"user_id" gorm:"uniqueIndex:idx_micro_user;index"`
|
||||
IPHash string `json:"-" gorm:"uniqueIndex:idx_micro_ip;size:64"`
|
||||
}
|
||||
|
||||
// MicroComment 微语评论模型
|
||||
type MicroComment struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
MicroID uint `json:"micro_id" gorm:"index;not null"`
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
ParentID *uint `json:"parent_id" gorm:"index"`
|
||||
Content string `json:"content" gorm:"type:text;not null"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
Replies []MicroComment `json:"replies,omitempty" gorm:"foreignKey:ParentID"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user