This commit is contained in:
2025-12-08 09:08:10 +08:00
parent 976b6dec2d
commit 380eae3da5
5 changed files with 1563 additions and 0 deletions

View File

@@ -0,0 +1,483 @@
/**
* package api
* 作用:处理朋友圈相关的 HTTP 请求接口
*/
package api
import (
"strconv"
"xk-websocket-v2/internal/model"
"xk-websocket-v2/internal/service"
"xk-websocket-v2/internal/utils"
"github.com/gin-gonic/gin"
)
// ==========================================
// 动态相关接口
// ==========================================
/**
* CreateMomentHandler
* 功能:发布动态
* 路径POST /api/moments
*/
func CreateMomentHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
var req model.CreateMomentReq
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "参数错误")
return
}
// 校验内容不能为空(除非有媒体)
if req.Content == "" && len(req.MediaURLs) == 0 {
utils.BadRequest(c, "内容不能为空")
return
}
moment, err := service.MomentSvc.CreateMoment(userID, &req)
if err != nil {
utils.InternalError(c, err.Error())
return
}
utils.SuccessWithData(c, moment, "发布成功")
}
/**
* DeleteMomentHandler
* 功能:删除动态
* 路径DELETE /api/moments/:id
*/
func DeleteMomentHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
if err := service.MomentSvc.DeleteMoment(userID, uint(momentID)); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Success(c, "删除成功")
}
/**
* GetMomentDetailHandler
* 功能:获取动态详情
* 路径GET /api/moments/:id
*/
func GetMomentDetailHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
moment, err := service.MomentSvc.GetMomentDetail(userID, uint(momentID))
if err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.SuccessWithData(c, moment, "获取成功")
}
/**
* GetMomentsHandler
* 功能:获取好友动态列表
* 路径GET /api/moments
*/
func GetMomentsHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
moments, total, err := service.MomentSvc.GetFriendsMoments(userID, page, pageSize)
if err != nil {
utils.InternalError(c, err.Error())
return
}
// 确保返回空数组而不是null
if moments == nil {
moments = []model.Moment{}
}
utils.SuccessWithData(c, gin.H{
"data": moments,
"total": total,
"page": page,
"size": pageSize,
}, "获取成功")
}
/**
* GetUserMomentsHandler
* 功能:获取指定用户的动态列表
* 路径GET /api/moments/user/:user_id
*/
func GetUserMomentsHandler(c *gin.Context) {
viewerID := c.GetString("user_id")
if viewerID == "" {
utils.Unauthorized(c, "未登录")
return
}
targetUserID := c.Param("user_id")
if targetUserID == "" {
utils.BadRequest(c, "用户ID不能为空")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
moments, total, err := service.MomentSvc.GetUserMoments(viewerID, targetUserID, page, pageSize)
if err != nil {
utils.InternalError(c, err.Error())
return
}
if moments == nil {
moments = []model.Moment{}
}
utils.SuccessWithData(c, gin.H{
"data": moments,
"total": total,
"page": page,
"size": pageSize,
}, "获取成功")
}
// ==========================================
// 点赞相关接口
// ==========================================
/**
* LikeMomentHandler
* 功能:点赞动态
* 路径POST /api/moments/:id/like
*/
func LikeMomentHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
if err := service.MomentSvc.LikeMoment(userID, uint(momentID)); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Success(c, "点赞成功")
}
/**
* UnlikeMomentHandler
* 功能:取消点赞
* 路径DELETE /api/moments/:id/like
*/
func UnlikeMomentHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
if err := service.MomentSvc.UnlikeMoment(userID, uint(momentID)); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Success(c, "取消点赞成功")
}
/**
* GetMomentLikesHandler
* 功能:获取动态的点赞列表
* 路径GET /api/moments/:id/likes
*/
func GetMomentLikesHandler(c *gin.Context) {
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
likes, err := service.MomentSvc.GetMomentLikes(uint(momentID))
if err != nil {
utils.InternalError(c, err.Error())
return
}
if likes == nil {
likes = []model.MomentLike{}
}
utils.SuccessWithData(c, likes, "获取成功")
}
// ==========================================
// 评论相关接口
// ==========================================
/**
* CreateCommentHandler
* 功能:发表评论
* 路径POST /api/moments/:id/comments
*/
func CreateCommentHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
var req model.CreateCommentReq
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "参数错误")
return
}
if req.Content == "" {
utils.BadRequest(c, "评论内容不能为空")
return
}
comment, err := service.MomentSvc.CreateComment(userID, uint(momentID), &req)
if err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.SuccessWithData(c, comment, "评论成功")
}
/**
* DeleteCommentHandler
* 功能:删除评论
* 路径DELETE /api/moments/comments/:id
*/
func DeleteCommentHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
commentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的评论ID")
return
}
if err := service.MomentSvc.DeleteComment(userID, uint(commentID)); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Success(c, "删除成功")
}
/**
* GetMomentCommentsHandler
* 功能:获取动态的评论列表
* 路径GET /api/moments/:id/comments
*/
func GetMomentCommentsHandler(c *gin.Context) {
momentID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
utils.BadRequest(c, "无效的动态ID")
return
}
comments, err := service.MomentSvc.GetMomentComments(uint(momentID))
if err != nil {
utils.InternalError(c, err.Error())
return
}
if comments == nil {
comments = []model.MomentComment{}
}
utils.SuccessWithData(c, comments, "获取成功")
}
// ==========================================
// 通知相关接口
// ==========================================
/**
* GetMomentNotificationsHandler
* 功能:获取朋友圈通知列表
* 路径GET /api/moments/notifications
*/
func GetMomentNotificationsHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
notifications, total, err := service.MomentSvc.GetNotifications(userID, page, pageSize)
if err != nil {
utils.InternalError(c, err.Error())
return
}
if notifications == nil {
notifications = []model.MomentNotification{}
}
utils.SuccessWithData(c, gin.H{
"data": notifications,
"total": total,
"page": page,
"size": pageSize,
}, "获取成功")
}
/**
* MarkNotificationsReadHandler
* 功能:标记通知为已读
* 路径POST /api/moments/notifications/read
*/
func MarkNotificationsReadHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
var req struct {
IDs []uint `json:"ids"`
All bool `json:"all"` // 是否标记全部已读
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "参数错误")
return
}
var err error
if req.All {
err = service.MomentSvc.MarkAllNotificationsRead(userID)
} else if len(req.IDs) > 0 {
err = service.MomentSvc.MarkNotificationsRead(userID, req.IDs)
} else {
utils.BadRequest(c, "请提供通知ID或设置all为true")
return
}
if err != nil {
utils.InternalError(c, err.Error())
return
}
utils.Success(c, "标记成功")
}
/**
* GetUnreadCountHandler
* 功能:获取未读通知数量
* 路径GET /api/moments/notifications/unread-count
*/
func GetUnreadCountHandler(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
utils.Unauthorized(c, "未登录")
return
}
count, err := service.MomentSvc.GetUnreadCount(userID)
if err != nil {
utils.InternalError(c, err.Error())
return
}
utils.SuccessWithData(c, gin.H{"count": count}, "获取成功")
}
// ==========================================
// 路由注册函数
// ==========================================
/**
* RegisterMomentRoutes
* 功能:注册朋友圈相关路由
*/
func RegisterMomentRoutes(r *gin.RouterGroup) {
moments := r.Group("/moments")
{
// 动态相关
moments.POST("", CreateMomentHandler)
moments.GET("", GetMomentsHandler)
moments.GET("/:id", GetMomentDetailHandler)
moments.DELETE("/:id", DeleteMomentHandler)
moments.GET("/user/:user_id", GetUserMomentsHandler)
// 点赞相关
moments.POST("/:id/like", LikeMomentHandler)
moments.DELETE("/:id/like", UnlikeMomentHandler)
moments.GET("/:id/likes", GetMomentLikesHandler)
// 评论相关
moments.POST("/:id/comments", CreateCommentHandler)
moments.GET("/:id/comments", GetMomentCommentsHandler)
moments.DELETE("/comments/:id", DeleteCommentHandler)
// 通知相关
moments.GET("/notifications", GetMomentNotificationsHandler)
moments.POST("/notifications/read", MarkNotificationsReadHandler)
moments.GET("/notifications/unread-count", GetUnreadCountHandler)
}
}

