[转载]漂亮的后台WebUi框架(有源码下载)

mikel阅读(933)

[转载]漂亮的后台WebUi框架(有源码下载) – 孟晨 – 博客园.

今天分享下以前收藏的几个漂亮的后台WebUi框架,之前听人说到“一个项目最重要的是前台,后台谁看啊”,这话说的,是,领导是只看前台界面,但系统 管理员呢?不能不说前台不重要,前台是很重要,但后台怎么能不重要呢,大部分数据要靠后台来输入,操作。如果一个好的后台界面,漂亮的,易用的页面对系统 来说也很重要,如果你是系统管理员看到一个很草的后台,也会不爽吧,今天就给大家分享几套不错的后台框架,有源码下载。


打包下载,可用的模板

[转载]Android定位功能(一)

mikel阅读(857)

[转载]Android定位功能(一) – 熊猫82 – 博客园.

废话不多说,直接开始说说与实现Android定位有关的API吧。

这些API都在Android.location包下,一共有三个接口和八个类。它们配合使用即可实现定位功能。

三个接口:

GpsStatus.Listener: 这是一个当GPS状态发生改变时,用来接收通知的接口。

GpsStatus.NmeaListener: 这是一个用来从GPS里接收Nmea-0183(为海用电子设备制定的标准格式)信息的接口。

LocationListener: 位置监听器,用于接收当位置信息发生改变时从LocationManager接收通知的接口。

八个类:

Address: 描述地址的类,比如:北京天安门

Criteria: 用于描述Location Provider标准的类,标准包括位置精度水平,电量消耗水平,是否获取海拔、方位信息,是否允许接收付费服务。

GeoCoder: 用于处理地理位置的编码。

GpsSatellite: 和GpsStatus联合使用,用于描述当前GPS卫星的状态。

GpsStatus: 和GpsStatus.Listener联合使用,用于描述当前GPS卫星的状态。

Location: 用于描述位置信息。

LocationManager: 通过此类获取和调用系统位置服务

LocationProvider: 用于描述Location Provider的抽象超类,一个LocationProvider应该能够周期性的报告当前设备的位置信息。

这里通过一个代码示例,演示一下如何实现定位。

首先,在AndroidManifest.xml清单文件里需要加入ACCESS_FINE_LOCATION权限

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>

其次,实现代码如下:

 package com.test;
 import java.io.IOException;
 import java.util.List;

 import android.app.Activity;
 import android.location.Address;
 import android.location.Criteria;
 import android.location.Geocoder;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.os.Bundle;
 import android.util.Log;
 import android.widget.Toast;

 public class MainActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //获取到LocationManager对象
        LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        //创建一个Criteria对象
        Criteria criteria = new Criteria();
        //设置粗略精确度
        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
        //设置是否需要返回海拔信息
        criteria.setAltitudeRequired(false);
        //设置是否需要返回方位信息
        criteria.setBearingRequired(false);
        //设置是否允许付费服务
        criteria.setCostAllowed(true);
        //设置电量消耗等级
        criteria.setPowerRequirement(Criteria.POWER_HIGH);
        //设置是否需要返回速度信息
        criteria.setSpeedRequired(false);

        //根据设置的Criteria对象,获取最符合此标准的provider对象
        String currentProvider = locationManager.getBestProvider(criteria, true);
        Log.d("Location", "currentProvider: " + currentProvider);
        //根据当前provider对象获取最后一次位置信息
        Location currentLocation = locationManager.getLastKnownLocation(currentProvider);
        //如果位置信息为null,则请求更新位置信息
        if(currentLocation == null){
            locationManager.requestLocationUpdates(currentProvider, 0, 0, locationListener);
        }
        //直到获得最后一次位置信息为止,如果未获得最后一次位置信息,则显示默认经纬度
        //每隔10秒获取一次位置信息
        while(true){
            currentLocation = locationManager.getLastKnownLocation(currentProvider);
            if(currentLocation != null){
                Log.d("Location", "Latitude: " + currentLocation.getLatitude());
                Log.d("Location", "location: " + currentLocation.getLongitude());
                break;
            }else{
                Log.d("Location", "Latitude: " + 0);
                Log.d("Location", "location: " + 0);
            }
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                 Log.e("Location", e.getMessage());
            }
        }

        //解析地址并显示
        Geocoder geoCoder = new Geocoder(this);
        try {
            int latitude = (int) currentLocation.getLatitude();
            int longitude = (int) currentLocation.getLongitude();
            List

<address> list = geoCoder.getFromLocation(latitude, longitude, 2);
            for(int i=0; i  75                Address address = list.get(i);
                Toast.makeText(MainActivity.this, address.getCountryName() + address.getAdminArea() + address.getFeatureName(), Toast.LENGTH_LONG).show();
            }
        } catch (IOException e) {
            Toast.makeText(MainActivity.this,e.getMessage(), Toast.LENGTH_LONG).show();
        }

     }

     //创建位置监听器
     private LocationListener locationListener = new LocationListener(){
         //位置发生改变时调用
         @Override
         public void onLocationChanged(Location location) {
             Log.d("Location", "onLocationChanged");
             Log.d("Location", "onLocationChanged Latitude" + location.getLatitude());
                  Log.d("Location", "onLocationChanged location" + location.getLongitude());
         }

         //provider失效时调用
         @Override
         public void onProviderDisabled(String provider) {
             Log.d("Location", "onProviderDisabled");
         }

         //provider启用时调用
         @Override
         public void onProviderEnabled(String provider) {
             Log.d("Location", "onProviderEnabled");
        }

         //状态改变时调用
         @Override
         public void onStatusChanged(String provider, int status, Bundle extras) {
             Log.d("Location", "onStatusChanged");
         }
     };
 }

由于代码里的Criteria对象对位置精度要求并不高,所以一般会返回“network”作为provider,而基于network的定位 往往会存在一定的位置偏差,这对于需要精确定位的应用程序来说,显然不合要求。这时,需要则需要用到基于GPS的定位方法了。具体详情,请看后续博文 Android定位功能(二)。

[转载]FlexPaper+SwfTools实现的在线文档功能

mikel阅读(1159)

[转载]FlexPaper+SwfTools实现的在线文档功能 – penbox – 博客园.

最近一个项目需要实现一个在线浏览文档的功能。准备使用FlexPaper配合Pdf2Swf实现。

主要需求在于:

➔ 文档页数很多,少则几百页,多则上千页。
➔ 相应的文档大小也在50MB以上。

根据需求,将该功能拆分成了三部分:

上传:支持大文件,断点续传。
文件转换服务:在后台进行Pdf转Swf。
在线浏览:分页加载。

其中文件上传部分是我同事实现的,这里主要讲下后台服务和在线浏览部分。

文件转换服务

大体思路是:
后台服务定时扫描文件夹PdfPath,如果发现Pdf文件,则调用Pdf2Swf.exe,将PDF逐页转换成Swf文件,存入SwfPath中的与文件名称对应的文件夹里。

这里使用了SwfTools套件中的Pdf2Swf工具:下载

主要代码:

//PDF转换成SWF
private void ConvertPDFtoSWF(string pdfFile)
{
using (Process p = new Process())
{
SystemLog.CurrentLogger.Debug(string.Format("正在处理 {0} ...", pdfFile));

string pdf2swfExe = "pdf2swf.exe";
string savePath = GetSavePathFromName(pdfFile);

string cmd = pdf2swfExe;
string args = "  -t \"" + pdfFile + "\"  -o \"" + savePath + pdfFile.Split('\\').Last().Replace(".pdf", "")
+ "%.swf\" -s drawonlyshapes -s flashversion=9";

p.StartInfo.FileName = cmd;
p.StartInfo.Arguments = args;
p.StartInfo.UseShellExecute = false;

//此类提供的标准output流只有2k,不要重定向
p.StartInfo.RedirectStandardOutput = false;

p.StartInfo.CreateNoWindow = true;
p.Start();
p.PriorityClass = ProcessPriorityClass.High;
p.WaitForExit();

SystemLog.CurrentLogger.Debug(string.Format("{0} 处理完成。", pdfFile));

if (AppConfiguration.DeleteConvertedPdf)
{
//转换完成后删除Pdf
File.Delete(pdfFile);
SystemLog.CurrentLogger.Debug(string.Format("{0} 已删除。", pdfFile));
}
else
{
//重命名Pdf
File.Move(pdfFile, pdfFile + ".bak");
SystemLog.CurrentLogger.Debug(string.Format("{0} 已重命名。", pdfFile));
}
}
}
gentleface.com free icon set 写服务的时候遇到一个奇怪的Bug,发布为服务的时候,测试用的Pdf文件每次转换到37页的时候就卡住了,但是用命令行进行调试的时候却一切OK。
Google后发现,原来是Process的output流问题:output流只有2KB,而Pdf2Swf会产生大量输出,2KB很快就耗尽了。

