Golang通用REST Get函数实现

Golang通用REST Get函数实现 问题:我有一个拥有大量端点的REST服务,并且一直在反复复用代码。

我该如何编写一个通用的GET函数,为其提供所需的对象类型,并让该函数解组响应,将结果以正确的类型返回。

我目前真的不知道如何实现这一点,但如果有人能给我一个入门的指引,我肯定愿意花时间去尝试解决。

谢谢

package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"encoding/json"
)

var Client *http.Client

type Car struct {
	Name string
	Colour string
	EngineSize int
}

type Fruit struct {
	Name string
	Calories int
	Seedless bool
}

func Main() {
	car := Car{}

	url := "/blah/blah/cars"
	respJson, err := getIt(url, &car)
	fmt.Println(respJson.Colour)

	url = "/blah/blah/fruits"
	fruit := Fruit{}
	respJson, err = getIt(url, &fruit)
	fmt.Println(respJson.Seedless)
}

func getIt(urlStr string, i interface{}) (interface{}, error) {
		req, _ := http.NewRequest("GET", urlStr, nil)
		req.Header.Add("Accept", "application/json")

		resp, err := Client.Do(req)
		if err != nil {
			return nil, fmt.Errorf("er......")
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("er......")
		}
		err = json.Unmarshal(body, &i)
		if err != nil {
			return nil, fmt.Errorf("can't unmarshal response......")
		}
		return  &i , nil
}

更多关于Golang通用REST Get函数实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

@mje。谢谢,我会看看的。

更多关于Golang通用REST Get函数实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


tranman:

我目前真的不知道该如何处理这个问题,但如果有人能给我一个入门的指引,我肯定愿意花时间去尝试解决。

我曾尝试通过创建一个API来解决这个问题,并取得了一些成功。我在这里寻求过一些反馈,但没有得到任何回应。在此处阅读更多关于我的尝试

教程:泛型入门 - Go 编程语言

Go 是一种开源编程语言,它使得构建简单、可靠且高效的软件变得容易。

图片

学习使用 Go 反射

Go 系列文章第 5 篇

阅读时间:8 分钟

要实现一个通用的REST GET函数,可以使用Go的泛型(Go 1.18+)或接口反射。以下是两种实现方式:

方法1:使用泛型(推荐,Go 1.18+)

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

var Client = &http.Client{}

type Car struct {
    Name       string `json:"name"`
    Colour     string `json:"colour"`
    EngineSize int    `json:"engineSize"`
}

type Fruit struct {
    Name     string `json:"name"`
    Calories int    `json:"calories"`
    Seedless bool   `json:"seedless"`
}

func main() {
    url := "https://api.example.com/cars/1"
    car, err := GetJSON[Car](url)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Car colour: %s\n", car.Colour)

    url = "https://api.example.com/fruits/1"
    fruit, err := GetJSON[Fruit](url)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Fruit seedless: %v\n", fruit.Seedless)
}

func GetJSON[T any](url string) (*T, error) {
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("创建请求失败: %w", err)
    }
    req.Header.Add("Accept", "application/json")

    resp, err := Client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("请求失败: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("读取响应失败: %w", err)
    }

    var result T
    if err := json.Unmarshal(body, &result); err != nil {
        return nil, fmt.Errorf("JSON解析失败: %w", err)
    }

    return &result, nil
}

方法2:使用接口反射(兼容旧版本)

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "reflect"
)

var Client = &http.Client{}

func main() {
    url := "https://api.example.com/cars/1"
    car := &Car{}
    err := GetJSON(url, car)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Car colour: %s\n", car.Colour)

    url = "https://api.example.com/fruits/1"
    fruit := &Fruit{}
    err = GetJSON(url, fruit)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Fruit seedless: %v\n", fruit.Seedless)
}

func GetJSON(url string, result interface{}) error {
    if reflect.ValueOf(result).Kind() != reflect.Ptr {
        return fmt.Errorf("result必须是指针类型")
    }

    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return fmt.Errorf("创建请求失败: %w", err)
    }
    req.Header.Add("Accept", "application/json")

    resp, err := Client.Do(req)
    if err != nil {
        return fmt.Errorf("请求失败: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("HTTP错误: %s", resp.Status)
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return fmt.Errorf("读取响应失败: %w", err)
    }

    if err := json.Unmarshal(body, result); err != nil {
        return fmt.Errorf("JSON解析失败: %w", err)
    }

    return nil
}

方法3:带配置选项的通用GET函数

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

type ClientOptions struct {
    Timeout    time.Duration
    Headers    map[string]string
    BaseURL    string
}

type RESTClient struct {
    client  *http.Client
    options ClientOptions
}

func NewRESTClient(options ClientOptions) *RESTClient {
    return &RESTClient{
        client: &http.Client{
            Timeout: options.Timeout,
        },
        options: options,
    }
}

func (rc *RESTClient) GetJSON[T any](ctx context.Context, endpoint string) (*T, error) {
    url := rc.options.BaseURL + endpoint
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("创建请求失败: %w", err)
    }
    
    req.Header.Add("Accept", "application/json")
    for key, value := range rc.options.Headers {
        req.Header.Add(key, value)
    }

    resp, err := rc.client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("请求失败: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(resp.Body)
        return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("读取响应失败: %w", err)
    }

    var result T
    if err := json.Unmarshal(body, &result); err != nil {
        return nil, fmt.Errorf("JSON解析失败: %w", err)
    }

    return &result, nil
}

// 使用示例
func main() {
    client := NewRESTClient(ClientOptions{
        Timeout: 30 * time.Second,
        Headers: map[string]string{
            "Authorization": "Bearer token",
        },
        BaseURL: "https://api.example.com",
    })

    ctx := context.Background()
    
    // 获取Car
    car, err := client.GetJSON[Car](ctx, "/cars/1")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Car: %+v\n", car)
    
    // 获取Fruit
    fruit, err := client.GetJSON[Fruit](ctx, "/fruits/1")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Fruit: %+v\n", fruit)
}

泛型方法提供了类型安全且简洁的API,而反射方法兼容旧版本Go。带配置选项的版本更适合生产环境,提供了更好的可配置性和错误处理。

回到顶部