ops_ingress_reject_repo.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. )
  8. const ingressRejectUpsertChunkSize = 500
  9. func (r *opsRepository) BatchUpsertIngressRejects(ctx context.Context, items []*service.OpsIngressRejectAggregate) error {
  10. if r == nil || r.db == nil || len(items) == 0 {
  11. return nil
  12. }
  13. tx, err := r.db.BeginTx(ctx, nil)
  14. if err != nil {
  15. return err
  16. }
  17. defer func() { _ = tx.Rollback() }()
  18. for start := 0; start < len(items); start += ingressRejectUpsertChunkSize {
  19. end := start + ingressRejectUpsertChunkSize
  20. if end > len(items) {
  21. end = len(items)
  22. }
  23. valid := make([]*service.OpsIngressRejectAggregate, 0, end-start)
  24. for _, item := range items[start:end] {
  25. if item != nil && item.RequestCount > 0 {
  26. valid = append(valid, item)
  27. }
  28. }
  29. if len(valid) == 0 {
  30. continue
  31. }
  32. var query strings.Builder
  33. _, _ = query.WriteString(`INSERT INTO ops_ingress_reject_aggregates
  34. (bucket_start, reject_reason, route_family, protocol, client_ip, user_id, api_key_id, request_count, first_seen, last_seen)
  35. VALUES `)
  36. args := make([]any, 0, len(valid)*10)
  37. for i, item := range valid {
  38. if i > 0 {
  39. _ = query.WriteByte(',')
  40. }
  41. base := len(args)
  42. fmt.Fprintf(&query, "($%d,$%d,$%d,$%d,$%d,$%d,$%d,$%d,$%d,$%d)",
  43. base+1, base+2, base+3, base+4, base+5, base+6, base+7, base+8, base+9, base+10)
  44. var userID, apiKeyID int64
  45. if item.UserID != nil {
  46. userID = *item.UserID
  47. }
  48. if item.APIKeyID != nil {
  49. apiKeyID = *item.APIKeyID
  50. }
  51. args = append(args, item.BucketStart.UTC(), item.RejectReason, item.RouteFamily, item.Protocol,
  52. item.ClientIP, userID, apiKeyID, item.RequestCount, item.FirstSeen.UTC(), item.LastSeen.UTC())
  53. }
  54. _, _ = query.WriteString(`
  55. ON CONFLICT (bucket_start, reject_reason, route_family, protocol, client_ip, user_id, api_key_id)
  56. DO UPDATE SET request_count = ops_ingress_reject_aggregates.request_count + EXCLUDED.request_count,
  57. first_seen = LEAST(ops_ingress_reject_aggregates.first_seen, EXCLUDED.first_seen),
  58. last_seen = GREATEST(ops_ingress_reject_aggregates.last_seen, EXCLUDED.last_seen),
  59. updated_at = NOW()`)
  60. if _, err := tx.ExecContext(ctx, query.String(), args...); err != nil {
  61. return err
  62. }
  63. }
  64. return tx.Commit()
  65. }
  66. func (r *opsRepository) ListIngressRejects(ctx context.Context, filter *service.OpsIngressRejectFilter) (*service.OpsIngressRejectList, error) {
  67. if r == nil || r.db == nil {
  68. return nil, fmt.Errorf("nil ops repository")
  69. }
  70. if filter == nil {
  71. filter = &service.OpsIngressRejectFilter{}
  72. }
  73. page, pageSize := filter.Page, filter.PageSize
  74. if page <= 0 {
  75. page = 1
  76. }
  77. if pageSize <= 0 {
  78. pageSize = 50
  79. }
  80. if pageSize > 200 {
  81. pageSize = 200
  82. }
  83. clauses := []string{"1=1"}
  84. args := make([]any, 0)
  85. add := func(expr string, value any) {
  86. args = append(args, value)
  87. clauses = append(clauses, fmt.Sprintf(expr, len(args)))
  88. }
  89. if filter.StartTime != nil {
  90. add("bucket_start >= $%d", filter.StartTime.UTC())
  91. }
  92. if filter.EndTime != nil {
  93. add("bucket_start < $%d", filter.EndTime.UTC())
  94. }
  95. if value := strings.TrimSpace(filter.RejectReason); value != "" {
  96. add("reject_reason = $%d", value)
  97. }
  98. if value := strings.TrimSpace(filter.RouteFamily); value != "" {
  99. add("route_family = $%d", value)
  100. }
  101. if value := strings.TrimSpace(filter.Protocol); value != "" {
  102. add("protocol = $%d", value)
  103. }
  104. if value := strings.TrimSpace(filter.ClientIP); value != "" {
  105. add("client_ip = $%d::inet", value)
  106. }
  107. if filter.UserID != nil {
  108. add("user_id = $%d", *filter.UserID)
  109. }
  110. if filter.APIKeyID != nil {
  111. add("api_key_id = $%d", *filter.APIKeyID)
  112. }
  113. where := "WHERE " + strings.Join(clauses, " AND ")
  114. var total int
  115. if err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM ops_ingress_reject_aggregates "+where, args...).Scan(&total); err != nil {
  116. return nil, err
  117. }
  118. args = append(args, pageSize, (page-1)*pageSize)
  119. query := fmt.Sprintf(`SELECT id,bucket_start,reject_reason,route_family,protocol,host(client_ip),user_id,api_key_id,request_count,first_seen,last_seen
  120. FROM ops_ingress_reject_aggregates %s ORDER BY bucket_start DESC,id DESC LIMIT $%d OFFSET $%d`, where, len(args)-1, len(args))
  121. rows, err := r.db.QueryContext(ctx, query, args...)
  122. if err != nil {
  123. return nil, err
  124. }
  125. defer func() { _ = rows.Close() }()
  126. result := &service.OpsIngressRejectList{
  127. Items: make([]*service.OpsIngressRejectAggregate, 0, pageSize), Total: total, Page: page, PageSize: pageSize,
  128. }
  129. for rows.Next() {
  130. item := &service.OpsIngressRejectAggregate{}
  131. var userID, apiKeyID int64
  132. if err := rows.Scan(&item.ID, &item.BucketStart, &item.RejectReason, &item.RouteFamily, &item.Protocol,
  133. &item.ClientIP, &userID, &apiKeyID, &item.RequestCount, &item.FirstSeen, &item.LastSeen); err != nil {
  134. return nil, err
  135. }
  136. if userID > 0 {
  137. item.UserID = &userID
  138. }
  139. if apiKeyID > 0 {
  140. item.APIKeyID = &apiKeyID
  141. }
  142. result.Items = append(result.Items, item)
  143. }
  144. if err := rows.Err(); err != nil {
  145. return nil, err
  146. }
  147. return result, nil
  148. }