实现七月迭代公共技术基础
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s

This commit is contained in:
2026-07-23 17:52:48 +09:00
parent f7c42252c0
commit 17782d5f8e
76 changed files with 5246 additions and 158 deletions

View File

@@ -16,6 +16,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/polling"
iot_card_svc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
@@ -36,6 +37,7 @@ const (
workerModulePollingInitializer = "polling_initializer"
workerModulePollingScheduler = "polling_scheduler"
workerModuleAsynqScheduler = "asynq_scheduler"
workerModuleOutboxRelay = "outbox_relay"
importRescueLimit = 500 // 启动补偿单次扫描的最大导入任务数
)
@@ -55,6 +57,8 @@ type workerRuntime struct {
asynqClient *asynq.Client
workerResult *bootstrap.WorkerBootstrapResult
workerServer *queue.Server
outboxQueueClient *queue.Client
outboxConsumers *outbox.ConsumerRegistry
pollingConfigMgr *polling.PollingConfigManager
pollingQueueMgr *polling.PollingQueueManager
pollingIotCardStore *postgres.IotCardStore
@@ -124,6 +128,9 @@ func runWorker(cfg *config.Config) {
taskHandler := createTaskHandler(runtime, appLogger)
taskHandler.RegisterHandlers()
outboxHandler := outbox.NewHandler(runtime.outboxConsumers)
taskHandler.GetMux().HandleFunc(constants.TaskTypeOutboxDeliver, outboxHandler.Handle)
startOutboxRelay(ctx, runtime, cfg.Worker.InstanceName, appLogger)
rescuePendingImportTasks(ctx, runtime, appLogger)
appLogger.Info("Worker 服务器配置完成",
@@ -149,7 +156,7 @@ func runWorker(cfg *config.Config) {
// buildWorkerModuleStatus 根据角色生成启用/禁用模块列表,供启动日志和人工验收使用。
func buildWorkerModuleStatus(role string) workerModuleStatus {
status := workerModuleStatus{
enabled: []string{workerModuleQueueServer},
enabled: []string{workerModuleQueueServer, workerModuleOutboxRelay},
}
if runsSingletonModules(role) {
@@ -233,6 +240,7 @@ func initWorkerRuntime(ctx context.Context, cfg *config.Config, appLogger *zap.L
}
workerServer := queue.NewServer(redisClient, &cfg.Queue, appLogger)
outboxQueueClient := queue.NewClient(redisClient, appLogger)
pollingConfigStore := postgres.NewPollingConfigStore(db)
pollingConfigMgr := polling.NewPollingConfigManager(pollingConfigStore, redisClient, appLogger)
@@ -275,6 +283,8 @@ func initWorkerRuntime(ctx context.Context, cfg *config.Config, appLogger *zap.L
asynqClient: asynqClient,
workerResult: workerResult,
workerServer: workerServer,
outboxQueueClient: outboxQueueClient,
outboxConsumers: outbox.NewConsumerRegistry(),
pollingConfigMgr: pollingConfigMgr,
pollingQueueMgr: pollingQueueMgr,
pollingIotCardStore: pollingIotCardStore,
@@ -285,6 +295,11 @@ func initWorkerRuntime(ctx context.Context, cfg *config.Config, appLogger *zap.L
// close 在 Worker 退出时关闭共享客户端与数据库连接。
func (r *workerRuntime) close(appLogger *zap.Logger) {
if r.outboxQueueClient != nil {
if err := r.outboxQueueClient.Close(); err != nil {
appLogger.Error("关闭 Outbox 队列客户端失败", zap.Error(err))
}
}
if r.asynqClient != nil {
if err := r.asynqClient.Close(); err != nil {
appLogger.Error("关闭 Asynq 客户端失败", zap.Error(err))
@@ -305,6 +320,39 @@ func (r *workerRuntime) close(appLogger *zap.Logger) {
}
}
// startOutboxRelay 启动公共 Outbox 的可取消轮询;多个 Worker 依靠 PostgreSQL 租约并发协调。
func startOutboxRelay(ctx context.Context, runtime *workerRuntime, instanceName string, appLogger *zap.Logger) {
owner := instanceName
if owner == "" {
owner = fmt.Sprintf("worker-%d", os.Getpid())
}
relay, err := outbox.NewRelay(
runtime.db,
outbox.NewQueuePublisher(runtime.outboxQueueClient),
appLogger,
outbox.RelayOptions{Owner: owner},
)
if err != nil {
appLogger.Fatal("初始化 Outbox Relay 失败", zap.Error(err))
}
go func() {
ticker := time.NewTicker(constants.OutboxRelayPollInterval)
defer ticker.Stop()
for {
if _, err := relay.ProcessBatch(ctx); err != nil && ctx.Err() == nil {
appLogger.Error("Outbox Relay 批次处理失败",
zap.String("component", "outbox_relay"), zap.String("error_code", "OUTBOX_RELAY_BATCH_FAILED"), zap.Error(err))
}
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}()
appLogger.Info("Outbox Relay 已启动", zap.String("lease_owner", owner))
}
// startPollingInitializer 为 leader/all 启动渐进式初始化器,并保留配置变更重启逻辑。
func startPollingInitializer(ctx context.Context, runtime *workerRuntime, appLogger *zap.Logger) *polling.PollingInitializer {
pollingInitializer := polling.NewPollingInitializer(