ASP.NET 4.0 一些隐性的扩展 - 阿不 - 博客园

mikel阅读(859)

来源: ASP.NET 4.0 一些隐性的扩展 – 阿不 – 博客园

ASP.NET 4.0在很多方面都做了改进,在这篇ASP.NET 4.0白皮书就描述了很多ASP.NET 4.0的机制改变和改进。在我的博客中,也有几篇关于ASP.NET4.0的特性修改的文章。但是作为一个全新的框架和运行时,内部肯定还会有很多API和扩展点不会暴露的那么明显。比如今天从这篇文章Three Hidden Extensibility Gems in ASP.NET 4的介绍中,我又了解了一些在我平常开发中绝对非常有用的扩展点。

PreApplicationStartMethodAttribute

这个新的Attribute可以让我们指定一个公共的静态函数,让它在站点的Application_Start之前执行该函数。如果你的站点有App_code目录,这个函数同样也会在App_code目录下的代码被编译之前执行。从我的直觉中,这是一个相当有用的扩展点。

我们必须从assembly级别上来使用这个attribute,也就是通常情况下我们会用在AssemblyInfo.cs中:

1
2
[assembly: PreApplicationStartMethod(
typeof(SomeClassLib.Initializer), "Initialize")]

我们需要指定的是类型和类型里面静态函数的名称,这个静态函数必须是不带参数和返回值的公共函数。

这个功能最重要的地方是在于,它可以做一些我们原来在Application_Start无法完成的事情,所以很多事情在执行到Application_Start时已经完成了,不可改变的,比如下面要介绍的关于编译的扩展。

BuildProvider.RegisterBuildProvider

原来我们要注册BuildProvider都是通过添加web.config的<buildproviders>来完成。在ASP.NET 4.0当中,我们就可以配合PreApplicationStartMethodAttribute,在站点启动前添加自定义的来BuildProvider达到目的。

BuildManager.AddReferencedAssembly

在做.ASPX/.ASPCX和App_code目录下的代码文件编译时,需要依赖一些程序集。以前,我们都需要将这些程序集配置在web.config的<assemblies>节点下来完成。现在,你就只需要配合以上的attribute和这个新的方法,直接通过代码的形式来增加这些依赖。

Config-free IHttpModule Registration

这也是对PreApplicationStartMethodAttribute的一个绝对的妙用,在Nikhil Kothari这篇文章中有详细的介绍。它的主要目标,也是脱离Web.config就可以通过代码来注册IHttpModule。

总之虽然只是一个简单的attribute,可是它却非常的有用。当我们开发是一个可复用的框架时,我们不可避免的会需要在程序中做很多相关的配置才能让程序跑起来。而以前,我们就只能要求用户通过Web.config来完成,而当用户少了一个配置就可能会导致整个框架无法运行。如果我们能将这些必须的配置,都在我们的框架内部来完成,这样就可以大大降低框架的使用门槛。

阿不 http://hjf1223.cnblogs.com

使用PreApplicationStartMethodAttribute - liqipeng - 博客园

mikel阅读(1230)

来源: 使用PreApplicationStartMethodAttribute – liqipeng – 博客园

第一次见到这个东西是在公司的框架里,刚开始还挺郁闷怎么框架的Application_Start里没东西,初始化的那些代码都哪去了,后来通过一些线索找到了PreApplicationStartMethodAttribute。

这里简单记录一下。

 

1. 作用

指定某个函数在站点的Application_Start之前执行。

2. 用法

1
[assembly: PreApplicationStartMethod(typeof(SomeClassLib.Initializer), "Initialize")]

一般放在AssemblyInfo.cs。

可以注册多个。

3. 用途

它可以让我们脱离web.config做一些事情,如注册自定义IHttpModule、注册BuildProvider

4. 注意

不能保证调用程序集定义的应用程序启动方法的顺序。 因此,每个注册的开始方法应该将代码编写为分开运行,不应该依赖于其他注册开始方法的副作用。(摘自MSDN)

使用asp.net mvc引擎开发插件系统 - 产品经理是条狗 - 博客园

mikel阅读(1446)

来源: 使用asp.net mvc引擎开发插件系统 – 产品经理是条狗 – 博客园

一、前言

我心中的插件系统应该是像Nop那样(更牛逼的如Orchard,OSGI.NET),每个插件模块不只是一堆实现了某个业务接口的dll,然后采用反射或IOC技术来调用,而是一个完整的mvc小应用,我可以在后台控制插件的安装和禁用,目录结构如下:

生成后放在站点根目录下的Plugins文件夹中,每个插件有一个子文件夹

Plugins/Sms.AliYun/

Plugins/Sms.ManDao/

我是一个有强迫症的的懒人,我不想将生成的dll文件拷贝到bin目录。

二、要解决的问题

1.ASP.NET引擎默认只会加载“bin”文件夹中的dll,而我们想要的插件文件则是分散在Plugins目录下的各个子目录中。

2.视图中使用了模型时如何处理?默认情况下RazorViewEngine使用BuildManager将视图编译成动态程序集,然后使用Activator.CreateInstance实例化新编译的对象,而使用插件dll时,当前的AppDomain不知道如何解析这种引用了模型的视图,因为它不存在于“bin”或GAC中。更糟糕的是,不会收到任何错误消息,告诉您为什么它不工作,或者问题在哪。相反,他会告诉你,从View目录中找不到文件。

3.某个插件正挂在站点下运行着,直接覆盖插件的dll,会告诉你当前dll正在使用,不能被覆盖。

4.视图文件不放站点的View目录中,该如何加载。

三.Net 4.0让这一切变成可能

