[转载]asp.net mvc3的静态化实现

mikel阅读(1045)

[转载]asp.net mvc3的静态化实现 – TerryLiang – 博客园.

静态化处理,可以大大提高客户的访问浏览速度,提高用户体验,同时也降低了服务器本身的压力。在ASP.NET mvc3中,可以相对容易地处理静态化问题,不用过多考虑静态网页的同步,生成等等问题。我提供这个方法很简单,就需要在需要静态化处理的 Controller或Action上加一个Attribute就可以。下面是我写的一个生成静态文件的ActionFilterAttribute。

    using System;
  2     using System.IO;
  3     using System.Text;
  4     using System.Web;
  5     using System.Web.Mvc;
  6     using NLog;
  7 
  8     /// <summary>
  9     /// 生成静态文件
 10     /// </summary>
 11     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
 12     public class GenerateStaticFileAttribute : ActionFilterAttribute
 13     {
 14         #region 私有属性
 15 
 16         private static readonly Logger logger = LogManager.GetCurrentClassLogger();
 17 
 18         #endregion
 19 
 20         #region 公共属性
 21 
 22         /// <summary>
 23         /// 过期时间,以小时为单位
 24         /// </summary>
 25         public int Expiration { get; set; }
 26 
 27         /// <summary>
 28         /// 文件后缀名
 29         /// </summary>
 30         public string Suffix { get; set; }
 31 
 32         /// <summary>
 33         /// 缓存目录
 34         /// </summary>
 35         public string CacheDirectory { get; set; }
 36 
 37         /// <summary>
 38         /// 指定生成的文件名
 39         /// </summary>
 40         public string FileName { get; set; }
 41 
 42         #endregion
 43 
 44         #region 构造函数
 45 
 46         /// <summary>
 47         /// 默认构造函数
 48         /// </summary>
 49         public GenerateStaticFileAttribute()
 50         {
 51             Expiration = 1;
 52             CacheDirectory = AppDomain.CurrentDomain.BaseDirectory;
 53         }
 54 
 55         #endregion
 56 
 57         #region 方法
 58 
 59         public override void OnResultExecuted(ResultExecutedContext filterContext)
 60         {
 61             var fileInfo = GetCacheFileInfo(filterContext);
 62 
 63             if ((fileInfo.Exists && fileInfo.CreationTime.AddHours(Expiration) < DateTime.Now) || !fileInfo.Exists)
 64             {
 65                 var deleted = false;
 66 
 67                 try
 68                 {
 69                     if (fileInfo.Exists)
 70                     {
 71                         fileInfo.Delete();
 72                     }
 73 
 74                     deleted = true;
 75                 }
 76                 catch (Exception ex)
 77                 {
 78                     logger.Error("删除文件:{0}发生异常:{1}", fileInfo.FullName, ex.StackTrace);
 79                 }
 80 
 81                 var created = false;
 82 
 83                 try
 84                 {
 85                     if (!fileInfo.Directory.Exists)
 86                     {
 87                         fileInfo.Directory.Create();
 88                     }
 89 
 90                     created = true;
 91                 }
 92                 catch (IOException ex)
 93                 {
 94                     logger.Error("创建目录:{0}发生异常:{1}", fileInfo.DirectoryName, ex.StackTrace);
 95                 }
 96 
 97                 if (deleted && created)
 98                 {
 99                     FileStream fileStream = null;
100                     StreamWriter streamWriter = null;
101 
102                     try
103                     {
104                         var viewResult = filterContext.Result as ViewResult;
105                         fileStream = new FileStream(fileInfo.FullName, FileMode.CreateNew, FileAccess.Write, FileShare.None);
106                         streamWriter = new StreamWriter(fileStream);
107                         var viewContext = new ViewContext(filterContext.Controller.ControllerContext, viewResult.View, viewResult.ViewData, viewResult.TempData, streamWriter);
108                         viewResult.View.Render(viewContext, streamWriter);
109                     }
110                     catch (Exception ex)
111                     {
112                         logger.Error("生成缓存文件:{0}发生异常:{1}", fileInfo.FullName, ex.StackTrace);
113                     }
114                     finally
115                     {
116                         if (streamWriter != null)
117                         {
118                             streamWriter.Close();
119                         }
120 
121                         if (fileStream != null)
122                         {
123                             fileStream.Close();
124                         }
125                     }
126                 }
127             }
128         }
129 
130         /// <summary>
131         /// 生成文件Key
132         /// </summary>
133         /// <param name="controllerContext">ControllerContext</param>
134         /// <returns>文件Key</returns>
135         protected virtual string GenerateKey(ControllerContext controllerContext)
136         {
137             var url = controllerContext.HttpContext.Request.Url.ToString();
138 
139             if (string.IsNullOrWhiteSpace(url))
140             {
141                 return null;
142             }
143 
144             var th = new TigerHash();
145             var data = th.ComputeHash(Encoding.Unicode.GetBytes(url));
146             var key = Convert.ToBase64String(data, Base64FormattingOptions.None);
147             key = HttpUtility.UrlEncode(key);
148 
149             return key;
150         }
151 
152         /// <summary>
153         /// 获取静态的文件信息
154         /// </summary>
155         /// <param name="controllerContext">ControllerContext</param>
156         /// <returns>缓存文件信息</returns>
157         protected virtual FileInfo GetCacheFileInfo(ControllerContext controllerContext)
158         {
159             var fileName = string.Empty;
160 
161             if (string.IsNullOrWhiteSpace(FileName))
162             {
163                 var key = GenerateKey(controllerContext);
164 
165                 if (!string.IsNullOrWhiteSpace(key))
166                 {
167                     fileName = Path.Combine(CacheDirectory, string.IsNullOrWhiteSpace(Suffix) ? key : string.Format("{0}.{1}", key, Suffix));
168                 }
169             }
170             else
171             {
172                 fileName = Path.Combine(CacheDirectory, FileName);
173             }
174 
175             return new FileInfo(fileName);
176         }
177 
178         #endregion
179     }

如果大家对于生成的文件和目录有特殊的要求,那可以重写GetCacheFileInfo方法,比如按照日期生成目录等等更复杂的目录和文件结构。当然以上代码只是提供了生成静态页的方法,但是访问如何解决呢? 访问静态文件和规则就需要在HttpApplication的Application_BeginRequest实现了。首先可以设置需要静态化访问的路由地址以html结尾。下面的是一个用于首页的静态化访问的实现,很简单,当然你可以实现比较复杂的逻辑,比如根据文件时间来判断是否应该访问静态文件等等。

       protected void Application_BeginRequest(object sender, EventArgs e)
 2         {
 3             StaticContentRewrite();
 4         }
 5 
 6         /// <summary>
 7         /// 处理静态发布内容
 8         /// </summary>
 9         private void StaticContentRewrite()
10         {
11             if (Context.Request.FilePath == "/" || Context.Request.FilePath.StartsWith("/index.html", StringComparison.OrdinalIgnoreCase))
12             {
13                 if (File.Exists(Server.MapPath("index.html")))
14                 {
15                     Context.RewritePath("index.html");
16                 }
17             }

18         } 

[转载]Asp.Net MVC与Template 4 定制的视图模板

mikel阅读(907)

[转载]AspNet MVC与T4,我定制的视图模板 – fchzzwcsr201 – 博客园.

一. 遇到的问题

文章开头部分想先说一下自己的困惑,在用AspNet MVC时,完成Action的编写,然后添加一个视图,这个时候弹出一个添加视图的选项窗口,如下:

很熟悉吧,继续上面说的,我添加一个视图,强类型的、继承母版页的视图,点击确定,mvc会为我们添加一些自动生成的代码,感觉很方便。呵呵,刚开始的时候还真方便一些,但也仅仅只是方便一些而已。当遇到以下情景的时候,可能我们就不觉得了:

程序中都要对N个实体类进行CRUD,就只说添加的功能,生成一个强类型的Create视图,但是这个自带的Create视图的布局可能并不能 符合我们界面的要求,没关系啊,这个改改界面就ok了,这个是个不错的方法。但是有N个实体要进行CRUD的时候工作量就是time*N了,而且因为要求 页面风格一致,我们几乎在做一样的工作,有必要吗?

当然没必要了,应该把重复的工作交给程序去做,省点时间去……

举个例子吧,自带的Create视图使用的是div进行排版的,而我们需要的是table来进行排版,这个改起来不难,但蛮麻烦的。为什么我就不能定制Create视图的模板,让它生成我想要的布局呢?这个可以有。

二. 解决问题

经过多方搜罗,终于找到了AspNet MVC中用于生成这些视图的东东:T4(Text Template Transformation Toolkit)文本模板转换工具箱。在MVC项目中正是使用了T4来生成视图模板的,它藏在哪呢?是在你VS安装目录下:…\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web,在这个文件夹下面有MVC2、MVC3和MVC4的模板,创建什 么项目就会用到对应的模板。这里只演示MVC3 生成Razor的,用2和4的同学就自己摸索了,都差不多的。在MVC3文件夹里面的CodeTemplates文件夹中包含了生成Controller 和View两个文件夹。这里只说视图的,Controller里面的东西也可以去试试。在…\MVC 3\CodeTemplates\AddView\CSHTML,在这个文件夹下我们可以看到:

这些正好是在创建强类型视图时系统自带的模板。好了,源头找到了,也应该进行修改了吧。不急,还有一点东西要了解,T4的基本编写语法:

