PAYATHON 2026

启用 Billing 后 Google Maps Places API 仍返回 OVER_QUERY_LIMIT

数据小陈

结论

OVER_QUERY_LIMIT 不一定意味着当前页面发送了大量请求。即使 Billing Account 显示为 enabled,只要它没有关联到 API key 所属的 Google Cloud 项目,或者该项目的 Places/Maps 配额很低、已被其他 key 用完,Google Maps Platform 仍可能返回这个状态。

建议先核对以下四项:

  1. 当前网页实际使用哪个 API key。
  2. 这个 key 属于哪个 Google Cloud 项目。
  3. Billing Account 是否已关联到该项目,而不只是计费账号本身处于启用状态。
  4. 项目是否启用了所需 API,相关配额是否被设为 0 或很小的值。

仅仅重新生成 key 通常解决不了问题。如果新 key 仍属于同一个计费关系错误或配额异常的项目,报错还会出现。

为什么请求很少也会超限

Billing 已启用,但没有关联到正确项目

Google Cloud 的计费关系如下:

API key → Google Cloud project → Billing Account

Billing Account 显示为 active/enabled,只能说明该计费账号可用,不能证明当前项目已经与它关联。

常见情况有:

  • Billing Account 已创建,但项目没有关联它。
  • Billing 关联的是项目 A,网页中的 key 却属于项目 B。
  • 开发期间创建了多个同名项目,检查时选错了项目。
  • 项目以前关联过 Billing,后来被解除,或者付款方式出现问题。
  • 计费关系刚刚修改,配置还没有完全生效。

排查时应从网页实际加载的 API key 入手,反向确认它所属的项目,不能只查看 Billing Account 页面。

配额由项目共享

Google Maps Platform 的部分配额按项目或具体 API 统计,不只计算当前浏览器中这个 key 的请求。

即使当前页面只测试了几次,配额也可能受到以下情况影响:

  • 同一项目中的其他 API key 正在消耗配额。
  • key 被提交到公开仓库,或暴露在没有限制的网页中,遭到第三方调用。
  • 开发服务器产生了循环请求。
  • 自动化测试、其他环境或其他成员也在使用该项目。
  • 每日配额曾被手动设为较低的数值。
  • 短时间内的请求量超过每分钟或每秒限制。

OVER_QUERY_LIMIT 可能表示日配额不足,也可能表示触发了短时间速率限制。题目中的错误明确提到 daily request quota,因此应先检查 Billing 关联和每日配额。如果错误只是偶尔出现,再重点检查请求速率。

Places 自动补全并非提交一次才请求一次

react-places-autocomplete 会在用户输入时持续查询建议。输入一个地址可能产生多次请求,而且页面上还有 Source 和 Destination 两个输入框。

正常输入一般不会很快用完合理的配额,但以下代码问题会增加请求量:

  • 程序反复修改输入值。
  • 组件不断卸载和重新挂载。
  • onChange 触发额外查询。
  • 没有防抖,快速输入时每个字符都会触发请求。
  • 页面重复加载 Google Maps 脚本。
  • React 开发环境中的生命周期或副作用写法导致重复调用。

应通过浏览器开发者工具的 Network 面板查看实际请求数,不要用点击次数估算。

建议的排查顺序

1. 确认浏览器实际加载的 API key

检查 public/index.html 或最终生成的 HTML,确保 Maps JavaScript API 只加载一次:

<script
  async
  defer
  src="https://maps.googleapis.com/maps/api/js?key=MY_KEY_HERE&libraries=places">
</script>

检查以下内容:

  • key= 后的值是否为预期 key。
  • 是否包含 libraries=places。
  • 其他组件、模板或 npm 包是否再次加载了脚本。
  • 部署环境是否还在使用旧构建产物或旧环境变量。
  • Network 面板中的实际请求 URL 是否使用了正确的 key。

不要在公开问题、日志或截图中粘贴完整的 API key。

2. 从 API key 页面确认所属项目

在 Google Cloud Console 的 Credentials 页面找到该 key,记下当前项目的名称和 Project ID。

接下来的检查都要在这个 Project ID 下完成,包括:

  • Billing
  • APIs & Services
  • Quotas
  • Metrics
  • Credentials

项目名称可能重复,因此 Project ID 更适合用来确认项目。

3. 确认项目已关联 Billing Account

进入该项目的 Billing 页面,确认页面明确显示该项目已关联到有效的 Billing Account。

注意区分以下两种状态:

Billing Account: Active

和:

Current project is linked to this Billing Account

前者并不代表后者。还要检查:

  • 付款方式是否有效。
  • Billing Account 是否已暂停或关闭。
  • 项目是否因预算通知、组织策略或付款问题受到限制。

预算告警通常不会自动停止服务,除非另行配置了自动化措施或组织策略,所以不能只查看预算数值。

修改 Billing 关联后,配置传播可能需要一些时间。等待后可以重新加载页面测试,但不能用等待代替配置检查。

4. 启用对应 API

使用 Maps JavaScript API Places Library 的页面通常需要检查:

  • Maps JavaScript API
  • Places API

如果项目使用新的 Places 服务或调用其他端点,还要根据实际请求启用相应 API。Google Cloud Console 中的 API 名称和产品迁移状态可能变化,应以失败请求对应的 API 及控制台提示为准。

