Files
dysync/service/DyCollectVideoService.cs
T
yuming b328e82eb2 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/
2026-04-25 00:22:33 +08:00

280 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using dy.net.dto;
using dy.net.model;
using dy.net.repository;
using dy.net.utils;
using System.ComponentModel;
using System.Threading.Tasks;
namespace dy.net.service
{
public class DyCollectVideoService
{
private readonly DyCollectVideoRepository _dyCollectVideoRepository;
public DyCollectVideoService(DyCollectVideoRepository dyCollectVideoRepository)
{
_dyCollectVideoRepository = dyCollectVideoRepository;
}
public async Task<bool> batchInsert(List<DyCollectVideo> videos)
{
// 边界处理:如果传入的列表为空,直接返回成功(或根据业务返回false)
if (videos == null || !videos.Any())
return true;
// 1. 提取待插入的所有AwemeId(去重,减少数据库查询压力)
var newAwemeIds = videos.Select(x => x.AwemeId)
.Distinct()
.ToList();
// 2. 查询数据库中已存在的AwemeId(只查需要的字段,提高效率)
var existingAwemeIds = await _dyCollectVideoRepository
.Query(x => newAwemeIds.Contains(x.AwemeId)) // 使用Query方法构建查询
.Select(x => x.AwemeId) // 只获取AwemeId,减少数据传输
.ToListAsync();
// 3. 过滤出数据库中不存在的视频(只保留新记录)
var videosToInsert = videos
.Where(video => !existingAwemeIds.Contains(video.AwemeId))
.ToList();
Serilog.Log.Debug($"batchInsert: 待插入{videos.Count}条,已存在{existingAwemeIds.Count}条,需要插入{videosToInsert.Count}条");
// 4. 如果没有需要插入的新记录,直接返回成功
if (!videosToInsert.Any())
{
Serilog.Log.Debug($"batchInsert: 所有视频都已存在,无需插入");
return true;
}
// 5. 批量插入过滤后的新记录
var insertedCount = await _dyCollectVideoRepository.InsertRangeAsync(videosToInsert);
Serilog.Log.Debug($"batchInsert: 实际插入{insertedCount}条记录");
// 返回是否插入成功(至少插入一条)
return insertedCount > 0;
}
public async Task<VideoStaticsDto> GetStatics()
{
List<DyCollectVideo> allList = await this._dyCollectVideoRepository.GetAllAsync();
if (!allList.Any())
return new VideoStaticsDto();
// 只统计文件实际存在的视频
var list = allList.Where(x =>
!string.IsNullOrWhiteSpace(x.VideoSavePath) &&
System.IO.File.Exists(x.VideoSavePath)
).ToList();
Serilog.Log.Debug($"统计视频:数据库记录 {allList.Count} 条,文件存在 {list.Count} 条");
if (!list.Any())
return new VideoStaticsDto();
// 按 viedoCate(Tag1/Tag2 组合)分组统计分类
var Categories = list
.GroupBy(x =>
{
// 使用与模型 ViedoCate 属性相同的逻辑:Tag1 + "/" + Tag2
var tag1 = string.IsNullOrWhiteSpace(x.Tag1) ? "" : x.Tag1;
var tag2 = string.IsNullOrWhiteSpace(x.Tag2) ? "" : x.Tag2;
var viedoCate = tag1 + "/" + tag2;
// 如果 Tag1 和 Tag2 都为空,标记为"未分类"
if (string.IsNullOrWhiteSpace(tag1) && string.IsNullOrWhiteSpace(tag2))
{
return "未分类";
}
return viedoCate;
})
.Select(x => new VideoStaticsItemDto
{
Name = x.Key,
Count = x.LongCount()
})
.OrderByDescending(p => p.Count)
.ToList();
var data = new VideoStaticsDto
{
AuthorCount = list.Select(x => x.AuthorId).Distinct().Count(),
CategoryCount = Categories.Count, // 使用实际分类数量
VideoCount = list.Count,
Categories = Categories,
FavoriteCount = list.Count(x => x.ViedoType == "1"),
CollectCount = list.Count(x => x.ViedoType == "2"),
FollowCount = list.Count(x => x.ViedoType == "3"),
VideoSizeTotal = ByteToGbConverter.ConvertBytesToGb(list.Sum(x => x.FileSize)),
VideoFavoriteSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == "1").Sum(x => x.FileSize)),
VideoCollectSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == "2").Sum(x => x.FileSize)),
VideoFollowSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == "3").Sum(x => x.FileSize)),
//TotalDiskSize= ByteToGbConverter.GetHostTotalDiskSpaceGB(),
};
data.Authors = list.GroupBy(x => x.Author).Select(x => new VideoStaticsItemDto { Name = x.Key, Count = x.LongCount(), Icon = x.FirstOrDefault().AuthorAvatarUrl }).OrderByDescending(d => d.Count).ToList();
return data;
}
//分页查询
public async Task<(List<DyCollectVideo> list, int totalCount)> GetPagedAsync(int pageIndex, int pageSize, string tag = null, List<string> tags = null, string author = null, string viedoType = null, List<string>? dates = null, bool? isDeleted = null, string sortField = null, string sortOrder = null)
{
return await _dyCollectVideoRepository.GetPagedAsync(pageIndex, pageSize, tag, tags, author, viedoType, dates, isDeleted, sortField, sortOrder);
}
/// <summary>
/// 获取所有视频类型列表(用于下拉框)
/// </summary>
/// <returns></returns>
public async Task<List<string>> GetAllCategoriesAsync()
{
try
{
// 获取所有视频记录
var allVideos = await _dyCollectVideoRepository.GetAllAsync();
Serilog.Log.Debug($"获取视频类型列表:共 {allVideos?.Count ?? 0} 条视频记录");
// 提取所有非空的 viedoCate(Tag1/Tag2 组合),去重并排序
// 使用与模型 ViedoCate 属性相同的逻辑:Tag1 + "/" + Tag2
var categories = allVideos
.Select(x =>
{
// 使用与模型 ViedoCate 属性相同的逻辑
var tag1 = string.IsNullOrWhiteSpace(x.Tag1) ? "" : x.Tag1;
var tag2 = string.IsNullOrWhiteSpace(x.Tag2) ? "" : x.Tag2;
var viedoCate = tag1 + "/" + tag2;
return viedoCate;
})
.Where(x => !string.IsNullOrWhiteSpace(x) && x != "/") // 过滤空值和只有 "/" 的情况
.Distinct()
.OrderBy(x => x)
.ToList();
// 检查是否存在空类型的视频(Tag1 和 Tag2 都为空)
var hasEmptyCategory = allVideos.Any(x => string.IsNullOrWhiteSpace(x.Tag1) && string.IsNullOrWhiteSpace(x.Tag2));
if (hasEmptyCategory)
{
// 在列表开头添加"未分类"选项
categories.Insert(0, "未分类");
}
Serilog.Log.Debug($"提取到的视频类型数量:{categories.Count},类型列表:{string.Join(", ", categories)}");
return categories;
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "获取视频类型列表失败");
return new List<string>();
}
}
/// <summary>
/// 根据ID获取视频信息
/// </summary>
/// <param name="id">视频ID</param>
/// <returns></returns>
public async Task<DyCollectVideo> GetByIdAsync(string id)
{
return await _dyCollectVideoRepository.GetByIdAsync(id);
}
/// <summary>
/// 清理数据库中文件不存在的视频记录
/// </summary>
/// <returns>清理的记录数量</returns>
public async Task<int> CleanupMissingFilesAsync()
{
try
{
var allVideos = await _dyCollectVideoRepository.GetAllAsync();
if (!allVideos.Any())
return 0;
// 找出需要清理的视频ID:
// 1. VideoSavePath 不为空但文件不存在的记录
// 2. VideoSavePath 为空或null的记录(这些记录没有视频文件,也应该被清理)
var missingFileIds = allVideos
.Where(x =>
// 情况1:有路径但文件不存在
(!string.IsNullOrWhiteSpace(x.VideoSavePath) && !System.IO.File.Exists(x.VideoSavePath)) ||
// 情况2:路径为空(这些记录没有视频文件,也应该清理)
string.IsNullOrWhiteSpace(x.VideoSavePath)
)
.Select(x => x.Id)
.ToList();
if (!missingFileIds.Any())
{
Serilog.Log.Debug("清理检查:所有视频文件都存在,无需清理");
return 0;
}
var pathEmptyCount = allVideos.Count(x => string.IsNullOrWhiteSpace(x.VideoSavePath));
var fileMissingCount = allVideos.Count(x => !string.IsNullOrWhiteSpace(x.VideoSavePath) && !System.IO.File.Exists(x.VideoSavePath));
Serilog.Log.Debug($"清理检查:发现 {missingFileIds.Count} 条需要清理的记录(路径为空:{pathEmptyCount} 条,文件不存在:{fileMissingCount} 条)");
// 批量删除
var deletedCount = await _dyCollectVideoRepository.DeleteByIdsAsync(missingFileIds);
Serilog.Log.Debug($"清理完成:删除了 {deletedCount} 条记录");
return deletedCount;
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "清理文件不存在的记录失败");
throw;
}
}
/// <summary>
/// 删除指定作者的所有视频数据
/// </summary>
/// <param name="authorName">作者名称</param>
/// <returns>删除的记录数量</returns>
public async Task<int> DeleteAuthorVideosAsync(string authorName)
{
try
{
var allVideos = await _dyCollectVideoRepository.GetAllAsync();
if (!allVideos.Any())
return 0;
// 找出该作者的所有视频ID
var authorVideoIds = allVideos
.Where(x => !string.IsNullOrWhiteSpace(x.Author) && x.Author.Equals(authorName, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Id)
.ToList();
if (!authorVideoIds.Any())
{
Serilog.Log.Debug($"删除作者视频:未找到作者 {authorName} 的视频记录");
return 0;
}
Serilog.Log.Debug($"删除作者视频:发现作者 {authorName} 的 {authorVideoIds.Count} 条视频记录");
// 批量删除
var deletedCount = await _dyCollectVideoRepository.DeleteByIdsAsync(authorVideoIds);
Serilog.Log.Debug($"删除完成:删除了 {deletedCount} 条记录");
return deletedCount;
}
catch (Exception ex)
{
Serilog.Log.Error(ex, $"删除作者 {authorName} 的视频失败");
throw;
}
}
}
}