├── 00.go语言基础 ├── .gitignore ├── 00-基础语法 │ ├── 01-环境安装.md │ ├── 02-变量声明.md │ ├── 03-数组.md │ ├── 04-Slice 切片.md │ ├── 05-Struct 结构体.md │ ├── 06-Map 集合.md │ ├── 07-循环.md │ ├── 08-函数.md │ ├── 09-chan 通道.md │ ├── 10-defer 函数.md │ ├── 11-解析 JSON 数据.md │ ├── 12-json.Unmarshal 遇到的小坑.md │ ├── 13-结构(struct) 实现 接口(interface) .md │ ├── 14-学习 grpc.Dial(target string, opts …DialOption) 的写法.md │ ├── 15-time.RFC3339 时间格式化.md │ ├── 16-常用签名算法的基准测试.md │ ├── 17-分享两个在开发中需注意的小点.md │ ├── 18-使用 sync.Pool 来减少 GC 压力.md │ ├── 19-基于逃逸分析来提升程序性能.md │ ├── 20-使用 sync.Map 来解决 map 的并发操作问题.md │ ├── 21-使用 sync.WaitGroup 来实现并发操作 .md │ ├── codes │ │ ├── README.md │ │ ├── demo_1.go │ │ ├── demo_10.go │ │ ├── demo_11.go │ │ ├── demo_12.go │ │ ├── demo_13.go │ │ ├── demo_14.go │ │ ├── demo_15.go │ │ ├── demo_16.go │ │ ├── demo_17.go │ │ ├── demo_18.go │ │ ├── demo_19.go │ │ ├── demo_2.go │ │ ├── demo_20.go │ │ ├── demo_21.go │ │ ├── demo_22.go │ │ ├── demo_23.go │ │ ├── demo_24.go │ │ ├── demo_25.go │ │ ├── demo_26.go │ │ ├── demo_3.go │ │ ├── demo_4.go │ │ ├── demo_5.go │ │ ├── demo_6.go │ │ ├── demo_7.go │ │ ├── demo_8.go │ │ └── demo_9.go │ └── images │ │ ├── 01-环境安装 │ │ ├── 1_go_1.png │ │ ├── 1_go_2.png │ │ ├── 1_go_3.png │ │ └── 1_go_4.png │ │ ├── 02-变量声明 │ │ ├── 2_go_1.png │ │ ├── 2_go_2.png │ │ └── 2_go_3.png │ │ ├── 03-数组 │ │ ├── 3_go_1.png │ │ ├── 3_go_2.png │ │ └── 3_go_3.png │ │ ├── 04-切片 │ │ ├── 4_go_1.png │ │ ├── 4_go_2.png │ │ ├── 4_go_3.png │ │ └── 4_go_4.png │ │ ├── 05-结构体 │ │ ├── 5_go_1.png │ │ ├── 5_go_2.png │ │ └── 5_go_3.png │ │ ├── 06-集合 │ │ ├── 6_go_1.png │ │ ├── 6_go_2.png │ │ └── 6_go_3.png │ │ ├── 07-循环 │ │ ├── 7_go_1.png │ │ ├── 7_go_2.png │ │ ├── 7_go_3.png │ │ ├── 7_go_4.png │ │ ├── 7_go_5.png │ │ └── 7_go_6.png │ │ ├── 08-函数 │ │ ├── 8_go_0.png │ │ ├── 8_go_1.png │ │ ├── 8_go_2.png │ │ ├── 8_go_3.png │ │ ├── 8_go_4.png │ │ └── 8_go_5.png │ │ ├── 10_go_1.jpeg │ │ ├── 11_go_1.jpeg │ │ ├── 11_go_2.jpeg │ │ ├── 11_go_3.jpeg │ │ └── qr.jpg ├── 01-Gin框架 │ ├── 01-框架安装.md │ ├── 02-路由配置.md │ ├── 03-日志记录.md │ ├── 04-数据绑定和验证.md │ ├── 05-自定义错误处理.md │ ├── 06-统一定义 API 错误码.md │ ├── codes │ │ ├── 02-路由配置 │ │ │ ├── README.md │ │ │ └── ginDemo │ │ │ │ ├── Gopkg.lock │ │ │ │ ├── Gopkg.toml │ │ │ │ ├── common │ │ │ │ └── common.go │ │ │ │ ├── config │ │ │ │ └── config.go │ │ │ │ ├── controller │ │ │ │ ├── v1 │ │ │ │ │ ├── member.go │ │ │ │ │ └── product.go │ │ │ │ └── v2 │ │ │ │ │ ├── member.go │ │ │ │ │ └── product.go │ │ │ │ ├── main.go │ │ │ │ └── router │ │ │ │ └── router.go │ │ ├── 03-日志记录 │ │ │ ├── README.md │ │ │ └── ginDemo │ │ │ │ ├── Gopkg.lock │ │ │ │ ├── Gopkg.toml │ │ │ │ ├── common │ │ │ │ └── common.go │ │ │ │ ├── config │ │ │ │ └── config.go │ │ │ │ ├── main.go │ │ │ │ ├── middleware │ │ │ │ └── logger.go │ │ │ │ └── router │ │ │ │ ├── router.go │ │ │ │ ├── v1 │ │ │ │ ├── member.go │ │ │ │ └── product.go │ │ │ │ └── v2 │ │ │ │ ├── member.go │ │ │ │ └── product.go │ │ ├── 04-数据绑定和验证 │ │ │ ├── README.md │ │ │ └── ginDemo │ │ │ │ ├── Gopkg.lock │ │ │ │ ├── Gopkg.toml │ │ │ │ ├── common │ │ │ │ └── common.go │ │ │ │ ├── config │ │ │ │ └── config.go │ │ │ │ ├── entity │ │ │ │ ├── member.go │ │ │ │ └── result.go │ │ │ │ ├── main.go │ │ │ │ ├── middleware │ │ │ │ ├── logger │ │ │ │ │ └── logger.go │ │ │ │ └── sign │ │ │ │ │ └── sign.go │ │ │ │ ├── router │ │ │ │ ├── router.go │ │ │ │ ├── v1 │ │ │ │ │ ├── member.go │ │ │ │ │ └── product.go │ │ │ │ └── v2 │ │ │ │ │ ├── member.go │ │ │ │ │ └── product.go │ │ │ │ └── validator │ │ │ │ └── member │ │ │ │ └── member.go │ │ └── 05-自定义错误处理 │ │ │ ├── README.md │ │ │ └── ginDemo │ │ │ ├── Gopkg.lock │ │ │ ├── Gopkg.toml │ │ │ ├── common │ │ │ ├── alarm │ │ │ │ └── alarm.go │ │ │ └── function │ │ │ │ └── common.go │ │ │ ├── config │ │ │ └── config.go │ │ │ ├── entity │ │ │ ├── member.go │ │ │ └── result.go │ │ │ ├── main.go │ │ │ ├── middleware │ │ │ ├── logger │ │ │ │ └── logger.go │ │ │ ├── recover │ │ │ │ └── recover.go │ │ │ └── sign │ │ │ │ └── sign.go │ │ │ ├── router │ │ │ ├── router.go │ │ │ ├── v1 │ │ │ │ ├── member.go │ │ │ │ └── product.go │ │ │ └── v2 │ │ │ │ ├── member.go │ │ │ │ └── product.go │ │ │ └── validator │ │ │ └── member │ │ │ └── member.go │ └── images │ │ ├── 01-框架安装 │ │ └── 1_go_1.png │ │ ├── 02-路由配置 │ │ ├── 2_go_1.png │ │ ├── 2_go_2.png │ │ ├── 2_go_3.png │ │ └── 2_go_4.png │ │ └── 1_api_1.png ├── 02-Go gRPC │ ├── 01-Go gRPC Hello World.md │ ├── 02-Go gRPC 调试工具.md │ ├── codes │ │ └── 01-gRPC Hello World │ │ │ ├── README.md │ │ │ ├── go_client │ │ │ ├── main.go │ │ │ └── proto │ │ │ │ └── hello │ │ │ │ └── hello.pb.go │ │ │ └── go_server │ │ │ ├── controller │ │ │ └── hello_controller │ │ │ │ └── hello_server.go │ │ │ ├── main.go │ │ │ └── proto │ │ │ └── hello │ │ │ ├── hello.pb.go │ │ │ └── hello.proto │ └── images │ │ ├── 2_grpc_1.gif │ │ └── 2_grpc_2.gif ├── 03-go-gin-api [文档] │ ├── 01-[go-gin-api] 使用 go modules 初始化项目.md │ ├── 02-[go-gin-api] 规划项目目录和参数验证.md │ ├── 03-[go-gin-api] 路由中间件 - 日志记录.md │ ├── 04-[go-gin-api] 路由中间件 - 捕获异常.md │ ├── 05-[go-gin-api] 路由中间件 - 链路追踪(Jaeger).md │ ├── 06-[go-gin-api] 路由中间件 - 链路追踪(Jaeger)实战.md │ ├── 07-[go-gin-api] 路由中间件 - 签名验证.md │ └── images │ │ ├── 1_api_1.png │ │ ├── 1_api_2.png │ │ ├── 2_api_1.png │ │ ├── 3_api_1.png │ │ ├── 4_api_1.png │ │ ├── 4_api_2.png │ │ ├── 4_api_3.png │ │ ├── 4_api_4.png │ │ ├── 4_api_5.jpeg │ │ ├── 5_api_1.png │ │ ├── 5_api_10.jpeg │ │ ├── 5_api_2.png │ │ ├── 5_api_3.png │ │ ├── 5_api_4.jpeg │ │ ├── 5_api_5.jpeg │ │ ├── 5_api_6.png │ │ ├── 5_api_7.png │ │ ├── 5_api_8.png │ │ ├── 5_api_9.jpg │ │ ├── 6_api_1.png │ │ ├── 6_api_2.png │ │ ├── 6_api_3.png │ │ ├── 6_api_4.png │ │ ├── 6_api_5.png │ │ ├── 6_api_6.png │ │ ├── 7_api_1.png │ │ ├── 7_api_2.gif │ │ ├── go-gin-api-logo.png │ │ ├── jaeger_demo_1.png │ │ ├── jaeger_demo_2.png │ │ ├── jaeger_demo_3.png │ │ └── jaeger_demo_4.png └── README.md ├── 01.区块链概念简介.md ├── 02.了解如何使用Solidity.md ├── 03.使用Solidity 编写 Ethereum智能合同.md ├── 04.使用OpenZeppelin创建代币.md ├── 05.以太坊网络基础知识.md ├── 06.加密钱包的使用.md ├── 07.连接并部署到 Ethereum网络.md ├── 08.区块链概念简介作业题.md ├── 09.Solidity 经典用例.md ├── 10.以太坊基础知识作业题.md ├── 11.加密钱包和网络的使用作业题.md ├── README.md ├── pic ├── 1 │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── 3 │ ├── 1.jpg │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ └── 8.png ├── 4 │ ├── 1.png │ ├── 2.png │ └── 3.png ├── 5 │ └── 1.PNG ├── 6 │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.jpg │ ├── 19.jpg │ ├── 2.jpg │ ├── 20.jpg │ ├── 21.jpg │ ├── 22.jpg │ ├── 23.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg └── 7 │ ├── 1.png │ ├── 2.png │ ├── 3.jpg │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ └── 8.png └── static ├── CDzabZ4I5owzCkxYJXGcb6PSnby.png ├── DHMkbei1ZoZqakxFOskcrrQknMg.png ├── G01Pbyh9voKfrPxdNnwcFRMfnGg.png ├── KC60bGhqcoocedx5XkbcWKFKnic.png ├── OYw4b8uCtoT5qpxxiXTcuYOPnzn.png ├── Pn3Qbeuxkog7KixfIe4cciKKnVd.png ├── QduabrtzaofYqlxd1SkcxWSmnje.png ├── Sknib4l1roybGpx7FJpcwwkVnJe.jpg ├── TGeUbEqQKoVRjnx2DCvcRAL1nCb.png ├── W43KbYjuKozv7hxXhMecCepFnJb.png ├── YD7Vb8kNeo2k8KxdOxTcHGvXnTe.png ├── YbGvbI8TloGRANx7grKck2CpnSd.png ├── YpfpbfnajoiZcsxPG6bcp5rfnPd.png └── Yt4Sbw7I1oudozxTn3ocnmRMnxg.png /00.go语言基础/.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/01-环境安装.md: -------------------------------------------------------------------------------- 1 | ## 你好,Go语言 2 | 3 | > Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。 4 | 5 | 因工作需要,准备入坑,先从环境安装开始,输出一个 Hello World。 6 | 7 | ## 环境安装 8 | 9 | **目标** 10 | 11 | 安装完成并运行 Hello World 成功! 12 | 13 | 本机系统:macOS High Sierra 10.13.4 14 | 15 | Go 版本:1.12 16 | 17 | 18 | **方式一:** 19 | 20 | 通过 brew 安装 21 | 22 | ``` 23 | brew install go 24 | ``` 25 | 26 | 根据提示进行安装吧,我使用的 方式二 进行安装的。 27 | 28 | 29 | **方式二:** 30 | 31 | 通过安装包安装 32 | 33 | 地址:https://dl.google.com/go/go1.12.darwin-amd64.pkg 34 | 35 | 下载之后直接点击安装,一步步继续即可。 36 | 37 | 38 | **配置环境变量** 39 | 40 | ``` 41 | vi ~/.bashrc 42 | 43 | //新增 44 | export GOROOT=/usr/local/go 45 | export GOPATH=/Users/username/go/code //代码目录,自定义即可 46 | export PATH=$PATH:$GOPATH/bin 47 | ``` 48 | 49 | 及时生效,请执行命令:source ~/.bashrc 50 | 51 | **如果命令行使用的是zsh,请修改 .zshrc 文件。** 52 | 53 | ``` 54 | vi ~/.zshrc 55 | 56 | //新增 57 | export GOROOT=/usr/local/go 58 | export GOPATH=/Users/username/go/code //自定义代码目录 59 | export PATH=$PATH:$GOPATH/bin 60 | ``` 61 | 62 | 及时生效,请执行命令:source ~/.zshrc 63 | 64 | 验证是否安装成功,命令行下执行: 65 | 66 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/01-环境安装/1_go_1.png) 67 | 68 | ## 目录结构 69 | 70 | **bin** 71 | 72 | 存放编译后可执行的文件。 73 | 74 | **pkg** 75 | 76 | 存放编译后的应用包。 77 | 78 | **src** 79 | 80 | 存放应用源代码。 81 | 82 | 例如: 83 | 84 | ``` 85 | ├─ code -- 代码根目录 86 | │ ├─ bin 87 | │ ├─ pkg 88 | │ ├─ src 89 | │ ├── hello 90 | │ ├── hello.go 91 | ``` 92 | 93 | **Hello World 代码** 94 | 95 | ``` 96 | //在 hello 目录下创建 hello.go 97 | 98 | package main 99 | 100 | import ( 101 | "fmt" 102 | ) 103 | 104 | func main() { 105 | fmt.Println("Hello World!") 106 | } 107 | ``` 108 | 109 | 命令行执行: 110 | 111 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/01-环境安装/1_go_2.png) 112 | 113 | ## 命令 114 | 115 | 查看完整的命令: 116 | 117 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/01-环境安装/1_go_3.png) 118 | 119 | **go build hello** 120 | 121 | 在src目录或hello目录下执行 go build hello,只在对应当前目录下生成文件。 122 | 123 | **go install hello** 124 | 125 | 在src目录或hello目录下执行 go install hello,会把编译好的结果移动到 $GOPATH/bin。 126 | 127 | **go run hello** 128 | 129 | 在src目录或hello目录下执行 go run hello,不生成任何文件只运行程序。 130 | 131 | **go fmt hello** 132 | 133 | 在src目录或hello目录下执行 go run hello,格式化代码,将代码修改成标准格式。 134 | 135 | 其他命令,需要的时候再进行研究吧。 136 | 137 | ## 开发工具 138 | 139 | **GoLand** 140 | 141 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/01-环境安装/1_go_4.png) 142 | 143 | GoLand 是 JetBrains 公司推出的 Go 语言集成开发环境,与我们用的 WebStorm、PhpStorm、PyCharm 是一家,同样支持 Windows、Linux、macOS 等操作系统。 144 | 145 | 下载地址:https://www.jetbrains.com/go/ 146 | 147 | 软件是付费的,不过想想办法,软件可以永久激活的。 148 | 149 | ## 学习网址 150 | 151 | - Go语言:https://golang.org/ 152 | - Go语言中文网:https://studygolang.com/ 153 | - Go语言包管理:https://gopm.io/ -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/02-变量声明.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 在声明变量之前,咱们先了解下变量的数据类型,这篇文章主要涉及 字符串、布尔、数字,其他类型后面开篇再说。 4 | 5 | ## 数据类型 6 | 7 | #### 字符串 8 | 9 | `string` 10 | 11 | 只能用一对双引号("")或反引号(``)括起来定义,不能用单引号('')定义! 12 | 13 | #### 布尔 14 | 15 | `bool` 16 | 17 | 只有 true 和 false,默认为 false。 18 | 19 | #### 数字 20 | 21 | **整型** 22 | 23 | `int8` `uint8` 24 | 25 | `int16` `uint16` 26 | 27 | `int32` `uint32` 28 | 29 | `int64` `uint64` 30 | 31 | `int` `uint`,具体长度取决于 CPU 位数。 32 | 33 | **浮点型** 34 | 35 | `float32` `float64` 36 | 37 | ## 常量声明 38 | 39 | **常量**,在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。 40 | 41 | **单个常量声明** 42 | 43 | 第一种:const 变量名称 数据类型 = 变量值 44 | 45 | 如果不赋值,使用的是该数据类型的默认值。 46 | 47 | 第二种:const 变量名称 = 变量值 48 | 49 | 根据变量值,自行判断数据类型。 50 | 51 | **多个常量声明** 52 | 53 | 第一种:const 变量名称,变量名称 ... ,数据类型 = 变量值,变量值 ... 54 | 55 | 第二种:const 变量名称,变量名称 ... = 变量值,变量值 ... 56 | 57 | **测试代码** 58 | 59 | ``` 60 | //demo_1.go 61 | package main 62 | 63 | import ( 64 | "fmt" 65 | ) 66 | 67 | func main() { 68 | const name string = "Tom" 69 | fmt.Println(name) 70 | 71 | const age = 30 72 | fmt.Println(age) 73 | 74 | const name_1, name_2 string = "Tom", "Jay" 75 | fmt.Println(name_1, name_2) 76 | 77 | const name_3, age_1 = "Tom", 30 78 | fmt.Println(name_3, age_1) 79 | } 80 | ``` 81 | 82 | 运行结果: 83 | 84 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/02-变量声明/2_go_1.png) 85 | 86 | ## 变量声明 87 | 88 | **单个变量声明** 89 | 90 | 第一种:var 变量名称 数据类型 = 变量值 91 | 92 | 如果不赋值,使用的是该数据类型的默认值。 93 | 94 | 第二种:var 变量名称 = 变量值 95 | 96 | 根据变量值,自行判断数据类型。 97 | 98 | 第三种:变量名称 := 变量值 99 | 100 | 省略了 var 和数据类型,变量名称一定要是未声明过的。 101 | 102 | **多个变量声明** 103 | 104 | 第一种:var 变量名称,变量名称 ... ,数据类型 = 变量值,变量值 ... 105 | 106 | 第二种:var 变量名称,变量名称 ... = 变量值,变量值 ... 107 | 108 | 第三种:变量名称,变量名称 ... := 变量值,变量值 ... 109 | 110 | **测试代码** 111 | 112 | ``` 113 | //demo_2.go 114 | package main 115 | 116 | import ( 117 | "fmt" 118 | ) 119 | 120 | func main() { 121 | var age_1 uint8 = 31 122 | var age_2 = 32 123 | age_3 := 33 124 | fmt.Println(age_1, age_2, age_3) 125 | 126 | var age_4, age_5, age_6 int = 31, 32, 33 127 | fmt.Println(age_4, age_5, age_6) 128 | 129 | var name_1, age_7 = "Tom", 30 130 | fmt.Println(name_1, age_7) 131 | 132 | name_2, is_boy, height := "Jay", true, 180.66 133 | fmt.Println(name_2, is_boy, height) 134 | } 135 | ``` 136 | 137 | 运行结果: 138 | 139 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/02-变量声明/2_go_2.png) 140 | 141 | ## 输出方法 142 | 143 | **fmt.Print**:输出到控制台(仅只是输出) 144 | 145 | **fmt.Println**:输出到控制台并换行 146 | 147 | **fmt.Printf**:仅输出格式化的字符串和字符串变量(整型和整型变量不可以) 148 | 149 | **fmt.Sprintf**:格式化并返回一个字符串,不输出。 150 | 151 | 测试代码 152 | 153 | ``` 154 | //demo_3.go 155 | package main 156 | 157 | import ( 158 | "fmt" 159 | ) 160 | 161 | func main() { 162 | fmt.Print("输出到控制台不换行") 163 | fmt.Println("---") 164 | fmt.Println("输出到控制台并换行") 165 | fmt.Printf("name=%s,age=%d\n", "Tom", 30) 166 | fmt.Printf("name=%s,age=%d,height=%v\n", "Tom", 30, fmt.Sprintf("%.2f", 180.567)) 167 | } 168 | ``` 169 | 170 | 运行结果: 171 | 172 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/02-变量声明/2_go_3.png) 173 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/03-数组.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成,一旦声明了,数组的长度就固定了,不能动态变化。 4 | 5 | `len()` 和 `cap()` 返回结果始终一样。 6 | 7 | ## 声明数组 8 | 9 | ``` 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | ) 15 | 16 | func main() { 17 | //一维数组 18 | var arr_1 [5] int 19 | fmt.Println(arr_1) 20 | 21 | var arr_2 = [5] int {1, 2, 3, 4, 5} 22 | fmt.Println(arr_2) 23 | 24 | arr_3 := [5] int {1, 2, 3, 4, 5} 25 | fmt.Println(arr_3) 26 | 27 | arr_4 := [...] int {1, 2, 3, 4, 5, 6} 28 | fmt.Println(arr_4) 29 | 30 | arr_5 := [5] int {0:3, 1:5, 4:6} 31 | fmt.Println(arr_5) 32 | 33 | //二维数组 34 | var arr_6 = [3][5] int {{1, 2, 3, 4, 5}, {9, 8, 7, 6, 5}, {3, 4, 5, 6, 7}} 35 | fmt.Println(arr_6) 36 | 37 | arr_7 := [3][5] int {{1, 2, 3, 4, 5}, {9, 8, 7, 6, 5}, {3, 4, 5, 6, 7}} 38 | fmt.Println(arr_7) 39 | 40 | arr_8 := [...][5] int {{1, 2, 3, 4, 5}, {9, 8, 7, 6, 5}, {0:3, 1:5, 4:6}} 41 | fmt.Println(arr_8) 42 | } 43 | ``` 44 | 45 | 运行结果: 46 | 47 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/03-数组/3_go_1.png) 48 | 49 | ## 注意事项 50 | 51 | 一、数组不可动态变化问题,一旦声明了,其长度就是固定的。 52 | 53 | ``` 54 | var arr_1 = [5] int {1, 2, 3, 4, 5} 55 | arr_1[5] = 6 56 | fmt.Println(arr_1) 57 | ``` 58 | 运行会报错:invalid array index 5 (out of bounds for 5-element array) 59 | 60 | 二、数组是值类型问题,在函数中传递的时候是传递的值,如果传递数组很大,这对内存是很大开销。 61 | 62 | ``` 63 | //demo_5.go 64 | package main 65 | 66 | import ( 67 | "fmt" 68 | ) 69 | 70 | func main() { 71 | var arr = [5] int {1, 2, 3, 4, 5} 72 | modifyArr(arr) 73 | fmt.Println(arr) 74 | } 75 | 76 | func modifyArr(a [5] int) { 77 | a[1] = 20 78 | } 79 | ``` 80 | 81 | 运行结果: 82 | 83 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/03-数组/3_go_2.png) 84 | 85 | ``` 86 | //demo_6.go 87 | package main 88 | 89 | import ( 90 | "fmt" 91 | ) 92 | 93 | func main() { 94 | var arr = [5] int {1, 2, 3, 4, 5} 95 | modifyArr(&arr) 96 | fmt.Println(arr) 97 | } 98 | 99 | func modifyArr(a *[5] int) { 100 | a[1] = 20 101 | } 102 | ``` 103 | 104 | 运行结果: 105 | 106 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/03-数组/3_go_3.png) 107 | 108 | 三、数组赋值问题,同样类型的数组(长度一样且每个元素类型也一样)才可以相互赋值,反之不可以。 109 | 110 | ``` 111 | var arr = [5] int {1, 2, 3, 4, 5} 112 | var arr_1 [5] int = arr 113 | var arr_2 [6] int = arr 114 | ``` 115 | 116 | 运行会报错:cannot use arr (type [5]int) as type [6]int in assignment -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/04-Slice 切片.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 切片是一种动态数组,比数组操作灵活,长度不是固定的,可以进行追加和删除。 4 | 5 | `len()` 和 `cap()` 返回结果可相同和不同。 6 | 7 | ## 声明切片 8 | 9 | ``` 10 | //demo_7.go 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | ) 16 | 17 | func main() { 18 | var sli_1 [] int //nil 切片 19 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_1),cap(sli_1),sli_1) 20 | 21 | var sli_2 = [] int {} //空切片 22 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_1),cap(sli_2),sli_2) 23 | 24 | var sli_3 = [] int {1, 2, 3, 4, 5} 25 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_3),cap(sli_3),sli_3) 26 | 27 | sli_4 := [] int {1, 2, 3, 4, 5} 28 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_4),cap(sli_4),sli_4) 29 | 30 | var sli_5 [] int = make([] int, 5, 8) 31 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_5),cap(sli_5),sli_5) 32 | 33 | sli_6 := make([] int, 5, 9) 34 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_6),cap(sli_6),sli_6) 35 | } 36 | ``` 37 | 38 | 运行结果: 39 | 40 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/04-切片/4_go_1.png) 41 | 42 | ## 截取切片 43 | 44 | ``` 45 | //demo_8.go 46 | package main 47 | 48 | import ( 49 | "fmt" 50 | ) 51 | 52 | func main() { 53 | sli := [] int {1, 2, 3, 4, 5, 6} 54 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 55 | 56 | fmt.Println("sli[1] ==", sli[1]) 57 | fmt.Println("sli[:] ==", sli[:]) 58 | fmt.Println("sli[1:] ==", sli[1:]) 59 | fmt.Println("sli[:4] ==", sli[:4]) 60 | 61 | fmt.Println("sli[0:3] ==", sli[0:3]) 62 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[0:3]),cap(sli[0:3]),sli[0:3]) 63 | 64 | fmt.Println("sli[0:3:4] ==", sli[0:3:4]) 65 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[0:3:4]),cap(sli[0:3:4]),sli[0:3:4]) 66 | } 67 | ``` 68 | 69 | 运行结果: 70 | 71 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/04-切片/4_go_2.png) 72 | 73 | ## 追加切片 74 | 75 | ``` 76 | //demo_9.go 77 | package main 78 | 79 | import ( 80 | "fmt" 81 | ) 82 | 83 | func main() { 84 | sli := [] int {4, 5, 6} 85 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 86 | 87 | sli = append(sli, 7) 88 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 89 | 90 | sli = append(sli, 8) 91 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 92 | 93 | sli = append(sli, 9) 94 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 95 | 96 | sli = append(sli, 10) 97 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 98 | } 99 | ``` 100 | 101 | 运行结果: 102 | 103 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/04-切片/4_go_3.png) 104 | 105 | append 时,容量不够需要扩容时,cap 会翻倍。 106 | 107 | ## 删除切片 108 | 109 | ``` 110 | //demo_10.go 111 | package main 112 | 113 | import ( 114 | "fmt" 115 | ) 116 | 117 | func main() { 118 | sli := [] int {1, 2, 3, 4, 5, 6, 7, 8} 119 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 120 | 121 | //删除尾部 2 个元素 122 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[:len(sli)-2]),cap(sli[:len(sli)-2]),sli[:len(sli)-2]) 123 | 124 | //删除开头 2 个元素 125 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[2:]),cap(sli[2:]),sli[2:]) 126 | 127 | //删除中间 2 个元素 128 | sli = append(sli[:3], sli[3+2:]...) 129 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 130 | } 131 | ``` 132 | 133 | 运行结果: 134 | 135 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/04-切片/4_go_4.png) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/05-Struct 结构体.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 结构体是将零个或多个任意类型的变量,组合在一起的聚合数据类型,也可以看做是数据的集合。 4 | 5 | ## 声明结构体 6 | 7 | ``` 8 | //demo_11.go 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | type Person struct { 16 | Name string 17 | Age int 18 | } 19 | 20 | func main() { 21 | var p1 Person 22 | p1.Name = "Tom" 23 | p1.Age = 30 24 | fmt.Println("p1 =", p1) 25 | 26 | var p2 = Person{Name:"Burke", Age:31} 27 | fmt.Println("p2 =", p2) 28 | 29 | p3 := Person{Name:"Aaron", Age:32} 30 | fmt.Println("p2 =", p3) 31 | 32 | //匿名结构体 33 | p4 := struct { 34 | Name string 35 | Age int 36 | } {Name:"匿名", Age:33} 37 | fmt.Println("p4 =", p4) 38 | } 39 | ``` 40 | 运行结果: 41 | 42 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/05-结构体/5_go_1.png) 43 | 44 | ## 生成 JSON 45 | 46 | ``` 47 | //demo_12.go 48 | package main 49 | 50 | import ( 51 | "encoding/json" 52 | "fmt" 53 | ) 54 | 55 | type Result struct { 56 | Code int `json:"code"` 57 | Message string `json:"msg"` 58 | } 59 | 60 | func main() { 61 | var res Result 62 | res.Code = 200 63 | res.Message = "success" 64 | 65 | //序列化 66 | jsons, errs := json.Marshal(res) 67 | if errs != nil { 68 | fmt.Println("json marshal error:", errs) 69 | } 70 | fmt.Println("json data :", string(jsons)) 71 | 72 | //反序列化 73 | var res2 Result 74 | errs = json.Unmarshal(jsons, &res2) 75 | if errs != nil { 76 | fmt.Println("json unmarshal error:", errs) 77 | } 78 | fmt.Println("res2 :", res2) 79 | } 80 | ``` 81 | 运行结果: 82 | 83 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/05-结构体/5_go_2.png) 84 | 85 | ## 改变数据 86 | 87 | ``` 88 | //demo_13.go 89 | package main 90 | 91 | import ( 92 | "encoding/json" 93 | "fmt" 94 | ) 95 | 96 | type Result struct { 97 | Code int `json:"code"` 98 | Message string `json:"msg"` 99 | } 100 | 101 | func main() { 102 | var res Result 103 | res.Code = 200 104 | res.Message = "success" 105 | toJson(&res) 106 | 107 | setData(&res) 108 | toJson(&res) 109 | } 110 | 111 | func setData (res *Result) { 112 | res.Code = 500 113 | res.Message = "fail" 114 | } 115 | 116 | func toJson (res *Result) { 117 | jsons, errs := json.Marshal(res) 118 | if errs != nil { 119 | fmt.Println("json marshal error:", errs) 120 | } 121 | fmt.Println("json data :", string(jsons)) 122 | } 123 | ``` 124 | 125 | 运行结果: 126 | 127 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/05-结构体/5_go_3.png) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/06-Map 集合.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | Map 集合是无序的 key-value 数据结构。 4 | 5 | Map 集合中的 key / value 可以是任意类型,但所有的 key 必须属于同一数据类型,所有的 value 必须属于同一数据类型,key 和 value 的数据类型可以不相同。 6 | 7 | ## 声明 Map 8 | 9 | ``` 10 | //demo_14.go 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | ) 16 | 17 | func main() { 18 | var p1 map[int]string 19 | p1 = make(map[int]string) 20 | p1[1] = "Tom" 21 | fmt.Println("p1 :", p1) 22 | 23 | var p2 map[int]string = map[int]string{} 24 | p2[1] = "Tom" 25 | fmt.Println("p2 :", p2) 26 | 27 | var p3 map[int]string = make(map[int]string) 28 | p3[1] = "Tom" 29 | fmt.Println("p3 :", p3) 30 | 31 | p4 := map[int]string{} 32 | p4[1] = "Tom" 33 | fmt.Println("p4 :", p4) 34 | 35 | p5 := make(map[int]string) 36 | p5[1] = "Tom" 37 | fmt.Println("p5 :", p5) 38 | 39 | p6 := map[int]string{ 40 | 1 : "Tom", 41 | } 42 | fmt.Println("p6 :", p6) 43 | } 44 | ``` 45 | 46 | 运行结果: 47 | 48 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/06-集合/6_go_1.png) 49 | 50 | ## 生成 JSON 51 | 52 | ``` 53 | //demo_15.go 54 | package main 55 | 56 | import ( 57 | "encoding/json" 58 | "fmt" 59 | ) 60 | 61 | func main() { 62 | res := make(map[string]interface{}) 63 | res["code"] = 200 64 | res["msg"] = "success" 65 | res["data"] = map[string]interface{}{ 66 | "username" : "Tom", 67 | "age" : "30", 68 | "hobby" : []string{"读书","爬山"}, 69 | } 70 | fmt.Println("map data :", res) 71 | 72 | //序列化 73 | jsons, errs := json.Marshal(res) 74 | if errs != nil { 75 | fmt.Println("json marshal error:", errs) 76 | } 77 | fmt.Println("") 78 | fmt.Println("--- map to json ---") 79 | fmt.Println("json data :", string(jsons)) 80 | 81 | //反序列化 82 | res2 := make(map[string]interface{}) 83 | errs = json.Unmarshal([]byte(jsons), &res2) 84 | if errs != nil { 85 | fmt.Println("json marshal error:", errs) 86 | } 87 | fmt.Println("") 88 | fmt.Println("--- json to map ---") 89 | fmt.Println("map data :", res2) 90 | } 91 | ``` 92 | 93 | 运行结果: 94 | 95 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/06-集合/6_go_2.png) 96 | 97 | ## 编辑和删除 98 | 99 | ``` 100 | //demo_16.go 101 | package main 102 | 103 | import ( 104 | "fmt" 105 | ) 106 | 107 | func main() { 108 | person := map[int]string{ 109 | 1 : "Tom", 110 | 2 : "Aaron", 111 | 3 : "John", 112 | } 113 | fmt.Println("data :",person) 114 | 115 | delete(person, 2) 116 | fmt.Println("data :",person) 117 | 118 | person[2] = "Jack" 119 | person[3] = "Kevin" 120 | fmt.Println("data :",person) 121 | } 122 | ``` 123 | 运行结果: 124 | 125 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/06-集合/6_go_3.png) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/08-函数.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 学习了一些基础语法,开始学习写函数了,分享几个自己写的函数。 4 | 5 | - MD5 6 | - 获取当前时间戳 7 | - 获取当前时间字符串 8 | - 生成签名 9 | 10 | ## 函数定义 11 | 12 | ``` 13 | func function_name(input1 type1, input2 type2) (type1, type2) { 14 | // 函数体 15 | // 返回多个值 16 | return value1, value2 17 | } 18 | ``` 19 | 20 | - 函数用 `func` 声明。 21 | - 函数可以有一个或多个参数,需要有参数类型,用 `,` 分割。 22 | - 函数可以有一个或多个返回值,需要有返回值类型,用 `,` 分割。 23 | - 函数的参数是可选的,返回值也是可选的。 24 | 25 | ## 值传递 26 | 27 | 传递参数时,将参数复制一份传递到函数中,对参数进行调整后,不影响参数值。 28 | 29 | Go 语言默认是值传递。 30 | 31 | ## 引用传递 32 | 33 | 传递参数时,将参数的地址传递到函数中,对参数进行调整后,影响参数值。 34 | 35 | 这个是 [Go - Struct 结构体](https://mp.weixin.qq.com/s/PB3dTnu4DKw7S1-rZD9nmQ)的例子: 36 | 37 | ``` 38 | //demo_13.go 39 | package main 40 | 41 | import ( 42 | "encoding/json" 43 | "fmt" 44 | ) 45 | 46 | type Result struct { 47 | Code int `json:"code"` 48 | Message string `json:"msg"` 49 | } 50 | 51 | func main() { 52 | var res Result 53 | res.Code = 200 54 | res.Message = "success" 55 | toJson(&res) 56 | 57 | setData(&res) 58 | toJson(&res) 59 | } 60 | 61 | func setData (res *Result) { 62 | res.Code = 500 63 | res.Message = "fail" 64 | } 65 | 66 | func toJson (res *Result) { 67 | jsons, errs := json.Marshal(res) 68 | if errs != nil { 69 | fmt.Println("json marshal error:", errs) 70 | } 71 | fmt.Println("json data :", string(jsons)) 72 | } 73 | ``` 74 | 75 | 运行结果: 76 | 77 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/08-函数/8_go_0.png) 78 | 79 | ## MD5 80 | 81 | ``` 82 | // MD5 方法 83 | func MD5(str string) string { 84 | s := md5.New() 85 | s.Write([]byte(str)) 86 | return hex.EncodeToString(s.Sum(nil)) 87 | } 88 | 89 | str := "12345" 90 | fmt.Printf("MD5(%s): %s\n", str, MD5(str)) 91 | ``` 92 | 93 | 运行结果: 94 | 95 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/08-函数/8_go_1.png) 96 | 97 | ## 获取当前时间字符串 98 | 99 | ``` 100 | // xxxx-xx-xx xx:xx:xx 101 | func getTimeStr() string { 102 | return time.Now().Format("2006-01-02 15:04:05") 103 | } 104 | 105 | fmt.Printf("current time str : %s\n", getTimeStr()) 106 | ``` 107 | 108 | 运行结果: 109 | 110 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/08-函数/8_go_2.png) 111 | 112 | ## 获取当前时间戳 113 | 114 | ``` 115 | // 获取当前时间戳 116 | func getTimeInt() int64 { 117 | return time.Now().Unix() 118 | } 119 | 120 | fmt.Printf("current time str : %s\n", getTimeStr()) 121 | fmt.Printf("current time unix : %d\n", getTimeInt()) 122 | ``` 123 | 124 | 运行结果: 125 | 126 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/08-函数/8_go_3.png) 127 | 128 | ## 生成签名 129 | 130 | ``` 131 | //demo_26.go 132 | package main 133 | 134 | import ( 135 | "crypto/md5" 136 | "encoding/hex" 137 | "fmt" 138 | "sort" 139 | ) 140 | 141 | func main() { 142 | params := map[string]interface{} { 143 | "name" : "Tom", 144 | "pwd" : "123456", 145 | "age" : 30, 146 | } 147 | fmt.Printf("sign : %s\n", createSign(params)) 148 | } 149 | 150 | // MD5 方法 151 | func MD5(str string) string { 152 | s := md5.New() 153 | s.Write([]byte(str)) 154 | return hex.EncodeToString(s.Sum(nil)) 155 | } 156 | 157 | // 生成签名 158 | func createSign(params map[string]interface{}) string { 159 | var key []string 160 | var str = "" 161 | for k := range params { 162 | key = append(key, k) 163 | } 164 | sort.Strings(key) 165 | for i := 0; i < len(key); i++ { 166 | if i == 0 { 167 | str = fmt.Sprintf("%v=%v", key[i], params[key[i]]) 168 | } else { 169 | str = str + fmt.Sprintf("&xl_%v=%v", key[i], params[key[i]]) 170 | } 171 | } 172 | // 自定义密钥 173 | var secret = "123456789" 174 | 175 | // 自定义签名算法 176 | sign := MD5(MD5(str) + MD5(secret)) 177 | return sign 178 | } 179 | ``` 180 | 181 | 运行结果: 182 | 183 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/08-函数/8_go_4.png) 184 | 185 | 对应 PHP 生成签名方法: 186 | 187 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/08-函数/8_go_5.png) 188 | 189 | 有兴趣的可以在签名方法中,增加时间戳 和 secret 在配置文件中读取。 -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/09-chan 通道.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 原来分享基础语法的时候,还未分享过 chan 通道,这次把它补上。 4 | 5 | chan 可以理解为队列,遵循先进先出的规则。 6 | 7 | 在说 chan 之前,咱们先说一下 go 关键字。 8 | 9 | 在 go 关键字后面加一个函数,就可以创建一个线程,函数可以为已经写好的函数,也可以是匿名函数。 10 | 11 | 举个例子: 12 | 13 | ``` 14 | func main() { 15 | fmt.Println("main start") 16 | 17 | go func() { 18 | fmt.Println("goroutine") 19 | }() 20 | 21 | fmt.Println("main end") 22 | } 23 | ``` 24 | 25 | 输出: 26 | 27 | ``` 28 | main start 29 | main end 30 | ``` 31 | 32 | 为什么没有输出 goroutine ? 33 | 34 | 首先,我们清楚 Go 语言的线程是并发机制,不是并行机制。 35 | 36 | 那么,什么是并发,什么是并行? 37 | 38 | 并发是不同的代码块交替执行,也就是交替可以做不同的事情。 39 | 40 | 并行是不同的代码块同时执行,也就是同时可以做不同的事情。 41 | 42 | 举个生活化场景的例子: 43 | 44 | 你正在家看书,忽然电话来了,然后你接电话,通话完成后继续看书,这就是并发,看书和接电话交替做。 45 | 46 | 如果电话来了,你一边看书一遍接电话,这就是并行,看书和接电话一起做。 47 | 48 | 说回上面的例子,为什么没有输出 goroutine ? 49 | 50 | main 函数是一个主线程,是因为主线程执行太快了,子线程还没来得及执行,所以看不到输出。 51 | 52 | 现在让主线程休眠 1 秒钟,再试试。 53 | 54 | ``` 55 | func main() { 56 | fmt.Println("main start") 57 | 58 | go func() { 59 | fmt.Println("goroutine") 60 | }() 61 | 62 | time.Sleep(1 * time.Second) 63 | 64 | fmt.Println("main end") 65 | } 66 | ``` 67 | 68 | 输出: 69 | 70 | ``` 71 | main start 72 | goroutine 73 | main end 74 | ``` 75 | 76 | 这就对了。 77 | 78 | 接下来,看看如何使用 chan 。 79 | 80 | ## 声明 chan 81 | 82 | ``` 83 | // 声明不带缓冲的通道 84 | ch1 := make(chan string) 85 | 86 | // 声明带10个缓冲的通道 87 | ch2 := make(chan string, 10) 88 | 89 | // 声明只读通道 90 | ch3 := make(<-chan string) 91 | 92 | // 声明只写通道 93 | ch4 := make(chan<- string) 94 | ``` 95 | 96 | 注意: 97 | 98 | 不带缓冲的通道,进和出都会阻塞。 99 | 100 | 带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。 101 | 102 | ## 写入 chan 103 | 104 | ``` 105 | ch1 := make(chan string, 10) 106 | 107 | ch1 <- "a" 108 | ``` 109 | 110 | ## 读取 chan 111 | 112 | ``` 113 | val, ok := <- ch1 114 | // 或 115 | val := <- ch1 116 | ``` 117 | 118 | ## 关闭 chan 119 | 120 | ``` 121 | close(chan) 122 | ``` 123 | 124 | 注意: 125 | 126 | - close 以后不能再写入,写入会出现 panic 127 | - 重复 close 会出现 panic 128 | - 只读的 chan 不能 close 129 | - close 以后还可以读取数据 130 | 131 | 132 | ## 示例 133 | 134 | ``` 135 | func main() { 136 | fmt.Println("main start") 137 | ch := make(chan string) 138 | ch <- "a" // 入 chan 139 | go func() { 140 | val := <- ch // 出 chan 141 | fmt.Println(val) 142 | }() 143 | fmt.Println("main end") 144 | } 145 | ``` 146 | 147 | 输出: 148 | 149 | ``` 150 | main start 151 | fatal error: all goroutines are asleep - deadlock! 152 | ``` 153 | 154 | What ? 这是为啥,刚开始就出师不利呀? 155 | 156 | 因为,定义的是一个无缓冲的 chan,赋值后就陷入了阻塞。 157 | 158 | 怎么解决它? 159 | 160 | 声明一个有缓冲的 chan。 161 | 162 | ``` 163 | func main() { 164 | fmt.Println("main start") 165 | ch := make(chan string, 1) 166 | ch <- "a" // 入 chan 167 | go func() { 168 | val := <- ch // 出 chan 169 | fmt.Println(val) 170 | }() 171 | fmt.Println("main end") 172 | } 173 | ``` 174 | 175 | 输出: 176 | 177 | ``` 178 | main start 179 | main end 180 | ``` 181 | 182 | 为啥没有输出 a , 和前面一样,主线程执行太快了,加个休眠 1 秒钟,再试试。 183 | 184 | ``` 185 | func main() { 186 | fmt.Println("main start") 187 | ch := make(chan string, 1) 188 | ch <- "a" // 入 chan 189 | go func() { 190 | val := <- ch // 出 chan 191 | fmt.Println(val) 192 | }() 193 | time.Sleep(1 * time.Second) 194 | fmt.Println("main end") 195 | } 196 | ``` 197 | 198 | 输出: 199 | 200 | ``` 201 | main start 202 | a 203 | main end 204 | ``` 205 | 206 | 这就对了。 207 | 208 | 再看一个例子: 209 | 210 | ``` 211 | func main() { 212 | fmt.Println("main start") 213 | ch := make(chan string) 214 | go func() { 215 | ch <- "a" // 入 chan 216 | }() 217 | go func() { 218 | val := <- ch // 出 chan 219 | fmt.Println(val) 220 | }() 221 | time.Sleep(1 * time.Second) 222 | fmt.Println("main end") 223 | } 224 | ``` 225 | 226 | 输出: 227 | 228 | ``` 229 | main start 230 | a 231 | main end 232 | ``` 233 | 234 | 再看一个例子: 235 | 236 | ``` 237 | func producer(ch chan string) { 238 | fmt.Println("producer start") 239 | ch <- "a" 240 | ch <- "b" 241 | ch <- "c" 242 | ch <- "d" 243 | fmt.Println("producer end") 244 | } 245 | 246 | func main() { 247 | fmt.Println("main start") 248 | ch := make(chan string, 3) 249 | go producer(ch) 250 | 251 | time.Sleep(1 * time.Second) 252 | fmt.Println("main end") 253 | } 254 | ``` 255 | 256 | 输出: 257 | 258 | ``` 259 | main start 260 | producer start 261 | main end 262 | ``` 263 | 264 | 带缓冲的通道,如果长度等于缓冲长度时,再进就会阻塞。 265 | 266 | 再看一个例子: 267 | 268 | ``` 269 | func producer(ch chan string) { 270 | fmt.Println("producer start") 271 | ch <- "a" 272 | ch <- "b" 273 | ch <- "c" 274 | ch <- "d" 275 | fmt.Println("producer end") 276 | } 277 | 278 | func customer(ch chan string) { 279 | for { 280 | msg := <- ch 281 | fmt.Println(msg) 282 | } 283 | } 284 | 285 | func main() { 286 | fmt.Println("main start") 287 | ch := make(chan string, 3) 288 | go producer(ch) 289 | go customer(ch) 290 | 291 | time.Sleep(1 * time.Second) 292 | fmt.Println("main end") 293 | } 294 | ``` 295 | 296 | 输出: 297 | 298 | ``` 299 | main start 300 | producer start 301 | producer end 302 | a 303 | b 304 | c 305 | d 306 | main end 307 | ``` 308 | 309 | 就到这吧。 310 | 311 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/10-defer 函数.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | defer 函数大家肯定都用过,它在声明时不会立刻去执行,而是在函数 return 后去执行的。 4 | 5 | 它的主要应用场景有异常处理、记录日志、清理数据、释放资源 等等。 6 | 7 | 这篇文章不是分享 defer 的应用场景,而是分享使用 defer 需要注意的点。 8 | 9 | 咱们先从一道题开始,一起来感受下 ... 10 | 11 | ``` 12 | func calc(index string, a, b int) int { 13 | ret := a + b 14 | fmt.Println(index, a, b, ret) 15 | return ret 16 | } 17 | 18 | func main() { 19 | x := 1 20 | y := 2 21 | defer calc("A", x, calc("B", x, y)) 22 | x = 3 23 | defer calc("C", x, calc("D", x, y)) 24 | y = 4 25 | } 26 | ``` 27 | 28 | 输出什么? 29 | 30 | ... 31 | 32 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/10_go_1.jpeg) 33 | 34 | 接下来,先容我分享几个小例子,再进行作答。 35 | 36 | ## 执行顺序 37 | 38 | ``` 39 | func main() { 40 | 41 | defer fmt.Println("1") 42 | defer fmt.Println("2") 43 | defer fmt.Println("3") 44 | 45 | fmt.Println("main") 46 | } 47 | ``` 48 | 49 | 输出: 50 | 51 | ``` 52 | main 53 | 3 54 | 2 55 | 1 56 | ``` 57 | 58 | 结论:defer 函数定义的顺序 与 实际执的行顺序是相反的,也就是最先声明的最后才执行。 59 | 60 | ## 闭包 61 | 62 | ``` 63 | func main() { 64 | 65 | var a = 1 66 | var b = 2 67 | 68 | defer fmt.Println(a + b) 69 | 70 | a = 2 71 | 72 | fmt.Println("main") 73 | } 74 | ``` 75 | 76 | 输出: 77 | 78 | ``` 79 | main 80 | 3 81 | ``` 82 | 83 | 稍微修改一下,再看看: 84 | 85 | ``` 86 | func main() { 87 | var a = 1 88 | var b = 2 89 | 90 | defer func() { 91 | fmt.Println(a + b) 92 | }() 93 | 94 | a = 2 95 | 96 | fmt.Println("main") 97 | } 98 | ``` 99 | 100 | 输出: 101 | 102 | ``` 103 | main 104 | 4 105 | ``` 106 | 107 | 结论:闭包获取变量相当于引用传递,而非值传递。 108 | 109 | 稍微再修改一下,再看看: 110 | 111 | ``` 112 | func main() { 113 | var a = 1 114 | var b = 2 115 | 116 | defer func(a int, b int) { 117 | fmt.Println(a + b) 118 | }(a, b) 119 | 120 | a = 2 121 | 122 | fmt.Println("main") 123 | } 124 | ``` 125 | 126 | 输出: 127 | 128 | ``` 129 | main 130 | 3 131 | ``` 132 | 133 | 结论:传参是值复制。 134 | 135 | 还可以理解为:defer 调用的函数,参数的值在 defer 定义时就确定了,看下代码 136 | 137 | `defer fmt.Println(a + b)`,在这时,参数的值已经确定了。 138 | 139 | 而 defer 函数内部所使用的变量的值需要在这个函数运行时才确定,看下代码 140 | 141 | `defer func() { fmt.Println(a + b) }()`,a 和 b 的值在函数运行时,才能确定。 142 | 143 | ## Return 144 | 145 | #### 一 146 | 147 | ``` 148 | func t1() int { 149 | a := 1 150 | defer func() { 151 | a++ 152 | }() 153 | return a 154 | } 155 | ``` 156 | 157 | 输出:1 158 | 159 | #### 二 160 | 161 | ``` 162 | func t2() (a int) { 163 | defer func() { 164 | a++ 165 | }() 166 | return 1 167 | } 168 | ``` 169 | 170 | 输出:2 171 | 172 | #### 三 173 | 174 | ``` 175 | func t3() (b int) { 176 | a := 1 177 | defer func() { 178 | a++ 179 | }() 180 | return 1 181 | } 182 | ``` 183 | 184 | 输出:1 185 | 186 | #### 四 187 | 188 | ``` 189 | func t4() (a int) { 190 | defer func(a int) { 191 | a++ 192 | }(a) 193 | return 1 194 | } 195 | ``` 196 | 197 | 输出:1 198 | 199 | 结论:return 不是原子操作。 200 | 201 | ## os.Exit 202 | 203 | ``` 204 | func main() { 205 | defer fmt.Println("1") 206 | fmt.Println("main") 207 | os.Exit(0) 208 | } 209 | ``` 210 | 211 | 输出:main 212 | 213 | 结论:当`os.Exit()`方法退出程序时,defer不会被执行。 214 | 215 | ## 不同协程 216 | 217 | ``` 218 | func main() { 219 | GoA() 220 | time.Sleep(1 * time.Second) 221 | fmt.Println("main") 222 | } 223 | 224 | func GoA() { 225 | defer (func(){ 226 | if err := recover(); err != nil { 227 | fmt.Println("panic:" + fmt.Sprintf("%s", err)) 228 | } 229 | })() 230 | 231 | go GoB() 232 | } 233 | 234 | func GoB() { 235 | panic("error") 236 | } 237 | ``` 238 | 239 | `GoB()` panic 捕获不到。 240 | 241 | 结论:defer 只对当前协程有效。 242 | 243 | 这个问题怎么解?咱们下回再说。 244 | 245 | 接下来,咱们分析下文章开头的问题吧。 246 | 247 | ## 答案解析 248 | 249 | 先列出答案: 250 | 251 | ``` 252 | B 1 2 3 253 | D 3 2 5 254 | C 3 5 8 255 | A 1 3 4 256 | ``` 257 | 258 | 其实上面那道题,可以拆解为: 259 | 260 | ``` 261 | func calc(index string, a, b int) int { 262 | ret := a + b 263 | fmt.Println(index, a, b, ret) 264 | return ret 265 | } 266 | 267 | func main() { 268 | x := 1 269 | y := 2 270 | tmp1 := calc("B", x, y) 271 | defer calc("A", x, tmp1) 272 | x = 3 273 | tmp2 := calc("D", x, y) 274 | defer calc("C", x, tmp2) 275 | y = 4 276 | } 277 | ``` 278 | 279 | 所以顺序就是:B D C A。 280 | 281 | 执行到 tmp1 时,输出:B 1 2 3。 282 | 283 | 执行到 tmp2 时,输出:D 3 2 5。 284 | 285 | 根据 defer 执行顺序原则,先声明的后执行,所以下一个该执行 C 了。 286 | 287 | 又因为传参是值赋值,所以在 A 的时候,无法用到 `x = 3` 和 `y = 4`,在 C 的时候,无法用到 `y = 4`。 288 | 289 | 执行到 C 时,输出:C 3 5 8 290 | 291 | 执行到 A 时,输出:A 1 3 4 292 | 293 | 到这,基本上 defer 就清楚了,大家可以根据自己的理解去记忆。 294 | 295 | ## go-gin-api 系列文章 296 | 297 | - [7. 路由中间件 - 签名验证](https://mp.weixin.qq.com/s/0cozELotcpX3Gd6WPJiBbQ) 298 | - [6. 路由中间件 - Jaeger 链路追踪(实战篇)](https://mp.weixin.qq.com/s/Ea28475_UTNaM9RNfgPqJA) 299 | - [5. 路由中间件 - Jaeger 链路追踪(理论篇)](https://mp.weixin.qq.com/s/28UBEsLOAHDv530ePilKQA) 300 | - [4. 路由中间件 - 捕获异常](https://mp.weixin.qq.com/s/SconDXB_x7Gan6T0Awdh9A) 301 | - [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg) 302 | - [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA) 303 | - [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/11-解析 JSON 数据.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 最近掉进需求坑了,刚爬上来,评估排期出现了严重问题,下面三张图很符合当时的心境。 4 | 5 | 谈需求 6 | 7 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/11_go_1.jpeg) 8 | 9 | 估排期 10 | 11 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/11_go_2.jpeg) 12 | 13 | 开始干 14 | 15 | ![](https://github.com/xinliangnote/Go/blob/master/00-基础语法/images/11_go_3.jpeg) 16 | 17 | 为啥会这样,我简单总结了下: 18 | 19 | - 与第三方对接。 20 | - 跨团队对接。 21 | - 首次用 Go 做项目。 22 | - 业务流程没屡清楚就出排期(大坑)。 23 | - 需求调整后未进行调整排期(大坑)。 24 | 25 | 有了这次经验,后期关于如何评估排期也可以和大家唠唠。 26 | 27 | 废话不多说了,进入今天主题。 28 | 29 | 今天给大家分享用 Go 如何解析 JSON 数据,包含三种情况,强类型解析、弱类型解析、返回结构不确定 等。 30 | 31 | ## JSON 结构 32 | 33 | 比如,请求了手机归属地的接口,json 数据返回如下: 34 | 35 | ``` 36 | { 37 | "resultcode": "200", 38 | "reason": "Return Successd!", 39 | "result": { 40 | "province": "浙江", 41 | "city": "杭州", 42 | "areacode": "0571", 43 | "zip": "310000", 44 | "company": "中国移动", 45 | "card": "" 46 | } 47 | } 48 | ``` 49 | 50 | 思路是这样的: 51 | 52 | 1.先将 json 转成 struct。 53 | 54 | 2.然后 `json.Unmarshal()` 即可。 55 | 56 | json 转 struct ,自己手写就太麻烦了,有很多在线的工具可以直接用,我用的这个: 57 | 58 | https://mholt.github.io/json-to-go/ 59 | 60 | 在左边贴上 json 后面就生成 struct 了。 61 | 62 | 用代码实现下: 63 | 64 | ``` 65 | type MobileInfo struct { 66 | Resultcode string `json:"resultcode"` 67 | Reason string `json:"reason"` 68 | Result struct { 69 | Province string `json:"province"` 70 | City string `json:"city"` 71 | Areacode string `json:"areacode"` 72 | Zip string `json:"zip"` 73 | Company string `json:"company"` 74 | Card string `json:"card"` 75 | } `json:"result"` 76 | } 77 | 78 | func main() { 79 | jsonStr := ` 80 | { 81 | "resultcode": "200", 82 | "reason": "Return Successd!", 83 | "result": { 84 | "province": "浙江", 85 | "city": "杭州", 86 | "areacode": "0571", 87 | "zip": "310000", 88 | "company": "中国移动", 89 | "card": "" 90 | } 91 | } 92 | ` 93 | 94 | var mobile MobileInfo 95 | err := json.Unmarshal([]byte(jsonStr), &mobile) 96 | if err != nil { 97 | fmt.Println(err.Error()) 98 | } 99 | fmt.Println(mobile.Resultcode) 100 | fmt.Println(mobile.Reason) 101 | fmt.Println(mobile.Result.City) 102 | } 103 | ``` 104 | 105 | 输出: 106 | 107 | ``` 108 | 200 109 | Return Successd! 110 | 杭州 111 | ``` 112 | 113 | 完美解析。 114 | 115 | 到这问题还没结束,思考下这些问题: 116 | 117 | 如果 json 格式的数据类型不确定怎么办? 118 | 119 | 如果 json 格式的数据 result 中参数不固定怎么办? 120 | 121 | 思路是这样的: 122 | 123 | 去 github 上找开源类库,哈哈,我使用的是这个: 124 | 125 | https://github.com/mitchellh/mapstructure 126 | 127 | 咱们一起学习下,先解决第一个问题,数据类型不确定怎么办? 128 | 129 | 先定义一个 string 类型的 resultcode,json 却返回了 int 类型的 resultcode。 130 | 131 | 看文档有一个弱类型解析的方法 `WeakDecode()`,咱们试一下: 132 | 133 | ``` 134 | type MobileInfo struct { 135 | Resultcode string `json:"resultcode"` 136 | } 137 | 138 | func main() { 139 | jsonStr := ` 140 | { 141 | "resultcode": 200 142 | } 143 | ` 144 | 145 | var result map[string]interface{} 146 | err := json.Unmarshal([]byte(jsonStr), &result) 147 | if err != nil { 148 | fmt.Println(err.Error()) 149 | } 150 | 151 | var mobile MobileInfo 152 | err = mapstructure.WeakDecode(result, &mobile) 153 | if err != nil { 154 | fmt.Println(err.Error()) 155 | } 156 | 157 | fmt.Println(mobile.Resultcode) 158 | } 159 | ``` 160 | 161 | 输出: 162 | 163 | 200 164 | 165 | 第一个问题已解决。 166 | 167 | 再解决第二个问题,result 中参数不固定怎么办? 168 | 169 | 这个就不用上面的例子了,看下官方提供的例子 `Example (EmbeddedStruct)` 。 170 | 171 | ``` 172 | type Family struct { 173 | LastName string 174 | } 175 | type Location struct { 176 | City string 177 | } 178 | type Person struct { 179 | Family `mapstructure:",squash"` 180 | Location `mapstructure:",squash"` 181 | FirstName string 182 | } 183 | 184 | func main() { 185 | input := map[string]interface{}{ 186 | "FirstName": "Mitchell", 187 | "LastName": "Hashimoto", 188 | "City": "San Francisco", 189 | } 190 | 191 | var result Person 192 | err := mapstructure.Decode(input, &result) 193 | if err != nil { 194 | panic(err) 195 | } 196 | 197 | fmt.Println(result.FirstName) 198 | fmt.Println(result.LastName) 199 | fmt.Println(result.City) 200 | } 201 | ``` 202 | 203 | 输出: 204 | 205 | ``` 206 | Mitchell 207 | Hashimoto 208 | San Francisco 209 | ``` 210 | 211 | 使用的是 mapstructure 包,struct tag 标识不要写 json,要写 mapstructure。 212 | 213 | 其他情况自己探索吧,比如: `Example (Tags)`。 214 | 215 | ## go-gin-api 系列文章 216 | 217 | - [7. 路由中间件 - 签名验证](https://mp.weixin.qq.com/s/0cozELotcpX3Gd6WPJiBbQ) 218 | - [6. 路由中间件 - Jaeger 链路追踪(实战篇)](https://mp.weixin.qq.com/s/Ea28475_UTNaM9RNfgPqJA) 219 | - [5. 路由中间件 - Jaeger 链路追踪(理论篇)](https://mp.weixin.qq.com/s/28UBEsLOAHDv530ePilKQA) 220 | - [4. 路由中间件 - 捕获异常](https://mp.weixin.qq.com/s/SconDXB_x7Gan6T0Awdh9A) 221 | - [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg) 222 | - [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA) 223 | - [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A) 224 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/12-json.Unmarshal 遇到的小坑.md: -------------------------------------------------------------------------------- 1 | ## 1.问题现象描述 2 | 3 | 使用 `json.Unmarshal()`,反序列化时,出现了科学计数法,参考代码如下: 4 | 5 | ``` 6 | jsonStr := `{"number":1234567}` 7 | result := make(map[string]interface{}) 8 | err := json.Unmarshal([]byte(jsonStr), &result) 9 | if err != nil { 10 | fmt.Println(err) 11 | } 12 | fmt.Println(result) 13 | 14 | // 输出 15 | // map[number:1.234567e+06] 16 | ``` 17 | 18 | 这个问题不是必现,只有当数字的位数大于 6 位时,才会变成了科学计数法。 19 | 20 | ## 2.问题影响描述 21 | 22 | 当数据结构未知,使用 `map[string]interface{}` 来接收反序列化结果时,如果数字的位数大于 6 位,都会变成科学计数法,用到的地方都会受到影响。 23 | 24 | ## 3.引起问题的原因 25 | 26 | 从 `encoding/json` 可以找到答案,看一下这段注释: 27 | 28 | ``` 29 | // To unmarshal JSON into an interface value, 30 | // Unmarshal stores one of these in the interface value: 31 | // 32 | // bool, for JSON booleans 33 | // float64, for JSON numbers 34 | // string, for JSON strings 35 | // []interface{}, for JSON arrays 36 | // map[string]interface{}, for JSON objects 37 | // nil for JSON null 38 | ``` 39 | 40 | 是因为当 `JSON` 中存在一个比较大的数字时,它会被解析成 `float64` 类型,就有可能会出现科学计数法的形式。 41 | 42 | ## 4.问题的解决方案 43 | 44 | **方案一** 45 | 46 | 强制类型转换,参考代码如下: 47 | 48 | ``` 49 | jsonStr := `{"number":1234567}` 50 | result := make(map[string]interface{}) 51 | err := json.Unmarshal([]byte(jsonStr), &result) 52 | if err != nil { 53 | fmt.Println(err) 54 | } 55 | fmt.Println(int(result["number"].(float64))) 56 | 57 | // 输出 58 | // 1234567 59 | ``` 60 | 61 | **方案二** 62 | 63 | 尽量避免使用 `interface`,对 `json` 字符串结构定义结构体,快捷方法可使用在线工具:`https://mholt.github.io/json-to-go/`。 64 | 65 | ``` 66 | type Num struct { 67 | Number int `json:"number"` 68 | } 69 | 70 | jsonStr := `{"number":1234567}` 71 | var result Num 72 | err := json.Unmarshal([]byte(jsonStr), &result) 73 | if err != nil { 74 | fmt.Println(err) 75 | } 76 | fmt.Println(result) 77 | 78 | // 输出 79 | // {1234567} 80 | ``` 81 | 82 | **方案三** 83 | 84 | 使用 `UseNumber()` 方法。 85 | 86 | ``` 87 | jsonStr := `{"number":1234567}` 88 | result := make(map[string]interface{}) 89 | d := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) 90 | d.UseNumber() 91 | err := d.Decode(&result) 92 | if err != nil { 93 | fmt.Println(err) 94 | } 95 | fmt.Println(result) 96 | 97 | // 输出 98 | // map[number:1234567] 99 | ``` 100 | 101 | 这时一定要注意 `result["number"]` 的数据类型! 102 | 103 | ``` 104 | fmt.Println(fmt.Sprintf("type: %v", reflect.TypeOf(result["number"]))) 105 | 106 | // 输出 107 | // type: json.Number 108 | ``` 109 | 110 | 通过代码可以看出 `json.Number` 其实就是字符串类型: 111 | 112 | ``` 113 | // A Number represents a JSON number literal. 114 | type Number string 115 | ``` 116 | 117 | 如果转换其他类型,参考如下代码: 118 | 119 | ``` 120 | // 转成 int64 121 | numInt, _ := result["number"].(json.Number).Int64() 122 | fmt.Println(fmt.Sprintf("value: %v, type: %v", numInt, reflect.TypeOf(numInt))) 123 | 124 | // 输出 125 | // value: 1234567, type: int64 126 | 127 | // 转成 string 128 | numStr := result["number"].(json.Number).String() 129 | fmt.Println(fmt.Sprintf("value: %v, type: %v", numStr, reflect.TypeOf(numStr))) 130 | 131 | // 输出 132 | // value: 1234567, type: string 133 | ``` -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/13-结构(struct) 实现 接口(interface) .md: -------------------------------------------------------------------------------- 1 | 在 Go 语言中,一个 struct 实现了某个接口里的所有方法,就叫做这个 struct 实现了该接口。 2 | 3 | 下面写一个 Demo 实现一下,先写一个 Study interface{},里面需要实现 4 个方法 Listen、Speak、Read、Write,然后再写一个 study struct{},去全部实现里面的方法,然后分享一下代码心得。 4 | 5 | ## 代码示例 6 | 7 | ``` 8 | // main.go 9 | 10 | package main 11 | 12 | import ( 13 | "demo/study" 14 | "fmt" 15 | ) 16 | 17 | func main() { 18 | name := "Tom" 19 | s, err := study.New(name) 20 | if err != nil { 21 | fmt.Println(err) 22 | } 23 | 24 | fmt.Println(s.Listen("english")) 25 | fmt.Println(s.Speak("english")) 26 | fmt.Println(s.Read("english")) 27 | fmt.Println(s.Write("english")) 28 | } 29 | 30 | // 输出 31 | Tom 听 english 32 | Tom 说 english 33 | Tom 读 english 34 | Tom 写 english 35 | ``` 36 | 37 | ``` 38 | // study.go 39 | 40 | package study 41 | 42 | import "github.com/pkg/errors" 43 | 44 | var _ Study = (*study)(nil) 45 | 46 | type Study interface { 47 | Listen(msg string) string 48 | Speak(msg string) string 49 | Read(msg string) string 50 | Write(msg string) string 51 | } 52 | 53 | type study struct { 54 | Name string 55 | } 56 | 57 | func (s *study) Listen(msg string) string { 58 | return s.Name + " 听 " + msg 59 | } 60 | 61 | func (s *study) Speak(msg string) string { 62 | return s.Name + " 说 " + msg 63 | } 64 | 65 | func (s *study) Read(msg string) string { 66 | return s.Name + " 读 " + msg 67 | } 68 | 69 | func (s *study) Write(msg string) string { 70 | return s.Name + " 写 " + msg 71 | } 72 | 73 | func New(name string) (Study, error) { 74 | if name == "" { 75 | return nil, errors.New("name required") 76 | } 77 | 78 | return &study{ 79 | Name: name, 80 | }, nil 81 | } 82 | 83 | 84 | ``` 85 | 86 | ## 代码解释 87 | 88 | #### 一、 89 | 90 | ``` 91 | var _ Study = (*study)(nil) 92 | ``` 93 | 94 | 要求 `*study` 去实现 `Study`,若 `Study` 接口被更改或未全部实现时,在编译时就会报错。 95 | 96 | #### 二、 97 | 98 | ``` 99 | type study struct { 100 | Name string 101 | } 102 | ``` 103 | 104 | 之所以定义为私有的结构体,是因为不想在其他地方被使用,比如后面将 `Name` 改成 `UserName` 只需要在本包内修改即可。 105 | 106 | #### 三、 107 | 108 | ``` 109 | func New(name string) (Study, error) { 110 | if name == "" { 111 | return nil, errors.New("name required") 112 | } 113 | 114 | return &study{ 115 | Name: name, 116 | }, nil 117 | } 118 | ``` 119 | 120 | 在其他地方调用 `New()` 使用 `Study` 包时,仅对外暴露了 4 个方法,别人只管调用就好了,内部实现别人无需关心。 121 | 122 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/14-学习 grpc.Dial(target string, opts …DialOption) 的写法.md: -------------------------------------------------------------------------------- 1 | 咱们平时是这样使用 `grpc.Dial` 方法的,比如: 2 | 3 | ``` 4 | conn, err := grpc.Dial("127.0.0.1:8000", 5 | grpc.WithChainStreamInterceptor(), 6 | grpc.WithInsecure(), 7 | grpc.WithBlock(), 8 | grpc.WithDisableRetry(), 9 | ) 10 | ``` 11 | 12 | 咱们怎么能写出类似这样的调用方式,它是怎么实现的? 13 | 14 | 这篇文章咱们写一个 Demo,其实很简单,一步步往下看。 15 | 16 | ## 一 17 | 18 | `opts …DialOption`,这个是不定参数传递,参数的类型为 `DialOption`,不定参数是指函数传入的参数个数为不定数量,可以不传,也可以为多个。 19 | 20 | 写一个不定参数传递的方法也很简单,看看下面这个方法 1 + 2 + 3 = 6。 21 | 22 | ``` 23 | func Add(a int, args ...int) (result int) { 24 | result += a 25 | for _, arg := range args { 26 | result += arg 27 | } 28 | return 29 | } 30 | 31 | fmt.Println(Add(1, 2, 3)) 32 | 33 | // 输出 6 34 | ``` 35 | 36 | 其实平时我们用的 `fmt.Println()`、`fmt.Sprintf()` 都属于不定参数的传递。 37 | 38 | ## 二 39 | 40 | `WithInsecure()`、`WithBlock()` 类似于这样的 With 方法,其实作用就是修改 `dialOptions` 结构体的配置,之所以这样写我个人认为是面向对象的思想,当配置项调整的时候调用方无需修改。 41 | 42 | ## 场景 43 | 44 | 咱们模拟一个场景,使用 `不定参数` 和 `WithXXX` 这样的写法,写个 Demo,比如我们要做一个从附近找朋友的功能,配置项有:性别、年龄、身高、体重、爱好,我们要找性别为女性,年龄为30岁,身高为160cm,体重为55kg,爱好为爬山的人,希望是这样的调用方式: 45 | 46 | ``` 47 | friends, err := friend.Find("附近的人", 48 | friend.WithSex(1), 49 | friend.WithAge(30), 50 | friend.WithHeight(160), 51 | friend.WithWeight(55), 52 | friend.WithHobby("爬山")) 53 | ``` 54 | 55 | ## 代码实现 56 | 57 | ``` 58 | // option.go 59 | 60 | package friend 61 | 62 | import ( 63 | "sync" 64 | ) 65 | 66 | var ( 67 | cache = &sync.Pool{ 68 | New: func() interface{} { 69 | return &option{sex: 0} 70 | }, 71 | } 72 | ) 73 | 74 | type Option func(*option) 75 | 76 | type option struct { 77 | sex int 78 | age int 79 | height int 80 | weight int 81 | hobby string 82 | } 83 | 84 | func (o *option) reset() { 85 | o.sex = 0 86 | o.age = 0 87 | o.height = 0 88 | o.weight = 0 89 | o.hobby = "" 90 | } 91 | 92 | func getOption() *option { 93 | return cache.Get().(*option) 94 | } 95 | 96 | func releaseOption(opt *option) { 97 | opt.reset() 98 | cache.Put(opt) 99 | } 100 | 101 | // WithSex setup sex, 1=female 2=male 102 | func WithSex(sex int) Option { 103 | return func(opt *option) { 104 | opt.sex = sex 105 | } 106 | } 107 | 108 | // WithAge setup age 109 | func WithAge(age int) Option { 110 | return func(opt *option) { 111 | opt.age = age 112 | } 113 | } 114 | 115 | // WithHeight set up height 116 | func WithHeight(height int) Option { 117 | return func(opt *option) { 118 | opt.height = height 119 | } 120 | } 121 | 122 | // WithWeight set up weight 123 | func WithWeight(weight int) Option { 124 | return func(opt *option) { 125 | opt.weight = weight 126 | } 127 | } 128 | 129 | // WithHobby set up Hobby 130 | func WithHobby(hobby string) Option { 131 | return func(opt *option) { 132 | opt.hobby = hobby 133 | } 134 | } 135 | 136 | ``` 137 | 138 | ``` 139 | // friend.go 140 | 141 | package friend 142 | 143 | import ( 144 | "fmt" 145 | ) 146 | 147 | func Find(where string, options ...Option) (string, error) { 148 | friend := fmt.Sprintf("从 %s 找朋友\n", where) 149 | 150 | opt := getOption() 151 | defer func() { 152 | releaseOption(opt) 153 | }() 154 | 155 | for _, f := range options { 156 | f(opt) 157 | } 158 | 159 | if opt.sex == 1 { 160 | sex := "性别:女性" 161 | friend += fmt.Sprintf("%s\n", sex) 162 | } 163 | if opt.sex == 2 { 164 | sex := "性别:男性" 165 | friend += fmt.Sprintf("%s\n", sex) 166 | } 167 | 168 | if opt.age != 0 { 169 | age := fmt.Sprintf("年龄:%d岁", opt.age) 170 | friend += fmt.Sprintf("%s\n", age) 171 | } 172 | 173 | if opt.height != 0 { 174 | height := fmt.Sprintf("身高:%dcm", opt.height) 175 | friend += fmt.Sprintf("%s\n", height) 176 | } 177 | 178 | if opt.weight != 0 { 179 | weight := fmt.Sprintf("体重:%dkg", opt.weight) 180 | friend += fmt.Sprintf("%s\n", weight) 181 | } 182 | 183 | if opt.hobby != "" { 184 | hobby := fmt.Sprintf("爱好:%s", opt.hobby) 185 | friend += fmt.Sprintf("%s\n", hobby) 186 | } 187 | 188 | return friend, nil 189 | } 190 | 191 | ``` 192 | 193 | ``` 194 | // main.go 195 | 196 | package main 197 | 198 | import ( 199 | "demo/friend" 200 | "fmt" 201 | ) 202 | 203 | func main() { 204 | friends, err := friend.Find("附近的人", 205 | friend.WithSex(1), 206 | friend.WithAge(30), 207 | friend.WithHeight(160), 208 | friend.WithWeight(55), 209 | friend.WithHobby("爬山")) 210 | 211 | if err != nil { 212 | fmt.Println(err) 213 | } 214 | 215 | fmt.Println(friends) 216 | } 217 | 218 | ``` 219 | 220 | ## 输出 221 | 222 | ``` 223 | 从 附近的人 找朋友 224 | 性别:女性 225 | 年龄:30岁 226 | 身高:160cm 227 | 体重:55kg 228 | 爱好:爬山 229 | ``` 230 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/15-time.RFC3339 时间格式化.md: -------------------------------------------------------------------------------- 1 | 在开发过程中,我们有时会遇到这样的问题,将 `2020-11-08T08:18:46+08:00` 转成 `2020-11-08 08:18:46`,怎么解决这个问题? 2 | 3 | 解决这个问题,最好不要用字符串截取,或者说字符串截取是最笨的方法,这应该是时间格式化的问题。 4 | 5 | 我们先看一下 golang time 包中支持的 format 格式: 6 | 7 | ``` 8 | const ( 9 | ANSIC = "Mon Jan _2 15:04:05 2006" 10 | UnixDate = "Mon Jan _2 15:04:05 MST 2006" 11 | RubyDate = "Mon Jan 02 15:04:05 -0700 2006" 12 | RFC822 = "02 Jan 06 15:04 MST" 13 | RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone 14 | RFC850 = "Monday, 02-Jan-06 15:04:05 MST" 15 | RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" 16 | RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone 17 | RFC3339 = "2006-01-02T15:04:05Z07:00" 18 | RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" 19 | Kitchen = "3:04PM" 20 | // Handy time stamps. 21 | Stamp = "Jan _2 15:04:05" 22 | StampMilli = "Jan _2 15:04:05.000" 23 | StampMicro = "Jan _2 15:04:05.000000" 24 | StampNano = "Jan _2 15:04:05.000000000" 25 | ) 26 | ``` 27 | 28 | 我们找到了 `RFC3339` ,那就很简单了,我们封装一个方法 `RFC3339ToCSTLayout`,见下面代码。 29 | 30 | ``` 31 | package timeutil 32 | 33 | import "time" 34 | 35 | var ( 36 | cst *time.Location 37 | ) 38 | 39 | // CSTLayout China Standard Time Layout 40 | const CSTLayout = "2006-01-02 15:04:05" 41 | 42 | func init() { 43 | var err error 44 | if cst, err = time.LoadLocation("Asia/Shanghai"); err != nil { 45 | panic(err) 46 | } 47 | } 48 | 49 | // RFC3339ToCSTLayout convert rfc3339 value to china standard time layout 50 | func RFC3339ToCSTLayout(value string) (string, error) { 51 | ts, err := time.Parse(time.RFC3339, value) 52 | if err != nil { 53 | return "", err 54 | } 55 | 56 | return ts.In(cst).Format(CSTLayout), nil 57 | } 58 | 59 | ``` 60 | 61 | ## 运行一下 62 | 63 | ``` 64 | RFC3339Str := "2020-11-08T08:18:46+08:00" 65 | cst, err := timeutil.RFC3339ToCSTLayout(RFC3339Str) 66 | if err != nil { 67 | fmt.Println(err) 68 | } 69 | fmt.Println(cst) 70 | ``` 71 | 72 | 输出: 73 | 74 | ``` 75 | 2020-11-08 08:18:46 76 | ``` 77 | 78 | ## 小结 79 | 80 | 同理,若遇到 `RFC3339Nano`、`RFC822`、`RFC1123` 等格式,也可以使用类似的方法,只需要在 `time.Parse()` 中指定时间格式即可。 -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/16-常用签名算法的基准测试.md: -------------------------------------------------------------------------------- 1 | 签名验证是为了保证接口安全和识别调用方身份,同时还需要满足以下几点: 2 | 3 | - 可变性:每次的签名必须是不一样的。 4 | - 时效性:每次请求的时效性,过期作废。 5 | - 唯一性:每次的签名是唯一的。 6 | - 完整性:能够对传入数据进行验证,防止篡改。 7 | 8 | 签名规则大同小异,根据自己的业务情况进行制定即可。 9 | 10 | 签名过程中我们会用到的几种算法,接下来分享一下每个算法的基准测试,可能会存在误差,供大家参考。 11 | 12 | ## MD5 单向散列加密 13 | 14 | ``` 15 | func BenchmarkEncrypt(b *testing.B) { 16 | b.ResetTimer() 17 | for i := 0; i < b.N; i++ { 18 | New().Encrypt("123456") 19 | } 20 | } 21 | 22 | // 输出 23 | goos: darwin 24 | goarch: amd64 25 | pkg: github.com/xinliangnote/go-gin-api/pkg/md5 26 | BenchmarkEncrypt-12 10000000 238 ns/op 27 | PASS 28 | ``` 29 | 30 | ## AES 对称加密 31 | 32 | ``` 33 | func BenchmarkEncryptAndDecrypt(b *testing.B) { 34 | b.ResetTimer() 35 | aes := New(key, iv) 36 | for i := 0; i < b.N; i++ { 37 | encryptString, _ := aes.Encrypt("123456") 38 | aes.Decrypt(encryptString) 39 | } 40 | } 41 | 42 | // 输出 43 | goos: darwin 44 | goarch: amd64 45 | pkg: github.com/xinliangnote/go-gin-api/pkg/aes 46 | BenchmarkEncryptAndDecrypt-12 1000000 1009 ns/op 47 | PASS 48 | ``` 49 | 50 | ## RSA 非对称加密 51 | 52 | ``` 53 | func BenchmarkEncryptAndDecrypt(b *testing.B) { 54 | b.ResetTimer() 55 | rsaPublic := NewPublic(publicKey) 56 | rsaPrivate := NewPrivate(privateKey) 57 | for i := 0; i < b.N; i++ { 58 | encryptString, _ := rsaPublic.Encrypt("123456") 59 | rsaPrivate.Decrypt(encryptString) 60 | } 61 | } 62 | 63 | // 输出 64 | goos: darwin 65 | goarch: amd64 66 | pkg: github.com/xinliangnote/go-gin-api/pkg/rsa 67 | BenchmarkEncryptAndDecrypt-12 1000 1345384 ns/op 68 | PASS 69 | ``` 70 | 71 | ## 最后 72 | 73 | JWT 的签名验证也使用过,分享一下 `JWT` 的基准测试,使用的是 `jwt.SigningMethodHS256` 方法。 74 | 75 | ``` 76 | func BenchmarkSignAndParse(b *testing.B) { 77 | b.ResetTimer() 78 | token := New(secret) 79 | for i := 0; i < b.N; i++ { 80 | tokenString, _ := token.Sign(123456789, "xinliangnote") 81 | token.Parse(tokenString) 82 | } 83 | } 84 | 85 | // 输出 86 | goos: darwin 87 | goarch: amd64 88 | pkg: github.com/xinliangnote/go-gin-api/pkg/token 89 | BenchmarkSignAndParse-12 200000 11749 ns/op 90 | PASS 91 | ``` 92 | 93 | 以上代码在 `go-gin-api` 项目中,地址:github.com/xinliangnote/go-gin-api 94 | 95 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/17-分享两个在开发中需注意的小点.md: -------------------------------------------------------------------------------- 1 | 文章目录: 2 | [TOC] 3 | 4 | ## 不要使用 + 和 fmt.Sprintf 操作字符串 5 | 6 | 不要使用 `+` 和 `fmt.Sprintf` 操作字符串,虽然很方便,但是真的很慢! 7 | 8 | 我们要使用 `bytes.NewBufferString` 进行处理。 9 | 10 | 基准测试如下: 11 | 12 | ### + 13 | 14 | ``` 15 | func BenchmarkStringOperation1(b *testing.B) { 16 | b.ResetTimer() 17 | str := "" 18 | for i := 0; i < b.N; i++ { 19 | str += "golang" 20 | } 21 | } 22 | 23 | // 输出 24 | goos: darwin 25 | goarch: amd64 26 | pkg: demo/stringoperation 27 | cpu: Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz 28 | BenchmarkStringOperation1 29 | BenchmarkStringOperation1-12 353318 114135 ns/op 30 | PASS 31 | 32 | Process finished with the exit code 0 33 | ``` 34 | 35 | ### fmt.Sprintf 36 | 37 | ``` 38 | func BenchmarkStringOperation2(b *testing.B) { 39 | b.ResetTimer() 40 | str := "" 41 | for i := 0; i < b.N; i++ { 42 | str = fmt.Sprintf("%s%s", str, "golang") 43 | } 44 | } 45 | 46 | // 输出 47 | goos: darwin 48 | goarch: amd64 49 | pkg: demo/stringoperation 50 | cpu: Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz 51 | BenchmarkStringOperation2 52 | BenchmarkStringOperation2-12 280140 214098 ns/op 53 | PASS 54 | 55 | Process finished with the exit code 0 56 | ``` 57 | 58 | ### bytes.NewBufferString 59 | 60 | ``` 61 | func BenchmarkStringOperation3(b *testing.B) { 62 | b.ResetTimer() 63 | strBuf := bytes.NewBufferString("") 64 | for i := 0; i < b.N; i++ { 65 | strBuf.WriteString("golang") 66 | } 67 | } 68 | 69 | // 输出 70 | goos: darwin 71 | goarch: amd64 72 | pkg: demo/stringoperation 73 | cpu: Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz 74 | BenchmarkStringOperation3 75 | BenchmarkStringOperation3-12 161292136 8.582 ns/op 76 | PASS 77 | 78 | Process finished with the exit code 0 79 | ``` 80 | ## 对于固定字段的键值对,不要使用 map[string]interface{} 81 | 82 | 对于固定字段的键值对,不要使用 `map[string]interface{}`! 83 | 84 | 我们要使用`临时 Struct`。 85 | 86 | 基准测试如下: 87 | 88 | ### map[string]interface{} 89 | 90 | ``` 91 | func BenchmarkStructOperation1(b *testing.B) { 92 | b.ResetTimer() 93 | for i := 0; i < b.N; i++ { 94 | var demo = map[string]interface{}{} 95 | demo["Name"] = "Tom" 96 | demo["Age"] = 30 97 | } 98 | } 99 | 100 | // 输出 101 | goos: darwin 102 | goarch: amd64 103 | pkg: demo/structoperation 104 | cpu: Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz 105 | BenchmarkStructOperation1 106 | BenchmarkStructOperation1-12 43300134 27.97 ns/op 107 | PASS 108 | 109 | Process finished with the exit code 0 110 | ``` 111 | 112 | ### 临时 Struct 113 | 114 | ``` 115 | func BenchmarkStructOperation2(b *testing.B) { 116 | b.ResetTimer() 117 | for i := 0; i < b.N; i++ { 118 | var demo struct { 119 | Name string 120 | Age int 121 | } 122 | demo.Name = "Tom" 123 | demo.Age = 30 124 | } 125 | } 126 | 127 | // 输出 128 | oos: darwin 129 | goarch: amd64 130 | pkg: demo/structoperation 131 | cpu: Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz 132 | BenchmarkStructOperation2 133 | BenchmarkStructOperation2-12 1000000000 0.2388 ns/op 134 | PASS 135 | 136 | Process finished with the exit code 0 137 | ``` 138 | 139 | ## 小结 140 | 141 | 你有类似这样的注意点吗,欢迎留言~ 142 | 143 | 下面推荐阅读的这几篇文章也是关于开发中需要知道的小技术点,更多技术细节和代码讨论,可以加入到我的星球。 144 | 145 | ### 推荐阅读 146 | 147 | - [函数的不定参数你是这样用吗?](https://mp.weixin.qq.com/s/jvSbZ0_g_EFqaR2TmjjO8w) 148 | - [优雅地处理错误真是一门学问啊!](https://mp.weixin.qq.com/s/W_LsZtnjGIKQ-LB6EkRgBA) 149 | - [如何设计 API 接口,实现统一格式返回?](https://mp.weixin.qq.com/s/6c6uapjIzJC9wmjUFyZuZA) 150 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/18-使用 sync.Pool 来减少 GC 压力.md: -------------------------------------------------------------------------------- 1 | **文章目录:** 2 | [TOC] 3 | 4 | ## 前言 5 | 6 | `sync.Pool` 是临时对象池,存储的是临时对象,不可以用它来存储 `socket` 长连接和数据库连接池等。 7 | 8 | `sync.Pool` 本质是用来保存和复用临时对象,以减少内存分配,降低 GC 压力,比如需要使用一个对象,就去 Pool 里面拿,如果拿不到就分配一份,这比起不停生成新的对象,用完了再等待 GC 回收要高效的多。 9 | 10 | ## sync.Pool 11 | 12 | `sync.Pool` 的使用很简单,看下示例代码: 13 | 14 | ``` 15 | package student 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | type student struct { 22 | Name string 23 | Age int 24 | } 25 | 26 | var studentPool = &sync.Pool{ 27 | New: func() interface{} { 28 | return new(student) 29 | }, 30 | } 31 | 32 | func New(name string, age int) *student { 33 | stu := studentPool.Get().(*student) 34 | stu.Name = name 35 | stu.Age = age 36 | return stu 37 | } 38 | 39 | func Release(stu *student) { 40 | stu.Name = "" 41 | stu.Age = 0 42 | studentPool.Put(stu) 43 | } 44 | ``` 45 | 46 | 当使用 `student` 对象时,只需要调用 `New()` 方法获取对象,获取之后使用 `defer` 函数进行释放即可。 47 | 48 | ``` 49 | stu := student.New("tom", 30) 50 | defer student.Release(stu) 51 | 52 | // 业务逻辑 53 | ... 54 | 55 | ``` 56 | 57 | 关于 `sync.Pool` 里面的对象具体是什么时候真正释放,是由系统决定的。 58 | 59 | ## 小结 60 | 61 | 1. 一定要注意存储的是临时对象! 62 | 2. 一定要注意 `Get` 后,要调用 `Put` ! 63 | 64 | 以上,希望对你能够有所帮助。 65 | 66 | ## 推荐阅读 67 | 68 | - [Go - 使用 options 设计模式](https://mp.weixin.qq.com/s/jvSbZ0_g_EFqaR2TmjjO8w) 69 | - [Go - json.Unmarshal 遇到的小坑](https://mp.weixin.qq.com/s/ykZCZb9IAXJaKAx_cO7YjA) 70 | - [Go - 两个在开发中需注意的小点](https://mp.weixin.qq.com/s/-QCG61vh6NVJUWz6tOY7Gw) 71 | - [Go - time.RFC3339 时间格式化](https://mp.weixin.qq.com/s/1pFVaMaWItp8zCXotQ9iBg) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/19-基于逃逸分析来提升程序性能.md: -------------------------------------------------------------------------------- 1 | **文章目录:** 2 | [TOC] 3 | 4 | ## 前言 5 | 6 | 为什么需要了解逃逸分析? 7 | 8 | 因为我们想要提升程序性能,通过逃逸分析我们能够知道变量是分配到堆上还是栈上,如果分配到栈上,内存的分配和释放都是由编译器进行管理,分配和释放的速度非常快,如果分配到堆上,堆不像栈那样可以自动清理,它会引起频繁地进行垃圾回收(`GC`),而垃圾回收会占用比较大的系统开销。 9 | 10 | ## 什么是逃逸分析? 11 | 12 | > 在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法,简单来说就是分析在程序的哪些地方可以访问到该指针。 13 | 14 | 简单的说,它是在对变量放到堆上还是栈上进行分析,该分析在编译阶段完成。如果一个变量超过了函数调用的生命周期,也就是这个变量在函数外部存在引用,编译器会把这个变量分配到堆上,这时我们就说这个变量发生逃逸了。 15 | 16 | ## 如何确定是否逃逸? 17 | 18 | ``` 19 | go run -gcflags '-m -l' main.go 20 | ``` 21 | 22 | ## 可能出现逃逸的场景 23 | 24 | ### 01 25 | 26 | ``` 27 | package main 28 | 29 | type Student struct { 30 | Name interface{} 31 | } 32 | 33 | func main() { 34 | stu := new(Student) 35 | stu.Name = "tom" 36 | 37 | } 38 | ``` 39 | 40 | 分析结果: 41 | 42 | ``` 43 | go run -gcflags '-m -l' 01.go 44 | # command-line-arguments 45 | ./01.go:8:12: new(Student) does not escape 46 | ./01.go:9:11: "tom" escapes to heap 47 | ``` 48 | 49 | `interface{}` 赋值,会发生逃逸,优化方案是将类型设置为固定类型,例如:`string` 50 | 51 | ``` 52 | package main 53 | 54 | type Student struct { 55 | Name string 56 | } 57 | 58 | func main() { 59 | stu := new(Student) 60 | stu.Name = "tom" 61 | 62 | } 63 | ``` 64 | 65 | 分析结果: 66 | 67 | ``` 68 | go run -gcflags '-m -l' 01.go 69 | # command-line-arguments 70 | ./01.go:8:12: new(Student) does not escape 71 | ``` 72 | 73 | ### 02 74 | 75 | ``` 76 | package main 77 | 78 | type Student struct { 79 | Name string 80 | } 81 | 82 | func GetStudent() *Student { 83 | stu := new(Student) 84 | stu.Name = "tom" 85 | return stu 86 | } 87 | 88 | func main() { 89 | GetStudent() 90 | } 91 | 92 | ``` 93 | 94 | 分析结果: 95 | 96 | ``` 97 | go run -gcflags '-m -l' 02.go 98 | # command-line-arguments 99 | ./02.go:8:12: new(Student) escapes to heap 100 | ``` 101 | 102 | 返回指针类型,会发生逃逸,优化方案视情况而定。 103 | 104 | 函数传递指针和传值哪个效率高吗?我们知道传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加 `GC` 的负担,所以传递指针不一定是高效的。 105 | 106 | 不要盲目使用变量指针作为参数,虽然减少了复制,但变量逃逸的开销可能更大。 107 | 108 | ### 03 109 | 110 | ``` 111 | package main 112 | 113 | func main() { 114 | nums := make([]int, 10000, 10000) 115 | 116 | for i := range nums { 117 | nums[i] = i 118 | } 119 | } 120 | ``` 121 | 122 | 分析结果: 123 | 124 | ``` 125 | go run -gcflags '-m -l' 03.go 126 | # command-line-arguments 127 | ./03.go:4:14: make([]int, 10000, 10000) escapes to heap 128 | ``` 129 | 130 | 栈空间不足,会发生逃逸,优化方案尽量设置容量,如果容量实在过大那就没办法了。 131 | 132 | ## 小结 133 | 134 | 1. 逃逸分析是编译器在静态编译时完成的。 135 | 2. 逃逸分析后可以确定哪些变量可以分配在栈上,栈的性能好。 136 | 137 | 以上,希望对你能够有所帮助。 138 | 139 | ## 推荐阅读 140 | 141 | - [Go - 使用 sync.Pool 来减少 GC 压力](https://mp.weixin.qq.com/s/0NVp59uI8h9WTp68wtb7XQ) 142 | - [Go - 使用 options 设计模式](https://mp.weixin.qq.com/s/jvSbZ0_g_EFqaR2TmjjO8w) 143 | - [Go - json.Unmarshal 遇到的小坑](https://mp.weixin.qq.com/s/ykZCZb9IAXJaKAx_cO7YjA) 144 | - [Go - 两个在开发中需注意的小点](https://mp.weixin.qq.com/s/-QCG61vh6NVJUWz6tOY7Gw) 145 | - [Go - time.RFC3339 时间格式化](https://mp.weixin.qq.com/s/1pFVaMaWItp8zCXotQ9iBg) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/20-使用 sync.Map 来解决 map 的并发操作问题.md: -------------------------------------------------------------------------------- 1 | **文章目录:** 2 | [TOC] 3 | 4 | ## 前言 5 | 6 | 在 `Golang` 中 `map` 不是并发安全的,自 1.9 才引入了 `sync.Map` ,`sync.Map` 的引入确实解决了 `map` 的并发安全问题,不过 `sync.Map` 却没有实现 `len()` 函数,如果想要计算 `sync.Map` 的长度,稍微有点麻烦,需要使用 `Range` 函数。 7 | 8 | ## map 并发操作出现问题 9 | 10 | ``` 11 | func main() { 12 | demo := make(map[int]int) 13 | 14 | go func() { 15 | for j := 0; j < 1000; j++ { 16 | demo[j] = j 17 | } 18 | }() 19 | 20 | go func() { 21 | for j := 0; j < 1000; j++ { 22 | fmt.Println(demo[j]) 23 | } 24 | }() 25 | 26 | time.Sleep(time.Second * 1) 27 | } 28 | ``` 29 | 30 | 执行输出: 31 | 32 | ``` 33 | fatal error: concurrent map read and map write 34 | ``` 35 | 36 | ## sync.Map 解决并发操作问题 37 | 38 | ``` 39 | func main() { 40 | demo := sync.Map{} 41 | 42 | go func() { 43 | for j := 0; j < 1000; j++ { 44 | demo.Store(j, j) 45 | } 46 | }() 47 | 48 | go func() { 49 | for j := 0; j < 1000; j++ { 50 | fmt.Println(demo.Load(j)) 51 | } 52 | }() 53 | 54 | time.Sleep(time.Second * 1) 55 | } 56 | ``` 57 | 58 | 执行输出: 59 | 60 | ``` 61 | false 62 | 1 true 63 | 64 | ... 65 | 66 | 999 true 67 | ``` 68 | 69 | ## 计算 map 长度 70 | 71 | ``` 72 | func main() { 73 | demo := make(map[int]int) 74 | 75 | for j := 0; j < 1000; j++ { 76 | demo[j] = j 77 | } 78 | 79 | fmt.Println("len of demo:", len(demo)) 80 | } 81 | ``` 82 | 83 | 执行输出: 84 | 85 | ``` 86 | len of demo: 1000 87 | ``` 88 | 89 | ## 计算 sync.Map 长度 90 | 91 | ``` 92 | func main() { 93 | demo := sync.Map{} 94 | 95 | for j := 0; j < 1000; j++ { 96 | demo.Store(j, j) 97 | } 98 | 99 | lens := 0 100 | demo.Range(func(key, value interface{}) bool { 101 | lens++ 102 | return true 103 | }) 104 | 105 | fmt.Println("len of demo:", lens) 106 | } 107 | ``` 108 | 109 | 执行输出: 110 | 111 | ``` 112 | len of demo: 1000 113 | ``` 114 | 115 | ## 小结 116 | 117 | 1. `Load` 加载 key 数据 118 | 2. `Store` 更新或新增 key 数据 119 | 3. `Delete` 删除 key 数据 120 | 4. `Range` 遍历数据 121 | 5. `LoadOrStore` 如果存在 key 数据则返回,反之则设置 122 | 6. `LoadAndDelete` 如果存在 key 数据则删除 123 | 124 | 以上,希望对你能够有所帮助。 125 | 126 | ## 推荐阅读 127 | 128 | - [Go - 基于逃逸分析来提升程序性能](https://mp.weixin.qq.com/s/gAz87qPA8sBJMeq6MZbqwg) 129 | - [Go - 使用 sync.Pool 来减少 GC 压力](https://mp.weixin.qq.com/s/0NVp59uI8h9WTp68wtb7XQ) 130 | - [Go - 使用 options 设计模式](https://mp.weixin.qq.com/s/jvSbZ0_g_EFqaR2TmjjO8w) 131 | - [Go - json.Unmarshal 遇到的小坑](https://mp.weixin.qq.com/s/ykZCZb9IAXJaKAx_cO7YjA) 132 | - [Go - 两个在开发中需注意的小点](https://mp.weixin.qq.com/s/-QCG61vh6NVJUWz6tOY7Gw) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/21-使用 sync.WaitGroup 来实现并发操作 .md: -------------------------------------------------------------------------------- 1 | **文章目录:** 2 | [TOC] 3 | 4 | ## 前言 5 | 6 | 如果你有一个任务可以分解成多个子任务进行处理,同时每个子任务没有先后执行顺序的限制,等到全部子任务执行完毕后,再进行下一步处理。这时每个子任务的执行可以并发处理,这种情景下适合使用 `sync.WaitGroup`。 7 | 8 | 虽然 `sync.WaitGroup` 使用起来比较简单,但是一不留神很有可能踩到坑里。 9 | 10 | ## sync.WaitGroup 正确使用 11 | 12 | 比如,有一个任务需要执行 3 个子任务,那么可以这样写: 13 | 14 | ``` 15 | func main() { 16 | var wg sync.WaitGroup 17 | 18 | wg.Add(3) 19 | 20 | go handlerTask1(&wg) 21 | go handlerTask2(&wg) 22 | go handlerTask3(&wg) 23 | 24 | wg.Wait() 25 | 26 | fmt.Println("全部任务执行完毕.") 27 | } 28 | 29 | func handlerTask1(wg *sync.WaitGroup) { 30 | defer wg.Done() 31 | fmt.Println("执行任务 1") 32 | } 33 | 34 | func handlerTask2(wg *sync.WaitGroup) { 35 | defer wg.Done() 36 | fmt.Println("执行任务 2") 37 | } 38 | 39 | func handlerTask3(wg *sync.WaitGroup) { 40 | defer wg.Done() 41 | fmt.Println("执行任务 3") 42 | } 43 | ``` 44 | 45 | 执行输出: 46 | 47 | ``` 48 | 执行任务 3 49 | 执行任务 1 50 | 执行任务 2 51 | 全部任务执行完毕. 52 | ``` 53 | 54 | ## sync.WaitGroup 闭坑指南 55 | 56 | ### 01 57 | 58 | ``` 59 | // 正确 60 | go handlerTask1(&wg) 61 | 62 | // 错误 63 | go handlerTask1(wg) 64 | ``` 65 | 66 | 执行子任务时,使用的 `sync.WaitGroup` 一定要是 `wg` 的引用类型! 67 | 68 | ### 02 69 | 70 | 注意不要将 `wg.Add()` 放在 `go handlerTask1(&wg)` 中! 71 | 72 | 例如: 73 | 74 | ``` 75 | // 错误 76 | var wg sync.WaitGroup 77 | 78 | go handlerTask1(&wg) 79 | 80 | wg.Wait() 81 | 82 | ... 83 | 84 | func handlerTask1(wg *sync.WaitGroup) { 85 | wg.Add(1) 86 | defer wg.Done() 87 | fmt.Println("执行任务 1") 88 | } 89 | ``` 90 | 91 | 注意 `wg.Add()` 一定要在 `wg.Wait()` 执行前执行! 92 | 93 | ### 03 94 | 95 | 注意 `wg.Add()` 和 `wg.Done()` 的计数器保持一致!其实 `wg.Done()` 就是执行的 `wg.Add(-1)` 。 96 | 97 | ## 小结 98 | 99 | `sync.WaitGroup` 使用起来比较简单,一定要注意不要踩到坑里。 100 | 101 | 其实 `sync.WaitGroup` 使用场景比较局限,仅适用于等待全部子任务执行完毕后,再进行下一步处理,如果需求是当第一个子任务执行失败时,通知其他子任务停止运行,这时 `sync.WaitGroup` 是无法满足的,需要使用到通知机制(`channel`)。 102 | 103 | 以上,希望对你能够有所帮助。 104 | 105 | ## 推荐阅读 106 | 107 | - [Go - 使用 sync.Map 解决 map 并发安全问题](https://mp.weixin.qq.com/s/WOuzCJWeuH41qoUP4_zRQA) 108 | - [Go - 基于逃逸分析来提升程序性能](https://mp.weixin.qq.com/s/gAz87qPA8sBJMeq6MZbqwg) 109 | - [Go - 使用 sync.Pool 来减少 GC 压力](https://mp.weixin.qq.com/s/0NVp59uI8h9WTp68wtb7XQ) 110 | - [Go - 使用 options 设计模式](https://mp.weixin.qq.com/s/jvSbZ0_g_EFqaR2TmjjO8w) 111 | - [Go - 两个在开发中需注意的小点](https://mp.weixin.qq.com/s/-QCG61vh6NVJUWz6tOY7Gw) -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | [基础语法](https://github.com/xinliangnote/Go/blob/master/00-基础语法) 4 | 5 | ## 运行 6 | 7 | 编辑器运行 或 命令行运行。 8 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | 9 | const name string = "Tom" 10 | fmt.Println(name) 11 | 12 | const age = 30 13 | fmt.Println(age) 14 | 15 | const name_1, name_2 string = "Tom", "Jay" 16 | fmt.Println(name_1, name_2) 17 | 18 | const name_3, age_1 = "Tom", 30 19 | fmt.Println(name_3, age_1) 20 | } 21 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_10.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | sli := [] int {1, 2, 3, 4, 5, 6, 7, 8} 9 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 10 | 11 | //删除尾部 2 个元素 12 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[:len(sli)-2]),cap(sli[:len(sli)-2]),sli[:len(sli)-2]) 13 | 14 | //删除开头 2 个元素 15 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[2:]),cap(sli[2:]),sli[2:]) 16 | 17 | //删除中间 2 个元素 18 | sli = append(sli[:3], sli[3+2:]...) 19 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 20 | } 21 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_11.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Person struct { 8 | Name string 9 | Age int 10 | } 11 | 12 | func main() { 13 | var p1 Person 14 | p1.Name = "Tom" 15 | p1.Age = 30 16 | fmt.Println("p1 =", p1) 17 | 18 | var p2 = Person{Name:"Burke", Age:31} 19 | fmt.Println("p2 =", p2) 20 | 21 | p3 := Person{Name:"Aaron", Age:32} 22 | fmt.Println("p2 =", p3) 23 | 24 | //匿名结构体 25 | p4 := struct { 26 | Name string 27 | Age int 28 | } {Name:"匿名", Age:33} 29 | fmt.Println("p4 =", p4) 30 | } 31 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_12.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type Result struct { 9 | Code int `json:"code"` 10 | Message string `json:"msg"` 11 | } 12 | 13 | func main() { 14 | var res Result 15 | res.Code = 200 16 | res.Message = "success" 17 | 18 | //序列化 19 | jsons, errs := json.Marshal(res) 20 | if errs != nil { 21 | fmt.Println("json marshal error:", errs) 22 | } 23 | fmt.Println("json data :", string(jsons)) 24 | 25 | //反序列化 26 | var res2 Result 27 | errs = json.Unmarshal(jsons, &res2) 28 | if errs != nil { 29 | fmt.Println("json unmarshal error:", errs) 30 | } 31 | fmt.Println("res2 :", res2) 32 | } 33 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_13.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type Result struct { 9 | Code int `json:"code"` 10 | Message string `json:"msg"` 11 | } 12 | 13 | func main() { 14 | var res Result 15 | res.Code = 200 16 | res.Message = "success" 17 | toJson(&res) 18 | setData(&res) 19 | toJson(&res) 20 | } 21 | 22 | func setData (res *Result) { 23 | res.Code = 500 24 | res.Message = "fail" 25 | } 26 | 27 | func toJson (res *Result) { 28 | jsons, errs := json.Marshal(res) 29 | if errs != nil { 30 | fmt.Println("json marshal error:", errs) 31 | } 32 | fmt.Println("json data :", string(jsons)) 33 | } 34 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_14.go: -------------------------------------------------------------------------------- 1 | //demo_14.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | var p1 map[int]string 10 | p1 = make(map[int]string) 11 | p1[1] = "Tom" 12 | fmt.Println("p1 :", p1) 13 | 14 | var p2 map[int]string = map[int]string{} 15 | p2[1] = "Tom" 16 | fmt.Println("p2 :", p2) 17 | 18 | var p3 map[int]string = make(map[int]string) 19 | p3[1] = "Tom" 20 | fmt.Println("p3 :", p3) 21 | 22 | p4 := map[int]string{} 23 | p4[1] = "Tom" 24 | fmt.Println("p4 :", p4) 25 | 26 | p5 := make(map[int]string) 27 | p5[1] = "Tom" 28 | fmt.Println("p5 :", p5) 29 | 30 | p6 := map[int]string{ 31 | 1 : "Tom", 32 | } 33 | fmt.Println("p6 :", p6) 34 | } 35 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_15.go: -------------------------------------------------------------------------------- 1 | //demo_15.go 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | res := make(map[string]interface{}) 11 | res["code"] = 200 12 | res["msg"] = "success" 13 | res["data"] = map[string]interface{}{ 14 | "username" : "Tom", 15 | "age" : "30", 16 | "hobby" : []string{"读书","爬山"}, 17 | } 18 | fmt.Println("map data :", res) 19 | 20 | //序列化 21 | jsons, errs := json.Marshal(res) 22 | if errs != nil { 23 | fmt.Println("json marshal error:", errs) 24 | } 25 | fmt.Println("") 26 | fmt.Println("--- map to json ---") 27 | fmt.Println("json data :", string(jsons)) 28 | 29 | //反序列化 30 | res2 := make(map[string]interface{}) 31 | errs = json.Unmarshal([]byte(jsons), &res2) 32 | if errs != nil { 33 | fmt.Println("json marshal error:", errs) 34 | } 35 | fmt.Println("") 36 | fmt.Println("--- json to map ---") 37 | fmt.Println("map data :", res2) 38 | } 39 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_16.go: -------------------------------------------------------------------------------- 1 | //demo_16.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | person := map[int]string{ 10 | 1 : "Tom", 11 | 2 : "Aaron", 12 | 3 : "John", 13 | } 14 | fmt.Println("data :",person) 15 | 16 | delete(person, 2) 17 | fmt.Println("data :",person) 18 | 19 | person[2] = "Jack" 20 | person[3] = "Kevin" 21 | fmt.Println("data :",person) 22 | } 23 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_17.go: -------------------------------------------------------------------------------- 1 | //demo_17.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | person := map[int]string{ 10 | 1 : "Tom", 11 | 2 : "Aaron", 12 | 3 : "John", 13 | } 14 | fmt.Println("data :",person) 15 | 16 | delete(person, 2) 17 | fmt.Println("data :",person) 18 | 19 | person[2] = "Jack" 20 | person[3] = "Kevin" 21 | fmt.Println("data :",person) 22 | } 23 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_18.go: -------------------------------------------------------------------------------- 1 | //demo_18.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | 10 | person := [3] string {"Tom", "Aaron", "John"} 11 | fmt.Printf("len=%d cap=%d array=%v\n",len(person),cap(person),person) 12 | 13 | fmt.Println("") 14 | 15 | //循环 16 | for k, v := range person { 17 | fmt.Printf("person[%d]: %s\n", k, v) 18 | } 19 | 20 | fmt.Println("") 21 | 22 | for i := range person { 23 | fmt.Printf("person[%d]: %s\n", i, person[i]) 24 | } 25 | 26 | fmt.Println("") 27 | 28 | for i := 0; i < len(person); i++ { 29 | fmt.Printf("person[%d]: %s\n", i, person[i]) 30 | } 31 | 32 | fmt.Println("") 33 | 34 | //使用空白符 35 | for _, name := range person { 36 | fmt.Println("name :", name) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_19.go: -------------------------------------------------------------------------------- 1 | //demo_19.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | 10 | person := [] string {"Tom", "Aaron", "John"} 11 | fmt.Printf("len=%d cap=%d slice=%v\n",len(person),cap(person),person) 12 | 13 | fmt.Println("") 14 | 15 | //循环 16 | for k, v := range person { 17 | fmt.Printf("person[%d]: %s\n", k, v) 18 | } 19 | 20 | fmt.Println("") 21 | 22 | for i := range person { 23 | fmt.Printf("person[%d]: %s\n", i, person[i]) 24 | } 25 | 26 | fmt.Println("") 27 | 28 | for i := 0; i < len(person); i++ { 29 | fmt.Printf("person[%d]: %s\n", i, person[i]) 30 | } 31 | 32 | fmt.Println("") 33 | 34 | //使用空白符 35 | for _, name := range person { 36 | fmt.Println("name :", name) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var age_1 uint8 = 31 9 | var age_2 = 32 10 | age_3 := 33 11 | fmt.Println(age_1, age_2, age_3) 12 | 13 | var age_4, age_5, age_6 int = 31, 32, 33 14 | fmt.Println(age_4, age_5, age_6) 15 | 16 | var name_1, age_7 = "Tom", 30 17 | fmt.Println(name_1, age_7) 18 | 19 | name_2, is_boy, height := "Jay", true, 180.66 20 | fmt.Println(name_2, is_boy, height) 21 | } 22 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_20.go: -------------------------------------------------------------------------------- 1 | //demo_20.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | 10 | person := map[int]string{ 11 | 1 : "Tom", 12 | 2 : "Aaron", 13 | 3 : "John", 14 | } 15 | 16 | fmt.Printf("len=%d map=%v\n", len(person), person) 17 | 18 | fmt.Println("") 19 | 20 | //循环 21 | for k, v := range person { 22 | fmt.Printf("person[%d]: %s\n", k, v) 23 | } 24 | 25 | fmt.Println("") 26 | 27 | for i := range person { 28 | fmt.Printf("person[%d]: %s\n", i, person[i]) 29 | } 30 | 31 | fmt.Println("") 32 | 33 | for i := 1; i <= len(person); i++ { 34 | fmt.Printf("person[%d]: %s\n", i, person[i]) 35 | } 36 | 37 | fmt.Println("") 38 | 39 | //使用空白符 40 | for _, name := range person { 41 | fmt.Println("name :", name) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_21.go: -------------------------------------------------------------------------------- 1 | //demo_21.go 2 | package main 3 | 4 | import "fmt" 5 | 6 | func main() { 7 | 8 | for i := 1; i <= 10; i++ { 9 | if i == 6 { 10 | break 11 | } 12 | fmt.Println("i =", i) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_22.go: -------------------------------------------------------------------------------- 1 | //demo_22.go 2 | package main 3 | 4 | import "fmt" 5 | 6 | func main() { 7 | 8 | for i := 1; i <= 10; i++ { 9 | if i == 6 { 10 | continue 11 | } 12 | fmt.Println("i =", i) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_23.go: -------------------------------------------------------------------------------- 1 | //demo_23.go 2 | package main 3 | 4 | import "fmt" 5 | 6 | func main() { 7 | fmt.Println("begin") 8 | 9 | for i := 1; i <= 10; i++ { 10 | if i == 6 { 11 | goto END 12 | } 13 | fmt.Println("i =", i) 14 | } 15 | 16 | END : 17 | fmt.Println("end") 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_24.go: -------------------------------------------------------------------------------- 1 | //demo_24.go 2 | package main 3 | 4 | import "fmt" 5 | 6 | func main() { 7 | i := 3 8 | fmt.Printf("当 i = %d 时:\n", i) 9 | 10 | switch i { 11 | case 1: 12 | fmt.Println("输出 i =", 1) 13 | case 2: 14 | fmt.Println("输出 i =", 2) 15 | case 3: 16 | fmt.Println("输出 i =", 3) 17 | fallthrough 18 | case 4,5,6: 19 | fmt.Println("输出 i =", "4 or 5 or 6") 20 | case 7: 21 | fmt.Println("输出 i =", "7") 22 | default: 23 | fmt.Println("输出 i =", "xxx") 24 | } 25 | } -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_25.go: -------------------------------------------------------------------------------- 1 | //demo_25.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | str := "abcdef" 11 | fmt.Printf("MD5(%s): %s\n", str, MD5("abcdef")) 12 | fmt.Printf("current time str : %s\n", getTimeStr()) 13 | fmt.Printf("current time unix : %d\n", getTimeInt()) 14 | } 15 | 16 | // 获取当前时间字符串 17 | func getTimeStr() string { 18 | return time.Now().Format("2006-01-02 15:04:05") 19 | } 20 | 21 | // 获取当前时间戳 22 | func getTimeInt() int64 { 23 | return time.Now().Unix() 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_26.go: -------------------------------------------------------------------------------- 1 | //demo_26.go 2 | package main 3 | 4 | import ( 5 | "crypto/md5" 6 | "encoding/hex" 7 | "fmt" 8 | "sort" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | str := "12345" 14 | fmt.Printf("MD5(%s): %s\n", str, MD5(str)) 15 | 16 | fmt.Printf("current time str : %s\n", getTimeStr()) 17 | 18 | fmt.Printf("current time unix : %d\n", getTimeInt()) 19 | 20 | params := map[string]interface{} { 21 | "name" : "Tom", 22 | "pwd" : "123456", 23 | "age" : 30, 24 | } 25 | fmt.Printf("sign : %s\n", createSign(params)) 26 | } 27 | 28 | // MD5 方法 29 | func MD5(str string) string { 30 | s := md5.New() 31 | s.Write([]byte(str)) 32 | return hex.EncodeToString(s.Sum(nil)) 33 | } 34 | 35 | // 获取当前时间字符串 36 | func getTimeStr() string { 37 | return time.Now().Format("2006-01-02 15:04:05") 38 | } 39 | 40 | // 获取当前时间戳 41 | func getTimeInt() int64 { 42 | return time.Now().Unix() 43 | } 44 | 45 | // 生成签名 46 | func createSign(params map[string]interface{}) string { 47 | var key []string 48 | var str = "" 49 | for k := range params { 50 | key = append(key, k) 51 | } 52 | sort.Strings(key) 53 | for i := 0; i < len(key); i++ { 54 | if i == 0 { 55 | str = fmt.Sprintf("%v=%v", key[i], params[key[i]]) 56 | } else { 57 | str = str + fmt.Sprintf("&xl_%v=%v", key[i], params[key[i]]) 58 | } 59 | } 60 | // 自定义密钥 61 | var secret = "123456789" 62 | 63 | // 自定义签名算法 64 | sign := MD5(MD5(str) + MD5(secret)) 65 | return sign 66 | } 67 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | fmt.Print("输出到控制台不换行") 9 | fmt.Println("---") 10 | fmt.Println("输出到控制台并换行") 11 | fmt.Printf("name=%s,age=%d\n", "Tom", 30) 12 | fmt.Printf("name=%s,age=%d,height=%v\n", "Tom", 30, fmt.Sprintf("%.2f", 180.567)) 13 | } 14 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_4.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | //一维数组 9 | var arr_1 [5] int 10 | fmt.Println(arr_1) 11 | 12 | var arr_2 = [5] int {1, 2, 3, 4, 5} 13 | fmt.Println(arr_2) 14 | 15 | arr_3 := [5] int {1, 2, 3, 4, 5} 16 | fmt.Println(arr_3) 17 | 18 | arr_4 := [...] int {1, 2, 3, 4, 5, 6} 19 | fmt.Println(arr_4) 20 | 21 | arr_5 := [5] int {0:3, 1:5, 4:6} 22 | fmt.Println(arr_5) 23 | 24 | //二维数组 25 | var arr_6 = [3][5] int {{1, 2, 3, 4, 5}, {9, 8, 7, 6, 5}, {3, 4, 5, 6, 7}} 26 | fmt.Println(arr_6) 27 | 28 | arr_7 := [3][5] int {{1, 2, 3, 4, 5}, {9, 8, 7, 6, 5}, {3, 4, 5, 6, 7}} 29 | fmt.Println(arr_7) 30 | 31 | arr_8 := [...][5] int {{1, 2, 3, 4, 5}, {9, 8, 7, 6, 5}, {0:3, 1:5, 4:6}} 32 | fmt.Println(arr_8) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_5.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var arr = [5] int {1, 2, 3, 4, 5} 9 | modifyArr(arr) 10 | fmt.Println(arr) 11 | } 12 | 13 | func modifyArr(a [5] int){ 14 | a[1] = 20 15 | } 16 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_6.go: -------------------------------------------------------------------------------- 1 | 2 | //demo_6.go 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | func main() { 11 | var arr = [5] int {1, 2, 3, 4, 5} 12 | modifyArr(&arr) 13 | fmt.Println(arr) 14 | } 15 | 16 | func modifyArr(a *[5] int){ 17 | a[1] = 20 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_7.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | var sli_1 [] int //nil 切片 9 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_1),cap(sli_1),sli_1) 10 | 11 | var sli_2 = [] int {} //空切片 12 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_1),cap(sli_2),sli_2) 13 | 14 | var sli_3 = [] int {1, 2, 3, 4, 5} 15 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_3),cap(sli_3),sli_3) 16 | 17 | sli_4 := [] int {1, 2, 3, 4, 5} 18 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_4),cap(sli_4),sli_4) 19 | 20 | var sli_5 [] int = make([] int, 5, 8) 21 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_5),cap(sli_5),sli_5) 22 | 23 | sli_6 := make([] int, 5, 9) 24 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli_6),cap(sli_6),sli_6) 25 | } 26 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_8.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | sli := [] int {1, 2, 3, 4, 5, 6} 9 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 10 | 11 | fmt.Println("sli[1] ==", sli[1]) 12 | fmt.Println("sli[:] ==", sli[:]) 13 | fmt.Println("sli[1:] ==", sli[1:]) 14 | fmt.Println("sli[:4] ==", sli[:4]) 15 | 16 | fmt.Println("sli[0:3] ==", sli[0:3]) 17 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[0:3]),cap(sli[0:3]),sli[0:3]) 18 | 19 | fmt.Println("sli[0:3:4] ==", sli[0:3:4]) 20 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli[0:3:4]),cap(sli[0:3:4]),sli[0:3:4]) 21 | } 22 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/codes/demo_9.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | sli := [] int {4, 5, 6} 9 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 10 | 11 | sli = append(sli, 7) 12 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 13 | 14 | sli = append(sli, 8) 15 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 16 | 17 | sli = append(sli, 9) 18 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 19 | 20 | sli = append(sli, 10) 21 | fmt.Printf("len=%d cap=%d slice=%v\n",len(sli),cap(sli),sli) 22 | } 23 | -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/01-环境安装/1_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/01-环境安装/1_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/01-环境安装/1_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/01-环境安装/1_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/01-环境安装/1_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/01-环境安装/1_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/01-环境安装/1_go_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/01-环境安装/1_go_4.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/02-变量声明/2_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/02-变量声明/2_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/02-变量声明/2_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/02-变量声明/2_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/02-变量声明/2_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/02-变量声明/2_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/03-数组/3_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/03-数组/3_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/03-数组/3_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/03-数组/3_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/03-数组/3_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/03-数组/3_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/04-切片/4_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/04-切片/4_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/04-切片/4_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/04-切片/4_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/04-切片/4_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/04-切片/4_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/04-切片/4_go_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/04-切片/4_go_4.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/05-结构体/5_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/05-结构体/5_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/05-结构体/5_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/05-结构体/5_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/05-结构体/5_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/05-结构体/5_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/06-集合/6_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/06-集合/6_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/06-集合/6_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/06-集合/6_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/06-集合/6_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/06-集合/6_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/07-循环/7_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/07-循环/7_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/07-循环/7_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/07-循环/7_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/07-循环/7_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/07-循环/7_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/07-循环/7_go_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/07-循环/7_go_4.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/07-循环/7_go_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/07-循环/7_go_5.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/07-循环/7_go_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/07-循环/7_go_6.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/08-函数/8_go_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/08-函数/8_go_0.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/08-函数/8_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/08-函数/8_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/08-函数/8_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/08-函数/8_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/08-函数/8_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/08-函数/8_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/08-函数/8_go_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/08-函数/8_go_4.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/08-函数/8_go_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/08-函数/8_go_5.png -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/10_go_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/10_go_1.jpeg -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/11_go_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/11_go_1.jpeg -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/11_go_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/11_go_2.jpeg -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/11_go_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/11_go_3.jpeg -------------------------------------------------------------------------------- /00.go语言基础/00-基础语法/images/qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/00-基础语法/images/qr.jpg -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/01-框架安装.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 看下 Gin 框架的官方介绍: 4 | 5 | > Gin 是一个用 Go (Golang) 编写的 web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架, 由于 httprouter,速度提高了近 40 倍。 如果你是性能和高效的追求者, 你会爱上 Gin。 6 | 7 | 是的,就是用 Gin 来写 API 接口。 8 | 9 | ## Gin 安装 10 | 11 | 必须要先安装 Go,Go 的安装可以参考:[Go - 环境安装](https://mp.weixin.qq.com/s/ByhEuCncxcXvq7am7D4IPg)。 12 | 13 | 框架安装可以参考官网: 14 | 15 | https://gin-gonic.com/zh-cn/docs/quickstart/ 16 | 17 | 我在安装时,用的是 dep 安装,给大家分享下。 18 | 19 | **dep 是啥 ?** 20 | 21 | 它是 Golang 官方依赖管理工具,可以认为它与 PHP 中的 composer 类似。 22 | 23 | 在这就不多做介绍了,可以自己去了解,安装也比较简单。 24 | 25 | 我本机是 Mac,安装只需一个命令: 26 | 27 | ``` 28 | brew install dep 29 | ``` 30 | 31 | 咱们接下来创建一个新项目:ginDemo。 32 | 33 | 在 ginDemo 目录下执行: 34 | 35 | ``` 36 | dep init 37 | ``` 38 | 39 | 执行完毕,会生成如下三个文件: 40 | 41 | ``` 42 | ├─ ginDemo 43 | │ ├─ vendor 44 | │ ├─ Gopkg.toml 45 | │ ├─ Gopkg.lock 46 | ``` 47 | 48 | - 依赖包都会下载到 `vendor` 目录。 49 | - 需要的依赖配置写在 `Gopkg.toml` 文件。 50 | - `Gopkg.lock` 暂时可以不用管。 51 | 52 | 在 `Gopkg.toml` 文件中增加依赖: 53 | 54 | ``` 55 | [[constraint]] 56 | name = "github.com/gin-gonic/gin" 57 | version = "1.4.0" 58 | ``` 59 | 60 | 新增一个 main.go 文件: 61 | 62 | ``` 63 | // 官方 Demo 64 | package main 65 | 66 | import "github.com/gin-gonic/gin" 67 | 68 | func main() { 69 | r := gin.Default() 70 | r.GET("/ping", func(c *gin.Context) { 71 | c.JSON(200, gin.H{ 72 | "message": "pong", 73 | }) 74 | }) 75 | r.Run() // listen and serve on 0.0.0.0:8080 76 | } 77 | ``` 78 | 79 | ginDemo 目录下执行: 80 | 81 | ``` 82 | dep ensure 83 | ``` 84 | 85 | 执行完毕,`vendor` 目录会存在安装包,这时整体目录结构如下: 86 | 87 | ``` 88 | ├─ ginDemo 89 | │ ├─ vendor 90 | │ ├── github.com 91 | │ ├── ... 92 | │ ├── golang.org 93 | │ ├── ... 94 | │ ├── gopkg.in 95 | │ ├── ... 96 | │ ├─ Gopkg.toml 97 | │ ├─ Gopkg.lock 98 | │ ├─ main.go 99 | ``` 100 | 101 | ginDemo 目录下执行: 102 | 103 | ``` 104 | go run main.go 105 | ``` 106 | 107 | 浏览器访问:`http://localhost:8080/ping` 108 | 109 | ![](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/images/01-框架安装/1_go_1.png) 110 | 111 | 大功告成! -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/06-统一定义 API 错误码.md: -------------------------------------------------------------------------------- 1 | ## 改之前 2 | 3 | 在使用 `gin` 开发接口的时候,返回接口数据是这样写的。 4 | 5 | ``` 6 | type response struct { 7 | Code int `json:"code"` 8 | Msg string `json:"msg"` 9 | Data interface{} `json:"data"` 10 | } 11 | 12 | // always return http.StatusOK 13 | c.JSON(http.StatusOK, response{ 14 | Code: 20101, 15 | Msg: "用户手机号不合法", 16 | Data: nil, 17 | }) 18 | ``` 19 | 20 | 这种写法 `code`、`msg` 都是在哪需要返回在哪定义,没有进行统一管理。 21 | 22 | ## 改之后 23 | 24 | ``` 25 | // 比如,返回“用户手机号不合法”错误 26 | c.JSON(http.StatusOK, errno.ErrUserPhone.WithID(c.GetString("trace-id"))) 27 | 28 | // 正确返回 29 | c.JSON(http.StatusOK, errno.OK.WithData(data).WithID(c.GetString("trace-id"))) 30 | ``` 31 | 32 | `errno.ErrUserPhone`、`errno.OK` 表示自定义的错误码,下面会看到定义的地方。 33 | 34 | `.WithID()` 设置当前请求的唯一ID,也可以理解为链路ID,忽略也可以。 35 | 36 | `.WithData()` 设置成功时返回的数据。 37 | 38 | 下面分享下编写的 `errno` 包源码,非常简单,希望大家不要介意。 39 | 40 | ## errno 包源码 41 | 42 | ``` 43 | // errno/errno.go 44 | 45 | package errno 46 | 47 | import ( 48 | "encoding/json" 49 | ) 50 | 51 | var _ Error = (*err)(nil) 52 | 53 | type Error interface { 54 | // i 为了避免被其他包实现 55 | i() 56 | // WithData 设置成功时返回的数据 57 | WithData(data interface{}) Error 58 | // WithID 设置当前请求的唯一ID 59 | WithID(id string) Error 60 | // ToString 返回 JSON 格式的错误详情 61 | ToString() string 62 | } 63 | 64 | type err struct { 65 | Code int `json:"code"` // 业务编码 66 | Msg string `json:"msg"` // 错误描述 67 | Data interface{} `json:"data"` // 成功时返回的数据 68 | ID string `json:"id,omitempty"` // 当前请求的唯一ID,便于问题定位,忽略也可以 69 | } 70 | 71 | func NewError(code int, msg string) Error { 72 | return &err{ 73 | Code: code, 74 | Msg: msg, 75 | Data: nil, 76 | } 77 | } 78 | 79 | func (e *err) i() {} 80 | 81 | func (e *err) WithData(data interface{}) Error { 82 | e.Data = data 83 | return e 84 | } 85 | 86 | func (e *err) WithID(id string) Error { 87 | e.ID = id 88 | return e 89 | } 90 | 91 | // ToString 返回 JSON 格式的错误详情 92 | func (e *err) ToString() string { 93 | err := &struct { 94 | Code int `json:"code"` 95 | Msg string `json:"msg"` 96 | Data interface{} `json:"data"` 97 | ID string `json:"id,omitempty"` 98 | }{ 99 | Code: e.Code, 100 | Msg: e.Msg, 101 | Data: e.Data, 102 | ID: e.ID, 103 | } 104 | 105 | raw, _ := json.Marshal(err) 106 | return string(raw) 107 | } 108 | 109 | ``` 110 | 111 | ``` 112 | // errno/code.go 113 | 114 | package errno 115 | 116 | var ( 117 | // OK 118 | OK = NewError(0, "OK") 119 | 120 | // 服务级错误码 121 | ErrServer = NewError(10001, "服务异常,请联系管理员") 122 | ErrParam = NewError(10002, "参数有误") 123 | ErrSignParam = NewError(10003, "签名参数有误") 124 | 125 | // 模块级错误码 - 用户模块 126 | ErrUserPhone = NewError(20101, "用户手机号不合法") 127 | ErrUserCaptcha = NewError(20102, "用户验证码有误") 128 | 129 | // ... 130 | ) 131 | ``` 132 | 133 | ## 错误码规则 134 | 135 | - 错误码需在 `code.go` 文件中定义。 136 | - 错误码需为 > 0 的数,反之表示正确。 137 | 138 | #### 错误码为 5 位数 139 | 140 | | 1 | 01 | 01 | 141 | | :------ | :------ | :------ | 142 | | 服务级错误码 | 模块级错误码 | 具体错误码 | 143 | 144 | - 服务级别错误码:1 位数进行表示,比如 1 为系统级错误;2 为普通错误,通常是由用户非法操作引起。 145 | - 模块级错误码:2 位数进行表示,比如 01 为用户模块;02 为订单模块。 146 | - 具体错误码:2 位数进行表示,比如 01 为手机号不合法;02 为验证码输入错误。 147 | 148 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | [Gin 路由配置](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/02-路由配置.md) 4 | 5 | ## 配置 6 | 7 | ``` 8 | func InitRouter(r *gin.Engine) { 9 | 10 | r.GET("/sn", SignDemo) 11 | 12 | // v1 版本 13 | GroupV1 := r.Group("/v1") 14 | { 15 | GroupV1.Any("/product/add", v1.AddProduct) 16 | GroupV1.Any("/member/add", v1.AddMember) 17 | } 18 | 19 | // v2 版本 20 | GroupV2 := r.Group("/v2", common.VerifySign) 21 | { 22 | GroupV2.Any("/product/add", v2.AddProduct) 23 | GroupV2.Any("/member/add", v2.AddMember) 24 | } 25 | } 26 | ``` 27 | 28 | ## 运行 29 | 30 | **下载源码后,请先执行 `dep ensure` 下载依赖包!** 31 | 32 | ## 效果图 33 | 34 | ![](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/images/02-路由配置/2_go_1.png) 35 | 36 | ![](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/images/02-路由配置/2_go_2.png) 37 | 38 | ![](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/images/02-路由配置/2_go_3.png) 39 | 40 | ![](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/images/02-路由配置/2_go_4.png) 41 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:3ee1d175a75b911a659fbd860060874c4f503e793c5870d13e5a0ede529a63cf" 6 | name = "github.com/gin-contrib/sse" 7 | packages = ["."] 8 | pruneopts = "UT" 9 | revision = "54d8467d122d380a14768b6b4e5cd7ca4755938f" 10 | version = "v0.1.0" 11 | 12 | [[projects]] 13 | digest = "1:d8bd2a337f6ff2188e08f72c614f2f3f0fd48e6a7b37a071b197e427d77d3a47" 14 | name = "github.com/gin-gonic/gin" 15 | packages = [ 16 | ".", 17 | "binding", 18 | "internal/json", 19 | "render", 20 | ] 21 | pruneopts = "UT" 22 | revision = "b75d67cd51eb53c3c3a2fc406524c940021ffbda" 23 | version = "v1.4.0" 24 | 25 | [[projects]] 26 | digest = "1:573ca21d3669500ff845bdebee890eb7fc7f0f50c59f2132f2a0c6b03d85086a" 27 | name = "github.com/golang/protobuf" 28 | packages = ["proto"] 29 | pruneopts = "UT" 30 | revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" 31 | version = "v1.3.2" 32 | 33 | [[projects]] 34 | digest = "1:f5a2051c55d05548d2d4fd23d244027b59fbd943217df8aa3b5e170ac2fd6e1b" 35 | name = "github.com/json-iterator/go" 36 | packages = ["."] 37 | pruneopts = "UT" 38 | revision = "0ff49de124c6f76f8494e194af75bde0f1a49a29" 39 | version = "v1.1.6" 40 | 41 | [[projects]] 42 | digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de" 43 | name = "github.com/konsorten/go-windows-terminal-sequences" 44 | packages = ["."] 45 | pruneopts = "UT" 46 | revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e" 47 | version = "v1.0.2" 48 | 49 | [[projects]] 50 | branch = "master" 51 | digest = "1:8654122ef85f2bc03859b0a7ea2d36c9965888689cdac7640cceaa6edb11cff6" 52 | name = "github.com/lestrrat-go/strftime" 53 | packages = ["."] 54 | pruneopts = "UT" 55 | revision = "8b31f9c59b0feb56c456ce49a7b3d2b2e93a6f18" 56 | 57 | [[projects]] 58 | digest = "1:9b90c7639a41697f3d4ad12d7d67dfacc9a7a4a6e0bbfae4fc72d0da57c28871" 59 | name = "github.com/mattn/go-isatty" 60 | packages = ["."] 61 | pruneopts = "UT" 62 | revision = "1311e847b0cb909da63b5fecfb5370aa66236465" 63 | version = "v0.0.8" 64 | 65 | [[projects]] 66 | digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563" 67 | name = "github.com/modern-go/concurrent" 68 | packages = ["."] 69 | pruneopts = "UT" 70 | revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" 71 | version = "1.0.3" 72 | 73 | [[projects]] 74 | digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" 75 | name = "github.com/modern-go/reflect2" 76 | packages = ["."] 77 | pruneopts = "UT" 78 | revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" 79 | version = "1.0.1" 80 | 81 | [[projects]] 82 | digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" 83 | name = "github.com/pkg/errors" 84 | packages = ["."] 85 | pruneopts = "UT" 86 | revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" 87 | version = "v0.8.1" 88 | 89 | [[projects]] 90 | digest = "1:5a1cf4e370bc86137b58da2ae065e76526d32b11f62a7665f36dbd5f41fa95ff" 91 | name = "github.com/ugorji/go" 92 | packages = ["codec"] 93 | pruneopts = "UT" 94 | revision = "23ab95ef5dc3b70286760af84ce2327a2b64ed62" 95 | version = "v1.1.7" 96 | 97 | [[projects]] 98 | branch = "master" 99 | digest = "1:5b3f90037a9027c43bdcae488c39d41aadecb9d85e645bb62b773a0a6f6e86b8" 100 | name = "golang.org/x/sys" 101 | packages = ["unix"] 102 | pruneopts = "UT" 103 | revision = "6ec70d6a5542cba804c6d16ebe8392601a0b7b60" 104 | 105 | [[projects]] 106 | digest = "1:cbc72c4c4886a918d6ab4b95e347ffe259846260f99ebdd8a198c2331cf2b2e9" 107 | name = "gopkg.in/go-playground/validator.v8" 108 | packages = ["."] 109 | pruneopts = "UT" 110 | revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" 111 | version = "v8.18.2" 112 | 113 | [[projects]] 114 | digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" 115 | name = "gopkg.in/yaml.v2" 116 | packages = ["."] 117 | pruneopts = "UT" 118 | revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" 119 | version = "v2.2.2" 120 | 121 | [solve-meta] 122 | analyzer-name = "dep" 123 | analyzer-version = 1 124 | input-imports = [ 125 | "github.com/gin-gonic/gin", 126 | ] 127 | solver-name = "gps-cdcl" 128 | solver-version = 1 129 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [[constraint]] 28 | name = "github.com/gin-gonic/gin" 29 | version = "1.4.0" 30 | 31 | [prune] 32 | go-tests = true 33 | unused-packages = true 34 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "ginDemo/config" 8 | "github.com/gin-gonic/gin" 9 | "net/http" 10 | "net/url" 11 | "sort" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | // 打印 17 | func Print(i interface{}) { 18 | fmt.Println("---") 19 | fmt.Println(i) 20 | fmt.Println("---") 21 | } 22 | 23 | // 返回JSON 24 | func RetJson(code, msg string, data interface{}, c *gin.Context) { 25 | c.JSON(http.StatusOK, gin.H{ 26 | "code" : code, 27 | "msg" : msg, 28 | "data" : data, 29 | }) 30 | c.Abort() 31 | } 32 | 33 | // 获取当前时间戳 34 | func GetTimeUnix() int64 { 35 | return time.Now().Unix() 36 | } 37 | 38 | // MD5 方法 39 | func MD5(str string) string { 40 | s := md5.New() 41 | s.Write([]byte(str)) 42 | return hex.EncodeToString(s.Sum(nil)) 43 | } 44 | 45 | // 生成签名 46 | func CreateSign(params url.Values) string { 47 | var key []string 48 | var str = "" 49 | for k := range params { 50 | if k != "sn" { 51 | key = append(key, k) 52 | } 53 | } 54 | sort.Strings(key) 55 | for i := 0; i < len(key); i++ { 56 | if i == 0 { 57 | str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) 58 | } else { 59 | str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) 60 | } 61 | } 62 | // 自定义签名算法 63 | sign := MD5(MD5(str) + MD5(config.APP_NAME + config.APP_SECRET)) 64 | return sign 65 | } 66 | 67 | // 验证签名 68 | func VerifySign(c *gin.Context) { 69 | var method = c.Request.Method 70 | var ts int64 71 | var sn string 72 | var req url.Values 73 | 74 | if method == "GET" { 75 | req = c.Request.URL.Query() 76 | sn = c.Query("sn") 77 | ts, _ = strconv.ParseInt(c.Query("ts"), 10, 64) 78 | 79 | } else if method == "POST" { 80 | c.Request.ParseForm() 81 | req = c.Request.PostForm 82 | sn = c.PostForm("sn") 83 | ts, _ = strconv.ParseInt(c.PostForm("ts"), 10, 64) 84 | } else { 85 | RetJson("500", "Illegal requests", "", c) 86 | return 87 | } 88 | 89 | exp, _ := strconv.ParseInt(config.API_EXPIRY, 10, 64) 90 | 91 | // 验证过期时间 92 | if ts > GetTimeUnix() || GetTimeUnix() - ts >= exp { 93 | RetJson("500", "Ts Error", "", c) 94 | return 95 | } 96 | 97 | // 验证签名 98 | if sn == "" || sn != CreateSign(req) { 99 | RetJson("500", "Sn Error", "", c) 100 | return 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | PORT = ":8080" 5 | APP_NAME = "ginDemo" 6 | APP_SECRET = "6YJSuc50uJ18zj45" 7 | API_EXPIRY = "120" 8 | 9 | ) 10 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/controller/v1/member.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddMember(c *gin.Context) { 6 | // 获取 Get 参数 7 | name := c.Query("name") 8 | price := c.DefaultQuery("price", "100") 9 | 10 | c.JSON(200, gin.H{ 11 | "v1" : "AddMember", 12 | "name" : name, 13 | "price" : price, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/controller/v1/product.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddProduct(c *gin.Context) { 6 | // 获取 Get 参数 7 | name := c.Query("name") 8 | price := c.DefaultQuery("price", "100") 9 | 10 | c.JSON(200, gin.H{ 11 | "v1" : "AddProduct", 12 | "name" : name, 13 | "price" : price, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/controller/v2/member.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddMember(c *gin.Context) { 6 | c.JSON(200, gin.H{ 7 | "v2" : "AddMember", 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/controller/v2/product.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func AddProduct(c *gin.Context) { 8 | // 获取 Get 参数 9 | name := c.Query("name") 10 | price := c.DefaultQuery("price", "100") 11 | 12 | c.JSON(200, gin.H{ 13 | "v2" : "AddProduct", 14 | "name" : name, 15 | "price" : price, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ginDemo/config" 5 | "ginDemo/router" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | gin.SetMode(gin.ReleaseMode) // 默认为 debug 模式,设置为发布模式 11 | engine := gin.Default() 12 | router.InitRouter(engine) // 设置路由 13 | engine.Run(config.PORT) 14 | } -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/02-路由配置/ginDemo/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "ginDemo/common" 5 | "ginDemo/controller/v1" 6 | "ginDemo/controller/v2" 7 | "github.com/gin-gonic/gin" 8 | "net/url" 9 | "strconv" 10 | ) 11 | 12 | func InitRouter(r *gin.Engine) { 13 | 14 | r.GET("/sn", SignDemo) 15 | 16 | // v1 版本 17 | GroupV1 := r.Group("/v1") 18 | { 19 | GroupV1.Any("/product/add", v1.AddProduct) 20 | GroupV1.Any("/member/add", v1.AddMember) 21 | } 22 | 23 | // v2 版本 24 | GroupV2 := r.Group("/v2", common.VerifySign) 25 | { 26 | GroupV2.Any("/product/add", v2.AddProduct) 27 | GroupV2.Any("/member/add", v2.AddMember) 28 | } 29 | } 30 | 31 | func SignDemo(c *gin.Context) { 32 | ts := strconv.FormatInt(common.GetTimeUnix(), 10) 33 | res := map[string]interface{}{} 34 | params := url.Values{ 35 | "name" : []string{"a"}, 36 | "price" : []string{"10"}, 37 | "ts" : []string{ts}, 38 | } 39 | res["sn"] = common.CreateSign(params) 40 | res["ts"] = ts 41 | common.RetJson("200", "", res, c) 42 | } -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | [Gin 日志记录](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/03-日志记录.md) 4 | 5 | ## 修改日志 6 | 2019-07-30 优化了 logger.go,日志新增了返回数据,见[最新代码包](https://github.com/xinliangnote/Go/tree/master/01-Gin框架/codes/05-自定义错误处理)。 7 | 8 | ## 配置 9 | 10 | ``` 11 | // 日志记录到文件 12 | func LoggerToFile() gin.HandlerFunc { 13 | 14 | logFilePath := config.Log_FILE_PATH 15 | logFileName := config.LOG_FILE_NAME 16 | 17 | //日志文件 18 | fileName := path.Join(logFilePath, logFileName) 19 | 20 | //写入文件 21 | src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 22 | if err != nil { 23 | fmt.Println("err", err) 24 | } 25 | 26 | //实例化 27 | logger := logrus.New() 28 | 29 | //设置输出 30 | logger.Out = src 31 | 32 | //设置日志级别 33 | logger.SetLevel(logrus.DebugLevel) 34 | 35 | //设置日志格式 36 | logger.SetFormatter(&logrus.TextFormatter{}) 37 | 38 | return func(c *gin.Context) { 39 | // 开始时间 40 | startTime := time.Now() 41 | 42 | // 处理请求 43 | c.Next() 44 | 45 | // 结束时间 46 | endTime := time.Now() 47 | 48 | // 执行时间 49 | latencyTime := endTime.Sub(startTime) 50 | 51 | // 请求方式 52 | reqMethod := c.Request.Method 53 | 54 | // 请求路由 55 | reqUri := c.Request.RequestURI 56 | 57 | // 状态码 58 | statusCode := c.Writer.Status() 59 | 60 | // 请求IP 61 | clientIP := c.ClientIP() 62 | 63 | // 日志格式 64 | logger.Infof("| %3d | %13v | %15s | %s | %s |", 65 | statusCode, 66 | latencyTime, 67 | clientIP, 68 | reqMethod, 69 | reqUri, 70 | ) 71 | } 72 | } 73 | ``` 74 | 75 | ## 运行 76 | 77 | **下载源码后,请先执行 `dep ensure` 下载依赖包!** 78 | 79 | ## 效果图 80 | 81 | ``` 82 | time="2019-07-17T22:10:45+08:00" level=info msg="| 200 | 27.698µs | ::1 | GET | /v1/product/add?name=a&price=10 |" 83 | time="2019-07-17T22:10:46+08:00" level=info msg="| 200 | 27.239µs | ::1 | GET | /v1/product/add?name=a&price=10 |" 84 | ``` 85 | 86 | ``` 87 | time="2019-07-17 22:15:57" level=info msg="| 200 | 185.027µs | ::1 | GET | /v1/product/add?name=a&price=10 |" 88 | time="2019-07-17 22:15:58" level=info msg="| 200 | 56.989µs | ::1 | GET | /v1/product/add?name=a&price=10 |" 89 | ``` 90 | 91 | ``` 92 | {"level":"info","msg":"| 200 | 24.78µs | ::1 | GET | /v1/product/add?name=a\u0026price=10 |","time":"2019-07-17 22:23:55"} 93 | {"level":"info","msg":"| 200 | 26.946µs | ::1 | GET | /v1/product/add?name=a\u0026price=10 |","time":"2019-07-17 22:23:56"} 94 | ``` 95 | 96 | ``` 97 | {"client_ip":"::1","latency_time":26681,"level":"info","msg":"","req_method":"GET","req_uri":"/v1/product/add?name=a\u0026price=10","status_code":200,"time":"2019-07-17 22:37:54"} 98 | {"client_ip":"::1","latency_time":24315,"level":"info","msg":"","req_method":"GET","req_uri":"/v1/product/add?name=a\u0026price=10","status_code":200,"time":"2019-07-17 22:37:55"} 99 | ``` 100 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [[constraint]] 28 | name = "github.com/gin-gonic/gin" 29 | version = "1.4.0" 30 | 31 | [[constraint]] 32 | name = "github.com/sirupsen/logrus" 33 | version = "1.4.2" 34 | 35 | [[constraint]] 36 | name = "github.com/rifflock/lfshook" 37 | version = "2.4" 38 | 39 | [[constraint]] 40 | name = "github.com/lestrrat-go/file-rotatelogs" 41 | version = "v2.2.0" 42 | 43 | [prune] 44 | go-tests = true 45 | unused-packages = true 46 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "ginDemo/config" 8 | "github.com/gin-gonic/gin" 9 | "net/http" 10 | "net/url" 11 | "sort" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | // 打印 17 | func Print(i interface{}) { 18 | fmt.Println("---") 19 | fmt.Println(i) 20 | fmt.Println("---") 21 | } 22 | 23 | // 返回JSON 24 | func RetJson(code, msg string, data interface{}, c *gin.Context) { 25 | c.JSON(http.StatusOK, gin.H{ 26 | "code" : code, 27 | "msg" : msg, 28 | "data" : data, 29 | }) 30 | c.Abort() 31 | } 32 | 33 | // 获取当前时间戳 34 | func GetTimeUnix() int64 { 35 | return time.Now().Unix() 36 | } 37 | 38 | // MD5 方法 39 | func MD5(str string) string { 40 | s := md5.New() 41 | s.Write([]byte(str)) 42 | return hex.EncodeToString(s.Sum(nil)) 43 | } 44 | 45 | // 生成签名 46 | func CreateSign(params url.Values) string { 47 | var key []string 48 | var str = "" 49 | for k := range params { 50 | if k != "sn" { 51 | key = append(key, k) 52 | } 53 | } 54 | sort.Strings(key) 55 | for i := 0; i < len(key); i++ { 56 | if i == 0 { 57 | str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) 58 | } else { 59 | str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) 60 | } 61 | } 62 | // 自定义签名算法 63 | sign := MD5(MD5(str) + MD5(config.APP_NAME + config.APP_SECRET)) 64 | return sign 65 | } 66 | 67 | // 验证签名 68 | func VerifySign(c *gin.Context) { 69 | var method = c.Request.Method 70 | var ts int64 71 | var sn string 72 | var req url.Values 73 | 74 | if method == "GET" { 75 | req = c.Request.URL.Query() 76 | sn = c.Query("sn") 77 | ts, _ = strconv.ParseInt(c.Query("ts"), 10, 64) 78 | 79 | } else if method == "POST" { 80 | c.Request.ParseForm() 81 | req = c.Request.PostForm 82 | sn = c.PostForm("sn") 83 | ts, _ = strconv.ParseInt(c.PostForm("ts"), 10, 64) 84 | } else { 85 | RetJson("500", "Illegal requests", "", c) 86 | return 87 | } 88 | 89 | exp, _ := strconv.ParseInt(config.API_EXPIRY, 10, 64) 90 | 91 | // 验证过期时间 92 | if ts > GetTimeUnix() || GetTimeUnix() - ts >= exp { 93 | RetJson("500", "Ts Error", "", c) 94 | return 95 | } 96 | 97 | // 验证签名 98 | if sn == "" || sn != CreateSign(req) { 99 | RetJson("500", "Sn Error", "", c) 100 | return 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | PORT = ":8080" 5 | APP_NAME = "ginDemo" 6 | APP_SECRET = "6YJSuc50uJ18zj45" 7 | API_EXPIRY = "120" 8 | 9 | Log_FILE_PATH = "/Users/xinliangnote/go/logs" 10 | LOG_FILE_NAME = "system.log" 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ginDemo/config" 5 | "ginDemo/middleware" 6 | "ginDemo/router" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | gin.SetMode(gin.ReleaseMode) // 默认为 debug 模式,设置为发布模式 12 | engine := gin.Default() 13 | engine.Use(middleware.LoggerToFile()) 14 | router.InitRouter(engine) // 设置路由 15 | engine.Run(config.PORT) 16 | } -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "ginDemo/config" 6 | "github.com/gin-gonic/gin" 7 | rotatelogs "github.com/lestrrat-go/file-rotatelogs" 8 | "github.com/rifflock/lfshook" 9 | "github.com/sirupsen/logrus" 10 | "os" 11 | "path" 12 | "time" 13 | ) 14 | 15 | // 日志记录到文件 16 | func LoggerToFile() gin.HandlerFunc { 17 | 18 | logFilePath := config.Log_FILE_PATH 19 | logFileName := config.LOG_FILE_NAME 20 | 21 | // 日志文件 22 | fileName := path.Join(logFilePath, logFileName) 23 | 24 | // 写入文件 25 | src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 26 | if err != nil { 27 | fmt.Println("err", err) 28 | } 29 | 30 | // 实例化 31 | logger := logrus.New() 32 | 33 | // 设置输出 34 | logger.Out = src 35 | 36 | // 设置日志级别 37 | logger.SetLevel(logrus.DebugLevel) 38 | 39 | // 设置 rotatelogs 40 | logWriter, err := rotatelogs.New( 41 | // 分割后的文件名称 42 | fileName + ".%Y%m%d.log", 43 | 44 | // 生成软链,指向最新日志文件 45 | rotatelogs.WithLinkName(fileName), 46 | 47 | // 设置最大保存时间(7天) 48 | rotatelogs.WithMaxAge(7*24*time.Hour), 49 | 50 | // 设置日志切割时间间隔(1天) 51 | rotatelogs.WithRotationTime(24*time.Hour), 52 | ) 53 | 54 | writeMap := lfshook.WriterMap{ 55 | logrus.InfoLevel: logWriter, 56 | logrus.FatalLevel: logWriter, 57 | logrus.DebugLevel: logWriter, 58 | logrus.WarnLevel: logWriter, 59 | logrus.ErrorLevel: logWriter, 60 | logrus.PanicLevel: logWriter, 61 | } 62 | 63 | lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{ 64 | TimestampFormat:"2006-01-02 15:04:05", 65 | }) 66 | 67 | // 新增钩子 68 | logger.AddHook(lfHook) 69 | 70 | return func(c *gin.Context) { 71 | // 开始时间 72 | startTime := time.Now() 73 | 74 | // 处理请求 75 | c.Next() 76 | 77 | // 结束时间 78 | endTime := time.Now() 79 | 80 | // 执行时间 81 | latencyTime := endTime.Sub(startTime) 82 | 83 | // 请求方式 84 | reqMethod := c.Request.Method 85 | 86 | // 请求路由 87 | reqUri := c.Request.RequestURI 88 | 89 | // 状态码 90 | statusCode := c.Writer.Status() 91 | 92 | // 请求IP 93 | clientIP := c.ClientIP() 94 | 95 | // 日志格式 96 | logger.WithFields(logrus.Fields{ 97 | "status_code" : statusCode, 98 | "latency_time" : latencyTime, 99 | "client_ip" : clientIP, 100 | "req_method" : reqMethod, 101 | "req_uri" : reqUri, 102 | }).Info() 103 | } 104 | } 105 | 106 | // 日志记录到 MongoDB 107 | func LoggerToMongo() gin.HandlerFunc { 108 | return func(c *gin.Context) { 109 | 110 | } 111 | } 112 | 113 | // 日志记录到 ES 114 | func LoggerToES() gin.HandlerFunc { 115 | return func(c *gin.Context) { 116 | 117 | } 118 | } 119 | 120 | // 日志记录到 MQ 121 | func LoggerToMQ() gin.HandlerFunc { 122 | return func(c *gin.Context) { 123 | 124 | } 125 | } -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "ginDemo/common" 5 | "ginDemo/router/v1" 6 | "ginDemo/router/v2" 7 | "github.com/gin-gonic/gin" 8 | "net/url" 9 | "strconv" 10 | ) 11 | 12 | func InitRouter(r *gin.Engine) { 13 | 14 | r.GET("/sn", SignDemo) 15 | 16 | // v1 版本 17 | GroupV1 := r.Group("/v1") 18 | { 19 | GroupV1.Any("/product/add", v1.AddProduct) 20 | GroupV1.Any("/member/add", v1.AddMember) 21 | } 22 | 23 | // v2 版本 24 | GroupV2 := r.Group("/v2", common.VerifySign) 25 | { 26 | GroupV2.Any("/product/add", v2.AddProduct) 27 | GroupV2.Any("/member/add", v2.AddMember) 28 | } 29 | } 30 | 31 | func SignDemo(c *gin.Context) { 32 | ts := strconv.FormatInt(common.GetTimeUnix(), 10) 33 | res := map[string]interface{}{} 34 | params := url.Values{ 35 | "name" : []string{"a"}, 36 | "price" : []string{"10"}, 37 | "ts" : []string{ts}, 38 | } 39 | res["sn"] = common.CreateSign(params) 40 | res["ts"] = ts 41 | common.RetJson("200", "", res, c) 42 | } -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/router/v1/member.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddMember(c *gin.Context) { 6 | // 获取 Get 参数 7 | name := c.Query("name") 8 | price := c.DefaultQuery("price", "100") 9 | 10 | c.JSON(200, gin.H{ 11 | "v1" : "AddMember", 12 | "name" : name, 13 | "price" : price, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/router/v1/product.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddProduct(c *gin.Context) { 6 | // 获取 Get 参数 7 | name := c.Query("name") 8 | price := c.DefaultQuery("price", "100") 9 | 10 | c.JSON(200, gin.H{ 11 | "v1": "AddProduct", 12 | "name" : name, 13 | "price" : price, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/router/v2/member.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddMember(c *gin.Context) { 6 | c.JSON(200, gin.H{ 7 | "v2": "AddMember", 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/03-日志记录/ginDemo/router/v2/product.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func AddProduct(c *gin.Context) { 8 | // 获取 Get 参数 9 | name := c.Query("name") 10 | price := c.DefaultQuery("price", "100") 11 | 12 | c.JSON(200, gin.H{ 13 | "v2": "AddProduct", 14 | "name" : name, 15 | "price" : price, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | [Gin 数据绑定和验证](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/04-数据绑定和验证.md) 4 | 5 | ## 配置 6 | 7 | ``` 8 | package entity 9 | 10 | // 定义 Member 结构体 11 | type Member struct { 12 | Name string `form:"name" json:"name" binding:"required,NameValid"` 13 | Age int `form:"age" json:"age" binding:"required,gt=10,lt=120"` 14 | } 15 | ``` 16 | 17 | ## 运行 18 | 19 | **下载源码后,请先执行 `dep ensure` 下载依赖包!** 20 | 21 | ## 效果图 22 | 23 | 访问:`http://localhost:8080/v1/member/add` 24 | 25 | ``` 26 | { 27 | "code": -1, 28 | "msg": "Key: 'Member.Name' Error:Field validation for 'Name' failed on the 'required' tag\nKey: 'Member.Age' Error:Field validation for 'Age' failed on the 'required' tag", 29 | "data": null 30 | } 31 | ``` 32 | 33 | 访问:`http://localhost:8080/v1/member/add?name=1` 34 | 35 | ``` 36 | { 37 | "code": -1, 38 | "msg": "Key: 'Member.Age' Error:Field validation for 'Age' failed on the 'required' tag", 39 | "data": null 40 | } 41 | ``` 42 | 43 | 访问:`http://localhost:8080/v1/member/add?age=1` 44 | 45 | ``` 46 | { 47 | "code": -1, 48 | "msg": "Key: 'Member.Age' Error:Field validation for 'Age' failed on the 'required' tag", 49 | "data": null 50 | } 51 | ``` 52 | 53 | 访问:`http://localhost:8080/v1/member/add?name=admin&age=1` 54 | 55 | ``` 56 | { 57 | "code": -1, 58 | "msg": "Key: 'Member.Name' Error:Field validation for 'Name' failed on the 'NameValid' tag", 59 | "data": null 60 | } 61 | ``` 62 | 63 | 访问:`http://localhost:8080/v1/member/add?name=1&age=1` 64 | 65 | ``` 66 | { 67 | "code": -1, 68 | "msg": "Key: 'Member.Age' Error:Field validation for 'Age' failed on the 'gt' tag", 69 | "data": null 70 | } 71 | ``` 72 | 73 | 访问:`http://localhost:8080/v1/member/add?name=1&age=121` 74 | 75 | ``` 76 | { 77 | "code": -1, 78 | "msg": "Key: 'Member.Age' Error:Field validation for 'Age' failed on the 'lt' tag", 79 | "data": null 80 | } 81 | ``` 82 | 83 | 访问:`http://localhost:8080/v1/member/add?name=Tom&age=30` 84 | 85 | ``` 86 | { 87 | "code": 1, 88 | "msg": "", 89 | "data": { 90 | "age": 30, 91 | "name": "Tom" 92 | } 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [[constraint]] 28 | name = "github.com/gin-gonic/gin" 29 | version = "1.4.0" 30 | 31 | [[constraint]] 32 | name = "github.com/sirupsen/logrus" 33 | version = "1.4.2" 34 | 35 | [[constraint]] 36 | name = "github.com/rifflock/lfshook" 37 | version = "2.4" 38 | 39 | [[constraint]] 40 | name = "github.com/lestrrat-go/file-rotatelogs" 41 | version = "v2.2.0" 42 | 43 | [prune] 44 | go-tests = true 45 | unused-packages = true 46 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "ginDemo/config" 8 | "net/url" 9 | "sort" 10 | "time" 11 | ) 12 | 13 | // 获取当前时间戳 14 | func GetTimeUnix() int64 { 15 | return time.Now().Unix() 16 | } 17 | 18 | // MD5 方法 19 | func MD5(str string) string { 20 | s := md5.New() 21 | s.Write([]byte(str)) 22 | return hex.EncodeToString(s.Sum(nil)) 23 | } 24 | 25 | // 生成签名 26 | func CreateSign(params url.Values) string { 27 | var key []string 28 | var str = "" 29 | for k := range params { 30 | if k != "sn" && k != "ts" && k != "debug" { 31 | key = append(key, k) 32 | } 33 | } 34 | sort.Strings(key) 35 | for i := 0; i < len(key); i++ { 36 | if i == 0 { 37 | str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) 38 | } else { 39 | str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) 40 | } 41 | } 42 | 43 | // 自定义签名算法 44 | sign := MD5(MD5(str) + MD5(config.APP_NAME + config.APP_SECRET)) 45 | return sign 46 | } 47 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | PORT = ":8080" 5 | APP_NAME = "ginDemo" 6 | APP_SECRET = "6YJSuc50uJ18zj45" 7 | API_EXPIRY = "120" 8 | 9 | Log_FILE_PATH = "/Users/xinliangnote/go/logs" 10 | LOG_FILE_NAME = "system.log" 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/entity/member.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | // 定义 Member 结构体 4 | type Member struct { 5 | Name string `form:"name" json:"name" binding:"required,NameValid"` 6 | Age int `form:"age" json:"age" binding:"required,gt=10,lt=120"` 7 | } 8 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/entity/result.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | // 定义 Result 结构体 4 | type Result struct { 5 | Code int `json:"code"` 6 | Message string `json:"msg"` 7 | Data interface{} `json:"data"` 8 | } 9 | 10 | // 定义错误码 11 | const ( 12 | // 成功 13 | CODE_SUCCESS int = 1 14 | 15 | //失败 16 | CODE_ERROR int = -1 17 | 18 | //自定义... 19 | ) 20 | 21 | // 设置错误码 22 | func (res *Result) SetCode(code int) *Result { 23 | res.Code = code 24 | return res 25 | } 26 | 27 | // 设置错误信息 28 | func (res *Result) SetMessage(msg string) *Result { 29 | res.Message = msg 30 | return res 31 | } 32 | 33 | // 设置返回数据 34 | func (res *Result) SetData(data interface{}) *Result { 35 | res.Data = data 36 | return res 37 | } 38 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "ginDemo/config" 6 | "ginDemo/router" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | gin.SetMode(gin.ReleaseMode) // 默认为 debug 模式,设置为发布模式 12 | engine := gin.New() 13 | router.InitRouter(engine) // 设置路由 14 | err := engine.Run(config.PORT) 15 | if err != nil { 16 | fmt.Println(err.Error()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/middleware/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "ginDemo/config" 6 | "github.com/gin-gonic/gin" 7 | rotatelogs "github.com/lestrrat-go/file-rotatelogs" 8 | "github.com/rifflock/lfshook" 9 | "github.com/sirupsen/logrus" 10 | "os" 11 | "path" 12 | "time" 13 | ) 14 | 15 | // 日志记录到文件 16 | func LoggerToFile() gin.HandlerFunc { 17 | 18 | logFilePath := config.Log_FILE_PATH 19 | logFileName := config.LOG_FILE_NAME 20 | 21 | // 日志文件 22 | fileName := path.Join(logFilePath, logFileName) 23 | 24 | // 写入文件 25 | src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 26 | if err != nil { 27 | fmt.Println("err", err) 28 | } 29 | 30 | // 实例化 31 | logger := logrus.New() 32 | 33 | // 设置输出 34 | logger.Out = src 35 | 36 | // 设置日志级别 37 | logger.SetLevel(logrus.DebugLevel) 38 | 39 | // 设置 rotatelogs 40 | logWriter, err := rotatelogs.New( 41 | // 分割后的文件名称 42 | fileName + ".%Y%m%d.log", 43 | 44 | // 生成软链,指向最新日志文件 45 | rotatelogs.WithLinkName(fileName), 46 | 47 | // 设置最大保存时间(7天) 48 | rotatelogs.WithMaxAge(7*24*time.Hour), 49 | 50 | // 设置日志切割时间间隔(1天) 51 | rotatelogs.WithRotationTime(24*time.Hour), 52 | ) 53 | 54 | writeMap := lfshook.WriterMap{ 55 | logrus.InfoLevel: logWriter, 56 | logrus.FatalLevel: logWriter, 57 | logrus.DebugLevel: logWriter, 58 | logrus.WarnLevel: logWriter, 59 | logrus.ErrorLevel: logWriter, 60 | logrus.PanicLevel: logWriter, 61 | } 62 | 63 | lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{ 64 | TimestampFormat:"2006-01-02 15:04:05", 65 | }) 66 | 67 | // 新增钩子 68 | logger.AddHook(lfHook) 69 | 70 | return func(c *gin.Context) { 71 | // 开始时间 72 | startTime := time.Now() 73 | 74 | // 处理请求 75 | c.Next() 76 | 77 | // 结束时间 78 | endTime := time.Now() 79 | 80 | // 执行时间 81 | latencyTime := endTime.Sub(startTime) 82 | 83 | // 请求方式 84 | reqMethod := c.Request.Method 85 | 86 | // 请求路由 87 | reqUri := c.Request.RequestURI 88 | 89 | // 状态码 90 | statusCode := c.Writer.Status() 91 | 92 | // 请求IP 93 | clientIP := c.ClientIP() 94 | 95 | // 日志格式 96 | logger.WithFields(logrus.Fields{ 97 | "status_code" : statusCode, 98 | "latency_time" : latencyTime, 99 | "client_ip" : clientIP, 100 | "req_method" : reqMethod, 101 | "req_uri" : reqUri, 102 | }).Info() 103 | 104 | } 105 | } 106 | 107 | // 日志记录到 MongoDB 108 | func LoggerToMongo() gin.HandlerFunc { 109 | return func(c *gin.Context) { 110 | 111 | } 112 | } 113 | 114 | // 日志记录到 ES 115 | func LoggerToES() gin.HandlerFunc { 116 | return func(c *gin.Context) { 117 | 118 | } 119 | } 120 | 121 | // 日志记录到 MQ 122 | func LoggerToMQ() gin.HandlerFunc { 123 | return func(c *gin.Context) { 124 | 125 | } 126 | } -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/middleware/sign/sign.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | import ( 4 | "ginDemo/common/function" 5 | "ginDemo/config" 6 | "ginDemo/entity" 7 | "github.com/gin-gonic/gin" 8 | "github.com/pkg/errors" 9 | "net/http" 10 | "net/url" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | func Sign() gin.HandlerFunc { 16 | return func(c *gin.Context) { 17 | res := entity.Result{} 18 | 19 | sign, err := verifySign(c) 20 | 21 | if sign != nil { 22 | res.SetCode(entity.CODE_ERROR) 23 | res.SetMessage("Debug Sign") 24 | res.SetData(sign) 25 | c.JSON(http.StatusUnauthorized, res) 26 | c.Abort() 27 | return 28 | } 29 | 30 | if err != nil { 31 | res.SetCode(entity.CODE_ERROR) 32 | res.SetMessage(err.Error()) 33 | c.JSON(http.StatusUnauthorized, res) 34 | c.Abort() 35 | return 36 | } 37 | 38 | c.Next() 39 | } 40 | } 41 | 42 | // 验证签名 43 | func verifySign(c *gin.Context) (res map[string]string, err error) { 44 | var method = c.Request.Method 45 | var ts int64 46 | var sn string 47 | var req url.Values 48 | var debug string 49 | 50 | if method == "GET" { 51 | req = c.Request.URL.Query() 52 | sn = c.Query("sn") 53 | debug = c.Query("debug") 54 | ts, _ = strconv.ParseInt(c.Query("ts"), 10, 64) 55 | } else if method == "POST" { 56 | c.Request.ParseForm() 57 | req = c.Request.PostForm 58 | sn = c.PostForm("sn") 59 | debug = c.PostForm("debug") 60 | ts, _ = strconv.ParseInt(c.PostForm("ts"), 10, 64) 61 | } else { 62 | err = errors.New("非法请求") 63 | return 64 | } 65 | 66 | if debug == "1" { 67 | res = map[string]string{ 68 | "ts" : strconv.FormatInt(function.GetTimeUnix(), 10), 69 | "sn" : function.CreateSign(req), 70 | } 71 | return 72 | } 73 | 74 | exp, _ := strconv.ParseInt(config.API_EXPIRY, 10, 64) 75 | 76 | // 验证过期时间 77 | timestamp := time.Now().Unix() 78 | if ts > timestamp || timestamp - ts >= exp { 79 | err = errors.New("Ts Error") 80 | return 81 | } 82 | 83 | // 验证签名 84 | if sn == "" || sn != function.CreateSign(req) { 85 | err = errors.New("sn Error") 86 | return 87 | } 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "ginDemo/middleware/logger" 5 | "ginDemo/middleware/sign" 6 | "ginDemo/router/v1" 7 | "ginDemo/router/v2" 8 | "ginDemo/validator/member" 9 | "github.com/gin-gonic/gin" 10 | "github.com/gin-gonic/gin/binding" 11 | "gopkg.in/go-playground/validator.v8" 12 | ) 13 | 14 | func InitRouter(r *gin.Engine) { 15 | 16 | r.Use(logger.LoggerToFile()) 17 | 18 | // v1 版本 19 | GroupV1 := r.Group("/v1") 20 | { 21 | GroupV1.Any("/product/add", v1.AddProduct) 22 | GroupV1.Any("/member/add", v1.AddMember) 23 | } 24 | 25 | // v2 版本 26 | GroupV2 := r.Group("/v2").Use(sign.Sign()) 27 | { 28 | GroupV2.Any("/product/add", v2.AddProduct) 29 | GroupV2.Any("/member/add", v2.AddMember) 30 | } 31 | 32 | // 绑定验证器 33 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 34 | v.RegisterValidation("NameValid", member.NameValid) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/router/v1/member.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "ginDemo/entity" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | func AddMember(c *gin.Context) { 10 | 11 | res := entity.Result{} 12 | mem := entity.Member{} 13 | 14 | if err := c.ShouldBind(&mem); err != nil { 15 | res.SetCode(entity.CODE_ERROR) 16 | res.SetMessage(err.Error()) 17 | c.JSON(http.StatusForbidden, res) 18 | c.Abort() 19 | return 20 | } 21 | 22 | // 处理业务(下次再分享) 23 | 24 | data := map[string]interface{}{ 25 | "name" : mem.Name, 26 | "age" : mem.Age, 27 | } 28 | res.SetCode(entity.CODE_SUCCESS) 29 | res.SetData(data) 30 | c.JSON(http.StatusOK, res) 31 | } 32 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/router/v1/product.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddProduct(c *gin.Context) { 6 | // 获取 Get 参数 7 | name := c.Query("name") 8 | price := c.DefaultQuery("price", "100") 9 | 10 | c.JSON(200, gin.H{ 11 | "v1": "AddProduct", 12 | "name" : name, 13 | "price" : price, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/router/v2/member.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddMember(c *gin.Context) { 6 | c.JSON(200, gin.H{ 7 | "v2": "AddMember", 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/router/v2/product.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func AddProduct(c *gin.Context) { 8 | // 获取 Get 参数 9 | name := c.Query("name") 10 | price := c.DefaultQuery("price", "100") 11 | 12 | c.JSON(200, gin.H{ 13 | "v2": "AddProduct", 14 | "name" : name, 15 | "price" : price, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/04-数据绑定和验证/ginDemo/validator/member/member.go: -------------------------------------------------------------------------------- 1 | package member 2 | 3 | import ( 4 | "gopkg.in/go-playground/validator.v8" 5 | "reflect" 6 | ) 7 | 8 | func NameValid( 9 | v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, 10 | field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, 11 | ) bool { 12 | if s, ok := field.Interface().(string); ok { 13 | if s == "admin" { 14 | return false 15 | } 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | [Gin 自定义错误处理](https://github.com/xinliangnote/Go/blob/master/01-Gin框架/05-自定义错误处理.md) 4 | 5 | ## 修改日志 6 | 7 | - 2019-07-30 优化了 logger.go,日志新增了返回数据。 8 | 9 | ## 调用 10 | 11 | ``` 12 | alarm.WeChat("错误信息") 13 | 14 | alarm.Email("错误信息") 15 | 16 | alarm.Sms("错误信息") 17 | 18 | alarm.Panic("错误信息") 19 | ``` 20 | 21 | ## 运行 22 | 23 | **下载源码后,请先执行 `dep ensure` 下载依赖包!** 24 | 25 | ## 效果 26 | 27 | 28 | ``` 29 | {"time":"2019-07-23 22:55:27","alarm":"PANIC","message":"runtime error: index out of range","filename":"绝对路径/ginDemo/router/v1/product.go","line":34,"funcname":"hello"} 30 | ``` 31 | 32 | ``` 33 | {"time":"2019-07-23 22:19:17","alarm":"WX","message":"name 不能为空","filename":"绝对路径/ginDemo/router/v1/product.go","line":33,"funcname":"hello"} 34 | ``` 35 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [[constraint]] 28 | name = "github.com/gin-gonic/gin" 29 | version = "1.4.0" 30 | 31 | [[constraint]] 32 | name = "github.com/sirupsen/logrus" 33 | version = "1.4.2" 34 | 35 | [[constraint]] 36 | name = "github.com/rifflock/lfshook" 37 | version = "2.4" 38 | 39 | [[constraint]] 40 | name = "github.com/lestrrat-go/file-rotatelogs" 41 | version = "v2.2.0" 42 | 43 | [prune] 44 | go-tests = true 45 | unused-packages = true 46 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/common/alarm/alarm.go: -------------------------------------------------------------------------------- 1 | package alarm 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "ginDemo/common/function" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | type errorString struct { 13 | s string 14 | } 15 | 16 | type errorInfo struct { 17 | Time string `json:"time"` 18 | Alarm string `json:"alarm"` 19 | Message string `json:"message"` 20 | Filename string `json:"filename"` 21 | Line int `json:"line"` 22 | Funcname string `json:"funcname"` 23 | } 24 | 25 | func (e *errorString) Error() string { 26 | return e.s 27 | } 28 | 29 | func New (text string) error { 30 | alarm("INFO", text,2) 31 | return &errorString{text} 32 | } 33 | 34 | // 发邮件 35 | func Email (text string) error { 36 | alarm("EMAIL", text,2) 37 | return &errorString{text} 38 | } 39 | 40 | // 发短信 41 | func Sms (text string) error { 42 | alarm("SMS", text, 2) 43 | return &errorString{text} 44 | } 45 | 46 | // 发微信 47 | func WeChat (text string) error { 48 | alarm("WX", text, 2) 49 | return &errorString{text} 50 | } 51 | 52 | // Panic 异常 53 | func Panic (text string) error { 54 | alarm("PANIC", text, 5) 55 | return &errorString{text} 56 | } 57 | 58 | // 告警方法 59 | func alarm(level string, str string, skip int) { 60 | // 当前时间 61 | currentTime := function.GetTimeStr() 62 | 63 | // 定义 文件名、行号、方法名 64 | fileName, line, functionName := "?", 0 , "?" 65 | 66 | pc, fileName, line, ok := runtime.Caller(skip) 67 | if ok { 68 | functionName = runtime.FuncForPC(pc).Name() 69 | functionName = filepath.Ext(functionName) 70 | functionName = strings.TrimPrefix(functionName, ".") 71 | } 72 | 73 | var msg = errorInfo { 74 | Time : currentTime, 75 | Alarm : level, 76 | Message : str, 77 | Filename : fileName, 78 | Line : line, 79 | Funcname : functionName, 80 | } 81 | 82 | jsons, errs := json.Marshal(msg) 83 | 84 | if errs != nil { 85 | fmt.Println("json marshal error:", errs) 86 | } 87 | 88 | errorJsonInfo := string(jsons) 89 | 90 | fmt.Println(errorJsonInfo) 91 | 92 | if level == "EMAIL" { 93 | // 执行发邮件 94 | 95 | } else if level == "SMS" { 96 | // 执行发短信 97 | 98 | } else if level == "WX" { 99 | // 执行发微信 100 | 101 | } else if level == "INFO" { 102 | // 执行记日志 103 | 104 | } else if level == "PANIC" { 105 | // 执行PANIC方式 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/common/function/common.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "ginDemo/config" 8 | "net/url" 9 | "sort" 10 | "time" 11 | ) 12 | 13 | func GetTimeStr() string { 14 | return time.Now().Format("2006-01-02 15:04:05") 15 | } 16 | // 获取当前时间戳 17 | func GetTimeUnix() int64 { 18 | return time.Now().Unix() 19 | } 20 | 21 | // MD5 方法 22 | func MD5(str string) string { 23 | s := md5.New() 24 | s.Write([]byte(str)) 25 | return hex.EncodeToString(s.Sum(nil)) 26 | } 27 | 28 | // 生成签名 29 | func CreateSign(params url.Values) string { 30 | var key []string 31 | var str = "" 32 | for k := range params { 33 | if k != "sn" && k != "ts" && k != "debug" { 34 | key = append(key, k) 35 | } 36 | } 37 | sort.Strings(key) 38 | for i := 0; i < len(key); i++ { 39 | if i == 0 { 40 | str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) 41 | } else { 42 | str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) 43 | } 44 | } 45 | 46 | // 自定义签名算法 47 | sign := MD5(MD5(str) + MD5(config.APP_NAME + config.APP_SECRET)) 48 | return sign 49 | } 50 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | PORT = ":8080" 5 | APP_NAME = "ginDemo" 6 | APP_SECRET = "6YJSuc50uJ18zj45" 7 | API_EXPIRY = "120" 8 | 9 | Log_FILE_PATH = "/Users/xinliangnote/go/logs" 10 | LOG_FILE_NAME = "system.log" 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/entity/member.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | // 定义 Member 结构体 4 | type Member struct { 5 | Name string `form:"name" json:"name" binding:"required,NameValid"` 6 | Age int `form:"age" json:"age" binding:"required,gt=10,lt=120"` 7 | } 8 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/entity/result.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | // 定义 Result 结构体 4 | type Result struct { 5 | Code int `json:"code"` 6 | Message string `json:"msg"` 7 | Data interface{} `json:"data"` 8 | } 9 | 10 | // 定义错误码 11 | const ( 12 | // 成功 13 | CODE_SUCCESS int = 1 14 | 15 | //失败 16 | CODE_ERROR int = -1 17 | 18 | //自定义... 19 | ) 20 | 21 | // 设置错误码 22 | func (res *Result) SetCode(code int) *Result { 23 | res.Code = code 24 | return res 25 | } 26 | 27 | // 设置错误信息 28 | func (res *Result) SetMessage(msg string) *Result { 29 | res.Message = msg 30 | return res 31 | } 32 | 33 | // 设置返回数据 34 | func (res *Result) SetData(data interface{}) *Result { 35 | res.Data = data 36 | return res 37 | } 38 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "ginDemo/config" 6 | "ginDemo/router" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | gin.SetMode(gin.ReleaseMode) // 默认为 debug 模式,设置为发布模式 12 | engine := gin.New() 13 | router.InitRouter(engine) // 设置路由 14 | err := engine.Run(config.PORT) 15 | if err != nil { 16 | fmt.Println(err.Error()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/middleware/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "ginDemo/config" 8 | "ginDemo/entity" 9 | "github.com/gin-gonic/gin" 10 | rotatelogs "github.com/lestrrat-go/file-rotatelogs" 11 | "github.com/rifflock/lfshook" 12 | "github.com/sirupsen/logrus" 13 | "os" 14 | "path" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | type bodyLogWriter struct { 20 | gin.ResponseWriter 21 | body *bytes.Buffer 22 | } 23 | func (w bodyLogWriter) Write(b []byte) (int, error) { 24 | w.body.Write(b) 25 | return w.ResponseWriter.Write(b) 26 | } 27 | func (w bodyLogWriter) WriteString(s string) (int, error) { 28 | w.body.WriteString(s) 29 | return w.ResponseWriter.WriteString(s) 30 | } 31 | 32 | // 日志记录到文件 33 | func LoggerToFile() gin.HandlerFunc { 34 | 35 | logFilePath := config.Log_FILE_PATH 36 | logFileName := config.LOG_FILE_NAME 37 | 38 | // 日志文件 39 | fileName := path.Join(logFilePath, logFileName) 40 | 41 | // 写入文件 42 | src, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) 43 | if err != nil { 44 | fmt.Println("err", err) 45 | } 46 | 47 | // 实例化 48 | logger := logrus.New() 49 | 50 | // 设置输出 51 | logger.Out = src 52 | 53 | // 设置日志级别 54 | logger.SetLevel(logrus.DebugLevel) 55 | 56 | // 设置 rotatelogs 57 | logWriter, err := rotatelogs.New( 58 | // 分割后的文件名称 59 | fileName + ".%Y%m%d.log", 60 | 61 | // 生成软链,指向最新日志文件 62 | rotatelogs.WithLinkName(fileName), 63 | 64 | // 设置最大保存时间(7天) 65 | rotatelogs.WithMaxAge(7*24*time.Hour), 66 | 67 | // 设置日志切割时间间隔(1天) 68 | rotatelogs.WithRotationTime(24*time.Hour), 69 | ) 70 | 71 | writeMap := lfshook.WriterMap{ 72 | logrus.InfoLevel: logWriter, 73 | logrus.FatalLevel: logWriter, 74 | logrus.DebugLevel: logWriter, 75 | logrus.WarnLevel: logWriter, 76 | logrus.ErrorLevel: logWriter, 77 | logrus.PanicLevel: logWriter, 78 | } 79 | 80 | lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{ 81 | TimestampFormat:"2006-01-02 15:04:05", 82 | }) 83 | 84 | // 新增钩子 85 | logger.AddHook(lfHook) 86 | 87 | return func(c *gin.Context) { 88 | 89 | bodyLogWriter := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer} 90 | c.Writer = bodyLogWriter 91 | 92 | // 开始时间 93 | startTime := time.Now() 94 | 95 | // 处理请求 96 | c.Next() 97 | 98 | responseBody := bodyLogWriter.body.String() 99 | 100 | var responseCode string 101 | var responseMsg string 102 | var responseData interface{} 103 | 104 | 105 | if responseBody != "" { 106 | res := entity.Result{} 107 | err := json.Unmarshal([]byte(responseBody), &res) 108 | if err == nil { 109 | responseCode = strconv.Itoa(res.Code) 110 | responseMsg = res.Message 111 | responseData = res.Data 112 | } 113 | } 114 | 115 | // 结束时间 116 | endTime := time.Now() 117 | 118 | if c.Request.Method == "POST" { 119 | c.Request.ParseForm() 120 | } 121 | 122 | // 日志格式 123 | logger.WithFields(logrus.Fields { 124 | "request_method" : c.Request.Method, 125 | "request_uri" : c.Request.RequestURI, 126 | "request_proto" : c.Request.Proto, 127 | "request_useragent" : c.Request.UserAgent(), 128 | "request_referer" : c.Request.Referer(), 129 | "request_post_data" : c.Request.PostForm.Encode(), 130 | "request_client_ip" : c.ClientIP(), 131 | 132 | "response_status_code" : c.Writer.Status(), 133 | "response_code" : responseCode, 134 | "response_msg" : responseMsg, 135 | "response_data" : responseData, 136 | 137 | "cost_time" : endTime.Sub(startTime), 138 | }).Info() 139 | } 140 | } 141 | 142 | // 日志记录到 MongoDB 143 | func LoggerToMongo() gin.HandlerFunc { 144 | return func(c *gin.Context) { 145 | 146 | } 147 | } 148 | 149 | // 日志记录到 ES 150 | func LoggerToES() gin.HandlerFunc { 151 | return func(c *gin.Context) { 152 | 153 | } 154 | } 155 | 156 | // 日志记录到 MQ 157 | func LoggerToMQ() gin.HandlerFunc { 158 | return func(c *gin.Context) { 159 | 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/middleware/recover/recover.go: -------------------------------------------------------------------------------- 1 | package recover 2 | 3 | import ( 4 | "fmt" 5 | "ginDemo/common/alarm" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Recover() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | defer func() { 12 | if r := recover(); r != nil { 13 | alarm.Panic(fmt.Sprintf("%s", r)) 14 | } 15 | }() 16 | c.Next() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/middleware/sign/sign.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | import ( 4 | "ginDemo/common/alarm" 5 | "ginDemo/common/function" 6 | "ginDemo/config" 7 | "ginDemo/entity" 8 | "github.com/gin-gonic/gin" 9 | "net/http" 10 | "net/url" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | func Sign() gin.HandlerFunc { 16 | return func(c *gin.Context) { 17 | res := entity.Result{} 18 | 19 | sign, err := verifySign(c) 20 | 21 | if sign != nil { 22 | res.SetCode(entity.CODE_ERROR) 23 | res.SetMessage("Debug Sign") 24 | res.SetData(sign) 25 | c.JSON(http.StatusUnauthorized, res) 26 | c.Abort() 27 | return 28 | } 29 | 30 | if err != nil { 31 | res.SetCode(entity.CODE_ERROR) 32 | res.SetMessage(err.Error()) 33 | c.JSON(http.StatusUnauthorized, res) 34 | c.Abort() 35 | return 36 | } 37 | 38 | c.Next() 39 | } 40 | } 41 | 42 | // 验证签名 43 | func verifySign(c *gin.Context) (res map[string]string, err error) { 44 | var method = c.Request.Method 45 | var ts int64 46 | var sn string 47 | var req url.Values 48 | var debug string 49 | 50 | if method == "GET" { 51 | req = c.Request.URL.Query() 52 | sn = c.Query("sn") 53 | debug = c.Query("debug") 54 | ts, _ = strconv.ParseInt(c.Query("ts"), 10, 64) 55 | } else if method == "POST" { 56 | c.Request.ParseForm() 57 | req = c.Request.PostForm 58 | sn = c.PostForm("sn") 59 | debug = c.PostForm("debug") 60 | ts, _ = strconv.ParseInt(c.PostForm("ts"), 10, 64) 61 | } else { 62 | err = alarm.New("非法请求") 63 | return 64 | } 65 | 66 | if debug == "1" { 67 | res = map[string]string{ 68 | "ts" : strconv.FormatInt(function.GetTimeUnix(), 10), 69 | "sn" : function.CreateSign(req), 70 | } 71 | return 72 | } 73 | 74 | exp, _ := strconv.ParseInt(config.API_EXPIRY, 10, 64) 75 | 76 | // 验证过期时间 77 | timestamp := time.Now().Unix() 78 | if ts > timestamp || timestamp - ts >= exp { 79 | err = alarm.New("Ts Error") 80 | return 81 | } 82 | 83 | // 验证签名 84 | if sn == "" || sn != function.CreateSign(req) { 85 | err = alarm.New("sn Error") 86 | return 87 | } 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "ginDemo/middleware/logger" 5 | "ginDemo/middleware/recover" 6 | "ginDemo/middleware/sign" 7 | "ginDemo/router/v1" 8 | "ginDemo/router/v2" 9 | "ginDemo/validator/member" 10 | "github.com/gin-gonic/gin" 11 | "github.com/gin-gonic/gin/binding" 12 | "gopkg.in/go-playground/validator.v8" 13 | ) 14 | 15 | func InitRouter(r *gin.Engine) { 16 | 17 | r.Use(logger.LoggerToFile(), recover.Recover()) 18 | 19 | // v1 版本 20 | GroupV1 := r.Group("/v1") 21 | { 22 | GroupV1.Any("/product/add", v1.AddProduct) 23 | GroupV1.Any("/member/add", v1.AddMember) 24 | } 25 | 26 | // v2 版本 27 | GroupV2 := r.Group("/v2").Use(sign.Sign()) 28 | { 29 | GroupV2.Any("/product/add", v2.AddProduct) 30 | GroupV2.Any("/member/add", v2.AddMember) 31 | } 32 | 33 | // 绑定验证器 34 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 35 | v.RegisterValidation("NameValid", member.NameValid) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/router/v1/member.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "ginDemo/entity" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | func AddMember(c *gin.Context) { 10 | 11 | res := entity.Result{} 12 | mem := entity.Member{} 13 | 14 | if err := c.ShouldBind(&mem); err != nil { 15 | res.SetCode(entity.CODE_ERROR) 16 | res.SetMessage(err.Error()) 17 | c.JSON(http.StatusForbidden, res) 18 | c.Abort() 19 | return 20 | } 21 | 22 | // 处理业务(下次再分享) 23 | 24 | data := map[string]interface{}{ 25 | "name" : mem.Name, 26 | "age" : mem.Age, 27 | } 28 | res.SetCode(entity.CODE_SUCCESS) 29 | res.SetData(data) 30 | c.JSON(http.StatusOK, res) 31 | } 32 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/router/v1/product.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "fmt" 5 | "ginDemo/entity" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | ) 9 | 10 | func AddProduct(c *gin.Context) { 11 | // 获取 Get 参数 12 | name := c.Query("name") 13 | 14 | var res = entity.Result{} 15 | 16 | str, err := hello(name) 17 | if err != nil { 18 | res.SetCode(entity.CODE_ERROR) 19 | res.SetMessage(err.Error()) 20 | c.JSON(http.StatusOK, res) 21 | c.Abort() 22 | return 23 | } 24 | 25 | res.SetCode(entity.CODE_SUCCESS) 26 | res.SetMessage(str) 27 | c.JSON(http.StatusOK, res) 28 | } 29 | 30 | func hello(name string) (str string, err error) { 31 | if name == "" { 32 | // 无意抛出 panic 33 | var slice = [] int {1, 2, 3, 4, 5} 34 | slice[6] = 6 35 | return 36 | } 37 | str = fmt.Sprintf("hello: %s", name) 38 | return 39 | } 40 | 41 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/router/v2/member.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func AddMember(c *gin.Context) { 6 | c.JSON(200, gin.H{ 7 | "v2": "AddMember", 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/router/v2/product.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func AddProduct(c *gin.Context) { 8 | // 获取 Get 参数 9 | name := c.Query("name") 10 | price := c.DefaultQuery("price", "100") 11 | 12 | c.JSON(200, gin.H{ 13 | "v2": "AddProduct", 14 | "name" : name, 15 | "price" : price, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/codes/05-自定义错误处理/ginDemo/validator/member/member.go: -------------------------------------------------------------------------------- 1 | package member 2 | 3 | import ( 4 | "gopkg.in/go-playground/validator.v8" 5 | "reflect" 6 | ) 7 | 8 | func NameValid( 9 | v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, 10 | field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, 11 | ) bool { 12 | if s, ok := field.Interface().(string); ok { 13 | if s == "admin" { 14 | return false 15 | } 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/images/01-框架安装/1_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/01-Gin框架/images/01-框架安装/1_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/images/02-路由配置/2_go_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/01-Gin框架/images/02-路由配置/2_go_1.png -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/images/02-路由配置/2_go_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/01-Gin框架/images/02-路由配置/2_go_2.png -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/images/02-路由配置/2_go_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/01-Gin框架/images/02-路由配置/2_go_3.png -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/images/02-路由配置/2_go_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/01-Gin框架/images/02-路由配置/2_go_4.png -------------------------------------------------------------------------------- /00.go语言基础/01-Gin框架/images/1_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/01-Gin框架/images/1_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/02-Go gRPC 调试工具.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 最近这段时间工作挺忙的,发现已经 3 周没更文了... 4 | 5 | 感谢你们还在,今天给大家分享一款 gRPC 的调试工具。 6 | 7 | 进入正题。 8 | 9 | 10 | 当我们在写 HTTP 接口的时候,使用的是 Postman 进行接口调试,那么在写 gRPC 接口的时候,有没有类似于 Postman 的调试工具呢? 11 | 12 | ![](https://github.com/xinliangnote/Go/blob/master/02-Go%20gRPC/images/2_grpc_1.gif) 13 | 14 | 15 | 这是有的。 16 | 17 | 咱们一起看下 `grpcui`,源码地址: 18 | 19 | https://github.com/fullstorydev/grpcui 20 | 21 | 看下官方描述: 22 | 23 | > grpcui is a command-line tool that lets you interact with gRPC servers via a browser. It's sort of like Postman, but for gRPC APIs instead of REST. 24 | 25 | ## 写一个 gRPC API 26 | 27 | 我原来写过 Demo,可以直接用原来写的 listen 项目。 28 | 29 | 端口:9901 30 | 31 | `.proto` 文件: 32 | 33 | ``` 34 | syntax = "proto3"; // 指定 proto 版本 35 | 36 | package listen; // 指定包名 37 | 38 | // 定义服务 39 | service Listen { 40 | 41 | // 定义方法 42 | rpc ListenData(Request) returns (Response) {} 43 | 44 | } 45 | 46 | // Request 请求结构 47 | message Request { 48 | string name = 1; 49 | } 50 | 51 | // Response 响应结构 52 | message Response { 53 | string message = 1; 54 | } 55 | ``` 56 | 57 | 很简单,这个大家一看就知道了。 58 | 59 | - Service name 为 listen.Listen 60 | - Method name 为 ListenData 61 | 62 | 再看下 ListenData 方法: 63 | 64 | ``` 65 | func (l *ListenController) ListenData(ctx context.Context, in *listen.Request) (*listen.Response, error) { 66 | return &listen.Response{Message : fmt.Sprintf("[%s]", in.Name)}, nil 67 | } 68 | ``` 69 | 70 | 这表示,将 `Name` 直接返回。 71 | 72 | 源码地址: 73 | 74 | https://github.com/xinliangnote/go-jaeger-demo/tree/master/listen 75 | 76 | #### 启动服务 77 | 78 | ``` 79 | cd listen && go run main.go 80 | ``` 81 | 82 | 服务启动成功后,等待使用。 83 | 84 | ## grpcui 使用 85 | 86 | #### 安装 87 | 88 | 根据官方 `README.md` 文档安装即可。 89 | 90 | ``` 91 | go get github.com/fullstorydev/grpcui 92 | go install github.com/fullstorydev/grpcui/cmd/grpcui 93 | ``` 94 | 95 | 这时,在 `$GOPATH/bin` 目录下,生成一个 `grpcui` 可执行文件。 96 | 97 | 执行个命令,验证下: 98 | 99 | ``` 100 | grpcui -help 101 | ``` 102 | 103 | 输出: 104 | 105 | ``` 106 | Usage: 107 | grpcui [flags] [address] 108 | 109 | ...... 110 | ``` 111 | 112 | 表示安装成功了。 113 | 114 | #### 运行 115 | 116 | ``` 117 | grpcui -plaintext 127.0.0.1:9901 118 | 119 | Failed to compute set of methods to expose: server does not support the reflection API 120 | ``` 121 | 122 | 这种情况下,加个反射就可以了,在 listen 的 main.go 新增如下代码即可: 123 | 124 | ``` 125 | reflection.Register(s) 126 | ``` 127 | 128 | 在运行一次试试: 129 | 130 | ``` 131 | grpcui -plaintext 127.0.0.1:9901 132 | gRPC Web UI available at http://127.0.0.1:63027/ 133 | ``` 134 | 135 | 在浏览器中访问:`http://127.0.0.1:63027/` 136 | 137 | ![](https://github.com/xinliangnote/Go/blob/master/02-Go%20gRPC/images/2_grpc_2.gif) 138 | 139 | 到这,我们看到 Service name、Method name 都出来了,传输参数直接在页面上进行操作即可。 140 | 141 | 当发起 Request "Tom",也能获得 Response “Tom”。 142 | 143 | 当然,如果这个服务下面有多个 Service name,多个 Method name 也都会显示出来的,去试试吧。 144 | 145 | ## go-gin-api 系列文章 146 | 147 | - [7. 路由中间件 - 签名验证](https://mp.weixin.qq.com/s/0cozELotcpX3Gd6WPJiBbQ) 148 | - [6. 路由中间件 - Jaeger 链路追踪(实战篇)](https://mp.weixin.qq.com/s/Ea28475_UTNaM9RNfgPqJA) 149 | - [5. 路由中间件 - Jaeger 链路追踪(理论篇)](https://mp.weixin.qq.com/s/28UBEsLOAHDv530ePilKQA) 150 | - [4. 路由中间件 - 捕获异常](https://mp.weixin.qq.com/s/SconDXB_x7Gan6T0Awdh9A) 151 | - [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg) 152 | - [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA) 153 | - [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A) 154 | 155 | -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/codes/01-gRPC Hello World/README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 3 | [gRPC Hello World](https://github.com/xinliangnote/Go/blob/master/02-Go%20gRPC/01-Go%20gRPC%20Hello%20World.md) 4 | 5 | **目录结构** 6 | 7 | ``` 8 | ├─ hello -- 代码根目录 9 | │ ├─ go_client 10 | │ ├── main.go 11 | │ ├── proto 12 | │ ├── hello 13 | │ ├── hello.pb.go 14 | │ ├─ go_server 15 | │ ├── main.go 16 | │ ├── controller 17 | │ ├── hello_controller 18 | │ ├── hello_server.go 19 | │ ├── proto 20 | │ ├── hello 21 | │ ├── hello.pb.go 22 | │ ├── hello.proto 23 | ``` 24 | 25 | **hello.proto** 26 | 27 | ``` 28 | syntax = "proto3"; // 指定 proto 版本 29 | 30 | package hello; // 指定包名 31 | 32 | // 定义 Hello 服务 33 | service Hello { 34 | 35 | // 定义 SayHello 方法 36 | rpc SayHello(HelloRequest) returns (HelloResponse) {} 37 | 38 | // 定义 LotsOfReplies 方法 39 | rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){} 40 | } 41 | 42 | // HelloRequest 请求结构 43 | message HelloRequest { 44 | string name = 1; 45 | } 46 | 47 | // HelloResponse 响应结构 48 | message HelloResponse { 49 | string message = 1; 50 | } 51 | ``` 52 | 53 | ## 效果 54 | 55 | ``` 56 | go run main.go 57 | 58 | 2019/07/28 17:58:13 Hello World 59 | 2019/07/28 17:58:13 Hello World Reply 0 60 | 2019/07/28 17:58:13 Hello World Reply 1 61 | 2019/07/28 17:58:13 Hello World Reply 2 62 | 2019/07/28 17:58:13 Hello World Reply 3 63 | 2019/07/28 17:58:13 Hello World Reply 4 64 | 2019/07/28 17:58:13 Hello World Reply 5 65 | 2019/07/28 17:58:13 Hello World Reply 6 66 | 2019/07/28 17:58:13 Hello World Reply 7 67 | 2019/07/28 17:58:13 Hello World Reply 8 68 | 2019/07/28 17:58:13 Hello World Reply 9 69 | ``` -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/codes/01-gRPC Hello World/go_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "hello/go_client/proto/hello" 5 | "io" 6 | "log" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | // gRPC 服务地址 13 | Address = "0.0.0.0:9090" 14 | ) 15 | 16 | func main() { 17 | conn, err := grpc.Dial(Address, grpc.WithInsecure()) 18 | if err != nil { 19 | log.Fatalln(err) 20 | } 21 | defer conn.Close() 22 | 23 | // 初始化客户端 24 | c := hello.NewHelloClient(conn) 25 | 26 | // 调用 SayHello 方法 27 | res, err := c.SayHello(context.Background(), &hello.HelloRequest{Name: "Hello World"}) 28 | 29 | if err != nil { 30 | log.Fatalln(err) 31 | } 32 | 33 | log.Println(res.Message) 34 | 35 | // 调用 LotsOfReplies 方法 36 | stream, err := c.LotsOfReplies(context.Background(),&hello.HelloRequest{Name: "Hello World"}) 37 | if err != nil { 38 | log.Fatalln(err) 39 | } 40 | 41 | for { 42 | res, err := stream.Recv() 43 | if err == io.EOF { 44 | break 45 | } 46 | 47 | if err != nil { 48 | log.Printf("stream.Recv: %v", err) 49 | } 50 | 51 | log.Printf("%s", res.Message) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/codes/01-gRPC Hello World/go_server/controller/hello_controller/hello_server.go: -------------------------------------------------------------------------------- 1 | package hello_controller 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/net/context" 6 | "hello/go_server/proto/hello" 7 | ) 8 | 9 | type HelloController struct{} 10 | 11 | func (h *HelloController) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) { 12 | return &hello.HelloResponse{Message : fmt.Sprintf("%s", in.Name)}, nil 13 | } 14 | 15 | func (h *HelloController) LotsOfReplies(in *hello.HelloRequest, stream hello.Hello_LotsOfRepliesServer) error { 16 | for i := 0; i < 10; i++ { 17 | stream.Send(&hello.HelloResponse{Message : fmt.Sprintf("%s %s %d", in.Name, "Reply", i)}) 18 | } 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/codes/01-gRPC Hello World/go_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "hello/go_server/proto/hello" 7 | "hello/go_server/controller/hello_controller" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | Address = "0.0.0.0:9090" 13 | ) 14 | 15 | func main() { 16 | listen, err := net.Listen("tcp", Address) 17 | if err != nil { 18 | log.Fatalf("Failed to listen: %v", err) 19 | } 20 | 21 | s := grpc.NewServer() 22 | 23 | // 服务注册 24 | hello.RegisterHelloServer(s, &hello_controller.HelloController{}) 25 | 26 | log.Println("Listen on " + Address) 27 | 28 | if err := s.Serve(listen); err != nil { 29 | log.Fatalf("Failed to serve: %v", err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/codes/01-gRPC Hello World/go_server/proto/hello/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 指定 proto 版本 2 | 3 | package hello; // 指定包名 4 | 5 | // 定义 Hello 服务 6 | service Hello { 7 | 8 | // 定义 SayHello 方法 9 | rpc SayHello(HelloRequest) returns (HelloResponse) {} 10 | 11 | // 定义 LotsOfReplies 方法 12 | rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){} 13 | } 14 | 15 | // HelloRequest 请求结构 16 | message HelloRequest { 17 | string name = 1; 18 | } 19 | 20 | // HelloResponse 响应结构 21 | message HelloResponse { 22 | string message = 1; 23 | } 24 | -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/images/2_grpc_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/02-Go gRPC/images/2_grpc_1.gif -------------------------------------------------------------------------------- /00.go语言基础/02-Go gRPC/images/2_grpc_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/02-Go gRPC/images/2_grpc_2.gif -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/01-[go-gin-api] 使用 go modules 初始化项目.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 我想实现一个开箱即用的 API 框架的轮子,这个轮子是基于 Gin 基础上开发的。 4 | 5 | 为什么是开箱即用,它会集成哪些功能? 6 | 7 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/1_api_1.png) 8 | 9 | 以上功能点,都是常用的,后期可能还会增加。 10 | 11 | 废话不多说,咱们开始吧。 12 | 13 | 创建一个项目,咱们首先要考虑一个依赖包的管理工具。 14 | 15 | 常见的包管理有,dep、go vendor、glide、go modules 等。 16 | 17 | 最开始,使用过 dep,当时被朋友 diss 了,推荐我使用 go modules 。 18 | 19 | 现在来说一下 go modules ,这个是随着 Go 1.11 的发布和我们见面的,这是官方提倡的新的包管理。 20 | 21 | 说一个环境变量:GO111MODULE,默认值为 auto 。 22 | 23 | 当项目中有 go.mod 时,使用 go modules 管理,反之使用 旧的 GOPATH 和 vendor机制。 24 | 25 | 如果就想使用 go modules ,可以将 GO111MODULE 设置为 on 。 26 | 27 | 直接上手吧。 28 | 29 | ## 初始化 30 | 31 | 咱们在 GOPATH 之外的地方,新建一个空文件夹 `go-gin-api` 。 32 | 33 | ``` 34 | cd go-gin-api && go mod init go-gin-api 35 | ``` 36 | 37 | 输出: 38 | 39 | go: creating new go.mod: module go-gin-api 40 | 41 | 这时目录中多一个 go.mod 文件,内容如下: 42 | 43 | ``` 44 | module go-gin-api 45 | 46 | go 1.12 47 | ``` 48 | 49 | 到这,go mod 初始化就完成,接下来添加依赖包 - gin。 50 | 51 | 52 | ## 添加依赖包 53 | 54 | 在目录中创建一个 `main.go` 的文件,放上如下代码: 55 | 56 | ``` 57 | package main 58 | 59 | import "github.com/gin-gonic/gin" 60 | 61 | func main() { 62 | r := gin.Default() 63 | r.GET("/ping", func(c *gin.Context) { 64 | c.JSON(200, gin.H{ 65 | "message": "pong", 66 | }) 67 | }) 68 | r.Run() // listen and serve on 0.0.0.0:8080 69 | } 70 | ``` 71 | 72 | 这代码没什么特别的,就是官方的入门Demo。 73 | 74 | 接下来,开始下载依赖包。 75 | 76 | ``` 77 | go mod tidy 78 | ``` 79 | 80 | 执行完成后,看一下 `go.mod` 文件: 81 | 82 | ``` 83 | module go-gin-api 84 | 85 | go 1.12 86 | 87 | require github.com/gin-gonic/gin v1.4.0 88 | ``` 89 | 90 | 这时,看到新增一个 gin v1.4.0 的包。 91 | 92 | 还生成了一个 go.sum 的文件,这个文件可以暂时先不管。 93 | 94 | 这时发现了 2 个问题。 95 | 96 | 1、目录中没发现 gin 包,包下载到哪了? 97 | 98 | 下载到了 GOPATH/pkg/mod 目录中。 99 | 100 | 2、GoLand 编辑器中关于 Gin 的引用变红了? 101 | 102 | 在这里编辑器需要设置一下,如图: 103 | 104 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/1_api_2.png) 105 | 106 | 点击 Apply 和 OK 即可。 107 | 108 | 如果这招不灵,还可以执行: 109 | 110 | ``` 111 | go mod vendor 112 | ``` 113 | 114 | 这个命令是将项目依赖的包,放到项目的 vendor 目录中,这肯定就可以了。 115 | 116 | ## go mod 命令 117 | 118 | **go mod tidy** 119 | 120 | 拉取缺少的模块,移除不用的模块。 121 | 122 | 我常用这个命令。 123 | 124 | **go mod vendor** 125 | 126 | 将依赖复制到vendor下。 127 | 128 | 我常用这个命令。 129 | 130 | **go mod download** 131 | 132 | 下载依赖包。 133 | 134 | **go mod verify** 135 | 136 | 检验依赖。 137 | 138 | **go mod graph** 139 | 140 | 打印模块依赖图。 141 | 142 | 143 | 其他命令,可以执行 `go mod` ,查看即可。 144 | 145 | ## 小结 146 | 147 | 这篇文章,分享了 go modules 的使用。 148 | 149 | - 使用 go modules 从零搭建一个项目。 150 | - GoLand 编辑器使用 go modules。 151 | 152 | 今天就到这了,下一篇文章开始搭建 API 项目了,写参数验证。 153 | 154 | ## 源码地址 155 | 156 | https://github.com/xinliangnote/go-gin-api 157 | 158 | 159 | -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/04-[go-gin-api] 路由中间件 - 捕获异常.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 首先同步下项目概况: 4 | 5 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/4_api_1.png) 6 | 7 | 上篇文章分享了,路由中间件 - 日志记录,这篇文章咱们分享:路由中间件 - 捕获异常。当系统发生异常时,提示 “系统异常,请联系管理员!”,同时并发送 panic 告警邮件。 8 | 9 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/4_api_2.png) 10 | 11 | ## 什么是异常? 12 | 13 | 在 Go 中异常就是 panic,它是在程序运行的时候抛出的,当 panic 抛出之后,如果在程序里没有添加任何保护措施的话,控制台就会在打印出 panic 的详细情况,然后终止运行。 14 | 15 | 我们可以将 panic 分为两种: 16 | 17 | 一种是有意抛出的,比如, 18 | 19 | ``` 20 | panic("自定义的 panic 信息") 21 | ``` 22 | 23 | 输出: 24 | 25 | ``` 26 | 2019/09/10 20:25:27 http: panic serving [::1]:61547: 自定义的 panic 信息 27 | goroutine 8 [running]: 28 | ... 29 | ``` 30 | 31 | 一种是无意抛出的,写程序马虎造成,比如, 32 | 33 | ``` 34 | var slice = [] int {1, 2, 3, 4, 5} 35 | 36 | slice[6] = 6 37 | ``` 38 | 39 | 输出: 40 | 41 | ``` 42 | 2019/09/10 15:27:05 http: panic serving [::1]:61616: runtime error: index out of range 43 | goroutine 6 [running]: 44 | ... 45 | ``` 46 | 47 | 想象一下,如果在线上环境出现了 panic,命令行输出的,因为咱们无法捕获就无法定位问题呀,想想都可怕,那么问题来了,怎么捕获异常? 48 | 49 | ## 怎么捕获异常? 50 | 51 | 当程序发生 panic 后,在 defer(延迟函数) 内部可以调用 recover 进行捕获。 52 | 53 | 不多说,直接上代码: 54 | 55 | ``` 56 | defer func() { 57 | if err := recover(); err != nil { 58 | fmt.Println(err) 59 | } 60 | }() 61 | ``` 62 | 63 | 在运行一下 “无意抛出的 panic ”,输出: 64 | 65 | ``` 66 | runtime error: index out of range 67 | ``` 68 | 69 | OK,错误捕获到了,这时我们可以进行做文章了。 70 | 71 | 做啥文章,大家应该都知道了吧: 72 | 73 | - 获取运行时的调用栈(debug.Stack()) 74 | - 获取当时的 Request 数据 75 | - 组装数据,进行发邮件 76 | 77 | 那么,Go 怎么发邮件呀,有没有开源包呀? 78 | 79 | 当然有,请往下看。 80 | 81 | ## 封装发邮件方法 82 | 83 | 使用包:`gopkg.in/gomail.v2` 84 | 85 | 直接上代码: 86 | 87 | ``` 88 | func SendMail(mailTo string, subject string, body string) error { 89 | 90 | if config.ErrorNotifyOpen != 1 { 91 | return nil 92 | } 93 | 94 | m := gomail.NewMessage() 95 | 96 | //设置发件人 97 | m.SetHeader("From", config.SystemEmailUser) 98 | 99 | //设置发送给多个用户 100 | mailArrTo := strings.Split(mailTo, ",") 101 | m.SetHeader("To", mailArrTo...) 102 | 103 | //设置邮件主题 104 | m.SetHeader("Subject", subject) 105 | 106 | //设置邮件正文 107 | m.SetBody("text/html", body) 108 | 109 | d := gomail.NewDialer(config.SystemEmailHost, config.SystemEmailPort, config.SystemEmailUser, config.SystemEmailPass) 110 | 111 | err := d.DialAndSend(m) 112 | if err != nil { 113 | fmt.Println(err) 114 | } 115 | return err 116 | } 117 | ``` 118 | 119 | 在这块我加了一个开关,想开想关,您随意。 120 | 121 | 现在会发送邮件了,再整个邮件模板就完美了。 122 | 123 | ## 自定义邮件模板 124 | 125 | 如图: 126 | 127 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/4_api_3.png) 128 | 129 | 这就是告警邮件的模板,还不错吧,大家还想记录什么,可以自定义去修改。 130 | 131 | ## 封装一个中间件 132 | 133 | 最后,封装一下。 134 | 135 | 直接上代码: 136 | 137 | ``` 138 | func SetUp() gin.HandlerFunc { 139 | 140 | return func(c *gin.Context) { 141 | defer func() { 142 | if err := recover(); err != nil { 143 | 144 | DebugStack := "" 145 | for _, v := range strings.Split(string(debug.Stack()), "\n") { 146 | DebugStack += v + "
" 147 | } 148 | 149 | subject := fmt.Sprintf("【重要错误】%s 项目出错了!", config.AppName) 150 | 151 | body := strings.ReplaceAll(MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", err)) 152 | body = strings.ReplaceAll(body, "{RequestTime}", util.GetCurrentDate()) 153 | body = strings.ReplaceAll(body, "{RequestURL}", c.Request.Method + " " + c.Request.Host + c.Request.RequestURI) 154 | body = strings.ReplaceAll(body, "{RequestUA}", c.Request.UserAgent()) 155 | body = strings.ReplaceAll(body, "{RequestIP}", c.ClientIP()) 156 | body = strings.ReplaceAll(body, "{DebugStack}", DebugStack) 157 | 158 | _ = util.SendMail(config.ErrorNotifyUser, subject, body) 159 | 160 | utilGin := util.Gin{Ctx: c} 161 | utilGin.Response(500, "系统异常,请联系管理员!", nil) 162 | } 163 | }() 164 | c.Next() 165 | } 166 | } 167 | ``` 168 | 169 | 当发生 panic 异常时,输出: 170 | 171 | ``` 172 | { 173 | "code": 500, 174 | "msg": "系统异常,请联系管理员!", 175 | "data": null 176 | } 177 | ``` 178 | 179 | 同时,还会收到一封 panic 告警邮件。 180 | 181 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/4_api_4.png) 182 | 183 | 便于截图,DebugStack 删减了一些信息。 184 | 185 | 到这,就结束了。 186 | 187 | ![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/4_api_5.jpeg) 188 | 189 | ## 备注 190 | 191 | - 发邮件的地方,可以调整为异步发送。 192 | - 文章中仅贴了部分代码,相关代码请查阅 github。 193 | - 测试发邮件时,一定要配置邮箱信息。 194 | 195 | ## 源码地址 196 | 197 | https://github.com/xinliangnote/go-gin-api 198 | 199 | ## go-gin-api 系列文章 200 | 201 | - [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A) 202 | - [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA) 203 | - [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg) -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/1_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/1_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/1_api_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/1_api_2.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/2_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/2_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/3_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/3_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/4_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/4_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/4_api_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/4_api_2.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/4_api_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/4_api_3.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/4_api_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/4_api_4.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/4_api_5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/4_api_5.jpeg -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_10.jpeg -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_2.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_3.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_4.jpeg -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_5.jpeg -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_6.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_7.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_8.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/5_api_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/5_api_9.jpg -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/6_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/6_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/6_api_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/6_api_2.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/6_api_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/6_api_3.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/6_api_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/6_api_4.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/6_api_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/6_api_5.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/6_api_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/6_api_6.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/7_api_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/7_api_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/7_api_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/7_api_2.gif -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/go-gin-api-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/go-gin-api-logo.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_1.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_2.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_3.png -------------------------------------------------------------------------------- /00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/00.go语言基础/03-go-gin-api [文档]/images/jaeger_demo_4.png -------------------------------------------------------------------------------- /00.go语言基础/README.md: -------------------------------------------------------------------------------- 1 | ### 来源github 2 | # Golang 语言重点学习路线 3 | 4 | # Go 语言环境搭建 5 | 6 | ## 环境变量 7 | 8 | GOROOT 9 | 10 | GOPATH 11 | 12 | GOPROXY 13 | 14 | export PATH=$GOROOT/bin:$GOPATH/bin:$PATH 15 | 16 | ## 包管理 17 | 18 | ### Mod 19 | 20 | 添加依赖 21 | 22 | go get 23 | 24 | 更新依赖 25 | 26 | go get -u 27 | 28 | 整理依赖 29 | 30 | go mod tidy 31 | 32 | 替换依赖为其他包 33 | 34 | go mod edit -replace < 原 package>=< 新 package 或本地路径 > 35 | 36 | # 语言基础重点 37 | 38 | ## 函数与闭包 39 | 40 | ## 切片 41 | 42 | append 关键字 43 | 44 | copy 关键字 45 | 46 | range 关键字 47 | 48 | ## goroutine 使用 49 | 50 | 启动 51 | 52 | 结束 53 | 54 | ## channel 使用 55 | 56 | 声明 57 | 58 | 传递 59 | 60 | 读取与写入 61 | 62 | 配合 select 关键字 63 | 64 | ## defer 关键字 65 | 66 | 搭配 sync.Mutex 使用 67 | 68 | ## context 包 69 | 70 | 声明 71 | 72 | context.WithCancel 73 | 74 | context.WithTimeout 75 | 76 | ## sync 包工具 77 | 78 | sync.Mutex 79 | 80 | sync.Once 81 | 82 | sync.Pool 83 | 84 | sync.WaitGroup 85 | 86 | # 框架学习 87 | 88 | ## gin 89 | 90 | 官方文档 [https://gin-gonic.com/docs/examples/](https://gin-gonic.com/docs/examples/) 91 | 92 | 配置路由(声明公开 API) 93 | 94 | 配置中间件(类似 tomcat 的 filter 和 spring 中的 interceptor) 95 | 96 | 配置日志 97 | 98 | 读取配置 99 | 100 | ## gorm 101 | 102 | 官方文档 [https://gorm.io/docs](https://gorm.io/docs) 103 | 104 | 连接数据库 105 | 106 | 基本 crud 107 | 108 | 执行事务 109 | 110 | ## ethclient(sdk 使用) 111 | 112 | 查询区块 113 | 114 | 查询交易 115 | 116 | 查询 receipt 117 | 118 | 执行交易,部署合约,调用合约(非生成代码,手动构造 input 参数,知道合约调用的本质) 119 | 120 | 查询余额 121 | 122 | 查询 account 123 | 124 | 查询 event,过滤合约事件 125 | 126 | # CI/CD 基础操作 127 | 128 | ## Makefile 129 | 130 | go test 131 | 132 | go build 133 | 134 | check lint 135 | 136 | build docker 137 | 138 | Dockerfile 139 | 140 | Github action 使用 141 | -------------------------------------------------------------------------------- /02.了解如何使用Solidity.md: -------------------------------------------------------------------------------- 1 | # 了解如何使用Solidity 2 | ## 学习solidity推荐教程 3 | 完整应用: https://github.com/RemoteCodeCamp/solidity-v2 4 | # Solidity 教学大纲 5 | 6 | ## 1. Solidity 简介 7 | - Solidity 是什么? 8 | - Solidity 的特点和用途 9 | 10 | ## 2. Solidity 安装与使用 11 | - 安装 Remix IDE 12 | - Remix IDE 界面简介 13 | 14 | ## 3. Hello World 15 | - 编写简单的 Solidity 合约 16 | - 在 Remix IDE 中编译和部署合约 17 | 18 | ## 4. Solidity 合约组成结构 19 | - 合约结构概述 20 | - 合约的基本组成部分 21 | 22 | ## 5. 注释 23 | - Solidity 中的注释类型和用法 24 | 25 | ## 6. 数据类型 26 | - Solidity 中的基本数据类型 27 | 28 | ## 7. 变量 29 | - 定义和声明变量 30 | - 变量的作用域和生命周期 31 | 32 | ## 8. 数据位置 33 | - 内存、存储和栈上数据的区别和使用 34 | 35 | ## 9. 布尔类型 36 | - Solidity 中的布尔类型及其用法 37 | 38 | ## 10. 整型 39 | - 不同类型的整数及其范围 40 | 41 | ## 11. 地址类型 42 | - 地址类型的特点和用法 43 | 44 | ## 12. 静态字节数组 45 | - 定义和使用静态字节数组 46 | 47 | ## 13. 字面值 48 | - Solidity 中常见的字面值表示 49 | 50 | ## 14. 枚举类型 51 | - 定义和使用枚举类型 52 | 53 | ## 15. 自定义值类型 54 | - 如何定义和使用自定义值类型 55 | 56 | ## 16. 数组 57 | - 数组的定义和初始化 58 | - 数组的操作和常见用法 59 | 60 | ## 17. 数组切片 61 | - 对数组的部分进行操作和访问 62 | 63 | ## 18. 数组成员 64 | - 数组的成员属性和方法 65 | 66 | ## 19. 多维数组 67 | - 定义和操作多维数组 68 | 69 | ## 20. 动态字节数组 70 | - 动态字节数组的定义和使用 71 | 72 | ## 21. 结构体 73 | - 定义和使用结构体 74 | 75 | ## 22. 映射类型 76 | - 定义和使用映射 77 | 78 | ## 23. 控制结构 79 | - if-else 语句 80 | - for 循环 81 | - while 循环 82 | - do while 循环 83 | - break 和 continue 语句 84 | 85 | ## 24. require、assert 和 revert 86 | - 区别和用法 87 | 88 | ## 25. 合约及其组成结构 89 | - 合约概述 90 | - 合约的基本组成部分 91 | 92 | ## 26. constant 和 immutable 93 | - 区别和用法 94 | 95 | ## 27. 函数 96 | - 函数的定义和调用 97 | - 函数的参数和返回值 98 | 99 | ## 28. 构建函数 100 | - 合约的构建函数定义和使用 101 | 102 | ## 29. 修饰器 103 | - 修饰器的定义和使用 104 | 105 | ## 30. 可见性 106 | - 函数和状态变量的可见性修饰符 107 | 108 | ## 31. 状态可变性 109 | - 修改函数状态的关键字 110 | 111 | ## 32. view 和 pure 函数 112 | - 区别和用法 113 | 114 | ## 33. receive 和 fallback 函数 115 | - 接收以太币的特殊函数 116 | -------------------------------------------------------------------------------- /05.以太坊网络基础知识.md: -------------------------------------------------------------------------------- 1 | # 以太坊的基础知识 2 | ### 什么是以太坊? 3 | 以太坊是一个具有广阔愿景的公共区块链网络。以太坊的设计者意识到,如果你建立了一个公共区块链网络,你可以做的不仅仅是跟踪数字货币:你可以运行一台 **全球公共计算机**,始终向世界开放。这就是以太坊:它是一个全球网络,能够在**以太坊虚拟机(EVM)上运行程序**。**程序是用名为Solidity**的语言为 EVM 编写的,网络使用一种名为**以太坊**(或**ETH**,发音为“eeth”)的加密货币来补偿维护网络的人员,同时也作为进行交易的价值代币在网络上。 4 | ### 那么,你如何协调这一切呢? 5 | 如前所述,区块链网络的一个基本功能是协调网络中所有节点之间关于交易是否有效的一致过程。这种协议称为 共识,其发生的过程称为 ***共识机制***,或 共识协议。以太坊有两种不同的共识机制,第一个是 ***工作量证明(PoW)***,第二个是 ***权益证明(PoS)***。 2022 年,以太坊主网上 PoW 被 PoS 取代。 6 | 在这两种机制中,计算机都被用来完成验证交易有效性并达成一致的工作。 7 | ### 什么是矿工?验证器是同一回事吗? 8 | 在 **PoW共识** 下,被称为“**矿工**”的参与者承担验证交易、创建区块和维护链条的责任。作为交换,每当这些矿工的节点第一个完成或开采一个新区块时,他们就会获得奖励(以 ETH 为单位);这也激励矿工拥有优质的设备和连接速度,这反过来又有助于网络。 9 | 然而,存在足够多的矿工联合起来(至少 51% 的网络)并颠覆网络控制以达到自己目的的可能性,重写交易历史记录、窃取代币等。为了防止这种情况和其他安全问题为了解决这些问题,挖矿故意变得困难,即计算复杂(通常称为“昂贵”),以使任何流氓行为者几乎不可能执行所谓的“51%攻击”。 10 | 这种旨在确保网络安全的设计具有副作用,特别是环境和现实世界的经济影响;区块链上的经济模型也是低效的。更快地开采区块的持续激励意味着矿工有真正的动力去购买新计算机,甚至是除了在区块链上挖矿之外不做任何事情的专用计算机。大型、功能强大的计算机消耗大量电力,并产生大量热量。从很多方面来看,这种对环境不友好、效率低下的军备竞赛很难证明其合理性。 11 | 进入**PoS 共识 :验证者**不是矿工, 而是确保交易有效性和网络完整性的参与者。每个验证者必须** 抵押** 32 ETH,以取代昂贵的数字运算作为安全措施;也就是说,将其存入 ***智能合约***(一种存在于以太坊区块链上的计算机程序)中,并承诺他们将根据规则操作验证器。如果他们恶意行事,或者试图颠覆或攻击网络,或者只是没有保持足够的连接性,那么他们所质押的货币将被**削减**或被拿走。如果他们做了他们应该做的事情,保持连接并确认交易,他们将获得 ETH 奖励,就像矿工一样。有关 PoS 的更多信息,请参阅[此处](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/)。 12 | ### PoS 与 PoW:以太坊使用哪个? 13 | 以太坊目前使用 PoS,这意味着主网(即当前使用的单一区块链)依赖于称为验证器的特殊节点来创建和确认区块的有效性,并确保网络的持续完整性。以太坊于 2022 年 9 月 15 日从 PoW 切换到 PoS,意味着能源支出减少了 99%。 14 | ### 什么是Gas?为什么我必须使用它? 15 | Gas是以太坊虚拟机(EVM)上处理交易和智能合约所需计算量的计量单位。更复杂的智能合约和代码将需要更多的汽油来执行,就像更大、更强大的汽车需要更多的汽油来运行一样。 16 | 计算gas曾经非常复杂,但随着2021年8月4日以太坊改进协议 ***(EIP)1559***的实施,它被大大简化。本质上,您 为每单位的 Gas支付**基本费用**,这些费用在交易成功完成后会被**烧毁**或消失。除了基本费用之外,您还需要添加**优先费**(同样是每单位 Gas),其价值取决于您希望交易完成的速度。 17 | **以下是在 MetaMask 中**处理气体的一些基本细节: 18 | ### Gas limit 19 | Gas限额是您为了执行交易或 EVM 操作愿意**支付的最大 Gas 单位数**。发送 ETH 的标准交易通常需要花费**21,000 个 Gas** 。 20 | ### 最高优先费 21 | 最高优先费(也称为“矿工小费”)将支付给矿工或验证者,并激励他们优先处理您的交易。大多数情况下,您输入的“最大优先费”值就是您支付的金额。 22 | ### 最高费用 23 | 最高费用是为您的交易支付的全球总金额。计算公式为:***(基本费用 + 优先费用)x 使用的 Gas 单位***。每个 Gas 的最大费用与基本费用 + 每个 Gas 的最大优先费用之间的差额将“退还”给用户。 24 | ![](https://github.com/RCCCamp/openWeb3/blob/main/pic/5/1.PNG) 25 | 想了解更多关于天然气的信息吗?从这里开始。 26 | ### 代币 27 | 除了以太坊网络的原生货币以太币之外,以太坊上使用的代币主要有两种类型:**ERC-20**,它们是“可替代”代币,对应于人们所说的“加密货币”,以及**ERC-721和ERC-1155** ,***“不可替代的代币”,缩写NFT***的由来。 28 | 当然,两者之间的区别是高度技术性的,但归根结底是名称的区别。 ERC-20 代币经过专门设计,具有完全可互换性和流动性,就像传统的法定货币一样。例如,就像美元一样,每个相同类型的 ERC-20 代币都具有完全相同的价值:这就是“可替代”的含义。 29 | 另一方面,ERC-721 和 ERC-1155 代币经过专门设计,具有独特性和不可复制性,但当然可以在各方之间转移,**通常具有重大价值**。 ERC-721 是 以太坊上的OG NFT 标准,为“铸造”(创建)一次性的、独特的 NFT 提供了蓝图。 ERC-1155 出现得稍晚一些,它标准化了一种同时铸造多个 NFT(实际上是可替代代币)的方法。 30 | ### 如何访问以太坊? 31 | 由于区块链网络是一个独立的网络,与传统的互联网连接不同,因此需要特定的软件来访问网络并显示区块链上记录的数据。这可以通过**以太坊客户端**实现,该客户端是独立软件,通常只有命令行界面,是许多开发人员的首选工具。此外,以太坊社区还开发了许多资源,允许传统互联网和以太坊网络之间的连接。 32 | 这些努力的支柱是**MetaMask**,这是一款开创性的浏览器插件和移动应用程序,为用户提供托管(用户控制和拥有)以太坊钱包和**dapps**或去中心化应用程序的访问权限,使您可以与以太坊区块链进行交互。 33 | ### 什么是区块浏览器? 34 | 当您想要更深入地查看各个交易的详细信息或鸟瞰以太坊网络时,需要使用区块浏览器。区块浏览器是一个提供界面来导航和检查网络上包含的信息的网站。突出的例子包括**Etherscan和Ethplorer**,以及与以太坊相关的其他网络(“EVM 兼容”)都有自己的变体。 35 | ### 以太坊主网、测试网、侧链等 36 | 当您冒险进入去中心化网络(或 通常称为***Web3***)时 ,您将了解到以太坊实际上不仅仅是一个网络。以太坊区块链和 EVM 在以太坊主网上生存和运行,并且存在许多**以太坊测试网**,正如它们听起来的那样,是主网的沙盒版本,其中 ETH 除了测试应用程序之外没有任何实际价值。 37 | 但这只是开始;目前已经开发了许多与以太坊兼容的 侧链 ,为用户提供在单独的区块链上以该链的本机货币进行交易的选项,以避免有时昂贵的 EVM 和以太坊主网交易费用。其中一些链是专门为某个用例设计的,例如视频游戏或 DeFi;其他是通用的“扩展网络”,称为第 2 层或 L2,目的是增加以太坊的体积和容量。 38 | ***用户通常最终会在这些侧链上获得代币和 NFT,他们可以通过桥将其带回以太坊主网***; NFT 可以在市场上保存、展示、销售;这些代币可以交换为其他代币、兑换为 ETH、质押、借用、出借、用于其他 dapp、转移到其他侧链并用于视频游戏或视频游戏投资引擎混合体,或者,嗯……未来仍在发展中书面。 39 | -------------------------------------------------------------------------------- /08.区块链概念简介作业题.md: -------------------------------------------------------------------------------- 1 | 2 | ### 简述什么是区块链,以及它的基本工作原理。 3 | 4 | **回答要点:** 5 | 6 | - 区块链是一种用于记录和执行合同的分布式数据库技术 7 | - 使用加密确保历史记录难以被篡改 8 | - 参与者可通过共享账本跟踪变更来实现相互信任 9 | 10 | ### 为什么在需要多方参与的解决方案中,区块链比集中式数据库更有优势? 11 | 12 | **回答要点:** 13 | 14 | - 集中式数据库需要参与方相互信任中央机构 15 | - 区块链可在无需中央机构的情况下实现参与方之间的信任 16 | 17 | ### 区块链如何确保账本数据的一致性和不可变性? 18 | 19 | **回答要点:** 20 | 21 | - 通过块的概念和加密技术确保 22 | 23 | ### 在什么样的场景下使用区块链是合适的?需要考虑哪些因素? 24 | 25 | **回答要点:** 26 | 27 | - 需要参与多方协作且互不信任的场景 28 | - 需要考虑参与者、性能、业务逻辑和信任 29 | 30 | ### 区块链技术与传统集中式数据库相比有哪些不同? 31 | 32 | **回答要点:** 33 | 34 | - 分布式特性 35 | - 不可篡改的账本 36 | - 无需中央机构协调 37 | 38 | ### 区块链如何确保数据的不可篡改性? 39 | 40 | **回答要点:** 41 | 42 | - 区块链使用加密哈希函数(如 SHA-256)将区块内容转换为一个哈希值 43 | - 每个新区块都会包含前一个区块的哈希值,形成链式结构 44 | 45 | ### 什么是智能合约? 46 | 47 | **回答要点:** 48 | 49 | - 智能合约是存储在区块链上的程序 50 | - 满足预定条件时自动执行 51 | - 以太坊是最著名的支持智能合约的区块链平台 52 | 53 | ### 如何解释工作量证明(PoW)和权益证明(PoS)? 54 | 55 | **回答要点:** 56 | 57 | - 工作量证明(PoW):节点需解决复杂数学问题以获取记账权,这个过程被称为挖矿。解决问题需要大量计算资源,从而确保网络安全。 58 | - 权益证明(PoS):节点根据其持有的货币数量和持有时间来获取记账权。这种机制减少了能源消耗,提高了网络效率。 59 | 60 | ### 区块链的主要类型有哪些? 61 | 62 | **回答要点:** 63 | 64 | - 公链(Public Blockchain):任何人都可以参与验证和记录交易,例如比特币和以太坊。 65 | - 私链(Private Blockchain):参与验证和记录交易的节点是受限制的,通常用于企业内部。 66 | - 联盟链(Consortium Blockchain):由多个组织共同维护的区块链,适用于跨机构的业务场景,如供应链管理和金融交易。 67 | 68 | ### 如何实现跨链技术? 69 | 70 | **回答要点:** 71 | 72 | - **哈希锁定:** 哈希锁定技术涉及到创建一种需要正确密钥才能解锁资产的条件。 73 | - **侧链:** 侧链是与主链并行运行的独立区块链,它们通过一种双向锚定机制与主链相连。侧链允许资产和信息能够在两个链之间移动。 74 | - **跨链协议:** 跨链协议是一套协议和规范的集合,设计用来连接不同的区块链网络。 75 | - **中继链:** 中继链是连接两个或多个独立区块链的区块链,它充当这些不同区块链之间的中介。 76 | - **桥接技术:** 桥接是指连接两个独立区块链以允许资产和数据的互操作性的技术。 77 | -------------------------------------------------------------------------------- /10.以太坊基础知识作业题.md: -------------------------------------------------------------------------------- 1 | ### 什么是以太坊? 2 | 3 | **回答要点:** 4 | 5 | - 以太坊是一个公共区块链网络,设计为一个全球性的、开放的分布式计算平台。 6 | - 通过其内置的以太坊虚拟机(EVM)来执行用户编写的智能合约,这些合约是用 Solidity 等编程语言编写的。 7 | - 以太坊的原生货币是 Ether(ETH),它用作交易费用和网络服务的支付方式。 8 | 9 | ### 以太坊使用的共识机制有哪些? 10 | 11 | **回答要点:** 12 | 13 | - 以太坊最初使用工作量证明(PoW)共识机制,要求矿工通过解决复杂数学问题来验证交易和创建新区块。 14 | - 2022 年,以太坊转向了权益证明(PoS)机制,在这个系统中,验证者通过抵押 ETH 来参与网络的维护和新区块的创建。 15 | - PoS 机制显著降低了网络的能源消耗,并提高了其安全性和可扩展性。 16 | 17 | ### 为什么以太坊从 PoW 转向 PoS? 18 | 19 | **回答要点:** 20 | 21 | - 以太坊从 PoW 转向 PoS 主要是为了解决环境和效率问题。 22 | - PoS 机制通过减少计算需求来大幅降低能源消耗,增强网络的安全性,并通过减少对高性能硬件的依赖来降低参与门槛。 23 | 24 | ### 解释一下什么是 Gas,它在以太坊中扮演什么角色? 25 | 26 | **回答要点:** 27 | 28 | - Gas 是执行操作和运行智能合约在以太坊网络上所需的计算工作量的度量单位。 29 | - 每项操作或交易都需要消耗一定的 Gas,以支付网络中计算和执行的资源。 30 | - Gas 费用用以太币支付,是以太坊安全和抗拒绝服务攻击的关键机制。 31 | 32 | ### 解释“区块浏览器”是什么以及它的用途。 33 | 34 | **回答要点:** 35 | 36 | - 区块浏览器是一个在线工具,可以查看区块链上的所有交易和区块。 37 | - 用户可以使用区块浏览器查询地址的交易历史、区块的状态、智能合约的详情等。 38 | - 用于增加区块链网络的透明度和可追踪性。 39 | 40 | ### 以太坊主网、测试网和侧链有什么不同? 41 | 42 | **回答要点:** 43 | 44 | - 以太坊主网是实际运行真实资产的生产区块链。 45 | - 测试网如 Sepolia 或 Ropsten 用于开发和测试目的,使用的是无实际价值的 ETH。 46 | - 侧链是独立于以太坊主网,可以支持额外的应用或提供更低的交易费用,使用不同的区块链技术,但通常与主网有某种形式的互操作性。 47 | 48 | ### MetaMask 是什么,如何使用它? 49 | 50 | **回答要点:** 51 | 52 | - MetaMask 是一个浏览器插件,同时也是移动应用,允许用户通过一个用户友好的界面与以太坊区块链交互。 53 | - 用户可以通过 MetaMask 发送和接收以太币和代币,运行和交互智能合约,以及连接到去中心化应用(dApps)。它也支持用户创建和管理自己的身份。 54 | 55 | ### 什么是去中心化应用(dApps) 56 | 57 | **回答要点:** 58 | 59 | - 去中心化应用(dApps)是运行在区块链网络上的应用,它们利用智能合约在去中心化的环境中自动执行、操作和管理数据。 60 | - dApps 通常不受任何单一实体控制,提供透明性、抗审查性和安全性等特点。 61 | - dApps 可以服务于各种领域,从游戏到金融服务等。 62 | -------------------------------------------------------------------------------- /11.加密钱包和网络的使用作业题.md: -------------------------------------------------------------------------------- 1 | 2 | ### 什么是非托管型钱包?请举例说明。 3 | 4 | **回答要点:** 5 | 6 | - 非托管型钱包是指用户完全控制钱包密钥(助记词或私钥)的钱包。 7 | - 用户对资产有完全的控制权,意味着他们也承担保护密钥的责任。 8 | 9 | ### 描述冷钱包与热钱包的主要区别及各自的优缺点。 10 | 11 | **回答要点:** 12 | 13 | - 冷钱包是一种不触网的存储方式,如硬件钱包或纸质钱包,提供较高的安全性但使用不便。 14 | - 热钱包如手机应用或浏览器插件,常常触网,方便用户访问和使用,但安全性较低因为容易受到网络攻击。 15 | 16 | ### 什么是跨链桥?请说明其工作原理。 17 | 18 | **回答要点:** 19 | 20 | - 跨链桥是一种技术,允许在不同的区块链网络之间转移代币。 21 | - 它通过锁定一条链上的资产并在另一条链上发行对应的代币来工作,如将比特币转移到以太坊网络会产生对应的 wrapped Bitcoin (wBTC)。 22 | 23 | ### 什么是 RPC 服务器,它在区块链中扮演什么角色? 24 | 25 | **回答要点:** 26 | 27 | - RPC (Remote Procedure Call) 服务器允许区块链网络的客户端通过 HTTP 协议与区块链交互。 28 | - 它是执行交易、查询区块链数据等操作的中介。 29 | 30 | ### 解释智能合约的概念及其在区块链中的应用。 31 | 32 | **回答要点:** 33 | 34 | - 智能合约是自动执行、控制或记录合约条款的计算机程序。 35 | - 在区块链上,它们通常用于创建去中心化应用,如自动化的代币交易或复杂的金融交易。 36 | 37 | ### 描述 ERC20 代币标准的主要特点及其重要性。 38 | 39 | **回答要点:** 40 | 41 | - ERC20 是一种在以太坊网络上创建可交换代币的标准。 42 | - 它定义了一套规则,包括代币的转账、获取账户余额的方法,确保了不同代币间的互操作性。 43 | 44 | ### 如何处理加密钱包中的安全风险? 45 | 46 | **回答要点:** 47 | 48 | - 加密钱包的安全可以通过以下措施加强:使用复杂且唯一的密码,定期备份密钥,使用两因素认证,保持软件更新,避免使用公共 Wi-Fi 进行交易。 49 | 50 | ### 解释基础代币和合约代币的区别。 51 | 52 | **回答要点:** 53 | 54 | - 基础代币是区块链网络的原生代币,例如以太坊的 ETH 或比特币的 BTC,用于支付交易费用等。 55 | - 合约代币是在区块链上通过智能合约创建的代币,例如 ERC20 代币,它们通常在区块链平台上实现更复杂的功能。 56 | 57 | ### 区块链应用开发中常见的性能瓶颈有哪些,如何优化? 58 | 59 | **回答要点:** 60 | 61 | - 区块链应用常见的性能瓶颈包括交易速度慢和成本高。 62 | - 优化方法可以包括使用更高效的共识算法、进行状态通道的开发、或是通过分层解决方案如二层网络来增强扩展性。 63 | 64 | ### 解释什么是钱包地址、公钥和私钥及它们之间的关系。 65 | 66 | **回答要点:** 67 | 68 | - 钱包地址是资金存取的公开标识,由公钥通过加密算法生成。 69 | - 公钥是私钥对应的非保密配对,私钥是一个秘密数字,是区块链身份和资产控制的关键。 70 | 71 | ### 如何确保区块链网络的数据完整性? 72 | 73 | **回答要点:** 74 | 75 | - 区块链的数据完整性通常通过链上的数据加密、时间戳、区块链的不可变性特性以及所有参与节点对数据的共识来保证。 76 | 77 | ### 在区块链项目中,如何处理升级和数据迁移? 78 | 79 | **回答要点:** 80 | 81 | - 区块链项目的升级和数据迁移可以通过软分叉或硬分叉来实现。 82 | - 软分叉允许向后兼容,而硬分叉则创建了一个新的链。 83 | - 也可以使用智能合约进行可升级的设计,使合约逻辑可以更新。 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 欢迎加入区块链开发入门体验课程! 2 | 3 | 在本课程中,你将踏上探索区块链技术世界的旅程。我们的课程将涵盖从基本概念到实际应用的多个方面。以下是我们课程的具体任务和学习内容: 4 | 5 | ## 任务 :one:: 区块链概念简介 6 | - :book: **内容概览**:介绍区块链技术的基本概念,包括其结构、工作原理及其在各行各业的应用。 7 | - :white_check_mark: **预期成果**:理解区块链的基本概念及其潜在价值。 8 | - :writing_hand: **作业地址**:[区块链概念简介作业题](https://github.com/RCCCamp/openWeb3/blob/main/08.区块链概念简介作业题.md) 在discord社区✅-区块链概念简介作业题 晒结果 9 | - :hourglass: **预计时间**:30分钟 10 | - :tv: **视频地址**:[区块链与智能合约初识](https://meeting.tencent.com/crm/NQW0g6wK79) 11 | 12 | ## 任务 :two:: 了解如何使用Solidity 13 | - :book: **内容概览**:学习Solidity语言的基础,这是编写以太坊智能合约的主要语言。 14 | - :white_check_mark:**预期成果**:掌握Solidity的基本语法和经典用例。 15 | - :writing_hand: **作业地址**:[Solidity经典用例](https://github.com/RCCCamp/openWeb3/blob/main/09.Solidity%20经典用例.md) 在discord社区✅-solidity-经典用例 晒结果 16 | - :hourglass:**预计时间**:5小时 17 | 18 | ## 任务 :three:: 使用Solidity编写Ethereum智能合约 19 | - :book:**内容概览**:动手实践,使用Solidity编写你的第一个智能合约。 20 | - :white_check_mark:**预期成果**:能够独立编写基本的智能合约。 21 | - :hourglass:**预计时间**:1小时 22 | 23 | ## 任务 :four:: 使用OpenZeppelin创建代币 24 | - :book: **内容概览**:利用OpenZeppelin安全库来创建属于自己的代币。 25 | - :white_check_mark:**预期成果**:了解并能够使用OpenZeppelin来创建和管理代币。 26 | - :hourglass: **预计时间**:1小时 27 | 28 | ## 任务 :five:: 以太坊基础知识 29 | - :book:**内容概览**:深入了解以太坊平台的基础知识,包括其架构和关键技术组件。 30 | - :white_check_mark:**预期成果**:对以太坊有一个全面的基础理解。 31 | - :writing_hand:**作业地址**:[以太坊基础知识作业题](https://github.com/RCCCamp/openWeb3/blob/main/10.以太坊基础知识作业题.md) 在discord社区✅-以太坊基础知识作业题 晒结果 32 | - :hourglass:**预计时间**:30分钟 33 | 34 | ## 任务 :six:: 加密钱包和网络的使用 35 | - :book:**内容概览**:学习如何安全地使用加密钱包和与区块链网络交互。 36 | - :white_check_mark:**预期成果**:掌握使用和管理加密钱包的技能,了解不同区块链网络的特点。 37 | - :writing_hand:**作业地址**:[加密钱包和网络的使用作业题](https://github.com/RCCCamp/openWeb3/blob/main/11.加密钱包和网络的使用作业题.md) 在discord社区✅-加密钱包和网络的使用作业题 晒结果 38 | - :hourglass:**预计时间**:30分钟 39 | 40 | ## 任务 :seven:: 连接并部署到Ethereum网络 41 | - :book:**内容概览**:实际操作,将智能合约部署到以太坊网络。 42 | - :white_check_mark:**预期成果**:能够独立将智能合约部署到以太坊网络。 43 | - :hourglass:**预计时间**:30分钟 44 | - :tv: **视频地址**:[部署erc20代币到Sepolia网络](https://meeting.tencent.com/crm/NQBOd7pl07) 45 | 46 | **课程收益** 47 | 通过参加这个课程,你将获得以下几点: 48 | - **深入理解**:不仅仅是基本概念,你将能够深入理解如何在实际项目中应用区块链技术。 49 | - **实践经验**:通过编写智能合约和部署代币,你将获得宝贵的实战经验。 50 | - **技能提升**:学习如何使用现代区块链开发工具和语言,如Solidity和OpenZeppelin。 51 | 52 | **课程结构** 53 | 本课程旨在逐步引导学员从初学者到能够自信地使用区块链技术的开发者。每个模块都设计有递进性,确保学员在固化旧知识的同时掌握新技能。 -------------------------------------------------------------------------------- /pic/1/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/1.png -------------------------------------------------------------------------------- /pic/1/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/10.png -------------------------------------------------------------------------------- /pic/1/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/11.png -------------------------------------------------------------------------------- /pic/1/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/12.png -------------------------------------------------------------------------------- /pic/1/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/13.png -------------------------------------------------------------------------------- /pic/1/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/14.png -------------------------------------------------------------------------------- /pic/1/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/15.png -------------------------------------------------------------------------------- /pic/1/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/16.png -------------------------------------------------------------------------------- /pic/1/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/17.png -------------------------------------------------------------------------------- /pic/1/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/2.png -------------------------------------------------------------------------------- /pic/1/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/3.png -------------------------------------------------------------------------------- /pic/1/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/4.png -------------------------------------------------------------------------------- /pic/1/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/5.png -------------------------------------------------------------------------------- /pic/1/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/6.png -------------------------------------------------------------------------------- /pic/1/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/7.png -------------------------------------------------------------------------------- /pic/1/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/8.png -------------------------------------------------------------------------------- /pic/1/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/1/9.png -------------------------------------------------------------------------------- /pic/3/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/1.jpg -------------------------------------------------------------------------------- /pic/3/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/2.png -------------------------------------------------------------------------------- /pic/3/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/3.png -------------------------------------------------------------------------------- /pic/3/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/4.png -------------------------------------------------------------------------------- /pic/3/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/5.png -------------------------------------------------------------------------------- /pic/3/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/6.png -------------------------------------------------------------------------------- /pic/3/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/7.png -------------------------------------------------------------------------------- /pic/3/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/3/8.png -------------------------------------------------------------------------------- /pic/4/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/4/1.png -------------------------------------------------------------------------------- /pic/4/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/4/2.png -------------------------------------------------------------------------------- /pic/4/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/4/3.png -------------------------------------------------------------------------------- /pic/5/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/5/1.PNG -------------------------------------------------------------------------------- /pic/6/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/1.jpg -------------------------------------------------------------------------------- /pic/6/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/10.jpg -------------------------------------------------------------------------------- /pic/6/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/11.jpg -------------------------------------------------------------------------------- /pic/6/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/12.jpg -------------------------------------------------------------------------------- /pic/6/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/13.jpg -------------------------------------------------------------------------------- /pic/6/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/14.jpg -------------------------------------------------------------------------------- /pic/6/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/15.jpg -------------------------------------------------------------------------------- /pic/6/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/16.jpg -------------------------------------------------------------------------------- /pic/6/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/17.jpg -------------------------------------------------------------------------------- /pic/6/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/18.jpg -------------------------------------------------------------------------------- /pic/6/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/19.jpg -------------------------------------------------------------------------------- /pic/6/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/2.jpg -------------------------------------------------------------------------------- /pic/6/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/20.jpg -------------------------------------------------------------------------------- /pic/6/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/21.jpg -------------------------------------------------------------------------------- /pic/6/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/22.jpg -------------------------------------------------------------------------------- /pic/6/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/23.jpg -------------------------------------------------------------------------------- /pic/6/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/3.jpg -------------------------------------------------------------------------------- /pic/6/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/4.jpg -------------------------------------------------------------------------------- /pic/6/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/5.jpg -------------------------------------------------------------------------------- /pic/6/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/6.jpg -------------------------------------------------------------------------------- /pic/6/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/7.jpg -------------------------------------------------------------------------------- /pic/6/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/8.jpg -------------------------------------------------------------------------------- /pic/6/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/6/9.jpg -------------------------------------------------------------------------------- /pic/7/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/1.png -------------------------------------------------------------------------------- /pic/7/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/2.png -------------------------------------------------------------------------------- /pic/7/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/3.jpg -------------------------------------------------------------------------------- /pic/7/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/4.png -------------------------------------------------------------------------------- /pic/7/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/5.png -------------------------------------------------------------------------------- /pic/7/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/6.png -------------------------------------------------------------------------------- /pic/7/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/7.png -------------------------------------------------------------------------------- /pic/7/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/pic/7/8.png -------------------------------------------------------------------------------- /static/CDzabZ4I5owzCkxYJXGcb6PSnby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/CDzabZ4I5owzCkxYJXGcb6PSnby.png -------------------------------------------------------------------------------- /static/DHMkbei1ZoZqakxFOskcrrQknMg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/DHMkbei1ZoZqakxFOskcrrQknMg.png -------------------------------------------------------------------------------- /static/G01Pbyh9voKfrPxdNnwcFRMfnGg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/G01Pbyh9voKfrPxdNnwcFRMfnGg.png -------------------------------------------------------------------------------- /static/KC60bGhqcoocedx5XkbcWKFKnic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/KC60bGhqcoocedx5XkbcWKFKnic.png -------------------------------------------------------------------------------- /static/OYw4b8uCtoT5qpxxiXTcuYOPnzn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/OYw4b8uCtoT5qpxxiXTcuYOPnzn.png -------------------------------------------------------------------------------- /static/Pn3Qbeuxkog7KixfIe4cciKKnVd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/Pn3Qbeuxkog7KixfIe4cciKKnVd.png -------------------------------------------------------------------------------- /static/QduabrtzaofYqlxd1SkcxWSmnje.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/QduabrtzaofYqlxd1SkcxWSmnje.png -------------------------------------------------------------------------------- /static/Sknib4l1roybGpx7FJpcwwkVnJe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/Sknib4l1roybGpx7FJpcwwkVnJe.jpg -------------------------------------------------------------------------------- /static/TGeUbEqQKoVRjnx2DCvcRAL1nCb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/TGeUbEqQKoVRjnx2DCvcRAL1nCb.png -------------------------------------------------------------------------------- /static/W43KbYjuKozv7hxXhMecCepFnJb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/W43KbYjuKozv7hxXhMecCepFnJb.png -------------------------------------------------------------------------------- /static/YD7Vb8kNeo2k8KxdOxTcHGvXnTe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/YD7Vb8kNeo2k8KxdOxTcHGvXnTe.png -------------------------------------------------------------------------------- /static/YbGvbI8TloGRANx7grKck2CpnSd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/YbGvbI8TloGRANx7grKck2CpnSd.png -------------------------------------------------------------------------------- /static/YpfpbfnajoiZcsxPG6bcp5rfnPd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/YpfpbfnajoiZcsxPG6bcp5rfnPd.png -------------------------------------------------------------------------------- /static/Yt4Sbw7I1oudozxTn3ocnmRMnxg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RemoteCodeCamp/openWeb3/150a1dd1709f046ed8c3f8a9314f5963c28412f4/static/Yt4Sbw7I1oudozxTn3ocnmRMnxg.png --------------------------------------------------------------------------------