GoLand 收费
LiteIDE 免费,用户体验一般
可执行程序必须包含 main 函数
如果定义了 main 或 init 函数,启动后会自动执行,不需要显示调用,init 会在 main 之前执行,不管哪个先定义
main/init 外的代码会先执行,不管哪块代码先写
func main() {
fmt.Println("Hello, World!")
}
func init() {
fmt.Println("\ninit\n")
}
var b = test()
会先执行 test 再执行 init 再执行 main
var a string
var b string = "abc" // 声明同时初始化
var c = "abc" // 直接初始化可以省略类型
d := "abc" // 同时省略 var 和类型,如果 d 已经定义的话,会报错
var a, b, c string // 可以同时定义、赋值多个
var ( // 定义多个类型不同的变量
a int
b bool
)
和其他语言不同,go 的局部变量如果定义但是没使用,也会报错
declared and not used
需要使用变量才行
const LENGTH int = 10
常量当作枚举用
const (
Unknown = 0
Female = 1
Male = 2
)
iota 对 const 计数
const (
a = iota // 0
b // 1
c // 2
d = "ha" // "ha" iota = 3
e // "ha" iota = 4
f = 100 // 100 iota = 5
g // 100 iota = 6
h = iota // 7
i // 8
)
遇到新的 const 时 iota 会重置为 0
var a, _ = test()
空用 nil 表示
syntax error: non-declaration statement outside function body
错误原因: 函数体外的每个语句,都必须是 golang 的关键字开始
比如不能是
a := 1
test()
必须用
var a = 123
var b = test()
这样才不报错
go 的 if 条件语句不需要括号
if result > 100 {
fmt.Printf("%d + %d = %d\n", a, b, result)
} else {
fmt.Println(result)
}
switch 语法
switch var1 {
case val1:
...
case val2:
...
default:
...
}
type switch 可判断变量类型
var x = test()
switch x.(type) {
case nil:
...
case int:
...
case func(int) float64:
...
case bool, string: // bool 或 string 类型
...
default:
...
}
select 的所有 case 都必须是通信操作
如果有多个 case 可以执行,随机选择一个
如果都不可执行,那么阻塞直到某个通信可以运行
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3):
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
go 没有 while 语句,只用 for
for init; condition; post {}
for condition { }
for { }
init; condition; post 这些条件可以是空的
for a < 10 {
if a == 5 {
continue;
}
a++;
}
continue 可以加 label
re:
for i := 1; i <= 10; i++ {
for j := i; j <= 100; j++ {
if i + j == 100 {
continue re
}
}
}
break 和 goto 的用法类似,也可以加 label
var a int = 20
var i *int
i = &a
fmt.Printf("%d\n", *i)
和 c 语言一样
var n [10]int
for i = 0; i < 10; i++ {
n[i] = i + 100
}
切片就是长度可变的数组
var n []int
var n []int = make([]int, 10) // 初始化长度
s := arr[startIndex:endIndex]
s := arr[startIndex:]
s := arr[:endIndex]
len(n)
range 可用于迭代
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
集合
var m map[string]string // 声明,默认是 nil
m = make(map[string]string) // 初始化
m["key1"] = "value1"
m["key2"] = "value2"
value, ok := m ["key3"]
if ok {
// 存在
} else {
// 不存在
}
// 删除
delete(m, "kk")
// 迭代
for k := range m {
fmt.Println(m[k])
}
注意要用 make 初始化,不然变量是 nil
// 这个 layout 是固定的,必须用这几个数字,不然会报错,这应该是 golang 诞生的时间...
layout := "2006-01-02 15:04:05"
// 按 layout 指定的格式转换成 string
fmt.Printf("current time is %s\n", time.Now().Format(layout))
// t, err := time.Parse(layout, "2021-08-09 12:34:27")
var t time.Time
var err error
t, err = time.Parse(layout, "2021-08-09 12:34:27") // 按 layout 指定的格式转换成 Time 类型
if err == nil {
fmt.Println(t.Second())
}
做法和其他语言用 YYYYMMDD 这样的做法不一样,比较直观
go 没有类,继承等概念,而是通过 struct 实现面向对象
type student struct {
name string
age int
}
func (s student) getName() string {
return s.name
}
func (s * student) setName(name string) {
s.name = name
}
func (this student) getAge() int {
return this.age
}
func (this * student) setAge(age int) {
this.age = age
}
func (this student) display() {
fmt.Printf("name is %s, age is %d\n", this.name, this.age)
}
可以看到数据和方法的定义是分开的,
方法是在函数定义中加入 struct 比如 (s student),或 (s * student) 表示传引用
初始化和使用
s0 := student{"Li", 20}
s0.display()
s1 := student{}
s1.name = "Wang"
s1.age = 35
s1.display()
s2 := new(student)
s2.name = "Zhang"
s2.age = 30
s2.display()
s3 := new(student)
s3.setName("Mr." + s1.getName())
s3.setAge(s2.getAge() - 5)
s3.display()
s4 := student{name: "Han", age: 12}
s4.display()
和其他语言比还是很不一样
import (
"fmt"
"reflect"
)
/*
* tag 用 `` 标识
* 由多个 key-value 组成
* key 和 value 之间用 : 隔开,不能有空格
* value 用 "" 标识
* 多个 key-value 之间用空格隔开
*/
type Student struct {
Name string `json:"name" id:"100"`
Age int `json:"age" id:"101"`
}
func main() {
student := Student{"Wang", 18}
// 通过反射拿到类型
reflectType := reflect.TypeOf(student)
fmt.Printf("type of student is %+v\n", reflectType)
ageField, _ := reflectType.FieldByName("Age")
fmt.Println(ageField.Index)
fmt.Println(ageField.Name)
fmt.Println(ageField.Type)
// 通过反射拿到 tag
fmt.Println(ageField.Tag.Get("json"))
fmt.Println(ageField.Tag.Get("id"))
// 通过反射拿到值
reflectValue := reflect.ValueOf(student)
fmt.Printf("value of student is %+v\n", reflectValue)
nameField := reflectValue.FieldByName("Name")
fmt.Println(nameField)
}
结果
type of student is main.Student
[1]
Age
int
age
101
value of student is {Name:Wang Age:18}
Wang
比如 json 就用了 tag 实现序列化时使用和变量名不一样的字段名
import (
"fmt"
"encoding/json"
)
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
student := Student{"Wang", 18}
jsonStr, _ := json.Marshal(student)
fmt.Println(string(jsonStr))
}
输出 {"name":"Wang","age":18}
type Person struct {
Name string
Age int
}
type Teacher struct {
Title string
Class int
Grade int
Person // 匿名, 相当于定义了 Name 和 Age 变量,如果 Teacher 本身定义了这两个变量,那挑先定义的
}
func main() {
teacher := Teacher{}
teacher.Name = "Li" // Teacher 没有直接定义 Name 和 Age
teacher.Age = 30
teacher.Title = "senior"
teacher.Class = 3
teacher.Grade = 6
fmt.Println(teacher)
}
结果是 {senior 3 6 {Li 30}}
可以看到 {Li 30} 被作为一个子变量,可以自定义 String 方法输出
import (
"fmt"
strconv
)
func (this Teacher) String() string {
return "{name:" + this.Name + ", age:" + strconv.Itoa(this.Age) +
", title:" + this.Title + ", class:" + strconv.Itoa(this.Class) +
", grade:" + strconv.Itoa(this.Grade) + "}"
}
结果是 {name:Li, age:30, title:senior, class:3, grade:6}
除了定义 struct,还用于类型别名,和函数别名
func main() {
type myType int
var m myType = 123
fmt.Println(m)
type myFuncType func(int) string
var fn myFuncType = myFunc
fmt.Println(fn(5))
}
func myFunc(n int) string {
return fmt.Sprintf("receive %d", n)
}
其实定义 struct 也相当于是别名
func swap(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
var a int = 100
var b int= 200
swap(&a, &b)
struct 是值传递,如果要传 struct 的引用,同样要用指针
func updateStudent(s *student) {
s.age *= 2
}
updateStudent(&s0)
数组默认是值传递
func update(array [3]int) {
array[0] = 100
}
var array = [3]int{1, 2, 3}
update(array) // array 的值不会改变
数组的引用传递要用指针
func update(array *[3]int) {
(*array)[0] = 100
}
var array = [3]int{1, 2, 3}
update(&array) // array 的值会改变
切片默认就是引用传递
func update(slice []int) {
slice[0] = 100
}
slice := make([]int, 0)
slice = append(slice, 1, 2, 3)
update(slice) // slice 的值会改变
map 默认引用传递
func update(mapVar map[string]int) {
mapVar["key"] = 100
}
mapVar := make(map[string]int)
mapVar["key"] = 1
update(mapVar) // mapVar 的值会改变
go 的引用传递和 C 差不多
一个外部函数,如果定义了内部函数,并且内部函数引用了外部函数的变量,并且外部函数返回的是内部函数的引用,这个被返回的内部函数,就是闭包
python 的例子
def outer(a):
base = a
def inner(n):
return base ** n
return inner
f = outer(2) ## 这个 f 就是闭包
print(f(10))
print(f(3))
go 语言
func outer(a int) func(n int) float64 {
base := a
return func(n int) float64 {
return math.Pow(float64(base), float64(n))
}
}
f := outer(2) // 这个 f 就是闭包
fmt.Println(f(10))
fmt.Println(f(3))
通过把函数当作返回类型实现
标识符(包括常量、变量、类型、函数名、结构字段等等)
大写字母开头的,可以被外部包的代码所使用
小写字母开头的,对包外是不可见的,但包内可见
文件的第一行声明该文件所属的包
package fmt
通过 import 引用包
系统自带的 package 在 GOROOT/src
比如
import (
"fmt"
"net/http"
)
可以找到目录 GOROOT/src 有
src/
|- fmt
|- net
|- http
同一级目录下的所有文件必须属于同一个包,子目录下的所有文件属于另一个包
包名通常都和目录名一样,并且用小写
自定义 package
src/
|- test.go
|- calc
|- calc.go
|- func.go
calc.go
package calc
import "errors"
func Calc(num1, num2 int, operator string) (int, error) {
switch operator {
case "+":
return sum(num1, num2), nil
case "-":
return minus(num1, num2), nil
default:
return 0, errors.New("invalid operator!")
}
}
func.go
package calc
func sum(num1, num2 int) int {
return num1 + num2
}
func minus(num1, num2 int) int {
return num1 - num2
}
test.go
package main
import (
"fmt"
"calc"
)
func main() {
var result, _ = calc.Calc(100, 23, "+")
fmt.Println(result)
}
可以看到,calc.go 的 Calc 函数可以直接调用 func.go 的 sum 和 minus 函数,因为都是 package calc,都是同一个包的
但是 test.go 必须 import calc,并且只能调 calc.Calc 不能调用 calc.sum,因为它们是不同 package 的
大写开头的标识符可以被包外引用,小写开头的标识符只能被包内引用
但是直接运行会报错
package calc is not in GOROOT (C:\Program Files\Go\src\calc)
解决方案一
go env -w GO111MODULE=off
go env -w GOPATH=xxx (xxx 就是源代码 src 的上级目录)
解决方案二
go mod init calculator // 任意名字,在 src 目录下,会产生 go.mod 文件
import 改成
import (
"fmt"
"calculator/calc"
)
这样就能正常运行了
引用的 package 里面定义 init() 会被执行
// 定义接口
type Shape interface {
display()
reset()
}
// 定义结构体
type Rect struct {
m float64
n float64
}
type Circle struct {
radis float64
}
// 实现接口 (值传递)
func (this Rect) display() {
fmt.Printf("this is a rect : %f\n", (this.m*2 + this.n*2))
}
func (this Rect) reset() {
this.m = 0
this.n = 0
}
// 实现接口 (引用传递)
func (this *Circle) display() {
fmt.Printf("this is a circle : %f\n", (2 * math.Pi * this.radis))
}
func (this *Circle) reset() {
this.radis = 0
}
func main() {
// 接口对象
var shape Shape
// 初始化为实现该接口的结构体,并调用接口
shape = Rect{5, 10}
shape.display()
shape.reset()
shape.display() // 因为是值传递,所以实际上 reset 没改变 shape 的值,display 结果没变
// 初始化为实现该接口的另一结构体,并调用接口
shape = &Circle{5} // Circle 实现 Shape 接口用的是引用传递,所以初始化要用上 & 符号
shape.display()
shape.reset()
shape.display() // 因为是引用传递,所以 reset 改变了 shape 的值,display 的结果改变了
}
这部分有点像 Java
go 语言没有 try...catch... 命令
而是主张把参数作为返回值,由调用者决定怎么处理
import (
"errors"
"fmt"
)
func divide(a int, b int) (int, error) {
if b == 0 {
return -1, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
var result, err = divide(6, 0)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(result)
}
errors.New("除数不能为0") 返回一个实现了 Error() 接口的结构体 errorString
package errors
func New(text string) error {
return &errorString{text} // errorString 通过引用传递实现 error 接口,所以初始化要加上 & 符号
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
error 是一个接口
type error interface {
Error() string
}
errors.New("除数不能为0") 不能格式化字符串,可以改成
fmt.Errorf("除数不能为 %d", 0)
可以参考 errorString 按自己的需求,自定义自己的 struct
defer 语句会被延迟处理,在 defer 所属的函数即将返回时,会将 defer 语句返序执行
func main() {
fmt.Println("start")
defer fmt.Println("defer 1")
defer fmt.Println("defer 2")
fmt.Println("end")
结果是
start
end
defer 2
defer 1
可用于释放资源,比如关闭文件,释放锁等等
f1, err1 := os.Open(filename1)
defer f1.Close()
f2, err2 := os.Open(filename2)
defer f2.Close()
// 执行文件操作
甚至程序奔溃后还会执行
可以简化代码,作用就类似于 java 的 final
作用类似于 throw
func func1() {
panic("unknown error occur")
fmt.Println("func1")
}
func func2() {
func1()
fmt.Println("func2")
}
func main() {
fmt.Println("start....")
func2()
fmt.Println("end")
}
结果为
start....
panic: unknown error occur
goroutine 1 [running]:
main.func1(...)
xxx/test.go:125
main.func2()
xxx/test.go:130 +0x27
main.main()
xxx/test.go:12 +0x8a
exit status 2
可以看到后面的 end 不会被执行
如果希望程序能继续执行,可以用 recover,类似于 catch,并且 recover 必须写在 defer 函数里面
func func1() {
panic("unknown error occur")
fmt.Println("func1")
}
func func2() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("panic: %v\n", err)
}
}()
func1()
fmt.Println("func2")
}
func main() {
fmt.Println("start....")
func2()
fmt.Println("end")
}
结果
start....
panic: unknown error occur
end
panic 语句被 func2 的 defer 函数里的 recover 捕获,这样 func2() 后面的代码能继续执行
如下面导入三方包
import (
"github.com/petermattis/goid"
)
可能会报错
test.go:8:2: no required module provides package github.com/petermattis/goid; to add it:
go get github.com/petermattis/goid
需要执行命令按照包
go get github.com/petermattis/goid
如果报错可能需要设置代理
go env -w GOPROXY=https://goproxy.io,direct
如果报冲突错误,可以直接 set 环境变量
set GOPROXY=https://goproxy.io,direct
查看 go 的环境变量
go env
再执行 get 命令就成功了
go: downloading github.com/petermattis/goid v0.0.0-20180202154549-b0b16
15b78e5
go get: added github.com/petermattis/goid v0.0.0-20180202154549-b0b1615
b78e5
go.mod 会被自动改动,多了安装的这个模块
module calculator
go 1.17
require github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
并且会多一个 go.sum 文件
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
get 的时候可以指定版本
go get github.com/google/uuid@v1.0.0
go.mod 变成
module calculator
go 1.17
require (
github.com/google/uuid v1.0.0 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
)
go.sum 变成
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
如果包已经存在,然后 go get 指定了新的版本,相当于升降级,取消就用 @none
如果不指定参数,就会自动扫描代码,把需要的三方包,都添加进来,相当于对每个包做 go get 操作
# 不指定参数,添加代码 import 的三方包
go get
依赖包被下载到 $GOPATH/pkg/mod/cache/download
代码被下载到 $GOPATH/pkg/mod
go.sum 和 go.mod 比多了个 hash 值,这是包的 hash 值,用于校验,防止下载的包或本地的包不正常,比如被篡改过
go.sum 用于保证开发使用的依赖,和实际使用的依赖是一致的
如果有配置 GOSUMDB 变量比如 GOSUMDB=sum.golang.org 那么 go 除了校验 go.sum 的值,还会去 GOSUMDB 做二次校验
go.mod 只记录直接依赖的版本,而 go.sum 记录所有用到的依赖的版本
go run 编译并运行程序,不会生成可执行文件
go run test.go
go build 会编译并生成可执行文件(如果有 main 函数的话)
go build
go install 把编译的包放到 $GOPATH/pkg 把生成的可执行文件放到 $GOPATH/pkg
go install
和 build 比多一步包编译和安装
线程是操作系统调度的最小单位,通常为了并发执行请求,会启动多个线程
这种做法在请求少的时候没问题,当并发请求量很大时,就不适用,因为大量线程的频繁切换,会严重损耗性能
为了解决这种问题,引入了协程的概念
就是线程在执行一个请求中,如果遇到 IO 等操作,不是切换其他线程执行,而是可以继续执行其他就绪的请求
就是把协程放到队列,线程空闲时就挑就绪的协程执行,遇到 IO 操作,就挂起协程,然后挑其他协程执行,挂起的协程就绪再放回队列
这样即充分利用了多核 CPU 做并发操作,又避免了频繁切换线程,大大提高了性能和并发量
其他语言都是通过开发三方包来实现这样的功能
而 Go 语言天然支持协程,只需要通过 go 关键字来开启 goroutine 即可
Go 语言能控制线程调度协程
import (
"syscall"
"github.com/petermattis/goid"
)
func main() {
fmt.Printf("main tid : [%d], gid : [%d]\n", GetThreadId(), goid.Get())
for i := 1; i <= 5; i++ {
go gofunc(i)
}
// 如果主程序退出,协程也会退出
time.Sleep(time.Duration(5) * time.Second)
fmt.Printf("main tid : [%d], gid : [%d]\n", GetThreadId(), goid.Get())
}
func gofunc(functionId int) {
for i := 1; i <= 3; i++ {
fmt.Printf("go func [%d], tid : [%d], goid : [%d]\n",
functionId, GetThreadId(), goid.Get())
time.Sleep(time.Duration(1) * time.Second)
}
}
func GetThreadId() int {
var kernel32Dll *syscall.DLL
var GetCurrentThreadIdProc *syscall.Proc
var err error
kernel32Dll, err = syscall.LoadDLL("Kernel32.dll")
if err != nil {
fmt.Printf("syscall.LoadDLL fail: %v\n", err.Error())
return -1
}
// "GetCurrentThreadId" 这个名字不能改,应该是 Kernel32.dll 里的命令
GetCurrentThreadIdProc, err = kernel32Dll.FindProc("GetCurrentThreadId")
if err != nil {
fmt.Printf("kernel32Dll.FindProc fail: %v\n", err.Error())
return -1
}
var pid uintptr
pid, _, err = GetCurrentThreadIdProc.Call()
return int(pid)
}
结果
main tid : [22752], gid : [1]
go func [1], tid : [22744], goid : [19]
go func [5], tid : [22752], goid : [23]
go func [4], tid : [22752], goid : [22]
go func [2], tid : [12620], goid : [20]
go func [3], tid : [14964], goid : [21]
go func [4], tid : [22744], goid : [22]
go func [5], tid : [14964], goid : [23]
go func [1], tid : [12620], goid : [19]
go func [2], tid : [15288], goid : [20]
go func [3], tid : [14964], goid : [21]
go func [3], tid : [22752], goid : [21]
go func [2], tid : [14964], goid : [20]
go func [5], tid : [30092], goid : [23]
go func [4], tid : [22744], goid : [22]
go func [1], tid : [15288], goid : [19]
main tid : [15288], gid : [1]
线程和协程不是固定的一一对应关系,每个线程都可以调用任意一个协程
main 也是一个协程,同样可以被不同的线程调用
channel 可以用于协程间的通信
// 创建一个传递 int 数据的 channel, 缓冲区为 100
ch := make(chan int, 100)
// 发送消息到 channel
ch <- msg
// 从 channel 读取消息
msg := <-ch
如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值
如果通道带缓冲区,发送方不会阻塞,除非缓冲区满了
接收方在有值可以接收之前会一直阻塞
package main
import (
"fmt"
"time"
)
var channel = make(chan int, 5)
func main() {
go producer()
go consumer()
fmt.Println("start producer and consumer")
time.Sleep(time.Duration(5) * time.Second)
}
func producer() {
for i := 0; i <= 10; i++ {
channel <- i
fmt.Printf("produce %d\n", i)
}
}
func consumer() {
for i := 0; i <= 10; i++ {
msg := <-channel
fmt.Printf("consume %d\n", msg)
if i%3 == 0 {
time.Sleep(time.Duration(1) * time.Second)
}
}
}
结果
start producer and consumer
produce 0
produce 1
produce 2
produce 3
produce 4
produce 5
consume 0
consume 1
consume 2
consume 3
produce 6
produce 7
produce 8
consume 4
consume 5
consume 6
produce 9
produce 10
consume 7
consume 8
consume 9
consume 10
可以在读取数据的时候判断有没有出错
// ok 是 bool 类型
v, ok := <-ch
关闭通道
close(ch)
遍历通道
package main
import (
"fmt"
"time"
)
var channel = make(chan int, 5)
func main() {
go producer()
// 遍历通道
for i:= range channel {
fmt.Printf("consume %d\n", i)
}
time.Sleep(time.Duration(5) * time.Second)
}
func producer() {
for i := 0; i <= 10; i++ {
channel <- i
fmt.Printf("produce %d\n", i)
}
// 关闭通道
close(channel)
}
结果
produce 0
produce 1
produce 2
produce 3
produce 4
produce 5
consume 0
consume 1
consume 2
consume 3
consume 4
consume 5
consume 6
produce 6
produce 7
produce 8
produce 9
produce 10
consume 7
consume 8
consume 9
consume 10
如果 producer 里面没有 close channel 的话,range 会阻塞等待下一个消息,可是已经没有线程会发消息了,会报死锁错误
按 github star
gin 看起来比较流行
安装
go get -u github.com/gin-gonic/gin
例子
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// group 定义的 url 有相同的前缀
v1 := router.Group("/v1")
{
v1.POST("/login", loginHandler)
// :groupId 表示这个是必须有的变量
// 可以匹配 /service/groupA
// 不能匹配 /service/ 或 /service
v1.GET("/service/:groupId", listServiceHandler)
// 请求 /service/group 的时候,不会和上一个 /service/:groupId 的 handler 冲突
v1.GET("/service/group", listGroupHandler)
// url 可以有多个可变值
v1.PUT("/service/:groupId/:serviceId", addServiceHandler)
// *serviceId 表示这个是可有可无的变量
// 可以匹配 /service/groupA/ 或 /service/groupA/serviceB
v1.DELETE("/service/:groupId/*serviceId", deleteServiceHandler)
}
// 单独定义一个 url
router.GET("/status", statusHandler)
fmt.Println("start server")
// 启动
router.Run(":8080")
}
var db = make(map[string][]string)
type User struct {
Name string `json:"name"`
Password int64 `json:"password"`
}
func loginHandler(c *gin.Context) {
// 按 json 格式获取 body 的值
json := User{}
c.BindJSON(&json)
fmt.Println(json)
// 返回状态和内容
c.String(http.StatusOK, fmt.Sprintf("\nHello %s\n", json.Name))
}
func listServiceHandler(c *gin.Context) {
// 获取路径参数
group := c.Param("groupId")
services, ok := db[group]
if !ok {
c.String(http.StatusNotFound, "group not exist")
return
}
data, _ := json.Marshal(services)
c.String(http.StatusOK, string(data))
}
func listGroupHandler(c *gin.Context) {
// 获取 query 参数
includeEmptyGroup := c.Query("includeEmptyGroup")
keys := make([]string, 0)
for k := range db {
if len(db[k]) == 0 {
if includeEmptyGroup != "yes" {
continue
}
}
keys = append(keys, k)
}
data, _ := json.Marshal(keys)
c.String(http.StatusOK, string(data))
}
func addServiceHandler(c *gin.Context) {
group := c.Param("groupId")
service := c.Param("serviceId")
_, ok := db[group]
if !ok {
db[group] = make([]string, 0) // 初始化长度
}
db[group] = append(db[group], service)
c.String(http.StatusOK, "\nadd service successfully\n")
}
func deleteServiceHandler(c *gin.Context) {
group := c.Param("groupId")
service := c.Param("serviceId")
_, ok := db[group]
if !ok {
c.String(http.StatusNotFound, "group not exist")
return
}
// 收到的 service 带有 / 符号,即 /xxxx
fmt.Printf("receive service %s!\n", service)
service = service[1:]
found := false
if service == "" {
delete(db, group)
found = true
} else {
lenght := len(db[group])
for i := 0; i < lenght; i++ {
if service == db[group][i] {
db[group] = append(db[group][:i], db[group][i+1:]...) // ... 是必须的
found = true
break
}
}
}
if found {
c.String(http.StatusOK, "del successfully")
} else {
c.String(http.StatusNotFound, "service not exist")
}
}
func statusHandler(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}
测试
> curl -X POST localhost:8080/v1/login -d ‘{"name":"lin", "password":123}‘
Hello lin
> curl -X PUT localhost:8080/v1/service/group_A/service_1
> curl -X PUT localhost:8080/v1/service/group_B/service_2
> curl -X PUT localhost:8080/v1/service/group_B/service_3
> curl -X PUT localhost:8080/v1/service/group_C/service_4
add service successfully
> curl -X DELETE localhost:8080/v1/service/group_C/service_4
del successfully
> curl -X GET localhost:8080/v1/service/group
["group_A","group_B"]
> curl -X GET localhost:8080/v1/service/group?includeEmptyGroup=yes
["group_A","group_B","group_C"]
> curl -X GET localhost:8080/v1/service/group_A
["service_1"]
> curl -X GET localhost:8080/v1/service/group_B
["service_2","service_3"]
> curl -X GET localhost:8080/v1/service/group_C
[]
> curl -X GET localhost:8080/v1/service/group_D
group not exist
> curl -X DELETE localhost:8080/v1/service/group_D/service_4
group not exist
> curl -X DELETE localhost:8080/v1/service/group_C/service_4
service not exist
> curl -X DELETE localhost:8080/v1/service/group_B/
del successfully
> curl -X GET localhost:8080/v1/service/group
["group_A"]
> curl -X GET localhost:8080/v1/service/group?includeEmptyGroup=yes
["group_A","group_C"]
> curl -X GET localhost:8080/status
{"message":"pong"}
和 springboot 比感觉还差了一些
原文:https://www.cnblogs.com/moonlight-lin/p/15225532.html