T4基本语法

T4包括三个部分:

Directives(指令) 元素,用于控制模板如何被处理
Texts blocks(文本块) 用于直接复制到输出文件
Control blocks(控制块) 编程代码,用于控制变量显示文本

1)指令

语法:

<#@ DirectiveName [AttributeName = “AttributeValue”] … #>

常用的指令

<#@ template [language=”C#”] [hostspecific=”true”] [Debug=”true”] [inherits=”templateBaseClass”] [culture=”code”] [complierOption=”options”] #>

<#@ parameter type=”Full.TypeName” name=”ParameterName” #>

<#@ output extension=”.fileNameExtension” [encoding=”encoding”] #>

<#@ assembly name=”[assembly strong name| assembly file name]” #>

<#@ import namespace=”namespace” #>

<#@ include file=”filepath” #>

2)文本块

只需要输入文本就可以了

3)控制块
<# #> 代码表达式
<#= #> 显示表达式值
<#+ #> 声明定义方法、变量
T4和MVC2中的界面编码非常类似,这就不用多说了吧。

其实不仅仅有页面布局的问题,还有数据显示的问题,验证的问题等等,只要是界面上需要重复编写的东西都可以使用T4来减少工作量。

新建一个视图模板

将CodeTemplates文件夹拷贝到项目程序的根目录下,覆盖默认视图模板。可以在MVC自带视图模板基础上进行修改,也可以自己新创建 一个。建议做法是新建一个视图模板,是Text Template文件.tt后缀的,然后将要改动的系统视图代码复制过来,再进行修改。在此之前,选中所有CodeTemplates文件夹中的tt文 件,右键属性,将Custome Tool项默认值清掉,原因还不清楚,知道的大虾指点下啊。(不去掉的话编译不通过,缺少了xxx程序集;也可以添加xxx程序集到项目中,不过没这个必 要)

如本文修改Create模板,新建一个newCreate.tt文件,与Create.tt文件在同一文件夹内,将Create.tt内容复制到newCreate.tt中,在此基础上进行修改。

浏览一下newCreate.tt的代码

包括设定输出文件格式,引入程序集和命名空间。这些和我们编写cs时差不多,不多讲了。

最关键的是MvcTextTemplateHost这个类,这个类存储着视图信息,如视图名、视图类型(部分视图、强类型视图)、是否继承母版 页等,具体的内容是有文章开篇的那一张图中设定的内容,即添加视图窗口的信息将会保存到MvcTextTemplateHost这个类实例去。好,那么这 个类藏在哪呢?上网搜了一下,VS2008 sp1是在…\Microsoft Visual Studio 9.0\Common7\IDE\Microsoft.VisualStudio.Web.Extensions.dll这个程序集中定义的。不过我找了 好久都没找到,可能是因为我用的是VS2010吧。还好最终还是找到了,是在…\Microsoft Visual Studio 10.0\Common7\IDE\Microsoft.VisualStudio.Web.Mvc.2.0.dll这个程序集中定义的,在Microsoft.VisualStudio.Web.Mvc命名空间内。具体的内容用Reflector反编译看下就知道了。

啰嗦了好多东西,现在也该进入正题了。

修改视图生成界面

将默认视图中div排版改成table排版,修改里面的back to list、Create等英文单词为中文。这个是最简单的修改了,只需修改tt文件的文本块内容就可以了。直接上效果图了:

添加视图,这时我们自己新定义的模板已经在列表框中了:

两个模板的效果,左边是由newCreate模板生成的,右边是由Create模板生成

这个只是做了一下简单的页面修改,想怎么改就看具体要求了。

JQuery联用,自动添加时间插件

如果仅仅只是修改一下界面,那真的没必要这么大费周章的,我们还可以再进一步改进。在出生日期这一编辑框中,我们往往都会使用一个JQuery 时间插件来美化。那我们可不可以让newCreate.tt文本模板在检测到DateTime类型时自动添加js代码,答案是可以的。

在开始之前肯定是要先下载需要的js文件,布置到项目中。这里就只贴出关键部分的代码和效果图:

View Code

<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-1.8.12.custom.min.js")" type="text/javascript"></script>
    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-i18n.min.js")" type="text/javascript"></script>
    <script src="@Url.Content( "~/Content/jquery-ui-timepicker-addon.js")" type="text/javascript"></script>
    <link href="@Url.Content( "~/Content/jquery-ui/redmond/jquery-ui-1.8.5.custom.css")" rel="stylesheet" type="text/css" />
    <script type="text/javascript">
        $(document).ready(function () {
            // 自动绑定时间插件
            <#
        foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
            if (property.UnderlyingType == typeof(DateTime)) {
        #>
        $('#<#= property.Name #>').datetimepicker({
            currentText: '当前时间',
            closeText: '完成',
            timeText: '时间',
            hourText: '小时',
            minuteText: '分钟',
            dateFormat: 'yy年mm月dd日',
        });
        <#
            }
        }
        #>
        });
    </script>

这样只要程序中有用到时间的地方就会自动生成js代码然后就方便很多了。当然可以写个MVC的html扩展方法来实现,方便的程度差不多吧。

数据验证

还可以再改进么?必然可以,只要你想得到。数据验证,这个在添加编辑数据的地方都会用到。这回不说能够全自动吧,起码也是半自动。其实在MVC 中已经有通过后台编写Metadata来进行数据验证了,不过那个是在后台。这回要做的是在前端页面进行半自动添加js验证代码。要想做得方便的话就得自 己编写一些js代码,这个肯定要的,方便的前提是先需要复杂一段时间(不过也没多复杂)。

在做验证的时候,我们无非要验证不可空、验证数字、手机号码、邮件地址等这些东西。还有一点是js代码是通过文本模板生成的,这就要求我们需要 创建一个通用的验证函数。这个函数怎么设计呢?想想,每一个验证都会对应一个form表单、需要验证的格式、验证不通过时的提示信息。也就是说这个js函 数有三个参数:

1 被验证的表单的id:这个可以在文本模板中获得

2 验证的格式:这个编写一个js的枚举类型吧,把要用到的所有格式的正则表达式写好。

3 提示信息:这个总不能也要自动生成吧

说了思路我就直接贴代码和效果图了,想去了解的可以下载程序来看一下。

看一下这个自动生成的js代码:

第一个formValidatorRegex.js文件存储的就是各种格式的正则表达式。

第二个formValidatorUI.js文件是checkValue这个验证函数的定义。

自动生成之后就可以做一些小的修改来达到我们需要的验证功能了,两个地方要修改的,第一个就是设置验证格式,第二个是填写提示信息。

现在把newCreate.tt的全部代码献上:

View Code

<#@ template language="C#" HostSpecific="True" #>
<#@ output extension=".cshtml" #>
<#@ assembly name="System.ComponentModel.DataAnnotations" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="System.Data.Linq" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.ComponentModel.DataAnnotations" #>
<#@ import namespace="System.Data.Linq.Mapping" #>
<#@ import namespace="System.Data.Objects.DataClasses" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
<#
MvcTextTemplateHost mvcHost = (MvcTextTemplateHost)(Host);  // 关键类,其实例是通过Add View窗口所做的设定获取的
#>
@model <#= mvcHost.ViewDataTypeName #>    
<#
// The following chained if-statement outputs the file header code and markup for a partial view, a content page, or a regular view.
if(mvcHost.IsPartialView) {    // 部分视图
#>

<#
} else if(mvcHost.IsContentPage) {    // 内容页
#>

@{
    ViewBag.Title = "<#= mvcHost.ViewName#>";    @*ViewName视图名,第一张图中的View name 中的值*@
<#
if (!String.IsNullOrEmpty(mvcHost.MasterPageFile)) {    // 母版页
#>
    Layout = "<#= mvcHost.MasterPageFile#>";    @*MasterPageFile母版页路径*@
<#
}
#>
}

<h2><#= mvcHost.ViewName#></h2>

<#
} else {
#>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title><#= mvcHost.ViewName #></title>
</head>
<body>
<#
    PushIndent("");
}
#>
<#
if (mvcHost.ReferenceScriptLibraries) {    // ReferenceScriptLibraries是否勾取了引入javascript脚本库
#>
<#
    if (!mvcHost.IsContentPage) {
#>
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<#
    }
#>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<#
}
#>
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend><#= mvcHost.ViewDataType.Name #></legend>
        <table class="cls">
