升级组件:Longbow.Web升级到2.2.9

#Comment
OnlineUser功能整合到Longbow.Web组件中
This commit is contained in:
Argo-MacBookPro 2019-04-08 16:01:40 +08:00
parent 32894af881
commit 53900a563a
17 changed files with 91 additions and 466 deletions

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>

View File

@ -41,13 +41,14 @@ namespace Bootstrap.Admin.Controllers
/// </summary>
/// <returns>The login.</returns>
/// <param name="onlineUserSvr"></param>
/// <param name="ipLocator"></param>
/// <param name="userName">User name.</param>
/// <param name="password">Password.</param>
/// <param name="remember">Remember.</param>
[HttpPost]
public async Task<IActionResult> Login([FromServices]IOnlineUsers onlineUserSvr, string userName, string password, string remember)
public async Task<IActionResult> Login([FromServices]IOnlineUsers onlineUserSvr, [FromServices]IIPLocatorProvider ipLocator, string userName, string password, string remember)
{
if (UserHelper.Authenticate(userName, password, loginUser => CreateLoginUser(onlineUserSvr, HttpContext, loginUser)))
if (UserHelper.Authenticate(userName, password, loginUser => CreateLoginUser(onlineUserSvr, ipLocator, HttpContext, loginUser)))
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
@ -63,13 +64,14 @@ namespace Bootstrap.Admin.Controllers
///
/// </summary>
/// <param name="onlineUserSvr"></param>
/// <param name="ipLocator"></param>
/// <param name="context"></param>
/// <param name="loginUser"></param>
internal static void CreateLoginUser(IOnlineUsers onlineUserSvr, HttpContext context, LoginUser loginUser)
internal static void CreateLoginUser(IOnlineUsers onlineUserSvr, IIPLocatorProvider ipLocator, HttpContext context, LoginUser loginUser)
{
var agent = new UserAgent(context.Request.Headers["User-Agent"]);
loginUser.Ip = (context.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
loginUser.City = onlineUserSvr.RetrieveLocaleByIp(loginUser.Ip);
loginUser.City = ipLocator.Locate(loginUser.Ip);
loginUser.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
loginUser.OS = $"{agent.OS.Name} {agent.OS.Version}";
}

View File

@ -1,5 +1,6 @@
using Bootstrap.DataAccess;
using Bootstrap.Security;
using Longbow.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
@ -28,17 +29,18 @@ namespace Bootstrap.Admin.Controllers.Api
///
/// </summary>
/// <param name="onlineUserSvr"></param>
/// <param name="ipLocator"></param>
/// <param name="value"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost]
public string Post([FromServices]IOnlineUsers onlineUserSvr, [FromBody]JObject value)
public string Post([FromServices]IOnlineUsers onlineUserSvr, [FromServices]IIPLocatorProvider ipLocator, [FromBody]JObject value)
{
string token = null;
dynamic user = value;
string userName = user.userName;
string password = user.password;
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && UserHelper.Authenticate(userName, password, loginUser => AccountController.CreateLoginUser(onlineUserSvr, HttpContext, loginUser)))
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && UserHelper.Authenticate(userName, password, loginUser => AccountController.CreateLoginUser(onlineUserSvr, ipLocator, HttpContext, loginUser)))
{
token = BootstrapAdminJwtTokenHandler.CreateToken(userName);
}

View File

@ -29,16 +29,17 @@ namespace Bootstrap.Admin.Controllers.Api
///
/// </summary>
/// <param name="onlineUserSvr"></param>
/// <param name="ipLocator"></param>
/// <param name="value"></param>
/// <returns></returns>
[HttpPost]
public bool Post([FromServices]IOnlineUsers onlineUserSvr, [FromBody]Log value)
public bool Post([FromServices]IOnlineUsers onlineUserSvr, [FromServices]IIPLocatorProvider ipLocator, [FromBody]Log value)
{
var agent = new UserAgent(Request.Headers["User-Agent"]);
value.Ip = (HttpContext.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
value.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
value.OS = $"{agent.OS.Name} {agent.OS.Version}";
value.City = onlineUserSvr.RetrieveLocaleByIp(value.Ip);
value.City = ipLocator.Locate(value.Ip);
value.UserName = User.Identity.Name;
return LogHelper.Save(value);
}

View File

@ -1,3 +1,4 @@
using Longbow.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;

View File

@ -1,187 +0,0 @@
using Bootstrap.DataAccess;
using Longbow.Cache;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Bootstrap.Admin
{
/// <summary>
///
/// </summary>
internal class DefaultOnlineUsers : IOnlineUsers
{
private readonly ConcurrentDictionary<string, AutoExpireCacheEntry<OnlineUser>> _onlineUsers = new ConcurrentDictionary<string, AutoExpireCacheEntry<OnlineUser>>();
private readonly ConcurrentDictionary<string, AutoExpireCacheEntry<string>> _ipLocator = new ConcurrentDictionary<string, AutoExpireCacheEntry<string>>();
private readonly HttpClient _client;
private readonly IEnumerable<string> _local = new string[] { "::1", "127.0.0.1" };
/// <summary>
///
/// </summary>
/// <param name="factory"></param>
public DefaultOnlineUsers(IHttpClientFactory factory)
{
_client = factory.CreateClient(OnlineUsersServicesCollectionExtensions.IPSvrHttpClientName);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerable<OnlineUser> OnlineUsers
{
get { return _onlineUsers.Values.Select(v => v.Value); }
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="addValueFactory"></param>
/// <param name="updateValueFactory"></param>
/// <returns></returns>
public AutoExpireCacheEntry<OnlineUser> AddOrUpdate(string key, Func<string, AutoExpireCacheEntry<OnlineUser>> addValueFactory, Func<string, AutoExpireCacheEntry<OnlineUser>, AutoExpireCacheEntry<OnlineUser>> updateValueFactory) => _onlineUsers.AddOrUpdate(key, addValueFactory, updateValueFactory);
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="onlineUserCache"></param>
/// <returns></returns>
public bool TryRemove(string key, out AutoExpireCacheEntry<OnlineUser> onlineUserCache) => _onlineUsers.TryRemove(key, out onlineUserCache);
/// <summary>
///
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
public string RetrieveLocaleByIp(string ip = null) => _ipLocator.GetOrAdd(ip, key => new AutoExpireCacheEntry<string>(IPProxy(key), 1000 * 60, __ => _ipLocator.TryRemove(key, out _))).Value;
private string IPProxy(string ip)
{
var ipSvr = DictHelper.RetrieveLocaleIPSvr();
if (ipSvr.IsNullOrEmpty() || ipSvr.Equals("None", StringComparison.OrdinalIgnoreCase) || ip.IsNullOrEmpty() || _local.Any(p => p == ip)) return "本地连接";
var ipSvrUrl = DictHelper.RetrieveLocaleIPSvrUrl(ipSvr);
if (ipSvrUrl.IsNullOrEmpty()) return "本地连接";
var url = $"{ipSvrUrl}{ip}";
var task = ipSvr == "BaiDuIPSvr" ? RetrieveLocator<BaiDuIPLocator>(url) : RetrieveLocator<JuheIPLocator>(url);
task.Wait();
return task.Result;
}
private async Task<string> RetrieveLocator<T>(string url)
{
var ret = default(T);
try
{
ret = await _client.GetAsJsonAsync<T>(url);
}
catch (Exception ex)
{
ex.Log(new NameValueCollection()
{
["Url"] = url
});
}
return ret?.ToString();
}
/// <summary>
///
/// </summary>
private class BaiDuIPLocator
{
/// <summary>
/// 详细地址信息
/// </summary>
public string Address { get; set; }
/// <summary>
/// 结果状态返回码
/// </summary>
public string Status { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Status == "0" ? string.Join(" ", Address.SpanSplit("|").Skip(1).Take(2)) : "XX XX";
}
}
private class JuheIPLocator
{
/// <summary>
///
/// </summary>
public string ResultCode { get; set; }
/// <summary>
///
/// </summary>
public string Reason { get; set; }
/// <summary>
///
/// </summary>
public JuheIPLocatorResult Result { get; set; }
/// <summary>
///
/// </summary>
/// <value></value>
public int Error_Code { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Error_Code != 0 ? "XX XX" : Result.ToString();
}
}
private class JuheIPLocatorResult
{
/// <summary>
///
/// </summary>
public string Country { get; set; }
/// <summary>
///
/// </summary>
public string Province { get; set; }
/// <summary>
///
/// </summary>
public string City { get; set; }
/// <summary>
///
/// </summary>
public string Isp { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Country != "中国" ? $"{Country} {Province} {Isp}" : $"{Province} {City} {Isp}";
}
}
}
}

View File

@ -1,41 +0,0 @@
using Longbow.Cache;
using System;
using System.Collections.Generic;
namespace Bootstrap.Admin
{
/// <summary>
///
/// </summary>
public interface IOnlineUsers
{
/// <summary>
///
/// </summary>
IEnumerable<OnlineUser> OnlineUsers { get; }
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="addValueFactory"></param>
/// <param name="updateValueFactory"></param>
/// <returns></returns>
AutoExpireCacheEntry<OnlineUser> AddOrUpdate(string key, Func<string, AutoExpireCacheEntry<OnlineUser>> addValueFactory, Func<string, AutoExpireCacheEntry<OnlineUser>, AutoExpireCacheEntry<OnlineUser>> updateValueFactory);
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="onlineUserCache"></param>
/// <returns></returns>
bool TryRemove(string key, out AutoExpireCacheEntry<OnlineUser> onlineUserCache);
/// <summary>
///
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
string RetrieveLocaleByIp(string ip = null);
}
}

View File

@ -1,94 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Bootstrap.Admin
{
/// <summary>
///
/// </summary>
public class OnlineUser
{
private ConcurrentQueue<KeyValuePair<DateTime, string>> _requestUrls = new ConcurrentQueue<KeyValuePair<DateTime, string>>();
/// <summary>
///
/// </summary>
public string ConnectionId { get; set; }
/// <summary>
///
/// </summary>
public string UserName { get; set; }
/// <summary>
///
/// </summary>
public string DisplayName { get; set; }
/// <summary>
///
/// </summary>
public DateTime FirstAccessTime { get; set; }
/// <summary>
///
/// </summary>
public DateTime LastAccessTime { get; set; }
/// <summary>
///
/// </summary>
public string Location { get; set; }
/// <summary>
///
/// </summary>
public string Method { get; set; }
/// <summary>
///
/// </summary>
public string Ip { get; set; }
/// <summary>
///
/// </summary>
public string Browser { get; set; }
/// <summary>
///
/// </summary>
public string OS { get; set; }
/// <summary>
///
/// </summary>
public string RequestUrl { get; set; }
/// <summary>
///
/// </summary>
public IEnumerable<KeyValuePair<DateTime, string>> RequestUrls
{
get
{
return _requestUrls.ToArray();
}
}
/// <summary>
///
/// </summary>
/// <param name="url"></param>
public void AddRequestUrl(string url)
{
_requestUrls.Enqueue(new KeyValuePair<DateTime, string>(DateTime.Now, url));
if (_requestUrls.Count > 5)
{
_requestUrls.TryDequeue(out _);
}
}
}
}

View File

@ -1,66 +0,0 @@
using Bootstrap.Admin;
using Bootstrap.DataAccess;
using Longbow.Cache;
using Longbow.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Net;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
///
/// </summary>
public static class OnlineUsersMiddlewareExtensions
{
/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseOnlineUsers(this IApplicationBuilder builder) => builder.UseWhen(context => context.Filter(), app => app.Use(async (context, next) =>
{
await System.Threading.Tasks.Task.Run(() =>
{
var user = UserHelper.RetrieveUserByUserName(context.User.Identity.Name);
if (user == null) return;
var onlineUserSvr = context.RequestServices.GetRequiredService<IOnlineUsers>();
var proxy = new Func<AutoExpireCacheEntry<OnlineUser>, Action, AutoExpireCacheEntry<OnlineUser>>((c, action) =>
{
var v = c.Value;
v.UserName = user.UserName;
v.DisplayName = user.DisplayName;
v.LastAccessTime = DateTime.Now;
v.Method = context.Request.Method;
v.RequestUrl = context.Request.Path;
v.AddRequestUrl(context.Request.Path);
action?.Invoke();
TraceHelper.Save(new Trace { Ip = v.Ip, RequestUrl = v.RequestUrl, LogTime = v.LastAccessTime, City = v.Location, Browser = v.Browser, OS = v.OS, UserName = v.UserName });
return c;
});
onlineUserSvr.AddOrUpdate(context.Connection.Id ?? "", key =>
{
var agent = new UserAgent(context.Request.Headers["User-Agent"]);
var v = new OnlineUser();
v.ConnectionId = key;
v.Ip = (context.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
v.Location = onlineUserSvr.RetrieveLocaleByIp(v.Ip);
v.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
v.OS = $"{agent.OS.Name} {agent.OS.Version}";
v.FirstAccessTime = DateTime.Now;
return proxy(new AutoExpireCacheEntry<OnlineUser>(v, 1000 * 60, __ => onlineUserSvr.TryRemove(key, out _)), null);
}, (key, v) => proxy(v, () => v.Reset()));
});
await next();
}));
private static bool Filter(this HttpContext context)
{
var url = context.Request.Path;
return !new string[] { "/api", "/NotiHub", "/swagger" }.Any(r => url.StartsWithSegments(r, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@ -1,31 +0,0 @@
using Bootstrap.Admin;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
///
/// </summary>
public static class OnlineUsersServicesCollectionExtensions
{
/// <summary>
///
/// </summary>
internal const string IPSvrHttpClientName = "IPSvr";
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddOnlineUsers(this IServiceCollection services)
{
services.TryAddSingleton<IOnlineUsers, DefaultOnlineUsers>();
services.AddHttpClient(IPSvrHttpClientName, client =>
{
client.DefaultRequestHeaders.Connection.Add("keep-alive");
});
return services;
}
}
}

View File

@ -1,4 +1,4 @@
using Bootstrap.DataAccess;
using Bootstrap.DataAccess;
using Bootstrap.Security.Filter;
using Longbow.Web;
using Longbow.Web.SignalR;
@ -60,6 +60,7 @@ namespace Bootstrap.Admin
services.AddConfigurationManager(Configuration);
services.AddCacheManager(Configuration);
services.AddDbAdapter();
services.AddIPLocator(DictHelper.ConfigIPLocator);
services.AddOnlineUsers();
var dataProtectionBuilder = services.AddDataProtection(op => op.ApplicationDiscriminator = Configuration["ApplicationDiscriminator"])
.SetApplicationName(Configuration["ApplicationName"])
@ -127,7 +128,7 @@ namespace Bootstrap.Admin
app.UseStaticFiles();
app.UseAuthentication();
app.UseBootstrapAdminAuthorization(RoleHelper.RetrieveRolesByUserName, RoleHelper.RetrieveRolesByUrl, AppHelper.RetrievesByUserName);
app.UseOnlineUsers();
app.UseOnlineUsers(callback: TraceHelper.Save);
app.UseCacheManagerCorsHandler();
app.UseSignalR(routes => { routes.MapHub<SignalRHub>("/NotiHub"); });
app.UseMvc(routes =>

View File

@ -14,7 +14,7 @@
<PackageReference Include="Bootstrap.Security.DataAccess" Version="2.1.0" />
<PackageReference Include="Longbow.Cache" Version="2.2.5" />
<PackageReference Include="Longbow.Data" Version="2.2.6" />
<PackageReference Include="Longbow.Web" Version="2.2.8" />
<PackageReference Include="Longbow.Web" Version="2.2.9" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.2" />
</ItemGroup>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@ -14,7 +14,7 @@
<PackageReference Include="Bootstrap.Security.DataAccess" Version="2.1.0" />
<PackageReference Include="Longbow.Data" Version="2.2.6" />
<PackageReference Include="Longbow.Security.Cryptography" Version="1.3.0" />
<PackageReference Include="Longbow.Web" Version="2.2.8" />
<PackageReference Include="Longbow.Web" Version="2.2.9" />
<PackageReference Include="Longbow.Cache" Version="2.2.5" />
<PackageReference Include="Longbow" Version="2.2.8" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.2" />

View File

@ -1,6 +1,8 @@
using Bootstrap.Security;
using Longbow.Cache;
using Longbow.Data;
using Longbow.Web;
using System;
using System.Collections.Generic;
using System.Linq;
@ -75,6 +77,21 @@ namespace Bootstrap.DataAccess
return ret;
}
/// <summary>
///
/// </summary>
/// <param name="op"></param>
public static void ConfigIPLocator(IPLocatorOption op)
{
var name = RetrieveLocaleIPSvr();
if (!string.IsNullOrEmpty(name) && !name.Equals("None", StringComparison.OrdinalIgnoreCase))
{
var url = RetrieveLocaleIPSvrUrl(name);
op.Locator = string.IsNullOrEmpty(url) ? null : DefaultIPLocatorProvider.CreateLocator(name);
op.Url = string.IsNullOrEmpty(url) ? string.Empty : $"{url}{op.IP}";
}
}
/// <summary>
/// 保存网站个性化设置
/// </summary>

View File

@ -1,5 +1,7 @@
using Longbow.Data;
using Longbow.Web;
using Longbow.Web.Mvc;
using Microsoft.AspNetCore.Http;
using PetaPoco;
using System;
@ -11,8 +13,27 @@ namespace Bootstrap.DataAccess
/// <summary>
/// 保存访问历史记录
/// </summary>
/// <param name="p"></param>
public static void Save(Trace p) => DbContextManager.Create<Trace>().Save(p);
/// <param name="context"></param>
/// <param name="v"></param>
public static void Save(HttpContext context, OnlineUser v)
{
if (context.User.Identity.IsAuthenticated)
{
var user = UserHelper.RetrieveUserByUserName(context.User.Identity.Name);
v.UserName = user.UserName;
v.DisplayName = user.DisplayName;
DbContextManager.Create<Trace>().Save(new Trace
{
Ip = v.Ip,
RequestUrl = v.RequestUrl,
LogTime = v.LastAccessTime,
City = v.Location,
Browser = v.Browser,
OS = v.OS,
UserName = v.UserName
});
}
}
/// <summary>
/// 获得指定IP历史访问记录

View File

@ -1,32 +1,33 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace Bootstrap.Admin.Api
{
public class OnlineTest : ControllerTest
{
public OnlineTest(BAWebHost factory) : base(factory, "api/OnlineUsers") { }
[Fact]
public async void Get_Ok()
{
var users = await Client.GetAsJsonAsync<IEnumerable<OnlineUser>>();
Assert.Single(users);
}
[Fact]
public async void GetById_Ok()
{
var urls = await Client.GetAsJsonAsync<IEnumerable<KeyValuePair<DateTime, string>>>("UnitTest");
Assert.Empty(urls);
using Longbow.Web;
using System;
using System.Collections.Generic;
using Xunit;
namespace Bootstrap.Admin.Api
{
public class OnlineTest : ControllerTest
{
public OnlineTest(BAWebHost factory) : base(factory, "api/OnlineUsers") { }
[Fact]
public async void Get_Ok()
{
var users = await Client.GetAsJsonAsync<IEnumerable<OnlineUser>>();
Assert.Single(users);
}
[Fact]
public async void Put_Ok()
{
var ret = await Client.PutAsJsonAsync<string, bool>("");
Assert.False(ret);
[Fact]
public async void GetById_Ok()
{
var urls = await Client.GetAsJsonAsync<IEnumerable<KeyValuePair<DateTime, string>>>("UnitTest");
Assert.Empty(urls);
}
}
}
[Fact]
public async void Put_Ok()
{
var ret = await Client.PutAsJsonAsync<string, bool>("");
Assert.False(ret);
}
}
}

View File

@ -1,12 +1,10 @@
using Bootstrap.DataAccess;
using Longbow.Data;
using Longbow.Web.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Xunit;
using DbManager = Longbow.Data.DbManager;
namespace Bootstrap.Admin.Api
{