mirror of
https://github.com/isboyjc/GoProxy.git
synced 2026-05-06 20:02:54 +08:00
feat: ✨ add environment variable support for proxy authentication and geo-blocking
- Introduced `.env.example` for environment variable configuration. - Updated `docker-compose.yml` to utilize environment variables for proxy settings. - Implemented proxy authentication with Basic Auth and geo-blocking based on country codes. - Added `GEO_FILTER.md` for documentation on geo-filtering configuration. - Enhanced logging to indicate authentication status and blocked countries during proxy server startup.
This commit is contained in:
35
.env.example
Normal file
35
.env.example
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Docker 容器配置
|
||||||
|
CONTAINER_NAME=goproxy
|
||||||
|
|
||||||
|
# 端口绑定配置
|
||||||
|
# PROXY_HOST: 代理服务绑定地址
|
||||||
|
# - 127.0.0.1 = 仅本地访问(推荐,最安全)
|
||||||
|
# - 0.0.0.0 = 对外开放(如启用需配合认证或防火墙)
|
||||||
|
PROXY_HOST=127.0.0.1
|
||||||
|
STABLE_PORT=7776 # 最低延迟代理端口
|
||||||
|
RANDOM_PORT=7777 # 随机轮换代理端口
|
||||||
|
|
||||||
|
# 地理过滤配置
|
||||||
|
# 屏蔽指定国家代码的出口代理(逗号分隔,如 CN,RU,KP)
|
||||||
|
# 留空 = 不屏蔽任何国家
|
||||||
|
# 默认屏蔽中国大陆(CN),香港(HK)、澳门(MO)、台湾(TW)不受影响
|
||||||
|
BLOCKED_COUNTRIES=CN
|
||||||
|
|
||||||
|
# 代理服务认证配置
|
||||||
|
# ⚠️ 当 PROXY_HOST=0.0.0.0(对外开放)时,强烈建议启用认证!
|
||||||
|
# 使用方式:curl -x http://username:password@host:port https://example.com
|
||||||
|
PROXY_AUTH_ENABLED=false # 是否启用代理认证(true/false)
|
||||||
|
PROXY_AUTH_USERNAME=proxy # 代理认证用户名
|
||||||
|
PROXY_AUTH_PASSWORD= # 代理认证密码(留空=不启用认证)
|
||||||
|
# 示例:PROXY_AUTH_PASSWORD=my_secure_password_123
|
||||||
|
|
||||||
|
# WebUI 配置
|
||||||
|
WEBUI_HOST=0.0.0.0 # WebUI 可对外访问(有登录认证保护)
|
||||||
|
WEBUI_PORT=7778
|
||||||
|
WEBUI_PASSWORD=goproxy # ⚠️ 生产环境请修改为强密码
|
||||||
|
|
||||||
|
# 数据存储
|
||||||
|
DATA_DIR=./data
|
||||||
|
|
||||||
|
# 时区配置
|
||||||
|
TZ=Asia/Shanghai
|
||||||
127
.github/DOCKER_SETUP.md
vendored
Normal file
127
.github/DOCKER_SETUP.md
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# GitHub Actions Docker 镜像自动构建配置
|
||||||
|
|
||||||
|
本项目使用 GitHub Actions 自动构建并推送 Docker 镜像到多个镜像仓库。
|
||||||
|
|
||||||
|
## 📦 自动构建触发条件
|
||||||
|
|
||||||
|
- **Push to main 分支**:自动构建 `latest` 标签
|
||||||
|
- **推送版本标签**(如 `v1.0.0`):自动构建版本标签(`1.0.0`, `1.0`, `1`, `latest`)
|
||||||
|
- **手动触发**:在 GitHub Actions 页面手动运行
|
||||||
|
|
||||||
|
## 🔧 配置 GitHub Secrets
|
||||||
|
|
||||||
|
### 1. Docker Hub 配置(可选)
|
||||||
|
|
||||||
|
如果要推送到 Docker Hub,需要配置以下 Secrets:
|
||||||
|
|
||||||
|
1. 登录 [Docker Hub](https://hub.docker.com/)
|
||||||
|
2. 在 **Account Settings > Security** 创建 Access Token
|
||||||
|
3. 在 GitHub 仓库的 **Settings > Secrets and variables > Actions** 添加:
|
||||||
|
- `DOCKERHUB_USERNAME`: 你的 Docker Hub 用户名
|
||||||
|
- `DOCKERHUB_TOKEN`: 刚才创建的 Access Token
|
||||||
|
|
||||||
|
### 2. GitHub Container Registry(默认已配置)
|
||||||
|
|
||||||
|
GitHub Container Registry (ghcr.io) 无需额外配置,workflow 使用自动提供的 `GITHUB_TOKEN`。
|
||||||
|
|
||||||
|
**镜像地址**:`ghcr.io/isboyjc/goproxy`
|
||||||
|
|
||||||
|
## 🏗️ 构建特性
|
||||||
|
|
||||||
|
- ✅ **多架构支持**:`linux/amd64`、`linux/arm64`
|
||||||
|
- ✅ **多标签发布**:`latest`、版本号(如 `1.0.0`、`1.0`、`1`)
|
||||||
|
- ✅ **双仓库推送**:Docker Hub + GitHub Container Registry
|
||||||
|
- ✅ **构建缓存**:使用 GitHub Actions 缓存加速构建
|
||||||
|
|
||||||
|
## 📋 使用镜像
|
||||||
|
|
||||||
|
### 从 Docker Hub 拉取
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull isboyjc/goproxy:latest
|
||||||
|
docker run -d \
|
||||||
|
--name proxygo \
|
||||||
|
-p 127.0.0.1:7776:7776 \
|
||||||
|
-p 127.0.0.1:7777:7777 \
|
||||||
|
-p 7778:7778 \
|
||||||
|
-e WEBUI_PASSWORD=your_password \
|
||||||
|
-v "$(pwd)/data:/app/data" \
|
||||||
|
isboyjc/goproxy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从 GitHub Container Registry 拉取
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/isboyjc/goproxy:latest
|
||||||
|
docker run -d \
|
||||||
|
--name proxygo \
|
||||||
|
-p 127.0.0.1:7776:7776 \
|
||||||
|
-p 127.0.0.1:7777:7777 \
|
||||||
|
-p 7778:7778 \
|
||||||
|
-e WEBUI_PASSWORD=your_password \
|
||||||
|
-v "$(pwd)/data:/app/data" \
|
||||||
|
ghcr.io/isboyjc/goproxy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用特定版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用特定版本号
|
||||||
|
docker pull ghcr.io/isboyjc/goproxy:1.0.0
|
||||||
|
|
||||||
|
# 使用主版本号(自动获取最新的 1.x.x)
|
||||||
|
docker pull ghcr.io/isboyjc/goproxy:1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 发布新版本
|
||||||
|
|
||||||
|
创建并推送版本标签即可触发自动构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建版本标签
|
||||||
|
git tag -a v1.0.0 -m "Release version 1.0.0"
|
||||||
|
|
||||||
|
# 推送标签到 GitHub
|
||||||
|
git push origin v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
GitHub Actions 会自动:
|
||||||
|
1. 构建多架构镜像
|
||||||
|
2. 推送到 Docker Hub 和 GHCR
|
||||||
|
3. 创建多个标签(`1.0.0`, `1.0`, `1`, `latest`)
|
||||||
|
|
||||||
|
## 🌐 端口绑定说明
|
||||||
|
|
||||||
|
默认示例使用 `127.0.0.1:7776` 和 `127.0.0.1:7777`(仅本地访问):
|
||||||
|
|
||||||
|
- **场景**:在部署机器上本地使用代理
|
||||||
|
- **安全**:最安全,代理服务不对外暴露
|
||||||
|
- **限制**:其他机器无法使用该代理
|
||||||
|
|
||||||
|
**如需对外开放代理服务**(局域网/VPS 多机共享),修改端口绑定:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name proxygo \
|
||||||
|
-p 7776:7776 \ # ⚠️ 改为对外开放(无认证)
|
||||||
|
-p 7777:7777 \ # ⚠️ 改为对外开放(无认证)
|
||||||
|
-p 7778:7778 \
|
||||||
|
-e WEBUI_PASSWORD=strong_password \
|
||||||
|
-v "$(pwd)/data:/app/data" \
|
||||||
|
ghcr.io/isboyjc/goproxy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ 安全警告**:
|
||||||
|
- 代理服务本身**无认证机制**,对外开放后任何人都可使用
|
||||||
|
- 建议配合**防火墙白名单**或**云安全组**限制访问 IP
|
||||||
|
- WebUI 有登录认证,可以安全对外开放
|
||||||
|
|
||||||
|
更多部署场景说明,请查看 [README - 部署场景配置](../README.md#部署场景配置)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 注意事项
|
||||||
|
|
||||||
|
- **权限配置**:首次推送到 GHCR 可能需要在仓库 **Settings > Actions > General** 中设置 **Workflow permissions** 为 `Read and write permissions`
|
||||||
|
- **GHCR 可见性**:默认镜像为私有,可在 **Packages** 页面修改为公开
|
||||||
|
- **Docker Hub 可选**:如果不需要推送到 Docker Hub,可在 workflow 文件中删除相关配置,或不配置 DOCKERHUB_* secrets(workflow 会跳过 Docker Hub 登录失败的步骤)
|
||||||
72
.github/workflows/docker-image.yml
vendored
Normal file
72
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: Docker Image CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY_DOCKERHUB: docker.io
|
||||||
|
REGISTRY_GHCR: ghcr.io
|
||||||
|
IMAGE_NAME: isboyjc/goproxy
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.REGISTRY_DOCKERHUB }}/${{ env.IMAGE_NAME }}
|
||||||
|
${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY_DOCKERHUB }}
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY_GHCR }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -25,6 +25,9 @@ config.json
|
|||||||
data/
|
data/
|
||||||
!data/.gitkeep
|
!data/.gitkeep
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.bak
|
*.bak
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ ENV TZ=Asia/Shanghai
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/proxy-pool .
|
COPY --from=builder /app/proxy-pool .
|
||||||
|
|
||||||
EXPOSE 7777 7778
|
EXPOSE 7776 7777 7778
|
||||||
|
|
||||||
CMD ["./proxy-pool"]
|
CMD ["./proxy-pool"]
|
||||||
|
|||||||
242
GEO_FILTER.md
Normal file
242
GEO_FILTER.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
# 地理过滤配置指南
|
||||||
|
|
||||||
|
GoProxy 支持通过国家代码过滤代理的出口位置,让你可以灵活控制代理池的地理分布。
|
||||||
|
|
||||||
|
## 🌍 配置方式
|
||||||
|
|
||||||
|
### 环境变量配置
|
||||||
|
|
||||||
|
通过 `BLOCKED_COUNTRIES` 环境变量设置需要屏蔽的国家代码:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认:屏蔽中国大陆(CN)
|
||||||
|
BLOCKED_COUNTRIES=CN
|
||||||
|
|
||||||
|
# 屏蔽多个国家(逗号分隔)
|
||||||
|
BLOCKED_COUNTRIES=CN,RU,KP,IR
|
||||||
|
|
||||||
|
# 不屏蔽任何国家(留空)
|
||||||
|
BLOCKED_COUNTRIES=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose 配置
|
||||||
|
|
||||||
|
编辑 `.env` 文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 屏蔽中国大陆和俄罗斯
|
||||||
|
BLOCKED_COUNTRIES=CN,RU
|
||||||
|
```
|
||||||
|
|
||||||
|
启动服务:
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Run 配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name proxygo \
|
||||||
|
-p 127.0.0.1:7776:7776 -p 127.0.0.1:7777:7777 -p 7778:7778 \
|
||||||
|
-e BLOCKED_COUNTRIES=CN,RU \
|
||||||
|
-e WEBUI_PASSWORD=your_password \
|
||||||
|
-v "$(pwd)/data:/app/data" \
|
||||||
|
ghcr.io/isboyjc/goproxy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 本地运行配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BLOCKED_COUNTRIES=CN,RU,KP
|
||||||
|
go run .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗺️ 工作机制
|
||||||
|
|
||||||
|
### 双重过滤
|
||||||
|
|
||||||
|
地理过滤在两个阶段生效:
|
||||||
|
|
||||||
|
**1. 启动清理阶段**
|
||||||
|
- 程序启动时自动扫描数据库
|
||||||
|
- 删除所有屏蔽国家出口的代理
|
||||||
|
- 日志输出:`🧹 已清理 X 个屏蔽国家出口代理 (屏蔽: [CN RU])`
|
||||||
|
|
||||||
|
**2. 验证阶段**
|
||||||
|
- 新抓取的代理在验证时检查出口位置
|
||||||
|
- 如果出口国家在屏蔽列表中,直接拒绝入池
|
||||||
|
- 不会占用池子容量
|
||||||
|
|
||||||
|
### 国家代码识别
|
||||||
|
|
||||||
|
系统使用 **ISO 3166-1 alpha-2** 标准的两位国家代码:
|
||||||
|
|
||||||
|
```
|
||||||
|
出口位置格式:CC City
|
||||||
|
示例:
|
||||||
|
CN Beijing → 国家代码 CN(中国大陆)
|
||||||
|
HK Hong Kong → 国家代码 HK(香港)
|
||||||
|
US New York → 国家代码 US(美国)
|
||||||
|
RU Moscow → 国家代码 RU(俄罗斯)
|
||||||
|
```
|
||||||
|
|
||||||
|
匹配规则:`exit_location LIKE 'CC %'`(国家代码 + 空格 + 城市)
|
||||||
|
|
||||||
|
## 📋 常用国家代码
|
||||||
|
|
||||||
|
### 亚洲
|
||||||
|
| 代码 | 国家/地区 | 代码 | 国家/地区 |
|
||||||
|
|------|----------|------|----------|
|
||||||
|
| `CN` | 中国大陆 | `HK` | 香港 |
|
||||||
|
| `TW` | 台湾 | `MO` | 澳门 |
|
||||||
|
| `JP` | 日本 | `KR` | 韩国 |
|
||||||
|
| `SG` | 新加坡 | `IN` | 印度 |
|
||||||
|
| `TH` | 泰国 | `VN` | 越南 |
|
||||||
|
| `KP` | 朝鲜 | `IR` | 伊朗 |
|
||||||
|
|
||||||
|
### 欧洲
|
||||||
|
| 代码 | 国家 | 代码 | 国家 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `RU` | 俄罗斯 | `GB` | 英国 |
|
||||||
|
| `DE` | 德国 | `FR` | 法国 |
|
||||||
|
| `NL` | 荷兰 | `SE` | 瑞典 |
|
||||||
|
| `UA` | 乌克兰 | `PL` | 波兰 |
|
||||||
|
|
||||||
|
### 美洲
|
||||||
|
| 代码 | 国家 | 代码 | 国家 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `US` | 美国 | `CA` | 加拿大 |
|
||||||
|
| `BR` | 巴西 | `MX` | 墨西哥 |
|
||||||
|
| `AR` | 阿根廷 | `CL` | 智利 |
|
||||||
|
|
||||||
|
完整国家代码列表:[ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||||
|
|
||||||
|
## 🎯 使用场景
|
||||||
|
|
||||||
|
### 场景 1:屏蔽中国大陆(默认)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BLOCKED_COUNTRIES=CN
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用**:
|
||||||
|
- 需要海外 IP 代理
|
||||||
|
- 避免被识别为中国大陆流量
|
||||||
|
- 保留香港、澳门、台湾代理
|
||||||
|
|
||||||
|
### 场景 2:屏蔽多个敏感地区
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BLOCKED_COUNTRIES=CN,RU,KP,IR,SY
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用**:
|
||||||
|
- 合规要求(避免某些国家的 IP)
|
||||||
|
- 地缘政治考虑
|
||||||
|
- 防止特定地区的代理质量问题
|
||||||
|
|
||||||
|
### 场景 3:仅使用欧美代理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 屏蔽亚洲、非洲、中东等地区(示例,需根据实际需求调整)
|
||||||
|
BLOCKED_COUNTRIES=CN,IN,TH,VN,ID,PH,BD,PK,IR,IQ,SA,EG,NG,ZA
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 4:不做地理限制
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BLOCKED_COUNTRIES=
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用**:
|
||||||
|
- 需要最大化代理池容量
|
||||||
|
- 对地理位置无特殊要求
|
||||||
|
- 测试和开发环境
|
||||||
|
|
||||||
|
## 📊 实时查看
|
||||||
|
|
||||||
|
### 查看当前屏蔽配置
|
||||||
|
|
||||||
|
启动日志会显示:
|
||||||
|
```
|
||||||
|
[main] 🧹 已清理 15 个屏蔽国家出口代理 (屏蔽: [CN RU KP])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看池中国家分布
|
||||||
|
|
||||||
|
通过 WebUI 的**出口国家筛选器**可以看到当前池中所有国家的代理分布。
|
||||||
|
|
||||||
|
### 数据库查询
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看所有代理的国家分布
|
||||||
|
sqlite3 data/proxy.db "
|
||||||
|
SELECT SUBSTR(exit_location, 1, 2) AS country, COUNT(*) AS count
|
||||||
|
FROM proxies
|
||||||
|
GROUP BY country
|
||||||
|
ORDER BY count DESC;
|
||||||
|
"
|
||||||
|
|
||||||
|
# 查看特定国家的代理
|
||||||
|
sqlite3 data/proxy.db "
|
||||||
|
SELECT address, exit_ip, exit_location, latency
|
||||||
|
FROM proxies
|
||||||
|
WHERE exit_location LIKE 'US %';
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **大小写不敏感**:国家代码会自动转为大写(`cn` → `CN`)
|
||||||
|
2. **空格自动处理**:前后空格会自动去除
|
||||||
|
3. **重启生效**:修改配置后需要重启服务
|
||||||
|
4. **已有代理清理**:启动时会清理数据库中的屏蔽国家代理
|
||||||
|
5. **香港独立识别**:
|
||||||
|
- 中国大陆代码:`CN`
|
||||||
|
- 香港代码:`HK`(独立的国家代码)
|
||||||
|
- 设置 `BLOCKED_COUNTRIES=CN` 不会影响香港代理
|
||||||
|
|
||||||
|
## 🧪 测试验证
|
||||||
|
|
||||||
|
### 测试 1:屏蔽中国大陆
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动服务
|
||||||
|
export BLOCKED_COUNTRIES=CN
|
||||||
|
go run .
|
||||||
|
|
||||||
|
# 查看日志(应该显示清理信息)
|
||||||
|
# [main] 🧹 已清理 X 个屏蔽国家出口代理 (屏蔽: [CN])
|
||||||
|
|
||||||
|
# 查看 WebUI 的代理列表(不应该有 CN 开头的出口位置)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试 2:屏蔽多个国家
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BLOCKED_COUNTRIES=CN,RU,KP
|
||||||
|
go run .
|
||||||
|
|
||||||
|
# 使用测试脚本验证
|
||||||
|
./test/test_proxy.sh
|
||||||
|
|
||||||
|
# 观察输出的国旗 emoji(不应该有 🇨🇳 🇷🇺 🇰🇵)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试 3:不屏蔽任何国家
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BLOCKED_COUNTRIES=
|
||||||
|
go run .
|
||||||
|
|
||||||
|
# 查看日志(不应该有清理信息)
|
||||||
|
# 代理列表中可能出现各种国家的代理
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💡 最佳实践
|
||||||
|
|
||||||
|
1. **默认配置**:保持默认 `BLOCKED_COUNTRIES=CN`,适合大多数场景
|
||||||
|
2. **生产环境**:根据业务合规要求设置屏蔽国家
|
||||||
|
3. **测试环境**:可以设置为空(`BLOCKED_COUNTRIES=`)以获取更多代理
|
||||||
|
4. **定期调整**:根据代理质量和可用性调整屏蔽列表
|
||||||
|
5. **配合筛选**:利用 WebUI 的国家筛选器查看各国代理分布,辅助决策
|
||||||
385
README.md
385
README.md
@@ -2,10 +2,39 @@
|
|||||||
|
|
||||||
> **智能代理池系统** — 基于 Go 的轻量级、低资源消耗、自适应的代理池服务
|
> **智能代理池系统** — 基于 Go 的轻量级、低资源消耗、自适应的代理池服务
|
||||||
|
|
||||||
|
[](https://hub.docker.com/r/isboyjc/goproxy)
|
||||||
|
[](https://github.com/isboyjc/GoProxy/pkgs/container/goproxy)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://go.dev/)
|
||||||
|
|
||||||
GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供统一的 HTTP 代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。
|
GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供统一的 HTTP 代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。
|
||||||
|
|
||||||
|
**GitHub**:[github.com/isboyjc/GoProxy](https://github.com/isboyjc/GoProxy)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 📑 目录
|
||||||
|
|
||||||
|
- [核心特性](#-核心特性)
|
||||||
|
- [项目结构](#-项目结构)
|
||||||
|
- [快速开始](#-快速开始)
|
||||||
|
- [Docker 部署](#-docker-部署) - 推荐使用 docker-compose
|
||||||
|
- [配置说明](#️-配置说明) - 环境变量、池子参数、认证、地理过滤
|
||||||
|
- [WebUI 使用指南](#-webui-使用指南) - 双角色权限、操作功能
|
||||||
|
- [核心架构](#️-核心架构) - 智能池子、状态转换、质量分级
|
||||||
|
- [数据库 Schema](#-数据库-schema)
|
||||||
|
- [代理源](#-代理源)
|
||||||
|
- [测试代理服务](#-测试代理服务)
|
||||||
|
- [常见问题](#-常见问题)
|
||||||
|
- [致谢与声明](#-致谢与声明)
|
||||||
|
|
||||||
|
**扩展文档**:
|
||||||
|
- [Docker 镜像发布配置](.github/DOCKER_SETUP.md) - GitHub Actions CI/CD 设置
|
||||||
|
- [地理过滤配置指南](GEO_FILTER.md) - 国家代码、使用场景、测试方法
|
||||||
|
- [认证功能测试](test/AUTH_TEST.md) - 代理认证测试和多语言客户端示例
|
||||||
|
- [基础功能测试](test/TEST_GUIDE.md) - 测试脚本使用指南
|
||||||
|
- [架构设计文档](POOL_DESIGN.md) - 完整的系统设计和实现细节
|
||||||
|
|
||||||
## ✨ 核心特性
|
## ✨ 核心特性
|
||||||
|
|
||||||
### 🎯 智能池子机制
|
### 🎯 智能池子机制
|
||||||
@@ -39,6 +68,7 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
|
|||||||
- **7776 端口(最低延迟)**:固定使用延迟最低的代理,除非失败才切换,适合长连接和流媒体
|
- **7776 端口(最低延迟)**:固定使用延迟最低的代理,除非失败才切换,适合长连接和流媒体
|
||||||
- **自动切换**:两个端口都支持失败自动重试,7776 失败后切换到次优代理
|
- **自动切换**:两个端口都支持失败自动重试,7776 失败后切换到次优代理
|
||||||
- **共享池子**:两个端口使用同一个代理池,统一管理和优化
|
- **共享池子**:两个端口使用同一个代理池,统一管理和优化
|
||||||
|
- **可选认证**:支持 Basic Auth 认证保护,对外开放时可启用
|
||||||
|
|
||||||
### 🎨 黑客风格 WebUI
|
### 🎨 黑客风格 WebUI
|
||||||
- **Matrix 美学**:荧光绿 + 纯黑背景,CRT 扫描线效果,JetBrains Mono 等宽字体
|
- **Matrix 美学**:荧光绿 + 纯黑背景,CRT 扫描线效果,JetBrains Mono 等宽字体
|
||||||
@@ -58,26 +88,35 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
.
|
.
|
||||||
├── main.go # 程序入口,协调所有模块
|
├── main.go # 程序入口,协调所有模块
|
||||||
├── config/ # 配置系统(池子容量、延迟标准、验证参数等)
|
├── config/ # 配置系统(池子容量、延迟标准、验证参数等)
|
||||||
├── pool/ # 🆕 池子管理器(入池判断、替换逻辑、状态计算)
|
├── pool/ # 🆕 池子管理器(入池判断、替换逻辑、状态计算)
|
||||||
├── fetcher/ # 🆕 智能抓取器(源分组、断路器、按需抓取)
|
├── fetcher/ # 🆕 智能抓取器(源分组、断路器、按需抓取)
|
||||||
│ ├── fetcher.go # 多模式抓取逻辑
|
│ ├── fetcher.go # 多模式抓取逻辑
|
||||||
│ ├── source_manager.go # 源状态管理和断路器
|
│ ├── source_manager.go # 源状态管理和断路器
|
||||||
│ └── ip_query.go # IP查询限流和多源降级
|
│ └── ip_query.go # IP查询限流和多源降级
|
||||||
├── validator/ # 代理验证(连接测试 + 出口IP检测)
|
├── validator/ # 代理验证(连接测试 + 出口IP检测 + 地理过滤)
|
||||||
├── checker/ # 🆕 分批健康检查器
|
├── checker/ # 🆕 分批健康检查器
|
||||||
├── optimizer/ # 🆕 优化轮换器(定时优化池子质量)
|
├── optimizer/ # 🆕 优化轮换器(定时优化池子质量)
|
||||||
├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表)
|
├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表)
|
||||||
├── proxy/ # 对外 HTTP 代理服务
|
├── proxy/ # 🆕 对外 HTTP 代理服务(双端口 + 可选认证)
|
||||||
├── webui/ # 🆕 黑客风格 WebUI(健康仪表盘、配置界面)
|
├── webui/ # 🆕 黑客风格 WebUI(健康仪表盘、配置界面、RBAC)
|
||||||
├── logger/ # 内存日志收集
|
├── logger/ # 内存日志收集
|
||||||
├── test/ # 🧪 测试脚本(Bash/Go/Python)
|
├── test/ # 🧪 测试脚本与文档
|
||||||
│ ├── test_proxy.sh # Bash 测试脚本
|
│ ├── test_proxy.sh # Bash 测试脚本
|
||||||
│ ├── test_proxy.go # Go 测试脚本
|
│ ├── test_proxy.go # Go 测试脚本
|
||||||
│ ├── test_proxy.py # Python 测试脚本
|
│ ├── test_proxy.py # Python 测试脚本
|
||||||
│ └── TEST_GUIDE.md # 测试指南
|
│ ├── TEST_GUIDE.md # 基础功能测试指南
|
||||||
└── POOL_DESIGN.md # 🆕 完整架构设计文档
|
│ └── AUTH_TEST.md # 🆕 认证功能测试指南
|
||||||
|
├── .github/ # 🆕 GitHub Actions CI/CD
|
||||||
|
│ ├── workflows/docker-image.yml # 自动构建多架构镜像
|
||||||
|
│ └── DOCKER_SETUP.md # Docker 镜像发布配置指南
|
||||||
|
├── .env.example # 🆕 环境变量配置模板
|
||||||
|
├── docker-compose.yml # 🆕 Docker Compose 配置(自动更新镜像)
|
||||||
|
├── Dockerfile # Docker 构建文件
|
||||||
|
├── GEO_FILTER.md # 🆕 地理过滤配置指南
|
||||||
|
├── POOL_DESIGN.md # 完整架构设计文档
|
||||||
|
└── README.md # 本文件
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
@@ -100,15 +139,16 @@ go build -o proxygo .
|
|||||||
```
|
```
|
||||||
|
|
||||||
程序启动后会:
|
程序启动后会:
|
||||||
1. 加载配置(优先 `config.json`)
|
1. 加载配置(环境变量 + `config.json`)
|
||||||
2. 初始化数据库和限流器
|
2. 初始化数据库和限流器
|
||||||
3. 启动 WebUI(`:7778`)
|
3. 清理不符合条件的代理(屏蔽国家出口、无地理信息)
|
||||||
4. 立即执行智能填充(按需抓取 + 严格验证)
|
4. 启动 WebUI(`:7778`)
|
||||||
5. 启动后台协程:
|
5. 立即执行智能填充(按需抓取 + 严格验证)
|
||||||
- 状态监控(每分钟)
|
6. 启动后台协程:
|
||||||
|
- 状态监控(每 30 秒)
|
||||||
- 健康检查(默认 5 分钟)
|
- 健康检查(默认 5 分钟)
|
||||||
- 优化轮换(默认 30 分钟)
|
- 优化轮换(默认 30 分钟)
|
||||||
6. 启动两个代理服务:
|
7. 启动两个代理服务(支持可选认证):
|
||||||
- `:7776` - 最低延迟模式(稳定连接)
|
- `:7776` - 最低延迟模式(稳定连接)
|
||||||
- `:7777` - 随机轮换模式(IP 多样性)
|
- `:7777` - 随机轮换模式(IP 多样性)
|
||||||
|
|
||||||
@@ -151,6 +191,7 @@ curl -x http://127.0.0.1:7776 https://httpbin.org/ip
|
|||||||
|
|
||||||
#### 环境变量配置
|
#### 环境变量配置
|
||||||
|
|
||||||
|
**本地使用(无认证)**:
|
||||||
```bash
|
```bash
|
||||||
# 使用随机模式
|
# 使用随机模式
|
||||||
export http_proxy=http://127.0.0.1:7777
|
export http_proxy=http://127.0.0.1:7777
|
||||||
@@ -161,6 +202,13 @@ export http_proxy=http://127.0.0.1:7776
|
|||||||
export https_proxy=http://127.0.0.1:7776
|
export https_proxy=http://127.0.0.1:7776
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**远程使用(带认证)**:
|
||||||
|
```bash
|
||||||
|
# 格式:http://username:password@host:port
|
||||||
|
export http_proxy=http://proxy:your_password@192.168.1.100:7777
|
||||||
|
export https_proxy=http://proxy:your_password@192.168.1.100:7777
|
||||||
|
```
|
||||||
|
|
||||||
#### 端口对比
|
#### 端口对比
|
||||||
|
|
||||||
| 特性 | 7777(随机轮换) | 7776(最低延迟) |
|
| 特性 | 7777(随机轮换) | 7776(最低延迟) |
|
||||||
@@ -190,50 +238,130 @@ export https_proxy=http://127.0.0.1:7776
|
|||||||
|
|
||||||
## 🐳 Docker 部署
|
## 🐳 Docker 部署
|
||||||
|
|
||||||
### 使用 Dockerfile
|
> 💡 **自动构建**:GitHub Actions 自动构建多架构镜像(amd64/arm64),支持 Docker Hub 和 GHCR 双仓库推送。
|
||||||
|
> 详细配置说明请查看 [`.github/DOCKER_SETUP.md`](.github/DOCKER_SETUP.md)
|
||||||
|
|
||||||
|
### 快速启动(推荐)
|
||||||
|
|
||||||
|
使用 docker-compose,支持环境变量配置,自动拉取最新镜像:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t proxygo .
|
# 1. 复制环境变量模板
|
||||||
docker run -d \
|
cp .env.example .env
|
||||||
--name proxygo \
|
|
||||||
-p 127.0.0.1:7776:7776 \
|
# 2. 根据需要编辑 .env(可选,默认配置即可使用)
|
||||||
-p 127.0.0.1:7777:7777 \
|
# vim .env
|
||||||
-p 7778:7778 \
|
|
||||||
-e TZ=Asia/Shanghai \
|
# 3. 启动服务(自动拉取最新镜像)
|
||||||
-e WEBUI_PASSWORD=your_password \
|
docker compose up -d
|
||||||
-e DATA_DIR=/app/data \
|
|
||||||
-v "$(pwd)/data:/app/data" \
|
# 4. 访问 WebUI
|
||||||
proxygo
|
# http://localhost:7778(默认密码:goproxy)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用 docker-compose
|
### 使用 Docker Hub 镜像
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --build
|
docker pull isboyjc/goproxy:latest
|
||||||
|
docker run -d --name proxygo \
|
||||||
|
-p 127.0.0.1:7776:7776 -p 127.0.0.1:7777:7777 -p 7778:7778 \
|
||||||
|
-e WEBUI_PASSWORD=your_password -v "$(pwd)/data:/app/data" \
|
||||||
|
isboyjc/goproxy:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### WebUI 公网访问配置
|
### 使用 GitHub Container Registry 镜像
|
||||||
|
|
||||||
得益于**双角色权限系统**,WebUI 可以安全地对外开放:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker pull ghcr.io/isboyjc/goproxy:latest
|
||||||
--name proxygo \
|
docker run -d --name proxygo \
|
||||||
-p 127.0.0.1:7776:7776 \
|
-p 127.0.0.1:7776:7776 -p 127.0.0.1:7777:7777 -p 7778:7778 \
|
||||||
-p 127.0.0.1:7777:7777 \
|
-e WEBUI_PASSWORD=your_password -v "$(pwd)/data:/app/data" \
|
||||||
-p 0.0.0.0:7778:7778 \ # 公网访问 WebUI
|
ghcr.io/isboyjc/goproxy:latest
|
||||||
-e TZ=Asia/Shanghai \
|
|
||||||
-e WEBUI_PASSWORD=strong_password \
|
|
||||||
-e DATA_DIR=/app/data \
|
|
||||||
-v "$(pwd)/data:/app/data" \
|
|
||||||
proxygo
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**安全说明**:
|
### 环境变量配置
|
||||||
- 访客(未登录)只能查看数据,无法执行任何操作
|
|
||||||
- 所有写操作(抓取、删除、配置修改)都需要管理员密码
|
**核心配置**(`.env` 文件):
|
||||||
- 建议设置强密码(通过 `WEBUI_PASSWORD` 环境变量)
|
|
||||||
- 代理服务端口(7776、7777)建议仅绑定内网(`127.0.0.1`)
|
| 变量 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `PROXY_HOST` | `127.0.0.1` | 代理服务绑定地址(`0.0.0.0` = 对外开放) |
|
||||||
|
| `BLOCKED_COUNTRIES` | `CN` | 屏蔽的国家代码(逗号分隔,如 `CN,RU`,留空=不屏蔽) |
|
||||||
|
| `PROXY_AUTH_ENABLED` | `false` | 是否启用代理认证 |
|
||||||
|
| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 |
|
||||||
|
| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码 |
|
||||||
|
| `WEBUI_PASSWORD` | `goproxy` | WebUI 登录密码 |
|
||||||
|
|
||||||
|
完整环境变量列表请查看 `.env.example` 文件。
|
||||||
|
|
||||||
|
**常用配置示例**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 场景 1:本地使用(默认,最安全)
|
||||||
|
# 直接运行 docker compose up -d 即可
|
||||||
|
|
||||||
|
# 场景 2:对外开放 + 启用认证(推荐)
|
||||||
|
cat > .env << EOF
|
||||||
|
PROXY_HOST=0.0.0.0
|
||||||
|
PROXY_AUTH_ENABLED=true
|
||||||
|
PROXY_AUTH_USERNAME=myuser
|
||||||
|
PROXY_AUTH_PASSWORD=secure_pass_123
|
||||||
|
WEBUI_PASSWORD=admin_pass_456
|
||||||
|
BLOCKED_COUNTRIES=CN
|
||||||
|
EOF
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 场景 3:屏蔽多个国家
|
||||||
|
cat > .env << EOF
|
||||||
|
BLOCKED_COUNTRIES=CN,RU,KP,IR
|
||||||
|
WEBUI_PASSWORD=admin_pass
|
||||||
|
EOF
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 场景 4:不屏蔽任何国家
|
||||||
|
cat > .env << EOF
|
||||||
|
BLOCKED_COUNTRIES=
|
||||||
|
WEBUI_PASSWORD=admin_pass
|
||||||
|
EOF
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安全部署配置
|
||||||
|
|
||||||
|
**默认配置**:代理服务仅本地访问(`127.0.0.1`),WebUI 可对外访问。
|
||||||
|
|
||||||
|
**如需对外开放代理服务**,强烈建议启用认证:
|
||||||
|
|
||||||
|
| 使用场景 | 配置建议 | 安全级别 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| **个人 VPS + 本地使用** | `PROXY_HOST=127.0.0.1`(默认) | ⭐⭐⭐⭐⭐ 最安全 |
|
||||||
|
| **团队共享 + 认证** | `PROXY_HOST=0.0.0.0` + 启用认证 | ⭐⭐⭐⭐ 安全 |
|
||||||
|
| **内网 + 防火墙** | `PROXY_HOST=0.0.0.0` + iptables 白名单 | ⭐⭐⭐ 中等 |
|
||||||
|
|
||||||
|
**启用代理认证**(编辑 `.env`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PROXY_HOST=0.0.0.0
|
||||||
|
PROXY_AUTH_ENABLED=true
|
||||||
|
PROXY_AUTH_USERNAME=myuser
|
||||||
|
PROXY_AUTH_PASSWORD=secure_pass_123
|
||||||
|
```
|
||||||
|
|
||||||
|
**客户端使用(带认证)**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 环境变量方式
|
||||||
|
export http_proxy=http://myuser:secure_pass_123@server-ip:7777
|
||||||
|
export https_proxy=http://myuser:secure_pass_123@server-ip:7777
|
||||||
|
|
||||||
|
# curl 直接指定
|
||||||
|
curl -x http://myuser:secure_pass_123@server-ip:7777 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# Python requests
|
||||||
|
proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': '...'}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💡 **认证测试指南**:详细的认证功能测试和多语言客户端示例,请查看 [`test/AUTH_TEST.md`](test/AUTH_TEST.md)
|
||||||
|
|
||||||
## ⚙️ 配置说明
|
## ⚙️ 配置说明
|
||||||
|
|
||||||
@@ -268,6 +396,16 @@ docker run -d \
|
|||||||
| `stable_proxy_port` | `:7776` | 最低延迟代理端口 |
|
| `stable_proxy_port` | `:7776` | 最低延迟代理端口 |
|
||||||
| `webui_port` | `:7778` | WebUI 端口 |
|
| `webui_port` | `:7778` | WebUI 端口 |
|
||||||
|
|
||||||
|
**代理认证配置**
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `proxy_auth_enabled` | `false` | 是否启用代理认证(对外开放时建议启用) |
|
||||||
|
| `proxy_auth_username` | `proxy` | 代理认证用户名 |
|
||||||
|
| `proxy_auth_password_hash` | 空 | 代理认证密码 SHA256 哈希(通过环境变量 `PROXY_AUTH_PASSWORD` 设置原始密码) |
|
||||||
|
|
||||||
|
> 💡 **注意**:代理认证配置通过**环境变量**设置,不在 `config.json` 中。启动时从 `PROXY_AUTH_ENABLED`、`PROXY_AUTH_USERNAME`、`PROXY_AUTH_PASSWORD` 环境变量读取。
|
||||||
|
|
||||||
**池子容量配置**
|
**池子容量配置**
|
||||||
|
|
||||||
| 参数 | 默认值 | 说明 | 推荐范围 |
|
| 参数 | 默认值 | 说明 | 推荐范围 |
|
||||||
@@ -349,17 +487,28 @@ docker run -d \
|
|||||||
|
|
||||||
### 固定配置
|
### 固定配置
|
||||||
|
|
||||||
以下配置在代码中固定,无需调整:
|
以下配置在代码中固定或通过环境变量设置,无需在 `config.json` 中调整:
|
||||||
|
|
||||||
| 配置项 | 值 | 说明 |
|
| 配置项 | 值 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `WebUIPort` | `:7778` | Web 管理后台端口 |
|
| `WebUIPort` | `:7778` | WebUI 端口 |
|
||||||
| `ProxyPort` | `:7777` | 对外统一代理端口 |
|
| `ProxyPort` | `:7777` | 随机轮换代理端口 |
|
||||||
|
| `StableProxyPort` | `:7776` | 最低延迟代理端口 |
|
||||||
| `ValidateURL` | `http://www.gstatic.com/generate_204` | 验证目标地址 |
|
| `ValidateURL` | `http://www.gstatic.com/generate_204` | 验证目标地址 |
|
||||||
| `IPQueryRateLimit` | `10` | IP 查询限流(次/秒) |
|
| `IPQueryRateLimit` | `10 次/秒` | IP 查询限流 |
|
||||||
| `SourceFailThreshold` | `3` | 源降级阈值 |
|
| `SourceFailThreshold` | `3` | 源降级阈值(连续失败) |
|
||||||
| `SourceDisableThreshold` | `5` | 源禁用阈值 |
|
| `SourceDisableThreshold` | `5` | 源禁用阈值(连续失败) |
|
||||||
| `SourceCooldownMinutes` | `30` | 源禁用冷却时间(分钟) |
|
| `SourceCooldownMinutes` | `30` | 源禁用冷却时间 |
|
||||||
|
| `MaxRetry` | `3` | 代理请求失败重试次数 |
|
||||||
|
|
||||||
|
**环境变量配置**(启动时读取):
|
||||||
|
|
||||||
|
| 环境变量 | 默认值 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PROXY_AUTH_ENABLED` | `false` | 是否启用代理认证 |
|
||||||
|
| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 |
|
||||||
|
| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码(原始密码,自动哈希) |
|
||||||
|
| `BLOCKED_COUNTRIES` | `CN` | 屏蔽的国家代码(逗号分隔,如 `CN,RU,KP`,留空=不屏蔽) |
|
||||||
|
|
||||||
## 🎨 WebUI 使用指南
|
## 🎨 WebUI 使用指南
|
||||||
|
|
||||||
@@ -415,23 +564,29 @@ GoProxy WebUI 支持**访客模式**和**管理员模式**:
|
|||||||
### 代理注册表
|
### 代理注册表
|
||||||
|
|
||||||
**表格字段**
|
**表格字段**
|
||||||
- **Grade**:质量等级(S/A/B/C)
|
- **Grade**:质量等级(S/A/B/C,基于延迟计算)
|
||||||
- **Protocol**:协议类型(HTTP/SOCKS5)
|
- **Protocol**:协议类型(HTTP/SOCKS5)
|
||||||
- **Address**:代理地址(host:port)
|
- **Address**:代理地址(host:port),点击可复制
|
||||||
- **Exit IP**:出口 IP
|
- **Exit IP**:代理的出口 IP 地址
|
||||||
- **Location**:出口地理位置(国旗 + 国家代码 + 城市)
|
- **Location**:出口地理位置(国旗 emoji + 国家代码 + 城市)
|
||||||
- **Latency**:延迟(毫秒,颜色编码)
|
- **Latency**:连接延迟(毫秒,动态颜色编码:绿/黄/橙/红)
|
||||||
- **Usage**:使用次数 / 成功次数
|
- **Usage**:使用统计(使用总次数 / 成功次数,成功率指标)
|
||||||
- **Action**:删除按钮
|
- **Action**:操作按钮(刷新单个代理、删除代理,管理员可见)
|
||||||
|
|
||||||
**操作功能**
|
**操作功能**
|
||||||
- **筛选**:All / HTTP / SOCKS5(所有用户可用)
|
|
||||||
- **点击复制地址**:点击代理地址单元格复制到剪贴板(所有用户可用)
|
**所有用户可用**:
|
||||||
- **Fetch Proxies**:手动触发智能抓取(⚡ 管理员专属)
|
- **协议筛选**:下拉选择协议类型(全部/HTTP/SOCKS5)
|
||||||
- **Refresh Latency**:重新验证所有代理并更新延迟(⚡ 管理员专属)
|
- **国家筛选**:下拉选择出口国家(全部/动态国家列表,带国旗 emoji)
|
||||||
- **刷新单个代理**:点击行内刷新按钮验证单个代理(⚡ 管理员专属)
|
- **点击复制地址**:点击代理地址单元格直接复制到剪贴板
|
||||||
- **删除代理**:点击行内删除按钮移除指定代理(⚡ 管理员专属)
|
- **查看数据**:池子状态、质量分布、系统日志
|
||||||
- **Configure Pool**:打开配置界面(⚡ 管理员专属)
|
|
||||||
|
**管理员专属**(需登录):
|
||||||
|
- **Fetch Proxies**:手动触发智能抓取
|
||||||
|
- **Refresh Latency**:重新验证所有代理并更新延迟
|
||||||
|
- **刷新单个代理**:点击行内刷新按钮验证单个代理
|
||||||
|
- **删除代理**:点击行内删除按钮移除指定代理
|
||||||
|
- **Configure Pool**:打开配置界面修改池子参数
|
||||||
|
|
||||||
### 配置界面(⚡ 管理员专属)
|
### 配置界面(⚡ 管理员专属)
|
||||||
|
|
||||||
@@ -488,9 +643,9 @@ GoProxy WebUI 支持**访客模式**和**管理员模式**:
|
|||||||
### 状态转换机制
|
### 状态转换机制
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Healthy (总数≥80% 且 各协议≥80%槽位)
|
Healthy (总数≥95% 且 各协议≥80%槽位)
|
||||||
↓ 代理失效
|
↓ 代理失效
|
||||||
Warning (总数<80% 或 任一协议<80%)
|
Warning (总数<95% 或 任一协议<80%)
|
||||||
↓ 继续失效
|
↓ 继续失效
|
||||||
Critical (总数<50% 或 任一协议<20%槽位)
|
Critical (总数<50% 或 任一协议<20%槽位)
|
||||||
↓ 继续失效
|
↓ 继续失效
|
||||||
@@ -499,12 +654,14 @@ Emergency (总数<10% 或 单协议缺失)
|
|||||||
└─ 自动触发紧急抓取 ─┘
|
└─ 自动触发紧急抓取 ─┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 💡 **自动补充阈值**:当总数低于 95% 时进入 Warning 状态并触发自动补充,确保池子始终接近满容量运行。
|
||||||
|
|
||||||
### 抓取模式选择
|
### 抓取模式选择
|
||||||
|
|
||||||
| 池子状态 | 抓取模式 | 使用源 | 触发条件 |
|
| 池子状态 | 抓取模式 | 使用源 | 触发条件 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Emergency | 紧急模式 | 所有可用源 | 单协议缺失或总数<10% |
|
| Emergency | 紧急模式 | 所有可用源 | 单协议缺失或总数<10% |
|
||||||
| Critical/Warning | 补充模式 | 快更新源 | 总数<80%或协议不均 |
|
| Critical/Warning | 补充模式 | 快更新源 | 总数<95%或协议不均 |
|
||||||
| Healthy | 优化模式 | 慢更新源(随机2-3个) | 定时触发(30分钟) |
|
| Healthy | 优化模式 | 慢更新源(随机2-3个) | 定时触发(30分钟) |
|
||||||
|
|
||||||
### 质量分级标准
|
### 质量分级标准
|
||||||
@@ -648,8 +805,8 @@ A:
|
|||||||
|
|
||||||
### Q: 池子状态如何计算?
|
### Q: 池子状态如何计算?
|
||||||
A:
|
A:
|
||||||
- **Healthy**:总数 ≥80% 且各协议 ≥80% 槽位
|
- **Healthy**:总数 ≥95% 且各协议 ≥80% 槽位
|
||||||
- **Warning**:总数 <80% 或任一协议 <80% 槽位
|
- **Warning**:总数 <95% 或任一协议 <80% 槽位
|
||||||
- **Critical**:总数 <50% 或任一协议 <20% 槽位
|
- **Critical**:总数 <50% 或任一协议 <20% 槽位
|
||||||
- **Emergency**:总数 <10% 或单协议缺失
|
- **Emergency**:总数 <10% 或单协议缺失
|
||||||
|
|
||||||
@@ -665,6 +822,31 @@ A:
|
|||||||
- IP 查询有限流(10 次/秒)
|
- IP 查询有限流(10 次/秒)
|
||||||
- 部分代理可能不支持 IP 查询
|
- 部分代理可能不支持 IP 查询
|
||||||
- 系统会在后续健康检查中补全信息
|
- 系统会在后续健康检查中补全信息
|
||||||
|
- 没有出口信息的代理会在启动时被自动清理
|
||||||
|
|
||||||
|
### Q: 如何配置地理过滤?
|
||||||
|
A:
|
||||||
|
通过 `BLOCKED_COUNTRIES` 环境变量配置需要屏蔽的国家:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认屏蔽中国大陆(CN)
|
||||||
|
BLOCKED_COUNTRIES=CN
|
||||||
|
|
||||||
|
# 屏蔽多个国家(逗号分隔)
|
||||||
|
BLOCKED_COUNTRIES=CN,RU,KP
|
||||||
|
|
||||||
|
# 不屏蔽任何国家(留空)
|
||||||
|
BLOCKED_COUNTRIES=
|
||||||
|
```
|
||||||
|
|
||||||
|
**工作机制**:
|
||||||
|
- **验证阶段**:检测到屏蔽国家出口直接拒绝入池
|
||||||
|
- **启动清理**:自动删除数据库中屏蔽国家的代理
|
||||||
|
- **精确匹配**:使用 ISO 3166-1 alpha-2 国家代码(CN、HK、US 等)
|
||||||
|
|
||||||
|
**常用国家代码**:`CN`=中国大陆 | `HK`=香港 | `RU`=俄罗斯 | `US`=美国 | `JP`=日本 | `SG`=新加坡
|
||||||
|
|
||||||
|
> 📖 **详细配置指南**:更多国家代码、使用场景、测试方法,请查看 [`GEO_FILTER.md`](./GEO_FILTER.md)
|
||||||
|
|
||||||
### Q: 资源消耗如何?
|
### Q: 资源消耗如何?
|
||||||
A:
|
A:
|
||||||
@@ -675,11 +857,24 @@ A:
|
|||||||
- 按需抓取,避免无效流量
|
- 按需抓取,避免无效流量
|
||||||
- 健康检查批次小(20 个)
|
- 健康检查批次小(20 个)
|
||||||
|
|
||||||
## 📚 详细设计文档
|
### Q: 代理服务如何启用认证?
|
||||||
|
A:
|
||||||
|
1. 编辑 `.env` 文件:
|
||||||
|
```bash
|
||||||
|
PROXY_AUTH_ENABLED=true
|
||||||
|
PROXY_AUTH_USERNAME=myuser
|
||||||
|
PROXY_AUTH_PASSWORD=mypass
|
||||||
|
```
|
||||||
|
2. 重启服务:`docker compose up -d`
|
||||||
|
3. 客户端使用:`http://myuser:mypass@server-ip:7777`
|
||||||
|
|
||||||
完整的架构设计、模块说明、配置策略、资源优化方案,请查看:
|
详细说明和测试方法请查看 [`test/AUTH_TEST.md`](./test/AUTH_TEST.md)
|
||||||
|
|
||||||
👉 [POOL_DESIGN.md](./POOL_DESIGN.md)
|
### Q: 代理认证和 WebUI 认证有什么区别?
|
||||||
|
A:
|
||||||
|
- **代理认证**:保护 7776/7777 代理服务端口,防止代理被滥用
|
||||||
|
- **WebUI 认证**:保护 7778 管理后台,区分访客和管理员权限
|
||||||
|
- 两者独立配置,互不影响
|
||||||
|
|
||||||
## 🛠️ 开发与调试
|
## 🛠️ 开发与调试
|
||||||
|
|
||||||
@@ -759,7 +954,18 @@ proxy from 🇯🇵 198.51.100.12: seq=5 time=890ms
|
|||||||
50 requests transmitted, 47 received, 3 failed, 6.0% packet loss
|
50 requests transmitted, 47 received, 3 failed, 6.0% packet loss
|
||||||
```
|
```
|
||||||
|
|
||||||
详细测试指南请查看:👉 [test/TEST_GUIDE.md](./test/TEST_GUIDE.md)
|
**测试认证功能**:
|
||||||
|
```bash
|
||||||
|
# 启用认证后测试
|
||||||
|
curl -x http://myuser:mypass@127.0.0.1:7777 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# 无认证请求(应该返回 407 错误)
|
||||||
|
curl -x http://127.0.0.1:7777 https://httpbin.org/ip
|
||||||
|
```
|
||||||
|
|
||||||
|
**更多测试指南**:
|
||||||
|
- 基础功能测试:[`test/TEST_GUIDE.md`](./test/TEST_GUIDE.md)
|
||||||
|
- 认证功能测试:[`test/AUTH_TEST.md`](./test/AUTH_TEST.md)
|
||||||
|
|
||||||
## 🙏 致谢与声明
|
## 🙏 致谢与声明
|
||||||
|
|
||||||
@@ -778,10 +984,13 @@ proxy from 🇯🇵 198.51.100.12: seq=5 time=890ms
|
|||||||
- 🆕 **分层健康管理**:批次检查、智能跳过 S 级、定时优化轮换
|
- 🆕 **分层健康管理**:批次检查、智能跳过 S 级、定时优化轮换
|
||||||
- 🆕 **智能重试机制**:自动故障切换、失败即删除、防重复尝试
|
- 🆕 **智能重试机制**:自动故障切换、失败即删除、防重复尝试
|
||||||
- 🆕 **双端口服务**:7777 随机轮换(IP 多样性)+ 7776 最低延迟(稳定连接)
|
- 🆕 **双端口服务**:7777 随机轮换(IP 多样性)+ 7776 最低延迟(稳定连接)
|
||||||
|
- 🆕 **代理认证保护**:可选 Basic Auth 认证,对外开放时保护代理服务不被滥用
|
||||||
- 🆕 **黑客风格 WebUI**:Matrix 美学、实时仪表盘、完整配置界面、中英文切换
|
- 🆕 **黑客风格 WebUI**:Matrix 美学、实时仪表盘、完整配置界面、中英文切换
|
||||||
- 🆕 **双角色权限**:访客模式(只读)+ 管理员模式(完全控制),可安全公网开放
|
- 🆕 **双角色权限**:访客模式(只读)+ 管理员模式(完全控制),可安全公网开放
|
||||||
- 🆕 **扩展存储层**:质量等级、使用统计、源状态管理
|
- 🆕 **扩展存储层**:质量等级、使用统计、源状态管理
|
||||||
- 🆕 **测试套件**:Bash/Go/Python 三种测试脚本,持续运行模式,显示国旗 emoji
|
- 🆕 **测试套件**:Bash/Go/Python 三种测试脚本,持续运行模式,显示国旗 emoji
|
||||||
|
- 🆕 **CI/CD 自动化**:GitHub Actions 自动构建多架构镜像(amd64/arm64),双仓库发布
|
||||||
|
- 🆕 **环境变量配置**:docker-compose + .env 文件,灵活配置各种部署场景
|
||||||
|
|
||||||
感谢原作者提供的基础实现,让我们能够在此之上构建更强大的代理池系统。
|
感谢原作者提供的基础实现,让我们能够在此之上构建更强大的代理池系统。
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +34,14 @@ type Config struct {
|
|||||||
// 稳定代理端口(最低延迟模式)
|
// 稳定代理端口(最低延迟模式)
|
||||||
StableProxyPort string
|
StableProxyPort string
|
||||||
|
|
||||||
|
// 代理服务认证配置
|
||||||
|
ProxyAuthEnabled bool // 是否启用代理认证(默认 false)
|
||||||
|
ProxyAuthUsername string // 代理认证用户名(默认 "proxy")
|
||||||
|
ProxyAuthPasswordHash string // 代理认证密码 SHA256 哈希
|
||||||
|
|
||||||
|
// 地理过滤配置
|
||||||
|
BlockedCountries []string // 屏蔽的国家代码列表(如 ["CN", "RU"],默认 ["CN"])
|
||||||
|
|
||||||
// SQLite 数据库路径
|
// SQLite 数据库路径
|
||||||
DBPath string
|
DBPath string
|
||||||
|
|
||||||
@@ -97,6 +106,33 @@ func DefaultConfig() *Config {
|
|||||||
if password == "" {
|
if password == "" {
|
||||||
password = DefaultPassword
|
password = DefaultPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取代理认证配置
|
||||||
|
proxyAuthEnabled := os.Getenv("PROXY_AUTH_ENABLED") == "true"
|
||||||
|
proxyAuthUsername := os.Getenv("PROXY_AUTH_USERNAME")
|
||||||
|
if proxyAuthUsername == "" {
|
||||||
|
proxyAuthUsername = "proxy"
|
||||||
|
}
|
||||||
|
proxyAuthPassword := os.Getenv("PROXY_AUTH_PASSWORD")
|
||||||
|
proxyAuthHash := ""
|
||||||
|
if proxyAuthPassword != "" {
|
||||||
|
proxyAuthHash = passwordHash(proxyAuthPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取地理过滤配置
|
||||||
|
blockedCountries := []string{"CN"} // 默认屏蔽中国大陆
|
||||||
|
if blockedEnv := os.Getenv("BLOCKED_COUNTRIES"); blockedEnv != "" {
|
||||||
|
// 支持逗号分隔的国家代码,如 "CN,RU,KP"
|
||||||
|
countries := strings.Split(blockedEnv, ",")
|
||||||
|
blockedCountries = make([]string, 0, len(countries))
|
||||||
|
for _, c := range countries {
|
||||||
|
c = strings.TrimSpace(strings.ToUpper(c))
|
||||||
|
if c != "" {
|
||||||
|
blockedCountries = append(blockedCountries, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
// 基础服务配置
|
// 基础服务配置
|
||||||
WebUIPort: ":7778",
|
WebUIPort: ":7778",
|
||||||
@@ -104,6 +140,14 @@ func DefaultConfig() *Config {
|
|||||||
ProxyPort: ":7777",
|
ProxyPort: ":7777",
|
||||||
StableProxyPort: ":7776",
|
StableProxyPort: ":7776",
|
||||||
DBPath: dataDir() + "proxy.db",
|
DBPath: dataDir() + "proxy.db",
|
||||||
|
|
||||||
|
// 代理认证配置
|
||||||
|
ProxyAuthEnabled: proxyAuthEnabled,
|
||||||
|
ProxyAuthUsername: proxyAuthUsername,
|
||||||
|
ProxyAuthPasswordHash: proxyAuthHash,
|
||||||
|
|
||||||
|
// 地理过滤配置
|
||||||
|
BlockedCountries: blockedCountries,
|
||||||
|
|
||||||
// 池子容量配置
|
// 池子容量配置
|
||||||
PoolMaxSize: 100, // 总容量
|
PoolMaxSize: 100, // 总容量
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
services:
|
services:
|
||||||
proxygo:
|
proxygo:
|
||||||
build: .
|
# 使用预构建镜像(每次部署自动拉取最新版本)
|
||||||
container_name: proxygo
|
image: ghcr.io/isboyjc/goproxy:latest
|
||||||
|
pull_policy: always
|
||||||
|
|
||||||
|
# 或使用本地构建(取消下面注释,注释上面的 image 和 pull_policy 行)
|
||||||
|
# build: .
|
||||||
|
|
||||||
|
container_name: ${CONTAINER_NAME:-proxygo}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:7776:7776" # 稳定代理端口(最低延迟)
|
# 代理端口:默认仅本地访问(最安全)
|
||||||
- "127.0.0.1:7777:7777" # 随机代理端口(轮换模式)
|
# 如需对外开放,在 .env 中设置 PROXY_HOST=0.0.0.0(⚠️ 无认证,需配合防火墙)
|
||||||
- "7778:7778" # WebUI 端口(外网可访问)
|
- "${PROXY_HOST:-127.0.0.1}:${STABLE_PORT:-7776}:7776" # 稳定代理(最低延迟)
|
||||||
|
- "${PROXY_HOST:-127.0.0.1}:${RANDOM_PORT:-7777}:7777" # 随机轮换
|
||||||
|
- "${WEBUI_HOST:-0.0.0.0}:${WEBUI_PORT:-7778}:7778" # WebUI(有登录认证)
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ${DATA_DIR:-./data}:/app/data
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=${TZ:-Asia/Shanghai}
|
||||||
- DATA_DIR=/app/data
|
- DATA_DIR=/app/data
|
||||||
# - WEBUI_PASSWORD=your-password # 自定义 WebUI 登录密码,默认: proxygo
|
- WEBUI_PASSWORD=${WEBUI_PASSWORD:-goproxy}
|
||||||
|
- PROXY_AUTH_ENABLED=${PROXY_AUTH_ENABLED:-false}
|
||||||
|
- PROXY_AUTH_USERNAME=${PROXY_AUTH_USERNAME:-proxy}
|
||||||
|
- PROXY_AUTH_PASSWORD=${PROXY_AUTH_PASSWORD}
|
||||||
|
- BLOCKED_COUNTRIES=${BLOCKED_COUNTRIES:-CN}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:7778/"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:7778/"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
networks:
|
|
||||||
- cursor2api_default
|
|
||||||
|
|
||||||
networks:
|
|
||||||
cursor2api_default:
|
|
||||||
external: true
|
|
||||||
|
|||||||
8
main.go
8
main.go
@@ -59,9 +59,11 @@ func main() {
|
|||||||
|
|
||||||
// 清理无效代理
|
// 清理无效代理
|
||||||
totalDeleted := 0
|
totalDeleted := 0
|
||||||
if deleted, err := store.DeleteChinaMainland(); err == nil && deleted > 0 {
|
if len(cfg.BlockedCountries) > 0 {
|
||||||
log.Printf("[main] 🧹 已清理 %d 个中国大陆出口代理", deleted)
|
if deleted, err := store.DeleteBlockedCountries(cfg.BlockedCountries); err == nil && deleted > 0 {
|
||||||
totalDeleted += int(deleted)
|
log.Printf("[main] 🧹 已清理 %d 个屏蔽国家出口代理 (屏蔽: %v)", deleted, cfg.BlockedCountries)
|
||||||
|
totalDeleted += int(deleted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if deleted, err := store.DeleteWithoutExitInfo(); err == nil && deleted > 0 {
|
if deleted, err := store.DeleteWithoutExitInfo(); err == nil && deleted > 0 {
|
||||||
log.Printf("[main] 🧹 已清理 %d 个无出口信息的代理", deleted)
|
log.Printf("[main] 🧹 已清理 %d 个无出口信息的代理", deleted)
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
@@ -35,11 +39,24 @@ func (s *Server) Start() error {
|
|||||||
if s.mode == "lowest-latency" {
|
if s.mode == "lowest-latency" {
|
||||||
modeDesc = "最低延迟"
|
modeDesc = "最低延迟"
|
||||||
}
|
}
|
||||||
log.Printf("proxy server listening on %s [%s]", s.port, modeDesc)
|
authStatus := "无认证"
|
||||||
|
if s.cfg.ProxyAuthEnabled {
|
||||||
|
authStatus = fmt.Sprintf("需认证 (用户: %s)", s.cfg.ProxyAuthUsername)
|
||||||
|
}
|
||||||
|
log.Printf("proxy server listening on %s [%s] [%s]", s.port, modeDesc, authStatus)
|
||||||
return http.ListenAndServe(s.port, s)
|
return http.ListenAndServe(s.port, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 认证检查(如果启用)
|
||||||
|
if s.cfg.ProxyAuthEnabled {
|
||||||
|
if !s.checkAuth(r) {
|
||||||
|
w.Header().Set("Proxy-Authenticate", `Basic realm="GoProxy"`)
|
||||||
|
http.Error(w, "Proxy Authentication Required", http.StatusProxyAuthRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodConnect {
|
if r.Method == http.MethodConnect {
|
||||||
s.handleTunnel(w, r)
|
s.handleTunnel(w, r)
|
||||||
} else {
|
} else {
|
||||||
@@ -47,6 +64,40 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkAuth 验证代理 Basic Auth
|
||||||
|
func (s *Server) checkAuth(r *http.Request) bool {
|
||||||
|
auth := r.Header.Get("Proxy-Authorization")
|
||||||
|
if auth == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 Basic Auth
|
||||||
|
const prefix = "Basic "
|
||||||
|
if !strings.HasPrefix(auth, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials := strings.SplitN(string(decoded), ":", 2)
|
||||||
|
if len(credentials) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
username := credentials[0]
|
||||||
|
password := credentials[1]
|
||||||
|
|
||||||
|
// 验证用户名和密码
|
||||||
|
usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(s.cfg.ProxyAuthUsername)) == 1
|
||||||
|
passwordHash := fmt.Sprintf("%x", sha256.Sum256([]byte(password)))
|
||||||
|
passwordMatch := subtle.ConstantTimeCompare([]byte(passwordHash), []byte(s.cfg.ProxyAuthPasswordHash)) == 1
|
||||||
|
|
||||||
|
return usernameMatch && passwordMatch
|
||||||
|
}
|
||||||
|
|
||||||
// handleHTTP 处理普通 HTTP 请求(带自动重试)
|
// handleHTTP 处理普通 HTTP 请求(带自动重试)
|
||||||
func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
var tried []string
|
var tried []string
|
||||||
|
|||||||
@@ -556,15 +556,24 @@ func (s *Storage) DeleteInvalid(maxFailCount int) (int64, error) {
|
|||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteChinaMainland 删除中国大陆出口的代理(保留香港)
|
// DeleteBlockedCountries 删除指定国家代码出口的代理
|
||||||
func (s *Storage) DeleteChinaMainland() (int64, error) {
|
func (s *Storage) DeleteBlockedCountries(countryCodes []string) (int64, error) {
|
||||||
// 删除 exit_location 以 "CN " 开头的代理(CN后面有空格表示是城市)
|
if len(countryCodes) == 0 {
|
||||||
// 香港是 "HK Hong Kong" 或 "HK" 开头,不会被删除
|
return 0, nil
|
||||||
res, err := s.db.Exec(`DELETE FROM proxies WHERE exit_location LIKE 'CN %'`)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
return res.RowsAffected()
|
|
||||||
|
var totalDeleted int64
|
||||||
|
for _, code := range countryCodes {
|
||||||
|
// exit_location 格式:如 "CN Beijing" 或 "HK Hong Kong"
|
||||||
|
// 使用 LIKE 'CODE %' 来匹配国家代码(后面有空格表示有城市信息)
|
||||||
|
res, err := s.db.Exec(`DELETE FROM proxies WHERE exit_location LIKE ?`, code+" %")
|
||||||
|
if err != nil {
|
||||||
|
return totalDeleted, err
|
||||||
|
}
|
||||||
|
affected, _ := res.RowsAffected()
|
||||||
|
totalDeleted += affected
|
||||||
|
}
|
||||||
|
return totalDeleted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWithoutExitInfo 删除没有出口信息的代理
|
// DeleteWithoutExitInfo 删除没有出口信息的代理
|
||||||
|
|||||||
165
test/AUTH_TEST.md
Normal file
165
test/AUTH_TEST.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# 代理认证功能测试指南
|
||||||
|
|
||||||
|
本文档说明如何测试 GoProxy 的代理认证功能。
|
||||||
|
|
||||||
|
## 🔒 启用认证
|
||||||
|
|
||||||
|
### 使用 docker-compose
|
||||||
|
|
||||||
|
编辑 `.env` 文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PROXY_HOST=0.0.0.0
|
||||||
|
PROXY_AUTH_ENABLED=true
|
||||||
|
PROXY_AUTH_USERNAME=testuser
|
||||||
|
PROXY_AUTH_PASSWORD=testpass123
|
||||||
|
```
|
||||||
|
|
||||||
|
启动服务:
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 直接运行
|
||||||
|
|
||||||
|
设置环境变量后启动:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PROXY_AUTH_ENABLED=true
|
||||||
|
export PROXY_AUTH_USERNAME=testuser
|
||||||
|
export PROXY_AUTH_PASSWORD=testpass123
|
||||||
|
go run .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 测试认证功能
|
||||||
|
|
||||||
|
### 测试 1:无认证请求(应该失败)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -x http://127.0.0.1:7777 https://httpbin.org/ip -v
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP 状态码:`407 Proxy Authentication Required`
|
||||||
|
- 响应头:`Proxy-Authenticate: Basic realm="GoProxy"`
|
||||||
|
|
||||||
|
### 测试 2:错误的用户名/密码(应该失败)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -x http://wronguser:wrongpass@127.0.0.1:7777 https://httpbin.org/ip -v
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP 状态码:`407 Proxy Authentication Required`
|
||||||
|
|
||||||
|
### 测试 3:正确的认证(应该成功)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -x http://testuser:testpass123@127.0.0.1:7777 https://httpbin.org/ip
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP 状态码:`200 OK`
|
||||||
|
- 返回代理的出口 IP 信息
|
||||||
|
|
||||||
|
### 测试 4:环境变量方式(应该成功)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export http_proxy=http://testuser:testpass123@127.0.0.1:7777
|
||||||
|
export https_proxy=http://testuser:testpass123@127.0.0.1:7777
|
||||||
|
curl https://httpbin.org/ip
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试 5:HTTPS CONNECT 隧道(应该成功)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -x http://testuser:testpass123@127.0.0.1:7776 https://www.google.com -I
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧩 多语言客户端示例
|
||||||
|
|
||||||
|
### Python (requests)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
proxies = {
|
||||||
|
'http': 'http://testuser:testpass123@127.0.0.1:7777',
|
||||||
|
'https': 'http://testuser:testpass123@127.0.0.1:7777',
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get('https://httpbin.org/ip', proxies=proxies)
|
||||||
|
print(response.text)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
proxyURL, _ := url.Parse("http://testuser:testpass123@127.0.0.1:7777")
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyURL(proxyURL),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get("https://httpbin.org/ip")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
fmt.Println(string(body))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
const HttpsProxyAgent = require('https-proxy-agent');
|
||||||
|
|
||||||
|
const proxyUrl = 'http://testuser:testpass123@127.0.0.1:7777';
|
||||||
|
const agent = new HttpsProxyAgent(proxyUrl);
|
||||||
|
|
||||||
|
axios.get('https://httpbin.org/ip', {
|
||||||
|
httpAgent: agent,
|
||||||
|
httpsAgent: agent
|
||||||
|
})
|
||||||
|
.then(response => console.log(response.data))
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 日志验证
|
||||||
|
|
||||||
|
启动时会输出认证状态:
|
||||||
|
|
||||||
|
```
|
||||||
|
proxy server listening on :7777 [随机轮换] [需认证 (用户: testuser)]
|
||||||
|
proxy server listening on :7776 [最低延迟] [需认证 (用户: testuser)]
|
||||||
|
```
|
||||||
|
|
||||||
|
无认证模式:
|
||||||
|
|
||||||
|
```
|
||||||
|
proxy server listening on :7777 [随机轮换] [无认证]
|
||||||
|
proxy server listening on :7776 [最低延迟] [无认证]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 注意事项
|
||||||
|
|
||||||
|
- 认证采用 **HTTP Basic Auth** 标准,兼容绝大多数客户端
|
||||||
|
- 密码存储为 **SHA256 哈希**,安全性高
|
||||||
|
- 认证失败不会删除代理或影响池子状态
|
||||||
|
- 两个端口(7776、7777)共享相同的认证配置
|
||||||
|
- 认证仅保护代理服务,WebUI 有独立的登录系统
|
||||||
@@ -20,6 +20,7 @@ type Validator struct {
|
|||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
validateURL string
|
validateURL string
|
||||||
maxResponseMs int
|
maxResponseMs int
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func concurrencyBuffer(total, concurrency int) int {
|
func concurrencyBuffer(total, concurrency int) int {
|
||||||
@@ -40,6 +41,7 @@ func New(concurrency, timeoutSec int, validateURL string) *Validator {
|
|||||||
timeout: time.Duration(timeoutSec) * time.Second,
|
timeout: time.Duration(timeoutSec) * time.Second,
|
||||||
validateURL: validateURL,
|
validateURL: validateURL,
|
||||||
maxResponseMs: maxMs,
|
maxResponseMs: maxMs,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,12 +162,13 @@ func (v *Validator) ValidateOne(p storage.Proxy) (bool, time.Duration, string, s
|
|||||||
return false, latency, exitIP, exitLocation
|
return false, latency, exitIP, exitLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤中国大陆出口(香港的countryCode是HK,不是CN)
|
// 过滤屏蔽国家出口(根据配置)
|
||||||
if len(exitLocation) >= 2 {
|
if v.cfg != nil && len(v.cfg.BlockedCountries) > 0 && len(exitLocation) >= 2 {
|
||||||
countryCode := exitLocation[:2]
|
countryCode := exitLocation[:2]
|
||||||
if countryCode == "CN" {
|
for _, blocked := range v.cfg.BlockedCountries {
|
||||||
// 中国大陆出口,直接拒绝
|
if countryCode == blocked {
|
||||||
return false, latency, exitIP, exitLocation
|
return false, latency, exitIP, exitLocation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user