Net4.0有一个新特性是在应用程序初始化之前执行代码的能力(PreApplicationStartMethodAttribute),这个特性使得应用程序在Application_Star前可以做一些工作,例如我们可以在应用启动之前告知我们的mvc插件系统的dll放在哪,做预加载处理等。关于.net的几个新特性,有歪果仁写得有博客来介绍,点我。,关于PreApplicationStartMethodAttribute,有博友已经写过了,点我。 Abp的启动模块应该也是使用PreApplicationStartMethodAttribute这个特性原理来实现的,具体是不是这样还没看。

四、解决方案

1.修改主站点web.config目录,让运行时除了加载bin目录中的文件,还可以从其它目录加载

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

2.开发一个简易的插件管理类,这个类的作用就是在Application_Start之前就把Plugins各子目录中的dll拷贝到第1步指定的文件夹中,为了让demo尽可能简单,没有对重复的dll进行检测(比如插件中引用了ef程序集,主站点也引用了,在站点bin目录中已经存在ef的dll了,就没必要再把插件中的dll拷贝到上面设置的动态程序集目录中)

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Compilation;
using System.Web.Hosting;
[assembly: PreApplicationStartMethod(typeof(Plugins.Core.PreApplicationInit), "Initialize")]
namespace Plugins.Core
{
    public class PreApplicationInit
    {

        static PreApplicationInit()
        {
            PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins"));
            ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins/temp"));
        }

        /// <summary>
        /// 插件所在目录信息
        /// </summary>
        private static readonly DirectoryInfo PluginFolder;

        /// <summary>
        /// 程序应行时指定的dll目录
        /// </summary>
        private static readonly DirectoryInfo ShadowCopyFolder;

        public static void Initialize()
        {
            Directory.CreateDirectory(ShadowCopyFolder.FullName);
            //清空插件dll运行目录中的文件
            foreach (var f in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories))
            {
                f.Delete();
            }
            foreach (var plug in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories).Where(i=>i.Directory.Parent.Name== "plugins"))
            {
                File.Copy(plug.FullName, Path.Combine(ShadowCopyFolder.FullName, plug.Name), true);
            }
            foreach (var a in
                ShadowCopyFolder
                .GetFiles("*.dll", SearchOption.AllDirectories)
                .Select(x => AssemblyName.GetAssemblyName(x.FullName))
                .Select(x => Assembly.Load(x.FullName)))
            {
                BuildManager.AddReferencedAssembly(a);
            }

        }
    }
}
复制代码

 

3.如何让View引擎找到我们的视图呢?答案是重写RazorViewEngine的方法,我采用了约定大于配置的方式(假设我们的插件项目命名空间为Plugins.Apps.Sms,那么默认的控制器命名空间为Plugins.Apps.Sms.Controllers,插件生成后的文件夹必须为/Plugins/Plugins.Apps.Sms/),通过分析当前控制器就可以知道当前插件的View目录位置

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages.Razor;

namespace Plugins.Web
{
    public class CustomerViewEngine : RazorViewEngine
    {

        /// <summary>
        /// 定义视图页所在地址。
        /// </summary>
        private string[] _viewLocationFormats = new[]
        {
            "~/Views/Parts/{0}.cshtml",
            "~/Plugins/{pluginFolder}/Views/{1}/{0}.cshtml",
            "~/Plugins/{pluginFolder}/Views/Shared/{0}.cshtml",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.cshtml",
        };
        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            string ns = controllerContext.Controller.GetType().Namespace;
            string controller = controllerContext.Controller.GetType().Name.Replace("Controller", "");
            //说明是插件中的控制器,View目录需要单独处理
            if (ns.ToLower().Contains("plugins"))
            {
                var pluginsFolder = ns.ToLower().Replace(".controllers", "");
                ViewLocationFormats = ReplacePlaceholder(pluginsFolder);
            }
            return base.FindView(controllerContext, viewName, masterName, useCache);
        }
        /// <summary>
        /// 替换pluginFolder占位符
        /// </summary>
        /// <param name="folderName"></param>
        private string[] ReplacePlaceholder(string folderName)
        {
            string[] tempArray = new string[_viewLocationFormats.Length];
            if (_viewLocationFormats != null)
            {
                for (int i = 0; i < _viewLocationFormats.Length; i++)
                {
                    tempArray[i] = _viewLocationFormats[i].Replace("{pluginFolder}", folderName);
                }
            }
            return tempArray;
        }
    }
}
复制代码

然后在主站点的Global.asax中将Razor引擎指定为我们重写过的

 

4.开始制作一个插件目录,跟我们平时建立的MVC项目并没有太大区别,只是发布时需要做一些设置。

.生成路径要按照第3条的约定来写,不然会找不到视图文件

.View目录下的web.config和.cshtml文件要复制到生成目录(在文件中点右键)

3.设置引用项目中的生成属性,主程序下面已经有了的就把“复制到输出目录”设置为无,要不然拷贝到动态bin目录时会出错,可以对第2步中的那个类改造一下,加入文件比较功能,bin目录中没有的,才拷贝到动态bin目录中。

4.生成后的目录结构如下:

 

5.跑一下,一切正常,插件中的控制器工作正常,视图中引用了Model也没问题

到此,一个插件系统的核心部分就算完成了,你可继续进行扩展,增加插件的发现、安装、卸载功能,这些相对于核心功能来说,都是小儿科。后续我会基于Abp框架出一个插件系统的文章,有兴趣的把小板凳准备好,瓜子花生买上:)

五、源代码

点我下载

ASP.NET MVC 4 插件化架构简单实现-思路篇 - cjnmy36723 - 博客园

