1. GORM
1.1 什么是ORM
- ORM是object relational mapping就是对象映射关系程序
- 简单的说类似python这种面向对象的程序来说,一切都是对象.
- 为了保证一致性的使用习惯,通过orm将编程语言的对象模型和数据库的关系型模型建立映射关系
- 这样我们直接使用编程语言的对象模型进行数据库操作就可以了.而不是直接使用sql语言.
1.2 什么是GORM
GORM是Golang ORM库
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持
Preload
、Joins
的预加载 - 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
1.3 初始化项目
mkdir day04
cd day04
go mod init day04
1.4 安装GORM
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
go get -u gorm.io/driver/mysql
2. 创建数据库
docker run --name mysql -itd -h mysql-server -e MYSQL_ROOT_PASSWORD=root -v /data/mysql5.7:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.34 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
root@Ubuntu-1:/data# docker exec -it 0af13655 bash
root@mysql-server:/# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.34 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create database test_db charset utf8;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test_db |
+--------------------+
5 rows in set (0.00 sec)
3. 连接数据库
先要确认数据库连接正常.
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err) // 异常处理
}
fmt.Println("db:", db)
结果
db: &{0xc0001245a0 <nil> 0 0xc0001d2380 1}
err结果是nil就说明连接正常.
如果失败会报以下错误
[error] failed to initialize database, got error Error 1045 (28000): Access denied for user 'root'@'192.168.31.1' (using password:
YES)
4. 创建表
// 1. 定义字段
type User struct {
// gorm:"primary_key" 主键
Id int64 `json:"id" gorm:"primary_key"`
Username string `json:"username" gorm:"username"`
Password string `json:"password" gorm:"password"`
}
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 2. 创建表
// 迁移 https://gorm.io/zh_CN/docs/migration.html
db.AutoMigrate(User{})
}
mysql> desc users;
+----------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| username | longtext | YES | | NULL | |
| password | longtext | YES | | NULL | |
+----------+------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
运行后该表就被创建了
5. 增删改查
5.1 增
db.Create(&User{
Username: "zhangsan",
Password: "123456",
})
mysql> select * from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | zhangsan | 123456 |
+----+----------+----------+
1 row in set (0.00 sec)
5.2 改
// 5. 修改字段
db.Model(User{
Id: 1,
}).Update("password", "10JQKA")
mysql> select * from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | zhangsan | 10JQKA |
+----+----------+----------+
1 row in set (0.00 sec)
5.3 查询
再增加一条数据
mysql> select * from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | zhangsan | 10JQKA |
| 2 | lisi | 111111 |
+----+----------+----------+
2 rows in set (0.00 sec)
查询一条数据
// 查询
// 过滤单挑数据
u := User{Id: 2}
db.First(&u)
fmt.Printf("%#v\n", u)
// 查询所有数据
users := []User{}
db.Find(&users)
fmt.Printf("%#v\n", users)
结果
main.User{Id:2, Username:"lisi", Password:"111111"}
[]main.User{main.User{Id:1, Username:"zhangsan", Password:"10JQKA"}, main.User{Id:2, Username:"lisi", Password:"111111"}}
5.4 删除
// 删除数据
db.Delete(&User{Id: 1})
db.Find(&users)
fmt.Printf("%#v\n", users)
// 条件删除
db.Where("Username = ?", "lisi").Delete(&User{})
db.Find(&users)
fmt.Printf("%#v\n", users)
结果
[]main.User{main.User{Id:2, Username:"lisi", Password:"111111"}}
[]main.User{}
6. 模型类型
6.1 数据类型
参数 | 含义 |
---|---|
paimary_key | 主键 |
string | 默认字符串对应的是text文本类型 |
*time.Time | datatime |
varchar(100) | 将默认string设置成varchar(100) |
unique_index | 唯一索引 |
size:255 | 设置字段大小255个字节 |
not null | 不为空 |
unique | 字段唯一 |
AUTO_INCREMENT | 自增 |
index:addr | 创建一个名字是addr的索引 |
gorm:“-” | 忽略这个字段 |
先创建表
type User struct {
Id int64 `gorm:"paimary_key" json:"id"`
Name string
CreateAt *time.Time `json:"createAt" gorm:"column:create_at"`
Email string `gorm:"type:varchar(100);unique_index"`
Role string `gorm:"size:255"`
MemberNumber int `gorm:"unique;not null"`
Num int `gorm:"AUTO_INCREMENT"`
Address string `gorm:"index:addr"`
IgnoreMe int `gorm:"-"`
}
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.AutoMigrate(User{})
}
mysql> desc users;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | longtext | YES | | NULL | |
| create_at | datetime(3) | YES | | NULL | |
| email | varchar(100) | YES | | NULL | |
| role | varchar(255) | YES | | NULL | |
| member_number | bigint(20) | NO | UNI | NULL | |
| num | bigint(20) | YES | | NULL | |
| address | varchar(191) | YES | MUL | NULL | |
+---------------+--------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
6.2 支持结构标签
标签名 | 说明 |
---|---|
column | 指定 db 列名 |
type | 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null 、size , autoIncrement … 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime |
size | 定义列数据类型的大小或长度,例如 size: 256 |
primaryKey | 将列定义为主键 |
unique | 将列定义为唯一键 |
default | 定义列的默认值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
autoIncrementIncrement | 自动步长,控制连续记录之间的间隔 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前缀 |
autoCreateTime | 创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime | 创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
index | 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情 |
uniqueIndex | 与 index 相同,但创建的是唯一索引 |
check | 创建检查约束,例如 check:age > 13 ,查看 约束 获取详情 |
<- | 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 |
-> | 设置字段读的权限,->:false 无读权限 |
- | 忽略该字段,- 表示无读写,-:migration 表示无迁移权限,-:all 表示无读写迁移权限 |
comment | 迁移时为字段添加注释 |
7. 一对多关联查询
通过外键实现两张表之间的关联.
type User struct {
gorm.Model
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint // 默认会在CreditCard表中生成UserId字段作为User表中关联的外键ID
}
7.1 外键
- 为了定义一对多关系,外键必须存在,默认外键的名字是所有者类型的名字加上他的主键.
- 就像上面的例子,为了定义一个属于User的模型,外键就应该为UserID
- 使用其他的字段名字作为外键,可以通过foreignkey来定制它
type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}
7.1.1 建立一对多表
// 2. 创建一对多
// 2.1 定义User表
type User struct {
gorm.Model
Username string `json:"username" gorm:"column:username"`
// 添加外键关联
CreditCard []Card
}
// 2.2 定义Card表
type Card struct {
gorm.Model
Number string
// 这个就是User表关联的外键 名字是 结构体+主键
UserID uint
}
func main() {
// 1. 建立连接
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println(db)
// 3. 创建表结构
db.AutoMigrate(&User{}, &Card{})
// 4. 添加一对多
}
表结构
mysql> desc cards;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
| number | longtext | YES | | NULL | |
| user_id | bigint(20) unsigned | YES | MUL | NULL | |
+------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
mysql> desc users;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
| username | longtext | YES | | NULL | |
+------------+---------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
7.1.2 创建一对多数据
// 2. 创建一对多
// 2.1 定义User表
type User struct {
gorm.Model
Username string `json:"username" gorm:"column:username"`
// 添加外键关联
CreditCard []Card
}
// 2.2 定义Card表
type Card struct {
gorm.Model
Number string
// 这个就是User表关联的外键 名字是 结构体+主键
UserID uint
}
func main() {
// 1. 建立连接
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println(db)
// 3. 创建表结构
//db.AutoMigrate(&User{}, &Card{})
// 4. 添加一对多
user := User{
Username: "zhangsan",
CreditCard: []Card{
{Number: "00001"},
{Number: "00002"},
},
}
db.Create(&user)
fmt.Println(db)
}
此时数据已经被创建
mysql> select * from users;
+----+-------------------------+-------------------------+------------+----------+
| id | created_at | updated_at | deleted_at | username |
+----+-------------------------+-------------------------+------------+----------+
| 1 | 2022-12-07 10:55:11.397 | 2022-12-07 10:55:11.397 | NULL | zhangsan |
+----+-------------------------+-------------------------+------------+----------+
1 row in set (0.00 sec)
mysql> select * from cards;
+----+-------------------------+-------------------------+------------+--------+---------+
| id | created_at | updated_at | deleted_at | number | user_id |
+----+-------------------------+-------------------------+------------+--------+---------+
| 1 | 2022-12-07 10:55:11.415 | 2022-12-07 10:55:11.415 | NULL | 00001 | 1 |
| 2 | 2022-12-07 10:55:11.415 | 2022-12-07 10:55:11.415 | NULL | 00002 | 1 |
+----+-------------------------+-------------------------+------------+--------+---------+
2 rows in set (0.00 sec)
7.1.3 添加一对多数据
// 给zhangsan添加一个信用卡
// 查出zhangsan相关的记录
u := User{Username: "zhangsan"}
db.First(&u)
// Association 关联查找
db.Model(&u).Association("CreditCard").Append([]Card{
{Number: "00003"},
})
此时就在Card表中追加了一条00003的记录
mysql> select * from users;
+----+-------------------------+-------------------------+------------+----------+
| id | created_at | updated_at | deleted_at | username |
+----+-------------------------+-------------------------+------------+----------+
| 1 | 2022-12-07 10:55:11.397 | 2022-12-07 11:33:36.351 | NULL | zhangsan |
+----+-------------------------+-------------------------+------------+----------+
1 row in set (0.00 sec)
mysql> select * from cards;
+----+-------------------------+-------------------------+------------+--------+---------+
| id | created_at | updated_at | deleted_at | number | user_id |
+----+-------------------------+-------------------------+------------+--------+---------+
| 1 | 2022-12-07 10:55:11.415 | 2022-12-07 10:55:11.415 | NULL | 00001 | 1 |
| 2 | 2022-12-07 10:55:11.415 | 2022-12-07 10:55:11.415 | NULL | 00002 | 1 |
| 3 | 2022-12-07 11:33:36.352 | 2022-12-07 11:33:36.352 | NULL | 00003 | 1 |
+----+-------------------------+-------------------------+------------+--------+---------+
3 rows in set (0.00 sec)
7.2 一对多查找
7.2.1 Association
使用Association方法要把User查询好,然后根据User定义中指定的AssociationForeignKey去查找Card,性能较低
// 2. 创建一对多
// 2.1 定义User表
type User struct {
gorm.Model
Username string `json:"username" gorm:"column:username"`
// 添加外键关联
CreditCard []Card
}
// 2.2 定义Card表
type Card struct {
gorm.Model
Number string
// 这个就是User表关联的外键 名字是 结构体+主键
UserID uint
}
func main() {
// 1. 建立连接
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 3. 查询关联表数据
// 3.1 使用Association 方法把User查询好
// 3.2然后根据User中定义的AssociationForeignKey去查找Card
u := User{Username: "zhangsan"}
db.First(&u)
//fmt.Printf("%v", u.Username)
err := db.Model(&u).Association("CreditCard").Find(&u.CreditCard)
if err != nil {
fmt.Println("err", err)
}
fmt.Println(u)
// 转成json数据
strUser, err := json.Marshal(&u)
if err != nil {
fmt.Println("err", err)
}
fmt.Println(string(strUser))
结果
{"ID":1,"CreatedAt":"2022-12-07T10:55:11.397+08:00","UpdatedAt":"2022-12-07T11:33:36.351+08:00","DeletedAt":null,"username":"zhangs
an","CreditCard":[{"ID":1,"CreatedAt":"2022-12-07T10:55:11.415+08:00","UpdatedAt":"2022-12-07T10:55:11.415+08:00","DeletedAt":null,
"Number":"00001","UserID":1},{"ID":2,"CreatedAt":"2022-12-07T10:55:11.415+08:00","UpdatedAt":"2022-12-07T10:55:11.415+08:00","Delet
edAt":null,"Number":"00002","UserID":1},{"ID":3,"CreatedAt":"2022-12-07T11:33:36.352+08:00","UpdatedAt":"2022-12-07T11:33:36.352+08
:00","DeletedAt":null,"Number":"00003","UserID":1}]}
7.2.2 Preload预加载
// 2. 创建一对多
// 2.1 定义User表
type User struct {
gorm.Model
Username string `json:"username" gorm:"column:username"`
// 添加外键关联
CreditCard []Card
}
// 2.2 定义Card表
type Card struct {
gorm.Model
Number string
// 这个就是User表关联的外键 名字是 结构体+主键
UserID uint
}
func main() {
// 1. 建立连接
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//fmt.Println(db)
// 3. 通过Preload进行一对多的查询
users := []User{}
db.Preload("CreditCard").Find(&users)
// 4. 通过FindOne查询
strUser, _ := json.Marshal(&users)
fmt.Println(string(strUser))
}
结果
[{"ID":1,"CreatedAt":"2022-12-07T10:55:11.397+08:00","UpdatedAt":"2022-12-07T11:33:36.351+08:00","DeletedAt":null,"username":"zhang
san","CreditCard":[{"ID":1,"CreatedAt":"2022-12-07T10:55:11.415+08:00","UpdatedAt":"2022-12-07T10:55:11.415+08:00","DeletedAt":null
,"Number":"00001","UserID":1},{"ID":2,"CreatedAt":"2022-12-07T10:55:11.415+08:00","UpdatedAt":"2022-12-07T10:55:11.415+08:00","Dele
tedAt":null,"Number":"00002","UserID":1},{"ID":3,"CreatedAt":"2022-12-07T11:33:36.352+08:00","UpdatedAt":"2022-12-07T11:33:36.352+0
8:00","DeletedAt":null,"Number":"00003","UserID":1}]}]
8. 多对多关系
双向的一对多,就是多对多
Many to Many 会在两个 model 中添加一张连接表。
8.1 many2many
type User struct {
gorm.Model
// Languages 是字段名
// gorm:"many2many指定了User表和Language是多对多关系 user_language是第三张表的表名
Languages []Language `gorm:"many2many:user_language"`
}
type Language struct {
gorm.Model
Name string
}
func main() {
// 1. 建立连接
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println(db)
// 3. 创建表结构
db.AutoMigrate(User{}, Language{})
}
执行后User和Language被创建,同时第三张表user_language也被创建
mysql> desc users;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> desc languages;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
| name | longtext | YES | | NULL | |
+------------+---------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> desc user_language;
+-------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+-------+
| user_id | bigint(20) unsigned | NO | PRI | NULL | |
| language_id | bigint(20) unsigned | NO | PRI | NULL | |
+-------------+---------------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
8.2 自定义第三张表
// 2. 定义多对多
// 一个用户可以多个地址,一个地址可以对应多个用户
type Persons struct {
Id string
Name string `gorm:"many2many:PersonAddress"`
}
type Addresses struct {
Id uint
Name string
}
// 3. 定义第三张表
type PersonAddresses struct {
PersonId string `gorm:"primaryKey"` // 对应表的主键
AddressId string `gorm:"primaryKey"` // 对应表的主键
CreateAt time.Time
}
func main() {
// 1. 建立连接
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(192.168.31.24:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println(db)
db.AutoMigrate(Persons{}, Addresses{}, PersonAddresses{})
}
创建后
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| addresses |
| languages |
| person_addresses |
| persons |
| user_language |
| users |
+-------------------+
6 rows in set (0.00 sec)
mysql> desc addresses;
+-------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| name | longtext | YES | | NULL | |
+-------+---------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> desc persons;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | varchar(191) | NO | PRI | NULL | |
| name | longtext | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> desc person_addresses;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| person_id | varchar(191) | NO | PRI | NULL | |
| address_id | varchar(191) | NO | PRI | NULL | |
| create_at | datetime(3) | YES | | NULL | |
+------------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
9. 中间件
9.1 中间件
- Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数.
- 这个钩子函数就是中间件,中间件适合处理一些公共的业务逻辑
9.2 全局中间件
// 定义一个全局中间件(所有路由都会使用)
// MiddleWare 函数名字,随便写.
// gin.HandlerFunc中间件必须要返回的方法
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("我是一个全局中间件")
}
}
func main() {
// 1. 实例化引擎
r := gin.Default()
// 5. 全局使用中间件
r.Use(MiddleWare())
// 2. 定义路由
r.GET("/", func(c *gin.Context) {
fmt.Println("访问了/")
c.JSON(200, gin.H{
"code": 200,
"msg": "Success",
})
})
fmt.Println("http://192.168.31.1:8000")
r.Run(":8000")
}
9.3 局部中间件
只对指定的路劲进行限制
// 定义一个全局中间件(所有路由都会使用)
// MiddleWare 函数名字,随便写.
// gin.HandlerFunc中间件必须要返回的方法
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("我是一个全局中间件")
}
}
func main() {
// 1. 实例化引擎
r := gin.Default()
// 5. 全局使用中间件
//r.Use(MiddleWare())
// 2. 定义路由
r.GET("/", func(c *gin.Context) {
fmt.Println("访问了/")
c.JSON(200, gin.H{
"code": 200,
"msg": "Success",
})
})
r.GET("/home", MiddleWare(), func(c *gin.Context) {
fmt.Println("访问了/home")
c.JSON(200, gin.H{
"code": 200,
"msg": "In Home",
})
})
fmt.Println("http://192.168.31.1:8000")
r.Run(":8000")
}
此时访问/不会再有加载中间件
但访问/home就会加载
访问了/
[GIN] 2022/12/07 - 15:26:20 | 200 | 0s | 192.168.31.1 | GET "/"
我是一个全局中间件
访问了/home
[GIN] 2022/12/07 - 15:26:26 | 200 | 608.7µs | 192.168.31.1 | GET "/home"
9.4 Next方法
- 先执行中间件.
- 执行c.next(),那么就交给了视图函数执行
- 视图函数执行完毕后,返回中间件继续往下执行
整个过程中MiddleWare只被调用了1次,处理完视图函数后继续执行后续操作.
// 定义一个全局中间件(所有路由都会使用)
// MiddleWare 函数名字,随便写.
// gin.HandlerFunc中间件必须要返回的方法
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("开始执行中间件")
c.Next()
fmt.Println("视图函数执行完成后再调用next()方法")
}
}
func main() {
// 1. 实例化引擎
r := gin.Default()
// 5. 全局使用中间件
r.Use(MiddleWare())
// 2. 定义路由
r.GET("/", func(c *gin.Context) {
fmt.Println("访问了/")
c.JSON(200, gin.H{
"code": 200,
"msg": "Success",
})
})
fmt.Println("http://192.168.31.1:8000")
r.Run(":8000")
}
结果
开始执行中间件
访问了/
视图函数执行完成后再调用next()方法
[GIN] 2022/12/07 - 15:31:53 | 200 | 0s | 192.168.31.1 | GET "/"
9.5 实现Token认证
/index页面无需登录即可访问 http://192.168.31.1:8000/index
/home目录需要登录验证成功才能访问 http://192.168.31.1:8000/home
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
Token := c.Request.Header.Get("token")
fmt.Println("获取到的Token为: ", Token)
if Token != "twgdh" {
c.String(403, "身份验证失败,请先登录!")
c.Abort() // 终止当前请求,不再转发给路由,处理函数不再执行
return
}
c.Next()
}
}
func main() {
r := gin.Default()
// 无需登录就能访问
r.GET("/index", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "Index Page!无需登录就能访问"})
})
// 需要登录才能访问
r.GET("/home", Auth(), func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "Home Page!您已登录成功"})
})
fmt.Println("http://192.168.31.1:3000/index")
fmt.Println("http://192.168.31.1:3000/home")
r.Run(":3000")
}
/index 无需token
/home 当token不等于twgdh时,提示登录失败
/home当获取到正确的token时,提示登录成功
10. Restful 风格
10.1 什么是RESTful
- Restful是一种软件架构风格(REST是Representational State Transfer的简称,中文意思为: 表征状态转移)
- REST从资源角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识
- 所有数据不过是通过网络获取还是操作(增删改查)的数据,都是资源,将一切数据视为资源就是REST区别于其他架构风格的最本质熟悉
10.2 web开发本质
- 对数据库的增删改查操作
- Restful风格就是把所有数据都当做资源,对表的操作就是对资源的操作
- 在url通过资源名称来指定资源
- 通过get/post/put/delete/patch 对资源进行操作
- get 获取数据信息(一条或多条)
- post 添加数据
- put 修改数据
- delete 删除数据
10.2.1 URL路径
路径都使用名词或名词的复数,不要使用动词
获取所有信息 GET /products
获取1条信息 GET /products/4
新增信息 POST /products
更新一条信息 PUT /products/4
10.2.2 请求方式
访问一个URL地址,采用不同的求情方式,代表要执行不同的操作
请求方式 | 说明 |
---|---|
GET | 获取一个或多个资源 |
POST | 新增资源数据 |
PUT | 修改资源数据 |
DELETE | 删除资源数据 |
10.2.3 过滤信息
通过?进行数据过滤
?limit=10 指定返回记录的数量
?offset=10 指定返回记录开始位置
?page=2&pagesize=100 指定第几页开始,每页的记录数
?sortby=name&order=asc 指定返回结果按照哪个熟悉排序,以及排序的顺序
10.2.4 响应状态码
状态码 | 含义 |
---|---|
2xx | 都是请求成功的 |
200 | 请求成功,一般用于GET和POST请求 |
201 | Created [POST/PUT/PATCH] 用户新建或修改数据成功 |
202 | Accept [*] 表示一个请求已经进入后台排队(异步任务) |
204 | NO CONTENT [DELETE],用户删除数据成功 |
3xx | 重定向 |
301 | 永久重定向 |
302 | 临时重定向 |
4xx | 客户端错误 |
400 | INVALID REQUEST [POST/PUT/PATCH] 用户发出的请求有错误. |
401 | Unauthorized [*] 授权错误 用户没有权限(令牌,用户名,密码错误) |
403 | Forbidden [*] 授权正确,但操作被禁止. 表示用户得到授权(与401相同),但访问是被禁止的. |
404 | NOT FOUND [*] 用户发出的请求记录不存在 |
406 | Not Acceptale [GET] 用户请求的格式不可得(比如需要json,提交了xml) |
410 | Gone [GET] 用户请求的资源被永久删除了,切不会再得到. |
422 | Unprocesable entity [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误 |
5xx | 服务端错误 |
500 | INTERNAL SERVER ERROR [*] 服务器内部错误,无法完成请求 |
501 | Not Implemented 服务器不支持请求的功能,无法完成请求 |