| package sql |
|
|
| import ( |
| "context" |
| "fmt" |
| "log" |
| "strings" |
| "time" |
|
|
| "ccLoad/internal/model" |
| ) |
|
|
| |
| type ChannelInfo struct { |
| Name string |
| Priority int |
| Type string |
| CostMultiplier float64 |
| } |
|
|
| |
| |
| |
| |
| |
| func (s *SQLStore) fetchChannelInfoBatch(ctx context.Context, channelIDs map[int64]bool) (map[int64]ChannelInfo, error) { |
| if len(channelIDs) == 0 { |
| return make(map[int64]ChannelInfo), nil |
| } |
|
|
| |
| |
| rows, err := s.db.QueryContext(ctx, ` |
| SELECT |
| id, |
| name, |
| priority, |
| LOWER(COALESCE(NULLIF(TRIM(channel_type), ''), 'anthropic')), |
| COALESCE(cost_multiplier, 1) |
| FROM channels |
| `) |
| if err != nil { |
| return nil, fmt.Errorf("query all channel info: %w", err) |
| } |
| defer func() { _ = rows.Close() }() |
|
|
| |
| channelInfos := make(map[int64]ChannelInfo, len(channelIDs)) |
| for rows.Next() { |
| var id int64 |
| var name string |
| var priority int |
| var channelType string |
| var costMultiplier float64 |
| if err := rows.Scan(&id, &name, &priority, &channelType, &costMultiplier); err != nil { |
| log.Printf("[WARN] 扫描渠道信息失败: %v", err) |
| continue |
| } |
| |
| if channelIDs[id] { |
| channelInfos[id] = ChannelInfo{ |
| Name: name, |
| Priority: priority, |
| Type: channelType, |
| CostMultiplier: normalizeCostMultiplier(costMultiplier), |
| } |
| } |
| } |
| if err := rows.Err(); err != nil { |
| return nil, fmt.Errorf("iterate channel info rows: %w", err) |
| } |
|
|
| return channelInfos, nil |
| } |
|
|
| |
| |
| |
| func (s *SQLStore) fetchChannelNamesBatch(ctx context.Context, channelIDs map[int64]bool) (map[int64]string, error) { |
| infos, err := s.fetchChannelInfoBatch(ctx, channelIDs) |
| if err != nil { |
| return nil, err |
| } |
| names := make(map[int64]string, len(infos)) |
| for id, info := range infos { |
| names[id] = info.Name |
| } |
| return names, nil |
| } |
|
|
| |
| func (s *SQLStore) fetchAuthTokenDescriptionsBatch(ctx context.Context, tokenIDs map[int64]bool) (map[int64]string, error) { |
| if len(tokenIDs) == 0 { |
| return make(map[int64]string), nil |
| } |
|
|
| ids := make([]any, 0, len(tokenIDs)) |
| placeholders := make([]string, 0, len(tokenIDs)) |
| for id := range tokenIDs { |
| ids = append(ids, id) |
| placeholders = append(placeholders, "?") |
| } |
|
|
| query := "SELECT id, description FROM auth_tokens WHERE id IN (" + |
| strings.Join(placeholders, ",") + ")" |
|
|
| rows, err := s.db.QueryContext(ctx, query, ids...) |
| if err != nil { |
| return nil, fmt.Errorf("query auth token descriptions: %w", err) |
| } |
| defer func() { _ = rows.Close() }() |
|
|
| descriptions := make(map[int64]string, len(tokenIDs)) |
| for rows.Next() { |
| var id int64 |
| var desc string |
| if err := rows.Scan(&id, &desc); err != nil { |
| return nil, fmt.Errorf("scan auth token description: %w", err) |
| } |
| descriptions[id] = desc |
| } |
| if err := rows.Err(); err != nil { |
| return nil, fmt.Errorf("iterate auth token descriptions: %w", err) |
| } |
| return descriptions, nil |
| } |
|
|
| |
| func (s *SQLStore) fetchChannelIDsByNameFilter(ctx context.Context, exact string, like string) ([]int64, error) { |
| |
| var ( |
| query string |
| args []any |
| ) |
| if exact != "" { |
| query = "SELECT id FROM channels WHERE name = ?" |
| args = []any{exact} |
| } else if like != "" { |
| query = "SELECT id FROM channels WHERE name LIKE ?" |
| args = []any{"%" + like + "%"} |
| } else { |
| return nil, nil |
| } |
|
|
| rows, err := s.db.QueryContext(ctx, query, args...) |
| if err != nil { |
| return nil, fmt.Errorf("query channel ids by name: %w", err) |
| } |
| defer func() { _ = rows.Close() }() |
|
|
| var ids []int64 |
| for rows.Next() { |
| var id int64 |
| if err := rows.Scan(&id); err != nil { |
| return nil, fmt.Errorf("scan channel id: %w", err) |
| } |
| ids = append(ids, id) |
| } |
| if err := rows.Err(); err != nil { |
| return nil, err |
| } |
| return ids, nil |
| } |
|
|
| |
| |
| func (s *SQLStore) fetchChannelIDsByType(ctx context.Context, channelType string) ([]int64, error) { |
| if channelType == "" { |
| return nil, nil |
| } |
|
|
| query := "SELECT id FROM channels WHERE channel_type = ?" |
| rows, err := s.db.QueryContext(ctx, query, channelType) |
| if err != nil { |
| return nil, fmt.Errorf("query channel ids by type: %w", err) |
| } |
| defer func() { _ = rows.Close() }() |
|
|
| var ids []int64 |
| for rows.Next() { |
| var id int64 |
| if err := rows.Scan(&id); err != nil { |
| return nil, fmt.Errorf("scan channel id: %w", err) |
| } |
| ids = append(ids, id) |
| } |
| if err := rows.Err(); err != nil { |
| return nil, err |
| } |
| return ids, nil |
| } |
|
|
| |
| |
| |
| func (s *SQLStore) applyChannelFilter(ctx context.Context, qb *QueryBuilder, filter *model.LogFilter) (bool, bool, error) { |
| channelIDs, isEmpty, err := s.resolveChannelFilter(ctx, filter) |
| if err != nil { |
| return false, false, err |
| } |
| if isEmpty { |
| return true, true, nil |
| } |
| if len(channelIDs) > 0 { |
| vals := make([]any, 0, len(channelIDs)) |
| for _, id := range channelIDs { |
| vals = append(vals, id) |
| } |
| qb.WhereIn("channel_id", vals) |
| return true, false, nil |
| } |
| return false, false, nil |
| } |
|
|
| |
| func intersectIDs(a, b []int64) []int64 { |
| set := make(map[int64]bool, len(a)) |
| for _, id := range a { |
| set[id] = true |
| } |
| var result []int64 |
| for _, id := range b { |
| if set[id] { |
| result = append(result, id) |
| } |
| } |
| return result |
| } |
|
|
| |
| |
| func timeToUnix(t time.Time) int64 { |
| return t.Unix() |
| } |
|
|
| |
| func unixToTime(ts int64) time.Time { |
| return time.Unix(ts, 0) |
| } |
|
|
| |
| |
| func boolToInt(b bool) int { |
| if b { |
| return 1 |
| } |
| return 0 |
| } |
|
|
| |
| func normalizeCostMultiplier(m float64) float64 { |
| if m < 0 { |
| return 1 |
| } |
| return m |
| } |
|
|