BOHE commited on
Commit
2a72108
·
0 Parent(s):

first commit

Browse files
Files changed (15) hide show
  1. .dockerignore +7 -0
  2. .gitignore +32 -0
  3. Dockerfile +41 -0
  4. README.md +2 -0
  5. api/main.go +330 -0
  6. config/config.go +49 -0
  7. config/proxy_config.go +16 -0
  8. docker-compose.yml +35 -0
  9. go.mod +3 -0
  10. logger/logger.go +33 -0
  11. metrics/metrics.go +19 -0
  12. prometheus.yml +7 -0
  13. proxy/proxy.go +46 -0
  14. start.go +48 -0
  15. start_test.go +25 -0
.dockerignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .git
2
+ .gitignore
3
+ README.md
4
+ Dockerfile
5
+ docker-compose.yml
6
+ *.md
7
+ .env
.gitignore ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 二进制文件
2
+ *.exe
3
+ *.exe~
4
+ *.dll
5
+ *.so
6
+ *.dylib
7
+ main
8
+
9
+ # 测试文件
10
+ *.test
11
+ *.tmp
12
+
13
+ # 输出文件
14
+ *.out
15
+
16
+ # 依赖目录
17
+ /vendor/
18
+
19
+ # IDE 相关
20
+ .idea/
21
+ .vscode/
22
+ *.swp
23
+ *.swo
24
+
25
+ # 环境变量文件
26
+ .env
27
+
28
+ # 日志文件
29
+ *.log
30
+
31
+ # macOS 系统文件
32
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Go 镜像作为构建环境
2
+ FROM golang:1.21-alpine AS builder
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 复制 go.mod 和 go.sum
8
+ COPY go.mod go.sum ./
9
+
10
+ # 下载依赖
11
+ RUN go mod download
12
+
13
+ # 复制源代码
14
+ COPY . .
15
+
16
+ # 构建应用
17
+ RUN CGO_ENABLED=0 GOOS=linux go build -o main .
18
+
19
+ # 使用轻量级的 alpine 作为运行环境
20
+ FROM alpine:latest
21
+
22
+ # 安装 ca-certificates
23
+ RUN apk --no-cache add ca-certificates
24
+
25
+ WORKDIR /root/
26
+
27
+ # 从构建阶段复制二进制文件
28
+ COPY --from=builder /app/main .
29
+
30
+ # 设置环境变量
31
+ ENV PORT=8080
32
+ ENV ENABLE_PROXY=false
33
+ ENV PROXY_URL=""
34
+ ENV PROXY_TIMEOUT_MS=5000
35
+ ENV LOG_LEVEL=info
36
+
37
+ # 暴露端口
38
+ EXPOSE 8080
39
+
40
+ # 运行应用
41
+ CMD ["./main"]
README.md ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ 懂得都懂
2
+ 自己研究
api/main.go ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handler
2
+
3
+ import (
4
+ "bufio"
5
+ "encoding/json"
6
+ "fmt"
7
+ "net/http"
8
+ "strings"
9
+ "time"
10
+ )
11
+
12
+ type YouChatResponse struct {
13
+ YouChatToken string `json:"youChatToken"`
14
+ }
15
+
16
+ type OpenAIStreamResponse struct {
17
+ ID string `json:"id"`
18
+ Object string `json:"object"`
19
+ Created int64 `json:"created"`
20
+ Model string `json:"model"`
21
+ Choices []Choice `json:"choices"`
22
+ }
23
+
24
+ type Choice struct {
25
+ Delta Delta `json:"delta"`
26
+ Index int `json:"index"`
27
+ FinishReason string `json:"finish_reason"`
28
+ }
29
+
30
+ type Delta struct {
31
+ Content string `json:"content"`
32
+ }
33
+
34
+ type OpenAIRequest struct {
35
+ Messages []Message `json:"messages"`
36
+ Stream bool `json:"stream"`
37
+ Model string `json:"model"`
38
+ }
39
+
40
+ type Message struct {
41
+ Role string `json:"role"`
42
+ Content string `json:"content"`
43
+ }
44
+
45
+ type OpenAIResponse struct {
46
+ ID string `json:"id"`
47
+ Object string `json:"object"`
48
+ Created int64 `json:"created"`
49
+ Model string `json:"model"`
50
+ Choices []OpenAIChoice `json:"choices"`
51
+ }
52
+
53
+ type OpenAIChoice struct {
54
+ Message Message `json:"message"`
55
+ Index int `json:"index"`
56
+ FinishReason string `json:"finish_reason"`
57
+ }
58
+
59
+ var modelMap = map[string]string{
60
+ "deepseek-reasoner": "deepseek_r1",
61
+ "deepseek-chat": "deepseek_v3",
62
+ "o3-mini-high": "openai_o3_mini_high",
63
+ "o3-mini-medium": "openai_o3_mini_medium",
64
+ "o1": "openai_o1",
65
+ "o1-mini": "openai_o1_mini",
66
+ "o1-preview": "openai_o1_preview",
67
+ "gpt-4o": "gpt_4o",
68
+ "gpt-4o-mini": "gpt_4o_mini",
69
+ "gpt-4-turbo": "gpt_4_turbo",
70
+ "gpt-3.5-turbo": "gpt_3.5",
71
+ "claude-3-opus": "claude_3_opus",
72
+ "claude-3-sonnet": "claude_3_sonnet",
73
+ "claude-3.5-sonnet": "claude_3_5_sonnet",
74
+ "claude-3.5-haiku": "claude_3_5_haiku",
75
+ "gemini-1.5-pro": "gemini_1_5_pro",
76
+ "gemini-1.5-flash": "gemini_1_5_flash",
77
+ "llama-3.2-90b": "llama3_2_90b",
78
+ "llama-3.1-405b": "llama3_1_405b",
79
+ "mistral-large-2": "mistral_large_2",
80
+ "qwen-2.5-72b": "qwen2p5_72b",
81
+ "qwen-2.5-coder-32b": "qwen2p5_coder_32b",
82
+ "command-r-plus": "command_r_plus",
83
+ }
84
+
85
+ func getReverseModelMap() map[string]string {
86
+ reverse := make(map[string]string, len(modelMap))
87
+ for k, v := range modelMap {
88
+ reverse[v] = k
89
+ }
90
+ return reverse
91
+ }
92
+
93
+ func mapModelName(openAIModel string) string {
94
+ if mappedModel, exists := modelMap[openAIModel]; exists {
95
+ return mappedModel
96
+ }
97
+ return "deepseek_v3"
98
+ }
99
+
100
+ func reverseMapModelName(youModel string) string {
101
+ reverseMap := getReverseModelMap()
102
+ if mappedModel, exists := reverseMap[youModel]; exists {
103
+ return mappedModel
104
+ }
105
+ return "deepseek-chat"
106
+ }
107
+
108
+ var originalModel string
109
+
110
+ func Handler(w http.ResponseWriter, r *http.Request) {
111
+ if r.URL.Path != "/v1/chat/completions" {
112
+ w.Header().Set("Content-Type", "application/json")
113
+ json.NewEncoder(w).Encode(map[string]string{
114
+ "status": "You2Api Service Running...",
115
+ "message": "MoLoveSze...",
116
+ })
117
+ return
118
+ }
119
+
120
+ w.Header().Set("Access-Control-Allow-Origin", "*")
121
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
122
+ w.Header().Set("Access-Control-Allow-Headers", "*")
123
+
124
+ if r.Method == "OPTIONS" {
125
+ w.WriteHeader(http.StatusOK)
126
+ return
127
+ }
128
+
129
+ authHeader := r.Header.Get("Authorization")
130
+ if !strings.HasPrefix(authHeader, "Bearer ") {
131
+ http.Error(w, "Missing or invalid authorization header", http.StatusUnauthorized)
132
+ return
133
+ }
134
+ dsToken := strings.TrimPrefix(authHeader, "Bearer ")
135
+
136
+ var openAIReq OpenAIRequest
137
+ if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil {
138
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
139
+ return
140
+ }
141
+
142
+ originalModel = openAIReq.Model
143
+ lastMessage := openAIReq.Messages[len(openAIReq.Messages)-1].Content
144
+ var chatHistory []map[string]interface{}
145
+ for _, msg := range openAIReq.Messages {
146
+ chatMsg := map[string]interface{}{
147
+ "question": msg.Content,
148
+ "answer": "",
149
+ }
150
+ if msg.Role == "assistant" {
151
+ chatMsg["question"] = ""
152
+ chatMsg["answer"] = msg.Content
153
+ }
154
+ chatHistory = append(chatHistory, chatMsg)
155
+ }
156
+
157
+ chatHistoryJSON, _ := json.Marshal(chatHistory)
158
+
159
+ youReq, _ := http.NewRequest("GET", "https://you.com/api/streamingSearch", nil)
160
+
161
+ q := youReq.URL.Query()
162
+ q.Add("q", lastMessage)
163
+ q.Add("page", "1")
164
+ q.Add("count", "10")
165
+ q.Add("safeSearch", "Moderate")
166
+ q.Add("mkt", "zh-HK")
167
+ q.Add("enable_worklow_generation_ux", "true")
168
+ q.Add("domain", "youchat")
169
+ q.Add("use_personalization_extraction", "true")
170
+ q.Add("pastChatLength", fmt.Sprintf("%d", len(chatHistory)-1))
171
+ q.Add("selectedChatMode", "custom")
172
+ q.Add("selectedAiModel", mapModelName(openAIReq.Model))
173
+ q.Add("enable_agent_clarification_questions", "true")
174
+ q.Add("use_nested_youchat_updates", "true")
175
+ q.Add("chat", string(chatHistoryJSON))
176
+ youReq.URL.RawQuery = q.Encode()
177
+
178
+ youReq.Header = http.Header{
179
+ "sec-ch-ua-platform": {"Windows"},
180
+ "Cache-Control": {"no-cache"},
181
+ "sec-ch-ua": {`"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"`},
182
+ "sec-ch-ua-bitness": {"64"},
183
+ "sec-ch-ua-model": {""},
184
+ "sec-ch-ua-mobile": {"?0"},
185
+ "sec-ch-ua-arch": {"x86"},
186
+ "sec-ch-ua-full-version": {"133.0.3065.39"},
187
+ "Accept": {"text/event-stream"},
188
+ "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 Edg/133.0.0.0"},
189
+ "sec-ch-ua-platform-version": {"19.0.0"},
190
+ "Sec-Fetch-Site": {"same-origin"},
191
+ "Sec-Fetch-Mode": {"cors"},
192
+ "Sec-Fetch-Dest": {"empty"},
193
+ "Host": {"you.com"},
194
+ }
195
+
196
+ cookies := getCookies(dsToken)
197
+ var cookieStrings []string
198
+ for name, value := range cookies {
199
+ cookieStrings = append(cookieStrings, fmt.Sprintf("%s=%s", name, value))
200
+ }
201
+ youReq.Header.Add("Cookie", strings.Join(cookieStrings, ";"))
202
+
203
+ if !openAIReq.Stream {
204
+ handleNonStreamingResponse(w, youReq)
205
+ return
206
+ }
207
+
208
+ handleStreamingResponse(w, youReq)
209
+ }
210
+
211
+ func getCookies(dsToken string) map[string]string {
212
+ return map[string]string{
213
+ "guest_has_seen_legal_disclaimer": "true",
214
+ "youchat_personalization": "true",
215
+ "DS": dsToken,
216
+ "you_subscription": "youpro_standard_year",
217
+ "youpro_subscription": "true",
218
+ "ai_model": "deepseek_r1",
219
+ "youchat_smart_learn": "true",
220
+ }
221
+ }
222
+
223
+ func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
224
+ client := &http.Client{
225
+ Timeout: 60 * time.Second,
226
+ }
227
+ resp, err := client.Do(youReq)
228
+ if err != nil {
229
+ http.Error(w, err.Error(), http.StatusInternalServerError)
230
+ return
231
+ }
232
+ defer resp.Body.Close()
233
+
234
+ var fullResponse strings.Builder
235
+ scanner := bufio.NewScanner(resp.Body)
236
+
237
+ buf := make([]byte, 0, 64*1024)
238
+ scanner.Buffer(buf, 1024*1024)
239
+
240
+ for scanner.Scan() {
241
+ line := scanner.Text()
242
+ if strings.HasPrefix(line, "event: youChatToken") {
243
+ scanner.Scan()
244
+ data := scanner.Text()
245
+ if !strings.HasPrefix(data, "data: ") {
246
+ continue
247
+ }
248
+ var token YouChatResponse
249
+ if err := json.Unmarshal([]byte(strings.TrimPrefix(data, "data: ")), &token); err != nil {
250
+ continue
251
+ }
252
+ fullResponse.WriteString(token.YouChatToken)
253
+ }
254
+ }
255
+
256
+ if scanner.Err() != nil {
257
+ http.Error(w, "Error reading response", http.StatusInternalServerError)
258
+ return
259
+ }
260
+
261
+ openAIResp := OpenAIResponse{
262
+ ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()),
263
+ Object: "chat.completion",
264
+ Created: time.Now().Unix(),
265
+ Model: reverseMapModelName(mapModelName(originalModel)),
266
+ Choices: []OpenAIChoice{
267
+ {
268
+ Message: Message{
269
+ Role: "assistant",
270
+ Content: fullResponse.String(),
271
+ },
272
+ Index: 0,
273
+ FinishReason: "stop",
274
+ },
275
+ },
276
+ }
277
+
278
+ w.Header().Set("Content-Type", "application/json")
279
+ if err := json.NewEncoder(w).Encode(openAIResp); err != nil {
280
+ http.Error(w, "Error encoding response", http.StatusInternalServerError)
281
+ return
282
+ }
283
+ }
284
+
285
+ func handleStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
286
+ client := &http.Client{}
287
+ resp, err := client.Do(youReq)
288
+ if err != nil {
289
+ http.Error(w, err.Error(), http.StatusInternalServerError)
290
+ return
291
+ }
292
+ defer resp.Body.Close()
293
+
294
+ w.Header().Set("Content-Type", "text/event-stream")
295
+ w.Header().Set("Cache-Control", "no-cache")
296
+ w.Header().Set("Connection", "keep-alive")
297
+
298
+ scanner := bufio.NewScanner(resp.Body)
299
+ for scanner.Scan() {
300
+ line := scanner.Text()
301
+
302
+ if strings.HasPrefix(line, "event: youChatToken") {
303
+ scanner.Scan()
304
+ data := scanner.Text()
305
+
306
+ var token YouChatResponse
307
+ json.Unmarshal([]byte(strings.TrimPrefix(data, "data: ")), &token)
308
+
309
+ openAIResp := OpenAIStreamResponse{
310
+ ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()),
311
+ Object: "chat.completion.chunk",
312
+ Created: time.Now().Unix(),
313
+ Model: reverseMapModelName(mapModelName(originalModel)),
314
+ Choices: []Choice{
315
+ {
316
+ Delta: Delta{
317
+ Content: token.YouChatToken,
318
+ },
319
+ Index: 0,
320
+ FinishReason: "",
321
+ },
322
+ },
323
+ }
324
+
325
+ respBytes, _ := json.Marshal(openAIResp)
326
+ fmt.Fprintf(w, "data: %s\n\n", string(respBytes))
327
+ w.(http.Flusher).Flush()
328
+ }
329
+ }
330
+ }
config/config.go ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ import (
4
+ "os"
5
+ "strconv"
6
+ )
7
+
8
+ type Config struct {
9
+ Port int `json:"port"`
10
+ LogLevel string `json:"log_level"`
11
+ Proxy ProxyConfig `json:"proxy"`
12
+ // 其他配置项...
13
+ }
14
+
15
+ func Load() (*Config, error) {
16
+ config := &Config{
17
+ Port: 8080,
18
+ LogLevel: "info",
19
+ Proxy: ProxyConfig{
20
+ EnableProxy: getEnvBool("ENABLE_PROXY", false),
21
+ ProxyURL: getEnv("PROXY_URL", ""),
22
+ ProxyTimeoutMS: getEnvInt("PROXY_TIMEOUT_MS", 5000),
23
+ },
24
+ }
25
+ return config, nil
26
+ }
27
+
28
+ func getEnv(key, defaultValue string) string {
29
+ if value, exists := os.LookupEnv(key); exists {
30
+ return value
31
+ }
32
+ return defaultValue
33
+ }
34
+
35
+ func getEnvBool(key string, defaultValue bool) bool {
36
+ if value, exists := os.LookupEnv(key); exists {
37
+ return value == "true"
38
+ }
39
+ return defaultValue
40
+ }
41
+
42
+ func getEnvInt(key string, defaultValue int) int {
43
+ if value, exists := os.LookupEnv(key); exists {
44
+ if intValue, err := strconv.Atoi(value); err == nil {
45
+ return intValue
46
+ }
47
+ }
48
+ return defaultValue
49
+ }
config/proxy_config.go ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ type ProxyConfig struct {
4
+ EnableProxy bool `json:"enable_proxy"`
5
+ ProxyURL string `json:"proxy_url"`
6
+ ProxyTimeoutMS int `json:"proxy_timeout_ms"`
7
+ }
8
+
9
+ func (c *Config) WithProxy() *Config {
10
+ c.Proxy = ProxyConfig{
11
+ EnableProxy: true,
12
+ ProxyURL: "http://your-proxy-server:8080",
13
+ ProxyTimeoutMS: 5000,
14
+ }
15
+ return c
16
+ }
docker-compose.yml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ you2api:
5
+ build: .
6
+ container_name: you2api
7
+ ports:
8
+ - "8080:8080"
9
+ environment:
10
+ - PORT=8080
11
+ - ENABLE_PROXY=false
12
+ - PROXY_URL=
13
+ - PROXY_TIMEOUT_MS=5000
14
+ - LOG_LEVEL=info
15
+ restart: unless-stopped
16
+
17
+ # 如果需要添加 Prometheus 监控
18
+ prometheus:
19
+ image: prom/prometheus:latest
20
+ container_name: prometheus
21
+ ports:
22
+ - "9090:9090"
23
+ volumes:
24
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
25
+ depends_on:
26
+ - you2api
27
+
28
+ # 如果需要添加 Grafana 监控面板
29
+ grafana:
30
+ image: grafana/grafana:latest
31
+ container_name: grafana
32
+ ports:
33
+ - "3000:3000"
34
+ depends_on:
35
+ - prometheus
go.mod ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ module you2api
2
+
3
+ go 1.22.2
logger/logger.go ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package logger
2
+
3
+ import (
4
+ "go.uber.org/zap"
5
+ "go.uber.org/zap/zapcore"
6
+ )
7
+
8
+ var log *zap.Logger
9
+
10
+ func Init(level string) error {
11
+ config := zap.NewProductionConfig()
12
+ config.Level.SetLevel(getLogLevel(level))
13
+
14
+ logger, err := config.Build()
15
+ if err != nil {
16
+ return err
17
+ }
18
+
19
+ log = logger
20
+ return nil
21
+ }
22
+
23
+ func getLogLevel(level string) zapcore.Level {
24
+ switch level {
25
+ case "debug":
26
+ return zapcore.DebugLevel
27
+ case "info":
28
+ return zapcore.InfoLevel
29
+ // ... 其他级别
30
+ default:
31
+ return zapcore.InfoLevel
32
+ }
33
+ }
metrics/metrics.go ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package metrics
2
+
3
+ import (
4
+ "github.com/prometheus/client_golang/prometheus"
5
+ )
6
+
7
+ var (
8
+ RequestCounter = prometheus.NewCounterVec(
9
+ prometheus.CounterOpts{
10
+ Name: "http_requests_total",
11
+ Help: "HTTP请求总数",
12
+ },
13
+ []string{"method", "endpoint", "status"},
14
+ )
15
+ )
16
+
17
+ func Init() {
18
+ prometheus.MustRegister(RequestCounter)
19
+ }
prometheus.yml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ global:
2
+ scrape_interval: 15s
3
+
4
+ scrape_configs:
5
+ - job_name: 'you2api'
6
+ static_configs:
7
+ - targets: ['you2api:8080']
proxy/proxy.go ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package proxy
2
+
3
+ import (
4
+ "net/http"
5
+ "net/http/httputil"
6
+ "net/url"
7
+ "time"
8
+ )
9
+
10
+ type Proxy struct {
11
+ target *url.URL
12
+ proxy *httputil.ReverseProxy
13
+ timeoutMS int
14
+ }
15
+
16
+ func NewProxy(targetURL string, timeoutMS int) (*Proxy, error) {
17
+ target, err := url.Parse(targetURL)
18
+ if err != nil {
19
+ return nil, err
20
+ }
21
+
22
+ proxy := &Proxy{
23
+ target: target,
24
+ timeoutMS: timeoutMS,
25
+ }
26
+
27
+ proxy.proxy = &httputil.ReverseProxy{
28
+ Director: proxy.director,
29
+ Transport: &http.Transport{
30
+ Proxy: http.ProxyURL(target),
31
+ ResponseHeaderTimeout: time.Duration(timeoutMS) * time.Millisecond,
32
+ },
33
+ }
34
+
35
+ return proxy, nil
36
+ }
37
+
38
+ func (p *Proxy) director(req *http.Request) {
39
+ req.URL.Scheme = p.target.Scheme
40
+ req.URL.Host = p.target.Host
41
+ req.Host = p.target.Host
42
+ }
43
+
44
+ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
45
+ p.proxy.ServeHTTP(w, r)
46
+ }
start.go ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+ "net/http"
7
+
8
+ api "you2api/api" // 请替换为您的实际项目名
9
+ config "you2api/config"
10
+ proxy "you2api/proxy"
11
+ )
12
+
13
+ func main() {
14
+ if err := run(); err != nil {
15
+ log.Fatalf("运行错误: %v", err)
16
+ }
17
+ }
18
+
19
+ func run() error {
20
+ // 加载配置
21
+ config, err := config.Load()
22
+ if err != nil {
23
+ return fmt.Errorf("加载配置失败: %w", err)
24
+ }
25
+
26
+ // 如果启用代理
27
+ if config.Proxy.EnableProxy {
28
+ proxy, err := proxy.NewProxy(config.Proxy.ProxyURL, config.Proxy.ProxyTimeoutMS)
29
+ if err != nil {
30
+ return fmt.Errorf("初始化代理失败: %w", err)
31
+ }
32
+
33
+ // 注册代理处理器
34
+ http.Handle("/proxy/", http.StripPrefix("/proxy", proxy))
35
+ }
36
+
37
+ // 注册普通API处理器
38
+ http.HandleFunc("/api/", api.Handler)
39
+
40
+ port := fmt.Sprintf(":%d", config.Port)
41
+ fmt.Printf("Server is running on http://localhost%s\n", port)
42
+
43
+ // 启动服务器
44
+ if err := http.ListenAndServe(port, nil); err != nil {
45
+ return fmt.Errorf("启动服务器失败: %w", err)
46
+ }
47
+ return nil
48
+ }
start_test.go ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "testing"
5
+ )
6
+
7
+ func TestRun(t *testing.T) {
8
+ tests := []struct {
9
+ name string
10
+ wantErr bool
11
+ }{
12
+ {
13
+ name: "正常运行",
14
+ wantErr: false,
15
+ },
16
+ }
17
+
18
+ for _, tt := range tests {
19
+ t.Run(tt.name, func(t *testing.T) {
20
+ if err := run(); (err != nil) != tt.wantErr {
21
+ t.Errorf("run() error = %v, wantErr %v", err, tt.wantErr)
22
+ }
23
+ })
24
+ }
25
+ }