using ClockSnowFlake; using dy.net.dto; using dy.net.extension; using dy.net.model; using SqlSugar; namespace dy.net.repository { public class DouyinFollowRepository : BaseRepository { // 注入SQLSugar客户端 public DouyinFollowRepository(ISqlSugarClient db) : base(db) { } public async Task> GetDouyinFollowGroup() { var data = this.Db.Queryable() .LeftJoin((f, u) => f.mySelfId == u.MyUserId) .Where((f, u) => u.Status == 1) // 注意:LeftJoin+u.Status==1 等价于 InnerJoin(u必须存在) .GroupBy((f, u) => f.mySelfId) // 按 mySelfId 分组 .Select((f, u) => new DouyinFollowGroupDto { Key = f.mySelfId, // 分组键(mySelfId) Name = u.UserName, // 取每组的 UserName(因分组唯一,用 First() 兼容 SQL) Total = SqlFunc.AggregateCount(f.Id) // 统计每组的记录数(用主表主键避免 null 影响) }) .ToList(); // 执行查询 return data; } /// /// 分页查询收藏视频 /// /// /// 分页结果(视频列表和总数) public async Task<(List list, int totalCount)> GetPagedAsync(FollowRequestDto dto) { var where = this.Db.Queryable() .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 BatchInsert(List followeds) { return await Db.Insertable(followeds).ExecuteCommandAsync() > 0; } public async Task BatchUpdate(List followeds) { return await Db.Updateable(followeds).ExecuteCommandAsync() > 0; } public async Task GetBySecUId(string secUid) { return await this.GetFirstAsync(x => x.SecUid == secUid); } public async Task GetBySecUId(string uperId,string myId) { return await this.GetFirstAsync(x => x.UperId == uperId && x.mySelfId == myId); } public async Task Update(DouyinFollowed followed) { return await this.UpdateAsync(followed); } public async Task Insert(DouyinFollowed followed) { return await this.InsertAsync(followed); } public async Task> GetSyncFollows(string userId) { return await this.Db.Queryable() .Where(x => x.OpenSync == true).Where(x=>x.mySelfId== userId) .ToListAsync(); } /// /// 同步关注列表(新增名字和签名变更检测) /// /// /// /// public async Task Sync(List followInfos, DouyinCookie ck) { // 基础参数校验 if (followInfos == null) followInfos = new List(); if (ck == null || string.IsNullOrWhiteSpace(ck.MyUserId)) { Serilog.Log.Error("同步关注列表失败:当前用户ID为空"); return false; } try { // 1. 提取当前批次的SecUid集合(去重) HashSet currentSecUids = followInfos.Select(x => x.SecUid).ToHashSet(); if (!currentSecUids.Any()) { Serilog.Log.Debug($"同步关注列表:当前批次无有效数据({ck.UserName}),直接返回成功"); return true; } // 2. 查询当前批次对应的现有记录(仅查需要对比的,减少数据量) List existFollows = await Db.Queryable() .Where(x => x.mySelfId == ck.MyUserId) .Where(x => !x.IsNoFollowed) .Where(x => currentSecUids.Contains(x.SecUid)) // 仅查当前批次的SecUid .ToListAsync() ?? new List(); // 3. 拆分:新增(当前批次有,数据库无) + 更新(当前批次有,数据库也有且字段变化) HashSet existSecUids = existFollows.Select(x => x.SecUid).ToHashSet(); var toAddFollows = followInfos.Where(x => !existSecUids.Contains(x.SecUid)).ToList(); var toUpdateFollows = new List(); // 3.1 筛选需要更新的记录 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); if (nameChanged || signatureChanged || uperAvatarChanged || enterpriseChanged) { toUpdateFollows.Add(new DouyinFollowed { Id = existFollow.Id, // 主键用于匹配 mySelfId = ck.MyUserId, SecUid = existFollow.SecUid, UperName = newFollow.NickName, Signature = newFollow.Signature, Enterprise = newFollow.EnterpriseVerifyReason, UperAvatar = newFollow.Avatar?.UrlList?.FirstOrDefault() ?? "", LastSyncTime = DateTime.UtcNow // 更新同步时间 }); } } // 4. 分批处理新增(单批200条) if (toAddFollows.Any()) { Func mapToDouyinFollowed = follow => new DouyinFollowed { Id = IdGener.GetLong().ToString(), Enterprise = follow.EnterpriseVerifyReason, LastSyncTime = DateTime.UtcNow, mySelfId = ck.MyUserId, 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语法) if (toUpdateFollows.Any()) { bool batchUpdateSuccess = await BatchProcessAsync(toUpdateFollows, 200, async batch => { int affectedRows = await Db.Updateable(batch) .UpdateColumns(x => new { x.UperName, x.Signature, x.LastSyncTime, x.Enterprise, x.UperAvatar }) .WhereColumns(x => x.Id) // 按主键匹配 .ExecuteCommandAsync(); return affectedRows >= 0; }); if (!batchUpdateSuccess) { Serilog.Log.Error("同步关注列表失败:关注信息更新异常"); return false; } } // 【重要】删除逻辑已移除:增量场景下不能通过批次对比删除,需单独设计取消关注逻辑 Serilog.Log.Debug($"dy_followed_users({ck.UserName})关注列表同步完成:新增{toAddFollows.Count}条,更新{toUpdateFollows.Count}条"); return true; } catch (Exception ex) { Serilog.Log.Error(ex, $"同步关注列表失败({ck.UserName}):{ex.Message}"); return false; } } //public async Task Sync(List followInfos, DouyinCookie ck) //{ // // 基础参数校验 // if (followInfos == null) followInfos = new List(); // if (ck==null || string.IsNullOrWhiteSpace(ck.MyUserId)) // { // Serilog.Log.Error("同步关注列表失败:当前用户ID为空"); // return false; // } // try // { // // 1. 查询现有关注列表 // List existFollows = await Db.Queryable() // .Where(x => x.mySelfId == ck.MyUserId) // .Where(x=>!x.IsNoFollowed) //排除手动添加但未关注的用户 // .ToListAsync() ?? new List(); // // 2. 提取现有和当前的SecUid集合(去重优化) // HashSet existSecUids = existFollows.Select(x => x.SecUid).ToHashSet(); // HashSet 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(); // 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 mapToDouyinFollowed = follow => new DouyinFollowed // { // Id = IdGener.GetLong().ToString(), // Enterprise = follow.EnterpriseVerifyReason, // LastSyncTime = DateTime.UtcNow, // mySelfId = ck.MyUserId, // 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; // } // } // // 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 == ck.MyUserId && secUids.Contains(x.SecUid)); // return true; // }); // if (!batchDeleteSuccess) // { // Serilog.Log.Error("同步关注列表失败:取消关注分批删除异常"); // return false; // } // } // Serilog.Log.Debug($"dy_followed_users({ck.UserName})关注列表同步完成:新增{toAddFollows.Count}条,更新{toUpdateFollows.Count}条,删除{toRemoveFollows.Count}条"); // return true; // } // catch (Exception ex) // { // Serilog.Log.Error(ex, $"同步关注列表失败({ck.UserName}):{ex.Message}"); // return false; // } //} /// /// 通用分批处理工具方法 /// /// 数据类型 /// 待处理数据 /// 单批大小 /// 单批处理逻辑(返回是否成功) /// 整体处理结果 private async Task BatchProcessAsync(List dataList, int batchSize, Func, Task> 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; } } }