feat: 视频预览/数据看板/下载配置 三项业务功能

- 新增视频预览:VideoPlayer 组件、VideoController/DyCollectVideoService/Repository、VideoPageRequestDTO 对应后端改动
- 新增数据看板:workplace/statics.vue、RecordTable、相关 store/http/axios 改动
- 新增下载配置:DownloadConfigService、ConfigController、AppSet.vue、AppConfig
- 其他:DyHttpClientService / 同步 Job(Collect, Favorit) / Cookie 表 / CommonService / ServiceExtension 的配套调整
- 仓库卫生:.gitignore 追加 feiniu 过滤;运维脚本归档到 docs/scripts/
This commit is contained in:
yuming
2026-04-25 00:22:33 +08:00
parent ca530312d0
commit b328e82eb2
27 changed files with 3602 additions and 348 deletions
+77 -61
View File
@@ -79,6 +79,7 @@ namespace dy.net.job
string cursor = "0";
while (hasMore)
{
Serilog.Log.Debug($"favorite-正在获取第 {index + 1} 页数据,cursor: {cursor}, secUserId: {cookie.SecUserId}");
var data = await _douyinService.SyncFavoriteVideos(count,cursor, cookie.SecUserId, cookie.Cookies);
hasMore = data != null && data.HasMore == 1 && cookie.FavHasSyncd == 0;
if (cookie.FavHasSyncd == 1)
@@ -91,10 +92,14 @@ namespace dy.net.job
Serilog.Log.Debug($"favorite-Cookie[{cookie.UserName}]获取喜欢的数据失败,请检查一下Cookie和sec_user_id");
break;
}
Serilog.Log.Debug($"favorite-API返回: HasMore={data.HasMore}, AwemeList数量={data.AwemeList?.Count ?? 0}");
cursor = data != null && !string.IsNullOrWhiteSpace(data.MaxCursor) ? data.MaxCursor : "0";
if (data.AwemeList == null || !data.AwemeList.Any())
{
Serilog.Log.Debug($"favorite-第 {index + 1} 页数据为空,结束同步");
break;
}
@@ -122,7 +127,27 @@ namespace dy.net.job
var fileName = $"{item.AwemeId}.{v.Format}"; // 用ID做文件名,避免特殊字符
var savePath = Path.Combine(saveFolder, fileName);
if (!File.Exists(savePath))
// 检查是否应该下载(根据配置)
var videoCreateTime = DateTimeUtil.Convert10BitTimestamp(item.CreateTime);
var downloadConfigService = new DownloadConfigService(commonService);
var shouldDownload = downloadConfigService.ShouldDownload("1", item.Author?.Uid, item.Author?.Nickname, videoCreateTime);
// 获取作者头像信息(无论是否下载都需要)
var avatarImgName = $"{item.Author.Uid}.jpg";
var avatarSavePath = Path.Combine(cookie.FavSavePath, "author", avatarImgName);
var avatarUrl = item.Author?.AvatarLarger?.UrlList != null && item.Author.AvatarLarger.UrlList.Any()
? item.Author.AvatarLarger.UrlList[0] : null;
// 尝试使用AvatarThumb作为备用头像URL
if (string.IsNullOrWhiteSpace(avatarUrl))
{
avatarUrl = item.Author?.AvatarThumb?.UrlList != null && item.Author.AvatarThumb.UrlList.Any()
? item.Author.AvatarThumb.UrlList[0] : null;
}
// 下载视频和封面(如果配置允许)
string finalSavePath = null;
string finalCoverSavePath = null;
if (!File.Exists(savePath) && shouldDownload)
{
Serilog.Log.Debug($"favorite-视频[{SanitizePath(item.Desc)}]开始下载");
await Task.Delay(_random.Next(1, 4) * 1000);
@@ -130,86 +155,77 @@ namespace dy.net.job
if (downVideo)
{
Serilog.Log.Debug($"favorite-视频[{SanitizePath(item.Desc)}]下载{(downVideo ? "成功" : "失败")}");
}
else
{
Serilog.Log.Error($"favorite-视频[{SanitizePath(item.Desc)}]下载{(downVideo ? "成功" : "失败")}");
}
if (downVideo)
{
Serilog.Log.Debug($"favorite-视频[{SanitizePath(item.Desc)}]下载成功");
await DownVideoCover(item, saveFolder, cookie.Cookies);
// 用AuthorId做文件名,避免昵称特殊字符
var avatarImgName = $"{item.Author.Uid}.jpg";
var avatarSavePath = Path.Combine(cookie.FavSavePath, "author", avatarImgName);
await DownAuthorAvatar(cookie.FavSavePath, item, avatarSavePath, cookie.Cookies);
var avatarUrl = item.Author?.AvatarLarger?.UrlList != null && item.Author.AvatarLarger.UrlList.Any()
? item.Author.AvatarLarger.UrlList[0] : null;
// 尝试使用AvatarThumb作为备用头像URL
if (string.IsNullOrWhiteSpace(avatarUrl))
{
avatarUrl = item.Author?.AvatarThumb?.UrlList != null && item.Author.AvatarThumb.UrlList.Any()
? item.Author.AvatarThumb.UrlList[0] : null;
}
// 构造视频数据
DyCollectVideo video = new()
{
ViedoType="1",
AwemeId = item.AwemeId,
Author = item.Author?.Nickname,
AuthorId = item.Author?.Uid,
AuthorAvatar = avatarSavePath,
AuthorAvatarUrl = avatarUrl,
CreateTime = DateTimeUtil.Convert10BitTimestamp(item.CreateTime),
VideoTitle = item.Desc,
Id = IdGener.GetLong().ToString(),
Resolution = $"{v.PlayAddr.Width}×{v.PlayAddr.Height}",
FileSize = v.PlayAddr.DataSize,
FileHash = v.PlayAddr.FileHash,
Tag1 = tag1,
Tag2 = tag2,
Tag3 = tag3,
VideoUrl = videoUrl,
VideoCoverUrl = item.Video.Cover.UrlList != null && item.Video.Cover.UrlList.Any()
? item.Video.Cover.UrlList[0] : null,
VideoSavePath = savePath,
VideoCoverSavePath = Path.Combine(saveFolder, "poster.jpg"),
SyncTime = DateTime.Now,
DyUserId = data.Uid,
CookieId = cookie.Id
};
videos.Add(video);
finalSavePath = savePath;
finalCoverSavePath = Path.Combine(saveFolder, "poster.jpg");
var nfoPath = Path.Combine(saveFolder, $"{item.AwemeId}.nfo");
NfoFileGenerator.GenerateNfoFile(new VideoNfo
{
Author = video.Author,
Author = item.Author?.Nickname,
Poster = Path.Combine(saveFolder, "poster.jpg"),
Title = video.VideoTitle,
Thumbnail= Path.Combine(saveFolder, "fanart.jpg"),
ReleaseDate = video.CreateTime,
Title = item.Desc,
Thumbnail = Path.Combine(saveFolder, "fanart.jpg"),
ReleaseDate = videoCreateTime,
Genres = new List<string> { tag1, tag2, tag3 }.Where(t => !string.IsNullOrWhiteSpace(t)).ToList()
}, nfoPath);
}
else
{
Serilog.Log.Error($"favorite-视频[{SanitizePath(item.Desc)}]下载失败");
}
}
else
else if (!shouldDownload)
{
//Serilog.Log.Debug($"视频[{item.AwemeId}]已存在,跳过下载");
Serilog.Log.Debug($"favorite-视频[{SanitizePath(item.Desc)}]不符合下载配置,仅同步信息到数据库");
}
else if (File.Exists(savePath))
{
// 文件已存在,使用现有路径
finalSavePath = savePath;
finalCoverSavePath = Path.Combine(saveFolder, "poster.jpg");
}
// 无论是否下载,都保存视频信息到数据库
DyCollectVideo video = new()
{
ViedoType = "1",
AwemeId = item.AwemeId,
Author = item.Author?.Nickname,
AuthorId = item.Author?.Uid,
AuthorAvatar = avatarSavePath,
AuthorAvatarUrl = avatarUrl,
CreateTime = videoCreateTime,
VideoTitle = item.Desc,
Id = IdGener.GetLong().ToString(),
Resolution = $"{v.PlayAddr.Width}×{v.PlayAddr.Height}",
FileSize = v.PlayAddr.DataSize,
FileHash = v.PlayAddr.FileHash,
Tag1 = tag1,
Tag2 = tag2,
Tag3 = tag3,
VideoUrl = videoUrl,
VideoCoverUrl = item.Video.Cover.UrlList != null && item.Video.Cover.UrlList.Any()
? item.Video.Cover.UrlList[0] : null,
VideoSavePath = finalSavePath, // 如果未下载则为 null
VideoCoverSavePath = finalCoverSavePath, // 如果未下载则为 null
SyncTime = DateTime.Now,
DyUserId = data.Uid,
CookieId = cookie.Id
};
videos.Add(video);
}
index++;
// 批量保存到数据库(减少数据库操作频率)
if (videos.Any())
{
//Serilog.Log.Debug($"处理Cookie[{cookie.UserName}]的第{index}页数据,共{videos.Count}条");
Serilog.Log.Debug($"favorite-处理Cookie[{cookie.UserName}]的第{index}页数据,共{videos.Count}条,准备保存到数据库");
try
{
await _douyinVideoService.batchInsert(videos);
var insertResult = await _douyinVideoService.batchInsert(videos);
Serilog.Log.Debug($"favorite-batchInsert返回结果: {insertResult}, 本次处理{videos.Count}条视频");
syncCount += videos.Count;
}
catch (Exception ex)