package remote import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/http/httputil" "net/url" "platform/pkg/env" "platform/pkg/rds" "strconv" "strings" "time" ) // CloudClient 定义云服务接口 type CloudClient interface { CloudEdges(param CloudEdgesReq) (*CloudEdgesResp, error) CloudConnect(param CloudConnectReq) error CloudDisconnect(param CloudDisconnectReq) (int, error) CloudAutoQuery() (CloudConnectResp, error) } // GatewayClient 定义网关接口 type GatewayClient interface { GatewayPortConfigs(params []PortConfigsReq) error GatewayPortActive(param ...PortActiveReq) (map[string]PortData, error) } type cloud struct { url string } var Cloud CloudClient func Init() { Cloud = &cloud{ url: env.RemoteAddr, } } type AutoConfig struct { Province string `json:"province"` City string `json:"city"` Isp string `json:"isp"` Count int `json:"count"` } // region cloud:/edges type CloudEdgesReq struct { Province string City string Isp string Offset int Limit int } type CloudEdgesResp struct { Edges []Edge `json:"edges"` Total int `json:"total"` Offset int `json:"offset"` Limit int `json:"limit"` } type Edge struct { EdgesId int `json:"edges_id"` Province string `json:"province"` City string `json:"city"` Isp string `json:"isp"` Ip string `json:"ip"` Rtt int `json:"rtt"` PacketLoss int `json:"packet_loss"` } func (c *cloud) CloudEdges(param CloudEdgesReq) (*CloudEdgesResp, error) { data := strings.Builder{} data.WriteString("province=") data.WriteString(param.Province) data.WriteString("&city=") data.WriteString(param.City) data.WriteString("&isp=") data.WriteString(param.Isp) data.WriteString("&offset=") data.WriteString(strconv.Itoa(param.Offset)) data.WriteString("&limit=") data.WriteString(strconv.Itoa(param.Limit)) resp, err := c.requestCloud("GET", "/edges?"+data.String(), "") if err != nil { return nil, err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) if resp.StatusCode != http.StatusOK { return nil, errors.New("failed to get edges") } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var result CloudEdgesResp err = json.Unmarshal(body, &result) if err != nil { return nil, err } return &result, nil } // endregion // region cloud:/connect type CloudConnectReq struct { Uuid string `json:"uuid"` Edge []string `json:"edge,omitempty"` AutoConfig []AutoConfig `json:"auto_config,omitempty"` } func (c *cloud) CloudConnect(param CloudConnectReq) error { data, err := json.Marshal(param) if err != nil { return err } resp, err := c.requestCloud("POST", "/connect", string(data)) if err != nil { return err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) if resp.StatusCode != http.StatusOK { return errors.New("failed to connect") } body, err := io.ReadAll(resp.Body) if err != nil { return err } var result map[string]any err = json.Unmarshal(body, &result) if err != nil { return err } if result["status"] == "error" { return errors.New(result["details"].(string)) } return nil } // endregion // region cloud:/disconnect type CloudDisconnectReq struct { Uuid string `json:"uuid"` Edge []string `json:"edge,omitempty"` Config []Config `json:"auto_config,omitempty"` } type Config struct { Province string `json:"province"` City string `json:"city"` Isp string `json:"isp"` Count int `json:"count"` Online bool `json:"online"` } func (c *cloud) CloudDisconnect(param CloudDisconnectReq) (int, error) { data, err := json.Marshal(param) if err != nil { return 0, err } resp, err := c.requestCloud("POST", "/disconnect", string(data)) if err != nil { return 0, err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) if resp.StatusCode != http.StatusOK { return 0, errors.New("failed to disconnect") } body, err := io.ReadAll(resp.Body) if err != nil { return 0, err } var result map[string]any err = json.Unmarshal(body, &result) if err != nil { return 0, err } if result["status"] == "error" { return 0, errors.New(result["details"].(string)) } return int(result["disconnected_edges"].(float64)), nil } // endregion // region cloud:/auto_query type CloudConnectResp map[string][]AutoConfig func (c *cloud) CloudAutoQuery() (CloudConnectResp, error) { resp, err := c.requestCloud("GET", "/auto_query", "") if err != nil { return nil, err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) if resp.StatusCode != http.StatusOK { return nil, errors.New("failed to get auto_query") } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var result CloudConnectResp err = json.Unmarshal(body, &result) if err != nil { return nil, err } return result, nil } // endregion func (c *cloud) requestCloud(method string, url string, data string) (*http.Response, error) { url = fmt.Sprintf("%s/api%s", c.url, url) req, err := http.NewRequest(method, url, strings.NewReader(data)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") var resp *http.Response for i := 0; i < 2; i++ { token, err := c.token(i == 1) if err != nil { return nil, err } req.Header.Set("token", token) if env.DebugHttpDump { str, err := httputil.DumpRequest(req, true) if err != nil { return nil, err } fmt.Println("==============================") fmt.Println(string(str)) } resp, err = http.DefaultClient.Do(req) if err != nil { return nil, err } if env.DebugHttpDump { str, err := httputil.DumpResponse(resp, true) if err != nil { return nil, err } fmt.Println("------------------------------") fmt.Println(string(str)) } if resp.StatusCode != 401 { break } } return resp, nil } func (c *cloud) token(refresh bool) (string, error) { // redis 获取令牌 if !refresh { token, err := rds.Client.Get(context.Background(), "remote:token").Result() if err == nil && token != "" { return token, nil } } // redis 获取失败,重新获取 resp, err := http.Get(env.RemoteToken) if err != nil { return "", err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) dump, err := httputil.DumpResponse(resp, true) if err != nil { return "", err } fmt.Println(string(dump)) if resp.StatusCode != http.StatusOK { return "", errors.New("failed to get token") } body, err := io.ReadAll(resp.Body) if err != nil { return "", err } var result map[string]any err = json.Unmarshal(body, &result) if err != nil { return "", err } if result["code"].(float64) != 1 { return "", errors.New("failed to get cloud token") } // redis 设置令牌 token := result["token"].(string) err = rds.Client.Set(context.Background(), "remote:token", token, 1*time.Hour).Err() if err != nil { return "", err } return token, nil } type gateway struct { url string username string password string } var GatewayInitializer = func(url, username, password string) GatewayClient { return &gateway{ url: url, username: username, password: password, } } func NewGateway(url, username, password string) GatewayClient { return GatewayInitializer(url, username, password) } // region gateway:/port/configs type PortConfigsReq struct { Port int `json:"port"` Edge *[]string `json:"edge,omitempty"` Type string `json:"type,omitempty"` Time int `json:"time,omitempty"` Status bool `json:"status"` Rate int `json:"rate,omitempty"` Whitelist *[]string `json:"whitelist,omitempty"` Userpass *string `json:"userpass,omitempty"` AutoEdgeConfig *AutoEdgeConfig `json:"auto_edge_config,omitempty"` } type AutoEdgeConfig struct { Province string `json:"province,omitempty"` City string `json:"city,omitempty"` Isp string `json:"isp,omitempty"` Count *int `json:"count,omitempty"` PacketLoss int `json:"packet_loss,omitempty"` } func (c *gateway) GatewayPortConfigs(params []PortConfigsReq) error { if len(params) == 0 { return errors.New("params is empty") } data, err := json.Marshal(params) if err != nil { return err } resp, err := c.requestGateway("POST", "/port/configs", string(data)) if err != nil { return err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) body, err := io.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode != http.StatusOK { return errors.New("failed to get port configs: " + string(body)) } var result map[string]any err = json.Unmarshal(body, &result) if err != nil { return err } if result["code"].(float64) != 0 { return errors.New("failed to configure port") } return nil } // endregion // region gateway:/port/active type PortActiveReq struct { Port string `json:"port"` Active *bool `json:"active"` Status *bool `json:"status"` } type PortActiveResp struct { Code int `json:"code"` Msg string `json:"msg"` Data map[string]PortData `json:"data"` } type PortData struct { Edge []string `json:"edge"` Type string `json:"type"` Status bool `json:"status"` Active bool `json:"active"` Time int `json:"time"` Whitelist []string `json:"whitelist"` Userpass string `json:"userpass"` } func (c *gateway) GatewayPortActive(param ...PortActiveReq) (map[string]PortData, error) { _param := PortActiveReq{} if len(param) != 0 { _param = param[0] } path := strings.Builder{} path.WriteString("/port/active") if _param.Port != "" { path.WriteString("/") path.WriteString(_param.Port) } values := url.Values{} if _param.Active != nil { values.Set("active", strconv.FormatBool(*_param.Active)) } if _param.Status != nil { values.Set("status", strconv.FormatBool(*_param.Status)) } if len(values) > 0 { path.WriteString("?") path.WriteString(values.Encode()) } resp, err := c.requestGateway("GET", path.String(), "") if err != nil { return nil, err } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) if resp.StatusCode != http.StatusOK { return nil, errors.New("failed to get port active") } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var result PortActiveResp err = json.Unmarshal(body, &result) if err != nil { return nil, err } if result.Code != 0 { return nil, errors.New(result.Msg) } return result.Data, nil } // endregion func (c *gateway) requestGateway(method string, url string, data string) (*http.Response, error) { url = fmt.Sprintf("http://%s:%s@%s:9990%s", c.username, c.password, c.url, url) req, err := http.NewRequest(method, url, strings.NewReader(data)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") if env.DebugHttpDump { str, err := httputil.DumpRequest(req, true) if err != nil { return nil, err } fmt.Println("==============================") fmt.Println(string(str)) } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } if env.DebugHttpDump { str, err := httputil.DumpResponse(resp, true) if err != nil { return nil, err } fmt.Println("------------------------------") fmt.Println(string(str)) } return resp, nil }