diff --git a/internal/domain/notify.go b/internal/domain/notify.go
index 3d71a3a7..247c691f 100644
--- a/internal/domain/notify.go
+++ b/internal/domain/notify.go
@@ -13,6 +13,7 @@ const (
NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk")
NotifyChannelTypeEmail = NotifyChannelType("email")
NotifyChannelTypeLark = NotifyChannelType("lark")
+ NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
NotifyChannelTypeTelegram = NotifyChannelType("telegram")
NotifyChannelTypeWebhook = NotifyChannelType("webhook")
diff --git a/internal/notify/providers.go b/internal/notify/providers.go
index 66927390..a5f93a91 100644
--- a/internal/notify/providers.go
+++ b/internal/notify/providers.go
@@ -9,6 +9,7 @@ import (
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
+ pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
@@ -50,6 +51,11 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
})
+ case domain.NotifyChannelTypePushPlus:
+ return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{
+ Token: maputil.GetString(channelConfig, "token"),
+ })
+
case domain.NotifyChannelTypeServerChan:
return pServerChan.NewNotifier(&pServerChan.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
diff --git a/internal/pkg/core/notifier/notifier.go b/internal/pkg/core/notifier/notifier.go
index 97485215..876b5d48 100644
--- a/internal/pkg/core/notifier/notifier.go
+++ b/internal/pkg/core/notifier/notifier.go
@@ -1,4 +1,4 @@
-package notifier
+package notifier
import (
"context"
diff --git a/internal/pkg/core/notifier/providers/pushplus/pushplus.go b/internal/pkg/core/notifier/providers/pushplus/pushplus.go
new file mode 100644
index 00000000..4edac14e
--- /dev/null
+++ b/internal/pkg/core/notifier/providers/pushplus/pushplus.go
@@ -0,0 +1,113 @@
+package pushplus
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log/slog"
+ "net/http"
+
+ "github.com/pkg/errors"
+
+ "github.com/usual2970/certimate/internal/pkg/core/notifier"
+)
+
+type NotifierConfig struct {
+ // PushPlus Token
+ Token string `json:"token"`
+}
+
+type NotifierProvider struct {
+ config *NotifierConfig
+ logger *slog.Logger
+ // 未来将移除
+ httpClient *http.Client
+}
+
+var _ notifier.Notifier = (*NotifierProvider)(nil)
+
+func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ return &NotifierProvider{
+ config: config,
+ httpClient: http.DefaultClient,
+ }, nil
+}
+
+func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
+ if logger == nil {
+ n.logger = slog.Default()
+ } else {
+ n.logger = logger
+ }
+ return n
+}
+
+// Notify 发送通知
+// 参考文档:https://pushplus.plus/doc/guide/api.html
+func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
+ // 请求体
+ reqBody := &struct {
+ Token string `json:"token"`
+ Title string `json:"title"`
+ Content string `json:"content"`
+ }{
+ Token: n.config.Token,
+ Title: subject,
+ Content: message,
+ }
+
+ // Make request
+ body, err := json.Marshal(reqBody)
+ if err != nil {
+ return nil, errors.Wrap(err, "encode message body")
+ }
+
+ req, err := http.NewRequestWithContext(
+ ctx,
+ http.MethodPost,
+ "https://www.pushplus.plus/send",
+ bytes.NewReader(body),
+ )
+ if err != nil {
+ return nil, errors.Wrap(err, "create new request")
+ }
+
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
+
+ // Send request to pushplus service
+ resp, err := n.httpClient.Do(req)
+ if err != nil {
+ return nil, errors.Wrapf(err, "send request to pushplus server")
+ }
+ defer resp.Body.Close()
+
+ result, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, errors.Wrap(err, "read response")
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("pushplus returned status code %d: %s", resp.StatusCode, string(result))
+ }
+
+ // 解析响应
+ var errorResponse struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+ }
+ if err := json.Unmarshal(result, &errorResponse); err != nil {
+ return nil, errors.Wrap(err, "decode response")
+ }
+
+ if errorResponse.Code != 200 {
+ return nil, fmt.Errorf("pushplus returned error: %s", errorResponse.Msg)
+ }
+
+ return ¬ifier.NotifyResult{}, nil
+}
diff --git a/internal/pkg/core/notifier/providers/pushplus/pushplus_test.go b/internal/pkg/core/notifier/providers/pushplus/pushplus_test.go
new file mode 100644
index 00000000..f504c168
--- /dev/null
+++ b/internal/pkg/core/notifier/providers/pushplus/pushplus_test.go
@@ -0,0 +1,56 @@
+package pushplus_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
+)
+
+const (
+ mockSubject = "test_subject"
+ mockMessage = "test_message"
+)
+
+var fToken string
+
+func init() {
+ argsPrefix := "CERTIMATE_NOTIFIER_PUSHPLUS_"
+ flag.StringVar(&fToken, argsPrefix+"TOKEN", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./pushplus_test.go -args \
+ --CERTIMATE_NOTIFIER_PUSHPLUS_TOKEN="your-pushplus-token" \
+*/
+func TestNotify(t *testing.T) {
+ flag.Parse()
+
+ t.Run("Notify", func(t *testing.T) {
+ t.Log(strings.Join([]string{
+ "args:",
+ fmt.Sprintf("TOKEN: %v", fToken),
+ }, "\n"))
+
+ notifier, err := provider.NewNotifier(&provider.NotifierConfig{
+ Token: fToken,
+ })
+ 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 d818ee4c..dbeddaaf 100644
--- a/ui/src/components/notification/NotifyChannelEditForm.tsx
+++ b/ui/src/components/notification/NotifyChannelEditForm.tsx
@@ -8,6 +8,7 @@ import NotifyChannelEditFormBarkFields from "./NotifyChannelEditFormBarkFields";
import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalkFields";
import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields";
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
+import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields";
import NotifyChannelEditFormWebhookFields from "./NotifyChannelEditFormWebhookFields";
@@ -50,6 +51,8 @@ const NotifyChannelEditForm = forwardRef