asp.net下利用MVC模式实现Extjs表格增删改查 - yfsmooth - 博客园

mikel阅读(771)

来源: asp.net下利用MVC模式实现Extjs表格增删改查 – yfsmooth – 博客园

在网上看到有很多人写extjs下的表格控件的增删改查,但是大多数都是直接从后台读取数据,很少有跟数据库进行交互的模式。

今天就来写一个这样的例子。欢迎大家交流指正。

 

首先简单介绍一下MVC模式,MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。

Model(模型)
是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
View(视图)
是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
Controller(控制器)
是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
下面就为大家说明代码示例:
视图部分:
前台JS部分代码:
复制代码
Ext.onReady(function () {
                    var csm = new Ext.grid.CheckboxSelectionModel({//创建checkbox对象
                        handleMouseDown: new Ext.emptyFn()
                    });
                    var cum = new Ext.grid.ColumnModel([
                    csm, //checkbox对象
                    {header: "用户ID", dataIndex: "id", sortable: true
                  },
                    { header: '用户姓名', dataIndex: 'name', sortable: true,
                        editor: new Ext.grid.GridEditor(
                        new Ext.form.TextField({ allowBlank: false }))
                    },
                    { header: '性别', dataIndex: 'sex', sortable: true,
                        editor: new Ext.grid.GridEditor(
                        new Ext.form.TextField({ allowBlank: false }))
                    }
                   ]);
                ;

                var store = new Ext.data.Store({
                    proxy: new Ext.data.HttpProxy({ url: 'UserData.aspx' }),
                    reader: new Ext.data.JsonReader({
                        totalProperty: 'totalCount', //json字符串中的字段,数据量大小
                        root: 'data'//json字符串中的字段
                    }, [
                            { name: 'id' },
                            { name: 'name' },
                            { name: 'sex' }
                        ]),
                    remoteSort: true
                });
                var Record = Ext.data.Record.create([
                           { name: 'id', type: 'string' },
                        { name: 'name', type: 'string' },
                        { name: 'sex', type: 'string' }
                        ]);
                store.load({ params: { start: 0, limit: 5} }); //运行加载表格数据

                var cumgrid = new Ext.grid.EditorGridPanel({
                    renderTo: 'cumGrid',
                    store: store,
                    stripeRows: true, //斑马线效果
                    viewConfig: {
                        forceFit: true,
                        columnsText: "显示的列",
                        sortAscText: "升序",
                        sortDescText: "降序"
                    },
                    height: 200,
                    width: 550,
                    sm: csm,
                    bbar: new Ext.PagingToolbar({
                        pageSize: 5, //每页信息条数
                        store: store,
                        autowidth: true,
                        autoHeight: true,
                        displayInfo: true,
                        prevText: "上一页",
                        nextText: "下一页",
                        refreshText: "刷新",
                        lastText: "最后页",
                        firstText: "第一页",
                        beforePageText: "当前页",
                        afterPageText: "共{0}页",
                        displayMsg: '显示第{0}条到第{1}条记录,一共{2}条',
                        emptyMsg: '没有记录'
                    }),
                    tbar: new Ext.Toolbar(['-', {
                        text: '添加一行',
                        handler: function () {
                            var win = new Ext.Window({
                                title: '添加用户',
                                layout: 'fit',   
                                height: 300,   
                                width: 300, 
                                border: 0,    
                                frame: true,    //去除窗体的panel框架
                                plain: true,
                                html: '<iframe frameborder=0 width="100%" height="100%" allowtransparency="true" scrolling=auto src="addUser.htm"></iframe>'
                            });
                            win.show();    //显示窗口

                        }
                    }, '-', {
                        text: '删除一行',
                        handler: function () {
                            Ext.Msg.confirm('信息', '确定要删除?', function (btn) {
                                if (btn = 'yes') {
                                    var id = "";
                                    function getid() {
                                        for (var i = 0; i < cumgrid.getSelectionModel().getSelections().length; i++) {
                                            id += cumgrid.getSelectionModel().getSelections()[i].get("id");
                                            id += ',';
                                        }
                                    };
                                    getid(); //初始化选中行id字符串数组
                                    Ext.Ajax.request({
                                        url: "DelUserInfo.aspx",
                                        method: "post",
                                        params: { id: id },
                                        success: function (response) {
                                            Ext.Msg.alert("恭喜", "删除成功了!");
                                            store.reload();
                                        },
                                        failure: function () {
                                            Ext.Msg.alert("提 示", "删除失败了!");
                                        }
                                    })
                                }
                            });
                        }
                    }, '-', {
                        text: '保存',
                        handler: function () {
                            var m = store.getModifiedRecords().slice(0);
                            var jsonArray = [];
                            Ext.each(m, function (item) {
                                jsonArray.push(item.data);

                            })

                            if (false) {//判断条件
                                return;
                            } else {

                                // alert(Ext.encode(jsonArray))

                                Ext.Ajax.request({
                                    //url: "Demo/Operator.aspx",
                                    url: "SaveUserInfo.aspx",
                                    method: "POST",
                                    params: 'data=' + encodeURIComponent(Ext.encode(jsonArray)),
                                    success: function (response, option) {
                                        store.reload();
                                        alert(response.responseText);
                                    },
                                    failure: function (response) {
                                        store.reload();
                                        alert(response.responseText)
                                        Ext.Msg.alert("提示", "修改失败了!");
                                    }
                                });
                            }
                        }
                    }, '-']),
                    cm: cum
                });
                cumgrid.render(); //刷新表格
            });
复制代码

 

表格界面如上图所示。增删改查分别通过,添加,删除,保存修改,和后台读取数据并分页实现。

业务处理部分:
UserBll.cs:
复制代码
public class UserBLL
{
    public UserBLL()
    {
        //
        //TODO: 在此处添加构造函数逻辑
        //
    }

    UserDao dao = new UserDao();
    JsonHelper json = new JsonHelper();
    DataTable dt = new DataTable();
    //获取全部数据
    public string GetUserInfo() 
    {
        dt = dao.GetAllInfo();//从数据库中读取数据
        json.success = true;
        foreach (DataRow dr in dt.Rows) //将读取出的数据转换成字符串
        {
            json.AddItem("id", dr["id"].ToString());
            json.AddItem("name", dr["name"].ToString());
            json.AddItem("sex", dr["sex"].ToString());
            json.ItemOk();
        }
        string jsons = json.ToString();
        return jsons;
    }
    //根据id获取用户数据
    public UserBean getUserInfosNoState(int id) 
    {
        return dao.getUserInfosNoState(id);
    }
    //分页获取数据
    public string GetUserInfoPage(int start, int limit)
    {
        JSONHelper json = new JSONHelper();
        dt = dao.GetUserInfoPaging(start, limit); //从数据库中读取数据
        json.success = true;
        foreach (DataRow dr in dt.Rows) //将读取出的数据转换成字符串
        {
            json.AddItem("id", dr["id"].ToString());
            json.AddItem("name", dr["name"].ToString());
            json.AddItem("sex", dr["sex"].ToString());
            json.ItemOk();
        }
        json.totlalCount = dao.GetAllUserCount();
        string jsons = "";
        if (json.totlalCount > 0)
        {
            jsons = json.ToString();
        }
        else
        {
            jsons = @"{success:false}";
        }
        return jsons;
    }
    //删除用户信息
    public int DelUserInfos(int id)
    {
        return dao.DelUserInfos(id);
    }
    //更新用户信息
    public int SaveUserInfo(UserBean user)
    {
        return dao.SaveUserInfo(user);
    }
    //添加用户信息
    public int AddUserInfo(UserBean user)
    {
        return dao.AddUserInfo(user);
    }


}
复制代码

 

后台查询数据部分:
UserData.aspx.cs:
复制代码
     public string ds = string.Empty;
        public UserBLL ub = new UserBLL();

        protected void Page_Load(object sender, EventArgs e)
        {
            string starts = Request.Form["start"];//获取读取数据的范围
            string limits = Request.Form["limit"];
            int index = Convert.ToInt32(starts);
            int length = Convert.ToInt32(limits);
            ds = ub.GetUserInfoPage(index, length);
        }
复制代码

 

增加数据:

AddUserInfo.aspx.cs:

复制代码
UserBLL ub = new UserBLL();
    protected void Page_Load(object sender, EventArgs e)
    {
        AddUserInfos();
    }

    public void AddUserInfos() //添加数据
    {
        //获取姓名性别
        string name = Request.Form["name"];
        string sex = Request.Form["sex"];
        UserBean user = new UserBean();
        user.Name = name;
        user.Sex = sex;
        int count = ub.AddUserInfo(user);
        if (count > 0)
            Response.Write(@"{success:true}");
        else
            Response.Write(@"{success:false}");
    }
复制代码

 

删除数据部分:

DelUserInfo.aspx.cs:

需要注意因为从前台传来的是所有改变行所有数据id的json字符串,需要进行字符串分割获取到要删除行的id

