mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-16 21:23:44 +08:00
✨ feat: add notifier (#144)
* ♻️ refactor: email refactor * ✨ feat: add notifier
This commit is contained in:
168
common/stmp/email.go
Normal file
168
common/stmp/email.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package stmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"one-api/common"
|
||||
"strings"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
type StmpConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
From string
|
||||
}
|
||||
|
||||
func NewStmp(host string, port int, username string, password string, from string) *StmpConfig {
|
||||
if from == "" {
|
||||
from = username
|
||||
}
|
||||
|
||||
return &StmpConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password,
|
||||
From: from,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StmpConfig) Send(to, subject, body string) error {
|
||||
message := mail.NewMsg()
|
||||
message.From(s.From)
|
||||
message.To(to)
|
||||
message.Subject(subject)
|
||||
message.SetGenHeader("References", s.getReferences())
|
||||
message.SetBodyString(mail.TypeTextHTML, body)
|
||||
message.SetUserAgent(fmt.Sprintf("One API %s // https://github.com/MartialBE/one-api", common.Version))
|
||||
|
||||
client, err := mail.NewClient(
|
||||
s.Host,
|
||||
mail.WithPort(s.Port),
|
||||
mail.WithUsername(s.Username),
|
||||
mail.WithPassword(s.Password),
|
||||
mail.WithSMTPAuth(mail.SMTPAuthPlain))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch s.Port {
|
||||
case 465:
|
||||
client.SetSSL(true)
|
||||
case 587:
|
||||
client.SetTLSPolicy(mail.TLSMandatory)
|
||||
}
|
||||
|
||||
if err := client.DialAndSend(message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StmpConfig) getReferences() string {
|
||||
froms := strings.Split(s.From, "@")
|
||||
return fmt.Sprintf("<%s.%s@%s>", froms[0], common.GetUUID(), froms[1])
|
||||
}
|
||||
|
||||
func (s *StmpConfig) Render(to, subject, content string) error {
|
||||
body := getDefaultTemplate(content)
|
||||
|
||||
return s.Send(to, subject, body)
|
||||
}
|
||||
|
||||
func GetSystemStmp() (*StmpConfig, error) {
|
||||
if common.SMTPServer == "" || common.SMTPPort == 0 || common.SMTPAccount == "" || common.SMTPToken == "" {
|
||||
return nil, fmt.Errorf("SMTP 信息未配置")
|
||||
}
|
||||
|
||||
return NewStmp(common.SMTPServer, common.SMTPPort, common.SMTPAccount, common.SMTPToken, common.SMTPFrom), nil
|
||||
}
|
||||
|
||||
func SendPasswordResetEmail(userName, email, link string) error {
|
||||
stmp, err := GetSystemStmp()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentTemp := `<p style="font-size: 30px">Hi <strong>%s,</strong></p>
|
||||
<p>
|
||||
您正在进行密码重置。点击下方按钮以重置密码。
|
||||
</p>
|
||||
|
||||
<p style="text-align: center; font-size: 13px;">
|
||||
<a target="__blank" href="%s" class="button" style="color: #ffffff;">重置密码</a>
|
||||
</p>
|
||||
|
||||
<p style="color: #858585; padding-top: 15px;">
|
||||
如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开<br> %s
|
||||
</p>
|
||||
<p style="color: #858585;">重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>`
|
||||
|
||||
subject := fmt.Sprintf("%s密码重置", common.SystemName)
|
||||
content := fmt.Sprintf(contentTemp, userName, link, link, common.VerificationValidMinutes)
|
||||
|
||||
return stmp.Render(email, subject, content)
|
||||
}
|
||||
|
||||
func SendVerificationCodeEmail(email, code string) error {
|
||||
stmp, err := GetSystemStmp()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentTemp := `
|
||||
<p>
|
||||
您正在进行邮箱验证。您的验证码为:
|
||||
</p>
|
||||
|
||||
<p style="text-align: center; font-size: 30px; color: #58a6ff;">
|
||||
<strong>%s</strong>
|
||||
</p>
|
||||
|
||||
<p style="color: #858585; padding-top: 15px;">
|
||||
验证码 %d 分钟内有效,如果不是本人操作,请忽略。
|
||||
</p>`
|
||||
|
||||
subject := fmt.Sprintf("%s邮箱验证邮件", common.SystemName)
|
||||
content := fmt.Sprintf(contentTemp, code, common.VerificationValidMinutes)
|
||||
|
||||
return stmp.Render(email, subject, content)
|
||||
}
|
||||
|
||||
func SendQuotaWarningCodeEmail(userName, email string, quota int, noMoreQuota bool) error {
|
||||
stmp, err := GetSystemStmp()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentTemp := `<p style="font-size: 30px">Hi <strong>%s,</strong></p>
|
||||
<p>
|
||||
%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。
|
||||
</p>
|
||||
|
||||
<p style="text-align: center; font-size: 13px;">
|
||||
<a target="__blank" href="%s" class="button" style="color: #ffffff;">点击充值</a>
|
||||
</p>
|
||||
|
||||
<p style="color: #858585; padding-top: 15px;">
|
||||
如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开<br> %s
|
||||
</p>`
|
||||
|
||||
subject := "您的额度即将用尽"
|
||||
if noMoreQuota {
|
||||
subject = "您的额度已用尽"
|
||||
}
|
||||
topUpLink := fmt.Sprintf("%s/topup", common.ServerAddress)
|
||||
|
||||
content := fmt.Sprintf(contentTemp, userName, subject, quota, topUpLink, topUpLink)
|
||||
|
||||
return stmp.Render(email, subject, content)
|
||||
}
|
||||
110
common/stmp/template.go
Normal file
110
common/stmp/template.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package stmp
|
||||
|
||||
import (
|
||||
"one-api/common"
|
||||
)
|
||||
|
||||
func getLogo() string {
|
||||
if common.Logo == "" {
|
||||
return ""
|
||||
}
|
||||
return `<table class="logo" width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<img src="` + common.Logo + `" width="130" style="max-width: 100%"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>`
|
||||
}
|
||||
|
||||
func getSystemName() string {
|
||||
if common.SystemName == "" {
|
||||
return "One API"
|
||||
}
|
||||
|
||||
return common.SystemName
|
||||
}
|
||||
|
||||
func getDefaultTemplate(content string) string {
|
||||
return `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #eceff1;
|
||||
font-family: Helvetica, sans-serif;
|
||||
}
|
||||
table {
|
||||
border-spacing: 0;
|
||||
}
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
background-color: #eceff1;
|
||||
padding-bottom: 60px;
|
||||
padding-top: 60px;
|
||||
}
|
||||
.main {
|
||||
background-color: #ffffff;
|
||||
border-spacing: 0;
|
||||
color: #000000;
|
||||
border-radius: 10px;
|
||||
border-color: #ebebeb;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
padding: 10px 30px;
|
||||
line-height: 25px;
|
||||
font-size: 16px;
|
||||
text-align: start;
|
||||
width: 600px;
|
||||
}
|
||||
.button {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
padding: 12px 20px;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.logo {
|
||||
text-align: center;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: #858585
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<center class="wrapper">
|
||||
` + getLogo() + `
|
||||
<table class="main" width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
` + content + `
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="footer" width="100%">
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<p>© ` + getSystemName() + `</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
Reference in New Issue
Block a user