mirror of
https://github.com/woodchen-ink/aimodels-prices.git
synced 2025-07-18 13:41:59 +08:00
feat(config): 添加飞书Webhook配置支持,并在定时任务中注册价格审核检查功能
This commit is contained in:
parent
07ebd78cdd
commit
81a5eb61e9
160
FEISHU_WEBHOOK_README.md
Normal file
160
FEISHU_WEBHOOK_README.md
Normal file
@ -0,0 +1,160 @@
|
||||
# 飞书Webhook通知功能
|
||||
|
||||
本系统支持通过飞书Webhook发送待审核价格的通知,帮助管理员及时了解需要处理的价格审核请求。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🕐 **定期检查**:每5分钟自动检查一次待审核价格
|
||||
- 📊 **智能汇总**:按厂商分组统计待审核价格数量
|
||||
- 🔔 **智能通知**:30分钟内不重复发送相同通知,避免打扰
|
||||
- 📋 **详细信息**:显示最近的5个待审核价格详情
|
||||
- 🎨 **美观卡片**:使用飞书卡片格式,信息展示清晰美观
|
||||
|
||||
## 配置方法
|
||||
|
||||
### 1. 创建飞书自定义机器人
|
||||
|
||||
1. 在飞书群聊中,点击右上角设置按钮
|
||||
2. 选择"群机器人" -> "添加机器人" -> "自定义机器人"
|
||||
3. 填写机器人名称(如:AI模型价格通知)
|
||||
4. 复制生成的Webhook地址
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
#### 方法一:Docker Compose配置
|
||||
|
||||
编辑 `docker-compose.yml` 文件:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
aimodels-prices:
|
||||
environment:
|
||||
- FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-url
|
||||
```
|
||||
|
||||
#### 方法二:环境变量文件
|
||||
|
||||
在 `data/.env` 文件中添加:
|
||||
|
||||
```bash
|
||||
FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-url
|
||||
```
|
||||
|
||||
#### 方法三:系统环境变量
|
||||
|
||||
```bash
|
||||
export FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-url
|
||||
```
|
||||
|
||||
### 3. 重启服务
|
||||
|
||||
配置完成后重启应用:
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 通知内容
|
||||
|
||||
### 通知时机
|
||||
|
||||
- 系统每5分钟检查一次待审核价格
|
||||
- 只有当存在待审核价格时才发送通知
|
||||
- 30分钟内不会重复发送相同的通知
|
||||
|
||||
### 通知内容
|
||||
|
||||
通知卡片包含以下信息:
|
||||
|
||||
1. **总计统计**:待审核价格总数
|
||||
2. **分厂商统计**:按厂商分组的价格数量
|
||||
3. **价格详情**:最近的5个待审核价格信息,包括:
|
||||
- 模型名称
|
||||
- 所属厂商
|
||||
- 创建者
|
||||
4. **操作提醒**:提示管理员及时处理
|
||||
|
||||
### 示例通知
|
||||
|
||||
```
|
||||
🔍 待审核价格检查报告 - 8个待审核
|
||||
|
||||
📋 待审核价格统计
|
||||
|
||||
总计: 8 个模型价格待审核
|
||||
|
||||
分厂商统计:
|
||||
- OpenAI:3 个模型
|
||||
- Anthropic:2 个模型
|
||||
- 字节跳动:3 个模型
|
||||
|
||||
最近待审核价格(最多显示5个):
|
||||
1. gpt-4o-mini (OpenAI) - 创建者:张三
|
||||
2. claude-3-sonnet (Anthropic) - 创建者:李四
|
||||
3. doubao-pro-4k (字节跳动) - 创建者:王五
|
||||
4. gpt-4o (OpenAI) - 创建者:赵六
|
||||
5. claude-3-haiku (Anthropic) - 创建者:钱七
|
||||
|
||||
...还有 3 个价格等待审核
|
||||
|
||||
⏰ 请及时处理待审核价格!
|
||||
```
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 自动化检查
|
||||
|
||||
- 定时任务每5分钟运行一次
|
||||
- 自动查询数据库中状态为 `pending` 的价格记录
|
||||
- 如果没有待审核价格,不会发送通知
|
||||
|
||||
### 防止打扰
|
||||
|
||||
- 系统记录上次发送通知的时间
|
||||
- 30分钟内不会重复发送相同内容的通知
|
||||
- 避免频繁通知造成打扰
|
||||
|
||||
### 异步处理
|
||||
|
||||
- 通知发送采用异步方式
|
||||
- 不会阻塞主要业务流程
|
||||
- 即使通知发送失败,也不影响系统正常运行
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 通知未收到
|
||||
|
||||
1. **检查配置**:确认 `FEISHU_WEBHOOK_URL` 环境变量已正确设置
|
||||
2. **检查网络**:确认服务器能够访问飞书API
|
||||
3. **检查日志**:查看应用日志中是否有错误信息
|
||||
4. **检查机器人**:确认飞书群中的机器人未被移除
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
# 查看容器日志
|
||||
docker-compose logs -f aimodels-prices
|
||||
|
||||
# 查看最近的日志
|
||||
docker-compose logs --tail=100 aimodels-prices
|
||||
```
|
||||
|
||||
### 常见错误
|
||||
|
||||
- `webhook returned status code: 400`:Webhook地址错误或格式不正确
|
||||
- `failed to send webhook: connection refused`:网络连接问题
|
||||
- `未配置飞书webhook,跳过通知`:环境变量未设置
|
||||
|
||||
## 安全说明
|
||||
|
||||
- Webhook URL包含敏感token,请妥善保管
|
||||
- 建议定期更换Webhook URL
|
||||
- 不要在公开的代码仓库中暴露Webhook URL
|
||||
|
||||
## 技术实现
|
||||
|
||||
- 使用Go的cron库实现定时任务
|
||||
- 采用飞书卡片格式发送美观的通知
|
||||
- 支持异步发送,不阻塞主流程
|
||||
- 智能去重,避免重复通知
|
@ -21,6 +21,9 @@ type Config struct {
|
||||
|
||||
// SQLite配置(用于数据迁移)
|
||||
SQLitePath string
|
||||
|
||||
// 飞书Webhook配置
|
||||
FeishuWebhookURL string
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
@ -53,6 +56,9 @@ func LoadConfig() (*Config, error) {
|
||||
|
||||
// SQLite路径(用于数据迁移)
|
||||
SQLitePath: filepath.Join(dbDir, "aimodels.db"),
|
||||
|
||||
// 飞书Webhook配置
|
||||
FeishuWebhookURL: getEnv("FEISHU_WEBHOOK_URL", ""),
|
||||
}
|
||||
|
||||
return config, nil
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
openrouter_api "aimodels-prices/cron/openrouter-api"
|
||||
price_audit "aimodels-prices/cron/price-audit"
|
||||
siliconflow_api "aimodels-prices/cron/siliconflow-api"
|
||||
)
|
||||
|
||||
@ -43,6 +44,18 @@ func Init() {
|
||||
log.Printf("注册价格更新定时任务失败: %v", err)
|
||||
}
|
||||
|
||||
// 注册价格审核检查任务
|
||||
// 每5分钟执行一次
|
||||
_, err = cronScheduler.AddFunc("0 */5 * * * *", func() {
|
||||
if err := price_audit.CheckPendingPrices(); err != nil {
|
||||
log.Printf("价格审核检查任务执行失败: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("注册价格审核检查定时任务失败: %v", err)
|
||||
}
|
||||
|
||||
// 启动定时任务
|
||||
cronScheduler.Start()
|
||||
log.Println("定时任务已启动")
|
||||
|
129
backend/cron/price-audit/check.go
Normal file
129
backend/cron/price-audit/check.go
Normal file
@ -0,0 +1,129 @@
|
||||
package price_audit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"aimodels-prices/database"
|
||||
"aimodels-prices/models"
|
||||
"aimodels-prices/notification"
|
||||
)
|
||||
|
||||
// lastNotificationTime 记录上次发送通知的时间,避免重复发送
|
||||
var lastNotificationTime time.Time
|
||||
|
||||
// CheckPendingPrices 检查待审核价格并发送通知
|
||||
func CheckPendingPrices() error {
|
||||
log.Println("开始检查待审核价格...")
|
||||
|
||||
// 查询所有待审核的价格
|
||||
var pendingPrices []models.Price
|
||||
if err := database.DB.Where("status = 'pending'").Find(&pendingPrices).Error; err != nil {
|
||||
log.Printf("查询待审核价格失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pendingPrices) == 0 {
|
||||
log.Println("当前没有待审核的价格")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("发现 %d 个待审核价格", len(pendingPrices))
|
||||
|
||||
// 检查是否需要发送通知(避免频繁发送)
|
||||
now := time.Now()
|
||||
if now.Sub(lastNotificationTime) < 24*time.Hour {
|
||||
log.Println("距离上次通知时间较短,跳过本次通知")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 发送飞书通知
|
||||
webhook := notification.NewFeishuWebhook()
|
||||
if webhook == nil {
|
||||
log.Println("未配置飞书webhook,跳过通知")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 异步发送通知
|
||||
go func() {
|
||||
if err := sendPendingPricesNotification(webhook, pendingPrices); err != nil {
|
||||
log.Printf("发送飞书通知失败: %v", err)
|
||||
} else {
|
||||
log.Printf("成功发送飞书通知,包含 %d 个待审核价格", len(pendingPrices))
|
||||
lastNotificationTime = now
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendPendingPricesNotification 发送待审核价格的详细通知
|
||||
func sendPendingPricesNotification(webhook *notification.FeishuWebhook, pendingPrices []models.Price) error {
|
||||
// 按厂商分组统计
|
||||
providerStats := make(map[uint][]models.Price)
|
||||
for _, price := range pendingPrices {
|
||||
channelType := getChannelType(price)
|
||||
providerStats[channelType] = append(providerStats[channelType], price)
|
||||
}
|
||||
|
||||
// 构建详细的通知内容
|
||||
content := fmt.Sprintf("📋 **待审核价格统计**\n\n**总计:** %d 个模型价格待审核\n\n", len(pendingPrices))
|
||||
|
||||
// 按厂商分组显示
|
||||
content += "**分厂商统计:**\n"
|
||||
for channelType, prices := range providerStats {
|
||||
var provider models.Provider
|
||||
if err := database.DB.Where("id = ?", channelType).First(&provider).Error; err != nil {
|
||||
provider.Name = fmt.Sprintf("厂商ID:%d", channelType)
|
||||
}
|
||||
content += fmt.Sprintf("- %s:%d 个模型\n", provider.Name, len(prices))
|
||||
}
|
||||
|
||||
// 显示最近的几个待审核价格
|
||||
content += "\n**最近待审核价格(最多显示5个):**\n"
|
||||
maxDisplay := 5
|
||||
if len(pendingPrices) < maxDisplay {
|
||||
maxDisplay = len(pendingPrices)
|
||||
}
|
||||
|
||||
for i := 0; i < maxDisplay; i++ {
|
||||
price := pendingPrices[i]
|
||||
var provider models.Provider
|
||||
channelType := getChannelType(price)
|
||||
if err := database.DB.Where("id = ?", channelType).First(&provider).Error; err != nil {
|
||||
provider.Name = fmt.Sprintf("厂商ID:%d", channelType)
|
||||
}
|
||||
|
||||
content += fmt.Sprintf("%d. **%s** (%s) - 创建者:%s\n",
|
||||
i+1,
|
||||
getDisplayModel(price),
|
||||
provider.Name,
|
||||
price.CreatedBy)
|
||||
}
|
||||
|
||||
if len(pendingPrices) > maxDisplay {
|
||||
content += fmt.Sprintf("\n...还有 %d 个价格等待审核", len(pendingPrices)-maxDisplay)
|
||||
}
|
||||
|
||||
content += "\n\n⏰ 请及时处理待审核价格!"
|
||||
|
||||
// 发送卡片通知
|
||||
return webhook.SendPendingPricesDetailedNotification(content, len(pendingPrices))
|
||||
}
|
||||
|
||||
// getChannelType 获取厂商类型
|
||||
func getChannelType(price models.Price) uint {
|
||||
if price.TempChannelType != nil {
|
||||
return *price.TempChannelType
|
||||
}
|
||||
return price.ChannelType
|
||||
}
|
||||
|
||||
// getDisplayModel 获取显示用的模型名称
|
||||
func getDisplayModel(price models.Price) string {
|
||||
if price.TempModel != nil {
|
||||
return *price.TempModel
|
||||
}
|
||||
return price.Model
|
||||
}
|
376
backend/notification/feishu.go
Normal file
376
backend/notification/feishu.go
Normal file
@ -0,0 +1,376 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"aimodels-prices/models"
|
||||
)
|
||||
|
||||
// FeishuWebhook 飞书webhook配置
|
||||
type FeishuWebhook struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
// TextMessage 文本消息结构
|
||||
type TextMessage struct {
|
||||
MsgType string `json:"msg_type"`
|
||||
Content struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"content"`
|
||||
}
|
||||
|
||||
// CardMessage 卡片消息结构
|
||||
type CardMessage struct {
|
||||
MsgType string `json:"msg_type"`
|
||||
Card Card `json:"card"`
|
||||
}
|
||||
|
||||
// Card 卡片结构
|
||||
type Card struct {
|
||||
Schema string `json:"schema"`
|
||||
Config CardConfig `json:"config"`
|
||||
Header CardHeader `json:"header"`
|
||||
Body CardBody `json:"body"`
|
||||
}
|
||||
|
||||
// CardConfig 卡片配置
|
||||
type CardConfig struct {
|
||||
UpdateMulti bool `json:"update_multi"`
|
||||
}
|
||||
|
||||
// CardHeader 卡片头部
|
||||
type CardHeader struct {
|
||||
Title Title `json:"title"`
|
||||
Template string `json:"template"`
|
||||
Padding string `json:"padding"`
|
||||
}
|
||||
|
||||
// Title 标题
|
||||
type Title struct {
|
||||
Tag string `json:"tag"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// CardBody 卡片主体
|
||||
type CardBody struct {
|
||||
Direction string `json:"direction"`
|
||||
Padding string `json:"padding"`
|
||||
Elements []CardElement `json:"elements"`
|
||||
}
|
||||
|
||||
// CardElement 卡片元素
|
||||
type CardElement struct {
|
||||
Tag string `json:"tag"`
|
||||
Content string `json:"content,omitempty"`
|
||||
TextAlign string `json:"text_align,omitempty"`
|
||||
TextSize string `json:"text_size,omitempty"`
|
||||
Margin string `json:"margin,omitempty"`
|
||||
}
|
||||
|
||||
// NewFeishuWebhook 创建飞书webhook实例
|
||||
func NewFeishuWebhook() *FeishuWebhook {
|
||||
url := os.Getenv("FEISHU_WEBHOOK_URL")
|
||||
if url == "" {
|
||||
return nil
|
||||
}
|
||||
return &FeishuWebhook{URL: url}
|
||||
}
|
||||
|
||||
// SendTextMessage 发送文本消息
|
||||
func (f *FeishuWebhook) SendTextMessage(text string) error {
|
||||
if f == nil || f.URL == "" {
|
||||
return nil // 如果没有配置webhook,则跳过
|
||||
}
|
||||
|
||||
message := TextMessage{
|
||||
MsgType: "text",
|
||||
Content: struct {
|
||||
Text string `json:"text"`
|
||||
}{
|
||||
Text: text,
|
||||
},
|
||||
}
|
||||
|
||||
return f.sendMessage(message)
|
||||
}
|
||||
|
||||
// SendPendingPriceNotification 发送待审核价格通知卡片
|
||||
func (f *FeishuWebhook) SendPendingPriceNotification(price models.Price, providerName string, isNew bool) error {
|
||||
if f == nil || f.URL == "" {
|
||||
return nil // 如果没有配置webhook,则跳过
|
||||
}
|
||||
|
||||
var actionText string
|
||||
if isNew {
|
||||
actionText = "新增"
|
||||
} else {
|
||||
actionText = "更新"
|
||||
}
|
||||
|
||||
// 构建卡片内容
|
||||
content := fmt.Sprintf("**%s模型价格**\n\n", actionText)
|
||||
content += fmt.Sprintf("**模型名称:** %s\n", getDisplayModel(price))
|
||||
content += fmt.Sprintf("**厂商:** %s\n", providerName)
|
||||
content += fmt.Sprintf("**计费类型:** %s\n", getBillingTypeText(getDisplayBillingType(price)))
|
||||
content += fmt.Sprintf("**输入价格:** %.6f %s/1K tokens\n", getDisplayInputPrice(price), getDisplayCurrency(price))
|
||||
content += fmt.Sprintf("**输出价格:** %.6f %s/1K tokens\n", getDisplayOutputPrice(price), getDisplayCurrency(price))
|
||||
content += fmt.Sprintf("**创建者:** %s\n", price.CreatedBy)
|
||||
content += fmt.Sprintf("**创建时间:** %s", time.Now().Format("2006-01-02 15:04:05"))
|
||||
|
||||
// 如果有扩展价格字段,也显示出来
|
||||
if hasExtendedPrices(price) {
|
||||
content += "\n\n**扩展价格:**\n"
|
||||
if getDisplayInputAudioTokens(price) != nil {
|
||||
content += fmt.Sprintf("- 音频输入:%.6f %s/1K tokens\n", *getDisplayInputAudioTokens(price), getDisplayCurrency(price))
|
||||
}
|
||||
if getDisplayOutputAudioTokens(price) != nil {
|
||||
content += fmt.Sprintf("- 音频输出:%.6f %s/1K tokens\n", *getDisplayOutputAudioTokens(price), getDisplayCurrency(price))
|
||||
}
|
||||
if getDisplayCachedTokens(price) != nil {
|
||||
content += fmt.Sprintf("- 缓存:%.6f %s/1K tokens\n", *getDisplayCachedTokens(price), getDisplayCurrency(price))
|
||||
}
|
||||
if getDisplayReasoningTokens(price) != nil {
|
||||
content += fmt.Sprintf("- 推理:%.6f %s/1K tokens\n", *getDisplayReasoningTokens(price), getDisplayCurrency(price))
|
||||
}
|
||||
}
|
||||
|
||||
card := CardMessage{
|
||||
MsgType: "interactive",
|
||||
Card: Card{
|
||||
Schema: "2.0",
|
||||
Config: CardConfig{
|
||||
UpdateMulti: true,
|
||||
},
|
||||
Header: CardHeader{
|
||||
Title: Title{
|
||||
Tag: "plain_text",
|
||||
Content: fmt.Sprintf("🔔 有新的价格待审核 - %s", actionText),
|
||||
},
|
||||
Template: "orange",
|
||||
Padding: "12px 12px 12px 12px",
|
||||
},
|
||||
Body: CardBody{
|
||||
Direction: "vertical",
|
||||
Padding: "12px 12px 12px 12px",
|
||||
Elements: []CardElement{
|
||||
{
|
||||
Tag: "markdown",
|
||||
Content: content,
|
||||
TextAlign: "left",
|
||||
TextSize: "normal",
|
||||
Margin: "0px 0px 0px 0px",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return f.sendMessage(card)
|
||||
}
|
||||
|
||||
// SendBatchNotification 发送批量通知
|
||||
func (f *FeishuWebhook) SendBatchNotification(count int) error {
|
||||
if f == nil || f.URL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
content := fmt.Sprintf("📢 **批量价格更新通知**\n\n本次共有 **%d** 个模型价格等待审核,请及时处理。", count)
|
||||
|
||||
card := CardMessage{
|
||||
MsgType: "interactive",
|
||||
Card: Card{
|
||||
Schema: "2.0",
|
||||
Config: CardConfig{
|
||||
UpdateMulti: true,
|
||||
},
|
||||
Header: CardHeader{
|
||||
Title: Title{
|
||||
Tag: "plain_text",
|
||||
Content: "📊 批量价格更新通知",
|
||||
},
|
||||
Template: "blue",
|
||||
Padding: "12px 12px 12px 12px",
|
||||
},
|
||||
Body: CardBody{
|
||||
Direction: "vertical",
|
||||
Padding: "12px 12px 12px 12px",
|
||||
Elements: []CardElement{
|
||||
{
|
||||
Tag: "markdown",
|
||||
Content: content,
|
||||
TextAlign: "left",
|
||||
TextSize: "normal",
|
||||
Margin: "0px 0px 0px 0px",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return f.sendMessage(card)
|
||||
}
|
||||
|
||||
// SendPendingPricesDetailedNotification 发送详细的待审核价格统计通知
|
||||
func (f *FeishuWebhook) SendPendingPricesDetailedNotification(content string, count int) error {
|
||||
if f == nil || f.URL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
card := CardMessage{
|
||||
MsgType: "interactive",
|
||||
Card: Card{
|
||||
Schema: "2.0",
|
||||
Config: CardConfig{
|
||||
UpdateMulti: true,
|
||||
},
|
||||
Header: CardHeader{
|
||||
Title: Title{
|
||||
Tag: "plain_text",
|
||||
Content: fmt.Sprintf("🔍 待审核价格检查报告 - %d个待审核", count),
|
||||
},
|
||||
Template: "red",
|
||||
Padding: "12px 12px 12px 12px",
|
||||
},
|
||||
Body: CardBody{
|
||||
Direction: "vertical",
|
||||
Padding: "12px 12px 12px 12px",
|
||||
Elements: []CardElement{
|
||||
{
|
||||
Tag: "markdown",
|
||||
Content: content,
|
||||
TextAlign: "left",
|
||||
TextSize: "normal",
|
||||
Margin: "0px 0px 0px 0px",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return f.sendMessage(card)
|
||||
}
|
||||
|
||||
// sendMessage 发送消息到飞书
|
||||
func (f *FeishuWebhook) sendMessage(message interface{}) error {
|
||||
jsonData, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal message: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.Post(f.URL, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send webhook: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("webhook returned status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的模型名称
|
||||
func getDisplayModel(price models.Price) string {
|
||||
if price.TempModel != nil {
|
||||
return *price.TempModel
|
||||
}
|
||||
return price.Model
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的计费类型
|
||||
func getDisplayBillingType(price models.Price) string {
|
||||
if price.TempBillingType != nil {
|
||||
return *price.TempBillingType
|
||||
}
|
||||
return price.BillingType
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的货币
|
||||
func getDisplayCurrency(price models.Price) string {
|
||||
if price.TempCurrency != nil {
|
||||
return *price.TempCurrency
|
||||
}
|
||||
return price.Currency
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的输入价格
|
||||
func getDisplayInputPrice(price models.Price) float64 {
|
||||
if price.TempInputPrice != nil {
|
||||
return *price.TempInputPrice
|
||||
}
|
||||
return price.InputPrice
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的输出价格
|
||||
func getDisplayOutputPrice(price models.Price) float64 {
|
||||
if price.TempOutputPrice != nil {
|
||||
return *price.TempOutputPrice
|
||||
}
|
||||
return price.OutputPrice
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的音频输入价格
|
||||
func getDisplayInputAudioTokens(price models.Price) *float64 {
|
||||
if price.TempInputAudioTokens != nil {
|
||||
return price.TempInputAudioTokens
|
||||
}
|
||||
return price.InputAudioTokens
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的音频输出价格
|
||||
func getDisplayOutputAudioTokens(price models.Price) *float64 {
|
||||
if price.TempOutputAudioTokens != nil {
|
||||
return price.TempOutputAudioTokens
|
||||
}
|
||||
return price.OutputAudioTokens
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的缓存价格
|
||||
func getDisplayCachedTokens(price models.Price) *float64 {
|
||||
if price.TempCachedTokens != nil {
|
||||
return price.TempCachedTokens
|
||||
}
|
||||
return price.CachedTokens
|
||||
}
|
||||
|
||||
// 辅助函数:获取显示用的推理价格
|
||||
func getDisplayReasoningTokens(price models.Price) *float64 {
|
||||
if price.TempReasoningTokens != nil {
|
||||
return price.TempReasoningTokens
|
||||
}
|
||||
return price.ReasoningTokens
|
||||
}
|
||||
|
||||
// 辅助函数:检查是否有扩展价格字段
|
||||
func hasExtendedPrices(price models.Price) bool {
|
||||
return getDisplayInputAudioTokens(price) != nil ||
|
||||
getDisplayOutputAudioTokens(price) != nil ||
|
||||
getDisplayCachedTokens(price) != nil ||
|
||||
getDisplayReasoningTokens(price) != nil
|
||||
}
|
||||
|
||||
// 辅助函数:获取计费类型中文显示
|
||||
func getBillingTypeText(billingType string) string {
|
||||
switch billingType {
|
||||
case "token":
|
||||
return "按Token计费"
|
||||
case "request":
|
||||
return "按请求计费"
|
||||
case "minute":
|
||||
return "按分钟计费"
|
||||
case "hour":
|
||||
return "按小时计费"
|
||||
case "day":
|
||||
return "按天计费"
|
||||
case "month":
|
||||
return "按月计费"
|
||||
default:
|
||||
return billingType
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ services:
|
||||
- GIN_MODE=release
|
||||
- PORT=8080
|
||||
- TZ=Asia/Shanghai
|
||||
# 飞书Webhook配置(可选)
|
||||
# - FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-url
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
ports:
|
||||
|
Loading…
x
Reference in New Issue
Block a user