[后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统

时间:2023-02-02 17:55:05

一、引言

  上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中。本篇博文将使用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只负责调用对应的应用层中的服务。这样设计好处有:

  1. REST 服务部依赖与应用层接口,使得职责分离,将应用层服务的实例化交给单独的依赖注入容器去完成,而REST服务只负责调用对应应用服务的方法来获取数据。采用依赖接口而不依赖与具体类的实现,使得类与类之间低耦合。
  2. 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().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 :
filterExp = user => user.Name.Contains(pageInput.Name) || user.Email.Contains(pageInput.Name);
break;
case :
filterExp = user => user.Name.Contains(pageInput.Name);
break;
case :
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 <= )
throw new ArgumentOutOfRangeException("pageNumber", pageNumber, "pageNumber must great than or equal to 1.");
if (pageSize <= )
throw new ArgumentOutOfRangeException("pageSize", pageSize, "pageSize must great than or equal to 1."); var query = DbContext.Set<TEntity>().Where(expression);
var skip = (pageNumber - ) * 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 ==)
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前端的代码结构如下图所示:

[后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统

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篇:使用AngularJs打造一个简易权限系统

六、总结

  到此,本文的所有内容都介绍完了,尽管本文的AngularJS的应用项目还有很多完善的地方,例如没有缓冲的支持、没有实现读写分离,没有对一些API进行压力测试等。但AngularJS在实际项目中的应用基本是这样的,大家如果在项目中有需要用到AngularJS,正好你们公司的后台又是.NET的话,相信本文的分享可以是一个很好的参考。另外,关于架构的设计也可以参考我的另一个开源项目:OnlineStoreFastWorks

  本文所有源码下载地址:PrivilegeManagement

[后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统的更多相关文章

  1. &lbrack;后端人员耍前端系列&rsqb;KnockoutJs篇:使用WebApi&plus;Bootstrap&plus;KnockoutJs打造单页面程序

    一.前言 在前一个专题快速介绍了KnockoutJs相关知识点,也写了一些简单例子,希望通过这些例子大家可以快速入门KnockoutJs.为了让大家可以清楚地看到KnockoutJs在实际项目中的应用 ...

  2. &lbrack;后端人员耍前端系列&rsqb;Bootstrap篇:30分钟快速掌握Bootstrap

    一.引言 很久没有写过博客了,但是最近这段时间都没有闲着,接触了很多方面.比如一些前端框架和组件.还有移动开发React-Native.以及对.NET框架设计的一些重新认识.这些内容在接下来的时间都会 ...

  3. &lbrack;后端人员耍前端系列&rsqb;KnockoutJs篇:使用KnockoutJs&plus;Bootstrap实现分页

    一.引言 由于最近公司的系统需要改版,改版的新系统我打算使用KnockoutJs来制作Web前端.在做的过程中,遇到一个问题——如何使用KnockoutJs来完成分页的功能.在前一篇文章中并没有介绍使 ...

  4. &lbrack;后端人员耍前端系列&rsqb;KnockoutJs篇:快速掌握KnockoutJs

    一.引言 之前这个系列文章已经介绍Bootstrap.由于最近项目中,前端是Asp.net MVC + KnockoutJs + Bootstrap来做的.所以我又重新开始写这个系列.今天就让我们来看 ...

  5. AngularJs打造一个简易权限系统

    AngularJs打造一个简易权限系统 一.引言 上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中.本篇博文将使用AngularJ ...

  6. &lbrack;后端人员耍前端系列&rsqb;AngularJs篇:30分钟快速掌握AngularJs

    一.前言 对于前端系列,自然少不了AngularJs的介绍了.在前面文章中,我们介绍了如何使用KnockoutJs来打造一个单页面程序,后面一篇文章将介绍如何使用AngularJs的开发一个单页面应用 ...

  7. Angularjs&comma;WebAPI 搭建一个简易权限管理系统

    Angularjs,WebAPI 搭建一个简易权限管理系统 Angularjs名词与概念(一)   1. 目录 前言 Angularjs名词与概念 权限系统原型 权限系统业务 数据库设计和实现 Web ...

  8. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入&lbrack;4&rsqb;&colon; 创建一个简易版的DI框架&lbrack;上篇&rsqb;

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  9. Angularjs&comma;WebAPI 搭建一个简易权限管理系统 —— Angularjs 前端主体结构&lpar;五&rpar;

    目录 前言 Angularjs名词与概念 Angularjs 基本功能演示 系统业务与实现 WebAPI项目主体结构 Angularjs 前端主体结构 6 Angularjs 前端主体结构 6.1 A ...

随机推荐

  1. 3-udev

    Udev 这个是挂载上的u盘 拔掉再插上 查看u盘设备信息 拔掉再插上,显示了 来自为知笔记(Wiz) 附件列表

  2. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  3. Android RecyclerView 使用完全解析 体验艺术般的控件

    概述 RecyclerView出现已经有一段时间了,相信大家肯定不陌生了,大家可以通过导入support-v7对其进行使用. 据官方的介绍,该控件用于在有限的窗口中展示大量数据集,其实这样功能的控件我 ...

  4. 宜昌IT软件资源汇总

    三峡云计算中心大厦 http://castd-ycci.com/ 三峡云计算中心http://www.sanxiacloud.com/index.htm 宜昌创业者(罗新:小卡片做成大产业 年销售额5 ...

  5. 【高精度乘法】NOIP2003麦森数

    题目描述 形如2^{P}-12P−1的素数称为麦森数,这时PP一定也是个素数.但反过来不一定,即如果PP是个素数,2^{P}-12P−1不一定也是素数.到1998年底,人们已找到了37个麦森数.最大的 ...

  6. 前端JS面试题汇总 Part 2 &lpar;null与undefined&sol;闭包&sol;foreach与map&sol;匿名函数&sol;代码组织&rpar;

    原文:https://github.com/yangshun/front-end-interview-handbook/blob/master/questions/javascript-questio ...

  7. redis&lowbar;哈希对象

    redis哈希对象的底层编码有两种:ziplist.hashtable ziplist编码 当一个哈希键只包含少量kv对.且key和value都是小整数值.短字符串时,redis会使用压缩列表来做 z ...

  8. mysql触发器详解 mysql触发器

    目录 21.1. CREATE TRIGGER语法 21.2. DROP TRIGGER语法 21.3. 使用触发程序 MySQL 5.1包含对触发程序的支持.触发程序是与表有关的命名数据库对象,当表 ...

  9. 简易版AC自动机

    为什么说是简易版? 因为复杂度大概是\(O(M*\overline N)\),而似乎还有另一种大概是\(O(M+\sum N)\)的. 不过据说比赛不会卡前一种做法,因为模式串一般不会很长. 那么步入 ...

  10. opencv-python教程学习系列6-用滑动条做调色板

    前言 opencv-python教程学习系列记录学习python-opencv过程的点滴,本文主要介绍opencv-python用滑动条做调色板,坚持学习,共同进步. 系列教程参照OpenCV-Pyt ...