refactor: log params and channel params

refactor: log params and channel params
This commit is contained in:
Xiangyuan-liu
2025-07-07 14:26:37 +08:00
parent 265c7d93a2
commit 7b29f429ee
27 changed files with 314 additions and 173 deletions

View File

@@ -76,3 +76,13 @@ func GetContextKeyStringMap(c *gin.Context, key constant.ContextKey) map[string]
func GetContextKeyTime(c *gin.Context, key constant.ContextKey) time.Time { func GetContextKeyTime(c *gin.Context, key constant.ContextKey) time.Time {
return c.GetTime(string(key)) return c.GetTime(string(key))
} }
func GetContextKeyType[T any](c *gin.Context, key constant.ContextKey) (T, bool) {
if value, ok := c.Get(string(key)); ok {
if v, ok := value.(T); ok {
return v, true
}
}
var t T
return t, false
}

View File

@@ -73,3 +73,11 @@ func StringToByteSlice(s string) []byte {
func EncodeBase64(str string) string { func EncodeBase64(str string) string {
return base64.StdEncoding.EncodeToString([]byte(str)) return base64.StdEncoding.EncodeToString([]byte(str))
} }
func GetJsonString(data any) string {
if data == nil {
return ""
}
b, _ := json.Marshal(data)
return string(b)
}

View File

@@ -1,7 +0,0 @@
package constant
var (
ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式
ChanelSettingProxy = "proxy" // Proxy 代理
ChannelSettingThinkingToContent = "thinking_to_content" // ThinkingToContent
)

View File

@@ -1,16 +0,0 @@
package constant
var (
UserSettingNotifyType = "notify_type" // QuotaWarningType 额度预警类型
UserSettingQuotaWarningThreshold = "quota_warning_threshold" // QuotaWarningThreshold 额度预警阈值
UserSettingWebhookUrl = "webhook_url" // WebhookUrl webhook地址
UserSettingWebhookSecret = "webhook_secret" // WebhookSecret webhook密钥
UserSettingNotificationEmail = "notification_email" // NotificationEmail 通知邮箱地址
UserAcceptUnsetRatioModel = "accept_unset_model_ratio_model" // AcceptUnsetRatioModel 是否接受未设置价格的模型
UserSettingRecordIpLog = "record_ip_log" // 是否记录请求和错误日志IP
)
var (
NotifyTypeEmail = "email" // Email 邮件
NotifyTypeWebhook = "webhook" // Webhook
)

View File

@@ -173,8 +173,19 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
consumedTime := float64(milliseconds) / 1000.0 consumedTime := float64(milliseconds) / 1000.0
other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.CompletionRatio, other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.CompletionRatio,
usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.GroupRatioInfo.GroupSpecialRatio) usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试", model.RecordConsumeLog(c, 1, model.RecordConsumeLogParams{
quota, "模型测试", 0, quota, int(consumedTime), false, info.UsingGroup, other) ChannelId: channel.Id,
PromptTokens: usage.PromptTokens,
CompletionTokens: usage.CompletionTokens,
ModelName: info.OriginModelName,
TokenName: "模型测试",
Quota: quota,
Content: "模型测试",
UseTimeSeconds: int(consumedTime),
IsStream: false,
Group: info.UsingGroup,
Other: other,
})
common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody))) common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
return nil, nil return nil, nil
} }

View File

