| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- package service
- import (
- "context"
- "errors"
- "maps"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/config"
- infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
- )
- var ErrUnknownAntiDegradeMode = infraerrors.BadRequest("UNKNOWN_PROTECTION_STRATEGY", "未知的账号保护策略")
- // Account protection configures stable identity, isolated conversations,
- // bounded concurrency and protocol-aware request integrity. Mode 1 v3 uses
- // the original standard transport. These settings cannot prove model quality.
- // Preview is read-only; Apply preserves a snapshot for explicit Revert.
- const (
- // AntiDegradeMarkerExtraKey 一键防降智标记与快照。
- AntiDegradeMarkerExtraKey = "anti_degrade"
- // AntiDegradeConcurrencyCap 无限制并发一键收敛的目标值。
- AntiDegradeConcurrencyCap = 16
- )
- // AntiDegradeMode selects the current compatibility policy or the legacy policy.
- type AntiDegradeMode string
- const (
- AntiDegradeMode1 AntiDegradeMode = "mode1"
- AntiDegradeMode2 AntiDegradeMode = "mode2"
- AntiDegradeModeNative AntiDegradeMode = "native_baseline"
- AntiDegradeModeMinimal AntiDegradeMode = "minimal_compat"
- AntiDegradeModeSession AntiDegradeMode = "session_standard"
- // AntiDegradeModeLegacy is the original (sub2初代) strategy: session
- // fingerprint convergence with the nodejs24 TLS template. It is kept as
- // an explicit option so operators can reproduce the known-good baseline.
- AntiDegradeModeLegacy AntiDegradeMode = "legacy"
- AntiDegradeModeTLSNode24 AntiDegradeMode = "tls_node24"
- AntiDegradeModeLowConcurrency AntiDegradeMode = "low_concurrency"
- // DefaultAntiDegradeMode applies only when selecting a new policy. Existing
- // persisted policies must keep their original interpretation.
- DefaultAntiDegradeMode AntiDegradeMode = AntiDegradeModeLegacy
- )
- func normalizeAntiDegradeMode(mode AntiDegradeMode) AntiDegradeMode {
- if mode == "" {
- return DefaultAntiDegradeMode
- }
- return mode
- }
- func antiDegradeModeSettings(mode AntiDegradeMode) (codexFingerprintMode, string, int) {
- profile := antiDegradeStrategyProfile(normalizeAntiDegradeMode(mode))
- if profile.ID == AntiDegradeModeNative {
- return codexFingerprintOff, "", 0
- }
- fingerprint := codexFingerprintMode(profile.IdentityMode)
- if fingerprint == "" {
- fingerprint = codexFingerprintOff
- }
- return fingerprint, profile.TLSProfile, profile.MaxConcurrency
- }
- // AntiDegradeChange 单项改动(展示用)。
- type AntiDegradeChange struct {
- Key string `json:"key"`
- From any `json:"from,omitempty"`
- To any `json:"to"`
- Note string `json:"note,omitempty"`
- }
- // AntiDegradePreview 预览结果。
- type AntiDegradePreview struct {
- Runtime *ProtectionRuntimeState `json:"runtime,omitempty"`
- ActiveMode string `json:"active_mode"`
- PolicyVersion int `json:"policy_version"`
- IdentityReady bool `json:"identity_ready"`
- TLSProfile string `json:"tls_profile"`
- Issues []string `json:"issues"`
- AccountID int64 `json:"account_id"`
- Enabled bool `json:"enabled"`
- Eligible bool `json:"eligible"`
- Reason string `json:"reason,omitempty"`
- Changes []AntiDegradeChange `json:"changes"`
- }
- // AntiDegradeStore 防降智需要的账号读写接口(AdminService 已实现,无需改接口)。
- type AntiDegradeStore interface {
- GetAccount(ctx context.Context, id int64) (*Account, error)
- UpdateAccount(ctx context.Context, id int64, input *UpdateAccountInput) (*Account, error)
- }
- // AntiDegradeService 一键防降智服务(无状态,可直接构造)。
- type AntiDegradeService struct {
- admin AntiDegradeStore
- cfg *config.Config
- pluginManager *PluginManager
- }
- // NewAntiDegradeService 创建防降智服务。
- func NewAntiDegradeService(admin AntiDegradeStore) *AntiDegradeService {
- return &AntiDegradeService{admin: admin}
- }
- func isOpenAIOAuthLike(a *Account) bool {
- return a != nil && a.Platform == PlatformOpenAI &&
- (a.Type == AccountTypeOAuth || a.Type == AccountTypeSetupToken)
- }
- func isAnthropicOAuthLike(a *Account) bool {
- return a != nil && a.Platform == PlatformAnthropic &&
- (a.Type == AccountTypeOAuth || a.Type == AccountTypeSetupToken)
- }
- // antiDegradeEnabled 是否已启用一键防降智。
- func antiDegradeEnabled(a *Account) bool {
- if a == nil || len(a.Extra) == 0 {
- return false
- }
- marker, ok := a.Extra[AntiDegradeMarkerExtraKey].(map[string]any)
- if !ok {
- return false
- }
- enabled, _ := marker["enabled"].(bool)
- return enabled
- }
- func antiDegradePrev(a *Account) map[string]any {
- if a == nil || len(a.Extra) == 0 {
- return nil
- }
- marker, ok := a.Extra[AntiDegradeMarkerExtraKey].(map[string]any)
- if !ok {
- return nil
- }
- prev, _ := marker["prev"].(map[string]any)
- return prev
- }
- func antiDegradeMode(a *Account) AntiDegradeMode {
- if a != nil && a.Extra != nil {
- if marker, ok := a.Extra[AntiDegradeMarkerExtraKey].(map[string]any); ok {
- if raw, ok := marker["mode"].(string); ok && raw != "" {
- return AntiDegradeMode(raw)
- }
- }
- }
- return AntiDegradeMode1
- }
- // PreviewAntiDegrade 计算改动集(不写库)。
- func PreviewAntiDegrade(account *Account) AntiDegradePreview {
- return PreviewAntiDegradeMode(account, DefaultAntiDegradeMode)
- }
- // PreviewAntiDegradeMode 计算指定方案的真实改动集(不写库)。
- func PreviewAntiDegradeMode(account *Account, mode AntiDegradeMode) AntiDegradePreview {
- mode = normalizeAntiDegradeMode(mode)
- if antiDegradeStrategyProfile(mode).ID == "" {
- return AntiDegradePreview{Changes: []AntiDegradeChange{}, Reason: "未知的账号保护策略"}
- }
- if mode == AntiDegradeModeNative {
- out := AntiDegradePreview{Changes: []AntiDegradeChange{}, TLSProfile: "account"}
- if account == nil {
- out.Reason = "account not found"
- return out
- }
- out.AccountID = account.ID
- out.Enabled = antiDegradeEnabled(account)
- if out.Enabled {
- out.ActiveMode = string(antiDegradeMode(account))
- out.Changes = append(out.Changes, AntiDegradeChange{Key: "strategy", From: string(antiDegradeMode(account)), To: string(AntiDegradeModeNative), Note: "切换为原生基线;需要管理员确认关闭保护"})
- out.Eligible = true
- out.Reason = "原生基线不会写入新的保护配置"
- } else {
- out.Reason = "当前已是原生基线"
- }
- return out
- }
- if mode == AntiDegradeMode1 {
- return previewMode1(account)
- }
- out := AntiDegradePreview{Changes: []AntiDegradeChange{}}
- if account == nil {
- out.Reason = "account not found"
- return out
- }
- out.AccountID = account.ID
- out.Enabled = antiDegradeEnabled(account)
- if reason := antiDegradeEligibilityIssue(account, mode); reason != "" {
- out.Reason = reason
- return out
- }
- if out.Enabled {
- out.ActiveMode = string(antiDegradeMode(account))
- }
- // Surface the effective baseline values in previews as well as the change
- // list. This lets the admin UI show which strategy is actually active
- // instead of falling back to an "unverified" badge after a modal refresh.
- if _, ok := codexFingerprintSeed(account.Extra); ok {
- out.IdentityReady = true
- }
- effectiveMode := mode
- if out.Enabled {
- effectiveMode = antiDegradeMode(account)
- }
- _, effectiveTLS, _ := antiDegradeModeSettings(effectiveMode)
- if (isOpenAIOAuthLike(account) || isAnthropicOAuthLike(account)) && effectiveTLS != "" {
- out.TLSProfile = effectiveTLS
- }
- if out.Enabled {
- if antiDegradeMode(account) == mode {
- out.Reason = "already enabled, can revert"
- return out
- }
- out.Eligible = true
- out.Reason = "可切换到所选防降智策略"
- out.Changes = append(out.Changes, AntiDegradeChange{Key: "strategy", From: string(antiDegradeMode(account)), To: string(mode), Note: "先安全还原当前快照,再应用所选策略"})
- return out
- }
- targetFingerprint, targetTLS, targetConcurrency := antiDegradeModeSettings(mode)
- if isOpenAIOAuthLike(account) {
- if account.GetCodexFingerprintMode() != targetFingerprint {
- out.Changes = append(out.Changes, AntiDegradeChange{
- Key: "extra.codex_fingerprint_mode",
- From: string(account.GetCodexFingerprintMode()),
- To: string(targetFingerprint),
- Note: "账号级 Codex 身份收敛",
- })
- }
- if targetTLS == "standard" {
- if account.IsTLSFingerprintEnabled() || account.Extra["tls_fingerprint_builtin"] != nil || account.Extra["tls_fingerprint_profile_id"] != nil {
- out.Changes = append(out.Changes, AntiDegradeChange{Key: "transport", From: account.Extra["tls_fingerprint_builtin"], To: targetTLS, Note: "恢复标准 TLS 传输"})
- }
- } else if !account.IsTLSFingerprintEnabled() || account.Extra["tls_fingerprint_builtin"] != targetTLS {
- out.Changes = append(out.Changes, AntiDegradeChange{Key: "extra.tls_fingerprint_builtin", From: account.Extra["tls_fingerprint_builtin"], To: targetTLS, Note: "完整 TLS ClientHello 模板"})
- }
- } else if isAnthropicOAuthLike(account) {
- if targetTLS != "standard" && (!account.IsTLSFingerprintEnabled() || account.Extra["tls_fingerprint_builtin"] != targetTLS) {
- out.Changes = append(out.Changes, AntiDegradeChange{Key: "extra.tls_fingerprint_builtin", From: account.Extra["tls_fingerprint_builtin"], To: targetTLS, Note: "完整 TLS ClientHello 模板"})
- }
- } else {
- out.Reason = "platform has no fingerprint convergence; only generic items apply"
- }
- if account.Concurrency <= 0 {
- out.Changes = append(out.Changes, AntiDegradeChange{
- Key: "concurrency",
- From: account.Concurrency,
- To: targetConcurrency,
- Note: "不限流并发收敛,避免突发触发上游限流",
- })
- }
- out.Eligible = len(out.Changes) > 0
- // Selecting a strategy is itself a meaningful change even when the
- // account's low-level fields already happen to match the target values.
- // Persisting the marker makes the effective mode explicit and allows the
- // runtime/UI to distinguish legacy/mode2 from an unconfigured account.
- if !out.Eligible && !out.Enabled {
- out.Eligible = true
- out.Changes = append(out.Changes, AntiDegradeChange{
- Key: "strategy", From: string(antiDegradeMode(account)), To: string(mode),
- Note: "记录所选防降智策略",
- })
- }
- if !out.Eligible && out.Reason == "" {
- out.Reason = "nothing to change"
- }
- return out
- }
- // Preview 获取账号并计算改动预览(不写库,供 handler 用)。
- func (s *AntiDegradeService) Preview(ctx context.Context, id int64) (AntiDegradePreview, error) {
- return s.PreviewMode(ctx, id, DefaultAntiDegradeMode)
- }
- func (s *AntiDegradeService) PreviewMode(ctx context.Context, id int64, mode AntiDegradeMode) (AntiDegradePreview, error) {
- if s == nil || s.admin == nil {
- return AntiDegradePreview{}, errors.New("anti-degrade service unavailable")
- }
- mode = normalizeAntiDegradeMode(mode)
- if antiDegradeStrategyProfile(mode).ID == "" {
- return AntiDegradePreview{}, ErrUnknownAntiDegradeMode
- }
- account, err := s.admin.GetAccount(ctx, id)
- if err != nil {
- return AntiDegradePreview{}, err
- }
- preview := PreviewAntiDegradeMode(account, mode)
- preview.Runtime = ResolveProtectionRuntime(account, s.cfg, s.pluginManager)
- return preview, nil
- }
- // Apply handler 适配:应用一键防降智。
- func (s *AntiDegradeService) Apply(ctx context.Context, id int64) (*Account, error) {
- return s.ApplyAntiDegradeMode(ctx, id, DefaultAntiDegradeMode)
- }
- // Revert handler 适配:一键还原。
- func (s *AntiDegradeService) Revert(ctx context.Context, id int64) (*Account, error) {
- return s.RevertAntiDegrade(ctx, id)
- }
- // ApplyAntiDegrade 应用一键防降智并快照旧值。
- func (s *AntiDegradeService) ApplyAntiDegrade(ctx context.Context, id int64) (*Account, error) {
- return s.ApplyAntiDegradeMode(ctx, id, DefaultAntiDegradeMode)
- }
- // ApplyAntiDegradeMode 应用指定方案并快照旧值。
- func (s *AntiDegradeService) ApplyAntiDegradeMode(ctx context.Context, id int64, mode AntiDegradeMode) (*Account, error) {
- return s.transition(ctx, id, func(planner *AntiDegradeService) error {
- _, err := planner.applyAntiDegradeMode(ctx, id, mode)
- return err
- })
- }
- func (s *AntiDegradeService) applyAntiDegradeMode(ctx context.Context, id int64, mode AntiDegradeMode) (*Account, error) {
- if s == nil || s.admin == nil {
- return nil, errors.New("anti-degrade service unavailable")
- }
- mode = normalizeAntiDegradeMode(mode)
- if antiDegradeStrategyProfile(mode).ID == "" {
- return nil, ErrUnknownAntiDegradeMode
- }
- account, err := s.admin.GetAccount(ctx, id)
- if err != nil {
- return nil, err
- }
- if mode == AntiDegradeModeNative {
- return nil, errors.New("native baseline requires the explicit protection revert confirmation")
- }
- if reason := antiDegradeEligibilityIssue(account, mode); reason != "" {
- return nil, infraerrors.BadRequest("PROTECTION_NOT_ELIGIBLE", reason)
- }
- // This service operates on the transition's in-memory draft. Only its
- // final state is committed by transition().
- if antiDegradeEnabled(account) && antiDegradeMode(account) != mode {
- if _, err := s.revertAntiDegrade(ctx, id); err != nil {
- return nil, err
- }
- account, err = s.admin.GetAccount(ctx, id)
- if err != nil {
- return nil, err
- }
- }
- if mode == AntiDegradeMode1 {
- return s.applyMode1(ctx, account)
- }
- preview := PreviewAntiDegradeMode(account, mode)
- if preview.Enabled {
- return account, nil
- }
- if !preview.Eligible {
- return account, nil
- }
- extra := maps.Clone(account.Extra)
- if extra == nil {
- extra = make(map[string]any)
- }
- prev := snapshotAntiDegradeConfig(account)
- input := &UpdateAccountInput{Extra: extra}
- targetFingerprint, targetTLS, targetConcurrency := antiDegradeModeSettings(mode)
- if isOpenAIOAuthLike(account) && account.GetCodexFingerprintMode() != targetFingerprint {
- extra[codexFingerprintModeExtraKey] = string(targetFingerprint)
- // seed 由 prepareCodexFingerprintExtraForUpdate 在 Update 内自动签发。
- }
- if (isOpenAIOAuthLike(account) || isAnthropicOAuthLike(account)) && targetTLS == "standard" && (account.IsTLSFingerprintEnabled() || account.Extra["tls_fingerprint_builtin"] != nil || account.Extra["tls_fingerprint_profile_id"] != nil) {
- extra["enable_tls_fingerprint"] = false
- delete(extra, "tls_fingerprint_builtin")
- delete(extra, "tls_fingerprint_profile_id")
- } else if (isOpenAIOAuthLike(account) || isAnthropicOAuthLike(account)) && targetTLS != "standard" && (!account.IsTLSFingerprintEnabled() || account.Extra["tls_fingerprint_builtin"] != targetTLS) {
- extra["enable_tls_fingerprint"] = true
- extra["tls_fingerprint_builtin"] = targetTLS
- delete(extra, "tls_fingerprint_profile_id")
- }
- if account.Concurrency <= 0 {
- cap := targetConcurrency
- input.Concurrency = &cap
- }
- if account.Concurrency > 0 {
- targetConcurrency = account.Concurrency
- }
- extra[AntiDegradeMarkerExtraKey] = map[string]any{
- "enabled": true,
- "mode": string(mode),
- // Keep an explicit runtime cap in the marker for the legacy policy too;
- // BoundAccountProtectionConcurrency uses this value on every request.
- "max_concurrency": targetConcurrency,
- "applied_at": time.Now().UTC().Format(time.RFC3339),
- "prev": prev,
- }
- extra[AntiDegradationExtraKey] = true
- extra[ProtectionScopeExtraKey] = "legacy"
- return s.admin.UpdateAccount(context.WithValue(ctx, mode1ManagedWriteKey{}, true), id, input)
- }
- // RevertAntiDegrade 一键还原:恢复快照旧值并清除标记。
- func (s *AntiDegradeService) RevertAntiDegrade(ctx context.Context, id int64) (*Account, error) {
- return s.transition(ctx, id, func(planner *AntiDegradeService) error {
- _, err := planner.revertAntiDegrade(ctx, id)
- return err
- })
- }
- func (s *AntiDegradeService) revertAntiDegrade(ctx context.Context, id int64) (*Account, error) {
- if s == nil || s.admin == nil {
- return nil, errors.New("anti-degrade service unavailable")
- }
- account, err := s.admin.GetAccount(ctx, id)
- if err != nil {
- return nil, err
- }
- if !account.AntiDegradationEnabled() {
- return account, nil
- }
- prev := antiDegradePrev(account)
- if isMode1ProtectionRequested(account) && prev == nil {
- return nil, errors.New("mode1 original configuration snapshot is missing")
- }
- // 先校验快照类型:未知类型直接报错,不写库(避免半还原后快照丢失无法重试)。
- var concurrency *int
- if conc, ok := prev["concurrency"]; ok {
- switch v := conc.(type) {
- case float64:
- c := int(v)
- if v != float64(c) {
- return nil, errors.New("anti-degrade snapshot has invalid concurrency value")
- }
- concurrency = &c
- case int:
- concurrency = &v
- case int64:
- c := int(v)
- concurrency = &c
- default:
- return nil, errors.New("anti-degrade snapshot has unsupported concurrency type")
- }
- }
- extra := maps.Clone(account.Extra)
- if extra == nil {
- extra = make(map[string]any)
- }
- // Concurrency is now independent from identity/TLS. Restoring a strategy
- // must not undo the administrator's later concurrency edits.
- _ = concurrency // historical snapshot remains validated but is not reapplied
- input := &UpdateAccountInput{Extra: extra}
- extra[AntiDegradationExtraKey] = false
- extra[ProtectionScopeExtraKey] = "disabled"
- if isMode1ProtectionRequested(account) {
- for _, key := range mode1ManagedExtraKeys {
- if key == AntiDegradeMarkerExtraKey {
- continue
- }
- if value, exists := prev[key]; exists && value != nil {
- extra[key] = value
- } else {
- delete(extra, key)
- }
- }
- delete(extra, AntiDegradeMarkerExtraKey)
- return s.admin.UpdateAccount(context.WithValue(ctx, mode1ManagedWriteKey{}, true), id, input)
- }
- // New snapshots include all managed keys, with nil recording absence.
- // Historical snapshots were sparse: omitted keys must be left intact.
- for _, key := range mode1ManagedExtraKeys {
- if key == AntiDegradeMarkerExtraKey {
- continue
- }
- if value, exists := prev[key]; exists {
- if value == nil {
- delete(extra, key)
- } else {
- extra[key] = value
- }
- }
- }
- delete(extra, AntiDegradeMarkerExtraKey)
- return s.admin.UpdateAccount(context.WithValue(ctx, mode1ManagedWriteKey{}, true), id, input)
- }
- func snapshotAntiDegradeConfig(account *Account) map[string]any {
- prev := map[string]any{"concurrency": account.Concurrency}
- for _, key := range mode1ManagedExtraKeys {
- if key != AntiDegradeMarkerExtraKey {
- prev[key] = account.Extra[key]
- }
- }
- return prev
- }
|