style: 统一代码格式和行尾
This commit is contained in:
@@ -1,128 +1,127 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type CommentWorker struct {
|
||||
ch *amqp.Channel
|
||||
comments *video.CommentRepository
|
||||
videos *video.VideoRepository
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewCommentWorker(ch *amqp.Channel, comments *video.CommentRepository, videos *video.VideoRepository, queue string) *CommentWorker {
|
||||
return &CommentWorker{ch: ch, comments: comments, videos: videos, queue: queue}
|
||||
}
|
||||
|
||||
func (w *CommentWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.comments == nil || w.videos == nil {
|
||||
return errors.New("comment worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CommentWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("comment worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("comment worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *CommentWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.CommentEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
return nil
|
||||
}
|
||||
switch evt.Action {
|
||||
case "publish":
|
||||
return w.applyPublish(ctx, &evt)
|
||||
case "delete":
|
||||
return w.applyDelete(ctx, &evt)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CommentWorker) applyPublish(ctx context.Context, evt *rabbitmq.CommentEvent) error {
|
||||
if evt == nil || evt.VideoID == 0 || evt.AuthorID == 0 || strings.TrimSpace(evt.Content) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := w.videos.IsExist(ctx, evt.VideoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := &video.Comment{
|
||||
Username: strings.TrimSpace(evt.Username),
|
||||
VideoID: evt.VideoID,
|
||||
AuthorID: evt.AuthorID,
|
||||
Content: strings.TrimSpace(evt.Content),
|
||||
}
|
||||
if err := w.comments.CreateComment(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.videos.ChangePopularity(ctx, evt.VideoID, 1)
|
||||
}
|
||||
|
||||
func (w *CommentWorker) applyDelete(ctx context.Context, evt *rabbitmq.CommentEvent) error {
|
||||
if evt == nil || evt.CommentID == 0 {
|
||||
return nil
|
||||
}
|
||||
c, err := w.comments.GetByID(ctx, evt.CommentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return w.comments.DeleteComment(ctx, c)
|
||||
}
|
||||
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type CommentWorker struct {
|
||||
ch *amqp.Channel
|
||||
comments *video.CommentRepository
|
||||
videos *video.VideoRepository
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewCommentWorker(ch *amqp.Channel, comments *video.CommentRepository, videos *video.VideoRepository, queue string) *CommentWorker {
|
||||
return &CommentWorker{ch: ch, comments: comments, videos: videos, queue: queue}
|
||||
}
|
||||
|
||||
func (w *CommentWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.comments == nil || w.videos == nil {
|
||||
return errors.New("comment worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CommentWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("comment worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("comment worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *CommentWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.CommentEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
return nil
|
||||
}
|
||||
switch evt.Action {
|
||||
case "publish":
|
||||
return w.applyPublish(ctx, &evt)
|
||||
case "delete":
|
||||
return w.applyDelete(ctx, &evt)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CommentWorker) applyPublish(ctx context.Context, evt *rabbitmq.CommentEvent) error {
|
||||
if evt == nil || evt.VideoID == 0 || evt.AuthorID == 0 || strings.TrimSpace(evt.Content) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := w.videos.IsExist(ctx, evt.VideoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := &video.Comment{
|
||||
Username: strings.TrimSpace(evt.Username),
|
||||
VideoID: evt.VideoID,
|
||||
AuthorID: evt.AuthorID,
|
||||
Content: strings.TrimSpace(evt.Content),
|
||||
}
|
||||
if err := w.comments.CreateComment(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.videos.ChangePopularity(ctx, evt.VideoID, 1)
|
||||
}
|
||||
|
||||
func (w *CommentWorker) applyDelete(ctx context.Context, evt *rabbitmq.CommentEvent) error {
|
||||
if evt == nil || evt.CommentID == 0 {
|
||||
return nil
|
||||
}
|
||||
c, err := w.comments.GetByID(ctx, evt.CommentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return w.comments.DeleteComment(ctx, c)
|
||||
}
|
||||
|
||||
@@ -1,142 +1,142 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"log"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LikeWorker struct {
|
||||
ch *amqp.Channel
|
||||
likes *video.LikeRepository
|
||||
videos *video.VideoRepository
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewLikeWorker(ch *amqp.Channel, likes *video.LikeRepository, videos *video.VideoRepository, queue string) *LikeWorker {
|
||||
return &LikeWorker{ch: ch, likes: likes, videos: videos, queue: queue}
|
||||
}
|
||||
|
||||
func (w *LikeWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.likes == nil || w.videos == nil {
|
||||
return errors.New("like worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LikeWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("like worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("like worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *LikeWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.LikeEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
// 解析事件失败,直接丢弃
|
||||
return nil
|
||||
}
|
||||
if evt.UserID == 0 || evt.VideoID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch evt.Action {
|
||||
case "like":
|
||||
return w.applyLike(ctx, evt.UserID, evt.VideoID)
|
||||
case "unlike":
|
||||
return w.applyUnlike(ctx, evt.UserID, evt.VideoID)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LikeWorker) applyLike(ctx context.Context, userID, videoID uint) error {
|
||||
ok, err := w.videos.IsExist(ctx, videoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
created, err := w.likes.LikeIgnoreDuplicate(ctx, &video.Like{
|
||||
VideoID: videoID,
|
||||
AccountID: userID,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.videos.ChangeLikesCount(ctx, videoID, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.videos.ChangePopularity(ctx, videoID, 1)
|
||||
}
|
||||
|
||||
func (w *LikeWorker) applyUnlike(ctx context.Context, userID, videoID uint) error {
|
||||
ok, err := w.videos.IsExist(ctx, videoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
deleted, err := w.likes.DeleteByVideoAndAccount(ctx, videoID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !deleted {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.videos.ChangeLikesCount(ctx, videoID, -1); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.videos.ChangePopularity(ctx, videoID, -1)
|
||||
}
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
"feedsystem_video_go/internal/video"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LikeWorker struct {
|
||||
ch *amqp.Channel
|
||||
likes *video.LikeRepository
|
||||
videos *video.VideoRepository
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewLikeWorker(ch *amqp.Channel, likes *video.LikeRepository, videos *video.VideoRepository, queue string) *LikeWorker {
|
||||
return &LikeWorker{ch: ch, likes: likes, videos: videos, queue: queue}
|
||||
}
|
||||
|
||||
func (w *LikeWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.likes == nil || w.videos == nil {
|
||||
return errors.New("like worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LikeWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("like worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("like worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *LikeWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.LikeEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
// 解析事件失败,直接丢弃
|
||||
return nil
|
||||
}
|
||||
if evt.UserID == 0 || evt.VideoID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch evt.Action {
|
||||
case "like":
|
||||
return w.applyLike(ctx, evt.UserID, evt.VideoID)
|
||||
case "unlike":
|
||||
return w.applyUnlike(ctx, evt.UserID, evt.VideoID)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LikeWorker) applyLike(ctx context.Context, userID, videoID uint) error {
|
||||
ok, err := w.videos.IsExist(ctx, videoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
created, err := w.likes.LikeIgnoreDuplicate(ctx, &video.Like{
|
||||
VideoID: videoID,
|
||||
AccountID: userID,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.videos.ChangeLikesCount(ctx, videoID, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.videos.ChangePopularity(ctx, videoID, 1)
|
||||
}
|
||||
|
||||
func (w *LikeWorker) applyUnlike(ctx context.Context, userID, videoID uint) error {
|
||||
ok, err := w.videos.IsExist(ctx, videoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
deleted, err := w.likes.DeleteByVideoAndAccount(ctx, videoID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !deleted {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.videos.ChangeLikesCount(ctx, videoID, -1); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.videos.ChangePopularity(ctx, videoID, -1)
|
||||
}
|
||||
|
||||
@@ -13,21 +13,21 @@ import (
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
RecipientID uint `gorm:"index;not null" json:"recipient_id"`
|
||||
SenderID uint `gorm:"not null" json:"sender_id"`
|
||||
Type string `gorm:"type:varchar(50);not null" json:"type"`
|
||||
TargetID uint `json:"target_id"`
|
||||
Content string `gorm:"type:varchar(255)" json:"content"`
|
||||
IsRead bool `gorm:"default:false" json:"is_read"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
RecipientID uint `gorm:"index;not null" json:"recipient_id"`
|
||||
SenderID uint `gorm:"not null" json:"sender_id"`
|
||||
Type string `gorm:"type:varchar(50);not null" json:"type"`
|
||||
TargetID uint `json:"target_id"`
|
||||
Content string `gorm:"type:varchar(255)" json:"content"`
|
||||
IsRead bool `gorm:"default:false" json:"is_read"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
}
|
||||
|
||||
type NotificationWorker struct {
|
||||
ch *amqp.Channel
|
||||
db *gorm.DB
|
||||
queue string
|
||||
hub NotificationHub
|
||||
ch *amqp.Channel
|
||||
db *gorm.DB
|
||||
queue string
|
||||
hub NotificationHub
|
||||
}
|
||||
|
||||
type NotificationHub interface {
|
||||
@@ -96,7 +96,10 @@ func (w *NotificationWorker) process(ctx context.Context, d amqp.Delivery) error
|
||||
return nil
|
||||
}
|
||||
var authorID uint
|
||||
w.db.WithContext(ctx).Model(&struct{ ID uint; AuthorID uint }{}).Table("videos").Where("id = ?", evt.VideoID).Select("author_id").Scan(&authorID)
|
||||
w.db.WithContext(ctx).Model(&struct {
|
||||
ID uint
|
||||
AuthorID uint
|
||||
}{}).Table("videos").Where("id = ?", evt.VideoID).Select("author_id").Scan(&authorID)
|
||||
if authorID == 0 || authorID == evt.UserID {
|
||||
return nil
|
||||
}
|
||||
@@ -111,7 +114,10 @@ func (w *NotificationWorker) process(ctx context.Context, d amqp.Delivery) error
|
||||
return nil
|
||||
}
|
||||
var authorID uint
|
||||
w.db.WithContext(ctx).Model(&struct{ ID uint; AuthorID uint }{}).Table("videos").Where("id = ?", evt.VideoID).Select("author_id").Scan(&authorID)
|
||||
w.db.WithContext(ctx).Model(&struct {
|
||||
ID uint
|
||||
AuthorID uint
|
||||
}{}).Table("videos").Where("id = ?", evt.VideoID).Select("author_id").Scan(&authorID)
|
||||
if authorID == 0 || authorID == evt.AuthorID {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,85 +1,84 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
rediscache "feedsystem_video_go/internal/middleware/redis"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"log"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type PopularityWorker struct {
|
||||
ch *amqp.Channel
|
||||
cache *rediscache.Client
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewPopularityWorker(ch *amqp.Channel, cache *rediscache.Client, queue string) *PopularityWorker {
|
||||
return &PopularityWorker{ch: ch, cache: cache, queue: queue}
|
||||
}
|
||||
|
||||
func (w *PopularityWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.cache == nil {
|
||||
return errors.New("popularity worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *PopularityWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("popularity worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("popularity worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *PopularityWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.PopularityEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
return nil
|
||||
}
|
||||
if evt.VideoID == 0 || evt.Change == 0 {
|
||||
return nil
|
||||
}
|
||||
video.UpdatePopularityCache(ctx, w.cache, evt.VideoID, evt.Change)
|
||||
return nil
|
||||
}
|
||||
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
rediscache "feedsystem_video_go/internal/middleware/redis"
|
||||
"feedsystem_video_go/internal/video"
|
||||
"log"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type PopularityWorker struct {
|
||||
ch *amqp.Channel
|
||||
cache *rediscache.Client
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewPopularityWorker(ch *amqp.Channel, cache *rediscache.Client, queue string) *PopularityWorker {
|
||||
return &PopularityWorker{ch: ch, cache: cache, queue: queue}
|
||||
}
|
||||
|
||||
func (w *PopularityWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.cache == nil {
|
||||
return errors.New("popularity worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *PopularityWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("popularity worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("popularity worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *PopularityWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.PopularityEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
return nil
|
||||
}
|
||||
if evt.VideoID == 0 || evt.Change == 0 {
|
||||
return nil
|
||||
}
|
||||
video.UpdatePopularityCache(ctx, w.cache, evt.VideoID, evt.Change)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,106 +1,106 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
"feedsystem_video_go/internal/social"
|
||||
"log"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type SocialWorker struct {
|
||||
ch *amqp.Channel
|
||||
repo *social.SocialRepository
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewSocialWorker(ch *amqp.Channel, repo *social.SocialRepository, queue string) *SocialWorker {
|
||||
return &SocialWorker{ch: ch, repo: repo, queue: queue}
|
||||
}
|
||||
|
||||
func (w *SocialWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.repo == nil {
|
||||
return errors.New("social worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SocialWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("social worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("social worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *SocialWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.SocialEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
// 解析事件失败,直接丢弃
|
||||
return nil
|
||||
}
|
||||
if evt.FollowerID == 0 || evt.VloggerID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch evt.Action {
|
||||
case "follow":
|
||||
err := w.repo.Follow(ctx, &social.Social{
|
||||
FollowerID: evt.FollowerID,
|
||||
VloggerID: evt.VloggerID,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var mysqlErr *mysql.MySQLError
|
||||
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
case "unfollow":
|
||||
return w.repo.Unfollow(ctx, &social.Social{
|
||||
FollowerID: evt.FollowerID,
|
||||
VloggerID: evt.VloggerID,
|
||||
})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"feedsystem_video_go/internal/middleware/rabbitmq"
|
||||
"feedsystem_video_go/internal/social"
|
||||
"log"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type SocialWorker struct {
|
||||
ch *amqp.Channel
|
||||
repo *social.SocialRepository
|
||||
queue string
|
||||
}
|
||||
|
||||
func NewSocialWorker(ch *amqp.Channel, repo *social.SocialRepository, queue string) *SocialWorker {
|
||||
return &SocialWorker{ch: ch, repo: repo, queue: queue}
|
||||
}
|
||||
|
||||
func (w *SocialWorker) Run(ctx context.Context) error {
|
||||
if w == nil || w.ch == nil || w.repo == nil {
|
||||
return errors.New("social worker is not initialized")
|
||||
}
|
||||
if w.queue == "" {
|
||||
return errors.New("queue is required")
|
||||
}
|
||||
|
||||
deliveries, err := w.ch.Consume(
|
||||
w.queue,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case d, ok := <-deliveries:
|
||||
if !ok {
|
||||
return errors.New("deliveries channel closed")
|
||||
}
|
||||
w.handleDelivery(ctx, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SocialWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||
if err := w.process(ctx, d.Body); err != nil {
|
||||
retryCount := rabbitmq.GetRetryCount(d)
|
||||
if retryCount >= rabbitmq.MaxRetryCount {
|
||||
log.Printf("social worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||
_ = d.Ack(false)
|
||||
return
|
||||
}
|
||||
log.Printf("social worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||
_ = d.Nack(false, true)
|
||||
return
|
||||
}
|
||||
_ = d.Ack(false)
|
||||
}
|
||||
|
||||
func (w *SocialWorker) process(ctx context.Context, body []byte) error {
|
||||
var evt rabbitmq.SocialEvent
|
||||
if err := json.Unmarshal(body, &evt); err != nil {
|
||||
// 解析事件失败,直接丢弃
|
||||
return nil
|
||||
}
|
||||
if evt.FollowerID == 0 || evt.VloggerID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch evt.Action {
|
||||
case "follow":
|
||||
err := w.repo.Follow(ctx, &social.Social{
|
||||
FollowerID: evt.FollowerID,
|
||||
VloggerID: evt.VloggerID,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var mysqlErr *mysql.MySQLError
|
||||
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
case "unfollow":
|
||||
return w.repo.Unfollow(ctx, &social.Social{
|
||||
FollowerID: evt.FollowerID,
|
||||
VloggerID: evt.VloggerID,
|
||||
})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
)
|
||||
|
||||
type SSEHub struct {
|
||||
mu sync.RWMutex
|
||||
clients map[uint][]chan *Notification
|
||||
db *gorm.DB
|
||||
mu sync.RWMutex
|
||||
clients map[uint][]chan *Notification
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSSEHub(db *gorm.DB) *SSEHub {
|
||||
|
||||
Reference in New Issue
Block a user