1. 认识Go语言
推荐IDE:GOLAND
推荐教程:这里首推咱们网校web后端的GO教程,写得很好很仔细
我是两个教程来回看的
Go环境
GOPATH
按照惯例,所有Go的代码都存在于一个指定目录下面,这个目录可以放在任何地方,称为GOPATH(默认在$HOME/go),这个工作区应该由你自己设置一下,不然你在其他地方写代码就会出现很多环境问题,我感觉是这样,你不设置的话,半天跑不起来代码。。。
目录结构
----go
|----src(所有源代码放置)
|----Demo
|----main.go
|----go.mod
|----go.sum
|----........
|----pkg(包对象)
|----bin(编译好的程序)
此时,你可以用 go get,它会把下载到的资源按 src/pkg/bin 结构正确安装在相应的 $GOPATH/xxx 目录中。
go第三方工具
GolangCI-Lint
对默认的语法检查进行增强可以用 GolangCI-Lint。可通过以下方式安装:
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
其他
Go 拥有非常庞大的第三方生态系统。有关更完整的列表,请参阅https://awesome-go.com
内置的GO文档
Go 的另一个高质量特征是文档。通过运行 godoc -http :8000
,可以在本地启动文档。如果你访问 localhost:8000/pkg,将看到系统上安装的所有包。
大多数标准库都有优秀的文档和示例。例如查看testing包的帮助,浏览 http://localhost:8000/pkg/testing/ 。
go 1.13.x 之后,不再自带 godoc, 就需要通过
go get golang.org/x/tools/cmd/godoc
来手动安装了。
2. 写代码
写在哪里
前面说到,GO语言死板的工作目录在$HOME/go
下面,所以,我们的go代码毫无疑问要写在$HOME/go/src
下面
所以,建立$HOME\go\src\github.com\yyz\GODemo
右键使用 GOLAND打开
为什么要用github.com命名,这里不太明白,而且很多包里面都有这个。
第一个Demo
创建Hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
然后在terminal里面运行一下
C:\Users\yyz\go\src\github.com\yyz\GODemo>go run .
Hello World!
那些规矩
程序运行必须要一个main包,并且里面包含一个main函数作为入口。
包是把go代码组合在一起的一种方式,类似于其他语言里面的包吧,比如Python,Java。
语法规则是类C,但是,没有分号作为结尾,并且定义函数的花括号必须和函数名在一行。。。等等很多规矩自己看吧,多写写就记住了。
编写测试函数
go语言内置了go test这个命令,那我们来写一个test文件试试吧
hello.go
package main
import "fmt"
func main() {
fmt.Println(Hello())
}
func Hello() string{
return "Hello World!"
}
hello_test.go
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello()
want := "Hello World!"
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
}
然后运行go test即可看到测试的输出,尝试改变want里面的值,go test的结果也不一样。
这里尝试改变了一下hello_test.go文件的名字,好像必须后面有个_test,改成_testing就不行,但是前面的hello随便改好像没关系。
具体规则是:
- 程序需要在一个名为
xxx_test.go
的文件中编写 - 测试函数的命名必须以单词
Test
开始 - 测试函数只接受一个参数
t *testing.T
现在这些信息足以让我们明白,类型为 *testing.T
的变量 t
是你在测试框架中的 hook(钩子),所以当你想让测试失败时可以执行 t.Fail()
之类的操作。
让我们从在测试中捕获这些需求开始。这是基本的测试驱动开发,可以确保我们的测试用例 真正 在测试我们想要的功能。当你回顾编写的测试时,存在一个风险:即使代码没有按照预期工作,测试也可能继续通过。
上面这句话是抄的,感觉说的很对。。
更多的测试函数
我们拿到了更多的需求,这次,需要根据使用者的语言来识别需要问候的语句,于是我们要编写更多的Demo与test
hello_test.go
package main
import "testing"
func TestHello(t *testing.T) {
assertCorrecctMessage := func(t *testing.T, got string, want string) {
t.Helper()
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
}
t.Run("Saying hello to people: ", func(t *testing.T) {
got := Hello("yyz", "spanish")
want := "Hola yyz"
assertCorrecctMessage(t, got ,want)
})
t.Run("Say hello to everybody", func(t *testing.T) {
got := Hello("", "Chinese")
want := "你好 world"
assertCorrecctMessage(t, got, want)
})
}
- 函数内部使用了新的函数简化代码
- t.helper()可以帮助编译器准确识别报错信息在哪一行
hello.go
package main
import "fmt"
func main() {
fmt.Println(Hello("yyz", ""))
}
func Hello(name string, language string) string {
if name == "" {
name = "world"
}
return getPrefix(language) + name
}
func getPrefix(language string) (prefix string) {
switch language {
case "Chinese":
prefix = "你好 "
case "English":
prefix = "Hello "
case "spanish":
prefix = "Hola "
default:
prefix = "Hello "
}
return
}
- 这里使用到了命名返回值
- 这将在你函数的内存里面创建名为
prefix
的变量 - 变量将根据类型赋予初始值
- 调用
return
即可,而不需要调用return prefix
- 这样做能使代码更清晰,因为这将显示在go doc里面
- 这将在你函数的内存里面创建名为
- switch case语句和c语言差不多
- 函数名称以小写字母开头。在 Go 中,公共函数以大写字母开始,私有函数以小写字母开头。我们不希望我们算法的内部结构暴露给外部,所以我们将这个功能私有化。这里私有共有可能和Java里面的public和private啥的差不多,但暂时还是不是很理解。
- TDD(Test-Driven Development)测试驱动开发,是一个追求完美的开发规范,代码量会增多,但是完整性和健壮性可以很好的保持。
3. 更多的基础知识
int
这次我们换个目录编写,因为一个目录里面只能有一个package,所以换个目录使用integers包,而不是main包
编写Adder_test.go
package integers
import "testing"
func TestAdder(t *testing.T) {
sum := Add(2, 2)
expected := 4
if sum != expected {
t.Errorf("expected '%d' but got '%d'", expected, sum)
}
}
这次为了测试代码,我们编写一个代码数量最少的go函数
package integers
func Add(a, b int) int {
return 0
}
- 这里,如果参数都是一个类型的话可以简写
a, b int
而不是a int, b, int
go test过后肯定是FAIL的,如果改成return 4,哈哈哈哈是不是就通过了,这样感觉完完全全欺骗了编译器
在Adder_test.go添加如下函数:
func ExampleAdd() {
fmt.Println(Add(5, 7))
// Output: 12
}
- 这里Output就是期待的输出,然后go test -v 可以同时测试所有函数
- 没有// Output: 12这一行是不会对这个函数进行测试的
再次打开godoc:godoc -http :8000
访问http://127.0.0.1:8000/pkg/github.com/yyz/AdderDemo/
即可查看自己 写的Demo自动生成的doc文档
循环和迭代
在go中只有for,没有while do until等,好事。
写个简单的字符串重复函数:
package iterDemo // Repeat returns the result repeat by num of a given string. func Repeat(s string, num int) (res string) { for i:=0; i<num; i++ { res += s } return }
然后测试他:
package iterDemo import ( "fmt" "testing" ) func TestIter(t *testing.T) { got := Repeat("a", 3) want := "aaa" if got != want { t.Errorf("expected '%q' but got '%q'", want, got) } } func BenchmarkRepeat(b *testing.B) { for i := 0; i < b.N; i++ { Repeat("a", 10) } } func ExampleRepeat() { fmt.Println(Repeat("a", 10)) //Output: aaaaaaaaaa }
- 用到的测试方法有一般的测试,Benchmark测试,Example测试
- 第二个可以自己看看输出,很有意思
- for的用法在后面会有更多讲解
数组与切片
数组
我们来写一个简单的int数组并求一下和
package main
func Sum(arr [5]int) (sum int) {
for i:=0; i<5; i++ {
sum += arr[i]
}
return
}
测试:
package main
import "testing"
func TestSum(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if want != got {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
}
可以PASS,这时候习惯顺手git add . && git commit -m “简单的数组求和测试”
此时如果你正在使用源码的版本控制工具(你应该使用它!),我会在此刻先提交一次代码。因为我们已经拥有了一个有测试支持的程序。
但我 不会 将它推送到远程的 master 分支,因为我马上就会重构它。在此时提交一次代码是一种很好的习惯。因为你可以在之后重构导致的代码乱掉时回退到当前版本
你总是能够回到这个可用的版本。
重构:
package main func Sum(arr [5]int) (sum int) { for _, num := range arr{ sum += num } return }
这里 _
符号的用法可以说是GO当中很经典的一个特性了,我把他叫做黑洞变量,哈哈哈,有点像/dev/null,只能接收,不能使用。
这里 _
替代的是数组的下标,如果输出出来的话,上面会打印0, 1, 2, 3, 4
发现的问题:
- []int和[5]int不是一个类型,传入函数的时候会报错不能通过编译
- 书上说的:“因为这个原因,所以数组比较笨重,大多数情况下我们都不会使用它。”
- Go 的切片(slice)类型不会将集合的长度保存在类型中,因此它的尺寸可以是不固定的。所以下面讲切片
切片
实际上语法和数组是一样的,就是前面不指定长度:
mySlice := []int{1, 2, 3, 4, 5}
更多的实例:
我们现在需要一个SumAll函数,接收多个切片,并返回一个切片,例如:SumAll([]int{1,2}, []int{0,9})
返回 []int{3, 9}
func SumAll(arrs ... []int) (sum []int){ sum = make([]int, len(arrs)) for length, arr := range arrs { sum[length] = Sum(arr) } return }
- 这里make可以制作指定类型、长度的切片
- 切片索引可以访问切片内部元素,使用等号可以赋值
- 也可以使用append修改一下代码,避免使用make,例如:
func SumAll(arrs ... []int) (sum []int){ for _, arr := range arrs { sum = append(sum, Sum(arr)) } return }
- 在这个实例中,不用担心slice的长度
然后编写go测试:
func TestSumAll(t *testing.T) { arr1 := []int {1, 3, 4} arr2 := []int {1, 4, 4} got := SumAll(arr1, arr2) want := []int {8, 9} if !reflect.DeepEqual(got, want) { t.Errorf("got %v want %v", got, want) } }
- 两个切片不能直接使用等号==比较,你可以
- 使用for循环依次比较内部元素,但是显得太麻烦
- 使用reflect.DeepEqual(slice a, slice b)这样来比较两个slice
- 注意,不同的类型传递在DeepEqual里面会编译通过,引发奇怪的结果
更多的实例:
llTails函数,返回每个切片的尾部元素之和,就是除去第一个元素的剩余元素之和,即是Sum(slice[1:])
func SumAllTail(arrs ...[]int) (sum []int) { for _, arr := range arrs { if len(arr) == 0{ sum = append(sum, 0) } else { sum = append(sum, Sum(arr[1:])) } } return }
- 注意获取子切片的时候,传入0长度的切片会通过编译但是运行时引发错误,这是不可取的,所以我们要正确的处理错误,有错误最好在编译的时候就不通过
下面是test
func TestSumAllTail(t *testing.T) {
arr1 := []int {}
arr2 := []int {1}
got := SumAllTail(arr1, arr2)
want := []int {0, 0}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
结构体,接口和方法(被禁止的重载)
我们有个计算长方形面积的需求,可能会这么写
func Area(length float64, weith float64) float64 {
return weith * length
}
但是使用的人并不知道到这个函数到底要干嘛,他可能会传递一个三角形进来。
结构体
我们也可以重命名一下函数名叫RectangleArea,但更简洁的方案是使用structure来定义一个Rectangle类型
type Rectangle struct {
Width float64
Height float64
}
然后编写计算面积的函数:
// 下面的叫函数 func Area(Rec Rectangle) float64 { return Rec.Width * Rec.Height }
接下来编写测试:
func TestArea(t *testing.T) { rec := Rectangle{Height: 1.1, Width: 1.1} got := Area(rec) want := 1.21 if got != want { t.Errorf("got %f, want %f", got, want) } }
很好,虽然不能通过测试,但是已经可以成功计算出结果了,过不了是因为浮点数的比较不能精确。
— FAIL: TestArea (0.00s) Shapes_test.go:12: got 1.210000, want 1.210000
好,那我们尝试写一个计算圆形的面积函数:
type Circle struct { Radius float64 }
那么我们肯定得写一下具体的实现,比如:
// 下面的Area用于计算长方形的面积 func Area(Rec Rectangle) float64 { return Rec.Width * Rec.Height } // 下面的Area用于计算圆形的面积 func Area(cir Circle) float64 { return cir.Radius * cir.Radius * 3.14 }
‘Area’ redeclared in this package
这个在别的语言例如Java中叫做函数的重载,这个在Go里面是不被允许的
注意之前我们定义的都叫方法,那我们来定义一个方法
方法
// 下面的叫函数 func Area(Rec Rectangle) float64 { return Rec.Width * Rec.Height } // 下面的是方法 func (rec Rectangle) Area() float64 { return rec.Width * rec.Height }
这样的话,test就能正常运行了
方法的语法是:func(receiverName ReceiverType) MethodName(args)
当方法被这种类型的变量调用时,数据的引用通过变量
receiverName
获得。在其他许多编程语言中这些被隐藏起来并且通过this
来获得接收者。
这就是咱们熟悉的成员方法了,只不过在Go里面,直接结构体没有class,所以定义的时候是在struct外面单独定义
接口
我们现在有个需求是需要一个CheckArea函数,这个函数的参数是多种类型的,也就是说,你随意传入一个形状shape,然后调用shape.Area()
就能正确计算面积,接下来就要用到接口
定义一个接口
type shape interface { Area() float64 }
抄一下网校的课件:
接口即一组方法定义的集合,定义了对象的一组行为,由具体的类型实例实现具体的方法。换句话说,一个接口就是定义(规范或约束),而方法就是实现,接口的作用应该是将定义与实现分离,降低耦合度。(习惯用“er”结尾来命名,例如“Reader”。)接口与对象的关系是多对多,即一个对象可以实现多个接口,一个接口也可以被多个对象实现。
当你定义完毕之后就会发现type Rectangle struct {
和type Circle struct {
的左边就会多出来一个向上的箭头,说明这两个类实例化的对象都实现了shape接口。
接下来编写测试函数
func TestArea(t *testing.T) {
checkArea := func(t *testing.T, shape1 shape, want float64) {
t.Helper()
got := shape1.Area()
if got != want {
t.Errorf("got %.2f want %.2f", got, want)
}
}
t.Run("rectangles", func(t *testing.T) {
rectangle := Rectangle{12, 6}
checkArea(t, rectangle, 72.0)
})
t.Run("circles", func(t *testing.T) {
circle := Circle{10}
checkArea(t, circle, 314.1592653589793)
})
}
空接口
我们可以发现,实现一个接口,必须要实现接口里面的方法,那么,我们写一个空接口,里面没有任何方法,是不是所有的类型都能实现这个接口呢?答案是肯定的
type AllInterfaces interface {
}
空接口有什么用呢?这里给大家一个map,大家细细品味就知道了
a := map[interface{}]interface{}{}
a["aaa"] = 1
a[3] = []int{1, 2, 3}
fmt.Println(a)
多态
我们实现了接口,其实也就是多态的一种体现。
多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作,而多态的好处需要大家在后续的学习中慢慢体会。
指针和错误
错误的成员变量的使用
这次的示例是写一个BitCoin的储存:
package PointerDemo type Wallet struct { balance int } func (w Wallet) Balance() int { return w.balance } func (w Wallet) Deposit(amount int) { w.balance += amount }
代码很好理解,我们赶快测试一下吧:
package PointerDemo import ( "testing" ) func TestWallet(t *testing.T) { wallet := Wallet{} wallet.Deposit(10) got := wallet.Balance() want := 10 if got != want { t.Errorf("got %d want %d", got, want) } }
go test,oops,发生了意料之外的错误
Wallet_test.go:16: got 0 want 10
原因是:在 Go 中,当调用一个函数或方法时,参数会被复制。
我们可以在两边打印一下对象各自的地址fmt.Println("address of balance in Deposit is", &w.balance)
那么我们应该传递指针进去才能正确处理我们的对象:
func (w *Wallet) Balance() int { return w.balance } func (w *Wallet) Deposit(amount int) { // 这里上面换成(w Wallet)就是错误的,里面是不能正确改变balance的值的,而且打印出来的地址也不一样 w.balance += amount }
创造比特币!
GO语言里可以在原生类型的基础上创建我们专属的类型
type BitCoin int
创建我们独有的打印类型
interface{String() string}这个接口是在
fmt
包中定义的。当使用%s
打印格式化的字符串时,你可以定义此类型的打印方式。
type Stringer interface { String() string } func (b Bitcoin) String() string { return fmt.Sprintf("%d BTC", b) } if got != want { t.Errorf("got %s want %s", got, want) }
于是我们编写测试函数就可以直接储存我们自己的BitCoin了,是不是一夜暴富了!!
func TestWallet(t *testing.T) {
wallet := Wallet{}
wallet.Deposit(10)
got := wallet.Balance()
want := BitCoin(101)
if got != want {
t.Errorf("got %s want %s", got, want)
}
}
//Output: got 10 BTC want 101 BTC
error && panic && recover
我们现在来写一个提现的函数:
func (w *Wallet) Withdraw(amount BitCoin) {
w.balance -= amount
rmb := float64(amount) * 356623.06
fmt.Printf("你提现了%s, 价值大约%.2f人民币\n", amount, rmb)
}
那如果我们提现的金额大于我们的余额会怎么样,至少那肯定不是一个好的合约
那我们可以在用户传入的金额大于余额的时候返回一个错误阻止程序继续运行:
func (w *Wallet) Withdraw(amount BitCoin) error {
if amount > w.balance {
return errors.New("You hava not enought money")
}
w.balance -= amount
rmb := float64(amount) * 356623.06
fmt.Printf("你提现了%s, 价值大约%.2f人民币\n", amount, rmb)
return nil
}
有关错误与异常:
- Go是一门类型安全的语,,其运行的时候不会出现这种编译器和运行时都无法捕获的错误,也就是说,不会出现untrapped error,所以从这个角度来说,Go语言不存在所谓的异常,出现的”异常”全是错误
- 常用fmt.Errorf()与error.New()
- 处理异常时,函数应当设置最后一个返回值为error类型,并且把返回值与nil比较来处理异常
- 调用panic(error)会立即打印调用堆栈信息并且结束程序
- 自我理解,正确与优雅的处理错误是一项艰巨且复杂的任务,这里不想多写。
可能是因为在Python里面处理异常被处理得自闭了
Maps
之前讲空接口的时候放了一个map的格式:
map[interface{}]interface{}{}
我是先把map理解为Python里面的字典,感觉差不多
定义一个存放int
:int
的map就是map[int]int{}
这样
但是注意:键的类型只能是可比较类型,值的类型就无所谓,甚至还可以是另一个map
发现一件事,在Python里面查找一个不存在的键值对是会报错的,而GO里面不会,他只会给你返回一个类型的默认值,那么我们可以这样
// value为索引对应的值,ok为一个布尔变量 // 值为true时表示map中有这个索引,为false时表示map中没有这个索引 value, ok := m["c"] fmt.Println(value, ok)
- 用一个ok变量接收一个bool返回值来判断值是否存在
- 删除键值对用delete函数,用range遍历map
- map是引用类型,在函数传递的时候不需要传递指针就可以修改他!
并发
Do not communicate by sharing memory; instead, share memory by communicating.
不要通过共享内存来通信,而应通过通信来共享内存。
其实,俺就是奔着Go的并发来的。
我们先使用最简单的锁机制完成一个简单的异步打印操作
锁
func main() { var lock sync.Mutex fun1 := func() { fmt.Println("go func1()") lock.Unlock() } lock.Lock() go fun1() lock.Lock() fmt.Println("main()") }
这里注意看一下lock的时机,多跑几遍代码体会一下
channel
接下来使用Go里面先进的channel
channel是什么?自己百度看
func main() {
done := make(chan int, 1)
fun1 := func() {
fmt.Println("go func1()")
done <- 1
}
go fun1()
fmt.Println("main()")
<- done
}
这里使用了一个带缓存的channel来进行接收发送操作来保证两个线程正确打印
来看看生产者消费者模型
func Producer(factor int, out chan int) {
for i := 0 ;; i++{
fmt.Println("生产!!")
out <- factor * i
}
}
func Consumer(out chan int) {
for i := range out {
fmt.Println(i)
//time.Sleep(100 * time.Millisecond)
}
}
func main() {
ch := make(chan int, 1)
go Producer(5, ch)
go Producer(7, ch)
go Consumer(ch)
time.Sleep(5 * time.Second)
}
不想在继续看了,直接开始写我前几天准备写的脚本吧555
4. 多线程执行大量http请求实战
package main
import (
"fmt"
"github.com/yyz/PGDic"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
)
var URL string = "http://127.0.0.1/src/"
var channel = make(chan int, 500)
var time1 = time.Now().Unix()
func main() {
GetDic, _ := PGDic.MakeDic()
go doGet(GetDic)
//go doPost(PostDic)
for{}
}
func doGet(GetDic map[string][]string) {
for filename, params := range GetDic {
for _, param := range params {
channel <- 1
go GetRequest(filename, param)
}
}
fmt.Println("endGet")
}
func doPost(PostDic map[string][]string) {
for filename, params := range PostDic {
for _, param := range params {
channel <- 1
go PostRequest(filename, param)
}
}
fmt.Println("endPost")
}
func GetRequest(filename string, param string) {
FullURL := URL + filename + "?" + param + "=echo yyzyyzyyzyyz;"
res, err := http.Get(FullURL)
if err != nil {
panic(err)
}
bodyByte, err := ioutil.ReadAll(res.Body)
body := string(bodyByte)
if strings.Contains(body, "yyzyyzyyzyyz") {
time2 := time.Now().Unix()
panic(filename + " " + param + strconv.FormatInt(time2-time1, 10))
}
res.Body.Close()
<-channel
}
func PostRequest(filename string, param string) {
FullURL := URL + filename
param += "=echo yyzyyzyyzyyz;"
res, err := http.Post(FullURL, "application/x-www-form-urlencoded", strings.NewReader(param))
if err != nil {
panic(err)
}
bodyByte, err := ioutil.ReadAll(res.Body)
body := string(bodyByte)
if strings.Contains(body, "yyzyyzyyzyyz") {
time2 := time.Now().Unix()
panic(filename + " " + param + strconv.FormatInt(time2-time1, 10))
}
res.Body.Close()
<-channel
}
下面是字典生成代码:
package PGDic
import (
"io/ioutil"
"os"
"regexp"
"strings"
)
var Path string = "D:\\Software\\phpstudy\\WWW\\src"
type Dic map[string][]string
func MakeDic() (GetDic, PostDic map[string][]string){
GetDic = make(map[string][]string)
PostDic = make(map[string][]string)
files, _ := os.ReadDir(Path)
for _, file := range files {
Fullfilename := Path + "\\" + file.Name()
fileContent := FileContent(Fullfilename)
AppendGetMap(GetDic, file.Name(), fileContent)
AppendPostMap(PostDic, file.Name(), fileContent)
}
return
}
func AppendGetMap(GetDic map[string][]string,filename string , fileContent []byte) {
GetDic[filename] = GetParams(fileContent)
}
func AppendPostMap(PostDic map[string][]string,filename string, fileContent []byte) {
PostDic[filename] = PostParams(fileContent)
}
func FileContent(filename string) []byte {
TmpFile, err := os.Open(filename)
if err != nil {
panic(err)
}
defer TmpFile.Close()
content, err := ioutil.ReadAll(TmpFile)
return content
}
func GetParams(content []byte) (paras []string) {
reg1 := regexp.MustCompile("\\$_GET\\['.*?']")
paras1 := reg1.FindAllStringSubmatch(string(content), -1)
for _, i := range paras1 {
param := strings.ReplaceAll(i[0], "$_GET['", "")
param = strings.ReplaceAll(param, "']", "")
paras = append(paras, param)
}
return
}
func PostParams(content []byte) (paras []string) {
reg1 := regexp.MustCompile("\\$_POST\\['.*?']")
paras1 := reg1.FindAllStringSubmatch(string(content), -1)
for _, i := range paras1 {
param := strings.ReplaceAll(i[0], "$_POST['", "")
param = strings.ReplaceAll(param, "']", "")
paras = append(paras, param)
}
return
}