<#
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
    if (!property.IsPrimaryKey && !property.IsReadOnly) {
#>
        <tr>
            <td>
                @Html.LabelFor(model => model.<#= property.Name #>)
            </td>
            <td>
                @Html.EditorFor(model => model.<#= property.Name #>)
                @Html.ValidationMessageFor(model => model.<#= property.Name #>)
            </td>
        </tr>
<#
    }
}
#>
        <tr>
            <td></td>
            <td><input type="submit" id="submit1" value="创建" /></td>
        </tr>
        </table>
    </fieldset>
}

<div>
    @Html.ActionLink("返回列表", "Index")
</div>
<div id="alertDialog" title="消息提示"></div>
    
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-1.8.12.custom.min.js")" type="text/javascript"></script>
    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-i18n.min.js")" type="text/javascript"></script>
    <script src="@Url.Content( "~/Content/jquery-ui-timepicker-addon.js")" type="text/javascript"></script>
    <link href="@Url.Content( "~/Content/jquery-ui/redmond/jquery-ui-1.8.5.custom.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Content/formValidatorRegex.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Content/formValidatorUI.js")" type="text/javascript"></script>

    <script type="text/javascript">
        $(document).ready(function () {
        // 添加验证代码
        $("#submit1").click(function () {
                if (1==2  //初始化,验证默认不通过(验证时将其删除)
                <#
                foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
                #>
                    && checkValue($("#<#= property.Name #>").val(), "", "")
                <#
                }
                #>
                ) {  //checkValue($("#").val(), regexEnum, "") 
                     //第1个参数是需要验证的值
                     //第2个参数为正则表达式
                     //第3个参数是验证不通过的提示信息
                    return true;
                }
                return false;
            });

        // 自动绑定时间插件
            <#
        foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
            if (property.UnderlyingType == typeof(DateTime)) {
        #>
        $('#<#= property.Name #>').datetimepicker({
            currentText: '当前时间',
            closeText: '完成',
            timeText: '时间',
            hourText: '小时',
            minuteText: '分钟',
            dateFormat: 'yy年mm月dd日',
        });
        <#
            }
        }
        #>
        });
    </script>


<#
// The following code closes the asp:Content tag used in the case of a master page and the body and html tags in the case of a regular view page
#>
<#
if(!mvcHost.IsPartialView && !mvcHost.IsContentPage) {
    ClearIndent();
#>
</body>
</html>
<#
}
#>

<#+
// Describes the information about a property on the model
class ModelProperty {
    public string Name { get; set; }
    public string ValueExpression { get; set; }
    public Type UnderlyingType { get; set; }
    public bool IsPrimaryKey { get; set; }
    public bool IsReadOnly { get; set; }
}

// Change this list to include any non-primitive types you think should be eligible for display/edit
static Type[] bindableNonPrimitiveTypes = new[] {
    typeof(string),
    typeof(decimal),
    typeof(Guid),
    typeof(DateTime),
    typeof(DateTimeOffset),
    typeof(TimeSpan),
};

// Call this to get the list of properties in the model. Change this to modify or add your
// own default formatting for display values.
List<ModelProperty> GetModelProperties(Type type) {
    List<ModelProperty> results = GetEligibleProperties(type);
    
    foreach (ModelProperty prop in results) {
        if (prop.UnderlyingType == typeof(double) || prop.UnderlyingType == typeof(decimal)) {
            prop.ValueExpression = "String.Format(\"{0:F}\", " + prop.ValueExpression + ")";
        }
        else if (prop.UnderlyingType == typeof(DateTime)) {
            prop.ValueExpression = "String.Format(\"{0:g}\", " + prop.ValueExpression + ")";
        }
    }

    return results;
}

// Call this to determine if the property represents a primary key. Change the
// code to change the definition of primary key.
bool IsPrimaryKey(PropertyInfo property) {
    if (string.Equals(property.Name, "id", StringComparison.OrdinalIgnoreCase)) {  // EF Code First convention
        return true;
    }

    if (string.Equals(property.Name, property.DeclaringType.Name + "id", StringComparison.OrdinalIgnoreCase)) {  // EF Code First convention
        return true;
    }

    foreach (object attribute in property.GetCustomAttributes(true)) {
        if (attribute is KeyAttribute) {  // WCF RIA Services and EF Code First explicit
            return true;
        }
        
        var edmScalar = attribute as EdmScalarPropertyAttribute;
        if (edmScalar != null && edmScalar.EntityKeyProperty) {  // EF traditional
            return true;
        }

        var column = attribute as ColumnAttribute;
        if (column != null && column.IsPrimaryKey) {  // LINQ to SQL
            return true;
        }
    }
    
    return false;
}

// This will return the primary key property name, if and only if there is exactly
// one primary key. Returns null if there is no PK, or the PK is composite.
string GetPrimaryKeyName(Type type) {
    IEnumerable<string> pkNames = GetPrimaryKeyNames(type);
    return pkNames.Count() == 1 ? pkNames.First() : null;
}

// This will return all the primary key names. Will return an empty list if there are none.
IEnumerable<string> GetPrimaryKeyNames(Type type) {
    return GetEligibleProperties(type).Where(mp => mp.IsPrimaryKey).Select(mp => mp.Name);
}

// Helper
List<ModelProperty> GetEligibleProperties(Type type) {
    List<ModelProperty> results = new List<ModelProperty>();

    foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) {    // 遍历所有公共实例属性
        Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;    // 去掉可空之后的类型
        // GetGetMethod()获取公共get访问器(就是属性定义中的get方法)、GetIndexParameters()获取索引器
        // 这个判断条件就是属性必须有get方法,且不能为索引器类型,再一个返回类型必须是基元类型或者[String、Guid、DateTime、TimeSpan、decimal、TimeSpan、DateTimeOffset]这些类型之一
        if (prop.GetGetMethod() != null && prop.GetIndexParameters().Length == 0 && IsBindableType(underlyingType)) {
            results.Add(new ModelProperty {    // 添加到ModelProperty
                Name = prop.Name,
                ValueExpression = "Model." + prop.Name,
                UnderlyingType = underlyingType,
                IsPrimaryKey = IsPrimaryKey(prop),
                IsReadOnly = prop.GetSetMethod() == null
            });
        }
    }

    return results;
}

// Helper
bool IsBindableType(Type type) {
    return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);
}

#>

好了,写得差不多了。其实还好很多可以改进的地方,感兴趣的同学可以再去修改一下默认的controller模板,然后再结合自己自定的视图模板,完成一个实体的简单增删改查应该用10分钟就可以搞定了,只是简单的,不要想太多了。

程序下载:MvcT4Project

[转载]android主流UI布局

mikel阅读(746)

[转载]android主流UI布局 – 左浪国 – 博客园.

这篇文章主要说的是Android中的UI设定。先看真题效果图,说明。本程序参考新浪微博,图片为猫扑图片。

程序只有最基本的模版,没有任何内容。

UI布局

点击效果图

点击效果

这布局为顶部+中间内容+底部模式,现在很多布局都采用这种模式。或者九宫图布局

1:首先我们先实现顶部,代码如下:

<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/main_tab_banner"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_title"
        android:paddingLeft="10dip"
        android:paddingRight="10dip">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left|center_vertical"
            android:padding="8dip"
            android:text="返回"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="标题内容"
            android:textColor="#000000"
            android:textSize="18dip"/>

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right|center_vertical"
            android:padding="8dip"
            android:text="前进"/>
    </FrameLayout>

这里的TextView可以用一张图片或者一个按钮代替,看个人需要。

2:实现底部。底部采用TabHost,效果图:

底部

代码如下:

 <LinearLayout
        android:id="@+id/main_tab"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@drawable/bg_foot"
        android:gravity="center"
        android:orientation="horizontal">

        <TabHost
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/main_tab2"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">

            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical">

                <FrameLayout
                    android:id="@android:id/tabcontent"
                    android:layout_width="fill_parent"
                    android:layout_height="0.0dip"
                    android:layout_weight="1.0"/>

                <TabWidget
                    android:id="@android:id/tabs"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="0.0"
                    android:visibility="gone"/>

                <RadioGroup
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="bottom"
                    android:background="@drawable/bg_footbar"
                    android:gravity="center_vertical"
                    android:orientation="horizontal">

                    <RadioButton
                        android:id="@+id/radio_button1"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_zy01"
                        android:text="主页"/>

                    <RadioButton
                        android:id="@+id/radio_button2"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_dp01"
                        android:text="地盘"/>

                    <RadioButton
                        android:id="@+id/radio_button3"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_tt01"
                        android:text="贴帖"/>

                    <RadioButton
                        android:id="@+id/radio_button4"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_gd01"
                        android:text="更多"/>
                </RadioGroup>
            </LinearLayout>
        </TabHost>
    </LinearLayout>

底部有很多做法,这里只是纯模版。

3:把顶部和底部加起来。

 <LinearLayout
        android:id="@+id/main_tab_container"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@id/main_tab"
        android:layout_below="@id/main_tab_banner"
        android:background="#FFFFE0">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dip"
            android:text="中间内容"/>
    </LinearLayout>

最后我们的整体代码就出来了。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/main_tab_banner"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_title"
        android:paddingLeft="10dip"
        android:paddingRight="10dip">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left|center_vertical"
            android:padding="8dip"
            android:text="返回"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="标题内容"
            android:textColor="#000000"
            android:textSize="18dip"/>

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right|center_vertical"
            android:padding="8dip"
            android:text="前进"/>
    </FrameLayout>

    <LinearLayout
        android:id="@+id/main_tab"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@drawable/bg_foot"
        android:gravity="center"
        android:orientation="horizontal">

        <TabHost
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/main_tab2"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">

            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical">

                <FrameLayout
                    android:id="@android:id/tabcontent"
                    android:layout_width="fill_parent"
                    android:layout_height="0.0dip"
                    android:layout_weight="1.0"/>

                <TabWidget
                    android:id="@android:id/tabs"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="0.0"
                    android:visibility="gone"/>

                <RadioGroup
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="bottom"
                    android:background="@drawable/bg_footbar"
                    android:gravity="center_vertical"
                    android:orientation="horizontal">

                    <RadioButton
                        android:id="@+id/radio_button1"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_zy01"
                        android:text="主页"/>

                    <RadioButton
                        android:id="@+id/radio_button2"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_dp01"
                        android:text="地盘"/>

                    <RadioButton
                        android:id="@+id/radio_button3"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_tt01"
                        android:text="贴帖"/>

                    <RadioButton
                        android:id="@+id/radio_button4"
                        style="@style/tab_style"
                        android:layout_marginTop="2.0dip"
                        android:drawableTop="@drawable/ico_gd01"
                        android:text="更多"/>
                </RadioGroup>
            </LinearLayout>
        </TabHost>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/main_tab_container"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@id/main_tab"
        android:layout_below="@id/main_tab_banner"
        android:background="#FFFFE0">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dip"
            android:text="中间内容"/>
    </LinearLayout>

