
昨天我們成功啟動了 Java 21 + Spring Boot 3 的伺服器,今天我們終於要變得更強!將 Day 5 申請的 Gemini API 整合進後端,建立一個專屬的 HTTP Client 了!
我們不打算手動複製金鑰到 IDE 的環境變數裡。今天我們要挑戰的是:使用 Spring Boot 3 的最新技術,結合 Google Cloud Secret Manager 與 ADC (Application Default Credentials),優雅且極度安全地完成這次串接。同時,在正式發送請求前,我們也要釐清 API 的計費層級,確保你的錢包跟程式碼一樣安全!
在把金鑰寫進專案前,我們必須先了解你的 Google Cloud 帳號層級帶來的影響:
| 評估維度 | Free Tier (免費試用層級) | Tier 1 (Pay-as-you-go 付費層級) |
|---|---|---|
| 開通方式 | 註冊 Google AI Studio 即可使用 | 綁定 Google Cloud 帳單與信用卡後升級 |
| 速率限制 (Rate Limit) | 限制較嚴格 (如 15 RPM) | 額度大幅提升,適合正式環境 |
| 資料隱私 | 資料可能被用於改進模型 | 承諾資料隱私保護,不被訓練 |
| 計費方式 | 一定量完全免費 | 依百萬 Token 數精準計費 |
如果只是個人本地端測試,Free Tier 相當夠用;但當我們準備將 API 部署上線時,升級至 Tier 1 才能確保高併發流量下不被限流,同時保障企業級的資料隱私,由於我的帳號目前是Tier 1(帳號被默默地升上去,開KAKERU專案後怎麼選都沒辦法改成免費的😭),所以先以Tier 1來說明。
在 Spring Boot 3.2 以前,我們發送 HTTP 請求通常有兩個選擇:
而全新引入的 RestClient 完美解決了這個痛點!它擁有流暢的 Fluent API,底層依然保持同步呼叫的輕量,寫起來卻像 WebClient 一樣優雅,絕對是目前串接 RESTful API 的最佳選擇。
為了達成「本機不寫死金鑰、程式碼不見機密」的最高資安原則,我們透過以下兩步驟來實現:
1. 本機驗證 (ADC):
請在你的終端機執行以下指令:gcloud auth application-default login
執行後,系統會生成 ADC。這讓 Spring Boot 在本地開發時,能直接「代表你的權限」連線至 Google Cloud Secret Manager,完全不需要手動複製貼上金鑰。
2. 引入依賴與配置導入:
在實作前,請務必確認你的 pom.xml 已經引入了 Google Cloud 專屬套件,但你也可以透過下 Prompt的方式來達成。
你可以透過下Prompt的方式請AI幫你產生:
請給我幫達成Spring Boot 3,將 GCP Secret Manager 動態注入 Gemini API Key 的方法。
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-Google Cloud-starter-secretmanager</artifactId>
</dependency>
接著,在 application.yml 中可以看到此設定:
spring:
config:
import: "sm://" # 核心:告知 Spring Boot 啟動時加載 Google Cloud Secret Manager 屬性源
cloud:
Google Cloud:
project-id: kakeru-ai # 指定你的 Google Cloud 專案 ID
gemini:
api:
# 核心:透過 sm:// 協議自動注入 Google Cloud Secret Manager 中的最新金鑰
key: ${sm://projects/kakeru-ai/secrets/GEMINI_API_KEY/versions/latest}
model: gemini-3.5-flash-lite
url: https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash-lite:generateContent
叫 Gemini API 時,最大的痛點是它的 JSON 結構層級較深(contents -> parts -> text)。
我們直接把需求拋給 AI:
我想用 Spring Boot 3 的 RestClient 寫一個 GeminiService 來發送文字請求,請幫我寫 Service 實作。
AI 立刻運用建構子注入與 API 寫出了乾淨的實作:
@Service
public class GeminiServiceImpl implements GeminiService {
private final RestClient restClient;
private final String apiKey;
private final String apiUrl;
// 透過建構子注入 (Constructor Injection)
public GeminiServiceImpl(
RestClient.Builder restClientBuilder,
@Value("${gemini.api.key}") String apiKey,
@Value("${gemini.api.url}") String apiUrl) {
this.restClient = restClientBuilder.build();
this.apiKey = apiKey;
this.apiUrl = apiUrl;
}
@Override
public String generateText(String prompt) {
// 透過 RestClient 發送 POST 請求並帶入由 Secret Manager 注入的金鑰
GeminiResponse response = restClient.post()
.uri(apiUrl + "?key=" + apiKey)
.contentType(MediaType.APPLICATION_JSON)
.body(new GeminiRequest(prompt))
.retrieve()
.body(GeminiResponse.class);
return response != null ? response.extractText() : "";
}
}
透過@Value("${gemini.api.key}"),Spring Boot 會自動幫我們完成金鑰的拉取。發送請求時只要一句.post().uri(...).body(...).retrieve(),就完成了原本繁瑣的 HTTP 傳輸。程式碼乾淨俐落!
GeminiController)很多端點通常透過 POST 傳遞複雜物件,但在開發與除錯初期,每次改動都要開 Postman 或組裝 Payload 很麻煩,因此可以下以下Prompt:
請在 Controller 加一個 GET /test 端點,讓我可以直接在瀏覽器打網址測試 Gemini API 連線正不正常。
AI 便很貼心地幫我們加上了預設 prompt 與簡明的 JSON 回傳結構:
@RestController
@RequestMapping("/gemini")
public class GeminiController {
private final GeminiService geminiService;
public GeminiController(GeminiService geminiService) {
this.geminiService = geminiService;
}
/**
* 開發與測試專用的 GET 端點(方便直接在瀏覽器網址列或終端機驗證連線)
*/
@GetMapping("/test")
public ResponseEntity<Map<String, Object>> testGemini(
@RequestParam(defaultValue = "請用正體中文向使用者打個招呼!") String prompt) {
String result = geminiService.generateText(prompt);
return ResponseEntity.ok(Map.of(
"status", "success",
"prompt", prompt,
"response", result
));
}
}
defaultValue):即使不帶任何參數,也能直接回傳問候語,快速驗證「應用程式 → Secret Manager → Gemini API」整條呼叫鏈是否暢通。http://localhost:8080/gemini/test?prompt=早安
curl -G "http://localhost:8080/gemini/test" \
--data-urlencode "prompt=早安"
RestClient 時,將底層的 ReadTimeout 設定為 30 ~ 60 秒,避免請求因為等太久而自動中斷崩潰。application.yml(例如指定 gemini-3.5-flash-lite),未來不論官方推展到什麼新版本,都能在不修改任何 Java 程式碼的前提下完成無痛切換。--data-urlencode(或瀏覽器自動轉換),避免 URL 特殊字元導致 400 Bad Request。今天我們釐清了計費層級,透過最簡單的 Prompt 與 AI 協同開發,利用 RestClient 與 Secret Manager 成功打通了後端連線,並實作了極速驗證的 /gemini/test 端點。但目前的對話是「單次(Stateless)」的,AI 講完就忘了我們剛才聊過什麼。
明天(Day 8),我們將進入實戰應用的關鍵環節:設計資料庫來儲存歷史對話,打造具備上下文記憶 (Context) 的 AI 助手!我們明天見!