From fcbbbbabffa6812ea836c1758a4731f4c2fd67f1 Mon Sep 17 00:00:00 2001 From: landaiqing <3517283258@qq.com> Date: Tue, 1 Oct 2024 17:31:05 +0800 Subject: [PATCH] :recycle: update code structure --- common/constant/redis_key.go | 4 + common/enum/comment_like.go | 6 + .../comment_controller/comment_controller.go | 2 +- controller/oauth_controller/oauth.go | 70 ------ controller/user_controller/handler.go | 134 ----------- controller/user_controller/user_controller.go | 93 +++++++- go.mod | 2 +- go.sum | 2 - model/sca_comment_likes.go | 11 +- mq/comment_like_mq.go | 112 +++++++-- mq/comment_reply_mq.go | 37 +++ router/modules/oauth_router.go | 1 - router/modules/user_router.go | 2 + service/impl/comment_reply_service_impl.go | 216 +++++++++++------- service/impl/user_service_impl.go | 118 ++++++++++ service/user_service.go | 3 + 16 files changed, 491 insertions(+), 322 deletions(-) create mode 100644 common/enum/comment_like.go delete mode 100644 controller/user_controller/handler.go create mode 100644 mq/comment_reply_mq.go diff --git a/common/constant/redis_key.go b/common/constant/redis_key.go index e139fa9..402449d 100644 --- a/common/constant/redis_key.go +++ b/common/constant/redis_key.go @@ -14,6 +14,10 @@ const ( const ( CommentSubmitCaptchaRedisKey = "comment:submit:captcha:" CommentOfflineMessageRedisKey = "comment:offline:message:" + CommentLikeLockRedisKey = "comment:like:lock:" + CommentDislikeLockRedisKey = "comment:dislike:lock:" + CommentLikeListRedisKey = "comment:like:list:" + CommentUserListRedisKey = "comment:user:list:" ) // 系统相关的redis key diff --git a/common/enum/comment_like.go b/common/enum/comment_like.go new file mode 100644 index 0000000..0a51867 --- /dev/null +++ b/common/enum/comment_like.go @@ -0,0 +1,6 @@ +package enum + +var ( + CommentLike = 0 + CommentDislike = 1 +) diff --git a/controller/comment_controller/comment_controller.go b/controller/comment_controller/comment_controller.go index a35682e..e878ca8 100644 --- a/controller/comment_controller/comment_controller.go +++ b/controller/comment_controller/comment_controller.go @@ -263,7 +263,7 @@ func (CommentController) ReplyList(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) return } - response := commentReplyService.GetCommentReplyService(replyListRequest.UserID, replyListRequest.TopicId, replyListRequest.CommentId, replyListRequest.Page, replyListRequest.Size) + response := commentReplyService.GetCommentReplyListService(replyListRequest.UserID, replyListRequest.TopicId, replyListRequest.CommentId, replyListRequest.Page, replyListRequest.Size) result.OkWithData(response, c) return } diff --git a/controller/oauth_controller/oauth.go b/controller/oauth_controller/oauth.go index cd4ffc7..4f9aa89 100644 --- a/controller/oauth_controller/oauth.go +++ b/controller/oauth_controller/oauth.go @@ -2,30 +2,22 @@ package oauth_controller import ( "encoding/json" - "errors" "fmt" "github.com/gin-gonic/gin" - "github.com/mssola/useragent" - "gorm.io/gorm" "net/http" "schisandra-cloud-album/common/constant" "schisandra-cloud-album/common/redis" "schisandra-cloud-album/common/result" "schisandra-cloud-album/global" - "schisandra-cloud-album/model" "schisandra-cloud-album/service/impl" "schisandra-cloud-album/utils" - "sync" "time" ) -var mu sync.Mutex - type OAuthController struct{} var userSocialService = impl.UserSocialServiceImpl{} var userService = impl.UserServiceImpl{} -var userDeviceService = impl.UserDeviceServiceImpl{} type Token struct { AccessToken string `json:"access_token"` @@ -129,65 +121,3 @@ func HandelUserLogin(userId string, c *gin.Context) (bool, result.Response) { } return true, responseData } - -// GetUserLoginDevice 获取用户登录设备 -func (OAuthController) GetUserLoginDevice(c *gin.Context) { - userId := c.Query("user_id") - if userId == "" { - return - } - userAgent := c.GetHeader("User-Agent") - if userAgent == "" { - global.LOG.Errorln("user-agent is empty") - return - } - ua := useragent.New(userAgent) - - ip := utils.GetClientIP(c) - location, err := global.IP2Location.SearchByStr(ip) - location = utils.RemoveZeroAndAdjust(location) - if err != nil { - global.LOG.Errorln(err) - return - } - isBot := ua.Bot() - browser, browserVersion := ua.Browser() - os := ua.OS() - mobile := ua.Mobile() - mozilla := ua.Mozilla() - platform := ua.Platform() - engine, engineVersion := ua.Engine() - device := model.ScaAuthUserDevice{ - UserID: &userId, - IP: &ip, - Location: &location, - Agent: userAgent, - Browser: &browser, - BrowserVersion: &browserVersion, - OperatingSystem: &os, - Mobile: &mobile, - Bot: &isBot, - Mozilla: &mozilla, - Platform: &platform, - EngineName: &engine, - EngineVersion: &engineVersion, - } - mu.Lock() - defer mu.Unlock() - userDevice, err := userDeviceService.GetUserDeviceByUIDIPAgentService(userId, ip, userAgent) - if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { - err = userDeviceService.AddUserDeviceService(&device) - if err != nil { - global.LOG.Errorln(err) - return - } - return - } else { - err := userDeviceService.UpdateUserDeviceService(userDevice.ID, &device) - if err != nil { - global.LOG.Errorln(err) - return - } - return - } -} diff --git a/controller/user_controller/handler.go b/controller/user_controller/handler.go deleted file mode 100644 index 48f9ec5..0000000 --- a/controller/user_controller/handler.go +++ /dev/null @@ -1,134 +0,0 @@ -package user_controller - -import ( - "encoding/gob" - "errors" - ginI18n "github.com/gin-contrib/i18n" - "github.com/gin-gonic/gin" - "github.com/mssola/useragent" - "gorm.io/gorm" - "schisandra-cloud-album/common/constant" - "schisandra-cloud-album/common/redis" - "schisandra-cloud-album/common/result" - "schisandra-cloud-album/global" - "schisandra-cloud-album/model" - "schisandra-cloud-album/utils" - "time" -) - -// getUserLoginDevice 获取用户登录设备 -func getUserLoginDevice(user model.ScaAuthUser, c *gin.Context) bool { - - // 检查user.UID是否为空 - if user.UID == nil { - global.LOG.Errorln("user.UID is nil") - return false - } - userAgent := c.GetHeader("User-Agent") - if userAgent == "" { - global.LOG.Errorln("user-agent is empty") - return false - } - ua := useragent.New(userAgent) - - ip := utils.GetClientIP(c) - location, err := global.IP2Location.SearchByStr(ip) - if err != nil { - global.LOG.Errorln(err) - return false - } - location = utils.RemoveZeroAndAdjust(location) - - isBot := ua.Bot() - browser, browserVersion := ua.Browser() - os := ua.OS() - mobile := ua.Mobile() - mozilla := ua.Mozilla() - platform := ua.Platform() - engine, engineVersion := ua.Engine() - - device := model.ScaAuthUserDevice{ - UserID: user.UID, - IP: &ip, - Location: &location, - Agent: userAgent, - Browser: &browser, - BrowserVersion: &browserVersion, - OperatingSystem: &os, - Mobile: &mobile, - Bot: &isBot, - Mozilla: &mozilla, - Platform: &platform, - EngineName: &engine, - EngineVersion: &engineVersion, - } - - mu.Lock() - defer mu.Unlock() - - userDevice, err := userDeviceService.GetUserDeviceByUIDIPAgentService(*user.UID, ip, userAgent) - if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { - err = userDeviceService.AddUserDeviceService(&device) - if err != nil { - global.LOG.Errorln(err) - return false - } - } else if err != nil { - global.LOG.Errorln(err) - return false - } else { - err := userDeviceService.UpdateUserDeviceService(userDevice.ID, &device) - if err != nil { - global.LOG.Errorln(err) - return false - } - } - - return true -} - -// handelUserLogin 处理用户登录 -func handelUserLogin(user model.ScaAuthUser, autoLogin bool, c *gin.Context) { - // 检查 user.UID 是否为 nil - if user.UID == nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "ParamsError"), c) - return - } - if !getUserLoginDevice(user, c) { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) - return - } - accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: user.UID}) - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) - return - } - - var days time.Duration - if autoLogin { - days = 7 * 24 * time.Hour - } else { - days = time.Minute * 30 - } - - refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: user.UID}, days) - data := ResponseData{ - AccessToken: accessToken, - RefreshToken: refreshToken, - ExpiresAt: expiresAt, - UID: user.UID, - } - - err = redis.Set(constant.UserLoginTokenRedisKey+*user.UID, data, days).Err() - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) - return - } - gob.Register(ResponseData{}) - err = utils.SetSession(c, constant.SessionKey, data) - if err != nil { - result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) - return - } - result.OkWithData(data, c) -} diff --git a/controller/user_controller/user_controller.go b/controller/user_controller/user_controller.go index 825cc77..bbd7531 100644 --- a/controller/user_controller/user_controller.go +++ b/controller/user_controller/user_controller.go @@ -1,8 +1,10 @@ package user_controller import ( + "errors" ginI18n "github.com/gin-contrib/i18n" "github.com/gin-gonic/gin" + "github.com/mssola/useragent" "github.com/yitter/idgenerator-go/idgen" "gorm.io/gorm" "reflect" @@ -141,7 +143,12 @@ func (UserController) AccountLogin(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "PasswordError"), c) return } - handelUserLogin(user, accountLoginRequest.AutoLogin, c) + data, res := userService.HandelUserLogin(user, accountLoginRequest.AutoLogin, c) + if !res { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + result.OkWithData(data, c) } // PhoneLogin 手机号登录/注册 @@ -209,16 +216,21 @@ func (UserController) PhoneLogin(c *gin.Context) { errChan := make(chan error) go func() { - err := global.DB.Transaction(func(tx *gorm.DB) error { - addUser, err := userService.AddUserService(createUser) - if err != nil { - return err + err = global.DB.Transaction(func(tx *gorm.DB) error { + addUser, w := userService.AddUserService(createUser) + if w != nil { + return w } _, err = global.Casbin.AddRoleForUser(uidStr, enum.User) if err != nil { return err } - handelUserLogin(*addUser, autoLogin, c) + data, res := userService.HandelUserLogin(*addUser, autoLogin, c) + if !res { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return errors.New("login failed") + } + result.OkWithData(data, c) return nil }) errChan <- err @@ -249,7 +261,12 @@ func (UserController) PhoneLogin(c *gin.Context) { result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaError"), c) return } - handelUserLogin(user, autoLogin, c) + data, res := userService.HandelUserLogin(user, autoLogin, c) + if !res { + result.FailWithMessage(ginI18n.MustGetMessage(c, "LoginFailed"), c) + return + } + result.OkWithData(data, c) } } @@ -384,3 +401,65 @@ func (UserController) Logout(c *gin.Context) { } result.OkWithMessage(ginI18n.MustGetMessage(c, "LogoutSuccess"), c) } + +// GetUserLoginDevice 获取用户登录设备 +func (UserController) GetUserLoginDevice(c *gin.Context) { + userId := c.Query("user_id") + if userId == "" { + return + } + userAgent := c.GetHeader("User-Agent") + if userAgent == "" { + global.LOG.Errorln("user-agent is empty") + return + } + ua := useragent.New(userAgent) + + ip := utils.GetClientIP(c) + location, err := global.IP2Location.SearchByStr(ip) + location = utils.RemoveZeroAndAdjust(location) + if err != nil { + global.LOG.Errorln(err) + return + } + isBot := ua.Bot() + browser, browserVersion := ua.Browser() + os := ua.OS() + mobile := ua.Mobile() + mozilla := ua.Mozilla() + platform := ua.Platform() + engine, engineVersion := ua.Engine() + device := model.ScaAuthUserDevice{ + UserID: &userId, + IP: &ip, + Location: &location, + Agent: userAgent, + Browser: &browser, + BrowserVersion: &browserVersion, + OperatingSystem: &os, + Mobile: &mobile, + Bot: &isBot, + Mozilla: &mozilla, + Platform: &platform, + EngineName: &engine, + EngineVersion: &engineVersion, + } + mu.Lock() + defer mu.Unlock() + userDevice, err := userDeviceService.GetUserDeviceByUIDIPAgentService(userId, ip, userAgent) + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = userDeviceService.AddUserDeviceService(&device) + if err != nil { + global.LOG.Errorln(err) + return + } + return + } else { + err := userDeviceService.UpdateUserDeviceService(userDevice.ID, &device) + if err != nil { + global.LOG.Errorln(err) + return + } + return + } +} diff --git a/go.mod b/go.mod index dc7414b..1a293b5 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20240510055607-89e20ab7b6c6 github.com/lxzan/gws v1.8.8 github.com/mssola/useragent v1.0.0 + github.com/nsqio/go-nsq v1.1.0 github.com/pkg6/go-sms v0.1.2 github.com/rbcervilla/redisstore/v9 v9.0.0 github.com/redis/go-redis/v9 v9.6.1 @@ -94,7 +95,6 @@ require ( github.com/montanaflynn/stats v0.7.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect - github.com/nsqio/go-nsq v1.1.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index c4bf4ad..70e262d 100644 --- a/go.sum +++ b/go.sum @@ -396,8 +396,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.2.1 h1:r+g0bk4LPCW2v4+Ls7aeNgGme7JYdNDQ2VtvlNUfBh0= -gorm.io/datatypes v1.2.1/go.mod h1:hYK6OTb/1x+m96PgoZZq10UXJ6RvEBb9kRDQ2yyhzGs= gorm.io/datatypes v1.2.2 h1:sdn7ZmG4l7JWtMDUb3L98f2Ym7CO5F8mZLlrQJMfF9g= gorm.io/datatypes v1.2.2/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= diff --git a/model/sca_comment_likes.go b/model/sca_comment_likes.go index b48f93c..8a42922 100644 --- a/model/sca_comment_likes.go +++ b/model/sca_comment_likes.go @@ -1,13 +1,16 @@ package model +import "time" + const ScaCommentLikesTableName = "sca_comment_likes" // ScaCommentLikes 评论点赞表 type ScaCommentLikes struct { - Id int64 `gorm:"column:id;type:bigint(20);primary_key" json:"id"` - TopicId string `gorm:"column:topic_id;type:varchar(20);NOT NULL;comment:主题ID" json:"topic_id"` - UserId string `gorm:"column:user_id;type:varchar(20);comment:用户ID;NOT NULL" json:"user_id"` - CommentId int64 `gorm:"column:comment_id;type:bigint(20);comment:评论ID;NOT NULL" json:"comment_id"` + Id int64 `gorm:"column:id;type:bigint(20);primary_key" json:"id"` + TopicId string `gorm:"column:topic_id;type:varchar(20);NOT NULL;comment:主题ID" json:"topic_id"` + UserId string `gorm:"column:user_id;type:varchar(20);comment:用户ID;NOT NULL" json:"user_id"` + CommentId int64 `gorm:"column:comment_id;type:bigint(20);comment:评论ID;NOT NULL" json:"comment_id"` + LikeTime time.Time `gorm:"column:like_time;type:datetime;default:CURRENT_TIMESTAMP;comment:点赞时间;NOT NULL" json:"like_time"` } func (like *ScaCommentLikes) TableName() string { diff --git a/mq/comment_like_mq.go b/mq/comment_like_mq.go index 70df13c..26e962c 100644 --- a/mq/comment_like_mq.go +++ b/mq/comment_like_mq.go @@ -1,32 +1,34 @@ package mq import ( - "fmt" + "encoding/json" + "github.com/acmestack/gorm-plus/gplus" "github.com/nsqio/go-nsq" "log" + "schisandra-cloud-album/common/constant" + "schisandra-cloud-album/common/enum" + "schisandra-cloud-album/common/redis" "schisandra-cloud-album/core" + dao "schisandra-cloud-album/dao/impl" "schisandra-cloud-album/global" + "schisandra-cloud-album/model" ) const CommentLikeTopic = "comment_like" -type MessageHandler struct{} +type CommentLikeMessageHandler struct{} -func (h *MessageHandler) HandleMessage(m *nsq.Message) error { - if len(m.Body) == 0 { - // Returning nil will automatically send a FIN command to NSQ to mark the message as processed. - // In this case, a message with an empty body is simply ignored/discarded. - return nil - } +var commentReplyDao = dao.CommentReplyDaoImpl{} - // do whatever actual message processing is desired - //err := processMessage(m.Body) - fmt.Println("comment_like_mq:", string(m.Body)) - - // Returning a non-nil error will automatically send a REQ command to NSQ to re-queue the message. - return nil +// LikeData 点赞数据 +type LikeData struct { + CommentId int64 `json:"comment_id"` + UserId string `json:"user_id"` + TopicId string `json:"topic_id"` + Type int `json:"type"` } +// CommentLikeProducer 点赞消息生产 func CommentLikeProducer(messageBody []byte) { err := global.NSQProducer.Publish(CommentLikeTopic, messageBody) if err != nil { @@ -34,11 +36,91 @@ func CommentLikeProducer(messageBody []byte) { } } +// CommentLikeConsumer 点赞消息消费 func CommentLikeConsumer() { consumer := core.InitConsumer(CommentLikeTopic) - consumer.AddHandler(&MessageHandler{}) + consumer.AddHandler(&CommentLikeMessageHandler{}) err := consumer.ConnectToNSQD(global.CONFIG.NSQ.NsqAddr()) if err != nil { log.Fatal(err) } } + +// HandleMessage 处理消息 +func (h *CommentLikeMessageHandler) HandleMessage(m *nsq.Message) error { + if len(m.Body) == 0 { + return nil + } + + var likeData LikeData + if err := json.Unmarshal(m.Body, &likeData); err != nil { + global.LOG.Println(err) + return err + } + var err error + tx := global.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } else if err == nil { + err = tx.Commit().Error // 确保 Commit 错误也被捕获 + } else { + tx.Rollback() + } + }() + + switch likeData.Type { + case enum.CommentLike: + like := model.ScaCommentLikes{ + CommentId: likeData.CommentId, + UserId: likeData.UserId, + TopicId: likeData.TopicId, + } + if err = global.DB.Create(&like).Error; err != nil { + tx.Rollback() + global.LOG.Errorln(err) + return err + } + + err = commentReplyDao.UpdateCommentLikesCount(likeData.CommentId, likeData.TopicId) + if err != nil { + tx.Rollback() + global.LOG.Errorln(err) + return err + } + + if err = redis.SAdd(constant.CommentLikeListRedisKey+likeData.UserId+":"+likeData.TopicId, likeData.CommentId).Err(); err != nil { + tx.Rollback() + return err + } + + case enum.CommentDislike: // 取消点赞 + query, u := gplus.NewQuery[model.ScaCommentLikes]() + query.Eq(&u.CommentId, likeData.CommentId). + Eq(&u.UserId, likeData.UserId). + Eq(&u.TopicId, likeData.TopicId) + if err = gplus.Delete[model.ScaCommentLikes](query).Error; err != nil { + tx.Rollback() + return err + } + + err = commentReplyDao.DecrementCommentLikesCount(likeData.CommentId, likeData.TopicId) + if err != nil { + tx.Rollback() + global.LOG.Errorln(err) + return err + } + + if err = redis.SRem(constant.CommentLikeListRedisKey+likeData.UserId+":"+likeData.TopicId, likeData.CommentId).Err(); err != nil { + global.LOG.Errorln(err) + return err + } + + default: + global.LOG.Println("unknown comment like type") + return nil + } + + tx.Commit() + return nil +} diff --git a/mq/comment_reply_mq.go b/mq/comment_reply_mq.go new file mode 100644 index 0000000..f5293be --- /dev/null +++ b/mq/comment_reply_mq.go @@ -0,0 +1,37 @@ +package mq + +import ( + "github.com/nsqio/go-nsq" + "log" + "schisandra-cloud-album/core" + "schisandra-cloud-album/global" +) + +const CommentReplyTopic = "comment_reply" + +type CommentReplyMessageHandler struct{} + +// CommentReplyProducer 评论回复消息生产 +func CommentReplyProducer(messageBody []byte) { + err := global.NSQProducer.Publish(CommentReplyTopic, messageBody) + if err != nil { + global.LOG.Fatal(err) + } +} + +// CommentReplyConsumer 评论回复消息消费 +func CommentReplyConsumer() { + consumer := core.InitConsumer(CommentReplyTopic) + consumer.AddHandler(&CommentReplyMessageHandler{}) + err := consumer.ConnectToNSQD(global.CONFIG.NSQ.NsqAddr()) + if err != nil { + log.Fatal(err) + } +} + +func (h *CommentReplyMessageHandler) HandleMessage(m *nsq.Message) error { + if len(m.Body) == 0 { + return nil + } + return nil +} diff --git a/router/modules/oauth_router.go b/router/modules/oauth_router.go index 12c8be5..9e5b8f3 100644 --- a/router/modules/oauth_router.go +++ b/router/modules/oauth_router.go @@ -48,6 +48,5 @@ func OauthRouterAuth(router *gin.RouterGroup) { { qqRouter.GET("/get_url", oauth.GetQQRedirectUrl) } - group.GET("/get_device", oauth.GetUserLoginDevice) } } diff --git a/router/modules/user_router.go b/router/modules/user_router.go index e2886bf..b1d145c 100644 --- a/router/modules/user_router.go +++ b/router/modules/user_router.go @@ -14,11 +14,13 @@ func UserRouter(router *gin.RouterGroup) { userGroup.POST("/login", userApi.AccountLogin) userGroup.POST("/phone_login", userApi.PhoneLogin) userGroup.POST("/reset_password", userApi.ResetPassword) + userGroup.GET("/get_device", userApi.GetUserLoginDevice) } tokenGroup := router.Group("token") { tokenGroup.POST("/refresh", userApi.RefreshHandler) } + } // UserRouterAuth 用户相关路由 有auth接口组需要token验证 diff --git a/service/impl/comment_reply_service_impl.go b/service/impl/comment_reply_service_impl.go index b1cd9b8..7b0fdbc 100644 --- a/service/impl/comment_reply_service_impl.go +++ b/service/impl/comment_reply_service_impl.go @@ -3,15 +3,20 @@ package impl import ( "context" "encoding/base64" + "encoding/json" "fmt" "github.com/acmestack/gorm-plus/gplus" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" + "schisandra-cloud-album/common/constant" "schisandra-cloud-album/common/enum" + "schisandra-cloud-album/common/redis" "schisandra-cloud-album/dao/impl" "schisandra-cloud-album/global" "schisandra-cloud-album/model" + "schisandra-cloud-album/mq" "schisandra-cloud-album/utils" + "strconv" "sync" "time" ) @@ -64,6 +69,14 @@ type CommentResponse struct { Comments []CommentContent `json:"comments"` } +// LikeData 点赞 +type LikeData struct { + CommentId int64 `json:"comment_id"` + UserId string `json:"user_id"` + TopicId string `json:"topic_id"` + Type int `json:"type"` +} + // SubmitCommentService 提交评论 func (CommentReplyServiceImpl) SubmitCommentService(comment *model.ScaCommentReply, topicId string, uid string, images []string) bool { tx := global.DB.Begin() @@ -72,19 +85,20 @@ func (CommentReplyServiceImpl) SubmitCommentService(comment *model.ScaCommentRep 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 { + err := commentReplyDao.CreateCommentReply(comment) + if err != nil { global.LOG.Errorln(err) - tx.Rollback() return false } + if comment.ReplyId != 0 { + err = commentReplyDao.UpdateCommentReplyCount(comment.ReplyId) + if err != nil { + global.LOG.Errorln(err) + tx.Rollback() + return false + } + } if len(images) > 0 { imagesDataCh := make(chan [][]byte) @@ -123,8 +137,8 @@ func (CommentReplyServiceImpl) SubmitCommentService(comment *model.ScaCommentRep return true } -// GetCommentReplyService 获取评论回复 -func (CommentReplyServiceImpl) GetCommentReplyService(uid string, topicId string, commentId int64, pageNum int, size int) *CommentResponse { +// GetCommentReplyListService 获取评论回复列表 +func (CommentReplyServiceImpl) GetCommentReplyListService(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) @@ -135,7 +149,6 @@ func (CommentReplyServiceImpl) GetCommentReplyService(uid string, topicId string } if len(page.Records) == 0 { return nil - } userIdsSet := make(map[string]struct{}) // 使用集合去重用户 ID @@ -162,15 +175,34 @@ func (CommentReplyServiceImpl) GetCommentReplyService(uid string, topicId string 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 + var user = model.ScaAuthUser{} + var missingUserIds []string + for _, userId := range userIds { + redisKey := constant.CommentUserListRedisKey + userId + userInfo := redis.Get(redisKey).Val() + if userInfo != "" { + err := json.Unmarshal([]byte(userInfo), &user) + if err != nil { + global.LOG.Errorln(err) + continue + } + userInfoMap[userId] = user + } else { + missingUserIds = append(missingUserIds, userId) + } } - for _, userInfo := range userInfos { - userInfoMap[*userInfo.UID] = *userInfo + if len(missingUserIds) > 0 { + queryUser, n := gplus.NewQuery[model.ScaAuthUser]() + queryUser.Select(&n.UID, &n.Avatar, &n.Nickname).In(&n.UID, missingUserIds) + userInfos, userInfosDB := gplus.SelectList(queryUser) + if userInfosDB.Error != nil { + global.LOG.Errorln(userInfosDB.Error) + return + } + for _, userInfo := range userInfos { + userInfoMap[*userInfo.UID] = *userInfo + redis.Set(constant.CommentUserListRedisKey+*userInfo.UID, userInfo, 24*time.Hour) + } } }() @@ -328,32 +360,62 @@ func (CommentReplyServiceImpl) GetCommentListService(uid string, topicId string, // 查询评论用户信息 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 + var user = model.ScaAuthUser{} + var missingUserIds []string + for _, userId := range userIds { + redisKey := constant.CommentUserListRedisKey + userId + userInfo := redis.Get(redisKey).Val() + if userInfo != "" { + err := json.Unmarshal([]byte(userInfo), &user) + if err != nil { + global.LOG.Errorln(err) + continue + } + userInfoMap[userId] = user + } else { + missingUserIds = append(missingUserIds, userId) + } } - for _, userInfo := range userInfos { - userInfoMap[*userInfo.UID] = *userInfo + if len(missingUserIds) > 0 { + queryUser, n := gplus.NewQuery[model.ScaAuthUser]() + queryUser.Select(&n.UID, &n.Avatar, &n.Nickname).In(&n.UID, missingUserIds) + userInfos, userInfosDB := gplus.SelectList(queryUser) + if userInfosDB.Error != nil { + global.LOG.Errorln(userInfosDB.Error) + return + } + for _, userInfo := range userInfos { + userInfoMap[*userInfo.UID] = *userInfo + redis.Set(constant.CommentUserListRedisKey+*userInfo.UID, userInfo, 24*time.Hour) + } } }() - // 查询评论点赞状态 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 + redisKey := constant.CommentLikeListRedisKey + uid + ":" + topicId + // 查询 Redis 中的点赞状态 + for _, commentId := range commentIds { + exists, err := redis.SIsMember(redisKey, commentId).Result() + if err != nil { + global.LOG.Errorln(err) + return + } + likeMap[commentId] = exists // `exists` 为 true 则表示已点赞,false 则表示未点赞 } + //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 + // _ = redis.SAdd(redisKey, like.CommentId) + //} + } }() @@ -452,67 +514,47 @@ func (CommentReplyServiceImpl) GetCommentListService(uid string, topicId string, // CommentLikeService 评论点赞 func (CommentReplyServiceImpl) CommentLikeService(uid string, commentId int64, topicId string) bool { - mx.Lock() - defer mx.Unlock() - likes := model.ScaCommentLikes{ + key := constant.CommentLikeLockRedisKey + topicId + ":" + strconv.FormatInt(commentId, 10) + ":" + uid + locked, err := redis.Set(key, "locked", 5*time.Minute).Result() + if err != nil || locked != "OK" { + return false + } + defer redis.Del(key) + + likeData := LikeData{ CommentId: commentId, UserId: uid, TopicId: topicId, + Type: enum.CommentLike, } - - 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) + jsonData, err := json.Marshal(likeData) + if err != nil { + global.LOG.Error(err.Error()) return false } - - // 异步更新点赞计数 - go func() { - if err := commentReplyDao.UpdateCommentLikesCount(commentId, topicId); err != nil { - global.LOG.Errorln(err) - } - }() - tx.Commit() + mq.CommentLikeProducer(jsonData) 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() + key := constant.CommentDislikeLockRedisKey + topicId + ":" + strconv.FormatInt(commentId, 10) + ":" + uid + locked, err := redis.Set(key, "locked", 5*time.Minute).Result() + if err != nil || locked != "OK" { return false } - - // 异步更新点赞计数 - go func() { - if err := commentReplyDao.DecrementCommentLikesCount(commentId, topicId); err != nil { - global.LOG.Errorln(err) - } - }() - tx.Commit() + defer redis.Del(key) + likeData := LikeData{ + CommentId: commentId, + UserId: uid, + TopicId: topicId, + Type: enum.CommentDislike, + } + jsonData, err := json.Marshal(likeData) + if err != nil { + global.LOG.Error(err.Error()) + return false + } + mq.CommentLikeProducer(jsonData) return true } diff --git a/service/impl/user_service_impl.go b/service/impl/user_service_impl.go index d33394c..614e7eb 100644 --- a/service/impl/user_service_impl.go +++ b/service/impl/user_service_impl.go @@ -1,13 +1,19 @@ package impl import ( + "encoding/gob" "encoding/json" + "errors" + "github.com/gin-gonic/gin" + "github.com/mssola/useragent" + "gorm.io/gorm" "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" + "sync" "time" ) @@ -15,6 +21,8 @@ var userDao = impl.UserDaoImpl{} type UserServiceImpl struct{} +var mu = &sync.Mutex{} + // ResponseData 返回数据 type ResponseData struct { AccessToken string `json:"access_token"` @@ -103,3 +111,113 @@ func (UserServiceImpl) RefreshTokenService(refreshToken string) (*ResponseData, } return &data, true } + +// HandelUserLogin 处理用户登录 +func (UserServiceImpl) HandelUserLogin(user model.ScaAuthUser, autoLogin bool, c *gin.Context) (*ResponseData, bool) { + // 检查 user.UID 是否为 nil + if user.UID == nil { + return nil, false + } + if !GetUserLoginDevice(user, c) { + return nil, false + } + accessToken, err := utils.GenerateAccessToken(utils.AccessJWTPayload{UserID: user.UID}) + if err != nil { + return nil, false + } + var days time.Duration + if autoLogin { + days = 7 * 24 * time.Hour + } else { + days = time.Minute * 30 + } + + refreshToken, expiresAt := utils.GenerateRefreshToken(utils.RefreshJWTPayload{UserID: user.UID}, days) + data := ResponseData{ + AccessToken: accessToken, + RefreshToken: refreshToken, + ExpiresAt: expiresAt, + UID: user.UID, + } + + err = redis.Set(constant.UserLoginTokenRedisKey+*user.UID, data, days).Err() + if err != nil { + return nil, false + } + gob.Register(ResponseData{}) + err = utils.SetSession(c, constant.SessionKey, data) + if err != nil { + return nil, false + } + return &data, true +} + +// GetUserLoginDevice 获取用户登录设备 +func GetUserLoginDevice(user model.ScaAuthUser, c *gin.Context) bool { + + // 检查user.UID是否为空 + if user.UID == nil { + global.LOG.Errorln("user.UID is nil") + return false + } + userAgent := c.GetHeader("User-Agent") + if userAgent == "" { + global.LOG.Errorln("user-agent is empty") + return false + } + ua := useragent.New(userAgent) + + ip := utils.GetClientIP(c) + location, err := global.IP2Location.SearchByStr(ip) + if err != nil { + global.LOG.Errorln(err) + return false + } + location = utils.RemoveZeroAndAdjust(location) + + isBot := ua.Bot() + browser, browserVersion := ua.Browser() + os := ua.OS() + mobile := ua.Mobile() + mozilla := ua.Mozilla() + platform := ua.Platform() + engine, engineVersion := ua.Engine() + + device := model.ScaAuthUserDevice{ + UserID: user.UID, + IP: &ip, + Location: &location, + Agent: userAgent, + Browser: &browser, + BrowserVersion: &browserVersion, + OperatingSystem: &os, + Mobile: &mobile, + Bot: &isBot, + Mozilla: &mozilla, + Platform: &platform, + EngineName: &engine, + EngineVersion: &engineVersion, + } + + mu.Lock() + defer mu.Unlock() + + userDevice, err := userDeviceDao.GetUserDeviceByUIDIPAgent(*user.UID, ip, userAgent) + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = userDeviceDao.AddUserDevice(&device) + if err != nil { + global.LOG.Errorln(err) + return false + } + } else if err != nil { + global.LOG.Errorln(err) + return false + } else { + err := userDeviceDao.UpdateUserDevice(userDevice.ID, &device) + if err != nil { + global.LOG.Errorln(err) + return false + } + } + return true +} diff --git a/service/user_service.go b/service/user_service.go index 785a608..bb50302 100644 --- a/service/user_service.go +++ b/service/user_service.go @@ -1,6 +1,7 @@ package service import ( + "github.com/gin-gonic/gin" "schisandra-cloud-album/model" "schisandra-cloud-album/service/impl" ) @@ -24,4 +25,6 @@ type UserService interface { UpdateUserService(phone, encrypt string) error // RefreshTokenService 刷新token RefreshTokenService(refreshToken string) (*impl.ResponseData, bool) + // HandelUserLogin 处理用户登录 + HandelUserLogin(user model.ScaAuthUser, autoLogin bool, c *gin.Context) (*impl.ResponseData, bool) }