跳转到内容
Integration

如何在 20 分钟内将 IP 地理定位添加到您的 SaaS

| 7 min read

需要 IP 地理定位的四种 SaaS 功能:货币默认值、GDPR 横幅、欺诈检测和分析仪表板。 每个的工作代码,不需要谷歌地图。

World map with location pins showing IP geolocation data
Photo by NASA on Unsplash

您的 SaaS 向柏林的用户显示美元。 您的 cookie 横幅会向德克萨斯州的访客显示。 当尼日利亚 IP 使用德国帐单地址时,您的欺诈系统无法标记。 四大特点 需要 IP 地理定位,您可以使用一个 API 在 20 分钟内添加所有四个。

API 调用

本文中的每个功能都以相同的端点开始。 这是原始卷曲:

curl -X POST https://api.botoi.com/v1/ip/lookup \\
  -H "Content-Type: application/json" \\
  -d '{"ip": "8.8.8.8"}'

回复:

{
  "success": true,
  "data": {
    "ip": "8.8.8.8",
    "city": "Mountain View",
    "region": "California",
    "country": "US",
    "countryName": "United States",
    "latitude": 37.386,
    "longitude": -122.0838,
    "timezone": "America/Los_Angeles",
    "isp": "Google LLC",
    "org": "Google Public DNS",
    "as": "AS15169 Google LLC"
  }
}

一篇帖子将为您提供城市、地区、国家/地区代码、完整的国家/地区名称、坐标、时区、 ISP、组织和 AS 编号。 这些数据足以支持以下所有四个功能。

功能1:结帐时自动选择货币

在结帐时显示错误的货币会降低转化率。 来自德国的访客 看到“$49.99”,在决定之前必须在心里换算成欧元。 更糟糕的是,他们可能 假设您不为他们的地区提供服务。

使用将访问者的 IP 国家/地区映射到默认货币的中间件解决此问题:

const COUNTRY_CURRENCY = {
  US: "USD", GB: "GBP", DE: "EUR", FR: "EUR", JP: "JPY",
  IN: "INR", BR: "BRL", AU: "AUD", CA: "CAD", CN: "CNY",
  KR: "KRW", MX: "MXN", SE: "SEK", CH: "CHF", SG: "SGD",
};

async function currencyMiddleware(req, res, next) {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip;

  try {
    const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": process.env.BOTOI_API_KEY,
      },
      body: JSON.stringify({ ip }),
    });

    const { data } = await response.json();
    req.defaultCurrency = COUNTRY_CURRENCY[data.country] || "USD";
  } catch {
    req.defaultCurrency = "USD";
  }

  next();
}

// Usage in Express
app.get("/checkout", currencyMiddleware, (req, res) => {
  res.render("checkout", { currency: req.defaultCurrency });
});

国家/地区/货币地图涵盖了 15 个排名前 15 的 SaaS 市场。 为您的受众扩展它。 回退到 USD 可以优雅地处理 API 故障; 没有访客见过损坏的结账台 因为地理定位调用超时。

功能 2:仅适用于欧盟访客的 GDPR cookie 横幅

向每个访问者显示 cookie 同意横幅是不必要的,而且很烦人。 GDPR 适用于欧盟的访客。 其他人都可以跳过它。

该中间件根据欧盟成员国列表检查访问者的 IP 国家/地区 并设置前端读取的 cookie:

const EU_COUNTRIES = new Set([
  "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
  "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
  "PL", "PT", "RO", "SK", "SI", "ES", "SE",
]);

async function gdprMiddleware(req, res, next) {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip;

  try {
    const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": process.env.BOTOI_API_KEY,
      },
      body: JSON.stringify({ ip }),
    });

    const { data } = await response.json();
    req.isEU = EU_COUNTRIES.has(data.country);
  } catch {
    // Default to showing the banner when the lookup fails
    req.isEU = true;
  }

  next();
}

// Set a cookie so the frontend knows whether to show the banner
app.use(gdprMiddleware, (req, res, next) => {
  res.cookie("gdpr_applies", req.isEU ? "1" : "0", {
    httpOnly: false,
    maxAge: 86400 * 1000,
  });
  next();
});