</RelativeLayout>

styles.xml文件代码

<?xml version="1.0" encoding="UTF-8"?>
<resources>
   
    <style name="tab_style">
        <item name="android:textSize">9.0dip</item>
        <item name="android:ellipsize">middle</item>
        <item name="android:gravity">center</item>
        <item name="android:background">@drawable/radio_bg</item>
        <item name="android:layout_width">fill_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">2.0dip</item>
        <item name="android:button">@null</item>
        <item name="android:singleLine">true</item>
        <item name="android:layout_weight">1.0</item>
    </style>
   
</resources>

radio_bg.xml代码

<?xml version="1.0" encoding="UTF-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true" android:state_enabled="true" android:drawable="@drawable/home_btn_bg_s"/>
    <item android:state_enabled="true" android:state_pressed="true" android:drawable="@drawable/home_btn_bg_s"/>
    <item android:state_enabled="true" android:state_checked="true" android:drawable="@drawable/home_btn_bg_d"/>
    
</selector>

源码下载http://115.com/file/bhqy968k# Test.7z.建议用400*800分辨率测试

转载请注明出处:小左程序 http://xzuo.cnblogs.com/

[转载]让Django支持数据库长连接

mikel阅读(1110)

[转载]让Django支持数据库长连接(可以提高不少性能哦) – 亚历山大同志 – 博客园.

书接上回

上回我们说到:《在生产系统使用Tornado WebServer来代替FastCGI加速你的Django应用

那么现在很流行用一些高性能的nonblock的app server来host Django的应用,这些Server可以看做是一个单进程单线程的程序,然后用nginx在前端反向代理并且负载均衡到N多个后端工作进城来充分利用多 CPU的性能,当然这部分的配置工作在上回已经说得很清楚了。但是对于Django来说有一个问题。因为Django的数据库连接是在查询的时候实时创建 的,用完就会关掉,这样就会频繁的开闭连接。但是对于Tornado这种Server来说这种方式是低效的。这种Server最高效的工作模式是每个进程 开启一个连接,并长期保持不关闭。本文的目的就是尝试使Django改变一贯的作风,采用这种高效的工作模式。本文基于Django1.3的版本,如果是 低版本可以稍加更改一样可以使用。

Django的数据库可以通过配置使用专门定制的Backend,我们就从这里入手。

首先我们看看Django自带的Backend是如何实现的。在Django官网上可以看到自带MySQL的Package结构,可以点击 此处 前往瞻仰。

通观源码我们可以发现,Django基本上是封装了MySQLdb的Connection和Cursor这两个对象。而且重头实现整个 Backend既不实际而且也不能从根本上解决问题。所以我们可以换一个思路。所有的数据库操作都是从获取Connection对象开始的,而获取 Connection对象只有一个入口,就是MySQLdb.connect这个函数。所以我们只需要包装MySQLdb这个模块,用我们自己的 connect方法替代原本的,这样就从根源上解决了问题。我们在包装器内部维护MySQLdb的Connection对象,使其保持长连接,每次 connect被调用的时候判断一下,如果连接存在就返回现有连接,不就完美了吗?所以我们可以分分钟写下第一个解决方案:

01 proxies = {}
02
03 class _DbWrapper():
04 def __init__(self,module):
05 self.connection=None #这个就是维护的长连接对象
06 self.db=module #这个是被包装的原生MySQLdb的module
07
08 def __getattr__(self, key):
09 return getattr(self.db, key) #代理所有不关心的函数
10
11 def connect(self,*argv,**kwargv):
12 “”“
13 替换原有的connection对象
14 ”“”
15 if not self.connection:
16 self.connection=self.db.connect(*argv,**kwargv)
17 return _ConnectionWrapper(self.connection)
18
19 def manage(module,keepalive=7*3600):
20 “”“
21 返回代替原生MySQLdb模块的对象
22 ”“”
23 try:
24 return proxies[module]
25 except KeyError:
26 return proxies.setdefault(module,_DbWrapper(module))

把上面代码存到一个叫pool.py的文件里。然后把Django源码里的db/backend/mysql这个package拷贝出来,单独存到 我们project目录里一个mysql_pool的目录里。然后修改其中的base.py,在顶上import的部分,找到 import MySQLdb as Database 这句,用下面代码替换之

1 try:
2 import MySQLdb as Database
3 Database = pool.manage(Database)
4 except ImportError, e:
5 from django.core.exceptions import ImproperlyConfigured
6 raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)

这样我们就用自己的模块替换了MySQLdb的,当要connect的时候判断到有连接的时候就不重新创建连接了。

把站点跑起来看,结果如何?刷新几次后报错了。Why?看看日志可以看到如下的错误:

Traceback (most recent call last):
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/gevent/wsgi.py”, line 114, in handle
result = self.server.application(env, self.start_response)
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/core/handlers/wsgi.py”, line 275, in __call__
signals.request_finished.send(sender=self.__class__)
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/dispatch/dispatcher.py”, line 172, in send
response = receiver(signal=self, sender=sender, **named)
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/db/__init__.py”, line 85, in close_connection
conn.close()
File “/home/www/.virtualenvs/django13/lib/python2.7/site-packages/django/db/backends/__init__.py”, line 244, in close
self.connection.close()

看来我们光是包装了MySQLdb本身还不行,在connect后Django获取了Connection的对象,之后就能为所欲为,他用完后很自 觉的关掉了,因为他直觉的以为每次connect都拿到了新的Connection对象。所以我们必须把Connection对象也包装了了。所以升级后 的解决方案代码如下:

01 proxies = {}
02
03 class _ConnectionWrapper(object):
04 """
05 用来包装Connection的类
06 """
07 def __init__(self,conn):
08 self.conn=conn
09
10 def close(self):
11 """
12 屏蔽掉关闭连接的行为
13 """
14 pass
15
16 def __getattr__(self,key):
17 """
18 把其他属性都原封不动的代理出去
19 """
20 return getattr(self.conn, key)
21
22 class _DbWrapper():
23 """
24 代理MySQLdb模块的对象
25 """
26 def __init__(self,module):
27 self.connection=None #HOLD住的长连接
28 self.db=module #原始的MySQLdb模块
29
30 def __getattr__(self, key):
31 """
32 代理除connect外的所有属性
33 """
34 return getattr(self.db, key)
35
36 def connect(self,*argv,**kwargv):
37 if not self.connection:
38 self.connection=self.db.connect(*argv,**kwargv)
39 return _ConnectionWrapper(self.connection)
40
41 def manage(module):
42 try:
43 return proxies[module]
44 except KeyError:
45 return proxies.setdefault(module,_DbWrapper(module))

我们增加了一个_ConnectionWrapper类来代理Connection对象,然后屏蔽掉close函数。把站点跑起来后发现不会出 现之前的问题了,跑起来也顺畅不少。但是过了几个小时后问题又来了。因为MySQLdb的Connection有个很蛋痛的问题,就是连接闲置8小时后会 自己断掉。不过要解决这个问题很简单,我们发现连接如果闲置了快8小时就close掉重新建立一个连接不就行了么?所以最后解决方案的代码如下:

01 import time
02
03 proxies = {}
04
05 class _ConnectionWrapper(object):
06 def __init__(self,conn):
07 self.conn=conn
08
09 def close(self):
10 pass
11
12 def __getattr__(self,key):
13 return getattr(self.conn, key)
14
15 class _DbWrapper():
16 def __init__(self,module,max_idle):
17 self.connection=None
18 self.db=module
19 self.max_idle=max_idle
20 self.connected=0
21
22 def __getattr__(self, key):
23 return getattr(self.db, key)
24
25 def connect(self,*argv,**kwargv):
26 if not self.connection or time.time()-self.connected>=self.max_idle:
27 try:
28 if self.connection:
29 self.connection.close()
30 except:
31 pass
32 self.connection=self.db.connect(*argv,**kwargv)
33 self.connected=time.time()
34 return _ConnectionWrapper(self.connection)
35
36 def manage(module,keepalive=7*3600):
37 try:
38 return proxies[module]
39 except KeyError:
40 return proxies.setdefault(module,_DbWrapper(module,keepalive))

就此问题解决,世界终于清净了

[转载]国内图片网站Yupoo的架构

mikel阅读(866)

[转载]国内图片网站Yupoo的架构 – 一个寂寞的分享者 – 博客园.

之前向大家介绍过全球最大在线图片服务网站Flickr网站架构,Yupoo(又拍网)作为国内最大的图片服务提供商,我们也一起来看看它的架构,同样是提供图片服务,看看他与Flickr的差别在哪里,大家看完本文可以思考一下。

