自从有了IP数据库这种东西,QQ外挂的显示IP功能也随之而生,本人见识颇窄,是否还有其他应用不得而知,不过,IP数据库确实是个不错的东西。如今网络上最流行的IP数据库我想应该是纯真版的(说错了也不要扁我),迄今为止其IP记录条数已经接近30000,对于有些IP甚至能精确到楼层,不亦快哉。2004年4、5月间,正逢LumaQQ破土动工,为了加上这个人人都喜欢,但是好像人人都不知道为什么喜欢的显IP功能,我也采用了纯真版IP数据库,它的优点是记录多,查询速度快,它只用一个文件QQWry.dat就包含了所有记录,方便嵌入到其他程序中,也方便升级。
基本结构
QQWry.dat文件在结构上分为3块:文件头,记录区,索引区。一般我们要查找IP时,先在索引区查找记录偏移,然后再到记录区读出信息。由于记录区的记录是不定长的,所以直接在记录区中搜索是不可能的。由于记录数比较多,如果我们遍历索引区也会是有点慢的,一般来说,我们可以用二分查找法搜索索引区,其速度比遍历索引区快若干数量级。图1是QQWry.dat的文件结构图。
图1. QQWry.dat文件结构
要注意的是,QQWry.dat里面全部采用了little-endian字节序
一. 了解文件头
QQWry.dat的文件头只有8个字节,其结构非常简单,首四个字节是第一条索引的绝对偏移,后四个字节是最后一条索引的绝对偏移。
二. 了解记录区
每条IP记录都由国家和地区名组成,国家地区在这里并不是太确切,因为可能会查出来“清华大学计算机系”之类的,这里清华大学就成了国家名了,所以这个国家地区名和IP数据库制作的时候有关系。所以记录的格式有点像QName,有一个全局部分和局部部分组成,我们这里还是沿用国家名和地区名的说法。
于是我们想象着一条记录的格式应该是: [IP地址][国家名][地区名],当然,这个没有什么问题,但是这只是最简单的情况。很显然,国家名和地区名可能会有很多的重复,如果每条记录都保存一个完整的名称拷贝是非常不理想的,所以我们就需要重定向以节省空间。所以为了得到一个国家名或者地区名,我们就有了两个可能:第一就是直接的字符串表示的国家名,第二就是一个4字节的结构,第一个字节表明了重定向的模式,后面3个字节是国家名或者地区名的实际偏移位置。对于国家名来说,情况还可能更复杂些,因为这样的重定向最多可能有两次。
那么什么是重定向模式?根据上面所说,一条记录的格式是[IP地址][国家记录][地区记录],如果国家记录是重定向的话,那么地区记录是有可能没有的,于是就有了两种情况,我管他叫做模式1和模式2。我们对这些格式的情况举图说明:
图2. IP记录的最简单形式
图2表示了最简单的IP记录格式,我想没有什么可以解释的
图3. 重定向模式1
图3演示了重定向模式1的情况。我们看到在模式1的情况下,地区记录也跟着国家记录走了,在IP地址之后只剩下了国家记录的4字节,后面3个字节构成了一个指针,指向了实际的国家名,然后又跟着地址名。模式1的标识字节是0x01。
图4. 重定向模式2
图4演示了重定向模式2的情况。我们看到了在模式2的情况下(其标识字节是0x02),地区记录没有跟着国家记录走,因此在国家记录之后4个字节之后还是有地区记录。我想你已经明白了模式1和模式2的区别,即:模式1的国家记录后面不会再有地区记录,模式2的国家记录后会有地区记录。下面我们来看一下更复杂的情况。
图5. 混和情况1
图5演示了当国家记录为模式1的时候可能出现的更复杂情况,在这种情况下,重定向指向的位置仍然是个重定向,不过第二次重定向为模式2。大家不用担心,没有模式3了,这个重定向也最多只有两次,并且如果发生了第二次重定向,则其一定为模式2,而且这种情况只会发生在国家记录上,对于地区记录,模式1和模式2是一样的,地区记录也不会发生2次重定向。不过,这个图还可以更复杂,如图7:
图6. 混和情况2
图6是模式1下最复杂的混和情况,不过我想应该也很好理解,只不过地区记录也来重定向而已,有一点我要提醒你,如果重定向的地址是0,则表示未知的地区名。
所以我们总结如下:一条IP记录由[IP地址][国家记录][地区记录]组成,对于国家记录,可以有三种表示方式:字符串形式,重定向模式1和重定向模式2。对于地区记录,可以有两种表示方式:字符串形式和重定向,另外有一条规则:重定向模式1的国家记录后不能跟地区记录。按照这个总结,在这些方式中合理组合,就构成了IP记录的所有可能情况。
设计的理由
在我们继续去了解索引区的结构之前,我们先来了解一下为何记录区的结构要如此设计。我想你可能想到了答案:字符串重用。没错,在这种结构下,对于一个国家名和地区名,我只需要保存其一次就可以了。我们举例说明,为了表示方便,我们用小写字母代表IP记录,C表示国家名,A表示地区名:
有两条记录a(C1, A1), b(C2, A2),如果C1 = C2, A1 = A2,那么我们就可以使用图3显示的结构来实现重用
有三条记录a(C1, A1), b(C2, A2), c(C3, A3),如果C1 = C2, A2 = A3,现在我们想存储记录b,那么我们可以用图6的结构来实现重用
有两条记录a(C1, A1), b(C2, A2),如果C1 = C2,现在我们想存储记录b,那么我们可以采用模式2表示C2,用字符串表示A2
你可以举出更多的情况,你也会发现在这种结构下,不同的字符串只需要存储一次。
了解索引区
在"了解文件头"部分,我们说明了文件头实际上是两个指针,分别指向了第一条索引和最后一条索引的绝对偏移。如图8所示:
图8. 文件头指向索引区图示
实在是很简单,不是吗?从文件头你就可以定位到索引区,然后你就可以开始搜索IP了!每条索引长度为7个字节,前4个字节是起始IP地址,后三个字节就指向了IP记录。这里有些概念需要说明一下,什么是起始IP,那么有没有结束IP?假设有这么一条记录:166.111.0.0 - 166.111.255.255,那么166.111.0.0就是起始IP,166.111.255.255就是结束IP,结束IP就是IP记录中的那头4个字节,这下你应该就清楚了吧。于是乎,每条索引配合一条记录,构成了一个IP范围,如果你要查找166.111.138.138所在的位置,你就会发现166.111.138.138落在了166.111.0.0- 166.111.255.255 这个范围内,那么你就可以顺着这条索引去读取国家和地区名了。那么我们给出一个最详细的图解吧:
图9. 文件详细结构
现在一切都清楚了是不是?也许还有一点你不清楚,QQWry.dat的版本信息存在哪里呢? 答案是:最后一条IP记录实际上就是版本信息,最后一条记录显示出来就是这样:255.255.255.0255.255.255.255 纯真网络 2004年6月25日IP数据。OK,到现在你应该全部清楚了。
Demo
下一步:我给出一个读取IP记录的程序片断,此片断摘录自LumaQQ源文件edu.tsinghua.lumaqq.IPSeeker.java,如果你有兴趣,可以下载源代码详细看看。
/** *//**
* 给定一个ip国家地区记录的偏移,返回一个IPLocation结构
* @param offset 国家记录的起始偏移
* @return IPLocation对象
*/
private IPLocation getIPLocation(long offset) {
try {
// 跳过4字节ip
ipFile.seek(offset + 4);
// 读取第一个字节判断是否标志字节
byte b = ipFile.readByte();
if(b == REDIRECT_MODE_1) {
// 读取国家偏移
long countryOffset = readLong3();
// 跳转至偏移处
ipFile.seek(countryOffset);
// 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向
b = ipFile.readByte();
if(b == REDIRECT_MODE_2) {
loc.country = readString(readLong3());
ipFile.seek(countryOffset + 4);
} else
loc.country = readString(countryOffset);
// 读取地区标志
loc.area = readArea(ipFile.getFilePointer());
} else if(b == REDIRECT_MODE_2) {
loc.country = readString(readLong3());
loc.area = readArea(offset + 8);
} else {
loc.country = readString(ipFile.getFilePointer() - 1);
loc.area = readArea(ipFile.getFilePointer());
}
return loc;
} catch (IOException e) {
return null;
}
}
/** *//**
* 从offset偏移开始解析后面的字节,读出一个地区名
* @param offset 地区记录的起始偏移
* @return 地区名字符串
* @throws IOException 地区名字符串
*/
private String readArea(long offset) throws IOException {
ipFile.seek(offset);
byte b = ipFile.readByte();
if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {
long areaOffset = readLong3(offset + 1);
if(areaOffset == 0)
return LumaQQ.getString("unknown.area");
else
return readString(areaOffset);
} else
return readString(offset);
}
/** *//**
* 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法
* 用了这么一个函数来做转换
* @param offset 整数的起始偏移
* @return 读取的long值,返回-1表示读取文件失败
*/
private long readLong3(long offset) {
long ret = 0;
try {
ipFile.seek(offset);
ipFile.readFully(b3);
ret |= (b3[0] & 0xFF);
ret |= ((b3[1] << 8) & 0xFF00);
ret |= ((b3[2] << 16) & 0xFF0000);
return ret;
} catch (IOException e) {
return -1;
}
}
/** *//**
* 从当前位置读取3个字节转换成long
* @return 读取的long值,返回-1表示读取文件失败
*/
private long readLong3() {
long ret = 0;
try {
ipFile.readFully(b3);
ret |= (b3[0] & 0xFF);
ret |= ((b3[1] << 8) & 0xFF00);
ret |= ((b3[2] << 16) & 0xFF0000);
return ret;
} catch (IOException e) {
return -1;
}
}
/** *//**
* 从offset偏移处读取一个以0结束的字符串
* @param offset 字符串起始偏移
* @return 读取的字符串,出错返回空字符串
*/
private String readString(long offset) {
try {
ipFile.seek(offset);
int i;
for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte());
if(i != 0)
return Utils.getString(buf, 0, i, "GBK");
} catch (IOException e) {
log.error(e.getMessage());
}
return "";
}
代码并不复杂,getIPLocation是主要方法,它检查国家记录格式,并针对字符串形式,模式1,模式2采用不同的代码,readArea则相对简单,因为只有字符串和重定向两种情况需要处理。
总结
纯真IP数据库的结构使得查找IP简单迅速,不过你想要编辑它却是比较麻烦的,我想应该需要专门的工具来生成QQWry.dat文件,由于其文件格式的限制,你要直接添加IP记录就不容易了。不过,能查到IP已经很开心了,希望纯真记录越来越多~。
LumaQQ is a Java QQ client which has a reusablepure Java core and SWT-based GUI
微软已经开始正式对系统更新的使用者强制检查其所使用的 XP系统是否为盗版的检查,若发现盗版,系统自动更新后,每次启动都提示"您可能是软件盗版的受害者",进入桌面后右下角启动栏里有一个五角星的图案.
相关报道:
即日起微软将启动「正版Windows客户权益独享计划1.0 」 (Windows Genuine Advantage 1.0 , WGA 1.0),用户进入下载中心必须验证是否正版软件,若为盗版软件则不能下载,但是自动更新(Automatic updates)不在此限,所有的用户都可以自动更新修补漏洞。用户在第一次进入Windows update、Microsoft update及Download Center网站进行下载时,将被要求参与WGA验证程序。客户须要下载一个ActiveX控制程序,此程序将自动验证用户计算机所使用的是否为正版 Windows软件。若通过验证,将会有一组Download Key储存在客户计算机中,作为将来进入微软网站进行下载动作时计算机自动辨识之用,微软表示,此验证流程不搜集任何可用来辨认或联络使用者的信息。验证流程已经改善并简化。验证程序将自动验证用户计算机所使用的是否为正版Windows软件,用户不再需键入25码的产品序号,就可以轻松的完成验证过程,大幅降低客户进行验证时的负担。换句话说使用盗版Windows操作系统的用户,将不能直接从下载中心下载Windows Media Player、Antispy等软件,但是依然可以透过自动更新来修补漏洞。
手工解决方法:
方法一:
1.IE => 工具=>Internet 选项 => 程序=> 管理加载项=>IE已经使用的加载项=>Windows Genuine Advantage Validation Tool=>禁用=>然后再上线更新,这样就不会验证了。此WGA(Windows Genuine Advantage Notifications)组件对应文件:legitchechcontrol.dll。WGA是反盗版自动更新验证ActiveX组件,这个正版检验程序植入系统后,会在每次系统启动时,透过网络回报讯息给微软的网站。没更新过的计算机里没有此组件,So..先去update下载此组件后再用此方法去停用此组件,以后即可自动更新了。
2、移除验证码:
计算机=>控制面板=>添加或删除程序=>Windows Installer 3.1(KB893803)=>删除=>重新下载更新档=>windows genuine advantage 不要勾选=>完成。保证不会再出现了(Windows Installer 3.1是修复windows installer安装错误或者提示无法修复MSI出错的微软官方补丁)
3.重启你的计算机,进入安全模式,登陆管理员帐号--保证你拥有进入Windows\system32以及Windows\system32\dllcache路径的权限。在这两个路径下均有一个名为WGATray.EXE的文件。你必须将两个路径下的WGATray.EXE均删除--这将阻止Windows在启动时弹出“盗版XP”气球。
在删除Windows\system32下的WGATray.EXE可能需要一点技巧:
步奏一:WGATray.EXE图标-->右键-->删除-->出现删除确认框后先不要按‘确定’按钮
步奏二:Ctrl+Alt+Del-->打开进程管理器-->找到WGATray.EXE-->删除-->出现删除确认框后先不要按‘确定’按钮
步奏三:先按打开进程管理器的删除确认键,然后用最快速度按步奏一的删除确认键。
步奏四:如果Windows\system32下的WGATray.EXE不会出现,代表操作成功。继续删除Windows\system32\dllcache下的WGATray.EXE。
4.进入注册表,进入
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Winlogon\Notify
删除其中的“WGALOGON”文件夹--这将Windows将无法加载任何有关WGA的DLL文件。
重启计算机,你将暂时告别微软正版增值计划的“骚扰”。
方法二:
在进入Window update 网页后选择「Express(快速)」或「Custom(自订)」按钮前,在 IE 地址栏输入:
void(window.g_sDisableWGACheck='all')
再按下ENTER后,再选取你要更新的方法"快速","自订"即可
此法可以用来破解微软反盗版的检查。系统就不会查你是用什么版本的WIN了
能保证系统更新还是很有必要的。系统更新的目的多数就是防止电脑病毒的需要。
对于使用VOL免激活版(上海政府、xx银行等)的用户,可以使用替换序列号的简单办法。因为目前尚有部分VOL序列号未被封,并且可以通过正版验证。
HCQ9D-TVCWX-X9QRG-J4B2Y-GR2TT (番茄花园版就是使用的这个序列号)
MRX3F-47B9T-2487J-KWKMF-RPWBY(工行版)
QC986-27D34-6M3TY-JJXP9-TBGMD(台湾交大学生版)
点击下载此文件
替换后请到C:\Documents and Settings\All Users\Application Data下删除Windows Genuine Advantage文件夹。以后即可通过正版验证。
如果XP是很老的破解版本(比如零售版的破解版本)用户,无法使用上述序列号,请下载RemoveWGA工具。RemoveWGA 是一个专门用来清除 WGA 的小程序,使用者只要通过它,就可以阻止微软的 WGA 在系统每次启动时企图连回微软网站的动作,且使用它并不需要担心微软的正版验证机制,你还是一样能够正常的使用 Windows update 的功能,两者之间并没有任何冲突。虽然系统仍是破解的,但将不会再有任何提示,和以前没有变化。注:该工具会被部分杀毒软件识别为病毒。如须使用,下载前可能需要暂时禁用或者关闭一下病毒防火墙。使用过后再恢复。
华军下载地址:http://www.onlinedown.net/soft/6914.htm
2000/XP/2003 下 IIS+PHP+MySQL+Zend Optimizer+GD库+phpMyAdmin安装配置
(假设 C:\ 为你现在所使用操作系统的系统盘,如果你目前操作系统不是安装在 C:\ ,请自行修改。)
一、安装 IIS(Windows2003只要安装“应用程序服务器(IIS,ASP.NET)”即可跳过此步):
(1)插入系统盘;
(2)进入控制面板-》添加或删除程序-》添加/删除Windows组件-》在“Internet信息服务(IIS)打勾-》下一步安装。
(3)安装完成后在浏览器地址栏输入 htt://127.0.0.1 检查是否安装正常。
如果没有系统盘,那么下载 IIS 5.1 的安装包(for XP),重复上述步骤,安装时指定路径即可。
IIS下载地址:http://soft.mumayi.net/downinfo/2597.html
二、安装 PHP:
(1)官方下载:http://www.php.net/downloads.php。
(2)下载后得到 php 的 zip 压缩包,解压至 C:\php(这个路径可以随意,下面要是用到这个路径,请相应修改);
(3)将 C:\php\libmysql.dll 和 C:\php\ext\php_mysql.dll 复制到 C:\Windows\system32 下;
将 C:\php\php.ini-recommended 复制到 C:\Windows (Windows 2000 下为 C:\WINNT)并将改名为php.ini,
然后用记事本打开,利用记事本的查找功能搜索:
搜索“; Windows Extensions”并仅打开需要的模块以节省内存(去掉每个模块前的;号即可):
extension=php_gd2.dll
GD库支持,支持水印。
extension=php_mbstring.dll
支持phpMyAdmin
extension=php_mysql.dll
支持MySQL。
(4)配置 IIS 使其支持 PHP
在“控制面板”的“管理工具”中选择“Internet 服务管理器”,
然后在左侧选择你需要支持 PHP 的 Web 站点上单击右键选择“属性”,
在打开的“Web 站点属性”窗口的“ISAPI 筛选器”标签里找到并点击“添加”按钮,在弹出的“筛选器属性”窗口中的“筛选器名称”栏中输入:PHP,再将可执行文件指向 php5isapi.dll 所在路径,如:C:\php\php5isapi.dll。
打开“Web 站点属性”窗口的“主目录”标签,找到并点击“配置”按钮,在弹出的“应用程序配置”窗口中找到并点击“添加”按钮,在弹出的窗口中新增一个扩展名映射,扩展名为 .php ,单击“浏览”将可执行文件指向 php5isapi.dll 所在路径,如:C:\php\php5isapi.dll,然后一路确定即可。
再打开“Web 站点属性”窗口的“文档”标签,找到并点击“添加”按钮,向默认的 Web 站点启动文档列表中添加 index.php 项。您可以将 index.php 升到最高优先级,这样,访问站点时就会首先自动寻找并打开 index.php 文档。
确定 Web 目录的应用程序设置和执行许可中选择为纯脚本,然后关闭 Internet 信息服务管理器。
2003系统配置:在“Internet 服务管理器”左边的“WEB服务扩展”中设置:
ISAPI 扩展允许
开始-》运行-》输入“iisreset”,重启IIS。
在 IIS 根目录下新建一个 phpinfo.php,内容如下:
<?php
phpinfo();
?>
打开浏览器,输入:http://127.0.0.1/phpinfo.php,将显示当前服务器所支持 PHP 的全部信息。
三、安装 MySQL:
(1)官方下载:http://dev.mysql.com/downloads/mysql/5.2.html
custom
安装
Skip Sign-Up
Finish
Next
Standard Comfiguration
设置数据库密码
(重要)重新启动电脑!
四、安装 Zend Optimizer :(有需要时安装)
(1)官方下载:http://www.zend.com/products/zend_optimizer
(2)安装时只需选择Web服务器为iis,指定php.ini路径即可。
五、安装 phpMyAdmin:(用于管理MYSQL数据库,可选择)
下载 http://www.phpmyadmin.net,将其解压到站点根目录,
找到 ./libraties/config.default.php,做以下修改:
方案一:
搜索 $cfg['Servers'][$i]['auth_type'] 设置为config
搜索 $cfg['Servers'][$i]['user'] 用户名一般为root
搜索 $cfg['Servers'][$i]['password'] 设置MySQL的密码
打开浏览器,输入:http://127.0.0.1/phpMyAdmin/ ,若 IIS 和 MySQL 均已启动,即可浏览数据库内容。
(此设置允许任何人通过以上路径登录管理数据库,安全性极低)
方案二:
搜索 $cfg['Servers'][$i]['auth_type'] 设置为cookie
搜索 $cfg['Servers'][$i]['user'] 用户名一般为root
搜索 $cfg['PmaAbsoluteUri'] 访问路径,设置为http://127.0.0.1/phpmyadmin/
搜索 $cfg['blowfish_secret'] 这是cookie作用域,设置为127.0.0.1
(此设置只允许本机上登录,并且需要输入用户名密码才能登录,安全性极高)
-----------------------------------------------------------------------------------
至此所有安装完毕。以上配置在所有老版本软件中不一定有效,强烈推荐下载最新版本的软件。
简单安装过程:请各位注意安装顺序
----------------------------------------------------
(一)Apache:
装在默认目录。安装-自定义-全部选中。
----------------------------------------------------
(二)PHP:
第一步:压缩C:\php5目录下。
第二步:(将PHP目录添加到PATH环境变量中)开始 -> 控制面板 -> 系统 -> 高级 -> 环境变量 -> 系统变量 -> Path -> 双击 -> 加入“;C:\php5”
第三步:将C:\php5\php.ini-recommended 重命名为 php.ini 。
用记事本打开 php.ini ,查找register_globals = Off,把off改成On (有二处)
查找short_open_tag = Off,把off改成On 有一处
查找extension_dir = "./" 改为 extension_dir = "C:\php5\ext"
查找;extension=php_mbstring.dll,把下面几句前面的分号去掉
extension=php_mbstring.dll
extension=php_dba.dll
extension=php_dbase.dll
extension=php_filepro.dll
extension=php_gd2.dll
extension=php_imap.dll
extension=php_ldap.dll
extension=php_mysql.dll
修改了一些文件上传以及内存使用最大限制:
memory_limit = 20M 内存容量
post_max_size = 20M 闪存容量
upload_max_filesize = 20M 附件容量
保存退出。
第四步:(使php.ini文件在Windows下被PHP所用)
开始 -> 控制面板 -> 系统 -> 高级 -> 环境变量 -> 系统变量 -> 新建 -> 输入“PHPRC” -> 在“变量值”中输入 php.ini文件所在的目录(例如:C:\php5)
点击“确定”
Microsoft Windows 下的 Apache 2.0.x
先停止Apache
用记事本打开C:\Apache2\conf(可能在C:\Program Files\Apache Group\Apache2\conf)下的httpd.conf
这个文件我改了几个地方:
把PHP直接作为apache的一个模块运行,我在里面加了两句:
查找到 #LoadModule ssl_module modules/mod_ssl.so 在它的下面一行加上
LoadModule php5_module "C:/PHP5/php5apache2.dll"
AddType application/x-httpd-php .php 注意点前面有个空格滴,大家最好用复制粘贴
(新版apache2.2的php5apache2_2.dll在外面,放上去同理用)
我在D盘下建立了一个www的目录用于存放我的站点文件,在http.conf里改了这样一句话:
把
DocumentRoot "C:/Program Files/Apache Group/Apache2/htdocs"
改成了
DocumentRoot "D:/www"
所有
Apache2.0.x于1.3.x相比2.0.x默认不能直接列目录,如需,就改:
把DocumentRoot "E:/www"这句下的如下语句
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
改为:
<Directory />
Options Indexes FollowSymLinks
AllowOverride None
</Directory>
修改目录的默认首页:
如:DirectoryIndex index.htm index.html index.php index.html index.html.var
别的就没再动什么地方,保存后退出。
注意一点的是,要使以上所有这些配置生效需要重新启动电脑
最后我们来测试PHP是否正常载入。打开记事本,输入
<?php
phpinfo();
?>
保存,在弹出的窗口中,文件类型选所有文件,文件名为phpinfo.php,保存位置为网站目录下。如(E:\www\phpinfo.php)
打开IE浏览器输入:http://localhost/phpinfo.php 如果能正常显示就OK了。
-------------------------------------------------------
(三)Zend:
先停止Apache
安装Zend Optimizer也很简单,安装中它会两处提示其一:选择那种服务器?请选择Apache。其二:PHP.ini的路径?请选择C:\PHP5。其它的按默认配置安装就行了。OK安装完后我的Zend目录就是C:\Zend。
-------------------------------------------------------
(四)Mysql:
安装ASP.NET AJAX
安装Toolkit:
下载解压AjaxControlToolkit.zip移至
C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\AjaxControlToolkit
安装模板C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\AjaxControlToolkit\AjaxControlExtender\AjaxControlExtender.vsi
若安装失败,手工建立以下文件夹:
\My Documents\Visual Studio 2005\Templates\ItemTemplates\Visual C#
\My Documents\Visual Studio 2005\Templates\ItemTemplates\Visual Basic
\My Documents\Visual Studio 2005\Templates\ProjectTemplates\Visual C#
\My Documents\Visual Studio 2005\Templates\ProjectTemplates\Visual Basic
把SampleWebSite\Bin下的AjaxControlToolkit.dll和AjaxControlToolkit.pdb
复制至Binaries下。
打开VS,添加选项卡AjaxControlToolkit,添加项浏览至Binaries\AjaxControlToolkit.dll
CatalogEdit.aspx
<asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1" DataTextField="catalog" DataValueField="ID" Rows="10"></asp:ListBox> <asp:LinkButton ID="LinkButton_up" runat="server" OnClick="LinkButton_up_Click">上移</asp:LinkButton> <asp:LinkButton ID="LinkButton_down" runat="server" OnClick="LinkButton_down_Click">下移</asp:LinkButton> <asp:LinkButton ID="LinkButton_del" runat="server" OnClick="LinkButton_del_Click">删除</asp:LinkButton> <asp:Label ID="Label_alert" runat="server" ForeColor="Red" Text="Label"></asp:Label><br /> <asp:TextBox ID="TextBox_newCatalog" runat="server"></asp:TextBox> <asp:LinkButton ID="LinkButton_add" runat="server" OnClick="LinkButton_add_Click">新增</asp:LinkButton> <asp:LinkButton ID="LinkButton_edit" runat="server" OnClick="LinkButton_edit_Click">修改</asp:LinkButton> <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionStringLink %>" DeleteCommand="DELETE FROM [xLinkCategory] WHERE [ID] = ?" InsertCommand="INSERT INTO [xLinkCategory] ([username],[catalog],[order]) VALUES (?,?,0)" ProviderName="<%$ ConnectionStrings:ConnectionStringLink.ProviderName %>" SelectCommand="SELECT [ID], [catalog] FROM [xLinkCategory] WHERE ([username] = ?) ORDER BY [order],[ID] desc" UpdateCommand="UPDATE [xLinkCategory] SET [catalog] = ? WHERE [ID] = ?"> <DeleteParameters> <asp:Parameter Name="ID" Type="Int32" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="catalog" Type="String" /> <asp:Parameter Name="ID" Type="Int32" /> </UpdateParameters> <SelectParameters> <asp:SessionParameter Name="username" SessionField="username" Type="String" /> </SelectParameters> <InsertParameters> <asp:SessionParameter Name="username" SessionField="username" Type="String" /> <asp:Parameter Name="catalog" Type="String" /> </InsertParameters> </asp:SqlDataSource>
CatalogEdit.aspx.cs
/**//// <summary> /// 上移 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void LinkButton_up_Click(object sender, EventArgs e) { if (ListBox1.SelectedItem != null && ListBox1.SelectedIndex > 0) { string tempValue = ListBox1.SelectedValue; string tempText = ListBox1.SelectedItem.Text; int index = ListBox1.SelectedIndex; ListBox1.SelectedItem.Value = ListBox1.Items[index - 1].Value; ListBox1.SelectedItem.Text = ListBox1.Items[index - 1].Text; ListBox1.Items[index - 1].Value = tempValue; ListBox1.Items[index - 1].Text = tempText; ListBox1.SelectedIndex--; operateDB_move(); } } /**//// <summary> /// 下移 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void LinkButton_down_Click(object sender, EventArgs e) { if (ListBox1.SelectedItem != null && ListBox1.SelectedIndex < ListBox1.Items.Count - 1) { string tempValue = ListBox1.SelectedValue; string tempText = ListBox1.SelectedItem.Text; int index = ListBox1.SelectedIndex; ListBox1.SelectedItem.Value = ListBox1.Items[index + 1].Value; ListBox1.SelectedItem.Text = ListBox1.Items[index + 1].Text; ListBox1.Items[index + 1].Value = tempValue; ListBox1.Items[index + 1].Text = tempText; ListBox1.SelectedIndex++; operateDB_move(); } } /**//// <summary> /// 添加 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void LinkButton_add_Click(object sender, EventArgs e) { if (TextBox_newCatalog.Text != "") { //检查同名 bool goon = true; foreach (ListItem li in ListBox1.Items) { if (li.Text == TextBox_newCatalog.Text) { goon = false; } } if (goon) { //操作 SqlDataSource1.InsertParameters["catalog"].DefaultValue = TextBox_newCatalog.Text; SqlDataSource1.Insert(); //设置selected if (ListBox1.Items.Count > 0) { ListBox1.SelectedIndex = 0; } } else { Label_alert.Text = "已存在"; } } } /**//// <summary> /// 修改 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void LinkButton_edit_Click(object sender, EventArgs e) { if (TextBox_newCatalog.Text != "" && ListBox1.SelectedItem != null) { int index = ListBox1.SelectedIndex; //检查同名 bool goon = true; foreach (ListItem li in ListBox1.Items) { if (li.Text == TextBox_newCatalog.Text) { goon = false; } } if (goon) { //操作 SqlDataSource1.UpdateParameters["catalog"].DefaultValue = TextBox_newCatalog.Text; SqlDataSource1.UpdateParameters["ID"].DefaultValue = ListBox1.SelectedItem.Value; SqlDataSource1.Update(); //设置selected ListBox1.SelectedIndex = index; } else { Label_alert.Text = "已存在"; } } } /**//// <summary> /// 删除 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void LinkButton_del_Click(object sender, EventArgs e) { if (ListBox1.SelectedItem != null) { int index = ListBox1.SelectedIndex; int count = ListBox1.Items.Count; //检查该 Catalog 下是否有 Link bool goon = true; goon = !checkSubLink(ListBox1.SelectedValue); //下有 Link 则返回真,注意前面有个"!",有则不继续 if (goon) { //操作 SqlDataSource1.DeleteParameters["ID"].DefaultValue = ListBox1.SelectedItem.Value; SqlDataSource1.Delete(); //设置selected if (index < count - 1) //删的不是最末项 { ListBox1.SelectedIndex = index; } if (index == count - 1 && index != 0) //删的是最末项,并且不是最首项 { ListBox1.SelectedIndex = index - 1; } if (index == count - 1 && index == 0) //删的是最末项,并且是最首项 { //不设索引 } } else { Label_alert.Text = "该节点下面存在Link(s),请删除后再删除该节点!"; } } }
推荐参考更新的文章:.NET 5 / ASP.NET Core / ASP.NET Frameworks 从纯真 IP 数据库(QQWry.Dat)中查询 IP 归属地
纯真IP数据库官方下载地址:http://www.cz88.net/
之前我写过将 QQWry.Dat 转为 Access 格式后,在 ASP.NET 中通过简单算法去查询,缺点是 IP 数据库体积变大。现在有了更简单的方法:
1,下载 IPLocation.dll (备用地址),将它放在网站的 Bin 目录下,将 QQWry.Dat 放在网站的任何位置(一般为 App_Data 目录)。刷新解决方案管理器。
2,在使用的页面加入引用
using IPLocation;
或者在 web.config 文件中,节点 pages -> controls 添加
<add tagPrefix="IPLocation" namespace="IPLocation" assembly="IPLocation"/>
3,调用方法:
Label1.Text = IPLocation.IPLocation.IPLocate(Server.MapPath("~/App_Data/QQWry.Dat"), TextBox1.Text.Trim());
方法 IPLocate() 参数一为 QQWry.Dat 路径,参数二为 IP 地址,返回 IP 归属地。
够简单吧,补充一点,在 VS2003 需要在项目中添加 IPLocation.dll 的引用。
需要 URLRewriter.dll,它可以在微软官方下载到:取出Bin里面的文件
接下来配置 Web.Config 文件:
在 <configSections> 节点下添加:
<section name="RewriterConfig" type="URLRewriter.Config.RewriterConfigSerializerSectionHandler, URLRewriter" />
在 <configuration> 节点下添加:
<RewriterConfig> <Rules> <RewriterRule> <!--测试--> <LookFor>~/(\d{2})/(\d{2})\.aspx</LookFor> <SendTo>~/Default.aspx?m=$1&d=$2</SendTo> </RewriterRule> </Rules> </RewriterConfig>
在 <httpHandlers> 节点下添加:
<add verb="*" path="*.aspx" type="URLRewriter.RewriterFactoryHandler, URLRewriter" />
完成以上步骤即可以实现URL重写的。但是它只能实现 ~/11/11.aspx 到 ~/Default.aspx?m=11&d=11 的重写,如果要实现 ~/11/11.html 到 ~/Default.aspx?m=11&d=11 的重写,那么需要通过设置 IIS 来实现 .html 文件的筛选。(适合 IIS5 IIS6,在 IIS7 中不用这么麻烦,具体见本文末尾的链接,scott 的文章)
进入:Internet 信息管理服务 -》网站属性 -》主目录 -》配置 -》映射 -》添加 -》把扩展名 .html 映射到 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll。
然后在 <httpHandlers> 节点中,把 *.html 也交给 URLRewriter 处理就行了。
OK,试试看,访问 ~/11/11.html 确实是 ~/Default.aspx?m=11&d=11 的内容,可是只要 postback 一下,地址栏又显示实际路径了。只要在你的/app_browsers文件夹里添加一个.browser文件,注册使用一个控件适配类即可输出新的action属性。
你可在这里查看一个我创建的样例实现,其展示了该如何实现与URL重写协作的表单控件适配器(Form Control Adapter) 。
参考:http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx
译文:http://blog.joycode.com/scottgu/archive/2007/03/01/94004.aspx
许多情况下,asp.net 在 Page_Load 事件中需要动态生成控件,这对于一些新手,包括我在内,会因为回传控件消失,或重复叠加控件等原因搞得头大,经过我翻阅资料和自己的实践,以博客评论页为例,把整个框架写在下面,仅供参考。
<asp:Panel>
<asp:Panel 记录索引 3 >
<asp:Label 昵称 /> <asp:Label 时间 /> <asp:LinkButton 支持按钮_3 /> <asp:LinkButton 删除按钮_3 /> 1楼
</asp:Panel>
<asp:Panel 记录索引 5 >
<asp:Label 昵称 /> <asp:Label 时间 /> <asp:LinkButton 支持按钮_5 /> <asp:LinkButton 删除按钮_5 /> 2楼
</asp:Panel>
</asp:Panel>
记录索引是指数据库中的自动编号。
支持按钮触发事件 LinkButton_support_Click(object sender, EventArgs e)
删除按钮触发事件 LinkButton_del_Click(object sender, EventArgs e)
所有子 Panel 和内部的所有控件都是在 Page_Load 事件中动态创建的,并且不能被嵌套在 if(!IsPostBack)
“点支持累计点击数”和“点删除删除一条记录”都是需要操作数据库的,我以前的做法是操作完数据库后 .Clear() 掉所有控件,再重新生成一次,这样既浪费服务器资源,又容易出现界面上的混乱,譬如显示了两遍该文章的评论。
其实上面代码中 Panel 套 Panel 的好处就是可以根据索引直接删除相应控件,然后直接在页面呈现,而省去上述烦恼。至于累计支持数就更简单了,下面的代码可以轻松解决问题:
string n = ((Label)Panel_commentLists.FindControl("Label_against_" + id)).Text;
n = (Convert.ToInt32(n) + 1).ToString();
((Label)Panel_commentLists.FindControl("Label_against_" + id)).Text = n;
如果有修改和添加评论操作,同样道理,不要重复地全部 .Controls.Add 一次。为了添加时追加一个子 Panel 方便,建议在 Page_Load 的地方传参使用方法来做。
补充:在现在的开发中,我更喜欢用动态生成TableRow,TableCell来操作。