学用ASP制作自己的论坛

2005-6-18 admin 分享
1. ASP语法基础

1.1 VBScript语法简介


由于本文主要使用VBScript编写ASP脚本,因此在这一节中主要介绍VBScript的一些简单语法,这些语句对于编写一个简单的论坛来说已经足以胜任了。如果你了解VB的语法就请跳过这一节,因为本节涉及的语法决不会比你所学的更多;如果你曾经学习过任一种语言,那么本节只需简单的浏览即可,因为下面这些VBScript语法与你所学的语言中相应语法是类似的;如果你是个初学者,不要担心,VBScript是只要认字就能理解的简单工具。

(1)条件判断语句IF:
句法:if [条件式1] then
[语句块1]
elseif [条件式2] then
[语句块2]
......(n次)
else
[语句块n+1]
end if

BASIC是相当易用的,我们从它的IF语句就很容易看出这一点。如同其它任何一种语言,条件式中主要是对于表达式值的判断,主要包括相等(=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)和不等(<>),对于BOOL型的变量,判断为真(TRUE)只要直接用[变量名]作为判断式即可,为假(FALSE)只要使用[not 变量名]进行判断。不同的判断条件之间可以使用and/or连接,and类似于C语言中的&&,表示同时满足and左右两边的条件;or类似于||,表示或者满足or左侧的条件或满足其右侧的条件均可视为判断成立。对于各种判断符号的使用我们将在后面的实际应用中进一步讲解。
elseif语句在其它许多种语言中是不存在的(如被广泛使用的C语言),elseif的作用正如其字面的意义,是在所有不满足上一次判断要求的剩余情况中进行进一步的分类判断,它所使用的判断和if是一致的。elseif的使用虽然感觉上很像switch语句,但在实际的使用中还是能够感觉到一些不同的,在后面遇到使用它的时候再详细地介绍。
else的作用于其它语言中的else一样,表示对于不满足判断条件的情况进行的处理(如果使用了elseif就表示对所有判断都不满足时所进行的处理)。
end if标志着一套if语句的结束,如果忘记了end if(这是初学者常犯的毛病,尤其是当你比较熟悉C那样简练的语言的时候)往往会使ASP解析失败,并且返回错误的错误报告(也就是报告的错误位置并不存在任何错误)。如果if语句能在一行之内结束就可以省略掉end if语句,但每一个if都配套一个end if不失为一种良好的习惯。

(2)do while [条件式]
[语句块]
loop
do while是VBScript的一种循环语句,另一种与之类似的语句是while...wend,二者几乎没有区别。do while 所使用的条件式与if语句是一样的,其运行效果是在条件式成立的情况下反复顺序执行do while和loop之间的语句块。使用do while语句时一定要注意避免死循环的出现,这一条对于所有的循环语句都是适用的。

(3)select case [变量名]
case [变量取值]:
[语句块1]
......(n次)
case else:
[语句块n+1]
end select
该语句的作用和用法与普通C语言的switch语句是一样的,对指定变量的取值进行判断,不同的取值执行相应的case下的语句块,case else相当于default,其中的语句块用来处理不满足所有case的情况,select的结束由end select标示。

(4)for [步进变量] to [目标步数] (step [步长] ’默认为1)
[语句块]
next
for是另外一类循环语句,功能是将语句块执行[目标步数]/([步进变量]*[步长])次,下面我来举个例子。for i=1 to 16,该语句表示先赋予步进变量i初值1,之后每循环一次i自动增加一个步长(本例中没有输入步长表示使用默认值),直到i值超过16退出循环,即循环16次;如果将其改为for i=1 to 16 step 2,循环次数将减少为8次。

(5)dim [变量名]
VBScript使用dim关键字来声明变量,已被声明的变量可以作为任何类型的变量来使用(对于使用像C那样严格区分变量类型的语言的人来说这实在是一大福音)。切记,任何变量一定要保证在使用之前进行了声明,并且除非在循环中,否则重复声明一个变量也是非法的。

