实现函数对象
在 Go 中,可以使用函数类型和闭包来实现类似于 C++ 函数对象(Functor)的功能。函数类型可以作为参数传递和返回值,而闭包可以捕获外部变量并形成一个可调用的函数对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main
import "fmt"
// 定义一个函数类型
type MyFunc func(int) int
// 函数对象, 返回类型为 MyFunc
func AddN(n int) MyFunc {
return func(x int) int {
return x + n
}
}
func main() {
// 创建一个函数对象
addTwo := AddN(2)
// 调用函数对象
result := addTwo(3)
fmt.Println(result) // 输出:5
}
|
分层设计的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
type Response struct {
StatusCode int32 `json:"status_code"`
StatusMsg string `json:"status_msg,omitempty"`
}
type Video struct {
Id int64 `json:"id,omitempty"`
Author User `json:"author"`
PlayUrl string `json:"play_url" json:"play_url,omitempty"`
CoverUrl string `json:"cover_url,omitempty"`
FavoriteCount int64 `json:"favorite_count,omitempty"`
CommentCount int64 `json:"comment_count,omitempty"`
IsFavorite bool `json:"is_favorite,omitempty"`
}
type Comment struct {
Id int64 `json:"id,omitempty"`
User User `json:"user"`
Content string `json:"content,omitempty"`
CreateDate string `json:"create_date,omitempty"`
}
type User struct {
Id int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
FollowCount int64 `json:"follow_count,omitempty"`
FollowerCount int64 `json:"follower_count,omitempty"`
IsFollow bool `json:"is_follow,omitempty"`
}
type Message struct {
Id int64 `json:"id,omitempty"`
Content string `json:"content,omitempty"`
CreateTime string `json:"create_time,omitempty"`
}
type MessageSendEvent struct {
UserId int64 `json:"user_id,omitempty"`
ToUserId int64 `json:"to_user_id,omitempty"`
MsgContent string `json:"msg_content,omitempty"`
}
type MessagePushEvent struct {
FromUserId int64 `json:"user_id,omitempty"`
MsgContent string `json:"msg_content,omitempty"`
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
type User struct {
UserId int64 `gorm:"primary_key"`
Name string
// FollowingCount int64 `gorm:"default:(-)"`
// FollowerCount int64 `gorm:"default:(-)"`
Password string
// Avatar string `gorm:"default:(-)"`
// BackgroundImage string `gorm:"default:(-)"`
Signature string
// TotalFavorited int64 `gorm:"default:(-)"`
// WorkCount int64 `gorm:"default:(-)"`
// FavoriteCount int64 `gorm:"default:(-)"`
// CreateAt time.Time
// DeleteAt time.Time
}
type Video struct {
VideoId int64 `gorm:"primaryKey"`
UserId int64
PlayUrl string
CoverUrl string
FavoriteCount int64
CommentCount int64
// Title string `gorm:"default:(-)"`
CreateAt time.Time
UpdateAt time.Time
DeleteAt time.Time
}
func GetUserDao() *UserDao
func (*UserDao) CreateUser(user *User) (int64, error)
func (*UserDao) FindUserByName(username string) (*User, error)
func (d *UserDao) FindUserById(id int64) (*User, error)
func GetVideoDao() *VideoDao
func (*VideoDao) CreateVideo(video *Video) (*Video, error)
func (d *VideoDao) FindVideoById(id int64) (*Video, error)
func (*VideoDao) QueryVideoByUserId(userId int64) ([]*Video, error)
// 根据时间和需要查询的条数,获取video列表
func (*VideoDao) QueryVideo(date *string, limit int) []*Video
|
test
Go 语言中的测试依赖 go test 命令。编写测试代码和编写普通的 Go 代码过程是类似的,并不需要学习新的语法、规则或工具。
go test 命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go 为后缀名的源代码文件都是 go test 测试的一部分,不会被 go build 编译到最终的可执行文件中。
在 *_test.go
文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。
类型 |
格式 |
作用 |
测试函数 |
函数名前缀为 Test |
测试程序的一些逻辑行为是否正确 |
基准函数 |
函数名前缀为 Benchmark |
测试函数的性能 |
示例函数 |
函数名前缀为 Example |
为文档提供示例文档 |
Golang 单元测试对文件名和方法名,参数都有很严格的要求。
-
文件名必须以xx_test.go
命名
-
方法必须是Test[^a-z]
开头
-
方法参数必须 t *testing.T
-
使用go test执行单元测试
go test参数
1
|
go test [-c] [-i] [build flags] [packages] [flags for test binary]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// split/split.go
package split
import "strings"
// split package with a single split function.
// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) (result []string) {
i := strings.Index(s, sep)
for i > -1 {
result = append(result, s[:i])
s = s[i+1:]
i = strings.Index(s, sep)
}
result = append(result, s)
return
}
|
在当前目录下,我们创建一个 split_test.go 的测试文件,并定义一个测试函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// split/split_test.go
package split
import (
"reflect"
"testing"
)
func TestSplit(t *testing.T) { // 测试函数名必须以Test开头,必须接收一个*testing.T类型参数
got := Split("a:b:c", ":") // 程序输出的结果
want := []string{"a", "b", "c"} // 期望的结果
if !reflect.DeepEqual(want, got) { // 因为slice不能比较直接,借助反射包中的方法比较
t.Errorf("excepted:%v, got:%v", want, got) // 测试失败输出错误提示
}
}
|
测试结果
1
2
3
|
$ go test
PASS
ok main/ttt 0.002s
|
log
OS
1
2
3
4
5
6
|
// 如果文件已存在会截断它(为空文件)。如果成功,返回的文件对象可用于 I/O;
func Create(name string) (file *File, err error)
func Open(name string) (file *File, err error)
func Rename(oldpath, newpath string) error
|
IO
1、Reader
1
2
3
|
type Reader interface {
Read(p []byte) (n int, err error)
}
|
Reader 是包装基本 Read 方法的接口。
Read 最多将 len (p) 个字节读入 p。它返回读取的字节数 (0 <= n <= len (p)) 和遇到的错误。即使 Read 返回 n < len (p),它也可能在调用期间使用所有 p 作为暂存空间。
如果有一些数据可用,但长度不为 len (p) ,则 Read 通常会返回可用的数据,而不是等待更多数据。
当 Read 遇到错误,或者在成功读取 n > 0 个字节后,遇到 end-of-file 条件时,返回读取的字节数。它可能会从同一次调用中返回(非空)错误,或者从后续调用中返回错误(并且 n == 0)。
这种情况一个例子是:Reader 在读取到输入流末尾时,返回一个非零的字节数,并返回 err == EOF 或者 err == nil。下一次读取应该返回 0,EOF。
在考虑错误错误之前,调用者应始终处理返回的 n > 0 字节。这样做可以正确处理读取某些字节后发生的 I/O 错误以及允许的 EOF 行为。
对于 Read 的实现,不鼓励同时返回零字节计数和 nil 错误,除非 len (p) == 0。
调用者应将返回 0 和 nil 视为表示没有发生任何事情;特别是它不表示 EOF。
实现不得持有 p。
2、Writer
1
2
3
|
type Writer interface {
Write(p []byte) (n int, err error)
}
|
Writer 是封装了基本 Write 方法的接口。
Write 将 len (p) 个字节从 p 写入底层数据流。
它返回从 p (0 <= n <= len (p)) 写入的字节数,以及遇到的任何导致写入提前停止的错误。
如果返回 n <len (p),则写入必须返回非零错误。
写入不能修改切片数据,即使是临时的。
实现不得保留 p。
3、Closer
1
2
3
|
type Closer interface {
Close() error
}
|
Closer 是封装了基本 Close 方法的接口。
第一次调用 Close 后的行为未定义。
具体实现的行为可以查看文档。
4、Seeker
1
2
3
|
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
|
Seeker 是封装了基本 Seek 方法的接口。
Seek 将下一次读取或写入的偏移量设置为偏移量,根据 wherece
进行解释:
SeekStart
表示相对于文件的开头,
SeekCurrent
表示相对于当前偏移量,并且
SeekEnd
表示相对于终点(例如,offset = -2 指定文件的倒数第二个字节)。
Seek 返回相对于文件开头的新偏移量或错误(如果有)。
Seek 到开始之前的偏移量是错误的。
可以允许 Seek 任何正偏移量,但如果新偏移量超过底层对象的大小,则后续 I/O 操作的行为取决于实现。
bufio
bufio 是 Go 语言标准库中用于缓存 I/O 的包。当程序频繁进行 I/O 读写操作时,使用 bufio 包可以减少系统调用次数,大大提高程序性能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// NewReader 创建一个具有默认大小缓冲、从 r 读取的 * Reader。NewReader 相当于 NewReaderSize (rd, 4096)
func NewReader(rd io.Reader) *Reader
// NewReaderSize 创建一个具有最少有 size 尺寸的缓冲、从 r 读取的 Reader。如果参数 r 已经是一个具有足够大缓冲的 Reader 类型值,会返回 r。
func NewReaderSize(rd io.Reader, size int) *Reader
// Reset 丢弃缓冲中的数据,清除任何错误,将 b 重设为其下层从 r 读取数据。
func (b *Reader) Reset(r io.Reader)
// Buffered 返回缓冲中现有的可读取的字节数。
func (b *Reader) Buffered() int
// 返回输入流的下 n 个字节, 而不会移动读取位置。
func (b *Reader) Peek(n int) ([]byte, error)
|
string
1
2
|
// strings.NewReader() 可以方便地将一个字符串转换为实现了 io.Reader 接口的读取器
func NewReader(s string) *Reader
|
GORM
ORM(对象关系映射)框架是一种软件工具,用于在面向对象编程语言和关系型数据库之间建立映射关系。它提供了一种将数据库表格的数据映射到面向对象编程语言中的对象的方法,使得开发人员可以使用面向对象的方式来操作数据库。
ORM 框架的主要目标是解决对象模型与关系数据库之间的不匹配问题。在传统的关系型数据库中,数据是以表格的形式进行组织和存储的,而在面向对象编程语言中,数据是以对象的形式表示的。ORM 框架通过自动将对象转换为数据库中的表格以及表格中的行和列转换为对象的属性和关系,实现了面向对象编程语言和关系型数据库之间的无缝集成。
ORM 框架通常提供了一组 API 和工具,用于执行以下任务:
- 对象关系映射:将数据库表格的数据映射到面向对象编程语言中的对象,并将对象的属性和关系映射到数据库的列和行。
- 数据库操作:提供了一组简化的方法来执行常见的数据库操作,如插入、更新、删除和查询数据。
- 数据库事务管理:支持事务操作,确保数据库操作的一致性和完整性。
- 查询语言:提供了一种类似于编程语言的接口,用于指定和执行复杂的数据库查询。
- 缓存管理:提供了缓存机制,以减少对数据库的频繁访问,并提高性能。
- 数据库迁移:简化数据库结构的变更和迁移操作,保证数据的一致性和无缝升级。
模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
go 语言 gorm 基本使用_go gorm_含光君~的博客 - CSDN 博客
- Gorm使用名为
ID
的字段作为主键
- GORM 支持 MySQLSQLServer、 PostgreSQLSQLite.
demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"fmt"
"os"
)
type User struct { // 默认表名是`users`
gorm.Model
Name string
}
func main() {
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
os.Getenv("MYSQL_USER"),
os.Getenv("MYSQL_PASSWORD"),
os.Getenv("MYSQL_HOST"),
os.Getenv("MYSQL_PORT"),
// conf.UserName,
// conf.Password,
// conf.Url,
// conf.Port,
conf.DBName,
)
// 创建了一个新的 gorm.Config 对象,并将其作为参数传递给 Open 函数
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 自动迁移
db.AutoMigrate(&User{})
// 创建
db.Create(&User{Name: "张三"})
// 查询
res := User{}
db.Where(&User{Name: "张三"}).First(&res)
// 不为 字符串
db.Where(&models.User{UserId:17}).First(&user)
db.Where("user_id=?", 18).First(&user)
db.Find(&res, "hobby=?", "足球")
// 批量查询
var list = make([]User, 9)
models.SqlSession.Where("user_id<?", 18).Find(&list)
fmt.Printf("%+v", list) // 输出了1~17
// 单列修改
// 该列名不存在,那么 GORM 将不会执行任何操作,也不会返回任何错误。
db.Model(&User{}).Where("user_id = ?", 17).Update("name", "changed")
// Close
sqlDB, err := db.DB()
sqlDB.Close()
}
|
定义数据类型
字段 |
解释 |
autoIncrement |
指定列为自动增长 |
primaryKey |
将列定义为主键 |
unique |
将列定义为唯一键 |
default |
定义列的默认值 |
not null |
指定列为 NOT NULL |
autoCreateTime |
创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime |
创建 / 更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
// 内部定义类型, 直接引用就好
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// 对于匿名字段,GORM 会将其字段包含到其父结构中
// 表名默认是模型的复数形式,列名默认是字段名的驼峰形式
type User struct { // 默认表名是`users`
gorm.Model
Name string
}
// equals
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
// default 的使用
type User struct {
ID uint `gorm:"not null;comment:'用户ID'"`
Name string `gorm:"type:varchar(50);default:'张三';comment:'姓名'"`
Age int `gorm:"default:'18';comment:'年龄'"`
}
type User struct{
UserId int64 `gorm:"primary_key"`
Name string
Password string
signature string
CreatedAt time.Time `gorm:"default(autoCreateTime)"`
// DeleteAt time.Time
}
|
连接/ 关闭数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
// 创建了一个新的 gorm.Config 对象,并将其作为参数传递给 Open 函数
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// Close
sqlDB, err := db.DB()
sqlDB.Close()
}
|
使用环境变量连接数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package main
import (
"database/sql"
"fmt"
"log"
"os"
"strconv"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", getConnectionString())
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Ping()
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MySQL database!")
}
func getConnectionString() string {
server := os.Getenv("MYSQL_HOST")
port := os.Getenv("MYSQL_PORT")
user := os.Getenv("MYSQL_USER")
password := os.Getenv("MYSQL_PASSWORD")
database := "mydatabase"
portInt, err := strconv.Atoi(port)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", user, password, server, portInt, database)
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
conf.UserName,
conf.Password,
conf.Url,
conf.Port,
conf.DBName,
)
|
创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // pass pointer of data to Create
// 批量处理数据
users := []*User{
User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}
result := db.Create(users) // pass a slice to insert multiple row
// 如果 users 中的 user 已经存在于数据库中,则会返回一个错误。具体的错误类型和错误信息取决于所使用的 ORM 库和数据库驱动程序。
result.Error // returns error
result.RowsAffected // returns inserted records count
|
简单查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// 函数原型
DB.First(dest interface{}, conds ...interface{}) error
// 获取第一条记录,按主键排序
db.First(&user);
select * from users order by id limit 1;
// 获取最后一条记录,按主键排序
db.Last(&user);
select * from users order by id desc limit 1;
// 获取所有记录
db.Find(&user);
select * from users;
// GORM 允许通过内联条件指定主键来检索对象,但只支持整形数值,因为 string 可能导致 SQL 注入。
// select * from users where id =10;
db.First(&user,10);
// 如果表里没有数据,会报错 result.Error返回:ErrRecordNotFound,
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
|
select 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// select name, age from users;
db.Select("name","age").Find(&user);
db.Select([]string{"name","age"}).Find(&user);
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
SELECT COALESCE(age,'42') FROM users;
// 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;
// 主键切片条件
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
|
返回多条记录
1
2
3
4
5
6
|
var userList []gorme.User
// 指针查询字段
result := mysqlClient.Select("id", "nick_name").Find(&userList)
for _, user := range userList {
fmt.Printf("id: %d nick_name: %s \n", user.ID, user.NickName)
}
|
更新
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func (db *DB) Update(column string, value interface{}) (tx *DB)
// 条件更新
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;
|
不存在则创建
1
|
db.FirstOrCreate(&user, User{Name: "John Doe"})
|
net/ http
Http・Go 语言中文文档 (topgoer.com)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package main
import (
"fmt"
"net/http"
)
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Println("handle hello") // 服务端打印输出
fmt.Fprintf(w, "hello GoLangWEB")
}
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("handle login") // 服务端打印输出
fmt.Fprintf(w, "login...")
}
func main() {
http.HandleFunc("/", Hello) // 匹配根路由
http.HandleFunc("/login", login) // 匹配根路由/login
err := http.ListenAndServe("0.0.0.0:8080", nil)
if err != nil{
fmt.Println("http listen failed")
}
}
|
客户端方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
resp, err := http.Get("http://5lmh.com/")
// 程序在使用完 response 后必须关闭回复的主体。
if err != nil {
// handle error
}
defer resp.Body.Close()
...
resp, err := http.Post("http://5lmh.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://5lmh.com/form",
url.Values{"key": {"Value"}, "id": {"123"}})
body, err := ioutil.ReadAll(resp.Body)
// ...
|
客户端参数 net/url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func ParseRequestURI(requestURI string) (*url.URL, error)
// requestURI 是要解析的 URL 请求 URI,而返回值是一个指向 url.URL 结构体的指针
type URL struct {
Scheme string // 协议,例如 "http"、"https" 等
Opaque string // 不透明部分,用于存储不透明数据,例如 "//example.com/path"
User string // 用户名,例如 "user"
Password string // 密码,例如 "passwd"
Host string // 域名或 IP 地址,例如 "example.com" 或 "127.0.0.1"
port string // 端口号,例如 "8080"
path string // 路径,例如 "/path/to/file.html"
query string // 查询字符串,例如 "key1=value1&key2=value2"
fragment string // 片段标识符,例如 "#fragment"
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// net/url 处理带参数的请求
func main() {
apiUrl := "http://127.0.0.1:9090/get"
// URL param
data := url.Values{}
data.Set("name", "枯藤")
data.Set("age", "18")
u, err := url.ParseRequestURI(apiUrl)
if err != nil {
fmt.Printf("parse url requestUrl failed,err:%v\n", err)
}
u.RawQuery = data.Encode() // URL encode
fmt.Println(u.String())
resp, err := http.Get(u.String())
if err != nil {
fmt.Println("post failed, err:%v\n", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("get resp failed,err:%v\n", err)
return
}
fmt.Println(string(b))
}
|
post方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
// net/http post demo
func main() {
url := "http://127.0.0.1:9090/post"
// 表单数据
//contentType := "application/x-www-form-urlencoded"
//data := "name=枯藤&age=18"
// json
contentType := "application/json"
data := `{"name":"枯藤","age":18}`
resp, err := http.Post(url, contentType, strings.NewReader(data))
if err != nil {
fmt.Println("post failed, err:%v\n", err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("get resp failed,err:%v\n", err)
return
}
fmt.Println(string(b))
}
|
服务器端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// http server
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello 枯藤!")
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Printf("http server failed, err:%v\n", err)
return
}
}
|
gin
官方文档
Gin 的基本架构:
- Router:它是 Gin 应用程序的核心部分,它接收 HTTP 请求并根据请求的路径和 HTTP 方法将其路由到正确的处理程序。
- Handlers:它是 Gin 应用程序中的业务逻辑代码,它接收 HTTP 请求并返回 HTTP 响应。
- Middleware:它是在 HTTP 请求和处理程序之间发生的代码。它可以用来完成一些常见的操作,如身份验证、日志记录、处理请求等。
gin.context
- Context 是gin中最重要的部分。 例如,它允许我们在中间件之间传递变量、管理流程、验证请求的 JSON 并呈现 JSON 响应。
- Context 中封装了原生的 Go http 请求和响应对象,同时还提供了一些方法,用于获取请求和响应的信息、设置响应头、设置响应状态码等操作。
- 在 Gin 中,Context 是通过中间件来传递的。在处理 HTTP请求 时,Gin 会依次执行注册的中间件,每个中间件可以对 Context 进行一些操作,然后将 Context 传递给下一个中间件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
params *Params
skippedNodes *[]skippedNode
// 该互斥锁保护键映射。
mu sync.RWMutex
// Keys 是专门用于每个请求上下文的键/值对。
Keys map[string]any
// 错误是附加到使用此上下文的所有处理程序/中间件的错误列表。
Errors errorMsgs
// 已接受定义了用于内容协商的手动接受格式的列表。
Accepted []string
// queryCache 缓存 c.Request.URL.Query() 的查询结果。
queryCache url.Values
// formCache 缓存 c.Request.PostForm,其中包含从 POST、PATCH 或 PUT 正文参数解析的表单数据。
formCache url.Values
// SameSite 允许服务器定义 cookie 属性,从而使浏览器无法随跨站点请求发送此 cookie。
sameSite http.SameSite
}
|
下面是 Context 提供的一些常用方法:
- Context.Key ():获取上下文中的键值。
- Context.Get ():获取上下文中的值,如果不存在则返回默认值。
- Context.Set ():设置上下文中的值。
- Context.Map ():获取上下文中的所有键值对。
- Context.PostForm ():获取请求的表单数据。
- Context.Query ():获取请求的查询字符串参数。
- Context.Param ():获取请求的路径参数。
- Context.Cookie ():获取请求的 cookie。
- Context.Redirect ():重定向到指定的 URL。
- Context.Render ():渲染模板并返回响应。
- Context.String ():返回一个字符串响应。
- Context.HTML ():返回一个 HTML 响应。
- Context.JSON ():返回一个 JSON 响应。
- Context.XML ():返回一个 XML 响应。
快速使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个Gin路由器
r := gin.Default()
// 定义路由
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello World!",
})
})
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong!",
})
})
r.POST("/submit", func(c *gin.Context) {
var json struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := c.Bind(&json); err == nil {
c.JSON(200, gin.H{
"message": "JSON received",
"name": json.Name,
"email": json.Email,
})
} else {
c.JSON(400, gin.H{"error": err.Error()})
}
})
// 启动Gin应用程序
r.Run()
}
|
json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func main() {
r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) {
data := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
// 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
c.AsciiJSON(http.StatusOK, data)
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
|
获取参数
- 可以通过 Context 的 Param 方法来获取 API 参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
//截取/
action = strings.Trim(action, "/")
c.String(http.StatusOK, name+" is "+action)
})
//默认为监听8080端口
r.Run(":8000")
}
|
其中 :name
和 *action
都是动态参数。当客户端向服务器发送 GET /user/小明/list
的请求时,服务器将匹配到该路由,并将 小明
和 list
两个参数的值分别赋值给 :name
和 *action
。
在 Go 语言的路由匹配中,:
和 *
符号具有不同的含义:
:
符号表示一个动态参数,它可以匹配任意长度的字符串,包括空字符串。例如,r.GET("/user/:name")
表示一个处理 GET
请求的路由,其中 :name
是一个动态参数,它可以匹配任意长度的字符串,包括空字符串。
*
符号表示一个可选的动态参数,它可以匹配任意长度的字符串,但不能为空字符串。例如,r.GET("/user/:name/*action")
表示一个处理 GET
请求的路由,其中 :name
是一个动态参数,而 *action
是一个可选的动态参数。当客户端向服务器发送 GET /user/小明/list
的请求时,服务器将匹配到该路由,并将 小明
和 list
两个参数的值分别赋值给 :name
和 *action
。但是,如果客户端发送 GET /user/小明
的请求,则服务器将无法匹配该路由,因为 *action
参数不能为空字符串。
路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package main
import (
"github.com/gin-gonic/gin"
"fmt"
)
// gin的helloWorld
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 路由组1 ,处理GET请求
v1 := r.Group("/v1")
// {} 是书写规范
{
v1.GET("/login", login)
v1.GET("submit", submit)
}
v2 := r.Group("/v2")
{
v2.POST("/login", login)
v2.POST("/submit", submit)
}
r.Run(":8000")
}
func login(c *gin.Context) {
name := c.DefaultQuery("name", "jack")
c.String(200, fmt.Sprintf("hello %s\n", name))
}
func submit(c *gin.Context) {
name := c.DefaultQuery("name", "lily")
c.String(200, fmt.Sprintf("hello %s\n", name))
}
|
GET /user//res
是合法的么
因为 GET
请求的路径中包含了两个连续的 /
符号,所以它将匹配到 GET /user//res
这个路由。在这个路由中,两个 /
符号之间的部分是一个可选的动态参数,它可以是任意长度的字符串,但不能为空字符串。因此,如果客户端向服务器发送 GET /user//res
的请求,则服务器将匹配到该路由,并将 res
参数的值赋值给该动态参数。
中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
// 定义中间
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
// 执行函数
c.Next()
// 中间件执行完后续的一些事情
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 注册中间件
r.Use(MiddleWare())
// {}为了代码规范
{
r.GET("/ce", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
}
r.Run()
}
|
日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func main() {
// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
gin.DisableConsoleColor()
// 记录到文件。
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
// 如果需要同时将日志写入文件和控制台,请使用以下代码。
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080")
}
|
回复
1
2
3
4
5
6
7
|
// 回复 json 字符串
func JSON(c *Context, code int, obj interface{}) error
c.JSON(200, map[string]interface{}{
"message": "Hello, World!",
})
|
gin 项目目录结构
其中:
- configs 目录包含项目的配置文件
- controllers 目录包含控制器文件
- middleware 目录包含中间件文件
- models 目录包含模型(ORM)文件
- routes 目录包含路由文件
- services 目录包含服务文件
- utils 目录包含多个工具文件
- main.go 是项目的入口文件,
README.md
是项目的介绍文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
├── configs
│ ├── config.yaml
│ └── db.yaml
├── controllers
│ ├── auth_controller.go
│ └── user_controller.go
├── middleware
│ ├── auth_middleware.go
│ └── logger_middleware.go
├── models
│ ├── db.go
│ ├── user.go
│ └── ...
├── routes
│ └── routes.go
├── services
│ ├── auth_service.go
│ └── user_service.go
├── utils
│ ├── response.go
│ └── ...
├── main.go
└── README.md
|
YAML
go-yaml/yaml: YAML support for the Go language. (github.com)
官方文档
YAML 全称是 ”YAML Ain’t a Markup Language” 的递归缩写,该语言的设计参考了 JSON / XML 和 SDL 等语言,强调以数据为中心,简洁易读,编写简单。
语法特点
- 大小写敏感
- 通过缩进表示层级关系
- 禁止使用 tab 缩进,只能使用空格键
- 缩进的空格数目不重要,只要相同层级左对齐
- 使用 # 表示注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# 以下是yaml
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org
# 以下是Json
{
languages: [
'Ruby',
'Perl',
'Python'
],
websites: {
YAML: 'yaml.org',
Ruby: 'ruby-lang.org',
Python: 'python.org',
Perl: 'use.perl.org'
}
}
|
属性
omitempty
是一个属性,用于指示该字段是否可以为空。如果这些字段没有值,它们将不会被序列化到 YAML 文件中
使用说明
1
|
func Marshal(in interface{}) (out []byte, err error)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v3"
)
var data = `
a: Easy!
b:
c: 2
d: [3, 4]
`
// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
A string
B struct {
RenamedC int `yaml:"c"`
D []int `yaml:",flow"`
}
}
func main() {
t := T{}
err := yaml.Unmarshal([]byte(data), &t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t:\n%v\n\n", t)
d, err := yaml.Marshal(&t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t dump:\n%s\n\n", string(d))
m := make(map[interface{}]interface{})
err = yaml.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m:\n%v\n\n", m)
d, err = yaml.Marshal(&m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m dump:\n%s\n\n", string(d))
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
--- t:
{Easy! {2 [3 4]}}
--- t dump:
a: Easy!
b:
c: 2
d: [3, 4]
--- m:
map[a:Easy! b:map[c:2 d:[3 4]]]
--- m dump:
a: Easy!
b:
c: 2
d:
- 3
- 4
|
jwt
摘要
golang-jwt/jwt: Community maintained clone of https://github.com/dgrijalva/jwt-go
Starting with v4.0.0 this project adds Go module support
在介绍 JWT 之前,我们先来回顾一下利用 token
进行用户身份验证的流程:
- 客户端使用用户名和密码请求登录
- 服务端收到请求,验证用户名和密码
- 验证成功后,服务端会签发一个
token
,再把这个 token
返回给客户端
- 客户端收到 token 后可以把它存储起来,比如放到 cookie 中
- 客户端每次向服务端请求资源时需要携带服务端签发的 token,可以在
cookie
或者 header
中携带
- 服务端收到请求,然后去验证客户端请求里面带着的
token
,如果验证成功,就向客户端返回请求数据
这种基于 token
的认证方式相比传统的 session
认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:
JWT 在线解码 - 开发工具箱 (box3.cn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJsaXF3ZXR0IiwiZXhwIjoxNjkzMjk1MDY3LCJpYXQiOjE2OTMyMDg2NjgsImlzcyI6ImRlbW8iLCJuYmYiOjE2OTMyMDg2Njh9.GWPbzwiNBsCuAeE_dbn21xjAlGpK7Xh683F4-X1NPJc
{
"alg": "HS256",
"typ": "JWT"
}
{
"aud": "liqwett",
"exp": 1693295067,
"iat": 1693208668,
# 发行人
"iss": "demo",
"nbf": 1693208668
}
|
结构
JWT 由 3 部分组成:标头 (Header)、有效载荷 (Payload) 和签名 (Signature)
Header
JWT 头是一个描述 JWT 元数据的 JSON 对象,alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为 HS256);typ 属性表示令牌的类型,JWT 令牌统一写为 JWT。最后,使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存
1
2
3
4
|
{
"alg": "HS256",
"typ": "JWT"
}
|
Playload —有效荷载部分
默认情况下 JWT 是未加密的,因为只是采用 base64 算法,拿到 JWT 字符串后可以转换回原本的 JSON 数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到 JWT 中,以防止信息泄露。 JWT 只是适合在网络中传输一些非敏感的信息
jwt 指定七个默认字段共选择
1
2
3
4
5
6
7
|
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
|
也可以定义私有字段, 一般吧包含用户信息的数据放到playload
中
1
2
3
4
5
|
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
|
signature
- 需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。
- 使用 header 中指定的签名算法(默认情况下为 HMAC SHA256)根据以下公式生成签名
$\text{HMACSHA256}(\text{base}64\text{UrlEncode}(\text{header})+\text{“.”}+\text{base}64\text{UrlEncode}(\text{payload}),\text{secret})$
在计算出签名哈希后,JWT 头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.
分隔,就构成整个 JWT 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
base64enc({
"alg": "HS256",
"typ": "JWT"
})
.
base64enc({
"iss": "toptal.com",
"exp": 1426420800,
"company": "Toptal",
"awesome":true
})
.
HMACSHA256(
base64enc(header)+ '.'+ base64enc(payload)
, secretKey )
|
签名算法
到目前为止,jwt 的签名算法有三种:
- HMAC【哈希消息验证码 (对称)】:HS256/HS384/HS512
- RSASSA【RSA 签名算法 (非对称)】(RS256/RS384/RS512)
- ECDSA【椭圆曲线数据签名算法 (非对称)】(ES256/ES384/ES512)
jwt 产生的 token 还需要加密么
签名的目的是为了验证 JWT 的完整性和真实性。当服务器接收到 JWT 时,它可以使用相同的密钥对 JWT 进行解密和验证签名。如果签名验证成功,服务器可以相信 JWT 中的数据是可信的。
因此,一般情况下,JWT 的签名已经提供了足够的安全性,不需要额外的加密。然而,如果你对数据的保密性有更高的要求,你可以使用加密算法对载荷部分进行加密,然后将加密后的数据放入 JWT 的载荷中。
在传输 JWT 时可能会被其他人窃取,这确实存在一定的风险。JWT 本身是基于文本的,因此在传输过程中可能会被截获或窃取。
为了减轻这种风险,有几个常见的做法可以采用:
- 使用 HTTPS:使用 HTTPS 协议进行通信可以加密传输的数据,从而提供更高的安全性。这样,即使 JWT 被截获,攻击者也无法轻易解密和篡改其中的数据。
- 限制 JWT 的有效期:在生成 JWT 时,可以设置一个较短的有效期,使得 JWT 在一段时间后自动失效。这样即使 JWT 被截获,攻击者也只能在有效期内使用,有效期过后就无法继续使用。
- 避免在 JWT 中存储敏感信息:尽量避免将敏感信息直接存储在 JWT 的载荷中,特别是不要将密码等敏感凭据放在 JWT 中。如果需要传递敏感信息,可以考虑使用加密算法对敏感数据进行加密,然后将加密后的数据放入 JWT 的载荷中。
- 使用签名验证:在服务器端接收到 JWT 后,始终要进行签名验证,确保 JWT 的完整性和真实性。如果签名验证失败,说明 JWT 可能已被篡改,应该拒绝接受该 JWT。
使用说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v4"
)
func main() {
// 创建秘钥
key := []byte("aaa")
// 创建Token结构体
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user": "zhangshan",
"pass": "123123",
})
// 调用加密方法,发挥Token字符串
signingString, err := claims.SignedString(key)
if err != nil {
return
}
fmt.Println(signingString)
// 根据Token字符串解析成Claims结构体
_, err = jwt.ParseWithClaims(signingString, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
fmt.Println(token.Header)
return []byte("aaa"), nil
})
if err != nil {
fmt.Println(err)
return
}
}
//这边是输出结果
&{ 0xc0000c2690 map[alg:ES256 typ:JWT] map[user:zhangshan] false}
// 这是加密后的字符串
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXNzIjoiMTIzMTIzIiwidXNlciI6InpoYW5nc2hhbiJ9.-2-xIJXMGKV-GyhM24OKbDVqWs4dsIANBsGhzXEfEFM
|
结构映射器
实际项目开发中,需要各个层的数据流转,entity 转换到 domain,domain 转换到 response,常见实现的方式基本都是通过反射机制实现,例如 Spring BeanUtils. copyProperties,Golang 的 copier 第三方库,反射的效率众所周知,基本都是惨不忍睹。
当然也可以手动一个个的赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
type User struct {
ID int
Mobile string
Level int
Name string
RegisterTime time.Time
}
func (u *User) Mapping (from *User) {
u.ID = from.ID
u.Mobile = from.Mobile
u.Level = from.Level
u.Name = from.Name
u.RegisterTime = from.RegisterTime
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
//go:generate go run github.com/wizardshan/structmapper -toName User -fromName User -toPath ./response -fromPath ./domain -toVar resp -fromVar dom
//go:generate go run github.com/wizardshan/structmapper -toName Orders -fromName Orders -toPath ./response -fromPath ./domain -toVar resp -fromVar dom
package response
type Users []*User
type User struct {
ID int `json:"id"`
CreateTime DateTime `json:"createTime"`
UpdateTime *DateTime `json:"updateTime"`
DeleteTime *DateTime `json:"deleteTime"`
Mobile string `json:"mobile"`
Nickname string `json:"nickname"`
Money Money `json:"money"`
Level int `json:"level"`
}
package domain
type Users []*User
type User struct {
ID int
CreateTime time.Time
UpdateTime *time.Time
Mobile string
Nickname string
Money int
}
func (dom *User) Level() int {
return 1
}
user_mapping.go
func (resp *User) Mapping(dom *domain.User) {
/**************** mapping start ****************/
resp.ID = dom.ID
resp.CreateTime = DateTime(dom.CreateTime)
if dom.UpdateTime != nil {
updateTime := DateTime(*dom.UpdateTime)
resp.UpdateTime = &(updateTime)
}
//resp.DeleteTime = fromStruct property not exist
resp.Mobile = dom.Mobile
resp.Nickname = dom.Nickname
resp.Money = Money(dom.Money)
resp.Level = dom.Level()
/**************** mapping end ****************/
}
orders_mapping.go
func (resp *Orders) Mapping(dom domain.Orders) {
/**************** mapping start ****************/
domOrdersLen := len(dom)
*resp = make(Orders, domOrdersLen)
if domOrdersLen > 0 {
for domOrdersIndex := 0; domOrdersIndex < domOrdersLen; domOrdersIndex++ {
domOrdersItem := dom[domOrdersIndex]
respOrder := new(Order)
respOrder.ID = domOrdersItem.ID
respOrder.Status = domOrdersItem.Status
respOrder.Consignee = domOrdersItem.Consignee
respOrder.Mobile = domOrdersItem.Mobile
respOrder.Province = domOrdersItem.Province
respOrder.City = domOrdersItem.City
respOrder.Expired = domOrdersItem.Expired()
if domOrdersItem.Shop != nil {
respOrderShop := new(Shop)
respOrderShop.ID = domOrdersItem.Shop.ID
respOrderShop.Name = domOrdersItem.Shop.Name
respOrder.Shop = respOrderShop
}
domOrdersItemItemsLen := len(domOrdersItem.Items)
respOrderItems := make([]*Item, domOrdersItemItemsLen)
if domOrdersItemItemsLen > 0 {
for domOrdersItemItemsIndex := 0; domOrdersItemItemsIndex < domOrdersItemItemsLen; domOrdersItemItemsIndex++ {
domOrdersItemItemsItem := domOrdersItem.Items[domOrdersItemItemsIndex]
respOrderItem := new(Item)
respOrderItem.ID = domOrdersItemItemsItem.ID
respOrderItem.Title = domOrdersItemItemsItem.Title
respOrderItem.Price = Money(domOrdersItemItemsItem.Price)
respOrderItems[domOrdersItemItemsIndex] = respOrderItem
}
}
respOrder.Items = respOrderItems
(*resp)[domOrdersIndex] = respOrder
}
}
/**************** mapping end ****************/
}
|
hertz
thriftgo是什么
thriftgo
是 Thrift 协议的 Go 语言实现。Thrift 是一种跨语言的服务框架,它可以帮助开发人员在不同的编程语言之间进行通信和数据交换。
thriftgo
是一个开源的项目,它提供了一个用于生成 Thrift 服务和客户端代码的 Go 语言库。使用 thriftgo
,开发人员可以轻松地定义 Thrift 服务契约,生成服务端和客户端代码,并在不同的语言之间进行通信。
thriftgo
的主要特点如下:
- 支持多种语言:
thriftgo
可以生成多种语言的代码,包括 Go、Java、C++、Python 等。
- 简单易用:
thriftgo
提供了一个简单的 API,可以帮助开发人员快速生成 Thrift 服务和客户端代码。
- 高效:
thriftgo
使用了 Thrift 协议的二进制编码方式,可以提高通信的效率。
- 可扩展:
thriftgo
支持多种拓展功能,如可定制的生成代码、多种序列化方式等。
总之,thriftgo
是一个非常实用的工具,可以帮助开发人员快速实现跨语言的通信和数据交换。
protoc是什么
protoc
是 Protocol Buffers 协议的编译器,它用于将定义的 Protocol Buffers 协议文件(.proto 文件)转换成对应的编程语言代码,如 Java、C++、Python 等。Protocol Buffers 是一种用于数据交换的二进制格式,它可以在不同的编程语言和平台之间进行数据交换。
protoc
是一个命令行工具,可以在命令行中使用,也可以在代码中调用。使用 protoc
可以极大地简化 Protocol Buffers 协议的使用,减少代码的耦合度,提高代码的可读性和可维护性。
使用 protoc
编译 Protocol Buffers 协议文件的语法如下: