Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

204 строки
6.3 KiB

  1. package controller
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. "time"
  10. "github.com/QuantumNous/new-api/common"
  11. "github.com/QuantumNous/new-api/constant"
  12. "github.com/QuantumNous/new-api/logger"
  13. "github.com/QuantumNous/new-api/model"
  14. "github.com/QuantumNous/new-api/service"
  15. "github.com/QuantumNous/new-api/setting/system_setting"
  16. "github.com/gin-gonic/gin"
  17. )
  18. const defaultDoubaoAssetBaseURL = "https://ark.cn-beijing.volcengineapi.com"
  19. var blockedAssetActions = map[string]struct{}{
  20. "createassetgroup": {},
  21. "getassetgroup": {},
  22. "listassetgroups": {},
  23. "updateassetgroup": {},
  24. "deleteassetgroup": {},
  25. }
  26. func assetProxyError(c *gin.Context, status int, errType, message string) {
  27. c.JSON(status, gin.H{
  28. "error": gin.H{
  29. "message": message,
  30. "type": errType,
  31. },
  32. })
  33. }
  34. func buildDoubaoAssetURL(channel *model.Channel, action string, version string) (string, error) {
  35. baseURL := defaultDoubaoAssetBaseURL
  36. if channel != nil && channel.BaseURL != nil {
  37. if configuredBaseURL := strings.TrimSpace(*channel.BaseURL); configuredBaseURL != "" {
  38. baseURL = configuredBaseURL
  39. }
  40. }
  41. u, err := url.Parse(baseURL)
  42. if err != nil {
  43. return "", err
  44. }
  45. if u.Scheme == "" || u.Host == "" {
  46. return "", fmt.Errorf("invalid Doubao asset base URL: %s", baseURL)
  47. }
  48. u.Path = strings.TrimRight(u.Path, "/") + "/api/v1/multimodal/sd/assets"
  49. u.RawQuery = ""
  50. u.Fragment = ""
  51. query := u.Query()
  52. query.Set("Action", action)
  53. if strings.TrimSpace(version) == "" {
  54. version = "2024-01-01"
  55. }
  56. query.Set("Version", version)
  57. u.RawQuery = query.Encode()
  58. return u.String(), nil
  59. }
  60. func effectiveDoubaoAssetGroup(c *gin.Context) string {
  61. if group := strings.TrimSpace(common.GetContextKeyString(c, constant.ContextKeyUsingGroup)); group != "" {
  62. return group
  63. }
  64. return strings.TrimSpace(common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
  65. }
  66. func concreteDoubaoAssetGroupsForRequest(c *gin.Context, autoGroups func(string) []string) []string {
  67. group := effectiveDoubaoAssetGroup(c)
  68. if group != "auto" {
  69. if group == "" {
  70. return nil
  71. }
  72. return []string{group}
  73. }
  74. userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup)
  75. groups := autoGroups(userGroup)
  76. concreteGroups := make([]string, 0, len(groups))
  77. for _, candidate := range groups {
  78. candidate = strings.TrimSpace(candidate)
  79. if candidate == "" || candidate == "auto" {
  80. continue
  81. }
  82. concreteGroups = append(concreteGroups, candidate)
  83. }
  84. return concreteGroups
  85. }
  86. func resolveDoubaoAssetChannelForRequest(c *gin.Context) (*model.Channel, string, error) {
  87. userId := c.GetInt("id")
  88. var lastErr error
  89. for _, group := range concreteDoubaoAssetGroupsForRequest(c, service.GetUserAutoGroup) {
  90. channel, err := service.ResolveDoubaoAssetChannel(userId, group)
  91. if err != nil {
  92. lastErr = err
  93. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to resolve Doubao asset channel for group %s: %v", group, err))
  94. continue
  95. }
  96. if channel != nil {
  97. return channel, group, nil
  98. }
  99. }
  100. if lastErr != nil {
  101. return nil, "", lastErr
  102. }
  103. return nil, "", nil
  104. }
  105. func DoubaoAssetProxy(c *gin.Context) {
  106. action := strings.TrimSpace(c.Query("Action"))
  107. if action == "" {
  108. assetProxyError(c, http.StatusBadRequest, "invalid_request_error", "Action query parameter is required")
  109. return
  110. }
  111. if _, ok := blockedAssetActions[strings.ToLower(action)]; ok {
  112. assetProxyError(c, http.StatusBadRequest, "invalid_request_error", fmt.Sprintf("Asset group API (%s) is not supported", action))
  113. return
  114. }
  115. version := strings.TrimSpace(c.Query("Version"))
  116. if version == "" {
  117. version = "2024-01-01"
  118. }
  119. channel, _, err := resolveDoubaoAssetChannelForRequest(c)
  120. if err != nil {
  121. assetProxyError(c, http.StatusBadGateway, "server_error", fmt.Sprintf("Failed to resolve Doubao asset channel: %v", err))
  122. return
  123. }
  124. if channel == nil {
  125. assetProxyError(c, http.StatusBadGateway, "server_error", "Failed to resolve Doubao asset channel")
  126. return
  127. }
  128. if strings.TrimSpace(channel.Key) == "" {
  129. assetProxyError(c, http.StatusBadGateway, "server_error", "Doubao asset channel key is missing")
  130. return
  131. }
  132. upstreamURL, err := buildDoubaoAssetURL(channel, action, version)
  133. if err != nil {
  134. assetProxyError(c, http.StatusBadGateway, "server_error", fmt.Sprintf("Failed to build upstream URL: %v", err))
  135. return
  136. }
  137. fetchSetting := system_setting.GetFetchSetting()
  138. if err := common.ValidateURLWithFetchSetting(upstreamURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil {
  139. logger.LogError(c.Request.Context(), fmt.Sprintf("Doubao asset URL blocked: %v", err))
  140. assetProxyError(c, http.StatusForbidden, "server_error", fmt.Sprintf("request blocked: %v", err))
  141. return
  142. }
  143. client, err := service.GetHttpClientWithProxy(channel.GetSetting().Proxy)
  144. if err != nil {
  145. assetProxyError(c, http.StatusBadGateway, "server_error", fmt.Sprintf("Failed to create proxy client: %v", err))
  146. return
  147. }
  148. if client == nil {
  149. assetProxyError(c, http.StatusBadGateway, "server_error", "Failed to create proxy client")
  150. return
  151. }
  152. ctx, cancel := context.WithTimeout(c.Request.Context(), 60*time.Second)
  153. defer cancel()
  154. req, err := http.NewRequestWithContext(ctx, http.MethodPost, upstreamURL, c.Request.Body)
  155. if err != nil {
  156. assetProxyError(c, http.StatusBadGateway, "server_error", fmt.Sprintf("Failed to create upstream request: %v", err))
  157. return
  158. }
  159. req.Header.Set("Content-Type", "application/json")
  160. req.Header.Set("Accept", "application/json")
  161. req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(channel.Key))
  162. resp, err := client.Do(req)
  163. if err != nil {
  164. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to proxy Doubao asset request to %s: %s", upstreamURL, err.Error()))
  165. assetProxyError(c, http.StatusBadGateway, "server_error", fmt.Sprintf("Failed to proxy Doubao asset request: %v", err))
  166. return
  167. }
  168. defer resp.Body.Close()
  169. for key, values := range resp.Header {
  170. for _, value := range values {
  171. c.Writer.Header().Add(key, value)
  172. }
  173. }
  174. c.Writer.WriteHeader(resp.StatusCode)
  175. if _, err = io.Copy(c.Writer, resp.Body); err != nil {
  176. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to copy Doubao asset upstream response: %s", err.Error()))
  177. }
  178. }