diff --git a/.vscode/launch.json b/.vscode/launch.json index 16c9c47..2d6bda4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,11 @@ "mode": "debug", "program": "${workspaceRoot}/cmd/main.go", "env": { + "SMTP_HOST":"smtp.mail.ru", + // "SMTP_PORT":"465", + "SMTP_PORT":"587", + "SMTP_SENDER":"test3kbot@mail.ru", + "SMTP_PASSWORD":"hwNhMgPyBzMjwCj3hFPp", "KAFKA_PORT":"9092", }, "args": [] diff --git a/internal/config/config.go b/internal/config/config.go index 99f0914..cdb927b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,13 @@ import ( "github.com/kelseyhightower/envconfig" ) +type smtpConfig struct { + Port int `envconfig:"SMTP_PORT"` + Host string `envconfig:"SMTP_HOST"` + Sender string `envconfig:"SMTP_SENDER"` + Password string `envconfig:"SMTP_PASSWORD"` +} + type kafkaConfig struct { Host string `envconfig:"KAFKA_HOST"` Port int `envconfig:"KAFKA_PORT"` @@ -13,6 +20,7 @@ type kafkaConfig struct { // ... type Config struct { + Smtp smtpConfig Kafka kafkaConfig } diff --git a/internal/postman.go b/internal/postman.go index 9691a9a..edab04f 100644 --- a/internal/postman.go +++ b/internal/postman.go @@ -2,22 +2,32 @@ package postman import ( "context" + "encoding/json" "log" "net" "strconv" "test3k/authPostman/internal/config" + smtp "test3k/authPostman/internal/smtp" + "github.com/segmentio/kafka-go" ) +type msg struct { + Code string + Email string +} + type AuthPostmanServer struct { ctx context.Context + config *config.Config kafkaReader *kafka.Reader } func NewServer(ctx context.Context, config *config.Config, topic string) *AuthPostmanServer { return &AuthPostmanServer{ - ctx: ctx, + ctx: ctx, + config: config, kafkaReader: kafka.NewReader(kafka.ReaderConfig{ Topic: topic, Brokers: []string{net.JoinHostPort(config.Kafka.Host, strconv.Itoa(config.Kafka.Port))}, @@ -44,6 +54,26 @@ func (s *AuthPostmanServer) ReadMessage(offset int64) error { return err } - log.Printf("message at offset %d: %s = %s\n", m.Offset, string(m.Key), string(m.Value)) + // Декодируем сообщение + amsg := msg{} + erk := json.Unmarshal(m.Value, &amsg) + if erk != nil { + return erk + } + + // + message := smtp.NewMessage("Confirmation code", amsg.Code) + message.AppendRecipient(amsg.Email) + + // + smtpSender := smtp.NewService(s.config.Smtp.Host, s.config.Smtp.Port, s.config.Smtp.Sender, s.config.Smtp.Password) + ers := smtpSender.Send(message) + if ers != nil { + log.Print(ers) + } + + // + // log.Printf("message at offset %d: %s = %s\n", m.Offset, string(m.Key), string(m.Value)) + log.Printf("send code %v to %v", amsg.Code, amsg.Email) } } diff --git a/internal/smtp/attachment.go b/internal/smtp/attachment.go new file mode 100644 index 0000000..a0c6f42 --- /dev/null +++ b/internal/smtp/attachment.go @@ -0,0 +1,50 @@ +package postman + +import ( + "bytes" + "encoding/base64" + "os" +) + +// Вложение +type Attachment struct { + name string + contentType string + withFile bool +} + +func NewAttachment(file string) Attachment { + return Attachment{ + name: file, + contentType: "application/octet-stream", + withFile: true, + } +} + +// Импорт файла в тело письма +func (a *Attachment) WriteFile(buffer *bytes.Buffer) error { + file, err := os.ReadFile(a.name) + if err != nil { + return err + } + + // + payload := make([]byte, base64.StdEncoding.EncodedLen(len(file))) + base64.StdEncoding.Encode(payload, file) + + // + buffer.WriteString("\r\n") + + for index, line := 0, len(payload); index < line; index++ { + buffer.WriteByte(payload[index]) + + if (index+1)%76 == 0 { + _, era := buffer.WriteString("\r\n") + if era != nil { + return era + } + } + } + + return nil +} diff --git a/internal/smtp/message.go b/internal/smtp/message.go new file mode 100644 index 0000000..a33615f --- /dev/null +++ b/internal/smtp/message.go @@ -0,0 +1,35 @@ +package postman + +// Почтовое сообщения предназначенное для отправки +type Message struct { + to []string + subject string + body string + contentType string + attachments []Attachment +} + +func NewMessage(subject string, body string) Message { + return Message{ + to: []string{}, + subject: subject, + contentType: "text/plain;charset=utf8", + attachments: []Attachment{}, + body: body, + } +} + +// Пополнение списка получателей +func (m *Message) AppendRecipient(recipient string) error { + m.to = append(m.to, recipient) + + return nil +} + +// Пополнение списка вложений +func (m *Message) AppendAttachment(file string) error { + a := NewAttachment(file) + m.attachments = append(m.attachments, a) + + return nil +} diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go new file mode 100644 index 0000000..ab933a8 --- /dev/null +++ b/internal/smtp/smtp.go @@ -0,0 +1,87 @@ +package postman + +import ( + "bytes" + "errors" + "net/smtp" + "strconv" + "strings" + "time" +) + +// SMTP сервис +type Service struct { + user string + password string + host string + port int +} + +func NewService(host string, port int, sender string, password string) *Service { + return &Service{ + user: sender, + password: password, + host: host, + port: port, + } +} + +// Отправка письма +func (s *Service) Send(message Message) error { + if len(message.to) < 1 { + return errors.New("empty list of recipients") + } + + // + auth := smtp.PlainAuth("", s.user, s.password, s.host) + + // + buffer := bytes.NewBuffer(nil) + boundary := "GoBoundary" + headery := "" + + // header + header := make(map[string]string) + header["From"] = s.user + + header["To"] = message.to[0] + header["Cc"] = strings.Join(message.to[1:], ";") + header["Subject"] = message.subject + header["Content-Type"] = "multipart/mixed;boundary=" + boundary + header["Mime-Version"] = "1.0" + header["Date"] = time.Now().String() + + for key, value := range header { + headery += key + ":" + value + "\r\n" + } + + buffer.WriteString(headery + "\r\n") + + // body + body := "\r\n--" + boundary + "\r\n" + body += "Content-Type:" + message.contentType + "\r\n" + body += "\r\n" + message.body + "\r\n" + + buffer.WriteString(body) + + // attachments + for _, x := range message.attachments { + if x.withFile { + attachment := "\r\n--" + boundary + "\r\n" + attachment += "Content-Transfer-Encoding:base64\r\n" + attachment += "Content-Disposition:attachment\r\n" + attachment += "Content-Type:" + x.contentType + ";name=\"" + x.name + "\"\r\n" + + // + buffer.WriteString(attachment) + + // + x.WriteFile(buffer) + } + } + + buffer.WriteString("\r\n--" + boundary + "--") + + // Sending message + return smtp.SendMail(s.host+":"+strconv.Itoa(s.port), auth, s.user, message.to, buffer.Bytes()) +}