This commit is contained in:
yvan 2026-08-15 01:28:51 +08:00
parent 6698e59c3a
commit 2f165d3a45
26 changed files with 242 additions and 216 deletions

View File

@ -30,18 +30,6 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
systemRepo := data.NewSystemRepo(dataData)
cache := data.NewCache(dataData)
fileStorage, err := data.NewFileStorage(dataData)
if err != nil {
cleanup()
return nil, nil, err
}
systemUsecase := biz.NewSystemUsecase(systemRepo, cache, fileStorage)
settingsRepo := data.NewSettingsRepo(dataData)
settingsUsecase := biz.NewSettingsUsecase(settingsRepo)
settingsService := service.NewSettingsService(settingsUsecase, runtime, cache)
systemService := service.NewSystemService(systemUsecase, runtime, settingsService)
accessRepo := data.NewAccessRepo(dataData) accessRepo := data.NewAccessRepo(dataData)
accessUsecase := biz.NewAccessUsecase(accessRepo) accessUsecase := biz.NewAccessUsecase(accessRepo)
accessService := service.NewAccessService(accessUsecase, runtime) accessService := service.NewAccessService(accessUsecase, runtime)
@ -66,10 +54,16 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
email := handler.NewEmail(emailService) email := handler.NewEmail(emailService)
taskRepo := data.NewTaskRepo(dataData) taskRepo := data.NewTaskRepo(dataData)
taskUsecase := biz.NewTaskUsecase(taskRepo) taskUsecase := biz.NewTaskUsecase(taskRepo)
taskService := service.NewTaskService(taskUsecase)
mediaRepo := data.NewMediaRepo(dataData) mediaRepo := data.NewMediaRepo(dataData)
fileStorage, err := data.NewFileStorage(dataData)
if err != nil {
cleanup()
return nil, nil, err
}
mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage) mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage)
taskService := service.NewTaskService(taskUsecase, mediaUsecase, runtime) taskExecutor := worker.NewTaskExecutor(taskUsecase, mediaUsecase, runtime)
taskScheduler := worker.NewTaskScheduler(taskService, logger) taskScheduler := worker.NewTaskScheduler(taskUsecase, taskExecutor, logger)
task := handler.NewTask(taskService, taskScheduler) task := handler.NewTask(taskService, taskScheduler)
mediaService := service.NewMediaService(mediaUsecase, runtime) mediaService := service.NewMediaService(mediaUsecase, runtime)
media := handler.NewMedia(mediaService) media := handler.NewMedia(mediaService)
@ -77,10 +71,14 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
auditUsecase := biz.NewAuditUsecase(auditRepo) auditUsecase := biz.NewAuditUsecase(auditRepo)
auditService := service.NewAuditService(auditUsecase) auditService := service.NewAuditService(auditUsecase)
audit := handler.NewAudit(auditService) audit := handler.NewAudit(auditService)
settingsRepo := data.NewSettingsRepo(dataData)
settingsUsecase := biz.NewSettingsUsecase(settingsRepo)
cache := data.NewCache(dataData)
settingsService := service.NewSettingsService(settingsUsecase, runtime, cache)
exportRepo := data.NewExportRepo(dataData) exportRepo := data.NewExportRepo(dataData)
exportUsecase := biz.NewExportUsecase(exportRepo) exportUsecase := biz.NewExportUsecase(exportRepo)
exportService := service.NewExportService(exportUsecase) exportService := service.NewExportService(exportUsecase)
export := handler.NewExport(systemService, exportService) export := handler.NewExport(settingsService, exportService)
versionRepo := data.NewVersionRepo(dataData) versionRepo := data.NewVersionRepo(dataData)
versionUsecase := biz.NewVersionUsecase(versionRepo) versionUsecase := biz.NewVersionUsecase(versionRepo)
versionService := service.NewVersionService(versionUsecase) versionService := service.NewVersionService(versionUsecase)
@ -88,12 +86,19 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
dictionary := handler.NewDictionary(settingsService) dictionary := handler.NewDictionary(settingsService)
parameter := handler.NewParameter(settingsService) parameter := handler.NewParameter(settingsService)
apiToken := handler.NewAPIToken(settingsService) apiToken := handler.NewAPIToken(settingsService)
systemConfig := handler.NewSystemConfig(systemService, settingsService, taskScheduler) initializationRepo := data.NewInitializationRepo(dataData)
public := handler.NewPublic(runtime, systemService, settingsService, auditService, taskScheduler) systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo)
user := handler.NewUser(systemService) systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtime)
navigation := handler.NewNavigation(systemService) systemConfig := handler.NewSystemConfig(systemConfigService, settingsService, taskScheduler)
userRepo := data.NewUserRepo(dataData)
userUsecase := biz.NewUserUsecase(userRepo)
authService := service.NewAuthService(userUsecase, runtime, settingsService)
public := handler.NewPublic(runtime, authService, systemConfigService, settingsService, auditService, taskScheduler)
userService := service.NewUserService(userUsecase, settingsService)
user := handler.NewUser(userService, authService)
navigation := handler.NewNavigation(userService)
session := handler.NewSession(settingsService) session := handler.NewSession(settingsService)
engine := server.NewGinEngine(runtime, systemService, accessService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, settingsService, auditService, logger) engine := server.NewGinEngine(runtime, accessService, authority, menu, api, permission, organization, announcement, email, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, settingsService, auditService, logger)
httpServer := server.NewGinServer(confServer, engine) httpServer := server.NewGinServer(confServer, engine)
app := newApp(logger, httpServer, taskScheduler) app := newApp(logger, httpServer, taskScheduler)
return app, func() { return app, func() {

View File

@ -3,4 +3,4 @@ package biz
import "github.com/google/wire" import "github.com/google/wire"
// ProviderSet is biz providers. // ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewSystemUsecase, NewAccessUsecase, NewMenuUsecase, NewOrganizationUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) var ProviderSet = wire.NewSet(NewUserUsecase, NewSystemConfigUsecase, NewAccessUsecase, NewMenuUsecase, NewOrganizationUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase)

View File

@ -1,34 +0,0 @@
package biz
import (
"context"
"time"
)
type SystemRepo interface {
InitializationRepo
UserRepo
}
type SystemUsecase struct {
repo SystemRepo
cache Cache
files FileStorage
}
func NewSystemUsecase(repo SystemRepo, cache Cache, files FileStorage) *SystemUsecase {
return &SystemUsecase{repo: repo, cache: cache, files: files}
}
func (uc *SystemUsecase) CacheGet(ctx context.Context, key string) (string, bool, error) {
return uc.cache.Get(ctx, key)
}
func (uc *SystemUsecase) CacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
return uc.cache.Set(ctx, key, value, expiration)
}
func (uc *SystemUsecase) CacheDelete(ctx context.Context, key string) error {
return uc.cache.Delete(ctx, key)
}
func (uc *SystemUsecase) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
return uc.cache.Increment(ctx, key, expiration)
}

View File

