将 vBulletin 4 论坛迁移到 Discourse

感谢您提供的宝贵信息!:+1:t6:

我还有一个问题,关于选择 vBulletin 用户的 SQL 查询。

三年前,当我将旧的 phpbb 论坛导入 Discourse 时,大约有 20000 名用户。显然,其中大多数是未使用的账户。在这三年间,Discourse 自动清理了不活跃用户,现在我们有了更真实的 3000 名成员。

当我从 vBulletin 导入 180000 名用户并让 Discourse 积极执行清理任务后,最终剩下 27000 名用户,这看起来是合理的。

在我的 vBulletin 中:

  1. 所有用户在其他用户个人资料上发布的消息都被导入到 Discourse 的“未分类”类别中,形成了没有标题的话题,除了增加无用的噪音外毫无价值;
  2. 绝大多数在其他用户个人资料上发布内容的用户似乎是垃圾账号;
    因此,我希望在导入过程中就进行清理,而不是在导入之后。

我对 vBulletin 的数据库并不完全了解,其节点结构有些令人困惑,但我只想导入那些至少发布过 1 个话题回复的用户。
在我看来,话题回复会填充 vBulletin 用户表中的 lastpostid 字段,但公开个人资料帖子则不会

是的,我打算修改以下 SQL:

  SELECT u.userid, u.username, u.homepage, u.usertitle, u.usergroupid, u.joindate, u.email
    CASE WHEN u.scheme='blowfish:10' THEN token
         WHEN u.scheme='legacy' THEN REPLACE(token, ' ', ':')
    END AS password,
    IF(ug.title = 'Administrators', 1, 0) AS admin
    FROM #{DB_PREFIX}user u
    LEFT JOIN #{DB_PREFIX}usergroup ug ON ug.usergroupid = u.usergroupid
ORDER BY userid
   LIMIT #{BATCH_SIZE}
  OFFSET #{offset}

改为:

  SELECT u.userid, u.username, u.homepage, u.usertitle, u.usergroupid, u.joindate, u.email, u.lastpost
    CASE WHEN u.scheme='blowfish:10' THEN token
         WHEN u.scheme='legacy' THEN REPLACE(token, ' ', ':')
    END AS password,
    IF(ug.title = 'Administrators', 1, 0) AS admin
    FROM #{DB_PREFIX}user u
    LEFT JOIN #{DB_PREFIX}usergroup ug ON ug.usergroupid = u.usergroupid
    WHERE u.lastpost > 0
ORDER BY userid
   LIMIT #{BATCH_SIZE}
  OFFSET #{offset}

我只添加了 WHERE u.lastpost > 0

使用此查询进行计数后,我得到了 25000 名用户,而之前导入并经过 Discourse 清理后(但仍保留那些无标题话题,即以前的公开个人资料消息)的活跃用户数为 27000。这意味着大约有 2000 名用户在其他用户的个人资料上发布过内容,这个数字并不算荒谬。

您认为我的推理正确吗?添加 WHERE u.lastpost > 0 是否能在不产生任何负面影响的情况下很好地清理我的用户基础?

这取决于您的数据库的迁移历史。如果是从 phpBB 迁移到 vBulletin,或者经历了多次 vBulletin 更新,那么上述推理可能是错误的。

您能做的最好的事情是通过运行更多查询来验证您的推理,例如列出所有由没有 lastpost 字段的用户发布的帖子。

此外,如果您安装了诸如“点赞”或“投票”之类的插件,可能会误删不应删除的用户。

我的策略是在删除或省略内容时非常保守,因为存储成本很低。

4 个赞

谢谢,我会按你说的做,我宁愿谨慎一些。:slight_smile:
我会让 Discourse 随时间自行清理。

由于论坛非常古老,且是从 phpBB 迁移到 vBulletin 再迁移到现在的系统,许多内容都需要处理,因此我这几天一直在为你的迁移脚本添加自定义帖子清理的正则表达式。我想,也希望能很快完成收尾工作。

我对 vBulletin 不太熟悉,但我正在处理的这个论坛使用的是 vBulletin 5.6,其中我发布的一些外部图片在 vBulletin 数据库中使用了如下语法:
[IMG2=JSON]{"data-align":"none","data-size":"full","src":"https:\/\/forum.monocycle.info\/uploads\/default\/original\/2X\/3\/396192845ba93e7df2a6109a2608072efa21ee32.jpeg"}[/IMG2]
我不确定这是你的脚本中“遗漏”的内容,还是论坛管理员使用了某个生成这些 img2 标签的插件。

