fix(settings): restore wechat and payment config persistence
This commit is contained in:
@@ -111,6 +111,15 @@ const (
|
||||
SettingKeyLinuxDoConnectClientSecret = "linuxdo_connect_client_secret"
|
||||
SettingKeyLinuxDoConnectRedirectURL = "linuxdo_connect_redirect_url"
|
||||
|
||||
// WeChat Connect OAuth 登录设置
|
||||
SettingKeyWeChatConnectEnabled = "wechat_connect_enabled"
|
||||
SettingKeyWeChatConnectAppID = "wechat_connect_app_id"
|
||||
SettingKeyWeChatConnectAppSecret = "wechat_connect_app_secret"
|
||||
SettingKeyWeChatConnectMode = "wechat_connect_mode"
|
||||
SettingKeyWeChatConnectScopes = "wechat_connect_scopes"
|
||||
SettingKeyWeChatConnectRedirectURL = "wechat_connect_redirect_url"
|
||||
SettingKeyWeChatConnectFrontendRedirectURL = "wechat_connect_frontend_redirect_url"
|
||||
|
||||
// Generic OIDC OAuth 登录设置
|
||||
SettingKeyOIDCConnectEnabled = "oidc_connect_enabled"
|
||||
SettingKeyOIDCConnectProviderName = "oidc_connect_provider_name"
|
||||
|
||||
@@ -93,6 +93,11 @@ type UpdatePaymentConfigRequest struct {
|
||||
CancelRateLimitWindow *int `json:"cancel_rate_limit_window"`
|
||||
CancelRateLimitUnit *string `json:"cancel_rate_limit_unit"`
|
||||
CancelRateLimitMode *string `json:"cancel_rate_limit_window_mode"`
|
||||
|
||||
VisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
|
||||
VisibleMethodWxpaySource *string `json:"payment_visible_method_wxpay_source"`
|
||||
VisibleMethodAlipayEnabled *bool `json:"payment_visible_method_alipay_enabled"`
|
||||
VisibleMethodWxpayEnabled *bool `json:"payment_visible_method_wxpay_enabled"`
|
||||
}
|
||||
|
||||
// MethodLimits holds per-payment-type limits.
|
||||
@@ -319,6 +324,10 @@ func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req Upda
|
||||
SettingCancelWindowSize: formatPositiveInt(req.CancelRateLimitWindow),
|
||||
SettingCancelWindowUnit: derefStr(req.CancelRateLimitUnit),
|
||||
SettingCancelWindowMode: derefStr(req.CancelRateLimitMode),
|
||||
SettingPaymentVisibleMethodAlipaySource: derefStr(req.VisibleMethodAlipaySource),
|
||||
SettingPaymentVisibleMethodWxpaySource: derefStr(req.VisibleMethodWxpaySource),
|
||||
SettingPaymentVisibleMethodAlipayEnabled: formatBoolOrEmpty(req.VisibleMethodAlipayEnabled),
|
||||
SettingPaymentVisibleMethodWxpayEnabled: formatBoolOrEmpty(req.VisibleMethodWxpayEnabled),
|
||||
}
|
||||
if req.EnabledTypes != nil {
|
||||
m[SettingEnabledPaymentTypes] = strings.Join(req.EnabledTypes, ",")
|
||||
|
||||
@@ -366,7 +366,8 @@ func newPaymentConfigServiceTestClient(t *testing.T) *dbent.Client {
|
||||
}
|
||||
|
||||
type paymentConfigSettingRepoStub struct {
|
||||
values map[string]string
|
||||
values map[string]string
|
||||
updates map[string]string
|
||||
}
|
||||
|
||||
func (s *paymentConfigSettingRepoStub) Get(context.Context, string) (*Setting, error) {
|
||||
@@ -383,10 +384,52 @@ func (s *paymentConfigSettingRepoStub) GetMultiple(_ context.Context, keys []str
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (s *paymentConfigSettingRepoStub) SetMultiple(context.Context, map[string]string) error {
|
||||
func (s *paymentConfigSettingRepoStub) SetMultiple(_ context.Context, values map[string]string) error {
|
||||
s.updates = make(map[string]string, len(values))
|
||||
for key, value := range values {
|
||||
s.updates[key] = value
|
||||
if s.values == nil {
|
||||
s.values = map[string]string{}
|
||||
}
|
||||
s.values[key] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *paymentConfigSettingRepoStub) GetAll(context.Context) (map[string]string, error) {
|
||||
return s.values, nil
|
||||
}
|
||||
func (s *paymentConfigSettingRepoStub) Delete(context.Context, string) error { return nil }
|
||||
|
||||
func TestUpdatePaymentConfig_PersistsVisibleMethodRouting(t *testing.T) {
|
||||
repo := &paymentConfigSettingRepoStub{values: map[string]string{}}
|
||||
svc := &PaymentConfigService{settingRepo: repo}
|
||||
|
||||
alipayEnabled := true
|
||||
wxpayEnabled := false
|
||||
err := svc.UpdatePaymentConfig(context.Background(), UpdatePaymentConfigRequest{
|
||||
VisibleMethodAlipayEnabled: &alipayEnabled,
|
||||
VisibleMethodAlipaySource: paymentConfigStrPtr(VisibleMethodSourceEasyPayAlipay),
|
||||
VisibleMethodWxpayEnabled: &wxpayEnabled,
|
||||
VisibleMethodWxpaySource: paymentConfigStrPtr(VisibleMethodSourceOfficialWechat),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("UpdatePaymentConfig returned error: %v", err)
|
||||
}
|
||||
|
||||
if repo.values[SettingPaymentVisibleMethodAlipayEnabled] != "true" {
|
||||
t.Fatalf("alipay enabled = %q, want true", repo.values[SettingPaymentVisibleMethodAlipayEnabled])
|
||||
}
|
||||
if repo.values[SettingPaymentVisibleMethodAlipaySource] != VisibleMethodSourceEasyPayAlipay {
|
||||
t.Fatalf("alipay source = %q, want %q", repo.values[SettingPaymentVisibleMethodAlipaySource], VisibleMethodSourceEasyPayAlipay)
|
||||
}
|
||||
if repo.values[SettingPaymentVisibleMethodWxpayEnabled] != "false" {
|
||||
t.Fatalf("wxpay enabled = %q, want false", repo.values[SettingPaymentVisibleMethodWxpayEnabled])
|
||||
}
|
||||
if repo.values[SettingPaymentVisibleMethodWxpaySource] != VisibleMethodSourceOfficialWechat {
|
||||
t.Fatalf("wxpay source = %q, want %q", repo.values[SettingPaymentVisibleMethodWxpaySource], VisibleMethodSourceOfficialWechat)
|
||||
}
|
||||
}
|
||||
|
||||
func paymentConfigStrPtr(value string) *string {
|
||||
return &value
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -512,16 +511,21 @@ func requiresWeChatJSAPICompatibleSelection(req CreateOrderRequest, sel *payment
|
||||
return req.IsWeChatBrowser || strings.TrimSpace(req.OpenID) != ""
|
||||
}
|
||||
|
||||
func (s *PaymentService) getWeChatPaymentOAuthCredential(context.Context) (string, string, error) {
|
||||
appID := strings.TrimSpace(os.Getenv("WECHAT_OAUTH_MP_APP_ID"))
|
||||
appSecret := strings.TrimSpace(os.Getenv("WECHAT_OAUTH_MP_APP_SECRET"))
|
||||
if appID == "" || appSecret == "" {
|
||||
func (s *PaymentService) getWeChatPaymentOAuthCredential(ctx context.Context) (string, string, error) {
|
||||
if s == nil || s.configService == nil || s.configService.settingRepo == nil {
|
||||
return "", "", infraerrors.ServiceUnavailable(
|
||||
"WECHAT_PAYMENT_MP_NOT_CONFIGURED",
|
||||
"wechat in-app payment requires a complete WeChat MP OAuth credential",
|
||||
)
|
||||
}
|
||||
return appID, appSecret, nil
|
||||
cfg, err := (&SettingService{settingRepo: s.configService.settingRepo}).GetWeChatConnectOAuthConfig(ctx)
|
||||
if err != nil || cfg.Mode != "mp" || strings.TrimSpace(cfg.AppID) == "" || strings.TrimSpace(cfg.AppSecret) == "" {
|
||||
return "", "", infraerrors.ServiceUnavailable(
|
||||
"WECHAT_PAYMENT_MP_NOT_CONFIGURED",
|
||||
"wechat in-app payment requires a complete WeChat MP OAuth credential",
|
||||
)
|
||||
}
|
||||
return strings.TrimSpace(cfg.AppID), strings.TrimSpace(cfg.AppSecret), nil
|
||||
}
|
||||
|
||||
func classifyCreatePaymentError(req CreateOrderRequest, providerKey string, err error) error {
|
||||
|
||||
@@ -60,10 +60,17 @@ func TestSelectCreateOrderInstancePrefersJSAPICompatibleWxpayInstance(t *testing
|
||||
}
|
||||
|
||||
configService := &PaymentConfigService{
|
||||
entClient: client,
|
||||
entClient: client,
|
||||
settingRepo: &paymentConfigSettingRepoStub{values: map[string]string{
|
||||
SettingPaymentVisibleMethodWxpayEnabled: "true",
|
||||
SettingPaymentVisibleMethodWxpaySource: VisibleMethodSourceOfficialWechat,
|
||||
SettingPaymentVisibleMethodWxpayEnabled: "true",
|
||||
SettingPaymentVisibleMethodWxpaySource: VisibleMethodSourceOfficialWechat,
|
||||
SettingKeyWeChatConnectEnabled: "true",
|
||||
SettingKeyWeChatConnectAppID: "wx-mp-app",
|
||||
SettingKeyWeChatConnectAppSecret: "wechat-secret",
|
||||
SettingKeyWeChatConnectMode: "mp",
|
||||
SettingKeyWeChatConnectScopes: "snsapi_base",
|
||||
SettingKeyWeChatConnectRedirectURL: "https://api.example.com/api/v1/auth/oauth/wechat/callback",
|
||||
SettingKeyWeChatConnectFrontendRedirectURL: "/auth/wechat/callback",
|
||||
}},
|
||||
encryptionKey: []byte(jsapiTestEncryptionKey),
|
||||
}
|
||||
@@ -77,9 +84,6 @@ func TestSelectCreateOrderInstancePrefersJSAPICompatibleWxpayInstance(t *testing
|
||||
configService: configService,
|
||||
}
|
||||
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_ID", "wx-mp-app")
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_SECRET", "wechat-secret")
|
||||
|
||||
sel, err := svc.selectCreateOrderInstance(ctx, CreateOrderRequest{
|
||||
PaymentType: payment.TypeWxpay,
|
||||
OpenID: "openid-123",
|
||||
|
||||
@@ -91,10 +91,15 @@ func TestBuildCreateOrderResponseCopiesJSAPIPayload(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMaybeBuildWeChatOAuthRequiredResponse(t *testing.T) {
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_ID", "wx123456")
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_SECRET", "wechat-secret")
|
||||
|
||||
svc := &PaymentService{}
|
||||
svc := newWeChatPaymentOAuthTestService(map[string]string{
|
||||
SettingKeyWeChatConnectEnabled: "true",
|
||||
SettingKeyWeChatConnectAppID: "wx123456",
|
||||
SettingKeyWeChatConnectAppSecret: "wechat-secret",
|
||||
SettingKeyWeChatConnectMode: "mp",
|
||||
SettingKeyWeChatConnectScopes: "snsapi_base",
|
||||
SettingKeyWeChatConnectRedirectURL: "https://api.example.com/api/v1/auth/oauth/wechat/callback",
|
||||
SettingKeyWeChatConnectFrontendRedirectURL: "/auth/wechat/callback",
|
||||
})
|
||||
|
||||
resp, err := svc.maybeBuildWeChatOAuthRequiredResponse(context.Background(), CreateOrderRequest{
|
||||
Amount: 12.5,
|
||||
@@ -132,7 +137,7 @@ func TestMaybeBuildWeChatOAuthRequiredResponse(t *testing.T) {
|
||||
func TestMaybeBuildWeChatOAuthRequiredResponseRequiresMPConfigInWeChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc := &PaymentService{}
|
||||
svc := newWeChatPaymentOAuthTestService(nil)
|
||||
|
||||
resp, err := svc.maybeBuildWeChatOAuthRequiredResponse(context.Background(), CreateOrderRequest{
|
||||
Amount: 12.5,
|
||||
@@ -155,10 +160,15 @@ func TestMaybeBuildWeChatOAuthRequiredResponseRequiresMPConfigInWeChat(t *testin
|
||||
}
|
||||
|
||||
func TestMaybeBuildWeChatOAuthRequiredResponseForSelectionSkipsEasyPayProvider(t *testing.T) {
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_ID", "wx123456")
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_SECRET", "wechat-secret")
|
||||
|
||||
svc := &PaymentService{}
|
||||
svc := newWeChatPaymentOAuthTestService(map[string]string{
|
||||
SettingKeyWeChatConnectEnabled: "true",
|
||||
SettingKeyWeChatConnectAppID: "wx123456",
|
||||
SettingKeyWeChatConnectAppSecret: "wechat-secret",
|
||||
SettingKeyWeChatConnectMode: "mp",
|
||||
SettingKeyWeChatConnectScopes: "snsapi_base",
|
||||
SettingKeyWeChatConnectRedirectURL: "https://api.example.com/api/v1/auth/oauth/wechat/callback",
|
||||
SettingKeyWeChatConnectFrontendRedirectURL: "/auth/wechat/callback",
|
||||
})
|
||||
|
||||
resp, err := svc.maybeBuildWeChatOAuthRequiredResponseForSelection(context.Background(), CreateOrderRequest{
|
||||
Amount: 12.5,
|
||||
@@ -175,3 +185,11 @@ func TestMaybeBuildWeChatOAuthRequiredResponseForSelectionSkipsEasyPayProvider(t
|
||||
t.Fatalf("expected nil response, got %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func newWeChatPaymentOAuthTestService(values map[string]string) *PaymentService {
|
||||
return &PaymentService{
|
||||
configService: &PaymentConfigService{
|
||||
settingRepo: &paymentConfigSettingRepoStub{values: values},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -173,8 +172,43 @@ var (
|
||||
const (
|
||||
defaultAuthSourceBalance = 0
|
||||
defaultAuthSourceConcurrency = 5
|
||||
defaultWeChatConnectMode = "open"
|
||||
defaultWeChatConnectScopes = "snsapi_login"
|
||||
defaultWeChatConnectFrontend = "/auth/wechat/callback"
|
||||
)
|
||||
|
||||
func normalizeWeChatConnectModeSetting(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "mp":
|
||||
return "mp"
|
||||
default:
|
||||
return "open"
|
||||
}
|
||||
}
|
||||
|
||||
func defaultWeChatConnectScopeForMode(mode string) string {
|
||||
if normalizeWeChatConnectModeSetting(mode) == "mp" {
|
||||
return "snsapi_userinfo"
|
||||
}
|
||||
return defaultWeChatConnectScopes
|
||||
}
|
||||
|
||||
func normalizeWeChatConnectScopeSetting(raw, mode string) string {
|
||||
switch normalizeWeChatConnectModeSetting(mode) {
|
||||
case "mp":
|
||||
switch strings.TrimSpace(raw) {
|
||||
case "snsapi_base":
|
||||
return "snsapi_base"
|
||||
case "snsapi_userinfo":
|
||||
return "snsapi_userinfo"
|
||||
default:
|
||||
return defaultWeChatConnectScopeForMode(mode)
|
||||
}
|
||||
default:
|
||||
return defaultWeChatConnectScopes
|
||||
}
|
||||
}
|
||||
|
||||
// NewSettingService 创建系统设置服务实例
|
||||
func NewSettingService(settingRepo SettingRepository, cfg *config.Config) *SettingService {
|
||||
return &SettingService{
|
||||
@@ -240,6 +274,13 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
SettingKeyCustomMenuItems,
|
||||
SettingKeyCustomEndpoints,
|
||||
SettingKeyLinuxDoConnectEnabled,
|
||||
SettingKeyWeChatConnectEnabled,
|
||||
SettingKeyWeChatConnectAppID,
|
||||
SettingKeyWeChatConnectAppSecret,
|
||||
SettingKeyWeChatConnectMode,
|
||||
SettingKeyWeChatConnectScopes,
|
||||
SettingKeyWeChatConnectRedirectURL,
|
||||
SettingKeyWeChatConnectFrontendRedirectURL,
|
||||
SettingKeyBackendModeEnabled,
|
||||
SettingPaymentEnabled,
|
||||
SettingKeyOIDCConnectEnabled,
|
||||
@@ -274,9 +315,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
if oidcProviderName == "" {
|
||||
oidcProviderName = "OIDC"
|
||||
}
|
||||
weChatOpenEnabled := isWeChatOAuthOpenConfigured()
|
||||
weChatMPEnabled := isWeChatOAuthMPConfigured()
|
||||
weChatEnabled := weChatOpenEnabled || weChatMPEnabled
|
||||
weChatEnabled, weChatOpenEnabled, weChatMPEnabled := s.weChatOAuthCapabilitiesFromSettings(settings)
|
||||
|
||||
// Password reset requires email verification to be enabled
|
||||
emailVerifyEnabled := settings[SettingKeyEmailVerifyEnabled] == "true"
|
||||
@@ -431,6 +470,56 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DefaultWeChatConnectScopesForMode(mode string) string {
|
||||
return defaultWeChatConnectScopeForMode(mode)
|
||||
}
|
||||
|
||||
func (s *SettingService) parseWeChatConnectOAuthConfig(settings map[string]string) (WeChatConnectOAuthConfig, error) {
|
||||
cfg := WeChatConnectOAuthConfig{
|
||||
Enabled: settings[SettingKeyWeChatConnectEnabled] == "true",
|
||||
AppID: strings.TrimSpace(settings[SettingKeyWeChatConnectAppID]),
|
||||
AppSecret: strings.TrimSpace(settings[SettingKeyWeChatConnectAppSecret]),
|
||||
Mode: normalizeWeChatConnectModeSetting(settings[SettingKeyWeChatConnectMode]),
|
||||
Scopes: normalizeWeChatConnectScopeSetting(settings[SettingKeyWeChatConnectScopes], settings[SettingKeyWeChatConnectMode]),
|
||||
RedirectURL: strings.TrimSpace(settings[SettingKeyWeChatConnectRedirectURL]),
|
||||
FrontendRedirectURL: strings.TrimSpace(settings[SettingKeyWeChatConnectFrontendRedirectURL]),
|
||||
}
|
||||
if cfg.FrontendRedirectURL == "" {
|
||||
cfg.FrontendRedirectURL = defaultWeChatConnectFrontend
|
||||
}
|
||||
|
||||
if !cfg.Enabled {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.NotFound("OAUTH_DISABLED", "wechat oauth is disabled")
|
||||
}
|
||||
if cfg.AppID == "" {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth app id not configured")
|
||||
}
|
||||
if cfg.AppSecret == "" {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth app secret not configured")
|
||||
}
|
||||
if cfg.RedirectURL == "" {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth redirect url not configured")
|
||||
}
|
||||
if cfg.FrontendRedirectURL == "" {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth frontend redirect url not configured")
|
||||
}
|
||||
if err := config.ValidateAbsoluteHTTPURL(cfg.RedirectURL); err != nil {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth redirect url invalid")
|
||||
}
|
||||
if err := config.ValidateFrontendRedirectURL(cfg.FrontendRedirectURL); err != nil {
|
||||
return WeChatConnectOAuthConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "wechat oauth frontend redirect url invalid")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) weChatOAuthCapabilitiesFromSettings(settings map[string]string) (bool, bool, bool) {
|
||||
cfg, err := s.parseWeChatConnectOAuthConfig(settings)
|
||||
if err != nil {
|
||||
return false, false, false
|
||||
}
|
||||
return true, cfg.Mode == "open", cfg.Mode == "mp"
|
||||
}
|
||||
|
||||
// filterUserVisibleMenuItems filters out admin-only menu items from a raw JSON
|
||||
// array string, returning only items with visibility != "admin".
|
||||
func filterUserVisibleMenuItems(raw string) json.RawMessage {
|
||||
@@ -467,20 +556,6 @@ func filterUserVisibleMenuItems(raw string) json.RawMessage {
|
||||
return result
|
||||
}
|
||||
|
||||
func isWeChatOAuthConfigured() bool {
|
||||
return isWeChatOAuthOpenConfigured() || isWeChatOAuthMPConfigured()
|
||||
}
|
||||
|
||||
func isWeChatOAuthOpenConfigured() bool {
|
||||
return strings.TrimSpace(os.Getenv("WECHAT_OAUTH_OPEN_APP_ID")) != "" &&
|
||||
strings.TrimSpace(os.Getenv("WECHAT_OAUTH_OPEN_APP_SECRET")) != ""
|
||||
}
|
||||
|
||||
func isWeChatOAuthMPConfigured() bool {
|
||||
return strings.TrimSpace(os.Getenv("WECHAT_OAUTH_MP_APP_ID")) != "" &&
|
||||
strings.TrimSpace(os.Getenv("WECHAT_OAUTH_MP_APP_SECRET")) != ""
|
||||
}
|
||||
|
||||
// safeRawJSONArray returns raw as json.RawMessage if it's valid JSON, otherwise "[]".
|
||||
func safeRawJSONArray(raw string) json.RawMessage {
|
||||
raw = strings.TrimSpace(raw)
|
||||
@@ -625,6 +700,15 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
}
|
||||
settings.PaymentVisibleMethodAlipaySource = alipaySource
|
||||
settings.PaymentVisibleMethodWxpaySource = wxpaySource
|
||||
settings.WeChatConnectAppID = strings.TrimSpace(settings.WeChatConnectAppID)
|
||||
settings.WeChatConnectAppSecret = strings.TrimSpace(settings.WeChatConnectAppSecret)
|
||||
settings.WeChatConnectMode = normalizeWeChatConnectModeSetting(settings.WeChatConnectMode)
|
||||
settings.WeChatConnectScopes = normalizeWeChatConnectScopeSetting(settings.WeChatConnectScopes, settings.WeChatConnectMode)
|
||||
settings.WeChatConnectRedirectURL = strings.TrimSpace(settings.WeChatConnectRedirectURL)
|
||||
settings.WeChatConnectFrontendRedirectURL = strings.TrimSpace(settings.WeChatConnectFrontendRedirectURL)
|
||||
if settings.WeChatConnectFrontendRedirectURL == "" {
|
||||
settings.WeChatConnectFrontendRedirectURL = defaultWeChatConnectFrontend
|
||||
}
|
||||
|
||||
updates := make(map[string]string)
|
||||
|
||||
@@ -694,6 +778,17 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
updates[SettingKeyOIDCConnectClientSecret] = settings.OIDCConnectClientSecret
|
||||
}
|
||||
|
||||
// WeChat Connect OAuth 登录
|
||||
updates[SettingKeyWeChatConnectEnabled] = strconv.FormatBool(settings.WeChatConnectEnabled)
|
||||
updates[SettingKeyWeChatConnectAppID] = settings.WeChatConnectAppID
|
||||
updates[SettingKeyWeChatConnectMode] = settings.WeChatConnectMode
|
||||
updates[SettingKeyWeChatConnectScopes] = settings.WeChatConnectScopes
|
||||
updates[SettingKeyWeChatConnectRedirectURL] = settings.WeChatConnectRedirectURL
|
||||
updates[SettingKeyWeChatConnectFrontendRedirectURL] = settings.WeChatConnectFrontendRedirectURL
|
||||
if settings.WeChatConnectAppSecret != "" {
|
||||
updates[SettingKeyWeChatConnectAppSecret] = settings.WeChatConnectAppSecret
|
||||
}
|
||||
|
||||
// OEM设置
|
||||
updates[SettingKeySiteName] = settings.SiteName
|
||||
updates[SettingKeySiteLogo] = settings.SiteLogo
|
||||
@@ -1200,6 +1295,10 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
|
||||
SettingKeyTablePageSizeOptions: "[10,20,50,100]",
|
||||
SettingKeyCustomMenuItems: "[]",
|
||||
SettingKeyCustomEndpoints: "[]",
|
||||
SettingKeyWeChatConnectEnabled: "false",
|
||||
SettingKeyWeChatConnectMode: "open",
|
||||
SettingKeyWeChatConnectScopes: "snsapi_login",
|
||||
SettingKeyWeChatConnectFrontendRedirectURL: defaultWeChatConnectFrontend,
|
||||
SettingKeyOIDCConnectEnabled: "false",
|
||||
SettingKeyOIDCConnectProviderName: "OIDC",
|
||||
SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency),
|
||||
@@ -1491,6 +1590,19 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
}
|
||||
result.OIDCConnectClientSecretConfigured = result.OIDCConnectClientSecret != ""
|
||||
|
||||
// WeChat Connect 设置:完全以 DB 系统设置为准。
|
||||
result.WeChatConnectEnabled = settings[SettingKeyWeChatConnectEnabled] == "true"
|
||||
result.WeChatConnectAppID = strings.TrimSpace(settings[SettingKeyWeChatConnectAppID])
|
||||
result.WeChatConnectAppSecret = strings.TrimSpace(settings[SettingKeyWeChatConnectAppSecret])
|
||||
result.WeChatConnectAppSecretConfigured = result.WeChatConnectAppSecret != ""
|
||||
result.WeChatConnectMode = normalizeWeChatConnectModeSetting(settings[SettingKeyWeChatConnectMode])
|
||||
result.WeChatConnectScopes = normalizeWeChatConnectScopeSetting(settings[SettingKeyWeChatConnectScopes], settings[SettingKeyWeChatConnectMode])
|
||||
result.WeChatConnectRedirectURL = strings.TrimSpace(settings[SettingKeyWeChatConnectRedirectURL])
|
||||
result.WeChatConnectFrontendRedirectURL = strings.TrimSpace(settings[SettingKeyWeChatConnectFrontendRedirectURL])
|
||||
if result.WeChatConnectFrontendRedirectURL == "" {
|
||||
result.WeChatConnectFrontendRedirectURL = defaultWeChatConnectFrontend
|
||||
}
|
||||
|
||||
// Model fallback settings
|
||||
result.EnableModelFallback = settings[SettingKeyEnableModelFallback] == "true"
|
||||
result.FallbackModelAnthropic = s.getStringOrDefault(settings, SettingKeyFallbackModelAnthropic, "claude-3-5-sonnet-20241022")
|
||||
@@ -1972,6 +2084,26 @@ func (s *SettingService) GetLinuxDoConnectOAuthConfig(ctx context.Context) (conf
|
||||
return effective, nil
|
||||
}
|
||||
|
||||
// GetWeChatConnectOAuthConfig 返回用于登录的最终生效 WeChat Connect 配置。
|
||||
//
|
||||
// WeChat Connect 已回归 DB 系统设置模型,不再回退到 config/env。
|
||||
func (s *SettingService) GetWeChatConnectOAuthConfig(ctx context.Context) (WeChatConnectOAuthConfig, error) {
|
||||
keys := []string{
|
||||
SettingKeyWeChatConnectEnabled,
|
||||
SettingKeyWeChatConnectAppID,
|
||||
SettingKeyWeChatConnectAppSecret,
|
||||
SettingKeyWeChatConnectMode,
|
||||
SettingKeyWeChatConnectScopes,
|
||||
SettingKeyWeChatConnectRedirectURL,
|
||||
SettingKeyWeChatConnectFrontendRedirectURL,
|
||||
}
|
||||
settings, err := s.settingRepo.GetMultiple(ctx, keys)
|
||||
if err != nil {
|
||||
return WeChatConnectOAuthConfig{}, fmt.Errorf("get wechat connect settings: %w", err)
|
||||
}
|
||||
return s.parseWeChatConnectOAuthConfig(settings)
|
||||
}
|
||||
|
||||
// GetOverloadCooldownSettings 获取529过载冷却配置
|
||||
func (s *SettingService) GetOverloadCooldownSettings(ctx context.Context) (*OverloadCooldownSettings, error) {
|
||||
value, err := s.settingRepo.GetValue(ctx, SettingKeyOverloadCooldownSettings)
|
||||
|
||||
@@ -92,16 +92,21 @@ func TestSettingService_GetPublicSettings_ExposesForceEmailOnThirdPartySignup(t
|
||||
}
|
||||
|
||||
func TestSettingService_GetPublicSettings_ExposesWeChatOAuthModeCapabilities(t *testing.T) {
|
||||
t.Setenv("WECHAT_OAUTH_OPEN_APP_ID", "wx-open-app")
|
||||
t.Setenv("WECHAT_OAUTH_OPEN_APP_SECRET", "wx-open-secret")
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_ID", "")
|
||||
t.Setenv("WECHAT_OAUTH_MP_APP_SECRET", "")
|
||||
|
||||
svc := NewSettingService(&settingPublicRepoStub{}, &config.Config{})
|
||||
svc := NewSettingService(&settingPublicRepoStub{
|
||||
values: map[string]string{
|
||||
SettingKeyWeChatConnectEnabled: "true",
|
||||
SettingKeyWeChatConnectAppID: "wx-mp-app",
|
||||
SettingKeyWeChatConnectAppSecret: "wx-mp-secret",
|
||||
SettingKeyWeChatConnectMode: "mp",
|
||||
SettingKeyWeChatConnectScopes: "snsapi_base",
|
||||
SettingKeyWeChatConnectRedirectURL: "https://api.example.com/api/v1/auth/oauth/wechat/callback",
|
||||
SettingKeyWeChatConnectFrontendRedirectURL: "/auth/wechat/callback",
|
||||
},
|
||||
}, &config.Config{})
|
||||
|
||||
settings, err := svc.GetPublicSettings(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.True(t, settings.WeChatOAuthEnabled)
|
||||
require.True(t, settings.WeChatOAuthOpenEnabled)
|
||||
require.False(t, settings.WeChatOAuthMPEnabled)
|
||||
require.False(t, settings.WeChatOAuthOpenEnabled)
|
||||
require.True(t, settings.WeChatOAuthMPEnabled)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type settingWeChatRepoStub struct {
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) Get(context.Context, string) (*Setting, error) {
|
||||
panic("unexpected Get call")
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) GetValue(_ context.Context, key string) (string, error) {
|
||||
if value, ok := s.values[key]; ok {
|
||||
return value, nil
|
||||
}
|
||||
return "", ErrSettingNotFound
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) Set(context.Context, string, string) error {
|
||||
panic("unexpected Set call")
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) GetMultiple(_ context.Context, keys []string) (map[string]string, error) {
|
||||
out := make(map[string]string, len(keys))
|
||||
for _, key := range keys {
|
||||
if value, ok := s.values[key]; ok {
|
||||
out[key] = value
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) SetMultiple(context.Context, map[string]string) error {
|
||||
panic("unexpected SetMultiple call")
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) GetAll(context.Context) (map[string]string, error) {
|
||||
panic("unexpected GetAll call")
|
||||
}
|
||||
|
||||
func (s *settingWeChatRepoStub) Delete(context.Context, string) error {
|
||||
panic("unexpected Delete call")
|
||||
}
|
||||
|
||||
func TestSettingService_GetWeChatConnectOAuthConfig_UsesDatabaseOverrides(t *testing.T) {
|
||||
repo := &settingWeChatRepoStub{
|
||||
values: map[string]string{
|
||||
SettingKeyWeChatConnectEnabled: "true",
|
||||
SettingKeyWeChatConnectAppID: "wx-db-app",
|
||||
SettingKeyWeChatConnectAppSecret: "wx-db-secret",
|
||||
SettingKeyWeChatConnectMode: "mp",
|
||||
SettingKeyWeChatConnectScopes: "snsapi_base",
|
||||
SettingKeyWeChatConnectRedirectURL: "https://api.example.com/api/v1/auth/oauth/wechat/callback",
|
||||
SettingKeyWeChatConnectFrontendRedirectURL: "/auth/wechat/callback",
|
||||
},
|
||||
}
|
||||
svc := NewSettingService(repo, &config.Config{})
|
||||
|
||||
got, err := svc.GetWeChatConnectOAuthConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.Enabled)
|
||||
require.Equal(t, "wx-db-app", got.AppID)
|
||||
require.Equal(t, "wx-db-secret", got.AppSecret)
|
||||
require.Equal(t, "mp", got.Mode)
|
||||
require.Equal(t, "snsapi_base", got.Scopes)
|
||||
require.Equal(t, "https://api.example.com/api/v1/auth/oauth/wechat/callback", got.RedirectURL)
|
||||
require.Equal(t, "/auth/wechat/callback", got.FrontendRedirectURL)
|
||||
}
|
||||
@@ -31,6 +31,16 @@ type SystemSettings struct {
|
||||
LinuxDoConnectClientSecretConfigured bool
|
||||
LinuxDoConnectRedirectURL string
|
||||
|
||||
// WeChat Connect OAuth 登录
|
||||
WeChatConnectEnabled bool
|
||||
WeChatConnectAppID string
|
||||
WeChatConnectAppSecret string
|
||||
WeChatConnectAppSecretConfigured bool
|
||||
WeChatConnectMode string
|
||||
WeChatConnectScopes string
|
||||
WeChatConnectRedirectURL string
|
||||
WeChatConnectFrontendRedirectURL string
|
||||
|
||||
// Generic OIDC OAuth 登录
|
||||
OIDCConnectEnabled bool
|
||||
OIDCConnectProviderName string
|
||||
@@ -177,6 +187,16 @@ type PublicSettings struct {
|
||||
BalanceLowNotifyRechargeURL string
|
||||
}
|
||||
|
||||
type WeChatConnectOAuthConfig struct {
|
||||
Enabled bool
|
||||
AppID string
|
||||
AppSecret string
|
||||
Mode string
|
||||
Scopes string
|
||||
RedirectURL string
|
||||
FrontendRedirectURL string
|
||||
}
|
||||
|
||||
// StreamTimeoutSettings 流超时处理配置(仅控制超时后的处理方式,超时判定由网关配置控制)
|
||||
type StreamTimeoutSettings struct {
|
||||
// Enabled 是否启用流超时处理
|
||||
|
||||
Reference in New Issue
Block a user