BrnShop开源网上商城第三讲:插件的工作机制-阿里云开发者社区

开发者社区> 杰克.陈> 正文

BrnShop开源网上商城第三讲:插件的工作机制

简介: 原文:BrnShop开源网上商城第三讲:插件的工作机制   这几天BrnShop的开发工作比较多,所以这一篇文章来的晚了一些,还请大家见谅呀!还有通知大家一下BrnShop1.0.312版本已经发布,此版本添加了报表统计等新功能,需要源码的园友可以点此下载。
+关注继续查看
原文:BrnShop开源网上商城第三讲:插件的工作机制

  这几天BrnShop的开发工作比较多,所以这一篇文章来的晚了一些,还请大家见谅呀!还有通知大家一下BrnShop1.0.312版本已经发布,此版本添加了报表统计等新功能,需要源码的园友可以点此下载。好了,我们现在进入今天的正题。关于BrnShop插件内容比较多,所以我分成两篇文章来讲解,今天先讲第一部分内容:插件的工作机制。

  对于任意一种插件机制来说,基本上只要解决以下三个方面的问题,这个插件机制就算成功了。这三个方面如下:

  • 插件程序集的加载
  • 视图文件的路径和编译
  • 插件的部署

  首先是插件程序集的加载(请仔细阅读,下面的坑比较多),最简单的方式就是直接将插件程序集复制到网站根目录下的bin文件夹(够简单吧!),但是我们不推荐使用这种方式,因为这种方式导致插件的程序集和视图文件等内容分布在不同目录,为以后的维护带来不便。我们期望的是插件的所有内容都在同一个目录中,以BrnShop的支付宝插件为例:

所有的插件文件都在“BrnShop.PayPlugin.Alipay”目录中,这样我们以后的删除,扩展等就方便多了。好了现在第一个坑来了,那就是如何让asp.net加载此目录下的程序集文件?我们可能会想到使用web.config文件中probing节点来配置asp.net运行时探测程序集的目录,但是很不幸它不管用(不信你可以试一下)。代码如下:

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="Plugins/bin/" />
    </assemblyBinding>
  </runtime>

  既然自动加载不行,那我们就手动加载吧。手动加载需要使用一个方法:System.Web.Compilation.BuildManager.AddReferencedAssembly(Assembly assembly),此方法的的MSDN解释如下图:

通过调用这个方法可以手动加载指定的程序集(就是它的参数)到我们的程序中。在调用这个方法时有两个坑需要注意,第一个坑是这个方法必须在Application_Start 事件发生前调用,针对这个坑我们可以使用微软在.NET4.0中提供的PreApplicationStartMethodAttribute性质(此性质的具体介绍大家可以看这篇文章:http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx/)来解决这个问题。代码如下:

[assembly: PreApplicationStartMethod(typeof(BrnShop.Core.BSPPlugin), "Load")]

第二个坑是CLR会锁定程序集文件,所以如果我们直接读取此文件会导致异常,解决这个坑的方式是复制它的一个副本,然后不读取原程序集,而是读取程序集的副本并传入AddReferencedAssembly方法中。具体代码如下:

            try
            {
//复制程序集 File.Copy(dllFile.FullName, newDllFile.FullName,
true); } catch { //在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制 File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked"); File.Copy(dllFile.FullName, newDllFile.FullName, true); } //加载程序集的副本到应用程序中 Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(newDllFile.FullName)); //将程序集添加到当前应用程序域 BuildManager.AddReferencedAssembly(assembly);

不过这时又产生一个坑:此副本复制到哪个目录呢?我们期望这个副本能够保存下来,不必每次应用程序启动都要复制一次,熟悉asp.net编译的园友们估计都会想到,这个目录就是应用程序域的DynamicDirectory目录,一般情况下这个目录位于C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root\15a300ab\6af0b19a类似的目录,截图如下:

不过这时又产生了一个坑(哎,步步有坑呀!):权限问题,就是只有在Full Trust级别下才有操作此目录的权限,而在Medium Trust级别下我们没有操作此目录的权限。信任级别的MSDN解释如下:

针对这个坑我们只能根据不同的级别复制到不同的目录,Full Trust级别时复制到DynamicDirectory目录,Medium Trust级别时复制到一个影子目录(/Plugins/bin)代码如下:

            DirectoryInfo copyFolder;
            //根据当前的信任级别设置复制目录
            if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)//非完全信任级别
            {
//shadowFolder就是"/Plugins/bin"影子目录 copyFolder
= shadowFolder; } else//完全信任级别 { copyFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory); }

