From ee179d6b34442636d020d766aaa764f6990ae271 Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Tue, 25 Aug 2026 00:20:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/system/security.go | 8 -- internal/biz/system/user.go | 10 -- internal/config/secrets.go | 35 +++++ internal/data/config_helpers.go | 21 --- .../data/integration/integration_config.go | 5 +- internal/data/integration_config_test.go | 4 +- internal/global/resources.go | 129 ------------------ internal/global/resources_test.go | 62 ++++----- internal/initialize/configuration.go | 57 +++----- internal/integration/connectivity.go | 5 +- internal/server/handler/announcement.go | 8 +- internal/server/middleware/access.go | 9 +- internal/server/middleware/access_log.go | 26 ++-- internal/server/middleware/audit.go | 17 +-- internal/server/middleware/auth.go | 17 +-- internal/server/middleware/error_audit.go | 5 +- internal/server/middleware/http.go | 18 --- internal/server/middleware/rate_limit.go | 7 +- internal/server/middleware/rate_limit_test.go | 6 +- internal/server/middleware/recovery.go | 5 +- internal/server/middleware/redact.go | 41 ++++++ internal/server/router/aliases.go | 27 ---- internal/server/router/announcement.go | 3 +- internal/server/router/api.go | 3 +- internal/server/router/api_token.go | 9 +- internal/server/router/audit.go | 3 +- internal/server/router/authority.go | 3 +- internal/server/router/dictionary.go | 3 +- internal/server/router/email.go | 3 +- internal/server/router/export.go | 3 +- internal/server/router/integration_config.go | 18 ++- internal/server/router/media.go | 3 +- internal/server/router/menu.go | 3 +- internal/server/router/organization.go | 3 +- internal/server/router/parameter.go | 17 +-- internal/server/router/payment.go | 3 +- internal/server/router/permission.go | 3 +- internal/server/router/public.go | 11 +- internal/server/router/routes.go | 5 +- internal/server/router/system_config.go | 15 +- internal/server/router/task.go | 3 +- internal/server/router/user.go | 33 ++--- internal/server/router/version.go | 3 +- internal/service/service.go | 97 +------------ internal/service/system/announcement.go | 14 +- internal/service/system/audit.go | 6 - internal/service/system/email.go | 4 - internal/service/system/security_session.go | 4 - internal/service/system/user.go | 83 ++++------- 49 files changed, 289 insertions(+), 591 deletions(-) create mode 100644 internal/config/secrets.go delete mode 100644 internal/server/middleware/http.go create mode 100644 internal/server/middleware/redact.go delete mode 100644 internal/server/router/aliases.go diff --git a/internal/biz/system/security.go b/internal/biz/system/security.go index d63bfd9..09edbf5 100644 --- a/internal/biz/system/security.go +++ b/internal/biz/system/security.go @@ -158,14 +158,6 @@ func (uc *SecurityUsecase) ValidatePassword(value *SecurityConfig, password stri return nil } -func (uc *SecurityUsecase) ValidateCurrentPassword(ctx context.Context, password string) error { - config, err := uc.Current(ctx) - if err != nil { - return err - } - return uc.ValidatePassword(config, password) -} - func (uc *SecurityUsecase) LoginLocked(ctx context.Context, username string) (bool, error) { _, locked, err := uc.cache.Get(ctx, loginLockKey(username)) return locked, err diff --git a/internal/biz/system/user.go b/internal/biz/system/user.go index 2aa6915..b848231 100644 --- a/internal/biz/system/user.go +++ b/internal/biz/system/user.go @@ -69,16 +69,6 @@ func (uc *UserUsecase) Login(ctx context.Context, username, password string) (*U return u, nil } -func (uc *UserUsecase) User(ctx context.Context, id uint) (*User, error) { - user, err := uc.repo.FindUserByID(ctx, id) - if err != nil { - return nil, err - } - _ = uc.repo.FillDepartmentNamePaths(ctx, user) - uc.fallbackDefaultRouter(ctx, user) - return user, nil -} - // UserByUUID resolves the identity from the UUID embedded in the JWT rather // than from the mutable numeric ID used by administrative records. func (uc *UserUsecase) UserByUUID(ctx context.Context, uuid string) (*User, error) { diff --git a/internal/config/secrets.go b/internal/config/secrets.go new file mode 100644 index 0000000..51e70e7 --- /dev/null +++ b/internal/config/secrets.go @@ -0,0 +1,35 @@ +package config + +// MaskedSecret is the placeholder written in place of a stored secret whenever a +// configuration payload leaves the process. Every layer that masks or preserves +// configuration secrets shares this sentinel so a masked read round-trips into a +// write without silently overwriting the stored value. +const MaskedSecret = "******" + +// IsMaskedSecret reports whether an inbound value carries no new secret, either +// because it was omitted or because it is the mask handed out on read. +func IsMaskedSecret(value string) bool { return value == "" || value == MaskedSecret } + +// ObjectStores lists every object-store block of a storage configuration so +// callers iterate the providers instead of repeating the list. +func ObjectStores(value *Storage) []*ObjectStore { + if value == nil { + return nil + } + return []*ObjectStore{value.AliyunOSS, value.HuaweiOBS, value.TencentCOS, value.AWSS3, value.CloudflareR2, value.Minio} +} + +// MaskStorageSecrets replaces every populated storage secret with MaskedSecret. +func MaskStorageSecrets(value *Storage) { + if value == nil { + return + } + if value.Qiniu != nil && value.Qiniu.SecretKey != "" { + value.Qiniu.SecretKey = MaskedSecret + } + for _, item := range ObjectStores(value) { + if item != nil && item.SecretKey != "" { + item.SecretKey = MaskedSecret + } + } +} diff --git a/internal/data/config_helpers.go b/internal/data/config_helpers.go index 50a22dd..1317767 100644 --- a/internal/data/config_helpers.go +++ b/internal/data/config_helpers.go @@ -10,24 +10,3 @@ func cloneAdminConfig(value *config.Admin) *config.Admin { } return config.CloneAdmin(value) } - -func maskStorageSecrets(storage *config.Storage) { - if storage == nil { - return - } - if storage.Qiniu != nil && storage.Qiniu.SecretKey != "" { - storage.Qiniu.SecretKey = "******" - } - for _, item := range []*config.ObjectStore{ - storage.AliyunOSS, - storage.HuaweiOBS, - storage.TencentCOS, - storage.AWSS3, - storage.CloudflareR2, - storage.Minio, - } { - if item != nil && item.SecretKey != "" { - item.SecretKey = "******" - } - } -} diff --git a/internal/data/integration/integration_config.go b/internal/data/integration/integration_config.go index 7dea23a..ad0f7cc 100644 --- a/internal/data/integration/integration_config.go +++ b/internal/data/integration/integration_config.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "kra/internal/config" "kra/internal/integration/runtimeconfig" "gorm.io/gorm" @@ -158,7 +159,7 @@ func maskIntegrationSecrets(kind, provider string, values map[string]any) { for key, value := range values { if secretFields[key] || likelyIntegrationSecret(key) { if text, ok := value.(string); ok && text != "" { - values[key] = "******" + values[key] = config.MaskedSecret } continue } @@ -172,7 +173,7 @@ func mergeIntegrationSecrets(kind, provider string, values, old map[string]any) secretFields := integrationSecretFields(kind, provider) for key, value := range values { if secretFields[key] || likelyIntegrationSecret(key) { - if text, ok := value.(string); ok && text == "******" { + if text, ok := value.(string); ok && text == config.MaskedSecret { if prior, exists := old[key]; exists { values[key] = prior } diff --git a/internal/data/integration_config_test.go b/internal/data/integration_config_test.go index 1e740e6..7415b11 100644 --- a/internal/data/integration_config_test.go +++ b/internal/data/integration_config_test.go @@ -157,11 +157,11 @@ func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) { AliyunOSS: &config.ObjectStore{SecretKey: "configured-secret"}, Minio: &config.ObjectStore{}, } - maskStorageSecrets(storage) + config.MaskStorageSecrets(storage) if storage.Qiniu.SecretKey != "" || storage.Minio.SecretKey != "" { t.Fatalf("empty provider secrets were masked: %#v", storage) } - if storage.AliyunOSS.SecretKey != "******" { + if storage.AliyunOSS.SecretKey != config.MaskedSecret { t.Fatalf("configured secret was not masked: %q", storage.AliyunOSS.SecretKey) } } diff --git a/internal/global/resources.go b/internal/global/resources.go index 93973b0..297da8e 100644 --- a/internal/global/resources.go +++ b/internal/global/resources.go @@ -77,93 +77,6 @@ func (r *ResourceRegistry) Replace(resources Resources) { r.mu.Unlock() } -func (r *ResourceRegistry) Logger() *slog.Logger { return r.Snapshot().Logger } - -func (r *ResourceRegistry) SetLogger(logger *slog.Logger) { - r.update(func(resources *Resources) { resources.Logger = logger }) -} - -func (r *ResourceRegistry) DB() *gorm.DB { return r.Snapshot().DB } - -func (r *ResourceRegistry) SetDB(db *gorm.DB) { - r.update(func(resources *Resources) { resources.DB = db }) -} - -// NamedDB returns a configured secondary database by alias. An empty alias -// resolves to the primary database. -func (r *ResourceRegistry) NamedDB(name string) *gorm.DB { - if name == "" { - return r.DB() - } - return r.Snapshot().NamedDBs[name] -} - -// SetNamedDBs replaces the complete secondary-database alias set. -func (r *ResourceRegistry) SetNamedDBs(databases map[string]*gorm.DB) { - r.update(func(resources *Resources) { resources.NamedDBs = cloneDBMap(databases) }) -} - -func (r *ResourceRegistry) Redis() redis.UniversalClient { return r.Snapshot().Redis } - -func (r *ResourceRegistry) SetRedis(client redis.UniversalClient) { - r.update(func(resources *Resources) { resources.Redis = client }) -} - -// NamedRedis returns a configured secondary Redis client by alias. An empty -// alias resolves to the primary Redis client. -func (r *ResourceRegistry) NamedRedis(name string) redis.UniversalClient { - if name == "" { - return r.Redis() - } - return r.Snapshot().NamedRedis[name] -} - -// SetNamedRedis replaces the complete secondary-Redis alias set. -func (r *ResourceRegistry) SetNamedRedis(clients map[string]redis.UniversalClient) { - r.update(func(resources *Resources) { resources.NamedRedis = cloneRedisMap(clients) }) -} - -func (r *ResourceRegistry) Mongo() *mongo.Client { return r.Snapshot().Mongo } - -func (r *ResourceRegistry) SetMongo(client *mongo.Client) { - r.update(func(resources *Resources) { resources.Mongo = client }) -} - -func (r *ResourceRegistry) Storage() FileStorage { return r.Snapshot().Storage } - -func (r *ResourceRegistry) SetStorage(storage FileStorage) { - r.update(func(resources *Resources) { resources.Storage = storage }) -} - -func (r *ResourceRegistry) MQ() platformmq.Registry { return r.Snapshot().MQ } - -func (r *ResourceRegistry) SetMQ(registry platformmq.Registry) { - r.update(func(resources *Resources) { resources.MQ = registry }) -} - -func (r *ResourceRegistry) WebSocket() platformws.Hub { return r.Snapshot().WebSocket } - -func (r *ResourceRegistry) SetWebSocket(hub platformws.Hub) { - r.update(func(resources *Resources) { resources.WebSocket = hub }) -} - -func (r *ResourceRegistry) Scheduler() Scheduler { return r.Snapshot().Scheduler } - -func (r *ResourceRegistry) SetScheduler(scheduler Scheduler) { - r.update(func(resources *Resources) { resources.Scheduler = scheduler }) -} - -func (r *ResourceRegistry) update(change func(*Resources)) { - if r == nil || change == nil { - return - } - r.mu.Lock() - resources := cloneResources(r.resources) - change(&resources) - r.resources = normalizeResources(resources) - r.mu.Unlock() -} - func normalizeResources(resources Resources) Resources { if resources.Logger == nil { resources.Logger = slog.Default() @@ -248,45 +161,3 @@ func Install(resources Resources) *ResourceRegistry { func DefaultRegistry() *ResourceRegistry { return defaultResources } func ResourceSnapshot() Resources { return defaultResources.Snapshot() } - -func ReplaceResources(resources Resources) { defaultResources.Replace(resources) } - -func Logger() *slog.Logger { return defaultResources.Logger() } - -func SetLogger(logger *slog.Logger) { defaultResources.SetLogger(logger) } - -func DB() *gorm.DB { return defaultResources.DB() } - -func SetDB(db *gorm.DB) { defaultResources.SetDB(db) } - -func NamedDB(name string) *gorm.DB { return defaultResources.NamedDB(name) } - -func SetNamedDBs(databases map[string]*gorm.DB) { defaultResources.SetNamedDBs(databases) } - -func Redis() redis.UniversalClient { return defaultResources.Redis() } - -func SetRedis(client redis.UniversalClient) { defaultResources.SetRedis(client) } - -func NamedRedis(name string) redis.UniversalClient { return defaultResources.NamedRedis(name) } - -func SetNamedRedis(clients map[string]redis.UniversalClient) { defaultResources.SetNamedRedis(clients) } - -func Mongo() *mongo.Client { return defaultResources.Mongo() } - -func SetMongo(client *mongo.Client) { defaultResources.SetMongo(client) } - -func Storage() FileStorage { return defaultResources.Storage() } - -func SetStorage(storage FileStorage) { defaultResources.SetStorage(storage) } - -func MQ() platformmq.Registry { return defaultResources.MQ() } - -func SetMQ(registry platformmq.Registry) { defaultResources.SetMQ(registry) } - -func WebSocket() platformws.Hub { return defaultResources.WebSocket() } - -func SetWebSocket(hub platformws.Hub) { defaultResources.SetWebSocket(hub) } - -func TaskScheduler() Scheduler { return defaultResources.Scheduler() } - -func SetTaskScheduler(scheduler Scheduler) { defaultResources.SetScheduler(scheduler) } diff --git a/internal/global/resources_test.go b/internal/global/resources_test.go index d55b486..c38958b 100644 --- a/internal/global/resources_test.go +++ b/internal/global/resources_test.go @@ -55,16 +55,16 @@ func (*webSocketStub) OnDisconnect(func(*platformws.Session)) {} func TestResourceRegistryDefaults(t *testing.T) { registry := NewResourceRegistry() - if registry.Logger() == nil { + resources := registry.Snapshot() + if resources.Logger == nil { t.Fatal("default logger is nil") } - resources := registry.Snapshot() if resources.DB != nil || len(resources.NamedDBs) != 0 || resources.Redis != nil || len(resources.NamedRedis) != 0 || resources.Mongo != nil || resources.Storage != nil || resources.MQ != nil || resources.WebSocket != nil || resources.Scheduler != nil { t.Fatalf("unexpected initialized resource: %#v", resources) } } -func TestResourceRegistryReplaceAndSetters(t *testing.T) { +func TestResourceRegistryReplaceAndSnapshot(t *testing.T) { logger := slog.Default().With("test", true) db := &gorm.DB{} redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"}) @@ -76,41 +76,33 @@ func TestResourceRegistryReplaceAndSetters(t *testing.T) { scheduler := &schedulerStub{} registry := NewResourceRegistry() + inputDBs := map[string]*gorm.DB{"reporting": db} + inputRedis := map[string]redis.UniversalClient{"cache": redisClient} registry.Replace(Resources{ - Logger: logger, DB: db, NamedDBs: map[string]*gorm.DB{"reporting": db}, - Redis: redisClient, NamedRedis: map[string]redis.UniversalClient{"cache": redisClient}, Mongo: mongoClient, + Logger: logger, DB: db, NamedDBs: inputDBs, + Redis: redisClient, NamedRedis: inputRedis, Mongo: mongoClient, Storage: storage, MQ: mq, WebSocket: webSocket, Scheduler: scheduler, }) resources := registry.Snapshot() if resources.Logger != logger || resources.DB != db || resources.Redis != redisClient || resources.Mongo != mongoClient || resources.Storage != storage || resources.MQ != mq || resources.WebSocket != webSocket || resources.Scheduler != scheduler { t.Fatalf("replacement snapshot does not match: %#v", resources) } + if resources.NamedDBs["reporting"] != db || resources.NamedRedis["cache"] != redisClient { + t.Fatal("named resource lookup returned the wrong resource") + } - nextDB := &gorm.DB{} - registry.SetDB(nextDB) - if registry.DB() != nextDB { - t.Fatal("database setter did not publish the new database") - } - if registry.Redis() != redisClient || registry.Storage() != storage { - t.Fatal("database setter changed unrelated resources") - } - if registry.NamedDB("reporting") != db || registry.NamedDB("") != nextDB { - t.Fatal("named database lookup returned the wrong resource") - } - if registry.NamedRedis("cache") != redisClient || registry.NamedRedis("") != redisClient { - t.Fatal("named redis lookup returned the wrong resource") - } - inputDBs := map[string]*gorm.DB{"analytics": db} - registry.SetNamedDBs(inputDBs) - inputDBs["mutated"] = nextDB - if registry.NamedDB("mutated") != nil || registry.NamedDB("analytics") != db { - t.Fatal("named database map was not copied") - } - inputRedis := map[string]redis.UniversalClient{"sessions": redisClient} - registry.SetNamedRedis(inputRedis) + inputDBs["mutated"] = &gorm.DB{} inputRedis["mutated"] = redisClient - if registry.NamedRedis("mutated") != nil || registry.NamedRedis("sessions") != redisClient { - t.Fatal("named redis map was not copied") + if _, exists := registry.Snapshot().NamedDBs["mutated"]; exists { + t.Fatal("named database map was not copied on replacement") + } + if _, exists := registry.Snapshot().NamedRedis["mutated"]; exists { + t.Fatal("named redis map was not copied on replacement") + } + + resources.NamedDBs["mutated"] = db + if _, exists := registry.Snapshot().NamedDBs["mutated"]; exists { + t.Fatal("snapshot shares its named database map with the registry") } } @@ -140,14 +132,16 @@ func TestResourceRegistryConcurrentAccess(t *testing.T) { go func() { defer wait.Done() for step := 0; step < iterations; step++ { - registry.SetDB(&gorm.DB{}) - _ = registry.Snapshot() - _ = registry.Logger() + registry.Replace(Resources{DB: &gorm.DB{}}) + if registry.Snapshot().Logger == nil { + t.Error("concurrent snapshot lost the default logger") + return + } } }() } wait.Wait() - if registry.DB() == nil { - t.Fatal("concurrent setters lost the database") + if registry.Snapshot().DB == nil { + t.Fatal("concurrent replacement lost the database") } } diff --git a/internal/initialize/configuration.go b/internal/initialize/configuration.go index be4e53e..a1d09f9 100644 --- a/internal/initialize/configuration.go +++ b/internal/initialize/configuration.go @@ -322,12 +322,12 @@ func maskConfigSecrets(value *config.Config) { return } if value.Admin.JWT != nil && value.Admin.JWT.SigningKey != "" { - value.Admin.JWT.SigningKey = "******" + value.Admin.JWT.SigningKey = config.MaskedSecret } if value.Admin.Email != nil && value.Admin.Email.Secret != "" { - value.Admin.Email.Secret = "******" + value.Admin.Email.Secret = config.MaskedSecret } - maskStorageSecrets(value.Admin.Storage) + config.MaskStorageSecrets(value.Admin.Storage) } func maskDataSecrets(value *config.Data) { @@ -335,24 +335,24 @@ func maskDataSecrets(value *config.Data) { return } if value.Database != nil { - value.Database.Password = "******" + value.Database.Password = config.MaskedSecret value.Database.Source = "" } if value.Redis != nil { - value.Redis.Password = "******" + value.Redis.Password = config.MaskedSecret } if value.Mongo != nil { - value.Mongo.Password = "******" + value.Mongo.Password = config.MaskedSecret } for _, item := range value.DatabaseList { if item != nil { - item.Password = "******" + item.Password = config.MaskedSecret item.Source = "" } } for _, item := range value.RedisList { if item != nil { - item.Password = "******" + item.Password = config.MaskedSecret } } } @@ -365,10 +365,10 @@ func preserveConfigSecrets(next, current *config.Config) { if next.Admin == nil || current.Admin == nil { return } - if next.Admin.JWT != nil && current.Admin.JWT != nil && maskedSecret(next.Admin.JWT.SigningKey) { + if next.Admin.JWT != nil && current.Admin.JWT != nil && config.IsMaskedSecret(next.Admin.JWT.SigningKey) { next.Admin.JWT.SigningKey = current.Admin.JWT.SigningKey } - if next.Admin.Email != nil && current.Admin.Email != nil && maskedSecret(next.Admin.Email.Secret) { + if next.Admin.Email != nil && current.Admin.Email != nil && config.IsMaskedSecret(next.Admin.Email.Secret) { next.Admin.Email.Secret = current.Admin.Email.Secret } preserveStorageSecrets(next.Admin.Storage, current.Admin.Storage) @@ -379,17 +379,17 @@ func preserveDataSecrets(next, current *config.Data) { return } if next.Database != nil && current.Database != nil { - if maskedSecret(next.Database.Password) { + if config.IsMaskedSecret(next.Database.Password) { next.Database.Password = current.Database.Password } if next.Database.Source == "" { next.Database.Source = current.Database.Source } } - if next.Redis != nil && current.Redis != nil && maskedSecret(next.Redis.Password) { + if next.Redis != nil && current.Redis != nil && config.IsMaskedSecret(next.Redis.Password) { next.Redis.Password = current.Redis.Password } - if next.Mongo != nil && current.Mongo != nil && maskedSecret(next.Mongo.Password) { + if next.Mongo != nil && current.Mongo != nil && config.IsMaskedSecret(next.Mongo.Password) { next.Mongo.Password = current.Mongo.Password } preserveDatabaseListSecrets(next.DatabaseList, current.DatabaseList) @@ -414,7 +414,7 @@ func preserveDatabaseListSecrets(next, current []*config.Database) { if previous == nil { continue } - if maskedSecret(item.Password) { + if config.IsMaskedSecret(item.Password) { item.Password = previous.Password } if item.Source == "" { @@ -438,43 +438,22 @@ func preserveRedisListSecrets(next, current []*config.Redis) { if previous == nil && index < len(current) { previous = current[index] } - if previous != nil && maskedSecret(item.Password) { + if previous != nil && config.IsMaskedSecret(item.Password) { item.Password = previous.Password } } } -func maskedSecret(value string) bool { return value == "" || value == "******" } - -func objectStores(value *config.Storage) []*config.ObjectStore { - if value == nil { - return nil - } - return []*config.ObjectStore{value.AliyunOSS, value.HuaweiOBS, value.TencentCOS, value.AWSS3, value.CloudflareR2, value.Minio} -} -func maskStorageSecrets(value *config.Storage) { - if value == nil { - return - } - if value.Qiniu != nil && value.Qiniu.SecretKey != "" { - value.Qiniu.SecretKey = "******" - } - for _, item := range objectStores(value) { - if item != nil && item.SecretKey != "" { - item.SecretKey = "******" - } - } -} func preserveStorageSecrets(next, current *config.Storage) { if next == nil || current == nil { return } - if next.Qiniu != nil && current.Qiniu != nil && maskedSecret(next.Qiniu.SecretKey) { + if next.Qiniu != nil && current.Qiniu != nil && config.IsMaskedSecret(next.Qiniu.SecretKey) { next.Qiniu.SecretKey = current.Qiniu.SecretKey } - nextItems, currentItems := objectStores(next), objectStores(current) + nextItems, currentItems := config.ObjectStores(next), config.ObjectStores(current) for index := range nextItems { - if nextItems[index] != nil && currentItems[index] != nil && maskedSecret(nextItems[index].SecretKey) { + if nextItems[index] != nil && currentItems[index] != nil && config.IsMaskedSecret(nextItems[index].SecretKey) { nextItems[index].SecretKey = currentItems[index].SecretKey } } diff --git a/internal/integration/connectivity.go b/internal/integration/connectivity.go index ef7caaa..c639e8c 100644 --- a/internal/integration/connectivity.go +++ b/internal/integration/connectivity.go @@ -8,6 +8,7 @@ import ( integrationbiz "kra/internal/biz/integration" "strings" + "kra/internal/config" "kra/internal/integration/mq" "kra/internal/integration/runtimeconfig" websocketintegration "kra/internal/integration/websocket" @@ -73,11 +74,11 @@ func (t *ConnectivityTester) restoreMaskedSecrets(kind, provider string, values } for key := range masked { value, _ := values[key].(string) - if strings.TrimSpace(value) != "******" { + if strings.TrimSpace(value) != config.MaskedSecret { continue } prior, _ := currentValues[key].(string) - if strings.TrimSpace(prior) == "" || strings.TrimSpace(prior) == "******" { + if config.IsMaskedSecret(strings.TrimSpace(prior)) { return fmt.Errorf("配置字段 %s 已脱敏,请重新填写后再测试", key) } values[key] = prior diff --git a/internal/server/handler/announcement.go b/internal/server/handler/announcement.go index 4118208..0eb0b24 100644 --- a/internal/server/handler/announcement.go +++ b/internal/server/handler/announcement.go @@ -14,17 +14,13 @@ func NewAnnouncement(service *service.AnnouncementService) *Announcement { return &Announcement{service: service} } -func announcementInput(req dto.AnnouncementRequest) service.AnnouncementInput { - return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments} -} - func (h *Announcement) Create(c *gin.Context) { var req dto.AnnouncementRequest if err := c.ShouldBindJSON(&req); err != nil { Fail(c, err.Error()) return } - if err := h.service.Create(c.Request.Context(), announcementInput(req)); err != nil { + if err := h.service.Create(c.Request.Context(), &req); err != nil { Fail(c, "创建失败") return } @@ -53,7 +49,7 @@ func (h *Announcement) Update(c *gin.Context) { Fail(c, err.Error()) return } - if err := h.service.Update(c.Request.Context(), announcementInput(req)); err != nil { + if err := h.service.Update(c.Request.Context(), &req); err != nil { Fail(c, "更新失败") return } diff --git a/internal/server/middleware/access.go b/internal/server/middleware/access.go index ad96342..7183699 100644 --- a/internal/server/middleware/access.go +++ b/internal/server/middleware/access.go @@ -3,6 +3,7 @@ package middleware import ( "context" "kra/internal/biz/system" + httpx "kra/pkg/httpx" "kra/internal/config" "kra/internal/utils/routepath" @@ -19,11 +20,11 @@ func AccessControl(runtime *config.Store, access accessController) gin.HandlerFu return func(c *gin.Context) { claims := Claims(c) if claims == nil { - NoAuth(c, "未登录或非法访问") + httpx.NoAuth(c, "未登录或非法访问") return } if access == nil { - Write(c, CodeError, gin.H{}, "权限服务不可用") + httpx.Write(c, httpx.CodeError, gin.H{}, "权限服务不可用") c.Abort() return } @@ -36,13 +37,13 @@ func AccessControl(runtime *config.Store, access accessController) gin.HandlerFu } allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, policyPath, c.Request.Method) if err != nil || !allowed { - Write(c, CodeError, gin.H{}, "权限不足") + httpx.Write(c, httpx.CodeError, gin.H{}, "权限不足") c.Abort() return } requestContext, err := access.ContextWithDataScope(c.Request.Context(), claims.AuthorityID, claims.ID) if err != nil { - Write(c, CodeError, gin.H{}, "数据权限解析失败") + httpx.Write(c, httpx.CodeError, gin.H{}, "数据权限解析失败") c.Abort() return } diff --git a/internal/server/middleware/access_log.go b/internal/server/middleware/access_log.go index 05b4432..62a5769 100644 --- a/internal/server/middleware/access_log.go +++ b/internal/server/middleware/access_log.go @@ -7,6 +7,7 @@ import ( "errors" "io" "kra/internal/biz/system" + httpx "kra/pkg/httpx" "log/slog" "mime" "net/http" @@ -51,7 +52,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H c.Header("X-Kra-Version", version) requestReadFailed := c.Request.ContentLength > bodyLimit if requestReadFailed { - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": CodeError, "msg": "请求体超过大小上限"}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": httpx.CodeError, "msg": "请求体超过大小上限"}) } else if c.Request.Body != nil && !mediaUpload { limited := http.MaxBytesReader(c.Writer, c.Request.Body, bodyLimit) var err error @@ -60,9 +61,9 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H requestReadFailed = true var tooLarge *http.MaxBytesError if errors.As(err, &tooLarge) { - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": CodeError, "msg": "请求体超过大小上限"}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": httpx.CodeError, "msg": "请求体超过大小上限"}) } else { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": CodeError, "msg": "请求体读取失败"}) + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": httpx.CodeError, "msg": "请求体读取失败"}) } } else { c.Request.Body = io.NopCloser(bytes.NewReader(requestBody)) @@ -204,9 +205,8 @@ func paymentConfigSummary(body []byte) string { func redactHeaders(headers map[string][]string) map[string]string { out := make(map[string]string, len(headers)) for key, values := range headers { - lower := strings.ToLower(key) - if lower == "authorization" || lower == "cookie" || lower == "set-cookie" || lower == "x-token" { - out[key] = "***" + if isSensitiveHeader(key) { + out[key] = redactedValue } else { out[key] = strings.Join(values, ",") } @@ -220,23 +220,13 @@ func redactQuery(raw string) string { } values, _ := url.ParseQuery(raw) for key := range values { - if sensitiveQueryKey(key) { - values[key] = []string{"***"} + if isSensitivePayloadKey(key) { + values[key] = []string{redactedValue} } } return values.Encode() } -func sensitiveQueryKey(key string) bool { - normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(key, "_", ""), "-", "")) - switch normalized { - case "token", "accesstoken", "refreshtoken", "authorization", "apikey", "secret": - return true - default: - return strings.HasSuffix(normalized, "token") - } -} - func stringValueFromContext(c *gin.Context, key string) string { value, _ := c.Get(key) return stringValue(value) diff --git a/internal/server/middleware/audit.go b/internal/server/middleware/audit.go index fd30291..275df8d 100644 --- a/internal/server/middleware/audit.go +++ b/internal/server/middleware/audit.go @@ -135,25 +135,12 @@ func operationRequestBody(raw []byte, contentType string, limit int) string { return text } -// operationSecretKeys holds the normalized JSON keys whose values never reach -// the operation record. Keys are compared after lowercasing and stripping -// separators, so "new_password" and "newPassword" both match "newpassword". -var operationSecretKeys = map[string]struct{}{ - "password": {}, "newpassword": {}, "oldpassword": {}, "confirmpassword": {}, - "passwd": {}, "pwd": {}, "token": {}, "accesstoken": {}, "refreshtoken": {}, - "secret": {}, "clientsecret": {}, "apikey": {}, "privatekey": {}, "idcard": {}, - "appkey": {}, "mchkey": {}, "apiv3key": {}, "clientcert": {}, "clientkey": {}, - "platformcert": {}, "platformserialno": {}, "credentialcode": {}, "certfile": {}, - "keyfile": {}, "publickey": {}, "rootcert": {}, "appcert": {}, "webhookid": {}, -} - func maskOperationBody(value any) { switch current := value.(type) { case map[string]any: for key, item := range current { - normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(key, "_", ""), "-", "")) - if _, secret := operationSecretKeys[normalized]; secret { - current[key] = "***" + if isSensitivePayloadKey(key) { + current[key] = redactedValue continue } maskOperationBody(item) diff --git a/internal/server/middleware/auth.go b/internal/server/middleware/auth.go index 1cc57c3..57814e9 100644 --- a/internal/server/middleware/auth.go +++ b/internal/server/middleware/auth.go @@ -4,6 +4,7 @@ import ( "context" "errors" "kra/internal/biz/system" + httpx "kra/pkg/httpx" "net/http" "strconv" "strings" @@ -43,11 +44,11 @@ func AuthenticateWebSocket(c *gin.Context, auth TokenAuthenticator) bool { func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool) bool { token := RequestToken(c, allowQueryToken) if token == "" { - NoAuth(c, "未登录或非法访问,请登录") + httpx.NoAuth(c, "未登录或非法访问,请登录") return false } if auth == nil { - NoAuth(c, "认证服务不可用") + httpx.NoAuth(c, "认证服务不可用") return false } // Followers of a singleflight flight must not inherit the leader's request @@ -61,20 +62,20 @@ func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool) return auth.AuthenticateToken(ctx, token) }) if err != nil { - SetTokenCookie(c, "", -1) - NoAuth(c, tokenErrorMessage(err)) + httpx.SetTokenCookie(c, "", -1) + httpx.NoAuth(c, tokenErrorMessage(err)) return false } authentication, ok := value.(*system.TokenAuthentication) if !ok || authentication == nil || authentication.Claims == nil { - SetTokenCookie(c, "", -1) - NoAuth(c, "无法处理此token") + httpx.SetTokenCookie(c, "", -1) + httpx.NoAuth(c, "无法处理此token") return false } if authentication.Refreshed != nil { c.Header("new-token", authentication.Refreshed.Value) c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10)) - SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds())) + httpx.SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds())) } c.Set(claimsKey, authentication.Claims) return true @@ -140,6 +141,6 @@ func MustChangePassword() gin.HandlerFunc { c.Next() return } - c.AbortWithStatusJSON(http.StatusConflict, Response{Code: CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"}) + c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"}) } } diff --git a/internal/server/middleware/error_audit.go b/internal/server/middleware/error_audit.go index b299063..2ff43ee 100644 --- a/internal/server/middleware/error_audit.go +++ b/internal/server/middleware/error_audit.go @@ -3,6 +3,7 @@ package middleware import ( "bytes" "encoding/json" + httpx "kra/pkg/httpx" "log/slog" "strings" @@ -24,7 +25,7 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc { if auditPersistFailed != true && (strings.Contains(c.Request.URL.Path, "/sysError/") || strings.Contains(c.Request.URL.Path, "/logViewer/") || c.Writer.Status() >= 300 && c.Writer.Status() < 500) { return } - var response Response + var response httpx.Response var body []byte if value, ok := c.Get(ctxRespBufferKey); ok { if buffer, valid := value.(*bytes.Buffer); valid { @@ -34,7 +35,7 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc { if json.Unmarshal(body, &response) != nil && privateErrors == "" { return } - if response.Code == CodeSuccess && privateErrors == "" { + if response.Code == httpx.CodeSuccess && privateErrors == "" { return } if privateErrors == "" && expectedClientFailure(response.Msg) { diff --git a/internal/server/middleware/http.go b/internal/server/middleware/http.go deleted file mode 100644 index 24d59a0..0000000 --- a/internal/server/middleware/http.go +++ /dev/null @@ -1,18 +0,0 @@ -package middleware - -import ( - "github.com/gin-gonic/gin" - httpx "kra/pkg/httpx" -) - -const ( - CodeSuccess = httpx.CodeSuccess - CodeError = httpx.CodeError - CodePasswordChangeRequired = httpx.CodePasswordChangeRequired -) - -type Response = httpx.Response - -func Write(c *gin.Context, code int, data any, message string) { httpx.Write(c, code, data, message) } -func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) } -func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) } diff --git a/internal/server/middleware/rate_limit.go b/internal/server/middleware/rate_limit.go index 6069572..e0015bd 100644 --- a/internal/server/middleware/rate_limit.go +++ b/internal/server/middleware/rate_limit.go @@ -2,6 +2,7 @@ package middleware import ( "kra/internal/service" + httpx "kra/pkg/httpx" "net/http" "strings" "time" @@ -22,7 +23,7 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc { } config, err := settings.CurrentSecurity(c.Request.Context()) if err != nil || config == nil { - c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"}) + c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": httpx.CodeError, "msg": "安全服务暂不可用"}) return } if !config.LimitEnable { @@ -40,11 +41,11 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc { key := "KRA_SecLimit:" + c.ClientIP() + ":" + route count, cacheErr := settings.IncrementRateLimit(c.Request.Context(), key, time.Duration(window)*time.Second) if cacheErr != nil { - c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"}) + c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": httpx.CodeError, "msg": "安全服务暂不可用"}) return } if int(count) > config.LimitCount { - c.JSON(200, gin.H{"code": CodeError, "msg": "请求太过频繁,请稍后再试"}) + c.JSON(200, gin.H{"code": httpx.CodeError, "msg": "请求太过频繁,请稍后再试"}) c.Abort() return } diff --git a/internal/server/middleware/rate_limit_test.go b/internal/server/middleware/rate_limit_test.go index 451cdfb..6baa2d4 100644 --- a/internal/server/middleware/rate_limit_test.go +++ b/internal/server/middleware/rate_limit_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "kra/internal/service" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" ) @@ -38,7 +38,7 @@ func (c rateLimitCache) Increment(context.Context, string, time.Duration) (int64 func TestSecurityRateLimitMatchesResponseContract(t *testing.T) { gin.SetMode(gin.TestMode) - settings := service.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{}, nil, nil)) + settings := systemservice.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{}, nil, nil)) engine := gin.New() engine.Use(SecurityRateLimit(settings)) engine.POST("/base/login", func(c *gin.Context) { c.Status(http.StatusNoContent) }) @@ -56,7 +56,7 @@ func TestSecurityRateLimitMatchesResponseContract(t *testing.T) { func TestSecurityRateLimitFailsClosedWhenCacheIsUnavailable(t *testing.T) { gin.SetMode(gin.TestMode) - settings := service.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{err: errors.New("cache unavailable")}, nil, nil)) + settings := systemservice.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{err: errors.New("cache unavailable")}, nil, nil)) engine := gin.New() called := false engine.Use(SecurityRateLimit(settings)) diff --git a/internal/server/middleware/recovery.go b/internal/server/middleware/recovery.go index 7444205..ec3ea49 100644 --- a/internal/server/middleware/recovery.go +++ b/internal/server/middleware/recovery.go @@ -64,9 +64,8 @@ func recoveryRequestDump(request *http.Request) string { clone := request.Clone(request.Context()) clone.Header = request.Header.Clone() for key := range clone.Header { - switch strings.ToLower(key) { - case "authorization", "cookie", "set-cookie", "x-token", "proxy-authorization": - clone.Header[key] = []string{"***"} + if isSensitiveHeader(key) { + clone.Header[key] = []string{redactedValue} } } if request.URL != nil { diff --git a/internal/server/middleware/redact.go b/internal/server/middleware/redact.go new file mode 100644 index 0000000..6dd56fc --- /dev/null +++ b/internal/server/middleware/redact.go @@ -0,0 +1,41 @@ +package middleware + +import "strings" + +// redactedValue replaces a sensitive value in every log line and audit record. +const redactedValue = "***" + +// sensitiveHeaders lists the headers whose value never reaches a log, compared +// after lowercasing. +var sensitiveHeaders = map[string]struct{}{ + "authorization": {}, "proxy-authorization": {}, "cookie": {}, "set-cookie": {}, "x-token": {}, +} + +// sensitivePayloadKeys holds the normalized query and JSON body keys whose +// values never reach a log or an operation record. Keys are compared after +// lowercasing and stripping separators, so "new_password" and "newPassword" +// both match "newpassword". +var sensitivePayloadKeys = map[string]struct{}{ + "password": {}, "newpassword": {}, "oldpassword": {}, "confirmpassword": {}, + "passwd": {}, "pwd": {}, "token": {}, "accesstoken": {}, "refreshtoken": {}, + "secret": {}, "clientsecret": {}, "apikey": {}, "privatekey": {}, "idcard": {}, + "appkey": {}, "mchkey": {}, "apiv3key": {}, "clientcert": {}, "clientkey": {}, + "platformcert": {}, "platformserialno": {}, "credentialcode": {}, "certfile": {}, + "keyfile": {}, "publickey": {}, "rootcert": {}, "appcert": {}, "webhookid": {}, + "authorization": {}, +} + +func isSensitiveHeader(key string) bool { + _, sensitive := sensitiveHeaders[strings.ToLower(key)] + return sensitive +} + +// isSensitivePayloadKey also matches every key ending in "token" so provider +// specific token names stay masked without being enumerated. +func isSensitivePayloadKey(key string) bool { + normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(key, "_", ""), "-", "")) + if _, sensitive := sensitivePayloadKeys[normalized]; sensitive { + return true + } + return strings.HasSuffix(normalized, "token") +} diff --git a/internal/server/router/aliases.go b/internal/server/router/aliases.go deleted file mode 100644 index 5e3370f..0000000 --- a/internal/server/router/aliases.go +++ /dev/null @@ -1,27 +0,0 @@ -package router - -import "kra/internal/server/handler" - -type Set = handler.Set -type Announcement = handler.Announcement -type API = handler.API -type APIToken = handler.APIToken -type Audit = handler.Audit -type Authority = handler.Authority -type Dictionary = handler.Dictionary -type Email = handler.Email -type Export = handler.Export -type Media = handler.Media -type Menu = handler.Menu -type Navigation = handler.Navigation -type Organization = handler.Organization -type Parameter = handler.Parameter -type Payment = handler.Payment -type Permission = handler.Permission -type Public = handler.Public -type Session = handler.Session -type SystemConfig = handler.SystemConfig -type Task = handler.Task -type User = handler.User -type Version = handler.Version -type IntegrationConfig = handler.IntegrationConfig diff --git a/internal/server/router/announcement.go b/internal/server/router/announcement.go index aeb3369..db2652c 100644 --- a/internal/server/router/announcement.go +++ b/internal/server/router/announcement.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterAnnouncement(private, public *gin.RouterGroup, h *Announcement) { +func RegisterAnnouncement(private, public *gin.RouterGroup, h *handler.Announcement) { privateInfo := private.Group("/info") privateInfo.POST("/createInfo", h.Create) privateInfo.DELETE("/deleteInfo", h.Delete) diff --git a/internal/server/router/api.go b/internal/server/router/api.go index a306e2a..47dec6e 100644 --- a/internal/server/router/api.go +++ b/internal/server/router/api.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *API) { +func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *handler.API) { api := group.Group("/api") api.POST("/getApiList", h.List) api.POST("/getAllApis", h.All) diff --git a/internal/server/router/api_token.go b/internal/server/router/api_token.go index 8e2296c..b8289a1 100644 --- a/internal/server/router/api_token.go +++ b/internal/server/router/api_token.go @@ -2,11 +2,12 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterAPIToken(group *gin.RouterGroup, handler *APIToken) { +func RegisterAPIToken(group *gin.RouterGroup, h *handler.APIToken) { router := group.Group("/sysApiToken") - router.POST("/createApiToken", handler.Create) - router.POST("/getApiTokenList", handler.List) - router.POST("/deleteApiToken", handler.Delete) + router.POST("/createApiToken", h.Create) + router.POST("/getApiTokenList", h.List) + router.POST("/deleteApiToken", h.Delete) } diff --git a/internal/server/router/audit.go b/internal/server/router/audit.go index c36a3c0..ccd9636 100644 --- a/internal/server/router/audit.go +++ b/internal/server/router/audit.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterAudit(group, public *gin.RouterGroup, h *Audit) { +func RegisterAudit(group, public *gin.RouterGroup, h *handler.Audit) { operations := group.Group("/sysOperationRecord") operations.GET("/getSysOperationRecordList", h.Operations) operations.GET("/findSysOperationRecord", h.Operation) diff --git a/internal/server/router/authority.go b/internal/server/router/authority.go index abbfd66..92768d3 100644 --- a/internal/server/router/authority.go +++ b/internal/server/router/authority.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterAuthority(group *gin.RouterGroup, h *Authority) { +func RegisterAuthority(group *gin.RouterGroup, h *handler.Authority) { router := group.Group("/authority") router.POST("/getAuthorityList", h.List) router.POST("/createAuthority", h.Create) diff --git a/internal/server/router/dictionary.go b/internal/server/router/dictionary.go index 0f6fe84..2223b74 100644 --- a/internal/server/router/dictionary.go +++ b/internal/server/router/dictionary.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterDictionary(group *gin.RouterGroup, h *Dictionary) { +func RegisterDictionary(group *gin.RouterGroup, h *handler.Dictionary) { dictionaries := group.Group("/sysDictionary") dictionaries.POST("/createSysDictionary", h.Create) dictionaries.PUT("/updateSysDictionary", h.Update) diff --git a/internal/server/router/email.go b/internal/server/router/email.go index 24f916b..cb82e7b 100644 --- a/internal/server/router/email.go +++ b/internal/server/router/email.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterEmail(group *gin.RouterGroup, h *Email) { +func RegisterEmail(group *gin.RouterGroup, h *handler.Email) { email := group.Group("/email") email.POST("/emailTest", h.Test) email.POST("/sendEmail", h.Send) diff --git a/internal/server/router/export.go b/internal/server/router/export.go index 4930e2c..d785f4b 100644 --- a/internal/server/router/export.go +++ b/internal/server/router/export.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterExport(group, public *gin.RouterGroup, h *Export) { +func RegisterExport(group, public *gin.RouterGroup, h *handler.Export) { router := group.Group("/sysExportTemplate") router.POST("/createSysExportTemplate", h.Create) router.PUT("/updateSysExportTemplate", h.Update) diff --git a/internal/server/router/integration_config.go b/internal/server/router/integration_config.go index dd225c0..052f138 100644 --- a/internal/server/router/integration_config.go +++ b/internal/server/router/integration_config.go @@ -1,12 +1,16 @@ package router -import "github.com/gin-gonic/gin" +import ( + "kra/internal/server/handler" -func RegisterIntegrationConfig(group *gin.RouterGroup, handler *IntegrationConfig) { + "github.com/gin-gonic/gin" +) + +func RegisterIntegrationConfig(group *gin.RouterGroup, h *handler.IntegrationConfig) { configs := group.Group("/integration/configs") - configs.GET("/:kind", handler.List) - configs.GET("/:kind/:provider", handler.Find) - configs.PUT("/:kind/:provider", handler.Save) - configs.POST("/:kind/:provider/test", handler.Test) - configs.DELETE("/:kind/:provider", handler.Delete) + configs.GET("/:kind", h.List) + configs.GET("/:kind/:provider", h.Find) + configs.PUT("/:kind/:provider", h.Save) + configs.POST("/:kind/:provider/test", h.Test) + configs.DELETE("/:kind/:provider", h.Delete) } diff --git a/internal/server/router/media.go b/internal/server/router/media.go index e2f9692..2bea7c8 100644 --- a/internal/server/router/media.go +++ b/internal/server/router/media.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterMedia(group *gin.RouterGroup, h *Media) { +func RegisterMedia(group *gin.RouterGroup, h *handler.Media) { files := group.Group("/fileUploadAndDownload") files.POST("/upload", h.Upload) files.POST("/getFileList", h.List) diff --git a/internal/server/router/menu.go b/internal/server/router/menu.go index 087695e..19ced85 100644 --- a/internal/server/router/menu.go +++ b/internal/server/router/menu.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterMenu(group *gin.RouterGroup, h *Menu) { +func RegisterMenu(group *gin.RouterGroup, h *handler.Menu) { router := group.Group("/menu") router.POST("/getMenuList", h.List) router.POST("/getBaseMenuTree", h.Tree) diff --git a/internal/server/router/organization.go b/internal/server/router/organization.go index bec04aa..d0263e6 100644 --- a/internal/server/router/organization.go +++ b/internal/server/router/organization.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterOrganization(group *gin.RouterGroup, h *Organization) { +func RegisterOrganization(group *gin.RouterGroup, h *handler.Organization) { departments := group.Group("/department") departments.POST("/getDepartmentList", h.ListDepartments) departments.POST("/createDepartment", h.CreateDepartment) diff --git a/internal/server/router/parameter.go b/internal/server/router/parameter.go index c06a9d9..12ed539 100644 --- a/internal/server/router/parameter.go +++ b/internal/server/router/parameter.go @@ -2,15 +2,16 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterParameter(group *gin.RouterGroup, handler *Parameter) { +func RegisterParameter(group *gin.RouterGroup, h *handler.Parameter) { router := group.Group("/sysParams") - router.POST("/createSysParams", handler.Create) - router.PUT("/updateSysParams", handler.Update) - router.DELETE("/deleteSysParams", handler.Delete) - router.DELETE("/deleteSysParamsByIds", handler.DeleteMany) - router.GET("/findSysParams", handler.Find) - router.GET("/getSysParam", handler.Get) - router.GET("/getSysParamsList", handler.List) + router.POST("/createSysParams", h.Create) + router.PUT("/updateSysParams", h.Update) + router.DELETE("/deleteSysParams", h.Delete) + router.DELETE("/deleteSysParamsByIds", h.DeleteMany) + router.GET("/findSysParams", h.Find) + router.GET("/getSysParam", h.Get) + router.GET("/getSysParamsList", h.List) } diff --git a/internal/server/router/payment.go b/internal/server/router/payment.go index 8e40e65..f4ca82a 100644 --- a/internal/server/router/payment.go +++ b/internal/server/router/payment.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterPayment(group, public *gin.RouterGroup, h *Payment) { +func RegisterPayment(group, public *gin.RouterGroup, h *handler.Payment) { payment := group.Group("/payment") payment.GET("/orders", h.Orders) payment.POST("/providers/:provider/test", h.TestProvider) diff --git a/internal/server/router/permission.go b/internal/server/router/permission.go index 2de4bfc..3411ec2 100644 --- a/internal/server/router/permission.go +++ b/internal/server/router/permission.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterPermission(group *gin.RouterGroup, h *Permission) { +func RegisterPermission(group *gin.RouterGroup, h *handler.Permission) { buttons := group.Group("/authorityBtn") buttons.POST("/getAuthorityBtn", h.Buttons) buttons.POST("/setAuthorityBtn", h.SetButtons) diff --git a/internal/server/router/public.go b/internal/server/router/public.go index de6d36d..1e6e370 100644 --- a/internal/server/router/public.go +++ b/internal/server/router/public.go @@ -2,13 +2,14 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, handler *Public) { +func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, h *handler.Public) { base := group.Group("/base") - base.POST("/captcha", handler.Captcha) - base.POST("/login", handler.Login) + base.POST("/captcha", h.Captcha) + base.POST("/login", h.Login) init := group.Group("/init") - init.POST("/checkdb", handler.CheckDatabase) - init.POST("/initdb", handler.InitializeDatabase(engine)) + init.POST("/checkdb", h.CheckDatabase) + init.POST("/initdb", h.InitializeDatabase(engine)) } diff --git a/internal/server/router/routes.go b/internal/server/router/routes.go index 774a14e..ffa87f0 100644 --- a/internal/server/router/routes.go +++ b/internal/server/router/routes.go @@ -1,16 +1,17 @@ package router import ( + "kra/internal/server/handler" platformmodule "kra/pkg/module" "github.com/gin-gonic/gin" ) type Routes struct { - handlers *Set + handlers *handler.Set } -func NewRoutes(handlers *Set) *Routes { return &Routes{handlers: handlers} } +func NewRoutes(handlers *handler.Set) *Routes { return &Routes{handlers: handlers} } var _ platformmodule.RouteRegistrar = (*Routes)(nil) diff --git a/internal/server/router/system_config.go b/internal/server/router/system_config.go index 2237426..263577d 100644 --- a/internal/server/router/system_config.go +++ b/internal/server/router/system_config.go @@ -2,16 +2,17 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterSystemConfig(group *gin.RouterGroup, handler *SystemConfig) { +func RegisterSystemConfig(group *gin.RouterGroup, h *handler.SystemConfig) { security := group.Group("/securityConfig") - security.GET("/getSecurityConfig", handler.GetSecurity) - security.POST("/setSecurityConfig", handler.SetSecurity) + security.GET("/getSecurityConfig", h.GetSecurity) + security.POST("/setSecurityConfig", h.SetSecurity) system := group.Group("/system") - system.POST("/getSystemConfig", handler.Get) - system.POST("/setSystemConfig", handler.Set) - system.POST("/reloadSystem", handler.Reload) - system.POST("/getServerInfo", handler.ServerInfo) + system.POST("/getSystemConfig", h.Get) + system.POST("/setSystemConfig", h.Set) + system.POST("/reloadSystem", h.Reload) + system.POST("/getServerInfo", h.ServerInfo) } diff --git a/internal/server/router/task.go b/internal/server/router/task.go index 0427a49..99319c0 100644 --- a/internal/server/router/task.go +++ b/internal/server/router/task.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterTask(group *gin.RouterGroup, h *Task) { +func RegisterTask(group *gin.RouterGroup, h *handler.Task) { router := group.Group("/timedTask") router.POST("/createTimedTask", h.Create) router.PUT("/updateTimedTask", h.Update) diff --git a/internal/server/router/user.go b/internal/server/router/user.go index bd063e3..c31ca8f 100644 --- a/internal/server/router/user.go +++ b/internal/server/router/user.go @@ -2,27 +2,28 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterUser(group *gin.RouterGroup, handler *User) { +func RegisterUser(group *gin.RouterGroup, h *handler.User) { user := group.Group("/user") - user.POST("/getUserList", handler.List) - user.POST("/admin_register", handler.Create) - user.PUT("/setUserInfo", handler.Update) - user.PUT("/setSelfInfo", handler.UpdateSelf) - user.DELETE("/deleteUser", handler.Delete) - user.POST("/resetPassword", handler.ResetPassword) - user.POST("/changePassword", handler.ChangePassword) - user.PUT("/setSelfSetting", handler.SetSelfSetting) - user.POST("/setUserAuthorities", handler.SetAuthorities) - user.POST("/setUserAuthority", handler.SwitchAuthority) - user.GET("/getUserInfo", handler.Get) + user.POST("/getUserList", h.List) + user.POST("/admin_register", h.Create) + user.PUT("/setUserInfo", h.Update) + user.PUT("/setSelfInfo", h.UpdateSelf) + user.DELETE("/deleteUser", h.Delete) + user.POST("/resetPassword", h.ResetPassword) + user.POST("/changePassword", h.ChangePassword) + user.PUT("/setSelfSetting", h.SetSelfSetting) + user.POST("/setUserAuthorities", h.SetAuthorities) + user.POST("/setUserAuthority", h.SwitchAuthority) + user.GET("/getUserInfo", h.Get) } -func RegisterNavigation(group *gin.RouterGroup, handler *Navigation) { - group.Group("/menu").POST("/getMenu", handler.Menu) +func RegisterNavigation(group *gin.RouterGroup, h *handler.Navigation) { + group.Group("/menu").POST("/getMenu", h.Menu) } -func RegisterSession(group *gin.RouterGroup, handler *Session) { - group.Group("/jwt").POST("/jsonInBlacklist", handler.Logout) +func RegisterSession(group *gin.RouterGroup, h *handler.Session) { + group.Group("/jwt").POST("/jsonInBlacklist", h.Logout) } diff --git a/internal/server/router/version.go b/internal/server/router/version.go index 0e83228..e920f2b 100644 --- a/internal/server/router/version.go +++ b/internal/server/router/version.go @@ -2,9 +2,10 @@ package router import ( "github.com/gin-gonic/gin" + "kra/internal/server/handler" ) -func RegisterVersion(group *gin.RouterGroup, h *Version) { +func RegisterVersion(group *gin.RouterGroup, h *handler.Version) { router := group.Group("/sysVersion") router.DELETE("/deleteSysVersion", h.Delete) router.DELETE("/deleteSysVersionByIds", h.DeleteMany) diff --git a/internal/service/service.go b/internal/service/service.go index 4dac3d2..3be96fb 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,27 +1,21 @@ package service -// This package is a compatibility facade for the transport layer. Concrete -// implementations live under service/system, service/payment, -// service/integration, and service/task; aliases here preserve the original -// service.* API used by handlers and the composition root. +// This package is the transport layer's entry point into the service tier. +// Concrete implementations live under service/system, service/payment, +// service/integration, and service/task; the aliases here give handlers one +// import instead of four, and Wire composes the module provider sets directly. import ( - "kra/internal/biz/integration" - paymentbiz "kra/internal/biz/payment" - "kra/internal/biz/system" - taskbiz "kra/internal/biz/task" "kra/internal/service/dto" integrationservice "kra/internal/service/integration" paymentservice "kra/internal/service/payment" systemservice "kra/internal/service/system" taskservice "kra/internal/service/task" - "kra/internal/utils/routepath" "github.com/google/wire" ) type AccessControlService = systemservice.AccessControlService -type AnnouncementInput = systemservice.AnnouncementInput type AnnouncementService = systemservice.AnnouncementService type APIService = systemservice.APIService type AuditService = systemservice.AuditService @@ -32,7 +26,6 @@ type DepartmentService = systemservice.DepartmentService type DictionaryService = systemservice.DictionaryService type EmailService = systemservice.EmailService type ExportService = systemservice.ExportService -type ExportToken = systemservice.ExportToken type LogViewerService = systemservice.LogViewerService type MediaService = systemservice.MediaService type MenuService = systemservice.MenuService @@ -40,7 +33,6 @@ type ParameterService = systemservice.ParameterService type PermissionService = systemservice.PermissionService type PositionService = systemservice.PositionService type SecurityService = systemservice.SecurityService -type PasswordPolicyError = systemservice.PasswordPolicyError type SystemConfigService = systemservice.SystemConfigService type TokenService = systemservice.TokenService type VersionService = systemservice.VersionService @@ -49,90 +41,9 @@ type IntegrationConfigService = integrationservice.IntegrationConfigService type TaskService = taskservice.TaskService type UserService = systemservice.UserService -func NewAccessControlService(uc *system.AccessControlUsecase) *AccessControlService { - return systemservice.NewAccessControlService(uc) -} -func NewAnnouncementService(uc *system.AnnouncementUsecase) *AnnouncementService { - return systemservice.NewAnnouncementService(uc) -} -func NewAPIService(uc *system.APIUsecase, settings system.RuntimeSettings) *APIService { - return systemservice.NewAPIService(uc, settings) -} -func NewAuditService(uc *system.AuditUsecase) *AuditService { - return systemservice.NewAuditService(uc) -} -func NewAuditRecorder(uc *system.AuditRecorderUsecase) *AuditRecorder { - return systemservice.NewAuditRecorder(uc) -} -func NewAuthService(uc *system.AuthenticationUsecase) *AuthService { - return systemservice.NewAuthService(uc) -} -func NewAuthorityService(uc *system.AuthorityUsecase) *AuthorityService { - return systemservice.NewAuthorityService(uc) -} -func NewDepartmentService(uc *system.DepartmentUsecase) *DepartmentService { - return systemservice.NewDepartmentService(uc) -} -func NewDictionaryService(uc *system.DictionaryUsecase) *DictionaryService { - return systemservice.NewDictionaryService(uc) -} -func NewEmailService(uc *system.EmailUsecase) *EmailService { - return systemservice.NewEmailService(uc) -} -func NewExportService(uc *system.ExportUsecase, cache system.Cache) *ExportService { - return systemservice.NewExportService(uc, cache) -} -func NewIntegrationConfigService(uc *integration.IntegrationConfigUsecase) *IntegrationConfigService { - return integrationservice.NewIntegrationConfigService(uc) -} -func NewLogViewerService(uc *system.LogViewerUsecase) *LogViewerService { - return systemservice.NewLogViewerService(uc) -} -func NewMediaService(uc *system.MediaUsecase, settings system.RuntimeSettings) *MediaService { - return systemservice.NewMediaService(uc, settings) -} -func NewMenuService(uc *system.MenuUsecase) *MenuService { - return systemservice.NewMenuService(uc) -} -func NewParameterService(uc *system.ParameterUsecase) *ParameterService { - return systemservice.NewParameterService(uc) -} -func NewPaymentService(uc *paymentbiz.PaymentUsecase) *PaymentService { - return paymentservice.NewPaymentService(uc) -} -func NewPermissionService(uc *system.PermissionUsecase) *PermissionService { - return systemservice.NewPermissionService(uc) -} -func NewPositionService(uc *system.PositionUsecase) *PositionService { - return systemservice.NewPositionService(uc) -} -func NewSecurityService(uc *system.SecurityUsecase) *SecurityService { - return systemservice.NewSecurityService(uc) -} -func NewSystemConfigService(uc *system.SystemConfigUsecase, settings system.RuntimeSettings) *SystemConfigService { - return systemservice.NewSystemConfigService(uc, settings) -} -func NewTaskService(uc *taskbiz.TaskApplicationUsecase) *TaskService { - return taskservice.NewTaskService(uc) -} -func NewTokenService(uc *system.TokenUsecase, issuer system.TokenIssuer) *TokenService { - return systemservice.NewTokenService(uc, issuer) -} -func NewUserService(uc *system.UserUsecase, settings *SecurityService) *UserService { - return systemservice.NewUserService(uc, settings) -} -func NewVersionService(uc *system.VersionUsecase) *VersionService { - return systemservice.NewVersionService(uc) -} - -var ErrExportTokenInvalid = systemservice.ErrExportTokenInvalid var ErrExportTokenMalformed = systemservice.ErrExportTokenMalformed var ErrExportTokenType = systemservice.ErrExportTokenType -func NormalizeRoutePath(path, routerPrefix string) string { - return routepath.Normalize(path, routerPrefix) -} - func IsPasswordPolicyError(err error) bool { return systemservice.IsPasswordPolicyError(err) } func DefaultPaymentCallbackAck(provider string, success bool) dto.PaymentCallbackAck { diff --git a/internal/service/system/announcement.go b/internal/service/system/announcement.go index 3a4025e..7fc3a79 100644 --- a/internal/service/system/announcement.go +++ b/internal/service/system/announcement.go @@ -9,14 +9,6 @@ import ( "kra/internal/service/dto" ) -type AnnouncementInput struct { - ID uint - Title string - Content string - UserID *int - Attachments json.RawMessage -} - type AnnouncementService struct{ uc *system.AnnouncementUsecase } func NewAnnouncementService(uc *system.AnnouncementUsecase) *AnnouncementService { @@ -27,15 +19,15 @@ func announcementDTO(item *system.Announcement) *dto.AnnouncementResponse { return &dto.AnnouncementResponse{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, DeletedAt: nil, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} } -func announcementDO(in AnnouncementInput) *system.Announcement { +func announcementDO(in *dto.AnnouncementRequest) *system.Announcement { return &system.Announcement{ID: in.ID, Title: in.Title, Content: in.Content, UserID: in.UserID, Attachments: in.Attachments} } -func (s *AnnouncementService) Create(ctx context.Context, in AnnouncementInput) error { +func (s *AnnouncementService) Create(ctx context.Context, in *dto.AnnouncementRequest) error { return s.uc.Create(ctx, announcementDO(in)) } -func (s *AnnouncementService) Update(ctx context.Context, in AnnouncementInput) error { +func (s *AnnouncementService) Update(ctx context.Context, in *dto.AnnouncementRequest) error { return s.uc.Update(ctx, announcementDO(in)) } diff --git a/internal/service/system/audit.go b/internal/service/system/audit.go index 60d49ef..c2f77fb 100644 --- a/internal/service/system/audit.go +++ b/internal/service/system/audit.go @@ -56,9 +56,6 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []int) error { func (s *AuditRecorder) RecordLogin(ctx context.Context, v *system.LoginLog) error { return s.uc.RecordLogin(ctx, v) } -func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error { - return s.RecordLogin(ctx, &system.LoginLog{Username: value.Username, IP: value.IP, Status: value.Status, ErrorMessage: value.ErrorMessage, Agent: value.Agent, UserID: value.UserID}) -} func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]*dto.LoginLogResponse, int64, error) { return s.Logins(ctx, page, size, &system.LoginLog{Username: username, Status: status, FilterByStatus: status}) } @@ -94,9 +91,6 @@ func (s *AuditService) DeleteLogins(ctx context.Context, ids []int) error { func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *system.DataAccessLog) error { return s.uc.RecordDataAccess(ctx, v) } -func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error { - return s.RecordDataAccess(ctx, &system.DataAccessLog{EventType: value.EventType, Operation: value.Operation, UserID: value.UserID, AuthorityID: value.AuthorityID, RequestID: value.RequestID, Method: value.Method, Path: value.Path, Detail: value.Detail}) -} func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]*dto.DataAccessLogResponse, int64, error) { return s.DataAccess(ctx, req.Page, req.PageSize, &system.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable}) } diff --git a/internal/service/system/email.go b/internal/service/system/email.go index a2b871a..89c143a 100644 --- a/internal/service/system/email.go +++ b/internal/service/system/email.go @@ -14,7 +14,3 @@ func (s *EmailService) Test(ctx context.Context) error { return s.uc.Test(ctx) } func (s *EmailService) Send(ctx context.Context, to, subject, body string) error { return s.uc.Send(ctx, to, subject, body) } - -func (s *EmailService) Alert(ctx context.Context, subject, body string) error { - return s.uc.Alert(ctx, subject, body) -} diff --git a/internal/service/system/security_session.go b/internal/service/system/security_session.go index bb16872..e84e8c8 100644 --- a/internal/service/system/security_session.go +++ b/internal/service/system/security_session.go @@ -64,10 +64,6 @@ func (s *SecurityService) CaptchaSettings() system.CaptchaSettings { return s.uc.CaptchaRuntimeSettings() } -func (s *SecurityService) ActiveTokenMatches(ctx context.Context, username, token string) (bool, error) { - return s.uc.ActiveTokenMatches(ctx, username, token) -} - func (s *SecurityService) RotateActiveToken(ctx context.Context, username, oldToken, newToken string, expiration time.Duration) error { return s.uc.RotateActiveToken(ctx, username, oldToken, newToken, expiration) } diff --git a/internal/service/system/user.go b/internal/service/system/user.go index 85e12ea..e8f007a 100644 --- a/internal/service/system/user.go +++ b/internal/service/system/user.go @@ -7,14 +7,6 @@ import ( "kra/internal/service/dto" ) -type userInput struct { - ID uint - Username, Password, NickName, HeaderImg, Phone, Email string - AuthorityID uint - Enable int - AuthorityIDs []uint -} - type UserService struct { uc *system.UserUsecase settings *SecurityService @@ -24,28 +16,39 @@ func NewUserService(uc *system.UserUsecase, settings *SecurityService) *UserServ return &UserService{uc: uc, settings: settings} } -func userRequestInput(value *dto.UserRequest) userInput { - return userInput{ID: value.ID, Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, AuthorityIDs: value.AuthorityIDs, Enable: value.Enable, Phone: value.Phone, Email: value.Email} -} func (s *UserService) ListUsersRequest(ctx context.Context, value *dto.UserListRequest) ([]*dto.UserResponse, int64, error) { - return s.ListUsers(ctx, value.Page, value.PageSize, &system.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc}) -} -func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (*dto.UserResponse, error) { - return s.createUser(ctx, userRequestInput(value)) -} -func (s *UserService) UpdateUserRequest(ctx context.Context, value *dto.UserRequest) error { - return s.updateUser(ctx, userRequestInput(value)) -} -func (s *UserService) UpdateSelfUserRequest(ctx context.Context, id uint, value *dto.SelfUserRequest) error { - return s.updateSelfUser(ctx, userInput{ID: id, NickName: value.NickName, HeaderImg: value.HeaderImg, Phone: value.Phone, Email: value.Email, Enable: value.Enable}) + users, total, err := s.uc.ListUsers(ctx, value.Page, value.PageSize, &system.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc}) + if err != nil { + return nil, 0, err + } + result := make([]*dto.UserResponse, 0, len(users)) + for _, user := range users { + result = append(result, convertUser(user)) + } + return result, total, nil } -func (s *UserService) User(ctx context.Context, id uint) (*dto.UserResponse, error) { - value, err := s.uc.User(ctx, id) +func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (*dto.UserResponse, error) { + if err := s.settings.ValidatePassword(ctx, value.Password); err != nil { + return nil, err + } + security, _ := s.settings.CurrentSecurity(ctx) + mustChange := security != nil && security.ForceNewUserChangePassword + user, err := s.uc.CreateUser(ctx, &system.User{Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, Phone: value.Phone, Email: value.Email, Enable: value.Enable, MustChangePassword: mustChange}, value.AuthorityIDs) if err != nil { return nil, err } - return convertUser(value), nil + return convertUser(user), nil +} + +func (s *UserService) UpdateUserRequest(ctx context.Context, value *dto.UserRequest) error { + // The compatible ChangeUserInfo payload uses authorityIds for role assignment; the + // standalone authorityId field is not applied by setUserInfo. + return s.uc.UpdateUser(ctx, &system.User{ID: value.ID, NickName: value.NickName, HeaderImg: value.HeaderImg, Phone: value.Phone, Email: value.Email, Enable: value.Enable}, value.AuthorityIDs) +} + +func (s *UserService) UpdateSelfUserRequest(ctx context.Context, id uint, value *dto.SelfUserRequest) error { + return s.uc.UpdateSelfUser(ctx, &system.User{ID: id, NickName: value.NickName, HeaderImg: value.HeaderImg, Phone: value.Phone, Email: value.Email, Enable: value.Enable}) } func (s *UserService) UserByUUID(ctx context.Context, uuid string) (*dto.UserResponse, error) { @@ -64,18 +67,6 @@ func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]*dto.Dynam return dynamicMenuResponses(menus), nil } -func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *system.UserListFilter) ([]*dto.UserResponse, int64, error) { - users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter) - if err != nil { - return nil, 0, err - } - result := make([]*dto.UserResponse, 0, len(users)) - for _, user := range users { - result = append(result, convertUser(user)) - } - return result, total, nil -} - func (s *UserService) Authorities(ctx context.Context) ([]*dto.AuthorityResponse, error) { values, err := s.uc.Authorities(ctx) if err != nil { @@ -88,26 +79,6 @@ func (s *UserService) Authorities(ctx context.Context) ([]*dto.AuthorityResponse return result, nil } -func (s *UserService) createUser(ctx context.Context, input userInput) (*dto.UserResponse, error) { - if err := s.settings.ValidatePassword(ctx, input.Password); err != nil { - return nil, err - } - security, _ := s.settings.CurrentSecurity(ctx) - mustChange := security != nil && security.ForceNewUserChangePassword - user, err := s.uc.CreateUser(ctx, &system.User{Username: input.Username, Password: input.Password, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable, MustChangePassword: mustChange}, input.AuthorityIDs) - if err != nil { - return nil, err - } - return convertUser(user), nil -} -func (s *UserService) updateUser(ctx context.Context, input userInput) error { - // The compatible ChangeUserInfo payload uses authorityIds for role assignment; the - // standalone authorityId field is not applied by setUserInfo. - return s.uc.UpdateUser(ctx, &system.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}, input.AuthorityIDs) -} -func (s *UserService) updateSelfUser(ctx context.Context, input userInput) error { - return s.uc.UpdateSelfUser(ctx, &system.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}) -} func (s *UserService) DeleteUser(ctx context.Context, id uint) error { return s.uc.DeleteUser(ctx, id) }