开发RESTAPI初始客户端
立即解锁
发布时间: 2025-09-14 00:08:02 阅读量: 3 订阅数: 38 AIGC 

### 开发 REST API 初始客户端
在拥有待办事项 REST API 服务器后,我们可以构建一个命令行应用程序,利用该 API 来查询、添加、完成和删除待办事项。
#### 1. 创建项目目录结构
首先,在根目录下创建 REST API 客户端的目录结构:
```bash
$ mkdir -p $HOME/pragprog.com/rggo/apis/todoClient
$ cd $HOME/pragprog.com/rggo/apis/todoClient
```
#### 2. 初始化 Cobra 应用
使用 Cobra 框架生成器生成应用程序的一些样板代码:
```bash
$ cobra init --pkg-name pragprog.com/rggo/apis/todoClient
```
此命令假设你在主目录中有一个 Cobra 配置文件。若之前执行过相关示例,应该已有该文件;否则,需创建配置文件。也可以不使用配置文件初始化应用,Cobra 会使用默认选项,代码中的许可证和注释会与示例不同。
#### 3. 初始化 Go 模块
为项目初始化 Go 模块,并确保使用 Cobra v1.1.3:
```bash
$ cd $HOME/pragprog.com/rggo/apis/todoClient
$ go mod init pragprog.com/rggo/apis/todoClient
$ go mod edit --require github.com/spf13/[email protected]
$ go mod tidy
```
#### 4. 定义子命令
该命令行应用程序有五个子命令:
| 命令 | 描述 |
| --- | --- |
| add <task> | 后跟任务字符串,将新任务添加到列表中 |
| list | 列出列表中的所有项目 |
| complete <n> | 完成第 n 项 |
| del <n> | 从列表中删除第 n 项 |
| view <n> | 查看第 n 项的详细信息 |
#### 5. 开发应用骨架并实现 list 命令
##### 5.1 修改根命令
编辑 `cmd/root.go` 文件,更新导入部分,添加 `strings` 包。更新 `rootCmd` 命令定义,修改简短描述并删除长描述。修改 `init` 函数,添加 `api-root` 命令行标志,并使用 Viper 绑定到环境变量 `TODO_API_ROOT`。
```go
// apis/todoClient/cmd/root.go
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
var rootCmd = &cobra.Command{
Use: "todoClient",
Short: "A Todo API client",
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
"config file (default is $HOME/.todoClient.yaml)")
rootCmd.PersistentFlags().String("api-root",
"http://localhost:8080", "Todo API URL")
replacer := strings.NewReplacer("-", "_")
viper.SetEnvKeyReplacer(replacer)
viper.SetEnvPrefix("TODO")
viper.BindPFlag("api-root", rootCmd.PersistentFlags().Lookup("api-root"))
}
```
##### 5.2 定义连接到 REST API 的逻辑
创建并编辑 `cmd/client.go` 文件,添加包定义和导入列表。定义错误值、自定义类型、新客户端函数、获取待办事项的函数以及获取所有待办事项的函数。
```go
// apis/todoClient/cmd/client.go
package cmd
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
var (
ErrConnection = errors.New("Connection error")
ErrNotFound = errors.New("Not found")
ErrInvalidResponse = errors.New("Invalid server response")
ErrInvalid = errors.New("Invalid data")
ErrNotNumber = errors.New("Not a number")
)
type item struct {
Task string
Done bool
CreatedAt time.Time
CompletedAt time.Time
}
type response struct {
Results []item `json:"results"`
Date int `json:"date"`
TotalResults int `json:"total_results"`
}
func newClient() *http.Client {
c := &http.Client{
Timeout: 10 * time.Second,
}
return c
}
func getItems(url string) ([]item, error) {
r, err := newClient().Get(url)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrConnection, err)
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
msg, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, fmt.Errorf("Cannot read body: %w", err)
}
err = ErrInvalidResponse
if r.StatusCode == http.StatusNotFound {
err = ErrNotFound
}
return nil, fmt.Errorf("%w: %s", err, msg)
}
var resp response
if err := json.NewDecoder(r.Body).Decode(&resp); err != nil {
return nil, err
}
if resp.TotalResults == 0 {
return nil, fmt.Errorf("%w: No results found", ErrNotFound)
}
return resp.Results, nil
}
func getAll(apiRoot string) ([]item, error) {
u := fmt.Sprintf("%s/todo", apiRoot)
return getItems(u)
}
```
##### 5.3 实现 list 命令
使用 Cobra 生成器添加 `list` 命令:
```bash
$ cobra add list
```
编辑 `cmd/list.go` 文件,更新导入部分,编辑命令实例定义,定义 `listAction` 函数和 `printAll` 函数。
```go
// apis/todoClient/cmd/list.go
import (
"fmt"
"io"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List todo items",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
apiRoot := viper.GetString("api-root")
return listAction(os.Stdout, apiRoot)
},
}
func listAction(out io.Writer, apiRoot string) error {
items, err := getAll(apiRoot)
if err != nil {
return err
}
return printAll(out, items)
}
func printAll(out io.Writer, items []item) error {
w := tabwriter.NewWriter(out, 3, 2, 0, ' ', 0)
for k, v := range items {
done := "-"
if v.Done {
done = "X"
}
fmt.Fprintf(w, "%s\t%d\t%s\t\n", done, k+1, v.Task)
}
return w.Flush()
}
```
#### 6. 测试客户端
测试 API 客户端时,直接连接到真实 API 存在困难,因此我们使用 `httptest.Server` 类型在本地模拟 API 进行测试。
##### 6.1 定义模拟 API 资源
创建并编辑 `cmd/mock_test.go` 文件,添加包定义和导入列表,定义模拟响应数据和模拟服务器函数。
```go
// apis/todoClient/cmd/mock_test.go
package cmd
import (
"net/http"
"net/http/httptest"
)
var testResp = map[string]struct {
Status int
Body string
}{
"resultsMany": {
Status: http.StatusOK,
Body: `{
"results": [
{
"Task": "Task 1",
"Done": false,
"CreatedAt": "2019-10-28T08:23:38.310097076-04:00",
"CompletedAt": "0001-01-01T00:00:00Z"
},
{
"Task": "Task 2",
"Done": false,
"CreatedAt": "2019-10-
```
0
0
复制全文
相关推荐









