优化
This commit is contained in:
Generated
+18
-12
@@ -42,7 +42,8 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
||||
menuService := service.NewMenuService(menuUsecase)
|
||||
menu := handler.NewMenu(menuService)
|
||||
apiUsecase := biz.NewAPIUsecase(apiRepo)
|
||||
apiService := service.NewAPIService(apiUsecase, runtime)
|
||||
runtimeSettings := data.NewRuntimeSettings(runtime)
|
||||
apiService := service.NewAPIService(apiUsecase, runtimeSettings)
|
||||
api := handler.NewAPI(apiService)
|
||||
permissionRepo := data.NewPermissionRepo(dataData)
|
||||
permissionUsecase := biz.NewPermissionUsecase(permissionRepo)
|
||||
@@ -65,7 +66,6 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
||||
email := handler.NewEmail(emailService)
|
||||
taskRepo := data.NewTaskRepo(dataData)
|
||||
taskUsecase := biz.NewTaskUsecase(taskRepo)
|
||||
taskService := service.NewTaskService(taskUsecase)
|
||||
mediaRepo := data.NewMediaRepo(dataData)
|
||||
fileStorage, err := data.NewFileStorage(dataData)
|
||||
if err != nil {
|
||||
@@ -75,8 +75,11 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
||||
mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage)
|
||||
taskExecutor := worker.NewTaskExecutor(taskUsecase, mediaUsecase, runtime)
|
||||
taskScheduler := worker.NewTaskScheduler(taskUsecase, taskExecutor, logger)
|
||||
task := handler.NewTask(taskService, taskScheduler)
|
||||
mediaService := service.NewMediaService(mediaUsecase, runtime)
|
||||
taskRuntime := worker.NewTaskRuntime(taskScheduler)
|
||||
taskApplicationUsecase := biz.NewTaskApplicationUsecase(taskUsecase, taskRuntime)
|
||||
taskService := service.NewTaskService(taskApplicationUsecase)
|
||||
task := handler.NewTask(taskService)
|
||||
mediaService := service.NewMediaService(mediaUsecase, runtimeSettings)
|
||||
media := handler.NewMedia(mediaService)
|
||||
auditQueryRepo := data.NewAuditRepo(dataData)
|
||||
auditUsecase := biz.NewAuditUsecase(auditQueryRepo)
|
||||
@@ -107,24 +110,27 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
||||
parameter := handler.NewParameter(parameterService)
|
||||
apiTokenRepo := data.NewAPITokenRepo(dataData)
|
||||
tokenUsecase := biz.NewTokenUsecase(apiTokenRepo)
|
||||
tokenService := service.NewTokenService(tokenUsecase, runtime)
|
||||
tokenIssuer := data.NewTokenIssuer(runtimeSettings)
|
||||
tokenService := service.NewTokenService(tokenUsecase, tokenIssuer)
|
||||
apiToken := handler.NewAPIToken(tokenService)
|
||||
initializationRepo := data.NewInitializationRepo(dataData)
|
||||
systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo)
|
||||
systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo, taskRuntime)
|
||||
systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtime)
|
||||
securityRepo := data.NewSecurityRepo(dataData)
|
||||
securityUsecase := biz.NewSecurityUsecase(securityRepo)
|
||||
securityService := service.NewSecurityService(securityUsecase, runtime, cache, tokenUsecase)
|
||||
systemConfig := handler.NewSystemConfig(systemConfigService, securityService, taskScheduler)
|
||||
securityUsecase := biz.NewSecurityUsecase(securityRepo, cache, runtimeSettings, tokenUsecase)
|
||||
securityService := service.NewSecurityService(securityUsecase)
|
||||
systemConfig := handler.NewSystemConfig(systemConfigService, securityService)
|
||||
userRepo := data.NewUserRepo(dataData)
|
||||
userUsecase := biz.NewUserUsecase(userRepo)
|
||||
authService := service.NewAuthService(userUsecase, runtime, securityService)
|
||||
public := handler.NewPublic(runtime, authService, systemConfigService, securityService, auditRecorder, taskScheduler)
|
||||
authenticationUsecase := biz.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo)
|
||||
authService := service.NewAuthService(authenticationUsecase)
|
||||
public := handler.NewPublic(authService, systemConfigService, securityService)
|
||||
userService := service.NewUserService(userUsecase, securityService)
|
||||
user := handler.NewUser(userService, authService)
|
||||
navigation := handler.NewNavigation(userService)
|
||||
session := handler.NewSession(tokenService)
|
||||
engine := server.NewGinEngine(runtime, accessControlService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, securityService, tokenService, auditRecorder, logger)
|
||||
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)
|
||||
httpServer := server.NewGinServer(confServer, engine)
|
||||
app := newApp(logger, httpServer, taskScheduler)
|
||||
return app, func() {
|
||||
|
||||
@@ -177,6 +177,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -410,6 +412,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -506,6 +510,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAccountLocked = errors.New("account locked")
|
||||
ErrCaptchaInvalid = errors.New("captcha invalid")
|
||||
ErrTokenIssue = errors.New("token issue failed")
|
||||
ErrLoginState = errors.New("login state failed")
|
||||
)
|
||||
|
||||
type UserDisabledError struct{ UserID uint }
|
||||
|
||||
func (e *UserDisabledError) Error() string { return ErrUserDisabled.Error() }
|
||||
func (e *UserDisabledError) Unwrap() error { return ErrUserDisabled }
|
||||
|
||||
type AccountLockedError struct{ Minutes int }
|
||||
|
||||
func (e *AccountLockedError) Error() string { return ErrAccountLocked.Error() }
|
||||
func (e *AccountLockedError) Unwrap() error { return ErrAccountLocked }
|
||||
|
||||
type LoginAttempt struct {
|
||||
Username string
|
||||
Password string
|
||||
CaptchaID string
|
||||
Captcha string
|
||||
IP string
|
||||
Agent string
|
||||
}
|
||||
|
||||
type AuthenticationResult struct {
|
||||
User *User
|
||||
Token string
|
||||
ExpiresAt time.Time
|
||||
NeedChangePassword bool
|
||||
}
|
||||
|
||||
type AuthenticationUsecase struct {
|
||||
users *UserUsecase
|
||||
security *SecurityUsecase
|
||||
issuer TokenIssuer
|
||||
audit AuditRecordRepo
|
||||
}
|
||||
|
||||
func NewAuthenticationUsecase(users *UserUsecase, security *SecurityUsecase, issuer TokenIssuer, audit AuditRecordRepo) *AuthenticationUsecase {
|
||||
return &AuthenticationUsecase{users: users, security: security, issuer: issuer, audit: audit}
|
||||
}
|
||||
|
||||
func (uc *AuthenticationUsecase) recordLogin(ctx context.Context, attempt *LoginAttempt, status bool, message string, userID uint) {
|
||||
_ = uc.audit.RecordLogin(ctx, &LoginLog{Username: attempt.Username, IP: attempt.IP, Status: status, ErrorMessage: message, Agent: attempt.Agent, UserID: userID})
|
||||
}
|
||||
|
||||
func (uc *AuthenticationUsecase) Login(ctx context.Context, attempt *LoginAttempt) (*AuthenticationResult, error) {
|
||||
config, _ := uc.security.Current(ctx)
|
||||
if config != nil && config.LockEnable {
|
||||
if locked, _ := uc.security.LoginLocked(ctx, attempt.Username); locked {
|
||||
uc.recordLogin(ctx, attempt, false, "账号已锁定", 0)
|
||||
return nil, &AccountLockedError{Minutes: config.LockDuration}
|
||||
}
|
||||
}
|
||||
|
||||
ipTTL := time.Hour
|
||||
requireCaptcha := config == nil || config.CaptchaOpen == 0
|
||||
if config != nil {
|
||||
if config.CaptchaTimeout > 0 {
|
||||
ipTTL = time.Duration(config.CaptchaTimeout) * time.Second
|
||||
}
|
||||
failures, _ := uc.security.EnsureLoginIPCounter(ctx, attempt.IP, ipTTL)
|
||||
requireCaptcha = config.CaptchaOpen == 0 || failures > config.CaptchaOpen
|
||||
}
|
||||
if requireCaptcha && !uc.security.VerifyCaptcha(ctx, attempt.CaptchaID, attempt.Captcha, true) {
|
||||
_, _ = uc.security.IncrementLoginIP(ctx, attempt.IP, ipTTL)
|
||||
uc.recordLogin(ctx, attempt, false, "验证码错误", 0)
|
||||
return nil, ErrCaptchaInvalid
|
||||
}
|
||||
|
||||
user, err := uc.users.Login(ctx, attempt.Username, attempt.Password)
|
||||
if err != nil {
|
||||
_, _ = uc.security.IncrementLoginIP(ctx, attempt.IP, ipTTL)
|
||||
if config != nil && config.LockEnable {
|
||||
lockTTL := time.Duration(config.LockDuration) * time.Minute
|
||||
failures, _ := uc.security.IncrementLoginFailure(ctx, attempt.Username, lockTTL)
|
||||
if int(failures) >= config.LockThreshold {
|
||||
_ = uc.security.LockLogin(ctx, attempt.Username, lockTTL)
|
||||
}
|
||||
}
|
||||
uc.recordLogin(ctx, attempt, false, "用户名不存在或者密码错误", 0)
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
_, _ = uc.security.IncrementLoginIP(ctx, attempt.IP, ipTTL)
|
||||
uc.recordLogin(ctx, attempt, false, "用户被禁止登录", user.ID)
|
||||
return nil, &UserDisabledError{UserID: user.ID}
|
||||
}
|
||||
|
||||
uc.security.ClearLoginState(ctx, attempt.Username)
|
||||
if config != nil && config.PwdExpireEnable && user.PasswordUpdatedAt != nil && time.Since(*user.PasswordUpdatedAt) > time.Duration(config.PwdExpireDays)*24*time.Hour {
|
||||
user.MustChangePassword = true
|
||||
}
|
||||
issued, err := uc.issuer.IssueToken(user, user.AuthorityID, user.MustChangePassword, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTokenIssue, err)
|
||||
}
|
||||
if uc.security.UseMultipoint() {
|
||||
oldToken, cacheErr := uc.security.ActiveToken(ctx, user.Username)
|
||||
if cacheErr != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrLoginState, cacheErr)
|
||||
}
|
||||
if cacheErr = uc.security.RotateActiveToken(ctx, user.Username, oldToken, issued.Value, issued.TTL); cacheErr != nil {
|
||||
if errors.Is(cacheErr, ErrTokenRevoke) {
|
||||
return nil, cacheErr
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %v", ErrLoginState, cacheErr)
|
||||
}
|
||||
}
|
||||
uc.recordLogin(ctx, attempt, true, "登录成功", user.ID)
|
||||
return &AuthenticationResult{User: user, Token: issued.Value, ExpiresAt: issued.ExpiresAt, NeedChangePassword: user.MustChangePassword}, nil
|
||||
}
|
||||
|
||||
func (uc *AuthenticationUsecase) SwitchAuthority(ctx context.Context, userID, authorityID uint) (*AuthenticationResult, error) {
|
||||
if err := uc.users.SetUserAuthority(ctx, userID, authorityID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := uc.users.User(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issued, err := uc.issuer.IssueToken(user, authorityID, user.MustChangePassword, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uc.security.UseMultipoint() {
|
||||
oldToken, cacheErr := uc.security.ActiveToken(ctx, user.Username)
|
||||
if cacheErr != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrLoginState, cacheErr)
|
||||
}
|
||||
if cacheErr = uc.security.RotateActiveToken(ctx, user.Username, oldToken, issued.Value, issued.TTL); cacheErr != nil {
|
||||
if errors.Is(cacheErr, ErrTokenRevoke) {
|
||||
return nil, cacheErr
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %v", ErrLoginState, cacheErr)
|
||||
}
|
||||
}
|
||||
return &AuthenticationResult{User: user, Token: issued.Value, ExpiresAt: issued.ExpiresAt, NeedChangePassword: user.MustChangePassword}, nil
|
||||
}
|
||||
+1
-1
@@ -3,4 +3,4 @@ package biz
|
||||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSet is biz providers.
|
||||
var ProviderSet = wire.NewSet(NewUserUsecase, NewSystemConfigUsecase, NewAuthorityUsecase, NewAPIUsecase, NewPermissionUsecase, NewAccessControlUsecase, NewMenuUsecase, NewDepartmentUsecase, NewPositionUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewAuditRecorderUsecase, NewLogViewerUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase)
|
||||
var ProviderSet = wire.NewSet(NewUserUsecase, NewAuthenticationUsecase, NewSystemConfigUsecase, NewAuthorityUsecase, NewAPIUsecase, NewPermissionUsecase, NewAccessControlUsecase, NewMenuUsecase, NewDepartmentUsecase, NewPositionUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewAuditRecorderUsecase, NewLogViewerUsecase, NewTaskUsecase, NewTaskApplicationUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase)
|
||||
|
||||
@@ -31,3 +31,43 @@ type FileStorage interface {
|
||||
DeletePrefix(context.Context, string) error
|
||||
List(context.Context, string, string, int) ([]*StoredFile, string, bool, error)
|
||||
}
|
||||
|
||||
type JWTSettings struct {
|
||||
SigningKey string
|
||||
Issuer string
|
||||
Expires time.Duration
|
||||
Buffer time.Duration
|
||||
}
|
||||
|
||||
type CaptchaSettings struct {
|
||||
KeyLong int
|
||||
ImageWidth int
|
||||
ImageHeight int
|
||||
StoreExpiration time.Duration
|
||||
}
|
||||
|
||||
type MediaSettings struct {
|
||||
SessionTTL int
|
||||
MaxFileSize int64
|
||||
}
|
||||
|
||||
// RuntimeSettings exposes only the active values needed by the application.
|
||||
// The data implementation resolves every call from conf.Runtime so hot reloads
|
||||
// take effect without rebuilding services.
|
||||
type RuntimeSettings interface {
|
||||
RouterPrefix() string
|
||||
JWTSettings() JWTSettings
|
||||
CaptchaSettings() CaptchaSettings
|
||||
MediaSettings() MediaSettings
|
||||
UseMultipoint() bool
|
||||
}
|
||||
|
||||
type IssuedToken struct {
|
||||
Value string
|
||||
ExpiresAt time.Time
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
type TokenIssuer interface {
|
||||
IssueToken(*User, uint, bool, time.Duration) (*IssuedToken, error)
|
||||
}
|
||||
|
||||
+158
-4
@@ -3,6 +3,10 @@ package biz
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SecurityConfig struct {
|
||||
@@ -33,14 +37,54 @@ type SecurityRepo interface {
|
||||
SaveSecurityConfig(context.Context, *SecurityConfig) error
|
||||
}
|
||||
|
||||
type SecurityUsecase struct{ SecurityRepo }
|
||||
type SecurityUsecase struct {
|
||||
repo SecurityRepo
|
||||
cache Cache
|
||||
settings RuntimeSettings
|
||||
tokens *TokenUsecase
|
||||
mu sync.RWMutex
|
||||
cachedConfig *SecurityConfig
|
||||
}
|
||||
|
||||
func NewSecurityUsecase(repo SecurityRepo) *SecurityUsecase {
|
||||
return &SecurityUsecase{SecurityRepo: repo}
|
||||
var ErrTokenRevoke = errors.New("token revoke failed")
|
||||
|
||||
type TokenRevokeError struct{ Err error }
|
||||
|
||||
func (e *TokenRevokeError) Error() string { return e.Err.Error() }
|
||||
func (e *TokenRevokeError) Is(target error) bool { return target == ErrTokenRevoke }
|
||||
|
||||
func NewSecurityUsecase(repo SecurityRepo, cache Cache, settings RuntimeSettings, tokens *TokenUsecase) *SecurityUsecase {
|
||||
return &SecurityUsecase{repo: repo, cache: cache, settings: settings, tokens: tokens}
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) UpdateSecurity(ctx context.Context, value *SecurityConfig) error {
|
||||
return uc.SaveSecurityConfig(ctx, value)
|
||||
if err := uc.repo.SaveSecurityConfig(ctx, value); err != nil {
|
||||
return err
|
||||
}
|
||||
uc.mu.Lock()
|
||||
copy := *value
|
||||
uc.cachedConfig = ©
|
||||
uc.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) Current(ctx context.Context) (*SecurityConfig, error) {
|
||||
uc.mu.RLock()
|
||||
if uc.cachedConfig != nil {
|
||||
copy := *uc.cachedConfig
|
||||
uc.mu.RUnlock()
|
||||
return ©, nil
|
||||
}
|
||||
uc.mu.RUnlock()
|
||||
value, err := uc.repo.SecurityConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uc.mu.Lock()
|
||||
copy := *value
|
||||
uc.cachedConfig = ©
|
||||
uc.mu.Unlock()
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) ValidatePassword(value *SecurityConfig, password string) error {
|
||||
@@ -74,3 +118,113 @@ func (uc *SecurityUsecase) ValidatePassword(value *SecurityConfig, password stri
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) ValidateCurrentPassword(ctx context.Context, password string) error {
|
||||
config, err := uc.Current(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return uc.ValidatePassword(config, password)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) LoginLocked(ctx context.Context, username string) (bool, error) {
|
||||
_, locked, err := uc.cache.Get(ctx, "login:lock:"+username)
|
||||
return locked, err
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) IncrementLoginFailure(ctx context.Context, username string, expiration time.Duration) (int64, error) {
|
||||
return uc.cache.Increment(ctx, "login:fail:"+username, expiration)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) LockLogin(ctx context.Context, username string, expiration time.Duration) error {
|
||||
return uc.cache.Set(ctx, "login:lock:"+username, "1", expiration)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) ClearLoginState(ctx context.Context, username string) {
|
||||
_ = uc.cache.Delete(ctx, "login:fail:"+username)
|
||||
_ = uc.cache.Delete(ctx, "login:lock:"+username)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) EnsureLoginIPCounter(ctx context.Context, ip string, expiration time.Duration) (int, error) {
|
||||
value, exists, err := uc.cache.Get(ctx, ip)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if exists {
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
if expiration <= 0 {
|
||||
expiration = time.Hour
|
||||
}
|
||||
if err = uc.cache.Set(ctx, ip, "1", expiration); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) IncrementLoginIP(ctx context.Context, ip string, expiration time.Duration) (int64, error) {
|
||||
return uc.cache.Increment(ctx, ip, expiration)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) IncrementRateLimit(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
return uc.cache.Increment(ctx, key, expiration)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) SetCaptcha(ctx context.Context, id, value string, expiration time.Duration) error {
|
||||
return uc.cache.Set(ctx, "captcha:"+id, value, expiration)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) GetCaptcha(ctx context.Context, id string) (string, bool, error) {
|
||||
return uc.cache.Get(ctx, "captcha:"+id)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) DeleteCaptcha(ctx context.Context, id string) error {
|
||||
return uc.cache.Delete(ctx, "captcha:"+id)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) VerifyCaptcha(ctx context.Context, id, answer string, clear bool) bool {
|
||||
if id == "" || answer == "" {
|
||||
return false
|
||||
}
|
||||
value, ok, err := uc.GetCaptcha(ctx, id)
|
||||
if err != nil || !ok {
|
||||
return false
|
||||
}
|
||||
if clear {
|
||||
_ = uc.DeleteCaptcha(ctx, id)
|
||||
}
|
||||
return strings.EqualFold(value, answer)
|
||||
}
|
||||
|
||||
func activeTokenKey(username string) string { return "jwt:active:" + username }
|
||||
|
||||
func (uc *SecurityUsecase) ActiveToken(ctx context.Context, username string) (string, error) {
|
||||
value, _, err := uc.cache.Get(ctx, activeTokenKey(username))
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) ActiveTokenMatches(ctx context.Context, username, token string) (bool, error) {
|
||||
if !uc.settings.UseMultipoint() {
|
||||
return true, nil
|
||||
}
|
||||
active, ok, err := uc.cache.Get(ctx, activeTokenKey(username))
|
||||
return ok && active == token, err
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) RotateActiveToken(ctx context.Context, username, oldToken, newToken string, expiration time.Duration) error {
|
||||
if !uc.settings.UseMultipoint() {
|
||||
return nil
|
||||
}
|
||||
if oldToken != "" && oldToken != newToken {
|
||||
if err := uc.tokens.BlacklistToken(ctx, oldToken); err != nil {
|
||||
return &TokenRevokeError{Err: err}
|
||||
}
|
||||
}
|
||||
return uc.cache.Set(ctx, activeTokenKey(username), newToken, expiration)
|
||||
}
|
||||
|
||||
func (uc *SecurityUsecase) UseMultipoint() bool { return uc.settings.UseMultipoint() }
|
||||
|
||||
func (uc *SecurityUsecase) CaptchaRuntimeSettings() CaptchaSettings {
|
||||
return uc.settings.CaptchaSettings()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
package biz
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var ErrTaskRuntimeReload = errors.New("task runtime reload failed")
|
||||
|
||||
type TaskRuntimeReloadError struct{ Err error }
|
||||
|
||||
func (e *TaskRuntimeReloadError) Error() string { return e.Err.Error() }
|
||||
func (e *TaskRuntimeReloadError) Is(target error) bool { return target == ErrTaskRuntimeReload }
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Driver, Host, Port, User, Password, Name, Path, Config, Template, AdminPassword string
|
||||
@@ -16,10 +26,13 @@ type InitializationRepo interface {
|
||||
ReloadConfig(context.Context) error
|
||||
}
|
||||
|
||||
type SystemConfigUsecase struct{ repo InitializationRepo }
|
||||
type SystemConfigUsecase struct {
|
||||
repo InitializationRepo
|
||||
tasks TaskRuntime
|
||||
}
|
||||
|
||||
func NewSystemConfigUsecase(repo InitializationRepo) *SystemConfigUsecase {
|
||||
return &SystemConfigUsecase{repo: repo}
|
||||
func NewSystemConfigUsecase(repo InitializationRepo, tasks TaskRuntime) *SystemConfigUsecase {
|
||||
return &SystemConfigUsecase{repo: repo, tasks: tasks}
|
||||
}
|
||||
|
||||
func (uc *SystemConfigUsecase) IsInitialized(ctx context.Context) (bool, error) {
|
||||
@@ -29,7 +42,13 @@ func (uc *SystemConfigUsecase) Initialize(ctx context.Context, config *DatabaseC
|
||||
if config == nil || len(config.AdminPassword) < 6 {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
return uc.repo.Initialize(ctx, config)
|
||||
if err := uc.repo.Initialize(ctx, config); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uc.tasks.Reload(ctx); err != nil {
|
||||
return &TaskRuntimeReloadError{Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (uc *SystemConfigUsecase) PersistConfig(ctx context.Context) error {
|
||||
return uc.repo.PersistConfig(ctx)
|
||||
@@ -41,5 +60,11 @@ func (uc *SystemConfigUsecase) PersistRuntimeConfig(ctx context.Context, data, a
|
||||
return uc.repo.PersistRuntimeConfig(ctx, data, admin)
|
||||
}
|
||||
func (uc *SystemConfigUsecase) ReloadConfig(ctx context.Context) error {
|
||||
return uc.repo.ReloadConfig(ctx)
|
||||
if err := uc.repo.ReloadConfig(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uc.tasks.Reload(ctx); err != nil {
|
||||
return &TaskRuntimeReloadError{Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ const (
|
||||
TaskMethodUploads = "CleanStaleUploads"
|
||||
)
|
||||
|
||||
var ErrTaskSchedule = errors.New("task schedule failed")
|
||||
|
||||
type TaskScheduleError struct{ Err error }
|
||||
|
||||
func (e *TaskScheduleError) Error() string { return e.Err.Error() }
|
||||
func (e *TaskScheduleError) Is(target error) bool { return target == ErrTaskSchedule }
|
||||
|
||||
type TaskMethod struct {
|
||||
Name string
|
||||
Description string
|
||||
@@ -77,6 +84,17 @@ type TaskRepo interface {
|
||||
CleanupLogs(context.Context) error
|
||||
TaskNameExists(context.Context, string, uint) (bool, error)
|
||||
}
|
||||
|
||||
type TaskRuntime interface {
|
||||
ScheduleID(context.Context, uint) error
|
||||
Remove(uint)
|
||||
TriggerID(context.Context, uint) error
|
||||
NextRuns() map[uint]time.Time
|
||||
Reload(context.Context) error
|
||||
Subscribe() chan []byte
|
||||
Unsubscribe(chan []byte)
|
||||
}
|
||||
|
||||
type TaskUsecase struct{ TaskRepo }
|
||||
|
||||
func NewTaskUsecase(repo TaskRepo) *TaskUsecase { return &TaskUsecase{TaskRepo: repo} }
|
||||
@@ -149,3 +167,69 @@ func (uc *TaskUsecase) Update(ctx context.Context, value *TimedTask) error {
|
||||
}
|
||||
return uc.UpdateTask(ctx, value)
|
||||
}
|
||||
|
||||
type TaskApplicationUsecase struct {
|
||||
tasks *TaskUsecase
|
||||
runtime TaskRuntime
|
||||
}
|
||||
|
||||
func NewTaskApplicationUsecase(tasks *TaskUsecase, runtime TaskRuntime) *TaskApplicationUsecase {
|
||||
return &TaskApplicationUsecase{tasks: tasks, runtime: runtime}
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Create(ctx context.Context, value *TimedTask) error {
|
||||
if err := uc.tasks.Create(ctx, value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uc.runtime.ScheduleID(ctx, value.ID); err != nil {
|
||||
return &TaskScheduleError{Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Update(ctx context.Context, value *TimedTask) error {
|
||||
if err := uc.tasks.Update(ctx, value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uc.runtime.ScheduleID(ctx, value.ID); err != nil {
|
||||
return &TaskScheduleError{Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Delete(ctx context.Context, id uint) error {
|
||||
uc.runtime.Remove(id)
|
||||
return uc.tasks.DeleteTask(ctx, id)
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Toggle(ctx context.Context, id uint, enabled bool) error {
|
||||
if err := uc.tasks.ToggleTask(ctx, id, enabled); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uc.runtime.ScheduleID(ctx, id); err != nil {
|
||||
return &TaskScheduleError{Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Trigger(ctx context.Context, id uint) error {
|
||||
return uc.runtime.TriggerID(ctx, id)
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) List(ctx context.Context, page, size int, filter *TimedTask) ([]*TimedTask, int64, map[uint]time.Time, error) {
|
||||
items, total, err := uc.tasks.ListTasks(ctx, page, size, filter)
|
||||
return items, total, uc.runtime.NextRuns(), err
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]*TimedTaskLog, int64, error) {
|
||||
return uc.tasks.ListTaskLogs(ctx, page, size, taskID, status)
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Reload(ctx context.Context) error {
|
||||
return uc.runtime.Reload(ctx)
|
||||
}
|
||||
|
||||
func (uc *TaskApplicationUsecase) Subscribe() chan []byte { return uc.runtime.Subscribe() }
|
||||
func (uc *TaskApplicationUsecase) Unsubscribe(events chan []byte) {
|
||||
uc.runtime.Unsubscribe(events)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,14 @@ package data
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type accessRepo struct{ data *Data }
|
||||
type authorityAccessRepo struct{ data *Data }
|
||||
type apiRepo struct{ data *Data }
|
||||
type permissionRepo struct{ data *Data }
|
||||
|
||||
func NewAuthorityAccessRepo(data *Data) biz.AuthorityAccessRepo { return &accessRepo{data: data} }
|
||||
func NewAuthorityAccessRepo(data *Data) biz.AuthorityAccessRepo {
|
||||
return &authorityAccessRepo{data: data}
|
||||
}
|
||||
|
||||
func NewAPIRepo(data *Data) biz.APIRepo { return &accessRepo{data: data} }
|
||||
func NewAPIRepo(data *Data) biz.APIRepo { return &apiRepo{data: data} }
|
||||
|
||||
func NewPermissionRepo(data *Data) biz.PermissionRepo { return &accessRepo{data: data} }
|
||||
func NewPermissionRepo(data *Data) biz.PermissionRepo { return &permissionRepo{data: data} }
|
||||
|
||||
+14
-14
@@ -44,7 +44,7 @@ func (authorityAPIPO) TableName() string { return "sys_authority_apis" }
|
||||
func apiFromPO(po apiPO) *biz.API {
|
||||
return &biz.API{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Path: po.Path, Description: po.Description, APIGroup: po.APIGroup, Method: po.Method}
|
||||
}
|
||||
func (r *accessRepo) CreateAPI(ctx context.Context, v *biz.API) error {
|
||||
func (r *apiRepo) CreateAPI(ctx context.Context, v *biz.API) error {
|
||||
po := apiPO{Path: v.Path, Description: v.Description, APIGroup: v.APIGroup, Method: strings.ToUpper(v.Method)}
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", po.Path, po.Method).Count(&count).Error; err != nil {
|
||||
@@ -59,7 +59,7 @@ func (r *accessRepo) CreateAPI(ctx context.Context, v *biz.API) error {
|
||||
v.ID, v.CreatedAt, v.UpdatedAt, v.Method = po.ID, po.CreatedAt, po.UpdatedAt, po.Method
|
||||
return nil
|
||||
}
|
||||
func (r *accessRepo) UpdateAPI(ctx context.Context, v *biz.API) error {
|
||||
func (r *apiRepo) UpdateAPI(ctx context.Context, v *biz.API) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var old apiPO
|
||||
if err := db.First(&old, v.ID).Error; err != nil {
|
||||
@@ -77,7 +77,7 @@ func (r *accessRepo) UpdateAPI(ctx context.Context, v *biz.API) error {
|
||||
}
|
||||
return db.Model(&old).Updates(map[string]any{"path": v.Path, "description": v.Description, "api_group": v.APIGroup, "method": method}).Error
|
||||
}
|
||||
func (r *accessRepo) DeleteAPIs(ctx context.Context, ids []uint) error {
|
||||
func (r *apiRepo) DeleteAPIs(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("api_id IN ?", ids).Delete(&authorityAPIPO{}).Error; err != nil {
|
||||
return err
|
||||
@@ -85,14 +85,14 @@ func (r *accessRepo) DeleteAPIs(ctx context.Context, ids []uint) error {
|
||||
return tx.Delete(&apiPO{}, ids).Error
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) FindAPI(ctx context.Context, id uint) (*biz.API, error) {
|
||||
func (r *apiRepo) FindAPI(ctx context.Context, id uint) (*biz.API, error) {
|
||||
var po apiPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apiFromPO(po), nil
|
||||
}
|
||||
func (r *accessRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]*biz.API, int64, error) {
|
||||
func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]*biz.API, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&apiPO{})
|
||||
if q != nil && q.StrictAll {
|
||||
config := r.data.runtime.Admin()
|
||||
@@ -149,7 +149,7 @@ func (r *accessRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) (
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *accessRepo) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) {
|
||||
func (r *apiRepo) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_authority_apis").
|
||||
Joins("JOIN sys_apis ON sys_apis.id = sys_authority_apis.api_id").
|
||||
@@ -157,7 +157,7 @@ func (r *accessRepo) APIRoleIDs(ctx context.Context, path, method string) ([]uin
|
||||
Pluck("sys_authority_apis.authority_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetAPIRoles(ctx context.Context, path, method string, ids []uint) error {
|
||||
func (r *apiRepo) SetAPIRoles(ctx context.Context, path, method string, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var api apiPO
|
||||
if err := tx.Where("path = ? AND method = ?", path, strings.ToUpper(method)).First(&api).Error; err != nil {
|
||||
@@ -176,7 +176,7 @@ func (r *accessRepo) SetAPIRoles(ctx context.Context, path, method string, ids [
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
|
||||
func (r *apiRepo) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
|
||||
policies, err := r.PolicyPaths(ctx, aid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -204,7 +204,7 @@ m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)`)
|
||||
}
|
||||
return enforcer.Enforce(subject, path, strings.ToUpper(method))
|
||||
}
|
||||
func (r *accessRepo) PolicyPaths(ctx context.Context, aid uint) ([]*biz.API, error) {
|
||||
func (r *apiRepo) PolicyPaths(ctx context.Context, aid uint) ([]*biz.API, error) {
|
||||
var pos []apiPO
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_apis").Joins("JOIN sys_authority_apis ON sys_authority_apis.api_id = sys_apis.id").Where("sys_authority_apis.authority_id = ? AND sys_apis.deleted_at IS NULL", aid).Order("sys_apis.path,sys_apis.method").Find(&pos).Error
|
||||
if err != nil {
|
||||
@@ -216,8 +216,8 @@ func (r *accessRepo) PolicyPaths(ctx context.Context, aid uint) ([]*biz.API, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.API) error {
|
||||
if err := r.checkAuthorityIDAuth(ctx, aid); err != nil {
|
||||
func (r *apiRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.API) error {
|
||||
if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, aid); err != nil {
|
||||
return err
|
||||
}
|
||||
config := r.data.runtime.Admin()
|
||||
@@ -267,7 +267,7 @@ func (r *accessRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.
|
||||
})
|
||||
}
|
||||
|
||||
func (r *accessRepo) IgnoredAPIs(ctx context.Context) ([]*biz.API, error) {
|
||||
func (r *apiRepo) IgnoredAPIs(ctx context.Context) ([]*biz.API, error) {
|
||||
var pos []ignoredAPIPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("path,method").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -278,14 +278,14 @@ func (r *accessRepo) IgnoredAPIs(ctx context.Context) ([]*biz.API, error) {
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
|
||||
func (r *apiRepo) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
|
||||
po := ignoredAPIPO{Path: path, Method: strings.ToUpper(method)}
|
||||
if ignored {
|
||||
return r.data.gormDB.WithContext(ctx).FirstOrCreate(&po, po).Error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Where("path = ? AND method = ?", po.Path, po.Method).Delete(&ignoredAPIPO{}).Error
|
||||
}
|
||||
func (r *accessRepo) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error {
|
||||
func (r *apiRepo) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
for _, item := range added {
|
||||
po := apiPO{Path: item.Path, Method: strings.ToUpper(item.Method), Description: item.Description, APIGroup: item.APIGroup}
|
||||
|
||||
@@ -35,7 +35,7 @@ type jwtBlacklistPO struct {
|
||||
|
||||
func (jwtBlacklistPO) TableName() string { return "jwt_blacklists" }
|
||||
|
||||
func (r *settingsRepo) UserHasAuthority(ctx context.Context, userID, authorityID uint) (*biz.User, bool, error) {
|
||||
func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID uint) (*biz.User, bool, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, userID).Error; err != nil {
|
||||
return nil, false, err
|
||||
@@ -45,10 +45,10 @@ func (r *settingsRepo) UserHasAuthority(ctx context.Context, userID, authorityID
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
user, err := (&systemRepo{data: r.data}).toBizUser(ctx, &po)
|
||||
user, err := (&userRepo{data: r.data}).toBizUser(ctx, &po)
|
||||
return user, count > 0 || po.AuthorityID == authorityID, err
|
||||
}
|
||||
func (r *settingsRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) error {
|
||||
func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) error {
|
||||
po := apiTokenPO{UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
@@ -57,7 +57,7 @@ func (r *settingsRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) erro
|
||||
v.CreatedAt = po.CreatedAt
|
||||
return nil
|
||||
}
|
||||
func (r *settingsRepo) ListAPITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*biz.APIToken, int64, error) {
|
||||
func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*biz.APIToken, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&apiTokenPO{})
|
||||
if userID != 0 {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
@@ -94,14 +94,14 @@ func (r *settingsRepo) ListAPITokens(ctx context.Context, page, size int, userID
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *settingsRepo) DisableAPIToken(ctx context.Context, id uint) (string, error) {
|
||||
func (r *apiTokenRepo) DisableAPIToken(ctx context.Context, id uint) (string, error) {
|
||||
var po apiTokenPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
return po.Token, r.data.gormDB.WithContext(ctx).Model(&po).Update("status", false).Error
|
||||
}
|
||||
func (r *settingsRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint) error {
|
||||
func (r *apiTokenRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var po apiTokenPO
|
||||
if err := tx.First(&po, id).Error; err != nil {
|
||||
@@ -114,13 +114,13 @@ func (r *settingsRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint)
|
||||
return tx.Model(&po).Update("status", false).Error
|
||||
})
|
||||
}
|
||||
func (r *settingsRepo) BlacklistToken(ctx context.Context, token string) error {
|
||||
func (r *apiTokenRepo) BlacklistToken(ctx context.Context, token string) error {
|
||||
if token == "" {
|
||||
return errors.New("token is empty")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&jwtBlacklistPO{JWT: token}).Error
|
||||
}
|
||||
func (r *settingsRepo) IsTokenDisabled(ctx context.Context, token string) (bool, error) {
|
||||
func (r *apiTokenRepo) IsTokenDisabled(ctx context.Context, token string) (bool, error) {
|
||||
var blacklistCount int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&jwtBlacklistPO{}).Where("jwt = ?", token).Count(&blacklistCount).Error; err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -2,10 +2,12 @@ package data
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type auditRepo struct{ data *Data }
|
||||
type auditQueryRepo struct{ data *Data }
|
||||
type auditRecorderRepo struct{ data *Data }
|
||||
type logFileRepo struct{ data *Data }
|
||||
|
||||
func NewAuditRepo(data *Data) biz.AuditQueryRepo { return &auditRepo{data: data} }
|
||||
func NewAuditRepo(data *Data) biz.AuditQueryRepo { return &auditQueryRepo{data: data} }
|
||||
|
||||
func NewAuditRecorderRepo(data *Data) biz.AuditRecordRepo { return &auditRepo{data: data} }
|
||||
func NewAuditRecorderRepo(data *Data) biz.AuditRecordRepo { return &auditRecorderRepo{data: data} }
|
||||
|
||||
func NewLogFileRepo(data *Data) biz.LogFileRepo { return &auditRepo{data: data} }
|
||||
func NewLogFileRepo(data *Data) biz.LogFileRepo { return &logFileRepo{data: data} }
|
||||
|
||||
+12
-12
@@ -11,7 +11,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (r *accessRepo) strictAuthorityIDs(ctx context.Context, actorID uint) (map[uint]bool, error) {
|
||||
func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID uint) (map[uint]bool, error) {
|
||||
allowed := make(map[uint]bool)
|
||||
if actorID == 0 {
|
||||
return allowed, nil
|
||||
@@ -46,7 +46,7 @@ func (r *accessRepo) strictAuthorityIDs(ctx context.Context, actorID uint) (map[
|
||||
return allowed, nil
|
||||
}
|
||||
|
||||
func (r *accessRepo) checkAuthorityIDAuth(ctx context.Context, targetID uint) error {
|
||||
func (r *authorityAccessRepo) checkAuthorityIDAuth(ctx context.Context, targetID uint) error {
|
||||
config := r.data.runtime.Admin()
|
||||
if config == nil || config.System == nil || !config.System.UseStrictAuth {
|
||||
return nil
|
||||
@@ -65,7 +65,7 @@ func (r *accessRepo) checkAuthorityIDAuth(ctx context.Context, targetID uint) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *accessRepo) CreateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
config := r.data.runtime.Admin()
|
||||
if config != nil && config.System != nil && config.System.UseStrictAuth && (value.ParentID == nil || *value.ParentID == 0) {
|
||||
if actor, ok := biz.ActorFromContext(ctx); ok {
|
||||
@@ -104,7 +104,7 @@ func (r *accessRepo) CreateAuthority(ctx context.Context, value *biz.Authority)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *biz.Authority) error {
|
||||
func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *biz.Authority) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
config := r.data.runtime.Admin()
|
||||
actor, hasActor := biz.ActorFromContext(ctx)
|
||||
@@ -179,10 +179,10 @@ func (r *accessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *bi
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Updates(map[string]any{"authority_name": value.AuthorityName, "parent_id": value.ParentID, "data_scope": value.DataScope, "default_router": value.DefaultRouter}).Error
|
||||
}
|
||||
func (r *accessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
||||
func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
||||
if id == 888 {
|
||||
return errors.New("不能删除超级管理员")
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func (r *accessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
||||
return tx.Delete(&authorityPO{}, "authority_id = ?", id).Error
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
var pos []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -245,7 +245,7 @@ func (r *accessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
func (r *authorityAccessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var oldIDs []uint
|
||||
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &oldIDs).Error; err != nil {
|
||||
@@ -287,12 +287,12 @@ func (r *accessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
func (r *authorityAccessRepo) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptIDs []uint) error {
|
||||
func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptIDs []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", id).Update("data_scope", scope).Error; err != nil {
|
||||
return err
|
||||
@@ -312,12 +312,12 @@ func (r *accessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptI
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
func (r *authorityAccessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityDepartmentPO{}).Where("authority_id = ?", id).Pluck("department_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
|
||||
func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
|
||||
identity := biz.DataScope{UserID: userID, AuthorityID: authorityID}
|
||||
var user userPO
|
||||
_ = r.data.gormDB.WithContext(ctx).Select("id", "dept_id").First(&user, userID).Error
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewInitializationRepo, NewAuthorityAccessRepo, NewAPIRepo, NewPermissionRepo, NewMenuRepo, NewDepartmentRepo, NewPositionRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewAuditRecorderRepo, NewLogFileRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage)
|
||||
var ProviderSet = wire.NewSet(NewData, NewRuntimeSettings, NewTokenIssuer, NewUserRepo, NewInitializationRepo, NewAuthorityAccessRepo, NewAPIRepo, NewPermissionRepo, NewMenuRepo, NewDepartmentRepo, NewPositionRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewAuditRecorderRepo, NewLogFileRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage)
|
||||
|
||||
type Data struct {
|
||||
initMu sync.Mutex
|
||||
|
||||
@@ -22,13 +22,13 @@ type dataAccessLogPO struct {
|
||||
|
||||
func (dataAccessLogPO) TableName() string { return "sys_data_access_logs" }
|
||||
|
||||
func (r *auditRepo) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
|
||||
func (r *auditRecorderRepo) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&dataAccessLogPO{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}).Error
|
||||
}
|
||||
func dataAccessFromPO(v dataAccessLogPO) *biz.DataAccessLog {
|
||||
return &biz.DataAccessLog{ID: v.ID, CreatedAt: v.CreatedAt, 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 (r *auditRepo) ListDataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*biz.DataAccessLog, int64, error) {
|
||||
func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*biz.DataAccessLog, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dataAccessLogPO{})
|
||||
if q != nil {
|
||||
if q.EventType != "" {
|
||||
@@ -55,6 +55,6 @@ func (r *auditRepo) ListDataAccess(ctx context.Context, page, size int, q *biz.D
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *auditRepo) DeleteDataAccess(ctx context.Context, ids []uint) error {
|
||||
func (r *auditQueryRepo) DeleteDataAccess(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&dataAccessLogPO{}, ids).Error
|
||||
}
|
||||
|
||||
+10
-10
@@ -45,7 +45,7 @@ func deptFromPO(po departmentPO) *biz.Department {
|
||||
return &biz.Department{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID, Ancestors: po.Ancestors, Sort: po.Sort, LeaderID: po.LeaderID, Status: po.Status}
|
||||
}
|
||||
|
||||
func (r *organizationRepo) attachDepartmentLeaders(ctx context.Context, departments []*biz.Department) error {
|
||||
func (r *departmentRepo) attachDepartmentLeaders(ctx context.Context, departments []*biz.Department) error {
|
||||
leaderIDs := make([]uint, 0, len(departments))
|
||||
for _, department := range departments {
|
||||
if department.LeaderID != 0 {
|
||||
@@ -68,7 +68,7 @@ func (r *organizationRepo) attachDepartmentLeaders(ctx context.Context, departme
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (r *organizationRepo) CreateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
func (r *departmentRepo) CreateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
v.Ancestors = "0"
|
||||
if v.ParentID != 0 {
|
||||
var parent departmentPO
|
||||
@@ -79,7 +79,7 @@ func (r *organizationRepo) CreateDepartment(ctx context.Context, v *biz.Departme
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error
|
||||
}
|
||||
func (r *organizationRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
func (r *departmentRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
if v.ParentID == v.ID {
|
||||
return errors.New("父部门不能是自己")
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (r *organizationRepo) UpdateDepartment(ctx context.Context, v *biz.Departme
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error
|
||||
}
|
||||
func (r *organizationRepo) DeleteDepartment(ctx context.Context, id uint) error {
|
||||
func (r *departmentRepo) DeleteDepartment(ctx context.Context, id uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("部门ID不能为空")
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (r *organizationRepo) DeleteDepartment(ctx context.Context, id uint) error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&departmentPO{}, id).Error
|
||||
}
|
||||
func (r *organizationRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) {
|
||||
func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) {
|
||||
var po departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -129,7 +129,7 @@ func (r *organizationRepo) FindDepartment(ctx context.Context, id uint) (*biz.De
|
||||
}
|
||||
return department, nil
|
||||
}
|
||||
func (r *organizationRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
||||
func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
||||
var pos []departmentPO
|
||||
db := r.data.gormDB.WithContext(ctx).Order("sort,id")
|
||||
if name != "" {
|
||||
@@ -162,15 +162,15 @@ func (r *organizationRepo) ListDepartments(ctx context.Context, name string) ([]
|
||||
}
|
||||
return roots, nil
|
||||
}
|
||||
func (r *organizationRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
func (r *departmentRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *organizationRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
func (r *departmentRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
return r.replaceUserDepartments(ctx, id, ids)
|
||||
}
|
||||
func (r *organizationRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error {
|
||||
func (r *departmentRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("部门ID不能为空")
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func (r *organizationRepo) replaceUserDepartments(ctx context.Context, id uint,
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *organizationRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error {
|
||||
func (r *departmentRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var user userPO
|
||||
if err := tx.First(&user, uid).Error; err != nil {
|
||||
|
||||
+13
-13
@@ -54,7 +54,7 @@ func parameterFromPO(po parameterPO) *biz.SystemParameter {
|
||||
return &biz.SystemParameter{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Key: po.Key, Value: po.Value, Desc: po.Desc}
|
||||
}
|
||||
|
||||
func (r *settingsRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error {
|
||||
func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error {
|
||||
po := dictionaryPO{Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
@@ -63,7 +63,7 @@ func (r *settingsRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *settingsRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dictionary, details []*biz.DictionaryDetail) error {
|
||||
func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dictionary, details []*biz.DictionaryDetail) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := dictionaryPO{Name: dictionary.Name, Type: dictionary.Type, Status: dictionary.Status, Desc: dictionary.Desc, ParentID: dictionary.ParentID}
|
||||
if err := tx.Create(&po).Error; err != nil {
|
||||
@@ -103,10 +103,10 @@ func (r *settingsRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dic
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *settingsRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error {
|
||||
func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error
|
||||
}
|
||||
func (r *settingsRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
||||
func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var count int64
|
||||
if err := tx.Model(&dictionaryPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
@@ -121,7 +121,7 @@ func (r *settingsRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
||||
return tx.Delete(&dictionaryPO{}, id).Error
|
||||
})
|
||||
}
|
||||
func (r *settingsRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
|
||||
func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
|
||||
var po dictionaryPO
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
active := true
|
||||
@@ -148,7 +148,7 @@ func (r *settingsRepo) FindDictionary(ctx context.Context, id uint, typ string,
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (r *settingsRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dictionary, error) {
|
||||
func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dictionary, error) {
|
||||
var po dictionaryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -161,7 +161,7 @@ func (r *settingsRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dict
|
||||
value.Details = details
|
||||
return value, nil
|
||||
}
|
||||
func (r *settingsRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) {
|
||||
func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{})
|
||||
if name != "" {
|
||||
like := "%" + name + "%"
|
||||
@@ -219,7 +219,7 @@ func (r *settingsRepo) ListDictionaries(ctx context.Context, page, size int, nam
|
||||
return out, total, nil
|
||||
}
|
||||
|
||||
func (r *settingsRepo) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
|
||||
func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
|
||||
po := dictionaryDetailPO{Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID}
|
||||
po.Level = 0
|
||||
po.Path = ""
|
||||
@@ -239,7 +239,7 @@ func (r *settingsRepo) CreateDictionaryDetail(ctx context.Context, v *biz.Dictio
|
||||
v.Path = po.Path
|
||||
return nil
|
||||
}
|
||||
func (r *settingsRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
|
||||
func (r *dictionaryRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
|
||||
var po dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, v.ID).Error; err != nil {
|
||||
return err
|
||||
@@ -268,7 +268,7 @@ func (r *settingsRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.Dictio
|
||||
return tx.Model(&dictionaryDetailPO{}).Where("path = ? OR path LIKE ?", oldPrefix, oldPrefix+",%").Update("path", gorm.Expr("REPLACE(path, ?, ?)", oldPrefix, newPrefix)).Error
|
||||
})
|
||||
}
|
||||
func (r *settingsRepo) DeleteDictionaryDetail(ctx context.Context, id uint) error {
|
||||
func (r *dictionaryRepo) DeleteDictionaryDetail(ctx context.Context, id uint) error {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
@@ -278,14 +278,14 @@ func (r *settingsRepo) DeleteDictionaryDetail(ctx context.Context, id uint) erro
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error
|
||||
}
|
||||
func (r *settingsRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) {
|
||||
func (r *dictionaryRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) {
|
||||
var po dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return detailFromPO(po), nil
|
||||
}
|
||||
func (r *settingsRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*biz.DictionaryDetail, int64, error) {
|
||||
func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*biz.DictionaryDetail, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{})
|
||||
if filter.DictionaryID != 0 {
|
||||
db = db.Where("sys_dictionary_id = ?", filter.DictionaryID)
|
||||
@@ -319,7 +319,7 @@ func (r *settingsRepo) ListDictionaryDetails(ctx context.Context, page, size int
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *settingsRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*biz.DictionaryDetail, error) {
|
||||
func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*biz.DictionaryDetail, error) {
|
||||
if dictionaryID == 0 {
|
||||
active := true
|
||||
dictionary, err := r.FindDictionary(ctx, 0, typ, &active, false)
|
||||
|
||||
@@ -28,26 +28,26 @@ func (errorRecordPO) TableName() string { return "sys_error" }
|
||||
func errorFromPO(v errorRecordPO) *biz.ErrorRecord {
|
||||
return &biz.ErrorRecord{ID: v.ID, CreatedAt: v.CreatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}
|
||||
}
|
||||
func (r *auditRepo) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
func (r *auditRecorderRepo) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
if v.Status == "" {
|
||||
v.Status = "未处理"
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&errorRecordPO{Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}).Error
|
||||
}
|
||||
func (r *auditRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
func (r *auditQueryRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&errorRecordPO{}).Where("id = ?", v.ID).Updates(map[string]any{"form": v.Form, "info": v.Info, "level": v.Level, "solution": v.Solution, "status": v.Status}).Error
|
||||
}
|
||||
func (r *auditRepo) DeleteErrors(ctx context.Context, ids []uint) error {
|
||||
func (r *auditQueryRepo) DeleteErrors(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&errorRecordPO{}, ids).Error
|
||||
}
|
||||
func (r *auditRepo) FindError(ctx context.Context, id uint) (*biz.ErrorRecord, error) {
|
||||
func (r *auditQueryRepo) FindError(ctx context.Context, id uint) (*biz.ErrorRecord, error) {
|
||||
var po errorRecordPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return errorFromPO(po), nil
|
||||
}
|
||||
func (r *auditRepo) ListErrors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*biz.ErrorRecord, int64, error) {
|
||||
func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*biz.ErrorRecord, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&errorRecordPO{})
|
||||
if q != nil {
|
||||
if len(q.CreatedAtRange) == 2 {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"kra/internal/biz"
|
||||
)
|
||||
|
||||
func (r *auditRepo) logRoot() (string, error) {
|
||||
func (r *logFileRepo) logRoot() (string, error) {
|
||||
admin := r.data.runtime.Admin()
|
||||
if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" {
|
||||
return "", biz.ErrLogRootUnavailable
|
||||
@@ -35,7 +35,7 @@ func (r *auditRepo) logRoot() (string, error) {
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
func (r *auditRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
||||
func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
||||
if parsed, err := time.Parse("2006-01", month); err != nil || parsed.Format("2006-01") != month {
|
||||
return nil, biz.ErrInvalidLogMonth
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func (r *auditRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate,
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
|
||||
return out, nil
|
||||
}
|
||||
func (r *auditRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
|
||||
func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
|
||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
||||
return nil, biz.ErrInvalidLogDate
|
||||
}
|
||||
@@ -115,7 +115,7 @@ func (r *auditRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, e
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
|
||||
return out, err
|
||||
}
|
||||
func (r *auditRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) {
|
||||
func (r *logFileRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) {
|
||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
||||
return nil, biz.ErrInvalidLogDate
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ type loginLogPO struct {
|
||||
|
||||
func (loginLogPO) TableName() string { return "sys_login_logs" }
|
||||
|
||||
func (r *auditRepo) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
|
||||
func (r *auditRecorderRepo) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&loginLogPO{Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}).Error
|
||||
}
|
||||
func loginFromPO(v loginLogPO) *biz.LoginLog {
|
||||
return &biz.LoginLog{ID: v.ID, CreatedAt: v.CreatedAt, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}
|
||||
}
|
||||
func (r *auditRepo) ListLogins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*biz.LoginLog, int64, error) {
|
||||
func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*biz.LoginLog, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&loginLogPO{})
|
||||
if q != nil {
|
||||
if q.Username != "" {
|
||||
@@ -61,13 +61,13 @@ func (r *auditRepo) ListLogins(ctx context.Context, page, size int, q *biz.Login
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *auditRepo) FindLogin(ctx context.Context, id uint) (*biz.LoginLog, error) {
|
||||
func (r *auditQueryRepo) FindLogin(ctx context.Context, id uint) (*biz.LoginLog, error) {
|
||||
var po loginLogPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loginFromPO(po), nil
|
||||
}
|
||||
func (r *auditRepo) DeleteLogins(ctx context.Context, ids []uint) error {
|
||||
func (r *auditQueryRepo) DeleteLogins(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&loginLogPO{}, ids).Error
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) {
|
||||
}
|
||||
|
||||
func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error {
|
||||
if err := (&accessRepo{data: r.data}).checkAuthorityIDAuth(ctx, id); err != nil {
|
||||
if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
config := r.data.runtime.Admin()
|
||||
|
||||
@@ -29,13 +29,13 @@ type operationPO struct {
|
||||
|
||||
func (operationPO) TableName() string { return "sys_operation_records" }
|
||||
|
||||
func (r *auditRepo) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
|
||||
func (r *auditRecorderRepo) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&operationPO{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}).Error
|
||||
}
|
||||
func opFromPO(v operationPO) *biz.OperationRecord {
|
||||
return &biz.OperationRecord{ID: v.ID, CreatedAt: v.CreatedAt, 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}
|
||||
}
|
||||
func (r *auditRepo) ListOperations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*biz.OperationRecord, int64, error) {
|
||||
func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*biz.OperationRecord, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&operationPO{})
|
||||
if q != nil {
|
||||
if q.Path != "" {
|
||||
@@ -90,13 +90,13 @@ func auditUsers(ctx context.Context, db *gorm.DB, ids []uint) map[uint]*biz.User
|
||||
}
|
||||
return result
|
||||
}
|
||||
func (r *auditRepo) FindOperation(ctx context.Context, id uint) (*biz.OperationRecord, error) {
|
||||
func (r *auditQueryRepo) FindOperation(ctx context.Context, id uint) (*biz.OperationRecord, error) {
|
||||
var po operationPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return opFromPO(po), nil
|
||||
}
|
||||
func (r *auditRepo) DeleteOperations(ctx context.Context, ids []uint) error {
|
||||
func (r *auditQueryRepo) DeleteOperations(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&operationPO{}, ids).Error
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package data
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type organizationRepo struct{ data *Data }
|
||||
type departmentRepo struct{ data *Data }
|
||||
type positionRepo struct{ data *Data }
|
||||
|
||||
func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &organizationRepo{data: data} }
|
||||
func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &departmentRepo{data: data} }
|
||||
|
||||
func NewPositionRepo(data *Data) biz.PositionRepo { return &organizationRepo{data: data} }
|
||||
func NewPositionRepo(data *Data) biz.PositionRepo { return &positionRepo{data: data} }
|
||||
|
||||
@@ -23,7 +23,7 @@ type parameterPO struct {
|
||||
|
||||
func (parameterPO) TableName() string { return "sys_params" }
|
||||
|
||||
func (r *settingsRepo) CreateParameter(ctx context.Context, v *biz.SystemParameter) error {
|
||||
func (r *parameterRepo) CreateParameter(ctx context.Context, v *biz.SystemParameter) error {
|
||||
po := parameterPO{Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
@@ -31,13 +31,13 @@ func (r *settingsRepo) CreateParameter(ctx context.Context, v *biz.SystemParamet
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
func (r *settingsRepo) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error {
|
||||
func (r *parameterRepo) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(¶meterPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "key": v.Key, "value": v.Value, "desc": v.Desc}).Error
|
||||
}
|
||||
func (r *settingsRepo) DeleteParameters(ctx context.Context, ids []uint) error {
|
||||
func (r *parameterRepo) DeleteParameters(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(¶meterPO{}, ids).Error
|
||||
}
|
||||
func (r *settingsRepo) FindParameter(ctx context.Context, id uint, key string) (*biz.SystemParameter, error) {
|
||||
func (r *parameterRepo) FindParameter(ctx context.Context, id uint, key string) (*biz.SystemParameter, error) {
|
||||
var po parameterPO
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var err error
|
||||
@@ -51,7 +51,7 @@ func (r *settingsRepo) FindParameter(ctx context.Context, id uint, key string) (
|
||||
}
|
||||
return parameterFromPO(po), nil
|
||||
}
|
||||
func (r *settingsRepo) ListParameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*biz.SystemParameter, int64, error) {
|
||||
func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*biz.SystemParameter, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(¶meterPO{})
|
||||
if q != nil {
|
||||
if q.StartCreatedAt != nil && q.EndCreatedAt != nil {
|
||||
|
||||
@@ -25,7 +25,7 @@ type authorityButtonPO struct {
|
||||
|
||||
func (authorityButtonPO) TableName() string { return "sys_authority_btns" }
|
||||
|
||||
func (r *accessRepo) Buttons(ctx context.Context, menuID uint) ([]*biz.MenuButton, error) {
|
||||
func (r *permissionRepo) Buttons(ctx context.Context, menuID uint) ([]*biz.MenuButton, error) {
|
||||
var pos []menuButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menuID).Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -36,7 +36,7 @@ func (r *accessRepo) Buttons(ctx context.Context, menuID uint) ([]*biz.MenuButto
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *accessRepo) SetAuthorityButtons(ctx context.Context, aid uint, buttons map[uint][]uint) error {
|
||||
func (r *permissionRepo) SetAuthorityButtons(ctx context.Context, aid uint, buttons map[uint][]uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ?", aid).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
@@ -53,12 +53,12 @@ func (r *accessRepo) SetAuthorityButtons(ctx context.Context, aid uint, buttons
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
|
||||
func (r *permissionRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Pluck("sys_base_menu_btn_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *accessRepo) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
|
||||
func (r *permissionRepo) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
@@ -73,12 +73,12 @@ func (r *accessRepo) SetSelectedButtons(ctx context.Context, aid, menuID uint, i
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *accessRepo) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
|
||||
func (r *permissionRepo) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
|
||||
var count int64
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("sys_base_menu_btn_id = ?", id).Count(&count).Error
|
||||
return count == 0, err
|
||||
}
|
||||
func (r *accessRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, error) {
|
||||
func (r *permissionRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ?", aid).Pluck("sys_base_menu_btn_id", &ids).Error
|
||||
return ids, err
|
||||
|
||||
@@ -34,13 +34,13 @@ func (userPositionPO) TableName() string { return "sys_user_positions" }
|
||||
func posFromPO(po positionPO) *biz.Position {
|
||||
return &biz.Position{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Code: po.Code, Sort: po.Sort, Status: po.Status, Remark: po.Remark}
|
||||
}
|
||||
func (r *organizationRepo) CreatePosition(ctx context.Context, v *biz.Position) error {
|
||||
func (r *positionRepo) CreatePosition(ctx context.Context, v *biz.Position) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&positionPO{Name: v.Name, Code: v.Code, Sort: v.Sort, Status: v.Status, Remark: v.Remark}).Error
|
||||
}
|
||||
func (r *organizationRepo) UpdatePosition(ctx context.Context, v *biz.Position) error {
|
||||
func (r *positionRepo) UpdatePosition(ctx context.Context, v *biz.Position) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&positionPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}).Error
|
||||
}
|
||||
func (r *organizationRepo) DeletePosition(ctx context.Context, id uint) error {
|
||||
func (r *positionRepo) DeletePosition(ctx context.Context, id uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("岗位ID不能为空")
|
||||
}
|
||||
@@ -53,14 +53,14 @@ func (r *organizationRepo) DeletePosition(ctx context.Context, id uint) error {
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&positionPO{}, id).Error
|
||||
}
|
||||
func (r *organizationRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) {
|
||||
func (r *positionRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) {
|
||||
var po positionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return posFromPO(po), nil
|
||||
}
|
||||
func (r *organizationRepo) ListPositions(ctx context.Context, page, size int, q *biz.PositionListFilter) ([]*biz.Position, int64, error) {
|
||||
func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *biz.PositionListFilter) ([]*biz.Position, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&positionPO{})
|
||||
if q != nil {
|
||||
if q.Name != "" {
|
||||
@@ -87,12 +87,12 @@ func (r *organizationRepo) ListPositions(ctx context.Context, page, size int, q
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
func (r *organizationRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
func (r *positionRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *organizationRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
func (r *positionRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("岗位ID不能为空")
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (r *organizationRepo) SetPositionUsers(ctx context.Context, id uint, ids []
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *organizationRepo) SetUserPositions(ctx context.Context, uid uint, ids []uint) error {
|
||||
func (r *positionRepo) SetUserPositions(ctx context.Context, uid uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", uid).Delete(&userPositionPO{}).Error; err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/pkg/adminauth"
|
||||
)
|
||||
|
||||
type runtimeSettings struct{ runtime *conf.Runtime }
|
||||
|
||||
func NewRuntimeSettings(runtime *conf.Runtime) biz.RuntimeSettings {
|
||||
return &runtimeSettings{runtime: runtime}
|
||||
}
|
||||
|
||||
func (s *runtimeSettings) RouterPrefix() string {
|
||||
config := s.runtime.Admin()
|
||||
if config == nil {
|
||||
return ""
|
||||
}
|
||||
return config.RouterPrefix
|
||||
}
|
||||
|
||||
func (s *runtimeSettings) JWTSettings() biz.JWTSettings {
|
||||
value := biz.JWTSettings{Issuer: "kra", Expires: 7 * 24 * time.Hour, Buffer: 24 * time.Hour}
|
||||
config := s.runtime.Admin()
|
||||
if config == nil || config.Jwt == nil {
|
||||
return value
|
||||
}
|
||||
value.SigningKey = config.Jwt.SigningKey
|
||||
if config.Jwt.Issuer != "" {
|
||||
value.Issuer = config.Jwt.Issuer
|
||||
}
|
||||
if config.Jwt.ExpiresTime != nil {
|
||||
value.Expires = config.Jwt.ExpiresTime.AsDuration()
|
||||
}
|
||||
if config.Jwt.BufferTime != nil {
|
||||
value.Buffer = config.Jwt.BufferTime.AsDuration()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *runtimeSettings) CaptchaSettings() biz.CaptchaSettings {
|
||||
value := biz.CaptchaSettings{KeyLong: 6, ImageWidth: 240, ImageHeight: 80, StoreExpiration: 3 * time.Minute}
|
||||
config := s.runtime.Admin()
|
||||
if config == nil || config.Captcha == nil {
|
||||
return value
|
||||
}
|
||||
if config.Captcha.KeyLong > 0 {
|
||||
value.KeyLong = int(config.Captcha.KeyLong)
|
||||
}
|
||||
if config.Captcha.ImgWidth > 0 {
|
||||
value.ImageWidth = int(config.Captcha.ImgWidth)
|
||||
}
|
||||
if config.Captcha.ImgHeight > 0 {
|
||||
value.ImageHeight = int(config.Captcha.ImgHeight)
|
||||
}
|
||||
if config.Captcha.StoreExpiration != nil {
|
||||
value.StoreExpiration = config.Captcha.StoreExpiration.AsDuration()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *runtimeSettings) MediaSettings() biz.MediaSettings {
|
||||
config := s.runtime.Admin()
|
||||
if config == nil || config.Media == nil {
|
||||
return biz.MediaSettings{}
|
||||
}
|
||||
return biz.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize}
|
||||
}
|
||||
|
||||
func (s *runtimeSettings) UseMultipoint() bool {
|
||||
config := s.runtime.Admin()
|
||||
return config != nil && config.System != nil && config.System.UseMultipoint
|
||||
}
|
||||
|
||||
type tokenIssuer struct{ settings biz.RuntimeSettings }
|
||||
|
||||
func NewTokenIssuer(settings biz.RuntimeSettings) biz.TokenIssuer {
|
||||
return &tokenIssuer{settings: settings}
|
||||
}
|
||||
|
||||
func (i *tokenIssuer) IssueToken(user *biz.User, authorityID uint, mustChangePassword bool, expires time.Duration) (*biz.IssuedToken, error) {
|
||||
settings := i.settings.JWTSettings()
|
||||
if expires <= 0 {
|
||||
expires = settings.Expires
|
||||
}
|
||||
token, claims, err := adminauth.Generate(settings.SigningKey, settings.Issuer, expires, settings.Buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, mustChangePassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &biz.IssuedToken{Value: token, ExpiresAt: claims.ExpiresAt.Time, TTL: expires}, nil
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func securityFromPO(v securityConfigPO) *biz.SecurityConfig {
|
||||
func securityToPO(v *biz.SecurityConfig) securityConfigPO {
|
||||
return securityConfigPO{ID: 1, 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 (r *settingsRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig, error) {
|
||||
func (r *securityRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig, error) {
|
||||
var po securityConfigPO
|
||||
err := r.data.gormDB.WithContext(ctx).First(&po, 1).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -56,7 +56,7 @@ func (r *settingsRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig,
|
||||
}
|
||||
return securityFromPO(po), nil
|
||||
}
|
||||
func (r *settingsRepo) SaveSecurityConfig(ctx context.Context, v *biz.SecurityConfig) error {
|
||||
func (r *securityRepo) SaveSecurityConfig(ctx context.Context, v *biz.SecurityConfig) error {
|
||||
previous, err := r.SecurityConfig(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,12 +2,15 @@ package data
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type settingsRepo struct{ data *Data }
|
||||
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 &settingsRepo{data: data} }
|
||||
func NewDictionaryRepo(data *Data) biz.DictionaryRepo { return &dictionaryRepo{data: data} }
|
||||
|
||||
func NewParameterRepo(data *Data) biz.ParameterRepo { return &settingsRepo{data: data} }
|
||||
func NewParameterRepo(data *Data) biz.ParameterRepo { return ¶meterRepo{data: data} }
|
||||
|
||||
func NewAPITokenRepo(data *Data) biz.APITokenRepo { return &settingsRepo{data: data} }
|
||||
func NewAPITokenRepo(data *Data) biz.APITokenRepo { return &apiTokenRepo{data: data} }
|
||||
|
||||
func NewSecurityRepo(data *Data) biz.SecurityRepo { return &settingsRepo{data: data} }
|
||||
func NewSecurityRepo(data *Data) biz.SecurityRepo { return &securityRepo{data: data} }
|
||||
|
||||
@@ -90,8 +90,9 @@ type menuParameterPO struct {
|
||||
|
||||
func (menuParameterPO) TableName() string { return "sys_base_menu_parameters" }
|
||||
|
||||
type systemRepo struct{ data *Data }
|
||||
type userRepo struct{ data *Data }
|
||||
type initializationRepo struct{ data *Data }
|
||||
|
||||
func NewUserRepo(data *Data) biz.UserRepo { return &systemRepo{data: data} }
|
||||
func NewUserRepo(data *Data) biz.UserRepo { return &userRepo{data: data} }
|
||||
|
||||
func NewInitializationRepo(data *Data) biz.InitializationRepo { return &systemRepo{data: data} }
|
||||
func NewInitializationRepo(data *Data) biz.InitializationRepo { return &initializationRepo{data: data} }
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (r *systemRepo) PersistConfig(context.Context) error { return r.data.persistConfig() }
|
||||
func (r *systemRepo) PersistAdminConfig(_ context.Context, raw []byte) error {
|
||||
func (r *initializationRepo) PersistConfig(context.Context) error { return r.data.persistConfig() }
|
||||
func (r *initializationRepo) PersistAdminConfig(_ context.Context, raw []byte) error {
|
||||
currentData, currentAdmin := r.data.runtime.Values()
|
||||
next := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
||||
if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(raw, next); err != nil {
|
||||
@@ -33,7 +33,7 @@ func (r *systemRepo) PersistAdminConfig(_ context.Context, raw []byte) error {
|
||||
r.data.runtime.Replace(currentData, next)
|
||||
return nil
|
||||
}
|
||||
func (r *systemRepo) PersistRuntimeConfig(_ context.Context, dataRaw, adminRaw []byte) error {
|
||||
func (r *initializationRepo) PersistRuntimeConfig(_ context.Context, dataRaw, adminRaw []byte) error {
|
||||
currentData, currentAdmin := r.data.runtime.Values()
|
||||
nextData := proto.Clone(currentData).(*conf.Data)
|
||||
nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
||||
@@ -51,11 +51,11 @@ func (r *systemRepo) PersistRuntimeConfig(_ context.Context, dataRaw, adminRaw [
|
||||
r.data.runtime.Replace(nextData, nextAdmin)
|
||||
return nil
|
||||
}
|
||||
func (r *systemRepo) ReloadConfig(ctx context.Context) error {
|
||||
func (r *initializationRepo) ReloadConfig(ctx context.Context) error {
|
||||
return r.data.reloadConfig(ctx)
|
||||
}
|
||||
|
||||
func (r *systemRepo) IsInitialized(ctx context.Context) (bool, error) {
|
||||
func (r *initializationRepo) IsInitialized(ctx context.Context) (bool, error) {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
if !db.Migrator().HasTable(&userPO{}) {
|
||||
return false, nil
|
||||
@@ -67,7 +67,7 @@ func (r *systemRepo) IsInitialized(ctx context.Context) (bool, error) {
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error {
|
||||
func (r *initializationRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error {
|
||||
config := &conf.Data_Database{Driver: input.Driver, Host: input.Host, Port: input.Port, User: input.User, Password: input.Password, Name: input.Name, Path: input.Path, Config: input.Config}
|
||||
r.data.initMu.Lock()
|
||||
defer r.data.initMu.Unlock()
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestUserAuthorityWritesAreAtomic(t *testing.T) {
|
||||
if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{{AuthorityID: 888, AuthorityName: "admin"}, {AuthorityID: 999, AuthorityName: "operator"}}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo := &systemRepo{data: data}
|
||||
repo := &userRepo{data: data}
|
||||
user := &biz.User{Username: "atomic", Password: "hash", NickName: "before", AuthorityID: 888, Enable: 1}
|
||||
created, err := repo.CreateUserWithAuthorities(ctx, user, []uint{888, 999})
|
||||
if err != nil {
|
||||
@@ -66,7 +66,7 @@ func TestUserAuthorityWritesAreAtomic(t *testing.T) {
|
||||
func TestDictionaryImportKeepsHierarchyInOneTransaction(t *testing.T) {
|
||||
data := newTransactionTestData(t)
|
||||
ctx := context.Background()
|
||||
repo := &settingsRepo{data: data}
|
||||
repo := &dictionaryRepo{data: data}
|
||||
parentID := uint(10)
|
||||
dictionary := &biz.Dictionary{Name: "status", Type: "status", Status: true}
|
||||
details := []*biz.DictionaryDetail{{ID: 10, Label: "parent", Value: "1", Status: true}, {ID: 11, Label: "child", Value: "2", ParentID: &parentID, Status: true}}
|
||||
|
||||
+20
-20
@@ -14,7 +14,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (r *systemRepo) FindUserByUsername(ctx context.Context, username string) (*biz.User, error) {
|
||||
func (r *userRepo) FindUserByUsername(ctx context.Context, username string) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("username = ?", username).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -25,7 +25,7 @@ func (r *systemRepo) FindUserByUsername(ctx context.Context, username string) (*
|
||||
return r.toBizUser(ctx, &po)
|
||||
}
|
||||
|
||||
func (r *systemRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) {
|
||||
func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -36,7 +36,7 @@ func (r *systemRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, erro
|
||||
return r.toBizUser(ctx, &po)
|
||||
}
|
||||
|
||||
func (r *systemRepo) toBizUser(ctx context.Context, po *userPO) (*biz.User, error) {
|
||||
func (r *userRepo) toBizUser(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 *systemRepo) toBizUsers(ctx context.Context, pos []userPO) ([]*biz.User, error) {
|
||||
func (r *userRepo) toBizUsers(ctx context.Context, pos []userPO) ([]*biz.User, error) {
|
||||
if len(pos) == 0 {
|
||||
return []*biz.User{}, nil
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func (r *systemRepo) toBizUsers(ctx context.Context, pos []userPO) ([]*biz.User,
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*biz.Menu, error) {
|
||||
func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*biz.Menu, error) {
|
||||
var pos []menuPO
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_base_menus").
|
||||
Joins("JOIN sys_authority_menus ON sys_authority_menus.sys_base_menu_id = sys_base_menus.id").
|
||||
@@ -243,7 +243,7 @@ func (r *systemRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*biz.User, int64, error) {
|
||||
func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*biz.User, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&userPO{})
|
||||
order := "id desc"
|
||||
if filter != nil {
|
||||
@@ -279,11 +279,11 @@ func (r *systemRepo) ListUsers(ctx context.Context, page, pageSize int, filter *
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
func (r *systemRepo) CreateUser(ctx context.Context, user *biz.User) (*biz.User, error) {
|
||||
func (r *userRepo) CreateUser(ctx context.Context, user *biz.User) (*biz.User, error) {
|
||||
return r.CreateUserWithAuthorities(ctx, user, nil)
|
||||
}
|
||||
|
||||
func (r *systemRepo) CreateUserWithAuthorities(ctx context.Context, user *biz.User, authorityIDs []uint) (*biz.User, error) {
|
||||
func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *biz.User, authorityIDs []uint) (*biz.User, error) {
|
||||
if user.UUID == "" {
|
||||
user.UUID = uuid.NewString()
|
||||
}
|
||||
@@ -332,11 +332,11 @@ func validateAuthorities(tx *gorm.DB, authorityIDs []uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) UpdateUser(ctx context.Context, user *biz.User) error {
|
||||
func (r *userRepo) UpdateUser(ctx context.Context, user *biz.User) error {
|
||||
return r.updateUser(r.data.gormDB.WithContext(ctx), user)
|
||||
}
|
||||
|
||||
func (r *systemRepo) UpdateSelfUser(ctx context.Context, user *biz.User) error {
|
||||
func (r *userRepo) UpdateSelfUser(ctx context.Context, user *biz.User) error {
|
||||
updates := make(map[string]any)
|
||||
if user.NickName != "" {
|
||||
updates["nick_name"] = user.NickName
|
||||
@@ -359,7 +359,7 @@ func (r *systemRepo) UpdateSelfUser(ctx context.Context, user *biz.User) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *systemRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User) error {
|
||||
func (r *userRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User) error {
|
||||
if user == nil || len(user.Departments) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -408,7 +408,7 @@ func (r *systemRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) updateUser(tx *gorm.DB, user *biz.User) error {
|
||||
func (r *userRepo) updateUser(tx *gorm.DB, user *biz.User) error {
|
||||
updates := map[string]any{"nick_name": user.NickName, "header_img": user.HeaderImg, "phone": user.Phone, "email": user.Email, "enable": user.Enable}
|
||||
if user.AuthorityID != 0 {
|
||||
updates["authority_id"] = user.AuthorityID
|
||||
@@ -416,7 +416,7 @@ func (r *systemRepo) updateUser(tx *gorm.DB, user *biz.User) error {
|
||||
return tx.Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *systemRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.User, authorityIDs []uint) error {
|
||||
func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.User, authorityIDs []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := r.updateUser(tx, user); err != nil {
|
||||
return err
|
||||
@@ -427,7 +427,7 @@ func (r *systemRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.Us
|
||||
return setUserAuthorities(tx, user.ID, authorityIDs)
|
||||
})
|
||||
}
|
||||
func (r *systemRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||
func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||
return err
|
||||
@@ -441,7 +441,7 @@ func (r *systemRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||
return tx.Delete(&userPO{}, id).Error
|
||||
})
|
||||
}
|
||||
func (r *systemRepo) UpdatePassword(ctx context.Context, id uint, password string, clearMustChange bool) error {
|
||||
func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string, clearMustChange bool) error {
|
||||
now := time.Now()
|
||||
updates := map[string]any{"password": password, "password_updated_at": now}
|
||||
if clearMustChange {
|
||||
@@ -449,7 +449,7 @@ func (r *systemRepo) UpdatePassword(ctx context.Context, id uint, password strin
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
func (r *systemRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
func (r *userRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
var pos []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -462,9 +462,9 @@ func (r *systemRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, err
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *systemRepo) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
|
||||
func (r *userRepo) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
access := &accessRepo{data: r.data}
|
||||
access := &authorityAccessRepo{data: r.data}
|
||||
for _, authorityID := range authorityIDs {
|
||||
if err := access.checkAuthorityIDAuth(ctx, authorityID); err != nil {
|
||||
return err
|
||||
@@ -494,7 +494,7 @@ func setUserAuthorities(tx *gorm.DB, id uint, authorityIDs []uint) error {
|
||||
return tx.Model(&userPO{}).Where("id = ?", id).Update("authority_id", authorityIDs[0]).Error
|
||||
}
|
||||
|
||||
func (r *systemRepo) SetUserAuthority(ctx context.Context, id, authorityID uint) error {
|
||||
func (r *userRepo) SetUserAuthority(ctx context.Context, id, authorityID uint) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var count int64
|
||||
if err := db.Model(&userAuthorityPO{}).Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityID).Count(&count).Error; err != nil {
|
||||
@@ -520,7 +520,7 @@ func (r *systemRepo) SetUserAuthority(ctx context.Context, id, authorityID uint)
|
||||
return db.Model(&userPO{}).Where("id = ?", id).Update("authority_id", authorityID).Error
|
||||
}
|
||||
|
||||
func (r *systemRepo) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
|
||||
func (r *userRepo) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
|
||||
value, err := json.Marshal(setting)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -147,11 +147,11 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
settings := &settingsRepo{data: r.data}
|
||||
dictionariesRepo := &dictionaryRepo{data: r.data}
|
||||
for _, po := range dictionaries {
|
||||
v := dictionaryFromPO(po)
|
||||
v.ID = 0
|
||||
details, _, err := settings.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: po.ID})
|
||||
details, _, err := dictionariesRepo.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: po.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+21
-21
@@ -20,7 +20,7 @@ import (
|
||||
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
|
||||
)
|
||||
|
||||
func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, 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, security *service.SecurityService, tokens *service.TokenService, 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))
|
||||
@@ -32,29 +32,29 @@ func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, a
|
||||
}
|
||||
public := engine.Group(prefix)
|
||||
public.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") })
|
||||
serverrouter.RegisterPublic(public, engine, publicHandler)
|
||||
serverrouter.RegisterPublic(public, engine, handlers.Public)
|
||||
|
||||
private := engine.Group(prefix)
|
||||
private.Use(servermiddleware.Auth(runtime, security, tokens), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access, audit))
|
||||
serverrouter.RegisterUser(private, user)
|
||||
serverrouter.RegisterNavigation(private, navigation)
|
||||
serverrouter.RegisterSession(private, session)
|
||||
serverrouter.RegisterAuthority(private, authority)
|
||||
serverrouter.RegisterMenu(private, menu)
|
||||
serverrouter.RegisterAPI(private, public, engine, api)
|
||||
serverrouter.RegisterPermission(private, permission)
|
||||
serverrouter.RegisterOrganization(private, organization)
|
||||
serverrouter.RegisterDictionary(private, dictionary)
|
||||
serverrouter.RegisterParameter(private, parameter)
|
||||
serverrouter.RegisterAPIToken(private, apiToken)
|
||||
serverrouter.RegisterSystemConfig(private, systemConfig)
|
||||
serverrouter.RegisterVersion(private, version)
|
||||
serverrouter.RegisterExport(private, public, export)
|
||||
serverrouter.RegisterAudit(private, public, auditHandler)
|
||||
serverrouter.RegisterTask(private, task)
|
||||
serverrouter.RegisterMedia(private, media)
|
||||
serverrouter.RegisterAnnouncement(private, public, announcement)
|
||||
serverrouter.RegisterEmail(private, email)
|
||||
serverrouter.RegisterUser(private, handlers.User)
|
||||
serverrouter.RegisterNavigation(private, handlers.Navigation)
|
||||
serverrouter.RegisterSession(private, handlers.Session)
|
||||
serverrouter.RegisterAuthority(private, handlers.Authority)
|
||||
serverrouter.RegisterMenu(private, handlers.Menu)
|
||||
serverrouter.RegisterAPI(private, public, engine, handlers.API)
|
||||
serverrouter.RegisterPermission(private, handlers.Permission)
|
||||
serverrouter.RegisterOrganization(private, handlers.Organization)
|
||||
serverrouter.RegisterDictionary(private, handlers.Dictionary)
|
||||
serverrouter.RegisterParameter(private, handlers.Parameter)
|
||||
serverrouter.RegisterAPIToken(private, handlers.APIToken)
|
||||
serverrouter.RegisterSystemConfig(private, handlers.SystemConfig)
|
||||
serverrouter.RegisterVersion(private, handlers.Version)
|
||||
serverrouter.RegisterExport(private, public, handlers.Export)
|
||||
serverrouter.RegisterAudit(private, public, handlers.Audit)
|
||||
serverrouter.RegisterTask(private, handlers.Task)
|
||||
serverrouter.RegisterMedia(private, handlers.Media)
|
||||
serverrouter.RegisterAnnouncement(private, public, handlers.Announcement)
|
||||
serverrouter.RegisterEmail(private, handlers.Email)
|
||||
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
if serveLocalStorage(c, runtime) {
|
||||
|
||||
@@ -173,7 +173,7 @@ func (h *Media) InitUpload(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if config := h.service.MediaConfig(); config != nil && config.MaxFileSize > 0 && req.FileSize > config.MaxFileSize {
|
||||
if config := h.service.MediaConfig(); config.MaxFileSize > 0 && req.FileSize > config.MaxFileSize {
|
||||
httpx.Fail(c, "文件超过大小上限")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,45 +8,28 @@ import (
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
"kra/internal/worker"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
type Public struct {
|
||||
auth *service.AuthService
|
||||
system *service.SystemConfigService
|
||||
settings *service.SecurityService
|
||||
audit *service.AuditRecorder
|
||||
scheduler *worker.TaskScheduler
|
||||
runtime *conf.Runtime
|
||||
store *captchaStore
|
||||
auth *service.AuthService
|
||||
system *service.SystemConfigService
|
||||
settings *service.SecurityService
|
||||
store *captchaStore
|
||||
}
|
||||
|
||||
func NewPublic(runtime *conf.Runtime, auth *service.AuthService, system *service.SystemConfigService, settings *service.SecurityService, audit *service.AuditRecorder, scheduler *worker.TaskScheduler) *Public {
|
||||
return &Public{auth: auth, system: system, settings: settings, audit: audit, scheduler: scheduler, runtime: runtime, store: &captchaStore{service: settings, runtime: runtime}}
|
||||
func NewPublic(auth *service.AuthService, system *service.SystemConfigService, settings *service.SecurityService) *Public {
|
||||
return &Public{auth: auth, system: system, settings: settings, store: &captchaStore{service: settings}}
|
||||
}
|
||||
|
||||
func (h *Public) captchaConfig() (int, int, int) {
|
||||
keyLong, width, height := 6, 240, 80
|
||||
config := h.runtime.Admin()
|
||||
if config != nil && config.Captcha != nil {
|
||||
if config.Captcha.KeyLong > 0 {
|
||||
keyLong = int(config.Captcha.KeyLong)
|
||||
}
|
||||
if config.Captcha.ImgWidth > 0 {
|
||||
width = int(config.Captcha.ImgWidth)
|
||||
}
|
||||
if config.Captcha.ImgHeight > 0 {
|
||||
height = int(config.Captcha.ImgHeight)
|
||||
}
|
||||
}
|
||||
return keyLong, width, height
|
||||
config := h.settings.CaptchaSettings()
|
||||
return config.KeyLong, config.ImageWidth, config.ImageHeight
|
||||
}
|
||||
|
||||
func (h *Public) Captcha(c *gin.Context) {
|
||||
@@ -74,57 +57,32 @@ func (h *Public) Login(c *gin.Context) {
|
||||
httpx.Fail(c, "请输入用户名和密码")
|
||||
return
|
||||
}
|
||||
security, _ := h.settings.CurrentSecurity(c.Request.Context())
|
||||
if security != nil && security.LockEnable {
|
||||
if locked, _ := h.settings.LoginLocked(c.Request.Context(), req.Username); locked {
|
||||
httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(security.LockDuration)+" 分钟后再试")
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "账号已锁定", Agent: c.Request.UserAgent()})
|
||||
return
|
||||
}
|
||||
}
|
||||
requireCaptcha := security == nil || security.CaptchaOpen == 0
|
||||
ipTTL := time.Hour
|
||||
if security != nil {
|
||||
if security.CaptchaTimeout > 0 {
|
||||
ipTTL = time.Duration(security.CaptchaTimeout) * time.Second
|
||||
}
|
||||
failures, _ := h.settings.EnsureLoginIPCounter(c.Request.Context(), c.ClientIP(), ipTTL)
|
||||
requireCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
|
||||
}
|
||||
if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !h.store.Verify(req.CaptchaID, req.Captcha, true)) {
|
||||
_, _ = h.settings.IncrementLoginIP(c.Request.Context(), c.ClientIP(), ipTTL)
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "验证码错误", Agent: c.Request.UserAgent()})
|
||||
httpx.Fail(c, "验证码错误")
|
||||
return
|
||||
}
|
||||
result, err := h.auth.Login(c.Request.Context(), req.Username, req.Password)
|
||||
result, err := h.auth.Login(c.Request.Context(), &req, c.ClientIP(), c.Request.UserAgent())
|
||||
if err != nil {
|
||||
_, _ = h.settings.IncrementLoginIP(c.Request.Context(), c.ClientIP(), ipTTL)
|
||||
if errors.Is(err, biz.ErrUserDisabled) {
|
||||
var disabled *service.UserDisabledError
|
||||
errors.As(err, &disabled)
|
||||
userID := uint(0)
|
||||
if disabled != nil {
|
||||
userID = disabled.UserID
|
||||
switch {
|
||||
case errors.Is(err, biz.ErrAccountLocked):
|
||||
var locked *biz.AccountLockedError
|
||||
errors.As(err, &locked)
|
||||
minutes := 0
|
||||
if locked != nil {
|
||||
minutes = locked.Minutes
|
||||
}
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户被禁止登录", Agent: c.Request.UserAgent(), UserID: userID})
|
||||
httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试")
|
||||
case errors.Is(err, biz.ErrCaptchaInvalid):
|
||||
httpx.Fail(c, "验证码错误")
|
||||
case errors.Is(err, biz.ErrUserDisabled):
|
||||
httpx.Fail(c, "用户被禁止登录")
|
||||
return
|
||||
case errors.Is(err, biz.ErrTokenIssue):
|
||||
httpx.Fail(c, "获取token失败")
|
||||
case errors.Is(err, biz.ErrLoginState):
|
||||
httpx.Fail(c, "设置登录状态失败")
|
||||
case errors.Is(err, biz.ErrTokenRevoke):
|
||||
httpx.Fail(c, "jwt作废失败")
|
||||
default:
|
||||
httpx.Fail(c, "用户名不存在或者密码错误")
|
||||
}
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户名不存在或者密码错误", Agent: c.Request.UserAgent()})
|
||||
if security != nil && security.LockEnable {
|
||||
lockTTL := time.Duration(security.LockDuration) * time.Minute
|
||||
failures, _ := h.settings.IncrementLoginFailure(c.Request.Context(), req.Username, lockTTL)
|
||||
if int(failures) >= security.LockThreshold {
|
||||
_ = h.settings.LockLogin(c.Request.Context(), req.Username, lockTTL)
|
||||
}
|
||||
}
|
||||
httpx.Fail(c, "用户名不存在或者密码错误")
|
||||
return
|
||||
}
|
||||
userID, _ := result.User["ID"].(uint)
|
||||
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: true, Agent: c.Request.UserAgent(), UserID: userID})
|
||||
h.settings.ClearLoginState(c.Request.Context(), req.Username)
|
||||
maxAge := int(time.Until(time.UnixMilli(result.ExpiresAt)).Seconds())
|
||||
httpx.SetTokenCookie(c, result.Token, maxAge)
|
||||
httpx.Write(c, httpx.CodeSuccess, result, "登录成功")
|
||||
@@ -165,28 +123,23 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
|
||||
values = append(values, dto.Route{Path: route.Path, Method: route.Method})
|
||||
}
|
||||
if err := h.system.InitializeRoutes(c.Request.Context(), &input, values); err != nil {
|
||||
if errors.Is(err, biz.ErrTaskRuntimeReload) {
|
||||
httpx.Fail(c, "数据库已初始化,但定时任务加载失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Fail(c, "自动创建数据库失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.scheduler.Reload(c.Request.Context()); err != nil {
|
||||
httpx.Fail(c, "数据库已初始化,但定时任务加载失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "自动创建数据库成功")
|
||||
}
|
||||
}
|
||||
|
||||
type captchaStore struct {
|
||||
service *service.SecurityService
|
||||
runtime *conf.Runtime
|
||||
}
|
||||
|
||||
func (s *captchaStore) Set(id, value string) error {
|
||||
expiration := 3 * time.Minute
|
||||
config := s.runtime.Admin()
|
||||
if config != nil && config.Captcha != nil && config.Captcha.StoreExpiration != nil {
|
||||
expiration = config.Captcha.StoreExpiration.AsDuration()
|
||||
}
|
||||
expiration := s.service.CaptchaSettings().StoreExpiration
|
||||
return s.service.SetCaptcha(context.Background(), id, value, expiration)
|
||||
}
|
||||
func (s *captchaStore) Get(id string, clear bool) string {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
type Set struct {
|
||||
Authority *Authority
|
||||
Menu *Menu
|
||||
API *API
|
||||
Permission *Permission
|
||||
Organization *Organization
|
||||
Announcement *Announcement
|
||||
Email *Email
|
||||
Task *Task
|
||||
Media *Media
|
||||
Audit *Audit
|
||||
Export *Export
|
||||
Version *Version
|
||||
Dictionary *Dictionary
|
||||
Parameter *Parameter
|
||||
APIToken *APIToken
|
||||
SystemConfig *SystemConfig
|
||||
Public *Public
|
||||
User *User
|
||||
Navigation *Navigation
|
||||
Session *Session
|
||||
}
|
||||
|
||||
func NewSet(authority *Authority, menu *Menu, api *API, permission *Permission, organization *Organization, announcement *Announcement, email *Email, task *Task, media *Media, audit *Audit, export *Export, version *Version, dictionary *Dictionary, parameter *Parameter, apiToken *APIToken, systemConfig *SystemConfig, public *Public, user *User, navigation *Navigation, session *Session) *Set {
|
||||
return &Set{Authority: authority, Menu: menu, API: api, Permission: permission, Organization: organization, Announcement: announcement, Email: email, Task: task, Media: media, Audit: audit, Export: export, Version: version, Dictionary: dictionary, Parameter: parameter, APIToken: apiToken, SystemConfig: systemConfig, Public: public, User: user, Navigation: navigation, Session: session}
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
"kra/internal/worker"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SystemConfig struct {
|
||||
system *service.SystemConfigService
|
||||
settings *service.SecurityService
|
||||
scheduler *worker.TaskScheduler
|
||||
system *service.SystemConfigService
|
||||
settings *service.SecurityService
|
||||
}
|
||||
|
||||
func NewSystemConfig(system *service.SystemConfigService, settings *service.SecurityService, scheduler *worker.TaskScheduler) *SystemConfig {
|
||||
return &SystemConfig{system: system, settings: settings, scheduler: scheduler}
|
||||
func NewSystemConfig(system *service.SystemConfigService, settings *service.SecurityService) *SystemConfig {
|
||||
return &SystemConfig{system: system, settings: settings}
|
||||
}
|
||||
|
||||
func (h *SystemConfig) GetSecurity(c *gin.Context) {
|
||||
@@ -61,13 +62,13 @@ func (h *SystemConfig) Set(c *gin.Context) {
|
||||
|
||||
func (h *SystemConfig) Reload(c *gin.Context) {
|
||||
if err := h.system.ReloadConfig(c.Request.Context()); err != nil {
|
||||
if errors.Is(err, biz.ErrTaskRuntimeReload) {
|
||||
httpx.Fail(c, "系统配置已重载,但定时任务重载失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Fail(c, "重载系统失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.scheduler.Reload(c.Request.Context()); err != nil {
|
||||
httpx.Fail(c, "系统配置已重载,但定时任务重载失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重载系统成功")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
"kra/internal/worker"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
service *service.TaskService
|
||||
scheduler *worker.TaskScheduler
|
||||
service *service.TaskService
|
||||
}
|
||||
|
||||
func NewTask(service *service.TaskService, scheduler *worker.TaskScheduler) *Task {
|
||||
return &Task{service: service, scheduler: scheduler}
|
||||
func NewTask(service *service.TaskService) *Task {
|
||||
return &Task{service: service}
|
||||
}
|
||||
|
||||
func (h *Task) Create(c *gin.Context) {
|
||||
@@ -29,15 +29,17 @@ func (h *Task) Create(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
id, err := h.service.CreateRequest(c.Request.Context(), &req)
|
||||
_, err := h.service.CreateRequest(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
if errors.Is(err, biz.ErrTaskSchedule) {
|
||||
var scheduleErr *biz.TaskScheduleError
|
||||
errors.As(err, &scheduleErr)
|
||||
httpx.Fail(c, "调度失败:"+scheduleErr.Error())
|
||||
return
|
||||
}
|
||||
httpx.Fail(c, "创建失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err = h.scheduler.ScheduleID(c.Request.Context(), id); err != nil {
|
||||
httpx.Fail(c, "调度失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
|
||||
}
|
||||
|
||||
@@ -48,13 +50,15 @@ func (h *Task) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
|
||||
if errors.Is(err, biz.ErrTaskSchedule) {
|
||||
var scheduleErr *biz.TaskScheduleError
|
||||
errors.As(err, &scheduleErr)
|
||||
httpx.Fail(c, "调度失败:"+scheduleErr.Error())
|
||||
return
|
||||
}
|
||||
httpx.Fail(c, "更新失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "调度失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
|
||||
}
|
||||
|
||||
@@ -64,7 +68,6 @@ func (h *Task) Delete(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
h.scheduler.Remove(req.ID)
|
||||
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "删除失败")
|
||||
return
|
||||
@@ -82,10 +85,6 @@ func (h *Task) Toggle(c *gin.Context) {
|
||||
httpx.Fail(c, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "操作成功")
|
||||
}
|
||||
|
||||
@@ -95,7 +94,7 @@ func (h *Task) Trigger(c *gin.Context) {
|
||||
httpx.Fail(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.scheduler.TriggerID(c.Request.Context(), req.ID); err != nil {
|
||||
if err := h.service.Trigger(c.Request.Context(), req.ID); err != nil {
|
||||
httpx.Fail(c, "触发失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -110,7 +109,7 @@ func (h *Task) List(c *gin.Context) {
|
||||
value, _ := strconv.ParseBool(raw)
|
||||
enabled = &value
|
||||
}
|
||||
items, total, err := h.service.ListRequest(c.Request.Context(), page, size, c.Query("name"), c.Query("executorType"), enabled, h.scheduler.NextRuns())
|
||||
items, total, err := h.service.ListRequest(c.Request.Context(), page, size, c.Query("name"), c.Query("executorType"), enabled)
|
||||
if err != nil {
|
||||
httpx.Fail(c, "获取失败")
|
||||
return
|
||||
@@ -146,8 +145,8 @@ func (h *Task) AlertStream(c *gin.Context) {
|
||||
}
|
||||
// Remove the normal server deadline for long-lived SSE connections.
|
||||
_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Time{})
|
||||
events := h.scheduler.Subscribe()
|
||||
defer h.scheduler.Unsubscribe(events)
|
||||
events := h.service.Subscribe()
|
||||
defer h.service.Unsubscribe(events)
|
||||
ticker := time.NewTicker(20 * time.Second)
|
||||
defer ticker.Stop()
|
||||
_, _ = fmt.Fprint(c.Writer, "event: connected\ndata: {}\n\n")
|
||||
|
||||
@@ -8,4 +8,4 @@ import (
|
||||
)
|
||||
|
||||
// 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, worker.NewTaskExecutor, worker.NewTaskScheduler)
|
||||
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)
|
||||
|
||||
@@ -5,25 +5,24 @@ import (
|
||||
"strings"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type APIService struct {
|
||||
uc *biz.APIUsecase
|
||||
runtime *conf.Runtime
|
||||
uc *biz.APIUsecase
|
||||
settings biz.RuntimeSettings
|
||||
}
|
||||
|
||||
func NewAPIService(uc *biz.APIUsecase, runtime *conf.Runtime) *APIService {
|
||||
return &APIService{uc: uc, runtime: runtime}
|
||||
func NewAPIService(uc *biz.APIUsecase, settings biz.RuntimeSettings) *APIService {
|
||||
return &APIService{uc: uc, settings: settings}
|
||||
}
|
||||
|
||||
func (s *APIService) NormalizeRoutePath(path string) string {
|
||||
config := s.runtime.Admin()
|
||||
if config == nil || config.RouterPrefix == "" {
|
||||
routerPrefix := s.settings.RouterPrefix()
|
||||
if routerPrefix == "" {
|
||||
return path
|
||||
}
|
||||
prefix := strings.TrimSuffix(config.RouterPrefix, "/")
|
||||
prefix := strings.TrimSuffix(routerPrefix, "/")
|
||||
normalized := strings.TrimPrefix(path, prefix)
|
||||
if normalized == "" {
|
||||
return "/"
|
||||
|
||||
@@ -5,17 +5,15 @@ import (
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/pkg/adminauth"
|
||||
)
|
||||
|
||||
type TokenService struct {
|
||||
uc *biz.TokenUsecase
|
||||
runtime *conf.Runtime
|
||||
uc *biz.TokenUsecase
|
||||
issuer biz.TokenIssuer
|
||||
}
|
||||
|
||||
func NewTokenService(uc *biz.TokenUsecase, runtime *conf.Runtime) *TokenService {
|
||||
return &TokenService{uc: uc, runtime: runtime}
|
||||
func NewTokenService(uc *biz.TokenUsecase, issuer biz.TokenIssuer) *TokenService {
|
||||
return &TokenService{uc: uc, issuer: issuer}
|
||||
}
|
||||
|
||||
func tokenDTO(v *biz.APIToken) map[string]any {
|
||||
@@ -31,27 +29,15 @@ func (s *TokenService) CreateAPIToken(ctx context.Context, userID, authorityID u
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
secret, issuer := "", "kra"
|
||||
buffer := 24 * time.Hour
|
||||
config := s.runtime.Admin()
|
||||
if config != nil && config.Jwt != nil {
|
||||
secret = config.Jwt.SigningKey
|
||||
if config.Jwt.Issuer != "" {
|
||||
issuer = config.Jwt.Issuer
|
||||
}
|
||||
if config.Jwt.BufferTime != nil {
|
||||
buffer = config.Jwt.BufferTime.AsDuration()
|
||||
}
|
||||
}
|
||||
token, _, err := adminauth.Generate(secret, issuer, duration, buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, false)
|
||||
issued, err := s.issuer.IssueToken(user, authorityID, false, duration)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
record := &biz.APIToken{UserID: userID, AuthorityID: authorityID, Token: token, Status: true, ExpiresAt: time.Now().Add(duration), Remark: remark}
|
||||
record := &biz.APIToken{UserID: userID, AuthorityID: authorityID, Token: issued.Value, Status: true, ExpiresAt: time.Now().Add(duration), Remark: remark}
|
||||
if err = s.uc.CreateAPIToken(ctx, record); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
return issued.Value, nil
|
||||
}
|
||||
func (s *TokenService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]map[string]any, int64, error) {
|
||||
items, total, err := s.uc.ListAPITokens(ctx, page, size, userID, status)
|
||||
|
||||
@@ -2,18 +2,11 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/pkg/adminauth"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type UserDisabledError struct{ UserID uint }
|
||||
|
||||
func (e *UserDisabledError) Error() string { return biz.ErrUserDisabled.Error() }
|
||||
func (e *UserDisabledError) Unwrap() error { return biz.ErrUserDisabled }
|
||||
|
||||
type LoginResult struct {
|
||||
User map[string]any `json:"user"`
|
||||
Token string `json:"token"`
|
||||
@@ -22,65 +15,29 @@ type LoginResult struct {
|
||||
}
|
||||
|
||||
type AuthService struct {
|
||||
uc *biz.UserUsecase
|
||||
runtime *conf.Runtime
|
||||
settings *SecurityService
|
||||
uc *biz.AuthenticationUsecase
|
||||
}
|
||||
|
||||
func NewAuthService(uc *biz.UserUsecase, runtime *conf.Runtime, settings *SecurityService) *AuthService {
|
||||
return &AuthService{uc: uc, runtime: runtime, settings: settings}
|
||||
func NewAuthService(uc *biz.AuthenticationUsecase) *AuthService {
|
||||
return &AuthService{uc: uc}
|
||||
}
|
||||
|
||||
func (s *AuthService) Login(ctx context.Context, username, password string) (*LoginResult, error) {
|
||||
u, err := s.uc.Login(ctx, username, password)
|
||||
func loginResult(value *biz.AuthenticationResult) *LoginResult {
|
||||
return &LoginResult{User: convertUser(value.User), Token: value.Token, ExpiresAt: value.ExpiresAt.UnixMilli(), NeedChangePassword: value.NeedChangePassword}
|
||||
}
|
||||
|
||||
func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest, ip, agent string) (*LoginResult, error) {
|
||||
value, err := s.uc.Login(ctx, &biz.LoginAttempt{Username: req.Username, Password: req.Password, CaptchaID: req.CaptchaID, Captcha: req.Captcha, IP: ip, Agent: agent})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Enable != 1 {
|
||||
return nil, &UserDisabledError{UserID: u.ID}
|
||||
}
|
||||
if security, securityErr := s.settings.CurrentSecurity(ctx); securityErr == nil && security.PwdExpireEnable && u.PasswordUpdatedAt != nil && time.Since(*u.PasswordUpdatedAt) > time.Duration(security.PwdExpireDays)*24*time.Hour {
|
||||
u.MustChangePassword = true
|
||||
}
|
||||
return s.issueLogin(ctx, u, u.AuthorityID)
|
||||
}
|
||||
|
||||
func (s *AuthService) issueLogin(ctx context.Context, user *biz.User, authorityID uint) (*LoginResult, error) {
|
||||
expires, buffer := 7*24*time.Hour, 24*time.Hour
|
||||
secret, issuer := "", "kra"
|
||||
config := s.runtime.Admin()
|
||||
if config != nil && config.Jwt != nil {
|
||||
secret, issuer = config.Jwt.SigningKey, config.Jwt.Issuer
|
||||
if config.Jwt.ExpiresTime != nil {
|
||||
expires = config.Jwt.ExpiresTime.AsDuration()
|
||||
}
|
||||
if config.Jwt.BufferTime != nil {
|
||||
buffer = config.Jwt.BufferTime.AsDuration()
|
||||
}
|
||||
}
|
||||
token, claims, err := adminauth.Generate(secret, issuer, expires, buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, user.MustChangePassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.settings.UseMultipoint() {
|
||||
oldToken, cacheErr := s.settings.ActiveToken(ctx, user.Username)
|
||||
if cacheErr != nil {
|
||||
return nil, cacheErr
|
||||
}
|
||||
if cacheErr = s.settings.RotateActiveToken(ctx, user.Username, oldToken, token, expires); cacheErr != nil {
|
||||
return nil, cacheErr
|
||||
}
|
||||
}
|
||||
return &LoginResult{User: convertUser(user), Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli(), NeedChangePassword: user.MustChangePassword}, nil
|
||||
return loginResult(value), nil
|
||||
}
|
||||
|
||||
func (s *AuthService) SwitchAuthority(ctx context.Context, id, authorityID uint) (*LoginResult, error) {
|
||||
if err := s.uc.SetUserAuthority(ctx, id, authorityID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := s.uc.User(ctx, id)
|
||||
value, err := s.uc.SwitchAuthority(ctx, id, authorityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.issueLogin(ctx, user, authorityID)
|
||||
return loginResult(value), nil
|
||||
}
|
||||
|
||||
@@ -5,23 +5,19 @@ import (
|
||||
"io"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type MediaService struct {
|
||||
uc *biz.MediaUsecase
|
||||
runtime *conf.Runtime
|
||||
uc *biz.MediaUsecase
|
||||
settings biz.RuntimeSettings
|
||||
}
|
||||
|
||||
func NewMediaService(uc *biz.MediaUsecase, runtime *conf.Runtime) *MediaService {
|
||||
return &MediaService{uc: uc, runtime: runtime}
|
||||
func NewMediaService(uc *biz.MediaUsecase, settings biz.RuntimeSettings) *MediaService {
|
||||
return &MediaService{uc: uc, settings: settings}
|
||||
}
|
||||
func (s *MediaService) MediaConfig() *conf.AdminBackend_Media {
|
||||
if config := s.runtime.Admin(); config != nil {
|
||||
return config.Media
|
||||
}
|
||||
return nil
|
||||
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}
|
||||
|
||||
@@ -11,22 +11,7 @@ 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 (s *SecurityService) CurrentSecurity(ctx context.Context) (*biz.SecurityConfig, error) {
|
||||
s.securityMu.RLock()
|
||||
if s.securityCache != nil {
|
||||
copy := *s.securityCache
|
||||
s.securityMu.RUnlock()
|
||||
return ©, nil
|
||||
}
|
||||
s.securityMu.RUnlock()
|
||||
value, err := s.uc.SecurityConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.securityMu.Lock()
|
||||
s.securityCache = value
|
||||
copy := *value
|
||||
s.securityMu.Unlock()
|
||||
return ©, nil
|
||||
return s.uc.Current(ctx)
|
||||
}
|
||||
func (s *SecurityService) Security(ctx context.Context) (map[string]any, error) {
|
||||
value, err := s.CurrentSecurity(ctx)
|
||||
@@ -39,9 +24,6 @@ func (s *SecurityService) SaveSecurity(ctx context.Context, value *biz.SecurityC
|
||||
if err := s.uc.UpdateSecurity(ctx, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.securityMu.Lock()
|
||||
s.securityCache = value
|
||||
s.securityMu.Unlock()
|
||||
return securityDTO(value), nil
|
||||
}
|
||||
func (s *SecurityService) SaveSecurityRequest(ctx context.Context, value *dto.SecurityConfigRequest) (map[string]any, error) {
|
||||
|
||||
@@ -2,126 +2,73 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
type SecurityService struct {
|
||||
uc *biz.SecurityUsecase
|
||||
runtime *conf.Runtime
|
||||
securityMu sync.RWMutex
|
||||
securityCache *biz.SecurityConfig
|
||||
cache biz.Cache
|
||||
tokens *biz.TokenUsecase
|
||||
uc *biz.SecurityUsecase
|
||||
}
|
||||
|
||||
func NewSecurityService(uc *biz.SecurityUsecase, runtime *conf.Runtime, cache biz.Cache, tokens *biz.TokenUsecase) *SecurityService {
|
||||
return &SecurityService{uc: uc, runtime: runtime, cache: cache, tokens: tokens}
|
||||
}
|
||||
|
||||
func (s *SecurityService) cacheGet(ctx context.Context, key string) (string, bool, error) {
|
||||
return s.cache.Get(ctx, key)
|
||||
}
|
||||
|
||||
func (s *SecurityService) cacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
|
||||
return s.cache.Set(ctx, key, value, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) cacheDelete(ctx context.Context, key string) error {
|
||||
return s.cache.Delete(ctx, key)
|
||||
}
|
||||
|
||||
func (s *SecurityService) cacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
return s.cache.Increment(ctx, key, expiration)
|
||||
func NewSecurityService(uc *biz.SecurityUsecase) *SecurityService {
|
||||
return &SecurityService{uc: uc}
|
||||
}
|
||||
|
||||
func (s *SecurityService) ActiveToken(ctx context.Context, username string) (string, error) {
|
||||
value, _, err := s.cacheGet(ctx, activeTokenKey(username))
|
||||
return value, err
|
||||
return s.uc.ActiveToken(ctx, username)
|
||||
}
|
||||
|
||||
func (s *SecurityService) LoginLocked(ctx context.Context, username string) (bool, error) {
|
||||
_, locked, err := s.cacheGet(ctx, "login:lock:"+username)
|
||||
return locked, err
|
||||
return s.uc.LoginLocked(ctx, username)
|
||||
}
|
||||
|
||||
func (s *SecurityService) IncrementLoginFailure(ctx context.Context, username string, expiration time.Duration) (int64, error) {
|
||||
return s.cacheIncrement(ctx, "login:fail:"+username, expiration)
|
||||
return s.uc.IncrementLoginFailure(ctx, username, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) LockLogin(ctx context.Context, username string, expiration time.Duration) error {
|
||||
return s.cacheSet(ctx, "login:lock:"+username, "1", expiration)
|
||||
return s.uc.LockLogin(ctx, username, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) ClearLoginState(ctx context.Context, username string) {
|
||||
_ = s.cacheDelete(ctx, "login:fail:"+username)
|
||||
_ = s.cacheDelete(ctx, "login:lock:"+username)
|
||||
s.uc.ClearLoginState(ctx, username)
|
||||
}
|
||||
|
||||
func (s *SecurityService) EnsureLoginIPCounter(ctx context.Context, ip string, expiration time.Duration) (int, error) {
|
||||
value, exists, err := s.cacheGet(ctx, ip)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if exists {
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
if expiration <= 0 {
|
||||
expiration = time.Hour
|
||||
}
|
||||
if err = s.cacheSet(ctx, ip, "1", expiration); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 1, nil
|
||||
return s.uc.EnsureLoginIPCounter(ctx, ip, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) IncrementLoginIP(ctx context.Context, ip string, expiration time.Duration) (int64, error) {
|
||||
return s.cacheIncrement(ctx, ip, expiration)
|
||||
return s.uc.IncrementLoginIP(ctx, ip, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) IncrementRateLimit(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
return s.cacheIncrement(ctx, key, expiration)
|
||||
return s.uc.IncrementRateLimit(ctx, key, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) SetCaptcha(ctx context.Context, id, value string, expiration time.Duration) error {
|
||||
return s.cacheSet(ctx, "captcha:"+id, value, expiration)
|
||||
return s.uc.SetCaptcha(ctx, id, value, expiration)
|
||||
}
|
||||
|
||||
func (s *SecurityService) GetCaptcha(ctx context.Context, id string) (string, bool, error) {
|
||||
return s.cacheGet(ctx, "captcha:"+id)
|
||||
return s.uc.GetCaptcha(ctx, id)
|
||||
}
|
||||
|
||||
func (s *SecurityService) DeleteCaptcha(ctx context.Context, id string) error {
|
||||
return s.cacheDelete(ctx, "captcha:"+id)
|
||||
return s.uc.DeleteCaptcha(ctx, id)
|
||||
}
|
||||
|
||||
func (s *SecurityService) UseMultipoint() bool {
|
||||
config := s.runtime.Admin()
|
||||
return config != nil && config.System != nil && config.System.UseMultipoint
|
||||
}
|
||||
func (s *SecurityService) UseMultipoint() bool { return s.uc.UseMultipoint() }
|
||||
|
||||
func activeTokenKey(username string) string { return "jwt:active:" + username }
|
||||
func (s *SecurityService) CaptchaSettings() biz.CaptchaSettings {
|
||||
return s.uc.CaptchaRuntimeSettings()
|
||||
}
|
||||
|
||||
func (s *SecurityService) ActiveTokenMatches(ctx context.Context, username, token string) (bool, error) {
|
||||
if !s.UseMultipoint() {
|
||||
return true, nil
|
||||
}
|
||||
active, ok, err := s.cacheGet(ctx, activeTokenKey(username))
|
||||
return ok && active == token, err
|
||||
return s.uc.ActiveTokenMatches(ctx, username, token)
|
||||
}
|
||||
|
||||
func (s *SecurityService) RotateActiveToken(ctx context.Context, username, oldToken, newToken string, expiration time.Duration) error {
|
||||
if !s.UseMultipoint() {
|
||||
return nil
|
||||
}
|
||||
if oldToken != "" && oldToken != newToken {
|
||||
if err := s.tokens.BlacklistToken(ctx, oldToken); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s.cacheSet(ctx, activeTokenKey(username), newToken, expiration)
|
||||
return s.uc.RotateActiveToken(ctx, username, oldToken, newToken, expiration)
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
type TaskService struct {
|
||||
uc *biz.TaskUsecase
|
||||
uc *biz.TaskApplicationUsecase
|
||||
}
|
||||
|
||||
func NewTaskService(uc *biz.TaskUsecase) *TaskService {
|
||||
func NewTaskService(uc *biz.TaskApplicationUsecase) *TaskService {
|
||||
return &TaskService{uc: uc}
|
||||
}
|
||||
func taskDomain(v *dto.TaskRequest) *biz.TimedTask {
|
||||
@@ -29,8 +29,8 @@ 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, next map[uint]time.Time) ([]map[string]any, int64, error) {
|
||||
return s.Tasks(ctx, page, size, &biz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}, next)
|
||||
func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]map[string]any, 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 {
|
||||
return s.uc.Create(ctx, v)
|
||||
@@ -39,16 +39,16 @@ func (s *TaskService) Update(ctx context.Context, v *biz.TimedTask) error {
|
||||
return s.uc.Update(ctx, v)
|
||||
}
|
||||
func (s *TaskService) Delete(ctx context.Context, id uint) error {
|
||||
return s.uc.DeleteTask(ctx, id)
|
||||
return s.uc.Delete(ctx, id)
|
||||
}
|
||||
func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error {
|
||||
return s.uc.ToggleTask(ctx, id, enabled)
|
||||
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 (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask, next map[uint]time.Time) ([]map[string]any, int64, error) {
|
||||
items, total, err := s.uc.ListTasks(ctx, page, size, q)
|
||||
func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]map[string]any, int64, error) {
|
||||
items, total, next, err := s.uc.List(ctx, page, size, q)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTas
|
||||
}
|
||||
|
||||
func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]map[string]any, int64, error) {
|
||||
items, total, err := s.uc.ListTaskLogs(ctx, page, size, taskID, status)
|
||||
items, total, err := s.uc.Logs(ctx, page, size, taskID, status)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -76,6 +76,11 @@ func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, sta
|
||||
return out, total, nil
|
||||
}
|
||||
|
||||
func (s *TaskService) Trigger(ctx context.Context, id uint) error { return s.uc.Trigger(ctx, id) }
|
||||
func (s *TaskService) Reload(ctx context.Context) error { return s.uc.Reload(ctx) }
|
||||
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 {
|
||||
methods := biz.RegisteredTaskMethods()
|
||||
out := make([]map[string]any, 0, len(methods))
|
||||
|
||||
@@ -36,6 +36,8 @@ func NewTaskScheduler(tasks *biz.TaskUsecase, executor *TaskExecutor, logger *sl
|
||||
return &TaskScheduler{tasks: tasks, executor: executor, logger: logger, standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, subscribers: map[chan []byte]struct{}{}}
|
||||
}
|
||||
|
||||
func NewTaskRuntime(scheduler *TaskScheduler) biz.TaskRuntime { return scheduler }
|
||||
|
||||
func (s *TaskScheduler) Start(ctx context.Context) error {
|
||||
runContext, cancel := context.WithCancel(ctx)
|
||||
s.ctxMu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user