diff --git a/cmd/wire.go b/cmd/wire.go index 19169d4..9bcf4c4 100644 --- a/cmd/wire.go +++ b/cmd/wire.go @@ -10,6 +10,7 @@ import ( "kra/internal/app" "kra/internal/biz" + systembiz "kra/internal/biz/system" "kra/internal/conf" "kra/internal/data" "kra/internal/initialize" @@ -37,6 +38,7 @@ func wireApp(*conf.Server, *conf.Runtime, *slog.Logger, *logging.ReloadableLogge worker.ProviderSet, app.Catalog, app.TaskRegistry, + runtimeContributions, app.Runtime, data.ProviderSet, integration.ProviderSet, @@ -44,7 +46,7 @@ func wireApp(*conf.Server, *conf.Runtime, *slog.Logger, *logging.ReloadableLogge wire.Bind(new(initialize.Backend), new(*data.Data)), wire.Bind(new(cache.RedisProvider), new(*data.Data)), wire.Bind(new(middleware.TokenAuthenticator), new(*service.AuthService)), - wire.Bind(new(biz.TaskMethodRegistry), new(*platformtask.Registry)), + wire.Bind(new(systembiz.TaskMethodRegistry), new(*platformtask.Registry)), biz.ProviderSet, service.ProviderSet, newApp, diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 40b26f6..45f96bc 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -9,7 +9,7 @@ package main import ( "github.com/go-kratos/kratos/v3" "kra/internal/app" - "kra/internal/biz" + system2 "kra/internal/biz/system" "kra/internal/conf" "kra/internal/data" "kra/internal/data/payment" @@ -49,97 +49,97 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger } authorityAccessRepo := system.NewAuthorityAccessRepo(dataData) apiRepo := system.NewAPIRepo(dataData) - accessControlUsecase := biz.NewAccessControlUsecase(authorityAccessRepo, apiRepo) + accessControlUsecase := system2.NewAccessControlUsecase(authorityAccessRepo, apiRepo) accessControlService := service.NewAccessControlService(accessControlUsecase) userRepo := system.NewUserRepo(dataData) - userUsecase := biz.NewUserUsecase(userRepo) + userUsecase := system2.NewUserUsecase(userRepo) securityRepo := system.NewSecurityRepo(dataData) - bizCache := cache.New(dataData) + systemCache := cache.New(dataData) runtimeSettings := system.NewRuntimeSettings(runtime) apiTokenRepo := system.NewAPITokenRepo(dataData) - tokenUsecase := biz.NewTokenUsecase(apiTokenRepo) - securityUsecase := biz.NewSecurityUsecase(securityRepo, bizCache, runtimeSettings, tokenUsecase) + tokenUsecase := system2.NewTokenUsecase(apiTokenRepo) + securityUsecase := system2.NewSecurityUsecase(securityRepo, systemCache, runtimeSettings, tokenUsecase) tokenIssuer := system.NewTokenIssuer(runtimeSettings) auditRecordRepo := system.NewAuditRecorderRepo(dataData) - authenticationUsecase := biz.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo) + authenticationUsecase := system2.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo) authService := service.NewAuthService(authenticationUsecase) securityService := service.NewSecurityService(securityUsecase) - auditRecorderUsecase := biz.NewAuditRecorderUsecase(auditRecordRepo) + auditRecorderUsecase := system2.NewAuditRecorderUsecase(auditRecordRepo) auditRecorder := service.NewAuditRecorder(auditRecorderUsecase) - authorityUsecase := biz.NewAuthorityUsecase(authorityAccessRepo) + authorityUsecase := system2.NewAuthorityUsecase(authorityAccessRepo) authorityService := service.NewAuthorityService(authorityUsecase) authority := handler.NewAuthority(authorityService) menuRepo := system.NewMenuRepo(dataData) - menuUsecase := biz.NewMenuUsecase(menuRepo) + menuUsecase := system2.NewMenuUsecase(menuRepo) menuService := service.NewMenuService(menuUsecase) menu := handler.NewMenu(menuService) - apiUsecase := biz.NewAPIUsecase(apiRepo) + apiUsecase := system2.NewAPIUsecase(apiRepo) apiService := service.NewAPIService(apiUsecase, runtimeSettings) api := handler.NewAPI(apiService) permissionRepo := system.NewPermissionRepo(dataData) - permissionUsecase := biz.NewPermissionUsecase(permissionRepo) + permissionUsecase := system2.NewPermissionUsecase(permissionRepo) permissionService := service.NewPermissionService(permissionUsecase) permission := handler.NewPermission(permissionService) departmentRepo := system.NewDepartmentRepo(dataData) - departmentUsecase := biz.NewDepartmentUsecase(departmentRepo) + departmentUsecase := system2.NewDepartmentUsecase(departmentRepo) departmentService := service.NewDepartmentService(departmentUsecase) positionRepo := system.NewPositionRepo(dataData) - positionUsecase := biz.NewPositionUsecase(positionRepo) + positionUsecase := system2.NewPositionUsecase(positionRepo) positionService := service.NewPositionService(positionUsecase) organization := handler.NewOrganization(departmentService, positionService) announcementRepo := system.NewAnnouncementRepo(dataData) - announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo) + announcementUsecase := system2.NewAnnouncementUsecase(announcementRepo) announcementService := service.NewAnnouncementService(announcementUsecase) announcement := handler.NewAnnouncement(announcementService) emailRepo := email.NewEmailRepo(runtime) - emailUsecase := biz.NewEmailUsecase(emailRepo) + emailUsecase := system2.NewEmailUsecase(emailRepo) emailService := service.NewEmailService(emailUsecase) handlerEmail := handler.NewEmail(emailService) paymentRepo := payment.NewPaymentRepo(dataData) paymentOrderRepo := payment.NewPaymentOrderRepo(dataData) - paymentUsecase := biz.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger) + paymentUsecase := system2.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger) paymentService := service.NewPaymentService(paymentUsecase) handlerPayment := handler.NewPayment(paymentService) taskRepo := system.NewTaskRepo(dataData) registry := app.TaskRegistry(catalog) - taskUsecase := biz.NewTaskUsecaseWithRegistry(taskRepo, registry) + taskUsecase := system2.NewTaskUsecaseWithRegistry(taskRepo, registry) mediaRepo := system.NewMediaRepo(dataData) - mediaUsecase := biz.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings) + mediaUsecase := system2.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings) taskExecutor := worker.NewTaskExecutorWithRegistry(taskUsecase, mediaUsecase, runtime, registry) taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger) taskRuntime := worker.NewTaskRuntime(taskScheduler) - taskApplicationUsecase := biz.NewTaskApplicationUsecase(taskUsecase, taskRuntime) + taskApplicationUsecase := system2.NewTaskApplicationUsecase(taskUsecase, taskRuntime) taskService := service.NewTaskService(taskApplicationUsecase) task := handler.NewTask(taskService) mediaService := service.NewMediaService(mediaUsecase, runtimeSettings) media := handler.NewMedia(mediaService) auditQueryRepo := system.NewAuditRepo(dataData) - auditUsecase := biz.NewAuditUsecase(auditQueryRepo) + auditUsecase := system2.NewAuditUsecase(auditQueryRepo) auditService := service.NewAuditService(auditUsecase) logFileRepo := system.NewLogFileRepo(dataData) - logViewerUsecase := biz.NewLogViewerUsecase(logFileRepo) + logViewerUsecase := system2.NewLogViewerUsecase(logFileRepo) logViewerService := service.NewLogViewerService(logViewerUsecase) audit := handler.NewAudit(auditService, auditRecorder, logViewerService, logger) exportRepo := system.NewExportRepo(dataData) - exportUsecase := biz.NewExportUsecase(exportRepo) - exportService := service.NewExportService(exportUsecase, bizCache) + exportUsecase := system2.NewExportUsecase(exportRepo) + exportService := service.NewExportService(exportUsecase, systemCache) export := handler.NewExport(exportService) versionRepo := system.NewVersionRepo(dataData) - versionUsecase := biz.NewVersionUsecase(versionRepo) + versionUsecase := system2.NewVersionUsecase(versionRepo) versionService := service.NewVersionService(versionUsecase) version := handler.NewVersion(versionService) dictionaryRepo := system.NewDictionaryRepo(dataData) - dictionaryUsecase := biz.NewDictionaryUsecase(dictionaryRepo) + dictionaryUsecase := system2.NewDictionaryUsecase(dictionaryRepo) dictionaryService := service.NewDictionaryService(dictionaryUsecase) dictionary := handler.NewDictionary(dictionaryService) parameterRepo := system.NewParameterRepo(dataData) - parameterUsecase := biz.NewParameterUsecase(parameterRepo) + parameterUsecase := system2.NewParameterUsecase(parameterRepo) parameterService := service.NewParameterService(parameterUsecase) parameter := handler.NewParameter(parameterService) tokenService := service.NewTokenService(tokenUsecase, tokenIssuer) apiToken := handler.NewAPIToken(tokenService) initializationRepo := initialize.NewRepo(dataData, catalog) - systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo, taskRuntime) + systemConfigUsecase := system2.NewSystemConfigUsecase(initializationRepo, taskRuntime) systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtimeSettings) systemConfig := handler.NewSystemConfig(systemConfigService, securityService) public := handler.NewPublic(authService, systemConfigService, securityService) @@ -150,13 +150,14 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger integrationConfigRepo := system.NewIntegrationConfigRepo(dataData) store := data.NewIntegrationRuntime(dataData) connectivityTester := integration.NewConnectivityTester(store) - integrationConfigUsecase := biz.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester) + integrationConfigUsecase := system2.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester) integrationConfigService := service.NewIntegrationConfigService(integrationConfigUsecase) integrationConfig := handler.NewIntegrationConfig(integrationConfigService) v := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, integrationConfig) routes := router.NewRoutes(v) taskMethods := worker.NewTaskMethods(taskUsecase, mediaUsecase, runtime) - moduleRuntime := app.Runtime(routes, taskMethods, registry) + appRuntimeContributions := runtimeContributions(routes, taskMethods) + moduleRuntime := app.Runtime(appRuntimeContributions, registry) websocketServer, cleanup2, err := websocket.New(store) if err != nil { cleanup() diff --git a/docs/system-pkg-audit.md b/docs/system-pkg-audit.md index c0353a3..3fe6c8a 100644 --- a/docs/system-pkg-audit.md +++ b/docs/system-pkg-audit.md @@ -18,7 +18,9 @@ ## system 内部保留边界 -- `app`:组合根,绑定 system 的迁移、菜单、路由和任务贡献。 +- `app`:组合根,汇总各模块的迁移、菜单、路由和任务贡献。 +- `modules/system`:system 模块的 Definition,声明迁移、管理面和默认任务。 +- `modules/payment`:payment 模块的 Definition,声明支付迁移和支付管理面。 - `biz`:用户、权限、菜单、审计、任务、支付订单和系统配置等领域模型与用例。 - `conf`:system 配置 proto、运行时快照和生成代码。 - `data`:数据库连接、PO、仓储、system 表、支付持久化和配置 watcher。 @@ -36,7 +38,8 @@ ```text internal/ - app/ # 组合根和模块定义 + app/ # 组合根和 catalog + modules/ # 业务模块定义及其模块级贡献 biz/ # DO、usecase、repo interface conf/ # 配置 proto/runtime data/ # PO、repo、数据库和迁移 diff --git a/docs/system-structure-audit.md b/docs/system-structure-audit.md index 4063709..1a174ae 100644 --- a/docs/system-structure-audit.md +++ b/docs/system-structure-audit.md @@ -1,6 +1,6 @@ # `internal` 目录结构优化结论 -参考 Go Kratos 的分层方式,顶层保留 `app`、`biz`、`conf`、`data`、 +参考 Go Kratos 的分层方式,顶层保留 `app`、`modules`、`biz`、`conf`、`data`、 `initialize`、`integration`、`security`、`server`、`service`、`worker` 十个 稳定职责。目录不是越少越好:同一技术角色文件较多时,应在所属层下分组,避免 一个目录堆积几十个文件。 @@ -9,7 +9,9 @@ ```text internal/ - app/ # 组合根、模块 catalog 和 definition + app/ # 组合根、模块 catalog 和运行时组合 + modules/system/ # system 模块定义 + modules/payment/ # payment 模块定义 biz/ # DO、usecase、repo interface conf/ # 配置 proto/runtime data/ # PO、repo、数据库和迁移 @@ -38,12 +40,15 @@ internal/ 独立边界时不继续拆分。 - 删除只转发 `pkg/protoutil` 的 `utils/configutil`。 -## `internal/app` 为什么只有两个文件 +## `internal/app` 为什么只保留组合代码 -`app/catalog.go` 和 `app/definition.go` 是有意保留的组合根。`catalog.go` 负责 -组装模块、任务注册与运行时;`definition.go` 声明 system 提供的迁移、管理面 -和默认定时任务。它们依赖多个层,只能位于应用组合边界,不能并入 `biz`、 -`service` 或 `data`。 +`app/catalog.go` 是有意保留的组合根,负责组装模块、任务注册与运行时。 +system 自身的迁移、管理面和默认定时任务位于 +`modules/system/definition.go`,由模块包声明后再被 catalog 汇总。这样模块 +定义不再和应用组合逻辑混在一起,也不能误并入 `biz`、`service` 或 `data`。 + +Catalog 只能自动汇总静态模块贡献;新增模块若提供运行时路由或依赖型任务,仍需 +在 cmd/Wire 中显式注册,直到统一的 runtime contribution 协议落地。 ## 其他目录审查 diff --git a/internal/README.md b/internal/README.md index f33eb44..c53a36a 100644 --- a/internal/README.md +++ b/internal/README.md @@ -3,7 +3,8 @@ 系统模块承载当前管理后台的完整业务边界。`internal` 顶层只保留有明确 生命周期或分层职责的包: -- `app`:组合根、system 模块定义和任务/路由运行时组合 +- `app`:组合根、模块 catalog 和任务/路由运行时组合 +- `modules`:按业务模块维护 Definition 等模块贡献 - `biz`:系统领域对象、用例和仓储接口 - `conf`:基础配置 proto 与运行时配置解析 - `data`:数据库生命周期、系统仓储、系统表和支付持久化 @@ -18,18 +19,19 @@ 目录代表边界,模块文件按资源命名。DTO、handler、中间件、路由和 HTTP 响应工具分别放在独立子包中,避免 `service`/`server` 根目录堆积几十个 -文件,同时不把只有一两个文件的业务逻辑再拆成新包。单文件的 module -定义并入 `app`,JWT 实现集中在 `security`,protobuf JSON 统一使用 +文件,同时不把只有一两个文件的业务逻辑再拆成新包。system 的 module +定义位于 `modules/system`,JWT 实现集中在 `security`,protobuf JSON 统一使用 `pkg/protoutil`。 -`internal/app` 只有 `catalog.go` 和 `definition.go` 是有意保留的组合根: -前者组装模块 catalog、任务注册和运行时,后者声明 system 的迁移、管理面 -和定时任务。它们不是可以下沉到 `service` 或 `data` 的业务文件。 +`internal/app` 只保留 `catalog.go` 作为组合根:它负责组装模块 catalog、任务 +注册和运行时。system 的迁移、管理面和定时任务由 `internal/modules/system` +自己的 `Definition()` 声明,便于后续业务模块独立接入。 系统表统一使用 `sys_` 前缀;业务表应由新业务模块自行命名和迁移,不要混入 本目录。 -system 通过 `Definition()` 提供迁移、支付菜单/API 和默认任务,通过 +system 通过 `modules/system.Definition()` 提供系统迁移、通信集成菜单/API 和默认任务; +payment 通过 `modules/payment.Definition()` 提供支付迁移及支付菜单/API。两者通过 `worker.TaskMethods` 提供依赖系统用例的任务实现,通过 `server/router.Routes` 提供 -路由。应用组合根消费这些公共协议;新增业务不需要修改 system 的初始化、 -worker、路由或数据层。 +路由。静态模块贡献可由 catalog 汇总;带运行时依赖的路由和任务仍需在 cmd/Wire +中显式装配,不应误认为只添加 Definition 就能自动发现。 diff --git a/internal/app/catalog.go b/internal/app/catalog.go index 594305e..f2ca3b5 100644 --- a/internal/app/catalog.go +++ b/internal/app/catalog.go @@ -4,17 +4,21 @@ package app import ( - systemrouter "kra/internal/server/router" - systemworker "kra/internal/worker" + paymentmodule "kra/internal/modules/payment" + systemmodule "kra/internal/modules/system" "kra/pkg/module" platformtask "kra/pkg/task" ) -// Catalog lists the business modules enabled in this binary. Adding an order -// module means adding one Definition here; system initialization and runtime -// code consume the catalog without knowing that module's implementation. +// Catalog lists the business modules enabled in this binary. Static module +// contributions (migrations, admin metadata, default tasks) enter through a +// Definition; runtime routes and dependency-bearing task contributors still +// need explicit wiring below. func Catalog() module.Catalog { - return module.Catalog{Definitions: []module.Definition{Definition()}} + return module.Catalog{Definitions: []module.Definition{ + systemmodule.Definition(), + paymentmodule.Definition(), + }} } // TaskRegistry builds the process-wide registry from dependency-free module @@ -26,8 +30,15 @@ func TaskRegistry(catalog module.Catalog) *platformtask.Registry { return registry } -// Runtime composes HTTP route contributors from the enabled modules. -func Runtime(systemRoutes *systemrouter.Routes, systemTasks *systemworker.TaskMethods, registry *platformtask.Registry) *module.Runtime { - platformtask.Apply(registry, systemTasks) - return module.NewRuntime(systemRoutes) +// RuntimeContributions groups runtime objects that need constructed +// dependencies. The binary composition root supplies the concrete modules. +type RuntimeContributions struct { + Routes []module.RouteRegistrar + Tasks []platformtask.Contributor +} + +// Runtime activates dependency-bearing tasks and composes module routes. +func Runtime(contributions RuntimeContributions, registry *platformtask.Registry) *module.Runtime { + platformtask.Apply(registry, contributions.Tasks...) + return module.NewRuntime(contributions.Routes...) } diff --git a/internal/app/catalog_test.go b/internal/app/catalog_test.go new file mode 100644 index 0000000..7261e6d --- /dev/null +++ b/internal/app/catalog_test.go @@ -0,0 +1,74 @@ +package app + +import ( + "context" + "encoding/json" + "testing" + + "github.com/gin-gonic/gin" + "kra/pkg/module" + platformtask "kra/pkg/task" +) + +func TestCatalogIncludesSystemDefinition(t *testing.T) { + catalog := Catalog() + if len(catalog.Definitions) != 2 { + t.Fatalf("definitions = %d, want 2", len(catalog.Definitions)) + } + if catalog.Definitions[0].Name != "system" || catalog.Definitions[1].Name != "payment" { + t.Fatalf("definition order = [%q, %q], want [system, payment]", catalog.Definitions[0].Name, catalog.Definitions[1].Name) + } + if got := catalog.MigrationSteps(); len(got) != 5 { + t.Fatalf("module migrations = %d, want 5", len(got)) + } + if surface := catalog.Surface(); len(surface.Menus) != 3 || len(surface.APIs) != 15 { + t.Fatalf("admin surface = %d menus/%d APIs, want 3/15", len(surface.Menus), len(surface.APIs)) + } + if got := catalog.DefaultTimedTasks(); len(got) != 2 { + t.Fatalf("default timed tasks = %d, want 2", len(got)) + } +} + +func TestTaskRegistryRegistersStaticModuleMethods(t *testing.T) { + method := platformtask.Method{ + Name: "test.static", + Run: func(context.Context, json.RawMessage) error { return nil }, + } + catalog := module.Catalog{Definitions: []module.Definition{{Tasks: []platformtask.Method{method}}}} + registry := TaskRegistry(catalog) + if _, ok := registry.Lookup(method.Name); !ok { + t.Fatalf("method %q was not registered", method.Name) + } +} + +type testRouteRegistrar struct{ called bool } + +func (registrar *testRouteRegistrar) RegisterRoutes(*gin.RouterGroup, *gin.RouterGroup, *gin.Engine) { + registrar.called = true +} + +type testTaskContributor struct{ name string } + +func (contributor testTaskContributor) RegisterTasks(registry *platformtask.Registry) { + registry.Register(platformtask.Method{ + Name: contributor.name, + Run: func(context.Context, json.RawMessage) error { return nil }, + }) +} + +func TestRuntimeAppliesAllContributions(t *testing.T) { + registry := platformtask.NewRegistry() + route := &testRouteRegistrar{} + runtime := Runtime(RuntimeContributions{ + Routes: []module.RouteRegistrar{route}, + Tasks: []platformtask.Contributor{testTaskContributor{name: "test.runtime"}}, + }, registry) + + if _, ok := registry.Lookup("test.runtime"); !ok { + t.Fatal("runtime task contributor was not applied") + } + runtime.RegisterRoutes(nil, nil, nil) + if !route.called { + t.Fatal("runtime route contributor was not called") + } +} diff --git a/internal/biz/biz.go b/internal/biz/biz.go index a4cbbc1..24515af 100644 --- a/internal/biz/biz.go +++ b/internal/biz/biz.go @@ -1,6 +1,10 @@ package biz -import "github.com/google/wire" +import ( + "kra/internal/biz/system" + + "github.com/google/wire" +) // ProviderSet is biz providers. -var ProviderSet = wire.NewSet(NewUserUsecase, NewAuthenticationUsecase, NewSystemConfigUsecase, NewAuthorityUsecase, NewAPIUsecase, NewPermissionUsecase, NewAccessControlUsecase, NewMenuUsecase, NewDepartmentUsecase, NewPositionUsecase, NewDictionaryUsecase, NewParameterUsecase, NewTokenUsecase, NewSecurityUsecase, NewVersionUsecase, NewExportUsecase, NewAuditUsecase, NewAuditRecorderUsecase, NewLogViewerUsecase, NewTaskUsecaseWithRegistry, NewTaskApplicationUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase, NewPaymentUsecase, NewIntegrationConfigUsecase) +var ProviderSet = wire.NewSet(system.NewUserUsecase, system.NewAuthenticationUsecase, system.NewSystemConfigUsecase, system.NewAuthorityUsecase, system.NewAPIUsecase, system.NewPermissionUsecase, system.NewAccessControlUsecase, system.NewMenuUsecase, system.NewDepartmentUsecase, system.NewPositionUsecase, system.NewDictionaryUsecase, system.NewParameterUsecase, system.NewTokenUsecase, system.NewSecurityUsecase, system.NewVersionUsecase, system.NewExportUsecase, system.NewAuditUsecase, system.NewAuditRecorderUsecase, system.NewLogViewerUsecase, system.NewTaskUsecaseWithRegistry, system.NewTaskApplicationUsecase, system.NewMediaUsecase, system.NewAnnouncementUsecase, system.NewEmailUsecase, system.NewPaymentUsecase, system.NewIntegrationConfigUsecase) diff --git a/internal/biz/access_control.go b/internal/biz/system/access_control.go similarity index 97% rename from internal/biz/access_control.go rename to internal/biz/system/access_control.go index fc88b06..9ce656d 100644 --- a/internal/biz/access_control.go +++ b/internal/biz/system/access_control.go @@ -1,4 +1,4 @@ -package biz +package system import "context" diff --git a/internal/biz/actor.go b/internal/biz/system/actor.go similarity index 96% rename from internal/biz/actor.go rename to internal/biz/system/actor.go index 118a6c5..0ddf910 100644 --- a/internal/biz/actor.go +++ b/internal/biz/system/actor.go @@ -1,4 +1,4 @@ -package biz +package system import "context" diff --git a/internal/biz/announcement.go b/internal/biz/system/announcement.go similarity index 99% rename from internal/biz/announcement.go rename to internal/biz/system/announcement.go index a1e8165..328d410 100644 --- a/internal/biz/announcement.go +++ b/internal/biz/system/announcement.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/api.go b/internal/biz/system/api.go similarity index 99% rename from internal/biz/api.go rename to internal/biz/system/api.go index 310a944..c52202f 100644 --- a/internal/biz/api.go +++ b/internal/biz/system/api.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/api_token.go b/internal/biz/system/api_token.go similarity index 98% rename from internal/biz/api_token.go rename to internal/biz/system/api_token.go index 0f312bd..07f82f1 100644 --- a/internal/biz/api_token.go +++ b/internal/biz/system/api_token.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/audit.go b/internal/biz/system/audit.go similarity index 99% rename from internal/biz/audit.go rename to internal/biz/system/audit.go index 42e835e..7e0d865 100644 --- a/internal/biz/audit.go +++ b/internal/biz/system/audit.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/authentication.go b/internal/biz/system/authentication.go similarity index 99% rename from internal/biz/authentication.go rename to internal/biz/system/authentication.go index 0d44e0c..7fddbe3 100644 --- a/internal/biz/authentication.go +++ b/internal/biz/system/authentication.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/authentication_test.go b/internal/biz/system/authentication_test.go similarity index 99% rename from internal/biz/authentication_test.go rename to internal/biz/system/authentication_test.go index ac0a82a..aaaa35f 100644 --- a/internal/biz/authentication_test.go +++ b/internal/biz/system/authentication_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/authority.go b/internal/biz/system/authority.go similarity index 99% rename from internal/biz/authority.go rename to internal/biz/system/authority.go index 82d7177..81c925a 100644 --- a/internal/biz/authority.go +++ b/internal/biz/system/authority.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/data_scope.go b/internal/biz/system/data_scope.go similarity index 97% rename from internal/biz/data_scope.go rename to internal/biz/system/data_scope.go index 5f803eb..a4d5a93 100644 --- a/internal/biz/data_scope.go +++ b/internal/biz/system/data_scope.go @@ -1,4 +1,4 @@ -package biz +package system import "context" diff --git a/internal/biz/department.go b/internal/biz/system/department.go similarity index 98% rename from internal/biz/department.go rename to internal/biz/system/department.go index 7f94cea..137a53a 100644 --- a/internal/biz/department.go +++ b/internal/biz/system/department.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/dictionary.go b/internal/biz/system/dictionary.go similarity index 99% rename from internal/biz/dictionary.go rename to internal/biz/system/dictionary.go index 40c332d..edcce59 100644 --- a/internal/biz/dictionary.go +++ b/internal/biz/system/dictionary.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/email.go b/internal/biz/system/email.go similarity index 98% rename from internal/biz/email.go rename to internal/biz/system/email.go index e65dd95..f8bd151 100644 --- a/internal/biz/email.go +++ b/internal/biz/system/email.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/errors.go b/internal/biz/system/errors.go similarity index 96% rename from internal/biz/errors.go rename to internal/biz/system/errors.go index 1eb7be4..2bbb461 100644 --- a/internal/biz/errors.go +++ b/internal/biz/system/errors.go @@ -1,4 +1,4 @@ -package biz +package system import ( "github.com/go-kratos/kratos/v3/errors" diff --git a/internal/biz/export.go b/internal/biz/system/export.go similarity index 99% rename from internal/biz/export.go rename to internal/biz/system/export.go index dddf868..6d478b1 100644 --- a/internal/biz/export.go +++ b/internal/biz/system/export.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/infrastructure.go b/internal/biz/system/infrastructure.go similarity index 99% rename from internal/biz/infrastructure.go rename to internal/biz/system/infrastructure.go index 95f940c..6537734 100644 --- a/internal/biz/infrastructure.go +++ b/internal/biz/system/infrastructure.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/integration_config.go b/internal/biz/system/integration_config.go similarity index 99% rename from internal/biz/integration_config.go rename to internal/biz/system/integration_config.go index 7431a5b..f6e5733 100644 --- a/internal/biz/integration_config.go +++ b/internal/biz/system/integration_config.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/integration_config_communication_test.go b/internal/biz/system/integration_config_communication_test.go similarity index 99% rename from internal/biz/integration_config_communication_test.go rename to internal/biz/system/integration_config_communication_test.go index 3ef3643..12ef8ae 100644 --- a/internal/biz/integration_config_communication_test.go +++ b/internal/biz/system/integration_config_communication_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/integration_config_definition.go b/internal/biz/system/integration_config_definition.go similarity index 99% rename from internal/biz/integration_config_definition.go rename to internal/biz/system/integration_config_definition.go index 7d21365..a3fec2f 100644 --- a/internal/biz/integration_config_definition.go +++ b/internal/biz/system/integration_config_definition.go @@ -1,4 +1,4 @@ -package biz +package system func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField { if fieldType == "" { diff --git a/internal/biz/media.go b/internal/biz/system/media.go similarity index 99% rename from internal/biz/media.go rename to internal/biz/system/media.go index 3f714e1..155ffc4 100644 --- a/internal/biz/media.go +++ b/internal/biz/system/media.go @@ -1,4 +1,4 @@ -package biz +package system import ( "bufio" diff --git a/internal/biz/media_metadata.go b/internal/biz/system/media_metadata.go similarity index 98% rename from internal/biz/media_metadata.go rename to internal/biz/system/media_metadata.go index 48ad5d4..e4364dc 100644 --- a/internal/biz/media_metadata.go +++ b/internal/biz/system/media_metadata.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/media_test.go b/internal/biz/system/media_test.go similarity index 99% rename from internal/biz/media_test.go rename to internal/biz/system/media_test.go index e337b1f..b8daef3 100644 --- a/internal/biz/media_test.go +++ b/internal/biz/system/media_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/media_upload.go b/internal/biz/system/media_upload.go similarity index 99% rename from internal/biz/media_upload.go rename to internal/biz/system/media_upload.go index c27da39..d0caff3 100644 --- a/internal/biz/media_upload.go +++ b/internal/biz/system/media_upload.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/menu.go b/internal/biz/system/menu.go similarity index 99% rename from internal/biz/menu.go rename to internal/biz/system/menu.go index 55f57f7..9c904a4 100644 --- a/internal/biz/menu.go +++ b/internal/biz/system/menu.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/pagination.go b/internal/biz/system/pagination.go similarity index 97% rename from internal/biz/pagination.go rename to internal/biz/system/pagination.go index 27bdbc4..9ed3054 100644 --- a/internal/biz/pagination.go +++ b/internal/biz/system/pagination.go @@ -1,4 +1,4 @@ -package biz +package system import ( "go.einride.tech/aip/filtering" diff --git a/internal/biz/parameter.go b/internal/biz/system/parameter.go similarity index 98% rename from internal/biz/parameter.go rename to internal/biz/system/parameter.go index 526ce15..3972541 100644 --- a/internal/biz/parameter.go +++ b/internal/biz/system/parameter.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/payment.go b/internal/biz/system/payment.go similarity index 99% rename from internal/biz/payment.go rename to internal/biz/system/payment.go index a8aa7d8..0508516 100644 --- a/internal/biz/payment.go +++ b/internal/biz/system/payment.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/payment_log.go b/internal/biz/system/payment_log.go similarity index 98% rename from internal/biz/payment_log.go rename to internal/biz/system/payment_log.go index 59f281f..dba6ae6 100644 --- a/internal/biz/payment_log.go +++ b/internal/biz/system/payment_log.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/payment_order.go b/internal/biz/system/payment_order.go similarity index 99% rename from internal/biz/payment_order.go rename to internal/biz/system/payment_order.go index 60a08be..ac7cab0 100644 --- a/internal/biz/payment_order.go +++ b/internal/biz/system/payment_order.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/payment_test.go b/internal/biz/system/payment_test.go similarity index 99% rename from internal/biz/payment_test.go rename to internal/biz/system/payment_test.go index c224993..ef20df7 100644 --- a/internal/biz/payment_test.go +++ b/internal/biz/system/payment_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/permission.go b/internal/biz/system/permission.go similarity index 97% rename from internal/biz/permission.go rename to internal/biz/system/permission.go index 7d83e6e..c418152 100644 --- a/internal/biz/permission.go +++ b/internal/biz/system/permission.go @@ -1,4 +1,4 @@ -package biz +package system import "context" diff --git a/internal/biz/position.go b/internal/biz/system/position.go similarity index 98% rename from internal/biz/position.go rename to internal/biz/system/position.go index 304e8cc..ef010f9 100644 --- a/internal/biz/position.go +++ b/internal/biz/system/position.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/security.go b/internal/biz/system/security.go similarity index 99% rename from internal/biz/security.go rename to internal/biz/system/security.go index 7229a75..b04db8d 100644 --- a/internal/biz/security.go +++ b/internal/biz/system/security.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/security_test.go b/internal/biz/system/security_test.go similarity index 99% rename from internal/biz/security_test.go rename to internal/biz/system/security_test.go index 87dfd0b..b22a797 100644 --- a/internal/biz/security_test.go +++ b/internal/biz/system/security_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/system_init.go b/internal/biz/system/system_init.go similarity index 99% rename from internal/biz/system_init.go rename to internal/biz/system/system_init.go index 7dde125..0d63e3a 100644 --- a/internal/biz/system_init.go +++ b/internal/biz/system/system_init.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/task.go b/internal/biz/system/task.go similarity index 99% rename from internal/biz/task.go rename to internal/biz/system/task.go index d60680a..fbd552c 100644 --- a/internal/biz/task.go +++ b/internal/biz/system/task.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/task_registry.go b/internal/biz/system/task_registry.go similarity index 98% rename from internal/biz/task_registry.go rename to internal/biz/system/task_registry.go index d92e322..93cc46f 100644 --- a/internal/biz/task_registry.go +++ b/internal/biz/system/task_registry.go @@ -1,4 +1,4 @@ -package biz +package system import platformtask "kra/pkg/task" diff --git a/internal/biz/task_test.go b/internal/biz/system/task_test.go similarity index 99% rename from internal/biz/task_test.go rename to internal/biz/system/task_test.go index 8162bf1..d01b5d8 100644 --- a/internal/biz/task_test.go +++ b/internal/biz/system/task_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/upload_session.go b/internal/biz/system/upload_session.go similarity index 98% rename from internal/biz/upload_session.go rename to internal/biz/system/upload_session.go index ed966cf..83f801a 100644 --- a/internal/biz/upload_session.go +++ b/internal/biz/system/upload_session.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/user.go b/internal/biz/system/user.go similarity index 99% rename from internal/biz/user.go rename to internal/biz/system/user.go index 2da723a..46d860e 100644 --- a/internal/biz/user.go +++ b/internal/biz/system/user.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/user_test.go b/internal/biz/system/user_test.go similarity index 98% rename from internal/biz/user_test.go rename to internal/biz/system/user_test.go index ee74a11..d02d678 100644 --- a/internal/biz/user_test.go +++ b/internal/biz/system/user_test.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/biz/version.go b/internal/biz/system/version.go similarity index 99% rename from internal/biz/version.go rename to internal/biz/system/version.go index 59012e8..23c189d 100644 --- a/internal/biz/version.go +++ b/internal/biz/system/version.go @@ -1,4 +1,4 @@ -package biz +package system import ( "context" diff --git a/internal/data/data_scope.go b/internal/data/data_scope.go index 8211723..24bb189 100644 --- a/internal/data/data_scope.go +++ b/internal/data/data_scope.go @@ -3,12 +3,11 @@ package data import ( "database/sql" "errors" + "kra/internal/biz/system" "log/slog" "reflect" "strings" - "kra/internal/biz" - "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" @@ -64,7 +63,7 @@ func skipDataScope(db *gorm.DB) bool { return ok && value } -func validDataScope(scope biz.DataScope) bool { +func validDataScope(scope system.DataScope) bool { return scope.UserID != 0 && scope.AuthorityID != 0 && scope.Scope >= 1 && scope.Scope <= 5 && scope.All == (scope.Scope == 1) } @@ -79,10 +78,10 @@ func applyDataScope(operation string, enqueue dataScopeAuditEnqueue) func(*gorm. if skipDataScope(db) { return } - scope, ok := biz.DataScopeFromContext(db.Statement.Context) + scope, ok := system.DataScopeFromContext(db.Statement.Context) if !ok { slog.WarnContext(db.Statement.Context, "数据权限: 业务表访问无身份上下文, 已拒绝", "mod", "data-scope", "table", db.Statement.Table) - recordDataScopeEvent(db, enqueue, "no_identity", operation, "无身份上下文访问受控表, 已拒绝", biz.DataScope{}) + recordDataScopeEvent(db, enqueue, "no_identity", operation, "无身份上下文访问受控表, 已拒绝", system.DataScope{}) _ = db.AddError(errDataScopeRequired) return } @@ -116,7 +115,7 @@ func applyDataScope(operation string, enqueue dataScopeAuditEnqueue) func(*gorm. } } -func recordDataScopeEvent(db *gorm.DB, enqueue dataScopeAuditEnqueue, eventType, operation, detail string, scope biz.DataScope) { +func recordDataScopeEvent(db *gorm.DB, enqueue dataScopeAuditEnqueue, eventType, operation, detail string, scope system.DataScope) { if enqueue == nil { return } @@ -128,7 +127,7 @@ func auditBlockedWrite(operation string, enqueue dataScopeAuditEnqueue) func(*go if _, applied := db.Statement.Clauses["data_scope:applied"]; !applied || db.Error != nil || db.RowsAffected != 0 { return } - if scope, ok := biz.DataScopeFromContext(db.Statement.Context); ok && !scope.All { + if scope, ok := system.DataScopeFromContext(db.Statement.Context); ok && !scope.All { recordDataScopeEvent(db, enqueue, "blocked_write", operation, "数据范围过滤后写操作影响 0 行(疑似越权尝试)", scope) } } @@ -139,9 +138,9 @@ func stampOwnership(enqueue dataScopeAuditEnqueue) func(*gorm.DB) { if !isControlledTable(db) || skipDataScope(db) { return } - scope, ok := biz.DataScopeFromContext(db.Statement.Context) + scope, ok := system.DataScopeFromContext(db.Statement.Context) if !ok { - recordDataScopeEvent(db, enqueue, "no_identity", "create", "无身份上下文访问受控表, 已拒绝", biz.DataScope{}) + recordDataScopeEvent(db, enqueue, "no_identity", "create", "无身份上下文访问受控表, 已拒绝", system.DataScope{}) _ = db.AddError(errDataScopeRequired) return } @@ -164,7 +163,7 @@ func stampUpdatedBy(db *gorm.DB) { if !isControlledTable(db) || stmt.SkipHooks || !hasScopeField(db, "updated_by") { return } - scope, ok := biz.DataScopeFromContext(db.Statement.Context) + scope, ok := system.DataScopeFromContext(db.Statement.Context) if !ok || scope.UserID == 0 { return } @@ -212,7 +211,7 @@ func stampDeletedBy(db *gorm.DB) { if _, customZero := deletedAt.TagSettings["ZEROVALUE"]; customZero { return } - scope, ok := biz.DataScopeFromContext(stmt.Context) + scope, ok := system.DataScopeFromContext(stmt.Context) if !ok || scope.UserID == 0 { return } diff --git a/internal/data/data_scope_test.go b/internal/data/data_scope_test.go index 56175e7..b9427b3 100644 --- a/internal/data/data_scope_test.go +++ b/internal/data/data_scope_test.go @@ -3,10 +3,9 @@ package data import ( "context" "errors" + "kra/internal/biz/system" "testing" - "kra/internal/biz" - "gorm.io/gorm" ) @@ -53,12 +52,12 @@ func TestDataScopeCallbacksFailClosedAndAllowExplicitSystemBypass(t *testing.T) t.Fatalf("query without data scope error = %v", err) } - invalidCtx := biz.NewDataScopeContext(context.Background(), biz.DataScope{UserID: 7, AuthorityID: 1, Scope: 1, All: false}) + invalidCtx := system.NewDataScopeContext(context.Background(), system.DataScope{UserID: 7, AuthorityID: 1, Scope: 1, All: false}) if err := db.WithContext(invalidCtx).Find(&rows).Error; !errors.Is(err, errInvalidDataScope) { t.Fatalf("query with invalid data scope error = %v", err) } - scopedCtx := biz.NewDataScopeContext(context.Background(), biz.DataScope{UserID: 7, AuthorityID: 1, Scope: 3, DepartmentIDs: []uint{10}}) + scopedCtx := system.NewDataScopeContext(context.Background(), system.DataScope{UserID: 7, AuthorityID: 1, Scope: 3, DepartmentIDs: []uint{10}}) rows = nil if err := db.WithContext(scopedCtx).Order("id").Find(&rows).Error; err != nil { t.Fatal(err) @@ -67,7 +66,7 @@ func TestDataScopeCallbacksFailClosedAndAllowExplicitSystemBypass(t *testing.T) t.Fatalf("scoped query rows = %+v", rows) } - emptyCtx := biz.NewDataScopeContext(context.Background(), biz.DataScope{UserID: 7, AuthorityID: 1, Scope: 5}) + emptyCtx := system.NewDataScopeContext(context.Background(), system.DataScope{UserID: 7, AuthorityID: 1, Scope: 5}) rows = nil if err := db.WithContext(emptyCtx).Find(&rows).Error; err != nil { t.Fatal(err) @@ -91,7 +90,7 @@ func TestDataScopeCreateRequiresIdentityAndStampsOwnership(t *testing.T) { t.Fatalf("create without data scope error = %v", err) } - ctx := biz.NewDataScopeContext(context.Background(), biz.DataScope{UserID: 7, AuthorityID: 1, Scope: 3, PrimaryDeptID: 10, DepartmentIDs: []uint{10}}) + ctx := system.NewDataScopeContext(context.Background(), system.DataScope{UserID: 7, AuthorityID: 1, Scope: 3, PrimaryDeptID: 10, DepartmentIDs: []uint{10}}) created := dataScopeRecord{Name: "owned", DeptID: 999, CreatedBy: 999} if err := db.WithContext(ctx).Create(&created).Error; err != nil { t.Fatal(err) diff --git a/internal/data/initialization_backend.go b/internal/data/initialization_backend.go index cb825ba..de1f7fd 100644 --- a/internal/data/initialization_backend.go +++ b/internal/data/initialization_backend.go @@ -4,8 +4,8 @@ import ( "context" "errors" "fmt" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/conf" "kra/internal/integration/storage" @@ -139,7 +139,7 @@ func (d *Data) IsInitialized(context.Context) (bool, error) { // InitializeDatabase opens and activates the configured database. The callback // is the application-level first-install hook; data owns only lifecycle and // schema migration, while initialize owns system seed orchestration. -func (d *Data) InitializeDatabase(ctx context.Context, input *biz.DatabaseConfig, seed func(context.Context, *gorm.DB) error) error { +func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseConfig, seed func(context.Context, *gorm.DB) error) error { config := &conf.Data_Database{} if current := d.runtime.Data(); current != nil && current.Database != nil { config = proto.Clone(current.Database).(*conf.Data_Database) diff --git a/internal/data/integration_defaults.go b/internal/data/integration_defaults.go index 7f2177d..f000791 100644 --- a/internal/data/integration_defaults.go +++ b/internal/data/integration_defaults.go @@ -3,8 +3,7 @@ package data import ( "encoding/json" "errors" - - "kra/internal/biz" + "kra/internal/biz/system" "gorm.io/gorm" ) @@ -14,15 +13,15 @@ func ensureCommunicationIntegrationConfigs(db *gorm.DB) error { kind string provider string }{ - {kind: biz.IntegrationKindMQ, provider: "emqx"}, - {kind: biz.IntegrationKindMQ, provider: "rabbitmq"}, - {kind: biz.IntegrationKindWebSocket, provider: "melody"}, + {kind: system.IntegrationKindMQ, provider: "emqx"}, + {kind: system.IntegrationKindMQ, provider: "rabbitmq"}, + {kind: system.IntegrationKindWebSocket, provider: "melody"}, } for _, item := range defaults { var row integrationConfigPO err := db.Where("kind = ? AND provider = ?", item.kind, item.provider).First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { - values, marshalErr := json.Marshal(biz.DefaultIntegrationConfig(item.kind, item.provider)) + values, marshalErr := json.Marshal(system.DefaultIntegrationConfig(item.kind, item.provider)) if marshalErr != nil { return marshalErr } diff --git a/internal/data/payment/migrations.go b/internal/data/payment/migrations.go index bf27f09..1982499 100644 --- a/internal/data/payment/migrations.go +++ b/internal/data/payment/migrations.go @@ -2,7 +2,6 @@ package payment import ( "kra/pkg/database/migration" - platformmodule "kra/pkg/module" "gorm.io/gorm" ) @@ -15,27 +14,3 @@ func Migrations() []migration.Step { {ID: "202608200004_payment_defaults", Migrate: ensurePaymentIntegrationConfigs}, } } - -// AdminSurface describes the payment-owned entries shown in the system -// administration UI. The system module persists these records because it owns -// the menu/API/policy tables. -func AdminSurface() platformmodule.Surface { - return platformmodule.Surface{ - Menus: []platformmodule.Menu{ - {Name: "paymentOrders", Path: "paymentOrders", ParentName: "extensions", Component: "view/systemTools/payment/orders.vue", Title: "支付订单", Icon: "wallet", Sort: 6}, - {Name: "paymentConfig", Path: "paymentConfig", ParentName: "extensions", Component: "view/systemTools/payment/config.vue", Title: "支付配置", Icon: "credit-card", Sort: 7}, - }, - APIs: []platformmodule.API{ - {Path: "/payment/orders", Method: "GET", Group: "支付", Description: "分页查询支付订单"}, - {Path: "/payment/order", Method: "POST", Group: "支付", Description: "查询支付订单"}, - {Path: "/payment/orders/:provider/:tradeNo", Method: "GET", Group: "支付", Description: "按路径查询支付订单"}, - {Path: "/payment/create", Method: "POST", Group: "支付", Description: "创建支付订单"}, - {Path: "/payment/query", Method: "POST", Group: "支付", Description: "同步支付订单状态"}, - {Path: "/payment/refund", Method: "POST", Group: "支付", Description: "申请支付订单退款"}, - {Path: "/payment/orders/:provider/:tradeNo/refund", Method: "POST", Group: "支付", Description: "按路径申请支付订单退款"}, - {Path: "/payment/fulfill", Method: "POST", Group: "支付", Description: "重试支付订单发货"}, - {Path: "/payment/orders/:provider/:tradeNo/fulfill", Method: "POST", Group: "支付", Description: "按路径重试支付订单发货"}, - {Path: "/payment/providers/:provider/test", Method: "POST", Group: "支付", Description: "测试支付渠道"}, - }, - } -} diff --git a/internal/data/payment/payment.go b/internal/data/payment/payment.go index 353bab8..2ef89fa 100644 --- a/internal/data/payment/payment.go +++ b/internal/data/payment/payment.go @@ -7,12 +7,12 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "net/url" "strconv" "strings" "time" - "kra/internal/biz" datapayment "kra/internal/integration/payment" "github.com/google/uuid" @@ -21,13 +21,13 @@ import ( type paymentRepo struct{ data Provider } -func NewPaymentRepo(data Provider) biz.PaymentRepo { return &paymentRepo{data: data} } +func NewPaymentRepo(data Provider) system.PaymentRepo { return &paymentRepo{data: data} } func ensurePaymentIntegrationConfigs(db *gorm.DB) error { - for _, provider := range biz.SupportedPaymentProviders { + for _, provider := range system.SupportedPaymentProviders { var row integrationConfigPO err := db.Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error - defaults := biz.DefaultIntegrationConfig(integrationKindPayment, provider) + defaults := system.DefaultIntegrationConfig(integrationKindPayment, provider) if errors.Is(err, gorm.ErrRecordNotFound) { encoded, _ := json.Marshal(defaults) if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: provider, Enabled: false, Config: string(encoded)}).Error; err != nil { @@ -61,7 +61,7 @@ func (r *paymentRepo) row(ctx context.Context, provider string) (*integrationCon var row integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil, biz.ErrPaymentProviderNotFound + return nil, nil, system.ErrPaymentProviderNotFound } return nil, nil, err } @@ -84,11 +84,11 @@ func (r *paymentRepo) adapter(ctx context.Context, provider string) (datapayment return adapter, values, err } -func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.PaymentTestResult, error) { +func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*system.PaymentTestResult, error) { started := time.Now() - test := &biz.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []biz.PaymentTestStage{}} + test := &system.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []system.PaymentTestStage{}} add := func(name, status, message, tradeNo string, since time.Time) { - test.Stages = append(test.Stages, biz.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()}) + test.Stages = append(test.Stages, system.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()}) } values, err := r.testRow(ctx, provider) if err != nil { @@ -97,7 +97,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P } test.Mode = strings.ToLower(strings.TrimSpace(text(values, "environment"))) configStart := time.Now() - if err = biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, provider, values); err != nil { + if err = system.ValidateIntegrationConfig(system.IntegrationKindPayment, provider, values); err != nil { add("config", "failed", err.Error(), "", configStart) return test, err } @@ -117,11 +117,11 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P orders := &paymentOrderRepo{data: r.data} extra, _ := json.Marshal(req.Extra) localStart := time.Now() - order, _, err := orders.CreatePaymentOrder(ctx, &biz.PaymentOrder{ + order, _, err := orders.CreatePaymentOrder(ctx, &system.PaymentOrder{ TradeNo: req.TradeNo, Provider: provider, BusinessType: req.BusinessType, BusinessID: req.BusinessID, - Subject: req.Subject, PaymentMode: biz.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount, - Currency: req.Currency, PaymentStatus: biz.PaymentStatusInitialized, FulfillmentStatus: biz.FulfillmentStatusPending, - RefundStatus: biz.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra, + Subject: req.Subject, PaymentMode: system.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount, + Currency: req.Currency, PaymentStatus: system.PaymentStatusInitialized, FulfillmentStatus: system.FulfillmentStatusPending, + RefundStatus: system.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra, }) if err != nil { add("local_order", "failed", err.Error(), req.TradeNo, localStart) @@ -159,7 +159,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P if queryID == "" { queryID = req.TradeNo } - if provider == biz.PaymentApple { + if provider == system.PaymentApple { queryID = strings.TrimSpace(text(values, "test_transaction_id")) if queryID == "" { err = errors.New("Apple 连通性测试需要配置 test_transaction_id(沙箱交易 ID)") @@ -179,7 +179,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P add("query", "failed", err.Error(), req.TradeNo, queryStart) return test, err } - if provider != biz.PaymentApple { + if provider != system.PaymentApple { if order, err = orders.ApplyPaymentResult(ctx, provider, req.TradeNo, paymentTestProviderUpdate(queried)); err != nil { add("local_order", "failed", "回写测试查单结果失败: "+err.Error(), req.TradeNo, queryStart) return test, err @@ -193,9 +193,9 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P test.Result = queried add("query", "passed", "测试订单查询成功,状态: "+queried.Status, req.TradeNo, queryStart) - if queried.Status != "success" || provider == biz.PaymentApple { + if queried.Status != "success" || provider == system.PaymentApple { message := "订单尚未支付成功,已完成配置、下单和查单连通性测试;请在沙箱完成付款后重试" - if provider == biz.PaymentApple { + if provider == system.PaymentApple { message = "Apple 退款由 App Store 管理,已完成配置、下单和交易查询测试" } add("refund", "skipped", message, req.TradeNo, time.Now()) @@ -208,7 +208,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P add("refund", "failed", beginErr.Error(), req.TradeNo, refundStart) return test, beginErr } - refund, refundErr := adapter.Refund(ctx, &biz.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values) + refund, refundErr := adapter.Refund(ctx, &system.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values) if refundErr != nil { recordPaymentTestError(ctx, r.data, provider, req.TradeNo, refundErr) add("refund", "failed", refundErr.Error(), req.TradeNo, refundStart) @@ -230,17 +230,17 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.P return test, nil } -func paymentTestFingerprint(req *biz.PaymentRequest) string { +func paymentTestFingerprint(req *system.PaymentRequest) string { raw, _ := json.Marshal(req) hash := sha256.Sum256(raw) return hex.EncodeToString(hash[:]) } -func paymentTestProviderUpdate(result *biz.PaymentResult) *biz.PaymentProviderUpdate { +func paymentTestProviderUpdate(result *system.PaymentResult) *system.PaymentProviderUpdate { if result == nil { return nil } - return &biz.PaymentProviderUpdate{ + return &system.PaymentProviderUpdate{ Status: result.Status, ProviderStatus: result.Status, ProviderTradeNo: result.ProviderTradeNo, QueryID: result.QueryID, Amount: result.Amount, PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount, PointPaidAmount: result.PointPaidAmount, DiscountAmount: result.DiscountAmount, @@ -250,21 +250,21 @@ func paymentTestProviderUpdate(result *biz.PaymentResult) *biz.PaymentProviderUp } } -func validatePaymentTestResult(provider, tradeNo string, result *biz.PaymentResult) error { +func validatePaymentTestResult(provider, tradeNo string, result *system.PaymentResult) error { if result == nil { return errors.New("支付渠道响应为空") } if strings.TrimSpace(result.Provider) != provider { return errors.New("支付渠道响应的 provider 不匹配") } - if value := strings.TrimSpace(result.TradeNo); provider != biz.PaymentApple && value != "" && value != tradeNo { + if value := strings.TrimSpace(result.TradeNo); provider != system.PaymentApple && value != "" && value != tradeNo { return errors.New("支付渠道响应的商户订单号不匹配") } return nil } -func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*biz.PaymentResult, error) { - var result *biz.PaymentResult +func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*system.PaymentResult, error) { + var result *system.PaymentResult var err error for attempt := 0; attempt < 3; attempt++ { result, err = adapter.Query(ctx, queryID, values) @@ -300,7 +300,7 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string] var row integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrPaymentProviderNotFound + return nil, system.ErrPaymentProviderNotFound } return nil, err } @@ -311,17 +311,17 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string] return values, nil } -func paymentTestRequest(provider string, values map[string]any) *biz.PaymentRequest { +func paymentTestRequest(provider string, values map[string]any) *system.PaymentRequest { tradeNo := "kra-test-" + time.Now().UTC().Format("20060102150405.000000000") amount := configuredInt64(values, "test_amount", 1) if amount <= 0 { amount = 1 } - req := &biz.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}} + req := &system.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}} if req.Currency == "" { req.Currency = "CNY" } - if provider == biz.PaymentApple { + if provider == system.PaymentApple { req.TradeNo = uuid.NewString() req.Extra["product_id"] = firstAny(values, "product_id", "test_product_id") } @@ -351,10 +351,10 @@ func validatePaymentTestSettings(provider string, values map[string]any) error { return fmt.Errorf("test_extra 必须是 JSON 对象: %w", err) } } - if provider == biz.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" { + if provider == system.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" { return errors.New("Apple 测试需要 test_transaction_id(沙箱交易 ID)") } - if provider == biz.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" { + if provider == system.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" { return errors.New("Apple 测试需要 test_product_id(沙箱商品 ID)") } return nil @@ -377,7 +377,7 @@ func testModeEnabled(values map[string]any) bool { } } -func (r *paymentRepo) Create(ctx context.Context, req *biz.PaymentRequest) (*biz.PaymentResult, error) { +func (r *paymentRepo) Create(ctx context.Context, req *system.PaymentRequest) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付下单请求为空") } @@ -396,7 +396,7 @@ func (r *paymentRepo) Create(ctx context.Context, req *biz.PaymentRequest) (*biz func paymentProviderRequiresNotifyURL(provider string) bool { switch provider { - case biz.PaymentApple, biz.PaymentAllinPay, biz.PaymentSaobei, biz.PaymentPayPal: + case system.PaymentApple, system.PaymentAllinPay, system.PaymentSaobei, system.PaymentPayPal: return false default: return true @@ -413,15 +413,15 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an } keys := []string{"method", "pay_method", "trade_type", "pay_type", "channel"} switch provider { - case biz.PaymentAlipay, biz.PaymentAlipayV3: + case system.PaymentAlipay, system.PaymentAlipayV3: keys = []string{"method", "pay_method", "trade_type", "channel"} - case biz.PaymentWechatV2: + case system.PaymentWechatV2: keys = []string{"trade_type", "pay_type", "method", "pay_method", "channel"} - case biz.PaymentWechatV3: + case system.PaymentWechatV3: keys = []string{"trade_type", "pay_type", "method"} - case biz.PaymentQQ: + case system.PaymentQQ: keys = []string{"trade_type", "pay_type", "method", "pay_method"} - case biz.PaymentLakala: + case system.PaymentLakala: keys = []string{"method", "pay_method", "trade_type"} } value := firstAny(extra, keys...) @@ -430,28 +430,28 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an } normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value))) switch provider { - case biz.PaymentAlipay, biz.PaymentAlipayV3: + case system.PaymentAlipay, system.PaymentAlipayV3: return !contains([]string{"pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face"}, normalized) - case biz.PaymentWechatV2: + case system.PaymentWechatV2: return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay", "pay_code", "payment_code"}, normalized) - case biz.PaymentWechatV3: + case system.PaymentWechatV3: return !contains([]string{"micropay", "micro_pay", "codepay", "code_pay", "barcode", "barcode_pay", "facepay", "face_pay"}, normalized) - case biz.PaymentQQ: + case system.PaymentQQ: return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay"}, normalized) - case biz.PaymentLakala: + case system.PaymentLakala: return !contains([]string{"retail", "retail_pay", "micropay", "barcode"}, normalized) default: return true } } -func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*biz.PaymentResult, error) { +func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*system.PaymentResult, error) { a, c, err := r.adapter(ctx, provider) if err != nil { return nil, err } return a.Query(ctx, tradeNo, c) } -func (r *paymentRepo) Refund(ctx context.Context, req *biz.PaymentRefundRequest) (*biz.PaymentResult, error) { +func (r *paymentRepo) Refund(ctx context.Context, req *system.PaymentRefundRequest) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付退款请求为空") } @@ -461,7 +461,7 @@ func (r *paymentRepo) Refund(ctx context.Context, req *biz.PaymentRefundRequest) } return a.Refund(ctx, req, c) } -func (r *paymentRepo) HandleCallback(ctx context.Context, callback *biz.PaymentCallback) (*biz.PaymentResult, error) { +func (r *paymentRepo) HandleCallback(ctx context.Context, callback *system.PaymentCallback) (*system.PaymentResult, error) { if callback == nil { return nil, errors.New("支付回调为空") } @@ -471,23 +471,23 @@ func (r *paymentRepo) HandleCallback(ctx context.Context, callback *biz.PaymentC } result, err := a.Callback(ctx, callback, c) if err != nil { - return nil, &biz.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} + return nil, &system.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} } if result == nil { err = errors.New("支付回调解析结果为空") - return nil, &biz.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} + return nil, &system.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} } result.SuccessAck = paymentCallbackAck(callback.Provider, c, true) result.FailureAck = paymentCallbackAck(callback.Provider, c, false) if result.Provider != callback.Provider { err = errors.New("支付回调渠道不匹配") - return nil, &biz.PaymentCallbackError{Cause: err, Ack: result.FailureAck} + return nil, &system.PaymentCallbackError{Cause: err, Ack: result.FailureAck} } result.EventID = paymentCallbackEventID(callback, result) return result, nil } -func paymentCallbackEventID(callback *biz.PaymentCallback, result *biz.PaymentResult) string { +func paymentCallbackEventID(callback *system.PaymentCallback, result *system.PaymentResult) string { if result != nil { if eventID := strings.TrimSpace(result.EventID); eventID != "" { return eventID @@ -504,8 +504,8 @@ func paymentCallbackEventID(callback *biz.PaymentCallback, result *biz.PaymentRe return hex.EncodeToString(hash[:]) } -func paymentCallbackAck(provider string, values map[string]any, success bool) biz.PaymentCallbackAck { - ack := biz.DefaultPaymentCallbackAck(provider, success) +func paymentCallbackAck(provider string, values map[string]any, success bool) system.PaymentCallbackAck { + ack := system.DefaultPaymentCallbackAck(provider, success) prefix := "callback_success_" if !success { prefix = "callback_failure_" @@ -524,7 +524,7 @@ func paymentCallbackAck(provider string, values map[string]any, success bool) bi return ack } -func callbackFields(callback *biz.PaymentCallback) map[string]string { +func callbackFields(callback *system.PaymentCallback) map[string]string { fields := map[string]string{} for key, value := range callback.Query { fields[key] = value @@ -569,5 +569,5 @@ func contains(values []string, value string) bool { } func validatePaymentConfig(provider string, values map[string]any) error { - return biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, provider, values) + return system.ValidateIntegrationConfig(system.IntegrationKindPayment, provider, values) } diff --git a/internal/data/payment/payment_config_test.go b/internal/data/payment/payment_config_test.go index b4b34e5..1a6b3c9 100644 --- a/internal/data/payment/payment_config_test.go +++ b/internal/data/payment/payment_config_test.go @@ -1,10 +1,9 @@ package payment import ( + "kra/internal/biz/system" "strings" "testing" - - "kra/internal/biz" ) func TestValidatePaymentConfigRequiresDouyinAppIDWhenEnabled(t *testing.T) { @@ -12,18 +11,18 @@ func TestValidatePaymentConfigRequiresDouyinAppIDWhenEnabled(t *testing.T) { "merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901", "private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_serial_no": "platform-serial", } - err := biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, biz.PaymentDouyin, values) + err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentDouyin, values) if err == nil || !strings.Contains(err.Error(), "app_id") { t.Fatalf("missing app_id error = %v", err) } values["app_id"] = "douyin-app" - if err = biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, biz.PaymentDouyin, values); err != nil { + if err = system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentDouyin, values); err != nil { t.Fatalf("valid Douyin configuration rejected: %v", err) } } func TestValidatePaymentConfigAcceptsProviderAliases(t *testing.T) { - err := validatePaymentConfig(biz.PaymentDouyin, map[string]any{ + err := validatePaymentConfig(system.PaymentDouyin, map[string]any{ "app_id": "douyin-app", "merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901", "private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_cert_serial": "platform-serial", }) @@ -37,13 +36,13 @@ func TestValidatePaymentConfigProviderRules(t *testing.T) { name, provider, want string values map[string]any }{ - {"allinpay order type", biz.PaymentAllinPay, "reqsn", map[string]any{"cus_id": "customer", "app_id": "app", "private_key": "private-key", "public_key": "public-key", "query_order_type": "payinfo"}}, - {"paypal webhook", biz.PaymentPayPal, "webhook_id", map[string]any{"client_id": "client-id", "client_secret": "client-secret"}}, - {"wechat v2 refund cert", biz.PaymentWechatV2, "client_cert", map[string]any{"app_id": "app", "merchant_id": "merchant", "mch_key": "key"}}, + {"allinpay order type", system.PaymentAllinPay, "reqsn", map[string]any{"cus_id": "customer", "app_id": "app", "private_key": "private-key", "public_key": "public-key", "query_order_type": "payinfo"}}, + {"paypal webhook", system.PaymentPayPal, "webhook_id", map[string]any{"client_id": "client-id", "client_secret": "client-secret"}}, + {"wechat v2 refund cert", system.PaymentWechatV2, "client_cert", map[string]any{"app_id": "app", "merchant_id": "merchant", "mch_key": "key"}}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, test.provider, test.values) + err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, test.provider, test.values) if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want %q", err, test.want) } @@ -52,13 +51,13 @@ func TestValidatePaymentConfigProviderRules(t *testing.T) { } func TestValidatePaymentConfigGenericRequiresRuntimeFields(t *testing.T) { - values := biz.DefaultIntegrationConfig(biz.IntegrationKindPayment, biz.PaymentChinaums) - if err := biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, biz.PaymentChinaums, values); err == nil { + values := system.DefaultIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums) + if err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums, values); err == nil { t.Fatal("empty generic payment config unexpectedly accepted") } values["app_id"], values["merchant_id"] = "app", "merchant" values["create_url"], values["query_url"], values["refund_url"], values["app_key"] = "https://pay.test/create", "https://pay.test/query", "https://pay.test/refund", "secret" - if err := biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, biz.PaymentChinaums, values); err == nil { + if err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums, values); err == nil { t.Fatal("generic config with only identity/endpoints unexpectedly accepted") } } diff --git a/internal/data/payment/payment_native_test.go b/internal/data/payment/payment_native_test.go index 2c8e22f..80f64aa 100644 --- a/internal/data/payment/payment_native_test.go +++ b/internal/data/payment/payment_native_test.go @@ -1,16 +1,15 @@ package payment import ( + "kra/internal/biz/system" "strings" "testing" - - "kra/internal/biz" ) func TestPaymentDefinitionsProvideNonEmptyDefaults(t *testing.T) { - definitions := biz.IntegrationDefinitions(biz.IntegrationKindPayment) - if len(definitions) != len(biz.SupportedPaymentProviders) { - t.Fatalf("payment definitions = %d, want %d", len(definitions), len(biz.SupportedPaymentProviders)) + definitions := system.IntegrationDefinitions(system.IntegrationKindPayment) + if len(definitions) != len(system.SupportedPaymentProviders) { + t.Fatalf("payment definitions = %d, want %d", len(definitions), len(system.SupportedPaymentProviders)) } for _, definition := range definitions { if definition.Provider == "" || definition.Name == "" || len(definition.Fields) == 0 { diff --git a/internal/data/payment/payment_order.go b/internal/data/payment/payment_order.go index 776972d..5cbc025 100644 --- a/internal/data/payment/payment_order.go +++ b/internal/data/payment/payment_order.go @@ -4,14 +4,15 @@ import ( "context" "encoding/json" "errors" + "kra/internal/biz/system" "strings" "time" + "kra/pkg/database/pagination" + "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" - "kra/internal/biz" - "kra/pkg/database/pagination" ) type paymentOrderPO struct { @@ -67,11 +68,11 @@ func (paymentOrderPO) TableName() string { return "pay_orders" } type paymentOrderRepo struct{ data Provider } -func NewPaymentOrderRepo(data Provider) biz.PaymentOrderRepo { +func NewPaymentOrderRepo(data Provider) system.PaymentOrderRepo { return &paymentOrderRepo{data: data} } -func newPaymentOrderPO(order *biz.PaymentOrder) (*paymentOrderPO, error) { +func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) { if order == nil { return nil, errors.New("支付订单为空") } @@ -84,16 +85,16 @@ func newPaymentOrderPO(order *biz.PaymentOrder) (*paymentOrderPO, error) { ID: order.ID, TradeNo: order.TradeNo, Provider: order.Provider, ProviderTradeNo: optionalString(order.ProviderTradeNo), QueryID: order.QueryID, BusinessType: order.BusinessType, BusinessID: order.BusinessID, Subject: order.Subject, - PaymentMode: defaultString(order.PaymentMode, biz.PaymentModeExternal), OriginalAmount: order.OriginalAmount, + PaymentMode: defaultString(order.PaymentMode, system.PaymentModeExternal), OriginalAmount: order.OriginalAmount, Amount: order.Amount, PaidAmount: order.PaidAmount, PayerPaidAmount: order.PayerPaidAmount, CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount, DiscountAmount: order.DiscountAmount, ProviderDiscountAmount: order.ProviderDiscountAmount, MerchantDiscountAmount: order.MerchantDiscountAmount, SettlementAmount: order.SettlementAmount, Currency: order.Currency, PayerCurrency: order.PayerCurrency, AmountBreakdownKnown: order.AmountBreakdownKnown, - PaymentStatus: defaultString(order.PaymentStatus, biz.PaymentStatusInitialized), + PaymentStatus: defaultString(order.PaymentStatus, system.PaymentStatusInitialized), ProviderStatus: order.ProviderStatus, - FulfillmentStatus: defaultString(order.FulfillmentStatus, biz.FulfillmentStatusPending), - RefundStatus: defaultString(order.RefundStatus, biz.RefundStatusNone), + FulfillmentStatus: defaultString(order.FulfillmentStatus, system.FulfillmentStatusPending), + RefundStatus: defaultString(order.RefundStatus, system.RefundStatusNone), RefundedAmount: order.RefundedAmount, RefundRequestedAmount: order.RefundRequestedAmount, RefundNo: order.RefundNo, ConfirmationID: order.ConfirmationID, RequestFingerprint: order.RequestFingerprint, CreatePayload: createPayload, Extra: extra, LastEventID: order.LastEventID, @@ -104,15 +105,15 @@ func newPaymentOrderPO(order *biz.PaymentOrder) (*paymentOrderPO, error) { }, nil } -func toBizPaymentOrder(po *paymentOrderPO) *biz.PaymentOrder { +func toBizPaymentOrder(po *paymentOrderPO) *system.PaymentOrder { if po == nil { return nil } - return &biz.PaymentOrder{ + return &system.PaymentOrder{ ID: po.ID, TradeNo: po.TradeNo, Provider: po.Provider, ProviderTradeNo: dereferenceString(po.ProviderTradeNo), QueryID: po.QueryID, BusinessType: po.BusinessType, BusinessID: po.BusinessID, Subject: po.Subject, - PaymentMode: defaultString(po.PaymentMode, biz.PaymentModeExternal), OriginalAmount: po.OriginalAmount, + PaymentMode: defaultString(po.PaymentMode, system.PaymentModeExternal), OriginalAmount: po.OriginalAmount, Amount: po.Amount, PaidAmount: po.PaidAmount, PayerPaidAmount: po.PayerPaidAmount, CashPaidAmount: po.CashPaidAmount, PointPaidAmount: po.PointPaidAmount, DiscountAmount: po.DiscountAmount, ProviderDiscountAmount: po.ProviderDiscountAmount, MerchantDiscountAmount: po.MerchantDiscountAmount, @@ -130,7 +131,7 @@ func toBizPaymentOrder(po *paymentOrderPO) *biz.PaymentOrder { } } -func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *biz.PaymentOrder) (*biz.PaymentOrder, bool, error) { +func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *system.PaymentOrder) (*system.PaymentOrder, bool, error) { po, err := newPaymentOrderPO(order) if err != nil { return nil, false, err @@ -153,18 +154,18 @@ func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *biz.Pa return nil, false, err } -func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*biz.PaymentOrder, error) { +func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*system.PaymentOrder, error) { var po paymentOrderPO if err := r.data.DB().WithContext(ctx).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrPaymentOrderNotFound + return nil, system.ErrPaymentOrderNotFound } return nil, err } return toBizPaymentOrder(&po), nil } -func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter biz.PaymentOrderFilter) ([]*biz.PaymentOrder, int64, error) { +func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter system.PaymentOrderFilter) ([]*system.PaymentOrder, int64, error) { db := r.data.DB().WithContext(ctx).Model(&paymentOrderPO{}) if value := strings.TrimSpace(filter.Provider); value != "" { db = db.Where("provider = ?", value) @@ -192,14 +193,14 @@ func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize if err := pagination.ApplyRequired(db.Order("id desc"), page, pageSize, 100).Find(&rows).Error; err != nil { return nil, 0, err } - items := make([]*biz.PaymentOrder, 0, len(rows)) + items := make([]*system.PaymentOrder, 0, len(rows)) for i := range rows { items = append(items, toBizPaymentOrder(&rows[i])) } return items, total, nil } -func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *biz.PaymentProviderUpdate) (*biz.PaymentOrder, error) { +func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *system.PaymentProviderUpdate) (*system.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { if update == nil { return errors.New("支付下单结果为空") @@ -214,11 +215,11 @@ func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tr po.LastEventID = trimTo(update.EventID, 128) po.LastPayloadHash = trimTo(update.PayloadHash, 64) status := normalizeOrderPaymentStatus(update.Status) - if status == biz.PaymentStatusPaid { + if status == system.PaymentStatusPaid { // Provider create responses are never sufficient proof of payment. - status = biz.PaymentStatusPending + status = system.PaymentStatusPending } - if po.PaymentStatus != biz.PaymentStatusPaid && po.PaymentStatus != biz.PaymentStatusPartiallyRefunded && po.PaymentStatus != biz.PaymentStatusRefunded && status != "" { + if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded && status != "" { po.PaymentStatus = status } po.Version++ @@ -226,7 +227,7 @@ func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tr }) } -func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tradeNo string, update *biz.PaymentProviderUpdate) (*biz.PaymentOrder, error) { +func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tradeNo string, update *system.PaymentProviderUpdate) (*system.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { if update == nil { return errors.New("支付查单结果为空") @@ -235,13 +236,13 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra return err } if update.Amount > 0 && update.Amount != po.Amount { - return biz.ErrPaymentOrderConflict + return system.ErrPaymentOrderConflict } if update.PayerPaidAmount < 0 || update.CashPaidAmount < 0 || update.PointPaidAmount < 0 || update.DiscountAmount < 0 || update.ProviderDiscountAmount < 0 || update.MerchantDiscountAmount < 0 || update.SettlementAmount < 0 { - return biz.ErrPaymentOrderConflict + return system.ErrPaymentOrderConflict } if update.Currency != "" && !strings.EqualFold(update.Currency, po.Currency) { - return biz.ErrPaymentOrderConflict + return system.ErrPaymentOrderConflict } po.ProviderStatus = trimTo(update.ProviderStatus, 64) po.LastEventID = trimTo(update.EventID, 128) @@ -258,22 +259,22 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra po.AmountBreakdownKnown = true } switch normalizeOrderPaymentStatus(update.Status) { - case biz.PaymentStatusPaid: - if po.PaymentStatus != biz.PaymentStatusPartiallyRefunded && po.PaymentStatus != biz.PaymentStatusRefunded { - po.PaymentStatus = biz.PaymentStatusPaid + case system.PaymentStatusPaid: + if po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded { + po.PaymentStatus = system.PaymentStatusPaid } po.PaidAmount = po.Amount if po.PaidAt == nil { now := time.Now().UTC() po.PaidAt = &now } - case biz.PaymentStatusPending: - if po.PaymentStatus != biz.PaymentStatusPaid && po.PaymentStatus != biz.PaymentStatusPartiallyRefunded && po.PaymentStatus != biz.PaymentStatusRefunded { - po.PaymentStatus = biz.PaymentStatusPending + case system.PaymentStatusPending: + if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded { + po.PaymentStatus = system.PaymentStatusPending } - case biz.PaymentStatusFailed: - if po.PaymentStatus != biz.PaymentStatusPaid && po.PaymentStatus != biz.PaymentStatusPartiallyRefunded && po.PaymentStatus != biz.PaymentStatusRefunded { - po.PaymentStatus = biz.PaymentStatusFailed + case system.PaymentStatusFailed: + if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded { + po.PaymentStatus = system.PaymentStatusFailed } } po.Version++ @@ -281,27 +282,27 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra }) } -func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider, tradeNo string, lease time.Duration) (*biz.PaymentOrder, string, bool, error) { +func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider, tradeNo string, lease time.Duration) (*system.PaymentOrder, string, bool, error) { var token string var duplicate bool order, err := r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.FulfillmentStatus == biz.FulfillmentStatusSucceeded { + if po.FulfillmentStatus == system.FulfillmentStatusSucceeded { duplicate = true return nil } - if po.PaymentStatus != biz.PaymentStatusPaid { - return biz.ErrPaymentOrderState + if po.PaymentStatus != system.PaymentStatusPaid { + return system.ErrPaymentOrderState } now := time.Now().UTC() - if po.FulfillmentStatus == biz.FulfillmentStatusProcessing && po.FulfillmentLeaseUntil != nil && po.FulfillmentLeaseUntil.After(now) { - return biz.ErrPaymentOrderBusy + if po.FulfillmentStatus == system.FulfillmentStatusProcessing && po.FulfillmentLeaseUntil != nil && po.FulfillmentLeaseUntil.After(now) { + return system.ErrPaymentOrderBusy } if lease <= 0 { lease = 10 * time.Minute } token = uuid.NewString() until := now.Add(lease) - po.FulfillmentStatus = biz.FulfillmentStatusProcessing + po.FulfillmentStatus = system.FulfillmentStatusProcessing po.FulfillmentToken = token po.FulfillmentLeaseUntil = &until po.LastError = "" @@ -311,56 +312,56 @@ func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider return order, token, duplicate, err } -func (r *paymentOrderRepo) CompletePaymentFulfillment(ctx context.Context, provider, tradeNo, token string, success bool, message string) (*biz.PaymentOrder, error) { +func (r *paymentOrderRepo) CompletePaymentFulfillment(ctx context.Context, provider, tradeNo, token string, success bool, message string) (*system.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.FulfillmentStatus != biz.FulfillmentStatusProcessing || po.FulfillmentToken != token { - return biz.ErrPaymentOrderBusy + if po.FulfillmentStatus != system.FulfillmentStatusProcessing || po.FulfillmentToken != token { + return system.ErrPaymentOrderBusy } po.FulfillmentToken = "" po.FulfillmentLeaseUntil = nil po.LastError = trimTo(message, 512) if success { - po.FulfillmentStatus = biz.FulfillmentStatusSucceeded + po.FulfillmentStatus = system.FulfillmentStatusSucceeded now := time.Now().UTC() po.FulfilledAt = &now } else { - po.FulfillmentStatus = biz.FulfillmentStatusFailed + po.FulfillmentStatus = system.FulfillmentStatusFailed } po.Version++ return tx.Save(po).Error }) } -func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tradeNo string, amount int64, lease time.Duration) (*biz.PaymentOrder, string, error) { +func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tradeNo string, amount int64, lease time.Duration) (*system.PaymentOrder, string, error) { var token string order, err := r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.PaymentStatus != biz.PaymentStatusPaid && po.PaymentStatus != biz.PaymentStatusPartiallyRefunded { - return biz.ErrPaymentOrderState + if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded { + return system.ErrPaymentOrderState } if amount <= 0 { - return biz.ErrPaymentOrderConflict + return system.ErrPaymentOrderConflict } now := time.Now().UTC() - if po.RefundStatus == biz.RefundStatusProcessing && po.RefundLeaseUntil != nil && po.RefundLeaseUntil.After(now) { - return biz.ErrPaymentOrderBusy + if po.RefundStatus == system.RefundStatusProcessing && po.RefundLeaseUntil != nil && po.RefundLeaseUntil.After(now) { + return system.ErrPaymentOrderBusy } - if po.RefundStatus == biz.RefundStatusProcessing && po.RefundRequestedAmount != amount { - return biz.ErrPaymentOrderConflict + if po.RefundStatus == system.RefundStatusProcessing && po.RefundRequestedAmount != amount { + return system.ErrPaymentOrderConflict } - if po.RefundStatus == biz.RefundStatusPending { - return biz.ErrPaymentOrderBusy + if po.RefundStatus == system.RefundStatusPending { + return system.ErrPaymentOrderBusy } // An expired processing lease means the provider outcome is unknown. // Retry the same refund amount with the same durable refund number. A // different amount must never reuse that operation identity. reserved := int64(0) - if po.RefundStatus == biz.RefundStatusProcessing && po.RefundLeaseUntil != nil && !po.RefundLeaseUntil.After(now) { + if po.RefundStatus == system.RefundStatusProcessing && po.RefundLeaseUntil != nil && !po.RefundLeaseUntil.After(now) { reserved = 0 } else { reserved = po.RefundRequestedAmount } if amount > po.Amount-po.RefundedAmount-reserved { - return biz.ErrPaymentOrderConflict + return system.ErrPaymentOrderConflict } if lease <= 0 { lease = 10 * time.Minute @@ -370,7 +371,7 @@ func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tra po.RefundNo = uuid.NewString() } until := now.Add(lease) - po.RefundStatus = biz.RefundStatusProcessing + po.RefundStatus = system.RefundStatusProcessing po.RefundRequestedAmount = amount po.RefundToken = token po.RefundLeaseUntil = &until @@ -381,18 +382,18 @@ func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tra return order, token, err } -func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, provider, tradeNo, token string, accepted bool, message string) (*biz.PaymentOrder, error) { +func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, provider, tradeNo, token string, accepted bool, message string) (*system.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.RefundStatus != biz.RefundStatusProcessing || po.RefundToken != token { - return biz.ErrPaymentOrderBusy + if po.RefundStatus != system.RefundStatusProcessing || po.RefundToken != token { + return system.ErrPaymentOrderBusy } po.RefundToken = "" po.RefundLeaseUntil = nil po.LastError = trimTo(message, 512) if accepted { - po.RefundStatus = biz.RefundStatusPending + po.RefundStatus = system.RefundStatusPending } else { - po.RefundStatus = biz.RefundStatusFailed + po.RefundStatus = system.RefundStatusFailed po.RefundRequestedAmount = 0 po.RefundNo = "" } @@ -401,26 +402,26 @@ func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, pro }) } -func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, tradeNo, refundNo string, amount int64, success bool, message string) (*biz.PaymentOrder, error) { +func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, tradeNo, refundNo string, amount int64, success bool, message string) (*system.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.RefundStatus != biz.RefundStatusPending || po.RefundNo == "" || po.RefundNo != refundNo || po.RefundRequestedAmount != amount { - return biz.ErrPaymentOrderState + if po.RefundStatus != system.RefundStatusPending || po.RefundNo == "" || po.RefundNo != refundNo || po.RefundRequestedAmount != amount { + return system.ErrPaymentOrderState } po.LastError = trimTo(message, 512) po.RefundRequestedAmount = 0 if !success { - po.RefundStatus = biz.RefundStatusFailed + po.RefundStatus = system.RefundStatusFailed po.RefundNo = "" po.Version++ return tx.Save(po).Error } po.RefundedAmount += amount if po.RefundedAmount >= po.Amount { - po.PaymentStatus = biz.PaymentStatusRefunded - po.RefundStatus = biz.RefundStatusSucceeded + po.PaymentStatus = system.PaymentStatusRefunded + po.RefundStatus = system.RefundStatusSucceeded } else { - po.PaymentStatus = biz.PaymentStatusPartiallyRefunded - po.RefundStatus = biz.RefundStatusPartial + po.PaymentStatus = system.PaymentStatusPartiallyRefunded + po.RefundStatus = system.RefundStatusPartial } po.RefundNo = "" now := time.Now().UTC() @@ -430,13 +431,13 @@ func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, t }) } -func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*biz.PaymentOrder, error) { - var result *biz.PaymentOrder +func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*system.PaymentOrder, error) { + var result *system.PaymentOrder err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var po paymentOrderPO if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return biz.ErrPaymentOrderNotFound + return system.ErrPaymentOrderNotFound } return err } @@ -449,16 +450,16 @@ func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeN return result, err } -func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *biz.PaymentProviderUpdate) error { +func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.PaymentProviderUpdate) error { if update.ProviderTradeNo != "" { if po.ProviderTradeNo != nil && *po.ProviderTradeNo != update.ProviderTradeNo { - return biz.ErrPaymentProviderConflict + return system.ErrPaymentProviderConflict } var other paymentOrderPO // This check is repeated under the order transaction so a platform // transaction cannot be attached to a different merchant order. if err := tx.Where("provider = ? AND provider_trade_no = ? AND id <> ?", po.Provider, update.ProviderTradeNo, po.ID).First(&other).Error; err == nil { - return biz.ErrPaymentProviderConflict + return system.ErrPaymentProviderConflict } else if !errors.Is(err, gorm.ErrRecordNotFound) { return err } @@ -467,7 +468,7 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *biz.PaymentP } if update.QueryID != "" { if po.QueryID != "" && po.QueryID != update.QueryID { - return biz.ErrPaymentProviderConflict + return system.ErrPaymentProviderConflict } po.QueryID = update.QueryID } @@ -477,11 +478,11 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *biz.PaymentP func normalizeOrderPaymentStatus(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case "success", "paid", "fulfilled": - return biz.PaymentStatusPaid + return system.PaymentStatusPaid case "pending", "created", "client_pending", "processing": - return biz.PaymentStatusPending + return system.PaymentStatusPending case "failed", "closed", "cancelled", "canceled": - return biz.PaymentStatusFailed + return system.PaymentStatusFailed default: return "" } diff --git a/internal/data/payment/payment_order_test.go b/internal/data/payment/payment_order_test.go index af5ced5..bfb2827 100644 --- a/internal/data/payment/payment_order_test.go +++ b/internal/data/payment/payment_order_test.go @@ -2,10 +2,9 @@ package payment import ( "context" + "kra/internal/biz/system" "testing" "time" - - "kra/internal/biz" ) func newPaymentOrderRepoForTest(t *testing.T) *paymentOrderRepo { @@ -20,11 +19,11 @@ func newPaymentOrderRepoForTest(t *testing.T) *paymentOrderRepo { return &paymentOrderRepo{data: &Data{gormDB: newReloadableDB(db, nil)}} } -func testPaymentOrder() *biz.PaymentOrder { - return &biz.PaymentOrder{ - TradeNo: "order-1", Provider: biz.PaymentAlipay, BusinessType: "game_item", BusinessID: "item-1", - Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: biz.PaymentStatusInitialized, - FulfillmentStatus: biz.FulfillmentStatusPending, RefundStatus: biz.RefundStatusNone, +func testPaymentOrder() *system.PaymentOrder { + return &system.PaymentOrder{ + TradeNo: "order-1", Provider: system.PaymentAlipay, BusinessType: "game_item", BusinessID: "item-1", + Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: system.PaymentStatusInitialized, + FulfillmentStatus: system.FulfillmentStatusPending, RefundStatus: system.RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111", RequestFingerprint: "fingerprint", } } @@ -36,36 +35,36 @@ func TestPaymentOrderRepositoryPersistsPaymentFulfillmentAndRefundState(t *testi if err != nil || !created { t.Fatalf("create order = %#v created=%v err=%v", order, created, err) } - update := &biz.PaymentProviderUpdate{Status: "success", ProviderStatus: "TRADE_SUCCESS", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY", EventID: "event-1"} - order, err = repo.ApplyPaymentResult(ctx, biz.PaymentAlipay, "order-1", update) - if err != nil || order.PaymentStatus != biz.PaymentStatusPaid || order.PaidAmount != 100 { + update := &system.PaymentProviderUpdate{Status: "success", ProviderStatus: "TRADE_SUCCESS", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY", EventID: "event-1"} + order, err = repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-1", update) + if err != nil || order.PaymentStatus != system.PaymentStatusPaid || order.PaidAmount != 100 { t.Fatalf("apply payment = %#v err=%v", order, err) } - order, token, duplicate, err := repo.BeginPaymentFulfillment(ctx, biz.PaymentAlipay, "order-1", time.Minute) - if err != nil || duplicate || token == "" || order.FulfillmentStatus != biz.FulfillmentStatusProcessing { + order, token, duplicate, err := repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute) + if err != nil || duplicate || token == "" || order.FulfillmentStatus != system.FulfillmentStatusProcessing { t.Fatalf("begin fulfillment = %#v token=%q duplicate=%v err=%v", order, token, duplicate, err) } - if _, _, _, err = repo.BeginPaymentFulfillment(ctx, biz.PaymentAlipay, "order-1", time.Minute); err == nil { + if _, _, _, err = repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute); err == nil { t.Fatal("concurrent fulfillment was accepted") } - order, err = repo.CompletePaymentFulfillment(ctx, biz.PaymentAlipay, "order-1", token, true, "") - if err != nil || order.FulfillmentStatus != biz.FulfillmentStatusSucceeded { + order, err = repo.CompletePaymentFulfillment(ctx, system.PaymentAlipay, "order-1", token, true, "") + if err != nil || order.FulfillmentStatus != system.FulfillmentStatusSucceeded { t.Fatalf("complete fulfillment = %#v err=%v", order, err) } - _, _, duplicate, err = repo.BeginPaymentFulfillment(ctx, biz.PaymentAlipay, "order-1", time.Minute) + _, _, duplicate, err = repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute) if err != nil || !duplicate { t.Fatalf("duplicate fulfillment = duplicate=%v err=%v", duplicate, err) } - order, token, err = repo.BeginPaymentRefund(ctx, biz.PaymentAlipay, "order-1", 40, time.Minute) - if err != nil || token == "" || order.RefundStatus != biz.RefundStatusProcessing { + order, token, err = repo.BeginPaymentRefund(ctx, system.PaymentAlipay, "order-1", 40, time.Minute) + if err != nil || token == "" || order.RefundStatus != system.RefundStatusProcessing { t.Fatalf("begin refund = %#v token=%q err=%v", order, token, err) } - order, err = repo.CompletePaymentRefundRequest(ctx, biz.PaymentAlipay, "order-1", token, true, "") - if err != nil || order.RefundStatus != biz.RefundStatusPending || order.RefundRequestedAmount != 40 { + order, err = repo.CompletePaymentRefundRequest(ctx, system.PaymentAlipay, "order-1", token, true, "") + if err != nil || order.RefundStatus != system.RefundStatusPending || order.RefundRequestedAmount != 40 { t.Fatalf("accept refund = %#v err=%v", order, err) } - order, err = repo.ConfirmPaymentRefund(ctx, biz.PaymentAlipay, "order-1", order.RefundNo, 40, true, "") - if err != nil || order.RefundedAmount != 40 || order.PaymentStatus != biz.PaymentStatusPartiallyRefunded { + order, err = repo.ConfirmPaymentRefund(ctx, system.PaymentAlipay, "order-1", order.RefundNo, 40, true, "") + if err != nil || order.RefundedAmount != 40 || order.PaymentStatus != system.PaymentStatusPartiallyRefunded { t.Fatalf("confirm refund = %#v err=%v", order, err) } } @@ -83,11 +82,11 @@ func TestPaymentOrderRepositoryRejectsProviderTradeReuse(t *testing.T) { if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil { t.Fatal(err) } - update := &biz.PaymentProviderUpdate{Status: "success", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY"} - if _, err := repo.ApplyPaymentResult(ctx, biz.PaymentAlipay, "order-1", update); err != nil { + update := &system.PaymentProviderUpdate{Status: "success", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY"} + if _, err := repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-1", update); err != nil { t.Fatal(err) } - if _, err := repo.ApplyPaymentResult(ctx, biz.PaymentAlipay, "order-2", update); err != biz.ErrPaymentProviderConflict { + if _, err := repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-2", update); err != system.ErrPaymentProviderConflict { t.Fatalf("provider trade reuse err = %v", err) } } @@ -101,13 +100,13 @@ func TestPaymentOrderRepositoryListsWithFilters(t *testing.T) { } second := testPaymentOrder() second.TradeNo = "wechat-order-2" - second.Provider = biz.PaymentWechatV3 + second.Provider = system.PaymentWechatV3 second.BusinessID = "item-2" second.ConfirmationID = "33333333-3333-3333-3333-333333333333" if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil { t.Fatal(err) } - items, total, err := repo.ListPaymentOrders(ctx, 1, 10, biz.PaymentOrderFilter{Provider: biz.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"}) + items, total, err := repo.ListPaymentOrders(ctx, 1, 10, system.PaymentOrderFilter{Provider: system.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"}) if err != nil { t.Fatal(err) } diff --git a/internal/data/repository/announcement.go b/internal/data/repository/announcement.go index 41f09af..6e330ac 100644 --- a/internal/data/repository/announcement.go +++ b/internal/data/repository/announcement.go @@ -3,9 +3,9 @@ package system import ( "context" "encoding/json" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/gormkit" "kra/pkg/database/pagination" @@ -27,17 +27,17 @@ func (announcementPO) TableName() string { return "sys_announcements" } type announcementRepo struct{ data Provider } -func NewAnnouncementRepo(data Provider) biz.AnnouncementRepo { return &announcementRepo{data: data} } +func NewAnnouncementRepo(data Provider) system.AnnouncementRepo { return &announcementRepo{data: data} } -func newAnnouncement(item *biz.Announcement) announcementPO { +func newAnnouncement(item *system.Announcement) announcementPO { return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: gormkit.JSON(item.Attachments)} } -func announcementToBiz(item announcementPO) *biz.Announcement { - return &biz.Announcement{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} +func announcementToBiz(item announcementPO) *system.Announcement { + return &system.Announcement{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} } -func (r *announcementRepo) Create(ctx context.Context, item *biz.Announcement) error { +func (r *announcementRepo) Create(ctx context.Context, item *system.Announcement) error { po := newAnnouncement(item) if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -54,12 +54,12 @@ func (r *announcementRepo) DeleteByIDs(ctx context.Context, ids []string) error return r.data.DB().WithContext(ctx).Delete(&[]announcementPO{}, "id IN ?", ids).Error } -func (r *announcementRepo) Update(ctx context.Context, item *biz.Announcement) error { +func (r *announcementRepo) Update(ctx context.Context, item *system.Announcement) error { po := newAnnouncement(item) return r.data.DB().WithContext(ctx).Model(&announcementPO{}).Where("id = ?", item.ID).Updates(&po).Error } -func (r *announcementRepo) Find(ctx context.Context, id string) (*biz.Announcement, error) { +func (r *announcementRepo) Find(ctx context.Context, id string) (*system.Announcement, error) { var po announcementPO if err := r.data.DB().WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil { return nil, err @@ -67,7 +67,7 @@ func (r *announcementRepo) Find(ctx context.Context, id string) (*biz.Announceme return announcementToBiz(po), nil } -func (r *announcementRepo) List(ctx context.Context, filter biz.AnnouncementFilter) ([]*biz.Announcement, int64, error) { +func (r *announcementRepo) List(ctx context.Context, filter system.AnnouncementFilter) ([]*system.Announcement, int64, error) { db := r.data.DB().WithContext(ctx).Model(&announcementPO{}) if filter.StartCreatedAt != nil && filter.EndCreatedAt != nil { db = db.Where("created_at BETWEEN ? AND ?", filter.StartCreatedAt, filter.EndCreatedAt) @@ -83,14 +83,14 @@ func (r *announcementRepo) List(ctx context.Context, filter biz.AnnouncementFilt if err := db.Find(&pos).Error; err != nil { return nil, 0, err } - items := make([]*biz.Announcement, 0, len(pos)) + items := make([]*system.Announcement, 0, len(pos)) for _, po := range pos { items = append(items, announcementToBiz(po)) } return items, total, nil } -func (r *announcementRepo) UserOptions(ctx context.Context) ([]biz.UserOption, error) { +func (r *announcementRepo) UserOptions(ctx context.Context) ([]system.UserOption, error) { var rows []struct { Label string Value uint @@ -98,9 +98,9 @@ func (r *announcementRepo) UserOptions(ctx context.Context) ([]biz.UserOption, e // The generated data-source endpoint is best effort: return collected // options even when the underlying scan reports an error. _ = r.data.DB().WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Scan(&rows).Error - items := make([]biz.UserOption, 0, len(rows)) + items := make([]system.UserOption, 0, len(rows)) for _, row := range rows { - items = append(items, biz.UserOption{Label: row.Label, Value: row.Value}) + items = append(items, system.UserOption{Label: row.Label, Value: row.Value}) } return items, nil } diff --git a/internal/data/repository/announcement_test.go b/internal/data/repository/announcement_test.go index 1623352..7bdee5e 100644 --- a/internal/data/repository/announcement_test.go +++ b/internal/data/repository/announcement_test.go @@ -2,16 +2,15 @@ package system import ( "context" + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func TestAnnouncementRepositoryKeepsRawIDQuerySemantics(t *testing.T) { data := newTransactionTestData(t) repo := &announcementRepo{data: data} ctx := context.Background() - item := &biz.Announcement{Title: "notice"} + item := &system.Announcement{Title: "notice"} if err := repo.Create(ctx, item); err != nil { t.Fatal(err) } @@ -44,7 +43,7 @@ func TestAnnouncementRepositoryPreservesSignedUserID(t *testing.T) { repo := &announcementRepo{data: data} ctx := context.Background() userID := -1 - item := &biz.Announcement{Title: "notice", UserID: &userID} + item := &system.Announcement{Title: "notice", UserID: &userID} if err := repo.Create(ctx, item); err != nil { t.Fatal(err) } diff --git a/internal/data/repository/api.go b/internal/data/repository/api.go index b598cfc..073d459 100644 --- a/internal/data/repository/api.go +++ b/internal/data/repository/api.go @@ -3,10 +3,10 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "strconv" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -14,7 +14,7 @@ import ( type apiRepo struct{ data Provider } -func NewAPIRepo(data Provider) biz.APIRepo { return &apiRepo{data: data} } +func NewAPIRepo(data Provider) system.APIRepo { return &apiRepo{data: data} } type apiPO struct { ID uint `gorm:"primaryKey"` @@ -47,10 +47,10 @@ type authorityAPIPO struct { func (authorityAPIPO) TableName() string { return "sys_authority_apis" } -func apiFromPO(po apiPO) *biz.API { - return &biz.API{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Path: po.Path, Description: po.Description, APIGroup: po.APIGroup, Method: po.Method} +func apiFromPO(po apiPO) *system.API { + return &system.API{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Path: po.Path, Description: po.Description, APIGroup: po.APIGroup, Method: po.Method} } -func (r *apiRepo) CreateAPI(ctx context.Context, v *biz.API) error { +func (r *apiRepo) CreateAPI(ctx context.Context, v *system.API) error { po := apiPO{Path: v.Path, Description: v.Description, APIGroup: v.APIGroup, Method: v.Method} var count int64 if err := r.data.DB().WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", po.Path, po.Method).Count(&count).Error; err != nil { @@ -65,7 +65,7 @@ func (r *apiRepo) CreateAPI(ctx context.Context, v *biz.API) error { v.ID, v.CreatedAt, v.UpdatedAt, v.Method = po.ID, po.CreatedAt, po.UpdatedAt, po.Method return nil } -func (r *apiRepo) UpdateAPI(ctx context.Context, v *biz.API) error { +func (r *apiRepo) UpdateAPI(ctx context.Context, v *system.API) error { db := r.data.DB().WithContext(ctx) var old apiPO if err := db.First(&old, v.ID).Error; err != nil { @@ -109,18 +109,18 @@ func (r *apiRepo) DeleteAPIs(ctx context.Context, ids []uint) error { return tx.Delete(&apiPO{}, ids).Error }) } -func (r *apiRepo) FindAPI(ctx context.Context, id uint) (*biz.API, error) { +func (r *apiRepo) FindAPI(ctx context.Context, id uint) (*system.API, error) { var po apiPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return apiFromPO(po), nil } -func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]*biz.API, int64, error) { +func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *system.API) ([]*system.API, int64, error) { db := r.data.DB().WithContext(ctx).Model(&apiPO{}) if q != nil && q.StrictAll { config := r.data.Runtime().Admin() - if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth { + if actor, ok := system.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth { var authority authorityPO if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil { return nil, 0, err @@ -173,7 +173,7 @@ func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]* if err := query.Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.API, 0, len(pos)) + out := make([]*system.API, 0, len(pos)) for _, po := range pos { out = append(out, apiFromPO(po)) } diff --git a/internal/data/repository/api_policy.go b/internal/data/repository/api_policy.go index 95a4eb8..c6451ba 100644 --- a/internal/data/repository/api_policy.go +++ b/internal/data/repository/api_policy.go @@ -3,10 +3,9 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "strconv" - "kra/internal/biz" - "github.com/casbin/casbin/v3" casbinmodel "github.com/casbin/casbin/v3/model" "gorm.io/gorm" @@ -43,7 +42,7 @@ func (r *apiRepo) SetAPIRoles(ctx context.Context, path, method string, ids []ui return errors.New("您提交的角色ID不合法") } } - if err := r.checkPolicyPathsAuth(ctx, []*biz.API{{Path: path, Method: method}}); err != nil { + if err := r.checkPolicyPathsAuth(ctx, []*system.API{{Path: path, Method: method}}); err != nil { return err } } @@ -105,21 +104,21 @@ m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && r.act == p.act`) } return enforcer.Enforce(subject, path, method) } -func (r *apiRepo) PolicyPaths(ctx context.Context, aid uint) ([]*biz.API, error) { +func (r *apiRepo) PolicyPaths(ctx context.Context, aid uint) ([]*system.API, error) { rows, err := policyRowsForAuthority(r.data.DB().WithContext(ctx), aid) if err != nil { return nil, err } - var out []*biz.API + var out []*system.API if len(rows) > 0 { - out = make([]*biz.API, 0, len(rows)) + out = make([]*system.API, 0, len(rows)) } for _, row := range rows { - out = append(out, &biz.API{Path: row.V1, Method: row.V2}) + out = append(out, &system.API{Path: row.V1, Method: row.V2}) } return out, nil } -func (r *apiRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.API) error { +func (r *apiRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*system.API) error { if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, aid); err != nil { return err } @@ -155,7 +154,7 @@ func (r *apiRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.API return nil } -func (r *apiRepo) checkPolicyPathsAuth(ctx context.Context, paths []*biz.API) error { +func (r *apiRepo) checkPolicyPathsAuth(ctx context.Context, paths []*system.API) error { actor, _, strict, err := (&authorityAccessRepo{data: r.data}).strictAuthorityAccess(ctx) if err != nil || !strict { return err diff --git a/internal/data/repository/api_policy_test.go b/internal/data/repository/api_policy_test.go index 2d87813..5d3c8c2 100644 --- a/internal/data/repository/api_policy_test.go +++ b/internal/data/repository/api_policy_test.go @@ -2,10 +2,10 @@ package system import ( "context" + "kra/internal/biz/system" "strings" "testing" - "kra/internal/biz" "kra/internal/conf" ) @@ -33,7 +33,7 @@ func apiPolicyUintPointer(value uint) *uint { return &value } func TestSetPolicyPathsStrictRootRequiresRegisteredAPI(t *testing.T) { data := newPolicyTestData(t) - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 888}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: 888}) if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{ {AuthorityID: 888, ParentID: apiPolicyUintPointer(0)}, {AuthorityID: 999, ParentID: apiPolicyUintPointer(888)}, @@ -44,18 +44,18 @@ func TestSetPolicyPathsStrictRootRequiresRegisteredAPI(t *testing.T) { t.Fatal(err) } - err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*biz.API{{Path: "/unknown", Method: "POST"}}) + err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*system.API{{Path: "/unknown", Method: "POST"}}) if err == nil || !strings.Contains(err.Error(), "存在api不在权限列表中") { t.Fatalf("unregistered root assignment error = %v", err) } - if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*biz.API{{Path: "/known", Method: "POST"}}); err != nil { + if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*system.API{{Path: "/known", Method: "POST"}}); err != nil { t.Fatal(err) } } func TestSetPolicyPathsStrictChildRejectsStalePolicy(t *testing.T) { data := newPolicyTestData(t) - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 1001}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: 1001}) if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{ {AuthorityID: 888, ParentID: apiPolicyUintPointer(0)}, {AuthorityID: 1001, ParentID: apiPolicyUintPointer(888)}, @@ -73,18 +73,18 @@ func TestSetPolicyPathsStrictChildRejectsStalePolicy(t *testing.T) { t.Fatal(err) } - err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 1002, []*biz.API{{Path: "/stale", Method: "POST"}}) + err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 1002, []*system.API{{Path: "/stale", Method: "POST"}}) if err == nil || !strings.Contains(err.Error(), "存在api不在权限列表中") { t.Fatalf("stale child assignment error = %v", err) } - if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 1002, []*biz.API{{Path: "/known", Method: "POST"}}); err != nil { + if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 1002, []*system.API{{Path: "/known", Method: "POST"}}); err != nil { t.Fatal(err) } } func TestSetPolicyPathsUsesCompatibleDedupeKey(t *testing.T) { data := newPolicyTestData(t) - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 888}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: 888}) if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{ {AuthorityID: 888, ParentID: apiPolicyUintPointer(0)}, {AuthorityID: 999, ParentID: apiPolicyUintPointer(888)}, @@ -98,7 +98,7 @@ func TestSetPolicyPathsUsesCompatibleDedupeKey(t *testing.T) { t.Fatal(err) } - if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*biz.API{ + if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*system.API{ {Path: "/a", Method: "BC"}, {Path: "/aB", Method: "C"}, }); err != nil { @@ -148,7 +148,7 @@ func TestSetAPIRolesStrictOnlyChangesManagedAuthorities(t *testing.T) { }).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &apiRepo{data: data} if err := repo.SetAPIRoles(ctx, "/known", "POST", []uint{siblingID}); err == nil { t.Fatal("SetAPIRoles() accepted an out-of-scope authority") diff --git a/internal/data/repository/api_sync.go b/internal/data/repository/api_sync.go index e3aa634..f448050 100644 --- a/internal/data/repository/api_sync.go +++ b/internal/data/repository/api_sync.go @@ -2,20 +2,19 @@ package system import ( "context" - - "kra/internal/biz" + "kra/internal/biz/system" "gorm.io/gorm" ) -func (r *apiRepo) IgnoredAPIs(ctx context.Context) ([]*biz.API, error) { +func (r *apiRepo) IgnoredAPIs(ctx context.Context) ([]*system.API, error) { var pos []ignoredAPIPO if err := r.data.DB().WithContext(ctx).Find(&pos).Error; err != nil { return nil, err } - out := make([]*biz.API, 0, len(pos)) + out := make([]*system.API, 0, len(pos)) for _, po := range pos { - out = append(out, &biz.API{Path: po.Path, Method: po.Method}) + out = append(out, &system.API{Path: po.Path, Method: po.Method}) } return out, nil } @@ -28,7 +27,7 @@ func (r *apiRepo) SetAPIIgnored(ctx context.Context, path, method string, ignore } return r.data.DB().WithContext(ctx).Unscoped().Where("path = ? AND method = ?", po.Path, po.Method).Delete(&ignoredAPIPO{}).Error } -func (r *apiRepo) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error { +func (r *apiRepo) ApplyAPISync(ctx context.Context, added, deleted []*system.API) error { return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if len(added) > 0 { pos := make([]apiPO, 0, len(added)) diff --git a/internal/data/repository/api_token.go b/internal/data/repository/api_token.go index a17c8f3..7af9294 100644 --- a/internal/data/repository/api_token.go +++ b/internal/data/repository/api_token.go @@ -3,9 +3,9 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -13,7 +13,7 @@ import ( type apiTokenRepo struct{ data Provider } -func NewAPITokenRepo(data Provider) biz.APITokenRepo { return &apiTokenRepo{data: data} } +func NewAPITokenRepo(data Provider) system.APITokenRepo { return &apiTokenRepo{data: data} } type apiTokenPO struct { ID uint `gorm:"primaryKey"` @@ -40,7 +40,7 @@ type jwtBlacklistPO struct { func (jwtBlacklistPO) TableName() string { return "jwt_blacklists" } -func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID uint) (*biz.User, bool, error) { +func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID uint) (*system.User, bool, error) { var po userPO if err := r.data.DB().WithContext(ctx).First(&po, userID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -59,7 +59,7 @@ func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID user, err := (&userRepo{data: r.data}).loadUser(ctx, &po) return user, count > 0 || po.AuthorityID == authorityID, err } -func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) error { +func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *system.APIToken) error { po := apiTokenPO{UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark} if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -68,7 +68,7 @@ func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) erro v.CreatedAt = po.CreatedAt return nil } -func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*biz.APIToken, int64, error) { +func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*system.APIToken, int64, error) { db := r.data.DB().WithContext(ctx).Model(&apiTokenPO{}) if userID != 0 { db = db.Where("user_id = ?", userID) @@ -94,13 +94,13 @@ func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID return nil, 0, err } } - users := make(map[uint]*biz.User, len(userPOs)) + users := make(map[uint]*system.User, len(userPOs)) for i := range userPOs { users[userPOs[i].ID] = baseBizUser(&userPOs[i]) } - out := make([]*biz.APIToken, 0, len(pos)) + out := make([]*system.APIToken, 0, len(pos)) for _, po := range pos { - v := &biz.APIToken{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UserID: po.UserID, AuthorityID: po.AuthorityID, Token: po.Token, Status: po.Status, ExpiresAt: po.ExpiresAt, Remark: po.Remark, User: users[po.UserID]} + v := &system.APIToken{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UserID: po.UserID, AuthorityID: po.AuthorityID, Token: po.Token, Status: po.Status, ExpiresAt: po.ExpiresAt, Remark: po.Remark, User: users[po.UserID]} out = append(out, v) } return out, total, nil diff --git a/internal/data/repository/audit.go b/internal/data/repository/audit.go index e10dc88..7c0c0c7 100644 --- a/internal/data/repository/audit.go +++ b/internal/data/repository/audit.go @@ -1,13 +1,17 @@ package system -import "kra/internal/biz" +import ( + "kra/internal/biz/system" +) type auditQueryRepo struct{ data Provider } type auditRecorderRepo struct{ data Provider } type logFileRepo struct{ data Provider } -func NewAuditRepo(data Provider) biz.AuditQueryRepo { return &auditQueryRepo{data: data} } +func NewAuditRepo(data Provider) system.AuditQueryRepo { return &auditQueryRepo{data: data} } -func NewAuditRecorderRepo(data Provider) biz.AuditRecordRepo { return &auditRecorderRepo{data: data} } +func NewAuditRecorderRepo(data Provider) system.AuditRecordRepo { + return &auditRecorderRepo{data: data} +} -func NewLogFileRepo(data Provider) biz.LogFileRepo { return &logFileRepo{data: data} } +func NewLogFileRepo(data Provider) system.LogFileRepo { return &logFileRepo{data: data} } diff --git a/internal/data/repository/authority.go b/internal/data/repository/authority.go index 2eae127..9cbb55c 100644 --- a/internal/data/repository/authority.go +++ b/internal/data/repository/authority.go @@ -3,8 +3,7 @@ package system import ( "context" "errors" - - "kra/internal/biz" + "kra/internal/biz/system" "gorm.io/gorm" ) @@ -13,7 +12,7 @@ var errInvalidDataScope = errors.New("数据权限范围不合法") type authorityAccessRepo struct{ data Provider } -func NewAuthorityAccessRepo(data Provider) biz.AuthorityAccessRepo { +func NewAuthorityAccessRepo(data Provider) system.AuthorityAccessRepo { return &authorityAccessRepo{data: data} } @@ -58,18 +57,18 @@ func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID ui return allowed, nil } -func (r *authorityAccessRepo) strictAuthorityAccess(ctx context.Context) (biz.Actor, map[uint]bool, bool, error) { +func (r *authorityAccessRepo) strictAuthorityAccess(ctx context.Context) (system.Actor, map[uint]bool, bool, error) { config := r.data.Runtime().Admin() if config == nil || config.System == nil || !config.System.UseStrictAuth { - return biz.Actor{}, nil, false, nil + return system.Actor{}, nil, false, nil } - actor, ok := biz.ActorFromContext(ctx) + actor, ok := system.ActorFromContext(ctx) if !ok { - return biz.Actor{}, nil, true, errors.New("您提交的角色ID不合法") + return system.Actor{}, nil, true, errors.New("您提交的角色ID不合法") } allowed, err := r.strictAuthorityIDs(ctx, actor.AuthorityID) if err != nil { - return biz.Actor{}, nil, true, err + return system.Actor{}, nil, true, err } return actor, allowed, true, nil } @@ -91,7 +90,7 @@ func (r *authorityAccessRepo) checkAuthorityIDAuth(ctx context.Context, targetID return r.checkAuthorityIDsAuth(ctx, []uint{targetID}) } -func managedAuthorityParent(actor biz.Actor, allowed map[uint]bool, targetID uint, parentID *uint, creating bool) (*uint, error) { +func managedAuthorityParent(actor system.Actor, allowed map[uint]bool, targetID uint, parentID *uint, creating bool) (*uint, error) { if creating && (parentID == nil || *parentID == 0) { value := actor.AuthorityID return &value, nil @@ -131,7 +130,7 @@ func (r *authorityAccessRepo) ensureAuthorityParentAcyclic(ctx context.Context, return nil } -func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Authority) error { +func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *system.Authority) error { if value.DataScope == 0 { value.DataScope = 1 } else if value.DataScope < 1 || value.DataScope > 5 { @@ -168,7 +167,7 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au } value.CreatedAt, value.UpdatedAt = po.CreatedAt, po.UpdatedAt value.DataScope, value.DefaultRouter = po.DataScope, po.DefaultRouter - value.Menus = []*biz.Menu{{ID: 1, Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Sort: 1, Title: "仪表盘", Icon: "setting"}} + value.Menus = []*system.Menu{{ID: 1, Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Sort: 1, Title: "仪表盘", Icon: "setting"}} var dashboard menuPO if err := tx.Where("name = ?", "dashboard").First(&dashboard).Error; err == nil { grantDashboard := true @@ -209,7 +208,7 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au return nil }) } -func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *biz.Authority) error { +func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *system.Authority) error { if value.DataScope == 0 { value.DataScope = 1 } else if value.DataScope < 1 || value.DataScope > 5 { @@ -283,7 +282,7 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, if err := tx.Where("id IN ?", menuIDs).Order("sort").Find(&copiedMenus).Error; err != nil { return err } - value.Menus = make([]*biz.Menu, 0, len(copiedMenus)) + value.Menus = make([]*system.Menu, 0, len(copiedMenus)) for _, menu := range copiedMenus { value.Menus = append(value.Menus, menuFromPO(menu)) } @@ -362,7 +361,7 @@ func (r *authorityAccessRepo) copyPolicyAllowed(ctx context.Context, tx *gorm.DB } return policyExists(tx.WithContext(ctx), actorID, path, method) } -func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error { +func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *system.Authority) error { if err := r.checkAuthorityIDAuth(ctx, value.AuthorityID); err != nil { return err } @@ -464,11 +463,11 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro return tx.Unscoped().Delete(&authorityPO{}, "authority_id = ?", id).Error }) } -func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) { +func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*system.Authority, error) { db := r.data.DB().WithContext(ctx) var allowed map[uint]bool config := r.data.Runtime().Admin() - if actor, ok := biz.ActorFromContext(ctx); ok { + if actor, ok := system.ActorFromContext(ctx); ok { // The current authority is loaded even when strict mode is disabled; // an invalid token authority therefore fails the list request instead of // exposing the complete role tree. @@ -488,7 +487,7 @@ func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Autho if err := db.Find(&pos).Error; err != nil { return nil, err } - out := make([]*biz.Authority, 0, len(pos)) + out := make([]*system.Authority, 0, len(pos)) for _, po := range pos { if allowed != nil && !allowed[po.AuthorityID] { continue @@ -752,8 +751,8 @@ func (r *authorityAccessRepo) DataScopeDepartmentIDs(ctx context.Context, id uin err := r.data.DB().WithContext(ctx).Model(&authorityDepartmentPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_department_id", &ids).Error return ids, err } -func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) { - identity := biz.DataScope{UserID: userID, AuthorityID: authorityID} +func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (system.DataScope, error) { + identity := system.DataScope{UserID: userID, AuthorityID: authorityID} var user userPO if err := r.data.DB().WithContext(ctx).Select("id", "dept_id").First(&user, userID).Error; err != nil { return identity, err diff --git a/internal/data/repository/authority_test.go b/internal/data/repository/authority_test.go index cec035d..5919099 100644 --- a/internal/data/repository/authority_test.go +++ b/internal/data/repository/authority_test.go @@ -3,9 +3,9 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "testing" - "kra/internal/biz" "kra/internal/conf" "gorm.io/gorm" @@ -44,8 +44,8 @@ func TestCopyAuthorityStrictPolicyValidationMatchesAdministrationContract(t *tes } repo := &authorityAccessRepo{data: data} - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: rootID}) - created := &biz.Authority{AuthorityID: 910, AuthorityName: "copy", ParentID: &rootID} + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: rootID}) + created := &system.Authority{AuthorityID: 910, AuthorityName: "copy", ParentID: &rootID} if err := repo.CopyAuthority(ctx, 900, created); err != nil { t.Fatalf("root copy of a registered API failed: %v", err) } @@ -53,7 +53,7 @@ func TestCopyAuthorityStrictPolicyValidationMatchesAdministrationContract(t *tes t.Fatalf("copied policy exists = %v, err = %v", exists, err) } - staleCopy := &biz.Authority{AuthorityID: 911, AuthorityName: "stale-copy", ParentID: &rootID} + staleCopy := &system.Authority{AuthorityID: 911, AuthorityName: "stale-copy", ParentID: &rootID} if err := repo.CopyAuthority(ctx, 901, staleCopy); err == nil || err.Error() != "存在api不在权限列表中" { t.Fatalf("stale API copy error = %v", err) } @@ -78,8 +78,8 @@ func TestCopyAuthorityDuplicateIDWinsOverStrictParentValidation(t *testing.T) { t.Fatal(err) } invalidParent := uint(999999) - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: rootID}) - err := (&authorityAccessRepo{data: data}).CopyAuthority(ctx, 0, &biz.Authority{AuthorityID: 920, AuthorityName: "duplicate", ParentID: &invalidParent}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: rootID}) + err := (&authorityAccessRepo{data: data}).CopyAuthority(ctx, 0, &system.Authority{AuthorityID: 920, AuthorityName: "duplicate", ParentID: &invalidParent}) if err == nil || err.Error() != "存在相同角色id" { t.Fatalf("duplicate copy error = %v", err) } @@ -87,7 +87,7 @@ func TestCopyAuthorityDuplicateIDWinsOverStrictParentValidation(t *testing.T) { func TestListAuthoritiesRequiresCurrentAuthorityOutsideStrictMode(t *testing.T) { data := newTransactionTestData(t) - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 999999}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: 999999}) if _, err := (&authorityAccessRepo{data: data}).ListAuthorities(ctx); !errors.Is(err, gorm.ErrRecordNotFound) { t.Fatalf("missing current authority error = %v", err) } @@ -198,7 +198,7 @@ func TestSetAuthorityUsersStrictRejectsUsersOutsideManagedRoles(t *testing.T) { if err := db.Create(&users).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &authorityAccessRepo{data: data} if err := repo.SetAuthorityUsers(ctx, childID, []uint{users[1].ID}); err == nil { t.Fatal("SetAuthorityUsers() accepted a user outside the managed role tree") @@ -231,7 +231,7 @@ func TestSetAuthorityUsersStrictRejectsMixedRoleUserAlreadyLinked(t *testing.T) }).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) if err := (&authorityAccessRepo{data: data}).SetAuthorityUsers(ctx, childID, nil); err == nil { t.Fatal("SetAuthorityUsers() modified a linked user that also has an out-of-scope role") } @@ -257,8 +257,8 @@ func TestUpdateAuthorityStrictRejectsHierarchyCycle(t *testing.T) { }).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) - err := (&authorityAccessRepo{data: data}).UpdateAuthority(ctx, &biz.Authority{AuthorityID: targetID, AuthorityName: "target", ParentID: &childID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) + err := (&authorityAccessRepo{data: data}).UpdateAuthority(ctx, &system.Authority{AuthorityID: targetID, AuthorityName: "target", ParentID: &childID}) if err == nil { t.Fatal("UpdateAuthority() accepted a parent that forms a cycle") } @@ -283,7 +283,7 @@ func TestSetDataScopeValidatesScopeAndStrictDepartments(t *testing.T) { }).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &authorityAccessRepo{data: data} if err := repo.SetDataScope(ctx, childID, 0, nil); !errors.Is(err, errInvalidDataScope) { t.Fatalf("SetDataScope() invalid scope error = %v", err) @@ -323,7 +323,7 @@ func TestSetDataScopeStrictRejectsDepartmentsOutsideActorScope(t *testing.T) { if err := db.Create(&authorityDepartmentPO{AuthorityID: actorID, DepartmentID: departments[0].ID}).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: actorUser.ID, AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{UserID: actorUser.ID, AuthorityID: actorID}) repo := &authorityAccessRepo{data: data} if err := repo.SetDataScope(ctx, childID, 5, []uint{departments[1].ID}); err == nil { t.Fatal("SetDataScope() accepted a department outside the actor's data scope") @@ -350,31 +350,31 @@ func TestStrictDataScopeGrantRejectsBroaderChildScopes(t *testing.T) { if err := db.Create(&actorUser).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: actorUser.ID, AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{UserID: actorUser.ID, AuthorityID: actorID}) repo := &authorityAccessRepo{data: data} - created := &biz.Authority{AuthorityID: 1373, AuthorityName: "created", ParentID: &actorID} + created := &system.Authority{AuthorityID: 1373, AuthorityName: "created", ParentID: &actorID} if err := repo.CreateAuthority(ctx, created); !errors.Is(err, errInvalidDataScope) { t.Fatalf("CreateAuthority() broader scope error = %v", err) } if created.DataScope != 1 { t.Fatalf("CreateAuthority() did not normalize zero scope before validation: %d", created.DataScope) } - copied := &biz.Authority{AuthorityID: 1374, AuthorityName: "copied", ParentID: &actorID} + copied := &system.Authority{AuthorityID: 1374, AuthorityName: "copied", ParentID: &actorID} if err := repo.CopyAuthority(ctx, sourceID, copied); !errors.Is(err, errInvalidDataScope) { t.Fatalf("CopyAuthority() broader scope error = %v", err) } if copied.DataScope != 1 { t.Fatalf("CopyAuthority() did not normalize zero scope before validation: %d", copied.DataScope) } - if err := repo.UpdateAuthority(ctx, &biz.Authority{AuthorityID: targetID, AuthorityName: "target", ParentID: &actorID, DataScope: 2}); !errors.Is(err, errInvalidDataScope) { + if err := repo.UpdateAuthority(ctx, &system.Authority{AuthorityID: targetID, AuthorityName: "target", ParentID: &actorID, DataScope: 2}); !errors.Is(err, errInvalidDataScope) { t.Fatalf("UpdateAuthority() broader scope error = %v", err) } if err := repo.SetDataScope(ctx, targetID, 1, nil); !errors.Is(err, errInvalidDataScope) { t.Fatalf("SetDataScope() broader scope error = %v", err) } - allowed := &biz.Authority{AuthorityID: 1375, AuthorityName: "self-only", ParentID: &actorID, DataScope: 4} + allowed := &system.Authority{AuthorityID: 1375, AuthorityName: "self-only", ParentID: &actorID, DataScope: 4} if err := repo.CreateAuthority(ctx, allowed); err != nil { t.Fatalf("CreateAuthority() rejected a narrower scope: %v", err) } @@ -405,9 +405,9 @@ func TestCreateAuthorityStrictDefaultsStayWithinActorPermissions(t *testing.T) { if err := db.Create(&apiPO{Path: "/menu/getMenu", Method: "POST"}).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &authorityAccessRepo{data: data} - first := &biz.Authority{AuthorityID: 1401, AuthorityName: "no-defaults", ParentID: &actorID} + first := &system.Authority{AuthorityID: 1401, AuthorityName: "no-defaults", ParentID: &actorID} if err := repo.CreateAuthority(ctx, first); err != nil { t.Fatal(err) } @@ -429,7 +429,7 @@ func TestCreateAuthorityStrictDefaultsStayWithinActorPermissions(t *testing.T) { if err := db.Create(&policy).Error; err != nil { t.Fatal(err) } - second := &biz.Authority{AuthorityID: 1402, AuthorityName: "owned-defaults", ParentID: &actorID} + second := &system.Authority{AuthorityID: 1402, AuthorityName: "owned-defaults", ParentID: &actorID} if err := repo.CreateAuthority(ctx, second); err != nil { t.Fatal(err) } @@ -476,21 +476,21 @@ func TestCopyAuthorityStrictValidatesCopiedMenusAndButtons(t *testing.T) { }).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &authorityAccessRepo{data: data} - if err := repo.CopyAuthority(ctx, sourceID, &biz.Authority{AuthorityID: 1510, AuthorityName: "menu-fail", ParentID: &actorID}); err == nil { + if err := repo.CopyAuthority(ctx, sourceID, &system.Authority{AuthorityID: 1510, AuthorityName: "menu-fail", ParentID: &actorID}); err == nil { t.Fatal("CopyAuthority() copied a menu not assigned to the actor") } if err := db.Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", sourceID, 11).Delete(&authorityMenuPO{}).Error; err != nil { t.Fatal(err) } - if err := repo.CopyAuthority(ctx, sourceID, &biz.Authority{AuthorityID: 1511, AuthorityName: "button-fail", ParentID: &actorID}); err == nil { + if err := repo.CopyAuthority(ctx, sourceID, &system.Authority{AuthorityID: 1511, AuthorityName: "button-fail", ParentID: &actorID}); err == nil { t.Fatal("CopyAuthority() copied a button not assigned to the actor") } if err := db.Where("authority_id = ? AND sys_base_menu_btn_id = ?", sourceID, 32).Delete(&authorityButtonPO{}).Error; err != nil { t.Fatal(err) } - if err := repo.CopyAuthority(ctx, sourceID, &biz.Authority{AuthorityID: 1512, AuthorityName: "valid", ParentID: &actorID}); err != nil { + if err := repo.CopyAuthority(ctx, sourceID, &system.Authority{AuthorityID: 1512, AuthorityName: "valid", ParentID: &actorID}); err != nil { t.Fatalf("CopyAuthority() rejected owned permissions: %v", err) } for _, failedID := range []uint{1510, 1511} { @@ -509,7 +509,7 @@ func TestAuthorityCreateAndCopyNormalizeZeroDataScope(t *testing.T) { db := data.gormDB.WithContext(context.Background()) repo := &authorityAccessRepo{data: data} - created := &biz.Authority{AuthorityID: 1600, AuthorityName: "created"} + created := &system.Authority{AuthorityID: 1600, AuthorityName: "created"} if err := repo.CreateAuthority(context.Background(), created); err != nil { t.Fatal(err) } @@ -520,7 +520,7 @@ func TestAuthorityCreateAndCopyNormalizeZeroDataScope(t *testing.T) { if err := db.Create(&authorityPO{AuthorityID: 1601, AuthorityName: "source", DataScope: 3}).Error; err != nil { t.Fatal(err) } - copied := &biz.Authority{AuthorityID: 1602, AuthorityName: "copied"} + copied := &system.Authority{AuthorityID: 1602, AuthorityName: "copied"} if err := repo.CopyAuthority(context.Background(), 1601, copied); err != nil { t.Fatal(err) } @@ -549,13 +549,13 @@ func TestAuthorityMutationsRejectInvalidDataScope(t *testing.T) { } for _, scope := range []int{-1, 6} { - if err := repo.CreateAuthority(context.Background(), &biz.Authority{AuthorityID: uint(1800 + scope + 1), AuthorityName: "invalid-create", DataScope: scope}); !errors.Is(err, errInvalidDataScope) { + if err := repo.CreateAuthority(context.Background(), &system.Authority{AuthorityID: uint(1800 + scope + 1), AuthorityName: "invalid-create", DataScope: scope}); !errors.Is(err, errInvalidDataScope) { t.Fatalf("CreateAuthority() scope %d error = %v", scope, err) } - if err := repo.CopyAuthority(context.Background(), 1700, &biz.Authority{AuthorityID: uint(1900 + scope + 1), AuthorityName: "invalid-copy", DataScope: scope}); !errors.Is(err, errInvalidDataScope) { + if err := repo.CopyAuthority(context.Background(), 1700, &system.Authority{AuthorityID: uint(1900 + scope + 1), AuthorityName: "invalid-copy", DataScope: scope}); !errors.Is(err, errInvalidDataScope) { t.Fatalf("CopyAuthority() scope %d error = %v", scope, err) } - if err := repo.UpdateAuthority(context.Background(), &biz.Authority{AuthorityID: 1701, AuthorityName: "invalid-update", DataScope: scope}); !errors.Is(err, errInvalidDataScope) { + if err := repo.UpdateAuthority(context.Background(), &system.Authority{AuthorityID: 1701, AuthorityName: "invalid-update", DataScope: scope}); !errors.Is(err, errInvalidDataScope) { t.Fatalf("UpdateAuthority() scope %d error = %v", scope, err) } } @@ -568,7 +568,7 @@ func TestUpdateAuthorityZeroDataScopeKeepsStoredValue(t *testing.T) { t.Fatal(err) } - if err := (&authorityAccessRepo{data: data}).UpdateAuthority(context.Background(), &biz.Authority{AuthorityID: 2000, AuthorityName: "after", DataScope: 0}); err != nil { + if err := (&authorityAccessRepo{data: data}).UpdateAuthority(context.Background(), &system.Authority{AuthorityID: 2000, AuthorityName: "after", DataScope: 0}); err != nil { t.Fatal(err) } var stored authorityPO diff --git a/internal/data/repository/data_access_log.go b/internal/data/repository/data_access_log.go index ab92eff..1e6da45 100644 --- a/internal/data/repository/data_access_log.go +++ b/internal/data/repository/data_access_log.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -23,13 +23,13 @@ type dataAccessLogPO struct { func (dataAccessLogPO) TableName() string { return "sys_data_access_logs" } -func (r *auditRecorderRepo) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error { +func (r *auditRecorderRepo) RecordDataAccess(ctx context.Context, v *system.DataAccessLog) error { return r.data.DB().WithContext(ctx).Create(&dataAccessLogPO{EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail}).Error } -func dataAccessFromPO(v dataAccessLogPO) *biz.DataAccessLog { - return &biz.DataAccessLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail} +func dataAccessFromPO(v dataAccessLogPO) *system.DataAccessLog { + return &system.DataAccessLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail} } -func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*biz.DataAccessLog, int64, error) { +func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *system.DataAccessLog) ([]*system.DataAccessLog, int64, error) { db := r.data.DB().WithContext(ctx).Model(&dataAccessLogPO{}) if q != nil { if q.EventType != "" { @@ -47,7 +47,7 @@ func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q * if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.DataAccessLog, 0, len(pos)) + out := make([]*system.DataAccessLog, 0, len(pos)) for _, po := range pos { out = append(out, dataAccessFromPO(po)) } diff --git a/internal/data/repository/department.go b/internal/data/repository/department.go index 0a4cf19..64cf292 100644 --- a/internal/data/repository/department.go +++ b/internal/data/repository/department.go @@ -3,18 +3,17 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "strconv" "strings" "time" - "kra/internal/biz" - "gorm.io/gorm" ) type departmentRepo struct{ data Provider } -func NewDepartmentRepo(data Provider) biz.DepartmentRepo { return &departmentRepo{data: data} } +func NewDepartmentRepo(data Provider) system.DepartmentRepo { return &departmentRepo{data: data} } type departmentPO struct { ID uint `gorm:"primaryKey"` @@ -47,11 +46,11 @@ type authorityDepartmentPO struct { func (authorityDepartmentPO) TableName() string { return "sys_authority_departments" } -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 deptFromPO(po departmentPO) *system.Department { + return &system.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 *departmentRepo) attachDepartmentLeaders(ctx context.Context, departments []*biz.Department) error { +func (r *departmentRepo) attachDepartmentLeaders(ctx context.Context, departments []*system.Department) error { leaderIDs := make([]uint, 0, len(departments)) for _, department := range departments { if department.LeaderID != 0 { @@ -65,7 +64,7 @@ func (r *departmentRepo) attachDepartmentLeaders(ctx context.Context, department if err := r.data.DB().WithContext(ctx).Where("id IN ?", leaderIDs).Find(&leaders).Error; err != nil { return err } - leaderByID := make(map[uint]*biz.User, len(leaders)) + leaderByID := make(map[uint]*system.User, len(leaders)) for i := range leaders { leaderByID[leaders[i].ID] = baseBizUser(&leaders[i]) } @@ -74,7 +73,7 @@ func (r *departmentRepo) attachDepartmentLeaders(ctx context.Context, department } return nil } -func (r *departmentRepo) CreateDepartment(ctx context.Context, v *biz.Department) error { +func (r *departmentRepo) CreateDepartment(ctx context.Context, v *system.Department) error { v.Ancestors = "0" if v.ParentID != 0 { var parent departmentPO @@ -88,7 +87,7 @@ func (r *departmentRepo) CreateDepartment(ctx context.Context, v *biz.Department } return r.data.DB().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 *departmentRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error { +func (r *departmentRepo) UpdateDepartment(ctx context.Context, v *system.Department) error { if v.ParentID == v.ID { return errors.New("父部门不能是自己") } @@ -130,18 +129,18 @@ func (r *departmentRepo) DeleteDepartment(ctx context.Context, id uint) error { } return r.data.DB().WithContext(ctx).Delete(&departmentPO{}, id).Error } -func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) { +func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*system.Department, error) { var po departmentPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } department := deptFromPO(po) - if err := r.attachDepartmentLeaders(ctx, []*biz.Department{department}); err != nil { + if err := r.attachDepartmentLeaders(ctx, []*system.Department{department}); err != nil { return nil, err } return department, nil } -func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) { +func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*system.Department, error) { var pos []departmentPO db := r.data.DB().WithContext(ctx).Order("sort") if name != "" { @@ -150,15 +149,15 @@ func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*b if err := db.Find(&pos).Error; err != nil { return nil, err } - nodes := map[uint]*biz.Department{} - items := make([]*biz.Department, 0, len(pos)) + nodes := map[uint]*system.Department{} + items := make([]*system.Department, 0, len(pos)) for _, po := range pos { item := deptFromPO(po) if name == "" { // Recursive GORM child queries initialize leaf collections to an empty // slice. Preserve that tree response shape while search and single-item // queries keep the model's nil children value. - item.Children = []*biz.Department{} + item.Children = []*system.Department{} } nodes[po.ID] = item items = append(items, item) @@ -169,7 +168,7 @@ func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*b if name != "" { return items, nil } - roots := make([]*biz.Department, 0) + roots := make([]*system.Department, 0) for _, po := range pos { n := nodes[po.ID] if p := nodes[po.ParentID]; p != nil { diff --git a/internal/data/repository/dictionary.go b/internal/data/repository/dictionary.go index 4ff35dd..08051f2 100644 --- a/internal/data/repository/dictionary.go +++ b/internal/data/repository/dictionary.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" + "kra/internal/biz/system" "strings" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -15,7 +15,7 @@ import ( type dictionaryRepo struct{ data Provider } -func NewDictionaryRepo(data Provider) biz.DictionaryRepo { return &dictionaryRepo{data: data} } +func NewDictionaryRepo(data Provider) system.DictionaryRepo { return &dictionaryRepo{data: data} } type dictionaryPO struct { ID uint `gorm:"primaryKey"` @@ -49,17 +49,17 @@ type dictionaryDetailPO struct { func (dictionaryDetailPO) TableName() string { return "sys_dictionary_details" } -func dictionaryFromPO(po dictionaryPO) *biz.Dictionary { - return &biz.Dictionary{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Type: po.Type, Status: po.Status, Desc: po.Desc, ParentID: po.ParentID} +func dictionaryFromPO(po dictionaryPO) *system.Dictionary { + return &system.Dictionary{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Type: po.Type, Status: po.Status, Desc: po.Desc, ParentID: po.ParentID} } -func detailFromPO(po dictionaryDetailPO) *biz.DictionaryDetail { - return &biz.DictionaryDetail{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Label: po.Label, Value: po.Value, Extend: po.Extend, Status: po.Status, Sort: po.Sort, DictionaryID: po.DictionaryID, ParentID: po.ParentID, Level: po.Level, Path: po.Path} +func detailFromPO(po dictionaryDetailPO) *system.DictionaryDetail { + return &system.DictionaryDetail{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Label: po.Label, Value: po.Value, Extend: po.Extend, Status: po.Status, Sort: po.Sort, DictionaryID: po.DictionaryID, ParentID: po.ParentID, Level: po.Level, Path: po.Path} } -func parameterFromPO(po parameterPO) *biz.SystemParameter { - return &biz.SystemParameter{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Key: po.Key, Value: po.Value, Desc: po.Desc} +func parameterFromPO(po parameterPO) *system.SystemParameter { + return &system.SystemParameter{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Key: po.Key, Value: po.Value, Desc: po.Desc} } -func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error { +func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *system.Dictionary) error { var existing dictionaryPO if err := r.data.DB().WithContext(ctx).Where("type = ?", v.Type).First(&existing).Error; err == nil { return errors.New("存在相同的type,不允许创建") @@ -75,7 +75,7 @@ func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary return nil } -func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dictionary, details []*biz.DictionaryDetail) error { +func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *system.Dictionary, details []*system.DictionaryDetail) error { var existing dictionaryPO if err := r.data.DB().WithContext(ctx).Where("type = ?", dictionary.Type).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("存在相同的type,不允许导入") @@ -119,7 +119,7 @@ func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *biz.D return nil }) } -func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error { +func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *system.Dictionary) error { db := r.data.DB().WithContext(ctx) var current dictionaryPO if err := db.Where("id = ?", v.ID).First(¤t).Error; err != nil { @@ -175,7 +175,7 @@ func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error { } return db.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error } -func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) { +func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*system.Dictionary, error) { var po dictionaryPO db := r.data.DB().WithContext(ctx) active := true @@ -192,7 +192,7 @@ func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string if err = db.Where("sys_dictionary_id = ? AND status = ?", out.ID, true).Order("sort").Find(&detailPOs).Error; err != nil { return nil, err } - out.Details = make([]*biz.DictionaryDetail, 0, len(detailPOs)) + out.Details = make([]*system.DictionaryDetail, 0, len(detailPOs)) for _, detail := range detailPOs { out.Details = append(out.Details, detailFromPO(detail)) } @@ -200,7 +200,7 @@ func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string return out, err } -func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dictionary, error) { +func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*system.Dictionary, error) { var po dictionaryPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err @@ -210,13 +210,13 @@ func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Di if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ?", id).Order("sort").Find(&detailPOs).Error; err != nil { return nil, err } - value.Details = make([]*biz.DictionaryDetail, 0, len(detailPOs)) + value.Details = make([]*system.DictionaryDetail, 0, len(detailPOs)) for _, detail := range detailPOs { value.Details = append(value.Details, detailFromPO(detail)) } return value, nil } -func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) { +func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*system.Dictionary, int64, error) { db := r.data.DB().WithContext(ctx).Model(&dictionaryPO{}) if name != "" { like := "%" + name + "%" @@ -265,16 +265,16 @@ func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, n detailsByDictionary[value.DictionaryID] = append(detailsByDictionary[value.DictionaryID], value) } } - out := make([]*biz.Dictionary, 0, len(pos)) + out := make([]*system.Dictionary, 0, len(pos)) for _, po := range pos { v := dictionaryFromPO(po) if details { - v.Details = make([]*biz.DictionaryDetail, 0, len(detailsByDictionary[v.ID])) + v.Details = make([]*system.DictionaryDetail, 0, len(detailsByDictionary[v.ID])) for _, detail := range detailsByDictionary[v.ID] { v.Details = append(v.Details, detailFromPO(detail)) } } else { - v.Children = make([]*biz.Dictionary, 0, len(childrenByParent[po.ID])) + v.Children = make([]*system.Dictionary, 0, len(childrenByParent[po.ID])) for _, child := range childrenByParent[po.ID] { v.Children = append(v.Children, dictionaryFromPO(child)) } @@ -284,7 +284,7 @@ func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, n return out, total, nil } -func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { +func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *system.DictionaryDetail) error { po := dictionaryDetailPO{Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID} po.Level = 0 po.Path = "" @@ -304,7 +304,7 @@ func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *biz.Dict v.Path = po.Path return nil } -func (r *dictionaryRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { +func (r *dictionaryRepo) UpdateDictionaryDetail(ctx context.Context, v *system.DictionaryDetail) error { var po dictionaryDetailPO db := r.data.DB().WithContext(ctx) if err := db.First(&po, v.ID).Error; err != nil { @@ -359,14 +359,14 @@ func (r *dictionaryRepo) DeleteDictionaryDetail(ctx context.Context, id uint) er } return r.data.DB().WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error } -func (r *dictionaryRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) { +func (r *dictionaryRepo) FindDictionaryDetail(ctx context.Context, id uint) (*system.DictionaryDetail, error) { var po dictionaryDetailPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return detailFromPO(po), nil } -func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*biz.DictionaryDetail, int64, error) { +func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter system.DictionaryDetailFilter) ([]*system.DictionaryDetail, int64, error) { db := r.data.DB().WithContext(ctx).Model(&dictionaryDetailPO{}) if filter.DictionaryID != 0 { db = db.Where("sys_dictionary_id = ?", filter.DictionaryID) @@ -394,13 +394,13 @@ func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size i if err := pagination.ApplyRequired(db.Order("sort,id"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.DictionaryDetail, 0, len(pos)) + out := make([]*system.DictionaryDetail, 0, len(pos)) for _, po := range pos { out = append(out, detailFromPO(po)) } return out, total, nil } -func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*biz.DictionaryDetail, error) { +func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID uint, typ string) ([]*system.DictionaryDetail, error) { if dictionaryID == 0 { // The tree-by-type endpoint resolves only by dictionary type. Unlike the // public dictionary lookup, it does not require the dictionary itself to @@ -415,7 +415,7 @@ func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ? AND parent_id IS NULL", dictionaryID).Order("sort").Find(&pos).Error; err != nil { return nil, err } - roots := make([]*biz.DictionaryDetail, 0, len(pos)) + roots := make([]*system.DictionaryDetail, 0, len(pos)) for _, po := range pos { item := detailFromPO(po) if err := r.loadDictionaryDetailChildren(ctx, item); err != nil { @@ -426,7 +426,7 @@ func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID return roots, nil } -func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*biz.DictionaryDetail, error) { +func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*system.DictionaryDetail, error) { db := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ?", dictionaryID) if parentID == nil { db = db.Where("parent_id IS NULL") @@ -437,7 +437,7 @@ func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictiona if err := db.Order("sort").Find(&pos).Error; err != nil { return nil, err } - items := make([]*biz.DictionaryDetail, 0, len(pos)) + items := make([]*system.DictionaryDetail, 0, len(pos)) for _, po := range pos { item := detailFromPO(po) if includeChildren { @@ -450,12 +450,12 @@ func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictiona return items, nil } -func (r *dictionaryRepo) loadDictionaryDetailChildren(ctx context.Context, parent *biz.DictionaryDetail) error { +func (r *dictionaryRepo) loadDictionaryDetailChildren(ctx context.Context, parent *system.DictionaryDetail) error { var pos []dictionaryDetailPO if err := r.data.DB().WithContext(ctx).Where("parent_id = ?", parent.ID).Order("sort").Find(&pos).Error; err != nil { return err } - parent.Children = make([]*biz.DictionaryDetail, 0, len(pos)) + parent.Children = make([]*system.DictionaryDetail, 0, len(pos)) for _, po := range pos { child := detailFromPO(po) if err := r.loadDictionaryDetailChildren(ctx, child); err != nil { diff --git a/internal/data/repository/dictionary_department_parity_test.go b/internal/data/repository/dictionary_department_parity_test.go index 10954a4..bac02b6 100644 --- a/internal/data/repository/dictionary_department_parity_test.go +++ b/internal/data/repository/dictionary_department_parity_test.go @@ -2,9 +2,8 @@ package system import ( "context" + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func TestDictionaryListPreservesPreloadCollectionShapes(t *testing.T) { @@ -12,10 +11,10 @@ func TestDictionaryListPreservesPreloadCollectionShapes(t *testing.T) { ctx := context.Background() repo := &dictionaryRepo{data: data} - if err := repo.CreateDictionary(ctx, &biz.Dictionary{Name: "status", Type: "status"}); err != nil { + if err := repo.CreateDictionary(ctx, &system.Dictionary{Name: "status", Type: "status"}); err != nil { t.Fatal(err) } - if err := repo.CreateDictionary(ctx, &biz.Dictionary{Name: "kind", Type: "kind"}); err != nil { + if err := repo.CreateDictionary(ctx, &system.Dictionary{Name: "kind", Type: "kind"}); err != nil { t.Fatal(err) } @@ -47,10 +46,10 @@ func TestImportDictionaryRejectsDuplicateType(t *testing.T) { ctx := context.Background() repo := &dictionaryRepo{data: data} - if err := repo.CreateDictionary(ctx, &biz.Dictionary{Name: "first", Type: "duplicate"}); err != nil { + if err := repo.CreateDictionary(ctx, &system.Dictionary{Name: "first", Type: "duplicate"}); err != nil { t.Fatal(err) } - if err := repo.ImportDictionary(ctx, &biz.Dictionary{Name: "second", Type: "duplicate"}, nil); err == nil || err.Error() != "存在相同的type,不允许导入" { + if err := repo.ImportDictionary(ctx, &system.Dictionary{Name: "second", Type: "duplicate"}, nil); err == nil || err.Error() != "存在相同的type,不允许导入" { t.Fatalf("duplicate import error = %v", err) } @@ -68,16 +67,16 @@ func TestDictionaryTreeAndParentQueriesPreserveLoadedChildrenShape(t *testing.T) ctx := context.Background() repo := &dictionaryRepo{data: data} - dictionary := &biz.Dictionary{Name: "tree", Type: "tree"} + dictionary := &system.Dictionary{Name: "tree", Type: "tree"} if err := repo.CreateDictionary(ctx, dictionary); err != nil { t.Fatal(err) } - parent := &biz.DictionaryDetail{Label: "parent", Value: "parent", DictionaryID: dictionary.ID, Sort: 2} + parent := &system.DictionaryDetail{Label: "parent", Value: "parent", DictionaryID: dictionary.ID, Sort: 2} if err := repo.CreateDictionaryDetail(ctx, parent); err != nil { t.Fatal(err) } parentID := parent.ID - child := &biz.DictionaryDetail{Label: "child", Value: "child", DictionaryID: dictionary.ID, ParentID: &parentID, Sort: 1} + child := &system.DictionaryDetail{Label: "child", Value: "child", DictionaryID: dictionary.ID, ParentID: &parentID, Sort: 1} if err := repo.CreateDictionaryDetail(ctx, child); err != nil { t.Fatal(err) } @@ -112,15 +111,15 @@ func TestDictionaryDetailListAppliesZeroLimit(t *testing.T) { ctx := context.Background() repo := &dictionaryRepo{data: data} - dictionary := &biz.Dictionary{Name: "limited", Type: "limited"} + dictionary := &system.Dictionary{Name: "limited", Type: "limited"} if err := repo.CreateDictionary(ctx, dictionary); err != nil { t.Fatal(err) } - if err := repo.CreateDictionaryDetail(ctx, &biz.DictionaryDetail{Label: "one", Value: "one", DictionaryID: dictionary.ID}); err != nil { + if err := repo.CreateDictionaryDetail(ctx, &system.DictionaryDetail{Label: "one", Value: "one", DictionaryID: dictionary.ID}); err != nil { t.Fatal(err) } - items, total, err := repo.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: dictionary.ID}) + items, total, err := repo.ListDictionaryDetails(ctx, 0, 0, system.DictionaryDetailFilter{DictionaryID: dictionary.ID}) if err != nil { t.Fatal(err) } @@ -136,7 +135,7 @@ func TestUserListAppliesZeroLimitForNegativePageSize(t *testing.T) { t.Fatal(err) } - items, total, err := (&userRepo{data: data}).ListUsers(ctx, 1, -1, &biz.UserListFilter{}) + items, total, err := (&userRepo{data: data}).ListUsers(ctx, 1, -1, &system.UserListFilter{}) if err != nil { t.Fatal(err) } @@ -150,7 +149,7 @@ func TestDepartmentTreeAndFlatResultsPreserveChildrenShape(t *testing.T) { ctx := context.Background() repo := &departmentRepo{data: data} - root := &biz.Department{Name: "root"} + root := &system.Department{Name: "root"} if err := repo.CreateDepartment(ctx, root); err != nil { t.Fatal(err) } @@ -158,7 +157,7 @@ func TestDepartmentTreeAndFlatResultsPreserveChildrenShape(t *testing.T) { if err := data.gormDB.WithContext(ctx).Where("name = ?", "root").First(&rootPO).Error; err != nil { t.Fatal(err) } - if err := repo.CreateDepartment(ctx, &biz.Department{Name: "child", ParentID: rootPO.ID}); err != nil { + if err := repo.CreateDepartment(ctx, &system.Department{Name: "child", ParentID: rootPO.ID}); err != nil { t.Fatal(err) } diff --git a/internal/data/repository/error_record.go b/internal/data/repository/error_record.go index 01afda6..cbeffff 100644 --- a/internal/data/repository/error_record.go +++ b/internal/data/repository/error_record.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -26,10 +26,10 @@ type errorRecordPO struct { func (errorRecordPO) TableName() string { return "sys_error" } -func errorFromPO(v errorRecordPO) *biz.ErrorRecord { - return &biz.ErrorRecord{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status} +func errorFromPO(v errorRecordPO) *system.ErrorRecord { + return &system.ErrorRecord{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status} } -func (r *auditRecorderRepo) CreateError(ctx context.Context, v *biz.ErrorRecord) error { +func (r *auditRecorderRepo) CreateError(ctx context.Context, v *system.ErrorRecord) error { if !r.data.DatabaseReady() { // Silently ignore error records before the database is initialized. return nil @@ -39,7 +39,7 @@ func (r *auditRecorderRepo) CreateError(ctx context.Context, v *biz.ErrorRecord) } return r.data.DB().WithContext(ctx).Create(&errorRecordPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}).Error } -func (r *auditQueryRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) error { +func (r *auditQueryRepo) UpdateError(ctx context.Context, v *system.ErrorRecord) error { updates := make(map[string]any, 9) if v.ID != 0 { updates["id"] = v.ID @@ -73,14 +73,14 @@ func (r *auditQueryRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) er func (r *auditQueryRepo) DeleteErrors(ctx context.Context, ids []uint) error { return r.data.DB().WithContext(ctx).Delete(&errorRecordPO{}, ids).Error } -func (r *auditQueryRepo) FindError(ctx context.Context, id uint) (*biz.ErrorRecord, error) { +func (r *auditQueryRepo) FindError(ctx context.Context, id uint) (*system.ErrorRecord, error) { var po errorRecordPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return errorFromPO(po), nil } -func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*biz.ErrorRecord, int64, error) { +func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *system.ErrorRecord) ([]*system.ErrorRecord, int64, error) { db := r.data.DB().WithContext(ctx).Model(&errorRecordPO{}) if q != nil { if len(q.CreatedAtRange) == 2 { @@ -101,7 +101,7 @@ func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *biz. if err := pagination.Apply(db.Order("created_at desc"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.ErrorRecord, 0, len(pos)) + out := make([]*system.ErrorRecord, 0, len(pos)) for _, po := range pos { out = append(out, errorFromPO(po)) } diff --git a/internal/data/repository/error_record_test.go b/internal/data/repository/error_record_test.go index d3c5e81..9ffe63b 100644 --- a/internal/data/repository/error_record_test.go +++ b/internal/data/repository/error_record_test.go @@ -2,10 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "testing" "time" - - "kra/internal/biz" ) func errorString(value string) *string { return &value } @@ -41,7 +40,7 @@ func TestCreateErrorBeforeDatabaseInitializationIsNoop(t *testing.T) { t.Cleanup(func() { _ = sqlDB.Close() }) recorder := &auditRecorderRepo{data: &Data{gormDB: newReloadableDB(db, nil)}} form := "后端" - if err = recorder.CreateError(context.Background(), &biz.ErrorRecord{Form: &form}); err != nil { + if err = recorder.CreateError(context.Background(), &system.ErrorRecord{Form: &form}); err != nil { t.Fatalf("uninitialized error write = %v, want nil", err) } } @@ -50,7 +49,7 @@ func TestCreateErrorPreservesNullableFieldsAndDefaultStatus(t *testing.T) { recorder, query := newErrorRecordRepos(t) createdAt := time.Date(2025, time.March, 4, 5, 6, 7, 0, time.UTC) form := "前端" - if err := recorder.CreateError(context.Background(), &biz.ErrorRecord{ + if err := recorder.CreateError(context.Background(), &system.ErrorRecord{ ID: 41, CreatedAt: createdAt, Form: &form, @@ -80,7 +79,7 @@ func TestCreateErrorPreservesNullableFieldsAndDefaultStatus(t *testing.T) { func TestUpdateErrorDistinguishesOmittedAndExplicitEmptyFields(t *testing.T) { recorder, query := newErrorRecordRepos(t) form, info, solution := "后端", "错误内容", "解决方案" - if err := recorder.CreateError(context.Background(), &biz.ErrorRecord{ + if err := recorder.CreateError(context.Background(), &system.ErrorRecord{ Form: &form, Info: &info, Solution: &solution, @@ -98,7 +97,7 @@ func TestUpdateErrorDistinguishesOmittedAndExplicitEmptyFields(t *testing.T) { } id := items[0].ID newForm := "服务端" - if err = query.UpdateError(context.Background(), &biz.ErrorRecord{ID: id, Form: &newForm}); err != nil { + if err = query.UpdateError(context.Background(), &system.ErrorRecord{ID: id, Form: &newForm}); err != nil { t.Fatal(err) } got, err := query.FindError(context.Background(), id) @@ -110,7 +109,7 @@ func TestUpdateErrorDistinguishesOmittedAndExplicitEmptyFields(t *testing.T) { } changedCreatedAt := time.Date(2024, time.January, 2, 3, 4, 5, 0, time.UTC) - if err = query.UpdateError(context.Background(), &biz.ErrorRecord{ + if err = query.UpdateError(context.Background(), &system.ErrorRecord{ ID: id, CreatedAt: changedCreatedAt, Form: errorString(""), diff --git a/internal/data/repository/export.go b/internal/data/repository/export.go index c7c2065..18a5978 100644 --- a/internal/data/repository/export.go +++ b/internal/data/repository/export.go @@ -5,12 +5,13 @@ import ( "encoding/json" "errors" "fmt" - "gorm.io/gorm" - "gorm.io/gorm/clause" - "kra/internal/biz" + "kra/internal/biz/system" "strconv" "strings" "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" ) type exportTemplatePO struct { @@ -59,27 +60,27 @@ func (exportJoinPO) TableName() string { return "sys_export_template_join" } type exportRepo struct{ data Provider } -func NewExportRepo(data Provider) biz.ExportRepo { return &exportRepo{data: data} } -func exportFromPO(po exportTemplatePO, conditions []exportConditionPO, joins []exportJoinPO) *biz.ExportTemplate { - v := &biz.ExportTemplate{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DBName: po.DBName, Name: po.Name, TableName: po.DBTableName, TemplateID: po.TemplateID, TemplateInfo: po.TemplateInfo, SQL: po.SQL, ImportSQL: po.ImportSQL, Limit: po.Limit, Order: po.Order} +func NewExportRepo(data Provider) system.ExportRepo { return &exportRepo{data: data} } +func exportFromPO(po exportTemplatePO, conditions []exportConditionPO, joins []exportJoinPO) *system.ExportTemplate { + v := &system.ExportTemplate{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DBName: po.DBName, Name: po.Name, TableName: po.DBTableName, TemplateID: po.TemplateID, TemplateInfo: po.TemplateInfo, SQL: po.SQL, ImportSQL: po.ImportSQL, Limit: po.Limit, Order: po.Order} if conditions != nil { - v.Conditions = make([]biz.ExportCondition, 0, len(conditions)) + v.Conditions = make([]system.ExportCondition, 0, len(conditions)) } for _, x := range conditions { - v.Conditions = append(v.Conditions, biz.ExportCondition{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, From: x.From, Column: x.Column, Operator: x.Operator}) + v.Conditions = append(v.Conditions, system.ExportCondition{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, From: x.From, Column: x.Column, Operator: x.Operator}) } if joins != nil { - v.Joins = make([]biz.ExportJoin, 0, len(joins)) + v.Joins = make([]system.ExportJoin, 0, len(joins)) } for _, x := range joins { - v.Joins = append(v.Joins, biz.ExportJoin{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, Join: x.Join, Table: x.Table, On: x.On}) + v.Joins = append(v.Joins, system.ExportJoin{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, Join: x.Join, Table: x.Table, On: x.On}) } return v } -func exportToPO(v *biz.ExportTemplate) exportTemplatePO { +func exportToPO(v *system.ExportTemplate) exportTemplatePO { return exportTemplatePO{ID: v.ID, DBName: v.DBName, Name: v.Name, DBTableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order} } -func (r *exportRepo) saveRelations(tx *gorm.DB, v *biz.ExportTemplate, resetIDs, replace, forceTemplateID bool) error { +func (r *exportRepo) saveRelations(tx *gorm.DB, v *system.ExportTemplate, resetIDs, replace, forceTemplateID bool) error { if replace { if err := tx.Where("template_id = ?", v.TemplateID).Delete(&exportConditionPO{}).Error; err != nil { return err @@ -137,7 +138,7 @@ func (r *exportRepo) saveRelations(tx *gorm.DB, v *biz.ExportTemplate, resetIDs, } return nil } -func (r *exportRepo) CreateExportTemplate(ctx context.Context, v *biz.ExportTemplate) error { +func (r *exportRepo) CreateExportTemplate(ctx context.Context, v *system.ExportTemplate) error { return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { po := exportToPO(v) if err := tx.Create(&po).Error; err != nil { @@ -147,7 +148,7 @@ func (r *exportRepo) CreateExportTemplate(ctx context.Context, v *biz.ExportTemp return r.saveRelations(tx, v, false, false, true) }) } -func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemplate) error { +func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *system.ExportTemplate) error { return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { po := exportToPO(v) if err := tx.Model(&exportTemplatePO{}).Where("id = ?", v.ID).Updates(&po).Error; err != nil { @@ -159,7 +160,7 @@ func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemp func (r *exportRepo) DeleteExportTemplates(ctx context.Context, ids []uint) error { return r.data.DB().WithContext(ctx).Delete(&[]exportTemplatePO{}, "id IN ?", ids).Error } -func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*biz.ExportTemplate, error) { +func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*system.ExportTemplate, error) { var po exportTemplatePO db := r.data.DB().WithContext(ctx) var err error @@ -181,7 +182,7 @@ func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string } return exportFromPO(po, conditions, joins), nil } -func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*biz.ExportTemplate, int64, error) { +func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q *system.ExportTemplate) ([]*system.ExportTemplate, int64, error) { db := r.data.DB().WithContext(ctx).Model(&exportTemplatePO{}) if q != nil { if q.StartCreatedAt != nil && q.EndCreatedAt != nil { @@ -210,14 +211,14 @@ func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q if err := db.Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.ExportTemplate, 0, len(pos)) + out := make([]*system.ExportTemplate, 0, len(pos)) for _, po := range pos { out = append(out, exportFromPO(po, nil, nil)) } return out, total, nil } -func (r *exportRepo) QueryExport(ctx context.Context, t *biz.ExportTemplate, params map[string]string) ([]map[string]any, string, error) { +func (r *exportRepo) QueryExport(ctx context.Context, t *system.ExportTemplate, params map[string]string) ([]map[string]any, string, error) { selected, err := r.data.Database(t.DBName) if err != nil { return nil, "", err @@ -327,7 +328,7 @@ func (r *exportRepo) QueryExport(ctx context.Context, t *biz.ExportTemplate, par return rows, preview, err } -func (r *exportRepo) PreviewExport(ctx context.Context, t *biz.ExportTemplate, params map[string]string) (string, error) { +func (r *exportRepo) PreviewExport(ctx context.Context, t *system.ExportTemplate, params map[string]string) (string, error) { columns := make([]string, 0) decoder := json.NewDecoder(strings.NewReader(t.TemplateInfo)) if token, err := decoder.Token(); err != nil || token != json.Delim('{') { @@ -437,7 +438,7 @@ func parseTemplateColumns(raw string) map[string]string { } return out } -func (r *exportRepo) ImportExportRows(ctx context.Context, t *biz.ExportTemplate, rows []map[string]any) error { +func (r *exportRepo) ImportExportRows(ctx context.Context, t *system.ExportTemplate, rows []map[string]any) error { // ImportSQL is checked verbatim. In particular, a whitespace-only value // is still treated as custom SQL and is allowed to return the driver's // native error instead of silently falling back to GORM insertion. diff --git a/internal/data/repository/integration_config.go b/internal/data/repository/integration_config.go index 55e2ef1..99880f6 100644 --- a/internal/data/repository/integration_config.go +++ b/internal/data/repository/integration_config.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" "errors" + "kra/internal/biz/system" "strings" "time" - "kra/internal/biz" "kra/internal/integration/runtimeconfig" "gorm.io/gorm" @@ -31,23 +31,23 @@ type integrationRuntimeProvider interface { IntegrationRuntime() *runtimeconfig.Store } -func NewIntegrationConfigRepo(data Provider) biz.IntegrationConfigRepo { +func NewIntegrationConfigRepo(data Provider) system.IntegrationConfigRepo { return &integrationConfigRepo{data: data} } -func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*biz.IntegrationConfig, error) { +func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*system.IntegrationConfig, error) { var rows []integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ?", kind).Order("provider ASC").Find(&rows).Error; err != nil { return nil, err } - result := make([]*biz.IntegrationConfig, 0, len(rows)) + result := make([]*system.IntegrationConfig, 0, len(rows)) for _, row := range rows { result = append(result, integrationConfigFromPO(row)) } return result, nil } -func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*biz.IntegrationConfig, error) { +func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*system.IntegrationConfig, error) { var row integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", kind, provider).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -58,14 +58,14 @@ func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, return integrationConfigFromPO(row), nil } -func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *biz.IntegrationConfig) error { +func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *system.IntegrationConfig) error { db := r.data.DB().WithContext(ctx) var row integrationConfigPO err := db.Where("kind = ? AND provider = ?", config.Kind, config.Provider).First(&row).Error values := integrationObject(config.Values) if errors.Is(err, gorm.ErrRecordNotFound) { if config.Enabled { - if err = biz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { + if err = system.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { return err } } @@ -81,7 +81,7 @@ func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, confi } mergeIntegrationSecrets(config.Kind, config.Provider, values, integrationObject(json.RawMessage(row.Config))) if config.Enabled { - if err = biz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { + if err = system.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { return err } } @@ -116,11 +116,11 @@ func integrationRuntime(provider Provider) *runtimeconfig.Store { return nil } -func integrationConfigFromPO(row integrationConfigPO) *biz.IntegrationConfig { +func integrationConfigFromPO(row integrationConfigPO) *system.IntegrationConfig { values := integrationObject(json.RawMessage(row.Config)) maskIntegrationSecrets(row.Kind, row.Provider, values) encoded, _ := json.Marshal(values) - return &biz.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded} + return &system.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded} } func integrationObject(raw json.RawMessage) map[string]any { @@ -165,7 +165,7 @@ func mergeIntegrationSecrets(kind, provider string, values, old map[string]any) func integrationSecretFields(kind, provider string) map[string]bool { result := map[string]bool{} - if definition, ok := biz.IntegrationDefinition(kind, provider); ok { + if definition, ok := system.IntegrationDefinition(kind, provider); ok { for _, field := range definition.Fields { if field.Secret { result[field.Key] = true diff --git a/internal/data/repository/integration_config_runtime_test.go b/internal/data/repository/integration_config_runtime_test.go index ff2c989..6b7c3d3 100644 --- a/internal/data/repository/integration_config_runtime_test.go +++ b/internal/data/repository/integration_config_runtime_test.go @@ -3,9 +3,9 @@ package system import ( "context" "encoding/json" + "kra/internal/biz/system" "testing" - "kra/internal/biz" "kra/internal/integration/runtimeconfig" ) @@ -27,19 +27,19 @@ func TestIntegrationConfigSavePublishesUnmaskedRuntimeValues(t *testing.T) { provider := &integrationRuntimeTestProvider{Data: &Data{gormDB: newReloadableDB(db, nil)}, store: runtimeconfig.NewStore()} repo := &integrationConfigRepo{data: provider} - values := biz.DefaultIntegrationConfig(biz.IntegrationKindMQ, "rabbitmq") + values := system.DefaultIntegrationConfig(system.IntegrationKindMQ, "rabbitmq") values["password"] = "runtime-secret" raw, _ := json.Marshal(values) - if err = repo.SaveIntegrationConfig(context.Background(), &biz.IntegrationConfig{Kind: biz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { + if err = repo.SaveIntegrationConfig(context.Background(), &system.IntegrationConfig{Kind: system.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { t.Fatal(err) } values["password"] = "******" raw, _ = json.Marshal(values) - if err = repo.SaveIntegrationConfig(context.Background(), &biz.IntegrationConfig{Kind: biz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { + if err = repo.SaveIntegrationConfig(context.Background(), &system.IntegrationConfig{Kind: system.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { t.Fatal(err) } - active, ok := provider.store.Get(biz.IntegrationKindMQ, "rabbitmq") + active, ok := provider.store.Get(system.IntegrationKindMQ, "rabbitmq") if !ok || !active.Enabled { t.Fatalf("runtime config = %#v, ok=%v", active, ok) } diff --git a/internal/data/repository/log_file.go b/internal/data/repository/log_file.go index 3127fc8..4b78e5f 100644 --- a/internal/data/repository/log_file.go +++ b/internal/data/repository/log_file.go @@ -7,14 +7,13 @@ import ( "fmt" "io" "io/fs" + "kra/internal/biz/system" "os" pathpkg "path" "path/filepath" "sort" "strings" "time" - - "kra/internal/biz" ) const ( @@ -26,21 +25,21 @@ const ( func (r *logFileRepo) configuredLogRoot() (root string, exists bool, err error) { admin := r.data.Runtime().Admin() if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" { - return "", false, biz.ErrLogRootUnavailable + return "", false, system.ErrLogRootUnavailable } root, err = filepath.Abs(admin.Zap.Director) if err != nil { - return "", false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return "", false, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } info, err := os.Stat(root) if errors.Is(err, fs.ErrNotExist) { return root, false, nil } if err != nil { - return "", false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return "", false, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } if !info.IsDir() { - return "", false, biz.ErrLogRootUnavailable + return "", false, system.ErrLogRootUnavailable } return root, true, nil } @@ -52,12 +51,12 @@ func (r *logFileRepo) openConfiguredLogRoot() (root *os.Root, exists bool, err e } root, err = os.OpenRoot(rootPath) if err != nil { - return nil, false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return nil, false, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } return root, true, nil } -func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) { +func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]system.LogDate, error) { if err := validateLogMonth(month); err != nil { return nil, err } @@ -66,15 +65,15 @@ func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate return nil, err } if !exists { - return []biz.LogDate{}, nil + return []system.LogDate{}, nil } defer logRoot.Close() entries, err := fs.ReadDir(logRoot.FS(), ".") if err != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } - result := make([]biz.LogDate, 0) + result := make([]system.LogDate, 0) for _, entry := range entries { if err = ctx.Err(); err != nil { return nil, err @@ -90,14 +89,14 @@ func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate return nil, countErr } if count > 0 { - result = append(result, biz.LogDate{Date: entry.Name(), FileCount: count}) + result = append(result, system.LogDate{Date: entry.Name(), FileCount: count}) } } sort.Slice(result, func(i, j int) bool { return result[i].Date < result[j].Date }) return result, nil } -func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) { +func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]system.LogFile, error) { if err := validateLogDate(date); err != nil { return nil, err } @@ -106,27 +105,27 @@ func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, return nil, err } if !exists { - return []biz.LogFile{}, nil + return []system.LogFile{}, nil } defer logRoot.Close() info, err := logRoot.Lstat(date) if errors.Is(err, fs.ErrNotExist) { - return []biz.LogFile{}, nil + return []system.LogFile{}, nil } if err != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { - return []biz.LogFile{}, nil + return []system.LogFile{}, nil } dateRoot, err := logRoot.OpenRoot(date) if err != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } defer dateRoot.Close() - result := make([]biz.LogFile, 0) + result := make([]system.LogFile, 0) err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr @@ -151,24 +150,24 @@ func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, return infoErr } if fileInfo.Mode().IsRegular() { - result = append(result, biz.LogFile{Path: path, Name: entry.Name(), Size: fileInfo.Size(), ModifiedAt: fileInfo.ModTime()}) + result = append(result, system.LogFile{Path: path, Name: entry.Name(), Size: fileInfo.Size(), ModifiedAt: fileInfo.ModTime()}) } return nil }) if err != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } sort.Slice(result, func(i, j int) bool { return result[i].Path < result[j].Path }) return result, nil } -func (r *logFileRepo) LogContent(ctx context.Context, date, apiPath string, cursor *int64) (*biz.LogContent, error) { - result := &biz.LogContent{Date: date, Path: apiPath} +func (r *logFileRepo) LogContent(ctx context.Context, date, apiPath string, cursor *int64) (*system.LogContent, error) { + result := &system.LogContent{Date: date, Path: apiPath} if err := ctx.Err(); err != nil { return nil, err } if cursor != nil && *cursor < 0 { - return nil, biz.ErrInvalidLogPath + return nil, system.ErrInvalidLogPath } file, info, err := r.openValidatedLogFile(date, apiPath) if err != nil { @@ -182,19 +181,19 @@ func (r *logFileRepo) LogContent(ctx context.Context, date, apiPath string, curs } start, limitedByBytes, err := findLogChunkStart(file, end) if err != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err) + return nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err) } data, err := readLogRange(file, start, end) if err != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err) + return nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err) } if int64(len(data)) < end-start { currentInfo, statErr := file.Stat() if statErr != nil || currentInfo.Size() < start { if statErr != nil { - return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, statErr) + return nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, statErr) } - return nil, biz.ErrLogFileUnreadable + return nil, system.ErrLogFileUnreadable } info = currentInfo } @@ -221,23 +220,23 @@ func (r *logFileRepo) openValidatedLogFile(date, apiPath string) (file *os.File, return nil, nil, err } if !exists { - return nil, nil, biz.ErrLogFileNotFound + return nil, nil, system.ErrLogFileNotFound } defer logRoot.Close() dateInfo, err := logRoot.Lstat(date) if errors.Is(err, fs.ErrNotExist) { - return nil, nil, biz.ErrLogFileNotFound + return nil, nil, system.ErrLogFileNotFound } if err != nil { - return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err) + return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err) } if dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() { - return nil, nil, biz.ErrInvalidLogPath + return nil, nil, system.ErrInvalidLogPath } dateRoot, err := logRoot.OpenRoot(date) if err != nil { - return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err) + return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err) } defer dateRoot.Close() @@ -247,35 +246,35 @@ func (r *logFileRepo) openValidatedLogFile(date, apiPath string) (file *os.File, relativePath = filepath.Join(relativePath, segment) validatedInfo, err = dateRoot.Lstat(relativePath) if errors.Is(err, fs.ErrNotExist) { - return nil, nil, biz.ErrLogFileNotFound + return nil, nil, system.ErrLogFileNotFound } if err != nil { - return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err) + return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err) } if validatedInfo.Mode()&os.ModeSymlink != 0 { - return nil, nil, biz.ErrInvalidLogPath + return nil, nil, system.ErrInvalidLogPath } if index < len(segments)-1 && !validatedInfo.IsDir() { - return nil, nil, biz.ErrInvalidLogPath + return nil, nil, system.ErrInvalidLogPath } } if validatedInfo == nil || !validatedInfo.Mode().IsRegular() { - return nil, nil, biz.ErrInvalidLogPath + return nil, nil, system.ErrInvalidLogPath } file, err = dateRoot.Open(relativePath) if err != nil { if errors.Is(err, fs.ErrNotExist) { - return nil, nil, biz.ErrLogFileNotFound + return nil, nil, system.ErrLogFileNotFound } - return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err) + return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err) } openedInfo, statErr := file.Stat() if statErr != nil || !openedInfo.Mode().IsRegular() || !os.SameFile(validatedInfo, openedInfo) { file.Close() if statErr != nil { - return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, statErr) + return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, statErr) } - return nil, nil, biz.ErrInvalidLogPath + return nil, nil, system.ErrInvalidLogPath } return file, openedInfo, nil } @@ -283,7 +282,7 @@ func (r *logFileRepo) openValidatedLogFile(date, apiPath string) (file *os.File, func validateLogMonth(month string) error { parsed, err := time.Parse("2006-01", month) if err != nil || parsed.Format("2006-01") != month { - return biz.ErrInvalidLogMonth + return system.ErrInvalidLogMonth } return nil } @@ -291,23 +290,23 @@ func validateLogMonth(month string) error { func validateLogDate(date string) error { parsed, err := time.Parse("2006-01-02", date) if err != nil || parsed.Format("2006-01-02") != date { - return biz.ErrInvalidLogDate + return system.ErrInvalidLogDate } return nil } func validateLogAPIPath(apiPath string) ([]string, error) { if apiPath == "" || strings.Contains(apiPath, "\\") || strings.Contains(apiPath, ":") || pathpkg.IsAbs(apiPath) { - return nil, biz.ErrInvalidLogPath + return nil, system.ErrInvalidLogPath } segments := strings.Split(apiPath, "/") for _, segment := range segments { if segment == "" || segment == "." || segment == ".." { - return nil, biz.ErrInvalidLogPath + return nil, system.ErrInvalidLogPath } } if !strings.EqualFold(pathpkg.Ext(apiPath), ".log") { - return nil, biz.ErrInvalidLogPath + return nil, system.ErrInvalidLogPath } return segments, nil } @@ -351,7 +350,7 @@ func countLogFiles(ctx context.Context, logRoot *os.Root, date string) (count in return nil }) if err != nil { - return 0, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err) + return 0, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err) } return count, nil } diff --git a/internal/data/repository/log_file_test.go b/internal/data/repository/log_file_test.go index f8588b6..3865432 100644 --- a/internal/data/repository/log_file_test.go +++ b/internal/data/repository/log_file_test.go @@ -3,11 +3,11 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "os" "path/filepath" "testing" - "kra/internal/biz" "kra/internal/conf" ) @@ -44,7 +44,7 @@ func TestLogViewerRejectsPathTraversal(t *testing.T) { runtime := conf.NewRuntime(nil, &conf.AdminBackend{Zap: &conf.AdminBackend_Zap{Director: t.TempDir()}}) repo := &logFileRepo{data: &Data{runtime: runtime}} _, err := repo.LogContent(context.Background(), "2026-08-16", "../application.log", nil) - if !errors.Is(err, biz.ErrInvalidLogPath) { + if !errors.Is(err, system.ErrInvalidLogPath) { t.Fatalf("expected invalid path error, got %v", err) } } diff --git a/internal/data/repository/login_log.go b/internal/data/repository/login_log.go index 32b88b4..0310074 100644 --- a/internal/data/repository/login_log.go +++ b/internal/data/repository/login_log.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -23,7 +23,7 @@ type loginLogPO struct { func (loginLogPO) TableName() string { return "sys_login_logs" } -func (r *auditRecorderRepo) RecordLogin(ctx context.Context, v *biz.LoginLog) error { +func (r *auditRecorderRepo) RecordLogin(ctx context.Context, v *system.LoginLog) error { if !r.data.DatabaseReady() { // The login endpoint remains reachable before database initialization; // skip the audit write until storage is ready. @@ -31,10 +31,10 @@ func (r *auditRecorderRepo) RecordLogin(ctx context.Context, v *biz.LoginLog) er } return r.data.DB().WithContext(ctx).Create(&loginLogPO{Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}).Error } -func loginFromPO(v loginLogPO) *biz.LoginLog { - return &biz.LoginLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID} +func loginFromPO(v loginLogPO) *system.LoginLog { + return &system.LoginLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID} } -func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*biz.LoginLog, int64, error) { +func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *system.LoginLog) ([]*system.LoginLog, int64, error) { db := r.data.DB().WithContext(ctx).Model(&loginLogPO{}) if q != nil { if q.Username != "" { @@ -59,7 +59,7 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz. } } users := auditUsers(ctx, r.data.DB().WithContext(ctx), ids) - out := make([]*biz.LoginLog, 0, len(pos)) + out := make([]*system.LoginLog, 0, len(pos)) for _, po := range pos { value := loginFromPO(po) value.User = users[po.UserID] @@ -67,7 +67,7 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz. } return out, total, nil } -func (r *auditQueryRepo) FindLogin(ctx context.Context, id uint) (*biz.LoginLog, error) { +func (r *auditQueryRepo) FindLogin(ctx context.Context, id uint) (*system.LoginLog, error) { var po loginLogPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err diff --git a/internal/data/repository/media.go b/internal/data/repository/media.go index 67c53d4..b5f975b 100644 --- a/internal/data/repository/media.go +++ b/internal/data/repository/media.go @@ -3,10 +3,9 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" ) @@ -41,14 +40,14 @@ func (categoryPO) TableName() string { return "media_attachment_category" } type mediaRepo struct{ data Provider } -func NewMediaRepo(data Provider) biz.MediaRepo { return &mediaRepo{data: data} } -func mediaFromPO(v mediaPO) *biz.MediaFile { - return &biz.MediaFile{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} +func NewMediaRepo(data Provider) system.MediaRepo { return &mediaRepo{data: data} } +func mediaFromPO(v mediaPO) *system.MediaFile { + return &system.MediaFile{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} } -func mediaToPO(v *biz.MediaFile) mediaPO { +func mediaToPO(v *system.MediaFile) mediaPO { return mediaPO{ID: v.ID, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} } -func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error { +func (r *mediaRepo) CreateMedia(ctx context.Context, v *system.MediaFile) error { po := mediaToPO(v) if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -58,21 +57,21 @@ func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error { v.UpdatedAt = po.UpdatedAt return nil } -func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*biz.MediaFile, error) { +func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*system.MediaFile, error) { var po mediaPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return mediaFromPO(po), nil } -func (r *mediaRepo) FindMediaByHash(ctx context.Context, userID uint, hash string) (*biz.MediaFile, error) { +func (r *mediaRepo) FindMediaByHash(ctx context.Context, userID uint, hash string) (*system.MediaFile, error) { var po mediaPO if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND md5 = ?", userID, hash).First(&po).Error; err != nil { return nil, err } return mediaFromPO(po), nil } -func (r *mediaRepo) ListMedia(ctx context.Context, filter biz.MediaFilter) ([]*biz.MediaFile, int64, error) { +func (r *mediaRepo) ListMedia(ctx context.Context, filter system.MediaFilter) ([]*system.MediaFile, int64, error) { db := r.data.DB().WithContext(ctx).Model(&mediaPO{}) if filter.Keyword != "" { db = db.Where("name LIKE ?", "%"+filter.Keyword+"%") @@ -119,7 +118,7 @@ func (r *mediaRepo) ListMedia(ctx context.Context, filter biz.MediaFilter) ([]*b if err := db.Limit(limit).Offset(offset).Order(orderKey).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.MediaFile, 0, len(pos)) + out := make([]*system.MediaFile, 0, len(pos)) for _, po := range pos { out = append(out, mediaFromPO(po)) } @@ -142,14 +141,14 @@ func (r *mediaRepo) MediaKeyReferences(ctx context.Context, key string) (int64, err := r.data.DB().WithContext(ctx).Model(&mediaPO{}).Where(map[string]any{"key": key}).Count(&count).Error return count, err } -func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*biz.MediaFile) error { +func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*system.MediaFile) error { pos := make([]mediaPO, 0, len(items)) for _, v := range items { pos = append(pos, mediaToPO(v)) } return r.data.DB().WithContext(ctx).Create(&pos).Error } -func (r *mediaRepo) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error { +func (r *mediaRepo) SaveCategory(ctx context.Context, v *system.AttachmentCategory) error { db := r.data.DB().WithContext(ctx) var count int64 query := db.Model(&categoryPO{}).Where("name = ? AND pid = ?", v.Name, v.ParentID) @@ -179,17 +178,17 @@ func (r *mediaRepo) DeleteCategory(ctx context.Context, id uint) error { } return r.data.DB().WithContext(ctx).Unscoped().Delete(&categoryPO{}, id).Error } -func (r *mediaRepo) ListCategories(ctx context.Context) ([]*biz.AttachmentCategory, error) { +func (r *mediaRepo) ListCategories(ctx context.Context) ([]*system.AttachmentCategory, error) { var pos []categoryPO if err := r.data.DB().WithContext(ctx).Find(&pos).Error; err != nil { return nil, err } - byID := map[uint]*biz.AttachmentCategory{} + byID := map[uint]*system.AttachmentCategory{} for _, po := range pos { // Children remains nil for leaf nodes, which serializes as null. - byID[po.ID] = &biz.AttachmentCategory{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID} + byID[po.ID] = &system.AttachmentCategory{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID} } - roots := []*biz.AttachmentCategory{} + roots := []*system.AttachmentCategory{} for _, po := range pos { v := byID[po.ID] if parent := byID[po.ParentID]; parent != nil { diff --git a/internal/data/repository/media_test.go b/internal/data/repository/media_test.go index cd91a3e..82348f4 100644 --- a/internal/data/repository/media_test.go +++ b/internal/data/repository/media_test.go @@ -2,20 +2,19 @@ package system import ( "context" + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func TestMediaListKeepsZeroPageSizeContract(t *testing.T) { data := newTransactionTestData(t) repo := &mediaRepo{data: data} ctx := context.Background() - if err := repo.CreateMedia(ctx, &biz.MediaFile{Name: "one.txt"}); err != nil { + if err := repo.CreateMedia(ctx, &system.MediaFile{Name: "one.txt"}); err != nil { t.Fatal(err) } - items, total, err := repo.ListMedia(ctx, biz.MediaFilter{}) + items, total, err := repo.ListMedia(ctx, system.MediaFilter{}) if err != nil { t.Fatal(err) } diff --git a/internal/data/repository/media_upload.go b/internal/data/repository/media_upload.go index ca30eba..37b2270 100644 --- a/internal/data/repository/media_upload.go +++ b/internal/data/repository/media_upload.go @@ -3,10 +3,9 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -42,30 +41,30 @@ type uploadChunkPO struct { func (uploadChunkPO) TableName() string { return "media_upload_chunks" } -func uploadFromPO(v uploadSessionPO) *biz.UploadSession { - return &biz.UploadSession{ID: v.ID, UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status, StorageKey: v.StorageKey, MediaID: v.MediaID} +func uploadFromPO(v uploadSessionPO) *system.UploadSession { + return &system.UploadSession{ID: v.ID, UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status, StorageKey: v.StorageKey, MediaID: v.MediaID} } -func (r *mediaRepo) FindCompletedSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) { +func (r *mediaRepo) FindCompletedSession(ctx context.Context, userID uint, hash string) (*system.UploadSession, error) { var po uploadSessionPO if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "completed").First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrUploadSessionNotFound + return nil, system.ErrUploadSessionNotFound } return nil, err } return uploadFromPO(po), nil } -func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) { +func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash string) (*system.UploadSession, error) { var po uploadSessionPO if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "uploading").First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrUploadSessionNotFound + return nil, system.ErrUploadSessionNotFound } return nil, err } return uploadFromPO(po), nil } -func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *biz.UploadSession) error { +func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *system.UploadSession) error { po := uploadSessionPO{UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status} if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -73,7 +72,7 @@ func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *biz.UploadSessio v.ID = po.ID return nil } -func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*biz.UploadSession, error) { +func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*system.UploadSession, error) { var po uploadSessionPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err @@ -95,21 +94,21 @@ func (r *mediaRepo) DeleteUploadSession(ctx context.Context, id uint) error { // for audit/recovery rather than physically removing it. return r.data.DB().WithContext(ctx).Delete(&uploadSessionPO{}, id).Error } -func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *biz.UploadChunk) error { +func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *system.UploadChunk) error { po := uploadChunkPO{UploadID: uploadID, ChunkIndex: v.Index, ChunkHash: v.Hash, Size: v.Size} return r.data.DB().WithContext(ctx).Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "upload_id"}, {Name: "chunk_index"}}, DoUpdates: clause.AssignmentColumns([]string{"chunk_hash", "size", "updated_at", "deleted_at"}), }).Create(&po).Error } -func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.UploadChunk, error) { +func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*system.UploadChunk, error) { var pos []uploadChunkPO if err := r.data.DB().WithContext(ctx).Where("upload_id = ?", uploadID).Order("chunk_index").Find(&pos).Error; err != nil { return nil, err } - out := make([]*biz.UploadChunk, 0, len(pos)) + out := make([]*system.UploadChunk, 0, len(pos)) for _, po := range pos { - out = append(out, &biz.UploadChunk{Index: po.ChunkIndex, Hash: po.ChunkHash, Size: po.Size}) + out = append(out, &system.UploadChunk{Index: po.ChunkIndex, Hash: po.ChunkHash, Size: po.Size}) } return out, nil } diff --git a/internal/data/repository/menu.go b/internal/data/repository/menu.go index 681a8d0..0a5c08b 100644 --- a/internal/data/repository/menu.go +++ b/internal/data/repository/menu.go @@ -3,31 +3,30 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" ) type menuRepo struct{ data Provider } -func NewMenuRepo(data Provider) biz.MenuRepo { return &menuRepo{data: data} } +func NewMenuRepo(data Provider) system.MenuRepo { return &menuRepo{data: data} } -func menuFromPO(po menuPO) *biz.Menu { +func menuFromPO(po menuPO) *system.Menu { var deletedAt *time.Time if po.DeletedAt.Valid { value := po.DeletedAt.Time deletedAt = &value } - return &biz.Menu{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, 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} + return &system.Menu{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, 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 { +func menuToPO(v *system.Menu) menuPO { return menuPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, 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 { +func replaceMenuRelations(tx *gorm.DB, menu *system.Menu) error { if err := tx.Unscoped().Where("sys_base_menu_id = ?", menu.ID).Delete(&menuParameterPO{}).Error; err != nil { return err } @@ -37,7 +36,7 @@ func replaceMenuRelations(tx *gorm.DB, menu *biz.Menu) error { return createMenuRelations(tx, menu) } -func createMenuRelations(tx *gorm.DB, menu *biz.Menu) error { +func createMenuRelations(tx *gorm.DB, menu *system.Menu) error { parameters := make([]menuParameterPO, 0, len(menu.Parameters)) for _, parameter := range menu.Parameters { parameters = append(parameters, menuParameterPO{ID: parameter.ID, CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, MenuID: menu.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) @@ -57,7 +56,7 @@ func createMenuRelations(tx *gorm.DB, menu *biz.Menu) error { return nil } -func (r *menuRepo) Create(ctx context.Context, v *biz.Menu) error { +func (r *menuRepo) Create(ctx context.Context, v *system.Menu) error { return r.data.DB().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 { @@ -98,7 +97,7 @@ func (r *menuRepo) Create(ctx context.Context, v *biz.Menu) error { }) } -func (r *menuRepo) Update(ctx context.Context, v *biz.Menu) error { +func (r *menuRepo) Update(ctx context.Context, v *system.Menu) error { return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var old menuPO if err := tx.First(&old, v.ID).Error; err != nil { @@ -156,12 +155,12 @@ func (r *menuRepo) Delete(ctx context.Context, id uint) error { }) } -func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error { +func (r *menuRepo) loadRelations(ctx context.Context, menu *system.Menu) error { var parameters []menuParameterPO if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Find(¶meters).Error; err != nil { return err } - menu.Parameters = make([]*biz.MenuParameter, 0, len(parameters)) + menu.Parameters = make([]*system.MenuParameter, 0, len(parameters)) for _, parameter := range parameters { menu.Parameters = append(menu.Parameters, menuParameterFromPO(parameter)) } @@ -169,14 +168,14 @@ func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error { if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Find(&buttons).Error; err != nil { return err } - menu.Buttons = make([]*biz.MenuButton, 0, len(buttons)) + menu.Buttons = make([]*system.MenuButton, 0, len(buttons)) for _, button := range buttons { menu.Buttons = append(menu.Buttons, menuButtonFromPO(button)) } return nil } -func (r *menuRepo) Find(ctx context.Context, id uint) (*biz.Menu, error) { +func (r *menuRepo) Find(ctx context.Context, id uint) (*system.Menu, error) { var po menuPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err @@ -188,11 +187,11 @@ func (r *menuRepo) Find(ctx context.Context, id uint) (*biz.Menu, error) { return menu, nil } -func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) { +func (r *menuRepo) List(ctx context.Context) ([]*system.Menu, error) { var pos []menuPO db := r.data.DB().WithContext(ctx).Model(&menuPO{}).Order("sort") config := r.data.Runtime().Admin() - if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth { + if actor, ok := system.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth { var authority authorityPO if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil { return nil, err @@ -204,20 +203,20 @@ func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) { if err := db.Find(&pos).Error; err != nil { return nil, err } - out := make([]*biz.Menu, 0, len(pos)) + out := make([]*system.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)) + byID := make(map[uint]*system.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{} + menu.Parameters = []*system.MenuParameter{} + menu.Buttons = []*system.MenuButton{} } var parameters []menuParameterPO if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Find(¶meters).Error; err != nil { @@ -238,22 +237,22 @@ func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) { return out, nil } -func menuParameterFromPO(po menuParameterPO) *biz.MenuParameter { +func menuParameterFromPO(po menuParameterPO) *system.MenuParameter { var deletedAt *time.Time if po.DeletedAt.Valid { value := po.DeletedAt.Time deletedAt = &value } - return &biz.MenuParameter{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, MenuID: po.MenuID, Type: po.Type, Key: po.Key, Value: po.Value} + return &system.MenuParameter{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, MenuID: po.MenuID, Type: po.Type, Key: po.Key, Value: po.Value} } -func menuButtonFromPO(po menuButtonPO) *biz.MenuButton { +func menuButtonFromPO(po menuButtonPO) *system.MenuButton { var deletedAt *time.Time if po.DeletedAt.Valid { value := po.DeletedAt.Time deletedAt = &value } - return &biz.MenuButton{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, Name: po.Name, Description: po.Description, MenuID: po.MenuID} + return &system.MenuButton{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, Name: po.Name, Description: po.Description, MenuID: po.MenuID} } func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error { @@ -292,7 +291,7 @@ func (r *menuRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error // menus. In particular, it deliberately does not call List: List applies the // current request actor's strict-auth filter, which would incorrectly hide // menus when an administrator inspects a different (child) authority. -func (r *menuRepo) ListAuthorityMenus(ctx context.Context, authorityID uint) ([]*biz.Menu, error) { +func (r *menuRepo) ListAuthorityMenus(ctx context.Context, authorityID uint) ([]*system.Menu, error) { var pos []menuPO db := r.data.DB().WithContext(ctx).Model(&menuPO{}). Where("id IN (?)", r.data.DB().WithContext(ctx).Model(&authorityMenuPO{}). @@ -301,7 +300,7 @@ func (r *menuRepo) ListAuthorityMenus(ctx context.Context, authorityID uint) ([] if err := db.Find(&pos).Error; err != nil { return nil, err } - out := make([]*biz.Menu, 0, len(pos)) + out := make([]*system.Menu, 0, len(pos)) for _, po := range pos { // This endpoint does not preload Parameters or MenuBtn. Keep // both relations nil so the response shape remains compatible. diff --git a/internal/data/repository/menu_test.go b/internal/data/repository/menu_test.go index b02e3a6..bb981ca 100644 --- a/internal/data/repository/menu_test.go +++ b/internal/data/repository/menu_test.go @@ -2,10 +2,10 @@ package system import ( "context" + "kra/internal/biz/system" "testing" "time" - "kra/internal/biz" "kra/internal/conf" ) @@ -31,7 +31,7 @@ func TestMenuCreatePreservesSubmittedTimestamps(t *testing.T) { ctx := context.Background() createdAt := time.Date(2025, time.January, 2, 3, 4, 5, 0, time.UTC) updatedAt := time.Date(2025, time.February, 3, 4, 5, 6, 0, time.UTC) - menu := &biz.Menu{CreatedAt: createdAt, UpdatedAt: updatedAt, Name: "created", Path: "created", Component: "view/created.vue", Title: "Created"} + menu := &system.Menu{CreatedAt: createdAt, UpdatedAt: updatedAt, Name: "created", Path: "created", Component: "view/created.vue", Title: "Created"} if err := (&menuRepo{data: data}).Create(ctx, menu); err != nil { t.Fatal(err) @@ -90,16 +90,16 @@ func TestMenuUpdateRebuildsRelationsWithoutChangingRequestedIdentity(t *testing. } repo := &menuRepo{data: data} - err := repo.Update(ctx, &biz.Menu{ + err := repo.Update(ctx, &system.Menu{ ID: 10, Name: "after", Path: "after", Component: "view/after.vue", Title: "After", - Parameters: []*biz.MenuParameter{{ + Parameters: []*system.MenuParameter{{ ID: 21, CreatedAt: createdAt, UpdatedAt: updatedAt, Type: "params", Key: "new", Value: "value", }}, - Buttons: []*biz.MenuButton{{ + Buttons: []*system.MenuButton{{ ID: 31, CreatedAt: createdAt, UpdatedAt: updatedAt, Name: "edit", Description: "new", }}, }) @@ -163,7 +163,7 @@ func TestSetMenuRolesStrictOnlyChangesManagedAuthorities(t *testing.T) { }).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &menuRepo{data: data} if err := repo.SetMenuRoles(ctx, menu.ID, []uint{siblingID}); err == nil { t.Fatal("SetMenuRoles() accepted an out-of-scope authority") diff --git a/internal/data/repository/models.go b/internal/data/repository/models.go index 4ebe473..68aa2ab 100644 --- a/internal/data/repository/models.go +++ b/internal/data/repository/models.go @@ -1,10 +1,9 @@ package system import ( + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" ) @@ -98,4 +97,4 @@ func (menuParameterPO) TableName() string { return "sys_base_menu_parameters" } type userRepo struct{ data Provider } -func NewUserRepo(data Provider) biz.UserRepo { return &userRepo{data: data} } +func NewUserRepo(data Provider) system.UserRepo { return &userRepo{data: data} } diff --git a/internal/data/repository/operation_log.go b/internal/data/repository/operation_log.go index 0e701c8..b6a729c 100644 --- a/internal/data/repository/operation_log.go +++ b/internal/data/repository/operation_log.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -30,13 +30,13 @@ type operationPO struct { func (operationPO) TableName() string { return "sys_operation_records" } -func (r *auditRecorderRepo) RecordOperation(ctx context.Context, v *biz.OperationRecord) error { +func (r *auditRecorderRepo) RecordOperation(ctx context.Context, v *system.OperationRecord) error { return r.data.DB().WithContext(ctx).Create(&operationPO{IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID}).Error } -func opFromPO(v operationPO) *biz.OperationRecord { - return &biz.OperationRecord{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID} +func opFromPO(v operationPO) *system.OperationRecord { + return &system.OperationRecord{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID} } -func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*biz.OperationRecord, int64, error) { +func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *system.OperationRecord) ([]*system.OperationRecord, int64, error) { db := r.data.DB().WithContext(ctx).Model(&operationPO{}) if q != nil { if q.Path != "" { @@ -58,7 +58,7 @@ func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q * return nil, 0, err } users := auditUsers(ctx, r.data.DB().WithContext(ctx), operationUserIDs(pos)) - out := make([]*biz.OperationRecord, 0, len(pos)) + out := make([]*system.OperationRecord, 0, len(pos)) for _, po := range pos { value := opFromPO(po) value.User = users[po.UserID] @@ -77,8 +77,8 @@ func operationUserIDs(values []operationPO) []uint { return ids } -func auditUsers(ctx context.Context, db *gorm.DB, ids []uint) map[uint]*biz.User { - result := make(map[uint]*biz.User) +func auditUsers(ctx context.Context, db *gorm.DB, ids []uint) map[uint]*system.User { + result := make(map[uint]*system.User) if len(ids) == 0 { return result } @@ -87,11 +87,11 @@ func auditUsers(ctx context.Context, db *gorm.DB, ids []uint) map[uint]*biz.User return result } for _, value := range users { - result[value.ID] = &biz.User{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, UUID: value.UUID, Username: value.Username, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, DeptID: value.DeptID, Phone: value.Phone, Email: value.Email, Enable: value.Enable} + result[value.ID] = &system.User{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, UUID: value.UUID, Username: value.Username, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, DeptID: value.DeptID, Phone: value.Phone, Email: value.Email, Enable: value.Enable} } return result } -func (r *auditQueryRepo) FindOperation(ctx context.Context, id uint) (*biz.OperationRecord, error) { +func (r *auditQueryRepo) FindOperation(ctx context.Context, id uint) (*system.OperationRecord, error) { var po operationPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err diff --git a/internal/data/repository/parameter.go b/internal/data/repository/parameter.go index 625a4a7..6f84f3f 100644 --- a/internal/data/repository/parameter.go +++ b/internal/data/repository/parameter.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "gorm.io/gorm" @@ -13,7 +13,7 @@ import ( type parameterRepo struct{ data Provider } -func NewParameterRepo(data Provider) biz.ParameterRepo { return ¶meterRepo{data: data} } +func NewParameterRepo(data Provider) system.ParameterRepo { return ¶meterRepo{data: data} } type parameterPO struct { ID uint `gorm:"primaryKey"` @@ -28,7 +28,7 @@ type parameterPO struct { func (parameterPO) TableName() string { return "sys_params" } -func (r *parameterRepo) CreateParameter(ctx context.Context, v *biz.SystemParameter) error { +func (r *parameterRepo) CreateParameter(ctx context.Context, v *system.SystemParameter) error { po := parameterPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc} if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -36,7 +36,7 @@ func (r *parameterRepo) CreateParameter(ctx context.Context, v *biz.SystemParame v.ID, v.CreatedAt, v.UpdatedAt = po.ID, po.CreatedAt, po.UpdatedAt return nil } -func (r *parameterRepo) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error { +func (r *parameterRepo) UpdateParameter(ctx context.Context, v *system.SystemParameter) error { // The compatible update uses a struct, so zero-value optional fields (notably desc) // are intentionally ignored rather than clearing an existing value. return r.data.DB().WithContext(ctx).Model(¶meterPO{}).Where("id = ?", v.ID).Updates(¶meterPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}).Error @@ -47,21 +47,21 @@ func (r *parameterRepo) DeleteParameters(ctx context.Context, ids []string) erro } return r.data.DB().WithContext(ctx).Delete(&[]parameterPO{}, "id in ?", ids).Error } -func (r *parameterRepo) FindParameterByID(ctx context.Context, id string) (*biz.SystemParameter, error) { +func (r *parameterRepo) FindParameterByID(ctx context.Context, id string) (*system.SystemParameter, error) { var po parameterPO if err := r.data.DB().WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil { return nil, err } return parameterFromPO(po), nil } -func (r *parameterRepo) FindParameterByKey(ctx context.Context, key string) (*biz.SystemParameter, error) { +func (r *parameterRepo) FindParameterByKey(ctx context.Context, key string) (*system.SystemParameter, error) { var po parameterPO if err := r.data.DB().WithContext(ctx).Where(parameterPO{Key: key}).First(&po).Error; err != nil { return nil, err } return parameterFromPO(po), nil } -func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*biz.SystemParameter, int64, error) { +func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *system.SystemParameter) ([]*system.SystemParameter, int64, error) { db := r.data.DB().WithContext(ctx).Model(¶meterPO{}) if q != nil { if q.StartCreatedAt != nil && q.EndCreatedAt != nil { @@ -82,7 +82,7 @@ func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *b if err := pagination.Apply(db, page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.SystemParameter, 0, len(pos)) + out := make([]*system.SystemParameter, 0, len(pos)) for _, po := range pos { out = append(out, parameterFromPO(po)) } diff --git a/internal/data/repository/parameter_test.go b/internal/data/repository/parameter_test.go index d0430bb..734ccb4 100644 --- a/internal/data/repository/parameter_test.go +++ b/internal/data/repository/parameter_test.go @@ -2,17 +2,16 @@ package system import ( "context" + "kra/internal/biz/system" "testing" "time" - - "kra/internal/biz" ) func TestParameterRepositoryPreservesModelMetadata(t *testing.T) { data := newTransactionTestData(t) repo := ¶meterRepo{data: data} createdAt := time.Date(2025, time.January, 2, 3, 4, 5, 0, time.Local) - value := &biz.SystemParameter{ID: 41, CreatedAt: createdAt, Name: "name", Key: "key", Value: "value"} + value := &system.SystemParameter{ID: 41, CreatedAt: createdAt, Name: "name", Key: "key", Value: "value"} if err := repo.CreateParameter(context.Background(), value); err != nil { t.Fatal(err) @@ -40,7 +39,7 @@ func TestParameterRepositoryKeepsReferenceEmptyQuerySemantics(t *testing.T) { data := newTransactionTestData(t) repo := ¶meterRepo{data: data} ctx := context.Background() - first := &biz.SystemParameter{Name: "first", Key: "first", Value: "1"} + first := &system.SystemParameter{Name: "first", Key: "first", Value: "1"} if err := repo.CreateParameter(ctx, first); err != nil { t.Fatal(err) } diff --git a/internal/data/repository/permission.go b/internal/data/repository/permission.go index b98d67a..9524191 100644 --- a/internal/data/repository/permission.go +++ b/internal/data/repository/permission.go @@ -3,16 +3,15 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" ) type permissionRepo struct{ data Provider } -func NewPermissionRepo(data Provider) biz.PermissionRepo { return &permissionRepo{data: data} } +func NewPermissionRepo(data Provider) system.PermissionRepo { return &permissionRepo{data: data} } type menuButtonPO struct { ID uint `gorm:"primaryKey"` @@ -34,12 +33,12 @@ type authorityButtonPO struct { func (authorityButtonPO) TableName() string { return "sys_authority_btns" } -func (r *permissionRepo) Buttons(ctx context.Context, menuID uint) ([]*biz.MenuButton, error) { +func (r *permissionRepo) Buttons(ctx context.Context, menuID uint) ([]*system.MenuButton, error) { var pos []menuButtonPO if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menuID).Find(&pos).Error; err != nil { return nil, err } - out := make([]*biz.MenuButton, 0, len(pos)) + out := make([]*system.MenuButton, 0, len(pos)) for _, po := range pos { out = append(out, menuButtonFromPO(po)) } diff --git a/internal/data/repository/permission_test.go b/internal/data/repository/permission_test.go index e5cbc0a..4f6b863 100644 --- a/internal/data/repository/permission_test.go +++ b/internal/data/repository/permission_test.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "testing" - "kra/internal/biz" "kra/internal/conf" ) @@ -108,7 +108,7 @@ func TestSetSelectedButtonsStrictRequiresManagedRoleAndOwnedButton(t *testing.T) if err := db.Create(&authorityButtonPO{AuthorityID: actorID, MenuID: 10, ButtonID: 31}).Error; err != nil { t.Fatal(err) } - ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID}) repo := &permissionRepo{data: data} if err := repo.SetSelectedButtons(ctx, siblingID, 10, []uint{31}); err == nil { t.Fatal("SetSelectedButtons() accepted an out-of-scope authority") diff --git a/internal/data/repository/position.go b/internal/data/repository/position.go index 321f953..4814564 100644 --- a/internal/data/repository/position.go +++ b/internal/data/repository/position.go @@ -3,16 +3,15 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" ) type positionRepo struct{ data Provider } -func NewPositionRepo(data Provider) biz.PositionRepo { return &positionRepo{data: data} } +func NewPositionRepo(data Provider) system.PositionRepo { return &positionRepo{data: data} } type positionPO struct { ID uint `gorm:"primaryKey"` @@ -37,13 +36,13 @@ type userPositionPO struct { func (userPositionPO) TableName() string { return "sys_user_positions" } -func posFromPO(po positionPO) *biz.Position { - return &biz.Position{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Code: po.Code, Sort: po.Sort, Status: po.Status, Remark: po.Remark} +func posFromPO(po positionPO) *system.Position { + return &system.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 *positionRepo) CreatePosition(ctx context.Context, v *biz.Position) error { +func (r *positionRepo) CreatePosition(ctx context.Context, v *system.Position) error { return r.data.DB().WithContext(ctx).Create(&positionPO{Name: v.Name, Code: v.Code, Sort: v.Sort, Status: v.Status, Remark: v.Remark}).Error } -func (r *positionRepo) UpdatePosition(ctx context.Context, v *biz.Position) error { +func (r *positionRepo) UpdatePosition(ctx context.Context, v *system.Position) error { return r.data.DB().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 *positionRepo) DeletePosition(ctx context.Context, id uint) error { @@ -59,14 +58,14 @@ func (r *positionRepo) DeletePosition(ctx context.Context, id uint) error { } return r.data.DB().WithContext(ctx).Delete(&positionPO{}, id).Error } -func (r *positionRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) { +func (r *positionRepo) FindPosition(ctx context.Context, id uint) (*system.Position, error) { var po positionPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return posFromPO(po), nil } -func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *biz.PositionListFilter) ([]*biz.Position, int64, error) { +func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *system.PositionListFilter) ([]*system.Position, int64, error) { db := r.data.DB().WithContext(ctx).Model(&positionPO{}) if q != nil { if q.Name != "" { @@ -90,7 +89,7 @@ func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *biz if err := db.Order("sort").Limit(size).Offset(size * (page - 1)).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.Position, 0, len(pos)) + out := make([]*system.Position, 0, len(pos)) for _, po := range pos { out = append(out, posFromPO(po)) } diff --git a/internal/data/repository/runtime.go b/internal/data/repository/runtime.go index fa0e737..bc15762 100644 --- a/internal/data/repository/runtime.go +++ b/internal/data/repository/runtime.go @@ -4,9 +4,9 @@ package system import ( "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/internal/conf" "kra/internal/security" @@ -15,7 +15,7 @@ import ( type runtimeSettings struct{ runtime *conf.Runtime } -func NewRuntimeSettings(runtime *conf.Runtime) biz.RuntimeSettings { +func NewRuntimeSettings(runtime *conf.Runtime) system.RuntimeSettings { return &runtimeSettings{runtime: runtime} } @@ -27,8 +27,8 @@ func (s *runtimeSettings) RouterPrefix() string { return config.RouterPrefix } -func (s *runtimeSettings) JWTSettings() biz.JWTSettings { - value := biz.JWTSettings{Issuer: "kra", Expires: 7 * 24 * time.Hour, Buffer: 24 * time.Hour} +func (s *runtimeSettings) JWTSettings() system.JWTSettings { + value := system.JWTSettings{Issuer: "kra", Expires: 7 * 24 * time.Hour, Buffer: 24 * time.Hour} config := s.runtime.Admin() if config == nil || config.Jwt == nil { return value @@ -46,8 +46,8 @@ func (s *runtimeSettings) JWTSettings() biz.JWTSettings { return value } -func (s *runtimeSettings) CaptchaSettings() biz.CaptchaSettings { - value := biz.CaptchaSettings{KeyLong: 6, ImageWidth: 240, ImageHeight: 80, StoreExpiration: 3 * time.Minute} +func (s *runtimeSettings) CaptchaSettings() system.CaptchaSettings { + value := system.CaptchaSettings{KeyLong: 6, ImageWidth: 240, ImageHeight: 80, StoreExpiration: 3 * time.Minute} config := s.runtime.Admin() if config == nil || config.Captcha == nil { return value @@ -67,12 +67,12 @@ func (s *runtimeSettings) CaptchaSettings() biz.CaptchaSettings { return value } -func (s *runtimeSettings) MediaSettings() biz.MediaSettings { +func (s *runtimeSettings) MediaSettings() system.MediaSettings { config := s.runtime.Admin() if config == nil || config.Media == nil { - return biz.MediaSettings{} + return system.MediaSettings{} } - return biz.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize, ChunkDir: config.Media.ChunkDir} + return system.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize, ChunkDir: config.Media.ChunkDir} } func (s *runtimeSettings) UseMultipoint() bool { @@ -80,13 +80,13 @@ func (s *runtimeSettings) UseMultipoint() bool { return config != nil && config.System != nil && config.System.UseMultipoint } -type tokenIssuer struct{ settings biz.RuntimeSettings } +type tokenIssuer struct{ settings system.RuntimeSettings } -func NewTokenIssuer(settings biz.RuntimeSettings) biz.TokenIssuer { +func NewTokenIssuer(settings system.RuntimeSettings) system.TokenIssuer { return &tokenIssuer{settings: settings} } -func (i *tokenIssuer) IssueToken(user *biz.User, authorityID uint, mustChangePassword bool, expires time.Duration) (*biz.IssuedToken, error) { +func (i *tokenIssuer) IssueToken(user *system.User, authorityID uint, mustChangePassword bool, expires time.Duration) (*system.IssuedToken, error) { settings := i.settings.JWTSettings() if expires <= 0 { expires = settings.Expires @@ -99,10 +99,10 @@ func (i *tokenIssuer) IssueToken(user *biz.User, authorityID uint, mustChangePas if err != nil { return nil, err } - return &biz.IssuedToken{Value: token, ExpiresAt: claims.ExpiresAt.Time, TTL: expires}, nil + return &system.IssuedToken{Value: token, ExpiresAt: claims.ExpiresAt.Time, TTL: expires}, nil } -func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*biz.IssuedToken, error) { +func (i *tokenIssuer) ReissueToken(source *system.AuthClaims, authorityID uint) (*system.IssuedToken, error) { if source == nil { return nil, errors.New("nil JWT claims") } @@ -120,23 +120,23 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b if err != nil { return nil, err } - return &biz.IssuedToken{Value: token, ExpiresAt: source.ExpiresAt, TTL: time.Until(source.ExpiresAt)}, nil + return &system.IssuedToken{Value: token, ExpiresAt: source.ExpiresAt, TTL: time.Until(source.ExpiresAt)}, nil } -func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) { +func (i *tokenIssuer) ParseToken(token string) (*system.AuthClaims, error) { claims, err := security.Parse(token, i.settings.JWTSettings().SigningKey) if err != nil { switch { case errors.Is(err, security.ErrTokenExpired): - return nil, biz.ErrTokenExpired + return nil, system.ErrTokenExpired case errors.Is(err, security.ErrTokenMalformed): - return nil, biz.ErrTokenMalformed + return nil, system.ErrTokenMalformed case errors.Is(err, security.ErrTokenSignatureInvalid): - return nil, biz.ErrTokenSignatureInvalid + return nil, system.ErrTokenSignatureInvalid case errors.Is(err, security.ErrTokenNotValidYet): - return nil, biz.ErrTokenNotValidYet + return nil, system.ErrTokenNotValidYet default: - return nil, biz.ErrTokenInvalid + return nil, system.ErrTokenInvalid } } audience := append([]string(nil), claims.Audience...) @@ -144,5 +144,5 @@ func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) { if claims.IssuedAt != nil { issuedAt = claims.IssuedAt.Time } - return &biz.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, UserType: claims.UserType, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, PasswordVersion: claims.PasswordVersion, Issuer: claims.Issuer, Audience: audience, IssuedAt: issuedAt, NotBefore: claims.NotBefore.Time, ExpiresAt: claims.ExpiresAt.Time}, nil + return &system.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, UserType: claims.UserType, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, PasswordVersion: claims.PasswordVersion, Issuer: claims.Issuer, Audience: audience, IssuedAt: issuedAt, NotBefore: claims.NotBefore.Time, ExpiresAt: claims.ExpiresAt.Time}, nil } diff --git a/internal/data/repository/security.go b/internal/data/repository/security.go index a689b4a..817423c 100644 --- a/internal/data/repository/security.go +++ b/internal/data/repository/security.go @@ -3,10 +3,9 @@ package system import ( "context" "errors" + "kra/internal/biz/system" "time" - "kra/internal/biz" - "gorm.io/gorm" ) @@ -51,17 +50,17 @@ func DefaultSecurityConfig() SecurityConfigPO { type securityRepo struct{ data DatabaseProvider } -func NewSecurityRepo(data DatabaseProvider) biz.SecurityRepo { return &securityRepo{data: data} } +func NewSecurityRepo(data DatabaseProvider) system.SecurityRepo { return &securityRepo{data: data} } -func securityFromPO(v SecurityConfigPO) *biz.SecurityConfig { - return &biz.SecurityConfig{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} +func securityFromPO(v SecurityConfigPO) *system.SecurityConfig { + return &system.SecurityConfig{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } -func securityToPO(v *biz.SecurityConfig) SecurityConfigPO { +func securityToPO(v *system.SecurityConfig) SecurityConfigPO { return SecurityConfigPO{ID: 1, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } -func (r *securityRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig, error) { +func (r *securityRepo) SecurityConfig(ctx context.Context) (*system.SecurityConfig, error) { if !r.data.DatabaseReady() { po := DefaultSecurityConfig() po.ID = 0 @@ -80,7 +79,7 @@ func (r *securityRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig, return securityFromPO(po), nil } -func (r *securityRepo) SaveSecurityConfig(ctx context.Context, v *biz.SecurityConfig) error { +func (r *securityRepo) SaveSecurityConfig(ctx context.Context, v *system.SecurityConfig) error { po := securityToPO(v) if err := r.data.DB().WithContext(ctx).Save(&po).Error; err != nil { return err diff --git a/internal/data/repository/seed.go b/internal/data/repository/seed.go index b12a4cd..bdef008 100644 --- a/internal/data/repository/seed.go +++ b/internal/data/repository/seed.go @@ -2,10 +2,10 @@ package system import ( "context" + "kra/internal/biz/system" "strings" "time" - "kra/internal/biz" platformmodule "kra/pkg/module" "github.com/google/uuid" @@ -13,14 +13,14 @@ import ( "gorm.io/gorm" ) -func SeedSystem(ctx context.Context, db *gorm.DB, input *biz.DatabaseConfig, surfaces ...platformmodule.Surface) error { +func SeedSystem(ctx context.Context, db *gorm.DB, input *system.DatabaseConfig, surfaces ...platformmodule.Surface) error { return seedSystem(ctx, db, input, nil, surfaces...) } // SeedSystemWithCatalog applies module-contributed administration surfaces and // default timed tasks in one transaction. The system module remains the owner // of the system tables, while other modules contribute through the catalog. -func SeedSystemWithCatalog(ctx context.Context, db *gorm.DB, input *biz.DatabaseConfig, catalog platformmodule.Catalog) error { +func SeedSystemWithCatalog(ctx context.Context, db *gorm.DB, input *system.DatabaseConfig, catalog platformmodule.Catalog) error { surfaces := make([]platformmodule.Surface, 0, len(catalog.Definitions)) for _, definition := range catalog.Definitions { surfaces = append(surfaces, definition.Surface) @@ -28,7 +28,7 @@ func SeedSystemWithCatalog(ctx context.Context, db *gorm.DB, input *biz.Database return seedSystem(ctx, db, input, catalog.DefaultTimedTasks(), surfaces...) } -func seedSystem(ctx context.Context, db *gorm.DB, input *biz.DatabaseConfig, defaults []platformmodule.TimedTask, surfaces ...platformmodule.Surface) error { +func seedSystem(ctx context.Context, db *gorm.DB, input *system.DatabaseConfig, defaults []platformmodule.TimedTask, surfaces ...platformmodule.Surface) error { return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { rootParentID := uint(0) authority := authorityPO{AuthorityID: 888, AuthorityName: "超级管理员", ParentID: &rootParentID, DataScope: 1, DefaultRouter: "dashboard"} diff --git a/internal/data/repository/seed_test.go b/internal/data/repository/seed_test.go index d98c5e0..78aab0a 100644 --- a/internal/data/repository/seed_test.go +++ b/internal/data/repository/seed_test.go @@ -2,9 +2,9 @@ package system import ( "context" + "kra/internal/biz/system" "testing" - "kra/internal/biz" platformmodule "kra/pkg/module" ) @@ -20,7 +20,7 @@ func TestSeedSystemCreatesInitialDataAndModuleSurface(t *testing.T) { Menus: []platformmodule.Menu{{Name: "orders", Path: "orders", ParentName: "extensions", Component: "view/orders.vue", Title: "订单", Sort: 6}}, APIs: []platformmodule.API{{Path: "/orders", Method: "GET", Group: "订单", Description: "订单列表"}}, } - input := &biz.DatabaseConfig{AdminPassword: "admin-password", APIs: []*biz.API{{Path: "/healthz", Method: "GET", APIGroup: "系统"}}} + input := &system.DatabaseConfig{AdminPassword: "admin-password", APIs: []*system.API{{Path: "/healthz", Method: "GET", APIGroup: "系统"}}} if err = SeedSystem(context.Background(), db, input, surface); err != nil { t.Fatal(err) } diff --git a/internal/data/repository/task.go b/internal/data/repository/task.go index c4c9cd7..d9b4db0 100644 --- a/internal/data/repository/task.go +++ b/internal/data/repository/task.go @@ -2,7 +2,7 @@ package system import ( "context" - "kra/internal/biz" + "kra/internal/biz/system" "kra/pkg/database/gormkit" "kra/pkg/database/pagination" "time" @@ -46,14 +46,14 @@ func (taskLogPO) TableName() string { return "sys_timed_task_logs" } type taskRepo struct{ data Provider } -func NewTaskRepo(data Provider) biz.TaskRepo { return &taskRepo{data: data} } -func taskToPO(v *biz.TimedTask) taskPO { +func NewTaskRepo(data Provider) system.TaskRepo { return &taskRepo{data: data} } +func taskToPO(v *system.TimedTask) taskPO { return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: gormkit.JSON(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: gormkit.JSON(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } -func taskFromPO(v taskPO) *biz.TimedTask { - return &biz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} +func taskFromPO(v taskPO) *system.TimedTask { + return &system.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } -func (r *taskRepo) CreateTask(ctx context.Context, v *biz.TimedTask) error { +func (r *taskRepo) CreateTask(ctx context.Context, v *system.TimedTask) error { po := taskToPO(v) if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -70,26 +70,26 @@ func (r *taskRepo) TaskNameExists(ctx context.Context, name string, excludeID ui err := db.Count(&count).Error return count > 0, err } -func (r *taskRepo) UpdateTask(ctx context.Context, v *biz.TimedTask) error { +func (r *taskRepo) UpdateTask(ctx context.Context, v *system.TimedTask) error { po := taskToPO(v) return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error } func (r *taskRepo) DeleteTask(ctx context.Context, id uint) error { return r.data.DB().WithContext(ctx).Delete(&taskPO{}, id).Error } -func (r *taskRepo) FindTask(ctx context.Context, id uint) (*biz.TimedTask, error) { +func (r *taskRepo) FindTask(ctx context.Context, id uint) (*system.TimedTask, error) { var po taskPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return taskFromPO(po), nil } -func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*biz.TimedTask, int64, error) { +func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.TimedTask) ([]*system.TimedTask, int64, error) { // During first-install the data layer intentionally serves a bootstrap // database without system tables. The scheduler starts before /init/initdb // and should remain idle instead of logging a missing-table SQL error. if !r.data.DatabaseReady() { - return []*biz.TimedTask{}, 0, nil + return []*system.TimedTask{}, 0, nil } db := r.data.DB().WithContext(ctx).Model(&taskPO{}) if q != nil { @@ -111,7 +111,7 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTa if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.TimedTask, 0, len(pos)) + out := make([]*system.TimedTask, 0, len(pos)) for _, po := range pos { out = append(out, taskFromPO(po)) } @@ -120,13 +120,13 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTa func (r *taskRepo) ToggleTask(ctx context.Context, id uint, enabled bool) error { return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", id).Update("enabled", enabled).Error } -func (r *taskRepo) RecordTaskLog(ctx context.Context, v *biz.TimedTaskLog) error { +func (r *taskRepo) RecordTaskLog(ctx context.Context, v *system.TimedTaskLog) error { return r.data.DB().WithContext(ctx).Create(&taskLogPO{TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}).Error } -func taskLogFromPO(v taskLogPO) *biz.TimedTaskLog { - return &biz.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output} +func taskLogFromPO(v taskLogPO) *system.TimedTaskLog { + return &system.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output} } -func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*biz.TimedTaskLog, int64, error) { +func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*system.TimedTaskLog, int64, error) { db := r.data.DB().WithContext(ctx).Model(&taskLogPO{}) if taskID != 0 { db = db.Where("task_id = ?", taskID) @@ -142,7 +142,7 @@ func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.TimedTaskLog, 0, len(pos)) + out := make([]*system.TimedTaskLog, 0, len(pos)) for _, po := range pos { out = append(out, taskLogFromPO(po)) } diff --git a/internal/data/repository/transactions_test.go b/internal/data/repository/transactions_test.go index c3b00c4..f0a4142 100644 --- a/internal/data/repository/transactions_test.go +++ b/internal/data/repository/transactions_test.go @@ -2,10 +2,10 @@ package system import ( "context" + "kra/internal/biz/system" "strings" "testing" - "kra/internal/biz" "kra/internal/conf" ) @@ -31,7 +31,7 @@ func TestUserAuthorityWritesAreAtomic(t *testing.T) { t.Fatal(err) } repo := &userRepo{data: data} - user := &biz.User{Username: "atomic", Password: "hash", NickName: "before", AuthorityID: 888, Enable: 1} + user := &system.User{Username: "atomic", Password: "hash", NickName: "before", AuthorityID: 888, Enable: 1} created, err := repo.CreateUserWithAuthorities(ctx, user, []uint{888, 999}) if err != nil { t.Fatal(err) @@ -61,7 +61,7 @@ func TestUserAuthorityWritesAreAtomic(t *testing.T) { t.Fatalf("missing-authority link count = %d, err = %v", missingAuthorityLinks, err) } - withNewAuthority := &biz.User{Username: "with-new-authority", Password: "hash", AuthorityID: 888, Enable: 1} + withNewAuthority := &system.User{Username: "with-new-authority", Password: "hash", AuthorityID: 888, Enable: 1} createdWithNewAuthority, err := repo.CreateUserWithAuthorities(ctx, withNewAuthority, []uint{888, 123456}) if err != nil { t.Fatal(err) @@ -116,7 +116,7 @@ func TestUserRegistrationDoesNotAddPrimaryAuthorityToRequestedAssociations(t *te if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{{AuthorityID: 888, AuthorityName: "primary"}, {AuthorityID: 999, AuthorityName: "requested"}}).Error; err != nil { t.Fatal(err) } - created, err := (&userRepo{data: data}).CreateUserWithAuthorities(ctx, &biz.User{Username: "association-shape", Password: "hash", AuthorityID: 888, Enable: 1}, []uint{999}) + created, err := (&userRepo{data: data}).CreateUserWithAuthorities(ctx, &system.User{Username: "association-shape", Password: "hash", AuthorityID: 888, Enable: 1}, []uint{999}) if err != nil { t.Fatal(err) } @@ -173,8 +173,8 @@ func TestDictionaryImportKeepsHierarchyInOneTransaction(t *testing.T) { repo := &dictionaryRepo{data: data} parentID := uint(10) active := true - dictionary := &biz.Dictionary{Name: "status", Type: "status", Status: &active} - details := []*biz.DictionaryDetail{{ID: 10, Label: "parent", Value: "1", Status: &active}, {ID: 11, Label: "child", Value: "2", ParentID: &parentID, Status: &active}} + dictionary := &system.Dictionary{Name: "status", Type: "status", Status: &active} + details := []*system.DictionaryDetail{{ID: 10, Label: "parent", Value: "1", Status: &active}, {ID: 11, Label: "child", Value: "2", ParentID: &parentID, Status: &active}} if err := repo.ImportDictionary(ctx, dictionary, details); err != nil { t.Fatal(err) } diff --git a/internal/data/repository/user.go b/internal/data/repository/user.go index 7524349..89a71e2 100644 --- a/internal/data/repository/user.go +++ b/internal/data/repository/user.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" "errors" + "kra/internal/biz/system" "strconv" "strings" "time" - "kra/internal/biz" "kra/pkg/database/pagination" "github.com/google/uuid" @@ -16,40 +16,40 @@ import ( "gorm.io/gorm/clause" ) -func (r *userRepo) FindUserByUsername(ctx context.Context, username string) (*biz.User, error) { +func (r *userRepo) FindUserByUsername(ctx context.Context, username string) (*system.User, error) { var po userPO if err := r.data.DB().WithContext(ctx).Where("username = ?", username).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrUserNotFound + return nil, system.ErrUserNotFound } return nil, err } return r.loadUser(ctx, &po) } -func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) { +func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*system.User, error) { var po userPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrUserNotFound + return nil, system.ErrUserNotFound } return nil, err } return r.loadUser(ctx, &po) } -func (r *userRepo) FindUserByUUID(ctx context.Context, value string) (*biz.User, error) { +func (r *userRepo) FindUserByUUID(ctx context.Context, value string) (*system.User, error) { var po userPO if err := r.data.DB().WithContext(ctx).Where("uuid = ?", value).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, biz.ErrUserNotFound + return nil, system.ErrUserNotFound } return nil, err } return r.loadUser(ctx, &po) } -func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error) { +func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*system.User, error) { var authority authorityPO if err := r.data.DB().WithContext(ctx).First(&authority, "authority_id = ?", po.AuthorityID).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err @@ -58,7 +58,7 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error) if err := r.data.DB().WithContext(ctx).Table("sys_authorities").Joins("JOIN sys_user_authority ON sys_user_authority.sys_authority_authority_id = sys_authorities.authority_id").Where("sys_user_authority.sys_user_id = ? AND sys_authorities.deleted_at IS NULL", po.ID).Find(&authorityPOs).Error; err != nil { return nil, err } - authorities := make([]biz.Authority, 0, len(authorityPOs)) + authorities := make([]system.Authority, 0, len(authorityPOs)) for _, item := range authorityPOs { authorities = append(authorities, toBizAuthority(item)) } @@ -70,12 +70,12 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error) if err := r.data.DB().WithContext(ctx).Table("sys_departments").Joins("JOIN sys_user_departments ON sys_user_departments.sys_department_id = sys_departments.id").Where("sys_user_departments.sys_user_id = ? AND sys_departments.deleted_at IS NULL", po.ID).Find(&departmentPOs).Error; err != nil { return nil, err } - departments := make([]biz.Department, 0, len(departmentPOs)) + departments := make([]system.Department, 0, len(departmentPOs)) for _, item := range departmentPOs { value := deptFromPO(item) departments = append(departments, *value) } - var primary *biz.Department + var primary *system.Department if po.DeptID != 0 { var primaryPO departmentPO if err := r.data.DB().WithContext(ctx).First(&primaryPO, po.DeptID).Error; err == nil { @@ -88,33 +88,33 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error) if err := r.data.DB().WithContext(ctx).Table("sys_positions").Joins("JOIN sys_user_positions ON sys_user_positions.sys_position_id = sys_positions.id").Where("sys_user_positions.sys_user_id = ? AND sys_positions.deleted_at IS NULL", po.ID).Find(&positionPOs).Error; err != nil { return nil, err } - positions := make([]biz.Position, 0, len(positionPOs)) + positions := make([]system.Position, 0, len(positionPOs)) for _, item := range positionPOs { positions = append(positions, *posFromPO(item)) } - return &biz.User{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UUID: po.UUID, Username: po.Username, Password: po.Password, NickName: po.NickName, HeaderImg: po.HeaderImg, AuthorityID: po.AuthorityID, Authority: toBizAuthority(authority), Authorities: authorities, DeptID: po.DeptID, Department: primary, Departments: departments, Positions: positions, Phone: po.Phone, Email: po.Email, Enable: po.Enable, OriginSetting: setting, MustChangePassword: po.MustChangePassword, PasswordUpdatedAt: po.PasswordUpdatedAt}, nil + return &system.User{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UUID: po.UUID, Username: po.Username, Password: po.Password, NickName: po.NickName, HeaderImg: po.HeaderImg, AuthorityID: po.AuthorityID, Authority: toBizAuthority(authority), Authorities: authorities, DeptID: po.DeptID, Department: primary, Departments: departments, Positions: positions, Phone: po.Phone, Email: po.Email, Enable: po.Enable, OriginSetting: setting, MustChangePassword: po.MustChangePassword, PasswordUpdatedAt: po.PasswordUpdatedAt}, nil } -func toBizAuthority(po authorityPO) biz.Authority { +func toBizAuthority(po authorityPO) system.Authority { var deletedAt *time.Time if po.DeletedAt.Valid { value := po.DeletedAt.Time deletedAt = &value } - return biz.Authority{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, AuthorityID: po.AuthorityID, AuthorityName: po.AuthorityName, ParentID: po.ParentID, DataScope: po.DataScope, DefaultRouter: po.DefaultRouter} + return system.Authority{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, AuthorityID: po.AuthorityID, AuthorityName: po.AuthorityName, ParentID: po.ParentID, DataScope: po.DataScope, DefaultRouter: po.DefaultRouter} } -func baseBizUser(po *userPO) *biz.User { +func baseBizUser(po *userPO) *system.User { setting := map[string]any(nil) if po.OriginSetting != "" { _ = json.Unmarshal([]byte(po.OriginSetting), &setting) } - return &biz.User{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UUID: po.UUID, Username: po.Username, Password: po.Password, NickName: po.NickName, HeaderImg: po.HeaderImg, AuthorityID: po.AuthorityID, DeptID: po.DeptID, Phone: po.Phone, Email: po.Email, Enable: po.Enable, OriginSetting: setting, MustChangePassword: po.MustChangePassword, PasswordUpdatedAt: po.PasswordUpdatedAt} + return &system.User{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, UUID: po.UUID, Username: po.Username, Password: po.Password, NickName: po.NickName, HeaderImg: po.HeaderImg, AuthorityID: po.AuthorityID, DeptID: po.DeptID, Phone: po.Phone, Email: po.Email, Enable: po.Enable, OriginSetting: setting, MustChangePassword: po.MustChangePassword, PasswordUpdatedAt: po.PasswordUpdatedAt} } -func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*biz.User, error) { +func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*system.User, error) { if len(pos) == 0 { - return []*biz.User{}, nil + return []*system.User{}, nil } db := r.data.DB().WithContext(ctx) userIDs := make([]uint, 0, len(pos)) @@ -186,18 +186,18 @@ func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*biz.User, er positionByID[item.ID] = item } - users := make([]*biz.User, 0, len(pos)) + users := make([]*system.User, 0, len(pos)) for i := range pos { po := &pos[i] user := baseBizUser(po) user.Authority = toBizAuthority(authorityByID[po.AuthorityID]) - user.Authorities = make([]biz.Authority, 0, len(authorityByUser[po.ID])) + user.Authorities = make([]system.Authority, 0, len(authorityByUser[po.ID])) for _, id := range authorityByUser[po.ID] { if item, ok := authorityByID[id]; ok { user.Authorities = append(user.Authorities, toBizAuthority(item)) } } - user.Departments = make([]biz.Department, 0, len(departmentByUser[po.ID])) + user.Departments = make([]system.Department, 0, len(departmentByUser[po.ID])) for _, id := range departmentByUser[po.ID] { if item, ok := departmentByID[id]; ok { user.Departments = append(user.Departments, *deptFromPO(item)) @@ -206,7 +206,7 @@ func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*biz.User, er if primary, ok := departmentByID[po.DeptID]; ok { user.Department = deptFromPO(primary) } - user.Positions = make([]biz.Position, 0, len(positionByUser[po.ID])) + user.Positions = make([]system.Position, 0, len(positionByUser[po.ID])) for _, id := range positionByUser[po.ID] { if item, ok := positionByID[id]; ok { user.Positions = append(user.Positions, *posFromPO(item)) @@ -226,7 +226,7 @@ func (r *userRepo) HasAuthorityMenu(ctx context.Context, authorityID uint, name return count > 0, err } -func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*biz.Menu, error) { +func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*system.Menu, error) { var pos []menuPO err := r.data.DB().WithContext(ctx).Table("sys_base_menus"). Joins("JOIN sys_authority_menus ON sys_authority_menus.sys_base_menu_id = sys_base_menus.id"). @@ -235,13 +235,13 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*b if err != nil { return nil, err } - byID := make(map[uint]*biz.Menu, len(pos)) + byID := make(map[uint]*system.Menu, len(pos)) menuIDs := make([]uint, 0, len(pos)) for _, po := range pos { menu := menuFromPO(po) menu.MenuID = po.ID menu.Children = nil - menu.Parameters = []*biz.MenuParameter{} + menu.Parameters = []*system.MenuParameter{} menu.Buttons = nil byID[po.ID] = menu menuIDs = append(menuIDs, po.ID) @@ -266,7 +266,7 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*b menu.Buttons = append(menu.Buttons, value) } } - var roots []*biz.Menu + var roots []*system.Menu for _, po := range pos { menu := byID[po.ID] if parent := byID[po.ParentID]; parent != nil { @@ -278,7 +278,7 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*b return roots, nil } -func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*biz.User, int64, error) { +func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *system.UserListFilter) ([]*system.User, int64, error) { db := r.data.DB().WithContext(ctx).Model(&userPO{}) order := "id desc" if filter != nil { @@ -314,11 +314,11 @@ func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *bi return users, total, err } -func (r *userRepo) CreateUser(ctx context.Context, user *biz.User) (*biz.User, error) { +func (r *userRepo) CreateUser(ctx context.Context, user *system.User) (*system.User, error) { return r.CreateUserWithAuthorities(ctx, user, nil) } -func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *biz.User, authorityIDs []uint) (*biz.User, error) { +func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *system.User, authorityIDs []uint) (*system.User, error) { requestedAuthorities := append([]uint{user.AuthorityID}, authorityIDs...) if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDsAuth(ctx, requestedAuthorities); err != nil { return nil, err @@ -337,7 +337,7 @@ func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *biz.User } now := time.Now() po := userPO{UUID: user.UUID, Username: user.Username, Password: user.Password, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Phone: user.Phone, Email: user.Email, Enable: user.Enable, PasswordUpdatedAt: &now, MustChangePassword: user.MustChangePassword} - var createdAuthorities []biz.Authority + var createdAuthorities []system.Authority if err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { // The compatible registration rejects an existing non-deleted username before creating the // account. The username column is intentionally not unique for legacy @@ -381,14 +381,14 @@ func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *biz.User return created, nil } -func (r *userRepo) UpdateUser(ctx context.Context, user *biz.User) error { +func (r *userRepo) UpdateUser(ctx context.Context, user *system.User) error { if err := r.checkUserUpdateAuth(ctx, user); err != nil { return err } return r.updateUser(r.data.DB().WithContext(ctx), user) } -func (r *userRepo) checkUserUpdateAuth(ctx context.Context, user *biz.User) error { +func (r *userRepo) checkUserUpdateAuth(ctx context.Context, user *system.User) error { access := &authorityAccessRepo{data: r.data} if err := access.checkUserIDAuth(ctx, user.ID, false); err != nil { return err @@ -399,7 +399,7 @@ func (r *userRepo) checkUserUpdateAuth(ctx context.Context, user *biz.User) erro return nil } -func (r *userRepo) UpdateSelfUser(ctx context.Context, user *biz.User) error { +func (r *userRepo) UpdateSelfUser(ctx context.Context, user *system.User) error { updates := make(map[string]any) if user.NickName != "" { updates["nick_name"] = user.NickName @@ -422,7 +422,7 @@ func (r *userRepo) UpdateSelfUser(ctx context.Context, user *biz.User) error { return r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error } -func (r *userRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User) error { +func (r *userRepo) FillDepartmentNamePaths(ctx context.Context, user *system.User) error { if user == nil || len(user.Departments) == 0 { return nil } @@ -463,7 +463,7 @@ func (r *userRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User) return nil } -func (r *userRepo) updateUser(tx *gorm.DB, user *biz.User) error { +func (r *userRepo) updateUser(tx *gorm.DB, user *system.User) error { updates := map[string]any{"nick_name": user.NickName, "header_img": user.HeaderImg, "phone": user.Phone, "email": user.Email, "enable": user.Enable} if user.AuthorityID != 0 { updates["authority_id"] = user.AuthorityID @@ -471,7 +471,7 @@ func (r *userRepo) updateUser(tx *gorm.DB, user *biz.User) error { return tx.Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error } -func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.User, authorityIDs []uint) error { +func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *system.User, authorityIDs []uint) error { access := &authorityAccessRepo{data: r.data} if err := r.checkUserUpdateAuth(ctx, user); err != nil { return err @@ -520,12 +520,12 @@ func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string, } return r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Updates(updates).Error } -func (r *userRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) { +func (r *userRepo) ListAuthorities(ctx context.Context) ([]*system.Authority, error) { var pos []authorityPO if err := r.data.DB().WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil { return nil, err } - result := make([]*biz.Authority, 0, len(pos)) + result := make([]*system.Authority, 0, len(pos)) for _, po := range pos { a := toBizAuthority(po) result = append(result, &a) diff --git a/internal/data/repository/user_strict_test.go b/internal/data/repository/user_strict_test.go index 9933607..286dbbc 100644 --- a/internal/data/repository/user_strict_test.go +++ b/internal/data/repository/user_strict_test.go @@ -2,9 +2,8 @@ package system import ( "context" + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func seedStrictUserTree(t *testing.T, data *Data) (actorID, childID, siblingID uint, users []userPO) { @@ -35,10 +34,10 @@ func seedStrictUserTree(t *testing.T, data *Data) (actorID, childID, siblingID u func TestStrictUserMutationsRejectOutsideTargetAndAllowSelfPassword(t *testing.T) { data := newTransactionTestData(t) actorID, _, _, users := seedStrictUserTree(t, data) - ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: users[0].ID, AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{UserID: users[0].ID, AuthorityID: actorID}) repo := &userRepo{data: data} - if err := repo.UpdateUser(ctx, &biz.User{ID: users[2].ID, NickName: "changed"}); err == nil { + if err := repo.UpdateUser(ctx, &system.User{ID: users[2].ID, NickName: "changed"}); err == nil { t.Fatal("UpdateUser() accepted an out-of-scope target") } if err := repo.DeleteUser(ctx, users[2].ID); err == nil { @@ -55,13 +54,13 @@ func TestStrictUserMutationsRejectOutsideTargetAndAllowSelfPassword(t *testing.T func TestStrictUserCreationAndRoleAssignmentRequireManagedAuthorities(t *testing.T) { data := newTransactionTestData(t) actorID, childID, siblingID, users := seedStrictUserTree(t, data) - ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: users[0].ID, AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{UserID: users[0].ID, AuthorityID: actorID}) repo := &userRepo{data: data} - if _, err := repo.CreateUserWithAuthorities(ctx, &biz.User{Username: "outside-create", Password: "hash", AuthorityID: siblingID, Enable: 1}, nil); err == nil { + if _, err := repo.CreateUserWithAuthorities(ctx, &system.User{Username: "outside-create", Password: "hash", AuthorityID: siblingID, Enable: 1}, nil); err == nil { t.Fatal("CreateUserWithAuthorities() accepted an out-of-scope primary authority") } - if _, err := repo.CreateUserWithAuthorities(ctx, &biz.User{Username: "managed-create", Password: "hash", AuthorityID: childID, Enable: 1}, []uint{childID}); err != nil { + if _, err := repo.CreateUserWithAuthorities(ctx, &system.User{Username: "managed-create", Password: "hash", AuthorityID: childID, Enable: 1}, []uint{childID}); err != nil { t.Fatalf("CreateUserWithAuthorities() rejected managed authorities: %v", err) } if err := repo.SetUserAuthorities(ctx, users[1].ID, []uint{siblingID}); err == nil { @@ -75,13 +74,13 @@ func TestStrictUserCreationAndRoleAssignmentRequireManagedAuthorities(t *testing func TestStrictUserUpdateRejectsPrimaryAuthorityOutsideManagedTree(t *testing.T) { data := newTransactionTestData(t) actorID, childID, siblingID, users := seedStrictUserTree(t, data) - ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: users[0].ID, AuthorityID: actorID}) + ctx := system.NewActorContext(context.Background(), system.Actor{UserID: users[0].ID, AuthorityID: actorID}) repo := &userRepo{data: data} - if err := repo.UpdateUser(ctx, &biz.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}); err == nil { + if err := repo.UpdateUser(ctx, &system.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}); err == nil { t.Fatal("UpdateUser() accepted a primary authority outside the managed tree") } - if err := repo.UpdateUserWithAuthorities(ctx, &biz.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}, []uint{childID}); err == nil { + if err := repo.UpdateUserWithAuthorities(ctx, &system.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}, []uint{childID}); err == nil { t.Fatal("UpdateUserWithAuthorities() accepted a primary authority outside the managed tree") } diff --git a/internal/data/repository/version.go b/internal/data/repository/version.go index d85644b..bc597b3 100644 --- a/internal/data/repository/version.go +++ b/internal/data/repository/version.go @@ -2,12 +2,13 @@ package system import ( "context" + "kra/internal/biz/system" "sort" "time" - "gorm.io/gorm" - "kra/internal/biz" "kra/pkg/database/pagination" + + "gorm.io/gorm" ) type versionPO struct { @@ -25,11 +26,11 @@ func (versionPO) TableName() string { return "sys_versions" } type versionRepo struct{ data Provider } -func NewVersionRepo(data Provider) biz.VersionRepo { return &versionRepo{data: data} } -func versionFromPO(v versionPO) *biz.Version { - return &biz.Version{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data} +func NewVersionRepo(data Provider) system.VersionRepo { return &versionRepo{data: data} } +func versionFromPO(v versionPO) *system.Version { + return &system.Version{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data} } -func (r *versionRepo) CreateVersion(ctx context.Context, v *biz.Version) error { +func (r *versionRepo) CreateVersion(ctx context.Context, v *system.Version) error { po := versionPO{Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data} if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -40,14 +41,14 @@ func (r *versionRepo) CreateVersion(ctx context.Context, v *biz.Version) error { func (r *versionRepo) DeleteVersions(ctx context.Context, ids []uint) error { return r.data.DB().WithContext(ctx).Where("id IN ?", ids).Delete(&versionPO{}).Error } -func (r *versionRepo) FindVersion(ctx context.Context, id uint) (*biz.Version, error) { +func (r *versionRepo) FindVersion(ctx context.Context, id uint) (*system.Version, error) { var po versionPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return versionFromPO(po), nil } -func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*biz.Version, int64, error) { +func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*system.Version, int64, error) { db := r.data.DB().WithContext(ctx).Model(&versionPO{}) if len(createdAtRange) == 2 && createdAtRange[0] != nil && createdAtRange[1] != nil { db = db.Where("created_at BETWEEN ? AND ?", createdAtRange[0], createdAtRange[1]) @@ -66,21 +67,21 @@ func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, co if err := pagination.Apply(db, page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*biz.Version, 0, len(pos)) + out := make([]*system.Version, 0, len(pos)) for _, po := range pos { out = append(out, versionFromPO(po)) } return out, total, nil } -func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, dictIDs []uint) (*biz.VersionBundle, error) { - bundle := &biz.VersionBundle{} +func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, dictIDs []uint) (*system.VersionBundle, error) { + bundle := &system.VersionBundle{} var menus []menuPO if len(menuIDs) > 0 { if err := r.data.DB().WithContext(ctx).Where("id IN ?", menuIDs).Order("sort,id").Find(&menus).Error; err != nil { - return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err} + return nil, &system.VersionStageError{Stage: system.VersionStageMenus, Err: err} } } - byID := map[uint]*biz.Menu{} + byID := map[uint]*system.Menu{} for _, po := range menus { v := menuFromPO(po) byID[po.ID] = v @@ -88,20 +89,20 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d if len(menuIDs) > 0 { var buttons []menuButtonPO if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(&buttons).Error; err != nil { - return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err} + return nil, &system.VersionStageError{Stage: system.VersionStageMenus, Err: err} } for _, button := range buttons { if menu := byID[button.MenuID]; menu != nil { - menu.Buttons = append(menu.Buttons, &biz.MenuButton{Name: button.Name, Description: button.Description}) + menu.Buttons = append(menu.Buttons, &system.MenuButton{Name: button.Name, Description: button.Description}) } } var parameters []menuParameterPO if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(¶meters).Error; err != nil { - return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err} + return nil, &system.VersionStageError{Stage: system.VersionStageMenus, Err: err} } for _, parameter := range parameters { if menu := byID[parameter.MenuID]; menu != nil { - menu.Parameters = append(menu.Parameters, &biz.MenuParameter{Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + menu.Parameters = append(menu.Parameters, &system.MenuParameter{Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } } } @@ -113,16 +114,16 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d bundle.Menus = append(bundle.Menus, v) } } - var sortMenus func([]*biz.Menu) - sortMenus = func(items []*biz.Menu) { + var sortMenus func([]*system.Menu) + sortMenus = func(items []*system.Menu) { sort.Slice(items, func(i, j int) bool { return items[i].Sort < items[j].Sort }) for _, item := range items { sortMenus(item.Children) } } sortMenus(bundle.Menus) - var cleanMenu func(*biz.Menu) - cleanMenu = func(menu *biz.Menu) { + var cleanMenu func(*system.Menu) + cleanMenu = func(menu *system.Menu) { menu.ID = 0 menu.ParentID = 0 for _, button := range menu.Buttons { @@ -144,7 +145,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d var apis []apiPO if len(apiIDs) > 0 { if err := r.data.DB().WithContext(ctx).Where("id IN ?", apiIDs).Find(&apis).Error; err != nil { - return nil, &biz.VersionStageError{Stage: biz.VersionStageAPIs, Err: err} + return nil, &system.VersionStageError{Stage: system.VersionStageAPIs, Err: err} } } for _, po := range apis { @@ -155,16 +156,16 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d var dictionaries []dictionaryPO if len(dictIDs) > 0 { if err := r.data.DB().WithContext(ctx).Where("id IN ?", dictIDs).Find(&dictionaries).Error; err != nil { - return nil, &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err} + return nil, &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err} } } dictionariesRepo := &dictionaryRepo{data: r.data} for _, po := range dictionaries { v := dictionaryFromPO(po) v.ID = 0 - details, _, err := dictionariesRepo.ListDictionaryDetails(ctx, 0, 0, biz.DictionaryDetailFilter{DictionaryID: po.ID}) + details, _, err := dictionariesRepo.ListDictionaryDetails(ctx, 0, 0, system.DictionaryDetailFilter{DictionaryID: po.ID}) if err != nil { - return nil, &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err} + return nil, &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err} } for _, d := range details { d.ID = 0 @@ -178,27 +179,27 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d } return bundle, nil } -func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *biz.VersionBundle) error { +func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *system.VersionBundle) error { db := r.data.DB().WithContext(ctx) if len(b.Menus) > 0 { if err := db.Transaction(func(tx *gorm.DB) error { return importVersionMenus(tx, b.Menus, 0) }); err != nil { - return &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err} + return &system.VersionStageError{Stage: system.VersionStageMenus, Err: err} } } if len(b.APIs) > 0 { if err := db.Transaction(func(tx *gorm.DB) error { return importVersionAPIs(tx, b.APIs) }); err != nil { - return &biz.VersionStageError{Stage: biz.VersionStageAPIs, Err: err} + return &system.VersionStageError{Stage: system.VersionStageAPIs, Err: err} } } if len(b.Dictionaries) > 0 { if err := db.Transaction(func(tx *gorm.DB) error { return importVersionDictionaries(tx, b.Dictionaries) }); err != nil { - return &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err} + return &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err} } } return nil } -func importVersionMenus(tx *gorm.DB, items []*biz.Menu, parentID uint) error { +func importVersionMenus(tx *gorm.DB, items []*system.Menu, parentID uint) error { for _, item := range items { var existing menuPO if err := tx.Where("name = ? AND path = ?", item.Name, item.Path).First(&existing).Error; err == nil { @@ -230,7 +231,7 @@ func importVersionMenus(tx *gorm.DB, items []*biz.Menu, parentID uint) error { return nil } -func importVersionAPIs(tx *gorm.DB, items []*biz.API) error { +func importVersionAPIs(tx *gorm.DB, items []*system.API) error { for _, item := range items { var existing apiPO if err := tx.Where("path = ? AND method = ?", item.Path, item.Method).First(&existing).Error; err == nil { @@ -243,7 +244,7 @@ func importVersionAPIs(tx *gorm.DB, items []*biz.API) error { return nil } -func importVersionDictionaries(tx *gorm.DB, items []*biz.Dictionary) error { +func importVersionDictionaries(tx *gorm.DB, items []*system.Dictionary) error { for _, item := range items { var existing dictionaryPO if err := tx.Where("type = ?", item.Type).First(&existing).Error; err == nil { diff --git a/internal/initialize/backend.go b/internal/initialize/backend.go index 821cbf1..1837726 100644 --- a/internal/initialize/backend.go +++ b/internal/initialize/backend.go @@ -2,8 +2,8 @@ package initialize import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/conf" "gorm.io/gorm" @@ -13,7 +13,7 @@ import ( // initialization and runtime configuration management. type Backend interface { IsInitialized(context.Context) (bool, error) - InitializeDatabase(context.Context, *biz.DatabaseConfig, func(context.Context, *gorm.DB) error) error + InitializeDatabase(context.Context, *system.DatabaseConfig, func(context.Context, *gorm.DB) error) error PersistConfig(context.Context) error PersistAdminConfig(context.Context, []byte) error PersistRuntimeConfig(context.Context, []byte, []byte) error diff --git a/internal/initialize/initialize.go b/internal/initialize/initialize.go index ca676c6..df0036d 100644 --- a/internal/initialize/initialize.go +++ b/internal/initialize/initialize.go @@ -2,8 +2,8 @@ package initialize import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" datasystem "kra/internal/data/repository" platformmodule "kra/pkg/module" @@ -15,7 +15,7 @@ type Repo struct { catalog platformmodule.Catalog } -func NewRepo(backend Backend, catalog platformmodule.Catalog) biz.InitializationRepo { +func NewRepo(backend Backend, catalog platformmodule.Catalog) system.InitializationRepo { return &Repo{backend: backend, catalog: catalog} } @@ -23,7 +23,7 @@ func (r *Repo) IsInitialized(ctx context.Context) (bool, error) { return r.backend.IsInitialized(ctx) } -func (r *Repo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error { +func (r *Repo) Initialize(ctx context.Context, input *system.DatabaseConfig) error { return r.backend.InitializeDatabase(ctx, input, func(ctx context.Context, db *gorm.DB) error { return datasystem.SeedSystemWithCatalog(ctx, db, input, r.catalog) }) diff --git a/internal/integration/cache/cache.go b/internal/integration/cache/cache.go index 1393bab..67fc5fa 100644 --- a/internal/integration/cache/cache.go +++ b/internal/integration/cache/cache.go @@ -2,12 +2,12 @@ package cache import ( "context" + "kra/internal/biz/system" "strconv" "sync" "time" "github.com/redis/go-redis/v9" - "kra/internal/biz" ) type RedisProvider interface { @@ -27,7 +27,7 @@ type Store struct { const maxMemoryEntries = 10000 -func New(provider RedisProvider) biz.Cache { +func New(provider RedisProvider) system.Cache { return &Store{provider: provider, memory: make(map[string]memoryEntry)} } diff --git a/internal/integration/connectivity.go b/internal/integration/connectivity.go index 0c4500e..91a453c 100644 --- a/internal/integration/connectivity.go +++ b/internal/integration/connectivity.go @@ -5,9 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "strings" - "kra/internal/biz" "kra/internal/integration/mq" "kra/internal/integration/runtimeconfig" websocketintegration "kra/internal/integration/websocket" @@ -23,7 +23,7 @@ func NewConnectivityTester(store *runtimeconfig.Store) *ConnectivityTester { return &ConnectivityTester{store: store} } -func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *biz.IntegrationConfig) error { +func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *system.IntegrationConfig) error { if config == nil { return errors.New("集成配置请求为空") } @@ -39,9 +39,9 @@ func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *biz.In return fmt.Errorf("编码集成配置失败: %w", err) } switch strings.ToLower(strings.TrimSpace(config.Kind)) { - case biz.IntegrationKindMQ: + case system.IntegrationKindMQ: return mq.TestConfig(ctx, config.Provider, raw) - case biz.IntegrationKindWebSocket: + case system.IntegrationKindWebSocket: if strings.ToLower(strings.TrimSpace(config.Provider)) != websocketintegration.ProviderMelody { return fmt.Errorf("不支持的 WebSocket provider %q", config.Provider) } @@ -52,7 +52,7 @@ func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *biz.In } func (t *ConnectivityTester) restoreMaskedSecrets(kind, provider string, values map[string]any) error { - definition, ok := biz.IntegrationDefinition(kind, provider) + definition, ok := system.IntegrationDefinition(kind, provider) if !ok { return fmt.Errorf("不支持的集成 %s/%s", kind, provider) } diff --git a/internal/integration/email/email.go b/internal/integration/email/email.go index 6332085..41bb386 100644 --- a/internal/integration/email/email.go +++ b/internal/integration/email/email.go @@ -6,13 +6,13 @@ import ( "errors" "fmt" "io" + "kra/internal/biz/system" "mime" "net" "net/smtp" "strings" "time" - "kra/internal/biz" "kra/internal/conf" ) @@ -20,7 +20,7 @@ type emailRepo struct { runtime *conf.Runtime } -func NewEmailRepo(runtime *conf.Runtime) biz.EmailRepo { +func NewEmailRepo(runtime *conf.Runtime) system.EmailRepo { return &emailRepo{runtime: runtime} } diff --git a/internal/integration/payment/adapter.go b/internal/integration/payment/adapter.go index 6526702..25ee344 100644 --- a/internal/integration/payment/adapter.go +++ b/internal/integration/payment/adapter.go @@ -3,53 +3,52 @@ package payment import ( "context" "fmt" - - "kra/internal/biz" + "kra/internal/biz/system" ) // Adapter is the provider boundary used by the payment repository. Provider // SDK types stay in this package and are normalized to biz.PaymentResult. type Adapter interface { - Create(context.Context, *biz.PaymentRequest, map[string]any) (*biz.PaymentResult, error) - Query(context.Context, string, map[string]any) (*biz.PaymentResult, error) - Refund(context.Context, *biz.PaymentRefundRequest, map[string]any) (*biz.PaymentResult, error) - Callback(context.Context, *biz.PaymentCallback, map[string]any) (*biz.PaymentResult, error) + Create(context.Context, *system.PaymentRequest, map[string]any) (*system.PaymentResult, error) + Query(context.Context, string, map[string]any) (*system.PaymentResult, error) + Refund(context.Context, *system.PaymentRefundRequest, map[string]any) (*system.PaymentResult, error) + Callback(context.Context, *system.PaymentCallback, map[string]any) (*system.PaymentResult, error) } // New constructs the SDK-backed adapter for a configured provider. func New(provider string) (Adapter, error) { switch provider { - case biz.PaymentAlipay: + case system.PaymentAlipay: return &alipayAdapter{}, nil - case biz.PaymentAlipayV3: + case system.PaymentAlipayV3: return &alipayV3Adapter{}, nil - case biz.PaymentWechatV2: + case system.PaymentWechatV2: return &wechatV2Adapter{}, nil - case biz.PaymentWechatV3: + case system.PaymentWechatV3: return &wechatV3Adapter{}, nil - case biz.PaymentApple: + case system.PaymentApple: return &appleAdapter{}, nil - case biz.PaymentDouyin: + case system.PaymentDouyin: return &douyinAdapter{}, nil - case biz.PaymentQQ: + case system.PaymentQQ: return &qqAdapter{}, nil - case biz.PaymentAllinPay: + case system.PaymentAllinPay: return &allinpayAdapter{}, nil - case biz.PaymentLakala: + case system.PaymentLakala: return &lakalaAdapter{}, nil - case biz.PaymentPayPal: + case system.PaymentPayPal: return &paypalAdapter{}, nil - case biz.PaymentSaobei: + case system.PaymentSaobei: return &saobeiAdapter{}, nil - case biz.PaymentChinaums: + case system.PaymentChinaums: return newVendorAdapter(provider, vendorChinaums), nil - case biz.PaymentSFT: + case system.PaymentSFT: return newVendorAdapter(provider, vendorSFT), nil - case biz.PaymentSuperPay: + case system.PaymentSuperPay: return newVendorAdapter(provider, vendorSupperPay), nil - case biz.PaymentWechatGame: + case system.PaymentWechatGame: return newVendorAdapter(provider, vendorWechatGame), nil - case biz.PaymentDouyinGame: + case system.PaymentDouyinGame: return newVendorAdapter(provider, vendorDouyinGame), nil default: return nil, fmt.Errorf("支付渠道 %s 没有适配器", provider) diff --git a/internal/integration/payment/adapter_test.go b/internal/integration/payment/adapter_test.go index 29d6129..586b8e3 100644 --- a/internal/integration/payment/adapter_test.go +++ b/internal/integration/payment/adapter_test.go @@ -1,13 +1,12 @@ package payment import ( + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func TestEverySupportedProviderHasAdapter(t *testing.T) { - for _, provider := range biz.SupportedPaymentProviders { + for _, provider := range system.SupportedPaymentProviders { t.Run(provider, func(t *testing.T) { adapter, err := New(provider) if err != nil { diff --git a/internal/integration/payment/alipay.go b/internal/integration/payment/alipay.go index 8ad00f2..6691979 100644 --- a/internal/integration/payment/alipay.go +++ b/internal/integration/payment/alipay.go @@ -7,14 +7,13 @@ import ( "errors" "fmt" "io" + "kra/internal/biz/system" "net/http" "net/url" "sort" "strings" "time" - "kra/internal/biz" - "github.com/go-pay/gopay" gopayAlipay "github.com/go-pay/gopay/alipay" "github.com/go-pay/gopay/pkg/xhttp" @@ -39,7 +38,7 @@ type alipayResponseBody struct { io.Closer } -func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝下单参数为空") } @@ -92,8 +91,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if status == "success" && strings.TrimSpace(response.TradeNo) == "" { return nil, errors.New("支付宝付款码支付成功响应缺少 trade_no") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: status, TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: status, TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(response.TradeNo), Payload: sdk.payload(rsp), }, nil case "alipay.trade.precreate": @@ -113,8 +112,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m } // Precreate has no Alipay trade number yet. Keep the QR code in Payload // and query later by the merchant out_trade_no. - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, QueryID: queryID, Payload: sdk.payload(rsp), }, nil case "alipay.trade.app.pay": @@ -125,8 +124,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if strings.TrimSpace(orderString) == "" { return nil, errors.New("支付宝 APP 支付参数为空") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, Payload: mustMarshalAlipayPayload(map[string]string{"order_string": orderString}), }, nil case "alipay.trade.page.pay": @@ -141,8 +140,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if err != nil { return nil, err } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}), }, nil case "alipay.trade.wap.pay": @@ -157,8 +156,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if err != nil { return nil, err } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}), }, nil default: @@ -174,14 +173,14 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if providerTradeNo == "" { return nil, errors.New("支付宝创建响应缺少 trade_no") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: providerTradeNo, Payload: sdk.payload(rsp), }, nil } } -func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("支付宝查单缺少 out_trade_no") @@ -196,8 +195,8 @@ func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string] return nil, err } response := rsp.Response - result := &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"), + result := &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"), TradeNo: strings.TrimSpace(response.OutTradeNo), ProviderTradeNo: strings.TrimSpace(response.TradeNo), Payload: sdk.payload(rsp), } @@ -230,7 +229,7 @@ func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, nil } -func (a *alipayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝退款请求为空") } @@ -258,7 +257,7 @@ func (a *alipayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques return alipayRefundResult(req, response, sdk.payload(rsp)) } -func alipayRefundResult(req *biz.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*biz.PaymentResult, error) { +func alipayRefundResult(req *system.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*system.PaymentResult, error) { if req == nil || response == nil { return nil, errors.New("支付宝退款响应为空") } @@ -298,13 +297,13 @@ func alipayRefundResult(req *biz.PaymentRefundRequest, response *gopayAlipay.Tra if currency == "" { currency = "CNY" } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: "created", TradeNo: tradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: "created", TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Amount: amount, Currency: currency, Payload: payload, }, nil } -func (a *alipayAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("支付宝回调为空") } @@ -320,8 +319,8 @@ func (a *alipayAdapter) Callback(_ context.Context, callback *biz.PaymentCallbac status = "success" } payload, _ := json.Marshal(fields) - return &biz.PaymentResult{ - Provider: biz.PaymentAlipay, Status: status, + return &system.PaymentResult{ + Provider: system.PaymentAlipay, Status: status, TradeNo: first(fields, "out_trade_no"), ProviderTradeNo: first(fields, "trade_no"), Payload: payload, }, nil @@ -482,7 +481,7 @@ func firstNonEmptyAlipay(values ...string) string { return "" } -func populateAlipayBreakdown(result *biz.PaymentResult, response map[string]any) error { +func populateAlipayBreakdown(result *system.PaymentResult, response map[string]any) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/alipay_v3.go b/internal/integration/payment/alipay_v3.go index af71081..468a691 100644 --- a/internal/integration/payment/alipay_v3.go +++ b/internal/integration/payment/alipay_v3.go @@ -5,14 +5,13 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "net/http" "net/url" "os" "strings" "time" - "kra/internal/biz" - "github.com/go-pay/gopay" gopayAlipay "github.com/go-pay/gopay/alipay" gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3" @@ -26,7 +25,7 @@ type alipayV3Client struct { gateway *url.URL } -func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝 V3 下单参数为空") } @@ -94,8 +93,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c if strings.TrimSpace(rsp.QrCode) == "" { return nil, errors.New("支付宝 V3 预创建响应缺少 qr_code") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp), }, nil case "alipay.trade.app.pay": @@ -106,8 +105,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c if strings.TrimSpace(orderString) == "" { return nil, errors.New("支付宝 V3 APP 支付参数为空") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(map[string]string{"order_string": orderString}), }, nil case "alipay.trade.page.pay": @@ -122,8 +121,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c if strings.TrimSpace(payURL) == "" { return nil, errors.New("支付宝 V3 电脑网站支付 URL 为空") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(map[string]string{"pay_url": payURL}), }, nil case "alipay.trade.wap.pay": @@ -138,8 +137,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c if strings.TrimSpace(payURL) == "" { return nil, errors.New("支付宝 V3 手机网站支付 URL 为空") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(map[string]string{"pay_url": payURL}), }, nil default: @@ -167,14 +166,14 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c if providerTradeNo == "" { return nil, errors.New("支付宝 V3 创建响应缺少 trade_no") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: providerTradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp), }, nil } } -func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("支付宝 V3 查单缺少 out_trade_no") @@ -197,8 +196,8 @@ func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin if outTradeNo == "" || outTradeNo != tradeNo { return nil, errors.New("支付宝 V3 查询响应的 out_trade_no 缺失或不匹配") } - result := &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"), + result := &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"), TradeNo: outTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.TradeNo), Currency: alipayV3Currency(c, ""), Payload: mustJSON(rsp), } @@ -222,7 +221,7 @@ func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin return result, nil } -func (a *alipayV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝 V3 退款请求为空") } @@ -273,14 +272,14 @@ func (a *alipayV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ if refundedAmount != req.Amount { return nil, errors.New("支付宝 V3 退款响应金额不匹配") } - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: tradeNo, + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "created", TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Amount: refundedAmount, Currency: alipayV3Currency(c, req.Currency), Payload: mustJSON(rsp), }, nil } -func (a *alipayV3Adapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *alipayV3Adapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("支付宝 V3 回调为空") } @@ -306,13 +305,13 @@ func (a *alipayV3Adapter) Callback(_ context.Context, callback *biz.PaymentCallb return nil, errors.New("支付宝 V3 回调缺少 out_trade_no 或 trade_no") } payload, _ := json.Marshal(fields) - return &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"), + return &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"), TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload, }, nil } -func alipayV3TradePayResult(req *biz.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*biz.PaymentResult, error) { +func alipayV3TradePayResult(req *system.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*system.PaymentResult, error) { if req == nil || rsp == nil { return nil, errors.New("支付宝 V3 付款码支付响应为空") } @@ -331,8 +330,8 @@ func alipayV3TradePayResult(req *biz.PaymentRequest, rsp *gopayAlipayV3.TradePay if amount != req.Amount { return nil, errors.New("支付宝 V3 付款码支付响应金额不匹配") } - result := &biz.PaymentResult{ - Provider: biz.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo, + result := &system.PaymentResult{ + Provider: system.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo, ProviderTradeNo: providerTradeNo, Amount: amount, Currency: strings.ToUpper(strings.TrimSpace(req.Currency)), Payload: mustJSON(rsp), } diff --git a/internal/integration/payment/alipay_v3_test.go b/internal/integration/payment/alipay_v3_test.go index 37edf0d..17babfc 100644 --- a/internal/integration/payment/alipay_v3_test.go +++ b/internal/integration/payment/alipay_v3_test.go @@ -9,6 +9,7 @@ import ( "encoding/json" "encoding/pem" "io" + "kra/internal/biz/system" "math/big" "net/http" "net/http/httptest" @@ -17,8 +18,6 @@ import ( "testing" "time" - "kra/internal/biz" - gopayAlipay "github.com/go-pay/gopay/alipay" gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3" ) @@ -136,8 +135,8 @@ func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) { if tc.method != "" { extra["method"] = tc.method } - result, err := (&alipayV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000, + result, err := (&alipayV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000, Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify", Extra: extra, }, config) if err != nil { @@ -146,7 +145,7 @@ func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) { if requestBody == nil || requestBody["out_trade_no"] != tradeNo || requestBody["notify_url"] == "" { t.Fatalf("request body = %#v", requestBody) } - if result.Provider != biz.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade { + if result.Provider != system.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade { t.Fatalf("result = %+v", result) } if tc.method == "barcode" { @@ -169,8 +168,8 @@ func TestAlipayV3GeneratedPaymentMethodsUseGoPay(t *testing.T) { config["gateway_url"] = "https://merchant-gateway.example/gateway.do" for _, method := range []string{"app", "page", "wap"} { t.Run(method, func(t *testing.T) { - result, err := (&alipayV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject", + result, err := (&alipayV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject", Amount: 1000, Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify", ReturnURL: "https://merchant.example/alipay-v3/return", Extra: map[string]any{"method": method}, }, config) @@ -273,8 +272,8 @@ func TestAlipayV3RefundValidatesResponse(t *testing.T) { defer server.Close() config := cloneAlipayV3Config(baseConfig) config["api_base_url"] = server.URL - result, err := (&alipayV3Adapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{ - Provider: biz.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND", + result, err := (&alipayV3Adapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ + Provider: system.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND", RefundNo: "R-V3-REFUND", Amount: 123, Currency: "CNY", }, config) if tc.wantError { @@ -316,14 +315,14 @@ func TestAlipayV3CallbackUsesGoPayCertificateVerification(t *testing.T) { for key, value := range fields { values.Set(key, value) } - callback := &biz.PaymentCallback{ + callback := &system.PaymentCallback{ Body: []byte(values.Encode()), Headers: map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, } result, err := (&alipayV3Adapter{}).Callback(context.Background(), callback, config) if err != nil { t.Fatal(err) } - if result.Provider != biz.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" { + if result.Provider != system.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" { t.Fatalf("callback result = %+v", result) } callback.Body = []byte(strings.ReplaceAll(values.Encode(), "P-V3-CALLBACK", "P-V3-TAMPERED")) diff --git a/internal/integration/payment/allinpay.go b/internal/integration/payment/allinpay.go index 37ffe10..9b1e6d7 100644 --- a/internal/integration/payment/allinpay.go +++ b/internal/integration/payment/allinpay.go @@ -11,12 +11,11 @@ import ( "errors" "fmt" "io" + "kra/internal/biz/system" "net/http" "strings" "sync" - "kra/internal/biz" - "github.com/go-pay/crypto/xpem" "github.com/go-pay/crypto/xrsa" "github.com/go-pay/gopay" @@ -121,7 +120,7 @@ func (a *allinpayAdapter) client(c map[string]any) (*allinpay.Client, error) { return client, nil } -func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *allinpayAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("通联支付下单请求为空") } @@ -159,7 +158,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c if returnedTradeNo := strings.TrimSpace(rsp.Reqsn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) { return nil, errors.New("通联支付扫码响应的 reqsn 不匹配") } - return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil } if method == "native" { if orderType == allinpay.OrderTypeTrxId { @@ -176,7 +175,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c if returnedTradeNo := strings.TrimSpace(rsp.ReqSn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) { return nil, errors.New("通联支付 Native 响应的 reqsn 不匹配") } - return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil } payType := firstAny(req.Extra, "paytype", "pay_type") if payType == "" { @@ -200,7 +199,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c if err != nil { return nil, err } - return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil } func allinpayCreateMethod(extra, config map[string]any) (string, error) { @@ -221,7 +220,7 @@ func allinpayCreateMethod(extra, config map[string]any) (string, error) { } } -func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { orderType, err := allinpayOrderType(c) if err != nil { return nil, err @@ -248,7 +247,7 @@ func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[strin if rsp == nil { return nil, errors.New("通联支付查单响应为空") } - result := &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)} + result := &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)} if strings.EqualFold(orderType, allinpay.OrderTypeTrxId) { if result.ProviderTradeNo == "" { return nil, errors.New("通联支付查单响应缺少 trxid") @@ -301,7 +300,7 @@ func allinpayCreateQueryID(orderType, transactionID string) (string, error) { return "", nil } -func (a *allinpayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *allinpayAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("通联支付退款请求为空") } @@ -346,7 +345,7 @@ func (a *allinpayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ return allinpayRefundResult(&effective, rsp, firstAny(c, "currency")) } -func allinpayRefundResult(req *biz.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*biz.PaymentResult, error) { +func allinpayRefundResult(req *system.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*system.PaymentResult, error) { if rsp == nil { return nil, errors.New("通联支付退款响应为空") } @@ -369,9 +368,9 @@ func allinpayRefundResult(req *biz.PaymentRefundRequest, rsp *allinpay.RefundRsp if refundAmount != req.Amount { return nil, errors.New("通联支付退款响应金额不匹配") } - return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil } -func (a *allinpayAdapter) Callback(_ context.Context, _ *biz.PaymentCallback, _ map[string]any) (*biz.PaymentResult, error) { +func (a *allinpayAdapter) Callback(_ context.Context, _ *system.PaymentCallback, _ map[string]any) (*system.PaymentResult, error) { return nil, errors.New("通联支付回调没有可复用的 GoPay 验签器,请改用主动查单") } diff --git a/internal/integration/payment/allinpay_test.go b/internal/integration/payment/allinpay_test.go index 7a09ce6..f7e454b 100644 --- a/internal/integration/payment/allinpay_test.go +++ b/internal/integration/payment/allinpay_test.go @@ -11,6 +11,7 @@ import ( "encoding/base64" "encoding/json" "io" + "kra/internal/biz/system" "net" "net/http" "net/http/httptest" @@ -18,8 +19,6 @@ import ( "strings" "testing" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/allinpay" ) @@ -121,8 +120,8 @@ func TestAllinPayTrxIDCreateRejectsResponseWithoutTransactionID(t *testing.T) { _, _ = w.Write(response) })) - _, err := (&allinpayAdapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentAllinPay, TradeNo: "MERCHANT-TRXID-MISSING", Subject: "subject", + _, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-TRXID-MISSING", Subject: "subject", Amount: 100, Currency: "CNY", }, allinPayTestConfig(privateKey, publicKey, allinpay.OrderTypeTrxId)) if err == nil || !strings.Contains(strings.ToLower(err.Error()), "trxid") { @@ -132,8 +131,8 @@ func TestAllinPayTrxIDCreateRejectsResponseWithoutTransactionID(t *testing.T) { func TestAllinPayNativeCreateRejectsTrxIDLookupMode(t *testing.T) { _, privateKey, publicKey := allinPayTestKeys(t) - _, err := (&allinpayAdapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentAllinPay, TradeNo: "MERCHANT-NATIVE-TRXID", Subject: "subject", + _, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-NATIVE-TRXID", Subject: "subject", Amount: 100, Currency: "CNY", Extra: map[string]any{"method": "native", "expiretime": "20261231235959"}, }, allinPayTestConfig(privateKey, publicKey, allinpay.OrderTypeTrxId)) if err == nil || !strings.Contains(strings.ToLower(err.Error()), "native") || !strings.Contains(strings.ToLower(err.Error()), "trxid") { @@ -142,8 +141,8 @@ func TestAllinPayNativeCreateRejectsTrxIDLookupMode(t *testing.T) { } func TestAllinPayCreateRejectsUnknownMethodBeforeClient(t *testing.T) { - _, err := (&allinpayAdapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentAllinPay, TradeNo: "MERCHANT-UNKNOWN-METHOD", Subject: "subject", + _, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-UNKNOWN-METHOD", Subject: "subject", Amount: 100, Currency: "CNY", Extra: map[string]any{"method": "unsupported"}, }, nil) if err == nil || !strings.Contains(err.Error(), "不支持的下单方式") { @@ -249,8 +248,8 @@ func TestAllinPayRefundUsesConfiguredLookupIdentity(t *testing.T) { _, _ = w.Write(response) })) - result, err := (&allinpayAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{ - Provider: biz.PaymentAllinPay, TradeNo: tc.tradeNo, QueryID: tc.queryID, + result, err := (&allinpayAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ + Provider: system.PaymentAllinPay, TradeNo: tc.tradeNo, QueryID: tc.queryID, RefundNo: "REFUND-1", Amount: 40, TotalAmount: 100, Currency: "CNY", }, allinPayTestConfig(privateKey, publicKey, tc.orderType)) if err != nil { @@ -264,7 +263,7 @@ func TestAllinPayRefundUsesConfiguredLookupIdentity(t *testing.T) { } func TestAllinPayRefundResultBindsRefundIdentity(t *testing.T) { - req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 40, Currency: "CNY"} + req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 40, Currency: "CNY"} base := func() *allinpay.RefundRsp { return &allinpay.RefundRsp{Reqsn: "REFUND-1", Trxid: "ALLIN-REFUND-1", TrxStatus: "SUCCESS", Fee: "40"} } diff --git a/internal/integration/payment/apple.go b/internal/integration/payment/apple.go index c62e1c7..6438b7d 100644 --- a/internal/integration/payment/apple.go +++ b/internal/integration/payment/apple.go @@ -5,18 +5,17 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "math" "strings" - "kra/internal/biz" - gopayApple "github.com/go-pay/gopay/apple" "github.com/google/uuid" ) type appleAdapter struct{} -func (a *appleAdapter) Create(_ context.Context, req *biz.PaymentRequest, _ map[string]any) (*biz.PaymentResult, error) { +func (a *appleAdapter) Create(_ context.Context, req *system.PaymentRequest, _ map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("Apple 内购下单参数为空") } @@ -31,9 +30,9 @@ func (a *appleAdapter) Create(_ context.Context, req *biz.PaymentRequest, _ map[ return nil, errors.New("Apple 内购 tradeNo 必须是用于 appAccountToken 的 UUID") } payload, _ := json.Marshal(map[string]any{"product_id": productID, "app_account_token": req.TradeNo, "client_only": true, "trade_no": req.TradeNo}) - return &biz.PaymentResult{Provider: biz.PaymentApple, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil + return &system.PaymentResult{Provider: system.PaymentApple, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil } -func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[string]any) (*biz.PaymentResult, error) { +func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[string]any) (*system.PaymentResult, error) { transactionID = strings.TrimSpace(transactionID) if transactionID == "" { return nil, errors.New("Apple 查询缺少 transactionId") @@ -82,10 +81,10 @@ func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[st } return result, nil } -func (a *appleAdapter) Refund(context.Context, *biz.PaymentRefundRequest, map[string]any) (*biz.PaymentResult, error) { +func (a *appleAdapter) Refund(context.Context, *system.PaymentRefundRequest, map[string]any) (*system.PaymentResult, error) { return nil, errors.New("Apple 内购退款由 App Store 管理,服务端不提供主动退款接口") } -func (a *appleAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *appleAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil { return nil, errors.New("Apple 回调为空") } @@ -140,7 +139,7 @@ func (a *appleAdapter) Callback(_ context.Context, callback *biz.PaymentCallback if _, parseErr := uuid.Parse(tradeNo); parseErr != nil { return nil, errors.New("Apple 回调 appAccountToken 不是有效 UUID") } - return &biz.PaymentResult{Provider: biz.PaymentApple, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: transactionID, QueryID: transactionID, Payload: payload}, nil + return &system.PaymentResult{Provider: system.PaymentApple, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: transactionID, QueryID: transactionID, Payload: payload}, nil } func appleClaims(value any) (map[string]any, error) { @@ -155,7 +154,7 @@ func appleClaims(value any) (map[string]any, error) { return claims, nil } -func applePaymentResult(transactionID string, claims map[string]any, payload []byte, c map[string]any) (*biz.PaymentResult, error) { +func applePaymentResult(transactionID string, claims map[string]any, payload []byte, c map[string]any) (*system.PaymentResult, error) { revoked, err := appleRevoked(claims) if err != nil { return nil, err @@ -175,7 +174,7 @@ func applePaymentResult(transactionID string, claims map[string]any, payload []b if _, err := uuid.Parse(tradeNo); err != nil { return nil, errors.New("Apple 交易 appAccountToken 不是有效 UUID") } - result := &biz.PaymentResult{Provider: biz.PaymentApple, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, QueryID: providerTradeNo, Payload: payload} + result := &system.PaymentResult{Provider: system.PaymentApple, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, QueryID: providerTradeNo, Payload: payload} if status != "success" { return result, nil } diff --git a/internal/integration/payment/douyin.go b/internal/integration/payment/douyin.go index 748d187..fd86666 100644 --- a/internal/integration/payment/douyin.go +++ b/internal/integration/payment/douyin.go @@ -5,10 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "strings" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/douyin" ) @@ -35,7 +34,7 @@ func (a *douyinAdapter) client(c map[string]any) (*douyin.Client, error) { return client, nil } -func (a *douyinAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *douyinAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("抖音支付下单请求为空") } @@ -78,7 +77,7 @@ func (a *douyinAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m return nil, errors.New("抖音支付 H5 下单响应缺少 h5_url") } payload, _ := json.Marshal(rsp.Response) - return &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil + return &system.PaymentResult{Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil case "native": rsp, callErr := client.NativeOrder(ctx, bm) if callErr != nil { @@ -91,7 +90,7 @@ func (a *douyinAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m return nil, errors.New("抖音支付 Native 下单响应缺少 code_url") } payload, _ := json.Marshal(rsp.Response) - return &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil + return &system.PaymentResult{Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil case "jsapi": rsp, callErr := client.JsapiOrder(ctx, bm) if callErr != nil { @@ -126,7 +125,7 @@ func douyinCreateMethod(extra, config map[string]any) (string, error) { } } -func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*biz.PaymentResult, error) { +func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*system.PaymentResult, error) { if client == nil || strings.TrimSpace(prepayID) == "" { return nil, errors.New("抖音支付下单响应缺少 prepay_id") } @@ -142,15 +141,15 @@ func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, if err != nil { return nil, fmt.Errorf("生成抖音支付客户端调起参数: %w", err) } - return &biz.PaymentResult{ - Provider: biz.PaymentDouyin, + return &system.PaymentResult{ + Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: tradeNo, Payload: mustJSON(params), }, nil } -func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("抖音支付查单缺少 out_trade_no") @@ -178,7 +177,7 @@ func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string] payload = append([]byte(nil), signedBody...) } } - result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload} + result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload} if result.TradeNo == "" { result.TradeNo = tradeNo } else if result.TradeNo != tradeNo { @@ -190,7 +189,7 @@ func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, nil } -func (a *douyinAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *douyinAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("抖音支付退款请求为空") } @@ -227,7 +226,7 @@ func (a *douyinAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques if err != nil { return nil, err } - result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)} + result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)} if r.Amount == nil { return nil, errors.New("抖音支付退款响应缺少金额") } @@ -278,7 +277,7 @@ func validateDouyinRefundIdentity(refund *douyin.Refund, tradeNo, refundNo strin return refundID, nil } -func (a *douyinAdapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *douyinAdapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil { return nil, errors.New("抖音支付回调为空") } @@ -317,7 +316,7 @@ func (a *douyinAdapter) Callback(ctx context.Context, callback *biz.PaymentCallb if err != nil { return nil, err } - result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id} + result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id} if err = populateDouyinPaymentAmount(result, pay.Amount, plain); err != nil { return nil, err } @@ -339,7 +338,7 @@ func (a *douyinAdapter) Callback(ctx context.Context, callback *biz.PaymentCallb } else if strings.EqualFold(refund.RefundStatus, douyin.RefundStatusClosed) || strings.EqualFold(refund.RefundStatus, douyin.RefundStatusAbnormal) { status = "failed" } - result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id} + result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id} if refund.Amount != nil { result.Amount = int64(refund.Amount.Refund) result.Currency = strings.ToUpper(firstAny(c, "currency")) @@ -366,7 +365,7 @@ func validateDouyinMerchantIdentity(merchantID, appID string, c map[string]any, return nil } -func populateDouyinPaymentAmount(result *biz.PaymentResult, amount *douyin.AmountInfo, raw []byte) error { +func populateDouyinPaymentAmount(result *system.PaymentResult, amount *douyin.AmountInfo, raw []byte) error { if result == nil || amount == nil { return nil } diff --git a/internal/integration/payment/gopay_helpers.go b/internal/integration/payment/gopay_helpers.go index 62bfd80..4fa6ba4 100644 --- a/internal/integration/payment/gopay_helpers.go +++ b/internal/integration/payment/gopay_helpers.go @@ -4,11 +4,10 @@ import ( "bytes" "encoding/json" "fmt" + "kra/internal/biz/system" "net/http" "strings" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/douyin" ) @@ -16,7 +15,7 @@ import ( // The adapters in this package intentionally keep GoPay request/response // models below the data boundary. The business layer only sees PaymentResult. -func callbackRequest(callback *biz.PaymentCallback) (*http.Request, error) { +func callbackRequest(callback *system.PaymentCallback) (*http.Request, error) { request, err := http.NewRequest(http.MethodPost, "http://payment-callback.local", bytes.NewReader(callback.Body)) if err != nil { return nil, err diff --git a/internal/integration/payment/gopay_test.go b/internal/integration/payment/gopay_test.go index 066f079..ee7fc7e 100644 --- a/internal/integration/payment/gopay_test.go +++ b/internal/integration/payment/gopay_test.go @@ -14,13 +14,13 @@ import ( "encoding/pem" "fmt" "io" + "kra/internal/biz/system" "net/http" "net/http/httptest" "net/url" "strings" "testing" - "kra/internal/biz" "kra/pkg/paymentkit" gopayAlipay "github.com/go-pay/gopay/alipay" @@ -125,8 +125,8 @@ func TestAlipayTradePayUsesGoPaySDK(t *testing.T) { })) defer server.Close() - result, err := (&alipayAdapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000, + result, err := (&alipayAdapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000, Currency: "CNY", Extra: map[string]any{"method": "barcode", "auth_code": "BARCODE-1"}, }, map[string]any{ "app_id": "app-test", "private_key": privatePEM, "public_key": publicPEM, "gateway_url": server.URL, @@ -194,7 +194,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { })) defer server.Close() - result, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{ + result, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ TradeNo: "T-WECHAT", Subject: "subject", Amount: 123, Currency: "CNY", NotifyURL: "http://notify.local", }, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, @@ -220,7 +220,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { t.Fatal("verification mutated source values") } callbackBody := paymentutil.XMLEncode(callbackValues) - callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &biz.PaymentCallback{Body: callbackBody}, map[string]any{ + callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &system.PaymentCallback{Body: callbackBody}, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, }) if err != nil { @@ -267,8 +267,8 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { })) defer server.Close() - result, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, + result, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, Currency: "CNY", NotifyURL: "http://notify.local", Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-CODE-1"}, }, map[string]any{ @@ -287,8 +287,8 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { } func TestWechatV2MicropayRejectsMissingAuthCodeAndUnknownMode(t *testing.T) { - _, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1, + _, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1, Currency: "CNY", NotifyURL: "http://notify.local", Extra: map[string]any{"method": "micropay"}, }, map[string]any{"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": "wechat-secret"}) if err == nil || !strings.Contains(err.Error(), "auth_code") { @@ -348,8 +348,8 @@ func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) { })) defer server.Close() - _, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, + _, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, Currency: "CNY", Extra: map[string]any{"method": "micropay", "auth_code": "AUTH-CODE-1"}, }, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, @@ -520,7 +520,7 @@ func TestDouyinPrepayResultUsesGoPayClientSigning(t *testing.T) { if err = json.Unmarshal(result.Payload, &payload); err != nil { t.Fatal(err) } - if result.Provider != biz.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" { + if result.Provider != system.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" { t.Fatalf("app=%v result=%+v payload=%#v", app, result, payload) } if app { @@ -581,7 +581,7 @@ func TestWechatV3GoPayCallbackVerificationAndDecryption(t *testing.T) { t.Fatal(err) } signature := testRSA2Sign(t, platformKey, []byte(timestamp+"\n"+headerNonce+"\n"+string(body)+"\n")) - callback := &biz.PaymentCallback{ + callback := &system.PaymentCallback{ Body: body, Headers: map[string]string{ gopayWechatV3.HeaderTimestamp: timestamp, diff --git a/internal/integration/payment/identity_test.go b/internal/integration/payment/identity_test.go index 31b6cb7..28eb56c 100644 --- a/internal/integration/payment/identity_test.go +++ b/internal/integration/payment/identity_test.go @@ -12,14 +12,13 @@ import ( "encoding/json" "encoding/pem" "fmt" + "kra/internal/biz/system" "net" "net/http" "net/http/httptest" "strings" "testing" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/allinpay" "github.com/go-pay/gopay/douyin" @@ -117,7 +116,7 @@ func TestLakalaKeepsQueryAndFinalTradeIdentitiesSeparate(t *testing.T) { t.Fatalf("query result = %+v", result) } - refund, err := (&lakalaAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{ + refund, err := (&lakalaAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ TradeNo: "MERCHANT-LAKALA-1", QueryID: created.QueryID, RefundNo: "REFUND-LAKALA-1", Amount: 40, Currency: "JPY", }, config) @@ -217,8 +216,8 @@ func TestLakalaCreateRoutesSupportedMethods(t *testing.T) { if tc.configMethod != "" { config["method"] = tc.configMethod } - result, err := adapter.Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentLakala, TradeNo: tc.tradeNo, Subject: "subject", Amount: 100, + result, err := adapter.Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentLakala, TradeNo: tc.tradeNo, Subject: "subject", Amount: 100, Currency: "JPY", Extra: extra, }, config) if err != nil { @@ -239,8 +238,8 @@ func TestLakalaCreateRoutesSupportedMethods(t *testing.T) { } func TestLakalaCreateRejectsUnknownMethod(t *testing.T) { - _, err := (&lakalaAdapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentLakala, TradeNo: "L-UNKNOWN", Subject: "subject", Amount: 100, + _, err := (&lakalaAdapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentLakala, TradeNo: "L-UNKNOWN", Subject: "subject", Amount: 100, Currency: "JPY", Extra: map[string]any{"method": "unsupported"}, }, map[string]any{"partner_code": "PART", "credential_code": "credential", "channel": "Wechat"}) if err == nil || !strings.Contains(err.Error(), "不支持的下单方式") { diff --git a/internal/integration/payment/lakala.go b/internal/integration/payment/lakala.go index 6eae7a3..7cc4740 100644 --- a/internal/integration/payment/lakala.go +++ b/internal/integration/payment/lakala.go @@ -4,10 +4,9 @@ import ( "context" "errors" "fmt" + "kra/internal/biz/system" "strings" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/lakala" ) @@ -18,7 +17,7 @@ func (a *lakalaAdapter) client(c map[string]any) (*lakala.Client, error) { return lakala.NewClient(text(c, "partner_code"), text(c, "credential_code"), !strings.EqualFold(text(c, "environment"), "sandbox")) } -func (a *lakalaAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *lakalaAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("拉卡拉支付下单请求为空") } @@ -75,7 +74,7 @@ func lakalaCreateMethod(extra, config map[string]any) string { return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(method))) } -func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*biz.PaymentResult, error) { +func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*system.PaymentResult, error) { if rsp == nil || !lakalaCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("拉卡拉支付下单失败: %v", rsp) } @@ -86,10 +85,10 @@ func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*biz.PaymentRes if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo { return nil, errors.New("拉卡拉支付下单响应的商户订单号不匹配") } - return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil } -func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*biz.PaymentResult, error) { +func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*system.PaymentResult, error) { if rsp == nil || !lakalaRetailCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("拉卡拉线下支付下单失败: %v", rsp) } @@ -100,15 +99,15 @@ func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*biz.Pa if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo { return nil, errors.New("拉卡拉线下支付下单响应的商户订单号不匹配") } - result := &biz.PaymentResult{ - Provider: biz.PaymentLakala, Status: "created", TradeNo: tradeNo, + result := &system.PaymentResult{ + Provider: system.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp), } return result, nil } -func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { client, err := a.client(c) if err != nil { return nil, err @@ -132,7 +131,7 @@ func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string] if returnedTradeNo == "" { return nil, errors.New("拉卡拉支付查单响应缺少商户订单号") } - result := &biz.PaymentResult{Provider: biz.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)} + result := &system.PaymentResult{Provider: system.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)} if result.Currency == "" { result.Currency = strings.ToUpper(firstAny(c, "currency", "default_currency")) } @@ -150,7 +149,7 @@ func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, nil } -func (a *lakalaAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *lakalaAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("拉卡拉支付退款请求为空") } @@ -182,7 +181,7 @@ func (a *lakalaAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques return lakalaRefundResult(&effective, rsp) } -func lakalaRefundResult(req *biz.PaymentRefundRequest, rsp *lakala.RefundRsp) (*biz.PaymentResult, error) { +func lakalaRefundResult(req *system.PaymentRefundRequest, rsp *lakala.RefundRsp) (*system.PaymentResult, error) { if rsp == nil || !lakalaRefundResponseOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("拉卡拉支付退款失败: %v", rsp) } @@ -205,10 +204,10 @@ func lakalaRefundResult(req *biz.PaymentRefundRequest, rsp *lakala.RefundRsp) (* } else if requestCurrency != "" && currency != requestCurrency { return nil, errors.New("拉卡拉支付退款响应币种不匹配") } - return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil } -func (a *lakalaAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *lakalaAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil { return nil, errors.New("拉卡拉支付回调为空") } @@ -223,5 +222,5 @@ func (a *lakalaAdapter) Callback(_ context.Context, callback *biz.PaymentCallbac if err := lakala.VerifySign(notify, text(c, "partner_code"), text(c, "credential_code")); err != nil { return nil, err } - return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil + return &system.PaymentResult{Provider: system.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil } diff --git a/internal/integration/payment/lakala_test.go b/internal/integration/payment/lakala_test.go index 2972435..03fd1e2 100644 --- a/internal/integration/payment/lakala_test.go +++ b/internal/integration/payment/lakala_test.go @@ -1,10 +1,9 @@ package payment import ( + "kra/internal/biz/system" "testing" - "kra/internal/biz" - "github.com/go-pay/gopay/lakala" ) @@ -59,7 +58,7 @@ func TestLakalaQueryStateMapping(t *testing.T) { } func TestLakalaRefundResultBindsRefundIdentity(t *testing.T) { - req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} + req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} base := func() *lakala.RefundRsp { return &lakala.RefundRsp{ ErrorCode: lakala.ErrorCode{ReturnCode: "SUCCESS", ResultCode: "SUCCESS"}, @@ -99,7 +98,7 @@ func TestLakalaRefundResultBindsRefundIdentity(t *testing.T) { } func TestLakalaRefundResultMapsProviderState(t *testing.T) { - req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} + req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} for _, tc := range []struct { code string want string diff --git a/internal/integration/payment/native_test.go b/internal/integration/payment/native_test.go index 2c488da..e569707 100644 --- a/internal/integration/payment/native_test.go +++ b/internal/integration/payment/native_test.go @@ -4,11 +4,10 @@ import ( "crypto/aes" "crypto/cipher" "encoding/base64" + "kra/internal/biz/system" "net/url" "strings" "testing" - - "kra/internal/biz" ) func TestWechatV2SignOfficialStyle(t *testing.T) { @@ -95,7 +94,7 @@ func TestAlipaySignContentSortedAndExcludesSign(t *testing.T) { } func TestAlipayBreakdownWithPointPayment(t *testing.T) { - result := &biz.PaymentResult{Status: "success", Amount: 1000} + result := &system.PaymentResult{Status: "success", Amount: 1000} err := populateAlipayBreakdown(result, map[string]any{ "total_amount": "10.00", "buyer_pay_amount": "8.00", "point_amount": "2.00", "receipt_amount": "8.00", }) @@ -108,7 +107,7 @@ func TestAlipayBreakdownWithPointPayment(t *testing.T) { } func TestWechatV2BreakdownWithCoupon(t *testing.T) { - result := &biz.PaymentResult{Status: "success", Amount: 1000} + result := &system.PaymentResult{Status: "success", Amount: 1000} err := populateWechatV2Breakdown(result, map[string]string{"cash_fee": "900", "coupon_fee": "100", "settlement_total_fee": "950"}) if err != nil { t.Fatal(err) @@ -119,7 +118,7 @@ func TestWechatV2BreakdownWithCoupon(t *testing.T) { } func TestWechatV3BreakdownWithPromotion(t *testing.T) { - result := &biz.PaymentResult{Status: "success", Amount: 1000} + result := &system.PaymentResult{Status: "success", Amount: 1000} err := populateWechatV3Breakdown(result, map[string]any{ "amount": map[string]any{"payer_total": float64(800), "settlement_amount": float64(780)}, "promotion_detail": []any{map[string]any{"amount": float64(200)}}, @@ -133,7 +132,7 @@ func TestWechatV3BreakdownWithPromotion(t *testing.T) { } func TestVendorBreakdownUsesConfiguredUnits(t *testing.T) { - result := &biz.PaymentResult{Status: "success", Amount: 1000, Currency: "CNY"} + result := &system.PaymentResult{Status: "success", Amount: 1000, Currency: "CNY"} object := map[string]any{"data": map[string]any{ "payer": "8.00", "cash": "6.00", "points": "2.00", "discount": "2.00", "settlement": "7.80", }} diff --git a/internal/integration/payment/paypal.go b/internal/integration/payment/paypal.go index 58c38d5..eb8a72b 100644 --- a/internal/integration/payment/paypal.go +++ b/internal/integration/payment/paypal.go @@ -5,12 +5,11 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "math" "strconv" "strings" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/paypal" ) @@ -32,7 +31,7 @@ func (a *paypalAdapter) client(c map[string]any) (*paypal.Client, error) { return client, nil } -func (a *paypalAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *paypalAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("PayPal 下单参数为空") } @@ -75,7 +74,7 @@ func (a *paypalAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m return paypalCreateResult(req, order, c) } -func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*biz.PaymentResult, error) { +func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*system.PaymentResult, error) { orderID = strings.TrimSpace(orderID) if orderID == "" { return nil, errors.New("PayPal 查单缺少 order ID") @@ -154,7 +153,7 @@ func paypalAutoCaptureEnabled(c map[string]any) bool { return true } -func (a *paypalAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *paypalAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("PayPal 退款请求为空") } @@ -202,7 +201,7 @@ func (a *paypalAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques return paypalRefundResult(req, rsp.Response, c) } -func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*biz.PaymentResult, error) { +func paypalRefundResult(req *system.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*system.PaymentResult, error) { if req == nil || refund == nil { return nil, errors.New("PayPal 退款响应为空") } @@ -216,8 +215,8 @@ func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCap return nil, errors.New("PayPal 退款响应缺少 refund ID") } currency := strings.ToUpper(strings.TrimSpace(req.Currency)) - result := &biz.PaymentResult{ - Provider: biz.PaymentPayPal, + result := &system.PaymentResult{ + Provider: system.PaymentPayPal, Status: normalizePayPalRefundState(refund.Status), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, @@ -247,7 +246,7 @@ func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCap return result, nil } -func paypalCreateResult(req *biz.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*biz.PaymentResult, error) { +func paypalCreateResult(req *system.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*system.PaymentResult, error) { if req == nil || order == nil { return nil, errors.New("PayPal 下单响应为空") } @@ -278,7 +277,7 @@ func paypalCreateResult(req *biz.PaymentRequest, order *paypal.OrderDetail, c ma return result, nil } -func (a *paypalAdapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *paypalAdapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("PayPal 回调为空") } @@ -326,7 +325,7 @@ func (a *paypalAdapter) Callback(ctx context.Context, callback *biz.PaymentCallb return result, nil } -func paypalApplicationContext(req *biz.PaymentRequest, c map[string]any) map[string]any { +func paypalApplicationContext(req *system.PaymentRequest, c map[string]any) map[string]any { applicationContext := map[string]any{} if configured, ok := req.Extra["application_context"].(map[string]any); ok { for key, value := range configured { @@ -353,7 +352,7 @@ func paypalApplicationContext(req *biz.PaymentRequest, c map[string]any) map[str return applicationContext } -func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*system.PaymentResult, error) { if order == nil { return nil, errors.New("PayPal 订单结果为空") } @@ -365,8 +364,8 @@ func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo strin tradeNo = fallbackTradeNo } capture := paypalOrderCapture(order) - result := &biz.PaymentResult{ - Provider: biz.PaymentPayPal, + result := &system.PaymentResult{ + Provider: system.PaymentPayPal, Status: paypalOrderCaptureState(order), TradeNo: tradeNo, QueryID: queryID, @@ -499,7 +498,7 @@ func paypalOrderAmount(order *paypal.OrderDetail, c map[string]any) (int64, stri return total, currency, found, nil } -func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *biz.PaymentRefundRequest) (string, error) { +func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *system.PaymentRefundRequest) (string, error) { if req == nil { return "", errors.New("PayPal 退款请求为空") } @@ -534,7 +533,7 @@ func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *biz. return capture.Id, nil } -func paypalWebhookResult(event *paypal.WebhookEvent) (*biz.PaymentResult, error) { +func paypalWebhookResult(event *paypal.WebhookEvent) (*system.PaymentResult, error) { var resource map[string]any if err := json.Unmarshal(event.Resource, &resource); err != nil { return nil, fmt.Errorf("PayPal 回调 resource 无效: %w", err) @@ -568,8 +567,8 @@ func paypalWebhookResult(event *paypal.WebhookEvent) (*biz.PaymentResult, error) if tradeNo == "" || queryID == "" { return nil, errors.New("PayPal 回调缺少商户订单号或 order ID") } - return &biz.PaymentResult{ - Provider: biz.PaymentPayPal, + return &system.PaymentResult{ + Provider: system.PaymentPayPal, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, @@ -588,7 +587,7 @@ func paypalPurchaseUnitTradeNo(resource map[string]any) string { return "" } -func paypalCallbackHeader(callback *biz.PaymentCallback, name string) string { +func paypalCallbackHeader(callback *system.PaymentCallback, name string) string { for key, value := range callback.Headers { if strings.EqualFold(key, name) { return strings.TrimSpace(value) diff --git a/internal/integration/payment/paypal_test.go b/internal/integration/payment/paypal_test.go index a5c4c38..1506d40 100644 --- a/internal/integration/payment/paypal_test.go +++ b/internal/integration/payment/paypal_test.go @@ -3,13 +3,12 @@ package payment import ( "context" "encoding/json" + "kra/internal/biz/system" "net/http" "net/http/httptest" "strings" "testing" - "kra/internal/biz" - "github.com/go-pay/gopay/paypal" ) @@ -52,7 +51,7 @@ func TestPayPalCreateAndQueryKeepOrderAndCaptureIDsSeparate(t *testing.T) { adapter := &paypalAdapter{} config := paypalTestConfig(server.URL) - created, err := adapter.Create(context.Background(), &biz.PaymentRequest{ + created, err := adapter.Create(context.Background(), &system.PaymentRequest{ TradeNo: "LOCAL-1", Subject: "Order one", Amount: 1099, @@ -143,7 +142,7 @@ func TestPayPalCreateRejectsMismatchedResponseIdentityAndAmount(t *testing.T) { })) defer server.Close() - _, err := (&paypalAdapter{}).Create(context.Background(), &biz.PaymentRequest{ + _, err := (&paypalAdapter{}).Create(context.Background(), &system.PaymentRequest{ TradeNo: "LOCAL-1", Subject: "Order one", Amount: 1099, Currency: "USD", }, paypalTestConfig(server.URL)) if err == nil || !strings.Contains(err.Error(), tc.want) { @@ -243,8 +242,8 @@ func TestPayPalRefundUsesPersistedCaptureID(t *testing.T) { config := paypalTestConfig(server.URL) config["provider_trade_no"] = "WRONG-CONFIG-CAPTURE" - result, err := (&paypalAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{ - Provider: biz.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1", + result, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ + Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1", QueryID: "ORDER-1", RefundNo: "REFUND-1", Amount: 123, TotalAmount: 1099, Currency: "USD", }, config) if err != nil { @@ -287,8 +286,8 @@ func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) { config := paypalTestConfig(server.URL) config["capture_id"] = "WRONG-CONFIG-CAPTURE" - result, err := (&paypalAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{ - Provider: biz.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1", + result, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ + Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1", RefundNo: "REFUND-2", Amount: 123, TotalAmount: 1099, Currency: "USD", }, config) if err != nil { @@ -302,8 +301,8 @@ func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) { func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T) { config := paypalTestConfig("https://paypal.invalid") config["capture_id"] = "CONFIG-CAPTURE" - _, err := (&paypalAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{ - Provider: biz.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD", + _, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ + Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD", }, config) if err == nil { t.Fatal("Refund() accepted a static config capture ID") @@ -311,7 +310,7 @@ func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T) } func TestPayPalRefundResultBindsRefundIdentity(t *testing.T) { - req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"} + req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"} base := func() *paypal.PaymentCaptureRefund { return &paypal.PaymentCaptureRefund{ Id: "PAYPAL-REFUND-1", InvoiceId: "REFUND-1", Status: "PENDING", @@ -368,7 +367,7 @@ func TestPayPalCallbackUsesGoPayWebhookVerification(t *testing.T) { }`) config := paypalTestConfig(server.URL) config["webhook_id"] = "HOOK-1" - result, err := (&paypalAdapter{}).Callback(context.Background(), &biz.PaymentCallback{ + result, err := (&paypalAdapter{}).Callback(context.Background(), &system.PaymentCallback{ Body: body, Headers: map[string]string{ "Paypal-Auth-Algo": "SHA256withRSA", diff --git a/internal/integration/payment/qq.go b/internal/integration/payment/qq.go index 4477efb..c42b036 100644 --- a/internal/integration/payment/qq.go +++ b/internal/integration/payment/qq.go @@ -5,10 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "strings" - "kra/internal/biz" - "github.com/go-pay/gopay" gopayQQ "github.com/go-pay/gopay/qq" ) @@ -61,7 +60,7 @@ func verifyQQResponse(apiKey, signType string, response any) error { return nil } -func (a *qqAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *qqAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("QQ 支付下单请求为空") } @@ -104,7 +103,7 @@ func (a *qqAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[s if status == "pending" && strings.EqualFold(rsp.ResultCode, "SUCCESS") && strings.TrimSpace(rsp.TransactionId) != "" { status = "success" } - return &biz.PaymentResult{Provider: biz.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil } bm.Set("trade_type", tradeType) mergeGoPayExtras(bm, req.Extra, "nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "notify_url", "trade_type", "mch_id", "sign_type") @@ -118,7 +117,7 @@ func (a *qqAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[s if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("QQ 支付下单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des")) } - result := &biz.PaymentResult{Provider: biz.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)} + result := &system.PaymentResult{Provider: system.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)} return result, nil } @@ -153,7 +152,7 @@ func firstNonEmptyQQ(values ...string) string { return "" } -func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("QQ 支付查单缺少 out_trade_no") @@ -178,7 +177,7 @@ func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("QQ 支付查单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des")) } - result := &biz.PaymentResult{Provider: biz.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)} + result := &system.PaymentResult{Provider: system.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)} if result.TradeNo == "" { result.TradeNo = tradeNo } else if strings.TrimSpace(result.TradeNo) != tradeNo { @@ -205,7 +204,7 @@ func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) return result, nil } -func (a *qqAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *qqAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("QQ 支付退款请求为空") } @@ -269,7 +268,7 @@ func (a *qqAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c if returnedTradeNo == "" { returnedTradeNo = tradeNo } - return &biz.PaymentResult{Provider: biz.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil } func validateQQRefundIdentity(tradeNo, refundNo string, rsp *gopayQQ.RefundResponse) error { @@ -290,7 +289,7 @@ func validateQQRefundIdentity(tradeNo, refundNo string, rsp *gopayQQ.RefundRespo return nil } -func (a *qqAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *qqAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil { return nil, errors.New("QQ 支付回调为空") } @@ -319,5 +318,5 @@ func (a *qqAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c status = "success" } payload, _ := json.Marshal(values) - return &biz.PaymentResult{Provider: biz.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil + return &system.PaymentResult{Provider: system.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil } diff --git a/internal/integration/payment/qq_test.go b/internal/integration/payment/qq_test.go index 76fffaa..2e59c70 100644 --- a/internal/integration/payment/qq_test.go +++ b/internal/integration/payment/qq_test.go @@ -6,12 +6,12 @@ import ( "encoding/json" "fmt" "io" + "kra/internal/biz/system" "net" "net/http" "net/http/httptest" "testing" - "kra/internal/biz" "kra/pkg/paymentkit" "github.com/go-pay/gopay" @@ -84,14 +84,14 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) { _, _ = w.Write(paymentutil.XMLEncode(encoded)) })) - result, err := (&qqAdapter{}).Create(context.Background(), &biz.PaymentRequest{ + result, err := (&qqAdapter{}).Create(context.Background(), &system.PaymentRequest{ TradeNo: "MERCHANT-QQ-1", Subject: "subject", Amount: 100, Currency: "CNY", ClientIP: "127.0.0.1", NotifyURL: "https://merchant.example/qq/notify", }, map[string]any{"mch_id": "QQ-MERCHANT", "api_key": "qq-api-key"}) if err != nil { t.Fatal(err) } - if result.Provider != biz.PaymentQQ || result.Status != "created" || result.TradeNo != "MERCHANT-QQ-1" { + if result.Provider != system.PaymentQQ || result.Status != "created" || result.TradeNo != "MERCHANT-QQ-1" { t.Fatalf("create result = %+v", result) } if result.QueryID != "" || result.ProviderTradeNo != "" { diff --git a/internal/integration/payment/refund_boundary_test.go b/internal/integration/payment/refund_boundary_test.go index f098458..0e8dac2 100644 --- a/internal/integration/payment/refund_boundary_test.go +++ b/internal/integration/payment/refund_boundary_test.go @@ -2,11 +2,10 @@ package payment import ( "context" + "kra/internal/biz/system" "strings" "testing" - "kra/internal/biz" - "github.com/go-pay/gopay" gopayQQ "github.com/go-pay/gopay/qq" "github.com/go-pay/gopay/saobei" @@ -16,12 +15,12 @@ import ( func TestWechatV2RefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *biz.PaymentRefundRequest + req *system.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1, TotalAmount: 1}, want: "out_trade_no"}, - {name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1, TotalAmount: 1}, want: "out_refund_no"}, - {name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, + {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1, TotalAmount: 1}, want: "out_trade_no"}, + {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1, TotalAmount: 1}, want: "out_refund_no"}, + {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&wechatV2Adapter{}).Refund(context.Background(), tc.req, nil) @@ -61,12 +60,12 @@ func TestWechatV2RefundIdentityValidation(t *testing.T) { func TestQQRefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *biz.PaymentRefundRequest + req *system.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "out_trade_no"}, - {name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "out_refund_no"}, - {name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, + {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "out_trade_no"}, + {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "out_refund_no"}, + {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&qqAdapter{}).Refund(context.Background(), tc.req, nil) @@ -106,12 +105,12 @@ func TestQQRefundIdentityValidation(t *testing.T) { func TestSaobeiRefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *biz.PaymentRefundRequest + req *system.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "商户订单号"}, - {name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "退款单号"}, - {name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, + {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "商户订单号"}, + {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "退款单号"}, + {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { clientCreated := false @@ -134,7 +133,7 @@ func TestSaobeiRefundRejectsMismatchedRefundNumber(t *testing.T) { adapter := newSaobeiTestAdapter(&saobeiClientStub{refund: func(context.Context, gopay.BodyMap) (*saobei.RefundRsp, error) { return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "OTHER", OutRefundNo: "SAOBEI-REFUND-1", RefundFee: "25"}, nil }}) - _, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 25}, nil) + _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 25}, nil) if err == nil || !strings.Contains(err.Error(), "terminal_trace") { t.Fatalf("Refund() error = %v", err) } @@ -144,7 +143,7 @@ func TestSaobeiBarcodeCreateTreatsPayingAsPending(t *testing.T) { adapter := newSaobeiTestAdapter(&saobeiClientStub{barcodePay: func(context.Context, gopay.BodyMap) (*saobei.BarcodePayRsp, error) { return &saobei.BarcodePayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: saobei.ResultCodePaying}, OutTradeNo: "SAOBEI-ORDER-1"}, nil }}) - result, err := adapter.Create(context.Background(), &biz.PaymentRequest{TradeNo: "LOCAL-1", Amount: 10, Extra: map[string]any{"method": "barcode", "auth_no": "AUTH-1"}}, nil) + result, err := adapter.Create(context.Background(), &system.PaymentRequest{TradeNo: "LOCAL-1", Amount: 10, Extra: map[string]any{"method": "barcode", "auth_no": "AUTH-1"}}, nil) if err != nil { t.Fatalf("Create() error = %v", err) } diff --git a/internal/integration/payment/response_validation_test.go b/internal/integration/payment/response_validation_test.go index 4a1bd13..0e7aeca 100644 --- a/internal/integration/payment/response_validation_test.go +++ b/internal/integration/payment/response_validation_test.go @@ -1,11 +1,10 @@ package payment import ( + "kra/internal/biz/system" "strings" "testing" - "kra/internal/biz" - gopayAlipay "github.com/go-pay/gopay/alipay" "github.com/go-pay/gopay/douyin" ) @@ -29,7 +28,7 @@ func TestWechatV2ResponseRequiresBusinessSuccessCode(t *testing.T) { func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) { amount := &douyin.AmountInfo{Total: 100, PayerTotal: 0, Currency: "CNY", PayerCurrency: "CNY"} - result := &biz.PaymentResult{} + result := &system.PaymentResult{} if err := populateDouyinPaymentAmount(result, amount, []byte(`{"amount":{"total":100,"payer_total":0,"currency":"CNY","payer_currency":"CNY"}}`)); err != nil { t.Fatal(err) } @@ -37,7 +36,7 @@ func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) { t.Fatalf("explicit zero payer_total was not preserved: %+v", result) } - result = &biz.PaymentResult{} + result = &system.PaymentResult{} if err := populateDouyinPaymentAmount(result, amount, []byte(`{"amount":{"total":100,"currency":"CNY"}}`)); err != nil { t.Fatal(err) } @@ -45,7 +44,7 @@ func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) { t.Fatalf("missing payer_total was treated as authoritative: %+v", result) } - if err := populateDouyinPaymentAmount(&biz.PaymentResult{}, &douyin.AmountInfo{Total: 100, PayerTotal: 101}, []byte(`{"amount":{"total":100,"payer_total":101}}`)); err == nil { + if err := populateDouyinPaymentAmount(&system.PaymentResult{}, &douyin.AmountInfo{Total: 100, PayerTotal: 101}, []byte(`{"amount":{"total":100,"payer_total":101}}`)); err == nil { t.Fatal("accepted payer_total greater than total") } } @@ -74,7 +73,7 @@ func TestValidateDouyinMerchantIdentity(t *testing.T) { } func TestAlipayRefundResultValidatesIdentityAndAmount(t *testing.T) { - req := &biz.PaymentRefundRequest{ + req := &system.PaymentRefundRequest{ TradeNo: "T-ALIPAY", ProviderTradeNo: "P-ALIPAY", RefundNo: "R-ALIPAY", Amount: 123, Currency: "CNY", } diff --git a/internal/integration/payment/result.go b/internal/integration/payment/result.go index fcf63ce..948bca1 100644 --- a/internal/integration/payment/result.go +++ b/internal/integration/payment/result.go @@ -4,14 +4,14 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "net/url" "strings" - "kra/internal/biz" "kra/pkg/paymentkit" ) -func callbackFields(callback *biz.PaymentCallback) map[string]string { +func callbackFields(callback *system.PaymentCallback) map[string]string { fields := map[string]string{} if callback == nil { return fields diff --git a/internal/integration/payment/result_test.go b/internal/integration/payment/result_test.go index bfb0812..e178848 100644 --- a/internal/integration/payment/result_test.go +++ b/internal/integration/payment/result_test.go @@ -1,13 +1,12 @@ package payment import ( + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func TestCallbackFieldsAcceptsCaseInsensitiveContentType(t *testing.T) { - callback := &biz.PaymentCallback{ + callback := &system.PaymentCallback{ Headers: map[string]string{"content-type": "Application/X-WWW-Form-Urlencoded"}, Body: []byte("out_trade_no=ORDER-1&sign=signature"), } diff --git a/internal/integration/payment/saobei.go b/internal/integration/payment/saobei.go index 7102a58..87e6b39 100644 --- a/internal/integration/payment/saobei.go +++ b/internal/integration/payment/saobei.go @@ -4,11 +4,10 @@ import ( "context" "errors" "fmt" + "kra/internal/biz/system" "strings" "time" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/saobei" ) @@ -35,7 +34,7 @@ func (a *saobeiAdapter) client(c map[string]any) (saobeiClient, error) { return saobei.NewClient(text(c, "inst_no"), text(c, "key"), text(c, "merchant_no"), text(c, "terminal_id"), text(c, "access_token"), !strings.EqualFold(text(c, "environment"), "sandbox")) } -func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *saobeiAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("扫呗支付下单请求为空") } @@ -70,7 +69,7 @@ func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying { status = "pending" } - return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil } rsp, err := client.MiniPay(ctx, bm) if err != nil { @@ -82,7 +81,7 @@ func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m if strings.TrimSpace(rsp.OutTradeNo) == "" { return nil, errors.New("扫呗支付下单响应缺少 out_trade_no") } - return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil } func saobeiCreateMethod(extra, config map[string]any) (string, error) { @@ -101,7 +100,7 @@ func saobeiCreateMethod(extra, config map[string]any) (string, error) { } } -func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("扫呗支付查单缺少 out_trade_no") @@ -141,7 +140,7 @@ func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string] if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying { status = "pending" } - result := &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))} + result := &system.PaymentResult{Provider: system.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))} if rsp.TotalFee != "" { result.Amount, err = parseIntegerAmount(rsp.TotalFee) if err != nil { @@ -162,7 +161,7 @@ func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, err } -func (a *saobeiAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *saobeiAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("扫呗支付退款请求为空") } @@ -215,9 +214,9 @@ func (a *saobeiAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques if refundAmount != req.Amount { return nil, errors.New("扫呗支付退款响应金额不匹配") } - return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil + return &system.PaymentResult{Provider: system.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil } -func (a *saobeiAdapter) Callback(_ context.Context, _ *biz.PaymentCallback, _ map[string]any) (*biz.PaymentResult, error) { +func (a *saobeiAdapter) Callback(_ context.Context, _ *system.PaymentCallback, _ map[string]any) (*system.PaymentResult, error) { return nil, errors.New("扫呗支付回调没有可复用的 GoPay 验签器,请改用主动查单") } diff --git a/internal/integration/payment/saobei_test.go b/internal/integration/payment/saobei_test.go index c58b8ff..6afbbad 100644 --- a/internal/integration/payment/saobei_test.go +++ b/internal/integration/payment/saobei_test.go @@ -2,10 +2,9 @@ package payment import ( "context" + "kra/internal/biz/system" "testing" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/saobei" ) @@ -66,7 +65,7 @@ func TestSaobeiCreateKeepsQueryAndProviderTradeIDsSeparate(t *testing.T) { adapter := newSaobeiTestAdapter(&saobeiClientStub{miniPay: func(context.Context, gopay.BodyMap) (*saobei.MiniPayRsp, error) { return &saobei.MiniPayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, OutTradeNo: "SAOBEI-ORDER-1"}, nil }}) - result, err := adapter.Create(context.Background(), &biz.PaymentRequest{ + result, err := adapter.Create(context.Background(), &system.PaymentRequest{ TradeNo: "LOCAL-1", Amount: 100, Extra: map[string]any{"sub_appid": "APP-1", "open_id": "OPEN-1"}, @@ -85,7 +84,7 @@ func TestSaobeiCreateKeepsQueryAndProviderTradeIDsSeparate(t *testing.T) { request = bm return &saobei.BarcodePayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, OutTradeNo: "SAOBEI-ORDER-2", ChannelTradeNo: "CHANNEL-2"}, nil }}) - result, err := adapter.Create(context.Background(), &biz.PaymentRequest{ + result, err := adapter.Create(context.Background(), &system.PaymentRequest{ TradeNo: "LOCAL-2", Amount: 200, Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-2"}, @@ -117,7 +116,7 @@ func TestSaobeiCreateRejectsMissingPlatformOrderID(t *testing.T) { if method != "" { extra["method"] = method } - if _, err := adapter.Create(context.Background(), &biz.PaymentRequest{TradeNo: "LOCAL-MISSING", Amount: 1, Extra: extra}, nil); err == nil { + if _, err := adapter.Create(context.Background(), &system.PaymentRequest{TradeNo: "LOCAL-MISSING", Amount: 1, Extra: extra}, nil); err == nil { t.Fatal("Create() accepted response without out_trade_no") } }) @@ -131,7 +130,7 @@ func TestSaobeiCreateRejectsUnknownMethodBeforeClient(t *testing.T) { return &saobeiClientStub{}, nil }} - _, err := adapter.Create(context.Background(), &biz.PaymentRequest{ + _, err := adapter.Create(context.Background(), &system.PaymentRequest{ TradeNo: "LOCAL-UNKNOWN-METHOD", Amount: 1, Extra: map[string]any{"method": "unsupported"}, }, nil) if err == nil { @@ -230,7 +229,7 @@ func TestSaobeiRefundPrefersPersistedQueryID(t *testing.T) { return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "LOCAL-REFUND-1", OutTradeNo: "SAOBEI-ORDER-1", OutRefundNo: "SAOBEI-REFUND-1", RefundFee: "50"}, nil }}) - result, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{ + result, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ TradeNo: "LOCAL-1", QueryID: "SAOBEI-ORDER-1", ProviderTradeNo: "CHANNEL-1", RefundNo: "LOCAL-REFUND-1", Amount: 50, Currency: "cny", }, map[string]any{"refund_extra": map[string]any{"out_refund_no": "MUST-NOT-BE-SENT"}}) @@ -258,7 +257,7 @@ func TestSaobeiRefundFallsBackToMerchantTradeNo(t *testing.T) { return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "LOCAL-REFUND-2", OutRefundNo: "SAOBEI-REFUND-2", RefundFee: "25"}, nil }}) - _, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{ + _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ TradeNo: "LOCAL-2", RefundNo: "LOCAL-REFUND-2", Amount: 25, Currency: "CNY", }, nil) if err != nil { @@ -282,7 +281,7 @@ func TestSaobeiRefundTreatsPayingAsPending(t *testing.T) { }, nil }}) - result, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{ + result, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ TradeNo: "LOCAL-3", RefundNo: "LOCAL-REFUND-3", Amount: 25, Currency: "CNY", }, nil) if err != nil { @@ -303,7 +302,7 @@ func TestSaobeiRefundRejectsMismatchedAmount(t *testing.T) { }, nil }}) - if _, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{ + if _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ TradeNo: "LOCAL-1", RefundNo: "LOCAL-REFUND-1", Amount: 50, Currency: "CNY", }, nil); err == nil { t.Fatal("Refund() accepted a mismatched refund amount") diff --git a/internal/integration/payment/vendor.go b/internal/integration/payment/vendor.go index 2488b74..30b9425 100644 --- a/internal/integration/payment/vendor.go +++ b/internal/integration/payment/vendor.go @@ -10,11 +10,11 @@ import ( "errors" "fmt" "io" + "kra/internal/biz/system" "net/http" "strings" "time" - "kra/internal/biz" "kra/pkg/paymentkit" ) @@ -37,7 +37,7 @@ func newVendorAdapter(provider string, profile vendorProfile) Adapter { return &vendorPaymentAdapter{provider: provider, profile: profile} } -func (a *vendorPaymentAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *vendorPaymentAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("配置驱动支付下单请求为空") } @@ -45,16 +45,16 @@ func (a *vendorPaymentAdapter) Create(ctx context.Context, req *biz.PaymentReque mergeMap(payload, req.Extra, "merchant_id", "app_id", "trade_no", "subject", "amount", "currency", "notify_url", "client_ip", "timestamp", "nonce") return a.call(ctx, "create_url", payload, req.TradeNo, c) } -func (a *vendorPaymentAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *vendorPaymentAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { return a.call(ctx, "query_url", map[string]any{"merchant_id": text(c, "merchant_id"), "app_id": text(c, "app_id"), "trade_no": tradeNo, "timestamp": time.Now().Unix(), "nonce": nonce()}, tradeNo, c) } -func (a *vendorPaymentAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *vendorPaymentAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("支付退款请求为空") } return a.call(ctx, "refund_url", map[string]any{"merchant_id": text(c, "merchant_id"), "app_id": text(c, "app_id"), "trade_no": req.TradeNo, "refund_no": req.RefundNo, "amount": req.Amount, "total_amount": req.TotalAmount, "currency": req.Currency, "timestamp": time.Now().Unix(), "nonce": nonce()}, req.TradeNo, c) } -func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("配置驱动支付回调为空") } @@ -98,9 +98,9 @@ func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *biz.Payment status = "success" } payload, _ := json.Marshal(fields) - return &biz.PaymentResult{Provider: a.provider, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload}, nil + return &system.PaymentResult{Provider: a.provider, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload}, nil } -func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, payload map[string]any, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, payload map[string]any, tradeNo string, c map[string]any) (*system.PaymentResult, error) { endpoint := text(c, endpointKey) if endpoint == "" { return nil, fmt.Errorf("%s 未配置 %s", a.provider, endpointKey) @@ -131,7 +131,7 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay if response.StatusCode >= 300 { return nil, fmt.Errorf("%s HTTP %d", a.provider, response.StatusCode) } - result := &biz.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)} + result := &system.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)} if endpointKey != "query_url" { return result, nil } @@ -167,7 +167,7 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay return result, nil } -func populateVendorBreakdown(result *biz.PaymentResult, object map[string]any, c map[string]any) error { +func populateVendorBreakdown(result *system.PaymentResult, object map[string]any, c map[string]any) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/wechat_v2.go b/internal/integration/payment/wechat_v2.go index 904b291..9543ec1 100644 --- a/internal/integration/payment/wechat_v2.go +++ b/internal/integration/payment/wechat_v2.go @@ -8,13 +8,12 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "net/http" "net/url" "strings" "time" - "kra/internal/biz" - "github.com/go-pay/gopay" "github.com/go-pay/gopay/pkg/xhttp" gopayWechat "github.com/go-pay/gopay/wechat" @@ -49,7 +48,7 @@ func wechatV2Sign(values map[string]string, key, signType string) string { return gopayWechat.GetReleaseSign(key, wechatV2SignType(signType), body) } -func (a *wechatV2Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV2Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v2 下单请求为空") } @@ -99,15 +98,15 @@ func (a *wechatV2Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c if err = verifyWechatV2Response(resultValues, client.ApiKey, values["sign_type"]); err != nil { return nil, err } - return &biz.PaymentResult{ - Provider: biz.PaymentWechatV2, + return &system.PaymentResult{ + Provider: system.PaymentWechatV2, Status: "created", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues), }, nil } -func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *biz.PaymentRequest, c map[string]any, values map[string]string) (*biz.PaymentResult, error) { +func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *system.PaymentRequest, c map[string]any, values map[string]string) (*system.PaymentResult, error) { authCode := firstAny(req.Extra, "auth_code", "authcode", "barcode", "pay_code", "payment_code") if authCode == "" { authCode = firstAny(c, "auth_code", "authcode", "barcode", "pay_code", "payment_code") @@ -146,14 +145,14 @@ func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWecha return nil, errors.New("微信支付 v2 付款码支付响应的 out_trade_no 不匹配") } if strings.EqualFold(resultValues["err_code"], "USERPAYING") { - return &biz.PaymentResult{Provider: biz.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil + return &system.PaymentResult{Provider: system.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil } providerTradeNo := strings.TrimSpace(resultValues["transaction_id"]) if providerTradeNo == "" { return nil, errors.New("微信支付 v2 付款码支付响应缺少 transaction_id") } - result := &biz.PaymentResult{ - Provider: biz.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo, + result := &system.PaymentResult{ + Provider: system.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo, ProviderTradeNo: providerTradeNo, Payload: wechatV2Payload(resultValues), } amount := strings.TrimSpace(resultValues["total_fee"]) @@ -202,7 +201,7 @@ func wechatV2CreateMethod(extra, config map[string]any) (string, error) { } } -func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("微信支付 v2 查单缺少 out_trade_no") @@ -227,8 +226,8 @@ func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[strin if err = verifyWechatV2Response(values, client.ApiKey, body.GetString("sign_type")); err != nil { return nil, err } - result := &biz.PaymentResult{ - Provider: biz.PaymentWechatV2, + result := &system.PaymentResult{ + Provider: system.PaymentWechatV2, Status: normalizePaymentStatus(values["trade_state"], "pending"), TradeNo: first(values, "out_trade_no"), Payload: wechatV2Payload(values), @@ -259,7 +258,7 @@ func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[strin return result, nil } -func (a *wechatV2Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV2Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v2 退款请求为空") } @@ -306,8 +305,8 @@ func (a *wechatV2Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ if err = validateWechatV2RefundIdentity(tradeNo, refundNo, rsp); err != nil { return nil, err } - result := &biz.PaymentResult{ - Provider: biz.PaymentWechatV2, + result := &system.PaymentResult{ + Provider: system.PaymentWechatV2, Status: "created", TradeNo: tradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), @@ -352,7 +351,7 @@ func validateWechatV2RefundIdentity(tradeNo, refundNo string, rsp *gopayWechat.R return nil } -func (a *wechatV2Adapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV2Adapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("微信支付 v2 回调为空") } @@ -376,7 +375,7 @@ func (a *wechatV2Adapter) Callback(_ context.Context, callback *biz.PaymentCallb status = "success" } payload, _ := json.Marshal(values) - return &biz.PaymentResult{Provider: biz.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil + return &system.PaymentResult{Provider: system.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil } func newWechatV2Client(c map[string]any, endpoint string) (*gopayWechat.Client, error) { @@ -467,7 +466,7 @@ func wechatV2Payload(values map[string]string) []byte { return []byte(gopayWechat.GenerateXml(toGoPayBodyMap(values))) } -func populateWechatV2Breakdown(result *biz.PaymentResult, values map[string]string) error { +func populateWechatV2Breakdown(result *system.PaymentResult, values map[string]string) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/wechat_v3.go b/internal/integration/payment/wechat_v3.go index 9d49b55..f34d7c4 100644 --- a/internal/integration/payment/wechat_v3.go +++ b/internal/integration/payment/wechat_v3.go @@ -8,20 +8,19 @@ import ( "encoding/pem" "errors" "fmt" + "kra/internal/biz/system" "net/http" "net/url" "strings" "time" - "kra/internal/biz" - "github.com/go-pay/gopay" gopayWechatV3 "github.com/go-pay/gopay/wechat/v3" ) type wechatV3Adapter struct{} -func (a *wechatV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV3Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v3 下单请求为空") } @@ -124,7 +123,7 @@ func normalizeWechatV3TradeType(value string) string { return strings.NewReplacer(".", "", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value))) } -func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*biz.PaymentResult, error) { +func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*system.PaymentResult, error) { if rsp == nil { return nil, errors.New("微信支付 v3 付款码响应为空") } @@ -140,8 +139,8 @@ func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*biz. } else if returnedTradeNo != tradeNo { return nil, errors.New("微信支付 v3 付款码响应的商户订单号不匹配") } - result := &biz.PaymentResult{ - Provider: biz.PaymentWechatV3, + result := &system.PaymentResult{ + Provider: system.PaymentWechatV3, Status: normalizePaymentStatus(order.TradeState, "pending"), TradeNo: tradeNo, ProviderTradeNo: strings.TrimSpace(order.TransactionId), @@ -180,7 +179,7 @@ func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*biz. return result, nil } -func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("微信支付 v3 查单缺少 out_trade_no") @@ -205,8 +204,8 @@ func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin order := rsp.Response payload := wechatV3ResponsePayload(rsp.SignInfo, order) - result := &biz.PaymentResult{ - Provider: biz.PaymentWechatV3, + result := &system.PaymentResult{ + Provider: system.PaymentWechatV3, Status: normalizePaymentStatus(order.TradeState, "pending"), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, @@ -237,7 +236,7 @@ func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin return result, nil } -func (a *wechatV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v3 退款请求为空") } @@ -280,8 +279,8 @@ func (a *wechatV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ if err != nil { return nil, err } - result := &biz.PaymentResult{ - Provider: biz.PaymentWechatV3, + result := &system.PaymentResult{ + Provider: system.PaymentWechatV3, Status: normalizeRefundState(refund.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, @@ -337,7 +336,7 @@ func validateWechatV3RefundIdentity(refund *gopayWechatV3.RefundOrderResponse, t return refundID, nil } -func (a *wechatV3Adapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) { +func (a *wechatV3Adapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { if callback == nil { return nil, errors.New("微信支付 v3 回调为空") } @@ -396,8 +395,8 @@ func (a *wechatV3Adapter) Callback(ctx context.Context, callback *biz.PaymentCal return nil, errors.New("微信支付 v3 回调 appid 不匹配") } plain, _ := json.Marshal(pay) - return &biz.PaymentResult{ - Provider: biz.PaymentWechatV3, + return &system.PaymentResult{ + Provider: system.PaymentWechatV3, Status: status, TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, @@ -491,22 +490,22 @@ func wechatV3PlatformSerial(certificate string) (string, error) { return strings.ToUpper(cert.SerialNumber.Text(16)), nil } -func wechatV3CreateResult(tradeNo string, code int, signInfo *gopayWechatV3.SignInfo, response any, errResponse gopayWechatV3.ErrResponse, rawError string) (*biz.PaymentResult, error) { +func wechatV3CreateResult(tradeNo string, code int, signInfo *gopayWechatV3.SignInfo, response any, errResponse gopayWechatV3.ErrResponse, rawError string) (*system.PaymentResult, error) { if code != gopayWechatV3.Success { return nil, wechatV3APIError("下单", code, errResponse, rawError) } if response == nil { return nil, errors.New("微信支付 v3 下单响应缺少预支付信息") } - return &biz.PaymentResult{ - Provider: biz.PaymentWechatV3, + return &system.PaymentResult{ + Provider: system.PaymentWechatV3, Status: "created", TradeNo: tradeNo, Payload: wechatV3ResponsePayload(signInfo, response), }, nil } -func wechatV3PrepayCreateResult(client *gopayWechatV3.ClientV3, appID, tradeType, tradeNo string, rsp *gopayWechatV3.PrepayRsp) (*biz.PaymentResult, error) { +func wechatV3PrepayCreateResult(client *gopayWechatV3.ClientV3, appID, tradeType, tradeNo string, rsp *gopayWechatV3.PrepayRsp) (*system.PaymentResult, error) { if rsp == nil { return nil, errors.New("微信支付 v3 下单响应为空") } @@ -553,7 +552,7 @@ func wechatV3ResponsePayload(signInfo *gopayWechatV3.SignInfo, response any) []b return payload } -func populateWechatV3Breakdown(result *biz.PaymentResult, object map[string]any) error { +func populateWechatV3Breakdown(result *system.PaymentResult, object map[string]any) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/wechat_v3_codepay_test.go b/internal/integration/payment/wechat_v3_codepay_test.go index 64e6019..9d42d06 100644 --- a/internal/integration/payment/wechat_v3_codepay_test.go +++ b/internal/integration/payment/wechat_v3_codepay_test.go @@ -4,13 +4,12 @@ import ( "context" "encoding/json" "io" + "kra/internal/biz/system" "net/http" "net/http/httptest" "strings" "testing" - "kra/internal/biz" - gopayWechatV3 "github.com/go-pay/gopay/wechat/v3" ) @@ -52,8 +51,8 @@ func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) { })) defer server.Close() - result, err := (&wechatV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{ - Provider: biz.PaymentWechatV3, + result, err := (&wechatV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ + Provider: system.PaymentWechatV3, TradeNo: "T-WECHAT-CODEPAY", Subject: "subject", Amount: 123, @@ -86,7 +85,7 @@ func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) { if amount["total"] != float64(123) || amount["currency"] != "CNY" { t.Fatalf("request amount = %#v", amount) } - if result.Provider != biz.PaymentWechatV3 || result.Status != "success" || result.TradeNo != "T-WECHAT-CODEPAY" || result.ProviderTradeNo != "WX-CODEPAY-1" || result.QueryID != "" { + if result.Provider != system.PaymentWechatV3 || result.Status != "success" || result.TradeNo != "T-WECHAT-CODEPAY" || result.ProviderTradeNo != "WX-CODEPAY-1" || result.QueryID != "" { t.Fatalf("result identity = %+v", result) } if result.Amount != 123 || result.Currency != "CNY" || result.PayerCurrency != "CNY" || !result.AmountBreakdownKnown || result.PayerPaidAmount != 100 || result.CashPaidAmount != 100 || result.DiscountAmount != 23 { @@ -109,7 +108,7 @@ func TestWechatV3CodePayAliasesRequireAuthCode(t *testing.T) { for _, method := range []string{"micropay", "micro_pay", "micro-pay", "codepay", "code_pay", "code-pay", "barcode", "barcode_pay", "facepay", "face_pay"} { t.Run(method, func(t *testing.T) { - _, err := (&wechatV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{ + _, err := (&wechatV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ TradeNo: "T-WECHAT-CODEPAY", Subject: "subject", Amount: 1, Currency: "CNY", Extra: map[string]any{"method": method}, }, config) diff --git a/internal/integration/payment/wechat_v3_douyin_refund_test.go b/internal/integration/payment/wechat_v3_douyin_refund_test.go index 966c0b8..5558c25 100644 --- a/internal/integration/payment/wechat_v3_douyin_refund_test.go +++ b/internal/integration/payment/wechat_v3_douyin_refund_test.go @@ -2,11 +2,10 @@ package payment import ( "context" + "kra/internal/biz/system" "strings" "testing" - "kra/internal/biz" - gopayDouyin "github.com/go-pay/gopay/douyin" gopayWechatV3 "github.com/go-pay/gopay/wechat/v3" ) @@ -14,12 +13,12 @@ import ( func TestWechatV3RefundRejectsBlankIdentityBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *biz.PaymentRefundRequest + req *system.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, - {name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, - {name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, + {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, + {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, + {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&wechatV3Adapter{}).Refund(context.Background(), tc.req, nil) @@ -68,12 +67,12 @@ func TestWechatV3RefundIdentityValidation(t *testing.T) { func TestDouyinRefundRejectsBlankIdentityBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *biz.PaymentRefundRequest + req *system.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, - {name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, - {name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, + {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, + {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, + {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&douyinAdapter{}).Refund(context.Background(), tc.req, nil) diff --git a/internal/integration/provider.go b/internal/integration/provider.go index f719e3b..f261a89 100644 --- a/internal/integration/provider.go +++ b/internal/integration/provider.go @@ -4,7 +4,7 @@ package integration import ( - "kra/internal/biz" + "kra/internal/biz/system" "kra/internal/integration/cache" "kra/internal/integration/email" mqintegration "kra/internal/integration/mq" @@ -21,12 +21,12 @@ var ProviderSet = wire.NewSet( email.NewEmailRepo, storage.NewFileStorage, NewConnectivityTester, - wire.Bind(new(biz.IntegrationConnectionTester), new(*ConnectivityTester)), + wire.Bind(new(system.IntegrationConnectionTester), new(*ConnectivityTester)), mqintegration.New, wire.Bind(new(mq.Client), new(*mqintegration.Reloadable)), wire.Bind(new(mq.Registry), new(*mqintegration.Reloadable)), wire.Bind(new(mq.SubscriptionRegistrar), new(*mqintegration.Reloadable)), websocketintegration.New, wire.Bind(new(platformws.Hub), new(*websocketintegration.Server)), - wire.Bind(new(biz.FileStorage), new(*storage.Reloadable)), + wire.Bind(new(system.FileStorage), new(*storage.Reloadable)), ) diff --git a/internal/integration/storage/aliyun_storage.go b/internal/integration/storage/aliyun_storage.go index cd8b61a..8b7dc4b 100644 --- a/internal/integration/storage/aliyun_storage.go +++ b/internal/integration/storage/aliyun_storage.go @@ -4,12 +4,13 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "path" "strings" - "github.com/aliyun/aliyun-oss-go-sdk/oss" - "kra/internal/biz" "kra/internal/conf" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" ) type aliyunStorage struct { @@ -17,7 +18,7 @@ type aliyunStorage struct { baseURL, prefix string } -func newAliyunStorage(config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { +func newAliyunStorage(config *conf.AdminBackend_ObjectStore) (system.FileStorage, error) { if config == nil || config.Endpoint == "" || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { return nil, fmt.Errorf("aliyun-oss storage configuration is incomplete") } @@ -40,12 +41,12 @@ func (s *aliyunStorage) key(name string) string { func (s *aliyunStorage) unkey(key string) string { return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") } -func (s *aliyunStorage) file(key string, size int64) *biz.StoredFile { +func (s *aliyunStorage) file(key string, size int64) *system.StoredFile { name := s.unkey(key) url := s.baseURL + "/" + key - return &biz.StoredFile{Name: path.Base(name), Path: name, URL: url, Size: size} + return &system.StoredFile{Name: path.Base(name), Path: name, URL: url, Size: size} } -func (s *aliyunStorage) Put(_ context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *aliyunStorage) Put(_ context.Context, name string, reader io.Reader) (*system.StoredFile, error) { key := s.key(name) if err := s.bucket.PutObject(key, reader); err != nil { return nil, err @@ -64,7 +65,7 @@ func (s *aliyunStorage) Open(_ context.Context, name string) (io.ReadCloser, err func (s *aliyunStorage) Delete(_ context.Context, name string) error { return s.bucket.DeleteObject(s.key(name)) } -func (s *aliyunStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *aliyunStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *aliyunStorage) DeletePrefix(ctx context.Context, prefix string) error { @@ -85,7 +86,7 @@ func (s *aliyunStorage) DeletePrefix(ctx context.Context, prefix string) error { cursor = next } } -func (s *aliyunStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *aliyunStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } @@ -93,7 +94,7 @@ func (s *aliyunStorage) List(_ context.Context, prefix, cursor string, limit int if err != nil { return nil, "", false, err } - items := make([]*biz.StoredFile, 0, len(result.Objects)) + items := make([]*system.StoredFile, 0, len(result.Objects)) for _, object := range result.Objects { item := s.file(object.Key, object.Size) item.LastModified = object.LastModified diff --git a/internal/integration/storage/aws_storage.go b/internal/integration/storage/aws_storage.go index d355274..68d3a67 100644 --- a/internal/integration/storage/aws_storage.go +++ b/internal/integration/storage/aws_storage.go @@ -4,16 +4,17 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "path" "strings" + "kra/internal/conf" + "github.com/aws/aws-sdk-go-v2/aws" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" - "kra/internal/biz" - "kra/internal/conf" ) type awsStorage struct { @@ -21,7 +22,7 @@ type awsStorage struct { bucket, baseURL, prefix string } -func newAWSStorage(provider string, config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { +func newAWSStorage(provider string, config *conf.AdminBackend_ObjectStore) (system.FileStorage, error) { if config == nil || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { return nil, fmt.Errorf("%s storage configuration is incomplete", provider) } @@ -68,12 +69,12 @@ func (s *awsStorage) key(name string) string { func (s *awsStorage) unkey(key string) string { return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") } -func (s *awsStorage) file(key string, size int64) *biz.StoredFile { +func (s *awsStorage) file(key string, size int64) *system.StoredFile { name := s.unkey(key) url := s.baseURL + "/" + key - return &biz.StoredFile{Name: path.Base(name), Path: name, URL: url, Size: size} + return &system.StoredFile{Name: path.Base(name), Path: name, URL: url, Size: size} } -func (s *awsStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *awsStorage) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) { key := s.key(name) result, err := manager.NewUploader(s.client).Upload(ctx, &s3.PutObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(key), Body: reader}) if err != nil { @@ -101,7 +102,7 @@ func (s *awsStorage) Delete(ctx context.Context, name string) error { _, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(s.key(name))}) return err } -func (s *awsStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *awsStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *awsStorage) DeletePrefix(ctx context.Context, prefix string) error { @@ -120,7 +121,7 @@ func (s *awsStorage) DeletePrefix(ctx context.Context, prefix string) error { } } } -func (s *awsStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *awsStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } @@ -132,7 +133,7 @@ func (s *awsStorage) List(ctx context.Context, prefix, cursor string, limit int) if err != nil { return nil, "", false, err } - items := make([]*biz.StoredFile, 0, len(result.Contents)) + items := make([]*system.StoredFile, 0, len(result.Contents)) for _, object := range result.Contents { if object.Key == nil { continue diff --git a/internal/integration/storage/huawei_storage.go b/internal/integration/storage/huawei_storage.go index ebc1320..7c6d871 100644 --- a/internal/integration/storage/huawei_storage.go +++ b/internal/integration/storage/huawei_storage.go @@ -4,12 +4,13 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "path" "strings" - "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" - "kra/internal/biz" "kra/internal/conf" + + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" ) type huaweiStorage struct { @@ -17,7 +18,7 @@ type huaweiStorage struct { bucket, baseURL, prefix string } -func newHuaweiStorage(config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { +func newHuaweiStorage(config *conf.AdminBackend_ObjectStore) (system.FileStorage, error) { if config == nil || config.Endpoint == "" || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { return nil, fmt.Errorf("huawei-obs storage configuration is incomplete") } @@ -36,12 +37,12 @@ func (s *huaweiStorage) key(name string) string { func (s *huaweiStorage) unkey(key string) string { return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") } -func (s *huaweiStorage) file(key string, size int64) *biz.StoredFile { +func (s *huaweiStorage) file(key string, size int64) *system.StoredFile { name := s.unkey(key) fileURL := s.baseURL + "/" + key - return &biz.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size} + return &system.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size} } -func (s *huaweiStorage) Put(_ context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *huaweiStorage) Put(_ context.Context, name string, reader io.Reader) (*system.StoredFile, error) { key := s.key(name) if _, err := s.client.PutObject(&obs.PutObjectInput{PutObjectBasicInput: obs.PutObjectBasicInput{ObjectOperationInput: obs.ObjectOperationInput{Bucket: s.bucket, Key: key}}, Body: reader}); err != nil { return nil, err @@ -63,7 +64,7 @@ func (s *huaweiStorage) Delete(_ context.Context, name string) error { _, err := s.client.DeleteObject(&obs.DeleteObjectInput{Bucket: s.bucket, Key: s.key(name)}) return err } -func (s *huaweiStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *huaweiStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *huaweiStorage) DeletePrefix(ctx context.Context, prefix string) error { @@ -84,7 +85,7 @@ func (s *huaweiStorage) DeletePrefix(ctx context.Context, prefix string) error { cursor = next } } -func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } @@ -92,7 +93,7 @@ func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int if err != nil { return nil, "", false, err } - items := make([]*biz.StoredFile, 0, len(result.Contents)) + items := make([]*system.StoredFile, 0, len(result.Contents)) for _, object := range result.Contents { item := s.file(object.Key, object.Size) item.LastModified = object.LastModified diff --git a/internal/integration/storage/local.go b/internal/integration/storage/local.go index 7d83314..a4e1721 100644 --- a/internal/integration/storage/local.go +++ b/internal/integration/storage/local.go @@ -4,13 +4,13 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "mime" "os" "path/filepath" "sort" "strings" - "kra/internal/biz" "kra/internal/conf" ) @@ -19,7 +19,7 @@ type fileStorage struct { urlPrefix string } -func New(config *conf.AdminBackend) (biz.FileStorage, error) { +func New(config *conf.AdminBackend) (system.FileStorage, error) { storageType := "local" if config != nil && config.Storage != nil && config.Storage.Type != "" { storageType = strings.ToLower(config.Storage.Type) @@ -63,8 +63,8 @@ func New(config *conf.AdminBackend) (biz.FileStorage, error) { return &fileStorage{root: root, urlPrefix: "/" + strings.Trim(prefix, "/")}, nil } -func composeFiles(ctx context.Context, storage biz.FileStorage, names []string, destination string) (*biz.StoredFile, string, error) { - var stored *biz.StoredFile +func composeFiles(ctx context.Context, storage system.FileStorage, names []string, destination string) (*system.StoredFile, string, error) { + var stored *system.StoredFile hash, err := composeStreams(ctx, names, storage.Open, func(ctx context.Context, destination string, reader io.Reader) error { var putErr error stored, putErr = storage.Put(ctx, destination, reader) @@ -87,7 +87,7 @@ func (s *fileStorage) resolve(name string) (string, error) { return filepath.Join(s.root, clean), nil } -func (s *fileStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *fileStorage) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) { path, err := s.resolve(name) if err != nil { return nil, err @@ -107,7 +107,7 @@ func (s *fileStorage) Put(ctx context.Context, name string, reader io.Reader) (* if closeErr != nil { return nil, closeErr } - return &biz.StoredFile{Name: filepath.Base(name), Path: name, URL: s.urlPrefix + "/" + strings.TrimPrefix(filepath.ToSlash(name), "/"), Size: size}, nil + return &system.StoredFile{Name: filepath.Base(name), Path: name, URL: s.urlPrefix + "/" + strings.TrimPrefix(filepath.ToSlash(name), "/"), Size: size}, nil } func (s *fileStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) { @@ -134,7 +134,7 @@ func (s *fileStorage) Delete(ctx context.Context, name string) error { return nil } -func (s *fileStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *fileStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *fileStorage) DeletePrefix(ctx context.Context, prefix string) error { @@ -151,7 +151,7 @@ func (s *fileStorage) DeletePrefix(ctx context.Context, prefix string) error { } return nil } -func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } @@ -160,7 +160,7 @@ func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int return nil, "", false, err } walkRoot := filepath.Dir(root) - items := []*biz.StoredFile{} + items := []*system.StoredFile{} err = filepath.WalkDir(walkRoot, func(path string, entry os.DirEntry, walkErr error) error { if walkErr != nil { if os.IsNotExist(walkErr) { @@ -183,7 +183,7 @@ func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int if infoErr != nil { return infoErr } - items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size(), LastModified: info.ModTime(), ContentType: mime.TypeByExtension(filepath.Ext(entry.Name()))}) + items = append(items, &system.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size(), LastModified: info.ModTime(), ContentType: mime.TypeByExtension(filepath.Ext(entry.Name()))}) return nil }) if err != nil { diff --git a/internal/integration/storage/qiniu_storage.go b/internal/integration/storage/qiniu_storage.go index debed10..0b9ca73 100644 --- a/internal/integration/storage/qiniu_storage.go +++ b/internal/integration/storage/qiniu_storage.go @@ -4,16 +4,17 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "net/http" "os" "path" "strings" "time" + "kra/internal/conf" + "github.com/qiniu/go-sdk/v7/auth/qbox" qstorage "github.com/qiniu/go-sdk/v7/storage" - "kra/internal/biz" - "kra/internal/conf" ) type qiniuStorage struct { @@ -24,7 +25,7 @@ type qiniuStorage struct { token string } -func newQiniuStorage(config *conf.AdminBackend_Qiniu) (biz.FileStorage, error) { +func newQiniuStorage(config *conf.AdminBackend_Qiniu) (system.FileStorage, error) { if config == nil || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { return nil, fmt.Errorf("qiniu storage configuration is incomplete") } @@ -46,10 +47,10 @@ func newQiniuStorage(config *conf.AdminBackend_Qiniu) (biz.FileStorage, error) { token := policy.UploadToken(mac) return &qiniuStorage{config: config, upload: qstorage.NewFormUploader(&cfg), manager: qstorage.NewBucketManager(mac, &cfg), mac: mac, token: token}, nil } -func (s *qiniuStorage) file(key string, size int64) *biz.StoredFile { - return &biz.StoredFile{Name: path.Base(key), Path: key, URL: strings.TrimSuffix(s.config.BaseUrl, "/") + "/" + key, Size: size} +func (s *qiniuStorage) file(key string, size int64) *system.StoredFile { + return &system.StoredFile{Name: path.Base(key), Path: key, URL: strings.TrimSuffix(s.config.BaseUrl, "/") + "/" + key, Size: size} } -func (s *qiniuStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *qiniuStorage) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) { temporary, err := os.CreateTemp("", "kra-qiniu-upload-*") if err != nil { return nil, err @@ -91,7 +92,7 @@ func (s *qiniuStorage) Open(ctx context.Context, name string) (io.ReadCloser, er func (s *qiniuStorage) Delete(ctx context.Context, name string) error { return s.manager.Delete(s.config.Bucket, strings.TrimPrefix(name, "/")) } -func (s *qiniuStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *qiniuStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *qiniuStorage) DeletePrefix(ctx context.Context, prefix string) error { @@ -110,7 +111,7 @@ func (s *qiniuStorage) DeletePrefix(ctx context.Context, prefix string) error { } } } -func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } @@ -118,7 +119,7 @@ func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit in if err != nil { return nil, "", false, err } - out := make([]*biz.StoredFile, 0, len(entries)) + out := make([]*system.StoredFile, 0, len(entries)) for _, entry := range entries { item := s.file(entry.Key, entry.Fsize) item.LastModified = time.Unix(0, entry.PutTime*100) diff --git a/internal/integration/storage/reloadable.go b/internal/integration/storage/reloadable.go index ea70b3d..2ddfbfc 100644 --- a/internal/integration/storage/reloadable.go +++ b/internal/integration/storage/reloadable.go @@ -3,15 +3,15 @@ package storage import ( "context" "io" + "kra/internal/biz/system" "sync" - "kra/internal/biz" "kra/internal/conf" ) type Reloadable struct { mu sync.RWMutex - current biz.FileStorage + current system.FileStorage } func NewFileStorage(runtime *conf.Runtime) (*Reloadable, error) { @@ -30,13 +30,13 @@ func NewReloadable(config *conf.AdminBackend) (*Reloadable, error) { return &Reloadable{current: current}, nil } -func (s *Reloadable) Replace(current biz.FileStorage) { +func (s *Reloadable) Replace(current system.FileStorage) { s.mu.Lock() s.current = current s.mu.Unlock() } -func (s *Reloadable) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *Reloadable) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) { s.mu.RLock() defer s.mu.RUnlock() return s.current.Put(ctx, name, reader) @@ -51,7 +51,7 @@ func (s *Reloadable) Delete(ctx context.Context, name string) error { defer s.mu.RUnlock() return s.current.Delete(ctx, name) } -func (s *Reloadable) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *Reloadable) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { s.mu.RLock() defer s.mu.RUnlock() return s.current.Compose(ctx, names, destination) @@ -61,7 +61,7 @@ func (s *Reloadable) DeletePrefix(ctx context.Context, prefix string) error { defer s.mu.RUnlock() return s.current.DeletePrefix(ctx, prefix) } -func (s *Reloadable) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *Reloadable) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { s.mu.RLock() defer s.mu.RUnlock() return s.current.List(ctx, prefix, cursor, limit) diff --git a/internal/integration/storage/s3_storage.go b/internal/integration/storage/s3_storage.go index 4003229..53ad0c0 100644 --- a/internal/integration/storage/s3_storage.go +++ b/internal/integration/storage/s3_storage.go @@ -4,14 +4,15 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "net/url" "path" "strings" + "kra/internal/conf" + "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" - "kra/internal/biz" - "kra/internal/conf" ) type s3Storage struct { @@ -19,7 +20,7 @@ type s3Storage struct { bucket, baseURL, prefix string } -func newS3Storage(provider string, config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { +func newS3Storage(provider string, config *conf.AdminBackend_ObjectStore) (system.FileStorage, error) { if config == nil || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" { return nil, fmt.Errorf("%s storage configuration is incomplete", provider) } @@ -66,12 +67,12 @@ func (s *s3Storage) key(name string) string { func (s *s3Storage) unkey(key string) string { return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") } -func (s *s3Storage) file(key string, size int64) *biz.StoredFile { +func (s *s3Storage) file(key string, size int64) *system.StoredFile { name := s.unkey(key) rawURL := s.baseURL + "/" + key - return &biz.StoredFile{Name: path.Base(name), Path: name, URL: rawURL, Size: size} + return &system.StoredFile{Name: path.Base(name), Path: name, URL: rawURL, Size: size} } -func (s *s3Storage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *s3Storage) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) { key := s.key(name) info, err := s.client.PutObject(ctx, s.bucket, key, reader, -1, minio.PutObjectOptions{}) if err != nil { @@ -93,7 +94,7 @@ func (s *s3Storage) Open(ctx context.Context, name string) (io.ReadCloser, error func (s *s3Storage) Delete(ctx context.Context, name string) error { return s.client.RemoveObject(ctx, s.bucket, s.key(name), minio.RemoveObjectOptions{}) } -func (s *s3Storage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *s3Storage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *s3Storage) DeletePrefix(ctx context.Context, prefix string) error { @@ -108,12 +109,12 @@ func (s *s3Storage) DeletePrefix(ctx context.Context, prefix string) error { } return nil } -func (s *s3Storage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *s3Storage) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } items := s.client.ListObjects(ctx, s.bucket, minio.ListObjectsOptions{Prefix: s.key(prefix), Recursive: true, StartAfter: s.key(cursor)}) - out := make([]*biz.StoredFile, 0, limit+1) + out := make([]*system.StoredFile, 0, limit+1) for item := range items { if item.Err != nil { return nil, "", false, item.Err diff --git a/internal/integration/storage/tencent_storage.go b/internal/integration/storage/tencent_storage.go index f04602e..7cdb32a 100644 --- a/internal/integration/storage/tencent_storage.go +++ b/internal/integration/storage/tencent_storage.go @@ -4,15 +4,16 @@ import ( "context" "fmt" "io" + "kra/internal/biz/system" "net/http" "net/url" "path" "strings" "time" - cos "github.com/tencentyun/cos-go-sdk-v5" - "kra/internal/biz" "kra/internal/conf" + + cos "github.com/tencentyun/cos-go-sdk-v5" ) type tencentStorage struct { @@ -20,7 +21,7 @@ type tencentStorage struct { baseURL, prefix string } -func newTencentStorage(config *conf.AdminBackend_ObjectStore) (biz.FileStorage, error) { +func newTencentStorage(config *conf.AdminBackend_ObjectStore) (system.FileStorage, error) { if config == nil || config.Bucket == "" || config.Region == "" || config.AccessKey == "" || config.SecretKey == "" { return nil, fmt.Errorf("tencent-cos storage configuration is incomplete") } @@ -50,12 +51,12 @@ func (s *tencentStorage) key(name string) string { func (s *tencentStorage) unkey(key string) string { return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/") } -func (s *tencentStorage) file(key string, size int64) *biz.StoredFile { +func (s *tencentStorage) file(key string, size int64) *system.StoredFile { name := s.unkey(key) fileURL := s.baseURL + "/" + key - return &biz.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size} + return &system.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size} } -func (s *tencentStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) { +func (s *tencentStorage) Put(ctx context.Context, name string, reader io.Reader) (*system.StoredFile, error) { key := s.key(name) if _, err := s.client.Object.Put(ctx, key, reader, nil); err != nil { return nil, err @@ -77,7 +78,7 @@ func (s *tencentStorage) Delete(ctx context.Context, name string) error { _, err := s.client.Object.Delete(ctx, s.key(name)) return err } -func (s *tencentStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) { +func (s *tencentStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) { return composeFiles(ctx, s, names, destination) } func (s *tencentStorage) DeletePrefix(ctx context.Context, prefix string) error { @@ -98,7 +99,7 @@ func (s *tencentStorage) DeletePrefix(ctx context.Context, prefix string) error cursor = next } } -func (s *tencentStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) { +func (s *tencentStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) { if limit <= 0 { limit = 100 } @@ -106,7 +107,7 @@ func (s *tencentStorage) List(ctx context.Context, prefix, cursor string, limit if err != nil { return nil, "", false, err } - items := make([]*biz.StoredFile, 0, len(result.Contents)) + items := make([]*system.StoredFile, 0, len(result.Contents)) for _, object := range result.Contents { item := s.file(object.Key, object.Size) for _, layout := range []string{time.RFC3339, "2006-01-02T15:04:05.000Z"} { diff --git a/internal/modules/payment/definition.go b/internal/modules/payment/definition.go new file mode 100644 index 0000000..89528e0 --- /dev/null +++ b/internal/modules/payment/definition.go @@ -0,0 +1,34 @@ +// Package payment contains the payment module contribution to the application +// catalog. +package payment + +import ( + datapayment "kra/internal/data/payment" + "kra/pkg/module" +) + +// Definition declares the payment-owned schema and administration surface. +func Definition() module.Definition { + return module.Definition{ + Name: "payment", + Migrations: datapayment.Migrations(), + Surface: module.Surface{ + Menus: []module.Menu{ + {Name: "paymentOrders", Path: "paymentOrders", ParentName: "extensions", Component: "view/systemTools/payment/orders.vue", Title: "支付订单", Icon: "wallet", Sort: 6}, + {Name: "paymentConfig", Path: "paymentConfig", ParentName: "extensions", Component: "view/systemTools/payment/config.vue", Title: "支付配置", Icon: "credit-card", Sort: 7}, + }, + APIs: []module.API{ + {Path: "/payment/orders", Method: "GET", Group: "支付", Description: "分页查询支付订单"}, + {Path: "/payment/order", Method: "POST", Group: "支付", Description: "查询支付订单"}, + {Path: "/payment/orders/:provider/:tradeNo", Method: "GET", Group: "支付", Description: "按路径查询支付订单"}, + {Path: "/payment/create", Method: "POST", Group: "支付", Description: "创建支付订单"}, + {Path: "/payment/query", Method: "POST", Group: "支付", Description: "同步支付订单状态"}, + {Path: "/payment/refund", Method: "POST", Group: "支付", Description: "申请支付订单退款"}, + {Path: "/payment/orders/:provider/:tradeNo/refund", Method: "POST", Group: "支付", Description: "按路径申请支付订单退款"}, + {Path: "/payment/fulfill", Method: "POST", Group: "支付", Description: "重试支付订单发货"}, + {Path: "/payment/orders/:provider/:tradeNo/fulfill", Method: "POST", Group: "支付", Description: "按路径重试支付订单发货"}, + {Path: "/payment/providers/:provider/test", Method: "POST", Group: "支付", Description: "测试支付渠道"}, + }, + }, + } +} diff --git a/internal/modules/payment/definition_test.go b/internal/modules/payment/definition_test.go new file mode 100644 index 0000000..1075f2d --- /dev/null +++ b/internal/modules/payment/definition_test.go @@ -0,0 +1,16 @@ +package payment + +import "testing" + +func TestDefinitionOwnsPaymentContributions(t *testing.T) { + definition := Definition() + if definition.Name != "payment" { + t.Fatalf("name = %q, want payment", definition.Name) + } + if len(definition.Migrations) != 2 { + t.Fatalf("migrations = %d, want 2", len(definition.Migrations)) + } + if len(definition.Surface.Menus) != 2 || len(definition.Surface.APIs) != 10 { + t.Fatalf("surface = %d menus/%d APIs, want 2/10", len(definition.Surface.Menus), len(definition.Surface.APIs)) + } +} diff --git a/internal/app/definition.go b/internal/modules/system/definition.go similarity index 83% rename from internal/app/definition.go rename to internal/modules/system/definition.go index 59ce90b..f24dd08 100644 --- a/internal/app/definition.go +++ b/internal/modules/system/definition.go @@ -1,7 +1,8 @@ -package app +// Package system contains the built-in system module contribution to the +// application catalog. +package system import ( - datapayment "kra/internal/data/payment" datasystem "kra/internal/data/repository" "kra/pkg/module" ) @@ -9,7 +10,6 @@ import ( // Definition describes the built-in system contribution to the application // catalog. Other business modules can expose the same shape independently. func Definition() module.Definition { - surface := datapayment.AdminSurface() communication := module.Surface{ Menus: []module.Menu{{Name: "integrationConfig", Path: "integrationConfig", ParentName: "extensions", Component: "view/systemTools/integration/config.vue", Title: "通信集成", Icon: "connection", Sort: 8}}, APIs: []module.API{ @@ -20,12 +20,10 @@ func Definition() module.Definition { {Path: "/integration/configs/:kind/:provider", Method: "DELETE", Group: "集成配置", Description: "删除集成配置"}, }, } - surface.Menus = append(surface.Menus, communication.Menus...) - surface.APIs = append(surface.APIs, communication.APIs...) return module.Definition{ Name: "system", - Migrations: append(datasystem.Migrations(), datapayment.Migrations()...), - Surface: surface, + Migrations: datasystem.Migrations(), + Surface: communication, TimedTasks: []module.TimedTask{ {Name: "ClearDB", Description: "定时清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Spec: "@daily", MethodName: "ClearDB", Enabled: true}, {Name: "CleanStaleUploads", Description: "定时清理过期大文件上传会话", Spec: "@hourly", MethodName: "CleanStaleUploads", Enabled: true}, diff --git a/internal/app/definition_test.go b/internal/modules/system/definition_test.go similarity index 97% rename from internal/app/definition_test.go rename to internal/modules/system/definition_test.go index 6c8601d..52efa38 100644 --- a/internal/app/definition_test.go +++ b/internal/modules/system/definition_test.go @@ -1,4 +1,4 @@ -package app +package system import "testing" diff --git a/internal/server/handler/audit.go b/internal/server/handler/audit.go index 1090fcb..8d7ab44 100644 --- a/internal/server/handler/audit.go +++ b/internal/server/handler/audit.go @@ -2,11 +2,11 @@ package handler import ( "errors" + "kra/internal/biz/system" "log/slog" "strconv" "time" - "kra/internal/biz" "kra/internal/service" "kra/internal/service/dto" @@ -215,17 +215,17 @@ func failLogViewer(c *gin.Context, err error, logger *slog.Logger) { } message := "读取日志失败" switch { - case errors.Is(err, biz.ErrInvalidLogMonth): + case errors.Is(err, system.ErrInvalidLogMonth): message = "日志月份格式不正确" - case errors.Is(err, biz.ErrInvalidLogDate): + case errors.Is(err, system.ErrInvalidLogDate): message = "日志日期格式不正确" - case errors.Is(err, biz.ErrInvalidLogPath): + case errors.Is(err, system.ErrInvalidLogPath): message = "日志文件路径不合法" - case errors.Is(err, biz.ErrLogFileNotFound): + case errors.Is(err, system.ErrLogFileNotFound): message = "日志文件不存在" - case errors.Is(err, biz.ErrLogFileUnreadable): + case errors.Is(err, system.ErrLogFileUnreadable): message = "日志文件不可读取" - case errors.Is(err, biz.ErrLogRootUnavailable): + case errors.Is(err, system.ErrLogRootUnavailable): message = "日志目录不可读取" } Fail(c, message) diff --git a/internal/server/handler/http.go b/internal/server/handler/http.go index 8f4ed69..1dd4d98 100644 --- a/internal/server/handler/http.go +++ b/internal/server/handler/http.go @@ -1,10 +1,11 @@ package handler import ( - "github.com/gin-gonic/gin" - "kra/internal/biz" + "kra/internal/biz/system" "kra/internal/server/httpx" "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" ) const ( @@ -22,4 +23,4 @@ func OKWithData(c *gin.Context, data any) { httpx.OKWithDat func Fail(c *gin.Context, message string) { httpx.Fail(c, message) } func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) } func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) } -func Claims(c *gin.Context) *biz.AuthClaims { return middleware.Claims(c) } +func Claims(c *gin.Context) *system.AuthClaims { return middleware.Claims(c) } diff --git a/internal/server/handler/public.go b/internal/server/handler/public.go index 9720326..8076a03 100644 --- a/internal/server/handler/public.go +++ b/internal/server/handler/public.go @@ -3,11 +3,11 @@ package handler import ( "context" "errors" + "kra/internal/biz/system" "strconv" "strings" "time" - "kra/internal/biz" "kra/internal/service" "kra/internal/service/dto" @@ -75,23 +75,23 @@ func (h *Public) Login(c *gin.Context) { result, err := h.auth.Login(c.Request.Context(), &req, c.ClientIP(), c.Request.UserAgent()) if err != nil { switch { - case errors.Is(err, biz.ErrAccountLocked): - var locked *biz.AccountLockedError + case errors.Is(err, system.ErrAccountLocked): + var locked *system.AccountLockedError errors.As(err, &locked) minutes := 0 if locked != nil { minutes = locked.Minutes } Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试") - case errors.Is(err, biz.ErrCaptchaInvalid): + case errors.Is(err, system.ErrCaptchaInvalid): Fail(c, "验证码错误") - case errors.Is(err, biz.ErrUserDisabled): + case errors.Is(err, system.ErrUserDisabled): Fail(c, "用户被禁止登录") - case errors.Is(err, biz.ErrTokenIssue): + case errors.Is(err, system.ErrTokenIssue): Fail(c, "获取token失败") - case errors.Is(err, biz.ErrLoginState): + case errors.Is(err, system.ErrLoginState): Fail(c, "设置登录状态失败") - case errors.Is(err, biz.ErrTokenRevoke): + case errors.Is(err, system.ErrTokenRevoke): Fail(c, "jwt作废失败") default: Fail(c, "用户名不存在或者密码错误") diff --git a/internal/server/handler/version.go b/internal/server/handler/version.go index 8729d05..bca271f 100644 --- a/internal/server/handler/version.go +++ b/internal/server/handler/version.go @@ -3,11 +3,11 @@ package handler import ( "errors" "fmt" + "kra/internal/biz/system" "net/http" "strconv" "time" - "kra/internal/biz" "kra/internal/service" "kra/internal/service/dto" @@ -18,8 +18,8 @@ type Version struct{ service *service.VersionService } func NewVersion(service *service.VersionService) *Version { return &Version{service: service} } -func versionStage(err error) (biz.VersionStage, bool) { - var stageErr *biz.VersionStageError +func versionStage(err error) (system.VersionStage, bool) { + var stageErr *system.VersionStageError if !errors.As(err, &stageErr) { return "", false } @@ -89,15 +89,15 @@ func (h *Version) Export(c *gin.Context) { } if err := h.service.Export(c.Request.Context(), req.VersionName, req.VersionCode, req.Description, req.MenuIDs, req.APIIDs, req.DictIDs); err != nil { switch stage, _ := versionStage(err); stage { - case biz.VersionStageMenus: + case system.VersionStageMenus: Fail(c, "获取菜单数据失败:"+err.Error()) - case biz.VersionStageAPIs: + case system.VersionStageAPIs: Fail(c, "获取API数据失败:"+err.Error()) - case biz.VersionStageDictionaries: + case system.VersionStageDictionaries: Fail(c, "获取字典数据失败:"+err.Error()) - case biz.VersionStageJSON: + case system.VersionStageJSON: Fail(c, "JSON序列化失败:"+err.Error()) - case biz.VersionStageSave: + case system.VersionStageSave: Fail(c, "保存版本记录失败:"+err.Error()) default: Fail(c, "创建发版失败:"+err.Error()) @@ -131,16 +131,16 @@ func (h *Version) Import(c *gin.Context) { return } if err := h.service.ImportRequest(c.Request.Context(), &req); err != nil { - if errors.Is(err, biz.ErrInvalidVersion) { + if errors.Is(err, system.ErrInvalidVersion) { Fail(c, "版本信息格式错误") return } switch stage, _ := versionStage(err); stage { - case biz.VersionStageMenus: + case system.VersionStageMenus: Fail(c, "导入菜单失败: "+err.Error()) - case biz.VersionStageAPIs: + case system.VersionStageAPIs: Fail(c, "导入API失败: "+err.Error()) - case biz.VersionStageDictionaries: + case system.VersionStageDictionaries: Fail(c, "导入字典失败: "+err.Error()) default: Fail(c, "导入失败:"+err.Error()) diff --git a/internal/server/middleware/access.go b/internal/server/middleware/access.go index 5d54ec7..add21a0 100644 --- a/internal/server/middleware/access.go +++ b/internal/server/middleware/access.go @@ -2,8 +2,8 @@ package middleware import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/conf" "kra/internal/service" @@ -39,13 +39,13 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu c.Abort() return } - if scope, ok := biz.DataScopeFromContext(requestContext); ok { + if scope, ok := system.DataScopeFromContext(requestContext); ok { requestID, _ := c.Get("request_id") scope.RequestID, _ = requestID.(string) scope.Method, scope.Path = c.Request.Method, path - requestContext = biz.NewDataScopeContext(requestContext, scope) + requestContext = system.NewDataScopeContext(requestContext, scope) } - requestContext = biz.NewActorContext(requestContext, biz.Actor{UserID: claims.ID, AuthorityID: claims.AuthorityID}) + requestContext = system.NewActorContext(requestContext, system.Actor{UserID: claims.ID, AuthorityID: claims.AuthorityID}) c.Request = c.Request.WithContext(requestContext) c.Next() } diff --git a/internal/server/middleware/access_log.go b/internal/server/middleware/access_log.go index b32b733..04e0c39 100644 --- a/internal/server/middleware/access_log.go +++ b/internal/server/middleware/access_log.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "io" + "kra/internal/biz/system" "log/slog" "mime" "net/http" @@ -14,7 +15,6 @@ import ( "strings" "time" - "kra/internal/biz" "kra/internal/conf" "github.com/gin-gonic/gin" @@ -33,7 +33,7 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H config := runtime.Admin() bodyLimit := defaultRequestBodyLimit if mediaUpload { - bodyLimit = biz.DefaultMaxMediaFileSize + (1 << 20) + bodyLimit = system.DefaultMaxMediaFileSize + (1 << 20) if config != nil && config.Media != nil && config.Media.MaxFileSize > 0 { bodyLimit = config.Media.MaxFileSize + (1 << 20) } diff --git a/internal/server/middleware/access_test.go b/internal/server/middleware/access_test.go index 68c94a6..f287761 100644 --- a/internal/server/middleware/access_test.go +++ b/internal/server/middleware/access_test.go @@ -3,12 +3,12 @@ package middleware import ( "context" "errors" + "kra/internal/biz/system" "net/http" "net/http/httptest" "strings" "testing" - "kra/internal/biz" "kra/internal/conf" "github.com/gin-gonic/gin" @@ -26,14 +26,14 @@ func (s *accessControllerStub) ContextWithDataScope(ctx context.Context, authori if s.scopeErr != nil { return ctx, s.scopeErr } - return biz.NewDataScopeContext(ctx, biz.DataScope{UserID: userID, AuthorityID: authorityID, Scope: 1, All: true}), nil + return system.NewDataScopeContext(ctx, system.DataScope{UserID: userID, AuthorityID: authorityID, Scope: 1, All: true}), nil } func TestAccessControlFailsClosedWhenDataScopeResolutionFails(t *testing.T) { gin.SetMode(gin.TestMode) engine := gin.New() engine.Use(func(c *gin.Context) { - c.Set(claimsKey, &biz.AuthClaims{ID: 7, AuthorityID: 888}) + c.Set(claimsKey, &system.AuthClaims{ID: 7, AuthorityID: 888}) c.Next() }) engine.Use(AccessControl(conf.NewRuntime(nil, &conf.AdminBackend{}), &accessControllerStub{scopeErr: errors.New("database unavailable")})) diff --git a/internal/server/middleware/auth.go b/internal/server/middleware/auth.go index 1084b38..cfd5c2c 100644 --- a/internal/server/middleware/auth.go +++ b/internal/server/middleware/auth.go @@ -3,12 +3,11 @@ package middleware import ( "context" "errors" + "kra/internal/biz/system" "net/http" "strconv" "strings" - "kra/internal/biz" - "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" ) @@ -18,7 +17,7 @@ const claimsKey = "admin_claims" var refreshTokens singleflight.Group type TokenAuthenticator interface { - AuthenticateToken(context.Context, string) (*biz.TokenAuthentication, error) + AuthenticateToken(context.Context, string) (*system.TokenAuthentication, error) } func Auth(auth TokenAuthenticator) gin.HandlerFunc { @@ -54,7 +53,7 @@ func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool) NoAuth(c, tokenErrorMessage(err)) return false } - authentication, ok := value.(*biz.TokenAuthentication) + authentication, ok := value.(*system.TokenAuthentication) if !ok || authentication == nil || authentication.Claims == nil { SetTokenCookie(c, "", -1) NoAuth(c, "无法处理此token") @@ -95,23 +94,23 @@ func requestToken(c *gin.Context, allowQueryToken bool) string { func tokenErrorMessage(err error) string { message := "无法处理此token" switch { - case errors.Is(err, biz.ErrTokenExpired): + case errors.Is(err, system.ErrTokenExpired): message = "登录已过期,请重新登录" - case errors.Is(err, biz.ErrTokenMalformed): + case errors.Is(err, system.ErrTokenMalformed): message = "这不是一个token" - case errors.Is(err, biz.ErrTokenSignatureInvalid): + case errors.Is(err, system.ErrTokenSignatureInvalid): message = "无效签名" - case errors.Is(err, biz.ErrTokenNotValidYet): + case errors.Is(err, system.ErrTokenNotValidYet): message = "token尚未激活" - case errors.Is(err, biz.ErrTokenDisabled): + case errors.Is(err, system.ErrTokenDisabled): message = "您的帐户异地登陆或令牌失效" } return message } -func Claims(c *gin.Context) *biz.AuthClaims { +func Claims(c *gin.Context) *system.AuthClaims { value, _ := c.Get(claimsKey) - claims, _ := value.(*biz.AuthClaims) + claims, _ := value.(*system.AuthClaims) return claims } diff --git a/internal/server/middleware/auth_test.go b/internal/server/middleware/auth_test.go index b9a51b1..f7b4a9a 100644 --- a/internal/server/middleware/auth_test.go +++ b/internal/server/middleware/auth_test.go @@ -3,32 +3,31 @@ package middleware import ( "context" "errors" + "kra/internal/biz/system" "net/http" "net/http/httptest" "testing" - "kra/internal/biz" - "github.com/gin-gonic/gin" ) type tokenAuthenticatorStub struct { - claims *biz.AuthClaims + claims *system.AuthClaims err error got string } -func (s *tokenAuthenticatorStub) AuthenticateToken(_ context.Context, token string) (*biz.TokenAuthentication, error) { +func (s *tokenAuthenticatorStub) AuthenticateToken(_ context.Context, token string) (*system.TokenAuthentication, error) { s.got = token if s.err != nil { return nil, s.err } - return &biz.TokenAuthentication{Claims: s.claims}, nil + return &system.TokenAuthentication{Claims: s.claims}, nil } func TestAuthenticateWebSocketAcceptsQueryToken(t *testing.T) { gin.SetMode(gin.TestMode) - stub := &tokenAuthenticatorStub{claims: &biz.AuthClaims{ID: 7}} + stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 7}} recorder := httptest.NewRecorder() context, _ := gin.CreateTestContext(recorder) context.Request = httptest.NewRequest(http.MethodGet, "/ws?token=query-token", nil) @@ -46,7 +45,7 @@ func TestAuthenticateWebSocketAcceptsQueryToken(t *testing.T) { func TestHTTPAuthDoesNotAcceptQueryToken(t *testing.T) { gin.SetMode(gin.TestMode) - stub := &tokenAuthenticatorStub{claims: &biz.AuthClaims{ID: 7}} + stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 7}} engine := gin.New() called := false engine.GET("/protected", Auth(stub), func(c *gin.Context) { called = true }) @@ -60,7 +59,7 @@ func TestHTTPAuthDoesNotAcceptQueryToken(t *testing.T) { func TestAuthenticateWebSocketSupportsBearerHeaderAndReportsInvalidToken(t *testing.T) { gin.SetMode(gin.TestMode) - stub := &tokenAuthenticatorStub{claims: &biz.AuthClaims{ID: 7}} + stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 7}} context, _ := gin.CreateTestContext(httptest.NewRecorder()) context.Request = httptest.NewRequest(http.MethodGet, "/ws", nil) context.Request.Header.Set("Authorization", "Bearer header-token") diff --git a/internal/server/middleware/rate_limit_test.go b/internal/server/middleware/rate_limit_test.go index 8a1afff..451cdfb 100644 --- a/internal/server/middleware/rate_limit_test.go +++ b/internal/server/middleware/rate_limit_test.go @@ -3,13 +3,13 @@ package middleware import ( "context" "errors" + "kra/internal/biz/system" "net/http" "net/http/httptest" "strings" "testing" "time" - "kra/internal/biz" "kra/internal/service" "github.com/gin-gonic/gin" @@ -17,10 +17,10 @@ import ( type rateLimitSecurityRepo struct{} -func (rateLimitSecurityRepo) SecurityConfig(context.Context) (*biz.SecurityConfig, error) { - return &biz.SecurityConfig{LimitEnable: true, LimitWindow: 60, LimitCount: 1}, nil +func (rateLimitSecurityRepo) SecurityConfig(context.Context) (*system.SecurityConfig, error) { + return &system.SecurityConfig{LimitEnable: true, LimitWindow: 60, LimitCount: 1}, nil } -func (rateLimitSecurityRepo) SaveSecurityConfig(context.Context, *biz.SecurityConfig) error { +func (rateLimitSecurityRepo) SaveSecurityConfig(context.Context, *system.SecurityConfig) error { return nil } func (rateLimitSecurityRepo) BackfillPasswordUpdatedAt(context.Context, time.Time) error { @@ -38,7 +38,7 @@ func (c rateLimitCache) Increment(context.Context, string, time.Duration) (int64 func TestSecurityRateLimitMatchesResponseContract(t *testing.T) { gin.SetMode(gin.TestMode) - settings := service.NewSecurityService(biz.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{}, nil, nil)) + settings := service.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{}, nil, nil)) engine := gin.New() engine.Use(SecurityRateLimit(settings)) engine.POST("/base/login", func(c *gin.Context) { c.Status(http.StatusNoContent) }) @@ -56,7 +56,7 @@ func TestSecurityRateLimitMatchesResponseContract(t *testing.T) { func TestSecurityRateLimitFailsClosedWhenCacheIsUnavailable(t *testing.T) { gin.SetMode(gin.TestMode) - settings := service.NewSecurityService(biz.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{err: errors.New("cache unavailable")}, nil, nil)) + settings := service.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{err: errors.New("cache unavailable")}, nil, nil)) engine := gin.New() called := false engine.Use(SecurityRateLimit(settings)) diff --git a/internal/server/websocket_auth_test.go b/internal/server/websocket_auth_test.go index 68ebc53..762a49f 100644 --- a/internal/server/websocket_auth_test.go +++ b/internal/server/websocket_auth_test.go @@ -3,12 +3,12 @@ package server import ( "context" "encoding/json" + "kra/internal/biz/system" "net/http" "net/http/httptest" "strings" "testing" - "kra/internal/biz" "kra/internal/conf" "kra/internal/integration/runtimeconfig" websocketintegration "kra/internal/integration/websocket" @@ -19,13 +19,13 @@ import ( ) type websocketAuthStub struct { - claims *biz.AuthClaims + claims *system.AuthClaims got string } -func (s *websocketAuthStub) AuthenticateToken(_ context.Context, token string) (*biz.TokenAuthentication, error) { +func (s *websocketAuthStub) AuthenticateToken(_ context.Context, token string) (*system.TokenAuthentication, error) { s.got = token - return &biz.TokenAuthentication{Claims: s.claims}, nil + return &system.TokenAuthentication{Claims: s.claims}, nil } var _ middleware.TokenAuthenticator = (*websocketAuthStub)(nil) @@ -43,7 +43,7 @@ func TestWebSocketRouteRequiresAndAcceptsLoginToken(t *testing.T) { } defer cleanup() - auth := &websocketAuthStub{claims: &biz.AuthClaims{ID: 7}} + auth := &websocketAuthStub{claims: &system.AuthClaims{ID: 7}} engine := NewGinEngineWithRuntime(conf.NewRuntime(nil, &conf.AdminBackend{}), nil, auth, nil, nil, nil, "test", nil, ws) unauthorized := httptest.NewRecorder() diff --git a/internal/service/access_control.go b/internal/service/access_control.go index 220aace..c58fe6a 100644 --- a/internal/service/access_control.go +++ b/internal/service/access_control.go @@ -2,13 +2,12 @@ package service import ( "context" - - "kra/internal/biz" + "kra/internal/biz/system" ) -type AccessControlService struct{ uc *biz.AccessControlUsecase } +type AccessControlService struct{ uc *system.AccessControlUsecase } -func NewAccessControlService(uc *biz.AccessControlUsecase) *AccessControlService { +func NewAccessControlService(uc *system.AccessControlUsecase) *AccessControlService { return &AccessControlService{uc: uc} } @@ -16,7 +15,7 @@ func (s *AccessControlService) Authorize(ctx context.Context, authorityID uint, return s.uc.Authorize(ctx, authorityID, path, method) } -func (s *AccessControlService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) { +func (s *AccessControlService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (system.DataScope, error) { return s.uc.ResolveDataScope(ctx, authorityID, userID) } @@ -25,5 +24,5 @@ func (s *AccessControlService) ContextWithDataScope(ctx context.Context, authori if err != nil { return ctx, err } - return biz.NewDataScopeContext(ctx, scope), nil + return system.NewDataScopeContext(ctx, scope), nil } diff --git a/internal/service/announcement.go b/internal/service/announcement.go index b85a727..7a09175 100644 --- a/internal/service/announcement.go +++ b/internal/service/announcement.go @@ -3,9 +3,9 @@ package service import ( "context" "encoding/json" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/internal/service/dto" ) @@ -17,18 +17,18 @@ type AnnouncementInput struct { Attachments json.RawMessage } -type AnnouncementService struct{ uc *biz.AnnouncementUsecase } +type AnnouncementService struct{ uc *system.AnnouncementUsecase } -func NewAnnouncementService(uc *biz.AnnouncementUsecase) *AnnouncementService { +func NewAnnouncementService(uc *system.AnnouncementUsecase) *AnnouncementService { return &AnnouncementService{uc: uc} } -func announcementDTO(item *biz.Announcement) *dto.AnnouncementResponse { +func announcementDTO(item *system.Announcement) *dto.AnnouncementResponse { return &dto.AnnouncementResponse{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, DeletedAt: nil, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} } -func announcementDO(in AnnouncementInput) *biz.Announcement { - return &biz.Announcement{ID: in.ID, Title: in.Title, Content: in.Content, UserID: in.UserID, Attachments: in.Attachments} +func announcementDO(in AnnouncementInput) *system.Announcement { + return &system.Announcement{ID: in.ID, Title: in.Title, Content: in.Content, UserID: in.UserID, Attachments: in.Attachments} } func (s *AnnouncementService) Create(ctx context.Context, in AnnouncementInput) error { @@ -56,7 +56,7 @@ func (s *AnnouncementService) Find(ctx context.Context, id string) (*dto.Announc } func (s *AnnouncementService) List(ctx context.Context, page, pageSize int, start, end *time.Time) ([]*dto.AnnouncementResponse, int64, error) { - items, total, err := s.uc.List(ctx, biz.AnnouncementFilter{Page: page, PageSize: pageSize, StartCreatedAt: start, EndCreatedAt: end}) + items, total, err := s.uc.List(ctx, system.AnnouncementFilter{Page: page, PageSize: pageSize, StartCreatedAt: start, EndCreatedAt: end}) if err != nil { return nil, 0, err } diff --git a/internal/service/api.go b/internal/service/api.go index 820be99..f024c1f 100644 --- a/internal/service/api.go +++ b/internal/service/api.go @@ -2,18 +2,18 @@ package service import ( "context" + "kra/internal/biz/system" "strings" - "kra/internal/biz" "kra/internal/service/dto" ) type APIService struct { - uc *biz.APIUsecase - settings biz.RuntimeSettings + uc *system.APIUsecase + settings system.RuntimeSettings } -func NewAPIService(uc *biz.APIUsecase, settings biz.RuntimeSettings) *APIService { +func NewAPIService(uc *system.APIUsecase, settings system.RuntimeSettings) *APIService { return &APIService{uc: uc, settings: settings} } @@ -24,15 +24,15 @@ func (s *APIService) NormalizeRoutePath(path string) string { return NormalizeRoutePath(path, s.settings.RouterPrefix()) } -func (s *APIService) apiDomain(value *dto.APIRequest) *biz.API { - return &biz.API{ID: value.ID, Path: s.NormalizeRoutePath(value.Path), Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} +func (s *APIService) apiDomain(value *dto.APIRequest) *system.API { + return &system.API{ID: value.ID, Path: s.NormalizeRoutePath(value.Path), Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} } -func apiResponse(value *biz.API) *dto.APIResponse { +func apiResponse(value *system.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 { +func apiResponses(items []*system.API) []*dto.APIResponse { out := make([]*dto.APIResponse, 0, len(items)) for _, item := range items { out = append(out, apiResponse(item)) @@ -41,7 +41,7 @@ func apiResponses(items []*biz.API) []*dto.APIResponse { } func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) { - items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method, OrderKey: req.OrderKey, Desc: req.Desc}) + items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &system.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method, OrderKey: req.OrderKey, Desc: req.Desc}) if err != nil { return nil, 0, err } @@ -49,7 +49,7 @@ func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*d } func (s *APIService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) { - items, _, err := s.uc.ListAPIs(ctx, 0, 0, &biz.API{StrictAll: strict}) + items, _, err := s.uc.ListAPIs(ctx, 0, 0, &system.API{StrictAll: strict}) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func (s *APIService) Groups(ctx context.Context) ([]string, map[string]string, e // The compatible group endpoint uses an unqualified Find, whose natural order is // the primary-key ascending order. GetAllApis intentionally uses id DESC, // so do not reuse it here or the group/dropdown order changes. - items, _, err := s.uc.ListAPIs(ctx, 0, 0, &biz.API{OrderKey: "id"}) + items, _, err := s.uc.ListAPIs(ctx, 0, 0, &system.API{OrderKey: "id"}) if err != nil { return nil, nil, err } @@ -98,8 +98,8 @@ func (s *APIService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResp return apiResponse(value), nil } func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPISyncRequest) error { - added := make([]*biz.API, 0, len(req.NewAPIs)) - deleted := make([]*biz.API, 0, len(req.DeleteAPIs)) + added := make([]*system.API, 0, len(req.NewAPIs)) + deleted := make([]*system.API, 0, len(req.DeleteAPIs)) for i := range req.NewAPIs { added = append(added, s.apiDomain(&req.NewAPIs[i])) } @@ -109,9 +109,9 @@ func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPIS return s.uc.ApplyAPISync(ctx, added, deleted) } func (s *APIService) SetPolicyPathsRequest(ctx context.Context, req *dto.SetPolicyPathsRequest) error { - paths := make([]*biz.API, 0, len(req.Infos)) + paths := make([]*system.API, 0, len(req.Infos)) for _, value := range req.Infos { - paths = append(paths, &biz.API{Path: value.Path, Method: value.Method}) + paths = append(paths, &system.API{Path: value.Path, Method: value.Method}) } return s.uc.SetPolicyPaths(ctx, req.AuthorityID, paths) } @@ -130,7 +130,7 @@ func (s *APIService) PolicyPathResponses(ctx context.Context, authorityID uint) return out, nil } func (s *APIService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) { - items := make([]*biz.API, 0, len(routes)) + items := make([]*system.API, 0, len(routes)) for i := range routes { routes[i].Path = s.NormalizeRoutePath(routes[i].Path) // The compatible sync endpoint compares Gin's route table directly, so newly @@ -156,7 +156,7 @@ func (s *APIService) SetAPIRoles(ctx context.Context, path, method string, ids [ func (s *APIService) FreshCasbin(ctx context.Context) error { return s.uc.FreshCasbin(ctx) } -func (s *APIService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) { +func (s *APIService) SyncAPIs(ctx context.Context, routes []*system.API) (*dto.APISyncResponse, error) { diff, err := s.uc.SyncAPIs(ctx, routes) if err != nil { return nil, err diff --git a/internal/service/api_token.go b/internal/service/api_token.go index 79d8b7d..25fa2d9 100644 --- a/internal/service/api_token.go +++ b/internal/service/api_token.go @@ -2,22 +2,22 @@ package service import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/internal/service/dto" ) type TokenService struct { - uc *biz.TokenUsecase - issuer biz.TokenIssuer + uc *system.TokenUsecase + issuer system.TokenIssuer } -func NewTokenService(uc *biz.TokenUsecase, issuer biz.TokenIssuer) *TokenService { +func NewTokenService(uc *system.TokenUsecase, issuer system.TokenIssuer) *TokenService { return &TokenService{uc: uc, issuer: issuer} } -func tokenDTO(v *biz.APIToken) *dto.APITokenResponse { +func tokenDTO(v *system.APIToken) *dto.APITokenResponse { var user *dto.UserResponse if v.User != nil { user = convertUser(v.User) @@ -34,7 +34,7 @@ func (s *TokenService) CreateAPIToken(ctx context.Context, userID, authorityID u if err != nil { return "", err } - record := &biz.APIToken{UserID: userID, AuthorityID: authorityID, Token: issued.Value, Status: true, ExpiresAt: time.Now().Add(duration), Remark: remark} + record := &system.APIToken{UserID: userID, AuthorityID: authorityID, Token: issued.Value, Status: true, ExpiresAt: time.Now().Add(duration), Remark: remark} if err = s.uc.CreateAPIToken(ctx, record); err != nil { return "", err } diff --git a/internal/service/audit.go b/internal/service/audit.go index 01fa45b..17bc6eb 100644 --- a/internal/service/audit.go +++ b/internal/service/audit.go @@ -2,36 +2,36 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type AuditService struct{ uc *biz.AuditUsecase } +type AuditService struct{ uc *system.AuditUsecase } -func NewAuditService(uc *biz.AuditUsecase) *AuditService { return &AuditService{uc: uc} } +func NewAuditService(uc *system.AuditUsecase) *AuditService { return &AuditService{uc: uc} } -type AuditRecorder struct{ uc *biz.AuditRecorderUsecase } +type AuditRecorder struct{ uc *system.AuditRecorderUsecase } -func NewAuditRecorder(uc *biz.AuditRecorderUsecase) *AuditRecorder { return &AuditRecorder{uc: uc} } +func NewAuditRecorder(uc *system.AuditRecorderUsecase) *AuditRecorder { return &AuditRecorder{uc: uc} } -func (s *AuditRecorder) RecordOperation(ctx context.Context, v *biz.OperationRecord) error { +func (s *AuditRecorder) RecordOperation(ctx context.Context, v *system.OperationRecord) error { return s.uc.RecordOperation(ctx, v) } func (s *AuditRecorder) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error { - return s.RecordOperation(ctx, &biz.OperationRecord{IP: value.IP, Method: value.Method, Path: value.Path, Status: value.Status, LatencyMS: value.LatencyMS, Agent: value.Agent, ErrorMessage: value.ErrorMessage, Body: value.Body, Response: value.Response, UserID: value.UserID, RequestID: value.RequestID, TraceID: value.TraceID, DeviceID: value.DeviceID}) + return s.RecordOperation(ctx, &system.OperationRecord{IP: value.IP, Method: value.Method, Path: value.Path, Status: value.Status, LatencyMS: value.LatencyMS, Agent: value.Agent, ErrorMessage: value.ErrorMessage, Body: value.Body, Response: value.Response, UserID: value.UserID, RequestID: value.RequestID, TraceID: value.TraceID, DeviceID: value.DeviceID}) } func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]*dto.OperationRecordResponse, int64, error) { - return s.Operations(ctx, page, size, &biz.OperationRecord{Path: path, Method: method, Status: status}) + return s.Operations(ctx, page, size, &system.OperationRecord{Path: path, Method: method, Status: status}) } -func opDTO(v *biz.OperationRecord) *dto.OperationRecordResponse { +func opDTO(v *system.OperationRecord) *dto.OperationRecordResponse { var user any = map[string]any{} if v.User != nil { user = convertUser(v.User) } return &dto.OperationRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID, User: user} } -func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*dto.OperationRecordResponse, int64, error) { +func (s *AuditService) Operations(ctx context.Context, page, size int, q *system.OperationRecord) ([]*dto.OperationRecordResponse, int64, error) { items, total, err := s.uc.ListOperations(ctx, page, size, q) if err != nil { return nil, 0, err @@ -53,23 +53,23 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []int) error { return s.uc.DeleteOperations(ctx, ids) } -func (s *AuditRecorder) RecordLogin(ctx context.Context, v *biz.LoginLog) error { +func (s *AuditRecorder) RecordLogin(ctx context.Context, v *system.LoginLog) error { return s.uc.RecordLogin(ctx, v) } func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error { - return s.RecordLogin(ctx, &biz.LoginLog{Username: value.Username, IP: value.IP, Status: value.Status, ErrorMessage: value.ErrorMessage, Agent: value.Agent, UserID: value.UserID}) + return s.RecordLogin(ctx, &system.LoginLog{Username: value.Username, IP: value.IP, Status: value.Status, ErrorMessage: value.ErrorMessage, Agent: value.Agent, UserID: value.UserID}) } func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]*dto.LoginLogResponse, int64, error) { - return s.Logins(ctx, page, size, &biz.LoginLog{Username: username, Status: status, FilterByStatus: status}) + return s.Logins(ctx, page, size, &system.LoginLog{Username: username, Status: status, FilterByStatus: status}) } -func loginDTO(v *biz.LoginLog) *dto.LoginLogResponse { +func loginDTO(v *system.LoginLog) *dto.LoginLogResponse { var user any = map[string]any{} if v.User != nil { user = convertUser(v.User) } return &dto.LoginLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID, User: user} } -func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*dto.LoginLogResponse, int64, error) { +func (s *AuditService) Logins(ctx context.Context, page, size int, q *system.LoginLog) ([]*dto.LoginLogResponse, int64, error) { items, total, err := s.uc.ListLogins(ctx, page, size, q) if err != nil { return nil, 0, err @@ -91,19 +91,19 @@ func (s *AuditService) DeleteLogins(ctx context.Context, ids []int) error { return s.uc.DeleteLogins(ctx, ids) } -func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error { +func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *system.DataAccessLog) error { return s.uc.RecordDataAccess(ctx, v) } func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error { - return s.RecordDataAccess(ctx, &biz.DataAccessLog{EventType: value.EventType, Operation: value.Operation, UserID: value.UserID, AuthorityID: value.AuthorityID, RequestID: value.RequestID, Method: value.Method, Path: value.Path, Detail: value.Detail}) + return s.RecordDataAccess(ctx, &system.DataAccessLog{EventType: value.EventType, Operation: value.Operation, UserID: value.UserID, AuthorityID: value.AuthorityID, RequestID: value.RequestID, Method: value.Method, Path: value.Path, Detail: value.Detail}) } func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]*dto.DataAccessLogResponse, int64, error) { - return s.DataAccess(ctx, req.Page, req.PageSize, &biz.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable}) + return s.DataAccess(ctx, req.Page, req.PageSize, &system.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable}) } -func dataAccessDTO(v *biz.DataAccessLog) *dto.DataAccessLogResponse { +func dataAccessDTO(v *system.DataAccessLog) *dto.DataAccessLogResponse { return &dto.DataAccessLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail} } -func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*dto.DataAccessLogResponse, int64, error) { +func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *system.DataAccessLog) ([]*dto.DataAccessLogResponse, int64, error) { items, total, err := s.uc.ListDataAccess(ctx, page, size, q) if err != nil { return nil, 0, err diff --git a/internal/service/audit_error.go b/internal/service/audit_error.go index 01a86a4..26d87ef 100644 --- a/internal/service/audit_error.go +++ b/internal/service/audit_error.go @@ -2,29 +2,29 @@ package service import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/internal/service/dto" ) -func errorDTO(v *biz.ErrorRecord) *dto.ErrorRecordResponse { +func errorDTO(v *system.ErrorRecord) *dto.ErrorRecordResponse { return &dto.ErrorRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status} } -func (s *AuditRecorder) CreateError(ctx context.Context, v *biz.ErrorRecord) error { +func (s *AuditRecorder) CreateError(ctx context.Context, v *system.ErrorRecord) error { return s.uc.CreateError(ctx, v) } -func recordedErrorDomain(value *dto.ErrorRecordRequest) *biz.ErrorRecord { +func recordedErrorDomain(value *dto.ErrorRecordRequest) *system.ErrorRecord { form, info := value.Form, value.Info - result := &biz.ErrorRecord{Form: &form, Info: &info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Status: value.Status} + result := &system.ErrorRecord{Form: &form, Info: &info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Status: value.Status} if value.Solution != "" { solution := value.Solution result.Solution = &solution } return result } -func mutatedErrorDomain(value *dto.ErrorRecordMutationRequest) *biz.ErrorRecord { - return &biz.ErrorRecord{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Form: value.Form, Info: value.Info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Solution: value.Solution, Status: value.Status} +func mutatedErrorDomain(value *dto.ErrorRecordMutationRequest) *system.ErrorRecord { + return &system.ErrorRecord{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Form: value.Form, Info: value.Info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Solution: value.Solution, Status: value.Status} } func (s *AuditRecorder) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error { return s.CreateError(ctx, recordedErrorDomain(req)) @@ -36,7 +36,7 @@ func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRec return s.UpdateError(ctx, mutatedErrorDomain(req)) } func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, info string, createdAtRange []time.Time) ([]*dto.ErrorRecordResponse, int64, error) { - query := &biz.ErrorRecord{CreatedAtRange: createdAtRange} + query := &system.ErrorRecord{CreatedAtRange: createdAtRange} if form != "" { query.Form = &form } @@ -45,7 +45,7 @@ func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, i } return s.Errors(ctx, page, size, query) } -func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) error { +func (s *AuditService) UpdateError(ctx context.Context, v *system.ErrorRecord) error { return s.uc.UpdateError(ctx, v) } func (s *AuditService) DeleteErrors(ctx context.Context, ids []uint) error { @@ -58,7 +58,7 @@ func (s *AuditService) Error(ctx context.Context, id uint) (*dto.ErrorRecordResp } return errorDTO(v), nil } -func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*dto.ErrorRecordResponse, int64, error) { +func (s *AuditService) Errors(ctx context.Context, page, size int, q *system.ErrorRecord) ([]*dto.ErrorRecordResponse, int64, error) { items, total, err := s.uc.ListErrors(ctx, page, size, q) if err != nil { return nil, 0, err diff --git a/internal/service/audit_log_file.go b/internal/service/audit_log_file.go index be58959..5f58e6c 100644 --- a/internal/service/audit_log_file.go +++ b/internal/service/audit_log_file.go @@ -2,14 +2,14 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type LogViewerService struct{ uc *biz.LogViewerUsecase } +type LogViewerService struct{ uc *system.LogViewerUsecase } -func NewLogViewerService(uc *biz.LogViewerUsecase) *LogViewerService { +func NewLogViewerService(uc *system.LogViewerUsecase) *LogViewerService { return &LogViewerService{uc: uc} } diff --git a/internal/service/authentication.go b/internal/service/authentication.go index 0e2e28a..b971de3 100644 --- a/internal/service/authentication.go +++ b/internal/service/authentication.go @@ -2,32 +2,32 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) type AuthService struct { - uc *biz.AuthenticationUsecase + uc *system.AuthenticationUsecase } -func NewAuthService(uc *biz.AuthenticationUsecase) *AuthService { +func NewAuthService(uc *system.AuthenticationUsecase) *AuthService { return &AuthService{uc: uc} } -func loginResult(value *biz.AuthenticationResult) *dto.LoginResponse { +func loginResult(value *system.AuthenticationResult) *dto.LoginResponse { return &dto.LoginResponse{User: convertUser(value.User), Token: value.Token, ExpiresAt: value.ExpiresAt.UnixMilli(), NeedChangePassword: value.NeedChangePassword} } func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest, ip, agent string) (*dto.LoginResponse, error) { - value, err := s.uc.Login(ctx, &biz.LoginAttempt{Username: req.Username, Password: req.Password, CaptchaID: req.CaptchaID, Captcha: req.Captcha, IP: ip, Agent: agent}) + value, err := s.uc.Login(ctx, &system.LoginAttempt{Username: req.Username, Password: req.Password, CaptchaID: req.CaptchaID, Captcha: req.Captcha, IP: ip, Agent: agent}) if err != nil { return nil, err } return loginResult(value), nil } -func (s *AuthService) SwitchAuthority(ctx context.Context, claims *biz.AuthClaims, authorityID uint) (*dto.LoginResponse, error) { +func (s *AuthService) SwitchAuthority(ctx context.Context, claims *system.AuthClaims, authorityID uint) (*dto.LoginResponse, error) { value, err := s.uc.SwitchAuthority(ctx, claims, authorityID) if err != nil { return nil, err @@ -35,6 +35,6 @@ func (s *AuthService) SwitchAuthority(ctx context.Context, claims *biz.AuthClaim return loginResult(value), nil } -func (s *AuthService) AuthenticateToken(ctx context.Context, token string) (*biz.TokenAuthentication, error) { +func (s *AuthService) AuthenticateToken(ctx context.Context, token string) (*system.TokenAuthentication, error) { return s.uc.AuthenticateToken(ctx, token) } diff --git a/internal/service/authority.go b/internal/service/authority.go index 88b9196..995c15f 100644 --- a/internal/service/authority.go +++ b/internal/service/authority.go @@ -2,18 +2,18 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type AuthorityService struct{ uc *biz.AuthorityUsecase } +type AuthorityService struct{ uc *system.AuthorityUsecase } -func NewAuthorityService(uc *biz.AuthorityUsecase) *AuthorityService { +func NewAuthorityService(uc *system.AuthorityUsecase) *AuthorityService { return &AuthorityService{uc: uc} } -func authorityDTO(value *biz.Authority) *dto.AuthorityResponse { +func authorityDTO(value *system.Authority) *dto.AuthorityResponse { out := convertAuthority(*value) children := make([]*dto.AuthorityResponse, 0, len(value.Children)) for _, child := range value.Children { @@ -23,11 +23,11 @@ func authorityDTO(value *biz.Authority) *dto.AuthorityResponse { return out } -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 authorityDomain(req *dto.AuthorityRequest) *system.Authority { + return &system.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} } -func authorityResponse(value *biz.Authority) *dto.AuthorityResponse { +func authorityResponse(value *system.Authority) *dto.AuthorityResponse { var menus []*dto.MenuResponse if value.Menus != nil { menus = menuResponses(value.Menus) diff --git a/internal/service/department.go b/internal/service/department.go index f5d9402..a1ea3d6 100644 --- a/internal/service/department.go +++ b/internal/service/department.go @@ -2,18 +2,18 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type DepartmentService struct{ uc *biz.DepartmentUsecase } +type DepartmentService struct{ uc *system.DepartmentUsecase } -func NewDepartmentService(uc *biz.DepartmentUsecase) *DepartmentService { +func NewDepartmentService(uc *system.DepartmentUsecase) *DepartmentService { return &DepartmentService{uc: uc} } -func departmentResponse(value *biz.Department) *dto.DepartmentResponse { +func departmentResponse(value *system.Department) *dto.DepartmentResponse { var children []*dto.DepartmentResponse if value.Children != nil { children = make([]*dto.DepartmentResponse, 0, len(value.Children)) @@ -28,8 +28,8 @@ func departmentResponse(value *biz.Department) *dto.DepartmentResponse { return &dto.DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath} } -func departmentDomain(value *dto.DepartmentRequest) *biz.Department { - return &biz.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status} +func departmentDomain(value *dto.DepartmentRequest) *system.Department { + return &system.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status} } func (s *DepartmentService) Departments(ctx context.Context, name string) ([]*dto.DepartmentResponse, error) { diff --git a/internal/service/department_test.go b/internal/service/department_test.go index 1efc873..3a8fd4e 100644 --- a/internal/service/department_test.go +++ b/internal/service/department_test.go @@ -1,16 +1,15 @@ package service import ( + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) func TestDepartmentResponsePreservesNilAndEmptyChildren(t *testing.T) { - if got := departmentResponse(&biz.Department{}).Children; got != nil { + if got := departmentResponse(&system.Department{}).Children; got != nil { t.Fatalf("single/search department children = %#v, want nil", got) } - if got := departmentResponse(&biz.Department{Children: []*biz.Department{}}).Children; got == nil || len(got) != 0 { + if got := departmentResponse(&system.Department{Children: []*system.Department{}}).Children; got == nil || len(got) != 0 { t.Fatalf("tree leaf children = %#v, want empty slice", got) } } diff --git a/internal/service/dictionary.go b/internal/service/dictionary.go index e4cbdc4..6efb5c3 100644 --- a/internal/service/dictionary.go +++ b/internal/service/dictionary.go @@ -2,19 +2,19 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type DictionaryService struct{ uc *biz.DictionaryUsecase } +type DictionaryService struct{ uc *system.DictionaryUsecase } -func NewDictionaryService(uc *biz.DictionaryUsecase) *DictionaryService { +func NewDictionaryService(uc *system.DictionaryUsecase) *DictionaryService { return &DictionaryService{uc: uc} } -func dictionaryDomain(value *dto.DictionaryRequest) *biz.Dictionary { - return &biz.Dictionary{ID: value.ID, Name: value.Name, Type: value.Type, Status: value.Status, Desc: value.Description, ParentID: value.ParentID} +func dictionaryDomain(value *dto.DictionaryRequest) *system.Dictionary { + return &system.Dictionary{ID: value.ID, Name: value.Name, Type: value.Type, Status: value.Status, Desc: value.Description, ParentID: value.ParentID} } func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) (*dto.DictionaryResponse, error) { value := dictionaryDomain(req) @@ -26,7 +26,7 @@ func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *dt func (s *DictionaryService) UpdateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error { return s.UpdateDictionary(ctx, dictionaryDomain(req)) } -func dictionaryDTO(v *biz.Dictionary) *dto.DictionaryResponse { +func dictionaryDTO(v *system.Dictionary) *dto.DictionaryResponse { var children []*dto.DictionaryResponse if v.Children != nil { children = make([]*dto.DictionaryResponse, 0, len(v.Children)) @@ -78,18 +78,18 @@ func (s *DictionaryService) ExportDictionary(ctx context.Context, id uint) (*dto SysDictionaryDetails: details, }, nil } -func (s *DictionaryService) CreateDictionary(ctx context.Context, v *biz.Dictionary) error { +func (s *DictionaryService) CreateDictionary(ctx context.Context, v *system.Dictionary) error { return s.uc.CreateDictionary(ctx, v) } -func (s *DictionaryService) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error { +func (s *DictionaryService) UpdateDictionary(ctx context.Context, v *system.Dictionary) error { return s.uc.UpdateDictionary(ctx, v) } func (s *DictionaryService) DeleteDictionary(ctx context.Context, id uint) error { return s.uc.DeleteDictionary(ctx, id) } -func detailDomain(value *dto.DictionaryDetailRequest) *biz.DictionaryDetail { - return &biz.DictionaryDetail{ID: value.ID, Label: value.Label, Value: value.Value, Extend: value.Extend, Status: value.Status, Sort: value.Sort, DictionaryID: value.DictionaryID, ParentID: value.ParentID, Level: value.Level, Path: value.Path} +func detailDomain(value *dto.DictionaryDetailRequest) *system.DictionaryDetail { + return &system.DictionaryDetail{ID: value.ID, Label: value.Label, Value: value.Value, Extend: value.Extend, Status: value.Status, Sort: value.Sort, DictionaryID: value.DictionaryID, ParentID: value.ParentID, Level: value.Level, Path: value.Path} } func (s *DictionaryService) CreateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error { return s.CreateDictionaryDetail(ctx, detailDomain(req)) @@ -97,7 +97,7 @@ func (s *DictionaryService) CreateDictionaryDetailRequest(ctx context.Context, r func (s *DictionaryService) UpdateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error { return s.UpdateDictionaryDetail(ctx, detailDomain(req)) } -func detailDTO(v *biz.DictionaryDetail) *dto.DictionaryDetailResponse { +func detailDTO(v *system.DictionaryDetail) *dto.DictionaryDetailResponse { var children []*dto.DictionaryDetailResponse if v.Children != nil { children = make([]*dto.DictionaryDetailResponse, 0, len(v.Children)) @@ -108,7 +108,7 @@ func detailDTO(v *biz.DictionaryDetail) *dto.DictionaryDetailResponse { disabled := v.Status != nil && !*v.Status return &dto.DictionaryDetailResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID, Level: v.Level, Path: v.Path, Disabled: disabled, Children: children} } -func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*dto.DictionaryDetailResponse, int64, error) { +func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size int, filter system.DictionaryDetailFilter) ([]*dto.DictionaryDetailResponse, int64, error) { items, total, err := s.uc.ListDictionaryDetails(ctx, page, size, filter) if err != nil { return nil, 0, err @@ -120,7 +120,7 @@ func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size in return out, total, nil } func (s *DictionaryService) DictionaryDetailsRequest(ctx context.Context, page, size int, request dto.DictionaryDetailListRequest) ([]*dto.DictionaryDetailResponse, int64, error) { - return s.DictionaryDetails(ctx, page, size, biz.DictionaryDetailFilter{ + return s.DictionaryDetails(ctx, page, size, system.DictionaryDetailFilter{ DictionaryID: request.DictionaryID, Label: request.Label, Value: request.Value, @@ -162,10 +162,10 @@ func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ str } return out, nil } -func (s *DictionaryService) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { +func (s *DictionaryService) CreateDictionaryDetail(ctx context.Context, v *system.DictionaryDetail) error { return s.uc.CreateDictionaryDetail(ctx, v) } -func (s *DictionaryService) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error { +func (s *DictionaryService) UpdateDictionaryDetail(ctx context.Context, v *system.DictionaryDetail) error { return s.uc.UpdateDictionaryDetail(ctx, v) } func (s *DictionaryService) DeleteDictionaryDetail(ctx context.Context, id uint) error { diff --git a/internal/service/dictionary_import.go b/internal/service/dictionary_import.go index 57ecb7b..674a179 100644 --- a/internal/service/dictionary_import.go +++ b/internal/service/dictionary_import.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "errors" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) @@ -27,7 +27,7 @@ func (s *DictionaryService) ImportDictionaryJSON(ctx context.Context, raw string return errors.New("字典类型不能为空") } dictionary := dictionaryDomain(&dto.DictionaryRequest{Name: payload.Name, Type: payload.Type, Status: payload.Status, Description: payload.Description}) - details := make([]*biz.DictionaryDetail, 0, len(payload.Details)) + details := make([]*system.DictionaryDetail, 0, len(payload.Details)) for i := range payload.Details { details = append(details, detailDomain(&payload.Details[i])) } diff --git a/internal/service/dictionary_test.go b/internal/service/dictionary_test.go index 7a49645..d2263de 100644 --- a/internal/service/dictionary_test.go +++ b/internal/service/dictionary_test.go @@ -2,22 +2,21 @@ package service import ( "context" + "kra/internal/biz/system" "testing" - - "kra/internal/biz" ) type dictionaryExportRepo struct { - biz.DictionaryRepo - value *biz.Dictionary + system.DictionaryRepo + value *system.Dictionary } -func (r dictionaryExportRepo) ExportDictionary(context.Context, uint) (*biz.Dictionary, error) { +func (r dictionaryExportRepo) ExportDictionary(context.Context, uint) (*system.Dictionary, error) { return r.value, nil } func TestExportDictionaryPreservesNilDetails(t *testing.T) { - service := NewDictionaryService(biz.NewDictionaryUsecase(dictionaryExportRepo{value: &biz.Dictionary{Name: "empty", Type: "empty"}})) + service := NewDictionaryService(system.NewDictionaryUsecase(dictionaryExportRepo{value: &system.Dictionary{Name: "empty", Type: "empty"}})) result, err := service.ExportDictionary(context.Background(), 1) if err != nil { t.Fatal(err) diff --git a/internal/service/dto/integration_config.go b/internal/service/dto/integration_config.go index 8c38013..98e05b4 100644 --- a/internal/service/dto/integration_config.go +++ b/internal/service/dto/integration_config.go @@ -2,8 +2,7 @@ package dto import ( "encoding/json" - - "kra/internal/biz" + "kra/internal/biz/system" ) type IntegrationConfigRequest struct { @@ -12,12 +11,12 @@ type IntegrationConfigRequest struct { } type IntegrationConfigResponse struct { - Kind string `json:"kind"` - Provider string `json:"provider"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Enabled bool `json:"enabled"` - Configured bool `json:"configured"` - Config json.RawMessage `json:"config"` - Fields []biz.IntegrationConfigField `json:"fields"` + Kind string `json:"kind"` + Provider string `json:"provider"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled"` + Configured bool `json:"configured"` + Config json.RawMessage `json:"config"` + Fields []system.IntegrationConfigField `json:"fields"` } diff --git a/internal/service/email.go b/internal/service/email.go index 3f8051a..4ea578b 100644 --- a/internal/service/email.go +++ b/internal/service/email.go @@ -2,12 +2,12 @@ package service import ( "context" - "kra/internal/biz" + "kra/internal/biz/system" ) -type EmailService struct{ uc *biz.EmailUsecase } +type EmailService struct{ uc *system.EmailUsecase } -func NewEmailService(uc *biz.EmailUsecase) *EmailService { return &EmailService{uc: uc} } +func NewEmailService(uc *system.EmailUsecase) *EmailService { return &EmailService{uc: uc} } func (s *EmailService) Test(ctx context.Context) error { return s.uc.Test(ctx) } diff --git a/internal/service/export.go b/internal/service/export.go index 04f2a40..e4d9556 100644 --- a/internal/service/export.go +++ b/internal/service/export.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" "errors" + "kra/internal/biz/system" "strings" "time" - "kra/internal/biz" "kra/internal/service/dto" "github.com/google/uuid" @@ -20,8 +20,8 @@ var ( ) type ExportService struct { - uc *biz.ExportUsecase - cache biz.Cache + uc *system.ExportUsecase + cache system.Cache } type ExportToken struct { @@ -36,7 +36,7 @@ type exportTokenCache struct { Blank bool `json:"blank"` } -func NewExportService(uc *biz.ExportUsecase, cache biz.Cache) *ExportService { +func NewExportService(uc *system.ExportUsecase, cache system.Cache) *ExportService { return &ExportService{uc: uc, cache: cache} } @@ -67,13 +67,13 @@ func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBl _ = s.cache.Delete(ctx, "export:"+token) return &ExportToken{TemplateID: value.TemplateID, Params: value.Params, Blank: value.Blank}, nil } -func exportTemplateDomain(value *dto.ExportTemplateRequest) *biz.ExportTemplate { - out := &biz.ExportTemplate{ID: value.ID, DBName: value.DBName, Name: value.Name, TableName: value.TableName, TemplateID: value.TemplateID, TemplateInfo: value.TemplateInfo, SQL: value.SQL, ImportSQL: value.ImportSQL, Limit: value.Limit, Order: value.Order} +func exportTemplateDomain(value *dto.ExportTemplateRequest) *system.ExportTemplate { + out := &system.ExportTemplate{ID: value.ID, DBName: value.DBName, Name: value.Name, TableName: value.TableName, TemplateID: value.TemplateID, TemplateInfo: value.TemplateInfo, SQL: value.SQL, ImportSQL: value.ImportSQL, Limit: value.Limit, Order: value.Order} for _, item := range value.Conditions { - out.Conditions = append(out.Conditions, biz.ExportCondition{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, From: item.From, Column: item.Column, Operator: item.Operator}) + out.Conditions = append(out.Conditions, system.ExportCondition{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, From: item.From, Column: item.Column, Operator: item.Operator}) } for _, item := range value.Joins { - out.Joins = append(out.Joins, biz.ExportJoin{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, Join: item.Join, Table: item.Table, On: item.On}) + out.Joins = append(out.Joins, system.ExportJoin{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, Join: item.Join, Table: item.Table, On: item.On}) } return out } @@ -84,9 +84,9 @@ func (s *ExportService) UpdateRequest(ctx context.Context, req *dto.ExportTempla return s.Update(ctx, exportTemplateDomain(req)) } func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]*dto.ExportTemplateResponse, int64, error) { - return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end}) + return s.Templates(ctx, page, size, &system.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end}) } -func exportDTO(v *biz.ExportTemplate) *dto.ExportTemplateResponse { +func exportDTO(v *system.ExportTemplate) *dto.ExportTemplateResponse { var conditions []dto.ExportConditionResponse if v.Conditions != nil { conditions = make([]dto.ExportConditionResponse, 0, len(v.Conditions)) @@ -103,10 +103,10 @@ func exportDTO(v *biz.ExportTemplate) *dto.ExportTemplateResponse { } return &dto.ExportTemplateResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order, Conditions: conditions, Joins: joins} } -func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error { +func (s *ExportService) Create(ctx context.Context, v *system.ExportTemplate) error { return s.uc.CreateExportTemplate(ctx, v) } -func (s *ExportService) Update(ctx context.Context, v *biz.ExportTemplate) error { +func (s *ExportService) Update(ctx context.Context, v *system.ExportTemplate) error { return s.uc.UpdateExportTemplate(ctx, v) } func (s *ExportService) Delete(ctx context.Context, ids []uint) error { @@ -119,7 +119,7 @@ func (s *ExportService) Template(ctx context.Context, id uint, tid string) (*dto } return exportDTO(v), nil } -func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*dto.ExportTemplateResponse, int64, error) { +func (s *ExportService) Templates(ctx context.Context, page, size int, q *system.ExportTemplate) ([]*dto.ExportTemplateResponse, int64, error) { items, total, err := s.uc.ListExportTemplates(ctx, page, size, q) if err != nil { return nil, 0, err diff --git a/internal/service/integration_config.go b/internal/service/integration_config.go index dd61e0f..92e2c5f 100644 --- a/internal/service/integration_config.go +++ b/internal/service/integration_config.go @@ -3,14 +3,16 @@ package service import ( "context" "encoding/json" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type IntegrationConfigService struct{ uc *biz.IntegrationConfigUsecase } +type IntegrationConfigService struct { + uc *system.IntegrationConfigUsecase +} -func NewIntegrationConfigService(uc *biz.IntegrationConfigUsecase) *IntegrationConfigService { +func NewIntegrationConfigService(uc *system.IntegrationConfigUsecase) *IntegrationConfigService { return &IntegrationConfigService{uc: uc} } @@ -19,13 +21,13 @@ func (s *IntegrationConfigService) List(ctx context.Context, kind string) ([]*dt if err != nil { return nil, err } - byProvider := make(map[string]*biz.IntegrationConfig, len(configs)) + byProvider := make(map[string]*system.IntegrationConfig, len(configs)) for _, config := range configs { if config != nil { byProvider[config.Provider] = config } } - definitions := biz.IntegrationDefinitions(kind) + definitions := system.IntegrationDefinitions(kind) result := make([]*dto.IntegrationConfigResponse, 0, len(definitions)+len(configs)) for _, definition := range definitions { config, configured := byProvider[definition.Provider] @@ -52,7 +54,7 @@ func (s *IntegrationConfigService) Find(ctx context.Context, kind, provider stri if err != nil { return nil, err } - definition, found := biz.IntegrationDefinition(config.Kind, config.Provider) + definition, found := system.IntegrationDefinition(config.Kind, config.Provider) response := &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: config.Values} if found { response.Name = definition.Name @@ -67,14 +69,14 @@ func (s *IntegrationConfigService) Save(ctx context.Context, kind, provider stri if req == nil { return s.uc.Save(ctx, nil) } - return s.uc.Save(ctx, &biz.IntegrationConfig{Kind: kind, Provider: provider, Enabled: req.Enabled, Values: req.Config}) + return s.uc.Save(ctx, &system.IntegrationConfig{Kind: kind, Provider: provider, Enabled: req.Enabled, Values: req.Config}) } func (s *IntegrationConfigService) Test(ctx context.Context, kind, provider string, req *dto.IntegrationConfigRequest) error { if req == nil { return s.uc.Test(ctx, nil) } - return s.uc.Test(ctx, &biz.IntegrationConfig{Kind: kind, Provider: provider, Enabled: true, Values: req.Config}) + return s.uc.Test(ctx, &system.IntegrationConfig{Kind: kind, Provider: provider, Enabled: true, Values: req.Config}) } func (s *IntegrationConfigService) Delete(ctx context.Context, kind, provider string) error { diff --git a/internal/service/media.go b/internal/service/media.go index 8d576d4..1362a75 100644 --- a/internal/service/media.go +++ b/internal/service/media.go @@ -3,26 +3,26 @@ package service import ( "context" "io" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) type MediaService struct { - uc *biz.MediaUsecase - settings biz.RuntimeSettings + uc *system.MediaUsecase + settings system.RuntimeSettings } -func NewMediaService(uc *biz.MediaUsecase, settings biz.RuntimeSettings) *MediaService { +func NewMediaService(uc *system.MediaUsecase, settings system.RuntimeSettings) *MediaService { return &MediaService{uc: uc, settings: settings} } -func (s *MediaService) MediaConfig() biz.MediaSettings { +func (s *MediaService) MediaConfig() system.MediaSettings { return s.settings.MediaSettings() } -func mediaDTO(v *biz.MediaFile) *dto.MediaResponse { +func mediaDTO(v *system.MediaFile) *dto.MediaResponse { return &dto.MediaResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, ClassID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} } -func categoryDTO(v *biz.AttachmentCategory) *dto.MediaCategoryResponse { +func categoryDTO(v *system.AttachmentCategory) *dto.MediaCategoryResponse { var children []*dto.MediaCategoryResponse if v.Children != nil { children = make([]*dto.MediaCategoryResponse, 0, len(v.Children)) @@ -46,7 +46,7 @@ func (s *MediaService) Media(ctx context.Context, id uint) (*dto.MediaResponse, } return mediaDTO(v), nil } -func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([]*dto.MediaResponse, int64, error) { +func (s *MediaService) MediaList(ctx context.Context, filter system.MediaFilter) ([]*dto.MediaResponse, int64, error) { items, total, err := s.uc.ListMedia(ctx, filter) if err != nil { return nil, 0, err @@ -58,7 +58,7 @@ func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([ return out, total, nil } func (s *MediaService) MediaListRequest(ctx context.Context, request *dto.MediaListRequest) ([]*dto.MediaResponse, int64, error) { - return s.MediaList(ctx, biz.MediaFilter{ + return s.MediaList(ctx, system.MediaFilter{ Page: request.Page, PageSize: request.PageSize, Keyword: request.Keyword, CategoryID: request.ClassID, Tag: request.Tag, UserID: request.UserID, StartCreatedAt: request.StartCreatedAt, EndCreatedAt: request.EndCreatedAt, @@ -69,13 +69,13 @@ func (s *MediaService) Delete(ctx context.Context, id uint) error { return s.uc. func (s *MediaService) Rename(ctx context.Context, id uint, name string) error { return s.uc.UpdateMediaName(ctx, id, name) } -func (s *MediaService) ImportURLs(ctx context.Context, items []*biz.MediaFile) error { +func (s *MediaService) ImportURLs(ctx context.Context, items []*system.MediaFile) error { return s.uc.CreateMediaBatch(ctx, items) } func (s *MediaService) ImportURLRequests(ctx context.Context, values []dto.ImportMediaRequest) error { - items := make([]*biz.MediaFile, 0, len(values)) + items := make([]*system.MediaFile, 0, len(values)) for _, value := range values { - items = append(items, &biz.MediaFile{Name: value.Name, CategoryID: value.ClassID, URL: value.URL, Tag: value.Tag, Key: value.Key, Size: value.Size, Mime: value.Mime, MD5: value.MD5, UserID: value.UserID}) + items = append(items, &system.MediaFile{Name: value.Name, CategoryID: value.ClassID, URL: value.URL, Tag: value.Tag, Key: value.Key, Size: value.Size, Mime: value.Mime, MD5: value.MD5, UserID: value.UserID}) } return s.ImportURLs(ctx, items) } @@ -101,11 +101,11 @@ func (s *MediaService) Categories(ctx context.Context) ([]*dto.MediaCategoryResp } return out, nil } -func (s *MediaService) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error { +func (s *MediaService) SaveCategory(ctx context.Context, v *system.AttachmentCategory) error { return s.uc.SaveCategory(ctx, v) } func (s *MediaService) SaveCategoryRequest(ctx context.Context, value *dto.CategoryRequest) error { - return s.SaveCategory(ctx, &biz.AttachmentCategory{ID: value.ID, Name: value.Name, ParentID: value.PID}) + return s.SaveCategory(ctx, &system.AttachmentCategory{ID: value.ID, Name: value.Name, ParentID: value.PID}) } func (s *MediaService) DeleteCategory(ctx context.Context, id uint) error { return s.uc.DeleteCategory(ctx, id) diff --git a/internal/service/menu.go b/internal/service/menu.go index 7e55c58..4574cf6 100644 --- a/internal/service/menu.go +++ b/internal/service/menu.go @@ -2,14 +2,14 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type MenuService struct{ uc *biz.MenuUsecase } +type MenuService struct{ uc *system.MenuUsecase } -func NewMenuService(uc *biz.MenuUsecase) *MenuService { return &MenuService{uc: uc} } +func NewMenuService(uc *system.MenuUsecase) *MenuService { return &MenuService{uc: uc} } func (s *MenuService) List(ctx context.Context) ([]*dto.MenuResponse, error) { items, err := s.uc.List(ctx) @@ -74,20 +74,20 @@ func (s *MenuService) SetRoles(ctx context.Context, req *dto.SetMenuRolesRequest return s.uc.SetRoles(ctx, req.MenuID, req.AuthorityIDs) } -func menuDomain(v *dto.MenuRequest) *biz.Menu { - menu := &biz.Menu{CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, 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)) +func menuDomain(v *dto.MenuRequest) *system.Menu { + menu := &system.Menu{CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, 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([]*system.MenuButton, 0, len(v.MenuButtons)) for _, button := range v.MenuButtons { - menu.Buttons = append(menu.Buttons, &biz.MenuButton{CreatedAt: button.CreatedAt, UpdatedAt: button.UpdatedAt, ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID}) + menu.Buttons = append(menu.Buttons, &system.MenuButton{CreatedAt: button.CreatedAt, UpdatedAt: button.UpdatedAt, ID: button.ID, Name: button.Name, Description: button.Description, MenuID: v.ID}) } - menu.Parameters = make([]*biz.MenuParameter, 0, len(v.Parameters)) + menu.Parameters = make([]*system.MenuParameter, 0, len(v.Parameters)) for _, parameter := range v.Parameters { - menu.Parameters = append(menu.Parameters, &biz.MenuParameter{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + menu.Parameters = append(menu.Parameters, &system.MenuParameter{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, MenuID: v.ID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } return menu } -func menuResponse(menu *biz.Menu) *dto.MenuResponse { +func menuResponse(menu *system.Menu) *dto.MenuResponse { var children []*dto.MenuResponse if menu.Children != nil { children = make([]*dto.MenuResponse, 0, len(menu.Children)) @@ -112,7 +112,7 @@ func menuResponse(menu *biz.Menu) *dto.MenuResponse { return &dto.MenuResponse{CreatedAt: menu.CreatedAt, UpdatedAt: menu.UpdatedAt, ID: menu.ID, MenuID: menu.MenuID, 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} } -func dynamicMenuResponse(menu *biz.Menu) *dto.DynamicMenuResponse { +func dynamicMenuResponse(menu *system.Menu) *dto.DynamicMenuResponse { var children []*dto.DynamicMenuResponse if menu.Children != nil { children = make([]*dto.DynamicMenuResponse, 0, len(menu.Children)) @@ -140,7 +140,7 @@ func dynamicMenuResponse(menu *biz.Menu) *dto.DynamicMenuResponse { return &dto.DynamicMenuResponse{CreatedAt: menu.CreatedAt, UpdatedAt: menu.UpdatedAt, ID: menu.ID, MenuID: menu.MenuID, 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: nil, Buttons: buttonAuthorities} } -func menuResponses(items []*biz.Menu) []*dto.MenuResponse { +func menuResponses(items []*system.Menu) []*dto.MenuResponse { if items == nil { return nil } @@ -151,7 +151,7 @@ func menuResponses(items []*biz.Menu) []*dto.MenuResponse { return out } -func dynamicMenuResponses(items []*biz.Menu) []*dto.DynamicMenuResponse { +func dynamicMenuResponses(items []*system.Menu) []*dto.DynamicMenuResponse { out := make([]*dto.DynamicMenuResponse, 0, len(items)) for _, item := range items { out = append(out, dynamicMenuResponse(item)) diff --git a/internal/service/menu_test.go b/internal/service/menu_test.go index 3931d78..510b933 100644 --- a/internal/service/menu_test.go +++ b/internal/service/menu_test.go @@ -2,10 +2,10 @@ package service import ( "encoding/json" + "kra/internal/biz/system" "testing" "time" - "kra/internal/biz" "kra/internal/service/dto" ) @@ -42,7 +42,7 @@ func TestMenuResponseEmptyCollectionContracts(t *testing.T) { t.Fatalf("dynamic menu top-level empty = %#v, want non-nil empty slice", got) } - base := menuResponse(&biz.Menu{Children: nil, Buttons: []*biz.MenuButton{}, Parameters: []*biz.MenuParameter{}}) + base := menuResponse(&system.Menu{Children: nil, Buttons: []*system.MenuButton{}, Parameters: []*system.MenuParameter{}}) if base.Children != nil { t.Fatalf("leaf children = %#v, want nil", base.Children) } diff --git a/internal/service/parameter.go b/internal/service/parameter.go index 9c0633a..cc50a53 100644 --- a/internal/service/parameter.go +++ b/internal/service/parameter.go @@ -2,20 +2,20 @@ package service import ( "context" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/internal/service/dto" ) -type ParameterService struct{ uc *biz.ParameterUsecase } +type ParameterService struct{ uc *system.ParameterUsecase } -func NewParameterService(uc *biz.ParameterUsecase) *ParameterService { +func NewParameterService(uc *system.ParameterUsecase) *ParameterService { return &ParameterService{uc: uc} } -func parameterDomain(value *dto.SystemParameterRequest) *biz.SystemParameter { - return &biz.SystemParameter{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Name: value.Name, Key: value.Key, Value: value.Value, Desc: value.Description} +func parameterDomain(value *dto.SystemParameterRequest) *system.SystemParameter { + return &system.SystemParameter{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Name: value.Name, Key: value.Key, Value: value.Value, Desc: value.Description} } func (s *ParameterService) CreateParameterRequest(ctx context.Context, req *dto.SystemParameterRequest) error { return s.CreateParameter(ctx, parameterDomain(req)) @@ -24,12 +24,12 @@ func (s *ParameterService) UpdateParameterRequest(ctx context.Context, req *dto. return s.UpdateParameter(ctx, parameterDomain(req)) } func (s *ParameterService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]*dto.SystemParameterResponse, int64, error) { - return s.Parameters(ctx, page, size, &biz.SystemParameter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end}) + return s.Parameters(ctx, page, size, &system.SystemParameter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end}) } -func parameterDTO(v *biz.SystemParameter) *dto.SystemParameterResponse { +func parameterDTO(v *system.SystemParameter) *dto.SystemParameterResponse { return &dto.SystemParameterResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc} } -func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*dto.SystemParameterResponse, int64, error) { +func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *system.SystemParameter) ([]*dto.SystemParameterResponse, int64, error) { items, total, err := s.uc.ListParameters(ctx, page, size, q) if err != nil { return nil, 0, err @@ -54,10 +54,10 @@ func (s *ParameterService) ParameterByKey(ctx context.Context, key string) (*dto } return parameterDTO(v), nil } -func (s *ParameterService) CreateParameter(ctx context.Context, v *biz.SystemParameter) error { +func (s *ParameterService) CreateParameter(ctx context.Context, v *system.SystemParameter) error { return s.uc.CreateParameter(ctx, v) } -func (s *ParameterService) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error { +func (s *ParameterService) UpdateParameter(ctx context.Context, v *system.SystemParameter) error { return s.uc.UpdateParameter(ctx, v) } func (s *ParameterService) DeleteParameters(ctx context.Context, ids []string) error { diff --git a/internal/service/payment.go b/internal/service/payment.go index 5363153..7e268ba 100644 --- a/internal/service/payment.go +++ b/internal/service/payment.go @@ -3,14 +3,14 @@ package service import ( "context" "errors" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type PaymentService struct{ uc *biz.PaymentUsecase } +type PaymentService struct{ uc *system.PaymentUsecase } -func NewPaymentService(uc *biz.PaymentUsecase) *PaymentService { return &PaymentService{uc: uc} } +func NewPaymentService(uc *system.PaymentUsecase) *PaymentService { return &PaymentService{uc: uc} } func (s *PaymentService) Order(ctx context.Context, provider, tradeNo string) (*dto.PaymentOrderResponse, error) { order, err := s.uc.Order(ctx, provider, tradeNo) if err != nil { @@ -44,7 +44,7 @@ func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRe if pageSize <= 0 { pageSize = 10 } - items, total, err := s.uc.Orders(ctx, page, pageSize, biz.PaymentOrderFilter{ + items, total, err := s.uc.Orders(ctx, page, pageSize, system.PaymentOrderFilter{ Provider: req.Provider, TradeNo: req.TradeNo, BusinessType: req.BusinessType, BusinessID: req.BusinessID, PaymentStatus: req.PaymentStatus, RefundStatus: req.RefundStatus, }) @@ -62,7 +62,7 @@ func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRe return result, total, page, pageSize, nil } -func paymentOrderResponse(order *biz.PaymentOrder) (*dto.PaymentOrderResponse, error) { +func paymentOrderResponse(order *system.PaymentOrder) (*dto.PaymentOrderResponse, error) { if order == nil { return nil, errors.New("支付订单为空") } @@ -84,7 +84,7 @@ func (s *PaymentService) Create(ctx context.Context, req *dto.PaymentRequest) (* if req == nil { return nil, errors.New("支付请求为空") } - result, err := s.uc.Create(ctx, &biz.PaymentRequest{ + result, err := s.uc.Create(ctx, &system.PaymentRequest{ Provider: req.Provider, TradeNo: req.TradeNo, Subject: req.Subject, Amount: req.Amount, OriginalAmount: req.OriginalAmount, Currency: req.Currency, NotifyURL: req.NotifyURL, ReturnURL: req.ReturnURL, ClientIP: req.ClientIP, @@ -130,13 +130,13 @@ func (s *PaymentService) Fulfill(ctx context.Context, req *dto.PaymentFulfillReq } func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackRequest) (dto.PaymentCallbackAck, error) { if req == nil { - return paymentCallbackAckResponse(biz.DefaultPaymentCallbackAck("", false)), errors.New("支付回调请求为空") + return paymentCallbackAckResponse(system.DefaultPaymentCallbackAck("", false)), errors.New("支付回调请求为空") } - result, err := s.uc.Callback(ctx, &biz.PaymentCallback{Provider: req.Provider, Headers: req.Headers, Body: req.Body, Query: req.Query}) + result, err := s.uc.Callback(ctx, &system.PaymentCallback{Provider: req.Provider, Headers: req.Headers, Body: req.Body, Query: req.Query}) if err != nil { - return paymentCallbackAckResponse(biz.CallbackFailure(err, biz.DefaultPaymentCallbackAck(req.Provider, false))), err + return paymentCallbackAckResponse(system.CallbackFailure(err, system.DefaultPaymentCallbackAck(req.Provider, false))), err } - ack := biz.DefaultPaymentCallbackAck(req.Provider, true) + ack := system.DefaultPaymentCallbackAck(req.Provider, true) if result != nil && result.SuccessAck.StatusCode != 0 { ack = result.SuccessAck } @@ -144,10 +144,10 @@ func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackR } func DefaultPaymentCallbackAck(provider string, success bool) dto.PaymentCallbackAck { - return paymentCallbackAckResponse(biz.DefaultPaymentCallbackAck(provider, success)) + return paymentCallbackAckResponse(system.DefaultPaymentCallbackAck(provider, success)) } -func paymentResultResponse(result *biz.PaymentResult) *dto.PaymentResultResponse { +func paymentResultResponse(result *system.PaymentResult) *dto.PaymentResultResponse { if result == nil { return nil } @@ -165,6 +165,6 @@ func paymentResultResponse(result *biz.PaymentResult) *dto.PaymentResultResponse } } -func paymentCallbackAckResponse(ack biz.PaymentCallbackAck) dto.PaymentCallbackAck { +func paymentCallbackAckResponse(ack system.PaymentCallbackAck) dto.PaymentCallbackAck { return dto.PaymentCallbackAck{StatusCode: ack.StatusCode, ContentType: ack.ContentType, Body: ack.Body} } diff --git a/internal/service/permission.go b/internal/service/permission.go index eae7542..fe3f79c 100644 --- a/internal/service/permission.go +++ b/internal/service/permission.go @@ -2,13 +2,12 @@ package service import ( "context" - - "kra/internal/biz" + "kra/internal/biz/system" ) -type PermissionService struct{ uc *biz.PermissionUsecase } +type PermissionService struct{ uc *system.PermissionUsecase } -func NewPermissionService(uc *biz.PermissionUsecase) *PermissionService { +func NewPermissionService(uc *system.PermissionUsecase) *PermissionService { return &PermissionService{uc: uc} } diff --git a/internal/service/position.go b/internal/service/position.go index 35c1ee1..19f6d20 100644 --- a/internal/service/position.go +++ b/internal/service/position.go @@ -2,27 +2,27 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -type PositionService struct{ uc *biz.PositionUsecase } +type PositionService struct{ uc *system.PositionUsecase } -func NewPositionService(uc *biz.PositionUsecase) *PositionService { +func NewPositionService(uc *system.PositionUsecase) *PositionService { return &PositionService{uc: uc} } -func positionDomain(value *dto.PositionRequest) *biz.Position { - return &biz.Position{ID: value.ID, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} +func positionDomain(value *dto.PositionRequest) *system.Position { + return &system.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 { +func positionResponse(value *system.Position) *dto.PositionResponse { return &dto.PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} } func (s *PositionService) Positions(ctx context.Context, req *dto.PositionListRequest) ([]*dto.PositionResponse, int64, error) { - items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &biz.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status}) + items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &system.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status}) if err != nil { return nil, 0, err } diff --git a/internal/service/security.go b/internal/service/security.go index 71b5550..cf9e38e 100644 --- a/internal/service/security.go +++ b/internal/service/security.go @@ -3,8 +3,8 @@ package service import ( "context" "errors" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) @@ -18,10 +18,10 @@ func IsPasswordPolicyError(err error) bool { return errors.As(err, &policyErr) } -func securityDTO(v *biz.SecurityConfig) *dto.SecurityConfigResponse { +func securityDTO(v *system.SecurityConfig) *dto.SecurityConfigResponse { return &dto.SecurityConfigResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } -func (s *SecurityService) CurrentSecurity(ctx context.Context) (*biz.SecurityConfig, error) { +func (s *SecurityService) CurrentSecurity(ctx context.Context) (*system.SecurityConfig, error) { return s.uc.Current(ctx) } func (s *SecurityService) Security(ctx context.Context) (*dto.SecurityConfigResponse, error) { @@ -31,14 +31,14 @@ func (s *SecurityService) Security(ctx context.Context) (*dto.SecurityConfigResp } return securityDTO(value), nil } -func (s *SecurityService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (*dto.SecurityConfigResponse, error) { +func (s *SecurityService) SaveSecurity(ctx context.Context, value *system.SecurityConfig) (*dto.SecurityConfigResponse, error) { if err := s.uc.UpdateSecurity(ctx, value); err != nil { return nil, err } return securityDTO(value), nil } func (s *SecurityService) SaveSecurityRequest(ctx context.Context, value *dto.SecurityConfigRequest) (*dto.SecurityConfigResponse, error) { - return s.SaveSecurity(ctx, &biz.SecurityConfig{ + return s.SaveSecurity(ctx, &system.SecurityConfig{ ID: value.ID, CaptchaOpen: value.CaptchaOpen, CaptchaTimeout: value.CaptchaTimeout, KeyLong: value.KeyLong, ImgWidth: value.ImgWidth, ImgHeight: value.ImgHeight, PwdMinLength: value.PwdMinLength, PwdRequireUpper: value.PwdRequireUpper, diff --git a/internal/service/security_session.go b/internal/service/security_session.go index 2473a32..836dfa8 100644 --- a/internal/service/security_session.go +++ b/internal/service/security_session.go @@ -2,16 +2,15 @@ package service import ( "context" + "kra/internal/biz/system" "time" - - "kra/internal/biz" ) type SecurityService struct { - uc *biz.SecurityUsecase + uc *system.SecurityUsecase } -func NewSecurityService(uc *biz.SecurityUsecase) *SecurityService { +func NewSecurityService(uc *system.SecurityUsecase) *SecurityService { return &SecurityService{uc: uc} } @@ -61,7 +60,7 @@ func (s *SecurityService) DeleteCaptcha(ctx context.Context, id string) error { func (s *SecurityService) UseMultipoint() bool { return s.uc.UseMultipoint() } -func (s *SecurityService) CaptchaSettings() biz.CaptchaSettings { +func (s *SecurityService) CaptchaSettings() system.CaptchaSettings { return s.uc.CaptchaRuntimeSettings() } diff --git a/internal/service/system.go b/internal/service/system.go index 1857d6a..19286e5 100644 --- a/internal/service/system.go +++ b/internal/service/system.go @@ -2,16 +2,15 @@ package service import ( "context" - - "kra/internal/biz" + "kra/internal/biz/system" ) type SystemConfigService struct { - uc *biz.SystemConfigUsecase - settings biz.RuntimeSettings + uc *system.SystemConfigUsecase + settings system.RuntimeSettings } -func NewSystemConfigService(uc *biz.SystemConfigUsecase, settings biz.RuntimeSettings) *SystemConfigService { +func NewSystemConfigService(uc *system.SystemConfigUsecase, settings system.RuntimeSettings) *SystemConfigService { return &SystemConfigService{uc: uc, settings: settings} } diff --git a/internal/service/system_init.go b/internal/service/system_init.go index fd2d8b1..88cc550 100644 --- a/internal/service/system_init.go +++ b/internal/service/system_init.go @@ -2,12 +2,12 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) -func (s *SystemConfigService) Initialize(ctx context.Context, input *dto.DatabaseInitRequest, apis []*biz.API) error { +func (s *SystemConfigService) Initialize(ctx context.Context, input *dto.DatabaseInitRequest, apis []*system.API) error { driver, config := input.DBType, "" switch driver { case "mysql": @@ -21,15 +21,15 @@ func (s *SystemConfigService) Initialize(ctx context.Context, input *dto.Databas driver = "mysql" config = "charset=utf8mb4&parseTime=True&loc=Local" } - return s.uc.Initialize(ctx, &biz.DatabaseConfig{Driver: driver, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, Template: input.Template, AdminPassword: input.AdminPassword, APIs: apis}) + return s.uc.Initialize(ctx, &system.DatabaseConfig{Driver: driver, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, Template: input.Template, AdminPassword: input.AdminPassword, APIs: apis}) } func (s *SystemConfigService) InitializeRoutes(ctx context.Context, input *dto.DatabaseInitRequest, routes []dto.Route) error { - apis := make([]*biz.API, 0, len(routes)) + apis := make([]*system.API, 0, len(routes)) for _, route := range routes { path := NormalizeRoutePath(route.Path, s.settings.RouterPrefix()) group, description := RouteMetadata(route.Method, path) - apis = append(apis, &biz.API{Path: path, Method: route.Method, APIGroup: group, Description: description}) + apis = append(apis, &system.API{Path: path, Method: route.Method, APIGroup: group, Description: description}) } return s.Initialize(ctx, input, apis) } diff --git a/internal/service/task.go b/internal/service/task.go index fa8562d..37959cf 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -3,21 +3,21 @@ package service import ( "context" "encoding/json" + "kra/internal/biz/system" "time" - "kra/internal/biz" "kra/internal/service/dto" ) type TaskService struct { - uc *biz.TaskApplicationUsecase + uc *system.TaskApplicationUsecase } -func NewTaskService(uc *biz.TaskApplicationUsecase) *TaskService { +func NewTaskService(uc *system.TaskApplicationUsecase) *TaskService { return &TaskService{uc: uc} } -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 taskDomain(v *dto.TaskRequest) *system.TimedTask { + return &system.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) @@ -30,12 +30,12 @@ func (s *TaskService) UpdateRequest(ctx context.Context, req *dto.TaskRequest) e return s.Update(ctx, taskDomain(req)) } func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]*dto.TaskResponse, int64, error) { - return s.Tasks(ctx, page, size, &biz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}) + return s.Tasks(ctx, page, size, &system.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}) } -func (s *TaskService) Create(ctx context.Context, v *biz.TimedTask) error { +func (s *TaskService) Create(ctx context.Context, v *system.TimedTask) error { return s.uc.Create(ctx, v) } -func (s *TaskService) Update(ctx context.Context, v *biz.TimedTask) error { +func (s *TaskService) Update(ctx context.Context, v *system.TimedTask) error { return s.uc.Update(ctx, v) } func (s *TaskService) Delete(ctx context.Context, id uint) error { @@ -44,10 +44,10 @@ func (s *TaskService) Delete(ctx context.Context, id uint) error { func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error { return s.uc.Toggle(ctx, id, enabled) } -func taskDTO(v *biz.TimedTask, next *time.Time) *dto.TaskResponse { +func taskDTO(v *system.TimedTask, next *time.Time) *dto.TaskResponse { return &dto.TaskResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: json.RawMessage(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: json.RawMessage(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled, NextRunAt: next} } -func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*dto.TaskResponse, int64, error) { +func (s *TaskService) Tasks(ctx context.Context, page, size int, q *system.TimedTask) ([]*dto.TaskResponse, int64, error) { items, total, next, err := s.uc.List(ctx, page, size, q) if err != nil { return nil, 0, err diff --git a/internal/service/user.go b/internal/service/user.go index a34cde6..45e0977 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -2,8 +2,8 @@ package service import ( "context" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/service/dto" ) @@ -16,11 +16,11 @@ type userInput struct { } type UserService struct { - uc *biz.UserUsecase + uc *system.UserUsecase settings *SecurityService } -func NewUserService(uc *biz.UserUsecase, settings *SecurityService) *UserService { +func NewUserService(uc *system.UserUsecase, settings *SecurityService) *UserService { return &UserService{uc: uc, settings: settings} } @@ -28,7 +28,7 @@ func userRequestInput(value *dto.UserRequest) userInput { return userInput{ID: value.ID, Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, AuthorityIDs: value.AuthorityIDs, Enable: value.Enable, Phone: value.Phone, Email: value.Email} } func (s *UserService) ListUsersRequest(ctx context.Context, value *dto.UserListRequest) ([]*dto.UserResponse, int64, error) { - return s.ListUsers(ctx, value.Page, value.PageSize, &biz.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc}) + return s.ListUsers(ctx, value.Page, value.PageSize, &system.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc}) } func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (*dto.UserResponse, error) { return s.createUser(ctx, userRequestInput(value)) @@ -64,7 +64,7 @@ func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]*dto.Dynam return dynamicMenuResponses(menus), nil } -func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*dto.UserResponse, int64, error) { +func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *system.UserListFilter) ([]*dto.UserResponse, int64, error) { users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter) if err != nil { return nil, 0, err @@ -94,7 +94,7 @@ func (s *UserService) createUser(ctx context.Context, input userInput) (*dto.Use } security, _ := s.settings.CurrentSecurity(ctx) mustChange := security != nil && security.ForceNewUserChangePassword - user, err := s.uc.CreateUser(ctx, &biz.User{Username: input.Username, Password: input.Password, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable, MustChangePassword: mustChange}, input.AuthorityIDs) + user, err := s.uc.CreateUser(ctx, &system.User{Username: input.Username, Password: input.Password, NickName: input.NickName, HeaderImg: input.HeaderImg, AuthorityID: input.AuthorityID, Phone: input.Phone, Email: input.Email, Enable: input.Enable, MustChangePassword: mustChange}, input.AuthorityIDs) if err != nil { return nil, err } @@ -103,10 +103,10 @@ func (s *UserService) createUser(ctx context.Context, input userInput) (*dto.Use func (s *UserService) updateUser(ctx context.Context, input userInput) error { // The compatible ChangeUserInfo payload uses authorityIds for role assignment; the // standalone authorityId field is not applied by setUserInfo. - return s.uc.UpdateUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}, input.AuthorityIDs) + return s.uc.UpdateUser(ctx, &system.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}, input.AuthorityIDs) } func (s *UserService) updateSelfUser(ctx context.Context, input userInput) error { - return s.uc.UpdateSelfUser(ctx, &biz.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}) + return s.uc.UpdateSelfUser(ctx, &system.User{ID: input.ID, NickName: input.NickName, HeaderImg: input.HeaderImg, Phone: input.Phone, Email: input.Email, Enable: input.Enable}) } func (s *UserService) DeleteUser(ctx context.Context, id uint) error { return s.uc.DeleteUser(ctx, id) diff --git a/internal/service/user_conversion.go b/internal/service/user_conversion.go index c6e3e00..7c002a0 100644 --- a/internal/service/user_conversion.go +++ b/internal/service/user_conversion.go @@ -1,18 +1,18 @@ package service import ( - "kra/internal/biz" + "kra/internal/biz/system" "kra/internal/service/dto" ) -func convertAuthority(value biz.Authority) *dto.AuthorityResponse { +func convertAuthority(value system.Authority) *dto.AuthorityResponse { var menus []*dto.MenuResponse if value.Menus != nil { menus = menuResponses(value.Menus) } return &dto.AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: value.DeletedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} } -func convertUser(user *biz.User) *dto.UserResponse { +func convertUser(user *system.User) *dto.UserResponse { var authorities []*dto.AuthorityResponse if user.Authorities != nil { authorities = make([]*dto.AuthorityResponse, 0, len(user.Authorities)) diff --git a/internal/service/version.go b/internal/service/version.go index 5fcad99..48f6483 100644 --- a/internal/service/version.go +++ b/internal/service/version.go @@ -3,15 +3,14 @@ package service import ( "context" "encoding/json" + "kra/internal/biz/system" "kra/internal/service/dto" "time" - - "kra/internal/biz" ) -type VersionService struct{ uc *biz.VersionUsecase } +type VersionService struct{ uc *system.VersionUsecase } -func NewVersionService(uc *biz.VersionUsecase) *VersionService { return &VersionService{uc: uc} } +func NewVersionService(uc *system.VersionUsecase) *VersionService { return &VersionService{uc: uc} } type versionExportInfo struct { Name string `json:"name"` @@ -103,7 +102,7 @@ type versionExportDocument struct { Dictionaries []versionExportDictionary `json:"dictionaries"` } -func exportMenu(value *biz.Menu) *versionExportMenu { +func exportMenu(value *system.Menu) *versionExportMenu { menu := &versionExportMenu{ Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, Meta: dto.VersionMenuMetaRequest{ActiveName: value.ActiveName, KeepAlive: value.KeepAlive, DefaultMenu: value.DefaultMenu, Title: value.Title, Icon: value.Icon, CloseTab: value.CloseTab, TransitionType: value.TransitionType}, @@ -129,7 +128,7 @@ func exportMenu(value *biz.Menu) *versionExportMenu { return menu } -func versionDTO(v *biz.Version) *dto.VersionResponse { +func versionDTO(v *system.Version) *dto.VersionResponse { return &dto.VersionResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, VersionName: v.Name, VersionCode: v.Code, Description: v.Description, VersionData: v.Data} } @@ -213,23 +212,23 @@ func (s *VersionService) Export(ctx context.Context, name, code, description str payload := versionExportDocument{Version: versionExportInfo{Name: name, Code: code, Description: description, ExportTime: bundle.ExportTime}, Menus: menus, APIs: apis, Dictionaries: dictionaries} raw, err := json.MarshalIndent(payload, "", " ") if err != nil { - return &biz.VersionStageError{Stage: biz.VersionStageJSON, Err: err} + return &system.VersionStageError{Stage: system.VersionStageJSON, Err: err} } - if err = s.uc.CreateVersion(ctx, &biz.Version{Name: versionPointer(name), Code: versionPointer(code), Description: versionPointer(description), Data: versionPointer(string(raw))}); err != nil { - return &biz.VersionStageError{Stage: biz.VersionStageSave, Err: err} + if err = s.uc.CreateVersion(ctx, &system.Version{Name: versionPointer(name), Code: versionPointer(code), Description: versionPointer(description), Data: versionPointer(string(raw))}); err != nil { + return &system.VersionStageError{Stage: system.VersionStageSave, Err: err} } return nil } -func (s *VersionService) Import(ctx context.Context, bundle *biz.VersionBundle) error { +func (s *VersionService) Import(ctx context.Context, bundle *system.VersionBundle) error { return s.uc.ImportVersionBundle(ctx, bundle) } -func versionMenuDomain(value dto.VersionMenuRequest) *biz.Menu { - menu := &biz.Menu{Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, ActiveName: value.Meta.ActiveName, KeepAlive: value.Meta.KeepAlive, DefaultMenu: value.Meta.DefaultMenu, Title: value.Meta.Title, Icon: value.Meta.Icon, CloseTab: value.Meta.CloseTab, TransitionType: value.Meta.TransitionType} +func versionMenuDomain(value dto.VersionMenuRequest) *system.Menu { + menu := &system.Menu{Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, ActiveName: value.Meta.ActiveName, KeepAlive: value.Meta.KeepAlive, DefaultMenu: value.Meta.DefaultMenu, Title: value.Meta.Title, Icon: value.Meta.Icon, CloseTab: value.Meta.CloseTab, TransitionType: value.Meta.TransitionType} for _, button := range value.MenuButtons { - menu.Buttons = append(menu.Buttons, &biz.MenuButton{Name: button.Name, Description: button.Description}) + menu.Buttons = append(menu.Buttons, &system.MenuButton{Name: button.Name, Description: button.Description}) } for _, parameter := range value.Parameters { - menu.Parameters = append(menu.Parameters, &biz.MenuParameter{Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + menu.Parameters = append(menu.Parameters, &system.MenuParameter{Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } for _, child := range value.Children { menu.Children = append(menu.Children, versionMenuDomain(child)) @@ -238,19 +237,19 @@ func versionMenuDomain(value dto.VersionMenuRequest) *biz.Menu { } func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersionRequest) error { if req.Version.Name == "" || req.Version.Code == "" { - return biz.ErrInvalidVersion + return system.ErrInvalidVersion } - bundle := &biz.VersionBundle{Name: req.Version.Name, Code: req.Version.Code, Description: req.Version.Description, ExportTime: req.Version.ExportTime} + bundle := &system.VersionBundle{Name: req.Version.Name, Code: req.Version.Code, Description: req.Version.Description, ExportTime: req.Version.ExportTime} for _, menu := range req.Menus { bundle.Menus = append(bundle.Menus, versionMenuDomain(menu)) } for _, api := range req.APIs { - bundle.APIs = append(bundle.APIs, &biz.API{Path: api.Path, Description: api.Description, APIGroup: api.APIGroup, Method: api.Method}) + bundle.APIs = append(bundle.APIs, &system.API{Path: api.Path, Description: api.Description, APIGroup: api.APIGroup, Method: api.Method}) } for _, dictionary := range req.Dictionaries { - item := &biz.Dictionary{Name: dictionary.Name, Type: dictionary.Type, Status: dictionary.Status, Desc: dictionary.Description} + item := &system.Dictionary{Name: dictionary.Name, Type: dictionary.Type, Status: dictionary.Status, Desc: dictionary.Description} for _, detail := range dictionary.Details { - item.Details = append(item.Details, &biz.DictionaryDetail{Label: detail.Label, Value: detail.Value, Extend: detail.Extend, Status: detail.Status, Sort: detail.Sort, ParentID: detail.ParentID, Level: detail.Level, Path: detail.Path}) + item.Details = append(item.Details, &system.DictionaryDetail{Label: detail.Label, Value: detail.Value, Extend: detail.Extend, Status: detail.Status, Sort: detail.Sort, ParentID: detail.ParentID, Level: detail.Level, Path: detail.Path}) } bundle.Dictionaries = append(bundle.Dictionaries, item) } @@ -258,7 +257,7 @@ func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersi return err } raw, _ := json.Marshal(req) - _ = s.uc.CreateVersion(ctx, &biz.Version{ + _ = s.uc.CreateVersion(ctx, &system.Version{ Name: versionPointer(req.Version.Name), Code: versionPointer(req.Version.Code + "_imported_" + time.Now().Format("20060102150405")), Description: versionPointer("导入版本: " + req.Version.Description), diff --git a/internal/worker/task_executor.go b/internal/worker/task_executor.go index 3482367..77ea072 100644 --- a/internal/worker/task_executor.go +++ b/internal/worker/task_executor.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "kra/internal/biz/system" "net" "net/http" "net/url" @@ -14,24 +15,23 @@ import ( "syscall" "time" - "kra/internal/biz" "kra/internal/conf" ) type TaskExecutor struct { - tasks *biz.TaskUsecase - media *biz.MediaUsecase + tasks *system.TaskUsecase + media *system.MediaUsecase runtime *conf.Runtime - methods biz.TaskMethodRegistry + methods system.TaskMethodRegistry } -func NewTaskExecutor(tasks *biz.TaskUsecase, media *biz.MediaUsecase, runtime *conf.Runtime) *TaskExecutor { - return NewTaskExecutorWithRegistry(tasks, media, runtime, biz.DefaultTaskMethodRegistry()) +func NewTaskExecutor(tasks *system.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime) *TaskExecutor { + return NewTaskExecutorWithRegistry(tasks, media, runtime, system.DefaultTaskMethodRegistry()) } -func NewTaskExecutorWithRegistry(tasks *biz.TaskUsecase, media *biz.MediaUsecase, runtime *conf.Runtime, methods biz.TaskMethodRegistry) *TaskExecutor { +func NewTaskExecutorWithRegistry(tasks *system.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime, methods system.TaskMethodRegistry) *TaskExecutor { if methods == nil { - methods = biz.DefaultTaskMethodRegistry() + methods = system.DefaultTaskMethodRegistry() } return &TaskExecutor{tasks: tasks, media: media, runtime: runtime, methods: methods} } @@ -61,7 +61,7 @@ func taskHTTPClient(allowPrivate bool) *http.Client { return &http.Client{Timeout: 30 * time.Second, Transport: &http.Transport{Proxy: nil, DialContext: dialer.DialContext}} } -func (e *TaskExecutor) runHTTP(ctx context.Context, task *biz.TimedTask) (string, error) { +func (e *TaskExecutor) runHTTP(ctx context.Context, task *system.TimedTask) (string, error) { parsed, err := url.Parse(task.HTTPURL) if err != nil { return "", fmt.Errorf("URL 非法: %w", err) @@ -109,13 +109,13 @@ func truncateTaskText(value string) string { return value[:limit] + "...(截断)" } -func (e *TaskExecutor) runMethod(ctx context.Context, task *biz.TimedTask) error { - var method biz.TaskMethodFunc +func (e *TaskExecutor) runMethod(ctx context.Context, task *system.TimedTask) error { + var method system.TaskMethodFunc var ok bool if e.methods != nil { method, ok = e.methods.Lookup(task.MethodName) } else { - method, ok = biz.TaskMethodByName(task.MethodName) + method, ok = system.TaskMethodByName(task.MethodName) } if !ok { return fmt.Errorf("方法 %s 未注册(需通过 platform/task.Registry 注册)", task.MethodName) @@ -145,12 +145,12 @@ func (e *TaskExecutor) runMethod(ctx context.Context, task *biz.TimedTask) error } } -func (e *TaskExecutor) Run(ctx context.Context, task *biz.TimedTask, trigger string) (log *biz.TimedTaskLog) { +func (e *TaskExecutor) Run(ctx context.Context, task *system.TimedTask, trigger string) (log *system.TimedTaskLog) { if ctx == nil { ctx = context.Background() } started := time.Now() - log = &biz.TimedTaskLog{TaskID: task.ID, TaskName: task.Name, TriggerType: trigger, StartedAt: started, Status: "success"} + log = &system.TimedTaskLog{TaskID: task.ID, TaskName: task.Name, TriggerType: trigger, StartedAt: started, Status: "success"} defer func() { if recovered := recover(); recovered != nil { log.Status = "fail" @@ -165,9 +165,9 @@ func (e *TaskExecutor) Run(ctx context.Context, task *biz.TimedTask, trigger str }() var err error switch task.ExecutorType { - case biz.TaskExecutorMethod: + case system.TaskExecutorMethod: err = e.runMethod(ctx, task) - case biz.TaskExecutorHTTP: + case system.TaskExecutorHTTP: runCtx, cancel := context.WithTimeout(ctx, 30*time.Second) log.Output, err = e.runHTTP(runCtx, task) cancel() diff --git a/internal/worker/task_executor_test.go b/internal/worker/task_executor_test.go index b141aa0..d00ddde 100644 --- a/internal/worker/task_executor_test.go +++ b/internal/worker/task_executor_test.go @@ -4,12 +4,11 @@ import ( "context" "encoding/json" "errors" + "kra/internal/biz/system" "net" "strings" "testing" "time" - - "kra/internal/biz" ) func TestPrivateIP(t *testing.T) { @@ -25,25 +24,25 @@ func TestPrivateIP(t *testing.T) { func TestRunMethodReportsParentDeadlineAsTimeout(t *testing.T) { const methodName = "worker-test-parent-deadline" - biz.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { + system.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { <-ctx.Done() return ctx.Err() }) executor := &TaskExecutor{} ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() - if err := executor.runMethod(ctx, &biz.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { + if err := executor.runMethod(ctx, &system.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { t.Fatalf("runMethod() error = %v, want errTaskTimeout", err) } } func TestRunMethodConvertsPanicToError(t *testing.T) { const methodName = "worker-test-panic" - biz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { panic("boom") }) executor := &TaskExecutor{} - err := executor.runMethod(context.Background(), &biz.TimedTask{MethodName: methodName}) + err := executor.runMethod(context.Background(), &system.TimedTask{MethodName: methodName}) if err == nil || !strings.Contains(err.Error(), "panic: boom") { t.Fatalf("runMethod() error = %v, want recovered panic", err) } @@ -52,7 +51,7 @@ func TestRunMethodConvertsPanicToError(t *testing.T) { func TestRunMethodUsesParentContext(t *testing.T) { const methodName = "worker-test-parent-context" started := make(chan struct{}) - biz.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { + system.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { close(started) <-ctx.Done() return ctx.Err() @@ -61,7 +60,7 @@ func TestRunMethodUsesParentContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) done := make(chan error, 1) go func() { - done <- executor.runMethod(ctx, &biz.TimedTask{MethodName: methodName}) + done <- executor.runMethod(ctx, &system.TimedTask{MethodName: methodName}) }() select { case <-started: @@ -82,14 +81,14 @@ func TestRunMethodUsesParentContext(t *testing.T) { func TestRunMethodHonorsDeadlineWhenMethodIgnoresContext(t *testing.T) { const methodName = "worker-test-ignores-context" release := make(chan struct{}) - biz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { <-release return nil }) executor := &TaskExecutor{} ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() - if err := executor.runMethod(ctx, &biz.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { + if err := executor.runMethod(ctx, &system.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { close(release) t.Fatalf("runMethod() error = %v, want errTaskTimeout", err) } diff --git a/internal/worker/task_registry.go b/internal/worker/task_registry.go index 2b58f19..9bb5afd 100644 --- a/internal/worker/task_registry.go +++ b/internal/worker/task_registry.go @@ -3,8 +3,8 @@ package worker import ( "context" "encoding/json" + "kra/internal/biz/system" - "kra/internal/biz" "kra/internal/conf" platformtask "kra/pkg/task" ) @@ -12,12 +12,12 @@ import ( // TaskMethods is the system module's dependency-bearing task contribution. // Other modules provide their own contributor instead of editing this file. type TaskMethods struct { - tasks *biz.TaskUsecase - media *biz.MediaUsecase + tasks *system.TaskUsecase + media *system.MediaUsecase runtime *conf.Runtime } -func NewTaskMethods(tasks *biz.TaskUsecase, media *biz.MediaUsecase, runtime *conf.Runtime) *TaskMethods { +func NewTaskMethods(tasks *system.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime) *TaskMethods { return &TaskMethods{tasks: tasks, media: media, runtime: runtime} } @@ -28,12 +28,12 @@ func (methods *TaskMethods) RegisterTasks(registry *platformtask.Registry) { return } registry.Register(platformtask.Method{ - Name: biz.TaskMethodClearDB, Description: "清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Run: func(ctx context.Context, _ json.RawMessage) error { + Name: system.TaskMethodClearDB, Description: "清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Run: func(ctx context.Context, _ json.RawMessage) error { return methods.tasks.CleanupLogs(ctx) }, }) registry.Register(platformtask.Method{ - Name: biz.TaskMethodUploads, Description: "清理过期大文件上传会话", Run: func(ctx context.Context, _ json.RawMessage) error { + Name: system.TaskMethodUploads, Description: "清理过期大文件上传会话", Run: func(ctx context.Context, _ json.RawMessage) error { ttl := 24 config := methods.runtime.Admin() if config != nil && config.Media != nil && config.Media.SessionTtl > 0 { diff --git a/internal/worker/task_runtime_test.go b/internal/worker/task_runtime_test.go index d016301..70697a5 100644 --- a/internal/worker/task_runtime_test.go +++ b/internal/worker/task_runtime_test.go @@ -5,31 +5,30 @@ import ( "encoding/json" "errors" "io" + "kra/internal/biz/system" "log/slog" "sync" "sync/atomic" "testing" "time" - - "kra/internal/biz" ) type workerTaskRepo struct { mu sync.Mutex - items []*biz.TimedTask + items []*system.TimedTask listErr error - logs chan *biz.TimedTaskLog + logs chan *system.TimedTaskLog } -func (r *workerTaskRepo) CreateTask(context.Context, *biz.TimedTask) error { return nil } -func (r *workerTaskRepo) UpdateTask(context.Context, *biz.TimedTask) error { return nil } -func (r *workerTaskRepo) DeleteTask(context.Context, uint) error { return nil } -func (r *workerTaskRepo) ToggleTask(context.Context, uint, bool) error { return nil } -func (r *workerTaskRepo) CleanupLogs(context.Context) error { return nil } +func (r *workerTaskRepo) CreateTask(context.Context, *system.TimedTask) error { return nil } +func (r *workerTaskRepo) UpdateTask(context.Context, *system.TimedTask) error { return nil } +func (r *workerTaskRepo) DeleteTask(context.Context, uint) error { return nil } +func (r *workerTaskRepo) ToggleTask(context.Context, uint, bool) error { return nil } +func (r *workerTaskRepo) CleanupLogs(context.Context) error { return nil } func (r *workerTaskRepo) TaskNameExists(context.Context, string, uint) (bool, error) { return false, nil } -func (r *workerTaskRepo) FindTask(_ context.Context, id uint) (*biz.TimedTask, error) { +func (r *workerTaskRepo) FindTask(_ context.Context, id uint) (*system.TimedTask, error) { r.mu.Lock() defer r.mu.Unlock() for _, item := range r.items { @@ -39,31 +38,31 @@ func (r *workerTaskRepo) FindTask(_ context.Context, id uint) (*biz.TimedTask, e } return nil, errors.New("task not found") } -func (r *workerTaskRepo) ListTasks(context.Context, int, int, *biz.TimedTask) ([]*biz.TimedTask, int64, error) { +func (r *workerTaskRepo) ListTasks(context.Context, int, int, *system.TimedTask) ([]*system.TimedTask, int64, error) { r.mu.Lock() defer r.mu.Unlock() if r.listErr != nil { return nil, 0, r.listErr } - items := make([]*biz.TimedTask, 0, len(r.items)) + items := make([]*system.TimedTask, 0, len(r.items)) for _, item := range r.items { items = append(items, cloneTimedTask(item)) } return items, int64(len(items)), nil } -func (r *workerTaskRepo) RecordTaskLog(_ context.Context, value *biz.TimedTaskLog) error { +func (r *workerTaskRepo) RecordTaskLog(_ context.Context, value *system.TimedTaskLog) error { if r.logs != nil { copy := *value r.logs <- © } return nil } -func (r *workerTaskRepo) ListTaskLogs(context.Context, int, int, uint, string) ([]*biz.TimedTaskLog, int64, error) { +func (r *workerTaskRepo) ListTaskLogs(context.Context, int, int, uint, string) ([]*system.TimedTaskLog, int64, error) { return nil, 0, nil } func newTestTaskScheduler(repo *workerTaskRepo) *TaskScheduler { - tasks := biz.NewTaskUsecase(repo) + tasks := system.NewTaskUsecase(repo) executor := &TaskExecutor{tasks: tasks} logger := slog.New(slog.NewTextHandler(io.Discard, nil)) return NewTaskScheduler(tasks, nil, executor, logger) @@ -72,7 +71,7 @@ func newTestTaskScheduler(repo *workerTaskRepo) *TaskScheduler { func TestReloadPreservesExistingScheduleOnPreparationFailure(t *testing.T) { repo := &workerTaskRepo{} scheduler := newTestTaskScheduler(repo) - old := &biz.TimedTask{ID: 1, Name: "old", Spec: "0 0 * * *", Enabled: true} + old := &system.TimedTask{ID: 1, Name: "old", Spec: "0 0 * * *", Enabled: true} if err := scheduler.Schedule(old); err != nil { t.Fatalf("schedule old task: %v", err) } @@ -90,7 +89,7 @@ func TestReloadPreservesExistingScheduleOnPreparationFailure(t *testing.T) { t.Run("invalid cron", func(t *testing.T) { repo.mu.Lock() repo.listErr = nil - repo.items = []*biz.TimedTask{{ID: 2, Name: "invalid", Spec: "bad cron", Enabled: true}} + repo.items = []*system.TimedTask{{ID: 2, Name: "invalid", Spec: "bad cron", Enabled: true}} repo.mu.Unlock() if err := scheduler.Reload(context.Background()); err == nil { t.Fatal("Reload() error = nil, want cron error") @@ -122,7 +121,7 @@ func TestConcurrentScheduleKeepsSingleEntry(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - if err := scheduler.Schedule(&biz.TimedTask{ID: 7, Name: "same", Spec: "0 0 * * *", Enabled: true}); err != nil { + if err := scheduler.Schedule(&system.TimedTask{ID: 7, Name: "same", Spec: "0 0 * * *", Enabled: true}); err != nil { t.Errorf("Schedule() error = %v", err) } }() @@ -136,15 +135,15 @@ func TestManualTriggerSkipsOverlappingExecution(t *testing.T) { started := make(chan struct{}, 2) release := make(chan struct{}) var calls atomic.Int32 - biz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { calls.Add(1) started <- struct{}{} <-release return nil }) - task := &biz.TimedTask{ID: 9, Name: "single", ExecutorType: biz.TaskExecutorMethod, MethodName: methodName} - repo := &workerTaskRepo{items: []*biz.TimedTask{task}, logs: make(chan *biz.TimedTaskLog, 2)} + task := &system.TimedTask{ID: 9, Name: "single", ExecutorType: system.TaskExecutorMethod, MethodName: methodName} + repo := &workerTaskRepo{items: []*system.TimedTask{task}, logs: make(chan *system.TimedTaskLog, 2)} scheduler := newTestTaskScheduler(repo) if err := scheduler.TriggerID(context.Background(), task.ID); err != nil { t.Fatalf("first manual trigger failed: %v", err) @@ -193,15 +192,15 @@ func TestAutomaticAndManualTriggersShareOverlapGate(t *testing.T) { started := make(chan struct{}, 1) release := make(chan struct{}) var calls atomic.Int32 - biz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { calls.Add(1) started <- struct{}{} <-release return nil }) - task := &biz.TimedTask{ID: 10, Name: "shared-gate", ExecutorType: biz.TaskExecutorMethod, MethodName: methodName} - repo := &workerTaskRepo{logs: make(chan *biz.TimedTaskLog, 1)} + task := &system.TimedTask{ID: 10, Name: "shared-gate", ExecutorType: system.TaskExecutorMethod, MethodName: methodName} + repo := &workerTaskRepo{logs: make(chan *system.TimedTaskLog, 1)} scheduler := newTestTaskScheduler(repo) if !scheduler.dispatch(task, "auto", true) { t.Fatal("automatic dispatch was rejected while idle") diff --git a/internal/worker/task_scheduler.go b/internal/worker/task_scheduler.go index 66d3656..1e66e64 100644 --- a/internal/worker/task_scheduler.go +++ b/internal/worker/task_scheduler.go @@ -5,17 +5,17 @@ import ( "encoding/json" "errors" "fmt" + "kra/internal/biz/system" "log/slog" "sync" "time" "github.com/robfig/cron/v3" - "kra/internal/biz" ) type TaskScheduler struct { - tasks *biz.TaskUsecase - authorities *biz.AuthorityUsecase + tasks *system.TaskUsecase + authorities *system.AuthorityUsecase executor *TaskExecutor logger *slog.Logger standard *cron.Cron @@ -38,13 +38,13 @@ type scheduledEntry struct { entry cron.EntryID } -func NewTaskScheduler(tasks *biz.TaskUsecase, authorities *biz.AuthorityUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler { +func NewTaskScheduler(tasks *system.TaskUsecase, authorities *system.AuthorityUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler { idle := make(chan struct{}) close(idle) return &TaskScheduler{tasks: tasks, authorities: authorities, executor: executor, logger: logger.With("mod", "timedTask"), standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, running: map[uint]struct{}{}, idle: idle, subscribers: map[uint]map[chan []byte]struct{}{}} } -func NewTaskRuntime(scheduler *TaskScheduler) biz.TaskRuntime { return scheduler } +func NewTaskRuntime(scheduler *TaskScheduler) system.TaskRuntime { return scheduler } func (s *TaskScheduler) Start(ctx context.Context) error { runContext, cancel := context.WithCancel(ctx) @@ -116,7 +116,7 @@ func (s *TaskScheduler) Reload(ctx context.Context) error { return err } type preparedTask struct { - task *biz.TimedTask + task *system.TimedTask schedule cron.Schedule } prepared := make([]preparedTask, 0, len(items)) @@ -181,7 +181,7 @@ func (s *TaskScheduler) finishRun(id uint) { } } -func (s *TaskScheduler) dispatch(task *biz.TimedTask, trigger string, async bool) bool { +func (s *TaskScheduler) dispatch(task *system.TimedTask, trigger string, async bool) bool { ctx, ok := s.beginRun(task.ID) if !ok { s.logger.Warn("timed task skipped because it is already running or the scheduler is stopping", "task_id", task.ID, "task_name", task.Name, "trigger_type", trigger) @@ -199,7 +199,7 @@ func (s *TaskScheduler) dispatch(task *biz.TimedTask, trigger string, async bool return true } -func (s *TaskScheduler) run(ctx context.Context, task *biz.TimedTask, trigger string) { +func (s *TaskScheduler) run(ctx context.Context, task *system.TimedTask, trigger string) { log := s.executor.Run(ctx, task, trigger) attributes := []any{"task_id", log.TaskID, "task_name", log.TaskName, "trigger_type", log.TriggerType, "status", log.Status, "duration_ms", log.DurationMS, "started_at", log.StartedAt, "finished_at", log.FinishedAt} if log.ErrorMsg != "" { @@ -222,7 +222,7 @@ func (s *TaskScheduler) run(ctx context.Context, task *biz.TimedTask, trigger st } } -func (s *TaskScheduler) Schedule(task *biz.TimedTask) error { +func (s *TaskScheduler) Schedule(task *system.TimedTask) error { if task == nil { return errors.New("定时任务不能为空") } @@ -244,21 +244,21 @@ func (s *TaskScheduler) Schedule(task *biz.TimedTask) error { return nil } -func parseTaskSchedule(task *biz.TimedTask) (cron.Schedule, error) { +func parseTaskSchedule(task *system.TimedTask) (cron.Schedule, error) { if task.WithSeconds { return cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor).Parse(task.Spec) } return cron.ParseStandard(task.Spec) } -func cloneTimedTask(task *biz.TimedTask) *biz.TimedTask { +func cloneTimedTask(task *system.TimedTask) *system.TimedTask { copy := *task copy.Params = append([]byte(nil), task.Params...) copy.HTTPHeader = append([]byte(nil), task.HTTPHeader...) return © } -func (s *TaskScheduler) scheduleLocked(task *biz.TimedTask, schedule cron.Schedule) { +func (s *TaskScheduler) scheduleLocked(task *system.TimedTask, schedule cron.Schedule) { run := func() { s.dispatch(task, "auto", false) } @@ -317,7 +317,7 @@ func (s *TaskScheduler) NextRuns() map[uint]time.Time { return out } -func (s *TaskScheduler) Trigger(task *biz.TimedTask) bool { +func (s *TaskScheduler) Trigger(task *system.TimedTask) bool { if task == nil { return false }