mirror of
https://github.com/certimate-go/certimate.git
synced 2026-06-13 21:01:32 +08:00
feat: 完成邮件通知的html渲染功能 (#1183)
This commit is contained in:
parent
1dd48e04da
commit
34e2dae3d4
3
go.mod
3
go.mod
@ -49,6 +49,7 @@ require (
|
||||
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
|
||||
github.com/kong/go-kong v0.71.0
|
||||
github.com/luthermonson/go-proxmox v0.3.2
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.0.97
|
||||
github.com/mohuatech/mohuacloud-go-sdk v0.0.0-20251115182757-6fba4d0a4c47
|
||||
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
|
||||
@ -192,6 +193,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
@ -209,6 +211,7 @@ require (
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@ -238,6 +238,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfm
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/baidubce/bce-sdk-go v0.9.256 h1:/6UwBzDp+dRFpKRIb5WsvxfSiG4SLOIOghvagOK/q4Y=
|
||||
github.com/baidubce/bce-sdk-go v0.9.256/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
@ -495,6 +497,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
@ -648,6 +652,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
|
||||
@ -25,6 +25,7 @@ func init() {
|
||||
SenderAddress: credentials.SenderAddress,
|
||||
SenderName: credentials.SenderName,
|
||||
ReceiverAddress: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", credentials.ReceiverAddress),
|
||||
MessageFormat: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "format", email.MESSAGE_FORMAT_PLAIN),
|
||||
AllowInsecureConnections: credentials.AllowInsecureConnections,
|
||||
})
|
||||
return provider, err
|
||||
|
||||
6
pkg/core/notifier/providers/email/consts.go
Normal file
6
pkg/core/notifier/providers/email/consts.go
Normal file
@ -0,0 +1,6 @@
|
||||
package email
|
||||
|
||||
const (
|
||||
MESSAGE_FORMAT_PLAIN = "plain"
|
||||
MESSAGE_FORMAT_HTML = "html"
|
||||
)
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/tools/smtp"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier"
|
||||
)
|
||||
@ -28,6 +30,10 @@ type NotifierConfig struct {
|
||||
SenderName string `json:"senderName,omitempty"`
|
||||
// 收件人邮箱。
|
||||
ReceiverAddress string `json:"receiverAddress"`
|
||||
// 消息格式。
|
||||
// 可取值 [MESSAGE_FORMAT_PLAIN]、[MESSAGE_FORMAT_HTML]。
|
||||
// 零值时默认值 [MESSAGE_FORMAT_PLAIN]。
|
||||
MessageFormat string `json:"messageFormat,omitempty"`
|
||||
// 是否允许不安全的连接。
|
||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||
}
|
||||
@ -75,7 +81,16 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) (
|
||||
|
||||
msg := smtp.NewMessage()
|
||||
msg.Subject(subject)
|
||||
msg.SetBodyString(smtp.MIMETypeTextPlain, message)
|
||||
switch n.config.MessageFormat {
|
||||
case "", MESSAGE_FORMAT_PLAIN:
|
||||
msg.SetBodyString(smtp.MIMETypeTextPlain, message)
|
||||
case MESSAGE_FORMAT_HTML:
|
||||
msg.SetBodyString(smtp.MIMETypeTextHTML, bluemonday.UGCPolicy().Sanitize(message))
|
||||
msg.AddAlternativeString(smtp.MIMETypeTextPlain, bluemonday.StrictPolicy().Sanitize(message))
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported message format: '%s'", n.config.MessageFormat)
|
||||
}
|
||||
|
||||
if n.config.SenderName == "" {
|
||||
msg.From(n.config.SenderAddress)
|
||||
} else {
|
||||
|
||||
@ -11,8 +11,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
mockSubject = "test_subject"
|
||||
mockMessage = "test_message"
|
||||
mockSubject = "test_subject"
|
||||
mockMessage = "test_message"
|
||||
mockHtmlMessage = "<h1>Hello Certimate!</h1><a onblur=\"alert(secret)\" href=\"http://www.google.com\">Google</a>"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -86,4 +87,40 @@ func TestNotify(t *testing.T) {
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
|
||||
t.Run("Notify_Html", func(t *testing.T) {
|
||||
t.Log(strings.Join([]string{
|
||||
"args:",
|
||||
fmt.Sprintf("SMTPHOST: %v", fSmtpHost),
|
||||
fmt.Sprintf("SMTPPORT: %v", fSmtpPort),
|
||||
fmt.Sprintf("SMTPTLS: %v", fSmtpTLS),
|
||||
fmt.Sprintf("USERNAME: %v", fUsername),
|
||||
fmt.Sprintf("PASSWORD: %v", fPassword),
|
||||
fmt.Sprintf("SENDERADDRESS: %v", fSenderAddress),
|
||||
fmt.Sprintf("RECEIVERADDRESS: %v", fReceiverAddress),
|
||||
}, "\n"))
|
||||
|
||||
provider, err := provider.NewNotifier(&provider.NotifierConfig{
|
||||
SmtpHost: fSmtpHost,
|
||||
SmtpPort: int32(fSmtpPort),
|
||||
SmtpTls: fSmtpTLS,
|
||||
Username: fUsername,
|
||||
Password: fPassword,
|
||||
SenderAddress: fSenderAddress,
|
||||
ReceiverAddress: fReceiverAddress,
|
||||
MessageFormat: provider.MESSAGE_FORMAT_HTML,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := provider.Notify(context.Background(), mockSubject, mockHtmlMessage)
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("ok: %v", res)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
@ -7,6 +7,9 @@ import { isEmail } from "@/utils/validator";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const MESSAGE_FORMAT_PLAIN = "plain" as const;
|
||||
const MESSAGE_FORMAT_HTML = "html" as const;
|
||||
|
||||
const BizNotifyNodeConfigFieldsProviderEmail = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
@ -19,6 +22,22 @@ const BizNotifyNodeConfigFieldsProviderEmail = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "format"]}
|
||||
initialValue={initialValues.format}
|
||||
label={t("workflow_node.notify.form.email_format.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Select
|
||||
options={[MESSAGE_FORMAT_PLAIN, MESSAGE_FORMAT_HTML].map((s) => ({
|
||||
key: s,
|
||||
label: t(`workflow_node.notify.form.email_format.option.${s}.label`),
|
||||
value: s,
|
||||
}))}
|
||||
placeholder={t("workflow_node.notify.form.email_format.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "receiverAddress"]}
|
||||
initialValue={initialValues.receiverAddress}
|
||||
@ -33,13 +52,16 @@ const BizNotifyNodeConfigFieldsProviderEmail = () => {
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {};
|
||||
return {
|
||||
format: MESSAGE_FORMAT_PLAIN,
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z.object({
|
||||
format: z.enum([MESSAGE_FORMAT_PLAIN, MESSAGE_FORMAT_HTML]).nullish(),
|
||||
receiverAddress: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
||||
@ -1197,6 +1197,10 @@
|
||||
"workflow_node.notify.form.discordbot_channel_id.label": "Discord channel ID (Optional)",
|
||||
"workflow_node.notify.form.discordbot_channel_id.placeholder": "Please enter Discord channel ID",
|
||||
"workflow_node.notify.form.discordbot_channel_id.help": "Notes: Leave it blank to use the default channel ID provided by the credential.",
|
||||
"workflow_node.notify.form.email_format.label": "Message format (Optional)",
|
||||
"workflow_node.notify.form.email_format.placeholder": "Please select message format",
|
||||
"workflow_node.notify.form.email_format.option.plain.label": "Plain text",
|
||||
"workflow_node.notify.form.email_format.option.html.label": "HTML",
|
||||
"workflow_node.notify.form.email_receiver_address.label": "Receiver email address (Optional)",
|
||||
"workflow_node.notify.form.email_receiver_address.placeholder": "Please enter receiver email address",
|
||||
"workflow_node.notify.form.email_receiver_address.help": "Notes: Leave it blank to use the default receiver email address provided by the selected credential.",
|
||||
|
||||
@ -1195,6 +1195,10 @@
|
||||
"workflow_node.notify.form.discordbot_channel_id.label": "Discord 频道 ID(可选)",
|
||||
"workflow_node.notify.form.discordbot_channel_id.placeholder": "请输入 Discord 频道 ID",
|
||||
"workflow_node.notify.form.discordbot_channel_id.help": "提示:不填写时,将使用所选通知渠道授权的默认频道 ID。",
|
||||
"workflow_node.notify.form.email_format.label": "消息格式(可选)",
|
||||
"workflow_node.notify.form.email_format.placeholder": "请选择消息格式",
|
||||
"workflow_node.notify.form.email_format.option.plain.label": "纯文本",
|
||||
"workflow_node.notify.form.email_format.option.html.label": "HTML",
|
||||
"workflow_node.notify.form.email_receiver_address.label": "收件人邮箱(可选)",
|
||||
"workflow_node.notify.form.email_receiver_address.placeholder": "请输入收件人邮箱以覆盖默认值",
|
||||
"workflow_node.notify.form.email_receiver_address.help": "提示:不填写时,将使用所选通知渠道授权的默认收件人邮箱。",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user