(6)字符串拼接操作符&
&的执行结果是将其左右的两个字符串拼接成一个,这将是我们常用的一个操作符。

至此本文中使用的一些主要VBScript语法就介绍完了,好像种类很少的样子,但ASP本身就是与HTML联合使用的(在ASP.net中略有变化,详情请参考本站相关文章),只需要这些基本的VBScript判断和循环语句就可以完成我们的设计了。下面我们来看看SQL语句的使用方法。

1.2 SQL基本语句用法

只要程序设计中与数据库有关就免不了要和SQL语句打交道,这些简单的近似自然语言的句子提供了访问世界上任何一种标准数据库的统一接口,只要安装了数据库驱动我们就能通过编写SQL命令访问相应的数据库读取、添加或删除数据,而不管该类数据库的前端软件是否存在(对于本文中的例子就是即使不安装MS Access也可以访问*.mdb的数据库,但为了能更好的控制整体数据和监视程序对数据库的修改,还是建议读者在编写过程中安装一个Access)。要在ASP中使用SQL语句访问数据库首先要做的是建立通向数据库的连接,这部分的内容我要放在1.3节中再讲,下面我们先一起来学习使用SQL语句。

(1)select [数据内容] from [数据表名称] where [数据项1] like [值1] and/or [数据项2] like [值2] ... order by [数据项] asc/desc
select语句用来从数据库中筛选出所需要的数据,它是SQL语句中使用频率最高的语句,[数据内容]部分表示所要选取的表格中的数据项,使用*表示选取全部。[数据表名称]表示要从哪一个表格中选取,如果你没有接触过数据库可能很难了解什么是数据表格,没关系,我将在后面用到它的时候再说明。where表示选取的条件,使用like表示相等,也支持>=这样的判断符号,同时使用多个条件进行选取时中间要使用and进行连接。order by决定数据的排列顺序,asc表示按照[数据项]中的数据顺序排列,desc表示倒序,默认情况为顺序。select语句中除select和from之外其它均为可选项,如果都不填写表示选取该数据表中的全部数据。

(2)insert into [数据表名称] (数据项1,数据项2,...) values (值1,值2,...)
insert into语句用来添加新的数据到数据库中的指定表。通过(数据项1,数据项2,...) values (值1,值2,...)来为新添加的数据赋初值。

(3)update [数据表名称] set 数据项1=值1,数据项2=值2,... where [数据项1] like [值1] and/or [数据项2] like [值2] ...
该语句可以修改数据库中指定数据表内的指定数据,如果不是用where限定条件就表示修改该表内所有的数据条目。

(4)delete from [数据表名称] where [数据项1] like [值1] and/or [数据项2] like [值2] ...
显然,这一句用来删除指定的数据。

主要使用的SQL语句只有以上的4条,但是如果灵活的运用这四条语句可以实现相当多样的效果,当我们完成本文的例子论坛时你将会对此有更深刻的感受。接下来让我们了解一些关于ASP对象的知识。

1.3 ASP对象简介

建立访问数据库连接的CONNECTION对象
为了与上文衔接,我们先来看看如何建立到数据库的连接对象,这是使用ASP访问数据库的第一步。

首先当然是声明一个变量,例如名为conn,之后如下赋值:
set conn=Server.CreateObject("ADODB.CONNECTION")
该操作的结果是定义conn为ADODB.CONNECTION对象,要使该对象能够管理目的数据库还需要如下操作:
conn.open "DBQ="+server.mappath("bbs.mdb")+";DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};"
server.mappath是调用ASP的server内建对象的mappath方法将目的数据库路径(本例中为bbs.mdb)转换为Server端本地路径。DRIVER指定数据库驱动名,该名称必须与控制面板->管理工具->数据源 (ODBC)中的驱动程序分页里名称段的文字完全一致才能正常工作(本例中以Win2000为操作系统,Win9x直接在控制面板下查找ODBC工具)

照此步骤建立的conn就是一个能够访问Access数据库bbs.mdb的CONNECTION对像了,使用它执行SQL语句的方法是调用conn.execute方法,也可以使用该方法使另一个变量具有直接访问某一数据表格的能力,具体语法是:set 变量名=conn.execute ("数据表格名") ,我们在后面将会看到使用这一语句的实例。

数据对象RECORDSET

虽然CONNECTION对象本身已经能够进行对数据库的访问,但是每次读取数据都通过它调用SQL命令显得有些繁琐,而通过set 变量名=conn.execute ("数据表格名") 创建的数据对象在数据的访问上也存在着严重的缺陷--只能顺序的访问下一条数据的内容。所有的这些不便都会增加对数据库操作的难度,因此为了实现简单的数据库访问我们有了RECORDSET对象类型。

RECORDSET对象的建立方法为:
set 变量名=server.createobject("adodb.recordset")
变量名.open ("选取数据的SQL语句"),conn,1,1 ’conn为已建立的CONNECTION对象
该操作的第三个参数为数据库的打开方式,可以选择的类型如下:

选项 数值 简介
adOpenDynamic 2 动态的数据库打开方式,其他用户所进行的修改、删除和新建等工作都会立即在数据对象中体现出来,并且支持全部类型的数据移动方式,除非提供者不支持,否则也可以进行书签操作
adOpenForwardOnly 0 默认值。只支持向前顺序访问数据,如果只需要顺序遍历全部数据,该方法可以提供较高的执行性能
adOpenKeyset 1 本文中大量使用的方式。无法看到其他用户添加的新数据,但被其他用户删除的数据将变为不可访问,同时可以看到其他用户所作的修改。在其它方面类似于adOpenDynamic
adOpenStatic 3 静态打开方式。在你使用数据是其他用户无法访问该数据
adOpenUnspecified -1 不指定打开方式

第四个参数为锁定方式,具体介绍如下:

选项 数值 简介
adLockBatchOptimistic 4 批量乐观锁定,所有的数据更新工作都不立即执行,直到积累到一定数量时才一次性写入数据库,并且只在此时锁定数据库
adLockOptimistic 3 乐观锁定,只在调用Update方法时锁定数据库
adLockPessimistic 2 悲观锁定,修改数据后立即锁定数据库
adLockReadOnly 1 只读方式,不能对数据进行修改
adLockUnspecified -1 不指定锁定方式,在复制时将复制源数据对象的锁定类型

open还有第五个参数,但现在我们用不到,等以后有机会再来介绍。

要读取RECORDSET对象中的数据是很简单的,只需要使用如下的句法:数据对象名("数据项名")就可以读取相应数据项中的数据。注意这只是简写方式,是由于RECORDSET对象的默认方法就是读取其中数据,因此以后很可能见到使用更复杂的句法获取数据(这种情况几乎不存在),但对于一般使用来说这样的简写方式已经足够了。

RECORDSET对象中数据的移动方式有很多种,下面逐一介绍。movefirst,移动到最头部的数据;movelast,移动到尾部的数据;movenext,移动到下一条数据;moveprevious,移动到上一条数据。更通用的移动可以调用move方法,所有的RECORDSET对象都支持move方法,该方法有两个参数,第一个为从当前数据开始移动的数据个数,如果此数大于0,数据将向后移动(靠近数据串尾部),如果小于0,数据将向前移动(靠近数据串头部);第二个参数可选,为一个用来使用书签查找的字符串。当调用move方法时可能出现越界现象,如果向前移动超出了数据串头部,ADO将设置数据为数据串头部之前一个位置(设置BOF标识为1),超过尾部将设置数据为数据串尾部之后一个位置(设置EOF标识为1),此时再次调用move向前(BOF=1时)或向后(EOF=1时)将出现错误。在本文的例子中我们将经常见到通过判断RECORDSET对象的EOF是否为1来确定是否已到达数据串尾部。

对RECORDSET对象的介绍我们暂时先告一段落,下面让我们看看ASP的一些内建对象,这些内建对象对于我们使用ASP进行开发是极其重要的。

RESPONSE对象

