Files
sub2api/backend/cmd/server/wire.go
T
erio 6d0e056244 feat(fingerprint): Claude Code CLI fingerprint mimicry suite (v0.1.114.1)
Squash-merge feat/fingerprint-mimic into release/custom-0.1.114.

Functional changes:
- TLS ClientHello mimicry: 17→52 cipher suites, ALPN http/1.1 only,
  X25519MLKEM768, JA3/JA4 aligned with official CLI baseline
- HTTP layer: User-Agent 2.1.112 sdk-cli, 3 new anthropic-beta tokens,
  Stainless headers synced
- Sidecar traffic: usage poll + count_tokens injection + startup probe
  (three fire-and-forget channels with jittered intervals)
- Sticky session UUID: per-account metadata.user_id reuse via Redis,
  WithSessionHash propagation through gateway
- 429 no-switch: short-circuit on rate-limit instead of cross-account fail-over
- Admin UI: per-account "randomize fingerprint" button + ConfirmDialog +
  i18n (zh/en, 9 keys)

Engineering hardening (fixes carried over from hai/snapshot):
- safe.Go/Run package: panic-recovering goroutine helper with slog
- redis.Nil treated as cache miss in identity cache
- SOCKS5 dialer uses ContextDialer for ctx cancellation propagation
- Token refresh selects on stopCh during retry backoff
- Sidecar goroutines wrapped with safe.Go
- .gitattributes forces LF for *.json (fixes baseline file CRLF drift)

Refactoring (zero behavior change):
- sidecar probe constants centralized
- identity_service rewrite preflight extracted into helpers
- capture_fingerprint tool split from 894 lines into 9 files
- gofmt sweep on all hai-imported files

Tests:
- safe package: panic recovery + attrs propagation
- tlsfingerprint: SOCKS5 dialer + capture parity
- identity sticky-session preflight paths (early-return + cache-hit)
- sidecar probe: jittered interval + dry-run
- token refresh: graceful shutdown during backoff

Beta-validated on 0.1.112.21 (commit 565da163 of feat/fingerprint-mimic).
Bumps VERSION 0.1.112.20 -> 0.1.114.1.
2026-04-18 16:07:36 +08:00

306 lines
7.2 KiB
Go

