diff --git a/cmd/kratos-admin/wire_gen.go b/cmd/kratos-admin/wire_gen.go index 2728a4c..fa772f1 100644 --- a/cmd/kratos-admin/wire_gen.go +++ b/cmd/kratos-admin/wire_gen.go @@ -32,18 +32,24 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger } accessRepo := data.NewAccessRepo(dataData) accessUsecase := biz.NewAccessUsecase(accessRepo) - accessService := service.NewAccessService(accessUsecase, runtime) - authority := handler.NewAuthority(accessService) + accessControlService := service.NewAccessControlService(accessUsecase) + authorityService := service.NewAuthorityService(accessUsecase) + authority := handler.NewAuthority(authorityService) menuRepo := data.NewMenuRepo(dataData) menuUsecase := biz.NewMenuUsecase(menuRepo) menuService := service.NewMenuService(menuUsecase) menu := handler.NewMenu(menuService) - api := handler.NewAPI(accessService) - permission := handler.NewPermission(accessService) - organizationRepo := data.NewOrganizationRepo(dataData) - organizationUsecase := biz.NewOrganizationUsecase(organizationRepo) - organizationService := service.NewOrganizationService(organizationUsecase) - organization := handler.NewOrganization(organizationService) + apiService := service.NewAPIService(accessUsecase, runtime) + api := handler.NewAPI(apiService) + permissionService := service.NewPermissionService(accessUsecase) + permission := handler.NewPermission(permissionService) + departmentRepo := data.NewDepartmentRepo(dataData) + departmentUsecase := biz.NewDepartmentUsecase(departmentRepo) + departmentService := service.NewDepartmentService(departmentUsecase) + positionRepo := data.NewPositionRepo(dataData) + positionUsecase := biz.NewPositionUsecase(positionRepo) + positionService := service.NewPositionService(positionUsecase) + organization := handler.NewOrganization(departmentService, positionService) announcementRepo := data.NewAnnouncementRepo(dataData) announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo) announcementService := service.NewAnnouncementService(announcementUsecase) @@ -70,18 +76,14 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger auditRepo := data.NewAuditRepo(dataData) auditUsecase := biz.NewAuditUsecase(auditRepo) auditService := service.NewAuditService(auditUsecase) - audit := handler.NewAudit(auditService) - securityRepo := data.NewSecurityRepo(dataData) - securityUsecase := biz.NewSecurityUsecase(securityRepo) - cache := data.NewCache(dataData) - apiTokenRepo := data.NewAPITokenRepo(dataData) - tokenUsecase := biz.NewTokenUsecase(apiTokenRepo) - tokenService := service.NewTokenService(tokenUsecase, runtime) - securityService := service.NewSecurityService(securityUsecase, runtime, cache, tokenService) + auditRecorder := service.NewAuditRecorder(auditUsecase) + logViewerService := service.NewLogViewerService(auditUsecase) + audit := handler.NewAudit(auditService, auditRecorder, logViewerService) exportRepo := data.NewExportRepo(dataData) exportUsecase := biz.NewExportUsecase(exportRepo) - exportService := service.NewExportService(exportUsecase) - export := handler.NewExport(securityService, exportService) + cache := data.NewCache(dataData) + exportService := service.NewExportService(exportUsecase, cache) + export := handler.NewExport(exportService) versionRepo := data.NewVersionRepo(dataData) versionUsecase := biz.NewVersionUsecase(versionRepo) versionService := service.NewVersionService(versionUsecase) @@ -94,20 +96,26 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger parameterUsecase := biz.NewParameterUsecase(parameterRepo) parameterService := service.NewParameterService(parameterUsecase) parameter := handler.NewParameter(parameterService) + apiTokenRepo := data.NewAPITokenRepo(dataData) + tokenUsecase := biz.NewTokenUsecase(apiTokenRepo) + tokenService := service.NewTokenService(tokenUsecase, runtime) apiToken := handler.NewAPIToken(tokenService) initializationRepo := data.NewInitializationRepo(dataData) systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo) systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtime) + securityRepo := data.NewSecurityRepo(dataData) + securityUsecase := biz.NewSecurityUsecase(securityRepo) + securityService := service.NewSecurityService(securityUsecase, runtime, cache, tokenService) systemConfig := handler.NewSystemConfig(systemConfigService, securityService, taskScheduler) userRepo := data.NewUserRepo(dataData) userUsecase := biz.NewUserUsecase(userRepo) authService := service.NewAuthService(userUsecase, runtime, securityService) - public := handler.NewPublic(runtime, authService, systemConfigService, securityService, auditService, taskScheduler) + public := handler.NewPublic(runtime, authService, systemConfigService, securityService, auditRecorder, taskScheduler) userService := service.NewUserService(userUsecase, securityService) user := handler.NewUser(userService, authService) navigation := handler.NewNavigation(userService) session := handler.NewSession(tokenService) - 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, securityService, tokenService, auditService, logger) + 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) httpServer := server.NewGinServer(confServer, engine) app := newApp(logger, httpServer, taskScheduler) return app, func() { diff --git a/internal/biz/biz.go b/internal/biz/biz.go index e273ff1..c5a48aa 100644 --- a/internal/biz/biz.go +++ b/internal/biz/biz.go @@ -3,4 +3,4 @@ package biz import "github.com/google/wire" // ProviderSet is biz providers. -var ProviderSet = wire.NewSet(NewUserUsecase, NewSystemConfigUsecase, NewAccessUsecase, NewMenuUsecase, NewOrganizationUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) +var ProviderSet = wire.NewSet(NewUserUsecase, NewSystemConfigUsecase, NewAccessUsecase, NewMenuUsecase, NewDepartmentUsecase, NewPositionUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) diff --git a/internal/biz/organization.go b/internal/biz/organization.go index f097c74..c127408 100644 --- a/internal/biz/organization.go +++ b/internal/biz/organization.go @@ -31,10 +31,16 @@ type DepartmentRepo interface { SetUserDepartments(context.Context, uint, []uint, uint) error } -func (uc *OrganizationUsecase) Departments(ctx context.Context, name string) ([]*Department, error) { +type DepartmentUsecase struct{ DepartmentRepo } + +func NewDepartmentUsecase(repo DepartmentRepo) *DepartmentUsecase { + return &DepartmentUsecase{DepartmentRepo: repo} +} + +func (uc *DepartmentUsecase) Departments(ctx context.Context, name string) ([]*Department, error) { return uc.ListDepartments(ctx, name) } -func (uc *OrganizationUsecase) Department(ctx context.Context, id uint) (*Department, error) { +func (uc *DepartmentUsecase) Department(ctx context.Context, id uint) (*Department, error) { return uc.FindDepartment(ctx, id) } @@ -66,20 +72,15 @@ type PositionRepo interface { SetUserPositions(context.Context, uint, []uint) error } -func (uc *OrganizationUsecase) Positions(ctx context.Context, page, size int, filter *PositionListFilter) ([]*Position, int64, error) { +type PositionUsecase struct{ PositionRepo } + +func NewPositionUsecase(repo PositionRepo) *PositionUsecase { + return &PositionUsecase{PositionRepo: repo} +} + +func (uc *PositionUsecase) Positions(ctx context.Context, page, size int, filter *PositionListFilter) ([]*Position, int64, error) { return uc.ListPositions(ctx, page, size, filter) } -func (uc *OrganizationUsecase) Position(ctx context.Context, id uint) (*Position, error) { +func (uc *PositionUsecase) Position(ctx context.Context, id uint) (*Position, error) { return uc.FindPosition(ctx, id) } - -type OrganizationRepo interface { - DepartmentRepo - PositionRepo -} - -type OrganizationUsecase struct{ OrganizationRepo } - -func NewOrganizationUsecase(repo OrganizationRepo) *OrganizationUsecase { - return &OrganizationUsecase{OrganizationRepo: repo} -} diff --git a/internal/data/data.go b/internal/data/data.go index de3a857..c0d6646 100644 --- a/internal/data/data.go +++ b/internal/data/data.go @@ -13,7 +13,7 @@ import ( "kra/internal/conf" ) -var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewInitializationRepo, NewAccessRepo, NewMenuRepo, NewOrganizationRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) +var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewInitializationRepo, NewAccessRepo, NewMenuRepo, NewDepartmentRepo, NewPositionRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) type Data struct { initMu sync.Mutex diff --git a/internal/data/organization.go b/internal/data/organization.go index 5165fe9..8088c79 100644 --- a/internal/data/organization.go +++ b/internal/data/organization.go @@ -4,4 +4,6 @@ import "kra/internal/biz" type organizationRepo struct{ data *Data } -func NewOrganizationRepo(data *Data) biz.OrganizationRepo { return &organizationRepo{data: data} } +func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &organizationRepo{data: data} } + +func NewPositionRepo(data *Data) biz.PositionRepo { return &organizationRepo{data: data} } diff --git a/internal/server/gin.go b/internal/server/gin.go index 7caa7f6..e5ede82 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -20,7 +20,7 @@ import ( kratoshttp "github.com/go-kratos/kratos/v3/transport/http" ) -func NewGinEngine(runtime *conf.Runtime, access *service.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, security *service.SecurityService, tokens *service.TokenService, audit *service.AuditService, logger *slog.Logger) *gin.Engine { +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 { 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)) diff --git a/internal/server/handler/api.go b/internal/server/handler/api.go index 28ecc46..b7f5559 100644 --- a/internal/server/handler/api.go +++ b/internal/server/handler/api.go @@ -10,9 +10,9 @@ import ( "github.com/gin-gonic/gin" ) -type API struct{ service *service.AccessService } +type API struct{ service *service.APIService } -func NewAPI(service *service.AccessService) *API { return &API{service: service} } +func NewAPI(service *service.APIService) *API { return &API{service: service} } func (h *API) List(c *gin.Context) { var req dto.APIListRequest diff --git a/internal/server/handler/audit.go b/internal/server/handler/audit.go index ccd416a..fb5da58 100644 --- a/internal/server/handler/audit.go +++ b/internal/server/handler/audit.go @@ -13,9 +13,15 @@ import ( "github.com/gin-gonic/gin" ) -type Audit struct{ service *service.AuditService } +type Audit struct { + service *service.AuditService + recorder *service.AuditRecorder + logs *service.LogViewerService +} -func NewAudit(service *service.AuditService) *Audit { return &Audit{service: service} } +func NewAudit(service *service.AuditService, recorder *service.AuditRecorder, logs *service.LogViewerService) *Audit { + return &Audit{service: service, recorder: recorder, logs: logs} +} func page(c *gin.Context) (int, int) { value, _ := strconv.Atoi(c.Query("page")) @@ -152,7 +158,7 @@ func (h *Audit) DeleteDataAccess(c *gin.Context) { } func (h *Audit) LogDates(c *gin.Context) { - data, err := h.service.LogDates(c.Request.Context(), c.Query("month")) + data, err := h.logs.LogDates(c.Request.Context(), c.Query("month")) if err != nil { failLogViewer(c, err) return @@ -160,7 +166,7 @@ func (h *Audit) LogDates(c *gin.Context) { httpx.Write(c, httpx.CodeSuccess, data, "获取成功") } func (h *Audit) LogFiles(c *gin.Context) { - data, err := h.service.LogFiles(c.Request.Context(), c.Query("date")) + data, err := h.logs.LogFiles(c.Request.Context(), c.Query("date")) if err != nil { failLogViewer(c, err) return @@ -177,7 +183,7 @@ func (h *Audit) LogContent(c *gin.Context) { } cursor = &value } - data, err := h.service.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor) + data, err := h.logs.LogContent(c.Request.Context(), c.Query("date"), c.Query("path"), cursor) if err != nil { failLogViewer(c, err) return @@ -264,7 +270,7 @@ func (h *Audit) CreateError(c *gin.Context) { httpx.Fail(c, err.Error()) return } - if err := h.service.CreateErrorRequest(c.Request.Context(), &req); err != nil { + if err := h.recorder.CreateErrorRequest(c.Request.Context(), &req); err != nil { httpx.Fail(c, "创建失败:"+err.Error()) return } diff --git a/internal/server/handler/authority.go b/internal/server/handler/authority.go index d6e9d27..a1c7fc6 100644 --- a/internal/server/handler/authority.go +++ b/internal/server/handler/authority.go @@ -10,9 +10,9 @@ import ( "github.com/gin-gonic/gin" ) -type Authority struct{ service *service.AccessService } +type Authority struct{ service *service.AuthorityService } -func NewAuthority(service *service.AccessService) *Authority { return &Authority{service: service} } +func NewAuthority(service *service.AuthorityService) *Authority { return &Authority{service: service} } func (h *Authority) List(c *gin.Context) { items, err := h.service.Authorities(c.Request.Context()) diff --git a/internal/server/handler/export.go b/internal/server/handler/export.go index 40c50e4..fdac1ff 100644 --- a/internal/server/handler/export.go +++ b/internal/server/handler/export.go @@ -1,11 +1,10 @@ package handler import ( - "encoding/json" + "errors" "net/http" "net/url" "strings" - "time" "kra/internal/server/httpx" "kra/internal/service" @@ -16,18 +15,11 @@ import ( ) type Export struct { - settings *service.SecurityService - service *service.ExportService + service *service.ExportService } -func NewExport(settings *service.SecurityService, export *service.ExportService) *Export { - return &Export{settings: settings, service: export} -} - -type exportToken struct { - TemplateID string `json:"templateID"` - Params map[string]string `json:"params"` - Blank bool `json:"blank"` +func NewExport(service *service.ExportService) *Export { + return &Export{service: service} } func exportParams(values url.Values) map[string]string { @@ -151,9 +143,8 @@ func (h *Export) Issue(blank bool) gin.HandlerFunc { httpx.Fail(c, "模板ID不能为空") return } - token := strings.ReplaceAll(uuid.NewString(), "-", "") - raw, _ := json.Marshal(exportToken{TemplateID: templateID, Params: exportParams(c.Request.URL.Query()), Blank: blank}) - if err := h.settings.CacheSet(c.Request.Context(), "export:"+token, string(raw), 30*time.Minute); err != nil { + token, err := h.service.IssueToken(c.Request.Context(), templateID, exportParams(c.Request.URL.Query()), blank) + if err != nil { httpx.Fail(c, "导出令牌创建失败") return } @@ -196,21 +187,18 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc { httpx.Fail(c, "导出token不能为空") return } - raw, ok, err := h.settings.CacheGet(c.Request.Context(), "export:"+token) - if err != nil || !ok { - httpx.Fail(c, "导出token无效或已过期") + value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank) + if err != nil { + switch { + case errors.Is(err, service.ErrExportTokenMalformed): + httpx.Fail(c, "解析导出参数失败") + case errors.Is(err, service.ErrExportTokenType): + httpx.Fail(c, "token类型错误") + default: + httpx.Fail(c, "导出token无效或已过期") + } return } - var value exportToken - if json.Unmarshal([]byte(raw), &value) != nil { - httpx.Fail(c, "解析导出参数失败") - return - } - if value.Blank != expectBlank { - httpx.Fail(c, "token类型错误") - return - } - _ = h.settings.CacheDelete(c.Request.Context(), "export:"+token) var data []byte var name string if expectBlank { diff --git a/internal/server/handler/organization.go b/internal/server/handler/organization.go index ec13b7d..3497a89 100644 --- a/internal/server/handler/organization.go +++ b/internal/server/handler/organization.go @@ -10,16 +10,19 @@ import ( "github.com/gin-gonic/gin" ) -type Organization struct{ service *service.OrganizationService } +type Organization struct { + departments *service.DepartmentService + positions *service.PositionService +} -func NewOrganization(service *service.OrganizationService) *Organization { - return &Organization{service: service} +func NewOrganization(departments *service.DepartmentService, positions *service.PositionService) *Organization { + return &Organization{departments: departments, positions: positions} } func (h *Organization) ListDepartments(c *gin.Context) { var req dto.DepartmentListRequest _ = c.ShouldBindJSON(&req) - items, err := h.service.Departments(c.Request.Context(), req.Name) + items, err := h.departments.Departments(c.Request.Context(), req.Name) if err != nil { httpx.Fail(c, "获取失败") return @@ -36,7 +39,7 @@ func (h *Organization) CreateDepartment(c *gin.Context) { httpx.Fail(c, "部门名称不能为空") return } - if err := h.service.CreateDepartment(c.Request.Context(), &req); err != nil { + if err := h.departments.Create(c.Request.Context(), &req); err != nil { httpx.Fail(c, "创建失败:"+err.Error()) return } @@ -52,7 +55,7 @@ func (h *Organization) UpdateDepartment(c *gin.Context) { httpx.Fail(c, "部门ID不能为空") return } - if err := h.service.UpdateDepartment(c.Request.Context(), &req); err != nil { + if err := h.departments.Update(c.Request.Context(), &req); err != nil { httpx.Fail(c, "更新失败:"+err.Error()) return } @@ -64,7 +67,7 @@ func (h *Organization) DeleteDepartment(c *gin.Context) { httpx.Fail(c, "参数错误") return } - if err := h.service.DeleteDepartment(c.Request.Context(), req.ID); err != nil { + if err := h.departments.Delete(c.Request.Context(), req.ID); err != nil { httpx.Fail(c, err.Error()) return } @@ -72,7 +75,7 @@ func (h *Organization) DeleteDepartment(c *gin.Context) { } func (h *Organization) FindDepartment(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("id"), 10, 64) - item, err := h.service.Department(c.Request.Context(), uint(id)) + item, err := h.departments.Department(c.Request.Context(), uint(id)) if err != nil { httpx.Fail(c, "获取失败:"+err.Error()) return @@ -81,7 +84,7 @@ func (h *Organization) FindDepartment(c *gin.Context) { } func (h *Organization) DepartmentUsers(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64) - ids, err := h.service.DepartmentUserIDs(c.Request.Context(), uint(id)) + ids, err := h.departments.UserIDs(c.Request.Context(), uint(id)) if err != nil { httpx.Fail(c, "获取失败:"+err.Error()) return @@ -97,7 +100,7 @@ func (h *Organization) SetDepartmentUsers(c *gin.Context) { httpx.Fail(c, "参数错误") return } - if err := h.service.SetDepartmentUsers(c.Request.Context(), &req); err != nil { + if err := h.departments.SetUsers(c.Request.Context(), &req); err != nil { httpx.Fail(c, "设置失败:"+err.Error()) return } @@ -109,7 +112,7 @@ func (h *Organization) SetUserDepartments(c *gin.Context) { httpx.Fail(c, "参数错误") return } - if err := h.service.SetUserDepartments(c.Request.Context(), &req); err != nil { + if err := h.departments.SetUserDepartments(c.Request.Context(), &req); err != nil { httpx.Fail(c, "设置失败") return } @@ -122,7 +125,7 @@ func (h *Organization) ListPositions(c *gin.Context) { httpx.Fail(c, "参数错误") return } - items, total, err := h.service.Positions(c.Request.Context(), &req) + items, total, err := h.positions.Positions(c.Request.Context(), &req) if err != nil { httpx.Fail(c, "获取失败") return @@ -139,7 +142,7 @@ func (h *Organization) CreatePosition(c *gin.Context) { httpx.Fail(c, "岗位名称不能为空") return } - if err := h.service.CreatePosition(c.Request.Context(), &req); err != nil { + if err := h.positions.Create(c.Request.Context(), &req); err != nil { httpx.Fail(c, "创建失败:"+err.Error()) return } @@ -155,7 +158,7 @@ func (h *Organization) UpdatePosition(c *gin.Context) { httpx.Fail(c, "岗位ID不能为空") return } - if err := h.service.UpdatePosition(c.Request.Context(), &req); err != nil { + if err := h.positions.Update(c.Request.Context(), &req); err != nil { httpx.Fail(c, "更新失败:"+err.Error()) return } @@ -167,7 +170,7 @@ func (h *Organization) DeletePosition(c *gin.Context) { httpx.Fail(c, "参数错误") return } - if err := h.service.DeletePosition(c.Request.Context(), req.ID); err != nil { + if err := h.positions.Delete(c.Request.Context(), req.ID); err != nil { httpx.Fail(c, "删除失败:"+err.Error()) return } @@ -175,7 +178,7 @@ func (h *Organization) DeletePosition(c *gin.Context) { } func (h *Organization) FindPosition(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("id"), 10, 64) - item, err := h.service.Position(c.Request.Context(), uint(id)) + item, err := h.positions.Position(c.Request.Context(), uint(id)) if err != nil { httpx.Fail(c, "获取失败:"+err.Error()) return @@ -184,7 +187,7 @@ func (h *Organization) FindPosition(c *gin.Context) { } func (h *Organization) PositionUsers(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64) - ids, err := h.service.PositionUserIDs(c.Request.Context(), uint(id)) + ids, err := h.positions.UserIDs(c.Request.Context(), uint(id)) if err != nil { httpx.Fail(c, "获取失败:"+err.Error()) return @@ -200,7 +203,7 @@ func (h *Organization) SetPositionUsers(c *gin.Context) { httpx.Fail(c, "参数错误") return } - if err := h.service.SetPositionUsers(c.Request.Context(), &req); err != nil { + if err := h.positions.SetUsers(c.Request.Context(), &req); err != nil { httpx.Fail(c, "设置失败:"+err.Error()) return } @@ -212,7 +215,7 @@ func (h *Organization) SetUserPositions(c *gin.Context) { httpx.Fail(c, "参数错误") return } - if err := h.service.SetUserPositions(c.Request.Context(), &req); err != nil { + if err := h.positions.SetUserPositions(c.Request.Context(), &req); err != nil { httpx.Fail(c, "设置失败") return } diff --git a/internal/server/handler/permission.go b/internal/server/handler/permission.go index 2edbe29..f4c590a 100644 --- a/internal/server/handler/permission.go +++ b/internal/server/handler/permission.go @@ -10,9 +10,11 @@ import ( "github.com/gin-gonic/gin" ) -type Permission struct{ service *service.AccessService } +type Permission struct{ service *service.PermissionService } -func NewPermission(service *service.AccessService) *Permission { return &Permission{service: service} } +func NewPermission(service *service.PermissionService) *Permission { + return &Permission{service: service} +} func (h *Permission) Buttons(c *gin.Context) { var req dto.GetAuthorityButtonsRequest diff --git a/internal/server/handler/public.go b/internal/server/handler/public.go index 1297483..2968d4c 100644 --- a/internal/server/handler/public.go +++ b/internal/server/handler/public.go @@ -22,13 +22,13 @@ type Public struct { auth *service.AuthService system *service.SystemConfigService settings *service.SecurityService - audit *service.AuditService + audit *service.AuditRecorder scheduler *worker.TaskScheduler runtime *conf.Runtime store *captchaStore } -func NewPublic(runtime *conf.Runtime, auth *service.AuthService, system *service.SystemConfigService, settings *service.SecurityService, audit *service.AuditService, scheduler *worker.TaskScheduler) *Public { +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}} } @@ -56,7 +56,7 @@ func (h *Public) Captcha(c *gin.Context) { if security != nil { keyLong, width, height = security.KeyLong, security.ImgWidth, security.ImgHeight ttl := time.Duration(security.CaptchaTimeout) * time.Second - failures, _ := ensureLoginIPCounter(c.Request.Context(), h.settings, c.ClientIP(), ttl) + failures, _ := h.settings.EnsureLoginIPCounter(c.Request.Context(), c.ClientIP(), ttl) openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen } driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80) @@ -76,7 +76,7 @@ func (h *Public) Login(c *gin.Context) { } security, _ := h.settings.CurrentSecurity(c.Request.Context()) if security != nil && security.LockEnable { - if _, locked, _ := h.settings.CacheGet(c.Request.Context(), "login:lock:"+req.Username); locked { + 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 @@ -88,18 +88,18 @@ func (h *Public) Login(c *gin.Context) { if security.CaptchaTimeout > 0 { ipTTL = time.Duration(security.CaptchaTimeout) * time.Second } - failures, _ := ensureLoginIPCounter(c.Request.Context(), h.settings, c.ClientIP(), ipTTL) + 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.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL) + _, _ = 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) if err != nil { - _, _ = h.settings.CacheIncrement(c.Request.Context(), c.ClientIP(), ipTTL) + _, _ = h.settings.IncrementLoginIP(c.Request.Context(), c.ClientIP(), ipTTL) if errors.Is(err, biz.ErrUserDisabled) { var disabled *service.UserDisabledError errors.As(err, &disabled) @@ -114,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()}) if security != nil && security.LockEnable { lockTTL := time.Duration(security.LockDuration) * time.Minute - failures, _ := h.settings.CacheIncrement(c.Request.Context(), "login:fail:"+req.Username, lockTTL) + failures, _ := h.settings.IncrementLoginFailure(c.Request.Context(), req.Username, lockTTL) if int(failures) >= security.LockThreshold { - _ = h.settings.CacheSet(c.Request.Context(), "login:lock:"+req.Username, "1", lockTTL) + _ = h.settings.LockLogin(c.Request.Context(), req.Username, lockTTL) } } httpx.Fail(c, "用户名不存在或者密码错误") @@ -124,30 +124,12 @@ func (h *Public) Login(c *gin.Context) { } 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.CacheDelete(c.Request.Context(), "login:fail:"+req.Username) - _ = h.settings.CacheDelete(c.Request.Context(), "login:lock:"+req.Username) + 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, "登录成功") } -func ensureLoginIPCounter(ctx context.Context, settings *service.SecurityService, ip string, expiration time.Duration) (int, error) { - value, exists, err := settings.CacheGet(ctx, ip) - if err != nil { - return 0, err - } - if exists { - return strconv.Atoi(value) - } - if expiration <= 0 { - expiration = time.Hour - } - if err = settings.CacheSet(ctx, ip, "1", expiration); err != nil { - return 0, err - } - return 1, nil -} - func (h *Public) CheckDatabase(c *gin.Context) { initialized, err := h.system.IsInitialized(c.Request.Context()) if err != nil { @@ -199,22 +181,21 @@ type captchaStore struct { runtime *conf.Runtime } -func (s *captchaStore) key(id string) string { return "captcha:" + id } 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() } - return s.service.CacheSet(context.Background(), s.key(id), value, expiration) + return s.service.SetCaptcha(context.Background(), id, value, expiration) } func (s *captchaStore) Get(id string, clear bool) string { - value, ok, err := s.service.CacheGet(context.Background(), s.key(id)) + value, ok, err := s.service.GetCaptcha(context.Background(), id) if err != nil || !ok { return "" } if clear { - _ = s.service.CacheDelete(context.Background(), s.key(id)) + _ = s.service.DeleteCaptcha(context.Background(), id) } return value } diff --git a/internal/server/middleware/access.go b/internal/server/middleware/access.go index 9d4fccd..8afceb2 100644 --- a/internal/server/middleware/access.go +++ b/internal/server/middleware/access.go @@ -12,7 +12,7 @@ import ( "github.com/gin-gonic/gin" ) -func AccessControl(runtime *conf.Runtime, access *service.AccessService, audit *service.AuditService) gin.HandlerFunc { +func AccessControl(runtime *conf.Runtime, access *service.AccessControlService, audit *service.AuditRecorder) gin.HandlerFunc { return func(c *gin.Context) { claims := Claims(c) if claims == nil { diff --git a/internal/server/middleware/audit.go b/internal/server/middleware/audit.go index 843b327..7572ded 100644 --- a/internal/server/middleware/audit.go +++ b/internal/server/middleware/audit.go @@ -13,7 +13,7 @@ import ( "github.com/gin-gonic/gin" ) -func OperationAudit(runtime *conf.Runtime, service *service.AuditService) gin.HandlerFunc { +func OperationAudit(runtime *conf.Runtime, service *service.AuditRecorder) gin.HandlerFunc { return func(c *gin.Context) { path := c.Request.URL.Path if !recordsOperation(c.Request.Method, path) { diff --git a/internal/server/middleware/error_audit.go b/internal/server/middleware/error_audit.go index c8f1afe..2c53c77 100644 --- a/internal/server/middleware/error_audit.go +++ b/internal/server/middleware/error_audit.go @@ -14,7 +14,7 @@ import ( // ErrorAudit supplies the database sink that the Error-level logging core // provides. Expected authentication, permission and input failures are not // system errors and therefore are not inserted into sys_error. -func ErrorAudit(audit *service.AuditService) gin.HandlerFunc { +func ErrorAudit(audit *service.AuditRecorder) gin.HandlerFunc { return func(c *gin.Context) { writer := &captureWriter{ResponseWriter: c.Writer, maxBytes: 1 << 20} c.Writer = writer diff --git a/internal/server/middleware/rate_limit.go b/internal/server/middleware/rate_limit.go index d70e78a..3f14a96 100644 --- a/internal/server/middleware/rate_limit.go +++ b/internal/server/middleware/rate_limit.go @@ -27,7 +27,7 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc { window = 60 } key := "KRA_SecLimit" + c.ClientIP() + c.FullPath() - count, cacheErr := settings.CacheIncrement(c.Request.Context(), key, time.Duration(window)*time.Second) + count, cacheErr := settings.IncrementRateLimit(c.Request.Context(), key, time.Duration(window)*time.Second) if cacheErr == nil && int(count) > config.LimitCount { httpx.Fail(c, "请求太过频繁,请稍后再试") c.Abort() diff --git a/internal/server/middleware/recovery.go b/internal/server/middleware/recovery.go index 222dd2d..9b2bb7a 100644 --- a/internal/server/middleware/recovery.go +++ b/internal/server/middleware/recovery.go @@ -16,7 +16,7 @@ import ( "github.com/gin-gonic/gin" ) -func Recovery(audit *service.AuditService, logger *slog.Logger) gin.HandlerFunc { +func Recovery(audit *service.AuditRecorder, logger *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { defer func() { panicValue := recover() diff --git a/internal/service/access.go b/internal/service/access.go index d85edf4..055bd53 100644 --- a/internal/service/access.go +++ b/internal/service/access.go @@ -2,35 +2,30 @@ package service import ( "context" - "strings" "kra/internal/biz" - "kra/internal/conf" "kra/internal/service/dto" ) -type AccessService struct { - uc *biz.AccessUsecase - runtime *conf.Runtime +type AuthorityService struct{ uc *biz.AccessUsecase } + +func NewAuthorityService(uc *biz.AccessUsecase) *AuthorityService { + return &AuthorityService{uc: uc} } -func NewAccessService(uc *biz.AccessUsecase, runtime *conf.Runtime) *AccessService { - return &AccessService{uc: uc, runtime: runtime} +type PermissionService struct{ uc *biz.AccessUsecase } + +func NewPermissionService(uc *biz.AccessUsecase) *PermissionService { + return &PermissionService{uc: uc} } -func (s *AccessService) NormalizeRoutePath(path string) string { - config := s.runtime.Admin() - if config == nil || config.RouterPrefix == "" { - return path - } - prefix := strings.TrimSuffix(config.RouterPrefix, "/") - normalized := strings.TrimPrefix(path, prefix) - if normalized == "" { - return "/" - } - return normalized +type AccessControlService struct{ uc *biz.AccessUsecase } + +func NewAccessControlService(uc *biz.AccessUsecase) *AccessControlService { + return &AccessControlService{uc: uc} } -func (s *AccessService) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) { + +func (s *AccessControlService) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) { return s.uc.Authorize(ctx, aid, path, method) } @@ -43,7 +38,7 @@ func authorityDTO(v *biz.Authority) map[string]any { out["children"] = children return out } -func (s *AccessService) Authorities(ctx context.Context) ([]map[string]any, error) { +func (s *AuthorityService) Authorities(ctx context.Context) ([]map[string]any, error) { items, err := s.uc.AuthorityTree(ctx) if err != nil { return nil, err @@ -54,16 +49,16 @@ func (s *AccessService) Authorities(ctx context.Context) ([]map[string]any, erro } return roots, nil } -func (s *AccessService) CreateAuthority(ctx context.Context, v *biz.Authority) error { +func (s *AuthorityService) CreateAuthority(ctx context.Context, v *biz.Authority) error { return s.uc.CreateAuthority(ctx, v) } -func (s *AccessService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error { +func (s *AuthorityService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error { return s.uc.CopyAuthority(ctx, sourceID, v) } -func (s *AccessService) UpdateAuthority(ctx context.Context, v *biz.Authority) error { +func (s *AuthorityService) UpdateAuthority(ctx context.Context, v *biz.Authority) error { return s.uc.UpdateAuthority(ctx, v) } -func (s *AccessService) DeleteAuthority(ctx context.Context, id uint) error { +func (s *AuthorityService) DeleteAuthority(ctx context.Context, id uint) error { return s.uc.DeleteAuthority(ctx, id) } func authorityDomain(req *dto.AuthorityRequest) *biz.Authority { @@ -72,43 +67,43 @@ func authorityDomain(req *dto.AuthorityRequest) *biz.Authority { func authorityResponse(value *biz.Authority) *dto.AuthorityResponse { return &dto.AuthorityResponse{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} } -func (s *AccessService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { +func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { value := authorityDomain(req) if err := s.CreateAuthority(ctx, value); err != nil { return nil, err } return authorityResponse(value), nil } -func (s *AccessService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) { +func (s *AuthorityService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) { value := authorityDomain(&req.Authority) if err := s.CopyAuthority(ctx, req.OldAuthorityID, value); err != nil { return nil, err } return authorityResponse(value), nil } -func (s *AccessService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { +func (s *AuthorityService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { value := authorityDomain(req) if err := s.UpdateAuthority(ctx, value); err != nil { return nil, err } return authorityResponse(value), nil } -func (s *AccessService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { +func (s *AuthorityService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { return s.uc.SetAuthorityUsers(ctx, id, ids) } -func (s *AccessService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) { +func (s *AuthorityService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) { return s.uc.AuthorityUserIDs(ctx, id) } -func (s *AccessService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error { +func (s *AuthorityService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error { return s.uc.SetDataScope(ctx, id, scope, ids) } -func (s *AccessService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) { +func (s *AuthorityService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) { return s.uc.DataScopeDepartmentIDs(ctx, id) } -func (s *AccessService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) { +func (s *AccessControlService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) { return s.uc.ResolveDataScope(ctx, authorityID, userID) } -func (s *AccessService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) { +func (s *AccessControlService) ContextWithDataScope(ctx context.Context, authorityID, userID uint) (context.Context, error) { scope, err := s.ResolveDataScope(ctx, authorityID, userID) if err != nil { return ctx, err @@ -116,7 +111,7 @@ func (s *AccessService) ContextWithDataScope(ctx context.Context, authorityID, u return biz.NewDataScopeContext(ctx, scope), nil } -func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) { +func (s *PermissionService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) { items, err := s.uc.Buttons(ctx, id) if err != nil { return nil, err @@ -127,18 +122,18 @@ func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any, } return out, nil } -func (s *AccessService) SetAuthorityButtons(ctx context.Context, id uint, v map[uint][]uint) error { +func (s *PermissionService) SetAuthorityButtons(ctx context.Context, id uint, v map[uint][]uint) error { return s.uc.SetAuthorityButtons(ctx, id, v) } -func (s *AccessService) AuthorityButtonIDs(ctx context.Context, id uint) ([]uint, error) { +func (s *PermissionService) AuthorityButtonIDs(ctx context.Context, id uint) ([]uint, error) { return s.uc.AuthorityButtonIDs(ctx, id) } -func (s *AccessService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) { +func (s *PermissionService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) { return s.uc.SelectedButtons(ctx, aid, menuID) } -func (s *AccessService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error { +func (s *PermissionService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error { return s.uc.SetSelectedButtons(ctx, aid, menuID, ids) } -func (s *AccessService) CanRemoveButton(ctx context.Context, id uint) (bool, error) { +func (s *PermissionService) CanRemoveButton(ctx context.Context, id uint) (bool, error) { return s.uc.CanRemoveButton(ctx, id) } diff --git a/internal/service/api.go b/internal/service/api.go index c927eac..19b5d95 100644 --- a/internal/service/api.go +++ b/internal/service/api.go @@ -2,11 +2,35 @@ package service import ( "context" + "strings" "kra/internal/biz" + "kra/internal/conf" "kra/internal/service/dto" ) +type APIService struct { + uc *biz.AccessUsecase + runtime *conf.Runtime +} + +func NewAPIService(uc *biz.AccessUsecase, runtime *conf.Runtime) *APIService { + return &APIService{uc: uc, runtime: runtime} +} + +func (s *APIService) NormalizeRoutePath(path string) string { + config := s.runtime.Admin() + if config == nil || config.RouterPrefix == "" { + return path + } + prefix := strings.TrimSuffix(config.RouterPrefix, "/") + normalized := strings.TrimPrefix(path, prefix) + if normalized == "" { + return "/" + } + return normalized +} + func apiDomain(value *dto.APIRequest) *biz.API { return &biz.API{ID: value.ID, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} } @@ -23,7 +47,7 @@ func apiResponses(items []*biz.API) []*dto.APIResponse { return out } -func (s *AccessService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) { +func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) { items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method, OrderKey: req.OrderKey, Desc: req.Desc}) if err != nil { return nil, 0, err @@ -31,7 +55,7 @@ func (s *AccessService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([ return apiResponses(items), total, nil } -func (s *AccessService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) { +func (s *APIService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) { items, _, err := s.uc.ListAPIs(ctx, 0, 0, &biz.API{StrictAll: strict}) if err != nil { return nil, err @@ -39,24 +63,24 @@ func (s *AccessService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResp return apiResponses(items), nil } -func (s *AccessService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) (*dto.APIResponse, error) { +func (s *APIService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) (*dto.APIResponse, error) { value := apiDomain(req) if err := s.uc.CreateAPI(ctx, value); err != nil { return nil, err } return apiResponse(value), nil } -func (s *AccessService) UpdateAPIRequest(ctx context.Context, req *dto.APIRequest) error { +func (s *APIService) UpdateAPIRequest(ctx context.Context, req *dto.APIRequest) error { return s.uc.UpdateAPI(ctx, apiDomain(req)) } -func (s *AccessService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) { +func (s *APIService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) { value, err := s.uc.FindAPI(ctx, id) if err != nil { return nil, err } return apiResponse(value), nil } -func (s *AccessService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPISyncRequest) error { +func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPISyncRequest) error { added := make([]*biz.API, 0, len(req.NewAPIs)) deleted := make([]*biz.API, 0, len(req.DeleteAPIs)) for i := range req.NewAPIs { @@ -67,14 +91,14 @@ func (s *AccessService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyA } return s.uc.ApplyAPISync(ctx, added, deleted) } -func (s *AccessService) SetPolicyPathsRequest(ctx context.Context, req *dto.SetPolicyPathsRequest) error { +func (s *APIService) SetPolicyPathsRequest(ctx context.Context, req *dto.SetPolicyPathsRequest) error { paths := make([]*biz.API, 0, len(req.Infos)) for _, value := range req.Infos { paths = append(paths, &biz.API{Path: value.Path, Method: value.Method}) } return s.uc.SetPolicyPaths(ctx, req.AuthorityID, paths) } -func (s *AccessService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) { +func (s *APIService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) { items, err := s.uc.PolicyPaths(ctx, authorityID) if err != nil { return nil, err @@ -85,7 +109,7 @@ func (s *AccessService) PolicyPathResponses(ctx context.Context, authorityID uin } return out, nil } -func (s *AccessService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) { +func (s *APIService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) { items := make([]*biz.API, 0, len(routes)) for i := range routes { routes[i].Path = s.NormalizeRoutePath(routes[i].Path) @@ -106,22 +130,22 @@ func (s *AccessService) SyncAPIResponses(ctx context.Context, routes []dto.APIRe func apiDTO(v *biz.API) map[string]any { return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "path": v.Path, "description": v.Description, "apiGroup": v.APIGroup, "method": v.Method} } -func (s *AccessService) DeleteAPIs(ctx context.Context, ids []uint) error { +func (s *APIService) DeleteAPIs(ctx context.Context, ids []uint) error { return s.uc.DeleteAPIs(ctx, ids) } -func (s *AccessService) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) { +func (s *APIService) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) { return s.uc.APIRoleIDs(ctx, path, method) } -func (s *AccessService) SetAPIRoles(ctx context.Context, path, method string, ids []uint) error { +func (s *APIService) SetAPIRoles(ctx context.Context, path, method string, ids []uint) error { return s.uc.SetAPIRoles(ctx, path, method, ids) } -func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) { +func (s *APIService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) { diff, err := s.uc.SyncAPIs(ctx, routes) if err != nil { return nil, err } return &dto.APISyncResponse{NewAPIs: apiResponses(diff.Added), DeleteAPIs: apiResponses(diff.Deleted), IgnoreAPIs: apiResponses(diff.Ignored)}, nil } -func (s *AccessService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error { +func (s *APIService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error { return s.uc.SetAPIIgnored(ctx, path, method, ignored) } diff --git a/internal/service/audit.go b/internal/service/audit.go index 5931ffa..0a30e1f 100644 --- a/internal/service/audit.go +++ b/internal/service/audit.go @@ -11,10 +11,14 @@ type AuditService struct{ uc *biz.AuditUsecase } func NewAuditService(uc *biz.AuditUsecase) *AuditService { return &AuditService{uc: uc} } -func (s *AuditService) RecordOperation(ctx context.Context, v *biz.OperationRecord) error { +type AuditRecorder struct{ uc *biz.AuditUsecase } + +func NewAuditRecorder(uc *biz.AuditUsecase) *AuditRecorder { return &AuditRecorder{uc: uc} } + +func (s *AuditRecorder) RecordOperation(ctx context.Context, v *biz.OperationRecord) error { return s.uc.RecordOperation(ctx, v) } -func (s *AuditService) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error { +func (s *AuditRecorder) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error { return s.RecordOperation(ctx, &biz.OperationRecord{IP: value.IP, Method: value.Method, Path: value.Path, Status: value.Status, LatencyMS: value.LatencyMS, Agent: value.Agent, ErrorMessage: value.ErrorMessage, Body: value.Body, Response: value.Response, UserID: value.UserID, RequestID: value.RequestID, TraceID: value.TraceID, DeviceID: value.DeviceID}) } func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]map[string]any, int64, error) { @@ -49,10 +53,10 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []uint) error { return s.uc.DeleteOperations(ctx, ids) } -func (s *AuditService) RecordLogin(ctx context.Context, v *biz.LoginLog) error { +func (s *AuditRecorder) RecordLogin(ctx context.Context, v *biz.LoginLog) error { return s.uc.RecordLogin(ctx, v) } -func (s *AuditService) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error { +func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error { return s.RecordLogin(ctx, &biz.LoginLog{Username: value.Username, IP: value.IP, Status: value.Status, ErrorMessage: value.ErrorMessage, Agent: value.Agent, UserID: value.UserID}) } func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]map[string]any, int64, error) { @@ -87,10 +91,10 @@ func (s *AuditService) DeleteLogins(ctx context.Context, ids []uint) error { return s.uc.DeleteLogins(ctx, ids) } -func (s *AuditService) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error { +func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error { return s.uc.RecordDataAccess(ctx, v) } -func (s *AuditService) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error { +func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error { return s.RecordDataAccess(ctx, &biz.DataAccessLog{EventType: value.EventType, Operation: value.Operation, UserID: value.UserID, AuthorityID: value.AuthorityID, RequestID: value.RequestID, Method: value.Method, Path: value.Path, Detail: value.Detail}) } func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]map[string]any, int64, error) { diff --git a/internal/service/audit_error.go b/internal/service/audit_error.go index 644703f..f627061 100644 --- a/internal/service/audit_error.go +++ b/internal/service/audit_error.go @@ -11,13 +11,13 @@ import ( func errorDTO(v *biz.ErrorRecord) map[string]any { return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "form": v.Form, "info": v.Info, "level": v.Level, "request_id": v.RequestID, "trace_id": v.TraceID, "solution": v.Solution, "status": v.Status} } -func (s *AuditService) CreateError(ctx context.Context, v *biz.ErrorRecord) error { +func (s *AuditRecorder) CreateError(ctx context.Context, v *biz.ErrorRecord) error { return s.uc.CreateError(ctx, v) } func errorDomain(value *dto.ErrorRecordRequest) *biz.ErrorRecord { return &biz.ErrorRecord{ID: value.ID, Form: value.Form, Info: value.Info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Solution: value.Solution, Status: value.Status} } -func (s *AuditService) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error { +func (s *AuditRecorder) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error { return s.CreateError(ctx, errorDomain(req)) } func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error { diff --git a/internal/service/audit_log_file.go b/internal/service/audit_log_file.go index 63d433d..ef6d743 100644 --- a/internal/service/audit_log_file.go +++ b/internal/service/audit_log_file.go @@ -2,9 +2,17 @@ package service import ( "context" + + "kra/internal/biz" ) -func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]any, error) { +type LogViewerService struct{ uc *biz.AuditUsecase } + +func NewLogViewerService(uc *biz.AuditUsecase) *LogViewerService { + return &LogViewerService{uc: uc} +} + +func (s *LogViewerService) LogDates(ctx context.Context, month string) (map[string]any, error) { items, err := s.uc.LogDates(ctx, month) if err != nil { return nil, err @@ -15,7 +23,7 @@ func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]a } return map[string]any{"month": month, "dates": out}, nil } -func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]any, error) { +func (s *LogViewerService) LogFiles(ctx context.Context, date string) (map[string]any, error) { items, err := s.uc.LogFiles(ctx, date) if err != nil { return nil, err @@ -26,7 +34,7 @@ func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]an } return map[string]any{"date": date, "files": out}, nil } -func (s *AuditService) LogContent(ctx context.Context, date, path string, cursor *int64) (map[string]any, error) { +func (s *LogViewerService) LogContent(ctx context.Context, date, path string, cursor *int64) (map[string]any, error) { v, err := s.uc.LogContent(ctx, date, path, cursor) if err != nil { return nil, err diff --git a/internal/service/authentication.go b/internal/service/authentication.go index 859dc7e..aaae648 100644 --- a/internal/service/authentication.go +++ b/internal/service/authentication.go @@ -63,7 +63,7 @@ func (s *AuthService) issueLogin(ctx context.Context, user *biz.User, authorityI return nil, err } if s.settings.UseMultipoint() { - oldToken, _, cacheErr := s.settings.CacheGet(ctx, activeTokenKey(user.Username)) + oldToken, cacheErr := s.settings.ActiveToken(ctx, user.Username) if cacheErr != nil { return nil, cacheErr } diff --git a/internal/service/department.go b/internal/service/department.go new file mode 100644 index 0000000..7c50f84 --- /dev/null +++ b/internal/service/department.go @@ -0,0 +1,74 @@ +package service + +import ( + "context" + + "kra/internal/biz" + "kra/internal/service/dto" +) + +type DepartmentService struct{ uc *biz.DepartmentUsecase } + +func NewDepartmentService(uc *biz.DepartmentUsecase) *DepartmentService { + return &DepartmentService{uc: uc} +} + +func departmentResponse(value *biz.Department) *dto.DepartmentResponse { + children := make([]*dto.DepartmentResponse, 0, len(value.Children)) + for _, child := range value.Children { + children = append(children, departmentResponse(child)) + } + var leader any + if value.Leader != nil { + leader = convertUser(value.Leader) + } + return &dto.DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath} +} + +func departmentDomain(value *dto.DepartmentRequest) *biz.Department { + return &biz.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status} +} + +func (s *DepartmentService) Departments(ctx context.Context, name string) ([]*dto.DepartmentResponse, error) { + items, err := s.uc.Departments(ctx, name) + if err != nil { + return nil, err + } + out := make([]*dto.DepartmentResponse, 0, len(items)) + for _, item := range items { + out = append(out, departmentResponse(item)) + } + return out, nil +} + +func (s *DepartmentService) Create(ctx context.Context, req *dto.DepartmentRequest) error { + return s.uc.CreateDepartment(ctx, departmentDomain(req)) +} + +func (s *DepartmentService) Update(ctx context.Context, req *dto.DepartmentRequest) error { + return s.uc.UpdateDepartment(ctx, departmentDomain(req)) +} + +func (s *DepartmentService) Delete(ctx context.Context, id uint) error { + return s.uc.DeleteDepartment(ctx, id) +} + +func (s *DepartmentService) Department(ctx context.Context, id uint) (*dto.DepartmentResponse, error) { + value, err := s.uc.Department(ctx, id) + if err != nil { + return nil, err + } + return departmentResponse(value), nil +} + +func (s *DepartmentService) UserIDs(ctx context.Context, id uint) ([]uint, error) { + return s.uc.DepartmentUserIDs(ctx, id) +} + +func (s *DepartmentService) SetUsers(ctx context.Context, req *dto.SetDepartmentUsersRequest) error { + return s.uc.SetDepartmentUsers(ctx, req.DepartmentID, req.UserIDs) +} + +func (s *DepartmentService) SetUserDepartments(ctx context.Context, req *dto.SetUserDepartmentsRequest) error { + return s.uc.SetUserDepartments(ctx, req.ID, req.DepartmentIDs, req.Primary) +} diff --git a/internal/service/export.go b/internal/service/export.go index c0cdd04..cfdf26d 100644 --- a/internal/service/export.go +++ b/internal/service/export.go @@ -2,15 +2,71 @@ package service import ( "context" + "encoding/json" + "errors" + "strings" "time" "kra/internal/biz" "kra/internal/service/dto" + + "github.com/google/uuid" ) -type ExportService struct{ uc *biz.ExportUsecase } +var ( + ErrExportTokenInvalid = errors.New("export token invalid") + ErrExportTokenMalformed = errors.New("export token malformed") + ErrExportTokenType = errors.New("export token type mismatch") +) -func NewExportService(uc *biz.ExportUsecase) *ExportService { return &ExportService{uc: uc} } +type ExportService struct { + uc *biz.ExportUsecase + cache biz.Cache +} + +type ExportToken struct { + TemplateID string + Params map[string]string + Blank bool +} + +type exportTokenCache struct { + TemplateID string `json:"templateID"` + Params map[string]string `json:"params"` + Blank bool `json:"blank"` +} + +func NewExportService(uc *biz.ExportUsecase, cache biz.Cache) *ExportService { + return &ExportService{uc: uc, cache: cache} +} + +func (s *ExportService) IssueToken(ctx context.Context, templateID string, params map[string]string, blank bool) (string, error) { + token := strings.ReplaceAll(uuid.NewString(), "-", "") + raw, err := json.Marshal(exportTokenCache{TemplateID: templateID, Params: params, Blank: blank}) + if err != nil { + return "", err + } + if err = s.cache.Set(ctx, "export:"+token, string(raw), 30*time.Minute); err != nil { + return "", err + } + return token, nil +} + +func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBlank bool) (*ExportToken, error) { + raw, ok, err := s.cache.Get(ctx, "export:"+token) + if err != nil || !ok { + return nil, ErrExportTokenInvalid + } + var value exportTokenCache + if json.Unmarshal([]byte(raw), &value) != nil { + return nil, ErrExportTokenMalformed + } + if value.Blank != expectBlank { + return nil, ErrExportTokenType + } + _ = s.cache.Delete(ctx, "export:"+token) + return &ExportToken{TemplateID: value.TemplateID, Params: value.Params, Blank: value.Blank}, nil +} func exportTemplateDomain(value *dto.ExportTemplateRequest) *biz.ExportTemplate { out := &biz.ExportTemplate{ID: value.ID, DBName: value.DBName, Name: value.Name, TableName: value.TableName, TemplateID: value.TemplateID, TemplateInfo: value.TemplateInfo, SQL: value.SQL, ImportSQL: value.ImportSQL, Limit: value.Limit, Order: value.Order} for _, item := range value.Conditions { diff --git a/internal/service/organization.go b/internal/service/organization.go deleted file mode 100644 index 7792d2a..0000000 --- a/internal/service/organization.go +++ /dev/null @@ -1,110 +0,0 @@ -package service - -import ( - "context" - - "kra/internal/biz" - "kra/internal/service/dto" -) - -type OrganizationService struct{ uc *biz.OrganizationUsecase } - -func NewOrganizationService(uc *biz.OrganizationUsecase) *OrganizationService { - return &OrganizationService{uc: uc} -} - -func departmentResponse(value *biz.Department) *dto.DepartmentResponse { - children := make([]*dto.DepartmentResponse, 0, len(value.Children)) - for _, child := range value.Children { - children = append(children, departmentResponse(child)) - } - var leader any - if value.Leader != nil { - leader = convertUser(value.Leader) - } - return &dto.DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath} -} - -func departmentDomain(value *dto.DepartmentRequest) *biz.Department { - return &biz.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status} -} - -func (s *OrganizationService) Departments(ctx context.Context, name string) ([]*dto.DepartmentResponse, error) { - items, err := s.uc.Departments(ctx, name) - if err != nil { - return nil, err - } - out := make([]*dto.DepartmentResponse, 0, len(items)) - for _, item := range items { - out = append(out, departmentResponse(item)) - } - return out, nil -} -func (s *OrganizationService) CreateDepartment(ctx context.Context, req *dto.DepartmentRequest) error { - return s.uc.CreateDepartment(ctx, departmentDomain(req)) -} -func (s *OrganizationService) UpdateDepartment(ctx context.Context, req *dto.DepartmentRequest) error { - return s.uc.UpdateDepartment(ctx, departmentDomain(req)) -} -func (s *OrganizationService) DeleteDepartment(ctx context.Context, id uint) error { - return s.uc.DeleteDepartment(ctx, id) -} -func (s *OrganizationService) Department(ctx context.Context, id uint) (*dto.DepartmentResponse, error) { - value, err := s.uc.Department(ctx, id) - if err != nil { - return nil, err - } - return departmentResponse(value), nil -} -func (s *OrganizationService) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.DepartmentUserIDs(ctx, id) -} -func (s *OrganizationService) SetDepartmentUsers(ctx context.Context, req *dto.SetDepartmentUsersRequest) error { - return s.uc.SetDepartmentUsers(ctx, req.DepartmentID, req.UserIDs) -} -func (s *OrganizationService) SetUserDepartments(ctx context.Context, req *dto.SetUserDepartmentsRequest) error { - return s.uc.SetUserDepartments(ctx, req.ID, req.DepartmentIDs, req.Primary) -} - -func positionDomain(value *dto.PositionRequest) *biz.Position { - return &biz.Position{ID: value.ID, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} -} -func positionResponse(value *biz.Position) *dto.PositionResponse { - return &dto.PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} -} -func (s *OrganizationService) Positions(ctx context.Context, req *dto.PositionListRequest) ([]*dto.PositionResponse, int64, error) { - items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &biz.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status}) - if err != nil { - return nil, 0, err - } - out := make([]*dto.PositionResponse, 0, len(items)) - for _, item := range items { - out = append(out, positionResponse(item)) - } - return out, total, nil -} -func (s *OrganizationService) CreatePosition(ctx context.Context, req *dto.PositionRequest) error { - return s.uc.CreatePosition(ctx, positionDomain(req)) -} -func (s *OrganizationService) UpdatePosition(ctx context.Context, req *dto.PositionRequest) error { - return s.uc.UpdatePosition(ctx, positionDomain(req)) -} -func (s *OrganizationService) DeletePosition(ctx context.Context, id uint) error { - return s.uc.DeletePosition(ctx, id) -} -func (s *OrganizationService) Position(ctx context.Context, id uint) (*dto.PositionResponse, error) { - value, err := s.uc.Position(ctx, id) - if err != nil { - return nil, err - } - return positionResponse(value), nil -} -func (s *OrganizationService) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.PositionUserIDs(ctx, id) -} -func (s *OrganizationService) SetPositionUsers(ctx context.Context, req *dto.SetPositionUsersRequest) error { - return s.uc.SetPositionUsers(ctx, req.PositionID, req.UserIDs) -} -func (s *OrganizationService) SetUserPositions(ctx context.Context, req *dto.SetUserPositionsRequest) error { - return s.uc.SetUserPositions(ctx, req.ID, req.PositionIDs) -} diff --git a/internal/service/position.go b/internal/service/position.go new file mode 100644 index 0000000..35c1ee1 --- /dev/null +++ b/internal/service/position.go @@ -0,0 +1,66 @@ +package service + +import ( + "context" + + "kra/internal/biz" + "kra/internal/service/dto" +) + +type PositionService struct{ uc *biz.PositionUsecase } + +func NewPositionService(uc *biz.PositionUsecase) *PositionService { + return &PositionService{uc: uc} +} + +func positionDomain(value *dto.PositionRequest) *biz.Position { + return &biz.Position{ID: value.ID, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} +} + +func positionResponse(value *biz.Position) *dto.PositionResponse { + return &dto.PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} +} + +func (s *PositionService) Positions(ctx context.Context, req *dto.PositionListRequest) ([]*dto.PositionResponse, int64, error) { + items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &biz.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status}) + if err != nil { + return nil, 0, err + } + out := make([]*dto.PositionResponse, 0, len(items)) + for _, item := range items { + out = append(out, positionResponse(item)) + } + return out, total, nil +} + +func (s *PositionService) Create(ctx context.Context, req *dto.PositionRequest) error { + return s.uc.CreatePosition(ctx, positionDomain(req)) +} + +func (s *PositionService) Update(ctx context.Context, req *dto.PositionRequest) error { + return s.uc.UpdatePosition(ctx, positionDomain(req)) +} + +func (s *PositionService) Delete(ctx context.Context, id uint) error { + return s.uc.DeletePosition(ctx, id) +} + +func (s *PositionService) Position(ctx context.Context, id uint) (*dto.PositionResponse, error) { + value, err := s.uc.Position(ctx, id) + if err != nil { + return nil, err + } + return positionResponse(value), nil +} + +func (s *PositionService) UserIDs(ctx context.Context, id uint) ([]uint, error) { + return s.uc.PositionUserIDs(ctx, id) +} + +func (s *PositionService) SetUsers(ctx context.Context, req *dto.SetPositionUsersRequest) error { + return s.uc.SetPositionUsers(ctx, req.PositionID, req.UserIDs) +} + +func (s *PositionService) SetUserPositions(ctx context.Context, req *dto.SetUserPositionsRequest) error { + return s.uc.SetUserPositions(ctx, req.ID, req.PositionIDs) +} diff --git a/internal/service/security_session.go b/internal/service/security_session.go index e167852..394067b 100644 --- a/internal/service/security_session.go +++ b/internal/service/security_session.go @@ -2,6 +2,7 @@ package service import ( "context" + "strconv" "sync" "time" @@ -22,22 +23,82 @@ func NewSecurityService(uc *biz.SecurityUsecase, runtime *conf.Runtime, cache bi return &SecurityService{uc: uc, runtime: runtime, cache: cache, tokens: tokens} } -func (s *SecurityService) CacheGet(ctx context.Context, key string) (string, bool, error) { +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 { +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 { +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) { +func (s *SecurityService) cacheIncrement(ctx context.Context, key string, expiration time.Duration) (int64, error) { return s.cache.Increment(ctx, key, expiration) } +func (s *SecurityService) ActiveToken(ctx context.Context, username string) (string, error) { + value, _, err := s.cacheGet(ctx, activeTokenKey(username)) + return value, err +} + +func (s *SecurityService) LoginLocked(ctx context.Context, username string) (bool, error) { + _, locked, err := s.cacheGet(ctx, "login:lock:"+username) + return locked, err +} + +func (s *SecurityService) IncrementLoginFailure(ctx context.Context, username string, expiration time.Duration) (int64, error) { + return s.cacheIncrement(ctx, "login:fail:"+username, expiration) +} + +func (s *SecurityService) LockLogin(ctx context.Context, username string, expiration time.Duration) error { + return s.cacheSet(ctx, "login:lock:"+username, "1", expiration) +} + +func (s *SecurityService) ClearLoginState(ctx context.Context, username string) { + _ = s.cacheDelete(ctx, "login:fail:"+username) + _ = s.cacheDelete(ctx, "login:lock:"+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 +} + +func (s *SecurityService) IncrementLoginIP(ctx context.Context, ip string, expiration time.Duration) (int64, error) { + return s.cacheIncrement(ctx, ip, expiration) +} + +func (s *SecurityService) IncrementRateLimit(ctx context.Context, key string, expiration time.Duration) (int64, error) { + return s.cacheIncrement(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) +} + +func (s *SecurityService) GetCaptcha(ctx context.Context, id string) (string, bool, error) { + return s.cacheGet(ctx, "captcha:"+id) +} + +func (s *SecurityService) DeleteCaptcha(ctx context.Context, id string) error { + return s.cacheDelete(ctx, "captcha:"+id) +} + func (s *SecurityService) UseMultipoint() bool { config := s.runtime.Admin() return config != nil && config.System != nil && config.System.UseMultipoint @@ -49,7 +110,7 @@ func (s *SecurityService) ActiveTokenMatches(ctx context.Context, username, toke if !s.UseMultipoint() { return true, nil } - active, ok, err := s.CacheGet(ctx, activeTokenKey(username)) + active, ok, err := s.cacheGet(ctx, activeTokenKey(username)) return ok && active == token, err } @@ -62,5 +123,5 @@ func (s *SecurityService) RotateActiveToken(ctx context.Context, username, oldTo return err } } - return s.CacheSet(ctx, activeTokenKey(username), newToken, expiration) + return s.cacheSet(ctx, activeTokenKey(username), newToken, expiration) } diff --git a/internal/service/service.go b/internal/service/service.go index 0d6fa43..d0fb462 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -3,4 +3,4 @@ package service import "github.com/google/wire" // ProviderSet is service providers. -var ProviderSet = wire.NewSet(NewAuthService, NewUserService, NewSystemConfigService, NewAccessService, NewMenuService, NewOrganizationService, NewDictionaryService, NewParameterService, NewTokenService, NewSecurityService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) +var ProviderSet = wire.NewSet(NewAuthService, NewUserService, NewSystemConfigService, NewAuthorityService, NewPermissionService, NewAccessControlService, NewAPIService, NewMenuService, NewDepartmentService, NewPositionService, NewDictionaryService, NewParameterService, NewTokenService, NewSecurityService, NewVersionService, NewExportService, NewAuditService, NewAuditRecorder, NewLogViewerService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) diff --git a/internal/service/system_config.go b/internal/service/system_config.go index cefeafc..2123727 100644 --- a/internal/service/system_config.go +++ b/internal/service/system_config.go @@ -3,14 +3,12 @@ package service import ( "context" "encoding/json" - "time" "kra/internal/conf" "kra/internal/service/dto" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/durationpb" ) func (s *SystemConfigService) PersistConfig(ctx context.Context) error { @@ -116,127 +114,8 @@ func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.Set return nil } next := proto.Clone(config).(*conf.AdminBackend) - data := s.runtime.Data() - next.RouterPrefix = req.Config.Admin.RouterPrefix - if next.System == nil { - next.System = &conf.AdminBackend_System{} - } - next.System.UseRedis = req.Config.Admin.System.UseRedis - next.System.UseMultipoint = req.Config.Admin.System.UseMultipoint - next.System.UseStrictAuth = req.Config.Admin.System.UseStrictAuth - next.System.DisableAutoMigrate = req.Config.Admin.System.DisableAutoMigrate - next.System.UseMongo = req.Config.Admin.System.UseMongo - if req.Config.Admin.Zap != nil { - next.Zap = req.Config.Admin.Zap - } - if req.Config.Admin.Cors != nil { - next.Cors = req.Config.Admin.Cors - } - if req.Config.Admin.App != nil { - next.App = req.Config.Admin.App - } - if next.Jwt != nil { - if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" { - next.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey - } - if req.Config.Admin.JWT.Issuer != "" { - next.Jwt.Issuer = req.Config.Admin.JWT.Issuer - } - if value, err := time.ParseDuration(req.Config.Admin.JWT.ExpiresTime); err == nil && value > 0 { - next.Jwt.ExpiresTime = durationpb.New(value) - } - if value, err := time.ParseDuration(req.Config.Admin.JWT.BufferTime); err == nil && value >= 0 { - next.Jwt.BufferTime = durationpb.New(value) - } - } - if next.Captcha != nil { - if req.Config.Admin.Captcha.KeyLong > 0 { - next.Captcha.KeyLong = req.Config.Admin.Captcha.KeyLong - } - if req.Config.Admin.Captcha.ImgWidth > 0 { - next.Captcha.ImgWidth = req.Config.Admin.Captcha.ImgWidth - } - if req.Config.Admin.Captcha.ImgHeight > 0 { - next.Captcha.ImgHeight = req.Config.Admin.Captcha.ImgHeight - } - if value, err := time.ParseDuration(req.Config.Admin.Captcha.StoreExpiration); err == nil && value > 0 { - next.Captcha.StoreExpiration = durationpb.New(value) - } - } - if next.Local != nil { - if req.Config.Admin.Local.StorePath != "" { - next.Local.StorePath = req.Config.Admin.Local.StorePath - } - if req.Config.Admin.Local.PathPrefix != "" { - next.Local.PathPrefix = req.Config.Admin.Local.PathPrefix - } - } - if next.Media != nil { - if req.Config.Admin.Media.SessionTTL > 0 { - next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL - } - next.Media.MaxFileSize = req.Config.Admin.Media.MaxFileSize - } - if req.Config.Admin.Storage != nil { - preserveStorageSecrets(req.Config.Admin.Storage, next.Storage) - next.Storage = req.Config.Admin.Storage - } - if next.Email != nil && req.Config.Email != nil { - next.Email.To, next.Email.From, next.Email.Host = req.Config.Email.To, req.Config.Email.From, req.Config.Email.Host - next.Email.Nickname, next.Email.Port = req.Config.Email.Nickname, req.Config.Email.Port - next.Email.IsSsl, next.Email.IsLoginAuth = req.Config.Email.IsSSL, req.Config.Email.IsLoginAuth - if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" { - next.Email.Secret = req.Config.Email.Secret - } - } - if req.Config.Data != nil { - if data == nil { - data = &conf.Data{} - } - if value := req.Config.Data.Database; value != nil { - password := value.Password - if data.Database != nil && (password == "" || password == "******") { - password = data.Database.Password - } - data.Database = &conf.Data_Database{Driver: value.Driver, Source: value.Source, Host: value.Host, Port: value.Port, User: value.User, Password: password, Name: value.Name, Config: value.Config, Path: value.Path, Prefix: value.Prefix, Engine: value.Engine, LogMode: value.LogMode, MaxIdleConns: value.MaxIdleConns, MaxOpenConns: value.MaxOpenConns, ConnMaxLifetime: value.ConnMaxLifetime, Singular: value.Singular} - } - if value := req.Config.Data.Redis; value != nil { - password := value.Password - if data.Redis != nil && (password == "" || password == "******") { - password = data.Redis.Password - } - redis := &conf.Data_Redis{Network: value.Network, Addr: value.Addr, Name: value.Name, Password: password, Db: value.DB, UseCluster: value.UseCluster, ClusterAddrs: value.ClusterAddrs} - if duration, parseErr := time.ParseDuration(value.ReadTimeout); parseErr == nil && duration >= 0 { - redis.ReadTimeout = durationpb.New(duration) - } - if duration, parseErr := time.ParseDuration(value.WriteTimeout); parseErr == nil && duration >= 0 { - redis.WriteTimeout = durationpb.New(duration) - } - data.Redis = redis - } - if req.Config.Data.DatabaseList != nil { - for i, item := range req.Config.Data.DatabaseList { - if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.DatabaseList) && data.DatabaseList[i] != nil { - item.Password = data.DatabaseList[i].Password - } - } - data.DatabaseList = req.Config.Data.DatabaseList - } - if req.Config.Data.RedisList != nil { - for i, item := range req.Config.Data.RedisList { - if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.RedisList) && data.RedisList[i] != nil { - item.Password = data.RedisList[i].Password - } - } - data.RedisList = req.Config.Data.RedisList - } - if req.Config.Data.Mongo != nil { - if data.Mongo != nil && (req.Config.Data.Mongo.Password == "" || req.Config.Data.Mongo.Password == "******") { - req.Config.Data.Mongo.Password = data.Mongo.Password - } - data.Mongo = req.Config.Data.Mongo - } - } + applyAdminConfig(next, req) + data := applyDataConfig(s.runtime.Data(), req) dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(data) if err != nil { return err @@ -247,36 +126,3 @@ func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.Set } return s.uc.PersistRuntimeConfig(ctx, dataRaw, adminRaw) } - -func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore { - if storage == nil { - return nil - } - return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio} -} - -func maskStorageSecrets(storage *conf.AdminBackend_Storage) { - if storage.Qiniu != nil { - storage.Qiniu.SecretKey = "******" - } - for _, item := range objectStores(storage) { - if item != nil { - item.SecretKey = "******" - } - } -} - -func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) { - if next == nil || current == nil { - return - } - if next.Qiniu != nil && current.Qiniu != nil && (next.Qiniu.SecretKey == "" || next.Qiniu.SecretKey == "******") { - next.Qiniu.SecretKey = current.Qiniu.SecretKey - } - nextItems, currentItems := objectStores(next), objectStores(current) - for i := range nextItems { - if nextItems[i] != nil && currentItems[i] != nil && (nextItems[i].SecretKey == "" || nextItems[i].SecretKey == "******") { - nextItems[i].SecretKey = currentItems[i].SecretKey - } - } -} diff --git a/internal/service/system_config_apply.go b/internal/service/system_config_apply.go new file mode 100644 index 0000000..fcf8dc9 --- /dev/null +++ b/internal/service/system_config_apply.go @@ -0,0 +1,197 @@ +package service + +import ( + "time" + + "kra/internal/conf" + "kra/internal/service/dto" + + "google.golang.org/protobuf/types/known/durationpb" +) + +func applyAdminConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { + next.RouterPrefix = req.Config.Admin.RouterPrefix + if next.System == nil { + next.System = &conf.AdminBackend_System{} + } + next.System.UseRedis = req.Config.Admin.System.UseRedis + next.System.UseMultipoint = req.Config.Admin.System.UseMultipoint + next.System.UseStrictAuth = req.Config.Admin.System.UseStrictAuth + next.System.DisableAutoMigrate = req.Config.Admin.System.DisableAutoMigrate + next.System.UseMongo = req.Config.Admin.System.UseMongo + if req.Config.Admin.Zap != nil { + next.Zap = req.Config.Admin.Zap + } + if req.Config.Admin.Cors != nil { + next.Cors = req.Config.Admin.Cors + } + if req.Config.Admin.App != nil { + next.App = req.Config.Admin.App + } + applyJWTConfig(next, req) + applyCaptchaConfig(next, req) + applyMediaConfig(next, req) + if req.Config.Admin.Storage != nil { + preserveStorageSecrets(req.Config.Admin.Storage, next.Storage) + next.Storage = req.Config.Admin.Storage + } + if next.Email != nil && req.Config.Email != nil { + next.Email.To, next.Email.From, next.Email.Host = req.Config.Email.To, req.Config.Email.From, req.Config.Email.Host + next.Email.Nickname, next.Email.Port = req.Config.Email.Nickname, req.Config.Email.Port + next.Email.IsSsl, next.Email.IsLoginAuth = req.Config.Email.IsSSL, req.Config.Email.IsLoginAuth + if req.Config.Email.Secret != "" && req.Config.Email.Secret != "******" { + next.Email.Secret = req.Config.Email.Secret + } + } +} + +func applyJWTConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { + if next.Jwt == nil { + return + } + if req.Config.Admin.JWT.SigningKey != "" && req.Config.Admin.JWT.SigningKey != "******" { + next.Jwt.SigningKey = req.Config.Admin.JWT.SigningKey + } + if req.Config.Admin.JWT.Issuer != "" { + next.Jwt.Issuer = req.Config.Admin.JWT.Issuer + } + if value, err := time.ParseDuration(req.Config.Admin.JWT.ExpiresTime); err == nil && value > 0 { + next.Jwt.ExpiresTime = durationpb.New(value) + } + if value, err := time.ParseDuration(req.Config.Admin.JWT.BufferTime); err == nil && value >= 0 { + next.Jwt.BufferTime = durationpb.New(value) + } +} + +func applyCaptchaConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { + if next.Captcha != nil { + if req.Config.Admin.Captcha.KeyLong > 0 { + next.Captcha.KeyLong = req.Config.Admin.Captcha.KeyLong + } + if req.Config.Admin.Captcha.ImgWidth > 0 { + next.Captcha.ImgWidth = req.Config.Admin.Captcha.ImgWidth + } + if req.Config.Admin.Captcha.ImgHeight > 0 { + next.Captcha.ImgHeight = req.Config.Admin.Captcha.ImgHeight + } + if value, err := time.ParseDuration(req.Config.Admin.Captcha.StoreExpiration); err == nil && value > 0 { + next.Captcha.StoreExpiration = durationpb.New(value) + } + } + if next.Local != nil { + if req.Config.Admin.Local.StorePath != "" { + next.Local.StorePath = req.Config.Admin.Local.StorePath + } + if req.Config.Admin.Local.PathPrefix != "" { + next.Local.PathPrefix = req.Config.Admin.Local.PathPrefix + } + } +} + +func applyMediaConfig(next *conf.AdminBackend, req *dto.SetSystemConfigRequest) { + if next.Media == nil { + return + } + if req.Config.Admin.Media.SessionTTL > 0 { + next.Media.SessionTtl = req.Config.Admin.Media.SessionTTL + } + next.Media.MaxFileSize = req.Config.Admin.Media.MaxFileSize +} + +func applyDataConfig(data *conf.Data, req *dto.SetSystemConfigRequest) *conf.Data { + if req.Config.Data == nil { + return data + } + if data == nil { + data = &conf.Data{} + } + applyDatabaseConfig(data, req) + applyRedisConfig(data, req) + if req.Config.Data.DatabaseList != nil { + for i, item := range req.Config.Data.DatabaseList { + if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.DatabaseList) && data.DatabaseList[i] != nil { + item.Password = data.DatabaseList[i].Password + } + } + data.DatabaseList = req.Config.Data.DatabaseList + } + if req.Config.Data.RedisList != nil { + for i, item := range req.Config.Data.RedisList { + if item != nil && (item.Password == "" || item.Password == "******") && i < len(data.RedisList) && data.RedisList[i] != nil { + item.Password = data.RedisList[i].Password + } + } + data.RedisList = req.Config.Data.RedisList + } + if req.Config.Data.Mongo != nil { + if data.Mongo != nil && (req.Config.Data.Mongo.Password == "" || req.Config.Data.Mongo.Password == "******") { + req.Config.Data.Mongo.Password = data.Mongo.Password + } + data.Mongo = req.Config.Data.Mongo + } + return data +} + +func applyDatabaseConfig(data *conf.Data, req *dto.SetSystemConfigRequest) { + value := req.Config.Data.Database + if value == nil { + return + } + password := value.Password + if data.Database != nil && (password == "" || password == "******") { + password = data.Database.Password + } + data.Database = &conf.Data_Database{Driver: value.Driver, Source: value.Source, Host: value.Host, Port: value.Port, User: value.User, Password: password, Name: value.Name, Config: value.Config, Path: value.Path, Prefix: value.Prefix, Engine: value.Engine, LogMode: value.LogMode, MaxIdleConns: value.MaxIdleConns, MaxOpenConns: value.MaxOpenConns, ConnMaxLifetime: value.ConnMaxLifetime, Singular: value.Singular} +} + +func applyRedisConfig(data *conf.Data, req *dto.SetSystemConfigRequest) { + value := req.Config.Data.Redis + if value == nil { + return + } + password := value.Password + if data.Redis != nil && (password == "" || password == "******") { + password = data.Redis.Password + } + redis := &conf.Data_Redis{Network: value.Network, Addr: value.Addr, Name: value.Name, Password: password, Db: value.DB, UseCluster: value.UseCluster, ClusterAddrs: value.ClusterAddrs} + if duration, err := time.ParseDuration(value.ReadTimeout); err == nil && duration >= 0 { + redis.ReadTimeout = durationpb.New(duration) + } + if duration, err := time.ParseDuration(value.WriteTimeout); err == nil && duration >= 0 { + redis.WriteTimeout = durationpb.New(duration) + } + data.Redis = redis +} + +func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore { + if storage == nil { + return nil + } + return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio} +} + +func maskStorageSecrets(storage *conf.AdminBackend_Storage) { + if storage.Qiniu != nil { + storage.Qiniu.SecretKey = "******" + } + for _, item := range objectStores(storage) { + if item != nil { + item.SecretKey = "******" + } + } +} + +func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) { + if next == nil || current == nil { + return + } + if next.Qiniu != nil && current.Qiniu != nil && (next.Qiniu.SecretKey == "" || next.Qiniu.SecretKey == "******") { + next.Qiniu.SecretKey = current.Qiniu.SecretKey + } + nextItems, currentItems := objectStores(next), objectStores(current) + for i := range nextItems { + if nextItems[i] != nil && currentItems[i] != nil && (nextItems[i].SecretKey == "" || nextItems[i].SecretKey == "******") { + nextItems[i].SecretKey = currentItems[i].SecretKey + } + } +}