b328e82eb2
- 新增视频预览: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/
280 lines
12 KiB
C#
280 lines
12 KiB
C#
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;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|