faultRecordRepo.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package mongo
  2. import (
  3. "context"
  4. "errors"
  5. "strings"
  6. "time"
  7. "fwh-toolbox-server/database"
  8. "fwh-toolbox-server/domain"
  9. "go.mongodb.org/mongo-driver/bson"
  10. "go.mongodb.org/mongo-driver/bson/primitive"
  11. "go.mongodb.org/mongo-driver/mongo"
  12. "go.mongodb.org/mongo-driver/mongo/options"
  13. )
  14. const CollectionName_FaultRecordOperation = "FaultRecordOperation"
  15. var ErrFaultRecordOpNotFound = errors.New("fault record operation not found")
  16. func faultRecordOps() *mongo.Collection {
  17. return database.GetCollection(CollectionName_FaultRecordOperation)
  18. }
  19. func InitFaultRecordIndexes(ctx context.Context) error {
  20. _, err := faultRecordOps().Indexes().CreateMany(ctx, []mongo.IndexModel{
  21. {Keys: bson.D{{Key: "env", Value: 1}, {Key: "gatewaySn", Value: 1}, {Key: "createdAt", Value: -1}}},
  22. {Keys: bson.D{{Key: "status", Value: 1}}},
  23. {Keys: bson.D{{Key: "createdAt", Value: -1}}},
  24. })
  25. return err
  26. }
  27. func InsertFaultRecordOp(ctx context.Context, op *domain.FaultRecordOperation) error {
  28. now := time.Now()
  29. if op.ID.IsZero() {
  30. op.ID = primitive.NewObjectID()
  31. }
  32. op.CreatedAt, op.UpdatedAt = now, now
  33. if op.StageTimes == nil {
  34. op.StageTimes = map[string]time.Time{}
  35. }
  36. op.StageTimes[string(op.Status)] = now
  37. if op.Messages == nil {
  38. op.Messages = []domain.FaultRecordOpMessage{}
  39. }
  40. _, err := faultRecordOps().InsertOne(ctx, op)
  41. return err
  42. }
  43. func GetFaultRecordOp(ctx context.Context, id primitive.ObjectID) (*domain.FaultRecordOperation, error) {
  44. var op domain.FaultRecordOperation
  45. err := faultRecordOps().FindOne(ctx, bson.M{"_id": id}).Decode(&op)
  46. if errors.Is(err, mongo.ErrNoDocuments) {
  47. return nil, ErrFaultRecordOpNotFound
  48. }
  49. if err != nil {
  50. return nil, err
  51. }
  52. return &op, nil
  53. }
  54. // UpdateFaultRecordOpStatus 更新状态并记录阶段时间;extra 为同时写入的其他字段。
  55. // 只更新尚未终止的操作,返回是否更新成功(已终止时返回 false)。
  56. func UpdateFaultRecordOpStatus(ctx context.Context, id primitive.ObjectID, status domain.FaultRecordOpStatus, extra bson.M) (bool, error) {
  57. now := time.Now()
  58. set := bson.M{"status": status, "updatedAt": now, "stageTimes." + string(status): now}
  59. for k, v := range extra {
  60. set[k] = v
  61. }
  62. res, err := faultRecordOps().UpdateOne(ctx,
  63. bson.M{"_id": id, "status": bson.M{"$nin": domain.FaultRecordTerminalStatuses()}},
  64. bson.M{"$set": set})
  65. if err != nil {
  66. return false, err
  67. }
  68. return res.ModifiedCount > 0, nil
  69. }
  70. // SetFaultRecordOpFields 更新字段,不改变状态
  71. func SetFaultRecordOpFields(ctx context.Context, id primitive.ObjectID, fields bson.M) error {
  72. fields["updatedAt"] = time.Now()
  73. _, err := faultRecordOps().UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": fields})
  74. return err
  75. }
  76. func AppendFaultRecordOpMessage(ctx context.Context, id primitive.ObjectID, msg domain.FaultRecordOpMessage) error {
  77. _, err := faultRecordOps().UpdateOne(ctx, bson.M{"_id": id},
  78. bson.M{"$push": bson.M{"messages": msg}, "$set": bson.M{"updatedAt": time.Now()}})
  79. return err
  80. }
  81. // RequestFaultRecordOpCancel 标记取消请求,仅对未终止的操作生效
  82. func RequestFaultRecordOpCancel(ctx context.Context, id primitive.ObjectID) (bool, error) {
  83. res, err := faultRecordOps().UpdateOne(ctx,
  84. bson.M{"_id": id, "status": bson.M{"$nin": domain.FaultRecordTerminalStatuses()}},
  85. bson.M{"$set": bson.M{"cancelRequested": true, "updatedAt": time.Now()}})
  86. if err != nil {
  87. return false, err
  88. }
  89. return res.MatchedCount > 0, nil
  90. }
  91. // ReopenFaultRecordOpForDownloadRetry 将“上传已完成但下载失败/超时”的操作重新置为 uploaded,供重试下载。
  92. // 条件在同一次更新中校验,避免并发重复触发;返回是否成功重开。
  93. func ReopenFaultRecordOpForDownloadRetry(ctx context.Context, id primitive.ObjectID) (bool, error) {
  94. now := time.Now()
  95. res, err := faultRecordOps().UpdateOne(ctx,
  96. bson.M{
  97. "_id": id,
  98. "kind": domain.FaultRecordOpRecordFetch,
  99. "status": bson.M{"$in": []domain.FaultRecordOpStatus{domain.FaultRecordStatusDownloadFailed, domain.FaultRecordStatusTimeout}},
  100. "stageTimes.uploaded": bson.M{"$exists": true},
  101. "fileName": bson.M{"$nin": []any{"", nil}},
  102. },
  103. bson.M{
  104. "$set": bson.M{"status": domain.FaultRecordStatusUploaded, "downloadRetryAt": now, "cancelRequested": false, "updatedAt": now},
  105. "$unset": bson.M{"error": "", "timeoutStage": ""},
  106. "$inc": bson.M{"downloadRetryCount": 1},
  107. })
  108. if err != nil {
  109. return false, err
  110. }
  111. return res.ModifiedCount > 0, nil
  112. }
  113. // FaultRecordOpFilter 操作历史查询条件
  114. type FaultRecordOpFilter struct {
  115. Env string
  116. GatewaySn string
  117. Kind string // 多个用逗号分隔
  118. }
  119. // ListFaultRecordOps 按创建时间倒序分页,不返回报文原文
  120. func ListFaultRecordOps(ctx context.Context, f FaultRecordOpFilter, page, size int64) ([]domain.FaultRecordOperation, int64, error) {
  121. filter := bson.M{}
  122. if f.Env != "" {
  123. filter["env"] = f.Env
  124. }
  125. if f.GatewaySn != "" {
  126. filter["gatewaySn"] = f.GatewaySn
  127. }
  128. if f.Kind != "" {
  129. if kinds := strings.Split(f.Kind, ","); len(kinds) > 1 {
  130. filter["kind"] = bson.M{"$in": kinds}
  131. } else {
  132. filter["kind"] = f.Kind
  133. }
  134. }
  135. total, err := faultRecordOps().CountDocuments(ctx, filter)
  136. if err != nil {
  137. return nil, 0, err
  138. }
  139. opts := options.Find().
  140. SetSort(bson.D{{Key: "createdAt", Value: -1}}).
  141. SetSkip((page - 1) * size).
  142. SetLimit(size).
  143. SetProjection(bson.M{"messages": 0})
  144. cur, err := faultRecordOps().Find(ctx, filter, opts)
  145. if err != nil {
  146. return nil, 0, err
  147. }
  148. var list []domain.FaultRecordOperation
  149. if err := cur.All(ctx, &list); err != nil {
  150. return nil, 0, err
  151. }
  152. return list, total, nil
  153. }
  154. // ListActiveFaultRecordOps 全部未终止的操作,用于启动时恢复
  155. func ListActiveFaultRecordOps(ctx context.Context) ([]domain.FaultRecordOperation, error) {
  156. cur, err := faultRecordOps().Find(ctx,
  157. bson.M{"status": bson.M{"$nin": domain.FaultRecordTerminalStatuses()}},
  158. options.Find().SetProjection(bson.M{"messages": 0}))
  159. if err != nil {
  160. return nil, err
  161. }
  162. var list []domain.FaultRecordOperation
  163. err = cur.All(ctx, &list)
  164. return list, err
  165. }