From 9f614e67b649d8411c8b0dbb4ab583ae968db8fd Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 15 Jul 2026 15:23:02 +0800 Subject: [PATCH] refactor: add maps utility for json value replacements --- .../deployer/providers/webhook/webhook.go | 44 +++------ .../providers/dingtalkbot/dingtalkbot.go | 34 ++----- .../notifier/providers/larkbot/larkbot.go | 34 ++----- .../notifier/providers/webhook/webhook.go | 34 ++----- .../notifier/providers/wecombot/wecombot.go | 34 ++----- pkg/utils/loop/for.go | 92 +++++++++++++++++++ pkg/utils/maps/replace.go | 44 +++++++++ 7 files changed, 179 insertions(+), 137 deletions(-) create mode 100644 pkg/utils/loop/for.go create mode 100644 pkg/utils/maps/replace.go diff --git a/pkg/core/deployer/providers/webhook/webhook.go b/pkg/core/deployer/providers/webhook/webhook.go index 527bccc8..638e39b0 100644 --- a/pkg/core/deployer/providers/webhook/webhook.go +++ b/pkg/core/deployer/providers/webhook/webhook.go @@ -17,6 +17,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" xcert "github.com/certimate-go/certimate/pkg/utils/cert" xcertx509 "github.com/certimate-go/certimate/pkg/utils/cert/x509" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) type ( @@ -148,7 +149,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep } // 处理 Webhook 请求数据 - var webhookData interface{} + var webhookData any if d.config.WebhookData == "" { webhookData = map[string]string{ "name": strings.Join(xcertx509.GetSubjectAltNames(certX509), ";"), @@ -176,21 +177,22 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep // 替换变量值 webhookUrl.Path = strings.ReplaceAll(webhookUrl.Path, "${CERTIMATE_DEPLOYER_COMMONNAME}", url.PathEscape(xcertx509.GetSubjectCommonName(certX509))) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_DEPLOYER_COMMONNAME}", xcertx509.GetSubjectCommonName(certX509)) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_DEPLOYER_SUBJECTALTNAMES}", strings.Join(xcertx509.GetSubjectAltNames(certX509), ";")) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE}", certPEM) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE_SERVER}", serverCertPEM) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE_INTERMEDIA}", issuerCertPEM) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_DEPLOYER_PRIVATEKEY}", privkeyPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_COMMONNAME}", xcertx509.GetSubjectCommonName(certX509)) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_SUBJECTALTNAMES}", strings.Join(xcertx509.GetSubjectAltNames(certX509), ";")) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE}", certPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE_SERVER}", serverCertPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_CERTIFICATE_INTERMEDIA}", issuerCertPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_DEPLOYER_PRIVATEKEY}", privkeyPEM) // 兼容旧版变量 + // TODO: remove in future version webhookUrl.Path = strings.ReplaceAll(webhookUrl.Path, "${DOMAIN}", url.PathEscape(certX509.Subject.CommonName)) - replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) - replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) - replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM) - replaceJsonValueRecursively(webhookData, "${SERVER_CERTIFICATE}", serverCertPEM) - replaceJsonValueRecursively(webhookData, "${INTERMEDIA_CERTIFICATE}", issuerCertPEM) - replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${DOMAIN}", certX509.Subject.CommonName) + xmaps.DeepReplaceValueUnsafe(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIFICATE}", certPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${SERVER_CERTIFICATE}", serverCertPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${INTERMEDIA_CERTIFICATE}", issuerCertPEM) + xmaps.DeepReplaceValueUnsafe(webhookData, "${PRIVATE_KEY}", privkeyPEM) // 生成请求 // 其中 GET 请求需转换为查询参数 @@ -222,19 +224,3 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep return &DeployResult{}, nil } - -func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { - switch v := data.(type) { - case map[string]any: - for k, val := range v { - v[k] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []any: - for i, val := range v { - v[i] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case string: - return strings.ReplaceAll(v, oldStr, newStr) - } - return data -} diff --git a/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go b/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go index 0af574da..6d4c9124 100644 --- a/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go +++ b/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go @@ -10,13 +10,13 @@ import ( "log/slog" "net/http" "net/url" - "strings" "time" "github.com/go-resty/resty/v2" "github.com/certimate-go/certimate/internal/app" "github.com/certimate-go/certimate/pkg/core" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) type ( @@ -107,11 +107,13 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return nil, fmt.Errorf("failed to unmarshal custom payload: %w", err) } - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) + xmaps.DeepReplaceValue(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) + xmaps.DeepReplaceValue(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) - replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) + // 兼容旧版变量 + // TODO: remove in future version + xmaps.DeepReplaceValue(webhookData, "${SUBJECT}", subject) + xmaps.DeepReplaceValue(webhookData, "${MESSAGE}", message) } // REF: https://open.dingtalk.com/document/development/custom-robots-send-group-messages @@ -135,25 +137,3 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return &NotifyResult{}, nil } - -func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { - switch v := data.(type) { - case map[string]any: - for k, val := range v { - v[k] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []any: - for i, val := range v { - v[i] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []string: - for i, s := range v { - var val interface{} = s - var newVal interface{} = replaceJsonValueRecursively(val, oldStr, newStr) - v[i] = newVal.(string) - } - case string: - return strings.ReplaceAll(v, oldStr, newStr) - } - return data -} diff --git a/pkg/core/notifier/providers/larkbot/larkbot.go b/pkg/core/notifier/providers/larkbot/larkbot.go index 2b530365..b28b6b3a 100644 --- a/pkg/core/notifier/providers/larkbot/larkbot.go +++ b/pkg/core/notifier/providers/larkbot/larkbot.go @@ -9,13 +9,13 @@ import ( "fmt" "log/slog" "net/url" - "strings" "time" "github.com/go-resty/resty/v2" "github.com/certimate-go/certimate/internal/app" "github.com/certimate-go/certimate/pkg/core" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) type ( @@ -91,11 +91,13 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return nil, fmt.Errorf("failed to unmarshal custom payload: %w", err) } - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) + xmaps.DeepReplaceValue(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) + xmaps.DeepReplaceValue(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) - replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) + // 兼容旧版变量 + // TODO: remove in future version + xmaps.DeepReplaceValue(webhookData, "${SUBJECT}", subject) + xmaps.DeepReplaceValue(webhookData, "${MESSAGE}", message) } if n.config.Secret != "" { @@ -137,25 +139,3 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return &NotifyResult{}, nil } - -func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { - switch v := data.(type) { - case map[string]any: - for k, val := range v { - v[k] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []any: - for i, val := range v { - v[i] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []string: - for i, s := range v { - var val interface{} = s - var newVal interface{} = replaceJsonValueRecursively(val, oldStr, newStr) - v[i] = newVal.(string) - } - case string: - return strings.ReplaceAll(v, oldStr, newStr) - } - return data -} diff --git a/pkg/core/notifier/providers/webhook/webhook.go b/pkg/core/notifier/providers/webhook/webhook.go index e3c9f8f0..a544be3d 100644 --- a/pkg/core/notifier/providers/webhook/webhook.go +++ b/pkg/core/notifier/providers/webhook/webhook.go @@ -15,6 +15,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/certimate-go/certimate/pkg/core" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) type ( @@ -134,7 +135,7 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( } // 处理 Webhook 请求数据 - var webhookData interface{} + var webhookData any if n.config.WebhookData == "" { webhookData = map[string]string{ "subject": subject, @@ -160,12 +161,13 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( } // 替换变量值 - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) + xmaps.DeepReplaceValueUnsafe(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) // 兼容旧版变量 - replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) + // TODO: remove in future version + xmaps.DeepReplaceValueUnsafe(webhookData, "${SUBJECT}", subject) + xmaps.DeepReplaceValueUnsafe(webhookData, "${MESSAGE}", message) // 生成请求 // 其中 GET 请求需转换为查询参数 @@ -197,25 +199,3 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return &NotifyResult{}, nil } - -func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { - switch v := data.(type) { - case map[string]any: - for k, val := range v { - v[k] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []any: - for i, val := range v { - v[i] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []string: - for i, s := range v { - var val interface{} = s - var newVal interface{} = replaceJsonValueRecursively(val, oldStr, newStr) - v[i] = newVal.(string) - } - case string: - return strings.ReplaceAll(v, oldStr, newStr) - } - return data -} diff --git a/pkg/core/notifier/providers/wecombot/wecombot.go b/pkg/core/notifier/providers/wecombot/wecombot.go index 4469262c..ac23f9b1 100644 --- a/pkg/core/notifier/providers/wecombot/wecombot.go +++ b/pkg/core/notifier/providers/wecombot/wecombot.go @@ -6,12 +6,12 @@ import ( "fmt" "log/slog" "net/url" - "strings" "github.com/go-resty/resty/v2" "github.com/certimate-go/certimate/internal/app" "github.com/certimate-go/certimate/pkg/core" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" ) type ( @@ -84,11 +84,13 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return nil, fmt.Errorf("failed to unmarshal custom payload: %w", err) } - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) + xmaps.DeepReplaceValue(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) + xmaps.DeepReplaceValue(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) - replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) - replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) + // 兼容旧版变量 + // TODO: remove in future version + xmaps.DeepReplaceValue(webhookData, "${SUBJECT}", subject) + xmaps.DeepReplaceValue(webhookData, "${MESSAGE}", message) } // REF: https://developer.work.weixin.qq.com/document/path/91770 @@ -112,25 +114,3 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return &NotifyResult{}, nil } - -func replaceJsonValueRecursively(data interface{}, oldStr, newStr string) interface{} { - switch v := data.(type) { - case map[string]any: - for k, val := range v { - v[k] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []any: - for i, val := range v { - v[i] = replaceJsonValueRecursively(val, oldStr, newStr) - } - case []string: - for i, s := range v { - var val interface{} = s - var newVal interface{} = replaceJsonValueRecursively(val, oldStr, newStr) - v[i] = newVal.(string) - } - case string: - return strings.ReplaceAll(v, oldStr, newStr) - } - return data -} diff --git a/pkg/utils/loop/for.go b/pkg/utils/loop/for.go new file mode 100644 index 00000000..b5fc3611 --- /dev/null +++ b/pkg/utils/loop/for.go @@ -0,0 +1,92 @@ +package loop + +import ( + "context" + "errors" +) + +// 遍历集合并执行迭代函数。 +// +// 入参: +// - collection: 集合。 +// - iter: 迭代函数,接收集合中的元素和索引作为参数,返回错误。 +// +// 出参: +// - err: 错误。 +func ForRange[T any](collection []T, iter func(item T, index int) error) error { + iterWithContext := func(_ context.Context, item T, index int) error { + return iter(item, index) + } + return ForRangeWithContext(context.Background(), collection, iterWithContext) +} + +// 遍历集合并执行迭代函数,或上下文被取消。 +// +// 入参: +// - ctx: 上下文。 +// - collection: 集合。 +// - iter: 迭代函数,接收上下文、集合中的元素和索引作为参数,返回错误。 +// +// 出参: +// - err: 错误。 +func ForRangeWithContext[T any](ctx context.Context, collection []T, iter func(ctx context.Context, item T, index int) error) error { + for i, item := range collection { + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := iter(ctx, item, i); err != nil { + return nil + } + } + } + + return nil +} + +// 与 [ForRange] 类似,但在迭代时遇到错误不会中止、并在收集所有错误后返回。 +// +// 入参: +// - collection: 集合。 +// - iter: 迭代函数,接收集合中的元素和索引作为参数,返回错误。 +// +// 出参: +// - err: 错误。 +func ForRangeAll[T any](collection []T, iter func(item T, index int) error) error { + iterWithContext := func(_ context.Context, item T, index int) error { + return iter(item, index) + } + return ForRangeAllWithContext(context.Background(), collection, iterWithContext) +} + +// 与 [ForRangeAllWithContext] 类似,但在迭代时遇到错误不会中止、并在收集所有错误后返回。 +// +// 入参: +// - ctx: 上下文。 +// - collection: 集合。 +// - iter: 迭代函数,接收上下文、集合中的元素和索引作为参数,返回错误。 +// +// 出参: +// - err: 错误。 +func ForRangeAllWithContext[T any](ctx context.Context, collection []T, iter func(ctx context.Context, item T, index int) error) error { + var errs []error + + for i, item := range collection { + select { + case <-ctx.Done(): + return ctx.Err() + + default: + if err := iter(ctx, item, i); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} diff --git a/pkg/utils/maps/replace.go b/pkg/utils/maps/replace.go new file mode 100644 index 00000000..7943bc2d --- /dev/null +++ b/pkg/utils/maps/replace.go @@ -0,0 +1,44 @@ +package maps + +import ( + "strings" +) + +// 在字典中递归地替换指定的字符串的值。 +// 注意该函数会修改原始的字典。 +// +// 入参: +// - dict: 字典。 +// - oldStr:需要替换的字符串。 +// - newStr:替换后的字符串。 +// +// 出参: +// - dict: 替换后的字典。 +func DeepReplaceValue(dict map[string]any, oldStr, newStr string) map[string]any { + return deepReplaceMapValue(dict, oldStr, newStr).(map[string]any) +} + +// 与 [DeepReplaceValue] 类似,但入参类型为 `any`。 +func DeepReplaceValueUnsafe(dict any, oldStr, newStr string) any { + return deepReplaceMapValue(dict, oldStr, newStr) +} + +func deepReplaceMapValue(data any, oldStr, newStr string) any { + switch v := data.(type) { + case map[string]any: + for k, va := range v { + v[k] = deepReplaceMapValue(va, oldStr, newStr) + } + case []any: + for i, va := range v { + v[i] = deepReplaceMapValue(va, oldStr, newStr) + } + case []string: + for i, vs := range v { + v[i] = deepReplaceMapValue(vs, oldStr, newStr).(string) + } + case string: + return strings.ReplaceAll(v, oldStr, newStr) + } + return data +}