@ -16,20 +16,30 @@ type InitializationRepo interface {
ReloadConfig(context.Context) error ReloadConfig(context.Context) error
} }
func (uc *SystemUsecase) IsInitialized(ctx context.Context) (bool, error) { type SystemConfigUsecase struct{ repo InitializationRepo }
func NewSystemConfigUsecase(repo InitializationRepo) *SystemConfigUsecase {
return &SystemConfigUsecase{repo: repo}
}
func (uc *SystemConfigUsecase) IsInitialized(ctx context.Context) (bool, error) {
return uc.repo.IsInitialized(ctx) return uc.repo.IsInitialized(ctx)
} }
func (uc *SystemUsecase) Initialize(ctx context.Context, config *DatabaseConfig) error { func (uc *SystemConfigUsecase) Initialize(ctx context.Context, config *DatabaseConfig) error {
if config == nil || len(config.AdminPassword) < 6 { if config == nil || len(config.AdminPassword) < 6 {
return ErrInvalidCredentials return ErrInvalidCredentials
} }
return uc.repo.Initialize(ctx, config) return uc.repo.Initialize(ctx, config)
} }
func (uc *SystemUsecase) PersistConfig(ctx context.Context) error { return uc.repo.PersistConfig(ctx) } func (uc *SystemConfigUsecase) PersistConfig(ctx context.Context) error {
func (uc *SystemUsecase) PersistAdminConfig(ctx context.Context, value []byte) error { return uc.repo.PersistConfig(ctx)
}
func (uc *SystemConfigUsecase) PersistAdminConfig(ctx context.Context, value []byte) error {
return uc.repo.PersistAdminConfig(ctx, value) return uc.repo.PersistAdminConfig(ctx, value)
} }
func (uc *SystemUsecase) PersistRuntimeConfig(ctx context.Context, data, admin []byte) error { func (uc *SystemConfigUsecase) PersistRuntimeConfig(ctx context.Context, data, admin []byte) error {
return uc.repo.PersistRuntimeConfig(ctx, data, admin) return uc.repo.PersistRuntimeConfig(ctx, data, admin)
} }
func (uc *SystemUsecase) ReloadConfig(ctx context.Context) error { return uc.repo.ReloadConfig(ctx) } func (uc *SystemConfigUsecase) ReloadConfig(ctx context.Context) error {
return uc.repo.ReloadConfig(ctx)
}

View File

@ -63,7 +63,11 @@ type UserRepo interface {
FillDepartmentNamePaths(context.Context, *User) error FillDepartmentNamePaths(context.Context, *User) error
} }
func (uc *SystemUsecase) Login(ctx context.Context, username, password string) (*User, error) { type UserUsecase struct{ repo UserRepo }
func NewUserUsecase(repo UserRepo) *UserUsecase { return &UserUsecase{repo: repo} }
func (uc *UserUsecase) Login(ctx context.Context, username, password string) (*User, error) {
u, err := uc.repo.FindUserByUsername(ctx, username) u, err := uc.repo.FindUserByUsername(ctx, username)
if err != nil || bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) != nil { if err != nil || bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) != nil {
return nil, ErrInvalidCredentials return nil, ErrInvalidCredentials
@ -72,7 +76,7 @@ func (uc *SystemUsecase) Login(ctx context.Context, username, password string) (
return u, nil return u, nil
} }
func (uc *SystemUsecase) User(ctx context.Context, id uint) (*User, error) { func (uc *UserUsecase) User(ctx context.Context, id uint) (*User, error) {
user, err := uc.repo.FindUserByID(ctx, id) user, err := uc.repo.FindUserByID(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -82,7 +86,7 @@ func (uc *SystemUsecase) User(ctx context.Context, id uint) (*User, error) {
return user, nil return user, nil
} }
func (uc *SystemUsecase) fallbackDefaultRouter(ctx context.Context, user *User) { func (uc *UserUsecase) fallbackDefaultRouter(ctx context.Context, user *User) {
if user == nil || user.Authority.DefaultRouter == "" { if user == nil || user.Authority.DefaultRouter == "" {
return return
} }
@ -101,14 +105,14 @@ func menuNameExists(menus []*Menu, name string) bool {
return false return false
} }
func (uc *SystemUsecase) Menus(ctx context.Context, authorityID uint) ([]*Menu, error) { func (uc *UserUsecase) Menus(ctx context.Context, authorityID uint) ([]*Menu, error) {
return uc.repo.MenusByAuthority(ctx, authorityID) return uc.repo.MenusByAuthority(ctx, authorityID)
} }
func (uc *SystemUsecase) ListUsers(ctx context.Context, page, pageSize int, filter *UserListFilter) ([]*User, int64, error) { func (uc *UserUsecase) ListUsers(ctx context.Context, page, pageSize int, filter *UserListFilter) ([]*User, int64, error) {
return uc.repo.ListUsers(ctx, page, pageSize, filter) return uc.repo.ListUsers(ctx, page, pageSize, filter)
} }
func (uc *SystemUsecase) CreateUser(ctx context.Context, user *User, authorityIDs []uint) (*User, error) { func (uc *UserUsecase) CreateUser(ctx context.Context, user *User, authorityIDs []uint) (*User, error) {
if user.AuthorityID == 0 && len(authorityIDs) > 0 { if user.AuthorityID == 0 && len(authorityIDs) > 0 {
user.AuthorityID = authorityIDs[0] user.AuthorityID = authorityIDs[0]
} }
@ -120,12 +124,12 @@ func (uc *SystemUsecase) CreateUser(ctx context.Context, user *User, authorityID
user.Password = string(hash) user.Password = string(hash)
return uc.repo.CreateUserWithAuthorities(ctx, user, authorityIDs) return uc.repo.CreateUserWithAuthorities(ctx, user, authorityIDs)
} }
func (uc *SystemUsecase) UpdateUser(ctx context.Context, user *User, authorityIDs []uint) error { func (uc *UserUsecase) UpdateUser(ctx context.Context, user *User, authorityIDs []uint) error {
authorityIDs = includeAuthority(authorityIDs, user.AuthorityID) authorityIDs = includeAuthority(authorityIDs, user.AuthorityID)
return uc.repo.UpdateUserWithAuthorities(ctx, user, authorityIDs) return uc.repo.UpdateUserWithAuthorities(ctx, user, authorityIDs)
} }
func (uc *SystemUsecase) UpdateSelfUser(ctx context.Context, user *User) error { func (uc *UserUsecase) UpdateSelfUser(ctx context.Context, user *User) error {
return uc.repo.UpdateSelfUser(ctx, user) return uc.repo.UpdateSelfUser(ctx, user)
} }
@ -140,17 +144,17 @@ func includeAuthority(ids []uint, primary uint) []uint {
} }
return append(ids, primary) return append(ids, primary)
} }
func (uc *SystemUsecase) DeleteUser(ctx context.Context, id uint) error { func (uc *UserUsecase) DeleteUser(ctx context.Context, id uint) error {
return uc.repo.DeleteUser(ctx, id) return uc.repo.DeleteUser(ctx, id)
} }
func (uc *SystemUsecase) ResetPassword(ctx context.Context, id uint, password string) error { func (uc *UserUsecase) ResetPassword(ctx context.Context, id uint, password string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return err return err
} }
return uc.repo.UpdatePassword(ctx, id, string(hash), false) return uc.repo.UpdatePassword(ctx, id, string(hash), false)
} }
func (uc *SystemUsecase) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error { func (uc *UserUsecase) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error {
user, err := uc.repo.FindUserByID(ctx, id) user, err := uc.repo.FindUserByID(ctx, id)
if err != nil { if err != nil {
return err return err
@ -164,18 +168,18 @@ func (uc *SystemUsecase) ChangePassword(ctx context.Context, id uint, oldPasswor
} }
return uc.repo.UpdatePassword(ctx, id, string(hash), true) return uc.repo.UpdatePassword(ctx, id, string(hash), true)
} }
func (uc *SystemUsecase) Authorities(ctx context.Context) ([]*Authority, error) { func (uc *UserUsecase) Authorities(ctx context.Context) ([]*Authority, error) {
return uc.repo.ListAuthorities(ctx) return uc.repo.ListAuthorities(ctx)
} }
func (uc *SystemUsecase) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error { func (uc *UserUsecase) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
if len(authorityIDs) == 0 { if len(authorityIDs) == 0 {
return ErrInvalidCredentials return ErrInvalidCredentials
} }
return uc.repo.SetUserAuthorities(ctx, id, authorityIDs) return uc.repo.SetUserAuthorities(ctx, id, authorityIDs)
} }
func (uc *SystemUsecase) SetUserAuthority(ctx context.Context, id, authorityID uint) error { func (uc *UserUsecase) SetUserAuthority(ctx context.Context, id, authorityID uint) error {
return uc.repo.SetUserAuthority(ctx, id, authorityID) return uc.repo.SetUserAuthority(ctx, id, authorityID)
} }
func (uc *SystemUsecase) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error { func (uc *UserUsecase) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
return uc.repo.SetUserSetting(ctx, id, setting) return uc.repo.SetUserSetting(ctx, id, setting)
} }

