diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..95b0389 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: FastAPI", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": [ + "app.main:app", + "--reload" + ], + "jinja": true, + "cwd": "${workspaceFolder}", + "subProcess": true, + "env": { + "PYTHONPATH": "${workspaceFolder}" + } + }, + { + "name": "Python Debugger: FastAPI (no reload)", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": [ + "app.main:app", + "--host", + "127.0.0.1", + "--port", + "8000" + ], + "jinja": true, + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${workspaceFolder}" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 0cb140b..63fba06 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,41 @@ app/ - POST `/proxy/rotate` 执行轮换(可传 cityhash/num) - GET `/proxy/status` 当前状态 +#### POST `/proxy/rotate` +- 请求体(JSON): + - `cityhash`(可选,string):城市哈希;若不传,将使用环境变量 `EIP_DEFAULT_CITYHASH`。 + - `num`(可选,int):拉取设备数量上限;若不传,使用环境变量 `EIP_DEFAULT_NUM`,再不设则默认 10。 + +- 行为说明: + - 根据 `cityhash` 调用设备列表接口(数量为 `num`),从中选择「今天未使用过」的第一个 IP。 + - 将选中设备的 `edge` 配置到网关规则中,并把该 IP 记录为当天已使用。 + - 若没有可用且今天未使用的 IP,则返回未变更原因。 + +- 请求示例: +```bash +curl -X POST 'http://localhost:8000/proxy/rotate' \ + -H 'Content-Type: application/json' \ + -d '{"cityhash": "310000", "num": 20}' +``` + +- 响应示例(变更成功): +```json +{ + "changed": true, + "ip": "1.2.3.4", + "edge": "edge-id-xxx", + "status": { "...": "gateway_status_payload" } +} +``` + +- 响应示例(无可用 IP): +```json +{ + "changed": false, + "reason": "没有可用且今天未使用的 IP" +} +``` + ### 备注 - EIP 详细接口见 `API.md` diff --git a/app/routers/proxy.py b/app/routers/proxy.py index f9fa391..321b7c3 100644 --- a/app/routers/proxy.py +++ b/app/routers/proxy.py @@ -1,6 +1,6 @@ from typing import Optional -from fastapi import APIRouter +from fastapi import APIRouter, Body, Query, Form from pydantic import BaseModel from ..rotation_service import rotate as rotate_impl, status as status_impl @@ -15,8 +15,26 @@ class RotateRequest(BaseModel): @router.post("/rotate") -def rotate(req: RotateRequest): - result = rotate_impl(cityhash=req.cityhash, num=req.num) +def rotate( + # 查询参数 + cityhash_q: Optional[str] = Query(None, alias="cityhash"), + num_q: Optional[int] = Query(None, alias="num"), + # 表单参数(multipart/form-data 或 application/x-www-form-urlencoded) + cityhash_f: Optional[str] = Form(None), + num_f: Optional[int] = Form(None), + # JSON 请求体(application/json) + req: Optional[RotateRequest] = Body(None), +): + # 优先级:Query > Form > JSON + effective_cityhash = ( + cityhash_q + if cityhash_q is not None + else (cityhash_f if cityhash_f is not None else (req.cityhash if req else None)) + ) + effective_num = ( + num_q if num_q is not None else (num_f if num_f is not None else (req.num if req else None)) + ) + result = rotate_impl(cityhash=effective_cityhash, num=effective_num) return result