diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go
index 7701289..835b467 100644
--- a/server/cmd/server/main.go
+++ b/server/cmd/server/main.go
@@ -33,7 +33,6 @@ func main() {
authHandler := handlers.NewAuthHandler(jwtManager)
commentHandler := handlers.NewCommentHandler()
likeHandler := handlers.NewLikeHandler()
- microHandler := handlers.NewMicroHandler()
// 创建路由
r := gin.New()
@@ -71,15 +70,6 @@ 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))
@@ -91,14 +81,6 @@ 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)
}
// 管理员接口
diff --git a/server/internal/database/database.go b/server/internal/database/database.go
index f0c853f..c7da001 100644
--- a/server/internal/database/database.go
+++ b/server/internal/database/database.go
@@ -54,9 +54,6 @@ func autoMigrate() error {
&models.Like{},
&models.LikeCount{},
&models.PostMeta{},
- &models.Micro{},
- &models.MicroLike{},
- &models.MicroComment{},
)
}
diff --git a/server/internal/handlers/micro.go b/server/internal/handlers/micro.go
deleted file mode 100644
index acc1220..0000000
--- a/server/internal/handlers/micro.go
+++ /dev/null
@@ -1,595 +0,0 @@
-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": "评论已删除"})
-}
\ No newline at end of file
diff --git a/server/internal/models/models.go b/server/internal/models/models.go
index 8e0769c..87829e1 100644
--- a/server/internal/models/models.go
+++ b/server/internal/models/models.go
@@ -62,41 +62,3 @@ 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"`
-}
diff --git a/src/components/HeatmapCalendar.vue b/src/components/HeatmapCalendar.vue
deleted file mode 100644
index 9be9a28..0000000
--- a/src/components/HeatmapCalendar.vue
+++ /dev/null
@@ -1,230 +0,0 @@
-
- 加载中... 暂无微语,来发布第一条吧!{{ title }}
-
-
-
-
-
-
-
-
统计
- 热门标签
-
-
分享生活点滴,记录灵感瞬间
-
评论
- - -- 登录 后参与评论 -
-暂无评论
-