@
概述
定义
Go 官网地址 https://golang.google.cn/ 最新版本1.20.3
Go 官网文档地址 https://golang.google.cn/doc/
Go 源码地址 https://github.com/golang
Golang简称Go,是谷歌开源的编程语言,旨在提供程序员编程效率,易于学习,非常适合团队使用,天然支持高并发,有垃圾收集机制,且自带功能完善健壮的标准库。
Go语言表现力强、简洁、干净、高效。它的并发机制使得其容易编写出充分利用多核和网络机器的程序,其类型系统使程序构造变得灵活和模块化。Go可以快速编译为机器码,并且具有垃圾收集的便利性和运行时反射的强大功能;是一种快速的、静态类型的编译语言,感觉像是一种动态类型的解释语言。
Go语言由于来自全球技术大厂谷歌创造及推动,其生态发展极其迅速,从业界声音看大有可能成为接下来10年内最具统治力的语言,也即是替代Java霸主地位,至于未来是否可以静待结果。至少从目前国内大厂阿里、腾讯、百度、字节的使用趋势极其效应可以看到其迅速扩张的可能,越受开发者喜爱其生态完整就会越好,如果从事企业级开发的伙伴有时间精力建议的话不烦可以开始深入学学Go语言开发。
使用场景
- 云上和网络的应用:如云计算领域、区块链、并发网络编程,有主要的云提供商强大的工具和api生态系统,用Go构建服务很容易。下面列举小部分流行包:
- cloud.google.com/go
- aws/client
- Azure/azure-sdk-for-go
- Cli命令行接口:cli是纯文本的。云和基础设施应用程序主要基于cli,这样易于自动化和远程功能。使用流行的开放源码包和健壮的标准库,可以使用Go创建快速而优雅的cli。下面列举小部分流行包:
- spf13/cobra
- spf13/viper
- urfave/cli
- delve
- chzyer/readline
- Web开发:Go支持快速和可扩展的web应用程序。下面列举小部分流行包:
- net/http
- html/template
- flosch/pongo2
- database/sql
- elastic/go-elasticsearch
- 开发运营和网站可靠性工程(云原生运维方向,特别是基于k8s的运维开发):Go拥有快速的构建时间、简洁的语法、自动格式化器和文档生成器,可以同时支持DevOps和SRE。下面列举小部分流行包:
- open-telemetry/opentelemetry-go
- istio/istio
- urfave/cli
Go 安全
Go语言编写安全可靠的软件,主要有如下信息:
- Go安全策略:解释了如何向Go团队报告Go标准库和子库中的安全问题。
- Go安全发布:Go版本历史包含了过去安全问题的发布说明。根据发布策略发布了两个最新的Go主要版本的安全补丁。
- Go漏洞管理:支持帮助开发人员找到可能影响其Go项目的已知公共漏洞。
- Go Fuzzing:提供一种自动测试,持续地操作程序的输入以发现错误。
- Go加密:Go加密库是Go标准库和子库中的crypto/…和golang.org/x/crypto/…包,并遵循这些原则开发。
使用须知
搜索工具
先安装最新版本的Go。有关下载和安装Go编译器、工具和库的说明,请自行查找安装文档,版本下载(1.20.3),https://golang.google.cn/dl/

由于之前使用1.20.2安装,安装后查看版本

开发过程有需要了解相当包的使用,可以在Go开发官网上查询,这也是一个良好学习和使用方式。

IDE集成开发工具可以选择JetBrains的GoLand,一个扩展支持JavaScript, TypeScript和数据库的Go IDE,JetBrains Go地址
Go基础命令
# 通过命令行输入go可以查看支持命令 go go help build