无论如何,我用以下代码修复了这个问题:

    raw = raw.gsub(/\[img2=json\].+?(http.+?).}\[\/img2\]/i) {"\n#{$1}\n"}

不过我有个问题:Discourse 是否会随时间自动重新烘焙已导入的帖子?如果是,它会从最新的帖子开始处理吗?

2 个赞

再次你好,
我的论坛大约有 1000 个标签,但我们可能不会在 Discourse 上使用它们。而且,这些标签可能非常混乱。我可以直接在导入器中注释掉这一行吗:

    import_tags

或者这可能会造成一些连带损害?

1 个赞

您可以安全地将其注释掉。

2 个赞

导入某些内容后,不等待 Sidekiq 完成其任务是否安全?

我导入了用户,这是 Sidekiq 当前的状态。

如果我在生产论坛上创建备份并恢复它,这些任务会发生什么?

1 个赞

不安全。虽然完整重新生成会重建大多数此类任务,但我强烈建议您等待。

它们将继续在导入实例上运行。
它们不会被包含在备份中,也不会转移到生产论坛。

3 个赞

谢谢!:+

因为在备份恢复过程中出现了一些错误消息(这些错误并不妨碍恢复完成或论坛正常运行),我在想这是否是因为我没有等待 Sidekiq 完成它的工作。