mikel阅读(1106)

来源: ASP.NET MVC 4 插件化架构简单实现-思路篇 – cjnmy36723 – 博客园

用过和做过插件的都会了解插件的好处,园子里也有很多和讨论,但大都只些简单的加载程序集什么的,这里主要讨论的就是使用 ASP.NET MVC 4 来实现每个插件都可以完全从主站点剥离出来,即使只是一个插件,也是一个完整的站点,同时也可以和其它插件一起组装成一个庞大的系统。

参考资料:

ASP.NET MVC 4 源码。

Orchard 源码。

MVC3PlugInDemo 源码。

ASP.NET MVC的Razor引擎:View编译原理

基于ASP.NET MVC3 Razor的模块化/插件式架构实现

基于OSGi.NET开发ASP.NET MVC 3.0插件化应用程序

http://stackoverflow.com/questions/6923572/asp-net-mvc-3-portable-area-view-doesnt-find-my-model

首先,非常感谢以上几位大牛分享的文章,由于文笔不好,对.NET 了解也不够,希望大家多多指点。

理想情况下是希望能够像Orchard那样,可以运行时修改代码,又或者可以直接把插件(包含页面、样式、图片等资源文件)编译成一个DLL来使用(但是这样的做法会对前端与美工修改不便,而且就算改一个字也要重新编译一个DLL),只是依然还没找到方法ORZ。

最终结构图如下。

 

当把插件的站点发布出来后,目录名为插件名,并将该目录及目录下的所有文件复制到Plugins目录下即可自动安装并运行,不需要重启程序池。

 

要实现这么一个架构,最初认为,只要使用 Assembly.LoadFile(name);方法来加载外部的程序集,并且使用反射创建控制器,在定义一下MVC的模板引擎的搜索路径不就可以了吗?

 

当具体实现之后,发现,在不使用强类型的模型绑定时,可以正常使用,但是,使用了强类型的模型绑定时,则会出现以下错误。

问题产生原因:

.NET 会把.cshtml 与相关的程序集进行编译,之后访问的是编译后的临时程序集,但是,由于没有引用进入系统中,这里编译的时候没有该程序集,就会出现错误。

那么,要解决这个问题,就需要在编译时,把需要的程序集,都一起编译了,但是,怎么样才可以实现?

直接引用并使用类库的时候,系统会自行编译到一起了,所以解决办法有两种:

1、在系统启动前的预编译时,手动把相关的程序集增加进系统中,这样就是一个实际存在于系统的程序集,在页面编译时自然会编译进去。

在google查找相关的解决方法时,发现了该方法:

BuildManager.AddReferencedAssembly(assembly);

在查MSDN有这么一段话:此方法必须在 Global.asax 文件中的 Application_Start 事件发生前调用。

也就意味着加载程序集的方法就必须要在预启动阶段就是加载了。

并且使用上面的方法,来把程序集加到系统里。

虽然这样可以正常使用了,但是,偶尔还是会有出现编译错误的异常。

在调试阶段下,只有重新生成的代码时可以正常运行,重新生成之后的代码,在点启动调试时,就会出现编译错误问题,调试发现,在这个时候,系统并没有将需要的程序集加载到系统中,有大牛了解的话希望指点下原因。

但是,在使用了Web.config 配置文件中的节点“probing”以后,把相关的程序集复制到“probing”指定的目录下,就能正常运行了。

但是, 由于上面的那段代码只可以在预启动阶段使用,所有注定了该方法有个缺点,就是每个更新插件时,都要重启或者回收一次程序池,没能正常的做到插件化的灵活性。

 

2、在模板(cshtml)进行编译前,把外部引用的相关的程序集增加到编译信息中,这样在对模板进行编译的同时,会把该程序集也编译进去。

在谷歌娘的帮助下,找到了该事件:

RazorBuildProvider.CodeGenerationStarted

从名字可以看出,这是在编译启动时触发的事件,具体功能不明ORZ。

可以通过该事件,把外部的程序集增加到 RazorBuildProvider 类中。

provider.AssemblyBuilder.AddAssemblyReference(plugin.Assembly);

provider 是 RazorBuildProvider 类的一个实例。
plugin.Assembly 是一个页面所使用的程序集。
只要把这段代码放到模板引擎的搜索视图的位置,即可根据需要,将增加外部的程序集,由于重复增加会出现已添加组件异常,所以,这里加了个 isLoadAssembly 变量来确认是否已增加过。

 

复制代码
        /// <summary>
        /// 给运行时编译的页面加了引用程序集。
        /// </summary>
        /// <param name="pluginName"></param>
        private void CodeGeneration(string pluginName)
        {
            RazorBuildProvider.CodeGenerationStarted += (object sender, EventArgs e) =>
            {
                RazorBuildProvider provider = (RazorBuildProvider)sender;

                var plugin = PluginManager.GetPlugin(pluginName);

                if (plugin != null)
                {
                    provider.AssemblyBuilder.AddAssemblyReference(plugin.Assembly);
                }
            };
        }
复制代码

 

 

 

我不喜欢每装一个插件,都要重启一次,所以我选择使用了第二种方法。

下一篇,将使用第二种方法来进行具体的实践并发布源码。

ASP.NET MVC 4 插件化架构简单实现-思路篇

ASP.NET MVC 4 插件化架构简单实现-实例篇

我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(五)框架及Web项目的组件化 - 萧秦 - 博客园

mikel阅读(981)

来源: 我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(五)框架及Web项目的组件化 – 萧秦 – 博客园

一、组件化印象

1、先给大家看一张截图

image