View File

@ -13,7 +13,7 @@ import (
"kra/internal/conf" "kra/internal/conf"
) )
var ProviderSet = wire.NewSet(NewData, NewSystemRepo, NewAccessRepo, NewMenuRepo, NewOrganizationRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewInitializationRepo, NewAccessRepo, NewMenuRepo, NewOrganizationRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage)
type Data struct { type Data struct {
initMu sync.Mutex initMu sync.Mutex

View File

@ -92,4 +92,6 @@ func (menuParameterPO) TableName() string { return "sys_base_menu_parameters" }
type systemRepo struct{ data *Data } type systemRepo struct{ data *Data }
func NewSystemRepo(data *Data) biz.SystemRepo { return &systemRepo{data: data} } func NewUserRepo(data *Data) biz.UserRepo { return &systemRepo{data: data} }
func NewInitializationRepo(data *Data) biz.InitializationRepo { return &systemRepo{data: data} }

View File

@ -20,10 +20,10 @@ import (
kratoshttp "github.com/go-kratos/kratos/v3/transport/http" kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
) )
func NewGinEngine(runtime *conf.Runtime, system *service.SystemService, access *service.AccessService, 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, settings *service.SettingsService, audit *service.AuditService, logger *slog.Logger) *gin.Engine { func NewGinEngine(runtime *conf.Runtime, access *service.AccessService, 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, settings *service.SettingsService, audit *service.AuditService, logger *slog.Logger) *gin.Engine {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
engine := gin.New() engine := gin.New()
engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(audit, logger), servermiddleware.AccessLog(runtime, logger), servermiddleware.ErrorAudit(audit), servermiddleware.SecurityRateLimit(system, settings), servermiddleware.OperationAudit(runtime, audit)) engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(audit, logger), servermiddleware.AccessLog(runtime, logger), servermiddleware.ErrorAudit(audit), servermiddleware.SecurityRateLimit(settings), servermiddleware.OperationAudit(runtime, audit))
prefix := "" prefix := ""
config := runtime.Admin() config := runtime.Admin()

View File

@ -16,12 +16,12 @@ import (
) )
type Export struct { type Export struct {
system *service.SystemService settings *service.SettingsService
service *service.ExportService service *service.ExportService
} }
func NewExport(system *service.SystemService, service *service.ExportService) *Export { func NewExport(settings *service.SettingsService, export *service.ExportService) *Export {
return &Export{system: system, service: service} return &Export{settings: settings, service: export}
} }
type exportToken struct { type exportToken struct {
@ -153,7 +153,7 @@ func (h *Export) Issue(blank bool) gin.HandlerFunc {
} }
token := strings.ReplaceAll(uuid.NewString(), "-", "") token := strings.ReplaceAll(uuid.NewString(), "-", "")
raw, _ := json.Marshal(exportToken{TemplateID: templateID, Params: exportParams(c.Request.URL.Query()), Blank: blank}) raw, _ := json.Marshal(exportToken{TemplateID: templateID, Params: exportParams(c.Request.URL.Query()), Blank: blank})
if err := h.system.CacheSet(c.Request.Context(), "export:"+token, string(raw), 30*time.Minute); err != nil { if err := h.settings.CacheSet(c.Request.Context(), "export:"+token, string(raw), 30*time.Minute); err != nil {
httpx.Fail(c, "导出令牌创建失败") httpx.Fail(c, "导出令牌创建失败")
return return
} }
@ -196,7 +196,7 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
httpx.Fail(c, "导出token不能为空") httpx.Fail(c, "导出token不能为空")
return return
} }
raw, ok, err := h.system.CacheGet(c.Request.Context(), "export:"+token) raw, ok, err := h.settings.CacheGet(c.Request.Context(), "export:"+token)
if err != nil || !ok { if err != nil || !ok {
httpx.Fail(c, "导出token无效或已过期") httpx.Fail(c, "导出token无效或已过期")
return return
@ -210,7 +210,7 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
httpx.Fail(c, "token类型错误") httpx.Fail(c, "token类型错误")
return return
} }
_ = h.system.CacheDelete(c.Request.Context(), "export:"+token) _ = h.settings.CacheDelete(c.Request.Context(), "export:"+token)
var data []byte var data []byte
var name string var name string
if expectBlank { if expectBlank {

View File

@ -8,9 +8,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type Navigation struct{ service *service.SystemService } type Navigation struct{ service *service.UserService }
func NewNavigation(service *service.SystemService) *Navigation { return &Navigation{service: service} } func NewNavigation(service *service.UserService) *Navigation { return &Navigation{service: service} }
func (h *Navigation) Menu(c *gin.Context) { func (h *Navigation) Menu(c *gin.Context) {
claims := middleware.Claims(c) claims := middleware.Claims(c)
if claims == nil { if claims == nil {

View File

@ -19,7 +19,8 @@ import (
) )
type Public struct { type Public struct {
system *service.SystemService auth *service.AuthService
system *service.SystemConfigService
settings *service.SettingsService settings *service.SettingsService
audit *service.AuditService audit *service.AuditService
scheduler *worker.TaskScheduler scheduler *worker.TaskScheduler
@ -27,8 +28,8 @@ type Public struct {
store *captchaStore store *captchaStore
} }
func NewPublic(runtime *conf.Runtime, system *service.SystemService, settings *service.SettingsService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public { func NewPublic(runtime *conf.Runtime, auth *service.AuthService, system *service.SystemConfigService, settings *service.SettingsService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public {
return &Public{system: system, settings: settings, audit: audit, scheduler: scheduler, runtime: runtime, store: &captchaStore{service: system, runtime: runtime}} return &Public{auth: auth, system: system, settings: settings, audit: audit, scheduler: scheduler, runtime: runtime, store: &captchaStore{service: settings, runtime: runtime}}
} }
func (h *Public) captchaConfig() (int, int, int) { func (h *Public) captchaConfig() (int, int, int) {
@ -55,7 +56,7 @@ func (h *Public) Captcha(c *gin.Context) {
if security != nil { if security != nil {
keyLong, width, height = security.KeyLong, security.ImgWidth, security.ImgHeight keyLong, width, height = security.KeyLong, security.ImgWidth, security.ImgHeight
ttl := time.Duration(security.CaptchaTimeout) * time.Second ttl := time.Duration(security.CaptchaTimeout) * time.Second
failures, _ := ensureLoginIPCounter(c.Request.Context(), h.system, c.ClientIP(), ttl) failures, _ := ensureLoginIPCounter(c.Request.Context(), h.settings, c.ClientIP(), ttl)
openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
} }
driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80) driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80)
@ -75,7 +76,7 @@ func (h *Public) Login(c *gin.Context) {
} }
security, _ := h.settings.CurrentSecurity(c.Request.Context()) security, _ := h.settings.CurrentSecurity(c.Request.Context())
if security != nil && security.LockEnable { if security != nil && security.LockEnable {
if _, locked, _ := h.system.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked { if _, locked, _ := h.settings.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked {
httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(security.LockDuration)+" 分钟后再试") 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()}) _ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "账号已锁定", Agent: c.Request.UserAgent()})
return return
@ -87,18 +88,18 @@ func (h *Public) Login(c *gin.Context) {
if security.CaptchaTimeout > 0 { if security.CaptchaTimeout > 0 {
ipTTL = time.Duration(security.CaptchaTimeout) * time.Second ipTTL = time.Duration(security.CaptchaTimeout) * time.Second
} }
failures, _ := ensureLoginIPCounter(c.Request.Context(), h.system, c.ClientIP(), ipTTL) failures, _ := ensureLoginIPCounter(c.Request.Context(), h.settings, c.ClientIP(), ipTTL)
requireCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen requireCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
} }
if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !h.store.Verify(req.CaptchaID, req.Captcha, true)) { if requireCaptcha && (req.CaptchaID == "" || req.Captcha == "" || !h.store.Verify(req.CaptchaID, req.Captcha, true)) {
_, _ = h.system.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL) _, _ = h.settings.CacheIncrement(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()}) _ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "验证码错误", Agent: c.Request.UserAgent()})
httpx.Fail(c, "验证码错误") httpx.Fail(c, "验证码错误")
return return
} }
result, err := h.system.Login(c.Request.Context(), req.Username, req.Password) result, err := h.auth.Login(c.Request.Context(), req.Username, req.Password)
if err != nil { if err != nil {
_, _ = h.system.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL) _, _ = h.settings.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL)
if errors.Is(err, biz.ErrUserDisabled) { if errors.Is(err, biz.ErrUserDisabled) {
var disabled *service.UserDisabledError var disabled *service.UserDisabledError
errors.As(err, &disabled) errors.As(err, &disabled)
@ -113,9 +114,9 @@ func (h *Public) Login(c *gin.Context) {
_ = h.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: false, ErrorMessage: "用户名不存在或者密码错误", Agent: c.Request.UserAgent()}) _ = 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 { if security != nil && security.LockEnable {
lockTTL := time.Duration(security.LockDuration) * time.Minute lockTTL := time.Duration(security.LockDuration) * time.Minute
failures, _ := h.system.CacheIncrement(c.Request.Context(), "login:fail:"+req.Username, lockTTL) failures, _ := h.settings.CacheIncrement(c.Request.Context(), "login:fail:"+req.Username, lockTTL)
if int(failures) >= security.LockThreshold { if int(failures) >= security.LockThreshold {
_ = h.system.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", lockTTL) _ = h.settings.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", lockTTL)
} }
} }
httpx.Fail(c, "用户名不存在或者密码错误") httpx.Fail(c, "用户名不存在或者密码错误")
@ -123,15 +124,15 @@ func (h *Public) Login(c *gin.Context) {
} }
userID, _ := result.User["ID"].(uint) 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.audit.RecordLoginRequest(c.Request.Context(), &dto.LoginLogRequest{Username: req.Username, IP: c.ClientIP(), Status: true, Agent: c.Request.UserAgent(), UserID: userID})
_ = h.system.CacheDelete(c.Request.Context(), "login:fail:"+req.Username) _ = h.settings.CacheDelete(c.Request.Context(), "login:fail:"+req.Username)
_ = h.system.CacheDelete(c.Request.Context(), "login:lock:"+req.Username) _ = h.settings.CacheDelete(c.Request.Context(), "login:lock:"+req.Username)
maxAge := int(time.Until(time.UnixMilli(result.ExpiresAt)).Seconds()) maxAge := int(time.Until(time.UnixMilli(result.ExpiresAt)).Seconds())
httpx.SetTokenCookie(c, result.Token, maxAge) httpx.SetTokenCookie(c, result.Token, maxAge)
httpx.Write(c, httpx.CodeSuccess, result, "登录成功") httpx.Write(c, httpx.CodeSuccess, result, "登录成功")
} }
func ensureLoginIPCounter(ctx context.Context, system *service.SystemService, ip string, expiration time.Duration) (int, error) { func ensureLoginIPCounter(ctx context.Context, settings *service.SettingsService, ip string, expiration time.Duration) (int, error) {
value, exists, err := system.CacheGet(ctx, ip) value, exists, err := settings.CacheGet(ctx, ip)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -141,7 +142,7 @@ func ensureLoginIPCounter(ctx context.Context, system *service.SystemService, ip
if expiration <= 0 { if expiration <= 0 {
expiration = time.Hour expiration = time.Hour
} }
if err = system.CacheSet(ctx, ip, "1", expiration); err != nil { if err = settings.CacheSet(ctx, ip, "1", expiration); err != nil {
return 0, err return 0, err
} }
return 1, nil return 1, nil
@ -194,7 +195,7 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
} }
type captchaStore struct { type captchaStore struct {
service *service.SystemService service *service.SettingsService
runtime *conf.Runtime runtime *conf.Runtime
} }

View File

@ -16,12 +16,12 @@ import (
) )
type SystemConfig struct { type SystemConfig struct {
system *service.SystemService system *service.SystemConfigService
settings *service.SettingsService settings *service.SettingsService
scheduler *worker.TaskScheduler scheduler *worker.TaskScheduler
} }
func NewSystemConfig(system *service.SystemService, settings *service.SettingsService, scheduler *worker.TaskScheduler) *SystemConfig { func NewSystemConfig(system *service.SystemConfigService, settings *service.SettingsService, scheduler *worker.TaskScheduler) *SystemConfig {
return &SystemConfig{system: system, settings: settings, scheduler: scheduler} return &SystemConfig{system: system, settings: settings, scheduler: scheduler}
} }

