package vidmod import ( "fmt" "strings" "time" "91porn-server/common/log" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) func h265URLMissingFilter() bson.A { return bson.A{ bson.M{"h265Url": ""}, bson.M{"h265Url": nil}, bson.M{"h265Url": bson.M{"$exists": false}}, } } func h265FailCountRetryableFilter() bson.A { return bson.A{ bson.M{"h265FailCount": bson.M{"$exists": false}}, bson.M{"h265FailCount": bson.M{"$lt": H265MaxFailCount}}, } } func h265QueueableStatusFilter() bson.A { return bson.A{ bson.M{"h265Status": bson.M{"$exists": false}}, bson.M{"h265Status": H265StatusNone}, bson.M{"h265Status": H265StatusFailed, "$or": h265FailCountRetryableFilter()}, } } // QueueH265Transcode 将审核通过且尚无 H.265 地址的长视频加入等待队列。 func QueueH265Transcode(ids []primitive.ObjectID) (int64, error) { if len(ids) == 0 { return 0, nil } now := time.Now() filter := bson.M{ "_id": bson.M{"$in": ids}, "status": CheckPass, "newsType": SP, "sourceURL": bson.M{"$exists": true, "$ne": ""}, "$and": bson.A{ bson.M{"$or": h265URLMissingFilter()}, bson.M{"$or": h265QueueableStatusFilter()}, }, } update := bson.M{ "$set": bson.M{ "h265Status": H265StatusQueued, "h265QueuedAt": now, "updatedAt": now, }, "$unset": bson.M{"h265PendingAt": ""}, } result, err := coll(nil).UpdateMany(filter, update) if err != nil { log.Error("QueueH265Transcode UpdateMany failed", log.Any("ids", ids), log.E(err)) return 0, err } return result.ModifiedCount, nil } // CountH265Pending 统计已经提交云端、尚未取得结果的有效长视频。 func CountH265Pending() (int64, error) { filter := bson.M{ "h265Status": H265StatusPending, "status": CheckPass, "newsType": SP, "sourceURL": bson.M{"$exists": true, "$ne": ""}, "$or": h265URLMissingFilter(), } count, err := coll(nil).Count(filter) if err != nil { log.Error("CountH265Pending Count failed", log.E(err)) } return count, err } // GetQueuedH265Videos 获取等待提交或允许重试的 H.265 任务。 func GetQueuedH265Videos(limit int64) ([]*VideoModel, error) { if limit <= 0 { return []*VideoModel{}, nil } filter := bson.M{ "h265Status": bson.M{"$in": bson.A{H265StatusQueued, H265StatusFailed}}, "status": CheckPass, "newsType": SP, "sourceURL": bson.M{"$exists": true, "$ne": ""}, "$and": bson.A{ bson.M{"$or": h265URLMissingFilter()}, bson.M{"$or": h265FailCountRetryableFilter()}, }, } opts := options.Find(). SetLimit(limit). SetSort(bson.D{{Key: "h265QueuedAt", Value: 1}, {Key: "reviewAt", Value: -1}}) var out []*VideoModel if err := coll(nil).Find(&out, filter, opts); err != nil { log.Error("GetQueuedH265Videos Find failed", log.Any("filter", filter), log.E(err)) return nil, err } return out, nil } // GetPendingH265Videos 获取已经提交云端、等待轮询结果的 H.265 任务。 func GetPendingH265Videos(limit int64) ([]*VideoModel, error) { if limit <= 0 { return []*VideoModel{}, nil } filter := bson.M{ "h265Status": H265StatusPending, "status": CheckPass, "newsType": SP, "sourceURL": bson.M{"$exists": true, "$ne": ""}, "$or": h265URLMissingFilter(), } opts := options.Find().SetLimit(limit).SetSort(bson.D{{Key: "h265PendingAt", Value: 1}}) var out []*VideoModel if err := coll(nil).Find(&out, filter, opts); err != nil { log.Error("GetPendingH265Videos Find failed", log.Any("filter", filter), log.E(err)) return nil, err } return out, nil } // ClaimH265Pending 原子地将一个等待任务标记为云端处理中,并返回本次 // claim 的整秒时间。SKD 用该时间生成稳定的限时拉流 URL,重启后仍能算出 // 与提交时相同的云端 file_id。 func ClaimH265Pending(id primitive.ObjectID) (time.Time, bool, error) { now := time.Now().UTC().Truncate(time.Second) filter := bson.M{ "_id": id, "h265Status": bson.M{"$in": bson.A{H265StatusQueued, H265StatusFailed}}, "status": CheckPass, "newsType": SP, "sourceURL": bson.M{"$exists": true, "$ne": ""}, "$and": bson.A{ bson.M{"$or": h265URLMissingFilter()}, bson.M{"$or": h265FailCountRetryableFilter()}, }, } result, err := coll(nil).UpdateOne(filter, bson.M{"$set": bson.M{ "h265Status": H265StatusPending, "h265PendingAt": now, "updatedAt": now, }}) if err != nil { log.Error("ClaimH265Pending UpdateOne failed", log.Any("id", id), log.E(err)) return time.Time{}, false, err } if result.ModifiedCount == 0 { return time.Time{}, false, nil } return now, true, nil } // MarkH265Queued 将当前 pending 尝试重新放回等待队列。 // sourceURL 与 pendingAt 共同标识一次云端尝试,避免旧实例把同视频的 // 新尝试或已经成功回填的状态回退成 queued。 func MarkH265Queued(id primitive.ObjectID, sourceURL string, pendingAt time.Time) (bool, error) { now := time.Now() filter := pendingH265AttemptFilter(id, sourceURL, pendingAt) result, err := coll(nil).UpdateOne(filter, bson.M{ "$set": bson.M{ "h265Status": H265StatusQueued, "h265QueuedAt": now, "updatedAt": now, }, "$unset": bson.M{"h265PendingAt": ""}, }) if err != nil { log.Error("MarkH265Queued UpdateOne failed", log.Any("id", id), log.E(err)) return false, err } return result.ModifiedCount > 0, nil } // MarkH265Success 保存上游明确返回的可播放地址并将任务标记为成功。 // 老司机重新导入属于权威回填,不受旧云转码任务状态限制。 func MarkH265Success(id primitive.ObjectID, h265URL string) error { h265URL = strings.TrimSpace(h265URL) if h265URL == "" { return fmt.Errorf("empty h265 url") } now := time.Now() _, err := coll(nil).UpdateOne(bson.M{"_id": id}, bson.M{ "$set": bson.M{ "h265Url": h265URL, "h265Status": H265StatusSuccess, "h265FailCount": 0, "updatedAt": now, }, "$unset": bson.M{ "h265QueuedAt": "", "h265PendingAt": "", }, }) if err != nil { log.Error("MarkH265Success UpdateOne failed", log.Any("id", id), log.E(err)) } return err } // MarkPendingH265Success 仅完成仍对应同一源地址的 pending 任务。 // 如果视频源已变更,或其他流程已经回填 H.265,旧云任务结果必须被忽略。 func MarkPendingH265Success(id primitive.ObjectID, sourceURL string, pendingAt time.Time, h265URL string) (bool, error) { h265URL = strings.TrimSpace(h265URL) if strings.TrimSpace(sourceURL) == "" { return false, fmt.Errorf("empty source url") } if pendingAt.IsZero() { return false, fmt.Errorf("empty H265 pending time") } if h265URL == "" { return false, fmt.Errorf("empty h265 url") } now := time.Now() filter := pendingH265AttemptFilter(id, sourceURL, pendingAt) result, err := coll(nil).UpdateOne(filter, bson.M{ "$set": bson.M{ "h265Url": h265URL, "h265Status": H265StatusSuccess, "h265FailCount": 0, "updatedAt": now, }, "$unset": bson.M{ "h265QueuedAt": "", "h265PendingAt": "", }, }) if err != nil { log.Error("MarkPendingH265Success UpdateOne failed", log.Any("id", id), log.E(err)) return false, err } return result.ModifiedCount > 0, nil } // MarkH265Failed 记录当前 pending 尝试失败;达到 H265MaxFailCount 后将不再自动重试。 // 条件更新会跳过新尝试或已经成功回填的视频,避免旧轮询结果回退状态。 func MarkH265Failed(id primitive.ObjectID, sourceURL string, pendingAt time.Time) (bool, error) { now := time.Now() filter := pendingH265AttemptFilter(id, sourceURL, pendingAt) result, err := coll(nil).UpdateOne(filter, bson.M{ "$set": bson.M{ "h265Status": H265StatusFailed, "updatedAt": now, }, "$inc": bson.M{"h265FailCount": 1}, "$unset": bson.M{ "h265QueuedAt": "", "h265PendingAt": "", }, }) if err != nil { log.Error("MarkH265Failed UpdateOne failed", log.Any("id", id), log.E(err)) return false, err } return result.ModifiedCount > 0, nil } func pendingH265AttemptFilter(id primitive.ObjectID, sourceURL string, pendingAt time.Time) bson.M { return bson.M{ "_id": id, "sourceURL": sourceURL, "h265Status": H265StatusPending, "h265PendingAt": pendingAt, "$or": h265URLMissingFilter(), } }