RESPONSE对象是最常用的ASP内建对象之一,我们一般使用它的Write、End和Redirect这三种方法。Write方法负责输出数据到客户端,观察ASP的原文件将有利于我们理解该方法的具体作用,这里就不再多作介绍了。需要注意的是Write方法为RESPONSE对象的默认方法,而RESPONSE对象又为ASP的默认对象,因此我们可以把普通的输出--response.write 输出字符串--改写为<%=输出字符串%>(<%和%>为标示ASP代码开始和结束的特殊字符组合)这样简单的形式。End方法的作用是立即终结网页的输出,本文的例子中我们并没有使用到它。Redirect方法被用来提供页面的重定向功能,它可以将链接重定向到其它的ASP文件,但要注意一定要在任何数据被发送到客户端之前调用此方法,否则Redirect就会失去作用,我们的例子论坛中有许多Redirect的使用实例。如果你需要操作cookies,RESPONSE对象可以提供你一些简单的方法,我们将在后面遇到的时候再介绍。

REQUEST对象

大家在网上浏览那些使用ASP、CGI、PHP等动态页面的网站时一定注意过下面这种页面调用形式:
XXX.asp?aaa=aaa&bbb=bbb&....
?后面的是调用参数,它们都会储存在REQUEST对象中,当我们要使用到它们时可以使用类似RECORDSET对象的调用方式:request("参数名")。另外,REQUEST对象也会存储使用表单(form)提交上来的数据,读取这些数据的方法是:request.form("表单中输入框名")。我们将在例子论坛的编写中逐渐熟悉REQUEST对象的使用方法。

SERVER对象

SERVER对象提供对服务器的一些操作,我们一般只使用它的几种方法。首先是CreateObject方法,使用格式为:Server.CreateObject( progID ),该方法用来建立一些server端对象的实例(在前面我们已经见过使用它的例子了),这是最常用的SERVER方法之一。MapPath方法我们也已经见过了,它的功能就是把相对路径改变为物理路径,这在使用ASP打开server端的文件时是必须的,MapPath的使用方法很简单:Server.MapPath( Path )。当我们使用response.write方法向页面输出html语句时都是直接输出的,也就是说浏览器会将这些语句解释成标准的html语言,而如果我们希望把html代码作为文本的一部分输出时就需要使用HTMLEncode方法,语法是:Server.HTMLEncode( string ),使用该方法后将会自动对string的内容进行转化,使得html代码变为文本输出,而原字符串中的数据不会改变。SERVER对象还有其它多种方法,如果感兴趣可以查看MSDN中关于ASP的部分,这里就不再介绍了。

SESSION对象

SESSION对象是一个特殊的对象,每当有一个用户连接到服务器就会创建一个SESSION对象,SESSION对象的存活时间大约是20分钟,因此我们可以用它来存储每个在线用户的私人信息。SESSION对象的使用是很简单的,比如我们要在SESSION中存储当前用户的用户名,只需要session("username")=用户名即可,其中username是自定义的session内部数据,如果我们使用的session内部数据原来不存在,SESSION将会自动创建该数据,而不用事先声明。要读取SESSION对象的内部数据只需要session("数据名")即可。

现在我们已经把本文中使用的主要ASP对象都介绍过了,从下一章开始我们就要学习如何使用这些已知的知识来编写一个自己的ASP论坛,不用担心,因为这是极为简单的。:)

2.1 确立数据存储格式

由于我们的论坛计划使用数据库存储一切信息,因此必须首先建立一个数据库。第一步是确定都有哪些数据是要存储在数据库中的,一旦决定将某数据存储在数据库中也就表示用户应该有权力修改该数据(当然你完全可以不这么做)。本文仅仅选出最必要的几条作为数据库中保存的数据,实际应用时还要依具体情况定制数据库。下面列出本例所用到的两个数据表字段,括号中为说明和数据类型。

