From 6c4099ee1d637a979a6b9b6454384303b9fd10cf Mon Sep 17 00:00:00 2001 From: awei Date: Sat, 1 Aug 2026 18:42:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(dispatch):=20=E9=80=A3=E9=8C=AF=20Redis=20?= =?UTF-8?q?=E9=80=99=E4=BB=B6=E4=BA=8B=E9=82=84=E6=98=AF=E9=9D=9C=E9=BB=98?= =?UTF-8?q?=E7=9A=84=E2=80=94=E2=80=94=E8=A3=9C=E4=B8=8A=E5=95=9F=E5=8B=95?= =?UTF-8?q?=20log=E3=80=81=E7=BC=BA=E7=9C=81=E5=9F=A0=E8=88=87=E8=85=B3?= =?UTF-8?q?=E6=9C=AC=E8=A8=BB=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #72 讓 docker 的 redis 對外發布 6380,但「連到哪一台」這個問題本身還是查不出來, 而且有兩個地方仍把人導回 6379: 1. `scripts/push_e2e.sh` 的起服務註解寫 `REDIS_ADDR=localhost:6379`——照著做就會 重現 #72 修掉的那個坑(靜默連到 Mac 本機的 redis-server)。改成 6380 並寫明理由。 2. `config.Load()` 的缺省值是 `localhost:6379`。這個缺省值只有「服務跑在主機、 又忘了設 REDIS_ADDR」時才會用到——那正是會連錯台的情境。改成 6380。 docker compose 內一律明設 `REDIS_ADDR=redis:6379`,不受這行影響。 順帶更正 `Load()` 的註解:缺省值對應的是「服務在主機、相依服務在 docker」的 本地跑法,不是 compose 內部(compose 會把 DB_HOST/REDIS_ADDR 明設成服務名)。 但真正的根因不是埠號寫錯,是**連錯不會失敗**:兩台都是健康的 redis,ping 都會過, 於是限流/派單狀態寫進了另一台,而你在容器裡怎麼查都查不到。所以最重要的一行是 ping 成功之後把位址印出來(比照 DB 印 statement_timeout 的理由:ops 可見)—— 讓「連到哪一台」變成一行 log,而不是一小時的猜測。 驗收:gofmt 乾淨、go build ./... 通過、go vet ./... 無輸出、 go test ./internal/config/ ok。新增 2 個 config 測試(缺省 6380/明設就照用), 反向驗證:把缺省值改回 6379,缺省那案 FAIL。 啟動 log 那一行只驗到編譯,**沒有實跑觀察**——起 server 會跑 migration,依約定要先問過。 Co-Authored-By: Claude Opus 5 --- cmd/server/main.go | 6 ++++++ internal/config/config.go | 29 ++++++++++++++++++----------- internal/config/config_test.go | 29 +++++++++++++++++++++++++++++ scripts/push_e2e.sh | 5 ++++- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index f4b68a1..c2a1815 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -62,6 +62,12 @@ func main() { if err := redisClient.Ping(context.Background()).Err(); err != nil { log.Fatal().Err(err).Msg("連線 Redis 失敗") } + // **連錯台不會失敗**:開發機上常有第二個 redis 在聽(Mac 的 redis-server 綁 + // 127.0.0.1:6379,比 docker 的 *:6379 精確而優先接手),兩台都 ping 得通, + // 於是限流/派單狀態寫進了另一台,而你在容器裡怎麼查都查不到。 + // ping 成功時把位址印出來,讓「連到哪一台」是一行 log 而不是一小時的猜測 + // (比照上面 DB 印 statement_timeout 的理由:ops 可見)。 + log.Info().Str("redis_addr", cfg.RedisAddr).Msg("已連線 Redis") // Repositories customerRepo := repository.NewCustomerRepository(db) diff --git a/internal/config/config.go b/internal/config/config.go index a866b6c..563de1c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -58,19 +58,26 @@ type Config struct { FCMCredentialsFile string } -// Load 讀取環境變數,缺省值對應本地 docker-compose +// Load 讀取環境變數。 +// +// 缺省值對應的是「**服務跑在主機、相依服務跑在 docker**」這個本地開發跑法, +// 不是 compose 內部(compose 會把 DB_HOST、REDIS_ADDR 等明設成服務名)。 +// 所以 Redis 缺省是 docker 對外發布的 6380,不是 redis 的標準埠 6379——理由見該行。 func Load() (*Config, error) { cfg := &Config{ - AppPort: getEnvInt("APP_PORT", 8080), - AppEnv: getEnv("APP_ENV", "local"), - LogLevel: getEnv("LOG_LEVEL", "info"), - DBHost: getEnv("DB_HOST", "localhost"), - DBPort: getEnvInt("DB_PORT", 5432), - DBName: getEnv("DB_NAME", "fleet"), - DBUser: getEnv("DB_USER", "fleet"), - DBPassword: getEnv("DB_PASSWORD", "change_me"), - DBStatementTimeoutMs: getEnvInt("DB_STATEMENT_TIMEOUT_MS", 10000), - RedisAddr: getEnv("REDIS_ADDR", "localhost:6379"), + AppPort: getEnvInt("APP_PORT", 8080), + AppEnv: getEnv("APP_ENV", "local"), + LogLevel: getEnv("LOG_LEVEL", "info"), + DBHost: getEnv("DB_HOST", "localhost"), + DBPort: getEnvInt("DB_PORT", 5432), + DBName: getEnv("DB_NAME", "fleet"), + DBUser: getEnv("DB_USER", "fleet"), + DBPassword: getEnv("DB_PASSWORD", "change_me"), + DBStatementTimeoutMs: getEnvInt("DB_STATEMENT_TIMEOUT_MS", 10000), + // 預設 6380 而不是 6379:這個缺省值只有「服務跑在主機、又沒設 REDIS_ADDR」時才會用到, + // 而那正是會靜默連到開發者自己那台 redis-server 的情境(見 docker-compose 的說明)。 + // docker compose 內一律明設 REDIS_ADDR=redis:6379,不受這行影響。 + RedisAddr: getEnv("REDIS_ADDR", "localhost:6380"), LineChannelSecret: getEnv("LINE_CHANNEL_SECRET", ""), LineChannelAccessToken: getEnv("LINE_CHANNEL_ACCESS_TOKEN", ""), LiffID: getEnv("LIFF_ID", ""), diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 9bb5e57..5018b4f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -33,3 +33,32 @@ func TestMigrateDSN_不受statement_timeout影響(t *testing.T) { t.Fatalf("migrations 連線不應套用 statement_timeout:%s", c.MigrateDSN()) } } + +// 沒設 REDIS_ADDR 時的缺省值刻意是 6380,不是 redis 的標準埠 6379。 +// +// 這個缺省值只有「服務跑在主機、又忘了設 REDIS_ADDR」時才會用到,而那正是會靜默連到 +// 開發機自己那台 redis-server 的情境:兩台都 ping 得通,所以不會有任何錯誤, +// 只會讓限流/派單狀態寫進另一台,在容器裡怎麼查都查不到(見 docker-compose 的說明)。 +// 這條測試釘的是那個決定——看到 6380 覺得「怪,改回標準埠吧」的人會在這裡被擋下來。 +func TestLoad_Redis缺省埠是6380而非6379(t *testing.T) { + t.Setenv("REDIS_ADDR", "") + cfg, err := Load() + if err != nil { + t.Fatalf("Load 失敗:%v", err) + } + if cfg.RedisAddr != "localhost:6380" { + t.Fatalf("缺省 RedisAddr 應為 localhost:6380(避開本機 redis-server 的 6379),實際:%s", cfg.RedisAddr) + } +} + +func TestLoad_有設REDIS_ADDR就照用(t *testing.T) { + t.Setenv("REDIS_ADDR", "redis:6379") + cfg, err := Load() + if err != nil { + t.Fatalf("Load 失敗:%v", err) + } + // docker compose 內就是這個值——缺省值的改動不可以影響已明設的情況。 + if cfg.RedisAddr != "redis:6379" { + t.Fatalf("明設 REDIS_ADDR 應照用,實際:%s", cfg.RedisAddr) + } +} diff --git a/scripts/push_e2e.sh b/scripts/push_e2e.sh index 861eed3..6b30b1d 100755 --- a/scripts/push_e2e.sh +++ b/scripts/push_e2e.sh @@ -8,8 +8,11 @@ # load metadata for golang:1.25-alpine,改用這條): # docker compose up -d postgis redis # DB_HOST=localhost DB_PORT=5433 DB_NAME=fleet DB_USER=fleet DB_PASSWORD=change_me \ -# REDIS_ADDR=localhost:6379 ADMIN_SEED_USERNAME=admin ADMIN_SEED_PASSWORD=admin \ +# REDIS_ADDR=127.0.0.1:6380 ADMIN_SEED_USERNAME=admin ADMIN_SEED_PASSWORD=admin \ # go run ./cmd/server > /tmp/push_server.log 2>&1 & +# **Redis 要 6380 不要 6379**:Mac 上常駐的 redis-server 綁 127.0.0.1:6379,比 docker 的 +# *:6379 精確,localhost 一律由它接手——連錯不會報錯(兩台都 ping 得通),只會讓你在容器裡 +# 查不到任何鍵,誤判成「限流/狀態根本沒寫進去」。啟動 log 的 redis_addr 會說這次連了哪台。 # 跑完看證據: # grep 'App 推播(stub)' /tmp/push_server.log set -e