mirror of
https://github.com/keven1024/015.git
synced 2026-05-26 07:08:02 +00:00
feat(notifications): implement webhook and email notification services for share events, enhancing user communication capabilities
This commit is contained in:
@@ -6,9 +6,11 @@ require (
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hibiken/asynq v0.26.0
|
||||
github.com/openai/openai-go/v3 v3.30.0
|
||||
github.com/samber/lo v1.53.0
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
go.uber.org/zap v1.27.1
|
||||
)
|
||||
|
||||
@@ -16,7 +18,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/openai/openai-go/v3 v3.30.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.18.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
@@ -26,6 +27,7 @@ require (
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
|
||||
@@ -10,6 +10,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -49,6 +51,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
@@ -59,6 +63,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
|
||||
98
worker/internal/services/notify.go
Normal file
98
worker/internal/services/notify.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"pkg/i18n"
|
||||
"pkg/models"
|
||||
u "pkg/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/samber/lo"
|
||||
mail "github.com/wneessen/go-mail"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func SendWebhook(webhook models.NotifyWebhook) error {
|
||||
method := strings.ToUpper(strings.TrimSpace(webhook.Method))
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
request := resty.New().R()
|
||||
if strings.EqualFold(webhook.BodyType, "form-data") {
|
||||
request.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
for key, value := range webhook.Headers {
|
||||
request.SetHeader(key, value)
|
||||
}
|
||||
if !strings.EqualFold(webhook.BodyType, "none") && webhook.Body != "" {
|
||||
request.SetBody(webhook.Body)
|
||||
}
|
||||
|
||||
resp, err := request.Execute(method, webhook.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode() >= 400 {
|
||||
return fmt.Errorf("webhook %s returned status %d", webhook.URL, resp.StatusCode())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendEmail(to string, shareInfo *models.RedisShareInfo, ip string) error {
|
||||
host := u.GetEnv("smtp.host")
|
||||
if host == "" {
|
||||
zap.L().Warn("smtp host is empty, skip share notify email", zap.String("to", to))
|
||||
return nil
|
||||
}
|
||||
|
||||
username := u.GetEnv("smtp.username")
|
||||
password := u.GetEnv("smtp.password")
|
||||
from := u.GetEnvWithDefault("smtp.from", username)
|
||||
if from == "" {
|
||||
return fmt.Errorf("smtp.from or smtp.username is required")
|
||||
}
|
||||
|
||||
templateData := map[string]any{
|
||||
"IP": ip,
|
||||
"SiteURL": u.GetEnv("site.url"),
|
||||
"ShareType": i18n.T(shareInfo.Locale, lo.Ternary(shareInfo.Type == models.ShareTypeText, "share_type_text", "share_type_file")),
|
||||
"FileName": shareInfo.FileName,
|
||||
}
|
||||
subject := i18n.TWithData(shareInfo.Locale, "notify_email_subject", templateData)
|
||||
body := i18n.TWithData(shareInfo.Locale, "notify_email_body", templateData)
|
||||
message := mail.NewMsg()
|
||||
if err := message.From(from); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := message.To(to); err != nil {
|
||||
return err
|
||||
}
|
||||
message.Subject(subject)
|
||||
message.SetBodyString(mail.TypeTextPlain, body)
|
||||
|
||||
port, err := strconv.Atoi(u.GetEnvWithDefault("smtp.port", "587"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := []mail.Option{
|
||||
mail.WithPort(port),
|
||||
}
|
||||
if port == mail.DefaultPortSSL {
|
||||
options = append(options, mail.WithSSL())
|
||||
} else {
|
||||
options = append(options, mail.WithTLSPortPolicy(mail.TLSMandatory))
|
||||
}
|
||||
if username != "" {
|
||||
options = append(options, mail.WithUsername(username), mail.WithPassword(password), mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover))
|
||||
}
|
||||
|
||||
client, err := mail.NewClient(host, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.DialAndSend(message)
|
||||
}
|
||||
@@ -3,10 +3,13 @@ package tasks
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"pkg/models"
|
||||
u "pkg/utils"
|
||||
"worker/internal/services"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/samber/lo"
|
||||
@@ -54,5 +57,36 @@ func RemoveShare(ctx context.Context, task *asynq.Task) error {
|
||||
}
|
||||
|
||||
func ShareNotify(ctx context.Context, task *asynq.Task) error {
|
||||
return nil
|
||||
var payload ShareNotifyTaskPayload
|
||||
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
shareInfo, err := models.GetRedisShareInfo(payload.ShareId)
|
||||
if err != nil || shareInfo == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
successCount := 0
|
||||
|
||||
for _, webhook := range shareInfo.NotifyWebhooks {
|
||||
if err := services.SendWebhook(webhook); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
|
||||
for _, email := range shareInfo.NotifyEmails {
|
||||
if err := services.SendEmail(email, shareInfo, payload.IP); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
|
||||
if successCount > 0 || len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("all share notify targets failed: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ type RemoveFileTaskPayload struct {
|
||||
BaseFileTaskPayload
|
||||
}
|
||||
|
||||
type ShareNotifyTaskPayload struct {
|
||||
ShareId string `json:"share_id"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
type CompressImageTaskPayload struct {
|
||||
BaseFileTaskPayload
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"pkg/i18n"
|
||||
"pkg/utils"
|
||||
"worker/internal/tasks"
|
||||
"worker/middleware"
|
||||
@@ -22,6 +23,10 @@ func main() {
|
||||
defer logger.Sync() //nolint:errcheck
|
||||
zap.ReplaceGlobals(logger)
|
||||
|
||||
if err := i18n.Init(); err != nil {
|
||||
log.Fatalf("failed to init i18n: %v", err)
|
||||
}
|
||||
|
||||
srv := asynq.NewServer(
|
||||
utils.RedisURI2AsynqOpt(utils.GetEnv("redis.url")),
|
||||
asynq.Config{Concurrency: cast.ToInt(utils.GetEnvWithDefault("worker.concurrency", "4"))},
|
||||
@@ -30,6 +35,7 @@ func main() {
|
||||
mux := asynq.NewServeMux()
|
||||
mux.Use(middleware.LoggerMiddleware)
|
||||
mux.HandleFunc("share:remove", tasks.RemoveShare)
|
||||
mux.HandleFunc("share:notify", tasks.ShareNotify)
|
||||
mux.HandleFunc("file:remove", tasks.RemoveFile)
|
||||
mux.HandleFunc("image:compress", tasks.CompressImage)
|
||||
mux.HandleFunc("image:convert", tasks.ConvertImage)
|
||||
|
||||
Reference in New Issue
Block a user