[转载]T-SQL查询处理详解 (续)

mikel阅读(790)

[转载]T-SQL查询处理详解 (续) – 我的成长点滴 – 博客园.

在上一篇T-SQL查询处理详解, 文中提到了在如果不考虑查询优化器做的优化的情况下T-SQL查询的逻辑处理过程,讲得很粗糙,这一篇将补充说明这个逻辑处理过程,并对一些容易误解的地 方进行说明。不对之处,还请指出。另外再次声明一点,这种逻辑处理顺序,是理论上的处理过程,实际情况还会根据查询优化器选择最优的执行计划。

还是从FROM阶段讲开。

(1)FROM阶段

这个阶段是查询逻辑处理的第一步。想到这里,想起了LINQ表达式不就是从FROM开始的嘛,看来还是挺有道理的。FROM阶段负责表示表或要查询的表。 如果指定了表运算符,还需  要按从左到右的顺序,对运算符进行逐个处理。表运算符有4类,JOIN,APPLY,PIVOT,UNPIVOT。每个表运算符都有自己的处理规则。这里 挑最常见的JOIN来说。

对于联接(join),一般有以下几个个步骤:

a.求笛卡尔积,对两张表进行cross join,得出最大的可能结果集。如果左表有n行,右表有m行,则结果集有nxm行。

b.利用ON筛选条件来筛选,剔除不符合条件的行。

c.对于外联接(left,right,full outer join),还需要添加外部行。在上个步骤中,ON条件剔除掉了所有不匹配两张表的行。但是在外联接中,通过指定外连接的类型,需要将其中的一个或者两个 表标记为保留表,并返回该表中所有的行。所以这时候还需要将保留表中被ON筛选条件剔除的行重新加入到结果集中(这些重新加进来的表,书中称为外部行), 并将外部行中非保留表的列值标记为NULL.再次提醒一下,这一个步骤,只有外联接才执行,对于内联接(inner join)只需要执行a和b两个步骤的。

(2)WHERE阶段

对于上一步骤返回的虚拟表,经过where条件的判定,只有让where条件为true的行才会被保留下来。请注意,因为还没有对数据进行分组,所以在 where子句中不能聚合。也不能引用select列表中创建的别名,因为SELECT阶段还在后头呢。例如where orderid>max(orderid), select year(thedate) as theyear… where theyear>2010是不能使用的。

另外一个很让人迷惑的问题是,对于包含JOIN的查询,到底ON和WHERE子句有什么区别,应该什么时候使用ON子句,什么时候使用where子句。这里作一下说明。只有对于外联接,ON和where子句才会存在这种逻辑区别, 因为在外联接中,通过ON子句的筛选之后,还要对保留表进行外部行添加,而where子句则是在外部行添加过之后才进行筛选的。因此,ON子句对这种外联 接的情况的筛选,并不是最终的结果,在FROM阶段的第三个步骤,还会把外部行添加回来的。而对于内联接,where子句和on子句作用是完全一样的,在 哪里筛选都是同种效果,没有其他步骤。所以在处理这种含有外联接的查询,一定要注意ON筛选和where筛选的区别,避免使用错了,达不到筛选的效果。另 外,对于内联接,一个不错的建议是,对于两个表都存在的字段筛选,用ON子句,对于单个表的字段筛选,用where,例如:select * from a inner join b on a.col = b.col where a.col2 >1。

(3)GROUP BY阶段

在这一阶段将上一步返回的虚拟表中的 结果集按分组进行重组,由分组集所有列的每个唯一组合,标识出一个组。再用这些组,跟上一步返回的每一个行进行关系。注意,每个行只能关联一个组。最后, 生成的结果集中,每个组只能有一行。关于GROUP BY还有很多有意思的地方,比如cube,rollup,grouping等等,有时间再一一介绍。

(4)HAVING阶段

HAVING筛选器用于对上一步返回的结果集进行筛选。HAVING筛选器是唯一能筛选分组数据的筛选器,ON和where都不行。理由很简单,ON和where都是在分组之前进行处理的,自然不能对分组进行筛选。所以HAVING和WHERE的区别,也是很显而易见了。HAVING 只能与 SELECT 语句一起使用。HAVING 通常在 GROUP BY 子句中使用。如果不使用 GROUP BY 子句,则 HAVING 的行为与 WHERE 子句一样。

(5)SELECT阶段

这一个步骤,将构造最终返回给调用者的表。这个步骤涉及到3个子阶段。

a.计算表达式。在这个阶段中,select列表可以返回油上一步得到的虚拟表的基础列,也可以是对这些基础列的操作。有一点需要注意的是,在这个select列表中,所有的表达式是同时计算的。举个例子,在SQL中,可以这么交换两个列值:update tab_test set col1 = col2,col2 =col1;在别的语言看来这的确很神奇。

而且在select列表中创建的别名不能在同一select列表中的其他表达式中使用。所以,基于这个特性,我们就会得出一个结论,select列表的顺序是无关紧要的。

b.处理DINSTINCT,果查询中指定了DINSTINCT,则从上一步返回的虚拟表中删除重复的行。

c.应用TOP选项。对于指定了TOP选项的查询,则会根据查询的order by子句来选择指定数量的行。TOP选项里有很多特殊的地方,此处先不扯远,以后有机会单独讲。

(6)ORDER BY阶段

这一步按照order by列表中的列明对上一步的表进行排序,返回游标。

这里有必要谈一下集合和游标。SQL的理论基础是集合,集合是无序的,它只是成员的一种逻辑集合。对于带有排序作用的ORDER BY子句的查询,可以返回一个对象,其中的行按照特定的顺序组织在一起。ANSI把这种 对象成为游标(cursor)。

因为在这一步中,最后返回的是游标,所以带有order by的查询,是不能用来定义视图,子查询,公用表等。例如:

SELECT * FROM (SELECT col1,col2 FROM tab_test ORDER BY col1)是无效的,并且将报错。

但是如果同时指定了TOP选项,则是一个例外。SELECT * FROM (SELECT top (10) col1,col2 FROM tab_test ORDER BY col1),对于这个查询,因为同时指定了top和order by,则子查询中的结果一定是固定而且有序的的,但是外部的查询,则不能保证是有序排列的。

对于T-SQL查询逻辑处理,就整理到这里,有什么问题,还请大家指出,一起探讨。

参考文献:《Microsoft SQL Server 2008 技术内幕:T-SQL查询》

[转载]MongoDB学习笔记(三) 在ASP.NET MVC模式下通过Jqgrid表格操作MongoDB数据

mikel阅读(832)

[转载]MongoDB学习笔记(三) 在MVC模式下通过Jqgrid表格操作MongoDB数据 – lipan – 博客园.

看到下图,是通过Jqgrid实现表格数据的基本增删查改的操作。表格数据增删改是一般企业应用系统开发的常见功能,不过不同的是这个表格数据 来源是非关系型的数据库MongoDB。noSQL虽然概念新颖,但是MongoDB基本应用实现起来还是比较轻松的,甚至代码比基本的ADO.net访 问关系数据源还要简洁。由于其本身的“非关系”的数据存储方式,使得对象关系映射这个环节对于MongoDB来讲显得毫无意义,因此我们也不会对 MongoDB引入所谓的“ORM”框架。

下面我们将逐步讲解怎么在MVC模式下将MongoDB数据读取,并展示在前台Jqgrid表格上。这个“简易系统”的基本设计思想是这样的: 我们在视图层展示表格,Jqgrid相关Js逻辑全部放在一个Js文件中,控制层实现了“增删查改”四个业务,MongoDB的基本数据访问放在了模型层 实现。下面我们一步步实现。

一、实现视图层Jqgrid表格逻辑

首先,我们新建一个MVC空白项目,添加好JQueryJQueryUI、Jqgrid的前端框架代码:

然后在Views的Home文件夹下新建视图“Index.aspx”,在视图的body标签中添加如下HTML代码:


接着新建Scripts\Home文件夹,在该目录新建“Index.js”文件,并再视图中引用,代码如下:

jQuery(document).ready(function () {

//jqGrid初始化
jQuery("#table1").jqGrid({
url: '/Home/UserList',
datatype: 'json',
mtype: 'POST',
colNames: ['登录名', '姓名', '年龄', '手机号', '邮箱地址', '操作'],
colModel: [
{ name: 'UserId', index: 'UserId', width: 180, editable: true },
{ name: 'UserName', index: 'UserName', width: 200, editable: true },
{ name: 'Age', index: 'Age', width: 150, editable: true },
{ name: 'Tel', index: 'Tel', width: 150, editable: true },
{ name: 'Email', index: 'Email', width: 150, editable: true },
{ name: 'Edit', index: 'Edit', width: 150, editable: false, align: 'center' }
],
pager: '#div1',
postData: {},
rowNum: 5,
rowList: [5, 10, 20],
sortable: true,
caption: '用户信息管理',
hidegrid: false,
rownumbers: true,
viewrecords: true
}).navGrid('#div1', { edit: false, add: false, del: false })
.navButtonAdd('#div1', {
caption: "编辑",
buttonicon: "ui-icon-add",
onClickButton: function () {
var id = $("#table1").getGridParam("selrow");
if (id == null) {
alert("请选择行!");
return;
}
if (id == "newId") return;
$("#table1").editRow(id);
$("#table1").find("#" + id + "_UserId").attr("readonly","readOnly");
$("#table1").setCell(id, "Edit", "<input id="Button1" onclick="Update(\&quot;&quot; + id + &quot;\&quot;)" type="button" value="提交" /><input id="Button2" onclick="Cancel(\&quot;&quot; + id + &quot;\&quot;)" type="button" value="取消" />");
}
}).navButtonAdd('#div1', {
caption: "删除",
buttonicon: "ui-icon-del",
onClickButton: function () {
var id = $("#table1").getGridParam("selrow");
if (id == null) {
alert("请选择行!");
return;
}
Delete(id);
}
}).navButtonAdd('#div1', {
caption: "新增",
buttonicon: "ui-icon-add",
onClickButton: function () {
$("#table1").addRowData("newId", -1);
$("#table1").editRow("newId");
$("#table1").setCell("newId", "Edit", "<input id="Button1" onclick="Add()" type="button" value="提交" /><input id="Button2" onclick="Cancel(\&quot;newId\&quot;)" type="button" value="取消" />");
}
});
});

//取消编辑状态
function Cancel(id) {
if (id == "newId") $("#table1").delRowData("newId");
else $("#table1").restoreRow(id);
}

//向后台ajax请求新增数据
function Add() {
var UserId = $("#table1").find("#newId" + "_UserId").val();
var UserName = $("#table1").find("#newId" + "_UserName").val();
var Age = $("#table1").find("#newId" + "_Age").val();
var Tel = $("#table1").find("#newId" + "_Tel").val();
var Email = $("#table1").find("#newId" + "_Email").val();

$.ajax({
type: "POST",
url: "/Home/Add",
data: "UserId=" + UserId + "&amp;UserName=" + UserName + "&amp;Age=" + Age + "&amp;Tel=" + Tel + "&amp;Email=" + Email,
success: function (msg) {
alert("新增数据: " + msg);
$("#table1").trigger("reloadGrid");
}
});
}

//向后台ajax请求更新数据
function Update(id) {
var UserId = $("#table1").find("#" + id + "_UserId").val();
var UserName = $("#table1").find("#" + id + "_UserName").val();
var Age = $("#table1").find("#" + id + "_Age").val();
var Tel = $("#table1").find("#" + id + "_Tel").val();
var Email = $("#table1").find("#" + id + "_Email").val();

$.ajax({
type: "POST",
url: "/Home/Update",
data: "UserId=" + UserId + "&amp;UserName=" + UserName + "&amp;Age=" + Age + "&amp;Tel=" + Tel + "&amp;Email=" + Email,
success: function (msg) {
alert("修改数据: " + msg);
$("#table1").trigger("reloadGrid");
}
});
}

//向后台ajax请求删除数据
function Delete(id) {
var UserId = $("#table1").getCell(id, "UserId");
$.ajax({
type: "POST",
url: "/Home/Delete",
data: "UserId=" + UserId,
success: function (msg) {
alert("删除数据: " + msg);
$("#table1").trigger("reloadGrid");
}
});
}

二、实现控制层业务

在Controllers目录下新建控制器“HomeController.cs”,Index.js中产生了四个ajax请求,对应控制层也有四个业务方法。HomeController代码如下:

public class HomeController : Controller
{
UserModel userModel = new UserModel();
public ActionResult Index()
{
return View();
}

///
/// 获取全部用户列表,通过json将数据提供给jqGrid
///
public JsonResult UserList(string sord, string sidx, string rows, string page)
{
var list = userModel.FindAll();
int i = 0;
var query = from u in list
select new
{
id = i++,
cell = new string[]{
u["UserId"].ToString(),
u["UserName"].ToString(),
u["Age"].ToString(),
u["Tel"].ToString(),
u["Email"].ToString(),
"-"
}
};

var data = new
{
total = query.Count() / Convert.ToInt32(rows) + 1,
page = Convert.ToInt32(page),
records = query.Count(),
rows = query.Skip(Convert.ToInt32(rows) * (Convert.ToInt32(page) - 1)).Take(Convert.ToInt32(rows))
};

return Json(data, JsonRequestBehavior.AllowGet);
}

///
/// 响应Js的“Add”ajax请求,执行添加用户操作
///
public ContentResult Add(string UserId, string UserName, int Age, string Tel, string Email)
{
Document doc = new Document();
doc["UserId"] = UserId;
doc["UserName"] = UserName;
doc["Age"] = Age;
doc["Tel"] = Tel;
doc["Email"] = Email;

try
{
userModel.Add(doc);
return Content("添加成功");
}
catch
{
return Content("添加失败");
}
}

///
/// 响应Js的“Delete”ajax请求,执行删除用户操作
///
public ContentResult Delete(string UserId)
{
try
{
userModel.Delete(UserId);
return Content("删除成功");
}
catch
{
return Content("删除失败");
}
}

///
/// 响应Js的“Update”ajax请求,执行更新用户操作
///
public ContentResult Update(string UserId, string UserName, int Age, string Tel, string Email)
{
Document doc = new Document();
doc["UserId"] = UserId;
doc["UserName"] = UserName;
doc["Age"] = Age;
doc["Tel"] = Tel;
doc["Email"] = Email;
try
{
userModel.Update(doc);
return Content("修改成功");
}
catch
{
return Content("修改失败");
}
}
}

三、实现模型层数据访问

最后,我们在Models新建一个Home文件夹,添加模型“UserModel.cs”,实现MongoDB数据库访问代码如下:

public class UserModel
{
//链接字符串(此处三个字段值根据需要可为读配置文件)
public string connectionString = "mongodb://localhost";
//数据库名
public string databaseName = "myDatabase";
//集合名
public string collectionName = "userCollection";

private Mongo mongo;
private MongoDatabase mongoDatabase;
private MongoCollection mongoCollection;

public UserModel()
{
mongo = new Mongo(connectionString);
mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;
mongoCollection = mongoDatabase.GetCollection(collectionName) as MongoCollection;
mongo.Connect();
}
~UserModel()
{
mongo.Disconnect();
}

///
/// 增加一条用户记录
///
///
<span> </span> public void Add(Document doc)
{
mongoCollection.Insert(doc);
}

///
/// 删除一条用户记录
///
public void Delete(string UserId)
{
mongoCollection.Remove(new Document { { "UserId", UserId } });
}

///
/// 更新一条用户记录
///
///
public void Update(Document doc)
{
mongoCollection.FindAndModify(doc, new Document { { "UserId", doc["UserId"].ToString() } });
}

///
/// 查找所有用户记录
///
///
public IEnumerable FindAll()
{
return mongoCollection.FindAll().Documents;
}

}

四、小结

代码下载:http://files.cnblogs.com/lipan/MongoDB_003.rar

自此为止一个简单MongoDB表格数据操作的功能就实现完毕了,相信读者在看完这篇文章后,差不多都可以轻松实现MongoDB项目的开发应用了。聪明的你一定会比本文做的功能更完善,更好。下篇计划讲解linq的方式访问数据集合。

[转载]T-SQL查询处理详解

mikel阅读(1125)

[转载]T-SQL查询处理详解 – 我的成长点滴 – 博客园.

最近在看《Microsoft SQL Server 2008 技术内幕:T-SQL查询》这本书,受益匪浅。准备写点东西记录一下我的学习过程。

首先简单提一下T-SQL。T-SQL的正式名称是Transact-SQL,是ANSI和ISO SQL标准的Microsoft SQL Server扩展,而PL/SQL是ORACLE对SQL标准的扩展。

对于T-SQL编程,用得最广泛的,莫过于查询(Querying)。要想写出高质量、高性能的查询语句,必须深入地了解逻辑查询处理。

一、逻辑查询处理的各个阶段

(5)SELECT DISTINCT TOP(<top_specification>) <select_list>

(1)FROM <left_table> <join_type> JOIN <right_table> ON <on_predicate>

(2)WHERE <where_predicate>

(3)GROUP BY <group_by_specification>

(4)HAVING <having_predicate>

(6)ORDER BY <order_by_list>

上边语句是一个普通格式的查询语句,基本包含了所有的查询条件和关键字。你可能会发现前边的序号并不是按顺序来的,被你说对了,这是SQL与其他编 程语言不同的最明显特征,就是它的执行顺序并不是按照编写顺序来的。上边的序号,就是查询语句在执行过程中的逻辑处理顺序。下面简单介绍一下各个阶段都干 了啥事。

(1)FROM 阶段

FROM阶段标识出查询的来源表,并处理表运算符。在涉及到联接运算的查询中(各种join),主要有以下几个步骤:

a.求笛卡尔积。不论是什么类型的联接运算,首先都是执行交叉连接(cross join),求笛卡儿积,生成虚拟表VT1-J1。

b.ON筛选器。这个阶段对上个步骤生成的VT1-J1进行筛选,根据ON子句中出现的谓词进行筛选,让谓词取值为true的行通过了考验,插入到VT1-J2。

c.添加外部行。如果指定了outer join,还需要将VT1-J2中没有找到匹配的行,作为外部行添加到VT1-J2中,生成VT1-J3。

经过以上步骤,FROM阶段就完成了。概括地讲,FROM阶段就是进行预处理的,根据提供的运算符对语句中提到的各个表进行处理(除了join,还有apply,pivot,unpivot)

(2)WHERE阶段

WHERE阶段是根据<where_predicate>中条件对VT1中的行进行筛选,让条件成立的行才会插入到VT2中。

(3)GROUP BY阶段

GROUP阶段按照指定的列名列表,将VT2中的行进行分组,生成VT3。最后每个分组只有一行。

(4)HAVING阶段

该阶段根据HAVING子句中出现的谓词对VT3的分组进行筛选,并将符合条件的组插入到VT4中。

(5)SELECT阶段

这个阶段是投影的过程,处理SELECT子句提到的元素,产生VT5。这个步骤一般按下列顺序进行

a.计算SELECT列表中的表达式,生成VT5-1。

b.若有DISTINCT,则删除VT5-1中的重复行,生成VT5-2

c.若有TOP,则根据ORDER BY子句定义的逻辑顺序,从VT5-2中选择签名指定数量或者百分比的行,生成VT5-3

(6)ORDER BY阶段

根据ORDER BY子句中指定的列明列表,对VT5-3中的行,进行排序,生成游标VC6.

当然SQL SERVER在实际的查询过程中,有查询优化器来生成实际的工作计划。以何种顺序来访问表,使用什么方法和索引,应用哪种联接方法,都是由查询优化器来决 定的。优化器一般会生成多个工作计划,从中选择开销最小的那个去执行。逻辑查询处理都有非常特定的顺序,但是优化器常常会走捷径。

详细的介绍,待续。

参考文献:《Microsoft SQL Server 2008 技术内幕:T-SQL查询》

[原创]ASP.NET刷新页面SessionID不同问题

mikel阅读(1622)

今天突然发现apsx页面的session.SessionID在每次刷新或者点击按钮的时候,每次的SessionID都不同。和之前学到 session是客户端与服务器端建立的会话,一但建立始终保存在服务器端,除非session过期或主动关闭session。那SessionID就应 该是一个了,很是困惑。

在网上仔细查了一下才发现,原来当用户第一次请求给定的应用程序中的 .aspx 文件时,ASP 生成一个 SessionID,SessionID 是由一个复杂算法生成的号码,它唯一标识每个用户会话,在新会话开始时,服务器将 Session ID 作为一个 cookie 存储在用户的 Web 浏览器中。

所以,每个页面的SessionID会不时的变化,当保存过一个Session之后(如:Session[“test”]=”123′),页面中的SessionID就不会再变化了。

上述仅用于vs2005, vs2003Session.SessionID一直不变化。

[转载]各大网站架构总结笔记

mikel阅读(1016)

[转载]各大网站架构总结笔记 – sxwgf – 博客园.

记得在大学里不止一次关注网站架构方面的东西了,但每次都是泛泛了解,也没有着重记录,一段时间后对各种架构的思想也就模糊了。这几天不知怎么的又 心血来潮(可能是快毕业了冲动了)想深入了解一下网站架构方面的知识,并想通过这次来总结一下网站架构,记录一点东西供自己以后翻阅,也给那些希望了解这 方面知识的朋友提供一点点有用的信息,下面是我这次学习的总结笔记,有什么写得不妥的地方还请大家指出,还有希望这篇随笔能抛砖引玉,大家各抒己见。

1、MySpace架构

回顾了MySpace的成长史,真是让人惊叹,他的架构基本经历了五个里程碑,每个阶段都是显得那么仓促,那么无奈,那么坎坷,又是那么的精彩,网 站为了生存只能想尽一切办法去优化系统架构,让用户满意。他给我们后人的启示是要尽早发现系统的瓶颈,设计师在设计时要有前瞻思想,否则今后有可能也要这 样仓促的升级你的产品。
这里是“五个里程碑”的具体介绍。

2、Flickr网站架构总结

Flickr.com 是网上最受欢迎的照片共享网站之一,还记得那位给Windows Vista拍摄壁纸的Hamad Darwish吗?他就是将照片上传到Flickr,后而被微软看中成为Vista壁纸御用摄影师。

–Pair of ServerIron’s做负载均衡

–Squid做html和照片的缓存

