优化结构
This commit is contained in:
parent
d987244d0e
commit
b27a66e9d3
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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、数据库和迁移
|
||||
|
|
|
|||
|
|
@ -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 协议落地。
|
||||
|
||||
## 其他目录审查
|
||||
|
||||
|
|
|
|||
|
|
@ -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 就能自动发现。
|
||||
|
|
|
|||
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import "context"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import "context"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import "context"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/go-kratos/kratos/v3/errors"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField {
|
||||
if fieldType == "" {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"go.einride.tech/aip/filtering"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import "context"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import platformtask "kra/pkg/task"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package biz
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: "测试支付渠道"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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} }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(""),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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} }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue