昨天的 Day 16,我們成功建立了飲水量歷史查詢 API。
現在不只可以查詢今天的飲水量,也可以查看指定日期及最近七天的紀錄。
例如:
[
{
"date": "2026-10-15",
"totalWater": 1800
},
{
"date": "2026-10-16",
"totalWater": 1400
},
{
"date": "2026-10-17",
"totalWater": 1600
}
]
雖然已經可以查詢歷史資料,但如果只是回傳一大串 JSON,好像還是不太容易看出整體情況。
所以今天我想再進一步:
既然已經有歷史紀錄了,為什麼不直接讓 Java 幫我們計算統計結果?
例如:
這些問題其實不需要 AI,使用 Java 就可以完成。
今天的目標是:
使用 Java 處理資料庫查詢結果,建立七天飲水量統計 API。
目前已經有以下 API:
| HTTP Method | API | 功能 |
|---|---|---|
| POST | /health/water |
新增飲水紀錄 |
| GET | /health/water |
查詢今日飲水量 |
| GET | /health/water/date |
查詢指定日期 |
| GET | /health/water/history |
查詢最近七天 |
今天準備新增:
GET /health/water/statistics
讓使用者可以直接取得最近七天的統計結果。
例如:
{
"totalWater": 10500,
"averageWater": 1500.0,
"maxWater": 2000,
"maxDate": "2026-10-15",
"minWater": 1000,
"minDate": "2026-10-12",
"unit": "ml"
}
這樣就不用自己拿計算機慢慢加總了 XD
在開始寫程式之前,我覺得有一件事情需要先決定。
七天平均飲水量,到底要怎麼計算?
假設最近七天只有五天有紀錄:
| 日期 | 飲水紀錄 |
|---|---|
| 10/11 | 1200 ml |
| 10/12 | 無紀錄 |
| 10/13 | 1500 ml |
| 10/14 | 1800 ml |
| 10/15 | 無紀錄 |
| 10/16 | 1400 ml |
| 10/17 | 1600 ml |
如果只計算有紀錄的五天,平均值會是:
7500 ÷ 5 = 1500 ml
但如果計算完整七天,並把沒有紀錄的日期暫時以 0 表示:
7500 ÷ 7 ≈ 1071.43 ml
兩個結果其實差很多。
所以今天決定採用:
最近七個日曆日的平均紀錄量。
也就是:
七天總紀錄量 ÷ 7
不過要特別注意:
沒有紀錄不代表當天真的沒有喝水。
今天的統計只是根據系統保存的資料進行計算,不能直接代表使用者實際的飲水情況。
為了避免誤解,我們也會在結果中顯示有紀錄的天數。
昨天我們已經建立:
WaterHistoryResponse.java
用來儲存每一天的飲水總量。
程式如下:
package com.example.healthassistant.dto;
import java.time.LocalDate;
public class WaterHistoryResponse {
private LocalDate date;
private int totalWater;
public WaterHistoryResponse(LocalDate date, int totalWater) {
this.date = date;
this.totalWater = totalWater;
}
public LocalDate getDate() {
return date;
}
public int getTotalWater() {
return totalWater;
}
}
例如資料庫查詢結果是:
10/15 → 1800 ml
10/16 → 1400 ml
10/17 → 1600 ml
Java 就可以把它們整理成:
List<WaterHistoryResponse>
今天會直接使用這份歷史資料進行統計。
因此不需要重新建立資料表,也不需要修改昨天的 SQL。
接下來建立今天的新檔案:
WaterStatisticsResponse.java
因為這次要回傳的資料比較多,所以我決定建立一個專門的 DTO。
完整程式如下:
package com.example.healthassistant.dto;
import java.time.LocalDate;
public class WaterStatisticsResponse {
private long totalWater;
private double averageWater;
private int maxWater;
private LocalDate maxDate;
private int minWater;
private LocalDate minDate;
private int recordedDays;
private int periodDays;
private String unit;
public WaterStatisticsResponse(
long totalWater,
double averageWater,
int maxWater,
LocalDate maxDate,
int minWater,
LocalDate minDate,
int recordedDays,
int periodDays) {
this.totalWater = totalWater;
this.averageWater = averageWater;
this.maxWater = maxWater;
this.maxDate = maxDate;
this.minWater = minWater;
this.minDate = minDate;
this.recordedDays = recordedDays;
this.periodDays = periodDays;
this.unit = "ml";
}
public long getTotalWater() {
return totalWater;
}
public double getAverageWater() {
return averageWater;
}
public int getMaxWater() {
return maxWater;
}
public LocalDate getMaxDate() {
return maxDate;
}
public int getMinWater() {
return minWater;
}
public LocalDate getMinDate() {
return minDate;
}
public int getRecordedDays() {
return recordedDays;
}
public int getPeriodDays() {
return periodDays;
}
public String getUnit() {
return unit;
}
}
這次的 DTO 包含:
| 欄位 | 說明 |
|---|---|
| totalWater | 七天累積紀錄量 |
| averageWater | 七天平均紀錄量 |
| maxWater | 單日最高紀錄量 |
| maxDate | 最高紀錄日期 |
| minWater | 單日最低紀錄量 |
| minDate | 最低紀錄日期 |
| recordedDays | 有紀錄的天數 |
| periodDays | 統計期間天數 |
| unit | 單位 |
因為這些資料都有固定的欄位,所以使用 DTO 會比一直使用 Map 更容易管理。
接下來開始撰寫統計邏輯。
打開:
WaterService.java
今天要新增一個 Method:
getWaterStatistics()
首先取得最近七天的歷史紀錄:
List<WaterHistoryResponse> history = getWaterHistory();
接著使用 for 迴圈計算總量:
long total = 0;
for (WaterHistoryResponse record : history) {
total += record.getTotalWater();
}
假設歷史資料是:
1200
1500
1800
1400
1600
就會得到:
total = 7500
這樣就完成第一個統計功能。
這裡使用 long 儲存總量,是為了避免多筆整數資料加總時太容易超出 int 的範圍。
接著要計算平均值。
公式很簡單:
平均飲水紀錄量 = 七天總紀錄量 ÷ 7
在 Java 裡面可以寫:
double average = total / 7.0;
這裡使用:
7.0
而不是:
7
因為我們希望保留小數。
例如:
7500 ÷ 7 = 1071.428571...
如果希望只顯示小數點後兩位,可以使用:
double roundedAverage =
Math.round(average * 100.0) / 100.0;
結果就會是:
1071.43
這樣就完成平均值計算。
接下來要找出:
最高飲水紀錄
最低飲水紀錄
我們可以使用 Java 的 if 條件判斷。
不過這次要稍微注意一件事情:
如果最近七天完全沒有紀錄,最高和最低值應該怎麼處理?
如果直接把最高和最低都設成 0,可能會讓使用者以為系統真的有一筆 0 ml 的紀錄。
所以今天決定:
null。這樣可以避免在完全沒有資料時,憑空產生某一天的最高或最低紀錄。
接下來把剛剛的功能整理成一個 Method。
在 WaterService.java 加入:
public WaterStatisticsResponse getWaterStatistics() {
List<WaterHistoryResponse> history = getWaterHistory();
long total = 0;
int maxWater = 0;
int minWater = 0;
LocalDate maxDate = null;
LocalDate minDate = null;
boolean firstRecord = true;
for (WaterHistoryResponse record : history) {
int amount = record.getTotalWater();
total += amount;
if (firstRecord) {
maxWater = amount;
minWater = amount;
maxDate = record.getDate();
minDate = record.getDate();
firstRecord = false;
} else {
if (amount > maxWater) {
maxWater = amount;
maxDate = record.getDate();
}
if (amount < minWater) {
minWater = amount;
minDate = record.getDate();
}
}
}
double average = total / 7.0;
double roundedAverage =
Math.round(average * 100.0) / 100.0;
return new WaterStatisticsResponse(
total,
roundedAverage,
maxWater,
maxDate,
minWater,
minDate,
history.size(),
7
);
}
這段程式雖然比較長,但其實就是把今天的統計功能全部放在一起。
主要流程是:
取得最近七天紀錄
↓
建立統計變數
↓
使用 for 迴圈讀取資料
↓
計算總量
↓
找出最高紀錄
↓
找出最低紀錄
↓
計算七天平均
↓
回傳統計 DTO
另外,因為昨天的 getWaterHistory() 只會回傳有紀錄的日期,所以這裡的最高和最低,是指:
有紀錄日期中的最高與最低飲水量。
不是完整七天中實際飲水量的最高或最低。
今天不用把整個 WaterService 重新寫一次。
只需要保留之前的功能,再加入:
getWaterStatistics()
就可以了。
記得在檔案上方加入:
import com.example.healthassistant.dto.WaterStatisticsResponse;
目前 WaterService 的主要 Method 就會有:
addWater()
新增飲水紀錄。
getTotalWater()
查詢今天的飲水量。
getTotalWaterByDate()
查詢指定日期。
getWaterHistory()
查詢最近七天的紀錄。
以及今天新增的:
getWaterStatistics()
計算七天統計結果。
這樣 Service 的功能就越來越完整了。
接下來要讓使用者可以透過網址取得統計結果。
打開:
WaterController.java
加入:
@GetMapping("/statistics")
public ResponseEntity<WaterStatisticsResponse> getStatistics() {
WaterStatisticsResponse statistics =
waterService.getWaterStatistics();
return ResponseEntity.ok(statistics);
}
記得加入:
import com.example.healthassistant.dto.WaterStatisticsResponse;
因為之前已經使用:
@RequestMapping("/health/water")
所以這個 Method 對應的完整路徑就是:
GET /health/water/statistics
這樣就完成今天的新 API!
接下來重新啟動 Spring Boot。
在 Postman 輸入:
GET http://localhost:8080/health/water/statistics
不需要傳送 Request Body。
假設最近七天有以下紀錄:
| 日期 | 飲水紀錄 |
|---|---|
| 10/11 | 1200 ml |
| 10/12 | 1000 ml |
| 10/13 | 1500 ml |
| 10/14 | 1800 ml |
| 10/15 | 2000 ml |
| 10/16 | 1400 ml |
| 10/17 | 1600 ml |
七天總量:
1200 + 1000 + 1500 + 1800 + 2000 + 1400 + 1600
= 10500 ml
七天平均:
10500 ÷ 7 = 1500 ml
最高紀錄:
2000 ml
最低紀錄:
1000 ml
因此預期回傳:
{
"totalWater": 10500,
"averageWater": 1500.0,
"maxWater": 2000,
"maxDate": "2026-10-15",
"minWater": 1000,
"minDate": "2026-10-12",
"recordedDays": 7,
"periodDays": 7,
"unit": "ml"
}
這樣就成功把原本一筆一筆的飲水紀錄,整理成比較容易理解的統計資訊。
接下來考慮一個特殊情況。
如果資料庫裡完全沒有最近七天的飲水紀錄,系統會回傳什麼?
因為:
history.size()
會是:
0
所以總量和平均值都會是 0。
最高與最低日期則會是 null。
例如:
{
"totalWater": 0,
"averageWater": 0.0,
"maxWater": 0,
"maxDate": null,
"minWater": 0,
"minDate": null,
"recordedDays": 0,
"periodDays": 7,
"unit": "ml"
}
這樣至少可以讓系統正常回傳資料,而不會因為沒有紀錄就發生錯誤。
不過未來如果要讓前端顯示這些資訊,可能需要再設計更清楚的提示。
例如:
最近七天尚無飲水紀錄。
而不是直接告訴使用者平均飲水量是 0。
今天主要新增了一個 DTO,並擴充 Service 和 Controller。
目前專案結構如下:
health-assistant
│
├── src/main/java/com/example/healthassistant
│ │
│ ├── HealthAssistantApplication.java
│ │
│ ├── controller
│ │ ├── HealthController.java
│ │ └── WaterController.java
│ │
│ ├── service
│ │ ├── BmiService.java
│ │ └── WaterService.java
│ │
│ ├── repository
│ │ └── WaterRepository.java
│ │
│ ├── dto
│ │ ├── HealthRequest.java
│ │ ├── WaterRequest.java
│ │ ├── WaterHistoryResponse.java
│ │ └── WaterStatisticsResponse.java
│ │
│ └── exception
│ └── GlobalExceptionHandler.java
│
├── src/main/resources
│ ├── application.properties
│ └── schema.sql
│
└── pom.xml
今天的資料流程變成:
GET /health/water/statistics
↓
WaterController
↓
WaterService
↓
WaterRepository
↓
H2 Database
↓
取得七天飲水紀錄
↓
Java 計算統計結果
↓
WaterStatisticsResponse
↓
JSON 回傳
現在系統不只可以儲存資料,也開始具備基本的統計能力。
這次的功能包含:
加總
平均
最大值
最小值
這些都是有明確計算規則的事情。
所以直接使用 Java 處理就可以了。
如果讓 AI 幫忙計算,反而可能產生不必要的誤差。
例如:
Java → 負責數值計算
AI → 負責將統計結果整理成文字
這樣的分工比較適合我們目前的專案。
未來如果想產生一段摘要:
最近七天共有七天留下飲水紀錄,
平均每日紀錄量為 1500 ml,
其中 10/15 的紀錄量最高。
就可以先讓 Java 算出正確數字,再將統計結果提供給 AI 整理。
不過目前這些數字仍然只是飲水紀錄的統計,不能直接用來判斷個人的健康狀況。
雖然今天已經完成統計 API,但還有一些值得改進的地方。
目前歷史查詢只會回傳有紀錄的日期。
如果未來要製作完整的七天圖表,最好能顯示每天的日期,並清楚區分:
有紀錄
和:
沒有紀錄
而不是直接把沒有紀錄當成真的喝了 0 ml。
目前只能查詢最近七天。
未來可以讓使用者指定:
開始日期
結束日期
例如:
GET /health/water/statistics?startDate=2026-10-01&endDate=2026-10-17
這樣就能查詢更長時間的統計資料。
除了平均值、最大值和最小值,也可以加入:
有紀錄天數
每日變化
每週總量
讓使用者更容易理解自己的紀錄。
目前我們直接把統計邏輯放在 WaterService。
但如果未來統計功能越來越多,也可以考慮建立:
WaterStatisticsService
專門處理統計分析。
這樣就能讓不同功能有更清楚的分工。
今天成功讓智慧健康助理從單純的飲水紀錄,進一步加入統計功能!
目前已經可以:
今天也再次練習了 Java 的:
for
以及:
if
條件判斷。
目前的智慧健康助理已經可以:
新增飲水紀錄
↓
儲存至資料庫
↓
查詢歷史資料
↓
計算七天統計
↓
回傳 JSON
今天最大的收穫是:
有了資料庫之後,我們不只可以保存資料,也能透過 Java 將資料整理成有意義的統計結果。
雖然還沒有真正加入 AI,但已經開始為後面的智慧健康摘要建立基礎了!
目前我們已經完成 BMI 計算、飲水紀錄,以及七天飲水量統計。
不過既然要打造智慧健康助理,當然不能只有飲水量這一種健康紀錄!
所以下一篇準備加入:
每日睡眠時間紀錄功能!
預計會學習:
讓智慧健康助理開始記錄不同種類的日常健康資訊!