本帖最后由 rockage 于 2016-1-10 17:17 编辑
首先,最初的想法是论坛在上次从phpBB迁徙到Discuz X3.1的遗留问题,DZ论坛基于安全理由忽略了html标签,那么就造成了很多帖子原封不动地将html 显示出来而不加以解析,这带来的最要命的结果就是没有空行,因为不解析
标签,所有的内容没有任何空行全部粘成一团,几乎无法阅读。解决这个问题非常简单,只需要将帖子的抽离出来写一个小程序将html标签清除就行了。那么首先我需要在本机搭设一个Apache+MySQL+PHP的组合环境,然后再用VB+ADO+ODBC对MySQL数据库进行编程即可。
这又牵扯了一个新的话题,如果不选择xampp 3 in 1整合包,我就是想独立安装三剑客的话,该怎么做,因为用惯了xampp觉得有些腻味,所以这次想尝试一下新方式。殊不知这一步直接折腾了我一天时间,我的经验整合贴将放在另外的地方,这里就不讨论了。
这一环节流程如下:
1. 首先导入美国服务器的资料,使用MySQL命令行硬导出。指令如下:
登陆美国服务器:mysql -h 103.254.148.137 -u stoneren_rockage -p
dump数据库:mysqldump -h68.178.136.155 -urockage -p stoneren_rockage > d:/backupfile.sql
美国服务器的支持就是好,支持MySQL命令行远程访问,注意最好挂个VPN,否则因掩饰触发Timeout会造成错误。
backupfile.sql则是论坛数据库stoneren_rockage的完整备份。
通过以下指令还原到本机服务器:
mysql -uroot -p --default-character-set=utf8 (默认最高权限用户root)
use rockage; (选择rockage数据库)
source d:\backupfile.sql (导入)
第二步,通过ftp将美国服务器的论坛源文件原封不动地down下来放到本机。
第三步,此刻,数据库导入完成,源文件下载完成,那么我们直接访问localhost即相当于访问原美国主机。
之后我发现本机环境数据完全正常,但问题是奇卡无比,原因未知。迫于无奈又转回了xampp,发现仍然十分卡顿,说明问题不是出在散装或整合装,至今我也没有想明白,但是在奇卡无比的情况下仍然可以操作,接下来的操作如下:
1. 慢速登陆本机论坛
2. 慢速进入后台,进行备份。
这里如果你问我为何要多此一举,因为我们已经dump了完整的数据库备份,何必再用DZ自身的备份功能备份一次呢?因为我尝试用命令行将数据库导入到香港服务器失败,香港服务器貌似不支持命令行远程操作。而明确的是,美国服务器是支持的,所以,用命令行导入导出必须要看目标服务器支不支持,在本机环境能通过的事情放到真实服务器那就未必了,需要谨慎。
慢速备份完所有数据后,删除旧的DZ源码,下载一个全新的DZ X3.1安装版,重新干净安装DZ论坛,安装过程全部按默认处理,新论坛安装完成后,登陆后台,再将老数据还原到新论坛,完成后发现速度非常快,说明工作正常了。
接下来需要将MySQL与VB打通,在MySQL安装包里附有一个mysql_connector_odbc模块,但该模块是32位的不能使用,必须单独下载64位版的connector,然后64位版的下载后仍然不行,因为貌似xampp整合的MySQL又是32位的不支持,我简直快崩溃了,在无数次尝试中,我发现如果按照以下流程操作,可以解决问题,但原因未知,整个流程如下:
1. 安装xampp
2. 进入mysql命令行模式,用source命令导入后缀为.sql的原始数据库
3. 拷贝原始bbs的html文件,修改config设置(主要是数据库地址账户密码这三项,注意有3个地方要改,包括uc-center里面)
4. 进入原始bbs的html文件,这时候会比较卡(原因不明),龟速进入后台,备份全站一次
5. 删除原始bbs的html文件,纯净安装discuz x3.1
6. 初装的x3.1会比较快,进入后台导入之前备份的全站数据,清缓存,再次返回首页就不会卡了
7. 在xampp控制台关闭mysql服务(不关闭这个下一步无法进行)
8. 安装纯净版mysql 5.6.21 for 32位版(官网就只有32位版,装完后机内有两个mysql 5.6.21,一个是xampp捆绑的,另一个是独立常规安装的)
9. 按默认装好后,把c:\xampp\mysql\data下的bbs数据库拷贝到C:\ProgramData\MySQL\MySQL Server 5.6\data(相当于把xampp的mysql数据硬拷贝到新装的mysql下)
10. 安装mysql-connector-odbc-5.3.4-winx64 (win7 64只能装它才能将mysql和odbc打通,之前常规默认安装那个connector-odbc-32位的无效)
11. 控制面板 -> 管理工具 -> 数据源 -> ODBC数据源
12. 添加用户DSN (系统DSN加了也没用),输入密码账号和绑定的数据库,Test验证一下
13. 返回到VB这边,添加一个adodc控件,鼠标右键,ADODC属性
14. 使用连接字符串 -> 生成 -> 选Provider for ODBC Drivers -> 使用数据源名称(选刚才在控制面板里创建那个)->输入账号密码 ->选择起始目录(即初始数据库) -> 测试连接
15. 测试若通过,将这条字符串粘贴复制下来,就可以用在任何需要联通ODBC数据库的地方,否则不用试,这一步过不了其他连接字符串肯定也不行
那么最终,经过反复折腾,这条来之不易的连接字符串为:
Provider=MSDASQL.1;Persist Security Info=False;User ID=root;Data Source=mysql;Initial Catalog=rockage
前提是,在控制面板的数据源那里一定要能看到MySQL的驱动程序,还要添加一个用户DSN源,并能通过TEST验证后,就可以用这个字符串联通ADO与MySQL,这一步是所有后继编程方面操作的前提。
OK,到这里,我们整理一下至此我们完成了什么:
1. 架设了一个完整的Apache + MySQL + PHP本机环境。
2. 一个运行正常的,数据和以前一模一样的DZ论坛。
3. 通过ODBC,架起了MySQL 和 VB之间的桥梁。
回到从原帖内清除html标签这个问题上来,这是一个很小很简单的程序,我们首先要知道DZ论坛的帖子是放在pre_forum_post这个表里的,里面有很多内容,而最关键的帖子内容放在message列里。首先用一个Recordset对象读取全部post内容,然后逐条读取其中的message列,将去除html标签后的内容update回原数据库即可。
比较有意思的事,因为我想既然我都已经读取了html标签,那么我何不对它进行解析,即如果碰到
则解析为一个空行,<IMG>则解析为一幅图,然后再解析后面的图片地址,但后来发现这个工程不是一般的庞大,因为虽然是一个小小的帖子,涉及的html标签还是很多的,如果我把他们都解析完了,差不多我就是在做一个浏览器的节奏了,于是选择放弃。
最终,这段程序花了接近5分钟的时间将11万条记录中的html标签全部卡擦了。
接下来又发生了一件事情,正当我想把这个数据库整体恢复到真实主机去的时候,我在硬盘里发现了一个很古老的备份,这是一个2009年3月的备份,格式还是Access的mdb文件。不用说这是石凳论坛asp时代的产物,打开一看,发现有很多内容在我刚整理完的MySQL数据库里是不存在的!并且这个数据库主题和帖子之间的对接非常工整,这也许能帮助我解决一个遗留了很久的历史问题,就是现在的论坛主题和帖子不准,比如你点开某个主题,显示的却是另外一个主题的回帖。于是思绪开始进入回忆模式,我大概记得,最初的石凳论坛是ASP版的动网DvBBS格式,然后为了速度,又将它转换成效率较高的6kBBS格式,然后没用多久又觉得6K的功能不完善,再次改回了DvBBS,再然后因为论坛老是被天朝查封,为了逃避政策,把主机设在了美国,然后国外服务商又基本不提供Windows主机,迫于无奈只好将论坛换成了php型的,再然后因为phpWind功能不全,又将论坛换成了Dizcuz即现在使用的版本。石凳论坛版本的时间线整理如下:
2003-2007 DVBBS
2007-2008 6kBBS
2008-2009 DVBBS
2009-2011 phpWind
2011-2014 Discuz
在最初的三次转换中,因为两个论坛同属ASP格式,同样用的Access数据库,所以转换的准确率是很高的,我记得我没有为所谓的“转换”做过什么额外的工作,都是套用官方出品的转换器一次成型,数据的完整性可以说很可靠,但从ASP转换到PHP的时候问题来了,因为无论从源代码到服务器再到数据库都不一样,所以即使是官方出品的转换器也未必可靠,最可怕的是我记得从DV到phpWind,我先是用了官方转换器,然后发现不对,又用了自己的程序辅助,这种同时用两种方法去“转换”的过程本身就不太靠谱,我想所谓的“历史遗留问题”大概就是出现在第一次从ASP论坛转换到PHP论坛这个环节。
当时为了急于求成,没有认真考虑这个问题,因为有些东西,离开你的时间不长,你觉得其实也没什么值得珍惜的,正如你在2007年的时候也许不会觉得2003年的东西有多珍贵。但是当时间越拉越长,当时间滚动到2015年距离2003年已经有12年的历史之后,你才会发现以前那些点点滴滴是多么的有趣。万幸的是我居然翻出了这个备份,这个备份完整记录石凳论坛2003-2009年之间风风雨雨,于是我要做的事分成两段:
1. 以这个古董mdb文件为基础,将石凳论坛从建坛第一天到2009年3月的记录完整复制到新的数据库。
(这段历史是石凳论坛鼎盛时代的见证,也是石凳元老们的生活点滴)
2. 以原MySQL数据的老数据为基础,将石凳论坛从2009年3月至今的记录复制到新的数据库。
(2009年后石凳论坛开始衰退,石凳元老基本上归于沉默,这些记录90%以上基本上就是rockage,小宇,LXR1207以及弋人等4个人的记录)
下面说一下论坛最基本的运作方式,几乎所有的BBS在大结构上都以这个简单的模式在运转:
1. 一个主题拥有一个唯一的主题ID,即tid
2. 一个主题旗下拥有很多回帖(你可以把主题内容认为是“第一个”回帖),这些回帖各自拥有一个唯一ID(pid),然后他们
还有一个主题id(tid),通过tid来决定这些回帖是归属于哪些主题的。
3. 例子,比如说,主题数据表内有以下记录
主题A tid=0
主题B tid=1
回帖数据表内有以下记录
回帖1 pid=0 tid=0
回帖2 pid=1 tid=1
回帖3 pid=2 tid=0
回帖4 pid=3 tid=1
从上可以看出,通过tid可以解决回帖的归属问题,显然,回帖1、3属于主题A,回帖2、4属于主题B。
一旦主题表内的tid乱套了,或者回帖表里的tid乱了都会带来严重问题,即回帖表的回帖可能会依附到另外的主题中去,也有可能根本就找不到对应tid的主题,形成一些根本显示不出来的,没有主题归属的“孤回帖”。
第一步,我们要搞清楚DZ论坛的发帖机制:
一. 发主题流程
1. 用户按下发表按钮触发一段php程序,该程序对“主题表”`pre_forum_thread` 进行操作,自增一个唯一的主题tid,然后将主题的内容认为是第一个“回帖”,这个过程对“回贴表”`pre_forum_post`进行操作,将tid写入该表,然后关键的地方来了,`pre_forum_post`这个表有一个pid,即自身的唯一记录识别码,这在其他论坛里基本上是一个自增的主键,但DZ论坛不知道为什么没有将它设为自增,而只是一个不能重复的主键,这一点我至今没有想通。 那么DZ论坛是如何做到回帖pid唯一而不重复呢?经观察,它又单独建了一个forum_post_tableid表,这个表只有一个列就是pid,而且是自增主键。回到程序上来,在按下发表按钮后,php程序首先要进行“第一个”回帖操作(即主题的内容本身),首先对forum_post_tableid 表进行插入(INSERT)操作,然后该表产生一个自增的pid,然后再用MAX(pid)函数求出该表的pid最大值,得到这个值后再回写(UPDATE)进回帖表`pre_forum_post`的pid列。如此一来,回贴表`pre_forum_post`的pid列“变相”地实现了自增且永不重复。但为什么不直接将该列设为自增而非要通过如此迂回的方式来处理呢,这个不好说,本人表示不可理解。一个可能的原因是,回帖表pid不设为自增则可以对它进行UPDATE操作,最有可能的理由是为了论坛数据转换,回帖唯一记录pid不定死为自增列,反而可以保证原数据不易受到影响,插入别的数据记录会相对灵活一些,当然这只是我的猜测。
2. 基于该主题下的其他回帖,同样基于上述机制进行数据库操作,唯一不同的是不对主题表进行操作,因为回帖本身不是主题,只需要确定归属于哪个主题即可,而不需要确定主题本身。
在搞清楚DZ论坛的发帖机制后工作就变得很明晰起来,具体的操作如下:
1. 将原有的pre_forum_post、pre_forum_thread备份,清空(TRUNCATE)这两个表,但是保留其结构
2. 建立一个新表`pre_forum_thread_test` ,用于保存临时主题数据.
3. select * from Dv_Topic order by TopicID 并将结果Insert进MySQL的thread_test表 (克隆了古董mdb文件的主题)
4. 通过我的比对,tid=5614是那次失败的转换临界点,tid小于5614的帖子即石凳论坛2009年3月之前的帖子,此后为迁徙到php之后的帖子,而出问题的正是tid小于5614那部分,对于tid大于5614的那部分,是后来使用php论坛发的新帖,它与pid的对应关系就没有问题了。题外话:tid=5614这个主题是rockage发布的关于奥巴马总统的“旗帜”,最后跟帖是LXR1207不知所云的回帖“我吃西红柿的书看多了。。。”
5. 基于pre_forum_post创建一个新表`pre_forum_post_test` ,用于临时存放古董mdb文件的回帖,保持其结构但解除其主键约束
6. select * from dv_bbs1 order by AnnounceID ,克隆所有古董mdb的回帖记录到mySQL的新数据库,同样的,结果Insert进post_test临时表
7. 至此,我们目前得到了一套完整的古董mdb文件的MySQL版记录(主题+回帖),也就是说我们完整克隆了从石凳最初诞生开始截止至2009年4月的全部记录。
8. 因为tid=5614是临界点,在古董数据库里5614就是tid的最大值了,此后因为论坛迁徙到了php,所以这个id还在扩大,下面要做的事情就是将2009年3月至今的主题续写进thread_test数据表.
9. select * from pre_forum_thread_oldmysql where tid > 5614 order by tid ,然后跟前面一样Insert进thread_test表,相当于续写。现在我们已经可以将pre_forum_thread_test表写入正式的pre_forum_thread表了,因为数据完全OK,不再需要这个临时表,注意,恢复其tid的自增属性。
10. 看看现在我们得到了什么:
10.1 创坛至今所有的主题
10.2 创坛开始截止至2009年4月的所有回帖
11. 那么问题来了,2009年3月(即tid>5614)后的回帖怎么办?因为这里面有大量的早期数据是混乱的,它的MySQL回帖pid和古董mdb文件的pid有很多重复,不能强行合并,否则麻烦就大了。再扩展思维一下,当年极有可能就是因为人为地通过某种手段强制让pid不重复(比如说忽略主键唯一性报错等),所以才酿成了悲剧,我算是明白数据库为什么非要安个主键了,其目的倒不是为了约束用户,主要还是约束程序员本身不要太任性,不触犯约束下完成的操作基本上保证了数据的可靠性,如果非要忽略约束,蛮干往往意味着悲剧!
12. 经观察,pid > 114870 (即LXR1207那个莫名其妙的回帖)为临界点,此前为老数据,此后为新数据,以这个切入点进行克隆操作.
13. 问题仍然没有解决,pid大于114870的后面仍然还有大量的pid和古董mdb是重复的,因为当年忽略主键重复的警告,大约忽略了20000条之多的记录,所以古董mdb文件的pid反而比后来的PHP论坛的pid还大!好在回帖都是有记录时间的,除了pid我们还可以用时间来排序,以LXR那个莫名其妙的回帖为界,凡是那个帖子之后的回帖,算作php论坛回帖,之前的回帖因为已经从古董mdb那得到了完整版,可以忽略之。经观察这个时间点为1237787830(Unix时间表示法,换算过来是2009/3/23 13:57:10)
14. SELECT * FROM `pre_forum_post_oldmysql` where dateline > 1237787830 ORDER BY dateline,首先查询出这部分帖子
15. 然后回写进forum_post_test的时候,就不能直接写入原有的pid了(上面说了,有重复),因为我们之前解除了主键约束,所以要小心的处理,用max(pid)函数先获取当前pid最大值,然后对其+1,再将这个值连同其他数据一起Insert进forum_post_test
16. 至此,我们已经将整个石凳论坛从古至今的主题,从古至今的回帖,完完整整地克隆到两个test数据表了,通过这个操作,我悟出,进行数据操作时,能不改变原数据表的主键是最好的选择,实在没办法,就要想办法非常小心的解决唯一性问题,至少不能忽略报错,如果连报错都忽略了,基本上100%要出问题。
17. 因为这post_test表是取消了主键的,并且没有和DZ论坛的forum_post_tableid表相关联,尽管数据完整,对应关系良好,但是还不算真正地达到“能用”的地步
18. 下面的操作是让这张表符合DZ论坛的规定,也就是让它和tableid相关联:
18.1 `pre_forum_post` ->待处理的空白主表
18.2 `pre_forum_post_tableid` -> 主表`pre_forum_post的自增变量表(空白)
18.3 `pre_forum_post_test` -> 未整理的全站帖子
18.4 `pre_forum_thread` -> 全站主题记录 order by dateline 以日期顺序先后排列
流程:
18.5. 读取forum_thread表的一条记录tid,
18.6. 查出这个tid在forum_post_test里有哪些跟帖并将结果order by dateline
18.6 将forum_post_test里的这条记录insert回主表forum_post
18.6.1 先自增post_tableid`,(往里面insert一条null记录)
18.6.2 然后获取max(pid),
18.6.3 最后将这个maxid混合其他数据insert to forum_post
19. 因为需要好几个步骤的操作,所以以上运行过程相当耗费计算机资源,我花了将近10分钟才跑完这段程序,在这10分钟内,我的电脑相当于在飞速地回帖,从原理上来说这个过程和我们人手回帖没有本质区别,就是不停地对pre_forum_post进行insert操作,只不过,电脑花了10分钟,而我们整整花了12年。
后记:
尽管以上各种操作耗费了我接近4天时间,
但我觉得其过程还是很有趣的,我越来越像是一个考古工作者,一步一步地获取线索,小心翼翼地操作,力求这些操作在最大可能修葺文物的同时又不至于破坏他们。
我之所以完整地记录下以上所有是因为没准几天之后我就会把上述一切忘得干干净净,这里面有些知识点是有用的,没准以后哪天碰巧也会在某个场合遇到,这就是记录的意义,也许记录也是石凳论坛的意义,尽管它大量地记录了一些没用的东西,这个版本的石凳论坛诞生于2003年,就像我在前面说过的那样,当时间不断向前,当我们距离2003年越来越远时,某一天你会突然发现这些“没用”的东西会变得可爱起来,就像你不可能觉得前一天的日记有什么值得珍惜的,因为近在眼前,但是10年、20年、30年后你再看回那天的日记,感受就会不一样了。
首先,最初的想法是论坛在上次从phpBB迁徙到Discuz X3.1的遗留问题,DZ论坛基于安全理由忽略了html标签,那么就造成了很多帖子原封不动地将html 显示出来而不加以解析,这带来的最要命的结果就是没有空行,因为不解析
标签,所有的内容没有任何空行全部粘成一团,几乎无法阅读。解决这个问题非常简单,只需要将帖子的抽离出来写一个小程序将html标签清除就行了。那么首先我需要在本机搭设一个Apache+MySQL+PHP的组合环境,然后再用VB+ADO+ODBC对MySQL数据库进行编程即可。
这又牵扯了一个新的话题,如果不选择xampp 3 in 1整合包,我就是想独立安装三剑客的话,该怎么做,因为用惯了xampp觉得有些腻味,所以这次想尝试一下新方式。殊不知这一步直接折腾了我一天时间,我的经验整合贴将放在另外的地方,这里就不讨论了。
这一环节流程如下:
1. 首先导入美国服务器的资料,使用MySQL命令行硬导出。指令如下:
登陆美国服务器:mysql -h 103.254.148.137 -u stoneren_rockage -p
dump数据库:mysqldump -h68.178.136.155 -urockage -p stoneren_rockage > d:/backupfile.sql
美国服务器的支持就是好,支持MySQL命令行远程访问,注意最好挂个VPN,否则因掩饰触发Timeout会造成错误。
backupfile.sql则是论坛数据库stoneren_rockage的完整备份。
通过以下指令还原到本机服务器:
mysql -uroot -p --default-character-set=utf8 (默认最高权限用户root)
use rockage; (选择rockage数据库)
source d:\backupfile.sql (导入)
第二步,通过ftp将美国服务器的论坛源文件原封不动地down下来放到本机。
第三步,此刻,数据库导入完成,源文件下载完成,那么我们直接访问localhost即相当于访问原美国主机。
之后我发现本机环境数据完全正常,但问题是奇卡无比,原因未知。迫于无奈又转回了xampp,发现仍然十分卡顿,说明问题不是出在散装或整合装,至今我也没有想明白,但是在奇卡无比的情况下仍然可以操作,接下来的操作如下:
1. 慢速登陆本机论坛
2. 慢速进入后台,进行备份。
这里如果你问我为何要多此一举,因为我们已经dump了完整的数据库备份,何必再用DZ自身的备份功能备份一次呢?因为我尝试用命令行将数据库导入到香港服务器失败,香港服务器貌似不支持命令行远程操作。而明确的是,美国服务器是支持的,所以,用命令行导入导出必须要看目标服务器支不支持,在本机环境能通过的事情放到真实服务器那就未必了,需要谨慎。
慢速备份完所有数据后,删除旧的DZ源码,下载一个全新的DZ X3.1安装版,重新干净安装DZ论坛,安装过程全部按默认处理,新论坛安装完成后,登陆后台,再将老数据还原到新论坛,完成后发现速度非常快,说明工作正常了。
接下来需要将MySQL与VB打通,在MySQL安装包里附有一个mysql_connector_odbc模块,但该模块是32位的不能使用,必须单独下载64位版的connector,然后64位版的下载后仍然不行,因为貌似xampp整合的MySQL又是32位的不支持,我简直快崩溃了,在无数次尝试中,我发现如果按照以下流程操作,可以解决问题,但原因未知,整个流程如下:
1. 安装xampp
2. 进入mysql命令行模式,用source命令导入后缀为.sql的原始数据库
3. 拷贝原始bbs的html文件,修改config设置(主要是数据库地址账户密码这三项,注意有3个地方要改,包括uc-center里面)
4. 进入原始bbs的html文件,这时候会比较卡(原因不明),龟速进入后台,备份全站一次
5. 删除原始bbs的html文件,纯净安装discuz x3.1
6. 初装的x3.1会比较快,进入后台导入之前备份的全站数据,清缓存,再次返回首页就不会卡了
7. 在xampp控制台关闭mysql服务(不关闭这个下一步无法进行)
8. 安装纯净版mysql 5.6.21 for 32位版(官网就只有32位版,装完后机内有两个mysql 5.6.21,一个是xampp捆绑的,另一个是独立常规安装的)
9. 按默认装好后,把c:\xampp\mysql\data下的bbs数据库拷贝到C:\ProgramData\MySQL\MySQL Server 5.6\data(相当于把xampp的mysql数据硬拷贝到新装的mysql下)
10. 安装mysql-connector-odbc-5.3.4-winx64 (win7 64只能装它才能将mysql和odbc打通,之前常规默认安装那个connector-odbc-32位的无效)
11. 控制面板 -> 管理工具 -> 数据源 -> ODBC数据源
12. 添加用户DSN (系统DSN加了也没用),输入密码账号和绑定的数据库,Test验证一下
13. 返回到VB这边,添加一个adodc控件,鼠标右键,ADODC属性
14. 使用连接字符串 -> 生成 -> 选Provider for ODBC Drivers -> 使用数据源名称(选刚才在控制面板里创建那个)->输入账号密码 ->选择起始目录(即初始数据库) -> 测试连接
15. 测试若通过,将这条字符串粘贴复制下来,就可以用在任何需要联通ODBC数据库的地方,否则不用试,这一步过不了其他连接字符串肯定也不行
那么最终,经过反复折腾,这条来之不易的连接字符串为:
Provider=MSDASQL.1;Persist Security Info=False;User ID=root;Data Source=mysql;Initial Catalog=rockage
前提是,在控制面板的数据源那里一定要能看到MySQL的驱动程序,还要添加一个用户DSN源,并能通过TEST验证后,就可以用这个字符串联通ADO与MySQL,这一步是所有后继编程方面操作的前提。
OK,到这里,我们整理一下至此我们完成了什么:
1. 架设了一个完整的Apache + MySQL + PHP本机环境。
2. 一个运行正常的,数据和以前一模一样的DZ论坛。
3. 通过ODBC,架起了MySQL 和 VB之间的桥梁。
回到从原帖内清除html标签这个问题上来,这是一个很小很简单的程序,我们首先要知道DZ论坛的帖子是放在pre_forum_post这个表里的,里面有很多内容,而最关键的帖子内容放在message列里。首先用一个Recordset对象读取全部post内容,然后逐条读取其中的message列,将去除html标签后的内容update回原数据库即可。
比较有意思的事,因为我想既然我都已经读取了html标签,那么我何不对它进行解析,即如果碰到
则解析为一个空行,<IMG>则解析为一幅图,然后再解析后面的图片地址,但后来发现这个工程不是一般的庞大,因为虽然是一个小小的帖子,涉及的html标签还是很多的,如果我把他们都解析完了,差不多我就是在做一个浏览器的节奏了,于是选择放弃。
最终,这段程序花了接近5分钟的时间将11万条记录中的html标签全部卡擦了。
接下来又发生了一件事情,正当我想把这个数据库整体恢复到真实主机去的时候,我在硬盘里发现了一个很古老的备份,这是一个2009年3月的备份,格式还是Access的mdb文件。不用说这是石凳论坛asp时代的产物,打开一看,发现有很多内容在我刚整理完的MySQL数据库里是不存在的!并且这个数据库主题和帖子之间的对接非常工整,这也许能帮助我解决一个遗留了很久的历史问题,就是现在的论坛主题和帖子不准,比如你点开某个主题,显示的却是另外一个主题的回帖。于是思绪开始进入回忆模式,我大概记得,最初的石凳论坛是ASP版的动网DvBBS格式,然后为了速度,又将它转换成效率较高的6kBBS格式,然后没用多久又觉得6K的功能不完善,再次改回了DvBBS,再然后因为论坛老是被天朝查封,为了逃避政策,把主机设在了美国,然后国外服务商又基本不提供Windows主机,迫于无奈只好将论坛换成了php型的,再然后因为phpWind功能不全,又将论坛换成了Dizcuz即现在使用的版本。石凳论坛版本的时间线整理如下:
2003-2007 DVBBS
2007-2008 6kBBS
2008-2009 DVBBS
2009-2011 phpWind
2011-2014 Discuz
在最初的三次转换中,因为两个论坛同属ASP格式,同样用的Access数据库,所以转换的准确率是很高的,我记得我没有为所谓的“转换”做过什么额外的工作,都是套用官方出品的转换器一次成型,数据的完整性可以说很可靠,但从ASP转换到PHP的时候问题来了,因为无论从源代码到服务器再到数据库都不一样,所以即使是官方出品的转换器也未必可靠,最可怕的是我记得从DV到phpWind,我先是用了官方转换器,然后发现不对,又用了自己的程序辅助,这种同时用两种方法去“转换”的过程本身就不太靠谱,我想所谓的“历史遗留问题”大概就是出现在第一次从ASP论坛转换到PHP论坛这个环节。
当时为了急于求成,没有认真考虑这个问题,因为有些东西,离开你的时间不长,你觉得其实也没什么值得珍惜的,正如你在2007年的时候也许不会觉得2003年的东西有多珍贵。但是当时间越拉越长,当时间滚动到2015年距离2003年已经有12年的历史之后,你才会发现以前那些点点滴滴是多么的有趣。万幸的是我居然翻出了这个备份,这个备份完整记录石凳论坛2003-2009年之间风风雨雨,于是我要做的事分成两段:
1. 以这个古董mdb文件为基础,将石凳论坛从建坛第一天到2009年3月的记录完整复制到新的数据库。
(这段历史是石凳论坛鼎盛时代的见证,也是石凳元老们的生活点滴)
2. 以原MySQL数据的老数据为基础,将石凳论坛从2009年3月至今的记录复制到新的数据库。
(2009年后石凳论坛开始衰退,石凳元老基本上归于沉默,这些记录90%以上基本上就是rockage,小宇,LXR1207以及弋人等4个人的记录)
下面说一下论坛最基本的运作方式,几乎所有的BBS在大结构上都以这个简单的模式在运转:
1. 一个主题拥有一个唯一的主题ID,即tid
2. 一个主题旗下拥有很多回帖(你可以把主题内容认为是“第一个”回帖),这些回帖各自拥有一个唯一ID(pid),然后他们
还有一个主题id(tid),通过tid来决定这些回帖是归属于哪些主题的。
3. 例子,比如说,主题数据表内有以下记录
主题A tid=0
主题B tid=1
回帖数据表内有以下记录
回帖1 pid=0 tid=0
回帖2 pid=1 tid=1
回帖3 pid=2 tid=0
回帖4 pid=3 tid=1
从上可以看出,通过tid可以解决回帖的归属问题,显然,回帖1、3属于主题A,回帖2、4属于主题B。
一旦主题表内的tid乱套了,或者回帖表里的tid乱了都会带来严重问题,即回帖表的回帖可能会依附到另外的主题中去,也有可能根本就找不到对应tid的主题,形成一些根本显示不出来的,没有主题归属的“孤回帖”。
第一步,我们要搞清楚DZ论坛的发帖机制:
一. 发主题流程
1. 用户按下发表按钮触发一段php程序,该程序对“主题表”`pre_forum_thread` 进行操作,自增一个唯一的主题tid,然后将主题的内容认为是第一个“回帖”,这个过程对“回贴表”`pre_forum_post`进行操作,将tid写入该表,然后关键的地方来了,`pre_forum_post`这个表有一个pid,即自身的唯一记录识别码,这在其他论坛里基本上是一个自增的主键,但DZ论坛不知道为什么没有将它设为自增,而只是一个不能重复的主键,这一点我至今没有想通。 那么DZ论坛是如何做到回帖pid唯一而不重复呢?经观察,它又单独建了一个forum_post_tableid表,这个表只有一个列就是pid,而且是自增主键。回到程序上来,在按下发表按钮后,php程序首先要进行“第一个”回帖操作(即主题的内容本身),首先对forum_post_tableid 表进行插入(INSERT)操作,然后该表产生一个自增的pid,然后再用MAX(pid)函数求出该表的pid最大值,得到这个值后再回写(UPDATE)进回帖表`pre_forum_post`的pid列。如此一来,回贴表`pre_forum_post`的pid列“变相”地实现了自增且永不重复。但为什么不直接将该列设为自增而非要通过如此迂回的方式来处理呢,这个不好说,本人表示不可理解。一个可能的原因是,回帖表pid不设为自增则可以对它进行UPDATE操作,最有可能的理由是为了论坛数据转换,回帖唯一记录pid不定死为自增列,反而可以保证原数据不易受到影响,插入别的数据记录会相对灵活一些,当然这只是我的猜测。
2. 基于该主题下的其他回帖,同样基于上述机制进行数据库操作,唯一不同的是不对主题表进行操作,因为回帖本身不是主题,只需要确定归属于哪个主题即可,而不需要确定主题本身。
在搞清楚DZ论坛的发帖机制后工作就变得很明晰起来,具体的操作如下:
1. 将原有的pre_forum_post、pre_forum_thread备份,清空(TRUNCATE)这两个表,但是保留其结构
2. 建立一个新表`pre_forum_thread_test` ,用于保存临时主题数据.
3. select * from Dv_Topic order by TopicID 并将结果Insert进MySQL的thread_test表 (克隆了古董mdb文件的主题)
4. 通过我的比对,tid=5614是那次失败的转换临界点,tid小于5614的帖子即石凳论坛2009年3月之前的帖子,此后为迁徙到php之后的帖子,而出问题的正是tid小于5614那部分,对于tid大于5614的那部分,是后来使用php论坛发的新帖,它与pid的对应关系就没有问题了。题外话:tid=5614这个主题是rockage发布的关于奥巴马总统的“旗帜”,最后跟帖是LXR1207不知所云的回帖“我吃西红柿的书看多了。。。”
5. 基于pre_forum_post创建一个新表`pre_forum_post_test` ,用于临时存放古董mdb文件的回帖,保持其结构但解除其主键约束
6. select * from dv_bbs1 order by AnnounceID ,克隆所有古董mdb的回帖记录到mySQL的新数据库,同样的,结果Insert进post_test临时表
7. 至此,我们目前得到了一套完整的古董mdb文件的MySQL版记录(主题+回帖),也就是说我们完整克隆了从石凳最初诞生开始截止至2009年4月的全部记录。
8. 因为tid=5614是临界点,在古董数据库里5614就是tid的最大值了,此后因为论坛迁徙到了php,所以这个id还在扩大,下面要做的事情就是将2009年3月至今的主题续写进thread_test数据表.
9. select * from pre_forum_thread_oldmysql where tid > 5614 order by tid ,然后跟前面一样Insert进thread_test表,相当于续写。现在我们已经可以将pre_forum_thread_test表写入正式的pre_forum_thread表了,因为数据完全OK,不再需要这个临时表,注意,恢复其tid的自增属性。
10. 看看现在我们得到了什么:
10.1 创坛至今所有的主题
10.2 创坛开始截止至2009年4月的所有回帖
11. 那么问题来了,2009年3月(即tid>5614)后的回帖怎么办?因为这里面有大量的早期数据是混乱的,它的MySQL回帖pid和古董mdb文件的pid有很多重复,不能强行合并,否则麻烦就大了。再扩展思维一下,当年极有可能就是因为人为地通过某种手段强制让pid不重复(比如说忽略主键唯一性报错等),所以才酿成了悲剧,我算是明白数据库为什么非要安个主键了,其目的倒不是为了约束用户,主要还是约束程序员本身不要太任性,不触犯约束下完成的操作基本上保证了数据的可靠性,如果非要忽略约束,蛮干往往意味着悲剧!
12. 经观察,pid > 114870 (即LXR1207那个莫名其妙的回帖)为临界点,此前为老数据,此后为新数据,以这个切入点进行克隆操作.
13. 问题仍然没有解决,pid大于114870的后面仍然还有大量的pid和古董mdb是重复的,因为当年忽略主键重复的警告,大约忽略了20000条之多的记录,所以古董mdb文件的pid反而比后来的PHP论坛的pid还大!好在回帖都是有记录时间的,除了pid我们还可以用时间来排序,以LXR那个莫名其妙的回帖为界,凡是那个帖子之后的回帖,算作php论坛回帖,之前的回帖因为已经从古董mdb那得到了完整版,可以忽略之。经观察这个时间点为1237787830(Unix时间表示法,换算过来是2009/3/23 13:57:10)
14. SELECT * FROM `pre_forum_post_oldmysql` where dateline > 1237787830 ORDER BY dateline,首先查询出这部分帖子
15. 然后回写进forum_post_test的时候,就不能直接写入原有的pid了(上面说了,有重复),因为我们之前解除了主键约束,所以要小心的处理,用max(pid)函数先获取当前pid最大值,然后对其+1,再将这个值连同其他数据一起Insert进forum_post_test
16. 至此,我们已经将整个石凳论坛从古至今的主题,从古至今的回帖,完完整整地克隆到两个test数据表了,通过这个操作,我悟出,进行数据操作时,能不改变原数据表的主键是最好的选择,实在没办法,就要想办法非常小心的解决唯一性问题,至少不能忽略报错,如果连报错都忽略了,基本上100%要出问题。
17. 因为这post_test表是取消了主键的,并且没有和DZ论坛的forum_post_tableid表相关联,尽管数据完整,对应关系良好,但是还不算真正地达到“能用”的地步
18. 下面的操作是让这张表符合DZ论坛的规定,也就是让它和tableid相关联:
18.1 `pre_forum_post` ->待处理的空白主表
18.2 `pre_forum_post_tableid` -> 主表`pre_forum_post的自增变量表(空白)
18.3 `pre_forum_post_test` -> 未整理的全站帖子
18.4 `pre_forum_thread` -> 全站主题记录 order by dateline 以日期顺序先后排列
流程:
18.5. 读取forum_thread表的一条记录tid,
18.6. 查出这个tid在forum_post_test里有哪些跟帖并将结果order by dateline
18.6 将forum_post_test里的这条记录insert回主表forum_post
18.6.1 先自增post_tableid`,(往里面insert一条null记录)
18.6.2 然后获取max(pid),
18.6.3 最后将这个maxid混合其他数据insert to forum_post
19. 因为需要好几个步骤的操作,所以以上运行过程相当耗费计算机资源,我花了将近10分钟才跑完这段程序,在这10分钟内,我的电脑相当于在飞速地回帖,从原理上来说这个过程和我们人手回帖没有本质区别,就是不停地对pre_forum_post进行insert操作,只不过,电脑花了10分钟,而我们整整花了12年。
后记:
尽管以上各种操作耗费了我接近4天时间,
但我觉得其过程还是很有趣的,我越来越像是一个考古工作者,一步一步地获取线索,小心翼翼地操作,力求这些操作在最大可能修葺文物的同时又不至于破坏他们。
我之所以完整地记录下以上所有是因为没准几天之后我就会把上述一切忘得干干净净,这里面有些知识点是有用的,没准以后哪天碰巧也会在某个场合遇到,这就是记录的意义,也许记录也是石凳论坛的意义,尽管它大量地记录了一些没用的东西,这个版本的石凳论坛诞生于2003年,就像我在前面说过的那样,当时间不断向前,当我们距离2003年越来越远时,某一天你会突然发现这些“没用”的东西会变得可爱起来,就像你不可能觉得前一天的日记有什么值得珍惜的,因为近在眼前,但是10年、20年、30年后你再看回那天的日记,感受就会不一样了。