复制代码
UserBLL ub = new UserBLL();  
    protected void Page_Load(object sender, EventArgs e)
    {
        DeleteRoomInfo();
    }
    public void DeleteRoomInfo()
    {
        string id = Request.Form["id"];//获取要删除用户的id字符串
        if (id != null)
        {
            string[] ids = id.Split(',');//将每一名用户的id分割存入字符串数组
            try
            {
                foreach (string r in ids)
                {
                    ub.DelUserInfos(Convert.ToInt32(r));//循环删除用户信息
                }
                Response.Write(@"{success:true}");
            }
            catch (Exception)
            {
                Response.Write(@"{success:false}");
            }

        }
        else
        {
            Response.Write(@"{success:false}");
        }
    }
复制代码

 

修改数据部分:

SaveUserInfo.aspx.cs:

此处需要注意,因为前台传来的是所有改变行所有数据的json字符串,需要进行分割处理,先去掉多余的字符,在根据’,’分割成“数据名:数据值的形式”,再循环根据:进行分割

复制代码
UserBLL ub = new UserBLL();
    protected void Page_Load(object sender, EventArgs e)
    {
        SaveRoomInfo();
    }
    public void SaveRoomInfo()
    {
        int count=0;
        string data = Request.Form["data"];// 对传值过来的json字符串进行处理,分解成键值对

        data = RemoveChars(data,"[");
        data = RemoveChars(data, "]");
        data = RemoveChars(data,"\"");
        data = RemoveChars(data, "}");
        data = RemoveChars(data, "{");
        string[] field = data.Split(',');
        string[] keyvalue;
        for (int i = 0; i < field.Length / 3; i++)
        {
            UserBean user = new UserBean();
            for (int j = i * 3, k = 0; k < 3; j++, k++)
            {
                keyvalue = field[j].Split(':');//将json字符串中的数据项名和数据项值分开,分别存入user对象中
                if (keyvalue[0] == "id")
                {
                    user.Id = Convert.ToInt32(keyvalue[1]);
                }
                else if (keyvalue[0] == "name")
                {
                    user.Name = keyvalue[1];
                }
                else if (keyvalue[0] == "sex")
                {
                    user.Sex = keyvalue[1];
                }
            }
            count = ub.SaveUserInfo(user);
        }
        if (count > 0)
        {
            Response.Write("{success:'true'},{data:" + field[2] + "}");
        }
        else
        {
            Response.Write("{success:'false'},{data:" + field[2] + "}");
        }
            
    }

    //删除字符串中某个字符
    static string RemoveChars(string str, string remove)
    {
        if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(remove))
        {
            throw new ArgumentException("....");
        }

        StringBuilder result = new StringBuilder(str.Length);

        Dictionary<char, bool> dictionary = new Dictionary<char, bool>();

        foreach (char ch in remove)
        {
            dictionary[ch] = true;
        }

        foreach (char ch in str)
        {
            if (!dictionary.ContainsKey(ch))
            {
                result.Append(ch);
            }
        }

        return result.ToString();
    }
复制代码

 

 

 

模型部分:

UserBean.cs:

复制代码
public class UserDao
{
    public UserDao()
    {
        //
        //TODO: 在此处添加构造函数逻辑
        //
    }

    private DataTable dt;
    public DataTable GetAllInfo() 
    {
        try 
        {
            return SqlHelper.ExecuteDataTable("select * from T_user");
        }
        catch(Exception){throw;}
    }
    //分页读取数据
    public DataTable GetUserInfoPaging(int start, int limit) 
    {
        try
        {
            return SqlHelper.ExecuteDataTable("select top " + limit + " * from T_user where id not in(select top " + start + " id from T_user order by id asc) order by id asc");
        }
        catch (Exception) { throw; }
    }

    public UserBean getUserInfosNoState(int id) 
    {
        try
        {
            dt = SqlHelper.ExecuteDataTable("select * from T_user where id=" + id);
            UserBean user = new UserBean();
            user.Id = int.Parse(dt.Rows[0]["id"].ToString());
            user.Name = dt.Rows[0]["name"].ToString();
            user.Sex = dt.Rows[0]["sex"].ToString();
            return user;
        }
        catch (Exception){ throw; }
    }

    //获得用户总数
    public int GetAllUserCount() 
    {
        try
        {
            return (int)SqlHelper.ExecuteScalar("select count(*) from T_user");
        }
        catch (Exception) { throw; }
    }

    //删除用户信息
    public int DelUserInfos(int id)
    {
        try
        {
            int count = SqlHelper.ExecuteNonQuery("delete T_user where id=" + id);
            return count;
        }
        catch (Exception){ throw; }
    }

    //保存修改
    public int SaveUserInfo(UserBean user)
    {
        try
        {
            int count = SqlHelper.ExecuteNonQuery("update T_user set name='" + user.Name + "',sex='" + user.Sex + "' where id=" + user.Id);
            return count;
        }
        catch (Exception){ throw;}
    }

    //新增用户
    public int AddUserInfo(UserBean user)
    {
        try
        {
            int count = SqlHelper.ExecuteNonQuery("insert into T_user values('" + user.Name + "','" + user.Sex + "')");
            return count;
        }
        catch (Exception)
        { throw; }
    }
}
复制代码

 

UserBean.cs

复制代码
public class UserBean
{
    public UserBean()
    {
        //
        //TODO: 在此处添加构造函数逻辑
        //
    }

    private long id;
    private string sex;
    private string name;

    public long Id
    {
        get { return id; }
        set { id = value; }
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public string Sex
    {
        get { return sex; }
        set { sex = value; }
    }
}
复制代码
潮平帆远,击水三千

钓鱼的比捞鱼的还累

mikel阅读(900)

钓鱼的需要饵儿,捞鱼的只需要把握机会,所以大家都纷纷想提起抄子要捞鱼!

人的本性是不想投入只想着省事儿直接的做,钓鱼的往往都是有远见的,不惜那几个鱼饵,但是收获不见得比捞鱼的少,就是需要蹲点儿,候着。

捞鱼的喜欢乱窜,这里看着那里的鱼多,于是疲于奔命的跑过去,结果鱼又吓跑了,可是如果正好赶上一大群鱼的时候,那就是赶上了,也要看捞鱼的手法,手快手慢都是有讲究的,这个和钓鱼相比就没那么高难度了,所以说捞鱼的人多,钓鱼的人少。

再说说另一种钓鱼后养鱼的,这种人是做大事儿的人,知道鱼吃了也就是剩一堆骨头了,这些人懂得养鱼,然后养肥了还能卖钱或者保证自己的日常生活,比较靠谱,不再像钓鱼的和捞鱼的饥一顿饱一顿了,毕竟有了自己的鱼塘。

鱼还是鱼,可面对不同人的手法,一下子差距就出来了,你是哪种人呢?!

c# winform最小化到托盘,也就是最小化到右下角,其实很简单,winform中有专门的控件 - 要每天开心 - 博客园

mikel阅读(749)

来源: c# winform最小化到托盘,也就是最小化到右下角,其实很简单,winform中有专门的控件 – 要每天开心 – 博客园

C# winform最小化到托盘,也就是最小化到右下角,其实很简单,winform中有专门的控件。
2010年04月12日 星期一 15:23

C# winform最小化到托盘,也就是最小化到右下角,其实很简单,winform中有专门的控件。

下面是完整的代码,没太多需要解释的。

先在winform中添加notifyIcon控件,然后激活相应事件,添加代码。因为屏蔽了关闭窗体事件,可以设定一个全局变量bool变量来分辨是哪里引起的close事件。

//设置全局变量bool closeTag = true;

//最小化
private void form1_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
NormalToMinimized();
}
}
//close时最小化
private void form1_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
NormalToMinimized();
}
//单击显示窗体
private void notifyIcon_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
this.MinimizedToNormal();
}
}
private void MinimizedToNormal()
{
this.Visible = true;
this.WindowState = FormWindowState.Normal;
notifyIcon.Visible = false;

}
private void NormalToMinimized()
{
this.WindowState = FormWindowState.Minimized;
this.Visible = false;
this.notifyIcon.Visible = true;
}
private void frmMain_FormClosing(object sender, FormClosingEventHandler e)
{
if (closeTag)
{
e.Cancel = true;
NormalToMinimized();
}
}
//托盘右键关闭事件
private void 退出XToolStripMenuItem_Click(object sender, EventArgs e)
{
closeTag = false;
Application.Exit();
}

网络营销不是快速致富的捷径

mikel阅读(810)

随着互联网的普及,再也没有比网络上创业成本低的行业了,注册域名30多元+200元的空间,然后加上免费的cms系统,这就可以建立起一个自己的网站,而且还有模有样儿,300元的创业成本,就是卖个煎饼都要比这高得多,于是乎人们纷纷来到网上创业,似乎一下子成了风口,都在等风来,想要一飞冲天一夜暴富。

网络营销不是快速致富的捷径,为什么这么说,这是一个需要更多技能的行业,不仅仅是建站技术、同样各种网络营销手法、产品定位、策划能力、文字写作等等一系列的环节都需要你自己去学习和掌握,想要像媒体中报道的那百万分之一的创业成功的事迹一夜暴富,那都是一将功成万骨枯的结果,其实也不必那么悲壮,每天新建的网站有几百万都是少的,每天倒闭的网站也基本是这个数字,所以说没有什么捷径,有得只是站长们辛辛苦苦更新文章和内容,呕心沥血想着法儿的营销推广的脑力劳动。

各种媒体都是报喜不报忧的,所以刺激得人们都近乎疯狂的投资互联网,来淘金的人多了,是好事,可如果过分夸大了网络创业的盈利,而绝口不提风险,是不是有点儿愚弄百姓的嫌疑,尽管传统媒体一贯如此,但是随着社会化媒体的出现,是不是该改改这种只看一面儿的把戏?

从舆论到最近风起云涌的微商再到各种“剩者为王”的上市,让更多的人发现互联网是个看上去很美,跳进来很热,做起来很纠结的地方,所以说网络不是神话制造地,而是竞争残酷的战场。

MVC在IIS7.5发布后报错403.14问题解决

mikel阅读(856)

自己在服务器上部署基本没遇到问题,但是将发布后的程序发给别人,部署就出现问题。程序是MVC4,framework4.5。

参照别人的思路解决:

1、

在web.config增加<modules runAllManagedModulesForAllRequests=”true” />

如:问题依然不能解决。报错貌似是指webConfig不识别。

  1. <system.webServer>
  2.    <validationvalidateIntegratedModeConfigurationvalidationvalidateIntegratedModeConfiguration=“false”/>
  3.    <modules runAllManagedModulesForAllRequests=“true” />
  4. </system.webServer>

2、

如果还不行,请检查一下“处理程序映射”,里面是否有“ExtensionlessUrlHandler-Integrated-4.0”,如果没有,请注册.net4.0

在运行里输入:C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i

经检查发现存在“ExtensionlessUrlHandler-Integrated-4.0”,不管再重新注册一下吧。依然不能解决。

经过各方面的检查,发现服务器上安装的.netframework 不是4.5,而是4.5.1。

果断将4.5.1卸载,重新安装dotnetfx45_full_x86_x64.exe。重新注册iis,32位的和64位的都注册一下。

还是不行,再次参考第一步,竟然出现不一样的提示了。提示缺少一些dll文件。

找到相关dll放到bin文件夹下,问题解决。

参考:

http://www.cnblogs.com/yuejin/archive/2013/01/19/2867428.html

http://solin.cn.blog.163.com/blog/static/536402372013410115631805/

