This commit is contained in:
2026-08-15 02:35:23 +08:00
parent bd5dbf2522
commit a34133cce5
49 changed files with 929 additions and 504 deletions
+18 -12
View File
@@ -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() {
+6
View File
@@ -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=
+150
View File
@@ -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
View File
@@ -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)
+40
View File
@@ -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
View File
@@ -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 = &copy
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 &copy, nil
}
uc.mu.RUnlock()
value, err := uc.repo.SecurityConfig(ctx)
if err != nil {
return nil, err
}
uc.mu.Lock()
copy := *value
uc.cachedConfig = &copy
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()
}
+31 -6
View File
@@ -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
}
+84
View File
@@ -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)
}
+8 -4
View File
@@ -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
View File
@@ -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}
+8 -8
View File
@@ -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
+6 -4
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+5 -5
View File
@@ -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 {
+4 -4
View File
@@ -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
}
+4 -4
View File
@@ -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
}
+1 -1
View File
@@ -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()
+4 -4
View File
@@ -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
}
+4 -3
View File
@@ -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} }
+5 -5
View File
@@ -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(&parameterPO{}).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(&parameterPO{}, 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(&parameterPO{})
if q != nil {
if q.StartCreatedAt != nil && q.EndCreatedAt != nil {
+6 -6
View File
@@ -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
+8 -8
View File
@@ -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
+94
View File
@@ -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
}
+2 -2
View File
@@ -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
+8 -5
View File
@@ -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 &parameterRepo{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} }
+4 -3
View File
@@ -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} }
+6 -6
View File
@@ -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()
+2 -2
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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) {
+1 -1
View File
@@ -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
}
+33 -80
View File
@@ -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 {
+28
View File
@@ -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}
}
+11 -10
View File
@@ -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{}, "重载系统成功")
}
+22 -23
View File
@@ -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")
+1 -1
View File
@@ -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)
+7 -8
View File
@@ -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 "/"
+7 -21
View File
@@ -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)
+13 -56
View File
@@ -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
}
+6 -10
View File
@@ -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}
+1 -19
View File
@@ -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 &copy, 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 &copy, 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) {
+20 -73
View File
@@ -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)
}
+14 -9
View File
@@ -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))
+2
View File
@@ -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()