如果我告诉大家,这就是一个web管理系统发布后的所有内容,你们会不会觉得太简洁了,只有一个web.config、一个Global.asax文件,其它的都是dll文件,没有aspx、cshtml、html页面,没有js css images文件,但它的确能跑起来,跑起来之后的截图如下

image

2、再看我新建一个项目的流程

新建一个ASP.NET MVC 4的空的项目

image

image

点确定之后,创建项目,默认的项目目录结构如下

image

然后打开nuget包管理界面,选择我们需要安装的组件 Zephyr.Web.Sys,这是我们权限管理系统

image

点安装进行安装,实现上就是引入dll文件

image

安装完成后,直接F5运行,就出现了以下页面

image

点创建后

image

我们选择MySQL输入相应的数据库信息测试连接通过后,点创建

image

创建成功

image

现在登录跳转到登陆页面

image

登录成功后

image

权限系统中页面测试都没有问题

image

然后我们回过头看程序,什么都没改变,

image

只是增加了一些引用及web.config中添加了一些信息。

image

那回过头来,这样发布后,不就是我们之前所看到了什么都没有的Web应用程序了

image

大家是不是也是很感兴趣,我当时也只是一个想法,但直正驱动我去完成这些功能的还是以下几点:

a 代码的重用性提高,可以很方便的用NuGet管理类库或系统。即使不用NuGet管理,直接把dll文件拷贝到新建项目的bin下面,然后修改下web.config也是可以的。比以前我们引用项目的方式方便很多。

b 创建一个新项目时只需要安装组件即可

c 发布后的程序干净,项目更新只需覆盖对应的dll即可。

d 更方便做成产品发布

 

二、NuGet操作

那么我们要做到前面所看到的,我们要做哪些工作?

1、准备好要组件化的的类库

image

2、创建Nuget包

创建NuGet包有很多种方法,我采用的是编译时自动生成NuGet包,

首先要启用NuGet包还原功能(Eable NuGet Package Restore),然后VS会自动下载添加一个.nuget的文件夹

image

然后编辑项目工程文件,添加最后一行<BuildPackage>true</BuildPackage>

image

然后编译后就会在bin目录下发现一个后缀为nupkg的文件,这就是我们要的nuget包

image

当然你也可以在程序包管理器控制台输入命令行进行打包
也可以下载一个图形化的打包器nuget package explorer

3、推送到NuGet服务上

你可以在https://www.nuget.org/注册上帐号,放在nuget官网上
也可以自己搭建一个NuGet服务器:新建一个空项目,安装NuGet.Server然后直接发布就可以了。具体教程网上也很多了。

上传NuGet包只需要执行以下命令
nuget push Zephyr.Core.2.0.0.0.nupkg -s http://192.168.1.100:8888

到这里就已经ok了,你可以在Nuget包管理界面中添加一个程序包源服务地址,然后就可以安装你刚刚上传的包了

 

三、打包Web应用会碰到的问题

1、资源的嵌入

资源的嵌入分两种,一种是静态资源,直接内嵌到程序集中
第二种是动态的页面,可以预编译成类放到程序集中

image

 

2、资源的访问

获取嵌入资源的访问地址

public static string GetResourceUrl(Type type string embedFileName)
{
var page = new Page();
    return page.ClientScript.GetWebResourceUrl(type, embedFileName);
}

当然你也可以通过controller去处理

复制代码
[AllowAnonymous]
[MvcMenuFilter(false)]
[WebFrameworkFilter]
public class ResourceController : Controller
{
    public ActionResult Index()
    {
        var assemblyName = ResourceVirtualPathProvider.ResourceVirtualPath.GetAssemblyName(Request.Path);
        var filename = ResourceVirtualPathProvider.ResourceVirtualPath.GetResourceName(Request.Path);
        var assembly = ReflectionHelper.GetAssembly(assemblyName);

        var stream = ResourceHelper.GetFileStream(assembly,filename);
        var contentType = ResourceHelper.GetContentType(filename);

        if (stream == null)
            return HttpNotFound();

        return File(stream, contentType);
    }
}
复制代码

四、框架组件化操作

我的框架Zephyr.Net主要分为:

类库 描述 依赖
Zephyr.Utils 工具类库
Zephyr.Library 常用其它类库
Zephyr.Data 数据库访问组件
Zephyr.Core 框架核心类 Zephyr.Utils Zephyr.Library Zephyr.Data
Zephyr.Web.Resource 静态资源
Zephyr.Web.Mvc 纵云Mvc框架 Zephyr.Core
Zephyr.Web.Sys 权限管理系统 Zephyr.Web.Mvc

以上是七个独立的项目,依赖关系也不直接引用项目,而是从nuget上引用发布程序包,

其中前面四个是类库
Zephyr.Web.Resource是嵌入静态资源的程序集,如果不引入这个文件,直接把静态资源文件夹拷贝进项目中也是可以的。
Zephyr.Web.Mvc是我们的Mvc框架基础,已经包括了mvc及web api很多的路由注册过滤器设定等设置,并且有Login页面和Index页面,安装了这个组件,即使不安装纵云权限系统组件,程序也一样可以跑起来,只是没有权限系统,是个空的架子。
Zephyr.Web.Sys就是权限系统了,把权限系统的mvc及webapi控制器、View页面、js以及数据库初始化处理都放在这个程序集中。
以后我们开发新的项目也可以做成跟这个权限系统一样,都放在一个dll中(当然也可以分多个,把controller model等都分成不同的层),以后只要引入或者bin下面放入这个dll就会动态引入这个模块,非常方便。

打包成nuget packages发布到nuget服务上,大功就告成了,如果有更新,只需要更新版本号重新发布就行了