我从 vBulletin 开始了一次新的导入:在我的开发环境 Discourse 上仅导入了用户组和 30,000 个用户,等待了几十分钟,然后创建了一个备份,并将该备份恢复到一个基于 Docker 的安装环境中。恢复成功了,但日志中显示了以下错误:

ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: relation "users" does not exist LINE 1: SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1 ^ ) lib/a
晚上 10:03
7
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: relation "user_auth_tokens" does not exist LINE 1: SELECT "user_auth_tokens".* FROM "user_auth_tokens" WHERE ((...

这些错误不一致,且每次备份之间的表关系错误也不同。
我无法弄清楚这些错误来自哪里。:confused:

1 个赞

TL;DR:我认为这些错误与导入操作无关。

我之前见过这种情况,我认为这是一种竞态条件,是因为在备份恢复过程中数据库被访问导致的。

这可能是由于服务器接收到常规流量,或者某个 Sidekiq 进程未被暂停所致。在这两种情况下,这些错误都是无害的。如果我是你,我会完全忽略所有在恢复过程完全结束之前出现的 PG 错误。

4 个赞

这让人放心多了!

问题是,这些消息本身让我有点害怕,而且它们之所以可怕,还因为:

  1. 自从我开始导入以来,无论是将备份恢复到本地开发论坛还是 Docker 在线安装,每次(或几乎每次?:thinking:)备份都会出现这些消息;
  2. 它们会阻止恢复日志(在 Discourse 备份界面中)在恢复过程中继续写入:日志会卡在“正在解压归档”(如果是没有上传文件的备份,则卡在“正在 discourse_functions 架构中创建缺失的函数……”)的状态。
    看起来像是有什么东西崩溃了,但如果我等待一段时间,系统最终会自动将我正常登出;当我再次登录时,除了这些错误消息外,导入似乎都成功完成了。

既然论坛能正常运行(除了我在另一个帖子中描述的导致 502 错误的分类编辑问题),我只是担心论坛现在能用……但几周、几个月甚至几年后,会因为某些我尚未发现的问题而突然无法使用,这我绝对不想看到。毕竟,我已经连续一个月每天都在为这个导入工作付出努力了。:sweat_smile:

无论如何,非常感谢你的帮助,我真的很感激。我为一个大型社区投入了大量精力进行这项无偿的导入工作,而有人愿意回答我的问题,总是让我感到宽慰。

2 个赞

首先,感谢你的发帖。我刚尝试导入 vBulletin 3.7.x。按照所有步骤操作后,运行导入脚本时却提示无法连接,尽管我实际上可以连接。有什么建议吗?

root@uat-app:/var/www/discourse# export DB_NAME="vb4"
root@uat-app:/var/www/discourse# export DB_USER="root"
root@uat-app:/var/www/discourse# export DB_PW="1234"
root@uat-app:/var/www/discourse# export TABLE_PREFIX="vbulletin"
root@uat-app:/var/www/discourse# export ATTACHMENT_DIR='/shared/vbulletin'
root@uat-app:/var/www/discourse# export TIMEZONE="America/Vancouver"
root@uat-app:/var/www/discourse# su discourse -c 'bundle exec ruby script/import_scripts/vbulletin.rb'
root:1234@localhost wants vb4
正在加载现有群组...
正在加载现有用户...
正在加载现有分类...
正在加载现有帖子...
正在加载现有主题...
==================================================
用户 'root'@'localhost' 访问被拒绝
无法连接到数据库。

主机名:localhost
用户名:root
密码:1234
数据库:vb4

请编辑脚本或设置以下环境变量:

export DB_HOST="localhost"
export DB_NAME="vbulletin"
export DB_PW=""
export DB_USER="root"
export TABLE_PREFIX="vb_"
export ATTACHMENT_DIR '/path/to/your/attachment/folder'

正在退出。

如下所示,我可以使用相同的凭据登录数据库,并验证了数据库名称和表前缀。

root@uat-app:/var/www/discourse# mysql -uroot -p1234 -hlocalhost
欢迎使用 MariaDB 监控器。命令以 ; 或 \g 结束。
您的 MariaDB 连接 ID 为 70
服务器版本:10.3.25-MariaDB-0+deb10u1 Debian 10

版权所有 (c) 2000, 2018, Oracle, MariaDB Corporation Ab 及其他。

输入 'help;' 或 '\h' 获取帮助。输入 '\c' 可清除当前输入语句。

MariaDB [(none)]> use vb4;
正在读取表信息以完成表和列名称的补全
您可以通过使用 -A 选项关闭此功能以加快启动速度

数据库已切换
MariaDB [vb4]> select * from information_schema.tables where table_schema = 'vb4' and table_name = 'vbulletinpost'
    -> ;
+---------------+--------------+---------------+------------+--------+---------+------------+------------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------------+--------------------+-----------+
| TABLE_CATALOG | TABLE_SCHEMA | TABLE_NAME    | TABLE_TYPE | ENGINE | VERSION | ROW_FORMAT | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH | MAX_DATA_LENGTH | INDEX_LENGTH | DATA_FREE | AUTO_INCREMENT | CREATE_TIME         | UPDATE_TIME         | CHECK_TIME          | TABLE_COLLATION   | CHECKSUM | CREATE_OPTIONS | TABLE_COMMENT | MAX_INDEX_LENGTH   | TEMPORARY |
+---------------+--------------+---------------+------------+--------+---------+------------+------------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------------+--------------------+-----------+
| def           | vb4          | vbulletinpost | BASE TABLE | MyISAM |      10 | Dynamic    |    1037509 |            356 |   370191960 | 281474976710655 |     53087232 |         0 |        1046506 | 2020-11-14 14:27:01 | 2020-11-14 14:27:28 | 2020-11-14 14:27:32 | latin1_swedish_ci |     NULL |                |               | 288230376151710720 | N         |
+---------------+--------------+---------------+------------+--------+---------+------------+------------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------------+--------------------+-----------+
1 row in set (0.001 sec)

MariaDB [vb4]>

另外,我已通过访问 /admincp/avatar.php?do=storage 导出了头像,并将头像导出到三个不同的文件夹,如下所示:

/shared/vbulletin/signaturepics/
/shared/vbulletin/customprofilepics/
/shared/vbulletin/customavatars/

那我是否需要指定父文件夹,如下所示?还是必须将这三个文件夹的内容合并到一个文件夹中?

export ATTACHMENT_DIR='/shared/vbulletin'

1 个赞

我唯一的猜测是,您的密码中可能包含特殊字符?

2 个赞

实际上,它无非就是字母和数字字符。不管怎样,我刚刚又试了一次,确认在显式设置密码之前无法使用。我还注意到说明需要更新,所以在此粘贴对我有效的方法。

1 个赞

说明需要更新。以下是截至 2020 年 11 月对我有效的操作步骤。请注意,由于导入过程可能需要数小时,因此使用 screen 运行此导入确实更好;而使用 nohup 可能并无帮助,因为导入脚本会不断更新已导入各项的数量,导致 stdout 文件体积过大。

安装数据库以承载 vBulletin 数据

下载最新软件包

注意:除非显式将 Oracle MySQL 仓库添加到仓库列表中,否则 MySQL 已不再可用。MariaDB 已取代 MySQL。

root@uat-app:~# apt-get update
root@uat-app:~# apt-get install libmariadb-dev
root@uat-app:~# apt-get install default-mysql-server

启动数据库

root@uat-app:~# service mysql status
[info] MariaDB is stopped..
root@uat-app:~#
root@uat-app:~# service mysql start
[ ok ] Starting MariaDB database server: mysqld.
root@uat-app:~# service mysql status
[info] /usr/bin/mysqladmin Ver 9.1 Distrib 10.3.25-MariaDB, for debian-linux-gnu on x86_64
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Server version 10.3.25-MariaDB-0+deb10u1
Protocol version 10
Connection Localhost via UNIX socket
UNIX socket /var/run/mysqld/mysqld.sock
Uptime: 4 sec

Threads: 7 Questions: 461 Slow queries: 0 Opens: 177 Flush tables: 1 Open tables: 31 Queries per second avg: 115.250.

安装数据库连接所需的 Gems

以下显示最新的‘bundle’不喜欢原始说明中的某些标志,因此需要取消设置‘deployment’模式。

root@uat-app:~# echo "gem 'mysql2', require: false" >> /var/www/discourse/Gemfile

root@uat-app:~# echo "gem 'php_serialize', require: false" >> /var/www/discourse/Gemfile

root@uat-app:~# cd /var/www/discourse
root@uat-app:/var/www/discourse# su discourse -c 'bundle install --no-deployment --without test --without development --path vendor/bundle'
[DEPRECATED] The `--path` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set path 'vendor/bundle'`, and stop using this flag
[DEPRECATED] The `--without` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set without 'development'`, and stop using this flag
You are trying to install in deployment mode after changing
your Gemfile. Run `bundle install` elsewhere and add the
updated Gemfile.lock to version control.

If this is a development machine, remove the /var/www/discourse/Gemfile freeze by running `bundle config unset deployment`.

The dependencies in your gemfile changed

You have added to the Gemfile:
* mysql2
* php_serialize

更新配置并重新运行安装

通过 CLI 检查

检查配置确认其已设置为“deployment”模式。

root@uat-app:/var/www/discourse# bundle config list
Settings are listed in order of priority. The top value will be used.
deployment
Set for your local app (/var/www/discourse/.bundle/config): true

jobs
Set for your local app (/var/www/discourse/.bundle/config): 4

retry
Set for your local app (/var/www/discourse/.bundle/config): 3

path
Set for your local app (/var/www/discourse/.bundle/config): "vendor/bundle"

without
Set for your local app (/var/www/discourse/.bundle/config): [:development, :test]

通过检查配置文件进行验证

以下通过检查配置文件执行相同的检查。

root@uat-app:/var/www/discourse# cat /var/www/discourse/.bundle/config
---
BUNDLE_DEPLOYMENT: "true"
BUNDLE_JOBS: "4"
BUNDLE_RETRY: "3"
BUNDLE_PATH: "vendor/bundle"
BUNDLE_WITHOUT: "development:test"

更新配置

root@uat-app:/var/www/discourse# bundle config set path 'vendor/bundle'
Your application has set path to "vendor/bundle". This will override the global value you are currently setting
root@uat-app:/var/www/discourse# bundle config set without 'development:test'
Your application has set without to "development:test". This will override the global value you are currently setting
root@uat-app:/var/www/discourse# bundle config unset deployment

再次验证配置

root@uat-app:/var/www/discourse# bundle config list
Settings are listed in order of priority. The top value will be used.
path
Set for your local app (/var/www/discourse/.bundle/config): "vendor/bundle"
Set for the current user (/root/.bundle/config): "vendor/bundle"

without
Set for your local app (/var/www/discourse/.bundle/config): [:development, :test]
Set for the current user (/root/.bundle/config): [:development, :test]

jobs
Set for your local app (/var/www/discourse/.bundle/config): 4

retry
Set for your local app (/var/www/discourse/.bundle/config): 3

再次尝试安装

重新运行 Gems 的安装并退出容器。

root@uat-app:/var/www/discourse# su discourse -c 'bundle install'
...........
Bundle complete! 125 Gemfile dependencies, 163 gems now installed.
Gems in the groups development and test were not installed.
Bundled gems are installed into `./vendor/bundle`
root@uat-app:/var/www/discourse# exit

创建 vBulletin 数据目录

创建目录

[root@uat standalone]# pwd
/var/discourse/shared/standalone
[root@uat standalone]# mkdir vbulletin

复制 vBulletin 数据库

[root@uat standalone]# scp <login user>@<vbulletin server IP>:/home/backup/vbulletin/vbulletin-2020-11-14-03:30:01.sql.bz2 ./vbulletin/.

解压 vBulletin 数据库

[root@uat containers]# docker exec -it app bash
root@uat-app:/# cd /shared/vbulletin
root@uat-app:/shared/vbulletin# bunzip2 vbulletin-2020-11-14-03\:30\:01.sql.bz2

设置数据源

创建数据库 vb4

root@uat-app:/shared/vbulletin# mysql -uroot -p -e 'CREATE DATABASE vb4'
Enter password:

将 vBulletin 导入 MariaDB

root@uat-app:/shared/vbulletin# mysql -uroot -p vb4 < vbulletin-2020-11-14-03\:30\:01.sql
Enter password:

解压个人资料归档

[root@uat vbulletin]# tar xvfz signaturepics.tar.gz
[root@uat vbulletin]# tar xvfz customavatars.tar.gz
[root@uat vbulletin]# tar xvfz customprofilepics.tar.gz

更新数据库 root 密码

root@uat-app:/var/www/discourse# mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 77
Server version: 10.3.25-MariaDB-0+deb10u1 Debian 10

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> ALTER USER 'root'@'localhost' IDENTIFIED BY '1234';
Query OK, 0 rows affected (0.001 sec)

MariaDB [(none)]> quit
Bye

导入到 Discourse

设置数据源连接详情

[root@uat vbulletin]# export DB_NAME="vb4"
[root@uat vbulletin]# export DB_USER="root"
[root@uat vbulletin]# export DB_PW="1234"
[root@uat vbulletin]# export TABLE_PREFIX="vbulletin"
[root@uat vbulletin]# export ATTACHMENT_DIR='/shared/vbulletin'
[root@uat vbulletin]# export TIMEZONE="America/Vancouver"
[root@uat vbulletin]# cd /var/www/discourse
root@uat-app:/var/www/discourse# su discourse -c 'bundle exec ruby script/import_scripts/vbulletin.rb'
root:1234@localhost wants vb4
Loading existing groups...
Loading existing users...
Loading existing categories...
Loading existing posts...
Loading existing topics...

importing groups...
15 / 15 (100.0%) [3272 items/min] n]
importing users
117 / 11033 ( 1.1%) [145 items/min] in]
4 个赞

