PAYATHON 2026

Google Maps API 使用 sessiontoken 后仍单独计费

数据小许

结论

sessiontoken 的使用方式基本正确。同一次输入过程中应复用一个 token。用户选择预测结果后,把同一个 token 传给 Place Details;会话结束后,再生成新的 token。

Google Cloud Console 的 API usage 显示 9 次请求,不等于按 9 个独立会话收费。Usage 记录的是实际发出的 HTTP 请求。sessiontoken 不会把多次网络请求合并成一次,控制台也不会因此只显示一次调用。它的作用是让 Places API 后端关联这些请求,并按会话归类和计费。

“整个流程只按一次请求收费”也不一定表示 Autocomplete 和 Place Details 合计只产生一项费用。实际收费取决于使用的是 Places API(Legacy)还是 Places API (New),还与 Place Details 请求的字段及当前价格规则有关。

为什么控制台仍显示多次调用

下面的过程本来就会产生多次 HTTP 请求:

Autocomplete: input=s
Autocomplete: input=st
Autocomplete: input=sta
...
Autocomplete: input=station
Place Details

所以 API usage 分别记录每次请求是正常的。sessiontoken 只是把这些调用关联到同一个会话:

一个用户输入会话
├── 多次 Autocomplete 请求
└── 一次 Place Details 请求(结束该会话)

确认计费是否正确时,不要只看 API usage 中的请求数。应查看 Billing 里按 SKU 统计的用量和费用,例如 Autocomplete session、Autocomplete request、Place Details 等相关 SKU。具体名称可能会随 API 版本和 Google 的价格体系调整。

推荐的调用流程

用户每次开始新的地点搜索时:

  1. 创建一个全局唯一且不可预测的 session token。
  2. 本轮输入产生的所有 Autocomplete 请求共用这个 token。
  3. 用户选择预测结果后,使用该结果的 place_id 调用 Place Details。
  4. Place Details 传入完全相同的 token。
  5. Place Details 完成后立即作废这个 token。
  6. 下一轮搜索创建新的 token。
  7. 如果用户取消搜索,没有选择任何结果,也应结束当前会话,不要把 token 留到下一轮继续使用。

示例:

import 'package:uuid/uuid.dart';

const uuid = Uuid();

String? sessionToken;

void startAutocompleteSession() {
  sessionToken = uuid.v4();
}

Uri buildAutocompleteUri(String input) {
  sessionToken ??= uuid.v4();

  return Uri.https(
    'maps.googleapis.com',
    '/maps/api/place/autocomplete/json',
    {
      'input': input,
      'key': apiKey,
      'sessiontoken': sessionToken!,
      'location': '22.863185,87.3552233',
      'radius': '5000',
      'strictbounds': 'true',
    },
  );
}

Uri buildPlaceDetailsUri(String placeId) {
  if (sessionToken == null) {
    throw StateError('No active autocomplete session');
  }

  return Uri.https(
    'maps.googleapis.com',
    '/maps/api/place/details/json',
    {
      'place_id': placeId,
      'key': apiKey,
      'sessiontoken': sessionToken!,
      // Legacy Place Details 可通过 fields 限制返回内容。
      'fields': 'place_id,name,geometry,formatted_address',
    },
  );
}

void finishAutocompleteSession() {
  sessionToken = null;
}

调用顺序应类似:

startAutocompleteSession();

final autocompleteUri1 = buildAutocompleteUri('sta');
final autocompleteUri2 = buildAutocompleteUri('stat');
final autocompleteUri3 = buildAutocompleteUri('station');

final detailsUri = buildPlaceDetailsUri(selectedPlaceId);

try {
  await fetchPlaceDetails(detailsUri);
} finally {
  finishAutocompleteSession();
}

补充日志中的两组请求使用了不同的 token,而且每组最后都通过一个使用相同 token 的 Place Details 请求结束。从调用结构看,这样处理是合理的。

需要检查的关键点

1. 参数名称必须与 API 版本匹配

日志使用的是 Legacy Places Web Service 端点:

/maps/api/place/autocomplete/json
/maps/api/place/details/json

对应的查询参数是:

sessiontoken

Places API (New) 的端点、请求方式、token 字段位置和计费规则都不同,不能直接沿用 Legacy API 的示例或旧版价格说明。

2. Place Details 必须来自本次预测结果

用于结束会话的 Place Details 请求,应传入本次 Autocomplete 返回且由用户选中的 place_id。不要随意选择一个地点 ID 发起 Details 请求来“关闭”会话。

3. 一个 token 只对应一次用户搜索

遇到以下情况时,不要继续复用旧 token:

  • 用户已经选择地点并完成 Place Details;
  • 用户关闭或清空搜索框;
  • 用户开始一轮语义上独立的新搜索;
  • 多个用户或多个并发搜索控件同时工作。

并发输入框需要分别维护各自的 token。不要用单例 token 混合多个会话。

4. 不要为每个按键生成 token

下面的写法是错误的:

Future<void> autocomplete(String input) async {
  final token = const Uuid().v4(); // 每次输入都生成新 token
  // ...
}

token 应在搜索会话开始时生成,而不是在每次发送 Autocomplete 请求时生成。

5. 控制请求频率

即使使用了 session token,也应对输入事件进行 debounce,减少无意义的请求:

Timer? debounce;

void onSearchTextChanged(String value) {
  debounce?.cancel();

  debounce = Timer(const Duration(milliseconds: 300), () {
    if (value.trim().isNotEmpty) {
      requestAutocomplete(value);
    }
  });
}

这样既能减少请求量,也能防止旧响应覆盖新响应。生产代码还应取消已经过期的请求,或者通过递增序号忽略乱序返回的结果。

如何确认实际计费

应在 Google Cloud Billing 报表中查看对应项目和 Places SKU,不能根据 API usage 的请求数直接推断费用:

  1. 确认 Autocomplete 和 Place Details 使用的是同一 Google Cloud 项目下的凭据。
  2. 在 Billing Reports 或 Cost Table 中按 SKU 分组。
  3. 把筛选时间范围调整到请求发生后的完整结算周期。
  4. 分清“请求数量”“会话数量”和“计费用量”。
  5. 确认当前采用的是 Legacy 还是 New API 的价格规则。

计费数据可能延迟。请求刚完成时查看控制台,相关数据不一定已经归类完毕。

如果 Billing 报表确实将同一 token 下的所有 Autocomplete 调用都计入按请求收费的 SKU,应继续检查是否混用了不同 API 版本或不同项目的 API key,token 是否无效或过期,以及 Details 请求是否成功完成。仅凭 API usage 显示 9 次调用,无法判断 session token 是否生效。

备注:内容仅供参考。