win7 64位 java.sql.SQLException: [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 问题 - popo_9872的专栏 - 博客频道 - CSDN.NET

mikel阅读(753)

来源: win7 64位 java.sql.SQLException: [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 问题 – popo_9872的专栏 – 博客频道 – CSDN.NET

 

64位操作系统不支持Microsoft OLE DB Provider for Jet驱动程序,也不支持更早的Microsoft Access Driver (*.mdb)方式连接。所以,程序里面的链接字符串不能正常工作。需要修改下IIS的工作环境,改成32位的,在IIS的管理界面中,把应用程序池中的默认应用程序池常规选项中的32位方式启用就可以了。

 

Win7 下Access的 ODBC连接

直接在

控制面板—管理工具—-数据源(ODBC)

打开数据源配置,发现只有SQLServer的驱动,其他的都没有了。

 

解决方法是:

C:/Windows/SysWOW64

在这个目录下找到:

odbcad32.exe

这个文件,双击打开。

里面有很多可用个数据源驱动,然后就可用添加连接Access的ODBC的数据源了。

 

 

 

java学到Access数据库那里用ODBC时提示:”java.SQL.SQLException: [Microsoft][ODBC 驱动程序管理器] 在指定的 DSN 中,驱动程序和应用程序之间的体系结构不匹配”,现在终于弄好了,把过程跟大家分享一下。
Win7 64控制面板里面直接打开ODBC就只有MySQL,添加Access会出错:“[Microsoft][ODBC 驱动程序管理器] 在指定的 DSN 中,驱动程序和应用程序之间的体系结构不匹配”,到网上查了一下,直接运行C:/Windows/SysWOW64/odbcad32.exe 就是32位ODBC,这个里面倒是能添加Access数据库了。然后用我的64位Java编译,运行后还是有错:“指定之DNS中的驱动程式和应用程式架 构不相符”,终于明白64位Java要对应64位ODBC,于是安装了Microsoft Office 2010 64bit 这回使用控制面板里面的64位ODBC终于能加Access数据库了,Java编译运行也正常了。此时我在使用目录里面的32位ODBC反而不行了(之前 的32位Office卸载了)提示:“找不到Microsoft Access Driver (*.mdb,*.accdb)ODBC驱动程序的安装例程,请重新安装驱动程序”。据我分析如果安装32位Java则用系统目录里的32位Odbc也是 可以的,所以软件的构架要相对应。

从微盟融资看微信第三方发展:转型、被收购和寄生于微信之下

mikel阅读(810)

原文《从微盟融资看微信第三方发展:转型、被收购和寄生于微信之下》,原文摘要:

4月21日据深交所公告,微信第三方服务商微盟完成1.5亿元B轮融资,本轮融资由金字火腿领投,华映资本跟投。微盟为何发展如此迅速,微信第三方未来的前景如何,围绕微信之下的第三方未来会如何发展,笔者做了一些简单分析。

据笔者了解,微盟成立仅两年的时间, 主要两大核心业务为:一是在2014年推出的为传统零售企业布局微商分销体系的SDP;二是今年3月推出的V店App。纵观这两大产品,前者主要主要依托 微信,在微信提供的接口上做一些开发,后者主要发力微商。其创始人孙涛勇表示,B轮融资将重点搭建三大体系:供货商商品质量控制体系、V店主信用体系和消 费者保障体系。

在笔者看来,微盟的成功主要得益于“两股势”。一是“顺势而为”。2013年是微信发展最迅速的一年,也是微信电商元年。各种微商城,微官网,微 信支付,服务号开店等狼烟四起,借助微信的创业风潮风靡全国。一个叫“微信营销”的词汇开始被吵的热火朝天,微盟应运而生,顺势而为,在微信电商的窗口 期,寻找到了风口,于是飞了起来。

二是“乘势而上”。当微信开始减缓在商业化上的步伐后,一种叫做微商的“面膜党”开始在朋友圈里“兴风作浪”, 微盟想通过“V店”来重新规范“微商”市场。虽然很多人不看好微商,但却有越来越多的让开始谈论微商,为微商乱象寻找新的出路。

1、进入剩者为王的时代,要么转型要么被收购。微信营销刚刚进入公众视野的时候第三方市场之战不亚于2011年的“千团大战”,最火的时候超过 2000家,当时各种低价贱卖,免费使用的营销恶战不绝入耳,免费即意味着不对结果负责。当时为了抢占市场第三方用一个词语形容就是无所不用其极。只有一 些靠口碑和保障售后服务的公司活了下来。

今天,很多第三方开始撑不下去了,做得稍好一些的被并购了,做得差一点的被迫转型了。随着微信商业化战略的转移,第三方基本上进入了一个大浪淘沙,恒者恒强的局面。

2、寄生于微信之下,成为微信生态的探路者。今年3月份,微信发文称,不会既做裁判员又做运动员。微信的角色是更好地完成用户端的业务,并把商 业化的接口能力做的更灵活完善,至于”怎么连接“,这一块的市场空间交给更了解行业的第三方来做,微信平台上有大量的空间、大量的机会,等待第三方去挖掘 和开发,而且微信上大量的商家,也非常迫切地需要第三方给他们带来支持。微信提供连接能力,市场空间交由第三方来做。

正因为意识到了自己的“有所为,有所不为”,明白自己的角色定位和“开放+包容”的胸襟,微信才建立成了”连接一切”,“无所不能”的生态。而 第三方将成为微信生态不可或缺的一部分, 在“微信+智慧城市”,“微信+生活”的战略下,第三方的未来必将成为微信的马前卒,为微信在更深,更广的领域做连接和服务。

  未来微信第三方发展的两大方向:移动电商和O2O

这里说的移动电商更多的是微商,尽管PC时代的电商被淘宝、京东分割,但是还是冲出了聚美优品和唯品会,微信再强大也阻挡不了微博和陌陌的上 市。笔者认为移动电商的到来一定会冲破更多的藩篱,如果微商走上正轨,这将是一种无可匹敌的全新的商业形态。虽然微信引进了京东和美丽说,为他们提供了优 质入口,但是从效果上来看,还不太理想。

相比于电商,O2O的市场会更大,微信通过入股大众点评,58同城来布局生活服务市场。随着智能POS机,智能Wi-Fi,周边摇一摇,iBeacon等技术接口的开放,微信希望更多的第三方参与到这一生态建设中来。

在O2O上,微信更是千方百计想巩固自己的电商地位,否则腾讯也不会不计成本地大肆收购。在不久前结束的“两会”上,李克强总理更把“互联 网+”提到整个国家战略层面,而“微信+O2O”是践行“互联网+”的最佳落地方式,对于第三方来说,这无疑是未来发展的最可靠的方向。

  微商成为微信第三方创业新方向

随着媒体对微商行骗的揭露和央视对面膜微商造假的大肆报道,越来越多的微商开始退出朋友圈。开始选择正规的开店工具的微商们从幕后走到了台前。 靠信任和影响力支撑的个人微商走向没落。伴随国美,苏宁等线下门店开始进驻平台微商。一种由平台做保障,知名企业背书的新型商业形态有望矫正目前的微商病 态。而这种“低成本,高市场”的“拨乱反正”行为也恰好是第三方的另一种创业方向。(本文首发钛媒体)

观点:

微信生态圈中不可或缺的第三方平台的“剩者为王”的时代已经在微商火爆的大潮落幕中来了,微盟作为被招安的第三方平台融资上基本不用愁,毕竟人家抱了微信这棵大树,可是那些继续挣扎的第三方平台就岌岌可危了,毕竟面临得是微信的严管,同样是同行间的恶意竞争,不得不被架着转型,可似乎如何转型都是红海,毕竟大家都在海里面,往那边儿走都是海,要是想跳出来那需要的却是勇气,意味着现有市场的丧失。

微信一再表明中立的态度,可是谁又会敢动腾讯的蛋糕?

网络转载版权规范下的网媒抄袭之风可止?

mikel阅读(958)

《关于规范网络转载版权秩序的通知》的内容:

  我们该如何解读《通知》?

  首先,并非所有转载都要付费

通知第一条规定:

“一、互联网媒体转载他人作品,应当遵守著作权法律法规的相关规定,必须经过著作权人许可并支付报酬,并应当指明作者姓名、作品名称及作品来源。法律、法规另有规定的除外。”

 第一条被认为是转载就要收费的根据,但第七条有如此描述:

“七、报刊单位和互联网媒体应当建立健全本单位版权管理制度。建立本单位及本单位职工享有著作权的作品信息库,载明作品权属信息,对许可他人使 用的作品应载明授权方式、授权期限等相关信息。建立经许可使用的他人作品信息库,载明权利来源、授权方式、授权期限等相关信息。”

这一条对传统媒体和网媒有一个限定,就是授权方式双方可以协调,这就给予免费转载的可能性的空间。而且《通知》第四条的条文,“单纯事实消息”是可以转载的,也就是说付费所限定的新闻范围是传统媒体带有创作性质的内容,对于单纯的事实消息类是不限定的。

 其次,传统媒体话语权得到强化

虽然《通知》中给予了传统媒体自由授权的权力,但同时对版权转载价值的伸张,在一定程度上加强了传统媒体的话语权,因为传统媒体终于可以挺直腰 杆说不。当然在之前的网络上,传统媒体同样有权力禁止其他网媒的转载,只不过这一次有了《通知》的下发,这种话语权得到了强化。

 最后,《通知》并未限制传统媒体间相互转载的权力

  《通知》的第二条:

“二、报刊单位之间相互转载已经刊登的作品,适用《著作权法》第三十三条第二款的规定,即作品刊登后,除著作权人声明不得转载、摘编的外,其他报刊可以转载或者作为文摘、资料刊登,但应当按照规定向著作权人支付报酬。”

可见这次的版权《通知》针对的是网媒,而不是传统媒体相互间的转载。

  《通知》的出台会对中国网媒,或者说媒体生态产生哪些影响?

任何一个政策的推进都是有影响的,对网络转载磨刀霍霍的《通知》必定对中国网媒甚至可以说中国媒体生态产生影响。

  第一,为优质内容垄断提供了可能性

首先,网络转载付费的版权进步,首先是为优质内容的垄断提供了可能性,比如美国默多克对谷歌的屏蔽,就是建立在这个版权保护制度之上的一次反 击。我们知道在默多克对谷歌屏蔽的时间里,2009年11月,微软开始与新闻集团进行商谈,为了扶持必应(bing),微软向新闻集团尝试付费。这一幕会 不会发生在中国?至少理论上已经存在这个可能性。

 第二,对中国媒体内容传播灵活性产生限制

  我们再来看看《通知》第三条:

“三、互联网媒体转载他人作品,不得对作品内容进行实质性修改;对标题和内容做文字性修改和删节的,不得歪曲篡改标题和作品的原意。”

这一条比较特殊,这不是钱可以搞定的事情,而且从这一条中,也未说明如果网媒经过付费或者其他条件获得传统媒体授权,能否修改转载后的内容?传 统媒体有没有这个授权网媒修改的权力?不得而知,至少这对于大部分灵活度很高的网媒是一种限制,也对中国媒体内容的灵活性产生了限制。

 第三,小新闻网站迎来灭顶之灾

大的网络媒体,可以通过流量置换、少量的金钱补贴或其他利益措施,和传统媒体达成少收费或免费的转载合作。但对于少流量、缺资金的小新闻网站来 说,这一举动无疑是灭顶之灾,如今的小新闻网站依靠着在其他大网络新闻平台或报纸上拷贝新闻来补充内容,缺乏和传统媒体谈判筹码的它们,是这个《通知》下 最容易灰飞烟灭的炮灰。

  《通知》会给中国媒体生态带来什么后果呢?

第一个,这对于传统媒体来说是一件好事情,一个大型的网络新闻综合网站可能只需要二百人就够了,但一个携多个子品牌的单独的报纸、期刊、广播电 台、电视台等媒体,为了产出优质内容,各种人员相加就需要二百人甚至三百人的规模,以新晋的很有名气的由上海东方报业有限公司打造的《澎湃新闻》为例,仅 仅这个品牌报纸旗下的职工人数就是几百人,它的招聘职工规模如此写道150-500人。为了维持优质内容,传统媒体的承担了太高的成本,对版权的保护,话 语权的增加,至少为它们带来了可以议价的可能性。

第二个,新闻资源战或将再起。虽然《通知》中对宝贵的“单纯事实消息”没有禁止转载的限制,但在传统媒体中大量的优质内容一直是稀缺资源,高昂 的人力成本并非一无是处,在深度、广度甚至内容的精致性上,传统媒体产出的优质内容,如果可以垄断起来,就可以产生意想不到的价值收益。《通知》的下发会 不会产生中国版的“默多克”,恐怕在资本的诱惑下,并非没有可能。

第三个,自媒体或将迎来新一轮成长。这一条对于从事自媒体的自媒体人来说是件好事,对内容的保护,收益的内容生产者,而受限的是平台。所以说,对于产生优质内容并且不会产生纠纷的自媒体,未来对网媒的吸引力会增加,而这种改变势必会激励自媒体新一轮成长。

观点:

看来从国家召集新浪等各大媒体开了个新闻内容整治后,似乎对网络媒体更加的严厉下,这则规范,让网络上的版权问题再次摆上台面,也被人们所注意,似乎大家一直都知道这个问题,但是大家都没怎么注意,反正都是你转载我家的,我转载你家的,也没啥版权之说,觉得这些文章啥的都是网上的转转也涉及不到啥版权问题,可是很多有版权认识的作者总是在文章的显眼儿位置写上转载请征求作者同意,可又有多少人会注意。

转来转去倒成了理所当然,可网上的文章到底版权如何界定规范中也只是提到可以征求作者授权或者明确写清不得任意转载和不经授权转载都受版权保护。

那对于这则规范,做自媒体的人们是不是也应该通过这则规范更加注意到版本保护问题,毕竟大家辛苦写的文章不少人随手转载出去后,多少人喜欢别人转载自己的文章,以增加影响力,可是自己的版权保护意识还是要有。

[转载]安装Sql server 2008遇到无法安装.net 3.5的问题解决办法 - 木油 - 博客园

mikel阅读(783)

[转载]安装Sql server 2008遇到无法安装.net 3.5的问题解决办法 – 木油 – 博客园.

已经安装.net 3.5,安装SQL server 2008,总提示没有安装.net 3.5的解决办法,

 

手工下载 .net 3.5的安装程序copy到SQL server安装目录下的以下位置(根据操作系统选择对应的位置).
ia64 redist\DotNetFrameworks\dotNetFx35setup.exe
x86 redist\DotNetFrameworks\dotNetFx35setup.exe
x64 redist\DotNetFrameworks\dotNetFx35setup.exe.

WindowsInstaller 4.5的安装失败.
SQL Server 2008安装程序要求成功安装Windows Installer 4.5
顺着上一个问题的思路把Windows Installer 4.5的安装程序下载并改名放到安装目录下的对应位置.
x86 edist\Windows Installer\x86\INSTMSI45.msu(因为我安装的32位系统,所以我就改了这一个文件)
SQL server终于可以正常安装了

这面的办法是网上找的
但是我发现,我的情况有点不太一样,ia64和x64下面都有redist目录,就x86下面没有
于是我直接把x64下的redist直接拷到x86下,OK了

[转载]采集58信息的一些总结 - 糯米粥 - 博客园

mikel阅读(1141)

[转载]采集58信息的一些总结 – 糯米粥 – 博客园.

一个朋友问我能不能帮他做个小程序。抓取58上面包含”维修”的数据,比如公司名称,电话号码等等

 

打开58,收索”维修”

 

 

 

单击 房屋维修,进入一个列表页面,

 

随便单击一个,进入详细页面

 

需要请求58服务器3次。然后匹配html元素获取自己需要的信息,数据匹配自然少不了正则表达式,用过的都知道,

对于我来说,写正则表达式是非常头疼的事情,所以可以选择第三方库:比如HtmlAgilityPack,Jumony等等,我这里选择的是Jumony

博客园有对Jumony入门的文章: http://www.cnblogs.com/Ivony/archive/2010/12/19/jumony-guide-1.html

 

jumony直接安装在项目中:

首先:选择需要添加的项目,单击引用,然后选择管理NuGet程序包,在必要的情况下,需要升级NuGet

   

其次:收索Jumony安装即可

 

 

 

 

先看看我实现的效果图:因为公司比较忙,只能晚上回家写写,问题也是非常多。所有先记录这两天实现的效果

 

 

 

************************效果图结束*****************************

 

 

 

主窗体:左侧显示的是在首页匹配后的关键字。然后通过多线程月抓取每个列表页面的信息。

 

看看我主窗体的布局

 

显示数据的DatatGridView是动态创建的。

来看看核心代码:模拟请求58服务器,就要去观察58的请求与响应,可以通过Fiddler2和Firebug抓包观察

 

 

 

我根据我项目的需求封装了一个HttpWebHelper类,

 

复制代码
  1  /// <summary>
  2     /// 封装Http类
  3     /// </summary>
  4     class HttpWebHelper
  5     {
  6         /// <summary>
  7         /// 显示验证码页面容器
  8         /// </summary>
  9         public static WebBrowser webBrowser { get; set; }
 10 
 11         /// <summary>
 12         /// 验证码需要的唯一id
 13         /// </summary>
 14         public static string uuid { get; set; }
 15 
 16         /// <summary>
 17         /// 验证码是否通过
 18         /// </summary>
 19         public static bool isPass { get; set; }
 20 
 21         /// <summary>
 22         /// 首页关键字对应的url
 23         /// </summary>
 24         public static Dictionary<string, string> list;
 25 
 26         /// <summary>
 27         /// 抓取页面前缀
 28         /// </summary>
 29         public static string prefix;
 30         /// <summary>
 31         /// 显示验证码页面
 32         /// </summary>
 33         public string codeUrl { get; set; }
 34         /// <summary>
 35         /// 抓取首页
 36         /// </summary>
 37         public string dataUrl { get; set; }
 38         /// <summary>
 39         /// 验证码提交页面
 40         /// </summary>
 41         public static string verCode { get; set; }
 42 
 43         /// <summary>
 44         /// 页面请求方式
 45         /// </summary>
 46         public string Method { get; set; }
 47         /// <summary>
 48         /// RefererHTTP 表头值
 49         /// </summary>
 50         public string Referer { get; set; }
 51         /// <summary>
 52         /// 主机
 53         /// </summary>
 54         public string Host { get; set; }
 55         /// <summary>
 56         /// cookie
 57         /// </summary>
 58         public CookieContainer cookie { get; set; }
 59         /// <summary>
 60         /// 
 61         /// </summary>
 62         public string Accept { get; set; }
 63         public string UserAgent { get; set; }
 64         public string ContentType { get; set; }
 65         public string Accept_Language { get; set; }
 66         public Encoding encoding { get; set; }
 67 
 68         public Image PictureBox { get; set; }
 69 
 70 
 71 
 72 
 73         public HttpWebHelper()
 74         {
 75             this.codeUrl = "http://support.58.com/firewall/valid/3071088800.do";
 76             //this.verCode = "http://support.58.com/firewall/valid/3071088800.do";
 77 
 78             Method = "post";
 79             Referer = "http://support.58.com/firewall/valid/3071088800.do";
 80             Host = "support.58.com";
 81             Accept_Language = "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3";
 82             Accept = "*/*";
 83             ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
 84             UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0";
 85             encoding = Encoding.UTF8;
 86 
 87 
 88         }
 89 
 90         public HttpWebHelper(WebBrowser webBrowser, string uuid, string dataUrl)
 91         {
 92             //this.codeUrl = "http://support.58.com/firewall/valid/3071088800.do";
 93             //HttpWebHelper.webBrowser = webBrowser;
 94             //this.uuid = uuid;
 95             this.dataUrl = dataUrl;
 96         }
 97 
 98         /// <summary>
 99         /// 验证 验证码,验证码和页面生成的一个id值同时post到服务器
100         /// </summary>
101         /// <param name="code">验证码</param>
102         public void postVerCode(string code, string uuid)
103         {
104             try
105             {
106                 //HtmlElement d = webBrowser.Document.GetElementById("uuid");
107 
108                 //获取页面uid。
109                 /*
110                  * 验证方式:验证码和页面生成的一个id值
111                  */
112                 //string y = webBrowser.Document.GetElementById("uuid").GetAttribute("value");
113 
114                 // string postUrl = "http://support.58.com/firewall/valid/3071088800.do";
115                 HttpWebHelper h = new HttpWebHelper();
116 
117                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(verCode);
118                 request.Method = Method;
119                 request.Referer = Referer;
120                 request.Headers.Add("X-Requested-With", "XMLHttpRequest");
121                 request.Host = Host;
122                 CookieContainer cookie = new CookieContainer();
123                 request.CookieContainer = cookie;
124                 request.Accept = Accept;
125                 request.ContentType = ContentType;
126                 request.Headers.Add("Accept-Language", Accept_Language);
127                 request.UserAgent = UserAgent;
128                 string parameter = string.Format("inputcode={0}&namespace=infodetailweb&uuid={1}", HttpUtility.UrlEncode(code), uuid);
129 
130                 byte[] buffer = Encoding.Default.GetBytes(parameter);
131 
132                 string result = string.Empty;
133                 Stream reqStr = request.GetRequestStream();
134                 reqStr.Write(buffer, 0, buffer.Length);
135                 using (HttpWebResponse response1 = (HttpWebResponse)request.GetResponse())
136                 {
137 
138                     using (StreamReader reader = new StreamReader(response1.GetResponseStream(), encoding))
139                     {
140                         result = reader.ReadToEnd().Trim();
141                     }
142                 }
143                 HttpWebHelper.isPass = (result == "1" ? true : false);
144             }
145             catch (Exception ex)
146             {
147                 MessageBox.Show(ex.StackTrace);
148             }
149         }
150 
151         /// <summary>
152         /// WebClient简单下载页面
153         /// </summary>
154         /// <param name="url">下载html的页面</param>
155         /// <returns></returns>
156         public string webClient(string url)
157         {
158             string html = string.Empty;
159             try
160             {
161                 //WebClient client = new WebClient();
162                 //client.Encoding = encoding;
163                 //string html = client.DownloadString(url);
164                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
165 
166                 request.Method = "get";
167                 //request.Timeout = 300;
168                 using (HttpWebResponse response1 = (HttpWebResponse)request.GetResponse())
169                 {
170                     using (StreamReader reader = new StreamReader(response1.GetResponseStream(), encoding))
171                     {
172                         html = reader.ReadToEnd().Trim();
173                     }
174                 }
175             }
176             catch (Exception ex)
177             {
178                 MessageBox.Show(ex.StackTrace);
179             }
180             return html;
181         }
182     }
复制代码

 

 

模拟请求类有了。接下来就是在返回的htlm中抓取关键字,这里是匹配包含 “维修” 的a 标签

我封装了一个方法,根据url和关键字抓取数据后。直接给窗体的控件listBoxMenu绑定数据

 

复制代码
 /// <summary>
        /// 
        /// </summary>
        /// <param name="url">首页抓取</param>
        /// <param name="keyword">首页关键字</param>
        private void ProcessDownload(string url, string keyword)
        {
            this.Invoke(
                         new Action(() => { richTextBoxInfo.AppendText(url + "开始下载中......\n"); })
                        );

            //抓取关键字对应的url
            WebClient client = new WebClient();
            string html = client.DownloadString(url);
            IHtmlDocument document = new JumonyParser().Parse(html);
            IEnumerable<IHtmlElement> result = document.Find("a").Where(t => t.InnerText().Contains(keyword));

            Dictionary<string, string> dir = new Dictionary<string, string>();
            foreach (var item in result)
            {
                var href = item.Attribute("href").Value();
                var text = item.InnerText();
                if (!dir.ContainsKey(href)) dir.Add(text, href);
            }

            //左边菜单栏赋值
            this.Invoke(new Action(() =>
            {
                foreach (var item in dir)
                {
                    listBoxMenu.Items.Add(item.Key);
                }
            }));

            //共享数据
            HttpWebHelper.list = dir;
            HttpWebHelper.prefix = url;

            //开启多线程下载。

            //foreach (var item in dir)
            //{
            //    Thread thread = new Thread(() => { DownloadHtml(item.Key); });
            //    thread.Name = item.Key; //线程取名字
            //}

            try
            {
                foreach (var item in dir)
                {
                    //ThreadPool.QueueUserWorkItem(new WaitCallback(DownloadHtml), item.Key);

                    Thread thread = new Thread(ThreadDownload);
                    thread.Name = item.Key;
                    thread.Start(item.Key + "," + item.Value);

                }
            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.StackTrace);
            }
        }
复制代码

 

这个void ProcessDownload(string url, string keyword)有几点注意。这个方法是异步调用的。所以在这里给窗体的控件赋值,就属于跨线程操作UI,因为UI是在主线程中创建和绘制的

有关跨线程问题可以看此篇博文:http://www.cnblogs.com/nsky/p/4436309.html

可以看到里面是有用到线程池的 :ThreadPool,后来被我注释了。因为我需要给线程命名。但线程池我没找到此方法。是不是没有呢?

在ProcessDownload方法里面。当首页关键字匹配后,根据匹配的个数,开启多线程执行详细页面抓取,首页的关键字我保存在了字典里面

Dictionary<string, string> dir = new Dictionary<string, string>(); 分别用关键字和关键字对应的url来存取key-value。在HttpWebHelper类中。我也定义了static

复制代码
try
            {
                foreach (var item in dir)
                {
                    //ThreadPool.QueueUserWorkItem(new WaitCallback(DownloadHtml), item.Key);

                    Thread thread = new Thread(ThreadDownload);
                    thread.Name = item.Key;
                    thread.Start(item.Key + "," + item.Value);

                }
            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.StackTrace);
            }
复制代码

这里把key-value传值给ThreadDownload。

了解多线程可以看博文:http://www.cnblogs.com/nsky/p/4425286.html

 

 

首页抓取关键字的方法有了。那还缺一个什么方法?还需要一个抓取显示列表的页面,这里取名为:ThreadDownload方法

复制代码
  1 /// <summary>
  2         /// 
  3         /// </summary>
  4         /// <param name="title">当前抓取的关键字</param>
  5         private void ThreadDownload(object obj)
  6         {
  7             //因为58有采集频率限制。所以改成同步
  8             Monitor.Enter(this);
  9 
 10             string[] ob = obj.ToString().Split(',');
 11             this.Invoke(
 12                          new Action(() => { richTextBoxInfo.AppendText(string.Format("正在抓取:{0}\n", ob[0])); })
 13                      );
 14             Dictionary<string, string> list = HttpWebHelper.list;
 15             string prefix = HttpWebHelper.prefix;
 16 
 17 
 18             HttpWebHelper client = new HttpWebHelper();
 19             client.encoding = Encoding.UTF8;
 20             //client.webClient(prefix);
 21 
 22 
 23             DataTable dt = new DataTable();
 24             dt.Columns.Add("公司名字", typeof(string));
 25             dt.Columns.Add("联系人", typeof(string));
 26             dt.Columns.Add("联系电话", typeof(string));
 27 
 28             //遍历每个信息对象的url 如:家庭维修==》 www.baidu.com
 29             //foreach (var item in list)
 30             //{
 31             //获取列表
 32             string fullurl = string.Format("{0}{1}", prefix, ob[1]);
 33             string html = client.webClient(fullurl);
 34 
 35             IHtmlDocument document = new JumonyParser().Parse(html);
 36             IEnumerable<IHtmlElement> result = document.Find("table[id=jingzhun]");
 37 
 38             var items = result.Find("tr");
 39 
 40             foreach (var o in items)
 41             {
 42                 if (o.Find("a").Count() > 0)
 43                 {
 44                     /*
 45                      * 执行该url的时候。服务器判断了请求的频繁度,需要输入验证码。
 46                      * 输入验证码成功后。会执行该url  即下面的referer
 47                      */
 48                     //列表中找到a标签转到详细页面
 49                     string referer = o.FindFirst("a").Attribute("href").Value();
 50 
 51 
 52                     //http://support.58.com/firewall/valid/1032910901.do?namespace=infodetailweb&url=http://sz.58.com/qichejx/19720429696131x.shtml
 53 
 54                     //等待5秒,防止抓取频率过高 时间根据当前的环境来定
 55                     Thread.Sleep(5000);
 56 
 57 
 58 
 59                     string n = Thread.CurrentThread.Name;
 60                     string i = Thread.CurrentThread.ManagedThreadId.ToString();
 61 
 62                     //抓取详细页面。这里如果过于频繁,会跳到输入验证码页面
 63                     string sonHtml = client.webClient(referer);
 64 
 65                     //Monitor.Enter(this);
 66 
 67                     if (sonHtml.Contains("验证码"))
 68                     {
 69 
 70                         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(referer);
 71                         request.Method = "get";
 72                         string responseUrl = string.Empty;
 73                         string rediect = string.Empty;
 74                         using (HttpWebResponse response1 = (HttpWebResponse)request.GetResponse())
 75                         {
 76                             //"http://support.58.com/firewall/valid/1903444021.do?namespace=infodetailweb&url=http://sz.58.com/shoujiweixiu/21147587557513x.shtml"
 77                             responseUrl = response1.ResponseUri.ToString();
 78 
 79                             //获取绝对路径 "/firewall/valid/1032910901.do"
 80                             string absolutePath = response1.ResponseUri.AbsolutePath;
 81 
 82                             //ResponseUri.Authority  "support.58.com"
 83                             HttpWebHelper.verCode = "http://" + response1.ResponseUri.Authority + absolutePath;
 84 
 85                             //获取?后面的字符串
 86                             string query = response1.ResponseUri.Query;
 87 
 88                             //验证码成功后,重定向的url
 89                             rediect = query.Substring(query.LastIndexOf("=") + 1);
 90                         }
 91                         //response1.ResponseUri.GetComponents(UriComponents.Query, UriFormat.UriEscaped);
 92                         //HttpWebHelper http = new HttpWebHelper();
 93                         //HttpWebHelper.webBrowser = new WebBrowser();
 94                         //HttpWebHelper.webBrowser.Url = new Uri(http.codeUrl);
 95 
 96                         //http.webBrowser.Navigate(http.codeUrl);
 97                         //HttpWebHelper.webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted);
 98                         //HttpWebHelper.webBrowser.NewWindow += new CancelEventHandler(webBrowser_NewWindow);
 99                         //http://blog.csdn.net/jinjazz/article/details/1916883
100                         //while (waitHandle.WaitOne(10, false) == false) { Application.DoEvents(); }
101 
102                         //Thread thread = new Thread(() =>
103                         //{
104                         //    showCode code = new showCode();
105                         //    code.codeHandler = new HttpWebHelper().postVerCode;
106                         //    //code.p = h.PictureBox;
107                         //    if (code.ShowDialog() == DialogResult.OK)
108                         //    {
109                         //        code.Hide();
110                         //    }
111                         //});
112 
113                         this.Invoke(new Action(() =>
114                         {
115 
116                             showCode code = new showCode();
117                             code.codeHandler = new HttpWebHelper().postVerCode;
118                             code.showCodeUrl = responseUrl;
119                             //code.p = h.PictureBox;
120                             //this.dia
121                             if (code.ShowDialog() == DialogResult.OK)
122                             {
123                                 code.Hide();
124                                 if (HttpWebHelper.isPass)
125                                 {
126                                     sonHtml = client.webClient(rediect);
127 
128                                     getTable(sonHtml, ref dt);
129                                 }
130                             }
131                             //waitHandle.Set();
132 
133                             //waitHandle.WaitOne();
134                         }));
135                         //waitHandle.WaitOne();
136                     }
137                     else
138                         getTable(sonHtml, ref dt);
139 
140                     //获取当前线程
141                     Thread th = Thread.CurrentThread;
142                     string name = th.Name;
143 
144                     this.Invoke(new Action(() =>
145                     {
146                         //MessageBox.Show(name.ToString());
147 
148 
149                         //创建tab选项卡,如果不存在
150                         if (!tabControlWarp.TabPages.ContainsKey(name))
151                             tabControlWarp.TabPages.Add(name, name);
152 
153                         //动态创建选项卡中显示的数据,和一些属性设置
154                         DataGridView view = new DataGridView();
155                         view.AllowUserToAddRows = false;
156                         view.AllowUserToDeleteRows = false;
157                         view.AllowUserToResizeColumns = false;
158                         view.AllowUserToResizeRows = false;
159                         view.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
160                         view.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
161                         view.MultiSelect = false;
162                         view.ReadOnly = true;
163                         view.RowHeadersVisible = false;
164                         view.BackgroundColor = Color.White;
165                         view.ScrollBars = ScrollBars.Vertical;
166                         view.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
167                         view.Dock = DockStyle.Fill;
168                         view.DataSource = dt;
169                         //把DataGridView添加到当前选项卡
170                         tabControlWarp.TabPages[name].Controls.Add(view);
171 
172                         //刷新窗体,否则DataGridView数据没有变化
173                         this.Refresh();
174                     }));
175                 }
176             }
177             //当前线程执行完毕,把当前的数据导出为excel
178             ExcelRender.ExcelRender.RenderToExcel(dt, ob[0] + ".xls");
179             Monitor.Exit(this);
180         }
复制代码

 

 

这个地方有一个难点就是,如果你采集的频率过高,58会跳转到一个验证码登录页面。这里本来是用多线程执行异步任务,

但:比如同时在执行采集 “手机维修”和”电脑维修”的时候。只要”手机维修”雨打验证码的时候,显然”电脑维修”也会遇到。会有很多不确定的因素,

因为是多线程异步操作,当我弹窗让用户输入验证码的代码,同样会执行多次。

所以找了采取了线程同步 。我用了 Monitor.Enter(this);实现同步。当然你可以用更简单的lock关键字可以实现同样的效果。

 

 

 

说到验证码。58算是下了大功夫,都知道58信息量的巨大。采集的人肯定多。58验证码的机制是。当跳转到验证码登录页面,

页面会生成唯一一个uuid,和一个验证码post到服务器的url和显示验证码有相关联的信息,下面会说明

从图片中可以看出来,显示验证码中的url和post到服务器中的url都包含 1032910901。这是重点,当你提交验证码的时候,服务器会验证 这个 数字 和uuid如果不匹配则验证错误。

你要记住:这个数字和uuid每次都是不同的。

 

那我这里是怎么显示验证码的呢?

首先我是用最普通也是最大众的方式。

用HttpWebRequest读取,其实当HttpWebRequest读取的时候,服务器的验证码已经变了。

当跳转到验证码登录页面。服务器就已经记住了uuid,url中的数字 和验证码,当你用HttpWebRequest去获取验证码肯定

和之前的验证码不同。

除了这种方式,网上也提到了好几种方式,这里验证成功后,有一个回调方法

可以通过HttpWebResponse获取响应请求的url。比如

复制代码
 1                         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(referer);
 2                         request.Method = "get";
 3                         string responseUrl = string.Empty;
 4                         string rediect = string.Empty;
 5                         using (HttpWebResponse response1 = (HttpWebResponse)request.GetResponse())
 6                         {
 7                             //"http://support.58.com/firewall/valid/1903444021.do?namespace=infodetailweb&url=http://sz.58.com/shoujiweixiu/21147587557513x.shtml"
 8                             responseUrl = response1.ResponseUri.ToString();
 9 
10                             //获取绝对路径 "/firewall/valid/1032910901.do"
11                             string absolutePath = response1.ResponseUri.AbsolutePath;
12 
13                             //ResponseUri.Authority  "support.58.com"
14                             HttpWebHelper.verCode = "http://" + response1.ResponseUri.Authority + absolutePath;
15 
16                             //获取?后面的字符串
17                             string query = response1.ResponseUri.Query;
18 
19                             //验证码成功后,重定向的url
20                             rediect = query.Substring(query.LastIndexOf("=") + 1);
21                         }
复制代码

 

 

第一种:页面在WebBrowser中打开。读取验证码图片流。保存在剪切板中

 

复制代码
 1 /// <summary>
 2         /// 返回指定WebBrowser中图片<IMG></IMG>中的图内容
 3         /// </summary>
 4         /// <param name="WebCtl">WebBrowser控件</param>
 5         /// <param name="ImgeTag">IMG元素</param>
 6         /// <returns>IMG对象</returns>
 7         private Image GetWebImage(WebBrowser WebCtl, HtmlElement ImgeTag)
 8         {
 9 
10             /*
11              * 这种方法有时候会因为剪切板没有头像而报异常,
12              * 初步判断是页面(我这里是js对图片赋值)图片没有加载完成,而没获取到图片
13              * System.Threading.Thread.Sleep(8000);测试通过。但每次时间是不确定的。
14              */
15 
16             HTMLDocument doc = (HTMLDocument)WebCtl.Document.DomDocument;
17             HTMLBody body = (HTMLBody)doc.body;
18             IHTMLControlRange rang = (IHTMLControlRange)body.createControlRange();
19             IHTMLControlElement Img = (IHTMLControlElement)ImgeTag.DomElement; //图片地址
20             Image oldImage = Clipboard.GetImage();
21             rang.add(Img);
22             rang.execCommand("Copy", false, null);  //拷贝到内存
23             Image numImage = Clipboard.GetImage(); //如果为null则保存
24 
25             //判断剪切板是否有图片 
26             //https://msdn.microsoft.com/zh-cn/library/system.windows.forms.clipboard.getimage.aspx
27             if (Clipboard.ContainsImage())
28             { }
29 
30 
31             try
32             {
33                 Clipboard.SetImage(oldImage);
34             }
35             catch (Exception ex)
36             {
37                 MessageBox.Show(ex.Message);
38             }
39             return numImage;
40         }
复制代码

 

 调用代码:

1            //找到图片
2             HtmlElement ImgeTag = webBrowser1.Document.GetElementById("imgCode");
3             
4             Image numPic = GetWebImage(webBrowser1, ImgeTag); // 得到验证码图片
5             pictureBox1.Image = numPic; //图片赋值

 

 

HTMLDocument需要添加引 用:F:\Program Files (x86)\Microsoft Visual Studio 12.0\Visual Studio Tools for Office\PIA\Common\Microsoft.mshtml.dll

引入命名空间:using mshtml;

 

显然。页面必须加载完成后才能获取到图片。即在事件中webBrowser1_DocumentCompleted获取。但它却不能判断js脚本什么时候完成。

如果是多线程异步任务,还需要webBrowser1_DocumentCompleted执行后,在执行后面的方法,因为webBrowser1_DocumentCompleted本身就是异步的

此时的解决方案是 利用AutoResetEvent阻止线程,等当前线程执行完毕

 AutoResetEvent waitHandle = new AutoResetEvent(false);
 while (waitHandle.WaitOne(10, false) == false) { Application.DoEvents(); }

 

 

 

第二种:抓图。根据图片的高宽来剪切

首先动态创建WebBrowser,并注册事件

 WebBrowser we = new WebBrowser();
            we.Url = new Uri("http://support.58.com/firewall/valid/3071088800.do");
            we.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(we_DocumentCompleted);

 

 

复制代码
 1 void we_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
 2         {
 3      
 4             //HtmlElement d = webBrowser1.Document.GetElementById("uuid");
 5 
 6             //string y = webBrowser1.Document.GetElementById("uuid").GetAttribute("value");
 7 
 8 
 9 
10             //var wb = new WebBrowser();
11 
12             HtmlElementCollection docs = we.Document.All;
13             foreach (HtmlElement item in docs)
14             {
15                 string ii = item.Id;
16 
17                 if (item.Id == "uuid")
18                 {
19                     string c = item.GetAttribute("value");
20                 }
21                 else if (item.Id == "imgCode")
22                 {
23                     HtmlElement img = item.Document.GetElementById("imgCode");
24                     item.Style = "position: absolute; z-index: 9999; top: 0px; left: 0px";
25 
26                     //抓图
27                     var b = new Bitmap(item.ClientRectangle.Width, item.ClientRectangle.Height);
28                     we.DrawToBitmap(b, new Rectangle(new Point(), item.ClientRectangle.Size));
29                     pictureBox1.Image = b;
30                     break;
31 
32                 }
33             }
34         }
复制代码

 

 

第二种有个注意的地方:WebBrowser必须动态创建但不能依附于窗体上,即不将WebBrowser加载到窗体,否则截取后的图片是显示白色的。我也不知道什么原因

第3种:是根据第二种演化而来的,也是我当前用的。感觉有些投机取巧

你可以到显示验证码页面查看验证码图片的大小,也就是高度和宽度,然后新建一个显示验证码的窗体,我这里取名为showCode

在showCode上放一个webBrowser,高度和宽度设置为验证码图片的高度和宽度。比如:

AllowWebBrowserDrop=false //控件不能拖动
ScrollBarsEnabled = false //取消滚动条
size = 120,40 验证码图片的高度

然后找到webbrowser中的图片。设置样式。使其显示在最右上角

img.Style = “position: absolute; z-index: 9999; top: 0px; left: 0px”;

窗体布局:

核心代码

复制代码
 1  public partial class showCode : Form
 2     {
 3         public Image p { get; set; }
 4         public string showCodeUrl { get; set; } //显示验证码页面
 5         public delegate void delegateCode(string code, string uuid);
 6         public delegateCode codeHandler;
 7 
 8 
 9         public showCode()
10         {
11             InitializeComponent();
12             //InitializeEvents();
13         }
14         /// <summary>
15         /// 初始化
16         /// </summary>
17         //private void InitializeEvents()
18         //{
19         //    this.webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted);
20         //}
21 
22         void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
23         {
24             WebBrowser bro = (WebBrowser)sender;
25 
26             HtmlElement img = bro.Document.GetElementById("imgCode");
27 
28             bro.Document.GetElementById("uuid").GetAttribute("value");
29 
30             img.Style = "position: absolute; z-index: 9999; top: 0px; left: 0px"; //使其显示在最右上角
31             img.SetAttribute("onclick", "javascript:void(0)"); //取消单击图片刷新验证码操作
32         }
33         private void btnOk_Click(object sender, EventArgs e)
34         {
35             string code = textCode.Text;
36             if (string.IsNullOrEmpty(code))
37             {
38                 MessageBox.Show("请输入验证码", "验证码", MessageBoxButtons.OK, MessageBoxIcon.Information);
39                 textCode.Focus();
40                 return;
41             }
42             if (codeHandler != null)
43             {
44                 string uuid = webBrowser.Document.GetElementById("uuid").GetAttribute("value");
45 
46                 this.DialogResult = DialogResult.OK;
47                 codeHandler(code, uuid);
48             }
49         }
50 
51         private void showCode_Load(object sender, EventArgs e)
52         {
53             //pictureBoxCode.Image = p;
54             webBrowser.Url = new Uri(showCodeUrl);
55             this.webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted);
56         }
57     }
复制代码

 

 

