Go_常用框架/中间件

  • content {:toc}

实现函数对象

在 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 和工具,用于执行以下任务:

  1. 对象关系映射:将数据库表格的数据映射到面向对象编程语言中的对象,并将对象的属性和关系映射到数据库的列和行。
  2. 数据库操作:提供了一组简化的方法来执行常见的数据库操作,如插入、更新、删除和查询数据。
  3. 数据库事务管理:支持事务操作,确保数据库操作的一致性和完整性。
  4. 查询语言:提供了一种类似于编程语言的接口,用于指定和执行复杂的数据库查询。
  5. 缓存管理:提供了缓存机制,以减少对数据库的频繁访问,并提高性能。
  6. 数据库迁移:简化数据库结构的变更和迁移操作,保证数据的一致性和无缝升级。

模型定义 | 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,

	)

创建

  • GORM 在创建对象时不需要显式地指定要将其创建到哪张表中。GORM 会根据您定义的模型类和数据库的映射关系,自动确定要创建的表和列。

    您只需要定义模型类,并在需要时调用 Gorm 的创建方法即可。Gorm 会自动处理数据库操作,并确保在数据库中正确地创建对象。

 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 提供的一些常用方法:

  1. Context.Key ():获取上下文中的键值。
  2. Context.Get ():获取上下文中的值,如果不存在则返回默认值。
  3. Context.Set ():设置上下文中的值。
  4. Context.Map ():获取上下文中的所有键值对。
  5. Context.PostForm ():获取请求的表单数据。
  6. Context.Query ():获取请求的查询字符串参数。
  7. Context.Param ():获取请求的路径参数。
  8. Context.Cookie ():获取请求的 cookie。
  9. Context.Redirect ():重定向到指定的 URL。
  10. Context.Render ():渲染模板并返回响应。
  11. Context.String ():返回一个字符串响应。
  12. Context.HTML ():返回一个 HTML 响应。
  13. Context.JSON ():返回一个 JSON 响应。
  14. 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是合法的么

1
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 进行用户身份验证流程

  1. 客户端使用用户名和密码请求登录
  2. 服务端收到请求,验证用户名和密码
  3. 验证成功后,服务端会签发一个 token,再把这个 token 返回给客户端
  4. 客户端收到 token 后可以把它存储起来,比如放到 cookie 中
  5. 客户端每次向服务端请求资源时需要携带服务端签发的 token,可以在 cookie 或者 header 中携带
  6. 服务端收到请求,然后去验证客户端请求里面带着的 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发布时间
jtiJWT 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 本身是基于文本的,因此在传输过程中可能会被截获或窃取

为了减轻这种风险,有几个常见的做法可以采用:

  1. 使用 HTTPS:使用 HTTPS 协议进行通信可以加密传输的数据,从而提供更高的安全性。这样,即使 JWT 被截获,攻击者也无法轻易解密和篡改其中的数据。
  2. 限制 JWT 的有效期:在生成 JWT 时,可以设置一个较短的有效期,使得 JWT 在一段时间后自动失效。这样即使 JWT 被截获,攻击者也只能在有效期内使用,有效期过后就无法继续使用。
  3. 避免在 JWT 中存储敏感信息:尽量避免将敏感信息直接存储在 JWT 的载荷中,特别是不要将密码等敏感凭据放在 JWT 中。如果需要传递敏感信息,可以考虑使用加密算法对敏感数据进行加密,然后将加密后的数据放入 JWT 的载荷中。
  4. 使用签名验证:在服务器端接收到 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

structmapper (github.com)

结构映射器

实际项目开发中,需要各个层的数据流转,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 的主要特点如下:

  1. 支持多种语言:thriftgo 可以生成多种语言的代码,包括 Go、Java、C++、Python 等。
  2. 简单易用:thriftgo 提供了一个简单的 API,可以帮助开发人员快速生成 Thrift 服务和客户端代码。
  3. 高效:thriftgo 使用了 Thrift 协议的二进制编码方式,可以提高通信的效率。
  4. 可扩展:thriftgo 支持多种拓展功能,如可定制的生成代码、多种序列化方式等。

总之,thriftgo 是一个非常实用的工具,可以帮助开发人员快速实现跨语言的通信和数据交换。

protoc是什么

protoc 是 Protocol Buffers 协议的编译器,它用于将定义的 Protocol Buffers 协议文件(.proto 文件)转换成对应的编程语言代码,如 Java、C++、Python 等。Protocol Buffers 是一种用于数据交换的二进制格式,它可以在不同的编程语言和平台之间进行数据交换。

protoc 是一个命令行工具,可以在命令行中使用,也可以在代码中调用。使用 protoc 可以极大地简化 Protocol Buffers 协议的使用,减少代码的耦合度,提高代码的可读性和可维护性。

使用 protoc 编译 Protocol Buffers 协议文件的语法如下:

Licensed under CC BY-NC-SA 4.0
最后更新于 Oct 13, 2024 18:49 +0800
使用 Hugo 构建
主题 StackJimmy 设计