package main import ( "fmt" "io" "io/fs" "log" "net" "net/http" "net/http/httputil" "net/url" "os" "path/filepath" "strings" "sync" "time" "github.com/gorilla/websocket" "gopkg.in/yaml.v2" ) // Backend结构体 var Backends []*Backend // 设置跨域请求 func SetCors(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "*") w.Header().Set("Access-Control-Expose-Headers", "*") w.Header().Set("Access-Control-Allow-Credentials", "true") // 如果请求方法为 OPTIONS,直接返回 200 状态码 if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } } // 存储路径的并发安全的Map var pathdict sync.Map = sync.Map{} func main() { // 将静态资源路径存储到pathdict pathdict.Store("/css", true) pathdict.Store("/fonts", true) pathdict.Store("/img", true) pathdict.Store("/js", true) pathdict.Store("/svg", true) pathdict.Store("/favicon.ico", true) rootDir := "../server" // 更改为你的根目录 vueBuild := "/opt/fusenpack-vue-created" apiURL, err := url.Parse("http://localhost:9900") if err != nil { panic(err) } mux := http.NewServeMux() // 获取并解析服务信息 results := GetZeroInfo(rootDir) var allRoutes map[string]bool = make(map[string]bool) for _, result := range results { fmt.Printf("FolderName: %s, Host: %s, Port: %d, PrefixRoute: %v\n", result.FolderName, result.Host, result.Port, result.PrefixRoute) var routes []string for k := range result.PrefixRoute { routes = append(routes, k) allRoutes[k] = true } // 根据获取的服务信息创建后端服务 Backends = append(Backends, NewBackend(mux, fmt.Sprintf("http://%s:%d", result.Host, result.Port), routes...)) } // 定义用于服务Vue dist文件夹的静态文件服务器 fs := http.FileServer(http.Dir(vueBuild)) indexHtmlPath := vueBuild + "/index.html" mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/") { // 对/api开头的请求进行反向代理 proxy := httputil.NewSingleHostReverseProxy(apiURL) proxy.ServeHTTP(w, r) return } else { // 根据请求路径判断是服务静态文件或者是返回index.html idx := strings.Index(r.URL.Path[1:], "/") var prefix string if idx != -1 { prefix = r.URL.Path[:idx+1] } else { prefix = r.URL.Path } if _, ok := pathdict.Load(prefix); ok { fs.ServeHTTP(w, r) } else { http.ServeFile(w, r, indexHtmlPath) } } })) ServerAddress := ":9900" log.Println("listen on ", ServerAddress) log.Fatal(http.ListenAndServe(ServerAddress, mux)) } // 后端服务的类型 type Backend struct { HttpAddress string Client *http.Client Handler http.HandlerFunc Dialer *websocket.Dialer } func NewBackend(mux *http.ServeMux, httpAddress string, muxPaths ...string) *Backend { // 如果路径最后没有以'/'结尾,则添加'/' for i, muxPath := range muxPaths { if muxPath[len(muxPath)-1] != '/' { muxPath = muxPath + "/" muxPaths[i] = muxPath } } // 创建HTTP客户端,设置相关的超时参数和连接数限制 client := &http.Client{ Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: 60 * time.Second, KeepAlive: 60 * time.Second, }).DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, } dialer := &websocket.Dialer{ Proxy: http.ProxyFromEnvironment, NetDial: func(network, addr string) (net.Conn, error) { return net.Dial(network, addr) }, } // 创建后端服务对象,包含地址和客户端 backend := &Backend{ HttpAddress: httpAddress, Client: client, Dialer: dialer, } // 创建处理请求的函数 handleRequest := func(w http.ResponseWriter, r *http.Request) { if websocket.IsWebSocketUpgrade(r) { // Handle websocket connections handleWebSocketProxy(w, r, backend) return } // 解析目标URL,包含了查询参数 targetURL, err := url.Parse(httpAddress + r.URL.String()) if err != nil { http.Error(w, "Error parsing target URL", http.StatusInternalServerError) return } // 创建新的请求 proxyReq, err := http.NewRequest(r.Method, targetURL.String(), r.Body) if err != nil { http.Error(w, "Error creating proxy request", http.StatusInternalServerError) return } // 复制原始请求的 Header for key, values := range r.Header { for _, value := range values { proxyReq.Header.Add(key, value) } } // 设置 Content-Length 和 Content-Type proxyReq.ContentLength = r.ContentLength proxyReq.Header.Set("Content-Type", r.Header.Get("Content-Type")) // 发送请求 resp, err := backend.Client.Do(proxyReq) if err != nil { http.Error(w, "Error sending proxy request", http.StatusInternalServerError) return } defer resp.Body.Close() // 复制目标服务器的响应 Header for key, values := range resp.Header { for _, value := range values { w.Header().Add(key, value) } } // 转发目标服务器的响应状态码和主体 w.WriteHeader(resp.StatusCode) _, err = io.Copy(w, resp.Body) if err != nil { http.Error(w, "Error copying proxy response", http.StatusInternalServerError) return } } // 为每个路径注册处理函数 for _, muxPath := range muxPaths { mux.HandleFunc(muxPath, handleRequest) } // 返回后端服务对象 return backend } // get_zero_info.go // Config 结构体用于解析yaml配置文件 type Config struct { Host string `yaml:"Host"` Port int `yaml:"Port"` } // Result 结构体用于存储解析结果 type Result struct { FolderName string Host string Port int PrefixRoute map[string]bool } // GetZeroInfo 遍历指定目录,并解析相关信息 func GetZeroInfo(rootDir string) (results []*Result) { entries, err := os.ReadDir(rootDir) if err != nil { log.Fatal(err) } for _, entry := range entries { // 只处理目录类型 if entry.IsDir() { result, err := findFoldersAndExtractInfo(rootDir, entry) if err != nil { log.Fatal(err) } results = append(results, result) } } return } // findFoldersAndExtractInfo 查找目录并提取信息 func findFoldersAndExtractInfo(rootDir string, entry fs.DirEntry) (*Result, error) { var result *Result folderName := entry.Name() path := filepath.Join(rootDir, folderName) err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // 跳过非目录类型 if !info.IsDir() { return nil } relPath, err := filepath.Rel(path, path) if err != nil { return err } // 跳过非当前目录的子目录 if strings.Contains(relPath, string(os.PathSeparator)) { return filepath.SkipDir } // 读取配置文件 configPath := filepath.Join(path, "etc", folderName+".yaml") routesPath := filepath.Join(path, "internal", "handler", "routes.go") configContent, err := os.ReadFile(configPath) if err != nil { return err } var config Config err = yaml.Unmarshal(configContent, &config) if err != nil { return err } // 读取路由文件 routesContent, err := os.ReadFile(routesPath) if err != nil { return err } PrefixRoute := extractPrefixRouteValues(string(routesContent)) // 构建结果 result = &Result{ FolderName: folderName, Host: config.Host, Port: config.Port, PrefixRoute: PrefixRoute, } return filepath.SkipDir }) if err != nil { return nil, err } return result, nil } // extractPrefixRouteValues 提取路由前缀 func extractPrefixRouteValues(content string) map[string]bool { lines := strings.Split(content, "\n") var prefixPath map[string]bool = make(map[string]bool) for _, line := range lines { // 查找包含 "Path:" 的行 if strings.Contains(line, "Path:") { path := strings.TrimSpace(strings.TrimPrefix(line, "Path:")) paths := strings.Split(strings.Trim(path, `"`), "/") path1 := "/" + paths[1] + "/" + paths[2] if _, ok := prefixPath[path1]; !ok { prefixPath[path1] = true } } } return prefixPath } func handleWebSocketProxy(w http.ResponseWriter, r *http.Request, backend *Backend) { target := url.URL{Scheme: "ws", Host: backend.HttpAddress, Path: r.URL.Path} proxyConn, _, err := backend.Dialer.DialContext(r.Context(), target.String(), nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer proxyConn.Close() upgrader := websocket.Upgrader{} conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } defer conn.Close() go transfer(proxyConn, conn) go transfer(conn, proxyConn) } func transfer(src, dest *websocket.Conn) { for { messageType, data, err := src.ReadMessage() if err != nil { break } err = dest.WriteMessage(messageType, data) if err != nil { break } } src.Close() dest.Close() }