以下是可以从命令行启动的批量操作集合。您需要 SSH 访问权限,因此如果您是托管客户,请联系 Discourse 团队以运行这些命令。
在操作控制台之前,极其重要的是您必须拥有最近的备份。错误随时可能发生!
首先要做的是进入您站点的容器:
cd /var/discourse
./launcher enter app
附加指南:
- Performing bulk actions as a moderator
- 如何历史性地设置标签跟踪级别默认值
- (Obsolete) Set category tracking level defaults historically
- Change ownership of all posts by a specific user
- Replace a string in all posts
- Edit a user preference for everyone or a subset of users
- Modify trust level for all users
- Apply auto-close to existing topics
- Logout all users through the rails console
- Convert all existing topics in category to wikis
更改主题状态
在运行以下命令之前,请先运行 rails c 进入控制台。
-
取消列出某个类别下的所有主题(不包括帖子操作)
您可以将
visible替换为closed或archived,并根据需要调整 true/false 值cat_id = Category.find_by_slug('admins').id Topic.where(category_id: cat_id, visible: true).update_all(visible: false) -
取消列出某个类别下的所有主题(包括帖子操作)
cat_id = Category.find_by_slug("admins").id Topic.where(category_id: cat_id, visible: true).find_each do |topic| topic.update_status('visible', false, Discourse.system_user) end -
关闭指定日期之前创建的所有主题(包括帖子操作)
Topic.where(closed: false).where("created_at < '2015-01-01'").find_each do |topic| topic.update_status('closed', true, Discourse.system_user) end
移动主题
将一组主题从一个类别移动到另一个类别
rails c
topic_ids = [12,16,29]
cat_to = Category.find_by_slug('faq')
Topic.where(id: topic_ids).update_all(category_id: cat_to.id)
Category.update_stats
用户
删除部分用户
删除从未发帖且自指定日期以来未访问过的用户
rails c
User.joins(:user_stat).where("user_stats.post_count = 0 AND previous_visit_at <= '2016-05-20'::timestamp").destroy_all
根据条件暂停一组用户
设置谁将被记录为暂停用户
rails c
logger = StaffActionLogger.new(User.find_by(username_lower: "tshenry"))
创建暂停时间段和原因
suspend_till = DateTime.new(2057,12,31)
reason = 'Completed Course'
在此示例中,我们的用户条件将是组成员资格。
target_group = Group.find_by_name("summer_students")
users = User.joins(:group_users).where(group_users: {group_id: target_group.id})
根据上述设定的值暂停每个用户:
users.find_each do |u|
u.suspended_till = suspend_till
u.suspended_at = DateTime.now
u.save!
logger.log_user_suspend(u,reason)
putc '.'
end
更新用户暂停原因
也许您暂停了完成课程的用户(参见上方示例 above),现在您想添加课程年份,因为您已经教授了多年。
UserHistory.where(action: 10, details: "Completed Course").update_all(details: "Completed 2018 Course")
取消暂停用户
如果您需要批量取消暂停用户,例如因为他们属于前一年的 cohort 并将在今年回归,您可以按如下方式操作。在示例中,我们通过用户 ID 查找用户。
user_list = [1, 3, 5, 7, 11]
users = User.where("id in (?)", user_list)
users.each do |user|
user.suspended_till = nil
user.suspended_at = nil
user.save!
StaffActionLogger.new(User.find(-1)).log_user_unsuspend(user)
DiscourseEvent.trigger(:user_unsuspended, user: user)
end
导出/导入
导出/导入所有站点设置
要简单地打印出您站点上所有已更改的设置,请运行:
rake site_settings:export
如果您想将设置导出到文件:
rake site_settings:export > saved_settings.yml
如果您想从文件导入设置:
rake site_settings:import < saved_settings.yml
导出/导入类别
导出有两种选项,导入有一种方法。
导出一组完整的类别
首先获取您的类别 ID 列表:
rake categories:list
然后在导出 rake 任务中用空格分隔类别 ID。例如:
rake export:categories["12 6"]
导出您站点的类别结构
这基本上是您 Discourse 站点的“骨架”副本。它包括每个类别以及任何与现有类别权限关联的组。它不包括主题:
rake export:category_structure
如果您需要类别结构以及任何与类别权限关联的组和这些组的任何成员:
rake export:category_structure[true]
导入类别文件
使用导出文件的名称,如下例所示:
rake import:file["category-export-2019-05-16-052430.json"]
导出/导入组
导出所有用户组
rake export:groups
导出所有用户组(包括用户)
rake export:groups[true]
导入组文件
使用导出文件的名称,如下例所示:
rake import:file["group-export-2019-05-16-052430.json"]
为多个类别设置权限
请注意,这将删除您为相关类别设置的任何现有访问限制。请确保包含所有相关权限。
-
获取类别及其 ID 的列表
rails c Category.all.pluck("name", "id") -
创建一个包含您要操作的类别 ID 的数组。
category_ids = [6,7,8,10] -
更改权限。
set_permissions函数可以使用以下参数::full、:create_post、:readonly-
单个权限。例如,将一组类别设置为仅限员工:
Category.where(id: category_ids).find_each do |category| category.set_permissions(:staff => :full) category.save! end -
多个权限。例如,将一组类别设置为对普通用户只读:
Category.where(id: category_ids).find_each do |category| category.set_permissions(:everyone => :readonly, :staff => :full) category.save! end -
用户组权限。例如,为一组类别中的一个组授予完全权限,另一个组授予只读权限:
artists_group = Group.find_by_name("artists") buyers_group = Group.find_by_name("buyers") Category.where(id: category_ids).find_each do |category| category.set_permissions(artists_group.id => :full, buyers_group.id => :readonly) category.save! end
-
基于关键词批量标记所有主题
以下脚本允许您根据主题标题或其帖子中是否存在关键词来标记主题。首先创建一个关键词数组:
rails c
keywords = ['apples','oranges']
接下来我们需要定义一个方法:
def tag_by_keyword(word, tag_name)
tag = Tag.find_by_name(tag_name) || Tag.create(name: tag_name)
keyword_topics = Topic.joins(:posts).where("topics.title ~* :keyword or posts.raw ~* :keyword", keyword: "\\y#{word}\\y").distinct
keyword_topics.each do |topic|
if topic.tags.exclude?(tag)
topic.tags << tag
end
end
end
最后将每个关键词通过该方法运行。以下示例将每个相关主题标记为“fruit”:
keywords.each { |word| tag_by_keyword(word, 'fruit') }
批量标记某个类别下的所有主题
模板:rake tags:bulk_tag_category["<tag>|<tag>",<category_id>]
这在尝试将类别转换为标签时特别有用。
首先,使用以下 rake 任务查找相关类别 ID。
rake categories:list
标记您指定的类别下的所有主题。在此示例中,您将为 ID 为 6 的类别下的所有主题添加“support”标签。
这将从每个主题中移除所有其他标签。
rake tags:bulk_tag_category["support",6]
追加您指定的类别下的所有主题。在此示例中,您将为 ID 为 6 的类别下的所有主题添加“support”标签,同时保留现有标签。
rake tags:bulk_tag_category["support",6,true]
将带有特定标签的所有主题移动到单个类别
在尝试重构您的 Discourse 站点时,您可能会发现希望移动一组主题而不触发任何通知。一种方法是创建一个临时标签,将其应用到相关主题,使用以下代码将主题移动到特定类别,最后删除临时标签。
获取标签。
rails c
tag = Tag.find_by_name("tutorial")
获取目标类别。
- 对于常规类别:
cat_to = Category.find_by_slug('guides')
- 对于子类别:
cat_to = Category.find_by_slug('child-slug','parent-slug')
将带标签的主题移动到目标类别。
Topic.joins(:topic_tags).where("topic_tags.tag_id = ?", tag.id).update_all(category_id: cat_to.id)
更新受影响类别的主题计数。
Category.update_stats
CategoryTagStat.update_topic_counts
将一个类别中的所有主题移动到另一个类别
使用以下 rake 任务查找类别 ID:
rake categories:list
第一个值应为起始类别 ID。第二个值应为目标类别 ID。
rake categories:move_topics[15,6]
Rails 控制台脚本
cat_from_id = XX # 要移出主题的类别 ID
cat_to_id = XX # 要移入主题的类别 ID
Topic.where(category_id: cat_from_id).update_all(category_id: cat_to_id)
Category.update_stats
CategoryTagStat.update_topic_counts
更改类别中所有主题的拥有者
使用以下 rake 任务查找类别 ID:
rake categories:list
指定新拥有者和要操作的类别。类别应为类别 ID 数组,示例中的类别 1、2 和 3:
rails c
user = User.find_by(username_lower: "lowercase-username")
categories = [1, 2, 3]
获取给定类别的所有主题 ID,并更改所有匹配主题中第一个帖子的拥有者。
topics = Topic.where(category_id: categories).pluck(:id)
topics.each do |topic|
PostOwnerChanger.new(
post_ids: Post.where(topic_id: topic).where(post_number: 1).pluck(:id),
topic_id: topic,
new_owner: user,
acting_user: Discourse.system_user,
skip_revision: true
).change_owner!
end
向所有组成员授予徽章
向属于特定组的所有用户授予徽章。第一个值是组 ID,第二个值是徽章 ID。
rails c
Group.find_by_name("event_participants").id
Badge.find_by_name("event_badge").id
exit
rake groups:grant_badge[42,102]
请注意,上述 rake 任务仅授予徽章,如果用户不再属于指定组,它不会撤销之前授予的徽章。如果您需要批量撤销不再属于某个组的所有用户的徽章,可以运行以下命令:
rails c
badge_id = Badge.find_by_name("Some Group Member").id
group = Group.find_by_name("Some_Group")
group_user_id = group.users.pluck("id")
userBadge = UserBadge.where.not(user_id: group_user_id).where(badge_id: badge_id)
userBadge.each do |ub|
BadgeGranter.revoke(ub, revoked_by: Discourse.system_user)
end
exit
确保所有用户处于其自动信任级别
假设您将新用户或受邀用户的默认信任级别设置为一个不太符合预期的值(例如 TL4)。现在您想更改它,使您的用户处于根据其当前统计信息自动获得的信任级别。以下命令将确保所有用户都处于其应有的信任级别,详见 https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/。**注意**:具有锁定信任级别的用户不会受到影响。
确保所有用户都设置为正确的信任级别:
rails c
User.all.find_each do |user|
Promotion.recalculate(user)
end
刷新组统计信息以反映更改:
Group.ensure_consistency!
主题维护脚本
以下 Ruby 脚本演示了如何根据活动日期和其他条件对主题执行自动化维护。这些脚本结合了 SQL 查询以识别主题,以及 Ruby 代码以对其执行操作,必须通过您站点的 Rails 控制台运行。
每个脚本都遵循类似的模式:
- 识别相关主题的 SQL 查询
- 处理每个主题并应用所需操作的 Ruby 代码
- 基本的错误处理和日志记录
这些脚本可以通过以下方式自定义:
- 调整时间段(例如,‘6 MONTH’、‘1 YEAR’、‘2 YEAR’)
- 更改类别选择以匹配您的论坛结构
- 修改要执行的操作(关闭、取消列出或移动)
- 添加其他条件,如帖子数量或查看阈值
关闭、取消列出并移动不活跃的主题
此脚本识别符合以下条件的主题:
- 在特定类别中
- 开放
- 未解决(使用 Discourse Solved 插件)
- 在特定时间段内无近期活动
然后执行多个操作:
- 关闭它们,
- 取消列出它们,
- 将它们移动到指定类别以存放过时内容
SQL 查询
WITH topic_list AS (
SELECT ua.target_topic_id, MAX(ua.created_at) "created_at"
FROM user_actions ua
INNER JOIN topics t ON t.id = ua.target_topic_id
INNER JOIN categories c ON c.id = t.category_id
LEFT JOIN discourse_solved_solved_topics solved ON solved.topic_id = t.id
WHERE t.closed = false
AND t.category_id = [CATEGORY_ID]
AND solved.topic_id IS NULL
AND t.deleted_at IS NULL
GROUP BY ua.target_topic_id
HAVING MAX(ua.created_at) <= (CURRENT_DATE - (INTERVAL '[TIME_PERIOD]'))
ORDER BY "created_at" DESC
)
SELECT '' AS total, target_topic_id AS topic_id, created_at
FROM topic_list
UNION
SELECT ''||COUNT(*), 0, CURRENT_DATE
FROM topic_list
ORDER BY created_at DESC
组合 SQL + 脚本
sql = "WITH topic_list AS (
SELECT ua.target_topic_id, MAX(ua.created_at) \"created_at\"
FROM user_actions ua
INNER JOIN topics t ON t.id = ua.target_topic_id
INNER JOIN categories c ON c.id = t.category_id
LEFT JOIN discourse_solved_solved_topics solved ON solved.topic_id = t.id
WHERE t.closed = false
AND t.category_id = [CATEGORY_ID]
AND solved.topic_id IS NULL
AND t.deleted_at IS NULL
GROUP BY ua.target_topic_id
HAVING MAX(ua.created_at) <= (CURRENT_DATE - (INTERVAL '[TIME_PERIOD]'))
ORDER BY \"created_at\" DESC
)
SELECT '' AS total, target_topic_id AS topic_id, created_at
FROM topic_list
UNION
SELECT ''||COUNT(*), 0, CURRENT_DATE
FROM topic_list
ORDER BY created_at DESC"
results = ActiveRecord::Base.connection.execute(sql)
user = Discourse.system_user
destination_category = Category.find([DESTINATION_CATEGORY_ID])
puts "Found #{results.count} topics to process"
results.each do |row|
begin
topic = Topic.find(row["topic_id"])
# 1. 移动到目标类别
topic.update!(category_id: destination_category.id)
puts "#{topic.id} moved to destination category"
# 2. 关闭主题
topic.update_status('closed', true, user, until: nil)
puts "#{topic.id} is closed"
# 3. 取消列出主题
topic.update_status('visible', false, user, until: nil)
puts "#{topic.id} is unlisted"
# 错误处理
rescue => e
puts "Error processing topic #{row["topic_id"]}: #{e.message}"
end
end
puts "Process completed"
关闭无近期活动的已解决主题
此脚本关闭已解决但已闲置一段时间的主题。这有助于保持论坛整洁,同时保留有价值的已解决主题。
此脚本识别符合以下条件的主题:
- 在特定类别中
- 开放
- 已解决(使用 Discourse Solved 插件)
- 在特定时间段内无近期活动
SQL 查询
WITH topic_list AS (
SELECT ua.target_topic_id, MAX(ua.created_at) "created_at"
FROM user_actions ua
INNER JOIN topics t ON t.id = ua.target_topic_id
INNER JOIN categories c ON c.id = t.category_id
INNER JOIN discourse_solved_solved_topics solved ON solved.topic_id = t.id
WHERE t.closed = false
AND t.category_id IN ([CATEGORY_IDS])
AND t.deleted_at IS NULL
GROUP BY ua.target_topic_id
HAVING MAX(ua.created_at) <= (CURRENT_DATE - (INTERVAL '[TIME_PERIOD]'))
ORDER BY "created_at" DESC
)
SELECT '' AS total, target_topic_id AS topic_id, created_at
FROM topic_list
UNION
SELECT ''||COUNT(*), 0, CURRENT_DATE
FROM topic_list
ORDER BY created_at DESC
组合 SQL + 脚本
sql = "WITH topic_list AS (
SELECT ua.target_topic_id, MAX(ua.created_at) \"created_at\"
FROM user_actions ua
INNER JOIN topics t ON t.id = ua.target_topic_id
INNER JOIN categories c ON c.id = t.category_id
INNER JOIN discourse_solved_solved_topics solved ON solved.topic_id = t.id
WHERE t.closed = false
AND t.category_id IN ([CATEGORY_IDS])
AND t.deleted_at IS NULL
GROUP BY ua.target_topic_id
HAVING MAX(ua.created_at) <= (CURRENT_DATE - (INTERVAL '[TIME_PERIOD]'))
ORDER BY \"created_at\" DESC
)
SELECT '' AS total, target_topic_id AS topic_id, created_at
FROM topic_list
UNION
SELECT ''||COUNT(*), 0, CURRENT_DATE
FROM topic_list
ORDER BY created_at DESC"
results = ActiveRecord::Base.connection.execute(sql)
user = Discourse.system_user
puts "Found #{results.count} topics to process"
results.each do |row|
begin
topic = Topic.find(row["topic_id"])
# 关闭主题
topic.update_status('closed', true, user, until: nil)
puts "#{topic.id} is closed"
# 错误处理
rescue => e
puts "Error processing topic #{row["topic_id"]}: #{e.message}"
end
end
puts "Process completed"
归档之前已关闭的主题
此脚本识别在特定日期之前已关闭的主题,并将它们移动到归档类别,同时取消列出它们。
SQL 查询
WITH topic_list AS (
SELECT
t.id AS topic_id,
tt.execute_at AS closed_at
FROM topics t
INNER JOIN categories c ON c.id = t.category_id
LEFT JOIN topic_timers tt ON tt.topic_id = t.id AND tt.status_type IN (1, 8)
WHERE t.closed = true
AND t.category_id IN ([CATEGORY_IDS])
AND t.deleted_at IS NULL
AND tt.execute_at IS NOT NULL
AND tt.execute_at <= (CURRENT_DATE - INTERVAL '[TIME_PERIOD]')
ORDER BY tt.execute_at DESC
)
SELECT '' AS total, topic_id, closed_at
FROM topic_list
UNION
SELECT ''||COUNT(*), 0, CURRENT_DATE
FROM topic_list
ORDER BY closed_at DESC
组合 SQL + 脚本
sql = "WITH topic_list AS (
SELECT
t.id AS topic_id,
tt.execute_at AS closed_at
FROM topics t
INNER JOIN categories c ON c.id = t.category_id
LEFT JOIN topic_timers tt ON tt.topic_id = t.id AND tt.status_type IN (1, 8)
WHERE t.closed = true
AND t.category_id IN ([CATEGORY_IDS])
AND t.deleted_at IS NULL
AND tt.execute_at IS NOT NULL
AND tt.execute_at <= (CURRENT_DATE - INTERVAL '[TIME_PERIOD]')
ORDER BY tt.execute_at DESC
)
SELECT '' AS total, topic_id, closed_at
FROM topic_list
UNION
SELECT ''||COUNT(*), 0, CURRENT_DATE
FROM topic_list
ORDER BY closed_at DESC"
results = ActiveRecord::Base.connection.execute(sql)
user = Discourse.system_user
archive_category = Category.find([ARCHIVE_CATEGORY_ID])
puts "Found #{results.count} topics to process"
results.each do |row|
begin
topic = Topic.find(row["topic_id"])
# 1. 移动到归档类别
topic.update!(category_id: archive_category.id)
puts "#{topic.id} moved to archive category"
# 2. 取消列出主题
topic.update_status('visible', false, user, until: nil)
puts "#{topic.id} is unlisted"
# 错误处理
rescue => e
puts "Error processing topic #{row["topic_id"]}: #{e.message}"
end
end
puts "Process completed"
破坏性 rake 任务
删除整个类别
以下命令允许您销毁多个类别,以及属于这些类别的任何子类别和主题。
打印类别 ID 列表
rake categories:list
根据 ID 销毁一组类别
rake destroy:categories[10,11,12,18,30]
删除某个类别中的所有主题
移除所有私人消息
rake destroy:private_messages
销毁所有组
rake destroy:groups
销毁所有非管理员用户
rake destroy:users
销毁站点统计信息
rake destroy:stats
匿名化所有非员工用户
rake users:anonymize_all
永久删除一组帖子
以下 rake 任务将根据 ID 硬删除帖子列表。**如果帖子是主题中的第一个帖子,则该主题中的所有帖子都将被硬删除。**在成功运行任务之前,必须启用 can_permanently_delete 站点设置 启用。
一旦帖子被此任务删除,它将不再存在于数据库中,且无法恢复。
有两种可能的方法:
-
选项 1 – 将逗号分隔的帖子 ID 列表作为参数传递
rake destroy:posts[4,8,15,16,23,42] -
选项 2 – 指定一个包含逗号分隔的帖子 ID 列表的文本文件(适用于大量帖子)。
cat post_ids.txt | rake destroy:posts
我尝试在此主题中包含最有用的 rake 任务,但 Discourse 还打包了许多其他任务。如果您想查看完整列表,可以使用以下命令:
所有带有描述的任务
rake --tasks
所有任务,包括没有描述的任务
rake -AT