// 渠道列表请求
type ChannelListRequest struct {
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=20"`
Type int `form:"type,omitempty"`
Status int `form:"status,omitempty"`
Keyword string `form:"keyword,omitempty"`
OrderBy string `form:"order_by,default=created_time"`
Order string `form:"order,default=desc"`
}
// 渠道列表响应
type ChannelListResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data *ChannelList `json:"data,omitempty"`
}
type ChannelList struct {
Channels []ChannelInfo `json:"channels"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
type ChannelInfo struct {
ID int `json:"id"`
Type int `json:"type"`
TypeName string `json:"type_name"`
Name string `json:"name"`
Status int `json:"status"`
StatusName string `json:"status_name"`
Weight int `json:"weight"`
Priority int `json:"priority"`
BaseURL string `json:"base_url"`
Models []string `json:"models"`
Groups []string `json:"groups"`
RemainQuota int64 `json:"remain_quota"`
UsedQuota int64 `json:"used_quota"`
UnlimitedQuota bool `json:"unlimited_quota"`
RequestCount int64 `json:"request_count"`
ResponseTime float64 `json:"response_time"`
ErrorRate float64 `json:"error_rate"`
LastTestTime int64 `json:"last_test_time"`
CreatedTime int64 `json:"created_time"`
UpdatedTime int64 `json:"updated_time"`
Config ChannelConfig `json:"config"`
Stats *ChannelStats `json:"stats,omitempty"`
}
// 获取渠道列表
func GetChannelList(c *gin.Context) {
var req ChannelListRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "请求参数错误: " + err.Error(),
})
return
}
// 检查用户权限
user := c.MustGet("user").(*User)
if user.Role != common.RoleRootUser && user.Role != common.RoleAdminUser {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": "权限不足",
})
return
}
// 构建查询
query := common.DB.Model(&Channel{})
// 添加过滤条件
if req.Type > 0 {
query = query.Where("type = ?", req.Type)
}
if req.Status > 0 {
query = query.Where("status = ?", req.Status)
}
if req.Keyword != "" {
query = query.Where("name LIKE ? OR base_url LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
// 获取总数
var total int64
query.Count(&total)
// 添加排序
orderClause := req.OrderBy
if req.Order == "desc" {
orderClause += " DESC"
} else {
orderClause += " ASC"
}
query = query.Order(orderClause)
// 分页
offset := (req.Page - 1) * req.PageSize
query = query.Offset(offset).Limit(req.PageSize)
// 执行查询
var channels []Channel
if err := query.Find(&channels).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "查询渠道失败: " + err.Error(),
})
return
}
// 转换为响应格式
channelInfos := make([]ChannelInfo, len(channels))
for i, channel := range channels {
var models []string
var groups []string
if channel.Models != "" {
json.Unmarshal([]byte(channel.Models), &models)
}
if channel.Groups != "" {
json.Unmarshal([]byte(channel.Groups), &groups)
}
channelInfos[i] = ChannelInfo{
ID: channel.ID,
Type: channel.Type,
TypeName: getChannelTypeName(channel.Type),
Name: channel.Name,
Status: channel.Status,
StatusName: getChannelStatusName(channel.Status),
Weight: channel.Weight,
Priority: channel.Priority,
BaseURL: channel.BaseURL,
Models: models,
Groups: groups,
RemainQuota: channel.RemainQuota,
UsedQuota: channel.UsedQuota,
UnlimitedQuota: channel.UnlimitedQuota,
RequestCount: channel.RequestCount,
ResponseTime: channel.ResponseTime,
ErrorRate: channel.ErrorRate,
LastTestTime: channel.LastTestTime,
CreatedTime: channel.CreatedTime,
UpdatedTime: channel.UpdatedTime,
Config: channel.Config,
}
// 获取统计信息
if stats := getChannelStats(channel.ID); stats != nil {
channelInfos[i].Stats = stats
}
}
// 计算总页数
totalPages := int(math.Ceil(float64(total) / float64(req.PageSize)))
c.JSON(http.StatusOK, ChannelListResponse{
Success: true,
Message: "获取渠道列表成功",
Data: &ChannelList{
Channels: channelInfos,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: totalPages,
},
})
}
// 获取渠道类型名称
func getChannelTypeName(channelType int) string {
switch channelType {
case ChannelTypeOpenAI:
return "OpenAI"
case ChannelTypeAzure:
return "Azure OpenAI"
case ChannelTypeClaude:
return "Anthropic Claude"
case ChannelTypeGemini:
return "Google Gemini"
case ChannelTypeBaidu:
return "百度文心一言"
case ChannelTypeAlibaba:
return "阿里通义千问"
case ChannelTypeTencent:
return "腾讯混元"
case ChannelTypeCustom:
return "自定义"
default:
return "未知"
}
}
// 获取渠道状态名称
func getChannelStatusName(status int) string {
switch status {
case ChannelStatusEnabled:
return "启用"
case ChannelStatusDisabled:
return "禁用"
case ChannelStatusTesting:
return "测试中"
case ChannelStatusError:
return "错误"
default:
return "未知"
}
}
// 获取渠道统计信息
func getChannelStats(channelID int) *ChannelStats {
var stats ChannelStats
// 获取总统计
row := common.DB.Model(&ChannelUsage{}).
Select("COUNT(*) as total_requests, SUM(CASE WHEN success = true THEN 1 ELSE 0 END) as success_requests, SUM(CASE WHEN success = false THEN 1 ELSE 0 END) as failed_requests, SUM(total_tokens) as total_tokens, SUM(cost) as total_cost, AVG(response_time) as avg_response_time, MAX(created_time) as last_used_time").
Where("channel_id = ?", channelID).
Row()
row.Scan(&stats.TotalRequests, &stats.SuccessRequests, &stats.FailedRequests, &stats.TotalTokens, &stats.TotalCost, &stats.AvgResponseTime, &stats.LastUsedTime)
// 计算错误率
if stats.TotalRequests > 0 {
stats.ErrorRate = float64(stats.FailedRequests) / float64(stats.TotalRequests) * 100
}
stats.ChannelID = channelID
return &stats
}
// 获取单个渠道详情
func GetChannelDetail(c *gin.Context) {
channelID, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "无效的渠道ID",
})
return
}
// 检查用户权限
user := c.MustGet("user").(*User)
if user.Role != common.RoleRootUser && user.Role != common.RoleAdminUser {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": "权限不足",
})
return
}
var channel Channel
if err := common.DB.First(&channel, channelID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"message": "渠道不存在",
})
} else {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "查询渠道失败: " + err.Error(),
})
}
return
}
// 转换为响应格式
var models []string
var groups []string
var modelRatio map[string]float64
if channel.Models != "" {
json.Unmarshal([]byte(channel.Models), &models)
}
if channel.Groups != "" {
json.Unmarshal([]byte(channel.Groups), &groups)
}
if channel.ModelRatio != "" {
json.Unmarshal([]byte(channel.ModelRatio), &modelRatio)
}
channelInfo := ChannelInfo{
ID: channel.ID,
Type: channel.Type,
TypeName: getChannelTypeName(channel.Type),
Name: channel.Name,
Status: channel.Status,
StatusName: getChannelStatusName(channel.Status),
Weight: channel.Weight,
Priority: channel.Priority,
BaseURL: channel.BaseURL,
Models: models,
Groups: groups,
RemainQuota: channel.RemainQuota,
UsedQuota: channel.UsedQuota,
UnlimitedQuota: channel.UnlimitedQuota,
RequestCount: channel.RequestCount,
ResponseTime: channel.ResponseTime,
ErrorRate: channel.ErrorRate,
LastTestTime: channel.LastTestTime,
CreatedTime: channel.CreatedTime,
UpdatedTime: channel.UpdatedTime,
Config: channel.Config,
Stats: getChannelStats(channel.ID),
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "获取渠道详情成功",
"data": channelInfo,
})
}