1 Services.Customers.CustomerService.GetCustomerByEmailAsync
/// name="email">1个指定的电子邮箱字符串。
///
/// 【异步通过电子邮箱获取用户】
///
/// 摘要:
/// 直接从用户表中获用户实体的1个指定实例。
/// 说明:
/// 该方法没有定义缓存操作,更没有定义缓存的移除操作。
///
///
/// 返回:
/// 用户实体的1个指定实例。
///
///
public virtual async Task<Customer> GetCustomerByEmailAsync(string email)
{
if (string.IsNullOrWhiteSpace(email))
return null;
var query = from c in _customerRepository.Table
orderby c.Id
where c.Email == email
select c;
var customer = await query.FirstOrDefaultAsync();
return customer;
}
2 Services.Customers.CustomerService.GetCustomerByPhoneAsync
/// name="phone">1个指定的手机号。
///
/// 【异步通过手机号获取用户】
///
/// 摘要:
/// 直接从用户表中获用户实体的1个指定实例。
/// 说明:
/// 该方法没有定义缓存操作,更没有定义缓存的移除操作。
///
///
/// 返回:
/// 用户实体的1个指定实例。
///
///
public virtual async Task<Customer> GetCustomerByPhoneAsync(string phone)
{
if (string.IsNullOrWhiteSpace(phone))
return null;
var query = from c in _customerRepository.Table
orderby c.Id
where c.Phone == phone
select c;
var customer = await query.FirstOrDefaultAsync();
return customer;
}
3 Services.Customers.CustomerService.InsertCustomerAsync
/// name="customer">用户实体的1个指定实例。
///
/// 【异步插入用户】
///
/// 摘要:
/// 把用户实体的1个指定实例持久化插入到用户表中后,并从缓存数据库中移除与用户相关的所有缓存项。
///
///
public virtual async Task InsertCustomerAsync(Customer customer)
{
await _customerRepository.InsertAsync(customer);
}
4 Services.Customers.CustomerService.UpdateCustomerAsync
/// name="customer">用户实体的1个指定实例。
///
/// 【异步更新用户】
///
/// 摘要:
/// 把用户实体的1个指定实例持久化更新到用户表中后,并从缓存数据库中移除与用户实体相关的所有缓存项。
///
///
public virtual async Task UpdateCustomerAsync(Customer customer)
{
await _customerRepository.UpdateAsync(customer);
}
5 Services.Customers.CustomerService.DeleteCustomerAsync
/// name="customer">用户实体的1个指定实例。
///
/// 【异步逻辑删除用户】
///
/// 摘要:
/// 把用户实体的1个指定实例从用户表中逻辑删除后,并从缓存数据库中移除与用户实体相关的所有缓存项。
///
///
public virtual async Task DeleteCustomerAsync(Customer customer)
{
if (customer == null)
throw new ArgumentNullException(nameof(customer));
customer.Deleted = true;
await _customerRepository.UpdateAsync(customer, false);
await _customerRepository.DeleteAsync(customer);
}
6 Services.Customers.CustomerService.AddCustomerRoleMappingAsync
/// name="customerRoleMapping">用户角色映射实体的1个指定实例。
///
/// 【异步插入用户角色映射】
///
/// 摘要:
/// 把用户角色映射实体的1个指定实例持久化插入到用户角色映射表中后,并从缓存数据库中移除与用户角色映射相关的所有缓存项。
///
///
public async Task AddCustomerRoleMappingAsync(CustomerRoleMapping customerRoleMapping)
{
await _customerRoleMappingRepository.InsertAsync(customerRoleMapping);
}
7 Services.Customers.CustomerService.RemoveCustomerRoleMappingAsync
/// name="customer">用户实体的1个指定实例。
/// name="role">角色实体的1个指定实例。
///
/// 【异步物理删除用户角色映射】
///
/// 摘要:
/// 把用户角色映射实体的1个指定实例从用户角色映射表中物理删除后,并从缓存数据库中移除与用户角色映射实体相关的所有缓存项。
///
///
public async Task RemoveCustomerRoleMappingAsync(Customer customer, Role role)
{
if (customer is null)
throw new ArgumentNullException(nameof(customer));
if (role is null)
throw new ArgumentNullException(nameof(role));
var mapping = await _customerRoleMappingRepository.Table
.SingleOrDefaultAsync(ccrm => ccrm.CustomerId == customer.Id && ccrm.RoleId == role.Id);
if (mapping != null)
await _customerRoleMappingRepository.DeleteAsync(mapping);
}
8 重构Web.Areas.Admin.Models.Customers.CustomerModel
using Framework.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.ComponentModel.DataAnnotations;
namespace Web.Areas.Admin.Models.Customers
{
///
/// 【用户模型--纪录】
///
/// 摘要:
/// 为用户JQuery DataTables表格、添加和编辑Razor页面输入和验证的渲染显示提供数据支撑。
///
///
public record CustomerModel : BaseEntityModel
{
#region 拷贝构造方法
///
/// 【拷贝构造方法】
///
/// 摘要:
/// 实例化该纪录时,为列表接口类型的属性成员实例分配内存空间。
///
///
public CustomerModel()
{
SelectedRoleIds = new List
AvailableRoles = new List<SelectListItem>();
}
#endregion
#region 属性
///
/// 【用户名】
///
/// 摘要:
/// 获取/设置1个指定的用户名(账户、昵称)。
///
///
[Display(Name = "用户名")]
[Required(ErrorMessage = "必须输入用户名。")]
public string Username { get; set; }
///
/// 【电子邮箱】
///
/// 摘要:
/// 获取/设置1个指定用户所对应的电子邮箱。
///
///
[Display(Name = "邮箱")]
[Required(ErrorMessage = "必须输入电子邮箱。")]
[EmailAddress(ErrorMessage = "输入的电子邮件格式错误。")]
[Remote(action: "UniqueEmail", controller: "Customer", AdditionalFields = nameof(Id))]
public string Email { get; set; }
///
/// 【身份证号】
///
/// 摘要:
/// 获取/设置1个指定用户所对应的身份证号。
///
///
//public string IdCard { get; set; }
///
/// 【实名】
///
/// 摘要:
/// 获取/设置1个指定用户所对应的实名。
///
///
//public string RealName { get; set; }
///
/// 【密码】
///
/// 摘要:
/// 获取/设置经过指定加密方式加密后的密码字符串。
///
///
[Display(Name = "密码")]
[DataType(DataType.Password)]
public string Password { get; set; }
/*
可逆加密(Encryption)方式和哈希(Hash)加密方式的区别:
1、可逆加密(Encryption)是可逆的,即明码和加密码之间通过操作是可以相互转换;哈希(Hash)加密方式是不可逆的,哈希(Hash)加密方式一般会导致信息熵减小,即使用哈希(Hash)加密方式转换后可能导致,转换明码是原明码中的一段。
2、可逆加密(Encryption)的密码会随着明码的长度进行改变;而哈希(Hash)加密方式密码的长度是固定的,且只取决于所使用的算法,当明码的长度大于算法中所规则的长度时,哈希(Hash)加密方式会把明码截断后进行加密操作,这也是导致哈希(Hash)加密方式不可逆的与信息熵减小的根本原因。
3、应用场景:一般情况下用户密码加密操作使用可逆加密(Encryption)方式;哈希(Hash)加密方式(一般用于数字签名、数据校验(CRC、SHA、MD5),据说HTTPS协议运行所需要的CA证书就是使用哈希(Hash)算法生成的。
4、哈希(Hash)加密方式无解密操作,即哈希(Hash)加密方式是不能被解密的,如果用户输入的密码经过哈希(Hash)加密方式加密后的加密字符串与原加密字符相等,则用户通过验证,可以登录了。
*/
///
/// 【哈希密钥】
///
/// 摘要:
/// 获取/设置哈希(Hash)加密方式所需的密钥字符串(默认:5个字符的字符串)。
/// 说明:
/// 使用哈希加密方式对用户的密进行加密操作时,所使用的密钥字符串。
///
///
public string PasswordSalt { get; set; }
///
/// 【性别】
///
/// 摘要:
/// 获取/设置1个指定用户所对应的性别。
///
///
// public int Gender { get; set; }
///
/// 【手机号】
///
/// 摘要:
/// 获取/设置1个指定用户所对应的手机号。
///
///
[Display(Name = "手机号")]
[RegularExpression(@"^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$", ErrorMessage = "输入的手机号格式错误。")]
[Remote(action: "UniquePhone", controller: "Customer", AdditionalFields = nameof(Id))]
public string Phone { get; set; }
///
/// 【头像】
///
/// 摘要:
/// 获取/设置1个指定用户所对应的头像图片网络格式的路径字符串。
/// 说明:
/// 1、本地为网络格式的相对路径字符串。
/// 2、第3方云端为网络格式的绝对路径字符串。
///
///
public string Avatar { get; set; }
///
/// 【登录失败次数】
///
/// 摘要:
/// 获取/设置1个指定用户执行同1次登录操作时密码输入错误的次数。
/// 说明:
/// 用于纪录密码输入错误的次数,在用户输入的密码错误次数超过限定值时,设定1个指定的时间值,在该时间值内,用户将用法执行登录操作(通过直接重定向到登出操作)。
///
///
// public int FailedLoginAttempts { get; set; }
///
/// 【可用?】
///
/// 摘要:
/// 获取/设置1个值false(默认值:禁用)/true(可用),该值指示用户实体的1个指定实例是否处于可用状态。
///
///
[Display(Name = "可用")]
public bool Active { get; set; }
///
/// 【(辑删除?】
///
/// 摘要:
/// 获取/设置1个值false(可用)/true(已经被逻辑删除),该值指示用户实体的1个指定实例是否已经处于逻辑删除状态。
///
///
[Display(Name = "已被删除")]
public bool Deleted { get; set; }
///
/// 【系统帐户?】
///
/// 摘要:
/// 获取/设置1个值false(默认值:不是)/true(是),该值指示用户实体的1个指定实例是否是系统帐户。
/// 说明:
/// 如果是系统帐户,则该用户将不能被物理/逻辑删除,即在删除操作中会通过该属性实例过滤掉系统帐户的所有用户。
///
///
[Display(Name = "系统帐户")]
public bool IsSystemAccount { get; set; }
///
/// 【最后IP地址】
///
/// 摘要:
/// 获取/设置1个指定用户最后1次执行登录操作的IP地址。
///
///
// public string LastIpAddress { get; set; }
///
/// 【生日】
///
/// 摘要:
/// 获取/设置1个指定用户的生日。
///
///
//public DateTime? DateOfBirth { get; set; }
///
/// 【最后登录时间】
///
/// 摘要:
/// 获取/设置1个指定用户最后1次执行登录操作的时间。
///
///
//public DateTime? LastLoginDate { get; set; }
///
/// 【最后活动时间】
///
/// 摘要:
/// 获取/设置用户实体1个指定实例最后一次操作的时间。
/// 说明:
/// 在默认情况下每间隔20分钟,如果已经登录的用户有操作则对用户实体1个指定实例最后一次操作的时间进行更新。
///
///
//public DateTime LastActivityDate { get; set; }
///
/// 【无法登录时间】
///
/// 摘要:
/// 获取/设置1个指定用户无法登录的时间。
/// 说明:
/// 在用户输入的密码错误次数超过限定值时,设定1个指定的时间值,在该时间值内,用户将用法执行登录操作(通过直接重定向到登出操作)。
///
///
//public DateTime? CannotLoginUntilDate { get; set; }
///
/// 【创建时间】
///
/// 摘要:
/// 获取/设置用户实体1个指定实例第1次被持久化到用户表中的时间。
///
///
public DateTime CreatedDate { get; set; }
///
/// 【角色名集】
///
/// 摘要:
/// 获取/设置1个指定用户的所对应所有角色的角色名。
/// 说明:
/// 角色名之间用“,”进行分割,并把所有角色名通过JSON格式进行编码。
///
///
public string RoleNames { get; set; }
///
/// 【角色编号集】
///
/// 摘要:
/// 获取/设置1个指定用户的所对应所有角色的角色名。
/// 说明:
/// 角色名之间用“,”进行分割,并把所有角色名通过JSON格式进行编码。
///
///
[Display(Name = "角色")]
public IList<long> SelectedRoleIds { get; set; }
public IList<SelectListItem> AvailableRoles { get; set; }
#endregion
}
}
9 Web.Areas.Admin.Controllers.CustomerController.UniqueEmail
/// name="email">被验证的电子邮箱。
/// name="id">被验证用户实例所对应的长整型编号值,默认值:0,新的用户实例。
///
/// 【唯一电子邮箱】
///
/// 摘要:
/// 通过相应的参数实例,远程验证表单中所输入的电子邮箱是否已经被注册。
///
///
/// 返回:
/// JSON编码格式的验证结果状态信息。
///
///
public async Task<IActionResult> UniqueEmail(string email, long id = 0)
{
Customer customer = await _customerService.GetCustomerByEmailAsync(email.Trim());
if (customer != null && customer.Id != id)
return Json($"该电子邮箱已经被使用。");
return Json(true);
}
10 Web.Areas.Admin.Controllers.CustomerController.UniquePhone
/// name="phone">被验证的手机号。
/// name="id">被验证用户实例所对应的长整型编号值,默认值:0,新的用户实例。
///
/// 【唯一手机号】
///
/// 摘要:
/// 通过相应的参数实例,远程验证表单中所输入的手机号是否已经被注册。
///
///
/// 返回:
/// JSON编码格式的验证结果状态信息。
///
///
public async Task<IActionResult> UniquePhone(string phone, long id = 0)
{
var v = (await _customerService.GetAllCustomersAsync(true)).ToList();
Customer customer = await _customerService.GetCustomerByPhoneAsync(phone.Trim());
if (customer != null && customer.Id != id)
return Json($"该手机号已经被使用。");
return Json(true);
}
11 Web.Areas.Admin.Controllers.CustomerController.Create
/// name="model">用户模型记录的1个指定实例。
///
/// 【添加用户】
///
/// 摘要:
/// 通过添加用户Razor页面中的数据,把用户实体的1个指定实例持久化到用户表中。
///
///
/// 返回:
/// 用户模型记录的1个指定实例。
///
///
[HttpPost]
public async Task<IActionResult> Create(CustomerModel model)
{
//后端手动对角色多选下拉框中的输入进行验证。
if (model.SelectedRoleIds.Count <= 0)
{
ModelState.AddModelError("SelectedRoleIds", "必须选择1个角色。");
}
if (ModelState.IsValid)
{
var customer = model.ToEntity<Customer>();
var saltKey = _encryptionService.CreateSaltKey(CustomerServicesDefaults.PasswordSaltKeySize);
customer.PasswordSalt = saltKey;
customer.Password = _encryptionService.CreatePasswordHash("1", saltKey, CustomerServicesDefaults.DefaultHashedPasswordFormat);
customer.Active = true;
customer.CreatedDate = DateTime.Now;
await _customerService.InsertCustomerAsync(customer);
var allRoles = await _customerService.GetAllRolesAsync(true);
var newRoles = new List<Role>();
foreach (var role in allRoles)
if (model.SelectedRoleIds.Contains(role.Id))
newRoles.Add(role);
foreach (var role in newRoles)
{
await _customerService.AddCustomerRoleMappingAsync(new CustomerRoleMapping { CustomerId = customer.Id, RoleId = role.Id });
}
ViewBag.RefreshPage = true;
}
model = await _customerModelFactory.PrepareCustomerModelAsync(model, null, true);
return View(model);
}
12 重构Web\Areas\Admin\Views\Customer\Index.cshtml
//重置表格
$("#resetCustomerList").on("click", function () {
//重新加载DataTable插件,并返回第1页。
window.location.href = "/Admin/Customer/Index";
});
13 重构Web\Areas\Admin\Views\Customer\Create.cshtml
@model CustomerModel
@{
ViewData["Title"] = "添加用户";
Layout = "~/Areas/Admin/Views/Shared/_LayoutPopup.cshtml";
}
@if (ViewBag.RefreshPage == true)
{
}
asp-route-btnId="@Context.Request.Query["btnId"]"> asp-for="Username" class="form-control" /> asp-validation-for="Username" class="text-danger"> asp-for="Email" class="form-control" /> asp-validation-for="Email" class="text-danger"> asp-for="Phone" class="form-control" /> asp-validation-for="Phone" class="text-danger"> class="form-check-input checkBox25" asp-for="IsSystemAccount" /> @Html.DisplayNameFor(model => model.IsSystemAccount) asp-validation-for="SelectedRoleIds" class="text-danger"> type="submit" value="保存" class="btn btn-primary" />
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
$(document).ready(function () {
$("#SelectedRoleIds").kendoMultiSelect({
select: function (e) {
var current = this.value();
if (this.dataSource.view()[e.item.index()].value === "0") {
this.value("");
}
else if (current.indexOf("0") !== -1) {
current = $.grep(current, function (value) {
return value !== "0";
});
this.value(current);
}
},
change: function (e) {
if (this.value().length === 0)
this.value(["0"]);
}
}).data("kendoMultiSelect");
});
//把“Bootstrap v5”设定为jquery.validate.unobtrusive.js中验证样式。
$.validator.setDefaults({
errorClass: "",
validClass: "",
highlight: function (element, errorClass, validClass) {
$(element).addClass("is-invalid").removeClass("is-valid");
$(element.form).find("[data-valmsg-for=" + element.id + "]").addClass("invalid-feedback");
},
unhighlight: function (element, errorClass, validClass) {
$(element).addClass("is-valid").removeClass("is-invalid");
$(element.form).find("[data-valmsg-for=" + element.id + "]").removeClass("invalid-feedback");
},
});
}
对以上功能更为具体实现和注释见230606_024ShopRazor(DataAnnotations自定义手动验证的定义实现)。