iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Build on Google AI

《30天打造喵語日誌:Gemini API × Vibe Coding 實戰》系列 第 19

Day 19:【Firestore 儲存】將對話紀錄與情緒標籤寫入雲端資料庫:設計高效又省錢的 NoSQL 文件結構!

  • 分享至 

  • xImage
  •  

邁向雲端資料持久化:讓互動永恆留存

昨天完成 Firebase Auth 匿名登入後,每位造訪《喵語日誌》的使用者都擁有了專屬且唯一的 UID。

不過,目前使用者的對話內容,以及 Gemini 分析出的情緒分數,仍然只存在於 React 的前端記憶體中。只要重新整理網頁,剛剛的對話、情緒紀錄與貓咪的暖心回覆就會全部重置。

如果想讓《喵語日誌》成為真正具備記錄價值的情緒日記,就必須將資料保存到雲端。

因此,今天正式接通 Google Cloud Firestore,從 NoSQL 資料結構規劃開始,完成第一筆日記資料的雲端寫入,讓每一次與貓咪的互動都能被妥善保存。

Firestore 資料結構設計

在關聯式資料庫中,我們習慣使用資料表與外鍵建立資料之間的關聯;但 Firestore 是以 Collection 與 Document 為核心的 NoSQL 資料庫,因此資料結構需要換一種方式思考。

這次採用的資料路徑如下:

users/{uid}/diaries/{diaryId}

其中:

  • users 是使用者集合

  • {uid} 是 Firebase Auth 建立的使用者識別碼

  • diaries 是該使用者的日記子集合

  • {diaryId} 是每一筆日記文件的唯一 ID

每次使用者完成一筆對話與情緒分析,就會在自己的 diaries 子集合中建立一份新的 Document。

為什麼使用 Subcollection?

這種階層式結構主要有幾個好處。

首先,每位使用者的資料都會依照 UID 分隔,未來設定 Firestore Security Rules 時,可以精確限制使用者只能讀取與修改自己的資料。

其次,每筆日記都是獨立文件,不需要將所有紀錄不斷累積在同一個陣列中。長期使用時,可以避免單一文件內容無限膨脹,也更適合之後進行分頁、排序與條件查詢。

最後,當未來要載入情緒圖表或歷史紀錄時,前端可以只查詢需要的資料,例如特定月份或最近幾筆日記,降低不必要的資料傳輸與讀取成本。

日記文件欄位規劃

每筆日記文件會保存一次完整的互動資料,包括使用者輸入、心情標籤、Gemini 分析結果與貓咪回覆。

目前規劃的欄位如下:

userText         使用者輸入的文字
moodKey          使用者選擇的心情標籤
moodScore        Gemini 評估的情緒分數
lifestyleLabel   生活場景標籤
catResponse      貓咪回覆內容
imageBase64      上傳的圖片資料
createdAt        Firestore 伺服器時間

這樣的設計可以讓原始互動內容與 AI 分析後的結構化結果同時被保存。

未來在製作情緒折線圖、情緒日曆或成就判定時,就能直接使用這些資料進行計算。

建立 Firestore 資料庫

接著,我在 Firebase Console 中啟用 Cloud Firestore 資料庫,並選擇距離使用者較近的亞洲區域作為資料庫位置(如 asia-east1),希望降低資料傳輸延遲。

開發初期為了方便測試,暫時使用測試模式規則。但這只適合開發階段使用,正式上線前仍然必須設定嚴謹的 Security Rules,避免未授權的使用者讀取或修改其他人的資料。

封裝 Firestore 寫入函式

完成資料庫建立後,我在 src/services/firebase.ts 中封裝 saveDiaryEntry 函式,負責將單筆日記寫入使用者專屬的子集合。

// src/services/firebase.ts
import {
  addDoc,
  collection,
  serverTimestamp,
} from "firebase/firestore";

export interface DiaryEntryData {
  userText: string;
  moodKey?: string | null;
  moodScore: number;
  lifestyleLabel: string;
  catResponse: string;
  imageBase64?: string | null;
}

export async function saveDiaryEntry(
  uid: string,
  entry: DiaryEntryData
) {
  try {
    const userDiariesRef = collection(
      db,
      "users",
      uid,
      "diaries"
    );

    const docRef = await addDoc(userDiariesRef, {
      ...entry,
      createdAt: serverTimestamp(),
    });

    console.log(
      "✅ 日誌已成功存入 Firestore,Doc ID:",
      docRef.id
    );

    return docRef.id;
  } catch (error) {
    console.error("❌ 寫入 Firestore 失敗:", error);
    throw error;
  }
}

