Go RESTful API 接口开发

编程有一个准则——Don‘t Repeat Yourself(不要重复你的代码)。这个准则的核心概念是:如果有一些出现重复的代码,则应该把这些代码提取出来封装成一个方法。
随着时间的积累,有了一批方法,可以把它们整合成工具类。如果工具类形成了规模,则可以把它们整合成类库。类库更系统,功能更全。不仅不要自己重复造项目中已有的“轮子”,也不要造别人已经造好的“轮子”,直接使用已有的“轮子”即可。

什么是 RESTful API

  • 资源概述
    • 资源可以是单例或集合
    • 资源也可以包含子集合资源
    • REST API 使用统一资源标识符(URI)来定位资源
  • 使用名词表示资源
    • 文档
    • 集合
    • 存储
    • 控制器
  • 保持一致性
    • 使用正斜杠( \ )表示层次关系
    • 不要在 URI 尾部使用正斜杠
    • 使用连字符( - )来提高 URI 的可读性
    • 不要使用下划线( _ )
    • 在 URI 中使用小写字母
    • 不要使用文件拓展名
  • 切勿在 URI 中使用 CRUD 函数的名称
  • 使用查询参数过滤 URI 集合

Go 流行 Web 框架-Gin

Go HelloWorld

package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	// 创建一个默认的路由引擎
	r := gin.Default()
	// GET:请求方式;/hello:请求的路径
	// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
	r.GET("/hello", func(c *gin.Context) {
		// c.JSON:返回JSON格式的数据
		c.JSON(200, gin.H{
			"message": "Hello world!",
		})
	})
	// 启动HTTP服务,默认在0.0.0.0:8080启动服务
	r.Run()
}

Gin 路由和控制器

  • 路由规则
    • HTTP 请求方法
      • GET
      • POST
      • PUT
      • DELETE
    • URL 路径
      • 静态 URL 路径
      • 带路径的 URL 参数
      • 带星号(*)模糊匹配参数的 URL 路径
    • 处理器函数
  • 分组路由

Gin 处理请求参数

  • 获取 GET 请求参数
  • 获取 POST 请求参数
  • 获取 URL 路径参数
  • 将请求参数绑定到结构体

生成 HTTP 请求响应

  • 以字符串方式生成 HTTP 请求响应
  • 以 JSON 格式生成 HTTP 请求响应
  • 以 XML 格式生成 HTTP 请求响应
  • 以文件格式生成 HTTP 请求响应
  • 设置 HTTP 响应头

Gin 的学习内容

  • Gin 渲染 HTML 模板
  • Gin 处理静态资源
  • Gin 处理 cookie
  • Gin 文件上传
  • Gin 中间件
  • Gin Session

实战用 Gin 框架开发 RESTful API

