package config import ( "os" "strconv" "strings" sdkconfig "github.com/go-admin-team/go-admin-core/sdk/config" ) var ExtConfig Extend // Extend 扩展配置 // // extend: // demo: // name: demo-name // // 使用方法: config.ExtConfig......即可!! type Extend struct { AMap AMap // 这里配置对应配置文件的结构即可 SYB SYB Yeeke Yeeke } type AMap struct { Key string } // SYB holds the 顺云宝 ERP connection settings (#48). // // `[必须]` Username and Password are NOT read from settings.yml — they come // from GOAUTO_SYB_USERNAME / GOAUTO_SYB_PASSWORD via ApplyEnvironment, so no // credential ever lands in a tracked file. Everything else here is non-secret // and belongs in settings.yml. // // See docs/12-syb-erp-interface.md §8. type SYB struct { BaseURL string Username string Password string PageSize int // MaxMatches caps how many shipment orders one sync may touch. Zero means // the caller's own default applies. MaxMatches int // OcrURL is the captcha recognition service. Empty disables OCR and falls // back to the manual-entry path — that is a supported configuration, not an // error (§8.1). // // `[必须]` Captcha images are sent to this host. Re-evaluate before pointing // it at a service operated by somebody else. OcrURL string OcrMaxAttempts int } // SYBDefaults are the values used when settings.yml leaves a field blank. const ( DefaultSYBBaseURL = "https://www.shunyunbaoerp.com" DefaultSYBPageSize = 50 DefaultSYBMaxMatches = 10000 DefaultSYBOcrMaxAttempts = 5 ) // Resolved returns the SYB settings with blanks replaced by defaults. It never // defaults Username or Password: missing credentials must surface as an error // at the call site, not as an attempt to log in as nobody. func (s SYB) Resolved() SYB { if strings.TrimSpace(s.BaseURL) == "" { s.BaseURL = DefaultSYBBaseURL } if s.PageSize <= 0 { s.PageSize = DefaultSYBPageSize } if s.MaxMatches <= 0 { s.MaxMatches = DefaultSYBMaxMatches } if s.OcrMaxAttempts <= 0 { s.OcrMaxAttempts = DefaultSYBOcrMaxAttempts } s.Username = strings.TrimSpace(s.Username) s.BaseURL = strings.TrimRight(strings.TrimSpace(s.BaseURL), "/") s.OcrURL = strings.TrimSpace(s.OcrURL) return s } // HasCredentials reports whether both account fields were supplied. Callers // check this before starting a sync so a missing environment variable fails // with a clear message instead of a 未登录 error from SYB. func (s SYB) HasCredentials() bool { return strings.TrimSpace(s.Username) != "" && s.Password != "" } // Yeeke holds the mmt.yeeke.com 退货包裹只读对接 connection settings (#336). // // `[必须]` Username and Password are NOT read from settings.yml — same rule as // SYB above — they come from GOAUTO_YEEKE_USERNAME / GOAUTO_YEEKE_PASSWORD (or // config.yaml's yeeke: section) so no credential ever lands in a tracked file. type Yeeke struct { BaseURL string Username string Password string PageSize int MaxPages int Retry int // OcrURL is the captcha recognition service shared with SYB (#336, approved // 2026-09-23). Empty disables OCR; there is no manual-entry fallback here // because this is a server-side scheduled/triggered flow, not an interactive // login form, so a disabled OCR simply makes sync fail with a clear error. OcrURL string OcrMaxAttempts int } // YeekeDefaults are the values used when settings.yml leaves a field blank. const ( DefaultYeekeBaseURL = "https://mmt.yeeke.com" DefaultYeekePageSize = 100 DefaultYeekeMaxPages = 10000 DefaultYeekeRetry = 2 DefaultYeekeOcrMaxAttempts = 5 ) // Resolved returns the Yeeke settings with blanks replaced by defaults. It // never defaults Username or Password: missing credentials must surface as an // error at the call site, not as an attempt to log in as nobody. func (y Yeeke) Resolved() Yeeke { if strings.TrimSpace(y.BaseURL) == "" { y.BaseURL = DefaultYeekeBaseURL } if y.PageSize <= 0 { y.PageSize = DefaultYeekePageSize } if y.MaxPages <= 0 { y.MaxPages = DefaultYeekeMaxPages } if y.Retry < 0 { y.Retry = DefaultYeekeRetry } if y.OcrMaxAttempts <= 0 { y.OcrMaxAttempts = DefaultYeekeOcrMaxAttempts } y.Username = strings.TrimSpace(y.Username) y.BaseURL = strings.TrimRight(strings.TrimSpace(y.BaseURL), "/") y.OcrURL = strings.TrimSpace(y.OcrURL) return y } // HasCredentials reports whether both account fields were supplied. func (y Yeeke) HasCredentials() bool { return strings.TrimSpace(y.Username) != "" && y.Password != "" } // ApplyEnvironment replaces tracked defaults with process-local runtime values. // Credentials stay outside tracked configuration files. GOAUTO_DB_DRIVER // defaults to mysql when GOAUTO_DB_DSN is present. func ApplyEnvironment() { if port, err := strconv.Atoi(strings.TrimSpace(os.Getenv("GOAUTO_SERVER_PORT"))); err == nil && port >= 1 && port <= 65535 { sdkconfig.ApplicationConfig.Port = int64(port) } if username := strings.TrimSpace(os.Getenv("GOAUTO_SYB_USERNAME")); username != "" { ExtConfig.SYB.Username = username } // `[必须]` The password is taken verbatim: trimming it would silently change // a credential whose leading or trailing space is intentional. if password := os.Getenv("GOAUTO_SYB_PASSWORD"); password != "" { ExtConfig.SYB.Password = password } if username := strings.TrimSpace(os.Getenv("GOAUTO_YEEKE_USERNAME")); username != "" { ExtConfig.Yeeke.Username = username } // `[必须]` Taken verbatim, same reasoning as GOAUTO_SYB_PASSWORD above. if password := os.Getenv("GOAUTO_YEEKE_PASSWORD"); password != "" { ExtConfig.Yeeke.Password = password } dsn := strings.TrimSpace(os.Getenv("GOAUTO_DB_DSN")) if dsn == "" { return } driver := strings.TrimSpace(os.Getenv("GOAUTO_DB_DRIVER")) if driver == "" { driver = "mysql" } sdkconfig.DatabaseConfig.Driver = driver sdkconfig.DatabaseConfig.Source = dsn for _, database := range sdkconfig.DatabasesConfig { database.Driver = driver database.Source = dsn } }