优化结构

This commit is contained in:
Yvan 2026-08-28 10:21:59 +08:00
parent a16d362bb5
commit fec3820464
19 changed files with 138 additions and 136 deletions

View File

@ -11,12 +11,12 @@
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| W-1 | **payment 空注册表导致应用必然启动失败(高危,阻塞)**wire 装配链创建空注册表(`NewPaymentOrderSourceRegistry()`/`NewPaymentFulfillmentRegistry()` 返回空 map后仅流向 `NewConfiguredPaymentUsecase` 的 fail-fast 检查;`RegisterBusinessModule` 全库零调用、`PaymentBusinessModule` 无任何生产实现 → sources/fulfillments 恒为空 → `NewConfiguredPaymentUsecase` 必报"支付业务订单来源未注册"→ wireApp 失败应用无法启动。biz.go 无条件包含 payment.ProviderSet无跳过路径。task 域有 `taskRegistry()` 自定义 provider 做装配前注册payment 域缺等价机制 | cmd/wire_gen.go:114-120internal/biz/payment/payment.go:390-398,400-412biz/payment/payment_order.go:144-152接口无实现 | R-9 的 fail-fast 机制本身正确落地,但与"仓库内零业务模块"叠加产生阻塞。修法:装配前注册机制(仿 taskRegistry或默认业务模块或允许显式禁用 |
| W-2 | **CompleteUpload 吞掉 ListChunks 底层错误并误置会话失败**`err != nil || len(chunks) != ChunkTotal` 合并判定DB 瞬断被误报为"分片不全",且 fail() 将会话置 failed → 用户必须重建会话全量重传。直接违背 D-16 修复意图(仓储层已正确透传,仅此处用例层吞掉) | internal/biz/system/media_upload.go:208-211 | 修法:拆开 err 与数量不足两个分支分别返回 |
| W-3 | MergeRuntimeConfig 嵌套子节合并缺口:仅当 `next.Data == nil` 才补 current.Datanext.Data 非 nil 但子节Database 等)为 nil 时静默丢失watchLoop 无 reloadConfig 那样的后置校验Admin 子节同理 | internal/config/clone.go:29-40config/runtime.go:271 | 建议补"next.Data 非 nil 但子节 nil"回归测试并做嵌套合并 |
| W-4 | 退款状态词表两处语义边角:① `NormalizeStatus` 将裸词 "REFUND" 归 failed——微信 v2 已退款订单在 vendor 查询回退路径会被归为失败,与 `PaymentStatusRefunded` 语义冲突;② `NormalizeRefundStatus` 成功词表缺 "TRADE_SUCCESS"(支付宝风格),会触发保守失败关闭 | internal/paymentkit/status.go:12,16,25 | 低危但需知晓/修正词表 |
| W-5 | 公告/参数/版本列表无 ORDER BY统一接入 listRows 后这三个列表无排序LIMIT/OFFSET 翻页顺序不稳定MySQL/PG 均不保证) | data/system/announcement.go:74、parameter.go:73、version.go:67 | 收敛时遗留;补 `id desc``created_at desc` |
| W-6 | 外部退款分支存在不可达死分支(复制未裁剪):`accepted := providerErr == nil` 后 `else if !accepted` 永不触发 | internal/biz/payment/payment.go:923-928 | 删除死分支 |
| ~~W-1~~ | ~~payment 空注册表导致应用启动失败。~~ **已修复:模板允许无业务模块启动,调用未配置能力时返回明确错误。** | ~~原位置~~ | ~~已完成~~ |
| ~~W-2~~ | ~~CompleteUpload 吞掉 ListChunks 底层错误。~~ **已修复:底层错误与分片数量不足已拆分处理。** | ~~internal/biz/system/media_upload.go~~ | ~~已完成~~ |
| ~~W-3~~ | ~~MergeRuntimeConfig 嵌套子节合并缺口。~~ **已修复并增加回归测试。** | ~~internal/config/clone.goruntime_test.go~~ | ~~已完成~~ |
| ~~W-4~~ | ~~退款状态词表语义边角。~~ **已修复并补充测试。** | ~~internal/paymentkit/status.go~~ | ~~已完成~~ |
| ~~W-5~~ | ~~公告/参数/版本列表无 ORDER BY。~~ **已修复:统一按 `id desc` 排序。** | ~~data/system/*.go~~ | ~~已完成~~ |
| ~~W-6~~ | ~~外部退款分支存在不可达死分支。~~ **已修复。** | ~~internal/biz/payment/payment.go~~ | ~~已完成~~ |
## 二、死代码与零消费者机制
@ -24,10 +24,10 @@
| 层 | 死代码 | 位置 |
|----|--------|------|
| biz 接口+data 实现 | `RecordDataAccess` 整链(接口+实现,查询侧 ListDataAccess/DeleteDataAccess 是活的,仅写入侧死) | biz/system/audit.go:103data/system/data_access_log.go:24 |
| service 包装 | security_session.go 14 个方法中 8 个死ActiveToken/LoginLocked/IncrementLoginFailure/LockLogin/ClearLoginState/IncrementLoginIP/UseMultipoint/RotateActiveTokenbiz 内部直调 usecase这些包装无人调用存活 6 个被 public/rate_limit 消费) | service/system/security_session.go:17-67 |
| ~~biz 接口+data 实现~~ | ~~`RecordDataAccess` 写入链已删除;查询侧仍保留。~~ | ~~相关文件~~ |
| ~~service 包装~~ | ~~security_session.go 中 8 个无消费者透传方法已删除。~~ | ~~相关文件~~ |
| biz 注入面 | `RegisterBusinessModule`W-1 的成因之一,两阶段注册+回滚补偿零调用);`PaymentBusinessModule`/`PayInternal`/`RefundInternal`/`AuthorizeRefund` 接口面仍无生产实现biz 调用链真实存在,仅实现者缺——与 W-1 一并处理) | biz/payment/payment.go:400-412payment_order.go:139-152 |
| dto 死字段 | GetAuthorityButtonsRequest.Selected输入被丢弃MenuResponse.Authorities 恒 nullDynamicMenuResponse.MenuButtons/Authorities 恒 nilSysBaseMenuID 输入侧两处被丢弃version 导出结构体零值噪声字段群ID:0/CreatedAt 零时间/authoritys:null | dto/permission.go:6、menu.go:19,24,109,126,128service/system/version.go:22-96 |
| ~~dto 死字段(已确认项)~~ | ~~GetAuthorityButtonsRequest.Selected 已删除;其余字段因仍参与响应或兼容契约暂保留。~~ | ~~dto/permission.go~~ |
| 死分支 | export_excel.go 的 `case []byte` 在 data 层按列类型转换R-2 修复)后成为死分支 | service/system/export_excel.go:85-86 |
### 2.2 mq / websocket 零消费者基础设施【既定排除范围,历轮明确不处理,现状保持】
@ -44,7 +44,7 @@
| `paymentkit.XMLValues`/`XMLEncode` | internal/paymentkit/xml.go:16,41 | 仅测试调用,生产 XML 走 gopay 库 |
| `paymentkit.NestedString` | internal/paymentkit/json.go:18 | 生产+测试均零调用(旧 shim 删除后的孤儿) |
| `logging.NewZapLogger` | internal/logging/zap.go:542 | 仅测试调用,生产用 NewReloadableZapLogger |
| `data/payment.contains` | data/payment/payment.go:520-527 | 与 paymentkit.ContainsFold 功能重复 |
| ~~`data/payment.contains`~~ | ~~已改用 `paymentkit.ContainsFold`,本地实现已删除。~~ | ~~data/payment/payment.go~~ |
| `paymentkit status.go 的 ConfiguredInt64/ConfiguredValues/Text/FirstText/FirstString` 定位漂移 | internal/paymentkit/status.go:36-90 | 属"供应商配置解析"超出 README 声称范围(文档漂移,非死代码) |
## 三、重复实现 / 双份维护
@ -53,7 +53,7 @@
| # | 问题 | 位置 | 轮次 |
|---|------|------|------|
| D-2 | payment 渠道适配器剩余重复(第一非空字符串/JSON 编码/双状态归一化已收敛):下单方式归一化骨架仍 9 份Replacer 归一化行逐字出现 9 次);退款身份校验 4 份同构;状态归一化 SDK 专属词表 7 个 normalize*State 与 paymentkit 通用归一化双轨维护(同一状态词需两族词表同步) | integration/payment/alipay.go:434-456、douyin.go:108-126、qq.go:124-144 等gopay_helpers.go:145-225 | 二轮(四轮部分收敛) |
| ~~D-2部分完成~~ | ~~支付方式归一化骨架已统一到 `paymentkit.NormalizePaymentMethod`;各渠道状态词表与退款身份校验因语义不同保留。~~ | ~~integration/paymentinternal/paymentkit~~ | ~~部分完成~~ |
| D-4 | handler 四段式样板约 70 处ShouldBindJSON→Fail→service→Write | server/handler/* | 二轮 |
| D-6 | mq/websocket 两包各写一套 map 解码 helper 且逐字符相同TestConfig 探测骨架三处同构【属 2.2 排除范围交叉项】 | emqx.go:226-260 vs websocket/server.go:189-240 | 二轮 |
@ -69,11 +69,11 @@
| # | 问题 | 位置 | 轮次 |
|---|------|------|------|
| D-11 | authority 树构建算法两份【既定不采用,保留】 | biz authority.go:48-78 vs menu.go:80-99 | 一轮 |
| D-15 | defaults 合并逻辑三层三份 | service/integration / biz/integration / data/integration/migrations.go:24-29 | 二轮 |
| D-17 剩余 | CallbackFields 已复用 paymentkit见文末剩余 values() 与 testRow() 近重复 | data/payment/payment.go:31-50,273-292 | 二轮(四轮部分收敛) |
| D-19 | payment 金额守恒校验四处重复biz+vendor+wechat_v2+douyin | biz/payment/payment.go:1198-1211 等 | 二轮 |
| D-21 | service/payment 同文件两份 30 字段映射Order 方法内联映射与 paymentOrderResponse 重复同一张字段表 | service/payment/payment.go:14-34,65-82 | 四轮 |
| D-22 | data/payment 本地 contains 与 paymentkit.ContainsFold 重复(同 2.3 | data/payment/payment.go:520-527 | 四轮 |
| ~~D-15~~ | ~~defaults 合并逻辑三层三份。~~ **已修复:统一使用 `integrationbiz.MergeIntegrationDefaults`。** | ~~相关文件~~ | ~~已完成~~ |
| ~~D-17 剩余~~ | ~~`values()` 与 `testRow()` 已收敛为共享读取逻辑,并保留启用状态差异。~~ | ~~data/payment/payment.go~~ | ~~已完成~~ |
| ~~D-19评估后保留~~ | ~~payment 金额守恒校验四处重复。~~ **经复核保留四处输入字段与供应商容错语义不同强行合并会破坏分层biz 最终守恒校验作为跨边界不变式。** | ~~biz/payment 与各供应商适配器~~ | ~~不改动~~ |
| ~~D-21~~ | ~~支付订单响应映射已统一复用 `paymentOrderResponse`。~~ | ~~service/payment/payment.go~~ | ~~已完成~~ |
| ~~D-22~~ | ~~同 2.3,已改用 `paymentkit.ContainsFold`。~~ | ~~data/payment/payment.go~~ | ~~已完成~~ |
## 四、过度分层:转发门面 / 透传壳 / 回调穿透
@ -95,10 +95,10 @@
| # | 问题 | 位置 | 轮次 |
|---|------|------|------|
| S-1 | biz/system 33 个非测试文件17 个 <60 行微文件群仍在errors 14/cache 15/maintenance 19/actor 19/access_control 20/storage 26/data_scope 27 合计约 575 可归并 8-10 个文件 | biz/system/* | 三轮四轮复核未动 |
| S-2 | SystemConfigService 一型仍四文件system.go(19)+system_config.go(26)+system_init.go(38)+system_info.go(23) | service/system/* | 三轮(四轮复核未动) |
| S-3 | router 22 文件 464 行平均 21 行/文件routes.go 手工 21 连调(与 X-1 一并解决) | server/router/* | 三轮 |
| S-4 | provider.go+providers.go 双小文件模式 ×4 子包8 文件可并 4modules/surface 单函数包data/provider 单接口包data_scope_record.go 单行别名文件 | 各处 | 三轮 |
| ~~S-1第一批~~ | ~~已合并 actor/data-scope 上下文载荷文件;其余微文件仍待按职责归并。~~ | ~~internal/biz/system/context.go~~ | ~~部分完成~~ |
| ~~S-2~~ | ~~SystemConfigService 已合并为单文件。~~ | ~~internal/service/system/system.go~~ | ~~已完成~~ |
| ~~S-3第一批~~ | ~~routes.go 已改为有序注册表;各领域路由文件仍保留。~~ | ~~internal/server/router/routes.go~~ | ~~部分完成~~ |
| ~~S-4~~ | ~~四个 data 子包的 provider 文件已合并。~~ | ~~internal/data/*/provider.go~~ | ~~已完成~~ |
| S-5 | 巨微两极biz/payment/payment.go 1150 行 vs 同域微文件dto 超小文件 vs settings.go 170 行跨四域 | biz/payment、service/dto | 三轮 |
| S-6 | dto 包组织混乱system.go 混装三域settings.go 横跨四域 | service/dto/system.go、settings.go | 一轮 |
| S-7 | data 层组织纪律转换函数命名四种风格PO 分布无规则audit.go 名不副实runtime.go 拼盘 | data/system/* | 二轮 |
@ -119,15 +119,15 @@
| P-11 | pkg/mq 去项目化kra- 前缀)【属 2.2 排除范围交叉项】 | pkg/mq | 一轮 |
| P-13 | pkg/module、pkg/task、pkg/database/migration 同层契约组维持现状 | pkg/* | 一轮 |
| P-14 | httpx 移动后业务语义未剥离CodePasswordChangeRequired=10001 与 x-token cookie 仍留在 internal/server/httpxSetTokenCookie 注释自称 "no KRA business dependency" 与语义不符 | internal/server/httpx/response.go:17,58-64 | 四轮P-1 移动残留) |
| P-15 | 包移动注释漂移http.go:2、response.go:59 仍写 "pkg/httpx"zap.go:29 写 "pkg/logging"source.go:73 skipStackFile 仍是 "/pkg/logging/" 且缺 "/internal/logging/"logging 自身栈帧跳过标记失效——功能性缺口);:74-81 残留 4 个永不匹配的死标记transport 旧路径等) | server/handler/http.go:2、server/httpx/response.go:59、internal/logging/zap.go:29、source.go:73-81 | 四轮P-2 移动残留) |
| ~~P-15~~ | ~~包移动注释与 logging 栈跳过标记已修正。~~ | ~~相关文件~~ | ~~已完成~~ |
| P-16 | CLAUDE.md 结构描述整体过时(描述 api/、internal/global/ 等不存在目录),与 AGENTS.md 不同步 | CLAUDE.md:9-17 | 四轮 |
## 七、分层 / 职责违规
| # | 问题 | 位置 | 轮次 |
|---|------|------|------|
| L-3 | biz DO 带 json 标签PaymentResult/PaymentTestResult死标签PaymentRequest指纹编码格式锚死 DObiz/integration Definition 家族充当前端契约 | biz/payment/payment.go:59-204biz/integration | 二轮 |
| L-4 | DO 兼过滤器API.OrderKey/Desc/StrictAll、SystemParameter/ExportTemplate 时间区间混入实体 | biz/system/api.go、parameter.go、export.go | 二轮 |
| ~~L-3部分完成~~ | ~~PaymentResult/PaymentTestResult 的死 JSON 标签已移除PaymentRequest 字段与指纹语义保留Definition 家族因仍被 service 消费暂不迁移。~~ | ~~biz/payment/payment.gobiz/integration~~ | ~~部分完成~~ |
| ~~L-4部分完成~~ | ~~`SystemParameter` 的查询字段与时间区间已拆为 `SystemParameterFilter`API/Export 过滤字段仍待独立迁移。~~ | ~~biz/service/data system parameter~~ | ~~部分完成~~ |
| L-5 | middleware 硬编码业务语义:中文消息黑名单判断审计(改文案即改审计行为);业务路径硬编码;支付回调专用逻辑内嵌通用中间件;限流策略内联 | server/middleware/* | 二轮 |
| L-6 | biz 契约泄漏存储/表现原语QueryExport 返回 []map[string]anyexport DO 携带 SQL 片段UserOptions UI 形状AuthenticationResult 携带密码哈希 | biz/system/* | 二轮 |
| L-7 | data 层纪律Table("字符串") 绕过 POsaveRelations 回写入参 DOOriginSetting 裸转换 | data/system/* | 二轮 |
@ -181,7 +181,6 @@
1. **先修 W-1 payment 启动阻塞**(应用当前无法启动,最高优先级)+ W-2/W-5用户可感知的正确性缺陷
2. **清 2.1 死代码残留 + 2.3 新死代码**RecordDataAccess 链、security_session 8 方法、dto 死字段群、RegisterBusinessModule随 W-1 一并决策、CloneData/XML 系列等)
3. **补 W-3 嵌套合并回归 + W-4 词表修正 + P-15 移动残留清理**(小而具体)
4. **继续 S 系列文件级减法**S-1/S-2/S-4 未动,零行为变更
4. **继续 S 系列文件级减法**S-1/S-3 仍有剩余微文件与路由文件可按职责整理
5. **D-2/D-19 payment 剩余重复 + L 系列归位**(独立批次)
6. **X-1 路由单源化等大动作**(最后)

View File

@ -213,6 +213,11 @@ func DefaultIntegrationConfig(kind, provider string) map[string]any {
}
func mergeIntegrationDefaults(defaults, values map[string]any) map[string]any {
return MergeIntegrationDefaults(defaults, values)
}
// MergeIntegrationDefaults returns a new map with stored values overriding defaults.
func MergeIntegrationDefaults(defaults, values map[string]any) map[string]any {
out := make(map[string]any, len(defaults)+len(values))
for key, value := range defaults {
out[key] = value

View File

@ -158,51 +158,51 @@ type PaymentConfirmation struct {
}
type PaymentResult struct {
Provider string `json:"provider"`
Status string `json:"status"`
TradeNo string `json:"tradeNo"`
ProviderTradeNo string `json:"providerTradeNo"`
Amount int64 `json:"amount"`
PayerPaidAmount int64 `json:"payerPaidAmount,omitempty"`
CashPaidAmount int64 `json:"cashPaidAmount,omitempty"`
PointPaidAmount int64 `json:"pointPaidAmount,omitempty"`
DiscountAmount int64 `json:"discountAmount,omitempty"`
ProviderDiscountAmount int64 `json:"providerDiscountAmount,omitempty"`
MerchantDiscountAmount int64 `json:"merchantDiscountAmount,omitempty"`
SettlementAmount int64 `json:"settlementAmount,omitempty"`
Currency string `json:"currency"`
PayerCurrency string `json:"payerCurrency,omitempty"`
AmountBreakdownKnown bool `json:"amountBreakdownKnown"`
Duplicate bool `json:"duplicate"`
Payload json.RawMessage `json:"payload"`
OrderStatus string `json:"orderStatus,omitempty"`
FulfillmentStatus string `json:"fulfillmentStatus,omitempty"`
RefundStatus string `json:"refundStatus,omitempty"`
EventID string `json:"-"`
QueryID string `json:"-"`
SuccessAck PaymentCallbackAck `json:"-"`
FailureAck PaymentCallbackAck `json:"-"`
Provider string
Status string
TradeNo string
ProviderTradeNo string
Amount int64
PayerPaidAmount int64
CashPaidAmount int64
PointPaidAmount int64
DiscountAmount int64
ProviderDiscountAmount int64
MerchantDiscountAmount int64
SettlementAmount int64
Currency string
PayerCurrency string
AmountBreakdownKnown bool
Duplicate bool
Payload json.RawMessage
OrderStatus string
FulfillmentStatus string
RefundStatus string
EventID string
QueryID string
SuccessAck PaymentCallbackAck
FailureAck PaymentCallbackAck
}
// PaymentTestStage is one step of a provider connectivity test. A skipped
// query/refund is reported explicitly because a prepay flow cannot be marked
// paid without a sandbox payer interaction.
type PaymentTestStage struct {
Name string `json:"name"`
Status string `json:"status"`
Message string `json:"message,omitempty"`
TradeNo string `json:"tradeNo,omitempty"`
Duration int64 `json:"durationMs,omitempty"`
Name string
Status string
Message string
TradeNo string
Duration int64
}
type PaymentTestResult struct {
Provider string `json:"provider"`
TradeNo string `json:"tradeNo"`
Passed bool `json:"passed"`
FullFlow bool `json:"fullFlow"`
Mode string `json:"mode,omitempty"`
Stages []PaymentTestStage `json:"stages"`
Result *PaymentResult `json:"result,omitempty"`
Provider string
TradeNo string
Passed bool
FullFlow bool
Mode string
Stages []PaymentTestStage
Result *PaymentResult
}
type PaymentCallbackAck struct {

View File

@ -1,20 +0,0 @@
package system
import "context"
type AccessControlUsecase struct {
authorities AuthorityAccessRepo
apis APIRepo
}
func NewAccessControlUsecase(authorities AuthorityAccessRepo, apis APIRepo) *AccessControlUsecase {
return &AccessControlUsecase{authorities: authorities, apis: apis}
}
func (uc *AccessControlUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) {
return uc.apis.Authorize(ctx, authorityID, path, method)
}
func (uc *AccessControlUsecase) ResolveDataScope(ctx context.Context, authorityID, userID uint) (DataScope, error) {
return uc.authorities.ResolveDataScope(ctx, authorityID, userID)
}

View File

@ -45,6 +45,23 @@ func NewAuthorityUsecase(repo AuthorityAccessRepo) *AuthorityUsecase {
return &AuthorityUsecase{AuthorityAccessRepo: repo}
}
type AccessControlUsecase struct {
authorities AuthorityAccessRepo
apis APIRepo
}
func NewAccessControlUsecase(authorities AuthorityAccessRepo, apis APIRepo) *AccessControlUsecase {
return &AccessControlUsecase{authorities: authorities, apis: apis}
}
func (uc *AccessControlUsecase) Authorize(ctx context.Context, authorityID uint, path, method string) (bool, error) {
return uc.apis.Authorize(ctx, authorityID, path, method)
}
func (uc *AccessControlUsecase) ResolveDataScope(ctx context.Context, authorityID, userID uint) (DataScope, error) {
return uc.authorities.ResolveDataScope(ctx, authorityID, userID)
}
func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, error) {
items, err := uc.ListAuthorities(ctx)
if err != nil {

View File

@ -21,6 +21,25 @@ type MediaRepo interface {
UploadRepo
}
type StoredFile struct {
Name string
Path string
URL string
Size int64
LastModified time.Time
ContentType string
}
// FileStorage owns the persistence boundary for uploaded files.
type FileStorage interface {
Put(context.Context, string, io.Reader) (*StoredFile, error)
Open(context.Context, string) (io.ReadCloser, error)
Delete(context.Context, string) error
Compose(context.Context, []string, string) (*StoredFile, string, error)
DeletePrefix(context.Context, string) error
List(context.Context, string, string, int) ([]*StoredFile, string, bool, error)
}
type MediaUsecase struct {
MediaRepo
files FileStorage

View File

@ -6,13 +6,18 @@ import (
)
type SystemParameter struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
Key string
Value string
Desc string
}
type SystemParameterFilter struct {
Name string
Key string
Value string
Desc string
StartCreatedAt *time.Time
EndCreatedAt *time.Time
}
@ -23,7 +28,7 @@ type ParameterRepo interface {
DeleteParameters(context.Context, []string) error
FindParameterByID(context.Context, string) (*SystemParameter, error)
FindParameterByKey(context.Context, string) (*SystemParameter, error)
ListParameters(context.Context, int, int, *SystemParameter) ([]*SystemParameter, int64, error)
ListParameters(context.Context, int, int, *SystemParameterFilter) ([]*SystemParameter, int64, error)
}
type ParameterUsecase struct{ ParameterRepo }

View File

@ -1,26 +0,0 @@
package system
import (
"context"
"io"
"time"
)
type StoredFile struct {
Name string
Path string
URL string
Size int64
LastModified time.Time
ContentType string
}
// FileStorage owns the persistence boundary for uploaded files.
type FileStorage interface {
Put(context.Context, string, io.Reader) (*StoredFile, error)
Open(context.Context, string) (io.ReadCloser, error)
Delete(context.Context, string) error
Compose(context.Context, []string, string) (*StoredFile, string, error)
DeletePrefix(context.Context, string) error
List(context.Context, string, string, int) ([]*StoredFile, string, bool, error)
}

View File

@ -3,6 +3,7 @@ package integration
import (
"encoding/json"
"errors"
"reflect"
integrationbiz "kra/internal/biz/integration"
"kra/pkg/database/migration"
@ -66,13 +67,9 @@ func ensurePaymentIntegrationConfigs(db *gorm.DB) error {
return err
}
values := integrationObject(json.RawMessage(row.Config))
changed := false
for key, value := range defaults {
if _, exists := values[key]; !exists {
values[key] = value
changed = true
}
}
merged := integrationbiz.MergeIntegrationDefaults(defaults, values)
changed := !reflect.DeepEqual(values, merged)
values = merged
if changed {
encoded, marshalErr := json.Marshal(values)
if marshalErr != nil {

View File

@ -57,7 +57,7 @@ func (r *parameterRepo) FindParameterByKey(ctx context.Context, key string) (*sy
}
return parameterFromPO(po), nil
}
func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *system.SystemParameter) ([]*system.SystemParameter, int64, error) {
func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *system.SystemParameterFilter) ([]*system.SystemParameter, int64, error) {
db := r.data.DB().WithContext(ctx).Model(&parameterPO{})
if q != nil {
if q.StartCreatedAt != nil && q.EndCreatedAt != nil {

View File

@ -8,6 +8,7 @@ import (
"fmt"
"io"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"net/http"
"net/url"
"strings"
@ -435,7 +436,7 @@ func alipayCreateMethod(extra, config map[string]any) (string, error) {
if value == "" {
value = firstAny(config, "method", "pay_method", "trade_type", "channel")
}
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
normalized := paymentkit.NormalizePaymentMethod(value)
switch normalized {
case "", "create", "trade_create", "alipay_trade_create", "jsapi", "miniapp", "mini_program":
return "alipay.trade.create", nil

View File

@ -12,6 +12,7 @@ import (
"fmt"
"io"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"net/http"
"strings"
"sync"
@ -207,7 +208,7 @@ func allinpayCreateMethod(extra, config map[string]any) (string, error) {
if value == "" {
value = firstAny(config, "method", "pay_method", "trade_type")
}
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
normalized := paymentkit.NormalizePaymentMethod(value)
switch normalized {
case "", "pay", "unified", "unified_pay":
return "pay", nil

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"strings"
"github.com/go-pay/gopay"
@ -110,7 +111,7 @@ func douyinCreateMethod(extra, config map[string]any) (string, error) {
if value == "" {
value = firstAny(config, "method", "pay_method", "trade_type", "pay_type", "channel")
}
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
normalized := paymentkit.NormalizePaymentMethod(value)
switch normalized {
case "", "jsapi", "js_api", "mini", "mini_program", "miniprogram", "applet":
return "jsapi", nil

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"strings"
"github.com/go-pay/gopay"
@ -71,7 +72,7 @@ func lakalaCreateMethod(extra, config map[string]any) string {
if method == "" {
method = firstAny(config, "method", "pay_method", "trade_type")
}
return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(method)))
return paymentkit.NormalizePaymentMethod(method)
}
func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*bizpayment.PaymentResult, error) {

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"strings"
"time"
@ -89,7 +90,7 @@ func saobeiCreateMethod(extra, config map[string]any) (string, error) {
if value == "" {
value = firstAny(config, "method", "pay_method", "trade_type")
}
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
normalized := paymentkit.NormalizePaymentMethod(value)
switch normalized {
case "", "mini", "mini_pay", "miniapp", "mini_app", "mini_program", "miniprogram", "jsapi", "js_api":
return "mini", nil

View File

@ -11,6 +11,7 @@ import (
"errors"
"fmt"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"net/http"
"net/url"
"strings"
@ -180,7 +181,7 @@ func wechatV2CreateMethod(extra, config map[string]any) (string, error) {
if value == "" {
value = firstAny(config, "trade_type", "pay_type", "method", "pay_method", "channel")
}
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
normalized := paymentkit.NormalizePaymentMethod(value)
switch normalized {
case "", "jsapi", "js_api", "mini", "miniapp", "mini_program", "miniprogram", "applet":
return gopayWechat.TradeType_JsApi, nil

View File

@ -33,6 +33,12 @@ func NormalizeRefundStatus(value, fallback string) string {
}
}
// NormalizePaymentMethod canonicalizes provider method identifiers while
// leaving provider-specific classification to the integration adapter.
func NormalizePaymentMethod(value string) string {
return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
}
func ConfiguredInt64(values map[string]any, key string, fallback int64) int64 {
value, ok := values[key]
if !ok {

View File

@ -84,15 +84,9 @@ func (s *IntegrationConfigService) Delete(ctx context.Context, kind, provider st
}
func mergeConfigJSON(defaults map[string]any, raw json.RawMessage) json.RawMessage {
values := make(map[string]any, len(defaults))
for key, value := range defaults {
values[key] = value
}
stored := map[string]any{}
_ = json.Unmarshal(raw, &stored)
for key, value := range stored {
values[key] = value
}
values := integrationbiz.MergeIntegrationDefaults(defaults, stored)
encoded, _ := json.Marshal(values)
return encoded
}

View File

@ -24,12 +24,12 @@ func (s *ParameterService) UpdateParameterRequest(ctx context.Context, req *dto.
return s.UpdateParameter(ctx, parameterDomain(req))
}
func (s *ParameterService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]*dto.SystemParameterResponse, int64, error) {
return s.Parameters(ctx, page, size, &system.SystemParameter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end})
return s.Parameters(ctx, page, size, &system.SystemParameterFilter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end})
}
func parameterDTO(v *system.SystemParameter) *dto.SystemParameterResponse {
return &dto.SystemParameterResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}
}
func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *system.SystemParameter) ([]*dto.SystemParameterResponse, int64, error) {
func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *system.SystemParameterFilter) ([]*dto.SystemParameterResponse, int64, error) {
items, total, err := s.uc.ListParameters(ctx, page, size, q)
if err != nil {
return nil, 0, err