增加功能:用户管理页面增加重置密码功能 #IS7V2

* 此功能与首页忘记密码相关联
This commit is contained in:
Argo Zhang 2019-03-05 13:05:19 +08:00
parent d69e93c8f2
commit 2050c54eca
14 changed files with 325 additions and 16 deletions

View File

@ -1,9 +1,12 @@
using Bootstrap.DataAccess;
using Longbow.Web;
using Longbow.Web.SignalR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Bootstrap.Admin.Controllers.Api
@ -41,15 +44,20 @@ namespace Bootstrap.Admin.Controllers.Api
return ret;
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <param name="user"></param>
[HttpPut("{userName}")]
public bool Put(string userName, [FromBody]User user) => UserHelper.ResetPassword(userName, user.Password);
/// <summary>
/// 忘记密码调用
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[HttpPut]
public bool Put([FromBody]User user)
{
return UserHelper.ForgotPassword(user.UserName, user.DisplayName, user.Description);
}
public bool Put([FromBody]ResetUser user) => UserHelper.ForgotPassword(user.UserName, user.DisplayName, user.Reason);
}
}

View File

@ -63,7 +63,8 @@ namespace Bootstrap.Admin.Query
u.RegisterTime,
u.ApprovedTime,
u.ApprovedBy,
u.Description
u.Description,
u.IsReset
});
return ret;
}

View File

@ -71,4 +71,38 @@
@section customModal {
@await Html.PartialAsync("RoleConfig")
@await Html.PartialAsync("GroupConfig")
<div class="modal fade" id="dialogReset" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myResetModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnReset" data-valid-modal="#dialogReset">
<div class="modal-header">
<h5 class="modal-title" id="myResetModalLabel">重置密码窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form class="form-inline">
<div class="row" id="resetForm">
<div class="form-group col-sm-6">
<label class="control-label" for="password">登录密码</label>
<input type="password" class="form-control" id="resetPassword" placeholder="不可为空50字以内" maxlength="50" data-valid="true" />
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="confirm">确认密码</label>
<input type="password" class="form-control" id="resetConfirm" placeholder="与登陆密码一致50字以内" maxlength="50" equalTo="#resetPassword" data-valid="true" />
</div>
</div>
</form>
</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="btnReset">
<i class="fa fa-save"></i>
<span>保存</span>
</button>
</div>
</div>
</div>
</div>
}

View File

@ -34,8 +34,8 @@
<script src="~/lib/toastr.js/toastr.min.js"></script>
<script src="~/lib/dcjqaccordion/js/jquery.cookie.js"></script>
<script src="~/js/common-scripts.js" asp-append-version="true"></script>
<script src="~/js/log.js" asp-append-version="true"></script>
@RenderSection("javascript", false)
<script src="~/js/log.js" asp-append-version="true"></script>
}
@await Html.PartialAsync("navigator")
<section id="main-content" class="main-content">

View File

@ -30,6 +30,9 @@
},
'#btnSubmitMenu': function () {
this.log({ crud: '分配菜单' });
},
'#btnReset': function () {
this.log({ crud: '重置密码' });
}
}
};

View File

