Skip to content

Commit e6c9f3f

Browse files
committed
fix
1 parent b166e38 commit e6c9f3f

3 files changed

Lines changed: 39 additions & 6 deletions

File tree

server/internal/services/danmaku_burn.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os/exec"
99
"path/filepath"
1010
"strings"
11+
"sync"
1112
"time"
1213

1314
"github.com/gobup/server/internal/database"
@@ -25,6 +26,11 @@ const (
2526
DanmakuFontsDir = "/usr/share/fonts"
2627
)
2728

29+
// burningSourceParts 内存级防重入锁:防止同一源分P被多个 goroutine 并发烧录。
30+
// 键为源分P的数据库 ID(uint),值恒为 true。
31+
// 适用场景:Path A(上传完成实时触发)和 Path B/C(定时回补/启动恢复)同时调用 BurnDanmakuToVideo。
32+
var burningSourceParts sync.Map
33+
2834
// DanmakuBurnService 弹幕烧录服务
2935
type DanmakuBurnService struct{}
3036

@@ -35,6 +41,12 @@ func NewDanmakuBurnService() *DanmakuBurnService {
3541
// BurnDanmakuToVideo 将弹幕烧录到视频文件
3642
// 返回:生成的带弹幕视频路径,错误
3743
func (s *DanmakuBurnService) BurnDanmakuToVideo(part *models.RecordHistoryPart, history *models.RecordHistory, room *models.RecordRoom) (string, error) {
44+
// 内存级防重入:阻止同一源分P被并发烧录(解决 Path A 与 Path B/C 的 TOCTOU 竞争)
45+
if _, loaded := burningSourceParts.LoadOrStore(part.ID, true); loaded {
46+
return "", fmt.Errorf("[弹幕烧录] 源分P %d 正在烧录中,本次调用跳过(已有并发任务持有锁)", part.ID)
47+
}
48+
defer burningSourceParts.Delete(part.ID)
49+
3850
db := database.GetDB()
3951

4052
// 检查源视频文件是否存在

server/internal/upload/publish.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,18 @@ func (s *Service) PublishHistory(historyID uint, userID uint) error {
377377
// 注意:投稿后不修改UploadStatus,保持为2(已上传)
378378
db.Save(&history)
379379

380+
// 如果弹幕烧录功能已开启,标记此次一起投稿的弹幕版分P 为 appended_to_video=true。
381+
// 防止 AppendDanmakuBurnedPartsToApprovedVideos 定时任务在审核通过后再次追加已经包含在初始投稿里的弹幕版分P。
382+
if room.EnableDanmakuBurn {
383+
affected := db.Model(&models.RecordHistoryPart{}).Where(
384+
"history_id = ? AND is_temp_file = ? AND temp_file_type = ? AND upload = ?",
385+
historyID, true, "danmaku_burn", true,
386+
).Update("appended_to_video", true)
387+
if affected.RowsAffected > 0 {
388+
log.Printf("[投稿成功] 已标记 %d 个弹幕版分P 为 appended_to_video=true (history_id=%d)", affected.RowsAffected, historyID)
389+
}
390+
}
391+
380392
log.Printf("投稿成功: AV%d, BV%s", avID, bvid)
381393

382394
// 兜底检测机制:使用新的API验证投稿是否真的成功
@@ -723,6 +735,14 @@ func (s *Service) AppendPartsToExisting(newHistoryID uint, existingHistory *mode
723735
// UpdatePublishedVideoWithBurnedParts 回补更新已投稿视频,追加弹幕版分P
724736
// 当弹幕版分P上传完成后,检查对应的历史记录是否已投稿,如果已投稿且没有弹幕版,则追加弹幕版分P
725737
func (s *Service) UpdatePublishedVideoWithBurnedParts(burnedPartID uint) error {
738+
// 内存级防重入:防止 Path A(上传完成立即调用)与 Path B/C(定时任务发现 appended_to_video=false)并发调用
739+
// 导致同一弹幕版分P 在 B 站视频里出现两次
740+
if _, loaded := s.appendingBurnedParts.LoadOrStore(burnedPartID, true); loaded {
741+
log.Printf("[回补弹幕版] 烧录版分P %d 正在追加中,跳过并发调用", burnedPartID)
742+
return nil
743+
}
744+
defer s.appendingBurnedParts.Delete(burnedPartID)
745+
726746
db := database.GetDB()
727747

728748
// 获取弹幕版分P

server/internal/upload/service.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ const (
2121
)
2222

2323
type Service struct {
24-
uploadingParts sync.Map // partID -> true,防止同一分P并发上传
25-
publishingHistories sync.Map // historyID -> true,防止同一历史记录并发投稿
26-
wxPusher *services.WxPusherService
27-
templateSvc *services.TemplateService
28-
progressTracker *ProgressTracker
29-
queueManager *QueueManager
24+
uploadingParts sync.Map // partID -> true,防止同一分P并发上传
25+
publishingHistories sync.Map // historyID -> true,防止同一历史记录并发投稿
26+
appendingBurnedParts sync.Map // burnedPartID -> true,防止同一烧录版分P并发调用 EditVideo
27+
wxPusher *services.WxPusherService
28+
templateSvc *services.TemplateService
29+
progressTracker *ProgressTracker
30+
queueManager *QueueManager
3031
}
3132

3233
func NewService() *Service {

0 commit comments

Comments
 (0)