deps.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package faultRecordQueue
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "fwh-toolbox-server/cache/redisx"
  7. "fwh-toolbox-server/domain"
  8. "fwh-toolbox-server/repositories/api/fwh_pro"
  9. "fwh-toolbox-server/repositories/api/fwh_test"
  10. minioRepo "fwh-toolbox-server/repositories/minio"
  11. mongoRepo "fwh-toolbox-server/repositories/mongo"
  12. "fwh-toolbox-server/repositories/mqtt/fwh_mqtt"
  13. "go.mongodb.org/mongo-driver/bson"
  14. "go.mongodb.org/mongo-driver/bson/primitive"
  15. "go.uber.org/zap"
  16. )
  17. // 执行器依赖的外部能力,抽象为接口便于测试
  18. type mqttBus interface {
  19. Publish(ctx context.Context, topic string, msg fwh_mqtt.FwhMqttMsg) error
  20. Subscribe(ctx context.Context, topic string, h fwh_mqtt.Handler) (func(), error)
  21. }
  22. type opStore interface {
  23. Get(ctx context.Context, id primitive.ObjectID) (*domain.FaultRecordOperation, error)
  24. UpdateStatus(ctx context.Context, id primitive.ObjectID, status domain.FaultRecordOpStatus, extra bson.M) (bool, error)
  25. SetFields(ctx context.Context, id primitive.ObjectID, fields bson.M) error
  26. AppendMessage(ctx context.Context, id primitive.ObjectID, msg domain.FaultRecordOpMessage) error
  27. }
  28. type fileStore interface {
  29. Put(ctx context.Context, key string, data []byte) error
  30. Get(ctx context.Context, key string) ([]byte, error)
  31. }
  32. type locker interface {
  33. // Acquire 以 token 加锁并自动续期;返回的 release 停止续期并释放
  34. Acquire(ctx context.Context, key, token string, ttl time.Duration) (release func(), ok bool, err error)
  35. }
  36. type deps struct {
  37. bus func(env string) (mqttBus, error)
  38. store opStore
  39. files fileStore
  40. download func(env, equipNo, fileName string) ([]byte, error)
  41. downloadURL func(env, equipNo, fileName string) string
  42. lock locker
  43. timeouts timeouts
  44. }
  45. type timeouts struct {
  46. ack time.Duration // 发送 → 378
  47. reply time.Duration // 378 → 379(cmd=0/1/3)
  48. replyRecord time.Duration // 378 → 379(cmd=2,设备需读取子设备记录)
  49. replyPerRecord time.Duration // cmd=2 每多一条记录增加的等待
  50. upload time.Duration // 379 → 609
  51. uploadPerRecord time.Duration // 每多一条记录增加的上传等待
  52. cancelPoll time.Duration
  53. lockTTL time.Duration
  54. drain time.Duration // 操作结束后继续记录报文的时间
  55. afterUploadAck time.Duration // 收到 610 后等待多久开始下载
  56. downloadInterval time.Duration // 下载失败后的重试间隔,一直重试至总超时
  57. manualRetryAttempts int // 手动重试下载的最多尝试次数
  58. }
  59. var defaultTimeouts = timeouts{
  60. ack: 30 * time.Second,
  61. reply: 2 * time.Minute,
  62. replyRecord: 4 * time.Minute,
  63. replyPerRecord: time.Minute, // 样本单条约 43 秒
  64. upload: 5 * time.Minute,
  65. uploadPerRecord: 30 * time.Second,
  66. cancelPoll: time.Second,
  67. lockTTL: 2 * time.Minute,
  68. drain: 2 * time.Second,
  69. afterUploadAck: 15 * time.Second, // 用户要求:收到 610 后 15 秒开始下载
  70. downloadInterval: 15 * time.Second, // 用户要求:失败后每 15 秒重试
  71. manualRetryAttempts: 3, // 用户要求:手动重试只尝试 3 次
  72. }
  73. /************** 默认实现 **************/
  74. type mongoStore struct{}
  75. func (mongoStore) Get(ctx context.Context, id primitive.ObjectID) (*domain.FaultRecordOperation, error) {
  76. return mongoRepo.GetFaultRecordOp(ctx, id)
  77. }
  78. func (mongoStore) UpdateStatus(ctx context.Context, id primitive.ObjectID, s domain.FaultRecordOpStatus, extra bson.M) (bool, error) {
  79. return mongoRepo.UpdateFaultRecordOpStatus(ctx, id, s, extra)
  80. }
  81. func (mongoStore) SetFields(ctx context.Context, id primitive.ObjectID, f bson.M) error {
  82. return mongoRepo.SetFaultRecordOpFields(ctx, id, f)
  83. }
  84. func (mongoStore) AppendMessage(ctx context.Context, id primitive.ObjectID, m domain.FaultRecordOpMessage) error {
  85. return mongoRepo.AppendFaultRecordOpMessage(ctx, id, m)
  86. }
  87. type minioStore struct{}
  88. func (minioStore) Put(ctx context.Context, key string, data []byte) error {
  89. return minioRepo.PutFaultRecordFile(ctx, key, data)
  90. }
  91. func (minioStore) Get(ctx context.Context, key string) ([]byte, error) {
  92. return minioRepo.ReadFaultRecordFile(ctx, key)
  93. }
  94. func mqttBusFor(env string) (mqttBus, error) {
  95. e, err := fwh_mqtt.ParseEnv(env)
  96. if err != nil {
  97. return nil, err
  98. }
  99. return fwh_mqtt.GetClient(e)
  100. }
  101. func downloadURLFor(env, equipNo, fileName string) string {
  102. if env == string(fwh_mqtt.EnvTest) {
  103. return fwh_test.FaultRecordUploadFileURL(equipNo, fileName)
  104. }
  105. return fwh_pro.FaultRecordUploadFileURL(equipNo, fileName)
  106. }
  107. func downloadFor(env, equipNo, fileName string) ([]byte, error) {
  108. switch env {
  109. case string(fwh_mqtt.EnvProd):
  110. return fwh_pro.DownloadFaultRecordUploadFile(equipNo, fileName)
  111. case string(fwh_mqtt.EnvTest):
  112. return fwh_test.DownloadFaultRecordUploadFile(equipNo, fileName)
  113. }
  114. return nil, fmt.Errorf("unknown env %s", env)
  115. }
  116. type redisLocker struct{}
  117. func (redisLocker) Acquire(ctx context.Context, key, token string, ttl time.Duration) (func(), bool, error) {
  118. lock, ok, err := redisx.TryLockWithToken(ctx, key, token, ttl)
  119. if err != nil || !ok {
  120. return nil, ok, err
  121. }
  122. stop := make(chan struct{})
  123. go func() {
  124. t := time.NewTicker(ttl / 3)
  125. defer t.Stop()
  126. for {
  127. select {
  128. case <-stop:
  129. return
  130. case <-t.C:
  131. if err := lock.Extend(context.Background(), ttl); err != nil {
  132. zap.L().Warn("fault record gateway lock extend failed", zap.String("key", key), zap.Error(err))
  133. }
  134. }
  135. }
  136. }()
  137. return func() {
  138. close(stop)
  139. lock.SafeRelease(context.Background())
  140. }, true, nil
  141. }
  142. func defaultDeps() deps {
  143. return deps{
  144. bus: mqttBusFor,
  145. store: mongoStore{},
  146. files: minioStore{},
  147. download: downloadFor,
  148. downloadURL: downloadURLFor,
  149. lock: redisLocker{},
  150. timeouts: defaultTimeouts,
  151. }
  152. }