优化结构
This commit is contained in:
+3
-3
@@ -9,11 +9,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"kra/internal/config"
|
"kra/internal/config"
|
||||||
|
"kra/internal/logging"
|
||||||
"kra/internal/server/router"
|
"kra/internal/server/router"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"kra/internal/worker"
|
"kra/internal/worker"
|
||||||
"kra/pkg/logging"
|
|
||||||
"kra/pkg/module"
|
"kra/pkg/module"
|
||||||
"kra/pkg/mq"
|
"kra/pkg/mq"
|
||||||
platformtask "kra/pkg/task"
|
platformtask "kra/pkg/task"
|
||||||
@@ -62,7 +62,7 @@ func buildRuntime(registry *platformtask.Registry, systemRoutes *router.Routes,
|
|||||||
return systemRoutes
|
return systemRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *service.AuditRecorder, loggerControl *logging.ReloadableLogger, _ mq.Client) *kratos.App {
|
func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *systemservice.AuditRecorder, loggerControl *logging.ReloadableLogger, _ mq.Client) *kratos.App {
|
||||||
if audit != nil && loggerControl != nil {
|
if audit != nil && loggerControl != nil {
|
||||||
loggerControl.SetErrorSink(logging.ErrorSinkFunc(func(ctx context.Context, entry logging.ErrorEntry) error {
|
loggerControl.SetErrorSink(logging.ErrorSinkFunc(func(ctx context.Context, entry logging.ErrorEntry) error {
|
||||||
return audit.CreateErrorRequest(ctx, &dto.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID})
|
return audit.CreateErrorRequest(ctx, &dto.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID})
|
||||||
|
|||||||
+3
-2
@@ -15,14 +15,15 @@ import (
|
|||||||
"kra/internal/initialize"
|
"kra/internal/initialize"
|
||||||
"kra/internal/integration"
|
"kra/internal/integration"
|
||||||
"kra/internal/integration/cache"
|
"kra/internal/integration/cache"
|
||||||
|
"kra/internal/logging"
|
||||||
"kra/internal/modules"
|
"kra/internal/modules"
|
||||||
"kra/internal/server"
|
"kra/internal/server"
|
||||||
"kra/internal/server/handler"
|
"kra/internal/server/handler"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
"kra/internal/server/router"
|
"kra/internal/server/router"
|
||||||
"kra/internal/service"
|
"kra/internal/service"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"kra/internal/worker"
|
"kra/internal/worker"
|
||||||
"kra/pkg/logging"
|
|
||||||
platformtask "kra/pkg/task"
|
platformtask "kra/pkg/task"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v3"
|
"github.com/go-kratos/kratos/v3"
|
||||||
@@ -44,7 +45,7 @@ func wireApp(*config.Server, *config.Store, *slog.Logger, *logging.ReloadableLog
|
|||||||
initialize.ProviderSet,
|
initialize.ProviderSet,
|
||||||
wire.Bind(new(initialize.Backend), new(*data.Data)),
|
wire.Bind(new(initialize.Backend), new(*data.Data)),
|
||||||
wire.Bind(new(cache.RedisProvider), new(*data.Data)),
|
wire.Bind(new(cache.RedisProvider), new(*data.Data)),
|
||||||
wire.Bind(new(middleware.TokenAuthenticator), new(*service.AuthService)),
|
wire.Bind(new(middleware.TokenAuthenticator), new(*systemservice.AuthService)),
|
||||||
wire.Bind(new(taskbiz.TaskMethodRegistry), new(*platformtask.Registry)),
|
wire.Bind(new(taskbiz.TaskMethodRegistry), new(*platformtask.Registry)),
|
||||||
biz.ProviderSet,
|
biz.ProviderSet,
|
||||||
service.ProviderSet,
|
service.ProviderSet,
|
||||||
|
|||||||
Generated
+64
-55
@@ -9,13 +9,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/go-kratos/kratos/v3"
|
"github.com/go-kratos/kratos/v3"
|
||||||
integration3 "kra/internal/biz/integration"
|
integration3 "kra/internal/biz/integration"
|
||||||
payment2 "kra/internal/biz/payment"
|
payment3 "kra/internal/biz/payment"
|
||||||
system2 "kra/internal/biz/system"
|
system2 "kra/internal/biz/system"
|
||||||
task2 "kra/internal/biz/task"
|
task2 "kra/internal/biz/task"
|
||||||
"kra/internal/config"
|
"kra/internal/config"
|
||||||
"kra/internal/data"
|
"kra/internal/data"
|
||||||
"kra/internal/data/integration"
|
"kra/internal/data/integration"
|
||||||
"kra/internal/data/payment"
|
payment2 "kra/internal/data/payment"
|
||||||
"kra/internal/data/system"
|
"kra/internal/data/system"
|
||||||
"kra/internal/data/task"
|
"kra/internal/data/task"
|
||||||
"kra/internal/initialize"
|
"kra/internal/initialize"
|
||||||
@@ -23,18 +23,19 @@ import (
|
|||||||
"kra/internal/integration/cache"
|
"kra/internal/integration/cache"
|
||||||
"kra/internal/integration/email"
|
"kra/internal/integration/email"
|
||||||
"kra/internal/integration/mq"
|
"kra/internal/integration/mq"
|
||||||
|
"kra/internal/integration/payment"
|
||||||
"kra/internal/integration/storage"
|
"kra/internal/integration/storage"
|
||||||
"kra/internal/integration/websocket"
|
"kra/internal/integration/websocket"
|
||||||
|
"kra/internal/logging"
|
||||||
"kra/internal/modules"
|
"kra/internal/modules"
|
||||||
"kra/internal/server"
|
"kra/internal/server"
|
||||||
"kra/internal/server/handler"
|
"kra/internal/server/handler"
|
||||||
"kra/internal/server/router"
|
"kra/internal/server/router"
|
||||||
integration4 "kra/internal/service/integration"
|
integration4 "kra/internal/service/integration"
|
||||||
payment3 "kra/internal/service/payment"
|
payment4 "kra/internal/service/payment"
|
||||||
system3 "kra/internal/service/system"
|
system3 "kra/internal/service/system"
|
||||||
task3 "kra/internal/service/task"
|
task3 "kra/internal/service/task"
|
||||||
"kra/internal/worker"
|
"kra/internal/worker"
|
||||||
"kra/pkg/logging"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
|||||||
authorityAccessRepo := system.NewAuthorityAccessRepo(dataData)
|
authorityAccessRepo := system.NewAuthorityAccessRepo(dataData)
|
||||||
apiRepo := system.NewAPIRepo(dataData)
|
apiRepo := system.NewAPIRepo(dataData)
|
||||||
accessControlUsecase := system2.NewAccessControlUsecase(authorityAccessRepo, apiRepo)
|
accessControlUsecase := system2.NewAccessControlUsecase(authorityAccessRepo, apiRepo)
|
||||||
v := system3.NewAccessControlService(accessControlUsecase)
|
accessControlService := system3.NewAccessControlService(accessControlUsecase)
|
||||||
userRepo := system.NewUserRepo(dataData)
|
userRepo := system.NewUserRepo(dataData)
|
||||||
userUsecase := system2.NewUserUsecase(userRepo)
|
userUsecase := system2.NewUserUsecase(userRepo)
|
||||||
securityRepo := system.NewSecurityRepo(dataData)
|
securityRepo := system.NewSecurityRepo(dataData)
|
||||||
@@ -71,98 +72,106 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
|||||||
auditRecordRepo := system.NewAuditRecorderRepo(dataData)
|
auditRecordRepo := system.NewAuditRecorderRepo(dataData)
|
||||||
authenticationUsecase := system2.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo)
|
authenticationUsecase := system2.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo)
|
||||||
authService := system3.NewAuthService(authenticationUsecase)
|
authService := system3.NewAuthService(authenticationUsecase)
|
||||||
v2 := system3.NewSecurityService(securityUsecase)
|
securityService := system3.NewSecurityService(securityUsecase)
|
||||||
auditRecorderUsecase := system2.NewAuditRecorderUsecase(auditRecordRepo)
|
auditRecorderUsecase := system2.NewAuditRecorderUsecase(auditRecordRepo)
|
||||||
v3 := system3.NewAuditRecorder(auditRecorderUsecase)
|
auditRecorder := system3.NewAuditRecorder(auditRecorderUsecase)
|
||||||
registry := taskRegistry()
|
registry := taskRegistry()
|
||||||
authorityUsecase := system2.NewAuthorityUsecase(authorityAccessRepo)
|
authorityUsecase := system2.NewAuthorityUsecase(authorityAccessRepo)
|
||||||
v4 := system3.NewAuthorityService(authorityUsecase)
|
authorityService := system3.NewAuthorityService(authorityUsecase)
|
||||||
authority := handler.NewAuthority(v4)
|
authority := handler.NewAuthority(authorityService)
|
||||||
menuRepo := system.NewMenuRepo(dataData)
|
menuRepo := system.NewMenuRepo(dataData)
|
||||||
menuUsecase := system2.NewMenuUsecase(menuRepo)
|
menuUsecase := system2.NewMenuUsecase(menuRepo)
|
||||||
v5 := system3.NewMenuService(menuUsecase)
|
menuService := system3.NewMenuService(menuUsecase)
|
||||||
menu := handler.NewMenu(v5)
|
menu := handler.NewMenu(menuService)
|
||||||
apiUsecase := system2.NewAPIUsecase(apiRepo)
|
apiUsecase := system2.NewAPIUsecase(apiRepo)
|
||||||
v6 := system3.NewAPIService(apiUsecase, runtimeSettings)
|
apiService := system3.NewAPIService(apiUsecase, runtimeSettings)
|
||||||
api := handler.NewAPI(v6)
|
api := handler.NewAPI(apiService)
|
||||||
permissionRepo := system.NewPermissionRepo(dataData)
|
permissionRepo := system.NewPermissionRepo(dataData)
|
||||||
permissionUsecase := system2.NewPermissionUsecase(permissionRepo)
|
permissionUsecase := system2.NewPermissionUsecase(permissionRepo)
|
||||||
v7 := system3.NewPermissionService(permissionUsecase)
|
permissionService := system3.NewPermissionService(permissionUsecase)
|
||||||
permission := handler.NewPermission(v7)
|
permission := handler.NewPermission(permissionService)
|
||||||
departmentRepo := system.NewDepartmentRepo(dataData)
|
departmentRepo := system.NewDepartmentRepo(dataData)
|
||||||
departmentUsecase := system2.NewDepartmentUsecase(departmentRepo)
|
departmentUsecase := system2.NewDepartmentUsecase(departmentRepo)
|
||||||
v8 := system3.NewDepartmentService(departmentUsecase)
|
departmentService := system3.NewDepartmentService(departmentUsecase)
|
||||||
positionRepo := system.NewPositionRepo(dataData)
|
positionRepo := system.NewPositionRepo(dataData)
|
||||||
positionUsecase := system2.NewPositionUsecase(positionRepo)
|
positionUsecase := system2.NewPositionUsecase(positionRepo)
|
||||||
v9 := system3.NewPositionService(positionUsecase)
|
positionService := system3.NewPositionService(positionUsecase)
|
||||||
organization := handler.NewOrganization(v8, v9)
|
organization := handler.NewOrganization(departmentService, positionService)
|
||||||
announcementRepo := system.NewAnnouncementRepo(dataData)
|
announcementRepo := system.NewAnnouncementRepo(dataData)
|
||||||
announcementUsecase := system2.NewAnnouncementUsecase(announcementRepo)
|
announcementUsecase := system2.NewAnnouncementUsecase(announcementRepo)
|
||||||
v10 := system3.NewAnnouncementService(announcementUsecase)
|
announcementService := system3.NewAnnouncementService(announcementUsecase)
|
||||||
announcement := handler.NewAnnouncement(v10)
|
announcement := handler.NewAnnouncement(announcementService)
|
||||||
emailRepo := email.NewEmailRepo(store)
|
emailRepo := email.NewEmailRepo(store)
|
||||||
emailUsecase := system2.NewEmailUsecase(emailRepo)
|
emailUsecase := system2.NewEmailUsecase(emailRepo)
|
||||||
v11 := system3.NewEmailService(emailUsecase)
|
emailService := system3.NewEmailService(emailUsecase)
|
||||||
handlerEmail := handler.NewEmail(v11)
|
handlerEmail := handler.NewEmail(emailService)
|
||||||
paymentConfigReader := integration.NewPaymentConfigReader(dataData)
|
paymentConfigReader := integration.NewPaymentConfigReader(dataData)
|
||||||
paymentRepo := payment.NewPaymentRepo(dataData, paymentConfigReader)
|
factory := payment.NewFactory()
|
||||||
paymentOrderRepo := payment.NewPaymentOrderRepo(dataData)
|
paymentRepo := payment2.NewPaymentRepo(dataData, paymentConfigReader, factory)
|
||||||
paymentUsecase := payment2.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger)
|
paymentOrderRepo := payment2.NewPaymentOrderRepo(dataData)
|
||||||
v12 := payment3.NewPaymentService(paymentUsecase)
|
paymentHooks := payment3.NewPaymentHooks()
|
||||||
handlerPayment := handler.NewPayment(v12)
|
paymentOrderSourceRegistry := payment3.NewPaymentOrderSourceRegistry()
|
||||||
|
paymentFulfillmentRegistry := payment3.NewPaymentFulfillmentRegistry()
|
||||||
|
paymentUsecase, err := payment3.NewConfiguredPaymentUsecase(paymentRepo, paymentOrderRepo, paymentHooks, paymentOrderSourceRegistry, paymentFulfillmentRegistry, logger)
|
||||||
|
if err != nil {
|
||||||
|
cleanup()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
paymentService := payment4.NewPaymentService(paymentUsecase)
|
||||||
|
handlerPayment := handler.NewPayment(paymentService)
|
||||||
taskRepo := task.NewTaskRepo(dataData)
|
taskRepo := task.NewTaskRepo(dataData)
|
||||||
taskUsecase := task2.NewTaskUsecaseWithRegistry(taskRepo, registry)
|
taskUsecase := task2.NewTaskUsecaseWithRegistry(taskRepo, registry)
|
||||||
taskExecutor := worker.NewTaskExecutorWithRegistry(taskUsecase, registry)
|
taskExecutor := worker.NewTaskExecutorWithRegistry(taskUsecase, registry)
|
||||||
taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger)
|
taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger)
|
||||||
taskRuntime := worker.NewTaskRuntime(taskScheduler)
|
taskRuntime := worker.NewTaskRuntime(taskScheduler)
|
||||||
taskApplicationUsecase := task2.NewTaskApplicationUsecase(taskUsecase, taskRuntime)
|
taskApplicationUsecase := task2.NewTaskApplicationUsecase(taskUsecase, taskRuntime)
|
||||||
v13 := task3.NewTaskService(taskApplicationUsecase)
|
taskService := task3.NewTaskService(taskApplicationUsecase)
|
||||||
handlerTask := handler.NewTask(v13)
|
handlerTask := handler.NewTask(taskService)
|
||||||
mediaRepo := system.NewMediaRepo(dataData)
|
mediaRepo := system.NewMediaRepo(dataData)
|
||||||
mediaUsecase := system2.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings)
|
mediaUsecase := system2.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings)
|
||||||
v14 := system3.NewMediaService(mediaUsecase, runtimeSettings)
|
mediaService := system3.NewMediaService(mediaUsecase, runtimeSettings)
|
||||||
media := handler.NewMedia(v14)
|
media := handler.NewMedia(mediaService)
|
||||||
auditQueryRepo := system.NewAuditRepo(dataData)
|
auditQueryRepo := system.NewAuditRepo(dataData)
|
||||||
auditUsecase := system2.NewAuditUsecase(auditQueryRepo)
|
auditUsecase := system2.NewAuditUsecase(auditQueryRepo)
|
||||||
v15 := system3.NewAuditService(auditUsecase)
|
auditService := system3.NewAuditService(auditUsecase)
|
||||||
logFileRepo := system.NewLogFileRepo(dataData)
|
logFileRepo := system.NewLogFileRepo(dataData)
|
||||||
logViewerUsecase := system2.NewLogViewerUsecase(logFileRepo)
|
logViewerUsecase := system2.NewLogViewerUsecase(logFileRepo)
|
||||||
v16 := system3.NewLogViewerService(logViewerUsecase)
|
logViewerService := system3.NewLogViewerService(logViewerUsecase)
|
||||||
audit := handler.NewAudit(v15, v3, v16, logger)
|
audit := handler.NewAudit(auditService, auditRecorder, logViewerService, logger)
|
||||||
exportRepo := system.NewExportRepo(dataData)
|
exportRepo := system.NewExportRepo(dataData)
|
||||||
exportUsecase := system2.NewExportUsecase(exportRepo)
|
exportUsecase := system2.NewExportUsecase(exportRepo)
|
||||||
v17 := system3.NewExportService(exportUsecase, systemCache)
|
exportService := system3.NewExportService(exportUsecase, systemCache)
|
||||||
export := handler.NewExport(v17)
|
export := handler.NewExport(exportService)
|
||||||
versionRepo := system.NewVersionRepo(dataData)
|
versionRepo := system.NewVersionRepo(dataData)
|
||||||
versionUsecase := system2.NewVersionUsecase(versionRepo)
|
versionUsecase := system2.NewVersionUsecase(versionRepo)
|
||||||
v18 := system3.NewVersionService(versionUsecase)
|
versionService := system3.NewVersionService(versionUsecase)
|
||||||
version := handler.NewVersion(v18)
|
version := handler.NewVersion(versionService)
|
||||||
dictionaryRepo := system.NewDictionaryRepo(dataData)
|
dictionaryRepo := system.NewDictionaryRepo(dataData)
|
||||||
dictionaryUsecase := system2.NewDictionaryUsecase(dictionaryRepo)
|
dictionaryUsecase := system2.NewDictionaryUsecase(dictionaryRepo)
|
||||||
v19 := system3.NewDictionaryService(dictionaryUsecase)
|
dictionaryService := system3.NewDictionaryService(dictionaryUsecase)
|
||||||
dictionary := handler.NewDictionary(v19)
|
dictionary := handler.NewDictionary(dictionaryService)
|
||||||
parameterRepo := system.NewParameterRepo(dataData)
|
parameterRepo := system.NewParameterRepo(dataData)
|
||||||
parameterUsecase := system2.NewParameterUsecase(parameterRepo)
|
parameterUsecase := system2.NewParameterUsecase(parameterRepo)
|
||||||
v20 := system3.NewParameterService(parameterUsecase)
|
parameterService := system3.NewParameterService(parameterUsecase)
|
||||||
parameter := handler.NewParameter(v20)
|
parameter := handler.NewParameter(parameterService)
|
||||||
v21 := system3.NewTokenService(tokenUsecase, tokenIssuer)
|
tokenService := system3.NewTokenService(tokenUsecase, tokenIssuer)
|
||||||
apiToken := handler.NewAPIToken(v21)
|
apiToken := handler.NewAPIToken(tokenService)
|
||||||
initializationRepo := initialize.NewRepo(dataData, catalog)
|
initializationRepo := initialize.NewRepo(dataData, catalog)
|
||||||
taskReloader := worker.NewTaskReloader(taskScheduler)
|
taskReloader := worker.NewTaskReloader(taskScheduler)
|
||||||
systemConfigUsecase := system2.NewSystemConfigUsecase(initializationRepo, taskReloader)
|
systemConfigUsecase := system2.NewSystemConfigUsecase(initializationRepo, taskReloader)
|
||||||
v22 := system3.NewSystemConfigService(systemConfigUsecase, runtimeSettings)
|
systemConfigService := system3.NewSystemConfigService(systemConfigUsecase, runtimeSettings)
|
||||||
systemConfig := handler.NewSystemConfig(v22, v2)
|
systemConfig := handler.NewSystemConfig(systemConfigService, securityService)
|
||||||
public := handler.NewPublic(authService, v22, v2)
|
public := handler.NewPublic(authService, systemConfigService, securityService)
|
||||||
v23 := system3.NewUserService(userUsecase, v2)
|
userService := system3.NewUserService(userUsecase, securityService)
|
||||||
user := handler.NewUser(v23, authService)
|
user := handler.NewUser(userService, authService)
|
||||||
navigation := handler.NewNavigation(v23)
|
navigation := handler.NewNavigation(userService)
|
||||||
session := handler.NewSession(v21)
|
session := handler.NewSession(tokenService)
|
||||||
integrationConfigRepo := integration.NewIntegrationConfigRepo(dataData)
|
integrationConfigRepo := integration.NewIntegrationConfigRepo(dataData)
|
||||||
runtimeconfigStore := data.NewIntegrationRuntime(dataData)
|
runtimeconfigStore := data.NewIntegrationRuntime(dataData)
|
||||||
connectivityTester := integration2.NewConnectivityTester(runtimeconfigStore)
|
connectivityTester := integration2.NewConnectivityTester(runtimeconfigStore)
|
||||||
integrationConfigUsecase := integration3.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester)
|
integrationConfigUsecase := integration3.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester)
|
||||||
v24 := integration4.NewIntegrationConfigService(integrationConfigUsecase)
|
integrationConfigService := integration4.NewIntegrationConfigService(integrationConfigUsecase)
|
||||||
integrationConfig := handler.NewIntegrationConfig(v24)
|
integrationConfig := handler.NewIntegrationConfig(integrationConfigService)
|
||||||
set := &handler.Set{
|
set := &handler.Set{
|
||||||
Authority: authority,
|
Authority: authority,
|
||||||
Menu: menu,
|
Menu: menu,
|
||||||
@@ -197,7 +206,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
|||||||
cleanup()
|
cleanup()
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
engine := server.NewGinEngineWithRuntime(store, v, authService, v2, v3, logger, string2, routeRegistrar, websocketServer)
|
engine := server.NewGinEngineWithRuntime(store, accessControlService, authService, securityService, auditRecorder, logger, string2, routeRegistrar, websocketServer)
|
||||||
httpServer := server.NewGinServer(configServer, engine)
|
httpServer := server.NewGinServer(configServer, engine)
|
||||||
mqReloadable, cleanup3, err := mq.New(runtimeconfigStore, logger)
|
mqReloadable, cleanup3, err := mq.New(runtimeconfigStore, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -205,7 +214,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
|||||||
cleanup()
|
cleanup()
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
app := newApp(logger, httpServer, taskScheduler, v3, reloadableLogger, mqReloadable)
|
app := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger, mqReloadable)
|
||||||
return app, func() {
|
return app, func() {
|
||||||
cleanup3()
|
cleanup3()
|
||||||
cleanup2()
|
cleanup2()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
| stringOr;normalizePayPalOrderState;paypalOrderTradeNo;nestedString/amountFromDecimalField(shim 连调用者都没有) | wechat_v2.go:584、paypal.go:651,388-391、result.go:98,148-155 |
|
| stringOr;normalizePayPalOrderState;paypalOrderTradeNo;nestedString/amountFromDecimalField(shim 连调用者都没有) | wechat_v2.go:584、paypal.go:651,388-391、result.go:98,148-155 |
|
||||||
| RegisterAll 仅测试调用;TaskScheduler.Trigger(task) 可未导出(仅 TriggerID 内部用) | pkg/task/registry.go:39;worker/task_scheduler.go:425 |
|
| RegisterAll 仅测试调用;TaskScheduler.Trigger(task) 可未导出(仅 TriggerID 内部用) | pkg/task/registry.go:39;worker/task_scheduler.go:425 |
|
||||||
| 仅测试调用:decryptWechatV3、alipaySignContent、validatePaymentConfig、wechatV2Sign(可移测试文件) | wechat_v3.go:600-605、alipay.go:572-585、data/payment/payment.go:551-553、wechat_v2.go:47 |
|
| 仅测试调用:decryptWechatV3、alipaySignContent、validatePaymentConfig、wechatV2Sign(可移测试文件) | wechat_v3.go:600-605、alipay.go:572-585、data/payment/payment.go:551-553、wechat_v2.go:47 |
|
||||||
| P1-3 修复残留 micro-shim:result.go:58-64 与 payment_helpers.go:5-11 仍保留同名本地包装转发 paymentutil | integration/payment/result.go;data/payment/payment_helpers.go |
|
| P1-3 修复残留 micro-shim:result.go:58-64 与 payment_helpers.go:5-11 仍保留同名本地包装转发 paymentkit | integration/payment/result.go;data/payment/payment_helpers.go |
|
||||||
|
|
||||||
## 三、重复实现 / 双份维护
|
## 三、重复实现 / 双份维护
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
| D-17 | data/payment:callbackFields/first 整函数复制且已分叉(integration 版 Content-Type 大小写不敏感遍历 vs data 版直接下标,行为漂移隐患);values() 与 testRow() 近重复;渠道"免 notify_url"同义词表在 data 层重抄一份与各适配器 createMethod 表双份维护 | data/payment/payment.go:507-540,31-50,273-292,394-425 vs integration/payment/result.go:14-56 |
|
| D-17 | data/payment:callbackFields/first 整函数复制且已分叉(integration 版 Content-Type 大小写不敏感遍历 vs data 版直接下标,行为漂移隐患);values() 与 testRow() 近重复;渠道"免 notify_url"同义词表在 data 层重抄一份与各适配器 createMethod 表双份维护 | data/payment/payment.go:507-540,31-50,273-292,394-425 vs integration/payment/result.go:14-56 |
|
||||||
| D-18 | service:authorityResponse 与 convertAuthority 逐字重复;"单条 DTO helper + for-append"样板 9 处;"Request 包装+Filter 包装+裸方法"三重入口家族(audit/parameter/export/dictionary/media);user 空对象兜底三连 | service/system/authority.go:30-36 vs user_conversion.go:8-14 等 |
|
| D-18 | service:authorityResponse 与 convertAuthority 逐字重复;"单条 DTO helper + for-append"样板 9 处;"Request 包装+Filter 包装+裸方法"三重入口家族(audit/parameter/export/dictionary/media);user 空对象兜底三连 | service/system/authority.go:30-36 vs user_conversion.go:8-14 等 |
|
||||||
| D-19 | payment 金额守恒校验四处重复(biz validatePaymentBreakdown + vendor + wechat_v3 + douyin 各一份) | biz/payment/payment.go:1112-1135;vendor.go:235-253;wechat_v3.go:555;douyin.go |
|
| D-19 | payment 金额守恒校验四处重复(biz validatePaymentBreakdown + vendor + wechat_v3 + douyin 各一份) | biz/payment/payment.go:1112-1135;vendor.go:235-253;wechat_v3.go:555;douyin.go |
|
||||||
| D-20 | 状态词汇归一三处维护(paymentutil.NormalizeStatus + 各 adapter + data/payment normalizeOrderPaymentStatus) | paymentkit/status.go:8-19;data/payment/payment_order.go:478 |
|
| D-20 | 状态词汇归一三处维护(paymentkit.NormalizeStatus + 各 adapter + data/payment normalizeOrderPaymentStatus) | paymentkit/status.go:8-19;data/payment/payment_order.go:478 |
|
||||||
|
|
||||||
## 四、过度分层:转发门面 / 透传壳 / 回调穿透
|
## 四、过度分层:转发门面 / 透传壳 / 回调穿透
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
| P-1 | pkg/httpx | internal/server/httpx | 消费者 100% 在 internal/server 7 文件;中文文案+x-token cookie 是本项目契约;logging/source.go:59 已预留该路径 marker | 7 文件 |
|
| P-1 | pkg/httpx | internal/server/httpx | 消费者 100% 在 internal/server 7 文件;中文文案+x-token cookie 是本项目契约;logging/source.go:59 已预留该路径 marker | 7 文件 |
|
||||||
| P-2 | pkg/logging | internal/logging | source.go:47-69 硬编码本仓库 internal 路径;zap.go:366-382 中文文案;模块路由硬编码服务日志查看器语义;AGENTS.md 声称的 internal/logging 不存在 | 约 5 文件 |
|
| P-2 | pkg/logging | internal/logging | source.go:47-69 硬编码本仓库 internal 路径;zap.go:366-382 中文文案;模块路由硬编码服务日志查看器语义;AGENTS.md 声称的 internal/logging 不存在 | 约 5 文件 |
|
||||||
| P-3 | pkg/module | internal/modules | Menu/API/Surface/TimedTask 是本项目模块系统契约;import gin;15 消费者全在本仓库 | 15 文件 |
|
| P-3 | pkg/module | internal/modules | Menu/API/Surface/TimedTask 是本项目模块系统契约;import gin;15 消费者全在本仓库 | 15 文件 |
|
||||||
| P-4 | pkg/paymentkit 渠道常量(provider.go) | internal/biz/payment 本体 | 常量归位并删 biz/payment/payment.go:20-43 的 17+2 个别名转发层;目录更名 paymentutil 对齐包名(当前目录名≠包名);通用工具(signing/json/amount/xml)可留 pkg 或并入 internal/utils | 约 8 文件 |
|
| P-4 | pkg/paymentkit 渠道常量(provider.go) | internal/biz/payment 本体 | 常量归位并删 biz/payment/payment.go:20-43 的 17+2 个别名转发层;目录更名 paymentkit 对齐包名(当前目录名≠包名);通用工具(signing/json/amount/xml)可留 pkg 或并入 internal/utils | 约 8 文件 |
|
||||||
| P-5 | pkg/mq + pkg/websocket(Hub) | 收缩或合并进 internal/integration/mq | 全链零业务消费者(见 2.2);保留 TestConfig 探测+基础驱动,删 Registry/Client()/namedClient/legacy API/Hub/ApplySubscriptions | 约 7 文件 |
|
| P-5 | pkg/mq + pkg/websocket(Hub) | 收缩或合并进 internal/integration/mq | 全链零业务消费者(见 2.2);保留 TestConfig 探测+基础驱动,删 Registry/Client()/namedClient/legacy API/Hub/ApplySubscriptions | 约 7 文件 |
|
||||||
| P-6 | integration/mq 与 integration/websocket 的重复 JSON map helper | internal/utils/jsonvalue | 上收去重(逐字符相同) | 2-3 文件 |
|
| P-6 | integration/mq 与 integration/websocket 的重复 JSON map helper | internal/utils/jsonvalue | 上收去重(逐字符相同) | 2-3 文件 |
|
||||||
| P-7 | storage:aws-sdk-v2 栈与 minio-go 栈双 S3 实现并存 | 统一 S3 兼容单栈(可选) | qiniu/aliyun/huawei/tencent 原生 SDK 均有 S3 兼容端点,可收敛删 4 实现+s3 死分支 | 5-6 文件 |
|
| P-7 | storage:aws-sdk-v2 栈与 minio-go 栈双 S3 实现并存 | 统一 S3 兼容单栈(可选) | qiniu/aliyun/huawei/tencent 原生 SDK 均有 S3 兼容端点,可收敛删 4 实现+s3 死分支 | 5-6 文件 |
|
||||||
@@ -225,6 +225,30 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 第四轮处置结果(2026-08-27)
|
||||||
|
|
||||||
|
本轮按 `2.1`、`2.3`、第三至第九节逐项反查。保持现有 HTTP 路径和 JSON 契约;`2.2` 及其交叉项 D-6、P-5、P-6、P-11、C-7、C-10、`namedClient/Client()` 明确排除。
|
||||||
|
|
||||||
|
### 已修复
|
||||||
|
|
||||||
|
- **2.1/2.3 死代码**:删除 Permission 三个旧按钮入口、APITokenRepo 旧禁用入口、UserRepo 两个冗余组合入口、FindMediaByHash、AuthorityMenuIDs、Email.Alert、Security.ActiveTokenMatches、MenuService.List、无消费者 service/DTO 包装及零散支付 helper;测试专用签名/解密 helper 移入 `_test.go`;`TaskScheduler.Trigger` 改为私有。
|
||||||
|
- **支付与分层**:data 不再直接 import integration/payment,改由 biz 声明 `PaymentAdapterFactory`、integration 实现;hooks/订单来源/发货注册表改为构造注入;渠道常量与回调 ACK 归 `biz/payment`,通用金额/JSON/签名/XML 工具归 `internal/paymentkit`。
|
||||||
|
- **正确性残留**:vendor 退款非 JSON或无业务状态时失败关闭;支付回调读体错误加入 Gin error chain;ErrAuthoritiesRequired 原样透传;版本导入与导入留痕改为单事务;Excel `[]byte` 区分数值和前导零文本;登录日志补 IP/时间筛选;任务描述复用模块元数据。
|
||||||
|
- **包归属与门面**:`pkg/httpx`→`internal/server/httpx`、`pkg/logging`→`internal/logging`、`pkg/paymentkit`→`internal/paymentkit`;handler/server 直接依赖具体 service 子包,根 service 仅保留 Wire 聚合;DataScope 回调注册移回 Data 生命周期。
|
||||||
|
- **其他减法**:删除 `RegisterAll`、`parseTemplateColumns`、PayPal/WeChat/vendor 等确认无调用函数;`NewGinEngine` 移为测试 helper。S3 构造器分支和 vendor SuperPay 等存在交叉或误报的条目未强行删除。
|
||||||
|
|
||||||
|
### 核实后保留
|
||||||
|
|
||||||
|
- APIToken 作废链、Permission SelectedButtons 链、AuditRecorder.CreateErrorRequest、vendor SuperPay、支付内部支付/退款扩展接口均有真实消费者或明确扩展职责,不按死代码删除。
|
||||||
|
- 标准 biz usecase、service transport 边界、initialize 编排、config.Store/runtimeconfig.Store 分离、`pkg/module`、`pkg/database`、`internal/data/provider` 属于既定分层契约,保留。
|
||||||
|
- F-1 保留 DTO↔DO 转换,只移除无意义转发;D-9、D-11、P-3、P-7、P-10、P-13、F-7、F-9、F-10、C-8、X-6、X-8、X-11 经核实不采用原建议。
|
||||||
|
|
||||||
|
### 部分完成/后续批次
|
||||||
|
|
||||||
|
- D-1~D-5、D-7~D-8、D-10、D-12~D-20、L-2~L-12、S-1~S-9、X-1~X-5、X-7、X-9~X-10 中的大范围统一 ListOptions、路由单一声明源、配置通道统一、middleware 全量去重和文件重组未在本轮强行完成;这些变更耦合面大,应拆分为独立批次并逐批验证。
|
||||||
|
- L-1 已消除 data→integration/payment 直接依赖,但支付连通性测试编排仍位于 data/payment,后续可迁至 biz 应用流程。
|
||||||
|
- F-4 handler/http 便捷门面和部分 payment micro-shim 暂留;外部契约不变,后续可在独立机械重构中清理。
|
||||||
|
|
||||||
## 已修复并经复查确认(第三轮逐条验证,2026-08-27)
|
## 已修复并经复查确认(第三轮逐条验证,2026-08-27)
|
||||||
|
|
||||||
> 以下条目经第三轮 5 路代理逐条核查属实(含编译验证通过),从上方问题清单移除,此处留痕。复查中发现的部分修复残留已回升为一节 R 系列条目。
|
> 以下条目经第三轮 5 路代理逐条核查属实(含编译验证通过),从上方问题清单移除,此处留痕。复查中发现的部分修复残留已回升为一节 R 系列条目。
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ require (
|
|||||||
github.com/go-kratos/kratos/v3 v3.0.0
|
github.com/go-kratos/kratos/v3 v3.0.0
|
||||||
github.com/go-pay/crypto v0.0.1
|
github.com/go-pay/crypto v0.0.1
|
||||||
github.com/go-pay/gopay v1.5.122
|
github.com/go-pay/gopay v1.5.122
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/google/wire v0.7.0
|
github.com/google/wire v0.7.0
|
||||||
@@ -32,6 +33,7 @@ require (
|
|||||||
github.com/redis/go-redis/v9 v9.7.0
|
github.com/redis/go-redis/v9 v9.7.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7
|
github.com/shirou/gopsutil/v4 v4.25.7
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
@@ -43,7 +45,6 @@ require (
|
|||||||
go.uber.org/zap/exp v0.3.0
|
go.uber.org/zap/exp v0.3.0
|
||||||
golang.org/x/crypto v0.53.0
|
golang.org/x/crypto v0.53.0
|
||||||
golang.org/x/sync v0.21.0
|
golang.org/x/sync v0.21.0
|
||||||
google.golang.org/protobuf v1.36.11
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/mysql v1.6.0
|
gorm.io/driver/mysql v1.6.0
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
@@ -109,7 +110,6 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/gofrs/flock v0.8.1 // indirect
|
github.com/gofrs/flock v0.8.1 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
@@ -154,7 +154,6 @@ require (
|
|||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/spf13/viper v1.21.0 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/thoas/go-funk v0.7.0 // indirect
|
github.com/thoas/go-funk v0.7.0 // indirect
|
||||||
github.com/tinylib/msgp v1.3.0 // indirect
|
github.com/tinylib/msgp v1.3.0 // indirect
|
||||||
@@ -185,6 +184,7 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 // indirect
|
||||||
google.golang.org/grpc v1.81.1 // indirect
|
google.golang.org/grpc v1.81.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
modernc.org/fileutil v1.0.0 // indirect
|
modernc.org/fileutil v1.0.0 // indirect
|
||||||
modernc.org/libc v1.22.5 // indirect
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2I
|
|||||||
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
||||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
@@ -213,8 +215,6 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
|
||||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -315,8 +315,6 @@ github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISe
|
|||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
|
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
|
||||||
github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
paymentutil "kra/pkg/paymentkit"
|
bizpayment "kra/internal/biz/payment"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -316,7 +316,7 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch provider {
|
switch provider {
|
||||||
case paymentutil.ProviderAlipayV3:
|
case bizpayment.PaymentAlipayV3:
|
||||||
for _, group := range []struct {
|
for _, group := range []struct {
|
||||||
label string
|
label string
|
||||||
keys []string
|
keys []string
|
||||||
@@ -330,21 +330,21 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er
|
|||||||
return fmt.Errorf("%s 缺少配置字段 %s", provider, group.label)
|
return fmt.Errorf("%s 缺少配置字段 %s", provider, group.label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case paymentutil.ProviderWechatV2:
|
case bizpayment.PaymentWechatV2:
|
||||||
if integrationFirst(values, "client_cert", "cert_pem", "apiclient_cert") == "" || integrationFirst(values, "client_key", "key_pem", "apiclient_key") == "" {
|
if integrationFirst(values, "client_cert", "cert_pem", "apiclient_cert") == "" || integrationFirst(values, "client_key", "key_pem", "apiclient_key") == "" {
|
||||||
return fmt.Errorf("%s 退款要求同时配置 client_cert 和 client_key", provider)
|
return fmt.Errorf("%s 退款要求同时配置 client_cert 和 client_key", provider)
|
||||||
}
|
}
|
||||||
case paymentutil.ProviderApple:
|
case bizpayment.PaymentApple:
|
||||||
if integrationInt64(values, "price_divisor", 0) <= 0 {
|
if integrationInt64(values, "price_divisor", 0) <= 0 {
|
||||||
if _, exists := values["price_divisors"].(map[string]any); !exists {
|
if _, exists := values["price_divisors"].(map[string]any); !exists {
|
||||||
return fmt.Errorf("%s 缺少配置字段 price_divisor 或 price_divisors", provider)
|
return fmt.Errorf("%s 缺少配置字段 price_divisor 或 price_divisors", provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case paymentutil.ProviderDouyin:
|
case bizpayment.PaymentDouyin:
|
||||||
if integrationFirst(values, "platform_serial_no", "platform_cert_serial") == "" {
|
if integrationFirst(values, "platform_serial_no", "platform_cert_serial") == "" {
|
||||||
return fmt.Errorf("%s 缺少配置字段 platform_serial_no", provider)
|
return fmt.Errorf("%s 缺少配置字段 platform_serial_no", provider)
|
||||||
}
|
}
|
||||||
case paymentutil.ProviderQQ:
|
case bizpayment.PaymentQQ:
|
||||||
if integrationFirst(values, "mch_id", "merchant_id") == "" {
|
if integrationFirst(values, "mch_id", "merchant_id") == "" {
|
||||||
return fmt.Errorf("%s 缺少配置字段 mch_id", provider)
|
return fmt.Errorf("%s 缺少配置字段 mch_id", provider)
|
||||||
}
|
}
|
||||||
@@ -355,12 +355,12 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er
|
|||||||
if signType != "" && signType != "MD5" && signType != "HMAC-SHA256" {
|
if signType != "" && signType != "MD5" && signType != "HMAC-SHA256" {
|
||||||
return fmt.Errorf("%s sign_type 必须是 MD5 或 HMAC-SHA256", provider)
|
return fmt.Errorf("%s sign_type 必须是 MD5 或 HMAC-SHA256", provider)
|
||||||
}
|
}
|
||||||
case paymentutil.ProviderAllinPay:
|
case bizpayment.PaymentAllinPay:
|
||||||
orderType := strings.ToLower(integrationFirst(values, "query_order_type", "order_type"))
|
orderType := strings.ToLower(integrationFirst(values, "query_order_type", "order_type"))
|
||||||
if orderType != "" && orderType != "reqsn" && orderType != "trxid" {
|
if orderType != "" && orderType != "reqsn" && orderType != "trxid" {
|
||||||
return fmt.Errorf("%s query_order_type 必须是 reqsn 或 trxid", provider)
|
return fmt.Errorf("%s query_order_type 必须是 reqsn 或 trxid", provider)
|
||||||
}
|
}
|
||||||
case paymentutil.ProviderChinaums, paymentutil.ProviderSFT, paymentutil.ProviderSuperPay, paymentutil.ProviderWechatGame, paymentutil.ProviderDouyinGame:
|
case bizpayment.PaymentChinaums, bizpayment.PaymentSFT, bizpayment.PaymentSuperPay, bizpayment.PaymentWechatGame, bizpayment.PaymentDouyinGame:
|
||||||
if integrationFirst(values, "app_key", "merchant_key", "signing_secret", "token") == "" {
|
if integrationFirst(values, "app_key", "merchant_key", "signing_secret", "token") == "" {
|
||||||
return fmt.Errorf("%s 缺少签名密钥", provider)
|
return fmt.Errorf("%s 缺少签名密钥", provider)
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if environment := strings.ToLower(integrationText(values, "environment")); environment != "" {
|
if environment := strings.ToLower(integrationText(values, "environment")); environment != "" {
|
||||||
allowedSandbox := provider != paymentutil.ProviderQQ && provider != paymentutil.ProviderDouyin && provider != paymentutil.ProviderLakala
|
allowedSandbox := provider != bizpayment.PaymentQQ && provider != bizpayment.PaymentDouyin && provider != bizpayment.PaymentLakala
|
||||||
if environment != "production" && environment != "prod" && (!allowedSandbox || environment != "sandbox") {
|
if environment != "production" && environment != "prod" && (!allowedSandbox || environment != "sandbox") {
|
||||||
return fmt.Errorf("%s environment 配置无效", provider)
|
return fmt.Errorf("%s environment 配置无效", provider)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import paymentutil "kra/pkg/paymentkit"
|
import bizpayment "kra/internal/biz/payment"
|
||||||
|
|
||||||
func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField {
|
func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField {
|
||||||
if fieldType == "" {
|
if fieldType == "" {
|
||||||
@@ -150,35 +150,35 @@ var integrationDefinitions = map[string][]IntegrationConfigDefinition{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
IntegrationKindPayment: {
|
IntegrationKindPayment: {
|
||||||
paymentDefinition(paymentutil.ProviderAlipay, "支付宝", "支付宝 OpenAPI RSA2 支付", map[string]any{"app_id": "", "private_key": "", "public_key": "", "environment": "production", "sign_type": "RSA2", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"},
|
paymentDefinition(bizpayment.PaymentAlipay, "支付宝", "支付宝 OpenAPI RSA2 支付", map[string]any{"app_id": "", "private_key": "", "public_key": "", "environment": "production", "sign_type": "RSA2", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"},
|
||||||
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", true, true, "textarea"), integrationField("public_key", "支付宝公钥", true, true, "textarea"),
|
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", true, true, "textarea"), integrationField("public_key", "支付宝公钥", true, true, "textarea"),
|
||||||
integrationSelect("environment", "环境", false, "production", "sandbox"), integrationSelect("sign_type", "签名算法", false, "RSA2", "RSA"), integrationField("gateway_url", "网关地址", false, false, "url"),
|
integrationSelect("environment", "环境", false, "production", "sandbox"), integrationSelect("sign_type", "签名算法", false, "RSA2", "RSA"), integrationField("gateway_url", "网关地址", false, false, "url"),
|
||||||
integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")),
|
integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")),
|
||||||
paymentDefinition(paymentutil.ProviderAlipayV3, "支付宝 V3", "支付宝证书模式 V3 接口", map[string]any{"app_id": "", "private_key": "", "app_cert": "", "root_cert": "", "public_cert": "", "environment": "production", "api_base_url": "https://openapi.alipay.com", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"},
|
paymentDefinition(bizpayment.PaymentAlipayV3, "支付宝 V3", "支付宝证书模式 V3 接口", map[string]any{"app_id": "", "private_key": "", "app_cert": "", "root_cert": "", "public_cert": "", "environment": "production", "api_base_url": "https://openapi.alipay.com", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"},
|
||||||
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", false, true, "textarea"), integrationField("app_cert", "应用公钥证书", false, true, "textarea"), integrationField("root_cert", "支付宝根证书", false, true, "textarea"), integrationField("public_cert", "支付宝公钥证书", false, true, "textarea"),
|
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", false, true, "textarea"), integrationField("app_cert", "应用公钥证书", false, true, "textarea"), integrationField("root_cert", "支付宝根证书", false, true, "textarea"), integrationField("public_cert", "支付宝公钥证书", false, true, "textarea"),
|
||||||
integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("api_base_url", "API 地址", false, false, "url"), integrationField("gateway_url", "网关地址", false, false, "url"), integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")),
|
integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("api_base_url", "API 地址", false, false, "url"), integrationField("gateway_url", "网关地址", false, false, "url"), integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")),
|
||||||
paymentDefinition(paymentutil.ProviderWechatV2, "微信支付 V2", "微信支付 V2,含退款双向证书", map[string]any{"app_id": "", "merchant_id": "", "mch_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "client_cert": "", "client_key": ""},
|
paymentDefinition(bizpayment.PaymentWechatV2, "微信支付 V2", "微信支付 V2,含退款双向证书", map[string]any{"app_id": "", "merchant_id": "", "mch_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "client_cert": "", "client_key": ""},
|
||||||
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("mch_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "APP", "NATIVE", "MWEB", "MICROPAY"), integrationField("client_cert", "商户证书", false, true, "textarea"), integrationField("client_key", "证书私钥", false, true, "textarea")),
|
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("mch_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "APP", "NATIVE", "MWEB", "MICROPAY"), integrationField("client_cert", "商户证书", false, true, "textarea"), integrationField("client_key", "证书私钥", false, true, "textarea")),
|
||||||
paymentDefinition(paymentutil.ProviderWechatV3, "微信支付 V3", "微信支付 API v3", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "private_key": "", "api_v3_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi"},
|
paymentDefinition(bizpayment.PaymentWechatV3, "微信支付 V3", "微信支付 API v3", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "private_key": "", "api_v3_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi"},
|
||||||
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("api_v3_key", "API v3 密钥", true, true, "password"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "jsapi", "app", "native", "h5", "codepay")),
|
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("api_v3_key", "API v3 密钥", true, true, "password"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "jsapi", "app", "native", "h5", "codepay")),
|
||||||
paymentDefinition(paymentutil.ProviderApple, "Apple IAP", "Apple App Store Server API", map[string]any{"issuer_id": "", "key_id": "", "bundle_id": "", "private_key": "", "price_divisor": 10, "environment": "production", "test_product_id": "", "test_transaction_id": ""},
|
paymentDefinition(bizpayment.PaymentApple, "Apple IAP", "Apple App Store Server API", map[string]any{"issuer_id": "", "key_id": "", "bundle_id": "", "private_key": "", "price_divisor": 10, "environment": "production", "test_product_id": "", "test_transaction_id": ""},
|
||||||
integrationField("issuer_id", "Issuer ID", true, false, "text"), integrationField("key_id", "Key ID", true, false, "text"), integrationField("bundle_id", "Bundle ID", true, false, "text"), integrationField("private_key", "P8 私钥", true, true, "textarea"), integrationField("price_divisor", "价格除数", false, false, "number"), integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("test_product_id", "测试商品 ID", false, false, "text"), integrationField("test_transaction_id", "沙箱交易 ID", false, false, "text")),
|
integrationField("issuer_id", "Issuer ID", true, false, "text"), integrationField("key_id", "Key ID", true, false, "text"), integrationField("bundle_id", "Bundle ID", true, false, "text"), integrationField("private_key", "P8 私钥", true, true, "textarea"), integrationField("price_divisor", "价格除数", false, false, "number"), integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("test_product_id", "测试商品 ID", false, false, "text"), integrationField("test_transaction_id", "沙箱交易 ID", false, false, "text")),
|
||||||
paymentDefinition(paymentutil.ProviderDouyin, "抖音支付", "抖音开放平台支付", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "api_key": "", "private_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi", "environment": "production"},
|
paymentDefinition(bizpayment.PaymentDouyin, "抖音支付", "抖音开放平台支付", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "api_key": "", "private_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi", "environment": "production"},
|
||||||
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "app", "jsapi", "h5", "native"), integrationSelect("environment", "环境", false, "production")),
|
integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "app", "jsapi", "h5", "native"), integrationSelect("environment", "环境", false, "production")),
|
||||||
paymentDefinition(paymentutil.ProviderQQ, "QQ 钱包", "QQ 钱包支付", map[string]any{"mch_id": "", "api_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "cert_file": "", "key_file": "", "environment": "production"},
|
paymentDefinition(bizpayment.PaymentQQ, "QQ 钱包", "QQ 钱包支付", map[string]any{"mch_id": "", "api_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "cert_file": "", "key_file": "", "environment": "production"},
|
||||||
integrationField("mch_id", "商户号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "NATIVE", "APP", "MICROPAY"), integrationField("cert_file", "退款证书路径", false, false, "text"), integrationField("key_file", "退款私钥路径", false, true, "text"), integrationSelect("environment", "环境", false, "production")),
|
integrationField("mch_id", "商户号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "NATIVE", "APP", "MICROPAY"), integrationField("cert_file", "退款证书路径", false, false, "text"), integrationField("key_file", "退款私钥路径", false, true, "text"), integrationSelect("environment", "环境", false, "production")),
|
||||||
paymentDefinition(paymentutil.ProviderAllinPay, "通联支付", "通联收银宝支付", map[string]any{"cus_id": "", "app_id": "", "private_key": "", "public_key": "", "org_id": "", "pay_type": "W02", "query_order_type": "reqsn", "currency": "CNY", "environment": "production"},
|
paymentDefinition(bizpayment.PaymentAllinPay, "通联支付", "通联收银宝支付", map[string]any{"cus_id": "", "app_id": "", "private_key": "", "public_key": "", "org_id": "", "pay_type": "W02", "query_order_type": "reqsn", "currency": "CNY", "environment": "production"},
|
||||||
integrationField("cus_id", "商户号", true, false, "text"), integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("public_key", "平台公钥", true, true, "textarea"), integrationField("org_id", "机构号", false, false, "text"), integrationField("pay_type", "支付类型", false, false, "text"), integrationSelect("query_order_type", "查单标识", false, "reqsn", "trxid"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")),
|
integrationField("cus_id", "商户号", true, false, "text"), integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("public_key", "平台公钥", true, true, "textarea"), integrationField("org_id", "机构号", false, false, "text"), integrationField("pay_type", "支付类型", false, false, "text"), integrationSelect("query_order_type", "查单标识", false, "reqsn", "trxid"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")),
|
||||||
paymentDefinition(paymentutil.ProviderLakala, "拉卡拉", "拉卡拉聚合支付", map[string]any{"partner_code": "", "credential_code": "", "channel": "Wechat", "method": "jsapi", "currency": "CNY", "environment": "production"},
|
paymentDefinition(bizpayment.PaymentLakala, "拉卡拉", "拉卡拉聚合支付", map[string]any{"partner_code": "", "credential_code": "", "channel": "Wechat", "method": "jsapi", "currency": "CNY", "environment": "production"},
|
||||||
integrationField("partner_code", "合作方编号", true, false, "text"), integrationField("credential_code", "凭证码", true, true, "password"), integrationSelect("channel", "支付渠道", false, "Wechat", "Alipay", "UnionPay"), integrationSelect("method", "默认支付方式", false, "jsapi", "h5", "mini", "native", "qrcode", "native_jsapi", "sdk", "web", "retail", "retail_qrcode"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production")),
|
integrationField("partner_code", "合作方编号", true, false, "text"), integrationField("credential_code", "凭证码", true, true, "password"), integrationSelect("channel", "支付渠道", false, "Wechat", "Alipay", "UnionPay"), integrationSelect("method", "默认支付方式", false, "jsapi", "h5", "mini", "native", "qrcode", "native_jsapi", "sdk", "web", "retail", "retail_qrcode"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production")),
|
||||||
paymentDefinition(paymentutil.ProviderPayPal, "PayPal", "PayPal Checkout", map[string]any{"client_id": "", "client_secret": "", "webhook_id": "", "environment": "sandbox", "return_url": "", "cancel_url": "", "auto_capture": true, "test_currency": "USD"},
|
paymentDefinition(bizpayment.PaymentPayPal, "PayPal", "PayPal Checkout", map[string]any{"client_id": "", "client_secret": "", "webhook_id": "", "environment": "sandbox", "return_url": "", "cancel_url": "", "auto_capture": true, "test_currency": "USD"},
|
||||||
integrationField("client_id", "Client ID", true, false, "text"), integrationField("client_secret", "Client Secret", true, true, "password"), integrationField("webhook_id", "Webhook ID", true, false, "text"), integrationSelect("environment", "环境", false, "sandbox", "production"), integrationField("cancel_url", "取消跳转地址", false, false, "url"), integrationField("auto_capture", "自动捕获", false, false, "switch")),
|
integrationField("client_id", "Client ID", true, false, "text"), integrationField("client_secret", "Client Secret", true, true, "password"), integrationField("webhook_id", "Webhook ID", true, false, "text"), integrationSelect("environment", "环境", false, "sandbox", "production"), integrationField("cancel_url", "取消跳转地址", false, false, "url"), integrationField("auto_capture", "自动捕获", false, false, "switch")),
|
||||||
paymentDefinition(paymentutil.ProviderSaobei, "扫呗", "扫呗聚合支付", map[string]any{"inst_no": "", "key": "", "merchant_no": "", "terminal_id": "", "access_token": "", "pay_type": "010", "currency": "CNY", "environment": "production"},
|
paymentDefinition(bizpayment.PaymentSaobei, "扫呗", "扫呗聚合支付", map[string]any{"inst_no": "", "key": "", "merchant_no": "", "terminal_id": "", "access_token": "", "pay_type": "010", "currency": "CNY", "environment": "production"},
|
||||||
integrationField("inst_no", "机构号", true, false, "text"), integrationField("key", "机构密钥", true, true, "password"), integrationField("merchant_no", "商户号", true, false, "text"), integrationField("terminal_id", "终端号", true, false, "text"), integrationField("access_token", "访问令牌", true, true, "password"), integrationField("pay_type", "支付类型", false, false, "text"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")),
|
integrationField("inst_no", "机构号", true, false, "text"), integrationField("key", "机构密钥", true, true, "password"), integrationField("merchant_no", "商户号", true, false, "text"), integrationField("terminal_id", "终端号", true, false, "text"), integrationField("access_token", "访问令牌", true, true, "password"), integrationField("pay_type", "支付类型", false, false, "text"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")),
|
||||||
genericPaymentDefinition(paymentutil.ProviderChinaums, "银联商务", "按商户协议配置的银联商务适配器"),
|
genericPaymentDefinition(bizpayment.PaymentChinaums, "银联商务", "按商户协议配置的银联商务适配器"),
|
||||||
genericPaymentDefinition(paymentutil.ProviderSFT, "商福通", "按商户协议配置的商福通适配器"),
|
genericPaymentDefinition(bizpayment.PaymentSFT, "商福通", "按商户协议配置的商福通适配器"),
|
||||||
genericPaymentDefinition(paymentutil.ProviderSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"),
|
genericPaymentDefinition(bizpayment.PaymentSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"),
|
||||||
genericPaymentDefinition(paymentutil.ProviderWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"),
|
genericPaymentDefinition(bizpayment.PaymentWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"),
|
||||||
genericPaymentDefinition(paymentutil.ProviderDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"),
|
genericPaymentDefinition(bizpayment.PaymentDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+130
-44
@@ -12,37 +12,39 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/pkg/paymentkit"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PaymentAlipay = paymentutil.ProviderAlipay
|
PaymentAlipay = "alipay"
|
||||||
PaymentAlipayV3 = paymentutil.ProviderAlipayV3
|
PaymentAlipayV3 = "alipay-v3"
|
||||||
PaymentWechatV2 = paymentutil.ProviderWechatV2
|
PaymentWechatV2 = "wechat-v2"
|
||||||
PaymentWechatV3 = paymentutil.ProviderWechatV3
|
PaymentWechatV3 = "wechat-v3"
|
||||||
PaymentApple = paymentutil.ProviderApple
|
PaymentApple = "apple-iap"
|
||||||
PaymentChinaums = paymentutil.ProviderChinaums
|
PaymentChinaums = "chinaums"
|
||||||
PaymentSFT = paymentutil.ProviderSFT
|
PaymentSFT = "sft"
|
||||||
PaymentSuperPay = paymentutil.ProviderSuperPay
|
PaymentSuperPay = "supper-pay"
|
||||||
PaymentWechatGame = paymentutil.ProviderWechatGame
|
PaymentWechatGame = "wechat-game-pay"
|
||||||
PaymentDouyinGame = paymentutil.ProviderDouyinGame
|
PaymentDouyinGame = "douyin-game-pay"
|
||||||
// GoPay-backed providers. These are kept separate from the existing
|
// GoPay-backed providers. These are kept separate from the existing
|
||||||
// configuration-driven game/aggregator protocols above.
|
// configuration-driven game/aggregator protocols above.
|
||||||
PaymentDouyin = paymentutil.ProviderDouyin
|
PaymentDouyin = "douyin"
|
||||||
PaymentQQ = paymentutil.ProviderQQ
|
PaymentQQ = "qq"
|
||||||
PaymentAllinPay = paymentutil.ProviderAllinPay
|
PaymentAllinPay = "allinpay"
|
||||||
PaymentLakala = paymentutil.ProviderLakala
|
PaymentLakala = "lakala"
|
||||||
PaymentPayPal = paymentutil.ProviderPayPal
|
PaymentPayPal = "paypal"
|
||||||
PaymentSaobei = paymentutil.ProviderSaobei
|
PaymentSaobei = "saobei"
|
||||||
PaymentInternal = paymentutil.ProviderInternal
|
PaymentInternal = "internal"
|
||||||
|
|
||||||
PaymentModeExternal = paymentutil.ModeExternal
|
PaymentModeExternal = "external"
|
||||||
PaymentModeInternal = paymentutil.ModeInternal
|
PaymentModeInternal = "internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SupportedPaymentProviders = paymentutil.SupportedProviders()
|
var SupportedPaymentProviders = []string{
|
||||||
|
PaymentAlipay, PaymentAlipayV3, PaymentWechatV2, PaymentWechatV3, PaymentApple,
|
||||||
|
PaymentChinaums, PaymentSFT, PaymentSuperPay, PaymentWechatGame, PaymentDouyinGame,
|
||||||
|
PaymentDouyin, PaymentQQ, PaymentAllinPay, PaymentLakala, PaymentPayPal, PaymentSaobei,
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPaymentProviderNotFound = errors.New("支付渠道未配置")
|
ErrPaymentProviderNotFound = errors.New("支付渠道未配置")
|
||||||
@@ -203,15 +205,74 @@ type PaymentTestResult struct {
|
|||||||
Result *PaymentResult `json:"result,omitempty"`
|
Result *PaymentResult `json:"result,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentCallbackAck = paymentutil.CallbackAck
|
type PaymentCallbackAck struct {
|
||||||
type PaymentCallbackError = paymentutil.CallbackError
|
StatusCode int
|
||||||
|
ContentType string
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentCallbackError struct {
|
||||||
|
Cause error
|
||||||
|
Ack PaymentCallbackAck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PaymentCallbackError) Error() string {
|
||||||
|
if e == nil || e.Cause == nil {
|
||||||
|
return "支付回调处理失败"
|
||||||
|
}
|
||||||
|
return e.Cause.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PaymentCallbackError) Unwrap() error {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.Cause
|
||||||
|
}
|
||||||
|
|
||||||
func CallbackFailure(err error, fallback PaymentCallbackAck) PaymentCallbackAck {
|
func CallbackFailure(err error, fallback PaymentCallbackAck) PaymentCallbackAck {
|
||||||
return paymentutil.CallbackFailure(err, fallback)
|
var callbackErr *PaymentCallbackError
|
||||||
|
if errors.As(err, &callbackErr) && callbackErr.Ack.StatusCode != 0 {
|
||||||
|
return callbackErr.Ack
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultPaymentCallbackAck(provider string, success bool) PaymentCallbackAck {
|
func DefaultPaymentCallbackAck(provider string, success bool) PaymentCallbackAck {
|
||||||
return paymentutil.DefaultCallbackAck(provider, success)
|
status := 200
|
||||||
|
if !success {
|
||||||
|
status = 500
|
||||||
|
}
|
||||||
|
switch provider {
|
||||||
|
case PaymentAlipay, PaymentAlipayV3:
|
||||||
|
body := "success"
|
||||||
|
if !success {
|
||||||
|
body = "failure"
|
||||||
|
}
|
||||||
|
return PaymentCallbackAck{StatusCode: status, ContentType: "text/plain; charset=utf-8", Body: []byte(body)}
|
||||||
|
case PaymentWechatV2:
|
||||||
|
code, message := "SUCCESS", "OK"
|
||||||
|
if !success {
|
||||||
|
code, message = "FAIL", "FAIL"
|
||||||
|
}
|
||||||
|
body := fmt.Sprintf("<xml><return_code><![CDATA[%s]]></return_code><return_msg><![CDATA[%s]]></return_msg></xml>", code, message)
|
||||||
|
return PaymentCallbackAck{StatusCode: status, ContentType: "application/xml; charset=utf-8", Body: []byte(body)}
|
||||||
|
case PaymentWechatV3:
|
||||||
|
code, message := "SUCCESS", "成功"
|
||||||
|
if !success {
|
||||||
|
code, message = "FAIL", "失败"
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(map[string]string{"code": code, "message": message})
|
||||||
|
return PaymentCallbackAck{StatusCode: status, ContentType: "application/json; charset=utf-8", Body: body}
|
||||||
|
case PaymentApple:
|
||||||
|
return PaymentCallbackAck{StatusCode: status}
|
||||||
|
default:
|
||||||
|
body := "success"
|
||||||
|
if !success {
|
||||||
|
body = "failure"
|
||||||
|
}
|
||||||
|
return PaymentCallbackAck{StatusCode: status, ContentType: "text/plain; charset=utf-8", Body: []byte(body)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentCallback struct {
|
type PaymentCallback struct {
|
||||||
@@ -228,6 +289,19 @@ type PaymentRepo interface {
|
|||||||
HandleCallback(context.Context, *PaymentCallback) (*PaymentResult, error)
|
HandleCallback(context.Context, *PaymentCallback) (*PaymentResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PaymentAdapter is the integration boundary for provider protocols. SDK
|
||||||
|
// clients and wire formats stay outside the business and data packages.
|
||||||
|
type PaymentAdapter interface {
|
||||||
|
Create(context.Context, *PaymentRequest, map[string]any) (*PaymentResult, error)
|
||||||
|
Query(context.Context, string, map[string]any) (*PaymentResult, error)
|
||||||
|
Refund(context.Context, *PaymentRefundRequest, map[string]any) (*PaymentResult, error)
|
||||||
|
Callback(context.Context, *PaymentCallback, map[string]any) (*PaymentResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentAdapterFactory interface {
|
||||||
|
New(string) (PaymentAdapter, error)
|
||||||
|
}
|
||||||
|
|
||||||
type PaymentProviderTester interface {
|
type PaymentProviderTester interface {
|
||||||
TestProvider(context.Context, string) (*PaymentTestResult, error)
|
TestProvider(context.Context, string) (*PaymentTestResult, error)
|
||||||
}
|
}
|
||||||
@@ -240,6 +314,8 @@ type NoopPaymentHooks struct{}
|
|||||||
|
|
||||||
func (NoopPaymentHooks) BeforeCreate(context.Context, *PaymentRequest) error { return nil }
|
func (NoopPaymentHooks) BeforeCreate(context.Context, *PaymentRequest) error { return nil }
|
||||||
|
|
||||||
|
func NewPaymentHooks() PaymentHooks { return NoopPaymentHooks{} }
|
||||||
|
|
||||||
type PaymentFulfillmentHandler interface {
|
type PaymentFulfillmentHandler interface {
|
||||||
Type() string
|
Type() string
|
||||||
Fulfill(context.Context, *PaymentConfirmation) error
|
Fulfill(context.Context, *PaymentConfirmation) error
|
||||||
@@ -273,6 +349,15 @@ func (r *PaymentFulfillmentRegistry) Handler(kind string) PaymentFulfillmentHand
|
|||||||
return r.handlers[kind]
|
return r.handlers[kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PaymentFulfillmentRegistry) Len() int {
|
||||||
|
if r == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
return len(r.handlers)
|
||||||
|
}
|
||||||
|
|
||||||
type PaymentUsecase struct {
|
type PaymentUsecase struct {
|
||||||
repo PaymentRepo
|
repo PaymentRepo
|
||||||
orders PaymentOrderRepo
|
orders PaymentOrderRepo
|
||||||
@@ -282,33 +367,34 @@ type PaymentUsecase struct {
|
|||||||
logger PaymentLogger
|
logger PaymentLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentUsecase(repo PaymentRepo, orders PaymentOrderRepo, appLogger *slog.Logger) *PaymentUsecase {
|
func NewPaymentUsecase(repo PaymentRepo, orders PaymentOrderRepo, hooks PaymentHooks, sources *PaymentOrderSourceRegistry, fulfillments *PaymentFulfillmentRegistry, appLogger *slog.Logger) *PaymentUsecase {
|
||||||
|
if hooks == nil {
|
||||||
|
hooks = NoopPaymentHooks{}
|
||||||
|
}
|
||||||
|
if sources == nil {
|
||||||
|
sources = NewPaymentOrderSourceRegistry()
|
||||||
|
}
|
||||||
|
if fulfillments == nil {
|
||||||
|
fulfillments = NewPaymentFulfillmentRegistry()
|
||||||
|
}
|
||||||
return &PaymentUsecase{
|
return &PaymentUsecase{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
orders: orders,
|
orders: orders,
|
||||||
hooks: NoopPaymentHooks{},
|
hooks: hooks,
|
||||||
sources: NewPaymentOrderSourceRegistry(),
|
sources: sources,
|
||||||
fulfillments: NewPaymentFulfillmentRegistry(),
|
fulfillments: fulfillments,
|
||||||
logger: NewPaymentLogger(appLogger),
|
logger: NewPaymentLogger(appLogger),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *PaymentUsecase) SetHooks(hooks PaymentHooks) {
|
func NewConfiguredPaymentUsecase(repo PaymentRepo, orders PaymentOrderRepo, hooks PaymentHooks, sources *PaymentOrderSourceRegistry, fulfillments *PaymentFulfillmentRegistry, appLogger *slog.Logger) (*PaymentUsecase, error) {
|
||||||
if hooks != nil {
|
if sources == nil || sources.Len() == 0 {
|
||||||
uc.hooks = hooks
|
return nil, errors.New("支付业务订单来源未注册,无法启动支付服务")
|
||||||
}
|
}
|
||||||
}
|
if fulfillments == nil || fulfillments.Len() == 0 {
|
||||||
|
return nil, errors.New("支付发货 handler 未注册,无法启动支付服务")
|
||||||
func (uc *PaymentUsecase) SetOrderSourceRegistry(registry *PaymentOrderSourceRegistry) {
|
|
||||||
if registry != nil {
|
|
||||||
uc.sources = registry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *PaymentUsecase) SetFulfillmentRegistry(registry *PaymentFulfillmentRegistry) {
|
|
||||||
if registry != nil {
|
|
||||||
uc.fulfillments = registry
|
|
||||||
}
|
}
|
||||||
|
return NewPaymentUsecase(repo, orders, hooks, sources, fulfillments, appLogger), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *PaymentUsecase) RegisterBusinessModule(module PaymentBusinessModule) error {
|
func (uc *PaymentUsecase) RegisterBusinessModule(module PaymentBusinessModule) error {
|
||||||
|
|||||||
@@ -182,6 +182,15 @@ func (r *PaymentOrderSourceRegistry) Source(kind string) PaymentOrderSource {
|
|||||||
return r.sources[kind]
|
return r.sources[kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PaymentOrderSourceRegistry) Len() int {
|
||||||
|
if r == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
return len(r.sources)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *PaymentOrderSourceRegistry) Unregister(kind string) {
|
func (r *PaymentOrderSourceRegistry) Unregister(kind string) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -135,7 +135,12 @@ func (h *paymentFulfillmentStub) Fulfill(_ context.Context, confirmation *Paymen
|
|||||||
|
|
||||||
func newPaymentUsecaseForTest(repo PaymentRepo) *PaymentUsecase {
|
func newPaymentUsecaseForTest(repo PaymentRepo) *PaymentUsecase {
|
||||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
return NewPaymentUsecase(repo, &paymentOrderRepoStub{order: testBizPaymentOrder()}, logger)
|
return NewPaymentUsecase(repo, &paymentOrderRepoStub{order: testBizPaymentOrder()}, nil, nil, nil, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPaymentUsecaseWithFulfillmentRegistry(repo PaymentRepo, registry *PaymentFulfillmentRegistry) *PaymentUsecase {
|
||||||
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
|
return NewPaymentUsecase(repo, &paymentOrderRepoStub{order: testBizPaymentOrder()}, nil, nil, registry, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBizPaymentOrder() *PaymentOrder {
|
func testBizPaymentOrder() *PaymentOrder {
|
||||||
@@ -186,7 +191,7 @@ func TestPaymentCallbackPendingDoesNotFulfill(t *testing.T) {
|
|||||||
if err := registry.Register(handler); err != nil {
|
if err := registry.Register(handler); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
uc.SetFulfillmentRegistry(registry)
|
uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry)
|
||||||
result, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")})
|
result, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -200,10 +205,10 @@ func TestPaymentCallbackRejectsAmountMismatch(t *testing.T) {
|
|||||||
repo := &paymentRepoStub{callback: successPaymentResult(), query: successPaymentResult()}
|
repo := &paymentRepoStub{callback: successPaymentResult(), query: successPaymentResult()}
|
||||||
handler := &paymentFulfillmentStub{}
|
handler := &paymentFulfillmentStub{}
|
||||||
uc := newPaymentUsecaseForTest(repo)
|
uc := newPaymentUsecaseForTest(repo)
|
||||||
uc.orders.(*paymentOrderRepoStub).order.Amount = 101
|
|
||||||
registry := NewPaymentFulfillmentRegistry()
|
registry := NewPaymentFulfillmentRegistry()
|
||||||
_ = registry.Register(handler)
|
_ = registry.Register(handler)
|
||||||
uc.SetFulfillmentRegistry(registry)
|
uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry)
|
||||||
|
uc.orders.(*paymentOrderRepoStub).order.Amount = 101
|
||||||
if _, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}); err == nil {
|
if _, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}); err == nil {
|
||||||
t.Fatal("amount mismatch was accepted")
|
t.Fatal("amount mismatch was accepted")
|
||||||
}
|
}
|
||||||
@@ -226,7 +231,7 @@ func TestPaymentCallbackDuplicateDoesNotFulfillTwice(t *testing.T) {
|
|||||||
uc := newPaymentUsecaseForTest(repo)
|
uc := newPaymentUsecaseForTest(repo)
|
||||||
registry := NewPaymentFulfillmentRegistry()
|
registry := NewPaymentFulfillmentRegistry()
|
||||||
_ = registry.Register(handler)
|
_ = registry.Register(handler)
|
||||||
uc.SetFulfillmentRegistry(registry)
|
uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry)
|
||||||
callback := &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}
|
callback := &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}
|
||||||
if _, err := uc.Callback(context.Background(), callback); err != nil {
|
if _, err := uc.Callback(context.Background(), callback); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -246,7 +251,7 @@ func TestPaymentCallbackFulfillmentFailureCanRetry(t *testing.T) {
|
|||||||
uc := newPaymentUsecaseForTest(repo)
|
uc := newPaymentUsecaseForTest(repo)
|
||||||
registry := NewPaymentFulfillmentRegistry()
|
registry := NewPaymentFulfillmentRegistry()
|
||||||
_ = registry.Register(handler)
|
_ = registry.Register(handler)
|
||||||
uc.SetFulfillmentRegistry(registry)
|
uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry)
|
||||||
callback := &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}
|
callback := &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}
|
||||||
if _, err := uc.Callback(context.Background(), callback); err == nil {
|
if _, err := uc.Callback(context.Background(), callback); err == nil {
|
||||||
t.Fatal("first fulfillment failure was accepted")
|
t.Fatal("first fulfillment failure was accepted")
|
||||||
@@ -290,12 +295,11 @@ func TestPaymentCallbackErrorCarriesSafeAck(t *testing.T) {
|
|||||||
func TestPersistentPaymentCreateUsesBusinessCanonicalAmount(t *testing.T) {
|
func TestPersistentPaymentCreateUsesBusinessCanonicalAmount(t *testing.T) {
|
||||||
repo := &paymentRepoStub{create: &PaymentResult{Provider: PaymentAlipay, Status: "created", TradeNo: "order-1", Payload: []byte(`{"prepay":"x"}`)}}
|
repo := &paymentRepoStub{create: &PaymentResult{Provider: PaymentAlipay, Status: "created", TradeNo: "order-1", Payload: []byte(`{"prepay":"x"}`)}}
|
||||||
orders := &paymentOrderRepoStub{}
|
orders := &paymentOrderRepoStub{}
|
||||||
uc := NewPaymentUsecase(repo, orders, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
||||||
sources := NewPaymentOrderSourceRegistry()
|
sources := NewPaymentOrderSourceRegistry()
|
||||||
if err := sources.Register(&paymentSourceStub{intent: &PaymentIntent{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "canonical item", Amount: 100, Currency: "CNY", Extra: []byte(`{"product_id":"canonical"}`)}}); err != nil {
|
if err := sources.Register(&paymentSourceStub{intent: &PaymentIntent{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "canonical item", Amount: 100, Currency: "CNY", Extra: []byte(`{"product_id":"canonical"}`)}}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
uc.SetOrderSourceRegistry(sources)
|
uc := NewPaymentUsecase(repo, orders, nil, sources, nil, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
||||||
result, err := uc.Create(context.Background(), &PaymentRequest{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "forged", Amount: 1, Currency: "USD", Extra: map[string]any{"product_id": "forged"}})
|
result, err := uc.Create(context.Background(), &PaymentRequest{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "forged", Amount: 1, Currency: "USD", Extra: map[string]any{"product_id": "forged"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -309,12 +313,11 @@ func TestPersistentPaymentCallbackFulfillsAndPersistsDuplicate(t *testing.T) {
|
|||||||
repo := &paymentRepoStub{callback: successPaymentResult(), query: successPaymentResult()}
|
repo := &paymentRepoStub{callback: successPaymentResult(), query: successPaymentResult()}
|
||||||
orders := &paymentOrderRepoStub{order: &PaymentOrder{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: PaymentStatusPending, FulfillmentStatus: FulfillmentStatusPending, RefundStatus: RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111"}}
|
orders := &paymentOrderRepoStub{order: &PaymentOrder{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: PaymentStatusPending, FulfillmentStatus: FulfillmentStatusPending, RefundStatus: RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111"}}
|
||||||
handler := &paymentFulfillmentStub{}
|
handler := &paymentFulfillmentStub{}
|
||||||
uc := NewPaymentUsecase(repo, orders, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
||||||
registry := NewPaymentFulfillmentRegistry()
|
registry := NewPaymentFulfillmentRegistry()
|
||||||
if err := registry.Register(handler); err != nil {
|
if err := registry.Register(handler); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
uc.SetFulfillmentRegistry(registry)
|
uc := NewPaymentUsecase(repo, orders, nil, nil, registry, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
||||||
first, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")})
|
first, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -330,13 +333,12 @@ func TestPersistentPaymentCallbackFulfillsAndPersistsDuplicate(t *testing.T) {
|
|||||||
|
|
||||||
func TestPaymentFulfillRetriesFailedOrder(t *testing.T) {
|
func TestPaymentFulfillRetriesFailedOrder(t *testing.T) {
|
||||||
orders := &paymentOrderRepoStub{order: &PaymentOrder{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: PaymentStatusPaid, FulfillmentStatus: FulfillmentStatusFailed, RefundStatus: RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111"}}
|
orders := &paymentOrderRepoStub{order: &PaymentOrder{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: PaymentStatusPaid, FulfillmentStatus: FulfillmentStatusFailed, RefundStatus: RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111"}}
|
||||||
uc := NewPaymentUsecase(&paymentRepoStub{}, orders, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
||||||
handler := &paymentFulfillmentStub{}
|
handler := &paymentFulfillmentStub{}
|
||||||
registry := NewPaymentFulfillmentRegistry()
|
registry := NewPaymentFulfillmentRegistry()
|
||||||
if err := registry.Register(handler); err != nil {
|
if err := registry.Register(handler); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
uc.SetFulfillmentRegistry(registry)
|
uc := NewPaymentUsecase(&paymentRepoStub{}, orders, nil, nil, registry, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
||||||
result, err := uc.Fulfill(context.Background(), PaymentAlipay, "order-1")
|
result, err := uc.Fulfill(context.Background(), PaymentAlipay, "order-1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -5,4 +5,9 @@ import "github.com/google/wire"
|
|||||||
// ProviderSet wires the payment domain usecase independently from other
|
// ProviderSet wires the payment domain usecase independently from other
|
||||||
// business domains. Persistence and transport layers consume the payment
|
// business domains. Persistence and transport layers consume the payment
|
||||||
// interfaces without depending on the system package.
|
// interfaces without depending on the system package.
|
||||||
var ProviderSet = wire.NewSet(NewPaymentUsecase)
|
var ProviderSet = wire.NewSet(
|
||||||
|
NewPaymentHooks,
|
||||||
|
NewPaymentOrderSourceRegistry,
|
||||||
|
NewPaymentFulfillmentRegistry,
|
||||||
|
NewConfiguredPaymentUsecase,
|
||||||
|
)
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type APITokenRepo interface {
|
|||||||
UserHasAuthority(context.Context, uint, uint) (*User, bool, error)
|
UserHasAuthority(context.Context, uint, uint) (*User, bool, error)
|
||||||
CreateAPIToken(context.Context, *APIToken) error
|
CreateAPIToken(context.Context, *APIToken) error
|
||||||
ListAPITokens(context.Context, int, int, uint, *bool) ([]*APIToken, int64, error)
|
ListAPITokens(context.Context, int, int, uint, *bool) ([]*APIToken, int64, error)
|
||||||
DisableAPIToken(context.Context, uint) (string, error)
|
|
||||||
DisableAndBlacklistAPIToken(context.Context, uint) error
|
DisableAndBlacklistAPIToken(context.Context, uint) error
|
||||||
BlacklistToken(context.Context, string) error
|
BlacklistToken(context.Context, string) error
|
||||||
IsTokenDisabled(context.Context, string) (bool, error)
|
IsTokenDisabled(context.Context, string) (bool, error)
|
||||||
|
|||||||
@@ -40,8 +40,11 @@ type LoginLog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LoginLogFilter struct {
|
type LoginLogFilter struct {
|
||||||
Username string
|
Username string
|
||||||
Status *bool
|
IP string
|
||||||
|
Status *bool
|
||||||
|
StartCreatedAt *time.Time
|
||||||
|
EndCreatedAt *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataAccessLog struct {
|
type DataAccessLog struct {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
type EmailRepo interface {
|
type EmailRepo interface {
|
||||||
Send(context.Context, []string, string, string) error
|
Send(context.Context, []string, string, string) error
|
||||||
DefaultRecipients() []string
|
DefaultRecipients() []string
|
||||||
Enabled() bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailUsecase struct{ repo EmailRepo }
|
type EmailUsecase struct{ repo EmailRepo }
|
||||||
@@ -27,22 +26,3 @@ func (uc *EmailUsecase) Test(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
return uc.repo.Send(ctx, recipients, "test", "test")
|
return uc.repo.Send(ctx, recipients, "test", "test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *EmailUsecase) Alert(ctx context.Context, subject, body string) error {
|
|
||||||
if !uc.repo.Enabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// ErrorToEmail splits the configured comma-separated recipient
|
|
||||||
// string, while its explicit test endpoint deliberately sends the whole
|
|
||||||
// value as one recipient. Keep those two contracts separate.
|
|
||||||
configured := uc.repo.DefaultRecipients()
|
|
||||||
recipients := make([]string, 0, len(configured))
|
|
||||||
for _, value := range configured {
|
|
||||||
parts := strings.Split(value, ",")
|
|
||||||
if len(parts) > 1 && parts[len(parts)-1] == "" {
|
|
||||||
parts = parts[:len(parts)-1]
|
|
||||||
}
|
|
||||||
recipients = append(recipients, parts...)
|
|
||||||
}
|
|
||||||
return uc.repo.Send(ctx, recipients, subject, body)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ type AttachmentCategory struct {
|
|||||||
type MediaMetadataRepo interface {
|
type MediaMetadataRepo interface {
|
||||||
CreateMedia(context.Context, *MediaFile) error
|
CreateMedia(context.Context, *MediaFile) error
|
||||||
FindMedia(context.Context, uint) (*MediaFile, error)
|
FindMedia(context.Context, uint) (*MediaFile, error)
|
||||||
FindMediaByHash(context.Context, uint, string) (*MediaFile, error)
|
|
||||||
ListMedia(context.Context, MediaFilter) ([]*MediaFile, int64, error)
|
ListMedia(context.Context, MediaFilter) ([]*MediaFile, int64, error)
|
||||||
UpdateMediaName(context.Context, uint, string) error
|
UpdateMediaName(context.Context, uint, string) error
|
||||||
DeleteMedia(context.Context, uint) error
|
DeleteMedia(context.Context, uint) error
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ type MenuRepo interface {
|
|||||||
Find(context.Context, uint) (*Menu, error)
|
Find(context.Context, uint) (*Menu, error)
|
||||||
List(context.Context) ([]*Menu, error)
|
List(context.Context) ([]*Menu, error)
|
||||||
SetAuthorityMenus(context.Context, uint, []uint) error
|
SetAuthorityMenus(context.Context, uint, []uint) error
|
||||||
AuthorityMenuIDs(context.Context, uint) ([]uint, error)
|
|
||||||
// ListAuthorityMenus returns the base menus assigned to the target
|
// ListAuthorityMenus returns the base menus assigned to the target
|
||||||
// authority. Unlike List, it must not apply the current actor's strict
|
// authority. Unlike List, it must not apply the current actor's strict
|
||||||
// menu filter: the administration endpoint is explicitly querying another
|
// menu filter: the administration endpoint is explicitly querying another
|
||||||
@@ -73,10 +72,6 @@ type MenuUsecase struct{ repo MenuRepo }
|
|||||||
|
|
||||||
func NewMenuUsecase(repo MenuRepo) *MenuUsecase { return &MenuUsecase{repo: repo} }
|
func NewMenuUsecase(repo MenuRepo) *MenuUsecase { return &MenuUsecase{repo: repo} }
|
||||||
|
|
||||||
func (uc *MenuUsecase) List(ctx context.Context) ([]*Menu, error) {
|
|
||||||
return uc.repo.List(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *MenuUsecase) Tree(ctx context.Context) ([]*Menu, error) {
|
func (uc *MenuUsecase) Tree(ctx context.Context) ([]*Menu, error) {
|
||||||
items, err := uc.repo.List(ctx)
|
items, err := uc.repo.List(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ package system
|
|||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
type PermissionRepo interface {
|
type PermissionRepo interface {
|
||||||
Buttons(context.Context, uint) ([]*MenuButton, error)
|
|
||||||
SetAuthorityButtons(context.Context, uint, map[uint][]uint) error
|
|
||||||
AuthorityButtonIDs(context.Context, uint) ([]uint, error)
|
|
||||||
SelectedButtons(context.Context, uint, uint) ([]uint, error)
|
SelectedButtons(context.Context, uint, uint) ([]uint, error)
|
||||||
SetSelectedButtons(context.Context, uint, uint, []uint) error
|
SetSelectedButtons(context.Context, uint, uint, []uint) error
|
||||||
CanRemoveButton(context.Context, string) (bool, error)
|
CanRemoveButton(context.Context, string) (bool, error)
|
||||||
|
|||||||
@@ -233,14 +233,6 @@ func (uc *SecurityUsecase) ActiveToken(ctx context.Context, username string) (st
|
|||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *SecurityUsecase) ActiveTokenMatches(ctx context.Context, username, token string) (bool, error) {
|
|
||||||
if !uc.settings.UseMultipoint() {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
active, ok, err := uc.cache.Get(ctx, activeTokenKey(username))
|
|
||||||
return ok && active == token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *SecurityUsecase) RotateActiveToken(ctx context.Context, username, oldToken, newToken string, expiration time.Duration) error {
|
func (uc *SecurityUsecase) RotateActiveToken(ctx context.Context, username, oldToken, newToken string, expiration time.Duration) error {
|
||||||
if !uc.settings.UseMultipoint() {
|
if !uc.settings.UseMultipoint() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -42,11 +42,9 @@ type UserRepo interface {
|
|||||||
HasAuthorityMenu(context.Context, uint, string) (bool, error)
|
HasAuthorityMenu(context.Context, uint, string) (bool, error)
|
||||||
MenusByAuthority(context.Context, uint) ([]*Menu, error)
|
MenusByAuthority(context.Context, uint) ([]*Menu, error)
|
||||||
ListUsers(context.Context, int, int, *UserListFilter) ([]*User, int64, error)
|
ListUsers(context.Context, int, int, *UserListFilter) ([]*User, int64, error)
|
||||||
CreateUser(context.Context, *User) (*User, error)
|
|
||||||
CreateUserWithAuthorities(context.Context, *User, []uint) (*User, error)
|
CreateUserWithAuthorities(context.Context, *User, []uint) (*User, error)
|
||||||
UpdateUser(context.Context, *User) error
|
UpdateUser(context.Context, *User) error
|
||||||
UpdateSelfUser(context.Context, *User) error
|
UpdateSelfUser(context.Context, *User) error
|
||||||
UpdateUserWithAuthorities(context.Context, *User, []uint) error
|
|
||||||
DeleteUser(context.Context, uint) error
|
DeleteUser(context.Context, uint) error
|
||||||
UpdatePassword(context.Context, uint, string, bool) error
|
UpdatePassword(context.Context, uint, string, bool) error
|
||||||
SetUserAuthorities(context.Context, uint, []uint) error
|
SetUserAuthorities(context.Context, uint, []uint) error
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ type VersionRepo interface {
|
|||||||
FindVersion(context.Context, uint) (*Version, error)
|
FindVersion(context.Context, uint) (*Version, error)
|
||||||
ListVersions(context.Context, int, int, string, string, []*time.Time) ([]*Version, int64, error)
|
ListVersions(context.Context, int, int, string, string, []*time.Time) ([]*Version, int64, error)
|
||||||
BuildVersionBundle(context.Context, []uint, []uint, []uint) (*VersionBundle, error)
|
BuildVersionBundle(context.Context, []uint, []uint, []uint) (*VersionBundle, error)
|
||||||
ImportVersionBundle(context.Context, *VersionBundle) error
|
ImportVersionBundle(context.Context, *VersionBundle, *Version) error
|
||||||
}
|
}
|
||||||
type VersionUsecase struct{ VersionRepo }
|
type VersionUsecase struct{ VersionRepo }
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ var ProviderSet = wire.NewSet(
|
|||||||
wire.Bind(new(datasystem.Provider), new(*Data)),
|
wire.Bind(new(datasystem.Provider), new(*Data)),
|
||||||
wire.Bind(new(datasystem.DatabaseProvider), new(*Data)),
|
wire.Bind(new(datasystem.DatabaseProvider), new(*Data)),
|
||||||
wire.Bind(new(dataintegration.Provider), new(*Data)),
|
wire.Bind(new(dataintegration.Provider), new(*Data)),
|
||||||
wire.Bind(new(datatask.Provider), new(*Data)),
|
|
||||||
wire.Bind(new(datapayment.Provider), new(*Data)),
|
wire.Bind(new(datapayment.Provider), new(*Data)),
|
||||||
datasystem.ProviderSet,
|
datasystem.ProviderSet,
|
||||||
dataintegration.ProviderSet,
|
dataintegration.ProviderSet,
|
||||||
@@ -235,8 +234,9 @@ func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *stor
|
|||||||
usingFallback = true
|
usingFallback = true
|
||||||
}
|
}
|
||||||
d.databaseReady.Store(!usingFallback)
|
d.databaseReady.Store(!usingFallback)
|
||||||
d.gormDB = newReloadableDB(db, d.enqueueDataScopeAudit)
|
d.gormDB = newReloadableDB(db)
|
||||||
d.auditLog = newDataScopeAuditWriter(d, appLogger)
|
d.auditLog = newDataScopeAuditWriter(d, appLogger)
|
||||||
|
registerDataScopeCallbacks(db, d.enqueueDataScopeAudit)
|
||||||
d.dbList, err = openDatabaseList(c.DatabaseList, appLogger)
|
d.dbList, err = openDatabaseList(c.DatabaseList, appLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"kra/internal/logging"
|
||||||
"kra/pkg/database/gormkit"
|
"kra/pkg/database/gormkit"
|
||||||
"kra/pkg/logging"
|
|
||||||
|
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,18 +14,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
datapayment "kra/internal/integration/payment"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type paymentRepo struct {
|
type paymentRepo struct {
|
||||||
data Provider
|
data Provider
|
||||||
config integrationbiz.PaymentConfigReader
|
config integrationbiz.PaymentConfigReader
|
||||||
|
factory bizpayment.PaymentAdapterFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentRepo(data Provider, config integrationbiz.PaymentConfigReader) bizpayment.PaymentRepo {
|
func NewPaymentRepo(data Provider, config integrationbiz.PaymentConfigReader, factory bizpayment.PaymentAdapterFactory) bizpayment.PaymentRepo {
|
||||||
return &paymentRepo{data: data, config: config}
|
return &paymentRepo{data: data, config: config, factory: factory}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *paymentRepo) values(ctx context.Context, provider string) (map[string]any, error) {
|
func (r *paymentRepo) values(ctx context.Context, provider string) (map[string]any, error) {
|
||||||
@@ -49,12 +48,15 @@ func (r *paymentRepo) values(ctx context.Context, provider string) (map[string]a
|
|||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *paymentRepo) adapter(ctx context.Context, provider string) (datapayment.Adapter, map[string]any, error) {
|
func (r *paymentRepo) adapter(ctx context.Context, provider string) (bizpayment.PaymentAdapter, map[string]any, error) {
|
||||||
values, err := r.values(ctx, provider)
|
values, err := r.values(ctx, provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
adapter, err := datapayment.New(provider)
|
if r.factory == nil {
|
||||||
|
return nil, nil, errors.New("支付渠道适配器未接入")
|
||||||
|
}
|
||||||
|
adapter, err := r.factory.New(provider)
|
||||||
return adapter, values, err
|
return adapter, values, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +83,13 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
|
|||||||
return test, err
|
return test, err
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter, err := datapayment.New(provider)
|
if r.factory == nil {
|
||||||
|
err = errors.New("支付渠道适配器未接入")
|
||||||
|
}
|
||||||
|
var adapter bizpayment.PaymentAdapter
|
||||||
|
if err == nil {
|
||||||
|
adapter, err = r.factory.New(provider)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
add("adapter", "failed", err.Error(), "", time.Now())
|
add("adapter", "failed", err.Error(), "", time.Now())
|
||||||
return test, err
|
return test, err
|
||||||
@@ -237,7 +245,7 @@ func validatePaymentTestResult(provider, tradeNo string, result *bizpayment.Paym
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*bizpayment.PaymentResult, error) {
|
func queryPaymentTest(ctx context.Context, adapter bizpayment.PaymentAdapter, queryID string, values map[string]any) (*bizpayment.PaymentResult, error) {
|
||||||
var result *bizpayment.PaymentResult
|
var result *bizpayment.PaymentResult
|
||||||
var err error
|
var err error
|
||||||
for attempt := 0; attempt < 3; attempt++ {
|
for attempt := 0; attempt < 3; attempt++ {
|
||||||
@@ -547,7 +555,3 @@ func contains(values []string, value string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePaymentConfig(provider string, values map[string]any) error {
|
|
||||||
return integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestValidatePaymentConfigRequiresDouyinAppIDWhenEnabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePaymentConfigAcceptsProviderAliases(t *testing.T) {
|
func TestValidatePaymentConfigAcceptsProviderAliases(t *testing.T) {
|
||||||
err := validatePaymentConfig(bizpayment.PaymentDouyin, map[string]any{
|
err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentDouyin, map[string]any{
|
||||||
"app_id": "douyin-app", "merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901",
|
"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",
|
"private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_cert_serial": "platform-serial",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package payment
|
package payment
|
||||||
|
|
||||||
import "kra/pkg/paymentkit"
|
import "kra/internal/paymentkit"
|
||||||
|
|
||||||
func text(values map[string]any, key string) string {
|
func text(values map[string]any, key string) string {
|
||||||
return paymentutil.Text(values, key)
|
return paymentkit.Text(values, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstAny(values map[string]any, keys ...string) string {
|
func firstAny(values map[string]any, keys ...string) string {
|
||||||
return paymentutil.FirstText(values, keys...)
|
return paymentkit.FirstText(values, keys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// configuredInt64 keeps repository-side validation independent from the
|
// configuredInt64 keeps repository-side validation independent from the
|
||||||
// provider adapter package while sharing the canonical payment parser.
|
// provider adapter package while sharing the canonical payment parser.
|
||||||
func configuredInt64(values map[string]any, key string, fallback int64) int64 {
|
func configuredInt64(values map[string]any, key string, fallback int64) int64 {
|
||||||
return paymentutil.ConfiguredInt64(values, key, fallback)
|
return paymentkit.ConfiguredInt64(values, key, fallback)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestPaymentDefinitionsProvideNonEmptyDefaults(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPaymentValidationRejectsUnknownProvider(t *testing.T) {
|
func TestPaymentValidationRejectsUnknownProvider(t *testing.T) {
|
||||||
err := validatePaymentConfig("unknown-provider", map[string]any{})
|
err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, "unknown-provider", map[string]any{})
|
||||||
if err == nil || !strings.Contains(err.Error(), "不支持") {
|
if err == nil || !strings.Contains(err.Error(), "不支持") {
|
||||||
t.Fatalf("error = %v", err)
|
t.Fatalf("error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,10 +163,9 @@ func (r *rollback) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newReloadableDB attaches the row-level data scope callbacks the pool needs
|
// newReloadableDB owns only the hot-swappable database handle. Domain-specific
|
||||||
// before it becomes readable. Data.replacePrimaryDB is the reload counterpart.
|
// callbacks are attached by Data when a pool becomes active.
|
||||||
func newReloadableDB(db *gorm.DB, enqueue dataScopeAuditEnqueue) *reloadable[*gorm.DB] {
|
func newReloadableDB(db *gorm.DB, _ ...dataScopeAuditEnqueue) *reloadable[*gorm.DB] {
|
||||||
registerDataScopeCallbacks(db, enqueue)
|
|
||||||
return newReloadable(db, closeGormDB)
|
return newReloadable(db, closeGormDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,13 +109,6 @@ func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID
|
|||||||
}
|
}
|
||||||
return out, total, nil
|
return out, total, nil
|
||||||
}
|
}
|
||||||
func (r *apiTokenRepo) DisableAPIToken(ctx context.Context, id uint) (string, error) {
|
|
||||||
var po apiTokenPO
|
|
||||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return po.Token, r.data.DB().WithContext(ctx).Model(&po).Update("status", false).Error
|
|
||||||
}
|
|
||||||
func (r *apiTokenRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint) error {
|
func (r *apiTokenRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint) error {
|
||||||
// Persist the blacklist row first, then mark the
|
// Persist the blacklist row first, then mark the
|
||||||
// API-token record disabled. The two writes are intentionally separate in
|
// API-token record disabled. The two writes are intentionally separate in
|
||||||
|
|||||||
@@ -191,12 +191,103 @@ func (r *exportRepo) QueryExport(ctx context.Context, t *system.ExportTemplate,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
var rows []map[string]any
|
|
||||||
preview := query.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Find(&[]map[string]any{}) })
|
preview := query.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Find(&[]map[string]any{}) })
|
||||||
err = query.Find(&rows).Error
|
rows, err := queryExportRows(query)
|
||||||
return rows, preview, err
|
return rows, preview, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func queryExportRows(query *gorm.DB) ([]map[string]any, error) {
|
||||||
|
rows, err := query.Rows()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
columnTypes, err := rows.ColumnTypes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]map[string]any, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
values := make([]any, len(columns))
|
||||||
|
destinations := make([]any, len(values))
|
||||||
|
for index := range values {
|
||||||
|
destinations[index] = &values[index]
|
||||||
|
}
|
||||||
|
if err := rows.Scan(destinations...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item := make(map[string]any, len(columns))
|
||||||
|
for index, column := range columns {
|
||||||
|
typeName := ""
|
||||||
|
if index < len(columnTypes) {
|
||||||
|
typeName = columnTypes[index].DatabaseTypeName()
|
||||||
|
}
|
||||||
|
item[column] = exportQueryValue(values[index], typeName)
|
||||||
|
}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportQueryValue(value any, databaseType string) any {
|
||||||
|
bytesValue, ok := value.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
raw := string(bytesValue)
|
||||||
|
typeName := strings.ToUpper(databaseType)
|
||||||
|
if strings.Contains(typeName, "INT") || strings.Contains(typeName, "SERIAL") {
|
||||||
|
if integer, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
||||||
|
return integer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(typeName, "DECIMAL") || strings.Contains(typeName, "NUMERIC") || strings.Contains(typeName, "NUMBER") || strings.Contains(typeName, "REAL") || strings.Contains(typeName, "DOUBLE") || strings.Contains(typeName, "FLOAT") {
|
||||||
|
if decimal, ok := exactExportFloat(raw); ok {
|
||||||
|
return decimal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func exactExportFloat(value string) (float64, bool) {
|
||||||
|
decimal, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return decimal, normalizeDecimal(value) == normalizeDecimal(strconv.FormatFloat(decimal, 'f', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeDecimal(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
negative := strings.HasPrefix(value, "-")
|
||||||
|
value = strings.TrimPrefix(value, "+")
|
||||||
|
value = strings.TrimPrefix(value, "-")
|
||||||
|
parts := strings.SplitN(value, ".", 2)
|
||||||
|
whole := strings.TrimLeft(parts[0], "0")
|
||||||
|
if whole == "" {
|
||||||
|
whole = "0"
|
||||||
|
}
|
||||||
|
fraction := ""
|
||||||
|
if len(parts) == 2 {
|
||||||
|
fraction = strings.TrimRight(parts[1], "0")
|
||||||
|
}
|
||||||
|
result := whole
|
||||||
|
if fraction != "" {
|
||||||
|
result += "." + fraction
|
||||||
|
}
|
||||||
|
if negative && result != "0" {
|
||||||
|
result = "-" + result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func exportColumns(raw string) ([]string, error) {
|
func exportColumns(raw string) ([]string, error) {
|
||||||
columns := make([]string, 0)
|
columns := make([]string, 0)
|
||||||
decoder := json.NewDecoder(strings.NewReader(raw))
|
decoder := json.NewDecoder(strings.NewReader(raw))
|
||||||
@@ -338,17 +429,6 @@ func (r *exportRepo) PreviewExport(ctx context.Context, t *system.ExportTemplate
|
|||||||
}
|
}
|
||||||
return query.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Find(&[]map[string]any{}) }), nil
|
return query.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Find(&[]map[string]any{}) }), nil
|
||||||
}
|
}
|
||||||
func parseTemplateColumns(raw string) map[string]string {
|
|
||||||
out := map[string]string{}
|
|
||||||
parts := strings.Split(strings.Trim(strings.TrimSpace(raw), "{}"), ",")
|
|
||||||
for _, part := range parts {
|
|
||||||
pair := strings.SplitN(part, ":", 2)
|
|
||||||
if len(pair) == 2 {
|
|
||||||
out[strings.Trim(strings.TrimSpace(pair[0]), `"`)] = strings.Trim(strings.TrimSpace(pair[1]), `"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
func (r *exportRepo) ImportExportRows(ctx context.Context, t *system.ExportTemplate, rows []map[string]any) error {
|
func (r *exportRepo) ImportExportRows(ctx context.Context, t *system.ExportTemplate, rows []map[string]any) error {
|
||||||
if err := system.ValidateExportTemplate(t); err != nil {
|
if err := system.ValidateExportTemplate(t); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestExportQueryValueUsesDatabaseType(t *testing.T) {
|
||||||
|
if value := exportQueryValue([]byte("42"), "VARCHAR"); value != "42" {
|
||||||
|
t.Fatalf("text value = %#v, want string 42", value)
|
||||||
|
}
|
||||||
|
if value, ok := exportQueryValue([]byte("42"), "BIGINT").(int64); !ok || value != 42 {
|
||||||
|
t.Fatalf("integer value = %#v", exportQueryValue([]byte("42"), "BIGINT"))
|
||||||
|
}
|
||||||
|
if value, ok := exportQueryValue([]byte("12.5"), "DECIMAL").(float64); !ok || value != 12.5 {
|
||||||
|
t.Fatalf("decimal value = %#v", exportQueryValue([]byte("12.5"), "DECIMAL"))
|
||||||
|
}
|
||||||
|
if value, ok := exportQueryValue([]byte("1234567890123456.78"), "DECIMAL").(string); !ok || value != "1234567890123456.78" {
|
||||||
|
t.Fatalf("high-precision decimal value = %#v", exportQueryValue([]byte("1234567890123456.78"), "DECIMAL"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,9 +40,18 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *syst
|
|||||||
if q.Username != "" {
|
if q.Username != "" {
|
||||||
db = db.Where("username LIKE ?", "%"+q.Username+"%")
|
db = db.Where("username LIKE ?", "%"+q.Username+"%")
|
||||||
}
|
}
|
||||||
|
if q.IP != "" {
|
||||||
|
db = db.Where("ip LIKE ?", "%"+q.IP+"%")
|
||||||
|
}
|
||||||
if q.Status != nil {
|
if q.Status != nil {
|
||||||
db = db.Where("status = ?", *q.Status)
|
db = db.Where("status = ?", *q.Status)
|
||||||
}
|
}
|
||||||
|
if q.StartCreatedAt != nil {
|
||||||
|
db = db.Where("created_at >= ?", *q.StartCreatedAt)
|
||||||
|
}
|
||||||
|
if q.EndCreatedAt != nil {
|
||||||
|
db = db.Where("created_at <= ?", *q.EndCreatedAt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var total int64
|
var total int64
|
||||||
if err := db.Count(&total).Error; err != nil {
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
|||||||
@@ -64,13 +64,6 @@ func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*system.MediaFile,
|
|||||||
}
|
}
|
||||||
return mediaFromPO(po), nil
|
return mediaFromPO(po), nil
|
||||||
}
|
}
|
||||||
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 system.MediaFilter) ([]*system.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{})
|
db := r.data.DB().WithContext(ctx).Model(&mediaPO{})
|
||||||
if filter.Keyword != "" {
|
if filter.Keyword != "" {
|
||||||
|
|||||||
@@ -281,12 +281,6 @@ func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) e
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *menuRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) {
|
|
||||||
var ids []uint
|
|
||||||
err := r.data.DB().WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error
|
|
||||||
return ids, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAuthorityMenus reads the authority-menu query. It reads the
|
// ListAuthorityMenus reads the authority-menu query. It reads the
|
||||||
// target authority's relation rows first and then fetches only those base
|
// target authority's relation rows first and then fetches only those base
|
||||||
// menus. In particular, it deliberately does not call List: List applies the
|
// menus. In particular, it deliberately does not call List: List applies the
|
||||||
|
|||||||
@@ -33,40 +33,6 @@ type authorityButtonPO struct {
|
|||||||
|
|
||||||
func (authorityButtonPO) TableName() string { return "sys_authority_btns" }
|
func (authorityButtonPO) TableName() string { return "sys_authority_btns" }
|
||||||
|
|
||||||
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([]*system.MenuButton, 0, len(pos))
|
|
||||||
for _, po := range pos {
|
|
||||||
out = append(out, menuButtonFromPO(po))
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
func (r *permissionRepo) SetAuthorityButtons(ctx context.Context, aid uint, buttons map[uint][]uint) error {
|
|
||||||
if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, aid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := r.checkButtonAssignmentAuth(ctx, buttons); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Where("authority_id = ?", aid).Delete(&authorityButtonPO{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var links []authorityButtonPO
|
|
||||||
for menuID, ids := range buttons {
|
|
||||||
for _, id := range ids {
|
|
||||||
links = append(links, authorityButtonPO{AuthorityID: aid, MenuID: menuID, ButtonID: id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(links) > 0 {
|
|
||||||
return tx.Create(&links).Error
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
func (r *permissionRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
|
func (r *permissionRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
|
||||||
var rows []authorityButtonPO
|
var rows []authorityButtonPO
|
||||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Find(&rows).Error; err != nil {
|
if err := r.data.DB().WithContext(ctx).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Find(&rows).Error; err != nil {
|
||||||
@@ -110,12 +76,6 @@ func (r *permissionRepo) CanRemoveButton(ctx context.Context, id string) (bool,
|
|||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
func (r *permissionRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, error) {
|
|
||||||
var ids []uint
|
|
||||||
err := r.data.DB().WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ?", aid).Pluck("sys_base_menu_btn_id", &ids).Error
|
|
||||||
return ids, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *permissionRepo) checkButtonAssignmentAuth(ctx context.Context, buttons map[uint][]uint) error {
|
func (r *permissionRepo) checkButtonAssignmentAuth(ctx context.Context, buttons map[uint][]uint) error {
|
||||||
actor, allowedAuthorities, strict, err := (&authorityAccessRepo{data: r.data}).strictAuthorityAccess(ctx)
|
actor, allowedAuthorities, strict, err := (&authorityAccessRepo{data: r.data}).strictAuthorityAccess(ctx)
|
||||||
if err != nil || !strict {
|
if err != nil || !strict {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func newTransactionTestData(t *testing.T) *Data {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserAuthorityWritesAreAtomic(t *testing.T) {
|
func TestUserAuthorityWrites(t *testing.T) {
|
||||||
data := newTransactionTestData(t)
|
data := newTransactionTestData(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{{AuthorityID: 888, AuthorityName: "admin"}, {AuthorityID: 999, AuthorityName: "operator"}}).Error; err != nil {
|
if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{{AuthorityID: 888, AuthorityName: "admin"}, {AuthorityID: 999, AuthorityName: "operator"}}).Error; err != nil {
|
||||||
@@ -44,7 +44,10 @@ func TestUserAuthorityWritesAreAtomic(t *testing.T) {
|
|||||||
// With strict authority checks disabled, the reference implementation writes the supplied join rows
|
// With strict authority checks disabled, the reference implementation writes the supplied join rows
|
||||||
// without first requiring every role record to exist.
|
// without first requiring every role record to exist.
|
||||||
created.NickName = "compatible update"
|
created.NickName = "compatible update"
|
||||||
if err = repo.UpdateUserWithAuthorities(ctx, created, []uint{888, 123456}); err != nil {
|
if err = repo.UpdateUser(ctx, created); err != nil {
|
||||||
|
t.Fatalf("user update failed: %v", err)
|
||||||
|
}
|
||||||
|
if err = repo.SetUserAuthorities(ctx, created.ID, []uint{888, 123456}); err != nil {
|
||||||
t.Fatalf("missing authority ID was rejected in non-strict mode: %v", err)
|
t.Fatalf("missing authority ID was rejected in non-strict mode: %v", err)
|
||||||
}
|
}
|
||||||
var persisted userPO
|
var persisted userPO
|
||||||
|
|||||||
@@ -329,10 +329,6 @@ func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *sy
|
|||||||
return users, total, err
|
return users, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *userRepo) CreateUser(ctx context.Context, user *system.User) (*system.User, error) {
|
|
||||||
return r.CreateUserWithAuthorities(ctx, user, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *system.User, authorityIDs []uint) (*system.User, error) {
|
func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *system.User, authorityIDs []uint) (*system.User, error) {
|
||||||
requestedAuthorities := append([]uint{user.AuthorityID}, authorityIDs...)
|
requestedAuthorities := append([]uint{user.AuthorityID}, authorityIDs...)
|
||||||
if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDsAuth(ctx, requestedAuthorities); err != nil {
|
if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDsAuth(ctx, requestedAuthorities); err != nil {
|
||||||
@@ -489,24 +485,6 @@ func (r *userRepo) updateUser(tx *gorm.DB, user *system.User) error {
|
|||||||
return tx.Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
return tx.Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *system.User, authorityIDs []uint) error {
|
|
||||||
access := &authorityAccessRepo{data: r.data}
|
|
||||||
if err := r.checkUserUpdateAuth(ctx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := access.checkAuthorityIDsAuth(ctx, authorityIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := r.updateUser(tx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(authorityIDs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return setUserAuthorities(tx, user.ID, authorityIDs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||||
if err := (&authorityAccessRepo{data: r.data}).checkUserIDAuth(ctx, id, false); err != nil {
|
if err := (&authorityAccessRepo{data: r.data}).checkUserIDAuth(ctx, id, false); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -561,7 +539,7 @@ func (r *userRepo) SetUserAuthorities(ctx context.Context, id uint, authorityIDs
|
|||||||
|
|
||||||
func setUserAuthorities(tx *gorm.DB, id uint, authorityIDs []uint) error {
|
func setUserAuthorities(tx *gorm.DB, id uint, authorityIDs []uint) error {
|
||||||
if len(authorityIDs) == 0 {
|
if len(authorityIDs) == 0 {
|
||||||
return errors.New("角色不能为空")
|
return system.ErrAuthoritiesRequired
|
||||||
}
|
}
|
||||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -80,10 +80,6 @@ func TestStrictUserUpdateRejectsPrimaryAuthorityOutsideManagedTree(t *testing.T)
|
|||||||
if err := repo.UpdateUser(ctx, &system.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}); err == nil {
|
if err := repo.UpdateUser(ctx, &system.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}); err == nil {
|
||||||
t.Fatal("UpdateUser() accepted a primary authority outside the managed tree")
|
t.Fatal("UpdateUser() accepted a primary authority outside the managed tree")
|
||||||
}
|
}
|
||||||
if err := repo.UpdateUserWithAuthorities(ctx, &system.User{ID: users[1].ID, NickName: "changed", AuthorityID: siblingID}, []uint{childID}); err == nil {
|
|
||||||
t.Fatal("UpdateUserWithAuthorities() accepted a primary authority outside the managed tree")
|
|
||||||
}
|
|
||||||
|
|
||||||
var stored userPO
|
var stored userPO
|
||||||
if err := data.gormDB.WithContext(context.Background()).First(&stored, users[1].ID).Error; err != nil {
|
if err := data.gormDB.WithContext(context.Background()).First(&stored, users[1].ID).Error; err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -179,24 +179,32 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||||||
}
|
}
|
||||||
return bundle, nil
|
return bundle, nil
|
||||||
}
|
}
|
||||||
func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *system.VersionBundle) error {
|
func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *system.VersionBundle, audit *system.Version) error {
|
||||||
db := r.data.DB().WithContext(ctx)
|
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if len(b.Menus) > 0 {
|
if len(b.Menus) > 0 {
|
||||||
if err := db.Transaction(func(tx *gorm.DB) error { return importVersionMenus(tx, b.Menus, 0) }); err != nil {
|
if err := importVersionMenus(tx, b.Menus, 0); err != nil {
|
||||||
return &system.VersionStageError{Stage: system.VersionStageMenus, Err: err}
|
return &system.VersionStageError{Stage: system.VersionStageMenus, Err: err}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if len(b.APIs) > 0 {
|
||||||
if len(b.APIs) > 0 {
|
if err := importVersionAPIs(tx, b.APIs); err != nil {
|
||||||
if err := db.Transaction(func(tx *gorm.DB) error { return importVersionAPIs(tx, b.APIs) }); err != nil {
|
return &system.VersionStageError{Stage: system.VersionStageAPIs, Err: err}
|
||||||
return &system.VersionStageError{Stage: system.VersionStageAPIs, Err: err}
|
}
|
||||||
}
|
}
|
||||||
}
|
if len(b.Dictionaries) > 0 {
|
||||||
if len(b.Dictionaries) > 0 {
|
if err := importVersionDictionaries(tx, b.Dictionaries); err != nil {
|
||||||
if err := db.Transaction(func(tx *gorm.DB) error { return importVersionDictionaries(tx, b.Dictionaries) }); err != nil {
|
return &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err}
|
||||||
return &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err}
|
}
|
||||||
}
|
}
|
||||||
}
|
if audit != nil {
|
||||||
return nil
|
po := versionPO{Name: audit.Name, Code: audit.Code, Description: audit.Description, Data: audit.Data}
|
||||||
|
if err := tx.Create(&po).Error; err != nil {
|
||||||
|
return &system.VersionStageError{Stage: system.VersionStageSave, Err: err}
|
||||||
|
}
|
||||||
|
audit.ID = po.ID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func importVersionMenus(tx *gorm.DB, items []*system.Menu, parentID uint) error {
|
func importVersionMenus(tx *gorm.DB, items []*system.Menu, parentID uint) error {
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ func (r *emailRepo) email() *config.Email {
|
|||||||
return config.Email
|
return config.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *emailRepo) Enabled() bool {
|
|
||||||
return emailEnabled(r.email())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *emailRepo) DefaultRecipients() []string {
|
func (r *emailRepo) DefaultRecipients() []string {
|
||||||
config := r.email()
|
config := r.email()
|
||||||
if config == nil || strings.TrimSpace(config.To) == "" {
|
if config == nil || strings.TrimSpace(config.To) == "" {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package payment
|
package payment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
bizpayment "kra/internal/biz/payment"
|
bizpayment "kra/internal/biz/payment"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -9,12 +8,11 @@ import (
|
|||||||
|
|
||||||
// Adapter is the provider boundary used by the payment repository. Provider
|
// Adapter is the provider boundary used by the payment repository. Provider
|
||||||
// SDK types stay in this package and are normalized to biz/payment results.
|
// SDK types stay in this package and are normalized to biz/payment results.
|
||||||
type Adapter interface {
|
type Adapter = bizpayment.PaymentAdapter
|
||||||
Create(context.Context, *bizpayment.PaymentRequest, map[string]any) (*bizpayment.PaymentResult, error)
|
|
||||||
Query(context.Context, string, map[string]any) (*bizpayment.PaymentResult, error)
|
type Factory struct{}
|
||||||
Refund(context.Context, *bizpayment.PaymentRefundRequest, map[string]any) (*bizpayment.PaymentResult, error)
|
|
||||||
Callback(context.Context, *bizpayment.PaymentCallback, map[string]any) (*bizpayment.PaymentResult, error)
|
func NewFactory() *Factory { return &Factory{} }
|
||||||
}
|
|
||||||
|
|
||||||
type adapterFactory func() Adapter
|
type adapterFactory func() Adapter
|
||||||
|
|
||||||
@@ -51,3 +49,7 @@ func New(provider string) (Adapter, error) {
|
|||||||
}
|
}
|
||||||
return factory(), nil
|
return factory(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*Factory) New(provider string) (bizpayment.PaymentAdapter, error) {
|
||||||
|
return New(provider)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
bizpayment "kra/internal/biz/payment"
|
bizpayment "kra/internal/biz/payment"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -569,21 +568,6 @@ func verifyAlipayResponse(raw []byte, method string, c map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func alipaySignContent(v url.Values) string {
|
|
||||||
keys := make([]string, 0, len(v))
|
|
||||||
for key := range v {
|
|
||||||
if key != "sign" && v.Get(key) != "" {
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
parts := make([]string, 0, len(keys))
|
|
||||||
for _, key := range keys {
|
|
||||||
parts = append(parts, key+"="+v.Get(key))
|
|
||||||
}
|
|
||||||
return strings.Join(parts, "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyAlipay(fields map[string]string, c map[string]any) error {
|
func verifyAlipay(fields map[string]string, c map[string]any) error {
|
||||||
if text(c, "app_id") == "" || text(c, "public_key") == "" || fields["sign"] == "" {
|
if text(c, "app_id") == "" || text(c, "public_key") == "" || fields["sign"] == "" {
|
||||||
return errors.New("支付宝回调验签配置或 sign 缺失")
|
return errors.New("支付宝回调验签配置或 sign 缺失")
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"kra/pkg/paymentkit"
|
"kra/internal/paymentkit"
|
||||||
|
|
||||||
gopayAlipay "github.com/go-pay/gopay/alipay"
|
gopayAlipay "github.com/go-pay/gopay/alipay"
|
||||||
gopayDouyin "github.com/go-pay/gopay/douyin"
|
gopayDouyin "github.com/go-pay/gopay/douyin"
|
||||||
@@ -177,7 +177,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) {
|
|||||||
t.Errorf("read request: %v", err)
|
t.Errorf("read request: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestValues, err = paymentutil.XMLValues(body)
|
requestValues, err = paymentkit.XMLValues(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("parse request: %v", err)
|
t.Errorf("parse request: %v", err)
|
||||||
return
|
return
|
||||||
@@ -190,7 +190,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
response := map[string]string{"return_code": "SUCCESS", "return_msg": "OK", "result_code": "SUCCESS", "prepay_id": "prepay"}
|
response := map[string]string{"return_code": "SUCCESS", "return_msg": "OK", "result_code": "SUCCESS", "prepay_id": "prepay"}
|
||||||
response["sign"] = wechatV2Sign(response, key, "MD5")
|
response["sign"] = wechatV2Sign(response, key, "MD5")
|
||||||
_, _ = w.Write(paymentutil.XMLEncode(response))
|
_, _ = w.Write(paymentkit.XMLEncode(response))
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) {
|
|||||||
if callbackValues["sign"] != originalSign {
|
if callbackValues["sign"] != originalSign {
|
||||||
t.Fatal("verification mutated source values")
|
t.Fatal("verification mutated source values")
|
||||||
}
|
}
|
||||||
callbackBody := paymentutil.XMLEncode(callbackValues)
|
callbackBody := paymentkit.XMLEncode(callbackValues)
|
||||||
callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{Body: callbackBody}, map[string]any{
|
callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{Body: callbackBody}, map[string]any{
|
||||||
"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
|
"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
|
||||||
})
|
})
|
||||||
@@ -243,7 +243,7 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) {
|
|||||||
t.Errorf("read request: %v", err)
|
t.Errorf("read request: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestValues, err = paymentutil.XMLValues(body)
|
requestValues, err = paymentkit.XMLValues(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("parse request: %v", err)
|
t.Errorf("parse request: %v", err)
|
||||||
return
|
return
|
||||||
@@ -263,7 +263,7 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) {
|
|||||||
"transaction_id": "WX-TRANSACTION-1", "total_fee": "123", "cash_fee": "123", "fee_type": "CNY",
|
"transaction_id": "WX-TRANSACTION-1", "total_fee": "123", "cash_fee": "123", "fee_type": "CNY",
|
||||||
}
|
}
|
||||||
response["sign"] = wechatV2Sign(response, key, "MD5")
|
response["sign"] = wechatV2Sign(response, key, "MD5")
|
||||||
_, _ = w.Write(paymentutil.XMLEncode(response))
|
_, _ = w.Write(paymentkit.XMLEncode(response))
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@@ -344,7 +344,7 @@ func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
tc.mutate(response)
|
tc.mutate(response)
|
||||||
response["sign"] = wechatV2Sign(response, key, "MD5")
|
response["sign"] = wechatV2Sign(response, key, "MD5")
|
||||||
_, _ = w.Write(paymentutil.XMLEncode(response))
|
_, _ = w.Write(paymentkit.XMLEncode(response))
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,39 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
bizpayment "kra/internal/biz/payment"
|
bizpayment "kra/internal/biz/payment"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
gopayWechat "github.com/go-pay/gopay/wechat"
|
||||||
|
gopayWechatV3 "github.com/go-pay/gopay/wechat/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func wechatV2Sign(values map[string]string, key, signType string) string {
|
||||||
|
body := toGoPayBodyMap(values)
|
||||||
|
body.Remove("sign")
|
||||||
|
return gopayWechat.GetReleaseSign(key, wechatV2SignType(signType), body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptWechatV3(ciphertext, nonce, associatedData, key string) ([]byte, error) {
|
||||||
|
return gopayWechatV3.V3DecryptNotifyCipherTextToBytes(ciphertext, nonce, associatedData, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alipaySignContent(values url.Values) string {
|
||||||
|
keys := make([]string, 0, len(values))
|
||||||
|
for key := range values {
|
||||||
|
if key != "sign" && values.Get(key) != "" {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
parts := make([]string, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
parts = append(parts, key+"="+values.Get(key))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "&")
|
||||||
|
}
|
||||||
|
|
||||||
func TestWechatV2SignOfficialStyle(t *testing.T) {
|
func TestWechatV2SignOfficialStyle(t *testing.T) {
|
||||||
values := map[string]string{
|
values := map[string]string{
|
||||||
"appid": "wxd930ea5d5a258f4f", "mch_id": "10000100", "device_info": "1000",
|
"appid": "wxd930ea5d5a258f4f", "mch_id": "10000100", "device_info": "1000",
|
||||||
|
|||||||
@@ -385,11 +385,6 @@ func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo strin
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func paypalOrderTradeNo(order *paypal.OrderDetail) string {
|
|
||||||
tradeNo, _ := paypalOrderIdentity(order)
|
|
||||||
return tradeNo
|
|
||||||
}
|
|
||||||
|
|
||||||
func paypalOrderIdentity(order *paypal.OrderDetail) (string, error) {
|
func paypalOrderIdentity(order *paypal.OrderDetail) (string, error) {
|
||||||
if order == nil {
|
if order == nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -648,17 +643,6 @@ func paypalAmountScale(currency string, c map[string]any) (int64, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizePayPalOrderState(state string) string {
|
|
||||||
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
||||||
case "COMPLETED":
|
|
||||||
return "success"
|
|
||||||
case "VOIDED":
|
|
||||||
return "failed"
|
|
||||||
default:
|
|
||||||
return "pending"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizePayPalCaptureState(state string) string {
|
func normalizePayPalCaptureState(state string) string {
|
||||||
switch strings.ToUpper(strings.TrimSpace(state)) {
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
||||||
case "COMPLETED", "PARTIALLY_REFUNDED", "REFUNDED":
|
case "COMPLETED", "PARTIALLY_REFUNDED", "REFUNDED":
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"kra/pkg/paymentkit"
|
"kra/internal/paymentkit"
|
||||||
|
|
||||||
"github.com/go-pay/gopay"
|
"github.com/go-pay/gopay"
|
||||||
gopayQQ "github.com/go-pay/gopay/qq"
|
gopayQQ "github.com/go-pay/gopay/qq"
|
||||||
@@ -55,7 +55,7 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) {
|
|||||||
http.Error(w, "invalid XML", http.StatusBadRequest)
|
http.Error(w, "invalid XML", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
values, err := paymentutil.XMLValues(body)
|
values, err := paymentkit.XMLValues(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("parse request XML: %v", err)
|
t.Errorf("parse request XML: %v", err)
|
||||||
http.Error(w, "invalid XML", http.StatusBadRequest)
|
http.Error(w, "invalid XML", http.StatusBadRequest)
|
||||||
@@ -81,7 +81,7 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) {
|
|||||||
for key, value := range response {
|
for key, value := range response {
|
||||||
encoded[key] = fmt.Sprint(value)
|
encoded[key] = fmt.Sprint(value)
|
||||||
}
|
}
|
||||||
_, _ = w.Write(paymentutil.XMLEncode(encoded))
|
_, _ = w.Write(paymentkit.XMLEncode(encoded))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
result, err := (&qqAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
|
result, err := (&qqAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"kra/pkg/paymentkit"
|
"kra/internal/paymentkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func callbackFields(callback *bizpayment.PaymentCallback) map[string]string {
|
func callbackFields(callback *bizpayment.PaymentCallback) map[string]string {
|
||||||
@@ -56,57 +56,53 @@ func first(values map[string]string, keys ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func text(values map[string]any, key string) string {
|
func text(values map[string]any, key string) string {
|
||||||
return paymentutil.Text(values, key)
|
return paymentkit.Text(values, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstAny(values map[string]any, keys ...string) string {
|
func firstAny(values map[string]any, keys ...string) string {
|
||||||
return paymentutil.FirstText(values, keys...)
|
return paymentkit.FirstText(values, keys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compatibility shims keep channel adapters focused on protocol and
|
// Compatibility shims keep channel adapters focused on protocol and
|
||||||
// persistence concerns while stateless parsing rules live in paymentutil.
|
// persistence concerns while stateless parsing rules live in paymentkit.
|
||||||
func normalizePaymentStatus(value, fallback string) string {
|
func normalizePaymentStatus(value, fallback string) string {
|
||||||
return paymentutil.NormalizeStatus(value, fallback)
|
return paymentkit.NormalizeStatus(value, fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIntegerAmount(value string) (int64, error) {
|
func parseIntegerAmount(value string) (int64, error) {
|
||||||
return paymentutil.ParseIntegerAmount(value)
|
return paymentkit.ParseIntegerAmount(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDecimalAmount(value string, scale int64) (int64, error) {
|
func parseDecimalAmount(value string, scale int64) (int64, error) {
|
||||||
return paymentutil.ParseDecimalAmount(value, scale)
|
return paymentkit.ParseDecimalAmount(value, scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatDecimalAmount(amount, scale int64) (string, error) {
|
func formatDecimalAmount(amount, scale int64) (string, error) {
|
||||||
return paymentutil.FormatDecimalAmount(amount, scale)
|
return paymentkit.FormatDecimalAmount(amount, scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configuredInt64(values map[string]any, key string, fallback int64) int64 {
|
func configuredInt64(values map[string]any, key string, fallback int64) int64 {
|
||||||
return paymentutil.ConfiguredInt64(values, key, fallback)
|
return paymentkit.ConfiguredInt64(values, key, fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonObject(raw []byte) map[string]any {
|
func jsonObject(raw []byte) map[string]any {
|
||||||
return paymentutil.JSONObject(raw)
|
return paymentkit.JSONObject(raw)
|
||||||
}
|
|
||||||
|
|
||||||
func nestedString(value any, keys ...string) string {
|
|
||||||
return paymentutil.NestedString(value, keys...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueAtPath(value any, path string) any {
|
func valueAtPath(value any, path string) any {
|
||||||
return paymentutil.ValueAtPath(value, path)
|
return paymentkit.ValueAtPath(value, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringAtPath(value any, path string) string {
|
func stringAtPath(value any, path string) string {
|
||||||
return paymentutil.StringAtPath(value, path)
|
return paymentkit.StringAtPath(value, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configuredValues(values map[string]any, key string) []string {
|
func configuredValues(values map[string]any, key string) []string {
|
||||||
return paymentutil.ConfiguredValues(values, key)
|
return paymentkit.ConfiguredValues(values, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsFold(values []string, value string) bool {
|
func containsFold(values []string, value string) bool {
|
||||||
return paymentutil.ContainsFold(values, value)
|
return paymentkit.ContainsFold(values, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseConfiguredAmount reads a provider amount field using an explicit unit
|
// parseConfiguredAmount reads a provider amount field using an explicit unit
|
||||||
@@ -138,12 +134,3 @@ func parseConfiguredAmount(object any, config map[string]any, fieldKey, scaleKey
|
|||||||
}
|
}
|
||||||
return amount, true, nil
|
return amount, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func amountFromDecimalField(values map[string]any, key string, fallback int64) (int64, bool, error) {
|
|
||||||
value := strings.TrimSpace(fmt.Sprint(values[key]))
|
|
||||||
if value == "" || value == "<nil>" {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
amount, err := parseDecimalAmount(value, fallback)
|
|
||||||
return amount, true, err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/pkg/paymentkit"
|
"kra/internal/paymentkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type vendorProfile string
|
type vendorProfile string
|
||||||
@@ -133,8 +133,13 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay
|
|||||||
}
|
}
|
||||||
result := &bizpayment.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)}
|
result := &bizpayment.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)}
|
||||||
if endpointKey != "query_url" {
|
if endpointKey != "query_url" {
|
||||||
if object := jsonObject(body); object != nil {
|
operation := strings.TrimSuffix(endpointKey, "_url")
|
||||||
operation := strings.TrimSuffix(endpointKey, "_url")
|
object := jsonObject(body)
|
||||||
|
if object == nil {
|
||||||
|
if operation == "refund" {
|
||||||
|
return nil, fmt.Errorf("%s 退款响应不是 JSON 对象", a.provider)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
statusField := text(c, operation+"_status_field")
|
statusField := text(c, operation+"_status_field")
|
||||||
if statusField == "" {
|
if statusField == "" {
|
||||||
statusField = text(c, "query_status_field")
|
statusField = text(c, "query_status_field")
|
||||||
@@ -158,8 +163,20 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay
|
|||||||
case containsFold(configuredValues(c, operation+"_success_values"), state):
|
case containsFold(configuredValues(c, operation+"_success_values"), state):
|
||||||
result.Status = "created"
|
result.Status = "created"
|
||||||
default:
|
default:
|
||||||
result.Status = normalizePaymentStatus(state, "created")
|
if operation == "refund" {
|
||||||
|
normalized := normalizePaymentStatus(state, "")
|
||||||
|
switch normalized {
|
||||||
|
case "success", "pending", "failed":
|
||||||
|
result.Status = normalized
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s 退款响应状态无法识别: %s", a.provider, state)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.Status = normalizePaymentStatus(state, "created")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if operation == "refund" {
|
||||||
|
return nil, fmt.Errorf("%s 退款响应缺少业务状态", a.provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -273,18 +290,18 @@ func (a *vendorPaymentAdapter) signRequest(request *http.Request, payload map[st
|
|||||||
digest := sha256.Sum256([]byte(appID + timestamp + nonceValue + string(raw) + secret))
|
digest := sha256.Sum256([]byte(appID + timestamp + nonceValue + string(raw) + secret))
|
||||||
request.Header.Set("Authorization", "OPEN-BODY-SIG AppId="+appID+", Timestamp="+timestamp+", Nonce="+nonceValue+", Signature="+hex.EncodeToString(digest[:]))
|
request.Header.Set("Authorization", "OPEN-BODY-SIG AppId="+appID+", Timestamp="+timestamp+", Nonce="+nonceValue+", Signature="+hex.EncodeToString(digest[:]))
|
||||||
case vendorSFT:
|
case vendorSFT:
|
||||||
request.Header.Set("X-SFT-Sign", paymentutil.MD5Canonical(payload, secret))
|
request.Header.Set("X-SFT-Sign", paymentkit.MD5Canonical(payload, secret))
|
||||||
case vendorWechatGame:
|
case vendorWechatGame:
|
||||||
request.Header.Set("X-Wechat-Game-Sign", paymentutil.HMACSHA256Hex(raw, secret, false))
|
request.Header.Set("X-Wechat-Game-Sign", paymentkit.HMACSHA256Hex(raw, secret, false))
|
||||||
if token := text(c, "access_token"); token != "" {
|
if token := text(c, "access_token"); token != "" {
|
||||||
query := request.URL.Query()
|
query := request.URL.Query()
|
||||||
query.Set("access_token", token)
|
query.Set("access_token", token)
|
||||||
request.URL.RawQuery = query.Encode()
|
request.URL.RawQuery = query.Encode()
|
||||||
}
|
}
|
||||||
case vendorDouyinGame:
|
case vendorDouyinGame:
|
||||||
request.Header.Set("X-TT-Pay-Sign", paymentutil.MD5Canonical(payload, secret))
|
request.Header.Set("X-TT-Pay-Sign", paymentkit.MD5Canonical(payload, secret))
|
||||||
default:
|
default:
|
||||||
request.Header.Set("X-Payment-Sign", paymentutil.HMACSHA256Hex(raw, secret, false))
|
request.Header.Set("X-Payment-Sign", paymentkit.HMACSHA256Hex(raw, secret, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (a *vendorPaymentAdapter) verify(fields map[string]string, raw []byte, headers map[string]string, c map[string]any) error {
|
func (a *vendorPaymentAdapter) verify(fields map[string]string, raw []byte, headers map[string]string, c map[string]any) error {
|
||||||
@@ -327,9 +344,9 @@ func (a *vendorPaymentAdapter) verify(fields map[string]string, raw []byte, head
|
|||||||
values[key] = value
|
values[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actual = paymentutil.MD5Canonical(values, secret)
|
actual = paymentkit.MD5Canonical(values, secret)
|
||||||
default:
|
default:
|
||||||
actual = paymentutil.HMACSHA256Hex(raw, secret, false)
|
actual = paymentkit.HMACSHA256Hex(raw, secret, false)
|
||||||
}
|
}
|
||||||
if !hmac.Equal([]byte(strings.ToLower(expected)), []byte(strings.ToLower(actual))) {
|
if !hmac.Equal([]byte(strings.ToLower(expected)), []byte(strings.ToLower(actual))) {
|
||||||
return errors.New("支付回调签名校验失败")
|
return errors.New("支付回调签名校验失败")
|
||||||
|
|||||||
@@ -56,3 +56,23 @@ func TestVendorRefundMapsBusinessFailureStatus(t *testing.T) {
|
|||||||
t.Fatalf("Refund() result = %#v, want failed status", result)
|
t.Fatalf("Refund() result = %#v, want failed status", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVendorRefundRejectsUnknownBusinessStatus(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"status":"UNKNOWN"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
adapter := &vendorPaymentAdapter{provider: "vendor", profile: vendorSFT}
|
||||||
|
result, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{TradeNo: "trade-1", RefundNo: "refund-1", Amount: 100, TotalAmount: 100, Currency: "CNY"}, map[string]any{
|
||||||
|
"refund_url": server.URL,
|
||||||
|
"app_key": "secret",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "状态无法识别") {
|
||||||
|
t.Fatalf("Refund() error = %v, want unknown-status error", err)
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
t.Fatalf("Refund() result = %#v, want nil", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,12 +44,6 @@ func wechatV2SignType(value string) string {
|
|||||||
return gopayWechat.SignType_MD5
|
return gopayWechat.SignType_MD5
|
||||||
}
|
}
|
||||||
|
|
||||||
func wechatV2Sign(values map[string]string, key, signType string) string {
|
|
||||||
body := toGoPayBodyMap(values)
|
|
||||||
body.Remove("sign")
|
|
||||||
return gopayWechat.GetReleaseSign(key, wechatV2SignType(signType), body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *wechatV2Adapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
|
func (a *wechatV2Adapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
return nil, errors.New("微信支付 v2 下单请求为空")
|
return nil, errors.New("微信支付 v2 下单请求为空")
|
||||||
@@ -581,12 +575,6 @@ func nonce() string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%d", time.Now().UnixNano())
|
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
func stringOr(extra map[string]any, key, fallback string) string {
|
|
||||||
if value, ok := extra[key].(string); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
func stringMap(values map[string]any) map[string]string {
|
func stringMap(values map[string]any) map[string]string {
|
||||||
result := map[string]string{}
|
result := map[string]string{}
|
||||||
for key, value := range values {
|
for key, value := range values {
|
||||||
|
|||||||
@@ -596,10 +596,3 @@ func parseJSONInteger(object map[string]any, key string) (int64, bool, error) {
|
|||||||
amount, err := parseIntegerAmount(fmt.Sprint(raw))
|
amount, err := parseIntegerAmount(fmt.Sprint(raw))
|
||||||
return amount, true, err
|
return amount, true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptWechatV3(ciphertext, nonce, associatedData, key string) ([]byte, error) {
|
|
||||||
if len(key) != 32 {
|
|
||||||
return nil, errors.New("微信支付 v3 api_v3_key 必须为 32 字节")
|
|
||||||
}
|
|
||||||
return gopayWechatV3.V3DecryptNotifyCipherTextToBytes(ciphertext, nonce, associatedData, key)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
integrationbiz "kra/internal/biz/integration"
|
integrationbiz "kra/internal/biz/integration"
|
||||||
|
bizpayment "kra/internal/biz/payment"
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
"kra/internal/integration/cache"
|
"kra/internal/integration/cache"
|
||||||
"kra/internal/integration/email"
|
"kra/internal/integration/email"
|
||||||
mqintegration "kra/internal/integration/mq"
|
mqintegration "kra/internal/integration/mq"
|
||||||
|
paymentintegration "kra/internal/integration/payment"
|
||||||
"kra/internal/integration/storage"
|
"kra/internal/integration/storage"
|
||||||
websocketintegration "kra/internal/integration/websocket"
|
websocketintegration "kra/internal/integration/websocket"
|
||||||
"kra/pkg/mq"
|
"kra/pkg/mq"
|
||||||
@@ -20,6 +22,8 @@ import (
|
|||||||
var ProviderSet = wire.NewSet(
|
var ProviderSet = wire.NewSet(
|
||||||
cache.New,
|
cache.New,
|
||||||
email.NewEmailRepo,
|
email.NewEmailRepo,
|
||||||
|
paymentintegration.NewFactory,
|
||||||
|
wire.Bind(new(bizpayment.PaymentAdapterFactory), new(*paymentintegration.Factory)),
|
||||||
storage.NewFileStorage,
|
storage.NewFileStorage,
|
||||||
NewConnectivityTester,
|
NewConnectivityTester,
|
||||||
wire.Bind(new(integrationbiz.IntegrationConnectionTester), new(*ConnectivityTester)),
|
wire.Bind(new(integrationbiz.IntegrationConnectionTester), new(*ConnectivityTester)),
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ func Definition() module.Definition {
|
|||||||
return module.Definition{
|
return module.Definition{
|
||||||
Name: "task",
|
Name: "task",
|
||||||
Migrations: datatask.Migrations(),
|
Migrations: datatask.Migrations(),
|
||||||
TimedTasks: []module.TimedTask{
|
TimedTasks: TimedTasks(),
|
||||||
{Name: "ClearDB", Description: "定时清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Spec: "@daily", MethodName: "ClearDB", Enabled: true},
|
}
|
||||||
{Name: "CleanStaleUploads", Description: "定时清理过期大文件上传会话", Spec: "@hourly", MethodName: "CleanStaleUploads", Enabled: true},
|
}
|
||||||
},
|
|
||||||
|
func TimedTasks() []module.TimedTask {
|
||||||
|
return []module.TimedTask{
|
||||||
|
{Name: "ClearDB", Description: "定时清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Spec: "@daily", MethodName: "ClearDB", Enabled: true},
|
||||||
|
{Name: "CleanStaleUploads", Description: "定时清理过期大文件上传会话", Spec: "@hourly", MethodName: "CleanStaleUploads", Enabled: true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package paymentutil
|
package paymentkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -8,23 +8,17 @@ import (
|
|||||||
|
|
||||||
"kra/internal/config"
|
"kra/internal/config"
|
||||||
websocket "kra/internal/integration/websocket"
|
websocket "kra/internal/integration/websocket"
|
||||||
"kra/internal/server/handler"
|
"kra/internal/server/httpx"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
"kra/internal/server/router"
|
|
||||||
"kra/internal/server/staticfiles"
|
"kra/internal/server/staticfiles"
|
||||||
"kra/internal/service"
|
systemservice "kra/internal/service/system"
|
||||||
"kra/pkg/httpx"
|
|
||||||
platformmodule "kra/pkg/module"
|
platformmodule "kra/pkg/module"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
|
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewGinEngine(runtime *config.Store, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string) *gin.Engine {
|
func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.AccessControlService, auth middleware.TokenAuthenticator, security *systemservice.SecurityService, audit *systemservice.AuditRecorder, logger *slog.Logger, version string, routes platformmodule.RouteRegistrar, ws *websocket.Server) *gin.Engine {
|
||||||
return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, router.NewRoutes(handlers), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGinEngineWithRuntime(runtime *config.Store, access *service.AccessControlService, auth middleware.TokenAuthenticator, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes platformmodule.RouteRegistrar, ws *websocket.Server) *gin.Engine {
|
|
||||||
if runtime == nil {
|
if runtime == nil {
|
||||||
runtime = config.NewStore(nil)
|
runtime = config.NewStore(nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,16 @@ import (
|
|||||||
"kra/internal/config"
|
"kra/internal/config"
|
||||||
"kra/internal/routecatalog"
|
"kra/internal/routecatalog"
|
||||||
"kra/internal/server/handler"
|
"kra/internal/server/handler"
|
||||||
|
"kra/internal/server/router"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewGinEngine(runtime *config.Store, access *systemservice.AccessControlService, handlers *handler.Set, auth *systemservice.AuthService, security *systemservice.SecurityService, audit *systemservice.AuditRecorder, logger *slog.Logger, version string) *gin.Engine {
|
||||||
|
return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, router.NewRoutes(handlers), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func emptyHandlers() *handler.Set {
|
func emptyHandlers() *handler.Set {
|
||||||
return &handler.Set{
|
return &handler.Set{
|
||||||
Authority: &handler.Authority{}, Menu: &handler.Menu{}, API: &handler.API{},
|
Authority: &handler.Authority{}, Menu: &handler.Menu{}, API: &handler.API{},
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Announcement struct{ service *service.AnnouncementService }
|
type Announcement struct {
|
||||||
|
service *systemservice.AnnouncementService
|
||||||
|
}
|
||||||
|
|
||||||
func NewAnnouncement(service *service.AnnouncementService) *Announcement {
|
func NewAnnouncement(service *systemservice.AnnouncementService) *Announcement {
|
||||||
return &Announcement{service: service}
|
return &Announcement{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type API struct{ service *service.APIService }
|
type API struct{ service *systemservice.APIService }
|
||||||
|
|
||||||
func NewAPI(service *service.APIService) *API { return &API{service: service} }
|
func NewAPI(service *systemservice.APIService) *API { return &API{service: service} }
|
||||||
|
|
||||||
func (h *API) List(c *gin.Context) {
|
func (h *API) List(c *gin.Context) {
|
||||||
var req dto.APIListRequest
|
var req dto.APIListRequest
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type APIToken struct {
|
type APIToken struct {
|
||||||
service *service.TokenService
|
service *systemservice.TokenService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIToken(service *service.TokenService) *APIToken {
|
func NewAPIToken(service *systemservice.TokenService) *APIToken {
|
||||||
return &APIToken{service: service}
|
return &APIToken{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,20 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Audit struct {
|
type Audit struct {
|
||||||
service *service.AuditService
|
service *systemservice.AuditService
|
||||||
recorder *service.AuditRecorder
|
recorder *systemservice.AuditRecorder
|
||||||
logs *service.LogViewerService
|
logs *systemservice.LogViewerService
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAudit(service *service.AuditService, recorder *service.AuditRecorder, logs *service.LogViewerService, logger *slog.Logger) *Audit {
|
func NewAudit(service *systemservice.AuditService, recorder *systemservice.AuditRecorder, logs *systemservice.LogViewerService, logger *slog.Logger) *Audit {
|
||||||
return &Audit{service: service, recorder: recorder, logs: logs, logger: logger}
|
return &Audit{service: service, recorder: recorder, logs: logs, logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ func (h *Audit) Logins(c *gin.Context) {
|
|||||||
Fail(c, err.Error())
|
Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items, total, err := h.service.LoginsFilter(c.Request.Context(), req.Page, req.PageSize, req.Username, req.Status)
|
items, total, err := h.service.LoginsFilter(c.Request.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fail(c, "获取失败")
|
Fail(c, "获取失败")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Authority struct{ service *service.AuthorityService }
|
type Authority struct {
|
||||||
|
service *systemservice.AuthorityService
|
||||||
|
}
|
||||||
|
|
||||||
func NewAuthority(service *service.AuthorityService) *Authority { return &Authority{service: service} }
|
func NewAuthority(service *systemservice.AuthorityService) *Authority {
|
||||||
|
return &Authority{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Authority) List(c *gin.Context) {
|
func (h *Authority) List(c *gin.Context) {
|
||||||
items, err := h.service.Authorities(c.Request.Context())
|
items, err := h.service.Authorities(c.Request.Context())
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dictionary struct{ service *service.DictionaryService }
|
type Dictionary struct {
|
||||||
|
service *systemservice.DictionaryService
|
||||||
|
}
|
||||||
|
|
||||||
func NewDictionary(service *service.DictionaryService) *Dictionary {
|
func NewDictionary(service *systemservice.DictionaryService) *Dictionary {
|
||||||
return &Dictionary{service: service}
|
return &Dictionary{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Email struct{ service *service.EmailService }
|
type Email struct{ service *systemservice.EmailService }
|
||||||
|
|
||||||
func NewEmail(service *service.EmailService) *Email { return &Email{service: service} }
|
func NewEmail(service *systemservice.EmailService) *Email { return &Email{service: service} }
|
||||||
|
|
||||||
func (h *Email) Test(c *gin.Context) {
|
func (h *Email) Test(c *gin.Context) {
|
||||||
if err := h.service.Test(c.Request.Context()); err != nil {
|
if err := h.service.Test(c.Request.Context()); err != nil {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -13,10 +13,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Export struct {
|
type Export struct {
|
||||||
service *service.ExportService
|
service *systemservice.ExportService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExport(service *service.ExportService) *Export {
|
func NewExport(service *systemservice.ExportService) *Export {
|
||||||
return &Export{service: service}
|
return &Export{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,9 +214,9 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
|
|||||||
value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank)
|
value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, service.ErrExportTokenMalformed):
|
case errors.Is(err, systemservice.ErrExportTokenMalformed):
|
||||||
Fail(c, "解析导出参数失败")
|
Fail(c, "解析导出参数失败")
|
||||||
case errors.Is(err, service.ErrExportTokenType):
|
case errors.Is(err, systemservice.ErrExportTokenType):
|
||||||
Fail(c, "token类型错误")
|
Fail(c, "token类型错误")
|
||||||
default:
|
default:
|
||||||
Fail(c, "导出token无效或已过期")
|
Fail(c, "导出token无效或已过期")
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
|
"kra/internal/server/httpx"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
"kra/pkg/httpx"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
integrationservice "kra/internal/service/integration"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationConfig struct {
|
type IntegrationConfig struct {
|
||||||
service *service.IntegrationConfigService
|
service *integrationservice.IntegrationConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIntegrationConfig(service *service.IntegrationConfigService) *IntegrationConfig {
|
func NewIntegrationConfig(service *integrationservice.IntegrationConfigService) *IntegrationConfig {
|
||||||
return &IntegrationConfig{service: service}
|
return &IntegrationConfig{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Media struct{ service *service.MediaService }
|
type Media struct{ service *systemservice.MediaService }
|
||||||
|
|
||||||
func NewMedia(service *service.MediaService) *Media { return &Media{service: service} }
|
func NewMedia(service *systemservice.MediaService) *Media { return &Media{service: service} }
|
||||||
|
|
||||||
func (h *Media) limitMultipartBody(c *gin.Context) int64 {
|
func (h *Media) limitMultipartBody(c *gin.Context) int64 {
|
||||||
limit := h.service.MediaConfig().EffectiveMaxFileSize()
|
limit := h.service.MediaConfig().EffectiveMaxFileSize()
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Menu struct{ service *service.MenuService }
|
type Menu struct{ service *systemservice.MenuService }
|
||||||
|
|
||||||
func NewMenu(service *service.MenuService) *Menu { return &Menu{service: service} }
|
func NewMenu(service *systemservice.MenuService) *Menu { return &Menu{service: service} }
|
||||||
|
|
||||||
func validateMenuRequest(req *dto.MenuRequest) error {
|
func validateMenuRequest(req *dto.MenuRequest) error {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Navigation struct{ service *service.UserService }
|
type Navigation struct{ service *systemservice.UserService }
|
||||||
|
|
||||||
func NewNavigation(service *service.UserService) *Navigation { return &Navigation{service: service} }
|
func NewNavigation(service *systemservice.UserService) *Navigation {
|
||||||
|
return &Navigation{service: service}
|
||||||
|
}
|
||||||
func (h *Navigation) Menu(c *gin.Context) {
|
func (h *Navigation) Menu(c *gin.Context) {
|
||||||
claims := Claims(c)
|
claims := Claims(c)
|
||||||
if claims == nil {
|
if claims == nil {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
departments *service.DepartmentService
|
departments *systemservice.DepartmentService
|
||||||
positions *service.PositionService
|
positions *systemservice.PositionService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOrganization(departments *service.DepartmentService, positions *service.PositionService) *Organization {
|
func NewOrganization(departments *systemservice.DepartmentService, positions *systemservice.PositionService) *Organization {
|
||||||
return &Organization{departments: departments, positions: positions}
|
return &Organization{departments: departments, positions: positions}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Parameter struct {
|
type Parameter struct {
|
||||||
service *service.ParameterService
|
service *systemservice.ParameterService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParameter(service *service.ParameterService) *Parameter {
|
func NewParameter(service *systemservice.ParameterService) *Parameter {
|
||||||
return &Parameter{service: service}
|
return &Parameter{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
paymentservice "kra/internal/service/payment"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Payment struct{ service *service.PaymentService }
|
type Payment struct {
|
||||||
|
service *paymentservice.PaymentService
|
||||||
|
}
|
||||||
|
|
||||||
func NewPayment(service *service.PaymentService) *Payment { return &Payment{service: service} }
|
func NewPayment(service *paymentservice.PaymentService) *Payment { return &Payment{service: service} }
|
||||||
func (h *Payment) Order(c *gin.Context) {
|
func (h *Payment) Order(c *gin.Context) {
|
||||||
var req dto.PaymentQueryRequest
|
var req dto.PaymentQueryRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@@ -148,7 +150,8 @@ func (h *Payment) Callback(c *gin.Context) {
|
|||||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8<<20)
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8<<20)
|
||||||
raw, err := io.ReadAll(c.Request.Body)
|
raw, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writePaymentCallbackAck(c, service.DefaultPaymentCallbackAck(c.Param("provider"), false))
|
_ = c.Error(err)
|
||||||
|
writePaymentCallbackAck(c, paymentservice.DefaultPaymentCallbackAck(c.Param("provider"), false))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
headers := map[string]string{}
|
headers := map[string]string{}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Permission struct{ service *service.PermissionService }
|
type Permission struct {
|
||||||
|
service *systemservice.PermissionService
|
||||||
|
}
|
||||||
|
|
||||||
func NewPermission(service *service.PermissionService) *Permission {
|
func NewPermission(service *systemservice.PermissionService) *Permission {
|
||||||
return &Permission{service: service}
|
return &Permission{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,21 +9,21 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mojocn/base64Captcha"
|
"github.com/mojocn/base64Captcha"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Public struct {
|
type Public struct {
|
||||||
auth *service.AuthService
|
auth *systemservice.AuthService
|
||||||
system *service.SystemConfigService
|
system *systemservice.SystemConfigService
|
||||||
settings *service.SecurityService
|
settings *systemservice.SecurityService
|
||||||
store *captchaStore
|
store *captchaStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPublic(auth *service.AuthService, system *service.SystemConfigService, settings *service.SecurityService) *Public {
|
func NewPublic(auth *systemservice.AuthService, system *systemservice.SystemConfigService, settings *systemservice.SecurityService) *Public {
|
||||||
return &Public{auth: auth, system: system, settings: settings, store: &captchaStore{service: settings}}
|
return &Public{auth: auth, system: system, settings: settings, store: &captchaStore{service: settings}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type captchaStore struct {
|
type captchaStore struct {
|
||||||
service *service.SecurityService
|
service *systemservice.SecurityService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *captchaStore) Set(id, value string) error {
|
func (s *captchaStore) Set(id, value string) error {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
"kra/internal/service"
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct{ tokens *service.TokenService }
|
type Session struct{ tokens *systemservice.TokenService }
|
||||||
|
|
||||||
func NewSession(tokens *service.TokenService) *Session { return &Session{tokens: tokens} }
|
func NewSession(tokens *systemservice.TokenService) *Session { return &Session{tokens: tokens} }
|
||||||
func (h *Session) Logout(c *gin.Context) {
|
func (h *Session) Logout(c *gin.Context) {
|
||||||
token := middleware.RequestToken(c, false)
|
token := middleware.RequestToken(c, false)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SystemConfig struct {
|
type SystemConfig struct {
|
||||||
system *service.SystemConfigService
|
system *systemservice.SystemConfigService
|
||||||
settings *service.SecurityService
|
settings *systemservice.SecurityService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemConfig(system *service.SystemConfigService, settings *service.SecurityService) *SystemConfig {
|
func NewSystemConfig(system *systemservice.SystemConfigService, settings *systemservice.SecurityService) *SystemConfig {
|
||||||
return &SystemConfig{system: system, settings: settings}
|
return &SystemConfig{system: system, settings: settings}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
taskservice "kra/internal/service/task"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
service *service.TaskService
|
service *taskservice.TaskService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTask(service *service.TaskService) *Task {
|
func NewTask(service *taskservice.TaskService) *Task {
|
||||||
return &Task{service: service}
|
return &Task{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
"errors"
|
||||||
|
"kra/internal/biz/system"
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,11 +12,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
service *service.UserService
|
service *systemservice.UserService
|
||||||
auth *service.AuthService
|
auth *systemservice.AuthService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(user *service.UserService, auth *service.AuthService) *User {
|
func NewUser(user *systemservice.UserService, auth *systemservice.AuthService) *User {
|
||||||
return &User{service: user, auth: auth}
|
return &User{service: user, auth: auth}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ func (h *User) Create(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
result, err := h.service.CreateUserRequest(c.Request.Context(), &req)
|
result, err := h.service.CreateUserRequest(c.Request.Context(), &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if service.IsPasswordPolicyError(err) {
|
if systemservice.IsPasswordPolicyError(err) {
|
||||||
Fail(c, err.Error())
|
Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -141,7 +143,7 @@ func (h *User) ResetPassword(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.service.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil {
|
if err := h.service.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil {
|
||||||
if service.IsPasswordPolicyError(err) {
|
if systemservice.IsPasswordPolicyError(err) {
|
||||||
Fail(c, err.Error())
|
Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -171,7 +173,7 @@ func (h *User) ChangePassword(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.service.ChangePassword(c.Request.Context(), claims.ID, req.Password, req.NewPassword); err != nil {
|
if err := h.service.ChangePassword(c.Request.Context(), claims.ID, req.Password, req.NewPassword); err != nil {
|
||||||
if service.IsPasswordPolicyError(err) {
|
if systemservice.IsPasswordPolicyError(err) {
|
||||||
Fail(c, err.Error())
|
Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -206,7 +208,11 @@ func (h *User) SetAuthorities(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.service.SetUserAuthorities(c.Request.Context(), req.ID, req.AuthorityIDs); err != nil {
|
if err := h.service.SetUserAuthorities(c.Request.Context(), req.ID, req.AuthorityIDs); err != nil {
|
||||||
Fail(c, "修改失败")
|
if errors.Is(err, system.ErrAuthoritiesRequired) {
|
||||||
|
Fail(c, err.Error())
|
||||||
|
} else {
|
||||||
|
Fail(c, "修改失败")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Write(c, CodeSuccess, gin.H{}, "修改成功")
|
Write(c, CodeSuccess, gin.H{}, "修改成功")
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version struct{ service *service.VersionService }
|
type Version struct{ service *systemservice.VersionService }
|
||||||
|
|
||||||
func NewVersion(service *service.VersionService) *Version { return &Version{service: service} }
|
func NewVersion(service *systemservice.VersionService) *Version { return &Version{service: service} }
|
||||||
|
|
||||||
func versionStage(err error) (system.VersionStage, bool) {
|
func versionStage(err error) (system.VersionStage, bool) {
|
||||||
var stageErr *system.VersionStageError
|
var stageErr *system.VersionStageError
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
httpx "kra/pkg/httpx"
|
httpx "kra/internal/server/httpx"
|
||||||
|
|
||||||
"kra/internal/config"
|
"kra/internal/config"
|
||||||
"kra/internal/utils/routepath"
|
"kra/internal/utils/routepath"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
httpx "kra/pkg/httpx"
|
httpx "kra/internal/server/httpx"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ import (
|
|||||||
|
|
||||||
"kra/internal/config"
|
"kra/internal/config"
|
||||||
"kra/internal/routecatalog"
|
"kra/internal/routecatalog"
|
||||||
"kra/internal/service"
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
systemservice "kra/internal/service/system"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ctxOperationAuditPersistFailedKey = "operation_audit_persist_failed"
|
const ctxOperationAuditPersistFailedKey = "operation_audit_persist_failed"
|
||||||
|
|
||||||
func OperationAudit(runtime *config.Store, recorder *service.AuditRecorder) gin.HandlerFunc {
|
func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if runtime == nil || recorder == nil {
|
if runtime == nil || recorder == nil {
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
httpx "kra/pkg/httpx"
|
httpx "kra/internal/server/httpx"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -68,7 +68,3 @@ func redactJSON(raw []byte, contentType string, limit int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stringValue(value any) string { text, _ := value.(string); return text }
|
func stringValue(value any) string { text, _ := value.(string); return text }
|
||||||
|
|
||||||
func isBootstrapPath(path string) bool {
|
|
||||||
return strings.HasSuffix(path, "/health") || strings.Contains(path, "/init/")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
httpx "kra/pkg/httpx"
|
httpx "kra/internal/server/httpx"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kra/internal/service"
|
httpx "kra/internal/server/httpx"
|
||||||
httpx "kra/pkg/httpx"
|
systemservice "kra/internal/service/system"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc {
|
func SecurityRateLimit(settings *systemservice.SecurityService) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
path := strings.TrimSuffix(c.Request.URL.Path, "/")
|
path := strings.TrimSuffix(c.Request.URL.Path, "/")
|
||||||
if !strings.HasSuffix(path, "/base/login") && !strings.HasSuffix(path, "/base/captcha") {
|
if !strings.HasSuffix(path, "/base/login") && !strings.HasSuffix(path, "/base/captcha") {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user