Initial zhanlu OpenAI proxy
build / build (push) Successful in 53s

This commit is contained in:
M1saka
2026-07-08 09:13:45 +08:00
commit d4dd3a0f1b
14 changed files with 1673 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
package config
import (
"os"
"path/filepath"
"strings"
"time"
"git.misaka.ren/M1saka/zhanlu_proxy/internal/auth"
)
type Config struct {
ListenAddr string
ServerBaseURL string
UpstreamPath string
CredentialsPath string
SSOExchangeURL string
SSOBaseURL string
TokenDecryptKey string
PublicKeyPEM string
PhonePublicKeyPEM string
Models []string
DefaultModel string
OpenAIAPIKey string
UpstreamTimeout time.Duration
StreamIdleTimout time.Duration
Debug bool
Credentials auth.Credentials
}
func Load() (Config, error) {
cfg := Config{
ListenAddr: getenv("ZHANLU_LISTEN_ADDR", ":8080"),
ServerBaseURL: getenv("ZHANLU_SERVER_BASE_URL", "https://api-wuxi-1.cmecloud.cn:8443"),
UpstreamPath: getenv("ZHANLU_UPSTREAM_PATH", "/api/acepilot/zhanlu/aiDeveloper/chat"),
CredentialsPath: getenv("ZHANLU_CREDENTIALS_FILE", defaultCredentialsPath()),
SSOBaseURL: getenv("ZHANLU_SSO_BASE_URL", "http://rdcloud.4c.hq.cmcc"),
SSOExchangeURL: getenv("ZHANLU_SSO_EXCHANGE_URL", "https://api-wuxi-1.cmecloud.cn:8443/cmdevops-aiplus-agent-gateway/api/acepilot/zhanlu/checkoutCode"),
TokenDecryptKey: os.Getenv("ZHANLU_TOKEN_DECRYPT_KEY"),
PublicKeyPEM: getenv("ZHANLU_PUBLIC_KEY_PEM", defaultPublicKeyPEM),
PhonePublicKeyPEM: getenv("ZHANLU_PHONE_PUBLIC_KEY_PEM", defaultPhonePublicKeyPEM),
DefaultModel: getenv("ZHANLU_DEFAULT_MODEL", "minimax-m25"),
OpenAIAPIKey: os.Getenv("OPENAI_COMPAT_API_KEY"),
UpstreamTimeout: durationEnv("ZHANLU_UPSTREAM_TIMEOUT", 120*time.Second),
StreamIdleTimout: durationEnv("ZHANLU_STREAM_IDLE_TIMEOUT", 300*time.Second),
Debug: strings.EqualFold(os.Getenv("ZHANLU_DEBUG"), "true"),
}
cfg.Models = splitCSV(getenv("ZHANLU_MODELS", "glm47,minimax-m25"))
cfg.Credentials = auth.Credentials{
AccessKey: os.Getenv("ZHANLU_ACCESS_KEY"),
SecretKey: os.Getenv("ZHANLU_SECRET_KEY"),
Token: os.Getenv("ZHANLU_TOKEN"),
}
if cfg.Credentials.Validate() == nil {
return cfg, nil
}
if creds, err := auth.LoadCredentials(cfg.CredentialsPath); err == nil {
cfg.Credentials = creds
}
return cfg, nil
}
func getenv(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return fallback
}
func splitCSV(s string) []string {
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
}
}
return out
}
func durationEnv(key string, fallback time.Duration) time.Duration {
v := strings.TrimSpace(os.Getenv(key))
if v == "" {
return fallback
}
d, err := time.ParseDuration(v)
if err != nil {
return fallback
}
return d
}
func defaultCredentialsPath() string {
return filepath.Join(".", "credentials.json")
}
const defaultPublicKeyPEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhxudxTewPgljUHEZHkusP7m3I+zA4/RGvuUMt6TtII/m4zwUOm/Y31zHBTmkCCt8k5vj9y+AmO0TsGmHooNQuMebakdmEWdcA5h7YAHHFbF2w5LcxIXjib08vgVpA+m3R5xPbLK+vfHe2aAX36b5nHReDNncY5vAl3U4CgIEBGPqyG67vJytRWqP+sfEdw5+m192Rf4SCGyiBzRmjiVlH3zeEBjdbOrkAnzKOVz6AHBl2q7LPLJKIzxjoAyhEp5qnDjHUFo5VZUgFwUOt83A/jbGMyzmjRoxBuvKcs9tBuorZyUwIsZN6E+rtQk2YqMPj4RkDsZ7LRmj6on8sN2rHQIDAQAB
-----END PUBLIC KEY-----`
const defaultPhonePublicKeyPEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnqiA2qP9BNvKw5DnVnrBVBhd+5gJDVn3mDemCfq/AN1cdaHV57hQo6R1ufp45mOkSwLaJcTE82zFKmgKoEAKwD1SR10rp0xJC7x3yvx2FbpEsiW9TeZlvJdri1BYKUMS8OP8ykjHSJoy0oMaV6e95R2rsu4DEH7JuA9+Bt0sOoLewvHx/fs1e28tH+928uUEKdLug+cv/XTKjLudpLjiSMPZU6EHFqrUhA9zmEasOMmg9Dj0j4sChBooCeCGnh/pYHJaosH5amhlSQ8FnEG0BQBrQbZ+qhRH4LYyqGYN8grDNeSnPj7vPDcwiEm++85i5AngZfEMnGWZg5jYDhO9+QIDAQAB
-----END PUBLIC KEY-----`