| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- package faultRecordQueue
- import (
- "context"
- "fmt"
- "time"
- "fwh-toolbox-server/cache/redisx"
- "fwh-toolbox-server/domain"
- "fwh-toolbox-server/repositories/api/fwh_pro"
- "fwh-toolbox-server/repositories/api/fwh_test"
- minioRepo "fwh-toolbox-server/repositories/minio"
- mongoRepo "fwh-toolbox-server/repositories/mongo"
- "fwh-toolbox-server/repositories/mqtt/fwh_mqtt"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/bson/primitive"
- "go.uber.org/zap"
- )
- // 执行器依赖的外部能力,抽象为接口便于测试
- type mqttBus interface {
- Publish(ctx context.Context, topic string, msg fwh_mqtt.FwhMqttMsg) error
- Subscribe(ctx context.Context, topic string, h fwh_mqtt.Handler) (func(), error)
- }
- type opStore interface {
- Get(ctx context.Context, id primitive.ObjectID) (*domain.FaultRecordOperation, error)
- UpdateStatus(ctx context.Context, id primitive.ObjectID, status domain.FaultRecordOpStatus, extra bson.M) (bool, error)
- SetFields(ctx context.Context, id primitive.ObjectID, fields bson.M) error
- AppendMessage(ctx context.Context, id primitive.ObjectID, msg domain.FaultRecordOpMessage) error
- }
- type fileStore interface {
- Put(ctx context.Context, key string, data []byte) error
- Get(ctx context.Context, key string) ([]byte, error)
- }
- type locker interface {
- // Acquire 以 token 加锁并自动续期;返回的 release 停止续期并释放
- Acquire(ctx context.Context, key, token string, ttl time.Duration) (release func(), ok bool, err error)
- }
- type deps struct {
- bus func(env string) (mqttBus, error)
- store opStore
- files fileStore
- download func(env, equipNo, fileName string) ([]byte, error)
- downloadURL func(env, equipNo, fileName string) string
- lock locker
- timeouts timeouts
- }
- type timeouts struct {
- ack time.Duration // 发送 → 378
- reply time.Duration // 378 → 379(cmd=0/1/3)
- replyRecord time.Duration // 378 → 379(cmd=2,设备需读取子设备记录)
- replyPerRecord time.Duration // cmd=2 每多一条记录增加的等待
- upload time.Duration // 379 → 609
- uploadPerRecord time.Duration // 每多一条记录增加的上传等待
- cancelPoll time.Duration
- lockTTL time.Duration
- drain time.Duration // 操作结束后继续记录报文的时间
- afterUploadAck time.Duration // 收到 610 后等待多久开始下载
- downloadInterval time.Duration // 下载失败后的重试间隔,一直重试至总超时
- manualRetryAttempts int // 手动重试下载的最多尝试次数
- }
- var defaultTimeouts = timeouts{
- ack: 30 * time.Second,
- reply: 2 * time.Minute,
- replyRecord: 4 * time.Minute,
- replyPerRecord: time.Minute, // 样本单条约 43 秒
- upload: 5 * time.Minute,
- uploadPerRecord: 30 * time.Second,
- cancelPoll: time.Second,
- lockTTL: 2 * time.Minute,
- drain: 2 * time.Second,
- afterUploadAck: 15 * time.Second, // 用户要求:收到 610 后 15 秒开始下载
- downloadInterval: 15 * time.Second, // 用户要求:失败后每 15 秒重试
- manualRetryAttempts: 3, // 用户要求:手动重试只尝试 3 次
- }
- /************** 默认实现 **************/
- type mongoStore struct{}
- func (mongoStore) Get(ctx context.Context, id primitive.ObjectID) (*domain.FaultRecordOperation, error) {
- return mongoRepo.GetFaultRecordOp(ctx, id)
- }
- func (mongoStore) UpdateStatus(ctx context.Context, id primitive.ObjectID, s domain.FaultRecordOpStatus, extra bson.M) (bool, error) {
- return mongoRepo.UpdateFaultRecordOpStatus(ctx, id, s, extra)
- }
- func (mongoStore) SetFields(ctx context.Context, id primitive.ObjectID, f bson.M) error {
- return mongoRepo.SetFaultRecordOpFields(ctx, id, f)
- }
- func (mongoStore) AppendMessage(ctx context.Context, id primitive.ObjectID, m domain.FaultRecordOpMessage) error {
- return mongoRepo.AppendFaultRecordOpMessage(ctx, id, m)
- }
- type minioStore struct{}
- func (minioStore) Put(ctx context.Context, key string, data []byte) error {
- return minioRepo.PutFaultRecordFile(ctx, key, data)
- }
- func (minioStore) Get(ctx context.Context, key string) ([]byte, error) {
- return minioRepo.ReadFaultRecordFile(ctx, key)
- }
- func mqttBusFor(env string) (mqttBus, error) {
- e, err := fwh_mqtt.ParseEnv(env)
- if err != nil {
- return nil, err
- }
- return fwh_mqtt.GetClient(e)
- }
- func downloadURLFor(env, equipNo, fileName string) string {
- if env == string(fwh_mqtt.EnvTest) {
- return fwh_test.FaultRecordUploadFileURL(equipNo, fileName)
- }
- return fwh_pro.FaultRecordUploadFileURL(equipNo, fileName)
- }
- func downloadFor(env, equipNo, fileName string) ([]byte, error) {
- switch env {
- case string(fwh_mqtt.EnvProd):
- return fwh_pro.DownloadFaultRecordUploadFile(equipNo, fileName)
- case string(fwh_mqtt.EnvTest):
- return fwh_test.DownloadFaultRecordUploadFile(equipNo, fileName)
- }
- return nil, fmt.Errorf("unknown env %s", env)
- }
- type redisLocker struct{}
- func (redisLocker) Acquire(ctx context.Context, key, token string, ttl time.Duration) (func(), bool, error) {
- lock, ok, err := redisx.TryLockWithToken(ctx, key, token, ttl)
- if err != nil || !ok {
- return nil, ok, err
- }
- stop := make(chan struct{})
- go func() {
- t := time.NewTicker(ttl / 3)
- defer t.Stop()
- for {
- select {
- case <-stop:
- return
- case <-t.C:
- if err := lock.Extend(context.Background(), ttl); err != nil {
- zap.L().Warn("fault record gateway lock extend failed", zap.String("key", key), zap.Error(err))
- }
- }
- }
- }()
- return func() {
- close(stop)
- lock.SafeRelease(context.Background())
- }, true, nil
- }
- func defaultDeps() deps {
- return deps{
- bus: mqttBusFor,
- store: mongoStore{},
- files: minioStore{},
- download: downloadFor,
- downloadURL: downloadURLFor,
- lock: redisLocker{},
- timeouts: defaultTimeouts,
- }
- }
|