Class DatabaseBackupService

java.lang.Object
server.backup.DatabaseBackupService

public final class DatabaseBackupService extends Object
資料庫自動備份服務(單例):每隔一段時間把整個資料庫匯出成 .sql 並壓成 .zip, 存到專案根目錄下的 Backup/ 資料夾(檔名為 日期_時間.zip,例:2026-05-23_01.58.49.zip)。

實作方式:沿用 DatabaseBootstrap 呼叫外部 mysql.exe 的相同模式, 改呼叫同一個 bin 目錄下的 mysqldump.exe(自動偵測,亦可由 settings.iniMySQL_DumpBin 覆寫)。匯出以單一資料庫模式(mysqldump ... <DB_Name>),輸出的 SQL 僅含該庫的表結構與資料、不含 CREATE DATABASEUSE 語句 —— 還原時需先選定 目標資料庫再匯入(例:mysql -u... <DB_Name> < 檔案.sql)。另帶 --single-transaction 對 InnoDB 做一致性快照、不鎖表,故線上備份不影響玩家。

開關與排程:整個功能受 settings.ini 三鍵控制(皆對應 ServerConstants 欄位、可由日後的 GUI 即時調整):

  • DbBackupEnabled —— 總開關(預設 false)。為了讓 GUI 能不重啟即時開關, 排程一律註冊,是否真的執行於每次觸發時依此旗標判定。
  • DbBackupIntervalMinutes —— 幾分鐘備份一次(預設 60<=0 表示不排程)。
  • DbBackupKeep —— 最多保留幾份 zip、自動刪除較舊者(0 = 不限、全部保留)。
排程掛在 Timer.WorldTimer 上;首次備份於開服後一個間隔才發生(不在開機瞬間搶資源)。

穩健性:backupNow()AtomicBoolean 防止前一次備份未完成時重入;任何例外 只記錄、不向上拋(背景任務不可中斷)。mysqldump 的輸出由 --result-file 直接寫入暫存 .sql(二進位安全、避開 stdout 管線阻塞),壓成 zip 後刪除暫存檔。

  • Method Details

    • getInstance

      public static DatabaseBackupService getInstance()
      取得單例。
    • start

      public void start()
      註冊背景備份排程(由 server.Start.run()Timer.WorldTimer 啟動後呼叫一次)。

      只要 DbBackupIntervalMinutes > 0 就會註冊週期任務;是否真的備份於每次觸發時依 DbBackupEnabled 判定,故 GUI 之後切換開關可不重啟即生效。<=0 則完全不排程。

    • reschedule

      public void reschedule(int min)
      依新的備份間隔即時重排程(供 GUI 調整 DbBackupIntervalMinutes 後立即生效,免重啟)。

      若服務尚未 啟動(開機流程還沒跑到註冊排程那步),則直接返回—— 開機流程會以當下的設定自行排程,無需此處介入。

      Parameters:
      min - 新的備份間隔(分鐘);<= 0 表示停用週期備份(僅取消、不再排程)
    • listBackups

      列出 Backup/ 下所有備份 zip,依檔名時間戳由新到舊排序。

      檔名時間戳格式為 yyyy-MM-dd_HH.mm.ss,字典序即時間序,故以檔名遞減排序即「新→舊」。 備份資料夾不存在(尚未產生任何備份)時回傳空清單;個別檔案讀取屬性失敗時略過該筆並記警告。

      Returns:
      備份檔描述清單(新→舊);無備份或資料夾不存在時為空清單
    • backupNow

      public Path backupNow()
      立即執行一次資料庫備份(供排程與日後 GUI 手動觸發共用)。

      前一次備份尚未完成時會略過本次並警告一次。任何例外只記錄、不拋出。

      Returns:
      成功時回傳產生的 zip 路徑;失敗、被略過或設定不全時回傳 null
    • restoreFrom

      public String restoreFrom(String name)
      從某個既有備份 zip 還原(覆寫)線上資料庫。這是破壞性操作:會把目前資料庫的內容換成備份當時的狀態。

      沿用 DatabaseBootstrap.importSql(String, String, String, String, String, Properties) 的「呼叫外部 mysql.exe、把 SQL 餵入 stdin、 去除開頭 UTF-8 BOM」流程:自 zip 取出單一 .sql(優先 <DB_Name>.sql)後匯入目標資料庫。

      防護:

      • 防重疊:與備份共用 running 旗標(compareAndSet),任一還原/備份進行中即拒絕本次("busy")。
      • 防路徑穿越:僅接受 .zip 結尾、不含 / \ .. 的純檔名,且解析後的路徑其父目錄須等於 Backup/ 本身(resolve(name).normalize() 後比對父目錄),杜絕以檔名跳出備份資料夾。
      Parameters:
      name - 備份檔名(須位於 Backup/ 下、以 .zip 結尾)
      Returns:
      成功回 null;否則回錯誤碼:"busy"(有另一作業進行中)、"badName"(檔名非法/路徑穿越)、 "notfound"(檔案不存在)、"noTool"(找不到 mysql.exe)、"badEntry"(zip 內無可用的 .sql)、 "importFailed"(匯入失敗或設定不全)