存储分论坛信息的数据表包含如下几个字段:ID(数字自动编号)、论坛名称(文本)、论坛简介(文本)。
存储贴子的数据表包含如下几个字段:ID(数字自动编号)、标题(文本)、内容(备注)、ForumID(所在分论坛编号,数字)、Poster(发表人用户名,文本)、PostTime(发表时间,时间)、IsTopic(该贴是否是新主题,BOOL)、TopicID(当IsTopic为0时该值指出跟贴主题的ID号,整数)、LastTime(最后跟贴时间,时间)。

本文使用Microsoft Access数据库,在此简要介绍一下Access数据库所支持的几种数据类型。
首先是"自动编号"类型,一般实际是长整型数据,可设置其数据变化方式,即递增或递减,该类型的特点是不用在向数据中插入数据时指定其值,系统会自动按设定的变化方式付给该类子段新的数值。
第二类是"文本",即普通的字符串,默认最大长度是50字节(25个汉字),最大可调整为存储255字节。
第三类"备注",它与"文本"类最大的差别就是存储的容量,在Access中"备注"类型的存储没有上限限制。
第四类是"时间",该类型数据专门用来存储时间数据,其数据必须按照特定格式存入,例如:"年-月-日 小时:分钟:秒"。
最后介绍一下BOOL类型,在Access2000中它被称作"是/否"类型,只能接受作为数字输入的0和1两个数值,0表示假,1表示真。

2.2 Connection.con

基于数据库的论坛有一个特点,它的任何一页一般都要打开数据库读取其中的数据以完成数据输出,这就要求每一个ASP页面都要有建立数据库连接的代码段,为了减少重复代码的输入,我们使用一个名为Connection.con的文件来实现conn对象的建立。Connection.con文件类似于C语言中的.h头文件,你可以随便给它起个名字,本文中使用Connection.con。在该文件中写入如下代码:
<%
dim conn,connstr
set conn=Server.CreateObject("ADODB.CONNECTION")
connstr="DBQ="+server.mappath("bbs.mdb")+";DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};"
conn.open connstr
%>
这段代码表明建立了调用
Microsoft Access Driver (*.mdb)数据库驱动,用来存取位于同一目录下的bbs.mdb数据库文件的连接。

需要特别注意的是:这种方法虽然减少了代码编写的麻烦,但是会影响ASP脚本的运行速度,根据实际测试使用包含文件平均增加10%左右的计算量,对于一些较小的工程可能会额外的消耗20%以上的效率。因此,如果是特别需要效率的程序还是应该不使用包含文件。还有一种方法可以用来保存到数据库的连接,这就是利用Application对象。Application对象与Session对象一样是ASP的内建对象,它相当于一个全局变量,与一般变量不同的是它的作用域是整个Web程序,而不是以用户连接为界的,在系统的Web服务开始运行后系统会自动建立一个(并且只有一个)Application对象,并直到服务结束时关闭该对象。在Application对象被建立时会触发OnStart事件,如果此时建立连接并保存在Application对象中就不用在每一页都建立到数据库的连接了(可以通过在Global.asa文件中添加处理Application_OnStart的子函数实现,有关Global.asa文件的方法我们以后有机会再介绍),这是许多人喜欢使用的方法。但是如果网站的访问量较大则这样设计的程序将会遇到瓶颈,因为一般来说对数据库的访问都是在加锁情况下进行的,即同一个Connection对象只能同时被一个用户调用,即使用户要分别访问不同的数据表或数据库也必须等其他用户释放对Connection对象的占用才能进一步操作,这就造成一部分用户被迫等待一段时间,从而降低了程序的整体效率。

2.3 显示首页

首先我们要确定论坛首页所要输出的数据,参考各类已被广泛使用的论坛界面设计,我们可以在首页显示所有分论坛的列表,显示个分论坛中最后发表或恢复的贴子、发贴人、发贴时间,还可以显示分论坛中的主题数。要显示如上所列的信息就先要将所需数据从数据库中取出来,因此论坛的显示事实上就是对数据库数据高校正确的存取操作与HTML代码、脚本操作的结合,而其中正确的取得所需的数据更为重要,也是最需要理解的地方。

