浏览代码

feat(service): 添加 AoDun服务并实现域名白名单功能- 新增 AoDunService接口和 aoDunService 结构体
- 实现 GetToken、AddDomainWhiteList 和 DelDomainWhiteList 方法
- 在 webForwardingService 中集成 AoDun服务
- 添加 RabbitMQ 配置和相关功能
- 重构 job 和 task 相关代码,支持异步任务处理

fusu 1 月之前
父节点
当前提交
b80a9005e7

+ 52 - 0
api/v1/aodun.go

@@ -0,0 +1,52 @@
+package v1
+
+import "encoding/json"
+
+type GetTokenRespone struct {
+	Code        int    `json:"code"`                 // 操作结果代码;0成功,其他失败
+	Msg         string `json:"msg,omitempty"`        // 操作结果描述或错误原因 (omitempty 表示如果字段为空则在 JSON 中忽略)
+	RemoteIP    string `json:"remote_ip,omitempty"`  // 调用者 IP 地址 (omitempty)
+	TokenType   string `json:"token_type,omitempty"` // Token 类型 (omitempty)
+	AccessToken string `json:"access_token,omitempty"` // Token 数据 (omitempty)
+}
+
+
+type IpInfo struct {
+	FType string `json:"F_type"`
+	FStartIp string `json:"F_startIp"`
+	FEndIp string `json:"F_endIp"`
+	FRemark string `json:"F_remark"`
+	FServerIp string `json:"F_serverIp"`
+}
+
+type DeleteIp struct {
+	Ids string `json:"ids"`
+}
+
+type IpResponse struct {
+	Code        int    `json:"code"`
+	Msg         string `json:"msg,omitempty"`
+	Data        []interface{} `json:"data,omitempty"`
+}
+type DomainResponse struct {
+	Code        int    `json:"err"`
+	Msg         json.RawMessage `json:"msg,omitempty"`
+	Data        string `json:"data,omitempty"`
+}
+
+type IpGet struct {
+	LongIntEndIP      int64  `json:"long_int_end_ip"`
+	Remark            string `json:"remark"`
+	Index             int    `json:"index"`
+	LongIntServerIP   int64  `json:"long_int_server_ip"`
+	TType             int    `json:"t_type"`
+	Source            string `json:"source"`
+	IsRange           bool   `json:"range"` // "range" 是 Go 关键字,所以换个名字
+	EndIP             string `json:"end_ip"`
+	ServerIP          string `json:"server_ip"`
+	StartIP           string `json:"start_ip"`
+	IpType            int    `json:"ip_type"`
+	MongoID           string `json:"_id"`     // "_id" 不符合 Go 命名规范,换个名字
+	ID                int    `json:"id"`      // 这是我们最终需要的目标字段
+	LongIntStartIP    int64  `json:"long_int_start_ip"`
+}

+ 5 - 1
cmd/server/wire/wire.go