- go bug:打开Bug报告。
- go build:用于编译指定的源码文件或代码包以及它们的依赖包。常用
- go clean:移除当前源码包和关联源码包里面编译生成的文件,即删除掉执行其它命令时产生的一些文件和目录,go 命令会在临时目录中构建对象,因此 go clean 主要关注其他工具或手动调用 go build 留下的对象文件。常用
- go doc:打印与由其参数(包、const、func、类型、var、方法或结构字段)标识的项目相关联的文档注释。
- go env:打印Go语言的环境信息。
- go fix:把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
- go fmt:go程序格式化,自动对齐、空格等。如果用了IDE这个命令就不需要了。
- go generate:⽣成由现有⽂件中的指令描述的运⾏命令。这些命令可以运⾏任何进程,但⽬的是创建或更新 Go 源文件。
- go get:根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。常用
- go install:用于编译并安装指定的代码包及它们的依赖包。常用
- go list:列出指定的代码包的信息。
- go mod
- go mod init:生成go.mod文件。常用
- go mod download :下载go.mod文件中指明的所有依赖。
- go mod tidy:整理现有的依赖。常用
- go mod graph:查看现有的依赖结构。
- go mod edit:编辑go.mod文件。
- go mod vendor :导出项目所有的依赖到vendor目录。
- go mod verify:校验一个模块是否被篡改过。
- go mod why:查看为什么需要依赖某模块。
- go work:跨文件目录操作本地包、多个mod模块包调用、本地测试。
- go run:可以编译并运行命令源码文件,并把编译后的可执行文件存放到临时工作目录。常用
- go test:用于以代码包为单位对Go语言编写的程序进行测试。常用
- go tool:执行go自带的工具。go tool pprof对cpu、内存和协程进行监控;go tool trace跟踪协程的执行过程。
- go version:打印 Go 可执⾏⽂件的构建信息。
- go vet:用于检查Go语言源码中静态错误的简单工具。
标准库
目前官方提供190多种标准库包(包括cmd包),涵盖大部分的使用场景,如压缩、加密、数据库、编码、异常、画图、打印、IO操作、网络、操作系统、反射HTML模版等,可以先大概看下官方每个包功能大体说明,需要使用某些功能细节时再具体查看官网。相对于标准库,其他包也称为子库,这些包也是Go项目的一部分,但在Go主干之外。它们是在比Go内核更宽松的兼容性要求下开发的。可以用“go get”安装它们。
基础语法
Effective Go 概览
Go是一门新语言,尽管它借鉴了现有语言的思想,但它具有不同寻常的特性,使有效的Go程序与用其相关语言编写的程序在性质上有所不同。直接将c++或Java程序翻译成Go不太可能产生令人满意的结果。重要的是要先了解它的性质和习惯用法,了解用Go编程的既定约定比如命名、格式、程序结构等等。比如拿分号(Semicolons)来说,像C一样,Go的形式语法使用分号来终止语句,但与C不同的是,这些分号不会出现在源代码中。相反,词法分析器使用一个简单的规则在扫描时自动插入分号,因此输入文本基本上没有分号,也即是在Go语言中不需要采用分号结尾。