–Memcached做数据缓存

–尤其是mySQL数据库采用master-slave和shards技术实现了mySQL数据库的负载均衡,解决了数据库的瓶颈,达到了数据库横向扩展的目标。

这里是他详细的架构描述

3、YouTube架构总结

这个貌似在国内是被和谐的,要翻墙才能访问(不知到底何故)。看看他的架构:
--NetScaler用于负载均衡和静态内容缓存

--使用lighttpd作为Web服务器来提供视频服务

--CDN在多个地方备份内容,这样内容离用户更近的机会就会更高

--使用Google的BigTable,一个分布式数据存储、数据库分成shards,不同的用户指定到不同的shards、使用BigTable将图片备份到不同的数据中心,代码查看谁是最近的

这里是YouTube详细架构描述

4、PlentyOfFish架构总结

这个我觉的最神奇了,一个人每天花2个小时,可以维护一个每天3000W PV的,而且是基于.NET的(呵呵,终于给我们.net程序员一个好榜样了)。简述他的架构:
–用Microsoft Windows操作系统作为服务器

–使用ASP.NET技术

–使用IIS作为Web容器

–用Akamai CDN来缓存网页

–用Foundry ServerIron 来做负载均衡

SQLServer采用master-slave架构,两台负责read操作,master那台负责写操作

–所有的request数据都使用了gzip压缩

PlentyOfFish详细架构描述

5、WikiPedia架构总结

维基百科(Wikipedia)是一个基于Wiki技术的全球性多语言百科全书协作计划,同时也是一部在网际网路上呈现的网路百科全书,其目标及宗旨是为全人类提供自由的百科全书──用他们所选择的语言来书写而成的,是一个动态的、可自由和的全球知识体。

使用MediaWiki软件

[转载]利用SVN的POST-COMMIT钩子自动部署代码

mikel阅读(985)

[转载]利用SVN的POST-COMMIT钩子自动部署代码 – 小狼的世界 – 博客园.

最近配置了一台SVN服务器,用来保存自己一些小项目的代码。同时,SVN服务器也是一台前端的Web。所以希望利用SVN的POST-COMMIT钩子HOOKS,在提交代码的同时进行部署。

具体的步骤如下:

1、找到SVN项目的HOOKS目录。目录中默认会几个对应操作的钩子模板,我们需要创建一个post-commit的文件。

2、post-commit的主要内容

#/bin/bash

REPOS=”$1″
REV=”$2″
WEB_PATH=”/home/fltrpsrv2/www”
TEST_PATH=”/home/apache/testx/www.h.cn”

export LANG=zh_CN.UTF-8

CURDATE=`date`
echo “Code Deployed By at $CURDATE” >> /home/fltrpsrv2/svn/www.h.cn/hooks/code_deploy_log
/usr/local/bin/svn update –username xxx –password xxx /home/apache/htdocs/ >> /home/fltrpsrv2/svn/www.h.cn/hooks/
code_deploy_log

3、接下来需要我们首先Checkout一份代码到WEB的服务目录中。

4、 代码CO出来之后,可以进行post-commit脚本的测试了。因为svn的hooks执行的时候不带有任何的环境变量,所以我们不能通过简单的 ./post-commit 进行代码的测试。必须要使用sudo su 等命令切换到svn或者apache服务器运行用户下,用下面的方法进行测试

env – ./post-commit

这样,执行svn update 命令的用户就是svn或者apache服务器的运行用户,因此,我们需要保证代码的存放目录必须有对应用户的权限,否则,这个脚本是不能执行的。我就是因为这个权限的问题,耽误了很多时间。

关于权限问题,在SVN的官方站点中也有说明,大家可以参考后面的资料。

在Unix系统中,如果没有对应的用户,也可以使用c语言编写脚本的形式,并且赋予+S的权限来达到相应的目的,具体的查看附件中的资料吧。

Technorati 标签: ,,,

参考资料:
1、SVN Authentication and Auto Update
2、SVN Forum
3、Website auto update
4、Hook Debuging

[转载]HTML5 - 搭建移动Web应用

mikel阅读(976)

[转载]HTML5 – 搭建移动Web应用 – gzterrytan – 博客园.

关于HTML5

HTML5具有语义学本地存储设备访问连接性多媒体平面和三维效果性能和集成CSS3八大技术特征。让Web应用进入无插件时代,在功能和性能上逼近桌面应用。促使应用Web化,实现跨平台。

HTML5规范草案将于2012年发布候选推荐版,2022年发布计划推荐版。规范的实现似乎还在遥远的未来,其实不然!当前很多浏览器已经部分支持HTML5,caniuse.com提供了详尽的浏览器支持情况。HTML5规范本身并不多,很多相关规范都被独立出来,由浏览器各自实现。

      

移动Web应用方向

手机上网已经成为最重要的上网方式之一,手机网民已达3亿。移动互联网时代已经开启,发展势头迅猛,成为互联网行业的新战场。

Android和iOS手机的兴起,加速了HTML5在移动设备的普及。与桌面浏览器不同的是,移动操作系统和浏览器随着手机的换代而不断升 级。移动浏览器的不断升级,给HTML5在移动Web方向的发展提供源源不断的动力。也随着设备性能的不断提高,移动Web应用的能力也渐渐逼近客户端应 用。

移动Web应用对比客户端应用的优势:

  1. 更多开发人员有丰富的Web开发经验和工具积累,也形成了成熟的开发社区
  2. 迭代更敏捷,实现持续更新
  3. 跨平台,开发成本比客户端的较低

Web应用宿主选择

Web都有宿主,宿主是运行程序所需要的环境。Web常见的宿主有IE、FF、Chrome这些浏览器。JavaScript也运行在服务器端宿主,如node.js。在移动设备,移动Web可以运行在移动浏览器上,也可以运行在PhoneGap或Titanium等框架宿主上。当然我们也可以根据跨平台需要编写自己框架宿主。

框架宿主优势:

  1. 已形成成熟的社区,便于解决问题
  2. 如PhoneGap等,比浏览器拥有更高权限。可以访问联系人、文件、摄像头、录音等设备
  3. 可以通过模拟器进行测试,减少跨浏览器测试成本

框架宿主劣势

  1. 以客户端形式发布,版本更新难度大,动态发布需要额外代码支持
  2. 系统有可能只支持单进程
  3. 产品之间无法跳转,不容易衔接

PhoneGap兼容性


移动浏览器是系统附带的,不需要发布,随着系统升级而更新。在没有权限要求和高端目标设备的情况下,浏览器宿主作为移动Web应用宿主更为适合。

Android浏览器   Mobile Safari

浏览器宿主优势:

  1. 无需发布,浏览器一般还支持桌面快捷方式
  2. 产品更新维护方便,可以实现持续更新
  3. 移植性高(相对框架宿主的一些自定义接口)

浏览器宿主劣势:

  1. 浏览器兼容不高,对HTML5支持有差异
  2. 性能差异大,必须考虑设备间处理能力的差异
  3. 测试成本大(特别是Android系统设备),无法覆盖所有机型

移动JavaScript框架

在HTML5的支持下,交互集成取代Javascript浏览器兼容性成了移动Javascript框架的发展方向。其中JQuery Mobile和Sencha Touch最受追捧。

他们主要解决:

  1. 交互(包括UI设计、控件交互、页面切换等)集成
  2. Touch手势

交互集成框架优势:

  1. 交互都符合设备交互特点和设计规范(Android和iOS)
  2. 开发门槛低,适合快速开发
  3. 开发社区已经有丰富的跨平台经验和较全面的测试结果

交互集成框架劣势:

  1. 文件体积较大,效率往往不如原生HTML5
  2. 交互模式固定,难以摆脱
  3. 没有完善的业务框架,还需要自己实现基础业务框架(代码更新、业务模块重用等)

JQuery Mobile 兼容性

除了交互集成框架,我们还可以考虑使用原生HTML5开发自己的移动Javascript框架。自己开发更有针对性和目的性,适合业务扩展和性能优化,可以弥补集成框架的缺陷。

原生HTML5框架优势:

  1. 最适合长期业务扩展
  2. 开发灵活度更高
  3. 可针对目标设备的性能优化

原生HTML5框架劣势:

  1. 暂时只能兼容高端设备(Android2.0+、iOS3.2+性能较佳)
  2. 跨平台测试成本比较高
  3. 框架开发门槛比较高,需要一段时间发展

参考资料

http://www.caniuse.com

http://www.w3.org

《2010年中国手机上网用户行为研究报告》

《2010-2011年中国智能手机市场研究年度报告》

[转载]Android开发——MediaProvider源码分析(2)

mikel阅读(775)

[转载]Android开发——MediaProvider源码分析(2) – 努力吧,专注Android – 博客园.

欲读此文,先读上文:MediaProvider源码分析(1)

———————-START—————————

在 上一篇文章中说到系统当接收到扫描请求广播的时候就会调用scan或者scanFile去扫描手机(手机内存和sdcard)中的媒体文件。这两个方法都 是启动MediaScannerService这个服务来完成扫描任务的。接下来我们来看看MediaScannerService是怎么工作的……

4.MediaScannerService(MSS)

MSS实现了Runnable,所以必然的需要实现run方法了,代码如下:

public void run()

{

// reduce priority below other background threads to avoid interfering

// with other services at boot time.

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +

Process.THREAD_PRIORITY_LESS_FAVORABLE);

Looper.prepare();

mServiceLooper = Looper.myLooper();

mServiceHandler = new ServiceHandler();

Looper.loop();

}

在run方法中设置了线程的优先级,优先级比较低,主要为了避免跟其他服务抢夺资源。还有就是利用looper对ServiceHandler的消息进行循环控制。

接着看一下ServiceHandler的实现代码:

private final class ServiceHandler extends Handler

{

@Override

public void handleMessage(Message msg)

{

Bundle arguments = (Bundle) msg.obj;

//获取文件路径

String filePath = arguments.getString("filepath");

try {

if (filePath != null) {

//文件路径不为空,则调用扫面当个文件的方法

IBinder binder = arguments.getIBinder("listener");

IMediaScannerListener listener =

(binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));

Uri uri = scanFile(filePath, arguments.getString("mimetype"));//扫描单个文件

if (listener != null) {

//执行扫描完成方法

listener.scanCompleted(filePath, uri);

}

} else {

//如果文件路径为空,则获取扫面手机内存或者sdcard

String volume = arguments.getString("volume");

String[] directories = null;

//内置卡

if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {

// scan internal media storage

directories = new String[] {

Environment.getRootDirectory() + "/media",

};

}//外置卡

else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {

// scan external storage

directories = new String[] {

Environment.getExternalStorageDirectory().getPath(),

};

}

if (directories != null) {

if (Config.LOGD) Log.d(TAG, "start scanning volume " + volume);

//扫描

scan(directories, volume);

if (Config.LOGD) Log.d(TAG, "done scanning volume " + volume);

}

}

} catch (Exception e) {

Log.e(TAG, "Exception in handleMessage", e);

}

stopSelf(msg.arg1);

}

};

以上三个方法是属于Service的生命周期的。当我们调用startService的时候,如果对应的Service还未创建就会调用 onCreate方法===方法。每次startService的时候就调用onStartCommand,所以ServiceHandler就在此发送 消息了。

最后,稍微看一下MSS里面扫描方面。主要是调用MediaScanner对媒体文件进行扫描分析的。至于MediaScanner的实现以后在分析。

private void openDatabase(String volumeName) {

try {

ContentValues values = new ContentValues();

values.put("name", volumeName);

getContentResolver().insert(Uri.parse("content://media/"), values);

} catch (IllegalArgumentException ex) {

Log.w(TAG, "failed to open media database");

}

}

private void closeDatabase(String volumeName) {

try {

getContentResolver().delete(

Uri.parse("content://media/" + volumeName), null, null);

} catch (Exception e) {

Log.w(TAG, "failed to close media database " + volumeName + " exception: " + e);

}

}

//创建扫描器

private MediaScanner createMediaScanner() {

MediaScanner scanner = new MediaScanner(this);

Locale locale = getResources().getConfiguration().locale;

if (locale != null) {

String language = locale.getLanguage();

String country = locale.getCountry();

String localeString = null;

if (language != null) {

if (country != null) {

scanner.setLocale(language + "_" + country);

} else {

scanner.setLocale(language);

}

}

}

return scanner;

}

//扫描目录

private void scan(String[] directories, String volumeName) {

// don't sleep while scanning

mWakeLock.acquire();

ContentValues values = new ContentValues();

values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);

Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);

Uri uri = Uri.parse("file://" + directories[0]);

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

try {

if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {

openDatabase(volumeName);

}

MediaScanner scanner = createMediaScanner();

scanner.scanDirectories(directories, volumeName);

} catch (Exception e) {

Log.e(TAG, "exception in MediaScanner.scan()", e);

}

getContentResolver().delete(scanUri, null, null);

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));

mWakeLock.release();

}

//扫描文件

private Uri scanFile(String path, String mimeType) {

String volumeName = MediaProvider.INTERNAL_VOLUME;

String externalStoragePath = Environment.getExternalStorageDirectory().getPath();

if (path.startsWith(externalStoragePath)) {

volumeName = MediaProvider.EXTERNAL_VOLUME;

openDatabase(volumeName);

}

MediaScanner scanner = createMediaScanner();

//扫描单个文件

return scanner.scanSingleFile(path, volumeName, mimeType);

}

在MediaProvider中还有一个类:MediaThumbRequest,用来创建预览图的,比如视频的预览图,图片的预览图,音频的专辑图片…这些图片的信息也是保存在数据库的,有兴趣的同学可以自己打开数据库看看里面的表。如下图:

media_db_tables

啰哩啰唆的写了两篇文章,希望对大家有所帮助。

其中应该有不少是错误的观点,望大家指正。

———————-END——————————

[转载]Android 事件处理(—)

mikel阅读(1019)

[转载]Android 事件处理(—) – 神舟龙 – 博客园.

(一) 事件使我们在于UI交互式发生的,我们点击一个按键时,可能就已经除非好几个事件,例如我们点击数字键“0”,他会涉及到按下事件,和一个弹起(松开)事件,在我们Android中还可能涉及到触摸屏事件,所以在Android系统中,事件是作为常用的功能之一;

android下,事件的发生是在监听器下进行,android系统可以响应按键事件和触摸屏事件,事件说明如下:

l onClick(View v) 一个普通的点击按钮事件

l boolean onKeyMultiple(int keyCode,int repeatCount,KeyEvent event)用于在多个事件连续时发生,用于按键重复,必须重载@Override实现

l boolean onKeyDown(int keyCode,KeyEvent event) 用于在按键进行按下时发生