之前在测试的时候,发现转换过程中会出现文字丢失的现象。这里使用了-s drawonlyshapes 这个参数,将Pdf全部作为图片转换的。这样虽然保证了兼容性,但是牺牲了空间。作为图片生成的Swf比文本格式的Swf要大不少,不知道大家有没有什么 好的解决方法。

在线浏览

FlexPaper支持分页加载,采用{filename[*,padding],total pages}这种语法即可。

主要代码:

<a id="viewerPlaceHolder" style="width: 800px; height: 600px; display: block;"></a>

<script type="text/javascript">// <!&#91;CDATA&#91;
    var fp = new FlexPaperViewer(
    'FlexPaperViewer',
     'viewerPlaceHolder', { config: {
         SwfFile: 'SwfFolder/<%=Folder %>/{<%=Folder %>&#91;*,0&#93;.swf,<%=PageNum %>}',
         localeChain: "zh_CN", //中文
         Scale: 1,
         ZoomTransition: 'easeOut',
         ZoomTime: 0.5,
         ZoomInterval: 0.2,
         FitPageOnLoad: false,
         FitWidthOnLoad: false,
         PrintEnabled: true,
         FullScreenAsMaxWindow: false,
         ProgressiveLoading: false,
         MinZoomSize: 0.2,
         MaxZoomSize: 5,
         SearchMatchAll: false,
         InitViewMode: 'Portrait',

         ViewModeToolsVisible: true,
         ZoomToolsVisible: true,
         NavToolsVisible: true,
         CursorToolsVisible: true,
         SearchToolsVisible: true
     }
     });
// &#93;&#93;></script>
warning FlexPaper无法载入中文文件名。如果右上角的圈圈一直在转,注意是不是文件名称的问题。

参考文章

c# System.Diagnostics.Process 调用外部程序时WaitForExit锁死问题分析及解决方案

FlexPaper+SWFTools 实现仿百度文库及一些小问题

Pdf2Swf命令行参数

解决FlexPaper分页分段加载问题

源码

分页加载:FlexPaper.zip

文件转换服务:PDFtoSWFService.zip

[转载]ASP.NET MVC3实战系列(三):MVC3中使用依赖注入(IOC)

mikel阅读(817)

[转载]ASP.NET MVC3实战系列(三):MVC3中使用依赖注入(IOC) – 王德水 – 博客园.

ASP.NET MVC3实战系列(二):面向接口编程,提高系统可测试性。 中我们说之前的程序虽然可以单元测试。

第一就是我们暴露了HomeController的repository的属性。

第二我们在类内部new了一个对象,假如我们这次是从数据库中得到Lovers,下次想从文件或者Web Service里去数据时,我们必须修改Controller里的代码。

在这一节里我们引入一个依赖注入的工具Ninject.

image

点击安装

image

修改HomeController为如下代码

image

这样我们就不需要暴露属性了,修改对应的单元测试为如下图所示。测试顺利通过

image

但是,当我们运行程序时出错了,因为MVC在实例化Controller时出错,因为没有无参构造函数参数,而我们确要实例化带参的构造函数。

image

我们需要自己实例化Controller,所以我们需要建立一个ControllerFactory并让应用程序使用我们自己的这个ControllerFactory

image

然后在Global.asax.cs里指定使用这个ControllerFactory

image

按F5运行程序又运行正常了

image

至此,这个应用程序已经易于测试了,而且面向了接口编程。易于扩展。

最后,打个小广告:西安分公司急聘如下人员,有意者直接联系我,左边侧栏有我的联系方式,博客园招聘频道有详细描述 http://job.cnblogs.com/offer/13800/

基本要求:
4年以上C#开发经验, .Net基础扎实,熟练使用.Net3.5新特性。
精通ASP.NET Web开发。
熟练使用WCF.
熟练使用ORM,LINQ TO SQL /Entity Framework或者NHibernate.
熟练使用JavaScript, JQuery.
熟悉Web标准,熟悉HTML&CSS.
熟悉SQL Server, 熟练掌握T-SQL,存储过程。
英语读写熟练,可以看懂英文需求,可以和客户流畅的用英语文字沟通(MSN/Skype)
良好的编码习惯。

熟悉下面任意一项优先:
熟悉敏捷开发者优先
英语听说熟练者优先
熟悉ASP.NET MVC者优先。
熟悉WPF者优先
熟悉Silverlight优先
有系统架构经验者优先,有单元测试或者TDD经验者优先。

作者: 王德水
出处:http://www.cnblogs.com/cnblogsfans
版权:本文版权归作者和博客园共有,转载需经作者同意。

[转载]ASP.NET MVC3实战系列(二):面向接口编程,提高系统可测试性。

mikel阅读(890)

[转载]ASP.NET MVC3实战系列(二):面向接口编程,提高系统可测试性。 – 王德水 – 博客园.

ASP.NET MVC 使用MVC的架构,其架构本身就使应用程序更易于测试,但这并不意味着可以随便写出易于测试的程序。我们都知道单元测试在系统开发有着很重要的作用。

我们来写这样的一个程序,系统获取某个坏男人的情人信息,然后发送给他老婆。

1. 建一个Lover的ASP.NET MVC3项目

image

我们需要1个实体类,存储男人,情人和老婆的信息。

image

然后我们需要一个LoverRepository来获取某个人的情人,这里就想成从数据库取数据。我们这里先返回固定的数据

image

建一个HomeController, 代码如下

image

建立一个Index视图

image

建立一个Send 视图

image

F5运行

image

image

2. 重构这个小程序。

我们可以看到如果我们想测试HomeController里Send的这个Action,如果LoverRepository没有开发完或者出错,我们将无法测试

image

image

可以看到单元测试出错了。

image

那么我们如何来隔离LoveRepository呢?我们都知道面向接口编程可以提高系统的可测试性。

打开LoveRepository.cs, 右键重构为接口:

image

image

image

image

为了容易测试,我们把这个接口属性设为公有。

image

这时我们再自己可以实现一个FakeRepository继承ILoverRepository这个接口,这样就测试通过了。

image

但是这样有几个问题:

第一就是我们暴露了HomeController的repository的属性。

第二我们在类内部new了一个对象,假如我们这次是从数据库中得到Lovers,下次想从文件或者Web Service里去数据时,我们必须修改Controller里的代码。

第三我们需要自己写一个Fake类。

要想解决这些问题,下一节我们将讨论ASP.NET MVC3里如何使用IOC来解决对象的依赖问题。

最后,打个小广告:西安分公司急聘如下人员,有意者直接联系我,左边侧栏有我的联系方式,博客园招聘频道有详细描述 http://job.cnblogs.com/offer/13800/

基本要求:
4年以上C#开发经验, .Net基础扎实,熟练使用.Net3.5新特性。
精通ASP.NET Web开发。
熟练使用WCF.
熟练使用ORM,LINQ TO SQL /Entity Framework或者NHibernate.
熟练使用JavaScript, JQuery.
熟悉Web标准,熟悉HTML&CSS.
熟悉SQL Server, 熟练掌握T-SQL,存储过程。
英语读写熟练,可以看懂英文需求,可以和客户流畅的用英语文字沟通(MSN/Skype)
良好的编码习惯。

熟悉下面任意一项优先:
熟悉敏捷开发者优先
英语听说熟练者优先
熟悉ASP.NET MVC者优先。
熟悉WPF者优先
熟悉Silverlight优先
有系统架构经验者优先,有单元测试或者TDD经验者优先。

作者: 王德水
出处:http://www.cnblogs.com/cnblogsfans
版权:本文版权归作者和博客园共有,转载需经作者同意。

[转载]34个漂亮的应用程序后台管理系统界面(系列二)

mikel阅读(875)

[转载]34个漂亮的应用程序后台管理系统界面(系列二) – 梦想天空(山边小溪) – 博客园.

今天这篇文章收集了34个漂亮的应用程序后台管理界面分享给大家。这些界面都是来自themeforest网站,虽然直接下载需要付费的,不过 大部分都提供了在线预览,所以完全能够复制下来,有的提供了预览图,设计师可以根据预览图自己设计。希望这些漂亮的后台管理界面设计案例能帮助到你。(有 登录界面的,点击登录即可进入后台界面)

Flexy Admin


34个漂亮的应用程序后台管理系统界面

Broom Cupboard Admin Skin


34个漂亮的应用程序后台管理系统界面

Fresh CMS


34个漂亮的应用程序后台管理系统界面

