From 49b0cb7b465561447b8d4693ba988b13e0e1b57a Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 15 May 2021 16:01:21 +0800 Subject: feat: Timeline post change notification with signalr. --- .../Timeline/Controllers/TimelinePostController.cs | 16 ++- BackEnd/Timeline/SignalRHub/ITimelineClient.cs | 9 ++ BackEnd/Timeline/SignalRHub/TimelineHub.cs | 58 +++++++++ BackEnd/Timeline/Startup.cs | 4 + BackEnd/Timeline/Timeline.csproj | 140 ++++++++++----------- 5 files changed, 156 insertions(+), 71 deletions(-) create mode 100644 BackEnd/Timeline/SignalRHub/ITimelineClient.cs create mode 100644 BackEnd/Timeline/SignalRHub/TimelineHub.cs (limited to 'BackEnd/Timeline') diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs index da45cbea..f00a689c 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostController.cs +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -13,6 +15,7 @@ using Timeline.Models.Validation; using Timeline.Services.Mapper; using Timeline.Services.Timeline; using Timeline.Services.User; +using Timeline.SignalRHub; namespace Timeline.Controllers { @@ -24,6 +27,8 @@ namespace Timeline.Controllers [ProducesErrorResponseType(typeof(CommonResponse))] public class TimelinePostController : MyControllerBase { + private readonly ILogger _logger; + private readonly ITimelineService _timelineService; private readonly ITimelinePostService _postService; @@ -31,12 +36,16 @@ namespace Timeline.Controllers private readonly MarkdownProcessor _markdownProcessor; - public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor) + private readonly IHubContext _timelineHubContext; + + public TimelinePostController(ILogger logger, ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor, IHubContext timelineHubContext) { + _logger = logger; _timelineService = timelineService; _postService = timelinePostService; _mapper = mapper; _markdownProcessor = markdownProcessor; + _timelineHubContext = timelineHubContext; } private bool UserHasAllTimelineManagementPermission => UserHasPermission(UserPermission.AllTimelineManagement); @@ -207,6 +216,11 @@ namespace Timeline.Controllers try { var post = await _postService.CreatePostAsync(timelineId, userId, createRequest); + + var group = TimelineHub.GenerateTimelinePostChangeListeningGroupName(timeline); + await _timelineHubContext.Clients.Group(group).SendAsync(nameof(ITimelineClient.OnTimelinePostChanged), timeline); + _logger.LogInformation("Notify group {0} of timeline post change.", group); + var result = await Map(post); return result; } diff --git a/BackEnd/Timeline/SignalRHub/ITimelineClient.cs b/BackEnd/Timeline/SignalRHub/ITimelineClient.cs new file mode 100644 index 00000000..0d1be093 --- /dev/null +++ b/BackEnd/Timeline/SignalRHub/ITimelineClient.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Timeline.SignalRHub +{ + public interface ITimelineClient + { + Task OnTimelinePostChanged(string timelineName); + } +} diff --git a/BackEnd/Timeline/SignalRHub/TimelineHub.cs b/BackEnd/Timeline/SignalRHub/TimelineHub.cs new file mode 100644 index 00000000..2ad7bd66 --- /dev/null +++ b/BackEnd/Timeline/SignalRHub/TimelineHub.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using Timeline.Auth; +using Timeline.Services; +using Timeline.Services.Timeline; +using Timeline.Services.User; + +namespace Timeline.SignalRHub +{ + public class TimelineHub : Hub + { + private readonly ILogger _logger; + private readonly ITimelineService _timelineService; + + public TimelineHub(ILogger logger, ITimelineService timelineService) + { + _logger = logger; + _timelineService = timelineService; + } + + public static string GenerateTimelinePostChangeListeningGroupName(string timelineName) + { + return $"timeline-post-change-{timelineName}"; + } + + public async Task SubscribeTimelinePostChange(string timelineName) + { + try + { + var timelineId = await _timelineService.GetTimelineIdByNameAsync(timelineName); + var user = Context.User; + if (!user.HasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.HasReadPermissionAsync(timelineId, user.GetOptionalUserId())) + throw new HubException("Forbidden."); + + var group = GenerateTimelinePostChangeListeningGroupName(timelineName); + await Groups.AddToGroupAsync(Context.ConnectionId, group); + _logger.LogInformation("Add connection {0} to group {1}", Context.ConnectionId, group); + } + catch (ArgumentException) + { + throw new HubException("Timeline name is illegal."); + } + catch (EntityNotExistException) + { + throw new HubException("Timeline not exist."); + } + } + + public async Task UnsubscribeTimelinePostChange(string timelineName) + { + var group = GenerateTimelinePostChangeListeningGroupName(timelineName); + await Groups.RemoveFromGroupAsync(Context.ConnectionId, group); + _logger.LogInformation("Remove connection {0} from group {1}", Context.ConnectionId, group); + } + } +} diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 1e0d4779..18097e2c 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -26,6 +26,7 @@ using Timeline.Services.Mapper; using Timeline.Services.Timeline; using Timeline.Services.Token; using Timeline.Services.User; +using Timeline.SignalRHub; using Timeline.Swagger; namespace Timeline @@ -85,6 +86,8 @@ namespace Timeline options.InvalidModelStateResponseFactory = InvalidModelResponseFactory.Factory; }); + services.AddSignalR(); + services.AddAuthentication(AuthenticationConstants.Scheme) .AddScheme(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { }); services.AddAuthorization(); @@ -154,6 +157,7 @@ namespace Timeline app.UseEndpoints(endpoints => { endpoints.MapControllers(); + endpoints.MapHub("api/hub/timeline"); }); UnknownEndpointMiddleware.Attach(app); diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj index b9653b25..955947af 100644 --- a/BackEnd/Timeline/Timeline.csproj +++ b/BackEnd/Timeline/Timeline.csproj @@ -49,19 +49,19 @@ - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx True @@ -69,122 +69,122 @@ Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - True - True - Resource.resx + True + True + Resource.resx - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs ResXFileCodeGenerator Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs - ResXFileCodeGenerator - Resource.Designer.cs + ResXFileCodeGenerator + Resource.Designer.cs \ No newline at end of file -- cgit v1.2.3