1、go文件执行顺寻go文件由main包中的main()函数开始执行,文件开始执行时,由底层到上层开始加载import引用包,每个import引用包下.go文件内容加载顺序:const>var>init()方法>main方法,加载完再返回上层继续按这个顺序加载文件内容编译在go.mod文件目录下编译go项目:go build指定操作系统、系统架构编译:GOOS=linux GOARCH=amd64 go build修改编译后的文件输出路径: go build -o ./test/ ./main.go #把编译后的exe文件放到当前目录的./test/目录下,且main.go文件路径在当前目录下./main.go基础数据类型://go:build tag1package main //在本go文件的package头部加上 【//go:build tag1】,然后直接 go build -tags "tag1" , 就能编译本go文件,表示本go文件别名是tag1import ("context""errors""fmt""log""myproject2/models"_ "myproject2/models" // 下划线 _ 表示导入该包但不使用也可以,否则报错"os""strconv""sync""sync/atomic""time""unsafe""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger")type STR string //自定义类型string的别名为STRvar myFunc func() string //定义变量myFunc,它是一个方法,该方法入参为空,返回值是stringfunc main() {//一下var中定义的都是局部变量,如果不使用会编译报错,但是在main()外面定义的全局变量不使用则可以fmt.Println(os.Args)var s string = "1" // = 号必须先用var声明(后面的string可以不写),否则会报错, 而 := 号可以不用声明由编译器自动推导var ss1 = "1"ss := "11"var bb byte = s[0]fmt.Println("hello world " + string(bb) + ss)fmt.Println("hello world " + ss1)//二进制var a = 0b11var b = 0o11var c = 0x11//Printf需要写转义符号%dfmt.Printf("a=%d\n", a)fmt.Printf("b=%d\n", b)fmt.Printf("c=%d\n", c)//Print、Println不需要写转义符号,会自动转义fmt.Println("a=", a)fmt.Println("b=", b)fmt.Println("c=", c)//浮点数var f1 float32 = 1.0var f2 = 2.0f1 = float32(f2)fmt.Println("f1=", f1)fmt.Print("f1=", f1, "\n")fmt.Printf("f1=%f\n", f1)//复数 a+bi 的形式,a是实数,b是虚数var c1 complex64 = 1 + 1ivar c2 = 1 + 1i //默认complex128类型var c3 = complex(1, 1) //默认complex128类型c1 = complex64(c2) //高精度要强转成低精度,强转后c1等于complex64类型的complex64(c2),但c2仍是complex128类型的1 + 1i,c1不等于c2因为类型不同不能直接比较fmt.Println("c1==complex64(c2)?", c1 == complex64(c2)) //true,类型相同,同时比较实部和虚部也相同,但c2仍是complex128类型的1 + 1i,c1不等于c2因为类型不同不能直接比较fmt.Println("c2 == c3?", c2 == c3) //true,类型相同,同时比较实部和虚部也相同fmt.Println("complex128(c1) == c2?", complex128(c1) == c2) //true,类型相同,同时比较实部和虚部也相同x := real(c1)y := imag(c1)fmt.Println("c1的实部real(c1)=", x)fmt.Println("c1的虚部imag(c1)=", y)//byte类型:byte=uint8(uint8是无符号正整数取值范围0-256,所以byte取值0-256),byte可以和string类型相互转换,一个英文占一个byte长度、一个string长度,一个中文占三个byte长度、一个string长度var sss string = "go你好" //长度=8,go中一个string中文占三个长度,一个string英文占一个长度fmt.Println("sss=", sss)var bc byte = 127 // java中byte占8个字节,取值-128~127;但go中byte=uint8,uint8是无符号正整数取值范围0-256,所以byte取值0-256var bbb []byte = []byte(sss) //string转byte,占8个byte长度fmt.Println("string类型转byte,你好=", bbb)fmt.Println("byte类型转string:", bbb, "=", string(bbb))fmt.Println("sss=", sss[0:8]) //获取从sss[0]到sss[7]的字符串fmt.Println("bbb", string(bbb[0:8])) //获取从bbb[0]到b[7]的字符串fmt.Println("bc=", bc)//rune类型:类似于java中的char类型,可以和string类型相互转换,rune类型等价于int32类型//也就是byte[] rune[] string之间可以相互转换//中英文均占一个runevar r1 rune = 'a' //单字符用rune ,多字符要用rune数组var r2 []rune = []rune("你好go") //长度为4,多字符要用rune数组fmt.Println("r1=", r1) //直接打印会打印出数字97fmt.Println("r2=", r2) //直接打印会打印出数字fmt.Println("r1=", string(r1)) //打印出字符 afmt.Printf("r1=%c\n", r1) //%c会打印出字符 afmt.Printf("r1=%q\n", r1) //%q会打印出带引号的字符 'a'fmt.Println("r2=", string(r2[0:3])) //获取r2[0]到r2[2]的字符串:你好g//字符串转数字si := "123"num, err := strconv.Atoi(si) //Itoa是strconv包中的字符转数字方法,他返回2个返回值,转换成功则err=nilfmt.Println("字符转数字:", num, err)sii := strconv.Itoa(num) //Itoa是strconv包中的数字转字符方法,但他只返回1个返回值fmt.Println("数字转字符:", sii)//uint64数组和字符型十进制、十六进制、八进制、二进制互转uni, uerr := strconv.ParseUint(si, 10, 32) //把十进制字符si转为uint64形式,转换成功则err=nil;转换后检查数字是否<32位如果超出err也会返回报错fmt.Println("十进制字符'123'转uint64:", uni, uerr) //uni=123uni2, uerr2 := strconv.ParseUint(si, 16, 32) //把十六进制字符si转为uint64形式,转换成功则err=nil;转换后检查数字是否<32位如果超出err也会返回报错fmt.Println("十六进制字符'123'转uint64:", uni2, uerr2) //unit2=3*16^0+2*16^1+1*16^2=291//int64和字符型十进制、十六进制互转ni, uerr3 := strconv.ParseInt(si, 10, 32) //把十进制字符si转为int64形式,转换成功则err=nil;转换后检查数字是否<32位如果超出err也会返回报错fmt.Println("十进制字符'123'转int64:", ni, uerr3) //uni=123ni2, uerr4 := strconv.ParseUint(si, 16, 32) //把十六进制字符si转为int64形式,转换成功则err=nil;转换后检查数字是否<32位如果超出err也会返回报错fmt.Println("十六进制字符'123'转int64:", ni2, uerr4) //unit2=3*16^0+2*16^1+1*16^2=291//数字转字符串uintStr := strconv.FormatUint(uni, 10) //unit64的数字转为字符(该字符是十进制形式)fmt.Println("unit64的数字123转string:", uintStr) //123intStr := strconv.FormatInt(ni, 10) //int64的数字转为字符(该字符是十进制形式)fmt.Println("int64的数字123转string:", intStr) //123//string类型var str1 string = "hello\nworld" // \n是换行符号var str2 = `helloworld` //单引号里的所有字符串都是有效的,包括\n换行fmt.Println("str1=", str1)fmt.Println("str2=", str2)fmt.Println("str1==str2 ?", str1 == str2) //true,str2中hello和world之间有换行\n,所以str1和str2相等//零值:变量没有赋值时的默认值,string为空字符串,boolean为false,int为0,float为0.0,complex为0+0i//声明mapvar m1 map[string]int//初始化map+赋值m1 = make(map[string]int, 10) //初始化map并设置容量,可以不设置容量m1["a"] = 1 //赋值m1["b"] = 2var k int = m1["a"]fmt.Println("k=", k, ",map长度:", len(m1))val, exist := m1["c"]fmt.Println("c是否存在于m1中:", val, exist) //输出0 false ,0是int的零值//删除map中的keyaa, eex := m1["a"]fmt.Println("a是否存在于m1中:", aa, eex) //输出1 truedelete(m1, "a") //删除map中的keyaa1, eex1 := m1["a"]fmt.Println("a是否存在于m1中:", aa1, eex1) //输出0 false ,0是int的零值//不声明,直接初始化并赋值m2 := map[string]int{"a": 1, "b": 2}fmt.Println("m2=", m2)//多线程间修改map之前要用lock锁住,否则可能报错 ;goroutine线程引发的并发问题也可以用sync.Mutex锁解决var lock sync.Mutexgo func() { //线程goroutine(随main主线程结束而结束,除非使用同步方法):go+闭包lock.Lock() //或者用sync.Map结构代替普通的lock+map,但是sync.Map开销大且适合读多写少的情况所以一般推荐使用lock+map的方式for im := 1; im < 100; im++ {m2["a"]++fmt.Println("协程2,m2:", m2)}lock.Unlock()}() //有括号表示立即执行闭包(匿名方法),括号里是闭包的入参,该闭包没有入参所有括号里没有参数go func() { //线程goroutine(随main主线程结束而结束,除非使用同步方法):go+闭包lock.Lock()for im := 1; im < 100; im++ {m2["a"]++fmt.Println("协程2,m2:", m2)}lock.Unlock()}() //有括号表示立即执行闭包(匿名方法),括号里是闭包的入参,该闭包没有入参所有括号里没有参数select { //select case要么执行某个case要么执行default,否则会一直阻塞case <-time.After(3 * time.Second):fmt.Println("主线程过了3s") //监听3s}//定义返回值ma, mb := func1()fmt.Println("ma=", ma, "mb=", mb)fmt.Printf("ma=%d,mb=%s \n", ma, mb)//多变量声明:使用varvar d1, d2 = 1, "2" // 类似java中var d1=1,d2=2是不允许的fmt.Println("d1=", d1, "d2=", d2)fmt.Printf("d1的类型:%T,d2的类型:%T\n", d1, d2)//多变量声明:不使用var,自动推断类型d3, d4 := 3, 4fmt.Println("d3=", d3, "d4=", d4)//指针var p = 1var pp *int = &p //*指针类型只能通过获取变量的变量地址赋值,p是变量,&p是变量地址【区别于c语言int *p=1 是可以的,但是go语言会报错,同时go指针也不能做运算以偏移位置]var ppp **int = &pp //获取指针pp的地址,赋值给二级指针ppp,ppp=&pp,同时二级指针指向的内容 *ppp=*(&pp)=pp,**ppp=*pp=p=1fmt.Println("p=", p, "pp=", pp, "ppp=", ppp, "*ppp=", *ppp, "**ppp=", **ppp)*pp = 2 //通过指针改变值fmt.Println("通过指针修改p=", p, "pp=", pp, "ppp=", ppp)var pppp *int //指针的零值是nil,即pppp默认值是nil,同时 &pppp 是二级指针即nil的地址也是有值的fmt.Println("pppp=", pppp, "&pppp=", &pppp)//不建议这样操作:// 虽然不能直接用指针进行加减运算,但是可以通过unsafe.Pointer()函数将指针转换为unsafe.Pointer类型,再通过uintptr函数将unsafe.Pointer类型转为uintptr类型,再对uintptr进行加减运算// 反过来也可以通过unsafe.Pointer()函数将uintptr类型转为unsafe.Pointer类型,再将unsafe.Pointer类型强转成 *unit8 指针类型,就可以读取指针指向的值fmt.Println("指针加减运算前p=", p)u := unsafe.Pointer(&p) //得到unsafe.Pointer类型up := uintptr(u) //得到uintptr类型up = up + 1 //对uintptr类型加减操作uu := unsafe.Pointer(up) //得到unsafe.Pointer类型nn := (*uint8)(uu) //把unsafe.Pointer类型强转成 *unit 指针类型fmt.Println("指针加1运算后*nn=", *nn)*nn = 2fmt.Println("*nn=", *nn)//结构体sps := models.Person{Name: "猪八戒",// age: "20", 结构体中age是小写非公开的,所以此处不同包不能访问否则报错}ps1 := models.Person{Name: "猪八戒",// age: "20", 结构体中age是小写非公开的,所以此处不同包不能访问否则报错}fmt.Println("访问公开的方法,该方法可以修改调用对象的Name属性,修改前:", ps.GetOldName(), "希望把值修改成:", ps.GetNewName(), "实际修改后:", ps.GetOldName()) //依次输出:猪八戒 张三 张三fmt.Println("访问公开的方法,该方法不可以修改调用对象的Name属性,修改前:", ps1.GetOldName(), "希望把值修改成:", ps1.GetNewName2(), "实际修改后:", ps1.GetOldName()) //依次输出:猪八戒 张三 猪八戒//fmt.Println("访问非公开的方法报错:", ps.getName3())ps2 := &ps //ps2是结构体ps的地址fmt.Println("普通入参:", GetOldName2(ps2))fmt.Println("普通入参:", GetOldName3(ps))fmt.Println("结构体指针直接调用结构体方法:", ps2.GetNewName3(), ",ps2.Name:", ps2.Name) //GetNewName3()是值接收者,不会改变指针ps2.Name//结构体指针直接修改属性ps2.Name = "沙僧" //和c语言一样,直接指针访问属性fmt.Println("指针修改属性,ps2.Name:", (*ps2).Name)fmt.Println("ps.Name:", ps.Name)//结构体(*指针)直接修改属性(*ps2).Name = "孙悟空"fmt.Println("*指针修改属性,ps2.Name:", (*ps2).Name)fmt.Println("ps.Name:", ps.Name)//匿名结构体(可以在方法中或者方法外定义匿名结构体)var p1p1 = struct {Name stringage intint //匿名属性}{ //这里初始化Name: "王五",age: 12,int: 13, //赋值匿名属性}fmt.Println("p1p1.Name:", p1p1.Name, "p1p1.age:", p1p1.age, "匿名属性值:", p1p1.int) //age非公开属性,此处同包可以访问//常量:const定义全局、局部常量,定义时就要确定值,和java一样确定后就不能修改值const i int = 1const ii = 1const i1, i2 int = 1, 2const i3, i4 = 3, "4"const (i5, i6 = 5, "6"i7 = 7i8 int = 8)//不同于java,go用常量定义枚举const (male string = "男"female string = "女")fmt.Println("常量定义枚举,", male, ",", female)const (male1 STR = "男" //STR是string类型别名female1 STR = "女")fmt.Println("常量定义枚举,", male1, ",", female1)//iota是一个整数常量(默认值是int或者byte类型的0),它只能与const合用:iota位于const的第n行那么取值就是 iost=n-1 ,且后续定义的变量自动加一const (a0 int = iota //a0=iost=0,第1行-1a1 //值为1,自动加一a2 //值为2,自动加一)fmt.Println("a0:", a0, ",", "a1:", a1, "a2:", a2)const (a00 int = 11a11 int = iota //a0=iost=1,第2行-1a22 //值为2)fmt.Println("a00:", a00, ",", "a11:", a11, "a22:", a22)//iota默认值0const ba = iotafmt.Println("ba:", ba)var jj STR = "1"fmt.Println("string类型别名:", jj)//运算符:优先级 【[] * / % << >> &】> 【+ - | ^】>【> >= < <=】>【&& ||】var iii int = 1iii++ //自增自减运算符必须在后面,不能放前面否则报错,如--iii(但java可以)//if elseif 1 > 2 {} else if c := 1; iii == 1 { //先新定义变量c,再判断条件fmt.Println("c= ", c)} else if d := 1; c > 1 { //if else中新定义的变量c只在if else中有效fmt.Println("d=", d)}var er error = errors.New("报错了")if er != nil {fmt.Println("报错信息:", er.Error())}//switch caseswitch bi := 1; 1 { //bi只在switch中有效case 1:fmt.Println("1,", bi) //case自带break功能不需要特意声明break(java中要手动break)case 2:fmt.Println("2")default:fmt.Println("3") //所有的case都不符合时执行}var d interface{} = 1 //interface{}等价于java中的Object类型d = 1 //赋值d为int类型// d = byte(1)// d = models.Person{// Name: "猪八戒",// // age: "20", 结构体中age是小写非公开的,所以此处不同包不能访问否则报错// }// d = &ps //ps是结构体变量d = &iii //iii是int变量,d此时是 *int 类型指针switch t := d.(type) { //这些写法比较特殊,d只能是interface{}类型,且d.(type)在switch中使用,会先返回int类型匹配case情况,然后再把d的值1赋给tcase int:fmt.Println("d是int类型,t的值:", t)case byte:fmt.Println("d是byte类型,t的值:", t)case models.Person:fmt.Println("d是Person结构体类型,t的值:", t)case *models.Person:fmt.Println("d是Person结构体指针类型,t的值:", t)case *int:fmt.Println("d是int指针类型,t的值:", t)default:fmt.Println("d是未知类型,t的值:", t) //t=nil}//把interface{}转为具体的类型:int float...var ai interface{} = 123aii := ai.(int) //把ai转为int类型赋值给aii=123switch ai.(type) { //也可以写成switch i:=ai.(type)效果是一样的都是判断属于哪个case类型case int:fmt.Println("aii是int类型,值:", aii)}//结构体变量之间互转:两个结构体定义属性名称、类型、属性顺序必须相同才能转换成功否则编译报错(注意:结构体指针之间不能互转)ct := models.ConverTest1{Name: "小邓",Age: 28,}ct2 := models.ConverTest2(ct) //把ConverTest1结构体变量转为ConverTest2结构体变量(注意:结构体转换的写法不同于普通类型的 .type() 写法)fmt.Println("把ConverTest1结构体变量转为ConverTest2结构体变量:", ct2)//结构体实现接口的方法fmt.Println("开始刷信用卡...")creditCard := &CreditCard{Use: 0,Limit: 1000, //信用卡额度是1000元}buyMethod(creditCard, 500) //刷信用卡500元buyMethod(creditCard, 600) //再刷信用卡600元fmt.Println("开始刷借记卡...")debitCard := &DebitCard{Balance: 2000, //借记卡余额2000元}buyMethod(debitCard, 600) //刷借记卡500元buyMethod(debitCard, 1600) //再刷借记卡1600元//父类接口变量=子类结构体变量/子类结构体地址变量,类似java的继承用法var card1 CardMapper = creditCardfmt.Println("信用卡已使用额度:", card1.getBalance())var card2 CardMapper = debitCardfmt.Println("借记卡余额:", card2.getBalance())//空接口=任意类型的数据var blank BlankMapper = creditCard //blank是空接口BlankMapper变量blank = 2blank = &iiifmt.Print("空接口变量=", blank)//for循环f := 1for f < 10 {fmt.Println("f:", f)f++}//无限循环//ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*2)) //创建一个2秒后超时的context,下划线_为忽略cancel函数,即2s后自动调用context对应的cancel函数,但是建议还是加上手动调用[defer cancel()]释放资源,虽然ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*1/2)) //创建一个2秒后超时的context,2s后自动调用context对应的cancel函数,但是建议还是加上[defer cancel()]手动释放资源defer cancel() //main函数执行完后再执行cancel释放资源,此时整个过程会执行两次cancel,一次是等main函数执行完后执行defer cancel(),一次是2s后自动执行cancel()var started bool //默认falsevar stopped atomic.Bool // 原子型,默认false//无限循环for {if started == false { //只执行一次:第一次for循环的时候started = true //started置为truego func() { //线程goroutine(随main主线程结束而结束,除非使用同步方法):go+闭包,该闭包没有入参,启动一个goruntine线程(go的线程叫goroutine,它不同于java线程,它更轻量,goroutine之间一般用chanel通信)select { //启动goruntine线程中监听case <-ctx.Done(): //监听接收context超时信号,只有当context超时后才会执行一次,而手动defer cancel()和超时无关不会执行该代码fmt.Println("ctx done") //超时后打印一次stopped.Store(true) //超时后设置原子型stopped为truereturn}}() //有括号表示立即执行闭包(匿名方法),括号里是闭包的入参,该闭包没有入参所有括号里没有参数}fmt.Println("循环中...")if stopped.Load() { //重新加载原子型数据break}}//range遍历切片(数组)var ri [5]intri[1] = 2for i, item := range ri { //range遍历,i是下表,item是值fmt.Println(i, "下标值位:", item)}//range遍历mapvar mm = make(map[string]string)mm["k1"] = "v1"mm["k2"] = "v2"for k, v := range mm { //遍历输出的顺序不一定是k-v保存的顺序fmt.Println(k, ",", v)}//fmt中%v表示自动识别输出类型,%t输出true/false类型//切片、数组对比:// 1、go切片、数组都可以看作java中的数组;// 2、数组只有长度,切片有长度、容量;// 3、数组定义是要指定长度而切片不用;// 4、切片长度一般用于当前切片能打印显示的数据长度,容量可以用于别的切片赋值当前切片后能打印显示的数据长度// 5、切片容量不够时按照翻倍原则自动扩容,扩容后新切片数据相同但地址不同了var st []int = []int{} //切片,不指定长度var st1 []int //切片,不指定长度var at [2]int //数组,指定长度fmt.Printf("st:%v,st1:%v,at:%v", st, st1, at)//切片=java中的数组,但切片是引用类型所以一旦被别的切片关联赋值了就会共享修改数据:sl1 := make([]int, 4, 4) //make创建切片,长度10,容量10,容量可以不写就和长度一样;切片长度一般<=容量sl2 := []int{11, 12, 13} //直接定义切片sl3 := sl2[0:2] //从sl1切片中定义sl3,取值自下标0到1,而且切片是引用类型,改变sl3会同时改变原切片sl1(使用[]int{}类型进行append、copy修改则不会修改原切片内容,有一种情况例外:指针/引用类型的切片即使使用copy还是会改变原切片内容,此时用深拷贝就不会改变原切片内容)fmt.Printf("sl3:%v \n", sl3) //sl3=11 12//append总结:用append不想改变原切片就用新的 []int{} 去拼接,否则会检查append第一个参数切片的容量是否大于等于两个参数切片容量之和,若大于则不扩容即可共享引用地址则会改变第一个参数切片的内容,若小于则要扩容则不共用引用地址则不改变第一个参数切片的内容//sl4 := append(sl1[0:1], sl2...)//如果写成sl4 := append(sl1[0:1], sl2...) 则sl1会被改变成0 11 12 13,因为此时sl1因为容量为4>=sl1[0:1]+sl2的长度=1+3=4,相反如果sl1容量<4则不会改变sl1;append的第二个位置是切片的话必须写三个点... 否则报错,表示展开sl2的元素//如果写成这样则sl1不会被改变,仍然是 0 0 0 0sl4 := append(append([]int{}, sl1[0:1]...), sl2...)fmt.Println("sl4:", sl4) //sl4=0 11 12 13// sl1[0] = 44// fmt.Println("sl4元素改变:", sl4)fmt.Println("sl1元素不变:", sl1)// sl4[1] = 21// fmt.Println("sl1元素不变:", sl1)sl5 := append(sl1, 11, 12, 13) //和sl4等价,合并切片,sl5=0 11 12 13fmt.Println("sl5:", sl5)//sl6 := append(sl5[0:2], sl5[3:]...) //移除sl5中下标为2的元素,sl6=0 11 13 ,sl5[0:2]=1 11 ,sl5[3:]=13sl6 := append(append([]int{}, sl5[0:2]...), sl5[3:]...) //推荐这样使用就不会修改sl5,即sl5仍为0 11 12 13fmt.Println("sl6:", sl6) //sl6=0 11 13//如果写成【sl6 := append(sl5[0:2], sl5[3:]...)】则sl5会被改变成sl5=0 11 13 13,此时sl5因为容量为4>=sl5[0:2]+sl5[3:]的长度,所以赋值直接在sl5的前排位置修改后再赋给sl6,也就是sl5由【0 11 12 13】的前排【0 11 12】变成【0 11 13】再赋给sl6,则sl5最后变成【0 11 13 13】//如果写成【sl6 := append(append([]int{}, sl5[0:2]...), sl5[3:]...) 】则sl5=0 11 12 13不变fmt.Println("sl5:", sl5)sl7 := append(sl5[0:2], append([]int{1}, sl5[2:]...)...) //往切片sl5的下标2位置插入元素1,sl7=0 11 1 13 13fmt.Println("sl7:", sl7) //sl7=0 11 1 12 13var sl8 = make([]int, len(sl7)) //必须指定长度否则报错copy(sl8, sl7) //复制切片sl7到sl8,修改新切片的内容不会影响原切片;如果新切片长度<原切片那么只能赋值一部分元素到新切片sl8[0] = 1fmt.Println("sl8:", sl8) //sl8=1 11 1 12 13,且sl7内容不变fmt.Println("sl7:", sl7)var arr [2]int //定义了一个int数组,长度2,所有值默认初始化0;go数组只有长度,go切片有长度、容量arr[0] = 1//var arr1 []int = [2]int{1, 2} //这样写会报错,var定义数组的时候不能初始化arr1 := [2]int{1, 2} //定义长度为2的int数组,同时可以初始化arr2 := [...]int{1, 2, 3} //定义int数组并初始化,...会自动推导长度fmt.Println("arr1:", arr1, ",arr2:", arr2)arr3 := func(arr4 [2]int) { //变量arr3是一个匿名函数(闭包)fmt.Println("闭包arr4:", arr4)}arr3(arr1) //调用匿名函数,arr1长度要和入参要求长度一致否则报错var mp [2]map[string]string //定义了一个map的数组,长度2,所有值默认为nil没有初始化//mp[0]["a"]="b" //此处会报错因为没有初始化map数组,需要用make逐个初始化数组map元素再复制mp[0] = make(map[string]string) //初始化第一个mapmp[0]["a"] = "b" //赋值mp[1] = make(map[string]string) //初始化第二个mapmp[1]["a"] = "b" //赋值//多维数组arr5 := [2][3]int{{1, 1, 1}, {1, 1, 1}} //二维数组,2行3列arr6 := [2][3][4]int{{{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}}, {{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}}} //三维数组:先看2行,得到{ {},{} },再看3行,得到 { { {},{},{} },{ {},{},{} } } ,最后看4列说明最里头每一行有4个元素得到 { { {1,1,1,1},{1,1,1,1},{1,1,1,1} },{ {1,1,1,1},{1,1,1,1},{1,1,1,1} } }fmt.Println("arr5:", arr5, ",arr6:", arr6)//select case与switch case不同:switch case等价于java的switch case用法// select case只能用于chanel操作,select后面要么立即执行某一个case,要么立即执行default,如果既不能执行case又没有default那么会一直阻塞比如【select {}】,如果有多个case可以立即执行那么随机执行一个case;//如果要执行多个case可以把select case写在for里面,此时select case自带的break会跳出select但不会跳出forfmt.Println("=== 使用循环处理多个case ===")ch1 := make(chan string, 2) //建立2个缓冲区大小的chanel,可以接收2次string型的消息ch2 := make(chan string, 2)ch3 := make(chan string, 2)// 向channel发送多个数据ch1 <- "msg1-1" //第一次接收string型消息ch1 <- "msg1-2" //第二次接收string型消息ch2 <- "msg2-1"ch2 <- "msg2-2"ch3 <- "msg3-1"//for循环中使用Label关键字跳转UP:for ik := 1; ik < 3; ik++ {fmt.Println("ik:", ik)for ikk := 1; ikk < 3; ikk++ {fmt.Println("ikk:", ikk)continue UP //跳到UP关键字处,ik的值上下文还存在,所以还是会一直自增//goto UP //跳到UP关键字处,ik的值上下文不存在了,所以ik的值每次都为重置为1,这就导致了死循环,不建议用goto}}fmt.Println("")//匿名函数=闭包nf := func(a int, b int) string { //定义了一个匿名函数:入参是int ,返回值是string ;变量nf等价于一个匿名函数return string(a + b)}fmt.Println("变量nf是一个匿名函数,返回值:", nf(33, 33))nf1 := func(a int, b int) func(int, int) string { //定义了nf1=第一个匿名函数:入参是int ,返回值是第二个匿名函数【 func(int ,int) string 】,nf1=第一个匿名函数return func(a1 int, b1 int) string {return string(a1 + b1)}}nf2 := nf1(33, 33) //nf1(33, 33)会调用第一个匿名函数,同时返回值是第二个匿名函数,所以nf2=第二个匿名函数fmt.Println("变量nf1是一个匿名函数,该匿名函数的返回值也是一个匿名函数,调用第一个匿名函数:", nf2(33, 33)) //调用第二个匿名函数,nf2(33, 33)等价于nf1(33,33)(33,33)nf3 := func(a int, b int) func(int, int) string { //定义了nf3=第一个匿名函数:入参是int ,返回值是第二个匿名函数【 func(int ,int) string 】,nf1=第一个匿名函数return func(a1 int, b1 int) string {return string(a1 + b1)}}fmt.Println("调用第二个匿名函数:", nf3(33, 33)(33, 34))nf4 := func(a int, b int) func(string, string) string { //定义了nf4=第一个匿名函数:入参是int ,返回值是第二个匿名函数【 func(int ,int) string 】,nf1=第一个匿名函数return func(a1 string, b1 string) string {return a1 + b1}}(1, 2) //最大区别是这一行,此时会立即调用第一个匿名函数,且入参要和第一个匿名函数的入参一样,立即调用后会把第一个匿名函数的返回值赋给nf4,第一个匿名函数的返回值就是第二个匿名函数,所以此时nf4=第二个匿名函数,可以直接使用nf4("a","b")调用第二个匿名函数fmt.Println("第一个匿名函数会被立即调用,调用第二个匿名函数:", nf4("a", "b"))//变量等价于方法pp2 := models.Person{Name: "小猪",// age: "20", 结构体中age是小写非公开的,所以此处不同包不能访问否则报错}myFunc = pp2.GetNewName2fmt.Println("变量myFunc是一个方法,调用:", myFunc())// 使用循环处理所有可用的数据var bs bool = falsefor {select {case msg, ok := <-ch1: //获取chan1中的数据(包括正常发送的数据、close(ch1)数据,当接收到close(ch1)数据时ok=false)fmt.Printf("从ch1接收: %s\n", msg, ok) //case自带break效果case msg, ok := <-ch2: //获取chan2中的数据fmt.Printf("从ch2接收: %s\n", msg, ok)case msg, ok := <-ch3: //获取chan3中的数据fmt.Printf("从ch3接收: %s\n", msg, ok)default: //default自带break效果fmt.Println("所有channel都没有数据了")bs = true}if bs == true {break}}//遍历chan通道数组;定义chan类型数据必须使用makechh := make(chan int, 5) //该chan有5个缓冲容量go testChh(chh) //线程goroutine(随main主线程结束而结束,除非使用同步方法):go+方法,线程执行异步方法for ci := range chh { //从chan中获取数据,会一直等待通道数据,知道close(chh)关闭通道则退出循环,如果等了很久都没有等到通道数据则报异常fmt.Println("ci=", ci) //ci是值而不是下标,数组遍历时这里有下标和值(i v)}fmt.Println()//只能发送数据到这个chan,而不能用该chan接收数据chh1 := make(chan int, 2) //该chan只有两个缓存区(如果不指定缓存区则默认为1),所以当发满2个int数据到该chan时会马上阻塞直到从该chan读数据出去之后才能继续往该chan发送数据onlySend(chh1)fmt.Println("只发送数据的chan接收到数据:", <-chh1) //onlySend里面发送了2次数据到该chan,这里只接收一次所以只能打印第一次发送的数据 123//只能接收该chan的数据,而不能发送数据到这个chanchh1 <- 456onlyReceive(chh1)fmt.Println()// //创建一个无缓冲的结构体类型通道(省内存)// var sch = make(chan struct{}, 0)// //往通道发送消息// sch <- struct{}{}// //通道接收消息(发送和接收必须同时存在// <-sch// fmt.Println("sch:", sch)//gorm框架的使用:// 安装依赖:// go get http://gorm.io/gorm// go get http://gorm.io/driver/mysqlconfig := models.DatabaseConfig{Host: "localhost",Port: "3306",Username: "root",Password: "tiger",Database: "sakila",}// 构建DSNdsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",config.Username,config.Password,config.Host,config.Port,config.Database,)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info), // 显示SQL日志})if err != nil {log.Fatal("连接Mysql失败", err)}// 设置连接池sqlDB, err := db.DB()if err != nil {log.Fatal("Failed to get database instance:", err)}// 设置最大空闲连接数sqlDB.SetMaxIdleConns(10)// 设置最大打开连接数sqlDB.SetMaxOpenConns(100)// 设置连接的最大生存时间sqlDB.SetConnMaxLifetime(time.Hour)fmt.Println("Database connected successfully!")//连接Mysql,并进行crud操作models.MysqlTest(db)//测试外键操作,一对一models.MysqlTestForeignKey(db)//测试一对一多操作models.MysqlTestOneToMany(db)//测试多对多操作models.MysqlTestManyToMany(db)//测试多态models.MysqlTestPolymorphic(db)//测试事务models.MysqlTestTransaction(db)//测试自定义入库、出库数据类型models.MysqlTestCustomType(db)//测试分页// 构造一个假的 http.Request,带上分页参数reqUrl := &url.URL{RawQuery: "page=2&page_size=5",}r := &http.Request{URL: reqUrl,}models.MysqlTestPagination(db, r)//测试请求models.GinTest()}func testChh(chh chan int) {i := cap(chh) //for index := 0; index < i; index++ {chh <- index}close(chh) //一定要关闭通道}func func1() (a int, b string) { //入参为空,但是返回值为(int, string)类型,同时确定了返回值为 a,ba = 1b = "2"return a, b //也可以直接写成return,会默认返回return a,b}// 方法名大写开头:公开的(可以被不同的package访问)func GetOldName2(p *models.Person) string { //p是方法入参,接收者与入参是两个不同的概念;return p.Name}// 方法名大写开头:公开的(可以被不同的package访问)func GetOldName3(p models.Person) string { //p是方法入参,接收者与入参是两个不同的概念;return p.Name}func t1(arr *[5]int) { //arr是一个数组指针=数组首地址,arr是一个指向大数组的指针,arr就是数组的首地址,*arr 是数组内容,所以访问数组某个元素要用(*arr)[0](*arr)[1] = 1 //访问数组 *arr 的第1个元素 ;也可以直接用arr[1]=1,把地址arr[1]指向的int型内容改为1}func t2(arr [5]*int) { //arr是一个指针数组,每个元素都是一个指针*(arr[1]) = 1 //arr是一个指针数组,其每一个数组元素都是指针=地址,arr[1]内容是一个指针=地址,所以不能用arr[1]赋值int数据,要用*(arr[1])给arr[1]指针地址赋值int数据}// 定义类似于java中的接口:GetBalanceMappertype GetBalanceMapper interface {getBalance() int //获取信用卡已消费金额、借记卡余额 ;接口方法大小写都一样,都没有访问限制}// 定义类似于java中的接口:CardMappertype CardMapper interface {buy(amount int) bool //传入消费金额 ;接口方法大小写都一样,都没有访问限制;接口入参类型int和返回类型bool一定要写,但参数名可以不写,如buy(int) boolGetBalanceMapper //接口中可以存在另外一个接口,这样实现该CardMapper接口方法的子类也必须实现GetBalanceMapper接口方法}// 定义信用卡结构体type CreditCard struct {Use int //已使用额度Limit int //总额度}// 信用卡结构体实现接口CardMapper中的方法(接收者必须是指针否则无法修改后共享数据),看作CreditCard继承了CardMapper和GetBalanceMapperfunc (card *CreditCard) buy(amount int) bool { //本次消费金额//已使用额度+本次消费金额<最大额度if card.Use+amount < card.Limit {card.Use = card.Use + amountreturn true}fmt.Println("刷信用卡失败,额度超额!")return false}// 信用卡结构体实现接口GetBalanceMapper中的方法(接收者必须是指针否则无法修改后共享数据),看作CreditCard继承了CardMapper和GetBalanceMapperfunc (card *CreditCard) getBalance() int { //获取信用卡已消费金额return card.Use}// 定义借记卡结构体type DebitCard struct {Balance int //余额}// 借记卡结构体实现接口CardMapper中的方法(buy方法同名,但是接收者不同) (接收者必须是指针否则无法修改后共享数据),看作CreditCard继承了CardMapper和GetBalanceMapperfunc (card *DebitCard) buy(amount int) bool { //本次消费金额//借记卡余额>本次消费金额if card.Balance > amount {card.Balance = card.Balance - amountreturn true}fmt.Println("刷借记卡失败,余额不足!")return false}// 借记卡结构体实现接口GetBalanceMapper中的方法(接收者必须是指针否则无法修改后共享数据),看作CreditCard继承了CardMapper和GetBalanceMapperfunc (card *DebitCard) getBalance() int { //获取借记卡return card.Balance}// 定义普通的方法,传入CardMapper接口的实现类/实现结构体,可以传入CreditCard、DebitCard等结构体func buyMethod(CardMapper CardMapper, amount int) {if CardMapper.buy(amount) {fmt.Println("刷卡成功...")} else {fmt.Println("刷卡失败...")}}// 定义一个空接口type BlankMapper interface{}// 该chan只能用来发送数据func onlySend(chh1 chan<- int) { //c chan <- int 箭头要么在chan int中间,要么在chan int 前面chh1 <- 123chh1 <- 111}// 该chan只能用来接收数据func onlyReceive(chh1 <-chan int) { //c <-chan intfmt.Println("接收到chan数据:", <-chh1) //外部发送了2次数据(111、456)到该chan,但是这里只接收了一次,所以会打印 111// for v := range chh1 {// fmt.Println("接收到chan数据:", v)// }}./models/test.gopackage models// 结构体:大写开头是公开的(可以被不同的package访问),小写开头是非公开的(只能被同一个package包下访问),type Person struct { //单引号是字段标记,可以控制序列化、数据库映射、数据验证Name string `json:"name" db:"name" validate:"required" gorm:"column:name" ` //Person是公开的结构体,且Name字段也是公开的 (类似java类中的public类型);该字段序列化时对应字段name,在数据库中对应name,必填Name1 string `json:"name1,omitempty" db:"name1" gorm:"column:name" ` //该字段序列化时对应name1(空值时忽略json序列化),在数据库中对应name1,age int `json:"-" db:"age"` //age字段是非公开的(类型java类中的private类型);私有字段在JSON中忽略bir, bir1 stringFf func() string `json:"-"` //定义函数Ff且函数大写开头,可以被不同的package访问;函数Ff可以在定义结构体对象时,使用已存在的函数赋值,或赋值匿名函数ff func() string `json:"-"` //定义函数ff且函数小写开头,只能被同package访问;函数字段通常在序列化时忽略m map[string]int `json:"myMap,omitempty" db:"-"` //该字段序列化时对应myMap字段(空值时忽略json序列化),在数据库中忽略ch chan string `json:"-" db:"-"` //chan类型i;channel不能序列化a []int `json:"a" db:"-"` //数组类型;该字段序列化时对应字段ab []interface{} `json:"b,omitempty" db:"-"` //切片类型c *int `json:"c,omitempty"` //指针类型pp Person2 `json:"-" db:"-"` //其他结构体类型;调用时用(对象.pp.Name)获取Person2中的Nameint //定义匿名int类型字段}type Person2 struct {Name string}//结构体方法(go中没有重载概念,一个类中不能有同名方法,即使不同入参也不能有同名方法,除非方法的接收者不同就可以同名)//方法名大写开头:公开的(可以被不同的package访问)func (p *Person) GetOldName() string { //p是指针接收者,同时方法入参为空,接收者与入参是两个不同的概念;return p.Name}//结构体方法//方法名大写开头:公开的(可以被不同的package访问)func (p *Person) GetNewName() string { //p是指针接收者,同时方法入参为空,接收者与入参是两个不同的概念;p.Name = "张三" //调用时要用【调用对象.getName()】,指针接收者会成功修改调用对象的Name属性,可以把此处的指针p看作 调用对象 的一级指针return p.Name}//结构体方法//方法名大写开头:公开的(可以被不同的package访问)func (p Person) GetNewName2() string { //p是值接收者,同时方法入参为空,接收者与入参是两个不同的概念p.Name = "张三" //调用时要用【调用对象.getName()】,此处不会修改调用对象的Name属性,值接收者p只是调用对象的临时副本return p.Name}//结构体方法//方法名大写开头:公开的(可以被不同的package访问)func (p Person) GetNewName3() string { //p是值接收者,同时方法入参为空,接收者与入参是两个不同的概念p.Name = "张四" //调用时要用【调用对象.getName()】,此处不会修改调用对象的Name属性,值接收者p只是调用对象的临时副本return p.Name}//结构体方法//方法名小写开头:非公开的(只能被同package下访问)func (p *Person) getName3() string {return p.Name}//普通方法func ff() string { return "a" }//匿名结构体(可以在方法中或者方法外定义匿名结构体)var p1p1 = struct {Name stringage intint //匿名属性}{ //这里初始化Name: "王五",age: 12,int: 13, //赋值匿名属性}//声明一个空的匿名结构体var p2p2 = struct{}{}//结构体converTest1、converTest2可以互转(注意大写)type ConverTest1 struct {Name stringAge int}type ConverTest2 struct {Name stringAge int}./models/constTest.go:package modelstype STRR string// 不同于java,go用常量定义枚举const (male STRR = "男"female STRR = "女")func isMale(m STRR) bool {return male == m}func (m *STRR) isMale2() bool { //如何把STRR换成string会报错,go中不允许为基本类型定义接收者方法,但可以为自定义的STRR类型定义接收者方法return *m == male}func (m STRR) isMale3() bool { //如何把STRR换成string会报错,go中不允许为基本类型定义接收者方法,但可以为自定义的STRR类型定义接收者方法return m == male}mysql.go:package modelsimport ("database/sql""database/sql/driver""fmt""net/http""strconv""strings""time""gorm.io/gorm""gorm.io/gorm/clause")type Addres struct {address_id uint `gorm:"primaryKey;autoIncrement"`}type User struct {ID uint //等价mysql的int类型,gorm框架默认ID字段为主键自增//ID uint `gorm:"primaryKey;autoIncrement"` //与上一行写法等价,这里gorm可写可不写Name string //等价mysql的【varchar(255) NOT NULL】类型,gorm不会传入null给数据库字段默认传入''Name11 string `gorm:"size:255;not null"` //写法与上一行等价,等价mysql的【varchar(255) NOT NULL】类型,Email *string //等价mysql的【varchar(255) NULL】类型,指针类型方式传入nullAge uint8 //等价mysql的tinyint类型,8位无符号整数age uint8 //gorm会忽略小写字段不映射到数据库Birthday *time.Time //等价mysql的【datetime NULL】Birthday1 time.Time //等价mysql的【datetime NOT NULL】,gorm不会传入null给数据库字Birthday2 time.Time `gorm:"type:timestamp;uniqueIndex"` //等价mysql的【timestamp NOT NULL UNIQUE】,gorm不会传入null给数据库字Desc sql.NullString //等价mysql的【varchar(255) NULL】类型,sql包方式传入nullUser1 //会把User1结构体里面的字段也纳入管理gorm.Model //嵌入gorm.Model,包含四个自动维护的字段:ID、CreatedAt、UpdatedAt、DeletedAt,CreatedAt是创建时间、UpdatedAt是更新时间、DeletedAt是删除时间(实现软删除功能,删除时不真正删除记录,而是设置删除时间)}type User1 struct {// 字符串类型Name1 string // VARCHAR(255) NOT NULL,gorm不会传入null给数据库字默认传入''Name2 *string // VARCHAR(255) NULLName3 sql.NullString // VARCHAR(255) NULL// 整数类型Age1 int // INT NOT NULL,gorm不会传入null给数据库字默认传入0Age2 *int // INT NULLAge3 sql.NullInt64 // BIGINT NULLAge4 sql.NullInt32 // INT NULL// 浮点数类型Score1 float64 // DOUBLE NOT NULL,gorm不会传入null给数据库字默认传入0Score2 *float64 // DOUBLE NULLScore3 sql.NullFloat64 // DOUBLE NULL// 布尔类型Active1 bool // TINYINT(1) NOT NULL,gorm不会传入null给数据库字默认传入0Active2 *bool // TINYINT(1) NULLActive3 sql.NullBool // TINYINT(1) NULL// 时间类型CreatedAt1 time.Time // DATETIME NOT NULL,gorm不会传入null给数据库字CreatedAt2 *time.Time // DATETIME NULLCreatedAt3 sql.NullTime // DATETIME NULL (Go 1.13+)}// 一对一,一个UserB对应一个Citytype UserB struct {ID uint //等价mysql的int类型,gorm框架默认ID字段为主键自增//ID uint `gorm:"primaryKey;autoIncrement"` //与上一行写法等价,这里gorm可写可不写Name string //等价mysql的【varchar(255) NOT NULL】类型,gorm不会传入null给数据库字段默认传入''CityID uint //等价mysql的int类型,gorm框架默认“表名+ID”字段为外键字段,即CityID是UserB表的外键字段,指向City表的主键IDCity City `gorm:"constraint:OnDelete:CASCADE"` //OnDelete:CASCADE表示删除主表数据时会删除关联表数据}// 一对一,一个UserC对应一个Citytype UserC struct {ID uint //等价mysql的int类型,gorm框架默认ID字段为主键自增//ID uint `gorm:"primaryKey;autoIncrement"` //与上一行写法等价,这里gorm可写可不写Name string //等价mysql的【varchar(255) NOT NULL】类型,gorm不会传入null给数据库字段默认传入''CityIDD uint //等价mysql的int类型,gorm框架默认“表名+ID”字段为外键字段,也可以foreignKey自定义外键字段名,即CityIDD是UserC表的外键字段,指向City表的主键IDCity City `gorm:"foreignKey:CityIDD;references:ID2"` //foreignKey指定本表外键字段名,references指定该外键指向他表的哪个字段,此处指向City表的主键ID2}type City struct {ID uint `gorm:"primaryKey;autoIncrement"` // CityID是主键自增ID2 uint `gorm:"uniqueIndex"` // CityID2是唯一索引CityName string // CityName是城市名称UserDID uint // UserDID是外键字段,指向UserD表的主键ID}// 一对多,一个City2对应多个User2type User2 struct {ID uint //等价mysql的int类型,gorm框架默认ID字段为主键自增//ID uint `gorm:"primaryKey;autoIncrement"` //与上一行写法等价,这里gorm可写可不写Name string //等价mysql的【varchar(255) NOT NULL】类型,gorm不会传入null给数据库字段默认传入''City2ID uint // City2ID是外键字段,指向City2表的主键ID}type City2 struct {ID uint `gorm:"primaryKey;autoIncrement"` // CityID是主键自增CityName string // CityName是城市名称User2s []User2}// 多对多,一个User3对应多个City3,一个City3对应多个User3type User3 struct {gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段Name stringCity3s []City3 `gorm:"many2many:user3_city3s;"` // many2many指定创建一个多对多关系的中间表名为user3_city3s,在创建表User3s时会自动创建该中间表}type City3 struct {gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段CityName stringUser3s []User3 `gorm:"many2many:user3_city3s;"`}// 数据库配置type DatabaseConfig struct {Host stringPort stringUsername stringPassword stringDatabase string}func MysqlTest(db *gorm.DB) {//建表,gorm会建一张在User的复数形式users表明的表//db.AutoMigrate(&User{})//1、插入数据birthday := time.Date(1990, 5, 15, 0, 0, 0, 0, time.Local)birthday = time.Now()user1 := User1{CreatedAt1: birthday}email := "12345678@qq.com"user := User{Name: "afei",Name11: "tiger",Email: &email,Age: 30,Birthday: &birthday, //Birthday是指针类型Birthday1: birthday, //Birthday1不是指针类型Birthday2: birthday,User1: user1,}result := db.Create(&user) //user里会返回主键IDfmt.Printf("插入结果%v,插入行数%v", result.Error, result.RowsAffected)//插入多条数据users := []User{{Name: "小明",Name11: "tiger1",Birthday: &birthday, //Birthday是指针类型Birthday1: birthday, //Birthday1不是指针类型Birthday2: time.Now(),User1: user1,},{Name: "小鹏",Age: 30,Name11: "tiger2",Birthday: &birthday, //Birthday是指针类型Birthday1: birthday, //Birthday1不是指针类型Birthday2: time.Now().Add(time.Second * 1),User1: user1,},}result = db.Create(&users) //user里会返回主键IDfmt.Printf("插入结果%v,插入行数%v", result.Error, result.RowsAffected)//插入指定字段user.Birthday2 = time.Now()db.Select("Name", "Age", "Birthday", "Birthday1", "Birthday2", "Name11").Create(&user)//忽略插入字段user.ID = 0user.Birthday2 = time.Now().Add(time.Second * 1)db.Omit("Age").Create(&user)//2、批量查询数据var userss []Userdb.Find(&userss)for i, v := range userss {fmt.Println(fmt.Sprintf("第%d条数据:%v", i, v))}//按主键升序查询第一条var u Userresult = db.First(&u) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1fmt.Println("主键排序查询第一条:%v,数量:%v,查询错误:%v", u, result.RowsAffected, result.Error)//乱序查询第一条u.ID = 0 //置为零值 否则会把u.ID作为查询条件db.Take(&u) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL LIMIT 1fmt.Println("乱序查询第一条:%v", u)//按主键升序查询最后一条u.ID = 0db.Last(&u) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` DESC LIMIT 1fmt.Println("主键升序查询最后一条:%v", u)//主键=12u.ID = 0 //上面查询中已查询u的ID值,如果此处不置零,则会拼接两个users.ID=12 and users.ID=xxdb.First(&u, 12) //SELECT * FROM `users` WHERE `users`.`id` = 12 AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1fmt.Println("主键=12:%v", u)//查询所有数据var rms []Userdb.Find(&rms) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULLfmt.Println("查询所有数据:%v", rms)db.Find(&rms, "Name = ?", "afei") //SELECT * FROM `users` WHERE (Name = 'afei') AND `users`.`deleted_at` IS NULLfmt.Println("查询Name=afei的数据:%v", rms)db.Find(&rms, "Name = ? AND Age = ?", "afei", 30) //SELECT * FROM `users` WHERE (Name = 'afei' AND Age = 30) AND `users`.`deleted_at` IS NULLfmt.Println("查询Name=afei AND Age=30的数据:%v", rms)db.Find(&rms, User{Name: "afei", Age: 30}) //SELECT * FROM `users` WHERE (Name = 'afei' AND Age = 30) AND `users`.`deleted_at` IS NULLfmt.Println("查询Name=afei AND Age=30的数据:%v", rms)db.Find(&rms, map[string]interface{}{"Name": "小明", "Age": 31}) //SELECT * FROM `users` WHERE (Name = '小明' AND Age = 31) AND `users`.`deleted_at` IS NULL})fmt.Println("查询Name=小明 AND Age=31的数据:%v", rms)//主键in查询var us []Userdb.Find(&us, []int{1, 2, 3}) //SELECT * FROM `users` WHERE `users`.`id` IN (1,2,3) AND `users`.`deleted_at` IS NULLdfmt.Println("查询多条数据:%v", us)db.Where([]int{1, 2, 3}).Find(&us) //SELECT * FROM `users` WHERE `users`.`id` IN (1,2,3) AND `users`.`deleted_at` IS NULLfmt.Println("查询多条数据:%v", us)//where查询db.Where("Name = ?", "afei").Find(&us) //SELECT * FROM `users` WHERE (Name = 'afei') AND `users`.`deleted_at` IS NULLfmt.Println("where查询结果集:%v", us)db.Where("Name = ? AND Age = ?", "afei", 30).First(&us) //SELECT * FROM `users` WHERE (Name = 'afei' AND Age = 30) AND `users`.`deleted_at` IS NULL order by `users`.`id` limit 1fmt.Println("where查询结果:%v", us)db.Where("Name in ?", []string{"小明", "小强"}).Find(&us) //SELECT * FROM `users` WHERE Name in ('小明','小强') AND `users`.`deleted_at` IS NULLfmt.Println("where in查询结果:%v", us)db.Where("Name like ?", "%小%").Find(&us) // SELECT * FROM `users` WHERE Name like '%小%' AND `users`.`deleted_at` IS NULLfmt.Println("where like查询结果:%v", us)db.Where("Name like ? and age > ?", "%小%", 1).Find(&us) //SELECT * FROM `users` WHERE (Name like '%小%' and age > 1) AND `users`.`deleted_at` IS NULLfmt.Println("where and查询结果:%v", us)//AddDate三个参数分别是添加年、月、日,负数表示减去db.Where("birthday > ? and birthday < ?", time.Now().AddDate(-1, 0, 0), "2026-10-01").Find(&us) //SELECT * FROM `users` WHERE (birthday > '2024-07-08 12:19:56.1' and birthday < '2026-10-01')fmt.Println("where time查询结果:%v", us)db.Where("birthday between ? and ?", time.Now().AddDate(-2, 0, 0), "2026-10-01").Find(&us) //SELECT * FROM `users` WHERE (birthday between '2025-07-08 12:19:56.105' and '2026-10-01') AND `users`.`deleted_at` IS NULLfmt.Println("where between查询结果:%v", us)//Where+结构体查询db.Where(&User{Name: "afei", Age: 31}).Find(&us) //SELECT * FROM `users` WHERE (Name = 'afei' AND Age = 30) AND `users`.`deleted_at` IS NULLfmt.Println("Where+struct查询结果:%v", us)db.Where(&User{Name: "afei", Age: 0}).Find(&us) //Age是零值不会加入查询:SELECT * FROM `users` WHERE Name = 'afei' AND `users`.`deleted_at` IS NULLfmt.Println("Where+struct查询结果:%v", us)db.Where(&User{Name: "afei"}, "Age").Find(&us) //Age是零值会加入查询:SELECT * FROM `users` WHERE `users`.`age` = 0 AND `users`.`deleted_at` IS NULL//Where+map查询,Age要写成31,如果写成"31"则sql为age="31"db.Where(map[string]interface{}{"Name": "小明", "Age": 31}).Find(&us) //SELECT * FROM `users` WHERE (Name = '小明' AND Age = 31) AND `users`.`deleted_at` IS NULLfmt.Println("Where+map查询结果:%v", us)//db.Not可以是 != 或者not in()//普通的!=db.Not("Name = ?", "小明").Find(&us) // SELECT * FROM `users` WHERE NOT Name = '小明' AND `users`.`deleted_at` IS NULLfmt.Println("普通!=数据:", us)//map型!=db.Not(map[string]interface{}{"Name": "afei", "Age": 20}).Find(&us) //SELECT * FROM `users` WHERE (`users`.`Age` <> 20 AND `users`.`Name` <> 'afei') AND `users`.`deleted_at` IS NULLfmt.Println("map型!=数据:", us)//struct型!=db.Not(User{Name: "afei", Age: 20}).Find(&us) //SELECT * FROM `users` WHERE (`users`.`name` <> 'afei' AND `users`.`age` <> 20) AND `users`.`deleted_at` IS NULLfmt.Println("struct型!=数据:", us)//普通的not in()db.Not("Name in ?", []string{"小明", "小胖"}).Find(&us) // SELECT * FROM `users` WHERE not Name in ('小明','小胖') AND `users`.`deleted_at` IS NULLfmt.Println("普通not in数据:", us)//普通的not in()db.Not([]int{1, 2, 3}).Find(&us) // SELECT * FROM `users` WHERE not id in (1,2,3) AND `users`.`deleted_at` IS NULLfmt.Println("普通not in数据:", us)//map型not in()db.Not(map[string]interface{}{"Name": []string{"小明", "小胖"}}).Find(&us) //SELECT * FROM `users` WHERE `users`.`Name` NOT IN ('小明','小胖') AND `users`.`deleted_at` IS NULLfmt.Println("map型not in数据:", us)//or//普通ordb.Where("Name", "小明").Or("Age", "30").Find(&us) //SELECT * FROM `users` WHERE (`Name` = '小明' OR `Age` = '30') AND `users`.`deleted_at` IS NULLfmt.Println("普通or查询结果:%v", us)//map型or查询db.Where("Name", "小明").Or(map[string]interface{}{"Name": "afei", "Age": 30}).Find(&us) //SELECT * FROM `users` WHERE (`Name` = '小明' OR (`users`.`Age` = 30 AND `users`.`Name` = 'afei')) AND `users`.`deleted_at` IS NULLfmt.Println("map型or查询结果:%v", us)//struct型or查询db.Where("Name", "小明").Or(User{Name: "afei", Age: 30}).Find(&us) //SELECT * FROM `users` WHERE (`Name` = '小明' OR (`users`.`name` = 'afei' AND `users`.`age` = 30)) AND `users`.`deleted_at` IS NULLfmt.Println("struct型or查询结果:%v", us)//select特定字段db.Select("Name", "Age").Find(&us) //SELECT `name`,`age` FROM `users` WHERE `users`.`deleted_at` IS NULLfmt.Println("select特定字段查询结果:%v", us)db.Select([]string{"Name", "Age"}).Find(&us) // SELECT `name`,`age` FROM `users` WHERE `users`.`deleted_at` IS NULLfmt.Println("select特定字段查询结果:%v", us)db.Select("COALESCE(Name, '未知') AS Name, COALESCE(Email, '未知邮箱') AS Email").Find(&us) //SELECT COALESCE(Name, '未知') AS Name, COALESCE(Email, '未知邮箱') AS Email FROM `users` WHERE `users`.`deleted_at` IS NULLfmt.Println("select特定字段查询结果:%v", us)db.Select("COALESCE(Email, ?) AS Email", "未知邮箱").Find(&us) //SELECT CCOALESCE(Email, '未知邮箱') AS Email FROM `users` WHERE `users`.`deleted_at` IS NULLfmt.Println("select特定字段查询结果:%v", us)//orderdb.Order("name").Find(&us) // SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY namefmt.Println("name asc排序结果:", us)db.Order("name desc").Find(&us) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY name descfmt.Println("name desc排序结果:", us)//limit offsetdb.Order("Age desc").Offset(3).Limit(2).Find(&us) //从第3条(包括第3条)开始取2条数据(数据第0条数据开始),SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY Age desc limit 2 OFFSET 3等价于 SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY Age desc limit 3,2fmt.Println("limit offset查询结果:", us)//3、更新数据//save函数当有主键会更新所有字段即使字段是零值,没有主键则插入u.Age = 33db.Save(&u) //UPDATE `users` SET `name`='afei',`name11`='tiger',`email`=NULL,`age`=33,`birthday`='2025-07-04 12:09:12.829',`birthday1`='2025-07-04 12:09:12.829',`birthday2`='2025-07-04 12:09:13',`desc`=NULL,`name1`='',`name2`=NULL,`name3`=NULL,`age1`=0,`age2`=NULL,`age3`=NULL,`age4`=NULL,`score1`=0,`score2`=NULL,`score3`=NULL,`active1`=false,`active2`=NULL,`active3`=NULL,`created_at1`='2025-07-04 12:09:12.829',`created_at2`=NULL,`created_at3`=NULL,`created_at`='2025-07-04 12:09:16.304',`updated_at`='2025-07-09 12:27:24.15',`deleted_at`=NULL WHERE `users`.`deleted_at` IS NULL AND `id` = 12u.ID = 0u.Birthday2 = time.Now().AddDate(0, 0, 1)db.Save(&u) //INSERT INTO `users` (`name`,`name11`,`email`,`age`,`birthday`,`birthday1`,`birthday2`,`desc`,`name1`,`name2`,`name3`,`age1`,`age2`,`age3`,`age4`,`score1`,`score2`,`score3`,`active1`,`active2`,`active3`,`created_at1`,`created_at2`,`created_at3`,`created_at`,`updated_at`,`deleted_at`) VALUES ('afei','tiger',NULL,33,'2025-07-04 12:09:12.829','2025-07-04 12:09:12.829','2025-07-04 12:09:13',NULL,'',NULL,NULL,0,NULL,NULL,NULL,0,NULL,NULL,false,NULL,NULL,'2025-07-04 12:09:12.829',NULL,NULL,'2025-07-04 12:09:16.304','2025-07-09 12:27:24.15',NULL)//更新单个字段:把主键和Where条件一起拼接,单字段用Update(),多字段用Updates(),如果update语句不想带上主键就用db.Model(&Use{}).Where()db.Model(&u).Where("Name=?", "小明").Update("Name", "小明1") //UPDATE `users` SET `name`='小明1',`updated_at`='2025-07-09 14:10:23.2' WHERE Name='小明' AND `users`.`deleted_at` IS NULL AND `id` = 211//更新多个字段:结构体、Map形式db.Model(&u).Where("Name=?", "小明").Updates(User{Name: "小明2", Age: 40}) //UPDATE `users` SET `name`='小明2',`age`=40,`updated_at`='2025-07-09 14:10:23.202' WHERE Name='小明' AND `users`.`deleted_at` IS NULL AND `id` = 21db.Model(&u).Where("Name=?", "小明").Updates(map[string]interface{}{"Name": "小明3", "Age": 50}) //UPDATE `users` SET `age`=50,`name`='小明3',`updated_at`='2025-07-09 14:10:23.203' WHERE Name='小明' AND `users`.`deleted_at` IS NULL AND `id` = 211//当Update语句没有where条件是gorm不会执行,可以添加where 1=1 或 用db.Exec()执行原生sql 或 设置AllowGrobalUpdate允许更新db.Model(&User{}).Update("Age", "24") //没有where语句gorm不会执行更新( 但此处自动拼接了WHERE `users`.`deleted_at` IS NULL 所以还是会执行)db.Model(&User{}).Where("1=1").Update("Age", "24") //UPDATE `users` SET `age`='24',`updated_at`='2025-07-09 14:28:20.097' WHERE 1=1 AND `users`.`deleted_at` IS NULLdb.Exec("update users set age=?", 18) //执行原生sql: update user set age=18db.Model(&User{}).Session(&gorm.Session{AllowGlobalUpdate: true}).Update("Age", 33) //UPDATE `users` SET `age`=33,`updated_at`='2025-07-09 14:29:11.501' WHERE `users`.`deleted_at` IS NULL//使用表达式更新db.Model(&u).Update("age", gorm.Expr("age+?", 1))db.Model(&u).Updates(map[string]interface{}{"age": gorm.Expr("age+?", 1)})db.Model(&u).UpdateColumn("age", gorm.Expr("age+?", 1))//连表update :UPDATE users u1 SET `age1`=(SELECT city_id FROM address u2 WHERE u1.ID=u2.address_id) WHERE Name='小明'db.Table("users u1").Where("Name=?", "小明").Update("age1", db.Table("address u2").Select("city_id").Where("u1.ID=u2.address_id"))//4、删除数据//根据主键删除db.Delete(&User{}, 1)db.Delete(&User{}, []int{2, 3})//where条件删除:设置deleted_at字段值进行逻辑删除db.Delete(&User{}, "name like ?", "%小明%") //UPDATE `users` SET `deleted_at`='2025-07-09 16:12:32.495' WHERE name like '%小明%' AND `users`.`deleted_at` IS NULLdb.Where("name like ?", "%小明%").Delete(&User{}) //UPDATE `users` SET `deleted_at`='2025-07-09 16:12:32.495' WHERE name like '%小明%' AND `users`.`deleted_at` IS NULLdb.Where("name like ?", "%afei%").Delete(&u) //会把u的ID和name拼上:UPDATE `users` SET `deleted_at`='2025-07-09 16:12:32.502' WHERE name like '%afei%' AND `users`.`id` = 259 AND `users`.`deleted_at` IS NULL//禁用deleted_at字段//where条件删除:永久删除,不设置deleted_at字段值db.Unscoped().Delete(&u) // DELETE FROM `users` WHERE `users`.`id` = 271//查询被逻辑删除的数据db.Unscoped().Where("ID=1").Find(&us) //SELECT * FROM `users` WHERE ID=1//指定外键//建表,gorm会建一张在User的复数形式users表明的表db.AutoMigrate(&User{})}// 测试一对一外键func MysqlTestForeignKey(db *gorm.DB) {db.AutoMigrate(&City{}) //建表,gorm会建一张在City的复数形式cities名字的表//user_bs表的外键字段是citys表的主键ID字段db.AutoMigrate(&UserB{}) //建表,gorm会建一张在UserB的复数形式user_bs名字的表//user_cs表的外键字段是citys表的主键ID2字段db.AutoMigrate(&UserC{}) //建表,gorm会建一张在UserC的复数形式user_cs名字的表//一对一关系,插入数据city := City{CityName: "北京", ID2: uint(time.Now().Hour()) + uint(time.Now().Minute()) + uint(time.Now().Second())} //ID2默认插入0db.Create(&city)fmt.Println("插入City数据:", city)userb := UserB{Name: "小明",CityID: city.ID,}db.Create(&userb)userc := UserC{Name: "小强", CityIDD: city.ID2} //CityIDD是UserC表的外键字段,指向City表的主键ID2}db.Create(&userc)//查询数据var userbsFirst UserBdb.First(&userbsFirst) //这样会查不出关联表city的数据 {1 小明 1 {0 0 }}fmt.Println("查询UserB数据:", userbsFirst)db.Preload("City").First(&userbsFirst) //预加载City字段,这样可以查出关联表city的数据 {1 小明 1 {1 0 北京}}db.Preload(clause.Associations).First(&userbsFirst) //预加载所有关联表的的字段,上一行中是指定预加载某一个关联表字段fmt.Println("查询UserB数据:", userbsFirst)//查询关联表数据var city2First Citydb.Model(&userbsFirst).Association("City").Find(&city2First) //也可以通过Association方法从userbsFirst实体中的外键值查询关联City字段的数据fmt.Println("查询City数据:", city2First)//更新、插入关联表数据db.Session(&gorm.Session{FullSaveAssociations: true}).Model(&userbsFirst).Association("City").Replace([]*City{{ID: city2First.ID, CityName: "上海更新数据", UserDID: city2First.UserDID}, {CityName: "上海插入数据"}}) //第一条city数据做更新;第二条会先把City表中字段UserDID=userbsFirst.ID的数据先删掉,再重新插入新的UserDID、CityName字段数据//如果city表没有某个ID则插入db.Session(&gorm.Session{FullSaveAssociations: true}).Model(&userbsFirst).Association("City").Append(&City{ID: city2First.ID, CityName: "北京插入数据", UserDID: userbsFirst.ID}) //如果city表没有ID=100的数据则插入//删除(置空)外键字段:UPDATE `user_bs` SET `city_id`=NULL WHERE `id` = 1//db.Session(&gorm.Session{FullSaveAssociations: true}).Model(&userbsFirst).Association("City").Clear()//删除主表、关联表的数据db.Select("City").Delete(&userbsFirst) //删除主表UserB和关联表City的数据,会先删除City表中字段UserDID=userbsFirst.ID的数据,再删除UserB表中ID=userbsFirst.ID的数据}// 测试一对一多操作func MysqlTestOneToMany(db *gorm.DB) {//一对多关系,插入数据db.AutoMigrate(&User2{}) //建表,gorm会建一张复数形式的表db.AutoMigrate(&City2{}) //建表,gorm会建一张复数形式的表city2 := City2{CityName: "上海"}db.Create(&city2) //先插入City2表user2 := User2{Name: "小强", City2ID: city2.ID} //City2ID是User2表的外键字段,指向City2表的主键IDuser22 := User2{Name: "小杠", City2ID: city2.ID}db.Create(&user2) //再插入User2表db.Create(&user22)//查询数据var city2First City2// SELECT * FROM `city2` WHERE `city2`.`id` = 1 ORDER BY `city2`.`id` LIMIT 1db.First(&city2First, city2.ID) //这样会查不出关联表city的数据,fmt.Println("查询city2数据:", city2First)//先执行SELECT * FROM `user2` WHERE `user2`.`city2_id` = 1,再SELECT * FROM `city2` WHERE `city2`.`id` = 1 ORDER BY `city2`.`id` LIMIT 1db.Preload("User2s").First(&city2First) //预加载User2s字段,这样可以查出关联表user2的数据fmt.Println("查询city2数据:", city2First)}// 测试多对多关系func MysqlTestManyToMany(db *gorm.DB) {db.AutoMigrate(&User3{}) //建表,gorm会建一张复数形式的表db.AutoMigrate(&City3{}) //建表,gorm会建一张复数形式的表//多对多关系,插入数据user3 := User3{Name: "小明"}db.Create(&user3) //插入User3表,不会触发创建中间表city3 := City3{CityName: "北京"}db.Create(&city3) //插入City3表,不会触发创建中间表//插入中间表数据user33 := User3{Name: "小样", City3s: []City3{city3}}db.Create(&user33) //插入User3表,同时插入中间表user3_city3s//插入中间表数据itycity33 := City3{CityName: "广州", User3s: []User3{user33}}db.Create(&city33) //插入City3表,同时插入中间表user3_city3s//查询数据var user3First User3err := db.Preload("City3s").First(&user3First, 4).Error //预加载City3s字段,这样可以查出关联表city3的数据if err != nil {// panic 是一个内置函数,用于主动触发运行时错误(类似于抛出异常),程序会立即中断当前函数的执行,沿调用栈向上逐层终止panic(fmt.Sprintf("查询User3数据失败:%v", err))} else {fmt.Println("查询User3数据成功:", user3First)}}// 多态:dogtype Dog struct {gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段Name string // 狗的名字Animal Animal `gorm:"polymorphic:Owner;polymorphicValue:dog"` //多态关联,Owner是多态字段名,polymorphicValue是多态值(如果不指定polymorphicValue则默认使用结构体表名dogs)}type Cat struct {gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段Name string // 猫的名字Animal Animal `gorm:"polymorphic:Owner;polymorphicValue:cat"` //多态关联,Owner会在Animal表中组成默认字段名OwnerType和OwnerTypeID(也可以用polymorphicType和polymorphicId自定义默认字段名),polymorphicValue是多态值(如果不指定polymorphicValue则默认使用结构体表名cats)}type Animal struct {gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段Desc string // 描述OwnerType string // 多态类型,默认字段名,"Owner"由结构体中polymorphic指定,存储多态值=dog或者cat(可以在结构体中用polymorphicType和polymorphicId自定义默认字段名)OwnerID uint // 多态ID,默认字段名,"Owner"由结构体中polymorphic指定,存储多态ID=Dog或者Cat的ID}// 测试多态关联func MysqlTestPolymorphic(db *gorm.DB) {db.AutoMigrate(&Dog{}, &Cat{}, &Animal{}) //建表,gorm会建三张表:dogs、cats、animalsdog := Dog{Name: "小狗", Animal: Animal{Desc: "可爱的小狗"}} //插入Animal数据时,OwnerType=dog,OwnerID=当前dog的IDdb.Create(&dog) //插入Dog表,同时插入Animal表cat := Cat{Name: "小猫", Animal: Animal{Desc: "可爱的小猫"}} //插入Animal数据时,OwnerType=cat,OwnerID=当前cat的IDdb.Create(&cat) //插入Cat表,同时插入Animal表}// 测试事务func MysqlTestTransaction(db *gorm.DB) {//事务嵌套:事务嵌套时,哪个事务tx执行的方法返回errors.New,则该事务就会回滚不会执行sql,不会影响其他事务的sql执行(返回nil就算是事务正常执行)tx := db.Begin() //开启事务tx.Create(&Dog{Name: "事务小狗"}) //插入Dog表//tx.Rollback() //回滚事务,不会执行上面的sqltx.Commit() //提交事务,正常执行上面的sql提交到数据库//设置回滚点tx = db.Begin() //开启事务tx.Create(&Dog{Name: "事务小狗1"}) //插入Dog表tx.SavePoint("sp1") //设置回滚点sp1tx.Create(&Dog{Name: "事务小狗2"}) //插入Dog表tx.SavePoint("sp2") //设置回滚点sp2tx.RollbackTo("sp1") //回滚到回滚点sp1,sp2之后的sql不会执行tx.Commit() //提交事务,正常执行上面的sp1之前的sql提交到数据库}type myStringArr []string //自定义字符串切片类型// 重写myStringArr类型的入库方法:把切片变成'逗号隔开字符串'保存到库中func (m myStringArr) Value() (driver.Value, error) {// 实现 driver.Valuer 接口的 Value 方法return strings.Join(m, ","), nil}// 重写myStringArr类型的出库方法:把库中的'逗号隔开字符串'分割成切片func (m *myStringArr) Scan(value interface{}) error {// 实现 sql.Scanner 接口的 Scan 方法if v, ok := value.([]byte); ok {*m = strings.Split(string(v), ",") //将字节切片转换为字符串切片return nil //正常返回}return fmt.Errorf("failed to scan value: %v", value)}type People struct {gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段Name stringDesc myStringArr //自定义字符串切片类型,mysql中对应longtext}// 测试自定义入库、出库数据类型func MysqlTestCustomType(db *gorm.DB) {db.AutoMigrate(&People{}) //建表,gorm会建一张复数形式的表(若已存在表则自动忽略)p := People{Name: "自定义类型", Desc: myStringArr{"描述1", "描述2"}}db.Create(&p) //插入数据,p会把Desc字段的切片转换成逗号隔开的字符串存储到数据库中fmt.Println("插入自定义类型数据:", p)var pp Peopledb.First(&pp) //查询数据fmt.Println("查询自定义类型数据:", pp)}// 测试分页func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB { //函数名=Paginate,参数是http请求r,返回值是一个函数闭包【func(db *gorm.DB) *gorm.DB,该闭包参数是*gorm.DB,返回值是*gorm.DB】page, _ := strconv.Atoi(r.URL.Query().Get("page")) //获取页码pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) //获取每页条数if page <= 0 {page = 1 //默认第一页}if pageSize >= 10 || pageSize == 0 {pageSize = 10 //默认每页10条}offset := (page - 1) * pageSize //计算偏移量return func(db *gorm.DB) *gorm.DB {return db.Offset(offset).Limit(pageSize) //返回分页查询结果,从第offset条(包括第offset条)开始取pageSize条数据(数据从第0条数据开始)}}func MysqlTestPagination(db *gorm.DB, r *http.Request) {var people []Peopledb.Scopes(Paginate(r)).Find(&people)fmt.Println("分页查询结果:", people) //输出分页查询结果}ginTest.go:package modelsimport ("fmt""net/http""text/template""github.com/gin-gonic/gin")type User4 struct {ID uint //等价mysql的int类型,gorm框架默认ID字段为主键自增//ID uint `gorm:"primaryKey;autoIncrement"` //与上一行写法等价,这里gorm可写可不写//Name string `json:"name" form:"name"` //localhost:8081/users5?name=afei, post+form的形式, 接收URI参数时,form标签指定前端传入的字段名name;返回json时,json标签指定字段名name;//Name string `json:"name" uri:"name"` //localhost:8081/users5/:name, post+uri的形式, 接收URI参数时,uri标签指定前端传入的字段名name;返回json时,json标签指定字段名name//Name string `json:"name" binding:"required"` //name值必传,binding是官方验证器可以是required(必传)、number(传入数字格式)、email(传入邮件格式)...,表示前端传入的值必须满足这些条件,也可以自定义验证器再注册到binding使用Name string `json:"name"` //返回json时,json标签指定字段名name;接收前端json时,不区分name的大小写都可以接收如Name\name\NaMeAge int `json:"age"`}func GinTest() {r := gin.Default()r.Use(MyHandler1(), MyHandler2()) //注册拦截器,类似java的拦截器//Get请求,类似java的controllerr.GET("/hello", func(c *gin.Context) {name := c.Query("name")if name == "" {name = "world"}//返回c.String(200, fmt.Sprintf("hello %s", name))})//任何类型请求r.Any("/any", func(c *gin.Context) {name := c.Query("name")if name == "" {name = "any"}//返回c.String(200, fmt.Sprintf("hello %s", name))})//Post请求r.POST("/hello", func(c *gin.Context) {name := c.PostForm("name")if name == "" {name = "post"}//返回c.String(200, fmt.Sprintf("hello %s", name))})//分组请求group1 := r.Group("/products"){group1.GET("/list", func(c *gin.Context) {c.String(200, "product list")})group1.POST("/add", func(c *gin.Context) {c.String(200, "product added")})}group2 := r.Group("/books"){group2.GET("/list", func(c *gin.Context) {c.String(200, "books list")})group2.POST("/add", func(c *gin.Context) {c.String(200, "books added")})}//设置文件路径r.Static("/static", "./models/statics") //http://localhost:8081/static/1.txt会定向到项目目录下的./models/statics/1.txt//和上面等价r.StaticFS("/staticfs", http.Dir("./models/statics")) //http://localhost:8081/staticfs/1.txt会定向到项目目录下的./models/statics/1.txt//固定访问某个文件r.StaticFile("/static1", "./models/statics/1.txt")//输出Jsonr.GET("/users", func(c *gin.Context) {user := User{Name: "John Doe",Age: 30,}//返回c.JSON(http.StatusOK, user)})r.GET("/users1", func(c *gin.Context) {//返回c.JSON(http.StatusOK, gin.H{ //gin.H本质是一个map[string]any"name": "小明","age": 18,})})//localhost:8081/users2/111r.GET("/users2/:id", func(c *gin.Context) {id := c.Param("id") //获取URL参数//返回c.JSON(http.StatusOK, gin.H{"id": id,"name": "小明","age": 18,})})//localhost:8081/users3?name=小明&age=18r.GET("/users3", func(c *gin.Context) {name := c.Query("name") //获取查询参数age := c.Query("age")//返回c.JSON(http.StatusOK, gin.H{"name": name,"age": age,})})//localhost:8081/users4 post+Form形式r.POST("/users4", func(c *gin.Context) {name := c.PostForm("name") //获取表单参数age := c.PostForm("age")//返回c.JSON(http.StatusOK, gin.H{"name": name,"age": age,})})//localhost:8081/users5/afei/23 post+uri的形式r.POST("/users5/:Name/:Age", func(c *gin.Context) { //Name对应User4里面的Name字段,Age对应User4里面的Age字段;如果字段对不上需要在User4结构体中添加uri标签user4 := User4{}err := c.ShouldBindUri(&user4)//err := c.MustBindWith(&user4) //此时user4里面的字段必须要传入否则报错终止程序//err := c.ShouldBindBodyWith(&user4) //此时可以多次绑定,每次绑定赋值给不同的结构体变量if err != nil { //转化出错c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}//返回c.JSON(http.StatusOK, gin.H{"name": user4.Name,"age": user4.Age,})})//localhost:8081/users6 post+json的形式r.POST("/users6", MyHandler1(), func(c *gin.Context) { //可以单独指定某个请求进来前需要经过哪个拦截器,如MyHandler1fmt.Println("users6")user6 := User4{}err := c.ShouldBind(&user6)if err != nil { //转化出错c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}//返回c.JSON(http.StatusOK, gin.H{"name": user6.Name,"age": user6.Age,})})//localhost:8081/users7?name=小明&age=18 ;gin.BasicAuth使用gin自带的用户密码登陆页面,默认弹框让你输入用户密码afei/1234,验证通过才能进入该请求r.GET("/users7", gin.BasicAuth(gin.Accounts{"afei": "1234"}), func(c *gin.Context) {u := c.MustGet(gin.AuthUserKey).(string) //获取验证通过的用户名(Gin 框架出于安全考虑,验证通过后只会把用户名放到 context,密码不会保留)name := c.Query("name") //获取查询参数age := c.Query("age")//返回c.JSON(http.StatusOK, gin.H{"name": name,"age": age,"u": u,})})//自定义中间件(类似java的拦截器)r.Use()//访问html文件,测试访问外层文件r.LoadHTMLGlob("models/templates/**/*") //加载模板文件:【/templates/**】 会递归匹配 /templates/ 目录下的所有子目录和文件,它会同时匹配到目录和文件;而【/templates/**/*】 只会递归匹配目录及子目录下的所有文件,不包含目录本身r.GET("/html1", func(c *gin.Context) {//Go 的模板引擎默认只识别 .tmpl、.html 等常见后缀//要和1.html 匹配,必须在模板文件中使用 {{define "/table/1.html"}} 来定义模板名称c.HTML(http.StatusOK, "/table/1.html", gin.H{ //渲染模板内容,把test变量传递给模板;匹配到的模板名称是相对于 templates 的相对路径,所以这里直接写 "1.html" 即可"test1": "Hello, World!test1",})})//访问html文件,测试访问内层文件r.GET("/html2", func(c *gin.Context) {c.HTML(http.StatusOK, "/table/2.html", gin.H{ //渲染模板内容,把test变量传递给模板;匹配到的模板名称是相对于 templates 的相对路径,所以这里要写 "table/2.html""test2": "Hello, World!test2",})})//访问html文件,测试访问内层文件r.GET("/html3", func(c *gin.Context) {c.HTML(http.StatusOK, "3.html", gin.H{ //渲染模板内容,把test变量传递给模板;匹配到的模板名称是相对于 templates 的相对路径,所以这里要写 "3.html""test3": "Hello, World!test3",})})//打印所有已加载的模板名称tmpl, err := template.ParseGlob("models/templates/**/*")if err != nil {fmt.Println("parse error:", err)}for _, t := range tmpl.Templates() {fmt.Println("Loaded template:", t.Name())}//运行服务if err := r.Run(":8081"); err != nil {fmt.Println("Failed to start server:", err)} else {fmt.Println("Server is running on http://localhost:8081")}}// 拦截器func MyHandler1() gin.HandlerFunc {return func(c *gin.Context) {//在请求处理前执行的逻辑fmt.Println("Before request1")c.Next() //继续进入下一个已注册的拦截器MyHandler2,若没有下一个拦截器则进入controller方法//在请求处理后执行的逻辑fmt.Println("After request1")}}// 拦截器func MyHandler2() gin.HandlerFunc {return func(c *gin.Context) {//在请求处理前执行的逻辑fmt.Println("Before request2")c.Next() //继续进入下一个已注册的拦截器MyHandler3,若没有下一个拦截器则进入controller方法//在请求处理后执行的逻辑fmt.Println("After request2")}}
0
0
golang基础
Jerry出海记2025-09-05
2
导读:golang基础
【声明】内容源于网络
0
0
Jerry出海记
跨境分享社 | 长期分享行业动态
内容 44206
粉丝 0
总阅读249.1k
粉丝0
内容44.2k