在前端,读取 cookie 并切换横幅:

// Frontend: read the cookie and conditionally show the banner
function shouldShowCookieBanner() {
  const match = document.cookie.match(/gdpr_applies=(\d)/);
  return match ? match[1] === "1" : true; // default to showing
}

if (shouldShowCookieBanner()) {
  document.getElementById("cookie-banner").style.display = "block";
}

失败时的默认行为是显示横幅。 这在合规性方面是错误的; 如果地理查找失败,您仍然满足 GDPR 要求。 cookie 持续 24 小时, 因此,每个访问者每天只需调用一次 API。

特征 3:地理不匹配的欺诈检测

当某人使用德国的帐单地址结帐时,但他们的 IP 地理定位到 尼日利亚,这是一个值得调查的信号。 这并不意味着交易 欺诈; 人们旅行、使用 VPN 以及为国外的朋友购买礼物。 但这是一个数据 点您的欺诈审查团队需要的。

async function checkGeoMismatch(ip, billingCountry) {
  const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": process.env.BOTOI_API_KEY,
    },
    body: JSON.stringify({ ip }),
  });

  const { data } = await response.json();

  const mismatch = data.country !== billingCountry;

  return {
    mismatch,
    ipCountry: data.country,
    ipCity: data.city,
    billingCountry,
    riskNote: mismatch
      ? \`IP located in \${data.countryName} but billing address is \${billingCountry}\`
      : null,
  };
}

// Usage during checkout
app.post("/checkout", async (req, res) => {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip;
  const { billingCountry } = req.body;

  const geo = await checkGeoMismatch(ip, billingCountry);

  if (geo.mismatch) {
    // Flag for manual review instead of blocking
    await flagOrder(req.body.orderId, geo.riskNote);
  }

  // Continue processing the order
  await processOrder(req.body);
  res.json({ success: true });
});

该函数返回一个带有不匹配标志和人类可读的结构化对象 您的支持团队可以查看风险说明。 将订单标记为人工审核,而不是 直接阻止它。 将此与其他信号(电子邮件域名年龄、付款方式)结合起来 速度、设备指纹)以获得更完整的图片。

功能 4:具有用户分布的分析仪表板

了解您的用户在哪里可以帮助您决定支持哪些语言、哪些区域 营销目标以及边缘服务器的放置位置。 该脚本处理一批 访问者 IP 并生成排序的国家/地区分布:

async function buildCountryDistribution(ips) {
  const counts = {};

  // Process in batches to respect rate limits
  for (const ip of ips) {
    try {
      const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Api-Key": process.env.BOTOI_API_KEY,
        },
        body: JSON.stringify({ ip }),
      });

      const { data } = await response.json();
      const country = data.countryName || "Unknown";
      counts[country] = (counts[country] || 0) + 1;
    } catch {
      counts["Unknown"] = (counts["Unknown"] || 0) + 1;
    }
  }

  // Sort by count descending
  return Object.entries(counts)
    .sort(([, a], [, b]) => b - a)
    .map(([country, count]) => ({
      country,
      count,
      percentage: ((count / ips.length) * 100).toFixed(1) + "%",
    }));
}

// Example output:
// [
//   { country: "United States", count: 4521, percentage: "34.2%" },
//   { country: "Germany", count: 1893, percentage: "14.3%" },
//   { country: "United Kingdom", count: 1247, percentage: "9.4%" },
//   ...
// ]

根据您的访问日志将其作为每晚作业运行。 输出准确地告诉您哪个 国家/地区带来的流量最多。 如果您的用户有 14% 在德国,但您的应用只有 支持英语,这是一个可以量化的本地化机会。

缓存策略

IP 到位置的映射不会经常更改。 没有理由再次调用API 对于会话中的同一 IP。 此缓存使用一个具有 1 小时 TTL 的简单 Map:

class GeoCache {
  constructor(ttlMs = 60 * 60 * 1000) {
    this.cache = new Map();
    this.ttlMs = ttlMs;
  }

  get(ip) {
    const entry = this.cache.get(ip);
    if (!entry) return null;
    if (Date.now() - entry.timestamp > this.ttlMs) {
      this.cache.delete(ip);
      return null;
    }
    return entry.data;
  }

  set(ip, data) {
    this.cache.set(ip, { data, timestamp: Date.now() });
  }
}

const geoCache = new GeoCache();

async function lookupWithCache(ip) {
  const cached = geoCache.get(ip);
  if (cached) return cached;

  const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": process.env.BOTOI_API_KEY,
    },
    body: JSON.stringify({ ip }),
  });

  const { data } = await response.json();
  geoCache.set(ip, data);
  return data;
}

对于单实例 Node.js 服务器,内存中的 Map 工作得很好。 如果您运行多个 负载均衡器后面的实例,将 Map 交换为 Redis。 相同的 TTL 逻辑适用; 将地理数据存储为 JSON 字符串,有效期为 3600 秒。

提取客户端IP

IP 地理定位最棘手的部分不是 API 调用,而是 API 调用。 它正在获取正确的IP 首先。 如果您的应用程序位于反向代理、负载均衡器或 CDN 后面, req.connection.remoteAddress 返回代理的 IP,而不是访问者的 IP。

以下是获取各个环境下真实客户端IP的方法:

// Express
const ip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.ip;

// Next.js (App Router)
import { headers } from "next/headers";
const headerList = await headers();
const ip = headerList.get("x-forwarded-for")?.split(",")[0]?.trim();

// Cloudflare Workers
const ip = request.headers.get("cf-connecting-ip");

// Vercel (edge or serverless)
const ip = request.headers.get("x-real-ip")
  || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();

始终在第一个逗号处拆分 x-forwarded-for。 该标头可以包含 当流量通过多个代理时形成 IP 链。 第一个条目是 原始客户端IP。

如果您使用的是 Cloudflare,请使用 cf-connecting-ip。 Cloudflare 设置此标头 对每一个请求,欺骗比 x-forwarded-for

FAQ

如何将 IP 地理定位添加到我的 SaaS 应用程序?
将访问者的 IP 发送到 IP 地理定位 API(例如 POST /v1/ip/lookup),并使用返回的国家/地区、城市和时区数据来个性化他们的体验。 常见用例包括结帐时自动选择货币、向欧盟访客展示 GDPR 横幅、标记地理不匹配欺诈以及构建分析仪表板。 您可以使用单个 API 添加所有四个功能。
SaaS 产品的最佳 IP 定位 API 是什么?
寻找一个能够在一次调用中返回国家、城市、地区、坐标、时区和 ISP 数据的 API。 Botoi 的 /v1/ip/lookup 返回所有这些字段,无需注册即可进行匿名访问(5 请求/分钟)。 对于生产用途,免费的 API 密钥每天可提供 1,000 个请求。 付费计划起价为 9 美元/月。
我可以在没有 Google 地图的情况下通过 IP 对用户进行地理定位吗?
是的。 IP 地理定位 API 可返回纬度、经度、城市和国家/地区数据,无需 Google 地图或任何地图服务。 如果您想在可视地图上显示位置,则只需要地图库。 对于货币默认、GDPR 合规性和欺诈检测等功能,您只需要来自 API 的原始地理位置数据。
IP 地理定位检测用户位置的准确度如何?
IP 地理定位在国家/地区级别的准确率约为 99%,在城市级别的准确率约为 80-90%。 移动运营商和 VPN 用户的准确度下降。 对于货币选择和 GDPR 合规性等 SaaS 功能,国家/地区级别的准确性就足够了。 对于欺诈检测,请将​​ IP 地理定位与账单地址数据相结合,而不是依赖城市级精度。
我应该在 SaaS 中缓存 IP 地理定位结果吗?
是的。 IP 到位置的映射很少更改,因此每个 IP 缓存结果 1 小时可显着减少 API 调用。 Use an in-memory Map for single-instance deployments or Redis for multi-instance setups. 大多数 SaaS 应用程序的缓存命中率高达 60-80%,因为回访者在会话中访问了相同的 IP。

开始使用 botoi 构建

150+ 个 API 端点,涵盖查询、文本处理、图片生成和开发者工具。免费套餐,无需信用卡。