使用函数计算获取 IP 地址信息

背景

博客评论区支持 IP 地区显示后,虽然可以通过请求体拿到 IP,但是要查询对应的地区仍然需要单独的服务。

将其独立出来有以下几点理由

  • 一些免费的 IP 接口并不稳定(如 ip-api.com 有可能在国内访问受限)
  • IP 接口来源众多,有些免费、有些可以免费体验但是要定期更新密钥、有些则收费,如果需要长时间使用需要定期维护接口列表,放在博客中需要跟随博客发版
  • 该功能不仅仅适用于博客场景,在一些别的地方也会用到,应该解耦出来

而选择函数计算的理由如下

  • 博客评论并不是特别频繁,大部分情况没有流量,需要 弹性 提供服务
  • 并且经常性的接口维护每次重新部署比较麻烦(每次可能部署时间比改的时间还长),可以利用函数计算的 Web IDE
  • 免费额度的请求量足以应付博客评论区的需要,而 IP 本身数据量较小,CDN 费用也很低
  • 广告

配置

需要配置的项目并不多

  • runtime:首先由于使用 js 实现,因此选择选择函数计算 nodejs runtime。
  • 内存规格:本身没有复杂的计算功能,因此选择最小规格(128 MB)即可(如果处理过慢可以适当调大)
  • 单实例并发度:如果存在并发场景,每个请求的处理实际上只是在等待 IO,对机器本身性能要求不高,因此可以考虑允许一个实例同时承载 50 个并发请求(如果发生频繁的故障可以考虑调小)
  • 环境变量:部分接口需要用到密钥,配置在环境变量中避免泄露

代码

这里共实现了两个接口

const axios = require("axios");
const { env } = require('process');
const { APPCODE } = env;

async function ipApi(ip) {
    const res = (await axios.get(`http://ip-api.com/json/${ip}?fields=66842623&lang=zh-CN`)).data;

    return {
        success: res.status === "success",
        ip,
        continent: res.continent,
        country: res.country,
        region: res.regionName,
        city: res.city,
        mobile: res.mobile,
        proxy: res.proxy,
        lat: res.lat,
        lon: res.lon,
    }
}

async function aliyunApi(ip) {
    const res = (await axios.get(`http://api01.aliyun.venuscn.com/ip?ip=${ip}`, {
        headers: {
            "Authorization": `APPCODE ${APPCODE}`,
        }
    })).data;

    return {
        success: res.ret === 200,
        ip,
        country: res.data.country,
        region: res.data.region,
        city: res.data.city,
    }
}

const apis = [
    ipApi,
    aliyunApi,
]

exports.handler = async (req, resp, context) => {
    const ip = !!req.queries.ip ? req.queries.ip : req.clientIP
    console.log(ip);

    var result = { ip }

    for (const f of apis) {
        try {
            const res = await f(ip);
            console.log("call", f, res)
            if (!!res && res.success) {
                result = { ...result, ...res };
                break;
            }
        } catch (e) {
            console.log(f, "got error", e.message);
        }
    }

    resp.send(JSON.stringify(result))
}