l boolean onKeyUp(int keyCode,KeyEvent event 用于在按键进行释放时发生

l onTouchEvent(MotionEvent event)触摸屏事件,当在触摸屏上有动作时发生

l boolean onKeyLongPress(int keyCode, KeyEvent event)当你长时间按时发生(疑问?)

(二) 首先我们建立一个android项目,当项目建立好之后,直接在默认的main.xml文件中拖放一个button 按钮,其他的不需要在这里做什么了,然后就可以到命名好的.java文件中进行先关代码的书写;

1. 对要使用的控件进行引用,当然你也可以用到的时候再在相关类控件添加引用

import android.app.Activity;

import android.os.Bundle;

import android.view.KeyEvent;

import android.view.MotionEvent;

import android.view.View;

import android.widget.Button;

import android.widget.Toast;

2. 获得相关对象,设置控件监听器

Button button=(Button) findViewById(R.id.button1);

//设置监听

button.setOnClickListener(new Button.OnClickListener()

{

@Override

public void onClick(View v) {

// TODO Auto-generated method stub

DisplayToast(事件触发成功);

}

});

请注意这里末尾使用的是分号“;这里就是获得button的实例,然后对他进行监听,当用户点击时就会发生onClick事件,这里还用到一个方法,就是显示一个短消息,在屏幕停留几秒钟就会自动消失,其方法如下:

public void DisplayToast(String str)

{

Toast.makeText(this, str, Toast.LENGTH_SHORT).show();

}

当然你也可以设置显示长点,即Toast.LENGTH_SHORT改为Toast.LENGTH_LONG

3. 当按键按下是发生的事件

public boolean onKeyDown(int keyCode,KeyEvent event)

{

switch(keyCode)

{

case KeyEvent.KEYCODE_0:

DisplayToast(“你按下数字键0″);

break;

case KeyEvent.KEYCODE_DPAD_CENTER:

DisplayToast(“你按下中间键“);

break;sss

case KeyEvent.KEYCODE_DPAD_DOWN:

DisplayToast(“你按下下方向键“);

break;

case KeyEvent.KEYCODE_DPAD_LEFT:

DisplayToast(“你按下左方向键“);

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

DisplayToast(“你按下右方向键“);

break;

case KeyEvent.KEYCODE_DPAD_UP:

DisplayToast(“你按下上方向键“);

break;

case KeyEvent.KEYCODE_ALT_LEFT:

DisplayToast(“你按下组合键alt+←”);

break;

}

return super.onKeyDown(keyCode, event);

}

这里所有的keyCode都囊括了,这只是几个比较典型的例子,效果如下:







4. 当按键弹起时发生的事件,代码如下:

public boolean onKeyUp(int keyCode,KeyEvent event)

{

switch(keyCode)

{

case KeyEvent.KEYCODE_0:

DisplayToast(松开数字键0″);

break;

case KeyEvent.KEYCODE_DPAD_CENTER:

DisplayToast(松开中间键);

break;

case KeyEvent.KEYCODE_DPAD_DOWN:

DisplayToast(松开下方向键);

break;

case KeyEvent.KEYCODE_DPAD_LEFT:

DisplayToast(松开左方向键);

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

DisplayToast(松开右方向键);

break;

case KeyEvent.KEYCODE_DPAD_UP:

DisplayToast(松开上方向键);

break;

case KeyEvent.KEYCODE_ALT_LEFT:

DisplayToast(松开组合键alt+←”);

break;

}

return super.onKeyUp(keyCode, event);

}

效果与上图类似,只是文字不一样

5. 触摸屏事件,当用手或者用笔在触摸屏上做动作是发生,相关代码如下:

public boolean onTouchEvent(MotionEvent event)

{

int iAction=event.getAction();

if(iAction==MotionEvent.ACTION_MOVE)

{

DisplayToast(你在触摸屏上进行了滑动);

}

else

{

return false;

}

return super.onTouchEvent(event);

}

6. 连续点击按键时发生的事件

Publicboolean onKeyMultiple(int keyCode,int repeatCount,KeyEvent event)

{

Return super.onKeyMultiple(keyCode, repeatCount, event);

}

整体效果还不错,又向android迈进一步!!! 源码下载

[转载]好房别让中介抢了-抢房源信息的小程序

mikel阅读(952)

[转载]好房别让中介抢了-抢房源信息的小程序 – 调调儿 – 博客园.

马上又得换房子了,房子好贵啊,房租又涨了,房东涨也就算了,又让中介扒一层皮,悲剧!!

刘淇都说了,乱就乱在中介

但是没办法,只好试试看能否赶在中介前面抢到一手的房源。

好房别让中介抢了,

好白菜别让XX拱了,

好XX别让XX XX了

这个小程序代码借自 :

抢火车票利器:分享一个抓取火车票转让信息的小程序

只是换了winform的皮。

只要在xml中配置一些url就可以监控那些房源信息了

<?xml version="1.0" encoding="utf-8" ?>
<searcher>
    <item name="赶集网" domain="http://bj.ganji.com/" encoding="utf-8" keys="" top="2" exclude="经纪人推广">
        <pattern><![CDATA[<dt><a class="list_title" href="(.*?)" target="_blank">(.*?)</a>[\s\S]+?<span class="list_word">(?<cn>[\s\S]*?)</span>[\s\S]+?class="room">(\S+)?<[\s\S]+?class="price">(\d+)]]></pattern>
        <url desc="北苑.大屯一居"><![CDATA[http://bj.ganji.com/fang1/chaoyangbeiyuan-datun/h1/]]></url>
        <url desc="霍营,立水桥,天通苑一居"><![CDATA[http://bj.ganji.com/fang1/huoying-lishuiqiao-tiantongyuan/h1/]]></url>
    </item>
    <item name="58同城网" domain="" encoding="utf-8" keys="" exclude="" top="2">
        <pattern><![CDATA[<tr><td class="t" style="padding-left:10px;"><a href="(.*?)" target="_blank" class="t">(.*?)</a>(?<cn>[\s\S]*?)</td>[\s\S]*?<b>(\d+)</b>[\s\S]*?"tc" >(\S+?)</td>]]></pattern>
        <url desc="北苑一居"><![CDATA[http://bj.58.com/bjbeiyuan/zufang/i1/]]></url>
        <url desc="大屯一居"><![CDATA[http://bj.58.com/datun/zufang/i1/]]></url>
        <url desc="立水桥一居"><![CDATA[http://bj.58.com/lishuiqiao/zufang/i1/]]></url>
        <url desc="霍营一居"><![CDATA[http://bj.58.com/huoying/zufang/i1/]]></url>
        <url desc="天通苑一居"><![CDATA[http://bj.58.com/tiantongyuan/zufang/i1/]]></url>
    </item>
    <item name="百姓网" domain="http://beijing.baixing.com" encoding="utf-8" exclude="" keys="" top="2">
        <pattern><![CDATA[<td nowrap="nowrap" style="text-align:left;">(?:\S*?)</td><td style="text-align:left"><a href="(.*?)">([\S\s]*?)</a>\s*\((?<cn>[\S\s]*?)</span>\)</td><td nowrap=\\"nowrap\\">(\d+)]]></pattern>
        <url desc="北苑一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&areaName=chaoyang&areaName2=beiyuan&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4]]></url>
        <url desc="大屯一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=chaoyang&areaName2=datun]]></url>
        <url desc="立水桥一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=changping&areaName2=lishuiqiao]]></url>
        <url desc="霍营一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=changping&areaName2=huoying]]></url>
        <url desc="天通苑一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=changping&areaName2=tiantongyuan]]></url>
    </item>
</searcher>

运行开来后就最小化掉,右下角会弹框的。

按下Search..

image

搜出来的都弹在右下角了,点击可打开链接,瞅瞅有多贵。。。

image

OK.

源码:.net4.0+VS2010环境