View File

@@ -570,6 +570,224 @@ type SendCodeReq struct {
Type string `json:"type" binding:"required,oneof=email sms"`
}
// ==========================================
// 朋友圈相关实体 (Moments)
// ==========================================
/**
* Moment
* 对应数据库表moments
* 作用:朋友圈动态表
*/
type Moment struct {
// 动态ID
ID uint `gorm:"primaryKey;comment:动态ID" json:"id"`
// 发布者ID
UserID string `gorm:"type:varchar(100);index;comment:发布者ID" json:"user_id"`
// 文字内容
Content string `gorm:"type:text;comment:文字内容" json:"content"`
// 媒体类型: 0=纯文字 1=图片 2=视频
MediaType int8 `gorm:"type:tinyint;default:0;comment:媒体类型" json:"media_type"`
// 媒体URL列表(JSON数组)
MediaURLs string `gorm:"type:text;comment:媒体URL列表" json:"media_urls"`
// 位置信息
Location string `gorm:"type:varchar(255);comment:位置信息" json:"location"`
// 可见性: 0=公开 1=仅好友 2=部分好友可见 3=部分好友不可见
Visibility int8 `gorm:"type:tinyint;default:0;comment:可见性" json:"visibility"`
// 可见/不可见用户ID列表(JSON数组)
VisibleUserIDs string `gorm:"type:text;comment:可见用户ID列表" json:"visible_user_ids"`
// @的用户ID列表(JSON数组)
MentionUserIDs string `gorm:"type:text;comment:@的用户ID列表" json:"mention_user_ids"`
// 话题标签(JSON数组)
TopicTags string `gorm:"type:varchar(500);comment:话题标签" json:"topic_tags"`
// 点赞数
LikeCount int `gorm:"type:int;default:0;comment:点赞数" json:"like_count"`
// 评论数
CommentCount int `gorm:"type:int;default:0;comment:评论数" json:"comment_count"`
// 是否已删除
IsDeleted bool `gorm:"type:tinyint(1);default:0;comment:是否已删除" json:"-"`
// 创建时间
CreatedAt time.Time `gorm:"autoCreateTime;comment:创建时间" json:"created_at"`
// 更新时间
UpdatedAt time.Time `gorm:"autoUpdateTime;comment:更新时间" json:"updated_at"`
// 关联字段(不存储在数据库中)
User *User `gorm:"-" json:"user,omitempty"`
IsLiked bool `gorm:"-" json:"is_liked"`
Likes []MomentLike `gorm:"-" json:"likes,omitempty"`
Comments []MomentComment `gorm:"-" json:"comments,omitempty"`
}
// TableName 指定表名
func (Moment) TableName() string {
return "moments"
}
/**
* MomentLike
* 对应数据库表moment_likes
* 作用:动态点赞表
*/
type MomentLike struct {
// 主键ID
ID uint `gorm:"primaryKey;comment:主键ID" json:"id"`
// 动态ID
MomentID uint `gorm:"type:bigint;index;comment:动态ID" json:"moment_id"`
// 点赞用户ID
UserID string `gorm:"type:varchar(100);index;comment:点赞用户ID" json:"user_id"`
// 创建时间
CreatedAt time.Time `gorm:"autoCreateTime;comment:创建时间" json:"created_at"`
// 关联字段(不存储在数据库中)
User *User `gorm:"-" json:"user,omitempty"`
}
// TableName 指定表名
func (MomentLike) TableName() string {
return "moment_likes"
}
/**
* MomentComment
* 对应数据库表moment_comments
* 作用:动态评论表
*/
type MomentComment struct {
// 评论ID
ID uint `gorm:"primaryKey;comment:评论ID" json:"id"`
// 动态ID
MomentID uint `gorm:"type:bigint;index;comment:动态ID" json:"moment_id"`
// 评论者ID
UserID string `gorm:"type:varchar(100);index;comment:评论者ID" json:"user_id"`
// 回复的评论ID(NULL为直接评论)
ReplyToCommentID *uint `gorm:"type:bigint;index;comment:回复的评论ID" json:"reply_to_comment_id"`
// 回复的用户ID
ReplyToUserID string `gorm:"type:varchar(100);comment:回复的用户ID" json:"reply_to_user_id"`
// 评论内容
Content string `gorm:"type:text;comment:评论内容" json:"content"`
// 是否已删除
IsDeleted bool `gorm:"type:tinyint(1);default:0;comment:是否已删除" json:"-"`
// 创建时间
CreatedAt time.Time `gorm:"autoCreateTime;comment:创建时间" json:"created_at"`
// 关联字段(不存储在数据库中)
User *User `gorm:"-" json:"user,omitempty"`
ReplyToUser *User `gorm:"-" json:"reply_to_user,omitempty"`
}
// TableName 指定表名
func (MomentComment) TableName() string {
return "moment_comments"
}
/**
* MomentNotification
* 对应数据库表moment_notifications
* 作用:朋友圈通知表
*/
type MomentNotification struct {
// 通知ID
ID uint `gorm:"primaryKey;comment:通知ID" json:"id"`
// 接收通知的用户ID
UserID string `gorm:"type:varchar(100);index;comment:接收通知的用户ID" json:"user_id"`
// 触发通知的用户ID
FromUserID string `gorm:"type:varchar(100);comment:触发通知的用户ID" json:"from_user_id"`
// 关联的动态ID
MomentID uint `gorm:"type:bigint;index;comment:关联的动态ID" json:"moment_id"`
// 通知类型: 1=点赞 2=评论 3=回复 4=@提及
Type int8 `gorm:"type:tinyint;comment:通知类型" json:"type"`
// 关联的评论ID
CommentID *uint `gorm:"type:bigint;comment:关联的评论ID" json:"comment_id"`
// 是否已读
IsRead bool `gorm:"type:tinyint(1);default:0;index;comment:是否已读" json:"is_read"`
// 创建时间
CreatedAt time.Time `gorm:"autoCreateTime;index;comment:创建时间" json:"created_at"`
// 关联字段(不存储在数据库中)
FromUser *User `gorm:"-" json:"from_user,omitempty"`
Moment *Moment `gorm:"-" json:"moment,omitempty"`
Comment *MomentComment `gorm:"-" json:"comment,omitempty"`
}
// TableName 指定表名
func (MomentNotification) TableName() string {
return "moment_notifications"
}
// ==========================================
// 朋友圈相关 DTO
// ==========================================
/**
* CreateMomentReq
* 作用:发布动态请求参数
*/
type CreateMomentReq struct {
// 文字内容
Content string `json:"content"`
// 媒体类型: 0=纯文字 1=图片 2=视频
MediaType int8 `json:"media_type"`
// 媒体URL列表
MediaURLs []string `json:"media_urls"`
// 位置信息
Location string `json:"location,omitempty"`
// 可见性: 0=公开 1=仅好友 2=部分好友可见 3=部分好友不可见
Visibility int8 `json:"visibility"`
// 可见/不可见用户ID列表
VisibleUserIDs []string `json:"visible_user_ids,omitempty"`
// @的用户ID列表
MentionUserIDs []string `json:"mention_user_ids,omitempty"`
// 话题标签
TopicTags []string `json:"topic_tags,omitempty"`
}
/**
* CreateCommentReq
* 作用:发表评论请求参数
*/
type CreateCommentReq struct {
// 评论内容
Content string `json:"content" binding:"required"`
// 回复的评论ID(可选,回复评论时使用)
ReplyToCommentID *uint `json:"reply_to_comment_id,omitempty"`
}
/**
* MomentNotifPayload
* 作用WebSocket 推送朋友圈通知的数据结构
*/
type MomentNotifPayload struct {
// 通知类型: "like", "comment", "reply", "mention"
Type string `json:"type"`
// 动态ID
MomentID uint `json:"moment_id"`
// 触发通知的用户
FromUser *User `json:"from_user"`
// 评论内容(评论/回复时使用)
Content string `json:"content,omitempty"`
// 评论ID
CommentID uint `json:"comment_id,omitempty"`
}
// 朋友圈通知类型常量
const (
MomentNotifTypeLike = 1 // 点赞
MomentNotifTypeComment = 2 // 评论
MomentNotifTypeReply = 3 // 回复
MomentNotifTypeMention = 4 // @提及
)
// 动态可见性常量
const (
MomentVisibilityPublic = 0 // 公开
MomentVisibilityFriendsOnly = 1 // 仅好友可见
MomentVisibilityPartialVisible = 2 // 部分好友可见
MomentVisibilityPartialHidden = 3 // 部分好友不可见
)
// 媒体类型常量
const (
MomentMediaTypeText = 0 // 纯文字
MomentMediaTypeImage = 1 // 图片
MomentMediaTypeVideo = 2 // 视频
)
// ==========================================
// 常量定义
// ==========================================