命名规范
Go语言核心宗旨就是简洁,在Go语言的相关命名也是推荐如此,命名规则涉及变量、常量、全局函数、结构、接口、方法等的命名,从语法层面进行了以下限定;任何需要对外暴露的名字必须以大写字母开头类似public,不需要对外暴露的则应该以小写字母开头类似private。
- 一般命名:推荐驼峰命名方式,如userManger或UserManager;单词缩写默认全大写或全小写,如userID、baiduCDN、id、cdn。
- 项目名:小写,多个单词建议采用中划线分隔,比如github.com/gin-gonic。
- 包名:package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写,统一使用单数形式。如domain、main。
- 文件名:文件命名一律采用小写,见名思义,测试文件以test.go结尾,如stringutil.go, stringutil_test.go。
- Local变量:保持简短;如索引采用i而不采用index,reader简写r,buffer简写为b;避免冗余,命名不要和上下文重叠,在方法体内变量名count 会比runeCount更简洁,在map的上下文下可以简写:v,ok=m[k]。
func RuneCount(b []byte) int { count := 0 for i := 0; i < len(b); { if b[i] < RuneSelf { i++ } else { _, n := DecodeRune(b[i:]) i += n } count++ } return count } func Read(b *Buffer, p []byte) (n int, err error) { if b.empty() { b.Reset() } n = copy(p, b.buf[b.off:]) b.off += n return n, nil }
- 结构体:名词或名词短语,如Account、Book,避免使用Manager这样的。 如果该数据结构需要序列化,如json, 则首字母大写, 包括里面的字段。
type Host struct { Port string `json:"port"` Address string `json:"address"` }
- 接口:接口单个函数的接口名以 er 为后缀,两个函数的接口名可以综合两个函数名,三个以上函数的接口名类似于结构体名。
type Reader interface { Read(p []byte) (n int, err error) } type WriteFlusher interface { Write([]byte) (int, error) Flush() error } type Car interface { Start() Stop() Drive() }
- 方法:动词或动词短语;如果是结构体方法,那么 Receiver 的名称应该缩写, 要么都使用值, 要么都用指针;一般用一个或两个字母(优先r);对于Receiver命名应该统一, 要么都使用值, 要么都用指针,如果 Receiver 是指针可统一使用p。
func (b *Buffer) Read(p []byte) (n int, err error){ } func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request){ } func (r Rectangle) Size() Point{ } func (f foo) method() { } func (p *foo) method() { }
Go没对get/set方法特别支持,必要的时候可以自己定义,Go对get有不同建议,如
// 推荐 p.FirstName() // 不推荐 p.GetFirstName()
- error:自定义error命名通常以 “名称+Error” 作为结构体的名字,变量时会用简写err + 名称。
type TypeError struct { Errors []string } ErrShortDst = errors.New("transform: short destination buffer")
- 常用缩写
src = source srv = server arg = argument conn = connect, connection attr = attribute abs = absolute min = minimum len = length auth = authenticate buf = buffer ctl = control ctx = context str = string msg = message fmt = format dest = destination diff = difference orig = original recv = receive ref = reference repo = repository util = utility
注释
Go提供C风格的/* */块注释和c++风格的//行注释。行注释是常态;块注释主要作为包注释出现,但在表达式中或禁用大量代码时很有用。
变量
在Go语言中,变量被显式声明并被编译器用来检查函数调用的类型正确性;var声明1个或多个变量。
package main import "fmt" func main() { var a = "initial" fmt.Println(a) var b, c int = 1, 2 fmt.Println(b, c) var d = true fmt.Println(d) var e int fmt.Println(e) f := "apple" fmt.Println(f) }

常量(const)
Go支持字符常量、字符串常量、布尔常量和数值常量;const声明一个常数值。
package mainimport ( "fmt" "math")const s string = "constant"func main() { fmt.Println(s) const n = 500000000 const d = 3e20 / n fmt.Println(d) fmt.Println(int64(d)) fmt.Println(math.Sin(n))}

控制结构
- 循环(For):for是Go唯一的循环结构。
package mainimport "fmt"func main() { i := 1 for i <= 3 { fmt.Println(i) i = i + 1 } for j := 7; j <= 9; j++ { fmt.Println(j) } for { fmt.Println("loop") break } for n := 0; n <= 5; n++ { if n%2 == 0 { continue } fmt.Println(n) }}

