完善多个通道

This commit is contained in:
Yun
2024-11-20 19:42:07 +08:00
parent 868a5106e6
commit dc49e97912
33 changed files with 1861 additions and 191 deletions
+250
View File
@@ -0,0 +1,250 @@
package aliyun
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"code.yun.ink/pkg/mailx/interfaces"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
dm20151123 "github.com/alibabacloud-go/dm-20151123/v2/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/yuninks/loggerx"
)
type Aliyun struct {
interfaces.DefaultEmail
client *dm20151123.Client
params *interfaces.EmialConfigDataAliyun
logger loggerx.LoggerInterface
}
func (l *Aliyun) InitEmail(ctx context.Context, params interfaces.EmailConfigData, logger loggerx.LoggerInterface) (interfaces.Email, error) {
l.logger.Infof(ctx, "params:%+v", params)
if params.Aliyun == nil {
return nil, errors.New("not aliyun")
}
l.logger.Infof(ctx, "params:%+v", params.Aliyun)
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html。
config := &openapi.Config{
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
AccessKeyId: tea.String(params.Aliyun.AccessId),
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
AccessKeySecret: tea.String(params.Aliyun.AccessKey),
}
if params.Aliyun.Endpoint == "" {
params.Aliyun.Endpoint = "dm.aliyuncs.com"
}
// Endpoint 请参考 https://api.aliyun.com/product/Dm
config.Endpoint = tea.String(params.Aliyun.Endpoint)
result, err := dm20151123.NewClient(config)
if err != nil {
return nil, err
}
return &Aliyun{
client: result,
params: params.Aliyun,
logger: logger,
}, nil
}
func (l *Aliyun) Send(ctx context.Context, params interfaces.Message) error {
if l.client == nil {
return errors.New("client no init")
}
if len(params.To) > 100 {
return errors.New("最多 100 个地址")
}
if l.params.AccountName == "" {
return errors.New("AccountName 必填")
}
toAddress := strings.Join(params.To, ",")
singleSendMailRequest := &dm20151123.SingleSendMailRequest{}
singleSendMailRequest.AccountName = tea.String(l.params.AccountName)
singleSendMailRequest.ToAddress = tea.String(toAddress) // 目标地址,多个 email 地址可以用逗号分隔,最多 100 个地址(支持邮件组)。
singleSendMailRequest.Subject = tea.String(params.Subject)
singleSendMailRequest.HtmlBody = tea.String(params.Body)
singleSendMailRequest.AddressType = tea.Int32(0) // 地址类型。取值:0:为随机账号1:为发信地址
if params.ReplyTo != "" {
singleSendMailRequest.ReplyToAddress = tea.Bool(false)
singleSendMailRequest.ReplyAddress = tea.String(params.ReplyTo)
} else {
singleSendMailRequest.ReplyToAddress = tea.Bool(true)
}
runtime := &util.RuntimeOptions{}
tryErr := func() (_e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
// 复制代码运行请自行打印 API 的返回值
resp, err := l.client.SingleSendMailWithOptions(singleSendMailRequest, runtime)
by, _ := json.Marshal(resp)
fmt.Printf("resp:%+v err:%+v", string(by), err)
l.logger.Infof(ctx, "resp:%+v err:%+v", resp, err)
if err != nil {
return err
}
return nil
}()
if tryErr != nil {
l.logger.Errorf(ctx, "err:%+v", tryErr)
return tryErr
// var error = &tea.SDKError{}
// if _t, ok := tryErr.(*tea.SDKError); ok {
// error = _t
// } else {
// error.Message = tea.String(tryErr.Error())
// }
// // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// // 错误 message
// fmt.Println(tea.StringValue(error.Message))
// // 诊断地址
// var data interface{}
// d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
// d.Decode(&data)
// if m, ok := data.(map[string]interface{}); ok {
// recommend, _ := m["Recommend"]
// fmt.Println("recommend", recommend)
// }
// _, _err := util.AssertAsString(error.Message)
// if _err != nil {
// return _err
// }
}
return nil // 实现具体的 Aliyun 发送方法
// 如:return aliyunSDK.SendMail(params)
}
// 同步状态
func (l *Aliyun) SyncStatus(ctx context.Context) (resp []interfaces.EmailSendRecord, err error) {
start := ""
// 一次同步一天的数据
for {
list, next, err := l.getSendStatus(ctx, start)
l.logger.Infof(ctx, "list:%+v next:%+v err:%+v", list, next, err)
if err != nil {
return nil, err
}
for _, val := range list {
t, _ := time.ParseInLocation("2006-01-02T15:04Z", tea.StringValue(val.LastUpdateTime), time.Local)
// 0:成功 2:无效地址 3:垃圾邮件 4:失败
record := interfaces.EmailSendRecord{
AccountName: tea.StringValue(val.AccountName),
UpdateTime: t.UnixMilli(),
ToUser: tea.StringValue(val.ToAddress),
Subject: tea.StringValue(val.Subject),
ErrorMessage: tea.StringValue(val.Message),
}
switch tea.Int32Value(val.Status) {
case 0:
record.Status = interfaces.EmailSendStatusSuccess
case 2:
record.Status = interfaces.EmailSendStatusInvalidAddress
case 3:
record.Status = interfaces.EmailSendStatusSpam
case 4:
record.Status = interfaces.EmailSendStatusFailed
default:
record.Status = interfaces.EmailSendStatusUnknown
}
resp = append(resp, record)
}
if next == nil || len(*next) == 0 {
break
}
start = *next
}
return resp, nil
}
func (l *Aliyun) getSendStatus(ctx context.Context, start string) (list []*dm20151123.SenderStatisticsDetailByParamResponseBodyDataMailDetail, nextStart *string, err error) {
now := time.Now().Local()
senderStatisticsDetailByParamRequest := &dm20151123.SenderStatisticsDetailByParamRequest{
StartTime: tea.String(now.AddDate(0, 0, -1).Format("2006-01-02 15:04")),
EndTime: tea.String(now.Format("2006-01-02 15:04")),
Length: tea.Int32(100),
}
if start != "" {
senderStatisticsDetailByParamRequest.NextStart = tea.String(start)
}
runtime := &util.RuntimeOptions{}
tryErr := func() (_e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
// 复制代码运行请自行打印 API 的返回值
resp, _err := l.client.SenderStatisticsDetailByParamWithOptions(senderStatisticsDetailByParamRequest, runtime)
if _err != nil {
l.logger.Errorf(ctx, "resp:%+v err:%+v", resp, _err)
return _err
}
if resp == nil || resp.Body == nil || resp.Body.Data == nil {
return errors.New("resp.Body.Data is nil")
}
list = resp.Body.Data.MailDetail
nextStart = resp.Body.NextStart
return nil
}()
if tryErr != nil {
l.logger.Errorf(ctx, "err:%+v", tryErr)
return nil, nil, tryErr
// var error = &tea.SDKError{}
// if _t, ok := tryErr.(*tea.SDKError); ok {
// error = _t
// } else {
// error.Message = tea.String(tryErr.Error())
// }
// // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// // 错误 message
// fmt.Println(tea.StringValue(error.Message))
// // 诊断地址
// var data interface{}
// d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
// d.Decode(&data)
// if m, ok := data.(map[string]interface{}); ok {
// recommend, _ := m["Recommend"]
// fmt.Println("recommend:", recommend)
// }
// _, _err := util.AssertAsString(error.Message)
// if _err != nil {
// l.logger.Errorf(ctx, "resp:%+v err:%+v", error.Message, _err)
// return nil, nil, _err
// }
}
return list, nextStart, nil
}
+82
View File
@@ -0,0 +1,82 @@
package aliyun_test
import (
"context"
"fmt"
"os"
"testing"
"code.yun.ink/pkg/mailx/aliyun"
"code.yun.ink/pkg/mailx/interfaces"
"github.com/yuninks/loggerx"
)
func TestSend(t *testing.T) {
aliyun := &aliyun.Aliyun{}
ctx := context.Background()
logger := loggerx.NewLogger(ctx)
ali, err := aliyun.InitEmail(ctx, interfaces.EmailConfigData{
Aliyun: &interfaces.EmialConfigDataAliyun{
AccessId: "LTAI5tEQ8L8fmDir8udD3CFr",
AccessKey: "llg9M1U56s2SW5PuerlKPvTB1xYhn0",
Endpoint: "dm.aliyuncs.com",
AccountName: "test@email.aisz.org", //"test@email.aisz.org",
ReplyAddress: "287852692@qq.com",
},
}, logger)
if err != nil {
t.Fatal(err)
}
by, err := os.ReadFile("../../../static/email/msg/zh_Hant.html")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(by))
err = ali.Send(ctx, interfaces.Message{
To: []string{"huangxinyun@dreaminglife.cn"},
Subject: "测试主题",
Body: string(by),
})
if err != nil {
t.Fatal("resp err", err)
}
t.Log("send success")
}
// func TestSyncStatus(t *testing.T) {
// aliyun := &aliyun.Aliyun{}
// ctx := context.Background()
// global.Logger = loggerx.NewLogger(ctx)
// ali, err := aliyun.InitEmail(ctx, interfaces.EmailConfigData{
// Aliyun: &interfaces.EmialConfigDataAliyun{
// AccessId: "LTAI5tEQ8L8fmDir8udD3CFr",
// AccessKey: "llg9M1U56s2SW5PuerlKPvTB1xYhn0",
// Endpoint: "dm.aliyuncs.com",
// AccountName: "test@email.aisz.org",
// ReplyAddress: "287852692@qq.com",
// },
// })
// if err != nil {
// t.Fatal(err)
// }
// list, err := ali.SyncStatus(ctx)
// if err != nil {
// t.Fatal(err)
// }
// _ = list
// // global.Logger.Infof(ctx, "status: %v", list)
// t.Log("status:", list)
// }
+160
View File
@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- <link rel="stylesheet" href="./assets/css/index.css" /> -->
<style>
* {
padding: 0;
margin: 0;
}
img {
display: block;
}
#app {
width: 480px;
margin: 0 auto;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.header {
width: 100%;
height: 96px;
background-color: #1b1b22;
}
.header .logo {
width: 95px;
}
.code-main {
padding: 22px 24px;
}
.code-tips {
color: #5b5b65;
font-size: 12px;
margin-bottom: 22px;
}
.code-content {
color: #1b1b22;
font-weight: bold;
font-size: 28px;
margin-bottom: 61px;
}
.team-tips {
font-size: 11px;
color: #5b5b65;
margin-bottom: 59px;
}
/* .team-tips::marker {
margin-right: 2px !important;
} */
.contact-us {
color: #d1d1d1;
padding-top: 24px;
border-top: solid 0.5px #d1d1d1;
}
.contact-us-info {
text-align: center;
font-size: 10px;
color: #838384;
padding: 0 40px;
line-height: 18px;
}
.contact-tel {
color: #838384;
text-align: center;
font-size: 10px;
}
.contact-tel-number {
color: #1b1b22;
}
.contact-created-version {
color: #838384;
text-align: center;
margin-top: 7px;
font-size: 10px;
}
.qr-code {
width: 64px;
margin: 9px auto 15px;
}
/* 异常邮件 */
.exception-title {
color: #1b1b22;
font-size: 16px;
font-weight: bold;
margin-bottom: 24px;
}
.exception-content {
line-height: 24px;
color: #5b5b65;
font-size: 12px;
}
.exception-ignore {
margin-top: 45px;
color: #5b5b65;
font-size: 12px;
}
.exception-team-tips {
margin-top: 85px;
margin-bottom: 20px;
font-size: 11px;
}
</style>
</head>
<body>
<div id="app">
<header class="header flex-center">
<img src="https://abpay-pub.s3.ap-northeast-1.amazonaws.com/1077_1727166836625.png" alt="" class="logo" />
</header>
<main class="code-main">
<div class="code-tips">
以下是你的 ABPAY 验证码。请注意,该验证码将在 {{.shijian}} 分钟后过期, 请尽快完成验证。
</div>
<div class="code-content">{{.verify_code}}</div>
<li class="team-tips">ABpay开发者团队服务</li>
<section class="contact-us">
<div class="contact-us-info">
本邮件由系统自动发出请勿回复,如需要了解更多服务,欢迎访问ABpay官方网
</div>
<div class="contact-us-info">还可以通以下方式联系我们</div>
<img src="https://abpay-pub.s3.ap-northeast-1.amazonaws.com/1077_1727166836625.png" alt="" class="qr-code" />
<div class="contact-tel">
客服电话:<span class="contact-tel-number">400-278-2890</span>
</div>
<div class="contact-created-version">2024 ABpay.com.cn . All rightes reserved</div>
</section>
</main>
</div>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
[info]{"time":"2024-09-25 14:15:35.045218","file":"/aliyun.go:94","func":"func1","gid":"57","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 06:15:34 GMT\",\n \"etag\": \"8f9dfw6sRErJKrt+hoTBWhQ1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"DC2492CF-2A5B-56CB-A605-AEEB8A39E3B4\",\n \"x-acs-trace-id\": \"e616ffedeb4d2a0be8ef74bf36aa7d03\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109262317450\",\n \"RequestId\": \"DC2492CF-2A5B-56CB-A605-AEEB8A39E3B4\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 14:16:59.676574","file":"/aliyun.go:94","func":"func1","gid":"53","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 06:16:59 GMT\",\n \"etag\": \"8XWguuFK0kv64XgUSTRDjQQ1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"396FAEEA-EF53-50D9-86F6-4229E0081279\",\n \"x-acs-trace-id\": \"a70ec22bb9fd7218476ef4363242e166\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109175592530\",\n \"RequestId\": \"396FAEEA-EF53-50D9-86F6-4229E0081279\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 14:17:35.907625","file":"/aliyun.go:94","func":"func1","gid":"52","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 06:17:35 GMT\",\n \"etag\": \"8eICx6U2H3qOv6K8RGRRP7A1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"DC8695D8-D326-52A3-B73B-F916F386B2CF\",\n \"x-acs-trace-id\": \"c826dc3f513763620ba59f048d9d8e71\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109435113590\",\n \"RequestId\": \"DC8695D8-D326-52A3-B73B-F916F386B2CF\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 14:22:38.398287","file":"/aliyun.go:94","func":"func1","gid":"50","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 06:22:38 GMT\",\n \"etag\": \"89EbQ3zPb8RIEJbK53hyAgQ1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"801C56E0-1498-5B09-AF52-DE958E9DB834\",\n \"x-acs-trace-id\": \"1a3d50fec251c83edb55bf48dc431eff\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109262322078\",\n \"RequestId\": \"801C56E0-1498-5B09-AF52-DE958E9DB834\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 17:20:19.656969","file":"/aliyun.go:94","func":"func1","gid":"53","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 09:20:19 GMT\",\n \"etag\": \"8+qybIN7HNz96KPKbfNwipw1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"A23D5487-3B08-5F91-8890-C98EDD8A0902\",\n \"x-acs-trace-id\": \"1c20039869fc8fc0a71da2c288a059d5\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109348886893\",\n \"RequestId\": \"A23D5487-3B08-5F91-8890-C98EDD8A0902\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 17:20:54.628471","file":"/aliyun.go:94","func":"func1","gid":"50","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 09:20:54 GMT\",\n \"etag\": \"8QixS/p2+Z7xHHIZ/VpSwtQ1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"61555477-0B6F-5E19-B3C1-CAB53CC31AFE\",\n \"x-acs-trace-id\": \"eabe03d20719d8258e78779c4bd37c88\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109175763220\",\n \"RequestId\": \"61555477-0B6F-5E19-B3C1-CAB53CC31AFE\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 17:21:22.460201","file":"/aliyun.go:94","func":"func1","gid":"16","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 09:21:22 GMT\",\n \"etag\": \"87/gA8cEkLTFvPqJfgP9K1A1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"76FDF640-FBB0-5B0C-827E-70B92FF62C82\",\n \"x-acs-trace-id\": \"4ad06f6435167d8ccb7e36072cd6acb9\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109320087021\",\n \"RequestId\": \"76FDF640-FBB0-5B0C-827E-70B92FF62C82\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 17:21:47.550484","file":"/aliyun.go:94","func":"func1","gid":"9","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 09:21:47 GMT\",\n \"etag\": \"8biiNi4vpUJzYstObqFPfxQ1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"0097D9F9-5143-5B06-B211-50C6F0299471\",\n \"x-acs-trace-id\": \"e1d69216717b0323e90275f6c460c320\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109377685846\",\n \"RequestId\": \"0097D9F9-5143-5B06-B211-50C6F0299471\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 17:22:23.999040","file":"/aliyun.go:94","func":"func1","gid":"11","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 09:22:23 GMT\",\n \"etag\": \"8nQth23LiOl8DONamRuHNvg1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"51D36408-D2DB-5D49-AB15-C515C1483B2C\",\n \"x-acs-trace-id\": \"8c3710e125841396fd31b65c69fdd265\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109377686445\",\n \"RequestId\": \"51D36408-D2DB-5D49-AB15-C515C1483B2C\"\n }\n} err:\u003cnil\u003e"]}
[info]{"time":"2024-09-25 17:22:45.785559","file":"/aliyun.go:94","func":"func1","gid":"50","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Wed, 25 Sep 2024 09:22:45 GMT\",\n \"etag\": \"8ntJrjXOJMSTVmROovNQlSw1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"AB250371-0BF3-5627-92FB-4924E7127958\",\n \"x-acs-trace-id\": \"a8f4491d14db90fa47134b7f428bcb7e\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109262490484\",\n \"RequestId\": \"AB250371-0BF3-5627-92FB-4924E7127958\"\n }\n} err:\u003cnil\u003e"]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
View File
@@ -0,0 +1,4 @@
[info]{"time":"2024-09-30 11:31:02.535008","file":"/aliyun.go:26","func":"InitEmail","gid":"53","content":["params:{Smtp:\u003cnil\u003e Aws:\u003cnil\u003e Aliyun:0xc0000a3310 Mailgun:\u003cnil\u003e}"]}
[info]{"time":"2024-09-30 11:31:02.541008","file":"/aliyun.go:30","func":"InitEmail","gid":"53","content":["params:\u0026{AccessId:LTAI5tEQ8L8fmDir8udD3CFr AccessKey:llg9M1U56s2SW5PuerlKPvTB1xYhn0 Endpoint:dm.aliyuncs.com AccountName:test@email.aisz.org ReplyAddress:287852692@qq.com}"]}
[info]{"time":"2024-09-30 11:31:03.017155","file":"/aliyun.go:96","func":"func1","gid":"53","content":["resp:{\n \"headers\": {\n \"access-control-allow-origin\": \"*\",\n \"access-control-expose-headers\": \"*\",\n \"connection\": \"keep-alive\",\n \"content-length\": \"81\",\n \"content-type\": \"application/json;charset=utf-8\",\n \"date\": \"Mon, 30 Sep 2024 03:31:02 GMT\",\n \"etag\": \"8f4BKIO+S82BtzQYSJDv3Sg1\",\n \"keep-alive\": \"timeout=25\",\n \"x-acs-request-id\": \"48DE3F8A-AB82-5A9A-9B73-FD422B247391\",\n \"x-acs-trace-id\": \"80bfb0b978c2b82ac64dd59459485ab2\"\n },\n \"statusCode\": 200,\n \"body\": {\n \"EnvId\": \"600000109324064279\",\n \"RequestId\": \"48DE3F8A-AB82-5A9A-9B73-FD422B247391\"\n }\n} err:\u003cnil\u003e"]}
+82
View File
@@ -0,0 +1,82 @@
package aws
import (
"context"
"errors"
"code.yun.ink/pkg/mailx/interfaces"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/yuninks/loggerx"
)
// 不支持变更发信人(必须配置好)
type Aws struct {
interfaces.DefaultEmail
params *interfaces.EmailConfigDataAws
logger loggerx.LoggerInterface
}
func (l *Aws) InitEmail(ctx context.Context, params interfaces.EmailConfigData, logger loggerx.LoggerInterface) (interfaces.Email, error) {
l.logger.Infof(ctx, "params:%+v", params)
if params.Aws == nil {
return nil, errors.New("not aws")
}
l.logger.Infof(ctx, "params:%+v", params.Aws)
if params.Aws.Region == "" {
params.Aws.Region = "ap-northeast-1"
}
return &Aws{
params: params.Aws,
}, nil
}
func (l *Aws) Send(ctx context.Context, params interfaces.Message) error {
if l.params == nil {
return errors.New("not init")
}
// 配置AWS认证信息
config := aws.Config{
Region: aws.String(l.params.Region), // 设置你的AWS区域
Credentials: credentials.NewStaticCredentials(l.params.AccessId, l.params.AccessSecret, ""),
}
// 创建AWS会话
sess := session.Must(session.NewSession(&config))
// 创建SES客户端
svc := ses.New(sess)
toAddress := []*string{}
for _, val := range params.To {
toAddress = append(toAddress, aws.String(val))
}
// 使用SES服务发送邮件
_, err := svc.SendEmail(&ses.SendEmailInput{
Destination: &ses.Destination{
ToAddresses: toAddress,
},
Message: &ses.Message{
Body: &ses.Body{
Html: &ses.Content{
Data: aws.String(params.Body),
Charset: aws.String("UTF-8"),
},
},
Subject: &ses.Content{
Data: aws.String(params.Subject),
Charset: aws.String("UTF-8"),
},
},
Source: aws.String(l.params.Sender),
})
// svc.SendRawEmail()
return err
}
+52
View File
@@ -0,0 +1,52 @@
package aws_test
import (
"context"
"testing"
"code.yun.ink/pkg/mailx/aws"
"code.yun.ink/pkg/mailx/interfaces"
"github.com/yuninks/loggerx"
)
// https://ap-northeast-1.console.aws.amazon.com/ses/home?region=ap-northeast-1#/identities
func TestSend(t *testing.T) {
// email:
// #区域
// AwsRegion: "ap-northeast-1"
// #秘钥ID
// AwsAccessKeyId: "AKIAU6GD3MNRHKR4RZG5"
// #秘钥
// AwsSecretAccessKey: "GSdGuFbZlcpVHMODlqeIKr07R/BdTBGeurq0s+4l"
// #发件人
// Source: "chenlihan@dreaminglife.cn"
ctx := context.Background()
logger := loggerx.NewLogger(ctx)
mail := aws.Aws{}
ini, err := mail.InitEmail(ctx, interfaces.EmailConfigData{
Aws: &interfaces.EmailConfigDataAws{
AccessId: "AKIAU6GD3MNRHKR4RZG5",
AccessSecret: "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",
Region: "ap-northeast-1",
Sender: "chenlihan@dreaminglife.cn",
},
}, logger)
if err != nil {
t.Fatal(err)
}
err = ini.Send(ctx, interfaces.Message{
Form: "chenlihan@dreaminglife.cn",
To: []string{"huangxinyun@dreaminglife.cn"},
Body: "Hello",
Subject: "主题",
})
if err != nil {
t.Fatal(err)
}
t.Log("send success")
}
+3
View File
@@ -0,0 +1,3 @@
[info]{"time":"2024-09-27 18:32:19.629859","file":"/aws.go:23","func":"InitEmail","gid":"33","content":["params:{Smtp:\u003cnil\u003e Aws:0xc000698080 Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
[info]{"time":"2024-09-27 18:32:30.207798","file":"/aws.go:23","func":"InitEmail","gid":"28","content":["params:{Smtp:\u003cnil\u003e Aws:0xc000565400 Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
+3
View File
@@ -0,0 +1,3 @@
[info]{"time":"2024-09-30 11:31:01.195981","file":"/aws.go:23","func":"InitEmail","gid":"44","content":["params:{Smtp:\u003cnil\u003e Aws:0xc00078c080 Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
[info]{"time":"2024-09-30 11:31:01.201642","file":"/aws.go:27","func":"InitEmail","gid":"44","content":["params:\u0026{AccessId:AKIAU6GD3MNRHKR4RZG5 AccessSecret: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 Region:ap-northeast-1 Sender:chenlihan@dreaminglife.cn}"]}
+53 -4
View File
@@ -1,10 +1,59 @@
module code.yun.ink/pkg/mailx
go 1.20
go 1.22
require github.com/PuerkitoBio/goquery v1.9.2
toolchain go1.22.6
require (
github.com/andybalholm/cascadia v1.3.2 // indirect
golang.org/x/net v0.24.0 // indirect
github.com/PuerkitoBio/goquery v1.9.2
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
github.com/alibabacloud-go/dm-20151123/v2 v2.2.3
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
github.com/aws/aws-sdk-go v1.55.5
github.com/mailgun/mailgun-go/v4 v4.18.5
github.com/yuninks/loggerx v1.0.12
)
require (
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailgun/errors v0.3.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+286
View File
@@ -1,40 +1,326 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/ahmetb/go-linq v3.0.0+incompatible h1:qQkjjOXKrKOTy83X8OpRmnKflXKQIL/mC/gMVVDMhOA=
github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/dm-20151123/v2 v2.2.3 h1:nEq/9OGxV2yiB3pP100TSn1Av4k4QJvRTu1pGkyhUS4=
github.com/alibabacloud-go/dm-20151123/v2 v2.2.3/go.mod h1:82WXJL+riPgj7BxYHwaLmVIMdLaMW0X8Azh+GZGjESQ=
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mailgun/errors v0.3.0 h1:g8R8lodkwqk5WIVMAClyUqt0PSd5JTVgobB+H7C2sLs=
github.com/mailgun/errors v0.3.0/go.mod h1:+ltknP+jhv3gZ1StKY6ugoQECcPxDCaSdmYesqTZcLQ=
github.com/mailgun/mailgun-go/v4 v4.18.5 h1:wZnTutW/fzxNyJVBMa6O4LLGW/hO+IGfKpuqBT4nbVs=
github.com/mailgun/mailgun-go/v4 v4.18.5/go.mod h1:+d4FCswFAukgYc1XtKK2IxOYaVxjVm8AN2z/5TBiT8M=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuninks/loggerx v1.0.12 h1:5XxhSo5bQZEjLRdJ9FPmj+uS018s1meo/9OhG9y2hUg=
github.com/yuninks/loggerx v1.0.12/go.mod h1:+QFoywQ1ICh4v40zj6OHM8GBZHEqV0yvRbkZjZUe2o4=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"github.com/PuerkitoBio/goquery"
)
// 解析HTML资源,响应资源链接
// html路径替换
func ParseHtmlResource(html string) ([]string, error) {
resp := []string{}
+1 -2
View File
@@ -2,8 +2,7 @@ package mailx_test
import (
"testing"
"code.yun.ink/pkg/mailx"
"wallet-pay-api/pkg/mailx"
)
func TestParseHtmlResource(t *testing.T) {
+96
View File
@@ -0,0 +1,96 @@
package interfaces
import (
"context"
"errors"
"github.com/yuninks/loggerx"
)
type Email interface {
InitEmail(ctx context.Context, params EmailConfigData,logger loggerx.LoggerInterface) (Email, error)
// Send 发送邮件
Send(ctx context.Context, params Message) error
}
type EmailConfigData struct {
Smtp *EmailConfigDataSmtp `json:"smtp,omitempty"` // smtp
Aws *EmailConfigDataAws `json:"aws,omitempty"` // 亚马逊
Aliyun *EmialConfigDataAliyun `json:"aliyun,omitempty"` // 阿里云
Mailgun *EmialConfigDataMailgun `json:"mailgun,omitempty"` // mailgun
}
type EmialConfigDataMailgun struct {
ApiKey string `json:"api_key"` // mailgun api key
Domain string `json:"domain"` // mailgun domain
Sender string `json:"sender"` // 发件人
}
type EmailConfigDataSmtp struct {
Username string `json:"username"` // 邮箱账号
Password string `json:"password"` // 授权码
Host string `json:"host"` // SMTP 服务器【默认smtpdm.aliyun.com】
Port string `json:"port"` // 发信端口
ReplyTo string `json:"reply_to"` // 【选填】回复地址
// From string `json:"from"` // 【选填】阿里云邮箱发件人
}
type EmailConfigDataAws struct {
AccessId string `json:"access_id"` // 亚马逊AccessId
AccessSecret string `json:"access_secret"` // 亚马逊AccessSecret
Region string `json:"region"` // 亚马逊Region
Sender string `json:"sender"` // 亚马逊发件人
}
type EmialConfigDataAliyun struct {
AccessId string `json:"access_id"` // 阿里云AccessId
AccessKey string `json:"access_key"` // 阿里云AccessKey
Endpoint string `json:"endpoint"` // 区域 默认dm.aliyuncs.com
AccountName string `json:"account_name"` // 账号名
ReplyAddress string `json:"reply_address"` // 邮件回复地址
}
type Message struct {
Form string
To []string
Cc []string
Bcc []string
Subject string
Body string
ReplyTo string
Attachment []MessageAttachment // 附件
}
type MessageAttachment struct {
Content string
ContentType string
}
type EmailSendRecord struct {
AccountName string // 发件人
UpdateTime int64 // 毫秒时间戳
Status EmailSendStatus // 状态
ToUser string // 收件人
Subject string // 邮件主题
ErrorMessage string // 错误信息
}
type EmailSendStatus int
const (
EmailSendStatusUnknown EmailSendStatus = 0
EmailSendStatusSuccess EmailSendStatus = 1
EmailSendStatusInvalidAddress EmailSendStatus = 2
EmailSendStatusSpam EmailSendStatus = 3
EmailSendStatusFailed EmailSendStatus = 4
)
type DefaultEmail struct{}
func (l *DefaultEmail) InitEmail(ctx context.Context, params EmailConfigData,logger loggerx.LoggerInterface) (Email, error) {
return &DefaultEmail{}, errors.New("not implemented")
}
func (l *DefaultEmail) Send(ctx context.Context, params Message) error {
return errors.New("not implemented")
}
+2
View File
@@ -0,0 +1,2 @@
[error]{"time":"2024-09-30 11:31:04.522647","file":"/mailgun.go:40","func":"Send","gid":"47","content":["Could not send email: UnexpectedResponseError URL=https://api.mailgun.net/v3/sandboxd045b2448880433785c34f72a7fd0d45.mailgun.org/messages ExpectedOneOf=[]int{200, 202, 204} Got=403 Error: {\"message\":\"Domain sandboxd045b2448880433785c34f72a7fd0d45.mailgun.org is not allowed to send: Please activate your Mailgun account. Check your inbox or log in to your control panel to resend the activation email.\"}\n, resp message: , id: "]}
+2
View File
@@ -0,0 +1,2 @@
[info]{"time":"2024-09-30 11:31:03.950305","file":"/mailgun.go:23","func":"InitEmail","gid":"47","content":["params:\u0026{ApiKey:b87ece56257aa282dd3c809f683c60e5-32a0fef1-6327df27 Domain:sandboxd045b2448880433785c34f72a7fd0d45.mailgun.org Sender:zhaoyang@dreaminglife.cn}"]}
+46
View File
@@ -0,0 +1,46 @@
package mailgun
import (
"context"
"errors"
"code.yun.ink/pkg/mailx/interfaces"
"github.com/mailgun/mailgun-go/v4"
"github.com/yuninks/loggerx"
)
type MailGun struct {
interfaces.DefaultEmail
params *interfaces.EmialConfigDataMailgun
mg *mailgun.MailgunImpl
logger loggerx.LoggerInterface
}
func (l *MailGun) InitEmail(ctx context.Context, params interfaces.EmailConfigData, logger loggerx.LoggerInterface) (interfaces.Email, error) {
if params.Mailgun == nil {
return nil, errors.New("not mailgun")
}
l.logger.Infof(ctx, "params:%+v", params.Mailgun)
mg := mailgun.NewMailgun(params.Mailgun.Domain, params.Mailgun.ApiKey)
return &MailGun{
params: params.Mailgun,
mg: mg,
}, nil
}
func (l *MailGun) Send(ctx context.Context, params interfaces.Message) error {
if l.params == nil {
return errors.New("not init")
}
message := l.mg.NewMessage(l.params.Sender, params.Subject, params.Body, params.To...)
resp, id, err := l.mg.Send(ctx, message)
if err != nil {
l.logger.Errorf(ctx, "Could not send email: %v, resp message: %s, id: %s", err, resp, id)
return err
}
return err
}
+45
View File
@@ -0,0 +1,45 @@
package mailgun_test
import (
"context"
"testing"
"code.yun.ink/pkg/mailx/interfaces"
"code.yun.ink/pkg/mailx/mailgun"
"github.com/yuninks/loggerx"
)
var (
apikey = "b87ece56257aa282dd3c809f683c60e5-32a0fef1-6327df27"
domain = "sandboxd045b2448880433785c34f72a7fd0d45.mailgun.org"
sender = "zhaoyang@dreaminglife.cn"
)
func TestSendEmail(t *testing.T) {
gun := &mailgun.MailGun{}
ctx := context.Background()
logger := loggerx.NewLogger(ctx)
ini, err := gun.InitEmail(ctx, interfaces.EmailConfigData{
Mailgun: &interfaces.EmialConfigDataMailgun{
ApiKey: apikey,
Domain: domain,
Sender: sender,
},
}, logger)
if err != nil {
t.Fatal(err)
}
err = ini.Send(ctx, interfaces.Message{
To: []string{"995116474@qq.com"},
Subject: "test mail",
Body: "Hello",
})
if err != nil {
t.Fatal(err)
}
t.Log("send success")
}
+30 -171
View File
@@ -1,182 +1,41 @@
package mailx
import (
"bytes"
"encoding/base64"
"fmt"
"net/smtp"
"os"
"path"
"strings"
"time"
"context"
"errors"
"code.yun.ink/pkg/mailx/aliyun"
"code.yun.ink/pkg/mailx/aws"
"code.yun.ink/pkg/mailx/interfaces"
"code.yun.ink/pkg/mailx/mailgun"
"code.yun.ink/pkg/mailx/smtp"
)
// 邮件发送的封装
// 1. 支持文本
// 2. 支持文件
var platform map[consts.Platform3rdType]interfaces.Email
// 注册
func init() {
platform = make(map[consts.Platform3rdType]interfaces.Email)
// 阿里
platform[consts.Platform3rdTypeAliyun] = &aliyun.Aliyun{}
// AWS
platform[consts.Platform3rdTypeAws] = &aws.Aws{}
// Smtp
platform[consts.Platform3rdTypeSmtp] = &smtp.Smtp{}
// mailgun
platform[consts.Platform3rdTypeMailgun] = &mailgun.MailGun{}
type Mailx struct {
user string
password string
host string
port string
auth smtp.Auth
}
type Attachment struct {
Name string
ContentType string
WithFile bool
func GetPlatform(ctx context.Context, plat consts.Platform3rdType) (interfaces.Email, error) {
iplat, ok := platform[plat]
if ok {
return iplat, nil
}
type Message struct {
Form string
To []string
Cc []string
Bcc []string
Subject string
Body string
// ContentType string
ReplyTo string
Attachment []Attachment
}
func NewMailx(user, password, host, port string) *Mailx {
m := &Mailx{
user: user,
password: password,
host: host,
port: port,
}
m.auth = smtp.PlainAuth("", user, password, host)
return m
}
func (m *Mailx) Send(message Message) error {
// .Auth()
buffer := bytes.NewBuffer(nil)
boundary := "YunBoundaryYun"
Header := make(map[string]string)
// Header["From"] = "BOP<" + message.Form + ">"
Header["From"] = m.user
if len(message.To) > 0 {
str := ""
for _, val := range message.To {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["To"] = strings.Trim(str, ",")
// Header["To"] = strings.Join(message.To, ",")
}
if len(message.Cc) > 0 {
str := ""
for _, val := range message.Cc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Cc"] = strings.Trim(str, ",")
// Header["Cc"] = strings.Join(message.Cc, ",")
}
if len(message.Bcc) > 0 {
str := ""
for _, val := range message.Bcc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Bcc"] = strings.Trim(str, ",")
// Header["Bcc"] = strings.Join(message.Bcc, ",")
}
Header["Subject"] = message.Subject
Header["Content-Type"] = "multipart/mixed; charset=UTF-8; boundary=" + boundary
Header["Date"] = time.Now().String()
Header["Reply-To"] = message.ReplyTo
Header["X-Priority"] = "3"
m.writeHeader(buffer, Header)
body := "--" + boundary + "\r\n"
// body += "Content-Type: text/plain; charset=UTF-8 \r\n"
body += "Content-Type: text/html;charset=utf-8\r\n"
body += "Content-Transfer-Encoding:quoted-printable\r\n\r\n"
// body += "<html><body><h1>huang</h1><h2>xin</h2></body></html>\r\n"
body += "<html><body>" + message.Body + "</body></html>\r\n"
// body += "--" + boundary + "--\r\n\r\n"
buffer.WriteString(body)
for _, value := range message.Attachment {
newBuf := bytes.NewBuffer(nil)
err := m.writeFile(newBuf, value.Name)
if err != nil {
fmt.Println("file err:", err)
continue
}
f_name := path.Base(value.Name)
attachment := "--" + boundary + "\r\n"
attachment += "Content-Transfer-Encoding:base64\r\n"
attachment += "Content-Disposition:attachment;filename=" + f_name + "\r\n"
attachment += "Content-Type: application/octet-stream;charset=utf-8;name=" + f_name + "\r\n"
// attachment += "Contment-Type:" + message.attachment.contentType + ";name=\"" + message.attachment.name + "\"\r\n"
buffer.WriteString(attachment)
buffer.WriteString(newBuf.String())
}
if message.Form == "" {
message.Form = m.user
}
buffer.WriteString("\r\n--" + boundary + "--\r\n")
b := buffer.Bytes()
err := smtp.SendMail(m.host+":"+m.port, m.auth, message.Form, message.To, b)
fmt.Println("发送结束:", err)
fmt.Println(string(b))
return err
}
// 格式化header
func (m *Mailx) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
header := ""
// header := "Content-Type: multipart/mixed;charset=UTF-8;boundary=\"YunBoundaryYun\" \r\n"
for key, value := range Header {
if value != "" {
header += key + ": " + value + "\r\n"
}
}
header += "\r\n"
buffer.WriteString(header)
return header
}
// 格式化文件
func (m *Mailx) writeFile(buffer *bytes.Buffer, fileName string) error {
file, err := os.ReadFile(fileName)
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 {
buffer.WriteString("\r\n")
}
}
return nil
return nil, errors.New("not found")
}
+1
View File
@@ -0,0 +1 @@
hhhhhhhhhhh
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+77
View File
@@ -0,0 +1,77 @@
package mailx
import (
"bytes"
"github.com/PuerkitoBio/goquery"
)
// 解析HTML资源,响应资源链接
func ParseHtmlResource(html string) ([]string, error) {
resp := []string{}
b := bytes.NewBufferString(html)
doc, err := goquery.NewDocumentFromReader(b)
if err != nil {
return nil, err
}
// 找到所有的 css 标签,并且打印它们的 href 属性
doc.Find("link").Each(func(i int, s *goquery.Selection) {
// 忽略dns预请求
r, ok := s.Attr("rel")
if ok && r == "dns-prefetch" {
return
}
href, ok := s.Attr("href")
if ok && href != "" {
resp = append(resp, href)
}
})
// 找到所有的 script 标签,并且打印它们的 src 属性
doc.Find("script").Each(func(i int, s *goquery.Selection) {
src, ok := s.Attr("src")
if ok && src != "" {
resp = append(resp, src)
}
})
// 找到所有的 img 标签,并且打印它们的 src 属性
doc.Find("img").Each(func(i int, s *goquery.Selection) {
src, ok := s.Attr("src")
if ok && src != "" {
resp = append(resp, src)
}
data_src, ok := s.Attr("data-src")
if ok && data_src != "" {
resp = append(resp, data_src)
}
})
// 找到所有的 video 标签,并且打印它们的 src 属性
doc.Find("video").Each(func(i int, s *goquery.Selection) {
src, ok := s.Attr("src")
if ok && src != "" {
resp = append(resp, src)
}
data_src, ok := s.Attr("data-src")
if ok && data_src != "" {
resp = append(resp, data_src)
}
})
// 找到所有的 audio 标签,并且打印它们的 src 属性
doc.Find("audio").Each(func(i int, s *goquery.Selection) {
src, ok := s.Attr("src")
if ok && src != "" {
resp = append(resp, src)
}
})
return resp, nil
}
+60
View File
@@ -0,0 +1,60 @@
package mailx_test
import (
"testing"
"code.yun.ink/pkg/mailx"
)
func TestParseHtmlResource(t *testing.T) {
html := `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./assets/css/index.css" />
</head>
<body>
<header class="header flex-center">
<img src="./assets/img/ab-pay-logo.png" alt="" class="logo" />
</header>
<main class="code-main">
<div class="exception-title">您的收款方式审核失败</div>
<div class="exception-content">
尊敬的ABpay用户,您好!
您的收款方式审核失败,失败原因人脸识别失败,请检查您的面部是否被遮挡或处于模糊状态,并再次尝试。
如非本人操作,请立即修改密码或联系客服。
</div>
<div class="exception-ignore">这是一条自动发送的消息,请勿回复</div>
<li class="exception-team-tips">ABpay开发者团队服务</li>
<section class="contact-us">
<div class="contact-us-info">
本邮件由系统自动发出请勿回复,如需要了解更多服务,欢迎访问ABpay官方网
还可以通以下方式联系我们
</div>
<img src="./assets/img/contact-us-qrcode.png" alt="" class="qr-code" />
<div class="contact-tel">
客服电话:<span class="contact-tel-number">400-278-2890</span>
</div>
<div class="contact-created-version">2024 ABpay.com.cn . All rightes reserved</div>
</section>
</main>
</body>
</html>
`
res, err := mailx.ParseHtmlResource(html)
if err != nil {
t.Fatal(err)
}
t.Log(res)
}
+202
View File
@@ -0,0 +1,202 @@
package mailx
import (
"bytes"
"encoding/base64"
"fmt"
"net/smtp"
"os"
"path"
"strings"
"time"
)
// 邮件发送的封装
// 1. 支持文本
// 2. 支持文件
type Mailx struct {
user string
password string
host string
port string
auth smtp.Auth
}
type Attachment struct {
Name string
ContentType string
WithFile bool
}
type Message struct {
Form string
To []string
Cc []string
Bcc []string
Subject string
Body string
// ContentType string
ReplyTo string
Attachment []Attachment
}
func NewMailx(user, password, host, port string) *Mailx {
m := &Mailx{
user: user,
password: password,
host: host,
port: port,
}
m.auth = smtp.PlainAuth("", user, password, host)
return m
}
func (m *Mailx) Send(message Message) error {
// .Auth()
buffer := bytes.NewBuffer(nil)
boundary := "YunBoundaryYun"
Header := make(map[string]string)
// Header["From"] = "BOP<" + message.Form + ">"
Header["From"] = m.user
if len(message.To) > 0 {
str := ""
for _, val := range message.To {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["To"] = strings.Trim(str, ",")
// Header["To"] = strings.Join(message.To, ",")
}
if len(message.Cc) > 0 {
str := ""
for _, val := range message.Cc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Cc"] = strings.Trim(str, ",")
// Header["Cc"] = strings.Join(message.Cc, ",")
}
if len(message.Bcc) > 0 {
str := ""
for _, val := range message.Bcc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Bcc"] = strings.Trim(str, ",")
// Header["Bcc"] = strings.Join(message.Bcc, ",")
}
Header["Subject"] = message.Subject
Header["Content-Type"] = "multipart/mixed; charset=UTF-8; boundary=" + boundary
Header["Date"] = time.Now().String()
Header["Reply-To"] = message.ReplyTo
Header["X-Priority"] = "3"
m.writeHeader(buffer, Header)
body := "--" + boundary + "\r\n"
// body += "Content-Type: text/plain; charset=UTF-8 \r\n"
body += "Content-Type: text/html;charset=utf-8\r\n"
body += "Content-Transfer-Encoding:quoted-printable\r\n\r\n"
// body += "<html><body><h1>huang</h1><h2>xin</h2></body></html>\r\n"
body += "<html><body>" + message.Body + "</body></html>\r\n"
// body += "--" + boundary + "--\r\n\r\n"
buffer.WriteString(body)
for _, value := range message.Attachment {
newBuf := bytes.NewBuffer(nil)
err := m.writeFile(newBuf, value.Name)
if err != nil {
fmt.Println("file err:", err)
continue
}
f_name := path.Base(value.Name)
attachment := "--" + boundary + "\r\n"
attachment += "Content-Transfer-Encoding:base64\r\n"
attachment += "Content-Disposition:attachment;filename=" + f_name + "\r\n"
attachment += "Content-Type: application/octet-stream;charset=utf-8;name=" + f_name + "\r\n"
// attachment += "Contment-Type:" + message.attachment.contentType + ";name=\"" + message.attachment.name + "\"\r\n"
buffer.WriteString(attachment)
buffer.WriteString(newBuf.String())
buffer.WriteString("\r\n")
}
if message.Form == "" {
message.Form = m.user
}
imgBuf := bytes.NewBuffer(nil)
err := m.writeFile(imgBuf, "./asset/余额宝.png")
if err != nil {
return fmt.Errorf("file err: %w", err)
}
f_name:= "f_name"
attachment := "--" + boundary + "\r\n"
attachment += "Content-Transfer-Encoding:base64\r\n"
attachment += "Content-ID:myimage \r\n"
attachment += "Content-Disposition:inline;filename=" + f_name + ".png \r\n"
attachment += "Content-Type:image/png \r\n"
buffer.WriteString(attachment)
buffer.WriteString(imgBuf.String())
buffer.WriteString("\r\n--" + boundary + "--\r\n")
b := buffer.Bytes()
err = smtp.SendMail(m.host+":"+m.port, m.auth, message.Form, message.To, b)
fmt.Println("发送结束:", err)
fmt.Println(string(b))
return err
}
// 格式化header
func (m *Mailx) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
header := ""
// header := "Content-Type: multipart/mixed;charset=UTF-8;boundary=\"YunBoundaryYun\" \r\n"
for key, value := range Header {
if value != "" {
header += key + ": " + value + "\r\n"
}
}
header += "\r\n"
buffer.WriteString(header)
return header
}
// 格式化文件
func (m *Mailx) writeFile(buffer *bytes.Buffer, fileName string) error {
file, err := os.ReadFile(fileName)
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 {
buffer.WriteString("\r\n")
}
}
return nil
}
+10 -10
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
"code.yun.ink/pkg/mailx"
mailx "code.yun.ink/pkg/mailx/old"
)
func TestMail(t *testing.T) {
@@ -12,17 +12,17 @@ func TestMail(t *testing.T) {
msg := mailx.Message{
// Form: "support@email.blueoceanpay.com",
To: []string{"yun@blueoceanpay.com", "995116474@qq.com"},
Cc: []string{"287852692@qq.com"},
Bcc: []string{"1362716835@qq.com"},
To: []string{"huangxinyun520@gmail.com", "995116474@qq.com"},
// Cc: []string{"287852692@qq.com"},
// Bcc: []string{"1362716835@qq.com"},
Subject: "test mail",
Body: "测试文件",
Body: "<img src=\"cid:myimage\">dasdsadsasda<br>dasdsadsadsa",
Attachment: []mailx.Attachment{
// {
// Name: "/code/statistic/out.xlsx",
// ContentType: "",
// WithFile: true,
// },
{
Name: "asset/hhh.txt",
ContentType: "",
WithFile: true,
},
// {
// Name: "/code/statistic/origin.xlsx",
// ContentType: "",
+3
View File
@@ -0,0 +1,3 @@
[info]{"time":"2024-09-27 18:32:44.687712","file":"/smtp.go:29","func":"InitEmail","gid":"70","content":["params:{Smtp:0xc00013a910 Aws:\u003cnil\u003e Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
[info]{"time":"2024-09-27 18:54:34.654603","file":"/smtp.go:29","func":"InitEmail","gid":"43","content":["params:{Smtp:0xc0004780f0 Aws:\u003cnil\u003e Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
+2
View File
@@ -0,0 +1,2 @@
[info]{"time":"2024-09-30 11:31:05.402488","file":"/smtp.go:33","func":"InitEmail","gid":"54","content":["params:\u0026{Username:test@email.aisz.org Password:FDuSoJKiC9tNSDIG6DFh Host:smtpdm.aliyun.com Port:80 ReplyTo:}"]}
+177
View File
@@ -0,0 +1,177 @@
package smtp
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"net/smtp"
"os"
"path"
"strings"
"time"
"code.yun.ink/pkg/mailx/interfaces"
"github.com/yuninks/loggerx"
)
// 邮件发送的封装
// 1. 支持文本
// 2. 支持文件
type Smtp struct {
interfaces.DefaultEmail
params *interfaces.EmailConfigDataSmtp
auth smtp.Auth
logger loggerx.LoggerInterface
}
func (l *Smtp) InitEmail(ctx context.Context, params interfaces.EmailConfigData, logger loggerx.LoggerInterface) (interfaces.Email, error) {
if params.Smtp == nil {
return nil, errors.New("not smtp")
}
l.logger.Infof(ctx, "params:%+v", params.Smtp)
m := &Smtp{
params: params.Smtp,
}
m.auth = smtp.PlainAuth("", params.Smtp.Username, params.Smtp.Password, params.Smtp.Host)
return m, nil
}
func (l *Smtp) Send(ctx context.Context, message interfaces.Message) error {
if l.params == nil {
return errors.New("not init")
}
// .Auth()
buffer := bytes.NewBuffer(nil)
boundary := "YunBoundaryYun"
Header := make(map[string]string)
// Header["From"] = "BOP<" + message.Form + ">"
if message.Form != "" {
Header["From"] = message.Form
} else {
Header["From"] = l.params.Username
}
if len(message.To) > 0 {
str := ""
for _, val := range message.To {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["To"] = strings.Trim(str, ",")
// Header["To"] = strings.Join(message.To, ",")
}
if len(message.Cc) > 0 {
str := ""
for _, val := range message.Cc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Cc"] = strings.Trim(str, ",")
// Header["Cc"] = strings.Join(message.Cc, ",")
}
if len(message.Bcc) > 0 {
str := ""
for _, val := range message.Bcc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Bcc"] = strings.Trim(str, ",")
// Header["Bcc"] = strings.Join(message.Bcc, ",")
}
Header["Subject"] = message.Subject
Header["Content-Type"] = "multipart/mixed; charset=UTF-8; boundary=" + boundary
Header["Date"] = time.Now().String()
Header["Reply-To"] = message.ReplyTo
Header["X-Priority"] = "3"
l.writeHeader(buffer, Header)
body := "--" + boundary + "\r\n"
// body += "Content-Type: text/plain; charset=UTF-8 \r\n"
body += "Content-Type: text/html;charset=utf-8\r\n"
body += "Content-Transfer-Encoding:quoted-printable\r\n\r\n"
// body += "<html><body><h1>huang</h1><h2>xin</h2></body></html>\r\n"
// body += "<html><body>" + message.Body + "</body></html>\r\n"
body += message.Body + "\r\n"
// body += "--" + boundary + "--\r\n\r\n"
buffer.WriteString(body)
for _, value := range message.Attachment {
newBuf := bytes.NewBuffer(nil)
err := l.writeFile(newBuf, value.Content)
if err != nil {
fmt.Println("file err:", err)
continue
}
f_name := path.Base(value.Content)
attachment := "--" + boundary + "\r\n"
attachment += "Content-Transfer-Encoding:base64\r\n"
attachment += "Content-Disposition:attachment;filename=" + f_name + "\r\n"
attachment += "Content-Type: application/octet-stream;charset=utf-8;name=" + f_name + "\r\n"
// attachment += "Contment-Type:" + message.attachment.contentType + ";name=\"" + message.attachment.name + "\"\r\n"
buffer.WriteString(attachment)
buffer.WriteString(newBuf.String())
}
buffer.WriteString("\r\n--" + boundary + "--\r\n")
b := buffer.Bytes()
err := smtp.SendMail(l.params.Host+":"+l.params.Port, l.auth, l.params.Username, message.To, b)
return err
}
// 格式化header
func (l *Smtp) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
header := ""
// header := "Content-Type: multipart/mixed;charset=UTF-8;boundary=\"YunBoundaryYun\" \r\n"
for key, value := range Header {
if value != "" {
header += key + ": " + value + "\r\n"
}
}
header += "\r\n"
buffer.WriteString(header)
return header
}
// 格式化文件
func (l *Smtp) writeFile(buffer *bytes.Buffer, fileName string) error {
file, err := os.ReadFile(fileName)
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 {
buffer.WriteString("\r\n")
}
}
return nil
}
+96
View File
@@ -0,0 +1,96 @@
package smtp_test
import (
"context"
"fmt"
"io"
"net/http"
"testing"
"code.yun.ink/pkg/mailx/interfaces"
"code.yun.ink/pkg/mailx/smtp"
"github.com/yuninks/loggerx"
)
func TestMail(t *testing.T) {
sm := smtp.Smtp{}
ctx := context.Background()
logger := loggerx.NewLogger(ctx)
ini, err := sm.InitEmail(ctx, interfaces.EmailConfigData{
Smtp: &interfaces.EmailConfigDataSmtp{
Username: "test@email.aisz.org",
Password: "FDuSoJKiC9tNSDIG6DFh",
ReplyTo: "",
Host: "smtpdm.aliyun.com",
Port: "80",
},
}, logger)
if err != nil {
t.Fatal(err)
}
req, err := http.Get("https://baidu.com")
if err != nil {
t.Fatal(err)
}
defer req.Body.Close()
by, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
msg := interfaces.Message{
To: []string{"995116474@qq.com"},
Cc: []string{"287852692@qq.com"},
Bcc: []string{"1362716835@qq.com"},
ReplyTo: "huangxinyun@dreaminglife.cn",
Subject: "test mail",
Body: string(by),
Attachment: []interfaces.MessageAttachment{
// {
// Name: "/code/statistic/out.xlsx",
// ContentType: "",
// WithFile: true,
// },
// {
// Name: "/code/statistic/origin.xlsx",
// ContentType: "",
// WithFile: true,
// },
// {
// Name: "/code/statistic/out2.xlsx",
// ContentType: "",
// WithFile: true,
// },
},
}
err = ini.Send(ctx, msg)
fmt.Println(err)
}
// func TestQQ(t *testing.T) {
// // 发件人邮箱
// from := "995116474@qq.com"
// // 授权码,而非密码
// authCode := "xxxxxxxxxxxxxxxxxxxxxx"
// // 收件人邮箱,可以是多个收件人
// to := []string{"yun@yun.ink"}
// // 邮件服务器信息
// smtpHost := "smtp.qq.com"
// smtpPort := "587" // 或使用465,根据你的SMTP服务器要求设置
// mail := mailx.NewMailx(from, authCode, smtpHost, smtpPort)
// msg := mailx.Message{
// To: to,
// Subject: "test mail",
// Body: "测试",
// }
// err := mail.Send(msg)
// fmt.Println(err)
// }