Initial open-source release (MIT): image2api AI gateway

Full Go backend + Vue 3 frontend, OpenAI-compatible API, multi-provider
account pools, billing/admin, Docker one-command deploy with auto HTTPS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 22:59:04 +08:00
co-authored by Claude Opus 4.8
commit 606caaf047
142 changed files with 33648 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
package service
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/redis/go-redis/v9"
)
var ErrRateLimited = errors.New("rate limited")
type RateLimitService struct {
redis *redis.Client
prefix string
}
type RateLimitResult struct {
Allowed bool
Count int64
Limit int64
RetryAfter time.Duration
}
func NewRateLimitService(redis *redis.Client) *RateLimitService {
return &RateLimitService{
redis: redis,
prefix: "rl:",
}
}
func (s *RateLimitService) Allow(ctx context.Context, bucket string, limit int64, window time.Duration) (*RateLimitResult, error) {
if limit <= 0 || window <= 0 {
return &RateLimitResult{Allowed: true, Limit: limit}, nil
}
key := s.prefix + strings.TrimSpace(bucket)
count, err := s.redis.Incr(ctx, key).Result()
if err != nil {
return nil, err
}
if count == 1 {
if err := s.redis.Expire(ctx, key, window).Err(); err != nil {
return nil, err
}
}
ttl, err := s.redis.TTL(ctx, key).Result()
if err != nil {
return nil, err
}
if ttl < 0 {
ttl = window
}
return &RateLimitResult{
Allowed: count <= limit,
Count: count,
Limit: limit,
RetryAfter: ttl,
}, nil
}
func (s *RateLimitService) Enforce(ctx context.Context, bucket string, limit int64, window time.Duration) error {
result, err := s.Allow(ctx, bucket, limit, window)
if err != nil {
return err
}
if result.Allowed {
return nil
}
retry := int(result.RetryAfter.Seconds())
if retry < 1 {
retry = 1
}
return fmt.Errorf("%w: 请稍后再试(%d 秒后)", ErrRateLimited, retry)
}