Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

135 linhas
4.8 KiB

  1. package service
  2. import (
  3. "context"
  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. )
  11. // ManagedAssetGroupCreator creates an upstream asset group for a user on a
  12. // channel; implementations differ per upstream (China Mobile, DoubaoVideo).
  13. type ManagedAssetGroupCreator func(context.Context, int, *model.Channel) (string, *AssetError)
  14. func IsChinaMobileAssetChannel(channel *model.Channel) bool {
  15. return channel != nil && channel.Type == constant.ChannelTypeChinaMobileSeedance
  16. }
  17. func IsAssetGroupOperation(operation AssetOperation) bool {
  18. return strings.HasPrefix(string(operation), "asset_group.")
  19. }
  20. // GetOrCreateUserAssetGroup returns the platform-managed upstream asset group
  21. // for (user, channel). Shared by managed channels (China Mobile, DoubaoVideo):
  22. // the first request creates the upstream group via the provided creator and
  23. // persists the mapping; concurrent first requests reconcile via re-read.
  24. func GetOrCreateUserAssetGroup(ctx context.Context, userId int, channel *model.Channel, createGroup ManagedAssetGroupCreator) (string, *AssetError) {
  25. if channel == nil {
  26. return "", newAssetError(AssetErrorServer, "managed asset channel is required", http.StatusInternalServerError)
  27. }
  28. binding, err := model.GetUserAssetGroup(userId, channel.Id)
  29. if err != nil {
  30. return "", newAssetError(AssetErrorServer, err.Error(), http.StatusInternalServerError)
  31. }
  32. if binding != nil {
  33. return binding.GroupId, nil
  34. }
  35. groupId, assetErr := createGroup(ctx, userId, channel)
  36. if assetErr != nil {
  37. return "", assetErr
  38. }
  39. if strings.TrimSpace(groupId) == "" {
  40. return "", newAssetError(AssetErrorUpstream, "asset group creation returned an empty group ID", http.StatusBadGateway)
  41. }
  42. if err := model.CreateUserAssetGroup(userId, channel.Id, groupId); err == nil {
  43. return groupId, nil
  44. }
  45. // Another first request may have persisted the binding while this one created its upstream group.
  46. binding, readErr := model.GetUserAssetGroup(userId, channel.Id)
  47. if readErr == nil && binding != nil {
  48. return binding.GroupId, nil
  49. }
  50. if readErr != nil {
  51. return "", newAssetError(AssetErrorServer, readErr.Error(), http.StatusInternalServerError)
  52. }
  53. return "", newAssetError(AssetErrorServer, "failed to persist user asset group", http.StatusInternalServerError)
  54. }
  55. func CreateChinaMobileUserAssetGroup(ctx context.Context, userId int, adapter AssetAdapter, channel *model.Channel) (string, *AssetError) {
  56. spec, ok := ParseAssetAction("CreateAssetGroup")
  57. if !ok {
  58. return "", newAssetError(AssetErrorServer, "CreateAssetGroup action is not registered", http.StatusInternalServerError)
  59. }
  60. resp, assetErr := adapter.DoAssetRequest(ctx, channel, AssetRequest{
  61. Action: spec,
  62. Version: "2024-01-01",
  63. Body: map[string]any{
  64. "GroupType": "AIGC",
  65. "Name": fmt.Sprintf("new-api-user-%d-channel-%d", userId, channel.Id),
  66. },
  67. })
  68. if assetErr != nil {
  69. return "", assetErr
  70. }
  71. var payload struct {
  72. Result struct {
  73. GroupId string `json:"GroupId"`
  74. } `json:"Result"`
  75. }
  76. if err := common.Unmarshal(resp.Body, &payload); err != nil {
  77. return "", newAssetError(AssetErrorUpstream, fmt.Sprintf("invalid China Mobile asset group response: %v", err), http.StatusBadGateway)
  78. }
  79. return strings.TrimSpace(payload.Result.GroupId), nil
  80. }
  81. func ScopeManagedAssetRequest(req *AssetRequest, groupId string) {
  82. switch req.Action.Operation {
  83. case AssetOperationAssetCreate:
  84. req.Body["GroupId"] = groupId
  85. case AssetOperationAssetList:
  86. filter := mapValue(req.Body, "Filter")
  87. if filter == nil {
  88. return
  89. }
  90. filter["GroupIds"] = []string{groupId}
  91. req.Body["Filter"] = filter
  92. }
  93. }
  94. func RequireManagedAssetOwnership(ctx context.Context, adapter AssetAdapter, channel *model.Channel, request AssetRequest, groupId string) *AssetError {
  95. spec, ok := ParseAssetAction("GetAsset")
  96. if !ok {
  97. return newAssetError(AssetErrorServer, "GetAsset action is not registered", http.StatusInternalServerError)
  98. }
  99. assetId := strings.TrimSpace(stringValue(request.Body, "Id"))
  100. resp, assetErr := adapter.DoAssetRequest(ctx, channel, AssetRequest{
  101. Action: spec,
  102. Version: request.Version,
  103. Body: map[string]any{"Id": assetId},
  104. })
  105. if assetErr != nil {
  106. if assetErr.HTTPStatus == http.StatusNotFound {
  107. return newAssetError(AssetErrorNotFound, "asset not found", http.StatusNotFound)
  108. }
  109. return assetErr
  110. }
  111. var payload struct {
  112. Result struct {
  113. GroupId string `json:"GroupId"`
  114. } `json:"Result"`
  115. }
  116. if err := common.Unmarshal(resp.Body, &payload); err != nil {
  117. return newAssetError(AssetErrorUpstream, fmt.Sprintf("invalid asset response: %v", err), http.StatusBadGateway)
  118. }
  119. if strings.TrimSpace(payload.Result.GroupId) != groupId {
  120. return newAssetError(AssetErrorNotFound, "asset not found", http.StatusNotFound)
  121. }
  122. return nil
  123. }