您的位置:首页 > 其它

[.NET领域驱动设计实战系列]专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能

2015-06-14 23:52 1266 查看

一、引言

 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作。此时客户会在自己的订单状态看到店家已经发货。从上面的业务逻辑可以看出,当用户下完订单之后,店家或管理员可以对客户订单进行跟踪和操作。上一专题我们已经实现创建订单的功能,则接下来自然就是后台管理功能的实现了。所以在这一专题中将详细介绍如何在网上书店案例中实现后台管理功能。

二、后台管理中的权限管理的实现

  后台管理中,首先需要实现的自然就是权限管理了,因为要进行商品管理等操作的话,则必须对不同的用户指定的不同角色,然后为不同角色指定不同的权限。这样才能确保普通用户不能进行一些后台操作。

  然而角色和权限的赋予一般都是由系统管理员来操作。所以在最开始创建一个管理员用户,之后就可以以管理员的账号进行登录来进行后台操作的管理,包括添加角色,为用户分配角色、添加用户等操作。

  这里就牵涉到一个权限管理的问题了。系统如何针对不同用户的全新进行管理呢?

  其权限管理一个实现思路其实如下:

不同角色可以看到不同的链接,只有指定权限的用户才可以看到与其对应权限的操作。如只有管理员才可以添加用户和为用户赋予权限,而卖家只能对消费者订单的处理和对自己商店添加商品等操作。

  从上面的描述可以发现,权限管理的实现主要包括两部分:

为不同用户指定不同的链接显示。如管理员可以看到后台管理的所有链接:包括角色管理,商品管理,用户管理、订单管理,商品分类管理,而卖家只能看到订单管理,商品管理和商品类别管理等。其实现就是为这些链接的生成指定不同的权限,只有达到权限用户才进行生成该链接

既然要为不同用户指定不同的权限,则首先要获得用户的权限,然后根据用户的权限来动态生成对应的链接。

  有了上面的思路,下面就让我们一起为网上书店案例加入权限管理的功能:

  首先,我在Layout.cshtml页面加入指定权限的链接,具体的代码如下所示:

<table width="996" border="0" cellspacing="0" cellpadding="0" align="center">
<tr>
<td height="607" valign="top">
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="300" height="55" class="logo"></td>
<td width="480" class="menu">
<ul class="sf-menu">
<li>@Html.ActionLink("首页", "Index", "Home")</li>
@if (User.Identity.IsAuthenticated)
{
<li>@Html.ActionLink("我的", "Manage", "Account")
<ul>
<li>@Html.ActionLink("订单", "Orders", "Home")</li>
<li>@Html.ActionLink("账户", "Manage", "Account")</li>
<li>@Html.ActionLink("购物车", "ShoppingCart", "Home")</li>
</ul>
</li>
}
 @if (User.Identity.IsAuthenticated)
{
<li>@Html.ActionLinkWithPermission("管理", "Administration", "Admin", PermissionKeys.Administrators | PermissionKeys.Buyers | PermissionKeys.SalesReps)
<ul>
<li>@Html.ActionLinkWithPermission("销售订单管理", "Orders", "Admin", PermissionKeys.Administrators | PermissionKeys.SalesReps)</li>
<li>@Html.ActionLinkWithPermission("商品分类管理", "Categories", "Admin", PermissionKeys.Administrators | PermissionKeys.Buyers)</li>
<li>@Html.ActionLinkWithPermission("商品信息管理", "Products", "Admin", PermissionKeys.Administrators | PermissionKeys.Buyers)</li>
<li>@Html.ActionLinkWithPermission("用户账户管理", "UserAccounts", "Admin", PermissionKeys.Administrators)</li>
<li>@Html.ActionLinkWithPermission("用户角色管理", "Roles", "Admin", PermissionKeys.Administrators)</li>
</ul>
</li>
}
<li>@Html.ActionLink("关于", "About", "Home")
<ul>
<li>@Html.ActionLink("Online Store 项目", "About", "Home")</li>
<li>@Html.ActionLink("联系方式", "Contact", "Home")</li>
</ul>
</li>
</ul>
</td>
<td width="216" class="menu">
@{Html.RenderAction("_LoginPartial", "Layout");}
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="100%" height="10px" />
</tr>
</table>
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<img src="/images/header.jpg" alt="" width="996" height="400" border="0"></td>
</tr>
</table>
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr align="left" valign="top">
<td width="202" height="334">
@{Html.RenderAction("CategoriesPartial", "Layout");}
</td>
<td width="20"> </td>
<td width="774">

