File size: 5,612 Bytes
b1bcc51
75836b4
6a5b28d
a095012
6a5b28d
a095012
b4872ed
b1bcc51
4ed7447
559f768
c8e2a82
b1bcc51
6a5b28d
b1bcc51
6a5b28d
 
1fa736f
6a5b28d
559f768
 
 
 
 
 
 
6a5b28d
 
 
4ed7447
 
 
 
 
6a5b28d
 
1fa736f
6a5b28d
 
 
 
 
 
 
b1bcc51
75836b4
 
 
 
b1bcc51
a095012
 
 
 
 
fa6601b
 
559f768
b4872ed
0341228
bfb2d8e
b4872ed
 
 
 
 
 
 
 
 
 
559f768
b1bcc51
c8e2a82
 
 
 
 
 
b4872ed
 
b1bcc51
b4872ed
 
 
 
 
 
 
559f768
b4872ed
559f768
 
 
b4872ed
559f768
 
3375e89
559f768
 
b4872ed
 
559f768
 
 
 
 
b4872ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e2a82
 
b4872ed
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# uvicorn app:app --host 0.0.0.0 --port 7860 --reload 

from fastapi import FastAPI, Request, HTTPException, Depends, status
from fastapi.responses import Response # 导入Response
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials # 导入HTTPBearer和HTTPAuthorizationCredentials
import httpx # 使用httpx替代requests,因为requests是同步的,而FastAPI是异步的
import os, json, pprint
from dotenv import load_dotenv
from enum import Enum
from supabase import create_client, Client
from datetime import datetime, timezone, timedelta, time # 导入 datetime 和 timezone

# 加载环境变量
load_dotenv()

# 从环境变量获取代理自身的API密钥
proxy_api_key = os.getenv("PROXY_API_KEY")

# Supabase 配置
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")

# 初始化 Supabase 客户端
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

# 定义HTTPBearer安全方案
security = HTTPBearer()

# 枚举校验协议类型
class ProtocolType(str, Enum):
    HTTP = "http"
    HTTPS = "https"

# 依赖函数:验证代理的API密钥
def verify_proxy_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
    if not proxy_api_key or credentials.credentials != proxy_api_key:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or missing proxy API key",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return credentials.credentials

app = FastAPI(title="Gemini Proxy API")

@app.get("/")
def greet_json():
    return {"Hello": "World!"}

# 3. 健康检查接口(可选,用于验证服务是否正常)
@app.get("/health")
async def health_check():
    return {"status": "ok", "message": "Proxy service is running."}

@app.api_route("/v1/{protocol}/{host}/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
# @app.api_route("/v1/{protocol}/{host}/{path:path}", methods=["POST"])
async def proxy(request: Request, protocol: ProtocolType, host:str, path: str, proxy_api_key: str = Depends(verify_proxy_api_key)): # 添加代理认证依赖
    send_req = await make_send_req(request, protocol, host, path)
    print(send_req)
    print(send_req.get('headers'))

    # 使用httpx异步客户端发起请求
    async with httpx.AsyncClient() as client:
        response = await client.request(
            method=send_req.get('method'),
            url=send_req.get('url'),
            headers=send_req.get('headers'),
            content=send_req.get('content'), # httpx使用content参数传递请求体
            timeout=30  # 超时时间,可调整
        )

    try:
        # 创建北京时间时区对象(UTC+8)
        beijing_tz = timezone(timedelta(hours=8))
        now_beijing = datetime.now(beijing_tz)  # 带时区的datetime对象
        current_local_time = now_beijing.isoformat()
        # current_local_time = int(now_beijing.timestamp())  # 正确的时间戳
        # current_local_time = datetime.now().isoformat()
        supabase.table("airs_api_keys").update({"ran_at": current_local_time}).eq("id", send_req.get('api_key_info').get('api_key_id')).execute()
        print('更新成功')
    except Exception as e:
        raise HTTPException(status_code=500, detail="更新API密钥运行时间失败!")

    return Response(
        content=response.content,
        status_code=response.status_code,
        headers=dict(response.headers)
    )

async def get_api_key_info(model: str = None):
    try:
        # 根据用户提供的SQL语句修改,从 'airs_model_api_keys_view' 视图中获取 'api_key'
        if model:
            response = supabase.from_('airs_model_api_keys_view').select('api_key', 'api_key_id').eq('model_name', model).order('api_key_ran_at', desc=False).limit(1).execute()
        else:
            # 如果没有提供model,则获取所有模型的api_key
            raise HTTPException(status_code=400, detail="请提供模型名称!")
        
        if response.data:
            api_key_info = response.data[0]
            return api_key_info
        else:
            return None
    except Exception as e:
        print(f"从Supabase获取API密钥失败: {e}")
        return None

async def make_send_req(request: Request, protocol: ProtocolType, host:str, path: str):
    _method = request.method
    _url = f"{protocol.value}://{host}/{path}"
    _headers = dict(request.headers)
    # 移除FastAPI自带的Host头,避免Gemini校验报错
    _headers.pop("host", None)
    _headers.pop("authorization", None)
    _headers.pop("user-agent", None)
    _headers.pop("content-length", None)
    # 将User-Agent改为curl/8.7.1,以模拟curl请求
    _headers["User-Agent"] = "curl/8.7.1"

    try:
        # 读取客户端请求体
        _content = await request.body()
        content_json = json.loads(_content)
        try:
            _model = content_json.get("model")
        except Exception as e:
            print(f"获取模型名称失败: {e}")
            return None
    except Exception as e:
        print(f"读取客户端请求体失败: {e}")
        return None
    _api_key_info = await get_api_key_info(_model)
    if not _api_key_info:
        raise HTTPException(status_code=400, detail="未找到匹配的API密钥!")
    _headers["Authorization"] = f"Bearer {_api_key_info['api_key']}"

    send_req={
        "method": _method,
        "url": _url,
        "headers": _headers,
        "content": _content,
        "model": _model,
        "api_key_info": _api_key_info
    }
    return send_req