|
|
@@ -53,14 +53,7 @@ func parseStatusFilter(statusParam string) int {
|
|
|
}
|
|
|
|
|
|
func GetAllChannels(c *gin.Context) {
|
|
|
- p, _ := strconv.Atoi(c.Query("p"))
|
|
|
- pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
|
|
- if p < 1 {
|
|
|
- p = 1
|
|
|
- }
|
|
|
- if pageSize < 1 {
|
|
|
- pageSize = common.ItemsPerPage
|
|
|
- }
|
|
|
+ pageInfo := common.GetPageQuery(c)
|
|
|
channelData := make([]*model.Channel, 0)
|
|
|
idSort, _ := strconv.ParseBool(c.Query("id_sort"))
|
|
|
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
|
|
|
@@ -79,7 +72,7 @@ func GetAllChannels(c *gin.Context) {
|
|
|
var total int64
|
|
|
|
|
|
if enableTagMode {
|
|
|
- tags, err := model.GetPaginatedTags((p-1)*pageSize, pageSize)
|
|
|
+ tags, err := model.GetPaginatedTags(pageInfo.GetStartIdx(), pageInfo.GetPageSize())
|
|
|
if err != nil {
|
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
|
|
return
|
|
|
@@ -126,7 +119,7 @@ func GetAllChannels(c *gin.Context) {
|
|
|
order = "id desc"
|
|
|
}
|
|
|
|
|
|
- err := baseQuery.Order(order).Limit(pageSize).Offset((p - 1) * pageSize).Omit("key").Find(&channelData).Error
|
|
|
+ err := baseQuery.Order(order).Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Omit("key").Find(&channelData).Error
|
|
|
if err != nil {
|
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
|
|
return
|
|
|
@@ -148,17 +141,12 @@ func GetAllChannels(c *gin.Context) {
|
|
|
for _, r := range results {
|
|
|
typeCounts[r.Type] = r.Count
|
|
|
}
|
|
|
-
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": true,
|
|
|
- "message": "",
|
|
|
- "data": gin.H{
|
|
|
- "items": channelData,
|
|
|
- "total": total,
|
|
|
- "page": p,
|
|
|
- "page_size": pageSize,
|
|
|
- "type_counts": typeCounts,
|
|
|
- },
|
|
|
+ common.ApiSuccess(c, gin.H{
|
|
|
+ "items": channelData,
|
|
|
+ "total": total,
|
|
|
+ "page": pageInfo.GetPage(),
|
|
|
+ "page_size": pageInfo.GetPageSize(),
|
|
|
+ "type_counts": typeCounts,
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
@@ -166,19 +154,13 @@ func GetAllChannels(c *gin.Context) {
|
|
|
func FetchUpstreamModels(c *gin.Context) {
|
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
channel, err := model.GetChannelById(id, true)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -195,10 +177,7 @@ func FetchUpstreamModels(c *gin.Context) {
|
|
|
}
|
|
|
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -230,10 +209,7 @@ func FetchUpstreamModels(c *gin.Context) {
|
|
|
func FixChannelsAbilities(c *gin.Context) {
|
|
|
success, fails, err := model.FixAbility()
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -358,18 +334,12 @@ func SearchChannels(c *gin.Context) {
|
|
|
func GetChannel(c *gin.Context) {
|
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
channel, err := model.GetChannelById(id, false)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -380,76 +350,173 @@ func GetChannel(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+type AddChannelRequest struct {
|
|
|
+ Mode string `json:"mode"`
|
|
|
+ MultiKeyMode constant.MultiKeyMode `json:"multi_key_mode"`
|
|
|
+ Channel *model.Channel `json:"channel"`
|
|
|
+}
|
|
|
+
|
|
|
+func getVertexArrayKeys(keys string) ([]string, error) {
|
|
|
+ if keys == "" {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ var keyArray []interface{}
|
|
|
+ err := common.Unmarshal([]byte(keys), &keyArray)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("批量添加 Vertex AI 必须使用标准的JsonArray格式,例如[{key1}, {key2}...],请检查输入: %w", err)
|
|
|
+ }
|
|
|
+ cleanKeys := make([]string, 0, len(keyArray))
|
|
|
+ for _, key := range keyArray {
|
|
|
+ var keyStr string
|
|
|
+ switch v := key.(type) {
|
|
|
+ case string:
|
|
|
+ keyStr = strings.TrimSpace(v)
|
|
|
+ default:
|
|
|
+ bytes, err := json.Marshal(v)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("Vertex AI key JSON 编码失败: %w", err)
|
|
|
+ }
|
|
|
+ keyStr = string(bytes)
|
|
|
+ }
|
|
|
+ if keyStr != "" {
|
|
|
+ cleanKeys = append(cleanKeys, keyStr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if len(cleanKeys) == 0 {
|
|
|
+ return nil, fmt.Errorf("批量添加 Vertex AI 的 keys 不能为空")
|
|
|
+ }
|
|
|
+ return cleanKeys, nil
|
|
|
+}
|
|
|
+
|
|
|
func AddChannel(c *gin.Context) {
|
|
|
- channel := model.Channel{}
|
|
|
- err := c.ShouldBindJSON(&channel)
|
|
|
+ addChannelRequest := AddChannelRequest{}
|
|
|
+ err := c.ShouldBindJSON(&addChannelRequest)
|
|
|
+ if err != nil {
|
|
|
+ common.ApiError(c, err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ err = addChannelRequest.Channel.ValidateSettings()
|
|
|
if err != nil {
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": false,
|
|
|
- "message": err.Error(),
|
|
|
+ "message": "channel setting 格式错误:" + err.Error(),
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
- err = channel.ValidateSettings()
|
|
|
- if err != nil {
|
|
|
+
|
|
|
+ if addChannelRequest.Channel == nil || addChannelRequest.Channel.Key == "" {
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": false,
|
|
|
- "message": "channel setting 格式错误:" + err.Error(),
|
|
|
+ "message": "channel cannot be empty",
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
- channel.CreatedTime = common.GetTimestamp()
|
|
|
- keys := strings.Split(channel.Key, "\n")
|
|
|
- if channel.Type == constant.ChannelTypeVertexAi {
|
|
|
- if channel.Other == "" {
|
|
|
+
|
|
|
+ // Validate the length of the model name
|
|
|
+ for _, m := range addChannelRequest.Channel.GetModels() {
|
|
|
+ if len(m) > 255 {
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
+ "success": false,
|
|
|
+ "message": fmt.Sprintf("模型名称过长: %s", m),
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi {
|
|
|
+ if addChannelRequest.Channel.Other == "" {
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": false,
|
|
|
"message": "部署地区不能为空",
|
|
|
})
|
|
|
return
|
|
|
} else {
|
|
|
- if common.IsJsonStr(channel.Other) {
|
|
|
- // must have default
|
|
|
- regionMap := common.StrToMap(channel.Other)
|
|
|
- if regionMap["default"] == nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": "部署地区必须包含default字段",
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
+ regionMap, err := common.StrToMap(addChannelRequest.Channel.Other)
|
|
|
+ if err != nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
+ "success": false,
|
|
|
+ "message": "部署地区必须是标准的Json格式,例如{\"default\": \"us-central1\", \"region2\": \"us-east1\"}",
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if regionMap["default"] == nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
+ "success": false,
|
|
|
+ "message": "部署地区必须包含default字段",
|
|
|
+ })
|
|
|
+ return
|
|
|
}
|
|
|
}
|
|
|
- keys = []string{channel.Key}
|
|
|
}
|
|
|
- channels := make([]model.Channel, 0, len(keys))
|
|
|
- for _, key := range keys {
|
|
|
- if key == "" {
|
|
|
- continue
|
|
|
+
|
|
|
+ addChannelRequest.Channel.CreatedTime = common.GetTimestamp()
|
|
|
+ keys := make([]string, 0)
|
|
|
+ switch addChannelRequest.Mode {
|
|
|
+ case "multi_to_single":
|
|
|
+ addChannelRequest.Channel.ChannelInfo.IsMultiKey = true
|
|
|
+ addChannelRequest.Channel.ChannelInfo.MultiKeyMode = addChannelRequest.MultiKeyMode
|
|
|
+ if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi {
|
|
|
+ array, err := getVertexArrayKeys(addChannelRequest.Channel.Key)
|
|
|
+ if err != nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
+ "success": false,
|
|
|
+ "message": err.Error(),
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(array)
|
|
|
+ addChannelRequest.Channel.Key = strings.Join(array, "\n")
|
|
|
+ } else {
|
|
|
+ cleanKeys := make([]string, 0)
|
|
|
+ for _, key := range strings.Split(addChannelRequest.Channel.Key, "\n") {
|
|
|
+ if key == "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ key = strings.TrimSpace(key)
|
|
|
+ cleanKeys = append(cleanKeys, key)
|
|
|
+ }
|
|
|
+ addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(cleanKeys)
|
|
|
+ addChannelRequest.Channel.Key = strings.Join(cleanKeys, "\n")
|
|
|
}
|
|
|
- localChannel := channel
|
|
|
- localChannel.Key = key
|
|
|
- // Validate the length of the model name
|
|
|
- models := strings.Split(localChannel.Models, ",")
|
|
|
- for _, model := range models {
|
|
|
- if len(model) > 255 {
|
|
|
+ keys = []string{addChannelRequest.Channel.Key}
|
|
|
+ case "batch":
|
|
|
+ if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi {
|
|
|
+ // multi json
|
|
|
+ keys, err = getVertexArrayKeys(addChannelRequest.Channel.Key)
|
|
|
+ if err != nil {
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": false,
|
|
|
- "message": fmt.Sprintf("模型名称过长: %s", model),
|
|
|
+ "message": err.Error(),
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
+ } else {
|
|
|
+ keys = strings.Split(addChannelRequest.Channel.Key, "\n")
|
|
|
}
|
|
|
- channels = append(channels, localChannel)
|
|
|
- }
|
|
|
- err = model.BatchInsertChannels(channels)
|
|
|
- if err != nil {
|
|
|
+ case "single":
|
|
|
+ keys = []string{addChannelRequest.Channel.Key}
|
|
|
+ default:
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": false,
|
|
|
- "message": err.Error(),
|
|
|
+ "message": "不支持的添加模式",
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+ channels := make([]model.Channel, 0, len(keys))
|
|
|
+ for _, key := range keys {
|
|
|
+ if key == "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ localChannel := addChannelRequest.Channel
|
|
|
+ localChannel.Key = key
|
|
|
+ channels = append(channels, *localChannel)
|
|
|
+ }
|
|
|
+ err = model.BatchInsertChannels(channels)
|
|
|
+ if err != nil {
|
|
|
+ common.ApiError(c, err)
|
|
|
+ return
|
|
|
+ }
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": true,
|
|
|
"message": "",
|
|
|
@@ -462,10 +529,7 @@ func DeleteChannel(c *gin.Context) {
|
|
|
channel := model.Channel{Id: id}
|
|
|
err := channel.Delete()
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -478,10 +542,7 @@ func DeleteChannel(c *gin.Context) {
|
|
|
func DeleteDisabledChannel(c *gin.Context) {
|
|
|
rows, err := model.DeleteDisabledChannel()
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -514,10 +575,7 @@ func DisableTagChannels(c *gin.Context) {
|
|
|
}
|
|
|
err = model.DisableChannelByTag(channelTag.Tag)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -539,10 +597,7 @@ func EnableTagChannels(c *gin.Context) {
|
|
|
}
|
|
|
err = model.EnableChannelByTag(channelTag.Tag)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -571,10 +626,7 @@ func EditTagChannels(c *gin.Context) {
|
|
|
}
|
|
|
err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.ModelMapping, channelTag.Models, channelTag.Groups, channelTag.Priority, channelTag.Weight)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -601,10 +653,7 @@ func DeleteChannelBatch(c *gin.Context) {
|
|
|
}
|
|
|
err = model.BatchDeleteChannels(channelBatch.Ids)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -615,14 +664,16 @@ func DeleteChannelBatch(c *gin.Context) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+type PatchChannel struct {
|
|
|
+ model.Channel
|
|
|
+ MultiKeyMode *string `json:"multi_key_mode"`
|
|
|
+}
|
|
|
+
|
|
|
func UpdateChannel(c *gin.Context) {
|
|
|
- channel := model.Channel{}
|
|
|
+ channel := PatchChannel{}
|
|
|
err := c.ShouldBindJSON(&channel)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
err = channel.ValidateSettings()
|
|
|
@@ -641,20 +692,25 @@ func UpdateChannel(c *gin.Context) {
|
|
|
})
|
|
|
return
|
|
|
} else {
|
|
|
- if common.IsJsonStr(channel.Other) {
|
|
|
- // must have default
|
|
|
- regionMap := common.StrToMap(channel.Other)
|
|
|
- if regionMap["default"] == nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": "部署地区必须包含default字段",
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
+ regionMap, err := common.StrToMap(channel.Other)
|
|
|
+ if err != nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
+ "success": false,
|
|
|
+ "message": "部署地区必须是标准的Json格式,例如{\"default\": \"us-central1\", \"region2\": \"us-east1\"}",
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if regionMap["default"] == nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{
|
|
|
+ "success": false,
|
|
|
+ "message": "部署地区必须包含default字段",
|
|
|
+ })
|
|
|
+ return
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- err = channel.Update()
|
|
|
+ // Preserve existing ChannelInfo to ensure multi-key channels keep correct state even if the client does not send ChannelInfo in the request.
|
|
|
+ originChannel, err := model.GetChannelById(channel.Id, false)
|
|
|
if err != nil {
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": false,
|
|
|
@@ -662,6 +718,19 @@ func UpdateChannel(c *gin.Context) {
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+ // Always copy the original ChannelInfo so that fields like IsMultiKey and MultiKeySize are retained.
|
|
|
+ channel.ChannelInfo = originChannel.ChannelInfo
|
|
|
+
|
|
|
+ // If the request explicitly specifies a new MultiKeyMode, apply it on top of the original info.
|
|
|
+ if channel.MultiKeyMode != nil && *channel.MultiKeyMode != "" {
|
|
|
+ channel.ChannelInfo.MultiKeyMode = constant.MultiKeyMode(*channel.MultiKeyMode)
|
|
|
+ }
|
|
|
+ err = channel.Update()
|
|
|
+ if err != nil {
|
|
|
+ common.ApiError(c, err)
|
|
|
+ return
|
|
|
+ }
|
|
|
channel.Key = ""
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
"success": true,
|
|
|
@@ -764,10 +833,7 @@ func BatchSetChannelTag(c *gin.Context) {
|
|
|
}
|
|
|
err = model.BatchSetChannelTag(channelBatch.Ids, channelBatch.Tag)
|
|
|
if err != nil {
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "success": false,
|
|
|
- "message": err.Error(),
|
|
|
- })
|
|
|
+ common.ApiError(c, err)
|
|
|
return
|
|
|
}
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
@@ -818,3 +884,52 @@ func GetTagModels(c *gin.Context) {
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+// CopyChannel handles cloning an existing channel with its key.
|
|
|
+// POST /api/channel/copy/:id
|
|
|
+// Optional query params:
|
|
|
+// suffix - string appended to the original name (default "_复制")
|
|
|
+// reset_balance - bool, when true will reset balance & used_quota to 0 (default true)
|
|
|
+func CopyChannel(c *gin.Context) {
|
|
|
+ id, err := strconv.Atoi(c.Param("id"))
|
|
|
+ if err != nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": "invalid id"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ suffix := c.DefaultQuery("suffix", "_复制")
|
|
|
+ resetBalance := true
|
|
|
+ if rbStr := c.DefaultQuery("reset_balance", "true"); rbStr != "" {
|
|
|
+ if v, err := strconv.ParseBool(rbStr); err == nil {
|
|
|
+ resetBalance = v
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // fetch original channel with key
|
|
|
+ origin, err := model.GetChannelById(id, true)
|
|
|
+ if err != nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // clone channel
|
|
|
+ clone := *origin // shallow copy is sufficient as we will overwrite primitives
|
|
|
+ clone.Id = 0 // let DB auto-generate
|
|
|
+ clone.CreatedTime = common.GetTimestamp()
|
|
|
+ clone.Name = origin.Name + suffix
|
|
|
+ clone.TestTime = 0
|
|
|
+ clone.ResponseTime = 0
|
|
|
+ if resetBalance {
|
|
|
+ clone.Balance = 0
|
|
|
+ clone.UsedQuota = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // insert
|
|
|
+ if err := model.BatchInsertChannels([]model.Channel{clone}); err != nil {
|
|
|
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // success
|
|
|
+ c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": gin.H{"id": clone.Id}})
|
|
|
+}
|