<table width="774" border="0" cellspacing="0" cellpadding="0">
<tr>
@(MvcSiteMap.Instance.Navigator())
</tr>
<tr>
<td>
@RenderBody()
</td>
</tr>

</table>
</td>
</tr>
</table>
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<img src="/images/footer.jpg" alt="" width="996" height="5"></td>
</tr>
<tr>
<td height="76">
<table width="996" border="0" cellspacing="0" cellpadding="0" align="center">
<tr>
<td width="329" height="78" align="right"></td>
<td width="14"> </td>
<td width="653"><span class="style7">@Html.ActionLink("主页", "Index", "Home")  |   @Html.ActionLink("所有分类", "Category", "Home", null, null)  |   @Html.ActionLink("我的账户", "Account", "Account")   |   @Html.ActionLink("联系我们", "Contact", "Home")   |  @Html.ActionLink("关于本站", "About", "Home")</span><br>
版权所有 © 2014-2015, Online Store, 保留所有权利。 </td>
</tr>
</table>
</td>
</tr>
</table>

</td>
</tr>
</table>


  上面红色加粗部分就是设置不同角色的不同权限。其中ActionLinkWithPermission是一个扩展方法,其具体实现就是获得登陆用户的角色,然后把用户的角色与当前的需要权限进行比较,如果相同,则通过HtmlHelper.GenerateLink方法来生成对应的链接。该方法的实现代码如下所示:

public static MvcHtmlString ActionLinkWithPermission(this HtmlHelper helper, string linkText, string action, string controller, PermissionKeys required)
{
if (helper == null ||
helper.ViewContext == null ||
helper.ViewContext.RequestContext == null ||
helper.ViewContext.RequestContext.HttpContext == null ||
helper.ViewContext.RequestContext.HttpContext.User == null ||
helper.ViewContext.RequestContext.HttpContext.User.Identity == null)
return MvcHtmlString.Empty;

using (var proxy = new UserServiceClient())
{
var role = proxy.GetRoleByUserName(helper.ViewContext.RequestContext.HttpContext.User.Identity.Name);
if (role == null)
return MvcHtmlString.Empty;
var keyName = role.Name;
var permissionKey = (PermissionKeys)Enum.Parse(typeof(PermissionKeys), keyName);

// 通过用户的角色和对应对应的权限进行与操作
// 与结果等于用户角色时,表示用户角色与所需要的权限一样,则创建对应权限的链接
return (permissionKey & required) == permissionKey ?
MvcHtmlString.Create(HtmlHelper.GenerateLink(helper.ViewContext.RequestContext, helper.RouteCollection, linkText, null, action, controller, null, null))
: MvcHtmlString.Empty;
}
}


  通过上面的代码,我们就已经完成了权限管理的实现了。

三、后台管理中商品管理的实现

  如果你是管理员的话,这样你就可以进入后台页面对商品、用户、订单等进行管理了。在上面我们已经完成了权限管理的实现。接下来,我们可以用一个管理员账号登陆之后,你可以看到管理员对应的权限。这里我直接在数据库中添加了一条管理员账号,其账号信息是admin,密码也是admin。下面我就这个账号后看到的界面如下图所示:

public class EventAggregator : IEventAggregator
{
private readonly object _sync = new object();
private readonly Dictionary<Type, List<object>> _eventHandlers = new Dictionary<Type, List<object>>();
private readonly MethodInfo _registerEventHandlerMethod;

public EventAggregator()
{

// 通过反射获得EventAggregator的Register方法
_registerEventHandlerMethod = (from p in this.GetType().GetMethods()
let methodName = p.Name
let parameters = p.GetParameters()
where methodName == "Register" &&
parameters != null &&
parameters.Length == 1 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEventHandler<>)
select p).First();
}

