1.1go语言的起源
Go语言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明,他们最终的目标是设计一种适应网络和多核时代的C语言。所以Go语言很多时候被描述为“类C语言”,或者是“21世纪的C语言”,当然从各种角度看,Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想。但是Go语言更是对C语言最彻底的一次扬弃,它舍弃了C语言中灵活但是危险的指针运算,还重新设计了C语言中部分不太合理运算符的优先级,并在很多细微的地方都做了必要的打磨和改变。
1.2go版本的hello,world
我们学习一门语言总是从下面这段代码开始
package main
import "fmt"
func main() {// 终端输出hello world
fmt.Println("Hello world!")
}
和C语言相似,go语言的基本组成有:
- 包声明,编写源文件时,必须在非注释的第一行指明这个文件属于哪个包,如
package main
。 - 引入包,其实就是告诉Go 编译器这个程序需要使用的包,如
import "fmt"
其实就是引入了fmt包。 - 函数,和c语言相同,即是一个可以实现某一个功能的函数体,每一个可执行程序中必须拥有一个main函数。
- 变量,Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
- 语句/表达式,在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
- 注释,和c语言中的注释方式相同,可以在任何地方使用以 // 开头的单行注释。以 /* 开头,并以 */ 结尾来进行多行注释,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
需要注意的是:标识符是用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母和数字、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
- 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
- 标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected)。
就是一句话,Go的标识符严格区分大小写,以小写开头的是包外不可见,大写开头可以被外部的包使用
package main
import "fmt"
func main() {
fmt.Println("你好,世界")
// 1.两种定义变量,var 或者 := 常量使用const定义
var i1 int
i1 = 1
i2 := 2
const k = 3
fmt.Println(i1)
fmt.Println(i2)
fmt.Println(k)
i := 1 // 整型
f := 3.14 // 浮点型
b := false // 布尔型
s := "hello" // 字符串
fmt.Printf("i=%d, f=%f, b=%t, s=%s", i, f, b, s) //对应类型的占位符
}
1.3 数据类型
类型 | 详解 |
---|---|
布尔型 | 布尔型的值只可以是常量 true 或者 false。 |
数字类型 | 整型 int 和浮点型 float。Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 |
字符串类型 | 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 |
派生类型 | (a) 指针类型(Pointer)(b) 数组类型© 结构体类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型 |
对于派生类型,我们对指针,结构体,接口,Map类型单独说明
1.3.1指针
指针是一种变量,用于存储其他变量的内存地址。在Go中,指针类型表示为 *T
,其中 T
是指向的数据类型。指针用于引用和操作数据的内存地址,它允许在函数之间共享数据的引用,可以减少数据的拷贝,提高程序的性能。指针的零值是 nil
,表示不指向任何有效的内存地址。
var x int = 10
var p *int // 声明一个整数类型的指针
p = &x // 将指针p指向变量x的内存地址
fmt.Println(*p) // 输出:10,通过指针访问变量x的值
1.3.2 结构体类型
结构体是一种用户自定义的复合数据类型,它允许将不同类型的数据组合在一起,创建更复杂的数据结构。结构体由一组字段(字段名和字段类型)组成,每个字段可以是不同类型的数据。结构体通常用于表示实体对象或数据记录。
type Person struct {
Name string
Age int
}
var p Person
p.Name = "Alice"
p.Age = 30
1.3.3 接口类型
接口是一种抽象数据类型,用于定义对象的行为。接口描述了一组方法的签名,任何实现这些方法的类型都可以被认为实现了该接口。接口提供了多态性的概念,允许不同类型的对象以一致的方式进行处理。
package main
import "fmt"
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
// 创建一个 Circle 结构体实例
circle := Circle{Radius: 5.0}
// 通过接口调用 Area 方法
var shape Shape
shape = circle // 将 Circle 实例赋值给接口
area := shape.Area()
fmt.Printf("圆的面积是:%f\n", area)
}
1.3.4 Map(映射)类型
映射是一种无序的键-值对数据结构,也被称为字典。在Go中,映射是一种内置的数据类型,用于将键与值关联起来。映射通常用于查找表、关联数据以及用于快速查找和检索数据。
phonebook := make(map[string]string) // 创建一个字符串到字符串的映射
phonebook["Alice"] = "123-456-789"
phone := phonebook["Alice"] // 查找键对应的值
1.4分支循环
1.4.1 if
package main
import "fmt"
func main() {
x := 10
if x > 5 {
fmt.Println("x 大于 5")
} else {
fmt.Println("x 小于等于 5")
}
}
1.4.2 switch
package main
import "fmt"
func main() {
day := "Wednesday"
switch day {
case "Monday":
fmt.Println("星期一")
case "Wednesday":
fmt.Println("星期三")
default:
fmt.Println("其他天")
}
}
注意这里不需要break,
1.4.3 for
for
循环用于重复执行代码块,有两种常见的方式:
基本for
循环
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
for range
循环
package main
import "fmt"
func main() {
nums := []int{1, 2, 3, 4, 5}
for index, value := range nums {
fmt.Printf("索引 %d 的值是 %d\n", index, value)
}
}
1.4.4 while
go这个语言中没有专门的while循环,所以我们需要使用到无限循环的时候,可以使用for循环来模拟
package main
import "fmt"
func main() {
count := 0
for count < 5 {
fmt.Println(count)
count++
}
}
1.5数组和切片操作
数组是一种固定长度的数据结构,其中每个元素都具有相同的数据类型。在Go中,数组的长度是数组类型的一部分,因此不同长度的数组被认为是不同的类型。
1.5.1 数组定义
var arr [5]int // 声明一个包含5个整数的数组,元素初始化为零值
arr := [5]int{1, 2, 3, 4, 5} // 使用初始化值声明并初始化数组
arr := [...]int{1, 2, 3} // 根据初始化值自动确定数组长度
访问数组元素
arr[0] = 10 // 设置第一个元素的值为10
value := arr[2] // 获取第三个元素的值
获取数组长度
length := len(arr) // 获取数组长度,结果为5
数组的遍历
可以使用for
循环遍历数组元素。
//2.数组操作,数组定长
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
//切片操作,切片不定长
brr := []int{1, 2, 4}
fmt.Println(arr[0], brr[0])
//循环遍历数组
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
//循环遍历切片,_代表不接收参数的意思
for _, v := range brr {
fmt.Println(v)
}
for i2 := range brr {
fmt.Println(i2)
}
1.5.2 切片定义
切片是一种动态长度的数据结构,它基于数组构建。切片允许您动态添加或删除元素,因此更加灵活。切片由三个部分组成:指向底层数组的指针、切片的长度和切片的容量。
var slice []int // 声明一个整数切片,未分配底层数组
slice := []int{1, 2, 3, 4, 5} // 使用初始化值创建切片
slice := make([]int, 5) // 使用make函数创建长度为5的切片,元素初始化为零值
slice := make([]int, 5, 10) // 创建长度为5、容量为10的切片
基础操作如下
添加元素:使用append函数向切片中添加元素。
slice = append(slice, 6) // 添加元素6到切片
切片:使用切片表达式来获取部分切片。
newSlice := slice[1:4] // 获取索引1到3的切片,不包括索引4
修改元素:通过切片修改底层数组的元素也会影响原始切片和其他切片。
slice[0] = 10 // 修改第一个元素的值为10
删除元素:Go语言没有直接的删除切片元素的内置函数。可以通过创建新的切片来实现删除元素。
// 删除索引为2的元素
slice = append(slice[:2], slice[3:]...)
获取切片长度和容量
length := len(slice) // 获取切片长度
capacity := cap(slice) // 获取切片容量
切片的遍历
可以使用for循环遍历切片元素,与数组类似。
for index, value := range slice {
// 使用 index 和 value 执行代码
}
1.6函数操作
package main
import "fmt"
func main() {
//函数操作
print("我是函数调用" + "\n")
c := add(1, 2)
print(c)
x, y := pow(2, 3)
fmt.Println(x, y)
d := apply(1, 2, add)
fmt.Println(d)
}
// 函数调用返回单个值
func add(a int, b int) int {
return a + b
}
// 函数调用返回多个值
func pow(a int, b int) (int, int) {
a = a * a
b = b * b
return a, b
}
// 函数作为参数传递
func apply(a int, b int, f func(int, int) int) int {
return f(a, b)
}
需要注意的点:
go的函数可以返回多个值,并且可以把函数作为参数传递。
切片和数组的区别:简单地说,切片就是一种简化版的动态数组。因为动态数组的长度不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,而切片则使用得相当广泛。切片高效操作的要点是要降低内存分配的次数,尽量保证append操作(在后续的插入和删除操作中都涉及到这个函数)不会超出cap的容量,降低触发内存分配的次数和每次分配内存大小。
1.7 goroutine
goroutine(协程)是Go语言中的一种并发执行的轻量级线程。它是Go语言并发模型的核心组成部分,与传统的操作系统线程或进程相比,Goroutine更加轻量级、高效,且易于管理。
它有两个显著的特点
1. 7.1 轻量级
Goroutine相对于传统线程非常轻量级,可以在一个应用程序中创建数千甚至数百万个Goroutine而不会导致系统资源的枯竭。这是因为Goroutine共享相同的线程池(通常是操作系统的线程池),由Go运行时系统自动调度,而不是每个Goroutine都有自己的操作系统线程。
1.7.2. 并发执行
Goroutine允许程序同时执行多个任务,而不需要显式创建和管理线程。这使得编写并发程序变得更加容易,因为您可以将任务分解为多个Goroutine,它们可以并行执行。
1.7.3 Go关键字
要创建一个Goroutine,只需使用go
关键字后跟一个函数调用,就可以在新的Goroutine中执行该函数。例如:
go func() {
// 这部分代码在新的Goroutine中执行,相当于创建了一个子线程
}()
创建一个线程的代码如下
package main
import (
"fmt"
"time"
)
func main() {
// 启动一个新的Goroutine 子线程
go func() {
for i := 0; i < 5; i++ {
fmt.Println("Goroutine:", i)
}
}()
// 主Goroutine执行的代码 主线程
for i := 0; i < 3; i++ {
fmt.Println("Main Goroutine:", i)
}
// 等待一段时间,以便Goroutine有足够的时间执行
time.Sleep(time.Second)
}
注意上面的代码手动休眠的做法不是最佳选择,可以使用对应的同步机制,代码如下
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup // 创建一个等待组
// 增加等待组计数,表示有一个Goroutine正在执行
wg.Add(1)
// 启动一个新的Goroutine
go func() {
defer wg.Done() // 减少等待组计数,表示Goroutine执行完毕
for i := 0; i < 100; i++ {
fmt.Println("子线程:", i)
}
}()
// 主Goroutine执行的代码
for i := 0; i < 200; i++ {
fmt.Println("Main 主线程:", i)
}
// 等待所有Goroutines完成
wg.Wait() // 等待等待组中的计数归零,表示所有Goroutines都已完成
fmt.Println("所有线程执行完毕")
}
1.7.4 自动调度
Go运行时系统会自动管理Goroutine的调度,包括将Goroutines分配给可用的线程,管理线程池,以及在Goroutine阻塞时将线程切换到其他Goroutines。这样,程序员无需手动管理线程的创建和销毁,也无需担心死锁等并发问题。
1.7.5 通信通过通道
Goroutines之间的通信通常通过通道(Channel)进行。通道提供了一种同步的方式,用于在Goroutines之间传递数据和进行通信。通过通道,Goroutines可以安全地共享数据,避免竞态条件和数据竞争
1.8 Channel
Go 语言之所以开始流行起来,很大一部分原因是因为它自带的并发机制。如果说 goroutine 是 Go语言程序的并发体的话,那么 channel(信道) 就是 它们之间的通信机制。channel,是一个可以让一个 goroutine 与另一个 goroutine 传输信息的通道,我把他叫做信道,也有人将其翻译成通道,二者都是一个概念。信道,就是一个管道,连接多个goroutine程序 ,它是一种队列式的数据结构,遵循先入先出的规则。
Channel(通道)是Go语言中用于在不同goroutine之间传递数据和进行通信的重要机制。通道提供了一种同步的方式,确保数据的安全传递,并且可以帮助协调不同goroutine的执行。
1.8.1 创建通道
可以使用make
函数创建一个通道,指定通道中元素的类型:
信道实例 := make(chan 信道类型)
ch := make(chan int) // 创建一个整数类型的通道
1.8.2 发送和接收数据
- 使用
<-
操作符来发送数据到通道。 - 使用
<-
操作符从通道接收数据。
ch <- 42 // 将整数42发送到通道
value := <-ch // 从通道中接收一个整数并将其赋给变量value
close(ch) //关闭信道
x, ok := <-ch //当从信道中读取数据时,可以有多个返回值,其中第二个可以表示 信道是否被关闭,如果已经被关闭,ok 为 false,若还没被关闭,ok 为true。
1.8.3信道的长度与容量
一般创建信道都是使用 make 函数,make 函数接收两个参数
第一个参数:必填,指定信道类型
第二个参数:选填,不填默认为0,指定信道的容量(可缓存多少数据)
对于信道的容量,很重要,这里要多说几点:
当容量为0时,说明信道中不能存放数据,在发送数据时,必须要求立马有人接收,否则会报错。此时的信道称之为无缓冲信道。
当容量为1时,说明信道只能缓存一个数据,若信道中已有一个数据,此时再往里发送数据,会造成程序阻塞。 利用这点可以利用信道来做锁。
当容量大于1时,信道中可以存放多个数据,可以用于多个协程之间的通信管道,共享资源。
至此我们知道,信道就是一个容器。若将它比做一个纸箱子
它可以装10本书,代表其容量为10,当前只装了1本书,代表其当前长度为1
信道的容量,可以使用 cap 函数获取 ,而信道的长度,可以使用 len 长度获取。
package main
import "fmt"
func main() {
pipline := make(chan int, 10)
fmt.Printf("信道可缓冲 %d 个数据\n", cap(pipline))
pipline<- 1
fmt.Printf("信道中当前有 %d 个数据", len(pipline))
}
输出如下
信道可缓冲 10 个数据
信道中当前有 1 个数据
1.8.4信道的类型
缓冲信道
允许信道里存储一个或多个数据,这意味着,设置了缓冲区后,发送端和接收端可以处于异步的状态。
pipline := make(chan int, 10)
无缓冲信道
在信道里无法存储数据,这意味着,接收端必须先于发送端准备好,以确保你发送完数据后,有人立马接收数据,否则发送端就会造成阻塞,原因很简单,信道中无法存储数据。也就是说发送端和接收端是同步运行的。
pipline := make(chan int)
// 或者
pipline := make(chan int, 0)
双向信道与单向信道
通常情况下,我们定义的信道都是双向通道,可发送数据,也可以接收数据。
但有时候,我们希望对信道的数据流向做一些控制,比如这个信道只能接收数据或者这个信道只能发送数据。
因此,就有了 双向信道 和 单向信道 两种分类。
双向信道
默认情况下你定义的信道都是双向的,比如下面代码
import (
"fmt"
"time"
)
func main() {
pipline := make(chan int)
go func() {
fmt.Println("准备发送数据: 100")
pipline <- 100
}()
go func() {
num := <-pipline
fmt.Printf("接收到的数据是: %d", num)
}()
// 主函数sleep,使得上面两个goroutine有机会执行
time.Sleep(1)
}
单向信道
单向信道,可以细分为 只读信道 和 只写信道。
定义只读信道
var pipline = make(chan int)
type Receiver = <-chan int
var receiver Receiver = pipline
定义只写信道
var pipline = make(chan int)
type Sender = chan<- int
var sender Sender = pipline
仔细观察,区别在于 <-
符号在关键字 chan
的左边还是右边。
<-chan
表示这个信道,只能从里发出数据,对于程序来说就是只读
chan<-
表示这个信道,只能从外面接收数据,对于程序来说就是只写
需要注意的点
信道本身就是为了传输数据而存在的,如果只有接收者或者只有发送者,那信道就变成了只入不出或者只出不入了吗,没什么用。所以只读信道和只写信道,唇亡齿寒,缺一不可。
import (
"fmt"
"time"
)
//定义只写信道类型
type Sender = chan<- int
//定义只读信道类型
type Receiver = <-chan int
func main() {
var pipline = make(chan int)
go func() {
var sender Sender = pipline
fmt.Println("准备发送数据: 100")
sender <- 100
}()
go func() {
var receiver Receiver = pipline
num := <-receiver
fmt.Printf("接收到的数据是: %d", num)
}()
// 主函数sleep,使得上面两个goroutine有机会执行
time.Sleep(1)
}
1.8.4 遍历信道
遍历信道,可以使用 for 搭配 range关键字,在range时,要确保信道是处于关闭状态,否则循环会阻塞。
func fibonacci(mychan chan int) {
n := cap(mychan)
x, y := 1, 1
for i := 0; i < n; i++ {
mychan <- x
x, y = y, x+y
}
// 记得 close 信道
// 不然主函数中遍历完并不会结束,而是会阻塞。
close(mychan)
}
func main() {
pipline := make(chan int, 10)
go fibonacci(pipline)
// 取值
for k := range pipline {
fmt.Println(k)
}
}
1.8.5示例代码
package main
import (
"fmt"
"sync"
)
func main() {
ch := make(chan int)
var wg sync.WaitGroup
// 增加等待组计数,表示有两个Goroutines需要等待
wg.Add(2)
// 启动一个Goroutine向通道发送数据
go func() {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}()
// 启动另一个Goroutine从通道接收数据
go func() {
defer wg.Done()
for num := range ch {
fmt.Printf("接收到数据:%d\n", num)
}
}()
// 等待所有Goroutines完成
wg.Wait()
fmt.Println("所有Goroutines完成")
}
1.8.6注意事项
在Go语言中,panic 是一种表示程序发生严重错误的机制。当程序在运行时遇到无法处理的错误或异常情况时,它会触发 panic,导致程序立即终止。panic 是一种在运行时引发的异常,类似于其他编程语言中的异常或错误。
关闭一个未初始化的 channel 会产生 panic
重复关闭同一个 channel 会产生 panic
向一个已关闭的 channel 发送消息会产生 panic
从已关闭的 channel 读取消息永远不会阻塞,并且会返回一个为 false 的值,用以判断该 channel 是否已关闭(x,ok := <- ch)
channel 在 Golang 中是一等公民,它是线程安全的,面对并发问题,应首先想到 channel。
1.9 go-zero框架
1.9.1安装
可参考:https://go-zero.dev/docs/tasks
1.9.2初体验
1.先创建一个名为godemo_dev的go项目,我这里用的是idea创建,goland和vscode都可以,
2.创建一个godemo_dev.api的文件
3.在godemo_dev.api文件中输入以下内容
syntax = "v1"
type (
//1.用户表
AddSysUserRequest {
Name string `json:"realname"`
Username string `json:"username"`
Address string `json:"address"`
Status int64 `json:"status"` //0代表启用,1代表禁用
Password string `json:"password"`
Sex int64 `json:"sex"` //0代表男,1代表女
Phone string `json:"phone"`
Email string `json:"email"`
CreateBy int64 `json:"createBy"`
}
AddSysUserResponse {
Id int64 `json:"id"`
}
DeleteSysUserRequest {
Id int64 `path:"id"`
}
UpdateSysUserRequest {
Id int64 `json:"id"`
Username string `json:"username"`
Address string `json:"address"`
Status int64 `json:"status"`
Password string `json:"password"`
Sex int64 `json:"sex"`
Phone string `json:"phone"`
Email string `json:"email"`
CreateBy int64 `json:"createBy"`
}
Page {
CurPage int64 `json:"curPage,default=1"`
PageSize int64 `json:"pageSize,default=10"`
}
QuerySysUserRequest {
Keyword string `json:"keyword,optional"`
Page
}
QuerySysUserResponse {
List interface{} `json:"list"`
Total int64 `json:"total"`
}
)
service godemo_dev {
//1.系统用户模块
@doc(summary: "增加系统用户")
@handler addSysUser
post /sys-user/add (AddSysUserRequest)
@doc(summary: "删除系统用户")
@handler deleteSysUser
delete /sys-user/delete/:id (DeleteSysUserRequest)
@doc(summary: "更新系统用户")
@handler updateSysUser
put /sys-user/update (UpdateSysUserRequest)
@doc(summary: "查询系统用户")
@handler querySysUser
post /sys-user/query (QuerySysUserRequest) returns (QuerySysUserResponse)
}
4.执行以下命令
goctl api go -api godemo_dev.api -dir .
项目结构目录如下
这个时候如果发现代码报错,可以删除go.mod,重新生成依赖
打开终端执行下面的命令,重新导入依赖
go mod init godemo_dev
go mod tidy
此时代码已经不报错了,
5.在项目中创建model文件夹
然后在终端进入到model目录,执行命令
goctl model mysql datasource --url “root:你的密码@tc
p(你的地址:3306)/meijumeihu” -t=‘sys_user’ .
此时自动生成数据库相关代码
6 最后需要修改两个文件
修改的文件如下
config.go修改如下
package config
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/rest"
)
type Config struct {
rest.RestConf
MysqlUrl string //添加mysql连接
}
servicecontext.go修改如下
package svc
import (
"github.com/zeromicro/go-zero/core/stores/sqlx"
"godemo/internal/config"
"godemo/model"
)
type ServiceContext struct {
SysUserModel model.SysUserModel
Config config.Config
}
func NewServiceContext(c config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.MysqlUrl)
sysUserModel := model.NewSysUserModel(conn)
return &ServiceContext{
SysUserModel: sysUserModel,
Config: c,
}
}
最后修改以下我们的配置文件godemodev.yaml
Name: godemo_dev
Host: 0.0.0.0
Port: 8888
MysqlUrl: "用户名:密码@tcp(地址:3306)/数据库名称?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
go-zero配置基本完毕,剩下的就是撸代码了
o/core/stores/redis"
“github.com/zeromicro/go-zero/rest”
)
type Config struct {
rest.RestConf
MysqlUrl string //添加mysql连接
}
servicecontext.go修改如下
```go
package svc
import (
"github.com/zeromicro/go-zero/core/stores/sqlx"
"godemo/internal/config"
"godemo/model"
)
type ServiceContext struct {
SysUserModel model.SysUserModel
Config config.Config
}
func NewServiceContext(c config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.MysqlUrl)
sysUserModel := model.NewSysUserModel(conn)
return &ServiceContext{
SysUserModel: sysUserModel,
Config: c,
}
}
最后修改以下我们的配置文件godemodev.yaml
Name: godemo_dev
Host: 0.0.0.0
Port: 8888
MysqlUrl: "用户名:密码@tcp(地址:3306)/数据库名称?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
go-zero配置基本完毕,剩下的就是撸代码了