首先要做的是声明要用到的变量,并将它们指定为Recordset类型。我们使用变量Forum保存取出的分论坛信息,变量Topic保存贴子主题的信息,变量Reply保存每个主题的回复信息。具体定义代码如下:
<%
dim Forum,Topic,Reply
set Forum=server.createobject("adodb.recordset")
set Topic=server.createobject("adodb.recordset")
set Reply=server.createobject("adodb.recordset")
%>
这段代码中用到的set [变量名]=server.createobject("adodb.recordset")其功能是将变量定义为adodb.recordset类对象,这是ASP中常用的方法,如果您使用过本站KKnD编写的HTTP协议文件上传组件那么对这种定义方式应该并不陌生了。

我们计划按如下样式显示分论坛信息(当然,实际中是要做成更加精致的页面的):

论坛名称
论坛简介
最后主题标题 最后发表人
发表时间 论坛主题数 版主


其中"版主"一项要再以后添加了用户系统之后再作,下面给出获取所需数据的代码,请参照本文上一期中有关Select语句操作的部分。首先是分论坛信息:
Forum.open ("select * from Forum"),conn,1,1
然后是针对每个分论坛取主题信息,由于要得到最后发表的主题,所以使用按时间逆序排序的方法:
Topic.open ("select ID,标题,LastTime from Topic where ForumID like "& Forum("ID") &" and IsTopic=1 order by LastTime desc"),conn,1,1
之后按照所搜到的主题查找最后的回复人:
Reply.open ("select Poster from Topic where TopicID like "& Topic("ID") &" order by ID desc"),conn,1,1
由于本例中ID是按递增方式自动排序,后加入的部分其ID号必大于之前的所有数据,所以我们可以使用上面这句代码找出正确的数据。似乎还缺少论坛主题数和回复数的数据,不过由于Recordset对象的recordcount属性即为该对象中数据的条目数,事实上我们所需要显示的全部信息都已经准备齐全了。下面给出显示代码(假设显示分论坛的页面为同意目录下的ShowForum.asp,显示贴子内容为ShowTopic.asp,传入参数请参考上一期中request对象一节,有关输出部分请参考response对象一节):

<table width="100%">
<%
Forum.open ("select * from Forum"),conn,1,1
If not Forum.eof then
Do while not Forum.eof
%>
<tr><td width="30%">


<%=Forum("论坛简介")%>
</td><td width="35%">
<%
Topic.open ("select ID,标题,LastTime from Topic where ForumID like "& Forum("ID") &" and IsTopic=1 order by LastTime desc"),conn,1,1 ’注意VB Script是以回车判断语句结束的,一句代码中间不要插入回车
If not Topic.eof
Reply.open ("select Poster from Topic where TopicID like "& Topic("ID") &" order by ID desc"),conn,1,1
If not Reply.eof then
%>
</td>
<td width="20%"><%=Reply("Poster")%>
<%=Topic("LastTime")%></td>
<td width="5%"><%=Topic.recordcount%></td>
<%
End If
Else
%>
目前该论坛没有贴子</td><td width="20%">无</td><td width="10%">0</td>
<%
End If
%>
<td width="10%">版主</td>
<%
Topic.close
Reply.close
Forum.MoveNext
loop
Else
%>
<tr><td colspan="5">尚未添加任何子论坛</td></tr>
<%
End If
Forum.close
%>
</table>

在上面的代码中使用了do while循环遍历所有分论坛数据,判断条件为Forum.eof,即Forum数据尾。这里不要忘记一定要在循环中使用Forum.MoveNext方法使Forum指向的数据想后移动一位,否则将无法推出循环。另外,当一个Recordset对象已打开某一数据表,它将不能再被用来打开其他的数据,而必须先通过调用close方法关闭数据连结才能使用。以上两点都是刚开始学习ASP时常犯的错误,提醒大家注意一下。

现在我们基本上完成了首页的显示工作,代码量并不大而且只是一些最简单的对象属性和方法的应用。在下一期中我们将研究如何显示分论坛中贴子数据,分页处理以及制作一个从当前论坛直接跳转到其他论坛的选择框。

继续阅读