public EventAggregator(object[] handlers)
: this()
{
// 遍历注册的EventHandler来把配置文件中具体的EventHanler通过Register添加进_eventHandlers字典中
foreach (var obj in handlers)
{
var type = obj.GetType();
var implementedInterfaces = type.GetInterfaces();
foreach (var implementedInterface in implementedInterfaces)
{
if (implementedInterface.IsGenericType &&
implementedInterface.GetGenericTypeDefinition() == typeof(IEventHandler<>))
{
var eventType = implementedInterface.GetGenericArguments().First();
var method = _registerEventHandlerMethod.MakeGenericMethod(eventType);
// 调用Register方法将EventHandler添加进_eventHandlers字典中
method.Invoke(this, new object[] { obj });
}
}
}
}

public void Register<TEvent>(IEventHandler<TEvent> eventHandler)
where TEvent : class, IEvent
{
lock (_sync)
{
var eventType = typeof(TEvent);
if (_eventHandlers.ContainsKey(eventType))
{
var handlers = _eventHandlers[eventType];
if (handlers != null)
{
handlers.Add(eventHandler);
}
else
{
handlers = new List<object> {eventHandler};
}
}
else
_eventHandlers.Add(eventType, new List<object> { eventHandler });
}
}

public void Register<TEvent>(IEnumerable<IEventHandler<TEvent>> eventHandlers)
where TEvent : class, IEvent
{
foreach (var eventHandler in eventHandlers)
Register<TEvent>(eventHandler);
}

// 调用具体的EventHanler的Handle方法来对事件进行处理
public void Handle<TEvent>(TEvent evnt)
where TEvent : class, IEvent
{
if (evnt == null)
throw new ArgumentNullException("evnt");
var eventType = evnt.GetType();
if (_eventHandlers.ContainsKey(eventType) &&
_eventHandlers[eventType] != null &&
_eventHandlers[eventType].Count > 0)
{
var handlers = _eventHandlers[eventType];
foreach (var handler in handlers)
{
var eventHandler = handler as IEventHandler<TEvent>;
if(eventHandler == null)
continue;

// 异步处理
if (eventHandler.GetType().IsDefined(typeof(HandlesAsynchronouslyAttribute), false))
{
Task.Factory.StartNew((o) => eventHandler.Handle((TEvent)o), evnt);
}
else
{
eventHandler.Handle(evnt);
}
}
}
}


View Code
  至于确认收货操作的实现也是类似,大家可以自行参考Github源码进行实现。到此,我们商品发货和确认收货的功能就实现完成了。此时,我们解决方案已经调整为:



  经过本专题后,我们网上书店案例的业务功能都完成的差不多了,后面添加的一些功能都是附加功能,例如分布式缓存的支持、分布式消息队列的支持以及面向切面编程的支持等功能。既然业务功能都完成的差不多了,下面让我们具体看看发货操作的实现效果吧。

  首先是销售订单管理首页,在这里可以看到所有用户的订单状态。具体效果如下图示所示:



  点击上图的发货按钮后便可以完成商品发货操作,此时创建该订单的用户邮箱中会收到一份发货邮件通知,具体实现效果截图如下所示:



  其确认收货操作实现的效果与发货操作的效果差不多,这里就不一一截图了,大家可以自行到github上下载源码进行运行查看。

五、总结

  到这里,该专题的介绍的内容就结束。本专题主要介绍后台管理中权限管理的实现、商品管理、类别管理、角色管理、用户角色管理和订单管理等功能。正如上面所说的,到此,本网上书店的DDD案例一些业务功能都实现的差不多了,接下来需要完善的功能主要是一些附加功能,这些功能主要是为了提高网站的可扩展性和可伸缩性。这些主要包括缓存的支持、分布式消息队列的支持以及AOP的支持。在下一个专题将介绍分布式缓存和分布式消息队列的支持,请大家继续关注。

  本专题的所有源码下载:https://github.com/lizhi5753186/OnlineStore_Second/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: