后端学习一般路线

1.后端编程语言(基础语法)

2.后端框架(用于快速构建一套API)

3.软件包管理器

微服务

将每个单独的方法拿出来,放在一个服务器中

副数据库选择

比如快速搜索采用Elasticsearch,缓存使用redis提高性能,异步通信采用Rabbit MQ这样的消息队列

环境搭建

安装链接:下载并安装 - The Go Programming Language

检验安装

1
go version

截屏2024-05-15 15.07.56

当然可以!以下是你提供的 Golang 笔记标题的补全内容:

语法

Go语言的语法设计简洁明了,主要特点包括静态类型、编译型、垃圾回收、并发支持等。以下是一些关键语法点:

基本语法

  • 变量声明:使用 var 关键字或 := 短声明形式。

    1
    2
    var x int
    y := 10

    变量声明使用 var 关键字显式声明类型,或者使用 := 自动推断类型。

  • 常量:使用 const 关键字。

    1
    const Pi = 3.14

    常量声明使用 const 关键字,值在编译时确定,不能修改。

  • 数据类型:基本类型包括 int, float64, string, bool 等。

    1
    2
    3
    4
    var a int = 1
    var b float64 = 2.5
    var c string = "Hello"
    var d bool = true

    Go语言支持多种基本数据类型,使用 var 关键字声明并初始化。

  • 控制结构:包括 if-elsefor 循环、switch-case 等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    if x > 0 {
    // do something
    } else {
    // do something else
    }

    for i := 0; i < 10; i++ {
    fmt.Println(i)
    }

    switch day {
    case "Monday":
    fmt.Println("Start of the week")
    case "Friday":
    fmt.Println("End of the work week")
    default:
    fmt.Println("Midweek")
    }

    Go语言的控制结构类似于其他C系语言,if语句和for循环都非常直观,switch语句支持多种情况。

函数

  • 函数声明

    1
    2
    3
    func add(a int, b int) int {
    return a + b
    }

    函数声明使用 func 关键字,参数类型和返回类型都必须明确。

  • 多返回值

    1
    2
    3
    func swap(x, y string) (string, string) {
    return y, x
    }

    Go函数可以返回多个值,常用于返回结果和错误信息。

  • 匿名函数和闭包

    1
    2
    3
    4
    sum := func(a, b int) int {
    return a + b
    }
    fmt.Println(sum(3, 4)) // 输出: 7

    Go支持匿名函数和闭包,可以在函数内部定义并使用函数。

数组

  • 声明和初始化

    1
    2
    3
    var arr [5]int
    arr[0] = 1
    arr := [5]int{1, 2, 3, 4, 5}

    数组有固定长度,声明时需指定长度,初始化时可以使用字面值。

切片(Slices)

由于数组是固定的,在实际的应用场景中受限很大,所以通过切片(Slices)提供更加灵活的数组

语法上只需要省略定义数组时所需要写的长度即可

需要注意的是切片底层是使用指针的,如果你直接把一个变量=该切片的话,改变切片的值的同时,该变量也会一起改变

  • 声明和初始化

    1
    2
    var s []int
    s = append(s, 1, 2, 3)

    切片是动态数组,可以动态调整大小,常用 append 函数追加元素。

  • 切片操作

    1
    2
    s := []int{1, 2, 3, 4, 5}
    sub := s[1:3] // sub == []int{2, 3}

    切片可以从一个数组或另一个切片中截取部分元素。

映射 (Maps)

  • 声明和初始化

    1
    2
    3
    4
    var m map[string]int
    m = make(map[string]int)
    m["one"] = 1
    m := map[string]int{"one": 1, "two": 2}

    映射是键值对集合,使用 make 函数创建并初始化。

循环(range)

  • 遍历数组和切片

    1
    2
    3
    for i, v := range arr {
    fmt.Println(i, v)
    }

    使用 range 关键字遍历数组和切片。

  • 遍历映射

    1
    2
    3
    for k, v := range m {
    fmt.Println(k, v)
    }

    使用 range 关键字遍历映射,获取键和值。

字符串(string)

  • 声明和操作

    1
    2
    str := "Hello, 世界"
    fmt.Println(len(str)) // 字符串长度(字节数)

    字符串是不可变的字节序列,可以获取长度和进行切片操作。

符文(Runes)

Runes的存在是因为字符串的底层原因,会导致字符串长度并不是字符数,而是字节数,因为比如说中文表达为uft-8时,需要两份字节,所以最后len(str)会多出一格长度,而Runes则能够正常获取到字符数

  • 声明和操作

    1
    2
    3
    4
    5
    6
    7
    var mystring = []rune"resume"
    var indexed = myString[1]

    fmt.Printf("%v, %T\n", indexed, indexed)
    for i, v := range myString {
    fmt.Println(i, v)
    }

    rune 表示单个Unicode字符

结构体 (Structs)

结构体是一种聚合数据类型,它将多个不同类型的字段组合成一个类型,用于表示一个实体。例如,一个人的信息可以用一个结构体来表示。

  • 声明和初始化

    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 Person struct {
    Name string
    Age int
    }

    func main() {
    // 创建一个结构体实例
    p := Person{Name: "Alice", Age: 30}

    // 访问结构体字段
    fmt.Println(p.Name) // 输出: Alice
    fmt.Println(p.Age) // 输出: 30

    // 修改结构体字段
    p.Age = 31
    fmt.Println(p.Age) // 输出: 31
    }

    结构体是用户定义的类型,包含多个字段,可以使用字面值初始化。

接口 (Interfaces)

接口是一种抽象类型,它定义了一组方法(方法集),但是并不实现它们。任何类型只要实现了接口中的方法,就被认为是实现了该接口。接口在Go语言中用于实现多态和解耦。

  • 声明和实现

    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
    package main

    import "fmt"

    // 定义一个接口
    type Speaker interface {
    Speak()
    }

    // 定义一个结构体类型
    type Person struct {
    Name string
    }

    // 为结构体类型实现接口中的方法
    func (p Person) Speak() {
    fmt.Println(p.Name, "says hello!")
    }

    // 定义另一个结构体类型
    type Dog struct {
    Name string
    }

    // 为结构体类型实现接口中的方法
    func (d Dog) Speak() {
    fmt.Println(d.Name, "barks!")
    }

    func main() {
    // 创建结构体实例
    p := Person{Name: "Alice"}
    d := Dog{Name: "Buddy"}

    // 定义一个接口类型的变量
    var s Speaker

    // 将结构体实例赋值给接口类型变量
    s = p
    s.Speak() // 输出: Alice says hello!

    s = d
    s.Speak() // 输出: Buddy barks!
    }

    接口定义一组方法,任何实现这些方法的类型都隐式实现了该接口。

指针 (Pointers)

指针实际上是为了减少因为创建数据副本,导致多出来的性能消耗,这点在函数中尤为明显,因为正常的函数传递值都是去创建副本,而如果指定参数为指针的话就可以改变这一点

  • 指针声明和操作

    1
    2
    3
    4
    var p *int
    x := 5
    p = &x
    fmt.Println(*p) // 输出: 5

    指针保存变量的内存地址,使用 & 获取地址,使用 * 解引用指针。

  • 修改指针指向的值

    1
    2
    *p = 10
    fmt.Println(x) // 输出: 10

    通过指针可以修改变量的值。

  • 使用指针的原因和场景

    1. 修改变量的值:

    函数传参时,默认是值传递,这意味着函数内部修改参数的副本,而不影响原变量。如果希望函数能够修改传入的变量,可以使用指针。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import "fmt"

    func increment(x *int) {
    *x = *x + 1
    }

    func main() {
    var num int = 5
    increment(&num) // 传递num的指针
    fmt.Println("increment 后的 num:", num) // 输出: increment 后的 num: 6
    }
    1. 提高性能:

    在处理大数据结构(如大型数组或结构体)时,传递指针而不是整个数据结构,可以避免数据的拷贝,减少内存和时间开销。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main

    import "fmt"

    type LargeStruct struct {
    data [1000]int
    }

    func modifyStruct(ls *LargeStruct) {
    ls.data[0] = 1
    }

    func main() {
    var ls LargeStruct
    modifyStruct(&ls)
    fmt.Println("修改后的 LargeStruct:", ls.data[0]) // 输出: 修改后的 LargeStruct: 1
    }
    1. 共享数据:

    当多个函数或多个goroutine需要共享和修改相同的数据时,可以使用指针。

    例子:

    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
    package main

    import (
    "fmt"
    "sync"
    )

    type Counter struct {
    mu sync.Mutex
    value int
    }

    func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
    }

    func main() {
    counter := &Counter{}

    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
    wg.Add(1)
    go func() {
    defer wg.Done()
    counter.Increment()
    }()
    }

    wg.Wait()
    fmt.Println("最终计数器的值:", counter.value) // 输出: 最终计数器的值: 1000
    }

Go routines (线程)

Goroutines 是 Go 语言(Golang)中的一种轻量级线程。它们允许你在同一地址空间中并发执行函数,具有高效的内存和处理开销。Goroutines 是 Go 并发编程的核心特性,能够简化多线程编程的复杂性。

Go routines 的作用

  1. 并发执行:Goroutines 使得多个函数可以并发执行,而不是依次执行,从而提升程序的性能。
  2. 轻量级:相比操作系统线程,Goroutines 更轻量级,启动和切换的开销更小。
  3. 独立性:每个 Goroutine 都有自己的调用栈,并且该栈大小可以动态增长。

Go routines 的使用

要启动一个 Goroutine,只需在函数调用前加上 go 关键字。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
go say("world")
say("hello")
}

代码解释

  1. **函数 say**:该函数接收一个字符串参数,并在一个循环中打印该字符串。
  2. 启动 Goroutine:在 main 函数中,使用 go say("world") 启动一个新的 Goroutine 并发执行 say 函数。
  3. 主 Goroutine:主 Goroutine 继续执行 say("hello"),与新的 Goroutine 并发运行。

并发问题及其解决

如果在 main 函数中启动 Goroutines 后立即返回,程序会退出,导致 Goroutines 可能没有机会执行。可以使用 sync.WaitGrouptime.Sleep 来解决这个问题。

使用 sync.WaitGroup

sync.WaitGroup 用于等待一组 Goroutines 完成。

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
package main

import (
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
wg.Done() // 线程完成
}

func main() {

wg.Add(2) // 增加 WaitGroup 计数器,表示有两个 Goroutine
go say("world")
go say("hello")

wg.Wait() // // 等待所有 Goroutine 完成
}

使用 time.Sleep

另一种方法是使用 time.Sleep 暂时阻止 main 函数退出,以便让 Goroutines 有时间执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"time"
)

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
go say("world")
go say("hello")

time.Sleep(1 * time.Second) // 延长 main 函数的生命周期
}

并发存入数据及其解决

当你希望将 Goroutines 产生的结果存入一个共享的切片时,可能会遇到并发写入问题,因为多个 Goroutines 同时写入同一个切片可能会导致数据竞争和不一致的问题。为了解决这个问题,你可以使用同步机制来保护对切片的并发访问。以下是两种常见的解决方案:使用 sync.Mutex 或使用 Channels。

使用 sync.Mutex

sync.Mutex 提供了一种互斥锁机制,确保在同一时间只有一个 Goroutine 可以访问共享资源。

示例代码

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
package main

import (
"fmt"
"sync"
)

func worker(id int, wg *sync.WaitGroup, mu *sync.Mutex, results *[]int) {
defer wg.Done()
result := id * 2 // 假设计算结果是 id 的两倍
mu.Lock()
*results = append(*results, result)
mu.Unlock()
}

func main() {
var wg sync.WaitGroup
var mu sync.Mutex
results := []int{}

numGoroutines := 5
wg.Add(numGoroutines)

for i := 0; i < numGoroutines; i++ {
go worker(i, &wg, &mu, &results)
}

wg.Wait()

fmt.Println("Results:", results)
}

代码解释

  1. 引入 sync:导入 sync 包以使用 WaitGroupMutex
  2. **声明 WaitGroupMutex**:在 main 函数中声明 WaitGroup 变量 wgMutex 变量 mu
  3. 定义 worker 函数:该函数接收一个 ID、WaitGroupMutex 和结果切片。计算结果后,使用 mu.Lock()mu.Unlock() 保护对切片的并发写入。
  4. 启动 Goroutines:在 main 函数中启动多个 Goroutines,传递 wgmu 和结果切片的指针。
  5. 等待 Goroutines 完成:使用 wg.Wait() 等待所有 Goroutines 完成。
  6. 打印结果:输出结果切片。

使用 Channels