那么,您最初连接数据库时的问题是不是因为混用了库?

2 个赞

我不这么认为。我刚刚显式地修改了密码,它就能运行了。我现在在导入私信时遇到了问题。我看到了很多类似以下的报错。你知道这是什么原因吗?

正在导入私信...
      139 / 177409 (  0.1%)  [399 项/分钟]  其中一个参与者的 ID 为 nil -- [nil, 270]
pm-149 没有目标 (a:1:{i:486;s:5:"TonyN";})
      364 / 177409 (  0.2%)  [418 项/分钟]  其中一个参与者的 ID 为 nil -- [nil, 276]
pm-420 没有目标 (a:1:{i:623;s:14:"the other side";})
      571 / 177409 (  0.3%)  [414 项/分钟]  其中一个参与者的 ID 为 nil -- [nil, 445]
pm-702 没有目标 (a:1:{i:767;s:6:"greatg";})
      572 / 177409 (  0.3%)  [414 项/分钟]  其中一个参与者的 ID 为 nil -- [nil, 445]
      605 / 177409 (  0.3%)  [416 项/分钟]  其中一个参与者的 ID 为 nil -- [nil, 461]
1 个赞

要么是这些用户因某种原因未被导入(缺少电子邮件地址曾是一个原因,但现已解决),要么是查找已导入用户名的代码因其他原因无法正常工作(例如用户名的大小写问题)。