我这里定义了一个委托。利用回调机制,把验证码和uuid传给主窗体,这里显示验证码的url由主窗体传进来。

 

当遇到验证码的时候,就会弹窗,如果能做到自动识别就更好了。

 

当由列表页面抓取详细页面的时候,返回的html就是验证码页面的源码,这时候判断html中是否包含“验证码”关键字,

包含的话。则实例化窗口。把显示验证码的url传给显示验证码的窗体,并显示。

 

showCode code = new showCode();
code.codeHandler = new HttpWebHelper().postVerCode; //子窗体委托回调方法
code.showCodeUrl = responseUrl;//子窗体显示验证码的url

 

复制代码
//等待5秒,防止抓取频率过高 时间根据当前的环境来定
                    Thread.Sleep(5000);

                    //抓取详细页面。这里如果过于频繁,会跳到输入验证码页面
                    string sonHtml = client.webClient(referer);

                    //Monitor.Enter(this);

                    if (sonHtml.Contains("验证码"))
                    {
                        //这里的代码可以封装起来
                        /*
                         * 当遇到验证码后,我在抓取一次,以获取我需要的信息,
                         * 比如这里登录成功后有一个回调的url,我需要获得这个url。
                         * 比如下面的rediect字段
                         */
                        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(referer);
                        request.Method = "get";
                        string responseUrl = string.Empty;
                        string rediect = string.Empty;
                        using (HttpWebResponse response1 = (HttpWebResponse)request.GetResponse())
                        {
                            //"http://support.58.com/firewall/valid/1903444021.do?namespace=infodetailweb&url=http://sz.58.com/shoujiweixiu/21147587557513x.shtml"
                            responseUrl = response1.ResponseUri.ToString();

                            //获取绝对路径 "/firewall/valid/1032910901.do"
                            string absolutePath = response1.ResponseUri.AbsolutePath;

                           //ResponseUri.Authority  "support.58.com" 拼接成 post到服务器验证的完整路径
                            HttpWebHelper.verCode = "http://" + response1.ResponseUri.Authority + absolutePath;

                            //获取?后面的字符串
                            string query = response1.ResponseUri.Query;

                            //验证码成功后,重定向的url
                            rediect = query.Substring(query.LastIndexOf("=") + 1);
                        }
                        
                        this.Invoke(new Action(() =>
                        {

                            showCode code = new showCode();
                            code.codeHandler = new HttpWebHelper().postVerCode;//子窗体委托回调方法
                            code.showCodeUrl = responseUrl; //子窗体显示验证码的url
                            if (code.ShowDialog() == DialogResult.OK)
                            {
                                code.Hide();
                                if (HttpWebHelper.isPass)//说明验证码 验证成功
                                {
                                    sonHtml = client.webClient(rediect);

                                    getTable(sonHtml, ref dt);
                                }
                            }
                        }));
                    }
复制代码

 

 

好了。现在回到之前的问题上。现在需要抓取详细页面的数据,上面说了ThreadDownload只是抓取列表页面。

现在定义一个方法DataTable getTable(string document, ref DataTable dt),这里的dt是ref类型。是之前需要用的。好像现在已经用不上了。大家可以根据自己的要求修改

getTable方法是接收传来的详细页面。然后匹配信息:比如:用户名,手机号码,公司名称

复制代码
 1   private DataTable getTable(string document, ref DataTable dt)
 2         {
 3             try
 4             {
 5                 //if (IsDisposed) return null;
 6                 //this.Invoke(
 7                 //           new Action(() => { richTextBoxInfo.AppendText("正在下载\n"); })
 8                 //       );
 9 
10                 IHtmlDocument hd = new JumonyParser().Parse(document);
11                 //string company = hd.FindFirst("div[class=su_tit]").InnerText();
12 
13                 string company = "未知";
14                 string phone = "未知";
15                 string linkman = "未知";
16 
17                 //判断是个人还是企业
18                 var su = hd.Find("ul[class=suUl]");
19 
20                 //顶部html包含联系人。电话
21                 IHtmlDocument top = new JumonyParser().Parse(hd.FindFirst("ul[class=suUl]").InnerHtml());
22 
23                 if (su.Count() > 0)
24                 {
25                     if (top.Find("div[class=su_tit]").Count() > 0)
26                     {
27                         string txt = top.FindFirst("div[class=su_tit]").InnerText();
28                         if (txt.Contains("公司名称"))
29                         {
30                             if (top.Find("div[class=su_con]").Count() > 0)
31                                 //company = top.FindFirst("div[class=su_con]").FindFirst("a").InnerText();
32                                 company = top.FindFirst("div[class=su_con]").InnerText().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[0];
33                             if (top.Find("li:nth-child(1)").Count() > 0)
34                                 linkman = top.FindFirst("li:nth-child(2)").FindFirst("div[class=su_con]").FindFirst("a").InnerText();
35                             if (top.Find("span[class=l_phone]").Count() > 0)
36                                 phone = top.FindFirst("span[class=l_phone]").InnerText();
37                         }
38                         else if (txt.Contains("联系人"))
39                         {
40                             if (top.Find("li:nth-child(1)").Count() > 0)
41                                 linkman = top.FindFirst("li:nth-child(1)").FindFirst("div[class=su_con]").InnerText();
42                             if (top.Find("li:nth-child(2)").Count() > 0)
43                                 phone = top.FindFirst("li:nth-child(2)").FindFirst("span[id=t_phone]").InnerText();
44                         }
45                     }
46                 }
47 
48                 DataRow row = dt.NewRow();
49                 row["公司名字"] = company;
50                 row["联系电话"] = phone;
51                 row["联系人"] = linkman;
52 
53                 dt.Rows.Add(row);
54 
55 
56                 return dt;
57             }
58             catch (Exception)
59             {
60 
61                 return null;
62             }
63         }
复制代码

 

 

来看看入口函数,开启异步调用。显然是不让窗体假死

复制代码
 /// <summary>
        /// 开始抓取
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// 
        void btnStart_Click(object sender, EventArgs e)
        {
            btnStart.Enabled = false;

            //Dictionary<string, string> result = new Dictionary<string, string>();
            //string url = "http://sz.58.com/";
            //string keyword = "维修";

            string url = textBoxUrl.Text;
            string keyword = textBoxKeyword.Text;

            if (string.IsNullOrEmpty(url))
            {
                MessageBox.Show("请输入要抓取的网址", "网址", MessageBoxButtons.OK, MessageBoxIcon.Information);
                textBoxUrl.Focus();
                return;
            }
            else if (string.IsNullOrEmpty(keyword))
            {
                MessageBox.Show("请输入要抓取的关键字", "关键字", MessageBoxButtons.OK, MessageBoxIcon.Information);
                textBoxKeyword.Focus();
                return;
            }

            //string prefix = "http://sz.58.com";

            // 声明一个异步委托去处理下载操作
            Action downloadAction = new Action(() =>
            {
                ProcessDownload(url, keyword);
            });

            //Action<string, string> an = new Action<string, string>(ProcessDownload);

            //声明一个下载完成后的回调函数
            AsyncCallback callback = new AsyncCallback((asyncResult) =>
            {
                this.Invoke(
                         new Action(() => { richTextBoxInfo.AppendText("首页关键字匹配完成,显示在左侧列表中.....\n"); })
                     );
            });
            downloadAction.BeginInvoke(callback, null);
        }
复制代码

 

 

其余代码

 

复制代码
 /// <summary>
        /// 窗体关闭提醒
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Main_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (MessageBox.Show("是否退出当前程序", "关闭", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) e.Cancel = true;
            else Environment.Exit(0); //强制退出所以线程
        }

        /// <summary>
        /// 单击左边菜单栏
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void listBoxMenu_MouseClick(object sender, MouseEventArgs e)
        {
            string txt = listBoxMenu.Text;
            if (tabControlWarp.TabPages.ContainsKey(txt) && !string.IsNullOrEmpty(txt))
            {
                //tabControlWarp.TabPages.Add(txt, txt); //创建选项卡
                tabControlWarp.SelectedTab = tabControlWarp.TabPages[txt];//并且选中
            }
            //else tabControlWarp.SelectedTab = tabControlWarp.TabPages[txt];
        }
复制代码

 

 

项目中用到了NPOI导出excel,这里附上相关帮助类

View Code

 

代码没什么高级的地方。关键是看逻辑是否清晰,我这里优化的还很多。数据采集无非就是异步委托,多线程同步等等。就看你怎么灵活运用。

 

看了评论有很多需要源码的,源码分享于此:http://pan.baidu.com/s/1HagB8  密码:g4uw

源码还有很多不足的地方,可以看出,代码也有很多冗余的,很多注释都没时间去清理,

希望可以在你们的手上做得更好,而不是下载源码后做一个僵尸放到自己的硬盘里面。

 

作者: nsky
出处: http://nsky.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,共同学习;共同进步;但不能乱搞!