Channels 提供了一种安全的并发数据传递方式,可以避免数据竞争。

示例代码

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
package main

import (
"fmt"
"sync"
)

func worker(id int, wg *sync.WaitGroup, ch chan int) {
defer wg.Done()
result := id * 2 // 假设计算结果是 id 的两倍
ch <- result
}

func main() {
var wg sync.WaitGroup
results := []int{}
ch := make(chan int, 5) // 创建一个带缓冲区的通道

numGoroutines := 5
wg.Add(numGoroutines)

for i := 0; i < numGoroutines; i++ {
go worker(i, &wg, ch)
}

// 启动一个 Goroutine 来等待所有 worker 完成,然后关闭通道
go func() {
wg.Wait()
close(ch)
}()

// 读取通道中的数据
for result := range ch {
results = append(results, result)
}

fmt.Println("Results:", results)
}

代码解释

  1. 创建通道:在 main 函数中创建一个带缓冲区的通道 ch
  2. 定义 worker 函数:该函数接收一个 ID、WaitGroup 和通道。计算结果后,将结果发送到通道。
  3. 启动 Goroutines:在 main 函数中启动多个 Goroutines,传递 wg 和通道。
  4. 关闭通道:启动一个 Goroutine 来等待所有 worker 完成,然后关闭通道 ch
  5. 读取通道数据:使用 range 从通道中读取数据并将其追加到结果切片中。
  6. 打印结果:输出结果切片。

Channels

Golang(也称为Go)中的Channels是用于在不同goroutine之间进行通信和同步的机制。它们可以让你安全地在多个goroutine之间传递数据,而无需使用复杂的锁机制。Channels可以看作是一个管道,通过它一个goroutine可以将数据发送给另一个goroutine。

声明和创建

你可以使用make函数来创建一个channel,指定其传递的数据类型:

1
ch := make(chan int)

这里我们创建了一个传递int类型数据的channel。

发送和接收

使用<-操作符来发送和接收数据:

1
2
3
4
5
// 发送数据到channel
ch <- 42

// 从channel接收数据
value := <-ch

无缓冲和有缓冲的Channels

  • 无缓冲Channel:发送操作会阻塞直到另一个goroutine准备好接收这个值,接收操作会阻塞直到另一个goroutine发送一个值。
  • 有缓冲Channel:你可以在创建channel时指定缓冲区的大小。发送操作只有在缓冲区满时才会阻塞,接收操作只有在缓冲区为空时才会阻塞。
1
2
3
4
5
6
7
8
9
10
11
// 创建一个缓冲区大小为2的channel
ch := make(chan int, 2)

// 发送数据到有缓冲的channel
ch <- 1
ch <- 2 // 这两个发送操作不会阻塞

// 再次发送会阻塞,直到有goroutine接收数据
go func() {
ch <- 3 // 这会阻塞
}()

基本例子

下面是一个简单的例子,展示了如何在两个goroutine之间使用channel进行通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"time"
)

func main() {
ch := make(chan string)

// 启动一个goroutine
go func() {
time.Sleep(2 * time.Second)
ch <- "Hello from goroutine"
}()

// 从channel接收数据(这会阻塞,直到有数据可接收)
msg := <-ch
fmt.Println(msg)
}

在这个例子中,主goroutine启动了一个新的goroutine,该goroutine在2秒后向channel发送一条消息。主goroutine则阻塞在接收操作,直到收到消息并打印出来。

使用有缓冲Channel

下面的例子展示了如何使用有缓冲的channel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func main() {
ch := make(chan int, 2)

ch <- 1
ch <- 2

fmt.Println(<-ch)
fmt.Println(<-ch)
}

在这个例子中,channel有一个缓冲区,可以存放两个int值。我们在发送两个值后,从channel接收并打印它们。

Select语句

select语句可以让你同时等待多个channel操作。它类似于switch语句,但每个case都必须是一个channel操作。

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
package main

import (
"fmt"
"time"
)

func main() {
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()

go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()

for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Receive d", msg2)
}
}
}

在这个例子中,我们启动了两个goroutine,每个goroutine在不同的时间间隔后向各自的channel发送消息。select语句用于等待任意一个channel的消息,并打印接收到的消息。

泛型 (Generics)

