Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

109 Zeilen
4.6 KiB

  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/constant"
  9. "github.com/QuantumNous/new-api/model"
  10. "gorm.io/gorm"
  11. )
  12. // assetCredentialConfigured guards channel types whose asset adapter signs
  13. // requests with a dedicated AK/SK pair stored in channel_asset_credentials
  14. // (currently only official Volcengine DoubaoVideo). Without the credential
  15. // the channel must not serve, be bound, or be auto-matched for assets; a DB
  16. // error is treated as not configured so auto-match falls through to other
  17. // candidate channels.
  18. func assetCredentialConfigured(channel *model.Channel) bool {
  19. if channel.Type != constant.ChannelTypeDoubaoVideo {
  20. return true
  21. }
  22. credential, err := model.GetChannelAssetCredential(channel.Id)
  23. if err != nil || credential == nil {
  24. return false
  25. }
  26. return strings.TrimSpace(credential.AccessKey) != "" && strings.TrimSpace(credential.SecretKey) != ""
  27. }
  28. func ResolveAssetChannelForOperation(userID int, tokenGroup string, operation AssetOperation) (*model.Channel, AssetAdapter, *AssetError) {
  29. bindings, err := model.GetUserAssetChannelsByTypes(userID, RegisteredAssetChannelTypes(), tokenGroup)
  30. if err != nil {
  31. return nil, nil, newAssetError(AssetErrorServer, err.Error(), http.StatusInternalServerError)
  32. }
  33. if len(bindings) > 0 {
  34. binding := bindings[0]
  35. channel, err := model.CacheGetChannel(binding.ChannelId)
  36. if err != nil {
  37. if errors.Is(err, gorm.ErrRecordNotFound) || common.MemoryCacheEnabled {
  38. return nil, nil, newAssetError(AssetErrorBindingInvalid, "bound asset channel not found", http.StatusBadGateway)
  39. }
  40. return nil, nil, newAssetError(AssetErrorServer, err.Error(), http.StatusInternalServerError)
  41. }
  42. adapter, ok := GetAssetAdapter(channel.Type)
  43. if channel.Status != common.ChannelStatusEnabled || !assetChannelHasKey(channel) || !MatchDoubaoAssetGroup(channel.GetGroups(), tokenGroup) || !ok || !assetCredentialConfigured(channel) {
  44. return nil, nil, newAssetError(AssetErrorBindingInvalid, "bound asset channel is not available for asset library", http.StatusBadGateway)
  45. }
  46. if !adapter.Supports(operation) {
  47. return nil, nil, newAssetError(AssetErrorOperationNotSupported, fmt.Sprintf("asset operation %s is not supported by bound channel", operation), http.StatusBadRequest)
  48. }
  49. return channel, adapter, nil
  50. }
  51. channel, adapter, err := autoMatchAssetChannelForOperation(tokenGroup, operation)
  52. if err != nil {
  53. return nil, nil, newAssetError(AssetErrorServer, err.Error(), http.StatusInternalServerError)
  54. }
  55. if channel == nil {
  56. return nil, nil, newAssetError(AssetErrorChannelNotFound, "no available asset channel supports requested operation", http.StatusBadGateway)
  57. }
  58. // Persist the auto-matched channel as the user's asset binding so later
  59. // asset and video requests stay on the same channel (asset:// references
  60. // are passed through verbatim and must match the upload channel). This
  61. // mirrors the video-task binding backfill. Binding is best-effort: a
  62. // failure must not fail the asset operation itself.
  63. if family, ok := VideoAssetFamilyForChannelType(channel.Type); ok && tokenGroup != "" && tokenGroup != "auto" {
  64. if bindErr := BindVideoAssetChannel(userID, tokenGroup, channel, family); bindErr != nil {
  65. common.SysLog(fmt.Sprintf("failed to persist asset channel binding for user %d group %s channel %d: %s", userID, tokenGroup, channel.Id, bindErr.Error()))
  66. }
  67. }
  68. return channel, adapter, nil
  69. }
  70. func autoMatchAssetChannelForOperation(tokenGroup string, operation AssetOperation) (*model.Channel, AssetAdapter, error) {
  71. for _, channelType := range RegisteredAssetChannelTypes() {
  72. adapter, ok := GetAssetAdapter(channelType)
  73. if !ok || !adapter.Supports(operation) {
  74. continue
  75. }
  76. for startIdx := 0; ; startIdx += DoubaoAssetChannelPageSize {
  77. candidates, err := model.GetChannelsByType(startIdx, DoubaoAssetChannelPageSize, true, channelType)
  78. if err != nil {
  79. return nil, nil, err
  80. }
  81. for _, candidate := range candidates {
  82. if candidate == nil || !MatchDoubaoAssetGroup(candidate.GetGroups(), tokenGroup) {
  83. continue
  84. }
  85. channel, err := model.CacheGetChannel(candidate.Id)
  86. if err != nil {
  87. if errors.Is(err, gorm.ErrRecordNotFound) || common.MemoryCacheEnabled {
  88. continue
  89. }
  90. return nil, nil, err
  91. }
  92. if channel.Status == common.ChannelStatusEnabled && assetChannelHasKey(channel) && MatchDoubaoAssetGroup(channel.GetGroups(), tokenGroup) && assetCredentialConfigured(channel) {
  93. return channel, adapter, nil
  94. }
  95. }
  96. if len(candidates) < DoubaoAssetChannelPageSize {
  97. break
  98. }
  99. }
  100. }
  101. return nil, nil, nil
  102. }