refactor: add maps utility for json value replacements

This commit is contained in:
Fu Diwei 2026-07-15 15:23:02 +08:00
parent d1a8e93f43
commit 9f614e67b6
7 changed files with 179 additions and 137 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

92
pkg/utils/loop/for.go Normal file
View File

@ -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
}

44
pkg/utils/maps/replace.go Normal file
View File

@ -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
}