BBS的树形结构一直是大家讨论的话题,以前我做都是利用命名规则来实现,这样的好处是表的冗余字段少,结构清楚,容易理解,但其局限性也很明显。感谢廖家远提供算法(实话说,当年算法就没有学好),我决定采用一下这种算法来实现bbs的树形结构。基本思路如下:
bbs文章表中有这样几个字段:
RootID : 根ID , 新发贴子及其所有子贴都相同。
FatherID: 父ID , 父贴子ID
Layer: 层数 , 贴子在树中的深度。
OrderNum: 排序基数,关键所在,根据它来排序。
基本算法举例如下:
根16(拿个小的举例)
id ordernum Layer
1 16 0
2 16+16/2 1 回复第1贴
3 16+16/(2^2) 1 回复第1贴
4 16+16/2+16/(2^3) 2 回复第2贴
5 16+16/(2^2)+16/(2^4) 2 回复第3贴
然后,根据排序的结果是(加上回复的深度,就成了树状结构)
id ordernum 深度
1 16 0
3 16+16/(2^2) 1
5 16+16/(2^2)+16/(2^4) 2
2 16+16/2 1
4 16+16/2+16/(2^3) 2
成了这样的树:
1
3
5
2
4
根据以上思路,我们设计表如下:
/*BBS文章表*/
if exists (select * from sysobjects where ID = object_id("BBS"))
drop table BBS
go
create table BBS
(
ID int primary key identity not null ,
RootID int default 0 not null ,
FatherID int default 0 not null ,
Layer tinyint default 0 not null ,
ForumID int default 0 not null ,
UserID int default 0 not null ,
Title varchar(255) default "" not null ,
Content text default "" ,
PostTime datetime default getdate() not null ,
FaceID tinyint default 0 not null ,
TotalChilds int default 0 not null ,
OrderNum float default power(2,30) not null ,
Hits int default 0 not null ,
selected bit default 0 not null ,
closed bit default 0 not null ,
IfEmail bit default 0 not null ,
IfSignature bit default 0 not null
)
go
/*BBS注册用户表*/
if exists(select * from sysobjects where ID = object_id("BBSUser"))
drop table BBSUser
go
create table BBSUser
(
ID int Primary key identity not null ,
UserName varchar(20) default "" not null ,
Password varchar(10) default "" not null ,
UserType tinyint default 0 not null , --用户类型,1为斑竹
Email varchar(100) default "" not null ,
HomePage varchar(100) default "" not null ,
ICQ varchar(20) default "" not null ,
Signature varchar(255) default "" not null , --签名
Point int default 0 not null , --用户积分
)
go
表结构定了,剩下的就是怎样实现。我把所有相关的功能集成在一个存储过程中,包括贴子本身的存储,其关键是排序基数的生成;父贴子相关字段的更新 ; 根贴相关字段的更新,这些都放到一个事务中,以保持数据的一致性,另外如果父贴要求有回复用email通知,在存储过程中实现发回复email的功能,而不必借助任何asp或其他的组件。这样就使所有任务在一个存储过程中实现。
--------------------------------------------------------------------------------
下面是上篇文章所说的存储过程,其作用已经说过,在这里就不再赘述了。请大家自己看代码吧。这个存储过程只是存储数据的过程,以后如果有时间我将讲一下读取数据。
/**********************************************************************/
/* */
/* Stored Procudure : up_PostTopic */
/* */
/* Description: 贴子存储及回复Email */
/* */
/* Author: Bigeagle */
/* */
/* date: 2000/7/25 凌晨 */
/* */
/* History: version 1.0 by BigEagle , 2000/7/25 */
/* */
/**********************************************************************/
if exists (select * from sysobjects where id = object_id("up_PostTopic"))
drop proc up_PostTopic
go
create proc up_PostTopic @a_intID int OUTPUT ,
@a_intFatherID int , @a_intForumID int , @a_intUserID int ,
@a_strTitle varchar(255) , @a_strContent text , @a_intFaceID tinyint ,
@a_bIfEmail bit , @a_bIfSignature bit
as
declare @m_intTopicID int
declare @m_intLayer tinyint
declare @m_intRootID int
declare @m_fOrderNum float
select @m_fOrderNum = power(2 , 30) --初始化排序基数
/*首先判断是否有这个论坛,没有则退出*/
if not exists (select * from BBSCategory where CategoryID = @a_intForumID)
begin
select @a_intID = 0
return(0)
end
/*判断是新发贴子还是回应主题*/
if @a_intFatherID = 0 --没有父贴子,说明是新发贴子
select @m_intLayer = 1 , @m_intRootID = 0
else
begin
if not exists(select * from BBS where ID = @a_intFatherID) --如果没发现父贴子
begin
select 'TopicID' = 0
return (0)
end
else --如果发现父贴子,则取出层数和根ID
select @m_intLayer = Layer + 1 ,@m_intRootID = RootID ,@m_fOrderNum = OrderNum
from BBS where ID = @a_intFatherID
end
/*更新表,因为要对多个表操作,所以放到事务里*/
begin transaction
/*插入表BBS*/
insert into BBS (FatherID , Layer , ForumID , UserID , Title ,
Content , PostTime , FaceID , Hits , selected ,
closed , IfEmail , IfSignature , OrderNum)
values(@a_intFatherID , @m_intLayer , @a_intForumID , @a_intUserID , @a_strTitle ,
@a_strContent , getdate() , @a_intFaceID , 0 , 0 ,
0 , @a_bIfEmail , @a_bIfSignature , default)
if (@@error <> 0) goto On_Error --如果出错转向错误处理部分
select @m_intTopicID = @@identity --取出刚刚插入纪录的ID
/*如果是新发贴子则取ID为RootID*/
if @m_intRootID = 0 --新发贴子
begin
select @m_intRootID = @m_intTopicID
end
else --不是新发贴子则更新根纪录的TotalCounts
begin
update BBS set TotalChilds = TotalChilds + 1 --更新根的子贴数
where ID = @m_intRootID
if (@@error <> 0) goto On_Error --如果更新失败则转向错误处理部分
end
select @m_fOrderNum = @m_fOrderNum + power(2,30)/power(2,TotalChilds)
from BBS where ID = @m_intRootID
select @m_fOrderNum
/*更新RootID , OrderNum*/
update BBS set OrderNum = @m_fOrderNum , RootID = @m_intRootID
where ID = @m_intTopicID
if (@@error <> 0) goto On_Error --如果更新失败则转向错误处理部分
/*更新BBSCategory表*/
update BBSCategory set TopicCounts = TopicCounts + 1 , LastReplyTime = getDate()
where CategoryID = @a_intForumID
if (@@error <>0) goto On_Error --如果更新失败则转向错误处理部分
/*更新BBSUser表,将用户分数加一*/
update BBSUser set Point = Point + 1
where ID = @a_intUserID
/*如果全部成功则完成事务*/
commit transaction
/*如果要求回复则发邮件*/
declare @m_strEmail varchar(100) , @m_bIfEmail bit
declare @m_strName varchar(20) , @m_strSubject varchar(50)
declare @m_strMessage varchar(255)
select @m_bIfEmail = a.IfEmail , @m_strEmail = IsNull(b.Email , ""),
@m_strName = b.UserName
from BBS as a
left join BBSUser as b on a.UserID = b.ID
where a.ID = @a_intFatherID
select @m_strSubject = "来自eMatter Board : 您有回复"
select @m_strMessage = "您发表在eMatter Board的贴子现在有人回复:"
+ " http://server1/bbs/showtopic.asp?ID="
+ convert(varchar,@a_intFatherID)
if @m_StrEmail <> "" and @m_bIfEmail = 1
exec master..xp_sendmail @recipients = @m_strEmail , @subject = @m_strSubject,
@message = @m_strMessage
select @a_intID = @m_intTopicID --返回贴子ID
return (0)
On_error: --错误处理部分
rollback transaction
select @a_intID = 0 --贴子ID返回0,代表失败
return (-1)
go