大数跨境
0
0

golang基础

golang基础 Jerry出海记
2025-09-05
2
导读:golang基础
1go文件执行顺寻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 = 0b11	var b = 0o11	var c = 0x11	//Printf需要写转义符号%d	fmt.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.0	var f2 = 2.0	f1 = 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 + 1i	var c2 = 1 + 1i                                            //默认complex128类型	var c3 = complex(11)                                     //默认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-256	var 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之间可以相互转换	//中英文均占一个rune	var r1 rune = 'a'                   //单字符用rune ,多字符要用rune数组	var r2 []rune = []rune("你好go")      //长度为4,多字符要用rune数组	fmt.Println("r1=", r1)              //直接打印会打印出数字97	fmt.Println("r2=", r2)              //直接打印会打印出数字	fmt.Println("r1="string(r1))      //打印出字符 a	fmt.Printf("r1=%c\n", r1)           //%c会打印出字符 a	fmt.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=nil	fmt.Println("字符转数字:", num, err)	sii := strconv.Itoa(num) //Itoa是strconv包中的数字转字符方法,但他只返回1个返回值	fmt.Println("数字转字符:", sii)	//uint64数组和字符型十进制、十六进制、八进制、二进制互转	uni, uerr := strconv.ParseUint(si, 1032)      //把十进制字符si转为uint64形式,转换成功则err=nil;转换后检查数字是否<32位如果超出err也会返回报错	fmt.Println("十进制字符'123'转uint64:", uni, uerr)    //uni=123	uni2, uerr2 := strconv.ParseUint(si, 1632)    //把十六进制字符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, 1032)     //把十进制字符si转为int64形式,转换成功则err=nil;转换后检查数字是否<32位如果超出err也会返回报错	fmt.Println("十进制字符'123'转int64:", ni, uerr3)   //uni=123	ni2, uerr4 := strconv.ParseUint(si, 1632)   //把十六进制字符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) //123	intStr := 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	//声明map	var m1 map[string]int	//初始化map+赋值	m1 = make(map[string]int10//初始化map并设置容量,可以不设置容量	m1["a"] = 1                   //赋值	m1["b"] = 2	var 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中的key	aa, eex := m1["a"]	fmt.Println("a是否存在于m1中:", aa, eex) //输出1 true	delete(m1, "a")                    //删除map中的key	aa1, 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.Mutex	go 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)	//多变量声明:使用var	var 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 := 34	fmt.Println("d3=", d3, "d4=", d4)	//指针	var p = 1	var 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=1	fmt.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 = 2	fmt.Println("*nn=", *nn)	//结构体s	ps := 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 string		age  int		int  //匿名属性	}{ //这里初始化		Name: "王五",		age:  12,		int:  13//赋值匿名属性	}	fmt.Println("p1p1.Name:", p1p1.Name, "p1p1.age:", p1p1.age, "匿名属性值:", p1p1.int//age非公开属性,此处同包可以访问	//常量:const定义全局、局部常量,定义时就要确定值,和java一样确定后就不能修改值	const i int = 1	const ii = 1	const i1, i2 int = 12	const i3, i4 = 3"4"	const (		i5, i6     = 5"6"		i7         = 7		i8     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行-1		a1            //值为1,自动加一		a2            //值为2,自动加一	)	fmt.Println("a0:", a0, ",""a1:", a1, "a2:", a2)	const (		a00 int = 11		a11 int = iota //a0=iost=1,第2行-1		a22            //值为2	)	fmt.Println("a00:", a00, ",""a11:", a11, "a22:", a22)	//iota默认值0	const ba = iota	fmt.Println("ba:", ba)	var jj STR = "1"	fmt.Println("string类型别名:", jj)	//运算符:优先级 【[] * / % << >> &】> 【+ - | ^】>【> >= < <=】>【&& ||】	var iii int = 1	iii++ //自增自减运算符必须在后面,不能放前面否则报错,如--iii(但java可以)	//if else	if 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 case	switch bi := 11 { //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赋给t	case 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{} = 123	aii := ai.(int)    //把ai转为int类型赋值给aii=123	switch 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 = creditCard	fmt.Println("信用卡已使用额度:", card1.getBalance())	var card2 CardMapper = debitCard	fmt.Println("借记卡余额:", card2.getBalance())	//空接口=任意类型的数据	var blank BlankMapper = creditCard //blank是空接口BlankMapper变量	blank = 2	blank = &iii	fmt.Print("空接口变量=", blank)	//for循环	f := 1	for 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                                                                           //默认false	var stopped atomic.Bool                                                                    // 原子型,默认false	//无限循环	for {		if started == false { //只执行一次:第一次for循环的时候			started = true //started置为true			go 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为true					return				}			}() //有括号表示立即执行闭包(匿名方法),括号里是闭包的入参,该闭包没有入参所有括号里没有参数		}		fmt.Println("循环中...")		if stopped.Load() { //重新加载原子型数据			break		}	}	//range遍历切片(数组)	var ri [5]int	ri[1] = 2	for i, item := range ri { //range遍历,i是下表,item是值		fmt.Println(i, "下标值位:", item)	}	//range遍历map	var 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([]int44)     //make创建切片,长度10,容量10,容量可以不写就和长度一样;切片长度一般<=容量	sl2 := []int{111213}     //直接定义切片	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 0	sl4 := 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, 111213//和sl4等价,合并切片,sl5=0 11 12 13	fmt.Println("sl5:", sl5)	//sl6 := append(sl5[0:2], sl5[3:]...) //移除sl5中下标为2的元素,sl6=0 11 13 ,sl5[0:2]=1 11  ,sl5[3:]=13	sl6 := append(append([]int{}, sl5[0:2]...), sl5[3:]...) //推荐这样使用就不会修改sl5,即sl5仍为0 11 12 13	fmt.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 13	fmt.Println("sl7:", sl7)                                 //sl7=0 11 1 12 13	var sl8 = make([]intlen(sl7))                          //必须指定长度否则报错	copy(sl8, sl7)                                           //复制切片sl7到sl8,修改新切片的内容不会影响原切片;如果新切片长度<原切片那么只能赋值一部分元素到新切片	sl8[0] = 1	fmt.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{12}      //定义长度为2的int数组,同时可以初始化	arr2 := [...]int{123//定义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//初始化第一个map	mp[0]["a"] = "b"                //赋值	mp[1] = make(map[string]string//初始化第二个map	mp[1]["a"] = "b"                //赋值	//多维数组	arr5 := [2][3]int{{111}, {111}}                                                                      //二维数组,2行3列	arr6 := [2][3][4]int{{{1111}, {1111}, {1111}}, {{1111}, {1111}, {1111}}} //三维数组:先看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但不会跳出for	fmt.Println("=== 使用循环处理多个case ===")	ch1 := make(chan string2//建立2个缓冲区大小的chanel,可以接收2次string型的消息	ch2 := make(chan string2)	ch3 := make(chan string2)	// 向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(3333))	nf1 := func(a int, b int) func(intint) string { //定义了nf1=第一个匿名函数:入参是int  ,返回值是第二个匿名函数【 func(int ,int) string 】,nf1=第一个匿名函数		return func(a1 int, b1 int) string {			return string(a1 + b1)		}	}	nf2 := nf1(3333)                                                    //nf1(33, 33)会调用第一个匿名函数,同时返回值是第二个匿名函数,所以nf2=第二个匿名函数	fmt.Println("变量nf1是一个匿名函数,该匿名函数的返回值也是一个匿名函数,调用第一个匿名函数:", nf2(3333)) //调用第二个匿名函数,nf2(33, 33)等价于nf1(33,33)(33,33)	nf3 := func(a int, b int) func(intint) string { //定义了nf3=第一个匿名函数:入参是int  ,返回值是第二个匿名函数【 func(int ,int) string 】,nf1=第一个匿名函数		return func(a1 int, b1 int) string {			return string(a1 + b1)		}	}	fmt.Println("调用第二个匿名函数:", nf3(3333)(3334))	nf4 := func(a int, b int) func(stringstring) string { //定义了nf4=第一个匿名函数:入参是int  ,返回值是第二个匿名函数【 func(int ,int) string 】,nf1=第一个匿名函数		return func(a1 string, b1 string) string {			return a1 + b1		}	}(12//最大区别是这一行,此时会立即调用第一个匿名函数,且入参要和第一个匿名函数的入参一样,立即调用后会把第一个匿名函数的返回值赋给nf4,第一个匿名函数的返回值就是第二个匿名函数,所以此时nf4=第二个匿名函数,可以直接使用nf4("a","b")调用第二个匿名函数	fmt.Println("第一个匿名函数会被立即调用,调用第二个匿名函数:", nf4("a""b"))	//变量等价于方法	pp2 := models.Person{		Name: "小猪",		// age:  "20", 结构体中age是小写非公开的,所以此处不同包不能访问否则报错	}	myFunc = pp2.GetNewName2	fmt.Println("变量myFunc是一个方法,调用:", myFunc())	// 使用循环处理所有可用的数据	var bs bool = false	for {		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类型数据必须使用make	chh := make(chan int5//该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 int2//该chan只有两个缓存区(如果不指定缓存区则默认为1),所以当发满2个int数据到该chan时会马上阻塞直到从该chan读数据出去之后才能继续往该chan发送数据	onlySend(chh1)	fmt.Println("只发送数据的chan接收到数据:", <-chh1) //onlySend里面发送了2次数据到该chan,这里只接收一次所以只能打印第一次发送的数据 123	//只能接收该chan的数据,而不能发送数据到这个chan	chh1 <- 456	onlyReceive(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/mysql	config := models.DatabaseConfig{		Host:     "localhost",		Port:     "3306",		Username: "root",		Password: "tiger",		Database: "sakila",	}	// 构建DSN	dsn := 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,b a = 1 b = "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 intbool //传入消费金额 ;接口方法大小写都一样,都没有访问限制;接口入参类型int和返回类型bool一定要写,但参数名可以不写,如buy(int) bool GetBalanceMapper     //接口中可以存在另外一个接口,这样实现该CardMapper接口方法的子类也必须实现GetBalanceMapper接口方法}// 定义信用卡结构体type CreditCard struct { Use   int //已使用额度 Limit int //总额度}// 信用卡结构体实现接口CardMapper中的方法(接收者必须是指针否则无法修改后共享数据),看作CreditCard继承了CardMapper和GetBalanceMapperfunc (card *CreditCard) buy(amount intbool { //本次消费金额 //已使用额度+本次消费金额<最大额度 if card.Use+amount < card.Limit { card.Use = card.Use + amount return 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 intbool { //本次消费金额 //借记卡余额>本次消费金额 if card.Balance > amount { card.Balance = card.Balance - amount return 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 <- 123 chh1 <- 111}// 该chan只能用来接收数据func onlyReceive(chh1 <-chan int) { //c <-chan int fmt.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 string Ff        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:"-"`               //数组类型;该字段序列化时对应字段a b         []interface{}  `json:"b,omitempty" db:"-"`     //切片类型 c         *int           `json:"c,omitempty"`            //指针类型 pp        Person2        `json:"-" db:"-"`               //其他结构体类型;调用时用(对象.pp.Name)获取Person2中的Name int                      //定义匿名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 string age  int int  //匿名属性}{ //这里初始化 Name: "王五", age:  12, int:  13//赋值匿名属性}//声明一个空的匿名结构体var p2p2 = struct{}{}//结构体converTest1、converTest2可以互转(注意大写)type ConverTest1 struct { Name string Age  int}type ConverTest2 struct { Name string Age  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】类型,指针类型方式传入null Age        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包方式传入null User1                     //会把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) NULL Name3 sql.NullString // VARCHAR(255) NULL // 整数类型 Age1 int           // INT NOT NULL,gorm不会传入null给数据库字默认传入0 Age2 *int          // INT NULL Age3 sql.NullInt64 // BIGINT NULL Age4 sql.NullInt32 // INT NULL // 浮点数类型 Score1 float64         // DOUBLE NOT NULL,gorm不会传入null给数据库字默认传入0 Score2 *float64        // DOUBLE NULL Score3 sql.NullFloat64 // DOUBLE NULL // 布尔类型 Active1 bool         // TINYINT(1) NOT NULL,gorm不会传入null给数据库字默认传入0 Active2 *bool        // TINYINT(1) NULL Active3 sql.NullBool // TINYINT(1) NULL // 时间类型 CreatedAt1 time.Time    // DATETIME NOT NULL,gorm不会传入null给数据库字 CreatedAt2 *time.Time   // DATETIME NULL CreatedAt3 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表的主键ID City   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表的主键ID City    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       string City3s     []City3 `gorm:"many2many:user3_city3s;"` // many2many指定创建一个多对多关系的中间表名为user3_city3s,在创建表User3s时会自动创建该中间表}type City3 struct { gorm.Model // gorm.Model包含ID、CreatedAt、UpdatedAt、DeletedAt四个字段 CityName   string User3s     []User3 `gorm:"many2many:user3_city3s;"`}// 数据库配置type DatabaseConfig struct { Host     string Port     string Username string Password string Database string}func MysqlTest(db *gorm.DB) { //建表,gorm会建一张在User的复数形式users表明的表 //db.AutoMigrate(&User{}) //1、插入数据 birthday := time.Date(19905150000, 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里会返回主键ID fmt.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里会返回主键ID fmt.Printf("插入结果%v,插入行数%v", result.Error, result.RowsAffected) //插入指定字段 user.Birthday2 = time.Now() db.Select("Name""Age""Birthday""Birthday1""Birthday2""Name11").Create(&user) //忽略插入字段 user.ID = 0 user.Birthday2 = time.Now().Add(time.Second * 1) db.Omit("Age").Create(&user) //2、批量查询数据 var userss []User db.Find(&userss) for i, v := range userss { fmt.Println(fmt.Sprintf("第%d条数据:%v", i, v)) } //按主键升序查询第一条 var u User result = db.First(&u) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1 fmt.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 1 fmt.Println("乱序查询第一条:%v", u) //按主键升序查询最后一条 u.ID = 0 db.Last(&u) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` DESC LIMIT 1 fmt.Println("主键升序查询最后一条:%v", u) //主键=12 u.ID = 0         //上面查询中已查询u的ID值,如果此处不置零,则会拼接两个users.ID=12 and users.ID=xx db.First(&u, 12//SELECT * FROM `users` WHERE `users`.`id` = 12 AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1 fmt.Println("主键=12:%v", u) //查询所有数据 var rms []User db.Find(&rms) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL fmt.Println("查询所有数据:%v", rms) db.Find(&rms, "Name = ?""afei"//SELECT * FROM `users` WHERE (Name = 'afei') AND `users`.`deleted_at` IS NULL fmt.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 NULL fmt.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 NULL fmt.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 []User db.Find(&us, []int{123}) //SELECT * FROM `users` WHERE `users`.`id` IN (1,2,3) AND `users`.`deleted_at` IS NULLd fmt.Println("查询多条数据:%v", us) db.Where([]int{123}).Find(&us) //SELECT * FROM `users` WHERE `users`.`id` IN (1,2,3) AND `users`.`deleted_at` IS NULL fmt.Println("查询多条数据:%v", us) //where查询 db.Where("Name = ?""afei").Find(&us) //SELECT * FROM `users` WHERE (Name = 'afei') AND `users`.`deleted_at` IS NULL fmt.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 1 fmt.Println("where查询结果:%v", us) db.Where("Name in ?", []string{"小明""小强"}).Find(&us) //SELECT * FROM `users` WHERE Name in ('小明','小强') AND `users`.`deleted_at` IS NULL fmt.Println("where in查询结果:%v", us) db.Where("Name like ?""%小%").Find(&us) // SELECT * FROM `users` WHERE Name like '%小%' AND `users`.`deleted_at` IS NULL fmt.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 NULL fmt.Println("where and查询结果:%v", us) //AddDate三个参数分别是添加年、月、日,负数表示减去 db.Where("birthday > ? and birthday < ?", time.Now().AddDate(-100), "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(-200), "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 NULL fmt.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 NULL fmt.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 NULL fmt.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 NULL fmt.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 NULL fmt.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 NULL fmt.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 NULL fmt.Println("struct型!=数据:", us) //普通的not in() db.Not("Name in ?", []string{"小明""小胖"}).Find(&us) // SELECT * FROM `users` WHERE not Name in ('小明','小胖') AND `users`.`deleted_at` IS NULL fmt.Println("普通not in数据:", us) //普通的not in() db.Not([]int{123}).Find(&us) // SELECT * FROM `users` WHERE not id in (1,2,3) AND `users`.`deleted_at` IS NULL fmt.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 NULL fmt.Println("map型not in数据:", us) //or //普通or db.Where("Name""小明").Or("Age""30").Find(&us) //SELECT * FROM `users` WHERE (`Name` = '小明' OR `Age` = '30') AND `users`.`deleted_at` IS NULL fmt.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 NULL fmt.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 NULL fmt.Println("struct型or查询结果:%v", us) //select特定字段 db.Select("Name""Age").Find(&us) //SELECT `name`,`age` FROM `users` WHERE `users`.`deleted_at` IS NULL fmt.Println("select特定字段查询结果:%v", us) db.Select([]string{"Name""Age"}).Find(&us) // SELECT `name`,`age` FROM `users` WHERE `users`.`deleted_at` IS NULL fmt.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 NULL fmt.Println("select特定字段查询结果:%v", us) db.Select("COALESCE(Email, ?) AS Email""未知邮箱").Find(&us) //SELECT CCOALESCE(Email, '未知邮箱') AS Email FROM `users` WHERE `users`.`deleted_at` IS NULL fmt.Println("select特定字段查询结果:%v", us) //order db.Order("name").Find(&us) // SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY name fmt.Println("name asc排序结果:", us) db.Order("name desc").Find(&us) //SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY name desc fmt.Println("name desc排序结果:", us) //limit offset db.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,2 fmt.Println("limit offset查询结果:", us) //3、更新数据 //save函数当有主键会更新所有字段即使字段是零值,没有主键则插入 u.Age = 33 db.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` = 12 u.ID = 0 u.Birthday2 = time.Now().AddDate(001) 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` = 21 db.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 NULL db.Exec("update users set age=?"18)                                               //执行原生sql: update user set age=18 db.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{23}) //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 NULL db.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 NULL db.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默认插入0 db.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 UserB db.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 City db.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表的主键ID user22 := 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 1 db.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 1 db.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 //插入中间表数据ity city33 := City3{CityName: "广州", User3s: []User3{user33}} db.Create(&city33) //插入City3表,同时插入中间表user3_city3s //查询数据 var user3First User3 err := 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、animals dog := Dog{Name: "小狗", Animal: Animal{Desc: "可爱的小狗"}} //插入Animal数据时,OwnerType=dog,OwnerID=当前dog的ID db.Create(&dog)                                       //插入Dog表,同时插入Animal表 cat := Cat{Name: "小猫", Animal: Animal{Desc: "可爱的小猫"}} //插入Animal数据时,OwnerType=cat,OwnerID=当前cat的ID db.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()                 //回滚事务,不会执行上面的sql tx.Commit() //提交事务,正常执行上面的sql提交到数据库 //设置回滚点 tx = db.Begin()                //开启事务 tx.Create(&Dog{Name: "事务小狗1"}) //插入Dog表 tx.SavePoint("sp1")            //设置回滚点sp1 tx.Create(&Dog{Name: "事务小狗2"}) //插入Dog表 tx.SavePoint("sp2")            //设置回滚点sp2 tx.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       string Desc       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 People db.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 []People db.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\NaMe Age  int    `json:"age"`}func GinTest() { r := gin.Default() r.Use(MyHandler1(), MyHandler2()) //注册拦截器,类似java的拦截器 //Get请求,类似java的controller r.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") //输出Json r.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/111 r.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=18 r.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) { //可以单独指定某个请求进来前需要经过哪个拦截器,如MyHandler1 fmt.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
Jerry出海记
跨境分享社 | 长期分享行业动态
内容 44206
粉丝 0
Jerry出海记 跨境分享社 | 长期分享行业动态
总阅读249.1k
粉丝0
内容44.2k