一、先来看看Yupoo网站的基本信息:

带宽:4000M/S (参考)
服务器数量:60 台左右
Web服务器:Lighttpd, Apache, nginx
应用服务器:Tomcat

其他:Python, Java, MogileFS 、ImageMagick 等

其架构图如下:

image

原图链接

二、关于 Squid 与 Tomcat

Squid 与 Tomcat 似乎在 Web 2.0 站点的架构中较少看到。我首先是对 Squid 有点疑问,对此阿华的解释是”目前暂时还没找到效率比 Squid 高的缓存系统,原来命中率的确很差,后来在 Squid 前又装了层 Lighttpd, 基于 url 做 hash, 同一个图片始终会到同一台 squid 去,所以命中率彻底提高了”

对于应用服务器层的 Tomcat,现在 Yupoo! 技术人员也在逐渐用其他轻量级的东西替代,而 YPWS/YPFS 现在已经用 Python 进行开发了。

名次解释:

  • YPWS–Yupoo Web Server YPWS 是用 Python开发的一个小型 Web 服务器,提供基本的 Web 服务外,可以增加针对用户、图片、外链网站显示的逻辑判断,可以安装于任何有空闲资源的服务器中,遇到性能瓶颈时方便横向扩展。
  • YPFS–Yupoo File System 与 YPWS 类似,YPFS 也是基于这个 Web 服务器上开发的图片上传服务器。

【Updated: 有网友留言质疑 Python 的效率,Yupoo 老大刘平阳在 del.icio.us 上写到 “YPWS用Python自己写的,每台机器每秒可以处理294个请求, 现在压力几乎都在10%以下”】

三、图片处理层

接 下来的 Image Process Server 负责处理用户上传的图片。使用的软件包也是 ImageMagick,在上次存储升级的同时,对于锐化的比率也调整过了(我个人感觉,效果的确好了很多)。”Magickd“ 是图像处理的一个远程接口服务,可以安装在任何有空闲 CPU资源的机器上,类似 Memcached的服务方式。

我们知道 Flickr 的缩略图功能原来是用 ImageMagick 软件包的,后来被雅虎收购后出于版权原因而不用了(?);EXIF 与 IPTC Flicke 是用 Perl 抽取的,我是非常建议 Yupoo! 针对 EXIF 做些文章,这也是潜在产生受益的一个重点。

四、图片存储层

原来 Yupoo! 的存储采用了磁盘阵列柜,基于 NFS 方式的,随着数据量的增大,”Yupoo! 开发部从07年6月份就开始着手研究一套大容量的、能满足 Yupoo! 今后发展需要的、安全可靠的存储系统“,看来 Yupoo! 系统比较有信心,也是满怀期待的,毕竟这要支撑以 TB 计算的海量图片的存储和管理。我们知道,一张图片除了原图外,还有不同尺寸的,这些图片统一存储在 MogileFS 中。

对于其他部 分,常见的 Web 2.0 网站必须软件都能看到,如 MySQL、Memcached 、Lighttpd 等。Yupoo! 一方面采用不少相对比较成熟的开源软件,一方面也在自行开发定制适合自己的架构组件。这也是一个 Web 2.0 公司所必需要走的一个途径。

五、分库设计

和很多使用MySQL的2.0站点一样,又拍网的MySQL集群经历了从最初的一个主库一个从库、到一个主库多个从库、 然后到多个主库多个从库的一个发展过程。

image

图3:数据库的进化过程

最 初是由一台主库和一台从库组成,当时从库只用作备份和容灾,当主库出现故障时,从库就手动变成主库,一般情况下,从库不作读写操作(同步除外)。随着压力 的增加,我们加上了memcached,当时只用其缓存单行数据。 但是,单行数据的缓存并不能很好地解决压力问题,因为单行数据的查询通常很快。所以我们把一些实时性要求不高的Query放到从库去执行。后面又通过添加 多个从库来分流查询压力,不过随着数据量的增加,主库的写压力也越来越大。

在参考了一些相关产品和其它网站的做法后,我们决定进行数据库拆分。也就是将数据存放到不同的数据库服务器中,一般可以按两个纬度来拆分数据:

垂直拆分:是指按功能模块拆分,比如可以将群组相关表和照片相关表存放在不同的数据库中,这种方式多个数据库之间的表结构不同

水平拆分:而水平拆分是将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同

拆分方式

一 般都会先进行垂直拆分,因为这种方式拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。但是垂直拆分方式并不能彻底解决所有压力问题,另外, 也要看应用类型是否合适这种拆分方式。如果合适的话,也能很好的起到分散数据库压力的作用。比如对于豆瓣我觉得比较适合采用垂直拆分, 因为豆瓣的各核心业务/模块(书籍、电影、音乐)相对独立,数据的增加速度也比较平稳。不同的是,又拍网的核心业务对象是用户上传的照片,而照片数据的增 加速度随着用户量的增加越来越快。压力基本上都在照片表上,显然垂直拆分并不能从根本上解决我们的问题,所以,我们采用水平拆分的方式。

拆分规则

水 平拆分实现起来相对复杂,我们要先确定一个拆分规则,也就是按什么条件将数据进行切分。 一般2.0网站都以用户为中心,数据基本都跟随用户,比如用户的照片、朋友和评论等等。因此一个比较自然的选择是根据用户来切分。每个用户都对应一个数据 库,访问某个用户的数据时, 我们要先确定他/她所对应的数据库,然后连接到该数据库进行实际的数据读写。

那么,怎么样对应用户和数据库呢?我们有这些选择:

按算法对应

最 简单的算法是按用户ID的奇偶性来对应,将奇数ID的用户对应到数据库A,而偶数ID的用户则对应到数据库B。这个方法的最大问题是,只能分成两个库。另 一个算法是按用户ID所在区间对应,比如ID在0-10000之间的用户对应到数据库A, ID在10000-20000这个范围的对应到数据库B,以此类推。按算法分实现起来比较方便,也比较高效,但是不能满足后续的伸缩性要求,如果需要增加 数据库节点,必需调整算法或移动很大的数据集, 比较难做到在不停止服务的前提下进行扩充数据库节点。

按索引/映射表对应

这 种方法是指建立一个索引表,保存每个用户的ID和数据库ID的对应关系,每次读写用户数据时先从这个表获取对应数据库。新用户注册后,在所有可用的数据库 中随机挑选一个为其建立索引。这种方法比较灵活,有很好的伸缩性。一个缺点是增加了一次数据库访问,所以性能上没有按算法对应好。

比较之后,我们采用的是索引表的方式,我们愿意为其灵活性损失一些性能,更何况我们还有memcached, 因为索引数据基本不会改变的缘故,缓存命中率非常高。所以能很大程度上减少了性能损失。

image

图4:数据访问过程

索引表的方式能够比较方便地添加数据库节点,在增加节点时,只要将其添加到可用数据库列表里即可。 当然如果需要平衡各个节点的压力的话,还是需要进行数据的迁移,但是这个时候的迁移是少量的,可以逐步进行。要迁移用户A的数据,首先要将其状态置为迁移数据中,这个状态的用户不能进行写操作,并在页面上进行提示。 然后将用户A的数据全部复制到新增加的节点上后,更新映射表,然后将用户A的状态置为正常,最后将原来对应的数据库上的数据删除。这个过程通常会在临晨进行,所以,所以很少会有用户碰到迁移数据中的情况。

当然,有些数据是不属于某个用户的,比如系统消息、配置等等,我们把这些数据保存在一个全局库中。

问题

分库会给你在应用的开发和部署上都带来很多麻烦。

不能执行跨库的关联查询

如 果我们需要查询的数据分布于不同的数据库,我们没办法通过JOIN的方式查询获得。比如要获得好友的最新照片,你不能保证所有好友的数据都在同一个数据库 里。一个解决办法是通过多次查询,再进行聚合的方式。我们需要尽量避免类似的需求。有些需求可以通过保存多份数据来解决,比如User-A和 User-B的数据库分别是DB-1和DB-2, 当User-A评论了User-B的照片时,我们会同时在DB-1和DB-2中保存这条评论信息,我们首先在DB-2中的photo_comments表 中插入一条新的记录,然后在DB-1中的user_comments表中插入一条新的记录。这两个表的结构如下图所示。这样我们可以通过查询 photo_comments表得到User-B的某张照片的所有评论, 也可以通过查询user_comments表获得User-A的所有评论。另外可以考虑使用全文检索工具来解决某些需求, 我们使用Solr来提供全站标签检索和照片搜索服务。

image

图5:评论表结构

不能保证数据的一致/完整性

跨 库的数据没有外键约束,也没有事务保证。比如上面的评论照片的例子, 很可能出现成功插入photo_comments表,但是插入user_comments表时却出错了。一个办法是在两个库上都开启事务,然后先插入 photo_comments,再插入user_comments, 然后提交两个事务。这个办法也不能完全保证这个操作的原子性。

所有查询必须提供数据库线索

比 如要查看一张照片,仅凭一个照片ID是不够的,还必须提供上传这张照片的用户的ID(也就是数据库线索),才能找到它实际的存放位置。因此,我们必须重新 设计很多URL地址,而有些老的地址我们又必须保证其仍然有效。我们把照片地址改成/photos/{username}/{photo_id} /的形式,然后对于系统升级前上传的照片ID, 我们又增加一张映射表,保存photo_id和user_id的对应关系。当访问老的照片地址时,我们通过查询这张表获得用户信息, 然后再重定向到新的地址。