在跳过上面众多坑后,我们终于长舒一口气,来到胜利的彼岸:插件的程序集文件能够正确加载了。下面附上插件加载的完整代码:

using System;
using System.IO;
using System.Web;
using System.Reflection;
using System.Web.Compilation;
using System.Collections.Generic;

[assembly: PreApplicationStartMethod(typeof(BrnShop.Core.BSPPlugin), "Load")]
namespace BrnShop.Core
{
    /// <summary>
    /// BrnShop插件管理类
    /// </summary>
    public class BSPPlugin
    {
        private static object _locker = new object();//锁对象

        private static string _installedfilepath = "/App_Data/InstalledPlugin.config";//插件安装文件
        private static string _pluginfolderpath = "/Plugins";//插件目录
        private static string _shadowfolderpath = "/Plugins/bin";//插件影子目录

        private static List<PluginInfo> _oauthpluginlist = new List<PluginInfo>();//开放授权插件列表
        private static List<PluginInfo> _paypluginlist = new List<PluginInfo>();//支付插件列表
        private static List<PluginInfo> _shippluginlist = new List<PluginInfo>();//配送插件列表
        private static List<PluginInfo> _uninstalledpluginlist = new List<PluginInfo>();//未安装插件列表

        /// <summary>
        /// 开放授权插件列表
        /// </summary>
        public static List<PluginInfo> OAuthPluginList
        {
            get { return _oauthpluginlist; }
        }
        /// <summary>
        /// 支付插件列表
        /// </summary>
        public static List<PluginInfo> PayPluginList
        {
            get { return _paypluginlist; }
        }
        /// <summary>
        /// 配送插件列表
        /// </summary>
        public static List<PluginInfo> ShipPluginList
        {
            get { return _shippluginlist; }
        }
        /// <summary>
        /// 未安装插件列表
        /// </summary>
        public static List<PluginInfo> UnInstalledPluginList
        {
            get { return _uninstalledpluginlist; }
        }

