✨ smtp服务

This commit is contained in:
sugarBecomer
2026-03-26 11:37:38 +08:00
parent 93f696d4df
commit 5a244c329e
5 changed files with 163 additions and 23 deletions
+20
View File
@@ -27,3 +27,23 @@ type ConfigPublicGetReq struct {
g.Meta `path:"/config/public/{key}" method:"get" tags:"Config" summary:"获取公开配置(无需认证)" auth:"false"`
Key string `json:"key" v:"required#配置键不能为空"`
}
// SMTP 配置
type SmtpConfigGetReq struct {
g.Meta `path:"/config/smtp" method:"get" tags:"Config" summary:"获取SMTP配置" permission:"sys:config:edit"`
}
type SmtpConfigUpdateReq struct {
g.Meta `path:"/config/smtp" method:"put" tags:"Config" summary:"更新SMTP配置" permission:"sys:config:edit"`
SmtpHost string `json:"smtpHost" v:"required#SMTP服务器地址不能为空"`
SmtpPort int `json:"smtpPort" v:"required#SMTP端口不能为空"`
SmtpUsername string `json:"smtpUsername" v:"required#SMTP用户名不能为空"`
SmtpPassword string `json:"smtpPassword" v:"required#SMTP密码不能为空"`
FromEmail string `json:"fromEmail" v:"required#发件人邮箱不能为空"`
FromName string `json:"fromName" v:"required#发件人名称不能为空"`
}
type SmtpTestReq struct {
g.Meta `path:"/config/smtp/test" method:"post" tags:"Config" summary:"发送测试邮件" permission:"sys:config:edit"`
ToEmail string `json:"toEmail" v:"required|email#收件人邮箱不能为空|请输入有效邮箱"`
}
@@ -46,3 +46,21 @@ func (c ConfigCtrlV1) UpdatePriceConfig(ctx context.Context, req *v1.PriceConfig
func NewConfigCtrlV1() IConfigCtrlV1 {
return &ConfigCtrlV1{}
}
// GetSmtpConfig 获取SMTP配置
func (c ConfigCtrlV1) GetSmtpConfig(ctx context.Context, req *v1.SmtpConfigGetReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.Config.GetSmtpConfig(ctx, req), nil
}
// UpdateSmtpConfig 更新SMTP配置
func (c ConfigCtrlV1) UpdateSmtpConfig(ctx context.Context, req *v1.SmtpConfigUpdateReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.Config.UpdateSmtpConfig(ctx, req), nil
}
// TestSmtp 发送测试邮件
func (c ConfigCtrlV1) TestSmtp(ctx context.Context, req *v1.SmtpTestReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.Config.TestSmtp(ctx, req), nil
}
@@ -14,4 +14,7 @@ type IConfigCtrlV1 interface {
Update(ctx context.Context, req *v1.ConfigUpdateReq) (*utils.Response[any], error)
GetPriceConfig(ctx context.Context, req *v1.PriceConfigGetReq) (*utils.Response[*v1.PriceConfigResp], error)
UpdatePriceConfig(ctx context.Context, req *v1.PriceConfigUpdateReq) (*utils.Response[any], error)
GetSmtpConfig(ctx context.Context, req *v1.SmtpConfigGetReq) (*utils.Response[any], error)
UpdateSmtpConfig(ctx context.Context, req *v1.SmtpConfigUpdateReq) (*utils.Response[any], error)
TestSmtp(ctx context.Context, req *v1.SmtpTestReq) (*utils.Response[any], error)
}
+80
View File
@@ -2,12 +2,18 @@ package config
import (
"context"
"fmt"
v1 "likedream-goframe/internal/api/v1"
cst2 "likedream-goframe/internal/common/cst"
"likedream-goframe/internal/common/db"
errors2 "likedream-goframe/internal/common/errors"
"likedream-goframe/internal/ent"
"likedream-goframe/internal/ent/sysconfig"
"likedream-goframe/internal/service/email"
"likedream-goframe/internal/utils"
"time"
mail "github.com/wneessen/go-mail"
)
type ConfigServiceV1 struct {
@@ -189,3 +195,77 @@ func (s *ConfigServiceV1) upsertConfig(ctx context.Context, key, value, descript
SetValue(value).
ExecX(ctx)
}
// GetSmtpConfig 获取SMTP配置
func (s *ConfigServiceV1) GetSmtpConfig(ctx context.Context, req *v1.SmtpConfigGetReq) *utils.Response[any] {
keys := []string{
cst2.ConfigEmailSMTPHost,
cst2.ConfigEmailSMTPPort,
cst2.ConfigEmailSMTPUsername,
cst2.ConfigEmailSMTPPassword,
cst2.ConfigEmailFromEmail,
cst2.ConfigEmailFromName,
}
result := make(map[string]string)
for _, key := range keys {
config, err := db.Global.SysConfig.Query().Where(sysconfig.KeyEQ(key)).Only(ctx)
if err == nil {
result[key] = config.Value
}
}
return utils.SuccessWithData[any](map[string]string{
"smtpHost": result[cst2.ConfigEmailSMTPHost],
"smtpPort": result[cst2.ConfigEmailSMTPPort],
"smtpUsername": result[cst2.ConfigEmailSMTPUsername],
"smtpPassword": result[cst2.ConfigEmailSMTPPassword],
"fromEmail": result[cst2.ConfigEmailFromEmail],
"fromName": result[cst2.ConfigEmailFromName],
})
}
// UpdateSmtpConfig 更新SMTP配置
func (s *ConfigServiceV1) UpdateSmtpConfig(ctx context.Context, req *v1.SmtpConfigUpdateReq) *utils.Response[any] {
configs := map[string]string{
cst2.ConfigEmailSMTPHost: req.SmtpHost,
cst2.ConfigEmailSMTPPort: fmt.Sprintf("%d", req.SmtpPort),
cst2.ConfigEmailSMTPUsername: req.SmtpUsername,
cst2.ConfigEmailSMTPPassword: req.SmtpPassword,
cst2.ConfigEmailFromEmail: req.FromEmail,
cst2.ConfigEmailFromName: req.FromName,
}
for key, value := range configs {
exists, _ := db.Global.SysConfig.Query().Where(sysconfig.KeyEQ(key)).Exist(ctx)
if exists {
db.Global.SysConfig.Update().Where(sysconfig.KeyEQ(key)).SetValue(value).ExecX(ctx)
} else {
db.Global.SysConfig.Create().SetKey(key).SetValue(value).SetDescription("SMTP邮件配置").ExecX(ctx)
}
}
return utils.SuccessWithMsg("SMTP配置保存成功")
}
// TestSmtp 发送测试邮件
func (s *ConfigServiceV1) TestSmtp(ctx context.Context, req *v1.SmtpTestReq) *utils.Response[any] {
emailService := email.NewV1()
// 直接同步发送测试邮件,不走队列
msg := mail.NewMsg()
e := msg.FromFormat(emailService.GetConfig().FromName, emailService.GetConfig().FromEmail)
utils.OkOrPanic(e)
e = msg.AddTo(req.ToEmail)
utils.OkOrPanic(e)
msg.Subject("若梦系统 - SMTP测试邮件")
msg.SetBodyString(mail.TypeTextHTML, "<h2>SMTP配置测试成功</h2><p>如果您收到此邮件,说明SMTP配置正确。</p>")
cfg := emailService.GetConfig()
client, e := mail.NewClient(cfg.SMTPHost, mail.WithPort(cfg.SMTPPort),
mail.WithSMTPAuth(mail.SMTPAuthLogin),
mail.WithTLSPortPolicy(mail.TLSMandatory),
mail.WithTimeout(30*time.Second),
mail.WithUsername(cfg.SMTPUsername), mail.WithPassword(cfg.SMTPPassword))
utils.OkOrPanic(e)
defer client.Close()
e = client.DialAndSendWithContext(ctx, msg)
utils.OkOrPanic(e)
return utils.SuccessWithMsg("测试邮件发送成功")
}
+42 -23
View File
@@ -7,14 +7,17 @@ import (
"encoding/gob"
"fmt"
"html/template"
db "likedream-goframe/internal/common/db"
"strconv"
cst2 "likedream-goframe/internal/common/cst"
"likedream-goframe/internal/ent/sysconfig"
"likedream-goframe/internal/framework/logger"
email2 "likedream-goframe/internal/framework/model/email"
"likedream-goframe/internal/framework/queue"
"likedream-goframe/internal/utils"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/hibiken/asynq"
"github.com/samber/oops"
@@ -25,7 +28,6 @@ import (
var templateFiles embed.FS
type EmailServiceV1 struct {
config EmailConfig
}
type EmailConfig struct {
@@ -38,20 +40,32 @@ type EmailConfig struct {
}
func NewV1() *EmailServiceV1 {
ctx := gctx.New()
cfg := g.Cfg()
return &EmailServiceV1{}
}
config := EmailConfig{
SMTPHost: cfg.MustGetWithEnv(ctx, cst2.ConfigEmailSMTPHost).String(),
SMTPPort: cfg.MustGetWithEnv(ctx, cst2.ConfigEmailSMTPPort).Int(),
SMTPUsername: cfg.MustGetWithEnv(ctx, cst2.ConfigEmailSMTPUsername).String(),
SMTPPassword: cfg.MustGetWithEnv(ctx, cst2.ConfigEmailSMTPPassword).String(),
FromEmail: cfg.MustGetWithEnv(ctx, cst2.ConfigEmailFromEmail).String(),
FromName: cfg.MustGetWithEnv(ctx, cst2.ConfigEmailFromName).String(),
// GetConfig 从数据库读取当前邮件配置
func (s *EmailServiceV1) GetConfig() EmailConfig {
ctx := gctx.New()
getVal := func(key string) string {
config, err := db.Global.SysConfig.Query().
Where(sysconfig.KeyEQ(key)).
Only(ctx)
if err == nil {
return config.Value
}
return ""
}
return &EmailServiceV1{
config: config,
port, _ := strconv.Atoi(getVal(cst2.ConfigEmailSMTPPort))
return EmailConfig{
SMTPHost: getVal(cst2.ConfigEmailSMTPHost),
SMTPPort: port,
SMTPUsername: getVal(cst2.ConfigEmailSMTPUsername),
SMTPPassword: getVal(cst2.ConfigEmailSMTPPassword),
FromEmail: getVal(cst2.ConfigEmailFromEmail),
FromName: getVal(cst2.ConfigEmailFromName),
}
}
@@ -75,7 +89,8 @@ func brandCopyright() string {
// SendSignupEmail 发送注册成功邮件
func (s *EmailServiceV1) SendSignupEmail(ctx context.Context, toEmail, username string, token string) *utils.Response[any] {
if s.config.SMTPUsername == "" || s.config.SMTPPassword == "" {
cfg := s.GetConfig()
if cfg.SMTPUsername == "" || cfg.SMTPPassword == "" {
logger.Logger.Error("邮件服务未配置")
return utils.FailBusiness("邮件服务未配置")
}
@@ -99,7 +114,8 @@ func (s *EmailServiceV1) SendSignupEmail(ctx context.Context, toEmail, username
// SendTaskNotificationEmail 发送任务状态通知邮件
func (s *EmailServiceV1) SendTaskNotificationEmail(ctx context.Context, toEmail, taskName string, status email2.TaskStatus, details map[string]string) *utils.Response[any] {
if s.config.SMTPUsername == "" || s.config.SMTPPassword == "" {
cfg := s.GetConfig()
if cfg.SMTPUsername == "" || cfg.SMTPPassword == "" {
return utils.FailBusiness("邮件服务未配置")
}
@@ -119,7 +135,8 @@ func (s *EmailServiceV1) SendTaskNotificationEmail(ctx context.Context, toEmail,
// SendResetPasswordEmail 发送重置密码邮件
func (s *EmailServiceV1) SendResetPasswordEmail(ctx context.Context, toEmail, username string, token string) *utils.Response[any] {
if s.config.SMTPUsername == "" || s.config.SMTPPassword == "" {
cfg := s.GetConfig()
if cfg.SMTPUsername == "" || cfg.SMTPPassword == "" {
logger.Logger.Error("邮件服务未配置")
return utils.FailBusiness("邮件服务未配置")
}
@@ -143,7 +160,8 @@ func (s *EmailServiceV1) SendResetPasswordEmail(ctx context.Context, toEmail, us
// SendBalanceMigrateEmail 发送积分转移邮件
func (s *EmailServiceV1) SendBalanceMigrateEmail(ctx context.Context, toEmail, username string, balance int, token string) *utils.Response[any] {
if s.config.SMTPUsername == "" || s.config.SMTPPassword == "" {
cfg := s.GetConfig()
if cfg.SMTPUsername == "" || cfg.SMTPPassword == "" {
logger.Logger.Error("邮件服务未配置")
return utils.FailBusiness("邮件服务未配置")
}
@@ -167,18 +185,19 @@ func (s *EmailServiceV1) SendBalanceMigrateEmail(ctx context.Context, toEmail, u
// sendEmail 发送邮件的底层方法
func (s *EmailServiceV1) sendEmail(ctx context.Context, to, subject, body string) error {
cfg := s.GetConfig()
payload := bytes.NewBuffer(nil)
e := gob.NewEncoder(payload).Encode(&email2.SendPayload{
To: to,
Subject: subject,
Body: body,
FromName: s.config.FromName,
FromEmail: s.config.FromEmail,
SMTPHost: s.config.SMTPHost,
SMTPPort: s.config.SMTPPort,
SMTPUsername: s.config.SMTPUsername,
SMTPPassword: s.config.SMTPPassword,
FromName: cfg.FromName,
FromEmail: cfg.FromEmail,
SMTPHost: cfg.SMTPHost,
SMTPPort: cfg.SMTPPort,
SMTPUsername: cfg.SMTPUsername,
SMTPPassword: cfg.SMTPPassword,
})
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)