View File

@ -12,9 +12,14 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type User struct{ service *service.SystemService } type User struct {
service *service.UserService
auth *service.AuthService
}
func NewUser(service *service.SystemService) *User { return &User{service: service} } func NewUser(user *service.UserService, auth *service.AuthService) *User {
return &User{service: user, auth: auth}
}
func (h *User) List(c *gin.Context) { func (h *User) List(c *gin.Context) {
var req dto.UserListRequest var req dto.UserListRequest
@ -155,7 +160,7 @@ func (h *User) SwitchAuthority(c *gin.Context) {
httpx.Fail(c, "参数错误") httpx.Fail(c, "参数错误")
return return
} }
login, err := h.service.SwitchAuthority(c.Request.Context(), claims.ID, req.AuthorityID) login, err := h.auth.SwitchAuthority(c.Request.Context(), claims.ID, req.AuthorityID)
if err != nil { if err != nil {
httpx.Fail(c, err.Error()) httpx.Fail(c, err.Error())
return return

View File

@ -10,7 +10,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func SecurityRateLimit(system *service.SystemService, settings *service.SettingsService) gin.HandlerFunc { func SecurityRateLimit(settings *service.SettingsService) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
path := strings.TrimSuffix(c.Request.URL.Path, "/") path := strings.TrimSuffix(c.Request.URL.Path, "/")
if !strings.HasSuffix(path, "/base/login") && !strings.HasSuffix(path, "/base/captcha") { if !strings.HasSuffix(path, "/base/login") && !strings.HasSuffix(path, "/base/captcha") {
@ -27,7 +27,7 @@ func SecurityRateLimit(system *service.SystemService, settings *service.Settings
window = 60 window = 60
} }
key := "KRA_SecLimit" + c.ClientIP() + c.FullPath() key := "KRA_SecLimit" + c.ClientIP() + c.FullPath()
count, cacheErr := system.CacheIncrement(c.Request.Context(), key, time.Duration(window)*time.Second) count, cacheErr := settings.CacheIncrement(c.Request.Context(), key, time.Duration(window)*time.Second)
if cacheErr == nil && int(count) > config.LimitCount { if cacheErr == nil && int(count) > config.LimitCount {
httpx.Fail(c, "请求太过频繁,请稍后再试") httpx.Fail(c, "请求太过频繁,请稍后再试")
c.Abort() c.Abort()

View File

@ -8,4 +8,4 @@ import (
) )
// ProviderSet is server providers. // 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.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, worker.NewTaskExecutor, worker.NewTaskScheduler)

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"kra/internal/biz" "kra/internal/biz"
"kra/internal/conf"
"kra/pkg/adminauth" "kra/pkg/adminauth"
) )
@ -20,20 +21,17 @@ type LoginResult struct {
NeedChangePassword bool `json:"needChangePassword"` NeedChangePassword bool `json:"needChangePassword"`
} }
func (s *SystemService) CacheGet(ctx context.Context, key string) (string, bool, error) { type AuthService struct {
return s.uc.CacheGet(ctx, key) uc *biz.UserUsecase
} runtime *conf.Runtime
func (s *SystemService) CacheSet(ctx context.Context, key, value string, expiration time.Duration) error { settings *SettingsService
return s.uc.CacheSet(ctx, key, value, expiration)
}
func (s *SystemService) CacheDelete(ctx context.Context, key string) error {
return s.uc.CacheDelete(ctx, key)
}
func (s *SystemService) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
return s.uc.CacheIncrement(ctx, key, expiration)
} }
func (s *SystemService) Login(ctx context.Context, username, password string) (*LoginResult, error) { func NewAuthService(uc *biz.UserUsecase, runtime *conf.Runtime, settings *SettingsService) *AuthService {
return &AuthService{uc: uc, runtime: runtime, settings: settings}
}
func (s *AuthService) Login(ctx context.Context, username, password string) (*LoginResult, error) {
u, err := s.uc.Login(ctx, username, password) u, err := s.uc.Login(ctx, username, password)
if err != nil { if err != nil {
return nil, err return nil, err
@ -47,7 +45,7 @@ func (s *SystemService) Login(ctx context.Context, username, password string) (*
return s.issueLogin(ctx, u, u.AuthorityID) return s.issueLogin(ctx, u, u.AuthorityID)
} }
func (s *SystemService) issueLogin(ctx context.Context, user *biz.User, authorityID uint) (*LoginResult, error) { func (s *AuthService) issueLogin(ctx context.Context, user *biz.User, authorityID uint) (*LoginResult, error) {
expires, buffer := 7*24*time.Hour, 24*time.Hour expires, buffer := 7*24*time.Hour, 24*time.Hour
secret, issuer := "", "kra" secret, issuer := "", "kra"
config := s.runtime.Admin() config := s.runtime.Admin()
@ -65,7 +63,7 @@ func (s *SystemService) issueLogin(ctx context.Context, user *biz.User, authorit
return nil, err return nil, err
} }
if s.settings.UseMultipoint() { if s.settings.UseMultipoint() {
oldToken, _, cacheErr := s.settings.cache.Get(ctx, activeTokenKey(user.Username)) oldToken, _, cacheErr := s.settings.CacheGet(ctx, activeTokenKey(user.Username))
if cacheErr != nil { if cacheErr != nil {
return nil, cacheErr return nil, cacheErr
} }
@ -75,3 +73,14 @@ func (s *SystemService) issueLogin(ctx context.Context, user *biz.User, authorit
} }
return &LoginResult{User: convertUser(user), Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli(), NeedChangePassword: user.MustChangePassword}, nil return &LoginResult{User: convertUser(user), Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli(), NeedChangePassword: user.MustChangePassword}, 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)
if err != nil {
return nil, err
}
return s.issueLogin(ctx, user, authorityID)
}

View File

@ -3,4 +3,4 @@ package service
import "github.com/google/wire" import "github.com/google/wire"
// ProviderSet is service providers. // ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewSystemService, NewAccessService, NewMenuService, NewOrganizationService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) var ProviderSet = wire.NewSet(NewAuthService, NewUserService, NewSystemConfigService, NewAccessService, NewMenuService, NewOrganizationService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService)

View File

@ -21,6 +21,22 @@ func NewSettingsService(uc *biz.SettingsUsecase, runtime *conf.Runtime, cache bi
return &SettingsService{uc: uc, runtime: runtime, cache: cache} return &SettingsService{uc: uc, runtime: runtime, cache: cache}
} }
func (s *SettingsService) CacheGet(ctx context.Context, key string) (string, bool, error) {
return s.cache.Get(ctx, key)
}
func (s *SettingsService) CacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
return s.cache.Set(ctx, key, value, expiration)
}
func (s *SettingsService) CacheDelete(ctx context.Context, key string) error {
return s.cache.Delete(ctx, key)
}
func (s *SettingsService) CacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) {
return s.cache.Increment(ctx, key, expiration)
}
func (s *SettingsService) UseMultipoint() bool { func (s *SettingsService) UseMultipoint() bool {
config := s.runtime.Admin() config := s.runtime.Admin()
return config != nil && config.System != nil && config.System.UseMultipoint return config != nil && config.System != nil && config.System.UseMultipoint
@ -32,7 +48,7 @@ func (s *SettingsService) ActiveTokenMatches(ctx context.Context, username, toke
if !s.UseMultipoint() { if !s.UseMultipoint() {
return true, nil return true, nil
} }
active, ok, err := s.cache.Get(ctx, activeTokenKey(username)) active, ok, err := s.CacheGet(ctx, activeTokenKey(username))
return ok && active == token, err return ok && active == token, err
} }
@ -45,5 +61,5 @@ func (s *SettingsService) RotateActiveToken(ctx context.Context, username, oldTo
return err return err
} }
} }
return s.cache.Set(ctx, activeTokenKey(username), newToken, expiration) return s.CacheSet(ctx, activeTokenKey(username), newToken, expiration)
} }