@@ -25,6 +25,7 @@ var repositorySet = wire.NewSet(
 	//repository.NewRedis,
 	repository.NewMongoClient,
 	repository.NewMongoDB,
+	repository.NewRabbitMQ,
 	repository.NewRepository,
 	repository.NewTransaction,
 	repository.NewUserRepository,
@@ -47,14 +48,16 @@ var repositorySet = wire.NewSet(
 
 var serviceSet = wire.NewSet(
 	service.NewService,
+	service.NewAoDunService,
 	service.NewUserService,
 	service.NewGameShieldService,
-	service.NewCrawlerService,
+	
 	service.NewGameShieldPublicIpService,
 	service.NewDuedateService,
 	service.NewFormatterService,
 	service.NewParserService,
 	service.NewRequiredService,
+	service.NewCrawlerService,
 	service.NewWebForwardingService,
 	service.NewTcpforwardingService,
 	service.NewUdpForWardingService,
@@ -94,6 +97,7 @@ var handlerSet = wire.NewSet(
 var jobSet = wire.NewSet(
 	job.NewJob,
 	job.NewUserJob,
+	job.NewWhitelistJob,
 )
 
 // 限流器依赖集

+ 11 - 7
cmd/server/wire/wire_gen.go

@@ -33,7 +33,8 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 	db := repository.NewDB(viperViper, logger)
 	client := repository.NewMongoClient(viperViper)
 	database := repository.NewMongoDB(client, viperViper)
-	repositoryRepository := repository.NewRepository(logger, db, client, database)
+	rabbitMQ, cleanup := repository.NewRabbitMQ(viperViper, logger)
+	repositoryRepository := repository.NewRepository(logger, db, client, database, rabbitMQ)
 	transaction := repository.NewTransaction(repositoryRepository)
 	sidSid := sid.NewSid()
 	serviceService := service.NewService(transaction, logger, sidSid, jwtJWT)
@@ -63,7 +64,8 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 	tcpforwardingRepository := repository.NewTcpforwardingRepository(repositoryRepository)
 	udpForWardingRepository := repository.NewUdpForWardingRepository(repositoryRepository)
 	wafFormatterService := service.NewWafFormatterService(serviceService, globalLimitRepository, hostRepository, requiredService, parserService, tcpforwardingRepository, udpForWardingRepository, webForwardingRepository, hostService)
-	webForwardingService := service.NewWebForwardingService(serviceService, requiredService, webForwardingRepository, crawlerService, parserService, wafFormatterService)
+	aoDunService := service.NewAoDunService(serviceService, viperViper)
+	webForwardingService := service.NewWebForwardingService(serviceService, requiredService, webForwardingRepository, crawlerService, parserService, wafFormatterService, aoDunService, rabbitMQ)
 	webForwardingHandler := handler.NewWebForwardingHandler(handlerHandler, webForwardingService)
 	webLimitRepository := repository.NewWebLimitRepository(repositoryRepository)
 	webLimitService := service.NewWebLimitService(serviceService, webLimitRepository, requiredService, parserService, crawlerService, hostService)
@@ -83,23 +85,25 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 	globalLimitService := service.NewGlobalLimitService(serviceService, globalLimitRepository, duedateService, crawlerService, viperViper, requiredService, parserService, hostService, tcpLimitService, udpLimitService, webLimitService, gatewayGroupService, hostRepository, gatewayGroupRepository)
 	globalLimitHandler := handler.NewGlobalLimitHandler(handlerHandler, globalLimitService)
 	httpServer := server.NewHTTPServer(logger, viperViper, jwtJWT, limiterLimiter, handlerFunc, userHandler, gameShieldHandler, gameShieldBackendHandler, webForwardingHandler, webLimitHandler, tcpforwardingHandler, udpForWardingHandler, tcpLimitHandler, udpLimitHandler, globalLimitHandler)
-	jobJob := job.NewJob(transaction, logger, sidSid)
+	jobJob := job.NewJob(transaction, logger, sidSid, rabbitMQ)
 	userJob := job.NewUserJob(jobJob, userRepository)
-	jobServer := server.NewJobServer(logger, userJob)
+	whitelistJob := job.NewWhitelistJob(jobJob, aoDunService)
+	jobServer := server.NewJobServer(logger, userJob, whitelistJob)
 	appApp := newApp(httpServer, jobServer)
 	return appApp, func() {
+		cleanup()
 	}, nil
 }
 
 // wire.go:
 
-var repositorySet = wire.NewSet(repository.NewDB, repository.NewMongoClient, repository.NewMongoDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewGameShieldRepository, repository.NewGameShieldPublicIpRepository, repository.NewWebForwardingRepository, repository.NewTcpforwardingRepository, repository.NewUdpForWardingRepository, repository.NewGameShieldUserIpRepository, repository.NewWebLimitRepository, repository.NewTcpLimitRepository, repository.NewUdpLimitRepository, repository.NewGameShieldBackendRepository, repository.NewGameShieldSdkIpRepository, repository.NewHostRepository, repository.NewGlobalLimitRepository, repository.NewGatewayGroupRepository, repository.NewGateWayGroupIpRepository)
+var repositorySet = wire.NewSet(repository.NewDB, repository.NewMongoClient, repository.NewMongoDB, repository.NewRabbitMQ, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewGameShieldRepository, repository.NewGameShieldPublicIpRepository, repository.NewWebForwardingRepository, repository.NewTcpforwardingRepository, repository.NewUdpForWardingRepository, repository.NewGameShieldUserIpRepository, repository.NewWebLimitRepository, repository.NewTcpLimitRepository, repository.NewUdpLimitRepository, repository.NewGameShieldBackendRepository, repository.NewGameShieldSdkIpRepository, repository.NewHostRepository, repository.NewGlobalLimitRepository, repository.NewGatewayGroupRepository, repository.NewGateWayGroupIpRepository)
 
-var serviceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewGameShieldService, service.NewCrawlerService, service.NewGameShieldPublicIpService, service.NewDuedateService, service.NewFormatterService, service.NewParserService, service.NewRequiredService, service.NewWebForwardingService, service.NewTcpforwardingService, service.NewUdpForWardingService, service.NewGameShieldUserIpService, service.NewWebLimitService, service.NewTcpLimitService, service.NewUdpLimitService, service.NewGameShieldBackendService, service.NewGameShieldSdkIpService, service.NewHostService, service.NewGlobalLimitService, service.NewGatewayGroupService, service.NewWafFormatterService, service.NewGateWayGroupIpService)
+var serviceSet = wire.NewSet(service.NewService, service.NewAoDunService, service.NewUserService, service.NewGameShieldService, service.NewGameShieldPublicIpService, service.NewDuedateService, service.NewFormatterService, service.NewParserService, service.NewRequiredService, service.NewCrawlerService, service.NewWebForwardingService, service.NewTcpforwardingService, service.NewUdpForWardingService, service.NewGameShieldUserIpService, service.NewWebLimitService, service.NewTcpLimitService, service.NewUdpLimitService, service.NewGameShieldBackendService, service.NewGameShieldSdkIpService, service.NewHostService, service.NewGlobalLimitService, service.NewGatewayGroupService, service.NewWafFormatterService, service.NewGateWayGroupIpService)
 
 var handlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler, handler.NewGameShieldHandler, handler.NewGameShieldPublicIpHandler, handler.NewWebForwardingHandler, handler.NewTcpforwardingHandler, handler.NewUdpForWardingHandler, handler.NewGameShieldUserIpHandler, handler.NewWebLimitHandler, handler.NewTcpLimitHandler, handler.NewUdpLimitHandler, handler.NewGameShieldBackendHandler, handler.NewGameShieldSdkIpHandler, handler.NewHostHandler, handler.NewGlobalLimitHandler, handler.NewGatewayGroupHandler, handler.NewGateWayGroupIpHandler)
 
-var jobSet = wire.NewSet(job.NewJob, job.NewUserJob)
+var jobSet = wire.NewSet(job.NewJob, job.NewUserJob, job.NewWhitelistJob)
 
 // 限流器依赖集
 var limiterSet = wire.NewSet(limiter.NewLimiter, middleware.NewRateLimitMiddleware)

+ 2 - 1
cmd/task/main.go

@@ -22,7 +22,8 @@ func main() {
 	if err != nil {
 		panic(err)
 	}
-	if err = app.Run(context.Background()); err != nil {
+
+	app.Run()if err = app.Run(context.Background()); err != nil {
 		panic(err)
 	}
 

+ 16 - 1
config/prod.yml

@@ -91,4 +91,19 @@ aodun:
   username: "zznet_api"
   password: "Nbgaofang.com!@#4"
   clientId: "bd9d36fc-17e1-11ef-8a72-549f35180370"
-  Url: "https://115.238.184.13:16008"
+  Url: "https://115.238.184.13:16008"
+
+# RabbitMQ Configuration
+rabbitmq:
+  host: 127.0.0.1
+  port: 5672
+  username: "guest"
+  password: "guest"
+  vhost: "/"
+  connection_timeout: 5s
+  task:
+    exchange: "task.exchange"
+    queue: "task.queue"
+    routing_key: "task.routing.key"
+    consumer_count: 5
+    prefetch_count: 1

+ 1 - 0
go.mod

@@ -19,6 +19,7 @@ require (
 	github.com/jinzhu/copier v0.4.0
 	github.com/mcuadros/go-defaults v1.2.0
 	github.com/qiniu/qmgo v1.1.9
+	github.com/rabbitmq/amqp091-go v1.10.0
 	github.com/redis/go-redis/v9 v9.0.5
 	github.com/sony/sonyflake v1.1.0
 	github.com/spf13/cast v1.5.1

+ 4 - 2
go.sum

@@ -391,6 +391,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/qiniu/qmgo v1.1.9 h1:3G3h9RLyjIUW9YSAQEPP2WqqNnboZ2Z/zO3mugjVb3E=
 github.com/qiniu/qmgo v1.1.9/go.mod h1:aba4tNSlMWrwUhe7RdILfwBRIgvBujt1y10X+T1YZSI=
+github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
+github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
 github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
 github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -515,8 +517,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
-go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=

+ 11 - 7
internal/job/job.go

@@ -4,24 +4,28 @@ import (
 	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/jwt"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/rabbitmq"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/sid"
 )
 
 type Job struct {
-	logger *log.Logger
-	sid    *sid.Sid
-	jwt    *jwt.JWT
-	tm     repository.Transaction
+	logger   *log.Logger
+	sid      *sid.Sid
+	jwt      *jwt.JWT
+	tm       repository.Transaction
+	Rabbitmq *rabbitmq.RabbitMQ
 }
 
 func NewJob(
 	tm repository.Transaction,
 	logger *log.Logger,
 	sid *sid.Sid,
+	mq *rabbitmq.RabbitMQ,
 ) *Job {
 	return &Job{
-		logger: logger,
-		sid:    sid,
-		tm:     tm,
+		logger:   logger,
+		sid:      sid,
+		tm:       tm,
+		Rabbitmq: mq,
 	}
 }

+ 51 - 6
internal/job/user.go

@@ -2,12 +2,14 @@ package job
 
 import (
 	"context"
+	"fmt"
 	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
-	"time"
+	"go.uber.org/zap"
 )
 
 type UserJob interface {
-	KafkaConsumer(ctx context.Context) error
+	// RegisterConsumer 启动消费者,处理用户注册后的任务
+	RegisterConsumer(ctx context.Context)
 }
 
 func NewUserJob(
@@ -25,10 +27,53 @@ type userJob struct {
 	*Job
 }
 
-func (t userJob) KafkaConsumer(ctx context.Context) error {
-	// do something
+func (j *userJob) RegisterConsumer(ctx context.Context) {
+	taskName := "user_register"
+	taskCfg, ok := j.Rabbitmq.GetTaskConfig(taskName)
+	if !ok {
+		j.logger.Error(fmt.Sprintf("未找到任务 '%s' 的配置", taskName))
+		return
+	}
+
+	consumerName := "user_register_consumer"
+	j.logger.Info("正在启动消费者...",
+		zap.String("task", taskName),
+		zap.String("queue", taskCfg.Queue),
+		zap.String("consumer", consumerName),
+	)
+
+	msgs, err := j.Rabbitmq.Consume(taskCfg.Queue, consumerName, taskCfg.PrefetchCount)
+	if err != nil {
+		j.logger.Error("启动消费者失败", zap.Error(err))
+		return
+	}
+
 	for {
-		//t.logger.Info("KafkaConsumer")
-		time.Sleep(time.Second * 5)
+		select {
+		case <-ctx.Done():
+			j.logger.Info("消费者正在关闭...", zap.String("task", taskName))
+			return
+		case d, ok := <-msgs:
+			if !ok {
+				j.logger.Warn("消息通道已关闭,消费者退出。", zap.String("task", taskName))
+				return
+			}
+
+			j.logger.Info("收到新消息",
+				zap.String("exchange", d.Exchange),
+				zap.String("routing_key", d.RoutingKey),
+				zap.ByteString("body", d.Body),
+			)
+
+			// 在这里处理你的业务逻辑
+			// ...
+
+			// 业务处理完成后,手动发送确认
+			if err := d.Ack(false); err != nil {
+				j.logger.Error("消息确认失败", zap.Error(err))
+				// 你可以在这里决定是否需要重试或将消息放入死信队列
+				// d.Nack(false, true) // requeue
+			}
+		}
 	}
 }

+ 111 - 0
internal/job/whitelist.go

@@ -0,0 +1,111 @@
+package job
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/go-nunu/nunu-layout-advanced/internal/service"
+	"go.uber.org/zap"
+)
+
+// WhitelistJob 定义了处理白名单相关任务的接口
+type WhitelistJob interface {
+	// DomainConsumer 启动消费者,处理域名白名单任务
+	DomainConsumer(ctx context.Context)
+}
+
+// NewWhitelistJob 创建一个新的 WhitelistJob
+func NewWhitelistJob(job *Job, aoDunService service.AoDunService) WhitelistJob {
+	return &whitelistJob{
+		Job:          job,
+		aoDunService: aoDunService,
+	}
+}
+
+type whitelistJob struct {
+	*Job
+	aoDunService service.AoDunService
+}
+
+// DomainConsumer 是处理域名白名单任务的消费者
+func (j *whitelistJob) DomainConsumer(ctx context.Context) {
+	taskName := "domain_whitelist"
+	taskCfg, ok := j.Rabbitmq.GetTaskConfig(taskName)
+	if !ok {
+		j.logger.Error(fmt.Sprintf("未找到任务 '%s' 的配置", taskName))
+		return
+	}
+
+	consumerName := "domain_whitelist_consumer"
+	j.logger.Info("正在启动域名白名单消费者...",
+		zap.String("task", taskName),
+		zap.String("queue", taskCfg.Queue),
+		zap.String("consumer", consumerName),
+	)
+
+	msgs, err := j.Rabbitmq.Consume(taskCfg.Queue, consumerName, taskCfg.PrefetchCount)
+	if err != nil {
+		j.logger.Error("启动域名白名单消费者失败", zap.Error(err))
+		return
+	}
+
+	// Define the message payload structure, now including an action field
+	type domainTaskPayload struct {
+		Domain string `json:"domain"`
+		Action string `json:"action"` // "add" or "del"
+	}
+
+	for {
+		select {
+		case <-ctx.Done():
+			j.logger.Info("域名白名单消费者正在关闭...", zap.String("task", taskName))
+			return
+		case d, ok := <-msgs:
+			if !ok {
+				j.logger.Warn("消息通道已关闭,域名白名单消费者退出。", zap.String("task", taskName))
+				return
+			}
+
+			// 解析消息
+			var payload domainTaskPayload
+			if err := json.Unmarshal(d.Body, &payload); err != nil {
+				j.logger.Error("解析域名白名单消息失败", zap.Error(err), zap.ByteString("body", d.Body))
+				// 消息格式错误,直接拒绝且不重新入队
+				_ = d.Nack(false, false)
+				continue
+			}
+
+			j.logger.Info("收到域名白名单任务",
+				zap.String("domain", payload.Domain),
+				zap.String("routing_key", d.RoutingKey),
+			)
+
+			// Call business logic based on the action
+			switch payload.Action {
+			case "add":
+				if err := j.aoDunService.AddDomainWhiteList(ctx, []string{payload.Domain}); err != nil {
+					j.logger.Error("Failed to handle 'add' domain whitelist task", zap.Error(err), zap.String("domain", payload.Domain))
+					_ = d.Reject(false) // Business failure, reject message
+					continue
+				}
+				j.logger.Info("Successfully processed 'add' domain whitelist task", zap.String("domain", payload.Domain))
+			case "del":
+				if err := j.aoDunService.DeleteDomainWhiteList(ctx, []string{payload.Domain}); err != nil {
+					j.logger.Error("Failed to handle 'delete' domain whitelist task", zap.Error(err), zap.String("domain", payload.Domain))
+					_ = d.Reject(false) // Business failure, reject message
+					continue
+				}
+				j.logger.Info("Successfully processed 'delete' domain whitelist task", zap.String("domain", payload.Domain))
+			default:
+				j.logger.Warn("Received unknown action in domain whitelist task", zap.String("action", payload.Action), zap.String("domain", payload.Domain))
+				_ = d.Reject(false) // Reject message with unknown action
+				continue
+			}
+
+			// 业务处理完成后,手动发送确认
+			if err := d.Ack(false); err != nil {
+				j.logger.Error("域名白名单任务消息确认失败", zap.Error(err))
+			}
+		}
+	}
+}

+ 35 - 8
internal/repository/repository.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/glebarez/sqlite"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/rabbitmq"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/zapgorm2"
 	"github.com/qiniu/qmgo"
 	"github.com/redis/go-redis/v9"
@@ -20,26 +21,26 @@ import (
 const ctxTxKey = "TxKey"
 
 type Repository struct {
-	db *gorm.DB
-	//rdb    *redis.Client
+	db          *gorm.DB
 	mongoClient *qmgo.Client
 	mongoDB     *qmgo.Database
-	logger *log.Logger
+	mq          *rabbitmq.RabbitMQ
+	logger      *log.Logger
 }
 
 func NewRepository(
 	logger *log.Logger,
 	db *gorm.DB,
-// rdb *redis.Client,
 	mongoClient *qmgo.Client,
 	mongoDB *qmgo.Database,
+	mq *rabbitmq.RabbitMQ,
 ) *Repository {
 	return &Repository{
-		db: db,
-		//rdb:    rdb,
+		db:          db,
 		mongoClient: mongoClient,
-		mongoDB: mongoDB,
-		logger: logger,
+		mongoDB:     mongoDB,
+		mq:          mq,
+		logger:      logger,
 	}
 }
 
@@ -300,3 +301,29 @@ func NewMongoDB(client *qmgo.Client, conf *viper.Viper) *qmgo.Database {
 
 	return client.Database(databaseName)
 }
+
+func NewRabbitMQ(conf *viper.Viper, logger *log.Logger) (*rabbitmq.RabbitMQ, func()) {
+
+
+	var cfg rabbitmq.Config
+	if err := conf.UnmarshalKey("rabbitmq", &cfg); err != nil {
+		panic(fmt.Sprintf("unmarshal rabbitmq config error: %s", err.Error()))
+	}
+
+	mq, err := rabbitmq.New(cfg, logger)
+	if err != nil {
+		panic(fmt.Sprintf("init rabbitmq error: %s", err.Error()))
+	}
+
+	// Setup task queue
+	if err := mq.SetupAllTaskQueues(); err != nil {
+		panic(fmt.Sprintf("failed to setup rabbitmq task queues: %v", err))
+	}
+
+	cleanup := func() {
+		logger.Info("Closing RabbitMQ connection")
+		_ = mq.Close()
+	}
+
+	return mq, cleanup
+}

+ 28 - 8
internal/server/job.go

@@ -2,31 +2,51 @@ package server
 
 import (
 	"context"
+	"sync"
+
 	"github.com/go-nunu/nunu-layout-advanced/internal/job"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
 )
 
 type JobServer struct {
-	log     *log.Logger
-	userJob job.UserJob
+	log          *log.Logger
+	userJob      job.UserJob
+	whitelistJob job.WhitelistJob
+	wg           sync.WaitGroup
 }
 
 func NewJobServer(
 	log *log.Logger,
 	userJob job.UserJob,
+	whitelistJob job.WhitelistJob,
 ) *JobServer {
 	return &JobServer{
-		log:     log,
-		userJob: userJob,
+		log:          log,
+		userJob:      userJob,
+		whitelistJob: whitelistJob,
 	}
 }
 
 func (j *JobServer) Start(ctx context.Context) error {
-	// Tips: If you want job to start as a separate process, just refer to the task implementation and adjust the code accordingly.
+	j.log.Info("job server starting...")
+
+	// 启动 UserJob 的消费者
+	//j.wg.Add(1)
+	//go func() {
+	//	defer j.wg.Done()
+	//	j.userJob.RegisterConsumer(ctx)
+	//}()
+
+	// 启动 WhitelistJob 的消费者
+	j.wg.Add(1)
+	go func() {
+		defer j.wg.Done()
+		j.whitelistJob.DomainConsumer(ctx)
+	}()
 
-	// eg: kafka consumer
-	err := j.userJob.KafkaConsumer(ctx)
-	return err
+	j.wg.Wait()
+
+	return nil
 }
 func (j *JobServer) Stop(ctx context.Context) error {
 	return nil

+ 272 - 0
internal/service/aodun.go

@@ -0,0 +1,272 @@
+package service
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
+	"github.com/spf13/viper"
+	"io"
+	"net/http"
+	"strings"
+	"time"
+)
+
+type AoDunService interface {
+	AddDomainWhiteList(ctx context.Context, req []string) error
+	DeleteDomainWhiteList(ctx context.Context, req []string) error
+}
+func NewAoDunService(
+    service *Service,
+	conf *viper.Viper,
+
+) AoDunService {
+	return &aoDunService{
+		Service:        service,
+		Url:                       conf.GetString("aodun.Url"),
+		clientID:          conf.GetString("aodun.clientID"),
+		username:          conf.GetString("aodun.username"),
+		password:          conf.GetString("aodun.password"),
+	}
+}
+
+type aoDunService struct {
+	*Service
+	Url string
+	clientID string
+	username string
+	password string
+}
+
+func (s *aoDunService) DeleteDomainWhiteList(ctx context.Context, req []string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (s *aoDunService) sendFormData(ctx context.Context,apiUrl string,tokenType string,token string,formData map[string]interface{})  ([]byte,error) {
+	URL := s.Url + apiUrl
+	jsonData, err := json.Marshal(formData)
+	if err != nil {
+		return nil, fmt.Errorf("序列化请求数据失败: %w", err)
+	}
+	req, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonData))
+	if err != nil {
+		return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err)
+	}
+	// 设置请求头 Content-Type 为 "application/json"
+	req.Header.Set("Content-Type", "application/json")
+	if tokenType == "" {
+		req.Header.Set("Authorization", tokenType + " " + token)
+	}
+
+
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // <--- 关键修改:忽略 SSL 验证
+	}
+
+	// 5. 使用 HTTP 客户端发送请求
+	client := &http.Client{
+		Transport: tr,
+		Timeout: 15 * time.Second, // 设置一个合理的超时时间,例如15秒
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("发送 HTTP 请求失败: %w", err)
+	}
+	// defer 确保在函数返回前关闭响应体,防止资源泄露
+	defer resp.Body.Close()
+
+	// 6. 读取响应体内容
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("读取响应体失败: %w", err)
+	}
+	return body, nil
+}
+
+
+func (s *aoDunService) GetToken(ctx context.Context)  (string,string,error) {
+
+	formData := map[string]interface{}{
+		"ClientID":  s.clientID,
+		"GrantType": "password",
+		"Username":  s.username,
+		"Password":  s.password,
+	}
+
+	resBody, err := s.sendFormData(ctx,"/oauth/token","","",formData)
+	if err != nil {
+		return "", "", err
+	}
+	// 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体
+	var responsePayload v1.GetTokenRespone
+	if err := json.Unmarshal(resBody, &responsePayload); err != nil {
+		// 如果反序列化失败,可能是响应格式不符合预期
+		return "", "", fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
+	}
+
+	// 8. 检查 API 返回的操作结果代码
+	if responsePayload.Code != 0 {
+		return "", "", fmt.Errorf("API 错误: code %d, msg '%s', remote_ip '%s'",
+			responsePayload.Code, responsePayload.Msg, responsePayload.RemoteIP)
+	}
+
+	// 9. 成功:返回 access_token
+	if responsePayload.AccessToken == "" {
+		// 理论上 code 为 0 时应该有 access_token,这是一个额外的健壮性检查
+		return "", "", fmt.Errorf("API 成功 (code 0) 但 access_token 为空")
+	}
+	return responsePayload.TokenType,responsePayload.AccessToken, nil
+}
+
+func (s *aoDunService) AddWhiteStaticList(ctx context.Context,req []v1.IpInfo) error {
+	tokenType,token, err := s.GetToken(ctx)
+	if err != nil {
+		return err
+	}
+
+	formData := map[string]interface{}{
+		"action" : "add",
+		"bwflag" : "white",
+		"insert_bw_list": req,
+	}
+
+	resBody, err := s.sendFormData(ctx,"/v1.0/firewall/static_bw_list",tokenType,token,formData)
+	if err != nil {
+		return  err
+	}
+	// 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体
+	var res v1.IpResponse
+	if err := json.Unmarshal(resBody, &res); err != nil {
+		// 如果反序列化失败,可能是响应格式不符合预期
+		return  fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
+	}
+	if res.Code != 0 {
+		return  fmt.Errorf("API 错误: code %d, msg '%s'",
+			res.Code, res.Msg)
+	}
+
+	return nil
+}
+
+//func (s *aoDunService) GetWhiteStaticList(ctx context.Context,ip string) (int,error) {
+//	tokenType,token, err := s.GetToken(ctx)
+//	if err != nil {
+//		return 0, err
+//	}
+//
+//	formData := map[string]interface{}{
+//		"action" : "get",
+//		"bwflag" : "white",
+//		"page" : 1,
+//		"ids": ip,
+//	}
+//
+//	resBody, err := s.sendFormData(ctx,"/v1.0/firewall/static_bw_list",tokenType,token,formData)
+//	if err != nil {
+//		return nil, err
+//	}
+//	// 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体
+//	var res IpResponse // 使用我们定义的 IpResponse 结构体
+//	if err := json.Unmarshal(resBody, &res); err != nil {
+//		// 如果反序列化失败,说明响应格式不符合预期
+//		return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
+//	}
+//
+//	// 2. 检查 API 返回的 code,这是处理业务失败的关键
+//	if res.Code != 0 {
+//		// API 返回了错误码,例如 IP 不存在、参数错误等
+//		return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg)
+//	}
+//
+//	// 3. 检查 data 数组是否为空
+//	// 即使 code 为 0,也可能因为没有匹配的数据而返回一个空数组
+//	if len(res.Data) == 0 {
+//		return 0, fmt.Errorf("API 调用成功,但未找到与 IP '%s' 相关的记录", ip)
+//	}
+//
+//	// 4. 获取 ID 并返回
+//	// 假设我们总是取返回结果中的第一个元素的 ID
+//	id := res.Data[0].ID
+//	return id, nil // 成功!返回获取到的 id 和 nil 错误
+//}
+
+func (s *aoDunService) DelWhiteStaticList(ctx context.Context, req v1.DeleteIp) error {
+	tokenType, token, err := s.GetToken(ctx)
+	if err != nil {
+		return err
+	}
+
+	formData := map[string]interface{}{
+		"action": "del",
+		"bwflag": "white",
+		"flag":   0,
+		"ids":    req.Ids,
+	}
+
+	resBody, err := s.sendFormData(ctx, "/v1.0/firewall/static_bw_list", tokenType, token, formData)
+	if err != nil {
+		return err
+	}
+	var res v1.IpResponse
+	if err := json.Unmarshal(resBody, &res); err != nil {
+		return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
+	}
+	if res.Code != 0 {
+		return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg)
+	}
+	return nil
+}
+
+func (s *aoDunService) AddDomainWhiteList(ctx context.Context, req []string) error {
+	tokenType, token, err := s.GetToken(ctx)
+	if err != nil {
+		return err
+	}
+	formData := map[string]interface{}{
+		"domain": req,
+	}
+	resBody, err := s.sendFormData(ctx, "/v1.0/firewall/addDomainWhiteList", tokenType, token, formData)
+	if err != nil {
+		return err
+	}
+	var res v1.DomainResponse
+	if err := json.Unmarshal(resBody, &res); err != nil {
+		return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
+	}
+	if res.Code != 0 {
+		if strings.Contains(string(res.Msg), "重复列表") {
+			return nil
+		}
+		return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg)
+	}
+	return nil
+}
+
+func (s *aoDunService) DelDomainWhiteList(ctx context.Context, req []string) error {
+	tokenType, token, err := s.GetToken(ctx)
+	if err != nil {
+		return err
+	}
+	formData := map[string]interface{}{
+		"type":   1,
+		"domain": req,
+	}
+	resBody, err := s.sendFormData(ctx, "/v1.0/firewall/delDomainWhiteList", tokenType, token, formData)
+	if err != nil {
+		return err
+	}
+	var res v1.DomainResponse
+	if err := json.Unmarshal(resBody, &res); err != nil {
+		return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
+	}
+	if res.Code != 0 {
+		if strings.Contains(string(res.Msg), "重复列表") {
+			return nil
+		}
+		return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg)
+	}
+	return nil
+}

