Merge branch 'dev'

This commit is contained in:
Argo Zhang 2019-04-13 18:10:22 +08:00
commit c4a2431e70
48 changed files with 1702 additions and 1547 deletions

Binary file not shown.

View File

@ -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}";
}

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

@ -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; }
}
}

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

@ -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">&times;</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">&times;</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">&times;</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">&times;</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>

View File

@ -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 {

View File

@ -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');

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -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轴上下没有波动可能非人为操作
}
};

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -1,4 +1,4 @@
@{
ViewData["Title"] = "测试网页";
}
<p>测试网页</p>
@{
ViewData["Title"] = "关于";
}
<p>我是关于网页</p>

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -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();
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -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);

View File

@ -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;
}

View File

@ -11,7 +11,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.7.3" />
<PackageReference Include="MongoDB.Driver" Version="2.8.0" />
</ItemGroup>
<ItemGroup>

View File

@ -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>

View File

@ -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/");
}
}

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>
@ -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();
}
}

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

@ -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')

View File

@ -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)
}
]

View File

@ -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');

View File

@ -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');

View File

@ -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;

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
{

View File

@ -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>