diff --git a/internal/domain/domains.go b/internal/domain/domains.go index 6a228fff..f3d04a53 100644 --- a/internal/domain/domains.go +++ b/internal/domain/domains.go @@ -23,7 +23,7 @@ type DeployConfig struct { Config map[string]any `json:"config"` } -// 以字符串形式获取配置项。 +// Deprecated: 以字符串形式获取配置项。 // // 入参: // - key: 配置项的键。 @@ -34,7 +34,7 @@ func (dc *DeployConfig) GetConfigAsString(key string) string { return maps.GetValueAsString(dc.Config, key) } -// 以字符串形式获取配置项。 +// Deprecated: 以字符串形式获取配置项。 // // 入参: // - key: 配置项的键。 @@ -46,7 +46,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue stri return maps.GetValueOrDefaultAsString(dc.Config, key, defaultValue) } -// 以 32 位整数形式获取配置项。 +// Deprecated: 以 32 位整数形式获取配置项。 // // 入参: // - key: 配置项的键。 @@ -57,7 +57,7 @@ func (dc *DeployConfig) GetConfigAsInt32(key string) int32 { return maps.GetValueAsInt32(dc.Config, key) } -// 以 32 位整数形式获取配置项。 +// Deprecated: 以 32 位整数形式获取配置项。 // // 入参: // - key: 配置项的键。 @@ -69,7 +69,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsInt32(key string, defaultValue int32 return maps.GetValueOrDefaultAsInt32(dc.Config, key, defaultValue) } -// 以布尔形式获取配置项。 +// Deprecated: 以布尔形式获取配置项。 // // 入参: // - key: 配置项的键。 @@ -80,7 +80,7 @@ func (dc *DeployConfig) GetConfigAsBool(key string) bool { return maps.GetValueAsBool(dc.Config, key) } -// 以布尔形式获取配置项。 +// Deprecated: 以布尔形式获取配置项。 // // 入参: // - key: 配置项的键。 @@ -92,7 +92,7 @@ func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) return maps.GetValueOrDefaultAsBool(dc.Config, key, defaultValue) } -// 以变量字典形式获取配置项。 +// Deprecated: 以变量字典形式获取配置项。 // // 出参: // - 变量字典。 @@ -119,7 +119,7 @@ func (dc *DeployConfig) GetConfigAsVariables() map[string]string { return rs } -// GetDomain returns the domain from the deploy config +// Deprecated: GetDomain returns the domain from the deploy config, // if the domain is a wildcard domain, and wildcard is true, return the wildcard domain func (dc *DeployConfig) GetDomain(wildcard ...bool) string { val := dc.GetConfigAsString("domain") diff --git a/internal/domain/notify.go b/internal/domain/notify.go index 6c164f39..e307600c 100644 --- a/internal/domain/notify.go +++ b/internal/domain/notify.go @@ -1,13 +1,20 @@ package domain +/* +消息通知渠道常量值。 + + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. +*/ const ( - NotifyChannelEmail = "email" - NotifyChannelWebhook = "webhook" - NotifyChannelDingtalk = "dingtalk" - NotifyChannelLark = "lark" - NotifyChannelTelegram = "telegram" - NotifyChannelServerChan = "serverchan" NotifyChannelBark = "bark" + NotifyChannelDingtalk = "dingtalk" + NotifyChannelEmail = "email" + NotifyChannelLark = "lark" + NotifyChannelServerChan = "serverchan" + NotifyChannelTelegram = "telegram" + NotifyChannelWebhook = "webhook" + NotifyChannelWeCom = "wecom" ) type NotifyTestPushReq struct { diff --git a/internal/notify/factory.go b/internal/notify/factory.go index 3088f246..b77c7c91 100644 --- a/internal/notify/factory.go +++ b/internal/notify/factory.go @@ -12,6 +12,7 @@ import ( providerServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" providerTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" providerWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook" + providerWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" "github.com/usual2970/certimate/internal/pkg/utils/maps" ) @@ -64,6 +65,11 @@ func createNotifier(channel string, channelConfig map[string]any) (notifier.Noti return providerWebhook.New(&providerWebhook.WebhookNotifierConfig{ Url: maps.GetValueAsString(channelConfig, "url"), }) + + case domain.NotifyChannelWeCom: + return providerWeCom.New(&providerWeCom.WeComNotifierConfig{ + WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"), + }) } return nil, fmt.Errorf("unsupported notifier channel: %s", channelConfig) diff --git a/internal/pkg/core/notifier/providers/lark/lark.go b/internal/pkg/core/notifier/providers/lark/lark.go index 0ab94fbb..4714e280 100644 --- a/internal/pkg/core/notifier/providers/lark/lark.go +++ b/internal/pkg/core/notifier/providers/lark/lark.go @@ -10,7 +10,7 @@ import ( ) type LarkNotifierConfig struct { - // 飞书 Webhook 地址。 + // 飞书机器人 Webhook 地址。 WebhookUrl string `json:"webhookUrl"` } diff --git a/internal/pkg/core/notifier/providers/wecom/wecom.go b/internal/pkg/core/notifier/providers/wecom/wecom.go new file mode 100644 index 00000000..20938009 --- /dev/null +++ b/internal/pkg/core/notifier/providers/wecom/wecom.go @@ -0,0 +1,58 @@ +package serverchan + +import ( + "context" + "errors" + "net/http" + + notifyHttp "github.com/nikoksr/notify/service/http" + + "github.com/usual2970/certimate/internal/pkg/core/notifier" +) + +type WeComNotifierConfig struct { + // 企业微信机器人 Webhook 地址。 + WebhookUrl string `json:"webhookUrl"` +} + +type WeComNotifier struct { + config *WeComNotifierConfig +} + +var _ notifier.Notifier = (*WeComNotifier)(nil) + +func New(config *WeComNotifierConfig) (*WeComNotifier, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + return &WeComNotifier{ + config: config, + }, nil +} + +func (n *WeComNotifier) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { + srv := notifyHttp.New() + + srv.AddReceivers(¬ifyHttp.Webhook{ + URL: n.config.WebhookUrl, + Header: http.Header{}, + ContentType: "application/json", + Method: http.MethodPost, + BuildPayload: func(subject, message string) (payload any) { + return map[string]any{ + "msgtype": "text", + "text": map[string]string{ + "content": subject + "\n\n" + message, + }, + } + }, + }) + + err = srv.Send(ctx, subject, message) + if err != nil { + return nil, err + } + + return ¬ifier.NotifyResult{}, nil +} diff --git a/internal/pkg/core/notifier/providers/wecom/wecom_test.go b/internal/pkg/core/notifier/providers/wecom/wecom_test.go new file mode 100644 index 00000000..a9ac9d16 --- /dev/null +++ b/internal/pkg/core/notifier/providers/wecom/wecom_test.go @@ -0,0 +1,57 @@ +package serverchan_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var fWebhookUrl string + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_WECOM_" + + flag.StringVar(&fWebhookUrl, argsPrefix+"WEBHOOKURL", "", "") +} + +/* +Shell command to run this test: + + go test -v serverchan_test.go -args \ + --CERTIMATE_NOTIFIER_WECOM_WEBHOOKURL="https://example.com/your-webhook-url" \ +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("WEBHOOKURL: %v", fWebhookUrl), + }, "\n")) + + notifier, err := provider.New(&provider.WeComNotifierConfig{ + WebhookUrl: fWebhookUrl, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/ui/src/components/notification/NotifyChannelEditForm.tsx b/ui/src/components/notification/NotifyChannelEditForm.tsx index fc53e413..d93a6732 100644 --- a/ui/src/components/notification/NotifyChannelEditForm.tsx +++ b/ui/src/components/notification/NotifyChannelEditForm.tsx @@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useMemo, useState } from "react"; import { useCreation, useDeepCompareEffect } from "ahooks"; import { Form } from "antd"; -import { type NotifyChannelsSettingsContent } from "@/domain/settings"; +import { NOTIFY_CHANNELS, type NotifyChannelsSettingsContent } from "@/domain/settings"; import NotifyChannelEditFormBarkFields from "./NotifyChannelEditFormBarkFields"; import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalkFields"; import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields"; @@ -10,13 +10,14 @@ import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields"; import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields"; import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields"; import NotifyChannelEditFormWebhookFields from "./NotifyChannelEditFormWebhookFields"; +import NotifyChannelEditFormWeComFields from "./NotifyChannelEditFormWeComFields"; type NotifyChannelEditFormModelType = NotifyChannelsSettingsContent[keyof NotifyChannelsSettingsContent]; export type NotifyChannelEditFormProps = { className?: string; style?: React.CSSProperties; - channel: keyof NotifyChannelsSettingsContent; + channel: string; disabled?: boolean; loading?: boolean; model?: NotifyChannelEditFormModelType; @@ -39,20 +40,22 @@ const NotifyChannelEditForm = forwardRef; - case "dingtalk": + case NOTIFY_CHANNELS.DINGTALK: return ; - case "email": + case NOTIFY_CHANNELS.EMAIL: return ; - case "lark": + case NOTIFY_CHANNELS.LARK: return ; - case "serverchan": + case NOTIFY_CHANNELS.SERVERCHAN: return ; - case "telegram": + case NOTIFY_CHANNELS.TELEGRAM: return ; - case "webhook": + case NOTIFY_CHANNELS.WEBHOOK: return ; + case NOTIFY_CHANNELS.WECOM: + return ; } }, [channel]); diff --git a/ui/src/components/notification/NotifyChannelEditFormWeComFields.tsx b/ui/src/components/notification/NotifyChannelEditFormWeComFields.tsx new file mode 100644 index 00000000..1dddbbc4 --- /dev/null +++ b/ui/src/components/notification/NotifyChannelEditFormWeComFields.tsx @@ -0,0 +1,31 @@ +import { useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +const NotifyChannelEditFormWeComFields = () => { + const { t } = useTranslation(); + + const formSchema = z.object({ + webhookUrl: z + .string({ message: t("settings.notification.channel.form.wecom_webhook_url.placeholder") }) + .min(1, t("settings.notification.channel.form.wecom_webhook_url.placeholder")) + .url({ message: t("common.errmsg.url_invalid") }), + }); + const formRule = createSchemaFieldRule(formSchema); + + return ( + <> + } + > + + + + ); +}; + +export default NotifyChannelEditFormWeComFields; diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index c280189f..b022137a 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -39,19 +39,41 @@ export const defaultNotifyTemplate: NotifyTemplate = { // #endregion // #region Settings: NotifyChannels +export const NOTIFY_CHANNEL_BARK = "bark" as const; +export const NOTIFY_CHANNEL_DINGTALK = "dingtalk" as const; +export const NOTIFY_CHANNEL_EMAIL = "email" as const; +export const NOTIFY_CHANNEL_LARK = "lark" as const; +export const NOTIFY_CHANNEL_SERVERCHAN = "serverchan" as const; +export const NOTIFY_CHANNEL_TELEGRAM = "telegram" as const; +export const NOTIFY_CHANNEL_WEBHOOK = "webhook" as const; +export const NOTIFY_CHANNEL_WECOM = "wecom" as const; +export const NOTIFY_CHANNELS = Object.freeze({ + BARK: NOTIFY_CHANNEL_BARK, + DINGTALK: NOTIFY_CHANNEL_DINGTALK, + EMAIL: NOTIFY_CHANNEL_EMAIL, + LARK: NOTIFY_CHANNEL_LARK, + SERVERCHAN: NOTIFY_CHANNEL_SERVERCHAN, + TELEGRAM: NOTIFY_CHANNEL_TELEGRAM, + WEBHOOK: NOTIFY_CHANNEL_WEBHOOK, + WECOM: NOTIFY_CHANNEL_WECOM, +} as const); + +export type NotifyChannels = (typeof NOTIFY_CHANNELS)[keyof typeof NOTIFY_CHANNELS]; + export type NotifyChannelsSettingsContent = { /* 注意:如果追加新的类型,请保持以 ASCII 排序。 NOTICE: If you add new type, please keep ASCII order. */ [key: string]: ({ enabled?: boolean } & Record) | undefined; - bark?: BarkNotifyChannelConfig; - dingtalk?: DingTalkNotifyChannelConfig; - email?: EmailNotifyChannelConfig; - lark?: LarkNotifyChannelConfig; - serverchan?: ServerChanNotifyChannelConfig; - telegram?: TelegramNotifyChannelConfig; - webhook?: WebhookNotifyChannelConfig; + [NOTIFY_CHANNEL_BARK]?: BarkNotifyChannelConfig; + [NOTIFY_CHANNEL_DINGTALK]?: DingTalkNotifyChannelConfig; + [NOTIFY_CHANNEL_EMAIL]?: EmailNotifyChannelConfig; + [NOTIFY_CHANNEL_LARK]?: LarkNotifyChannelConfig; + [NOTIFY_CHANNEL_SERVERCHAN]?: ServerChanNotifyChannelConfig; + [NOTIFY_CHANNEL_TELEGRAM]?: TelegramNotifyChannelConfig; + [NOTIFY_CHANNEL_WEBHOOK]?: WebhookNotifyChannelConfig; + [NOTIFY_CHANNEL_WECOM]?: WeComNotifyChannelConfig; }; export type BarkNotifyChannelConfig = { @@ -98,6 +120,11 @@ export type WebhookNotifyChannelConfig = { enabled?: boolean; }; +export type WeComNotifyChannelConfig = { + webhookUrl: string; + enabled?: boolean; +}; + export type NotifyChannel = { type: string; name: string; @@ -108,6 +135,7 @@ export const notifyChannelsMap: Map = new ["email", "common.notifier.email"], ["dingtalk", "common.notifier.dingtalk"], ["lark", "common.notifier.lark"], + ["wecom", "common.notifier.wecom"], ["telegram", "common.notifier.telegram"], ["serverchan", "common.notifier.serverchan"], ["bark", "common.notifier.bark"], diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 971cde9a..25bb8769 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -90,5 +90,6 @@ "common.notifier.lark": "Lark", "common.notifier.serverchan": "ServerChan", "common.notifier.telegram": "Telegram", - "common.notifier.webhook": "Webhook" + "common.notifier.webhook": "Webhook", + "common.notifier.wecom": "WeCom" } diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index 742a2c31..cde32005 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -67,10 +67,12 @@ "settings.notification.channel.form.telegram_chat_id.tooltip": "For more information, see https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "settings.notification.channel.form.webhook_url.label": "Webhook URL", "settings.notification.channel.form.webhook_url.placeholder": "Please enter Webhook URL", + "settings.notification.channel.form.wecom_webhook_url.label": "Webhook URL", + "settings.notification.channel.form.wecom_webhook_url.placeholder": "Please enter Webhook URL", + "settings.notification.channel.form.wecom_webhook_url.tooltip": "For more information, see https://open.work.weixin.qq.com/help2/pc/18401", "settings.sslprovider.tab": "Certificate Authority", "settings.sslprovider.form.provider.label": "ACME Provider", - "settings.sslprovider.provider.errmsg.empty": "Please select a Certificate Authority", "settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID", "settings.sslprovider.form.zerossl_eab_kid.placeholder": "Please enter EAB KID", "settings.sslprovider.form.zerossl_eab_kid.tooltip": "For more information, see https://zerossl.com/documentation/acme/", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index e8636ee6..f961eb06 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -86,9 +86,10 @@ "common.notifier.bark": "Bark", "common.notifier.dingtalk": "钉钉", - "common.notifier.email": "电子邮件", + "common.notifier.email": "邮件", "common.notifier.lark": "飞书", "common.notifier.serverchan": "Server 酱", "common.notifier.telegram": "Telegram", - "common.notifier.webhook": "Webhook" + "common.notifier.webhook": "Webhook", + "common.notifier.wecom": "企业微信" } diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 9b33e0d2..9c77da23 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -53,8 +53,8 @@ "settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址", "settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址", "settings.notification.channel.form.email_receiver_address.placeholder": "请输入接收邮箱地址", - "settings.notification.channel.form.lark_webhook_url.label": "Webhook 地址", - "settings.notification.channel.form.lark_webhook_url.placeholder": "请输入 Webhook 地址", + "settings.notification.channel.form.lark_webhook_url.label": "机器人 Webhook 地址", + "settings.notification.channel.form.lark_webhook_url.placeholder": "请输入机器人 Webhook 地址", "settings.notification.channel.form.lark_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", "settings.notification.channel.form.serverchan_url.label": "服务器地址", "settings.notification.channel.form.serverchan_url.placeholder": "请输入服务器地址(形如: https://sctapi.ftqq.com/*****.send)", @@ -67,10 +67,12 @@ "settings.notification.channel.form.telegram_chat_id.tooltip": "这是什么?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "settings.notification.channel.form.webhook_url.label": "Webhook 回调地址", "settings.notification.channel.form.webhook_url.placeholder": "请输入 Webhook 回调地址", + "settings.notification.channel.form.wecom_webhook_url.label": "机器人 Webhook 地址", + "settings.notification.channel.form.wecom_webhook_url.placeholder": "请输入机器人 Webhook 地址", + "settings.notification.channel.form.wecom_webhook_url.tooltip": "这是什么?请参阅 https://open.work.weixin.qq.com/help2/pc/18401", "settings.sslprovider.tab": "证书颁发机构(CA)", "settings.sslprovider.form.provider.label": "ACME 提供商", - "settings.sslprovider.provider.errmsg.empty": "请选择证书分发机构", "settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID", "settings.sslprovider.form.zerossl_eab_kid.placeholder": "请输入 EAB KID", "settings.sslprovider.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 https://zerossl.com/documentation/acme/", diff --git a/ui/src/stores/notify/index.ts b/ui/src/stores/notify/index.ts index b61c1a93..86f81fe7 100644 --- a/ui/src/stores/notify/index.ts +++ b/ui/src/stores/notify/index.ts @@ -26,7 +26,7 @@ export const useNotifyChannelStore = create((set, get) => { produce(settings, (draft) => { draft.content ??= {}; draft.content[channel] = { ...draft.content[channel], ...config }; - }) + }).content ); },