feat(gateway): 计费规则可后台配置 —— token→积分汇率(DB) + 每模型积分权重
让「扣费按规则扣」的规则真正可后台热调(此前汇率是 env、积分权重无接口): - sundynix_setting KV 表 + GetSetting/SetSetting;TokensPerCredit 改为 DB 设置优先 → env 回退 → 1000,SaveUsageEvent 按 ctx 读,改完即对后续任务生效。 - Pricing.credit_weight 纳入 UpsertPricing + ListPricing/SavePricing API。 - GET/PUT /admin/billing-config(token→积分汇率)。 live 验证:UI 存汇率=500 → billing-config/DB=500 → 新任务 credits_micro=214000 =107tok/500×1e6,规则→扣费联动精确成立。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -217,32 +217,56 @@ func (h *Handler) ListPricing(c *gin.Context) {
|
||||
out := make([]gin.H, 0, len(rows))
|
||||
for _, p := range rows {
|
||||
out = append(out, gin.H{
|
||||
"model_id": p.ModelID, "input_per_1k": p.InputPer1K, "output_per_1k": p.OutputPer1K, "currency": p.Currency,
|
||||
"model_id": p.ModelID, "input_per_1k": p.InputPer1K, "output_per_1k": p.OutputPer1K,
|
||||
"credit_weight": p.CreditWeight, "currency": p.Currency,
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"pricing": out})
|
||||
}
|
||||
|
||||
// SavePricing: PUT /api/v1/admin/pricing —— 设置某模型的输入/输出单价(每 1K token)。
|
||||
// SavePricing: PUT /api/v1/admin/pricing —— 设置某模型的输入/输出单价(每 1K token)+ 积分权重。
|
||||
func (h *Handler) SavePricing(c *gin.Context) {
|
||||
var b struct {
|
||||
ModelID string `json:"model_id"`
|
||||
InputPer1K float64 `json:"input_per_1k"`
|
||||
OutputPer1K float64 `json:"output_per_1k"`
|
||||
Currency string `json:"currency"`
|
||||
ModelID string `json:"model_id"`
|
||||
InputPer1K float64 `json:"input_per_1k"`
|
||||
OutputPer1K float64 `json:"output_per_1k"`
|
||||
CreditWeight float64 `json:"credit_weight"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&b); err != nil || b.ModelID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "model_id required"})
|
||||
return
|
||||
}
|
||||
if b.InputPer1K < 0 || b.OutputPer1K < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "单价不能为负"})
|
||||
if b.InputPer1K < 0 || b.OutputPer1K < 0 || b.CreditWeight < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "单价/权重不能为负"})
|
||||
return
|
||||
}
|
||||
if b.Currency == "" {
|
||||
b.Currency = "CNY"
|
||||
}
|
||||
if err := h.db.UpsertPricing(c.Request.Context(), b.ModelID, b.InputPer1K, b.OutputPer1K, b.Currency); err != nil {
|
||||
if err := h.db.UpsertPricing(c.Request.Context(), b.ModelID, b.InputPer1K, b.OutputPer1K, b.CreditWeight, b.Currency); err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
}
|
||||
|
||||
// BillingConfig: GET /api/v1/admin/billing-config —— 全局计费规则(当前仅 token→积分汇率)。
|
||||
func (h *Handler) BillingConfig(c *gin.Context) {
|
||||
tpc := h.db.GetSetting(c.Request.Context(), store.SettingTokensPerCredit)
|
||||
c.JSON(http.StatusOK, gin.H{"tokens_per_credit": tpc}) // 空串=未设,前端回退默认
|
||||
}
|
||||
|
||||
// SaveBillingConfig: PUT /api/v1/admin/billing-config —— 设 token→积分汇率(>0)。
|
||||
func (h *Handler) SaveBillingConfig(c *gin.Context) {
|
||||
var b struct {
|
||||
TokensPerCredit float64 `json:"tokens_per_credit"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&b); err != nil || b.TokensPerCredit <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "tokens_per_credit 必须 > 0"})
|
||||
return
|
||||
}
|
||||
if err := h.db.SetSetting(c.Request.Context(), store.SettingTokensPerCredit, strconv.FormatFloat(b.TokensPerCredit, 'f', -1, 64)); err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user