返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期
写在前面的话
很多人说ABP不适合高并发大型,有一定的道理,但是我觉得还是可以的,就看架构师的能力了,我之前公司就是ABP绝对百万数据级项目,是一个在线教育网站,涉及到平台,学院,院系,班级,课程,学生等,一个平台多少大学,一个大学多少院系,一个院系多少班级多少课程,其负责程度一点都不简单,不说了,那是大神,比我在园子看到绝对大多数架构师都强悍.是我等仰望都对象.但是这不是停下脚步仰望的理由,只会是我们追求更强的脚步.
软件开发中会有大量机械重复的工作,也有大量重复的代码,用户需求越来越高,我们要做出的响应也越来越复杂,这就需要我们去集成更多的东西来应付工作的需要。
每当我们要开发一个新的系统的时候,我们要用到以前用到过的东西,比如mvc,ioc,日志,数据库的映射,日志管理,消息队列组件等等。我们更希望把更多时间时间用来处理业务逻辑,而不是花在软件结构,日志,容错等问题。
很多大公司都有自己的基础框架,自己公司封装了很多东西,像我们公司的日期组件,弹窗,表格样式,分页等等都是已经封装好了的,再后期开发的时候我们只需要去调用一下就可以了,不需要每个控件都自己再花时间和精力去封装。
这节我就来说一下在ABP module-zero基础上做AdminLTE+Bootstrap Table的系统,算是前面十一节的总结和扩展.
首先我们依旧去官网根据abp模板创建解决方案.勾选上module-zero,然后创建数据库连接,还原nuget包,然后update-database创建数据库.
接下来就是创建脚本更新数据库了,以上这些都在前面的章节讲到了,我就不做累述了,不明白可回去一下第一节.
然后在home控制器下建ActionResult login,添加一个试图页面.加上自己的登陆页面样式js等,这里我们就不用mvc的形式再在去请求控制器 了,我们直接请求webapi的登陆方法了.是的就是已经搭好了swagger的然后请求/swagger/ui/index的Account接口.如图.
abp javascript ajax的封装及应用
既然前面讲到ABP 对javascript ajax的封装
var newPerson = {
name: 'Dougles Adams',
age: 42
};
abp.ajax({
url: '/People/SavePerson',
data: JSON.stringify(newPerson)
}).done(function(data) {
abp.notify.success('created new person with id = ' + data.personId); });
其实这里我们还可以做一些再次封装,方便在项目中去使用,就以登陆为例效果如下。
abp.ui.block($('#login'));
var url = "/api/Account";
var login = function (para, ajaxtype,posturl) {
return abp.ajax({
url: posturl,
type: ajaxtype,
async: false,
data: JSON.stringify(para)
});
};
var loginModel = { "tenancyName": "", "usernameOrEmailAddress": $("#userName").val(), "password": $("#password").val() }; abp.ui.setBusy( $('#login'), login(loginModel, "post", url).done(function (data) { abp.auth.setToken("Bearer " + data); window.location.href = "/admin/userinfo/index" }), );
abp.ui.block
当然这是在ABP原来封装的效果上加上的,细心的你已经发现这里多了两个东西,一个是abp.ui.block,另外一个是abp.ui.setBusy,这其实是一个阻止用户重复提交,和正在提交繁忙状态,
其实就是一个遮罩层。
这里是ABP集成的jquery.blockUI.js插件,该API使用一个透明的涂层(transparent overlay)来阻塞整个页面或者该页面上的一个元素。这样,用户的点击就无效了。当保存一个表单或者加载一个区域(一个div或者整个页面)时这是很有用的。比如
另外abpjs中也对blockUI做了一些常用方法的封装,设置阻塞abp.ui.block,取消阻塞abp.ui.unblock ,设置繁忙状abp.ui.setBusy 和解除繁忙状态abp.ui.clearBusy
abp.ui.block = function (elm) {
abp.log.warn('abp.ui.block is not implemented!');
};
abp.ui.unblock = function (elm) {
abp.log.warn('abp.ui.unblock is not implemented!');
};
/* UI BUSY */
//Defines UI Busy API, not implements it
abp.ui.setBusy = function (elm, optionsOrPromise) { abp.log.warn('abp.ui.setBusy is not implemented!'); }; abp.ui.clearBusy = function (elm) { abp.log.warn('abp.ui.clearBusy is not implemented!'); };
abp.ui.block(); //阻塞整个页面 abp.ui.block($('#MyDivElement')); //可以使用jQuery 选择器.. abp.ui.block('#MyDivElement'); //..或者直接使用选择器 abp.ui.unblock(); //解除阻塞整个页面 abp.ui.unblock('#MyDivElement'); //解除阻塞特定的元素
abp.blockUI
UI Block API默认使用jQuery的blockUI插件实现的。要是它生效,你应该包含它的javascript文件,然后在页面中包含abp.blockUI.js作为适配器。
另外一个就是busy 该API用于使得某些页面或者元素处于繁忙状态。比如,你可能想阻塞一个表单,然后当提交表单至服务器时展示一个繁忙的指示器。例子:
abp.ui.setBusy('#MyLoginForm');
abp.ui.clearBusy('#MyLoginForm');
效果就是上面的繁忙效果。
该参数应该是一个选择器(如‘#MyLoginForm’)或者jQuery选择器(如$('#MyLoginForm'))。要使得整个页面处于繁忙状态,你可以传入null(或者'body')作为选择器。setBusy函数第二个参数接收一个promise(约定),当该约定完成时会自动清除繁忙的状态。因为abp.ajax返回promise,我们可以直接将它作为promise传入。要学习惯于promise更多的东西,查看jQuery的Deferred。
UI Busy API是使用spin.js实现的。要让它生效,应该包含它的javascript文件,然后在页面中包含abp.spin.js作为适配器。
经过上面的努力,我们得登陆也已经做好了。登陆成功之后我们要做事的事情就是一个保存token另外一个就是路由的重定向了。
Abp.AuthToken与cookie
token在ABP中很重要,我们在请求 /api/Account会反馈一个token,我们在登陆的时候就把token存到cookie中,以方便后面的使用。如登陆中的 abp.auth.setToken("Bearer " + data); 那ABP是怎么样设置cookie的了,这里也做了封装。
abp.auth.tokenCookieName = 'Abp.AuthToken';
abp.auth.setToken = function (authToken, expireDate) {
abp.utils.setCookieValue(abp.auth.tokenCookieName, authToken, expireDate, abp.appPath);
};
abp.auth.getToken = function () {
return abp.utils.getCookieValue(abp.auth.tokenCookieName);
}
abp.auth.clearToken = function () {
abp.auth.setToken();
}
这里面就包含了token常用的存取和清除的方法。页面上缓存的cookie名字就是Abp.AuthToken,获取的时候可以直接获取。
model和DTO
我们在已经添加了域,所以这里登陆成功之后直接把url指向/admin/userinfo/index。当然在/Areas/Common/Views/Layout里面我们已经AdminLTE的布局了,包括菜单已经加载出来了,其实现在服务层的时候漏了一些东西,我们这里补上,ABP既然是一个框架
那么在创建Service的时候自然要包含基础增删查改的方法,那么这时候IAsyncCrudAppService就派上用场了。这里我们以模板IModulesService讲一下。首先我们创建好model和DTO
using Abp.Domain.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text; using System.Threading.Tasks; namespace JCms.Meuns { public class Meun : Entity, IMayHaveTenant { public int? ParentId { get; set; } [Required] [StringLength(20)] public string Name { get; set; } [Required] [StringLength(50)] public string LinkUrl { get; set; } [StringLength(100)] public string Description { get; set; } public bool IsMenu { get; set; } public int Code { get; set; } public bool Enabled { get; set; } public DateTime UpdateDate { get; set; } public int? TenantId { get; set; } } }
DTO:
using Abp.Application.Services.Dto;
using Abp.AutoMapper;
using Abp.Domain.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace JCms.Meuns { /// /// 菜单 /// [Serializable] [AutoMapFrom(typeof(Meun))] public class MeunDto : EntityDto { /// /// id /// public int Id { get; set; } /// /// 父模块Id /// public int? ParentId { get; set; } /// /// 名称 /// [Required] [StringLength(20)] public string Name { get; set; } /// /// 链接地址 /// [Required] [StringLength(50)] public string LinkUrl { get; set; } /// /// 是否是菜单 /// public bool IsMenu { get; set; } /// /// 模块编号 /// public int Code { get; set; } /// /// 描述 /// [StringLength(100)] public string Description { get; set; } /// /// 是否激活 /// public bool Enabled { get; set; } public DateTime UpdateDate { get; set; } //public virtual MeunDto ParentModule { get; set; } //public List ChildModules { get; private set; } public List children { get; set; } } }
这里要注意的如果model集成了某个接口,那么DTO也要继承这个接口的DTO,不然再继承IAsyncCrudAppService就会报错。
比如面的model继承Entity
IAsyncCrudAppService
然后我们看一下IAsyncCrudAppService需要哪么参数。
这里都可以看得很清楚了,包括增删查改的DTO这里我比较懒,都用了一个。
接口:
public interface IModulesService : IAsyncCrudAppService// IApplicationService { }
实现
public class ModulesService : AsyncCrudAppService, IModulesService { public ModulesService(IRepository repository) : base(repository) { } }
apiJCMSWebApiModule
然后,我们在apiJCMSWebApiModule方法下加上.WithConventionalVerbs() 这样我们就可以看到特定的HTTP前缀,不然全是post,HTTP动词是通过方法名的前缀决定的:
- Get:方法名以Get开头。
- Put:方法名以Put或Update开头。
- Delete:方法名以Delete或Remove开头。
- Post:方法名以Post或Create开头。
- 否则,Post是HTTP动词的默认值。
我们可以通过对特定的方法使用WithVerb方法或者HTTP特性来覆盖上述惯例。
就这样,我们在业务层中常用的增删改查的方法就诞生了。
验证一下,传入参数,结果没毛病,可用。当然这里根据自己的需要可以重写这些方法的。页面和前面的页面差不多,没啥讲的,这里页面增删改查都已经实现。
首先我们看一下userinfo 页面,这里我们也根据abp ajax的封装和 swagger的应用做了一些改变,首先看页面,我们不通过控制器去获取创建和修改删除的方法了。控制器只有一个带分页的获取的方法 这是因为Bootstrap table必须要以这样的json结构返回
控制器方法
using Abp.Application.Services.Dto; using Abp.Web.Models; using Abp.Web.Security.AntiForgery; using JCMS.Authorization.Users; using JCMS.Sessions.Dto; using JCMS.Users; using JCMS.Users.Dto; using JCMS.Web.Controllers; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace JCMS.Web.Areas.Admin.Controllers { public class UserInfoController : JCMSControllerBase { private readonly IUserAppService _UserAppService; public UserInfoController( IUserAppService UserAppService) { _UserAppService = UserAppService; } public ActionResult Index() { return View(); } [DisableAbpAntiForgeryTokenValidation] [HttpGet] [DontWrapResult] public JsonResult GetUsersList() { string pageNumber = string.IsNullOrWhiteSpace(Request["pageNumber"]) ? "0" : Request["pageNumber"]; string pageSize = string.IsNullOrWhiteSpace(Request["pageSize"]) ? "20" : Request["pageSize"]; ListUserlist = new List (); Userlist = _UserAppService.GetAllList(); int totaldata = Userlist.Count(); Userlist = Userlist.Skip(int.Parse(pageNumber) * int.Parse(pageSize)).Take(int.Parse(pageSize)).ToList(); var result = new { total = totaldata, rows = Userlist }; return Json(result, JsonRequestBehavior.AllowGet); } } }
DontWrapResult标签打上的,不需要abp做的特殊封装。js页面的一些方法。
api,services应用
以careteorUpdatePerson方法为例,我都是封装好的。 调用的时候直接请求 "/api/services/app/user/Create"地址就可以了,其实这些代码都是可以复用的,我们也可以封装到一个共同的js页面。我这里就没有做这么详细。
var careteorUpdatePerson = function (person, ajaxtype) { return abp.ajax({ url: Url, type: ajaxtype, async: false, data: JSON.stringify(person) }); }; var newPerson = { "id": $("#id").val(), "UserName": $("#txt_Surname").val(), "EmailAddress" : $("#txt_Name").val(), "Name": $("#txt_UserName").val(), "Surname": "test", "Password": "123456" }; careteorUpdatePerson(newPerson, ajaxtype).done(function (data) { toastr.warning('操作成功!'); var actionUrl = "@Url.Action("GetUsersList")"; $("#tb_departments").bootstrapTable('refresh', { url: actionUrl }); }); });
首先我们传入的参数newPerson 必须与接口里面的需要的参数一致。而且接口里面自带验证。数据格式也必须一致。
界面及展示
比如这里邮箱地址我们随便传一个参数进去,他就会报错。页面上也是这样的。
当然这里的提示我们是可以修改的。角色管理页面的代码也是一样的。
@{ ViewBag.Title = "Index"; Layout = "~/Areas/Common/Views/Layout/_Layout.cshtml"; } "viewport" content="width=device-width" />class="content-header"> 用户明细 advanced cxdmles
class="breadcrumb">
- "#">class="fa fa-dashboard"> 主页
- "#">用户管理
- class="active">用户列表
class="content"> class="panel-body" style="padding-bottom:0px;">class="panel panel-default">class="panel-heading">查询条件class="panel-body">"toolbar" class="btn-group">"tb_departments">
class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">class="modal-dialog" role="document">class="modal-content">class="modal-header">class="modal-title" id="myModalLabel">
class="modal-body">class="form-group"> "text" name="id" class="form-control" id="id" placeholder="id" style="display:none"> "text" name="txt_departmentname" class="form-control" id="txt_Name" placeholder="角色名称">class="form-group"> "text" name="txt_parentdepartment" class="form-control" id="txt_DisplayName" placeholder="角色描述">@*class="form-group"> "text" name="txt_departmentlevel" class="form-control" id="txt_CreationTime" placeholder="创建时间">*@class="form-group">class="checkbox">class="modal-footer">
这里有共同的js我们已经放到 布局页面。
现在用户管理和角色管理功能已经实现,后面我会再做菜单管理,授权管理,做完基本上就完了,当然后面的也是重点。共同努力!
github 地址:https://github.com/Jimmey-Jiang/JCMS
module-zero角色管理
说说module-zero角色管理
角色实体
角色实体代表应用程序的角色。它应该从AbpRole类派生, 如下所示:
public class Role : AbpRole{ //add your own role properties here }
这个类是在安装模块零时创建的。角色存储在数据库的AbpRoles表中。您可以将您的自定义属性添加到角色类(并为更改创建数据库迁移)。
AbpRole定义了一些属性。最重要的是:
- 名称:租户中角色的唯一名称。
- DisplayName:显示角色的名称。
- IsDefault:这个角色默认分配给新用户吗?
- IsStatic:这个角色是静态的(预生成,不能被删除)。
角色用于对权限进行分组。当用户有一个角色,那么他/她将拥有该角色的所有权限。用户可以有 多个角色。此用户的权限将合并所有已分配角色的所有权限。
动态与静态角色
在模块零中,角色可以是动态的或静态的:
- 静态角色:一个静态角色有一个已知的名字 (如'admin'),不能改变这个名字(我们可以改变 显示名称)。它在系统启动时存在,不能被删除。因此,我们可以编写基于静态角色名称的代码。
- 动态(非静态)角色:我们可以在部署后创建动态角色。然后我们可以为该角色授予权限,我们可以将该角色分配给一些用户,我们可以将其删除。我们不能在开发时间知道动态角色的名字。
使用IsStatic属性为其设置角色。另外,我们应该 在模块的 PreInitialize上注册静态角色。假设我们对租户有一个“Admin”静态角色:
Configuration.Modules.Zero()。RoleManagement.StaticRoles.Add(new StaticRoleDefinition(“Admin”,MultiTenancySides.Tenant));
因此,模块零将意识到静态角色。
默认角色
一个或多个角色可以设置为默认值。默认角色分配给新增/注册用户。这不是开发时间属性,可以在部署后进行设置或更改。使用IsDefault 属性来设置它。
角色管理器
RoleManager是 为角色执行域逻辑的服务:
public class RoleManager : AbpRoleManager{ //... }
您可以注入并使用RoleManager来创建,删除,更新角色,为角色授予权限等等。你可以在这里添加你自己的方法。此外,您可以 根据自己的需要重写AbpRoleManager基类的任何方法。
像UserManager一样,RoleManager的某些方法也会返回IdentityResult,而不是在某些情况下抛出异常。查看 用户管理文档以获取更多信息。
>[ABP+AdminLTE+Bootstrap Table权限管理系统一期](http://www.cnblogs.com/anyushengcms/p/7325126.html)
[Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS](https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS)
**返回总目录:[ABP+AdminLTE+Bootstrap Table权限管理系统一期](http://www.cnblogs.com/anyushengcms/p/7325126.html)**
返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期