[AI] fix: 剧集去重键补上 videoUrl

媒资库会给同一集重新发 hashId(「JK学姐不为人知的秘密」源库那条
hashId 6aa23652…、本地那条 6acaf74c…,videoUrl 一模一样),
只比 hashId 会漏掉这一类重复,前端仍显示「1 1 2 2」。
改成 mediaId 下 hashId 或 videoUrl 任一相同即认定是同一集。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-29 20:24:38 +08:00
co-authored by Claude Opus 5
parent 0fbd63ba10
commit ed51664a09
+42 -24
View File
@@ -261,8 +261,8 @@ func runPass(ctx context.Context, srcDB *mongo.Database, t target, spec CollSpec
return out, c.Err() return out, c.Err()
} }
// localTwins 查出这批源文档在目标库的「同一集另一条」: // localTwins 查出这批源文档在目标库的「同一集另一条」:同属一个 mediaId,
// (mediaId, hashId) 相同但 _id 不同。 // 且 hashId 或 videoUrl 相同,但 _id 不同。
// //
// 各站点自己也在跑媒资库同步(common/media/syncMedia.go),它按 // 各站点自己也在跑媒资库同步(common/media/syncMedia.go),它按
// (mediaId, hashId) 去重、用自己的 _id 为同一集插一条;本同步再把源库那条 // (mediaId, hashId) 去重、用自己的 _id 为同一集插一条;本同步再把源库那条
@@ -270,29 +270,40 @@ func runPass(ctx context.Context, srcDB *mongo.Database, t target, spec CollSpec
// 更要紧的是媒资库那条在 SaleCoin=0 时 listenPermission=2(免费), // 更要紧的是媒资库那条在 SaleCoin=0 时 listenPermission=2(免费),
// 等于用户可以白看本该收费的集。 // 等于用户可以白看本该收费的集。
// //
// 必须同时认 videoUrl:媒资库会给同一集重新发 hashId(实测
// 「JK学姐不为人知的秘密」源库那条 hashId 6aa23652…、本地那条
// 6acaf74c…,videoUrl 却一模一样),只比 hashId 会漏掉这一类。
//
// 以源库那条为准(购买记录都挂在它上面),把本地那条下架 isActive:false, // 以源库那条为准(购买记录都挂在它上面),把本地那条下架 isActive:false,
// 不删除,便于事后回查。 // 不删除,便于事后回查。
localTwins := func(docs []bson.M) (map[any]any, error) { localTwins := func(docs []bson.M) (map[any]any, error) {
hashes := make([]any, 0, len(docs)) keys := make([]any, 0, len(docs)*2) // 待查的 hashId 与 videoUrl
want := map[string]any{} // mediaId|hashId -> 源库 _id want := map[string]any{} // mediaId|key -> 源库 _id
for _, d := range docs { add := func(mid primitive.ObjectID, v string, id any) {
h, ok := d["hashId"].(string) if v == "" {
if !ok || h == "" { return
continue
} }
keys = append(keys, v)
want[mid.Hex()+"|"+v] = id
}
for _, d := range docs {
mid, ok := d["mediaId"].(primitive.ObjectID) mid, ok := d["mediaId"].(primitive.ObjectID)
if !ok { if !ok {
continue continue
} }
hashes = append(hashes, h) h, _ := d["hashId"].(string)
want[mid.Hex()+"|"+h] = d["_id"] u, _ := d["videoUrl"].(string)
add(mid, h, d["_id"])
add(mid, u, d["_id"])
} }
if len(hashes) == 0 { if len(keys) == 0 {
return nil, nil return nil, nil
} }
// hashId 上有索引,按它捞再在内存里配 mediaId // hashId 上有索引;videoUrl 没有,所以放在同一个 $or 里一次查完
c, e := dst.Find(ctx, bson.M{"hashId": bson.M{"$in": hashes}, "isActive": true}, c, e := dst.Find(ctx, bson.M{"isActive": true, "$or": []bson.M{
options.Find().SetProjection(bson.M{"_id": 1, "mediaId": 1, "hashId": 1})) {"hashId": bson.M{"$in": keys}},
{"videoUrl": bson.M{"$in": keys}},
}}, options.Find().SetProjection(bson.M{"_id": 1, "mediaId": 1, "hashId": 1, "videoUrl": 1}))
if e != nil { if e != nil {
return nil, e return nil, e
} }
@@ -300,21 +311,28 @@ func runPass(ctx context.Context, srcDB *mongo.Database, t target, spec CollSpec
out := map[any]any{} out := map[any]any{}
for c.Next(ctx) { for c.Next(ctx) {
var row struct { var row struct {
ID primitive.ObjectID `bson:"_id"` ID primitive.ObjectID `bson:"_id"`
MediaID primitive.ObjectID `bson:"mediaId"` MediaID primitive.ObjectID `bson:"mediaId"`
HashID string `bson:"hashId"` HashID string `bson:"hashId"`
VideoURL string `bson:"videoUrl"`
} }
if e := c.Decode(&row); e != nil { if e := c.Decode(&row); e != nil {
return nil, e return nil, e
} }
srcID, ok := want[row.MediaID.Hex()+"|"+row.HashID] for _, k := range []string{row.HashID, row.VideoURL} {
if !ok { if k == "" {
continue continue
}
srcID, ok := want[row.MediaID.Hex()+"|"+k]
if !ok {
continue
}
if sid, ok := srcID.(primitive.ObjectID); ok && sid == row.ID {
continue // 就是源库那条自己,不是重复
}
out[srcID] = row.ID
break
} }
if sid, ok := srcID.(primitive.ObjectID); ok && sid == row.ID {
continue // 就是源库那条自己,不是重复
}
out[srcID] = row.ID
} }
return out, c.Err() return out, c.Err()
} }