package main import ( "encoding/json" "io" "net/http" "sync" "time" "github.com/golang/freetype/truetype" ) type Config struct { TimeAPIURL string `json:"timeApiUrl"` } type appPaths struct { ConfigFile string AccountFile string LogDir string SuccessDir string FontFiles []string } type TraceConfig struct { Enabled bool MaxBodyBytes int } type Account struct { Name string `json:"name"` Password string `json:"password"` Cookie string `json:"cookie"` } type Product struct { CommodityID string `json:"commodityId"` CommodityName string `json:"commodityName"` Score int `json:"score"` Count int `json:"count"` Pic string `json:"pic"` Status int `json:"status"` OpenStatus int `json:"openStatus"` StoreStatus int `json:"storeStatus"` TimeLimited int `json:"timeLimited"` CountLimited int `json:"countLimited"` EliteLimited int `json:"eliteLimited"` } type ProductPayload struct { Success bool `json:"success"` Source string `json:"source"` Products []Product `json:"products"` Count int `json:"count"` FetchedAt string `json:"fetchedAt"` AgeSecond int `json:"ageSecond"` Stale bool `json:"stale"` } type AccountView struct { Index int `json:"index"` Name string `json:"name"` Cookie string `json:"cookie"` } type SeckillRequest struct { AccountProducts map[string]string `json:"accountProducts"` TargetTime string `json:"targetTime"` Advance int `json:"advance"` } type APIResponse struct { Error int `json:"error"` Msg string `json:"msg"` Data json.RawMessage `json:"data"` } type LogEntry struct { ID int64 `json:"id"` Time string `json:"time"` Level string `json:"level"` Category string `json:"category"` Message string `json:"message"` } type appState struct { config Config paths appPaths client *DouyuClient logs *memoryLogStore trace *traceRecorder accountsMu sync.RWMutex accounts []Account productsMu sync.RWMutex products []Product productsAt time.Time resultsMu sync.RWMutex results map[string]string scheduleMu sync.Mutex schedule *time.Timer warmupSchedule *time.Timer resyncSchedule *time.Timer warmupsMu sync.RWMutex warmups map[string]WarmupData fontMu sync.Mutex font *truetype.Font } type DouyuClient struct { httpClient *http.Client timeAPIURL string trace *traceRecorder } type traceRecorder struct { enabled bool maxBodyBytes int writer *asyncLogWriter filePath string seq uint64 } type asyncLogWriter struct { dst io.Writer ch chan []byte wg sync.WaitGroup closed uint32 dropped uint64 } type ExchangeAttempt struct { Account Account Product Product Index int } type ExchangeResult struct { OK bool Account string Product string Attempt int RoleName string Message string } type TimeSample struct { ServerTime time.Time LocalTime time.Time RTT time.Duration Offset time.Duration ErrorBound time.Duration RawCurrentTime int64 Samples int Source string Fallback bool } type TimeCalibration struct { Offset time.Duration ErrorBound time.Duration RTT time.Duration Samples int CreatedAt time.Time Source string Fallback bool } func (c TimeCalibration) EstimatedServerNow() time.Time { if c.CreatedAt.IsZero() { return time.Now().UTC() } return time.Now().Add(c.Offset).UTC() } func (c TimeCalibration) IsZero() bool { return c.CreatedAt.IsZero() } type WarmupData struct { CSRF string RoleName string CreatedAt time.Time } // EstimatedServerNow 根据采样偏移估算当前斗鱼时间。 func (t TimeSample) EstimatedServerNow() time.Time { if t.LocalTime.IsZero() { return t.ServerTime } return time.Now().Add(t.Offset).UTC() } type memoryLogStore struct { mu sync.RWMutex nextID int64 max int filePath string entries []LogEntry } var state = &appState{ config: Config{TimeAPIURL: defaultTimeAPI}, results: make(map[string]string), }