diff --git a/controller/captcha_controller/captcha.go b/controller/captcha_controller/captcha.go deleted file mode 100644 index 5a972f8..0000000 --- a/controller/captcha_controller/captcha.go +++ /dev/null @@ -1,3 +0,0 @@ -package captcha_controller - -type CaptchaController struct{} diff --git a/controller/captcha_controller/captcha_controller.go b/controller/captcha_controller/captcha_controller.go index c712c39..239b50e 100644 --- a/controller/captcha_controller/captcha_controller.go +++ b/controller/captcha_controller/captcha_controller.go @@ -19,6 +19,8 @@ import ( "time" ) +type CaptchaController struct{} + // GenerateRotateCaptcha 生成旋转验证码 // @Summary 生成旋转验证码 // @Description 生成旋转验证码 diff --git a/controller/client_controller/client.go b/controller/client_controller/client.go deleted file mode 100644 index 778db37..0000000 --- a/controller/client_controller/client.go +++ /dev/null @@ -1,7 +0,0 @@ -package client_controller - -import "sync" - -type ClientController struct{} - -var mu sync.Mutex diff --git a/controller/client_controller/client_controller.go b/controller/client_controller/client_controller.go index 4a4b87e..d118abe 100644 --- a/controller/client_controller/client_controller.go +++ b/controller/client_controller/client_controller.go @@ -8,9 +8,14 @@ import ( "schisandra-cloud-album/common/result" "schisandra-cloud-album/global" "schisandra-cloud-album/utils" + "sync" "time" ) +type ClientController struct{} + +var mu sync.Mutex + // GenerateClientId 生成客户端ID // @Summary 生成客户端ID // @Description 生成客户端ID diff --git a/controller/comment_controller/comment.go b/controller/comment_controller/comment.go deleted file mode 100644 index 28710bd..0000000 --- a/controller/comment_controller/comment.go +++ /dev/null @@ -1,57 +0,0 @@ -package comment_controller - -import ( - "schisandra-cloud-album/service/impl" - "sync" - "time" -) - -type CommentController struct{} - -var wg sync.WaitGroup -var mx sync.Mutex -var commentReplyService = impl.CommentReplyServiceImpl{} - -// CommentImages 评论图片 -type CommentImages struct { - TopicId string `json:"topic_id" bson:"topic_id" required:"true"` - CommentId int64 `json:"comment_id" bson:"comment_id" required:"true"` - UserId string `json:"user_id" bson:"user_id" required:"true"` - Images [][]byte `json:"images" bson:"images" required:"true"` - CreatedAt string `json:"created_at" bson:"created_at" required:"true"` -} - -// CommentContent 评论内容 -type CommentContent struct { - NickName string `json:"nickname"` - Avatar string `json:"avatar"` - Level int `json:"level,omitempty"` - Id int64 `json:"id"` - UserId string `json:"user_id"` - TopicId string `json:"topic_id"` - Content string `json:"content"` - ReplyTo int64 `json:"reply_to,omitempty"` - ReplyId int64 `json:"reply_id,omitempty"` - ReplyUser string `json:"reply_user,omitempty"` - ReplyUsername string `json:"reply_username,omitempty"` - Author int `json:"author"` - Likes int64 `json:"likes"` - ReplyCount int64 `json:"reply_count"` - CreatedTime time.Time `json:"created_time"` - Location string `json:"location"` - Browser string `json:"browser"` - OperatingSystem string `json:"operating_system"` - IsLiked bool `json:"is_liked" default:"false"` - Images []string `json:"images,omitempty"` -} - -// CommentResponse 评论返回值 -type CommentResponse struct { - Size int `json:"size"` - Total int64 `json:"total"` - Current int `json:"current"` - Comments []CommentContent `json:"comments"` -} - -var likeChannel = make(chan CommentLikeRequest, 1000) -var cancelLikeChannel = make(chan CommentLikeRequest, 1000) diff --git a/controller/comment_controller/comment_controller.go b/controller/comment_controller/comment_controller.go index b7d3ca5..a35682e 100644 --- a/controller/comment_controller/comment_controller.go +++ b/controller/comment_controller/comment_controller.go @@ -1,25 +1,21 @@ package comment_controller import ( - "context" - "encoding/base64" - "encoding/json" - "fmt" - "github.com/acmestack/gorm-plus/gplus" ginI18n "github.com/gin-contrib/i18n" "github.com/gin-gonic/gin" "github.com/mssola/useragent" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" "schisandra-cloud-album/common/enum" "schisandra-cloud-album/common/result" "schisandra-cloud-album/global" "schisandra-cloud-album/model" - "schisandra-cloud-album/mq" + "schisandra-cloud-album/service/impl" "schisandra-cloud-album/utils" - "time" ) +type CommentController struct{} + +var commentReplyService = impl.CommentReplyServiceImpl{} + // CommentSubmit 提交评论 // @Summary 提交评论 // @Description 提交评论 @@ -67,13 +63,6 @@ func (CommentController) CommentSubmit(c *gin.Context) { isAuthor = 1 } - tx := global.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - commentReply := model.ScaCommentReply{ Content: commentRequest.Content, UserId: commentRequest.UserID, @@ -87,56 +76,11 @@ func (CommentController) CommentSubmit(c *gin.Context) { OperatingSystem: operatingSystem, Agent: userAgent, } - // 使用 goroutine 进行异步评论保存 - errCh := make(chan error, 2) - go func() { - errCh <- commentReplyService.CreateCommentReplyService(&commentReply) - }() - - // 等待评论回复的创建 - if err = <-errCh; err != nil { - global.LOG.Errorln(err) + response := commentReplyService.SubmitCommentService(&commentReply, commentRequest.TopicId, commentRequest.UserID, commentRequest.Images) + if !response { result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c) - tx.Rollback() return } - - // 处理图片异步上传 - if len(commentRequest.Images) > 0 { - imagesDataCh := make(chan [][]byte) - go func() { - imagesData, err := processImages(commentRequest.Images) - if err != nil { - global.LOG.Errorln(err) - imagesDataCh <- nil // 发送失败信号 - return - } - imagesDataCh <- imagesData // 发送处理成功的数据 - }() - - imagesData := <-imagesDataCh - if imagesData == nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c) - tx.Rollback() - return - } - - commentImages := CommentImages{ - TopicId: commentRequest.TopicId, - CommentId: commentReply.Id, - UserId: commentRequest.UserID, - Images: imagesData, - CreatedAt: time.Now().Format("2006-01-02 15:04:05"), - } - - // 使用 goroutine 进行异步图片保存 - go func() { - if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil { - global.LOG.Errorln(err) - } - }() - } - tx.Commit() result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitSuccess"), c) return } @@ -188,13 +132,6 @@ func (CommentController) ReplySubmit(c *gin.Context) { isAuthor = 1 } - tx := global.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - commentReply := model.ScaCommentReply{ Content: replyCommentRequest.Content, UserId: replyCommentRequest.UserID, @@ -210,60 +147,11 @@ func (CommentController) ReplySubmit(c *gin.Context) { OperatingSystem: operatingSystem, Agent: userAgent, } - // 使用 goroutine 进行异步评论保存 - errCh := make(chan error) - go func() { - - errCh <- commentReplyService.CreateCommentReplyService(&commentReply) - }() - go func() { - - errCh <- commentReplyService.UpdateCommentReplyCountService(replyCommentRequest.ReplyId) - }() - // 等待评论回复的创建 - if err = <-errCh; err != nil { - global.LOG.Errorln(err) + response := commentReplyService.SubmitCommentService(&commentReply, replyCommentRequest.TopicId, replyCommentRequest.UserID, replyCommentRequest.Images) + if !response { result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c) - tx.Rollback() return } - - // 处理图片异步上传 - if len(replyCommentRequest.Images) > 0 { - imagesDataCh := make(chan [][]byte) - go func() { - imagesData, err := processImages(replyCommentRequest.Images) - if err != nil { - global.LOG.Errorln(err) - imagesDataCh <- nil // 发送失败信号 - return - } - imagesDataCh <- imagesData // 发送处理成功的数据 - }() - - imagesData := <-imagesDataCh - if imagesData == nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c) - tx.Rollback() - return - } - - commentImages := CommentImages{ - TopicId: replyCommentRequest.TopicId, - CommentId: commentReply.Id, - UserId: replyCommentRequest.UserID, - Images: imagesData, - CreatedAt: time.Now().Format("2006-01-02 15:04:05"), - } - - // 使用 goroutine 进行异步图片保存 - go func() { - if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil { - global.LOG.Errorln(err) - } - }() - } - tx.Commit() result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitSuccess"), c) return } @@ -315,13 +203,6 @@ func (CommentController) ReplyReplySubmit(c *gin.Context) { isAuthor = 1 } - tx := global.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - commentReply := model.ScaCommentReply{ Content: replyReplyRequest.Content, UserId: replyReplyRequest.UserID, @@ -338,57 +219,11 @@ func (CommentController) ReplyReplySubmit(c *gin.Context) { OperatingSystem: operatingSystem, Agent: userAgent, } - - errCh := make(chan error, 2) - go func() { - errCh <- commentReplyService.CreateCommentReplyService(&commentReply) - }() - go func() { - errCh <- commentReplyService.UpdateCommentReplyCountService(replyReplyRequest.ReplyId) - }() - - if err = <-errCh; err != nil { - global.LOG.Errorln(err) + response := commentReplyService.SubmitCommentService(&commentReply, replyReplyRequest.TopicId, replyReplyRequest.UserID, replyReplyRequest.Images) + if !response { result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c) - tx.Rollback() return } - - if len(replyReplyRequest.Images) > 0 { - imagesDataCh := make(chan [][]byte) - go func() { - imagesData, err := processImages(replyReplyRequest.Images) - if err != nil { - global.LOG.Errorln(err) - imagesDataCh <- nil - return - } - imagesDataCh <- imagesData - }() - - imagesData := <-imagesDataCh - if imagesData == nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitFailed"), c) - tx.Rollback() - return - } - - commentImages := CommentImages{ - TopicId: replyReplyRequest.TopicId, - CommentId: commentReply.Id, - UserId: replyReplyRequest.UserID, - Images: imagesData, - CreatedAt: time.Now().Format("2006-01-02 15:04:05"), - } - - // 处理图片保存 - go func() { - if _, err = global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil { - global.LOG.Errorln(err) - } - }() - } - tx.Commit() result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentSubmitSuccess"), c) return } @@ -408,162 +243,7 @@ func (CommentController) CommentList(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } - // 查询评论列表 - query, u := gplus.NewQuery[model.ScaCommentReply]() - page := gplus.NewPage[model.ScaCommentReply](commentListRequest.Page, commentListRequest.Size) - if commentListRequest.IsHot { - query.OrderByDesc(&u.CommentOrder).OrderByDesc(&u.Likes).OrderByDesc(&u.ReplyCount) - } else { - query.OrderByDesc(&u.CommentOrder).OrderByDesc(&u.CreatedTime) - } - query.Eq(&u.TopicId, commentListRequest.TopicId).Eq(&u.CommentType, enum.COMMENT) - page, pageDB := gplus.SelectPage(page, query) - if pageDB.Error != nil { - global.LOG.Errorln(pageDB.Error) - return - } - if len(page.Records) == 0 { - result.OkWithData(CommentResponse{Comments: []CommentContent{}, Size: page.Size, Current: page.Current, Total: page.Total}, c) - return - } - - userIds := make([]string, 0, len(page.Records)) - commentIds := make([]int64, 0, len(page.Records)) - for _, comment := range page.Records { - userIds = append(userIds, comment.UserId) - commentIds = append(commentIds, comment.Id) - } - - // 结果存储 - userInfoMap := make(map[string]model.ScaAuthUser) - likeMap := make(map[int64]bool) - commentImagesMap := make(map[int64]CommentImages) - - // 使用 WaitGroup 等待协程完成 - wg.Add(3) - - // 查询评论用户信息 - go func() { - defer wg.Done() - queryUser, n := gplus.NewQuery[model.ScaAuthUser]() - queryUser.Select(&n.UID, &n.Avatar, &n.Nickname).In(&n.UID, userIds) - userInfos, userInfosDB := gplus.SelectList(queryUser) - if userInfosDB.Error != nil { - global.LOG.Errorln(userInfosDB.Error) - return - } - for _, userInfo := range userInfos { - userInfoMap[*userInfo.UID] = *userInfo - } - }() - - // 查询评论点赞状态 - go func() { - defer wg.Done() - if len(page.Records) > 0 { - queryLike, l := gplus.NewQuery[model.ScaCommentLikes]() - queryLike.Eq(&l.TopicId, commentListRequest.TopicId).Eq(&l.UserId, commentListRequest.UserID).In(&l.CommentId, commentIds) - likes, likesDB := gplus.SelectList(queryLike) - if likesDB.Error != nil { - global.LOG.Errorln(likesDB.Error) - return - } - for _, like := range likes { - likeMap[like.CommentId] = true - } - } - }() - - // 查询评论图片信息 - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 设置超时,2秒 - defer cancel() - - cursor, err := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").Find(ctx, bson.M{"comment_id": bson.M{"$in": commentIds}}) - if err != nil { - global.LOG.Errorf("Failed to get images for comments: %v", err) - return - } - defer func(cursor *mongo.Cursor, ctx context.Context) { - err := cursor.Close(ctx) - if err != nil { - return - } - }(cursor, ctx) - - for cursor.Next(ctx) { - var commentImages CommentImages - if err = cursor.Decode(&commentImages); err != nil { - global.LOG.Errorf("Failed to decode comment images: %v", err) - continue - } - commentImagesMap[commentImages.CommentId] = commentImages - } - }() - - // 等待所有查询完成 - wg.Wait() - commentChannel := make(chan CommentContent, len(page.Records)) - - for _, comment := range page.Records { - wg.Add(1) - go func(comment model.ScaCommentReply) { - defer wg.Done() - // 将图片转换为base64 - var imagesBase64 []string - if imgData, ok := commentImagesMap[comment.Id]; ok { - // 将图片转换为base64 - for _, img := range imgData.Images { - mimeType := getMimeType(img) - base64Img := base64.StdEncoding.EncodeToString(img) - base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img) - imagesBase64 = append(imagesBase64, base64WithPrefix) - } - } - - userInfo, exist := userInfoMap[comment.UserId] - if !exist { - global.LOG.Errorf("Failed to get user info for comment: %s", comment.UserId) - return - } - commentContent := CommentContent{ - Avatar: *userInfo.Avatar, - NickName: *userInfo.Nickname, - Id: comment.Id, - UserId: comment.UserId, - TopicId: comment.TopicId, - Content: comment.Content, - ReplyCount: comment.ReplyCount, - Likes: comment.Likes, - CreatedTime: comment.CreatedTime, - Author: comment.Author, - Location: comment.Location, - Browser: comment.Browser, - OperatingSystem: comment.OperatingSystem, - Images: imagesBase64, - IsLiked: likeMap[comment.Id], - } - commentChannel <- commentContent - }(*comment) - } - - go func() { - wg.Wait() - close(commentChannel) - }() - - var commentsWithImages []CommentContent - for commentContent := range commentChannel { - commentsWithImages = append(commentsWithImages, commentContent) - } - - response := CommentResponse{ - Comments: commentsWithImages, - Size: page.Size, - Current: page.Current, - Total: page.Total, - } + response := commentReplyService.GetCommentListService(commentListRequest.UserID, commentListRequest.TopicId, commentListRequest.Page, commentListRequest.Size, commentListRequest.IsHot) result.OkWithData(response, c) return } @@ -583,168 +263,7 @@ func (CommentController) ReplyList(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } - query, u := gplus.NewQuery[model.ScaCommentReply]() - page := gplus.NewPage[model.ScaCommentReply](replyListRequest.Page, replyListRequest.Size) - query.Eq(&u.TopicId, replyListRequest.TopicId).Eq(&u.ReplyId, replyListRequest.CommentId).Eq(&u.CommentType, enum.REPLY).OrderByDesc(&u.Likes).OrderByAsc(&u.CreatedTime) - page, pageDB := gplus.SelectPage(page, query) - if pageDB.Error != nil { - global.LOG.Errorln(pageDB.Error) - return - } - if len(page.Records) == 0 { - result.OkWithData(CommentResponse{Comments: []CommentContent{}, Size: page.Size, Current: page.Current, Total: page.Total}, c) - return - } - - userIdsSet := make(map[string]struct{}) // 使用集合去重用户 ID - commentIds := make([]int64, 0, len(page.Records)) - // 收集用户 ID 和评论 ID - for _, comment := range page.Records { - userIdsSet[comment.UserId] = struct{}{} // 去重 - commentIds = append(commentIds, comment.Id) - if comment.ReplyUser != "" { - userIdsSet[comment.ReplyUser] = struct{}{} // 去重 - } - } - // 将用户 ID 转换为切片 - userIds := make([]string, 0, len(userIdsSet)) - for userId := range userIdsSet { - userIds = append(userIds, userId) - } - - likeMap := make(map[int64]bool, len(page.Records)) - commentImagesMap := make(map[int64]CommentImages) - userInfoMap := make(map[string]model.ScaAuthUser, len(userIds)) - - wg.Add(3) - go func() { - defer wg.Done() - // 查询评论用户信息 - queryUser, n := gplus.NewQuery[model.ScaAuthUser]() - queryUser.Select(&n.UID, &n.Avatar, &n.Nickname).In(&n.UID, userIds) - userInfos, userInfosDB := gplus.SelectList(queryUser) - if userInfosDB.Error != nil { - global.LOG.Errorln(userInfosDB.Error) - return - } - for _, userInfo := range userInfos { - userInfoMap[*userInfo.UID] = *userInfo - } - }() - - go func() { - defer wg.Done() - // 查询评论点赞状态 - - if len(page.Records) > 0 { - queryLike, l := gplus.NewQuery[model.ScaCommentLikes]() - queryLike.Eq(&l.TopicId, replyListRequest.TopicId).Eq(&l.UserId, replyListRequest.UserID).In(&l.CommentId, commentIds) - likes, likesDB := gplus.SelectList(queryLike) - if likesDB.Error != nil { - global.LOG.Errorln(likesDB.Error) - return - } - for _, like := range likes { - likeMap[like.CommentId] = true - } - } - }() - - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 设置超时,2秒 - defer cancel() - cursor, err := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").Find(ctx, bson.M{"comment_id": bson.M{"$in": commentIds}}) - if err != nil { - global.LOG.Errorf("Failed to get images for comments: %v", err) - return - } - defer func(cursor *mongo.Cursor, ctx context.Context) { - warn := cursor.Close(ctx) - if warn != nil { - return - } - }(cursor, ctx) - - for cursor.Next(ctx) { - var commentImages CommentImages - if e := cursor.Decode(&commentImages); e != nil { - global.LOG.Errorf("Failed to decode comment images: %v", e) - continue - } - commentImagesMap[commentImages.CommentId] = commentImages - } - }() - wg.Wait() - - replyChannel := make(chan CommentContent, len(page.Records)) // 使用通道传递回复内容 - - for _, reply := range page.Records { - wg.Add(1) - go func(reply model.ScaCommentReply) { - defer wg.Done() - - var imagesBase64 []string - if imgData, ok := commentImagesMap[reply.Id]; ok { - // 将图片转换为base64 - for _, img := range imgData.Images { - mimeType := getMimeType(img) - base64Img := base64.StdEncoding.EncodeToString(img) - base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img) - imagesBase64 = append(imagesBase64, base64WithPrefix) - } - } - userInfo, exist := userInfoMap[reply.UserId] - if !exist { - global.LOG.Errorf("Failed to get user info for comment: %s", reply.UserId) - return - } - replyUserInfo, e := userInfoMap[reply.ReplyUser] - if !e { - global.LOG.Errorf("Failed to get reply user info for comment: %s", reply.ReplyUser) - return - } - commentContent := CommentContent{ - Avatar: *userInfo.Avatar, - NickName: *userInfo.Nickname, - Id: reply.Id, - UserId: reply.UserId, - TopicId: reply.TopicId, - Content: reply.Content, - ReplyUsername: *replyUserInfo.Nickname, - ReplyCount: reply.ReplyCount, - Likes: reply.Likes, - CreatedTime: reply.CreatedTime, - ReplyUser: reply.ReplyUser, - ReplyId: reply.ReplyId, - ReplyTo: reply.ReplyTo, - Author: reply.Author, - Location: reply.Location, - Browser: reply.Browser, - OperatingSystem: reply.OperatingSystem, - Images: imagesBase64, - IsLiked: likeMap[reply.Id], - } - replyChannel <- commentContent // 发送到通道 - }(*reply) - } - - go func() { - wg.Wait() - close(replyChannel) // 关闭通道 - }() - - var repliesWithImages []CommentContent - for replyContent := range replyChannel { - repliesWithImages = append(repliesWithImages, replyContent) // 从通道获取回复内容 - } - - response := CommentResponse{ - Comments: repliesWithImages, - Size: page.Size, - Current: page.Current, - Total: page.Total, - } + response := commentReplyService.GetCommentReplyService(replyListRequest.UserID, replyListRequest.TopicId, replyListRequest.CommentId, replyListRequest.Page, replyListRequest.Size) result.OkWithData(response, c) return } @@ -764,44 +283,11 @@ func (CommentController) CommentLikes(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } - - mx.Lock() - defer mx.Unlock() - - likes := model.ScaCommentLikes{ - CommentId: likeRequest.CommentId, - UserId: likeRequest.UserID, - TopicId: likeRequest.TopicId, - } - - tx := global.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - res := global.DB.Create(&likes) // 假设这是插入数据库的方法 - if res.Error != nil { - tx.Rollback() - global.LOG.Errorln(res.Error) + res := commentReplyService.CommentLikeService(likeRequest.UserID, likeRequest.CommentId, likeRequest.TopicId) + if !res { + result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentLikeFailed"), c) return } - - // 异步更新点赞计数 - go func() { - if err = commentReplyService.UpdateCommentLikesCountService(likeRequest.CommentId, likeRequest.TopicId); err != nil { - global.LOG.Errorln(err) - } - }() - marshal, err := json.Marshal(likes) - if err != nil { - global.LOG.Errorln(err) - return - } - mq.CommentLikeProducer(marshal) - - tx.Commit() result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentLikeSuccess"), c) return } @@ -820,34 +306,11 @@ func (CommentController) CancelCommentLikes(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } - mx.Lock() - defer mx.Unlock() - - tx := global.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - query, u := gplus.NewQuery[model.ScaCommentLikes]() - query.Eq(&u.CommentId, cancelLikeRequest.CommentId). - Eq(&u.UserId, cancelLikeRequest.UserID). - Eq(&u.TopicId, cancelLikeRequest.TopicId) - - res := gplus.Delete[model.ScaCommentLikes](query) - if res.Error != nil { - tx.Rollback() - return // 返回错误而非打印 + res := commentReplyService.CommentDislikeService(cancelLikeRequest.UserID, cancelLikeRequest.CommentId, cancelLikeRequest.TopicId) + if !res { + result.FailWithMessage(ginI18n.MustGetMessage(c, "CommentDislikeFailed"), c) + return } - - // 异步更新点赞计数 - go func() { - if err := commentReplyService.DecrementCommentLikesCountService(cancelLikeRequest.CommentId, cancelLikeRequest.TopicId); err != nil { - global.LOG.Errorln(err) - } - }() - tx.Commit() - result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentLikeCancelSuccess"), c) + result.OkWithMessage(ginI18n.MustGetMessage(c, "CommentDislikeSuccess"), c) return } diff --git a/controller/comment_controller/request_param.go b/controller/comment_controller/request_param.go index 2dd4a40..516aec1 100644 --- a/controller/comment_controller/request_param.go +++ b/controller/comment_controller/request_param.go @@ -1,5 +1,6 @@ package comment_controller +// CommentRequest 评论请求参数 type CommentRequest struct { Content string `json:"content" binding:"required"` Images []string `json:"images"` @@ -9,6 +10,8 @@ type CommentRequest struct { Key string `json:"key" binding:"required"` Point []int64 `json:"point" binding:"required"` } + +// ReplyCommentRequest 回复评论请求参数 type ReplyCommentRequest struct { Content string `json:"content" binding:"required"` Images []string `json:"images"` @@ -21,6 +24,7 @@ type ReplyCommentRequest struct { Point []int64 `json:"point" binding:"required"` } +// ReplyReplyRequest 回复回复请求参数 type ReplyReplyRequest struct { Content string `json:"content" binding:"required"` Images []string `json:"images"` @@ -34,6 +38,7 @@ type ReplyReplyRequest struct { Point []int64 `json:"point" binding:"required"` } +// CommentListRequest 评论列表请求参数 type CommentListRequest struct { UserID string `json:"user_id" binding:"required"` TopicId string `json:"topic_id" binding:"required"` @@ -41,6 +46,8 @@ type CommentListRequest struct { Size int `json:"size" default:"5"` IsHot bool `json:"is_hot" default:"true"` } + +// ReplyListRequest 回复列表请求参数 type ReplyListRequest struct { UserID string `json:"user_id" binding:"required"` TopicId string `json:"topic_id" binding:"required"` @@ -48,6 +55,8 @@ type ReplyListRequest struct { Page int `json:"page" default:"1"` Size int `json:"size" default:"5"` } + +// CommentLikeRequest 点赞评论的请求参数 type CommentLikeRequest struct { TopicId string `json:"topic_id" binding:"required"` CommentId int64 `json:"comment_id" binding:"required"` diff --git a/controller/permission_controller/permission.go b/controller/permission_controller/permission.go deleted file mode 100644 index 0e5a1f2..0000000 --- a/controller/permission_controller/permission.go +++ /dev/null @@ -1,3 +0,0 @@ -package permission_controller - -type PermissionController struct{} diff --git a/controller/permission_controller/permission_controller.go b/controller/permission_controller/permission_controller.go index e3001e0..5979cd5 100644 --- a/controller/permission_controller/permission_controller.go +++ b/controller/permission_controller/permission_controller.go @@ -8,6 +8,8 @@ import ( "schisandra-cloud-album/service/impl" ) +type PermissionController struct{} + var permissionService = impl.PermissionServiceImpl{} // AddPermissions 批量添加权限 diff --git a/controller/role_controller/role.go b/controller/role_controller/role.go deleted file mode 100644 index 9229677..0000000 --- a/controller/role_controller/role.go +++ /dev/null @@ -1,3 +0,0 @@ -package role_controller - -type RoleController struct{} diff --git a/controller/role_controller/role_controller.go b/controller/role_controller/role_controller.go index 9de290e..8d83e1b 100644 --- a/controller/role_controller/role_controller.go +++ b/controller/role_controller/role_controller.go @@ -9,6 +9,8 @@ import ( "schisandra-cloud-album/service/impl" ) +type RoleController struct{} + var roleService = impl.RoleServiceImpl{} // CreateRole 创建角色 diff --git a/controller/sms_controller/sms.go b/controller/sms_controller/sms.go deleted file mode 100644 index a3bdaa8..0000000 --- a/controller/sms_controller/sms.go +++ /dev/null @@ -1,3 +0,0 @@ -package sms_controller - -type SmsController struct{} diff --git a/controller/sms_controller/sms_controller.go b/controller/sms_controller/sms_controller.go index 6d04473..ff46ce7 100644 --- a/controller/sms_controller/sms_controller.go +++ b/controller/sms_controller/sms_controller.go @@ -15,6 +15,8 @@ import ( "time" ) +type SmsController struct{} + // SendMessageByAli 发送短信验证码 // @Summary 发送短信验证码 // @Description 发送短信验证码 diff --git a/controller/user_controller/request_param.go b/controller/user_controller/request_param.go index c934779..49a74c8 100644 --- a/controller/user_controller/request_param.go +++ b/controller/user_controller/request_param.go @@ -1,7 +1,5 @@ package user_controller -import "encoding/json" - // RefreshTokenRequest 刷新token请求 type RefreshTokenRequest struct { RefreshToken string `json:"refresh_token" binding:"required"` @@ -37,19 +35,3 @@ type ResetPasswordRequest struct { Password string `json:"password" binding:"required"` Repassword string `json:"repassword" binding:"required"` } - -// ResponseData 返回数据 -type ResponseData struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ExpiresAt int64 `json:"expires_at"` - UID *string `json:"uid"` -} - -func (res ResponseData) MarshalBinary() ([]byte, error) { - return json.Marshal(res) -} - -func (res ResponseData) UnmarshalBinary(data []byte) error { - return json.Unmarshal(data, &res) -} diff --git a/controller/user_controller/user.go b/controller/user_controller/user.go deleted file mode 100644 index 7edc620..0000000 --- a/controller/user_controller/user.go +++ /dev/null @@ -1,12 +0,0 @@ -package user_controller - -import ( - "schisandra-cloud-album/service/impl" - "sync" -) - -type UserController struct{} - -var mu sync.Mutex -var userService = impl.UserServiceImpl{} -var userDeviceService = impl.UserDeviceServiceImpl{} diff --git a/controller/user_controller/user_controller.go b/controller/user_controller/user_controller.go index 5240203..825cc77 100644 --- a/controller/user_controller/user_controller.go +++ b/controller/user_controller/user_controller.go @@ -13,11 +13,18 @@ import ( "schisandra-cloud-album/common/result" "schisandra-cloud-album/global" "schisandra-cloud-album/model" + "schisandra-cloud-album/service/impl" "schisandra-cloud-album/utils" "strconv" - "time" + "sync" ) +type UserController struct{} + +var mu sync.Mutex +var userService = impl.UserServiceImpl{} +var userDeviceService = impl.UserDeviceServiceImpl{} + // GetUserList // @Summary 获取所有用户列表 // @Tags 用户模块 @@ -258,31 +265,8 @@ func (UserController) RefreshHandler(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } - refreshToken := request.RefreshToken - parseRefreshToken, isUpd, err := utils.ParseRefreshToken(refreshToken) - if err != nil || !isUpd { - global.LOG.Errorln(err) - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c) - return - } - accessTokenString, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: parseRefreshToken.UserID}) - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c) - return - } - tokenKey := constant.UserLoginTokenRedisKey + *parseRefreshToken.UserID - token, err := redis.Get(tokenKey).Result() - if err != nil || token == "" { - global.LOG.Errorln(err) - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c) - return - } - data := ResponseData{ - AccessToken: accessTokenString, - RefreshToken: refreshToken, - UID: parseRefreshToken.UserID, - } - if err := redis.Set(tokenKey, data, time.Hour*24*7).Err(); err != nil { + data, res := userService.RefreshTokenService(request.RefreshToken) + if !res { result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginExpired"), c) return } @@ -329,11 +313,6 @@ func (UserController) ResetPassword(c *gin.Context) { } }() - if err := tx.Error; err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "DatabaseError"), c) - return - } - code := redis.Get(constant.UserLoginSmsRedisKey + phone).Val() if code == "" { result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaExpired"), c) @@ -353,7 +332,7 @@ func (UserController) ResetPassword(c *gin.Context) { } user := userService.QueryUserByPhoneService(phone) - if reflect.DeepEqual(user, model.ScaAuthUser{}) { + if user.ID == 0 { result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotRegister"), c) return } diff --git a/controller/websocket_controller/gws_controller.go b/controller/websocket_controller/gws_controller.go index 8cbd323..de2b2a1 100644 --- a/controller/websocket_controller/gws_controller.go +++ b/controller/websocket_controller/gws_controller.go @@ -12,6 +12,9 @@ import ( "time" ) +type WebsocketController struct { +} + const ( PingInterval = 5 * time.Second // 客户端心跳间隔 HeartbeatWaitTimeout = 10 * time.Second // 心跳等待超时时间 diff --git a/controller/websocket_controller/websocket.go b/controller/websocket_controller/websocket.go deleted file mode 100644 index ff1f4cf..0000000 --- a/controller/websocket_controller/websocket.go +++ /dev/null @@ -1,4 +0,0 @@ -package websocket_controller - -type WebsocketController struct { -} diff --git a/service/comment_reply_service.go b/service/comment_reply_service.go index 18bab5e..8c4bd84 100644 --- a/service/comment_reply_service.go +++ b/service/comment_reply_service.go @@ -1,10 +1,19 @@ package service -import "schisandra-cloud-album/model" +import ( + "schisandra-cloud-album/model" + "schisandra-cloud-album/service/impl" +) type CommentReplyService interface { - CreateCommentReplyService(comment *model.ScaCommentReply) error - UpdateCommentReplyCountService(replyId int64) error - UpdateCommentLikesCountService(commentId int64, topicId string) error - DecrementCommentLikesCountService(commentId int64, topicId string) error + // GetCommentReplyService 获取评论回复 + GetCommentReplyService(uid string, topicId string, commentId int64, pageNum int, size int) *impl.CommentResponse + // GetCommentListService 获取评论列表 + GetCommentListService(uid string, topicId string, pageNum int, size int, isHot bool) *impl.CommentResponse + // CommentLikeService 评论点赞 + CommentLikeService(uid string, commentId int64, topicId string) bool + // CommentDislikeService 评论取消点赞 + CommentDislikeService(uid string, commentId int64, topicId string) bool + // SubmitCommentService 提交评论 + SubmitCommentService(comment *model.ScaCommentReply, topicId string, uid string, images []string) bool } diff --git a/service/impl/comment_reply_service_impl.go b/service/impl/comment_reply_service_impl.go index a54810b..b1cd9b8 100644 --- a/service/impl/comment_reply_service_impl.go +++ b/service/impl/comment_reply_service_impl.go @@ -1,32 +1,518 @@ package impl import ( + "context" + "encoding/base64" + "fmt" + "github.com/acmestack/gorm-plus/gplus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "schisandra-cloud-album/common/enum" "schisandra-cloud-album/dao/impl" + "schisandra-cloud-album/global" "schisandra-cloud-album/model" + "schisandra-cloud-album/utils" + "sync" + "time" ) var commentReplyDao = impl.CommentReplyDaoImpl{} type CommentReplyServiceImpl struct{} -// CreateCommentReplyService 创建评论回复 -func (CommentReplyServiceImpl) CreateCommentReplyService(comment *model.ScaCommentReply) error { - return commentReplyDao.CreateCommentReply(comment) +var wg sync.WaitGroup +var mx sync.Mutex +// CommentImages 评论图片 +type CommentImages struct { + TopicId string `json:"topic_id" bson:"topic_id" required:"true"` + CommentId int64 `json:"comment_id" bson:"comment_id" required:"true"` + UserId string `json:"user_id" bson:"user_id" required:"true"` + Images [][]byte `json:"images" bson:"images" required:"true"` + CreatedAt string `json:"created_at" bson:"created_at" required:"true"` } -// UpdateCommentReplyCountService 更新评论回复数 -func (CommentReplyServiceImpl) UpdateCommentReplyCountService(replyId int64) error { - return commentReplyDao.UpdateCommentReplyCount(replyId) - +// CommentContent 评论内容 +type CommentContent struct { + NickName string `json:"nickname"` + Avatar string `json:"avatar"` + Level int `json:"level,omitempty"` + Id int64 `json:"id"` + UserId string `json:"user_id"` + TopicId string `json:"topic_id"` + Content string `json:"content"` + ReplyTo int64 `json:"reply_to,omitempty"` + ReplyId int64 `json:"reply_id,omitempty"` + ReplyUser string `json:"reply_user,omitempty"` + ReplyUsername string `json:"reply_username,omitempty"` + Author int `json:"author"` + Likes int64 `json:"likes"` + ReplyCount int64 `json:"reply_count"` + CreatedTime time.Time `json:"created_time"` + Location string `json:"location"` + Browser string `json:"browser"` + OperatingSystem string `json:"operating_system"` + IsLiked bool `json:"is_liked" default:"false"` + Images []string `json:"images,omitempty"` } -// UpdateCommentLikesCountService 更新评论点赞数 -func (CommentReplyServiceImpl) UpdateCommentLikesCountService(commentId int64, topicId string) error { - return commentReplyDao.UpdateCommentLikesCount(commentId, topicId) +// CommentResponse 评论返回值 +type CommentResponse struct { + Size int `json:"size"` + Total int64 `json:"total"` + Current int `json:"current"` + Comments []CommentContent `json:"comments"` } -// DecrementCommentLikesCountService 减少评论点赞数 -func (CommentReplyServiceImpl) DecrementCommentLikesCountService(commentId int64, topicId string) error { - return commentReplyDao.DecrementCommentLikesCount(commentId, topicId) +// SubmitCommentService 提交评论 +func (CommentReplyServiceImpl) SubmitCommentService(comment *model.ScaCommentReply, topicId string, uid string, images []string) bool { + tx := global.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + errCh := make(chan error, 2) + go func() { + errCh <- commentReplyDao.CreateCommentReply(comment) + }() + go func() { + errCh <- commentReplyDao.UpdateCommentReplyCount(comment.ReplyId) + }() + + if err := <-errCh; err != nil { + global.LOG.Errorln(err) + tx.Rollback() + return false + } + + if len(images) > 0 { + imagesDataCh := make(chan [][]byte) + go func() { + imagesData, err := utils.ProcessImages(images) + if err != nil { + global.LOG.Errorln(err) + imagesDataCh <- nil + return + } + imagesDataCh <- imagesData + }() + + imagesData := <-imagesDataCh + if imagesData == nil { + tx.Rollback() + return false + } + + commentImages := CommentImages{ + TopicId: topicId, + CommentId: comment.Id, + UserId: uid, + Images: imagesData, + CreatedAt: time.Now().Format("2006-01-02 15:04:05"), + } + + // 处理图片保存 + go func() { + if _, err := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").InsertOne(context.Background(), commentImages); err != nil { + global.LOG.Errorln(err) + } + }() + } + tx.Commit() + return true +} + +// GetCommentReplyService 获取评论回复 +func (CommentReplyServiceImpl) GetCommentReplyService(uid string, topicId string, commentId int64, pageNum int, size int) *CommentResponse { + query, u := gplus.NewQuery[model.ScaCommentReply]() + page := gplus.NewPage[model.ScaCommentReply](pageNum, size) + query.Eq(&u.TopicId, topicId).Eq(&u.ReplyId, commentId).Eq(&u.CommentType, enum.REPLY).OrderByDesc(&u.Likes).OrderByAsc(&u.CreatedTime) + page, pageDB := gplus.SelectPage(page, query) + if pageDB.Error != nil { + global.LOG.Errorln(pageDB.Error) + return nil + } + if len(page.Records) == 0 { + return nil + + } + + userIdsSet := make(map[string]struct{}) // 使用集合去重用户 ID + commentIds := make([]int64, 0, len(page.Records)) + // 收集用户 ID 和评论 ID + for _, comment := range page.Records { + userIdsSet[comment.UserId] = struct{}{} // 去重 + commentIds = append(commentIds, comment.Id) + if comment.ReplyUser != "" { + userIdsSet[comment.ReplyUser] = struct{}{} // 去重 + } + } + // 将用户 ID 转换为切片 + userIds := make([]string, 0, len(userIdsSet)) + for userId := range userIdsSet { + userIds = append(userIds, userId) + } + + likeMap := make(map[int64]bool, len(page.Records)) + commentImagesMap := make(map[int64]CommentImages) + userInfoMap := make(map[string]model.ScaAuthUser, len(userIds)) + + wg.Add(3) + go func() { + defer wg.Done() + // 查询评论用户信息 + queryUser, n := gplus.NewQuery[model.ScaAuthUser]() + queryUser.Select(&n.UID, &n.Avatar, &n.Nickname).In(&n.UID, userIds) + userInfos, userInfosDB := gplus.SelectList(queryUser) + if userInfosDB.Error != nil { + global.LOG.Errorln(userInfosDB.Error) + return + } + for _, userInfo := range userInfos { + userInfoMap[*userInfo.UID] = *userInfo + } + }() + + go func() { + defer wg.Done() + // 查询评论点赞状态 + + if len(page.Records) > 0 { + queryLike, l := gplus.NewQuery[model.ScaCommentLikes]() + queryLike.Eq(&l.TopicId, topicId).Eq(&l.UserId, uid).In(&l.CommentId, commentIds) + likes, likesDB := gplus.SelectList(queryLike) + if likesDB.Error != nil { + global.LOG.Errorln(likesDB.Error) + return + } + for _, like := range likes { + likeMap[like.CommentId] = true + } + } + }() + + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 设置超时,2秒 + defer cancel() + cursor, err := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").Find(ctx, bson.M{"comment_id": bson.M{"$in": commentIds}}) + if err != nil { + global.LOG.Errorf("Failed to get images for comments: %v", err) + return + } + defer func(cursor *mongo.Cursor, ctx context.Context) { + warn := cursor.Close(ctx) + if warn != nil { + return + } + }(cursor, ctx) + + for cursor.Next(ctx) { + var commentImages CommentImages + if e := cursor.Decode(&commentImages); e != nil { + global.LOG.Errorf("Failed to decode comment images: %v", e) + continue + } + commentImagesMap[commentImages.CommentId] = commentImages + } + }() + wg.Wait() + + replyChannel := make(chan CommentContent, len(page.Records)) // 使用通道传递回复内容 + + for _, reply := range page.Records { + wg.Add(1) + go func(reply model.ScaCommentReply) { + defer wg.Done() + + var imagesBase64 []string + if imgData, ok := commentImagesMap[reply.Id]; ok { + // 将图片转换为base64 + for _, img := range imgData.Images { + mimeType := utils.GetMimeType(img) + base64Img := base64.StdEncoding.EncodeToString(img) + base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img) + imagesBase64 = append(imagesBase64, base64WithPrefix) + } + } + userInfo, exist := userInfoMap[reply.UserId] + if !exist { + global.LOG.Errorf("Failed to get user info for comment: %s", reply.UserId) + return + } + replyUserInfo, e := userInfoMap[reply.ReplyUser] + if !e { + global.LOG.Errorf("Failed to get reply user info for comment: %s", reply.ReplyUser) + return + } + commentContent := CommentContent{ + Avatar: *userInfo.Avatar, + NickName: *userInfo.Nickname, + Id: reply.Id, + UserId: reply.UserId, + TopicId: reply.TopicId, + Content: reply.Content, + ReplyUsername: *replyUserInfo.Nickname, + ReplyCount: reply.ReplyCount, + Likes: reply.Likes, + CreatedTime: reply.CreatedTime, + ReplyUser: reply.ReplyUser, + ReplyId: reply.ReplyId, + ReplyTo: reply.ReplyTo, + Author: reply.Author, + Location: reply.Location, + Browser: reply.Browser, + OperatingSystem: reply.OperatingSystem, + Images: imagesBase64, + IsLiked: likeMap[reply.Id], + } + replyChannel <- commentContent // 发送到通道 + }(*reply) + } + + go func() { + wg.Wait() + close(replyChannel) // 关闭通道 + }() + + var repliesWithImages []CommentContent + for replyContent := range replyChannel { + repliesWithImages = append(repliesWithImages, replyContent) // 从通道获取回复内容 + } + + response := CommentResponse{ + Comments: repliesWithImages, + Size: page.Size, + Current: page.Current, + Total: page.Total, + } + return &response +} + +// GetCommentListService 评论列表 +func (CommentReplyServiceImpl) GetCommentListService(uid string, topicId string, pageNum int, size int, isHot bool) *CommentResponse { + // 查询评论列表 + query, u := gplus.NewQuery[model.ScaCommentReply]() + page := gplus.NewPage[model.ScaCommentReply](pageNum, size) + if isHot { + query.OrderByDesc(&u.CommentOrder).OrderByDesc(&u.Likes).OrderByDesc(&u.ReplyCount) + } else { + query.OrderByDesc(&u.CommentOrder).OrderByDesc(&u.CreatedTime) + } + query.Eq(&u.TopicId, topicId).Eq(&u.CommentType, enum.COMMENT) + page, pageDB := gplus.SelectPage(page, query) + if pageDB.Error != nil { + global.LOG.Errorln(pageDB.Error) + return nil + } + if len(page.Records) == 0 { + return nil + } + + userIds := make([]string, 0, len(page.Records)) + commentIds := make([]int64, 0, len(page.Records)) + for _, comment := range page.Records { + userIds = append(userIds, comment.UserId) + commentIds = append(commentIds, comment.Id) + } + + // 结果存储 + userInfoMap := make(map[string]model.ScaAuthUser) + likeMap := make(map[int64]bool) + commentImagesMap := make(map[int64]CommentImages) + + // 使用 WaitGroup 等待协程完成 + wg.Add(3) + + // 查询评论用户信息 + go func() { + defer wg.Done() + queryUser, n := gplus.NewQuery[model.ScaAuthUser]() + queryUser.Select(&n.UID, &n.Avatar, &n.Nickname).In(&n.UID, userIds) + userInfos, userInfosDB := gplus.SelectList(queryUser) + if userInfosDB.Error != nil { + global.LOG.Errorln(userInfosDB.Error) + return + } + for _, userInfo := range userInfos { + userInfoMap[*userInfo.UID] = *userInfo + } + }() + + // 查询评论点赞状态 + go func() { + defer wg.Done() + if len(page.Records) > 0 { + queryLike, l := gplus.NewQuery[model.ScaCommentLikes]() + queryLike.Eq(&l.TopicId, topicId).Eq(&l.UserId, uid).In(&l.CommentId, commentIds) + likes, likesDB := gplus.SelectList(queryLike) + if likesDB.Error != nil { + global.LOG.Errorln(likesDB.Error) + return + } + for _, like := range likes { + likeMap[like.CommentId] = true + } + } + }() + + // 查询评论图片信息 + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 设置超时,2秒 + defer cancel() + + cursor, err := global.MongoDB.Database(global.CONFIG.MongoDB.DB).Collection("comment_images").Find(ctx, bson.M{"comment_id": bson.M{"$in": commentIds}}) + if err != nil { + global.LOG.Errorf("Failed to get images for comments: %v", err) + return + } + defer func(cursor *mongo.Cursor, ctx context.Context) { + err := cursor.Close(ctx) + if err != nil { + return + } + }(cursor, ctx) + + for cursor.Next(ctx) { + var commentImages CommentImages + if err = cursor.Decode(&commentImages); err != nil { + global.LOG.Errorf("Failed to decode comment images: %v", err) + continue + } + commentImagesMap[commentImages.CommentId] = commentImages + } + }() + + // 等待所有查询完成 + wg.Wait() + commentChannel := make(chan CommentContent, len(page.Records)) + + for _, comment := range page.Records { + wg.Add(1) + go func(comment model.ScaCommentReply) { + defer wg.Done() + // 将图片转换为base64 + var imagesBase64 []string + if imgData, ok := commentImagesMap[comment.Id]; ok { + // 将图片转换为base64 + for _, img := range imgData.Images { + mimeType := utils.GetMimeType(img) + base64Img := base64.StdEncoding.EncodeToString(img) + base64WithPrefix := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Img) + imagesBase64 = append(imagesBase64, base64WithPrefix) + } + } + + userInfo, exist := userInfoMap[comment.UserId] + if !exist { + global.LOG.Errorf("Failed to get user info for comment: %s", comment.UserId) + return + } + commentContent := CommentContent{ + Avatar: *userInfo.Avatar, + NickName: *userInfo.Nickname, + Id: comment.Id, + UserId: comment.UserId, + TopicId: comment.TopicId, + Content: comment.Content, + ReplyCount: comment.ReplyCount, + Likes: comment.Likes, + CreatedTime: comment.CreatedTime, + Author: comment.Author, + Location: comment.Location, + Browser: comment.Browser, + OperatingSystem: comment.OperatingSystem, + Images: imagesBase64, + IsLiked: likeMap[comment.Id], + } + commentChannel <- commentContent + }(*comment) + } + + go func() { + wg.Wait() + close(commentChannel) + }() + + var commentsWithImages []CommentContent + for commentContent := range commentChannel { + commentsWithImages = append(commentsWithImages, commentContent) + } + + response := CommentResponse{ + Comments: commentsWithImages, + Size: page.Size, + Current: page.Current, + Total: page.Total, + } + return &response +} + +// CommentLikeService 评论点赞 +func (CommentReplyServiceImpl) CommentLikeService(uid string, commentId int64, topicId string) bool { + mx.Lock() + defer mx.Unlock() + likes := model.ScaCommentLikes{ + CommentId: commentId, + UserId: uid, + TopicId: topicId, + } + + tx := global.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + res := global.DB.Create(&likes) // 假设这是插入数据库的方法 + if res.Error != nil { + tx.Rollback() + global.LOG.Errorln(res.Error) + return false + } + + // 异步更新点赞计数 + go func() { + if err := commentReplyDao.UpdateCommentLikesCount(commentId, topicId); err != nil { + global.LOG.Errorln(err) + } + }() + tx.Commit() + return true +} + +// CommentDislikeService 取消评论点赞 +func (CommentReplyServiceImpl) CommentDislikeService(uid string, commentId int64, topicId string) bool { + mx.Lock() + defer mx.Unlock() + + tx := global.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + query, u := gplus.NewQuery[model.ScaCommentLikes]() + query.Eq(&u.CommentId, commentId). + Eq(&u.UserId, uid). + Eq(&u.TopicId, topicId) + + res := gplus.Delete[model.ScaCommentLikes](query) + if res.Error != nil { + tx.Rollback() + return false + } + + // 异步更新点赞计数 + go func() { + if err := commentReplyDao.DecrementCommentLikesCount(commentId, topicId); err != nil { + global.LOG.Errorln(err) + } + }() + tx.Commit() + return true } diff --git a/service/impl/user_service_impl.go b/service/impl/user_service_impl.go index 1661493..d33394c 100644 --- a/service/impl/user_service_impl.go +++ b/service/impl/user_service_impl.go @@ -1,14 +1,36 @@ package impl import ( + "encoding/json" + "schisandra-cloud-album/common/constant" + "schisandra-cloud-album/common/redis" "schisandra-cloud-album/dao/impl" + "schisandra-cloud-album/global" "schisandra-cloud-album/model" + "schisandra-cloud-album/utils" + "time" ) var userDao = impl.UserDaoImpl{} type UserServiceImpl struct{} +// ResponseData 返回数据 +type ResponseData struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresAt int64 `json:"expires_at"` + UID *string `json:"uid"` +} + +func (res ResponseData) MarshalBinary() ([]byte, error) { + return json.Marshal(res) +} + +func (res ResponseData) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, &res) +} + // GetUserListService 返回用户列表 func (UserServiceImpl) GetUserListService() []*model.ScaAuthUser { return userDao.GetUserList() @@ -52,3 +74,32 @@ func (UserServiceImpl) AddUserService(user model.ScaAuthUser) (*model.ScaAuthUse func (UserServiceImpl) UpdateUserService(phone, encrypt string) error { return userDao.UpdateUser(phone, encrypt) } + +// RefreshTokenService 刷新用户token +func (UserServiceImpl) RefreshTokenService(refreshToken string) (*ResponseData, bool) { + parseRefreshToken, isUpd, err := utils.ParseRefreshToken(refreshToken) + if err != nil || !isUpd { + global.LOG.Errorln(err) + return nil, false + } + accessTokenString, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: parseRefreshToken.UserID}) + if err != nil { + return nil, false + } + tokenKey := constant.UserLoginTokenRedisKey + *parseRefreshToken.UserID + token, err := redis.Get(tokenKey).Result() + if err != nil || token == "" { + global.LOG.Errorln(err) + return nil, false + } + data := ResponseData{ + AccessToken: accessTokenString, + RefreshToken: refreshToken, + UID: parseRefreshToken.UserID, + } + if err = redis.Set(tokenKey, data, time.Hour*24*7).Err(); err != nil { + global.LOG.Errorln(err) + return nil, false + } + return &data, true +} diff --git a/service/user_service.go b/service/user_service.go index 3b924bf..785a608 100644 --- a/service/user_service.go +++ b/service/user_service.go @@ -1,6 +1,9 @@ package service -import "schisandra-cloud-album/model" +import ( + "schisandra-cloud-album/model" + "schisandra-cloud-album/service/impl" +) type UserService interface { // GetUserListService 返回用户列表 @@ -19,4 +22,6 @@ type UserService interface { AddUserService(user model.ScaAuthUser) (*model.ScaAuthUser, error) // UpdateUserService 更新用户信息 UpdateUserService(phone, encrypt string) error + // RefreshTokenService 刷新token + RefreshTokenService(refreshToken string) (*impl.ResponseData, bool) } diff --git a/controller/comment_controller/handler.go b/utils/image_util.go similarity index 83% rename from controller/comment_controller/handler.go rename to utils/image_util.go index e547cb7..11a606f 100644 --- a/controller/comment_controller/handler.go +++ b/utils/image_util.go @@ -1,4 +1,4 @@ -package comment_controller +package utils import ( "encoding/base64" @@ -6,50 +6,13 @@ import ( "io" "regexp" "strings" + "sync" ) -// base64ToBytes 将base64字符串转换为字节数组 -func base64ToBytes(base64Str string) ([]byte, error) { - reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64Str)) - data, err := io.ReadAll(reader) - if err != nil { - return nil, errors.New("failed to decode base64 string") - } - return data, nil -} +var wg sync.WaitGroup -// processImages 处理图片,将 base64 字符串转换为字节数组 -func processImages(images []string) ([][]byte, error) { - var imagesData [][]byte - dataChan := make(chan []byte, len(images)) // 创建一个带缓冲的 channel - re := regexp.MustCompile(`^data:image/\w+;base64,`) - - for _, img := range images { - wg.Add(1) // 增加 WaitGroup 的计数 - go func(img string) { - defer wg.Done() // 函数结束时减少计数 - - imgWithoutPrefix := re.ReplaceAllString(img, "") - data, err := base64ToBytes(imgWithoutPrefix) - if err != nil { - return // 出错时直接返回 - } - dataChan <- data // 将结果发送到 channel - }(img) - } - - wg.Wait() // 等待所有 goroutine 完成 - close(dataChan) // 关闭 channel - - for data := range dataChan { // 收集所有结果 - imagesData = append(imagesData, data) - } - - return imagesData, nil -} - -// getMimeType 获取 MIME 类型 -func getMimeType(data []byte) string { +// GetMimeType 获取 MIME 类型 +func GetMimeType(data []byte) string { if len(data) < 4 { return "application/octet-stream" // 默认类型 } @@ -85,3 +48,43 @@ func getMimeType(data []byte) string { return "application/octet-stream" // 默认类型 } + +// ProcessImages 处理图片,将 base64 字符串转换为字节数组 +func ProcessImages(images []string) ([][]byte, error) { + var imagesData [][]byte + dataChan := make(chan []byte, len(images)) // 创建一个带缓冲的 channel + re := regexp.MustCompile(`^data:image/\w+;base64,`) + + for _, img := range images { + wg.Add(1) // 增加 WaitGroup 的计数 + go func(img string) { + defer wg.Done() // 函数结束时减少计数 + + imgWithoutPrefix := re.ReplaceAllString(img, "") + data, err := Base64ToBytes(imgWithoutPrefix) + if err != nil { + return // 出错时直接返回 + } + dataChan <- data // 将结果发送到 channel + }(img) + } + + wg.Wait() // 等待所有 goroutine 完成 + close(dataChan) // 关闭 channel + + for data := range dataChan { // 收集所有结果 + imagesData = append(imagesData, data) + } + + return imagesData, nil +} + +// Base64ToBytes 将base64字符串转换为字节数组 +func Base64ToBytes(base64Str string) ([]byte, error) { + reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64Str)) + data, err := io.ReadAll(reader) + if err != nil { + return nil, errors.New("failed to decode base64 string") + } + return data, nil +}