@@ -0,0 +1,276 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user