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

326 строки
8.4 KiB

  1. package controller
  2. import (
  3. "strconv"
  4. "strings"
  5. "github.com/QuantumNous/new-api/common"
  6. "github.com/QuantumNous/new-api/model"
  7. "github.com/QuantumNous/new-api/setting/ratio_setting"
  8. "github.com/gin-gonic/gin"
  9. )
  10. // GetAllChannelPricing 获取所有渠道定价(分页)
  11. func GetAllChannelPricing(c *gin.Context) {
  12. page, _ := strconv.Atoi(c.DefaultQuery("p", "1"))
  13. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
  14. offset := (page - 1) * pageSize
  15. list, total, err := model.GetAllChannelPricing(offset, pageSize)
  16. if err != nil {
  17. common.ApiError(c, err)
  18. return
  19. }
  20. common.ApiSuccess(c, gin.H{
  21. "page": page,
  22. "page_size": pageSize,
  23. "total": total,
  24. "items": list,
  25. })
  26. }
  27. // GetChannelPricingByModel 获取指定模型的所有渠道定价
  28. func GetChannelPricingByModel(c *gin.Context) {
  29. modelName := c.Param("name")
  30. if modelName == "" {
  31. common.ApiErrorMsg(c, "model name is required")
  32. return
  33. }
  34. list, err := model.GetChannelPricingByModel(modelName)
  35. if err != nil {
  36. common.ApiError(c, err)
  37. return
  38. }
  39. common.ApiSuccess(c, list)
  40. }
  41. // CreateChannelPricingRequest 创建渠道定价请求
  42. type CreateChannelPricingRequest struct {
  43. Id int `json:"id"`
  44. ModelName string `json:"model_name" binding:"required"`
  45. ChannelId int `json:"channel_id" binding:"required"`
  46. QuotaType int `json:"quota_type"`
  47. ModelRatio float64 `json:"model_ratio"`
  48. CompletionRatio float64 `json:"completion_ratio"`
  49. ModelPrice float64 `json:"model_price"`
  50. TagIds string `json:"tag_ids"`
  51. }
  52. // CreateChannelPricing 创建或更新渠道定价
  53. func CreateChannelPricing(c *gin.Context) {
  54. var req CreateChannelPricingRequest
  55. if err := c.ShouldBindJSON(&req); err != nil {
  56. common.ApiError(c, err)
  57. return
  58. }
  59. // 检查是否已存在
  60. existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId)
  61. if existing != nil {
  62. // 更新
  63. existing.QuotaType = req.QuotaType
  64. existing.ModelRatio = req.ModelRatio
  65. existing.CompletionRatio = req.CompletionRatio
  66. existing.ModelPrice = req.ModelPrice
  67. existing.TagIds = req.TagIds
  68. if err := existing.Update(); err != nil {
  69. common.ApiError(c, err)
  70. return
  71. }
  72. common.ApiSuccess(c, existing)
  73. return
  74. }
  75. // 创建
  76. cp := &model.ChannelPricing{
  77. ModelName: req.ModelName,
  78. ChannelId: req.ChannelId,
  79. QuotaType: req.QuotaType,
  80. ModelRatio: req.ModelRatio,
  81. CompletionRatio: req.CompletionRatio,
  82. ModelPrice: req.ModelPrice,
  83. TagIds: req.TagIds,
  84. }
  85. if err := cp.Insert(); err != nil {
  86. common.ApiError(c, err)
  87. return
  88. }
  89. common.ApiSuccess(c, cp)
  90. }
  91. // BatchCreateChannelPricingRequest 批量创建请求
  92. type BatchCreateChannelPricingRequest struct {
  93. Items []*CreateChannelPricingRequest `json:"items" binding:"required"`
  94. }
  95. // BatchCreateChannelPricing 批量创建或更新渠道定价
  96. func BatchCreateChannelPricing(c *gin.Context) {
  97. var req BatchCreateChannelPricingRequest
  98. if err := c.ShouldBindJSON(&req); err != nil {
  99. common.ApiError(c, err)
  100. return
  101. }
  102. pricings := make([]*model.ChannelPricing, 0, len(req.Items))
  103. for _, item := range req.Items {
  104. pricings = append(pricings, &model.ChannelPricing{
  105. ModelName: item.ModelName,
  106. ChannelId: item.ChannelId,
  107. QuotaType: item.QuotaType,
  108. ModelRatio: item.ModelRatio,
  109. CompletionRatio: item.CompletionRatio,
  110. ModelPrice: item.ModelPrice,
  111. TagIds: item.TagIds,
  112. })
  113. }
  114. // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致)
  115. for _, cp := range pricings {
  116. existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId)
  117. if existing != nil {
  118. existing.QuotaType = cp.QuotaType
  119. existing.ModelRatio = cp.ModelRatio
  120. existing.CompletionRatio = cp.CompletionRatio
  121. existing.ModelPrice = cp.ModelPrice
  122. existing.TagIds = cp.TagIds
  123. if err := existing.Update(); err != nil {
  124. common.ApiError(c, err)
  125. return
  126. }
  127. } else {
  128. if err := cp.Insert(); err != nil {
  129. common.ApiError(c, err)
  130. return
  131. }
  132. }
  133. }
  134. common.ApiSuccess(c, gin.H{"affected": len(pricings)})
  135. }
  136. // DeleteChannelPricing 删除渠道定价
  137. func DeleteChannelPricing(c *gin.Context) {
  138. idStr := c.Param("id")
  139. id, err := strconv.Atoi(idStr)
  140. if err != nil {
  141. common.ApiError(c, err)
  142. return
  143. }
  144. cp := &model.ChannelPricing{Id: id}
  145. if err := cp.Delete(); err != nil {
  146. common.ApiError(c, err)
  147. return
  148. }
  149. common.ApiSuccess(c, nil)
  150. }
  151. // CopyGlobalPricingRequest 复制全局定价请求
  152. type CopyGlobalPricingRequest struct {
  153. Overwrite bool `json:"overwrite"` // 是否覆盖已存在的渠道定价
  154. }
  155. // CopyGlobalPricing 复制全局定价到指定渠道
  156. // 从 ratio_setting 读取全局定价信息,复制到 channel_pricing 表
  157. func CopyGlobalPricing(c *gin.Context) {
  158. channelIdStr := c.Param("channel_id")
  159. channelId, err := strconv.Atoi(channelIdStr)
  160. if err != nil {
  161. common.ApiError(c, err)
  162. return
  163. }
  164. var req CopyGlobalPricingRequest
  165. c.ShouldBindJSON(&req)
  166. // 获取该渠道支持的所有模型(使用 GetAbilitiesByChannelId)
  167. abilities, err := model.GetAbilitiesByChannelId(channelId)
  168. if err != nil {
  169. common.ApiError(c, err)
  170. return
  171. }
  172. imported := 0
  173. for _, ability := range abilities {
  174. // 检查是否已存在
  175. existing, _ := model.GetChannelPricing(ability.Model, channelId)
  176. if existing != nil && !req.Overwrite {
  177. continue
  178. }
  179. // 确定定价类型
  180. var quotaType int
  181. var ratio, completionRatio, price float64
  182. // 优先检查是否有按次计费的价格
  183. modelPrice, hasPrice := ratio_setting.GetModelPrice(ability.Model, false)
  184. if hasPrice {
  185. quotaType = model.QuotaTypeByCall
  186. price = modelPrice
  187. } else {
  188. // 使用按量计费
  189. quotaType = model.QuotaTypeByTokens
  190. modelRatio, hasRatio, _ := ratio_setting.GetModelRatio(ability.Model)
  191. if hasRatio {
  192. ratio = modelRatio
  193. }
  194. completionRatio = ratio_setting.GetCompletionRatio(ability.Model)
  195. }
  196. if existing != nil {
  197. existing.QuotaType = quotaType
  198. existing.ModelRatio = ratio
  199. existing.CompletionRatio = completionRatio
  200. existing.ModelPrice = price
  201. if err := existing.Update(); err == nil {
  202. imported++
  203. }
  204. } else {
  205. cp := &model.ChannelPricing{
  206. ModelName: ability.Model,
  207. ChannelId: channelId,
  208. QuotaType: quotaType,
  209. ModelRatio: ratio,
  210. CompletionRatio: completionRatio,
  211. ModelPrice: price,
  212. }
  213. if err := cp.Insert(); err == nil {
  214. imported++
  215. }
  216. }
  217. }
  218. common.ApiSuccess(c, gin.H{
  219. "total": len(abilities),
  220. "imported": imported,
  221. })
  222. }
  223. // ChannelPricingWithTags 带标签详情的渠道定价响应
  224. type ChannelPricingWithTags struct {
  225. *model.ChannelPricing
  226. Tags []*model.PricingTag `json:"tags"`
  227. }
  228. // GetChannelPricingByModelWithChannelInfo 获取指定模型的渠道定价(带渠道信息,所有用户可访问)
  229. func GetChannelPricingByModelWithChannelInfo(c *gin.Context) {
  230. // 使用通配符路由时,参数包含前导斜杠,需要去除
  231. modelName := c.Param("name")
  232. if modelName == "" {
  233. common.ApiErrorMsg(c, "model name is required")
  234. return
  235. }
  236. // 去除前导斜杠(路由是 /channel-pricing/model/*name,name 会是 "/deepseek-ai/xxx")
  237. modelName = strings.TrimPrefix(modelName, "/")
  238. list, err := model.GetChannelPricingByModelWithChannelInfo(modelName)
  239. if err != nil {
  240. common.ApiError(c, err)
  241. return
  242. }
  243. common.ApiSuccess(c, list)
  244. }
  245. // GetChannelPricingWithTags 获取渠道定价(带标签详情)
  246. func GetChannelPricingWithTags(c *gin.Context) {
  247. page, _ := strconv.Atoi(c.DefaultQuery("p", "1"))
  248. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
  249. offset := (page - 1) * pageSize
  250. list, total, err := model.GetAllChannelPricing(offset, pageSize)
  251. if err != nil {
  252. common.ApiError(c, err)
  253. return
  254. }
  255. // 获取所有标签
  256. tags, _ := model.GetAllPricingTags()
  257. tagMap := make(map[int]*model.PricingTag)
  258. for _, tag := range tags {
  259. tagMap[tag.Id] = tag
  260. }
  261. // 为每个定价填充标签详情
  262. result := make([]*ChannelPricingWithTags, 0, len(list))
  263. for _, cp := range list {
  264. item := &ChannelPricingWithTags{
  265. ChannelPricing: cp,
  266. Tags: make([]*model.PricingTag, 0),
  267. }
  268. if cp.TagIds != "" {
  269. for _, idStr := range strings.Split(cp.TagIds, ",") {
  270. if id, err := strconv.Atoi(idStr); err == nil {
  271. if tag, ok := tagMap[id]; ok {
  272. item.Tags = append(item.Tags, tag)
  273. }
  274. }
  275. }
  276. }
  277. result = append(result, item)
  278. }
  279. common.ApiSuccess(c, gin.H{
  280. "page": page,
  281. "page_size": pageSize,
  282. "total": total,
  283. "items": result,
  284. })
  285. }