- 选择(If/Else):在Go中没有三元if,所以即使对于基本条件,也需要使用完整的if语句。
package mainimport "fmt"func main() { if 7%2 == 0 { fmt.Println("7 is even") } else { fmt.Println("7 is odd") } if 8%4 == 0 { fmt.Println("8 is divisible by 4") } if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") }}

- Switch:跨多个分支表达条件。
package mainimport ( "fmt" "time")func main() { i := 2 fmt.Print("Write ", i, " as ") switch i { case 1: fmt.Println("one") case 2: fmt.Println("two") case 3: fmt.Println("three") } switch time.Now().Weekday() { case time.Saturday, time.Sunday: fmt.Println("It's the weekend") default: fmt.Println("It's a weekday") } t := time.Now() switch { case t.Hour() < 12: fmt.Println("It's before noon") default: fmt.Println("It's after noon") } whatAmI := func(i interface{}) { switch t := i.(type) { case bool: fmt.Println("I'm a bool") case int: fmt.Println("I'm an int") default: fmt.Printf("Don't know type %Tn", t) } } whatAmI(true) whatAmI(1) whatAmI("hey")}

数据类型
- 数组(Arrays):数组是特定长度的元素的编号序列。单在典型的Go代码中,切片更为常见,后面介绍。
package mainimport "fmt"func main() { var a [5]int fmt.Println("emp:", a) a[4] = 100 fmt.Println("set:", a) fmt.Println("get:", a[4]) fmt.Println("len:", len(a)) b := [5]int{1, 2, 3, 4, 5} fmt.Println("dcl:", b) var twoD [2][3]int for i := 0; i < 2; i++ { for j := 0; j < 3; j++ { twoD[i][j] = i + j } } fmt.Println("2d: ", twoD)}

- 切片(Slices):是Go中的一种重要数据类型,它为序列提供了比数组更强大的接口。
package mainimport "fmt"func main() { s := make([]string, 3) fmt.Println("emp:", s) s[0] = "a" s[1] = "b" s[2] = "c" fmt.Println("set:", s) fmt.Println("get:", s[2]) fmt.Println("len:", len(s)) s = append(s, "d") s = append(s, "e", "f") fmt.Println("apd:", s) c := make([]string, len(s)) copy(c, s) fmt.Println("cpy:", c) l := s[2:5] fmt.Println("sl1:", l) l = s[:5] fmt.Println("sl2:", l) l = s[2:] fmt.Println("sl3:", l) t := []string{"g", "h", "i"} fmt.Println("dcl:", t) twoD := make([][]int, 3) for i := 0; i < 3; i++ { innerLen := i + 1 twoD[i] = make([]int, innerLen) for j := 0; j < innerLen; j++ { twoD[i][j] = i + j } } fmt.Println("2d: ", twoD)}

- 映射(Maps):Go内置的关联数据类型,在其他语言中有时称为哈希或字典。
package mainimport "fmt"func main() { m := make(map[string]int) m["k1"] = 7 m["k2"] = 13 fmt.Println("map:", m) v1 := m["k1"] fmt.Println("v1:", v1) v3 := m["k3"] fmt.Println("v3:", v3) fmt.Println("len:", len(m)) delete(m, "k2") fmt.Println("map:", m) _, prs := m["k2"] fmt.Println("prs:", prs) n := map[string]int{"foo": 1, "bar": 2} fmt.Println("map:", n)}

迭代(range)
Range遍历各种数据结构中的元素
package mainimport "fmt"func main() { nums := []int{2, 3, 4} sum := 0 for _, num := range nums { sum += num } fmt.Println("sum:", sum) for i, num := range nums { if num == 3 { fmt.Println("index:", i) } } kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %sn", k, v) } for k := range kvs { fmt.Println("key:", k) } for i, c := range "go" { fmt.Println(i, c) }}

函数
Go函数返回值需要显式返回,即它不会自动返回最后一个表达式的值。有多个相同类型的连续参数时可以省略类型相似的参数的类型名称,直到声明该类型的最后一个参数。
package main import "fmt" // 多个参数 func plus(a int, b int) int { return a + b } // 多个相同类型参数 func plusPlus(a, b, c int) int { return a + b + c } // 多个返回值 func vals() (int, int) { return 3, 7 } // 可变参数 func sum(nums ...int) { fmt.Print(nums, " ") total := 0 for _, num := range nums { total += num } fmt.Println(total) } // 闭包,Go支持匿名函数,它可以形成闭包。当想要内联定义一个函数而不必命名它时可以使用匿名函数很。 func intSeq() func() int { i := 0 return func() int { i++ return i } } // 递归函数 func fact(n int) int { if n == 0 { return 1 } return n * fact(n-1) } func main() { res := plus(1, 2) fmt.Println("1+2 =", res) res = plusPlus(1, 2, 3) fmt.Println("1+2+3 =", res) a, b := vals() fmt.Println(a) fmt.Println(b) _, c := vals() fmt.Println(c) sum(1, 2) sum(1, 2, 3) nums := []int{1, 2, 3, 4} sum(nums...) nextInt := intSeq() fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) newInts := intSeq() fmt.Println(newInts()) fmt.Println(fact(7)) var fib func(n int) int fib = func(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) } fmt.Println(fib(7)) }

指针
Go支持指针,允许在程序中传递对值和记录的引用。
package main import "fmt" func zeroval(ival int) { ival = 0 } func zeroptr(iptr *int) { *iptr = 0 } func main() { i := 1 fmt.Println("initial:", i) zeroval(i) fmt.Println("zeroval:", i) zeroptr(&i) fmt.Println("zeroptr:", i) fmt.Println("pointer:", &i) }
字符串和符文
Go字符串是字节的只读切片。该语言和标准库将字符串特殊地视为UTF-8编码文本的容器。在其他语言中,字符串是由“字符”组成的。在go中,字符的概念被称为符文——它是一个表示Unicode码点的整数。
package main import ( "fmt" "unicode/utf8" ) func main() { const s = "สวัสดี" fmt.Println("Len:", len(s)) for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) } fmt.Println() fmt.Println("Rune count:", utf8.RuneCountInString(s)) for idx, runeValue := range s { fmt.Printf("%#U starts at %dn", runeValue, idx) } fmt.Println("nUsing DecodeRuneInString") for i, w := 0, 0; i < len(s); i += w { runeValue, width := utf8.DecodeRuneInString(s[i:]) fmt.Printf("%#U starts at %dn", runeValue, i) w = width examineRune(runeValue) } } func examineRune(r rune) { if r == 't' { fmt.Println("found tee") } else if r == 'ส' { fmt.Println("found so sua") } }