自增ID

如果要在节点 数据库上使用自增字段,那么我们就不能保证全局唯一。这倒不是很严重的问题,但是当节点之间的数据发生关系时,就会使得问题变得比较麻烦。我们可以再来看 看上面提到的评论的例子。如果photo_comments表中的comment_id的自增字段,当我们在DB- 2.photo_comments表插入新的评论时, 得到一个新的comment_id,假如值为101,而User-A的ID为1,那么我们还需要在DB-1.user_comments表中插入(1, 101 …)。 User-A是个很活跃的用户,他又评论了User-C的照片,而User-C的数据库是DB-3。 很巧的是这条新评论的ID也是101,这种情况很用可能发生。那么我们又在DB-1.user_comments表中插入一行像这样(1, 101 …)的数据。 那么我们要怎么设置user_comments表的主键呢(标识一行数据)?可以不设啊,不幸的是有的时候(框架、缓存等原因)必需设置。那么可以以 user_id、 comment_id和photo_id为组合主键,但是photo_id也有可能一样(的确很巧)。看来只能再加上photo_owner_id了, 但是这个结果又让我们实在有点无法接受,太复杂的组合键在写入时会带来一定的性能影响,这样的自然键看起来也很不自然。所以,我们放弃了在节点上使用自增 字段,想办法让这些ID变成全局唯一。为此增加了一个专门用来生成ID的数据库,这个库中的表结构都很简单,只有一个自增字段id。 当我们要插入新的评论时,我们先在ID库的photo_comments表里插入一条空的记录,以获得一个唯一的评论ID。 当然这些逻辑都已经封装在我们的框架里了,对于开发人员是透明的。 为什么不用其它方案呢,比如一些支持incr操作的Key-Value数据库。我们还是比较放心把数据放在MySQL里。 另外,我们会定期清理ID库的数据,以保证获取新ID的效率。

实现

我们称前面提到的一个数据库节点为Shard,一个 Shard由两个台物理服务器组成, 我们称它们为Node-A和Node-B,Node-A和Node-B之间是配置成Master-Master相互复制的。 虽然是Master-Master的部署方式,但是同一时间我们还是只使用其中一个,原因是复制的延迟问题, 当然在Web应用里,我们可以在用户会话里放置一个A或B来保证同一用户一次会话里只访问一个数据库, 这样可以避免一些延迟问题。但是我们的Python任务是没有任何状态的,不能保证和PHP应用读写相同的数据库。那么为什么不配置成Master- Slave呢?我们觉得只用一台太浪费了,所以我们在每台服务器上都创建多个逻辑数据库。 如下图所示,在Node-A和Node-B上我们都建立了shard_001和shard_002两个逻辑数据库, Node-A上的shard_001和Node-B上的shard_001组成一个Shard,而同一时间只有一个逻辑数据库处于Active状态。 这个时候如果需要访问Shard-001的数据时,我们连接的是Node-A上的shard_001, 而访问Shard-002的数据则是连接Node-B上的shard_002。以这种交叉的方式将压力分散到每台物理服务器上。 以Master-Master方式部署的另一个好处是,我们可以不停止服务的情况下进行表结构升级, 升级前先停止复制,升级Inactive的库,然后升级应用,再将已经升级好的数据库切换成Active状态, 原来的Active数据库切换成Inactive状态,然后升级它的表结构,最后恢复复制。 当然这个步骤不一定适合所有升级过程,如果表结构的更改会导致数据复制失败,那么还是需要停止服务再升级的。

image

图6:数据库布局

前 面提到过添加服务器时,为了保证负载的平衡,我们需要迁移一部分数据到新的服务器上。为了避免短期内迁移的必要,我们在实际部署的时候,每台机器上部署了 8个逻辑数据库, 添加服务器后,我们只要将这些逻辑数据库迁移到新服务器就可以了。最好是每次添加一倍的服务器, 然后将每台的1/2逻辑数据迁移到一台新服务器上,这样能很好的平衡负载。当然,最后到了每台上只有一个逻辑库时,迁移就无法避免了,不过那应该是比较久 远的事情了。

我们把分库逻辑都封装在我们的PHP框架里了,开发人员基本上不需要被这些繁琐的事情困扰。下面是使用我们的框架进行照片数据的读写的一些例子:

array("type" => "long", "primary" => true, "global_auto_increment" => true),
                "user_id"     => array("type" => "long"),
                "title"       => array("type" => "string"),
                "posted_date" => array("type" => "date"),
            ));

    $photo = $Photos->new_object(array("user_id" => 1, "title" => "Workforme"));
    $photo->insert();

    // 加载ID为10001的照片,注意第一个参数为用户ID
    $photo = $Photos->load(1, 10001);

    // 更改照片属性
    $photo->title = "Database Sharding";
    $photo->update();

    // 删除照片
    $photo->delete();

    // 获取ID为1的用户在2010-06-01之后上传的照片
    $photos = $Photos->fetch(array("user_id" => 1, "posted_date__gt" => "2010-06-01"));

首 先要定义一个ShardedDBTable对象,所有的API都是通过这个对象开放。第一个参数是对象类型名称, 如果这个名称已经存在,那么将返回之前定义的对象。你也可以通过get_table(“Photos”)这个函数来获取之前定义的Table对象。 第二个参数是对应的数据库表名,而第三个参数是数据库线索字段,你会发现在后面的所有API中全部需要指定这个字段的值。 第四个参数是字段定义,其中photo_id字段的global_auto_increment属性被置为true,这就是前面所说的全局自增ID, 只要指定了这个属性,框架会处理好ID的事情。

如果我们要访问全局库中的数据,我们需要定义一个DBTable对象。

array("type" => "long", "primary" => true, "auto_increment" => true),
                "username" => array("type" => "string"),
            ));

DBTable是ShardedDBTable的父类,除了定义时参数有些不同(DBTable不需要指定数据库线索字段),它们提供一样的API。

六、缓存

我们的框架提供了缓存功能,对开发人员是透明的。

load(1, 10001);

比如上面的方法调用,框架先尝试以Photos-1-10001为Key在缓存中查找,未找到的话再执行数据库查询并放入缓存。当更改照片属性或删除照片时,框架负责从缓存中删除该照片。这种单个对象的缓存实现起来比较简单。稍微麻烦的是像下面这样的列表查询结果的缓存。
fetch(array("user_id" => 1, "posted_date__gt" => "2010-06-01"));

我们把这个查询分成两步,第一步先查出符合条件的照片ID,然后再根据照片ID分别查找具体的照片信息。 这么做可以更好的利用缓存。第一个查询的缓存Key为Photos-list-{shard_key}-{md5(查询条件SQL语句)}, Value是照片ID列表(逗号间隔)。其中shard_key为user_id的值1。目前来看,列表缓存也不麻烦。 但是如果用户修改了某张照片的上传时间呢,这个时候缓存中的数据就不一定符合条件了。所以,我们需要一个机制来保证我们不会从缓存中得到过期的列表数据。 我们为每张表设置了一个revision,当该表的数据发生变化时(调用insert/update/delete方法), 我们就更新它的revision,所以我们把列表的缓存Key改为Photos-list-{shard_key}-{md5(查询条件SQL语 句)}-{revision}, 这样我们就不会再得到过期列表了。

revision信息也是存放在缓存里的,Key为Photos-revision。这样做看起来不错,但是好像列表缓存的利用率不会太 高。因为我们是以整个数据类型的revision为缓存Key的后缀,显然这个revision更新的非常频繁,任何一个用户修改或上传了照片都会导致它 的更新,哪怕那个用户根本不在我们要查询的Shard里。要隔离用户的动作对其他用户的影响,我们可以通过缩小revision的作用范围来达到这个目 的。 所以revision的缓存Key变成Photos-{shard_key}-revision,这样的话当ID为1的用户修改了他的照片信息时, 只会更新Photos-1-revision这个Key所对应的revision。

因为全局库没有shard_key,所以修改了全局库中的表的一行数据,还是会导致整个表的缓存失效。 但是大部分情况下,数据都是有区域范围的,比如我们的帮助论坛的主题帖子, 帖子属于主题。修改了其中一个主题的一个帖子,没必要使所有主题的帖子缓存都失效。 所以我们在DBTable上增加了一个叫isolate_key的属性。

array("type" => "long", "primary" => true),
        "post_id"     => array("type" => "long", "primary" => true, "auto_increment" => true),
        "author_id"   => array("type" => "long"),
        "content"     => array("type" => "string"),
        "posted_at"   => array("type" => "datetime"),
        "modified_at" => array("type" => "datetime"),
        "modified_by" => array("type" => "long"),
    ), "topic_id");

注意构造函数的最后一个参数topic_id就是指以字段topic_id作为isolate_key,它的作用和shard_key一样用于隔离revision的作用范围。

ShardedDBTable继承自DBTable,所以也可以指定isolate_key。 ShardedDBTable指定了isolate_key的话,能够更大幅度缩小revision的作用范围。 比如相册和照片的关联表yp_album_photos,当用户往他的其中一个相册里添加了新的照片时, 会导致其它相册的照片列表缓存也失效。如果我指定这张表的isolate_key为album_id的话, 我们就把这种影响限制在了本相册内。

