如何使用AngularJs打造权限管理系统【简易型】
一、引言
本文将介绍如何把angularjs应用到实际项目中。本篇文章将使用angularjs来打造一个简易的权限管理系统。下面不多说,直接进入主题。
二、整体架构设计介绍
首先看下整个项目的架构设计图:
从上图可以看出整个项目的一个整体结构,接下来,我来详细介绍了项目的整体架构:
采用asp.net web api来实现rest 服务。这样的实现方式,已达到后端服务的公用、分别部署和更好地扩展。web层依赖应用服务接口,并且使用castle windsor实现依赖注入。
显示层(用户ui)
显示层采用了angularjs来实现的spa页面。所有的页面数据都是异步加载和局部刷新,这样的实现将会有更好的用户体验。
应用层(application service)
angularjs通过http服务去请求web api来获得数据,而web api的实现则是调用应用层来请求数据。
基础架构层
基础架构层包括仓储的实现和一些公用方法的实现。
仓储层的实现采用ef code first的方式来实现的,并使用ef migration的方式来创建数据库和更新数据库。
lh.common层实现了一些公用的方法,如日志帮助类、表达式树扩展等类的实现。
领域层
领域层主要实现了该项目的所有领域模型,其中包括领域模型的实现和仓储接口的定义。
介绍完整体结构外,接下来将分别介绍该项目的后端服务实现和web前端的实现。
三、后端服务实现
后端服务主要采用asp.net web api来实现后端服务,并且采用castle windsor来完成依赖注入。
这里拿权限管理中的用户管理来介绍rest web api服务的实现。
提供用户数据的rest服务的实现:
public class usercontroller : apicontroller { private readonly iuserservice _userservice; public usercontroller(iuserservice userservice) { _userservice = userservice; } [httpget] [route("api/user/getusers")] public outputbase getusers([fromuri]pageinput input) { return _userservice.getusers(input); } [httpget] [route("api/user/userinfo")] public outputbase getuserinfo(int id) { return _userservice.getuser(id); } [httppost] [route("api/user/adduser")] public outputbase createuser([frombody] userdto userdto) { return _userservice.adduser(userdto); } [httppost] [route("api/user/updateuser")] public outputbase updateuser([frombody] userdto userdto) { return _userservice.updateuser(userdto); } [httppost] [route("api/user/updateroles")] public outputbase updateroles([frombody] userdto userdto) { return _userservice.updateroles(userdto); } [httppost] [route("api/user/deleteuser/{id}")] public outputbase deleteuser(int id) { return _userservice.deleteuser(id); } [httppost] [route("api/user/deleterole/{id}/{roleid}")] public outputbase deleterole(int id, int roleid) { return _userservice.deleterole(id, roleid); } }
从上面代码实现可以看出,user rest 服务依赖与iuserservice接口,并且也没有像传统的方式将所有的业务逻辑放在web api实现中,而是将具体的一些业务实现封装到对应的应用层中,rest api只负责调用对应的应用层中的服务。这样设计好处有:
rest 服务部依赖与应用层接口,使得职责分离,将应用层服务的实例化交给单独的依赖注入容器去完成,而rest服务只负责调用对应应用服务的方法来获取数据。采用依赖接口而不依赖与具体类的实现,使得类与类之间低耦合。rest服务内不包括具体的业务逻辑实现。这样的设计可以使得服务更好地分离,如果你后期想用wcf来实现rest服务的,这样就不需要重复在wcf的rest服务类中重复写一篇web api中的逻辑了,这时候完全可以调用应用服务的接口方法来实现wcf rest服务。所以将业务逻辑实现抽到应用服务层去实现,这样的设计将使得rest 服务职责更加单一,rest服务实现更容易扩展。
用户应用服务的实现:
public class userservice : baseservice, iuserservice { private readonly iuserrepository _userrepository; private readonly iuserrolerepository _userrolerepository; public userservice(iuserrepository userrepository, iuserrolerepository userrolerepository) { _userrepository = userrepository; _userrolerepository = userrolerepository; } public getresults<userdto> getusers(pageinput input) { var result = getdefault<getresults<userdto>>(); var filterexp = buildexpression(input); var query = _userrepository.find(filterexp, user => user.id, sortorder.descending, input.current, input.size); result.total = _userrepository.find(filterexp).count(); result.data = query.select(user => new userdto() { id = user.id, createtime = user.creationtime, email = user.email, state = user.state, name = user.name, realname = user.realname, password = "*******", roles = user.userroles.take(4).select(z => new baseentitydto() { id = z.role.id, name = z.role.rolename }).tolist(), totalrole = user.userroles.count() }).tolist(); return result; } public updateresult updateuser(userdto user) { var result = getdefault<updateresult>(); var existuser = _userrepository.findsingle(u => u.id == user.id); if (existuser == null) { result.message = "user_not_exist"; result.statecode = 0x00303; return result; } if (ishassamename(existuser.name, existuser.id)) { result.message = "user_name_has_exist"; result.statecode = 0x00302; return result; } existuser.realname = user.realname; existuser.name = user.name; existuser.state = user.state; existuser.email = user.email; _userrepository.update(existuser); _userrepository.commit(); result.issaved = true; return result; } public createresult<int> adduser(userdto userdto) { var result = getdefault<createresult<int>>(); if (ishassamename(userdto.name, userdto.id)) { result.message = "user_name_has_exist"; result.statecode = 0x00302; return result; } var user = new user() { creationtime = datetime.now, password = "", email = userdto.email, state = userdto.state, realname = userdto.realname, name = userdto.name }; _userrepository.add(user); _userrepository.commit(); result.id = user.id; result.iscreated = true; return result; } public deleteresult deleteuser(int userid) { var result = getdefault<deleteresult>(); var user = _userrepository.findsingle(x => x.id == userid); if (user != null) { _userrepository.delete(user); _userrepository.commit(); } result.isdeleted = true; return result; } public updateresult updatepwd(userdto user) { var result = getdefault<updateresult>(); var userentity =_userrepository.findsingle(x => x.id == user.id); if (userentity == null) { result.message = string.format("当前编辑的用户“{0}”已经不存在", user.name); return result; } userentity.password = user.password; _userrepository.commit(); result.issaved = true; return result; } public getresult<userdto> getuser(int userid) { var result = getdefault<getresult<userdto>>(); var model = _userrepository.findsingle(x => x.id == userid); if (model == null) { result.message = "use_not_exist"; result.statecode = 0x00402; return result; } result.data = new userdto() { createtime = model.creationtime, email = model.email, id = model.id, realname = model.realname, state = model.state, name = model.name, password = "*******" }; return result; } public updateresult updateroles(userdto user) { var result = getdefault<updateresult>(); var model = _userrepository.findsingle(x => x.id == user.id); if (model == null) { result.message = "use_not_exist"; result.statecode = 0x00402; return result; } var list = model.userroles.tolist(); if (user.roles != null) { foreach (var item in user.roles) { if (!list.exists(x => x.role.id == item.id)) { _userrolerepository.add(new userrole { roleid = item.id, userid = model.id }); } } foreach (var item in list) { if (!user.roles.exists(x => x.id == item.id)) { _userrolerepository.delete(item); } } _userrolerepository.commit(); _userrepository.commit(); } result.issaved = true; return result; } public deleteresult deleterole(int userid, int roleid) { var result = getdefault<deleteresult>(); var model = _userrolerepository.findsingle(x => x.userid == userid && x.roleid == roleid); if (model != null) { _userrolerepository.delete(model); _userrolerepository.commit(); } result.isdeleted = true; return result; } public bool exist(string username, string password) { return _userrepository.findsingle(u => u.name == username && u.password == password) != null; } private bool ishassamename(string name, int userid) { return !string.isnullorwhitespace(name) && _userrepository.find(u=>u.name ==name && u.id != userid).any(); } private expression<func<user, bool>> buildexpression(pageinput pageinput) { expression<func<user, bool>> filterexp = user => true; if (string.isnullorwhitespace(pageinput.name)) return filterexp; switch (pageinput.type) { case 0: filterexp = user => user.name.contains(pageinput.name) || user.email.contains(pageinput.name); break; case 1: filterexp = user => user.name.contains(pageinput.name); break; case 2: filterexp = user => user.email.contains(pageinput.name); break; } return filterexp; } }
这里应用服务层其实还可以进一步的优化,实现代码层级的读写分离,定义ireadonlyservice接口和iwriteservie接口,并且把写操作可以采用泛型方法的方式抽象到baseservice中去实现。这样一些增删改操作实现公用,之所以可以将这里操作实现公用,是因为这些操作都是非常类似的,无非是操作的实体不一样罢了。其实这样的实现在我另一个开源项目中已经用到:onlinestore.大家可以参考这个自行去实现。
仓储层的实现:
用户应用服务也没有直接依赖与具体的仓储类,同样也是依赖其接口。对应的用户仓储类的实现如下:
public class baserepository<tentity> : irepository<tentity> where tentity :class , ientity { private readonly threadlocal<usermanagerdbcontext> _localctx = new threadlocal<usermanagerdbcontext>(() => new usermanagerdbcontext()); public usermanagerdbcontext dbcontext { get { return _localctx.value; } } public tentity findsingle(expression<func<tentity, bool>> exp = null) { return dbcontext.set<tentity>().asnotracking().firstordefault(exp); } public iqueryable<tentity> find(expression<func<tentity, bool>> exp = null) { return filter(exp); } public iqueryable<tentity> find(expression<func<tentity, bool>> expression, expression<func<tentity, dynamic>> sortpredicate, sortorder sortorder, int pagenumber, int pagesize) { if (pagenumber <= 0) throw new argumentoutofrangeexception("pagenumber", pagenumber, "pagenumber must great than or equal to 1."); if (pagesize <= 0) throw new argumentoutofrangeexception("pagesize", pagesize, "pagesize must great than or equal to 1."); var query = dbcontext.set<tentity>().where(expression); var skip = (pagenumber - 1) * pagesize; var take = pagesize; if (sortpredicate == null) throw new invalidoperationexception("based on the paging query must specify sorting fields and sort order."); switch (sortorder) { case sortorder.ascending: var pagedascending = query.sortby(sortpredicate).skip(skip).take(take); return pagedascending; case sortorder.descending: var pageddescending = query.sortbydescending(sortpredicate).skip(skip).take(take); return pageddescending; } throw new invalidoperationexception("based on the paging query must specify sorting fields and sort order."); } public int getcount(expression<func<tentity, bool>> exp = null) { return filter(exp).count(); } public void add(tentity entity) { dbcontext.set<tentity>().add(entity); } public void update(tentity entity) { dbcontext.entry(entity).state = entitystate.modified; } public void delete(tentity entity) { dbcontext.entry(entity).state = entitystate.deleted; dbcontext.set<tentity>().remove(entity); } public void delete(icollection<tentity> entitycollection) { if(entitycollection.count ==0) return; dbcontext.set<tentity>().attach(entitycollection.first()); dbcontext.set<tentity>().removerange(entitycollection); } private iqueryable<tentity> filter(expression<func<tentity, bool>> exp) { var dbset = dbcontext.set<tentity>().asqueryable(); if (exp != null) dbset = dbset.where(exp); return dbset; } public void commit() { dbcontext.savechanges(); } } public class userrepository :baserepository<user>, iuserrepository { }
四、angularjs前端实现
web前端的实现就是采用angularjs来实现,并且采用模块化开发模式。具体web前端的代码结构如下图所示:
app/images // 存放web前端使用的图片资源 app/styles // 存放样式文件 app/scripts // 整个web前端用到的脚本文件 / controllers // angularjs控制器模块存放目录 / directives // angularjs指令模块存放目录 / filters // 过滤器模块存放目录 / services // 服务模块存放目录 / app.js // web前端程序配置模块(路由配置) app/modules // 项目依赖库,angular、bootstrap、jquery库 app/views // angularjs视图模板存放目录
使用angularjs开发的web应用程序的代码之间的调用层次和后端基本一致,也是视图页面——》控制器模块——》服务模块——》web api服务。
并且web前端css和js资源的加载采用了bundle的方式来减少请求资源的次数,从而加快页面加载时间。具体bundle类的配置:
public class bundleconfig { // for more information on bundling, visit http://go.microsoft.com/fwlink/?linkid=301862 public static void registerbundles(bundlecollection bundles) { //类库依赖文件 bundles.add(new scriptbundle("~/js/base/lib").include( "~/app/modules/jquery-1.11.2.min.js", "~/app/modules/angular/angular.min.js", "~/app/modules/angular/angular-route.min.js", "~/app/modules/bootstrap/js/ui-bootstrap-tpls-0.13.0.min.js", "~/app/modules/bootstrap-notify/bootstrap-notify.min.js" )); //angularjs 项目文件 bundles.add(new scriptbundle("~/js/angularjs/app").include( "~/app/scripts/services/*.js", "~/app/scripts/controllers/*.js", "~/app/scripts/directives/*.js", "~/app/scripts/filters/*.js", "~/app/scripts/app.js")); //样式 bundles.add(new stylebundle("~/js/base/style").include( "~/app/modules/bootstrap/css/bootstrap.min.css", "~/app/styles/dashboard.css", "~/app/styles/console.css" )); } }
首页 index.cshtml
<!doctype html> <html ng-app="lh"> <head> <meta name="viewport" content="width=device-width" /> <title>简易权限管理系统demo</title> @styles.render("~/js/base/style") @scripts.render("~/js/base/lib") </head> <body ng-controller="navigation"> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">简易权限管理系统demo</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-left"> <li class="{{item.isactive?'active':''}}" ng-repeat="item in ls"> <a href="#{{item.urls[0].link}}">{{item.name}}</a> </li> </ul> <div class="navbar-form navbar-right"> <a href="@url.action("unlogin", "home", null)" class="btn btn-danger"> {{lang.exit}} </a> </div> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="{{item.isactive?'active':''}}" ng-repeat="item in urls"><a href="#{{item.link}}">{{item.title}}</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div ng-view></div> </div> </div> </div> @scripts.render("~/js/angularjs/app") </body> </html>
五、运行效果
介绍完前后端的实现之后,接下来让我们看下整个项目的运行效果:
六、总结
到此,本文的所有内容都介绍完了,尽管本文的angularjs的应用项目还有很多完善的地方,例如没有缓冲的支持、没有实现读写分离,没有对一些api进行压力测试等。但angularjs在实际项目中的应用基本是这样的,大家如果在项目中有需要用到angularjs,正好你们公司的后台又是.net的话,相信本文的分享可以是一个很好的参考。另外,关于架构的设计也可以参考我的另一个开源项目:onlinestore和fastworks。
以上所述是小编给大家介绍的使用angularjs打造权限管理系统的方法,希望对大家有所帮助!