文章目录
- 十八、go操作MySQL、Redis
- MySQL
- Redis
- 十九、泛型
- 泛型函数
- 泛型类型
- 泛型约束
- 泛型特化
- 泛型接口
- 二十、workspaces
- 核心概念
- 示例
- 二十一、模糊测试
十八、go操作MySQL、Redis
MySQL
package mainimport ("database/sql""errors""fmt"_ "github.com/go-sql-driver/mysql""log""time"
)type User struct {UserId int `db:"id"`Username string `db:"name"`Phone string `db:"phone"`Email string `db:"email"`IdCard string `db:"id_card"`
}var DB *sql.DBfunc init() {db, err := sql.Open("mysql", "root:123456@tcp(localhost:3306)/springbootclass")if err != nil {panic(err)}//最大空闲连接数,默认不配置,是2个最大空闲连接db.SetMaxIdleConns(5)//最大连接数,默认不配置,是不限制最大连接数db.SetMaxOpenConns(100)// 连接最大存活时间db.SetConnMaxLifetime(time.Minute * 3)//空闲连接最大存活时间db.SetConnMaxIdleTime(time.Minute * 1)err = db.Ping()if err != nil {log.Println("数据库连接失败")db.Close()panic(err)}DB = db}
func query(id int) (*User, error) {rows, err := DB.Query("select * from user where id=? limit 1", id)if err != nil {log.Println("执行sql语句出错")panic(err)}user := new(User)for rows.Next() {if err := rows.Scan(&user.UserId, &user.Username, &user.Phone, &user.Email, &user.IdCard); err != nil {log.Println("scan error:", err)return nil, errors.New(err.Error())}}return user, nil}
func save() {r, err := DB.Exec("insert into user (name,phone,email) values(?,?,?)", "test001", "man", "001@test.com")if err != nil {log.Println("执行sql语句出错")panic(err)}id, err := r.LastInsertId()if err != nil {log.Println("数据库无法连接")panic(err)}fmt.Println("插入成功:", id)
}func update(username string, id int) {ret, err := DB.Exec("update user set username=? where user_id=?", username, id)if err != nil {log.Println("更新出现问题:", err)return}affected, _ := ret.RowsAffected()fmt.Println("更新成功的行数:", affected)
}
func delete(id int) {ret, err := DB.Exec("delete from user where user_id=?", id)if err != nil {log.Println("删除出现问题:", err)return}affected, _ := ret.RowsAffected()fmt.Println("删除成功的行数:", affected)
}
func insertTx(username string) {tx, err := DB.Begin()if err != nil {log.Println("开启事务错误:", err)return}ret, err := tx.Exec("insert into user (username,sex,email) values (?,?,?)", username, "man", "test@test.com")if err != nil {log.Println("事务sql执行出错:", err)return}id, _ := ret.LastInsertId()fmt.Println("插入成功:", id)if username == "lisi" {fmt.Println("回滚...")_ = tx.Rollback()} else {_ = tx.Commit()}}
func main() {defer DB.Close()//save()query(14)
}
Redis
安装:go get github.com/go-redis/redis/v8
package mainimport ("context""fmt""github.com/go-redis/redis/v8"
)func main() {ctx := context.Background()rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379",Password: "", // no password setDB: 0, // use default DB})err := rdb.Set(ctx, "key", "value", 0).Err()if err != nil {panic(err)}val, err := rdb.Get(ctx, "key").Result()if err != nil {panic(err)}fmt.Println("key", val)val2, err := rdb.Get(ctx, "key2").Result()if err == redis.Nil {fmt.Println("key2 does not exist")} else if err != nil {panic(err)} else {fmt.Println("key2", val2)}
}
十九、泛型
泛型(Generics)是一种编程思想,它允许在编写代码时使用未知的类型。泛型可以增加代码的灵活性和可复用性,同时还能提高代码的安全性和可读性。泛型在 C++, Java 和 Python 等语言中已经被广泛应用,但在 Go 中一直未被支持。
Go 1.18 版本终于加入了泛型特性,这一特性的引入被认为是 Go 语言历史上的一件大事。
详细文档:Tutorial: Getting started with generics - The Go Programming Language
示例:
package mainimport "fmt"// // SumInts adds together the values of m.
//
// func SumInts(m map[string]int64) int64 {
// var s int64
// for _, v := range m {
// s += v
// }
// return s
// }
//
// // SumFloats adds together the values of m.
//
// func SumFloats(m map[string]float64) float64 {
// var s float64
// for _, v := range m {
// s += v
// }
// return s
// }
//
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {var s Vfor _, v := range m {s += v}return s
}
func main() {// Initialize a map for the integer valuesints := map[string]int64{"first": 34,"second": 12,}// Initialize a map for the float valuesfloats := map[string]float64{"first": 35.98,"second": 26.99,}fmt.Printf("Non-Generic Sums: %v and %v\n",//SumInts(ints),//SumFloats(floats))SumIntsOrFloats(ints),SumIntsOrFloats(floats))
}
泛型函数
在 Golang 中,泛型的语法包括类型参数、类型约束、泛型函数和泛型类型等。
在 Go 中,泛型函数的语法如下:
func FuncName[T Type](params) returnType {// Function body}
其中,T 表示泛型类型参数,Type 表示具体的类型,params 表示函数的参数,returnType 表示函数的返回值类型。
泛型类型
除了泛型函数之外,Go 1.18 版本还引入了泛型类型。泛型类型的语法如下:
type TypeName[T Type] struct {// Fields}
其中,TypeName 表示泛型类型名称,T 表示泛型类型参数,Type 表示具体的类型。
泛型约束
在使用泛型时,有时需要对泛型类型进行一定的约束。例如,我们希望某个泛型函数或类型只能接受特定类型的参数,或者特定类型的参数必须实现某个接口。在 Go 中,可以使用泛型约束来实现这些需求
类型约束可以让泛型函数或类型只接受特定类型的参数。在 Go 中,类型约束可以使用 interface{} 类型和类型断言来实现。
类型约束可以使用在类型参数后加上一个约束类型来实现。
除了使用 interface{} 类型进行类型约束之外,Go 还支持使用接口来约束泛型类型。
泛型特化
泛型特化是指将泛型代码转换为具体类型的代码。在 Go 中,泛型特化是在编译期间完成的。特化可以提高代码的性能和运行效率,因为编译器可以针对具体类型进行优化,避免了运行时的类型检查和类型转换。
在 Go 中,泛型特化是通过代码生成器实现的。代码生成器会根据泛型类型或函数的定义,生成具体类型或函数的代码。例如,下面是一个泛型函数的定义:
css 代码解读复制代码 func Swap[T any](a, b *T) {*a, *b = *b, *a}
该函数可以交换任意类型的两个变量的值。在编译期间,代码生成器会根据调用该函数时传递的参数类型生成具体的函数代码。例如,如果传递的是整数类型的指针,代码生成器会生成以下代码:
css 代码解读复制代码 func Swap_int(a, b *int) {*a, *b = *b, *a}
如果传递的是字符串类型的指针,代码生成器会生成以下代码:
css 代码解读复制代码 func Swap_string(a, b *string) {*a, *b = *b, *a}
泛型接口
泛型接口是一种可以处理多种类型数据的接口。在 Golang 中,可以使用类型参数来实现泛型接口。
二十、workspaces
Go 多模块工作区能够使开发者能够更容易地同时处理多个模块的工作,如:
方便进行依赖的代码调试(打断点、修改代码)、排查依赖代码 bug
方便同时进行多个仓库/模块并行开发调试
Tutorial: Getting started with multi-module workspaces - The Go Programming Language
go 使用的是多模块工作区,可以让开发者更容易同时处理多个模块的开发。在 Go 1.17 之前,只能使用 go.mod replace
指令来实现,如果你正巧是同时进行多个模块的开发,使用它可能是很痛苦的。每次当你想要提交代码的时候,都不得不删除掉 go.mod 中的 replace
才能使模块稳定的发布版本。•在使用 go 1.18 多模块工作区功能的时候,就使用这项工作变得简单容易处理。下面我来介绍怎么使用这一功能。•Go 多模块工作区文档、代码示例[5]
核心概念
在 Go 中,工作区(workspace)是由一个包含多个 Go 模块的目录结构构成的,主要用于管理依赖关系、构建过程以及代码模块的交互。
一个 Go 工作区通常由以下部分组成:
- 多个模块:每个模块都有自己的
go.mod
文件,定义了该模块的依赖、版本等。 - 共享的
go.work
文件:在 Go 1.18 引入的go.work
文件中,指定了多个 Go 模块的位置,Go 工具链通过这个文件了解工作区的所有模块,并能处理模块间的依赖。
go.work
文件是工作区的核心,类似于 go.mod
文件,但它不是用来定义单个模块的依赖,而是用来定义多个模块如何协作、如何管理工作区中的依赖关系。通过这个文件,Go 工具链知道工作区内所有模块的位置,并且能够更好地进行依赖解析和构建。
go.work
文件的基本结构:
goCopyEditgo 1.18use (./module1./module2
)
go 1.18
:指定 Go 的版本。use
:指定工作区中的模块位置。每个模块路径指向一个相对路径或 Git 仓库。
示例
创建工作区:
- 创建一个新的目录作为工作区。
- 在工作区中初始化多个 Go 模块(每个模块都有独立的
go.mod
)。 - 创建
go.work
文件并将模块添加到该文件中。
示例:
-
创建一个工作区目录
myworkspace
,并在其中创建两个模块:mkdir myworkspace cd myworkspace go mod init module1 cd .. go mod init module2
-
创建
go.work
文件来定义工作区:go work init ./module1 ./module2
这样就创建了一个包含
module1
和module2
的工作区,go.work
文件会自动更新,指示 Go 工具链知道这些模块是工作区的一部分。
二十一、模糊测试
Go 1.18在go工具链里引入了fuzzing模糊测试,可以帮助我们发现Go代码里的漏洞或者可能导致程序崩溃的输入。
参考文章:Tutorial: Getting started with fuzzing - The Go Programming Language
package mainimport "fmt"func Reverse(s string) string {fmt.Printf("input: %q\n", s)r := []rune(s)fmt.Printf("runes: %q\n", r)for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {r[i], r[j] = r[j], r[i]}return string(r)
}
func main() {input := "The quick brown fox jumped over the lazy dog"rev := Reverse(input)doubleRev := Reverse(rev)fmt.Printf("original: %q\n", input)fmt.Printf("reversed: %q\n", rev)fmt.Printf("reversed again: %q\n", doubleRev)
}
fuzzing的优点之一是可以基于开发者代码里指定的测试输入作为基础数据,进一步自动生成新的随机测试数据,用来发现指定测试输入没有覆盖到的边界情况。
在单元测试里,因为测试输入是固定的,你可以知道调用Reverse
函数后每个输入字符串得到的反转字符串应该是什么,然后在单元测试的代码里判断Reverse
的执行结果是否和预期相符。例如,对于测试用例Reverse("Hello, world")
,单元测试预期的结果是 "dlrow ,olleH"
。
但是使用fuzzing时,我们没办法预期输出结果是什么,因为测试的输入除了我们代码里指定的用例之外,还有fuzzing随机生成的。对于随机生成的测试输入,我们当然没办法提前知道输出结果是什么。
虽然如此,本文里的Reverse
函数有几个特性我们还是可以在模糊测试里做验证。
- 对一个字符串做2次反转,得到的结果和源字符串相同
- 反转后的字符串也仍然是一个有效的UTF-8编码的字符串
注意:fuzzing模糊测试和Go已有的单元测试以及性能测试框架是互为补充的,并不是替代关系。
参考文章:官方教程:Go fuzzing模糊测试 - 知乎 (zhihu.com)