天氣 APP 本地數據庫緩存天氣信息,Key 為城市名。需求:
| 方案 | 實現 | 優點 | 缺點 |
|---|---|---|---|
| 本地時間比對 | 存入時間戳,查詢時 System.currentTimeMillis() - savedTime > 24*3600*1000 | 簡單、無網絡依賴 | 用戶改系統時間即可繞過 |
| 服務器時間校驗 | App 啟動/定時請求 GET /api/time 獲取標準時間戳 | 防作弊、統一標準 | 需額外請求、離線不可用 |
| 網絡時間同步庫 | 引用 NTPClient 或 OkHttp 獲取 HTTP Date 頭 | 輕量、無需專用接口 | 需網絡、精度受 RTT 影響 |
| 混合策略(推薦) | 優先用本地時間;關鍵操作(如付費數據)二次校驗服務器時間 | 體驗與安全平衡 | 邏輯稍複雜 |
public class TimeProvider {
private static final long MAX_LOCAL_DRIFT = 5 * 60 * 1000; // 允許 5 分鐘漂移
private long serverTimeOffset = 0; // serverTime - localTime
private long lastSync = 0;
// 應用啟動時異步同步
public void syncServerTime() {
OkHttpClient client = new OkHttpClient();
Request req = new Request.Builder().url("https://api.example.com/time").build();
client.newCall(req).enqueue(new Callback() {
@Override public void onResponse(Call call, Response res) throws IOException {
long serverMs = Long.parseLong(res.header("Date")); // 或 JSON 體
serverTimeOffset = serverMs - System.currentTimeMillis();
lastSync = System.currentTimeMillis();
}
@Override public void onFailure(Call call, IOException e) { /* 忽略,繼續用本地 */ }
});
}
public long now() {
// 若同步較近且漂移可控,用修正後時間
if (System.currentTimeMillis() - lastSync < 24*3600*1000 && Math.abs(serverTimeOffset) < MAX_LOCAL_DRIFT) {
return System.currentTimeMillis() + serverTimeOffset;
}
return System.currentTimeMillis(); // 回退本地
}
public boolean isExpired(long savedMs, long ttlMs) {
return now() - savedMs > ttlMs;
}
}
CREATE TABLE weather_cache (
city_id TEXT PRIMARY KEY,
data TEXT NOT NULL, -- JSON
saved_at INTEGER NOT NULL, -- 服務器時間戳(毫秒)
expires_at INTEGER NOT NULL -- saved_at + TTL
);
查詢時:SELECT * FROM weather_cache WHERE city_id=? AND expires_at > ?(傳入 TimeProvider.now())。
同理,只需將 TTL 設為 30L * 24 * 3600 * 1000。若需自然月(如 1 月 15 日 → 2 月 15 日),用 Calendar 運算:
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(savedAt);
cal.add(Calendar.MONTH, 1);
long monthLater = cal.getTimeInMillis();