渠道: 远程候选按渠道过滤 + key 掩码 + 移除批量导入端点
- 远程模型候选只排除本渠道已允许的模型,同名模型可被多个渠道各自允许 - 渠道 key 掩码统一 xxxxxxx******Mq4Y(保留前 7 位与后 4 位) - 移除已弃用的批量导入端点 POST /channels/:id/models/import Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/openteam/server/internal/pkg/resp"
|
||||
"github.com/openteam/server/internal/store"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// AdminChannels GET /api/v1/admin/channels — 渠道列表(不返回加密 key,返回掩码)。
|
||||
@@ -28,7 +26,7 @@ func (h *Handler) AdminChannels(c *gin.Context) {
|
||||
for _, ch := range chs {
|
||||
masked := ""
|
||||
if key, err := h.a.Enc.Decrypt(ch.APIKeyEnc); err == nil && len(key) > 8 {
|
||||
masked = strings.Repeat("*", len(key)-4) + key[len(key)-4:]
|
||||
masked = maskAPIKey(key)
|
||||
} else if err == nil {
|
||||
masked = "****"
|
||||
}
|
||||
@@ -371,87 +369,15 @@ func (h *Handler) AdminTestChannel(c *gin.Context) {
|
||||
resp.OK(c, gin.H{"ok": true, "latency_ms": latency, "message": msg})
|
||||
}
|
||||
|
||||
// AdminImportChannelModels POST /api/v1/admin/channels/:id/models/import
|
||||
// 拉取渠道 GET /v1/models,导入模型库并绑定。
|
||||
func (h *Handler) AdminImportChannelModels(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusBadRequest, "invalid channel id")
|
||||
return
|
||||
// maskAPIKey 掩码渠道密钥:保留前 7 位与后 4 位,中间固定 ****** 遮蔽。
|
||||
// 示例:xxxxxxx******Mq4Y;密钥较短时退化为仅保留后 4 位。
|
||||
func maskAPIKey(key string) string {
|
||||
if len(key) <= 11 {
|
||||
return strings.Repeat("*", len(key)-4) + key[len(key)-4:]
|
||||
}
|
||||
var ch store.Channel
|
||||
if err := h.a.DB.First(&ch, id).Error; err != nil {
|
||||
resp.Fail(c, http.StatusNotFound, "channel not found")
|
||||
return
|
||||
}
|
||||
key, err := h.a.Enc.Decrypt(ch.APIKeyEnc)
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusInternalServerError, "failed to decrypt channel key")
|
||||
return
|
||||
}
|
||||
url := ch.UpstreamURL("", "/models")
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusBadGateway, "failed to reach channel: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
if resp2.StatusCode != http.StatusOK {
|
||||
resp.Fail(c, http.StatusBadGateway, "channel returned http "+strconv.Itoa(resp2.StatusCode))
|
||||
return
|
||||
}
|
||||
var list struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(resp2.Body).Decode(&list); err != nil {
|
||||
resp.Fail(c, http.StatusBadGateway, "failed to parse model list")
|
||||
return
|
||||
}
|
||||
if len(list.Data) == 0 {
|
||||
resp.Fail(c, http.StatusNotFound, "channel returned no models")
|
||||
return
|
||||
}
|
||||
|
||||
imported := 0
|
||||
err = h.a.DB.Transaction(func(tx *gorm.DB) error {
|
||||
for _, item := range list.Data {
|
||||
name := strings.TrimSpace(item.ID)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
var m store.Model
|
||||
if err := tx.Where("name = ?", name).FirstOrCreate(&m, store.Model{
|
||||
Name: name, DisplayName: name, Enabled: true,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// upsert 绑定(upstream_model 默认同名)
|
||||
var binding store.ChannelModelBinding
|
||||
err := tx.Where("channel_id = ? AND model_id = ?", ch.ID, m.ID).First(&binding).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
binding = store.ChannelModelBinding{ChannelID: ch.ID, ModelID: m.ID, UpstreamModel: name, Weight: 1}
|
||||
if err := tx.Create(&binding).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
imported++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusInternalServerError, "failed to import models")
|
||||
return
|
||||
}
|
||||
resp.OK(c, gin.H{"imported": imported})
|
||||
return key[:7] + "******" + key[len(key)-4:]
|
||||
}
|
||||
|
||||
var _ = clause.Assignments // 保留 gorm/clause 引用(后续定价批处理用)
|
||||
|
||||
func intOr(p *int, def int) int {
|
||||
if p == nil {
|
||||
return def
|
||||
|
||||
Reference in New Issue
Block a user