From d64eb48a592c732e74ec4ec670defa06bb28dd77 Mon Sep 17 00:00:00 2001 From: yokowu <18836617@qq.com> Date: Thu, 31 Jul 2025 14:36:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=89=8D=E7=BD=AE=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/cmd/server/wire_gen.go | 22 ++--- backend/config/config.go | 15 +++- backend/db/migrate/schema.go | 1 + backend/db/mutation.go | 75 ++++++++++++++++- backend/db/runtime/runtime.go | 4 +- backend/db/setting.go | 13 +++ backend/db/setting/setting.go | 8 ++ backend/db/setting/where.go | 80 +++++++++++++++++++ backend/db/setting_create.go | 78 ++++++++++++++++++ backend/db/setting_update.go | 52 ++++++++++++ backend/docs/swagger.json | 10 ++- backend/domain/user.go | 3 + backend/ent/schema/setting.go | 1 + backend/ent/types/types.go | 5 ++ backend/internal/openai/handler/v1/v1.go | 15 +++- backend/internal/user/handler/v1/user.go | 23 ++++-- backend/internal/user/repo/user.go | 18 ++++- backend/internal/user/usecase/user.go | 9 ++- ...3_add_base_url_for_settings_table.down.sql | 1 + ...013_add_base_url_for_settings_table.up.sql | 1 + ui/nginx.conf | 1 + 21 files changed, 409 insertions(+), 26 deletions(-) create mode 100644 backend/migration/000013_add_base_url_for_settings_table.down.sql create mode 100644 backend/migration/000013_add_base_url_for_settings_table.up.sql diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 35567b7..0cd6123 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -24,7 +24,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/internal/middleware" v1_2 "github.com/chaitin/MonkeyCode/backend/internal/model/handler/http/v1" repo2 "github.com/chaitin/MonkeyCode/backend/internal/model/repo" - usecase3 "github.com/chaitin/MonkeyCode/backend/internal/model/usecase" + usecase4 "github.com/chaitin/MonkeyCode/backend/internal/model/usecase" "github.com/chaitin/MonkeyCode/backend/internal/openai/handler/v1" repo3 "github.com/chaitin/MonkeyCode/backend/internal/openai/repo" "github.com/chaitin/MonkeyCode/backend/internal/openai/usecase" @@ -36,7 +36,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/internal/socket/handler" v1_3 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1" repo5 "github.com/chaitin/MonkeyCode/backend/internal/user/repo" - usecase4 "github.com/chaitin/MonkeyCode/backend/internal/user/usecase" + usecase3 "github.com/chaitin/MonkeyCode/backend/internal/user/usecase" repo8 "github.com/chaitin/MonkeyCode/backend/internal/workspace/repo" usecase7 "github.com/chaitin/MonkeyCode/backend/internal/workspace/usecase" "github.com/chaitin/MonkeyCode/backend/pkg" @@ -72,20 +72,20 @@ func newServer() (*Server, error) { openAIUsecase := openai.NewOpenAIUsecase(configConfig, openAIRepo, modelRepo, slogLogger) extensionRepo := repo4.NewExtensionRepo(client) extensionUsecase := usecase2.NewExtensionUsecase(extensionRepo, configConfig, slogLogger) - proxyMiddleware := middleware.NewProxyMiddleware(proxyUsecase) - activeMiddleware := middleware.NewActiveMiddleware(redisClient, slogLogger) - v1Handler := v1.NewV1Handler(slogLogger, web, llmProxy, proxyUsecase, openAIUsecase, extensionUsecase, proxyMiddleware, activeMiddleware, configConfig) - modelUsecase := usecase3.NewModelUsecase(slogLogger, modelRepo, configConfig) - sessionSession := session.NewSession(configConfig) - authMiddleware := middleware.NewAuthMiddleware(sessionSession, slogLogger) - readOnlyMiddleware := middleware.NewReadOnlyMiddleware(configConfig) - modelHandler := v1_2.NewModelHandler(web, modelUsecase, authMiddleware, activeMiddleware, readOnlyMiddleware, slogLogger) ipdbIPDB, err := ipdb.NewIPDB(slogLogger) if err != nil { return nil, err } userRepo := repo5.NewUserRepo(client, ipdbIPDB, redisClient, configConfig) - userUsecase := usecase4.NewUserUsecase(configConfig, redisClient, userRepo, slogLogger, sessionSession) + sessionSession := session.NewSession(configConfig) + userUsecase := usecase3.NewUserUsecase(configConfig, redisClient, userRepo, slogLogger, sessionSession) + proxyMiddleware := middleware.NewProxyMiddleware(proxyUsecase) + activeMiddleware := middleware.NewActiveMiddleware(redisClient, slogLogger) + v1Handler := v1.NewV1Handler(slogLogger, web, llmProxy, proxyUsecase, openAIUsecase, extensionUsecase, userUsecase, proxyMiddleware, activeMiddleware, configConfig) + modelUsecase := usecase4.NewModelUsecase(slogLogger, modelRepo, configConfig) + authMiddleware := middleware.NewAuthMiddleware(sessionSession, slogLogger) + readOnlyMiddleware := middleware.NewReadOnlyMiddleware(configConfig) + modelHandler := v1_2.NewModelHandler(web, modelUsecase, authMiddleware, activeMiddleware, readOnlyMiddleware, slogLogger) dashboardRepo := repo6.NewDashboardRepo(client) dashboardUsecase := usecase5.NewDashboardUsecase(dashboardRepo) billingRepo := repo7.NewBillingRepo(client) diff --git a/backend/config/config.go b/backend/config/config.go index 8fc1953..2171d76 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/viper" + "github.com/chaitin/MonkeyCode/backend/domain" "github.com/chaitin/MonkeyCode/backend/pkg/logger" ) @@ -77,7 +78,7 @@ type Config struct { } `mapstructure:"data_report"` } -func (c *Config) GetBaseURL(req *http.Request) string { +func (c *Config) GetBaseURL(req *http.Request, settings *domain.Setting) string { scheme := "http" if req.TLS != nil { scheme = "https" @@ -85,6 +86,18 @@ func (c *Config) GetBaseURL(req *http.Request) string { if proto := req.Header.Get("X-Forwarded-Proto"); proto != "" { scheme = proto } + + if settings != nil && settings.BaseURL != "" { + baseurl := settings.BaseURL + if !strings.HasPrefix(baseurl, "http") { + baseurl = fmt.Sprintf("%s://%s", scheme, baseurl) + } + return strings.TrimSuffix(baseurl, "/") + } + + if port := req.Header.Get("X-Forwarded-Port"); port != "" && port != "80" && port != "443" { + c.Server.Port = port + } baseurl := fmt.Sprintf("%s://%s", scheme, req.Host) if c.Server.Port != "" { baseurl = fmt.Sprintf("%s:%s", baseurl, c.Server.Port) diff --git a/backend/db/migrate/schema.go b/backend/db/migrate/schema.go index 2a967a5..37a314c 100644 --- a/backend/db/migrate/schema.go +++ b/backend/db/migrate/schema.go @@ -315,6 +315,7 @@ var ( {Name: "enable_auto_login", Type: field.TypeBool, Default: false}, {Name: "dingtalk_oauth", Type: field.TypeJSON, Nullable: true}, {Name: "custom_oauth", Type: field.TypeJSON, Nullable: true}, + {Name: "base_url", Type: field.TypeString, Nullable: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, } diff --git a/backend/db/mutation.go b/backend/db/mutation.go index 6dc87b4..d9fc9b5 100644 --- a/backend/db/mutation.go +++ b/backend/db/mutation.go @@ -10909,6 +10909,7 @@ type SettingMutation struct { enable_auto_login *bool dingtalk_oauth **types.DingtalkOAuth custom_oauth **types.CustomOAuth + base_url *string created_at *time.Time updated_at *time.Time clearedFields map[string]struct{} @@ -11263,6 +11264,55 @@ func (m *SettingMutation) ResetCustomOauth() { delete(m.clearedFields, setting.FieldCustomOauth) } +// SetBaseURL sets the "base_url" field. +func (m *SettingMutation) SetBaseURL(s string) { + m.base_url = &s +} + +// BaseURL returns the value of the "base_url" field in the mutation. +func (m *SettingMutation) BaseURL() (r string, exists bool) { + v := m.base_url + if v == nil { + return + } + return *v, true +} + +// OldBaseURL returns the old "base_url" field's value of the Setting entity. +// If the Setting object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SettingMutation) OldBaseURL(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldBaseURL is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldBaseURL requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldBaseURL: %w", err) + } + return oldValue.BaseURL, nil +} + +// ClearBaseURL clears the value of the "base_url" field. +func (m *SettingMutation) ClearBaseURL() { + m.base_url = nil + m.clearedFields[setting.FieldBaseURL] = struct{}{} +} + +// BaseURLCleared returns if the "base_url" field was cleared in this mutation. +func (m *SettingMutation) BaseURLCleared() bool { + _, ok := m.clearedFields[setting.FieldBaseURL] + return ok +} + +// ResetBaseURL resets all changes to the "base_url" field. +func (m *SettingMutation) ResetBaseURL() { + m.base_url = nil + delete(m.clearedFields, setting.FieldBaseURL) +} + // SetCreatedAt sets the "created_at" field. func (m *SettingMutation) SetCreatedAt(t time.Time) { m.created_at = &t @@ -11369,7 +11419,7 @@ func (m *SettingMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *SettingMutation) Fields() []string { - fields := make([]string, 0, 8) + fields := make([]string, 0, 9) if m.enable_sso != nil { fields = append(fields, setting.FieldEnableSSO) } @@ -11388,6 +11438,9 @@ func (m *SettingMutation) Fields() []string { if m.custom_oauth != nil { fields = append(fields, setting.FieldCustomOauth) } + if m.base_url != nil { + fields = append(fields, setting.FieldBaseURL) + } if m.created_at != nil { fields = append(fields, setting.FieldCreatedAt) } @@ -11414,6 +11467,8 @@ func (m *SettingMutation) Field(name string) (ent.Value, bool) { return m.DingtalkOauth() case setting.FieldCustomOauth: return m.CustomOauth() + case setting.FieldBaseURL: + return m.BaseURL() case setting.FieldCreatedAt: return m.CreatedAt() case setting.FieldUpdatedAt: @@ -11439,6 +11494,8 @@ func (m *SettingMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldDingtalkOauth(ctx) case setting.FieldCustomOauth: return m.OldCustomOauth(ctx) + case setting.FieldBaseURL: + return m.OldBaseURL(ctx) case setting.FieldCreatedAt: return m.OldCreatedAt(ctx) case setting.FieldUpdatedAt: @@ -11494,6 +11551,13 @@ func (m *SettingMutation) SetField(name string, value ent.Value) error { } m.SetCustomOauth(v) return nil + case setting.FieldBaseURL: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetBaseURL(v) + return nil case setting.FieldCreatedAt: v, ok := value.(time.Time) if !ok { @@ -11544,6 +11608,9 @@ func (m *SettingMutation) ClearedFields() []string { if m.FieldCleared(setting.FieldCustomOauth) { fields = append(fields, setting.FieldCustomOauth) } + if m.FieldCleared(setting.FieldBaseURL) { + fields = append(fields, setting.FieldBaseURL) + } return fields } @@ -11564,6 +11631,9 @@ func (m *SettingMutation) ClearField(name string) error { case setting.FieldCustomOauth: m.ClearCustomOauth() return nil + case setting.FieldBaseURL: + m.ClearBaseURL() + return nil } return fmt.Errorf("unknown Setting nullable field %s", name) } @@ -11590,6 +11660,9 @@ func (m *SettingMutation) ResetField(name string) error { case setting.FieldCustomOauth: m.ResetCustomOauth() return nil + case setting.FieldBaseURL: + m.ResetBaseURL() + return nil case setting.FieldCreatedAt: m.ResetCreatedAt() return nil diff --git a/backend/db/runtime/runtime.go b/backend/db/runtime/runtime.go index 0042397..d7525ce 100644 --- a/backend/db/runtime/runtime.go +++ b/backend/db/runtime/runtime.go @@ -243,11 +243,11 @@ func init() { // setting.DefaultEnableAutoLogin holds the default value on creation for the enable_auto_login field. setting.DefaultEnableAutoLogin = settingDescEnableAutoLogin.Default.(bool) // settingDescCreatedAt is the schema descriptor for created_at field. - settingDescCreatedAt := settingFields[7].Descriptor() + settingDescCreatedAt := settingFields[8].Descriptor() // setting.DefaultCreatedAt holds the default value on creation for the created_at field. setting.DefaultCreatedAt = settingDescCreatedAt.Default.(func() time.Time) // settingDescUpdatedAt is the schema descriptor for updated_at field. - settingDescUpdatedAt := settingFields[8].Descriptor() + settingDescUpdatedAt := settingFields[9].Descriptor() // setting.DefaultUpdatedAt holds the default value on creation for the updated_at field. setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time) // setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/backend/db/setting.go b/backend/db/setting.go index 20b0b63..840e541 100644 --- a/backend/db/setting.go +++ b/backend/db/setting.go @@ -32,6 +32,8 @@ type Setting struct { DingtalkOauth *types.DingtalkOAuth `json:"dingtalk_oauth,omitempty"` // CustomOauth holds the value of the "custom_oauth" field. CustomOauth *types.CustomOAuth `json:"custom_oauth,omitempty"` + // BaseURL holds the value of the "base_url" field. + BaseURL string `json:"base_url,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. @@ -48,6 +50,8 @@ func (*Setting) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case setting.FieldEnableSSO, setting.FieldForceTwoFactorAuth, setting.FieldDisablePasswordLogin, setting.FieldEnableAutoLogin: values[i] = new(sql.NullBool) + case setting.FieldBaseURL: + values[i] = new(sql.NullString) case setting.FieldCreatedAt, setting.FieldUpdatedAt: values[i] = new(sql.NullTime) case setting.FieldID: @@ -113,6 +117,12 @@ func (s *Setting) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field custom_oauth: %w", err) } } + case setting.FieldBaseURL: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field base_url", values[i]) + } else if value.Valid { + s.BaseURL = value.String + } case setting.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) @@ -179,6 +189,9 @@ func (s *Setting) String() string { builder.WriteString("custom_oauth=") builder.WriteString(fmt.Sprintf("%v", s.CustomOauth)) builder.WriteString(", ") + builder.WriteString("base_url=") + builder.WriteString(s.BaseURL) + builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(s.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") diff --git a/backend/db/setting/setting.go b/backend/db/setting/setting.go index 0b3b4c6..f08100d 100644 --- a/backend/db/setting/setting.go +++ b/backend/db/setting/setting.go @@ -25,6 +25,8 @@ const ( FieldDingtalkOauth = "dingtalk_oauth" // FieldCustomOauth holds the string denoting the custom_oauth field in the database. FieldCustomOauth = "custom_oauth" + // FieldBaseURL holds the string denoting the base_url field in the database. + FieldBaseURL = "base_url" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. @@ -42,6 +44,7 @@ var Columns = []string{ FieldEnableAutoLogin, FieldDingtalkOauth, FieldCustomOauth, + FieldBaseURL, FieldCreatedAt, FieldUpdatedAt, } @@ -101,6 +104,11 @@ func ByEnableAutoLogin(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEnableAutoLogin, opts...).ToFunc() } +// ByBaseURL orders the results by the base_url field. +func ByBaseURL(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldBaseURL, opts...).ToFunc() +} + // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() diff --git a/backend/db/setting/where.go b/backend/db/setting/where.go index 6651303..4953880 100644 --- a/backend/db/setting/where.go +++ b/backend/db/setting/where.go @@ -75,6 +75,11 @@ func EnableAutoLogin(v bool) predicate.Setting { return predicate.Setting(sql.FieldEQ(FieldEnableAutoLogin, v)) } +// BaseURL applies equality check predicate on the "base_url" field. It's identical to BaseURLEQ. +func BaseURL(v string) predicate.Setting { + return predicate.Setting(sql.FieldEQ(FieldBaseURL, v)) +} + // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.Setting { return predicate.Setting(sql.FieldEQ(FieldCreatedAt, v)) @@ -145,6 +150,81 @@ func CustomOauthNotNil() predicate.Setting { return predicate.Setting(sql.FieldNotNull(FieldCustomOauth)) } +// BaseURLEQ applies the EQ predicate on the "base_url" field. +func BaseURLEQ(v string) predicate.Setting { + return predicate.Setting(sql.FieldEQ(FieldBaseURL, v)) +} + +// BaseURLNEQ applies the NEQ predicate on the "base_url" field. +func BaseURLNEQ(v string) predicate.Setting { + return predicate.Setting(sql.FieldNEQ(FieldBaseURL, v)) +} + +// BaseURLIn applies the In predicate on the "base_url" field. +func BaseURLIn(vs ...string) predicate.Setting { + return predicate.Setting(sql.FieldIn(FieldBaseURL, vs...)) +} + +// BaseURLNotIn applies the NotIn predicate on the "base_url" field. +func BaseURLNotIn(vs ...string) predicate.Setting { + return predicate.Setting(sql.FieldNotIn(FieldBaseURL, vs...)) +} + +// BaseURLGT applies the GT predicate on the "base_url" field. +func BaseURLGT(v string) predicate.Setting { + return predicate.Setting(sql.FieldGT(FieldBaseURL, v)) +} + +// BaseURLGTE applies the GTE predicate on the "base_url" field. +func BaseURLGTE(v string) predicate.Setting { + return predicate.Setting(sql.FieldGTE(FieldBaseURL, v)) +} + +// BaseURLLT applies the LT predicate on the "base_url" field. +func BaseURLLT(v string) predicate.Setting { + return predicate.Setting(sql.FieldLT(FieldBaseURL, v)) +} + +// BaseURLLTE applies the LTE predicate on the "base_url" field. +func BaseURLLTE(v string) predicate.Setting { + return predicate.Setting(sql.FieldLTE(FieldBaseURL, v)) +} + +// BaseURLContains applies the Contains predicate on the "base_url" field. +func BaseURLContains(v string) predicate.Setting { + return predicate.Setting(sql.FieldContains(FieldBaseURL, v)) +} + +// BaseURLHasPrefix applies the HasPrefix predicate on the "base_url" field. +func BaseURLHasPrefix(v string) predicate.Setting { + return predicate.Setting(sql.FieldHasPrefix(FieldBaseURL, v)) +} + +// BaseURLHasSuffix applies the HasSuffix predicate on the "base_url" field. +func BaseURLHasSuffix(v string) predicate.Setting { + return predicate.Setting(sql.FieldHasSuffix(FieldBaseURL, v)) +} + +// BaseURLIsNil applies the IsNil predicate on the "base_url" field. +func BaseURLIsNil() predicate.Setting { + return predicate.Setting(sql.FieldIsNull(FieldBaseURL)) +} + +// BaseURLNotNil applies the NotNil predicate on the "base_url" field. +func BaseURLNotNil() predicate.Setting { + return predicate.Setting(sql.FieldNotNull(FieldBaseURL)) +} + +// BaseURLEqualFold applies the EqualFold predicate on the "base_url" field. +func BaseURLEqualFold(v string) predicate.Setting { + return predicate.Setting(sql.FieldEqualFold(FieldBaseURL, v)) +} + +// BaseURLContainsFold applies the ContainsFold predicate on the "base_url" field. +func BaseURLContainsFold(v string) predicate.Setting { + return predicate.Setting(sql.FieldContainsFold(FieldBaseURL, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.Setting { return predicate.Setting(sql.FieldEQ(FieldCreatedAt, v)) diff --git a/backend/db/setting_create.go b/backend/db/setting_create.go index 6fa6a35..2f85041 100644 --- a/backend/db/setting_create.go +++ b/backend/db/setting_create.go @@ -93,6 +93,20 @@ func (sc *SettingCreate) SetCustomOauth(to *types.CustomOAuth) *SettingCreate { return sc } +// SetBaseURL sets the "base_url" field. +func (sc *SettingCreate) SetBaseURL(s string) *SettingCreate { + sc.mutation.SetBaseURL(s) + return sc +} + +// SetNillableBaseURL sets the "base_url" field if the given value is not nil. +func (sc *SettingCreate) SetNillableBaseURL(s *string) *SettingCreate { + if s != nil { + sc.SetBaseURL(*s) + } + return sc +} + // SetCreatedAt sets the "created_at" field. func (sc *SettingCreate) SetCreatedAt(t time.Time) *SettingCreate { sc.mutation.SetCreatedAt(t) @@ -268,6 +282,10 @@ func (sc *SettingCreate) createSpec() (*Setting, *sqlgraph.CreateSpec) { _spec.SetField(setting.FieldCustomOauth, field.TypeJSON, value) _node.CustomOauth = value } + if value, ok := sc.mutation.BaseURL(); ok { + _spec.SetField(setting.FieldBaseURL, field.TypeString, value) + _node.BaseURL = value + } if value, ok := sc.mutation.CreatedAt(); ok { _spec.SetField(setting.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value @@ -412,6 +430,24 @@ func (u *SettingUpsert) ClearCustomOauth() *SettingUpsert { return u } +// SetBaseURL sets the "base_url" field. +func (u *SettingUpsert) SetBaseURL(v string) *SettingUpsert { + u.Set(setting.FieldBaseURL, v) + return u +} + +// UpdateBaseURL sets the "base_url" field to the value that was provided on create. +func (u *SettingUpsert) UpdateBaseURL() *SettingUpsert { + u.SetExcluded(setting.FieldBaseURL) + return u +} + +// ClearBaseURL clears the value of the "base_url" field. +func (u *SettingUpsert) ClearBaseURL() *SettingUpsert { + u.SetNull(setting.FieldBaseURL) + return u +} + // SetCreatedAt sets the "created_at" field. func (u *SettingUpsert) SetCreatedAt(v time.Time) *SettingUpsert { u.Set(setting.FieldCreatedAt, v) @@ -582,6 +618,27 @@ func (u *SettingUpsertOne) ClearCustomOauth() *SettingUpsertOne { }) } +// SetBaseURL sets the "base_url" field. +func (u *SettingUpsertOne) SetBaseURL(v string) *SettingUpsertOne { + return u.Update(func(s *SettingUpsert) { + s.SetBaseURL(v) + }) +} + +// UpdateBaseURL sets the "base_url" field to the value that was provided on create. +func (u *SettingUpsertOne) UpdateBaseURL() *SettingUpsertOne { + return u.Update(func(s *SettingUpsert) { + s.UpdateBaseURL() + }) +} + +// ClearBaseURL clears the value of the "base_url" field. +func (u *SettingUpsertOne) ClearBaseURL() *SettingUpsertOne { + return u.Update(func(s *SettingUpsert) { + s.ClearBaseURL() + }) +} + // SetCreatedAt sets the "created_at" field. func (u *SettingUpsertOne) SetCreatedAt(v time.Time) *SettingUpsertOne { return u.Update(func(s *SettingUpsert) { @@ -923,6 +980,27 @@ func (u *SettingUpsertBulk) ClearCustomOauth() *SettingUpsertBulk { }) } +// SetBaseURL sets the "base_url" field. +func (u *SettingUpsertBulk) SetBaseURL(v string) *SettingUpsertBulk { + return u.Update(func(s *SettingUpsert) { + s.SetBaseURL(v) + }) +} + +// UpdateBaseURL sets the "base_url" field to the value that was provided on create. +func (u *SettingUpsertBulk) UpdateBaseURL() *SettingUpsertBulk { + return u.Update(func(s *SettingUpsert) { + s.UpdateBaseURL() + }) +} + +// ClearBaseURL clears the value of the "base_url" field. +func (u *SettingUpsertBulk) ClearBaseURL() *SettingUpsertBulk { + return u.Update(func(s *SettingUpsert) { + s.ClearBaseURL() + }) +} + // SetCreatedAt sets the "created_at" field. func (u *SettingUpsertBulk) SetCreatedAt(v time.Time) *SettingUpsertBulk { return u.Update(func(s *SettingUpsert) { diff --git a/backend/db/setting_update.go b/backend/db/setting_update.go index 668cbf7..226a764 100644 --- a/backend/db/setting_update.go +++ b/backend/db/setting_update.go @@ -110,6 +110,26 @@ func (su *SettingUpdate) ClearCustomOauth() *SettingUpdate { return su } +// SetBaseURL sets the "base_url" field. +func (su *SettingUpdate) SetBaseURL(s string) *SettingUpdate { + su.mutation.SetBaseURL(s) + return su +} + +// SetNillableBaseURL sets the "base_url" field if the given value is not nil. +func (su *SettingUpdate) SetNillableBaseURL(s *string) *SettingUpdate { + if s != nil { + su.SetBaseURL(*s) + } + return su +} + +// ClearBaseURL clears the value of the "base_url" field. +func (su *SettingUpdate) ClearBaseURL() *SettingUpdate { + su.mutation.ClearBaseURL() + return su +} + // SetCreatedAt sets the "created_at" field. func (su *SettingUpdate) SetCreatedAt(t time.Time) *SettingUpdate { su.mutation.SetCreatedAt(t) @@ -210,6 +230,12 @@ func (su *SettingUpdate) sqlSave(ctx context.Context) (n int, err error) { if su.mutation.CustomOauthCleared() { _spec.ClearField(setting.FieldCustomOauth, field.TypeJSON) } + if value, ok := su.mutation.BaseURL(); ok { + _spec.SetField(setting.FieldBaseURL, field.TypeString, value) + } + if su.mutation.BaseURLCleared() { + _spec.ClearField(setting.FieldBaseURL, field.TypeString) + } if value, ok := su.mutation.CreatedAt(); ok { _spec.SetField(setting.FieldCreatedAt, field.TypeTime, value) } @@ -318,6 +344,26 @@ func (suo *SettingUpdateOne) ClearCustomOauth() *SettingUpdateOne { return suo } +// SetBaseURL sets the "base_url" field. +func (suo *SettingUpdateOne) SetBaseURL(s string) *SettingUpdateOne { + suo.mutation.SetBaseURL(s) + return suo +} + +// SetNillableBaseURL sets the "base_url" field if the given value is not nil. +func (suo *SettingUpdateOne) SetNillableBaseURL(s *string) *SettingUpdateOne { + if s != nil { + suo.SetBaseURL(*s) + } + return suo +} + +// ClearBaseURL clears the value of the "base_url" field. +func (suo *SettingUpdateOne) ClearBaseURL() *SettingUpdateOne { + suo.mutation.ClearBaseURL() + return suo +} + // SetCreatedAt sets the "created_at" field. func (suo *SettingUpdateOne) SetCreatedAt(t time.Time) *SettingUpdateOne { suo.mutation.SetCreatedAt(t) @@ -448,6 +494,12 @@ func (suo *SettingUpdateOne) sqlSave(ctx context.Context) (_node *Setting, err e if suo.mutation.CustomOauthCleared() { _spec.ClearField(setting.FieldCustomOauth, field.TypeJSON) } + if value, ok := suo.mutation.BaseURL(); ok { + _spec.SetField(setting.FieldBaseURL, field.TypeString, value) + } + if suo.mutation.BaseURLCleared() { + _spec.ClearField(setting.FieldBaseURL, field.TypeString) + } if value, ok := suo.mutation.CreatedAt(); ok { _spec.SetField(setting.FieldCreatedAt, field.TypeTime, value) } diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 7ee6972..593fac9 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -402,7 +402,7 @@ } }, "put": { - "description": "更新系统设置", + "description": "更新为增量更新,只传需要更新的字段", "consumes": [ "application/json" ], @@ -4948,6 +4948,10 @@ "domain.Setting": { "type": "object", "properties": { + "base_url": { + "description": "base url 配置,为了支持前置代理", + "type": "string" + }, "created_at": { "description": "创建时间", "type": "integer" @@ -5259,6 +5263,10 @@ "domain.UpdateSettingReq": { "type": "object", "properties": { + "base_url": { + "description": "base url 配置,为了支持前置代理", + "type": "string" + }, "custom_oauth": { "description": "自定义OAuth配置", "allOf": [ diff --git a/backend/domain/user.go b/backend/domain/user.go index d75b7aa..6f4e3c3 100644 --- a/backend/domain/user.go +++ b/backend/domain/user.go @@ -293,6 +293,7 @@ type UpdateSettingReq struct { EnableAutoLogin *bool `json:"enable_auto_login"` // 是否开启自动登录 DingtalkOAuth *DingtalkOAuthReq `json:"dingtalk_oauth"` // 钉钉OAuth配置 CustomOAuth *CustomOAuthReq `json:"custom_oauth"` // 自定义OAuth配置 + BaseURL *string `json:"base_url"` // base url 配置,为了支持前置代理 } type DingtalkOAuthReq struct { @@ -372,6 +373,7 @@ type Setting struct { EnableAutoLogin bool `json:"enable_auto_login"` // 是否开启自动登录 DingtalkOAuth DingtalkOAuth `json:"dingtalk_oauth"` // 钉钉OAuth接入 CustomOAuth CustomOAuth `json:"custom_oauth"` // 自定义OAuth接入 + BaseURL string `json:"base_url,omitempty"` // base url 配置,为了支持前置代理 CreatedAt int64 `json:"created_at"` // 创建时间 UpdatedAt int64 `json:"updated_at"` // 更新时间 } @@ -387,6 +389,7 @@ func (s *Setting) From(e *db.Setting) *Setting { s.EnableAutoLogin = e.EnableAutoLogin s.DingtalkOAuth = *cvt.From(e.DingtalkOauth, &DingtalkOAuth{}) s.CustomOAuth = *cvt.From(e.CustomOauth, &CustomOAuth{}) + s.BaseURL = e.BaseURL s.CreatedAt = e.CreatedAt.Unix() s.UpdatedAt = e.UpdatedAt.Unix() diff --git a/backend/ent/schema/setting.go b/backend/ent/schema/setting.go index f1d2534..8e33828 100644 --- a/backend/ent/schema/setting.go +++ b/backend/ent/schema/setting.go @@ -36,6 +36,7 @@ func (Setting) Fields() []ent.Field { field.Bool("enable_auto_login").Default(false), field.JSON("dingtalk_oauth", &types.DingtalkOAuth{}).Optional(), field.JSON("custom_oauth", &types.CustomOAuth{}).Optional(), + field.String("base_url").Optional(), field.Time("created_at").Default(time.Now), field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now), } diff --git a/backend/ent/types/types.go b/backend/ent/types/types.go index bc743ed..7c8f9d1 100644 --- a/backend/ent/types/types.go +++ b/backend/ent/types/types.go @@ -6,6 +6,11 @@ type DingtalkOAuth struct { ClientSecret string `json:"client_secret"` // 钉钉客户端密钥 } +type BaseURL struct { + Host string `json:"host"` + Port string `json:"port"` +} + type CustomOAuth struct { Enable bool `json:"enable"` // 自定义OAuth开关 ClientID string `json:"client_id"` // 自定义客户端ID diff --git a/backend/internal/openai/handler/v1/v1.go b/backend/internal/openai/handler/v1/v1.go index bf263a2..155c444 100644 --- a/backend/internal/openai/handler/v1/v1.go +++ b/backend/internal/openai/handler/v1/v1.go @@ -21,6 +21,7 @@ type V1Handler struct { proxyUse domain.ProxyUsecase usecase domain.OpenAIUsecase euse domain.ExtensionUsecase + uuse domain.UserUsecase config *config.Config } @@ -31,6 +32,7 @@ func NewV1Handler( proxyUse domain.ProxyUsecase, usecase domain.OpenAIUsecase, euse domain.ExtensionUsecase, + uuse domain.UserUsecase, middleware *middleware.ProxyMiddleware, active *middleware.ActiveMiddleware, config *config.Config, @@ -41,6 +43,7 @@ func NewV1Handler( proxyUse: proxyUse, usecase: usecase, euse: euse, + uuse: uuse, config: config, } @@ -73,9 +76,13 @@ func (h *V1Handler) Version(c *web.Context) error { return err } + s, err := h.uuse.GetSetting(c.Request().Context()) + if err != nil { + return err + } return c.JSON(http.StatusOK, domain.VersionInfo{ Version: v.Version, - URL: fmt.Sprintf("%s/api/v1/static/vsix/%s", h.config.GetBaseURL(c.Request()), v.Version), + URL: fmt.Sprintf("%s/api/v1/static/vsix/%s", h.config.GetBaseURL(c.Request(), s), v.Version), }) } @@ -182,8 +189,12 @@ func (h *V1Handler) Embeddings(c *web.Context) error { func (h *V1Handler) GetConfig(c *web.Context, req domain.ConfigReq) error { key := middleware.GetApiKey(c) + s, err := h.uuse.GetSetting(c.Request().Context()) + if err != nil { + return err + } req.Key = key.Key - req.BaseURL = h.config.GetBaseURL(c.Request()) + req.BaseURL = h.config.GetBaseURL(c.Request(), s) resp, err := h.usecase.GetConfig(c.Request().Context(), &req) if err != nil { return err diff --git a/backend/internal/user/handler/v1/user.go b/backend/internal/user/handler/v1/user.go index daa17b4..7c35c2a 100644 --- a/backend/internal/user/handler/v1/user.go +++ b/backend/internal/user/handler/v1/user.go @@ -127,7 +127,11 @@ func NewUserHandler( } func (h *UserHandler) VSCodeAuthInit(c *web.Context, req domain.VSCodeAuthInitReq) error { - req.BaseURL = h.cfg.GetBaseURL(c.Request()) + s, err := h.usecase.GetSetting(c.Request().Context()) + if err != nil { + return err + } + req.BaseURL = h.cfg.GetBaseURL(c.Request(), s) resp, err := h.usecase.VSCodeAuthInit(c.Request().Context(), &req) if err != nil { return err @@ -174,9 +178,14 @@ func (h *UserHandler) VSIXDownload(c *web.Context) error { return err } + s, err := h.usecase.GetSetting(c.Request().Context()) + if err != nil { + return err + } + host := c.Request().Host h.logger.With("url", c.Request().URL).With("header", c.Request().Header).With("host", host).DebugContext(c.Request().Context(), "vsix download") - cacheKey := h.generateCacheKey(v.Version, h.cfg.GetBaseURL(c.Request())) + cacheKey := h.generateCacheKey(v.Version, h.cfg.GetBaseURL(c.Request(), s)) h.cacheMu.RLock() if entry, exists := h.vsixCache[cacheKey]; exists { @@ -195,7 +204,7 @@ func (h *UserHandler) VSIXDownload(c *web.Context) error { h.cacheMu.RUnlock() var buf bytes.Buffer - if err := vsix.ChangeVsixEndpoint(v.Path, "extension/package.json", h.cfg.GetBaseURL(c.Request()), &buf); err != nil { + if err := vsix.ChangeVsixEndpoint(v.Path, "extension/package.json", h.cfg.GetBaseURL(c.Request(), s), &buf); err != nil { return err } @@ -534,7 +543,7 @@ func (h *UserHandler) GetSetting(c *web.Context) error { // // @Tags Admin // @Summary 更新系统设置 -// @Description 更新系统设置 +// @Description 更新为增量更新,只传需要更新的字段 // @ID update-setting // @Accept json // @Produce json @@ -562,7 +571,11 @@ func (h *UserHandler) UpdateSetting(c *web.Context, req domain.UpdateSettingReq) // @Router /api/v1/user/oauth/signup-or-in [get] func (h *UserHandler) OAuthSignUpOrIn(ctx *web.Context, req domain.OAuthSignUpOrInReq) error { h.logger.With("req", req).DebugContext(ctx.Request().Context(), "OAuthSignUpOrIn") - req.BaseURL = h.cfg.GetBaseURL(ctx.Request()) + s, err := h.usecase.GetSetting(ctx.Request().Context()) + if err != nil { + return err + } + req.BaseURL = h.cfg.GetBaseURL(ctx.Request(), s) resp, err := h.usecase.OAuthSignUpOrIn(ctx.Request().Context(), &req) if err != nil { return err diff --git a/backend/internal/user/repo/user.go b/backend/internal/user/repo/user.go index 2fd0c77..b6e9fa2 100644 --- a/backend/internal/user/repo/user.go +++ b/backend/internal/user/repo/user.go @@ -2,6 +2,7 @@ package repo import ( "context" + "encoding/json" "errors" "fmt" "time" @@ -225,9 +226,15 @@ func (r *UserRepo) GetUserByApiKey(ctx context.Context, apiKey string) (*db.User } func (r *UserRepo) GetSetting(ctx context.Context) (*db.Setting, error) { + if b, err := r.redis.Get(ctx, "setting").Result(); err == nil { + s := &db.Setting{} + if err := json.Unmarshal([]byte(b), s); err == nil { + return s, nil + } + } s, err := r.db.Setting.Query().First(ctx) if db.IsNotFound(err) { - return r.db.Setting.Create(). + s, err = r.db.Setting.Create(). SetEnableSSO(false). SetForceTwoFactorAuth(false). SetDisablePasswordLogin(false). @@ -236,6 +243,13 @@ func (r *UserRepo) GetSetting(ctx context.Context) (*db.Setting, error) { if err != nil { return nil, err } + b, err := json.Marshal(s) + if err != nil { + return nil, err + } + if err := r.redis.Set(ctx, "setting", b, time.Hour*24).Err(); err != nil { + return nil, err + } return s, nil } @@ -253,7 +267,7 @@ func (r *UserRepo) UpdateSetting(ctx context.Context, fn func(*db.Setting, *db.S return err } res = s - return nil + return r.redis.Del(ctx, "setting").Err() }) return res, err } diff --git a/backend/internal/user/usecase/user.go b/backend/internal/user/usecase/user.go index d112135..1ecf7b6 100644 --- a/backend/internal/user/usecase/user.go +++ b/backend/internal/user/usecase/user.go @@ -391,6 +391,9 @@ func (u *UserUsecase) UpdateSetting(ctx context.Context, req *domain.UpdateSetti } up.SetCustomOauth(custom) } + if req.BaseURL != nil { + up.SetBaseURL(*req.BaseURL) + } }) if err != nil { return nil, err @@ -518,7 +521,11 @@ func (u *UserUsecase) OAuthSignUpOrIn(ctx context.Context, req *domain.OAuthSign func (u *UserUsecase) OAuthCallback(c *web.Context, req *domain.OAuthCallbackReq) error { ctx := c.Request().Context() req.IP = c.RealIP() - req.BaseURL = u.cfg.GetBaseURL(c.Request()) + s, err := u.GetSetting(ctx) + if err != nil { + return err + } + req.BaseURL = u.cfg.GetBaseURL(c.Request(), s) b, err := u.redis.Get(ctx, fmt.Sprintf("oauth:state:%s", req.State)).Result() if err != nil { return err diff --git a/backend/migration/000013_add_base_url_for_settings_table.down.sql b/backend/migration/000013_add_base_url_for_settings_table.down.sql new file mode 100644 index 0000000..e1f84aa --- /dev/null +++ b/backend/migration/000013_add_base_url_for_settings_table.down.sql @@ -0,0 +1 @@ +ALTER TABLE settings DROP COLUMN IF EXISTS base_url; \ No newline at end of file diff --git a/backend/migration/000013_add_base_url_for_settings_table.up.sql b/backend/migration/000013_add_base_url_for_settings_table.up.sql new file mode 100644 index 0000000..e9ce404 --- /dev/null +++ b/backend/migration/000013_add_base_url_for_settings_table.up.sql @@ -0,0 +1 @@ +ALTER TABLE settings ADD COLUMN IF NOT EXISTS base_url TEXT; \ No newline at end of file diff --git a/ui/nginx.conf b/ui/nginx.conf index 43c1e29..0acb46d 100644 --- a/ui/nginx.conf +++ b/ui/nginx.conf @@ -32,6 +32,7 @@ http { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Port $http_x_forwarded_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;