View File

@ -7,16 +7,15 @@ import (
"kra/internal/conf" "kra/internal/conf"
) )
type SystemService struct { type SystemConfigService struct {
uc *biz.SystemUsecase uc *biz.SystemConfigUsecase
runtime *conf.Runtime runtime *conf.Runtime
settings *SettingsService
} }
func NewSystemService(uc *biz.SystemUsecase, runtime *conf.Runtime, settings *SettingsService) *SystemService { func NewSystemConfigService(uc *biz.SystemConfigUsecase, runtime *conf.Runtime) *SystemConfigService {
return &SystemService{uc: uc, runtime: runtime, settings: settings} return &SystemConfigService{uc: uc, runtime: runtime}
} }
func (s *SystemService) IsInitialized(ctx context.Context) (bool, error) { func (s *SystemConfigService) IsInitialized(ctx context.Context) (bool, error) {
return s.uc.IsInitialized(ctx) return s.uc.IsInitialized(ctx)
} }

View File

@ -13,9 +13,11 @@ import (
"google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/durationpb"
) )
func (s *SystemService) PersistConfig(ctx context.Context) error { return s.uc.PersistConfig(ctx) } func (s *SystemConfigService) PersistConfig(ctx context.Context) error {
return s.uc.PersistConfig(ctx)
}
func (s *SystemService) PersistAdminConfig(ctx context.Context, value *conf.AdminBackend) error { func (s *SystemConfigService) PersistAdminConfig(ctx context.Context, value *conf.AdminBackend) error {
raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(value) raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(value)
if err != nil { if err != nil {
return err return err
@ -23,9 +25,9 @@ func (s *SystemService) PersistAdminConfig(ctx context.Context, value *conf.Admi
return s.uc.PersistAdminConfig(ctx, raw) return s.uc.PersistAdminConfig(ctx, raw)
} }
func (s *SystemService) ReloadConfig(ctx context.Context) error { return s.uc.ReloadConfig(ctx) } func (s *SystemConfigService) ReloadConfig(ctx context.Context) error { return s.uc.ReloadConfig(ctx) }
func (s *SystemService) DiskMountPoints() []string { func (s *SystemConfigService) DiskMountPoints() []string {
config := s.runtime.Admin() config := s.runtime.Admin()
if config == nil { if config == nil {
return nil return nil
@ -39,7 +41,7 @@ func (s *SystemService) DiskMountPoints() []string {
return points return points
} }
func (s *SystemService) SystemConfig() map[string]any { func (s *SystemConfigService) SystemConfig() map[string]any {
admin := map[string]any{"routerPrefix": ""} admin := map[string]any{"routerPrefix": ""}
email := map[string]any{} email := map[string]any{}
config := s.runtime.Admin() config := s.runtime.Admin()
@ -108,7 +110,7 @@ func (s *SystemService) SystemConfig() map[string]any {
return map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}} return map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}}
} }
func (s *SystemService) SaveSystemConfig(ctx context.Context, req *dto.SetSystemConfigRequest) error { func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.SetSystemConfigRequest) error {
config := s.runtime.Admin() config := s.runtime.Admin()
if config == nil { if config == nil {
return nil return nil

View File

@ -20,7 +20,7 @@ type DatabaseInit struct {
AdminPassword string `json:"adminPassword" binding:"required"` AdminPassword string `json:"adminPassword" binding:"required"`
} }
func (s *SystemService) Initialize(ctx context.Context, input *DatabaseInit, apis []*biz.API) error { func (s *SystemConfigService) Initialize(ctx context.Context, input *DatabaseInit, apis []*biz.API) error {
config := "" config := ""
switch input.DBType { switch input.DBType {
case "mysql": case "mysql":
@ -31,7 +31,7 @@ func (s *SystemService) Initialize(ctx context.Context, input *DatabaseInit, api
return s.uc.Initialize(ctx, &biz.DatabaseConfig{Driver: input.DBType, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, Template: input.Template, AdminPassword: input.AdminPassword, APIs: apis}) return s.uc.Initialize(ctx, &biz.DatabaseConfig{Driver: input.DBType, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, Template: input.Template, AdminPassword: input.AdminPassword, APIs: apis})
} }
func (s *SystemService) InitializeRoutes(ctx context.Context, input *DatabaseInit, routes []dto.Route) error { func (s *SystemConfigService) InitializeRoutes(ctx context.Context, input *DatabaseInit, routes []dto.Route) error {
apis := make([]*biz.API, 0, len(routes)) apis := make([]*biz.API, 0, len(routes))
for _, route := range routes { for _, route := range routes {
path := route.Path path := route.Path

View File

@ -6,18 +6,15 @@ import (
"time" "time"
"kra/internal/biz" "kra/internal/biz"
"kra/internal/conf"
"kra/internal/service/dto" "kra/internal/service/dto"
) )
type TaskService struct { type TaskService struct {
uc *biz.TaskUsecase uc *biz.TaskUsecase
media *biz.MediaUsecase
runtime *conf.Runtime
} }
func NewTaskService(uc *biz.TaskUsecase, media *biz.MediaUsecase, runtime *conf.Runtime) *TaskService { func NewTaskService(uc *biz.TaskUsecase) *TaskService {
return &TaskService{uc: uc, media: media, runtime: runtime} return &TaskService{uc: uc}
} }
func taskDomain(v *dto.TaskRequest) *biz.TimedTask { func taskDomain(v *dto.TaskRequest) *biz.TimedTask {
return &biz.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} return &biz.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
@ -87,3 +84,12 @@ func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, sta
} }
return out, total, nil return out, total, nil
} }
func (s *TaskService) RegisteredMethods() []map[string]any {
methods := biz.RegisteredTaskMethods()
out := make([]map[string]any, 0, len(methods))
for _, method := range methods {
out = append(out, map[string]any{"name": method.Name, "description": method.Description})
}
return out
}

View File

@ -15,20 +15,29 @@ type UserInput struct {
AuthorityIDs []uint AuthorityIDs []uint
} }
type UserService struct {
uc *biz.UserUsecase
settings *SettingsService
}
func NewUserService(uc *biz.UserUsecase, settings *SettingsService) *UserService {
return &UserService{uc: uc, settings: settings}
}
func userInput(value *dto.UserRequest) UserInput { func userInput(value *dto.UserRequest) UserInput {
return UserInput{ID: value.ID, Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, AuthorityIDs: value.AuthorityIDs, Enable: value.Enable, Phone: value.Phone, Email: value.Email} return UserInput{ID: value.ID, Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, AuthorityIDs: value.AuthorityIDs, Enable: value.Enable, Phone: value.Phone, Email: value.Email}
} }
func (s *SystemService) ListUsersRequest(ctx context.Context, value *dto.UserListRequest) ([]map[string]any, int64, error) { func (s *UserService) ListUsersRequest(ctx context.Context, value *dto.UserListRequest) ([]map[string]any, int64, error) {
return s.ListUsers(ctx, value.Page, value.PageSize, &biz.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc}) return s.ListUsers(ctx, value.Page, value.PageSize, &biz.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc})
} }
func (s *SystemService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (map[string]any, error) { func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (map[string]any, error) {
return s.CreateUser(ctx, userInput(value)) return s.CreateUser(ctx, userInput(value))
} }
func (s *SystemService) UpdateUserRequest(ctx context.Context, value *dto.UserRequest) error { func (s *UserService) UpdateUserRequest(ctx context.Context, value *dto.UserRequest) error {
return s.UpdateUser(ctx, userInput(value)) return s.UpdateUser(ctx, userInput(value))
} }
func (s *SystemService) User(ctx context.Context, id uint) (map[string]any, error) { func (s *UserService) User(ctx context.Context, id uint) (map[string]any, error) {
value, err := s.uc.User(ctx, id) value, err := s.uc.User(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -36,7 +45,7 @@ func (s *SystemService) User(ctx context.Context, id uint) (map[string]any, erro
return convertUser(value), nil return convertUser(value), nil
} }
func (s *SystemService) Menus(ctx context.Context, authorityID uint) ([]map[string]any, error) { func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]map[string]any, error) {
menus, err := s.uc.Menus(ctx, authorityID) menus, err := s.uc.Menus(ctx, authorityID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -48,7 +57,7 @@ func (s *SystemService) Menus(ctx context.Context, authorityID uint) ([]map[stri
return result, nil return result, nil
} }
func (s *SystemService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]map[string]any, int64, error) { func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]map[string]any, int64, error) {
users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter) users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@ -60,7 +69,7 @@ func (s *SystemService) ListUsers(ctx context.Context, page, pageSize int, filte
return result, total, nil return result, total, nil
} }
func (s *SystemService) Authorities(ctx context.Context) ([]map[string]any, error) { func (s *UserService) Authorities(ctx context.Context) ([]map[string]any, error) {
values, err := s.uc.Authorities(ctx) values, err := s.uc.Authorities(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -72,7 +81,7 @@ func (s *SystemService) Authorities(ctx context.Context) ([]map[string]any, erro
return result, nil return result, nil
} }
func (s *SystemService) CreateUser(ctx context.Context, input UserInput) (map[string]any, error) { func (s *UserService) CreateUser(ctx context.Context, input UserInput) (map[string]any, error) {
if err := s.settings.ValidatePassword(ctx, input.Password); err != nil { if err := s.settings.ValidatePassword(ctx, input.Password); err != nil {
return nil, err return nil, err
} }
@ -84,41 +93,31 @@ func (s *SystemService) CreateUser(ctx context.Context, input UserInput) (map[st
} }
return convertUser(user), nil return convertUser(user), nil
} }
func (s *SystemService) UpdateUser(ctx context.Context, input UserInput) error { func (s *UserService) UpdateUser(ctx context.Context, input UserInput) error {
return s.uc.UpdateUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable}, input.AuthorityIDs) return s.uc.UpdateUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable}, input.AuthorityIDs)
} }
func (s *SystemService) UpdateSelfUser(ctx context.Context, input UserInput) error { func (s *UserService) UpdateSelfUser(ctx context.Context, input UserInput) error {
return s.uc.UpdateSelfUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}) return s.uc.UpdateSelfUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable})
} }
func (s *SystemService) DeleteUser(ctx context.Context, id uint) error { func (s *UserService) DeleteUser(ctx context.Context, id uint) error {
return s.uc.DeleteUser(ctx, id) return s.uc.DeleteUser(ctx, id)
} }
func (s *SystemService) ResetPassword(ctx context.Context, id uint, password string) error { func (s *UserService) ResetPassword(ctx context.Context, id uint, password string) error {
if err := s.settings.ValidatePassword(ctx, password); err != nil { if err := s.settings.ValidatePassword(ctx, password); err != nil {
return err return err
} }
return s.uc.ResetPassword(ctx, id, password) return s.uc.ResetPassword(ctx, id, password)
} }
func (s *SystemService) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error { func (s *UserService) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error {
if err := s.settings.ValidatePassword(ctx, newPassword); err != nil { if err := s.settings.ValidatePassword(ctx, newPassword); err != nil {
return err return err
} }
return s.uc.ChangePassword(ctx, id, oldPassword, newPassword) return s.uc.ChangePassword(ctx, id, oldPassword, newPassword)
} }
func (s *SystemService) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error { func (s *UserService) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
return s.uc.SetUserAuthorities(ctx, id, authorityIDs) return s.uc.SetUserAuthorities(ctx, id, authorityIDs)
} }
func (s *SystemService) SwitchAuthority(ctx context.Context, id, authorityID uint) (*LoginResult, error) { func (s *UserService) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
if err := s.uc.SetUserAuthority(ctx, id, authorityID); err != nil {
return nil, err
}
user, err := s.uc.User(ctx, id)
if err != nil {
return nil, err
}
return s.issueLogin(ctx, user, authorityID)
}
func (s *SystemService) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
return s.uc.SetUserSetting(ctx, id, setting) return s.uc.SetUserSetting(ctx, id, setting)
} }

