调用gemini api提示User location is not supported for the API use.

2025-06-14/码农修仙/共6478字/2条热评

众所周知,我的博客已经成功迁移到了越南的优质空间 Dataonline.vn

伴随着这次迁移,我决定重新启用一个备受喜爱的老功能——AI 课代表

这个功能的灵感来源于 冰剑 的 Gemini 助手,经过我的深度定制开发,已经成为博客的一个重要特色。

回顾它的技术演进历程,颇有些意思。

第一阶段:HEXO 时代的异步 JavaScript

最初,这个插件部署在 HEXO 博客上,托管在赛博大善人CloudFlare的怀抱中。那时候,我们用纯 JavaScript 异步调用来实现功能,简单而有效。

第二阶段:TYPECHO 的 PHP 尝试

后来转向 TYPECHO 平台后,我想着既然是 PHP 后端,何不用 PHP 来统一解决问题呢?于是开始了 PHP 版本的探索之旅。

第三阶段:遭遇地域限制的困境

前阵子在 ct8.pl 部署时,我采用了 冰剑的优雅解决方案,这个方案在gemini支持区域上运行得相当完美

新的挑战:地域限制的真面目

当我将服务迁移到 Dataonline.vn 后,却遭遇了意想不到的挫折。

系统返回了一个令人沮丧的错误:

User location is not supported for the API use.

对应的状态码是:

FAILED_PRECONDITION 

这时我才恍然大悟:原来通过 CloudFlare 做跳转的方法,只能绕过网络访问限制(俗称"翻墙"),却无法绕过 Gemini API 自身的地域限制。这是两个完全不同层面的问题!

新的解决方案:Deno Deploy 闪亮登场

面对这个技术挑战,我决定祭出另一位技术界的大善人——Deno.com,让它来充当我们的技术跳板。

Deno Deploy 控制台

部署步骤详解

  1. 快速登录:可以直接使用 Google 账号或 GitHub 账号一键登录,非常便捷。
  2. 创建新项目:点击右上角醒目的蓝色 "New Playground" 按钮,创建一个全新的后端服务。
  3. 安全升级:添加 Token 验证保护, 如果按照这个思路就这样直接使用,任何知道你 API 地址的人都可以滥用你的服务。这显然不是我们想要的结果,为了保护自己的 API 接口,我们需要添加一个 Token 验证机制。
  4. 代码实现:系统会打开代码编辑器,将以下精心调试的代码复制进去:
// Gemini API 代理服务 - 带Token验证 - 适配 Deno Deploy Playground
// 直接复制此代码到 Deno Deploy 的 New Playground 中

import { serve } from "https://deno.land/[email protected]/http/server.ts";

// Gemini 官方接口地址
const GEMINI_API_BASE = "https://generativelanguage.googleapis.com";

// 你的私有 Token - 请设置一个只有你知道的值
const REQUIRED_TOKEN = "[这里自己设置一个别人不知道的值]"; 

// 处理 CORS 跨域问题
function corsHeaders() {
  return {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization, x-goog-api-key",
    "Access-Control-Max-Age": "86400",
  };
}

// Token 验证函数
function validateToken(url: URL): boolean {
  const token = url.searchParams.get('token');
  return token === REQUIRED_TOKEN;
}

