1、去重

2、分享
3、视频播放
4、批量删除,重新下载
5、自定义标题(仅博主视频)
6、授权页面去掉博主添加,新增关注列表
7、图文视频合成优化
8、其他优化
This commit is contained in:
jianzhichu
2025-11-30 00:42:25 +08:00
parent 63b51d211e
commit e6e9ff3cc3
94 changed files with 8155 additions and 1544 deletions
+15
View File
@@ -98,6 +98,21 @@ namespace dy.net.repository
{
return Db.Deleteable<T>().In(id).ExecuteCommand() > 0;
}
/// <summary>
/// 事务执行
/// </summary>
/// <param name="action"></param>
/// <param name="errorCallBack"></param>
/// <returns></returns>
public async Task<bool> UseTranAsync(Func<Task> action, Action<Exception> errorCallBack)
{
var res = Db.Ado.UseTranAsync(async () =>
{
await action();
}, errorCallBack: errorCallBack);
return res.IsCompletedSuccessfully;
}
/// <summary>
/// 根据主键删除(异步)
+40
View File
@@ -0,0 +1,40 @@
using dy.net.model;
using SqlSugar;
using System.Linq.Expressions;
namespace dy.net.repository
{
public class DouyinCookieRepository : BaseRepository<DouyinCookie>
{
// 注入SQLSugar客户端
public DouyinCookieRepository(ISqlSugarClient db) : base(db)
{
}
public async Task<List<DouyinCookie>> GetAllCookies(Expression<Func<DouyinCookie, bool>> whereExpression = null)
{
// 1. 初始化查询:先加固定条件 Status == 1
var query = Db.Queryable<DouyinCookie>()
.Where(x => x.Status == 1); // 固定条件(必选)
// 2. 若传入自定义条件,叠加 Where(自动 AND 组合)
if (whereExpression != null)
{
query = query.Where(whereExpression); // 自定义条件(可选)
}
// 3. 执行查询(SqlSugar 自动合并所有 Where 条件)
return await query.ToListAsync();
}
public async Task<(List<DouyinCookie> list, int totalCount)> GetPagedAsync(int pageIndex, int pageSize)
{
var where = this.Db.Queryable<DouyinCookie>();
var totalCount = await where.CountAsync();
var list = await where.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return (list, totalCount);
}
}
}
+252
View File
@@ -0,0 +1,252 @@
using ClockSnowFlake;
using dy.net.dto;
using dy.net.extension;
using dy.net.model;
using SqlSugar;
namespace dy.net.repository
{
public class DouyinFollowRepository : BaseRepository<DouyinFollowed>
{
// 注入SQLSugar客户端
public DouyinFollowRepository(ISqlSugarClient db) : base(db)
{
}
/// <summary>
/// 分页查询收藏视频
/// </summary>
/// <param name="dto"></param>
/// <returns>分页结果(视频列表和总数)</returns>
public async Task<(List<DouyinFollowed> list, int totalCount)> GetPagedAsync(FollowRequestDto dto)
{
var where = this.Db.Queryable<DouyinFollowed>()
.Where(x=>x.mySelfId==dto.MySelfId)
.WhereIF(!string.IsNullOrWhiteSpace(dto.FollowUserName), x => x.UperName.Contains(dto.FollowUserName));
var totalCount = await where.CountAsync();
var list = await where.OrderByDescending(x=>x.OpenSync).OrderByDescending(x => x.LastSyncTime).Skip((dto.PageIndex - 1) * dto.PageSize).Take(dto.PageSize).ToListAsync();
return (list, totalCount);
}
public async Task<bool> BatchInsert(List<DouyinFollowed> followeds)
{
return await Db.Insertable(followeds).ExecuteCommandAsync() > 0;
}
public async Task<bool> BatchUpdate(List<DouyinFollowed> followeds)
{
return await Db.Updateable(followeds).ExecuteCommandAsync() > 0;
}
public async Task<DouyinFollowed> GetBySecUId(string secUid)
{
return await this.GetFirstAsync(x => x.SecUid == secUid);
}
public async Task<DouyinFollowed> GetBySecUId(string uperId,string myId)
{
return await this.GetFirstAsync(x => x.UperId == uperId && x.mySelfId == myId);
}
public async Task<bool> Update(DouyinFollowed followed)
{
return await this.UpdateAsync(followed);
}
public async Task<bool> Insert(DouyinFollowed followed)
{
return await this.InsertAsync(followed);
}
public async Task<List<DouyinFollowed>> GetSyncFollows(string userId)
{
return await this.Db.Queryable<DouyinFollowed>()
.Where(x => x.OpenSync == true).Where(x=>x.mySelfId== userId)
.ToListAsync();
}
/// <summary>
/// 同步关注列表(新增名字和签名变更检测)
/// </summary>
/// <param name="followInfos"></param>
/// <param name="myselfUserId"></param>
/// <returns></returns>
public async Task<bool> Sync(List<FollowingsItem> followInfos, string myselfUserId)
{
// 基础参数校验
if (followInfos == null) followInfos = new List<FollowingsItem>();
if (string.IsNullOrWhiteSpace(myselfUserId))
{
Serilog.Log.Error("同步关注列表失败:当前用户ID为空");
return false;
}
try
{
// 1. 查询现有关注列表
List<DouyinFollowed> existFollows = await Db.Queryable<DouyinFollowed>()
.Where(x => x.mySelfId == myselfUserId)
.ToListAsync() ?? new List<DouyinFollowed>();
// 2. 提取现有和当前的SecUid集合(去重优化)
HashSet<string> existSecUids = existFollows.Select(x => x.SecUid).ToHashSet();
HashSet<string> currentSecUids = followInfos.Select(x => x.SecUid).ToHashSet();
// 3. 计算新增、待删除和需要更新的记录
var toAddFollows = followInfos.Where(x => !existSecUids.Contains(x.SecUid)).ToList();
var toRemoveFollows = existFollows.Where(x => !currentSecUids.Contains(x.SecUid)).ToList();
// 3.1 筛选需要更新的记录(SecUid存在但名字或签名有变更)
var toUpdateFollows = new List<DouyinFollowed>();
foreach (var existFollow in existFollows)
{
var newFollow = followInfos.FirstOrDefault(x => x.SecUid == existFollow.SecUid);
if (newFollow == null) continue;
// 检查名字或签名是否变更(精确匹配,区分大小写和空格)
bool nameChanged = !string.Equals(existFollow.UperName, newFollow.NickName, StringComparison.Ordinal);
bool signatureChanged = !string.Equals(existFollow.Signature, newFollow.Signature, StringComparison.Ordinal);
bool enterpriseChanged = !string.Equals(existFollow.Enterprise, newFollow.EnterpriseVerifyReason, StringComparison.Ordinal);
bool uperAvatarChanged = !string.Equals(existFollow.UperAvatar, newFollow.Avatar.UrlList?.FirstOrDefault()??"", StringComparison.Ordinal);
//bool uperIdChanged = !string.Equals(existFollow.UperId, newFollow.UperId, StringComparison.Ordinal);
if (nameChanged || signatureChanged|| uperAvatarChanged|| enterpriseChanged)
{
// 构造更新实体(仅赋值变更字段和必要字段)
var updateEntity = new DouyinFollowed
{
Id = existFollow.Id, // 主键必须保留,用于匹配
mySelfId = existFollow.mySelfId,
SecUid = existFollow.SecUid,
UperName = newFollow.NickName, // 新名字
Signature = newFollow.Signature, // 新签名
UperId = newFollow.UperId, // 新的UperId
UperAvatar = newFollow.Avatar?.UrlList?.FirstOrDefault() ?? "", // 新头像
Enterprise = newFollow.EnterpriseVerifyReason, // 新企业认证
LastSyncTime = DateTime.UtcNow // 更新同步时间
// 其他字段(如Enterprise、UperAvatar、OpenSync)保留原有值,无需赋值
};
toUpdateFollows.Add(updateEntity);
}
}
// 4. 分批处理新增(单批200条)
if (toAddFollows.Any())
{
Func<FollowingsItem, DouyinFollowed> mapToDouyinFollowed = follow => new DouyinFollowed
{
Id = IdGener.GetLong().ToString(),
Enterprise = follow.EnterpriseVerifyReason,
LastSyncTime = DateTime.UtcNow,
mySelfId = myselfUserId,
SecUid = follow.SecUid,
OpenSync = false,
UperAvatar = follow.Avatar?.UrlList?.FirstOrDefault() ?? "",
UperName = follow.NickName,
Signature = follow.Signature,
UperId = follow.UperId
};
bool batchAddSuccess = await BatchProcessAsync(toAddFollows, 200,
async batch => await BatchInsert(batch.Select(mapToDouyinFollowed).ToList()));
if (!batchAddSuccess)
{
Serilog.Log.Error("同步关注列表失败:新增关注分批插入异常");
return false;
}
}
// 5. 分批处理更新(适配 SQLSugar 语法:UpdateColumns + WhereColumns
if (toUpdateFollows.Any())
{
bool batchUpdateSuccess = await BatchProcessAsync(toUpdateFollows, 200,
async batch =>
{
// SQLSugar 正确用法:实体集合更新 + UpdateColumns(指定更新字段) + WhereColumns(指定匹配字段/主键)
int affectedRows = await Db.Updateable(batch) // 传入更新实体集合
.UpdateColumns(x => new { x.UperName, x.Signature, x.LastSyncTime,x.Enterprise,x.UperAvatar}) // 仅更新这3个字段
.WhereColumns(x => x.Id) // 按主键Id匹配现有记录
.ExecuteCommandAsync();
// 受影响行数 > 0 或 批次无数据(正常),返回true;否则返回false
return affectedRows >= 0;
});
if (!batchUpdateSuccess)
{
Serilog.Log.Error("同步关注列表失败:关注信息更新异常");
return false;
}
Serilog.Log.Debug($"同步关注列表:成功更新{toUpdateFollows.Count}条关注信息(用户ID{myselfUserId}");
}
// 6. 分批处理删除(单批200条)
if (toRemoveFollows.Any())
{
bool batchDeleteSuccess = await BatchProcessAsync(toRemoveFollows, 200,
async batch =>
{
var secUids = batch.Select(x => x.SecUid).ToList();
await DeleteAsync(x => x.mySelfId == myselfUserId && secUids.Contains(x.SecUid));
return true;
});
if (!batchDeleteSuccess)
{
Serilog.Log.Error("同步关注列表失败:取消关注分批删除异常");
return false;
}
}
Serilog.Log.Debug($"同步关注列表完成(用户ID{myselfUserId}):新增{toAddFollows.Count}条,更新{toUpdateFollows.Count}条,删除{toRemoveFollows.Count}条");
return true;
}
catch (Exception ex)
{
Serilog.Log.Error(ex, $"同步关注列表失败(用户ID{myselfUserId}):{ex.Message}");
return false;
}
}
/// <summary>
/// 通用分批处理工具方法
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="dataList">待处理数据</param>
/// <param name="batchSize">单批大小</param>
/// <param name="processAction">单批处理逻辑(返回是否成功)</param>
/// <returns>整体处理结果</returns>
private async Task<bool> BatchProcessAsync<T>(List<T> dataList, int batchSize, Func<List<T>, Task<bool>> processAction)
{
if (dataList == null || !dataList.Any() || batchSize <= 0)
return true;
int totalCount = dataList.Count;
int batchCount = (int)Math.Ceiling((double)totalCount / batchSize);
for (int i = 0; i < batchCount; i++)
{
var batch = dataList.Skip(i * batchSize).Take(batchSize).ToList();
if (!batch.Any()) continue;
bool success = await processAction(batch);
if (!success)
{
Serilog.Log.Debug($"分批处理失败:第{i + 1}批(数据范围:{i * batchSize}-{Math.Min((i + 1) * batchSize - 1, totalCount - 1)}");
return false;
}
}
return true;
}
}
}
-29
View File
@@ -1,29 +0,0 @@
using dy.net.model;
using SqlSugar;
namespace dy.net.repository
{
public class DouyinUserCookieRepository : BaseRepository<DouyinUserCookie>
{
// 注入SQLSugar客户端
public DouyinUserCookieRepository(ISqlSugarClient db) : base(db)
{
}
public async Task<List<DouyinUserCookie>> GetAllCookies()
{
return await this.GetListAsync(x=>x.Status==1);
}
public async Task<(List<DouyinUserCookie> list, int totalCount)> GetPagedAsync(int pageIndex, int pageSize)
{
var where = this.Db.Queryable<DouyinUserCookie>();
var totalCount = await where.CountAsync();
var list = await where.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return (list, totalCount);
}
}
}
+127 -35
View File
@@ -15,50 +15,48 @@ namespace dy.net.repository
}
/// <summary>
/// 分页查询收藏视频
///
/// </summary>
/// <param name="pageIndex">页码(从1开始)</param>
/// <param name="pageSize">每页数量</param>
/// <param name="tag">可选标签过滤</param>
/// <param name="author">可选作者过滤</param>
/// <returns>分页结果(视频列表和总数)</returns>
public async Task<(List<DouyinVideo> list, int totalCount)> GetPagedAsync(int pageIndex, int pageSize, string tag = null, string author = null,string viedoType=null, List<string> dates = null)
/// <param name="dto"></param>
/// <returns></returns>
public async Task<(List<DouyinVideo> list, int totalCount)> GetPagedAsync(DouyinVideoPageRequestDto dto)
{
DateTime? start, end;
GetDateBetween(dto.Dates, out start, out end);
DateTime? start = null;
DateTime? end =null;
if(dates!=null && dates.Count==2)
DateTime? start2, end2;
GetDateBetween(dto.Dates2, out start2, out end2);
VideoTypeEnum? enumviedoType = null;
if (!string.IsNullOrEmpty(dto.ViedoType) && dto.ViedoType != "*")
{
start = Convert.ToDateTime(dates[0]);
end = Convert.ToDateTime(dates[1]);
enumviedoType = dto.ViedoType.ToVideoTypeEnum();
}
else if(dates!=null && dates.Count==1)
{
start = Convert.ToDateTime(dates[0]);
}
VideoTypeEnum? enumviedoType = null;
if (!string.IsNullOrEmpty(viedoType)&&viedoType!="*")
{
enumviedoType = viedoType.ToVideoTypeEnum();
}
var where = this.Db.Queryable<DouyinVideo>()
.WhereIF(!string.IsNullOrWhiteSpace(tag), x => x.Tag1 == tag)
.WhereIF(!string.IsNullOrWhiteSpace(author), x => x.Author == author)
//.WhereIF(!string.IsNullOrWhiteSpace(title), x => x.VideoTitle.Contains(title))
.WhereIF(!string.IsNullOrWhiteSpace(dto.Title), x => x.VideoTitle.Contains(dto.Title))
.WhereIF(!string.IsNullOrWhiteSpace(dto.Author), x => x.Author == dto.Author)
.WhereIF(start.HasValue, x => x.SyncTime >= start.Value)
.WhereIF(end.HasValue, x => x.SyncTime <= end.Value)
.WhereIF(start2.HasValue, x => x.CreateTime >= start2.Value)
.WhereIF(end2.HasValue, x => x.CreateTime <= end2.Value)
.WhereIF(enumviedoType.HasValue, x => x.ViedoType == enumviedoType);
var totalCount = await where.CountAsync();
var list = await where.OrderByDescending(x=>x.SyncTime).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
if (list.Any()) {
var users= await this.Db.Queryable<DouyinUserCookie>().ToListAsync();
var list = await where.OrderByDescending(x => x.SyncTime).Skip((dto.PageIndex - 1) * dto.PageSize).Take(dto.PageSize).ToListAsync();
if (list.Any())
{
var users = await this.Db.Queryable<DouyinCookie>().ToListAsync();
foreach (var item in list)
{
var user= users.FirstOrDefault(x=>x.Id== item.CookieId);
if(user!=null)
var user = users.FirstOrDefault(x => x.Id == item.CookieId);
if (user != null)
{
item.DyUser = user.UserName;
}
@@ -67,19 +65,34 @@ namespace dy.net.repository
return (list, totalCount);
}
private static void GetDateBetween(List<string> dates, out DateTime? start, out DateTime? end)
{
start = null;
end = null;
if (dates != null && dates.Count == 2)
{
start = Convert.ToDateTime(dates[0]);
end = Convert.ToDateTime(dates[1]);
}
else if (dates != null && dates.Count == 1)
{
start = Convert.ToDateTime(dates[0]);
}
}
/// <summary>
///
/// 对于重复标题进行处理
/// </summary>
/// <param name="AuthorId"></param>
/// <param name="ViedoNameSimplify"></param>
/// <returns></returns>
public async Task<(string, string)> GetUperLastViedoFileName(string AuthorId,string ViedoNameSimplify)
public async Task<(string, string)> GetUperLastViedoFileName(string AuthorId, string ViedoNameSimplify)
{
var video= await this.Db.Queryable<DouyinVideo>().Where(x => x.AuthorId == AuthorId && x.ViedoType == VideoTypeEnum.UperPost)
.Where(x => x.VideoTitleSimplify == ViedoNameSimplify)
.OrderByDescending(x => x.CreateTime).FirstAsync();
var video = await this.Db.Queryable<DouyinVideo>().Where(x => x.AuthorId == AuthorId && x.ViedoType == VideoTypeEnum.UperPost)
.Where(x => x.VideoTitleSimplify == ViedoNameSimplify)
.OrderByDescending(x => x.CreateTime).FirstAsync();
if (video != null)
{
@@ -107,9 +120,88 @@ namespace dy.net.repository
{
return (ViedoNameSimplify, "");
}
}
/// <summary>
/// 根据视频ID列表获取视频信息
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task<List<DouyinVideo>> GetByIds(List<string> ids)
{
// 使用 Queryable 方法构建查询
return await this.Db.Queryable<DouyinVideo>()
.Where(x => ids.Contains(x.Id))
.ToListAsync();
}
/// <summary>
/// 记录要重新下载的视频
/// </summary>
/// <param name="downs"></param>
/// <returns></returns>
public bool InsertReDowns(List<ViedoReDown> downs)
{
if (downs != null)
{
return Db.Insertable(downs).ExecuteCommand() > 0;
}
else
{
return false;
}
}
/// <summary>
/// 更新视频重新下载状态
/// </summary>
/// <param name="videoId">视频ID(对应ViedoReDown.Id</param>
/// <param name="status">状态值(1时会同步更新下载时间)</param>
/// <returns>是否更新成功(影响行数>0</returns>
public async Task<bool> UpdateReDownStatus(string videoId, int status)
{
// 校验必填参数(避免无效数据库操作)
if (string.IsNullOrWhiteSpace(videoId))
{
Serilog.Log.Error("更新重新下载状态失败:videoId为空");
return false;
}
// 构建更新条件(统一Where条件,避免重复代码)
var updateable = Db.Updateable<ViedoReDown>()
.Where(it => it.Id == videoId)
.SetColumns(it => new ViedoReDown
{
Status = status,
UpdateTime = DateTime.Now
});
if (status == 1)
{
updateable = updateable.SetColumns(it => it.DownTime == DateTime.Now);
}
int affectedRows = await updateable.ExecuteCommandAsync();
// 可选:记录更新结果日志
if (affectedRows <= 0)
{
Serilog.Log.Debug("更新重新下载状态无匹配数据:videoId={0}, status={1}", videoId, status);
}
return affectedRows > 0;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public async Task<List<ViedoReDown>> GetViedoReDowns()
{
return await this.Db.Queryable<ViedoReDown>()
.Where(x => x.Status == 0 || x.Status == 2)
.ToListAsync();
}
}
}