        /// <summary>
        /// 加载插件程序集到应用程序域中
        /// </summary>
        public static void Load()
        {
            try
            {
                //插件目录
                DirectoryInfo pluginFolder = new DirectoryInfo(IOHelper.GetMapPath(_pluginfolderpath));
                if (!pluginFolder.Exists)
                    pluginFolder.Create();
                //插件bin目录
                DirectoryInfo shadowFolder = new DirectoryInfo(IOHelper.GetMapPath(_shadowfolderpath));
                if (!shadowFolder.Exists)
                {
                    shadowFolder.Create();
                }
                else
                {
                    //清空影子复制目录中的dll文件
                    foreach (FileInfo fileInfo in shadowFolder.GetFiles())
                    {
                        fileInfo.Delete();
                    }
                }

                //获得安装的插件系统名称列表
                List<string> installedPluginSystemNameList = GetInstalledPluginSystemNameList();
                //获得全部插件
                List<KeyValuePair<FileInfo, PluginInfo>> allPluginFileAndInfo = GetAllPluginFileAndInfo(pluginFolder);
                foreach (KeyValuePair<FileInfo, PluginInfo> fileAndInfo in allPluginFileAndInfo)
                {
                    FileInfo pluginFile = fileAndInfo.Key;
                    PluginInfo pluginInfo = fileAndInfo.Value;

                    if (String.IsNullOrWhiteSpace(pluginInfo.SystemName))
                        throw new BSPException(string.Format("插件'{0}'没有\"systemName\", 请输入一个唯一的\"systemName\"", pluginFile.FullName));
                    if (pluginInfo.Type < 0 || pluginInfo.Type > 2)
                        throw new BSPException(string.Format("插件'{0}'不属于任何一种类型, 请输入正确的的\"type\"", pluginFile.FullName));

                    //加载插件dll文件
                    FileInfo[] dllFiles = pluginFile.Directory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
                    foreach (FileInfo dllFile in dllFiles)
                    {
                        //部署dll文件
                        DeployDllFile(dllFile, shadowFolder);
                    }

                    if (IsInstalledlPlugin(pluginInfo.SystemName, installedPluginSystemNameList))//安装的插件
                    {
                        //根据插件类型将插件添加到相应列表
                        switch (pluginInfo.Type)
                        {
                            case 0:
                                _oauthpluginlist.Add(pluginInfo);
                                break;
                            case 1:
                                _paypluginlist.Add(pluginInfo);
                                break;
                            case 2:
                                _shippluginlist.Add(pluginInfo);
                                break;
                        }
                    }
                    else//未安装的插件
                    {
                        _uninstalledpluginlist.Add(pluginInfo);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new BSPException("加载BrnShop插件时出错", ex);
            }
        }

        /// <summary>
        /// 安装插件
        /// </summary>
        /// <param name="systemName">插件系统名称</param>
        public static void Install(string systemName)
        {
            lock (_locker)
            {
                if (string.IsNullOrWhiteSpace(systemName))
                    return;

                //在未安装的插件列表中获得对应插件
                PluginInfo pluginInfo = _uninstalledpluginlist.Find(x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase));

                //当插件为空时直接返回
                if (pluginInfo == null)
                    return;

                //当插件不为空时将插件添加到相应列表
                switch (pluginInfo.Type)
                {
                    case 0:
                        _oauthpluginlist.Add(pluginInfo);
                        _oauthpluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                        break;
                    case 1:
                        _paypluginlist.Add(pluginInfo);
                        _paypluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                        break;
                    case 2:
                        _shippluginlist.Add(pluginInfo);
                        _shippluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                        break;
                }

                //在未安装的插件列表中移除对应插件
                _uninstalledpluginlist.Remove(pluginInfo);

                //将新安装的插件保存到安装的插件列表中
                List<string> installedPluginSystemNameList = GetInstalledPluginSystemNameList();
                installedPluginSystemNameList.Add(pluginInfo.SystemName);
                SaveInstalledPluginSystemNameList(installedPluginSystemNameList);
            }
        }

        /// <summary>
        /// 卸载插件
        /// </summary>
        /// <param name="systemName">插件系统名称</param>
        public static void Uninstall(string systemName)
        {
            lock (_locker)
            {
                if (string.IsNullOrEmpty(systemName))
                    return;

                PluginInfo pluginInfo = null;
                Predicate<PluginInfo> condition = x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase);
                pluginInfo = _oauthpluginlist.Find(condition);
                if (pluginInfo == null)
                    pluginInfo = _paypluginlist.Find(condition);
                if (pluginInfo == null)
                    pluginInfo = _shippluginlist.Find(condition);

                //当插件为空时直接返回
                if (pluginInfo == null)
                    return;

                //根据插件类型移除对应插件
                switch (pluginInfo.Type)
                {
                    case 0:
                        _oauthpluginlist.Remove(pluginInfo);
                        break;
                    case 1:
                        _paypluginlist.Remove(pluginInfo);
                        break;
                    case 2:
                        _shippluginlist.Remove(pluginInfo);
                        break;
                }

                //将插件添加到未安装插件列表
                _uninstalledpluginlist.Add(pluginInfo);

                //将卸载的插件从安装的插件列表中移除
                List<string> installedPluginSystemNameList = GetInstalledPluginSystemNameList();
                installedPluginSystemNameList.Remove(pluginInfo.SystemName);
                SaveInstalledPluginSystemNameList(installedPluginSystemNameList);
            }
        }

        /// <summary>
        /// 编辑插件信息
        /// </summary>
        /// <param name="systemName">插件系统名称</param>
        /// <param name="friendlyName">插件友好名称</param>
        /// <param name="description">插件描述</param>
        /// <param name="displayOrder">插件排序</param>
        public static void Edit(string systemName, string friendlyName, string description, int displayOrder)
        {
            lock (_locker)
            {
                bool isInstalled = true;//是否安装
                PluginInfo pluginInfo = null;
                Predicate<PluginInfo> condition = x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase);

                pluginInfo = _oauthpluginlist.Find(condition);
                if (pluginInfo == null)
                    pluginInfo = _paypluginlist.Find(condition);
                if (pluginInfo == null)
                    pluginInfo = _shippluginlist.Find(condition);

                //当插件为空时直接返回
                if (pluginInfo == null)
                {
                    pluginInfo = _uninstalledpluginlist.Find(condition); ;
                    //当插件为空时直接返回
                    if (pluginInfo == null)
                        return;
                    else
                        isInstalled = false;
                }

                pluginInfo.FriendlyName = friendlyName;
                pluginInfo.Description = description;
                pluginInfo.DisplayOrder = displayOrder;

                //将插件信息持久化到对应文件中
                IOHelper.SerializeToXml(pluginInfo, IOHelper.GetMapPath("/Plugins/" + pluginInfo.Folder + "/PluginInfo.config"));

                //插件列表重新排序
                if (isInstalled)
                {
                    switch (pluginInfo.Type)
                    {
                        case 0:
                            _oauthpluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                            break;
                        case 1:
                            _paypluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                            break;
                        case 2:
                            _shippluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                            break;
                    }
                }
                else
                {
                    _uninstalledpluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
                }

            }
        }