3 个赞

@pfaffman 是的,看起来确实如此,尽管某些具体细节尚不清楚。我们先看第一个例子。

  1. pm-149 是什么意思?
  2. 对于 a:1:{i:486;s:5:"TonyN";},文本 “TonyN” 看起来像是用户名,但其他数字代表什么?
  3. [nil, 270] 呢?270 代表什么?

如果我能明白它在抱怨什么,至少可以尝试查询数据库,看看是否存在数据问题。但我不太确定这些具体含义。

顺便提一下,我还注意到所有导入的论坛都设置了“所有人”权限。这意味着原本仅限版主访问的论坛被设置成了对所有人可见。有没有办法控制这一点?

1 个赞

抱歉,我记不太清了,无法详细解释。关于这一点,我能提供的免费帮助仅此而已。

当然有。请参阅 Understanding groups and category permissions

一些导入工具致力于导入群组,但很少有人知道如何将那些权限应用到被导入的分类中。你需要手动修复这些问题。

2 个赞

按照 @titusc 的说明操作,我似乎在导入数据库时遇到了问题…

root@DO-Discourse-app:/shared/vbulletin# mysql -uroot -p vb4 < CC12-Sat-Full-Backup.sql
Enter password: 
ERROR 1265 (01000) at line 4928: Data truncated for column 'method' at row 1
root@DO-Discourse-app:/shared/vbulletin#

有什么建议,它到底在寻找什么?

算了,是原始数据库中的错误…

2 个赞