我们的缓存分为两级,第一级只是一个PHP数组,有效范围是Request。而第二级是memcached。这么做的原因是,很多数据在一 个 Request周期内需要加载多次,这样可以减少memcached的网络请求。另外我们的框架也会尽可能的发送memcached的gets命令来获取 数据, 从而减少网络请求。

七、更多大型网站架构分析

Flickr网站架构
回顾MySpace架构的坎坷之路
优酷网架构
twitter网站架构
PlentyOfFish.com .NET网站的又一传奇

八、相关参考文章

http://www.kuqin.com/database/20100704/85908.html

http://www.dbanotes.net/arch/yupoo_arch.html

备注:原文链接 http://www.itivy.com/ivy/archive/2011/8/16/the-architecture-of-yupoo.html

[原创]EasyUI的edatagrid中的行combobox级联实例代码

mikel阅读(1208)

            //            $('#goods-grid').edatagrid({
            //                url: '/ClientGoods/Goods?client=' + client + '&' + Math.random(),
            //                saveUrl: '/ClientGoods/SaveGoods?Client_ID=' + client + '&' + Math.random(),
            //                updateUrl: '/ClientGoods/SaveGoods?Client_ID=' + client + '&' + Math.random(),
            //                destroyUrl: '/ClientGoods/Remove',
            //                onAdd: function(index, row) {
            //                    var product = $('#goods-grid').datagrid('getEditor', { index: index, field: 'Product_Name' }).target;
            //                    var name = $('#goods-grid').datagrid('getEditor', { index: index, field: 'Goods_Name' }).target;
            //                    product.combobox({
            //                        onSelect: function(value) {
            ////                            alert('/GoodsROM/Goods?product=' + value.Identifier + '&' + Math.random());
            //                            var url='/GoodsROM/Goods?product=' + value.Identifier + '&' + Math.random();

            //                            name.combobox({url:'/GoodsROM/Goods?product=' + value.Identifier + '&' + Math.random(),valueField:'Goods_ID',textField:'Goods_Name',required:true,panelHeight:'auto'});
            //                        }
            //                    });

            //                    name.combobox({
            //                        onSelect: function(value) {
            //                            //alert(value.Goods_Name);
            //                            //获取物理属性值
            //                            getGoods(value.Goods_ID, index);
            //                            //alert('end');
            //                        }
            //                    });

            //                },
            //                onEdit: function(index, row) {
            //                    var product = $('#goods-grid').datagrid('getEditor', { index: index, field: 'Product_Name' }).target;
            //                    var name = $('#goods-grid').datagrid('getEditor', { index: index, field: 'Goods_Name' }).target;
            //                    product.combobox({
            //                        onSelect: function(value) {
            //                            //alert('/GoodsROM/Goods?product=' + value.Identifier + '&' + Math.random());
            //                            var url='/GoodsROM/Goods?product=' + value.Identifier + '&' + Math.random();

            //                            name.combobox({url:'/GoodsROM/Goods?product=' + value.Identifier + '&' + Math.random(),valueField:'Goods_ID',textField:'Goods_Name',required:true,panelHeight:'auto'});
            //                        }
            //                    });
            //                    name.combobox({
            //                        onSelect: function(value) {
            //                            //alert(value.Goods_Name);
            //                            //获取物理属性值
            //                            getGoods(value.Goods_ID, index);
            //                            //alert('end');
            //                        }
            //                    });

            //                },
            //                onBeforeSave: function(index, row) {
            //                    row.Goods_ID = row.Goods_Name;
            //                    row.Client_ID = client;
            //                    row.Product_ID = row.Product_Name;
            //                }
            //            });


        });

        function getGoods(id,index) {
            $.post('/Goods/getGoods', { identifier: id }, function(data) {
                if (data) {
                    var phy = $('#goods-grid').datagrid('getEditor', { index: index, field: 'Physical' }).target;
                    var che = $('#goods-grid').datagrid('getEditor', { index: index, field: 'Chemistry' }).target;
                    $(phy).val(data.Physical);
                    $(che).val(data.Chemistry);
                }

            }, 'json');
        //         }

[转载]基于WEB技术的ERP和电子商务

mikel阅读(851)

[转载]基于WEB技术的ERP和电子商务 – 落叶#¥ – 博客园.

随着计算机和网络技术的飞速发展,电子商务(电商频道)已经成为企业无法回避的选择。这种新型的经济模式改变了企业的市场结构和竞争方式,在为企业提供更多机遇的同时,也对企业运营提出了更多的挑战。传统经济模式下电子商务与企业资源计划ERP各自独立,造成企业内、外部之间信息流、价值流和物流相互交换的脱节,使企业前后端成为各自独立的信息孤岛,生产与决策部门得不到有力的信息支持。因此,建立电子商务环境下的企业前后端系统集成,使企业、合作伙伴及客户集成在同一电子商务平台上,加强企业与合作伙伴及客户的纽带关系,是企业赢得竞争优势的关键之一。当前讨论较多的是电子商务和ERP的集成系统。

一、电子商务和ERP集成的必要性

电子商务与ERP都属于企业的信息系统,但从两者的职能范围来看,还是有着很大区别的。ERP系统是由MRP、MRP II等企业管理思想发展而来的,管理范围侧重于企业内部。相比之下,电子商务主要是解决企业与外部世界的通信、连接和交易,利用Internet以及相关的网络技术来解决商业交易问题,降低产、供、销成本,开拓新的市场,创造新的商机,从而增加企业利润的商业活动。

但是当前电子商务和ERP建设中存在诸多问题:电子商务与ERP被分裂开来,没有统一规划和统一设计;两个系统中的采购数据、销售数据和财务数据没有进 行合并,使数据失去一致性和完整性;软件资源、硬件资源和数据资源没有充分共享,造成建设成本过高和存储空间浪费;把电子商务片面理解成电子商店或仅仅建 立一个网站;ERP往往直接采用市场上的商品软件,而电子商务则自己开发,不能实现两者的会话与集成。

而事实上电子商务和ERP系统如同前线和后方的关系,两者息息相关,如果企业前端的电子商务和后台的ERP系统脱节,会导致很多关键的信息和数据被封闭 在相互独立的系统中,部门间重复着冗余的工作,不能对客户作出迅速及时有效的响应,使企业工作效率下降以及运营成本上升,从而给企业自身带来极大的损害。 因此,现在的ERP必须能够适应互联网的应用可以支持跨平台的多组织的应用,并和电子商务之间的应用具有广泛的数据、业务逻辑的接口,在建好后台ERP的 同时,作好前端电子商务的高度系统集成。

基于WEB SERVICES的 电子商务和ERP集成传统的分布式技术,主要有Microsoft的分布式组件对象模型(DCOM),OMG的公用对象请求代理程序体系结构 (CORBA)和SUN的远程方法调用(RMI)。通过对RMI、CORBA、DCOM三种传统分布式技术进行研究,并结合它们的应用情况,可以发现这三 种方式虽然都有自己独到的技术优势,但是其自身的缺陷也导致了这三种技术的进一步推广应用,比如:RMI、CORBA等传统分布式对象结构主要用于企业内 部,很难扩展到Internet上。RMI、CORBA、DCOM等体系结构要求在对等体系结构间才能进行通信,即基于不同体系结构的应用系统是无法相互 协作的,但是在电子商务中,要求所有参与者都采用同一种体系结构是不现实的。而WEB SERVICES可以提供松散耦合的集成结构,这是因为WEB服务彼此之间是松耦合。连接中的任何一方都可更改执行机制,却不影响应用程序的正常执行,能 较好保证不同系统或数据源之间的数据传输与共享,所以为了对ERP和电子商务进行集成,本文提出采用WEB SERVICES技术。

二、电子商务与ERP信息集成分析

在电子商务的竞争环境下,企业需要快速、柔性的生产模式与实现个性化、全方位服务的营销模式相互整合,实现以“客户满意”为中心,内外信息流充分融合与 畅通的开放式经营模式,以提高企业核心竞争力。ERP系统合理配置企业内部资源,优化了企业生产流程,电子商务改善了客户关系,提高了客户利润率,真正体 现了网络经济以“客户满意”为中心的管理思想和电子商务“端到端”的实质,提高了电子商务环境下的企业核心竞争力。

电子商务与ERP系统在客户信息管理、产品信息管理、营销与销售信息管理、订单信息管理、人员信息管理、客户服务与支持、决策支持等方面存在着功能的交 叉或重叠,但各自的侧重点有所不同。电子商务系统全面管理客户信息,并能够通过对客户信息的整理和挖掘,了解客户消费规律,预测客户未来消费行为。而 ERP系统则仪仅管理客户的一些基本信息,如联系人、地址、电话,等等。在产品信息管理方面,ERP系统包含了所有产品(产成品、非产成品)的基本信息、 产品BOM表、产品报价与客户化配置,等等,电子商务系统则只包含成品的上述信息。电子商务与ERP系统在营销与销售管理方面存在着互补性。电子商务强调 一对一客户关怀思想,包含完善的营销策略和营销管理功能,注重的是企业销售的机会管理和实践管理及过程管理,而ERP只管理一些市场与营销资料、销售计划 与销售业绩。

作为主生产计划输入变量的订单,在ERP 系统管理中是必不可少的。而电子商务作为电子商务环境下企业与客户交互的门户,同样接收订单并对其进行管理,在功能上二者是完全重叠的。ERP在客户服务 与支持上的管理功能非常薄弱,电子商务则进行了全面管理,并通过数据分析,根据一对一客户关怀方式提供有针对性的个性化服务,培养客户的忠诚度。电子商务 与ERP系统均具有决策支持功能,通过数据仓库(DW)和联机分析处理(OLAP)为企业提供商业智能和决策支持。二者所使用的数据仓库不同,因而提供不 同的决策内容。

综上分析,电子商务与ERP系统在功能 上存在很大交叉或重叠,而且存在优势互补的特点。因此,二者的集成突破丁集成供应链上企业问的地域边界和不同企业之间信息交流组织边界,真正解决了企业供 应链中的下游链节点的管理,将客户、经销商、企业整合在一起,融合了企业内外信息流,优化了企业业务流程,提高了企业电子商务环境下的市场竞争力。

三、基于WEB的电子商务与ERP信息集成框架

对于什么是WEB SERVICES,一直没有一个明确的答案,本文的观点是:WEB SERVICES是一种基于组件的软件平台,是面向Intemet的服务应用。WEB SERVICES是应用于Internet的,而不是限于局域网或试验环境。这要求WEB SERVICES框架必须适用于现有的Internet软件和硬件环境,即服务的提供者所提供的服务必须具有跨平台、跨语言的特性。其次,WEB SERVICES不仅服务于人,更需服务于其他应用系统。现有的WEB网站也可以认为是提供服务的,但这种服务仅仅提供给人使用(只有人类才可以读懂浏览 器下载的页面)。而新一代的WEB SERVICES所提供的服务应能被机器所读懂,如其他应用程序及移动设备中的软件系统。这样,我们可以看出,WEB SERVICES的发展方向实际上是构造一个现有Internet技术上的分布计算系统。

WEB SERVICES框架的核心技术包括SOAP、WSDL和UDDI,它们都是以标准的XML文档的形式表示的。SOAP是WEB SERVICES的通信协议。SOAP是一种简单的、轻量级的基于XML的机制,用于在网络应用程序之间进行结构化数据交换。SOAP包括三部分:一个定 义描述消息内容的框架的信封,一组表示应用程序定义的数据类型实例的编码规则,以及表示远程过程调用和响应的约定。

WSDL表示WEB服务说明语言。WSDL文件是一个XML文档,用于说明一组SOAP消息以及如何交换这些消息。UDDI(统一描述发现和集成)提供 一种发布和查找服务描述的方法。UDDI数据实体提供对定义业务和服务信息的支持。WSDL中定义的服务描述信息是UDDI注册中心信息的补充。

WEB SERVICES服务提供方通过WSDL描述所提供的服务,并将这一描述告知WEB SERVICES注册服务器。注册服务器依据WSDL的描述,依照UDDI的协定更新服务目录并在Internet上发布。用户在使用WEB SERVICES前先向注册服务器发出请求,获得WEB SERVICES提供者的地址和服务接口信息,之后使用SOAP协议(Simple Object Access Protoc01)与WEB SERVICES提供者建立连接,进行通信。WEB SERVICES的技术主要建立在XML的规范之上,这保证了这一体系结构的平台无关性、语言无关性和人机交互性能。

传统的应用系统大都不太灵活,它们都建立在点到点的解决方案的基础上,需要程序员大量的工作和一个比较长的开发周期,造成大量昂贵硬编码的单独应用,这 些应用不能很好地集成也不能灵活地适应、业务流程的改变。通过分析可知,功能组件化并具有良好集成性的WEB SERVICES可解决这些问题,即电子商务和ERF的集成框架。

在该模式中我们不使用传统点到点的集成,而是在原有集成模式的基础上结合Web服务新技术提出的一种新模式。该集成框架将企业后台的ERP系统和前台的电子商务系统集成到框架中。

SOAP消息代理是整个框架的信息处理中心,它根据预先定义好的应用消息转换机制将消息转换成目标应用需要的数据并存储到应用消息仓库中。除了保存和转发应用消息之外,消息代理还负责将不同的企业应用所保存的应用数据通过企业模型转化为一致的企业数据,保存到数据库中。

客户端通过UDDI的标准和机制来搜寻需要的WEB服务,绑定找到的WEB服务并使用它提供的服务。另外,框架为企业迸一步发展电子商务提供了接口,建立在UDDI基础上的WEN SERVICES可以完成这一功能。

四、结 语

目前关于企业内部系统集成的论述比较多,而关于企业内、外部系统的集成整合的观点比较少。事实上,随着企业规模的发展壮大,企业外部系统的集成比内部系统的集成更为重要,对电子商务和ERP系统进行整合集成顺理成章被提上关键日程。

[转载]jquery easyui 'rowspan' 为空或不是对象

mikel阅读(1112)

[转载]jquery easyui ‘rowspan’ 为空或不是对象 .

原因:
1,columns:[[
{field:’xsbh’,title:’编号’,width:80},
{field:’UserName’,title:’姓名’,width:100},
{field:’Sex’,title:’性别’,width:30},
{field:’SchoolYear’,title:’年份’,width:50},
{field:’opt‘,title:’操作’,width:100,align:’center’,
formatter:function(value,rec,index){
var s = ‘<a href=”http://blog.163.com/luckcq@yeah/blog/#” mce_href=”http://blog.163.com/luckcq@yeah/blog/#” onclick=”view(\”+ rec.xsbh + ‘\’)”>查看</a> ‘;
var e = ‘<a href=”http://blog.163.com/luckcq@yeah/blog/#” mce_href=”http://blog.163.com/luckcq@yeah/blog/#” onclick=”edit(\”+ rec.xsbh + ‘\’)”>编辑</a> ‘;
var d = ‘<a href=”http://blog.163.com/luckcq@yeah/blog/#” mce_href=”http://blog.163.com/luckcq@yeah/blog/#” onclick=”del(\”+ index +’\’)”>删除</a> ‘;
return s+e+d;
}
}
]],
“操作”的跨行,一定要带上field:’opt’,当然,field可以是任何值,这个值不用从数据库中绑定,随便取.如果没有field的话,会弹出 “rowspan为空或不是对象”的错误
2,在最后一个{field}多一个”,”号也有可能会有IE下引发这个错误,其它浏览器则不会

[转载]Filter ComboGrid - jQuery EasyUI

mikel阅读(1496)

[转载]Filter ComboGrid – jQuery EasyUI.

The combogrid component has the same functionalities as the combobox component except that the drop down panel is based on a datagrid. The combogrid component can be filtered, paged and some other datagrid capabilities. This tutorial will show you how to filter data records in a combogrid component.

Create ComboGrid

  1. <input id=“cg” style=“width:150px”>
  1. $(‘#cg’).combogrid({
  2. panelWidth:500,
  3. url: ‘form5_getdata.php’,
  4. idField:‘itemid’,
  5. textField:‘productid’,
  6. mode:‘remote’,
  7. fitColumns:true,
  8. columns:[[
  9. {field:‘itemid’,title:‘Item ID’,width:60},
  10. {field:‘productid’,title:‘Product ID’,align:‘right’,width:80},
  11. {field:‘listprice’,title:‘List Price’,align:‘right’,width:60},
  12. {field:‘unitcost’,title:‘Unit Cost’,align:‘right’,width:60},
  13. {field:‘attr1’,title:‘Attribute’,width:150},
  14. {field:‘status’,title:‘Stauts’,align:‘center’,width:60}
  15. ]]
  16. });