结构体(struct)
Go的结构体是字段的类型化集合,将数据分组形成记录;结构体是可变的。
package main import "fmt" type person struct { name string age int } func newPerson(name string) *person { p := person{name: name} p.age = 42 return &p } func main() { fmt.Println(person{"Bob", 20}) fmt.Println(person{name: "Alice", age: 30}) fmt.Println(person{name: "Fred"}) fmt.Println(&person{name: "Ann", age: 40}) fmt.Println(newPerson("Jon")) s := person{name: "Sean", age: 50} fmt.Println(s.name) sp := &s fmt.Println(sp.age) sp.age = 51 fmt.Println(sp.age) }

方法
Go支持在结构类型上定义的方法。方法的接受者可以是结构体也可以是指向结构体的指针。
package mainimport "fmt"type rect struct { width, height int}func (r *rect) area() int { return r.width * r.height}func (r rect) perim() int { return 2*r.width + 2*r.height}func main() { r := rect{width: 10, height: 5} fmt.Println("area: ", r.area()) fmt.Println("perim:", r.perim()) rp := &r fmt.Println("area: ", rp.area()) fmt.Println("perim:", rp.perim())}

接口(interface)
接口是方法签名的命名集合。
package mainimport ( "fmt" "math")type geometry interface { area() float64 perim() float64}type rect struct { width, height float64}type circle struct { radius float64}func (r rect) area() float64 { return r.width * r.height}func (r rect) perim() float64 { return 2*r.width + 2*r.height}func (c circle) area() float64 { return math.Pi * c.radius * c.radius}func (c circle) perim() float64 { return 2 * math.Pi * c.radius}func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim())}func main() { r := rect{width: 3, height: 4} c := circle{radius: 5} measure(r) measure(c)}

Go支持嵌入结构体和接口,以表达更丰富的类型组合。
package mainimport "fmt"type base struct { num int}func (b base) describe() string { return fmt.Sprintf("base with num=%v", b.num)}type container struct { base str string}func main() { co := container{ base: base{ num: 1, }, str: "some name", } fmt.Printf("co={num: %v, str: %v}n", co.num, co.str) fmt.Println("also num:", co.base.num) fmt.Println("describe:", co.describe()) type describer interface { describe() string } var d describer = co fmt.Println("describer:", d.describe())}