image

这样一来,基本就把我的框架以及权限系统都做成组件了,如果新项目需要引入,或者多个项目升级都很方便了。

.NET框架交流群(三)  21549700

我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(一) - 萧秦 - 博客园

mikel阅读(931)

来源: 我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(一) – 萧秦 – 博客园

一、前言

之前的博客一直都还没写到框架的实现及权限系统,今天开始写我的权限系统,我以前做过的项目基本上都有权限管理这个模块,但各个系统都会有一些不太一样,有些简单点,有些稍微复杂一点,一句话,我们做的系统都离不开这个权限系统。所以网上很多人尝试做一个通用的权限系统,不评论他们做的怎么样,只是说在网上能找到的直接能用的应该不多,适用的并且能集成到项目中的就更少了,所以还是考虑自己做一个,不一定很通用,但足够自己用的权限系统。

二、需求分析

关于权限系统的文章网上多如牛毛,很多都是基于角色的访问控制(RBAC)设计。但是发现完全实现RBAC的理论其实不一定好用,我想做一款适合自己的。所以首先我们必要明确我们要去实现哪些东西。

1、权限资源
a.菜单权限  经理和业务员登陆系统拥有的功能菜单是不一样的
b.按钮权限  经理能够审批,而业务员不可以
c.数据权限  A业务员看不到B业务员的单据
d.字段权限  某些人查询客户信息时看不到客户的手机号或其它字段

2、用户,应用系统的具体操作者,我这里设计用户是不能直接分配权限的,必须要分配一个角色,角色中再分配权限,如果某个用户权限比较特殊,可以为他专门建一个角色来应用解决,因为如果用户也可以分配权限系统就会复杂很多。

3、角色,为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,以上所有的权限资源都可以分配给角色,角色和用户N:N的关系。

4、机构,树形的公司部门结构,国内公司用的比较多,它实际上就是一个用户组,机构和角色N:N的关系,机构和用户也设计成N:N的关系,也就是说有时候一个用户可以从属于两个部门,这种情况在我们客户需求中的确都出现过。

当然除了上面提到的主要对象还包括以下对象

5、菜单

6、按钮

7、授权代码 以前我做的项目中控制数据权限有以下两种:
a.用部门编码来控制,只能查询到自己所属部门的数据
b.用项目编码来控制,只能查询到自己所属项目的数据。
这样就有一个问题,我到底要用什么字段来控制数据权限呢,所以我们就设计了一个授权代码,如果你是用机构来控制数据权限,那么你可以把授权代码建成跟机构一样的树,如果用项目编码控制,可以把它建成项目树,用其它字段控制都一样,所以会比较灵活,也把机构或项目解放出来了。

8、数据字典

9、系统参数

10、日志查询 登陆日志、操作日志(xx人xx时间对xx对象做了xx操作 自定义的)、系统日志(系统运行错误记录等 log4net输出的)

 

三、设计实现

1、表结构设计

image

 

2 程序设计

我还是采用mvc4.0 + web api + easyui + knockout去实现。实际上之前我就实现的差不多,最近也一直在花时间更新完善。
这个权限系统太大了,这一篇肯定是说不完的,我这里就先总体上说了一下设计思路,接下每一篇写具体实现,一篇一个功能,大家期待我的下一篇吧。

3、UI设计

不多说了,先上图吧,我在第一篇博客中贴过以前的版本,最近也有一些更新,再贴一次吧,这次截图比较详细点,也配了一些文字的说明介绍。

功能1:菜单导航
菜单导航】1:包括新增修改删除功能,直接在grid中编辑
image

【菜单导航】2:选择图标
image

【菜单导航】3:选择页面按钮 定义这个功能有只些按钮image

【菜单导航】4:管理按钮库 增删改
image

 

功能2:组织结构
【组织结构】1:图形化的树结构,可以新增编辑删除节点
image

【组织结构】2:编辑节点
image

【组织结构】3:设置角色
image

功能3:授权代码
【授权代码】树形grid 可以增添编辑删除
image

 

功能4:角色管理
【角色管理】1:直接在grid中 增添编辑删除
image

【角色管理】2:点编辑权限弹出角色极限窗口,点击选择菜单权限image

【角色管理】3:编辑按钮权限,grid中只会出现菜单权限中选中的行,每个功能有哪些按钮在菜单中已定义好,这里只要勾选按钮即可
image

【角色管理】4:编辑数据权限 可以多选,但只有一个默认值,该角色新增的业务单据的授权代码为默认的授权代码。
image

【角色管理】5:编辑字段权限 grid中只会出现菜单权限中选中的行,直接在允许或拒绝中输入字段名,多个字段用逗号隔开
image

【角色管理】6:点击管理成员,弹出此窗口
image

【角色管理】7:成员管理 点击添加按钮
image

 

功能5:用户管理
【用户管理】1:左边机构可以过滤,直接在grid中增删改查用户
image

【用户管理】2:设置机构 可多选,这里机构录入的太多了,出现了滚动条,少些会好看点。
image

【用户管理】3:设置角色 可多选
image

【用户管理】4:用户设置 用户登陆后的一些配置信息,比如指定用户的桌面,默认的项目,不同的系统可能会有不同的需求,所以这里设计可添加删除的,右上角下拉出来的[个人设置]页面也指向这里的数据image

 

功能6:数据字典
【数据字典】1:左边类别树可过滤,左边是树形grid,可以有层级关系,直接在grid中编辑
image

【数据字典】2:点击管理类别,类别可在弹出的grid中增删改查
image

 

