diff --git a/cmd/kratos-admin/wire.go b/cmd/kratos-admin/wire.go index 37b9903..b37a194 100644 --- a/cmd/kratos-admin/wire.go +++ b/cmd/kratos-admin/wire.go @@ -13,6 +13,7 @@ import ( "kra/internal/data" "kra/internal/server" "kra/internal/service" + "kra/internal/worker" "github.com/go-kratos/kratos/v3" "github.com/google/wire" @@ -20,5 +21,5 @@ import ( // wireApp init kratos application. func wireApp(*conf.Server, *conf.Runtime, *slog.Logger) (*kratos.App, func(), error) { - panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp)) + panic(wire.Build(server.ProviderSet, worker.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp)) } diff --git a/cmd/kratos-admin/wire_gen.go b/cmd/kratos-admin/wire_gen.go index 1489f17..67fe3f9 100644 --- a/cmd/kratos-admin/wire_gen.go +++ b/cmd/kratos-admin/wire_gen.go @@ -115,7 +115,7 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger apiToken := handler.NewAPIToken(tokenService) initializationRepo := data.NewInitializationRepo(dataData) systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo, taskRuntime) - systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtime) + systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtimeSettings) securityRepo := data.NewSecurityRepo(dataData) securityUsecase := biz.NewSecurityUsecase(securityRepo, cache, runtimeSettings, tokenUsecase) securityService := service.NewSecurityService(securityUsecase) @@ -130,7 +130,7 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger navigation := handler.NewNavigation(userService) session := handler.NewSession(tokenService) set := handler.NewSet(authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session) - engine := server.NewGinEngine(runtime, accessControlService, set, securityService, tokenService, auditRecorder, logger) + engine := server.NewGinEngine(runtime, accessControlService, set, authService, securityService, auditRecorder, logger) httpServer := server.NewGinServer(confServer, engine) app := newApp(logger, httpServer, taskScheduler) return app, func() { diff --git a/internal/biz/access.go b/internal/biz/access.go deleted file mode 100644 index 1633cb4..0000000 --- a/internal/biz/access.go +++ /dev/null @@ -1,160 +0,0 @@ -package biz - -import ( - "context" - "strings" - "time" -) - -type API struct { - ID uint - CreatedAt time.Time - UpdatedAt time.Time - Path string - Description string - APIGroup string - Method string - OrderKey string - Desc bool - StrictAll bool -} - -type APIRepo interface { - CreateAPI(context.Context, *API) error - UpdateAPI(context.Context, *API) error - DeleteAPIs(context.Context, []uint) error - FindAPI(context.Context, uint) (*API, error) - ListAPIs(context.Context, int, int, *API) ([]*API, int64, error) - APIRoleIDs(context.Context, string, string) ([]uint, error) - SetAPIRoles(context.Context, string, string, []uint) error - Authorize(context.Context, uint, string, string) (bool, error) - PolicyPaths(context.Context, uint) ([]*API, error) - SetPolicyPaths(context.Context, uint, []*API) error - IgnoredAPIs(context.Context) ([]*API, error) - SetAPIIgnored(context.Context, string, string, bool) error - ApplyAPISync(context.Context, []*API, []*API) error -} - -type AuthorityAccessRepo interface { - CreateAuthority(context.Context, *Authority) error - CopyAuthority(context.Context, uint, *Authority) error - UpdateAuthority(context.Context, *Authority) error - DeleteAuthority(context.Context, uint) error - ListAuthorities(context.Context) ([]*Authority, error) - SetAuthorityUsers(context.Context, uint, []uint) error - AuthorityUserIDs(context.Context, uint) ([]uint, error) - SetDataScope(context.Context, uint, int, []uint) error - DataScopeDepartmentIDs(context.Context, uint) ([]uint, error) - ResolveDataScope(context.Context, uint, uint) (DataScope, error) -} - -type PermissionRepo interface { - Buttons(context.Context, uint) ([]*MenuButton, error) - SetAuthorityButtons(context.Context, uint, map[uint][]uint) error - AuthorityButtonIDs(context.Context, uint) ([]uint, error) - SelectedButtons(context.Context, uint, uint) ([]uint, error) - SetSelectedButtons(context.Context, uint, uint, []uint) error - CanRemoveButton(context.Context, uint) (bool, error) -} - -type AuthorityUsecase struct{ AuthorityAccessRepo } - -func NewAuthorityUsecase(repo AuthorityAccessRepo) *AuthorityUsecase { - return &AuthorityUsecase{AuthorityAccessRepo: repo} -} - -type APIUsecase struct{ APIRepo } - -func NewAPIUsecase(repo APIRepo) *APIUsecase { return &APIUsecase{APIRepo: repo} } - -type PermissionUsecase struct{ PermissionRepo } - -func NewPermissionUsecase(repo PermissionRepo) *PermissionUsecase { - return &PermissionUsecase{PermissionRepo: repo} -} - -type AccessControlUsecase struct { - authorities AuthorityAccessRepo - apis APIRepo -} - -func NewAccessControlUsecase(authorities AuthorityAccessRepo, apis APIRepo) *AccessControlUsecase { - return &AccessControlUsecase{authorities: authorities, apis: apis} -} - -func (uc *AccessControlUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) { - if authorityID == 888 { - return true, nil - } - return uc.apis.Authorize(ctx, authorityID, path, method) -} - -func (uc *AccessControlUsecase) ResolveDataScope(ctx context.Context, authorityID, userID uint) (DataScope, error) { - return uc.authorities.ResolveDataScope(ctx, authorityID, userID) -} - -type APISyncDiff struct { - Added []*API - Deleted []*API - Ignored []*API -} - -func (uc *APIUsecase) SyncAPIs(ctx context.Context, routes []*API) (*APISyncDiff, error) { - stored, _, err := uc.ListAPIs(ctx, 0, 0, nil) - if err != nil { - return nil, err - } - ignored, err := uc.IgnoredAPIs(ctx) - if err != nil { - return nil, err - } - key := func(value *API) string { return strings.ToUpper(value.Method) + " " + value.Path } - ignoreSet := make(map[string]bool, len(ignored)) - for _, item := range ignored { - ignoreSet[key(item)] = true - } - routeSet := make(map[string]*API, len(routes)) - for _, item := range routes { - if !ignoreSet[key(item)] { - routeSet[key(item)] = item - } - } - storedSet := make(map[string]*API, len(stored)) - for _, item := range stored { - storedSet[key(item)] = item - } - diff := &APISyncDiff{Ignored: ignored} - for routeKey, item := range routeSet { - if storedSet[routeKey] == nil { - diff.Added = append(diff.Added, item) - } - } - for storedKey, item := range storedSet { - if routeSet[storedKey] == nil && !ignoreSet[storedKey] { - diff.Deleted = append(diff.Deleted, item) - } - } - return diff, nil -} - -func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, error) { - items, err := uc.ListAuthorities(ctx) - if err != nil { - return nil, err - } - byID := make(map[uint]*Authority, len(items)) - for _, item := range items { - item.Children = nil - byID[item.AuthorityID] = item - } - roots := make([]*Authority, 0) - for _, item := range items { - if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil { - parent := byID[*item.ParentID] - parent.Children = append(parent.Children, item) - } else { - roots = append(roots, item) - } - } - return roots, nil -} diff --git a/internal/biz/access_control.go b/internal/biz/access_control.go new file mode 100644 index 0000000..52b994b --- /dev/null +++ b/internal/biz/access_control.go @@ -0,0 +1,23 @@ +package biz + +import "context" + +type AccessControlUsecase struct { + authorities AuthorityAccessRepo + apis APIRepo +} + +func NewAccessControlUsecase(authorities AuthorityAccessRepo, apis APIRepo) *AccessControlUsecase { + return &AccessControlUsecase{authorities: authorities, apis: apis} +} + +func (uc *AccessControlUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) { + if authorityID == 888 { + return true, nil + } + return uc.apis.Authorize(ctx, authorityID, path, method) +} + +func (uc *AccessControlUsecase) ResolveDataScope(ctx context.Context, authorityID, userID uint) (DataScope, error) { + return uc.authorities.ResolveDataScope(ctx, authorityID, userID) +} diff --git a/internal/biz/api.go b/internal/biz/api.go new file mode 100644 index 0000000..79e0d9c --- /dev/null +++ b/internal/biz/api.go @@ -0,0 +1,84 @@ +package biz + +import ( + "context" + "strings" + "time" +) + +type API struct { + ID uint + CreatedAt time.Time + UpdatedAt time.Time + Path string + Description string + APIGroup string + Method string + OrderKey string + Desc bool + StrictAll bool +} + +type APIRepo interface { + CreateAPI(context.Context, *API) error + UpdateAPI(context.Context, *API) error + DeleteAPIs(context.Context, []uint) error + FindAPI(context.Context, uint) (*API, error) + ListAPIs(context.Context, int, int, *API) ([]*API, int64, error) + APIRoleIDs(context.Context, string, string) ([]uint, error) + SetAPIRoles(context.Context, string, string, []uint) error + Authorize(context.Context, uint, string, string) (bool, error) + PolicyPaths(context.Context, uint) ([]*API, error) + SetPolicyPaths(context.Context, uint, []*API) error + IgnoredAPIs(context.Context) ([]*API, error) + SetAPIIgnored(context.Context, string, string, bool) error + ApplyAPISync(context.Context, []*API, []*API) error +} + +type APIUsecase struct{ APIRepo } + +func NewAPIUsecase(repo APIRepo) *APIUsecase { return &APIUsecase{APIRepo: repo} } + +type APISyncDiff struct { + Added []*API + Deleted []*API + Ignored []*API +} + +func (uc *APIUsecase) SyncAPIs(ctx context.Context, routes []*API) (*APISyncDiff, error) { + stored, _, err := uc.ListAPIs(ctx, 0, 0, nil) + if err != nil { + return nil, err + } + ignored, err := uc.IgnoredAPIs(ctx) + if err != nil { + return nil, err + } + key := func(value *API) string { return strings.ToUpper(value.Method) + " " + value.Path } + ignoreSet := make(map[string]bool, len(ignored)) + for _, item := range ignored { + ignoreSet[key(item)] = true + } + routeSet := make(map[string]*API, len(routes)) + for _, item := range routes { + if !ignoreSet[key(item)] { + routeSet[key(item)] = item + } + } + storedSet := make(map[string]*API, len(stored)) + for _, item := range stored { + storedSet[key(item)] = item + } + diff := &APISyncDiff{Ignored: ignored} + for routeKey, item := range routeSet { + if storedSet[routeKey] == nil { + diff.Added = append(diff.Added, item) + } + } + for storedKey, item := range storedSet { + if routeSet[storedKey] == nil && !ignoreSet[storedKey] { + diff.Deleted = append(diff.Deleted, item) + } + } + return diff, nil +} diff --git a/internal/biz/audit.go b/internal/biz/audit.go index 4f3aebf..eb8b1fb 100644 --- a/internal/biz/audit.go +++ b/internal/biz/audit.go @@ -27,13 +27,6 @@ type OperationRecord struct { User *User } -type OperationLogRepo interface { - RecordOperation(context.Context, *OperationRecord) error - ListOperations(context.Context, int, int, *OperationRecord) ([]*OperationRecord, int64, error) - FindOperation(context.Context, uint) (*OperationRecord, error) - DeleteOperations(context.Context, []uint) error -} - type LoginLog struct { ID uint CreatedAt time.Time @@ -45,13 +38,6 @@ type LoginLog struct { FilterByStatus bool } -type LoginLogRepo interface { - RecordLogin(context.Context, *LoginLog) error - ListLogins(context.Context, int, int, *LoginLog) ([]*LoginLog, int64, error) - FindLogin(context.Context, uint) (*LoginLog, error) - DeleteLogins(context.Context, []uint) error -} - type DataAccessLog struct { ID uint CreatedAt time.Time @@ -61,12 +47,6 @@ type DataAccessLog struct { RequestID, Method, Path, Detail string } -type DataAccessLogRepo interface { - RecordDataAccess(context.Context, *DataAccessLog) error - ListDataAccess(context.Context, int, int, *DataAccessLog) ([]*DataAccessLog, int64, error) - DeleteDataAccess(context.Context, []uint) error -} - type LogDate struct { Date string FileCount int @@ -104,14 +84,6 @@ type ErrorRecord struct { CreatedAtRange []time.Time } -type ErrorRecordRepo interface { - CreateError(context.Context, *ErrorRecord) error - UpdateError(context.Context, *ErrorRecord) error - DeleteErrors(context.Context, []uint) error - FindError(context.Context, uint) (*ErrorRecord, error) - ListErrors(context.Context, int, int, *ErrorRecord) ([]*ErrorRecord, int64, error) -} - type AuditRecordRepo interface { RecordOperation(context.Context, *OperationRecord) error RecordLogin(context.Context, *LoginLog) error diff --git a/internal/biz/authentication.go b/internal/biz/authentication.go index d0cb2af..4a48450 100644 --- a/internal/biz/authentication.go +++ b/internal/biz/authentication.go @@ -40,6 +40,11 @@ type AuthenticationResult struct { NeedChangePassword bool } +type TokenAuthentication struct { + Claims *AuthClaims + Refreshed *IssuedToken +} + type AuthenticationUsecase struct { users *UserUsecase security *SecurityUsecase @@ -148,3 +153,28 @@ func (uc *AuthenticationUsecase) SwitchAuthority(ctx context.Context, userID, au } return &AuthenticationResult{User: user, Token: issued.Value, ExpiresAt: issued.ExpiresAt, NeedChangePassword: user.MustChangePassword}, nil } + +func (uc *AuthenticationUsecase) AuthenticateToken(ctx context.Context, token string) (*TokenAuthentication, error) { + claims, err := uc.issuer.ParseToken(token) + if err != nil { + return nil, err + } + disabled, err := uc.security.tokens.IsTokenDisabled(ctx, token) + if err != nil || disabled { + return nil, ErrTokenDisabled + } + result := &TokenAuthentication{Claims: claims} + if claims.BufferTime <= 0 || time.Until(claims.ExpiresAt) >= claims.BufferTime { + return result, nil + } + user := &User{ID: claims.ID, UUID: claims.UUID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, MustChangePassword: claims.MustChangePwd} + issued, err := uc.issuer.IssueToken(user, claims.AuthorityID, claims.MustChangePwd, 0) + if err != nil { + return result, nil + } + if err = uc.security.RotateActiveToken(ctx, claims.Username, token, issued.Value, issued.TTL); err != nil { + return result, nil + } + result.Refreshed = issued + return result, nil +} diff --git a/internal/biz/authority.go b/internal/biz/authority.go new file mode 100644 index 0000000..5961740 --- /dev/null +++ b/internal/biz/authority.go @@ -0,0 +1,44 @@ +package biz + +import "context" + +type AuthorityAccessRepo interface { + CreateAuthority(context.Context, *Authority) error + CopyAuthority(context.Context, uint, *Authority) error + UpdateAuthority(context.Context, *Authority) error + DeleteAuthority(context.Context, uint) error + ListAuthorities(context.Context) ([]*Authority, error) + SetAuthorityUsers(context.Context, uint, []uint) error + AuthorityUserIDs(context.Context, uint) ([]uint, error) + SetDataScope(context.Context, uint, int, []uint) error + DataScopeDepartmentIDs(context.Context, uint) ([]uint, error) + ResolveDataScope(context.Context, uint, uint) (DataScope, error) +} + +type AuthorityUsecase struct{ AuthorityAccessRepo } + +func NewAuthorityUsecase(repo AuthorityAccessRepo) *AuthorityUsecase { + return &AuthorityUsecase{AuthorityAccessRepo: repo} +} + +func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, error) { + items, err := uc.ListAuthorities(ctx) + if err != nil { + return nil, err + } + byID := make(map[uint]*Authority, len(items)) + for _, item := range items { + item.Children = nil + byID[item.AuthorityID] = item + } + roots := make([]*Authority, 0) + for _, item := range items { + if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil { + parent := byID[*item.ParentID] + parent.Children = append(parent.Children, item) + } else { + roots = append(roots, item) + } + } + return roots, nil +} diff --git a/internal/biz/department.go b/internal/biz/department.go new file mode 100644 index 0000000..704ca65 --- /dev/null +++ b/internal/biz/department.go @@ -0,0 +1,46 @@ +package biz + +import ( + "context" + "time" +) + +type Department struct { + ID uint + CreatedAt time.Time + UpdatedAt time.Time + Name string + ParentID uint + Ancestors string + Sort int + LeaderID uint + Leader *User + Status bool + Children []*Department + NamePath string +} + +type DepartmentRepo interface { + CreateDepartment(context.Context, *Department) error + UpdateDepartment(context.Context, *Department) error + DeleteDepartment(context.Context, uint) error + FindDepartment(context.Context, uint) (*Department, error) + ListDepartments(context.Context, string) ([]*Department, error) + DepartmentUserIDs(context.Context, uint) ([]uint, error) + SetDepartmentUsers(context.Context, uint, []uint) error + SetUserDepartments(context.Context, uint, []uint, uint) error +} + +type DepartmentUsecase struct{ DepartmentRepo } + +func NewDepartmentUsecase(repo DepartmentRepo) *DepartmentUsecase { + return &DepartmentUsecase{DepartmentRepo: repo} +} + +func (uc *DepartmentUsecase) Departments(ctx context.Context, name string) ([]*Department, error) { + return uc.ListDepartments(ctx, name) +} + +func (uc *DepartmentUsecase) Department(ctx context.Context, id uint) (*Department, error) { + return uc.FindDepartment(ctx, id) +} diff --git a/internal/biz/infrastructure.go b/internal/biz/infrastructure.go index 1eb8c4f..72c4c24 100644 --- a/internal/biz/infrastructure.go +++ b/internal/biz/infrastructure.go @@ -2,6 +2,7 @@ package biz import ( "context" + "errors" "io" "time" ) @@ -68,6 +69,28 @@ type IssuedToken struct { TTL time.Duration } +type AuthClaims struct { + UUID string + ID uint + Username string + NickName string + AuthorityID uint + BufferTime time.Duration + MustChangePwd bool + Issuer string + ExpiresAt time.Time +} + +var ( + ErrTokenExpired = errors.New("token expired") + ErrTokenMalformed = errors.New("token malformed") + ErrTokenSignatureInvalid = errors.New("token signature invalid") + ErrTokenNotValidYet = errors.New("token not valid yet") + ErrTokenInvalid = errors.New("token invalid") + ErrTokenDisabled = errors.New("token disabled") +) + type TokenIssuer interface { IssueToken(*User, uint, bool, time.Duration) (*IssuedToken, error) + ParseToken(string) (*AuthClaims, error) } diff --git a/internal/biz/permission.go b/internal/biz/permission.go new file mode 100644 index 0000000..4331607 --- /dev/null +++ b/internal/biz/permission.go @@ -0,0 +1,18 @@ +package biz + +import "context" + +type PermissionRepo interface { + Buttons(context.Context, uint) ([]*MenuButton, error) + SetAuthorityButtons(context.Context, uint, map[uint][]uint) error + AuthorityButtonIDs(context.Context, uint) ([]uint, error) + SelectedButtons(context.Context, uint, uint) ([]uint, error) + SetSelectedButtons(context.Context, uint, uint, []uint) error + CanRemoveButton(context.Context, uint) (bool, error) +} + +type PermissionUsecase struct{ PermissionRepo } + +func NewPermissionUsecase(repo PermissionRepo) *PermissionUsecase { + return &PermissionUsecase{PermissionRepo: repo} +} diff --git a/internal/biz/organization.go b/internal/biz/position.go similarity index 51% rename from internal/biz/organization.go rename to internal/biz/position.go index c127408..937d7de 100644 --- a/internal/biz/organization.go +++ b/internal/biz/position.go @@ -5,45 +5,6 @@ import ( "time" ) -type Department struct { - ID uint - CreatedAt time.Time - UpdatedAt time.Time - Name string - ParentID uint - Ancestors string - Sort int - LeaderID uint - Leader *User - Status bool - Children []*Department - NamePath string -} - -type DepartmentRepo interface { - CreateDepartment(context.Context, *Department) error - UpdateDepartment(context.Context, *Department) error - DeleteDepartment(context.Context, uint) error - FindDepartment(context.Context, uint) (*Department, error) - ListDepartments(context.Context, string) ([]*Department, error) - DepartmentUserIDs(context.Context, uint) ([]uint, error) - SetDepartmentUsers(context.Context, uint, []uint) error - SetUserDepartments(context.Context, uint, []uint, uint) error -} - -type DepartmentUsecase struct{ DepartmentRepo } - -func NewDepartmentUsecase(repo DepartmentRepo) *DepartmentUsecase { - return &DepartmentUsecase{DepartmentRepo: repo} -} - -func (uc *DepartmentUsecase) Departments(ctx context.Context, name string) ([]*Department, error) { - return uc.ListDepartments(ctx, name) -} -func (uc *DepartmentUsecase) Department(ctx context.Context, id uint) (*Department, error) { - return uc.FindDepartment(ctx, id) -} - type Position struct { ID uint CreatedAt time.Time @@ -81,6 +42,7 @@ func NewPositionUsecase(repo PositionRepo) *PositionUsecase { func (uc *PositionUsecase) Positions(ctx context.Context, page, size int, filter *PositionListFilter) ([]*Position, int64, error) { return uc.ListPositions(ctx, page, size, filter) } + func (uc *PositionUsecase) Position(ctx context.Context, id uint) (*Position, error) { return uc.FindPosition(ctx, id) } diff --git a/internal/biz/system_init.go b/internal/biz/system_init.go index 6e8c563..62eff42 100644 --- a/internal/biz/system_init.go +++ b/internal/biz/system_init.go @@ -2,6 +2,7 @@ package biz import ( "context" + "encoding/json" "errors" ) @@ -24,6 +25,9 @@ type InitializationRepo interface { PersistAdminConfig(context.Context, []byte) error PersistRuntimeConfig(context.Context, []byte, []byte) error ReloadConfig(context.Context) error + ConfigurationJSON() (json.RawMessage, error) + SaveConfigurationJSON(context.Context, json.RawMessage) error + DiskMountPoints() []string } type SystemConfigUsecase struct { @@ -68,3 +72,13 @@ func (uc *SystemConfigUsecase) ReloadConfig(ctx context.Context) error { } return nil } + +func (uc *SystemConfigUsecase) ConfigurationJSON() (json.RawMessage, error) { + return uc.repo.ConfigurationJSON() +} + +func (uc *SystemConfigUsecase) SaveConfigurationJSON(ctx context.Context, value json.RawMessage) error { + return uc.repo.SaveConfigurationJSON(ctx, value) +} + +func (uc *SystemConfigUsecase) DiskMountPoints() []string { return uc.repo.DiskMountPoints() } diff --git a/internal/data/access.go b/internal/data/access.go deleted file mode 100644 index 0282dae..0000000 --- a/internal/data/access.go +++ /dev/null @@ -1,15 +0,0 @@ -package data - -import "kra/internal/biz" - -type authorityAccessRepo struct{ data *Data } -type apiRepo struct{ data *Data } -type permissionRepo struct{ data *Data } - -func NewAuthorityAccessRepo(data *Data) biz.AuthorityAccessRepo { - return &authorityAccessRepo{data: data} -} - -func NewAPIRepo(data *Data) biz.APIRepo { return &apiRepo{data: data} } - -func NewPermissionRepo(data *Data) biz.PermissionRepo { return &permissionRepo{data: data} } diff --git a/internal/data/api.go b/internal/data/api.go index b727b2d..50db0c0 100644 --- a/internal/data/api.go +++ b/internal/data/api.go @@ -14,6 +14,10 @@ import ( "gorm.io/gorm" ) +type apiRepo struct{ data *Data } + +func NewAPIRepo(data *Data) biz.APIRepo { return &apiRepo{data: data} } + type apiPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time diff --git a/internal/data/api_token.go b/internal/data/api_token.go index 4e13b50..c22a4d5 100644 --- a/internal/data/api_token.go +++ b/internal/data/api_token.go @@ -10,6 +10,10 @@ import ( "gorm.io/gorm" ) +type apiTokenRepo struct{ data *Data } + +func NewAPITokenRepo(data *Data) biz.APITokenRepo { return &apiTokenRepo{data: data} } + type apiTokenPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time @@ -45,7 +49,7 @@ func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID if err != nil { return nil, false, err } - user, err := (&userRepo{data: r.data}).toBizUser(ctx, &po) + user, err := (&userRepo{data: r.data}).loadUser(ctx, &po) return user, count > 0 || po.AuthorityID == authorityID, err } func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) error { diff --git a/internal/data/authority.go b/internal/data/authority.go index e47bf78..789bea9 100644 --- a/internal/data/authority.go +++ b/internal/data/authority.go @@ -11,6 +11,12 @@ import ( "gorm.io/gorm" ) +type authorityAccessRepo struct{ data *Data } + +func NewAuthorityAccessRepo(data *Data) biz.AuthorityAccessRepo { + return &authorityAccessRepo{data: data} +} + func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID uint) (map[uint]bool, error) { allowed := make(map[uint]bool) if actorID == 0 { diff --git a/internal/data/config_management.go b/internal/data/config_management.go new file mode 100644 index 0000000..5a06a53 --- /dev/null +++ b/internal/data/config_management.go @@ -0,0 +1,303 @@ +package data + +import ( + "context" + "encoding/json" + + "kra/internal/conf" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/durationpb" +) + +type configurationEnvelope struct { + Data json.RawMessage `json:"data"` + Admin json.RawMessage `json:"admin"` + Email *struct { + To string `json:"to"` + From string `json:"from"` + Host string `json:"host"` + Secret string `json:"secret"` + Nickname string `json:"nickname"` + Port int32 `json:"port"` + IsSSL bool `json:"is-ssl"` + IsLoginAuth bool `json:"is-loginauth"` + } `json:"email"` +} + +func (r *initializationRepo) ConfigurationJSON() (json.RawMessage, error) { + dataConfig, adminConfig := r.data.runtime.Values() + admin := map[string]any{"routerPrefix": ""} + email := map[string]any{} + if adminConfig != nil { + admin["routerPrefix"] = adminConfig.RouterPrefix + if adminConfig.System != nil { + admin["system"] = map[string]any{"useRedis": adminConfig.System.UseRedis, "useMultipoint": adminConfig.System.UseMultipoint, "useStrictAuth": adminConfig.System.UseStrictAuth, "disableAutoMigrate": adminConfig.System.DisableAutoMigrate, "useMongo": adminConfig.System.UseMongo} + } + if adminConfig.Jwt != nil { + admin["jwt"] = map[string]any{"signingKey": "******", "expiresTime": durationString(adminConfig.Jwt.ExpiresTime), "bufferTime": durationString(adminConfig.Jwt.BufferTime), "issuer": adminConfig.Jwt.Issuer} + } + if adminConfig.Captcha != nil { + admin["captcha"] = map[string]any{"keyLong": adminConfig.Captcha.KeyLong, "imgWidth": adminConfig.Captcha.ImgWidth, "imgHeight": adminConfig.Captcha.ImgHeight, "storeExpiration": durationString(adminConfig.Captcha.StoreExpiration)} + } + if adminConfig.Local != nil { + admin["local"] = map[string]any{"storePath": adminConfig.Local.StorePath, "pathPrefix": adminConfig.Local.PathPrefix} + } + if adminConfig.Media != nil { + admin["media"] = map[string]any{"sessionTtl": adminConfig.Media.SessionTtl, "maxFileSize": adminConfig.Media.MaxFileSize} + } + if adminConfig.Email != nil { + email = map[string]any{"to": adminConfig.Email.To, "from": adminConfig.Email.From, "host": adminConfig.Email.Host, "secret": "******", "nickname": adminConfig.Email.Nickname, "port": adminConfig.Email.Port, "is-ssl": adminConfig.Email.IsSsl, "is-loginauth": adminConfig.Email.IsLoginAuth} + } + if adminConfig.Storage != nil { + storage := proto.Clone(adminConfig.Storage).(*conf.AdminBackend_Storage) + maskStorageSecrets(storage) + admin["storage"] = storage + } + if adminConfig.Zap != nil { + admin["zap"] = adminConfig.Zap + } + if adminConfig.Cors != nil { + admin["cors"] = adminConfig.Cors + } + if adminConfig.App != nil { + admin["app"] = adminConfig.App + } + } + maskDataSecrets(dataConfig) + dataMap := map[string]any{} + if dataConfig != nil { + raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(dataConfig) + if err != nil { + return nil, err + } + if err = json.Unmarshal(raw, &dataMap); err != nil { + return nil, err + } + } + return json.Marshal(map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}}) +} + +func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error { + currentData, currentAdmin := r.data.runtime.Values() + if currentAdmin == nil { + return nil + } + nextData := cloneDataConfig(currentData) + nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend) + var value configurationEnvelope + if err := json.Unmarshal(raw, &value); err != nil { + return err + } + options := protojson.UnmarshalOptions{DiscardUnknown: true} + if len(value.Data) > 0 && string(value.Data) != "null" { + patch := &conf.Data{} + if err := options.Unmarshal(value.Data, patch); err != nil { + return err + } + applyDataPatch(nextData, patch) + } + if len(value.Admin) > 0 && string(value.Admin) != "null" { + patch := &conf.AdminBackend{} + if err := options.Unmarshal(value.Admin, patch); err != nil { + return err + } + applyAdminPatch(nextAdmin, patch) + } + preserveDataSecrets(nextData, currentData) + preserveAdminSecrets(nextAdmin, currentAdmin) + if value.Email != nil { + if nextAdmin.Email == nil { + nextAdmin.Email = &conf.AdminBackend_Email{} + } + nextAdmin.Email.To, nextAdmin.Email.From, nextAdmin.Email.Host = value.Email.To, value.Email.From, value.Email.Host + nextAdmin.Email.Nickname, nextAdmin.Email.Port = value.Email.Nickname, value.Email.Port + nextAdmin.Email.IsSsl, nextAdmin.Email.IsLoginAuth = value.Email.IsSSL, value.Email.IsLoginAuth + if value.Email.Secret != "" && value.Email.Secret != "******" { + nextAdmin.Email.Secret = value.Email.Secret + } + } + nextAdmin.ConfigPath = currentAdmin.ConfigPath + dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(nextData) + if err != nil { + return err + } + adminRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(nextAdmin) + if err != nil { + return err + } + return r.PersistRuntimeConfig(ctx, dataRaw, adminRaw) +} + +func (r *initializationRepo) DiskMountPoints() []string { + config := r.data.runtime.Admin() + if config == nil { + return nil + } + points := make([]string, 0, len(config.DiskList)) + for _, item := range config.DiskList { + if item != nil && item.MountPoint != "" { + points = append(points, item.MountPoint) + } + } + return points +} + +func cloneDataConfig(value *conf.Data) *conf.Data { + if value == nil { + return &conf.Data{} + } + return proto.Clone(value).(*conf.Data) +} + +func durationString(value *durationpb.Duration) string { + if value == nil { + return "0s" + } + return value.AsDuration().String() +} + +func applyDataPatch(target, patch *conf.Data) { + if patch.Database != nil { + target.Database = patch.Database + } + if patch.Redis != nil { + target.Redis = patch.Redis + } + if patch.Mongo != nil { + target.Mongo = patch.Mongo + } + if patch.DatabaseList != nil { + target.DatabaseList = patch.DatabaseList + } + if patch.RedisList != nil { + target.RedisList = patch.RedisList + } +} + +func applyAdminPatch(target, patch *conf.AdminBackend) { + target.RouterPrefix = patch.RouterPrefix + if patch.System != nil { + target.System = patch.System + } + if patch.Jwt != nil { + target.Jwt = patch.Jwt + } + if patch.Captcha != nil { + target.Captcha = patch.Captcha + } + if patch.Local != nil { + target.Local = patch.Local + } + if patch.Media != nil { + target.Media = patch.Media + } + if patch.Storage != nil { + target.Storage = patch.Storage + } + if patch.Zap != nil { + target.Zap = patch.Zap + } + if patch.Cors != nil { + target.Cors = patch.Cors + } + if patch.App != nil { + target.App = patch.App + } +} + +func maskDataSecrets(value *conf.Data) { + if value == nil { + return + } + if value.Database != nil { + value.Database.Password = "******" + } + if value.Redis != nil { + value.Redis.Password = "******" + } + if value.Mongo != nil { + value.Mongo.Password = "******" + } + for _, item := range value.DatabaseList { + if item != nil { + item.Password = "******" + } + } + for _, item := range value.RedisList { + if item != nil { + item.Password = "******" + } + } +} + +func preserveDataSecrets(next, current *conf.Data) { + if next == nil || current == nil { + return + } + if next.Database != nil && current.Database != nil && maskedSecret(next.Database.Password) { + next.Database.Password = current.Database.Password + } + if next.Redis != nil && current.Redis != nil && maskedSecret(next.Redis.Password) { + next.Redis.Password = current.Redis.Password + } + if next.Mongo != nil && current.Mongo != nil && maskedSecret(next.Mongo.Password) { + next.Mongo.Password = current.Mongo.Password + } + for i, item := range next.DatabaseList { + if item != nil && i < len(current.DatabaseList) && current.DatabaseList[i] != nil && maskedSecret(item.Password) { + item.Password = current.DatabaseList[i].Password + } + } + for i, item := range next.RedisList { + if item != nil && i < len(current.RedisList) && current.RedisList[i] != nil && maskedSecret(item.Password) { + item.Password = current.RedisList[i].Password + } + } +} + +func preserveAdminSecrets(next, current *conf.AdminBackend) { + if next.Jwt != nil && current.Jwt != nil && maskedSecret(next.Jwt.SigningKey) { + next.Jwt.SigningKey = current.Jwt.SigningKey + } + if next.Email != nil && current.Email != nil && maskedSecret(next.Email.Secret) { + next.Email.Secret = current.Email.Secret + } + preserveStorageSecrets(next.Storage, current.Storage) +} + +func maskedSecret(value string) bool { return value == "" || value == "******" } + +func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore { + if storage == nil { + return nil + } + return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio} +} + +func maskStorageSecrets(storage *conf.AdminBackend_Storage) { + if storage.Qiniu != nil { + storage.Qiniu.SecretKey = "******" + } + for _, item := range objectStores(storage) { + if item != nil { + item.SecretKey = "******" + } + } +} + +func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) { + if next == nil || current == nil { + return + } + if next.Qiniu != nil && current.Qiniu != nil && maskedSecret(next.Qiniu.SecretKey) { + next.Qiniu.SecretKey = current.Qiniu.SecretKey + } + nextItems, currentItems := objectStores(next), objectStores(current) + for i := range nextItems { + if nextItems[i] != nil && currentItems[i] != nil && maskedSecret(nextItems[i].SecretKey) { + nextItems[i].SecretKey = currentItems[i].SecretKey + } + } +} diff --git a/internal/data/department.go b/internal/data/department.go index dd1c027..db9a90e 100644 --- a/internal/data/department.go +++ b/internal/data/department.go @@ -12,6 +12,10 @@ import ( "gorm.io/gorm" ) +type departmentRepo struct{ data *Data } + +func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &departmentRepo{data: data} } + type departmentPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time diff --git a/internal/data/dictionary.go b/internal/data/dictionary.go index b61a7cd..85acd22 100644 --- a/internal/data/dictionary.go +++ b/internal/data/dictionary.go @@ -12,6 +12,10 @@ import ( "gorm.io/gorm" ) +type dictionaryRepo struct{ data *Data } + +func NewDictionaryRepo(data *Data) biz.DictionaryRepo { return &dictionaryRepo{data: data} } + type dictionaryPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time diff --git a/internal/data/organization.go b/internal/data/organization.go deleted file mode 100644 index 2dd86e8..0000000 --- a/internal/data/organization.go +++ /dev/null @@ -1,10 +0,0 @@ -package data - -import "kra/internal/biz" - -type departmentRepo struct{ data *Data } -type positionRepo struct{ data *Data } - -func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &departmentRepo{data: data} } - -func NewPositionRepo(data *Data) biz.PositionRepo { return &positionRepo{data: data} } diff --git a/internal/data/parameter.go b/internal/data/parameter.go index 00ea504..e7db1a1 100644 --- a/internal/data/parameter.go +++ b/internal/data/parameter.go @@ -10,6 +10,10 @@ import ( "gorm.io/gorm/clause" ) +type parameterRepo struct{ data *Data } + +func NewParameterRepo(data *Data) biz.ParameterRepo { return ¶meterRepo{data: data} } + type parameterPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time diff --git a/internal/data/permission.go b/internal/data/permission.go index d7b8a3a..8c9c096 100644 --- a/internal/data/permission.go +++ b/internal/data/permission.go @@ -8,6 +8,10 @@ import ( "gorm.io/gorm" ) +type permissionRepo struct{ data *Data } + +func NewPermissionRepo(data *Data) biz.PermissionRepo { return &permissionRepo{data: data} } + type menuButtonPO struct { ID uint `gorm:"primaryKey"` Name string diff --git a/internal/data/position.go b/internal/data/position.go index eaf6ae3..2a23304 100644 --- a/internal/data/position.go +++ b/internal/data/position.go @@ -10,6 +10,10 @@ import ( "gorm.io/gorm" ) +type positionRepo struct{ data *Data } + +func NewPositionRepo(data *Data) biz.PositionRepo { return &positionRepo{data: data} } + type positionPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time diff --git a/internal/data/runtime_settings.go b/internal/data/runtime_settings.go index 71a5b8c..3abc0fe 100644 --- a/internal/data/runtime_settings.go +++ b/internal/data/runtime_settings.go @@ -1,6 +1,7 @@ package data import ( + "errors" "time" "kra/internal/biz" @@ -92,3 +93,22 @@ func (i *tokenIssuer) IssueToken(user *biz.User, authorityID uint, mustChangePas } return &biz.IssuedToken{Value: token, ExpiresAt: claims.ExpiresAt.Time, TTL: expires}, nil } + +func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) { + claims, err := adminauth.Parse(token, i.settings.JWTSettings().SigningKey) + if err != nil { + switch { + case errors.Is(err, adminauth.ErrTokenExpired): + return nil, biz.ErrTokenExpired + case errors.Is(err, adminauth.ErrTokenMalformed): + return nil, biz.ErrTokenMalformed + case errors.Is(err, adminauth.ErrTokenSignatureInvalid): + return nil, biz.ErrTokenSignatureInvalid + case errors.Is(err, adminauth.ErrTokenNotValidYet): + return nil, biz.ErrTokenNotValidYet + default: + return nil, biz.ErrTokenInvalid + } + } + return &biz.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, Issuer: claims.Issuer, ExpiresAt: claims.ExpiresAt.Time}, nil +} diff --git a/internal/data/security.go b/internal/data/security.go index 985b072..88d716e 100644 --- a/internal/data/security.go +++ b/internal/data/security.go @@ -10,6 +10,10 @@ import ( "gorm.io/gorm" ) +type securityRepo struct{ data *Data } + +func NewSecurityRepo(data *Data) biz.SecurityRepo { return &securityRepo{data: data} } + type securityConfigPO struct { ID uint `gorm:"primaryKey"` CaptchaOpen int diff --git a/internal/data/settings.go b/internal/data/settings.go deleted file mode 100644 index 9ecfd1b..0000000 --- a/internal/data/settings.go +++ /dev/null @@ -1,16 +0,0 @@ -package data - -import "kra/internal/biz" - -type dictionaryRepo struct{ data *Data } -type parameterRepo struct{ data *Data } -type apiTokenRepo struct{ data *Data } -type securityRepo struct{ data *Data } - -func NewDictionaryRepo(data *Data) biz.DictionaryRepo { return &dictionaryRepo{data: data} } - -func NewParameterRepo(data *Data) biz.ParameterRepo { return ¶meterRepo{data: data} } - -func NewAPITokenRepo(data *Data) biz.APITokenRepo { return &apiTokenRepo{data: data} } - -func NewSecurityRepo(data *Data) biz.SecurityRepo { return &securityRepo{data: data} } diff --git a/internal/data/user.go b/internal/data/user.go index d84c8a2..143697c 100644 --- a/internal/data/user.go +++ b/internal/data/user.go @@ -22,7 +22,7 @@ func (r *userRepo) FindUserByUsername(ctx context.Context, username string) (*bi } return nil, err } - return r.toBizUser(ctx, &po) + return r.loadUser(ctx, &po) } func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) { @@ -33,10 +33,10 @@ func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) } return nil, err } - return r.toBizUser(ctx, &po) + return r.loadUser(ctx, &po) } -func (r *userRepo) toBizUser(ctx context.Context, po *userPO) (*biz.User, error) { +func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error) { var authority authorityPO if err := r.data.gormDB.WithContext(ctx).First(&authority, "authority_id = ?", po.AuthorityID).Error; err != nil { return nil, err @@ -90,7 +90,7 @@ func baseBizUser(po *userPO) *biz.User { return &biz.User{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UUID: po.UUID, Username: po.Username, Password: po.Password, NickName: po.NickName, HeaderImg: po.HeaderImg, AuthorityID: po.AuthorityID, DeptID: po.DeptID, Phone: po.Phone, Email: po.Email, Enable: po.Enable, OriginSetting: setting, MustChangePassword: po.MustChangePassword, PasswordUpdatedAt: po.PasswordUpdatedAt} } -func (r *userRepo) toBizUsers(ctx context.Context, pos []userPO) ([]*biz.User, error) { +func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*biz.User, error) { if len(pos) == 0 { return []*biz.User{}, nil } @@ -275,7 +275,7 @@ func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *bi if err := applyPagination(db.Order(order), page, pageSize, 100).Find(&pos).Error; err != nil { return nil, 0, err } - users, err := r.toBizUsers(ctx, pos) + users, err := r.loadUsers(ctx, pos) return users, total, err } diff --git a/internal/server/gin.go b/internal/server/gin.go index 644a192..1f78ea2 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -20,7 +20,7 @@ import ( kratoshttp "github.com/go-kratos/kratos/v3/transport/http" ) -func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, handlers *handler.Set, security *service.SecurityService, tokens *service.TokenService, audit *service.AuditRecorder, logger *slog.Logger) *gin.Engine { +func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger) *gin.Engine { gin.SetMode(gin.ReleaseMode) engine := gin.New() engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(audit, logger), servermiddleware.AccessLog(runtime, logger), servermiddleware.ErrorAudit(audit), servermiddleware.SecurityRateLimit(security), servermiddleware.OperationAudit(runtime, audit)) @@ -35,7 +35,7 @@ func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, h serverrouter.RegisterPublic(public, engine, handlers.Public) private := engine.Group(prefix) - private.Use(servermiddleware.Auth(runtime, security, tokens), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access, audit)) + private.Use(servermiddleware.Auth(auth), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access, audit)) serverrouter.RegisterUser(private, handlers.User) serverrouter.RegisterNavigation(private, handlers.Navigation) serverrouter.RegisterSession(private, handlers.Session) diff --git a/internal/server/handler/dictionary.go b/internal/server/handler/dictionary.go index 2167e67..1e3ae24 100644 --- a/internal/server/handler/dictionary.go +++ b/internal/server/handler/dictionary.go @@ -92,17 +92,6 @@ func (h *Dictionary) Export(c *gin.Context) { httpx.Fail(c, "导出失败") return } - delete(item, "ID") - delete(item, "CreatedAt") - delete(item, "UpdatedAt") - delete(item, "DeletedAt") - if details, ok := item["sysDictionaryDetails"].([]map[string]any); ok { - for _, detail := range details { - for _, key := range []string{"ID", "CreatedAt", "UpdatedAt", "DeletedAt", "sysDictionaryID", "parentID", "disabled", "children"} { - delete(detail, key) - } - } - } httpx.Write(c, httpx.CodeSuccess, item, "导出成功") } func (h *Dictionary) Import(c *gin.Context) { @@ -258,5 +247,5 @@ func (h *Dictionary) Path(c *gin.Context) { httpx.Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"path": item["path"]}, "获取成功") + httpx.Write(c, httpx.CodeSuccess, gin.H{"path": item.Path}, "获取成功") } diff --git a/internal/server/handler/system_config.go b/internal/server/handler/system_config.go index 997e3ea..d81a3d2 100644 --- a/internal/server/handler/system_config.go +++ b/internal/server/handler/system_config.go @@ -44,7 +44,12 @@ func (h *SystemConfig) SetSecurity(c *gin.Context) { } func (h *SystemConfig) Get(c *gin.Context) { - httpx.Write(c, httpx.CodeSuccess, h.system.SystemConfig(), "获取成功") + value, err := h.system.SystemConfig() + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, value, "获取成功") } func (h *SystemConfig) Set(c *gin.Context) { diff --git a/internal/server/middleware/auth.go b/internal/server/middleware/auth.go index 9c360b1..7ab8f3a 100644 --- a/internal/server/middleware/auth.go +++ b/internal/server/middleware/auth.go @@ -5,12 +5,10 @@ import ( "net/http" "strconv" "strings" - "time" - "kra/internal/conf" + "kra/internal/biz" "kra/internal/server/httpx" "kra/internal/service" - "kra/pkg/adminauth" "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" @@ -20,12 +18,7 @@ const claimsKey = "admin_claims" var refreshTokens singleflight.Group -type refreshedToken struct { - token string - expiresAt int64 -} - -func Auth(runtime *conf.Runtime, security *service.SecurityService, tokens *service.TokenService) gin.HandlerFunc { +func Auth(auth *service.AuthService) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("x-token") if token == "" { @@ -35,73 +28,41 @@ func Auth(runtime *conf.Runtime, security *service.SecurityService, tokens *serv httpx.NoAuth(c, "未登录或非法访问,请登录") return } - secret := "" - config := runtime.Admin() - if config != nil && config.Jwt != nil { - secret = config.Jwt.SigningKey - } - claims, err := adminauth.Parse(token, secret) + value, err, _ := refreshTokens.Do(token, func() (any, error) { + return auth.AuthenticateToken(c.Request.Context(), token) + }) if err != nil { message := "无法处理此token" switch { - case errors.Is(err, adminauth.ErrTokenExpired): + case errors.Is(err, biz.ErrTokenExpired): message = "登录已过期,请重新登录" - case errors.Is(err, adminauth.ErrTokenMalformed): + case errors.Is(err, biz.ErrTokenMalformed): message = "这不是一个token" - case errors.Is(err, adminauth.ErrTokenSignatureInvalid): + case errors.Is(err, biz.ErrTokenSignatureInvalid): message = "无效签名" - case errors.Is(err, adminauth.ErrTokenNotValidYet): + case errors.Is(err, biz.ErrTokenNotValidYet): message = "token尚未激活" + case errors.Is(err, biz.ErrTokenDisabled): + message = "您的帐户异地登陆或令牌失效" } httpx.SetTokenCookie(c, "", -1) httpx.NoAuth(c, message) return } - if disabled, checkErr := tokens.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled { - httpx.SetTokenCookie(c, "", -1) - httpx.NoAuth(c, "您的帐户异地登陆或令牌失效") - return + authentication := value.(*biz.TokenAuthentication) + if authentication.Refreshed != nil { + c.Header("new-token", authentication.Refreshed.Value) + c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10)) + httpx.SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds())) } - if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second { - expires, buffer := 7*24*time.Hour, time.Duration(claims.BufferTime)*time.Second - issuer := claims.Issuer - config = runtime.Admin() - if config != nil && config.Jwt != nil { - if config.Jwt.ExpiresTime != nil { - expires = config.Jwt.ExpiresTime.AsDuration() - } - if config.Jwt.BufferTime != nil { - buffer = config.Jwt.BufferTime.AsDuration() - } - if config.Jwt.Issuer != "" { - issuer = config.Jwt.Issuer - } - } - value, refreshErr, _ := refreshTokens.Do(token, func() (any, error) { - newToken, newClaims, generateErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd) - if generateErr != nil { - return nil, generateErr - } - if rotateErr := security.RotateActiveToken(c.Request.Context(), claims.Username, token, newToken, expires); rotateErr != nil { - return nil, rotateErr - } - return refreshedToken{token: newToken, expiresAt: newClaims.ExpiresAt.Unix()}, nil - }) - if refreshErr == nil { - refreshed := value.(refreshedToken) - c.Header("new-token", refreshed.token) - c.Header("new-expires-at", strconv.FormatInt(refreshed.expiresAt, 10)) - httpx.SetTokenCookie(c, refreshed.token, int(expires.Seconds())) - } - } - c.Set(claimsKey, claims) + c.Set(claimsKey, authentication.Claims) c.Next() } } -func Claims(c *gin.Context) *adminauth.Claims { +func Claims(c *gin.Context) *biz.AuthClaims { value, _ := c.Get(claimsKey) - claims, _ := value.(*adminauth.Claims) + claims, _ := value.(*biz.AuthClaims) return claims } diff --git a/internal/server/server.go b/internal/server/server.go index 00cbfce..ace3287 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,10 +2,9 @@ package server import ( "kra/internal/server/handler" - "kra/internal/worker" "github.com/google/wire" ) // ProviderSet is server providers. -var ProviderSet = wire.NewSet(NewGinEngine, NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, handler.NewSet, worker.NewTaskExecutor, worker.NewTaskScheduler, worker.NewTaskRuntime) +var ProviderSet = wire.NewSet(NewGinEngine, NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, handler.NewSet) diff --git a/internal/service/access.go b/internal/service/access.go deleted file mode 100644 index 7fd78b3..0000000 --- a/internal/service/access.go +++ /dev/null @@ -1,122 +0,0 @@ -package service - -import ( - "context" - - "kra/internal/biz" - "kra/internal/service/dto" -) - -type AuthorityService struct{ uc *biz.AuthorityUsecase } - -func NewAuthorityService(uc *biz.AuthorityUsecase) *AuthorityService { - return &AuthorityService{uc: uc} -} - -type PermissionService struct{ uc *biz.PermissionUsecase } - -func NewPermissionService(uc *biz.PermissionUsecase) *PermissionService { - return &PermissionService{uc: uc} -} - -type AccessControlService struct{ uc *biz.AccessControlUsecase } - -func NewAccessControlService(uc *biz.AccessControlUsecase) *AccessControlService { - return &AccessControlService{uc: uc} -} - -func (s *AccessControlService) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) { - return s.uc.Authorize(ctx, aid, path, method) -} - -func authorityDTO(v *biz.Authority) map[string]any { - out := convertAuthority(*v) - children := make([]map[string]any, 0, len(v.Children)) - for _, child := range v.Children { - children = append(children, authorityDTO(child)) - } - out["children"] = children - return out -} -func (s *AuthorityService) Authorities(ctx context.Context) ([]map[string]any, error) { - items, err := s.uc.AuthorityTree(ctx) - if err != nil { - return nil, err - } - roots := make([]map[string]any, 0, len(items)) - for _, v := range items { - roots = append(roots, authorityDTO(v)) - } - return roots, nil -} -func (s *AuthorityService) CreateAuthority(ctx context.Context, v *biz.Authority) error { - return s.uc.CreateAuthority(ctx, v) -} -func (s *AuthorityService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error { - return s.uc.CopyAuthority(ctx, sourceID, v) -} -func (s *AuthorityService) UpdateAuthority(ctx context.Context, v *biz.Authority) error { - return s.uc.UpdateAuthority(ctx, v) -} -func (s *AuthorityService) DeleteAuthority(ctx context.Context, id uint) error { - return s.uc.DeleteAuthority(ctx, id) -} -func authorityDomain(req *dto.AuthorityRequest) *biz.Authority { - return &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} -} -func authorityResponse(value *biz.Authority) *dto.AuthorityResponse { - return &dto.AuthorityResponse{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} -} -func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { - value := authorityDomain(req) - if err := s.CreateAuthority(ctx, value); err != nil { - return nil, err - } - return authorityResponse(value), nil -} -func (s *AuthorityService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) { - value := authorityDomain(&req.Authority) - if err := s.CopyAuthority(ctx, req.OldAuthorityID, value); err != nil { - return nil, err - } - return authorityResponse(value), nil -} -func (s *AuthorityService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { - value := authorityDomain(req) - if err := s.UpdateAuthority(ctx, value); err != nil { - return nil, err - } - return authorityResponse(value), nil -} -func (s *AuthorityService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { - return s.uc.SetAuthorityUsers(ctx, id, ids) -} -func (s *AuthorityService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.AuthorityUserIDs(ctx, id) -} -func (s *AuthorityService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error { - return s.uc.SetDataScope(ctx, id, scope, ids) -} -func (s *AuthorityService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.DataScopeDepartmentIDs(ctx, id) -} -func (s *AccessControlService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) { - return s.uc.ResolveDataScope(ctx, authorityID, userID) -} -func (s *AccessControlService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) { - scope, err := s.ResolveDataScope(ctx, authorityID, userID) - if err != nil { - return ctx, err - } - return biz.NewDataScopeContext(ctx, scope), nil -} - -func (s *PermissionService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) { - return s.uc.SelectedButtons(ctx, aid, menuID) -} -func (s *PermissionService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error { - return s.uc.SetSelectedButtons(ctx, aid, menuID, ids) -} -func (s *PermissionService) CanRemoveButton(ctx context.Context, id uint) (bool, error) { - return s.uc.CanRemoveButton(ctx, id) -} diff --git a/internal/service/access_control.go b/internal/service/access_control.go new file mode 100644 index 0000000..220aace --- /dev/null +++ b/internal/service/access_control.go @@ -0,0 +1,29 @@ +package service + +import ( + "context" + + "kra/internal/biz" +) + +type AccessControlService struct{ uc *biz.AccessControlUsecase } + +func NewAccessControlService(uc *biz.AccessControlUsecase) *AccessControlService { + return &AccessControlService{uc: uc} +} + +func (s *AccessControlService) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) { + return s.uc.Authorize(ctx, authorityID, path, method) +} + +func (s *AccessControlService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) { + return s.uc.ResolveDataScope(ctx, authorityID, userID) +} + +func (s *AccessControlService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) { + scope, err := s.ResolveDataScope(ctx, authorityID, userID) + if err != nil { + return ctx, err + } + return biz.NewDataScopeContext(ctx, scope), nil +} diff --git a/internal/service/announcement.go b/internal/service/announcement.go index 3a90e20..15d09f9 100644 --- a/internal/service/announcement.go +++ b/internal/service/announcement.go @@ -6,6 +6,7 @@ import ( "time" "kra/internal/biz" + "kra/internal/service/dto" ) type AnnouncementInput struct { @@ -22,15 +23,12 @@ func NewAnnouncementService(uc *biz.AnnouncementUsecase) *AnnouncementService { return &AnnouncementService{uc: uc} } -func announcementDTO(item *biz.Announcement) map[string]any { +func announcementDTO(item *biz.Announcement) *dto.AnnouncementResponse { attachments := any([]any{}) if len(item.Attachments) > 0 { _ = json.Unmarshal(item.Attachments, &attachments) } - return map[string]any{ - "ID": item.ID, "CreatedAt": item.CreatedAt, "UpdatedAt": item.UpdatedAt, "DeletedAt": nil, - "title": item.Title, "content": item.Content, "userID": item.UserID, "attachments": attachments, - } + return &dto.AnnouncementResponse{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, DeletedAt: nil, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: attachments} } func announcementDO(in AnnouncementInput) *biz.Announcement { @@ -53,7 +51,7 @@ func (s *AnnouncementService) DeleteByIDs(ctx context.Context, ids []uint) error return s.uc.DeleteByIDs(ctx, ids) } -func (s *AnnouncementService) Find(ctx context.Context, id uint) (map[string]any, error) { +func (s *AnnouncementService) Find(ctx context.Context, id uint) (*dto.AnnouncementResponse, error) { item, err := s.uc.Find(ctx, id) if err != nil { return nil, err @@ -61,26 +59,26 @@ func (s *AnnouncementService) Find(ctx context.Context, id uint) (map[string]any return announcementDTO(item), nil } -func (s *AnnouncementService) List(ctx context.Context, page, pageSize int, start, end *time.Time) ([]map[string]any, int64, error) { +func (s *AnnouncementService) List(ctx context.Context, page, pageSize int, start, end *time.Time) ([]*dto.AnnouncementResponse, int64, error) { items, total, err := s.uc.List(ctx, biz.AnnouncementFilter{Page: page, PageSize: pageSize, StartCreatedAt: start, EndCreatedAt: end}) if err != nil { return nil, 0, err } - result := make([]map[string]any, 0, len(items)) + result := make([]*dto.AnnouncementResponse, 0, len(items)) for _, item := range items { result = append(result, announcementDTO(item)) } return result, total, nil } -func (s *AnnouncementService) UserOptions(ctx context.Context) ([]map[string]any, error) { +func (s *AnnouncementService) UserOptions(ctx context.Context) ([]*dto.SelectOptionResponse, error) { items, err := s.uc.UserOptions(ctx) if err != nil { return nil, err } - result := make([]map[string]any, 0, len(items)) + result := make([]*dto.SelectOptionResponse, 0, len(items)) for _, item := range items { - result = append(result, map[string]any{"label": item.Label, "value": item.Value}) + result = append(result, &dto.SelectOptionResponse{Label: item.Label, Value: item.Value}) } return result, nil } diff --git a/internal/service/api.go b/internal/service/api.go index 8bdffc5..2a87b16 100644 --- a/internal/service/api.go +++ b/internal/service/api.go @@ -147,9 +147,6 @@ func (s *APIService) SyncAPIResponses(ctx context.Context, routes []dto.APIReque return s.SyncAPIs(ctx, items) } -func apiDTO(v *biz.API) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "path": v.Path, "description": v.Description, "apiGroup": v.APIGroup, "method": v.Method} -} func (s *APIService) DeleteAPIs(ctx context.Context, ids []uint) error { return s.uc.DeleteAPIs(ctx, ids) } diff --git a/internal/service/api_token.go b/internal/service/api_token.go index ab2b514..79d8b7d 100644 --- a/internal/service/api_token.go +++ b/internal/service/api_token.go @@ -5,6 +5,7 @@ import ( "time" "kra/internal/biz" + "kra/internal/service/dto" ) type TokenService struct { @@ -16,12 +17,12 @@ func NewTokenService(uc *biz.TokenUsecase, issuer biz.TokenIssuer) *TokenService return &TokenService{uc: uc, issuer: issuer} } -func tokenDTO(v *biz.APIToken) map[string]any { - var user any = nil +func tokenDTO(v *biz.APIToken) *dto.APITokenResponse { + var user *dto.UserResponse if v.User != nil { user = convertUser(v.User) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "userId": v.UserID, "authorityId": v.AuthorityID, "token": v.Token, "status": v.Status, "expiresAt": v.ExpiresAt, "remark": v.Remark, "user": user} + return &dto.APITokenResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark, User: user} } func (s *TokenService) CreateAPIToken(ctx context.Context, userID, authorityID uint, days int, remark string) (string, error) { @@ -39,12 +40,12 @@ func (s *TokenService) CreateAPIToken(ctx context.Context, userID, authorityID u } return issued.Value, nil } -func (s *TokenService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]map[string]any, int64, error) { +func (s *TokenService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*dto.APITokenResponse, int64, error) { items, total, err := s.uc.ListAPITokens(ctx, page, size, userID, status) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.APITokenResponse, 0, len(items)) for _, v := range items { out = append(out, tokenDTO(v)) } diff --git a/internal/service/audit.go b/internal/service/audit.go index f19ed27..41680a8 100644 --- a/internal/service/audit.go +++ b/internal/service/audit.go @@ -21,28 +21,28 @@ func (s *AuditRecorder) RecordOperation(ctx context.Context, v *biz.OperationRec func (s *AuditRecorder) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error { return s.RecordOperation(ctx, &biz.OperationRecord{IP: value.IP, Method: value.Method, Path: value.Path, Status: value.Status, LatencyMS: value.LatencyMS, Agent: value.Agent, ErrorMessage: value.ErrorMessage, Body: value.Body, Response: value.Response, UserID: value.UserID, RequestID: value.RequestID, TraceID: value.TraceID, DeviceID: value.DeviceID}) } -func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]map[string]any, int64, error) { +func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]*dto.OperationRecordResponse, int64, error) { return s.Operations(ctx, page, size, &biz.OperationRecord{Path: path, Method: method, Status: status}) } -func opDTO(v *biz.OperationRecord) map[string]any { - user := map[string]any{} +func opDTO(v *biz.OperationRecord) *dto.OperationRecordResponse { + var user any = map[string]any{} if v.User != nil { user = convertUser(v.User) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "ip": v.IP, "method": v.Method, "path": v.Path, "status": v.Status, "latency_ms": v.LatencyMS, "agent": v.Agent, "error_message": v.ErrorMessage, "body": v.Body, "resp": v.Response, "user_id": v.UserID, "request_id": v.RequestID, "trace_id": v.TraceID, "device_id": v.DeviceID, "user": user} + return &dto.OperationRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, DeletedAt: nil, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID, User: user} } -func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]map[string]any, int64, error) { +func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*dto.OperationRecordResponse, int64, error) { items, total, err := s.uc.ListOperations(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.OperationRecordResponse, 0, len(items)) for _, v := range items { out = append(out, opDTO(v)) } return out, total, nil } -func (s *AuditService) Operation(ctx context.Context, id uint) (map[string]any, error) { +func (s *AuditService) Operation(ctx context.Context, id uint) (*dto.OperationRecordResponse, error) { v, err := s.uc.FindOperation(ctx, id) if err != nil { return nil, err @@ -59,28 +59,28 @@ func (s *AuditRecorder) RecordLogin(ctx context.Context, v *biz.LoginLog) error func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error { return s.RecordLogin(ctx, &biz.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) ([]map[string]any, int64, error) { +func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]*dto.LoginLogResponse, int64, error) { return s.Logins(ctx, page, size, &biz.LoginLog{Username: username, Status: status, FilterByStatus: status}) } -func loginDTO(v *biz.LoginLog) map[string]any { - user := map[string]any{} +func loginDTO(v *biz.LoginLog) *dto.LoginLogResponse { + var user any = map[string]any{} if v.User != nil { user = convertUser(v.User) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "username": v.Username, "ip": v.IP, "status": v.Status, "errorMessage": v.ErrorMessage, "agent": v.Agent, "userId": v.UserID, "user": user} + return &dto.LoginLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, DeletedAt: nil, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID, User: user} } -func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]map[string]any, int64, error) { +func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*dto.LoginLogResponse, int64, error) { items, total, err := s.uc.ListLogins(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.LoginLogResponse, 0, len(items)) for _, v := range items { out = append(out, loginDTO(v)) } return out, total, nil } -func (s *AuditService) Login(ctx context.Context, id uint) (map[string]any, error) { +func (s *AuditService) Login(ctx context.Context, id uint) (*dto.LoginLogResponse, error) { v, err := s.uc.FindLogin(ctx, id) if err != nil { return nil, err @@ -97,18 +97,18 @@ func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *biz.DataAccessL func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error { return s.RecordDataAccess(ctx, &biz.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) ([]map[string]any, int64, error) { +func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]*dto.DataAccessLogResponse, int64, error) { return s.DataAccess(ctx, req.Page, req.PageSize, &biz.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable, UserID: req.UserID}) } -func dataAccessDTO(v *biz.DataAccessLog) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "eventType": v.EventType, "targetTable": v.TargetTable, "operation": v.Operation, "userId": v.UserID, "authorityId": v.AuthorityID, "scope": v.Scope, "requestId": v.RequestID, "method": v.Method, "path": v.Path, "detail": v.Detail} +func dataAccessDTO(v *biz.DataAccessLog) *dto.DataAccessLogResponse { + return &dto.DataAccessLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, DeletedAt: nil, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail} } -func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]map[string]any, int64, error) { +func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*dto.DataAccessLogResponse, int64, error) { items, total, err := s.uc.ListDataAccess(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.DataAccessLogResponse, 0, len(items)) for _, v := range items { out = append(out, dataAccessDTO(v)) } diff --git a/internal/service/audit_error.go b/internal/service/audit_error.go index f627061..2cd9087 100644 --- a/internal/service/audit_error.go +++ b/internal/service/audit_error.go @@ -8,8 +8,8 @@ import ( "kra/internal/service/dto" ) -func errorDTO(v *biz.ErrorRecord) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "form": v.Form, "info": v.Info, "level": v.Level, "request_id": v.RequestID, "trace_id": v.TraceID, "solution": v.Solution, "status": v.Status} +func errorDTO(v *biz.ErrorRecord) *dto.ErrorRecordResponse { + return &dto.ErrorRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, DeletedAt: nil, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status} } func (s *AuditRecorder) CreateError(ctx context.Context, v *biz.ErrorRecord) error { return s.uc.CreateError(ctx, v) @@ -23,7 +23,7 @@ func (s *AuditRecorder) CreateErrorRequest(ctx context.Context, req *dto.ErrorRe func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error { return s.UpdateError(ctx, errorDomain(req)) } -func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, info string, createdAtRange []time.Time) ([]map[string]any, int64, error) { +func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, info string, createdAtRange []time.Time) ([]*dto.ErrorRecordResponse, int64, error) { return s.Errors(ctx, page, size, &biz.ErrorRecord{Form: form, Info: info, CreatedAtRange: createdAtRange}) } func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) error { @@ -32,19 +32,19 @@ func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) erro func (s *AuditService) DeleteErrors(ctx context.Context, ids []uint) error { return s.uc.DeleteErrors(ctx, ids) } -func (s *AuditService) Error(ctx context.Context, id uint) (map[string]any, error) { +func (s *AuditService) Error(ctx context.Context, id uint) (*dto.ErrorRecordResponse, error) { v, err := s.uc.FindError(ctx, id) if err != nil { return nil, err } return errorDTO(v), nil } -func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]map[string]any, int64, error) { +func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*dto.ErrorRecordResponse, int64, error) { items, total, err := s.uc.ListErrors(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.ErrorRecordResponse, 0, len(items)) for _, v := range items { out = append(out, errorDTO(v)) } diff --git a/internal/service/audit_log_file.go b/internal/service/audit_log_file.go index 3b07ef2..be58959 100644 --- a/internal/service/audit_log_file.go +++ b/internal/service/audit_log_file.go @@ -4,6 +4,7 @@ import ( "context" "kra/internal/biz" + "kra/internal/service/dto" ) type LogViewerService struct{ uc *biz.LogViewerUsecase } @@ -12,32 +13,32 @@ func NewLogViewerService(uc *biz.LogViewerUsecase) *LogViewerService { return &LogViewerService{uc: uc} } -func (s *LogViewerService) LogDates(ctx context.Context, month string) (map[string]any, error) { +func (s *LogViewerService) LogDates(ctx context.Context, month string) (*dto.LogDatesResponse, error) { items, err := s.uc.LogDates(ctx, month) if err != nil { return nil, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.LogDateResponse, 0, len(items)) for _, v := range items { - out = append(out, map[string]any{"date": v.Date, "fileCount": v.FileCount}) + out = append(out, &dto.LogDateResponse{Date: v.Date, FileCount: v.FileCount}) } - return map[string]any{"month": month, "dates": out}, nil + return &dto.LogDatesResponse{Month: month, Dates: out}, nil } -func (s *LogViewerService) LogFiles(ctx context.Context, date string) (map[string]any, error) { +func (s *LogViewerService) LogFiles(ctx context.Context, date string) (*dto.LogFilesResponse, error) { items, err := s.uc.LogFiles(ctx, date) if err != nil { return nil, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.LogFileResponse, 0, len(items)) for _, v := range items { - out = append(out, map[string]any{"path": v.Path, "name": v.Name, "size": v.Size, "modifiedAt": v.ModifiedAt}) + out = append(out, &dto.LogFileResponse{Path: v.Path, Name: v.Name, Size: v.Size, ModifiedAt: v.ModifiedAt}) } - return map[string]any{"date": date, "files": out}, nil + return &dto.LogFilesResponse{Date: date, Files: out}, nil } -func (s *LogViewerService) LogContent(ctx context.Context, date, path string, cursor *int64) (map[string]any, error) { +func (s *LogViewerService) LogContent(ctx context.Context, date, path string, cursor *int64) (*dto.LogContentResponse, error) { v, err := s.uc.LogContent(ctx, date, path, cursor) if err != nil { return nil, err } - return map[string]any{"date": v.Date, "path": v.Path, "content": v.Content, "lineCount": v.LineCount, "nextCursor": v.NextCursor, "hasMore": v.HasMore, "limitedByBytes": v.LimitedByBytes, "size": v.Size, "modifiedAt": v.ModifiedAt}, nil + return &dto.LogContentResponse{Date: v.Date, Path: v.Path, Content: v.Content, LineCount: v.LineCount, NextCursor: v.NextCursor, HasMore: v.HasMore, LimitedByBytes: v.LimitedByBytes, Size: v.Size, ModifiedAt: v.ModifiedAt}, nil } diff --git a/internal/service/authentication.go b/internal/service/authentication.go index 93c6fb5..659a94a 100644 --- a/internal/service/authentication.go +++ b/internal/service/authentication.go @@ -8,10 +8,10 @@ import ( ) type LoginResult struct { - User map[string]any `json:"user"` - Token string `json:"token"` - ExpiresAt int64 `json:"expiresAt"` - NeedChangePassword bool `json:"needChangePassword"` + User *dto.UserResponse `json:"user"` + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` + NeedChangePassword bool `json:"needChangePassword"` } type AuthService struct { @@ -41,3 +41,7 @@ func (s *AuthService) SwitchAuthority(ctx context.Context, id, authorityID uint) } return loginResult(value), nil } + +func (s *AuthService) AuthenticateToken(ctx context.Context, token string) (*biz.TokenAuthentication, error) { + return s.uc.AuthenticateToken(ctx, token) +} diff --git a/internal/service/authority.go b/internal/service/authority.go new file mode 100644 index 0000000..439b833 --- /dev/null +++ b/internal/service/authority.go @@ -0,0 +1,88 @@ +package service + +import ( + "context" + + "kra/internal/biz" + "kra/internal/service/dto" +) + +type AuthorityService struct{ uc *biz.AuthorityUsecase } + +func NewAuthorityService(uc *biz.AuthorityUsecase) *AuthorityService { + return &AuthorityService{uc: uc} +} + +func authorityDTO(value *biz.Authority) *dto.AuthorityResponse { + out := convertAuthority(*value) + children := make([]*dto.AuthorityResponse, 0, len(value.Children)) + for _, child := range value.Children { + children = append(children, authorityDTO(child)) + } + out.Children = children + return out +} + +func authorityDomain(req *dto.AuthorityRequest) *biz.Authority { + return &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} +} + +func authorityResponse(value *biz.Authority) *dto.AuthorityResponse { + return &dto.AuthorityResponse{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} +} + +func (s *AuthorityService) Authorities(ctx context.Context) ([]*dto.AuthorityResponse, error) { + items, err := s.uc.AuthorityTree(ctx) + if err != nil { + return nil, err + } + roots := make([]*dto.AuthorityResponse, 0, len(items)) + for _, value := range items { + roots = append(roots, authorityDTO(value)) + } + return roots, nil +} + +func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { + value := authorityDomain(req) + if err := s.uc.CreateAuthority(ctx, value); err != nil { + return nil, err + } + return authorityResponse(value), nil +} + +func (s *AuthorityService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) { + value := authorityDomain(&req.Authority) + if err := s.uc.CopyAuthority(ctx, req.OldAuthorityID, value); err != nil { + return nil, err + } + return authorityResponse(value), nil +} + +func (s *AuthorityService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { + value := authorityDomain(req) + if err := s.uc.UpdateAuthority(ctx, value); err != nil { + return nil, err + } + return authorityResponse(value), nil +} + +func (s *AuthorityService) DeleteAuthority(ctx context.Context, id uint) error { + return s.uc.DeleteAuthority(ctx, id) +} + +func (s *AuthorityService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { + return s.uc.SetAuthorityUsers(ctx, id, ids) +} + +func (s *AuthorityService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) { + return s.uc.AuthorityUserIDs(ctx, id) +} + +func (s *AuthorityService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error { + return s.uc.SetDataScope(ctx, id, scope, ids) +} + +func (s *AuthorityService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) { + return s.uc.DataScopeDepartmentIDs(ctx, id) +} diff --git a/internal/service/dictionary.go b/internal/service/dictionary.go index 3ceb274..d6e293b 100644 --- a/internal/service/dictionary.go +++ b/internal/service/dictionary.go @@ -20,7 +20,7 @@ func dictionaryDomain(value *dto.DictionaryRequest) *biz.Dictionary { } return &biz.Dictionary{ID: value.ID, Name: value.Name, Type: value.Type, Status: status, Desc: value.Description, ParentID: value.ParentID} } -func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) (map[string]any, error) { +func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) (*dto.DictionaryResponse, error) { value := dictionaryDomain(req) if err := s.CreateDictionary(ctx, value); err != nil { return nil, err @@ -30,41 +30,51 @@ func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *dt func (s *DictionaryService) UpdateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error { return s.UpdateDictionary(ctx, dictionaryDomain(req)) } -func dictionaryDTO(v *biz.Dictionary) map[string]any { - children := make([]map[string]any, 0, len(v.Children)) +func dictionaryDTO(v *biz.Dictionary) *dto.DictionaryResponse { + children := make([]*dto.DictionaryResponse, 0, len(v.Children)) for _, x := range v.Children { children = append(children, dictionaryDTO(x)) } - details := make([]map[string]any, 0, len(v.Details)) + details := make([]*dto.DictionaryDetailResponse, 0, len(v.Details)) for _, x := range v.Details { details = append(details, detailDTO(x)) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parentID": v.ParentID, "children": children, "sysDictionaryDetails": details} + return &dto.DictionaryResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID, Children: children, SysDictionaryDetails: details} } -func (s *DictionaryService) Dictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]map[string]any, int64, error) { +func (s *DictionaryService) Dictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*dto.DictionaryResponse, int64, error) { items, total, err := s.uc.ListDictionaries(ctx, page, size, name, typ, details) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.DictionaryResponse, 0, len(items)) for _, v := range items { out = append(out, dictionaryDTO(v)) } return out, total, nil } -func (s *DictionaryService) Dictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (map[string]any, error) { +func (s *DictionaryService) Dictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*dto.DictionaryResponse, error) { v, err := s.uc.FindDictionary(ctx, id, typ, status, details) if err != nil { return nil, err } return dictionaryDTO(v), nil } -func (s *DictionaryService) ExportDictionary(ctx context.Context, id uint) (map[string]any, error) { +func (s *DictionaryService) ExportDictionary(ctx context.Context, id uint) (*dto.DictionaryExportResponse, error) { v, err := s.uc.ExportDictionary(ctx, id) if err != nil { return nil, err } - return dictionaryDTO(v), nil + details := make([]*dto.DictionaryDetailExportResponse, 0, len(v.Details)) + for _, detail := range v.Details { + details = append(details, &dto.DictionaryDetailExportResponse{ + Label: detail.Label, Value: detail.Value, Extend: detail.Extend, + Status: detail.Status, Sort: detail.Sort, Level: detail.Level, Path: detail.Path, + }) + } + return &dto.DictionaryExportResponse{ + Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, + SysDictionaryDetails: details, + }, nil } func (s *DictionaryService) CreateDictionary(ctx context.Context, v *biz.Dictionary) error { return s.uc.CreateDictionary(ctx, v) @@ -89,37 +99,37 @@ func (s *DictionaryService) CreateDictionaryDetailRequest(ctx context.Context, r func (s *DictionaryService) UpdateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error { return s.UpdateDictionaryDetail(ctx, detailDomain(req)) } -func detailDTO(v *biz.DictionaryDetail) map[string]any { - children := make([]map[string]any, 0, len(v.Children)) +func detailDTO(v *biz.DictionaryDetail) *dto.DictionaryDetailResponse { + children := make([]*dto.DictionaryDetailResponse, 0, len(v.Children)) for _, x := range v.Children { children = append(children, detailDTO(x)) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sysDictionaryID": v.DictionaryID, "parentID": v.ParentID, "level": v.Level, "path": v.Path, "disabled": !v.Status, "children": children} + return &dto.DictionaryDetailResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID, Level: v.Level, Path: v.Path, Disabled: !v.Status, Children: children} } -func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]map[string]any, int64, error) { +func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*dto.DictionaryDetailResponse, int64, error) { items, total, err := s.uc.ListDictionaryDetails(ctx, page, size, filter) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.DictionaryDetailResponse, 0, len(items)) for _, v := range items { out = append(out, detailDTO(v)) } return out, total, nil } -func (s *DictionaryService) DictionaryDetail(ctx context.Context, id uint) (map[string]any, error) { +func (s *DictionaryService) DictionaryDetail(ctx context.Context, id uint) (*dto.DictionaryDetailResponse, error) { v, err := s.uc.FindDictionaryDetail(ctx, id) if err != nil { return nil, err } return detailDTO(v), nil } -func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ string) ([]map[string]any, error) { +func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ string) ([]*dto.DictionaryDetailResponse, error) { items, err := s.uc.DictionaryDetailTree(ctx, id, typ) if err != nil { return nil, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.DictionaryDetailResponse, 0, len(items)) for _, v := range items { out = append(out, detailDTO(v)) } diff --git a/internal/service/dictionary_import.go b/internal/service/dictionary_import.go index f7d5415..9cc3a39 100644 --- a/internal/service/dictionary_import.go +++ b/internal/service/dictionary_import.go @@ -27,12 +27,12 @@ func (s *DictionaryService) ImportDictionaryJSON(ctx context.Context, raw string } return s.uc.ImportDictionary(ctx, dictionary, details) } -func (s *DictionaryService) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]map[string]any, error) { +func (s *DictionaryService) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*dto.DictionaryDetailResponse, error) { items, err := s.uc.DictionaryDetailsByParent(ctx, dictionaryID, parentID, includeChildren) if err != nil { return nil, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.DictionaryDetailResponse, 0, len(items)) for _, item := range items { out = append(out, detailDTO(item)) } diff --git a/internal/service/dto/announcement.go b/internal/service/dto/announcement.go index 735d931..dd1c574 100644 --- a/internal/service/dto/announcement.go +++ b/internal/service/dto/announcement.go @@ -1,6 +1,9 @@ package dto -import "encoding/json" +import ( + "encoding/json" + "time" +) type AnnouncementRequest struct { ID uint `json:"ID"` @@ -9,3 +12,19 @@ type AnnouncementRequest struct { UserID *uint `json:"userID"` Attachments json.RawMessage `json:"attachments"` } + +type AnnouncementResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Title string `json:"title"` + Content string `json:"content"` + UserID *uint `json:"userID"` + Attachments any `json:"attachments"` +} + +type SelectOptionResponse struct { + Label string `json:"label"` + Value uint `json:"value"` +} diff --git a/internal/service/dto/audit.go b/internal/service/dto/audit.go index c9d719a..c43da37 100644 --- a/internal/service/dto/audit.go +++ b/internal/service/dto/audit.go @@ -1,5 +1,7 @@ package dto +import "time" + type OperationRecordRequest struct { IP string Method string @@ -44,3 +46,99 @@ type ErrorRecordRequest struct { Solution string `json:"solution"` Status string `json:"status"` } + +type OperationRecordResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + DeletedAt any `json:"DeletedAt"` + IP string `json:"ip"` + Method string `json:"method"` + Path string `json:"path"` + Status int `json:"status"` + LatencyMS int64 `json:"latency_ms"` + Agent string `json:"agent"` + ErrorMessage string `json:"error_message"` + Body string `json:"body"` + Response string `json:"resp"` + UserID uint `json:"user_id"` + RequestID string `json:"request_id"` + TraceID string `json:"trace_id"` + DeviceID string `json:"device_id"` + User any `json:"user"` +} + +type LoginLogResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + DeletedAt any `json:"DeletedAt"` + Username string `json:"username"` + IP string `json:"ip"` + Status bool `json:"status"` + ErrorMessage string `json:"errorMessage"` + Agent string `json:"agent"` + UserID uint `json:"userId"` + User any `json:"user"` +} + +type DataAccessLogResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + DeletedAt any `json:"DeletedAt"` + EventType string `json:"eventType"` + TargetTable string `json:"targetTable"` + Operation string `json:"operation"` + UserID uint `json:"userId"` + AuthorityID uint `json:"authorityId"` + Scope int `json:"scope"` + RequestID string `json:"requestId"` + Method string `json:"method"` + Path string `json:"path"` + Detail string `json:"detail"` +} + +type ErrorRecordResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + DeletedAt any `json:"DeletedAt"` + Form string `json:"form"` + Info string `json:"info"` + Level string `json:"level"` + RequestID string `json:"request_id"` + TraceID string `json:"trace_id"` + Solution string `json:"solution"` + Status string `json:"status"` +} + +type LogDateResponse struct { + Date string `json:"date"` + FileCount int `json:"fileCount"` +} + +type LogDatesResponse struct { + Month string `json:"month"` + Dates []*LogDateResponse `json:"dates"` +} + +type LogFileResponse struct { + Path string `json:"path"` + Name string `json:"name"` + Size int64 `json:"size"` + ModifiedAt time.Time `json:"modifiedAt"` +} + +type LogFilesResponse struct { + Date string `json:"date"` + Files []*LogFileResponse `json:"files"` +} + +type LogContentResponse struct { + Date string `json:"date"` + Path string `json:"path"` + Content string `json:"content"` + LineCount int `json:"lineCount"` + NextCursor int64 `json:"nextCursor"` + HasMore bool `json:"hasMore"` + LimitedByBytes bool `json:"limitedByBytes"` + Size int64 `json:"size"` + ModifiedAt time.Time `json:"modifiedAt"` +} diff --git a/internal/service/dto/authority.go b/internal/service/dto/authority.go index 41ee8ad..d65a2ee 100644 --- a/internal/service/dto/authority.go +++ b/internal/service/dto/authority.go @@ -27,9 +27,11 @@ type SetDataScopeRequest struct { } type AuthorityResponse struct { - AuthorityID uint `json:"authorityId"` - AuthorityName string `json:"authorityName"` - ParentID *uint `json:"parentId"` - DataScope int `json:"dataScope"` - DefaultRouter string `json:"defaultRouter"` + AuthorityID uint `json:"authorityId"` + AuthorityName string `json:"authorityName"` + ParentID *uint `json:"parentId"` + Children []*AuthorityResponse `json:"children"` + Menus []any `json:"menus"` + DataScope int `json:"dataScope"` + DefaultRouter string `json:"defaultRouter"` } diff --git a/internal/service/dto/export.go b/internal/service/dto/export.go index 5b28e51..273d396 100644 --- a/internal/service/dto/export.go +++ b/internal/service/dto/export.go @@ -1,5 +1,7 @@ package dto +import "time" + type ExportConditionRequest struct { From string `json:"from"` Column string `json:"column"` @@ -24,3 +26,33 @@ type ExportTemplateRequest struct { Conditions []ExportConditionRequest `json:"conditions"` Joins []ExportJoinRequest `json:"joinTemplate"` } + +type ExportConditionResponse struct { + From string `json:"from"` + Column string `json:"column"` + Operator string `json:"operator"` +} + +type ExportJoinResponse struct { + Join string `json:"joins"` + Table string `json:"table"` + On string `json:"on"` +} + +type ExportTemplateResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + DBName string `json:"dbName"` + Name string `json:"name"` + TableName string `json:"tableName"` + TemplateID string `json:"templateID"` + TemplateInfo string `json:"templateInfo"` + SQL string `json:"sql"` + ImportSQL string `json:"importSql"` + Limit *int `json:"limit"` + Order string `json:"order"` + Conditions []ExportConditionResponse `json:"conditions"` + Joins []ExportJoinResponse `json:"joinTemplate"` +} diff --git a/internal/service/dto/media.go b/internal/service/dto/media.go index 688f940..74e3d3a 100644 --- a/internal/service/dto/media.go +++ b/internal/service/dto/media.go @@ -2,6 +2,46 @@ package dto import "time" +type MediaResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + ClassID int `json:"classId"` + URL string `json:"url"` + Tag string `json:"tag"` + Key string `json:"key"` + Size int64 `json:"size"` + Mime string `json:"mime"` + MD5 string `json:"md5"` + UserID uint `json:"userId"` +} + +type MediaCategoryResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + PID uint `json:"pid"` + Children []*MediaCategoryResponse `json:"children"` +} + +type StorageObjectResponse struct { + Name string `json:"name"` + Key string `json:"key"` + URL string `json:"url"` + Size int64 `json:"size"` +} + +type InitUploadResponse struct { + Instant bool `json:"instant"` + UploadID uint `json:"uploadId,omitempty"` + UploadedChunks []int `json:"uploadedChunks"` + Media *MediaResponse `json:"media"` +} + type MediaListRequest struct { Page int `json:"page"` PageSize int `json:"pageSize"` diff --git a/internal/service/dto/settings.go b/internal/service/dto/settings.go index 6e9a7bf..8efaa20 100644 --- a/internal/service/dto/settings.go +++ b/internal/service/dto/settings.go @@ -1,5 +1,7 @@ package dto +import "time" + type DictionaryRequest struct { ID uint `json:"ID" form:"ID"` Name string `json:"name" form:"name"` @@ -47,3 +49,101 @@ type APITokenListRequest struct { UserID uint `json:"userId"` Status *bool `json:"status"` } + +type APITokenResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + UserID uint `json:"userId"` + AuthorityID uint `json:"authorityId"` + Token string `json:"token"` + Status bool `json:"status"` + ExpiresAt time.Time `json:"expiresAt"` + Remark string `json:"remark"` + User *UserResponse `json:"user"` +} + +type SecurityConfigResponse struct { + ID uint `json:"ID"` + CaptchaOpen int `json:"captchaOpen"` + CaptchaTimeout int `json:"captchaTimeout"` + KeyLong int `json:"keyLong"` + ImgWidth int `json:"imgWidth"` + ImgHeight int `json:"imgHeight"` + PwdMinLength int `json:"pwdMinLength"` + PwdRequireUpper bool `json:"pwdRequireUpper"` + PwdRequireLower bool `json:"pwdRequireLower"` + PwdRequireDigit bool `json:"pwdRequireDigit"` + PwdRequireSpecial bool `json:"pwdRequireSpecial"` + LimitEnable bool `json:"limitEnable"` + LimitWindow int `json:"limitWindow"` + LimitCount int `json:"limitCount"` + LockEnable bool `json:"lockEnable"` + LockThreshold int `json:"lockThreshold"` + LockDuration int `json:"lockDuration"` + PwdExpireEnable bool `json:"pwdExpireEnable"` + PwdExpireDays int `json:"pwdExpireDays"` + ForceNewUserChangePassword bool `json:"forceNewUserChangePassword"` +} + +type SystemParameterResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + Key string `json:"key"` + Value string `json:"value"` + Desc string `json:"desc"` +} + +type DictionaryResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + Type string `json:"type"` + Status bool `json:"status"` + Desc string `json:"desc"` + ParentID *uint `json:"parentID"` + Children []*DictionaryResponse `json:"children"` + SysDictionaryDetails []*DictionaryDetailResponse `json:"sysDictionaryDetails"` +} + +type DictionaryDetailResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Label string `json:"label"` + Value string `json:"value"` + Extend string `json:"extend"` + Status bool `json:"status"` + Sort int `json:"sort"` + DictionaryID uint `json:"sysDictionaryID"` + ParentID *uint `json:"parentID"` + Level int `json:"level"` + Path string `json:"path"` + Disabled bool `json:"disabled"` + Children []*DictionaryDetailResponse `json:"children"` +} + +type DictionaryExportResponse struct { + Name string `json:"name"` + Type string `json:"type"` + Status bool `json:"status"` + Desc string `json:"desc"` + SysDictionaryDetails []*DictionaryDetailExportResponse `json:"sysDictionaryDetails"` +} + +type DictionaryDetailExportResponse struct { + Label string `json:"label"` + Value string `json:"value"` + Extend string `json:"extend"` + Status bool `json:"status"` + Sort int `json:"sort"` + Level int `json:"level"` + Path string `json:"path"` +} diff --git a/internal/service/dto/system.go b/internal/service/dto/system.go index a7bd8ab..d79b29b 100644 --- a/internal/service/dto/system.go +++ b/internal/service/dto/system.go @@ -1,5 +1,7 @@ package dto +import "time" + type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` @@ -71,3 +73,60 @@ type LoginLogRequest struct { Agent string UserID uint } + +type UserResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + UUID string `json:"uuid"` + Username string `json:"userName"` + NickName string `json:"nickName"` + HeaderImg string `json:"headerImg"` + AuthorityID uint `json:"authorityId"` + Authority *AuthorityResponse `json:"authority"` + Authorities []*AuthorityResponse `json:"authorities"` + DeptID uint `json:"deptId"` + Department any `json:"dept"` + Departments []*DepartmentResponse `json:"departments"` + Positions []*PositionResponse `json:"positions"` + Phone string `json:"phone"` + Email string `json:"email"` + Enable int `json:"enable"` + OriginSetting map[string]any `json:"originSetting"` +} + +type ServerInfoResponse struct { + OS ServerOSResponse `json:"os"` + CPU ServerCPUResponse `json:"cpu"` + RAM ServerRAMResponse `json:"ram"` + Disk []ServerDiskResponse `json:"disk"` +} + +type ServerOSResponse struct { + GOOS string `json:"goos"` + NumCPU int `json:"numCpu"` + Compiler string `json:"compiler"` + GoVersion string `json:"goVersion"` + NumGoroutine int `json:"numGoroutine"` +} + +type ServerCPUResponse struct { + CPUs []float64 `json:"cpus"` + Cores int `json:"cores"` +} + +type ServerRAMResponse struct { + UsedMB uint64 `json:"usedMb"` + TotalMB uint64 `json:"totalMb"` + UsedPercent int `json:"usedPercent"` +} + +type ServerDiskResponse struct { + MountPoint string `json:"mountPoint"` + UsedMB uint64 `json:"usedMb"` + UsedGB uint64 `json:"usedGb"` + TotalMB uint64 `json:"totalMb"` + TotalGB uint64 `json:"totalGb"` + UsedPercent int `json:"usedPercent"` +} diff --git a/internal/service/dto/system_config.go b/internal/service/dto/system_config.go index f161206..64da372 100644 --- a/internal/service/dto/system_config.go +++ b/internal/service/dto/system_config.go @@ -1,6 +1,6 @@ package dto -import "kra/internal/conf" +import "encoding/json" type SecurityConfigRequest struct { ID uint `json:"ID"` @@ -26,84 +26,5 @@ type SecurityConfigRequest struct { } type SetSystemConfigRequest struct { - Config struct { - Data *struct { - Database *struct { - Driver string `json:"driver"` - Source string `json:"source"` - Host string `json:"host"` - Port string `json:"port"` - User string `json:"user"` - Password string `json:"password"` - Name string `json:"name"` - Config string `json:"config"` - Path string `json:"path"` - Prefix string `json:"prefix"` - Engine string `json:"engine"` - LogMode string `json:"log_mode"` - MaxIdleConns int32 `json:"max_idle_conns"` - MaxOpenConns int32 `json:"max_open_conns"` - ConnMaxLifetime int32 `json:"conn_max_lifetime"` - Singular bool `json:"singular"` - } `json:"database"` - Redis *struct { - Network string `json:"network"` - Addr string `json:"addr"` - ReadTimeout string `json:"read_timeout"` - WriteTimeout string `json:"write_timeout"` - Name string `json:"name"` - Password string `json:"password"` - DB int32 `json:"db"` - UseCluster bool `json:"use_cluster"` - ClusterAddrs []string `json:"cluster_addrs"` - } `json:"redis"` - DatabaseList []*conf.Data_Database `json:"database_list"` - RedisList []*conf.Data_Redis `json:"redis_list"` - Mongo *conf.Data_Mongo `json:"mongo"` - } `json:"data"` - Admin struct { - RouterPrefix string `json:"routerPrefix"` - System struct { - UseRedis bool `json:"useRedis"` - UseMultipoint bool `json:"useMultipoint"` - UseStrictAuth bool `json:"useStrictAuth"` - DisableAutoMigrate bool `json:"disableAutoMigrate"` - UseMongo bool `json:"useMongo"` - } `json:"system"` - JWT struct { - SigningKey string `json:"signingKey"` - ExpiresTime string `json:"expiresTime"` - BufferTime string `json:"bufferTime"` - Issuer string `json:"issuer"` - } `json:"jwt"` - Captcha struct { - KeyLong int32 `json:"keyLong"` - ImgWidth int32 `json:"imgWidth"` - ImgHeight int32 `json:"imgHeight"` - StoreExpiration string `json:"storeExpiration"` - } `json:"captcha"` - Local struct { - StorePath string `json:"storePath"` - PathPrefix string `json:"pathPrefix"` - } `json:"local"` - Media struct { - SessionTTL int32 `json:"sessionTtl"` - MaxFileSize int64 `json:"maxFileSize"` - } `json:"media"` - Storage *conf.AdminBackend_Storage `json:"storage"` - Zap *conf.AdminBackend_Zap `json:"zap"` - Cors *conf.AdminBackend_CORS `json:"cors"` - App *conf.AdminBackend_App `json:"app"` - } `json:"admin"` - Email *struct { - To string `json:"to"` - From string `json:"from"` - Host string `json:"host"` - Secret string `json:"secret"` - Nickname string `json:"nickname"` - Port int32 `json:"port"` - IsSSL bool `json:"is-ssl"` - IsLoginAuth bool `json:"is-loginauth"` - } `json:"email"` - } `json:"config"` + Config json.RawMessage `json:"config"` } diff --git a/internal/service/dto/task.go b/internal/service/dto/task.go index 077d6a3..2a3b9d2 100644 --- a/internal/service/dto/task.go +++ b/internal/service/dto/task.go @@ -1,6 +1,9 @@ package dto -import "encoding/json" +import ( + "encoding/json" + "time" +) type TaskRequest struct { ID uint `json:"ID"` @@ -26,3 +29,44 @@ type ToggleTaskRequest struct { ID uint `json:"ID"` Enabled bool `json:"enabled"` } + +type TaskResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + Description string `json:"description"` + Spec string `json:"spec"` + WithSeconds bool `json:"withSeconds"` + ExecutorType string `json:"executorType"` + MethodName string `json:"methodName"` + Params json.RawMessage `json:"params"` + HTTPURL string `json:"httpUrl"` + HTTPMethod string `json:"httpMethod"` + HTTPHeader json.RawMessage `json:"httpHeader"` + HTTPBody string `json:"httpBody"` + HTTPAllowPrivate bool `json:"httpAllowPrivate"` + Enabled bool `json:"enabled"` + NextRunAt *time.Time `json:"nextRunAt"` +} + +type TaskLogResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + DeletedAt any `json:"DeletedAt"` + TaskID uint `json:"taskId"` + TaskName string `json:"taskName"` + TriggerType string `json:"triggerType"` + StartedAt time.Time `json:"startedAt"` + FinishedAt time.Time `json:"finishedAt"` + DurationMS int64 `json:"durationMs"` + Status string `json:"status"` + ErrorMsg string `json:"errorMsg"` + Output string `json:"output"` +} + +type TaskMethodResponse struct { + Name string `json:"name"` + Description string `json:"description"` +} diff --git a/internal/service/dto/version.go b/internal/service/dto/version.go index e5e6fb2..cf4d4f1 100644 --- a/internal/service/dto/version.go +++ b/internal/service/dto/version.go @@ -1,5 +1,18 @@ package dto +import "time" + +type VersionResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + VersionName string `json:"versionName"` + VersionCode string `json:"versionCode"` + Description string `json:"description"` + VersionData string `json:"versionData"` +} + type VersionMenuMetaRequest struct { ActiveName string `json:"activeName"` KeepAlive bool `json:"keepAlive"` diff --git a/internal/service/export.go b/internal/service/export.go index cfdf26d..fedce5d 100644 --- a/internal/service/export.go +++ b/internal/service/export.go @@ -83,19 +83,19 @@ func (s *ExportService) CreateRequest(ctx context.Context, req *dto.ExportTempla func (s *ExportService) UpdateRequest(ctx context.Context, req *dto.ExportTemplateRequest) error { return s.Update(ctx, exportTemplateDomain(req)) } -func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]map[string]any, int64, error) { +func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]*dto.ExportTemplateResponse, int64, error) { return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end}) } -func exportDTO(v *biz.ExportTemplate) map[string]any { - conditions := make([]map[string]any, 0, len(v.Conditions)) +func exportDTO(v *biz.ExportTemplate) *dto.ExportTemplateResponse { + conditions := make([]dto.ExportConditionResponse, 0, len(v.Conditions)) for _, x := range v.Conditions { - conditions = append(conditions, map[string]any{"from": x.From, "column": x.Column, "operator": x.Operator}) + conditions = append(conditions, dto.ExportConditionResponse{From: x.From, Column: x.Column, Operator: x.Operator}) } - joins := make([]map[string]any, 0, len(v.Joins)) + joins := make([]dto.ExportJoinResponse, 0, len(v.Joins)) for _, x := range v.Joins { - joins = append(joins, map[string]any{"joins": x.Join, "table": x.Table, "on": x.On}) + joins = append(joins, dto.ExportJoinResponse{Join: x.Join, Table: x.Table, On: x.On}) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "dbName": v.DBName, "name": v.Name, "tableName": v.TableName, "templateID": v.TemplateID, "templateInfo": v.TemplateInfo, "sql": v.SQL, "importSql": v.ImportSQL, "limit": v.Limit, "order": v.Order, "conditions": conditions, "joinTemplate": joins} + return &dto.ExportTemplateResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order, Conditions: conditions, Joins: joins} } func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error { return s.uc.CreateExportTemplate(ctx, v) @@ -106,19 +106,19 @@ func (s *ExportService) Update(ctx context.Context, v *biz.ExportTemplate) error func (s *ExportService) Delete(ctx context.Context, ids []uint) error { return s.uc.DeleteExportTemplates(ctx, ids) } -func (s *ExportService) Template(ctx context.Context, id uint, tid string) (map[string]any, error) { +func (s *ExportService) Template(ctx context.Context, id uint, tid string) (*dto.ExportTemplateResponse, error) { v, err := s.uc.FindExportTemplate(ctx, id, tid) if err != nil { return nil, err } return exportDTO(v), nil } -func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]map[string]any, int64, error) { +func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*dto.ExportTemplateResponse, int64, error) { items, total, err := s.uc.ListExportTemplates(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.ExportTemplateResponse, 0, len(items)) for _, v := range items { out = append(out, exportDTO(v)) } diff --git a/internal/service/media.go b/internal/service/media.go index 73a7d92..0e83102 100644 --- a/internal/service/media.go +++ b/internal/service/media.go @@ -19,36 +19,36 @@ func NewMediaService(uc *biz.MediaUsecase, settings biz.RuntimeSettings) *MediaS func (s *MediaService) MediaConfig() biz.MediaSettings { return s.settings.MediaSettings() } -func mediaDTO(v *biz.MediaFile) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "classId": v.CategoryID, "url": v.URL, "tag": v.Tag, "key": v.Key, "size": v.Size, "mime": v.Mime, "md5": v.MD5, "userId": v.UserID} +func mediaDTO(v *biz.MediaFile) *dto.MediaResponse { + return &dto.MediaResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, ClassID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} } -func categoryDTO(v *biz.AttachmentCategory) map[string]any { - children := make([]map[string]any, 0, len(v.Children)) +func categoryDTO(v *biz.AttachmentCategory) *dto.MediaCategoryResponse { + children := make([]*dto.MediaCategoryResponse, 0, len(v.Children)) for _, x := range v.Children { children = append(children, categoryDTO(x)) } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "pid": v.ParentID, "children": children} + return &dto.MediaCategoryResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, PID: v.ParentID, Children: children} } -func (s *MediaService) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (map[string]any, error) { +func (s *MediaService) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (*dto.MediaResponse, error) { v, err := s.uc.Upload(ctx, userID, name, mime, categoryID, reader, save) if err != nil { return nil, err } return mediaDTO(v), nil } -func (s *MediaService) Media(ctx context.Context, id uint) (map[string]any, error) { +func (s *MediaService) Media(ctx context.Context, id uint) (*dto.MediaResponse, error) { v, err := s.uc.FindMedia(ctx, id) if err != nil { return nil, err } return mediaDTO(v), nil } -func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([]map[string]any, int64, error) { +func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([]*dto.MediaResponse, int64, error) { items, total, err := s.uc.ListMedia(ctx, filter) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.MediaResponse, 0, len(items)) for _, v := range items { out = append(out, mediaDTO(v)) } @@ -68,23 +68,23 @@ func (s *MediaService) ImportURLRequests(ctx context.Context, values []dto.Impor } return s.ImportURLs(ctx, items) } -func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit int) ([]map[string]any, string, bool, error) { +func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit int) ([]*dto.StorageObjectResponse, string, bool, error) { items, next, more, err := s.uc.ListStorage(ctx, prefix, cursor, limit) if err != nil { return nil, "", false, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.StorageObjectResponse, 0, len(items)) for _, v := range items { - out = append(out, map[string]any{"name": v.Name, "key": v.Path, "url": v.URL, "size": v.Size}) + out = append(out, &dto.StorageObjectResponse{Name: v.Name, Key: v.Path, URL: v.URL, Size: v.Size}) } return out, next, more, nil } -func (s *MediaService) Categories(ctx context.Context) ([]map[string]any, error) { +func (s *MediaService) Categories(ctx context.Context) ([]*dto.MediaCategoryResponse, error) { items, err := s.uc.ListCategories(ctx) if err != nil { return nil, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.MediaCategoryResponse, 0, len(items)) for _, v := range items { out = append(out, categoryDTO(v)) } diff --git a/internal/service/media_upload.go b/internal/service/media_upload.go index e42bfa0..40daf9a 100644 --- a/internal/service/media_upload.go +++ b/internal/service/media_upload.go @@ -3,22 +3,24 @@ package service import ( "context" "io" + + "kra/internal/service/dto" ) -func (s *MediaService) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (map[string]any, error) { +func (s *MediaService) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*dto.InitUploadResponse, error) { session, media, chunks, err := s.uc.InitUpload(ctx, userID, name, hash, size, chunkSize, total) if err != nil { return nil, err } if media != nil { - return map[string]any{"instant": true, "media": mediaDTO(media), "uploadedChunks": []int{}}, nil + return &dto.InitUploadResponse{Instant: true, Media: mediaDTO(media), UploadedChunks: []int{}}, nil } - return map[string]any{"instant": false, "uploadId": session.ID, "uploadedChunks": chunks, "media": nil}, nil + return &dto.InitUploadResponse{Instant: false, UploadID: session.ID, UploadedChunks: chunks, Media: nil}, nil } func (s *MediaService) SaveChunk(ctx context.Context, userID, uploadID uint, index int, hash string, reader io.Reader) error { return s.uc.SaveChunk(ctx, userID, uploadID, index, hash, reader) } -func (s *MediaService) CompleteUpload(ctx context.Context, userID, uploadID uint) (map[string]any, error) { +func (s *MediaService) CompleteUpload(ctx context.Context, userID, uploadID uint) (*dto.MediaResponse, error) { v, err := s.uc.CompleteUpload(ctx, userID, uploadID, "") if err != nil { return nil, err diff --git a/internal/service/parameter.go b/internal/service/parameter.go index fc7841c..22fec7f 100644 --- a/internal/service/parameter.go +++ b/internal/service/parameter.go @@ -23,24 +23,24 @@ func (s *ParameterService) CreateParameterRequest(ctx context.Context, req *dto. func (s *ParameterService) UpdateParameterRequest(ctx context.Context, req *dto.SystemParameterRequest) error { return s.UpdateParameter(ctx, parameterDomain(req)) } -func (s *ParameterService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]map[string]any, int64, error) { +func (s *ParameterService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]*dto.SystemParameterResponse, int64, error) { return s.Parameters(ctx, page, size, &biz.SystemParameter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end}) } -func parameterDTO(v *biz.SystemParameter) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "key": v.Key, "value": v.Value, "desc": v.Desc} +func parameterDTO(v *biz.SystemParameter) *dto.SystemParameterResponse { + return &dto.SystemParameterResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc} } -func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]map[string]any, int64, error) { +func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*dto.SystemParameterResponse, int64, error) { items, total, err := s.uc.ListParameters(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.SystemParameterResponse, 0, len(items)) for _, v := range items { out = append(out, parameterDTO(v)) } return out, total, nil } -func (s *ParameterService) Parameter(ctx context.Context, id uint, key string) (map[string]any, error) { +func (s *ParameterService) Parameter(ctx context.Context, id uint, key string) (*dto.SystemParameterResponse, error) { v, err := s.uc.FindParameter(ctx, id, key) if err != nil { return nil, err diff --git a/internal/service/permission.go b/internal/service/permission.go new file mode 100644 index 0000000..7961c14 --- /dev/null +++ b/internal/service/permission.go @@ -0,0 +1,25 @@ +package service + +import ( + "context" + + "kra/internal/biz" +) + +type PermissionService struct{ uc *biz.PermissionUsecase } + +func NewPermissionService(uc *biz.PermissionUsecase) *PermissionService { + return &PermissionService{uc: uc} +} + +func (s *PermissionService) SelectedButtons(ctx context.Context, authorityID, menuID uint) ([]uint, error) { + return s.uc.SelectedButtons(ctx, authorityID, menuID) +} + +func (s *PermissionService) SetSelectedButtons(ctx context.Context, authorityID, menuID uint, ids []uint) error { + return s.uc.SetSelectedButtons(ctx, authorityID, menuID, ids) +} + +func (s *PermissionService) CanRemoveButton(ctx context.Context, id uint) (bool, error) { + return s.uc.CanRemoveButton(ctx, id) +} diff --git a/internal/service/security.go b/internal/service/security.go index b7bbd8b..bcd9e09 100644 --- a/internal/service/security.go +++ b/internal/service/security.go @@ -7,26 +7,26 @@ import ( "kra/internal/service/dto" ) -func securityDTO(v *biz.SecurityConfig) map[string]any { - return map[string]any{"ID": v.ID, "captchaOpen": v.CaptchaOpen, "captchaTimeout": v.CaptchaTimeout, "keyLong": v.KeyLong, "imgWidth": v.ImgWidth, "imgHeight": v.ImgHeight, "pwdMinLength": v.PwdMinLength, "pwdRequireUpper": v.PwdRequireUpper, "pwdRequireLower": v.PwdRequireLower, "pwdRequireDigit": v.PwdRequireDigit, "pwdRequireSpecial": v.PwdRequireSpecial, "limitEnable": v.LimitEnable, "limitWindow": v.LimitWindow, "limitCount": v.LimitCount, "lockEnable": v.LockEnable, "lockThreshold": v.LockThreshold, "lockDuration": v.LockDuration, "pwdExpireEnable": v.PwdExpireEnable, "pwdExpireDays": v.PwdExpireDays, "forceNewUserChangePassword": v.ForceNewUserChangePassword} +func securityDTO(v *biz.SecurityConfig) *dto.SecurityConfigResponse { + return &dto.SecurityConfigResponse{ID: v.ID, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } func (s *SecurityService) CurrentSecurity(ctx context.Context) (*biz.SecurityConfig, error) { return s.uc.Current(ctx) } -func (s *SecurityService) Security(ctx context.Context) (map[string]any, error) { +func (s *SecurityService) Security(ctx context.Context) (*dto.SecurityConfigResponse, error) { value, err := s.CurrentSecurity(ctx) if err != nil { return nil, err } return securityDTO(value), nil } -func (s *SecurityService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (map[string]any, error) { +func (s *SecurityService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (*dto.SecurityConfigResponse, error) { if err := s.uc.UpdateSecurity(ctx, value); err != nil { return nil, err } return securityDTO(value), nil } -func (s *SecurityService) SaveSecurityRequest(ctx context.Context, value *dto.SecurityConfigRequest) (map[string]any, error) { +func (s *SecurityService) SaveSecurityRequest(ctx context.Context, value *dto.SecurityConfigRequest) (*dto.SecurityConfigResponse, error) { return s.SaveSecurity(ctx, &biz.SecurityConfig{ ID: value.ID, CaptchaOpen: value.CaptchaOpen, CaptchaTimeout: value.CaptchaTimeout, KeyLong: value.KeyLong, ImgWidth: value.ImgWidth, ImgHeight: value.ImgHeight, diff --git a/internal/service/system.go b/internal/service/system.go index 8d24b67..1857d6a 100644 --- a/internal/service/system.go +++ b/internal/service/system.go @@ -4,16 +4,15 @@ import ( "context" "kra/internal/biz" - "kra/internal/conf" ) type SystemConfigService struct { - uc *biz.SystemConfigUsecase - runtime *conf.Runtime + uc *biz.SystemConfigUsecase + settings biz.RuntimeSettings } -func NewSystemConfigService(uc *biz.SystemConfigUsecase, runtime *conf.Runtime) *SystemConfigService { - return &SystemConfigService{uc: uc, runtime: runtime} +func NewSystemConfigService(uc *biz.SystemConfigUsecase, settings biz.RuntimeSettings) *SystemConfigService { + return &SystemConfigService{uc: uc, settings: settings} } func (s *SystemConfigService) IsInitialized(ctx context.Context) (bool, error) { diff --git a/internal/service/system_config.go b/internal/service/system_config.go index 2123727..e1806e9 100644 --- a/internal/service/system_config.go +++ b/internal/service/system_config.go @@ -4,125 +4,23 @@ import ( "context" "encoding/json" - "kra/internal/conf" "kra/internal/service/dto" - - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" ) func (s *SystemConfigService) PersistConfig(ctx context.Context) error { return s.uc.PersistConfig(ctx) } -func (s *SystemConfigService) PersistAdminConfig(ctx context.Context, value *conf.AdminBackend) error { - raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(value) - if err != nil { - return err - } - return s.uc.PersistAdminConfig(ctx, raw) +func (s *SystemConfigService) ReloadConfig(ctx context.Context) error { + return s.uc.ReloadConfig(ctx) } -func (s *SystemConfigService) ReloadConfig(ctx context.Context) error { return s.uc.ReloadConfig(ctx) } +func (s *SystemConfigService) DiskMountPoints() []string { return s.uc.DiskMountPoints() } -func (s *SystemConfigService) DiskMountPoints() []string { - config := s.runtime.Admin() - if config == nil { - return nil - } - points := make([]string, 0, len(config.DiskList)) - for _, item := range config.DiskList { - if item != nil && item.MountPoint != "" { - points = append(points, item.MountPoint) - } - } - return points -} - -func (s *SystemConfigService) SystemConfig() map[string]any { - admin := map[string]any{"routerPrefix": ""} - email := map[string]any{} - config := s.runtime.Admin() - if config == nil { - return map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": map[string]any{}}} - } - admin["routerPrefix"] = config.RouterPrefix - if config.System != nil { - admin["system"] = map[string]any{"useRedis": config.System.UseRedis, "useMultipoint": config.System.UseMultipoint, "useStrictAuth": config.System.UseStrictAuth, "disableAutoMigrate": config.System.DisableAutoMigrate, "useMongo": config.System.UseMongo} - } - if config.Jwt != nil { - admin["jwt"] = map[string]any{"signingKey": "******", "expiresTime": config.Jwt.ExpiresTime.AsDuration().String(), "bufferTime": config.Jwt.BufferTime.AsDuration().String(), "issuer": config.Jwt.Issuer} - } - if config.Captcha != nil { - admin["captcha"] = map[string]any{"keyLong": config.Captcha.KeyLong, "imgWidth": config.Captcha.ImgWidth, "imgHeight": config.Captcha.ImgHeight, "storeExpiration": config.Captcha.StoreExpiration.AsDuration().String()} - } - if config.Local != nil { - admin["local"] = map[string]any{"storePath": config.Local.StorePath, "pathPrefix": config.Local.PathPrefix} - } - if config.Media != nil { - admin["media"] = map[string]any{"sessionTtl": config.Media.SessionTtl, "maxFileSize": config.Media.MaxFileSize} - } - if config.Email != nil { - email = map[string]any{"to": config.Email.To, "from": config.Email.From, "host": config.Email.Host, "secret": "******", "nickname": config.Email.Nickname, "port": config.Email.Port, "is-ssl": config.Email.IsSsl, "is-loginauth": config.Email.IsLoginAuth} - } - if config.Storage != nil { - storage := proto.Clone(config.Storage).(*conf.AdminBackend_Storage) - maskStorageSecrets(storage) - admin["storage"] = storage - } - if config.Zap != nil { - admin["zap"] = config.Zap - } - if config.Cors != nil { - admin["cors"] = config.Cors - } - if config.App != nil { - admin["app"] = config.App - } - data := s.runtime.Data() - if data != nil { - if data.Database != nil { - data.Database.Password = "******" - } - if data.Redis != nil { - data.Redis.Password = "******" - } - if data.Mongo != nil { - data.Mongo.Password = "******" - } - for _, database := range data.DatabaseList { - if database != nil { - database.Password = "******" - } - } - for _, redis := range data.RedisList { - if redis != nil { - redis.Password = "******" - } - } - } - dataMap := map[string]any{} - if raw, err := (protojson.MarshalOptions{UseProtoNames: true}).Marshal(data); err == nil { - _ = json.Unmarshal(raw, &dataMap) - } - return map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}} +func (s *SystemConfigService) SystemConfig() (json.RawMessage, error) { + return s.uc.ConfigurationJSON() } func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.SetSystemConfigRequest) error { - config := s.runtime.Admin() - if config == nil { - return nil - } - next := proto.Clone(config).(*conf.AdminBackend) - applyAdminConfig(next, req) - data := applyDataConfig(s.runtime.Data(), req) - dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(data) - if err != nil { - return err - } - adminRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(next) - if err != nil { - return err - } - return s.uc.PersistRuntimeConfig(ctx, dataRaw, adminRaw) + return s.uc.SaveConfigurationJSON(ctx, req.Config) } diff --git a/internal/service/system_config_apply.go b/internal/service/system_config_apply.go deleted file mode 100644 index fcf8dc9..0000000 --- a/internal/service/system_config_apply.go +++ /dev/null @@ -1,197 +0,0 @@ -package service - -import ( - "time" - - "kra/internal/conf" - "kra/internal/service/dto" - - "google.golang.org/protobuf/types/known/durationpb" -) - -func applyAdminConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { - next.RouterPrefix = req.Config.Admin.RouterPrefix - if next.System == nil { - next.System = &conf.AdminBackend_System{} - } - next.System.UseRedis = req.Config.Admin.System.UseRedis - next.System.UseMultipoint = req.Config.Admin.System.UseMultipoint - next.System.UseStrictAuth = req.Config.Admin.System.UseStrictAuth - next.System.DisableAutoMigrate = req.Config.Admin.System.DisableAutoMigrate - next.System.UseMongo = req.Config.Admin.System.UseMongo - if req.Config.Admin.Zap != nil { - next.Zap = req.Config.Admin.Zap - } - if req.Config.Admin.Cors != nil { - next.Cors = req.Config.Admin.Cors - } - if req.Config.Admin.App != nil { - next.App = req.Config.Admin.App - } - applyJWTConfig(next, req) - applyCaptchaConfig(next, req) - applyMediaConfig(next, req) - if req.Config.Admin.Storage != nil { - preserveStorageSecrets(req.Config.Admin.Storage, next.Storage) - next.Storage = req.Config.Admin.Storage - } - if next.Email != nil && req.Config.Email != nil { - next.Email.To, next.Email.From, next.Email.Host = req.Config.Email.To, req.Config.Email.From, req.Config.Email.Host - next.Email.Nickname, next.Email.Port = req.Config.Email.Nickname, req.Config.Email.Port - next.Email.IsSsl, next.Email.IsLoginAuth = req.Config.Email.IsSSL, req.Config.Email.IsLoginAuth - if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" { - next.Email.Secret = req.Config.Email.Secret - } - } -} - -func applyJWTConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { - if next.Jwt == nil { - return - } - if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" { - next.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey - } - if req.Config.Admin.JWT.Issuer != "" { - next.Jwt.Issuer = req.Config.Admin.JWT.Issuer - } - if value, err := time.ParseDuration(req.Config.Admin.JWT.ExpiresTime); err == nil && value > 0 { - next.Jwt.ExpiresTime = durationpb.New(value) - } - if value, err := time.ParseDuration(req.Config.Admin.JWT.BufferTime); err == nil && value >= 0 { - next.Jwt.BufferTime = durationpb.New(value) - } -} - -func applyCaptchaConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { - if next.Captcha != nil { - if req.Config.Admin.Captcha.KeyLong > 0 { - next.Captcha.KeyLong = req.Config.Admin.Captcha.KeyLong - } - if req.Config.Admin.Captcha.ImgWidth > 0 { - next.Captcha.ImgWidth = req.Config.Admin.Captcha.ImgWidth - } - if req.Config.Admin.Captcha.ImgHeight > 0 { - next.Captcha.ImgHeight = req.Config.Admin.Captcha.ImgHeight - } - if value, err := time.ParseDuration(req.Config.Admin.Captcha.StoreExpiration); err == nil && value > 0 { - next.Captcha.StoreExpiration = durationpb.New(value) - } - } - if next.Local != nil { - if req.Config.Admin.Local.StorePath != "" { - next.Local.StorePath = req.Config.Admin.Local.StorePath - } - if req.Config.Admin.Local.PathPrefix != "" { - next.Local.PathPrefix = req.Config.Admin.Local.PathPrefix - } - } -} - -func applyMediaConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { - if next.Media == nil { - return - } - if req.Config.Admin.Media.SessionTTL > 0 { - next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL - } - next.Media.MaxFileSize = req.Config.Admin.Media.MaxFileSize -} - -func applyDataConfig(data *conf.Data, req *dto.SetSystemConfigRequest) *conf.Data { - if req.Config.Data == nil { - return data - } - if data == nil { - data = &conf.Data{} - } - applyDatabaseConfig(data, req) - applyRedisConfig(data, req) - if req.Config.Data.DatabaseList != nil { - for i, item := range req.Config.Data.DatabaseList { - if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.DatabaseList) && data.DatabaseList[i] != nil { - item.Password = data.DatabaseList[i].Password - } - } - data.DatabaseList = req.Config.Data.DatabaseList - } - if req.Config.Data.RedisList != nil { - for i, item := range req.Config.Data.RedisList { - if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.RedisList) && data.RedisList[i] != nil { - item.Password = data.RedisList[i].Password - } - } - data.RedisList = req.Config.Data.RedisList - } - if req.Config.Data.Mongo != nil { - if data.Mongo != nil && (req.Config.Data.Mongo.Password == "" || req.Config.Data.Mongo.Password == "******") { - req.Config.Data.Mongo.Password = data.Mongo.Password - } - data.Mongo = req.Config.Data.Mongo - } - return data -} - -func applyDatabaseConfig(data *conf.Data, req *dto.SetSystemConfigRequest) { - value := req.Config.Data.Database - if value == nil { - return - } - password := value.Password - if data.Database != nil && (password == "" || password == "******") { - password = data.Database.Password - } - data.Database = &conf.Data_Database{Driver: value.Driver, Source: value.Source, Host: value.Host, Port: value.Port, User: value.User, Password: password, Name: value.Name, Config: value.Config, Path: value.Path, Prefix: value.Prefix, Engine: value.Engine, LogMode: value.LogMode, MaxIdleConns: value.MaxIdleConns, MaxOpenConns: value.MaxOpenConns, ConnMaxLifetime: value.ConnMaxLifetime, Singular: value.Singular} -} - -func applyRedisConfig(data *conf.Data, req *dto.SetSystemConfigRequest) { - value := req.Config.Data.Redis - if value == nil { - return - } - password := value.Password - if data.Redis != nil && (password == "" || password == "******") { - password = data.Redis.Password - } - redis := &conf.Data_Redis{Network: value.Network, Addr: value.Addr, Name: value.Name, Password: password, Db: value.DB, UseCluster: value.UseCluster, ClusterAddrs: value.ClusterAddrs} - if duration, err := time.ParseDuration(value.ReadTimeout); err == nil && duration >= 0 { - redis.ReadTimeout = durationpb.New(duration) - } - if duration, err := time.ParseDuration(value.WriteTimeout); err == nil && duration >= 0 { - redis.WriteTimeout = durationpb.New(duration) - } - data.Redis = redis -} - -func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore { - if storage == nil { - return nil - } - return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio} -} - -func maskStorageSecrets(storage *conf.AdminBackend_Storage) { - if storage.Qiniu != nil { - storage.Qiniu.SecretKey = "******" - } - for _, item := range objectStores(storage) { - if item != nil { - item.SecretKey = "******" - } - } -} - -func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) { - if next == nil || current == nil { - return - } - if next.Qiniu != nil && current.Qiniu != nil && (next.Qiniu.SecretKey == "" || next.Qiniu.SecretKey == "******") { - next.Qiniu.SecretKey = current.Qiniu.SecretKey - } - nextItems, currentItems := objectStores(next), objectStores(current) - for i := range nextItems { - if nextItems[i] != nil && currentItems[i] != nil && (nextItems[i].SecretKey == "" || nextItems[i].SecretKey == "******") { - nextItems[i].SecretKey = currentItems[i].SecretKey - } - } -} diff --git a/internal/service/system_info.go b/internal/service/system_info.go index f268b46..8cc810e 100644 --- a/internal/service/system_info.go +++ b/internal/service/system_info.go @@ -4,12 +4,14 @@ import ( "runtime" "time" + "kra/internal/service/dto" + "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/disk" "github.com/shirou/gopsutil/v4/mem" ) -func (s *SystemConfigService) ServerInfo() (map[string]any, error) { +func (s *SystemConfigService) ServerInfo() (*dto.ServerInfoResponse, error) { physicalCores, err := cpu.Counts(false) if err != nil { return nil, err @@ -22,19 +24,19 @@ func (s *SystemConfigService) ServerInfo() (map[string]any, error) { if err != nil { return nil, err } - diskInfo := make([]map[string]any, 0) + diskInfo := make([]dto.ServerDiskResponse, 0) for _, mountPoint := range s.DiskMountPoints() { usage, usageErr := disk.Usage(mountPoint) if usageErr != nil { return nil, usageErr } - diskInfo = append(diskInfo, map[string]any{"mountPoint": mountPoint, "usedMb": usage.Used / 1024 / 1024, "usedGb": usage.Used / 1024 / 1024 / 1024, "totalMb": usage.Total / 1024 / 1024, "totalGb": usage.Total / 1024 / 1024 / 1024, "usedPercent": int(usage.UsedPercent)}) + diskInfo = append(diskInfo, dto.ServerDiskResponse{MountPoint: mountPoint, UsedMB: usage.Used / 1024 / 1024, UsedGB: usage.Used / 1024 / 1024 / 1024, TotalMB: usage.Total / 1024 / 1024, TotalGB: usage.Total / 1024 / 1024 / 1024, UsedPercent: int(usage.UsedPercent)}) } usedMB, totalMB := memory.Used/1024/1024, memory.Total/1024/1024 - return map[string]any{ - "os": map[string]any{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, - "cpu": map[string]any{"cpus": cpuPercent, "cores": physicalCores}, - "ram": map[string]any{"usedMb": usedMB, "totalMb": totalMB, "usedPercent": int(memory.UsedPercent)}, - "disk": diskInfo, + return &dto.ServerInfoResponse{ + OS: dto.ServerOSResponse{GOOS: runtime.GOOS, NumCPU: runtime.NumCPU(), Compiler: runtime.Compiler, GoVersion: runtime.Version(), NumGoroutine: runtime.NumGoroutine()}, + CPU: dto.ServerCPUResponse{CPUs: cpuPercent, Cores: physicalCores}, + RAM: dto.ServerRAMResponse{UsedMB: usedMB, TotalMB: totalMB, UsedPercent: int(memory.UsedPercent)}, + Disk: diskInfo, }, nil } diff --git a/internal/service/system_init.go b/internal/service/system_init.go index 7399afe..ed38194 100644 --- a/internal/service/system_init.go +++ b/internal/service/system_init.go @@ -35,8 +35,8 @@ func (s *SystemConfigService) InitializeRoutes(ctx context.Context, input *Datab apis := make([]*biz.API, 0, len(routes)) for _, route := range routes { path := route.Path - if config := s.runtime.Admin(); config != nil && config.RouterPrefix != "" { - path = strings.TrimPrefix(path, strings.TrimSuffix(config.RouterPrefix, "/")) + if routerPrefix := s.settings.RouterPrefix(); routerPrefix != "" { + path = strings.TrimPrefix(path, strings.TrimSuffix(routerPrefix, "/")) if path == "" { path = "/" } diff --git a/internal/service/task.go b/internal/service/task.go index 5a6efc5..09d8fe0 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -29,7 +29,7 @@ func (s *TaskService) CreateRequest(ctx context.Context, req *dto.TaskRequest) ( func (s *TaskService) UpdateRequest(ctx context.Context, req *dto.TaskRequest) error { return s.Update(ctx, taskDomain(req)) } -func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]map[string]any, int64, error) { +func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]*dto.TaskResponse, int64, error) { return s.Tasks(ctx, page, size, &biz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}) } func (s *TaskService) Create(ctx context.Context, v *biz.TimedTask) error { @@ -44,15 +44,15 @@ func (s *TaskService) Delete(ctx context.Context, id uint) error { func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error { return s.uc.Toggle(ctx, id, enabled) } -func taskDTO(v *biz.TimedTask, next *time.Time) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "description": v.Description, "spec": v.Spec, "withSeconds": v.WithSeconds, "executorType": v.ExecutorType, "methodName": v.MethodName, "params": json.RawMessage(v.Params), "httpUrl": v.HTTPURL, "httpMethod": v.HTTPMethod, "httpHeader": json.RawMessage(v.HTTPHeader), "httpBody": v.HTTPBody, "httpAllowPrivate": v.HTTPAllowPrivate, "enabled": v.Enabled, "nextRunAt": next} +func taskDTO(v *biz.TimedTask, next *time.Time) *dto.TaskResponse { + return &dto.TaskResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: json.RawMessage(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: json.RawMessage(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled, NextRunAt: next} } -func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]map[string]any, int64, error) { +func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*dto.TaskResponse, int64, error) { items, total, next, err := s.uc.List(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.TaskResponse, 0, len(items)) for _, v := range items { var ptr *time.Time if value, ok := next[v.ID]; ok { @@ -64,14 +64,14 @@ func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTas return out, total, nil } -func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]map[string]any, int64, error) { +func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]*dto.TaskLogResponse, int64, error) { items, total, err := s.uc.Logs(ctx, page, size, taskID, status) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.TaskLogResponse, 0, len(items)) for _, v := range items { - out = append(out, map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "taskId": v.TaskID, "taskName": v.TaskName, "triggerType": v.TriggerType, "startedAt": v.StartedAt, "finishedAt": v.FinishedAt, "durationMs": v.DurationMS, "status": v.Status, "errorMsg": v.ErrorMsg, "output": v.Output}) + out = append(out, &dto.TaskLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, DeletedAt: nil, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}) } return out, total, nil } @@ -81,11 +81,11 @@ func (s *TaskService) Reload(ctx context.Context) error { return s.uc. func (s *TaskService) Subscribe() chan []byte { return s.uc.Subscribe() } func (s *TaskService) Unsubscribe(events chan []byte) { s.uc.Unsubscribe(events) } -func (s *TaskService) RegisteredMethods() []map[string]any { +func (s *TaskService) RegisteredMethods() []*dto.TaskMethodResponse { methods := biz.RegisteredTaskMethods() - out := make([]map[string]any, 0, len(methods)) + out := make([]*dto.TaskMethodResponse, 0, len(methods)) for _, method := range methods { - out = append(out, map[string]any{"name": method.Name, "description": method.Description}) + out = append(out, &dto.TaskMethodResponse{Name: method.Name, Description: method.Description}) } return out } diff --git a/internal/service/user.go b/internal/service/user.go index 0d64725..f4d2faa 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -27,17 +27,17 @@ func NewUserService(uc *biz.UserUsecase, settings *SecurityService) *UserService func userInput(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) ([]map[string]any, int64, error) { +func (s *UserService) ListUsersRequest(ctx context.Context, value *dto.UserListRequest) ([]*dto.UserResponse, int64, error) { return s.ListUsers(ctx, value.Page, value.PageSize, &biz.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) (map[string]any, error) { +func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (*dto.UserResponse, error) { return s.CreateUser(ctx, userInput(value)) } func (s *UserService) UpdateUserRequest(ctx context.Context, value *dto.UserRequest) error { return s.UpdateUser(ctx, userInput(value)) } -func (s *UserService) User(ctx context.Context, id uint) (map[string]any, error) { +func (s *UserService) User(ctx context.Context, id uint) (*dto.UserResponse, error) { value, err := s.uc.User(ctx, id) if err != nil { return nil, err @@ -45,43 +45,39 @@ func (s *UserService) User(ctx context.Context, id uint) (map[string]any, error) return convertUser(value), nil } -func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]map[string]any, error) { +func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]*dto.MenuResponse, error) { menus, err := s.uc.Menus(ctx, authorityID) if err != nil { return nil, err } - result := make([]map[string]any, 0, len(menus)) - for _, menu := range menus { - result = append(result, convertMenu(menu)) - } - return result, nil + return menuResponses(menus), nil } -func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]map[string]any, int64, error) { +func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*dto.UserResponse, int64, error) { users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter) if err != nil { return nil, 0, err } - result := make([]map[string]any, 0, len(users)) + 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) ([]map[string]any, error) { +func (s *UserService) Authorities(ctx context.Context) ([]*dto.AuthorityResponse, error) { values, err := s.uc.Authorities(ctx) if err != nil { return nil, err } - result := make([]map[string]any, 0, len(values)) + result := make([]*dto.AuthorityResponse, 0, len(values)) for _, value := range values { result = append(result, convertAuthority(*value)) } return result, nil } -func (s *UserService) CreateUser(ctx context.Context, input UserInput) (map[string]any, error) { +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 } diff --git a/internal/service/user_conversion.go b/internal/service/user_conversion.go index 2e07b97..23d9417 100644 --- a/internal/service/user_conversion.go +++ b/internal/service/user_conversion.go @@ -1,56 +1,29 @@ package service -import "kra/internal/biz" +import ( + "kra/internal/biz" + "kra/internal/service/dto" +) -func departmentDTO(v *biz.Department) map[string]any { - children := make([]map[string]any, 0, len(v.Children)) - for _, c := range v.Children { - children = append(children, departmentDTO(c)) - } - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "parentId": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leaderId": v.LeaderID, "leader": nil, "status": v.Status, "children": children, "namePath": v.NamePath} +func convertAuthority(value biz.Authority) *dto.AuthorityResponse { + return &dto.AuthorityResponse{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: []*dto.AuthorityResponse{}, Menus: []any{}, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} } -func positionDTO(v *biz.Position) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark} -} - -func convertAuthority(value biz.Authority) map[string]any { - return map[string]any{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "children": []map[string]any{}, "menus": []any{}, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter} -} -func convertUser(user *biz.User) map[string]any { - authorities := make([]map[string]any, 0, len(user.Authorities)) +func convertUser(user *biz.User) *dto.UserResponse { + authorities := make([]*dto.AuthorityResponse, 0, len(user.Authorities)) for _, value := range user.Authorities { authorities = append(authorities, convertAuthority(value)) } - departments := make([]map[string]any, 0, len(user.Departments)) + departments := make([]*dto.DepartmentResponse, 0, len(user.Departments)) for i := range user.Departments { - departments = append(departments, departmentDTO(&user.Departments[i])) + departments = append(departments, departmentResponse(&user.Departments[i])) } - positions := make([]map[string]any, 0, len(user.Positions)) + positions := make([]*dto.PositionResponse, 0, len(user.Positions)) for i := range user.Positions { - positions = append(positions, positionDTO(&user.Positions[i])) + positions = append(positions, positionResponse(&user.Positions[i])) } - department := map[string]any{} + var department any = map[string]any{} if user.Department != nil { - department = departmentDTO(user.Department) + department = departmentResponse(user.Department) } - return map[string]any{"ID": user.ID, "CreatedAt": user.CreatedAt, "UpdatedAt": user.UpdatedAt, "DeletedAt": nil, "uuid": user.UUID, "userName": user.Username, "nickName": user.NickName, "headerImg": user.HeaderImg, "authorityId": user.AuthorityID, "authority": convertAuthority(user.Authority), "authorities": authorities, "deptId": user.DeptID, "dept": department, "departments": departments, "positions": positions, "phone": user.Phone, "email": user.Email, "enable": user.Enable, "originSetting": user.OriginSetting} -} -func convertMenu(menu *biz.Menu) map[string]any { - children := make([]map[string]any, 0, len(menu.Children)) - for _, child := range menu.Children { - children = append(children, convertMenu(child)) - } - buttons := make([]map[string]any, 0, len(menu.Buttons)) - buttonAuthorities := make(map[string]uint, len(menu.Buttons)) - for _, button := range menu.Buttons { - buttons = append(buttons, map[string]any{"ID": button.ID, "name": button.Name, "desc": button.Description, "sysBaseMenuID": button.MenuID}) - if button.AuthorityID != 0 { - buttonAuthorities[button.Name] = button.AuthorityID - } - } - parameters := make([]map[string]any, 0, len(menu.Parameters)) - for _, parameter := range menu.Parameters { - parameters = append(parameters, map[string]any{"ID": parameter.ID, "SysBaseMenuID": parameter.MenuID, "type": parameter.Type, "key": parameter.Key, "value": parameter.Value}) - } - return map[string]any{"ID": menu.ID, "parentId": menu.ParentID, "path": menu.Path, "name": menu.Name, "hidden": menu.Hidden, "component": menu.Component, "sort": menu.Sort, "meta": map[string]any{"activeName": menu.ActiveName, "keepAlive": menu.KeepAlive, "defaultMenu": menu.DefaultMenu, "title": menu.Title, "icon": menu.Icon, "closeTab": menu.CloseTab, "transitionType": menu.TransitionType}, "children": children, "parameters": parameters, "menuBtn": buttons, "btns": buttonAuthorities} + return &dto.UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, DeletedAt: nil, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting} } diff --git a/internal/service/version.go b/internal/service/version.go index 37d00ca..e900845 100644 --- a/internal/service/version.go +++ b/internal/service/version.go @@ -12,21 +12,21 @@ import ( type VersionService struct{ uc *biz.VersionUsecase } func NewVersionService(uc *biz.VersionUsecase) *VersionService { return &VersionService{uc: uc} } -func versionDTO(v *biz.Version) map[string]any { - return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "versionName": v.Name, "versionCode": v.Code, "description": v.Description, "versionData": v.Data} +func versionDTO(v *biz.Version) *dto.VersionResponse { + return &dto.VersionResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, VersionName: v.Name, VersionCode: v.Code, Description: v.Description, VersionData: v.Data} } -func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]map[string]any, int64, error) { +func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*dto.VersionResponse, int64, error) { items, total, err := s.uc.ListVersions(ctx, page, size, name, code, createdAtRange) if err != nil { return nil, 0, err } - out := make([]map[string]any, 0, len(items)) + out := make([]*dto.VersionResponse, 0, len(items)) for _, v := range items { out = append(out, versionDTO(v)) } return out, total, nil } -func (s *VersionService) Version(ctx context.Context, id uint) (map[string]any, error) { +func (s *VersionService) Version(ctx context.Context, id uint) (*dto.VersionResponse, error) { v, err := s.uc.FindVersion(ctx, id) if err != nil { return nil, err @@ -53,15 +53,15 @@ func (s *VersionService) Export(ctx context.Context, name, code, description str bundle.Code = code bundle.Description = description bundle.ExportTime = time.Now().Format("2006-01-02 15:04:05") - menus := make([]map[string]any, 0, len(bundle.Menus)) + menus := make([]*dto.MenuResponse, 0, len(bundle.Menus)) for _, v := range bundle.Menus { - menus = append(menus, convertMenu(v)) + menus = append(menus, menuResponse(v)) } - apis := make([]map[string]any, 0, len(bundle.APIs)) + apis := make([]*dto.APIResponse, 0, len(bundle.APIs)) for _, v := range bundle.APIs { - apis = append(apis, apiDTO(v)) + apis = append(apis, apiResponse(v)) } - dicts := make([]map[string]any, 0, len(bundle.Dictionaries)) + dicts := make([]*dto.DictionaryResponse, 0, len(bundle.Dictionaries)) for _, v := range bundle.Dictionaries { dicts = append(dicts, dictionaryDTO(v)) } diff --git a/internal/worker/worker.go b/internal/worker/worker.go new file mode 100644 index 0000000..9a4a5d6 --- /dev/null +++ b/internal/worker/worker.go @@ -0,0 +1,6 @@ +package worker + +import "github.com/google/wire" + +// ProviderSet contains background task runtime providers. +var ProviderSet = wire.NewSet(NewTaskExecutor, NewTaskScheduler, NewTaskRuntime)