account_protection_transition.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. package service
  2. import (
  3. "context"
  4. "maps"
  5. "time"
  6. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  7. )
  8. var ErrProtectionConflict = infraerrors.Conflict("PROTECTION_CONFLICT", "账号配置已变化,请刷新后重试")
  9. type protectionWriteExpectationKey struct{}
  10. type ProtectionWriteExpectation struct {
  11. AccountID int64
  12. UpdatedAt time.Time
  13. }
  14. func GetProtectionWriteExpectation(ctx context.Context) (ProtectionWriteExpectation, bool) {
  15. v, ok := ctx.Value(protectionWriteExpectationKey{}).(ProtectionWriteExpectation)
  16. return v, ok
  17. }
  18. // Calculate transitions without publishing an intermediate disabled account.
  19. // The repository compares the original revision under its row lock before the
  20. // single final update, outbox insert and transaction commit.
  21. func (s *AntiDegradeService) transition(ctx context.Context, id int64, plan func(*AntiDegradeService) error) (*Account, error) {
  22. if s == nil || s.admin == nil {
  23. return nil, infraerrors.ServiceUnavailable("PROTECTION_UNAVAILABLE", "账号保护服务不可用")
  24. }
  25. original, err := s.admin.GetAccount(ctx, id)
  26. if err != nil {
  27. return nil, err
  28. }
  29. draft := *original
  30. draft.Extra = maps.Clone(original.Extra)
  31. store := &protectionDraftStore{account: &draft}
  32. planner := &AntiDegradeService{admin: store, cfg: s.cfg, pluginManager: s.pluginManager}
  33. if err := plan(planner); err != nil {
  34. return nil, err
  35. }
  36. if !store.changed {
  37. return original, nil
  38. }
  39. ctx = context.WithValue(ctx, mode1ManagedWriteKey{}, true)
  40. ctx = context.WithValue(ctx, protectionWriteExpectationKey{}, ProtectionWriteExpectation{id, original.UpdatedAt})
  41. return s.admin.UpdateAccount(ctx, id, &UpdateAccountInput{Extra: store.account.Extra, Concurrency: &store.account.Concurrency})
  42. }
  43. type protectionDraftStore struct {
  44. account *Account
  45. changed bool
  46. }
  47. func (s *protectionDraftStore) GetAccount(context.Context, int64) (*Account, error) {
  48. a := *s.account
  49. a.Extra = maps.Clone(a.Extra)
  50. return &a, nil
  51. }
  52. func (s *protectionDraftStore) UpdateAccount(_ context.Context, _ int64, in *UpdateAccountInput) (*Account, error) {
  53. a := *s.account
  54. if in.Extra != nil {
  55. a.Extra = prepareCodexFingerprintExtraForUpdate(&a, in.Extra)
  56. }
  57. if in.Concurrency != nil {
  58. a.Concurrency = *in.Concurrency
  59. }
  60. BoundAccountProtectionConcurrency(&a)
  61. if err := ValidateAccountProtectionConfiguration(&a); err != nil {
  62. return nil, err
  63. }
  64. s.account, s.changed = &a, true
  65. return s.GetAccount(context.Background(), a.ID)
  66. }