這裡使用 addDoc 新增文件,Firestore 會自動產生唯一的 Document ID。

另外,createdAt 使用 serverTimestamp(),而不是直接使用瀏覽器的本地時間。這樣可以避免因為使用者裝置時間不正確,造成日記排序或日期判斷錯亂。

串接 Gemini 分析與雲端寫入

完成服務函式後,接著在 ChatContainer.tsx 中整合資料寫入流程。

當使用者送出訊息後,程式會先呼叫 Gemini 進行分析,取得情緒分數、生活場景標籤與貓咪回覆;畫面更新完成後,再將完整資料寫入 Firestore。

// src/components/ChatContainer.tsx

const result = await analyzeDiary(
  trimmed,
  currentMood,
  currentImage
);

const catReply: Message = {
  id: Date.now() + 1,
  role: "cat",
  text: result.cat_response,
};

setMessages((prev) => [...prev, catReply]);

if (auth.currentUser) {
  await saveDiaryEntry(auth.currentUser.uid, {
    userText: trimmed,
    moodKey: currentMood,
    moodScore: result.mood_score,
    lifestyleLabel: result.lifestyle_label,
    catResponse: result.cat_response,
    imageBase64: currentImage,
  });
}

目前的完整流程如下:

使用者輸入文字或圖片
        ↓
呼叫 Gemini 進行分析
        ↓
取得情緒分數與貓咪回覆
        ↓
更新前端對話畫面
        ↓
將資料寫入 Firestore

這也代表《喵語日誌》第一次打通了:

AI 分析 → 前端回覆 → 雲端存檔

成果驗收:第一筆資料成功寫入

完成整合後,我在前端選取「😻 Great」心情,並輸入以下測試內容:

我今天在家裡休息一整天,好開心~

送出訊息後,Gemini 成功產生貓咪回覆與情緒分析結果,接著程式也在背景呼叫 saveDiaryEntry,將資料寫入 Firestore。

https://ithelp.ithome.com.tw/upload/images/20260819/201787085Y1nmqOhFM.png
▲ 前端成功觸發 Gemini 回覆,並在 Console 印出 Firestore Document ID:YkODIw7iim4s8LsaXjOh

接著進入 Firebase Console 的 Firestore 管理介面確認資料是否正確落地。

https://ithelp.ithome.com.tw/upload/images/20260819/20178708sZYOGdc4yB.png
▲ Firestore 後台成功在 users/{uid}/diaries 子集合下建立文件,並保存情緒分數、生活標籤與伺服器時間戳記

這次實作的心得

這次整合 Firestore 寫入的過程相當順利,也讓我更清楚理解前端應用程式如何與雲端資料庫高效互動。

透過將 Gemini 分析出的結構化資料,與原始的文字、圖片及貓咪回覆一起保存,我們不只解決了重新整理後資料消失的問題,也為後續的情緒折線圖、情緒日曆、每週情緒統計與成就解鎖系統備妥了真實且高品質的數據來源。

這次實作也提醒我,資料庫不只是把資料存起來而已,資料結構是否清晰,會直接影響後續的查詢方式、元件設計與功能擴充性。

結語

今天我們成功打通了 Firestore 雲端資料庫寫入!從匿名登入、UID 識別到子集合與文件欄位規劃,每一則與貓咪的互動、情緒分數及 Gemini 分析出的生活標籤,都能完整且安全地保存在使用者專屬的雲端空間中。

明天我們將邁入數據動態同步階段,實作非同步資料載入與 React 狀態提升,透過 getDocsonSnapshot 從 Firestore 讀取紀錄,並將數據即時渲染至情緒日曆與折線圖中,讓重新整理後的圖表也能精準呈現,讓《喵語日誌》的資料真正活起來!

明天見,喵~


上一篇
Day 18:【Firebase 初始化】建立雲端後端與 Auth 匿名登入:打造專屬使用者的隱形身分證
下一篇
Day 20:【數據動態同步】從 Firestore 讀取紀錄並渲染至情緒日曆與折線圖:狀態提升與即時監聽實戰!
系列文
《30天打造喵語日誌:Gemini API × Vibe Coding 實戰》20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言