diff --git a/cmd/main.go b/cmd/main.go index 323db7a..d341a27 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,11 +9,11 @@ import ( "strings" "kra/internal/config" + "kra/internal/logging" "kra/internal/server/router" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "kra/internal/worker" - "kra/pkg/logging" "kra/pkg/module" "kra/pkg/mq" platformtask "kra/pkg/task" @@ -62,7 +62,7 @@ func buildRuntime(registry *platformtask.Registry, systemRoutes *router.Routes, 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 { 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}) diff --git a/cmd/wire.go b/cmd/wire.go index 446f806..427ba0a 100644 --- a/cmd/wire.go +++ b/cmd/wire.go @@ -15,14 +15,15 @@ import ( "kra/internal/initialize" "kra/internal/integration" "kra/internal/integration/cache" + "kra/internal/logging" "kra/internal/modules" "kra/internal/server" "kra/internal/server/handler" "kra/internal/server/middleware" "kra/internal/server/router" "kra/internal/service" + systemservice "kra/internal/service/system" "kra/internal/worker" - "kra/pkg/logging" platformtask "kra/pkg/task" "github.com/go-kratos/kratos/v3" @@ -44,7 +45,7 @@ func wireApp(*config.Server, *config.Store, *slog.Logger, *logging.ReloadableLog initialize.ProviderSet, wire.Bind(new(initialize.Backend), new(*data.Data)), wire.Bind(new(cache.RedisProvider), new(*data.Data)), - wire.Bind(new(middleware.TokenAuthenticator), new(*service.AuthService)), + wire.Bind(new(middleware.TokenAuthenticator), new(*systemservice.AuthService)), wire.Bind(new(taskbiz.TaskMethodRegistry), new(*platformtask.Registry)), biz.ProviderSet, service.ProviderSet, diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index a75bd90..c4fd917 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -9,13 +9,13 @@ package main import ( "github.com/go-kratos/kratos/v3" integration3 "kra/internal/biz/integration" - payment2 "kra/internal/biz/payment" + payment3 "kra/internal/biz/payment" system2 "kra/internal/biz/system" task2 "kra/internal/biz/task" "kra/internal/config" "kra/internal/data" "kra/internal/data/integration" - "kra/internal/data/payment" + payment2 "kra/internal/data/payment" "kra/internal/data/system" "kra/internal/data/task" "kra/internal/initialize" @@ -23,18 +23,19 @@ import ( "kra/internal/integration/cache" "kra/internal/integration/email" "kra/internal/integration/mq" + "kra/internal/integration/payment" "kra/internal/integration/storage" "kra/internal/integration/websocket" + "kra/internal/logging" "kra/internal/modules" "kra/internal/server" "kra/internal/server/handler" "kra/internal/server/router" integration4 "kra/internal/service/integration" - payment3 "kra/internal/service/payment" + payment4 "kra/internal/service/payment" system3 "kra/internal/service/system" task3 "kra/internal/service/task" "kra/internal/worker" - "kra/pkg/logging" "log/slog" ) @@ -58,7 +59,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg authorityAccessRepo := system.NewAuthorityAccessRepo(dataData) apiRepo := system.NewAPIRepo(dataData) accessControlUsecase := system2.NewAccessControlUsecase(authorityAccessRepo, apiRepo) - v := system3.NewAccessControlService(accessControlUsecase) + accessControlService := system3.NewAccessControlService(accessControlUsecase) userRepo := system.NewUserRepo(dataData) userUsecase := system2.NewUserUsecase(userRepo) securityRepo := system.NewSecurityRepo(dataData) @@ -71,98 +72,106 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg auditRecordRepo := system.NewAuditRecorderRepo(dataData) authenticationUsecase := system2.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo) authService := system3.NewAuthService(authenticationUsecase) - v2 := system3.NewSecurityService(securityUsecase) + securityService := system3.NewSecurityService(securityUsecase) auditRecorderUsecase := system2.NewAuditRecorderUsecase(auditRecordRepo) - v3 := system3.NewAuditRecorder(auditRecorderUsecase) + auditRecorder := system3.NewAuditRecorder(auditRecorderUsecase) registry := taskRegistry() authorityUsecase := system2.NewAuthorityUsecase(authorityAccessRepo) - v4 := system3.NewAuthorityService(authorityUsecase) - authority := handler.NewAuthority(v4) + authorityService := system3.NewAuthorityService(authorityUsecase) + authority := handler.NewAuthority(authorityService) menuRepo := system.NewMenuRepo(dataData) menuUsecase := system2.NewMenuUsecase(menuRepo) - v5 := system3.NewMenuService(menuUsecase) - menu := handler.NewMenu(v5) + menuService := system3.NewMenuService(menuUsecase) + menu := handler.NewMenu(menuService) apiUsecase := system2.NewAPIUsecase(apiRepo) - v6 := system3.NewAPIService(apiUsecase, runtimeSettings) - api := handler.NewAPI(v6) + apiService := system3.NewAPIService(apiUsecase, runtimeSettings) + api := handler.NewAPI(apiService) permissionRepo := system.NewPermissionRepo(dataData) permissionUsecase := system2.NewPermissionUsecase(permissionRepo) - v7 := system3.NewPermissionService(permissionUsecase) - permission := handler.NewPermission(v7) + permissionService := system3.NewPermissionService(permissionUsecase) + permission := handler.NewPermission(permissionService) departmentRepo := system.NewDepartmentRepo(dataData) departmentUsecase := system2.NewDepartmentUsecase(departmentRepo) - v8 := system3.NewDepartmentService(departmentUsecase) + departmentService := system3.NewDepartmentService(departmentUsecase) positionRepo := system.NewPositionRepo(dataData) positionUsecase := system2.NewPositionUsecase(positionRepo) - v9 := system3.NewPositionService(positionUsecase) - organization := handler.NewOrganization(v8, v9) + positionService := system3.NewPositionService(positionUsecase) + organization := handler.NewOrganization(departmentService, positionService) announcementRepo := system.NewAnnouncementRepo(dataData) announcementUsecase := system2.NewAnnouncementUsecase(announcementRepo) - v10 := system3.NewAnnouncementService(announcementUsecase) - announcement := handler.NewAnnouncement(v10) + announcementService := system3.NewAnnouncementService(announcementUsecase) + announcement := handler.NewAnnouncement(announcementService) emailRepo := email.NewEmailRepo(store) emailUsecase := system2.NewEmailUsecase(emailRepo) - v11 := system3.NewEmailService(emailUsecase) - handlerEmail := handler.NewEmail(v11) + emailService := system3.NewEmailService(emailUsecase) + handlerEmail := handler.NewEmail(emailService) paymentConfigReader := integration.NewPaymentConfigReader(dataData) - paymentRepo := payment.NewPaymentRepo(dataData, paymentConfigReader) - paymentOrderRepo := payment.NewPaymentOrderRepo(dataData) - paymentUsecase := payment2.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger) - v12 := payment3.NewPaymentService(paymentUsecase) - handlerPayment := handler.NewPayment(v12) + factory := payment.NewFactory() + paymentRepo := payment2.NewPaymentRepo(dataData, paymentConfigReader, factory) + paymentOrderRepo := payment2.NewPaymentOrderRepo(dataData) + paymentHooks := payment3.NewPaymentHooks() + 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) taskUsecase := task2.NewTaskUsecaseWithRegistry(taskRepo, registry) taskExecutor := worker.NewTaskExecutorWithRegistry(taskUsecase, registry) taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger) taskRuntime := worker.NewTaskRuntime(taskScheduler) taskApplicationUsecase := task2.NewTaskApplicationUsecase(taskUsecase, taskRuntime) - v13 := task3.NewTaskService(taskApplicationUsecase) - handlerTask := handler.NewTask(v13) + taskService := task3.NewTaskService(taskApplicationUsecase) + handlerTask := handler.NewTask(taskService) mediaRepo := system.NewMediaRepo(dataData) mediaUsecase := system2.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings) - v14 := system3.NewMediaService(mediaUsecase, runtimeSettings) - media := handler.NewMedia(v14) + mediaService := system3.NewMediaService(mediaUsecase, runtimeSettings) + media := handler.NewMedia(mediaService) auditQueryRepo := system.NewAuditRepo(dataData) auditUsecase := system2.NewAuditUsecase(auditQueryRepo) - v15 := system3.NewAuditService(auditUsecase) + auditService := system3.NewAuditService(auditUsecase) logFileRepo := system.NewLogFileRepo(dataData) logViewerUsecase := system2.NewLogViewerUsecase(logFileRepo) - v16 := system3.NewLogViewerService(logViewerUsecase) - audit := handler.NewAudit(v15, v3, v16, logger) + logViewerService := system3.NewLogViewerService(logViewerUsecase) + audit := handler.NewAudit(auditService, auditRecorder, logViewerService, logger) exportRepo := system.NewExportRepo(dataData) exportUsecase := system2.NewExportUsecase(exportRepo) - v17 := system3.NewExportService(exportUsecase, systemCache) - export := handler.NewExport(v17) + exportService := system3.NewExportService(exportUsecase, systemCache) + export := handler.NewExport(exportService) versionRepo := system.NewVersionRepo(dataData) versionUsecase := system2.NewVersionUsecase(versionRepo) - v18 := system3.NewVersionService(versionUsecase) - version := handler.NewVersion(v18) + versionService := system3.NewVersionService(versionUsecase) + version := handler.NewVersion(versionService) dictionaryRepo := system.NewDictionaryRepo(dataData) dictionaryUsecase := system2.NewDictionaryUsecase(dictionaryRepo) - v19 := system3.NewDictionaryService(dictionaryUsecase) - dictionary := handler.NewDictionary(v19) + dictionaryService := system3.NewDictionaryService(dictionaryUsecase) + dictionary := handler.NewDictionary(dictionaryService) parameterRepo := system.NewParameterRepo(dataData) parameterUsecase := system2.NewParameterUsecase(parameterRepo) - v20 := system3.NewParameterService(parameterUsecase) - parameter := handler.NewParameter(v20) - v21 := system3.NewTokenService(tokenUsecase, tokenIssuer) - apiToken := handler.NewAPIToken(v21) + parameterService := system3.NewParameterService(parameterUsecase) + parameter := handler.NewParameter(parameterService) + tokenService := system3.NewTokenService(tokenUsecase, tokenIssuer) + apiToken := handler.NewAPIToken(tokenService) initializationRepo := initialize.NewRepo(dataData, catalog) taskReloader := worker.NewTaskReloader(taskScheduler) systemConfigUsecase := system2.NewSystemConfigUsecase(initializationRepo, taskReloader) - v22 := system3.NewSystemConfigService(systemConfigUsecase, runtimeSettings) - systemConfig := handler.NewSystemConfig(v22, v2) - public := handler.NewPublic(authService, v22, v2) - v23 := system3.NewUserService(userUsecase, v2) - user := handler.NewUser(v23, authService) - navigation := handler.NewNavigation(v23) - session := handler.NewSession(v21) + systemConfigService := system3.NewSystemConfigService(systemConfigUsecase, runtimeSettings) + systemConfig := handler.NewSystemConfig(systemConfigService, securityService) + public := handler.NewPublic(authService, systemConfigService, securityService) + userService := system3.NewUserService(userUsecase, securityService) + user := handler.NewUser(userService, authService) + navigation := handler.NewNavigation(userService) + session := handler.NewSession(tokenService) integrationConfigRepo := integration.NewIntegrationConfigRepo(dataData) runtimeconfigStore := data.NewIntegrationRuntime(dataData) connectivityTester := integration2.NewConnectivityTester(runtimeconfigStore) integrationConfigUsecase := integration3.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester) - v24 := integration4.NewIntegrationConfigService(integrationConfigUsecase) - integrationConfig := handler.NewIntegrationConfig(v24) + integrationConfigService := integration4.NewIntegrationConfigService(integrationConfigUsecase) + integrationConfig := handler.NewIntegrationConfig(integrationConfigService) set := &handler.Set{ Authority: authority, Menu: menu, @@ -197,7 +206,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg cleanup() 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) mqReloadable, cleanup3, err := mq.New(runtimeconfigStore, logger) if err != nil { @@ -205,7 +214,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg cleanup() return nil, nil, err } - app := newApp(logger, httpServer, taskScheduler, v3, reloadableLogger, mqReloadable) + app := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger, mqReloadable) return app, func() { cleanup3() cleanup2() diff --git a/docs/code-review-issues.md b/docs/code-review-issues.md index 8d23eba..267294c 100644 --- a/docs/code-review-issues.md +++ b/docs/code-review-issues.md @@ -58,7 +58,7 @@ | 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 | | 仅测试调用: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-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-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-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-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-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 文件 | @@ -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) > 以下条目经第三轮 5 路代理逐条核查属实(含编译验证通过),从上方问题清单移除,此处留痕。复查中发现的部分修复残留已回升为一节 R 系列条目。 diff --git a/go.mod b/go.mod index 826ccbf..1992898 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/go-kratos/kratos/v3 v3.0.0 github.com/go-pay/crypto v0.0.1 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/google/uuid v1.6.0 github.com/google/wire v0.7.0 @@ -32,6 +33,7 @@ require ( github.com/redis/go-redis/v9 v9.7.0 github.com/robfig/cron/v3 v3.0.1 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/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.4 @@ -43,7 +45,6 @@ require ( go.uber.org/zap/exp v0.3.0 golang.org/x/crypto v0.53.0 golang.org/x/sync v0.21.0 - google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql 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/validator/v10 v10.20.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/gofrs/flock v0.8.1 // 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/cast v1.10.0 // 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/thoas/go-funk v0.7.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/rpc v0.0.0-20260615183401-62b3387ff324 // 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 modernc.org/fileutil v1.0.0 // indirect modernc.org/libc v1.22.5 // indirect diff --git a/go.sum b/go.sum index b992629..7bd05d3 100644 --- a/go.sum +++ b/go.sum @@ -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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 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/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= 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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -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/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI= 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/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= diff --git a/internal/biz/integration/integration_config.go b/internal/biz/integration/integration_config.go index c8cf680..f26a00b 100644 --- a/internal/biz/integration/integration_config.go +++ b/internal/biz/integration/integration_config.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - paymentutil "kra/pkg/paymentkit" + bizpayment "kra/internal/biz/payment" "sort" "strconv" "strings" @@ -316,7 +316,7 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er } } switch provider { - case paymentutil.ProviderAlipayV3: + case bizpayment.PaymentAlipayV3: for _, group := range []struct { label string keys []string @@ -330,21 +330,21 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er 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") == "" { return fmt.Errorf("%s 退款要求同时配置 client_cert 和 client_key", provider) } - case paymentutil.ProviderApple: + case bizpayment.PaymentApple: if integrationInt64(values, "price_divisor", 0) <= 0 { if _, exists := values["price_divisors"].(map[string]any); !exists { return fmt.Errorf("%s 缺少配置字段 price_divisor 或 price_divisors", provider) } } - case paymentutil.ProviderDouyin: + case bizpayment.PaymentDouyin: if integrationFirst(values, "platform_serial_no", "platform_cert_serial") == "" { return fmt.Errorf("%s 缺少配置字段 platform_serial_no", provider) } - case paymentutil.ProviderQQ: + case bizpayment.PaymentQQ: if integrationFirst(values, "mch_id", "merchant_id") == "" { 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" { 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")) if orderType != "" && orderType != "reqsn" && orderType != "trxid" { 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") == "" { 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 != "" { - 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") { return fmt.Errorf("%s environment 配置无效", provider) } diff --git a/internal/biz/integration/integration_config_definition.go b/internal/biz/integration/integration_config_definition.go index 0258ace..60df387 100644 --- a/internal/biz/integration/integration_config_definition.go +++ b/internal/biz/integration/integration_config_definition.go @@ -1,6 +1,6 @@ package integration -import paymentutil "kra/pkg/paymentkit" +import bizpayment "kra/internal/biz/payment" func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField { if fieldType == "" { @@ -150,35 +150,35 @@ var integrationDefinitions = map[string][]IntegrationConfigDefinition{ }, }, 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"), 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")), - 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"), 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")), - 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")), - 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")), - 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")), - 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")), - 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")), - 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")), - 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")), - 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")), - genericPaymentDefinition(paymentutil.ProviderChinaums, "银联商务", "按商户协议配置的银联商务适配器"), - genericPaymentDefinition(paymentutil.ProviderSFT, "商福通", "按商户协议配置的商福通适配器"), - genericPaymentDefinition(paymentutil.ProviderSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"), - genericPaymentDefinition(paymentutil.ProviderWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"), - genericPaymentDefinition(paymentutil.ProviderDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"), + genericPaymentDefinition(bizpayment.PaymentChinaums, "银联商务", "按商户协议配置的银联商务适配器"), + genericPaymentDefinition(bizpayment.PaymentSFT, "商福通", "按商户协议配置的商福通适配器"), + genericPaymentDefinition(bizpayment.PaymentSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"), + genericPaymentDefinition(bizpayment.PaymentWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"), + genericPaymentDefinition(bizpayment.PaymentDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"), }, } diff --git a/internal/biz/payment/payment.go b/internal/biz/payment/payment.go index d369450..1895f64 100644 --- a/internal/biz/payment/payment.go +++ b/internal/biz/payment/payment.go @@ -12,37 +12,39 @@ import ( "sync" "time" - "kra/pkg/paymentkit" - "github.com/google/uuid" ) const ( - PaymentAlipay = paymentutil.ProviderAlipay - PaymentAlipayV3 = paymentutil.ProviderAlipayV3 - PaymentWechatV2 = paymentutil.ProviderWechatV2 - PaymentWechatV3 = paymentutil.ProviderWechatV3 - PaymentApple = paymentutil.ProviderApple - PaymentChinaums = paymentutil.ProviderChinaums - PaymentSFT = paymentutil.ProviderSFT - PaymentSuperPay = paymentutil.ProviderSuperPay - PaymentWechatGame = paymentutil.ProviderWechatGame - PaymentDouyinGame = paymentutil.ProviderDouyinGame + PaymentAlipay = "alipay" + PaymentAlipayV3 = "alipay-v3" + PaymentWechatV2 = "wechat-v2" + PaymentWechatV3 = "wechat-v3" + PaymentApple = "apple-iap" + PaymentChinaums = "chinaums" + PaymentSFT = "sft" + PaymentSuperPay = "supper-pay" + PaymentWechatGame = "wechat-game-pay" + PaymentDouyinGame = "douyin-game-pay" // GoPay-backed providers. These are kept separate from the existing // configuration-driven game/aggregator protocols above. - PaymentDouyin = paymentutil.ProviderDouyin - PaymentQQ = paymentutil.ProviderQQ - PaymentAllinPay = paymentutil.ProviderAllinPay - PaymentLakala = paymentutil.ProviderLakala - PaymentPayPal = paymentutil.ProviderPayPal - PaymentSaobei = paymentutil.ProviderSaobei - PaymentInternal = paymentutil.ProviderInternal + PaymentDouyin = "douyin" + PaymentQQ = "qq" + PaymentAllinPay = "allinpay" + PaymentLakala = "lakala" + PaymentPayPal = "paypal" + PaymentSaobei = "saobei" + PaymentInternal = "internal" - PaymentModeExternal = paymentutil.ModeExternal - PaymentModeInternal = paymentutil.ModeInternal + PaymentModeExternal = "external" + 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 ( ErrPaymentProviderNotFound = errors.New("支付渠道未配置") @@ -203,15 +205,74 @@ type PaymentTestResult struct { Result *PaymentResult `json:"result,omitempty"` } -type PaymentCallbackAck = paymentutil.CallbackAck -type PaymentCallbackError = paymentutil.CallbackError +type PaymentCallbackAck struct { + 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 { - 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 { - 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("", 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 { @@ -228,6 +289,19 @@ type PaymentRepo interface { 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 { TestProvider(context.Context, string) (*PaymentTestResult, error) } @@ -240,6 +314,8 @@ type NoopPaymentHooks struct{} func (NoopPaymentHooks) BeforeCreate(context.Context, *PaymentRequest) error { return nil } +func NewPaymentHooks() PaymentHooks { return NoopPaymentHooks{} } + type PaymentFulfillmentHandler interface { Type() string Fulfill(context.Context, *PaymentConfirmation) error @@ -273,6 +349,15 @@ func (r *PaymentFulfillmentRegistry) Handler(kind string) PaymentFulfillmentHand 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 { repo PaymentRepo orders PaymentOrderRepo @@ -282,33 +367,34 @@ type PaymentUsecase struct { 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{ repo: repo, orders: orders, - hooks: NoopPaymentHooks{}, - sources: NewPaymentOrderSourceRegistry(), - fulfillments: NewPaymentFulfillmentRegistry(), + hooks: hooks, + sources: sources, + fulfillments: fulfillments, logger: NewPaymentLogger(appLogger), } } -func (uc *PaymentUsecase) SetHooks(hooks PaymentHooks) { - if hooks != nil { - uc.hooks = hooks +func NewConfiguredPaymentUsecase(repo PaymentRepo, orders PaymentOrderRepo, hooks PaymentHooks, sources *PaymentOrderSourceRegistry, fulfillments *PaymentFulfillmentRegistry, appLogger *slog.Logger) (*PaymentUsecase, error) { + if sources == nil || sources.Len() == 0 { + return nil, errors.New("支付业务订单来源未注册,无法启动支付服务") } -} - -func (uc *PaymentUsecase) SetOrderSourceRegistry(registry *PaymentOrderSourceRegistry) { - if registry != nil { - uc.sources = registry - } -} - -func (uc *PaymentUsecase) SetFulfillmentRegistry(registry *PaymentFulfillmentRegistry) { - if registry != nil { - uc.fulfillments = registry + if fulfillments == nil || fulfillments.Len() == 0 { + return nil, errors.New("支付发货 handler 未注册,无法启动支付服务") } + return NewPaymentUsecase(repo, orders, hooks, sources, fulfillments, appLogger), nil } func (uc *PaymentUsecase) RegisterBusinessModule(module PaymentBusinessModule) error { diff --git a/internal/biz/payment/payment_order.go b/internal/biz/payment/payment_order.go index 1e9d576..ae6f060 100644 --- a/internal/biz/payment/payment_order.go +++ b/internal/biz/payment/payment_order.go @@ -182,6 +182,15 @@ func (r *PaymentOrderSourceRegistry) Source(kind string) PaymentOrderSource { 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) { if r == nil { return diff --git a/internal/biz/payment/payment_test.go b/internal/biz/payment/payment_test.go index 2361ba7..eea7ebc 100644 --- a/internal/biz/payment/payment_test.go +++ b/internal/biz/payment/payment_test.go @@ -135,7 +135,12 @@ func (h *paymentFulfillmentStub) Fulfill(_ context.Context, confirmation *Paymen func newPaymentUsecaseForTest(repo PaymentRepo) *PaymentUsecase { 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 { @@ -186,7 +191,7 @@ func TestPaymentCallbackPendingDoesNotFulfill(t *testing.T) { if err := registry.Register(handler); err != nil { t.Fatal(err) } - uc.SetFulfillmentRegistry(registry) + uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry) result, err := uc.Callback(context.Background(), &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")}) if err != nil { t.Fatal(err) @@ -200,10 +205,10 @@ func TestPaymentCallbackRejectsAmountMismatch(t *testing.T) { repo := &paymentRepoStub{callback: successPaymentResult(), query: successPaymentResult()} handler := &paymentFulfillmentStub{} uc := newPaymentUsecaseForTest(repo) - uc.orders.(*paymentOrderRepoStub).order.Amount = 101 registry := NewPaymentFulfillmentRegistry() _ = 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 { t.Fatal("amount mismatch was accepted") } @@ -226,7 +231,7 @@ func TestPaymentCallbackDuplicateDoesNotFulfillTwice(t *testing.T) { uc := newPaymentUsecaseForTest(repo) registry := NewPaymentFulfillmentRegistry() _ = registry.Register(handler) - uc.SetFulfillmentRegistry(registry) + uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry) callback := &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")} if _, err := uc.Callback(context.Background(), callback); err != nil { t.Fatal(err) @@ -246,7 +251,7 @@ func TestPaymentCallbackFulfillmentFailureCanRetry(t *testing.T) { uc := newPaymentUsecaseForTest(repo) registry := NewPaymentFulfillmentRegistry() _ = registry.Register(handler) - uc.SetFulfillmentRegistry(registry) + uc = newPaymentUsecaseWithFulfillmentRegistry(repo, registry) callback := &PaymentCallback{Provider: PaymentAlipay, Body: []byte("callback")} if _, err := uc.Callback(context.Background(), callback); err == nil { t.Fatal("first fulfillment failure was accepted") @@ -290,12 +295,11 @@ func TestPaymentCallbackErrorCarriesSafeAck(t *testing.T) { func TestPersistentPaymentCreateUsesBusinessCanonicalAmount(t *testing.T) { repo := &paymentRepoStub{create: &PaymentResult{Provider: PaymentAlipay, Status: "created", TradeNo: "order-1", Payload: []byte(`{"prepay":"x"}`)}} orders := &paymentOrderRepoStub{} - uc := NewPaymentUsecase(repo, orders, slog.New(slog.NewTextHandler(io.Discard, nil))) 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 { 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"}}) if err != nil { t.Fatal(err) @@ -309,12 +313,11 @@ func TestPersistentPaymentCallbackFulfillsAndPersistsDuplicate(t *testing.T) { 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"}} handler := &paymentFulfillmentStub{} - uc := NewPaymentUsecase(repo, orders, slog.New(slog.NewTextHandler(io.Discard, nil))) registry := NewPaymentFulfillmentRegistry() if err := registry.Register(handler); err != nil { 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")}) if err != nil { t.Fatal(err) @@ -330,13 +333,12 @@ func TestPersistentPaymentCallbackFulfillsAndPersistsDuplicate(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"}} - uc := NewPaymentUsecase(&paymentRepoStub{}, orders, slog.New(slog.NewTextHandler(io.Discard, nil))) handler := &paymentFulfillmentStub{} registry := NewPaymentFulfillmentRegistry() if err := registry.Register(handler); err != nil { 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") if err != nil { t.Fatal(err) diff --git a/internal/biz/payment/provider.go b/internal/biz/payment/provider.go index 4ed4dd7..f31ce07 100644 --- a/internal/biz/payment/provider.go +++ b/internal/biz/payment/provider.go @@ -5,4 +5,9 @@ import "github.com/google/wire" // ProviderSet wires the payment domain usecase independently from other // business domains. Persistence and transport layers consume the payment // interfaces without depending on the system package. -var ProviderSet = wire.NewSet(NewPaymentUsecase) +var ProviderSet = wire.NewSet( + NewPaymentHooks, + NewPaymentOrderSourceRegistry, + NewPaymentFulfillmentRegistry, + NewConfiguredPaymentUsecase, +) diff --git a/internal/biz/system/api_token.go b/internal/biz/system/api_token.go index 07f82f1..457ce25 100644 --- a/internal/biz/system/api_token.go +++ b/internal/biz/system/api_token.go @@ -23,7 +23,6 @@ type APITokenRepo interface { UserHasAuthority(context.Context, uint, uint) (*User, bool, error) CreateAPIToken(context.Context, *APIToken) error ListAPITokens(context.Context, int, int, uint, *bool) ([]*APIToken, int64, error) - DisableAPIToken(context.Context, uint) (string, error) DisableAndBlacklistAPIToken(context.Context, uint) error BlacklistToken(context.Context, string) error IsTokenDisabled(context.Context, string) (bool, error) diff --git a/internal/biz/system/audit.go b/internal/biz/system/audit.go index 882f2bf..8b18429 100644 --- a/internal/biz/system/audit.go +++ b/internal/biz/system/audit.go @@ -40,8 +40,11 @@ type LoginLog struct { } type LoginLogFilter struct { - Username string - Status *bool + Username string + IP string + Status *bool + StartCreatedAt *time.Time + EndCreatedAt *time.Time } type DataAccessLog struct { diff --git a/internal/biz/system/email.go b/internal/biz/system/email.go index f8bd151..28a6aee 100644 --- a/internal/biz/system/email.go +++ b/internal/biz/system/email.go @@ -9,7 +9,6 @@ import ( type EmailRepo interface { Send(context.Context, []string, string, string) error DefaultRecipients() []string - Enabled() bool } 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") } - -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) -} diff --git a/internal/biz/system/media_metadata.go b/internal/biz/system/media_metadata.go index e4364dc..c2569e7 100644 --- a/internal/biz/system/media_metadata.go +++ b/internal/biz/system/media_metadata.go @@ -41,7 +41,6 @@ type AttachmentCategory struct { type MediaMetadataRepo interface { CreateMedia(context.Context, *MediaFile) error FindMedia(context.Context, uint) (*MediaFile, error) - FindMediaByHash(context.Context, uint, string) (*MediaFile, error) ListMedia(context.Context, MediaFilter) ([]*MediaFile, int64, error) UpdateMediaName(context.Context, uint, string) error DeleteMedia(context.Context, uint) error diff --git a/internal/biz/system/menu.go b/internal/biz/system/menu.go index 9c904a4..d9734bc 100644 --- a/internal/biz/system/menu.go +++ b/internal/biz/system/menu.go @@ -58,7 +58,6 @@ type MenuRepo interface { Find(context.Context, uint) (*Menu, error) List(context.Context) ([]*Menu, error) SetAuthorityMenus(context.Context, uint, []uint) error - AuthorityMenuIDs(context.Context, uint) ([]uint, error) // ListAuthorityMenus returns the base menus assigned to the target // authority. Unlike List, it must not apply the current actor's strict // 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 (uc *MenuUsecase) List(ctx context.Context) ([]*Menu, error) { - return uc.repo.List(ctx) -} - func (uc *MenuUsecase) Tree(ctx context.Context) ([]*Menu, error) { items, err := uc.repo.List(ctx) if err != nil { diff --git a/internal/biz/system/permission.go b/internal/biz/system/permission.go index c418152..cc88867 100644 --- a/internal/biz/system/permission.go +++ b/internal/biz/system/permission.go @@ -3,9 +3,6 @@ package system import "context" 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) SetSelectedButtons(context.Context, uint, uint, []uint) error CanRemoveButton(context.Context, string) (bool, error) diff --git a/internal/biz/system/security.go b/internal/biz/system/security.go index 0d918ea..36f4aea 100644 --- a/internal/biz/system/security.go +++ b/internal/biz/system/security.go @@ -233,14 +233,6 @@ func (uc *SecurityUsecase) ActiveToken(ctx context.Context, username string) (st 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 { if !uc.settings.UseMultipoint() { return nil diff --git a/internal/biz/system/user.go b/internal/biz/system/user.go index 9b9851c..99ffc2b 100644 --- a/internal/biz/system/user.go +++ b/internal/biz/system/user.go @@ -42,11 +42,9 @@ type UserRepo interface { HasAuthorityMenu(context.Context, uint, string) (bool, error) MenusByAuthority(context.Context, uint) ([]*Menu, error) ListUsers(context.Context, int, int, *UserListFilter) ([]*User, int64, error) - CreateUser(context.Context, *User) (*User, error) CreateUserWithAuthorities(context.Context, *User, []uint) (*User, error) UpdateUser(context.Context, *User) error UpdateSelfUser(context.Context, *User) error - UpdateUserWithAuthorities(context.Context, *User, []uint) error DeleteUser(context.Context, uint) error UpdatePassword(context.Context, uint, string, bool) error SetUserAuthorities(context.Context, uint, []uint) error diff --git a/internal/biz/system/version.go b/internal/biz/system/version.go index 23c189d..6c78fe7 100644 --- a/internal/biz/system/version.go +++ b/internal/biz/system/version.go @@ -50,7 +50,7 @@ type VersionRepo interface { FindVersion(context.Context, uint) (*Version, error) ListVersions(context.Context, int, int, string, string, []*time.Time) ([]*Version, int64, error) BuildVersionBundle(context.Context, []uint, []uint, []uint) (*VersionBundle, error) - ImportVersionBundle(context.Context, *VersionBundle) error + ImportVersionBundle(context.Context, *VersionBundle, *Version) error } type VersionUsecase struct{ VersionRepo } diff --git a/internal/data/data.go b/internal/data/data.go index 8f41d63..5e081e6 100644 --- a/internal/data/data.go +++ b/internal/data/data.go @@ -29,7 +29,6 @@ var ProviderSet = wire.NewSet( wire.Bind(new(datasystem.Provider), new(*Data)), wire.Bind(new(datasystem.DatabaseProvider), new(*Data)), wire.Bind(new(dataintegration.Provider), new(*Data)), - wire.Bind(new(datatask.Provider), new(*Data)), wire.Bind(new(datapayment.Provider), new(*Data)), datasystem.ProviderSet, dataintegration.ProviderSet, @@ -235,8 +234,9 @@ func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *stor usingFallback = true } d.databaseReady.Store(!usingFallback) - d.gormDB = newReloadableDB(db, d.enqueueDataScopeAudit) + d.gormDB = newReloadableDB(db) d.auditLog = newDataScopeAuditWriter(d, appLogger) + registerDataScopeCallbacks(db, d.enqueueDataScopeAudit) d.dbList, err = openDatabaseList(c.DatabaseList, appLogger) if err != nil { return nil, nil, err diff --git a/internal/data/gorm_logger_test.go b/internal/data/gorm_logger_test.go index 283a683..40c3e6d 100644 --- a/internal/data/gorm_logger_test.go +++ b/internal/data/gorm_logger_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + "kra/internal/logging" "kra/pkg/database/gormkit" - "kra/pkg/logging" "gorm.io/gorm/logger" ) diff --git a/internal/data/payment/payment.go b/internal/data/payment/payment.go index ba64028..9428bf4 100644 --- a/internal/data/payment/payment.go +++ b/internal/data/payment/payment.go @@ -14,18 +14,17 @@ import ( "strings" "time" - datapayment "kra/internal/integration/payment" - "github.com/google/uuid" ) type paymentRepo struct { - data Provider - config integrationbiz.PaymentConfigReader + data Provider + config integrationbiz.PaymentConfigReader + factory bizpayment.PaymentAdapterFactory } -func NewPaymentRepo(data Provider, config integrationbiz.PaymentConfigReader) bizpayment.PaymentRepo { - return &paymentRepo{data: data, config: config} +func NewPaymentRepo(data Provider, config integrationbiz.PaymentConfigReader, factory bizpayment.PaymentAdapterFactory) bizpayment.PaymentRepo { + return &paymentRepo{data: data, config: config, factory: factory} } 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 } -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) if err != nil { 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 } @@ -81,7 +83,13 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa 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 { add("adapter", "failed", err.Error(), "", time.Now()) return test, err @@ -237,7 +245,7 @@ func validatePaymentTestResult(provider, tradeNo string, result *bizpayment.Paym 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 err error for attempt := 0; attempt < 3; attempt++ { @@ -547,7 +555,3 @@ func contains(values []string, value string) bool { } return false } - -func validatePaymentConfig(provider string, values map[string]any) error { - return integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values) -} diff --git a/internal/data/payment/payment_config_test.go b/internal/data/payment/payment_config_test.go index 702e23f..2a8cb29 100644 --- a/internal/data/payment/payment_config_test.go +++ b/internal/data/payment/payment_config_test.go @@ -23,7 +23,7 @@ func TestValidatePaymentConfigRequiresDouyinAppIDWhenEnabled(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", "private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_cert_serial": "platform-serial", }) diff --git a/internal/data/payment/payment_helpers.go b/internal/data/payment/payment_helpers.go index b9321cd..e327e9c 100644 --- a/internal/data/payment/payment_helpers.go +++ b/internal/data/payment/payment_helpers.go @@ -1,17 +1,17 @@ package payment -import "kra/pkg/paymentkit" +import "kra/internal/paymentkit" 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 { - return paymentutil.FirstText(values, keys...) + return paymentkit.FirstText(values, keys...) } // configuredInt64 keeps repository-side validation independent from the // provider adapter package while sharing the canonical payment parser. func configuredInt64(values map[string]any, key string, fallback int64) int64 { - return paymentutil.ConfiguredInt64(values, key, fallback) + return paymentkit.ConfiguredInt64(values, key, fallback) } diff --git a/internal/data/payment/payment_native_test.go b/internal/data/payment/payment_native_test.go index 0a94d03..9243a7f 100644 --- a/internal/data/payment/payment_native_test.go +++ b/internal/data/payment/payment_native_test.go @@ -23,7 +23,7 @@ func TestPaymentDefinitionsProvideNonEmptyDefaults(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(), "不支持") { t.Fatalf("error = %v", err) } diff --git a/internal/data/runtime_clients.go b/internal/data/runtime_clients.go index d1697fb..9f94098 100644 --- a/internal/data/runtime_clients.go +++ b/internal/data/runtime_clients.go @@ -163,10 +163,9 @@ func (r *rollback) run() { } } -// newReloadableDB attaches the row-level data scope callbacks the pool needs -// before it becomes readable. Data.replacePrimaryDB is the reload counterpart. -func newReloadableDB(db *gorm.DB, enqueue dataScopeAuditEnqueue) *reloadable[*gorm.DB] { - registerDataScopeCallbacks(db, enqueue) +// newReloadableDB owns only the hot-swappable database handle. Domain-specific +// callbacks are attached by Data when a pool becomes active. +func newReloadableDB(db *gorm.DB, _ ...dataScopeAuditEnqueue) *reloadable[*gorm.DB] { return newReloadable(db, closeGormDB) } diff --git a/internal/data/system/api_token.go b/internal/data/system/api_token.go index c17e9ca..aa02d09 100644 --- a/internal/data/system/api_token.go +++ b/internal/data/system/api_token.go @@ -109,13 +109,6 @@ func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID } 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 { // Persist the blacklist row first, then mark the // API-token record disabled. The two writes are intentionally separate in diff --git a/internal/data/system/export.go b/internal/data/system/export.go index c18d103..9d76da6 100644 --- a/internal/data/system/export.go +++ b/internal/data/system/export.go @@ -191,12 +191,103 @@ func (r *exportRepo) QueryExport(ctx context.Context, t *system.ExportTemplate, if err != nil { return nil, "", err } - var rows []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 } +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) { columns := make([]string, 0) 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 } -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 { if err := system.ValidateExportTemplate(t); err != nil { return err diff --git a/internal/data/system/export_value_test.go b/internal/data/system/export_value_test.go new file mode 100644 index 0000000..7c76ab3 --- /dev/null +++ b/internal/data/system/export_value_test.go @@ -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")) + } +} diff --git a/internal/data/system/login_log.go b/internal/data/system/login_log.go index 6febb93..6f3010d 100644 --- a/internal/data/system/login_log.go +++ b/internal/data/system/login_log.go @@ -40,9 +40,18 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *syst if q.Username != "" { db = db.Where("username LIKE ?", "%"+q.Username+"%") } + if q.IP != "" { + db = db.Where("ip LIKE ?", "%"+q.IP+"%") + } if q.Status != nil { 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 if err := db.Count(&total).Error; err != nil { diff --git a/internal/data/system/media.go b/internal/data/system/media.go index b5f975b..6c88ac4 100644 --- a/internal/data/system/media.go +++ b/internal/data/system/media.go @@ -64,13 +64,6 @@ func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*system.MediaFile, } 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) { db := r.data.DB().WithContext(ctx).Model(&mediaPO{}) if filter.Keyword != "" { diff --git a/internal/data/system/menu.go b/internal/data/system/menu.go index bf6662a..b3ec687 100644 --- a/internal/data/system/menu.go +++ b/internal/data/system/menu.go @@ -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 // target authority's relation rows first and then fetches only those base // menus. In particular, it deliberately does not call List: List applies the diff --git a/internal/data/system/permission.go b/internal/data/system/permission.go index 9524191..6fa422d 100644 --- a/internal/data/system/permission.go +++ b/internal/data/system/permission.go @@ -33,40 +33,6 @@ type authorityButtonPO struct { 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) { var rows []authorityButtonPO 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 } -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 { actor, allowedAuthorities, strict, err := (&authorityAccessRepo{data: r.data}).strictAuthorityAccess(ctx) if err != nil || !strict { diff --git a/internal/data/system/transactions_test.go b/internal/data/system/transactions_test.go index f5d3b1b..68b0982 100644 --- a/internal/data/system/transactions_test.go +++ b/internal/data/system/transactions_test.go @@ -24,7 +24,7 @@ func newTransactionTestData(t *testing.T) *Data { return data } -func TestUserAuthorityWritesAreAtomic(t *testing.T) { +func TestUserAuthorityWrites(t *testing.T) { data := newTransactionTestData(t) ctx := context.Background() 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 // without first requiring every role record to exist. 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) } var persisted userPO diff --git a/internal/data/system/user.go b/internal/data/system/user.go index a3ef401..353639a 100644 --- a/internal/data/system/user.go +++ b/internal/data/system/user.go @@ -329,10 +329,6 @@ func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *sy 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) { requestedAuthorities := append([]uint{user.AuthorityID}, authorityIDs...) 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 } -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 { if err := (&authorityAccessRepo{data: r.data}).checkUserIDAuth(ctx, id, false); err != nil { 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 { if len(authorityIDs) == 0 { - return errors.New("角色不能为空") + return system.ErrAuthoritiesRequired } if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil { return err diff --git a/internal/data/system/user_strict_test.go b/internal/data/system/user_strict_test.go index 286dbbc..bf505d7 100644 --- a/internal/data/system/user_strict_test.go +++ b/internal/data/system/user_strict_test.go @@ -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 { 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 if err := data.gormDB.WithContext(context.Background()).First(&stored, users[1].ID).Error; err != nil { t.Fatal(err) diff --git a/internal/data/system/version.go b/internal/data/system/version.go index bc597b3..83d201b 100644 --- a/internal/data/system/version.go +++ b/internal/data/system/version.go @@ -179,24 +179,32 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d } return bundle, nil } -func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *system.VersionBundle) error { - db := r.data.DB().WithContext(ctx) - if len(b.Menus) > 0 { - if err := db.Transaction(func(tx *gorm.DB) error { return importVersionMenus(tx, b.Menus, 0) }); err != nil { - return &system.VersionStageError{Stage: system.VersionStageMenus, Err: err} +func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *system.VersionBundle, audit *system.Version) error { + return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if len(b.Menus) > 0 { + if err := importVersionMenus(tx, b.Menus, 0); err != nil { + return &system.VersionStageError{Stage: system.VersionStageMenus, Err: err} + } } - } - if len(b.APIs) > 0 { - if err := db.Transaction(func(tx *gorm.DB) error { return importVersionAPIs(tx, b.APIs) }); err != nil { - return &system.VersionStageError{Stage: system.VersionStageAPIs, Err: err} + if len(b.APIs) > 0 { + if err := importVersionAPIs(tx, b.APIs); err != nil { + return &system.VersionStageError{Stage: system.VersionStageAPIs, Err: err} + } } - } - if len(b.Dictionaries) > 0 { - if err := db.Transaction(func(tx *gorm.DB) error { return importVersionDictionaries(tx, b.Dictionaries) }); err != nil { - return &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err} + if len(b.Dictionaries) > 0 { + if err := importVersionDictionaries(tx, b.Dictionaries); err != nil { + return &system.VersionStageError{Stage: system.VersionStageDictionaries, Err: err} + } } - } - return nil + if audit != 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 { diff --git a/internal/integration/email/email.go b/internal/integration/email/email.go index 03aeee9..8f6e27b 100644 --- a/internal/integration/email/email.go +++ b/internal/integration/email/email.go @@ -33,10 +33,6 @@ func (r *emailRepo) email() *config.Email { return config.Email } -func (r *emailRepo) Enabled() bool { - return emailEnabled(r.email()) -} - func (r *emailRepo) DefaultRecipients() []string { config := r.email() if config == nil || strings.TrimSpace(config.To) == "" { diff --git a/internal/integration/payment/adapter.go b/internal/integration/payment/adapter.go index ab21059..570b10f 100644 --- a/internal/integration/payment/adapter.go +++ b/internal/integration/payment/adapter.go @@ -1,7 +1,6 @@ package payment import ( - "context" "fmt" bizpayment "kra/internal/biz/payment" "strings" @@ -9,12 +8,11 @@ import ( // Adapter is the provider boundary used by the payment repository. Provider // SDK types stay in this package and are normalized to biz/payment results. -type Adapter interface { - Create(context.Context, *bizpayment.PaymentRequest, map[string]any) (*bizpayment.PaymentResult, error) - Query(context.Context, string, map[string]any) (*bizpayment.PaymentResult, error) - Refund(context.Context, *bizpayment.PaymentRefundRequest, map[string]any) (*bizpayment.PaymentResult, error) - Callback(context.Context, *bizpayment.PaymentCallback, map[string]any) (*bizpayment.PaymentResult, error) -} +type Adapter = bizpayment.PaymentAdapter + +type Factory struct{} + +func NewFactory() *Factory { return &Factory{} } type adapterFactory func() Adapter @@ -51,3 +49,7 @@ func New(provider string) (Adapter, error) { } return factory(), nil } + +func (*Factory) New(provider string) (bizpayment.PaymentAdapter, error) { + return New(provider) +} diff --git a/internal/integration/payment/alipay.go b/internal/integration/payment/alipay.go index 24b9cf3..500c7c2 100644 --- a/internal/integration/payment/alipay.go +++ b/internal/integration/payment/alipay.go @@ -10,7 +10,6 @@ import ( bizpayment "kra/internal/biz/payment" "net/http" "net/url" - "sort" "strings" "time" @@ -569,21 +568,6 @@ func verifyAlipayResponse(raw []byte, method string, c map[string]any) error { 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 { if text(c, "app_id") == "" || text(c, "public_key") == "" || fields["sign"] == "" { return errors.New("支付宝回调验签配置或 sign 缺失") diff --git a/internal/integration/payment/gopay_test.go b/internal/integration/payment/gopay_test.go index ebb3eeb..a137d50 100644 --- a/internal/integration/payment/gopay_test.go +++ b/internal/integration/payment/gopay_test.go @@ -21,7 +21,7 @@ import ( "strings" "testing" - "kra/pkg/paymentkit" + "kra/internal/paymentkit" gopayAlipay "github.com/go-pay/gopay/alipay" gopayDouyin "github.com/go-pay/gopay/douyin" @@ -177,7 +177,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { t.Errorf("read request: %v", err) return } - requestValues, err = paymentutil.XMLValues(body) + requestValues, err = paymentkit.XMLValues(body) if err != nil { t.Errorf("parse request: %v", err) 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["sign"] = wechatV2Sign(response, key, "MD5") - _, _ = w.Write(paymentutil.XMLEncode(response)) + _, _ = w.Write(paymentkit.XMLEncode(response)) })) defer server.Close() @@ -219,7 +219,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { if callbackValues["sign"] != originalSign { 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{ "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) return } - requestValues, err = paymentutil.XMLValues(body) + requestValues, err = paymentkit.XMLValues(body) if err != nil { t.Errorf("parse request: %v", err) return @@ -263,7 +263,7 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { "transaction_id": "WX-TRANSACTION-1", "total_fee": "123", "cash_fee": "123", "fee_type": "CNY", } response["sign"] = wechatV2Sign(response, key, "MD5") - _, _ = w.Write(paymentutil.XMLEncode(response)) + _, _ = w.Write(paymentkit.XMLEncode(response)) })) defer server.Close() @@ -344,7 +344,7 @@ func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) { } tc.mutate(response) response["sign"] = wechatV2Sign(response, key, "MD5") - _, _ = w.Write(paymentutil.XMLEncode(response)) + _, _ = w.Write(paymentkit.XMLEncode(response)) })) defer server.Close() diff --git a/internal/integration/payment/native_test.go b/internal/integration/payment/native_test.go index 20bab7d..8abdbbf 100644 --- a/internal/integration/payment/native_test.go +++ b/internal/integration/payment/native_test.go @@ -6,10 +6,39 @@ import ( "encoding/base64" bizpayment "kra/internal/biz/payment" "net/url" + "sort" "strings" "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) { values := map[string]string{ "appid": "wxd930ea5d5a258f4f", "mch_id": "10000100", "device_info": "1000", diff --git a/internal/integration/payment/paypal.go b/internal/integration/payment/paypal.go index e1431c7..5069aad 100644 --- a/internal/integration/payment/paypal.go +++ b/internal/integration/payment/paypal.go @@ -385,11 +385,6 @@ func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo strin return result, nil } -func paypalOrderTradeNo(order *paypal.OrderDetail) string { - tradeNo, _ := paypalOrderIdentity(order) - return tradeNo -} - func paypalOrderIdentity(order *paypal.OrderDetail) (string, error) { if order == 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 { switch strings.ToUpper(strings.TrimSpace(state)) { case "COMPLETED", "PARTIALLY_REFUNDED", "REFUNDED": diff --git a/internal/integration/payment/qq_test.go b/internal/integration/payment/qq_test.go index 9bdc99f..56bafd3 100644 --- a/internal/integration/payment/qq_test.go +++ b/internal/integration/payment/qq_test.go @@ -12,7 +12,7 @@ import ( "net/http/httptest" "testing" - "kra/pkg/paymentkit" + "kra/internal/paymentkit" "github.com/go-pay/gopay" gopayQQ "github.com/go-pay/gopay/qq" @@ -55,7 +55,7 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) { http.Error(w, "invalid XML", http.StatusBadRequest) return } - values, err := paymentutil.XMLValues(body) + values, err := paymentkit.XMLValues(body) if err != nil { t.Errorf("parse request XML: %v", err) http.Error(w, "invalid XML", http.StatusBadRequest) @@ -81,7 +81,7 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) { for key, value := range response { encoded[key] = fmt.Sprint(value) } - _, _ = w.Write(paymentutil.XMLEncode(encoded)) + _, _ = w.Write(paymentkit.XMLEncode(encoded)) })) result, err := (&qqAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ diff --git a/internal/integration/payment/result.go b/internal/integration/payment/result.go index f82cfff..a522676 100644 --- a/internal/integration/payment/result.go +++ b/internal/integration/payment/result.go @@ -8,7 +8,7 @@ import ( "net/url" "strings" - "kra/pkg/paymentkit" + "kra/internal/paymentkit" ) 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 { - return paymentutil.Text(values, key) + return paymentkit.Text(values, key) } 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 -// persistence concerns while stateless parsing rules live in paymentutil. +// persistence concerns while stateless parsing rules live in paymentkit. func normalizePaymentStatus(value, fallback string) string { - return paymentutil.NormalizeStatus(value, fallback) + return paymentkit.NormalizeStatus(value, fallback) } func parseIntegerAmount(value string) (int64, error) { - return paymentutil.ParseIntegerAmount(value) + return paymentkit.ParseIntegerAmount(value) } 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) { - return paymentutil.FormatDecimalAmount(amount, scale) + return paymentkit.FormatDecimalAmount(amount, scale) } 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 { - return paymentutil.JSONObject(raw) -} - -func nestedString(value any, keys ...string) string { - return paymentutil.NestedString(value, keys...) + return paymentkit.JSONObject(raw) } func valueAtPath(value any, path string) any { - return paymentutil.ValueAtPath(value, path) + return paymentkit.ValueAtPath(value, path) } 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 { - return paymentutil.ConfiguredValues(values, key) + return paymentkit.ConfiguredValues(values, key) } 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 @@ -138,12 +134,3 @@ func parseConfiguredAmount(object any, config map[string]any, fieldKey, scaleKey } 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 == "" { - return 0, false, nil - } - amount, err := parseDecimalAmount(value, fallback) - return amount, true, err -} diff --git a/internal/integration/payment/vendor.go b/internal/integration/payment/vendor.go index bf4dccc..bb3c4e3 100644 --- a/internal/integration/payment/vendor.go +++ b/internal/integration/payment/vendor.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "kra/pkg/paymentkit" + "kra/internal/paymentkit" ) 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)} 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") if statusField == "" { 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): result.Status = "created" 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 @@ -273,18 +290,18 @@ func (a *vendorPaymentAdapter) signRequest(request *http.Request, payload map[st 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[:])) case vendorSFT: - request.Header.Set("X-SFT-Sign", paymentutil.MD5Canonical(payload, secret)) + request.Header.Set("X-SFT-Sign", paymentkit.MD5Canonical(payload, secret)) 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 != "" { query := request.URL.Query() query.Set("access_token", token) request.URL.RawQuery = query.Encode() } 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: - 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 { @@ -327,9 +344,9 @@ func (a *vendorPaymentAdapter) verify(fields map[string]string, raw []byte, head values[key] = value } } - actual = paymentutil.MD5Canonical(values, secret) + actual = paymentkit.MD5Canonical(values, secret) default: - actual = paymentutil.HMACSHA256Hex(raw, secret, false) + actual = paymentkit.HMACSHA256Hex(raw, secret, false) } if !hmac.Equal([]byte(strings.ToLower(expected)), []byte(strings.ToLower(actual))) { return errors.New("支付回调签名校验失败") diff --git a/internal/integration/payment/vendor_test.go b/internal/integration/payment/vendor_test.go index 5e40ff0..994eb02 100644 --- a/internal/integration/payment/vendor_test.go +++ b/internal/integration/payment/vendor_test.go @@ -56,3 +56,23 @@ func TestVendorRefundMapsBusinessFailureStatus(t *testing.T) { 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) + } +} diff --git a/internal/integration/payment/wechat_v2.go b/internal/integration/payment/wechat_v2.go index 3d72762..00bdb70 100644 --- a/internal/integration/payment/wechat_v2.go +++ b/internal/integration/payment/wechat_v2.go @@ -44,12 +44,6 @@ func wechatV2SignType(value string) string { 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) { if req == nil { return nil, errors.New("微信支付 v2 下单请求为空") @@ -581,12 +575,6 @@ func nonce() string { } 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 { result := map[string]string{} for key, value := range values { diff --git a/internal/integration/payment/wechat_v3.go b/internal/integration/payment/wechat_v3.go index 9b25868..c734c45 100644 --- a/internal/integration/payment/wechat_v3.go +++ b/internal/integration/payment/wechat_v3.go @@ -596,10 +596,3 @@ func parseJSONInteger(object map[string]any, key string) (int64, bool, error) { amount, err := parseIntegerAmount(fmt.Sprint(raw)) 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) -} diff --git a/internal/integration/provider.go b/internal/integration/provider.go index 4753570..65c1bfe 100644 --- a/internal/integration/provider.go +++ b/internal/integration/provider.go @@ -5,10 +5,12 @@ package integration import ( integrationbiz "kra/internal/biz/integration" + bizpayment "kra/internal/biz/payment" "kra/internal/biz/system" "kra/internal/integration/cache" "kra/internal/integration/email" mqintegration "kra/internal/integration/mq" + paymentintegration "kra/internal/integration/payment" "kra/internal/integration/storage" websocketintegration "kra/internal/integration/websocket" "kra/pkg/mq" @@ -20,6 +22,8 @@ import ( var ProviderSet = wire.NewSet( cache.New, email.NewEmailRepo, + paymentintegration.NewFactory, + wire.Bind(new(bizpayment.PaymentAdapterFactory), new(*paymentintegration.Factory)), storage.NewFileStorage, NewConnectivityTester, wire.Bind(new(integrationbiz.IntegrationConnectionTester), new(*ConnectivityTester)), diff --git a/pkg/logging/context.go b/internal/logging/context.go similarity index 100% rename from pkg/logging/context.go rename to internal/logging/context.go diff --git a/pkg/logging/daily.go b/internal/logging/daily.go similarity index 100% rename from pkg/logging/daily.go rename to internal/logging/daily.go diff --git a/pkg/logging/source.go b/internal/logging/source.go similarity index 100% rename from pkg/logging/source.go rename to internal/logging/source.go diff --git a/pkg/logging/zap.go b/internal/logging/zap.go similarity index 100% rename from pkg/logging/zap.go rename to internal/logging/zap.go diff --git a/pkg/logging/zap_test.go b/internal/logging/zap_test.go similarity index 100% rename from pkg/logging/zap_test.go rename to internal/logging/zap_test.go diff --git a/internal/modules/task/definition.go b/internal/modules/task/definition.go index bb91dee..7968f38 100644 --- a/internal/modules/task/definition.go +++ b/internal/modules/task/definition.go @@ -10,9 +10,13 @@ func Definition() module.Definition { return module.Definition{ Name: "task", Migrations: datatask.Migrations(), - TimedTasks: []module.TimedTask{ - {Name: "ClearDB", Description: "定时清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Spec: "@daily", MethodName: "ClearDB", Enabled: true}, - {Name: "CleanStaleUploads", Description: "定时清理过期大文件上传会话", Spec: "@hourly", MethodName: "CleanStaleUploads", Enabled: true}, - }, + TimedTasks: TimedTasks(), + } +} + +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}, } } diff --git a/pkg/paymentkit/README.md b/internal/paymentkit/README.md similarity index 100% rename from pkg/paymentkit/README.md rename to internal/paymentkit/README.md diff --git a/pkg/paymentkit/amount.go b/internal/paymentkit/amount.go similarity index 99% rename from pkg/paymentkit/amount.go rename to internal/paymentkit/amount.go index 180fe74..0f71ac5 100644 --- a/pkg/paymentkit/amount.go +++ b/internal/paymentkit/amount.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import ( "errors" diff --git a/pkg/paymentkit/amount_test.go b/internal/paymentkit/amount_test.go similarity index 97% rename from pkg/paymentkit/amount_test.go rename to internal/paymentkit/amount_test.go index 1d82d33..5697861 100644 --- a/pkg/paymentkit/amount_test.go +++ b/internal/paymentkit/amount_test.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import "testing" diff --git a/pkg/paymentkit/json.go b/internal/paymentkit/json.go similarity index 98% rename from pkg/paymentkit/json.go rename to internal/paymentkit/json.go index c31d6bc..98bdf0e 100644 --- a/pkg/paymentkit/json.go +++ b/internal/paymentkit/json.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import ( "encoding/json" diff --git a/pkg/paymentkit/json_test.go b/internal/paymentkit/json_test.go similarity index 95% rename from pkg/paymentkit/json_test.go rename to internal/paymentkit/json_test.go index 34f46e2..fcb0bc8 100644 --- a/pkg/paymentkit/json_test.go +++ b/internal/paymentkit/json_test.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import "testing" diff --git a/pkg/paymentkit/signing.go b/internal/paymentkit/signing.go similarity index 97% rename from pkg/paymentkit/signing.go rename to internal/paymentkit/signing.go index 6c1d012..dfc0410 100644 --- a/pkg/paymentkit/signing.go +++ b/internal/paymentkit/signing.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import ( "crypto/hmac" diff --git a/pkg/paymentkit/status.go b/internal/paymentkit/status.go similarity index 98% rename from pkg/paymentkit/status.go rename to internal/paymentkit/status.go index 1f77fea..1fe8102 100644 --- a/pkg/paymentkit/status.go +++ b/internal/paymentkit/status.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import ( "fmt" diff --git a/pkg/paymentkit/xml.go b/internal/paymentkit/xml.go similarity index 98% rename from pkg/paymentkit/xml.go rename to internal/paymentkit/xml.go index 6c46733..60dd4fd 100644 --- a/pkg/paymentkit/xml.go +++ b/internal/paymentkit/xml.go @@ -1,4 +1,4 @@ -package paymentutil +package paymentkit import ( "bytes" diff --git a/internal/server/gin.go b/internal/server/gin.go index 4d9c02a..cb99b76 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -8,23 +8,17 @@ import ( "kra/internal/config" websocket "kra/internal/integration/websocket" - "kra/internal/server/handler" + "kra/internal/server/httpx" "kra/internal/server/middleware" - "kra/internal/server/router" "kra/internal/server/staticfiles" - "kra/internal/service" - "kra/pkg/httpx" + systemservice "kra/internal/service/system" platformmodule "kra/pkg/module" "github.com/gin-gonic/gin" 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 { - 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 { +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 { if runtime == nil { runtime = config.NewStore(nil) } diff --git a/internal/server/gin_test.go b/internal/server/gin_test.go index 4e3cc78..086b16a 100644 --- a/internal/server/gin_test.go +++ b/internal/server/gin_test.go @@ -14,10 +14,16 @@ import ( "kra/internal/config" "kra/internal/routecatalog" "kra/internal/server/handler" + "kra/internal/server/router" + systemservice "kra/internal/service/system" "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 { return &handler.Set{ Authority: &handler.Authority{}, Menu: &handler.Menu{}, API: &handler.API{}, diff --git a/internal/server/handler/announcement.go b/internal/server/handler/announcement.go index cec19f0..2d4d189 100644 --- a/internal/server/handler/announcement.go +++ b/internal/server/handler/announcement.go @@ -1,16 +1,18 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "time" "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} } diff --git a/internal/server/handler/api.go b/internal/server/handler/api.go index c81c452..d56fb58 100644 --- a/internal/server/handler/api.go +++ b/internal/server/handler/api.go @@ -1,15 +1,15 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "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) { var req dto.APIListRequest diff --git a/internal/server/handler/api_token.go b/internal/server/handler/api_token.go index fa8096a..03dbf3d 100644 --- a/internal/server/handler/api_token.go +++ b/internal/server/handler/api_token.go @@ -1,17 +1,17 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" ) 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} } diff --git a/internal/server/handler/audit.go b/internal/server/handler/audit.go index 38a4ace..d737fea 100644 --- a/internal/server/handler/audit.go +++ b/internal/server/handler/audit.go @@ -7,20 +7,20 @@ import ( "strconv" "time" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" ) type Audit struct { - service *service.AuditService - recorder *service.AuditRecorder - logs *service.LogViewerService + service *systemservice.AuditService + recorder *systemservice.AuditRecorder + logs *systemservice.LogViewerService 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} } @@ -85,7 +85,7 @@ func (h *Audit) Logins(c *gin.Context) { Fail(c, err.Error()) 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 { Fail(c, "获取失败") return diff --git a/internal/server/handler/authority.go b/internal/server/handler/authority.go index 22f5f0a..922feb4 100644 --- a/internal/server/handler/authority.go +++ b/internal/server/handler/authority.go @@ -1,15 +1,19 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "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) { items, err := h.service.Authorities(c.Request.Context()) diff --git a/internal/server/handler/dictionary.go b/internal/server/handler/dictionary.go index 652a58d..a6bdbd1 100644 --- a/internal/server/handler/dictionary.go +++ b/internal/server/handler/dictionary.go @@ -1,16 +1,18 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "strconv" "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} } diff --git a/internal/server/handler/email.go b/internal/server/handler/email.go index 2446c2e..93dff87 100644 --- a/internal/server/handler/email.go +++ b/internal/server/handler/email.go @@ -1,15 +1,15 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "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) { if err := h.service.Test(c.Request.Context()); err != nil { diff --git a/internal/server/handler/export.go b/internal/server/handler/export.go index deabe09..b6ef29f 100644 --- a/internal/server/handler/export.go +++ b/internal/server/handler/export.go @@ -2,8 +2,8 @@ package handler import ( "errors" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "net/http" "net/url" "strings" @@ -13,10 +13,10 @@ import ( ) 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} } @@ -214,9 +214,9 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc { value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank) if err != nil { switch { - case errors.Is(err, service.ErrExportTokenMalformed): + case errors.Is(err, systemservice.ErrExportTokenMalformed): Fail(c, "解析导出参数失败") - case errors.Is(err, service.ErrExportTokenType): + case errors.Is(err, systemservice.ErrExportTokenType): Fail(c, "token类型错误") default: Fail(c, "导出token无效或已过期") diff --git a/internal/server/handler/http.go b/internal/server/handler/http.go index b20e72c..03c2801 100644 --- a/internal/server/handler/http.go +++ b/internal/server/handler/http.go @@ -5,8 +5,8 @@ package handler import ( "kra/internal/biz/system" + "kra/internal/server/httpx" "kra/internal/server/middleware" - "kra/pkg/httpx" "github.com/gin-gonic/gin" ) diff --git a/internal/server/handler/integration_config.go b/internal/server/handler/integration_config.go index d78dfcd..db914ec 100644 --- a/internal/server/handler/integration_config.go +++ b/internal/server/handler/integration_config.go @@ -1,17 +1,17 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + integrationservice "kra/internal/service/integration" "github.com/gin-gonic/gin" ) 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} } diff --git a/internal/server/handler/media.go b/internal/server/handler/media.go index 6e3add9..97a24ee 100644 --- a/internal/server/handler/media.go +++ b/internal/server/handler/media.go @@ -2,17 +2,17 @@ package handler import ( "errors" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "net/http" "strconv" "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 { limit := h.service.MediaConfig().EffectiveMaxFileSize() diff --git a/internal/server/handler/menu.go b/internal/server/handler/menu.go index 0f68fcc..d2c274d 100644 --- a/internal/server/handler/menu.go +++ b/internal/server/handler/menu.go @@ -2,15 +2,15 @@ package handler import ( "errors" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "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 { if req == nil { diff --git a/internal/server/handler/navigation.go b/internal/server/handler/navigation.go index 8fb97d9..ad4ac90 100644 --- a/internal/server/handler/navigation.go +++ b/internal/server/handler/navigation.go @@ -1,14 +1,16 @@ package handler import ( - "kra/internal/service" + systemservice "kra/internal/service/system" "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) { claims := Claims(c) if claims == nil { diff --git a/internal/server/handler/organization.go b/internal/server/handler/organization.go index 4ae923c..fd557fd 100644 --- a/internal/server/handler/organization.go +++ b/internal/server/handler/organization.go @@ -1,18 +1,18 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" ) type Organization struct { - departments *service.DepartmentService - positions *service.PositionService + departments *systemservice.DepartmentService + 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} } diff --git a/internal/server/handler/parameter.go b/internal/server/handler/parameter.go index d92793e..6f6e97b 100644 --- a/internal/server/handler/parameter.go +++ b/internal/server/handler/parameter.go @@ -1,17 +1,17 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" ) 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} } diff --git a/internal/server/handler/payment.go b/internal/server/handler/payment.go index 82f4888..75232f8 100644 --- a/internal/server/handler/payment.go +++ b/internal/server/handler/payment.go @@ -2,16 +2,18 @@ package handler import ( "io" - "kra/internal/service" "kra/internal/service/dto" + paymentservice "kra/internal/service/payment" "net/http" "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) { var req dto.PaymentQueryRequest 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) raw, err := io.ReadAll(c.Request.Body) if err != nil { - writePaymentCallbackAck(c, service.DefaultPaymentCallbackAck(c.Param("provider"), false)) + _ = c.Error(err) + writePaymentCallbackAck(c, paymentservice.DefaultPaymentCallbackAck(c.Param("provider"), false)) return } headers := map[string]string{} diff --git a/internal/server/handler/permission.go b/internal/server/handler/permission.go index d4b9745..65a89b0 100644 --- a/internal/server/handler/permission.go +++ b/internal/server/handler/permission.go @@ -1,15 +1,17 @@ package handler import ( - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "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} } diff --git a/internal/server/handler/public.go b/internal/server/handler/public.go index 7a16b47..9e89826 100644 --- a/internal/server/handler/public.go +++ b/internal/server/handler/public.go @@ -9,21 +9,21 @@ import ( "strings" "time" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" "github.com/mojocn/base64Captcha" ) type Public struct { - auth *service.AuthService - system *service.SystemConfigService - settings *service.SecurityService + auth *systemservice.AuthService + system *systemservice.SystemConfigService + settings *systemservice.SecurityService 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}} } @@ -155,7 +155,7 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc { } type captchaStore struct { - service *service.SecurityService + service *systemservice.SecurityService } func (s *captchaStore) Set(id, value string) error { diff --git a/internal/server/handler/session.go b/internal/server/handler/session.go index 00c3e2d..41f20fb 100644 --- a/internal/server/handler/session.go +++ b/internal/server/handler/session.go @@ -2,14 +2,14 @@ package handler import ( "kra/internal/server/middleware" - "kra/internal/service" + systemservice "kra/internal/service/system" "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) { token := middleware.RequestToken(c, false) if token == "" { diff --git a/internal/server/handler/system_config.go b/internal/server/handler/system_config.go index c7c6467..3ec96c5 100644 --- a/internal/server/handler/system_config.go +++ b/internal/server/handler/system_config.go @@ -3,19 +3,19 @@ package handler import ( "errors" "kra/internal/biz/system" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "log/slog" "github.com/gin-gonic/gin" ) type SystemConfig struct { - system *service.SystemConfigService - settings *service.SecurityService + system *systemservice.SystemConfigService + 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} } diff --git a/internal/server/handler/task.go b/internal/server/handler/task.go index 77ca735..cf8e432 100644 --- a/internal/server/handler/task.go +++ b/internal/server/handler/task.go @@ -2,8 +2,8 @@ package handler import ( "fmt" - "kra/internal/service" "kra/internal/service/dto" + taskservice "kra/internal/service/task" "net/http" "time" @@ -11,10 +11,10 @@ import ( ) 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} } diff --git a/internal/server/handler/user.go b/internal/server/handler/user.go index a9aa855..21e4aa1 100644 --- a/internal/server/handler/user.go +++ b/internal/server/handler/user.go @@ -1,8 +1,10 @@ package handler import ( - "kra/internal/service" + "errors" + "kra/internal/biz/system" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "strconv" "time" @@ -10,11 +12,11 @@ import ( ) type User struct { - service *service.UserService - auth *service.AuthService + service *systemservice.UserService + 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} } @@ -64,7 +66,7 @@ func (h *User) Create(c *gin.Context) { } result, err := h.service.CreateUserRequest(c.Request.Context(), &req) if err != nil { - if service.IsPasswordPolicyError(err) { + if systemservice.IsPasswordPolicyError(err) { Fail(c, err.Error()) return } @@ -141,7 +143,7 @@ func (h *User) ResetPassword(c *gin.Context) { return } 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()) return } @@ -171,7 +173,7 @@ func (h *User) ChangePassword(c *gin.Context) { return } 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()) return } @@ -206,7 +208,11 @@ func (h *User) SetAuthorities(c *gin.Context) { return } 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 } Write(c, CodeSuccess, gin.H{}, "修改成功") diff --git a/internal/server/handler/version.go b/internal/server/handler/version.go index fd11345..5a18fad 100644 --- a/internal/server/handler/version.go +++ b/internal/server/handler/version.go @@ -8,15 +8,15 @@ import ( "strconv" "time" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "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) { var stageErr *system.VersionStageError diff --git a/pkg/httpx/response.go b/internal/server/httpx/response.go similarity index 100% rename from pkg/httpx/response.go rename to internal/server/httpx/response.go diff --git a/pkg/httpx/response_test.go b/internal/server/httpx/response_test.go similarity index 100% rename from pkg/httpx/response_test.go rename to internal/server/httpx/response_test.go diff --git a/internal/server/middleware/access.go b/internal/server/middleware/access.go index 7183699..309e900 100644 --- a/internal/server/middleware/access.go +++ b/internal/server/middleware/access.go @@ -3,7 +3,7 @@ package middleware import ( "context" "kra/internal/biz/system" - httpx "kra/pkg/httpx" + httpx "kra/internal/server/httpx" "kra/internal/config" "kra/internal/utils/routepath" diff --git a/internal/server/middleware/access_log.go b/internal/server/middleware/access_log.go index 62a5769..147eba4 100644 --- a/internal/server/middleware/access_log.go +++ b/internal/server/middleware/access_log.go @@ -7,7 +7,7 @@ import ( "errors" "io" "kra/internal/biz/system" - httpx "kra/pkg/httpx" + httpx "kra/internal/server/httpx" "log/slog" "mime" "net/http" diff --git a/internal/server/middleware/audit.go b/internal/server/middleware/audit.go index 502632b..fc75664 100644 --- a/internal/server/middleware/audit.go +++ b/internal/server/middleware/audit.go @@ -13,15 +13,15 @@ import ( "kra/internal/config" "kra/internal/routecatalog" - "kra/internal/service" "kra/internal/service/dto" + systemservice "kra/internal/service/system" "github.com/gin-gonic/gin" ) 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) { if runtime == nil || recorder == nil { c.Next() diff --git a/internal/server/middleware/auth.go b/internal/server/middleware/auth.go index 57814e9..ceedc54 100644 --- a/internal/server/middleware/auth.go +++ b/internal/server/middleware/auth.go @@ -4,7 +4,7 @@ import ( "context" "errors" "kra/internal/biz/system" - httpx "kra/pkg/httpx" + httpx "kra/internal/server/httpx" "net/http" "strconv" "strings" diff --git a/internal/server/middleware/capture.go b/internal/server/middleware/capture.go index b65dc46..5ce97b2 100644 --- a/internal/server/middleware/capture.go +++ b/internal/server/middleware/capture.go @@ -68,7 +68,3 @@ func redactJSON(raw []byte, contentType string, limit int) string { } func stringValue(value any) string { text, _ := value.(string); return text } - -func isBootstrapPath(path string) bool { - return strings.HasSuffix(path, "/health") || strings.Contains(path, "/init/") -} diff --git a/internal/server/middleware/error_audit.go b/internal/server/middleware/error_audit.go index 75b64e5..dd46d7a 100644 --- a/internal/server/middleware/error_audit.go +++ b/internal/server/middleware/error_audit.go @@ -3,7 +3,7 @@ package middleware import ( "bytes" "encoding/json" - httpx "kra/pkg/httpx" + httpx "kra/internal/server/httpx" "log/slog" "strings" diff --git a/internal/server/middleware/rate_limit.go b/internal/server/middleware/rate_limit.go index e0015bd..6b1ee2e 100644 --- a/internal/server/middleware/rate_limit.go +++ b/internal/server/middleware/rate_limit.go @@ -1,8 +1,8 @@ package middleware import ( - "kra/internal/service" - httpx "kra/pkg/httpx" + httpx "kra/internal/server/httpx" + systemservice "kra/internal/service/system" "net/http" "strings" "time" @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" ) -func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc { +func SecurityRateLimit(settings *systemservice.SecurityService) gin.HandlerFunc { return func(c *gin.Context) { path := strings.TrimSuffix(c.Request.URL.Path, "/") if !strings.HasSuffix(path, "/base/login") && !strings.HasSuffix(path, "/base/captcha") { diff --git a/internal/server/middleware/request.go b/internal/server/middleware/request.go index dbc90c2..4002ec1 100644 --- a/internal/server/middleware/request.go +++ b/internal/server/middleware/request.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "strings" - "kra/pkg/logging" + "kra/internal/logging" "github.com/gin-gonic/gin" "github.com/google/uuid" diff --git a/internal/service/dto/audit.go b/internal/service/dto/audit.go index 7af159f..0235d4a 100644 --- a/internal/service/dto/audit.go +++ b/internal/service/dto/audit.go @@ -18,17 +18,6 @@ type OperationRecordRequest struct { DeviceID string } -type DataAccessRecordRequest struct { - EventType string - Operation string - UserID uint - AuthorityID uint - RequestID string - Method string - Path string - Detail string -} - type DataAccessListRequest struct { Page int `json:"page"` PageSize int `json:"pageSize"` @@ -45,10 +34,13 @@ type OperationRecordSearchRequest struct { } type LoginLogSearchRequest struct { - Page int `form:"page"` - PageSize int `form:"pageSize"` - Username string `form:"username"` - Status *bool `form:"status"` + Page int `form:"page"` + PageSize int `form:"pageSize"` + Username string `form:"username"` + IP string `form:"ip"` + Status *bool `form:"status"` + StartCreatedAt *time.Time `form:"startCreatedAt" time_format:"2006-01-02 15:04:05"` + EndCreatedAt *time.Time `form:"endCreatedAt" time_format:"2006-01-02 15:04:05"` } type AuditIDQuery struct { diff --git a/internal/service/dto/system.go b/internal/service/dto/system.go index 28fefc3..f1955f4 100644 --- a/internal/service/dto/system.go +++ b/internal/service/dto/system.go @@ -65,15 +65,6 @@ type SwitchAuthorityRequest struct { AuthorityID uint `json:"authorityId"` } -type LoginLogRequest struct { - Username string - IP string - Status bool - ErrorMessage string - Agent string - UserID uint -} - type UserResponse struct { ID uint `json:"ID"` CreatedAt time.Time `json:"CreatedAt"` diff --git a/internal/service/service.go b/internal/service/service.go index 3be96fb..6f1df60 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,12 +1,6 @@ package service -// This package is the transport layer's entry point into the service tier. -// Concrete implementations live under service/system, service/payment, -// service/integration, and service/task; the aliases here give handlers one -// import instead of four, and Wire composes the module provider sets directly. - import ( - "kra/internal/service/dto" integrationservice "kra/internal/service/integration" paymentservice "kra/internal/service/payment" systemservice "kra/internal/service/system" @@ -15,41 +9,6 @@ import ( "github.com/google/wire" ) -type AccessControlService = systemservice.AccessControlService -type AnnouncementService = systemservice.AnnouncementService -type APIService = systemservice.APIService -type AuditService = systemservice.AuditService -type AuditRecorder = systemservice.AuditRecorder -type AuthService = systemservice.AuthService -type AuthorityService = systemservice.AuthorityService -type DepartmentService = systemservice.DepartmentService -type DictionaryService = systemservice.DictionaryService -type EmailService = systemservice.EmailService -type ExportService = systemservice.ExportService -type LogViewerService = systemservice.LogViewerService -type MediaService = systemservice.MediaService -type MenuService = systemservice.MenuService -type ParameterService = systemservice.ParameterService -type PermissionService = systemservice.PermissionService -type PositionService = systemservice.PositionService -type SecurityService = systemservice.SecurityService -type SystemConfigService = systemservice.SystemConfigService -type TokenService = systemservice.TokenService -type VersionService = systemservice.VersionService -type PaymentService = paymentservice.PaymentService -type IntegrationConfigService = integrationservice.IntegrationConfigService -type TaskService = taskservice.TaskService -type UserService = systemservice.UserService - -var ErrExportTokenMalformed = systemservice.ErrExportTokenMalformed -var ErrExportTokenType = systemservice.ErrExportTokenType - -func IsPasswordPolicyError(err error) bool { return systemservice.IsPasswordPolicyError(err) } - -func DefaultPaymentCallbackAck(provider string, success bool) dto.PaymentCallbackAck { - return paymentservice.DefaultPaymentCallbackAck(provider, success) -} - // ProviderSet aggregates all service modules for Wire. var ProviderSet = wire.NewSet( systemservice.ProviderSet, diff --git a/internal/service/system/api_token.go b/internal/service/system/api_token.go index 74cda78..584944b 100644 --- a/internal/service/system/api_token.go +++ b/internal/service/system/api_token.go @@ -57,6 +57,3 @@ func (s *TokenService) DisableAPIToken(ctx context.Context, id uint) error { func (s *TokenService) BlacklistToken(ctx context.Context, token string) error { return s.uc.BlacklistToken(ctx, token) } -func (s *TokenService) IsTokenDisabled(ctx context.Context, token string) (bool, error) { - return s.uc.IsTokenDisabled(ctx, token) -} diff --git a/internal/service/system/audit.go b/internal/service/system/audit.go index 943faff..cc393aa 100644 --- a/internal/service/system/audit.go +++ b/internal/service/system/audit.go @@ -53,11 +53,11 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []int) error { return s.uc.DeleteOperations(ctx, ids) } -func (s *AuditRecorder) RecordLogin(ctx context.Context, v *system.LoginLog) error { - return s.uc.RecordLogin(ctx, v) -} -func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status *bool) ([]*dto.LoginLogResponse, int64, error) { - return s.Logins(ctx, page, size, &system.LoginLogFilter{Username: username, Status: status}) +func (s *AuditService) LoginsFilter(ctx context.Context, request dto.LoginLogSearchRequest) ([]*dto.LoginLogResponse, int64, error) { + return s.Logins(ctx, request.Page, request.PageSize, &system.LoginLogFilter{ + Username: request.Username, IP: request.IP, Status: request.Status, + StartCreatedAt: request.StartCreatedAt, EndCreatedAt: request.EndCreatedAt, + }) } func loginDTO(v *system.LoginLog) *dto.LoginLogResponse { var user any = map[string]any{} @@ -88,9 +88,6 @@ func (s *AuditService) DeleteLogins(ctx context.Context, ids []int) error { return s.uc.DeleteLogins(ctx, ids) } -func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *system.DataAccessLog) error { - return s.uc.RecordDataAccess(ctx, v) -} func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]*dto.DataAccessLogResponse, int64, error) { return s.DataAccess(ctx, req.Page, req.PageSize, &system.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable}) } diff --git a/internal/service/system/export_excel_value_test.go b/internal/service/system/export_excel_value_test.go index 2b5d6dc..7ee3724 100644 --- a/internal/service/system/export_excel_value_test.go +++ b/internal/service/system/export_excel_value_test.go @@ -8,3 +8,9 @@ func TestExportCellValuePreservesNumericText(t *testing.T) { t.Fatalf("exportCellValue() = %#v, want string 007", exportCellValue("007")) } } + +func TestExportCellValuePreservesDriverBytes(t *testing.T) { + if value, ok := exportCellValue([]byte("42")).(string); !ok || value != "42" { + t.Fatalf("driver bytes = %#v", exportCellValue([]byte("42"))) + } +} diff --git a/internal/service/system/menu.go b/internal/service/system/menu.go index 5ca353c..3a2d490 100644 --- a/internal/service/system/menu.go +++ b/internal/service/system/menu.go @@ -11,14 +11,6 @@ type MenuService struct{ uc *system.MenuUsecase } func NewMenuService(uc *system.MenuUsecase) *MenuService { return &MenuService{uc: uc} } -func (s *MenuService) List(ctx context.Context) ([]*dto.MenuResponse, error) { - items, err := s.uc.List(ctx) - if err != nil { - return nil, err - } - return menuResponses(items), nil -} - func (s *MenuService) Tree(ctx context.Context) ([]*dto.MenuResponse, error) { items, err := s.uc.Tree(ctx) if err != nil { diff --git a/internal/service/system/version.go b/internal/service/system/version.go index da09d8c..fe37209 100644 --- a/internal/service/system/version.go +++ b/internal/service/system/version.go @@ -219,9 +219,6 @@ func (s *VersionService) Export(ctx context.Context, name, code, description str } return nil } -func (s *VersionService) Import(ctx context.Context, bundle *system.VersionBundle) error { - return s.uc.ImportVersionBundle(ctx, bundle) -} func versionMenuDomain(value dto.VersionMenuRequest) *system.Menu { menu := &system.Menu{Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, ActiveName: value.Meta.ActiveName, KeepAlive: value.Meta.KeepAlive, DefaultMenu: value.Meta.DefaultMenu, Title: value.Meta.Title, Icon: value.Meta.Icon, CloseTab: value.Meta.CloseTab, TransitionType: value.Meta.TransitionType} for _, button := range value.MenuButtons { @@ -253,20 +250,15 @@ func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersi } bundle.Dictionaries = append(bundle.Dictionaries, item) } - if err := s.Import(ctx, bundle); err != nil { - return err - } raw, err := json.Marshal(req) if err != nil { return &system.VersionStageError{Stage: system.VersionStageJSON, Err: err} } - if err = s.uc.CreateVersion(ctx, &system.Version{ + audit := &system.Version{ Name: versionPointer(req.Version.Name), Code: versionPointer(req.Version.Code + "_imported_" + time.Now().Format("20060102150405")), Description: versionPointer("导入版本: " + req.Version.Description), Data: versionPointer(string(raw)), - }); err != nil { - return &system.VersionStageError{Stage: system.VersionStageSave, Err: err} } - return nil + return s.uc.ImportVersionBundle(ctx, bundle, audit) } diff --git a/internal/service/system/version_import_test.go b/internal/service/system/version_import_test.go index 01b1b90..dbc833b 100644 --- a/internal/service/system/version_import_test.go +++ b/internal/service/system/version_import_test.go @@ -14,12 +14,8 @@ type versionImportRepo struct { saveErr error } -func (r *versionImportRepo) ImportVersionBundle(context.Context, *bizsystem.VersionBundle) error { - return nil -} - -func (r *versionImportRepo) CreateVersion(context.Context, *bizsystem.Version) error { - return r.saveErr +func (r *versionImportRepo) ImportVersionBundle(context.Context, *bizsystem.VersionBundle, *bizsystem.Version) error { + return &bizsystem.VersionStageError{Stage: bizsystem.VersionStageSave, Err: r.saveErr} } func TestImportRequestReturnsVersionAuditSaveFailure(t *testing.T) { diff --git a/internal/worker/task_registry.go b/internal/worker/task_registry.go index 01a60fc..bc83fb2 100644 --- a/internal/worker/task_registry.go +++ b/internal/worker/task_registry.go @@ -8,6 +8,7 @@ import ( taskbiz "kra/internal/biz/task" "kra/internal/config" + taskmodule "kra/internal/modules/task" platformtask "kra/pkg/task" ) @@ -27,8 +28,12 @@ func (methods *TaskMethods) RegisterTasks(registry *platformtask.Registry) { if methods == nil || registry == nil { return } + descriptions := map[string]string{} + for _, task := range taskmodule.TimedTasks() { + descriptions[task.MethodName] = task.Description + } registry.Register(platformtask.Method{ - Name: taskbiz.TaskMethodClearDB, Description: "清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Run: func(ctx context.Context, _ json.RawMessage) error { + Name: taskbiz.TaskMethodClearDB, Description: descriptions[taskbiz.TaskMethodClearDB], Run: func(ctx context.Context, _ json.RawMessage) error { if ctx == nil { ctx = context.Background() } @@ -42,7 +47,7 @@ func (methods *TaskMethods) RegisterTasks(registry *platformtask.Registry) { }, }) registry.Register(platformtask.Method{ - Name: taskbiz.TaskMethodUploads, Description: "清理过期大文件上传会话", Run: func(ctx context.Context, _ json.RawMessage) error { + Name: taskbiz.TaskMethodUploads, Description: descriptions[taskbiz.TaskMethodUploads], Run: func(ctx context.Context, _ json.RawMessage) error { if ctx == nil { ctx = context.Background() } diff --git a/internal/worker/task_runtime_test.go b/internal/worker/task_runtime_test.go index fff3191..eeaad77 100644 --- a/internal/worker/task_runtime_test.go +++ b/internal/worker/task_runtime_test.go @@ -219,7 +219,7 @@ func TestAutomaticAndManualTriggersShareOverlapGate(t *testing.T) { case <-time.After(time.Second): t.Fatal("automatic task execution did not start") } - if scheduler.Trigger(task) { + if scheduler.trigger(task) { t.Fatal("manual trigger bypassed an active automatic execution") } if got := calls.Load(); got != 1 { diff --git a/internal/worker/task_scheduler.go b/internal/worker/task_scheduler.go index 31d467b..31a0bfa 100644 --- a/internal/worker/task_scheduler.go +++ b/internal/worker/task_scheduler.go @@ -402,7 +402,7 @@ func (s *TaskScheduler) TriggerID(ctx context.Context, id uint) error { if task == nil { return errors.New("任务不存在") } - if !s.Trigger(task) { + if !s.trigger(task) { return errors.New("任务正在执行或调度器正在停止") } return nil @@ -422,7 +422,7 @@ func (s *TaskScheduler) NextRuns() map[uint]time.Time { return out } -func (s *TaskScheduler) Trigger(task *taskbiz.TimedTask) bool { +func (s *TaskScheduler) trigger(task *taskbiz.TimedTask) bool { if task == nil { return false } diff --git a/pkg/paymentkit/callback.go b/pkg/paymentkit/callback.go deleted file mode 100644 index d398209..0000000 --- a/pkg/paymentkit/callback.go +++ /dev/null @@ -1,83 +0,0 @@ -package paymentutil - -import ( - "encoding/json" - "errors" - "fmt" -) - -// CallbackAck is the provider-facing HTTP acknowledgement returned after a -// payment notification is processed. -type CallbackAck struct { - StatusCode int - ContentType string - Body []byte -} - -// CallbackError carries a safe provider acknowledgement alongside the cause -// that should be logged or returned to the application layer. -type CallbackError struct { - Cause error - Ack CallbackAck -} - -func (e *CallbackError) Error() string { - if e == nil || e.Cause == nil { - return "支付回调处理失败" - } - return e.Cause.Error() -} - -func (e *CallbackError) Unwrap() error { - if e == nil { - return nil - } - return e.Cause -} - -func CallbackFailure(err error, fallback CallbackAck) CallbackAck { - var callbackErr *CallbackError - if errors.As(err, &callbackErr) && callbackErr.Ack.StatusCode != 0 { - return callbackErr.Ack - } - return fallback -} - -// DefaultCallbackAck returns the conventional acknowledgement for providers -// whose callback wire formats are stable across modules. -func DefaultCallbackAck(provider string, success bool) CallbackAck { - status := 200 - if !success { - status = 500 - } - switch provider { - case ProviderAlipay, ProviderAlipayV3: - body := "success" - if !success { - body = "failure" - } - return CallbackAck{StatusCode: status, ContentType: "text/plain; charset=utf-8", Body: []byte(body)} - case ProviderWechatV2: - code, message := "SUCCESS", "OK" - if !success { - code, message = "FAIL", "FAIL" - } - body := fmt.Sprintf("", code, message) - return CallbackAck{StatusCode: status, ContentType: "application/xml; charset=utf-8", Body: []byte(body)} - case ProviderWechatV3: - code, message := "SUCCESS", "成功" - if !success { - code, message = "FAIL", "失败" - } - body, _ := json.Marshal(map[string]string{"code": code, "message": message}) - return CallbackAck{StatusCode: status, ContentType: "application/json; charset=utf-8", Body: body} - case ProviderApple: - return CallbackAck{StatusCode: status} - default: - body := "success" - if !success { - body = "failure" - } - return CallbackAck{StatusCode: status, ContentType: "text/plain; charset=utf-8", Body: []byte(body)} - } -} diff --git a/pkg/paymentkit/callback_test.go b/pkg/paymentkit/callback_test.go deleted file mode 100644 index 06e697a..0000000 --- a/pkg/paymentkit/callback_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package paymentutil - -import ( - "errors" - "testing" -) - -func TestDefaultCallbackAck(t *testing.T) { - cases := []struct { - provider string - success bool - status int - contentType string - body string - }{ - {ProviderAlipay, true, 200, "text/plain; charset=utf-8", "success"}, - {ProviderWechatV2, false, 500, "application/xml; charset=utf-8", ""}, - {ProviderWechatV3, true, 200, "application/json; charset=utf-8", `{"code":"SUCCESS","message":"成功"}`}, - {ProviderApple, true, 200, "", ""}, - } - for _, tc := range cases { - ack := DefaultCallbackAck(tc.provider, tc.success) - if ack.StatusCode != tc.status || ack.ContentType != tc.contentType || string(ack.Body) != tc.body { - t.Fatalf("%s ack = %#v", tc.provider, ack) - } - } -} - -func TestCallbackFailureUsesProviderAck(t *testing.T) { - fallback := DefaultCallbackAck(ProviderAlipay, false) - want := DefaultCallbackAck(ProviderWechatV3, false) - err := &CallbackError{Cause: errors.New("provider rejected"), Ack: want} - got := CallbackFailure(err, fallback) - if got.StatusCode != want.StatusCode || string(got.Body) != string(want.Body) { - t.Fatalf("ack = %#v, want %#v", got, want) - } -} diff --git a/pkg/paymentkit/provider.go b/pkg/paymentkit/provider.go deleted file mode 100644 index 003c952..0000000 --- a/pkg/paymentkit/provider.go +++ /dev/null @@ -1,36 +0,0 @@ -package paymentutil - -const ( - ProviderAlipay = "alipay" - ProviderAlipayV3 = "alipay-v3" - ProviderWechatV2 = "wechat-v2" - ProviderWechatV3 = "wechat-v3" - ProviderApple = "apple-iap" - ProviderChinaums = "chinaums" - ProviderSFT = "sft" - ProviderSuperPay = "supper-pay" - ProviderWechatGame = "wechat-game-pay" - ProviderDouyinGame = "douyin-game-pay" - ProviderDouyin = "douyin" - ProviderQQ = "qq" - ProviderAllinPay = "allinpay" - ProviderLakala = "lakala" - ProviderPayPal = "paypal" - ProviderSaobei = "saobei" - ProviderInternal = "internal" - - ModeExternal = "external" - ModeInternal = "internal" -) - -var externalProviders = [...]string{ - ProviderAlipay, ProviderAlipayV3, ProviderWechatV2, ProviderWechatV3, ProviderApple, - ProviderChinaums, ProviderSFT, ProviderSuperPay, ProviderWechatGame, ProviderDouyinGame, - ProviderDouyin, ProviderQQ, ProviderAllinPay, ProviderLakala, ProviderPayPal, ProviderSaobei, -} - -// SupportedProviders returns a copy so callers cannot mutate the process-wide -// provider catalog. -func SupportedProviders() []string { - return append([]string(nil), externalProviders[:]...) -} diff --git a/pkg/task/registry.go b/pkg/task/registry.go index 5fedeb7..c850120 100644 --- a/pkg/task/registry.go +++ b/pkg/task/registry.go @@ -36,12 +36,6 @@ func (r *Registry) Register(method Method) { r.methods[method.Name] = method } -func (r *Registry) RegisterAll(methods []Method) { - for _, method := range methods { - r.Register(method) - } -} - func (r *Registry) Lookup(name string) (MethodFunc, bool) { if r == nil { return nil, false diff --git a/pkg/task/registry_test.go b/pkg/task/registry_test.go index b2824ee..92a7644 100644 --- a/pkg/task/registry_test.go +++ b/pkg/task/registry_test.go @@ -8,7 +8,9 @@ import ( func TestRegistryIsComposableAndIdempotent(t *testing.T) { registry := NewRegistry() - registry.RegisterAll([]Method{{Name: "orders.sync", Run: func(context.Context, json.RawMessage) error { return nil }}, {Name: "system.cleanup", Run: func(context.Context, json.RawMessage) error { return nil }}}) + for _, method := range []Method{{Name: "orders.sync", Run: func(context.Context, json.RawMessage) error { return nil }}, {Name: "system.cleanup", Run: func(context.Context, json.RawMessage) error { return nil }}} { + registry.Register(method) + } registry.Register(Method{Name: "orders.sync", Description: "updated", Run: func(context.Context, json.RawMessage) error { return nil }}) methods := registry.List() if len(methods) != 2 || methods[0].Name != "orders.sync" || methods[0].Description != "updated" {