功能7:系统参数
【系统参数】直接grid中编辑,设计了一个是否可编辑字段,只能编辑 是否可编辑为true的参数
image

 

功能8:操作日志
【操作日志】1:登陆日志
image

【操作日志】2:业务操作记录
image

【操作日志】3:系统日志
image

【操作日志】4:系统日志 查看系统日志
image

四、后述

如果大家感兴趣,就在右下角帮我【推荐】一下吧,谢谢大家了。我接下来就按照功能列表一篇一篇的来写,下一篇就先开始写菜单管理。
这里整个系统都是采用我的zephyr.net框架开发,应用mvc4.0 webapi easyui knockoutjs开发的实现的。
很多朋友私信我一些问题,所以干脆我创建了一个技术交流QQ群:群一:328510073(已满),群二:167813846,欢迎大家来交流。

基于办公的 IM 的基础设计

mikel阅读(839)

http://blog.codingnow.com/2017/08/im_for_office.html

现在的 IM 在设计上是基于会话的,多个人可以组成一个会话,相当于一个聊天室,当一个人加入到一个会话后,就可以看到从加入开始之后这个聊天室里所有参与人的发言。有的 IM 会把两人对话也抽象成同一个东西,也可能出于优化的考虑把双人对话特殊处理。

所以,这些 IM 在操作界面上会有一个会话列表:表现出来会是联系人名单、聊天群列表等等。选中会话列表中的项目,进入会话查看聊天记录、发言,就是这类 IM 的使用逻辑。

我认为,这种对即时通讯的抽象方式,其实是不适合办公环境的。和日常个人社交环境不同,办公群体其实是一个相对关系密切的团体,我们通常不会拉黑一个同事不让他给你发消息,也不会拒收公司发的通告,也不会因为一个同事平常不和你打交道就拒绝建立联系。项目组里的讨论,也未见得是多么保密的事情,需要防止隔壁组的同事旁听。你也很少会在办公 IM 上和妹子私聊谈人生理想。

我们这几年使用腾讯的 RTX 作为公司办公使用,我就感受到了太多这类设计缺陷。比如,有同事找我有事,我忽略了他的私聊信息;找人一般在对方活跃的项目群组里吼;程序群沦为了日常扯淡的位置,常常同时讨论着不同的问题,线索及其混乱。“群”这个设计,我在很多年前就思考过 ,我一直觉得需要在根本上换个角度看待社交聊天的需求。

我现在的想法是这样的:

作为办公 IM ,我们不应该基于固定会话(群)来设计,而应该是“通知”和“话题”。

所谓通知,就是有人发起了一条消息,他需要把这条消息传达给某些对象,对象可以是人,也可以是某个组织:比如程序、游戏项目组、等等。

组织并非是群那样的聊天室,而仅仅是一个标签,由人来关注标签,而不是去组织这个聊天室里有多少听众。

而话题,则是由消息或旧话题衍生而来。任何通知消息、话题内部消息,都可以变成一个新话题。话题也可以包含在一则消息里转发给某个对象。

用户的客户端应该把所有的通知按时间线排列在一起,呈现在同一个地方。也就是说,无论是谁给我发消息(默认就是通知)都应该投递在一起,而不是像现在 RTX 那样只是在系统托盘里闪烁提醒、也不是微信 qq 那样,联系人名单上多出一个小红点。

而一旦我回复一个通知消息,其实就把这则通知转化成了一个话题,在时间线上,话题内的消息是归属在一起的。同一时刻,无论你的思维切换多么快,其实在短时间内你只能聚焦在一个话题上,所以客户端界面是很容易表达的,把当前话题展开在主界面(通知的时间线)即可,切换话题后自然可以折叠起来。

话题并不是聊天室、它更像是论坛的帖子。一个话题可以有很多人参与(至少发言一次),更应该支持更多的人浏览。我们不应该按聊天室的思路:用户只有在加入聊天室的那一刻开始,才能收到后续的消息,而应该像论坛那样,他只是打开了这个话题帖,可以随时聊天过去到现在发生的事情。话题内的任何一个消息,都可以由用户展开为新话题,老话题对新话题只是一个引用链接而已,并不需要有层级关系,我们也可以把任意一个话题或尚未转化为话题的消息转发出去,如果有人对他评论,就生成了新话题。

话题是一个有时效性的东西,对于办公来说,如果一个话题超过 8 小时没有新的消息,就可以认为这个话题已经结束了。但是事后我们依然可以对老话题浏览,或是继续讨论,而继续讨论就是生成的一个新话题了。

只要生成话题足够方便,每个用户的主时间线上就只会有不多的通知消息,信息传达更为有效。而管理每天的消息、检索旧消息也有很强的时间线。不像现有的 IM 群聊天,每天的聊天内容会被自然的组织成话题,这些话题上标识了参与人数、归属的组织的 tag 、继承于哪个父话题或通知、经历的时间段、衍生出哪些后续话题,等等。

即使是两个人之间的对话,也同样应该是话题的形式,而不应该把消息直接组织成一长串的聊天历史。话题未必有明确的主题,只是一种更自然的信息聚合形式而已。

对于办公场合来说,有意个最重要的优势:用户群有足够的自律。基于这种自律,我认为上面的思路若实现出来很容易推广使用。

在自律之外,或许还需要一些权限管理。这些权限管理应该是相对松散简单的,主要是限制用户订阅特定组织的 tag (比如一般员工不能订阅管理层的 tag ),限制围观特定话题(比如两人之间的私聊话题默认就是不对第三人开放权限的),话题可以锁定不准转发。权限设置的细节还需要进一步推敲。

android锁屏(三) - 老牛比 - CSDN博客