@@ -387,6 +387,14 @@ func AddChannel(c *gin.Context) {
}) })
return return
} }
err = channel.ValidateSettings()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "channel setting 格式错误:" + err.Error(),
})
return
}
channel.CreatedTime = common.GetTimestamp() channel.CreatedTime = common.GetTimestamp()
keys := strings.Split(channel.Key, "\n") keys := strings.Split(channel.Key, "\n")
if channel.Type == constant.ChannelTypeVertexAi { if channel.Type == constant.ChannelTypeVertexAi {
@@ -614,6 +622,14 @@ func UpdateChannel(c *gin.Context) {
}) })
return return
} }
err = channel.ValidateSettings()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "channel setting 格式错误:" + err.Error(),
})
return
}
if channel.Type == constant.ChannelTypeVertexAi { if channel.Type == constant.ChannelTypeVertexAi {
if channel.Other == "" { if channel.Other == "" {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"one-api/common" "one-api/common"
"one-api/dto"
"one-api/model" "one-api/model"
"one-api/setting" "one-api/setting"
"strconv" "strconv"
@@ -961,7 +962,7 @@ func UpdateUserSetting(c *gin.Context) {
} }
// 验证预警类型 // 验证预警类型
if req.QuotaWarningType != constant.NotifyTypeEmail && req.QuotaWarningType != constant.NotifyTypeWebhook { if req.QuotaWarningType != dto.NotifyTypeEmail && req.QuotaWarningType != dto.NotifyTypeWebhook {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
"message": "无效的预警类型", "message": "无效的预警类型",
@@ -979,7 +980,7 @@ func UpdateUserSetting(c *gin.Context) {
} }
// 如果是webhook类型,验证webhook地址 // 如果是webhook类型,验证webhook地址
if req.QuotaWarningType == constant.NotifyTypeWebhook { if req.QuotaWarningType == dto.NotifyTypeWebhook {
if req.WebhookUrl == "" { if req.WebhookUrl == "" {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
@@ -998,7 +999,7 @@ func UpdateUserSetting(c *gin.Context) {
} }
// 如果是邮件类型,验证邮箱地址 // 如果是邮件类型,验证邮箱地址
if req.QuotaWarningType == constant.NotifyTypeEmail && req.NotificationEmail != "" { if req.QuotaWarningType == dto.NotifyTypeEmail && req.NotificationEmail != "" {
// 验证邮箱格式 // 验证邮箱格式
if !strings.Contains(req.NotificationEmail, "@") { if !strings.Contains(req.NotificationEmail, "@") {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
@@ -1020,24 +1021,24 @@ func UpdateUserSetting(c *gin.Context) {
} }
// 构建设置 // 构建设置
settings := map[string]interface{}{ settings := dto.UserSetting{
constant.UserSettingNotifyType: req.QuotaWarningType, NotifyType: req.QuotaWarningType,
constant.UserSettingQuotaWarningThreshold: req.QuotaWarningThreshold, QuotaWarningThreshold: req.QuotaWarningThreshold,
"accept_unset_model_ratio_model": req.AcceptUnsetModelRatioModel, AcceptUnsetRatioModel: req.AcceptUnsetModelRatioModel,
constant.UserSettingRecordIpLog: req.RecordIpLog, RecordIpLog: req.RecordIpLog,
} }
// 如果是webhook类型,添加webhook相关设置 // 如果是webhook类型,添加webhook相关设置
if req.QuotaWarningType == constant.NotifyTypeWebhook { if req.QuotaWarningType == dto.NotifyTypeWebhook {
settings[constant.UserSettingWebhookUrl] = req.WebhookUrl settings.WebhookUrl = req.WebhookUrl
if req.WebhookSecret != "" { if req.WebhookSecret != "" {
settings[constant.UserSettingWebhookSecret] = req.WebhookSecret settings.WebhookSecret = req.WebhookSecret
} }
} }
// 如果提供了通知邮箱,添加到设置中 // 如果提供了通知邮箱,添加到设置中
if req.QuotaWarningType == constant.NotifyTypeEmail && req.NotificationEmail != "" { if req.QuotaWarningType == dto.NotifyTypeEmail && req.NotificationEmail != "" {
settings[constant.UserSettingNotificationEmail] = req.NotificationEmail settings.NotificationEmail = req.NotificationEmail
} }
// 更新用户设置 // 更新用户设置

7
dto/channel_settings.go Normal file
View File

@@ -0,0 +1,7 @@
package dto
type ChannelSettings struct {
ForceFormat bool `json:"force_format,omitempty"`
ThinkingToContent bool `json:"thinking_to_content,omitempty"`
Proxy string `json:"proxy"`
}

16
dto/user_settings.go Normal file
View File

@@ -0,0 +1,16 @@
package dto
type UserSetting struct {
NotifyType string `json:"notify_type,omitempty"` // QuotaWarningType 额度预警类型
QuotaWarningThreshold float64 `json:"quota_warning_threshold,omitempty"` // QuotaWarningThreshold 额度预警阈值
WebhookUrl string `json:"webhook_url,omitempty"` // WebhookUrl webhook地址
WebhookSecret string `json:"webhook_secret,omitempty"` // WebhookSecret webhook密钥
NotificationEmail string `json:"notification_email,omitempty"` // NotificationEmail 通知邮箱地址
AcceptUnsetRatioModel bool `json:"accept_unset_model_ratio_model,omitempty"` // AcceptUnsetRatioModel 是否接受未设置价格的模型
RecordIpLog bool `json:"record_ip_log,omitempty"` // 是否记录请求和错误日志IP
}
var (
NotifyTypeEmail = "email" // Email 邮件
NotifyTypeWebhook = "webhook" // Webhook
)

View File

@@ -39,7 +39,6 @@ func main() {
return return
} }
common.SetupLogger()
common.SysLog("New API " + common.Version + " started") common.SysLog("New API " + common.Version + " started")
if os.Getenv("GIN_MODE") != "debug" { if os.Getenv("GIN_MODE") != "debug" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
@@ -172,6 +171,8 @@ func InitResources() error {
// 加载环境变量 // 加载环境变量
common.InitEnv() common.InitEnv()
common.SetupLogger()
// Initialize model settings // Initialize model settings
ratio_setting.InitRatioSettings() ratio_setting.InitRatioSettings()

View File

@@ -247,9 +247,9 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
} }
c.Set("channel_id", channel.Id) c.Set("channel_id", channel.Id)
c.Set("channel_name", channel.Name) c.Set("channel_name", channel.Name)
c.Set("channel_type", channel.Type) common.SetContextKey(c, constant.ContextKeyChannelType, channel.Type)
c.Set("channel_create_time", channel.CreatedTime) c.Set("channel_create_time", channel.CreatedTime)
c.Set("channel_setting", channel.GetSetting()) common.SetContextKey(c, constant.ContextKeyChannelSetting, channel.GetSetting())
c.Set("param_override", channel.GetParamOverride()) c.Set("param_override", channel.GetParamOverride())
if nil != channel.OpenAIOrganization && "" != *channel.OpenAIOrganization { if nil != channel.OpenAIOrganization && "" != *channel.OpenAIOrganization {
c.Set("channel_organization", *channel.OpenAIOrganization) c.Set("channel_organization", *channel.OpenAIOrganization)
@@ -258,7 +258,7 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
c.Set("model_mapping", channel.GetModelMapping()) c.Set("model_mapping", channel.GetModelMapping())
c.Set("status_code_mapping", channel.GetStatusCodeMapping()) c.Set("status_code_mapping", channel.GetStatusCodeMapping())
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key)) c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key))
c.Set("base_url", channel.GetBaseURL()) common.SetContextKey(c, constant.ContextKeyBaseUrl, channel.GetBaseURL())
// TODO: api_version统一 // TODO: api_version统一
switch channel.Type { switch channel.Type {
case constant.ChannelTypeAzure: case constant.ChannelTypeAzure:

View File

@@ -3,6 +3,7 @@ package model
import ( import (
"encoding/json" "encoding/json"
"one-api/common" "one-api/common"
"one-api/dto"
"strings" "strings"
"sync" "sync"
@@ -514,8 +515,19 @@ func SearchTags(keyword string, group string, model string, idSort bool) ([]*str
return tags, nil return tags, nil
} }
func (channel *Channel) GetSetting() map[string]interface{} { func (channel *Channel) ValidateSettings() error {
setting := make(map[string]interface{}) channelParams := &dto.ChannelSettings{}
if channel.Setting != nil && *channel.Setting != "" {
err := json.Unmarshal([]byte(*channel.Setting), channelParams)
if err != nil {
return err
}
}
return nil
}
func (channel *Channel) GetSetting() dto.ChannelSettings {
setting := dto.ChannelSettings{}
if channel.Setting != nil && *channel.Setting != "" { if channel.Setting != nil && *channel.Setting != "" {
err := json.Unmarshal([]byte(*channel.Setting), &setting) err := json.Unmarshal([]byte(*channel.Setting), &setting)
if err != nil { if err != nil {
@@ -525,7 +537,7 @@ func (channel *Channel) GetSetting() map[string]interface{} {
return setting return setting
} }
func (channel *Channel) SetSetting(setting map[string]interface{}) { func (channel *Channel) SetSetting(setting dto.ChannelSettings) {
settingBytes, err := json.Marshal(setting) settingBytes, err := json.Marshal(setting)
if err != nil { if err != nil {
common.SysError("failed to marshal setting: " + err.Error()) common.SysError("failed to marshal setting: " + err.Error())

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"one-api/common" "one-api/common"
"one-api/constant"
"os" "os"
"strings" "strings"
"time" "time"
@@ -100,10 +99,8 @@ func RecordErrorLog(c *gin.Context, userId int, channelId int, modelName string,
// 判断是否需要记录 IP // 判断是否需要记录 IP
needRecordIp := false needRecordIp := false
if settingMap, err := GetUserSetting(userId, false); err == nil { if settingMap, err := GetUserSetting(userId, false); err == nil {
if v, ok := settingMap[constant.UserSettingRecordIpLog]; ok { if settingMap.RecordIpLog {
if vb, ok := v.(bool); ok && vb { needRecordIp = true
needRecordIp = true
}
} }
} }
log := &Log{ log := &Log{
@@ -136,22 +133,34 @@ func RecordErrorLog(c *gin.Context, userId int, channelId int, modelName string,
} }
} }
func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens int, completionTokens int, type RecordConsumeLogParams struct {
modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, ChannelId int `json:"channel_id"`
isStream bool, group string, other map[string]interface{}) { PromptTokens int `json:"prompt_tokens"`
common.LogInfo(c, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content)) CompletionTokens int `json:"completion_tokens"`
ModelName string `json:"model_name"`
TokenName string `json:"token_name"`
Quota int `json:"quota"`
Content string `json:"content"`
TokenId int `json:"token_id"`
UserQuota int `json:"user_quota"`
UseTimeSeconds int `json:"use_time_seconds"`
IsStream bool `json:"is_stream"`
Group string `json:"group"`
Other map[string]interface{} `json:"other"`
}
func RecordConsumeLog(c *gin.Context, userId int, params RecordConsumeLogParams) {
common.LogInfo(c, fmt.Sprintf("record consume log: userId=%d, params=%s", userId, common.GetJsonString(params)))
if !common.LogConsumeEnabled { if !common.LogConsumeEnabled {
return return
} }
username := c.GetString("username") username := c.GetString("username")
otherStr := common.MapToJsonStr(other) otherStr := common.MapToJsonStr(params.Other)
// 判断是否需要记录 IP // 判断是否需要记录 IP
needRecordIp := false needRecordIp := false
if settingMap, err := GetUserSetting(userId, false); err == nil { if settingMap, err := GetUserSetting(userId, false); err == nil {
if v, ok := settingMap[constant.UserSettingRecordIpLog]; ok { if settingMap.RecordIpLog {
if vb, ok := v.(bool); ok && vb { needRecordIp = true
needRecordIp = true
}
} }
} }
log := &Log{ log := &Log{
@@ -159,17 +168,17 @@ func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens in
Username: username, Username: username,
CreatedAt: common.GetTimestamp(), CreatedAt: common.GetTimestamp(),
Type: LogTypeConsume, Type: LogTypeConsume,
Content: content, Content: params.Content,
PromptTokens: promptTokens, PromptTokens: params.PromptTokens,
CompletionTokens: completionTokens, CompletionTokens: params.CompletionTokens,
TokenName: tokenName, TokenName: params.TokenName,
ModelName: modelName, ModelName: params.ModelName,
Quota: quota, Quota: params.Quota,
ChannelId: channelId, ChannelId: params.ChannelId,
TokenId: tokenId, TokenId: params.TokenId,
UseTime: useTimeSeconds, UseTime: params.UseTimeSeconds,
IsStream: isStream, IsStream: params.IsStream,
Group: group, Group: params.Group,
Ip: func() string { Ip: func() string {
if needRecordIp { if needRecordIp {
return c.ClientIP() return c.ClientIP()
@@ -184,7 +193,7 @@ func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens in
} }
if common.DataExportEnabled { if common.DataExportEnabled {
gopool.Go(func() { gopool.Go(func() {
LogQuotaData(userId, username, modelName, quota, common.GetTimestamp(), promptTokens+completionTokens) LogQuotaData(userId, username, params.ModelName, params.Quota, common.GetTimestamp(), params.PromptTokens+params.CompletionTokens)
}) })
} }
} }

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"one-api/common" "one-api/common"
"one-api/dto"
"strconv" "strconv"
"strings" "strings"
@@ -68,14 +69,18 @@ func (user *User) SetAccessToken(token string) {
user.AccessToken = &token user.AccessToken = &token
} }
func (user *User) GetSetting() map[string]interface{} { func (user *User) GetSetting() dto.UserSetting {
if user.Setting == "" { setting := dto.UserSetting{}
return nil if user.Setting != "" {
err := json.Unmarshal([]byte(user.Setting), &setting)
if err != nil {
common.SysError("failed to unmarshal setting: " + err.Error())
}
} }
return common.StrToMap(user.Setting) return setting
} }
func (user *User) SetSetting(setting map[string]interface{}) { func (user *User) SetSetting(setting dto.UserSetting) {
settingBytes, err := json.Marshal(setting) settingBytes, err := json.Marshal(setting)
if err != nil { if err != nil {
common.SysError("failed to marshal setting: " + err.Error()) common.SysError("failed to marshal setting: " + err.Error())
@@ -626,7 +631,7 @@ func GetUserGroup(id int, fromDB bool) (group string, err error) {
} }
// GetUserSetting gets setting from Redis first, falls back to DB if needed // GetUserSetting gets setting from Redis first, falls back to DB if needed
func GetUserSetting(id int, fromDB bool) (settingMap map[string]interface{}, err error) { func GetUserSetting(id int, fromDB bool) (settingMap dto.UserSetting, err error) {
var setting string var setting string
defer func() { defer func() {
// Update Redis cache asynchronously on successful DB read // Update Redis cache asynchronously on successful DB read
@@ -648,10 +653,12 @@ func GetUserSetting(id int, fromDB bool) (settingMap map[string]interface{}, err
fromDB = true fromDB = true
err = DB.Model(&User{}).Where("id = ?", id).Select("setting").Find(&setting).Error err = DB.Model(&User{}).Where("id = ?", id).Select("setting").Find(&setting).Error
if err != nil { if err != nil {
return map[string]interface{}{}, err return settingMap, err
} }
userBase := &UserBase{
return common.StrToMap(setting), nil Setting: setting,
}
return userBase.GetSetting(), nil
} }
func IncreaseUserQuota(id int, quota int, db bool) (err error) { func IncreaseUserQuota(id int, quota int, db bool) (err error) {

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"one-api/common" "one-api/common"
"one-api/constant" "one-api/constant"
"one-api/dto"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -32,20 +33,15 @@ func (user *UserBase) WriteContext(c *gin.Context) {
common.SetContextKey(c, constant.ContextKeyUserSetting, user.GetSetting()) common.SetContextKey(c, constant.ContextKeyUserSetting, user.GetSetting())
} }
func (user *UserBase) GetSetting() map[string]interface{} { func (user *UserBase) GetSetting() dto.UserSetting {
if user.Setting == "" { setting := dto.UserSetting{}
return nil if user.Setting != "" {
err := json.Unmarshal([]byte(user.Setting), &setting)
if err != nil {
common.SysError("failed to unmarshal setting: " + err.Error())
}
} }
return common.StrToMap(user.Setting) return setting
}
func (user *UserBase) SetSetting(setting map[string]interface{}) {
settingBytes, err := json.Marshal(setting)
if err != nil {
common.SysError("failed to marshal setting: " + err.Error())
return
}
user.Setting = string(settingBytes)
} }
// getUserCacheKey returns the key for user cache // getUserCacheKey returns the key for user cache
@@ -174,11 +170,10 @@ func getUserNameCache(userId int) (string, error) {
return cache.Username, nil return cache.Username, nil
} }
func getUserSettingCache(userId int) (map[string]interface{}, error) { func getUserSettingCache(userId int) (dto.UserSetting, error) {
setting := make(map[string]interface{})
cache, err := GetUserCache(userId) cache, err := GetUserCache(userId)
if err != nil { if err != nil {
return setting, err return dto.UserSetting{}, err
} }
return cache.GetSetting(), nil return cache.GetSetting(), nil
} }

View File

@@ -206,8 +206,8 @@ func sendPingData(c *gin.Context, mutex *sync.Mutex) error {
func doRequest(c *gin.Context, req *http.Request, info *common.RelayInfo) (*http.Response, error) { func doRequest(c *gin.Context, req *http.Request, info *common.RelayInfo) (*http.Response, error) {
var client *http.Client var client *http.Client
var err error var err error
if proxyURL, ok := info.ChannelSetting["proxy"]; ok { if info.ChannelSetting.Proxy != "" {
client, err = service.NewProxyHttpClient(proxyURL.(string)) client, err = service.NewProxyHttpClient(info.ChannelSetting.Proxy)
if err != nil { if err != nil {
return nil, fmt.Errorf("new proxy http client failed: %w", err) return nil, fmt.Errorf("new proxy http client failed: %w", err)
} }

View File

@@ -278,8 +278,8 @@ func getChatDetail(a *Adaptor, c *gin.Context, info *relaycommon.RelayInfo) (*ht
func doRequest(req *http.Request, info *relaycommon.RelayInfo) (*http.Response, error) { func doRequest(req *http.Request, info *relaycommon.RelayInfo) (*http.Response, error) {
var client *http.Client var client *http.Client
var err error // 声明 err 变量 var err error // 声明 err 变量
if proxyURL, ok := info.ChannelSetting["proxy"]; ok { if info.ChannelSetting.Proxy != "" {
client, err = service.NewProxyHttpClient(proxyURL.(string)) client, err = service.NewProxyHttpClient(info.ChannelSetting.Proxy)
if err != nil { if err != nil {
return nil, fmt.Errorf("new proxy http client failed: %w", err) return nil, fmt.Errorf("new proxy http client failed: %w", err)
} }

View File

@@ -53,7 +53,7 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
a.ChannelType = info.ChannelType a.ChannelType = info.ChannelType
// initialize ThinkingContentInfo when thinking_to_content is enabled // initialize ThinkingContentInfo when thinking_to_content is enabled
if think2Content, ok := info.ChannelSetting[constant.ChannelSettingThinkingToContent].(bool); ok && think2Content { if info.ChannelSetting.ThinkingToContent {
info.ThinkingContentInfo = relaycommon.ThinkingContentInfo{ info.ThinkingContentInfo = relaycommon.ThinkingContentInfo{
IsFirstThinkingContent: true, IsFirstThinkingContent: true,
SendLastThinkingContent: false, SendLastThinkingContent: false,

View File

@@ -124,12 +124,12 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
var forceFormat bool var forceFormat bool
var thinkToContent bool var thinkToContent bool
if forceFmt, ok := info.ChannelSetting[constant.ForceFormat].(bool); ok { if info.ChannelSetting.ForceFormat {
forceFormat = forceFmt forceFormat = true
} }
if think2Content, ok := info.ChannelSetting[constant.ChannelSettingThinkingToContent].(bool); ok { if info.ChannelSetting.ThinkingToContent {
thinkToContent = think2Content thinkToContent = true
} }
var ( var (
@@ -200,8 +200,8 @@ func OpenaiHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayI
} }
forceFormat := false forceFormat := false
if forceFmt, ok := info.ChannelSetting[constant.ForceFormat].(bool); ok { if info.ChannelSetting.ForceFormat {
forceFormat = forceFmt forceFormat = true
} }
if simpleResponse.Usage.TotalTokens == 0 || (simpleResponse.Usage.PromptTokens == 0 && simpleResponse.Usage.CompletionTokens == 0) { if simpleResponse.Usage.TotalTokens == 0 || (simpleResponse.Usage.PromptTokens == 0 && simpleResponse.Usage.CompletionTokens == 0) {

View File

@@ -106,8 +106,8 @@ func exchangeJwtForAccessToken(signedJWT string, info *relaycommon.RelayInfo) (s
var client *http.Client var client *http.Client
var err error var err error
if proxyURL, ok := info.ChannelSetting["proxy"]; ok { if info.ChannelSetting.Proxy != "" {
client, err = service.NewProxyHttpClient(proxyURL.(string)) client, err = service.NewProxyHttpClient(info.ChannelSetting.Proxy)
if err != nil { if err != nil {
return "", fmt.Errorf("new proxy http client failed: %w", err) return "", fmt.Errorf("new proxy http client failed: %w", err)
} }

View File

@@ -97,9 +97,9 @@ type RelayInfo struct {
IsFirstRequest bool IsFirstRequest bool
AudioUsage bool AudioUsage bool
ReasoningEffort string ReasoningEffort string
ChannelSetting map[string]interface{} ChannelSetting dto.ChannelSettings
ParamOverride map[string]interface{} ParamOverride map[string]interface{}
UserSetting map[string]interface{} UserSetting dto.UserSetting
UserEmail string UserEmail string
UserQuota int UserQuota int
RelayFormat string RelayFormat string
@@ -213,7 +213,6 @@ func GenRelayInfoImage(c *gin.Context) *RelayInfo {
func GenRelayInfo(c *gin.Context) *RelayInfo { func GenRelayInfo(c *gin.Context) *RelayInfo {
channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType) channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType)
channelId := common.GetContextKeyInt(c, constant.ContextKeyChannelId) channelId := common.GetContextKeyInt(c, constant.ContextKeyChannelId)
channelSetting := common.GetContextKeyStringMap(c, constant.ContextKeyChannelSetting)
paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyParamOverride) paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyParamOverride)
tokenId := common.GetContextKeyInt(c, constant.ContextKeyTokenId) tokenId := common.GetContextKeyInt(c, constant.ContextKeyTokenId)
@@ -227,7 +226,6 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
info := &RelayInfo{ info := &RelayInfo{
UserQuota: common.GetContextKeyInt(c, constant.ContextKeyUserQuota), UserQuota: common.GetContextKeyInt(c, constant.ContextKeyUserQuota),
UserSetting: common.GetContextKeyStringMap(c, constant.ContextKeyUserSetting),
UserEmail: common.GetContextKeyString(c, constant.ContextKeyUserEmail), UserEmail: common.GetContextKeyString(c, constant.ContextKeyUserEmail),
isFirstResponse: true, isFirstResponse: true,
RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path), RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
@@ -246,12 +244,12 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
OriginModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel), OriginModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
UpstreamModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel), UpstreamModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
//RecodeModelName: c.GetString("original_model"), //RecodeModelName: c.GetString("original_model"),
IsModelMapped: false, IsModelMapped: false,
ApiType: apiType, ApiType: apiType,
ApiVersion: c.GetString("api_version"), ApiVersion: c.GetString("api_version"),
ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "), ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
Organization: c.GetString("channel_organization"), Organization: c.GetString("channel_organization"),
ChannelSetting: channelSetting,
ChannelCreateTime: c.GetInt64("channel_create_time"), ChannelCreateTime: c.GetInt64("channel_create_time"),
ParamOverride: paramOverride, ParamOverride: paramOverride,
RelayFormat: RelayFormatOpenAI, RelayFormat: RelayFormatOpenAI,
@@ -277,6 +275,16 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
if streamSupportedChannels[info.ChannelType] { if streamSupportedChannels[info.ChannelType] {
info.SupportStreamOptions = true info.SupportStreamOptions = true
} }
channelSetting, ok := common.GetContextKeyType[dto.ChannelSettings](c, constant.ContextKeyChannelSetting)
if ok {
info.ChannelSetting = channelSetting
}
userSetting, ok := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting)
if ok {
info.UserSetting = userSetting
}
return info return info
} }

View File

@@ -3,7 +3,6 @@ package helper
import ( import (
"fmt" "fmt"
"one-api/common" "one-api/common"
constant2 "one-api/constant"
relaycommon "one-api/relay/common" relaycommon "one-api/relay/common"
"one-api/setting/ratio_setting" "one-api/setting/ratio_setting"
@@ -83,11 +82,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
modelRatio, success = ratio_setting.GetModelRatio(info.OriginModelName) modelRatio, success = ratio_setting.GetModelRatio(info.OriginModelName)
if !success { if !success {
acceptUnsetRatio := false acceptUnsetRatio := false
if accept, ok := info.UserSetting[constant2.UserAcceptUnsetRatioModel]; ok { if info.UserSetting.AcceptUnsetRatioModel {
b, ok := accept.(bool) acceptUnsetRatio = true
if ok {
acceptUnsetRatio = b
}
} }
if !acceptUnsetRatio { if !acceptUnsetRatio {
return PriceData{}, fmt.Errorf("模型 %s 倍率或价格未配置请联系管理员设置或开始自用模式Model %s ratio or price not set, please set or start self-use mode", info.OriginModelName, info.OriginModelName) return PriceData{}, fmt.Errorf("模型 %s 倍率或价格未配置请联系管理员设置或开始自用模式Model %s ratio or price not set, please set or start self-use mode", info.OriginModelName, info.OriginModelName)

View File

@@ -34,14 +34,13 @@ func RelayMidjourneyImage(c *gin.Context) {
} }
var httpClient *http.Client var httpClient *http.Client
if channel, err := model.CacheGetChannel(midjourneyTask.ChannelId); err == nil { if channel, err := model.CacheGetChannel(midjourneyTask.ChannelId); err == nil {
if proxy, ok := channel.GetSetting()["proxy"]; ok { proxy := channel.GetSetting().Proxy
if proxyURL, ok := proxy.(string); ok && proxyURL != "" { if proxy != "" {
if httpClient, err = service.NewProxyHttpClient(proxyURL); err != nil { if httpClient, err = service.NewProxyHttpClient(proxy); err != nil {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"error": "proxy_url_invalid", "error": "proxy_url_invalid",
}) })
return return
}
} }
} }
} }
@@ -175,7 +174,7 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse {
startTime := time.Now().UnixNano() / int64(time.Millisecond) startTime := time.Now().UnixNano() / int64(time.Millisecond)
tokenId := c.GetInt("token_id") tokenId := c.GetInt("token_id")
userId := c.GetInt("id") userId := c.GetInt("id")
group := c.GetString("group") //group := c.GetString("group")
channelId := c.GetInt("channel_id") channelId := c.GetInt("channel_id")
relayInfo := relaycommon.GenRelayInfo(c) relayInfo := relaycommon.GenRelayInfo(c)
var swapFaceRequest dto.SwapFaceRequest var swapFaceRequest dto.SwapFaceRequest
@@ -221,8 +220,17 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse {
tokenName := c.GetString("token_name") tokenName := c.GetString("token_name")
logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", priceData.ModelPrice, priceData.GroupRatioInfo.GroupRatio, constant.MjActionSwapFace) logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", priceData.ModelPrice, priceData.GroupRatioInfo.GroupRatio, constant.MjActionSwapFace)
other := service.GenerateMjOtherInfo(priceData) other := service.GenerateMjOtherInfo(priceData)
model.RecordConsumeLog(c, userId, channelId, 0, 0, modelName, tokenName, model.RecordConsumeLog(c, relayInfo.UserId, model.RecordConsumeLogParams{
priceData.Quota, logContent, tokenId, userQuota, 0, false, group, other) ChannelId: channelId,
ModelName: modelName,
TokenName: tokenName,
Quota: priceData.Quota,
Content: logContent,
TokenId: tokenId,
UserQuota: userQuota,
Group: relayInfo.UsingGroup,
Other: other,
})
model.UpdateUserUsedQuotaAndRequestCount(userId, priceData.Quota) model.UpdateUserUsedQuotaAndRequestCount(userId, priceData.Quota)
model.UpdateChannelUsedQuota(channelId, priceData.Quota) model.UpdateChannelUsedQuota(channelId, priceData.Quota)
} }
@@ -363,7 +371,7 @@ func RelayMidjourneyTask(c *gin.Context, relayMode int) *dto.MidjourneyResponse
func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyResponse { func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyResponse {
tokenId := c.GetInt("token_id") //tokenId := c.GetInt("token_id")
//channelType := c.GetInt("channel") //channelType := c.GetInt("channel")
userId := c.GetInt("id") userId := c.GetInt("id")
group := c.GetString("group") group := c.GetString("group")
@@ -518,8 +526,17 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons
tokenName := c.GetString("token_name") tokenName := c.GetString("token_name")
logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %sID %s", priceData.ModelPrice, priceData.GroupRatioInfo.GroupRatio, midjRequest.Action, midjResponse.Result) logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %sID %s", priceData.ModelPrice, priceData.GroupRatioInfo.GroupRatio, midjRequest.Action, midjResponse.Result)
other := service.GenerateMjOtherInfo(priceData) other := service.GenerateMjOtherInfo(priceData)
model.RecordConsumeLog(c, userId, channelId, 0, 0, modelName, tokenName, model.RecordConsumeLog(c, relayInfo.UserId, model.RecordConsumeLogParams{
priceData.Quota, logContent, tokenId, userQuota, 0, false, group, other) ChannelId: channelId,
ModelName: modelName,
TokenName: tokenName,
Quota: priceData.Quota,
Content: logContent,
TokenId: relayInfo.TokenId,
UserQuota: userQuota,
Group: group,
Other: other,
})
model.UpdateUserUsedQuotaAndRequestCount(userId, priceData.Quota) model.UpdateUserUsedQuotaAndRequestCount(userId, priceData.Quota)
model.UpdateChannelUsedQuota(channelId, priceData.Quota) model.UpdateChannelUsedQuota(channelId, priceData.Quota)
} }

View File

@@ -540,6 +540,19 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
other["audio_input_token_count"] = audioTokens other["audio_input_token_count"] = audioTokens
other["audio_input_price"] = audioInputPrice other["audio_input_price"] = audioInputPrice
} }
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.UsingGroup, other) ChannelId: relayInfo.ChannelId,
PromptTokens: promptTokens,
CompletionTokens: completionTokens,
ModelName: logModel,
TokenName: tokenName,
Quota: quota,
Content: logContent,
TokenId: relayInfo.TokenId,
UserQuota: userQuota,
UseTimeSeconds: int(useTimeSeconds),
IsStream: relayInfo.IsStream,
Group: relayInfo.UsingGroup,
Other: other,
})
} }

View File

@@ -139,8 +139,17 @@ func RelayTaskSubmit(c *gin.Context, relayMode int) (taskErr *dto.TaskError) {
if hasUserGroupRatio { if hasUserGroupRatio {
other["user_group_ratio"] = userGroupRatio other["user_group_ratio"] = userGroupRatio
} }
model.RecordConsumeLog(c, relayInfo.UserId, relayInfo.ChannelId, 0, 0, model.RecordConsumeLog(c, relayInfo.UserId, model.RecordConsumeLogParams{
modelName, tokenName, quota, logContent, relayInfo.TokenId, userQuota, 0, false, relayInfo.UsingGroup, other) ChannelId: relayInfo.ChannelId,
ModelName: modelName,
TokenName: tokenName,
Quota: quota,
Content: logContent,
TokenId: relayInfo.TokenId,
UserQuota: userQuota,
Group: relayInfo.UsingGroup,
Other: other,
})
model.UpdateUserUsedQuotaAndRequestCount(relayInfo.UserId, quota) model.UpdateUserUsedQuotaAndRequestCount(relayInfo.UserId, quota)
model.UpdateChannelUsedQuota(relayInfo.ChannelId, quota) model.UpdateChannelUsedQuota(relayInfo.ChannelId, quota)
} }

View File

@@ -209,8 +209,21 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
} }
other := GenerateWssOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio, other := GenerateWssOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio,
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, priceData.GroupRatioInfo.GroupSpecialRatio) completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.InputTokens, usage.OutputTokens, logModel, model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.UsingGroup, other) ChannelId: relayInfo.ChannelId,
PromptTokens: usage.InputTokens,
CompletionTokens: usage.OutputTokens,
ModelName: logModel,
TokenName: tokenName,
Quota: quota,
Content: logContent,
TokenId: relayInfo.TokenId,
UserQuota: userQuota,
UseTimeSeconds: int(useTimeSeconds),
IsStream: relayInfo.IsStream,
Group: relayInfo.UsingGroup,
Other: other,
})
} }
func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
@@ -286,8 +299,22 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio,
cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, priceData.GroupRatioInfo.GroupSpecialRatio) cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, modelName, model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.UsingGroup, other) ChannelId: relayInfo.ChannelId,
PromptTokens: promptTokens,
CompletionTokens: completionTokens,
ModelName: modelName,
TokenName: tokenName,
Quota: quota,
Content: logContent,
TokenId: relayInfo.TokenId,
UserQuota: userQuota,
UseTimeSeconds: int(useTimeSeconds),
IsStream: relayInfo.IsStream,
Group: relayInfo.UsingGroup,
Other: other,
})
} }
func CalcOpenRouterCacheCreateTokens(usage dto.Usage, priceData helper.PriceData) int { func CalcOpenRouterCacheCreateTokens(usage dto.Usage, priceData helper.PriceData) int {
@@ -384,8 +411,21 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
} }
other := GenerateAudioOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio, other := GenerateAudioOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio,
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, priceData.GroupRatioInfo.GroupSpecialRatio) completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.PromptTokens, usage.CompletionTokens, logModel, model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.UsingGroup, other) ChannelId: relayInfo.ChannelId,
PromptTokens: usage.PromptTokens,
CompletionTokens: usage.CompletionTokens,
ModelName: logModel,
TokenName: tokenName,
Quota: quota,
Content: logContent,
TokenId: relayInfo.TokenId,
UserQuota: userQuota,
UseTimeSeconds: int(useTimeSeconds),
IsStream: relayInfo.IsStream,
Group: relayInfo.UsingGroup,
Other: other,
})
} }
func PreConsumeTokenQuota(relayInfo *relaycommon.RelayInfo, quota int) error { func PreConsumeTokenQuota(relayInfo *relaycommon.RelayInfo, quota int) error {
@@ -447,8 +487,8 @@ func checkAndSendQuotaNotify(relayInfo *relaycommon.RelayInfo, quota int, preCon
gopool.Go(func() { gopool.Go(func() {
userSetting := relayInfo.UserSetting userSetting := relayInfo.UserSetting
threshold := common.QuotaRemindThreshold threshold := common.QuotaRemindThreshold
if userCustomThreshold, ok := userSetting[constant.UserSettingQuotaWarningThreshold]; ok { if userSetting.QuotaWarningThreshold != 0 {
threshold = int(userCustomThreshold.(float64)) threshold = int(userSetting.QuotaWarningThreshold)
} }
//noMoreQuota := userCache.Quota-(quota+preConsumedQuota) <= 0 //noMoreQuota := userCache.Quota-(quota+preConsumedQuota) <= 0

View File

@@ -3,7 +3,6 @@ package service
import ( import (
"fmt" "fmt"
"one-api/common" "one-api/common"
"one-api/constant"
"one-api/dto" "one-api/dto"
"one-api/model" "one-api/model"
"strings" "strings"
@@ -17,10 +16,10 @@ func NotifyRootUser(t string, subject string, content string) {
} }
} }
func NotifyUser(userId int, userEmail string, userSetting map[string]interface{}, data dto.Notify) error { func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data dto.Notify) error {
notifyType, ok := userSetting[constant.UserSettingNotifyType] notifyType := userSetting.NotifyType
if !ok { if notifyType == "" {
notifyType = constant.NotifyTypeEmail notifyType = dto.NotifyTypeEmail
} }
// Check notification limit // Check notification limit
@@ -34,34 +33,23 @@ func NotifyUser(userId int, userEmail string, userSetting map[string]interface{}
} }
switch notifyType { switch notifyType {
case constant.NotifyTypeEmail: case dto.NotifyTypeEmail:
// check setting email // check setting email
if settingEmail, ok := userSetting[constant.UserSettingNotificationEmail]; ok { userEmail = userSetting.NotificationEmail
userEmail = settingEmail.(string)
}
if userEmail == "" { if userEmail == "" {
common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId)) common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId))
return nil return nil
} }
return sendEmailNotify(userEmail, data) return sendEmailNotify(userEmail, data)
case constant.NotifyTypeWebhook: case dto.NotifyTypeWebhook:
webhookURL, ok := userSetting[constant.UserSettingWebhookUrl] webhookURLStr := userSetting.WebhookUrl
if !ok { if webhookURLStr == "" {
common.SysError(fmt.Sprintf("user %d has no webhook url, skip sending webhook", userId)) common.SysError(fmt.Sprintf("user %d has no webhook url, skip sending webhook", userId))
return nil return nil
} }
webhookURLStr, ok := webhookURL.(string)
if !ok {
common.SysError(fmt.Sprintf("user %d webhook url is not string type", userId))
return nil
}
// 获取 webhook secret // 获取 webhook secret
var webhookSecret string webhookSecret := userSetting.WebhookSecret
if secret, ok := userSetting[constant.UserSettingWebhookSecret]; ok {
webhookSecret, _ = secret.(string)
}
return SendWebhookNotify(webhookURLStr, webhookSecret, data) return SendWebhookNotify(webhookURLStr, webhookSecret, data)
} }
return nil return nil