        /// <summary>
        /// 获得安装的插件系统名称列表
        /// </summary>
        private static List<string> GetInstalledPluginSystemNameList()
        {
            return (List<string>)IOHelper.DeserializeFromXML(typeof(List<string>), IOHelper.GetMapPath(_installedfilepath));
        }

        /// <summary>
        /// 保存安装的插件系统名称列表
        /// </summary>
        /// <param name="installedPluginSystemNameList">安装的插件系统名称列表</param>
        private static void SaveInstalledPluginSystemNameList(List<string> installedPluginSystemNameList)
        {
            IOHelper.SerializeToXml(installedPluginSystemNameList, IOHelper.GetMapPath(_installedfilepath));
        }

        /// <summary>
        /// 获得全部插件
        /// </summary>
        /// <param name="pluginFolder">插件目录</param>
        /// <returns></returns>
        private static List<KeyValuePair<FileInfo, PluginInfo>> GetAllPluginFileAndInfo(DirectoryInfo pluginFolder)
        {
            List<KeyValuePair<FileInfo, PluginInfo>> list = new List<KeyValuePair<FileInfo, PluginInfo>>();
            FileInfo[] PluginInfoes = pluginFolder.GetFiles("PluginInfo.config", SearchOption.AllDirectories);
            Type pluginType = typeof(PluginInfo);
            foreach (FileInfo file in PluginInfoes)
            {
                PluginInfo info = (PluginInfo)IOHelper.DeserializeFromXML(pluginType, file.FullName);
                list.Add(new KeyValuePair<FileInfo, PluginInfo>(file, info));
            }

            list.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
            return list;
        }

        /// <summary>
        /// 判断插件是否已经安装
        /// </summary>
        /// <param name="systemName">插件系统名称</param>
        /// <param name="installedPluginSystemNameList">安装的插件系统名称列表</param>
        /// <returns> </returns>
        private static bool IsInstalledlPlugin(string systemName, List<string> installedPluginSystemNameList)
        {
            foreach (string name in installedPluginSystemNameList)
            {
                if (name.Equals(systemName, StringComparison.InvariantCultureIgnoreCase))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// 部署程序集
        /// </summary>
        /// <param name="dllFile">插件程序集文件</param>
        /// <param name="shadowFolder">/Plugins/bin目录</param>
        private static void DeployDllFile(FileInfo dllFile, DirectoryInfo shadowFolder)
        {
            DirectoryInfo copyFolder;
            //根据当前的信任级别设置复制目录
            if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)//非完全信任级别
            {
                copyFolder = shadowFolder;
            }
            else//完全信任级别
            {
                copyFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);
            }

            FileInfo newDllFile = new FileInfo(copyFolder.FullName + "\\" + dllFile.Name);
            try
            {
                File.Copy(dllFile.FullName, newDllFile.FullName, true);
            }
            catch
            {
                //在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制
                File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked");
                File.Copy(dllFile.FullName, newDllFile.FullName, true);
            }

            Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(newDllFile.FullName));
            //将程序集添加到当前应用程序域
            BuildManager.AddReferencedAssembly(assembly);
        }
    }
}
View Code

   在解决了插件程序集的加载问题后我们再来解决视图文件的路径和编译问题,由于插件目录不在传统View文件夹中,所以我们像普通视图那样返回插件路径,我们需要使用根目录路径来返回视图文件路径,以支付宝插件为例:

        /// <summary>
        /// 配置
        /// </summary>
        [HttpGet]
        [ChildActionOnly]
        public ActionResult Config()
        {
            ConfigModel model = new ConfigModel();

            model.Partner = PluginUtils.GetPluginSet().Partner;
            model.Key = PluginUtils.GetPluginSet().Key;
            model.Seller = PluginUtils.GetPluginSet().Seller;
            model.AllowRecharge = PluginUtils.GetPluginSet().AllowRecharge;

            //插件视图文件路径必须以"~"开头
            return View("~/Plugins/BrnShop.PayPlugin.Alipay/Views/AdminAlipay/Config.cshtml", model);
        }

