文章目录
编程有一个准则——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 路径
- 处理器函数
- HTTP 请求方法
- 分组路由
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 接口示例
- 做了解之后用到可能性比较小
- GitHub OAuth 应用注册
- 注册页面:https://github.com/settings/applications/new
- 登录授权页面
<!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>
- 欢迎界面
<!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>
- 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"`
}