HydroOJ 从入门到入土(13)批量修改题号前缀
题库的管理,无论是用前缀来分组,还是用域来分组,都有不好管理的地方,尤其是题号。有的时候导入了一堆题,导入完发现题号不是自己想要的,但删起来很麻烦,一个一个改更不现实,真是欲哭无泪。
本文主要记录了一次批量修改题号前缀的过程,仅供参考。
修改中涉及数据库操作,修改前一定要现在虚拟机上试过,然后做好备份!!!
数据库中需要修改的字段为本人自测,目前并未获得官方回复,如有回复再做更新。
想法
主要需求是:在某一个域中,修改以指定前缀开头的一系列题目,把字母前缀改成别的。至于将指定范围内、或指定标签的所有题目改成别的前缀,这个以后再说。
我这次想将之前导入的所有的 P
前缀的一本通启蒙题目改成 B
前缀,一方面 B
既表示基础,排的也靠前,方便刚入门的学生查找;另一方面后边加其他前缀也不会影响 B
题库的位置,可以有 24 个字母放心加。
后期配合插件,在搜索栏下方做一排标签,就可以方便的在同一个域内跳转到相应题库,实现分组。这样可以避免使用域分组带来的无法直接引用题目的问题。
至于为什么不在一开始导入的时候就定好前缀?一是没有人一开始就能定下所有的事情,总会修修改改,二是可能会有误导入的情况。
一、备份
备份有两种,一种是在终端中直接使用 HydroOJ 自带的全量备份:
hydrooj backup
好处是方便安全,但坏处是,如果题库太大了,备份一次占地方不说,还会很久,恢复备份同理。
当然这次我为了安全,在本地虚拟机上测试的时候,还是用 hydrooj backup
做了全量备份,当做保底,然后准备用第二种方法:直接备份 MongoDB 数据库,还原的时候也是直接还原数据库。
这样的好处是快,备份和还原都快,改错了也不怕,可以快速修正。坏处是,还原的时候因为需要先清空数据库,所以带来一定的风险。
实际生产环境下,强烈建议两种备份都做好,放心点。
需要额外参考点这里查看官方文档。
MongoDB 备份
打开终端,以 root 用户身份运行:
# 备份到当前目录(.), 需要认证,备份完会出现一个数据库同名文件夹(hydro)
mongodump --db hydro --out . -u hydro -p 换成你的数据库密码 --authenticationDatabase hydro
这里如果需要备份到指定目录,就把 --out
后边的 .
换成指定目录就行了。
备份完记得确认当前目录下是否已经出现了名为 hydro
的备份文件夹,大概几十mb,里边是一堆 bson
格式的文件!
MongoDB 还原
打开终端,以 root 用户身份运行:
# 从hydro恢复数据库, 需要认证(注意!需要先用--drop清空数据库,一定先做好备份!)
mongorestore --drop --db hydro hydro -u hydro -p 换成你的数据库密码 --authenticationDatabase hydro
注意!恢复备份的时候,需要先用 --drop
清空数据库,不然无法恢复,一定先做好备份!
二、分析数据库
如果是在生产环境,除了先备份,还要找个人少的时间。
第一步,先进入 MongoDB 的 shell,有两种方法,一种是直接使用 mongosh
加一堆认证参数,比较麻烦,这里建议直接使用以下自带命令进入:
hydrooj db
这样进来就是 hydro
的数据库。
先用 navicat(使用说明)查看数据库的结构,发现 pid
存储于 document
表中,尝试手动修改了一个题,发现并未修改显示位置,而且无法打开题目了。需要同时修改 pid
和 sort
字段。至于是否需要修改其他更多字段,等官方回复再说。
以及,这个 document
表里边存的不光是题,还有作业 / 比赛 / 训练 等等,以 docType
来区分类型,数字类型,题目的 docType
是 10
。
还有一个会影响定位的是 domainId
,这里我需要修改的是 system
。
最后,pid
的格式均为 字母+数字[+字母]
的形式,所以需要用正则把字母前缀抠出来,然后再用正则替换掉。
为了测试定位,我先用 find
只显示 pid
和 title
,查看定位是否精准:
db.document.find(
{
domainId: "system",
docType: 10,
pid: { $regex: /^P/i }
},
{
_id: 0, // Exclude the default _id field from the output
pid: 1, // Include the pid field
title: 1 // Include the title field
}
)
成功的话,应该会返回一堆结果,如下:
[
{ title: '【例2.1】Hello World', pid: 'P1' },
{ title: '【例4.1】 交换两个数的位置', pid: 'P10' },
...
]
Type "it" for more
三、修改数据库
使用 updateMany
对数据进行修改。
运行下列代码之前,应先检查:
- MongoDB 的备份是否完成?
- HydroOJ 的备份是否完成?
- 是否经过了本地虚拟机测试?
- (生产环境)是否是无人使用的状态?
- (生产环境)是否确认好了要修改的关键信息?
全部一一确认好之后,再使用下列代码:
db.document.updateMany(
{
domainId: "system",
docType: 10,
pid: { $regex: /^P/i }
},
[
{
$set: {
pid: {
$concat: [
"B",
{ $substrBytes: ["$pid", 1, { $subtract: [{ $strLenBytes: "$pid" }, 1] }] }
]
}
}
},
{
$set: {
sort: {
$concat: [
"B",
{ $substrBytes: ["$sort", 1, { $subtract: [{ $strLenBytes: "$sort" }, 1] }] }
]
}
}
}
]
)
这里的实现比较暴力,就是直接连接 B
和原字符串剩余的 len-1
个字符。如果修改的是两个字母的,那需要把这一行最后一个 1
改成 2
,以此类推。
有更好的实现欢迎提供建议。
如果运行成功,将会出现运行成功的通知:
{
acknowledged: true,
insertedId: null,
matchedCount: 480, // 找到题数,可能会不一样
modifiedCount: 480, // 已修改题数,可能会不一样
upsertedCount: 0
}
四、会有副作用吗
OJ 内部引用实际上用的都是题目的 id
,在数据库中的 field 是 docId
,所以理论上应该不受影响。
简单测试了一下作业、比赛和训练,目前没发现有什么影响。
如果你修改完发现有什么地方受影响,可以随时联系我。