在Go语言(Golang)中,Generics(泛型)是一种允许编写更加灵活和可重用代码的特性。通过使用泛型,可以定义能够处理多种数据类型的函数和数据结构,而无需为每种类型单独编写代码。Go 1.18开始正式支持泛型。

泛型函数

一个泛型函数可以接收任意类型的参数。例如,一个用于获取切片中最大值的泛型函数:

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
package main

import "fmt"

// 定义一个类型约束,T 可以是任何支持比较运算的类型
type Ordered interface {
~int | ~float64 | ~string
}

// Max 函数,返回切片中的最大值
func Max[T Ordered](slice []T) T {
if len(slice) == 0 {
var zero T
return zero
}
max := slice[0]
for _, v := range slice[1:] {
if v > max {
max = v
}
}
return max
}

func main() {
intSlice := []int{1, 2, 3, 4, 5}
floatSlice := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
stringSlice := []string{"a", "b", "c", "d", "e"}

fmt.Println(Max(intSlice)) // 输出: 5
fmt.Println(Max(floatSlice)) // 输出: 5.5
fmt.Println(Max(stringSlice)) // 输出: e
}

在这个例子中,Max 函数使用了类型参数 T,并且通过 Ordered 接口约束了 T 必须是支持比较运算的类型。

泛型数据结构

你也可以使用泛型来定义数据结构。例如,一个简单的栈(Stack)数据结构:

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
package main

import "fmt"

// Stack 数据结构,使用泛型
type Stack[T any] struct {
items []T
}

// Push 方法,向栈中添加元素
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}

// Pop 方法,从栈中移除并返回顶部元素
func (s *Stack[T]) Pop() T {
if len(s.items) == 0 {
var zero T
return zero
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}

func main() {
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
fmt.Println(intStack.Pop()) // 输出: 2
fmt.Println(intStack.Pop()) // 输出: 1

stringStack := Stack[string]{}
stringStack.Push("hello")
stringStack.Push("world")
fmt.Println(stringStack.Pop()) // 输出: world
fmt.Println(stringStack.Pop()) // 输出: hello
}

在这个例子中,Stack 数据结构使用了泛型类型参数 T,因此可以创建处理不同类型的栈实例。

网络请求框架

在Golang中,常用的网络请求框架包括 net/httpgin 等。

net/http

  • 创建简单的HTTP服务器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package main

    import (
    "fmt"
    "net/http"
    )

    func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
    }

    func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
    }

gin

  • 使用 gin 框架创建API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package main

    import (
    "github.com/gin-gonic/gin"
    )

    func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
    "message": "pong",
    })
    })
    r.Run() // 默认监听并服务于 0.0.0.0:8080
    }

连接数据库实现增删改查

Golang 提供了多种方法来连接和操作数据库,常用库有 database/sqlgorm

使用 database/sql

  • 连接数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    )

    func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
    panic(err)
    }
    defer db.Close()
    }
  • 查询数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    rows, err := db.Query("SELECT id, name FROM users")
    if err != nil {
    log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
    var id int
    var name string
    if err := rows.Scan(&id, &name); err != nil {
    log.Fatal(err)
    }
    fmt.Println(id, name)
    }
  • 插入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
    if err != nil {
    log.Fatal(err)
    }
    res, err := stmt.Exec("John Doe")
    if err != nil {
    log.Fatal(err)
    }
    lastId, err := res.LastInsertId()
    if err != nil {
    log.Fatal(err)
    }
    fmt.Println(lastId)

使用 gorm

  • 连接数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    )

    func main() {
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
    panic("failed to connect database")
    }
    }
  • 定义模型并迁移

    1
    2
    3
    4
    5
    6
    type User struct {
    ID uint
    Name string
    }

    db.AutoMigrate(&User{})
  • 插入数据

    1
    db.Create(&User{Name: "John Doe"})
  • 查询数据

    1
    2
    3
    var user User
    db.First(&user, 1) // 查询主键为1的用户
    db.First(&user, "name = ?", "John Doe")
  • 更新数据

    1
    db.Model(&user).Update("Name", "Jane Doe")
  • 删除数据

    1
    db.Delete(&user, 1)

微服务

Golang 在微服务开发中非常流行,常用框架有 go-microgrpc

