From e6775f0966085124a92da554ee0d7ae316815e39 Mon Sep 17 00:00:00 2001 From: yvan <8574526@qq.com> Date: Fri, 14 Aug 2026 01:04:21 +0800 Subject: [PATCH] refactor: split backend transport and domain modules --- cmd/kratos-admin/main.go | 3 +- cmd/kratos-admin/wire_gen.go | 42 ++- go.sum | 6 + internal/biz/access.go | 69 ----- internal/biz/biz.go | 2 +- internal/biz/menu.go | 134 +++++++++ internal/biz/organization.go | 104 +++++++ internal/biz/system.go | 20 -- internal/data/access.go | 280 ++---------------- internal/data/data.go | 2 +- internal/data/menu.go | 275 +++++++++++++++++ internal/server/access_routes.go | 44 --- internal/server/announcement_routes.go | 127 -------- internal/server/api_routes.go | 209 ------------- internal/server/authority_routes.go | 134 --------- internal/server/email_routes.go | 27 -- internal/server/gin.go | 104 ++----- internal/server/handler/announcement.go | 129 ++++++++ internal/server/handler/api.go | 201 +++++++++++++ internal/server/handler/authority.go | 117 ++++++++ internal/server/handler/email.go | 34 +++ internal/server/handler/menu.go | 136 +++++++++ internal/server/handler/organization.go | 198 +++++++++++++ internal/server/handler/permission.go | 56 ++++ internal/server/handler/task.go | 168 +++++++++++ internal/server/httpx/response.go | 40 +++ internal/server/menu_routes.go | 192 ------------ internal/server/middleware/access.go | 37 +++ internal/server/middleware/auth.go | 84 ++++++ internal/server/middleware/request.go | 18 ++ internal/server/organization_routes.go | 237 --------------- internal/server/policy_routes.go | 97 ------ internal/server/response.go | 43 +-- internal/server/router/announcement.go | 20 ++ internal/server/router/api.go | 28 ++ internal/server/router/authority.go | 20 ++ internal/server/router/email.go | 13 + internal/server/router/menu.go | 21 ++ internal/server/router/organization.go | 29 ++ internal/server/router/permission.go | 14 + internal/server/router/task.go | 20 ++ internal/server/server.go | 5 +- internal/server/system_config_routes.go | 3 +- internal/server/task_routes.go | 187 ------------ internal/service/access.go | 244 ++------------- internal/service/api.go | 90 ++++++ internal/service/dto/announcement.go | 11 + internal/service/dto/api.go | 71 +++++ internal/service/dto/authority.go | 35 +++ internal/service/dto/email.go | 7 + internal/service/dto/menu.go | 110 +++++++ internal/service/dto/organization.go | 83 ++++++ internal/service/dto/permission.go | 12 + internal/service/dto/task.go | 28 ++ internal/service/menu.go | 112 +++++++ internal/service/organization.go | 106 +++++++ internal/service/service.go | 2 +- internal/service/task.go | 17 ++ internal/{server => worker}/task_scheduler.go | 39 ++- 59 files changed, 2733 insertions(+), 1963 deletions(-) create mode 100644 internal/biz/menu.go create mode 100644 internal/biz/organization.go create mode 100644 internal/data/menu.go delete mode 100644 internal/server/access_routes.go delete mode 100644 internal/server/announcement_routes.go delete mode 100644 internal/server/api_routes.go delete mode 100644 internal/server/authority_routes.go create mode 100644 internal/server/handler/announcement.go create mode 100644 internal/server/handler/api.go create mode 100644 internal/server/handler/authority.go create mode 100644 internal/server/handler/email.go create mode 100644 internal/server/handler/menu.go create mode 100644 internal/server/handler/organization.go create mode 100644 internal/server/handler/permission.go create mode 100644 internal/server/handler/task.go create mode 100644 internal/server/httpx/response.go delete mode 100644 internal/server/menu_routes.go create mode 100644 internal/server/middleware/access.go create mode 100644 internal/server/middleware/auth.go create mode 100644 internal/server/middleware/request.go delete mode 100644 internal/server/organization_routes.go delete mode 100644 internal/server/policy_routes.go create mode 100644 internal/server/router/announcement.go create mode 100644 internal/server/router/api.go create mode 100644 internal/server/router/authority.go create mode 100644 internal/server/router/email.go create mode 100644 internal/server/router/menu.go create mode 100644 internal/server/router/organization.go create mode 100644 internal/server/router/permission.go create mode 100644 internal/server/router/task.go delete mode 100644 internal/server/task_routes.go create mode 100644 internal/service/api.go create mode 100644 internal/service/dto/announcement.go create mode 100644 internal/service/dto/api.go create mode 100644 internal/service/dto/authority.go create mode 100644 internal/service/dto/email.go create mode 100644 internal/service/dto/menu.go create mode 100644 internal/service/dto/organization.go create mode 100644 internal/service/dto/permission.go create mode 100644 internal/service/dto/task.go create mode 100644 internal/service/menu.go create mode 100644 internal/service/organization.go rename internal/{server => worker}/task_scheduler.go (90%) diff --git a/cmd/kratos-admin/main.go b/cmd/kratos-admin/main.go index 31674a9..5bd8d16 100644 --- a/cmd/kratos-admin/main.go +++ b/cmd/kratos-admin/main.go @@ -8,6 +8,7 @@ import ( "kra/internal/conf" "kra/internal/server" + "kra/internal/worker" "github.com/go-kratos/kratos/contrib/otel/v3/tracing" "github.com/go-kratos/kratos/v3" @@ -34,7 +35,7 @@ func init() { flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml") } -func newApp(logger *slog.Logger, hs *server.GinServer, scheduler *server.TaskScheduler) *kratos.App { +func newApp(logger *slog.Logger, hs *server.GinServer, scheduler *worker.TaskScheduler) *kratos.App { return kratos.New( kratos.ID(id), kratos.Name(Name), diff --git a/cmd/kratos-admin/wire_gen.go b/cmd/kratos-admin/wire_gen.go index 320acf6..6d2150d 100644 --- a/cmd/kratos-admin/wire_gen.go +++ b/cmd/kratos-admin/wire_gen.go @@ -12,7 +12,9 @@ import ( "kra/internal/conf" "kra/internal/data" "kra/internal/server" + "kra/internal/server/handler" "kra/internal/service" + "kra/internal/worker" "log/slog" ) @@ -43,6 +45,32 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad accessRepo := data.NewAccessRepo(dataData) accessUsecase := biz.NewAccessUsecase(accessRepo) accessService := service.NewAccessService(accessUsecase) + authority := handler.NewAuthority(accessService) + 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) + announcementRepo := data.NewAnnouncementRepo(dataData) + announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo) + announcementService := service.NewAnnouncementService(announcementUsecase) + announcement := handler.NewAnnouncement(announcementService) + emailRepo := data.NewEmailRepo(adminBackend) + emailUsecase := biz.NewEmailUsecase(emailRepo) + emailService := service.NewEmailService(emailUsecase) + email := handler.NewEmail(emailService) + taskRepo := data.NewTaskRepo(dataData) + taskUsecase := biz.NewTaskUsecase(taskRepo) + mediaRepo := data.NewMediaRepo(dataData) + mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage) + taskService := service.NewTaskService(taskUsecase, mediaUsecase, adminBackend) + taskScheduler := worker.NewTaskScheduler(taskService, logger) + task := handler.NewTask(taskService, taskScheduler) versionRepo := data.NewVersionRepo(dataData) versionUsecase := biz.NewVersionUsecase(versionRepo) versionService := service.NewVersionService(versionUsecase) @@ -52,20 +80,8 @@ func wireApp(confServer *conf.Server, confData *conf.Data, adminBackend *conf.Ad auditRepo := data.NewAuditRepo(dataData) auditUsecase := biz.NewAuditUsecase(auditRepo) auditService := service.NewAuditService(auditUsecase) - taskRepo := data.NewTaskRepo(dataData) - taskUsecase := biz.NewTaskUsecase(taskRepo) - mediaRepo := data.NewMediaRepo(dataData) - mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage) - taskService := service.NewTaskService(taskUsecase, mediaUsecase, adminBackend) mediaService := service.NewMediaService(mediaUsecase) - announcementRepo := data.NewAnnouncementRepo(dataData) - announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo) - announcementService := service.NewAnnouncementService(announcementUsecase) - emailRepo := data.NewEmailRepo(adminBackend) - emailUsecase := biz.NewEmailUsecase(emailRepo) - emailService := service.NewEmailService(emailUsecase) - taskScheduler := server.NewTaskScheduler(taskService, logger) - ginServer := server.NewGinServer(confServer, adminBackend, systemService, accessService, settingsService, versionService, exportService, auditService, taskService, mediaService, announcementService, emailService, taskScheduler, logger) + ginServer := server.NewGinServer(confServer, adminBackend, systemService, accessService, authority, menu, api, permission, organization, announcement, email, task, settingsService, versionService, exportService, auditService, mediaService, emailService, taskScheduler, logger) app := newApp(logger, ginServer, taskScheduler) return app, func() { cleanup() diff --git a/go.sum b/go.sum index 8224a73..6b7e0a6 100644 --- a/go.sum +++ b/go.sum @@ -175,6 +175,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -388,6 +390,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -483,6 +487,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= diff --git a/internal/biz/access.go b/internal/biz/access.go index 4148aa5..d586021 100644 --- a/internal/biz/access.go +++ b/internal/biz/access.go @@ -15,69 +15,18 @@ type API struct { Method string } -type MenuButton struct { - ID uint - Name string - Description string - MenuID uint - AuthorityID uint -} - -type MenuParameter struct { - ID uint - MenuID uint - Type string - Key string - Value string -} - -type Department struct { - ID uint - CreatedAt time.Time - UpdatedAt time.Time - Name string - ParentID uint - Ancestors string - Sort int - LeaderID uint - Status bool - Children []*Department -} - -type Position struct { - ID uint - CreatedAt time.Time - UpdatedAt time.Time - Name string - Code string - Sort int - Status bool - Remark string -} - type AccessRepo interface { CreateAuthority(context.Context, *Authority) error CopyAuthority(context.Context, uint, *Authority) error UpdateAuthority(context.Context, *Authority) error DeleteAuthority(context.Context, uint) error ListAuthorities(context.Context) ([]*Authority, error) - SetAuthorityMenus(context.Context, uint, []uint) error - AuthorityMenuIDs(context.Context, uint) ([]uint, error) SetAuthorityUsers(context.Context, uint, []uint) error AuthorityUserIDs(context.Context, uint) ([]uint, error) SetDataScope(context.Context, uint, int, []uint) error DataScopeDepartmentIDs(context.Context, uint) ([]uint, error) ResolveDataScope(context.Context, uint, uint) (DataScope, error) - CreateMenu(context.Context, *Menu) error - UpdateMenu(context.Context, *Menu) error - DeleteMenu(context.Context, uint) error - FindMenu(context.Context, uint) (*Menu, error) - ListMenus(context.Context) ([]*Menu, error) - MenuRoleIDs(context.Context, uint) ([]uint, error) - SetMenuRoles(context.Context, uint, []uint) error - ReplaceMenuButtons(context.Context, uint, []*MenuButton) error - CreateAPI(context.Context, *API) error UpdateAPI(context.Context, *API) error DeleteAPIs(context.Context, []uint) error @@ -98,24 +47,6 @@ type AccessRepo interface { SelectedButtons(context.Context, uint, uint) ([]uint, error) SetSelectedButtons(context.Context, uint, uint, []uint) error CanRemoveButton(context.Context, uint) (bool, error) - - CreateDepartment(context.Context, *Department) error - UpdateDepartment(context.Context, *Department) error - DeleteDepartment(context.Context, uint) error - FindDepartment(context.Context, uint) (*Department, error) - ListDepartments(context.Context, string) ([]*Department, error) - DepartmentUserIDs(context.Context, uint) ([]uint, error) - SetDepartmentUsers(context.Context, uint, []uint) error - SetUserDepartments(context.Context, uint, []uint, uint) error - - CreatePosition(context.Context, *Position) error - UpdatePosition(context.Context, *Position) error - DeletePosition(context.Context, uint) error - FindPosition(context.Context, uint) (*Position, error) - ListPositions(context.Context, int, int, *Position) ([]*Position, int64, error) - PositionUserIDs(context.Context, uint) ([]uint, error) - SetPositionUsers(context.Context, uint, []uint) error - SetUserPositions(context.Context, uint, []uint) error } type AccessUsecase struct{ repo AccessRepo } diff --git a/internal/biz/biz.go b/internal/biz/biz.go index 3de12e2..4981f41 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(NewSystemUsecase, NewAccessUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) +var ProviderSet = wire.NewSet(NewSystemUsecase, NewAccessUsecase, NewMenuUsecase, NewOrganizationUsecase, NewSettingsUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewTaskUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase) diff --git a/internal/biz/menu.go b/internal/biz/menu.go new file mode 100644 index 0000000..90430ae --- /dev/null +++ b/internal/biz/menu.go @@ -0,0 +1,134 @@ +package biz + +import "context" + +type MenuButton struct { + ID uint + Name string + Description string + MenuID uint + AuthorityID uint +} + +type MenuParameter struct { + ID uint + MenuID uint + Type string + Key string + Value string +} + +type Menu struct { + ID uint + ParentID uint + Path string + Name string + Hidden bool + Component string + Sort int + ActiveName string + KeepAlive bool + DefaultMenu bool + Title string + Icon string + CloseTab bool + TransitionType string + Children []*Menu + Buttons []*MenuButton + Parameters []*MenuParameter +} + +type MenuRepo interface { + Create(context.Context, *Menu) error + Update(context.Context, *Menu) error + Delete(context.Context, uint) error + Find(context.Context, uint) (*Menu, error) + List(context.Context) ([]*Menu, error) + SetAuthorityMenus(context.Context, uint, []uint) error + AuthorityMenuIDs(context.Context, uint) ([]uint, error) + MenuRoleIDs(context.Context, uint) ([]uint, error) + SetMenuRoles(context.Context, uint, []uint) error +} + +type MenuUsecase struct{ repo MenuRepo } + +func NewMenuUsecase(repo MenuRepo) *MenuUsecase { return &MenuUsecase{repo: repo} } + +func (uc *MenuUsecase) List(ctx context.Context) ([]*Menu, error) { + return uc.repo.List(ctx) +} + +func (uc *MenuUsecase) Tree(ctx context.Context) ([]*Menu, error) { + items, err := uc.repo.List(ctx) + if err != nil { + return nil, err + } + byID := make(map[uint]*Menu, len(items)) + for _, item := range items { + item.Children = []*Menu{} + byID[item.ID] = item + } + roots := make([]*Menu, 0) + for _, item := range items { + if parent := byID[item.ParentID]; parent != nil { + parent.Children = append(parent.Children, item) + } else { + roots = append(roots, item) + } + } + return roots, nil +} + +func (uc *MenuUsecase) Create(ctx context.Context, menu *Menu) error { + return uc.repo.Create(ctx, menu) +} + +func (uc *MenuUsecase) Update(ctx context.Context, menu *Menu) error { + return uc.repo.Update(ctx, menu) +} + +func (uc *MenuUsecase) Delete(ctx context.Context, id uint) error { + return uc.repo.Delete(ctx, id) +} + +func (uc *MenuUsecase) Find(ctx context.Context, id uint) (*Menu, error) { + return uc.repo.Find(ctx, id) +} + +func (uc *MenuUsecase) SetAuthorityMenus(ctx context.Context, authorityID uint, ids []uint) error { + return uc.repo.SetAuthorityMenus(ctx, authorityID, ids) +} + +func (uc *MenuUsecase) AuthorityMenus(ctx context.Context, authorityID uint) ([]*Menu, error) { + ids, err := uc.repo.AuthorityMenuIDs(ctx, authorityID) + if err != nil { + return nil, err + } + items, err := uc.repo.List(ctx) + if err != nil { + return nil, err + } + selected := make(map[uint]struct{}, len(ids)) + for _, id := range ids { + selected[id] = struct{}{} + } + result := make([]*Menu, 0, len(ids)) + for _, item := range items { + if _, ok := selected[item.ID]; ok { + // The authority-menu endpoint returns menu relations only. Keep its + // existing contract: button details are loaded by the dedicated + // authority-button endpoints. + item.Buttons = nil + result = append(result, item) + } + } + return result, nil +} + +func (uc *MenuUsecase) RoleIDs(ctx context.Context, menuID uint) ([]uint, error) { + return uc.repo.MenuRoleIDs(ctx, menuID) +} + +func (uc *MenuUsecase) SetRoles(ctx context.Context, menuID uint, ids []uint) error { + return uc.repo.SetMenuRoles(ctx, menuID, ids) +} diff --git a/internal/biz/organization.go b/internal/biz/organization.go new file mode 100644 index 0000000..7e1abfe --- /dev/null +++ b/internal/biz/organization.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" +) + +type Department struct { + ID uint + CreatedAt time.Time + UpdatedAt time.Time + Name string + ParentID uint + Ancestors string + Sort int + LeaderID uint + Status bool + Children []*Department +} + +type Position struct { + ID uint + CreatedAt time.Time + UpdatedAt time.Time + Name string + Code string + Sort int + Status bool + Remark string +} + +type OrganizationRepo interface { + CreateDepartment(context.Context, *Department) error + UpdateDepartment(context.Context, *Department) error + DeleteDepartment(context.Context, uint) error + FindDepartment(context.Context, uint) (*Department, error) + ListDepartments(context.Context, string) ([]*Department, error) + DepartmentUserIDs(context.Context, uint) ([]uint, error) + SetDepartmentUsers(context.Context, uint, []uint) error + SetUserDepartments(context.Context, uint, []uint, uint) error + CreatePosition(context.Context, *Position) error + UpdatePosition(context.Context, *Position) error + DeletePosition(context.Context, uint) error + FindPosition(context.Context, uint) (*Position, error) + ListPositions(context.Context, int, int, *Position) ([]*Position, int64, error) + PositionUserIDs(context.Context, uint) ([]uint, error) + SetPositionUsers(context.Context, uint, []uint) error + SetUserPositions(context.Context, uint, []uint) error +} + +type OrganizationUsecase struct{ repo OrganizationRepo } + +func NewOrganizationUsecase(repo OrganizationRepo) *OrganizationUsecase { + return &OrganizationUsecase{repo: repo} +} + +func (uc *OrganizationUsecase) Departments(ctx context.Context, name string) ([]*Department, error) { + return uc.repo.ListDepartments(ctx, name) +} +func (uc *OrganizationUsecase) CreateDepartment(ctx context.Context, value *Department) error { + return uc.repo.CreateDepartment(ctx, value) +} +func (uc *OrganizationUsecase) UpdateDepartment(ctx context.Context, value *Department) error { + return uc.repo.UpdateDepartment(ctx, value) +} +func (uc *OrganizationUsecase) DeleteDepartment(ctx context.Context, id uint) error { + return uc.repo.DeleteDepartment(ctx, id) +} +func (uc *OrganizationUsecase) Department(ctx context.Context, id uint) (*Department, error) { + return uc.repo.FindDepartment(ctx, id) +} +func (uc *OrganizationUsecase) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) { + return uc.repo.DepartmentUserIDs(ctx, id) +} +func (uc *OrganizationUsecase) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error { + return uc.repo.SetDepartmentUsers(ctx, id, ids) +} +func (uc *OrganizationUsecase) SetUserDepartments(ctx context.Context, id uint, ids []uint, primary uint) error { + return uc.repo.SetUserDepartments(ctx, id, ids, primary) +} +func (uc *OrganizationUsecase) Positions(ctx context.Context, page, size int, filter *Position) ([]*Position, int64, error) { + return uc.repo.ListPositions(ctx, page, size, filter) +} +func (uc *OrganizationUsecase) CreatePosition(ctx context.Context, value *Position) error { + return uc.repo.CreatePosition(ctx, value) +} +func (uc *OrganizationUsecase) UpdatePosition(ctx context.Context, value *Position) error { + return uc.repo.UpdatePosition(ctx, value) +} +func (uc *OrganizationUsecase) DeletePosition(ctx context.Context, id uint) error { + return uc.repo.DeletePosition(ctx, id) +} +func (uc *OrganizationUsecase) Position(ctx context.Context, id uint) (*Position, error) { + return uc.repo.FindPosition(ctx, id) +} +func (uc *OrganizationUsecase) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) { + return uc.repo.PositionUserIDs(ctx, id) +} +func (uc *OrganizationUsecase) SetPositionUsers(ctx context.Context, id uint, ids []uint) error { + return uc.repo.SetPositionUsers(ctx, id, ids) +} +func (uc *OrganizationUsecase) SetUserPositions(ctx context.Context, id uint, ids []uint) error { + return uc.repo.SetUserPositions(ctx, id, ids) +} diff --git a/internal/biz/system.go b/internal/biz/system.go index 6acb46a..c02ad12 100644 --- a/internal/biz/system.go +++ b/internal/biz/system.go @@ -48,26 +48,6 @@ type Authority struct { DefaultRouter string } -type Menu struct { - ID uint - ParentID uint - Path string - Name string - Hidden bool - Component string - Sort int - ActiveName string - KeepAlive bool - DefaultMenu bool - Title string - Icon string - CloseTab bool - TransitionType string - Children []*Menu - Buttons []*MenuButton - Parameters []*MenuParameter -} - type SystemRepo interface { IsInitialized(context.Context) (bool, error) Initialize(context.Context, *DatabaseConfig) error diff --git a/internal/data/access.go b/internal/data/access.go index 103341a..f77a8d1 100644 --- a/internal/data/access.go +++ b/internal/data/access.go @@ -109,8 +109,10 @@ type authorityDepartmentPO struct { func (authorityDepartmentPO) TableName() string { return "sys_authority_departments" } type accessRepo struct{ data *Data } +type organizationRepo struct{ data *Data } -func NewAccessRepo(data *Data) biz.AccessRepo { return &accessRepo{data: data} } +func NewAccessRepo(data *Data) biz.AccessRepo { return &accessRepo{data: data} } +func NewOrganizationRepo(data *Data) biz.OrganizationRepo { return &organizationRepo{data: data} } func (r *accessRepo) CreateAuthority(ctx context.Context, value *biz.Authority) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { @@ -263,26 +265,6 @@ func (r *accessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, err } return out, nil } -func (r *accessRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error { - return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { - return err - } - links := make([]authorityMenuPO, 0, len(ids)) - for _, mid := range ids { - links = append(links, authorityMenuPO{SysAuthorityAuthorityID: id, SysBaseMenuID: mid}) - } - if len(links) > 0 { - return tx.Create(&links).Error - } - return nil - }) -} -func (r *accessRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) { - var ids []uint - err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error - return ids, err -} func (r *accessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var oldIDs []uint @@ -403,228 +385,6 @@ func (r *accessRepo) ResolveDataScope(ctx context.Context, authorityID, userID u return biz.DataScope{DepartmentIDs: ids}, nil } -func menuFromPO(po menuPO) *biz.Menu { - return &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType} -} -func menuToPO(v *biz.Menu) menuPO { - return menuPO{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.ActiveName, KeepAlive: v.KeepAlive, DefaultMenu: v.DefaultMenu, Title: v.Title, Icon: v.Icon, CloseTab: v.CloseTab, TransitionType: v.TransitionType} -} -func replaceMenuRelations(tx *gorm.DB, menu *biz.Menu) error { - var oldButtonIDs []uint - if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", menu.ID).Pluck("id", &oldButtonIDs).Error; err != nil { - return err - } - if len(oldButtonIDs) > 0 { - if err := tx.Where("sys_base_menu_btn_id IN ?", oldButtonIDs).Delete(&authorityButtonPO{}).Error; err != nil { - return err - } - } - if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuButtonPO{}).Error; err != nil { - return err - } - if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuParameterPO{}).Error; err != nil { - return err - } - buttons := make([]menuButtonPO, 0, len(menu.Buttons)) - for _, button := range menu.Buttons { - buttons = append(buttons, menuButtonPO{Name: button.Name, Description: button.Description, MenuID: menu.ID}) - } - if len(buttons) > 0 { - if err := tx.Create(&buttons).Error; err != nil { - return err - } - } - parameters := make([]menuParameterPO, 0, len(menu.Parameters)) - for _, parameter := range menu.Parameters { - parameters = append(parameters, menuParameterPO{MenuID: menu.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) - } - if len(parameters) > 0 { - return tx.Create(¶meters).Error - } - return nil -} -func (r *accessRepo) CreateMenu(ctx context.Context, v *biz.Menu) error { - return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - var count int64 - if err := tx.Model(&menuPO{}).Where("name = ?", v.Name).Count(&count).Error; err != nil { - return err - } - if count > 0 { - return errors.New("存在重复name,请修改name") - } - if v.ParentID != 0 { - var parent menuPO - if err := tx.First(&parent, v.ParentID).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return errors.New("父菜单不存在") - } - return err - } - if err := tx.Model(&menuPO{}).Where("parent_id = ?", v.ParentID).Count(&count).Error; err != nil { - return err - } - if count == 0 { - if err := tx.Model(&authorityPO{}).Where("default_router = ?", parent.Name).Count(&count).Error; err != nil { - return err - } - if count > 0 { - return errors.New("父菜单已被其他角色的首页占用,请先释放父菜单的首页权限") - } - if err := tx.Where("sys_base_menu_id = ?", v.ParentID).Delete(&authorityMenuPO{}).Error; err != nil { - return err - } - } - } - po := menuToPO(v) - if err := tx.Create(&po).Error; err != nil { - return err - } - v.ID = po.ID - return replaceMenuRelations(tx, v) - }) -} -func (r *accessRepo) UpdateMenu(ctx context.Context, v *biz.Menu) error { - return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - var old menuPO - if err := tx.First(&old, v.ID).Error; err != nil { - return err - } - var count int64 - if old.Name != v.Name { - if err := tx.Model(&menuPO{}).Where("id <> ? AND name = ?", v.ID, v.Name).Count(&count).Error; err != nil { - return err - } - if count > 0 { - return errors.New("存在相同name修改失败") - } - } - if err := replaceMenuRelations(tx, v); err != nil { - return err - } - values := map[string]any{"parent_id": v.ParentID, "path": v.Path, "name": v.Name, "hidden": v.Hidden, "component": v.Component, "sort": v.Sort, "active_name": v.ActiveName, "keep_alive": v.KeepAlive, "default_menu": v.DefaultMenu, "title": v.Title, "icon": v.Icon, "close_tab": v.CloseTab, "transition_type": v.TransitionType} - return tx.Model(&old).Updates(values).Error - }) -} -func (r *accessRepo) DeleteMenu(ctx context.Context, id uint) error { - return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - var count int64 - if err := tx.Model(&menuPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { - return err - } - if count > 0 { - return errors.New("此菜单存在子菜单不可删除") - } - var menu menuPO - if err := tx.First(&menu, id).Error; err != nil { - return errors.New("记录不存在") - } - if err := tx.Model(&authorityPO{}).Where("default_router = ?", menu.Name).Count(&count).Error; err != nil { - return err - } - if count > 0 { - return errors.New("此菜单有角色正在作为首页,不可删除") - } - var buttonIDs []uint - if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", id).Pluck("id", &buttonIDs).Error; err != nil { - return err - } - if err := tx.Where("sys_menu_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil { - return err - } - if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuButtonPO{}).Error; err != nil { - return err - } - if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuParameterPO{}).Error; err != nil { - return err - } - if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { - return err - } - return tx.Delete(&menuPO{}, id).Error - }) -} -func (r *accessRepo) FindMenu(ctx context.Context, id uint) (*biz.Menu, error) { - var po menuPO - if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { - return nil, err - } - menu := menuFromPO(po) - if err := r.loadMenuParameters(ctx, menu); err != nil { - return nil, err - } - return menu, nil -} -func (r *accessRepo) loadMenuParameters(ctx context.Context, menu *biz.Menu) error { - var pos []menuParameterPO - if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Order("id").Find(&pos).Error; err != nil { - return err - } - menu.Parameters = make([]*biz.MenuParameter, 0, len(pos)) - for _, po := range pos { - menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: po.ID, MenuID: po.MenuID, Type: po.Type, Key: po.Key, Value: po.Value}) - } - return nil -} -func (r *accessRepo) ListMenus(ctx context.Context) ([]*biz.Menu, error) { - var pos []menuPO - if err := r.data.gormDB.WithContext(ctx).Order("sort,id").Find(&pos).Error; err != nil { - return nil, err - } - out := make([]*biz.Menu, 0, len(pos)) - for _, po := range pos { - menu := menuFromPO(po) - if err := r.loadMenuParameters(ctx, menu); err != nil { - return nil, err - } - out = append(out, menu) - } - return out, nil -} -func (r *accessRepo) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) { - var ids []uint - err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_base_menu_id = ?", id).Pluck("sys_authority_authority_id", &ids).Error - return ids, err -} -func (r *accessRepo) SetMenuRoles(ctx context.Context, id uint, ids []uint) error { - return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { - return err - } - links := make([]authorityMenuPO, 0, len(ids)) - for _, aid := range ids { - links = append(links, authorityMenuPO{SysAuthorityAuthorityID: aid, SysBaseMenuID: id}) - } - if len(links) > 0 { - return tx.Create(&links).Error - } - return nil - }) -} -func (r *accessRepo) ReplaceMenuButtons(ctx context.Context, menuID uint, buttons []*biz.MenuButton) error { - return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - var oldIDs []uint - if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", menuID).Pluck("id", &oldIDs).Error; err != nil { - return err - } - if len(oldIDs) > 0 { - if err := tx.Where("sys_base_menu_btn_id IN ?", oldIDs).Delete(&authorityButtonPO{}).Error; err != nil { - return err - } - } - if err := tx.Where("sys_base_menu_id = ?", menuID).Delete(&menuButtonPO{}).Error; err != nil { - return err - } - pos := make([]menuButtonPO, 0, len(buttons)) - for _, button := range buttons { - pos = append(pos, menuButtonPO{Name: button.Name, Description: button.Description, MenuID: menuID}) - } - if len(pos) > 0 { - return tx.Create(&pos).Error - } - return nil - }) -} - func apiFromPO(po apiPO) *biz.API { return &biz.API{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Path: po.Path, Description: po.Description, APIGroup: po.APIGroup, Method: po.Method} } @@ -874,7 +634,7 @@ func (r *accessRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, func deptFromPO(po departmentPO) *biz.Department { return &biz.Department{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID, Ancestors: po.Ancestors, Sort: po.Sort, LeaderID: po.LeaderID, Status: po.Status} } -func (r *accessRepo) CreateDepartment(ctx context.Context, v *biz.Department) error { +func (r *organizationRepo) CreateDepartment(ctx context.Context, v *biz.Department) error { v.Ancestors = "0" if v.ParentID != 0 { var parent departmentPO @@ -885,7 +645,7 @@ func (r *accessRepo) CreateDepartment(ctx context.Context, v *biz.Department) er } return r.data.gormDB.WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error } -func (r *accessRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error { +func (r *organizationRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error { if v.ParentID == v.ID { return errors.New("父部门不能是自己") } @@ -899,7 +659,7 @@ func (r *accessRepo) UpdateDepartment(ctx context.Context, v *biz.Department) er } return r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error } -func (r *accessRepo) DeleteDepartment(ctx context.Context, id uint) error { +func (r *organizationRepo) DeleteDepartment(ctx context.Context, id uint) error { if id == 0 { return errors.New("部门ID不能为空") } @@ -924,14 +684,14 @@ func (r *accessRepo) DeleteDepartment(ctx context.Context, id uint) error { } return r.data.gormDB.WithContext(ctx).Delete(&departmentPO{}, id).Error } -func (r *accessRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) { +func (r *organizationRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) { var po departmentPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return deptFromPO(po), nil } -func (r *accessRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) { +func (r *organizationRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) { var pos []departmentPO db := r.data.gormDB.WithContext(ctx).Order("sort,id") if name != "" { @@ -955,15 +715,15 @@ func (r *accessRepo) ListDepartments(ctx context.Context, name string) ([]*biz.D } return roots, nil } -func (r *accessRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) { +func (r *organizationRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", &ids).Error return ids, err } -func (r *accessRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error { +func (r *organizationRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error { return r.replaceUserDepartments(ctx, id, ids) } -func (r *accessRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error { +func (r *organizationRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error { if id == 0 { return errors.New("部门ID不能为空") } @@ -1007,7 +767,7 @@ func (r *accessRepo) replaceUserDepartments(ctx context.Context, id uint, ids [] return nil }) } -func (r *accessRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error { +func (r *organizationRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var user userPO if err := tx.First(&user, uid).Error; err != nil { @@ -1047,13 +807,13 @@ func (r *accessRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uin func posFromPO(po positionPO) *biz.Position { return &biz.Position{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Code: po.Code, Sort: po.Sort, Status: po.Status, Remark: po.Remark} } -func (r *accessRepo) CreatePosition(ctx context.Context, v *biz.Position) error { +func (r *organizationRepo) CreatePosition(ctx context.Context, v *biz.Position) error { return r.data.gormDB.WithContext(ctx).Create(&positionPO{Name: v.Name, Code: v.Code, Sort: v.Sort, Status: v.Status, Remark: v.Remark}).Error } -func (r *accessRepo) UpdatePosition(ctx context.Context, v *biz.Position) error { +func (r *organizationRepo) UpdatePosition(ctx context.Context, v *biz.Position) error { return r.data.gormDB.WithContext(ctx).Model(&positionPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}).Error } -func (r *accessRepo) DeletePosition(ctx context.Context, id uint) error { +func (r *organizationRepo) DeletePosition(ctx context.Context, id uint) error { if id == 0 { return errors.New("岗位ID不能为空") } @@ -1066,14 +826,14 @@ func (r *accessRepo) DeletePosition(ctx context.Context, id uint) error { } return r.data.gormDB.WithContext(ctx).Delete(&positionPO{}, id).Error } -func (r *accessRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) { +func (r *organizationRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) { var po positionPO if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return posFromPO(po), nil } -func (r *accessRepo) ListPositions(ctx context.Context, page, size int, q *biz.Position) ([]*biz.Position, int64, error) { +func (r *organizationRepo) ListPositions(ctx context.Context, page, size int, q *biz.Position) ([]*biz.Position, int64, error) { if page < 1 { page = 1 } @@ -1103,12 +863,12 @@ func (r *accessRepo) ListPositions(ctx context.Context, page, size int, q *biz.P } return out, total, nil } -func (r *accessRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) { +func (r *organizationRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.gormDB.WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Pluck("sys_user_id", &ids).Error return ids, err } -func (r *accessRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) error { +func (r *organizationRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("sys_position_id = ?", id).Delete(&userPositionPO{}).Error; err != nil { return err @@ -1123,7 +883,7 @@ func (r *accessRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) return nil }) } -func (r *accessRepo) SetUserPositions(ctx context.Context, uid uint, ids []uint) error { +func (r *organizationRepo) SetUserPositions(ctx context.Context, uid uint, ids []uint) error { return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("sys_user_id = ?", uid).Delete(&userPositionPO{}).Error; err != nil { return err diff --git a/internal/data/data.go b/internal/data/data.go index 18488cb..e1048cd 100644 --- a/internal/data/data.go +++ b/internal/data/data.go @@ -13,7 +13,7 @@ import ( "kra/internal/conf" ) -var ProviderSet = wire.NewSet(NewData, NewSystemRepo, NewAccessRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) +var ProviderSet = wire.NewSet(NewData, NewSystemRepo, NewAccessRepo, NewMenuRepo, NewOrganizationRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) type Data struct { mu sync.RWMutex diff --git a/internal/data/menu.go b/internal/data/menu.go new file mode 100644 index 0000000..09386a2 --- /dev/null +++ b/internal/data/menu.go @@ -0,0 +1,275 @@ +package data + +import ( + "context" + "errors" + + "kra/internal/biz" + + "gorm.io/gorm" +) + +type menuRepo struct{ data *Data } + +func NewMenuRepo(data *Data) biz.MenuRepo { return &menuRepo{data: data} } + +func menuFromPO(po menuPO) *biz.Menu { + return &biz.Menu{ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType} +} + +func menuToPO(v *biz.Menu) menuPO { + return menuPO{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.ActiveName, KeepAlive: v.KeepAlive, DefaultMenu: v.DefaultMenu, Title: v.Title, Icon: v.Icon, CloseTab: v.CloseTab, TransitionType: v.TransitionType} +} + +func replaceMenuRelations(tx *gorm.DB, menu *biz.Menu) error { + var oldButtonIDs []uint + if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", menu.ID).Pluck("id", &oldButtonIDs).Error; err != nil { + return err + } + if len(oldButtonIDs) > 0 { + if err := tx.Where("sys_base_menu_btn_id IN ?", oldButtonIDs).Delete(&authorityButtonPO{}).Error; err != nil { + return err + } + } + if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuButtonPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", menu.ID).Delete(&menuParameterPO{}).Error; err != nil { + return err + } + buttons := make([]menuButtonPO, 0, len(menu.Buttons)) + for _, button := range menu.Buttons { + buttons = append(buttons, menuButtonPO{Name: button.Name, Description: button.Description, MenuID: menu.ID}) + } + if len(buttons) > 0 { + if err := tx.Create(&buttons).Error; err != nil { + return err + } + } + parameters := make([]menuParameterPO, 0, len(menu.Parameters)) + for _, parameter := range menu.Parameters { + parameters = append(parameters, menuParameterPO{MenuID: menu.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + if len(parameters) > 0 { + return tx.Create(¶meters).Error + } + return nil +} + +func (r *menuRepo) Create(ctx context.Context, v *biz.Menu) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var count int64 + if err := tx.Model(&menuPO{}).Where("name = ?", v.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("存在重复name,请修改name") + } + if v.ParentID != 0 { + var parent menuPO + if err := tx.First(&parent, v.ParentID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("父菜单不存在") + } + return err + } + if err := tx.Model(&menuPO{}).Where("parent_id = ?", v.ParentID).Count(&count).Error; err != nil { + return err + } + if count == 0 { + if err := tx.Model(&authorityPO{}).Where("default_router = ?", parent.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("父菜单已被其他角色的首页占用,请先释放父菜单的首页权限") + } + if err := tx.Where("sys_base_menu_id = ?", v.ParentID).Delete(&authorityMenuPO{}).Error; err != nil { + return err + } + } + } + po := menuToPO(v) + if err := tx.Create(&po).Error; err != nil { + return err + } + v.ID = po.ID + return replaceMenuRelations(tx, v) + }) +} + +func (r *menuRepo) Update(ctx context.Context, v *biz.Menu) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var old menuPO + if err := tx.First(&old, v.ID).Error; err != nil { + return err + } + var count int64 + if old.Name != v.Name { + if err := tx.Model(&menuPO{}).Where("id <> ? AND name = ?", v.ID, v.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("存在相同name修改失败") + } + } + if err := replaceMenuRelations(tx, v); err != nil { + return err + } + values := map[string]any{"parent_id": v.ParentID, "path": v.Path, "name": v.Name, "hidden": v.Hidden, "component": v.Component, "sort": v.Sort, "active_name": v.ActiveName, "keep_alive": v.KeepAlive, "default_menu": v.DefaultMenu, "title": v.Title, "icon": v.Icon, "close_tab": v.CloseTab, "transition_type": v.TransitionType} + return tx.Model(&old).Updates(values).Error + }) +} + +func (r *menuRepo) Delete(ctx context.Context, id uint) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var count int64 + if err := tx.Model(&menuPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("此菜单存在子菜单不可删除") + } + var menu menuPO + if err := tx.First(&menu, id).Error; err != nil { + return errors.New("记录不存在") + } + if err := tx.Model(&authorityPO{}).Where("default_router = ?", menu.Name).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return errors.New("此菜单有角色正在作为首页,不可删除") + } + var buttonIDs []uint + if err := tx.Model(&menuButtonPO{}).Where("sys_base_menu_id = ?", id).Pluck("id", &buttonIDs).Error; err != nil { + return err + } + if err := tx.Where("sys_menu_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuButtonPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", id).Delete(&menuParameterPO{}).Error; err != nil { + return err + } + if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { + return err + } + return tx.Delete(&menuPO{}, id).Error + }) +} + +func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error { + var parameters []menuParameterPO + if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Order("id").Find(¶meters).Error; err != nil { + return err + } + menu.Parameters = make([]*biz.MenuParameter, 0, len(parameters)) + for _, parameter := range parameters { + menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + var buttons []menuButtonPO + if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Order("id").Find(&buttons).Error; err != nil { + return err + } + menu.Buttons = make([]*biz.MenuButton, 0, len(buttons)) + for _, button := range buttons { + menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: button.MenuID}) + } + return nil +} + +func (r *menuRepo) Find(ctx context.Context, id uint) (*biz.Menu, error) { + var po menuPO + if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil { + return nil, err + } + menu := menuFromPO(po) + if err := r.loadRelations(ctx, menu); err != nil { + return nil, err + } + return menu, nil +} + +func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) { + var pos []menuPO + if err := r.data.gormDB.WithContext(ctx).Order("sort,id").Find(&pos).Error; err != nil { + return nil, err + } + out := make([]*biz.Menu, 0, len(pos)) + for _, po := range pos { + out = append(out, menuFromPO(po)) + } + if len(out) == 0 { + return out, nil + } + byID := make(map[uint]*biz.Menu, len(out)) + ids := make([]uint, 0, len(out)) + for _, menu := range out { + byID[menu.ID] = menu + ids = append(ids, menu.ID) + menu.Parameters = []*biz.MenuParameter{} + menu.Buttons = []*biz.MenuButton{} + } + var parameters []menuParameterPO + if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Order("id").Find(¶meters).Error; err != nil { + return nil, err + } + for _, parameter := range parameters { + menu := byID[parameter.MenuID] + menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + var buttons []menuButtonPO + if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Order("id").Find(&buttons).Error; err != nil { + return nil, err + } + for _, button := range buttons { + menu := byID[button.MenuID] + menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: button.MenuID}) + } + return out, nil +} + +func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { + return err + } + links := make([]authorityMenuPO, 0, len(ids)) + for _, menuID := range ids { + links = append(links, authorityMenuPO{SysAuthorityAuthorityID: id, SysBaseMenuID: menuID}) + } + if len(links) > 0 { + return tx.Create(&links).Error + } + return nil + }) +} + +func (r *menuRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) { + var ids []uint + err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error + return ids, err +} + +func (r *menuRepo) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) { + var ids []uint + err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_base_menu_id = ?", id).Pluck("sys_authority_authority_id", &ids).Error + return ids, err +} + +func (r *menuRepo) SetMenuRoles(ctx context.Context, id uint, ids []uint) error { + return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := tx.Where("sys_base_menu_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil { + return err + } + links := make([]authorityMenuPO, 0, len(ids)) + for _, authorityID := range ids { + links = append(links, authorityMenuPO{SysAuthorityAuthorityID: authorityID, SysBaseMenuID: id}) + } + if len(links) > 0 { + return tx.Create(&links).Error + } + return nil + }) +} diff --git a/internal/server/access_routes.go b/internal/server/access_routes.go deleted file mode 100644 index 3c10b70..0000000 --- a/internal/server/access_routes.go +++ /dev/null @@ -1,44 +0,0 @@ -package server - -import ( - "net/http" - - "kra/internal/biz" - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -func registerAccessRoutes(group *gin.RouterGroup, engine *gin.Engine, svc *service.AccessService) { - registerAuthorityRoutes(group, svc) - registerMenuRoutes(group, svc) - registerAPIRoutes(group, engine, svc) - registerPolicyRoutes(group, svc) - registerDepartmentRoutes(group, svc) - registerPositionRoutes(group, svc) -} - -func accessControl(svc *service.AccessService, audit *service.AuditService) gin.HandlerFunc { - return func(c *gin.Context) { - claims := currentClaims(c) - if claims == nil { - noAuth(c, "未登录或非法访问") - return - } - path := c.Request.URL.Path - allowed, err := svc.Authorize(c.Request.Context(), claims.AuthorityID, path, c.Request.Method) - if err != nil || !allowed { - requestID, _ := c.Get("request_id") - _ = audit.RecordDataAccess(c.Request.Context(), &biz.DataAccessLog{EventType: "blocked_access", Operation: c.Request.Method, UserID: claims.ID, AuthorityID: claims.AuthorityID, RequestID: toString(requestID), Method: c.Request.Method, Path: c.Request.URL.Path, Detail: "Casbin policy denied the request"}) - c.AbortWithStatusJSON(http.StatusForbidden, response{Code: codeError, Data: nil, Msg: "权限不足"}) - return - } - scope, err := svc.ResolveDataScope(c.Request.Context(), claims.AuthorityID, claims.ID) - if err != nil { - fail(c, "数据权限加载失败") - return - } - c.Request = c.Request.WithContext(biz.NewDataScopeContext(c.Request.Context(), scope)) - c.Next() - } -} diff --git a/internal/server/announcement_routes.go b/internal/server/announcement_routes.go deleted file mode 100644 index ee006d5..0000000 --- a/internal/server/announcement_routes.go +++ /dev/null @@ -1,127 +0,0 @@ -package server - -import ( - "encoding/json" - "strconv" - "time" - - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -type announcementRequest struct { - ID uint `json:"ID"` - Title string `json:"title"` - Content string `json:"content"` - UserID *uint `json:"userID"` - Attachments json.RawMessage `json:"attachments"` -} - -func announcementInput(req announcementRequest) service.AnnouncementInput { - return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments} -} - -func registerAnnouncementRoutes(private, public *gin.RouterGroup, svc *service.AnnouncementService) { - privateInfo := private.Group("/info") - privateInfo.POST("/createInfo", func(c *gin.Context) { - var req announcementRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.Create(c.Request.Context(), announcementInput(req)); err != nil { - fail(c, "创建失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{}, "创建成功") - }) - privateInfo.DELETE("/deleteInfo", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - if err := svc.Delete(c.Request.Context(), uint(id)); err != nil { - fail(c, "删除失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{}, "删除成功") - }) - privateInfo.DELETE("/deleteInfoByIds", func(c *gin.Context) { - values := c.QueryArray("IDs[]") - if len(values) == 0 { - values = c.QueryArray("IDs") - } - ids := make([]uint, 0, len(values)) - for _, value := range values { - id, err := strconv.ParseUint(value, 10, 64) - if err != nil || id == 0 { - fail(c, "参数错误") - return - } - ids = append(ids, uint(id)) - } - if err := svc.DeleteByIDs(c.Request.Context(), ids); err != nil { - fail(c, "批量删除失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{}, "批量删除成功") - }) - privateInfo.PUT("/updateInfo", func(c *gin.Context) { - var req announcementRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.Update(c.Request.Context(), announcementInput(req)); err != nil { - fail(c, "更新失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{}, "更新成功") - }) - privateInfo.GET("/findInfo", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) - item, err := svc.Find(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "查询失败:"+err.Error()) - return - } - okWithData(c, item) - }) - privateInfo.GET("/getInfoList", func(c *gin.Context) { - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) - start, startErr := parseAnnouncementTime(c.Query("startCreatedAt")) - end, endErr := parseAnnouncementTime(c.Query("endCreatedAt")) - if startErr != nil || endErr != nil || (start == nil) != (end == nil) { - fail(c, "创建日期范围不合法") - return - } - items, total, err := svc.List(c.Request.Context(), page, pageSize, start, end) - if err != nil { - fail(c, "获取失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功") - }) - publicInfo := public.Group("/info") - publicInfo.GET("/getInfoDataSource", func(c *gin.Context) { - users, err := svc.UserOptions(c.Request.Context()) - if err != nil { - fail(c, "查询失败:"+err.Error()) - return - } - okWithData(c, gin.H{"userID": users}) - }) - publicInfo.GET("/getInfoPublic", func(c *gin.Context) { - writeResult(c, codeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功") - }) -} - -func parseAnnouncementTime(value string) (*time.Time, error) { - if value == "" { - return nil, nil - } - parsed, err := time.Parse(time.RFC3339, value) - if err != nil { - return nil, err - } - return &parsed, nil -} diff --git a/internal/server/api_routes.go b/internal/server/api_routes.go deleted file mode 100644 index bfac2c1..0000000 --- a/internal/server/api_routes.go +++ /dev/null @@ -1,209 +0,0 @@ -package server - -import ( - "strconv" - - "kra/internal/biz" - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -func registerAPIRoutes(group *gin.RouterGroup, engine *gin.Engine, svc *service.AccessService) { - router := group.Group("/api") - router.POST("/getApiList", func(c *gin.Context) { - var req struct { - Page int `json:"page"` - PageSize int `json:"pageSize"` - Path string `json:"path"` - Description string `json:"description"` - APIGroup string `json:"apiGroup"` - Method string `json:"method"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - items, total, err := svc.APIs(c.Request.Context(), req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method}) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") - }) - router.POST("/getAllApis", func(c *gin.Context) { - items, _, err := svc.APIs(c.Request.Context(), 1, 10000, nil) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, gin.H{"apis": items}, "获取成功") - }) - type apiRequest struct { - ID uint `json:"ID"` - Path string `json:"path"` - Description string `json:"description"` - APIGroup string `json:"apiGroup"` - Method string `json:"method"` - } - router.POST("/createApi", func(c *gin.Context) { - var req apiRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.CreateAPI(c.Request.Context(), &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method}); err != nil { - fail(c, "创建失败") - return - } - ok(c) - }) - router.POST("/updateApi", func(c *gin.Context) { - var req apiRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.UpdateAPI(c.Request.Context(), &biz.API{ID: req.ID, Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method}); err != nil { - fail(c, "更新失败") - return - } - ok(c) - }) - router.POST("/deleteApi", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.DeleteAPIs(c.Request.Context(), []uint{req.ID}); err != nil { - fail(c, "删除失败") - return - } - ok(c) - }) - router.DELETE("/deleteApisByIds", func(c *gin.Context) { - var req struct { - IDs []uint `json:"ids"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.DeleteAPIs(c.Request.Context(), req.IDs); err != nil { - fail(c, "删除失败") - return - } - ok(c) - }) - router.POST("/getApiById", func(c *gin.Context) { - var req struct { - ID uint `json:"id"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - item, err := svc.API(c.Request.Context(), req.ID) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, gin.H{"api": item}, "获取成功") - }) - router.GET("/getApiGroups", func(c *gin.Context) { - items, _, err := svc.APIs(c.Request.Context(), 1, 10000, nil) - if err != nil { - fail(c, "获取失败") - return - } - seen := map[string]bool{} - groups := []string{} - for _, item := range items { - value, _ := item["apiGroup"].(string) - if value != "" && !seen[value] { - seen[value] = true - groups = append(groups, value) - } - } - writeResult(c, codeSuccess, gin.H{"groups": groups}, "获取成功") - }) - router.GET("/getApiRoles", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("apiId"), 10, 64) - ids, err := svc.APIRoleIDs(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, ids, "获取成功") - }) - router.POST("/setApiRoles", func(c *gin.Context) { - var req struct { - APIID uint `json:"apiId"` - AuthorityIDs []uint `json:"authorityIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetAPIRoles(c.Request.Context(), req.APIID, req.AuthorityIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) - router.GET("/syncApi", func(c *gin.Context) { - routes := engine.Routes() - items := make([]*biz.API, 0, len(routes)) - for _, route := range routes { - items = append(items, &biz.API{Path: route.Path, Method: route.Method}) - } - result, err := svc.SyncAPIs(c.Request.Context(), items) - if err != nil { - fail(c, "同步检查失败") - return - } - writeResult(c, codeSuccess, result, "获取成功") - }) - router.POST("/ignoreApi", func(c *gin.Context) { - var req struct { - Path string `json:"path"` - Method string `json:"method"` - Flag bool `json:"flag"` - } - if c.ShouldBindJSON(&req) != nil || req.Path == "" || req.Method == "" { - fail(c, "参数错误") - return - } - if err := svc.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil { - fail(c, "忽略设置失败") - return - } - ok(c) - }) - router.POST("/enterSyncApi", func(c *gin.Context) { - var req struct { - NewAPIs []apiRequest `json:"newApis"` - DeleteAPIs []apiRequest `json:"deleteApis"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - toDomain := func(values []apiRequest) []*biz.API { - items := make([]*biz.API, 0, len(values)) - for _, value := range values { - items = append(items, &biz.API{ID: value.ID, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method}) - } - return items - } - if err := svc.ApplyAPISync(c.Request.Context(), toDomain(req.NewAPIs), toDomain(req.DeleteAPIs)); err != nil { - fail(c, "同步失败") - return - } - ok(c) - }) - router.GET("/freshCasbin", func(c *gin.Context) { ok(c) }) -} diff --git a/internal/server/authority_routes.go b/internal/server/authority_routes.go deleted file mode 100644 index 31edf98..0000000 --- a/internal/server/authority_routes.go +++ /dev/null @@ -1,134 +0,0 @@ -package server - -import ( - "strconv" - - "kra/internal/biz" - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -func registerAuthorityRoutes(group *gin.RouterGroup, svc *service.AccessService) { - router := group.Group("/authority") - router.POST("/getAuthorityList", func(c *gin.Context) { - items, err := svc.Authorities(c.Request.Context()) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, items, "获取成功") - }) - type authorityRequest struct { - AuthorityID uint `json:"authorityId"` - AuthorityName string `json:"authorityName"` - ParentID *uint `json:"parentId"` - DataScope int `json:"dataScope"` - DefaultRouter string `json:"defaultRouter"` - } - router.POST("/createAuthority", func(c *gin.Context) { - var req authorityRequest - if c.ShouldBindJSON(&req) != nil || req.AuthorityID == 0 { - fail(c, "参数错误") - return - } - value := &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} - if err := svc.CreateAuthority(c.Request.Context(), value); err != nil { - fail(c, "创建失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{"authority": gin.H{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}}, "创建成功") - }) - router.POST("/copyAuthority", func(c *gin.Context) { - var req struct { - Authority authorityRequest `json:"authority"` - OldAuthorityID uint `json:"oldAuthorityId"` - } - if c.ShouldBindJSON(&req) != nil || req.Authority.AuthorityID == 0 || req.OldAuthorityID == 0 { - fail(c, "参数错误") - return - } - value := &biz.Authority{AuthorityID: req.Authority.AuthorityID, AuthorityName: req.Authority.AuthorityName, ParentID: req.Authority.ParentID, DataScope: req.Authority.DataScope, DefaultRouter: req.Authority.DefaultRouter} - if err := svc.CopyAuthority(c.Request.Context(), req.OldAuthorityID, value); err != nil { - fail(c, "拷贝失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{"authority": gin.H{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}}, "拷贝成功") - }) - router.PUT("/updateAuthority", func(c *gin.Context) { - var req authorityRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - value := &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} - if err := svc.UpdateAuthority(c.Request.Context(), value); err != nil { - fail(c, "更新失败") - return - } - writeResult(c, codeSuccess, gin.H{"authority": gin.H{"authorityId": value.AuthorityID, "authorityName": value.AuthorityName, "parentId": value.ParentID, "dataScope": value.DataScope, "defaultRouter": value.DefaultRouter}}, "更新成功") - }) - router.POST("/deleteAuthority", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil { - fail(c, err.Error()) - return - } - ok(c) - }) - router.POST("/setRoleUsers", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - UserIDs []uint `json:"userIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) - router.GET("/getUsersByAuthority", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64) - ids, err := svc.AuthorityUserIDs(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, ids, "获取成功") - }) - router.POST("/setDataScope", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - DataScope int `json:"dataScope"` - DepartmentIDs []uint `json:"deptIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) - router.GET("/getDataScopeDepts", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64) - ids, err := svc.DataScopeDepartmentIDs(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, ids, "获取成功") - }) -} diff --git a/internal/server/email_routes.go b/internal/server/email_routes.go index 5349452..38775c4 100644 --- a/internal/server/email_routes.go +++ b/internal/server/email_routes.go @@ -13,33 +13,6 @@ import ( "github.com/gin-gonic/gin" ) -func registerEmailRoutes(group *gin.RouterGroup, svc *service.EmailService) { - email := group.Group("/email") - email.POST("/emailTest", func(c *gin.Context) { - if err := svc.Test(c.Request.Context()); err != nil { - fail(c, "发送失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{}, "发送成功") - }) - email.POST("/sendEmail", func(c *gin.Context) { - var req struct { - To string `json:"to"` - Subject string `json:"subject"` - Body string `json:"body"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil { - fail(c, "发送失败:"+err.Error()) - return - } - writeResult(c, codeSuccess, gin.H{}, "发送成功") - }) -} - func emailErrorAlert(svc *service.EmailService, logger *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { if strings.Contains(c.Request.URL.Path, "/email/") { diff --git a/internal/server/gin.go b/internal/server/gin.go index c0ed671..2969222 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -13,11 +13,14 @@ import ( "kra/internal/biz" "kra/internal/conf" + "kra/internal/server/handler" + servermiddleware "kra/internal/server/middleware" + serverrouter "kra/internal/server/router" "kra/internal/service" + "kra/internal/worker" "kra/pkg/adminauth" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/mojocn/base64Captcha" ) @@ -30,10 +33,10 @@ type GinServer struct { logger *slog.Logger } -func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.SystemService, access *service.AccessService, settings *service.SettingsService, versions *service.VersionService, exports *service.ExportService, audit *service.AuditService, tasks *service.TaskService, media *service.MediaService, announcements *service.AnnouncementService, emails *service.EmailService, scheduler *TaskScheduler, logger *slog.Logger) *GinServer { +func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *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, settings *service.SettingsService, versions *service.VersionService, exports *service.ExportService, audit *service.AuditService, media *service.MediaService, emails *service.EmailService, scheduler *worker.TaskScheduler, logger *slog.Logger) *GinServer { gin.SetMode(gin.ReleaseMode) engine := gin.New() - engine.Use(ginRequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit)) + engine.Use(servermiddleware.RequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit)) prefix := "" if config != nil { prefix = strings.TrimSuffix(config.RouterPrefix, "/") @@ -44,18 +47,22 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System }) registerPublicRoutes(public, engine, config, svc, settings, access, audit, scheduler) private := engine.Group(prefix) - private.Use(jwtAuth(config, settings), mustChangePasswordGuard(), accessControl(access, audit)) + private.Use(servermiddleware.Auth(config, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(access, audit)) registerPrivateRoutes(private, svc, settings) - registerAccessRoutes(private, engine, access) + serverrouter.RegisterAuthority(private, authority) + serverrouter.RegisterMenu(private, menu) + serverrouter.RegisterAPI(private, engine, api) + serverrouter.RegisterPermission(private, permission) + serverrouter.RegisterOrganization(private, organization) registerSettingsRoutes(private, public, settings) registerSystemConfigRoutes(private, config, svc, settings, scheduler) registerVersionRoutes(private, versions) registerExportRoutes(private, public, svc, exports) registerAuditRoutes(private, public, audit) - registerTaskRoutes(private, tasks, scheduler) + serverrouter.RegisterTask(private, task) registerMediaRoutes(private, media) - registerAnnouncementRoutes(private, public, announcements) - registerEmailRoutes(private, emails) + serverrouter.RegisterAnnouncement(private, public, announcement) + serverrouter.RegisterEmail(private, email) engine.NoRoute(func(c *gin.Context) { if serveLocalStorage(c, config) { @@ -100,74 +107,9 @@ func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool { return true } -func jwtAuth(config *conf.AdminBackend, settings *service.SettingsService) gin.HandlerFunc { - return func(c *gin.Context) { - token := c.GetHeader("x-token") - if token == "" { - token, _ = c.Cookie("x-token") - } - secret := "" - if config != nil && config.Jwt != nil { - secret = config.Jwt.SigningKey - } - claims, err := adminauth.Parse(token, secret) - if err != nil { - noAuth(c, "未登录或非法访问") - return - } - if disabled, checkErr := settings.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled { - noAuth(c, "登录状态已失效") - return - } - if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second { - expires, buffer := 7*24*time.Hour, time.Duration(claims.BufferTime)*time.Second - issuer := claims.Issuer - if config != nil && config.Jwt != nil { - if config.Jwt.ExpiresTime != nil { - expires = config.Jwt.ExpiresTime.AsDuration() - } - if config.Jwt.BufferTime != nil { - buffer = config.Jwt.BufferTime.AsDuration() - } - if config.Jwt.Issuer != "" { - issuer = config.Jwt.Issuer - } - } - newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd) - if refreshErr == nil { - c.Header("new-token", newToken) - c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) - http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: newToken, Path: "/", MaxAge: int(expires.Seconds()), HttpOnly: true, SameSite: http.SameSiteStrictMode}) - } - } - c.Set("admin_claims", claims) - c.Next() - } -} +func currentClaims(c *gin.Context) *adminauth.Claims { return servermiddleware.Claims(c) } -func currentClaims(c *gin.Context) *adminauth.Claims { - claims, _ := c.Get("admin_claims") - result, _ := claims.(*adminauth.Claims) - return result -} - -func mustChangePasswordGuard() gin.HandlerFunc { - return func(c *gin.Context) { - claims := currentClaims(c) - if claims == nil || !claims.MustChangePwd { - c.Next() - return - } - path := strings.TrimSuffix(c.Request.URL.Path, "/") - if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") { - c.Next() - return - } - c.AbortWithStatusJSON(http.StatusConflict, response{Code: codePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "请先修改初始密码"}) - } -} - -func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService, scheduler *TaskScheduler) { +func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService, scheduler *worker.TaskScheduler) { expiration := 3 * time.Minute keyLong, width, height := 6, 240, 80 if config != nil && config.Captcha != nil { @@ -644,15 +586,3 @@ func (s *GinServer) Stop(ctx context.Context) error { s.logger.InfoContext(ctx, "Gin HTTP server stopping") return s.server.Shutdown(ctx) } - -func ginRequestMeta() gin.HandlerFunc { - return func(c *gin.Context) { - requestID := c.GetHeader("X-Request-Id") - if requestID == "" { - requestID = uuid.NewString() - } - c.Header("X-Request-Id", requestID) - c.Set("request_id", requestID) - c.Next() - } -} diff --git a/internal/server/handler/announcement.go b/internal/server/handler/announcement.go new file mode 100644 index 0000000..16b7556 --- /dev/null +++ b/internal/server/handler/announcement.go @@ -0,0 +1,129 @@ +package handler + +import ( + "strconv" + "time" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type Announcement struct{ service *service.AnnouncementService } + +func NewAnnouncement(service *service.AnnouncementService) *Announcement { + return &Announcement{service: service} +} + +func announcementInput(req dto.AnnouncementRequest) service.AnnouncementInput { + return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments} +} + +func (h *Announcement) Create(c *gin.Context) { + var req dto.AnnouncementRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Create(c.Request.Context(), announcementInput(req)); err != nil { + httpx.Fail(c, "创建失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") +} + +func (h *Announcement) Delete(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) + if err := h.service.Delete(c.Request.Context(), uint(id)); err != nil { + httpx.Fail(c, "删除失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") +} + +func (h *Announcement) DeleteByIDs(c *gin.Context) { + values := c.QueryArray("IDs[]") + if len(values) == 0 { + values = c.QueryArray("IDs") + } + ids := make([]uint, 0, len(values)) + for _, value := range values { + id, err := strconv.ParseUint(value, 10, 64) + if err != nil || id == 0 { + httpx.Fail(c, "参数错误") + return + } + ids = append(ids, uint(id)) + } + if err := h.service.DeleteByIDs(c.Request.Context(), ids); err != nil { + httpx.Fail(c, "批量删除失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") +} + +func (h *Announcement) Update(c *gin.Context) { + var req dto.AnnouncementRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Update(c.Request.Context(), announcementInput(req)); err != nil { + httpx.Fail(c, "更新失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") +} + +func (h *Announcement) Find(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) + item, err := h.service.Find(c.Request.Context(), uint(id)) + if err != nil { + httpx.Fail(c, "查询失败:"+err.Error()) + return + } + httpx.OKWithData(c, item) +} + +func (h *Announcement) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) + start, startErr := parseTime(c.Query("startCreatedAt")) + end, endErr := parseTime(c.Query("endCreatedAt")) + if startErr != nil || endErr != nil || (start == nil) != (end == nil) { + httpx.Fail(c, "创建日期范围不合法") + return + } + items, total, err := h.service.List(c.Request.Context(), page, pageSize, start, end) + if err != nil { + httpx.Fail(c, "获取失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功") +} + +func (h *Announcement) DataSource(c *gin.Context) { + users, err := h.service.UserOptions(c.Request.Context()) + if err != nil { + httpx.Fail(c, "查询失败:"+err.Error()) + return + } + httpx.OKWithData(c, gin.H{"userID": users}) +} + +func (h *Announcement) Public(c *gin.Context) { + httpx.Write(c, httpx.CodeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功") +} + +func parseTime(value string) (*time.Time, error) { + if value == "" { + return nil, nil + } + parsed, err := time.Parse(time.RFC3339, value) + if err != nil { + return nil, err + } + return &parsed, nil +} diff --git a/internal/server/handler/api.go b/internal/server/handler/api.go new file mode 100644 index 0000000..0aa9710 --- /dev/null +++ b/internal/server/handler/api.go @@ -0,0 +1,201 @@ +package handler + +import ( + "strconv" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type API struct{ service *service.AccessService } + +func NewAPI(service *service.AccessService) *API { return &API{service: service} } + +func (h *API) List(c *gin.Context) { + var req dto.APIListRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + items, total, err := h.service.ListAPI(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") +} +func (h *API) All(c *gin.Context) { + items, err := h.service.AllAPI(c.Request.Context()) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"apis": items}, "获取成功") +} +func (h *API) Create(c *gin.Context) { + var req dto.APIRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.CreateAPIRequest(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "创建失败") + return + } + httpx.OK(c) +} +func (h *API) Update(c *gin.Context) { + var req dto.APIRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.UpdateAPIRequest(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "更新失败") + return + } + httpx.OK(c) +} +func (h *API) Delete(c *gin.Context) { + var req dto.DeleteAPIRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.DeleteAPIs(c.Request.Context(), []uint{req.ID}); err != nil { + httpx.Fail(c, "删除失败") + return + } + httpx.OK(c) +} +func (h *API) DeleteByIDs(c *gin.Context) { + var req dto.DeleteAPIsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.DeleteAPIs(c.Request.Context(), req.IDs); err != nil { + httpx.Fail(c, "删除失败") + return + } + httpx.OK(c) +} +func (h *API) Find(c *gin.Context) { + var req dto.GetAPIRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + item, err := h.service.FindAPIResponse(c.Request.Context(), req.ID) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"api": item}, "获取成功") +} +func (h *API) Groups(c *gin.Context) { + items, err := h.service.AllAPI(c.Request.Context()) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + seen := map[string]bool{} + groups := []string{} + for _, item := range items { + if item.APIGroup != "" && !seen[item.APIGroup] { + seen[item.APIGroup] = true + groups = append(groups, item.APIGroup) + } + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"groups": groups}, "获取成功") +} +func (h *API) Roles(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("apiId"), 10, 64) + ids, err := h.service.APIRoleIDs(c.Request.Context(), uint(id)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") +} +func (h *API) SetRoles(c *gin.Context) { + var req dto.SetAPIRolesRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetAPIRoles(c.Request.Context(), req.APIID, req.AuthorityIDs); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} +func (h *API) Sync(engine *gin.Engine) gin.HandlerFunc { + return func(c *gin.Context) { + routes := engine.Routes() + values := make([]dto.APIRequest, 0, len(routes)) + for _, route := range routes { + values = append(values, dto.APIRequest{Path: route.Path, Method: route.Method}) + } + result, err := h.service.SyncAPIResponses(c.Request.Context(), values) + if err != nil { + httpx.Fail(c, "同步检查失败") + return + } + httpx.Write(c, httpx.CodeSuccess, result, "获取成功") + } +} +func (h *API) Ignore(c *gin.Context) { + var req dto.IgnoreAPIRequest + if c.ShouldBindJSON(&req) != nil || req.Path == "" || req.Method == "" { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil { + httpx.Fail(c, "忽略设置失败") + return + } + httpx.OK(c) +} +func (h *API) ApplySync(c *gin.Context) { + var req dto.ApplyAPISyncRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.ApplyAPISyncRequest(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "同步失败") + return + } + httpx.OK(c) +} +func (h *API) FreshCasbin(c *gin.Context) { httpx.OK(c) } + +func (h *API) SetPolicyPaths(c *gin.Context) { + var req dto.SetPolicyPathsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetPolicyPathsRequest(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "更新失败") + return + } + httpx.OK(c) +} +func (h *API) PolicyPaths(c *gin.Context) { + var req dto.GetPolicyPathsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + paths, err := h.service.PolicyPathResponses(c.Request.Context(), req.AuthorityID) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"paths": paths}, "获取成功") +} diff --git a/internal/server/handler/authority.go b/internal/server/handler/authority.go new file mode 100644 index 0000000..d4900f4 --- /dev/null +++ b/internal/server/handler/authority.go @@ -0,0 +1,117 @@ +package handler + +import ( + "strconv" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type Authority struct{ service *service.AccessService } + +func NewAuthority(service *service.AccessService) *Authority { return &Authority{service: service} } + +func (h *Authority) List(c *gin.Context) { + items, err := h.service.Authorities(c.Request.Context()) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, items, "获取成功") +} +func (h *Authority) Create(c *gin.Context) { + var req dto.AuthorityRequest + if c.ShouldBindJSON(&req) != nil || req.AuthorityID == 0 { + httpx.Fail(c, "参数错误") + return + } + value, err := h.service.CreateAuthorityRequest(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "创建失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "创建成功") +} +func (h *Authority) Copy(c *gin.Context) { + var req dto.CopyAuthorityRequest + if c.ShouldBindJSON(&req) != nil || req.Authority.AuthorityID == 0 || req.OldAuthorityID == 0 { + httpx.Fail(c, "参数错误") + return + } + value, err := h.service.CopyAuthorityRequest(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "拷贝失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "拷贝成功") +} +func (h *Authority) Update(c *gin.Context) { + var req dto.AuthorityRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + value, err := h.service.UpdateAuthorityRequest(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "更新失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "更新成功") +} +func (h *Authority) Delete(c *gin.Context) { + var req dto.DeleteAuthorityRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil { + httpx.Fail(c, err.Error()) + return + } + httpx.OK(c) +} +func (h *Authority) SetUsers(c *gin.Context) { + var req dto.SetRoleUsersRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} +func (h *Authority) Users(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64) + ids, err := h.service.AuthorityUserIDs(c.Request.Context(), uint(id)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") +} +func (h *Authority) SetDataScope(c *gin.Context) { + var req dto.SetDataScopeRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} +func (h *Authority) DataScopeDepartments(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("authorityId"), 10, 64) + ids, err := h.service.DataScopeDepartmentIDs(c.Request.Context(), uint(id)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") +} diff --git a/internal/server/handler/email.go b/internal/server/handler/email.go new file mode 100644 index 0000000..5f2ac4f --- /dev/null +++ b/internal/server/handler/email.go @@ -0,0 +1,34 @@ +package handler + +import ( + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type Email struct{ service *service.EmailService } + +func NewEmail(service *service.EmailService) *Email { return &Email{service: service} } + +func (h *Email) Test(c *gin.Context) { + if err := h.service.Test(c.Request.Context()); err != nil { + httpx.Fail(c, "发送失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功") +} + +func (h *Email) Send(c *gin.Context) { + var req dto.SendEmailRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil { + httpx.Fail(c, "发送失败:"+err.Error()) + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功") +} diff --git a/internal/server/handler/menu.go b/internal/server/handler/menu.go new file mode 100644 index 0000000..5bf23c8 --- /dev/null +++ b/internal/server/handler/menu.go @@ -0,0 +1,136 @@ +package handler + +import ( + "strconv" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type Menu struct{ service *service.MenuService } + +func NewMenu(service *service.MenuService) *Menu { return &Menu{service: service} } + +func (h *Menu) List(c *gin.Context) { + items, err := h.service.List(c.Request.Context()) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: int64(len(items)), Page: 1, PageSize: 999}, "获取成功") +} + +func (h *Menu) Tree(c *gin.Context) { + items, err := h.service.Tree(c.Request.Context()) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": items}, "获取成功") +} + +func (h *Menu) Create(c *gin.Context) { + var req dto.MenuRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Create(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "新增失败") + return + } + httpx.OK(c) +} + +func (h *Menu) Update(c *gin.Context) { + var req dto.MenuRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Update(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "更新失败") + return + } + httpx.OK(c) +} + +func (h *Menu) Delete(c *gin.Context) { + var req dto.DeleteMenuRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Delete(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, err.Error()) + return + } + httpx.OK(c) +} + +func (h *Menu) Find(c *gin.Context) { + var req dto.GetMenuRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + item, err := h.service.Find(c.Request.Context(), req.ID) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"menu": item}, "获取成功") +} + +func (h *Menu) SetAuthorityMenus(c *gin.Context) { + var req dto.SetAuthorityMenusRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetAuthorityMenus(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "添加失败") + return + } + httpx.OK(c) +} + +func (h *Menu) AuthorityMenus(c *gin.Context) { + var req dto.GetAuthorityMenusRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + menus, err := h.service.AuthorityMenus(c.Request.Context(), req.AuthorityID) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功") +} + +func (h *Menu) RoleIDs(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("menuId"), 10, 64) + ids, err := h.service.RoleIDs(c.Request.Context(), uint(id)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") +} + +func (h *Menu) SetRoles(c *gin.Context) { + var req dto.SetMenuRolesRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetRoles(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} diff --git a/internal/server/handler/organization.go b/internal/server/handler/organization.go new file mode 100644 index 0000000..d302429 --- /dev/null +++ b/internal/server/handler/organization.go @@ -0,0 +1,198 @@ +package handler + +import ( + "strconv" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type Organization struct{ service *service.OrganizationService } + +func NewOrganization(service *service.OrganizationService) *Organization { + return &Organization{service: service} +} + +func (h *Organization) ListDepartments(c *gin.Context) { + var req dto.DepartmentListRequest + _ = c.ShouldBindJSON(&req) + items, err := h.service.Departments(c.Request.Context(), req.Name) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, items, "获取成功") +} +func (h *Organization) CreateDepartment(c *gin.Context) { + var req dto.DepartmentRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.CreateDepartment(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "创建失败") + return + } + httpx.OK(c) +} +func (h *Organization) UpdateDepartment(c *gin.Context) { + var req dto.DepartmentRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.UpdateDepartment(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "更新失败") + return + } + httpx.OK(c) +} +func (h *Organization) DeleteDepartment(c *gin.Context) { + var req dto.DeleteDepartmentRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.DeleteDepartment(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, err.Error()) + return + } + httpx.OK(c) +} +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)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, item, "获取成功") +} +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)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") +} +func (h *Organization) SetDepartmentUsers(c *gin.Context) { + var req dto.SetDepartmentUsersRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetDepartmentUsers(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} +func (h *Organization) SetUserDepartments(c *gin.Context) { + var req dto.SetUserDepartmentsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetUserDepartments(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} + +func (h *Organization) ListPositions(c *gin.Context) { + var req dto.PositionListRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + items, total, err := h.service.Positions(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") +} +func (h *Organization) CreatePosition(c *gin.Context) { + var req dto.PositionRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.CreatePosition(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "创建失败") + return + } + httpx.OK(c) +} +func (h *Organization) UpdatePosition(c *gin.Context) { + var req dto.PositionRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.UpdatePosition(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "更新失败") + return + } + httpx.OK(c) +} +func (h *Organization) DeletePosition(c *gin.Context) { + var req dto.DeletePositionRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.DeletePosition(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "删除失败") + return + } + httpx.OK(c) +} +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)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, item, "获取成功") +} +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)) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") +} +func (h *Organization) SetPositionUsers(c *gin.Context) { + var req dto.SetPositionUsersRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetPositionUsers(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} +func (h *Organization) SetUserPositions(c *gin.Context) { + var req dto.SetUserPositionsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetUserPositions(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "设置失败") + return + } + httpx.OK(c) +} diff --git a/internal/server/handler/permission.go b/internal/server/handler/permission.go new file mode 100644 index 0000000..c290408 --- /dev/null +++ b/internal/server/handler/permission.go @@ -0,0 +1,56 @@ +package handler + +import ( + "strconv" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + + "github.com/gin-gonic/gin" +) + +type Permission struct{ service *service.AccessService } + +func NewPermission(service *service.AccessService) *Permission { return &Permission{service: service} } + +func (h *Permission) Buttons(c *gin.Context) { + var req dto.GetAuthorityButtonsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + selected, err := h.service.SelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID) + if err != nil { + httpx.Fail(c, "查询失败") + return + } + httpx.Write(c, httpx.CodeSuccess, gin.H{"selected": selected}, "查询成功") +} + +func (h *Permission) SetButtons(c *gin.Context) { + var req dto.SetAuthorityButtonsRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.SetSelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID, req.Selected); err != nil { + httpx.Fail(c, "分配失败") + return + } + httpx.OK(c) +} + +func (h *Permission) CanRemoveButton(c *gin.Context) { + id, _ := strconv.ParseUint(c.Query("id"), 10, 64) + allowed, err := h.service.CanRemoveButton(c.Request.Context(), uint(id)) + if err != nil { + httpx.Fail(c, "检查失败") + return + } + if !allowed { + httpx.Fail(c, "此按钮正在被使用无法删除") + return + } + httpx.OK(c) +} diff --git a/internal/server/handler/task.go b/internal/server/handler/task.go new file mode 100644 index 0000000..fc4b1c0 --- /dev/null +++ b/internal/server/handler/task.go @@ -0,0 +1,168 @@ +package handler + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "kra/internal/server/httpx" + "kra/internal/service" + "kra/internal/service/dto" + "kra/internal/worker" + + "github.com/gin-gonic/gin" +) + +type Task struct { + service *service.TaskService + scheduler *worker.TaskScheduler +} + +func NewTask(service *service.TaskService, scheduler *worker.TaskScheduler) *Task { + return &Task{service: service, scheduler: scheduler} +} + +func (h *Task) Create(c *gin.Context) { + var req dto.TaskRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + id, err := h.service.CreateRequest(c.Request.Context(), &req) + if err != nil { + httpx.Fail(c, "创建失败:"+err.Error()) + return + } + if err = h.scheduler.ScheduleID(c.Request.Context(), id); err != nil { + httpx.Fail(c, "调度失败:"+err.Error()) + return + } + httpx.OK(c) +} + +func (h *Task) Update(c *gin.Context) { + var req dto.TaskRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil { + httpx.Fail(c, "更新失败:"+err.Error()) + return + } + if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "调度失败:"+err.Error()) + return + } + httpx.OK(c) +} + +func (h *Task) Delete(c *gin.Context) { + var req dto.DeleteTaskRequest + if c.ShouldBindJSON(&req) != nil || req.ID == 0 { + httpx.Fail(c, "参数错误") + return + } + h.scheduler.Remove(req.ID) + if err := h.service.Delete(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "删除失败") + return + } + httpx.OK(c) +} + +func (h *Task) Toggle(c *gin.Context) { + var req dto.ToggleTaskRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.service.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil { + httpx.Fail(c, "设置失败") + return + } + if err := h.scheduler.ScheduleID(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "调度失败") + return + } + httpx.OK(c) +} + +func (h *Task) Trigger(c *gin.Context) { + var req dto.DeleteTaskRequest + if c.ShouldBindJSON(&req) != nil { + httpx.Fail(c, "参数错误") + return + } + if err := h.scheduler.TriggerID(c.Request.Context(), req.ID); err != nil { + httpx.Fail(c, "任务不存在") + return + } + httpx.OK(c) +} + +func (h *Task) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) + var enabled *bool + if raw := c.Query("enabled"); raw != "" { + value, _ := strconv.ParseBool(raw) + enabled = &value + } + items, total, err := h.service.ListRequest(c.Request.Context(), page, size, c.Query("name"), c.Query("executorType"), enabled, h.scheduler.NextRuns()) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功") +} + +func (h *Task) Logs(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) + taskID, _ := strconv.ParseUint(c.Query("taskId"), 10, 64) + items, total, err := h.service.Logs(c.Request.Context(), page, size, uint(taskID), c.Query("status")) + if err != nil { + httpx.Fail(c, "获取失败") + return + } + httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功") +} + +func (h *Task) Methods(c *gin.Context) { + httpx.Write(c, httpx.CodeSuccess, gin.H{"methods": h.service.RegisteredMethods()}, "获取成功") +} + +func (h *Task) AlertStream(c *gin.Context) { + c.Header("Content-Type", "text/event-stream") + c.Header("Cache-Control", "no-cache") + c.Header("Connection", "keep-alive") + c.Header("X-Accel-Buffering", "no") + flusher, ok := c.Writer.(http.Flusher) + if !ok { + httpx.Fail(c, "SSE 不受支持") + return + } + events := h.scheduler.Subscribe() + defer h.scheduler.Unsubscribe(events) + ticker := time.NewTicker(20 * time.Second) + defer ticker.Stop() + _, _ = fmt.Fprint(c.Writer, "event: connected\ndata: {}\n\n") + flusher.Flush() + for { + select { + case event, open := <-events: + if !open { + return + } + _, _ = fmt.Fprintf(c.Writer, "event: alert\ndata: %s\n\n", event) + flusher.Flush() + case <-ticker.C: + _, _ = fmt.Fprint(c.Writer, ": keepalive\n\n") + flusher.Flush() + case <-c.Request.Context().Done(): + return + } + } +} diff --git a/internal/server/httpx/response.go b/internal/server/httpx/response.go new file mode 100644 index 0000000..8003bd9 --- /dev/null +++ b/internal/server/httpx/response.go @@ -0,0 +1,40 @@ +package httpx + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +const ( + CodeSuccess = 0 + CodeError = 7 + CodePasswordChangeRequired = 10001 +) + +type Response struct { + Code int `json:"code"` + Data any `json:"data"` + Msg string `json:"msg"` +} + +type PageResult struct { + List any `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} + +func Write(c *gin.Context, code int, data any, message string) { + c.JSON(http.StatusOK, Response{Code: code, Data: data, Msg: message}) +} + +func OK(c *gin.Context) { Write(c, CodeSuccess, gin.H{}, "操作成功") } + +func OKWithData(c *gin.Context, data any) { Write(c, CodeSuccess, data, "成功") } + +func Fail(c *gin.Context, message string) { Write(c, CodeError, gin.H{}, message) } + +func NoAuth(c *gin.Context, message string) { + c.AbortWithStatusJSON(http.StatusUnauthorized, Response{Code: CodeError, Data: nil, Msg: message}) +} diff --git a/internal/server/menu_routes.go b/internal/server/menu_routes.go deleted file mode 100644 index 4b5c390..0000000 --- a/internal/server/menu_routes.go +++ /dev/null @@ -1,192 +0,0 @@ -package server - -import ( - "strconv" - - "kra/internal/biz" - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -type menuRequest struct { - ID uint `json:"ID"` - ParentID uint `json:"parentId"` - Path string `json:"path"` - Name string `json:"name"` - Hidden bool `json:"hidden"` - Component string `json:"component"` - Sort int `json:"sort"` - Meta struct { - ActiveName string `json:"activeName"` - KeepAlive bool `json:"keepAlive"` - DefaultMenu bool `json:"defaultMenu"` - Title string `json:"title"` - Icon string `json:"icon"` - CloseTab bool `json:"closeTab"` - TransitionType string `json:"transitionType"` - } `json:"meta"` - MenuButtons []struct { - ID uint `json:"ID"` - Name string `json:"name"` - Description string `json:"desc"` - } `json:"menuBtn"` - Parameters []struct { - ID uint `json:"ID"` - Type string `json:"type"` - Key string `json:"key"` - Value string `json:"value"` - } `json:"parameters"` -} - -func menuDomain(v menuRequest) *biz.Menu { - menu := &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType} - menu.Buttons = menuButtons(v, v.ID) - menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters)) - for _, parameter := range v.Parameters { - menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) - } - return menu -} - -func menuButtons(v menuRequest, menuID uint) []*biz.MenuButton { - buttons := make([]*biz.MenuButton, 0, len(v.MenuButtons)) - for _, button := range v.MenuButtons { - buttons = append(buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: menuID}) - } - return buttons -} - -func registerMenuRoutes(group *gin.RouterGroup, svc *service.AccessService) { - router := group.Group("/menu") - router.POST("/getMenuList", func(c *gin.Context) { - items, err := svc.Menus(c.Request.Context()) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, pageResult{List: items, Total: int64(len(items)), Page: 1, PageSize: 999}, "获取成功") - }) - router.POST("/getBaseMenuTree", func(c *gin.Context) { - items, err := svc.MenuTree(c.Request.Context()) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, gin.H{"menus": items}, "获取成功") - }) - router.POST("/addBaseMenu", func(c *gin.Context) { - var req menuRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - menu := menuDomain(req) - if err := svc.CreateMenu(c.Request.Context(), menu); err != nil { - fail(c, "新增失败") - return - } - ok(c) - }) - router.POST("/updateBaseMenu", func(c *gin.Context) { - var req menuRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.UpdateMenu(c.Request.Context(), menuDomain(req)); err != nil { - fail(c, "更新失败") - return - } - ok(c) - }) - router.POST("/deleteBaseMenu", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.DeleteMenu(c.Request.Context(), req.ID); err != nil { - fail(c, err.Error()) - return - } - ok(c) - }) - router.POST("/getBaseMenuById", func(c *gin.Context) { - var req struct { - ID uint `json:"id"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - item, err := svc.Menu(c.Request.Context(), req.ID) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, gin.H{"menu": item}, "获取成功") - }) - router.POST("/addMenuAuthority", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - Menus []struct { - ID uint `json:"ID"` - } `json:"menus"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - ids := make([]uint, 0, len(req.Menus)) - for _, m := range req.Menus { - ids = append(ids, m.ID) - } - if err := svc.SetAuthorityMenus(c.Request.Context(), req.AuthorityID, ids); err != nil { - fail(c, "添加失败") - return - } - ok(c) - }) - router.POST("/getMenuAuthority", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - menus, err := svc.AuthorityMenus(c.Request.Context(), req.AuthorityID) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, gin.H{"menus": menus}, "获取成功") - }) - router.GET("/getMenuRoles", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("menuId"), 10, 64) - ids, err := svc.MenuRoleIDs(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, ids, "获取成功") - }) - router.POST("/setMenuRoles", func(c *gin.Context) { - var req struct { - MenuID uint `json:"menuId"` - AuthorityIDs []uint `json:"authorityIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetMenuRoles(c.Request.Context(), req.MenuID, req.AuthorityIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) -} diff --git a/internal/server/middleware/access.go b/internal/server/middleware/access.go new file mode 100644 index 0000000..5718a27 --- /dev/null +++ b/internal/server/middleware/access.go @@ -0,0 +1,37 @@ +package middleware + +import ( + "net/http" + + "kra/internal/biz" + "kra/internal/server/httpx" + "kra/internal/service" + + "github.com/gin-gonic/gin" +) + +func AccessControl(access *service.AccessService, audit *service.AuditService) gin.HandlerFunc { + return func(c *gin.Context) { + claims := Claims(c) + if claims == nil { + httpx.NoAuth(c, "未登录或非法访问") + return + } + path := c.Request.URL.Path + allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, path, c.Request.Method) + if err != nil || !allowed { + requestID, _ := c.Get("request_id") + requestIDText, _ := requestID.(string) + _ = audit.RecordDataAccess(c.Request.Context(), &biz.DataAccessLog{EventType: "blocked_access", Operation: c.Request.Method, UserID: claims.ID, AuthorityID: claims.AuthorityID, RequestID: requestIDText, Method: c.Request.Method, Path: path, Detail: "Casbin policy denied the request"}) + c.AbortWithStatusJSON(http.StatusForbidden, httpx.Response{Code: httpx.CodeError, Data: nil, Msg: "权限不足"}) + return + } + scope, err := access.ResolveDataScope(c.Request.Context(), claims.AuthorityID, claims.ID) + if err != nil { + httpx.Fail(c, "数据权限加载失败") + return + } + c.Request = c.Request.WithContext(biz.NewDataScopeContext(c.Request.Context(), scope)) + c.Next() + } +} diff --git a/internal/server/middleware/auth.go b/internal/server/middleware/auth.go new file mode 100644 index 0000000..22ae6e9 --- /dev/null +++ b/internal/server/middleware/auth.go @@ -0,0 +1,84 @@ +package middleware + +import ( + "net/http" + "strconv" + "strings" + "time" + + "kra/internal/conf" + "kra/internal/server/httpx" + "kra/internal/service" + "kra/pkg/adminauth" + + "github.com/gin-gonic/gin" +) + +const claimsKey = "admin_claims" + +func Auth(config *conf.AdminBackend, settings *service.SettingsService) gin.HandlerFunc { + return func(c *gin.Context) { + token := c.GetHeader("x-token") + if token == "" { + token, _ = c.Cookie("x-token") + } + secret := "" + if config != nil && config.Jwt != nil { + secret = config.Jwt.SigningKey + } + claims, err := adminauth.Parse(token, secret) + if err != nil { + httpx.NoAuth(c, "未登录或非法访问") + return + } + if disabled, checkErr := settings.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled { + httpx.NoAuth(c, "登录状态已失效") + return + } + if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second { + expires, buffer := 7*24*time.Hour, time.Duration(claims.BufferTime)*time.Second + issuer := claims.Issuer + if config != nil && config.Jwt != nil { + if config.Jwt.ExpiresTime != nil { + expires = config.Jwt.ExpiresTime.AsDuration() + } + if config.Jwt.BufferTime != nil { + buffer = config.Jwt.BufferTime.AsDuration() + } + if config.Jwt.Issuer != "" { + issuer = config.Jwt.Issuer + } + } + newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd) + if refreshErr == nil { + c.Header("new-token", newToken) + c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) + http.SetCookie(c.Writer, &http.Cookie{Name: "x-token", Value: newToken, Path: "/", MaxAge: int(expires.Seconds()), HttpOnly: true, SameSite: http.SameSiteStrictMode}) + } + } + c.Set(claimsKey, claims) + c.Next() + } +} + +func Claims(c *gin.Context) *adminauth.Claims { + value, _ := c.Get(claimsKey) + claims, _ := value.(*adminauth.Claims) + return claims +} + +func MustChangePassword() gin.HandlerFunc { + return func(c *gin.Context) { + claims := Claims(c) + if claims == nil || !claims.MustChangePwd { + c.Next() + return + } + path := strings.TrimSuffix(c.Request.URL.Path, "/") + if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") { + c.Next() + return + } + c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "请先修改初始密码"}) + } +} diff --git a/internal/server/middleware/request.go b/internal/server/middleware/request.go new file mode 100644 index 0000000..8fc2910 --- /dev/null +++ b/internal/server/middleware/request.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func RequestMeta() gin.HandlerFunc { + return func(c *gin.Context) { + requestID := c.GetHeader("X-Request-Id") + if requestID == "" { + requestID = uuid.NewString() + } + c.Header("X-Request-Id", requestID) + c.Set("request_id", requestID) + c.Next() + } +} diff --git a/internal/server/organization_routes.go b/internal/server/organization_routes.go deleted file mode 100644 index 4896998..0000000 --- a/internal/server/organization_routes.go +++ /dev/null @@ -1,237 +0,0 @@ -package server - -import ( - "strconv" - - "kra/internal/biz" - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -func registerDepartmentRoutes(group *gin.RouterGroup, svc *service.AccessService) { - router := group.Group("/department") - router.POST("/getDepartmentList", func(c *gin.Context) { - var req struct { - Name string `json:"name"` - } - _ = c.ShouldBindJSON(&req) - items, err := svc.Departments(c.Request.Context(), req.Name) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, items, "获取成功") - }) - type departmentRequest struct { - ID uint `json:"ID"` - Name string `json:"name"` - ParentID uint `json:"parentId"` - Sort int `json:"sort"` - LeaderID uint `json:"leaderId"` - Status bool `json:"status"` - } - router.POST("/createDepartment", func(c *gin.Context) { - var req departmentRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.CreateDepartment(c.Request.Context(), &biz.Department{Name: req.Name, ParentID: req.ParentID, Sort: req.Sort, LeaderID: req.LeaderID, Status: req.Status}); err != nil { - fail(c, "创建失败") - return - } - ok(c) - }) - router.PUT("/updateDepartment", func(c *gin.Context) { - var req departmentRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.UpdateDepartment(c.Request.Context(), &biz.Department{ID: req.ID, Name: req.Name, ParentID: req.ParentID, Sort: req.Sort, LeaderID: req.LeaderID, Status: req.Status}); err != nil { - fail(c, "更新失败") - return - } - ok(c) - }) - router.DELETE("/deleteDepartment", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.DeleteDepartment(c.Request.Context(), req.ID); err != nil { - fail(c, err.Error()) - return - } - ok(c) - }) - router.GET("/findDepartment", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("id"), 10, 64) - item, err := svc.Department(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, item, "获取成功") - }) - router.GET("/getDepartmentUsers", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64) - ids, err := svc.DepartmentUserIDs(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, ids, "获取成功") - }) - router.POST("/setDepartmentUsers", func(c *gin.Context) { - var req struct { - DepartmentID uint `json:"deptId"` - UserIDs []uint `json:"userIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetDepartmentUsers(c.Request.Context(), req.DepartmentID, req.UserIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) - group.POST("/user/setUserDepartments", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - DepartmentIDs []uint `json:"deptIds"` - Primary uint `json:"primaryDeptId"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetUserDepartments(c.Request.Context(), req.ID, req.DepartmentIDs, req.Primary); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) -} - -func registerPositionRoutes(group *gin.RouterGroup, svc *service.AccessService) { - router := group.Group("/position") - router.POST("/getPositionList", func(c *gin.Context) { - var req struct { - Page int `json:"page"` - PageSize int `json:"pageSize"` - Name string `json:"name"` - Code string `json:"code"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - items, total, err := svc.Positions(c.Request.Context(), req.Page, req.PageSize, &biz.Position{Name: req.Name, Code: req.Code}) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") - }) - type positionRequest struct { - ID uint `json:"ID"` - Name string `json:"name"` - Code string `json:"code"` - Sort int `json:"sort"` - Status bool `json:"status"` - Remark string `json:"remark"` - } - router.POST("/createPosition", func(c *gin.Context) { - var req positionRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.CreatePosition(c.Request.Context(), &biz.Position{Name: req.Name, Code: req.Code, Sort: req.Sort, Status: req.Status, Remark: req.Remark}); err != nil { - fail(c, "创建失败") - return - } - ok(c) - }) - router.PUT("/updatePosition", func(c *gin.Context) { - var req positionRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.UpdatePosition(c.Request.Context(), &biz.Position{ID: req.ID, Name: req.Name, Code: req.Code, Sort: req.Sort, Status: req.Status, Remark: req.Remark}); err != nil { - fail(c, "更新失败") - return - } - ok(c) - }) - router.DELETE("/deletePosition", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.DeletePosition(c.Request.Context(), req.ID); err != nil { - fail(c, "删除失败") - return - } - ok(c) - }) - router.GET("/findPosition", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("id"), 10, 64) - item, err := svc.Position(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, item, "获取成功") - }) - router.GET("/getPositionUsers", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64) - ids, err := svc.PositionUserIDs(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, ids, "获取成功") - }) - router.POST("/setPositionUsers", func(c *gin.Context) { - var req struct { - PositionID uint `json:"positionId"` - UserIDs []uint `json:"userIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetPositionUsers(c.Request.Context(), req.PositionID, req.UserIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) - group.POST("/user/setUserPositions", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - PositionIDs []uint `json:"positionIds"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetUserPositions(c.Request.Context(), req.ID, req.PositionIDs); err != nil { - fail(c, "设置失败") - return - } - ok(c) - }) -} diff --git a/internal/server/policy_routes.go b/internal/server/policy_routes.go deleted file mode 100644 index 2293346..0000000 --- a/internal/server/policy_routes.go +++ /dev/null @@ -1,97 +0,0 @@ -package server - -import ( - "strconv" - - "kra/internal/biz" - "kra/internal/service" - - "github.com/gin-gonic/gin" -) - -func registerPolicyRoutes(group *gin.RouterGroup, svc *service.AccessService) { - casbin := group.Group("/casbin") - casbin.POST("/updateCasbin", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - Infos []struct { - Path string `json:"path"` - Method string `json:"method"` - } `json:"casbinInfos"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - paths := make([]*biz.API, 0, len(req.Infos)) - for _, v := range req.Infos { - paths = append(paths, &biz.API{Path: v.Path, Method: v.Method}) - } - if err := svc.SetPolicyPaths(c.Request.Context(), req.AuthorityID, paths); err != nil { - fail(c, "更新失败") - return - } - ok(c) - }) - casbin.POST("/getPolicyPathByAuthorityId", func(c *gin.Context) { - var req struct { - AuthorityID uint `json:"authorityId"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - paths, err := svc.PolicyPaths(c.Request.Context(), req.AuthorityID) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, gin.H{"paths": paths}, "获取成功") - }) - buttons := group.Group("/authorityBtn") - buttons.POST("/getAuthorityBtn", func(c *gin.Context) { - var req struct { - MenuID uint `json:"menuID"` - AuthorityID uint `json:"authorityId"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - selected, err := svc.SelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID) - if err != nil { - fail(c, "查询失败") - return - } - writeResult(c, codeSuccess, gin.H{"selected": selected}, "查询成功") - }) - buttons.POST("/setAuthorityBtn", func(c *gin.Context) { - var req struct { - MenuID uint `json:"menuID"` - AuthorityID uint `json:"authorityId"` - Selected []uint `json:"selected"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.SetSelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID, req.Selected); err != nil { - fail(c, "分配失败") - return - } - ok(c) - }) - buttons.POST("/canRemoveAuthorityBtn", func(c *gin.Context) { - id, _ := strconv.ParseUint(c.Query("id"), 10, 64) - allowed, err := svc.CanRemoveButton(c.Request.Context(), uint(id)) - if err != nil { - fail(c, "检查失败") - return - } - if !allowed { - fail(c, "此按钮正在被使用无法删除") - return - } - ok(c) - }) -} diff --git a/internal/server/response.go b/internal/server/response.go index 2664482..cc5f924 100644 --- a/internal/server/response.go +++ b/internal/server/response.go @@ -1,50 +1,29 @@ package server import ( - "net/http" + "kra/internal/server/httpx" "github.com/gin-gonic/gin" ) const ( - codeSuccess = 0 - codeError = 7 - codePasswordChangeRequired = 10001 + codeSuccess = httpx.CodeSuccess + codeError = httpx.CodeError + codePasswordChangeRequired = httpx.CodePasswordChangeRequired ) -type response struct { - Code int `json:"code"` - Data any `json:"data"` - Msg string `json:"msg"` -} +type response = httpx.Response -type pageResult struct { - List any `json:"list"` - Total int64 `json:"total"` - Page int `json:"page"` - PageSize int `json:"pageSize"` -} +type pageResult = httpx.PageResult func writeResult(c *gin.Context, code int, data any, message string) { - c.JSON(http.StatusOK, response{Code: code, Data: data, Msg: message}) + httpx.Write(c, code, data, message) } -func ok(c *gin.Context) { - writeResult(c, codeSuccess, gin.H{}, "操作成功") -} +func ok(c *gin.Context) { httpx.OK(c) } -func okWithData(c *gin.Context, data any) { - writeResult(c, codeSuccess, data, "成功") -} +func okWithData(c *gin.Context, data any) { httpx.OKWithData(c, data) } -func fail(c *gin.Context, message string) { - writeResult(c, codeError, gin.H{}, message) -} +func fail(c *gin.Context, message string) { httpx.Fail(c, message) } -func noAuth(c *gin.Context, message string) { - c.AbortWithStatusJSON(http.StatusUnauthorized, response{ - Code: codeError, - Data: nil, - Msg: message, - }) -} +func noAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) } diff --git a/internal/server/router/announcement.go b/internal/server/router/announcement.go new file mode 100644 index 0000000..1d35d9d --- /dev/null +++ b/internal/server/router/announcement.go @@ -0,0 +1,20 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterAnnouncement(private, public *gin.RouterGroup, h *handler.Announcement) { + privateInfo := private.Group("/info") + privateInfo.POST("/createInfo", h.Create) + privateInfo.DELETE("/deleteInfo", h.Delete) + privateInfo.DELETE("/deleteInfoByIds", h.DeleteByIDs) + privateInfo.PUT("/updateInfo", h.Update) + privateInfo.GET("/findInfo", h.Find) + privateInfo.GET("/getInfoList", h.List) + publicInfo := public.Group("/info") + publicInfo.GET("/getInfoDataSource", h.DataSource) + publicInfo.GET("/getInfoPublic", h.Public) +} diff --git a/internal/server/router/api.go b/internal/server/router/api.go new file mode 100644 index 0000000..58681fe --- /dev/null +++ b/internal/server/router/api.go @@ -0,0 +1,28 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterAPI(group *gin.RouterGroup, engine *gin.Engine, h *handler.API) { + api := group.Group("/api") + api.POST("/getApiList", h.List) + api.POST("/getAllApis", h.All) + api.POST("/createApi", h.Create) + api.POST("/updateApi", h.Update) + api.POST("/deleteApi", h.Delete) + api.DELETE("/deleteApisByIds", h.DeleteByIDs) + api.POST("/getApiById", h.Find) + api.GET("/getApiGroups", h.Groups) + api.GET("/getApiRoles", h.Roles) + api.POST("/setApiRoles", h.SetRoles) + api.GET("/syncApi", h.Sync(engine)) + api.POST("/ignoreApi", h.Ignore) + api.POST("/enterSyncApi", h.ApplySync) + api.GET("/freshCasbin", h.FreshCasbin) + casbin := group.Group("/casbin") + casbin.POST("/updateCasbin", h.SetPolicyPaths) + casbin.POST("/getPolicyPathByAuthorityId", h.PolicyPaths) +} diff --git a/internal/server/router/authority.go b/internal/server/router/authority.go new file mode 100644 index 0000000..f904d40 --- /dev/null +++ b/internal/server/router/authority.go @@ -0,0 +1,20 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterAuthority(group *gin.RouterGroup, h *handler.Authority) { + router := group.Group("/authority") + router.POST("/getAuthorityList", h.List) + router.POST("/createAuthority", h.Create) + router.POST("/copyAuthority", h.Copy) + router.PUT("/updateAuthority", h.Update) + router.POST("/deleteAuthority", h.Delete) + router.POST("/setRoleUsers", h.SetUsers) + router.GET("/getUsersByAuthority", h.Users) + router.POST("/setDataScope", h.SetDataScope) + router.GET("/getDataScopeDepts", h.DataScopeDepartments) +} diff --git a/internal/server/router/email.go b/internal/server/router/email.go new file mode 100644 index 0000000..b1d748e --- /dev/null +++ b/internal/server/router/email.go @@ -0,0 +1,13 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterEmail(group *gin.RouterGroup, h *handler.Email) { + email := group.Group("/email") + email.POST("/emailTest", h.Test) + email.POST("/sendEmail", h.Send) +} diff --git a/internal/server/router/menu.go b/internal/server/router/menu.go new file mode 100644 index 0000000..d1a93c6 --- /dev/null +++ b/internal/server/router/menu.go @@ -0,0 +1,21 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterMenu(group *gin.RouterGroup, h *handler.Menu) { + router := group.Group("/menu") + router.POST("/getMenuList", h.List) + router.POST("/getBaseMenuTree", h.Tree) + router.POST("/addBaseMenu", h.Create) + router.POST("/updateBaseMenu", h.Update) + router.POST("/deleteBaseMenu", h.Delete) + router.POST("/getBaseMenuById", h.Find) + router.POST("/addMenuAuthority", h.SetAuthorityMenus) + router.POST("/getMenuAuthority", h.AuthorityMenus) + router.GET("/getMenuRoles", h.RoleIDs) + router.POST("/setMenuRoles", h.SetRoles) +} diff --git a/internal/server/router/organization.go b/internal/server/router/organization.go new file mode 100644 index 0000000..5f445d2 --- /dev/null +++ b/internal/server/router/organization.go @@ -0,0 +1,29 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterOrganization(group *gin.RouterGroup, h *handler.Organization) { + departments := group.Group("/department") + departments.POST("/getDepartmentList", h.ListDepartments) + departments.POST("/createDepartment", h.CreateDepartment) + departments.PUT("/updateDepartment", h.UpdateDepartment) + departments.DELETE("/deleteDepartment", h.DeleteDepartment) + departments.GET("/findDepartment", h.FindDepartment) + departments.GET("/getDepartmentUsers", h.DepartmentUsers) + departments.POST("/setDepartmentUsers", h.SetDepartmentUsers) + group.POST("/user/setUserDepartments", h.SetUserDepartments) + + positions := group.Group("/position") + positions.POST("/getPositionList", h.ListPositions) + positions.POST("/createPosition", h.CreatePosition) + positions.PUT("/updatePosition", h.UpdatePosition) + positions.DELETE("/deletePosition", h.DeletePosition) + positions.GET("/findPosition", h.FindPosition) + positions.GET("/getPositionUsers", h.PositionUsers) + positions.POST("/setPositionUsers", h.SetPositionUsers) + group.POST("/user/setUserPositions", h.SetUserPositions) +} diff --git a/internal/server/router/permission.go b/internal/server/router/permission.go new file mode 100644 index 0000000..bb960fd --- /dev/null +++ b/internal/server/router/permission.go @@ -0,0 +1,14 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterPermission(group *gin.RouterGroup, h *handler.Permission) { + buttons := group.Group("/authorityBtn") + buttons.POST("/getAuthorityBtn", h.Buttons) + buttons.POST("/setAuthorityBtn", h.SetButtons) + buttons.POST("/canRemoveAuthorityBtn", h.CanRemoveButton) +} diff --git a/internal/server/router/task.go b/internal/server/router/task.go new file mode 100644 index 0000000..7be9f1d --- /dev/null +++ b/internal/server/router/task.go @@ -0,0 +1,20 @@ +package router + +import ( + "kra/internal/server/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterTask(group *gin.RouterGroup, h *handler.Task) { + router := group.Group("/timedTask") + router.POST("/createTimedTask", h.Create) + router.PUT("/updateTimedTask", h.Update) + router.DELETE("/deleteTimedTask", h.Delete) + router.POST("/toggleTimedTask", h.Toggle) + router.POST("/triggerTimedTask", h.Trigger) + router.GET("/getTimedTaskList", h.List) + router.GET("/getTimedTaskLogList", h.Logs) + router.GET("/getRegisteredMethods", h.Methods) + router.GET("/alertStream", h.AlertStream) +} diff --git a/internal/server/server.go b/internal/server/server.go index dc0332d..bbe8739 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,8 +1,11 @@ package server import ( + "kra/internal/server/handler" + "kra/internal/worker" + "github.com/google/wire" ) // ProviderSet is server providers. -var ProviderSet = wire.NewSet(NewGinServer, NewTaskScheduler) +var ProviderSet = wire.NewSet(NewGinServer, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewTask, worker.NewTaskScheduler) diff --git a/internal/server/system_config_routes.go b/internal/server/system_config_routes.go index 9e54850..ace70a7 100644 --- a/internal/server/system_config_routes.go +++ b/internal/server/system_config_routes.go @@ -8,6 +8,7 @@ import ( "kra/internal/biz" "kra/internal/conf" "kra/internal/service" + "kra/internal/worker" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/v4/cpu" @@ -15,7 +16,7 @@ import ( "google.golang.org/protobuf/proto" ) -func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService, scheduler *TaskScheduler) { +func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService, scheduler *worker.TaskScheduler) { security := group.Group("/securityConfig") security.GET("/getSecurityConfig", func(c *gin.Context) { value, err := settings.Security(c.Request.Context()) diff --git a/internal/server/task_routes.go b/internal/server/task_routes.go deleted file mode 100644 index 3c671b5..0000000 --- a/internal/server/task_routes.go +++ /dev/null @@ -1,187 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/gin-gonic/gin" - "kra/internal/biz" - "kra/internal/service" -) - -type taskRequest struct { - ID uint `json:"ID"` - Name string `json:"name"` - Description string `json:"description"` - Spec string `json:"spec"` - WithSeconds bool `json:"withSeconds"` - ExecutorType string `json:"executorType"` - MethodName string `json:"methodName"` - Params json.RawMessage `json:"params"` - HTTPURL string `json:"httpUrl"` - HTTPMethod string `json:"httpMethod"` - HTTPHeader json.RawMessage `json:"httpHeader"` - HTTPBody string `json:"httpBody"` - HTTPAllowPrivate bool `json:"httpAllowPrivate"` - Enabled bool `json:"enabled"` -} - -func taskDomain(v 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} -} -func registerTaskRoutes(group *gin.RouterGroup, svc *service.TaskService, scheduler *TaskScheduler) { - router := group.Group("/timedTask") - router.POST("/createTimedTask", func(c *gin.Context) { - var req taskRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - task := taskDomain(req) - if err := svc.Create(c.Request.Context(), task); err != nil { - fail(c, "创建失败:"+err.Error()) - return - } - if err := scheduler.Schedule(task); err != nil { - fail(c, "调度失败:"+err.Error()) - return - } - ok(c) - }) - router.PUT("/updateTimedTask", func(c *gin.Context) { - var req taskRequest - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - task := taskDomain(req) - if err := svc.Update(c.Request.Context(), task); err != nil { - fail(c, "更新失败:"+err.Error()) - return - } - if err := scheduler.Schedule(task); err != nil { - fail(c, "调度失败:"+err.Error()) - return - } - ok(c) - }) - router.DELETE("/deleteTimedTask", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - } - if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - fail(c, "参数错误") - return - } - scheduler.Remove(req.ID) - if err := svc.Delete(c.Request.Context(), req.ID); err != nil { - fail(c, "删除失败") - return - } - ok(c) - }) - router.POST("/toggleTimedTask", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - Enabled bool `json:"enabled"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - if err := svc.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil { - fail(c, "设置失败") - return - } - task, err := svc.Task(c.Request.Context(), req.ID) - if err != nil { - fail(c, "任务不存在") - return - } - if err = scheduler.Schedule(task); err != nil { - fail(c, "调度失败") - return - } - ok(c) - }) - router.POST("/triggerTimedTask", func(c *gin.Context) { - var req struct { - ID uint `json:"ID"` - } - if c.ShouldBindJSON(&req) != nil { - fail(c, "参数错误") - return - } - task, err := svc.Task(c.Request.Context(), req.ID) - if err != nil { - fail(c, "任务不存在") - return - } - scheduler.Trigger(task) - ok(c) - }) - router.GET("/getTimedTaskList", func(c *gin.Context) { - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) - var enabled *bool - if raw := c.Query("enabled"); raw != "" { - value, _ := strconv.ParseBool(raw) - enabled = &value - } - items, total, err := svc.Tasks(c.Request.Context(), page, size, &biz.TimedTask{Name: c.Query("name"), ExecutorType: c.Query("executorType"), EnabledFilter: enabled}, scheduler.NextRuns()) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功") - }) - router.GET("/getTimedTaskLogList", func(c *gin.Context) { - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - size, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) - taskID, _ := strconv.ParseUint(c.Query("taskId"), 10, 64) - items, total, err := svc.Logs(c.Request.Context(), page, size, uint(taskID), c.Query("status")) - if err != nil { - fail(c, "获取失败") - return - } - writeResult(c, codeSuccess, pageResult{List: items, Total: total, Page: page, PageSize: size}, "获取成功") - }) - router.GET("/getRegisteredMethods", func(c *gin.Context) { - writeResult(c, codeSuccess, gin.H{"methods": svc.RegisteredMethods()}, "获取成功") - }) - router.GET("/alertStream", func(c *gin.Context) { - c.Header("Content-Type", "text/event-stream") - c.Header("Cache-Control", "no-cache") - c.Header("Connection", "keep-alive") - c.Header("X-Accel-Buffering", "no") - flusher, okFlush := c.Writer.(http.Flusher) - if !okFlush { - fail(c, "SSE 不受支持") - return - } - events := scheduler.Subscribe() - defer scheduler.Unsubscribe(events) - ticker := time.NewTicker(20 * time.Second) - defer ticker.Stop() - _, _ = fmt.Fprint(c.Writer, "event: connected\ndata: {}\n\n") - flusher.Flush() - for { - select { - case event, open := <-events: - if !open { - return - } - _, _ = fmt.Fprintf(c.Writer, "event: alert\ndata: %s\n\n", event) - flusher.Flush() - case <-ticker.C: - _, _ = fmt.Fprint(c.Writer, ": keepalive\n\n") - flusher.Flush() - case <-c.Request.Context().Done(): - return - } - } - }) -} diff --git a/internal/service/access.go b/internal/service/access.go index 659434e..4dc1b88 100644 --- a/internal/service/access.go +++ b/internal/service/access.go @@ -5,6 +5,7 @@ import ( "strings" "kra/internal/biz" + "kra/internal/service/dto" ) type AccessService struct{ uc *biz.AccessUsecase } @@ -63,32 +64,32 @@ func (s *AccessService) UpdateAuthority(ctx context.Context, v *biz.Authority) e func (s *AccessService) DeleteAuthority(ctx context.Context, id uint) error { return s.uc.Repo().DeleteAuthority(ctx, id) } -func (s *AccessService) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error { - return s.uc.Repo().SetAuthorityMenus(ctx, id, ids) +func authorityDomain(req *dto.AuthorityRequest) *biz.Authority { + return &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} } -func (s *AccessService) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.Repo().AuthorityMenuIDs(ctx, id) +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) AuthorityMenus(ctx context.Context, id uint) ([]map[string]any, error) { - ids, err := s.uc.Repo().AuthorityMenuIDs(ctx, id) - if err != nil { +func (s *AccessService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { + value := authorityDomain(req) + if err := s.CreateAuthority(ctx, value); err != nil { return nil, err } - selected := make(map[uint]bool, len(ids)) - for _, value := range ids { - selected[value] = true - } - menus, err := s.uc.Repo().ListMenus(ctx) - if err != nil { + return authorityResponse(value), nil +} +func (s *AccessService) 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 } - out := make([]map[string]any, 0, len(ids)) - for _, menu := range menus { - if selected[menu.ID] { - out = append(out, map[string]any{"menuId": menu.ID, "authorityId": id, "sysBaseMenu": convertMenu(menu), "parentId": menu.ParentID}) - } + return authorityResponse(value), nil +} +func (s *AccessService) 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 out, nil + return authorityResponse(value), nil } func (s *AccessService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { return s.uc.Repo().SetAuthorityUsers(ctx, id, ids) @@ -106,127 +107,16 @@ func (s *AccessService) ResolveDataScope(ctx context.Context, authorityID, userI return s.uc.Repo().ResolveDataScope(ctx, authorityID, userID) } -func (s *AccessService) Menus(ctx context.Context) ([]map[string]any, error) { - items, err := s.uc.Repo().ListMenus(ctx) - if err != nil { - return nil, err - } - out := make([]map[string]any, 0, len(items)) - for _, v := range items { - v.Buttons, err = s.uc.Repo().Buttons(ctx, v.ID) - if err != nil { - return nil, err - } - out = append(out, convertMenu(v)) - } - return out, nil -} -func (s *AccessService) MenuTree(ctx context.Context) ([]map[string]any, error) { - items, err := s.uc.Repo().ListMenus(ctx) - if err != nil { - return nil, err - } - byID := make(map[uint]*biz.Menu, len(items)) - for _, item := range items { - item.Buttons, err = s.uc.Repo().Buttons(ctx, item.ID) - if err != nil { - return nil, err - } - item.Children = []*biz.Menu{} - byID[item.ID] = item - } - roots := make([]*biz.Menu, 0) - for _, item := range items { - if parent := byID[item.ParentID]; parent != nil { - parent.Children = append(parent.Children, item) - } else { - roots = append(roots, item) - } - } - out := make([]map[string]any, 0, len(roots)) - for _, item := range roots { - out = append(out, convertMenu(item)) - } - return out, nil -} -func (s *AccessService) CreateMenu(ctx context.Context, v *biz.Menu) error { - return s.uc.Repo().CreateMenu(ctx, v) -} -func (s *AccessService) UpdateMenu(ctx context.Context, v *biz.Menu) error { - return s.uc.Repo().UpdateMenu(ctx, v) -} -func (s *AccessService) DeleteMenu(ctx context.Context, id uint) error { - return s.uc.Repo().DeleteMenu(ctx, id) -} -func (s *AccessService) Menu(ctx context.Context, id uint) (map[string]any, error) { - v, err := s.uc.Repo().FindMenu(ctx, id) - if err != nil { - return nil, err - } - v.Buttons, err = s.uc.Repo().Buttons(ctx, id) - if err != nil { - return nil, err - } - return convertMenu(v), nil -} -func (s *AccessService) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.Repo().MenuRoleIDs(ctx, id) -} -func (s *AccessService) SetMenuRoles(ctx context.Context, id uint, ids []uint) error { - return s.uc.Repo().SetMenuRoles(ctx, id, ids) -} -func (s *AccessService) ReplaceMenuButtons(ctx context.Context, menuID uint, buttons []*biz.MenuButton) error { - return s.uc.Repo().ReplaceMenuButtons(ctx, menuID, buttons) -} - -func (s *AccessService) APIs(ctx context.Context, page, size int, q *biz.API) ([]map[string]any, int64, error) { - items, total, err := s.uc.Repo().ListAPIs(ctx, page, size, q) - if err != nil { - return nil, 0, err - } - out := make([]map[string]any, 0, len(items)) - for _, v := range items { - out = append(out, apiDTO(v)) - } - return out, total, nil -} -func (s *AccessService) CreateAPI(ctx context.Context, v *biz.API) error { - return s.uc.Repo().CreateAPI(ctx, v) -} -func (s *AccessService) UpdateAPI(ctx context.Context, v *biz.API) error { - return s.uc.Repo().UpdateAPI(ctx, v) -} func (s *AccessService) DeleteAPIs(ctx context.Context, ids []uint) error { return s.uc.Repo().DeleteAPIs(ctx, ids) } -func (s *AccessService) API(ctx context.Context, id uint) (map[string]any, error) { - v, err := s.uc.Repo().FindAPI(ctx, id) - if err != nil { - return nil, err - } - return apiDTO(v), nil -} func (s *AccessService) APIRoleIDs(ctx context.Context, id uint) ([]uint, error) { return s.uc.Repo().APIRoleIDs(ctx, id) } func (s *AccessService) SetAPIRoles(ctx context.Context, id uint, ids []uint) error { return s.uc.Repo().SetAPIRoles(ctx, id, ids) } -func (s *AccessService) PolicyPaths(ctx context.Context, id uint) ([]map[string]any, error) { - items, err := s.uc.Repo().PolicyPaths(ctx, id) - if err != nil { - return nil, err - } - out := make([]map[string]any, 0, len(items)) - for _, v := range items { - out = append(out, map[string]any{"path": v.Path, "method": v.Method}) - } - return out, nil -} -func (s *AccessService) SetPolicyPaths(ctx context.Context, id uint, paths []*biz.API) error { - return s.uc.Repo().SetPolicyPaths(ctx, id, paths) -} -func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (map[string]any, error) { +func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) { stored, _, err := s.uc.Repo().ListAPIs(ctx, 1, 100000, nil) if err != nil { return nil, err @@ -250,31 +140,27 @@ func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (map[st for _, item := range stored { storedSet[key(item)] = item } - added := make([]map[string]any, 0) - deleted := make([]map[string]any, 0) - ignoredDTO := make([]map[string]any, 0, len(ignored)) + added := make([]*dto.APIResponse, 0) + deleted := make([]*dto.APIResponse, 0) + ignoredDTO := make([]*dto.APIResponse, 0, len(ignored)) for k, item := range routeSet { if storedSet[k] == nil { - added = append(added, apiDTO(item)) + added = append(added, apiResponse(item)) } } for k, item := range storedSet { if routeSet[k] == nil && !ignoreSet[k] { - deleted = append(deleted, apiDTO(item)) + deleted = append(deleted, apiResponse(item)) } } for _, item := range ignored { - ignoredDTO = append(ignoredDTO, apiDTO(item)) + ignoredDTO = append(ignoredDTO, apiResponse(item)) } - return map[string]any{"newApis": added, "deleteApis": deleted, "ignoreApis": ignoredDTO}, nil + return &dto.APISyncResponse{NewAPIs: added, DeleteAPIs: deleted, IgnoreAPIs: ignoredDTO}, nil } func (s *AccessService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error { return s.uc.Repo().SetAPIIgnored(ctx, path, method, ignored) } -func (s *AccessService) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error { - return s.uc.Repo().ApplyAPISync(ctx, added, deleted) -} - func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) { items, err := s.uc.Repo().Buttons(ctx, id) if err != nil { @@ -301,77 +187,3 @@ func (s *AccessService) SetSelectedButtons(ctx context.Context, aid, menuID uint func (s *AccessService) CanRemoveButton(ctx context.Context, id uint) (bool, error) { return s.uc.Repo().CanRemoveButton(ctx, id) } - -func (s *AccessService) Departments(ctx context.Context, name string) ([]map[string]any, error) { - items, err := s.uc.Repo().ListDepartments(ctx, name) - if err != nil { - return nil, err - } - out := make([]map[string]any, 0, len(items)) - for _, v := range items { - out = append(out, departmentDTO(v)) - } - return out, nil -} -func (s *AccessService) CreateDepartment(ctx context.Context, v *biz.Department) error { - return s.uc.Repo().CreateDepartment(ctx, v) -} -func (s *AccessService) UpdateDepartment(ctx context.Context, v *biz.Department) error { - return s.uc.Repo().UpdateDepartment(ctx, v) -} -func (s *AccessService) DeleteDepartment(ctx context.Context, id uint) error { - return s.uc.Repo().DeleteDepartment(ctx, id) -} -func (s *AccessService) Department(ctx context.Context, id uint) (map[string]any, error) { - v, err := s.uc.Repo().FindDepartment(ctx, id) - if err != nil { - return nil, err - } - return departmentDTO(v), nil -} -func (s *AccessService) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.Repo().DepartmentUserIDs(ctx, id) -} -func (s *AccessService) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error { - return s.uc.Repo().SetDepartmentUsers(ctx, id, ids) -} -func (s *AccessService) SetUserDepartments(ctx context.Context, id uint, ids []uint, primary uint) error { - return s.uc.Repo().SetUserDepartments(ctx, id, ids, primary) -} - -func (s *AccessService) Positions(ctx context.Context, page, size int, q *biz.Position) ([]map[string]any, int64, error) { - items, total, err := s.uc.Repo().ListPositions(ctx, page, size, q) - if err != nil { - return nil, 0, err - } - out := make([]map[string]any, 0, len(items)) - for _, v := range items { - out = append(out, positionDTO(v)) - } - return out, total, nil -} -func (s *AccessService) CreatePosition(ctx context.Context, v *biz.Position) error { - return s.uc.Repo().CreatePosition(ctx, v) -} -func (s *AccessService) UpdatePosition(ctx context.Context, v *biz.Position) error { - return s.uc.Repo().UpdatePosition(ctx, v) -} -func (s *AccessService) DeletePosition(ctx context.Context, id uint) error { - return s.uc.Repo().DeletePosition(ctx, id) -} -func (s *AccessService) Position(ctx context.Context, id uint) (map[string]any, error) { - v, err := s.uc.Repo().FindPosition(ctx, id) - if err != nil { - return nil, err - } - return positionDTO(v), nil -} -func (s *AccessService) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) { - return s.uc.Repo().PositionUserIDs(ctx, id) -} -func (s *AccessService) SetPositionUsers(ctx context.Context, id uint, ids []uint) error { - return s.uc.Repo().SetPositionUsers(ctx, id, ids) -} -func (s *AccessService) SetUserPositions(ctx context.Context, id uint, ids []uint) error { - return s.uc.Repo().SetUserPositions(ctx, id, ids) -} diff --git a/internal/service/api.go b/internal/service/api.go new file mode 100644 index 0000000..1ab08a7 --- /dev/null +++ b/internal/service/api.go @@ -0,0 +1,90 @@ +package service + +import ( + "context" + + "kra/internal/biz" + "kra/internal/service/dto" +) + +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} +} + +func apiResponse(value *biz.API) *dto.APIResponse { + return &dto.APIResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} +} + +func apiResponses(items []*biz.API) []*dto.APIResponse { + out := make([]*dto.APIResponse, 0, len(items)) + for _, item := range items { + out = append(out, apiResponse(item)) + } + return out +} + +func (s *AccessService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) { + items, total, err := s.uc.Repo().ListAPIs(ctx, req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method}) + if err != nil { + return nil, 0, err + } + return apiResponses(items), total, nil +} + +func (s *AccessService) AllAPI(ctx context.Context) ([]*dto.APIResponse, error) { + items, _, err := s.uc.Repo().ListAPIs(ctx, 1, 10000, nil) + if err != nil { + return nil, err + } + return apiResponses(items), nil +} + +func (s *AccessService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) error { + return s.uc.Repo().CreateAPI(ctx, apiDomain(req)) +} +func (s *AccessService) UpdateAPIRequest(ctx context.Context, req *dto.APIRequest) error { + return s.uc.Repo().UpdateAPI(ctx, apiDomain(req)) +} +func (s *AccessService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) { + value, err := s.uc.Repo().FindAPI(ctx, id) + if err != nil { + return nil, err + } + return apiResponse(value), nil +} +func (s *AccessService) 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 { + added = append(added, apiDomain(&req.NewAPIs[i])) + } + for i := range req.DeleteAPIs { + deleted = append(deleted, apiDomain(&req.DeleteAPIs[i])) + } + return s.uc.Repo().ApplyAPISync(ctx, added, deleted) +} +func (s *AccessService) 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.Repo().SetPolicyPaths(ctx, req.AuthorityID, paths) +} +func (s *AccessService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) { + items, err := s.uc.Repo().PolicyPaths(ctx, authorityID) + if err != nil { + return nil, err + } + out := make([]dto.PolicyPathRequest, 0, len(items)) + for _, item := range items { + out = append(out, dto.PolicyPathRequest{Path: item.Path, Method: item.Method}) + } + return out, nil +} +func (s *AccessService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) { + items := make([]*biz.API, 0, len(routes)) + for i := range routes { + items = append(items, apiDomain(&routes[i])) + } + return s.SyncAPIs(ctx, items) +} diff --git a/internal/service/dto/announcement.go b/internal/service/dto/announcement.go new file mode 100644 index 0000000..735d931 --- /dev/null +++ b/internal/service/dto/announcement.go @@ -0,0 +1,11 @@ +package dto + +import "encoding/json" + +type AnnouncementRequest struct { + ID uint `json:"ID"` + Title string `json:"title"` + Content string `json:"content"` + UserID *uint `json:"userID"` + Attachments json.RawMessage `json:"attachments"` +} diff --git a/internal/service/dto/api.go b/internal/service/dto/api.go new file mode 100644 index 0000000..5d90f6e --- /dev/null +++ b/internal/service/dto/api.go @@ -0,0 +1,71 @@ +package dto + +import "time" + +type APIRequest struct { + ID uint `json:"ID"` + Path string `json:"path"` + Description string `json:"description"` + APIGroup string `json:"apiGroup"` + Method string `json:"method"` +} + +type APIListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Path string `json:"path"` + Description string `json:"description"` + APIGroup string `json:"apiGroup"` + Method string `json:"method"` +} + +type APIResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Path string `json:"path"` + Description string `json:"description"` + APIGroup string `json:"apiGroup"` + Method string `json:"method"` +} + +type DeleteAPIRequest struct { + ID uint `json:"ID"` +} +type DeleteAPIsRequest struct { + IDs []uint `json:"ids"` +} +type GetAPIRequest struct { + ID uint `json:"id"` +} +type SetAPIRolesRequest struct { + APIID uint `json:"apiId"` + AuthorityIDs []uint `json:"authorityIds"` +} +type IgnoreAPIRequest struct { + Path string `json:"path"` + Method string `json:"method"` + Flag bool `json:"flag"` +} +type ApplyAPISyncRequest struct { + NewAPIs []APIRequest `json:"newApis"` + DeleteAPIs []APIRequest `json:"deleteApis"` +} +type PolicyPathRequest struct { + Path string `json:"path"` + Method string `json:"method"` +} +type SetPolicyPathsRequest struct { + AuthorityID uint `json:"authorityId"` + Infos []PolicyPathRequest `json:"casbinInfos"` +} +type GetPolicyPathsRequest struct { + AuthorityID uint `json:"authorityId"` +} + +type APISyncResponse struct { + NewAPIs []*APIResponse `json:"newApis"` + DeleteAPIs []*APIResponse `json:"deleteApis"` + IgnoreAPIs []*APIResponse `json:"ignoreApis"` +} diff --git a/internal/service/dto/authority.go b/internal/service/dto/authority.go new file mode 100644 index 0000000..41ee8ad --- /dev/null +++ b/internal/service/dto/authority.go @@ -0,0 +1,35 @@ +package dto + +type AuthorityRequest struct { + AuthorityID uint `json:"authorityId"` + AuthorityName string `json:"authorityName"` + ParentID *uint `json:"parentId"` + DataScope int `json:"dataScope"` + DefaultRouter string `json:"defaultRouter"` +} + +type CopyAuthorityRequest struct { + Authority AuthorityRequest `json:"authority"` + OldAuthorityID uint `json:"oldAuthorityId"` +} + +type DeleteAuthorityRequest struct { + AuthorityID uint `json:"authorityId"` +} +type SetRoleUsersRequest struct { + AuthorityID uint `json:"authorityId"` + UserIDs []uint `json:"userIds"` +} +type SetDataScopeRequest struct { + AuthorityID uint `json:"authorityId"` + DataScope int `json:"dataScope"` + DepartmentIDs []uint `json:"deptIds"` +} + +type AuthorityResponse struct { + AuthorityID uint `json:"authorityId"` + AuthorityName string `json:"authorityName"` + ParentID *uint `json:"parentId"` + DataScope int `json:"dataScope"` + DefaultRouter string `json:"defaultRouter"` +} diff --git a/internal/service/dto/email.go b/internal/service/dto/email.go new file mode 100644 index 0000000..5f8d6bc --- /dev/null +++ b/internal/service/dto/email.go @@ -0,0 +1,7 @@ +package dto + +type SendEmailRequest struct { + To string `json:"to"` + Subject string `json:"subject"` + Body string `json:"body"` +} diff --git a/internal/service/dto/menu.go b/internal/service/dto/menu.go new file mode 100644 index 0000000..ada7d5a --- /dev/null +++ b/internal/service/dto/menu.go @@ -0,0 +1,110 @@ +package dto + +type MenuMetaRequest struct { + ActiveName string `json:"activeName"` + KeepAlive bool `json:"keepAlive"` + DefaultMenu bool `json:"defaultMenu"` + Title string `json:"title"` + Icon string `json:"icon"` + CloseTab bool `json:"closeTab"` + TransitionType string `json:"transitionType"` +} + +type MenuButtonRequest struct { + ID uint `json:"ID"` + Name string `json:"name"` + Description string `json:"desc"` +} + +type MenuParameterRequest struct { + ID uint `json:"ID"` + Type string `json:"type"` + Key string `json:"key"` + Value string `json:"value"` +} + +type MenuRequest struct { + ID uint `json:"ID"` + ParentID uint `json:"parentId"` + Path string `json:"path"` + Name string `json:"name"` + Hidden bool `json:"hidden"` + Component string `json:"component"` + Sort int `json:"sort"` + Meta MenuMetaRequest `json:"meta"` + MenuButtons []MenuButtonRequest `json:"menuBtn"` + Parameters []MenuParameterRequest `json:"parameters"` +} + +type DeleteMenuRequest struct { + ID uint `json:"ID"` +} + +type GetMenuRequest struct { + ID uint `json:"id"` +} + +type AuthorityMenuItemRequest struct { + ID uint `json:"ID"` +} + +type SetAuthorityMenusRequest struct { + AuthorityID uint `json:"authorityId"` + Menus []AuthorityMenuItemRequest `json:"menus"` +} + +type GetAuthorityMenusRequest struct { + AuthorityID uint `json:"authorityId"` +} + +type SetMenuRolesRequest struct { + MenuID uint `json:"menuId"` + AuthorityIDs []uint `json:"authorityIds"` +} + +type MenuMetaResponse struct { + ActiveName string `json:"activeName"` + KeepAlive bool `json:"keepAlive"` + DefaultMenu bool `json:"defaultMenu"` + Title string `json:"title"` + Icon string `json:"icon"` + CloseTab bool `json:"closeTab"` + TransitionType string `json:"transitionType"` +} + +type MenuButtonResponse struct { + ID uint `json:"ID"` + Name string `json:"name"` + Description string `json:"desc"` + SysBaseMenuID uint `json:"sysBaseMenuID"` +} + +type MenuParameterResponse struct { + ID uint `json:"ID"` + SysBaseMenuID uint `json:"SysBaseMenuID"` + Type string `json:"type"` + Key string `json:"key"` + Value string `json:"value"` +} + +type MenuResponse struct { + ID uint `json:"ID"` + ParentID uint `json:"parentId"` + Path string `json:"path"` + Name string `json:"name"` + Hidden bool `json:"hidden"` + Component string `json:"component"` + Sort int `json:"sort"` + Meta MenuMetaResponse `json:"meta"` + Children []*MenuResponse `json:"children"` + Parameters []MenuParameterResponse `json:"parameters"` + MenuButtons []MenuButtonResponse `json:"menuBtn"` + Buttons map[string]uint `json:"btns"` +} + +type AuthorityMenuResponse struct { + MenuID uint `json:"menuId"` + AuthorityID uint `json:"authorityId"` + SysBaseMenu *MenuResponse `json:"sysBaseMenu"` + ParentID uint `json:"parentId"` +} diff --git a/internal/service/dto/organization.go b/internal/service/dto/organization.go new file mode 100644 index 0000000..742d6ec --- /dev/null +++ b/internal/service/dto/organization.go @@ -0,0 +1,83 @@ +package dto + +import "time" + +type DepartmentRequest struct { + ID uint `json:"ID"` + Name string `json:"name"` + ParentID uint `json:"parentId"` + Sort int `json:"sort"` + LeaderID uint `json:"leaderId"` + Status bool `json:"status"` +} + +type DepartmentListRequest struct { + Name string `json:"name"` +} +type DeleteDepartmentRequest struct { + ID uint `json:"ID"` +} +type SetDepartmentUsersRequest struct { + DepartmentID uint `json:"deptId"` + UserIDs []uint `json:"userIds"` +} +type SetUserDepartmentsRequest struct { + ID uint `json:"ID"` + DepartmentIDs []uint `json:"deptIds"` + Primary uint `json:"primaryDeptId"` +} + +type DepartmentResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + ParentID uint `json:"parentId"` + Ancestors string `json:"ancestors"` + Sort int `json:"sort"` + LeaderID uint `json:"leaderId"` + Leader any `json:"leader"` + Status bool `json:"status"` + Children []*DepartmentResponse `json:"children"` + NamePath string `json:"namePath"` +} + +type PositionRequest struct { + ID uint `json:"ID"` + Name string `json:"name"` + Code string `json:"code"` + Sort int `json:"sort"` + Status bool `json:"status"` + Remark string `json:"remark"` +} + +type PositionListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Name string `json:"name"` + Code string `json:"code"` +} +type DeletePositionRequest struct { + ID uint `json:"ID"` +} +type SetPositionUsersRequest struct { + PositionID uint `json:"positionId"` + UserIDs []uint `json:"userIds"` +} +type SetUserPositionsRequest struct { + ID uint `json:"ID"` + PositionIDs []uint `json:"positionIds"` +} + +type PositionResponse struct { + ID uint `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + DeletedAt any `json:"DeletedAt"` + Name string `json:"name"` + Code string `json:"code"` + Sort int `json:"sort"` + Status bool `json:"status"` + Remark string `json:"remark"` +} diff --git a/internal/service/dto/permission.go b/internal/service/dto/permission.go new file mode 100644 index 0000000..e260fbc --- /dev/null +++ b/internal/service/dto/permission.go @@ -0,0 +1,12 @@ +package dto + +type GetAuthorityButtonsRequest struct { + MenuID uint `json:"menuID"` + AuthorityID uint `json:"authorityId"` +} + +type SetAuthorityButtonsRequest struct { + MenuID uint `json:"menuID"` + AuthorityID uint `json:"authorityId"` + Selected []uint `json:"selected"` +} diff --git a/internal/service/dto/task.go b/internal/service/dto/task.go new file mode 100644 index 0000000..077d6a3 --- /dev/null +++ b/internal/service/dto/task.go @@ -0,0 +1,28 @@ +package dto + +import "encoding/json" + +type TaskRequest struct { + ID uint `json:"ID"` + Name string `json:"name"` + Description string `json:"description"` + Spec string `json:"spec"` + WithSeconds bool `json:"withSeconds"` + ExecutorType string `json:"executorType"` + MethodName string `json:"methodName"` + Params json.RawMessage `json:"params"` + HTTPURL string `json:"httpUrl"` + HTTPMethod string `json:"httpMethod"` + HTTPHeader json.RawMessage `json:"httpHeader"` + HTTPBody string `json:"httpBody"` + HTTPAllowPrivate bool `json:"httpAllowPrivate"` + Enabled bool `json:"enabled"` +} + +type DeleteTaskRequest struct { + ID uint `json:"ID"` +} +type ToggleTaskRequest struct { + ID uint `json:"ID"` + Enabled bool `json:"enabled"` +} diff --git a/internal/service/menu.go b/internal/service/menu.go new file mode 100644 index 0000000..6a9d922 --- /dev/null +++ b/internal/service/menu.go @@ -0,0 +1,112 @@ +package service + +import ( + "context" + + "kra/internal/biz" + "kra/internal/service/dto" +) + +type MenuService struct{ uc *biz.MenuUsecase } + +func NewMenuService(uc *biz.MenuUsecase) *MenuService { return &MenuService{uc: uc} } + +func menuDomain(v *dto.MenuRequest) *biz.Menu { + menu := &biz.Menu{ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType} + menu.Buttons = make([]*biz.MenuButton, 0, len(v.MenuButtons)) + for _, button := range v.MenuButtons { + menu.Buttons = append(menu.Buttons, &biz.MenuButton{ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID}) + } + menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters)) + for _, parameter := range v.Parameters { + menu.Parameters = append(menu.Parameters, &biz.MenuParameter{ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + return menu +} + +func menuResponse(menu *biz.Menu) *dto.MenuResponse { + children := make([]*dto.MenuResponse, 0, len(menu.Children)) + for _, child := range menu.Children { + children = append(children, menuResponse(child)) + } + buttons := make([]dto.MenuButtonResponse, 0, len(menu.Buttons)) + buttonAuthorities := make(map[string]uint, len(menu.Buttons)) + for _, button := range menu.Buttons { + buttons = append(buttons, dto.MenuButtonResponse{ID: button.ID, Name: button.Name, Description: button.Description, SysBaseMenuID: button.MenuID}) + if button.AuthorityID != 0 { + buttonAuthorities[button.Name] = button.AuthorityID + } + } + parameters := make([]dto.MenuParameterResponse, 0, len(menu.Parameters)) + for _, parameter := range menu.Parameters { + parameters = append(parameters, dto.MenuParameterResponse{ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + } + return &dto.MenuResponse{ID: menu.ID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: dto.MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: buttons, Buttons: buttonAuthorities} +} + +func menuResponses(items []*biz.Menu) []*dto.MenuResponse { + out := make([]*dto.MenuResponse, 0, len(items)) + for _, item := range items { + out = append(out, menuResponse(item)) + } + return out +} + +func (s *MenuService) List(ctx context.Context) ([]*dto.MenuResponse, error) { + items, err := s.uc.List(ctx) + if err != nil { + return nil, err + } + return menuResponses(items), nil +} + +func (s *MenuService) Tree(ctx context.Context) ([]*dto.MenuResponse, error) { + items, err := s.uc.Tree(ctx) + if err != nil { + return nil, err + } + return menuResponses(items), nil +} + +func (s *MenuService) Create(ctx context.Context, req *dto.MenuRequest) error { + return s.uc.Create(ctx, menuDomain(req)) +} +func (s *MenuService) Update(ctx context.Context, req *dto.MenuRequest) error { + return s.uc.Update(ctx, menuDomain(req)) +} +func (s *MenuService) Delete(ctx context.Context, id uint) error { return s.uc.Delete(ctx, id) } + +func (s *MenuService) Find(ctx context.Context, id uint) (*dto.MenuResponse, error) { + item, err := s.uc.Find(ctx, id) + if err != nil { + return nil, err + } + return menuResponse(item), nil +} + +func (s *MenuService) SetAuthorityMenus(ctx context.Context, req *dto.SetAuthorityMenusRequest) error { + ids := make([]uint, 0, len(req.Menus)) + for _, menu := range req.Menus { + ids = append(ids, menu.ID) + } + return s.uc.SetAuthorityMenus(ctx, req.AuthorityID, ids) +} + +func (s *MenuService) AuthorityMenus(ctx context.Context, authorityID uint) ([]*dto.AuthorityMenuResponse, error) { + items, err := s.uc.AuthorityMenus(ctx, authorityID) + if err != nil { + return nil, err + } + out := make([]*dto.AuthorityMenuResponse, 0, len(items)) + for _, item := range items { + out = append(out, &dto.AuthorityMenuResponse{MenuID: item.ID, AuthorityID: authorityID, SysBaseMenu: menuResponse(item), ParentID: item.ParentID}) + } + return out, nil +} + +func (s *MenuService) RoleIDs(ctx context.Context, menuID uint) ([]uint, error) { + return s.uc.RoleIDs(ctx, menuID) +} +func (s *MenuService) SetRoles(ctx context.Context, req *dto.SetMenuRolesRequest) error { + return s.uc.SetRoles(ctx, req.MenuID, req.AuthorityIDs) +} diff --git a/internal/service/organization.go b/internal/service/organization.go new file mode 100644 index 0000000..f8bd1c0 --- /dev/null +++ b/internal/service/organization.go @@ -0,0 +1,106 @@ +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)) + } + 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: nil, Status: value.Status, Children: children, 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.Position{Name: req.Name, Code: req.Code}) + 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/service.go b/internal/service/service.go index 5204d6e..45cb7f6 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(NewSystemService, NewAccessService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) +var ProviderSet = wire.NewSet(NewSystemService, NewAccessService, NewMenuService, NewOrganizationService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService, NewAnnouncementService, NewEmailService) diff --git a/internal/service/task.go b/internal/service/task.go index 3375e1c..70e6824 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -10,6 +10,7 @@ import ( "io" "kra/internal/biz" "kra/internal/conf" + "kra/internal/service/dto" "net" "net/http" "net/url" @@ -26,6 +27,22 @@ type TaskService struct { func NewTaskService(uc *biz.TaskUsecase, media *biz.MediaUsecase, config *conf.AdminBackend) *TaskService { return &TaskService{uc: uc, media: media, config: config} } +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} +} +func (s *TaskService) CreateRequest(ctx context.Context, req *dto.TaskRequest) (uint, error) { + value := taskDomain(req) + if err := s.Create(ctx, value); err != nil { + return 0, err + } + return value.ID, nil +} +func (s *TaskService) UpdateRequest(ctx context.Context, req *dto.TaskRequest) error { + return s.Update(ctx, taskDomain(req)) +} +func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool, next map[uint]time.Time) ([]map[string]any, int64, error) { + return s.Tasks(ctx, page, size, &biz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}, next) +} func (s *TaskService) Validate(v *biz.TimedTask) error { if v.Name == "" { return errors.New("任务名不能为空") diff --git a/internal/server/task_scheduler.go b/internal/worker/task_scheduler.go similarity index 90% rename from internal/server/task_scheduler.go rename to internal/worker/task_scheduler.go index 99c98ef..3c5fb52 100644 --- a/internal/server/task_scheduler.go +++ b/internal/worker/task_scheduler.go @@ -1,15 +1,17 @@ -package server +package worker import ( "context" "encoding/json" "fmt" - "github.com/robfig/cron/v3" - "kra/internal/biz" - "kra/internal/service" "log/slog" "sync" "time" + + "kra/internal/biz" + "kra/internal/service" + + "github.com/robfig/cron/v3" ) type TaskScheduler struct { @@ -22,6 +24,7 @@ type TaskScheduler struct { subMu sync.RWMutex subscribers map[chan []byte]struct{} } + type scheduledEntry struct { seconds bool entry cron.EntryID @@ -30,6 +33,7 @@ type scheduledEntry struct { func NewTaskScheduler(service *service.TaskService, 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{}{}} } + func (s *TaskScheduler) Start(ctx context.Context) error { s.standard.Start() s.seconds.Start() @@ -49,9 +53,9 @@ func (s *TaskScheduler) Start(ctx context.Context) error { <-ctx.Done() return nil } + func (s *TaskScheduler) Stop(ctx context.Context) error { - standardDone := s.standard.Stop().Done() - secondsDone := s.seconds.Stop().Done() + standardDone, secondsDone := s.standard.Stop().Done(), s.seconds.Stop().Done() select { case <-standardDone: case <-ctx.Done(): @@ -64,6 +68,7 @@ func (s *TaskScheduler) Stop(ctx context.Context) error { return ctx.Err() } } + func (s *TaskScheduler) Remove(id uint) { s.mu.Lock() defer s.mu.Unlock() @@ -76,6 +81,7 @@ func (s *TaskScheduler) Remove(id uint) { delete(s.entries, id) } } + func (s *TaskScheduler) Reload(ctx context.Context) error { s.mu.Lock() for id, old := range s.entries { @@ -105,6 +111,7 @@ func (s *TaskScheduler) Reload(ctx context.Context) error { } return nil } + func (s *TaskScheduler) Schedule(task *biz.TimedTask) error { s.Remove(task.ID) if !task.Enabled { @@ -132,6 +139,24 @@ func (s *TaskScheduler) Schedule(task *biz.TimedTask) error { s.mu.Unlock() return nil } + +func (s *TaskScheduler) ScheduleID(ctx context.Context, id uint) error { + task, err := s.service.Task(ctx, id) + if err != nil { + return err + } + return s.Schedule(task) +} + +func (s *TaskScheduler) TriggerID(ctx context.Context, id uint) error { + task, err := s.service.Task(ctx, id) + if err != nil { + return err + } + s.Trigger(task) + return nil +} + func (s *TaskScheduler) NextRuns() map[uint]time.Time { s.mu.Lock() defer s.mu.Unlock() @@ -145,6 +170,7 @@ func (s *TaskScheduler) NextRuns() map[uint]time.Time { } return out } + func (s *TaskScheduler) Trigger(task *biz.TimedTask) { go func() { log := s.service.Run(context.Background(), task, "manual") @@ -153,6 +179,7 @@ func (s *TaskScheduler) Trigger(task *biz.TimedTask) { } }() } + func (s *TaskScheduler) Subscribe() chan []byte { ch := make(chan []byte, 16) s.subMu.Lock()