mikel阅读(1014)

来源: android锁屏(三) – 老牛比 – CSDN博客

锁屏服务类。1,启动后首先注册接受屏幕的关闭和开启的广播:广播中处理开屏锁屏任务;2,获取电话管理服务,注册通话状态监听:监听手机响铃状态、挂起状态、空闲状态。以Handler方式执行不同的任务。同样这里也是处理闹钟啊一些可以你认为可以在锁屏时候运行的东西。这里我没写,Dome而已,还是凑合看看吧

  1. import Android.app.KeyguardManager;
  2. import Android.app.Service;
  3. import android.app.KeyguardManager.KeyguardLock;
  4. import android.content.BroadcastReceiver;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.IntentFilter;
  8. import android.os.Handler;
  9. import android.os.IBinder;
  10. import android.os.Message;
  11. import android.os.PowerManager;
  12. import android.telephony.PhoneStateListener;
  13. import android.telephony.TelephonyManager;
  14. import android.util.Log;
  15. public class LockService extends Service {
  16.     protected static final String TAG = “SAFLockService”;
  17.     KeyguardManager mKeyguardManager;
  18.     private KeyguardLock mKeyguardLock = null;
  19.     private TelephonyManager tm;
  20.     private Context mContext;
  21.     @Override
  22.     public void onCreate() {
  23.         super.onCreate();
  24.         mContext = this.getApplicationContext();
  25.         Log.v(TAG, “=============onCreate:================”);
  26.         IntentFilter filter = new IntentFilter();
  27.         filter.addAction(Intent.ACTION_SCREEN_OFF);
  28.         filter.addAction(Intent.ACTION_SCREEN_ON);
  29.         registerReceiver(mScreenReceiver, filter);
  30.         mKeyguardManager = (KeyguardManager) this
  31.                 .getSystemService(Context.KEYGUARD_SERVICE);
  32.         mKeyguardLock = mKeyguardManager.newKeyguardLock(“”);
  33.         mKeyguardLock.disableKeyguard();
  34.         tm = (TelephonyManager) this
  35.                 .getSystemService(Context.TELEPHONY_SERVICE);
  36.         SAFLockPhoneCallListener myPhoneCallListener = new SAFLockPhoneCallListener();
  37.         tm.listen(myPhoneCallListener, PhoneStateListener.LISTEN_CALL_STATE);
  38.         SAFLockConstants.loadInitParam(this);
  39.         this.setForeground(true);
  40.     }
  41.     @Override
  42.     public IBinder onBind(Intent intent) {
  43.         return null;
  44.     }
  45.     @Override
  46.     public void onDestroy() {
  47.         super.onDestroy();
  48.         mKeyguardLock.reenableKeyguard();
  49.         mHandler.removeMessages(SAFLockConstants.EVENT_LOCK_SCREEN);
  50.         unregisterReceiver(mScreenReceiver);
  51.     }
  52.     /**
  53.      * set a listener to listen the phonestate,that we can do somthing by one
  54.      * of phonestates.
  55.      * 
  56.      * @author liao 
  57.      */
  58.     public class SAFLockPhoneCallListener extends PhoneStateListener {
  59.         public void onCallStateChanged(int state, String incomingNumber) {
  60.             switch (state) {
  61.             // Phone in idle
  62.             case TelephonyManager.CALL_STATE_IDLE:
  63.                 Log.v(TAG, “=============CALL_STATE_IDLE:================”);
  64.                 SAFLockConstants.PHONE_STATE = TelephonyManager.CALL_STATE_IDLE;
  65.                 if (SAFLockConstants.isLockecd) {
  66.                     mHandler.removeMessages(SAFLockConstants.EVENT_LOCK_SCREEN);
  67.                     mHandler.sendEmptyMessage(SAFLockConstants.EVENT_LOCK_SCREEN);
  68.                 }
  69.                 break;
  70.             // Phone is hung up
  71.             case TelephonyManager.CALL_STATE_OFFHOOK:
  72.                 Log.v(TAG, “=============CALL_STATE_OFFHOOK:================”);
  73.                 SAFLockConstants.PHONE_STATE = TelephonyManager.CALL_STATE_OFFHOOK;
  74.                 break;
  75.             // Cell phone ring
  76.             case TelephonyManager.CALL_STATE_RINGING:
  77.                 Log.v(TAG, “=============CALL_STATE_RINGING:================”);
  78.                 SAFLockConstants.PHONE_STATE = TelephonyManager.CALL_STATE_RINGING;
  79.                 break;
  80.             default:
  81.                 Log.v(TAG, “=============default:================”);
  82.                 break;
  83.             }
  84.         }
  85.     }
  86.     private Handler mHandler = new Handler() {
  87.         @Override
  88.         public void handleMessage(Message msg) {
  89.             switch (msg.what) {
  90.             case SAFLockConstants.EVENT_LOCK_SCREEN:
  91.                 // ====read user customization
  92.                 SAFLockConstants.updateLockIntent(
  93.                         “saf.cmcc.lock.THEME_DEFAULT”, mContext);
  94.                 // keep the task running 2s in sleeping mode.
  95.                 PowerManager pm = (PowerManager) mContext
  96.                         .getSystemService(Context.POWER_SERVICE);
  97.                 // Keep the CPU running, and the screen and keyboard light may
  98.                 // be closed.
  99.                 PowerManager.WakeLock mWakeLock = pm.newWakeLock(
  100.                         PowerManager.PARTIAL_WAKE_LOCK, “SAFLock”);
  101.                 mWakeLock.acquire(2000L);
  102.                 Intent mLockScreenIntent = new Intent(mContext,
  103.                         SAFFullScreenActivity.class);
  104.                 mLockScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  105.                 mLockScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
  106.                 mContext.startActivity(mLockScreenIntent);
  107.                 break;
  108.             default:
  109.                 break;
  110.             }
  111.         }
  112.     };
  113.     private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
  114.         @Override
  115.         public void onReceive(Context context, Intent intent) {
  116.             String action = intent.getAction();
  117.             if (Intent.ACTION_SCREEN_OFF.equalsIgnoreCase(action)) {
  118.                 // load intent
  119.                 if (SAFLockConstants.LOCK_ENABLE
  120.                         && SAFLockConstants.PHONE_STATE == TelephonyManager.CALL_STATE_IDLE) {
  121.                     // =====send message after a short delay
  122.                     if (!SAFLockConstants.isLockecd) {
  123.                         mHandler.removeMessages(SAFLockConstants.EVENT_LOCK_SCREEN);
  124.                         mHandler.sendEmptyMessageDelayed(
  125.                                 SAFLockConstants.EVENT_LOCK_SCREEN, 100);
  126.                     }
  127.                 }
  128.             } else if (Intent.ACTION_SCREEN_ON.equalsIgnoreCase(action)) {
  129.                 // ====remove unprocessed event.
  130.                 mHandler.removeMessages(SAFLockConstants.EVENT_LOCK_SCREEN);
  131.             }
  132.         }
  133.     };
  134. }