//go:build wireinject
// +build wireinject
package main
import (
"context"
"log"
"net/http"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/repository"
"github.com/Wei-Shaw/sub2api/internal/server"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/google/wire"
"github.com/redis/go-redis/v9"
)
type Application struct {
Server *http.Server
Cleanup func()
}
func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
wire.Build(
// Infrastructure layer ProviderSets
config.ProviderSet,
// Business layer ProviderSets
repository.ProviderSet,
service.ProviderSet,
payment.ProviderSet,
middleware.ProviderSet,
handler.ProviderSet,
// Server layer ProviderSet
server.ProviderSet,
// Privacy client factory for OpenAI training opt-out
providePrivacyClientFactory,
// BuildInfo provider
provideServiceBuildInfo,
// Cleanup function provider
provideCleanup,
// Application struct
wire.Struct(new(Application), "Server", "Cleanup"),
)
return nil, nil
}
func providePrivacyClientFactory() service.PrivacyClientFactory {
return repository.CreatePrivacyReqClient
}
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
return service.BuildInfo{
Version: buildInfo.Version,
BuildType: buildInfo.BuildType,
}
}
func provideCleanup(
entClient *ent.Client,
rdb *redis.Client,
opsMetricsCollector *service.OpsMetricsCollector,
opsAggregation *service.OpsAggregationService,
opsAlertEvaluator *service.OpsAlertEvaluatorService,
opsCleanup *service.OpsCleanupService,
opsScheduledReport *service.OpsScheduledReportService,
opsSystemLogSink *service.OpsSystemLogSink,
schedulerSnapshot *service.SchedulerSnapshotService,
tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService,
subscriptionExpiry *service.SubscriptionExpiryService,
usageCleanup *service.UsageCleanupService,
idempotencyCleanup *service.IdempotencyCleanupService,
pricing *service.PricingService,
emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService,
usageRecordWorkerPool *service.UsageRecordWorkerPool,
subscriptionService *service.SubscriptionService,
oauth *service.OAuthService,
openaiOAuth *service.OpenAIOAuthService,
geminiOAuth *service.GeminiOAuthService,
antigravityOAuth *service.AntigravityOAuthService,
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
paymentOrderExpiry *service.PaymentOrderExpiryService,
claudeSidecarProbe *service.ClaudeSidecarProbeService,
) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
type cleanupStep struct {
name string
fn func() error
}
// 应用层清理步骤可并行执行,基础设施资源(Redis/Ent)最后按顺序关闭。
parallelSteps := []cleanupStep{
{"OpsScheduledReportService", func() error {
if opsScheduledReport != nil {
opsScheduledReport.Stop()
}
return nil
}},
{"OpsCleanupService", func() error {
if opsCleanup != nil {
opsCleanup.Stop()
}
return nil
}},
{"OpsSystemLogSink", func() error {
if opsSystemLogSink != nil {
opsSystemLogSink.Stop()
}
return nil
}},
{"OpsAlertEvaluatorService", func() error {
if opsAlertEvaluator != nil {
opsAlertEvaluator.Stop()
}
return nil
}},
{"OpsAggregationService", func() error {
if opsAggregation != nil {
opsAggregation.Stop()
}
return nil
}},
{"OpsMetricsCollector", func() error {
if opsMetricsCollector != nil {
opsMetricsCollector.Stop()
}
return nil
}},
{"SchedulerSnapshotService", func() error {
if schedulerSnapshot != nil {
schedulerSnapshot.Stop()
}
return nil
}},
{"UsageCleanupService", func() error {
if usageCleanup != nil {
usageCleanup.Stop()
}
return nil
}},
{"IdempotencyCleanupService", func() error {
if idempotencyCleanup != nil {
idempotencyCleanup.Stop()
}
return nil
}},
{"TokenRefreshService", func() error {
tokenRefresh.Stop()
return nil
}},
{"ClaudeSidecarProbeService", func() error {
if claudeSidecarProbe != nil {
claudeSidecarProbe.Stop()
}
return nil
}},
{"AccountExpiryService", func() error {
accountExpiry.Stop()
return nil
}},
{"SubscriptionExpiryService", func() error {
subscriptionExpiry.Stop()
return nil
}},
{"SubscriptionService", func() error {
if subscriptionService != nil {
subscriptionService.Stop()
}
return nil
}},
{"PricingService", func() error {
pricing.Stop()
return nil
}},
{"EmailQueueService", func() error {
emailQueue.Stop()
return nil
}},
{"BillingCacheService", func() error {
billingCache.Stop()
return nil
}},
{"UsageRecordWorkerPool", func() error {
if usageRecordWorkerPool != nil {
usageRecordWorkerPool.Stop()
}
return nil
}},
{"OAuthService", func() error {
oauth.Stop()
return nil
}},
{"OpenAIOAuthService", func() error {
openaiOAuth.Stop()
return nil
}},
{"GeminiOAuthService", func() error {
geminiOAuth.Stop()
return nil
}},
{"AntigravityOAuthService", func() error {
antigravityOAuth.Stop()
return nil
}},
{"OpenAIWSPool", func() error {
if openAIGateway != nil {
openAIGateway.CloseOpenAIWSPool()
}
return nil
}},
{"ScheduledTestRunnerService", func() error {
if scheduledTestRunner != nil {
scheduledTestRunner.Stop()
}
return nil
}},
{"BackupService", func() error {
if backupSvc != nil {
backupSvc.Stop()
}
return nil
}},
{"PaymentOrderExpiryService", func() error {
if paymentOrderExpiry != nil {
paymentOrderExpiry.Stop()
}
return nil
}},
}
infraSteps := []cleanupStep{
{"Redis", func() error {
if rdb == nil {
return nil
}
return rdb.Close()
}},
{"Ent", func() error {
if entClient == nil {
return nil
}
return entClient.Close()
}},
}
runParallel := func(steps []cleanupStep) {
var wg sync.WaitGroup
for i := range steps {
step := steps[i]
wg.Add(1)
go func() {
defer wg.Done()
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
return
}
log.Printf("[Cleanup] %s succeeded", step.name)
}()
}
wg.Wait()
}
runSequential := func(steps []cleanupStep) {
for i := range steps {
step := steps[i]
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
continue
}
log.Printf("[Cleanup] %s succeeded", step.name)
}
}
runParallel(parallelSteps)
runSequential(infraSteps)
// Check if context timed out
select {
case <-ctx.Done():
log.Printf("[Cleanup] Warning: cleanup timed out after 10 seconds")
default:
log.Printf("[Cleanup] All cleanup steps completed")
}
}
}