@ -29,7 +29,7 @@
$('#btnForgot').on('click', function () {
$.bc({
url: "api/Register",
data: { UserName: $('#f_userName').val(), DisplayName: $('#f_displayName').val(), Description: $('#f_desc').val() },
data: { UserName: $('#f_userName').val(), DisplayName: $('#f_displayName').val(), Reason: $('#f_desc').val() },
modal: '#dialogForgot',
method: "put",
callback: function (result) {

View File

@ -5,8 +5,11 @@
var $dialogGroup = $("#dialogGroup");
var $dialogGroupHeader = $('#myGroupModalLabel');
var $dialogGroupForm = $('#groupForm');
var $dialogReset = $('#dialogReset');
var $dialogResetHeader = $('#myResetModalLabel');
var $table = $('table');
$('table').lgbTable({
$table.lgbTable({
url: User.url,
dataBinder: {
map: {
@ -62,6 +65,9 @@
return $(element).val();
}).toArray();
$.bc({ id: userId, url: Group.url, method: 'put', data: groupIds, query: { type: "user" }, title: Group.title, modal: '#dialogGroup' });
},
'#btnReset': function (row) {
$.bc({ id: row.UserName, url: 'api/Register', method: 'put', data: { password: $('#resetPassword').val() }, modal: "#dialogReset", title: "重置密码", callback: function (result) { if (result) $table.bootstrapTable('refresh'); } });
}
},
callback: function (data) {
@ -86,7 +92,20 @@
{ title: "注册时间", field: "RegisterTime", sortable: true },
{ title: "授权时间", field: "ApprovedTime", sortable: true },
{ title: "授权人", field: "ApprovedBy", sortable: true },
{ title: "说明", field: "Description", sortable: false }
{ title: "说明", field: "Description", sortable: false },
{
title: "操作", field: "IsReset", formatter: function (value, row, index) {
return value === 1 ? '<button class="reset btn btn-danger"><i class="fa fa-remove"></i><span>重置</span></button>' : '';
},
events: {
'click .reset': function (e, value, row, index) {
$table.bootstrapTable('uncheckAll');
$table.bootstrapTable('check', index);
$dialogResetHeader.text($.format("{0} - 重置密码窗口", row.UserName));
$dialogReset.modal('show');
}
}
}
]
}
});

View File

@ -124,6 +124,14 @@ namespace Bootstrap.DataAccess
/// <returns></returns>
public static bool ChangePassword(string userName, string password, string newPass) => DbContextManager.Create<User>().ChangePassword(userName, password, newPass);
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <returns></returns>
public static bool ResetPassword(string userName, string password) => DbContextManager.Create<User>().ResetPassword(userName, password);
/// <summary>
///
/// </summary>
@ -131,11 +139,7 @@ namespace Bootstrap.DataAccess
/// <param name="displayName"></param>
/// <param name="desc"></param>
/// <returns></returns>
public static bool ForgotPassword(string userName, string displayName, string desc)
{
// UNDONE 忘记密码涉及到安全问题,防止用户恶意重置其他用户,待定
return true;
}
public static bool ForgotPassword(string userName, string displayName, string desc) => DbContextManager.Create<User>().ForgotPassword(userName, displayName, desc);
/// <summary>
///
@ -236,5 +240,12 @@ namespace Bootstrap.DataAccess
/// <param name="userName"></param>
/// <returns></returns>
public static BootstrapUser RetrieveUserByUserName(string userName) => CacheManager.GetOrAdd(string.Format("{0}-{1}", RetrieveUsersByNameDataKey, userName), k => DbContextManager.Create<User>().RetrieveUserByUserName(userName), RetrieveUsersByNameDataKey);
/// <summary>
/// 通过登录账号获得用户信息
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public static ResetUser RetrieveResetUserByUserName(string userName) => DbContextManager.Create<ResetUser>().RetrieveUserByUserName(userName);
}
}

View File

@ -0,0 +1,62 @@
using PetaPoco;
using System;
namespace Bootstrap.DataAccess
{
/// <summary>
///
/// </summary>
[TableName("ResetUsers")]
public class ResetUser
{
/// <summary>
/// 获得/设置 用户主键ID
/// </summary>
public string Id { get; set; }
/// <summary>
///
/// </summary>
public string UserName { get; set; }
/// <summary>
///
/// </summary>
public string DisplayName { get; set; }
/// <summary>
///
/// </summary>
public string Reason { get; set; }
/// <summary>
///
/// </summary>
public DateTime ResetTime { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public virtual bool Save()
{
var db = DbManager.Create();
db.Save(this);
return true;
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public virtual ResetUser RetrieveUserByUserName(string userName) => DbManager.Create().FirstOrDefault<ResetUser>("where UserName = @0 order by ResetTime desc", userName);
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public virtual void DeleteByUserName(string userName) => DbManager.Create().Delete<ResetUser>("where UserName = @0", userName);
}
}

View File

@ -2,6 +2,7 @@
using Bootstrap.Security.DataAccess;
using Longbow.Data;
using Longbow.Security.Cryptography;
using PetaPoco;
using System;
using System.Collections.Generic;
using System.Linq;
@ -68,6 +69,12 @@ namespace Bootstrap.DataAccess
/// </summary>
public string NewPassword { get; set; }
/// <summary>
/// 获得/设置 是否重置密码
/// </summary>
[Ignore]
public int IsReset { get; set; }
/// <summary>
/// 验证用户登陆账号与密码正确
/// </summary>
@ -114,7 +121,7 @@ namespace Bootstrap.DataAccess
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual IEnumerable<User> Retrieves() => DbManager.Create().Fetch<User>("select ID, UserName, DisplayName, RegisterTime, ApprovedTime, ApprovedBy, Description from Users Where ApprovedTime is not null");
public virtual IEnumerable<User> Retrieves() => DbManager.Create().Fetch<User>("select u.ID, u.UserName, u.DisplayName, RegisterTime, ApprovedTime, ApprovedBy, Description, ru.IsReset from Users u left join (select 1 as IsReset, UserName from ResetUsers group by UserName) ru on u.UserName = ru.UserName Where ApprovedTime is not null");
/// <summary>
/// 查询所有的新注册用户
@ -149,6 +156,56 @@ namespace Bootstrap.DataAccess
return ret;
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <returns></returns>
public virtual bool ResetPassword(string userName, string password)
{
var ret = false;
var resetUser = UserHelper.RetrieveResetUserByUserName(userName);
if (resetUser == null) return ret;
string sql = "set Password = @0, PassSalt = @1 where UserName = @2";
var passSalt = LgbCryptography.GenerateSalt();
var newPassword = LgbCryptography.ComputeHash(password, passSalt);
var db = DbManager.Create();
try
{
db.BeginTransaction();
ret = db.Update<User>(sql, newPassword, passSalt, userName) == 1;
if (ret) db.Execute("delete from ResetUsers where UserName = @0", userName);
db.CompleteTransaction();
}
catch (Exception ex)
{
db.AbortTransaction();
throw ex;
}
return ret;
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <param name="displayName"></param>
/// <param name="desc"></param>
/// <returns></returns>
public virtual bool ForgotPassword(string userName, string displayName, string desc)
{
ResetUser user = new ResetUser()
{
UserName = userName,
DisplayName = displayName,
Reason = desc,
ResetTime = DateTime.Now
};
return user.Save();
}
/// <summary>
/// 新建前台User View调用/注册用户调用
/// </summary>

View File

@ -507,3 +507,30 @@ GO
SET ANSI_PADDING OFF
GO
/****** Object: Table [dbo].[ResetUsers] Script Date: 03/05/2019 12:28:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[ResetUsers](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserName] [varchar](50) NOT NULL,
[DisplayName] [nvarchar](50) NOT NULL,
[Reason] [nvarchar](500) NOT NULL,
[ResetTime] [datetime] NOT NULL,
CONSTRAINT [PK_ResetUsers] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO

View File

@ -76,10 +76,15 @@ namespace Bootstrap.Admin
public HttpClient CreateClient(string baseAddress)
{
var client = CreateDefaultClient(new Uri($"http://localhost/{baseAddress}/"), new RedirectHandler(7), new CookieContainerHandler(_cookie));
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15");
return client;
}
protected override void ConfigureClient(HttpClient client)
{
base.ConfigureClient(client);
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15");
}
private readonly CookieContainer _cookie = new CookieContainer();
protected override void ConfigureWebHost(IWebHostBuilder builder)

View File

@ -25,5 +25,26 @@ namespace Bootstrap.Admin.Api
nusr.Delete(nusr.RetrieveNewUsers().Where(u => u.UserName == nusr.UserName).Select(u => u.Id));
}
[Fact]
public async void Put_Ok()
{
var user = new ResetUser() { DisplayName = "UnitTest", UserName = "UnitTest", Reason = "UnitTest" };
var resp = await Client.PutAsJsonAsync<ResetUser, bool>(user);
Assert.True(resp);
}
[Fact]
public async void Put_UserName()
{
var user = new User() { Password = "1" };
var resp = await Client.PutAsJsonAsync<User, bool>("UnitTest", user);
Assert.False(resp);
// 重置Admin密码
await Client.PutAsJsonAsync<ResetUser, bool>(new ResetUser { UserName = "Admin", DisplayName = "Administrator", Reason = "UnitTest" });
resp = await Client.PutAsJsonAsync<User, bool>("Admin", new User() { Password = "123789" });
Assert.True(resp);
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using Xunit;
namespace Bootstrap.DataAccess
{
[Collection("SQLServerContext")]
public class ResetUserTest
{
[Fact]
public void Save_Ok()
{
var resetUser = new ResetUser()
{
UserName = "UnitTest",
Reason = "UnitTest",
DisplayName = "UnitTest",
ResetTime = DateTime.Now
};
var db = DbManager.Create();
db.Save(resetUser);
var count = db.ExecuteScalar<int>("select count(Id) from ResetUsers");
Assert.True(count > 0);
}
[Fact]
public void RetrieveUserByUserName_Ok()
{
var resetUser = new ResetUser()
{
UserName = "UnitTest",
Reason = "UnitTest",
DisplayName = "UnitTest",
ResetTime = DateTime.Now
};
var db = DbManager.Create();
db.Save(resetUser);
var user = resetUser.RetrieveUserByUserName(resetUser.UserName);
Assert.Equal("UnitTest", user.UserName);
Assert.Equal("UnitTest", user.DisplayName);
}
[Fact]
public void DeleteByUserName_Ok()
{
var resetUser = new ResetUser()
{
UserName = "UnitTest",
Reason = "UnitTest",
DisplayName = "UnitTest",
ResetTime = DateTime.Now
};
var db = DbManager.Create();
db.Save(resetUser);
resetUser.DeleteByUserName(resetUser.UserName);
var count = db.ExecuteScalar<int>("select count(Id) from ResetUsers");
Assert.Equal(0, count);
}
}
}