+ 69 - 1
internal/service/webforwarding.go

@@ -7,6 +7,9 @@ import (
 	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
 	"github.com/go-nunu/nunu-layout-advanced/internal/model"
 	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/rabbitmq"
+	amqp "github.com/rabbitmq/amqp091-go"
+	"go.uber.org/zap"
 	"golang.org/x/sync/errgroup"
 	"sort"
 	"strconv"
@@ -28,6 +31,8 @@ func NewWebForwardingService(
 	crawler CrawlerService,
 	parser ParserService,
 	wafformatter WafFormatterService,
+	aoDun AoDunService,
+	mq *rabbitmq.RabbitMQ,
 ) WebForwardingService {
 	return &webForwardingService{
 		Service:                 service,
@@ -36,6 +41,8 @@ func NewWebForwardingService(
 		parser:                  parser,
 		crawler:                 crawler,
 		wafformatter:            wafformatter,
+		aoDun:                   aoDun,
+		mq:                      mq,
 	}
 }
 
@@ -45,7 +52,9 @@ type webForwardingService struct {
 	required                RequiredService
 	parser                  ParserService
 	crawler                 CrawlerService
-	wafformatter 			WafFormatterService
+	wafformatter            WafFormatterService
+	aoDun                   AoDunService
+	mq                      *rabbitmq.RabbitMQ
 }
 
 func (s *webForwardingService) require(ctx context.Context,req v1.GlobalRequire) (v1.GlobalRequire, error) {
@@ -298,6 +307,8 @@ func (s *webForwardingService) AddWebForwarding(ctx context.Context, req *v1.Web
 	if err != nil {
 		return err
 	}
+	// 异步任务:将域名添加到白名单
+	go s.publishDomainWhitelistTask(req.WebForwardingData.Domain, "add")
 
 	webModel := s.buildWebForwardingModel(&req.WebForwardingData, wafWebId, require)
 
@@ -306,6 +317,17 @@ func (s *webForwardingService) AddWebForwarding(ctx context.Context, req *v1.Web
 		return err
 	}
 	webRuleModel := s.buildWebRuleModel(&req.WebForwardingData, require, id)
+	//var ips []v1.IpInfo
+	//for _, v := range req.WebForwardingData.AllowIpList {
+	//	ips = append(ips, v1.IpInfo{
+	//		FType:      "allow",
+	//		FStartIp:   v,
+	//		FEndIp:     v,
+	//		FRemark:    "宁波高防IP过白",
+	//		FServerIp:  "",
+	//	})
+	//}
+	//err = s.aoDun.AddDomainWhiteList(ctx, req.WebForwardingData.Domain)
 	if _, err = s.webForwardingRepository.AddWebForwardingIps(ctx, *webRuleModel); err != nil {
 		return err
 	}
@@ -494,4 +516,50 @@ func (s *webForwardingService) GetWebForwardingWafWebAllIps(ctx context.Context,
 		return finalResults[i].Id > finalResults[j].Id
 	})
 	return finalResults, nil
+}
+
+// publishDomainWhitelistTask is a helper function to publish domain whitelist tasks to RabbitMQ.
+// It can handle different actions like "add" or "del".
+func (s *webForwardingService) publishDomainWhitelistTask(domain, action string) {
+	// Define message payload, including the action
+	type domainTaskPayload struct {
+		Domain string `json:"domain"`
+		Action string `json:"action"`
+	}
+	payload := domainTaskPayload{
+		Domain: domain,
+		Action: action,
+	}
+
+	// Serialize the message
+	msgBody, err := json.Marshal(payload)
+	if err != nil {
+		s.logger.Error("Failed to serialize domain whitelist task message", zap.Error(err), zap.String("domain", domain), zap.String("action", action))
+		return
+	}
+
+	// Get task configuration
+	taskCfg, ok := s.mq.GetTaskConfig("domain_whitelist")
+	if !ok {
+		s.logger.Error("Failed to get 'domain_whitelist' task configuration")
+		return
+	}
+
+	// Construct the routing key dynamically based on the action
+	routingKey := fmt.Sprintf("whitelist.domain.%s", action)
+
+	// Construct the amqp.Publishing message
+	publishingMsg := amqp.Publishing{
+		ContentType:  "application/json",
+		Body:         msgBody,
+		DeliveryMode: amqp.Persistent, // Persistent message
+	}
+
+	// Publish the message
+	err = s.mq.PublishWithCh(taskCfg.Exchange, routingKey, publishingMsg)
+	if err != nil {
+		s.logger.Error("Failed to publish domain whitelist task to MQ", zap.Error(err), zap.String("domain", domain), zap.String("action", action))
+	} else {
+		s.logger.Info("Successfully published domain whitelist task to MQ", zap.String("domain", domain), zap.String("action", action))
+	}
 }

+ 22 - 0
internal/task/waf.go

@@ -0,0 +1,22 @@
+package task
+
+import "context"
+
+type WafTask interface {
+}
+
+func NewWafTask (
+	task *Task,
+	) WafTask{
+	return &wafTask{
+		Task: task,
+	}
+}
+type wafTask struct {
+	*Task
+}
+
+func (t wafTask) CheckExpiredTask(ctx context.Context) error {
+	return nil
+
+}

+ 14 - 0
pkg/config/config.go

@@ -4,8 +4,22 @@ import (
 	"fmt"
 	"github.com/spf13/viper"
 	"os"
+	"time"
 )
 
+type Config struct {
+	Redis struct {
+		Addr         string        `mapstructure:"addr"`
+		Password     string        `mapstructure:"password"`
+		DB           int           `mapstructure:"db"`
+		ReadTimeout  time.Duration `mapstructure:"read_timeout"`
+		WriteTimeout time.Duration `mapstructure:"write_timeout"`
+	} `mapstructure:"redis"`
+	RabbitMQ struct {
+		URL string `mapstructure:"url"`
+	} `mapstructure:"rabbitmq"`
+}
+
 func NewConfig(p string) *viper.Viper {
 	envConf := os.Getenv("APP_CONF")
 	if envConf == "" {

+ 380 - 0
pkg/rabbitmq/rabbitmq.go

@@ -0,0 +1,380 @@
+package rabbitmq
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	amqp "github.com/rabbitmq/amqp091-go"
+	"go.uber.org/zap"
+)
+
+var (
+	ErrClosed = errors.New("rabbitmq: client is closed")
+)
+
+type Config struct {
+	Host              string                  `yaml:"host"`
+	Port              int                     `yaml:"port"`
+	Username          string                  `yaml:"username"`
+	Password          string                  `yaml:"password"`
+	VHost             string                  `yaml:"vhost"`
+	ConnectionTimeout time.Duration           `yaml:"connection_timeout"`
+	Tasks             map[string]TaskConfig   `yaml:"tasks"` // 支持多个任务配置
+}
+
+type TaskConfig struct {
+	Exchange      string `mapstructure:"exchange"`
+	ExchangeType  string `mapstructure:"exchange_type"`
+	Queue         string `mapstructure:"queue"`
+	RoutingKey    string `mapstructure:"routing_key"`
+	ConsumerCount int    `mapstructure:"consumer_count"`
+	PrefetchCount int    `mapstructure:"prefetch_count"`
+}
+
+type RabbitMQ struct {
+	config Config
+	conn   *amqp.Connection
+	ch     *amqp.Channel
+	logger *log.Logger
+
+	mu     sync.RWMutex
+	closed bool
+}
+
+// New 创建新的RabbitMQ客户端
+func New(config Config, logger *log.Logger) (*RabbitMQ, error) {
+	r := &RabbitMQ{
+		config: config,
+		logger: logger,
+	}
+
+	if err := r.Connect(); err != nil {
+		return nil, err
+	}
+
+	if err := r.SetupAllTaskQueues(); err != nil {
+		_ = r.Close() // Attempt to close the connection if setup fails
+		return nil, fmt.Errorf("failed to setup task queues: %w", err)
+	}
+
+	go r.reconnectLoop()
+
+	return r, nil
+}
+
+// Connect 连接到RabbitMQ服务器
+func (r *RabbitMQ) Connect() error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if r.conn != nil && !r.conn.IsClosed() {
+		_ = r.ch.Close()
+		_ = r.conn.Close()
+	}
+
+	vhost := r.config.VHost
+	if vhost == "" {
+		vhost = "/"
+	} else if vhost[0] != '/' {
+		vhost = "/" + vhost
+	}
+
+	// 构造完整的连接URL
+	fullURL := fmt.Sprintf("amqp://%s:%s@%s:%d%s",
+		r.config.Username,
+		r.config.Password,
+		r.config.Host,
+		r.config.Port,
+		vhost,
+	)
+
+	r.logger.Info("正在尝试连接到 RabbitMQ...", zap.String("url", fullURL))
+
+	var err error
+	r.conn, err = amqp.Dial(fullURL)
+
+	if err != nil {
+		// 记录详细的底层错误
+		r.logger.Error("连接RabbitMQ失败", zap.Error(err))
+		return fmt.Errorf("连接RabbitMQ失败: %w", err)
+	}
+
+	r.ch, err = r.conn.Channel()
+	if err != nil {
+		_ = r.conn.Close()
+		return fmt.Errorf("创建通道失败: %w", err)
+	}
+
+	r.closed = false
+	r.logger.Info("RabbitMQ连接成功")
+	return nil
+}
+
+// reconnectLoop 监控连接状态并处理重连
+func (r *RabbitMQ) reconnectLoop() {
+	for {
+		closeChan := make(chan *amqp.Error)
+		r.mu.RLock()
+		if r.conn == nil {
+			r.mu.RUnlock()
+			time.Sleep(5 * time.Second)
+			continue
+		}
+		r.conn.NotifyClose(closeChan)
+		isClosed := r.closed
+		r.mu.RUnlock()
+
+		if isClosed {
+			r.logger.Info("RabbitMQ客户端已关闭,停止重连循环。")
+			return
+		}
+
+		closeErr := <-closeChan
+		if closeErr != nil {
+			r.logger.Error("RabbitMQ连接断开,将尝试重新连接", zap.Error(closeErr))
+		} else {
+			r.logger.Info("RabbitMQ连接正常关闭。")
+		}
+
+		r.mu.RLock()
+		isClosed = r.closed
+		r.mu.RUnlock()
+		if isClosed {
+			r.logger.Info("RabbitMQ客户端已关闭,停止重连。")
+			return
+		}
+
+		backoff := 1 * time.Second
+		maxBackoff := 30 * time.Second
+
+		for {
+			if r.isClosed() {
+				return
+			}
+			err := r.Connect()
+			if err == nil {
+				r.logger.Info("RabbitMQ重新连接成功")
+				// 重新设置任务队列
+				if err := r.SetupAllTaskQueues(); err != nil {
+					r.logger.Error("重新设置所有任务队列失败", zap.Error(err))
+				}
+				break
+			}
+
+			r.logger.Error("RabbitMQ重连失败", zap.Error(err), zap.Duration("backoff", backoff))
+
+			time.Sleep(backoff)
+			backoff *= 2
+			if backoff > maxBackoff {
+				backoff = maxBackoff
+			}
+		}
+	}
+}
+
+// Close 关闭连接
+func (r *RabbitMQ) Close() error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if r.closed {
+		return nil
+	}
+	r.closed = true
+
+	var errs []error
+
+	if r.ch != nil {
+		if err := r.ch.Close(); err != nil {
+			errs = append(errs, fmt.Errorf("关闭channel失败: %w", err))
+		}
+	}
+
+	if r.conn != nil && !r.conn.IsClosed() {
+		if err := r.conn.Close(); err != nil {
+			errs = append(errs, fmt.Errorf("关闭connection失败: %w", err))
+		}
+	}
+
+	if len(errs) > 0 {
+		return fmt.Errorf("关闭RabbitMQ时发生错误: %v", errs)
+	}
+
+	return nil
+}
+
+func (r *RabbitMQ) isClosed() bool {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+	return r.closed
+}
+
+// GetTaskConfig retrieves a specific task's configuration.
+func (r *RabbitMQ) GetTaskConfig(name string) (TaskConfig, bool) {
+	taskCfg, ok := r.config.Tasks[name]
+	return taskCfg, ok
+}
+
+func (r *RabbitMQ) withChannel(fn func(*amqp.Channel) error) error {
+	if r.isClosed() {
+		return ErrClosed
+	}
+
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	if r.ch == nil || r.conn.IsClosed() {
+		return errors.New("rabbitmq: channel or connection is not available")
+	}
+	return fn(r.ch)
+}
+
+// Publish sends a message to the specified exchange with the given routing key.
+// This is a convenience wrapper around PublishWithCh.
+func (r *RabbitMQ) Publish(exchange, routingKey string, body []byte) error {
+	return r.PublishWithCh(exchange, routingKey, amqp.Publishing{
+		ContentType:  "text/plain",
+		Body:         body,
+		DeliveryMode: amqp.Persistent, // Default to persistent
+	})
+}
+
+// PublishWithCh sends a message to the specified exchange with the given routing key using a custom amqp.Publishing struct.
+func (r *RabbitMQ) PublishWithCh(exchange, routingKey string, msg amqp.Publishing) error {
+	if r.isClosed() {
+		return errors.New("rabbitmq connection is closed")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	return r.withChannel(func(ch *amqp.Channel) error {
+		return ch.PublishWithContext(ctx,
+			exchange,
+			routingKey,
+			false, // mandatory
+			false, // immediate
+			msg,
+		)
+	})
+}
+
+// Consume 获取消息消费通道. 注意: Qos的设置需要调用方在获取channel后自行处理,或者为Consume方法增加prefetchCount参数
+func (r *RabbitMQ) Consume(queue, consumer string, prefetchCount int) (<-chan amqp.Delivery, error) {
+	var deliveries <-chan amqp.Delivery
+	err := r.withChannel(func(ch *amqp.Channel) error {
+		if err := ch.Qos(prefetchCount, 0, false); err != nil {
+			return fmt.Errorf("设置Qos失败: %w", err)
+		}
+
+		var err error
+		deliveries, err = ch.Consume(
+			queue,
+			consumer,
+			false, // auto-ack: false, 手动确认
+			false, // exclusive
+			false, // no-local
+			false, // no-wait
+			nil,   // args
+		)
+		return err
+	})
+	return deliveries, err
+}
+
+// SetupAllTaskQueues 遍历配置中的所有任务,并为每个任务设置队列
+func (r *RabbitMQ) SetupAllTaskQueues() error {
+	if len(r.config.Tasks) == 0 {
+		r.logger.Info("在配置中未找到任何任务队列定义。")
+		return nil
+	}
+
+	for name, taskCfg := range r.config.Tasks {
+		r.logger.Info("正在设置任务队列", zap.String("task_name", name))
+		if err := r.setupQueue(taskCfg); err != nil {
+			return fmt.Errorf("为任务 '%s' 设置队列失败: %w", name, err)
+		}
+	}
+	return nil
+}
+
+// setupQueue 为单个任务配置设置交换机、队列和绑定
+func (r *RabbitMQ) setupQueue(taskCfg TaskConfig) error {
+	if taskCfg.Exchange == "" {
+		r.logger.Warn("任务队列的交换机名称为空,将使用默认交换机。这在多任务场景下可能导致问题。", zap.String("queue", taskCfg.Queue))
+		return r.withChannel(func(ch *amqp.Channel) error {
+			_, err := ch.QueueDeclare(taskCfg.Queue, true, false, false, false, nil)
+			if err != nil {
+				return fmt.Errorf("声明队列失败 (默认交换机): %w", err)
+			}
+			r.logger.Info("成功声明队列并绑定到默认交换机", zap.String("queue", taskCfg.Queue))
+			return nil
+		})
+	}
+
+	return r.withChannel(func(ch *amqp.Channel) error {
+		// 声明主交换机
+		exchangeType := taskCfg.ExchangeType
+	if exchangeType == "" {
+		exchangeType = "direct" // 默认为 direct 类型,兼容旧配置
+	}
+	err := ch.ExchangeDeclare(
+		taskCfg.Exchange, // name
+		exchangeType,     // type
+			true,               // durable
+			false,              // autoDelete
+			false,              // internal
+			false,              // noWait
+			nil,                // args
+		)
+		if err != nil {
+			return fmt.Errorf("声明主交换机 '%s' 失败: %w", taskCfg.Exchange, err)
+		}
+
+		// 为主队列设置死信交换机参数
+		dlxExchange := taskCfg.Exchange + ".dlx"
+		args := amqp.Table{
+			"x-dead-letter-exchange": dlxExchange,
+		}
+
+		// 声明主队列
+				_, err = ch.QueueDeclare(taskCfg.Queue, true, false, false, false, args)
+		if err != nil {
+			return fmt.Errorf("声明主队列 '%s' 失败: %w", taskCfg.Queue, err)
+		}
+
+		// 绑定主队列到主交换机
+		if err := ch.QueueBind(taskCfg.Queue, taskCfg.RoutingKey, taskCfg.Exchange, false, nil); err != nil {
+			return fmt.Errorf("绑定主队列失败: %w", err)
+		}
+
+		// --- 设置死信队列 ---
+		// 声明死信交换机 (DLX)
+		if err := ch.ExchangeDeclare(dlxExchange, "direct", true, false, false, false, nil); err != nil {
+			return fmt.Errorf("声明死信交换机 '%s' 失败: %w", dlxExchange, err)
+		}
+
+		// 声明死信队列 (DLQ)
+		dlq := taskCfg.Queue + ".dlq"
+		_, err = ch.QueueDeclare(dlq, true, false, false, false, nil)
+		if err != nil {
+			return fmt.Errorf("声明死信队列 '%s' 失败: %w", dlq, err)
+		}
+
+		// 绑定DLQ到DLX,使用与主队列相同的路由键
+		if err := ch.QueueBind(dlq, taskCfg.RoutingKey, dlxExchange, false, nil); err != nil {
+			return fmt.Errorf("绑定死信队列失败: %w", err)
+		}
+
+		r.logger.Info("成功设置任务队列及其死信队列",
+			zap.String("exchange", taskCfg.Exchange),
+			zap.String("queue", taskCfg.Queue),
+			zap.String("routing_key", taskCfg.RoutingKey),
+		)
+		return nil
+	})
+}