Ultimate Admin Panel Solution


34个漂亮的应用程序后台管理系统界面

Titanium


34个漂亮的应用程序后台管理系统界面

Wide Admin


34个漂亮的应用程序后台管理系统界面

AP Admin Panel


34个漂亮的应用程序后台管理系统界面

Ninja Admin


34个漂亮的应用程序后台管理系统界面

Sleek Admin Skin


34个漂亮的应用程序后台管理系统界面

Pro Admin Template


34个漂亮的应用程序后台管理系统界面

您可能还喜欢

[原创]DEDECMS仿站笔记1:自定义联动类别调用

mikel阅读(1138)

1. PrintAutoFieldsAdd 载入自定义表单(用于发布):
文件:inc/inc_archives_function.php
行号:472
函数声明:function PrintAutoFieldsAdd(&$fieldset,$loadtype=’all’)
参数:
$fieldset:自定义模型的字段集合
$loadtype:价值类型有两种:all ,autofield;autofield
功能说明:
发布自定义模型界面调用时,拼装自定义模型的表单。

2. DedeTagParse Dede织梦模板类
文件:include\dedetag.class.php
行号:70
类声明:class DedeTagParse
方法:
 LoadSource($str):载入模板字符串,调用data/tplcache/MD5($str).inc的模板文件,然后调用LoadTemplate()方法解析模板
 LoadTemplate($filename):载入模板文件 然后调用ParseTemplet()解析模板
 ParseTemplet():解析模板