go-micro

  • 创建微服务
    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
    package main

    import (
    "github.com/micro/go-micro/v2"
    "context"
    "fmt"
    )

    type Greeter struct{}

    func (g *Greeter) Hello(ctx context.Context, req *Request, rsp *Response) error {
    rsp.Msg = "Hello " + req.Name
    return nil
    }

    func main() {
    service := micro.NewService(
    micro.Name("greeter"),
    )
    service.Init()

    micro.RegisterHandler(service.Server(), new(Greeter))

    if err := service.Run(); err != nil {
    fmt.Println(err)
    }
    }

grpc

  • 创建 gRPC 服务
    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
    package main

    import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    "log"
    "net"
    pb "path/to/proto"
    )

    type server struct {
    pb.UnimplementedGreeterServer
    }

    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
    }

    func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
    log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
    }
    }

以上是各个部分的补全内容,希望对你有所帮助!

项目Demo

GORM+Gin实现API

好的,我们将项目修改为使用 GORM 框架来代替原生 SQL 查询,并将 Item 更改为一个更具体的名称,例如 Product。以下是修改后的代码和项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gin-mysql-api/
├── main.go
├── go.mod
├── go.sum
├── config/
│ └── config.go
├── controllers/
│ └── product.go
├── models/
│ └── product.go
├── routers/
│ └── router.go
└── database/
└── database.go

1. main.go

主文件,用于启动应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"GoAPIDemo/config"
"GoAPIDemo/routers"
)

func main() {
// 初始化配置
config.LoadConfig()

// 设置路由
r := routers.SetupRouter()

// 运行服务器
r.Run(":8080")
}

2. config/config.go

配置文件,包含数据库连接的配置。

注意这里的?charset=utf8&parseTime=true不配置这个的话,会导致无法访问时间类的字段,导致报错,无法返回对应的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package config

import (
"GoAPIDemo/database"
"GoAPIDemo/models"
"log"
)

func LoadConfig() {
// 初始化数据库
dsn := "root:12345678@tcp(127.0.0.1:3306)/gpAPIDemo?charset=utf8&parseTime=true"
if err := database.InitDB(dsn); err != nil {
log.Fatal(err)
}

// 自动迁移模型
database.DB.AutoMigrate(&models.Product{})
}

3. database/database.go

数据库初始化和连接管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package database

import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

var DB *gorm.DB

func InitDB(dsn string) error {
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
return nil
}

4. models/product.go

定义数据模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
package models

import (
"time"
)

type Product struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `json:"name"`
Price int `json:"price"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

5. controllers/product.go

处理器函数,实现增删改查操作。

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
package controllers

import (
"GoAPIDemo/database"
"GoAPIDemo/models"
"fmt"
"net/http"

"github.com/gin-gonic/gin"
)

func GetProducts(c *gin.Context) {
fmt.Println("进入GetProducts")
var products []models.Product
if err := database.DB.Find(&products).Error; err != nil {
fmt.Println("err:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

fmt.Println("products:", products)
c.JSON(http.StatusOK, products)
}

func CreateProduct(c *gin.Context) {
var product models.Product
if err := c.ShouldBindJSON(&product); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

if err := database.DB.Create(&product).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, product)
}

func UpdateProduct(c *gin.Context) {
id := c.Param("id")
var product models.Product

if err := database.DB.First(&product, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
return
}

if err := c.ShouldBindJSON(&product); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

if err := database.DB.Save(&product).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, product)
}

func DeleteProduct(c *gin.Context) {
id := c.Param("id")
var product models.Product

if err := database.DB.First(&product, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
return
}

if err := database.DB.Delete(&product).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"message": "Product deleted"})
}

6. routers/router.go

设置路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package routers

import (
"GoAPIDemo/controllers"

"github.com/gin-gonic/gin"
)

func SetupRouter() *gin.Engine {
r := gin.Default()

r.GET("/products", controllers.GetProducts)
r.POST("/products", controllers.CreateProduct)
r.PUT("/products/:id", controllers.UpdateProduct)
r.DELETE("/products/:id", controllers.DeleteProduct)

return r
}

完整的 go.mod 文件

确保你在项目根目录下初始化了 go mod 并安装了必要的依赖项。

1
2
3
4
go mod init gin-mysql-api
go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

这样,你的项目使用了 GORM 框架来简化数据库操作,并且代码结构更加清晰和易于维护。