Initial commit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 11:07:42 +08:00
co-authored by Claude Opus 5.5
commit 7974a42301
633 changed files with 120248 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
package videosync
import (
"time"
"videoweb/pub/models/videoinformationmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type MediaTaskSyncReq struct {
AppId int64 `bson:"appId" json:"appId"` // APPID
LastUpdateTime time.Time `bson:"lastUpdateTime" json:"lastUpdateTime"` // 最后更新时间
PageSize int64 `bson:"pageSize" json:"pageSize"`
}
type MediaTaskSyncResp struct {
Code int `json:"code,omitempty"` //
Data []MediaTaskInfo `json:"data,omitempty"` //
Msg string `json:"msg,omitempty"` //
}
// @table:media
type MediaTaskInfo struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"id"`
MediaSourceId primitive.ObjectID `bson:"mediaSourceId,omitempty" json:"mediaSourceId"` // 原始Id
VideoTime int32 `bson:"videoTime,omitempty" json:"videoTime"` // 影片长度
Size int64 `bson:"size,omitempty" json:"size"` // 文件size,单位byte
AppId int64 `bson:"appId,omitempty" json:"appId"` // 应用列表
CoverImg string `bson:"coverImg,omitempty" json:"coverImg"` // 封面
CoverImgNew string `bson:"coverImgNew,omitempty" json:"coverImgNew"` // 动态封面
UnencryptedImage string `bson:"unencryptedImage,omitempty" json:"unencryptedImage"` // 不加密封面
Title string `bson:"title,omitempty" json:"title"` // 标题
Publish string `bson:"publish,omitempty" json:"publish"` // 视频发布者
Remark string `bson:"remark,omitempty" json:"remark"` // 备注
Keyworld []string `bson:"keyworld,omitempty" json:"keyworld"` // 关键字
Nodes []videoinformationmod.Node `bson:"nodes" json:"nodes"` // 节点
FirstTag string `bson:"firstTag,omitempty" json:"firstTag"` // 一级标签
SecondTags []string `bson:"secondTags,omitempty" json:"secondTags"` // 二级标签标签
Status string `bson:"status,omitempty" json:"status"` // 当前状态
Desc string `bson:"desc,omitempty" json:"desc"` // 详细描述
TaskStartTime time.Time `bson:"taskStartTime,omitempty" json:"taskStartTime"` // 任务开始时间
SourceNameFs string `bson:"sourceNameFs,omitempty" json:"sourceNameFs"` // 远程源文件
IsDelLocalDir bool `bson:"isDelLocalDir,omitempty" json:"isDelLocalDir"` // 是否删除本机文件
WatermarkImage string `json:"watermarkImage" bson:"watermarkImage"` // 图片水印文件
WatermarkMedia string `json:"watermarkMedia" bson:"watermarkMedia"` // 视频水印文件
DownloadRetry int `bson:"downloadRetry,omitempty" json:"downloadRetry"` // 下载重试次数
WatermarkRetry int `bson:"watermarkRetry" json:"watermarkRetry"` // 水印重试次数
CompressRetry int `bson:"compressRetry" json:"compressRetry"` // 压缩重试次数
SegmentRetry int `bson:"segmentRetry" json:"segmentRetry"` // 切片重试次数
LinodeRetry int `bson:"linodeRetry" json:"linodeRetry"` // 上传重试次数
// 切片相关信息
Filename string `bson:"filename,omitempty" json:"filename"` // 文件名称
FieldNameFs string `bson:"fieldNameFs,omitempty" json:"fieldNameFs"` // 文件名称
TempFieldNameFs string `bson:"tempFieldNameFs,omitempty" json:"tempFieldNameFs"` // 临时存储
Ext string `bson:"ext,omitempty" json:"ext"` // 视频后缀
UnencryptedPreMp4 string `bson:"unencryptedPreMp4,omitempty" json:"unencryptedPreMp4"` // 不加密的预览MP4
TaskID string `bson:"taskId,omitempty" json:"taskId"` // 项目组传来的唯一标示
Width int64 `bson:"width,omitempty" json:"width"` // 分辨率宽
Height int64 `bson:"height,omitempty" json:"height"` // 分辨率高
Ratio float64 `bson:"ratio,omitempty" json:"ratio"` // 宽高比
FrameRate string `bson:"frameRate,omitempty" json:"frameRate"` // 视频贞数
SyBitRate string `bson:"syBitRate,omitempty" json:"syBitRate"` // 音频bit率
VidBitRate string `bson:"vidBitRate,omitempty" json:"vidBitRate"` // 视频bit率
CreatedAt time.Time `bson:"createdAt,omitempty" json:"createdAt"` // 创建时间
UpdatedAt time.Time `bson:"updatedAt,omitempty" json:"updatedAt"` // 更新时间
ProxyUpload *int32 `bson:"proxyUpload,omitempty" json:"proxyUpload"` // 代理上传
Retry int `bson:"retry" json:"retry"` // 重试次数 默认为零 最大重试3次
NotifySignal bool `bson:"notifySignal" json:"notifySignal"` //中止通知的信号 false 可以发送通知,true 中止发送通知
InfoTsName string `bson:"infoTsName" json:"infoTsName"` // 从该ts中获取视频分辨率等信息
SyncFlag int64 `bson:"syncFlag" json:"syncFlag"` // 同步状态
SrcKey string `bson:"srcKey,omitempty" json:"srcKey"` // 原始Key
UploadUserName string `bson:"uploadUserName" json:"uploadUserName,omitempty"` // 上传用户名
OptionAccount string `bson:"optionAccount,omitempty" json:"optionAccount"` // 最后操作人
}
+139
View File
@@ -0,0 +1,139 @@
package videosync
import (
"context"
"errors"
nethttp "net/http"
"sync"
"time"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/models/syncfilemod"
"videoweb/pub/pubdef"
"videoweb/pub/storage/proxy"
"videoweb/web/service/srv_videoinfo"
"go.mongodb.org/mongo-driver/bson"
)
/*
1.小说采集后上传媒资库
2.业务后台记录最新更新章节ID(数据ID自增)
3.查询对应小说ID是否存在,如果不存在需要先同步媒资库小说信息,再更新小说
注意:业务后台和媒资库 存储的小说ID和章节ID需一致, 资源以媒资库为准, 不单独更新业务后台小说
*/
var (
isRunning = false
lock sync.Mutex
)
func getIsRunning() bool {
lock.Lock()
defer lock.Unlock()
return isRunning
}
func setRunning(b bool) {
lock.Lock()
defer lock.Unlock()
isRunning = b
}
func VideoSync() {
isRunning = getIsRunning()
if isRunning {
return
}
setRunning(true)
ctx := context.Background()
log.Info("VideoSync begin")
pageSize := int64(200)
recordFilter := bson.M{
syncfilemod.FN.SyncRecord.Job: syncfilemod.VideoJob,
}
recordInfo, serr := syncfilemod.FindOneSyncRecord(ctx, recordFilter)
if serr != nil {
log.Error("VideoSync", log.Any("recordFilter", recordFilter), log.E(serr))
setRunning(false)
return
}
defer func() {
setRunning(false)
// 更新同步记录
set := bson.M{
"$set": bson.M{
syncfilemod.FN.SyncRecord.RecordTime: recordInfo.RecordTime,
syncfilemod.FN.SyncRecord.SuccessAt: time.Now(),
},
}
_ = syncfilemod.UpdateOneSyncRecord(ctx, recordFilter, set)
}()
// 循环同步
for {
// 查询接口
req := proxy.MediaTaskSyncReq{
AppId: pubdef.AppId,
LastUpdateTime: recordInfo.RecordTime,
PageSize: pageSize,
}
result, err := proxy.SyncMedia(&req, "")
if err != nil || result.Code != nethttp.StatusOK {
log.Error("VideoSync SyncMedia", log.Any("req", req), log.E(err))
return
}
// 处理
for _, media := range result.Data {
// 先推进水位,确保单条失败后下次不会卡在同一位置
if media.UpdatedAt.After(recordInfo.RecordTime) {
recordInfo.RecordTime = media.UpdatedAt
}
se := srv_videoinfo.SyncOneVideo(ctx, media)
if se != nil {
log.Error("VideoSync SyncOneVideo", log.Any("media", media), log.E(se))
continue
}
}
if len(result.Data) < int(pageSize) {
log.Info("VideoSync success", log.Any("RecordTime", recordInfo.RecordTime))
break
}
}
log.Info("VideoSync success", log.Any("RecordTime", recordInfo.RecordTime))
}
// SyncUnencryptedFields 全量补齐历史资源的 UnencryptedImage / UnencryptedPreMp4 字段。
// 从头分页拉取媒资库,仅在源端有值时写回,本地不存在或字段已有值不覆盖。
// 独立于增量同步的 SyncRecord,用于一次性修复;可安全重复执行。
func SyncUnencryptedFields() {
ctx := context.Background()
log.Info("SyncUnencryptedFields begin")
pageSize := int64(200)
lastUpdateTime := time.Time{}
for {
req := proxy.MediaTaskSyncReq{
AppId: pubdef.AppId,
LastUpdateTime: lastUpdateTime,
PageSize: pageSize,
}
result, err := proxy.SyncMedia(&req, "")
if err != nil || result.Code != nethttp.StatusOK {
log.Error("SyncUnencryptedFields SyncMedia", log.Any("req", req), log.E(err))
return
}
for _, media := range result.Data {
// 先推进时间戳,保证 continue 也不阻塞分页
if media.UpdatedAt.After(lastUpdateTime) {
lastUpdateTime = media.UpdatedAt
}
se := srv_videoinfo.SyncUnencryptedOne(ctx, media)
if se != nil && !errors.Is(se.Code, srvdef.ErrNoData) {
log.Warn("SyncUnencryptedFields SyncUnencryptedOne", log.Any("mediaId", media.Id.Hex()), log.E(se))
}
}
if len(result.Data) < int(pageSize) {
break
}
}
log.Info("SyncUnencryptedFields success", log.Any("lastUpdateTime", lastUpdateTime))
}