pyq
This commit is contained in:
@@ -101,6 +101,10 @@ func initDB() *gorm.DB {
|
||||
&model.Attachment{},
|
||||
&model.ApiRequestLog{},
|
||||
&model.LoginLog{},
|
||||
&model.Moment{},
|
||||
&model.MomentLike{},
|
||||
&model.MomentComment{},
|
||||
&model.MomentNotification{},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 数据库迁移失败: %v", err)
|
||||
@@ -122,6 +126,10 @@ func initDB() *gorm.DB {
|
||||
{"attachments", "附件表,记录上传的文件信息"},
|
||||
{"api_request_logs", "接口请求日志表"},
|
||||
{"login_logs", "登录日志表"},
|
||||
{"moments", "朋友圈动态表"},
|
||||
{"moment_likes", "动态点赞表"},
|
||||
{"moment_comments", "动态评论表"},
|
||||
{"moment_notifications", "朋友圈通知表"},
|
||||
}
|
||||
|
||||
for _, tc := range tableComments {
|
||||
@@ -215,6 +223,7 @@ func main() {
|
||||
service.InitRoomService(db) // 房间服务(聊天房间管理)
|
||||
service.InitAttachmentService(db) // 附件服务(文件上传、管理)
|
||||
service.InitLoginLogService(db) // 登录日志服务(记录登录历史)
|
||||
service.InitMomentService(db) // 朋友圈服务(动态、点赞、评论)
|
||||
|
||||
// 步骤5: 启动TURN服务器(用于WebRTC音视频通话)
|
||||
go turnserver.Start()
|
||||
@@ -373,6 +382,9 @@ func main() {
|
||||
authGroup.GET("/attachments", api.GetAttachmentsHandler)
|
||||
authGroup.GET("/attachments/:id", api.GetAttachmentHandler)
|
||||
authGroup.POST("/attachments/delete/:id", api.DeleteAttachmentHandler)
|
||||
|
||||
// 朋友圈管理
|
||||
api.RegisterMomentRoutes(authGroup)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
483
internal/api/moment_handler.go
Normal file
483
internal/api/moment_handler.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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 // 视频
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// 常量定义
|
||||
// ==========================================
|
||||
|
||||
769
internal/service/moment_service.go
Normal file
769
internal/service/moment_service.go
Normal 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(¬ifications).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 加载关联数据
|
||||
for i := range notifications {
|
||||
s.loadNotificationData(¬ifications[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)
|
||||
}
|
||||
@@ -336,4 +336,85 @@ CREATE TABLE `verification_codes` (
|
||||
-- Records of verification_codes
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for moments (朋友圈动态表)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `moments`;
|
||||
CREATE TABLE `moments` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '动态ID',
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '发布者ID',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '文字内容',
|
||||
`media_type` tinyint NULL DEFAULT 0 COMMENT '媒体类型: 0=纯文字 1=图片 2=视频',
|
||||
`media_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '媒体URL列表(JSON数组)',
|
||||
`location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '位置信息',
|
||||
`visibility` tinyint NULL DEFAULT 0 COMMENT '可见性: 0=公开 1=仅好友 2=部分好友可见 3=部分好友不可见',
|
||||
`visible_user_ids` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '可见/不可见用户ID列表(JSON数组)',
|
||||
`mention_user_ids` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '@的用户ID列表(JSON数组)',
|
||||
`topic_tags` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '话题标签(JSON数组)',
|
||||
`like_count` int NULL DEFAULT 0 COMMENT '点赞数',
|
||||
`comment_count` int NULL DEFAULT 0 COMMENT '评论数',
|
||||
`is_deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已删除',
|
||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime(3) NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_moments_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_moments_created_at`(`created_at` ASC) USING BTREE,
|
||||
INDEX `idx_moments_visibility`(`visibility` ASC) USING BTREE,
|
||||
INDEX `idx_moments_is_deleted`(`is_deleted` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '朋友圈动态表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for moment_likes (动态点赞表)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `moment_likes`;
|
||||
CREATE TABLE `moment_likes` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`moment_id` bigint UNSIGNED NOT NULL COMMENT '动态ID',
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '点赞用户ID',
|
||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_moment_user`(`moment_id` ASC, `user_id` ASC) USING BTREE,
|
||||
INDEX `idx_moment_likes_moment_id`(`moment_id` ASC) USING BTREE,
|
||||
INDEX `idx_moment_likes_user_id`(`user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '动态点赞表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for moment_comments (动态评论表)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `moment_comments`;
|
||||
CREATE TABLE `moment_comments` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '评论ID',
|
||||
`moment_id` bigint UNSIGNED NOT NULL COMMENT '动态ID',
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '评论者ID',
|
||||
`reply_to_comment_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '回复的评论ID(NULL为直接评论)',
|
||||
`reply_to_user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '回复的用户ID',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '评论内容',
|
||||
`is_deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已删除',
|
||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_moment_comments_moment_id`(`moment_id` ASC) USING BTREE,
|
||||
INDEX `idx_moment_comments_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_moment_comments_reply_to`(`reply_to_comment_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '动态评论表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for moment_notifications (朋友圈通知表)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `moment_notifications`;
|
||||
CREATE TABLE `moment_notifications` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '通知ID',
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '接收通知的用户ID',
|
||||
`from_user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '触发通知的用户ID',
|
||||
`moment_id` bigint UNSIGNED NOT NULL COMMENT '关联的动态ID',
|
||||
`type` tinyint NOT NULL COMMENT '通知类型: 1=点赞 2=评论 3=回复 4=@提及',
|
||||
`comment_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '关联的评论ID',
|
||||
`is_read` tinyint(1) NULL DEFAULT 0 COMMENT '是否已读',
|
||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_moment_notifications_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_moment_notifications_is_read`(`is_read` ASC) USING BTREE,
|
||||
INDEX `idx_moment_notifications_created_at`(`created_at` ASC) USING BTREE,
|
||||
INDEX `idx_moment_notifications_moment_id`(`moment_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '朋友圈通知表' ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
Reference in New Issue
Block a user