通过使用根目录路径,我们可以忽视一切路由匹配问题了。再来说说视图的编译问题,为了能够正确指导asp.net编译视图文件,我们需要在“/Plugins”文件夹(此文件夹中的web.cong能够覆盖所有插件目录)中添加一个web.config文件,并指定编译要求如下:

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Text" />
        <add namespace="System.Data" />
        <add namespace="System.Collections"/>
        <add namespace="System.Collections.Generic"/>
        <add namespace="BrnShop.Core" />
        <add namespace="BrnShop.Services" />
        <add namespace="BrnShop.Web.Framework" />
        <add namespace="BrnShop.Web.Models" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

这样我们的插件视图文件就能够正确编译了。

  现在只剩下插件的部署问题了。如果是手动部署我们只需要将插件目录及其文件复制到"/Plugins"目录中就可以。如果是使用vs自动部署我们需要做以下几步配置:

 第一步配置插件程序集的输出路径,通过在项目上点击右键选择属性进入,具体配置如下:

 现在你生成一下解决方案就会发现插件程序集已经到"/Plugins"文件夹中。

第二步是筛选程序集,就是只输出插件程序集,其它的程序集(包括系统自带和引用的程序集)不输出。具体配置如下如图:

最后一步是输出内容文件,例如视图文件,具体配置如下图:

到此BrnShop的插件能够正常工作了。

  PS:其实asp.net mvc插件的实现方式有许多种,大家可以google一下就会发现。而BrnShop之所以采用这种插件机制其实是服从于程序整体框架设计理念的。我们在设计BrnShop框架之初确定的框架设计理念是:在不损失框架的扩展性,稳定性和性能的条件下,一切从简,直接,一目了然,返璞归真。最后奉上本人的框架设计理念(如不喜,请轻喷!):

  • 高手和大神的区别:高手能够把简单的问题复杂化,大神能够把复杂的问题简单化。
  • 项目需要什么架构不是想出来的,而是在开发过程中为解决问题而引入的。不能因为存在或会某种设计模式就使用。

有对网上商城程序设计感兴趣的朋友,欢迎加入QQ群:235274151,大家可以交流下!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Croppic – 免费开源的 jQuery 图片裁剪插件
  Croppic 这款开源的 jQuery 图片裁剪插件能够满足网站开发人员各种不同的使用需要。只需要简单的上传图片,就可以实现你想要的图像缩放和裁剪功能。因为使用了 HTML5 FormData  对象,所以目前只支持 IE 10+、Chrome 和 Firefox 等现代浏览器。
943 0
【MyBean-开源框架】进行简单的逻辑插件(演示在控制台中应用)
【说明】 很多时候大家认为客户端插件一般是窗体。其实不然,很多功能都可以看成是插件,比如一个单据的审批功能,一个单据上面,单价的获取功能,都可以看成是插件,然后后期通过配置,可以灵活进行切换。MyBean支持这种插件,支持这种纯逻辑的插件。
617 0
好工具推荐系列:使用SwitchHosts(开源,跨平台)实现操作系统的hosts文件的便捷修改
好工具推荐系列:使用SwitchHosts(开源,跨平台)实现操作系统的hosts文件的便捷修改
14 0
打造一个上传图片到图床利器的插件(Mac版 开源)
打造一个上传图片到图床利器的插件 鉴于写博客截图手动上传到图床的步骤过于繁琐,很久之前写了一个windows版的截图软件插件,用于把图片快速上传到图床 重新打包用户量过亿的开源截图软件——加入图片自动上传到图床的功能 现在换系统了,由windows换成了Mac,系统确实好用,但截图上传到图床的功能没法子弄过来,网上搜了下现成的软件,都不太好用。
1899 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4602 0
BrnShop开源网上商城第四讲:自定义插件
原文:BrnShop开源网上商城第四讲:自定义插件   重要通知:BrnShop企业版NOSQL设计(基于Redis)已经开源!源码内置于最新版的BrnShop中,感兴趣的园友可以去下载来看看。官网地址:www.brnshop.com。
962 0
开源工作流引擎 Workflow Core 的研究和使用教程
开源工作流引擎 Workflow Core 的研究和使用教程 [TOC] 一,工作流对象和使用前说明 为了避免歧义,事先约定。 工作流有很多节点组成,一个节点成为步骤点(Step)。 1,IWorkflow / IWorkflowBuilder Workflow Core 中,用于构建工作流的类继承 IWorkflow,代表一条有任务规则的工作流,可以表示工作流任务的开始或者 Do() 方法,或工作流分支获取其它方法。
2630 0
+关注
杰克.陈
一个安静的程序猿~
9798
文章
2
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载