View File

@ -1,4 +1,4 @@
package service package worker
import ( import (
"bytes" "bytes"
@ -15,8 +15,19 @@ import (
"time" "time"
"kra/internal/biz" "kra/internal/biz"
"kra/internal/conf"
) )
type TaskExecutor struct {
tasks *biz.TaskUsecase
media *biz.MediaUsecase
runtime *conf.Runtime
}
func NewTaskExecutor(tasks *biz.TaskUsecase, media *biz.MediaUsecase, runtime *conf.Runtime) *TaskExecutor {
return &TaskExecutor{tasks: tasks, media: media, runtime: runtime}
}
func privateIP(ip net.IP) bool { func privateIP(ip net.IP) bool {
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()
} }
@ -42,28 +53,28 @@ func taskHTTPClient(allowPrivate bool) *http.Client {
return &http.Client{Timeout: 30 * time.Second, Transport: &http.Transport{Proxy: nil, DialContext: dialer.DialContext}} return &http.Client{Timeout: 30 * time.Second, Transport: &http.Transport{Proxy: nil, DialContext: dialer.DialContext}}
} }
func (s *TaskService) runHTTP(ctx context.Context, v *biz.TimedTask) (string, error) { func (e *TaskExecutor) runHTTP(ctx context.Context, task *biz.TimedTask) (string, error) {
parsed, err := url.Parse(v.HTTPURL) parsed, err := url.Parse(task.HTTPURL)
if err != nil { if err != nil {
return "", err return "", err
} }
if parsed.Scheme != "http" && parsed.Scheme != "https" { if parsed.Scheme != "http" && parsed.Scheme != "https" {
return "", fmt.Errorf("仅允许 http/https, 实际为 %q", parsed.Scheme) return "", fmt.Errorf("仅允许 http/https, 实际为 %q", parsed.Scheme)
} }
method := strings.ToUpper(v.HTTPMethod) method := strings.ToUpper(task.HTTPMethod)
if method == "" { if method == "" {
method = http.MethodGet method = http.MethodGet
} }
request, err := http.NewRequestWithContext(ctx, method, v.HTTPURL, bytes.NewBufferString(v.HTTPBody)) request, err := http.NewRequestWithContext(ctx, method, task.HTTPURL, bytes.NewBufferString(task.HTTPBody))
if err != nil { if err != nil {
return "", err return "", err
} }
headers := map[string]string{} headers := map[string]string{}
_ = json.Unmarshal(v.HTTPHeader, &headers) _ = json.Unmarshal(task.HTTPHeader, &headers)
for key, value := range headers { for key, value := range headers {
request.Header.Set(key, value) request.Header.Set(key, value)
} }
response, err := taskHTTPClient(v.HTTPAllowPrivate).Do(request) response, err := taskHTTPClient(task.HTTPAllowPrivate).Do(request)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -86,7 +97,7 @@ func truncateTaskText(value string) string {
return value[:limit] + "...(截断)" return value[:limit] + "...(截断)"
} }
func (s *TaskService) runMethod(v *biz.TimedTask) error { func (e *TaskExecutor) runMethod(task *biz.TimedTask) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel() defer cancel()
done := make(chan error, 1) done := make(chan error, 1)
@ -97,16 +108,16 @@ func (s *TaskService) runMethod(v *biz.TimedTask) error {
} }
}() }()
var err error var err error
switch v.MethodName { switch task.MethodName {
case biz.TaskMethodClearDB: case biz.TaskMethodClearDB:
err = s.uc.CleanupLogs(ctx) err = e.tasks.CleanupLogs(ctx)
case biz.TaskMethodUploads: case biz.TaskMethodUploads:
ttl := 24 ttl := 24
config := s.runtime.Admin() config := e.runtime.Admin()
if config != nil && config.Media != nil && config.Media.SessionTtl > 0 { if config != nil && config.Media != nil && config.Media.SessionTtl > 0 {
ttl = int(config.Media.SessionTtl) ttl = int(config.Media.SessionTtl)
} }
err = s.media.CleanupStale(ctx, ttl) err = e.media.CleanupStale(ctx, ttl)
} }
done <- err done <- err
}() }()
@ -121,9 +132,9 @@ func (s *TaskService) runMethod(v *biz.TimedTask) error {
} }
} }
func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string) (log *biz.TimedTaskLog) { func (e *TaskExecutor) Run(ctx context.Context, task *biz.TimedTask, trigger string) (log *biz.TimedTaskLog) {
started := time.Now() started := time.Now()
log = &biz.TimedTaskLog{TaskID: v.ID, TaskName: v.Name, TriggerType: trigger, StartedAt: started, Status: "success"} log = &biz.TimedTaskLog{TaskID: task.ID, TaskName: task.Name, TriggerType: trigger, StartedAt: started, Status: "success"}
defer func() { defer func() {
if recovered := recover(); recovered != nil { if recovered := recover(); recovered != nil {
log.Status = "fail" log.Status = "fail"
@ -134,18 +145,18 @@ func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string)
log.DurationMS = log.FinishedAt.Sub(started).Milliseconds() log.DurationMS = log.FinishedAt.Sub(started).Milliseconds()
logCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 3*time.Second) logCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 3*time.Second)
defer cancel() defer cancel()
_ = s.uc.RecordTaskLog(logCtx, log) _ = e.tasks.RecordTaskLog(logCtx, log)
}() }()
var err error var err error
switch v.ExecutorType { switch task.ExecutorType {
case biz.TaskExecutorMethod: case biz.TaskExecutorMethod:
err = s.runMethod(v) err = e.runMethod(task)
case biz.TaskExecutorHTTP: case biz.TaskExecutorHTTP:
runCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) runCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
log.Output, err = s.runHTTP(runCtx, v) log.Output, err = e.runHTTP(runCtx, task)
cancel() cancel()
default: default:
err = fmt.Errorf("未知执行器类型: %s", v.ExecutorType) err = fmt.Errorf("未知执行器类型: %s", task.ExecutorType)
} }
if err != nil { if err != nil {
if errors.Is(err, errTaskTimeout) || errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, errTaskTimeout) || errors.Is(err, context.DeadlineExceeded) {
@ -157,11 +168,3 @@ func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string)
} }
return log return log
} }
func (s *TaskService) RegisteredMethods() []map[string]any {
methods := biz.RegisteredTaskMethods()
out := make([]map[string]any, 0, len(methods))
for _, method := range methods {
out = append(out, map[string]any{"name": method.Name, "description": method.Description})
}
return out
}

