# apps/backend Harbor Desk Phase C(go-zero)。 ## 原則 | 做 | 不做 | |----|------| | `generate/api` + `make gen-api` | 手寫 handler/routes | | `generate/database/mongo` + `make migrate-up` | 在 gateway 啟動時隱式建 index/seed | | `monc` + `CacheRedis` | 自己半套 mongo cache | | `internal/logic` + `module/*/domain|repository|usecase` | 業務塞進 handler | ## 指令 ```bash cd apps/backend make build # gateway + worker make start # build 後背景啟動 gateway + worker(PID/log 在 /tmp/haixun) make stop make restart make test make gen-api # 改 .api 後 make migrate-up # 改 generate/database 後 make init # 只建立可重複執行的 operational indexes make seeder # 讀 etc/gateway.yaml 的 Seed.AdminPassword make crawler-install # 安裝 dev_mode Chrome crawler 的 Playwright/Chromium SCOUT_CRAWLER_TOKEN='...' make crawler-start SCOUT_CRAWLER_TOKEN='...' make crawler-restart make tidy ``` ### Scout Chrome Crawler `dev_mode=true` 時,Scout 只走私有 Playwright crawler,沒有同步有效 Chrome session 就會要求使用者先同步。 1. 在私有 `etc/gateway.yaml` 填 `Scout.SessionSecret`、`Scout.CrawlerEndpoint`、`Scout.CrawlerToken`。 2. 執行 `make crawler-install` 一次。 3. 執行 `SCOUT_CRAWLER_TOKEN='與 Scout.CrawlerToken 相同的值' make crawler-start`。 4. crawler 必須維持 `127.0.0.1` 私網綁定,禁止公開反向代理。 詳見 [`crawler/README.md`](crawler/README.md)。 ```bash # 需要 Mongo + Redis(go-zero monc / redis) # 私有設定只放在 etc/gateway.yaml(此檔不會被 git 追蹤): # cp etc/gateway.example.yaml etc/gateway.yaml # 填入 Mongo/Redis/MinIO 密碼與兩組不同的 JWT secret。 # Mongo 若有帳密(本機 infra 常見),一定要帶進 URI: MONGO_URL='mongodb://USER:PASS@127.0.0.1:27017/haixun?authSource=admin' make migrate-up go run . -f etc/gateway.yaml # Seed.AdminPassword 填好後執行 make seeder(uid=1000000 → 顯示 01000000) ``` ### 日誌與密碼(安全) | 禁止出現在 log | 說明 | |----------------|------| | `password` / `new_password` / `current_password` / `temporary_password` | 明碼 | | `password_hash` | bcrypt 也不可打 log | | access/refresh token、api_key | 憑證 | 實作: - 啟動時 `securelog.SilenceMongo()` → 關閉 go-zero **mon** 文件 dump(否則 ReplaceOne 會印出整份 member) - `RestConf.Verbose = false` → 不印 request body - 自寫 log 請用 `securelog.RedactAny(v)` / 勿 `logx.Infof("%+v", req)` HTTP access log 只記 method/path/status/duration,不含 body。 ### 物件儲存(頭像)— **僅 MinIO/S3** 不落本機磁碟(無 localstore / `/files`)。未設定 Endpoint 時 upload 直接失敗。 | 項目 | 說明 | |------|------| | API | `POST /api/v1/media/upload`(需 JWT)`{ content: dataURL, kind: "avatar" }` | | 回傳 | `{ url, key }` — Mongo member **只存 url** | | 後端 | MinIO(dev)或 S3(prod),S3 API + path-style | | env | `HAIXUN_STORAGE_S3_*` 或 `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` | ```yaml ObjectStorage: Endpoint: "http://127.0.0.1:9000" # 必填 Bucket: "haixun-assets" AccessKey: "haixun" SecretKey: "..." UsePathStyle: true PublicBaseURL: "" # 或 CDN / 反向代理公開前綴 ``` FE:選頭像 → upload → URL → `PATCH profile`(禁止 data URL)。 ### 密碼政策(前後端一致) | 規則 | 說明 | |------|------| | 長度 | **≥ 12** 字元 | | 組成 | **大寫 + 小寫 + 數字 + 符號** 皆須至少一個 | | 後端 | `ValidatePasswordPolicy` → 不符回 **400003** + `domain.PasswordPolicyEN` | | 前端 | `passwordPolicy.ts` + 語言包 `password.policy.hint` / `api.err.400003` | **同一句說明:** - EN: `Password must be at least 12 characters with upper, lower, digit, and symbol` - ZH: `密碼須至少 12 碼,且含大寫、小寫、數字與符號` 前端依 code 400003 顯示語系文案;placeholder/hint/錯誤都用同一 key,勿另寫一句。 ### 寄信(信箱驗證 / 重設密碼) 業務在 `internal/logic/auth/*`:產碼 → `Notification.RenderVerify` → `SendEmail`。 寄送實作在 `internal/module/notification`(SMTP,Mailgun 相容)。 #### 模板與多語 - 引擎與 stand-alone 相同:**hermes**(`github.com/matcornic/hermes/v2`) - 語系:`zh-TW`(預設)/`en`;來源為會員 `preferred_language`,忘記密碼則用請求 `language`(FE 當前 locale) - Logo:`Brand.LogoURL` 或預設 `{PublicWebBase}/brand-mark.jpg`(`apps/web/public/brand-mark.jpg`) ```yaml PublicWebBase: "http://127.0.0.1:5173" Brand: Name: "Harbor Desk" LogoURL: "" # 空 = PublicWebBase/brand-mark.jpg Copyright: "© Harbor Desk" ``` #### 本機:Mailpit(推薦) ```bash # 已含在 old/infra docker-compose(mongo/redis/mailpit) cd old/infra && docker compose --env-file ../deploy/.env.dev up -d mailpit # UI 看信 open http://127.0.0.1:8025 ``` | 項目 | 值 | |------|-----| | SMTP | `127.0.0.1:1025`(無密碼也可;可開 AUTH accept-any 模擬 Mailgun) | | Web UI | http://127.0.0.1:8025 | | gateway 預設 | `Mail.SMTP.Host=127.0.0.1` `Port=1025` | 呼叫 `POST /api/v1/auth/email/send-code` 或 `password/request-reset` 後,到 Mailpit UI 看 HTML 信。 #### 正式:Mailgun SMTP ```yaml Mail: Sender: "Harbor Desk " SMTP: Host: "smtp.mailgun.org" Port: 587 User: "postmaster@YOUR_DOMAIN" Password: "" DevExposeCode: false ``` 同一套 `Mail.SMTP` 設定;本機指 Mailpit、上線指 Mailgun,**code 不用改**。 env 覆寫:`MAIL_SMTP_*` 或 `HAIXUN_SMTP_*`(與 `old/deploy/.env.dev` 相容)。 | SMTP Host | 行為 | |-----------|------| | **空** | log-only,不真寄 | | **Mailpit / Mailgun** | 真走 SMTP 投遞 | ### 前後端串不起來?檢查這幾項 1. **gateway 有在跑** `ss -tlnp | grep 8888`;沒有就 `go run . -f etc/gateway.yaml` 2. **Mongo URI 有帳密**(`export MONGO_URI=...`;沒帶會查不到 seed) 3. **Redis 有密碼**(`export REDIS_PASSWORD=...`;沒帶 monc 回 `NOAUTH` → API 500) 4. **已跑 migrate**(`make migrate-up`,seed `admin@haixun.local`) 5. **FE 切到 live**:設定 → 資料來源 → Live(預設是 **mock**,根本不會打後端) 6. **API base**:`apps/web/.env` 建議 `VITE_API_BASE=`(空),走 Vite proxy `/api` → `:8888` 遠端開 `*.30cm.net` 時**不要**寫死 `http://127.0.0.1:8888`(那會打到使用者自己電腦) 7. 切 live 後**重新登入** seed:`admin@haixun.local` / `admin123` | 資料 | 存哪 | 關機 | |------|------|------| | 會員 / settings | **Mongo**(monc) | 保留 | | refresh token / 驗證碼 | **Mongo**(TTL index) | 保留(過期自動清) | | monc 實體快取 | Redis(CacheRedis) | 可丟,會從 Mongo 回填 | ## 目錄 ```text generate/api/ # goctl 契約 generate/database/mongo # golang-migrate JSON generate/goctl/ # handler 模板(102000) cmd/worker/ # 獨立 worker internal/handler|logic # goctl 產 / 薄邏輯 internal/module/ # domain · repository · usecase internal/lib/mongo # monc URI 小工具 etc/gateway.yaml # 唯一設定 ``` ## 設定摘要 ```yaml CacheRedis: # monc Redis 快取 - Host: 127.0.0.1:6379 Type: node Mongo: URI: "mongodb://127.0.0.1:27017" # 有 auth 用 MONGO_URI 覆寫 Database: "haixun" ``` ### 會員 uid - 從 **1_000_000** 起(seed admin = `1000000`) - 顯示補成 **8 位**:`01000000`(`%08d` / FE `padStart(8,"0")`) - 新註冊:`1000001`, `1000002`, …