// 主处理函数
async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);
  
  // 处理 CORS 预检请求
  if (req.method === "OPTIONS") {
    return new Response(null, {
      status: 200,
      headers: corsHeaders(),
    });
  }
  
  // 健康检查端点不需要 Token 验证
  if (url.pathname === "/" || url.pathname === "/health") {
    return new Response(
      JSON.stringify({
        status: "healthy",
        message: "Gemini API 代理服务正在运行",
        timestamp: new Date().toISOString(),
        note: "API调用需要token参数",
        usage: {
          format: "?token=YOUR_TOKEN&key=YOUR_API_KEY",
          example: `${url.origin}/v1/models/gemini-pro:generateContent?token=YOUR_TOKEN&key=YOUR_API_KEY`
        }
      }, null, 2),
      {
        status: 200,
        headers: {
          "Content-Type": "application/json",
          ...corsHeaders(),
        },
      }
    );
  }
  
  // Token 验证 - 如果验证失败,直接返回 403 状态码
  if (!validateToken(url)) {
    return new Response(null, {
      status: 403,
      headers: corsHeaders(),
    });
  }
  
  try {
    // 构建目标 URL - 移除 token 参数,只保留原始 API 参数
    const targetUrl = new URL(`${GEMINI_API_BASE}${url.pathname}`);
    
    // 复制查询参数,但排除 token 参数
    for (const [key, value] of url.searchParams.entries()) {
      if (key !== 'token') {
        targetUrl.searchParams.append(key, value);
      }
    }
    
    // 准备请求头
    const headers: Record<string, string> = {};
    
    // 复制原始请求头,排除一些可能有问题的头部
    for (const [key, value] of req.headers.entries()) {
      const lowerKey = key.toLowerCase();
      if (![
        "host", "origin", "referer", "cf-ray", "cf-connecting-ip", 
        "cf-visitor", "x-forwarded-for", "x-forwarded-proto"
      ].includes(lowerKey)) {
        headers[key] = value;
      }
    }
    
    // 确保有 User-Agent
    if (!headers["user-agent"] && !headers["User-Agent"]) {
      headers["User-Agent"] = "Deno-Gemini-Proxy/1.0";
    }
    
    console.log(`代理请求: ${req.method} ${targetUrl.toString()}`);
    
    // 构建代理请求选项
    const requestOptions: RequestInit = {
      method: req.method,
      headers,
    };
    
    // 处理请求体
    if (req.method !== "GET" && req.method !== "HEAD") {
      requestOptions.body = req.body;
    }
    
    // 发送请求到 Gemini API
    const response = await fetch(targetUrl.toString(), requestOptions);
    
    console.log(`响应状态: ${response.status}`);
    
    // 构建响应头
    const responseHeaders: Record<string, string> = {};
    
    // 复制响应头
    for (const [key, value] of response.headers.entries()) {
      responseHeaders[key] = value;
    }
    
    // 添加 CORS 头
    Object.assign(responseHeaders, corsHeaders());
    
    // 返回响应
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: responseHeaders,
    });
    
  } catch (error) {
    console.error("代理请求失败:", error);
    
    const errorResponse = {
      error: {
        code: 500,
        message: `代理服务器错误: ${error.message}`,
        status: "INTERNAL_ERROR",
        timestamp: new Date().toISOString()
      }
    };
    
    return new Response(JSON.stringify(errorResponse, null, 2), {
      status: 500,
      headers: {
        "Content-Type": "application/json",
        ...corsHeaders(),
      },
    });
  }
}

// 启动服务
serve(handler, { port: 8000 });

console.log("🚀 Gemini API 代理服务已启动!");
console.log("🔐 已启用Token验证保护");
console.log("📡 监听端口: 8000");
console.log("🔗 使用方法:");
console.log("   需要在URL中添加token参数");
console.log(`   例如: https://your-app.deno.dev/v1/models/gemini-pro:generateContent?token=${REQUIRED_TOKEN}&key=YOUR_API_KEY`);

最终使用方法

有了 Token 保护后,你的 API 调用格式变成:

https://your-app.deno.dev/v1/models/{model}:generateContent?key={key}&token=<你填的那个一个别人不知道的值>

这样就可以安全地替代原来的:

https://generativelanguage.googleapis.com/v1/models/{model}:generateContent?key={key}

总结

通过这次技术升级,我们不仅解决了地域限制的问题,还加强了 API 的安全性。Deno Deploy 作为一个现代化的边缘计算平台,为我们提供了稳定可靠的代理服务。

AI 课代表重新上线啦! 🎉

现在你可以在任何地区愉快地使用 Gemini API 了。记得保护好你的 Token,不要泄露给不相关的人哦~

Enjoy coding! 🚀

正文完

AI课代表总结

老哥牛哇!Dataonline.vn 都能搞到,真·技术极客!没想到 Gemini API 还有地域限制这茬,Deno Deploy 方案简直完美,既绕过了限制,又加了 Token 验证,安全感满满!AI 课代表复活,以后提问更方便了,坐等体验!🎉

已有 2 条评论

  1. 评论头像
    阿呆 2025-06-16
    边用边写插件么
    Windows 11/Google Chrome 137
    1. 评论头像
      猫东东 2025-06-16
      @阿呆 嗯,因为typecho的插件太乱了
      Windows 11/Google Chrome 137