Merge branch 'dev'
|
@ -33,7 +33,7 @@ namespace Bootstrap.Admin.Controllers
|
|||
ViewBag.UserName = "Admin";
|
||||
ViewBag.Password = "123789";
|
||||
}
|
||||
return User.Identity.IsAuthenticated ? (ActionResult)Redirect("~/Home/Index") : View("Login", new ModelBase());
|
||||
return User.Identity.IsAuthenticated ? (ActionResult)Redirect("~/Home/Index") : View("Login", new LoginModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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));
|
||||
|
@ -55,21 +56,22 @@ namespace Bootstrap.Admin.Controllers
|
|||
// redirect origin url
|
||||
var originUrl = Request.Query[CookieAuthenticationDefaults.ReturnUrlParameter].FirstOrDefault() ?? "~/Home/Index";
|
||||
return Redirect(originUrl);
|
||||
}
|
||||
return View("Login", new ModelBase());
|
||||
}
|
||||
return View("Login", new LoginModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </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}";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using Longbow.Web;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
using Bootstrap.DataAccess;
|
||||
|
||||
namespace Bootstrap.Admin.Models
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class LoginModel : ModelBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public LoginModel()
|
||||
{
|
||||
ImageLibUrl = DictHelper.RetrieveImagesLibUrl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证码图床地址
|
||||
/// </summary>
|
||||
public string ImageLibUrl { get; protected set; }
|
||||
}
|
||||
}
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 =>
|
||||
|
|
|
@ -1,219 +1,220 @@
|
|||
@model ModelBase
|
||||
@{
|
||||
ViewBag.Title = Model.Title;
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@section css {
|
||||
<environment include="Development">
|
||||
<link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
|
||||
<link href="~/lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
|
||||
<link href="~/lib/bootstrap-sweetalert/sweetalert.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link href="~/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/bootstrap-sweetalert/sweetalert.min.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<link href="~/lib/captcha/slidercaptcha.css" rel="stylesheet" />
|
||||
<link href="~/css/theme.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/css/login.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/css/login-responsive.css" rel="stylesheet" asp-append-version="true" />
|
||||
@if (!string.IsNullOrEmpty(Model.Theme))
|
||||
{
|
||||
<link href="~/css/@Model.Theme" rel="stylesheet" asp-append-version="true" />
|
||||
}
|
||||
}
|
||||
@section javascript {
|
||||
<environment include="Development">
|
||||
<script src="~/lib/twitter-bootstrap/js/bootstrap.bundle.js"></script>
|
||||
<script src="~/lib/validate/jquery.validate.js"></script>
|
||||
<script src="~/lib/validate/localization/messages_zh.js"></script>
|
||||
<script src="~/lib/bootstrap-sweetalert/sweetalert.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="~/lib/twitter-bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/lib/validate/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/validate/localization/messages_zh.min.js"></script>
|
||||
<script src="~/lib/bootstrap-sweetalert/sweetalert.min.js"></script>
|
||||
</environment>
|
||||
<script src="~/lib/captcha/longbow.slidercaptcha.js"></script>
|
||||
<script src="~/lib/longbow/longbow.common.js"></script>
|
||||
<script src="~/lib/longbow/longbow.validate.js"></script>
|
||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||
}
|
||||
<div class="container">
|
||||
<form id="login" method="post" class="form-signin">
|
||||
<h2 class="form-signin-heading">@Model.Title</h2>
|
||||
<div class="login-wrap" data-toggle="LgbValidate" data-valid-button="button[type='submit']">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" name="userName" class="form-control" placeholder="用户名" maxlength="16" data-required-msg="请输入用户名" value="@ViewBag.UserName" autofocus data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="password" name="password" class="form-control" value="@ViewBag.Password" placeholder="密码" maxlength="16" data-required-msg="请输入密码" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group rememberPwd" onselectstart="return false">
|
||||
<i class="fa fa-square-o"></i>
|
||||
<span>记住密码自动登录</span>
|
||||
<input id="remember" name="remember" type="hidden" value="false" />
|
||||
</div>
|
||||
<button class="btn btn-lg btn-login btn-block" type="submit">登 陆</button>
|
||||
<div class="login-footer">
|
||||
<a href="#" data-method="register">申请账号</a>
|
||||
<a href="#" data-method="forgot">忘记密码</a>
|
||||
</div>
|
||||
<div class="slidercaptcha card">
|
||||
<div class="card-header">
|
||||
<span>请完成安全验证</span>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="card-body"><div id="captcha"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal fade" id="dialogNew" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnSubmit" data-valid-modal="#dialogNew">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabel">新用户注册</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="userName">登陆名称:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-plus"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="userName" autocomplete="off" class="form-control" placeholder="登陆账号不可为空" userName="true" minlength="4" maxlength="16" remote="api/Register" data-remote-msg="此用户已存在" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="displayName">显示名称:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-circle-o"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="displayName" class="form-control" value="" placeholder="显示名称不可为空" maxlength="20" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="password" id="password" class="form-control" value="" placeholder="密码不可为空" maxlength="16" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="assurePassword">确认密码:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="password" id="assurePassword" class="form-control" value="" placeholder="确认密码" maxlength="16" equalTo="#password" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">申请理由:</label>
|
||||
<textarea id="description" class="form-control" placeholder="申请理由,500字以内" rows="3" maxlength="500" data-valid="true"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="btnSubmit">
|
||||
<i class="fa fa-save"></i>
|
||||
<span>提交</span>
|
||||
</button>
|
||||
<div class="slidercaptcha forgot reg card">
|
||||
<div class="card-header">
|
||||
<span>请完成安全验证</span>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="card-body"><div id="regcap"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="dialogForgot" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnForgot" data-valid-modal="#dialogForgot">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabelForgot">忘记密码</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="f_userName">登陆账号:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-plus"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="f_userName" autocomplete="off" class="form-control" placeholder="登陆账号不可为空" minlength="4" maxlength="16" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="f_displayName">显示名称:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-circle-o"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="f_displayName" class="form-control" value="" placeholder="显示名称不可为空" maxlength="20" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="f_desc">申请理由:</label>
|
||||
<textarea id="f_desc" class="form-control" placeholder="申请理由,500字以内" rows="3" maxlength="500" data-valid="true">我是用户XXX,我的手机号是XXXXXX,由于密码忘记,请将密码重置为123,登录后我自行更改,谢谢管理员</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="btnForgot">
|
||||
<i class="fa fa-send-o"></i>
|
||||
<span>提交</span>
|
||||
</button>
|
||||
<div class="slidercaptcha forgot card">
|
||||
<div class="card-header">
|
||||
<span>请完成安全验证</span>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="card-body"><div id="forgotcap"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@model LoginModel
|
||||
@{
|
||||
ViewBag.Title = Model.Title;
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@section css {
|
||||
<environment include="Development">
|
||||
<link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
|
||||
<link href="~/lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
|
||||
<link href="~/lib/bootstrap-sweetalert/sweetalert.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link href="~/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/bootstrap-sweetalert/sweetalert.min.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<link href="~/lib/captcha/slidercaptcha.css" rel="stylesheet" />
|
||||
<link href="~/css/theme.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/css/login.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/css/login-responsive.css" rel="stylesheet" asp-append-version="true" />
|
||||
@if (!string.IsNullOrEmpty(Model.Theme))
|
||||
{
|
||||
<link href="~/css/@Model.Theme" rel="stylesheet" asp-append-version="true" />
|
||||
}
|
||||
}
|
||||
@section javascript {
|
||||
<environment include="Development">
|
||||
<script src="~/lib/twitter-bootstrap/js/bootstrap.bundle.js"></script>
|
||||
<script src="~/lib/validate/jquery.validate.js"></script>
|
||||
<script src="~/lib/validate/localization/messages_zh.js"></script>
|
||||
<script src="~/lib/bootstrap-sweetalert/sweetalert.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="~/lib/twitter-bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/lib/validate/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/validate/localization/messages_zh.min.js"></script>
|
||||
<script src="~/lib/bootstrap-sweetalert/sweetalert.min.js"></script>
|
||||
</environment>
|
||||
<script src="~/lib/captcha/longbow.slidercaptcha.js"></script>
|
||||
<script src="~/lib/longbow/longbow.common.js"></script>
|
||||
<script src="~/lib/longbow/longbow.validate.js"></script>
|
||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||
}
|
||||
<div class="container">
|
||||
<input id="imgUrl" type="hidden" value="@Model.ImageLibUrl" />
|
||||
<form id="login" method="post" class="form-signin">
|
||||
<h2 class="form-signin-heading">@Model.Title</h2>
|
||||
<div class="login-wrap" data-toggle="LgbValidate" data-valid-button="button[type='submit']">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" name="userName" class="form-control" placeholder="用户名" maxlength="16" data-required-msg="请输入用户名" value="@ViewBag.UserName" autofocus data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="password" name="password" class="form-control" value="@ViewBag.Password" placeholder="密码" maxlength="16" data-required-msg="请输入密码" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group rememberPwd" onselectstart="return false">
|
||||
<i class="fa fa-square-o"></i>
|
||||
<span>记住密码自动登录</span>
|
||||
<input id="remember" name="remember" type="hidden" value="false" />
|
||||
</div>
|
||||
<button class="btn btn-lg btn-login btn-block" type="submit">登 陆</button>
|
||||
<div class="login-footer">
|
||||
<a href="#" data-method="register">申请账号</a>
|
||||
<a href="#" data-method="forgot">忘记密码</a>
|
||||
</div>
|
||||
<div class="slidercaptcha card">
|
||||
<div class="card-header">
|
||||
<span>请完成安全验证</span>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="card-body"><div id="captcha"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal fade" id="dialogNew" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnSubmit" data-valid-modal="#dialogNew">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabel">新用户注册</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="userName">登陆名称:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-plus"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="userName" autocomplete="off" class="form-control" placeholder="登陆账号不可为空" userName="true" minlength="4" maxlength="16" remote="api/Register" data-remote-msg="此用户已存在" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="displayName">显示名称:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-circle-o"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="displayName" class="form-control" value="" placeholder="显示名称不可为空" maxlength="20" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="password" id="password" class="form-control" value="" placeholder="密码不可为空" maxlength="16" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="assurePassword">确认密码:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="password" id="assurePassword" class="form-control" value="" placeholder="确认密码" maxlength="16" equalTo="#password" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">申请理由:</label>
|
||||
<textarea id="description" class="form-control" placeholder="申请理由,500字以内" rows="3" maxlength="500" data-valid="true"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="btnSubmit">
|
||||
<i class="fa fa-save"></i>
|
||||
<span>提交</span>
|
||||
</button>
|
||||
<div class="slidercaptcha forgot reg card">
|
||||
<div class="card-header">
|
||||
<span>请完成安全验证</span>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="card-body"><div id="regcap"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="dialogForgot" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnForgot" data-valid-modal="#dialogForgot">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabelForgot">忘记密码</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="f_userName">登陆账号:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-plus"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="f_userName" autocomplete="off" class="form-control" placeholder="登陆账号不可为空" minlength="4" maxlength="16" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="f_displayName">显示名称:</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<span class="fa fa-user-circle-o"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="f_displayName" class="form-control" value="" placeholder="显示名称不可为空" maxlength="20" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="f_desc">申请理由:</label>
|
||||
<textarea id="f_desc" class="form-control" placeholder="申请理由,500字以内" rows="3" maxlength="500" data-valid="true">我是用户XXX,我的手机号是XXXXXX,由于密码忘记,请将密码重置为123,登录后我自行更改,谢谢管理员</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="btnForgot">
|
||||
<i class="fa fa-send-o"></i>
|
||||
<span>提交</span>
|
||||
</button>
|
||||
<div class="slidercaptcha forgot card">
|
||||
<div class="card-header">
|
||||
<span>请完成安全验证</span>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="card-body"><div id="forgotcap"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
.nav [data-toggle="dropdown"] {
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
transition: color .25s linear, border-color .25s linear;
|
||||
transition: all .25s linear;
|
||||
}
|
||||
|
||||
.nav .dropdown-menu {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
$(function () {
|
||||
var $imgUrl = $('#imgUrl');
|
||||
$(".container").autoCenter();
|
||||
|
||||
$("a[data-method]").on('click', function () {
|
||||
|
@ -94,8 +95,11 @@
|
|||
$('#captcha, #regcap, #forgotcap').sliderCaptcha({
|
||||
width: $.capWidth(),
|
||||
height: $.capHeight(),
|
||||
localImages: function () {
|
||||
return '../../lib/captcha/images/Pic' + Math.round(Math.random() * 4) + '.jpg';
|
||||
},
|
||||
setSrc: function () {
|
||||
return 'http://pocoafrro.bkt.clouddn.com/Pic' + Math.round(Math.random() * 136) + '.jpg';
|
||||
return $imgUrl.val() + 'Pic' + Math.round(Math.random() * 136) + '.jpg';
|
||||
},
|
||||
onSuccess: function () {
|
||||
var parent = this.parents('.slidercaptcha').removeClass('d-block');
|
||||
|
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
@ -1,4 +1,4 @@
|
|||
(function ($) {
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var SliderCaptcha = function (element, options) {
|
||||
|
@ -16,11 +16,15 @@
|
|||
PI: Math.PI,
|
||||
sliderL: 42, // 滑块边长
|
||||
sliderR: 9, // 滑块半径
|
||||
offset: 5, // 容错偏差
|
||||
loadingText: '正在加载中...',
|
||||
failedText: '再试一次',
|
||||
barText: '向右滑动填充拼图',
|
||||
repeatIcon: 'fa fa-repeat',
|
||||
maxLoadCount: 3
|
||||
maxLoadCount: 3,
|
||||
localImages: function () {
|
||||
return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
|
||||
}
|
||||
};
|
||||
|
||||
function Plugin(option) {
|
||||
|
@ -40,9 +44,9 @@
|
|||
|
||||
var _proto = SliderCaptcha.prototype;
|
||||
_proto.init = function () {
|
||||
this.initDOM()
|
||||
this.initImg()
|
||||
this.bindEvents()
|
||||
this.initDOM();
|
||||
this.initImg();
|
||||
this.bindEvents();
|
||||
};
|
||||
|
||||
_proto.initDOM = function () {
|
||||
|
@ -127,9 +131,6 @@
|
|||
var getRandomNumberByRange = function (start, end) {
|
||||
return Math.round(Math.random() * (end - start) + start);
|
||||
};
|
||||
var localImg = function () {
|
||||
return '../images/Pic' + Math.round(Math.random() * 4) + '.jpg';
|
||||
};
|
||||
var img = new Image();
|
||||
img.crossOrigin = "Anonymous";
|
||||
var loadCount = 0;
|
||||
|
@ -158,8 +159,8 @@
|
|||
that.text.text('加载失败').addClass('text-danger');
|
||||
return;
|
||||
}
|
||||
img.src = localImg();
|
||||
}
|
||||
img.src = that.options.localImages();
|
||||
};
|
||||
img.setSrc = function () {
|
||||
var src = '';
|
||||
loadCount = 0;
|
||||
|
@ -274,7 +275,7 @@
|
|||
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
|
||||
var left = parseInt(this.block.style.left);
|
||||
return {
|
||||
spliced: Math.abs(left - this.x) < 4,
|
||||
spliced: Math.abs(left - this.x) < this.options.offset,
|
||||
verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\Keys\Longbow.Utility.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\Keys\Longbow.Utility.snk" Link="Longbow.Utility.snk" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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="Microsoft.Data.Sqlite" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\Keys\Longbow.Utility.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\Keys\Longbow.Utility.snk" Link="Longbow.Utility.snk" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bootstrap.Security.DataAccess" Version="2.1.0" />
|
||||
<PackageReference Include="Longbow.Cache" Version="2.2.6" />
|
||||
<PackageReference Include="Longbow.Data" Version="2.2.8" />
|
||||
<PackageReference Include="Longbow.Web" Version="2.2.9" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -82,5 +82,11 @@ namespace Bootstrap.Client.DataAccess
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string RetrieveIconFolderPath() => (RetrieveDicts().FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == 0) ?? new BootstrapDict() { Code = "~/images/uploader/" }).Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获取验证码图床
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string RetrieveImagesLibUrl() => (RetrieveDicts().FirstOrDefault(d => d.Name == "验证码图床" && d.Category == "系统设置" && d.Define == 0) ?? new BootstrapDict() { Code = "https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/" }).Code;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,17 @@ namespace Bootstrap.Client.Models
|
|||
public NavigatorBarModel(ControllerBase controller) : base(controller.User.Identity)
|
||||
{
|
||||
Navigations = MenuHelper.RetrieveAppMenus(UserName, $"~/{controller.ControllerContext.ActionDescriptor.ControllerName}/{controller.ControllerContext.ActionDescriptor.ActionName}");
|
||||
ImageLibUrl = DictHelper.RetrieveImagesLibUrl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IEnumerable<BootstrapMenu> Navigations { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string ImageLibUrl { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
@{
|
||||
ViewData["Title"] = "测试网页";
|
||||
}
|
||||
<p>测试网页</p>
|
||||
@{
|
||||
ViewData["Title"] = "关于";
|
||||
}
|
||||
<p>我是关于网页</p>
|
|
@ -1,6 +1,20 @@
|
|||
@{
|
||||
ViewData["Title"] = "Home Page";
|
||||
@model NavigatorBarModel
|
||||
@{
|
||||
ViewData["Title"] = "前台首页";
|
||||
}
|
||||
<p>这是测试系统首页,欢迎测试使用</p>
|
||||
<p>点击右上角登录信息下拉菜单中的设置按钮进入<b>后台管理</b></p>
|
||||
<p class="text-danger"><b>由于本系统为演示系统,内部对一些敏感操作进行了限制操作,如一些特殊用户不能删除。</b></p>
|
||||
@section css {
|
||||
<link href="~/lib/captcha/slidercaptcha.css" rel="stylesheet" />
|
||||
}
|
||||
@section javascript {
|
||||
<script src="~/lib/captcha/longbow.slidercaptcha.js"></script>
|
||||
<script src="~/js/index.js"></script>
|
||||
}
|
||||
<p>这是开源后台管理框架前台系统首页,欢迎使用</p>
|
||||
<p>点击右上角登录信息下拉菜单中的设置按钮进入 <b>后台管理</b> 或者 <b><a href="@Model.SettingsUrl">直接进入</a></b></p>
|
||||
<p class="text-danger"><b>由于本系统为演示系统,内部对一些敏感操作进行了限制操作,如一些特殊用户不能删除。</b></p>
|
||||
<p>
|
||||
<button id="btnCaptcha" class="btn btn-success"><i class="fa fa-send-o"></i><span>点击我出现行为验证码</span></button>
|
||||
<div class="card d-none" style="padding: 10px; height: 222px;">
|
||||
<div id="captcha" data-imageLibUrl="@Model.ImageLibUrl"></div>
|
||||
</div>
|
||||
</p>
|
|
@ -0,0 +1,26 @@
|
|||
$(function () {
|
||||
var $captcha = $('#captcha');
|
||||
|
||||
$captcha.sliderCaptcha({
|
||||
localImages: function () {
|
||||
return '../../lib/captcha/images/Pic' + Math.round(Math.random() * 4) + '.jpg';
|
||||
},
|
||||
setSrc: function () {
|
||||
return $captcha.attr('data-imageLibUrl') + 'Pic' + Math.round(Math.random() * 136) + '.jpg';
|
||||
},
|
||||
onSuccess: function () {
|
||||
var that = this;
|
||||
setTimeout(() => {
|
||||
that.parent().removeClass('d-inline-block');
|
||||
that.sliderCaptcha('reset');
|
||||
$('.userinfo .dropdown-menu a:first')[0].click();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnCaptcha').on('click', function () {
|
||||
$('#captcha').parent().addClass('d-inline-block');
|
||||
});
|
||||
|
||||
$.footer();
|
||||
});
|
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 8.4 KiB |
|
@ -0,0 +1,293 @@
|
|||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var SliderCaptcha = function (element, options) {
|
||||
this.$element = $(element);
|
||||
this.options = $.extend({}, SliderCaptcha.DEFAULTS, options);
|
||||
this.$element.css({ 'position': 'relative', 'width': this.options.width + 'px', 'margin': '0 auto' });
|
||||
this.init();
|
||||
};
|
||||
|
||||
SliderCaptcha.VERSION = '1.0';
|
||||
SliderCaptcha.Author = 'argo@163.com';
|
||||
SliderCaptcha.DEFAULTS = {
|
||||
width: 280, // canvas宽度
|
||||
height: 155, // canvas高度
|
||||
PI: Math.PI,
|
||||
sliderL: 42, // 滑块边长
|
||||
sliderR: 9, // 滑块半径
|
||||
offset: 5, // 容错偏差
|
||||
loadingText: '正在加载中...',
|
||||
failedText: '再试一次',
|
||||
barText: '向右滑动填充拼图',
|
||||
repeatIcon: 'fa fa-repeat',
|
||||
maxLoadCount: 3,
|
||||
localImages: function () {
|
||||
return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
|
||||
}
|
||||
};
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this);
|
||||
var data = $this.data('lgb.SliderCaptcha');
|
||||
var options = typeof option === 'object' && option;
|
||||
|
||||
if (!data && /init|reset|verify/.test(option)) return;
|
||||
if (!data) $this.data('lgb.SliderCaptcha', data = new SliderCaptcha(this, options));
|
||||
if (typeof option === 'string') data[option]();
|
||||
});
|
||||
}
|
||||
|
||||
$.fn.sliderCaptcha = Plugin;
|
||||
$.fn.sliderCaptcha.Constructor = SliderCaptcha;
|
||||
|
||||
var _proto = SliderCaptcha.prototype;
|
||||
_proto.init = function () {
|
||||
this.initDOM();
|
||||
this.initImg();
|
||||
this.bindEvents();
|
||||
};
|
||||
|
||||
_proto.initDOM = function () {
|
||||
var createElement = function (tagName, className) {
|
||||
var elment = document.createElement(tagName);
|
||||
elment.className = className;
|
||||
return elment;
|
||||
};
|
||||
|
||||
var createCanvas = function (width, height) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
};
|
||||
|
||||
var canvas = createCanvas(this.options.width - 2, this.options.height) // 画布
|
||||
var block = canvas.cloneNode(true) // 滑块
|
||||
var sliderContainer = createElement('div', 'sliderContainer');
|
||||
var refreshIcon = createElement('i', 'refreshIcon ' + this.options.repeatIcon);
|
||||
var sliderMask = createElement('div', 'sliderMask');
|
||||
var sliderbg = createElement('div', 'sliderbg');
|
||||
var slider = createElement('div', 'slider');
|
||||
var sliderIcon = createElement('i', 'fa fa-arrow-right sliderIcon');
|
||||
var text = createElement('span', 'sliderText');
|
||||
|
||||
block.className = 'block'
|
||||
text.innerHTML = this.options.barText;
|
||||
|
||||
var el = this.$element;
|
||||
el.append($(canvas));
|
||||
el.append($(refreshIcon));
|
||||
el.append($(block));
|
||||
slider.appendChild(sliderIcon);
|
||||
sliderMask.appendChild(slider);
|
||||
sliderContainer.appendChild(sliderbg);
|
||||
sliderContainer.appendChild(sliderMask);
|
||||
sliderContainer.appendChild(text);
|
||||
el.append($(sliderContainer));
|
||||
|
||||
Object.assign(this, {
|
||||
canvas,
|
||||
block,
|
||||
sliderContainer: $(sliderContainer),
|
||||
refreshIcon,
|
||||
slider,
|
||||
sliderMask,
|
||||
sliderIcon,
|
||||
text: $(text),
|
||||
canvasCtx: canvas.getContext('2d'),
|
||||
blockCtx: block.getContext('2d')
|
||||
})
|
||||
};
|
||||
|
||||
_proto.initImg = function () {
|
||||
var that = this;
|
||||
var isIE = window.navigator.userAgent.indexOf('Trident') > -1;
|
||||
var L = this.options.sliderL + this.options.sliderR * 2 + 3; // 滑块实际边长
|
||||
var drawImg = function (ctx, operation) {
|
||||
var l = that.options.sliderL;
|
||||
var r = that.options.sliderR;
|
||||
var PI = that.options.PI;
|
||||
var x = that.x;
|
||||
var y = that.y;
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, y)
|
||||
ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)
|
||||
ctx.lineTo(x + l, y)
|
||||
ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)
|
||||
ctx.lineTo(x + l, y + l)
|
||||
ctx.lineTo(x, y + l)
|
||||
ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)
|
||||
ctx.lineTo(x, y)
|
||||
ctx.lineWidth = 2
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'
|
||||
ctx.stroke()
|
||||
ctx[operation]()
|
||||
ctx.globalCompositeOperation = isIE ? 'xor' : 'overlay'
|
||||
}
|
||||
|
||||
var getRandomNumberByRange = function (start, end) {
|
||||
return Math.round(Math.random() * (end - start) + start);
|
||||
};
|
||||
var img = new Image();
|
||||
img.crossOrigin = "Anonymous";
|
||||
var loadCount = 0;
|
||||
img.onload = function () {
|
||||
// 随机创建滑块的位置
|
||||
that.x = getRandomNumberByRange(L + 10, that.options.width - (L + 10));
|
||||
that.y = getRandomNumberByRange(10 + that.options.sliderR * 2, that.options.height - (L + 10));
|
||||
drawImg(that.canvasCtx, 'fill');
|
||||
drawImg(that.blockCtx, 'clip');
|
||||
|
||||
that.canvasCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
|
||||
that.blockCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
|
||||
var y = that.y - that.options.sliderR * 2 - 1;
|
||||
var ImageData = that.blockCtx.getImageData(that.x - 3, y, L, L);
|
||||
that.block.width = L;
|
||||
that.blockCtx.putImageData(ImageData, 0, y);
|
||||
that.text.text(that.text.attr('data-text'));
|
||||
};
|
||||
img.onerror = function () {
|
||||
loadCount++;
|
||||
if (window.location.protocol === 'file:') {
|
||||
loadCount = that.options.maxLoadCount;
|
||||
console.error("can't load pic resource file from File protocal. Please try http or https");
|
||||
}
|
||||
if (loadCount >= that.options.maxLoadCount) {
|
||||
that.text.text('加载失败').addClass('text-danger');
|
||||
return;
|
||||
}
|
||||
img.src = that.options.localImages();
|
||||
};
|
||||
img.setSrc = function () {
|
||||
var src = '';
|
||||
loadCount = 0;
|
||||
that.text.removeClass('text-danger');
|
||||
if ($.isFunction(that.options.setSrc)) src = that.options.setSrc();
|
||||
if (!src || src === '') src = 'https://picsum.photos/' + that.options.width + '/' + that.options.height + '/?image=' + Math.round(Math.random() * 20);
|
||||
if (isIE) { // IE浏览器无法通过img.crossOrigin跨域,使用ajax获取图片blob然后转为dataURL显示
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.onloadend = function (e) {
|
||||
var file = new FileReader(); // FileReader仅支持IE10+
|
||||
file.readAsDataURL(e.target.response);
|
||||
file.onloadend = function (e) {
|
||||
img.src = e.target.result;
|
||||
}
|
||||
}
|
||||
xhr.open('GET', src);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
} else img.src = src;
|
||||
};
|
||||
img.setSrc();
|
||||
this.text.attr('data-text', this.options.barText);
|
||||
this.text.text(this.options.loadingText);
|
||||
this.img = img
|
||||
};
|
||||
|
||||
_proto.clean = function () {
|
||||
this.canvasCtx.clearRect(0, 0, this.options.width, this.options.height);
|
||||
this.blockCtx.clearRect(0, 0, this.options.width, this.options.height);
|
||||
this.block.width = this.options.width;
|
||||
};
|
||||
|
||||
_proto.bindEvents = function () {
|
||||
var that = this;
|
||||
this.$element.on('selectstart', function () {
|
||||
return false;
|
||||
});
|
||||
|
||||
$(this.refreshIcon).on('click', function () {
|
||||
that.text.text(that.options.barText);
|
||||
that.reset();
|
||||
if ($.isFunction(that.options.onRefresh)) that.options.onRefresh.call(that.$element);
|
||||
});
|
||||
|
||||
var originX, originY, trail = [],
|
||||
isMouseDown = false
|
||||
|
||||
var handleDragStart = function (e) {
|
||||
if (that.text.hasClass('text-danger')) return;
|
||||
originX = e.clientX || e.touches[0].clientX;
|
||||
originY = e.clientY || e.touches[0].clientY;
|
||||
isMouseDown = true;
|
||||
};
|
||||
|
||||
var handleDragMove = function (e) {
|
||||
if (!isMouseDown) return false;
|
||||
var eventX = e.clientX || e.touches[0].clientX;
|
||||
var eventY = e.clientY || e.touches[0].clientY;
|
||||
var moveX = eventX - originX;
|
||||
var moveY = eventY - originY;
|
||||
if (moveX < 0 || moveX + 40 > that.options.width) return false;
|
||||
that.slider.style.left = (moveX - 1) + 'px';
|
||||
var blockLeft = (that.options.width - 40 - 20) / (that.options.width - 40) * moveX;
|
||||
that.block.style.left = blockLeft + 'px';
|
||||
|
||||
that.sliderContainer.addClass('sliderContainer_active');
|
||||
that.sliderMask.style.width = (moveX + 4) + 'px';
|
||||
trail.push(moveY);
|
||||
};
|
||||
|
||||
var handleDragEnd = function (e) {
|
||||
if (!isMouseDown) return false
|
||||
isMouseDown = false
|
||||
var eventX = e.clientX || e.changedTouches[0].clientX
|
||||
if (eventX == originX) return false
|
||||
that.sliderContainer.removeClass('sliderContainer_active');
|
||||
that.trail = trail
|
||||
var {
|
||||
spliced,
|
||||
verified
|
||||
} = that.verify()
|
||||
if (spliced && verified) {
|
||||
that.sliderContainer.addClass('sliderContainer_success');
|
||||
if ($.isFunction(that.options.onSuccess)) that.options.onSuccess.call(that.$element);
|
||||
} else {
|
||||
that.sliderContainer.addClass('sliderContainer_fail');
|
||||
if ($.isFunction(that.options.onFail)) that.options.onFail.call(that.$element);
|
||||
setTimeout(() => {
|
||||
that.text.text(that.options.failedText);
|
||||
that.reset();
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
this.slider.addEventListener('mousedown', handleDragStart);
|
||||
this.slider.addEventListener('touchstart', handleDragStart);
|
||||
document.addEventListener('mousemove', handleDragMove);
|
||||
document.addEventListener('touchmove', handleDragMove);
|
||||
document.addEventListener('mouseup', handleDragEnd);
|
||||
document.addEventListener('touchend', handleDragEnd);
|
||||
|
||||
document.addEventListener('mousedown', function () { return false; });
|
||||
document.addEventListener('touchstart', function () { return false; });
|
||||
};
|
||||
|
||||
_proto.verify = function () {
|
||||
var sum = function (x, y) { return x + y; };
|
||||
var square = function (x) { return x * x; };
|
||||
var arr = this.trail // 拖动时y轴的移动距离
|
||||
var average = arr.reduce(sum) / arr.length;
|
||||
var deviations = arr.map(function (x) { return x - average; });
|
||||
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
|
||||
var left = parseInt(this.block.style.left);
|
||||
return {
|
||||
spliced: Math.abs(left - this.x) < this.options.offset,
|
||||
verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
|
||||
}
|
||||
};
|
||||
|
||||
_proto.reset = function () {
|
||||
this.sliderContainer.removeClass('sliderContainer_fail sliderContainer_success');
|
||||
this.slider.style.left = 0
|
||||
this.block.style.left = 0
|
||||
this.sliderMask.style.width = 0
|
||||
this.clean()
|
||||
this.text.attr('data-text', this.text.text());
|
||||
this.text.text(this.options.loadingText);
|
||||
this.img.setSrc();
|
||||
};
|
||||
})(jQuery);
|
|
@ -0,0 +1,131 @@
|
|||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.block {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
background: #f7f9fa;
|
||||
color: #45494c;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sliderbg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: #f7f9fa;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #e6e8eb;
|
||||
}
|
||||
|
||||
.sliderContainer_active .slider {
|
||||
top: -1px;
|
||||
border: 1px solid #1991FA;
|
||||
}
|
||||
|
||||
.sliderContainer_active .sliderMask {
|
||||
border-width: 1px 0 1px 1px;
|
||||
}
|
||||
|
||||
.sliderContainer_success .slider {
|
||||
top: -1px;
|
||||
border: 1px solid #52CCBA;
|
||||
background-color: #52CCBA !important;
|
||||
}
|
||||
|
||||
.sliderContainer_success .sliderMask {
|
||||
border: 1px solid #52CCBA;
|
||||
border-width: 1px 0 1px 1px;
|
||||
background-color: #D2F4EF;
|
||||
}
|
||||
|
||||
.sliderContainer_success .sliderIcon:before {
|
||||
content: "\f00c";
|
||||
}
|
||||
|
||||
.sliderContainer_fail .slider {
|
||||
top: -1px;
|
||||
border: 1px solid #f57a7a;
|
||||
background-color: #f57a7a !important;
|
||||
}
|
||||
|
||||
.sliderContainer_fail .sliderMask {
|
||||
border: 1px solid #f57a7a;
|
||||
background-color: #fce1e1;
|
||||
border-width: 1px 0 1px 1px;
|
||||
}
|
||||
|
||||
.sliderContainer_fail .sliderIcon:before {
|
||||
content: "\f00d";
|
||||
}
|
||||
.sliderContainer_active .sliderText, .sliderContainer_success .sliderText, .sliderContainer_fail .sliderText {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sliderMask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 40px;
|
||||
border: 0 solid #1991FA;
|
||||
background: #D1E9FE;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
transition: background .2s linear;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.slider:hover {
|
||||
background: #1991FA;
|
||||
}
|
||||
|
||||
.slider:hover .sliderIcon {
|
||||
background-position: 0 -13px;
|
||||
}
|
||||
|
||||
.sliderText {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sliderIcon {
|
||||
|
||||
}
|
||||
|
||||
.refreshIcon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
margin: 6px;
|
||||
color: rgba(0,0,0,.25);
|
||||
font-size: 1rem;
|
||||
z-index: 5;
|
||||
transition: color .3s linear;
|
||||
}
|
||||
|
||||
.refreshIcon:hover {
|
||||
color: #6c757d;
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.7.3" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\Keys\Longbow.Utility.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\Keys\Longbow.Utility.snk" Link="Longbow.Utility.snk" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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.Cache" Version="2.2.5" />
|
||||
<PackageReference Include="Longbow" Version="2.2.8" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\Keys\Longbow.Utility.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\Keys\Longbow.Utility.snk" Link="Longbow.Utility.snk" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bootstrap.Security.DataAccess" Version="2.1.0" />
|
||||
<PackageReference Include="Longbow.Data" Version="2.2.8" />
|
||||
<PackageReference Include="Longbow.Security.Cryptography" Version="1.3.0" />
|
||||
<PackageReference Include="Longbow.Web" Version="2.2.9" />
|
||||
<PackageReference Include="Longbow.Cache" Version="2.2.6" />
|
||||
<PackageReference Include="Longbow" Version="2.2.9" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,180 +1,186 @@
|
|||
using Bootstrap.Security;
|
||||
using Bootstrap.Security.DataAccess;
|
||||
using Longbow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bootstrap.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Dict : BootstrapDict
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除字典中的数据
|
||||
/// </summary>
|
||||
/// <param name="value">需要删除的IDs</param>
|
||||
/// <returns></returns>
|
||||
public virtual bool Delete(IEnumerable<string> value)
|
||||
{
|
||||
if (!value.Any()) return true;
|
||||
var ids = string.Join(",", value);
|
||||
string sql = $"where ID in ({ids})";
|
||||
DbManager.Create().Delete<BootstrapDict>(sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存新建/更新的字典信息
|
||||
/// </summary>
|
||||
/// <param name="dict"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool Save(BootstrapDict dict)
|
||||
{
|
||||
if (dict.Category.Length > 50) dict.Category = dict.Category.Substring(0, 50);
|
||||
if (dict.Name.Length > 50) dict.Name = dict.Name.Substring(0, 50);
|
||||
if (dict.Code.Length > 50) dict.Code = dict.Code.Substring(0, 50);
|
||||
|
||||
DbManager.Create().Save(dict);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存网站个性化设置
|
||||
/// </summary>
|
||||
/// <param name="dict"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool SaveSettings(BootstrapDict dict)
|
||||
{
|
||||
DbManager.Create().Update<BootstrapDict>("set Code = @Code where Category = @Category and Name = @Name", dict);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字典分类名称
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<string> RetrieveCategories() => DictHelper.RetrieveDicts().OrderBy(d => d.Category).Select(d => d.Category).Distinct();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveWebTitle() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站标题" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "后台管理系统" }).Code;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveWebFooter() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站页脚" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "2016 © 通用后台管理系统" }).Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得系统中配置的可以使用的网站样式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<BootstrapDict> RetrieveThemes() => DictHelper.RetrieveDicts().Where(d => d.Category == "网站样式");
|
||||
|
||||
/// <summary>
|
||||
/// 获得网站设置中的当前样式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveActiveTheme()
|
||||
{
|
||||
var theme = DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "使用样式" && d.Category == "当前样式" && d.Define == 0);
|
||||
return theme == null ? string.Empty : (theme.Code.Equals("site.css", StringComparison.OrdinalIgnoreCase) ? string.Empty : theme.Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取头像路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveIconFolderPath() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == 0) ?? new BootstrapDict { Code = "~/images/uploader/" }).Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得默认的前台首页地址,默认为~/Home/Index
|
||||
/// </summary>
|
||||
/// <param name="appCode"></param>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveHomeUrl(string appCode)
|
||||
{
|
||||
// https://gitee.com/LongbowEnterprise/dashboard/issues?id=IS0WK
|
||||
var url = "~/Home/Index";
|
||||
var dicts = DictHelper.RetrieveDicts();
|
||||
if (appCode != "0")
|
||||
{
|
||||
var appUrl = dicts.FirstOrDefault(d => d.Name.Equals(appCode, StringComparison.OrdinalIgnoreCase) && d.Category == "应用首页" && d.Define == 0)?.Code;
|
||||
if (!string.IsNullOrEmpty(appUrl))
|
||||
{
|
||||
url = appUrl;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
var defaultUrl = dicts.FirstOrDefault(d => d.Name == "前台首页" && d.Category == "网站设置" && d.Define == 0)?.Code;
|
||||
if (!string.IsNullOrEmpty(defaultUrl)) url = defaultUrl;
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<KeyValuePair<string, string>> RetrieveApps() => DictHelper.RetrieveDicts().Where(d => d.Category == "应用程序" && d.Define == 0).Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).OrderBy(d => d.Key);
|
||||
|
||||
/// <summary>
|
||||
/// 通过数据库获得所有字典表配置信息,缓存Key=DictHelper-RetrieveDicts
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<BootstrapDict> RetrieveDicts() => DbHelper.RetrieveDicts();
|
||||
|
||||
/// <summary>
|
||||
/// 程序异常时长 默认1月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveExceptionsLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "程序异常保留时长" && d.Define == 0)?.Code, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志时长 默认12月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "操作日志保留时长" && d.Define == 0)?.Code, 12);
|
||||
|
||||
/// <summary>
|
||||
/// 登录日志时长 默认12月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveLoginLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "登录日志保留时长" && d.Define == 0)?.Code, 12);
|
||||
|
||||
/// <summary>
|
||||
/// Cookie保存时长 默认7天
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveCookieExpiresPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "Cookie保留时长" && d.Define == 0)?.Code, 7);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 IP地理位置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string RetrieveLocaleIPSvr() => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "IP地理位置接口" && d.Define == 0)?.Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得 项目是否获取登录地点 默认为false
|
||||
/// </summary>
|
||||
/// <param name="ipSvr">服务提供名称</param>
|
||||
/// <returns></returns>
|
||||
public string RetrieveLocaleIPSvrUrl(string ipSvr) => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == ipSvr && d.Define == 0)?.Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得 访问日志保留时长 默认为1个月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveAccessLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "访问日志保留时长" && d.Define == 0)?.Code, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 是否为演示系统 默认为 false 不是演示系统
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool RetrieveSystemModel() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "演示系统" && d.Define == 0)?.Code, "0") == "1";
|
||||
}
|
||||
}
|
||||
using Bootstrap.Security;
|
||||
using Bootstrap.Security.DataAccess;
|
||||
using Longbow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bootstrap.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Dict : BootstrapDict
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除字典中的数据
|
||||
/// </summary>
|
||||
/// <param name="value">需要删除的IDs</param>
|
||||
/// <returns></returns>
|
||||
public virtual bool Delete(IEnumerable<string> value)
|
||||
{
|
||||
if (!value.Any()) return true;
|
||||
var ids = string.Join(",", value);
|
||||
string sql = $"where ID in ({ids})";
|
||||
DbManager.Create().Delete<BootstrapDict>(sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存新建/更新的字典信息
|
||||
/// </summary>
|
||||
/// <param name="dict"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool Save(BootstrapDict dict)
|
||||
{
|
||||
if (dict.Category.Length > 50) dict.Category = dict.Category.Substring(0, 50);
|
||||
if (dict.Name.Length > 50) dict.Name = dict.Name.Substring(0, 50);
|
||||
if (dict.Code.Length > 50) dict.Code = dict.Code.Substring(0, 50);
|
||||
|
||||
DbManager.Create().Save(dict);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存网站个性化设置
|
||||
/// </summary>
|
||||
/// <param name="dict"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool SaveSettings(BootstrapDict dict)
|
||||
{
|
||||
DbManager.Create().Update<BootstrapDict>("set Code = @Code where Category = @Category and Name = @Name", dict);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字典分类名称
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<string> RetrieveCategories() => DictHelper.RetrieveDicts().OrderBy(d => d.Category).Select(d => d.Category).Distinct();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveWebTitle() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站标题" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "后台管理系统" }).Code;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveWebFooter() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站页脚" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "2016 © 通用后台管理系统" }).Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得系统中配置的可以使用的网站样式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<BootstrapDict> RetrieveThemes() => DictHelper.RetrieveDicts().Where(d => d.Category == "网站样式");
|
||||
|
||||
/// <summary>
|
||||
/// 获得网站设置中的当前样式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveActiveTheme()
|
||||
{
|
||||
var theme = DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "使用样式" && d.Category == "当前样式" && d.Define == 0);
|
||||
return theme == null ? string.Empty : (theme.Code.Equals("site.css", StringComparison.OrdinalIgnoreCase) ? string.Empty : theme.Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取头像路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveIconFolderPath() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == 0) ?? new BootstrapDict { Code = "~/images/uploader/" }).Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得默认的前台首页地址,默认为~/Home/Index
|
||||
/// </summary>
|
||||
/// <param name="appCode"></param>
|
||||
/// <returns></returns>
|
||||
public virtual string RetrieveHomeUrl(string appCode)
|
||||
{
|
||||
// https://gitee.com/LongbowEnterprise/dashboard/issues?id=IS0WK
|
||||
var url = "~/Home/Index";
|
||||
var dicts = DictHelper.RetrieveDicts();
|
||||
if (appCode != "0")
|
||||
{
|
||||
var appUrl = dicts.FirstOrDefault(d => d.Name.Equals(appCode, StringComparison.OrdinalIgnoreCase) && d.Category == "应用首页" && d.Define == 0)?.Code;
|
||||
if (!string.IsNullOrEmpty(appUrl))
|
||||
{
|
||||
url = appUrl;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
var defaultUrl = dicts.FirstOrDefault(d => d.Name == "前台首页" && d.Category == "网站设置" && d.Define == 0)?.Code;
|
||||
if (!string.IsNullOrEmpty(defaultUrl)) url = defaultUrl;
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<KeyValuePair<string, string>> RetrieveApps() => DictHelper.RetrieveDicts().Where(d => d.Category == "应用程序" && d.Define == 0).Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).OrderBy(d => d.Key);
|
||||
|
||||
/// <summary>
|
||||
/// 通过数据库获得所有字典表配置信息,缓存Key=DictHelper-RetrieveDicts
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<BootstrapDict> RetrieveDicts() => DbHelper.RetrieveDicts();
|
||||
|
||||
/// <summary>
|
||||
/// 程序异常时长 默认1月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveExceptionsLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "程序异常保留时长" && d.Define == 0)?.Code, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志时长 默认12月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "操作日志保留时长" && d.Define == 0)?.Code, 12);
|
||||
|
||||
/// <summary>
|
||||
/// 登录日志时长 默认12月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveLoginLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "登录日志保留时长" && d.Define == 0)?.Code, 12);
|
||||
|
||||
/// <summary>
|
||||
/// Cookie保存时长 默认7天
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveCookieExpiresPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "Cookie保留时长" && d.Define == 0)?.Code, 7);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 IP地理位置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string RetrieveLocaleIPSvr() => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "IP地理位置接口" && d.Define == 0)?.Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得 项目是否获取登录地点 默认为false
|
||||
/// </summary>
|
||||
/// <param name="ipSvr">服务提供名称</param>
|
||||
/// <returns></returns>
|
||||
public string RetrieveLocaleIPSvrUrl(string ipSvr) => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == ipSvr && d.Define == 0)?.Code;
|
||||
|
||||
/// <summary>
|
||||
/// 获得 访问日志保留时长 默认为1个月
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int RetrieveAccessLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "访问日志保留时长" && d.Define == 0)?.Code, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 是否为演示系统 默认为 false 不是演示系统
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool RetrieveSystemModel() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "演示系统" && d.Define == 0)?.Code, "0") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// 获得 验证码图床地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string RetrieveImagesLibUrl() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "验证码图床" && d.Define == 0)?.Code, "https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
@ -183,5 +200,11 @@ namespace Bootstrap.DataAccess
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool RetrieveSystemModel() => DbContextManager.Create<Dict>().RetrieveSystemModel();
|
||||
|
||||
/// <summary>
|
||||
/// 获得验证码图床地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string RetrieveImagesLibUrl() => DbContextManager.Create<Dict>().RetrieveImagesLibUrl();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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历史访问记录
|
||||
|
|
|
@ -44,6 +44,7 @@ INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设
|
|||
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0)
|
||||
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0)
|
||||
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'演示系统', '0', 0)
|
||||
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0)
|
||||
|
||||
DELETE FROM Navigations Where Category = N'0'
|
||||
INSERT [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, N'后台管理', 10, N'fa fa-gear', N'~/Admin/Index', N'0')
|
||||
|
|
|
@ -341,5 +341,12 @@
|
|||
"Name": "演示系统",
|
||||
"Code": "0",
|
||||
"Define": NumberInt(0)
|
||||
},
|
||||
{
|
||||
"_id": ObjectId("5bd6c73d5fa31256f77e4a49"),
|
||||
"Category": "系统设置",
|
||||
"Name": "验证码图床",
|
||||
"Code": "https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/",
|
||||
"Define": NumberInt(0)
|
||||
}
|
||||
]
|
|
@ -41,6 +41,7 @@ INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'IP地
|
|||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0);
|
||||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0);
|
||||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '演示系统', '0', 0);
|
||||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0);
|
||||
|
||||
DELETE FROM Navigations Where Category = '0';
|
||||
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (0, '后台管理', 10, 'fa fa-gear', '~/Admin/Index', '0');
|
||||
|
|
|
@ -40,6 +40,7 @@ INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'IP地
|
|||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0);
|
||||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0);
|
||||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '演示系统', '0', 0);
|
||||
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0);
|
||||
|
||||
DELETE FROM Navigations Where Category = '0';
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '后台管理', 10, 'fa fa-gear', '~/Admin/Index', '0');
|
||||
|
|
|
@ -39,6 +39,7 @@ INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置
|
|||
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', 'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0);
|
||||
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', 'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0);
|
||||
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', '演示系统', '0', 0);
|
||||
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', '验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0);
|
||||
|
||||
DELETE FROM Navigations Where Category = '0';
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '后台管理', 10, 'fa fa-gear', '~/Admin/Index', '0');
|
||||
|
@ -136,6 +137,8 @@ INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category
|
|||
INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], [Application]) VALUES (0, '测试页面', 20, 'fa fa-fa', '#', '1', 2);
|
||||
INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], [Application]) VALUES (last_insert_rowid(), '关于', 10, 'fa fa-fa', '~/Home/About', '1', 2);
|
||||
|
||||
INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], [Application]) VALUES (0, '返回码云', 20, 'fa fa-fa', 'https://gitee.com/LongbowEnterprise/BootstrapAdmin', '1', 2);
|
||||
|
||||
-- 菜单授权
|
||||
DELETE FROM NavigationRole Where NavigationID in (Select ID From Navigations Where [Application] = 2);
|
||||
INSERT INTO NavigationRole SELECT NULL, ID, 2 FROM Navigations Where [Application] = 2;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.2.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
|
||||
<PackageReference Include="MySql.Data" Version="8.0.15" />
|
||||
<PackageReference Include="Npgsql" Version="4.0.4" />
|
||||
<PackageReference Include="Npgsql" Version="4.0.6" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|