/* Copyright 2026 linux.do Modified by Arctel.net, 2026 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package user import ( "context" "encoding/json" "errors" "fmt" "strconv" "strings" "github.com/Rain-kl/Wavelet/internal/model" "github.com/Rain-kl/Wavelet/internal/task" "github.com/Rain-kl/Wavelet/internal/util/mail" ) // SendEmailPayload 邮件发送任务载荷 type SendEmailPayload struct { To string `json:"to"` Subject string `json:"subject"` Body string `json:"body"` } // SendEmailHandler 发送验证码邮件的异步任务处理器 type SendEmailHandler struct{} // ValidatePayload 实现 task.PayloadValidator 接口 // 校验并标准化邮件发送参数,框架在 Admin 下发时自动调用 func (h *SendEmailHandler) ValidatePayload(payload []byte) ([]byte, error) { if len(payload) == 0 { return nil, errors.New(errTaskPayloadRequired) } var req SendEmailPayload if err := json.Unmarshal(payload, &req); err != nil { return nil, fmt.Errorf(errInvalidJSONFormat, err) } req.To = strings.TrimSpace(req.To) req.Subject = strings.TrimSpace(req.Subject) req.Body = strings.TrimSpace(req.Body) if req.To == "" || req.Subject == "" || req.Body == "" { return nil, errors.New(errEmailTaskFieldsRequired) } return json.Marshal(req) } // Execute 执行邮件异步发送逻辑 func (h *SendEmailHandler) Execute(ctx context.Context, payload []byte) (*task.TaskResult, error) { var req SendEmailPayload if err := json.Unmarshal(payload, &req); err != nil { task.AppendLog(ctx, "解析邮件发送参数失败: %v", err) return nil, fmt.Errorf(errParseEmailPayloadFailed, err) } task.AppendLog(ctx, "开始准备发送邮件到: %s, 主题: %s", req.To, req.Subject) // 从数据库读取最新的 SMTP 系统配置 var smtpHost string var smtpPortVal string var smtpUsername string var smtpPassword string var sc model.SystemConfig if err := sc.GetByKey(ctx, model.ConfigKeySMTPHost); err == nil { smtpHost = sc.Value } if err := sc.GetByKey(ctx, model.ConfigKeySMTPPort); err == nil { smtpPortVal = sc.Value } if err := sc.GetByKey(ctx, model.ConfigKeySMTPUsername); err == nil { smtpUsername = sc.Value } if err := sc.GetByKey(ctx, model.ConfigKeySMTPPassword); err == nil { smtpPassword = sc.Value } if smtpHost == "" || smtpPortVal == "" || smtpUsername == "" { err := errors.New(errSMTPConfigIncomplete) task.AppendLog(ctx, "发送失败: %v", err) return nil, err } smtpPort, err := strconv.Atoi(smtpPortVal) if err != nil { smtpPort = 587 } cfg := mail.Config{ Host: smtpHost, Port: smtpPort, Username: smtpUsername, Password: smtpPassword, } task.AppendLog(ctx, "连接 SMTP 服务器: %s:%d, 用户名: %s", smtpHost, smtpPort, smtpUsername) // 调用 SendMailHTML 执行邮件发送,这里会有 5s 拨号超时和 10s 读写限制 err = mail.SendMailHTML(cfg, req.To, req.Subject, req.Body) if err != nil { task.AppendLog(ctx, "邮件发送失败: %v", err) return nil, fmt.Errorf(errSendMailFailed, err) } msg := fmt.Sprintf("邮件成功发送至: %s", req.To) task.AppendLog(ctx, "%s", msg) return &task.TaskResult{ Message: msg, }, nil }