2026-04-26 15:49:16 +08:00
|
|
|
|
package worker
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"feedsystem_video_go/internal/middleware/rabbitmq"
|
2026-05-22 23:57:06 +08:00
|
|
|
|
rediscache "feedsystem_video_go/internal/middleware/redis"
|
2026-04-26 15:49:16 +08:00
|
|
|
|
"feedsystem_video_go/internal/video"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
oredis "github.com/redis/go-redis/v9"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func StartOutboxPoller(db *gorm.DB, tmq *rabbitmq.TimelineMQ) {
|
2026-05-22 23:57:06 +08:00
|
|
|
|
if db == nil || tmq == nil {
|
2026-04-26 15:49:16 +08:00
|
|
|
|
log.Printf("Outbox poller disabled: timeline mq is not initialized")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
for {
|
|
|
|
|
|
var messages []video.OutboxMsg
|
|
|
|
|
|
|
|
|
|
|
|
err := db.Where("status = ?", "pending").Order("create_time ASC").Limit(100).Find(&messages).Error
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil || len(messages) == 0 {
|
|
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, msg := range messages {
|
|
|
|
|
|
err := tmq.PublishVideo(context.Background(), msg.VideoID, msg.CreateTime)
|
|
|
|
|
|
|
|
|
|
|
|
if err == nil {
|
2026-05-20 16:34:46 +08:00
|
|
|
|
if err := db.Delete(&msg).Error; err != nil {
|
|
|
|
|
|
log.Printf("删除 outbox 消息失败: id=%d, err=%v", msg.ID, err)
|
|
|
|
|
|
}
|
2026-04-26 15:49:16 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
log.Printf("投递MQ失败: VideoID: %d, err: %v", msg.VideoID, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 23:57:06 +08:00
|
|
|
|
func StartConsumer(tmq *rabbitmq.TimelineMQ, queueName string, redisClient *rediscache.Client, rmq *rabbitmq.RabbitMQ) {
|
|
|
|
|
|
if tmq == nil || rmq == nil || rmq.Conn == nil {
|
|
|
|
|
|
log.Printf("Timeline consumer disabled: rabbitmq is not initialized")
|
2026-04-26 15:49:16 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if redisClient == nil {
|
|
|
|
|
|
log.Printf("Timeline consumer disabled: redis is not initialized")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
go func() {
|
2026-05-22 23:57:06 +08:00
|
|
|
|
for {
|
|
|
|
|
|
// 每次重连创建独立的 Channel,不与发布者共用
|
|
|
|
|
|
ch, err := rmq.NewChannel()
|
2026-04-26 15:49:16 +08:00
|
|
|
|
if err != nil {
|
2026-05-22 23:57:06 +08:00
|
|
|
|
log.Printf("Timeline consumer: 创建 Channel 失败: %v, 5秒后重试", err)
|
|
|
|
|
|
time.Sleep(5 * time.Second)
|
2026-04-26 15:49:16 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 23:57:06 +08:00
|
|
|
|
if err := ch.Qos(10, 0, false); err != nil {
|
|
|
|
|
|
log.Printf("Timeline consumer: QoS 设置失败: %v", err)
|
|
|
|
|
|
}
|
2026-04-26 15:49:16 +08:00
|
|
|
|
|
2026-05-22 23:57:06 +08:00
|
|
|
|
msgs, err := ch.Consume(queueName, "", false, false, false, false, nil)
|
2026-04-26 15:49:16 +08:00
|
|
|
|
if err != nil {
|
2026-05-22 23:57:06 +08:00
|
|
|
|
log.Printf("Timeline consumer: 注册消费失败: %v, 5秒后重试", err)
|
|
|
|
|
|
ch.Close()
|
|
|
|
|
|
time.Sleep(5 * time.Second)
|
2026-04-26 15:49:16 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 23:57:06 +08:00
|
|
|
|
log.Printf("Timeline consumer 已启动, queue=%s", queueName)
|
2026-04-26 15:49:16 +08:00
|
|
|
|
|
2026-05-22 23:57:06 +08:00
|
|
|
|
for msg := range msgs {
|
|
|
|
|
|
var event rabbitmq.TimelineEvent
|
|
|
|
|
|
if err := json.Unmarshal(msg.Body, &event); err != nil {
|
|
|
|
|
|
log.Printf("Timeline consumer: 反序列化失败: %v", err)
|
|
|
|
|
|
msg.Ack(false)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
|
|
|
|
timelineKey := redisClient.Key("feed:global_timeline")
|
|
|
|
|
|
err = redisClient.ZAdd(ctx, timelineKey, oredis.Z{
|
|
|
|
|
|
Score: float64(event.CreateTime),
|
|
|
|
|
|
Member: fmt.Sprintf("%d", event.VideoID),
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("Timeline consumer: 写入Zset失败: %v", err)
|
|
|
|
|
|
msg.Nack(false, true)
|
|
|
|
|
|
cancel()
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := redisClient.ZRemRangeByRank(ctx, timelineKey, 0, -1001); err != nil {
|
|
|
|
|
|
log.Printf("Timeline consumer: ZRem失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msg.Ack(false)
|
|
|
|
|
|
cancel()
|
2026-04-26 15:49:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 23:57:06 +08:00
|
|
|
|
// msgs channel 关闭说明 AMQP Channel 断开,关闭并重连
|
|
|
|
|
|
ch.Close()
|
|
|
|
|
|
log.Printf("Timeline consumer: Channel 断开, 5秒后重连...")
|
|
|
|
|
|
time.Sleep(5 * time.Second)
|
2026-04-26 15:49:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
}
|