3. 模板列表调用内容模型格式数据写法:
{dede:arclist channelid=’18’ addfields=’xuebu,course_author’}

  • [field:course_author/]aaa
  • {/dede:arclist}
    说明:
    Channelid:内容模型管理中的ID号
    AddFields:定义的字段名,需要在内容模型管理中的“列表附加字段”中添加

    4. 自定义联动类别信息的调用方法:
    1)函数编写
    打开 /include/extend.func.php 文件(注:本文件为自定义函数接口文件,若不存在,请自行创建),在最后一行的?>上添加如下代码:
    function Getsysenum($fields,$egroup){
    global $dSQL;
    $row = $dsql->GetOne(“select * from dede_sys_enum where evalue = ‘”.$fields.”‘and egroup=’”.$egroup. “’”);
    if(!is_array($row)){
    return “联动类别不存在“;
    }
    else{
    return $row[‘ename’];
    }
    }
    保存关闭即可!
    2)函数调用
    这个函数的调用非常简单,只需要将“枚举值”传递过来即可,这个缓存组名通常也就是内容模型中的自定义联动菜单字段名:
    function Getsysenum(‘枚举值’,’联动组名’);
    3)调用实例
    我们来看一下调用的实例代码:
    {dede:arclist titlelen=’60’ row=’8′ channelid=’1′ addfields=’nativeplace’}

  • [field:nativeplace function=”Getsysenum(@me,’ nativeplace’)”/]
    [field:title /]
  • {/dede:arclist}
    其中[field:nativeplace function=”Getsysenum(@me,’ nativeplace’)”/] 就是将nativeplace字段进行Getsysenum函数处理。

    5. 联动类别信息存储表dede_sys_enum:
    字段 类型 长度 是否主键 描述
    ID Smallint 6 是
    Ename Char 30 分类名称
    Evalue Smallint 6 分类ID
    Egroup Char 20 分组名称
    Disorder Smallint 5 排列顺序
    Issign Tinyint 1 是否系统1=系统 0=非系统

    6. 扩展函数编写文件/include/extend.func.php:
    通过上面的扩展查询分类的函数可见可以自定义扩展函数在/include/extend.func.php文件中,用于标签中进行调用,
    于是编写了扩展函数查询给定分类组合的,一级分类信息的扩展数:
    Function GetSysEnum($egroup,$level)
    global $dSQL;
    $row = $dsql->GetOne(“select ename from dede_sys_enum where evalue like ‘%”.$level.”‘and egroup=’”.$egroup. “’”);
    if(!is_array($row)){
    return “联动类别不存在“;
    }
    else{
    return $row[‘ename’];

    调用方式:
    Function=’GetSysEnum(‘xuebu’,’0’)’

    [转载]InfoQ: 如何查看我的订单-REST的流程API设计案例

    mikel阅读(1000)

    [转载]InfoQ: 如何查看我的订单-REST的流程API设计案例.

    这是一个关于订单的故事。四个月前,我在某刚刚上市不久的网上书城框框网购买了一包纸尿裤,因为尺寸不对,我选择了退货,由此开始了我糟糕的用户体 验:首先是快递公司取回了纸尿裤却没有还款给我,接下来,在两个月的时间里,我不得一次又一次的向框框的客服投诉,客服很客气,她让我说出我的订单号然后 说需要帮我查一查,两分钟后,她说需要和快递公司联系,稍后再打给我;随后的客服都很客气,但无一例外的,她们都不清楚我订单的处理情况,她们甚至很惊 讶,快递公司还没有给你办吗?终于,我失去了耐心,我说,难道你们框框就只负责卖东西吗?送货外包了你们就不需要负责了吗?究竟是谁在处理我的订单,谁是 责任人你们不知道吗?整个流程的处理状态你们清楚吗?客服支支吾吾半天,说,我们这就和快递公司联系。

    框框的问题出在什么地方?顾客找不到这个订单的直接负责人,只能和客服打电话,而框框自己也失去该订单的状态了,于是只能再打快递公司的客服,这样 问题透过组织结构层层传递下来,执行力可想而知。那为什么顾客找不到订单的直接负责人呢?因为顾客失去对自己订单的可视化了,我不知道我的订单现在处于一 种什么样的状态,正处于哪一个步骤,谁是这个步骤的责任人,网站上没有,自然无从得知。想想我们的迭代和show case,也正是一种对顾客的可视化手段。

    在下面的章节中,我们将一起来应用rest的架构风格逐步搭建一个端到端的流程管理系统,看看如何解决这个问题,这个问题就是:看在上帝的份上,让 我看看我的订单。为什么使用rest?因为在下面的故事中,我们将会有大量的系统集成工作。为什么集成要用rest?因为REST的实质是充分利用 HTTP协议,将其作为一种应用协议,而不仅仅是传输协议,这样在WEB上做集成时能够最大程度上的达成一致,形成大家都能接受的约定,减少集成的工作 量。另外,不可忽视的一点是,我们更多的是做数据的mashup,这很适合rest的应用特点。不能使用web service吗?当然可以使用web service,只是我们这里使用rest而已。

    第一个需求,我想随时随地查看我的订单

    好吧,很自然,这需要我们的程序支持移动设备。

    图 1  昨天我们这样开发程序

    似乎就在昨天,我们还在如图1一般开发程序,我们一边打开firebug进行调试,一边诅咒IE的不得好死,那时我们的关注点集中在前端,集中在如何使各个浏览器的行为和样式保持一致。而服务器端则是经典的MVC框架,直接将渲染好的HTML文档扔回客户端。

    图 2  今天我们这样开发程序

    然而到了今天,一切都发生了变化,我们开发的程序成了图2的样子,随着IE向标准的靠拢,HTML5似乎有一统客户端之势,然而,移动互联网的兴起 让我们编写程序重新变得复杂,昨天我只需要支持浏览器,现在则需要支持各种手机平台上的原生应用。自然,和昨天存在大量的JavaScript和css框 架来抹平不同浏览器之间的差别一样,现在我们也有了PhoneGapTitanium来抹平不同平台之间的差别,尽管目前这些跨平台工具还存在用户体验不理想的问题,但最重要的变化来自两个方面:一是客户端重新变胖;二是服务器端由返回渲染完成的页面退化为返回数据,具体表现就是对客户端暴露出API。

    对移动设备的支持使得我可以在写代码时、吃饭时、睡觉时甚至坐马桶时随时查看我的订单(当然前提是你得有这些设备),而本文后续的架构变化也会围绕 着图2逐渐演进。至于明天HTML5是否会最终代替原生应用,我觉得不会,不仅仅是技术原因(对设备硬件的使用)更重要的是商业原因,替代后的苹果变得和 现在的微软/诺基亚一样尴尬,那么,也许后天?

    好吧,既然是REST的API设计,我们来看看REST的架构风格。RESTful 架构遵从以下几个原则:

    • 请求是客户-服务器 式的,并很自然地使用一种基于拉的交互风格。
    • 请求是无状态的。每个从客户端到服务器端的请求都必须包含理解此请求所需的全部信息,而且不能利用服务器上所存储的上下文。
    • 客户端和服务器都遵从统一的接口。所有的资源都可通过 Web 的普通接口进行访问 —— HTTP 及 HTTP 方法:GET、POST、PUT 和 DELETE。
    • 客户端通过URI与命名的资源进行交互。
    • 将http 状态码作为系统的状态码。

    REST的实质是充分利用HTTP协议,形成大家都能接受的约定。

    看例子,我们以订单列表作为整个应用的调用入口,我们首先会GET:http://api.kuangkuang.com/orders,服务器返回以下的数据:

    1000
    draft
    
    1001
    completed
    
    

    在返回的数据中,我们看到了:

    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1001"/>
    这个链接引导我们查看具体的订单信息,我们GET:http://api.kuangkuang.com/order/1000,服务器返回以下的数据:
    1000
    draft
    88.0
    
    

    这里我们看到了两个链接:

    <link rel="edit" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <link rel="delete" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>

    它们告诉我们可以对这个处于草拟状态的订单进行修改和删除。我们GET另外一个已完成的订单看看:http://api.kuangkuang.com/order/1001,返回数据:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1001"/>
        <content>
            <id>1001</id>
            <state>completed</state>
            <cost>66.0</cost>
        </content>
    </order>

    没有更多的链接,这意味着我们只能对该订单进行查看。

    在这些交互中,最重要的是服务器端返回数据本身已包含了对其他资源访问和对现在资源操作的线索。这样的好处在于客户端只需要一个入口地址,其他所有 的操作地址全部由服务器端确定,这使得客户端与服务器端解耦,客户端不必再硬编码入URI,能够各自独立的进化,服务器端负责数据、权限以及交互URI的 确定,客户端重新回归展现数据的单一职责。

    第二个需求,实现一个简单的流程

    在上面的例子里,我们看到了订单的CRUD操作,但这并不是实际生活中的真实情况,整个订单的生命周期如下图所示:

    图 3  订单的完整流程

    在实现这个流程时,我们分为两步:第一步对订单进行资源建模;第二步通过工作流对订单进行流程的生命周期管理。

    图 4  订单的资源模型

    图 5  使用工作流管理订单的生命周期

    工作流的职责在于管理订单的生命周期,在其生命周期的不同阶段,我们会有不同的参与者,对订单不同的操作权限。我们的系统架构演变成下面的样子:

    图 6  使用工作流管理资源模型的生命周期

    看例子,这次将我们视角转移到框框网这边,看看框框如何处理我们已提交的订单。我们使用GET:http://api.kuangkuang.com/orders?status=waiting-review来获取所有需要审核的订单,服务器返回以下的数据:

    <orders>
        <link rel="list" media-type="application/xml" url="http://api.kuangkuang.com/orders"/>
        <order>
            <id>1000</id>
            <state>waiting review</state>
            <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        </order>
    </orders>

    我们查看具体的订单信息,我们GET:http://api.kuangkuang.com/order/1000,服务器判断出我们是框框网员工,返回以下的数据:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
            <state>waiting review</state>
            <squence>
                <activity rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000"/>
            </squence>
        </content>
    </order>

    注意到这两行:

    <state>waiting review</state>
    <squence>
        <activity rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000"/>
    </squence>

    这段信息是由工作流加入的,它告诉我们当前订单的状态为等待审核以及下一步需要我们来审核。那么,我们PUT http://api.kuangkuang.com/review/order/1000告诉服务器我们审核通过,服务器返回数据:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
            <state>waiting send</state>
            <squence>
            <activity rel="send" media-type="application/xml" url="http://api.kuangkuang.com/sent/order/1000"/>
            </squence>
        </content>
    </order>

    同样,工作流加入了这两行数据:

    <state>waiting send</state>
    <squence>
        <activity rel="send" media-type="application/xml" url="http://api.kuangkuang.com/sent/order/1000"/>
    </squence>

    告诉我们当前订单状态为等待送货,而下一步需要我们来完成这一步。此时,如果顾客来查看自己的订单会得到什么数据呢?服务器会判断出当前请求的用户是顾客,那么:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
            <state>waiting send</state>
        </content>
    </order>

    顾客能够看到自己的订单正处于等待送货状态,而不会有下一步的动作。恩,很好,框框网订单处理速度很快,而这正是网购的主要竞争力之一。

    等等,如果订单审核不通过呢,继续看流程:

    图 7  增加一个网关

    作为框框网的员工,我们GET:http://api.kuangkuang.com/order/1000,服务器判断出我们是框框网员工,返回以下的数据:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
            <state>waiting review</state>
            <squence>
                <xor rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000">
                    <choice>pass</choice>
                    <choice>reject</choice>
                </xor>
            </squence>
        </content>
    </order>

    注意到这两行:

    <squence>
        <xor rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000">
            <choice>pass</choice>
            <choice>reject</choice>
        </xor>
     </squence>

    系统告诉我们这是一个排他网关,我们需要作出选择,在客户端与服务器端就media-type达成一致的情况下(即客户端能够充分理解服务器端返回 的数据格式,这个数据格式被标准化),我们的代码不需要作出任何的修改,pass和reject作为工作流变量被put到服务器,由工作流引擎进行处理, 同样不会影响到订单的资源建模。

    在这些交互中,最重要的是我们通过工作流实现了REST社区所呼吁的“将超媒体作为应用状态的引擎(hypermedia as the engine of application state)”。更简单地说,URI代表了状态机里的状态迁移。我们通过<squence>、<activity> 和<xor>标签让客户端是通过跟随链接的方式来操作订单状态机的状态转移。

    第三个需求,框框将物流部分外包

    在我们实际的生活中,电商们并不自己送货,他们将这部分工作外包给了物流公司。从成本的角度考虑,外包送货是最合适的选择。实际上,整个订单从提交到最后的完成情况还要稍微复杂一些,如下图所示:

    图 8  订单从提交到完成的整个流程

    从图中我们可以看出,这个流程跨越了两家公司,同时也涉及到了三个系统的集成,这三个系统分别是框框网的前台网站、框框网的后台负责仓储、进出货和 物流的ERP系统以及外包物流公司的ERP系统。三个系统各自有自己的处理流程,整个订单的端到端处理流程由这三个系统的三个流程所共同完成:当我们在框 框网提交订单时,一个消息被发送到框框的后台ERP系统,这个消息触发一个货物的出库流程,当货物打包完毕出库时,一个消息被发送到物流公司的ERP系 统,同时触发物流公司的包裹配送流程,当我们给物流公司的配送员付款完毕时,对我们顾客来说框框的购物流程已经结束,然而整个流程依旧还要继续,配送员回 到公司完款,一个消息被发送回框框的后台ERP,物流公司的包裹配送流程结束,框框网的这个订单这才处理完成。

    在本文的一开始,我们提到了那个糟糕的退货故事,问题就在于当订单交由物流公司进行货物配送时,我们包括框框失去了对配送流程的可视化,物流公司的 处理情况在我们的流程中黑盒了。如何解决这部分的问题呢,有两种处理方法:一是在框框网订单处理流程中加入捕获事件,正如图8中所示的,当框框后台ERP 和物流公司ERP对订单进行处理时,每到一个任务节点就给框框网的订单处理流程发送消息,由此给我们标示出订单的实时状态。

    图 9  通过系统集成传递订单实时处理消息

    现在,让我们来看看自己的订单会得到什么数据呢,GET http://api.kuangkuang.com/order/1000框框网前台网站返回数据:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
            <state>waiting send</state>
            <history>
                <activity rel="submit" time="2011-6-28 14:00" participant="ronghao"/>
                <activity rel="review" time="2011-6-28 14:30" participant="xinpeng"/>
                <activity rel="delivery package" time="2011-6-28 15:00" participant="haorong"/>
                <activity rel="warehouse" time="2011-6-28 17:00" participant="pengxin"/>
            </history>
        </content>
    </order>

    订单状态为等待物流公司送货,注意到这段数据:

    <history>
        <activity rel="submit" time="2011-6-28 14:00" participant="ronghao"/>
        <activity rel="review" time="2011-6-28 14:30" participant="xinpeng"/>
        <activity rel="delivery package" time="2011-6-28 15:00" participant="haorong"/>
        <activity rel="warehouse" time="2011-6-28 17:00" participant="pengxin"/>
    </history>

    工作流加入了订单处理的历史信息,从这段信息可以看出,我们要明天上午才能收到自己的货物了。

    很不错不是吗,但是现实情况又是怎样呢。我们先来看看当当,当当如是说:订单状态变为“已发货”后,您可以登录“我的订单”,点击订单号进入订单详情页查看快递公司的联系方式,用订单号查询即可。我们再来看看卓越,卓越如是说:宅急送配送的订单:登录宅急送网站或拨打020-82252310-802查询;港中能达配送的订单:登录港中能达网站或拨打020-86443920查询。也就是说物流公司的配送流程状态并没有集成到网站中来,如下图所示:

    图 10  网站与物流公司系统没有集成

    为什么没有集成呢?第一是物流公司的客户往往不止框框一家,第二是框框也不会把鸡蛋放在一家物流公司的篮子里,这些给系统集成带来了难度,我们会突 然发现有大量的系统需要集成,而系统间的集成之间存在太多的集成点,调试以及约定,这些都需要大量的工作和成本,系统们被紧紧的耦合在一起。

    既然第一种实现方式使得我们即时查看我们订单状态成本太大,那我们看看第二种方法:使用一个统一的流程管理系统来管理整个端到端的流程。

    图 11  使用统一的业务流程管理系统管理端到端流程

    业务流程管理系统的职责有两个:一是由其管理起各个系统间的集成工作,这样避免了各个系统间的大量耦合;二是由其跟踪订单状态,完成订单在整个流程中的可视化。

    图 12  框框订单的端到端流程

    我们来看看具体的api调用,当我们在框框网站提交一个ID为1000的订单时,框框网站会发送一个消息到http://api.kuangkuang-bpm.com/process-definition/1,由此触发整个的流程,启动一个新的流程实例。发送的消息:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
        </content>
    </order>

    业务流程管理系统给我们返回的消息:

    1
    
    1000
    88.0
    
    订单提交
    
    

    返回的消息中给我们指出了该订单所关联的流程实例ID,当前正在执行的任务。流程系统创建流程实例后接下来继续往下执行,它发送一个消息到框框的后 台ERP系统,触发后台ERP系统对订单的处理,同时告诉其访问当前流程实例的URI。现在我们假设流程执行到物流公司的配送任务,我们在框框网站查看订 单的即时状态系统会有哪些动作。第一步我们同样是GET:http://api.kuangkuang.com/order/1000,返回的数据:

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
        <link rel="process-instance" media-type="application/xml" url="http://api.kuangkuang-bpm.com/process-instance/1"/>
        <content>
            <id>1000</id>
            <cost>88.0</cost>
        </content>
    </order>

    返回的消息中多了一个访问流程实例的URI,那么我们的客户端程序继续GET:http://api.kuangkuang-bpm.com/process-instance/1,返回的数据:

    1
    
    1000
    88.0
    
    
    
    物流配送
    sub-process
    
    
    
    
    
    

    我们看到当前正在执行的任务是物流配送,这是一个子流程任务,想具体了解这个子流程执行的情况,我们的客户端程序继续GET:http://api.zjs-erp.com/order/2000,啊哈,框框将配送任务外包给了宅急送啊。

    <order>
        <link rel="detail" media-type="application/xml" url="http://api.zjs-erp.com/order/2000"/>
        <link rel="process-instance" media-type="application/xml" url="http://api.kuangkuang-bpm.com/process-instance/1"/>
        <content>
            <id>2000</id>
            <cost>88.0</cost>
            <current-activity>
                <name>配送</name>
            </current-activity>
            <history>
                <activity name="接受包裹配送单" time="2011-6-29 15:40" participant="ronghao"/>
                <activity name="包裹入库" time="2011-6-29 15:45" participant="xinpeng"/>
            </history>
        </content>
    </order>

    好了,有了这三段数据,我们就可以清楚的看到订单所经过的各个环节以及当前的状态。从中可以看到两点:一是我们通过kuangkuang- bpm.com所提供的流程服务将各个系统进行了数据和流程的集成;二是各个被集成的系统需要实现rest的api以供我们的客户端程序进行数据的 mashup。

    故事完了吗?还没有,京东618活动简报:收获订单40多万份,订购金额超2亿,已经发货一个多亿,尚有十几万份订单积压,大约三日左右可以处理完 毕。不足之处:流量多次超过4个G,服务器运行缓慢;图书备货量严重不足。与服务器相比,我更加关心如何及时将这几十万份订单处理完毕以及库存如何应对促 销而产生的水平震荡(图书备货量严重不足,无法预测哪些书籍畅销哪些滞销,由此带来的订单迟迟无货),这显然不是一家物流公司可以完成的,需要多家物流公 司一起消化这些订单,那么,问题来了,当我们mashup数据时,如何对这些物流公司返回的不同的数据格式进行处理?为每家公司实现一个适配器?NO!作 为行业的老大,作为一家一流的企业,我们得制定标准。这时就需要标准化media type了,建立行业标准,企业级rest等于自定义、创造和标准化media type。

    第二个问题,kuangkuang-bpm.com算是云服务否?是的,目前算作框框私有云,对自己和业务伙伴提供流程服务。

    最后一个需求,框框要开放平台

    最后一个需求有些跑题,但是那帮人现在都在搞什么开放平台,框框自然也是要跟风一下的。那么,开放平台都开放了个啥?

    图 13  框框的开放平台

    第一是用户的开放,这个通过网站实现,我们增加上百货、品牌频道,吸引商家入驻,这些入驻的商家能够分享我们的网站用户,同时,品牌的用户也能够被 吸引到我们的网站上。同时,开心、人人、新浪微博、腾讯微博的账号能够直接登录我们网站,同时,我们网站的产品、评价也能分享到这些SNS网站里。

    第二是服务的开放,这个通过流程实现,我们能给商家提供仓储、物流、投诉等一系列的服务。不使用我们的网站,自己有网站?没关系,只要数据格式满足我们定义的media type就行。

    图 14  流程服务的开放

    这算完了吗?不算!小马哥说,要开放就全面开放。我们将仓储环节和物流配送环节也开放出来,允许任何服务提供商使用我们的流程服务。

    图 15  物流环节的开放

    只要系统提供数据格式满足我们定义的media type就可以加入到我们的流程中来,分享我们的客户,为上帝提供服务。我们则从中提成。

    所以,真正的开放是整个流程服务各个环节的开放,不仅仅对流程消费者,也对流程服务提供商,我们在其中起一个协调的作用。由此,我们将 kuangkuang-bpm.com服务开放出来变成一个公有云服务,后来,我们惊讶的发现,kuangkuang-bpm.com比 kuangkuang.com更赚钱,因为它建立起了一个生态链。

    小结

    在上面的故事里,我们一步步看到了框框的系统架构演进,不难看出,架构演进的过程实际上系统不断分解的过程。

    对移动设备的支持,使得我们将页面渲染逻辑从服务器端剥离出来交由客户端完成,服务器只负责提供数据。通过rest的超媒体特性,客户端和服务器端程序能够各自独立演进。

    对订单流程的支持,使得我们采用工作流技术,将资源模型(订单)与其生命周期模型分离,分别交由原有系统和工作流系统管理,这样当某部分需求发生变化(例如增加一个审核不通过步骤),能够隔离变化,容易修改。

    对端到端跨系统流程的支持,使得我们引入一个独立的业务流程管理系统,由此来协调各个系统间的集成工作,避免系统间的大量耦合。同时,我们看到定义和标准化一个大家都能理解的media type是如此的重要。

    对开放平台的支持,使得我们将业务流程管理系统开放出来,作为公有云服务平台,同时流程中的各个环节能够开放出来,作为服务/应用接入和接出的接口,真正做到松耦合,由此看到开放平台的实质是流程服务的开放。

    在同事陈金洲的《架构腐化之谜》中,文章最后提到解决这一问题的方法是:创建应用程序的生态环境,而非单一的项目。深得我心,而我们这里想表达的是,随着系统的演进,我们需要不断进行系统的分解,做到服务的独立演化。

    至于rest,它的作用就在于充分利用HTTP协议,将其作为一种应用协议,而不仅仅是传输协议,这样在WEB上做集成时能够最大程度上的达成一致,形成大家都能接受的约定,减少集成的工作量,对外接口一致,内部独立演化。

    [转载]FCKEditor源代码分析及基于.Net的简化

    mikel阅读(1083)

    [转载]FCKEditor源代码分析及基于.Net的简化 – 吴剑-WEB应用 – 博客园.

    FCKEditor源代码分析及基于.Net的简化

    吴剑 2010-01-08

    http://wu-jian.cnblogs.com/

    前言
    FCKEditor 是一款开源的非常优秀的WEB在线编辑器,它的JS类库几乎匹敌于当前流行的JQuery,目前最新版本为2.65,官方网站http://www.fckeditor.net/ 。本文基于.Net的应用针对FCKEditor的源代码进行分析,同时改造了部分过于复杂的功能,将其简单化。供大家学习讨论之用,个人能力有限,不足之处还请指正。
    DEMO环境:.Net Framework 3.5,Visual Studio 2008,Client:FCKEditor 2.6.5,Server:FCKEditor.Net 2.6.3

    下载安装

    FCKEditor包含了两部分:应用于客户端的FCKEditor 2.6.5 和 应用于.Net服务端的FCKEditor.Net 2.6.3,从官网下载解压后如下:

    2.6.3是C#的源代码,如果不需要自定义修改对DLL进行重新编译,那么只要引用项目中的 bin\Debug\2.0\FredCK.FCKeditorV2.dll即可。因为FCKEditor提供了对多语言的支持,在2.6.4中能看 到.PHP,.ASP,.PL,.CFC,.CFM等开发源文件后缀,在此我们只进行FCKEditor与.Net的结合应用,所以精减后如下:

    fckconfig.js配置

    工具栏菜单配置:

    FCKConfig.ToolbarSets["Default"] = [
    ['Source','DocProps','-','Save','NewPage','Preview','-','Templates'],
    ['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],
    ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
    ['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
    '/',
    ['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],
    ['OrderedList','UnorderedList','-','Outdent','Indent','Blockquote','CreateDiv'],
    ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
    ['Link','Unlink','Anchor'],
    ['Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],
    '/',
    ['Style','FontFormat','FontName','FontSize'],
    ['TextColor','BGColor'],
    ['FitWindow','ShowBlocks','-','About']    // No comma for the last row.
    ] ;
    
    /*
    -                :    分隔线
    /                :    换行
    
    Source            :    源代码
    DocProps        :    页面属性
    Save            :    保存
    NewPage            :    新建
    Preview            :    预览
    Templates        :    模版
    
    Cut                :    剪切
    Copy            :    复制
    Paste            :    粘贴
    PasteText        :    粘贴为无格式文本
    PasteWord        :    从MS-WORD粘贴
    Print            :    打印
    SpellCheck        :    拼写检查
    Undo            :    撤消
    Redo            :    重做
    Find            :    查找
    Replace            :    替换
    SelectAll        :    全选
    RemoveFormat    :    清除格式
    
    Form            :    表单
    Checkbox        :    复选框
    Radio            :    单选按钮
    TextField        :    单行文本
    Textarea        :    多行文本
    Select            :    列表/菜单
    Button            :    按钮
    ImageButton        :    图像域
    HiddenField        :    隐藏域
    
    Bold            :    加粗
    Italic            :    倾斜
    Underline        :    下划线
    StrikeThrough    :    删除线
    Subscript        :    下标
    Superscript        :    上标
    
    OrderedList        :    插入/删除编号列表
    UnorderedList    :    插入/删除项目列表
    Outdent            :    减少缩进量
    Indent            :    增加缩进量
    Blockquote        :    块引用
    CreateDiv        :    新增DIV标签
    
    JustifyLeft        :    左对齐
    JustifyCenter    :    居中对齐
    JustifyRight    :    右对齐
    JustifyFull        :    两端对齐
    
    Link            :    插入/编辑超链接
    Unlink            :    取消超链接
    Anchor            :    插入/编辑描点链接
    
    Image            :    插入/编辑图像
    Flash            :    插入/编辑FLASH
    Table            :    插入/编辑表格
    Rule            :    插入水平线
    Smiley            :    插入表情图标
    SpecialChar        :    插入特殊符号
    PageBreak        :    插入分页符
    
    Style            :    样式
    FontFormat        :    格式
    FontName        :    字体
    FontSize        :    大小
    
    TextColor        :    文本颜色
    BGColor            :    背景颜色
    
    FitWindow        :    全屏编辑
    ShowBlocks        :    显示区块
    About            :    关于FCKeditor
    */
    

    其它配置:

    /*回车键 [ p | div | br ]*/
    FCKConfig.EnterMode = 'br' ;
    /*Shift + 回车键 [ p | div | br ]*/
    FCKConfig.ShiftEnterMode = 'p' ;
    

    已发现BUG修复

    修复文件上传脚本执行权限错误

    参见:http://dev.fckeditor.net/attachment/ticket/2115/2115.patch

    使用VS2005打开FredCK.FCKeditorV2.vs2005.csproj,打开FileBrowser > FileWorkerBase.cs,在第118行

    //原始代码
    Response.Write( @"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();" );
    //修改后的代码
    Response.Write(@"(function(){var d=document.domain;while (true){try{var A=window.parent.OnUploadCompleted;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");
    

    插入/编辑超链接

    原始:

    简化:

    如上图所示,对“插入/编辑超链接”功能进行了彻底简化,不可否认源代码中考虑了超链接的几乎所有应用情景,非常之完善和全面,甚至提供了服务器目录的浏览和文件上传功能,但这也恰恰暴露了更多的安全隐患,参考了各大网站的应用,简化为仅显示文本和链接地址。

    这部分功能的源代码主要分布在两个文件中:

    UI部分 /editor/dialog/fck_link.html

    功能函数 /editor/dialog/fck_link/fck_link.js

    因为原始版本包含了过于复杂的功能,所以在分析和修改js代码时也费了不少功夫,删除了多余的功能函数,支持了文本的显示,缩减大约90%的代码。具体细节可查看DEMO。

    插入/编辑FLASH

    原始:

    简化:

    如上图所示, 去除了服务器浏览和文件上传功能;去除了“高级”里的多余选项,将功能整合在一个标签下。

    这部分功能源代码分布在三个文件中:

    UI界面 /editor/dialog/fck_flash.html

    功能函数 /editor/dialog/fck_flash/fck_flash.js

    Flash预览 /editor/dialog/fck_flash/fck_flash_preview.html

    插入/编辑表格

    原始:

    简化:

    该部分功能主要包含在 /editor/dialog/fck_table.html 中,如上图,去除了“标题”、“摘要”、“标题单元格”三项,通过生成的HTML代码可以发现,FCKEditor是一款非常严谨的软件,它严格遵循了 W3C。但大部开发人员都不熟悉的HTML标签对于用户来说使用就更较少了,固去除了这三项,原始与简化后控件生成的HTML差别如下:
    原始:

    <table style="height: 200px;" border="1" cellspacing="1" cellpadding="1" width="200" align="center" summary="summary"><caption>caption</caption>
    <thead>
    <tr>
    <th scope="col"></th>
    <th scope="col"></th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td></td>
    <td></td>
    </tr>
    </tbody>
    </table>
    

    简化:

    <table style="height: 200px;" border="1" cellspacing="1" cellpadding="1" width="200" align="center">
    <tbody>
    <tr>
    <td></td>
    <td></td>
    </tr>
    <tr>
    <td></td>
    <td></td>
    </tr>
    </tbody>
    </table>
    

    插入/编辑图片

    原始:

    简化:

    一直对 FCKEditor的上传功能不满意,首先是安全问题,“服务器浏览”这项功能之前就曾暴露过严重安全漏洞,所以完全屏蔽了这项功能,彻底删除了 /editor/filemanager/browser 目录;

    其次是上传代码的扩展受限,比如上传图片时生成缩略图,实现这样一个常见的附加功能在FCKEditor现有结构下困难重重;

    再有配置复杂且冗余,比如我的项目中十个页面用到了FCKEditor,每处的上传处理逻辑均不相同,这种需求难道要放十个FCK客户端十个配置?

    在此并非刻意将FCK的上传批的一无是处,恰恰相反,我倒觉得FCK的开发团队是绝对的完美主义者,如果仔细研究了源代码你就会毫不怀疑这一点。他们要把这个控件做的支持多语言,多功能,又要保持绝对一至性。过于追求完美必然就会带来其它的牺牲。

    OK,我决定重写FCKEditor的图片上传代码框架,仅争对.Net,目标是让开发者最简单最便捷的处理用户的上传。

    概述一下我的想法:删除FCKEditor 中一切上传相关的配置,而只需指定一个参数:“我的上传处理类”。FCK会将上传交由这个类来处理,在这个类里你可以随心所欲的添加任何代码,权限控制、 与数据库交互、图片处理、存放位置……所有逻辑均在这个类里实现,然后告诉FCK这个类的位置即可。下面详细描述现实的过程:

    Step1.让FCKEditor将上传交由“我的上传处理类”

    首先为FCKEditor添加属性“ImageUploadAssembly” ,这个属性告诉FCK“我的上传处理类”的位置,格式为“程序集名称,类名称”,后面将会在FCK中扩展代码利用反射来创建该类的对象,并将上传交由这个 对象处理。在服务端代码FCKEditor.cs中添加属性:

    ///
    /// 用于指明当前控件上传图片处理的类
    /// 格式为“程序集名称,类名称”
    ///
    [DefaultValue("")]
    public string ImageUploadAssembly
    {
    get
    {
    object o = ViewState["ImageUploadAssembly"];
    return (o == null ? "" : (string)o);
    }
    
    set
    {
    ViewState["ImageUploadAssembly"] = value;
    }
    }
    
    

    接着在页面上添加FCKEditor控件时就能指定该属性了,如下代码所示:

    
    

    然后是一个关键:如何将属性值传递到FCK中的Upload.cs中?花了不少时间对FCK源代码进行研究,整理出如下步骤:

    1、首先将属性值以URL方式传递到 /editor/fckeditor.html中,如 /editor/fckeditor.html?ImageUploadAssembly=FCKEditorSimpleNet.Web,FCKEditorSimpleNet.Web.MyImageUploadHandler.Default ,这一步需要修改服务端Fckeditor.cs,如下:

    protected override void Render(HtmlTextWriter writer)
    {
    //....部分代码略
    if ( _IsCompatible )
    {
    //....部分代码略
    
    //将图片上传处理程序集,类名称作为参数传递到 Fckeditor.html
    if (this.ImageUploadAssembly.Length &gt; 0)
    sLink += "&amp;ImageUploadAssembly=" + this.ImageUploadAssembly;
    }
    }
    

    2、从 fckeditor.html 将参数传递到 /editor/fckdialog.html ,这个过程包含在 /editor/js/fckeditorcode_gecko.js 和 /editor/js/fckeditorcode_ie.js 中,这两个js是经过min处理的,分别对应了IE和非IE浏览器,两个文件基本一至,只是JS写法上稍有差别,修改如下:

    O.src=FCKConfig.BasePath+'fckdialog.html?ImageUploadAssembly='+(FCKURLParams['ImageUploadAssembly']||'');
    

    3、从 fckdialog.html 将参数传递到 /editor/dialog/fck_image.html

    在fckdialog.html定义函数getUrlParameter,该函数供iframe子页面调用,以获取Url参数值:

    //获取fckdialog.html的参数
    function getUrlParameter(pName){
    var pVal = "";
    var aParams = document.location.search.substr(1).split('&amp;');
    if(aParams.length &gt; 0){
    for(var i = 0; i &lt; aParams.length; i++ ){
    var aParam = aParams[i].split('=') ;
    var sParamName  = decodeURIComponent( aParam[0] );
    var sParamValue = decodeURIComponent( aParam[1] );
    if(pName.toUpperCase() == sParamName.toUpperCase()){
    pVal = sParamValue;
    }
    }
    }
    return pVal;
    }
    

    4、从 fck_image.html 将参数传递到 Uploader.cs

    fck_image.html 会将图片放在一个INPUT中,然后POST到服务端(ASPX),INPUT的名称为NewFile,服务端ASPX对应的Class就是Uploader.cs,现在要添加一个ImageUploadAssembly属性值一并提交给服务端,在此使用GET方式在URL后附加一个参数,修改 /editor/dialog/fck_image/fck_image.js 的 window.onload 函数,将参数值附加到 form.action,如下:

    window.onload = function()
    {
    //....部分代码略
    GetE('frmUpload').action = FCKConfig.BasePath + "filemanager/image/upload.aspx?ImageUploadAssembly=" + dialog.getUrlParameter("ImageUploadAssembly");
    }
    

    OK,至此在Uploader.cs里用Request.QueryString[“ImageUploadAssembly”]就能获取到我们设置的属性值了。

    接下来是根据该属性的值动态构造出对象,这里利用了.Net的反射,帖出Uploader.cs代码:

    using System;
    using System.Reflection;
    using System.Web;
    
    namespace FredCK.FCKeditorV2.ImageUpload
    {
    ///
    /// 图片上传接口
    /// 接收GET参数 ImageUploadAssembly,根据参数值反射出自定义图片处理对象
    ///
    public class Uploader : System.Web.UI.Page
    {
    protected override void OnLoad(EventArgs e)
    {
    if (Request.QueryString["ImageUploadAssembly"] != null)
    {
    string ass = Request.QueryString["ImageUploadAssembly"].Trim();
    if (ass != string.Empty)
    {
    string[] assArray = ass.Split(',');
    
    if (assArray.Length == 2)
    {
    if (assArray[0].Trim() != string.Empty &amp;&amp; assArray[1].Trim() != string.Empty)
    {
    //获取上传文件
    HttpPostedFile hpf = Request.Files["NewFile"];
    if (hpf != null)
    {
    FredCK.FCKeditorV2.ImageUpload.Base customUploader = null;
    bool ok = true;
    
    try
    {
    customUploader = (FredCK.FCKeditorV2.ImageUpload.Base)this.createObject(assArray[0].Trim(), assArray[1].Trim());
    customUploader.PostFile = hpf;
    }
    catch
    {
    ok = false;
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "自定义处理类不存在或设置的值不正确!&lt; /span&gt;");
    }
    
    if (ok)
    {
    //调用自定义类中的Save方法&lt; /span&gt;
    customUploader.Save();
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "未获取上传文件!");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
    }
    }
    
    ///
    /// 反射创建对象
    ///
    ///
    <span> </span>程序集名称         ///
    <span> </span>完整类名称         ///
    private object createObject(string assemblyName, string classFullName)
    {
    return Assembly.Load(assemblyName).CreateInstance(classFullName);
    }
    }
    }
    

    至此上传逻辑告一段落,因前面的描述和代码即有客户端的又有服务端的,并且只是核心代码片断,不知各位有没有看明白。总结一下这部分的逻辑:在客户端定义一个参数,将该参数传递到服务端,服务端根据参数值构造对象。如下图所示:

    Step2.自定义上传处理类的约束

    很庆幸可以在.Net里用一个类来处理用户的上传,并且有反射这样的技术来动态构造对象。这段时间在写一些ANSI C,我想也只有在写C的时候才能越发体会C#、高级语言、OOP的好。所有代码都是对现实一件事情的抽象,用C#你的代码可以跟思维同步,而如果用C你就 得考虑计算机是否明白和接受你的思维和代码了。

    进入正题,分析一下自定义上传处理类的逻辑:首先得接收文件,.Net中是一个HttpPostedFile对象;然后对文件进行处理、保存等系列 操作;最后将结果告诉FCKEditor,由它来控制UI及用户交互。把这些必需的逻辑封装成一个抽象父类,每个用户自定义的上传处理类必需从该类继承, 并实现相应的抽象方法:

    using System;
    using System.Web;
    
    namespace FredCK.FCKeditorV2.ImageUpload
    {
    ///
    /// 图片上传的抽象基类
    ///
    public abstract class Base : System.Web.UI.Page
    {
    private System.Web.HttpPostedFile mPostFile;
    
    ///
    /// 上传文件
    ///
    public System.Web.HttpPostedFile PostFile {
    get { return this.mPostFile; }
    set { this.mPostFile = value; }
    }
    
    ///
    /// 文件上传结束回调客户端
    ///
    ///
    <span> </span>上传是否成功         ///
    <span> </span>上传完成后该文件的URL&lt; /span&gt;         ///
    <span> </span>自定义消息         public static void SendFileUploadResponse(bool isSucceed, string fileUrl, string customMsg)
    {
    System.Web.HttpContext.Current.Response.Clear();
    System.Web.HttpContext.Current.Response.Write("
    
    <script type="text/javascript">// <!&#91;CDATA&#91;
    ");
                System.Web.HttpContext.Current.Response.Write(@"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");
                System.Web.HttpContext.Current.Response.Write("window.parent.OnUploadCompleted(" + isSucceed.ToString().ToLower() + ", '" + fileUrl + "', '" + customMsg + "');");
                System.Web.HttpContext.Current.Response.Write("
    // &#93;&#93;></script>");
    System.Web.HttpContext.Current.Response.End();
    }
    
    ///
    /// 图片文件处理及保存
    /// 无论结果如何,类的逻辑终点处必需调用 SendFileUploadResponse 以响应FCK客户端
    ///
    public abstract void Save();
    
    }//end class
    }
    

    最后是自定义上传处理类,里面可以根据自己情况添加任何逻辑,但它必须继承于FredCK.FCKeditorV2.ImageUpload.Base,并且在处理终点调用SendFileUploadResponse方法,以告之Fck客户端进行后继处理,如下代码示例了对上传图片大小、文件类型验证、文件保存:

    using System;
    using System.Web;
    
    namespace FCKEditorSimpleNet.Web.MyImageUploadHandler
    {
    public class Default : FredCK.FCKeditorV2.ImageUpload.Base
    {
    ///
    /// 允许的上传图片大小(KB)
    ///
    private const int FILE_MAX = 1024;
    
    public override void Save()
    {
    if (this.PostFile != null)
    {
    if (this.PostFile.ContentLength &gt; 0)
    {
    if (this.PostFile.ContentLength &lt;= FILE_MAX * 1024)
    {
    if (this.PostFile.ContentType == "image/pjpeg" || this.PostFile.ContentType == "image/jpeg" || this.PostFile.ContentType == "image/gif" || this.PostFile.ContentType == "image/bmp" || this.PostFile.ContentType == "image/png")
    {
    //图片处理扩展
    
    //保存
    string fileName = Guid.NewGuid().ToString() + ".jpg";
    string path = HttpRuntime.AppDomainAppPath + @"fck_upload\";
    this.PostFile.SaveAs(path + fileName);
    
    //客户端响应
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(true, "/fck_upload/" + fileName, "图片上传成功!");
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "图片格式不正确!目前支持JPG、GIF、BMP与PNG格式");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "图处大小不能超过" + FILE_MAX.ToString() + "KB!");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "未获取图片数据!");
    }
    }
    else
    {
    FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "未获取图片对象!");
    }
    }
    
    }//end class
    }
    

    DEMO下载

    点击下载FCKEditorSimpleNet 2.65

    结束语

    从学习的角度对FCKEditor原代码进行分析和作了部分修改,因FCKEditor是一个复杂的基于JS的开源软件,更多细节难以在文章中描 述,可下载查看DEMO深入了解,希望对有需要的朋友带来帮助。同时因个人能力、时间、精力有限,不足之处还请指正,后继也会进一步完善本文,包括 FCKEditor的JS框架、配置、Dialog实现、JS拖拽实现、窗体关系、多语言实现……每个细节几乎都能作为一个独立项来研究,之后再 逐渐补充完整。

    [转载]LINQ学习之旅——第一站Linq To Objects基础

    mikel阅读(906)

    [转载]LINQ学习之旅——第一站”LTO”基础 – Rookie_J – 博客园.

    通过前几节对LINQ中所涉及到的C#语言新特性的学习,我们已经做好了LINQ学习之旅的准备。接着我们踏入LINQ学习之旅的第一 站”LTO”,即Linq To Object,它是用于操作内存对象的LINQ编程接口,针对内存中的集合进行操作。所以从今天开始以及之后的几节内容都将围绕Linq To Object这个主题展开讲解。那么今天所讲的主要内容是有关于Linq To Object中的一些基本概念:IEnumerable<T>泛型接口、序列以及标准查询操作符。其中标准查询操作符在这里只做概念上的统一 讲解,而对于单个具体的标准查询操作符将在后续几节里进行详细地讲解。

    泛型接口IEnumerable<T>与IEnumerable接口类似,允许对接口内部的元素进行列举操作,集合只要是实现了接口IEnumerable<T>或IEnumerable就可以对其进行查询。而序列则是表示一个实现了接口IEnumerable<T>的集合对象。同时Linq To Object中的标准查询操作符都是针对序列的,标准查询操作符实质上就是一些扩展方法, 这些扩展方法在静态类System.Linq.Enumerable中,其原型的第一个参数(即带this修饰符的参数)是 IEnumerable<T>类型,所以这些扩展方法可以直接在IEnumerable<T>的实例对象上直接调用。但并不是所 有的标准查询操作符都是扩展方法,也有一些是普通的静态方法,而这些静态方法一般是用来实现更为复杂的功能。

    另外在早期的C#语言里的一些集合类型,它们实现的是IEnumerable接口,而并不支持IEnumerable<T>接口,这就使得不能在这些类型集合对象上使用标准查询操作符,为了解决这个问题,C#中提供个Cast和OfType标准查询操作符把这些不支持IEnumerable<T>接口的集合类型转化为支持IEnumerable<T>接口的集合类型。这样标准查询操作符就可以适用于C#语言的所有集合类型。下面用标准查询操作符Select来说明标准查询操作符的一些重要特性:

    static void Main(string[] args)
    {
    char[] ABC = new char[] { 'A', 'B', 'C', 'D', 'E' };
    
    var ABCs = ABC.Select(i =&gt; i);//调用标准查询操作符Select
    
    foreach (char c in ABCs)
    {
    Console.WriteLine(c);
    }
    
    Console.Read();
    }
    

    结果

    示例中标准查询操作符Select返回了一个IEnumerable<T>集合对象(也称序列),但需要注意的是它所返回的序列对象内部包含的元素并不是在标准查询操作符被调用时立刻创建的

    var ABCs = ABC.Select(i => i);//返回的是空序列

    而是在执行列举该序列对象元素的代码时

    foreach (char c in ABCs)
    {
    Console.WriteLine(c);
    }

    系统动态利用yield关键字来创建的。我们把这个特性称为延时查询。因此在标准查询操作符里有分延时标准查询操作符和非延时标准查询操作符。Select方法就属于延时标准查询操作符。接下来我用一个示例来具体说明延时标准查询操作符和非延时标准查询操作符在查询数据时的区别:

    static void Main(string[] args)
    {
    //延时查询
    Console.WriteLine("延时查询:");
    Console.WriteLine("-----------------------------");
    
    char[] ABC = new char[] { 'A', 'B', 'C', 'D', 'E' };
    var ABCs = ABC.Select(i =&gt; i);//调用标准查询操作符Select
    
    //第一次列举
    Console.WriteLine("修改前:");
    foreach (char c in ABCs)
    {
    Console.WriteLine(c);
    }
    
    ABC[0] = 'H';
    ABC[1] = 'E';
    ABC[2] = 'L';
    ABC[3] = 'L';
    ABC[4] = 'O';
    
    //第二次列举
    Console.WriteLine("修改后:");
    foreach (char c in ABCs)
    {
    Console.WriteLine(c);
    }
    
    //非延时查询
    Console.WriteLine("非延时查询:");
    Console.WriteLine("-----------------------------");
    
    char[] ABC1 = new char[] { 'A', 'B', 'C', 'D', 'E' };
    var ABCs1 = ABC1.Select(i =&gt; i).ToArray();//调用标准查询操作符Select及ToArray
    
    //第一次列举
    Console.WriteLine("修改前:");
    foreach (char c in ABCs1)
    {
    Console.WriteLine(c);
    }
    
    ABC1[0] = 'H';
    ABC1[1] = 'E';
    ABC1[2] = 'L';
    ABC1[3] = 'L';
    ABC1[4] = 'O';
    
    //第二次列举
    Console.WriteLine("修改后:");
    foreach (char c in ABCs1)
    {
    Console.WriteLine(c);
    }
    
    Console.Read();
    }
    

    结果

    其中方法ToArray()属于非延时标准查询操作符。从运行结果中可以清楚地看到,延时查询在数据源修改前后发生变化,而非延时查询则相同。那么为什么会出现这样的情况呢?原 来当调用Select操作符返回时,返回的是一个空的IEnumerable<T>集合对象,在执行foreach语句时相应的元素才一个一 个地生成,并填充到这个空的IEnumerable<T>集合对象里去,所以在第一次列举完成后,修改原始数据,在执行第二次列举时,修改的 数据便马上呈现出来。而ToArray()操作符其实返回的是T[]集合,而非IEnumerable<T>集合对象,来缓存数据集合,所以 尽管对数据源进行了修改,但列举的集合对象是同一个,结果也就没有变化了

    最后讲解一下LINQ中这些标准查询操作符的方法原型里的一个委托类型的参数,这些委托类型定义在System.Linq中。以下是System.Linq空间里定义的委托:

    public detegate T Func<T>();

    public detegate T Func<A0,T>(A0 arg);

    public detegate T Func<A0,A1,T>(A0 arg0,A1 arg1);

    public detegate T Func<A0,A1,A2,T>(A0 arg0,A1 arg1,A2 arg2);

    public detegate T Func<A0,A1,A2,A3,T>(A0 arg0,A1 arg1,A2 arg2,A3 arg3);

    这些定义的委托在今后所要讲到的大部分标准查询操作符里都会涉及到,比如示例中所提到Select操作符,它的方法原型定义如下:

    public static IEnumerable<T> Select<T,S>(

    this IEnumerable<T> source,

    Func<T,S> selector);

    原型中的委托类型Func<T,S>就与上述委托类型的第二种相对应,其中的T就与public detegate T Func<A0,T>(A0 arg)中的A0相对应,而S则与public detegate T Func<A0,T>(A0 arg)中的T相对应,从中可以看出操作符Select能够接受一个匿名方法(或者Lambda表达式),且该匿名方法返回一个类型为S的值,正如上述代 码Select操作符接受的一个Lambda表达式i=>i,就是代入一个Char类型的值i,并返回同样类型的一个值i。从而可知,这些在 System.Linq空间里定义的委托的重要性。