/// <summary> /// 向数据库添加并一条邮政消息并保存 /// </summary> /// <param name="message">消息对象</param> public async Task AddAsync([NotNull] Message message) { if (message == null) { throw new ArgumentNullException(nameof(message)); } _dbContext.Messages.Add(message); await _dbContext.SaveChangesAsync(); await IncreaseUserUnreadMessageCountAsync(message.ReceiverId, 1); NotificationProvider.Hub <MessageHub, IMessageHubClient>().User(message.ReceiverId)? .OnUnreadCountChanged(await GetUserUnreadMessageCountAsync(message.ReceiverId)); }
/// <summary> /// 创建 <see cref="PostOfficeMessageList"/> /// </summary> /// <param name="pageType">邮政页面类型</param> /// <param name="currentUserId">当前登录用户 ID</param> /// <param name="page">分页页码</param> /// <param name="returnPageCount">是否返回总页数</param> /// <param name="dbContext"><see cref="KeylolDbContext"/></param> /// <param name="cachedData"><see cref="CachedDataProvider"/></param> /// <returns>Item1 表示 <see cref="PostOfficeMessageList"/>,Item2 表示总页数</returns> public static async Task <Tuple <PostOfficeMessageList, int> > CreateAsync(Type pageType, string currentUserId, int page, bool returnPageCount, KeylolDbContext dbContext, CachedDataProvider cachedData) { Expression <Func <Message, bool> > condition; if (pageType == typeof(UnreadPage)) { condition = m => m.ReceiverId == currentUserId; } else if (pageType == typeof(CommentPage)) { condition = m => m.ReceiverId == currentUserId && (int)m.Type >= 100 && (int)m.Type <= 199; } else if (pageType == typeof(LikePage)) { condition = m => m.ReceiverId == currentUserId && m.Type >= 0 && (int)m.Type <= 99; } else if (pageType == typeof(SubscriberPage)) { condition = m => m.ReceiverId == currentUserId && (int)m.Type >= 300 && (int)m.Type <= 399; } else if (pageType == typeof(MissivePage)) { condition = m => m.ReceiverId == currentUserId && (int)m.Type >= 200 && (int)m.Type <= 299; } else { throw new ArgumentOutOfRangeException(nameof(pageType)); } var messages = await dbContext.Messages.Include(m => m.Article) .Include(m => m.Article.Author) .Include(m => m.Activity) .Include(m => m.Activity.Author) .Include(m => m.Operator) .Include(m => m.ArticleComment) .Include(m => m.ArticleComment.Article) .Include(m => m.ArticleComment.Article.Author) .Include(m => m.ActivityComment) .Include(m => m.ActivityComment.Activity) .Include(m => m.ActivityComment.Activity.Author) .Where(condition) .OrderByDescending(m => m.Unread) .ThenByDescending(m => m.Sid) .TakePage(page, RecordsPerPage) .ToListAsync(); var result = new PostOfficeMessageList(messages.Count); var markReadCount = 0; foreach (var m in messages) { var item = new PostOfficeMessage { Type = m.Type, CreateTime = m.CreateTime, Unread = m.Unread }; if (m.Type.IsMissiveMessage()) { item.Id = m.Id; } else { item.OperatorIdCode = m.Operator.IdCode; item.OperatorAvatarImage = m.Operator.AvatarImage; item.OperatorUserName = m.Operator.UserName; } if (!string.IsNullOrWhiteSpace(m.Reasons)) { item.Reasons = m.Reasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList(); } if (m.ArticleId != null) { item.ArticleAuthorIdCode = m.Article.Author.IdCode; item.ArticleSidForAuthor = m.Article.SidForAuthor; item.ArticleTitle = CollapleArticleTitle(m.Article.Title); } else if (m.ActivityId != null) { item.ActivityAuthorIdCode = m.Activity.Author.IdCode; item.ActivitySidForAuthor = m.Activity.SidForAuthor; item.ActivityContent = CollapseActivityContent(m.Activity); } else if (m.ArticleCommentId != null) { item.CommentContent = CollapseCommentContent(m.ArticleComment.UnstyledContent); item.CommentSidForParent = m.ArticleComment.SidForArticle; item.ArticleAuthorIdCode = m.ArticleComment.Article.Author.IdCode; item.ArticleSidForAuthor = m.ArticleComment.Article.SidForAuthor; item.ArticleTitle = CollapleArticleTitle(m.ArticleComment.Article.Title); } else if (m.ActivityCommentId != null) { item.CommentContent = CollapseCommentContent(m.ActivityComment.Content); item.CommentSidForParent = m.ActivityComment.SidForActivity; item.ActivityAuthorIdCode = m.ActivityComment.Activity.Author.IdCode; item.ActivitySidForAuthor = m.ActivityComment.Activity.SidForAuthor; item.ActivityContent = CollapseActivityContent(m.ActivityComment.Activity); } if (m.Count > 0) { item.Count = m.Count; } if (m.SecondCount > 0) { item.SecondCount = m.SecondCount; } result.Add(item); if (m.Unread) { m.Unread = false; markReadCount++; } } await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.ClientWin); await cachedData.Messages.IncreaseUserUnreadMessageCountAsync(currentUserId, -markReadCount); NotificationProvider.Hub <MessageHub, IMessageHubClient>().User(currentUserId)? .OnUnreadCountChanged(await cachedData.Messages.GetUserUnreadMessageCountAsync(currentUserId)); var pageCount = 1; if (returnPageCount) { var totalCount = await dbContext.Messages.CountAsync(condition); pageCount = totalCount > 0 ? (int)Math.Ceiling(totalCount / (double)RecordsPerPage) : 1; } return(new Tuple <PostOfficeMessageList, int>(result, pageCount)); }
/// <summary> /// 当机器人收到新的聊天消息时,通过此方法通知协调器 /// </summary> /// <param name="senderSteamId">消息发送人 Steam ID</param> /// <param name="botId">机器人 ID</param> /// <param name="message">聊天消息内容</param> public async Task OnBotNewChatMessage(string senderSteamId, string botId, string message) { using (var dbContext = new KeylolDbContext()) { var userManager = new KeylolUserManager(dbContext); var user = await userManager.FindBySteamIdAsync(senderSteamId); if (user == null) { // 非会员,只接受绑定验证码 var code = message.Trim(); var token = await dbContext.SteamBindingTokens .SingleOrDefaultAsync(t => t.Code == code && t.SteamId == null); if (token == null) { await Client.SendChatMessage(botId, senderSteamId, "你的输入无法被识别,请确认登录验证码的长度和格式。如果需要帮助,请与其乐职员取得联系。"); } else { token.BotId = botId; token.SteamId = senderSteamId; await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin); NotificationProvider.Hub <SteamBindingHub, ISteamBindingHubClient>() .Client(token.BrowserConnectionId)? .OnBind(await Client.GetUserProfileName(botId, senderSteamId), await Client.GetUserAvatarHash(botId, senderSteamId)); await Client.SendChatMessage(botId, senderSteamId, "绑定成功,欢迎加入其乐!今后你可以向机器人发送对话快速登录社区,请勿将机器人从好友列表移除。"); await Task.Delay(TimeSpan.FromSeconds(5)); await Client.SendChatMessage(botId, senderSteamId, "若希望在其乐上获得符合游戏兴趣的据点推荐,请避免将 Steam 资料隐私设置为「仅自己可见」。"); } } else { // 已有会员,接受登录验证码和调侃调戏 var match = Regex.Match(message, @"^\s*(\d{4})\s*$"); if (match.Success) { var code = match.Groups[1].Value; try { var connectionId = await _oneTimeToken.Consume <string>(code, OneTimeTokenPurpose.SteamLogin); var loginToken = await _oneTimeToken.Generate(user.Id, TimeSpan.FromMinutes(1), OneTimeTokenPurpose.UserLogin); NotificationProvider.Hub <SteamLoginHub, ISteamLoginHubClient>() .Client(connectionId).OnLoginOneTimeToken(loginToken, user.UserName, user.AvatarImage); await Client.SendChatMessage(botId, senderSteamId, "欢迎回来,你已成功登录其乐社区。"); } catch (Exception) { await Client.SendChatMessage(botId, senderSteamId, "你的输入无法被识别,请确认登录验证码的长度和格式。如果需要帮助,请与其乐职员取得联系。"); } } else { if (!AutoChatDisabledBots.ContainsKey(botId)) { await Client.SendChatMessage(botId, senderSteamId, await AskTulingBotAsync(message, user.Id), true); } } } } }
/// <summary> /// 当机器人接收到用户好友请求时,通过此方法通知协调器 /// </summary> /// <param name="userSteamId">用户 Steam ID</param> /// <param name="botId">机器人 ID</param> public async Task OnBotNewFriendRequest(string userSteamId, string botId) { await Client.AddFriend(botId, userSteamId); // 先接受请求 using (var dbContext = new KeylolDbContext()) { var userManager = new KeylolUserManager(dbContext); var user = await userManager.FindBySteamIdAsync(userSteamId); if (user == null) { // 非会员,在注册时绑定机器人 NotificationProvider.Hub <SteamBindingHub, ISteamBindingHubClient>() .Clients(await dbContext.SteamBindingTokens.Where(t => t.BotId == botId) .Select(t => t.BrowserConnectionId) .ToListAsync())? .OnFriend(); await Task.Delay(TimeSpan.FromSeconds(3)); await Client.SendChatMessage(botId, userSteamId, "欢迎使用当前 Steam 账号加入其乐,请输入你在网页上获取的 8 位绑定验证码。"); var queueName = MqClientProvider.SteamBotDelayedActionQueue(botId); _mqChannel.QueueDeclare(queueName, true, false, false, null); _mqChannel.SendMessage(MqClientProvider.DelayedMessageExchange, queueName, new SteamBotDelayedActionDto { Type = SteamBotDelayedActionType.RemoveFriend, Properties = new { OnlyIfNotKeylolUser = true, Message = "抱歉,你的会话因超时被强制结束,机器人已将你从好友列表中暂时移除。若要加入其乐,请重新按照网页指示注册账号。", SteamId = userSteamId } }, 300000); } else { // 现有会员添加机器人为好友 var bot = await dbContext.SteamBots.FindAsync(botId); if (user.SteamBotId == null && bot != null && bot.FriendCount < bot.FriendUpperLimit) { // 用户此前删除了机器人好友,重新设定当前机器人为绑定的机器人 user.SteamBotId = botId; user.SteamBindingTime = DateTime.Now; await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin); await Task.Delay(TimeSpan.FromSeconds(3)); await Client.SendChatMessage(botId, userSteamId, "你已成功与其乐机器人再次绑定,请务必不要将其乐机器人从好友列表中移除。"); } else { // 用户状态正常但是添加了新的机器人好友,应当移除好友 await Task.Delay(TimeSpan.FromSeconds(3)); await Client.SendChatMessage(botId, userSteamId, "你已绑定另一其乐机器人,当前机器人已拒绝你的好友请求。如有需要,你可以在其乐设置表单中找到你绑定的机器人帐号。"); await Client.RemoveFriend(botId, userSteamId); } } } }
private static void RegisterServices() { Container.Options.DefaultScopedLifestyle = new OwinRequestLifestyle(); // log4net SetupLogger(); Container.RegisterConditional(typeof(ILogProvider), c => typeof(LogProvider <>).MakeGenericType(c.Consumer?.ImplementationType ?? typeof(Startup)), Lifestyle.Singleton, c => true); // RabbitMQ IConnection Container.RegisterSingleton <MqClientProvider>(); // RabbitMQ IModel Container.RegisterPerOwinRequest(() => Container.GetInstance <MqClientProvider>().CreateModel()); // StackExchange.Redis Container.RegisterSingleton <RedisProvider>(); // Geetest Container.RegisterSingleton <GeetestProvider>(); // OWIN Context Provider Container.RegisterSingleton <OwinContextProvider>(); // Keylol DbContext Container.RegisterPerOwinRequest(() => { var context = new KeylolDbContext(); #if DEBUG context.Database.Log = s => { NotificationProvider.Hub <LogHub, ILogHubClient>().All.OnWrite(s); }; #endif return(context); }); // Keylol User Manager Container.RegisterPerOwinRequest <KeylolUserManager>(); // Keylol Role Manager Container.RegisterPerOwinRequest <KeylolRoleManager>(); // Coupon Container.RegisterPerOwinRequest <CouponProvider>(); // Statistics Container.RegisterPerOwinRequest <CachedDataProvider>(); // One-time Token Container.RegisterSingleton <OneTimeTokenProvider>(); // Transient Fault Handling Retry Policy Container.RegisterSingleton <RetryPolicy>(() => { // 首次失败立即重试,之后重试每次增加 2 秒间隔 var strategy = new Incremental(3, TimeSpan.Zero, TimeSpan.FromSeconds(2)); return(new RetryPolicy <SoapFaultWebServiceTransientErrorDetectionStrategy>(strategy)); }); // Notification Container.RegisterSingleton <NotificationProvider>(); // HttpConfiguration / Web API Controllers var httpConfiguration = new HttpConfiguration(); Container.RegisterWebApiControllers(httpConfiguration); httpConfiguration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(Container); Container.RegisterSingleton(() => httpConfiguration); Container.Verify(); }
/// <summary> /// Subclasses of <see cref="T:log4net.Appender.AppenderSkeleton" /> should implement this method /// to perform actual logging. /// </summary> /// <param name="loggingEvent">The event to append.</param> /// <remarks> /// <para> /// A subclass must implement this method to perform /// logging of the <paramref name="loggingEvent" />. /// </para> /// <para>This method will be called by <see cref="M:DoAppend(LoggingEvent)" /> /// if all the conditions listed for that method are met. /// </para> /// <para> /// To restrict the logging of events in the appender /// override the <see cref="M:PreAppendCheck()" /> method. /// </para> /// </remarks> protected override void Append(LoggingEvent loggingEvent) { NotificationProvider.Hub <LogHub, ILogHubClient>() .All.OnWrite( $"[{loggingEvent.Level}] {loggingEvent.TimeStamp} {loggingEvent.LoggerName} - {loggingEvent.RenderedMessage}"); }