Gin
简单介绍
- gin是一个轻量级的go web框架,具有强大的功能
- 支持中间件,可以用来身份验证,记录日志信息等
- 路由组,可以更好地组织路由,是否需要授权,不同的API版本
- JSON解析,可以解析并验证请求的JSON
示例
安装、导入
go mod init demo //go mod init projectname
go mod tidy
go run test.go
第一个gin应用
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 初始化Gin路由器
r := gin.Default()
// 为路由器r添加一个新的GET路由。当访问/hello URL时执行关联的函数
r.GET("/hello", func(c *gin.Context) {
// 使用c.JSON返回JSON格式的响应。第一个参数是HTTP状态码,第二个参数是要返回的数据
c.JSON(http.StatusOK, "hello, world")
})
// 运行HTTP服务器,监听8080端口
r.Run(":8080")
}
- gin.Context:上下文,主要是处理请求、返回响应
具体使用
获取参数
queryString参数
- 示例:http://localhost:8080/hello?name=user&age=18
- postman:
func main() {
r := gin.Default()
r.GET("/hello", func(c * gin.Context) {
name := c.DefaultQuery("name", "user1") //没有获取到参数会赋默认值
age := c.Query("age")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.Run(":8080")
}
form参数
- poastman:
func main() {
r := gin.Default()
r.GET("/hello", func(c * gin.Context) {
name := c.DefaultPostForm("name", "user1") //没有获取到参数会赋默认值
age := c.PostForm("age")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.Run(":8080")
}
uri(path)路径参数
func main() {
r := gin.Default()
r.GET("/hello/:name/:age", func(c * gin.Context) {
name := c.Param("name")
age := c.Param("age")
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
r.Run(":8080")
}
参数绑定
-
为了提高开发效率,使用gin的ShouldBind()。shouldBind()方法会根据请求中的contentType类型,将请求的参数自动绑定
-
需要在结构体中声明:
form:"传递参数名":例如:user=xxx&password=123,json:"传递参数名":例如:-
{ "user": "test", "password": "123" }
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type loginUser struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
r := gin.Default()
// 绑定JSON参数 {"user": "user1", "password": "123"}
r.POST("/loginByJSON", func(c * gin.Context) {
var login loginUser
if err := c.ShouldBindJSON( & login) ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
})
//绑定Form参数 user=user1&password=123
r.POST("/loginByForm", func(c * gin.Context) {
var login loginUser
if err := c.ShouldBind( & login) ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
})
//绑定QueryString参数同理
r.Run(":8080")
}
路由
重定向
网址重定向
func main() {
r := gin.Default()
r.GET("/test", func(c * gin.Context) {
//// 发送一个HTTP 301永久重定向响应,重定向到"http://www.baidu.com"
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})
r.Run(":8080")
}
路由重定向
func main() {
r := gin.Default()
r.GET("/test1", func(c * gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c) // 将修改后的上下文传递给路由器以处理新路径
})
r.GET("/test2", func(c * gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello World",
})
})
r.Run(":8080")
}
路由组
func main() {
r := gin.Default()
test := r.Group("/test") //大括号必须换行
{
test.POST("/login", func(c * gin.Context) {
})
test.POST("/register", func(c * gin.Context) {
})
}
r.Run(":8080")
}
中间件
- gin框架可以在处理请求的过程中,加入自己的钩子函数。这个钩子函数就叫中间件。用于处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
- gin的中间件必须是一个gin.HandlerFunc类型
package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func f1 () gin.HandlerFunc { return func ( c * gin.Context) { fmt.Println("f1 start") c.Next() //继续处理 fmt.Println("f1 end") } } func f2() gin.HandlerFunc { return func ( c * gin.Context) { fmt.Println("f2 start") c.Abort() //终止后续处理 fmt.Println("f2 end") } } func f3() gin.HandlerFunc { return func ( c * gin.Context) { fmt.Println("f3 start") c.Next() fmt.Println("f3 end") } } func main() { r := gin.Default() r.GET("/hello", f1(), f2(), f3(), func ( c * gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "success", }) }) r.Run(":8080") }
- 配置全局中间件
func main () { r := gin.Default()
r.Use(f1()) //Use(中间件)即可 r.GET( "/hello" , f2(), f3(), func ( c * gin.Context) { c.JSON(http.StatusOK, gin.H{ "message" : "success" , }) }) r.Run( ":8080" ) }
data/sql
连接MySQL
- 在Go语言中,使用MySQL进行增删改查操作,通常需要通过一个数据库驱动,比如go-sql-driver/mysql,并结合database/sql标准库来完成。以下是一个简单的示例,使用Go语言对MySQL进行增删改查。
package main
import (
"database/sql"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
)
var db * sql.DB
func initDB() (err error) {
//更改为自己设置的mysql密码和数据库,用户:密码
dsn := "root:123456@tcp(localhost:3306)/demo1?charset=utf8mb4&parseTime=True&loc=Local"
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
err = db.Ping()
if err != nil {
return err
}
// db.SetMaxOpenConns(1024) // 设置连接数总数
// db.SetMaxIdleConns(100) // 设置最大空闲连接数
// db.SetConnMaxLifetime(0) // 设置连接最大生命周期, 默认为 0
return
}
func main() {
if err := initDB() ; err != nil {
fmt.Println("connect failed:", err)
panic(err)
}
defer db.Close()
fmt.Println("connect success")
}
建表
- 导入以下sql
/*
Navicat Premium Data Transfer
Source Server : demo
Source Server Type : MySQL
Source Server Version : 80039 (8.0.39)
Source Host : localhost:3306
Source Schema : demo1
Target Server Type : MySQL
Target Server Version : 80039 (8.0.39)
File Encoding : 65001
Date: 11/10/2024 16:02:24
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int NOT NULL AUTO_INCREMENT,
`account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`type` tinyint NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`age` int NOT NULL,
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'user1', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Mo Zhiyuan', 23);
INSERT INTO `user` VALUES (2, 'user2', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Fu Zhennan', 42);
INSERT INTO `user` VALUES (3, 'user3', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Du Zhennan', 72);
INSERT INTO `user` VALUES (4, 'user4', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Lok Kwok Ming', 87);
INSERT INTO `user` VALUES (5, 'user5', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Hashimoto Yuto', 39);
INSERT INTO `user` VALUES (6, 'user6', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Troy Ruiz', 14);
INSERT INTO `user` VALUES (7, 'user7', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Pan Rui', 76);
INSERT INTO `user` VALUES (8, 'user8', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Shao Yunxi', 99);
INSERT INTO `user` VALUES (9, 'user9', 'E10ADC3949BA59ABBE56E057F20F883E', 1, 'Heung Kwok Kuen', 48);
INSERT INTO `user` VALUES (10, 'admin', 'E10ADC3949BA59ABBE56E057F20F883E', 2, 'Che Ching Wan', 54);
SET FOREIGN_KEY_CHECKS = 1;
- 密码均为123456!!!
查询
用法
- 查询sql:SELECT 列名 FROM 表名
- Query(), QueryRow
Query() //查询多条数据
QueryRow() //查询一条数据
//rows代表查询结果集, sql即需要的sql语句, param为sql查询参数
rows, err := db.Query(sql, param)
if err != nil {
return nil, err
}
defer rows.Close()
//遍历结果集
for rows.Next() {
rows.Scan()
}
练习
接口
API:http://localhost:8080/user/{userId}
Method:GET
参数形式:url
参数字段:
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| userId | 用户id | yes | string |
- 请求示例
- 返回值
{
"data": {
"UserId": 1,
"Account": "user1",
"Name": "Mo Zhiyuan",
"Age": 23,
"Type": 1
}
}
参考代码
r.GET("/user/:userId", func(c * gin.Context) {
userId := c.Param("userId")
var user User
rows, err := db.Query(`
SELECT user_id, account, name, age, type
FROM user
WHERE user_id = ?`, userId)
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan( & user.UserId, & user.Account, & user.Name, & user.Age, & user.Type)
if err != nil {
panic(err)
}
}
if user.UserId == 0 {
c.JSON(http.StatusOK, gin.H{"msg": "用户不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
})
创建
用法
- 插入sql:INSERT INTO 表名(列名)VALUES(值)
_, err := db.Exec(sql, value)
练习
接口
Tip:需要对密码进行加密,不能将明文存储在数据库中
hash : = md5.Sum([]byte(user.Password))
npwd : = hex.EncodeToString(hash[:])
API: http://localhost:8080/user
Method: POST
参数形式:Body (application/json)
参数字段
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| account | 用户账号 | yes | string |
| password | 用户密码 | yes | string |
| type | 用户类型 | yes | int |
| name | 用户姓名 | yes | string |
| age | 用户年龄 | yes | int |
请求示例:
{
"account": "user9",
"password": "123456",
"type": 1,
"name": "qsfga",
"age": 10
}
参考代码
r.POST("/user", func(c * gin.Context) {
var user User
if err := c.ShouldBind( & user) ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
hash := md5.Sum([]byte(user.Password))
npwd := hex.EncodeToString(hash[:])
_, err := db.Exec(`
INSERT INTO user(account, password, name, age, type)
VALUES(?, ?, ?, ?, ?)`, user.Account, npwd, user.Name, user.Age, user.Type)
if err != nil {
panic(err)
}
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
更新
用法
- 更新sql:UPDATE 表名 SET 列名=
_, err := db.Exec(sql, value)
练习
接口
API: http://localhost:8080/user/{userId}
Method: PUT
参数形式:Body (application/json)
参数字段
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| userId | 用户id | yes | string |
| password | 用户密码 | yes | string |
| name | 用户姓名 | yes | string |
| age | 用户年龄 | yes | int |
请求示例:
{
"password": "123456",
"name": "xxx1",
"age": 20
}
参考代码
r.PUT("/user/:userId", func(c * gin.Context) {
userId := c.Param("userId")
var user User
if err := c.ShouldBind( & user) ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
hash := md5.Sum([]byte(user.Password))
npwd := hex.EncodeToString(hash[:])
_, err := db.Exec(`
UPDATE user
SET password = ?, name = ?, age = ?
WHERE user_id = ?`, npwd, user.Name, user.Age, userId)
if err != nil {
panic(err)
}
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
删除
用法
- 删除sql:DELETE FROM 表名
_, err := db.Exec(sql, value)
练习
接口
API: http://localhost:8080/user/{userId}
Method: DELETE
参数形式:url
参数字段
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| userId | 用户id | yes | string |
参考代码
r.DELETE("/user/:userId", func(c * gin.Context) {
userId := c.Param("userId")
_, err := db.Exec(`
DELETE FROM user
WHERE user_id = ?`, userId)
if err != nil {
panic(err)
}
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
自主练习(登录)
接口
API: http://localhost:8080/login
Method: POST
参数形式:Body (application/json)
参数字段:
| 参数名称 | 参数说明 | 是否必填 | 参数类型 | 说明 |
|---|---|---|---|---|
| account | 账户 | yes | string | |
| password | 密码 | yes | string | 需要md5加密 |
- md5加密代码
hash := md5.Sum([]byte(str)) //替换str为密码即可
npwd := hex.EncodeToString(hash[:])
- 请求示例
{
"account": "user1",
"password": "123456"
}
- 返回值
{
"data": {
"UserId": 1,
"Account": "user1",
"Name": "Mo Zhiyuan",
"Age": 23,
"Type": 1
}
}
- 参考代码
package main import ( "crypto/md5" "database/sql" "encoding/hex" "fmt" "net/http" "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" ) var db * sql.DB type LoginUser struct { Account string `json:"account" binding:"required"` Password string `json:"password" binding:"required"` } type User struct { UserId int `json:"UserId"` Account string `json:"Account"` Password string `json:"Password"` Name string `json:"Name"` Age int `json:"Age"` Type int `json:"Type"` } func initDB() ( err error) { ds := "root:1234@tcp(localhost:3306)/demo1" db, err = sql.Open("mysql", ds) if err != nil { return err } err = db.Ping() if err != nil { return err } // db.SetMaxOpenConns(1024) // 设置连接数总数 // db.SetMaxIdleConns(100) // 设置最大空闲连接数 // db.SetConnMaxLifetime(0) // 设置连接最大生命周期, 默认为 0 return } func main() { if err := initDB() ; err != nil { fmt.Println("connect failed:", err) panic(err) } defer db.Close() fmt.Println("connect success") r := gin.Default() r.POST("/login", func ( c * gin.Context) { var loginUser LoginUser if err := c.ShouldBind( & loginUser) ; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var user User //md5加密 hash := md5.Sum([]byte(loginUser.Password)) npwd := hex.EncodeToString(hash[:]) err := db.QueryRow(` SELECT user_id, account, name, age, type FROM user WHERE account = ? AND password = ?`, loginUser.Account, npwd).Scan( & user.UserId, & user.Account, & user.Name, & user.Age, & user.Type) if err != nil { fmt.Println(err) panic(err) } if user.UserId == 0 { c.JSON(http.StatusOK, gin.H{"msg": "用户名或密码错误"}) return } c.JSON(http.StatusOK, gin.H{"data": user}) }) r.Run(":8080") }
Gorm
- ORM(Object-Relational Mapping)对象关系映射,用于将对象模型与关系型数据库模型之间进行转换。从而让开发者不需要直接编写SQL语句操作数据库。
- GORM是Go语言中非常流行的ORM框架。
连接MySQL
package main import ( "fmt" "net/http" "gorm.io/driver/mysql" "gorm.io/gorm" ) var db *gorm.DB func initDB() (err error) { dsn := "root:1234@tcp(localhost:3306)/demo1" db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { return err } db = db.Debug() return } func main() { if err := initDB(); err != nil { fmt.Println("connect failed:", err) panic(err) } fmt.Println("connect success") }
查询
用法
根据主键查询
db.First(&user, 10)
//SELECT * FROM user WHERE id = 10
db.First(&user, "10")
//SELECT * FROM user WHERE id = 10
db.Find(&users, []int{1, 2, 3})
//SELECT * FROM users WHERE id in (1, 2, 3);
查询全部对象
db.Find(&users) //SELECT * FROM users;
查询条件
string
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
Struct & Map
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
内联
// Get by primary key if it were a non-integer type
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';
// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;
// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;
not
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
Or
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
排序
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
Limit、offest
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
Group by、having
type result struct {
Date time.Time
Total int
}
db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
defer rows.Close()
for rows.Next() {
...
}
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
defer rows.Close()
for rows.Next() {
...
}
type Result struct {
Date time.Time
Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
Distinct
db.Distinct("name", "age").Order("name, age desc").Find(&results)
Joins
type result struct {
Name string
Email string
}
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
...
}
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
练习
接口
API:http:localhost:8080/user/{userId}
Method:GET
参数形式:url
参数字段:
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| userId | 用户id | yes | string |
- 请求示例
- 返回值
{
"data": {
"UserId": 1,
"Account": "user1",
"Name": "Mo Zhiyuan",
"Age": 23,
"Type": 1
}
}
参考代码
r.GET("/user/:userId", func(c * gin.Context) {
userId := c.Param("userId")
var user User
if err := db.Find( & user, userId).Error ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if user.UserId == 0 {
c.JSON(http.StatusOK, gin.H{"data": "用户不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
})
创建
用法
所有字段创建
db.Save(&user)
选定字段创建
- 选定字段创建:Select
- 忽略字段创建:Omit
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
批量插入
- 传入切片
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
练习
接口
API: http://localhost:8080/user
Method: POST
参数形式:Body (application/json)
参数字段
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| account | 用户账号 | yes | string |
| password | 用户密码 | yes | string |
| name | 用户姓名 | yes | string |
| age | 用户年龄 | yes | int |
请求示例:
{
"account": "user9",
"password": "123456",
"name": "qsfga",
"age": 10
}
参考代码
r.POST("/user", func(c * gin.Context) {
var user User
if err := c.ShouldBind( & user) ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
hash := md5.Sum([]byte(user.Password))
user.Password = hex.EncodeToString(hash[:])
user.Type = 1
if err := db.Create( & user).Error ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
更新
用法
更新所有字段
- 保存所有的字段,即使字段是零值
- 如果保存值不包含主键,会执行create,否则执行update
db.Save(&user)
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1
更新单列
// 根据条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
更新多列
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
更新选定字段
// 选择 Map 的字段
// User 的 ID 是 `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// 选择 Struct 的字段(会选中零值的字段)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// 选择所有字段(选择包括零值字段的所有字段)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
// 选择除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
练习
接口
API: http://localhost:8080/user/{userId}
Method: PUT
参数形式:Body (application/json)
参数字段
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| userId | 用户id | yes | string |
| password | 用户密码 | yes | string |
| name | 用户姓名 | yes | string |
| age | 用户年龄 | yes | int |
请求示例:
{
"password": "123456",
"name": "xxx1",
"age": 20
}
参考代码
r.PUT("/user/:userId", func(c * gin.Context) {
userId := c.Param("userId")
var reqUser User
var user User
if err := c.ShouldBind( & reqUser) ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Find( & user, userId).Error ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
hash := md5.Sum([]byte(reqUser.Password))
user.Password = hex.EncodeToString(hash[:])
user.Name = reqUser.Name
user.Age = reqUser.Age
if err := db.Save( & user).Error ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
删除
用法
根据主键删除
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);
批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
练习
接口
API: http://localhost:8080/user/{userId}
Method: DELETE
参数形式:url
参数字段
| 参数名称 | 参数说明 | 是否必填 | 参数类型 |
|---|---|---|---|
| userId | 用户id | yes | string |
参考代码
r.DELETE("/user/:userId", func(c * gin.Context) {
userId := c.Param("userId")
if err := db.Delete( & User{}, userId).Error ; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
跨域问题
- 浏览器的同源策略:两个URL的协议、域名和端口都必须相同,不然默认不允许发起请求。
| 当前页面url | 被请求页面url | 是否跨域 | 原因 |
|---|---|---|---|
| www.test.com/ | www.test.com/index.html | 否 | 协议,域名,端口相同 |
| www.test.com/ | www.test.com/index.html | 是 | 协议不同 |
| www.test.com/ | www.baidu.com/ | 是 | 主域名不同 |
| www.test.com/ | test1.test.com/ | 是 | 子域名不同 |
| www.test.com:8080/ | www.test.com:8000/ | 是 | 端口号不同 |
- CORS是跨域资源分享。只需服务器设置Access-Control-Allow-Origin
func CORSMiddleware() gin.HandlerFunc {
return func(c * gin.Context) {
method : = c.Request.Method
c.Header("Access-Control-Allow-Origin", c.GetHeader("Origin"))
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.Header("Access-Control-Allow-Methods", c.GetHeader("Access-Control-Request-Method"))
c.Header("Access-Control-Allow-Headers", c.GetHeader("Access-Control-Request-Headers"))
c.Header("Access-Control-Max-Age", "7200")
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
身份认证
-
身份认证(俗称鉴权)是每个网站必不可少的
-
使用session的流程
- 用户使用账号密码登录
- 服务器验证通过后会在session中保存相关数据,并向用户返回一个session_id,写在用户的cookie中
- 之后用户每次请求都会通过cookie将session_id传回服务器
- 服务器根据session_id查询用户的数据
-
优点:安全性较高
-
缺点:扩展性不好,不利于集群部署
JWT
- JWT全称JSON Web Token。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJVc2VySWQiOjIsIlVzZXJUeXBlIjoxLCJleHAiOjE3MzY3MzYxNjksIm5iZiI6MTcyODk2MDE2OSwiaWF0IjoxNzI4OTYwMTY5fQ.
h-nTtfYj04FMLHZTh-R0gaZYCMaCoDu8wV2pE3ZcBk8
- 分为三个部分,header,payload,signature。header和payload以JSON形式存在,每部分都分别经过Base64编码,以.拼接成一个JWT Token。
Header
- header是一个JSON对象,描述JWT的元数据,alg表示签名的算法,默认是HMAC SHA256,type表示令牌的类型。最后将这个JSON对象用Base64URL算法转换成字符串
{
"alg": "HS256",
"typ": "JWT"
}
Payload
- payload是一个JSON对象,用来存放实际需要的数据。JWT规定7个官方字段
Issuer:签发人
ExpiresAt:过期时间
Subject:主题
Audience:受众
NotBefore:生效时间
IssuedAt:签发时间
ID:编号
- 除了这些官方字段外,你也可以指定字段和内容
- 注意JWT默认是不加密的,任何人都可以读到,不要将秘密信息放在这里
Signature
- signature是对前两部分的签名,防止数据篡改。需要指定一个密钥,然后使用签名算法
Base64URL
- Base64是一种编码方式,用于将二进制数据转换为文本格式。
- 而Base64URL与Base64的区别是会将+、/和=替换掉,=被省略,+替换成-,/替换成_,因为jwt可以会被放在url中+、/、=在URL里有特殊含义
JWT的使用
- 客户端收到服务器返回的JWT,可以存储在cookie里,也可以存储在localStorage。
- 客户端每次发送请求都需要带上JWT,最好的做法是放在HTTP请求头的Authorization字段里
Authorization: token
- postaman使用方法
Go中使用
- 定义结构体
//自定义结构体,嵌入了jwt.RegisteredClaims结构体
type MyClaims struct {
UserId int
UserType int
jwt.RegisteredClaims
}
- 生成jwt对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, MyClaims{
UserId: userId,
UserType: userType,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresAt),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "",
Subject: "",
},
})
- 签名
token.SignedString([]byte(secretKey))
- 解析jwt
func ParseToken(tokenString string, secreKey string) ( * MyClaims, error) {
//解析tokenstring根据签名的密钥,三个参数:
//tokenString表示要解析的jwt字符串,
//&MyClaims{}:用于指定jwt的声明类型,解析后的声明会存储到这个结构体中
//函数,接收一个jwt令牌,并返回用于签名的密钥
token, err := jwt.ParseWithClaims(tokenString, & MyClaims{}, func(token * jwt.Token) (interface{}, error) {
return []byte(secreKey), nil
})
if err != nil {
return nil, err
}
//token进行类型断言,检查是否是MyCalims类型,token.valid检查token是否在有效期内且签名正确
if claims, ok := token.Claims.( * MyClaims) ; ok && token.Valid {
return claims, nil
} else {
return nil, errors.New("invalid token")
}
}
- 完整代码
package jwt
import (
"errors"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
type MyClaims struct {
UserId int
UserType int
jwt.RegisteredClaims
}
func GetToken(userId int, userType int, secretKey string, expiresAt time.Time) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, MyClaims{
UserId: userId,
UserType: userType,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresAt),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "",
Subject: "",
},
})
return token.SignedString([]byte(secretKey))
}
func ParseToken(tokenString string, secreKey string) ( * MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, & MyClaims{}, func(token * jwt.Token) (interface{}, error) {
return []byte(secreKey), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.( * MyClaims) ; ok && token.Valid {
return claims, nil
} else {
return nil, errors.New("invalid token")
}
}
优缺点
JWT最大优势是不需要存储Session。使得服务器鉴权变得简单,但是由于有效期存储在Token中,JWT 一旦签发,在有效期内一直可用,无法在服务器禁止。比如需要禁用用户,单纯使用JWT就无法做到实时了。