feat: able to send alert message via message pusher (close #993)
This commit is contained in:
parent
71c61365eb
commit
5b50eb94e5
@ -106,6 +106,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
|
|||||||
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
||||||
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
||||||
23. 支持主题切换,设置环境变量 `THEME` 即可,默认为 `default`,欢迎 PR 更多主题,具体参考[此处](./web/README.md)。
|
23. 支持主题切换,设置环境变量 `THEME` 即可,默认为 `default`,欢迎 PR 更多主题,具体参考[此处](./web/README.md)。
|
||||||
|
24. 配合 [Message Pusher](https://github.com/songquanpeng/message-pusher) 可将报警信息推送到多种 App 上。
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
### 基于 Docker 进行部署
|
### 基于 Docker 进行部署
|
||||||
|
@ -70,6 +70,9 @@ var WeChatServerAddress = ""
|
|||||||
var WeChatServerToken = ""
|
var WeChatServerToken = ""
|
||||||
var WeChatAccountQRCodeImageURL = ""
|
var WeChatAccountQRCodeImageURL = ""
|
||||||
|
|
||||||
|
var MessagePusherAddress = ""
|
||||||
|
var MessagePusherToken = ""
|
||||||
|
|
||||||
var TurnstileSiteKey = ""
|
var TurnstileSiteKey = ""
|
||||||
var TurnstileSecretKey = ""
|
var TurnstileSecretKey = ""
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package common
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@ -12,6 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func SendEmail(subject string, receiver string, content string) error {
|
func SendEmail(subject string, receiver string, content string) error {
|
||||||
|
if receiver == "" {
|
||||||
|
return fmt.Errorf("receiver is empty")
|
||||||
|
}
|
||||||
if config.SMTPFrom == "" { // for compatibility
|
if config.SMTPFrom == "" { // for compatibility
|
||||||
config.SMTPFrom = config.SMTPAccount
|
config.SMTPFrom = config.SMTPAccount
|
||||||
}
|
}
|
22
common/message/main.go
Normal file
22
common/message/main.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ByAll = "all"
|
||||||
|
ByEmail = "email"
|
||||||
|
ByMessagePusher = "message_pusher"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Notify(by string, title string, description string, content string) error {
|
||||||
|
if by == ByEmail {
|
||||||
|
return SendEmail(title, config.RootUserEmail, content)
|
||||||
|
}
|
||||||
|
if by == ByMessagePusher {
|
||||||
|
return SendMessage(title, description, content)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unknown notify method: %s", by)
|
||||||
|
}
|
53
common/message/message-pusher.go
Normal file
53
common/message/message-pusher.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendMessage(title string, description string, content string) error {
|
||||||
|
if config.MessagePusherAddress == "" {
|
||||||
|
return errors.New("message pusher address is not set")
|
||||||
|
}
|
||||||
|
req := request{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
Content: content,
|
||||||
|
Token: config.MessagePusherToken,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := http.Post(config.MessagePusherAddress,
|
||||||
|
"application/json", bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var res response
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !res.Success {
|
||||||
|
return errors.New(res.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
"github.com/songquanpeng/one-api/common/config"
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
|
"github.com/songquanpeng/one-api/common/message"
|
||||||
"github.com/songquanpeng/one-api/middleware"
|
"github.com/songquanpeng/one-api/middleware"
|
||||||
"github.com/songquanpeng/one-api/model"
|
"github.com/songquanpeng/one-api/model"
|
||||||
"github.com/songquanpeng/one-api/monitor"
|
"github.com/songquanpeng/one-api/monitor"
|
||||||
@ -192,7 +193,7 @@ func testChannels(notify bool, scope string) error {
|
|||||||
testAllChannelsRunning = false
|
testAllChannelsRunning = false
|
||||||
testAllChannelsLock.Unlock()
|
testAllChannelsLock.Unlock()
|
||||||
if notify {
|
if notify {
|
||||||
err := common.SendEmail("通道测试完成", config.RootUserEmail, "通道测试完成,如果没有收到禁用通知,说明所有通道都正常")
|
err := message.Notify(message.ByAll, "通道测试完成", "", "通道测试完成,如果没有收到禁用通知,说明所有通道都正常")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
"github.com/songquanpeng/one-api/common/config"
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
|
"github.com/songquanpeng/one-api/common/message"
|
||||||
"github.com/songquanpeng/one-api/model"
|
"github.com/songquanpeng/one-api/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -110,7 +111,7 @@ func SendEmailVerification(c *gin.Context) {
|
|||||||
content := fmt.Sprintf("<p>您好,你正在进行%s邮箱验证。</p>"+
|
content := fmt.Sprintf("<p>您好,你正在进行%s邮箱验证。</p>"+
|
||||||
"<p>您的验证码为: <strong>%s</strong></p>"+
|
"<p>您的验证码为: <strong>%s</strong></p>"+
|
||||||
"<p>验证码 %d 分钟内有效,如果不是本人操作,请忽略。</p>", config.SystemName, code, common.VerificationValidMinutes)
|
"<p>验证码 %d 分钟内有效,如果不是本人操作,请忽略。</p>", config.SystemName, code, common.VerificationValidMinutes)
|
||||||
err := common.SendEmail(subject, email, content)
|
err := message.SendEmail(subject, email, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
@ -149,7 +150,7 @@ func SendPasswordResetEmail(c *gin.Context) {
|
|||||||
"<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
|
"<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
|
||||||
"<p>如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> %s </p>"+
|
"<p>如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> %s </p>"+
|
||||||
"<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>", config.SystemName, link, link, common.VerificationValidMinutes)
|
"<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>", config.SystemName, link, link, common.VerificationValidMinutes)
|
||||||
err := common.SendEmail(subject, email, content)
|
err := message.SendEmail(subject, email, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
|
2
main.go
2
main.go
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
"github.com/songquanpeng/one-api/common/config"
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
|
"github.com/songquanpeng/one-api/common/message"
|
||||||
"github.com/songquanpeng/one-api/controller"
|
"github.com/songquanpeng/one-api/controller"
|
||||||
"github.com/songquanpeng/one-api/middleware"
|
"github.com/songquanpeng/one-api/middleware"
|
||||||
"github.com/songquanpeng/one-api/model"
|
"github.com/songquanpeng/one-api/model"
|
||||||
@ -87,6 +88,7 @@ func main() {
|
|||||||
logger.SysLog("metric enabled, will disable channel if too much request failed")
|
logger.SysLog("metric enabled, will disable channel if too much request failed")
|
||||||
}
|
}
|
||||||
openai.InitTokenEncoders()
|
openai.InitTokenEncoders()
|
||||||
|
_ = message.SendMessage("One API", "", fmt.Sprintf("One API %s started", common.Version))
|
||||||
|
|
||||||
// Initialize HTTP server
|
// Initialize HTTP server
|
||||||
server := gin.New()
|
server := gin.New()
|
||||||
|
@ -57,6 +57,8 @@ func InitOptionMap() {
|
|||||||
config.OptionMap["WeChatServerAddress"] = ""
|
config.OptionMap["WeChatServerAddress"] = ""
|
||||||
config.OptionMap["WeChatServerToken"] = ""
|
config.OptionMap["WeChatServerToken"] = ""
|
||||||
config.OptionMap["WeChatAccountQRCodeImageURL"] = ""
|
config.OptionMap["WeChatAccountQRCodeImageURL"] = ""
|
||||||
|
config.OptionMap["MessagePusherAddress"] = ""
|
||||||
|
config.OptionMap["MessagePusherToken"] = ""
|
||||||
config.OptionMap["TurnstileSiteKey"] = ""
|
config.OptionMap["TurnstileSiteKey"] = ""
|
||||||
config.OptionMap["TurnstileSecretKey"] = ""
|
config.OptionMap["TurnstileSecretKey"] = ""
|
||||||
config.OptionMap["QuotaForNewUser"] = strconv.Itoa(config.QuotaForNewUser)
|
config.OptionMap["QuotaForNewUser"] = strconv.Itoa(config.QuotaForNewUser)
|
||||||
@ -179,6 +181,10 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
config.WeChatServerToken = value
|
config.WeChatServerToken = value
|
||||||
case "WeChatAccountQRCodeImageURL":
|
case "WeChatAccountQRCodeImageURL":
|
||||||
config.WeChatAccountQRCodeImageURL = value
|
config.WeChatAccountQRCodeImageURL = value
|
||||||
|
case "MessagePusherAddress":
|
||||||
|
config.MessagePusherAddress = value
|
||||||
|
case "MessagePusherToken":
|
||||||
|
config.MessagePusherToken = value
|
||||||
case "TurnstileSiteKey":
|
case "TurnstileSiteKey":
|
||||||
config.TurnstileSiteKey = value
|
config.TurnstileSiteKey = value
|
||||||
case "TurnstileSecretKey":
|
case "TurnstileSecretKey":
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/songquanpeng/one-api/common/config"
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
"github.com/songquanpeng/one-api/common/helper"
|
"github.com/songquanpeng/one-api/common/helper"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
|
"github.com/songquanpeng/one-api/common/message"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ func PreConsumeTokenQuota(tokenId int, quota int) (err error) {
|
|||||||
}
|
}
|
||||||
if email != "" {
|
if email != "" {
|
||||||
topUpLink := fmt.Sprintf("%s/topup", config.ServerAddress)
|
topUpLink := fmt.Sprintf("%s/topup", config.ServerAddress)
|
||||||
err = common.SendEmail(prompt, email,
|
err = message.SendEmail(prompt, email,
|
||||||
fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>", prompt, userQuota, topUpLink, topUpLink))
|
fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>", prompt, userQuota, topUpLink, topUpLink))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SysError("failed to send email" + err.Error())
|
logger.SysError("failed to send email" + err.Error())
|
||||||
|
@ -5,14 +5,23 @@ import (
|
|||||||
"github.com/songquanpeng/one-api/common"
|
"github.com/songquanpeng/one-api/common"
|
||||||
"github.com/songquanpeng/one-api/common/config"
|
"github.com/songquanpeng/one-api/common/config"
|
||||||
"github.com/songquanpeng/one-api/common/logger"
|
"github.com/songquanpeng/one-api/common/logger"
|
||||||
|
"github.com/songquanpeng/one-api/common/message"
|
||||||
"github.com/songquanpeng/one-api/model"
|
"github.com/songquanpeng/one-api/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyRootUser(subject string, content string) {
|
func notifyRootUser(subject string, content string) {
|
||||||
|
if config.MessagePusherAddress != "" {
|
||||||
|
err := message.SendMessage(subject, content, content)
|
||||||
|
if err != nil {
|
||||||
|
logger.SysError(fmt.Sprintf("failed to send message: %s", err.Error()))
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if config.RootUserEmail == "" {
|
if config.RootUserEmail == "" {
|
||||||
config.RootUserEmail = model.GetRootUserEmail()
|
config.RootUserEmail = model.GetRootUserEmail()
|
||||||
}
|
}
|
||||||
err := common.SendEmail(subject, config.RootUserEmail, content)
|
err := message.SendEmail(subject, config.RootUserEmail, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ const SystemSetting = () => {
|
|||||||
WeChatServerAddress: '',
|
WeChatServerAddress: '',
|
||||||
WeChatServerToken: '',
|
WeChatServerToken: '',
|
||||||
WeChatAccountQRCodeImageURL: '',
|
WeChatAccountQRCodeImageURL: '',
|
||||||
|
MessagePusherAddress: '',
|
||||||
|
MessagePusherToken: '',
|
||||||
TurnstileCheckEnabled: '',
|
TurnstileCheckEnabled: '',
|
||||||
TurnstileSiteKey: '',
|
TurnstileSiteKey: '',
|
||||||
TurnstileSecretKey: '',
|
TurnstileSecretKey: '',
|
||||||
@ -183,6 +185,21 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitMessagePusher = async () => {
|
||||||
|
if (originInputs['MessagePusherAddress'] !== inputs.MessagePusherAddress) {
|
||||||
|
await updateOption(
|
||||||
|
'MessagePusherAddress',
|
||||||
|
removeTrailingSlash(inputs.MessagePusherAddress)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
originInputs['MessagePusherToken'] !== inputs.MessagePusherToken &&
|
||||||
|
inputs.MessagePusherToken !== ''
|
||||||
|
) {
|
||||||
|
await updateOption('MessagePusherToken', inputs.MessagePusherToken);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const submitGitHubOAuth = async () => {
|
const submitGitHubOAuth = async () => {
|
||||||
if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
|
if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
|
||||||
await updateOption('GitHubClientId', inputs.GitHubClientId);
|
await updateOption('GitHubClientId', inputs.GitHubClientId);
|
||||||
@ -496,6 +513,42 @@ const SystemSetting = () => {
|
|||||||
保存 WeChat Server 设置
|
保存 WeChat Server 设置
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Header as='h3'>
|
||||||
|
配置 Message Pusher
|
||||||
|
<Header.Subheader>
|
||||||
|
用以推送报警信息,
|
||||||
|
<a
|
||||||
|
href='https://github.com/songquanpeng/message-pusher'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
点击此处
|
||||||
|
</a>
|
||||||
|
了解 Message Pusher
|
||||||
|
</Header.Subheader>
|
||||||
|
</Header>
|
||||||
|
<Form.Group widths={3}>
|
||||||
|
<Form.Input
|
||||||
|
label='Message Pusher 推送地址'
|
||||||
|
name='MessagePusherAddress'
|
||||||
|
placeholder='例如:https://msgpusher.com/push/your_username'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.MessagePusherAddress}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='Message Pusher 访问凭证'
|
||||||
|
name='MessagePusherToken'
|
||||||
|
type='password'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.MessagePusherToken}
|
||||||
|
placeholder='敏感信息不会发送到前端显示'
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Button onClick={submitMessagePusher}>
|
||||||
|
保存 Message Pusher 设置
|
||||||
|
</Form.Button>
|
||||||
|
<Divider />
|
||||||
<Header as='h3'>
|
<Header as='h3'>
|
||||||
配置 Turnstile
|
配置 Turnstile
|
||||||
<Header.Subheader>
|
<Header.Subheader>
|
||||||
|
Loading…
Reference in New Issue
Block a user