/// <summary> /// 获取指定目标获取的认可数 /// </summary> /// <param name="targetId">目标 ID</param> /// <param name="targetType">目标类型</param> /// <exception cref="ArgumentNullException"><paramref name="targetId"/> 为 null</exception> /// <returns>指定目标获取的认可数</returns> public async Task <int> GetTargetLikeCountAsync([NotNull] string targetId, LikeTargetType targetType) { if (targetId == null) { throw new ArgumentNullException(nameof(targetId)); } var cacheKey = TargetLikeCountCacheKey(targetId, targetType); var redisDb = _redis.GetDatabase(); var cachedResult = await redisDb.StringGetAsync(cacheKey); if (cachedResult.HasValue) { await redisDb.KeyExpireAsync(cacheKey, CachedDataProvider.DefaultTtl); return((int)cachedResult); } var likeCount = await _dbContext.Likes.CountAsync(l => l.TargetId == targetId && l.TargetType == targetType); await redisDb.StringSetAsync(cacheKey, likeCount, CachedDataProvider.DefaultTtl); return(likeCount); }
private async Task IncreaseTargetLikeCountAsync([NotNull] string targetId, LikeTargetType targetType, long value) { var cacheKey = TargetLikeCountCacheKey(targetId, targetType); var redisDb = _redis.GetDatabase(); if (await redisDb.KeyExistsAsync(cacheKey)) { if (value >= 0) { await redisDb.StringIncrementAsync(cacheKey, value); } else { await redisDb.StringDecrementAsync(cacheKey, -value); } } }
/// <summary> /// 判断指定用户是否认可过指定目标 /// </summary> /// <param name="userId">用户 ID</param> /// <param name="targetId">目标 ID</param> /// <param name="targetType">目标类型</param> /// <returns>如果用户认可过,返回 <c>true</c></returns> public async Task <bool> IsLikedAsync(string userId, string targetId, LikeTargetType targetType) { if (userId == null || targetId == null) { return(false); } var cacheKey = UserLikedTargetsCacheKey(userId); var redisDb = _redis.GetDatabase(); if (!await redisDb.KeyExistsAsync(cacheKey)) { foreach (var like in await _dbContext.Likes.Where(l => l.OperatorId == userId) .Select(l => new { l.TargetId, l.TargetType }).ToListAsync()) { await redisDb.SetAddAsync(cacheKey, UserLikedTargetCacheValue(like.TargetId, like.TargetType)); } } await redisDb.KeyExpireAsync(cacheKey, CachedDataProvider.DefaultTtl); return(await redisDb.SetContainsAsync(cacheKey, UserLikedTargetCacheValue(targetId, targetType))); }
private static string UserLikedTargetCacheValue(string targetId, LikeTargetType targetType) => $"{targetType.ToString().ToCase(NameConventionCase.PascalCase, NameConventionCase.DashedCase)}:{targetId}";
private static string TargetLikeCountCacheKey(string targetId, LikeTargetType targetType) => $"target-like-count:{targetType.ToString().ToCase(NameConventionCase.PascalCase, NameConventionCase.DashedCase)}:{targetId}";
/// <summary> /// 撤销一个操作者发出的认可 /// </summary> /// <param name="operatorId">操作者 ID</param> /// <param name="targetId">目标 ID</param> /// <param name="targetType">目标类型</param> /// <exception cref="ArgumentNullException">有参数为 null</exception> public async Task RemoveAsync([NotNull] string operatorId, [NotNull] string targetId, LikeTargetType targetType) { if (operatorId == null) { throw new ArgumentNullException(nameof(operatorId)); } if (targetId == null) { throw new ArgumentNullException(nameof(targetId)); } if (!await IsLikedAsync(operatorId, targetId, targetType)) { return; } string likeReceiverId; switch (targetType) { case LikeTargetType.Article: likeReceiverId = await _dbContext.Articles.Where(a => a.Id == targetId) .Select(a => a.AuthorId).SingleAsync(); break; case LikeTargetType.ArticleComment: likeReceiverId = await _dbContext.ArticleComments.Where(c => c.Id == targetId) .Select(c => c.CommentatorId).SingleAsync(); break; case LikeTargetType.Activity: likeReceiverId = await _dbContext.Activities.Where(a => a.Id == targetId) .Select(a => a.AuthorId).SingleAsync(); break; case LikeTargetType.ActivityComment: likeReceiverId = await _dbContext.ActivityComments.Where(c => c.Id == targetId) .Select(c => c.CommentatorId).SingleAsync(); break; case LikeTargetType.ConferenceEntry: likeReceiverId = await _dbContext.ConferenceEntries.Where(e => e.Id == targetId) .Select(e => e.AuthorId).SingleAsync(); break; default: throw new ArgumentOutOfRangeException(nameof(targetType), targetType, null); } var likes = await _dbContext.Likes .Where(l => l.OperatorId == operatorId && l.TargetId == targetId && l.TargetType == targetType) .ToListAsync(); _dbContext.Likes.RemoveRange(likes); await _dbContext.SaveChangesAsync(); var redisDb = _redis.GetDatabase(); var cacheKey = UserLikedTargetsCacheKey(operatorId); foreach (var like in likes) { await redisDb.SetRemoveAsync(cacheKey, UserLikedTargetCacheValue(like.TargetId, like.TargetType)); } await IncreaseUserLikeCountAsync(likeReceiverId, -likes.Count); await IncreaseTargetLikeCountAsync(targetId, targetType, -likes.Count); if (targetType == LikeTargetType.Article) { var receiver = await _dbContext.Users.Where(u => u.Id == likeReceiverId).SingleAsync(); bool saveFailed; do { try { saveFailed = false; receiver.SeasonLikeCount--; await _dbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException e) { saveFailed = true; await e.Entries.Single().ReloadAsync(); } } while (saveFailed); } }
/// <summary> /// 添加一个新认可 /// </summary> /// <param name="operatorId">认可操作者 ID</param> /// <param name="targetId">目标 ID</param> /// <param name="targetType">目标类型</param> /// <returns>认可成功返回认可 ID,失败返回 <c>null</c></returns> /// <exception cref="ArgumentNullException">有参数为 null</exception> public async Task <string> AddAsync([NotNull] string operatorId, [NotNull] string targetId, LikeTargetType targetType) { if (operatorId == null) { throw new ArgumentNullException(nameof(operatorId)); } if (targetId == null) { throw new ArgumentNullException(nameof(targetId)); } if (await IsLikedAsync(operatorId, targetId, targetType)) { return(null); } string likeReceiverId; switch (targetType) { case LikeTargetType.Article: likeReceiverId = await _dbContext.Articles.Where(a => a.Id == targetId) .Select(a => a.AuthorId).SingleAsync(); break; case LikeTargetType.ArticleComment: likeReceiverId = await _dbContext.ArticleComments.Where(c => c.Id == targetId) .Select(c => c.CommentatorId).SingleAsync(); break; case LikeTargetType.Activity: likeReceiverId = await _dbContext.Activities.Where(a => a.Id == targetId) .Select(a => a.AuthorId).SingleAsync(); break; case LikeTargetType.ActivityComment: likeReceiverId = await _dbContext.ActivityComments.Where(c => c.Id == targetId) .Select(c => c.CommentatorId).SingleAsync(); break; case LikeTargetType.ConferenceEntry: likeReceiverId = await _dbContext.ConferenceEntries.Where(e => e.Id == targetId) .Select(e => e.AuthorId).SingleAsync(); break; default: throw new ArgumentOutOfRangeException(nameof(targetType), targetType, null); } if (operatorId == likeReceiverId) { return(null); } var like = new Like { OperatorId = operatorId, TargetId = targetId, TargetType = targetType }; _dbContext.Likes.Add(like); await _dbContext.SaveChangesAsync(); var redisDb = _redis.GetDatabase(); await redisDb.SetAddAsync(UserLikedTargetsCacheKey(operatorId), UserLikedTargetCacheValue(targetId, targetType)); await IncreaseUserLikeCountAsync(likeReceiverId, 1); await IncreaseTargetLikeCountAsync(targetId, targetType, 1); if (targetType == LikeTargetType.Article) { var receiver = await _dbContext.Users.Where(u => u.Id == likeReceiverId).SingleAsync(); bool saveFailed; do { try { saveFailed = false; receiver.SeasonLikeCount++; await _dbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException e) { saveFailed = true; await e.Entries.Single().ReloadAsync(); } } while (saveFailed); } return(like.Id); }
public async Task <IHttpActionResult> CreateOne(string targetId, LikeTargetType targetType) { var operatorId = User.Identity.GetUserId(); var @operator = await _userManager.FindByIdAsync(operatorId); if (@operator.FreeLike <= 0 && !await _coupon.CanTriggerEventAsync(operatorId, CouponEvent.发出认可)) { return(Unauthorized()); } var likeId = await _cachedData.Likes.AddAsync(operatorId, targetId, targetType); var free = string.Empty; if (likeId != null) { if (targetType == LikeTargetType.Article || targetType == LikeTargetType.Activity) { _mqChannel.SendMessage(string.Empty, MqClientProvider.PushHubRequestQueue, new PushHubRequestDto { Type = ContentPushType.Like, ContentId = likeId }); } KeylolUser targetUser; bool notify, steamNotify; MessageType messageType; string steamNotifyText; object couponDescriptionForTargetUser, couponDescriptionForOperator; switch (targetType) { case LikeTargetType.Article: messageType = MessageType.ArticleLike; var article = await _dbContext.Articles.Where(a => a.Id == targetId) .Select(a => new { a.Id, a.Title, a.Author, a.SidForAuthor }).SingleAsync(); targetUser = article.Author; notify = targetUser.NotifyOnArticleLiked; steamNotify = targetUser.SteamNotifyOnArticleLiked; steamNotifyText = $"{@operator.UserName} 认可了你的文章《{article.Title}》:\nhttps://www.keylol.com/article/{targetUser.IdCode}/{article.SidForAuthor}"; couponDescriptionForTargetUser = new { ArticleId = article.Id, OperatorId = operatorId }; couponDescriptionForOperator = new { ArticleId = article.Id }; break; case LikeTargetType.ArticleComment: messageType = MessageType.ArticleCommentLike; var articleComment = await _dbContext.ArticleComments.Where(c => c.Id == targetId) .Select(c => new { c.Id, c.Commentator, c.SidForArticle, ArticleTitle = c.Article.Title, ArticleSidForAuthor = c.Article.SidForAuthor, ArticleAuthorIdCode = c.Article.Author.IdCode }).SingleAsync(); targetUser = articleComment.Commentator; notify = targetUser.NotifyOnCommentLiked; steamNotify = targetUser.SteamNotifyOnCommentLiked; steamNotifyText = $"{@operator.UserName} 认可了你在《{articleComment.ArticleTitle}》下的评论:\nhttps://www.keylol.com/article/{articleComment.ArticleAuthorIdCode}/{articleComment.ArticleSidForAuthor}#{articleComment.SidForArticle}"; couponDescriptionForTargetUser = new { ArticleCommentId = articleComment.Id, OperatorId = operatorId }; couponDescriptionForOperator = new { ArticleCommentId = articleComment.Id }; break; case LikeTargetType.Activity: messageType = MessageType.ActivityLike; var activity = await _dbContext.Activities.Include(a => a.Author) .Where(a => a.Id == targetId).SingleAsync(); targetUser = activity.Author; notify = targetUser.NotifyOnActivityLiked; steamNotify = targetUser.SteamNotifyOnActivityLiked; steamNotifyText = $"{@operator.UserName} 认可了你的动态「{PostOfficeMessageList.CollapseActivityContent(activity)}」:\nhttps://www.keylol.com/activity/{targetUser.IdCode}/{activity.SidForAuthor}"; couponDescriptionForTargetUser = new { ActivityId = activity.Id, OperatorId = operatorId }; couponDescriptionForOperator = new { ActivityId = activity.Id }; break; case LikeTargetType.ActivityComment: messageType = MessageType.ActivityCommentLike; var activityComment = await _dbContext.ActivityComments.Where(c => c.Id == targetId) .Select(c => new { c.Id, c.Commentator, c.SidForActivity, c.Activity, ActivityAuthorIdCode = c.Activity.Author.IdCode }).SingleAsync(); targetUser = activityComment.Commentator; notify = targetUser.NotifyOnCommentLiked; steamNotify = targetUser.SteamNotifyOnCommentLiked; steamNotifyText = $"{@operator.UserName} 认可了你在「{PostOfficeMessageList.CollapseActivityContent(activityComment.Activity)}」下的评论:\nhttps://www.keylol.com/activity/{activityComment.ActivityAuthorIdCode}/{activityComment.Activity.SidForAuthor}#{activityComment.SidForActivity}"; couponDescriptionForTargetUser = new { ActivityCommentId = activityComment.Id, OperatorId = operatorId }; couponDescriptionForOperator = new { ActivityCommentId = activityComment.Id }; break; default: throw new ArgumentOutOfRangeException(nameof(targetType), targetType, null); } if (@operator.FreeLike > 0) { @operator.FreeLike--; free = "Free"; } else { await _coupon.UpdateAsync(@operator, CouponEvent.发出认可, couponDescriptionForOperator); } await _coupon.UpdateAsync(targetUser, CouponEvent.获得认可, couponDescriptionForTargetUser); if (notify) { var message = new Message { Type = messageType, OperatorId = operatorId, ReceiverId = targetUser.Id, Count = await _cachedData.Likes.GetUserLikeCountAsync(targetUser.Id), SecondCount = await _cachedData.Likes.GetTargetLikeCountAsync(targetId, targetType) }; switch (targetType) { case LikeTargetType.Article: message.ArticleId = targetId; break; case LikeTargetType.ArticleComment: message.ArticleCommentId = targetId; break; case LikeTargetType.Activity: message.ActivityId = targetId; break; case LikeTargetType.ActivityComment: message.ActivityCommentId = targetId; break; default: throw new ArgumentOutOfRangeException(nameof(targetType), targetType, null); } await _cachedData.Messages.AddAsync(message); } if (steamNotify) { await _userManager.SendSteamChatMessageAsync(targetUser, steamNotifyText); } } return(Ok(free)); }