泛型
Go 1.18 引入了泛型,为 Go 语言带来了更强大的类型系统,使其可以更好地支持各种数据类型和算法。Go 泛型可以应用于各种数据类型和算法,使代码更加通用、简洁、易读和易维护。
package mainimport "fmt"func MapKeys[K comparable, V any](m map[K]V) []K { r := make([]K, 0, len(m)) for k := range m { r = append(r, k) } return r}type List[T any] struct { head, tail *element[T]}type element[T any] struct { next *element[T] val T}func (lst *List[T]) Push(v T) { if lst.tail == nil { lst.head = &element[T]{val: v} lst.tail = lst.head } else { lst.tail.next = &element[T]{val: v} lst.tail = lst.tail.next }}func (lst *List[T]) GetAll() []T { var elems []T for e := lst.head; e != nil; e = e.next { elems = append(elems, e.val) } return elems}func main() { var m = map[int]string{1: "2", 2: "4", 4: "8"} fmt.Println("keys:", MapKeys(m)) _ = MapKeys[int, string](m) lst := List[int]{} lst.Push(10) lst.Push(13) lst.Push(23) fmt.Println("list:", lst.GetAll())}

以下是一些使用 Go 泛型的场景:
- 集合数据类型:Go 泛型可以方便地定义各种集合数据类型,如栈、队列、链表、二叉树等。下面使用泛型定义栈的例子定义了一个 Stack[T] 类型,表示一个可以存储任意类型 T 的栈。Push 方法用于将一个元素压入栈中,Pop 方法用于弹出栈顶的元素。
- 算法实现:泛型还可以用于实现各种算法,如排序、查找、字符串匹配等。以下是一个使用泛型实现快速排序的例子:在这个例子中,使用泛型定义
QuickSort函数,可以对任意类型 T 的切片进行排序。该函数采用经典的快速排序算法实现。
错误(errors)
在Go中,习惯上通过显式的单独返回值来传达错误。这与Java和Ruby等语言中使用的异常以及c中有时使用的重载的单个结果/错误值形成对比。Go的方法可以很容易地看到哪些函数返回错误,并使用用于任何其他无错误任务的相同语言结构来处理它们。
package main import ( "errors" "fmt" ) func f1(arg int) (int, error) { if arg == 42 { return -1, errors.New("can't work with 42") } return arg + 3, nil } type argError struct { arg int prob string } func (e *argError) Error() string { return fmt.Sprintf("%d - %s", e.arg, e.prob) } func f2(arg int) (int, error) { if arg == 42 { return -1, &argError{arg, "can't work with it"} } return arg + 3, nil } func main() { for _, i := range []int{7, 42} { if r, e := f1(i); e != nil { fmt.Println("f1 failed:", e) } else { fmt.Println("f1 worked:", r) } } for _, i := range []int{7, 42} { if r, e := f2(i); e != nil { fmt.Println("f2 failed:", e) } else { fmt.Println("f2 worked:", r) } } _, e := f2(42) if ae, ok := e.(*argError); ok { fmt.Println(ae.arg) fmt.Println(ae.prob) } }

恐慌(pinic)
恐慌通常意味着事情出乎意料地出错了;大多数情况下,使用它来快速处理在正常操作中不应该发生的错误,或者没有准备好优雅地处理的错误。
package main import "os" func main() { panic("a problem") _, err := os.Create("/tmp/file") if err != nil { panic(err) } }

推迟(defer)
Defer用于确保函数调用在程序执行的后期执行,通常是出于清理目的。Defer通常用于其他语言中使用的地方,例如ensure和finally。比如用于释放或关闭连接、释放或关闭文件句柄等
package main import ( "fmt" "os" ) func main() { f := createFile("d:/tmp/defer.txt") defer closeFile(f) writeFile(f) } func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil { panic(err) } return f } func writeFile(f *os.File) { fmt.Println("writing") fmt.Fprintln(f, "data") } func closeFile(f *os.File) { fmt.Println("closing") err := f.Close() if err != nil { fmt.Fprintf(os.Stderr, "error: %vn", err) os.Exit(1) } }

恢复(recover)
通过使用内置的recover函数,Go使从恐慌中恢复成为可能。恢复可以阻止因恐慌而中止程序,而是让程序继续执行。
package main import "fmt" func mayPanic() { panic("a problem") } func main() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered. Error:n", r) } }() mayPanic() fmt.Println("After mayPanic()") }

- 本人博客网站IT小神 www.itxiaoshen.com