├── README.md
├── build.bat
├── config.json
├── eazyProxy
├── eazyProxy.exe
├── go.mod
├── main.go
├── main_web.go
├── models.go
├── static
├── css
│ └── style.css
└── js
│ ├── dashboard.js
│ └── login.js
├── templates
├── dashboard.html
└── login.html
└── test_proxy.py
/README.md:
--------------------------------------------------------------------------------
1 | # eazyProxy 代理服务器
2 |
3 | 一个功能强大的HTTP/HTTPS代理服务器,具有Web管理界面、多端口管理、代理转发和用户认证功能。
4 |
5 | ## 功能特点
6 |
7 | - **HTTP/HTTPS代理**:支持HTTP和HTTPS协议的代理转发
8 | - **多端口管理**:支持配置多个代理端口,每个端口可以有独立的设置
9 | - **代理转发**:支持将请求转发到另一个代理服务器(中转代理)
10 | - **自动化使用代理**:使用中转代理可以实现让浏览器自动化设置代理时可以使用账号密码的代理
11 | - **自动化时代理切换**:使用中转代理可以实现让浏览器自动化设置代理时不需要关闭浏览器强行切换代理
12 | - **用户认证**:基于用户名和密码的代理认证机制,支持端口级别的用户管理
13 | - **匿名访问**:可选择允许匿名访问,无需用户名和密码
14 | - **Web管理界面**:直观的Web界面,方便管理代理服务器
15 | - **实时监控**:查看代理服务器的运行状态和连接数
16 | - **域名访问统计**:记录和统计访问的域名和请求次数
17 | - **配置持久化**:自动保存配置到文件,重启后自动加载
18 |
19 | ## 系统要求
20 |
21 | - Go 1.16 或更高版本
22 | - 支持Windows、Linux和macOS系统
23 |
24 | ## 安装说明
25 |
26 | ### 从源码安装
27 |
28 | 1. 克隆代码仓库
29 |
30 | ```bash
31 | git clone https://github.com/2833844911/eazyProxy.git
32 | cd eazyProxy
33 | ```
34 |
35 | 2. 安装依赖
36 |
37 | ```bash
38 | go mod download
39 | ```
40 |
41 | 3. 编译项目
42 |
43 | ```bash
44 | go build -o eazyProxy
45 | ```
46 |
47 | ## 快速开始
48 |
49 | 1. 运行程序
50 |
51 | ```bash
52 | # Windows
53 | eazyProxy.exe
54 |
55 | # Linux/macOS
56 | ./eazyProxy
57 | ```
58 |
59 | 2. 访问Web管理界面
60 |
61 | 打开浏览器,访问 `http://localhost:8081`
62 |
63 | 3. 使用默认管理员账户登录
64 |
65 | - 用户名:admin
66 | - 密码:admin
67 |
68 | ## 配置说明
69 |
70 | ### 默认配置
71 |
72 | - 默认代理端口:0.0.0.0:8080
73 | - Web管理界面地址:0.0.0.0:8081
74 | - 管理员用户名:admin
75 | - 管理员密码:admin
76 |
77 | ### 配置文件
78 |
79 | 程序会自动创建并使用 `config.json` 文件保存配置。重启程序时会自动加载此配置文件,并启动所有已启用的代理端口。
80 |
81 | ### 通过Web界面配置
82 |
83 | 登录Web管理界面后,可以配置以下内容:
84 |
85 | - 代理端口管理(添加、编辑、删除、启动、停止)
86 | - 每个端口的基本设置(监听地址、是否启用、是否允许匿名访问)
87 | - 每个端口的用户管理(添加、删除专用用户)
88 | - 每个端口的代理转发设置(启用转发、远程代理地址、认证信息)
89 | - 全局设置(Web管理界面端口、管理员密码)
90 |
91 | ## 多端口管理
92 |
93 | eazyProxy 支持配置多个代理端口,每个端口可以有独立的设置:
94 |
95 | 1. 在Web管理界面的"多端口管理"选项卡中,点击"添加代理端口"
96 | 2. 设置监听地址(格式为 IP:端口,如 0.0.0.0:8888)
97 | 3. 选择是否启用匿名访问
98 | 4. 点击"添加"按钮创建新端口
99 | 5. 在端口列表中,可以启动、停止、编辑或删除端口
100 |
101 | ## 代理转发(中转代理)
102 |
103 | eazyProxy 支持将请求转发到另一个代理服务器,实现代理链:
104 |
105 | 1. 在端口管理界面,点击"编辑"按钮打开端口详情
106 | 2. 切换到"代理转发"选项卡
107 | 3. 勾选"启用代理转发"
108 | 4. 填写远程代理地址(格式为 IP:端口)
109 | 5. 如果远程代理需要认证,填写用户名和密码
110 | 6. 点击"保存转发设置"按钮
111 |
112 | 注意:修改代理转发设置后,如果代理端口正在运行,会自动重启以应用新设置。
113 |
114 | ## 端口专用用户
115 |
116 | 每个代理端口可以配置专用的用户账号:
117 |
118 | 1. 在端口管理界面,点击"编辑"按钮打开端口详情
119 | 2. 切换到"用户管理"选项卡
120 | 3. 填写用户名和密码
121 | 4. 如果需要创建无需认证的用户,勾选"无需认证的用户"
122 | 5. 点击"添加用户"按钮
123 |
124 | ## 使用示例
125 |
126 | ### 配置浏览器使用代理
127 |
128 | 1. 在浏览器的网络设置中,配置HTTP代理为你的服务器地址和端口(如 `127.0.0.1:8080`)
129 | 2. 如果启用了认证,当提示输入代理认证时,输入你创建的用户名和密码
130 |
131 | ### 使用Python请求示例
132 |
133 | ```python
134 | import requests
135 |
136 | proxies = {
137 | 'http': 'http://username:password@127.0.0.1:8080',
138 | 'https': 'http://username:password@127.0.0.1:8080'
139 | }
140 |
141 | response = requests.get('https://example.com', proxies=proxies)
142 | print(response.text)
143 | ```
144 |
145 | ### 使用curl示例
146 |
147 | ```bash
148 | curl -x http://username:password@127.0.0.1:8080 https://example.com
149 | ```
150 |
151 | ## 常见问题
152 |
153 | ### 代理服务器无法启动
154 |
155 | - 检查端口是否被占用
156 | - 确保有足够的权限绑定端口
157 | - 查看日志输出,了解具体错误原因
158 |
159 | ### 代理转发不生效
160 |
161 | - 确认已正确配置远程代理地址
162 | - 检查远程代理是否可访问
163 | - 如果远程代理需要认证,确保用户名和密码正确
164 | - 修改转发设置后,确保代理端口已重启
165 |
166 | ### 无法连接到代理
167 |
168 | - 确认代理服务器已启动
169 | - 检查防火墙设置
170 | - 验证用户名和密码是否正确
171 | - 如果使用端口专用用户,确保使用了正确的端口
172 |
173 | ## 贡献
174 |
175 | 欢迎提交问题和功能请求!如果你想贡献代码,请先开issue讨论你想要改变的内容。
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | set GOOS=linux
2 | set GOARCH=amd64
3 | go build -o eazyProxy .
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "ListenAddr": "0.0.0.0:8080",
3 | "CredStore": {},
4 | "AdminUsername": "admin",
5 | "AdminPassword": "admin",
6 | "WebPort": "8081",
7 | "Status": {
8 | "running": false,
9 | "start_time": "0001-01-01T00:00:00Z",
10 | "connection_count": 0
11 | },
12 | "StatsManager": {
13 | "total_requests": 2,
14 | "domain_stats": {
15 | "ip.me": {
16 | "domain": "ip.me",
17 | "count": 2,
18 | "last_visit": "2025-03-21T23:15:03.6740779+08:00"
19 | }
20 | }
21 | },
22 | "UseForwardProxy": false,
23 | "AllowAnonymous": false,
24 | "RemoteProxyAddr": "127.0.0.1:7890",
25 | "RemoteProxyUser": "dsdddd",
26 | "RemoteProxyPass": "dsdddd",
27 | "ProxyPorts": {
28 | "default": {
29 | "id": "default",
30 | "listen_addr": "0.0.0.0:8080",
31 | "enabled": true,
32 | "status": {
33 | "running": true,
34 | "start_time": "2025-03-25T19:56:20.5929378+08:00",
35 | "connection_count": 9
36 | },
37 | "allow_anonymous": true,
38 | "use_forward_proxy": false,
39 | "remote_proxy_addr": "",
40 | "remote_proxy_user": "",
41 | "remote_proxy_pass": ""
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/eazyProxy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2833844911/eazyProxy/98c789314df3ac59525d2c8adb6b6a7d9cb24982/eazyProxy
--------------------------------------------------------------------------------
/eazyProxy.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2833844911/eazyProxy/98c789314df3ac59525d2c8adb6b6a7d9cb24982/eazyProxy.exe
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module proxy
2 |
3 | go 1.18
4 |
5 | require github.com/gin-gonic/gin v1.10.0
6 |
7 | require (
8 | github.com/bytedance/sonic v1.11.6 // indirect
9 | github.com/bytedance/sonic/loader v0.1.1 // indirect
10 | github.com/cloudwego/base64x v0.1.4 // indirect
11 | github.com/cloudwego/iasm v0.2.0 // indirect
12 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect
13 | github.com/gin-contrib/sse v0.1.0 // indirect
14 | github.com/go-playground/locales v0.14.1 // indirect
15 | github.com/go-playground/universal-translator v0.18.1 // indirect
16 | github.com/go-playground/validator/v10 v10.20.0 // indirect
17 | github.com/goccy/go-json v0.10.2 // indirect
18 | github.com/json-iterator/go v1.1.12 // indirect
19 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
20 | github.com/leodido/go-urn v1.4.0 // indirect
21 | github.com/mattn/go-isatty v0.0.20 // indirect
22 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
23 | github.com/modern-go/reflect2 v1.0.2 // indirect
24 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect
25 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
26 | github.com/ugorji/go/codec v1.2.12 // indirect
27 | golang.org/x/arch v0.8.0 // indirect
28 | golang.org/x/crypto v0.23.0 // indirect
29 | golang.org/x/net v0.25.0 // indirect
30 | golang.org/x/sys v0.20.0 // indirect
31 | golang.org/x/text v0.15.0 // indirect
32 | google.golang.org/protobuf v1.34.1 // indirect
33 | gopkg.in/yaml.v3 v3.0.1 // indirect
34 | )
35 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/base64"
6 | "fmt"
7 | "io"
8 | "log"
9 | "net"
10 | "net/http"
11 | "strings"
12 | "sync"
13 | "time"
14 | )
15 |
16 | // 活动连接管理在main_web.go中声明
17 | // 注册连接
18 | func registerConnection(conn net.Conn) {
19 | activeConnectionsMu.Lock()
20 | defer activeConnectionsMu.Unlock()
21 | activeConnections[conn] = true
22 | }
23 |
24 | // 注销连接
25 | func unregisterConnection(conn net.Conn) {
26 | activeConnectionsMu.Lock()
27 | defer activeConnectionsMu.Unlock()
28 | delete(activeConnections, conn)
29 | }
30 |
31 | // 代理服务器配置
32 | type ProxyConfig struct {
33 | ListenAddr string
34 | CredStore *CredentialStore
35 | }
36 |
37 | // 代理服务器
38 | type ProxyServer struct {
39 | config interface{}
40 | portID string // 添加端口ID字段
41 | }
42 |
43 | // 创建新的代理服务器
44 | func NewProxyServer(config interface{}, portID string) *ProxyServer {
45 | return &ProxyServer{
46 | config: config,
47 | portID: portID,
48 | }
49 | }
50 |
51 | // 代理服务器监听器映射,key为端口ID
52 | var proxyListeners = make(map[string]net.Listener)
53 | var listenerMutex sync.Mutex
54 |
55 | // 启动代理服务器
56 | func (ps *ProxyServer) Start() error {
57 | // 获取监听地址
58 | var listenAddr string
59 | var allowAnonymous bool
60 |
61 | if config, ok := ps.config.(ProxyConfig); ok {
62 | listenAddr = config.ListenAddr
63 | } else if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
64 | // 如果是多端口配置,使用对应端口的配置
65 | if ps.portID != "" {
66 | portConfig := extConfig.GetProxyPort(ps.portID)
67 | if portConfig != nil {
68 | listenAddr = portConfig.ListenAddr
69 | allowAnonymous = portConfig.AllowAnonymous
70 | } else {
71 | return fmt.Errorf("无效的端口ID: %s", ps.portID)
72 | }
73 | } else {
74 | // 兼容旧版本
75 | listenAddr = extConfig.ListenAddr
76 | allowAnonymous = extConfig.AllowAnonymous
77 | }
78 | } else {
79 | return fmt.Errorf("无效的配置类型")
80 | }
81 |
82 | // 加锁确保线程安全
83 | listenerMutex.Lock()
84 |
85 | // 创建监听器
86 | listener, err := net.Listen("tcp", listenAddr)
87 | if err != nil {
88 | listenerMutex.Unlock()
89 | return fmt.Errorf("无法监听地址 %s: %v", listenAddr, err)
90 | }
91 |
92 | // 保存监听器的引用
93 | proxyListeners[ps.portID] = listener
94 | listenerMutex.Unlock()
95 |
96 | log.Printf("HTTPS代理服务器已启动,监听地址: %s (端口ID: %s)\n", listenAddr, ps.portID)
97 |
98 | // 更新状态
99 | if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
100 | if ps.portID != "" {
101 | portConfig := extConfig.GetProxyPort(ps.portID)
102 | if portConfig != nil {
103 | log.Printf("端口 %s 配置: 匿名访问=%v, 代理转发=%v, 远程代理=%s",
104 | ps.portID, portConfig.AllowAnonymous, portConfig.UseForwardProxy, portConfig.RemoteProxyAddr)
105 |
106 | portConfig.Status.mutex.Lock()
107 | portConfig.Status.Running = true
108 | portConfig.Status.StartTime = time.Now()
109 | portConfig.Status.ConnectionCount = 0
110 | portConfig.Status.mutex.Unlock()
111 | }
112 | } else {
113 | // 兼容旧版本
114 | log.Printf("全局配置: 匿名访问=%v, 代理转发=%v, 远程代理=%s",
115 | extConfig.AllowAnonymous, extConfig.UseForwardProxy, extConfig.RemoteProxyAddr)
116 |
117 | extConfig.Status.mutex.Lock()
118 | extConfig.Status.Running = true
119 | extConfig.Status.StartTime = time.Now()
120 | extConfig.Status.ConnectionCount = 0
121 | extConfig.Status.mutex.Unlock()
122 | }
123 | }
124 |
125 | // 监听连接请求
126 | for {
127 | client, err := listener.Accept()
128 | if err != nil {
129 | // 检查是否是因为监听器被关闭导致的错误
130 | if strings.Contains(err.Error(), "use of closed network connection") {
131 | log.Printf("监听器已关闭,代理服务器停止 (端口ID: %s)\n", ps.portID)
132 | break
133 | }
134 | log.Printf("接受连接失败: %v (端口ID: %s)\n", err, ps.portID)
135 | continue
136 | }
137 |
138 | go ps.handleConnection(client, allowAnonymous)
139 | }
140 |
141 | return nil
142 | }
143 |
144 | // 处理客户端连接
145 | func (ps *ProxyServer) handleConnection(client net.Conn, allowAnonymous bool) {
146 | // 注册连接
147 | registerConnection(client)
148 |
149 | // 确保连接结束时注销
150 | defer func() {
151 | unregisterConnection(client)
152 | client.Close()
153 | }()
154 |
155 | // 更新连接计数
156 | if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
157 | if ps.portID != "" {
158 | portConfig := extConfig.GetProxyPort(ps.portID)
159 | if portConfig != nil {
160 | portConfig.Status.mutex.Lock()
161 | portConfig.Status.ConnectionCount++
162 | portConfig.Status.mutex.Unlock()
163 | }
164 | } else {
165 | extConfig.Status.mutex.Lock()
166 | extConfig.Status.ConnectionCount++
167 | extConfig.Status.mutex.Unlock()
168 | }
169 | }
170 |
171 | // 创建缓冲读取器
172 | reader := bufio.NewReader(client)
173 |
174 | // 读取HTTP请求
175 | req, err := http.ReadRequest(reader)
176 | if err != nil {
177 | if err != io.EOF {
178 | log.Printf("读取HTTP请求失败: %v", err)
179 | }
180 | return
181 | }
182 |
183 | // 处理HTTP请求
184 | ps.handleHTTP(client, req, reader)
185 | }
186 |
187 | // 处理HTTP请求
188 | func (ps *ProxyServer) handleHTTP(client net.Conn, req *http.Request, reader *bufio.Reader) {
189 | // 确保请求有完整的URL
190 | if req.URL.Scheme == "" {
191 | req.URL.Scheme = "http"
192 | }
193 | if req.URL.Host == "" {
194 | req.URL.Host = req.Host
195 | }
196 |
197 | // 检查是否需要认证
198 | var allowAnonymous bool
199 | if _, ok := ps.config.(ProxyConfig); ok {
200 | // 使用基本配置
201 | } else if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
202 | // 如果是多端口配置,使用对应端口的配置
203 | if ps.portID != "" {
204 | portConfig := extConfig.GetProxyPort(ps.portID)
205 | if portConfig != nil {
206 | allowAnonymous = portConfig.AllowAnonymous
207 | }
208 | } else {
209 | // 兼容旧版本
210 | allowAnonymous = extConfig.AllowAnonymous
211 | }
212 | }
213 |
214 | // 如果不允许匿名访问,则需要认证
215 | if !allowAnonymous && !ps.authenticate(req) {
216 | // 发送认证失败响应
217 | authRequired := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
218 | "Proxy-Authenticate: Basic realm=\"Proxy\"\r\n" +
219 | "Content-Length: 0\r\n\r\n"
220 | client.Write([]byte(authRequired))
221 | return
222 | }
223 | // 移除Proxy-Authorization头,避免将凭证发送到目标服务器
224 | req.Header.Del("Proxy-Authorization")
225 | req.Header.Del("Proxy-Connection")
226 | // req.Header.Set("Connection", "close")
227 | // 记录域名访问统计
228 | if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
229 | domain := req.Host
230 | if strings.Contains(domain, ":") {
231 | domain = strings.Split(domain, ":")[0]
232 | }
233 | extConfig.StatsManager.RecordRequest(domain)
234 | }
235 |
236 | // 检查是否启用了代理转发
237 | var useForwardProxy bool
238 | var remoteProxyAddr string
239 |
240 | if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
241 | if ps.portID != "" {
242 | // 检查端口级别的转发设置(每次都重新获取,确保使用最新的设置)
243 | portConfig := extConfig.GetProxyPort(ps.portID)
244 | if portConfig != nil && portConfig.UseForwardProxy {
245 | useForwardProxy = true
246 | remoteProxyAddr = portConfig.RemoteProxyAddr
247 |
248 | log.Printf("使用端口 %s 的转发设置: %s (请求: %s)", ps.portID, remoteProxyAddr, req.Host)
249 | }
250 | } else if extConfig.UseForwardProxy {
251 | // 兼容旧版本
252 | useForwardProxy = true
253 | remoteProxyAddr = extConfig.RemoteProxyAddr
254 |
255 | log.Printf("使用全局转发设置: %s (请求: %s)", remoteProxyAddr, req.Host)
256 | }
257 | }
258 |
259 | if useForwardProxy && remoteProxyAddr != "" {
260 | // 使用代理转发处理请求
261 | ps.handleProxyForwarding(client, req, reader)
262 | return
263 | }
264 |
265 | // 处理CONNECT方法(HTTPS代理)
266 | if req.Method == http.MethodConnect {
267 | ps.handleHTTPS(client, req)
268 | return
269 | }
270 |
271 | // 连接到目标服务器
272 | log.Printf("处理HTTP请求: %s", req.URL.Host)
273 | var target net.Conn
274 | var err error
275 | if strings.Contains(req.URL.Host, ":") {
276 | target, err = net.Dial("tcp", strings.TrimSpace(req.URL.Host))
277 | } else {
278 | target, err = net.Dial("tcp", strings.TrimSpace(req.URL.Host)+":80")
279 | }
280 | if err != nil {
281 | log.Printf("无法连接到目标服务器 %s: %v\n", req.URL.Host, err)
282 | return
283 | }
284 | defer target.Close()
285 |
286 | // 将请求发送到目标服务器
287 | err = req.Write(target)
288 | if err != nil {
289 | log.Printf("发送请求到目标服务器失败: %v\n", err)
290 | return
291 | }
292 |
293 | // 将目标服务器的响应发送回客户端
294 | _, err = io.Copy(client, target)
295 | if err != nil && err != io.EOF {
296 | log.Printf("转发响应失败: %v\n", err)
297 | }
298 | }
299 |
300 | // 验证用户认证
301 | func (ps *ProxyServer) authenticate(req *http.Request) bool {
302 | // 获取Proxy-Authorization头
303 | authHeader := req.Header.Get("Proxy-Authorization")
304 | if authHeader == "" {
305 | return false
306 | }
307 |
308 | // 解析认证信息
309 | parts := strings.SplitN(authHeader, " ", 2)
310 | if len(parts) != 2 || parts[0] != "Basic" {
311 | return false
312 | }
313 |
314 | // 解码Base64编码的凭证
315 | credentials, err := base64.StdEncoding.DecodeString(parts[1])
316 | if err != nil {
317 | return false
318 | }
319 |
320 | // 分离用户名和密码
321 | pair := strings.SplitN(string(credentials), ":", 2)
322 | if len(pair) != 2 {
323 | return false
324 | }
325 |
326 | username, password := pair[0], pair[1]
327 | fmt.Println(username, password)
328 |
329 | // 首先检查端口专用用户
330 | if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok && ps.portID != "" {
331 | portConfig := extConfig.GetProxyPort(ps.portID)
332 | if portConfig != nil && portConfig.ValidateUser(username, password) {
333 | return true
334 | }
335 | }
336 |
337 | // 然后检查全局用户
338 | var credStore *CredentialStore
339 | if config, ok := ps.config.(ProxyConfig); ok {
340 | credStore = config.CredStore
341 | } else if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
342 | credStore = extConfig.CredStore
343 | } else {
344 | return false
345 | }
346 |
347 | return credStore.Validate(username, password)
348 | }
349 |
350 | // 发送认证要求响应
351 | func (ps *ProxyServer) sendAuthRequired(client net.Conn) {
352 | response := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
353 | "Proxy-Authenticate: Basic realm=\"Proxy\"\r\n" +
354 | "Content-Length: 0\r\n\r\n"
355 | client.Write([]byte(response))
356 | }
357 |
358 | // 处理HTTPS请求
359 | func (ps *ProxyServer) handleHTTPS(client net.Conn, req *http.Request) {
360 | log.Printf("处理HTTPS请求: %s", req.Host)
361 | // 连接到目标服务器
362 | target, err := net.Dial("tcp", strings.TrimSpace(req.Host))
363 | if err != nil {
364 | log.Printf("无法连接到目标服务器 %s: %v\n", req.Host, err)
365 | return
366 | }
367 | defer target.Close()
368 |
369 | // 发送200 Connection Established响应
370 | response := "HTTP/1.1 200 Connection Established\r\n\r\n"
371 | _, err = client.Write([]byte(response))
372 | if err != nil {
373 | log.Printf("发送响应失败: %v\n", err)
374 | return
375 | }
376 |
377 | // 双向转发数据
378 | ps.tunnel(client, target)
379 | }
380 |
381 | // 在客户端和目标服务器之间建立双向隧道
382 | func (ps *ProxyServer) tunnel(client, target net.Conn) {
383 | var wg sync.WaitGroup
384 | wg.Add(2)
385 |
386 | // 客户端 -> 目标服务器
387 | go func() {
388 | defer wg.Done()
389 | _, err := io.Copy(target, client)
390 | if err != nil {
391 | if strings.Contains(err.Error(), "connection reset by peer") {
392 | log.Printf("客户端连接已重置: %v", err)
393 | } else if err != io.EOF {
394 | log.Printf("转发客户端数据失败: %v", err)
395 | }
396 | }
397 | target.(*net.TCPConn).CloseWrite()
398 | }()
399 |
400 | // 目标服务器 -> 客户端
401 | go func() {
402 | defer wg.Done()
403 | _, err := io.Copy(client, target)
404 | if err != nil {
405 | if strings.Contains(err.Error(), "connection reset by peer") {
406 | log.Printf("目标服务器连接已重置: %v", err)
407 | } else if err != io.EOF {
408 | log.Printf("转发服务器数据失败: %v", err)
409 | }
410 | }
411 | client.(*net.TCPConn).CloseWrite()
412 | }()
413 |
414 | wg.Wait()
415 | }
416 |
417 | // 处理代理转发
418 | func (ps *ProxyServer) handleProxyForwarding(client net.Conn, req *http.Request, reader *bufio.Reader) {
419 | var remoteProxyAddr, remoteProxyUsername, remoteProxyPassword string
420 | var useForwardProxy bool
421 |
422 | // 获取代理转发设置(每次都从全局配置中读取,确保使用最新的设置)
423 | if extConfig, ok := ps.config.(*ExtendedProxyConfig); ok {
424 | if ps.portID != "" {
425 | // 每次都重新获取端口配置,确保使用最新的设置
426 | portConfig := extConfig.GetProxyPort(ps.portID)
427 | if portConfig != nil && portConfig.UseForwardProxy {
428 | useForwardProxy = true
429 | remoteProxyAddr = portConfig.RemoteProxyAddr
430 | remoteProxyUsername = portConfig.RemoteProxyUser
431 | remoteProxyPassword = portConfig.RemoteProxyPass
432 |
433 | log.Printf("使用端口 %s 的转发设置: %s (请求: %s)", ps.portID, remoteProxyAddr, req.Host)
434 | }
435 | } else if extConfig.UseForwardProxy {
436 | // 兼容旧版本
437 | useForwardProxy = true
438 | remoteProxyAddr = extConfig.RemoteProxyAddr
439 | remoteProxyUsername = extConfig.RemoteProxyUser
440 | remoteProxyPassword = extConfig.RemoteProxyPass
441 |
442 | log.Printf("使用全局转发设置: %s (请求: %s)", remoteProxyAddr, req.Host)
443 | }
444 | }
445 |
446 | // 如果没有启用代理转发,直接返回
447 | if !useForwardProxy || remoteProxyAddr == "" {
448 | log.Printf("代理转发未启用或远程代理地址为空 (请求: %s)", req.Host)
449 | return
450 | }
451 |
452 | log.Printf("转发请求到远程代理: %s (请求: %s)", remoteProxyAddr, req.Host)
453 |
454 | // 连接到远程代理服务器
455 | remoteProxy, err := net.Dial("tcp", remoteProxyAddr)
456 | if err != nil {
457 | log.Printf("无法连接到远程代理服务器 %s: %v\n", remoteProxyAddr, err)
458 | return
459 | }
460 | defer remoteProxy.Close()
461 |
462 | // 构建认证头
463 | auth := base64.StdEncoding.EncodeToString([]byte(remoteProxyUsername + ":" + remoteProxyPassword))
464 | req.Header.Set("Proxy-Authorization", "Basic "+auth)
465 |
466 | // 对于CONNECT请求(HTTPS),直接发送到远程代理
467 | if req.Method == http.MethodConnect {
468 | // 发送CONNECT请求到远程代理
469 | connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Authorization: Basic %s\r\nProxy-Connection: Keep-Alive\r\n\r\n",
470 | req.Host, req.Host, auth)
471 | _, err = remoteProxy.Write([]byte(connectReq))
472 | if err != nil {
473 | log.Printf("发送CONNECT请求到远程代理失败: %v\n", err)
474 | return
475 | }
476 |
477 | // 读取远程代理的响应
478 | proxyReader := bufio.NewReader(remoteProxy)
479 | resp, err := http.ReadResponse(proxyReader, req)
480 | if err != nil {
481 | log.Printf("读取远程代理响应失败: %v\n", err)
482 | return
483 | }
484 |
485 | // 检查响应状态
486 | if resp.StatusCode != http.StatusOK {
487 | log.Printf("远程代理响应非200状态: %s\n", resp.Status)
488 | resp.Write(client)
489 | return
490 | }
491 |
492 | // 发送成功连接响应给客户端
493 | successResp := "HTTP/1.1 200 Connection Established\r\n\r\n"
494 | _, err = client.Write([]byte(successResp))
495 | if err != nil {
496 | log.Printf("发送响应给客户端失败: %v\n", err)
497 | return
498 | }
499 |
500 | // 建立隧道
501 | ps.tunnel(client, remoteProxy)
502 | return
503 | }
504 |
505 | // 对于HTTP请求,转发到远程代理
506 | err = req.Write(remoteProxy)
507 | if err != nil {
508 | log.Printf("发送HTTP请求到远程代理失败: %v\n", err)
509 | return
510 | }
511 |
512 | // 将远程代理的响应转发给客户端
513 | _, err = io.Copy(client, remoteProxy)
514 | if err != nil && err != io.EOF {
515 | log.Printf("转发远程代理响应失败: %v\n", err)
516 | }
517 | }
518 |
519 | // 此文件不再包含main函数,main函数已移至main_web.go
520 | // 这里只保留代理服务器的核心功能实现
521 |
--------------------------------------------------------------------------------
/main_web.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base64"
6 | "encoding/json"
7 | "fmt"
8 | "log"
9 | "net"
10 | "net/http"
11 | "os"
12 | "strconv"
13 | "strings"
14 | "sync"
15 | "time"
16 |
17 | "github.com/gin-gonic/gin"
18 | )
19 |
20 | // 活动连接管理 - 连接管理在这里也声明
21 | var (
22 | activeConnections = make(map[net.Conn]bool)
23 | activeConnectionsMu sync.Mutex
24 | )
25 |
26 | // 关闭所有活动连接
27 | func closeAllConnections() {
28 | activeConnectionsMu.Lock()
29 | defer activeConnectionsMu.Unlock()
30 |
31 | log.Printf("正在关闭所有活动连接 (%d 个)...", len(activeConnections))
32 | for conn := range activeConnections {
33 | conn.Close()
34 | }
35 | activeConnections = make(map[net.Conn]bool)
36 | log.Printf("所有活动连接已关闭")
37 | }
38 |
39 | // JWT令牌密钥
40 | var jwtSecret []byte
41 |
42 | // 全局配置
43 | var globalConfig *ExtendedProxyConfig
44 |
45 | // 代理服务器实例
46 | var proxyServer *ProxyServer
47 |
48 | // 代理服务器控制
49 | var proxyMutex sync.Mutex
50 | var proxyRunning bool
51 | var proxyStopChan chan struct{}
52 |
53 | // 多端口代理服务器实例映射,key为端口ID
54 | var proxyServers = make(map[string]*ProxyServer)
55 | var proxyRunningStatus = make(map[string]bool)
56 |
57 | // 初始化函数
58 | func init() {
59 | // 生成随机JWT密钥
60 | jwtSecret = make([]byte, 32)
61 | _, err := rand.Read(jwtSecret)
62 | if err != nil {
63 | log.Fatalf("无法生成JWT密钥: %v\n", err)
64 | }
65 |
66 | // 初始化全局配置
67 | globalConfig = NewExtendedProxyConfig()
68 |
69 | // 尝试从配置文件加载配置
70 | loadConfigFromFile()
71 |
72 | // 初始化代理控制
73 | proxyRunning = false
74 | proxyStopChan = make(chan struct{})
75 | }
76 |
77 | // 从配置文件加载配置
78 | func loadConfigFromFile() {
79 | // 检查配置文件是否存在
80 | if _, err := os.Stat("config.json"); os.IsNotExist(err) {
81 | log.Println("配置文件不存在,使用默认配置")
82 | return
83 | }
84 |
85 | // 读取配置文件
86 | configData, err := os.ReadFile("config.json")
87 | if err != nil {
88 | log.Printf("读取配置文件失败: %v,使用默认配置", err)
89 | return
90 | }
91 |
92 | // 解析配置
93 | var config ExtendedProxyConfig
94 | err = json.Unmarshal(configData, &config)
95 | if err != nil {
96 | log.Printf("解析配置文件失败: %v,使用默认配置", err)
97 | return
98 | }
99 |
100 | // 更新全局配置
101 | globalConfig = &config
102 |
103 | // 确保所有端口的状态都初始化为未运行
104 | for _, port := range globalConfig.ProxyPorts {
105 | if port.Status == nil {
106 | port.Status = &ProxyStatus{
107 | Running: false,
108 | StartTime: time.Time{},
109 | ConnectionCount: 0,
110 | mutex: sync.RWMutex{},
111 | }
112 | } else {
113 | port.Status.Running = false
114 | port.Status.ConnectionCount = 0
115 | }
116 | }
117 |
118 | log.Println("已从配置文件加载配置")
119 | }
120 |
121 | // 主函数
122 | func main() {
123 | // 设置Gin模式
124 | gin.SetMode(gin.ReleaseMode)
125 |
126 | // 创建Gin路由
127 | r := gin.Default()
128 |
129 | // 提供静态文件
130 | r.Static("/static", "./static")
131 |
132 | // 加载HTML模板
133 | r.LoadHTMLGlob("templates/*")
134 |
135 | // 路由组
136 | api := r.Group("/api")
137 | {
138 | // 登录API
139 | api.POST("/login", handleLogin)
140 |
141 | // 需要认证的API
142 | auth := api.Group("/")
143 | auth.Use(authMiddleware())
144 | {
145 | // 代理服务器控制
146 | auth.GET("/proxy/status", handleProxyStatus)
147 | auth.POST("/proxy/start", handleProxyStart)
148 | auth.POST("/proxy/stop", handleProxyStop)
149 |
150 | // 用户管理
151 | auth.GET("/users", handleGetUsers)
152 | auth.POST("/users", handleAddUser)
153 | auth.DELETE("/users/:username", handleDeleteUser)
154 |
155 | // 设置管理
156 | auth.GET("/settings", handleGetSettings)
157 | auth.POST("/settings", handleUpdateSettings)
158 |
159 | // 统计信息
160 | auth.GET("/stats", handleGetStats)
161 | auth.POST("/stats/reset", handleResetStats)
162 |
163 | // 多端口管理API
164 | auth.GET("/proxy/ports", handleGetProxyPorts)
165 | auth.POST("/proxy/ports", handleAddProxyPort)
166 | auth.GET("/proxy/ports/:id", handleGetProxyPort)
167 | auth.PUT("/proxy/ports/:id", handleUpdateProxyPort)
168 | auth.DELETE("/proxy/ports/:id", handleDeleteProxyPort)
169 | auth.POST("/proxy/ports/:id/start", handleStartProxyPort)
170 | auth.POST("/proxy/ports/:id/stop", handleStopProxyPort)
171 |
172 | // 端口用户管理API
173 | auth.GET("/proxy/ports/:id/users", handleGetPortUsers)
174 | auth.POST("/proxy/ports/:id/users", handleAddPortUser)
175 | auth.DELETE("/proxy/ports/:id/users/:username", handleDeletePortUser)
176 |
177 | // 端口代理转发设置API
178 | auth.GET("/proxy/ports/:id/forward", handleGetPortForward)
179 | auth.POST("/proxy/ports/:id/forward", handleUpdatePortForward)
180 | }
181 | }
182 |
183 | // 页面路由
184 | r.GET("/", func(c *gin.Context) {
185 | c.HTML(http.StatusOK, "login.html", nil)
186 | })
187 |
188 | r.GET("/dashboard", func(c *gin.Context) {
189 | c.HTML(http.StatusOK, "dashboard.html", nil)
190 | })
191 |
192 | // 启动Web服务器
193 | log.Printf("启动Web管理界面,监听地址: 0.0.0.0:%s\n", globalConfig.WebPort)
194 | go r.Run(":" + globalConfig.WebPort)
195 |
196 | // 从配置文件启动所有已启用的代理端口
197 | startEnabledProxyPorts()
198 |
199 | // 等待信号
200 | select {}
201 | }
202 |
203 | // 启动所有已启用的代理端口
204 | func startEnabledProxyPorts() {
205 | // 如果没有端口配置,启动默认端口
206 | if len(globalConfig.ProxyPorts) == 0 {
207 | log.Println("没有端口配置,启动默认代理服务器")
208 | startProxyServer()
209 | startProxyPort("default")
210 | return
211 | }
212 |
213 | // 启动所有已启用的端口
214 | for id, port := range globalConfig.ProxyPorts {
215 | if port.Enabled {
216 | log.Printf("从配置启动代理端口: %s (%s)", id, port.ListenAddr)
217 | go startProxyPort(id)
218 | } else {
219 | log.Printf("代理端口未启用,跳过: %s (%s)", id, port.ListenAddr)
220 | }
221 | }
222 | }
223 |
224 | // 认证中间件
225 | func authMiddleware() gin.HandlerFunc {
226 | return func(c *gin.Context) {
227 | // 获取Authorization头
228 | auth := c.GetHeader("Authorization")
229 | if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
230 | c.JSON(http.StatusUnauthorized, gin.H{
231 | "success": false,
232 | "message": "未授权访问",
233 | })
234 | c.Abort()
235 | return
236 | }
237 |
238 | // 提取令牌
239 | token := auth[7:]
240 | //if token == "cbbiyhh" {
241 | // c.Next()
242 | // return
243 | //}
244 | // 验证令牌
245 | if !validateToken(token) {
246 | c.JSON(http.StatusUnauthorized, gin.H{
247 | "success": false,
248 | "message": "无效的令牌",
249 | })
250 | c.Abort()
251 | return
252 | }
253 |
254 | c.Next()
255 | }
256 | }
257 |
258 | // 验证令牌
259 | func validateToken(token string) bool {
260 | // 简单实现,实际应用中应该使用JWT库
261 | parts := strings.Split(token, ".")
262 | if len(parts) != 3 {
263 | return false
264 | }
265 |
266 | // 解码payload
267 | payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1])
268 | if err != nil {
269 | return false
270 | }
271 |
272 | // 解析payload
273 | var payload struct {
274 | Username string `json:"username"`
275 | Exp time.Time `json:"exp"`
276 | }
277 |
278 | err = json.Unmarshal(payloadBytes, &payload)
279 | if err != nil {
280 | return false
281 | }
282 |
283 | // 检查过期时间
284 | if time.Now().After(payload.Exp) {
285 | return false
286 | }
287 |
288 | // 检查用户名
289 | return payload.Username == globalConfig.AdminUsername
290 | }
291 |
292 | // 生成令牌
293 | func generateToken(username string) string {
294 | // 简单实现,实际应用中应该使用JWT库
295 | header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
296 |
297 | // 创建payload
298 | payload := struct {
299 | Username string `json:"username"`
300 | Exp time.Time `json:"exp"`
301 | }{
302 | Username: username,
303 | Exp: time.Now().Add(24 * time.Hour),
304 | }
305 |
306 | payloadBytes, _ := json.Marshal(payload)
307 | payloadBase64 := base64.RawURLEncoding.EncodeToString(payloadBytes)
308 |
309 | // 创建签名
310 | signature := base64.RawURLEncoding.EncodeToString([]byte("signature"))
311 |
312 | return header + "." + payloadBase64 + "." + signature
313 | }
314 |
315 | // 处理登录请求
316 | func handleLogin(c *gin.Context) {
317 | // 解析请求
318 | var req struct {
319 | Username string `json:"username"`
320 | Password string `json:"password"`
321 | }
322 |
323 | if err := c.BindJSON(&req); err != nil {
324 | c.JSON(http.StatusBadRequest, gin.H{
325 | "success": false,
326 | "message": "无效的请求",
327 | })
328 | return
329 | }
330 |
331 | // 验证管理员凭证
332 | if req.Username == globalConfig.AdminUsername && req.Password == globalConfig.AdminPassword {
333 | // 生成令牌
334 | token := generateToken(req.Username)
335 |
336 | c.JSON(http.StatusOK, gin.H{
337 | "success": true,
338 | "token": token,
339 | })
340 | } else {
341 | c.JSON(http.StatusUnauthorized, gin.H{
342 | "success": false,
343 | "message": "用户名或密码错误",
344 | })
345 | }
346 | }
347 |
348 | // 处理获取代理状态
349 | func handleProxyStatus(c *gin.Context) {
350 | globalConfig.Status.mutex.RLock()
351 | defer globalConfig.Status.mutex.RUnlock()
352 |
353 | c.JSON(http.StatusOK, gin.H{
354 | "success": true,
355 | "data": gin.H{
356 | "running": globalConfig.Status.Running,
357 | "start_time": globalConfig.Status.StartTime,
358 | "connection_count": globalConfig.Status.ConnectionCount,
359 | "address": globalConfig.ProxyConfig.ListenAddr,
360 | },
361 | })
362 | }
363 |
364 | // 处理启动代理服务器
365 | func handleProxyStart(c *gin.Context) {
366 | proxyMutex.Lock()
367 | defer proxyMutex.Unlock()
368 |
369 | if proxyRunning {
370 | c.JSON(http.StatusBadRequest, gin.H{
371 | "success": false,
372 | "message": "代理服务器已经在运行",
373 | })
374 | return
375 | }
376 |
377 | // 启动代理服务器
378 | go startProxyServer()
379 |
380 | c.JSON(http.StatusOK, gin.H{
381 | "success": true,
382 | "message": "代理服务器已启动",
383 | })
384 | }
385 |
386 | // 处理停止代理服务器
387 | func handleProxyStop(c *gin.Context) {
388 | proxyMutex.Lock()
389 | defer proxyMutex.Unlock()
390 |
391 | if !proxyRunning {
392 | c.JSON(http.StatusBadRequest, gin.H{
393 | "success": false,
394 | "message": "代理服务器未运行",
395 | })
396 | return
397 | }
398 |
399 | // 停止代理服务器
400 | stopProxyServer()
401 |
402 | c.JSON(http.StatusOK, gin.H{
403 | "success": true,
404 | "message": "代理服务器已停止",
405 | })
406 | }
407 |
408 | // 处理获取用户列表
409 | func handleGetUsers(c *gin.Context) {
410 | globalConfig.CredStore.mutex.RLock()
411 | defer globalConfig.CredStore.mutex.RUnlock()
412 |
413 | users := make([]gin.H, 0, len(globalConfig.CredStore.credentials))
414 | for username := range globalConfig.CredStore.credentials {
415 | users = append(users, gin.H{
416 | "username": username,
417 | "no_auth": globalConfig.CredStore.IsNoAuthUser(username),
418 | })
419 | }
420 |
421 | c.JSON(http.StatusOK, gin.H{
422 | "success": true,
423 | "data": users,
424 | })
425 | }
426 |
427 | // 处理添加用户
428 | func handleAddUser(c *gin.Context) {
429 | // 解析请求
430 | var req struct {
431 | Username string `json:"username"`
432 | Password string `json:"password"`
433 | NoAuth bool `json:"no_auth"`
434 | }
435 |
436 | if err := c.BindJSON(&req); err != nil {
437 | c.JSON(http.StatusBadRequest, gin.H{
438 | "success": false,
439 | "message": "无效的请求",
440 | })
441 | return
442 | }
443 |
444 | // 验证用户名
445 | if req.Username == "" {
446 | c.JSON(http.StatusBadRequest, gin.H{
447 | "success": false,
448 | "message": "用户名不能为空",
449 | })
450 | return
451 | }
452 |
453 | // 如果不是无认证用户,则验证密码
454 | if !req.NoAuth && req.Password == "" {
455 | c.JSON(http.StatusBadRequest, gin.H{
456 | "success": false,
457 | "message": "密码不能为空",
458 | })
459 | return
460 | }
461 |
462 | // 添加用户
463 | globalConfig.CredStore.AddCredential(req.Username, req.Password)
464 |
465 | // 如果是无认证用户,记录到无认证用户列表
466 | if req.NoAuth {
467 | globalConfig.CredStore.AddNoAuthUser(req.Username)
468 | }
469 |
470 | c.JSON(http.StatusOK, gin.H{
471 | "success": true,
472 | "message": "用户已添加",
473 | })
474 | }
475 |
476 | // 处理删除用户
477 | func handleDeleteUser(c *gin.Context) {
478 | username := c.Param("username")
479 |
480 | globalConfig.CredStore.mutex.Lock()
481 | defer globalConfig.CredStore.mutex.Unlock()
482 |
483 | // 检查用户是否存在
484 | if _, exists := globalConfig.CredStore.credentials[username]; !exists {
485 | c.JSON(http.StatusNotFound, gin.H{
486 | "success": false,
487 | "message": "用户不存在",
488 | })
489 | return
490 | }
491 |
492 | // 删除用户
493 | delete(globalConfig.CredStore.credentials, username)
494 |
495 | c.JSON(http.StatusOK, gin.H{
496 | "success": true,
497 | "message": "用户已删除",
498 | })
499 | }
500 |
501 | // 处理获取设置
502 | func handleGetSettings(c *gin.Context) {
503 | c.JSON(http.StatusOK, gin.H{
504 | "success": true,
505 | "data": gin.H{
506 | "proxy_port": strings.Split(globalConfig.ListenAddr, ":")[1],
507 | "web_port": globalConfig.WebPort,
508 | "admin_username": globalConfig.AdminUsername,
509 | "use_forward_proxy": globalConfig.UseForwardProxy,
510 | "allow_anonymous": globalConfig.AllowAnonymous,
511 | "remote_proxy_addr": globalConfig.RemoteProxyAddr,
512 | "remote_proxy_user": globalConfig.RemoteProxyUser,
513 | "remote_proxy_pass": globalConfig.RemoteProxyPass,
514 | },
515 | })
516 | }
517 |
518 | // 处理更新设置
519 | func handleUpdateSettings(c *gin.Context) {
520 | // 解析请求
521 | var req struct {
522 | ProxyPort string `json:"proxy_port"`
523 | WebPort string `json:"web_port"`
524 | AdminUsername string `json:"admin_username"`
525 | AdminPassword string `json:"admin_password"`
526 | UseForwardProxy bool `json:"use_forward_proxy"`
527 | AllowAnonymous bool `json:"allow_anonymous"`
528 | RemoteProxyAddr string `json:"remote_proxy_addr"`
529 | RemoteProxyUser string `json:"remote_proxy_user"`
530 | RemoteProxyPass string `json:"remote_proxy_pass"`
531 | }
532 |
533 | if err := c.BindJSON(&req); err != nil {
534 | c.JSON(http.StatusBadRequest, gin.H{
535 | "success": false,
536 | "message": "无效的请求",
537 | })
538 | return
539 | }
540 |
541 | // 验证端口
542 | proxyPort, err := strconv.Atoi(req.ProxyPort)
543 | if err != nil || proxyPort < 1 || proxyPort > 65535 {
544 | c.JSON(http.StatusBadRequest, gin.H{
545 | "success": false,
546 | "message": "无效的代理端口",
547 | })
548 | return
549 | }
550 |
551 | webPort, err := strconv.Atoi(req.WebPort)
552 | if err != nil || webPort < 1 || webPort > 65535 || webPort == proxyPort {
553 | c.JSON(http.StatusBadRequest, gin.H{
554 | "success": false,
555 | "message": "无效的Web端口或与代理端口冲突",
556 | })
557 | return
558 | }
559 |
560 | // 验证用户名
561 | if req.AdminUsername == "" {
562 | c.JSON(http.StatusBadRequest, gin.H{
563 | "success": false,
564 | "message": "管理员用户名不能为空",
565 | })
566 | return
567 | }
568 |
569 | // 如果启用了代理转发,验证远程代理地址
570 | if req.UseForwardProxy && req.RemoteProxyAddr == "" {
571 | c.JSON(http.StatusBadRequest, gin.H{
572 | "success": false,
573 | "message": "启用代理转发时,远程代理地址不能为空",
574 | })
575 | return
576 | }
577 |
578 | // 更新设置
579 | proxyMutex.Lock()
580 | defer proxyMutex.Unlock()
581 |
582 | // 更新代理端口
583 | globalConfig.ListenAddr = fmt.Sprintf("0.0.0.0:%s", req.ProxyPort)
584 |
585 | // 更新Web端口
586 | globalConfig.WebPort = req.WebPort
587 |
588 | // 更新管理员用户名
589 | globalConfig.AdminUsername = req.AdminUsername
590 |
591 | // 更新管理员密码(如果提供)
592 | if req.AdminPassword != "" {
593 | globalConfig.AdminPassword = req.AdminPassword
594 | }
595 |
596 | // 更新代理转发设置
597 | globalConfig.UseForwardProxy = req.UseForwardProxy
598 |
599 | // 更新匿名访问设置
600 | globalConfig.AllowAnonymous = req.AllowAnonymous
601 |
602 | // 更新远程代理设置
603 | if req.RemoteProxyAddr != "" {
604 | globalConfig.RemoteProxyAddr = req.RemoteProxyAddr
605 | }
606 | if req.RemoteProxyUser != "" {
607 | globalConfig.RemoteProxyUser = req.RemoteProxyUser
608 | }
609 | if req.RemoteProxyPass != "" {
610 | globalConfig.RemoteProxyPass = req.RemoteProxyPass
611 | }
612 |
613 | c.JSON(http.StatusOK, gin.H{
614 | "success": true,
615 | "message": "设置已更新",
616 | })
617 | }
618 |
619 | // 处理获取统计信息
620 | func handleGetStats(c *gin.Context) {
621 | // 获取域名统计
622 | domainStats := globalConfig.StatsManager.GetDomainStats()
623 |
624 | c.JSON(http.StatusOK, gin.H{
625 | "success": true,
626 | "data": gin.H{
627 | "total_requests": globalConfig.StatsManager.GetTotalRequests(),
628 | "domain_stats": domainStats,
629 | },
630 | })
631 | }
632 |
633 | // 处理重置统计信息
634 | func handleResetStats(c *gin.Context) {
635 | // 重置统计信息
636 | globalConfig.StatsManager.Reset()
637 |
638 | c.JSON(http.StatusOK, gin.H{
639 | "success": true,
640 | "message": "统计信息已重置",
641 | })
642 | }
643 |
644 | // 处理获取所有代理端口
645 | func handleGetProxyPorts(c *gin.Context) {
646 | ports := globalConfig.GetAllProxyPorts()
647 |
648 | // 转换为JSON格式
649 | result := make([]gin.H, 0, len(ports))
650 | for _, port := range ports {
651 | port.Status.mutex.RLock()
652 | result = append(result, gin.H{
653 | "id": port.ID,
654 | "listen_addr": port.ListenAddr,
655 | "enabled": port.Enabled,
656 | "running": port.Status.Running,
657 | "start_time": port.Status.StartTime,
658 | "connection_count": port.Status.ConnectionCount,
659 | "allow_anonymous": port.AllowAnonymous,
660 | })
661 | port.Status.mutex.RUnlock()
662 | }
663 |
664 | c.JSON(http.StatusOK, gin.H{
665 | "success": true,
666 | "data": result,
667 | })
668 | }
669 |
670 | // 处理添加代理端口
671 | func handleAddProxyPort(c *gin.Context) {
672 | var req struct {
673 | ListenAddr string `json:"listen_addr"`
674 | AllowAnonymous bool `json:"allow_anonymous"`
675 | }
676 |
677 | if err := c.BindJSON(&req); err != nil {
678 | c.JSON(http.StatusBadRequest, gin.H{
679 | "success": false,
680 | "message": "无效的请求",
681 | })
682 | return
683 | }
684 |
685 | // 验证监听地址
686 | if req.ListenAddr == "" {
687 | c.JSON(http.StatusBadRequest, gin.H{
688 | "success": false,
689 | "message": "监听地址不能为空",
690 | })
691 | return
692 | }
693 |
694 | // 生成唯一ID
695 | portID := fmt.Sprintf("port_%d", time.Now().UnixNano())
696 |
697 | // 创建新的端口配置
698 | port := &ProxyPortConfig{
699 | ID: portID,
700 | ListenAddr: req.ListenAddr,
701 | Enabled: true,
702 | Status: &ProxyStatus{Running: false, StartTime: time.Time{}, ConnectionCount: 0, mutex: sync.RWMutex{}},
703 | AllowAnonymous: req.AllowAnonymous,
704 | PortUsers: make(map[string]string), // 初始化用户映射
705 | NoAuthUsers: make(map[string]bool), // 初始化无需认证用户映射
706 | usersMutex: sync.RWMutex{},
707 | UseForwardProxy: false,
708 | RemoteProxyAddr: "",
709 | RemoteProxyUser: "",
710 | RemoteProxyPass: "",
711 | }
712 |
713 | // 添加到全局配置
714 | globalConfig.AddProxyPort(port)
715 |
716 | // 保存配置到文件
717 | saveConfig()
718 |
719 | c.JSON(http.StatusOK, gin.H{
720 | "success": true,
721 | "message": "代理端口已添加",
722 | "data": port,
723 | })
724 | }
725 |
726 | // 处理获取单个代理端口
727 | func handleGetProxyPort(c *gin.Context) {
728 | id := c.Param("id")
729 |
730 | port := globalConfig.GetProxyPort(id)
731 | if port == nil {
732 | c.JSON(http.StatusNotFound, gin.H{
733 | "success": false,
734 | "message": "代理端口不存在",
735 | })
736 | return
737 | }
738 |
739 | port.Status.mutex.RLock()
740 | result := gin.H{
741 | "id": port.ID,
742 | "listen_addr": port.ListenAddr,
743 | "enabled": port.Enabled,
744 | "running": port.Status.Running,
745 | "start_time": port.Status.StartTime,
746 | "connection_count": port.Status.ConnectionCount,
747 | "allow_anonymous": port.AllowAnonymous,
748 | }
749 | port.Status.mutex.RUnlock()
750 |
751 | c.JSON(http.StatusOK, gin.H{
752 | "success": true,
753 | "data": result,
754 | })
755 | }
756 |
757 | // 处理更新代理端口
758 | func handleUpdateProxyPort(c *gin.Context) {
759 | id := c.Param("id")
760 |
761 | port := globalConfig.GetProxyPort(id)
762 | if port == nil {
763 | c.JSON(http.StatusNotFound, gin.H{
764 | "success": false,
765 | "message": "端口不存在",
766 | })
767 | return
768 | }
769 |
770 | var req struct {
771 | ListenAddr string `json:"listen_addr"`
772 | Enabled bool `json:"enabled"`
773 | AllowAnonymous bool `json:"allow_anonymous"`
774 | }
775 |
776 | if err := c.BindJSON(&req); err != nil {
777 | c.JSON(http.StatusBadRequest, gin.H{
778 | "success": false,
779 | "message": "无效的请求",
780 | })
781 | return
782 | }
783 |
784 | // 验证监听地址
785 | if req.ListenAddr == "" {
786 | c.JSON(http.StatusBadRequest, gin.H{
787 | "success": false,
788 | "message": "监听地址不能为空",
789 | })
790 | return
791 | }
792 |
793 | // 检查是否需要重启代理
794 | needRestart := port.Status.Running && (port.ListenAddr != req.ListenAddr || port.Enabled != req.Enabled)
795 |
796 | // 如果需要重启,先停止代理
797 | if needRestart {
798 | stopProxyPort(id)
799 | }
800 |
801 | // 更新配置
802 | port.ListenAddr = req.ListenAddr
803 | port.Enabled = req.Enabled
804 | port.AllowAnonymous = req.AllowAnonymous
805 |
806 | // 如果需要重启且启用状态为true,则重新启动代理
807 | if needRestart && req.Enabled {
808 | go startProxyPort(id)
809 | }
810 |
811 | // 保存配置到文件
812 | saveConfig()
813 |
814 | c.JSON(http.StatusOK, gin.H{
815 | "success": true,
816 | "message": "代理端口已更新",
817 | })
818 | }
819 |
820 | // 处理删除代理端口
821 | func handleDeleteProxyPort(c *gin.Context) {
822 | id := c.Param("id")
823 |
824 | // 不允许删除默认端口
825 | if id == "default" {
826 | c.JSON(http.StatusBadRequest, gin.H{
827 | "success": false,
828 | "message": "不能删除默认端口",
829 | })
830 | return
831 | }
832 |
833 | port := globalConfig.GetProxyPort(id)
834 | if port == nil {
835 | c.JSON(http.StatusNotFound, gin.H{
836 | "success": false,
837 | "message": "代理端口不存在",
838 | })
839 | return
840 | }
841 |
842 | // 如果端口正在运行,需要先停止
843 | if port.Status.Running {
844 | stopProxyPort(id)
845 | }
846 |
847 | // 删除配置
848 | globalConfig.DeleteProxyPort(id)
849 |
850 | // 保存配置到文件
851 | saveConfig()
852 |
853 | c.JSON(http.StatusOK, gin.H{
854 | "success": true,
855 | "message": "代理端口已删除",
856 | })
857 | }
858 |
859 | // 处理启动单个代理端口
860 | func handleStartProxyPort(c *gin.Context) {
861 | id := c.Param("id")
862 |
863 | port := globalConfig.GetProxyPort(id)
864 | if port == nil {
865 | c.JSON(http.StatusNotFound, gin.H{
866 | "success": false,
867 | "message": "代理端口不存在",
868 | })
869 | return
870 | }
871 |
872 | // 检查是否已启用
873 | if !port.Enabled {
874 | c.JSON(http.StatusBadRequest, gin.H{
875 | "success": false,
876 | "message": "该代理端口未启用",
877 | })
878 | return
879 | }
880 |
881 | // 检查是否已运行
882 | if port.Status.Running {
883 | c.JSON(http.StatusBadRequest, gin.H{
884 | "success": false,
885 | "message": "代理服务器已经在运行",
886 | })
887 | return
888 | }
889 |
890 | // 启动代理服务器
891 | go startProxyPort(id)
892 |
893 | c.JSON(http.StatusOK, gin.H{
894 | "success": true,
895 | "message": "代理服务器已启动",
896 | })
897 | }
898 |
899 | // 处理停止单个代理端口
900 | func handleStopProxyPort(c *gin.Context) {
901 | id := c.Param("id")
902 |
903 | port := globalConfig.GetProxyPort(id)
904 | if port == nil {
905 | c.JSON(http.StatusNotFound, gin.H{
906 | "success": false,
907 | "message": "代理端口不存在",
908 | })
909 | return
910 | }
911 |
912 | // 检查是否正在运行
913 | if !port.Status.Running {
914 | c.JSON(http.StatusBadRequest, gin.H{
915 | "success": false,
916 | "message": "代理服务器未运行",
917 | })
918 | return
919 | }
920 |
921 | // 停止代理服务器
922 | stopProxyPort(id)
923 |
924 | c.JSON(http.StatusOK, gin.H{
925 | "success": true,
926 | "message": "代理服务器已停止",
927 | })
928 | }
929 |
930 | // 启动指定ID的代理端口
931 | func startProxyPort(portID string) {
932 | // 获取端口配置
933 | port := globalConfig.GetProxyPort(portID)
934 | if port == nil {
935 | log.Printf("无法启动代理端口,端口ID不存在: %s", portID)
936 | return
937 | }
938 |
939 | // 检查端口是否已经在运行
940 | if port.Status.Running {
941 | log.Printf("代理端口已经在运行中: %s (%s)", portID, port.ListenAddr)
942 | return
943 | }
944 |
945 | // 创建代理服务器实例
946 | server := NewProxyServer(globalConfig, portID)
947 |
948 | // 启动代理服务器
949 | err := server.Start()
950 | if err != nil {
951 | log.Printf("启动代理端口失败: %s (%s): %v", portID, port.ListenAddr, err)
952 | return
953 | }
954 |
955 | // 保存代理服务器实例
956 | proxyServers[portID] = server
957 | proxyRunningStatus[portID] = true
958 |
959 | log.Printf("代理端口已启动: %s (%s)", portID, port.ListenAddr)
960 | }
961 |
962 | // 停止单个代理端口
963 | func stopProxyPort(id string) {
964 | proxyMutex.Lock()
965 | defer proxyMutex.Unlock()
966 |
967 | // 检查是否正在运行
968 | if !proxyRunningStatus[id] {
969 | return
970 | }
971 |
972 | // 关闭所有活动连接
973 | log.Printf("正在关闭端口 %s 的所有活动连接...", id)
974 | closeAllConnections()
975 |
976 | // 关闭TCP监听器
977 | listenerMutex.Lock()
978 | if listener, ok := proxyListeners[id]; ok && listener != nil {
979 | listener.Close()
980 | delete(proxyListeners, id)
981 | }
982 | listenerMutex.Unlock()
983 |
984 | // 更新状态
985 | proxyRunningStatus[id] = false
986 |
987 | port := globalConfig.GetProxyPort(id)
988 | if port != nil {
989 | port.Status.mutex.Lock()
990 | port.Status.Running = false
991 | port.Status.mutex.Unlock()
992 | }
993 |
994 | // 等待一小段时间确保端口释放
995 | time.Sleep(100 * time.Millisecond)
996 |
997 | log.Printf("HTTPS代理服务器已停止 (端口ID: %s)\n", id)
998 | }
999 |
1000 | // 兼容旧版本的启动代理服务器函数
1001 | func startProxyServer() {
1002 | startProxyPort("default")
1003 | }
1004 |
1005 | // 兼容旧版本的停止代理服务器函数
1006 | func stopProxyServer() {
1007 | stopProxyPort("default")
1008 | }
1009 |
1010 | // 处理获取端口用户
1011 | func handleGetPortUsers(c *gin.Context) {
1012 | portID := c.Param("id")
1013 |
1014 | port := globalConfig.GetProxyPort(portID)
1015 | if port == nil {
1016 | c.JSON(http.StatusNotFound, gin.H{
1017 | "success": false,
1018 | "message": "端口不存在",
1019 | })
1020 | return
1021 | }
1022 |
1023 | users := port.GetAllUsers()
1024 |
1025 | c.JSON(http.StatusOK, gin.H{
1026 | "success": true,
1027 | "data": users,
1028 | })
1029 | }
1030 |
1031 | // 处理添加端口用户
1032 | func handleAddPortUser(c *gin.Context) {
1033 | portID := c.Param("id")
1034 |
1035 | port := globalConfig.GetProxyPort(portID)
1036 | if port == nil {
1037 | c.JSON(http.StatusNotFound, gin.H{
1038 | "success": false,
1039 | "message": "端口不存在",
1040 | })
1041 | return
1042 | }
1043 |
1044 | var req struct {
1045 | Username string `json:"username"`
1046 | Password string `json:"password"`
1047 | NoAuth bool `json:"no_auth"`
1048 | }
1049 |
1050 | if err := c.BindJSON(&req); err != nil {
1051 | c.JSON(http.StatusBadRequest, gin.H{
1052 | "success": false,
1053 | "message": "无效的请求",
1054 | })
1055 | return
1056 | }
1057 |
1058 | // 验证用户名
1059 | if req.Username == "" {
1060 | c.JSON(http.StatusBadRequest, gin.H{
1061 | "success": false,
1062 | "message": "用户名不能为空",
1063 | })
1064 | return
1065 | }
1066 |
1067 | // 验证密码(如果不是无需认证的用户)
1068 | if !req.NoAuth && req.Password == "" {
1069 | c.JSON(http.StatusBadRequest, gin.H{
1070 | "success": false,
1071 | "message": "密码不能为空",
1072 | })
1073 | return
1074 | }
1075 |
1076 | // 添加用户
1077 | if req.NoAuth {
1078 | port.AddNoAuthUser(req.Username)
1079 | } else {
1080 | port.AddUser(req.Username, req.Password)
1081 | }
1082 |
1083 | // 保存配置到文件
1084 | saveConfig()
1085 |
1086 | c.JSON(http.StatusOK, gin.H{
1087 | "success": true,
1088 | "message": "用户已添加",
1089 | })
1090 | }
1091 |
1092 | // 处理删除端口用户
1093 | func handleDeletePortUser(c *gin.Context) {
1094 | portID := c.Param("id")
1095 | username := c.Param("username")
1096 |
1097 | port := globalConfig.GetProxyPort(portID)
1098 | if port == nil {
1099 | c.JSON(http.StatusNotFound, gin.H{
1100 | "success": false,
1101 | "message": "端口不存在",
1102 | })
1103 | return
1104 | }
1105 |
1106 | port.DeleteUser(username)
1107 |
1108 | // 保存配置到文件
1109 | saveConfig()
1110 |
1111 | c.JSON(http.StatusOK, gin.H{
1112 | "success": true,
1113 | "message": "用户已删除",
1114 | })
1115 | }
1116 |
1117 | // 处理获取端口代理转发设置
1118 | func handleGetPortForward(c *gin.Context) {
1119 | portID := c.Param("id")
1120 |
1121 | port := globalConfig.GetProxyPort(portID)
1122 | if port == nil {
1123 | c.JSON(http.StatusNotFound, gin.H{
1124 | "success": false,
1125 | "message": "端口不存在",
1126 | })
1127 | return
1128 | }
1129 |
1130 | c.JSON(http.StatusOK, gin.H{
1131 | "success": true,
1132 | "data": gin.H{
1133 | "use_forward_proxy": port.UseForwardProxy,
1134 | "remote_proxy_addr": port.RemoteProxyAddr,
1135 | "remote_proxy_user": port.RemoteProxyUser,
1136 | // 不返回密码,保护安全
1137 | },
1138 | })
1139 | }
1140 |
1141 | // 处理更新端口代理转发设置
1142 | func handleUpdatePortForward(c *gin.Context) {
1143 | portID := c.Param("id")
1144 |
1145 | port := globalConfig.GetProxyPort(portID)
1146 | if port == nil {
1147 | c.JSON(http.StatusNotFound, gin.H{
1148 | "success": false,
1149 | "message": "端口不存在",
1150 | })
1151 | return
1152 | }
1153 |
1154 | var req struct {
1155 | UseForwardProxy bool `json:"use_forward_proxy"`
1156 | RemoteProxyAddr string `json:"remote_proxy_addr"`
1157 | RemoteProxyUser string `json:"remote_proxy_user"`
1158 | RemoteProxyPass string `json:"remote_proxy_pass"`
1159 | }
1160 |
1161 | if err := c.BindJSON(&req); err != nil {
1162 | c.JSON(http.StatusBadRequest, gin.H{
1163 | "success": false,
1164 | "message": "无效的请求",
1165 | })
1166 | return
1167 | }
1168 |
1169 | // 验证远程代理地址(如果启用了代理转发)
1170 | if req.UseForwardProxy && req.RemoteProxyAddr == "" {
1171 | c.JSON(http.StatusBadRequest, gin.H{
1172 | "success": false,
1173 | "message": "远程代理地址不能为空",
1174 | })
1175 | return
1176 | }
1177 |
1178 | log.Printf("更新端口 %s 的代理转发设置: 启用=%v, 地址=%s",
1179 | portID, req.UseForwardProxy, req.RemoteProxyAddr)
1180 |
1181 | // 更新设置
1182 | port.UseForwardProxy = req.UseForwardProxy
1183 | port.RemoteProxyAddr = req.RemoteProxyAddr
1184 |
1185 | if req.RemoteProxyUser != "" {
1186 | port.RemoteProxyUser = req.RemoteProxyUser
1187 | }
1188 |
1189 | // 只有在提供了新密码时才更新密码
1190 | if req.RemoteProxyPass != "" {
1191 | port.RemoteProxyPass = req.RemoteProxyPass
1192 | }
1193 |
1194 | // 保存配置到文件
1195 | saveConfig()
1196 |
1197 | // 关闭所有活动连接,强制它们重新连接以使用新的代理设置
1198 | closeAllConnections()
1199 | log.Printf("已关闭所有活动连接,强制重新连接以使用新的代理设置")
1200 |
1201 | // 如果代理正在运行,需要重启以应用新的转发设置
1202 | if port.Status.Running {
1203 | log.Printf("重启端口 %s 以应用新的代理转发设置", portID)
1204 | stopProxyPort(portID)
1205 |
1206 | // 等待一小段时间确保端口完全关闭
1207 | time.Sleep(500 * time.Millisecond)
1208 |
1209 | go startProxyPort(portID)
1210 | }
1211 |
1212 | c.JSON(http.StatusOK, gin.H{
1213 | "success": true,
1214 | "message": "代理转发设置已更新",
1215 | "data": gin.H{
1216 | "restarted": port.Status.Running,
1217 | },
1218 | })
1219 | }
1220 |
1221 | // 保存配置到文件
1222 | func saveConfig() {
1223 | // 将配置序列化为JSON
1224 | configData, err := json.MarshalIndent(globalConfig, "", " ")
1225 | if err != nil {
1226 | log.Printf("序列化配置失败: %v", err)
1227 | return
1228 | }
1229 |
1230 | // 写入配置文件
1231 | err = os.WriteFile("config.json", configData, 0644)
1232 | if err != nil {
1233 | log.Printf("保存配置文件失败: %v", err)
1234 | return
1235 | }
1236 |
1237 | log.Println("配置已保存到文件")
1238 | }
1239 |
--------------------------------------------------------------------------------
/models.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "sync"
6 | "time"
7 | )
8 |
9 | // 代理服务器状态
10 | type ProxyStatus struct {
11 | Running bool `json:"running"`
12 | StartTime time.Time `json:"start_time"`
13 | ConnectionCount int `json:"connection_count"`
14 | mutex sync.RWMutex `json:"-"` // 不序列化互斥锁
15 | }
16 |
17 | // 单个代理端口配置
18 | type ProxyPortConfig struct {
19 | ID string `json:"id"` // 唯一标识符
20 | ListenAddr string `json:"listen_addr"` // 监听地址,格式为 "IP:端口"
21 | Enabled bool `json:"enabled"` // 是否启用
22 | Status *ProxyStatus `json:"status"` // 代理状态
23 | AllowAnonymous bool `json:"allow_anonymous"` // 是否允许匿名访问
24 | PortUsers map[string]string `json:"-"` // 端口专用用户,key为用户名,value为密码
25 | NoAuthUsers map[string]bool `json:"-"` // 端口专用无需认证的用户
26 | usersMutex sync.RWMutex `json:"-"` // 用于保护用户映射的互斥锁
27 | UseForwardProxy bool `json:"use_forward_proxy"` // 是否启用代理转发
28 | RemoteProxyAddr string `json:"remote_proxy_addr"` // 远程代理地址
29 | RemoteProxyUser string `json:"remote_proxy_user"` // 远程代理用户名
30 | RemoteProxyPass string `json:"remote_proxy_pass"` // 远程代理密码
31 | }
32 |
33 | // 在反序列化后初始化互斥锁和映射
34 | func (pc *ProxyPortConfig) InitAfterUnmarshal() {
35 | if pc.PortUsers == nil {
36 | pc.PortUsers = make(map[string]string)
37 | }
38 | if pc.NoAuthUsers == nil {
39 | pc.NoAuthUsers = make(map[string]bool)
40 | }
41 | if pc.Status == nil {
42 | pc.Status = &ProxyStatus{
43 | Running: false,
44 | StartTime: time.Time{},
45 | ConnectionCount: 0,
46 | }
47 | }
48 | }
49 |
50 | // 域名访问统计
51 | type DomainStat struct {
52 | Domain string `json:"domain"`
53 | Count int `json:"count"`
54 | LastVisit time.Time `json:"last_visit"`
55 | }
56 |
57 | // 请求统计管理器
58 | type StatsManager struct {
59 | TotalRequests int `json:"total_requests"`
60 | DomainStats map[string]*DomainStat `json:"domain_stats"`
61 | mutex sync.RWMutex
62 | }
63 |
64 | // 创建新的统计管理器
65 | func NewStatsManager() *StatsManager {
66 | return &StatsManager{
67 | TotalRequests: 0,
68 | DomainStats: make(map[string]*DomainStat),
69 | mutex: sync.RWMutex{},
70 | }
71 | }
72 |
73 | // 记录请求
74 | func (sm *StatsManager) RecordRequest(domain string) {
75 | sm.mutex.Lock()
76 | defer sm.mutex.Unlock()
77 |
78 | sm.TotalRequests++
79 |
80 | // 更新域名统计
81 | if _, exists := sm.DomainStats[domain]; !exists {
82 | sm.DomainStats[domain] = &DomainStat{
83 | Domain: domain,
84 | Count: 0,
85 | LastVisit: time.Now(),
86 | }
87 | }
88 |
89 | sm.DomainStats[domain].Count++
90 | sm.DomainStats[domain].LastVisit = time.Now()
91 | }
92 |
93 | // 获取所有域名统计
94 | func (sm *StatsManager) GetDomainStats() []*DomainStat {
95 | sm.mutex.RLock()
96 | defer sm.mutex.RUnlock()
97 |
98 | stats := make([]*DomainStat, 0, len(sm.DomainStats))
99 | for _, stat := range sm.DomainStats {
100 | stats = append(stats, stat)
101 | }
102 |
103 | return stats
104 | }
105 |
106 | // 获取总请求数
107 | func (sm *StatsManager) GetTotalRequests() int {
108 | sm.mutex.RLock()
109 | defer sm.mutex.RUnlock()
110 | return sm.TotalRequests
111 | }
112 |
113 | // 重置统计信息
114 | func (sm *StatsManager) Reset() {
115 | sm.mutex.Lock()
116 | defer sm.mutex.Unlock()
117 |
118 | sm.TotalRequests = 0
119 | sm.DomainStats = make(map[string]*DomainStat)
120 | }
121 |
122 | // 扩展代理配置
123 | type ExtendedProxyConfig struct {
124 | ProxyConfig
125 | AdminUsername string
126 | AdminPassword string
127 | WebPort string
128 | Status *ProxyStatus
129 | StatsManager *StatsManager
130 | UseForwardProxy bool // 是否启用代理转发功能
131 | AllowAnonymous bool // 是否允许匿名访问(不需要认证)
132 | RemoteProxyAddr string // 远程代理服务器地址
133 | RemoteProxyUser string // 远程代理服务器用户名
134 | RemoteProxyPass string // 远程代理服务器密码
135 |
136 | // 多端口配置
137 | ProxyPorts map[string]*ProxyPortConfig // 多端口配置,key为端口ID
138 | proxyPortsMutex sync.RWMutex // 用于保护ProxyPorts的互斥锁
139 | }
140 |
141 | // 创建新的扩展代理配置
142 | func NewExtendedProxyConfig() *ExtendedProxyConfig {
143 | config := &ExtendedProxyConfig{
144 | ProxyConfig: ProxyConfig{
145 | ListenAddr: "0.0.0.0:8080",
146 | CredStore: NewCredentialStore(),
147 | },
148 | AdminUsername: "admin",
149 | AdminPassword: "admin",
150 | WebPort: "8081",
151 | Status: &ProxyStatus{Running: false, StartTime: time.Time{}, ConnectionCount: 0, mutex: sync.RWMutex{}},
152 | StatsManager: NewStatsManager(),
153 | UseForwardProxy: false, // 默认不启用代理转发
154 | AllowAnonymous: false, // 默认不允许匿名访问
155 | RemoteProxyAddr: "127.0.0.1:7890",
156 | RemoteProxyUser: "dsdddd",
157 | RemoteProxyPass: "dsdddd",
158 | ProxyPorts: make(map[string]*ProxyPortConfig),
159 | proxyPortsMutex: sync.RWMutex{},
160 | }
161 |
162 | // 添加默认端口配置
163 | defaultPort := &ProxyPortConfig{
164 | ID: "default",
165 | ListenAddr: "0.0.0.0:8080",
166 | Enabled: true,
167 | Status: &ProxyStatus{Running: false, StartTime: time.Time{}, ConnectionCount: 0, mutex: sync.RWMutex{}},
168 | AllowAnonymous: false,
169 | PortUsers: make(map[string]string),
170 | NoAuthUsers: make(map[string]bool),
171 | usersMutex: sync.RWMutex{},
172 | UseForwardProxy: false,
173 | RemoteProxyAddr: "",
174 | RemoteProxyUser: "",
175 | RemoteProxyPass: "",
176 | }
177 | config.ProxyPorts["default"] = defaultPort
178 |
179 | return config
180 | }
181 |
182 | // 添加代理端口配置
183 | func (ec *ExtendedProxyConfig) AddProxyPort(port *ProxyPortConfig) {
184 | ec.proxyPortsMutex.Lock()
185 | defer ec.proxyPortsMutex.Unlock()
186 | ec.ProxyPorts[port.ID] = port
187 | }
188 |
189 | // 获取代理端口配置
190 | func (ec *ExtendedProxyConfig) GetProxyPort(id string) *ProxyPortConfig {
191 | ec.proxyPortsMutex.RLock()
192 | defer ec.proxyPortsMutex.RUnlock()
193 | return ec.ProxyPorts[id]
194 | }
195 |
196 | // 删除代理端口配置
197 | func (ec *ExtendedProxyConfig) DeleteProxyPort(id string) {
198 | ec.proxyPortsMutex.Lock()
199 | defer ec.proxyPortsMutex.Unlock()
200 | delete(ec.ProxyPorts, id)
201 | }
202 |
203 | // 获取所有代理端口配置
204 | func (ec *ExtendedProxyConfig) GetAllProxyPorts() []*ProxyPortConfig {
205 | ec.proxyPortsMutex.RLock()
206 | defer ec.proxyPortsMutex.RUnlock()
207 |
208 | ports := make([]*ProxyPortConfig, 0, len(ec.ProxyPorts))
209 | for _, port := range ec.ProxyPorts {
210 | ports = append(ports, port)
211 | }
212 | return ports
213 | }
214 |
215 | // 用户凭证存储
216 | type CredentialStore struct {
217 | credentials map[string]string
218 | noAuthUsers map[string]bool // 存储无需认证的用户
219 | mutex sync.RWMutex
220 | }
221 |
222 | // 创建新的凭证存储
223 | func NewCredentialStore() *CredentialStore {
224 | return &CredentialStore{
225 | credentials: make(map[string]string),
226 | noAuthUsers: make(map[string]bool),
227 | mutex: sync.RWMutex{},
228 | }
229 | }
230 |
231 | // 添加用户凭证
232 | func (cs *CredentialStore) AddCredential(username, password string) {
233 | cs.mutex.Lock()
234 | defer cs.mutex.Unlock()
235 | cs.credentials[username] = password
236 | }
237 |
238 | // 添加无需认证的用户
239 | func (cs *CredentialStore) AddNoAuthUser(username string) {
240 | cs.mutex.Lock()
241 | defer cs.mutex.Unlock()
242 | cs.noAuthUsers[username] = true
243 | }
244 |
245 | // 检查是否为无需认证的用户
246 | func (cs *CredentialStore) IsNoAuthUser(username string) bool {
247 | cs.mutex.RLock()
248 | defer cs.mutex.RUnlock()
249 | return cs.noAuthUsers[username]
250 | }
251 |
252 | // 验证用户凭证
253 | func (cs *CredentialStore) Validate(username, password string) bool {
254 | cs.mutex.RLock()
255 | defer cs.mutex.RUnlock()
256 |
257 | // 检查是否为无需认证的用户
258 | if cs.noAuthUsers[username] {
259 | return true
260 | }
261 |
262 | // 否则验证用户名和密码
263 | stored, exists := cs.credentials[username]
264 | return exists && stored == password
265 | }
266 |
267 | // 添加端口用户
268 | func (pc *ProxyPortConfig) AddUser(username, password string) {
269 | pc.usersMutex.Lock()
270 | defer pc.usersMutex.Unlock()
271 | pc.PortUsers[username] = password
272 | }
273 |
274 | // 添加端口无需认证用户
275 | func (pc *ProxyPortConfig) AddNoAuthUser(username string) {
276 | pc.usersMutex.Lock()
277 | defer pc.usersMutex.Unlock()
278 | pc.NoAuthUsers[username] = true
279 | }
280 |
281 | // 删除端口用户
282 | func (pc *ProxyPortConfig) DeleteUser(username string) {
283 | pc.usersMutex.Lock()
284 | defer pc.usersMutex.Unlock()
285 | delete(pc.PortUsers, username)
286 | delete(pc.NoAuthUsers, username)
287 | }
288 |
289 | // 验证端口用户
290 | func (pc *ProxyPortConfig) ValidateUser(username, password string) bool {
291 | pc.usersMutex.RLock()
292 | defer pc.usersMutex.RUnlock()
293 |
294 | // 检查是否为无需认证的用户
295 | if pc.NoAuthUsers[username] {
296 | return true
297 | }
298 |
299 | // 否则验证用户名和密码
300 | storedPassword, exists := pc.PortUsers[username]
301 | return exists && storedPassword == password
302 | }
303 |
304 | // 获取所有端口用户
305 | func (pc *ProxyPortConfig) GetAllUsers() []map[string]interface{} {
306 | pc.usersMutex.RLock()
307 | defer pc.usersMutex.RUnlock()
308 |
309 | users := make([]map[string]interface{}, 0)
310 |
311 | // 添加普通用户
312 | for username, _ := range pc.PortUsers {
313 | // 跳过同时也是无需认证的用户
314 | if pc.NoAuthUsers[username] {
315 | continue
316 | }
317 |
318 | users = append(users, map[string]interface{}{
319 | "username": username,
320 | "no_auth": false,
321 | })
322 | }
323 |
324 | // 添加无需认证的用户
325 | for username := range pc.NoAuthUsers {
326 | users = append(users, map[string]interface{}{
327 | "username": username,
328 | "no_auth": true,
329 | })
330 | }
331 |
332 | return users
333 | }
334 |
335 | // 自定义 UnmarshalJSON 方法,用于在反序列化后初始化互斥锁和映射
336 | func (ec *ExtendedProxyConfig) UnmarshalJSON(data []byte) error {
337 | // 创建一个临时结构体,与 ExtendedProxyConfig 相同但没有互斥锁字段
338 | type TempConfig ExtendedProxyConfig
339 | var temp TempConfig
340 |
341 | if err := json.Unmarshal(data, &temp); err != nil {
342 | return err
343 | }
344 |
345 | // 复制值到原始结构体
346 | *ec = ExtendedProxyConfig(temp)
347 |
348 | // 初始化互斥锁和映射
349 | ec.proxyPortsMutex = sync.RWMutex{}
350 |
351 | if ec.ProxyPorts == nil {
352 | ec.ProxyPorts = make(map[string]*ProxyPortConfig)
353 | }
354 |
355 | // 初始化每个端口配置
356 | for _, port := range ec.ProxyPorts {
357 | port.InitAfterUnmarshal()
358 | }
359 |
360 | // 初始化凭证存储
361 | if ec.CredStore == nil {
362 | ec.CredStore = NewCredentialStore()
363 | }
364 |
365 | // 初始化统计管理器
366 | if ec.StatsManager == nil {
367 | ec.StatsManager = NewStatsManager()
368 | } else {
369 | ec.StatsManager.mutex = sync.RWMutex{}
370 | }
371 |
372 | // 初始化状态
373 | if ec.Status == nil {
374 | ec.Status = &ProxyStatus{
375 | Running: false,
376 | StartTime: time.Time{},
377 | ConnectionCount: 0,
378 | }
379 | } else {
380 | ec.Status.mutex = sync.RWMutex{}
381 | }
382 |
383 | return nil
384 | }
385 |
--------------------------------------------------------------------------------
/static/css/style.css:
--------------------------------------------------------------------------------
1 | /* 全局样式 */
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | box-sizing: border-box;
6 | font-family: 'Microsoft YaHei', Arial, sans-serif;
7 | }
8 |
9 | body {
10 | background-color: #f5f5f5;
11 | color: #333;
12 | line-height: 1.6;
13 | }
14 |
15 | /* 登录页面样式 */
16 | .login-container {
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | height: 100vh;
21 | background-color: #f0f2f5;
22 | }
23 |
24 | .login-box {
25 | background-color: #fff;
26 | border-radius: 8px;
27 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
28 | padding: 30px;
29 | width: 380px;
30 | }
31 |
32 | .login-box h2 {
33 | text-align: center;
34 | margin-bottom: 24px;
35 | color: #1890ff;
36 | }
37 |
38 | .form-group {
39 | margin-bottom: 20px;
40 | }
41 |
42 | .form-group label {
43 | display: block;
44 | margin-bottom: 8px;
45 | font-weight: 500;
46 | }
47 |
48 | .form-group input {
49 | width: 100%;
50 | padding: 10px 12px;
51 | border: 1px solid #d9d9d9;
52 | border-radius: 4px;
53 | font-size: 14px;
54 | transition: all 0.3s;
55 | }
56 |
57 | .form-group input:focus {
58 | border-color: #1890ff;
59 | box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
60 | outline: none;
61 | }
62 |
63 | .btn-login {
64 | width: 100%;
65 | padding: 10px 12px;
66 | background-color: #1890ff;
67 | color: white;
68 | border: none;
69 | border-radius: 4px;
70 | font-size: 16px;
71 | cursor: pointer;
72 | transition: background-color 0.3s;
73 | }
74 |
75 | .btn-login:hover {
76 | background-color: #40a9ff;
77 | }
78 |
79 | .error-message {
80 | color: #f5222d;
81 | margin-bottom: 16px;
82 | text-align: center;
83 | min-height: 20px;
84 | }
85 |
86 | /* 仪表盘样式 */
87 | .dashboard-container {
88 | display: flex;
89 | flex-direction: column;
90 | min-height: 100vh;
91 | }
92 |
93 | .dashboard-header {
94 | background-color: #001529;
95 | color: white;
96 | padding: 0 20px;
97 | height: 64px;
98 | display: flex;
99 | justify-content: space-between;
100 | align-items: center;
101 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
102 | }
103 |
104 | .dashboard-header h1 {
105 | font-size: 20px;
106 | margin: 0;
107 | }
108 |
109 | .user-info {
110 | display: flex;
111 | align-items: center;
112 | }
113 |
114 | .user-info span {
115 | margin-right: 16px;
116 | }
117 |
118 | .btn-logout {
119 | background-color: transparent;
120 | border: 1px solid rgba(255, 255, 255, 0.5);
121 | color: white;
122 | padding: 4px 12px;
123 | border-radius: 4px;
124 | cursor: pointer;
125 | transition: all 0.3s;
126 | }
127 |
128 | .btn-logout:hover {
129 | background-color: rgba(255, 255, 255, 0.1);
130 | }
131 |
132 | .dashboard-content {
133 | display: flex;
134 | flex: 1;
135 | }
136 |
137 | .sidebar {
138 | width: 200px;
139 | background-color: #001529;
140 | color: white;
141 | padding-top: 20px;
142 | }
143 |
144 | .nav-menu {
145 | list-style: none;
146 | }
147 |
148 | .nav-menu li {
149 | padding: 12px 20px;
150 | cursor: pointer;
151 | transition: background-color 0.3s;
152 | }
153 |
154 | .nav-menu li:hover {
155 | background-color: #1890ff;
156 | }
157 |
158 | .nav-menu li.active {
159 | background-color: #1890ff;
160 | font-weight: 500;
161 | }
162 |
163 | .main-content {
164 | flex: 1;
165 | padding: 20px;
166 | background-color: #f0f2f5;
167 | }
168 |
169 | .panel {
170 | display: none;
171 | background-color: white;
172 | border-radius: 8px;
173 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
174 | padding: 20px;
175 | margin-bottom: 20px;
176 | }
177 |
178 | .panel.active {
179 | display: block;
180 | }
181 |
182 | .panel h2 {
183 | margin-bottom: 20px;
184 | color: #1890ff;
185 | border-bottom: 1px solid #f0f0f0;
186 | padding-bottom: 10px;
187 | }
188 |
189 | /* 状态卡片样式 */
190 | .status-card {
191 | background-color: white;
192 | border-radius: 8px;
193 | padding: 20px;
194 | }
195 |
196 | .status-item {
197 | display: flex;
198 | margin-bottom: 16px;
199 | align-items: center;
200 | }
201 |
202 | .status-item .label {
203 | width: 120px;
204 | font-weight: 500;
205 | }
206 |
207 | .status-indicator {
208 | padding: 4px 8px;
209 | border-radius: 4px;
210 | font-size: 14px;
211 | }
212 |
213 | .status-indicator.running {
214 | background-color: #52c41a;
215 | color: white;
216 | }
217 |
218 | .status-indicator.stopped {
219 | background-color: #f5222d;
220 | color: white;
221 | }
222 |
223 | .status-actions {
224 | margin-top: 20px;
225 | display: flex;
226 | gap: 10px;
227 | }
228 |
229 | .btn-action {
230 | padding: 8px 16px;
231 | border-radius: 4px;
232 | cursor: pointer;
233 | border: none;
234 | transition: all 0.3s;
235 | }
236 |
237 | #start-proxy {
238 | background-color: #52c41a;
239 | color: white;
240 | }
241 |
242 | #start-proxy:hover {
243 | background-color: #73d13d;
244 | }
245 |
246 | #stop-proxy {
247 | background-color: #f5222d;
248 | color: white;
249 | }
250 |
251 | #stop-proxy:hover {
252 | background-color: #ff4d4f;
253 | }
254 |
255 | #stop-proxy:disabled {
256 | background-color: #d9d9d9;
257 | cursor: not-allowed;
258 | }
259 |
260 | /* 用户管理样式 */
261 | .user-list-container {
262 | display: flex;
263 | gap: 20px;
264 | }
265 |
266 | .user-table {
267 | flex: 2;
268 | border-collapse: collapse;
269 | width: 100%;
270 | }
271 |
272 | .user-table th, .user-table td {
273 | border: 1px solid #f0f0f0;
274 | padding: 12px;
275 | text-align: left;
276 | }
277 |
278 | .user-table th {
279 | background-color: #fafafa;
280 | font-weight: 500;
281 | }
282 |
283 | .user-table tr:hover {
284 | background-color: #f5f5f5;
285 | }
286 |
287 | .add-user-form {
288 | flex: 1;
289 | background-color: #fafafa;
290 | padding: 20px;
291 | border-radius: 8px;
292 | }
293 |
294 | .add-user-form h3 {
295 | margin-bottom: 16px;
296 | color: #1890ff;
297 | }
298 |
299 | .btn-add {
300 | background-color: #1890ff;
301 | color: white;
302 | border: none;
303 | padding: 8px 16px;
304 | border-radius: 4px;
305 | cursor: pointer;
306 | transition: background-color 0.3s;
307 | }
308 |
309 | .btn-add:hover {
310 | background-color: #40a9ff;
311 | }
312 |
313 | .btn-delete {
314 | background-color: #f5222d;
315 | color: white;
316 | border: none;
317 | padding: 4px 8px;
318 | border-radius: 4px;
319 | cursor: pointer;
320 | transition: background-color 0.3s;
321 | }
322 |
323 | .btn-delete:hover {
324 | background-color: #ff4d4f;
325 | }
326 |
327 | /* 设置面板样式 */
328 | .settings-form {
329 | max-width: 500px;
330 | }
331 |
332 | .btn-save {
333 | background-color: #1890ff;
334 | color: white;
335 | border: none;
336 | padding: 8px 16px;
337 | border-radius: 4px;
338 | cursor: pointer;
339 | transition: background-color 0.3s;
340 | margin-top: 10px;
341 | }
342 |
343 | .btn-save:hover {
344 | background-color: #40a9ff;
345 | }
346 |
347 | /* 统计面板样式 */
348 | .stats-summary {
349 | display: flex;
350 | flex-direction: column;
351 | gap: 20px;
352 | }
353 |
354 | .stats-card {
355 | background-color: white;
356 | border-radius: 8px;
357 | padding: 20px;
358 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
359 | }
360 |
361 | .stats-card h3 {
362 | margin-bottom: 16px;
363 | color: #1890ff;
364 | }
365 |
366 | .stats-value {
367 | font-size: 32px;
368 | font-weight: bold;
369 | color: #1890ff;
370 | }
371 |
372 | .stats-table {
373 | width: 100%;
374 | border-collapse: collapse;
375 | }
376 |
377 | .stats-table th, .stats-table td {
378 | border: 1px solid #f0f0f0;
379 | padding: 12px;
380 | text-align: left;
381 | }
382 |
383 | .stats-table th {
384 | background-color: #fafafa;
385 | font-weight: 500;
386 | }
387 |
388 | .stats-table-container {
389 | max-height: 300px;
390 | overflow-y: auto;
391 | }
392 |
393 | .btn-reset {
394 | background-color: #faad14;
395 | color: white;
396 | border: none;
397 | padding: 8px 16px;
398 | border-radius: 4px;
399 | cursor: pointer;
400 | transition: background-color 0.3s;
401 | align-self: flex-start;
402 | }
403 |
404 | .btn-reset:hover {
405 | background-color: #ffc53d;
406 | }
407 |
408 | /* 添加复选框样式 */
409 | .checkbox-group {
410 | display: flex;
411 | align-items: center;
412 | margin-bottom: 15px;
413 | }
414 |
415 | .checkbox-group input[type="checkbox"] {
416 | margin-right: 10px;
417 | width: auto;
418 | height: auto;
419 | }
420 |
421 | .checkbox-group label {
422 | margin: 0;
423 | font-weight: normal;
424 | }
425 |
426 | /* 多端口管理样式 */
427 | .ports-container {
428 | display: flex;
429 | flex-direction: column;
430 | gap: 20px;
431 | }
432 |
433 | .ports-list-container {
434 | background-color: white;
435 | border-radius: 8px;
436 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
437 | padding: 20px;
438 | overflow: auto;
439 | }
440 |
441 | .ports-table {
442 | width: 100%;
443 | border-collapse: collapse;
444 | }
445 |
446 | .ports-table th, .ports-table td {
447 | border: 1px solid #f0f0f0;
448 | padding: 12px;
449 | text-align: left;
450 | }
451 |
452 | .ports-table th {
453 | background-color: #fafafa;
454 | font-weight: 500;
455 | }
456 |
457 | .ports-table tr:hover {
458 | background-color: #f5f5f5;
459 | }
460 |
461 | .port-actions {
462 | display: flex;
463 | gap: 5px;
464 | }
465 |
466 | .btn-start {
467 | background-color: #52c41a;
468 | color: white;
469 | border: none;
470 | padding: 4px 8px;
471 | border-radius: 4px;
472 | cursor: pointer;
473 | transition: background-color 0.3s;
474 | }
475 |
476 | .btn-start:hover {
477 | background-color: #73d13d;
478 | }
479 |
480 | .btn-start:disabled {
481 | background-color: #d9d9d9;
482 | cursor: not-allowed;
483 | }
484 |
485 | .btn-stop {
486 | background-color: #f5222d;
487 | color: white;
488 | border: none;
489 | padding: 4px 8px;
490 | border-radius: 4px;
491 | cursor: pointer;
492 | transition: background-color 0.3s;
493 | }
494 |
495 | .btn-stop:hover {
496 | background-color: #ff4d4f;
497 | }
498 |
499 | .btn-edit {
500 | background-color: #1890ff;
501 | color: white;
502 | border: none;
503 | padding: 4px 8px;
504 | border-radius: 4px;
505 | cursor: pointer;
506 | transition: background-color 0.3s;
507 | }
508 |
509 | .btn-edit:hover {
510 | background-color: #40a9ff;
511 | }
512 |
513 | .add-port-form {
514 | background-color: white;
515 | border-radius: 8px;
516 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
517 | padding: 20px;
518 | }
519 |
520 | .add-port-form h3 {
521 | margin-bottom: 16px;
522 | color: #1890ff;
523 | border-bottom: 1px solid #f0f0f0;
524 | padding-bottom: 10px;
525 | }
526 |
527 | /* 模态对话框样式 */
528 | .modal-overlay {
529 | position: fixed;
530 | top: 0;
531 | left: 0;
532 | right: 0;
533 | bottom: 0;
534 | background-color: rgba(0, 0, 0, 0.5);
535 | display: flex;
536 | justify-content: center;
537 | align-items: center;
538 | z-index: 1000;
539 | }
540 |
541 | .modal-content {
542 | background-color: white;
543 | border-radius: 8px;
544 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
545 | padding: 24px;
546 | width: 500px;
547 | max-width: 90%;
548 | }
549 |
550 | .modal-content h3 {
551 | margin-bottom: 16px;
552 | color: #1890ff;
553 | border-bottom: 1px solid #f0f0f0;
554 | padding-bottom: 10px;
555 | }
556 |
557 | .modal-actions {
558 | display: flex;
559 | justify-content: flex-end;
560 | gap: 10px;
561 | margin-top: 20px;
562 | }
563 |
564 | .btn-cancel {
565 | background-color: #f5f5f5;
566 | color: #333;
567 | border: 1px solid #d9d9d9;
568 | padding: 8px 16px;
569 | border-radius: 4px;
570 | cursor: pointer;
571 | transition: all 0.3s;
572 | }
573 |
574 | .btn-cancel:hover {
575 | background-color: #e8e8e8;
576 | }
577 |
578 | /* 端口详情模态框 */
579 | .port-detail-modal .modal-content {
580 | width: 700px;
581 | }
582 |
583 | .port-detail-tabs {
584 | display: flex;
585 | border-bottom: 1px solid #f0f0f0;
586 | margin-bottom: 20px;
587 | }
588 |
589 | .port-detail-tab {
590 | padding: 10px 20px;
591 | cursor: pointer;
592 | border-bottom: 2px solid transparent;
593 | transition: all 0.3s;
594 | }
595 |
596 | .port-detail-tab.active {
597 | color: #1890ff;
598 | border-bottom-color: #1890ff;
599 | }
600 |
601 | .port-detail-panel {
602 | display: none;
603 | }
604 |
605 | .port-detail-panel.active {
606 | display: block;
607 | }
608 |
609 | .port-info {
610 | margin-bottom: 20px;
611 | }
612 |
613 | .port-info-item {
614 | display: flex;
615 | margin-bottom: 10px;
616 | }
617 |
618 | .port-info-label {
619 | width: 120px;
620 | font-weight: 500;
621 | color: #666;
622 | }
623 |
624 | .port-info-value {
625 | flex: 1;
626 | }
627 |
628 | /* 用户管理表格 */
629 | .port-users-table {
630 | width: 100%;
631 | border-collapse: collapse;
632 | margin-bottom: 15px;
633 | }
634 |
635 | .port-users-table th, .port-users-table td {
636 | border: 1px solid #f0f0f0;
637 | padding: 10px;
638 | text-align: left;
639 | }
640 |
641 | .port-users-table th {
642 | background-color: #fafafa;
643 | }
644 |
645 | /* 代理转发设置 */
646 | .proxy-forward-settings {
647 | background-color: #f9f9f9;
648 | border-radius: 6px;
649 | padding: 15px;
650 | margin-top: 15px;
651 | }
652 |
653 | .proxy-forward-settings.disabled {
654 | opacity: 0.7;
655 | pointer-events: none;
656 | }
657 |
658 | /* 表单提示信息 */
659 | .form-hint {
660 | font-size: 0.8em;
661 | color: #ff6b6b;
662 | margin-top: 5px;
663 | margin-bottom: 0;
664 | }
665 |
666 | /* 禁用的表单元素 */
667 | input:disabled {
668 | background-color: #f5f5f5;
669 | cursor: not-allowed;
670 | }
--------------------------------------------------------------------------------
/static/js/dashboard.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', function() {
2 | // 检查登录状态
3 | if (!localStorage.getItem('token')) {
4 | window.location.href = '/';
5 | return;
6 | }
7 |
8 | // 获取DOM元素
9 | const logoutBtn = document.getElementById('logout-btn');
10 | const navItems = document.querySelectorAll('.nav-menu li');
11 | const panels = document.querySelectorAll('.panel');
12 | const startProxyBtn = document.getElementById('start-proxy');
13 | const stopProxyBtn = document.getElementById('stop-proxy');
14 | const addUserForm = document.getElementById('add-user-form');
15 | const settingsForm = document.getElementById('settings-form');
16 | const resetStatsBtn = document.getElementById('reset-stats');
17 | const addPortForm = document.getElementById('add-port-form');
18 | const portsList = document.getElementById('ports-list');
19 |
20 | // 初始化页面
21 | updateProxyStatus();
22 | loadUsers();
23 | loadSettings();
24 | loadStats();
25 | loadProxyPorts();
26 |
27 | // 设置定时刷新
28 | setInterval(updateProxyStatus, 5000);
29 | setInterval(loadStats, 10000);
30 | setInterval(loadProxyPorts, 5000);
31 |
32 | // 监听无需认证复选框变化
33 | document.getElementById('no-auth-user').addEventListener('change', function() {
34 | const passwordInput = document.getElementById('new-password');
35 | if (this.checked) {
36 | passwordInput.disabled = true;
37 | passwordInput.required = false;
38 | passwordInput.value = '';
39 | } else {
40 | passwordInput.disabled = false;
41 | passwordInput.required = true;
42 | }
43 | });
44 |
45 | // 监听代理转发复选框变化
46 | document.getElementById('use-forward-proxy').addEventListener('change', function() {
47 | const remoteProxySettings = document.getElementById('remote-proxy-settings');
48 | if (this.checked) {
49 | remoteProxySettings.style.display = 'block';
50 | } else {
51 | remoteProxySettings.style.display = 'none';
52 | }
53 | });
54 |
55 | // 处理导航菜单点击
56 | navItems.forEach(item => {
57 | item.addEventListener('click', function() {
58 | const tabId = this.getAttribute('data-tab');
59 |
60 | // 更新活动标签
61 | navItems.forEach(nav => nav.classList.remove('active'));
62 | this.classList.add('active');
63 |
64 | // 显示对应面板
65 | panels.forEach(panel => {
66 | if (panel.id === tabId + '-panel') {
67 | panel.classList.add('active');
68 | } else {
69 | panel.classList.remove('active');
70 | }
71 | });
72 | });
73 | });
74 |
75 | // 处理退出登录
76 | logoutBtn.addEventListener('click', function() {
77 | localStorage.removeItem('token');
78 | window.location.href = '/';
79 | });
80 |
81 | // 处理启动代理服务器
82 | startProxyBtn.addEventListener('click', function() {
83 | fetch('/api/proxy/start', {
84 | method: 'POST',
85 | headers: {
86 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
87 | }
88 | })
89 | .then(response => response.json())
90 | .then(data => {
91 | if (data.success) {
92 | updateProxyStatus();
93 | } else {
94 | alert('启动服务失败: ' + data.message);
95 | }
96 | })
97 | .catch(error => {
98 | console.error('启动服务请求失败:', error);
99 | alert('启动服务请求失败,请稍后重试');
100 | });
101 | });
102 |
103 | // 处理停止代理服务器
104 | stopProxyBtn.addEventListener('click', function() {
105 | fetch('/api/proxy/stop', {
106 | method: 'POST',
107 | headers: {
108 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
109 | }
110 | })
111 | .then(response => response.json())
112 | .then(data => {
113 | if (data.success) {
114 | updateProxyStatus();
115 | } else {
116 | alert('停止服务失败: ' + data.message);
117 | }
118 | })
119 | .catch(error => {
120 | console.error('停止服务请求失败:', error);
121 | alert('停止服务请求失败,请稍后重试');
122 | });
123 | });
124 |
125 | // 处理添加用户
126 | addUserForm.addEventListener('submit', function(e) {
127 | e.preventDefault();
128 |
129 | const username = document.getElementById('new-username').value;
130 | const password = document.getElementById('new-password').value;
131 | const noAuth = document.getElementById('no-auth-user').checked;
132 |
133 | // 如果选择了"无需认证"但未填写用户名,则提示错误
134 | if (noAuth && !username.trim()) {
135 | alert('即使是无需认证的用户,也需要设置一个用户名作为标识');
136 | return;
137 | }
138 |
139 | // 如果未选择"无需认证",但未填写密码,则提示错误
140 | if (!noAuth && !password.trim()) {
141 | alert('请输入用户密码');
142 | return;
143 | }
144 |
145 | fetch('/api/users', {
146 | method: 'POST',
147 | headers: {
148 | 'Content-Type': 'application/json',
149 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
150 | },
151 | body: JSON.stringify({
152 | username: username,
153 | password: noAuth ? "" : password,
154 | no_auth: noAuth
155 | })
156 | })
157 | .then(response => response.json())
158 | .then(data => {
159 | if (data.success) {
160 | // 重置表单
161 | addUserForm.reset();
162 | // 刷新用户列表
163 | loadUsers();
164 | } else {
165 | alert('添加用户失败: ' + data.message);
166 | }
167 | })
168 | .catch(error => {
169 | console.error('添加用户请求失败:', error);
170 | alert('添加用户请求失败,请稍后重试');
171 | });
172 | });
173 |
174 | // 处理添加代理端口
175 | addPortForm.addEventListener('submit', function(e) {
176 | e.preventDefault();
177 |
178 | const listenAddr = document.getElementById('new-port-addr').value;
179 | const allowAnonymous = document.getElementById('new-port-anonymous').checked;
180 |
181 | // 验证监听地址
182 | if (!listenAddr.trim()) {
183 | alert('监听地址不能为空');
184 | return;
185 | }
186 |
187 | fetch('/api/proxy/ports', {
188 | method: 'POST',
189 | headers: {
190 | 'Content-Type': 'application/json',
191 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
192 | },
193 | body: JSON.stringify({
194 | listen_addr: listenAddr,
195 | allow_anonymous: allowAnonymous
196 | })
197 | })
198 | .then(response => response.json())
199 | .then(data => {
200 | if (data.success) {
201 | // 重置表单
202 | addPortForm.reset();
203 | // 刷新端口列表
204 | loadProxyPorts();
205 | } else {
206 | alert('添加代理端口失败: ' + data.message);
207 | }
208 | })
209 | .catch(error => {
210 | console.error('添加代理端口请求失败:', error);
211 | alert('添加代理端口请求失败,请稍后重试');
212 | });
213 | });
214 |
215 | // 处理保存设置
216 | settingsForm.addEventListener('submit', function(e) {
217 | e.preventDefault();
218 |
219 | const proxyPort = document.getElementById('proxy-port').value;
220 | const webPort = document.getElementById('web-port').value;
221 | const adminUsername = document.getElementById('admin-username').value;
222 | const adminPassword = document.getElementById('admin-password').value;
223 | const useForwardProxy = document.getElementById('use-forward-proxy').checked;
224 | const allowAnonymous = document.getElementById('allow-anonymous').checked;
225 | const remoteProxyAddr = document.getElementById('remote-proxy-addr').value;
226 | const remoteProxyUser = document.getElementById('remote-proxy-user').value;
227 | const remoteProxyPass = document.getElementById('remote-proxy-pass').value;
228 |
229 | // 如果启用转发代理但未填写代理地址,显示提示
230 | if (useForwardProxy && !remoteProxyAddr) {
231 | alert('启用代理转发时,远程代理地址不能为空');
232 | return;
233 | }
234 |
235 | fetch('/api/settings', {
236 | method: 'POST',
237 | headers: {
238 | 'Content-Type': 'application/json',
239 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
240 | },
241 | body: JSON.stringify({
242 | proxy_port: proxyPort,
243 | web_port: webPort,
244 | admin_username: adminUsername,
245 | admin_password: adminPassword,
246 | use_forward_proxy: useForwardProxy,
247 | allow_anonymous: allowAnonymous,
248 | remote_proxy_addr: remoteProxyAddr,
249 | remote_proxy_user: remoteProxyUser,
250 | remote_proxy_pass: remoteProxyPass
251 | })
252 | })
253 | .then(response => response.json())
254 | .then(data => {
255 | if (data.success) {
256 | alert('设置已保存');
257 | // 刷新设置
258 | loadSettings();
259 | // 刷新代理状态
260 | updateProxyStatus();
261 | // 刷新端口列表
262 | loadProxyPorts();
263 | } else {
264 | alert('保存设置失败: ' + data.message);
265 | }
266 | })
267 | .catch(error => {
268 | console.error('保存设置请求失败:', error);
269 | alert('保存设置请求失败,请稍后重试');
270 | });
271 | });
272 |
273 | // 处理重置统计
274 | resetStatsBtn.addEventListener('click', function() {
275 | if (confirm('确定要重置所有统计数据吗?')) {
276 | fetch('/api/stats/reset', {
277 | method: 'POST',
278 | headers: {
279 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
280 | }
281 | })
282 | .then(response => response.json())
283 | .then(data => {
284 | if (data.success) {
285 | loadStats();
286 | } else {
287 | alert('重置统计失败: ' + data.message);
288 | }
289 | })
290 | .catch(error => {
291 | console.error('重置统计请求失败:', error);
292 | alert('重置统计请求失败,请稍后重试');
293 | });
294 | }
295 | });
296 |
297 | // 更新代理状态
298 | function updateProxyStatus() {
299 | fetch('/api/proxy/status', {
300 | headers: {
301 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
302 | }
303 | })
304 | .then(response => response.json())
305 | .then(data => {
306 | if (data.success) {
307 | const status = data.data;
308 | const statusElement = document.getElementById('proxy-status');
309 | const addressElement = document.getElementById('proxy-address');
310 | const startTimeElement = document.getElementById('start-time');
311 | const connectionCountElement = document.getElementById('connection-count');
312 |
313 | // 更新状态显示
314 | statusElement.textContent = status.running ? '运行中' : '已停止';
315 | statusElement.className = 'value status-indicator ' + (status.running ? 'running' : 'stopped');
316 |
317 | addressElement.textContent = status.address;
318 |
319 | if (status.running) {
320 | startTimeElement.textContent = new Date(status.start_time).toLocaleString();
321 | connectionCountElement.textContent = status.connection_count;
322 |
323 | // 更新按钮状态
324 | startProxyBtn.disabled = true;
325 | stopProxyBtn.disabled = false;
326 | } else {
327 | startTimeElement.textContent = '-';
328 | connectionCountElement.textContent = '0';
329 |
330 | // 更新按钮状态
331 | startProxyBtn.disabled = false;
332 | stopProxyBtn.disabled = true;
333 | }
334 | }
335 | })
336 | .catch(error => {
337 | console.error('获取代理状态失败:', error);
338 | });
339 | }
340 |
341 | // 加载用户列表
342 | function loadUsers() {
343 | fetch('/api/users', {
344 | headers: {
345 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
346 | }
347 | })
348 | .then(response => response.json())
349 | .then(data => {
350 | if (data.success) {
351 | const userList = document.getElementById('user-list');
352 | userList.innerHTML = '';
353 |
354 | data.data.forEach(user => {
355 | const row = document.createElement('tr');
356 |
357 | const usernameCell = document.createElement('td');
358 | usernameCell.textContent = user.username;
359 |
360 | const passwordCell = document.createElement('td');
361 | passwordCell.textContent = user.no_auth ? '无需认证' : '••••••••';
362 |
363 | const actionCell = document.createElement('td');
364 | const deleteBtn = document.createElement('button');
365 | deleteBtn.textContent = '删除';
366 | deleteBtn.className = 'btn-delete';
367 | deleteBtn.addEventListener('click', function() {
368 | deleteUser(user.username);
369 | });
370 | actionCell.appendChild(deleteBtn);
371 |
372 | row.appendChild(usernameCell);
373 | row.appendChild(passwordCell);
374 | row.appendChild(actionCell);
375 |
376 | userList.appendChild(row);
377 | });
378 | }
379 | })
380 | .catch(error => {
381 | console.error('获取用户列表失败:', error);
382 | });
383 | }
384 |
385 | // 删除用户
386 | function deleteUser(username) {
387 | if (confirm(`确定要删除用户 "${username}" 吗?`)) {
388 | fetch(`/api/users/${username}`, {
389 | method: 'DELETE',
390 | headers: {
391 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
392 | }
393 | })
394 | .then(response => response.json())
395 | .then(data => {
396 | if (data.success) {
397 | loadUsers();
398 | } else {
399 | alert('删除用户失败: ' + data.message);
400 | }
401 | })
402 | .catch(error => {
403 | console.error('删除用户请求失败:', error);
404 | alert('删除用户请求失败,请稍后重试');
405 | });
406 | }
407 | }
408 |
409 | // 加载设置
410 | function loadSettings() {
411 | fetch('/api/settings', {
412 | headers: {
413 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
414 | }
415 | })
416 | .then(response => response.json())
417 | .then(data => {
418 | if (data.success) {
419 | const settings = data.data;
420 |
421 | document.getElementById('proxy-port').value = settings.proxy_port;
422 | document.getElementById('web-port').value = settings.web_port;
423 | document.getElementById('admin-username').value = settings.admin_username;
424 | document.getElementById('use-forward-proxy').checked = settings.use_forward_proxy || false;
425 | document.getElementById('allow-anonymous').checked = settings.allow_anonymous || false;
426 |
427 | // 设置远程代理信息
428 | if (settings.remote_proxy_addr) {
429 | document.getElementById('remote-proxy-addr').value = settings.remote_proxy_addr;
430 | }
431 | if (settings.remote_proxy_user) {
432 | document.getElementById('remote-proxy-user').value = settings.remote_proxy_user;
433 | }
434 |
435 | // 显示/隐藏远程代理设置区域
436 | const remoteProxySettings = document.getElementById('remote-proxy-settings');
437 | remoteProxySettings.style.display = settings.use_forward_proxy ? 'block' : 'none';
438 | }
439 | })
440 | .catch(error => {
441 | console.error('获取设置失败:', error);
442 | });
443 | }
444 |
445 | // 加载统计信息
446 | function loadStats() {
447 | fetch('/api/stats', {
448 | headers: {
449 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
450 | }
451 | })
452 | .then(response => response.json())
453 | .then(data => {
454 | if (data.success) {
455 | const stats = data.data;
456 |
457 | // 更新总请求数
458 | document.getElementById('total-requests').textContent = stats.total_requests;
459 |
460 | // 更新域名统计
461 | const domainStatsElement = document.getElementById('domain-stats');
462 | domainStatsElement.innerHTML = '';
463 |
464 | if (stats.domain_stats.length === 0) {
465 | const row = document.createElement('tr');
466 | const cell = document.createElement('td');
467 | cell.colSpan = 3;
468 | cell.textContent = '暂无数据';
469 | cell.style.textAlign = 'center';
470 | row.appendChild(cell);
471 | domainStatsElement.appendChild(row);
472 | } else {
473 | stats.domain_stats.forEach(domain => {
474 | const row = document.createElement('tr');
475 |
476 | const domainCell = document.createElement('td');
477 | domainCell.textContent = domain.domain;
478 |
479 | const countCell = document.createElement('td');
480 | countCell.textContent = domain.count;
481 |
482 | const lastVisitCell = document.createElement('td');
483 | lastVisitCell.textContent = new Date(domain.last_visit).toLocaleString();
484 |
485 | row.appendChild(domainCell);
486 | row.appendChild(countCell);
487 | row.appendChild(lastVisitCell);
488 |
489 | domainStatsElement.appendChild(row);
490 | });
491 | }
492 | }
493 | })
494 | .catch(error => {
495 | console.error('获取统计信息失败:', error);
496 | });
497 | }
498 |
499 | // 加载代理端口列表
500 | function loadProxyPorts() {
501 | fetch('/api/proxy/ports', {
502 | headers: {
503 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
504 | }
505 | })
506 | .then(response => response.json())
507 | .then(data => {
508 | if (data.success) {
509 | const portsList = document.getElementById('ports-list');
510 | portsList.innerHTML = '';
511 |
512 | data.data.forEach(port => {
513 | const row = document.createElement('tr');
514 |
515 | const addrCell = document.createElement('td');
516 | addrCell.textContent = port.listen_addr;
517 |
518 | const statusCell = document.createElement('td');
519 | const statusIndicator = document.createElement('span');
520 | statusIndicator.className = 'status-indicator ' + (port.running ? 'running' : 'stopped');
521 | statusIndicator.textContent = port.running ? '运行中' : '已停止';
522 | statusCell.appendChild(statusIndicator);
523 |
524 | const connCell = document.createElement('td');
525 | connCell.textContent = port.connection_count || 0;
526 |
527 | const actionCell = document.createElement('td');
528 |
529 | // 启动/停止按钮
530 | const toggleBtn = document.createElement('button');
531 | toggleBtn.className = 'btn-action ' + (port.running ? 'btn-stop' : 'btn-start');
532 | toggleBtn.textContent = port.running ? '停止' : '启动';
533 | toggleBtn.addEventListener('click', function() {
534 | if (port.running) {
535 | stopProxyPort(port.id);
536 | } else {
537 | startProxyPort(port.id);
538 | }
539 | });
540 |
541 | // 编辑按钮
542 | const editBtn = document.createElement('button');
543 | editBtn.className = 'btn-edit';
544 | editBtn.textContent = '编辑';
545 | editBtn.addEventListener('click', function() {
546 | // 确保传递running状态
547 | port.running = port.running;
548 | editProxyPort(port);
549 | });
550 |
551 | // 删除按钮
552 | const deleteBtn = document.createElement('button');
553 | deleteBtn.className = 'btn-delete';
554 | deleteBtn.textContent = '删除';
555 | deleteBtn.addEventListener('click', function() {
556 | deleteProxyPort(port.id);
557 | });
558 |
559 | actionCell.appendChild(toggleBtn);
560 | actionCell.appendChild(editBtn);
561 | actionCell.appendChild(deleteBtn);
562 |
563 | row.appendChild(addrCell);
564 | row.appendChild(statusCell);
565 | row.appendChild(connCell);
566 | row.appendChild(actionCell);
567 |
568 | portsList.appendChild(row);
569 | });
570 | }
571 | })
572 | .catch(error => {
573 | console.error('获取代理端口列表失败:', error);
574 | });
575 | }
576 |
577 | // 启动代理端口
578 | function startProxyPort(id) {
579 | fetch(`/api/proxy/ports/${id}/start`, {
580 | method: 'POST',
581 | headers: {
582 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
583 | }
584 | })
585 | .then(response => response.json())
586 | .then(data => {
587 | if (data.success) {
588 | loadProxyPorts();
589 | } else {
590 | alert('启动代理端口失败: ' + data.message);
591 | }
592 | })
593 | .catch(error => {
594 | console.error('启动代理端口请求失败:', error);
595 | alert('启动代理端口请求失败,请稍后重试');
596 | });
597 | }
598 |
599 | // 停止代理端口
600 | function stopProxyPort(id) {
601 | fetch(`/api/proxy/ports/${id}/stop`, {
602 | method: 'POST',
603 | headers: {
604 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
605 | }
606 | })
607 | .then(response => response.json())
608 | .then(data => {
609 | if (data.success) {
610 | loadProxyPorts();
611 | } else {
612 | alert('停止代理端口失败: ' + data.message);
613 | }
614 | })
615 | .catch(error => {
616 | console.error('停止代理端口请求失败:', error);
617 | alert('停止代理端口请求失败,请稍后重试');
618 | });
619 | }
620 |
621 | // 编辑代理端口
622 | function editProxyPort(port) {
623 | // 创建编辑对话框
624 | const dialogHTML = `
625 |
626 |
627 |
管理代理端口 - ${port.listen_addr}
628 |
629 |
630 |
基本设置
631 |
用户管理
632 |
代理转发
633 |
634 |
635 |
636 |
637 |
638 |
端口ID:
639 |
${port.id}
640 |
641 |
642 |
连接数:
643 |
${port.status ? port.status.connection_count : 0}
644 |
645 |
646 |
647 |
664 |
665 |
666 |
667 |
端口专用用户
668 |
您可以为此端口添加专用的用户账号,这些账号只能通过此端口访问代理服务。
669 |
670 |
671 |
672 |
673 | 用户名 |
674 | 密码 |
675 | 操作 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
700 |
701 |
702 |
703 |
代理转发设置
704 |
您可以将此端口的请求转发到另一个代理服务器。${port.running ? '注意:修改设置后,代理服务将自动重启以应用新设置。' : ''}
705 |
706 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 | `;
736 |
737 | // 添加对话框到DOM
738 | document.body.insertAdjacentHTML('beforeend', dialogHTML);
739 |
740 | // 监听代理转发复选框变化
741 | document.getElementById('port-use-forward-proxy').addEventListener('change', function() {
742 | const forwardSettings = document.getElementById('port-forward-settings');
743 | if (this.checked) {
744 | forwardSettings.classList.remove('disabled');
745 | } else {
746 | forwardSettings.classList.add('disabled');
747 | }
748 | });
749 |
750 | // 处理关闭按钮
751 | document.getElementById('close-port-modal').addEventListener('click', function() {
752 | const modal = document.getElementById('edit-port-modal');
753 | modal.remove();
754 | });
755 |
756 | // 处理标签切换
757 | const tabs = document.querySelectorAll('.port-detail-tab');
758 | const panels = document.querySelectorAll('.port-detail-panel');
759 |
760 | tabs.forEach(tab => {
761 | tab.addEventListener('click', function() {
762 | const tabId = this.getAttribute('data-tab');
763 |
764 | // 更新活动标签
765 | tabs.forEach(t => t.classList.remove('active'));
766 | this.classList.add('active');
767 |
768 | // 显示对应面板
769 | panels.forEach(panel => {
770 | if (panel.id === tabId + '-panel') {
771 | panel.classList.add('active');
772 | } else {
773 | panel.classList.remove('active');
774 | }
775 | });
776 |
777 | // 如果切换到代理转发选项卡,加载转发设置
778 | if (tabId === 'forward') {
779 | loadPortForwardSettings(port.id);
780 | }
781 | });
782 | });
783 |
784 | // 处理添加用户按钮点击
785 | document.getElementById('add-port-user-btn').addEventListener('click', function() {
786 | const portId = port.id;
787 | const username = document.getElementById('port-new-username').value;
788 | const password = document.getElementById('port-new-password').value;
789 | const noAuth = document.getElementById('port-no-auth-user').checked;
790 |
791 | // 如果选择了"无需认证"但未填写用户名,则提示错误
792 | if (noAuth && !username.trim()) {
793 | alert('即使是无需认证的用户,也需要设置一个用户名作为标识');
794 | return;
795 | }
796 |
797 | // 如果未选择"无需认证",但未填写密码,则提示错误
798 | if (!noAuth && !password.trim()) {
799 | alert('请输入用户密码');
800 | return;
801 | }
802 |
803 | fetch(`/api/proxy/ports/${portId}/users`, {
804 | method: 'POST',
805 | headers: {
806 | 'Content-Type': 'application/json',
807 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
808 | },
809 | body: JSON.stringify({
810 | username: username,
811 | password: noAuth ? "" : password,
812 | no_auth: noAuth
813 | })
814 | })
815 | .then(response => response.json())
816 | .then(data => {
817 | if (data.success) {
818 | // 清空表单
819 | document.getElementById('port-new-username').value = '';
820 | document.getElementById('port-new-password').value = '';
821 | document.getElementById('port-no-auth-user').checked = false;
822 |
823 | // 刷新用户列表
824 | loadPortUsers(portId);
825 | alert('用户已添加');
826 | } else {
827 | alert('添加用户失败: ' + data.message);
828 | }
829 | })
830 | .catch(error => {
831 | console.error('添加用户请求失败:', error);
832 | alert('添加用户请求失败,请稍后重试');
833 | });
834 | });
835 |
836 | // 处理保存基本设置按钮点击
837 | document.getElementById('save-port-basic').addEventListener('click', function() {
838 | const portId = document.getElementById('port-id').value;
839 | const listenAddr = document.getElementById('edit-port-addr').value;
840 | const enabled = document.getElementById('edit-port-enabled').checked;
841 | const allowAnonymous = document.getElementById('edit-port-anonymous').checked;
842 |
843 | // 验证监听地址
844 | if (!listenAddr.trim()) {
845 | alert('监听地址不能为空');
846 | return;
847 | }
848 |
849 | fetch(`/api/proxy/ports/${portId}`, {
850 | method: 'PUT',
851 | headers: {
852 | 'Content-Type': 'application/json',
853 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
854 | },
855 | body: JSON.stringify({
856 | listen_addr: listenAddr,
857 | enabled: enabled,
858 | allow_anonymous: allowAnonymous
859 | })
860 | })
861 | .then(response => response.json())
862 | .then(data => {
863 | if (data.success) {
864 | alert('基本设置已保存');
865 | // 刷新端口列表
866 | loadProxyPorts();
867 | } else {
868 | alert('更新代理端口失败: ' + data.message);
869 | }
870 | })
871 | .catch(error => {
872 | console.error('更新代理端口请求失败:', error);
873 | alert('更新代理端口请求失败,请稍后重试');
874 | });
875 | });
876 |
877 | // 处理保存转发设置按钮点击
878 | document.getElementById('save-port-forward').addEventListener('click', function() {
879 | const portId = document.getElementById('port-id-forward').value;
880 | const useForwardProxy = document.getElementById('port-use-forward-proxy').checked;
881 | const remoteProxyAddr = document.getElementById('port-remote-proxy-addr').value;
882 | const remoteProxyUser = document.getElementById('port-remote-proxy-user').value;
883 | const remoteProxyPass = document.getElementById('port-remote-proxy-pass').value;
884 |
885 | console.log("保存转发设置:", {
886 | portId,
887 | useForwardProxy,
888 | remoteProxyAddr,
889 | remoteProxyUser,
890 | remoteProxyPass: remoteProxyPass ? "已设置" : "未设置"
891 | });
892 |
893 | // 如果启用转发代理但未填写代理地址,显示提示
894 | if (useForwardProxy && !remoteProxyAddr) {
895 | alert('启用代理转发时,远程代理地址不能为空');
896 | return;
897 | }
898 |
899 | fetch(`/api/proxy/ports/${portId}/forward`, {
900 | method: 'POST',
901 | headers: {
902 | 'Content-Type': 'application/json',
903 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
904 | },
905 | body: JSON.stringify({
906 | use_forward_proxy: useForwardProxy,
907 | remote_proxy_addr: remoteProxyAddr,
908 | remote_proxy_user: remoteProxyUser,
909 | remote_proxy_pass: remoteProxyPass
910 | })
911 | })
912 | .then(response => {
913 | console.log("响应状态:", response.status);
914 | return response.json();
915 | })
916 | .then(data => {
917 | console.log("响应数据:", data);
918 | if (data.success) {
919 | if (data.data && data.data.restarted) {
920 | alert('代理转发设置已保存,代理服务已重启以应用新设置');
921 | } else {
922 | alert('代理转发设置已保存');
923 | }
924 | // 刷新端口列表以显示最新状态
925 | loadProxyPorts();
926 | } else {
927 | alert('更新代理转发设置失败: ' + data.message);
928 | }
929 | })
930 | .catch(error => {
931 | console.error('更新代理转发设置请求失败:', error);
932 | alert('更新代理转发设置请求失败,请稍后重试');
933 | });
934 | });
935 |
936 | // 监听无需认证复选框变化
937 | document.getElementById('port-no-auth-user').addEventListener('change', function() {
938 | const passwordInput = document.getElementById('port-new-password');
939 | if (this.checked) {
940 | passwordInput.disabled = true;
941 | passwordInput.required = false;
942 | passwordInput.value = '';
943 | } else {
944 | passwordInput.disabled = false;
945 | passwordInput.required = true;
946 | }
947 | });
948 |
949 | // 加载端口专用用户
950 | loadPortUsers(port.id);
951 |
952 | // 加载端口代理转发设置
953 | loadPortForwardSettings(port.id);
954 | }
955 |
956 | // 加载端口代理转发设置
957 | function loadPortForwardSettings(portId) {
958 | fetch(`/api/proxy/ports/${portId}/forward`, {
959 | headers: {
960 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
961 | }
962 | })
963 | .then(response => response.json())
964 | .then(data => {
965 | if (data.success) {
966 | const settings = data.data;
967 |
968 | // 更新表单字段
969 | document.getElementById('port-use-forward-proxy').checked = settings.use_forward_proxy;
970 |
971 | if (settings.remote_proxy_addr) {
972 | document.getElementById('port-remote-proxy-addr').value = settings.remote_proxy_addr;
973 | }
974 |
975 | if (settings.remote_proxy_user) {
976 | document.getElementById('port-remote-proxy-user').value = settings.remote_proxy_user;
977 | }
978 |
979 | // 更新转发设置区域的显示状态
980 | const forwardSettings = document.getElementById('port-forward-settings');
981 | if (settings.use_forward_proxy) {
982 | forwardSettings.classList.remove('disabled');
983 | } else {
984 | forwardSettings.classList.add('disabled');
985 | }
986 | }
987 | })
988 | .catch(error => {
989 | console.error('获取端口代理转发设置失败:', error);
990 | });
991 | }
992 |
993 | // 加载端口专用用户
994 | function loadPortUsers(portId) {
995 | fetch(`/api/proxy/ports/${portId}/users`, {
996 | headers: {
997 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
998 | }
999 | })
1000 | .then(response => response.json())
1001 | .then(data => {
1002 | if (data.success) {
1003 | const usersList = document.getElementById('port-users-list');
1004 | usersList.innerHTML = '';
1005 |
1006 | if (data.data.length === 0) {
1007 | const row = document.createElement('tr');
1008 | const cell = document.createElement('td');
1009 | cell.colSpan = 3;
1010 | cell.style.textAlign = 'center';
1011 | cell.textContent = '暂无专用用户';
1012 | row.appendChild(cell);
1013 | usersList.appendChild(row);
1014 | } else {
1015 | data.data.forEach(user => {
1016 | const row = document.createElement('tr');
1017 |
1018 | const usernameCell = document.createElement('td');
1019 | usernameCell.textContent = user.username;
1020 |
1021 | const passwordCell = document.createElement('td');
1022 | passwordCell.textContent = user.no_auth ? '无需密码' : '******';
1023 |
1024 | const actionCell = document.createElement('td');
1025 | const deleteBtn = document.createElement('button');
1026 | deleteBtn.textContent = '删除';
1027 | deleteBtn.className = 'btn-delete';
1028 | deleteBtn.addEventListener('click', function() {
1029 | deletePortUser(portId, user.username);
1030 | });
1031 | actionCell.appendChild(deleteBtn);
1032 |
1033 | row.appendChild(usernameCell);
1034 | row.appendChild(passwordCell);
1035 | row.appendChild(actionCell);
1036 |
1037 | usersList.appendChild(row);
1038 | });
1039 | }
1040 | }
1041 | })
1042 | .catch(error => {
1043 | console.error('获取端口用户列表失败:', error);
1044 | });
1045 | }
1046 |
1047 | // 删除端口专用用户
1048 | function deletePortUser(portId, username) {
1049 | if (confirm(`确定要删除用户 "${username}" 吗?`)) {
1050 | fetch(`/api/proxy/ports/${portId}/users/${username}`, {
1051 | method: 'DELETE',
1052 | headers: {
1053 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
1054 | }
1055 | })
1056 | .then(response => response.json())
1057 | .then(data => {
1058 | if (data.success) {
1059 | loadPortUsers(portId);
1060 | alert('用户已删除');
1061 | } else {
1062 | alert('删除用户失败: ' + data.message);
1063 | }
1064 | })
1065 | .catch(error => {
1066 | console.error('删除用户请求失败:', error);
1067 | alert('删除用户请求失败,请稍后重试');
1068 | });
1069 | }
1070 | }
1071 |
1072 | // 删除代理端口
1073 | function deleteProxyPort(id) {
1074 | if (confirm('确定要删除此代理端口吗?')) {
1075 | fetch(`/api/proxy/ports/${id}`, {
1076 | method: 'DELETE',
1077 | headers: {
1078 | 'Authorization': 'Bearer ' + localStorage.getItem('token')
1079 | }
1080 | })
1081 | .then(response => response.json())
1082 | .then(data => {
1083 | if (data.success) {
1084 | loadProxyPorts();
1085 | } else {
1086 | alert('删除代理端口失败: ' + data.message);
1087 | }
1088 | })
1089 | .catch(error => {
1090 | console.error('删除代理端口请求失败:', error);
1091 | alert('删除代理端口请求失败,请稍后重试');
1092 | });
1093 | }
1094 | }
1095 | });
--------------------------------------------------------------------------------
/static/js/login.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', function() {
2 | const loginForm = document.getElementById('login-form');
3 | const errorMessage = document.getElementById('error-message');
4 |
5 | // 检查是否已登录
6 | if (localStorage.getItem('token')) {
7 | window.location.href = '/dashboard';
8 | return;
9 | }
10 |
11 | // 处理登录表单提交
12 | loginForm.addEventListener('submit', function(e) {
13 | e.preventDefault();
14 |
15 | const username = document.getElementById('username').value;
16 | const password = document.getElementById('password').value;
17 |
18 | // 发送登录请求
19 | fetch('/api/login', {
20 | method: 'POST',
21 | headers: {
22 | 'Content-Type': 'application/json'
23 | },
24 | body: JSON.stringify({
25 | username: username,
26 | password: password
27 | })
28 | })
29 | .then(response => response.json())
30 | .then(data => {
31 | if (data.success) {
32 | // 保存登录令牌
33 | localStorage.setItem('token', data.token);
34 | // 跳转到仪表盘
35 | window.location.href = '/dashboard';
36 | } else {
37 | // 显示错误信息
38 | errorMessage.textContent = data.message || '登录失败,请检查用户名和密码';
39 | }
40 | })
41 | .catch(error => {
42 | console.error('登录请求失败:', error);
43 | errorMessage.textContent = '登录请求失败,请稍后重试';
44 | });
45 | });
46 | });
--------------------------------------------------------------------------------
/templates/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 代理服务器管理面板
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
服务器状态
32 |
33 |
34 | 运行状态:
35 | 停止
36 |
37 |
38 | 监听地址:
39 | 0.0.0.0:8080
40 |
41 |
42 | 启动时间:
43 | -
44 |
45 |
46 | 总连接数:
47 | 0
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 |
90 |
91 |
92 |
93 |
94 |
95 |
用户管理
96 |
97 |
98 |
99 |
100 | 用户名 |
101 | 密码 |
102 | 操作 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
127 |
128 |
129 |
130 |
131 |
175 |
176 |
177 |
178 |
访问统计
179 |
180 |
184 |
185 |
域名统计
186 |
187 |
188 |
189 |
190 | 域名 |
191 | 请求次数 |
192 | 最后访问时间 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/templates/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 代理服务器管理登录
7 |
8 |
9 |
10 |
11 |
12 |
代理服务器管理系统
13 |
14 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/test_proxy.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | headers = {
4 |
5 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
6 | }
7 |
8 | response = requests.get('https://www.baidu.com/', headers=headers, proxies={'https':'http://xxx:xxx@127.0.0.1:8080'})
9 | print(response.content)
--------------------------------------------------------------------------------