昨天的 /meeting 已經能把兩輪討論、審查與最終方案串起來。不過專案狀態、Guild 目前的專案,以及會議紀錄,原本分別放在三個 JSON 檔案中。
JSON 檔其實也能在 Bot 重啟後保留資料。這次改用 MySQL,主要是想讓相關資料有明確的關聯,並在一次操作要更新多張表時使用交易,避免只寫入一半。後續要查詢專案與會議,也比較容易從資料表著手。
今天的目標是讓正式啟動的 Bot 改用 MySQL Repository,同時把原有 JSON 資料匯入。資料庫的表格和匯入結果,我用 DBeaver Community 查看;圖片稍後再補。
專案使用 docker-compose.yml 啟動 MySQL 8.4。我的電腦已有服務使用 3306,所以這個容器對外改用 3307;容器內仍是 MySQL 的 3306。
services:
mysql:
image: mysql:8.4
ports:
- "3307:3306"
volumes:
- ai_company_mysql_data:/var/lib/mysql
ai_company_mysql_data 是具名 volume。重建容器時資料仍保存在 volume 中。Compose 也設定健康檢查,方便確認服務已能接受連線。
目前 Docker 只負責 MySQL;Python Bot 仍在 Mac 上執行。
設定好 .env 後,在專案根目錄執行:
docker compose up -d mysql
docker compose ps
這次檢查時,ai-company-mysql 顯示為 healthy,主機連接埠是 3307。
.envBot 使用 ai_company_app,只授權存取 ai_company 資料庫所需的 SELECT、INSERT、UPDATE、DELETE,不使用 root 帳號處理日常查詢。
連線設定由 config.py 從 .env 讀取。下面只列欄位名稱,不放真實密碼:
DB_HOST=127.0.0.1
DB_PORT=3307
DB_NAME=ai_company
DB_USER=ai_company_app
DB_PASSWORD=請填入自己的密碼
MYSQL_ROOT_PASSWORD=請填入另一組密碼
.env 要留在 .gitignore,文章與截圖也不要露出密碼。DB_PORT=3307 是這個專案目前的 Docker 設定;如果你用自己的 MySQL,應填實際連接埠。
database/migrations/001_initial_schema.sql 建立了專案、需求變更、Guild 狀態與會議相關資料表。這次把需要查詢的固定欄位分開存放,像專案標題、狀態、需求與驗收條件;Agent 回覆、PM 草案和最終方案則放在 MySQL 的 JSON 欄位。
| 資料表 | 用途 |
|---|---|
projects、project_requirements、project_acceptance_criteria |
專案基本資料、需求與驗收條件 |
requirement_changes |
專案的需求變更 |
guild_current_projects |
每個 Discord Guild 正在處理的專案 |
meetings、meeting_steps |
會議狀態、各 Agent 步驟與結果 |
guild_latest_meetings |
Guild 對應的最新會議 |
例如 meeting_steps.meeting_id 會連到 meetings.id。專案相關表格也有外鍵,避免存入找不到對應專案的需求變更。索引則放在常用的狀態與查詢欄位上。
Bot 原本透過 JSON Repository 讀寫資料。現在新增 MySQLProjectRepository、MySQLGuildProjectStore 和 MySQLMeetingRepository,正式啟動時由 bot.py 注入這三個版本。舊的 JSON Repository 仍保留,供匯入與測試使用。
資料庫連線集中由 MySQLDatabase 管理。它用 aiomysql.create_pool() 建立連線池,Repository 需要查詢時再借用連線;Bot 關閉時會關閉連線池。這樣不必為每次 Agent 步驟重新建立資料庫連線。
服務層現在能等待非同步 Repository 的結果,Discord 指令也改用 await 讀取專案與會議資料。原本的會議流程不用直接寫 SQL,仍由 Repository 負責資料儲存。
最容易出問題的是「專案已改成進行中,但 Guild 尚未指向該專案」。這兩筆資料應該一起成功,或一起取消。
MySQLDatabase.transaction() 會在操作成功時 commit(),遇到錯誤時 rollback()。claim_for_guild() 也會使用 SELECT ... FOR UPDATE,在交易中處理專案狀態與 Guild 目前專案。
保存會議時,MySQLMeetingRepository 在同一筆交易內更新會議主檔、Agent 步驟及 Guild 最新會議索引。至於呼叫 Ollama 或傳送 Discord 訊息,則不放進資料庫交易,避免長時間占住連線與鎖。
專案已有 projects.json、guild_projects.json、meetings.json。匯入腳本會讀取這三份資料,先檢查數量,再決定是否寫入 MySQL。
python scripts/migrate_json_to_mysql.py --dry-run
目前輸出為 projects=5 guilds=1 meetings=1。--dry-run 只統計,不會寫入資料庫。確認內容後,才使用 --apply 匯入:
python scripts/migrate_json_to_mysql.py --apply
匯入腳本採用既有 ID 更新或新增資料,重跑不會產生重複 ID。原本的 JSON 檔沒有被刪除,正式執行的 Bot 則改從 MySQL 讀取。
我使用 DBeaver Community 連到這個 MySQL。連線資料填入 Host 127.0.0.1、Port 3307、Database ai_company、User ai_company_app,密碼從自己的 .env 取得,不放進文章或截圖。
連線後可以在 Database Navigator 展開資料庫,檢查 projects、meetings、meeting_steps 等表格及匯入結果。這比只看終端機的匯入訊息更直觀,也方便對照某筆會議的 final_proposal 是否有保存。
不過 output_data、final_proposal 是 JSON 欄位,在 DBeaver 裡直接看仍是原始資料。DBeaver 適合檢查資料有沒有寫入、表格如何關聯;給 Discord 使用者看的會議結果,仍應由 Bot 整理成可讀訊息。

這次重新執行一般測試,結果是 Ran 85 tests、OK (skipped=2)。被跳過的兩項是需要設定 RUN_MYSQL_TESTS=1 才會執行的實際 MySQL 整合測試;DAY 22 實作報告另記錄這兩項已在 Docker MySQL 上通過。
報告也記錄:匯入了 5 個專案、1 筆 Guild 狀態與 1 筆會議;關閉並重開連線池後,仍可讀回同一筆會議。Bot 已成功開啟 MySQL 連線池、同步 8 個 Slash Command 並登入 Discord Gateway。
這些結果驗證了資料庫連線、匯入與 Repository 讀寫。文章圖片補上後,還可以在 DBeaver Community 對照資料表內容,以及 Bot 重啟後的 Discord 查詢結果。
這是資料庫的圖片,證明有把資料寫在資料庫裏。
DAY 22 把正式執行時的專案與會議資料搬進 MySQL。比較重要的改動是讓多張表的更新在交易中一起完成,並把資料庫連線與 SQL 留在 Repository 層。
原本的 JSON 資料還在,匯入前也能先用 dry-run 檢查。接下來的功能會繼續使用這些 MySQL 資料,而 DBeaver Community 可以幫忙直接確認保存結果