mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-06 19:36:05 +00:00
Compare commits
9 Commits
feature/su
...
v0.10.8-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1e6c1bf77 | ||
|
|
1ee80930d4 | ||
|
|
35a4c586aa | ||
|
|
85b5d0100a | ||
|
|
6a9522ac5b | ||
|
|
59c076978e | ||
|
|
5889856a55 | ||
|
|
9da3412fde | ||
|
|
8d67c571e4 |
32
.github/workflows/docker-image-arm64.yml
vendored
32
.github/workflows/docker-image-arm64.yml
vendored
@@ -4,6 +4,12 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag name to build (e.g., v0.10.8-alpha.3)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build_single_arch:
|
||||
@@ -25,15 +31,24 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Check out (shallow)
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-depth: ${{ github.event_name == 'workflow_dispatch' && 0 || 1 }}
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
|
||||
- name: Resolve tag & write VERSION
|
||||
run: |
|
||||
git fetch --tags --force --depth=1
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
if [ -n "${{ github.event.inputs.tag }}" ]; then
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
# Verify tag exists
|
||||
if ! git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then
|
||||
echo "Error: Tag '$TAG' does not exist in the repository"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
fi
|
||||
echo "TAG=$TAG" >> $GITHUB_ENV
|
||||
echo "$TAG" > VERSION
|
||||
echo "Building tag: $TAG for ${{ matrix.arch }}"
|
||||
@@ -87,10 +102,15 @@ jobs:
|
||||
name: Create multi-arch manifests (Docker Hub)
|
||||
needs: [build_single_arch]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- name: Extract tag
|
||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.tag }}" ]; then
|
||||
echo "TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
fi
|
||||
#
|
||||
# - name: Normalize GHCR repository
|
||||
# run: echo "GHCR_REPOSITORY=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||
|
||||
@@ -445,6 +445,14 @@ Bienvenue à toutes les formes de contribution!
|
||||
|
||||
---
|
||||
|
||||
## 📜 Licence
|
||||
|
||||
Ce projet est sous licence [GNU Affero General Public License v3.0 (AGPLv3)](./LICENSE).
|
||||
|
||||
Si les politiques de votre organisation ne permettent pas l'utilisation de logiciels sous licence AGPLv3, ou si vous souhaitez éviter les obligations open-source de l'AGPLv3, veuillez nous contacter à : [support@quantumnous.com](mailto:support@quantumnous.com)
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Historique des étoiles
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -445,6 +445,14 @@ docker run --name new-api -d --restart always \
|
||||
|
||||
---
|
||||
|
||||
## 📜 ライセンス
|
||||
|
||||
このプロジェクトは [GNU Affero General Public License v3.0 (AGPLv3)](./LICENSE) の下でライセンスされています。
|
||||
|
||||
お客様の組織のポリシーがAGPLv3ライセンスのソフトウェアの使用を許可していない場合、またはAGPLv3のオープンソース義務を回避したい場合は、こちらまでお問い合わせください:[support@quantumnous.com](mailto:support@quantumnous.com)
|
||||
|
||||
---
|
||||
|
||||
## 🌟 スター履歴
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -445,6 +445,14 @@ Welcome all forms of contribution!
|
||||
|
||||
---
|
||||
|
||||
## 📜 License
|
||||
|
||||
This project is licensed under the [GNU Affero General Public License v3.0 (AGPLv3)](./LICENSE).
|
||||
|
||||
If your organization's policies do not permit the use of AGPLv3-licensed software, or if you wish to avoid the open-source obligations of AGPLv3, please contact us at: [support@quantumnous.com](mailto:support@quantumnous.com)
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star History
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -445,6 +445,14 @@ docker run --name new-api -d --restart always \
|
||||
|
||||
---
|
||||
|
||||
## 📜 许可证
|
||||
|
||||
本项目采用 [GNU Affero 通用公共许可证 v3.0 (AGPLv3)](./LICENSE) 授权。
|
||||
|
||||
如果您所在的组织政策不允许使用 AGPLv3 许可的软件,或您希望规避 AGPLv3 的开源义务,请发送邮件至:[support@quantumnous.com](mailto:support@quantumnous.com)
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star History
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -118,6 +118,14 @@ func AdminCreateSubscriptionPlan(c *gin.Context) {
|
||||
common.ApiErrorMsg(c, "套餐标题不能为空")
|
||||
return
|
||||
}
|
||||
if req.Plan.PriceAmount < 0 {
|
||||
common.ApiErrorMsg(c, "价格不能为负数")
|
||||
return
|
||||
}
|
||||
if req.Plan.PriceAmount > 9999 {
|
||||
common.ApiErrorMsg(c, "价格不能超过9999")
|
||||
return
|
||||
}
|
||||
if req.Plan.Currency == "" {
|
||||
req.Plan.Currency = "USD"
|
||||
}
|
||||
@@ -172,6 +180,14 @@ func AdminUpdateSubscriptionPlan(c *gin.Context) {
|
||||
common.ApiErrorMsg(c, "套餐标题不能为空")
|
||||
return
|
||||
}
|
||||
if req.Plan.PriceAmount < 0 {
|
||||
common.ApiErrorMsg(c, "价格不能为负数")
|
||||
return
|
||||
}
|
||||
if req.Plan.PriceAmount > 9999 {
|
||||
common.ApiErrorMsg(c, "价格不能超过9999")
|
||||
return
|
||||
}
|
||||
req.Plan.Id = id
|
||||
if req.Plan.Currency == "" {
|
||||
req.Plan.Currency = "USD"
|
||||
|
||||
@@ -112,21 +112,31 @@ func SubscriptionRequestEpay(c *gin.Context) {
|
||||
}
|
||||
|
||||
func SubscriptionEpayNotify(c *gin.Context) {
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
return
|
||||
}
|
||||
params := lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.PostForm.Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
if len(params) == 0 {
|
||||
var params map[string]string
|
||||
|
||||
if c.Request.Method == "POST" {
|
||||
// POST 请求:从 POST body 解析参数
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
return
|
||||
}
|
||||
params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.PostForm.Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
} else {
|
||||
// GET 请求:从 URL Query 解析参数
|
||||
params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.URL.Query().Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
return
|
||||
}
|
||||
|
||||
client := GetEpayClient()
|
||||
if client == nil {
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
@@ -157,21 +167,31 @@ func SubscriptionEpayNotify(c *gin.Context) {
|
||||
// SubscriptionEpayReturn handles browser return after payment.
|
||||
// It verifies the payload and completes the order, then redirects to console.
|
||||
func SubscriptionEpayReturn(c *gin.Context) {
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
|
||||
return
|
||||
}
|
||||
params := lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.PostForm.Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
if len(params) == 0 {
|
||||
var params map[string]string
|
||||
|
||||
if c.Request.Method == "POST" {
|
||||
// POST 请求:从 POST body 解析参数
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
|
||||
return
|
||||
}
|
||||
params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.PostForm.Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
} else {
|
||||
// GET 请求:从 URL Query 解析参数
|
||||
params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.URL.Query().Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
|
||||
return
|
||||
}
|
||||
|
||||
client := GetEpayClient()
|
||||
if client == nil {
|
||||
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
|
||||
|
||||
@@ -228,21 +228,32 @@ func UnlockOrder(tradeNo string) {
|
||||
}
|
||||
|
||||
func EpayNotify(c *gin.Context) {
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
log.Println("易支付回调解析失败:", err)
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
return
|
||||
}
|
||||
params := lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.PostForm.Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
if len(params) == 0 {
|
||||
var params map[string]string
|
||||
|
||||
if c.Request.Method == "POST" {
|
||||
// POST 请求:从 POST body 解析参数
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
log.Println("易支付回调POST解析失败:", err)
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
return
|
||||
}
|
||||
params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.PostForm.Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
} else {
|
||||
// GET 请求:从 URL Query 解析参数
|
||||
params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.URL.Query().Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
log.Println("易支付回调参数为空")
|
||||
_, _ = c.Writer.Write([]byte("fail"))
|
||||
return
|
||||
}
|
||||
client := GetEpayClient()
|
||||
if client == nil {
|
||||
log.Println("易支付回调失败 未找到配置信息")
|
||||
|
||||
156
model/main.go
156
model/main.go
@@ -248,6 +248,9 @@ func InitLogDB() (err error) {
|
||||
}
|
||||
|
||||
func migrateDB() error {
|
||||
// Migrate price_amount column from float/double to decimal for existing tables
|
||||
migrateSubscriptionPlanPriceAmount()
|
||||
|
||||
err := DB.AutoMigrate(
|
||||
&Channel{},
|
||||
&Token{},
|
||||
@@ -268,7 +271,6 @@ func migrateDB() error {
|
||||
&TwoFA{},
|
||||
&TwoFABackupCode{},
|
||||
&Checkin{},
|
||||
&SubscriptionPlan{},
|
||||
&SubscriptionOrder{},
|
||||
&UserSubscription{},
|
||||
&SubscriptionPreConsumeRecord{},
|
||||
@@ -276,6 +278,15 @@ func migrateDB() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if common.UsingSQLite {
|
||||
if err := ensureSubscriptionPlanTableSQLite(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := DB.AutoMigrate(&SubscriptionPlan{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -306,7 +317,6 @@ func migrateDBFast() error {
|
||||
{&TwoFA{}, "TwoFA"},
|
||||
{&TwoFABackupCode{}, "TwoFABackupCode"},
|
||||
{&Checkin{}, "Checkin"},
|
||||
{&SubscriptionPlan{}, "SubscriptionPlan"},
|
||||
{&SubscriptionOrder{}, "SubscriptionOrder"},
|
||||
{&UserSubscription{}, "UserSubscription"},
|
||||
{&SubscriptionPreConsumeRecord{}, "SubscriptionPreConsumeRecord"},
|
||||
@@ -334,6 +344,15 @@ func migrateDBFast() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if common.UsingSQLite {
|
||||
if err := ensureSubscriptionPlanTableSQLite(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := DB.AutoMigrate(&SubscriptionPlan{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
common.SysLog("database migrated")
|
||||
return nil
|
||||
}
|
||||
@@ -346,6 +365,139 @@ func migrateLOGDB() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type sqliteColumnDef struct {
|
||||
Name string
|
||||
DDL string
|
||||
}
|
||||
|
||||
func ensureSubscriptionPlanTableSQLite() error {
|
||||
if !common.UsingSQLite {
|
||||
return nil
|
||||
}
|
||||
tableName := "subscription_plans"
|
||||
if !DB.Migrator().HasTable(tableName) {
|
||||
createSQL := `CREATE TABLE ` + "`" + tableName + "`" + ` (
|
||||
` + "`id`" + ` integer,
|
||||
` + "`title`" + ` varchar(128) NOT NULL,
|
||||
` + "`subtitle`" + ` varchar(255) DEFAULT '',
|
||||
` + "`price_amount`" + ` decimal(10,6) NOT NULL,
|
||||
` + "`currency`" + ` varchar(8) NOT NULL DEFAULT 'USD',
|
||||
` + "`duration_unit`" + ` varchar(16) NOT NULL DEFAULT 'month',
|
||||
` + "`duration_value`" + ` integer NOT NULL DEFAULT 1,
|
||||
` + "`custom_seconds`" + ` bigint NOT NULL DEFAULT 0,
|
||||
` + "`enabled`" + ` numeric DEFAULT 1,
|
||||
` + "`sort_order`" + ` integer DEFAULT 0,
|
||||
` + "`stripe_price_id`" + ` varchar(128) DEFAULT '',
|
||||
` + "`creem_product_id`" + ` varchar(128) DEFAULT '',
|
||||
` + "`max_purchase_per_user`" + ` integer DEFAULT 0,
|
||||
` + "`upgrade_group`" + ` varchar(64) DEFAULT '',
|
||||
` + "`total_amount`" + ` bigint NOT NULL DEFAULT 0,
|
||||
` + "`quota_reset_period`" + ` varchar(16) DEFAULT 'never',
|
||||
` + "`quota_reset_custom_seconds`" + ` bigint DEFAULT 0,
|
||||
` + "`created_at`" + ` bigint,
|
||||
` + "`updated_at`" + ` bigint,
|
||||
PRIMARY KEY (` + "`id`" + `)
|
||||
)`
|
||||
return DB.Exec(createSQL).Error
|
||||
}
|
||||
var cols []struct {
|
||||
Name string `gorm:"column:name"`
|
||||
}
|
||||
if err := DB.Raw("PRAGMA table_info(`" + tableName + "`)").Scan(&cols).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
existing := make(map[string]struct{}, len(cols))
|
||||
for _, c := range cols {
|
||||
existing[c.Name] = struct{}{}
|
||||
}
|
||||
required := []sqliteColumnDef{
|
||||
{Name: "title", DDL: "`title` varchar(128) NOT NULL"},
|
||||
{Name: "subtitle", DDL: "`subtitle` varchar(255) DEFAULT ''"},
|
||||
{Name: "price_amount", DDL: "`price_amount` decimal(10,6) NOT NULL"},
|
||||
{Name: "currency", DDL: "`currency` varchar(8) NOT NULL DEFAULT 'USD'"},
|
||||
{Name: "duration_unit", DDL: "`duration_unit` varchar(16) NOT NULL DEFAULT 'month'"},
|
||||
{Name: "duration_value", DDL: "`duration_value` integer NOT NULL DEFAULT 1"},
|
||||
{Name: "custom_seconds", DDL: "`custom_seconds` bigint NOT NULL DEFAULT 0"},
|
||||
{Name: "enabled", DDL: "`enabled` numeric DEFAULT 1"},
|
||||
{Name: "sort_order", DDL: "`sort_order` integer DEFAULT 0"},
|
||||
{Name: "stripe_price_id", DDL: "`stripe_price_id` varchar(128) DEFAULT ''"},
|
||||
{Name: "creem_product_id", DDL: "`creem_product_id` varchar(128) DEFAULT ''"},
|
||||
{Name: "max_purchase_per_user", DDL: "`max_purchase_per_user` integer DEFAULT 0"},
|
||||
{Name: "upgrade_group", DDL: "`upgrade_group` varchar(64) DEFAULT ''"},
|
||||
{Name: "total_amount", DDL: "`total_amount` bigint NOT NULL DEFAULT 0"},
|
||||
{Name: "quota_reset_period", DDL: "`quota_reset_period` varchar(16) DEFAULT 'never'"},
|
||||
{Name: "quota_reset_custom_seconds", DDL: "`quota_reset_custom_seconds` bigint DEFAULT 0"},
|
||||
{Name: "created_at", DDL: "`created_at` bigint"},
|
||||
{Name: "updated_at", DDL: "`updated_at` bigint"},
|
||||
}
|
||||
for _, col := range required {
|
||||
if _, ok := existing[col.Name]; ok {
|
||||
continue
|
||||
}
|
||||
if err := DB.Exec("ALTER TABLE `" + tableName + "` ADD COLUMN " + col.DDL).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateSubscriptionPlanPriceAmount migrates price_amount column from float/double to decimal(10,6)
|
||||
// This is safe to run multiple times - it checks the column type first
|
||||
func migrateSubscriptionPlanPriceAmount() {
|
||||
// SQLite doesn't support ALTER COLUMN, and its type affinity handles this automatically
|
||||
// Skip early to avoid GORM parsing the existing table DDL which may cause issues
|
||||
if common.UsingSQLite {
|
||||
return
|
||||
}
|
||||
|
||||
tableName := "subscription_plans"
|
||||
columnName := "price_amount"
|
||||
|
||||
// Check if table exists first
|
||||
if !DB.Migrator().HasTable(tableName) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if column exists
|
||||
if !DB.Migrator().HasColumn(&SubscriptionPlan{}, columnName) {
|
||||
return
|
||||
}
|
||||
|
||||
var alterSQL string
|
||||
if common.UsingPostgreSQL {
|
||||
// PostgreSQL: Check if already decimal/numeric
|
||||
var dataType string
|
||||
DB.Raw(`SELECT data_type FROM information_schema.columns
|
||||
WHERE table_name = ? AND column_name = ?`, tableName, columnName).Scan(&dataType)
|
||||
if dataType == "numeric" {
|
||||
return // Already decimal/numeric
|
||||
}
|
||||
alterSQL = fmt.Sprintf(`ALTER TABLE %s ALTER COLUMN %s TYPE decimal(10,6) USING %s::decimal(10,6)`,
|
||||
tableName, columnName, columnName)
|
||||
} else if common.UsingMySQL {
|
||||
// MySQL: Check if already decimal
|
||||
var columnType string
|
||||
DB.Raw(`SELECT COLUMN_TYPE FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?`,
|
||||
tableName, columnName).Scan(&columnType)
|
||||
if strings.HasPrefix(strings.ToLower(columnType), "decimal") {
|
||||
return // Already decimal
|
||||
}
|
||||
alterSQL = fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s decimal(10,6) NOT NULL DEFAULT 0",
|
||||
tableName, columnName)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if alterSQL != "" {
|
||||
if err := DB.Exec(alterSQL).Error; err != nil {
|
||||
common.SysLog(fmt.Sprintf("Warning: failed to migrate %s.%s to decimal: %v", tableName, columnName, err))
|
||||
} else {
|
||||
common.SysLog(fmt.Sprintf("Successfully migrated %s.%s to decimal(10,6)", tableName, columnName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeDB(db *gorm.DB) error {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
|
||||
@@ -149,7 +149,7 @@ type SubscriptionPlan struct {
|
||||
Subtitle string `json:"subtitle" gorm:"type:varchar(255);default:''"`
|
||||
|
||||
// Display money amount (follow existing code style: float64 for money)
|
||||
PriceAmount float64 `json:"price_amount" gorm:"type:double;not null;default:0"`
|
||||
PriceAmount float64 `json:"price_amount" gorm:"type:decimal(10,6);not null;default:0"`
|
||||
Currency string `json:"currency" gorm:"type:varchar(8);not null;default:'USD'"`
|
||||
|
||||
DurationUnit string `json:"duration_unit" gorm:"type:varchar(16);not null;default:'month'"`
|
||||
|
||||
@@ -988,11 +988,9 @@ func unescapeMapOrSlice(data interface{}) interface{} {
|
||||
func getResponseToolCall(item *dto.GeminiPart) *dto.ToolCallResponse {
|
||||
var argsBytes []byte
|
||||
var err error
|
||||
if result, ok := item.FunctionCall.Arguments.(map[string]interface{}); ok {
|
||||
argsBytes, err = json.Marshal(unescapeMapOrSlice(result))
|
||||
} else {
|
||||
argsBytes, err = json.Marshal(item.FunctionCall.Arguments)
|
||||
}
|
||||
// 移除 unescapeMapOrSlice 调用,直接使用 json.Marshal
|
||||
// JSON 序列化/反序列化已经正确处理了转义字符
|
||||
argsBytes, err = json.Marshal(item.FunctionCall.Arguments)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
@@ -59,6 +59,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
//userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog)
|
||||
userRoute.GET("/logout", controller.Logout)
|
||||
userRoute.POST("/epay/notify", controller.EpayNotify)
|
||||
userRoute.GET("/epay/notify", controller.EpayNotify)
|
||||
userRoute.GET("/groups", controller.GetUserGroups)
|
||||
|
||||
selfRoute := userRoute.Group("/")
|
||||
@@ -149,6 +150,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
|
||||
// Subscription payment callbacks (no auth)
|
||||
apiRouter.POST("/subscription/epay/notify", controller.SubscriptionEpayNotify)
|
||||
apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify)
|
||||
apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn)
|
||||
apiRouter.POST("/subscription/epay/return", controller.SubscriptionEpayReturn)
|
||||
optionRoute := apiRouter.Group("/option")
|
||||
|
||||
Reference in New Issue
Block a user