Google Apps Script的 API调用不使用API密钥

人工智能 2026-07-12

我是初学者,正在为一个小项目尝试处理实时API数据。

我想创建一个HTML页面(以后用于iframe),用于获取布鲁塞尔一个特定地铁站的实时数据。

问题在于,无论我怎么尝试,它都不会使用我的API密钥,而是走公开API,配额非常有限。

我已经尝试了我能想到的一切(包括AI能想到的),但都没用。

这里是我最新的code.gs(不含API密钥),如果你想试,可以在 https://api-management-opendata-production.developer.azure-api.net/apis 上轻松生成一个。我的参数是where=pointid: 8271和 8272。

const POINT_IDS = ["8271", "8272"];
const BASE_URL = "https://api-management-discovery-production.azure-api.net/api/datasets/stibmivb/rt/WaitingTimes";
const API_KEY = "--------";
const CACHE_KEY = "stib_arrivals";

function doGet(e) {
  if (e && e.parameter && e.parameter.data === "1") {
    const cache = CacheService.getScriptCache();
    const cached = cache.get(CACHE_KEY);
    const data = cached || "[]";
    return ContentService
      .createTextOutput(data)
      .setMimeType(ContentService.MimeType.JSON);
  } else {
    return HtmlService.createTemplateFromFile("Page").evaluate()
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
  }
}