注释是英文的,上头要求,没办法

android 锁屏(二) - 老牛比 - CSDN博客

mikel阅读(920)

来源: android 锁屏(二) – 老牛比 – CSDN博客

这篇呢,我们讲程序的入口。

SAFScreenReceiver.Java :作为锁屏模块的入口。通过获取开机 和 锁屏键的广播,根据SAFScreenReceiver中存储是否允许锁屏,允许则启动锁屏服务

  1. package saf.cmcc.home.lock;
  2. import Android.content.BroadcastReceiver;
  3. import Android.content.Context;
  4. import Android.content.Intent;
  5. import android.util.Log;
  6. /**
  7.  * this class role is to receiver the system broadcast:open phone
  8.  * @author issuser
  9.  *
  10.  */
  11. public class SAFScreenReceiver extends BroadcastReceiver {
  12.     private static final String TAG = “SAFScreenReceiver”;
  13.     @Override
  14.     public void onReceive(Context context,Intent intent) {
  15.         String action = intent.getAction();
  16.         if(Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(action)
  17.                 || Intent.ACTION_USER_PRESENT.equalsIgnoreCase(action)){
  18.             Log.v(TAG, “action:”+action);
  19.             SAFLockConstants.loadInitParam(context);
  20.             if(SAFLockConstants.LOCK_ENABLE){
  21.                 Intent i = new Intent(context, SAFLockService.class);
  22.                 context.startService(i);
  23.             }
  24.         }
  25.     }
  26. }

抱歉,没注释,不知道要写什么的。凑合看着吧

android锁屏原理(一) - 老牛比 - CSDN博客

mikel阅读(1029)

来源: android锁屏原理(一) – 老牛比 – CSDN博客

最近忙着跳槽,跑来移动后的第一个项目就是做Android定制的锁屏模块。目前已经差不多了,就缺UI工程师的图片了。在这里给大家讲讲我做的这个。先画张图吧

 

不知道大家看了这图恶心不,反正我恶心了,呵呵。还是回正题吧

首先从左边看吧,由上而下。1,第一个ScreenReceiver是作为锁屏的入口。接收系统开机和屏幕关闭开启广播,根据设置中保存是否可以锁屏来启动锁屏服务。

2,ScreenService作为锁屏程序中的核心类,启动后注册接收屏幕开/关广播,分别处理对应的任务。其次是对手机状态的监听,就是那个电话的三状态,空闲、响铃、挂起(接听),分别处理。处理闹钟什么的,都一样。呵呵,不在阐述。最后是处理一些锁屏的初始化数据。

3,ScreenActiviy,这个类命名有点2,估计会误导他人。作用很简单,被锁屏服务调用后启动,然后在这个Activity中处理要显示哪个主题的锁屏,就是调用哪个LockScreenActivity。相当于一个控制器(Controller)吧。

4,LockScreenActivity,就是我们手机在锁屏后看到的那个界面。这里的话就要看你怎么处理啦。就是要处理一些解锁动作啊,时间啊,日期啊,电量什么的。这个类代码比较多。后边我会贴上。上个图体现啊,别笑话,目前没图片,随便上网了拔下一些。

很烂吧,哈哈

5,LockTamplateActivity,这个类是是LockScreenActivity的基类(模板类)。没啥,就一些唧唧哇哇的东西,不过还提供了一些给子类的公共方法。还有就是负责屏蔽Home键,回退键,搜索键,菜单,还有状态栏。

6,LockConstants,看名字就知道干嘛了,一个常量类嘛,一些数据的存放,初始化什么的。保存方式是用SharedPreferences。

7,LockSettingActivity嘛,就是设置一些锁屏的功能的,保存还是SharedPreferences。

基本思路:毫无疑问,每次需要显示LockScreen—锁屏界面时,我们并不真正的去锁屏,而只是提供了一个空的方法去给系统调用,让系统觉得我们“锁屏”了,同样也不去真正的隐藏“锁屏”界面,提供一个空壳给系统调用。由于可能涉及 到其它问题,例如:能否下拉状态栏,按下POWER键后,屏幕很快休眠等。Come on ,我们需要统一做处理。——引用一前辈的话

好了,下一篇我会讲讲每个木块的实现。