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 表中,尝试手动修改了一个题,发现并未修改显示位置,而且无法打开题目了。需要同时修改 pidsort 字段。至于是否需要修改其他更多字段,等官方回复再说。

以及,这个 document 表里边存的不光是题,还有作业 / 比赛 / 训练 等等,以 docType 来区分类型,数字类型,题目的 docType10

还有一个会影响定位的是 domainId,这里我需要修改的是 system

最后,pid 的格式均为 字母+数字[+字母] 的形式,所以需要用正则把字母前缀抠出来,然后再用正则替换掉。

为了测试定位,我先用 find 只显示 pidtitle,查看定位是否精准:

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 对数据进行修改。

运行下列代码之前,应先检查:

  1. MongoDB 的备份是否完成?
  2. HydroOJ 的备份是否完成?
  3. 是否经过了本地虚拟机测试?
  4. (生产环境)是否是无人使用的状态?
  5. (生产环境)是否确认好了要修改的关键信息?

全部一一确认好之后,再使用下列代码:

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,所以理论上应该不受影响。

简单测试了一下作业、比赛和训练,目前没发现有什么影响。

如果你修改完发现有什么地方受影响,可以随时联系我。

posted @ 2024-02-15 21:24  Bowen404  阅读(383)  评论(0编辑  收藏  举报