function refreshCache() {
  const allArrivals = [];
  const cache = CacheService.getScriptCache();

  for (const pointId of POINT_IDS) {
    const url = `${BASE_URL}?where=pointid%3D%22${pointId}%22&limit=10`;

    let response;
    try {
      response = UrlFetchApp.fetch(url, {
        headers: {
          "bmc-partner-key": API_KEY  // Custom header name
        },
        muteHttpExceptions: true
      });
    } catch (err) {
      Logger.log(`Fetch error ${pointId}: ${err.message}`);
      continue;
    }

    if (response.getResponseCode() !== 200) {
      Logger.log(`HTTP ${response.getResponseCode()} ${pointId}: ${response.getContentText()}`);
      continue;
    }

    let json;
    try {
      json = JSON.parse(response.getContentText());
    } catch (err) {
      Logger.log(`JSON parse error ${pointId}: ${err.message}`);
      continue;
    }

    const records = json.results || [];

    for (const record of records) {
      let times;
      try {
        // From curl: \\\"destination\\\" → replace \\\" → "
        const cleanTimes = record.passingtimes.replace(/\\\"/g, '"');
        times = JSON.parse(cleanTimes);
      } catch (err) {
        Logger.log(`passingtimes error ${pointId}: ${err.message}`);
        continue;
      }

      for (const pt of times) {
        if (!pt?.destination) continue;
        allArrivals.push({
          line: pt.lineId || "?",
          dest: pt.destination.fr || pt.destination.nl || "—",
          arrival: pt.expectedArrivalTime,
          pointid: pointId
        });
      }
    }
  }

  Logger.log(`Cached ${allArrivals.length} arrivals`);
  cache.put(CACHE_KEY, JSON.stringify(allArrivals), 120);
}

下面是我的page.html(我觉得问题不在这里,但万一出现问题,我还是把它放在这里)

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <style>
    * { box-sizing: border-box; }
    body {
      margin: 0;
      padding: 10px;
      font-family: sans-serif;
      background: #1c1c1c;
      color: #e1e1e1;
    }
    h4 {
      margin: 10px 0 6px 0;
      font-size: 0.78em;
      text-transform: uppercase;
      letter-spacing: 0.1em;
      opacity: 0.5;
      font-weight: normal;
    }
    h4:first-of-type { margin-top: 0; }
    table {
      width: 100%;
      border-collapse: collapse;
      font-size: 0.92em;
      margin-bottom: 14px;
    }
    th {
      text-align: left;
      padding: 4px 8px;
      font-weight: normal;
      opacity: 0.45;
      font-size: 0.82em;
    }
    td { padding: 7px 8px; }
    tr:not(:last-child) td {
    }
    .badge {
      display: inline-block;
      border-radius: 5px;
      padding: 2px 8px;
      font-weight: bold;
      font-size: 0.88em;
      min-width: 22px;
      text-align: center;
      color: #fff;
    }
    .line-1 { background: #B9378D; }
    .line-5 { background: #FFB537; color: #1c1c1c; }
    .normal { font-weight: bold; }
    .soon   { font-weight: bold; color: #f28b31; }
    .asap   { font-weight: bold; color: #e05c5c; }
    .divider {
      border: none;
      border-top: 1px solid rgba(255,255,255,0.1);
      margin: 4px 0 12px 0;
    }
    #footer {
      font-size: 0.68em;
      opacity: 0.3;
      text-align: right;
      margin-top: 4px;
    }
  </style>
</head>
<body>

  <table>
    <thead><tr><th>Ligne</th><th>Direction</th><th>Dans</th></tr></thead>
    <tbody id="tbody-a"></tbody>
  </table>

  <hr class="divider"/>

  <table>
    <thead><tr><th>Ligne</th><th>Direction</th><th>Dans</th></tr></thead>
    <tbody id="tbody-b"></tbody>
  </table>

  <div id="footer">—</div>

  <script>
    const PROXY_URL = "<?= ScriptApp.getService().getUrl() ?>?data=1";

    const DEST_A = ["ERASME", "GARE DE L'OUEST"];
    const DEST_B = ["STOCKEL", "HERRMANN-DEBROUX"];

    function showUnavailable() {
      ["tbody-a", "tbody-b"].forEach(id => {
        document.getElementById(id).innerHTML =
          `<tr><td colspan="3" style="opacity:0.5; padding:8px;">🚧 Service indisponible</td></tr>`;
      });
    }

    function renderTable(tbodyId, arrivals) {
      const tbody = document.getElementById(tbodyId);
      if (arrivals.length === 0) {
        tbody.innerHTML = `<tr><td colspan="3" style="opacity:0.4; padding:8px;">Aucun passage prévu.</td></tr>`;
        return;
      }
      tbody.innerHTML = arrivals.map(a => {
        const mins = Math.floor(a.diffMs / 60000);
        const secs = Math.floor((a.diffMs % 60000) / 1000);
        let label, cls;
        if (mins < 1)      { label = `${secs} sec`; cls = "asap"; }
        else if (mins < 3) { label = `${mins} min`; cls = "soon"; }
        else               { label = `${mins} min`; cls = "normal"; }

        const badgeCls = `badge line-${a.line}`;
        return `<tr>
          <td><span class="${badgeCls}">${a.line}</span></td>
          <td>${a.dest}</td>
          <td><span class="${cls}">${label}</span></td>
        </tr>`;
      }).join("");
    }

    async function refresh() {
      const footer = document.getElementById("footer");
      try {
        const res = await fetch(PROXY_URL);
        const arrivals = await res.json();
        const now = new Date();

        // API is up but returning no data — service outage
        if (!Array.isArray(arrivals) || arrivals.length === 0) {
          showUnavailable();
          footer.textContent = "API hors service – " + now.toLocaleTimeString("fr-BE");
          return;
        }

        const upcoming = arrivals
          .map(a => ({ ...a, diffMs: new Date(a.arrival) - now }))
          .filter(a => a.diffMs > 0)
          .sort((a, b) => a.diffMs - b.diffMs);

        const groupA = upcoming.filter(a => DEST_A.includes(a.dest.toUpperCase()));
        const groupB = upcoming.filter(a => DEST_B.includes(a.dest.toUpperCase()));

        renderTable("tbody-a", groupA);
        renderTable("tbody-b", groupB);

        footer.textContent = "Mis à jour : " + now.toLocaleTimeString("fr-BE");

      } catch (err) {
        ["tbody-a","tbody-b"].forEach(id => {
          document.getElementById(id).innerHTML =
            `<tr><td colspan="3" style="color:#e05c5c; padding:8px;">⚠️ Erreur : ${err.message}</td></tr>`;
        });
        footer.textContent = "Échec – " + new Date().toLocaleTimeString("fr-BE");
      }
    }

    refresh();
    setInterval(refresh, 20000);
  </script>

</body>
</html>

解决方案

经过一些研究,我找到了答案。如果有人遇到同样的问题,这里是修改后的代码。

const POINT_IDS = ["8271", "8272"];
const BASE_URL = "https://api-management-opendata-production.azure-api.net/api/datasets/stibmivb/rt/WaitingTimes/";
const API_KEY = "YOUR-API-KEY";
const CACHE_KEY = "stib_arrivals";

function doGet(e) {
  if (e && e.parameter && e.parameter.data === "1") {
    // Serve from cache — fast, always available
    const cache = CacheService.getScriptCache();
    const cached = cache.get(CACHE_KEY);
    const data = cached || "[]";
    return ContentService
      .createTextOutput(data)
      .setMimeType(ContentService.MimeType.JSON);
  } else {
    return HtmlService.createTemplateFromFile("Page").evaluate()
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
  }
}

// This function is called by the time-based trigger, NOT by the page
function refreshCache() {
  const allArrivals = [];
  const cache = CacheService.getScriptCache();

  for (const pointId of POINT_IDS) {
    const url = `${BASE_URL}?where=pointid=%22${pointId}%22`;

    let response;
    try {
      response = UrlFetchApp.fetch(url, {
        method: "get",
        headers: {
          "Cache-Control": "no-cache",
          "bmc-partner-key": API_KEY
        },
        muteHttpExceptions: true
      });
    } catch (err) {
      Logger.log(`Fetch error ${pointId}: ${err.message}`);
      continue;
    }

    Logger.log(`HTTP ${response.getResponseCode()} ${pointId}`);
    Logger.log(response.getContentText());

    if (response.getResponseCode() !== 200) {
      continue;
    }

    let json;
    try {
      json = JSON.parse(response.getContentText());
    } catch (err) {
      Logger.log(`JSON parse error ${pointId}: ${err.message}`);
      continue;
    }

    const records = json.results || [];
    for (const record of records) {
      let times;
      try {
        const clean = record.passingtimes.replace(/\\\"/g, '"');
        times = JSON.parse(clean);
      } catch (err) {
        Logger.log(`passingtimes error ${pointId}: ${err.message}`);
        continue;
      }

      for (const pt of times) {
        if (!pt?.destination) continue;
        allArrivals.push({
          line: pt.lineId || "?",
          dest: pt.destination.fr || pt.destination.nl || "—",
          arrival: pt.expectedArrivalTime,
          pointid: pointId
        });
      }
    }
  }

  Logger.log(`Cached ${allArrivals.length} arrivals`);
  cache.put(CACHE_KEY, JSON.stringify(allArrivals), 120);
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章