View File

@ -1,4 +1,4 @@
package service package worker
import ( import (
"net" "net"

View File

@ -8,14 +8,13 @@ import (
"sync" "sync"
"time" "time"
"kra/internal/biz"
"kra/internal/service"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"kra/internal/biz"
) )
type TaskScheduler struct { type TaskScheduler struct {
service *service.TaskService tasks *biz.TaskUsecase
executor *TaskExecutor
logger *slog.Logger logger *slog.Logger
standard *cron.Cron standard *cron.Cron
seconds *cron.Cron seconds *cron.Cron
@ -33,8 +32,8 @@ type scheduledEntry struct {
entry cron.EntryID entry cron.EntryID
} }
func NewTaskScheduler(service *service.TaskService, logger *slog.Logger) *TaskScheduler { func NewTaskScheduler(tasks *biz.TaskUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler {
return &TaskScheduler{service: service, logger: logger, standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, subscribers: map[chan []byte]struct{}{}} 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 (s *TaskScheduler) Start(ctx context.Context) error { func (s *TaskScheduler) Start(ctx context.Context) error {
@ -44,7 +43,7 @@ func (s *TaskScheduler) Start(ctx context.Context) error {
s.ctxMu.Unlock() s.ctxMu.Unlock()
s.standard.Start() s.standard.Start()
s.seconds.Start() s.seconds.Start()
items, err := s.service.ScheduledTasks(ctx) items, _, err := s.tasks.ListTasks(ctx, 0, 0, nil)
if err == nil { if err == nil {
for _, task := range items { for _, task := range items {
if task.Enabled { if task.Enabled {
@ -104,7 +103,7 @@ func (s *TaskScheduler) Reload(ctx context.Context) error {
delete(s.entries, id) delete(s.entries, id)
} }
s.mu.Unlock() s.mu.Unlock()
items, err := s.service.ScheduledTasks(ctx) items, _, err := s.tasks.ListTasks(ctx, 0, 0, nil)
if err != nil { if err != nil {
return err return err
} }
@ -128,7 +127,7 @@ func (s *TaskScheduler) executionContext() context.Context {
} }
func (s *TaskScheduler) run(task *biz.TimedTask, trigger string) { func (s *TaskScheduler) run(task *biz.TimedTask, trigger string) {
log := s.service.Run(s.executionContext(), task, trigger) log := s.executor.Run(s.executionContext(), task, trigger)
if log.Status != "success" { if log.Status != "success" {
s.Broadcast(map[string]any{"taskId": task.ID, "taskName": task.Name, "status": log.Status, "errorMsg": log.ErrorMsg, "time": time.Now()}) s.Broadcast(map[string]any{"taskId": task.ID, "taskName": task.Name, "status": log.Status, "errorMsg": log.ErrorMsg, "time": time.Now()})
} }
@ -160,7 +159,7 @@ func (s *TaskScheduler) Schedule(task *biz.TimedTask) error {
} }
func (s *TaskScheduler) ScheduleID(ctx context.Context, id uint) error { func (s *TaskScheduler) ScheduleID(ctx context.Context, id uint) error {
task, err := s.service.Task(ctx, id) task, err := s.tasks.FindTask(ctx, id)
if err != nil { if err != nil {
return err return err
} }
@ -168,7 +167,7 @@ func (s *TaskScheduler) ScheduleID(ctx context.Context, id uint) error {
} }
func (s *TaskScheduler) TriggerID(ctx context.Context, id uint) error { func (s *TaskScheduler) TriggerID(ctx context.Context, id uint) error {
task, err := s.service.Task(ctx, id) task, err := s.tasks.FindTask(ctx, id)
if err != nil { if err != nil {
return err return err
} }