mysql> CREATE TABLE `users` (
    ->     `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
    ->     `phone` VARCHAR(255) DEFAULT NULL,
    ->     `name` VARCHAR(255) DEFAULT NULL,
    ->     `password` VARCHAR(255) DEFAULT NULL,
    ->     PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;
package main

import (
	"crypto/sha256"
	"fmt"
	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"net/http"
)

type (
	User struct {
		ID       uint   `json:"id" gorm:"column:id"`
		Phone    string `json:"phone" gorm:"column:phone"`
		Name     string `json:"name" gorm:"column:name"`
		Password string `json:"password" gorm:"column:password"`
	}

	UserRes struct {
		ID    uint   `json:"id"`
		Phone string `json:"phone"`
		Name  string `json:"name"`
	}
)

var db *gorm.DB

func main() {
	// Connect to the database
	var err error
	dsn := "root:mm..1213@tcp(127.0.0.1:3306)/UserManager?charset=utf8mb4&parseTime=True&loc=Local"
	db, err = gorm.Open(mysql.New(mysql.Config{
		DriverName: "mysql",
		DSN:        dsn,
	}), &gorm.Config{})
	if err != nil {
		panic("Failed to connect to database")
	}

	// Auto migrate the User struct to create the corresponding table in the database
	err = db.AutoMigrate(&User{})
	if err != nil {
		panic("Failed to migrate the database")
	}

	router := gin.Default()
	v2 := router.Group("/api/v2/user")
	{
		v2.POST("/", createUser)
		v2.GET("/", fetchAllUser)
		v2.GET("/:id", fetchUser)
		v2.PUT("/:id", updateUser)
		v2.DELETE("/:id", deleteUser)
	}
	router.Run(":8080")
}

func createUser(c *gin.Context) {
	phone := c.PostForm("phone")
	name := c.PostForm("name")
	user := User{
		Phone:    phone,
		Name:     name,
		Password: md5Password(phone),
	}

	tx := db.Begin()
	if err := tx.Create(&user).Error; err != nil {
		tx.Rollback()
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}
	tx.Commit()

	c.JSON(http.StatusCreated, gin.H{
		"status":  http.StatusCreated,
		"message": "User created successfully!",
		"ID":      user.ID,
	})
}

func md5Password(password string) string {
	hash := sha256.Sum256([]byte(password))
	return fmt.Sprintf("%x", hash)
}

func fetchAllUser(c *gin.Context) {
	var user []User
	var _userRes []UserRes
	db.Find(&user)
	if len(user) <= 0 {
		c.JSON(
			http.StatusNotFound,
			gin.H{
				"status":  http.StatusNotFound,
				"message": "No user found!",
			})
		return
	}
	for _, item := range user {
		_userRes = append(_userRes,
			UserRes{
				ID:    item.ID,
				Phone: item.Phone,
				Name:  item.Name,
			})
	}
	c.JSON(http.StatusOK,
		gin.H{
			"status": http.StatusOK,
			"data":   _userRes,
		})
}

func fetchUser(c *gin.Context) {
	var user User
	ID := c.Param("id")
	db.First(&user, ID)
	if user.ID == 0 {
		c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No user found!"})
		return
	}
	res := UserRes{
		ID:    user.ID,
		Phone: user.Phone,
		Name:  user.Name,
	}
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": res})
}

func updateUser(c *gin.Context) {
	var user User
	userID := c.Param("id")
	db.First(&user, userID)
	if user.ID == 0 {
		c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No user found!"})
		return
	}
	db.Model(&user).Update("phone", c.PostForm("phone"))
	db.Model(&user).Update("name", c.PostForm("name"))
	c.JSON(http.StatusOK, gin.H{
		"status":  http.StatusOK,
		"message": "Updated User Successfully!",
	})
}

func deleteUser(c *gin.Context) {
	var user User
	userID := c.Param("id")
	db.First(&user, userID)
	if user.ID == 0 {
		c.JSON(http.StatusNotFound, gin.H{
			"status":  http.StatusNotFound,
			"message": "No user found!",
		})
		return
	}
	db.Delete(&user)
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "User deleted successfully!"})
}

  • GoLand Tools-Http Client 测试
DELETE http://127.0.0.1:8080/api/v2/user/58
Content-Type: application/x-www-form-urlencoded

phone=10086&name=chYiDong

OAuth 2.0接口了解

用 Go 开发 OAuth2.0 接口示例

  • 做了解之后用到可能性比较小
  1. GitHub OAuth 应用注册
  • 注册页面:https://github.com/settings/applications/new
    在这里插入图片描述
  1. 登录授权页面
<!DOCTYPE HTML>
<html>
<body>
<a href="https://github.com/login/oauth/authorize?client_id=5bcf804cfeb0ef7120f5&redirect_uri=http://localhost:8087/oauth/redirect">
    Login by GitHub
</a>
</body>
</html>
  1. 欢迎界面
<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, INItial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello</title>
</head>
<body>
</body>
<script>
    //获取url参数
    function getQueryVariable(variable) {
        var query = window.location.search.substring(1);
        var vars = query.split("&");
        for (var i = 0; i < vars.length; i++) {
            var pair = vars[i].split("=");
            if (pair[0] == variable) {
                return pair[1];
            }
        }
        return (false);
    }
    // 获取access_token
    const token = getQueryVariable("access_token");
    // 调用用户信息接口
    fetch('https://api.github.com/user', {
        headers: {
            Authorization: 'token ' + token
        }
    })
    // 解析请求的JSON
     .then(res => res.json())
     .then(res => {
        // 返回用户信息
        const nameNode = document.createTextNode(`Hi, ${res.name}, Welcome to login our site by GitHub!`)
        document.body.appendChild(nameNode)
     })
</script>
</html>
  1. Go 语言编写


package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"os"
)

// const clientID = "<your client id>"
const clientID = "5bcf804cfeb0ef7120f5"

// const clientSecret = "<your client secret>"
const clientSecret = "8d31102da18096d13eb6ec819cd81ca898ed7189"

func hello(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("hello.html")
		t.Execute(w, nil)
	}
}

func login(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("login.html")
		t.Execute(w, nil)
	}
}

func main() {
	http.HandleFunc("/login", login)
	http.HandleFunc("/", hello)
	http.HandleFunc("/hello", hello)

	httpClient := http.Client{}
	http.HandleFunc("/oauth/redirect", func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			fmt.Fprintf(os.Stdout, "could not parse query: %v", err)
			w.WriteHeader(http.StatusBadRequest)
		}
		code := r.FormValue("code")

		reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?"+
			"client_id=%s&client_secret=%s&code=%s", clientID, clientSecret, code)
		req, err := http.NewRequest(http.MethodPost, reqURL, nil)
		if err != nil {
			fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
			w.WriteHeader(http.StatusBadRequest)
		}
		req.Header.Set("accept", "application/json")

		res, err := httpClient.Do(req)
		if err != nil {
			fmt.Fprintf(os.Stdout, "could not send HTTP request: %v", err)
			w.WriteHeader(http.StatusInternalServerError)
		}
		defer res.Body.Close()

		var t AccessTokenResponse
		if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
			fmt.Fprintf(os.Stdout, "could not parse JSON response: %v", err)
			w.WriteHeader(http.StatusBadRequest)
		}

		w.Header().Set("Location", "/hello.html?access_token="+t.AccessToken)
		w.WriteHeader(http.StatusFound)
	})

	http.ListenAndServe(":8087", nil)
}

type AccessTokenResponse struct {
	AccessToken string `json:"access_token"`
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蒋的学习笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值