View File

@@ -0,0 +1,769 @@
/**
* package service
* 作用:朋友圈业务逻辑服务
*/
package service
import (
"encoding/json"
"errors"
"log"
"xk-websocket-v2/internal/model"
"gorm.io/gorm"
)
// MomentService 朋友圈服务结构体
type MomentService struct {
DB *gorm.DB
}
// MomentSvc 全局单例
var MomentSvc *MomentService
/**
* InitMomentService
* 功能:初始化朋友圈服务
*/
func InitMomentService(db *gorm.DB) {
MomentSvc = &MomentService{DB: db}
}
// ==========================================
// 动态相关方法
// ==========================================
/**
* CreateMoment
* 功能:发布动态
*/
func (s *MomentService) CreateMoment(userID string, req *model.CreateMomentReq) (*model.Moment, error) {
// 序列化数组字段为 JSON
mediaURLsJSON, _ := json.Marshal(req.MediaURLs)
visibleUserIDsJSON, _ := json.Marshal(req.VisibleUserIDs)
mentionUserIDsJSON, _ := json.Marshal(req.MentionUserIDs)
topicTagsJSON, _ := json.Marshal(req.TopicTags)
moment := &model.Moment{
UserID: userID,
Content: req.Content,
MediaType: req.MediaType,
MediaURLs: string(mediaURLsJSON),
Location: req.Location,
Visibility: req.Visibility,
VisibleUserIDs: string(visibleUserIDsJSON),
MentionUserIDs: string(mentionUserIDsJSON),
TopicTags: string(topicTagsJSON),
}
if err := s.DB.Create(moment).Error; err != nil {
return nil, err
}
// 处理@用户通知
if len(req.MentionUserIDs) > 0 {
go s.createMentionNotifications(userID, moment.ID, req.MentionUserIDs)
}
// 加载用户信息
s.loadMomentUser(moment)
return moment, nil
}
/**
* DeleteMoment
* 功能:删除动态(软删除)
*/
func (s *MomentService) DeleteMoment(userID string, momentID uint) error {
result := s.DB.Model(&model.Moment{}).
Where("id = ? AND user_id = ?", momentID, userID).
Update("is_deleted", true)
if result.RowsAffected == 0 {
return errors.New("动态不存在或无权删除")
}
return result.Error
}
/**
* GetMomentDetail
* 功能:获取动态详情(含权限校验)
*/
func (s *MomentService) GetMomentDetail(viewerID string, momentID uint) (*model.Moment, error) {
var moment model.Moment
if err := s.DB.Where("id = ? AND is_deleted = ?", momentID, false).First(&moment).Error; err != nil {
return nil, err
}
// 检查可见性权限
if !s.checkVisibility(&moment, viewerID) {
return nil, errors.New("无权查看该动态")
}
// 加载关联数据
s.loadMomentUser(&moment)
s.loadMomentLikes(&moment, viewerID)
s.loadMomentComments(&moment)
return &moment, nil
}
/**
* GetFriendsMoments
* 功能:获取好友动态列表(含可见性过滤)
*/
func (s *MomentService) GetFriendsMoments(userID string, page, pageSize int) ([]model.Moment, int64, error) {
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 50 {
pageSize = 20
}
offset := (page - 1) * pageSize
// 获取好友ID列表
friendIDs := s.getFriendIDs(userID)
// 加上自己的ID可以看到自己的动态
friendIDs = append(friendIDs, userID)
var total int64
var moments []model.Moment
// 查询动态(包含公开和好友可见的)
query := s.DB.Model(&model.Moment{}).
Where("is_deleted = ?", false).
Where("user_id IN ?", friendIDs)
// 统计总数
query.Count(&total)
// 分页查询
if err := query.Order("created_at DESC").
Offset(offset).
Limit(pageSize).
Find(&moments).Error; err != nil {
return nil, 0, err
}
// 过滤可见性并加载关联数据
var visibleMoments []model.Moment
for i := range moments {
if s.checkVisibility(&moments[i], userID) {
s.loadMomentUser(&moments[i])
s.loadMomentLikes(&moments[i], userID)
s.loadMomentComments(&moments[i])
visibleMoments = append(visibleMoments, moments[i])
}
}
return visibleMoments, total, nil
}
/**
* GetUserMoments
* 功能:获取指定用户的动态列表
*/
func (s *MomentService) GetUserMoments(viewerID, targetUserID string, page, pageSize int) ([]model.Moment, int64, error) {
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 50 {
pageSize = 20
}
offset := (page - 1) * pageSize
var total int64
var moments []model.Moment
query := s.DB.Model(&model.Moment{}).
Where("user_id = ? AND is_deleted = ?", targetUserID, false)
query.Count(&total)
if err := query.Order("created_at DESC").
Offset(offset).
Limit(pageSize).
Find(&moments).Error; err != nil {
return nil, 0, err
}
// 过滤可见性并加载关联数据
var visibleMoments []model.Moment
for i := range moments {
if s.checkVisibility(&moments[i], viewerID) {
s.loadMomentUser(&moments[i])
s.loadMomentLikes(&moments[i], viewerID)
s.loadMomentComments(&moments[i])
visibleMoments = append(visibleMoments, moments[i])
}
}
return visibleMoments, total, nil
}
// ==========================================
// 点赞相关方法
// ==========================================
/**
* LikeMoment
* 功能:点赞动态
*/
func (s *MomentService) LikeMoment(userID string, momentID uint) error {
// 检查动态是否存在
var moment model.Moment
if err := s.DB.Where("id = ? AND is_deleted = ?", momentID, false).First(&moment).Error; err != nil {
return errors.New("动态不存在")
}
// 检查是否已点赞
var existingLike model.MomentLike
if err := s.DB.Where("moment_id = ? AND user_id = ?", momentID, userID).First(&existingLike).Error; err == nil {
return errors.New("已点赞过该动态")
}
// 开启事务
tx := s.DB.Begin()
// 创建点赞记录
like := model.MomentLike{
MomentID: momentID,
UserID: userID,
}
if err := tx.Create(&like).Error; err != nil {
tx.Rollback()
return err
}
// 更新动态点赞数
if err := tx.Model(&model.Moment{}).Where("id = ?", momentID).
UpdateColumn("like_count", gorm.Expr("like_count + 1")).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
// 发送通知(异步)
if moment.UserID != userID {
go s.createNotification(moment.UserID, userID, momentID, model.MomentNotifTypeLike, nil)
}
return nil
}
/**
* UnlikeMoment
* 功能:取消点赞
*/
func (s *MomentService) UnlikeMoment(userID string, momentID uint) error {
tx := s.DB.Begin()
// 删除点赞记录
result := tx.Where("moment_id = ? AND user_id = ?", momentID, userID).Delete(&model.MomentLike{})
if result.RowsAffected == 0 {
tx.Rollback()
return errors.New("未点赞过该动态")
}
// 更新动态点赞数
if err := tx.Model(&model.Moment{}).Where("id = ?", momentID).
UpdateColumn("like_count", gorm.Expr("like_count - 1")).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
/**
* GetMomentLikes
* 功能:获取动态的点赞列表
*/
func (s *MomentService) GetMomentLikes(momentID uint) ([]model.MomentLike, error) {
var likes []model.MomentLike
if err := s.DB.Where("moment_id = ?", momentID).
Order("created_at DESC").
Find(&likes).Error; err != nil {
return nil, err
}
// 加载用户信息
for i := range likes {
var user model.User
if err := s.DB.Where("id = ?", likes[i].UserID).First(&user).Error; err == nil {
user.Password = ""
likes[i].User = &user
}
}
return likes, nil
}
// ==========================================
// 评论相关方法
// ==========================================
/**
* CreateComment
* 功能:发表评论/回复
*/
func (s *MomentService) CreateComment(userID string, momentID uint, req *model.CreateCommentReq) (*model.MomentComment, error) {
// 检查动态是否存在
var moment model.Moment
if err := s.DB.Where("id = ? AND is_deleted = ?", momentID, false).First(&moment).Error; err != nil {
return nil, errors.New("动态不存在")
}
comment := &model.MomentComment{
MomentID: momentID,
UserID: userID,
Content: req.Content,
}
// 如果是回复评论
var replyToUserID string
if req.ReplyToCommentID != nil {
var replyComment model.MomentComment
if err := s.DB.Where("id = ? AND is_deleted = ?", *req.ReplyToCommentID, false).First(&replyComment).Error; err != nil {
return nil, errors.New("回复的评论不存在")
}
comment.ReplyToCommentID = req.ReplyToCommentID
comment.ReplyToUserID = replyComment.UserID
replyToUserID = replyComment.UserID
}
// 开启事务
tx := s.DB.Begin()
if err := tx.Create(comment).Error; err != nil {
tx.Rollback()
return nil, err
}
// 更新动态评论数
if err := tx.Model(&model.Moment{}).Where("id = ?", momentID).
UpdateColumn("comment_count", gorm.Expr("comment_count + 1")).Error; err != nil {
tx.Rollback()
return nil, err
}
tx.Commit()
// 发送通知(异步)
go func() {
if req.ReplyToCommentID != nil && replyToUserID != userID {
// 回复评论通知
s.createNotification(replyToUserID, userID, momentID, model.MomentNotifTypeReply, &comment.ID)
} else if moment.UserID != userID {
// 评论动态通知
s.createNotification(moment.UserID, userID, momentID, model.MomentNotifTypeComment, &comment.ID)
}
}()
// 加载用户信息
s.loadCommentUser(comment)
return comment, nil
}
/**
* DeleteComment
* 功能:删除评论
*/
func (s *MomentService) DeleteComment(userID string, commentID uint) error {
// 查找评论
var comment model.MomentComment
if err := s.DB.Where("id = ? AND is_deleted = ?", commentID, false).First(&comment).Error; err != nil {
return errors.New("评论不存在")
}
// 检查权限(评论者或动态作者可以删除)
var moment model.Moment
s.DB.Where("id = ?", comment.MomentID).First(&moment)
if comment.UserID != userID && moment.UserID != userID {
return errors.New("无权删除该评论")
}
// 开启事务
tx := s.DB.Begin()
// 软删除评论
if err := tx.Model(&comment).Update("is_deleted", true).Error; err != nil {
tx.Rollback()
return err
}
// 更新动态评论数
if err := tx.Model(&model.Moment{}).Where("id = ?", comment.MomentID).
UpdateColumn("comment_count", gorm.Expr("comment_count - 1")).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
/**
* GetMomentComments
* 功能:获取动态的评论列表
*/
func (s *MomentService) GetMomentComments(momentID uint) ([]model.MomentComment, error) {
var comments []model.MomentComment
if err := s.DB.Where("moment_id = ? AND is_deleted = ?", momentID, false).
Order("created_at ASC").
Find(&comments).Error; err != nil {
return nil, err
}
// 加载用户信息
for i := range comments {
s.loadCommentUser(&comments[i])
}
return comments, nil
}
// ==========================================
// 通知相关方法
// ==========================================
/**
* GetNotifications
* 功能:获取通知列表
*/
func (s *MomentService) GetNotifications(userID string, page, pageSize int) ([]model.MomentNotification, int64, error) {
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 50 {
pageSize = 20
}
offset := (page - 1) * pageSize
var total int64
var notifications []model.MomentNotification
query := s.DB.Model(&model.MomentNotification{}).Where("user_id = ?", userID)
query.Count(&total)
if err := query.Order("created_at DESC").
Offset(offset).
Limit(pageSize).
Find(&notifications).Error; err != nil {
return nil, 0, err
}
// 加载关联数据
for i := range notifications {
s.loadNotificationData(&notifications[i])
}
return notifications, total, nil
}
/**
* MarkNotificationsRead
* 功能:标记通知为已读
*/
func (s *MomentService) MarkNotificationsRead(userID string, ids []uint) error {
return s.DB.Model(&model.MomentNotification{}).
Where("user_id = ? AND id IN ?", userID, ids).
Update("is_read", true).Error
}
/**
* MarkAllNotificationsRead
* 功能:标记所有通知为已读
*/
func (s *MomentService) MarkAllNotificationsRead(userID string) error {
return s.DB.Model(&model.MomentNotification{}).
Where("user_id = ? AND is_read = ?", userID, false).
Update("is_read", true).Error
}
/**
* GetUnreadCount
* 功能:获取未读通知数量
*/
func (s *MomentService) GetUnreadCount(userID string) (int64, error) {
var count int64
err := s.DB.Model(&model.MomentNotification{}).
Where("user_id = ? AND is_read = ?", userID, false).
Count(&count).Error
return count, err
}
// ==========================================
// 内部辅助方法
// ==========================================
/**
* checkVisibility
* 功能:检查动态对某用户是否可见
*/
func (s *MomentService) checkVisibility(moment *model.Moment, viewerID string) bool {
// 作者自己总是可见
if moment.UserID == viewerID {
return true
}
switch moment.Visibility {
case model.MomentVisibilityPublic:
// 公开,所有人可见
return true
case model.MomentVisibilityFriendsOnly:
// 仅好友可见
return s.isFriend(moment.UserID, viewerID)
case model.MomentVisibilityPartialVisible:
// 部分好友可见
var visibleIDs []string
json.Unmarshal([]byte(moment.VisibleUserIDs), &visibleIDs)
for _, id := range visibleIDs {
if id == viewerID {
return true
}
}
return false
case model.MomentVisibilityPartialHidden:
// 部分好友不可见
if !s.isFriend(moment.UserID, viewerID) {
return false
}
var hiddenIDs []string
json.Unmarshal([]byte(moment.VisibleUserIDs), &hiddenIDs)
for _, id := range hiddenIDs {
if id == viewerID {
return false
}
}
return true
default:
return false
}
}
/**
* isFriend
* 功能:检查两个用户是否是好友关系
*/
func (s *MomentService) isFriend(userID1, userID2 string) bool {
var count int64
s.DB.Model(&model.UserContact{}).
Where("user_id = ? AND contact_id = ?", userID1, userID2).
Count(&count)
return count > 0
}
/**
* getFriendIDs
* 功能获取用户的所有好友ID
*/
func (s *MomentService) getFriendIDs(userID string) []string {
var contacts []model.UserContact
s.DB.Where("user_id = ?", userID).Find(&contacts)
ids := make([]string, 0, len(contacts))
for _, c := range contacts {
ids = append(ids, c.ContactID)
}
return ids
}
/**
* loadMomentUser
* 功能:加载动态的发布者信息
*/
func (s *MomentService) loadMomentUser(moment *model.Moment) {
var user model.User
if err := s.DB.Where("id = ?", moment.UserID).First(&user).Error; err == nil {
user.Password = ""
moment.User = &user
}
}
/**
* loadMomentLikes
* 功能:加载动态的点赞列表,并判断当前用户是否已点赞
*/
func (s *MomentService) loadMomentLikes(moment *model.Moment, viewerID string) {
var likes []model.MomentLike
s.DB.Where("moment_id = ?", moment.ID).
Order("created_at DESC").
Limit(10). // 只加载最近10个点赞
Find(&likes)
for i := range likes {
var user model.User
if err := s.DB.Where("id = ?", likes[i].UserID).First(&user).Error; err == nil {
user.Password = ""
likes[i].User = &user
}
if likes[i].UserID == viewerID {
moment.IsLiked = true
}
}
moment.Likes = likes
// 如果没有在前10个点赞中找到再单独查询是否已点赞
if !moment.IsLiked && viewerID != "" {
var count int64
s.DB.Model(&model.MomentLike{}).
Where("moment_id = ? AND user_id = ?", moment.ID, viewerID).
Count(&count)
moment.IsLiked = count > 0
}
}
/**
* loadMomentComments
* 功能:加载动态的评论列表
*/
func (s *MomentService) loadMomentComments(moment *model.Moment) {
var comments []model.MomentComment
s.DB.Where("moment_id = ? AND is_deleted = ?", moment.ID, false).
Order("created_at ASC").
Find(&comments)
for i := range comments {
s.loadCommentUser(&comments[i])
}
moment.Comments = comments
}
/**
* loadCommentUser
* 功能:加载评论的用户信息
*/
func (s *MomentService) loadCommentUser(comment *model.MomentComment) {
var user model.User
if err := s.DB.Where("id = ?", comment.UserID).First(&user).Error; err == nil {
user.Password = ""
comment.User = &user
}
if comment.ReplyToUserID != "" {
var replyUser model.User
if err := s.DB.Where("id = ?", comment.ReplyToUserID).First(&replyUser).Error; err == nil {
replyUser.Password = ""
comment.ReplyToUser = &replyUser
}
}
}
/**
* loadNotificationData
* 功能:加载通知的关联数据
*/
func (s *MomentService) loadNotificationData(notif *model.MomentNotification) {
// 加载发送者信息
var fromUser model.User
if err := s.DB.Where("id = ?", notif.FromUserID).First(&fromUser).Error; err == nil {
fromUser.Password = ""
notif.FromUser = &fromUser
}
// 加载动态信息
var moment model.Moment
if err := s.DB.Where("id = ?", notif.MomentID).First(&moment).Error; err == nil {
notif.Moment = &moment
}
// 加载评论信息
if notif.CommentID != nil {
var comment model.MomentComment
if err := s.DB.Where("id = ?", *notif.CommentID).First(&comment).Error; err == nil {
notif.Comment = &comment
}
}
}
/**
* createNotification
* 功能:创建通知并推送
*/
func (s *MomentService) createNotification(toUserID, fromUserID string, momentID uint, notifType int8, commentID *uint) {
notif := &model.MomentNotification{
UserID: toUserID,
FromUserID: fromUserID,
MomentID: momentID,
Type: notifType,
CommentID: commentID,
}
if err := s.DB.Create(notif).Error; err != nil {
log.Printf("❌ 创建朋友圈通知失败: %v", err)
return
}
// 通过 WebSocket 推送实时通知
s.sendRealtimeNotification(notif)
}
/**
* createMentionNotifications
* 功能:为@的用户创建通知
*/
func (s *MomentService) createMentionNotifications(fromUserID string, momentID uint, mentionUserIDs []string) {
for _, userID := range mentionUserIDs {
if userID != fromUserID {
s.createNotification(userID, fromUserID, momentID, model.MomentNotifTypeMention, nil)
}
}
}
/**
* sendRealtimeNotification
* 功能:通过 WebSocket 推送实时通知
*/
func (s *MomentService) sendRealtimeNotification(notif *model.MomentNotification) {
if ChatSvc == nil {
return
}
// 加载关联数据
s.loadNotificationData(notif)
// 构建通知类型字符串
var typeStr string
switch notif.Type {
case model.MomentNotifTypeLike:
typeStr = "like"
case model.MomentNotifTypeComment:
typeStr = "comment"
case model.MomentNotifTypeReply:
typeStr = "reply"
case model.MomentNotifTypeMention:
typeStr = "mention"
}
// 构建推送消息
payload := model.MomentNotifPayload{
Type: typeStr,
MomentID: notif.MomentID,
FromUser: notif.FromUser,
}
if notif.Comment != nil {
payload.Content = notif.Comment.Content
payload.CommentID = notif.Comment.ID
}
pushMsg := model.WsPayload{
RequestType: "moment_notification",
Data: payload,
}
msgBytes, _ := json.Marshal(pushMsg)
ChatSvc.DispatchMessage(notif.UserID, msgBytes)
log.Printf("📢 [朋友圈] 推送通知: To=%s Type=%s", notif.UserID, typeStr)
}