API 未启用时,更常见的是 REQUEST_DENIED 或特定加载错误,而不是 OVER_QUERY_LIMIT,但排查时仍应一并确认。

5. 检查 Quotas 和 Metrics

在 APIs & Services 中分别打开相关 API,查看 Quotas 和 Metrics。

重点查看:

  • Requests per day
  • Requests per minute
  • Requests per minute per user
  • 是否设置了自定义 quota override
  • 当前用量是否来自意外流量
  • 报错时是否出现请求量突增

如果每日配额被手动设为 0、1 或其他很小的数值,应调整到合理范围。允许设置的最大值取决于项目、API、计费状态以及 Google 当时的政策,不能假定所有项目都有相同上限。

如果配额页面显示用量很低,客户端却仍提示日配额超限,通常是因为检查了错误的项目或 API,或者网页实际使用的并不是当前检查的 key。

正确限制浏览器 API key

前端使用的 key 必然会出现在浏览器中,无法通过隐藏代码来保护。应同时设置应用限制和 API 限制。

典型配置如下:

Application restrictions:
  Websites (HTTP referrers)

Allowed referrers:
  http://localhost:3000/*
  https://example.com/*
  https://www.example.com/*

API restrictions:
  Maps JavaScript API
  Places API

允许列表应根据实际部署域名填写,不要直接照搬示例。

限制配置错误通常会导致 RefererNotAllowedMapError、ApiNotActivatedMapError 或 REQUEST_DENIED,而不是典型的 OVER_QUERY_LIMIT。不过,限制 key 可以避免它泄露后被他人用于消耗项目配额。

如果 key 曾被提交到公开 Git 仓库,或暴露在不受控的环境中,应按以下顺序处理:

  1. 创建新的浏览器 key。
  2. 立即添加 HTTP referrer 和 API 限制。
  3. 更新应用配置并重新部署。
  4. 确认新 key 已生效,再删除或停用旧 key。

减少自动补全请求

可以为输入添加简单的防抖,避免用户快速输入时立即更新每一次查询。具体写法取决于 react-places-autocomplete 的版本和项目结构。下面是一个通用示例:

class FindaRide extends Component {
  sourceTimer = null;

  handleSourceAddressChange = address => {
    clearTimeout(this.sourceTimer);

    this.sourceTimer = setTimeout(() => {
      this.setState({
        source: address,
        latitude: null,
        longitude: null,
        errorMessage: ''
      });
    }, 300);
  };

  componentWillUnmount() {
    clearTimeout(this.sourceTimer);
  }
}

这种写法也会延迟受控输入框的显示。实际项目中,通常更适合分别管理“立即显示的输入值”和“用于查询的值”。是否需要自行实现防抖,还要看当前组件版本是否支持 debounce 属性。

如果支持,可以优先使用组件自带的配置:

<PlacesAutocomplete
  value={this.state.source}
  onChange={this.handleSourceAddressChange}
  onSelect={this.handleSourceAddressSelect}
  debounce={300}
>
  {/* render props */}
</PlacesAutocomplete>

用户选择建议后,再用选中的结果完成后续处理。不要在每次输入变化时调用 Place Details 或 Geocoding:

handleSourceAddressSelect = async address => {
  try {
    const results = await geocodeByAddress(address);
    const { lat, lng } = await getLatLng(results[0]);

    this.setState({
      source: address,
      latitude: lat,
      longitude: lng,
      errorMessage: ''
    });
  } catch (error) {
    this.setState({
      errorMessage: error.message || 'Unable to resolve address'
    });
  }
};

对应组件如下:

<PlacesAutocomplete
  value={this.state.source}
  onChange={this.handleSourceAddressChange}
  onSelect={this.handleSourceAddressSelect}
  debounce={300}
>
  {({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
    <div>
      <input
        {...getInputProps({
          placeholder: 'Source'
        })}
      />

      {loading && <div>Loading...</div>}

      {suggestions.map(suggestion => (
        <div
          key={suggestion.placeId}
          {...getSuggestionItemProps(suggestion)}
        >
          {suggestion.description}
        </div>
      ))}
    </div>
  )}
</PlacesAutocomplete>

使用 key={suggestion.placeId} 也比禁用 react/jsx-key 检查更稳妥。

如何判断具体是哪一类问题

可以根据报错特征缩小排查范围:

  • 始终返回 OVER_QUERY_LIMIT,且明确提示 daily quota:先检查项目的 Billing 关联、每日配额,以及实际 key 所属的项目。
  • 快速输入时偶尔出现,稍后自行恢复:更可能是每分钟或短时间速率限制。
  • 只有部署域名失败,本地正常:检查 HTTP referrer 限制。
  • 提示 API 未激活:启用 Maps JavaScript API 和请求所需的 Places API。
  • 控制台用量远高于实际测试量:检查 key 是否泄露、脚本是否重复加载,以及同项目中的其他调用方。
  • 控制台完全没有对应请求记录:网页可能使用了另一个项目的 key,或者线上部署的仍是旧版本。

排查的重点应放在整条配置链上:浏览器中的 key、key 所属项目、项目关联的 Billing Account,以及该项目的配额。只要其中一环不匹配,即使 Billing Account 本身显示 enabled,Places API 仍可能返回 OVER_QUERY_LIMIT。

备注:内容仅供参考。