Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

277 lines
8.2 KiB
Go

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(),
}
}