The combogrid component should define ‘idField’ and ‘textField’ properties. The ‘idField’ property store the component value and ‘textField’ property display the text message on the input box. The combogrid can filter its records in ‘local’ or ‘remote’ mode. On remote mode the combogrid will post the ‘q’ parameter to remote server when users typeing chars in input box.

The Server Code

form5_getdata.php
  1. $q = isset($_POST[‘q’]) ? strval($_POST[‘q’]) : ;
  2. include ‘conn.php’;
  3. $rs = mySQL_query(“select * from item where itemid like ‘%$q%’ or productid like ‘%$q%'”);
  4. $rows = array();
  5. while($row = mySQL_fetch_assoc($rs)){
  6. $rows[] = $row;
  7. }
  8. echo json_encode($rows);

Download the EasyUI example:



[转载]jQuery.Easyui 1.2.4 CHM API 下载

mikel阅读(1015)

[转载]jQuery.Easyui 1.2.4 CHM API 下载 – 疯狂秀才 – 博客园.

JQuery easyui 1.2. 4

ChangeLog

Bug

  • menu: The menu position is wrong when scroll bar appears. fixed.
  • accordion: Cannot display the default selected panel in JQuery 1.6.2. fixed.
  • tabs: Cannot display the default selected tab panel in JQuery 1.6.2. fixed.

Improvement

  • menu: Allow users to disable or enable menu item.
  • combo: Add ‘delay’ property to set the delay time to do searching from the last key input event.
  • treegrid: The ‘getEditors‘ and ‘getEditor‘ methods are supported now.
  • treegrid: The ‘loadFilter‘ option is supported now.
  • messager: Add ‘progress’ method to display a message box with a progress bar.
  • panel: Add ‘extractor’ option to allow users to extract panel content from ajax response.

New Plugins

  • searchbox: Allow users to type words into box and do searching operation.
  • progressbar: To display the progress of a task.