├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── lesson1 ├── echo │ └── echo_server.go ├── homework │ ├── guessing_game │ │ └── guessing_game.go │ └── simple_dict │ │ ├── main.go │ │ └── src │ │ ├── query_caiyun.go │ │ ├── query_deepl.go │ │ ├── query_youdao.go │ │ └── type.go ├── proxy │ ├── v2 │ │ └── main.go │ ├── v3 │ │ └── main.go │ └── v4 │ │ └── main.go └── simple_dict │ └── word_dict.go └── lesson2 ├── go-project-example ├── .gitignore ├── attention │ ├── array.go │ ├── array_test.go │ ├── closure.go │ ├── closure_test.go │ ├── json.go │ ├── json_test.go │ ├── string.go │ └── string_test.go ├── avatar.jpg ├── concurrence │ ├── channel.go │ ├── channel_test.go │ ├── goroutine.go │ └── goroutine_test.go ├── example.sql ├── go.mod ├── go.sum ├── handler │ ├── publish_post.go │ └── query_page_info.go ├── repository │ ├── db_init.go │ ├── post.go │ ├── topic.go │ └── user.go ├── service │ ├── publish_post.go │ ├── publish_post_test.go │ ├── query_page_info.go │ └── query_page_info_test.go ├── sever.go └── util │ └── logger.go └── homework ├── benchmark ├── server_select.go └── server_select_test.go ├── controller ├── publish_post.go └── query_page_info.go ├── data ├── post └── topic ├── repository ├── data_init.go ├── data_save.go ├── post.go └── topic.go ├── server.go └── service ├── publish_post.go ├── query_page_info.go └── query_page_info_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | gh-md-toc.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 字节跳动青训营笔记 2 | * [Go语言快速上手](#go语言快速上手) 3 | * [语法速览](#语法速览) 4 | * [基础语法](#基础语法) 5 | * [一:类型](#一类型) 6 | * [二:内置库部分](#二内置库部分) 7 | * [json库的使用](#json库的使用) 8 | * [时间库的使用](#时间库的使用) 9 | * [字符串和数字互转](#字符串和数字互转) 10 | * [os相关信息](#os相关信息) 11 | * [实战项目](#实战项目) 12 | * [猜谜游戏(pass,过于简单)](#猜谜游戏pass过于简单) 13 | * [在线词典](#在线词典) 14 | * [第一步:抓包得得到数据进行分析](#第一步抓包得得到数据进行分析) 15 | * [第二步:利用工具生成代码](#第二步利用工具生成代码) 16 | * [curl请求直接转为go的请求代码](#curl请求直接转为go的请求代码) 17 | * [JSON转Golang Struct](#json转golang-struct) 18 | * [第三步:更改代码实现功能](#第三步更改代码实现功能) 19 | * [homework](#homework) 20 | * [SOCKS5代理服务器](#socks5代理服务器) 21 | * [SOCKS5简单介绍](#socks5简单介绍) 22 | * [SOCKS5代理原理](#socks5代理原理) 23 | * [具体实现](#具体实现) 24 | * [v1\-简单echo服务器](#v1-简单echo服务器) 25 | * [v2\-实现SOCKS5的握手阶段](#v2-实现socks5的握手阶段) 26 | * [v3\-实现SOCKS5的请求阶段](#v3-实现socks5的请求阶段) 27 | * [v4\-实现SOCKS5的转发阶段(最终完全版本](#v4-实现socks5的转发阶段最终完全版本) 28 | * [验证](#验证) 29 | * [Go语言工程实践](#go语言工程实践) 30 | * [并发和Goroutine](#并发和goroutine) 31 | * [并发和并行的区别](#并发和并行的区别) 32 | * [线程与协程的区别](#线程与协程的区别) 33 | * [Goroutine](#goroutine) 34 | * [用法](#用法) 35 | * [并发的通信](#并发的通信) 36 | * [Channel](#channel) 37 | * [并发安全](#并发安全) 38 | * [依赖管理](#依赖管理) 39 | * [GOPATH](#gopath) 40 | * [GOPATH弊端](#gopath弊端) 41 | * [Go Vendor](#go-vendor) 42 | * [Go Vendor弊端](#go-vendor弊端) 43 | * [Go Module (最终解决方案](#go-module-最终解决方案) 44 | * [依赖管理三要素](#依赖管理三要素) 45 | * [配置文件](#配置文件) 46 | * [版本规则](#版本规则) 47 | * [杂项](#杂项) 48 | * [中心仓库管理依赖库](#中心仓库管理依赖库) 49 | * [依赖的分发](#依赖的分发) 50 | * [较为神奇的地方](#较为神奇的地方) 51 | * [本地工具](#本地工具) 52 | * [测试](#测试) 53 | * [为什么要测试?](#为什么要测试) 54 | * [测试类型](#测试类型) 55 | * [单元测试](#单元测试) 56 | * [go单测的规则](#go单测的规则) 57 | * [go单测实例](#go单测实例) 58 | * [单元测试框架](#单元测试框架) 59 | * [衡量单元测试的标准](#衡量单元测试的标准) 60 | * [代码覆盖率](#代码覆盖率) 61 | * [打桩测试](#打桩测试) 62 | * [基准测试(Benchmark)](#基准测试benchmark) 63 | * [具体例子](#具体例子) 64 | * [代码分析](#代码分析) 65 | * [代码效率分析](#代码效率分析) 66 | * [项目实战](#项目实战) 67 | * [需求描述](#需求描述) 68 | * [项目分层结构](#项目分层结构) 69 | * [代码实现](#代码实现) 70 | * [Repository层实现](#repository层实现) 71 | * [数据映射](#数据映射) 72 | * [数据的增删改查](#数据的增删改查) 73 | * [Service层实现](#service层实现) 74 | * [参数校验](#参数校验) 75 | * [准备数据](#准备数据) 76 | * [组装实体](#组装实体) 77 | * [Controller层实现](#controller层实现) 78 | * [具体代码](#具体代码) 79 | * [homework部分](#homework部分) 80 | * [作业内容与思考](#作业内容与思考) 81 | * [具体实现](#具体实现-1) 82 | * [Repository层](#repository层) 83 | * [Service层实现](#service层实现-1) 84 | * [Controller层](#controller层) 85 | * [实测结果](#实测结果) 86 | * [服务端代码](#服务端代码) 87 | * [请求结果](#请求结果) 88 | * [GET请求测试(成功)](#get请求测试成功) 89 | * [POST请求测试(成功)](#post请求测试成功) 90 | 91 | # Go语言快速上手 92 | ## 语法速览 93 | 94 | ### 基础语法 95 | 96 | 基础语法有几点需要注意: 97 | 98 | #### 一:类型 99 | 100 | > 有值类型,有指针,指针只能作为引用的替代品,无法指针直接运算。 101 | 102 | go语言有值类型,可以直接像下面这样定义变量: 103 | 104 | ```go 105 | type Student struct { 106 | name string 107 | sid string 108 | } 109 | func main(){ 110 | var student = Student{name: "John", sid: "1001"} //student为值类型 111 | var student = &Student{name: "John", sid: "1001"} //student为指针类型(注意由于go有垃圾回收机制,所以这里会自动为我们开辟堆内存 112 | student := new(Student) //也可通过内置的new()函数直接开辟堆内存,而不立马初始化,得到一个指针 113 | } 114 | 115 | ``` 116 | 117 | > go语言的切片 118 | 119 | 同样切片类型也有上述两种获得内存的定义方式,也可通过内置的make函数对内部的cap和len进行初始的控制。 120 | 121 | ```cpp 122 | nums := make([]int,2,10)//得到一个底层数组长度为2,cap为10的初始切片 123 | nums1 := nums2[0:3] //第二种切片方式 124 | ``` 125 | 126 | #### 二:内置库部分 127 | 128 | ##### json库的使用 129 | 130 | 通过在字段后面跟着的字符串进行序列化的定义,后面跟着的称为域标签。 131 | 132 | ```cpp 133 | package main 134 | 135 | import ( 136 | "encoding/json" 137 | "fmt" 138 | ) 139 | 140 | type Student struct { 141 | Name string `json:"name"` 142 | Sid string `json:"sid"` 143 | } 144 | 145 | func main() { 146 | s := Student{Name: "jonh" ,Sid: "10323"} 147 | //序列化 148 | p ,err := json.Marshal(s) 149 | if err!=nil { 150 | panic(err) 151 | } 152 | fmt.Println(string(p)) 153 | 154 | //反序列化 155 | err = json.Unmarshal(p,&s) 156 | if err!=nil { 157 | panic(err) 158 | } 159 | fmt.Println(s) 160 | } 161 | 162 | ``` 163 | 164 | 官方对域标签有以下说明: 165 | 166 | ```cpp 167 | // Field appears in JSON as key "myName". 168 | Field int `json:"myName"` 169 | 170 | // Field appears in JSON as key "myName" and 171 | // the field is omitted from the object if its value is empty, 172 | // as defined above. 173 | Field int `json:"myName,omitempty"` 174 | 175 | // Field appears in JSON as key "Field" (the default), but 176 | // the field is skipped if empty. 177 | // Note the leading comma. 178 | Field int `json:",omitempty"` 179 | 180 | // Field is ignored by this package. 181 | Field int `json:"-"` 182 | 183 | // Field appears in JSON as key "-". 184 | Field int `json:"-,"` 185 | ``` 186 | 187 | ##### 时间库的使用 188 | 189 | **时间的获取** 190 | 191 | > 获取当前时间: 192 | 193 | ```go 194 | package main 195 | 196 | import ( 197 | "fmt" 198 | "time" 199 | ) 200 | 201 | func main() { 202 | now := time.Now() //获取当前时间 203 | fmt.Printf("current time:%v\n", now) 204 | year := now.Year() //年 205 | month := now.Month() //月 206 | day := now.Day() //日 207 | hour := now.Hour() //小时 208 | minute := now.Minute() //分钟 209 | second := now.Second() //秒 210 | fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) 211 | } 212 | ``` 213 | 214 | > 获取时间戳 215 | 216 | ```go 217 | package main 218 | 219 | import ( 220 | "fmt" 221 | "time" 222 | ) 223 | 224 | func main() { 225 | now := time.Now() //获取当前时间 226 | timestamp1 := now.Unix() //时间戳 227 | timestamp2 := now.UnixNano() //纳秒时间戳 228 | fmt.Printf("现在的时间戳:%v\n", timestamp1) 229 | fmt.Printf("现在的纳秒时间戳:%v\n", timestamp2) 230 | } 231 | ``` 232 | 233 | > 时间戳与时间的转换 234 | 235 | ```go 236 | package main 237 | 238 | import ( 239 | "fmt" 240 | "time" 241 | ) 242 | 243 | func main() { 244 | now := time.Now() //获取当前时间 245 | timestamp := now.Unix() //时间戳 246 | timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式 247 | fmt.Println(timeObj) 248 | year := timeObj.Year() //年 249 | month := timeObj.Month() //月 250 | day := timeObj.Day() //日 251 | hour := timeObj.Hour() //小时 252 | minute := timeObj.Minute() //分钟 253 | second := timeObj.Second() //秒 254 | fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second) 255 | } 256 | ``` 257 | 258 | > 获取星期几 259 | 260 | ```go 261 | package main 262 | 263 | import ( 264 | "fmt" 265 | "time" 266 | ) 267 | 268 | func main() { 269 | t := time.Now() 270 | fmt.Println(t.Weekday().String()) 271 | } 272 | ``` 273 | 274 | **时间的操作** 275 | 276 | (1)Add(during)函数实现某个时间 + 时间间隔 277 | 278 | ```go 279 | package main 280 | import ( 281 | "fmt" 282 | "time" 283 | ) 284 | func main() { 285 | now := time.Now() 286 | later := now.Add(time.Hour) // 当前时间加1小时后的时间 287 | fmt.Println(later) 288 | } 289 | ``` 290 | 291 | (2)Sub(Time)获取时间差值 292 | 293 | 返回一个时间段 t - u 的值。如果结果超出了 Duration 可以表示的最大值或最小值,将返回最大值或最小值,要获取时间点 t - d(d 为 Duration),可以使用 t.Add(-d)。 294 | 295 | (3)Equal(Time)判断时间是否相同 296 | 297 | (4)Before 和 After某个时间是否在他之前或之后 298 | 299 | **定时任务** 300 | 301 | 使用 time.Tick(时间间隔) 可以设置定时器,定时器的本质上是一个通道(channel) 302 | 303 | ```go 304 | package main 305 | import ( 306 | "fmt" 307 | "time" 308 | ) 309 | func main() { 310 | ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器 311 | for i := range ticker { 312 | fmt.Println(i) //每秒都会执行的任务 313 | } 314 | } 315 | ``` 316 | 317 | **解析字符串格式的时间** 318 | 319 | Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间。 320 | 321 | ```go 322 | func Parse(layout, value string) (Time, error) 323 | ``` 324 | 325 | 与 Parse 函数类似的还有 ParseInLocation 函数。 326 | 327 | ```go 328 | func ParseInLocation(layout, value string, loc *Location) (Time, error) 329 | ``` 330 | 331 | ParseInLocation 与 Parse 函数类似,但有两个重要的不同之处: 332 | 333 | - 第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc; 334 | - 第二,当时间字符串提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc。 335 | 336 | 337 | 示例代码如下: 338 | 339 | ```go 340 | package main 341 | import ( 342 | "fmt" 343 | "time" 344 | ) 345 | func main() { 346 | var layout string = "2006-01-02 15:04:05" 347 | var timeStr string = "2019-12-12 15:22:12" 348 | timeObj1, _ := time.Parse(layout, timeStr) 349 | fmt.Println(timeObj1) 350 | timeObj2, _ := time.ParseInLocation(layout, timeStr, time.Local) 351 | fmt.Println(timeObj2) 352 | } 353 | ``` 354 | 355 | ##### 字符串和数字互转 356 | 357 | > 字符串与数字互转的想关库函数全在一个包内:strconv包 358 | 359 | 一图胜千言: 360 | 361 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd3af99c9e8d457c86005f95687aa058~tplv-k3u1fbpfcp-watermark.image?) 362 | 363 | ##### os相关信息 364 | 365 | > os包里面封装了很多和操作系统相关的内容。 366 | 367 | 如下: 368 | 369 | ```go 370 | package main 371 | 372 | import ( 373 | "fmt" 374 | "os" 375 | "os/exec" 376 | ) 377 | func main() { 378 | fmt.Println(os.Args) //打印命令行参数 379 | fmt.Println(os.Getenv("PATH")) //打印环境变量 380 | fmt.Println(os.Setenv("AA","BB")) //设置环境变量,key,val形式设置 381 | buf,err := exec.Command("grep").CombinedOutput() //执行cmd命令 382 | if err != nil { 383 | panic(err) 384 | } 385 | fmt.Println(string(buf)) 386 | } 387 | ``` 388 | 389 | 390 | 391 | 其他语法pass跳过 392 | 393 | ## 实战项目 394 | 395 | ### 猜谜游戏(pass,过于简单) 396 | 397 | ### 在线词典 398 | 399 | > 想要实现在线词典,首先就得用到别人的翻译引擎 400 | 401 | #### 第一步:抓包得得到数据进行分析 402 | 403 | 以彩云词典为例: 404 | 405 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b3d1d0fca51c48e1b5d60988d30c1a93~tplv-k3u1fbpfcp-watermark.image?) 406 | 407 | 从网页调试工具里面查看随时收发的网络数据包,挨个查看它们的response,如果里面的json数据出现翻译结果,那么说明这个包就是返回的翻译结果! 408 | 409 | 那么我们只需要让go语言来做同样的两件事: 410 | 411 | 1. 发起请求。 412 | 2. 解析返回的json内容。 413 | 414 | 只要做好了这两件事,那么就很快得到了一个单词的翻译了。 415 | 416 | #### 第二步:利用工具生成代码 417 | 418 | 在此之前我们需要清楚有两个神器般存在的网站: 419 | 420 | 1. [curlconverter](https://curlconverter.com/#go) 把curl请求直接转为go的请求代码。 421 | 2. [oktools](https://oktools.net/json2go) JSON转Golang Struct 422 | 423 | 那么我们先肯定是要得到请求的代码,然后稍作更改,解析body后得出想要的结果。 424 | 425 | ###### curl请求直接转为go的请求代码 426 | 427 | > 如下图进到刚才我们捕捉到的目标包,然后复制cURL,到网站进行解析得到最终代码。 428 | 429 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b89931a7485e44bdb7f7a70418698933~tplv-k3u1fbpfcp-watermark.image?) 430 | 431 | ```go 432 | package main 433 | 434 | import ( 435 | "fmt" 436 | "io/ioutil" 437 | "log" 438 | "net/http" 439 | "strings" 440 | ) 441 | 442 | func main() { 443 | client := &http.Client{} 444 | var data = strings.NewReader(`{"trans_type":"en2zh","source":"hello"}`) 445 | req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) 446 | if err != nil { 447 | log.Fatal(err) 448 | } 449 | req.Header.Set("Connection", "keep-alive") 450 | req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`) 451 | req.Header.Set("sec-ch-ua-mobile", "?0") 452 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") 453 | req.Header.Set("app-name", "xy") 454 | req.Header.Set("Content-Type", "application/json;charset=UTF-8") 455 | req.Header.Set("Accept", "application/json, text/plain, */*") 456 | req.Header.Set("os-type", "web") 457 | req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") 458 | req.Header.Set("sec-ch-ua-platform", `"Windows"`) 459 | req.Header.Set("Origin", "https://fanyi.caiyunapp.com") 460 | req.Header.Set("Sec-Fetch-Site", "cross-site") 461 | req.Header.Set("Sec-Fetch-Mode", "cors") 462 | req.Header.Set("Sec-Fetch-Dest", "empty") 463 | req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") 464 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") 465 | resp, err := client.Do(req) 466 | if err != nil { 467 | log.Fatal(err) 468 | } 469 | defer resp.Body.Close() 470 | bodyText, err := ioutil.ReadAll(resp.Body) 471 | if err != nil { 472 | log.Fatal(err) 473 | } 474 | fmt.Printf("%s\n", bodyText) 475 | } 476 | ``` 477 | 478 | 观察代码的改变我们只需对source部分的内容进行更改,即可得到对应的翻译结果。 479 | 480 | ###### JSON转Golang Struct 481 | 482 | 得到翻译结果,body内容后,我们需要把body内容解析为本地的sturct才能正常使用(当然你头铁的话可以直接找对应的字符串即可,也不需要反序列化。 483 | 484 | ```cpp 485 | type AutoGenerated struct { 486 | Rc int `json:"rc"` 487 | Wiki struct { 488 | KnownInLaguages int `json:"known_in_laguages"` 489 | Description struct { 490 | Source string `json:"source"` 491 | Target interface{} `json:"target"` 492 | } `json:"description"` 493 | ID string `json:"id"` 494 | Item struct { 495 | Source string `json:"source"` 496 | Target string `json:"target"` 497 | } `json:"item"` 498 | ImageURL string `json:"image_url"` 499 | IsSubject string `json:"is_subject"` 500 | Sitelink string `json:"sitelink"` 501 | } `json:"wiki"` 502 | Dictionary struct { 503 | Prons struct { 504 | EnUs string `json:"en-us"` 505 | En string `json:"en"` 506 | } `json:"prons"` 507 | Explanations []string `json:"explanations"` 508 | Synonym []string `json:"synonym"` 509 | Antonym []interface{} `json:"antonym"` 510 | WqxExample [][]string `json:"wqx_example"` 511 | Entry string `json:"entry"` 512 | Type string `json:"type"` 513 | Related []interface{} `json:"related"` 514 | Source string `json:"source"` 515 | } `json:"dictionary"` 516 | } 517 | ``` 518 | 519 | #### 第三步:更改代码实现功能 520 | 521 | 通过前面生成的代码已经能够实现请求和接收响应,且可以直接把响应内容反序列化为结构体,那么接下来,只需要把想要的部分遍历打印即可。 522 | 523 | 最终代码如下: 524 | 525 | ```go 526 | package src 527 | 528 | import ( 529 | "bytes" 530 | "encoding/json" 531 | "fmt" 532 | "io/ioutil" 533 | "log" 534 | "net/http" 535 | ) 536 | 537 | func QueryCaiyun(word string) { 538 | client := &http.Client{} 539 | request := DictRequestCaiyun{TransType: "en2zh", Source: word} 540 | buf, err := json.Marshal(request) 541 | if err != nil { 542 | log.Fatal(err) 543 | } 544 | var data = bytes.NewReader(buf) 545 | req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) 546 | if err != nil { 547 | log.Fatal(err) 548 | } 549 | req.Header.Set("Connection", "keep-alive") 550 | req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`) 551 | req.Header.Set("sec-ch-ua-mobile", "?0") 552 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") 553 | req.Header.Set("app-name", "xy") 554 | req.Header.Set("Content-Type", "application/json;charset=UTF-8") 555 | req.Header.Set("Accept", "application/json, text/plain, */*") 556 | req.Header.Set("os-type", "web") 557 | req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") 558 | req.Header.Set("sec-ch-ua-platform", `"Windows"`) 559 | req.Header.Set("Origin", "https://fanyi.caiyunapp.com") 560 | req.Header.Set("Sec-Fetch-Site", "cross-site") 561 | req.Header.Set("Sec-Fetch-Mode", "cors") 562 | req.Header.Set("Sec-Fetch-Dest", "empty") 563 | req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") 564 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") 565 | resp, err := client.Do(req) 566 | if err != nil { 567 | log.Fatal(err) 568 | } 569 | defer resp.Body.Close() 570 | bodyText, err := ioutil.ReadAll(resp.Body) 571 | if err != nil { 572 | log.Fatal(err) 573 | } 574 | if resp.StatusCode != 200 { //防止返回错误 575 | log.Fatal("bad Status code:", resp.StatusCode, "body", string(bodyText)) 576 | } 577 | var dictResponse DictResponseCaiyun 578 | err = json.Unmarshal(bodyText, &dictResponse) 579 | if err != nil { 580 | log.Fatal(err) 581 | } 582 | 583 | fmt.Println("translate from Caiyun\n", "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs) 584 | for _, item := range dictResponse.Dictionary.Explanations { 585 | fmt.Println(item) 586 | } 587 | } 588 | ``` 589 | 590 | #### homework 591 | 592 | 后面我依次通过这个方式还弄了其他的翻译引擎,但是有很多翻译引擎的请求头都是动态实时的,或者加了密。我做的第二个有道的翻译引擎使用就是动态实时的,然后去查阅了破解方法,发现是通过阅读原本的js源码进行反推,得出请求头里面动态变化的内容。 593 | 594 | 最终写了Deepl和有道两个搜索引擎。 595 | 596 | 源码实现链接:[https://github.com/ACking-you/TraningCamp/tree/master/lesson1/homework/simple_dict/src](https://github.com/ACking-you/TraningCamp/tree/master/lesson1/homework/simple_dict/src) 597 | 598 | ### SOCKS5代理服务器 599 | 600 | #### SOCKS5简单介绍 601 | 602 | SOCKS5是代理协议,在使用TCP/IP协议通信的前端机器和服务器之间发挥中介作用,使内部网络的前端机器能够访问互联网的服务器,使通信更加安全。SOCKS5服务器将前端发送的请求转发给真正的目标服务器,模拟前端行为。此处,前端与SOCKS5之间也是通过TCP/IP协议进行通信的,前端向SOCKS5服务器发送请求发送给SOCKS5服务器,然后SOCKS5服务器将请求发送给真正的服务器。SOCKS5服务器在将通讯请求发送给真正服务器的过程中,对于请求数据包本身不加任何改变(明文传输)。SOCKS5服务器在收到真实服务器响应后,也原样转发到前端。 603 | 604 | 它的用途是, 比如某些企业的内网为了确保安全性,有很严格的防火墙策略,但是带来的副作用就是访问某些资源会很麻烦。 socks5 相当于在防火墙开了个口子,让授权的用户可以通过单个端口去访问内部的所有资源。实际上很多翻墙软件,最终暴露的也是一个 socks5 协议的端口。 605 | 606 | #### SOCKS5代理原理 607 | 608 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e8524dafa35a4a9399fc17c034846140~tplv-k3u1fbpfcp-watermark.image?) 609 | 610 | 正常浏览器访问一个网站,如果不经过代理服务器的话,就是先和对方的网站建立 TCP 连接,然后三次握手,握手完之后发起 HTTP 请求,然后服务返回 HTTP 响应。如果设置代理服务器之后,流程会变得复杂一些。 首先是浏览器和 socks5 代理建立 TCP 连接,代理再和真正的服务器建立 TCP 连接。这里可以分成四个阶段,**握手阶段、认证阶段、请求阶段、 relay 阶段**。 611 | 612 | * 握手阶段:浏览器会向 socks5 代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks5 服务器会选中一个认证方式,返回给浏览器。如果**返回的是 00 的话就代表不需要认证**,返回其他类型的话会开始认证流程,这里我们就不对认证流程进行概述了。(本次课程跳过认证阶段) 613 | * 请求阶段:认证通过之后浏览器会对 socks5 服务器发起请求。主要信息包括 版本号,请求的类型,一般主要是 connection 请求,就代表代**理服务器要和某个域名或者某个 IP 地址某个端口建立 TCP 连接**。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应。 614 | * relay 阶段:此时浏览器会发送 正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。然后实际上 **代理服务器并不关心流量的细节**,可以是 HTTP流量,也可以是其它 TCP 流量。 这个就是 socks5 协议的工作原理。 615 | 616 | ```mermaid 617 | graph LR 618 | a[握手阶段] 619 | b[认证阶段] 620 | c[请求阶段] 621 | d[转发relay阶段] 622 | a-->b-->c-->d 623 | 624 | ``` 625 | 626 | 627 | 628 | #### 具体实现 629 | 630 | ##### v1-简单echo服务器 631 | 632 | ```go 633 | package main 634 | 635 | import ( 636 | "bufio" 637 | "fmt" 638 | "log" 639 | "net" 640 | ) 641 | 642 | func main() { 643 | server, err := net.Listen("tcp", "0.0.0.0:1080") 644 | if err != nil { 645 | panic(err) 646 | } 647 | for { 648 | client, err := server.Accept() 649 | if err != nil { 650 | log.Printf("Accept failed %v", err) 651 | continue 652 | } 653 | fmt.Printf("连接成功! clent:%v \n", client.RemoteAddr()) 654 | go process(client) 655 | } 656 | } 657 | 658 | func process(conn net.Conn) { 659 | defer func() { 660 | conn.Close() 661 | fmt.Printf("连接断开! clent:%v \n", conn.RemoteAddr()) 662 | }() 663 | 664 | //用缓冲流进行一次包装,减少底层IO次数,让读取效率更高效 665 | reader := bufio.NewReader(conn) 666 | for { 667 | b, err := reader.ReadByte() 668 | if err != nil { 669 | break 670 | } 671 | _, err = conn.Write([]byte{b}) 672 | if err != nil { 673 | break 674 | } 675 | } 676 | } 677 | ``` 678 | 679 | **客户端验证:** 680 | 681 | 没必要再写一个客户端,这时完全可以netcat工具进行tcp连接测试。 682 | 683 | 如下: 684 | 685 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7aaf956b1ab8433fa07d9c8b37ae3f7c~tplv-k3u1fbpfcp-watermark.image?) 686 | 687 | ##### v2-实现SOCKS5的握手阶段 688 | 689 | 实现SOCKS5之前我们需要清楚SOCKS5的握手阶段的请求和返回是怎么样的,如下面的图表所示: 690 | 691 | | VER | NMETHODS | METHODS | 692 | | -------------------------- | ------------------------------------------ | --------------------------------------- | 693 | | 1byte | 1byte | 1 to 255 byte | 694 | | 协议版本信息*socks5为0x05* | 支持认证的方法数量*值为0x00则表示无需认证* | NMETHODS的值为多少METHODS就有多少个字节 | 695 | 696 | ```go 697 | package main 698 | 699 | //auth 阶段 700 | import ( 701 | "bufio" 702 | "fmt" 703 | "io" 704 | "log" 705 | "net" 706 | ) 707 | const( 708 | socks5Ver = 0x05 709 | cmdBind = 0x01 710 | atypIPV4 = 0x01 711 | atypeHOST = 0x03 712 | atypeIPV6 = 0x04 713 | ) 714 | func main() { 715 | server, err := net.Listen("tcp", "0.0.0.0:1080") 716 | if err != nil { 717 | panic(err) 718 | } 719 | for { 720 | client, err := server.Accept() 721 | if err != nil { 722 | log.Printf("Accept failed %v", err) 723 | continue 724 | } 725 | fmt.Printf("连接成功! clent:%v \n", client.RemoteAddr()) 726 | go process(client) 727 | } 728 | } 729 | 730 | func process(conn net.Conn) { 731 | defer func() { 732 | conn.Close() 733 | fmt.Printf("连接断开! clent:%v \n", conn.RemoteAddr()) 734 | }() 735 | 736 | reader := bufio.NewReader(conn) 737 | err := auth(reader,conn) 738 | if err!=nil{ 739 | log.Printf("client %v auth failed:%v",conn.RemoteAddr(),err) 740 | } 741 | log.Println("auth success") 742 | } 743 | 744 | func auth(reader *bufio.Reader, conn net.Conn) (err error) { 745 | //协议版本 746 | ver,err := reader.ReadByte() 747 | if err != nil{ 748 | return fmt.Errorf("read ver failed:%w",err) 749 | } 750 | if ver != socks5Ver{ 751 | return fmt.Errorf("not supported ver:%v",ver) 752 | } 753 | 754 | //支持的方法数量 755 | methodSize,err := reader.ReadByte() 756 | if err!=nil{ 757 | return fmt.Errorf("read methodSize failed:%w",err) 758 | } 759 | //方法值 760 | method := make([]byte,methodSize) 761 | _,err = io.ReadFull(reader,method) 762 | if err!=nil{ 763 | return fmt.Errorf("read method failed %w",err) 764 | } 765 | log.Println("ver",ver,"method",method) 766 | 767 | //返回的内容表示SOCKS5通信,且无需认证 768 | _,err = conn.Write([]byte{socks5Ver,0x00}) 769 | if err !=nil{ 770 | return fmt.Errorf("write failed:%w",err) 771 | } 772 | return nil 773 | } 774 | 775 | ``` 776 | 777 | ##### v3-实现SOCKS5的请求阶段 778 | 779 | 同样来看看此时的消息协议: 780 | 781 | 客户端请求: 782 | 783 | | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 784 | | -------------------- | --------------------------------- | ------------------ | ------------------------------ | ---------------------------------- | ------------------------ | 785 | | 1byte | 1byte | 1byte | 1byte | Variable | 2byte | 786 | | 协议版本0x05为SOCKS5 | 代表请求类型*0x01表示CONNECT请求* | 保留字段(不理会) | 目标地址类型(IPV4/IPV6/域名) | 地址值,根据不同地址类型,长度不同 | 需要访问的服务器的端口号 | 787 | 788 | 服务端响应: 789 | 790 | | VER | REP | RSV | ATYP | BIND.ADDR | BIND.PORT | 791 | | -------------------- | --------------------- | ------------------ | -------------------------- | ---------------------- | ---------------------- | 792 | | 1byte | 1byte | 1byte | 1byte | Variable | 2byte | 793 | | 协议版本0x05为SOCKS5 | 代表响应。成功就返回0 | 保留字段(不理会) | 地址类型(IPV4/IPV6/域名) | 地址值(这里暂时不需要 | 端口号(这里暂时不需要 | 794 | 795 | 796 | > 这一过程的代码: 797 | 798 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c31306676e3c408383a7c1a81fb2a30a~tplv-k3u1fbpfcp-watermark.image?) 799 | 800 | > 对当前的实现进行测试: 801 | 802 | 进行如下curl命令: 803 | 804 | ```shell 805 | curl --socks5 localhost:1080 -v http://www.qq.com 806 | ``` 807 | 808 | 此时请求会失败,但我们已经能看到正常打印出来的ip和端口号 809 | 810 | ##### v4-实现SOCKS5的转发阶段(最终完全版本 811 | 812 | 最后的转发过程,由于不需要对流量进行任何的处理,所以没有上层协议,直接再Write操作完后把流量进行转发即可。 813 | 814 | 对于两个连接流量的转发,标准库里有有一些好用的函数库。 815 | 816 | 1. 通过net.Dial建立tcp连接。 817 | 818 | ```go 819 | dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port)) 820 | if err != nil { 821 | return fmt.Errorf("dial dst failed:%w", err) 822 | } 823 | defer dest.Close() 824 | ``` 825 | 826 | 2. 标准库的 io.copy 可以实现一个单向数据转发,双向转发的话,需要启动两个 goroutinue。 827 | 828 | ```go 829 | go func() { 830 | _, _ = io.Copy(dest, reader) 831 | cancel() 832 | }() 833 | go func() { 834 | _, _ = io.Copy(conn, dest) 835 | cancel() 836 | }() 837 | ``` 838 | 839 | 840 | 841 | 现在有一个问题,connect 函数会立刻返回,返回的时候连接就被关闭了。需要等待任意一个方向copy出错的时候,再返回 connect 函数。 842 | 843 | > 这个context目前还弄不明白 844 | 845 | 这里可以使用到标准库里面的一个 context 机制,用 context 连 with cancel 来创建一个context。 在最后等待 ctx.Done() , 只要 cancel 被调用, ctx.Done就会立刻返回。 然后在上面的两个 goroutinue 里面 调用一次 cancel 即可。 846 | 847 | ##### 验证 848 | 849 | > 同样是以下请求命令此时终于返回正常内容了! 850 | 851 | ```shell 852 | curl --socks5 localhost:1080 -v http://www.qq.com 853 | ``` 854 | 855 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f22da14bdfbf45b9893a8bb6a89d9066~tplv-k3u1fbpfcp-watermark.image?) 856 | 857 | 现在SOCKS5代理服务器程序已经写好,可以使用[SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=zh-CN)插件对该代理服务器进行正式的使用(可以用连接了学校内网的电脑,作为SOCKS5代理服务器对学校内网的内容进行访问( ̄▽ ̄)" 858 | 859 | # Go语言工程实践 860 | 861 | ## 并发和Goroutine 862 | 863 | ### 并发和并行的区别 864 | 865 | 并发可能更多的是精确到语言的逻辑,也就是直接的多线程,或者多进程。 866 | 867 | 而并行则是一种表述程序运行的方式,就如同异步和同步的描述。 868 | 869 | 并发程序不一定是并行的,这个看操作系统的调度。 870 | 871 | ### 线程与协程的区别 872 | 873 | 线程:是比进程更小粒度的运行单位,存在于内核态,需要操作系统来调度,内存消耗是MB级别。 874 | 875 | 协程:是比线程更小的粒度,通过m:n的比例在一个线程中再细分出来的单位,存在于用户态,用户可以自由调度,内存消耗是KB级别。 876 | 877 | 协程对比线程的优势: 878 | 879 | 1. 存在于用户态,可操作性强,调度可由自己控制。 880 | 2. 更轻量,所需资源更少。 881 | 882 | ### Goroutine 883 | 884 | go语言的go关键字跑的就是协程,我们称为goroutine。 885 | > 关于协程背后更多的故事,可以看这个视频 [go协程实现原理](https://www.bilibili.com/video/BV1hv411x7we?p=16) ,我们这里只讲简单使用。 886 | 887 | #### 用法 888 | 889 | 简单用法如下: 890 | 891 | ```go 892 | package main 893 | 894 | import ( 895 | "fmt" 896 | "sync" 897 | ) 898 | 899 | func hello(i int) { 900 | println("hello world : " + fmt.Sprint(i)) 901 | } 902 | 903 | func main() { 904 | //go的风格来说一般都喜欢运行一个闭包 905 | go func(j int) { 906 | hello(j) 907 | }(i) 908 | } 909 | ``` 910 | 911 | #### 并发的通信 912 | 913 | > 并发程序之间的通信,一般都是通过共享内存的形式实现通信,临界区一般需要加锁保护。 914 | 915 | ![communication](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7863d82123bf4f2a8d74794e3be46fc8~tplv-k3u1fbpfcp-watermark.image?) 916 | 917 | 而go语言采取的是通过通信来实现共享内存,这个过程是反过来的,但用起来更为直观。 918 | 919 | ##### Channel 920 | 921 | ![channel](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ca30515f10ec4a318f9f4b7fc88270a3~tplv-k3u1fbpfcp-watermark.image?) 922 | 923 | 通过内置函数 make 可以得到两种类型的 channel 。 924 | 925 | **注意**:channel是类似于引用的一个类型,如果直接通过var声明定义是没法初始化得到内部内存的,故记得通过make创建channel。还有就是记得不用的时候关闭。 926 | 927 | **channel的使用** 928 | 929 | > channel的简单使用如下: 930 | 931 | ```go 932 | func main() { 933 | var src chan int 934 | src = make(chan int)//不带缓冲 935 | dest := make(chan int, 3)//带缓冲 936 | go func() { 937 | defer close(src) 938 | for i := 0; i < 10; i++ { 939 | src <- i//生产 940 | } 941 | }() 942 | go func() { 943 | defer close(dest) 944 | for i := range src {//消费者1 945 | dest <- i * i 946 | } 947 | }() 948 | for i := range dest {//消费者2 949 | println(i) 950 | } 951 | } 952 | ``` 953 | 954 | **使用带缓冲channel的好处** 955 | 956 | 在一个生产者消费者模型中,生产者的生产效率远高于消费者,那么可以使用带缓冲的channel,防止生产者因为等待消费者消费过程而产生阻塞。反之对消费者来说也是受用的。 957 | 958 | ##### 并发安全 959 | 960 | **互斥锁** 961 | 962 | go语言并没有对加锁机制的弃用,标准库里面仍然有sync.Mutex。 963 | 964 | 以下为简单加锁实现并发安全: 965 | 966 | ```go 967 | package main 968 | 969 | import ( 970 | "fmt" 971 | "sync" 972 | "time" 973 | ) 974 | var( 975 | x int 976 | mut sync.Mutex 977 | ) 978 | func AddWithLock() { 979 | mut.Lock() 980 | for i:=0;i<2000;i++ { 981 | x++ 982 | } 983 | mut.Unlock() 984 | } 985 | 986 | func AddWithoutLock() { 987 | for i:=0;i<2000;i++ { 988 | x++ 989 | } 990 | } 991 | 992 | func main() { 993 | //开五个协程的锁版本,再打印最终结果 994 | for i := 0; i < 5; i++ { 995 | go AddWithoutLock() 996 | } 997 | //等待上面的协程执行结束 998 | time.Sleep(time.Second) 999 | fmt.Println(x) 1000 | 1001 | //有锁版本 1002 | x = 0 1003 | for i:=0;i<5;i++{ 1004 | go AddWithLock() 1005 | } 1006 | time.Sleep(time.Second) 1007 | fmt.Println(x) 1008 | } 1009 | 1010 | ``` 1011 | 1012 | 1013 | 1014 | **计数器** 1015 | 1016 | WaitGroup,通过Add(a)计时器+a,通过Done()计数器-1,通过Wait()阻塞直到计数器为0。这个东西我觉得有些类似于操作系统的信号量。 1017 | 1018 | 以下为实例: 1019 | 1020 | ```go 1021 | package main 1022 | 1023 | import ( 1024 | "fmt" 1025 | "sync" 1026 | ) 1027 | 1028 | func hello(){ 1029 | fmt.Println("hello") 1030 | } 1031 | func main() { 1032 | var wg sync.WaitGroup 1033 | wg.Add(5) 1034 | for i := 0; i < 5; i++ { 1035 | go func() { 1036 | defer wg.Done() 1037 | hello() 1038 | }() 1039 | } 1040 | wg.Wait() 1041 | } 1042 | 1043 | ``` 1044 | 1045 | 1046 | 1047 | ------ 1048 | 1049 | ## 依赖管理 1050 | 1051 | Go依赖管理的演进: 1052 | 1053 | ```mermaid 1054 | graph LR 1055 | a[GOPATH] 1056 | b[Go Vendor] 1057 | c[Go Module] 1058 | a-->b-->c 1059 | ``` 1060 | 1061 | ### GOPATH 1062 | 1063 | go语言有一个内置的全局环境变量GOPATH,指定了GOPATH文件夹后,他会在这个文件夹内创建以下三个文件夹: 1064 | 1065 | |——bin:项目编译的二进制文件 1066 | 1067 | |——pkg:项目编译的中间产物,加速编译 1068 | 1069 | |——src:项目源码 1070 | 1071 | 项目直接依赖src下的代码,go get命令下载的软件包都会在src目录下。 1072 | 1073 | #### GOPATH弊端 1074 | 1075 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f66164dd6cf649708ec9357f7e858f80~tplv-k3u1fbpfcp-watermark.image?) 1076 | 1077 | 当我们对某个依赖进行升级后,则项目A依赖的版本可能无法实现兼容,这就是GOPATH无法解决的**多版本控制问题**。 1078 | 1079 | ### Go Vendor 1080 | 1081 | 为了解决多版本控制问题,go又增加了Go Vendor的方式来管理依赖。 1082 | 1083 | 使用govendor init 在项目根目录会生成vendor文件夹,其中存放了当前项目依赖的副本。在Vendor机制下,如果当前项目存在Vendor目录,会优先使用该目录下的依赖,如果依赖不存在,会从GOPATH中寻找;这样解决了更新GOPATH依赖源码后之前的版本不兼容的问题。 1084 | 1085 | #### Go Vendor弊端 1086 | 1087 | 弊端很明显,无法解决依赖的依赖。 1088 | 1089 | 同样还是无法解决依赖的冲突。 1090 | 1091 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36697e481dd2470badd68c1181e735ad~tplv-k3u1fbpfcp-watermark.image?) 1092 | 1093 | 归根到底vendor不能很清晰的标识依赖的版本概念。 1094 | 1095 | ### Go Module (最终解决方案 1096 | 1097 | 特点: 1098 | 1099 | * 通过 go.mod 管理依赖包版本。 1100 | * 通过 go get/mod 工具管理依赖包。 1101 | 1102 | 最终目标:定义版本规则和管理项目的依赖关系。 1103 | 1104 | #### 依赖管理三要素 1105 | 1106 | 1. 配置文件,描述依赖 (对应go.mod) 1107 | 2. 中心仓库管理依赖库 (GoProxy) 1108 | 3. 本地工具 go get/mod 1109 | 1110 | ##### 配置文件 1111 | 1112 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58d8d2cff08141afb920f0b73015f630~tplv-k3u1fbpfcp-watermark.image?) 1113 | 1114 | 每个依赖单元用模块路径+版本来唯一标示。 1115 | 1116 | ###### 版本规则 1117 | 1118 | ![版本规则](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c1fbd9f8468c44c3ada2d8dca8dfb277~tplv-k3u1fbpfcp-watermark.image?) 1119 | 1120 | gopath和govendor都是源码副本方式依赖,没有版本规则概念,而gomod为了放方便管理则定义了版本规则。 1121 | 1122 | 对于语义化版本有如下规则: 1123 | 1124 | * MAJOR:表示是不兼容的 API,所以即使是同一个库,MAJOR 版本不同也会被认为是不同的模块。 1125 | * MINOR:通常是新增函数或功能,向后(向下)兼容。 1126 | * PATCH:修复 bug。 1127 | 1128 | ###### 杂项 1129 | 1130 | 版本号后面添加 `//indirect` 表示间接依赖。 1131 | 1132 | **选择题** 1133 | 1134 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aaf73772466a49cf8a9a1e439c50f9a7~tplv-k3u1fbpfcp-watermark.image?) 1135 | 1136 | 选择1.4,因为它向后兼容。 1137 | 1138 | #### 中心仓库管理依赖库 1139 | 1140 | ##### 依赖的分发 1141 | 1142 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f297d3d4b4b04299a6f3105ba3809a98~tplv-k3u1fbpfcp-watermark.image?) 1143 | 1144 | 如果直接向代码托管平台进行依赖的请求,很快会发现有以下这些问题: 1145 | 1146 | * 无法保证构建的稳定性(可能代码仓库的所有者更改删除了包版本 1147 | * 无法保证可用性 1148 | * 增加了平台压力 1149 | 1150 | 为了很好的解决以上依赖分发的问题,go采用Proxy进行代理分发。 1151 | 1152 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f0d00bcd1b294408b59fcc201506772e~tplv-k3u1fbpfcp-watermark.image?) 1153 | 1154 | Go Proxy 是一个服务站点,它会缓源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用。 1155 | 1156 | ###### 较为神奇的地方 1157 | 1158 | Go语言通过设置环境变量GOPROXY来设置具体的服务站点。可以通过逗号设置多个Proxy站点,最后如果这几个都没有找到,那么会通过direct进行回源,也就是回到本来的请求站点,而不是代理站。有意思的是,当你此时从源站下载好依赖后,你之前走过的Proxy站点也会将这个缓存下来。 1159 | 1160 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f8435cb7cb14c6597c1ce34011471c1~tplv-k3u1fbpfcp-watermark.image?) 1161 | 1162 | **有趣的实践** 1163 | 1164 | 通过go mod init创建一个项目,写好后提交到GitHub仓库里,然后通过go get对你的代码进行请求,注意最后回源的direct要加上,否则肯定get不到,最后你会发现你的Proxy站上,也有了你的代码🥳 1165 | 1166 | 你会发现这样的过程,让go语言的代码仓库非常的繁荣,各种库都可以go get得到! 1167 | 1168 | #### 本地工具 1169 | 1170 | > go get命令 1171 | 1172 | ![go get](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6cd346b7b9e4e2585fd543498ba110c~tplv-k3u1fbpfcp-watermark.image?) 1173 | 1174 | > go mod命令 1175 | 1176 | ![go mod](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7261881ed04a4894b630877d524fd609~tplv-k3u1fbpfcp-watermark.image?) 1177 | 1178 | ------ 1179 | 1180 | 1181 | 1182 | ## 测试 1183 | 1184 | ### 为什么要测试? 1185 | 1186 | 测试是避免事故发生的最后一道关口! 1187 | 1188 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bfe38dac19b345578cecadfc26de9c2b~tplv-k3u1fbpfcp-watermark.image?) 1189 | 1190 | ### 测试类型 1191 | 1192 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a98f3157596d4d20ab67ea07943b2fac~tplv-k3u1fbpfcp-watermark.image?) 1193 | 1194 | * 回归测试:是指修改了旧代码后,重新测试以确认修改没有引入新的错误或导致其他代码产生错误。 1195 | * 集成测试:集成测试的目的是在集成这些不同的软件模块时揭示它们之间交互中的缺陷。 1196 | * 单元测试:单元测试测试开发阶段,开发者对单独的函数、模块做功能验证。 1197 | 1198 | 层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率一定程度上决定这代码的质量。 1199 | 1200 | ### 单元测试 1201 | 1202 | #### go单测的规则 1203 | 1204 | ![单测规则](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/17cae9c93adc496780ac152729b33680~tplv-k3u1fbpfcp-watermark.image?) 1205 | 1206 | #### go单测实例 1207 | 1208 | > 写了一个json解析的单测 1209 | 1210 | json.go 1211 | 1212 | ```go 1213 | package attention 1214 | 1215 | import ( 1216 | "bytes" 1217 | "encoding/json" 1218 | "fmt" 1219 | ) 1220 | 1221 | func NumUnmarshal() { 1222 | jsonStr := `{"id":1,"name":"Jerry"}` 1223 | var res map[string]interface{} 1224 | _ = json.Unmarshal([]byte(jsonStr), &res) 1225 | fmt.Printf("%T\n", res["id"]) 1226 | i := res["id"].(int64) 1227 | fmt.Println(i) 1228 | } 1229 | 1230 | func NumDecode() { 1231 | jsonStr := `{"id":1,"name":"Jerry"}` 1232 | var res map[string]interface{} 1233 | decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) 1234 | decoder.UseNumber() 1235 | _ = decoder.Decode(&res) 1236 | i, _ := res["id"].(json.Number).Int64() 1237 | fmt.Println(i) 1238 | } 1239 | ``` 1240 | 1241 | json_test.go 1242 | 1243 | ```go 1244 | package attention 1245 | 1246 | import "testing" 1247 | 1248 | func TestNumUnmarshal(t *testing.T) { 1249 | NumUnmarshal() 1250 | } 1251 | 1252 | func TestNumDecode(t *testing.T) { 1253 | NumDecode() 1254 | } 1255 | ``` 1256 | 1257 | > 测试结果:通过 go test 会执行这个软件包里面所有的测试。如果需要执行特定的测试在后面跟上这个测试的go文件名以及对应的测试文件名。 1258 | 1259 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58c338d8fe524c20a87242dfea8ddbc3~tplv-k3u1fbpfcp-watermark.image?) 1260 | 1261 | #### 单元测试框架 1262 | 1263 | go语言常见的测试框架有testfy。在go mod文件里面的require部分填上以下代码便可通过`go mod download`进行下载。 1264 | 1265 | ```http 1266 | github.com/stretchr/testify v1.7.1 1267 | ``` 1268 | 1269 | 或者直接 go get这个包也行。 1270 | 1271 | 这个包里包含测试常用的断言。 1272 | 1273 | > 基础用法如下,更多用法请去查看官方文档。 1274 | 1275 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1a6d581e480438cb1ede805ca1d74de~tplv-k3u1fbpfcp-watermark.image?) 1276 | 1277 | #### 衡量单元测试的标准 1278 | 1279 | ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50070402b72c4283a46d72b6073b4ae5~tplv-k3u1fbpfcp-watermark.image?) 1280 | 1281 | ##### 代码覆盖率 1282 | 1283 | 需要在测试时展示代码覆盖率可以通过添加--cover命令行参数。 1284 | 1285 | 下面是我的一次带代码覆盖率的单元测试结果: 1286 | 1287 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/10cc4c8b26424adda7581bde5ae02701~tplv-k3u1fbpfcp-watermark.image?) 1288 | 1289 | 我们可以看到百分比的覆盖率,也就是本次测试经过的代码块占比。 1290 | 1291 | 被测试到的代码都变成了绿色。 1292 | 1293 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ca5c089484b4d40b2be0b2420c9ff59~tplv-k3u1fbpfcp-watermark.image?) 1294 | 1295 | 1296 | 1297 | ### 打桩测试 1298 | 1299 | 在打桩测试前,我们先了解单侧的稳定性和幂等性。 1300 | 1301 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a2194021c4524f2c81f12d8d81d70dd6~tplv-k3u1fbpfcp-watermark.image?) 1302 | 1303 | * 稳定:稳定是指相互隔离,能在任何时间,任何环境,运行测试。 1304 | * 幂等:幂等是指每一次测试运行都应该产生与之前一样的结果。 1305 | 1306 | 如果在有外部依赖的情况下进行单测,换一个测试环境,那么这个外部依赖信息可能会发生变化,比如需要打开某个文件,如果你把这个给别人测试,那么在他本地的文件路径肯定就不一致。这就完全没法符合稳定和幂等两个条件。 1307 | 1308 | 如下代码: 1309 | 1310 | ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e12bc1f3bcb4151ac40bb56c1b80f7e~tplv-k3u1fbpfcp-watermark.image?) 1311 | 1312 | 那么我们如何解决这样的问题呢? 1313 | 1314 | 我们通过打桩来解决这个问题。 1315 | 1316 | 所谓打桩就是通过你指定的行为来对原本的行为替换,到计算机语言里面来讲就是通过你定义的桩函数把原本的函数进行替换,这就是打桩。 1317 | 1318 | > 那打桩有什么用呢? 1319 | 1320 | * 隔离:将测试任务从产品项目中分离出来,使之能够独立编译、链接,并独立运行。 1321 | 1322 | * 补齐:用桩来代替未实现的代码,例如,函数A调用了函数B,而函数B由其他程序员编写,且未实现,那么,可以用桩来代替函数B,使函数A能够运行并测试。 1323 | 1324 | * 控制:控制是指在测试时,人为设定相关代码的行为,使之符合测试需求。 1325 | 1326 | > go语言的打桩实现原理: 1327 | > 1328 | > 在运行时通过通过 Go 的 unsafe 包,将内存中函数的地址替换为运行时函数的地址。 将待打桩函数或方法的实现跳转到。 1329 | 1330 | 打桩更改后的测试: 1331 | 1332 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b40aef1c719245768b4760192ed662a7~tplv-k3u1fbpfcp-watermark.image?) 1333 | 1334 | ### 基准测试(Benchmark) 1335 | 1336 | 很多时候我们需要清楚代码的运行效率,这个时候,我们就需要对代码进行基准测试了。 1337 | 1338 | 基准测试需要遵循以下语法规定: 1339 | 1340 | 1. go语言中的基准测试也是基于单元测试,所以还是需要遵循 `*_test.go` 的命名规则。 1341 | 2. 用于基准测试的函数名必须以Benchmark开头。 1342 | 3. 函数的入参需要是 `*testing.B` 。 1343 | 1344 | #### 具体例子 1345 | 1346 | ##### 代码分析 1347 | 1348 | 负载均衡中随机选择执行服务器。 1349 | 1350 | `server_select.go` 1351 | 1352 | ```go 1353 | package benchmark 1354 | 1355 | import ( 1356 | "github.com/bytedance/gopkg/lang/fastrand" 1357 | "math/rand" 1358 | ) 1359 | 1360 | var ServerIndex [10]int 1361 | 1362 | // InitServerIndex 初始化服务器的描述符 1363 | func InitServerIndex() { 1364 | for i:=0;i<10;i++{ 1365 | ServerIndex[i] = i+100 1366 | } 1367 | } 1368 | 1369 | // RandSelect 随机选择一个服务器 1370 | func RandSelect() int { 1371 | return ServerIndex[rand.Intn(10)] 1372 | } 1373 | 1374 | // FastRandSelect 用外部的fast包 1375 | func FastRandSelect()int{ 1376 | return ServerIndex[fastrand.Intn(10)] 1377 | } 1378 | ``` 1379 | 1380 | `server_select_test.go` 1381 | 1382 | ```go 1383 | package benchmark 1384 | 1385 | import "testing" 1386 | 1387 | func BenchmarkSelect(b *testing.B){ 1388 | InitServerIndex() 1389 | b.ResetTimer() 1390 | for i:=0;i 我们对Benchmark的代码进行以下讲解: 1407 | > 1408 | > 1. 对一个测试用例的默认测试时间是 1 秒,当测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,并以递增后的值重新进行用例函数测试。 1409 | > 2. Resttimer重置计时器,我们在reset之前做了init或其他的准备操作,这些操作不应该作为基准测试的范围。 1410 | > 3. runparallel是多协程并发测试。 1411 | 1412 | ##### 代码效率分析 1413 | 1414 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec0280bd099a44d38f6ec2a00e3b9aa2~tplv-k3u1fbpfcp-watermark.image?) 1415 | 1416 | 我们发现,多线程的测试反而效率更慢了! 1417 | 1418 | 主要原因是rand为了保证全局的随机性和并发安全,持有了一把全局锁。 1419 | 1420 | 这里贴了字节实现的较为快速的随机数实现库:[fastrand](github.com/bytedance/gopkg/lang/fastrand) 1421 | 1422 | 安装这个库也很简单,下面一行命令即可: 1423 | 1424 | ```go 1425 | go get github.com/bytedance/gopkg/lang/fastrand 1426 | ``` 1427 | 1428 | **优化代码** 1429 | 1430 | 通过把 rand 替换为 fastrand 后,重新测试结果如下: 1431 | 1432 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f46d15c9188c423589c53e8c5f02c6d9~tplv-k3u1fbpfcp-watermark.image?) 1433 | 1434 | 我们发现多线程的效率与之前的效率相比,提升了百倍! 1435 | 1436 | > fastrand主要的实现思路是牺牲了一定的数列一致性,在大多数场景是适用的,同学在后面遇到随机的场景可以尝试用一下。 1437 | 1438 | ------ 1439 | 1440 | 1441 | 1442 | ## 项目实战 1443 | 1444 | ### 需求描述 1445 | 1446 | - [x] 展示话题(标题,文字描述)和回帖列表 1447 | - [x] 暂不考虑前端页面实现,仅实现一个本地的web服务 1448 | - [x] 话题和回帖数据用文件存储 1449 | 1450 | > 用户浏览 1451 | 1452 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1625e2b7f579427a8022c6f51b7c9b17~tplv-k3u1fbpfcp-watermark.image?) 1453 | 1454 | > 实例图 1455 | 1456 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec319dffe6ae49ad977a8d6a092c7d42~tplv-k3u1fbpfcp-watermark.image?) 1457 | 1458 | ### 项目分层结构 1459 | 1460 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d578dcdb48674044a09f6144daa380af~tplv-k3u1fbpfcp-watermark.image?) 1461 | 1462 | * 数据层:Repository 数据**Model**,**封装外部数据的增删改查**,并将数据初步反序列化,且需要直接与底层的数据存储形式打交道,比如存储形式是文件,还是数据库,还是微服务等等。 1463 | * 逻辑层:Service 业务**Entity**,这里会利用数据层得到封装好的数据再次封装得到更贴近客户端请求的数据,同样也需要写好增删改查,但这里的增删改查并不会与真正的外部数据打交道,也就是说Service层不关心底层数据的存储形式,只关心**核心业务输出**。 1464 | * 视图层:Controller 视图View,**处理和外部的交互逻辑**,也就是说,这个层级也是依赖于上一个层级的数据,它负责真正和客户端交互的过程,只关心返回什么样的数据给客户端,而前面两个层级都是为这个层级做的铺垫。 1465 | 1466 | ### 代码实现 1467 | 1468 | > 代码实现可以到[TraningCamp](https://github.com/ACking-you/TraningCamp)查看lesson2源码(温馨提示github域名后加上1s可以有意想不到的源码阅读体验哦 1469 | 1470 | #### Repository层实现 1471 | 1472 | > 主要实现底层存储数据序列化到具体的结构体上,以及对应的增删改查。 1473 | 1474 | 一般经过以下过程: 1475 | 1476 | ```mermaid 1477 | graph LR 1478 | a(初始化) 1479 | b(底层存储的交互) 1480 | a-->b 1481 | ``` 1482 | 1483 | * 初始化:主要是对数据的准备,或者时数据库的连接的初始化。 1484 | * 底层存储的交互:如果数据库,那么就是对数据库发起请求得到对应的Model,如果是文件存储,那么数据应该已经初始化到内存,直接进行取值即可。 1485 | 1486 | ##### 数据映射 1487 | 1488 | 由于本次的存储实现采取的是文件存储,故需要每次一次性把文件读取好并完成数据的反序列化。这里用到的map进行映射数据方便查询。 1489 | 1490 | > 如果是数据库,这时应该通过一些orm框架直接进行数据的增删改查映射,但在此之前还是得连接数据库(初始化过程 1491 | 1492 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7d5632cccf7f44f2a7e6ea2c10d3fcab~tplv-k3u1fbpfcp-watermark.image?) 1493 | 1494 | > 具体源码实现(我多加了一个记录最后一个Id的,方便完成id的不重复生成 1495 | 1496 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/857dc93539494a79a34baf8065406afb~tplv-k3u1fbpfcp-watermark.image?) 1497 | 1498 | ##### 数据的增删改查 1499 | 1500 | **topic.go** 1501 | 1502 | > 实现对话题的增删改查,这里用到了一个结构体+方法的方式去实现,且用sync.Once实现单例,我觉得好处在于: 1503 | > 1504 | > 1. 防止重名。 1505 | > 2. 方便记忆,方便调用时进行对应的语法补全(比如想要对Topic进行操作,只需要想到TopicDao这个即可补全后续的操作 1506 | 1507 | ![topic.go](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/999b2037706f4a09976e14a8785fda88~tplv-k3u1fbpfcp-watermark.image?) 1508 | 1509 | **post.go** 1510 | 1511 | > 和前面的实现类似,这里我完成了**homework**,添加了AddPost方法以及对应的将数据插入到文件的方法,由于可能出现多个客户端同时发起post请求,这时我们需要对数据进行并发安全的保护,这里我使用的Mutex加锁的方式。 1512 | 1513 | ![post.go](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b1ba9b4fa254d9191ffec0a0efeed3f~tplv-k3u1fbpfcp-watermark.image?) 1514 | 1515 | 1516 | 1517 | #### Service层实现 1518 | 1519 | > 主要是对Repository层的Modle进行进一步的封装成更上层需要的Entity。 1520 | 1521 | 一般经过以下流程: 1522 | 1523 | ```mermaid 1524 | graph LR 1525 | a(参数校验) 1526 | b(准备数据) 1527 | c(组装实体) 1528 | a-->b-->c 1529 | ``` 1530 | 1531 | * **参数校验**:由于是和上层通信的层,上层调用得到数据时,首先**需要传入对应的参数,那么我们需要对这个参数进行校验,**不同的方法需要的参数是不同的,需要进行的校验也是不同的,比如本项目查询的方法和插入的方法,需要的参数就不同,所以对应的也是走的这三个流程。 1532 | * **准备数据**:在正式组装得到整个实体之前,我们应该先进行数据的准备,也就是需要把零件得到,当然,不一次性组装好的原因,我认为更重要的是这样可以**减少代码的耦合**,这样一来准备每个数据的过程可以独立开,且可以进行针对性的优化,或者进行局部的修改,也不会直接对组装代码造成影响。 1533 | * **组装实体**:把准备好的数据返回即可。 1534 | 1535 | 为了实现上述过程,我们建立一个结构体,保存准备的数据,且把整个组装实体的过程流程化。 1536 | 1537 | > 结构体如下: 1538 | 1539 | ```go 1540 | // PageInfo 一个页面的信息包括,topic和它上面的post言论 1541 | type PageInfo struct { 1542 | Topic *repository.Topic 1543 | PostList []*repository.Post 1544 | } 1545 | 1546 | // QueryPageInfoFlow 为了防止高耦合度的构造PageInfo,可以构造如下结构体实现流式处理 1547 | type QueryPageInfoFlow struct { 1548 | topicId int64 1549 | pageInfo *PageInfo 1550 | 1551 | topic *repository.Topic 1552 | posts []*repository.Post 1553 | } 1554 | ``` 1555 | 1556 | > 整个组装过程: 1557 | 1558 | ```go 1559 | // Do 整个组装过程 1560 | func (q *QueryPageInfoFlow) Do() (*PageInfo, error) { 1561 | //对id进行合法性验证 1562 | if err := q.checkNum(); err != nil { 1563 | return nil, err 1564 | } 1565 | //准备好生成PageInfo的数据 1566 | if err := q.prepareInfo(); err != nil { 1567 | return nil, err 1568 | } 1569 | //打包最终的PageInfo 1570 | if err := q.packPageInfo(); err != nil { 1571 | return nil, err 1572 | } 1573 | return q.pageInfo, nil 1574 | } 1575 | ``` 1576 | 1577 | 1578 | 1579 | ##### 参数校验 1580 | 1581 | > 由于这个查询过程暂时只需要校验这一个参数 1582 | 1583 | ```go 1584 | func (q *QueryPageInfoFlow) checkNum() error { 1585 | if q.topicId <= 0 { 1586 | return errors.New("topic must larger than 0") 1587 | } 1588 | return nil 1589 | } 1590 | ``` 1591 | 1592 | ##### 准备数据 1593 | 1594 | > 由于两个数据的查询毫无关联,可以通过并行处理。 1595 | 1596 | ```mermaid 1597 | graph LR 1598 | a[话题信息] 1599 | b[回帖信息] 1600 | c[查询] 1601 | d[结束] 1602 | c-->a 1603 | c-->b 1604 | a-->d 1605 | b-->d 1606 | ``` 1607 | 1608 | 1609 | 1610 | ```go 1611 | //这两个过程,由于是毫无关联的,可以用go协程进行并发处理 1612 | func (q *QueryPageInfoFlow) prepareInfo() error { 1613 | var wg sync.WaitGroup 1614 | wg.Add(2) 1615 | //获取Topic 1616 | go func() { 1617 | defer wg.Done() 1618 | q.topic = repository.NewTopicDao().QueryTopicFromId(q.topicId) 1619 | }() 1620 | //获取Posts 1621 | go func() { 1622 | defer wg.Done() 1623 | q.posts = repository.NewPostDao().QueryPostsFromParentId(q.topicId) 1624 | }() 1625 | 1626 | wg.Wait() 1627 | return nil 1628 | } 1629 | ``` 1630 | 1631 | ##### 组装实体 1632 | 1633 | ```go 1634 | //更新最终的PageInfo 1635 | func (q *QueryPageInfoFlow) packPageInfo() error { 1636 | q.pageInfo = &PageInfo{ 1637 | Topic: q.topic, 1638 | PostList: q.posts, 1639 | } 1640 | return nil 1641 | } 1642 | 1643 | ``` 1644 | 1645 | > 这样的话实现整个QueryPageInfo函数就只需要调用这个结构体的方法即可。 1646 | > 1647 | > 如下: 1648 | 1649 | ```go 1650 | func QueryPageInfo(id int64) (*PageInfo, error) { 1651 | return NewQueryPageInfoFlow(id).Do() 1652 | } 1653 | ``` 1654 | 1655 | #### Controller层实现 1656 | 1657 | > 这个层级是真正对客户端发来的请求进行直接响应的层级,直接与客户端交互。 1658 | 1659 | 一般经过以下过程: 1660 | 1661 | ```mermaid 1662 | graph LR 1663 | a[参数解析] 1664 | b[构造数据] 1665 | c[返回数据] 1666 | a-->b-->c 1667 | ``` 1668 | 1669 | * **参数解析**:由于对接的数据直接是上层收到的信息,所以大概率是纯字符串,所以需要先对参数进行解析。 1670 | * **构造数据**:也就是构造响应的数据,一般来说除了直接的数据外,还需要提供一个错误码和错误信息给前端。 1671 | * **返回数据**:根据不同情况构造的不同数据直接返回即可。 1672 | 1673 | ##### 具体代码 1674 | 1675 | ```go 1676 | // PageData 最终发送给客户端的json数据对应的结构体,我们需要错误码,以及对应错误码对应的消息,最后再是数据(用空接口实现泛型 1677 | type PageData struct { 1678 | Code int64 `json:"code"` 1679 | Msg string `json:"msg"` 1680 | Data interface{} `json:"data"` 1681 | } 1682 | 1683 | // QueryPageINfo 真正和客户端进行交互的函数,需要注意客户端发来的流量都是字符串形式 1684 | func QueryPageINfo(topicIdStr string) *PageData { 1685 | pageId, err := strconv.Atoi(topicIdStr) 1686 | if err != nil { 1687 | return &PageData{Code: 1, Msg: err.Error(), Data: nil} 1688 | } 1689 | pageInfo, err := service.QueryPageInfo(int64(pageId)) 1690 | if err != nil { 1691 | return &PageData{Code: 2, Msg: err.Error(), Data: nil} 1692 | } 1693 | return &PageData{Code: 0, Msg: "success", Data: pageInfo} 1694 | } 1695 | 1696 | ``` 1697 | 1698 | #### homework部分 1699 | 1700 | ##### 作业内容与思考 1701 | 1702 | 课后实战: 1703 | 1704 | * 支持发布帖子。 1705 | * 本地Id生成保证不重复。 1706 | * Append文件,更新索引,注意并发安全问题。 1707 | 1708 | > 我发现一个特点,这种分Controller、Service、Repository层的情况, 1709 | > 1710 | > 当你上层调用**查询**接口的时候,**数据是自下往上的**,也就是数据是从下往上依次封装。 1711 | > 1712 | > 而如果是实现**添加操作**接口的时候,**数据是自上往下的**,则数据是从上往下依次封装。 1713 | 1714 | ##### 具体实现 1715 | 1716 | > 思路: 1717 | > 1718 | > 1. Id生成唯一性,是用的一个lastIndexId保存整个post中最大的id,之后每次添加post都继续增加这个lastIndexId来得到新的id。 1719 | > 2. 并发安全问题,用到Mutex加锁临界区即可。 1720 | 1721 | ###### Repository层 1722 | 1723 | > AddPost提供是提供给Service层的接口。 1724 | > 1725 | > 需要实现把数据添加到map里以及append到文件中(对应fileDataInsertPost函数) 1726 | 1727 | ```go 1728 | func (d *PostDao) AddPost(post *Post) error { 1729 | //加锁保证同时请求的并发安全 1730 | lock := sync.Mutex{} 1731 | lock.Lock() 1732 | posts, ok := postIndexMap[post.ParentId] 1733 | if !ok { 1734 | return errors.New("post invalid,not exist parent id") 1735 | } 1736 | //注意更新map里的数据,go切片并不像C++里的Vector,可能append后操作的就不是同一片 底层数组了 1737 | 1738 | postIndexMap[post.ParentId] = append(posts, post) 1739 | err := fileDataInsertPost("./lesson2/homework/data/", post) 1740 | if err != nil { 1741 | return err 1742 | } 1743 | 1744 | lock.Unlock() 1745 | return nil 1746 | } 1747 | 1748 | func fileDataInsertPost(filePath string, post *Post) error { 1749 | open, err := os.OpenFile(filePath+"post", os.O_WRONLY|os.O_APPEND, 0666) 1750 | if err != nil { 1751 | return err 1752 | } 1753 | writer := bufio.NewWriter(open) 1754 | 1755 | data, err := json.Marshal(*post) 1756 | if err != nil { 1757 | return err 1758 | } 1759 | writer.WriteString("\r\n") 1760 | writer.Write(data) 1761 | writer.Flush() 1762 | return nil 1763 | } 1764 | ``` 1765 | 1766 | ###### Service层实现 1767 | 1768 | > 之前实现的流程基本一致,先校验上层传来的参数,数据准备过程换成数据的发布(publish)过程,将得到的数据封装好后再传给下层(**我们发现这个数据的组织过程和查询是反着的** 1769 | 1770 | ```go 1771 | package service 1772 | 1773 | import ( 1774 | "errors" 1775 | "github.com/ACking-you/TraningCamp/lesson2/homework/repository" 1776 | "time" 1777 | "unicode/utf8" 1778 | ) 1779 | 1780 | func PublishPost(topicId, userId int64, content string) (int64, error) { 1781 | return NewPublishPostFlow(topicId, userId, content).Do() 1782 | } 1783 | 1784 | func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow { 1785 | return &PublishPostFlow{ 1786 | userId: userId, 1787 | content: content, 1788 | topicId: topicId, 1789 | } 1790 | } 1791 | 1792 | type PublishPostFlow struct { 1793 | userId int64 1794 | content string 1795 | topicId int64 1796 | 1797 | postId int64 1798 | } 1799 | 1800 | func (f *PublishPostFlow) Do() (int64, error) { 1801 | if err := f.checkParam(); err != nil { 1802 | return 0, err 1803 | } 1804 | if err := f.publish(); err != nil { 1805 | return 0, err 1806 | } 1807 | return f.postId, nil 1808 | } 1809 | 1810 | func (f *PublishPostFlow) checkParam() error { 1811 | if f.userId <= 0 { 1812 | return errors.New("userId id must be larger than 0") 1813 | } 1814 | if utf8.RuneCountInString(f.content) >= 500 { 1815 | return errors.New("content length must be less than 500") 1816 | } 1817 | return nil 1818 | } 1819 | 1820 | func (f *PublishPostFlow) publish() error { 1821 | post := &repository.Post{ 1822 | ParentId: f.topicId, 1823 | UserId: f.userId, 1824 | Content: f.content, 1825 | CreateTime: time.Now().Unix(), 1826 | } 1827 | repository.LastPostId++ 1828 | post.Id = repository.LastPostId 1829 | if err := repository.NewPostDao().AddPost(post); err != nil { 1830 | return err 1831 | } 1832 | f.postId = post.Id 1833 | return nil 1834 | } 1835 | 1836 | ``` 1837 | 1838 | ###### Controller层 1839 | 1840 | > 和之前的Query处理过程是完全一致的,解析参数-->构造内容-->返回内容 1841 | 1842 | ```go 1843 | package controller 1844 | 1845 | import ( 1846 | "strconv" 1847 | 1848 | "github.com/ACking-you/TraningCamp/lesson2/homework/service" 1849 | ) 1850 | 1851 | func PublishPost(uidStr, topicIdStr, content string) *PageData { 1852 | //参数转换 1853 | uid, _ := strconv.ParseInt(uidStr, 10, 64) 1854 | 1855 | topic, _ := strconv.ParseInt(topicIdStr, 10, 64) 1856 | //获取service层结果 1857 | postId, err := service.PublishPost(topic, uid, content) 1858 | if err != nil { 1859 | return &PageData{ 1860 | Code: 1, 1861 | Msg: err.Error(), 1862 | } 1863 | } 1864 | return &PageData{ 1865 | Code: 0, 1866 | Msg: "success", 1867 | Data: map[string]int64{ 1868 | "post_id": postId, 1869 | }, 1870 | } 1871 | } 1872 | 1873 | ``` 1874 | 1875 | 1876 | 1877 | ### 实测结果 1878 | 1879 | #### 服务端代码 1880 | 1881 | **server.go** 1882 | 1883 | ```go 1884 | package main 1885 | 1886 | import ( 1887 | "github.com/ACking-you/TraningCamp/lesson2/homework/controller" 1888 | "github.com/ACking-you/TraningCamp/lesson2/homework/repository" 1889 | "gopkg.in/gin-gonic/gin.v1" 1890 | "os" 1891 | "strings" 1892 | ) 1893 | 1894 | //最后再通过gin框架搭建服务器 1895 | 1896 | func main() { 1897 | //准备数据 1898 | if err := Init("./lesson2/homework/data/"); err != nil { 1899 | os.Exit(-1) 1900 | } 1901 | 1902 | //注册路由 1903 | 1904 | r := gin.Default() 1905 | r.GET("me:id", func(c *gin.Context) { 1906 | topicId := c.Param("id") 1907 | topicId = strings.TrimLeft(topicId, ":,") 1908 | println(topicId) 1909 | data := controller.QueryPageINfo(topicId) 1910 | c.JSONP(200, data) 1911 | }) 1912 | 1913 | r.POST("/post/do", func(c *gin.Context) { 1914 | uid, _ := c.GetPostForm("uid") 1915 | println(uid) 1916 | topicId, _ := c.GetPostForm("topic_id") 1917 | println(topicId) 1918 | content, _ := c.GetPostForm("content") 1919 | println(content) 1920 | data := controller.PublishPost(uid, topicId, content) 1921 | c.JSON(200, data) 1922 | }) 1923 | err := r.Run() 1924 | if err != nil { 1925 | return 1926 | } 1927 | } 1928 | 1929 | func Init(filepath string) error { 1930 | err := repository.Init(filepath) 1931 | if err != nil { 1932 | return err 1933 | } 1934 | return nil 1935 | } 1936 | ``` 1937 | 1938 | #### 请求结果 1939 | 1940 | > 使用的是goland里面的http请求工具进行的。 1941 | 1942 | ##### GET请求测试(成功) 1943 | 1944 | 请求报文如下: 1945 | 1946 | ```http 1947 | GET http://localhost:8080/me:1 1948 | Accept: application/json 1949 | ``` 1950 | 1951 | 返回报文如下: 1952 | 1953 | ```http 1954 | HTTP/1.1 200 OK 1955 | Content-Type: application/json; charset=utf-8 1956 | Date: Mon, 09 May 2022 05:17:28 GMT 1957 | Content-Length: 426 1958 | 1959 | { 1960 | "code": 0, 1961 | "msg": "success", 1962 | "data": { 1963 | "Topic": { 1964 | "id": 1, 1965 | "title": "青训营来啦!", 1966 | "content": "小姐姐,快到碗里来~", 1967 | "create_time": 1650437625 1968 | }, 1969 | "PostList": [ 1970 | { 1971 | "id": 1, 1972 | "parent_id": 1, 1973 | "content": "小姐姐快来1", 1974 | "create_time": 1650437616, 1975 | "user_id": 1 1976 | }, 1977 | { 1978 | "id": 2, 1979 | "parent_id": 1, 1980 | "content": "小姐姐快来2", 1981 | "create_time": 1650437617, 1982 | "user_id": 2 1983 | }, 1984 | { 1985 | "id": 3, 1986 | "parent_id": 1, 1987 | "content": "小姐姐快来3", 1988 | "create_time": 1650437618, 1989 | "user_id": 13 1990 | } 1991 | ] 1992 | } 1993 | } 1994 | 1995 | Response code: 200 (OK); Time: 174ms; Content length: 368 bytes 1996 | 1997 | ``` 1998 | 1999 | ##### POST请求测试(成功) 2000 | 2001 | 请求报文: 2002 | 2003 | ```http 2004 | POST http://localhost:8080/post/do 2005 | Content-Type: application/x-www-form-urlencoded 2006 | 2007 | uid=2&topic_id=1&content=测试内容嗨嗨嗨嗨 2008 | ``` 2009 | 2010 | 返回报文: 2011 | 2012 | ```http 2013 | HTTP/1.1 200 OK 2014 | Content-Type: application/json; charset=utf-8 2015 | Date: Mon, 09 May 2022 05:22:38 GMT 2016 | Content-Length: 47 2017 | 2018 | { 2019 | "code": 0, 2020 | "msg": "success", 2021 | "data": { 2022 | "post_id": 5 2023 | } 2024 | } 2025 | 2026 | Response code: 200 (OK); Time: 103ms; Content length: 47 bytes 2027 | ``` 2028 | 2029 | 再看看文件里面的内容是否添加: 2030 | 2031 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0ac87642c8e24e119b7de400578b2ada~tplv-k3u1fbpfcp-watermark.image?) 2032 | 2033 | 成功! 2034 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ACking-you/TraningCamp 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986 7 | github.com/gin-contrib/sse v0.1.0 // indirect 8 | github.com/gin-gonic/gin v1.3.0 // indirect 9 | github.com/golang/protobuf v1.5.2 // indirect 10 | github.com/json-iterator/go v1.1.12 // indirect 11 | github.com/kr/pretty v0.3.0 // indirect 12 | github.com/mattn/go-isatty v0.0.14 // indirect 13 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 14 | github.com/rogpeppe/go-internal v1.8.0 // indirect 15 | github.com/stretchr/testify v1.7.1 16 | github.com/ugorji/go v1.2.7 // indirect 17 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 18 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect 19 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 20 | google.golang.org/protobuf v1.28.0 // indirect 21 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 22 | gopkg.in/gin-gonic/gin.v1 v1.3.0 23 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 24 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 25 | gopkg.in/yaml.v2 v2.4.0 // indirect 26 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986 h1:6RbXPuVEF5+jeTXrKY/UA+rcK2w6vNIA9vUZ9MKeopc= 2 | github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 8 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 9 | github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= 10 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 11 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 12 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 13 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 14 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 15 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 16 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 17 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 18 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 19 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 20 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 21 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 22 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 23 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 24 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 25 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 26 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 27 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 28 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 29 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 31 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 32 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 33 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 34 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 35 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 36 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 37 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 38 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 39 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 40 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 41 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 42 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 43 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 44 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 45 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 46 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 47 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 48 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 49 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 50 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 51 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 52 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= 57 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 59 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 60 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 61 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 62 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 63 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 65 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 66 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 67 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 69 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 71 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 72 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 73 | gopkg.in/gin-gonic/gin.v1 v1.3.0 h1:DjAu49rN1YttQsOkVCPlAO3INcZNFT0IKsNVMk5MRT4= 74 | gopkg.in/gin-gonic/gin.v1 v1.3.0/go.mod h1:Eljh74A/zAvUOQ835v6ySeZ+5gQG6tKjbZTaZ9iWU3A= 75 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 76 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 77 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 78 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 79 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 80 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 81 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 82 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 83 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 84 | -------------------------------------------------------------------------------- /lesson1/echo/echo_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "net" 8 | ) 9 | 10 | func main() { 11 | server, err := net.Listen("tcp", "0.0.0.0:1080") 12 | if err != nil { 13 | panic(err) 14 | } 15 | for { 16 | client, err := server.Accept() 17 | if err != nil { 18 | log.Printf("Accept failed %v", err) 19 | continue 20 | } 21 | 22 | fmt.Printf("连接成功! clent:%v \n", client.RemoteAddr()) 23 | go process(client) 24 | } 25 | } 26 | 27 | func process(conn net.Conn) { 28 | defer func() { 29 | conn.Close() 30 | fmt.Printf("连接断开! clent:%v \n", conn.RemoteAddr()) 31 | }() 32 | 33 | reader := bufio.NewReader(conn) 34 | for { 35 | b, err := reader.ReadByte() 36 | if err != nil { 37 | break 38 | } 39 | _, err = conn.Write([]byte{b}) 40 | if err != nil { 41 | break 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lesson1/homework/guessing_game/guessing_game.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | maxNum := 100 13 | rand.Seed(time.Now().UnixNano()) 14 | secretNumber := rand.Intn(maxNum) 15 | // fmt.Println("The secret number is ", secretNumber) 16 | 17 | fmt.Println("Please input your guess") 18 | var input string 19 | for { 20 | _, err := fmt.Scanf("%s", &input) 21 | if err != nil { 22 | fmt.Println("An error occured while reading input. Please try again") 23 | continue 24 | } 25 | 26 | input = strings.TrimSuffix(input, "\r\n") 27 | 28 | guess, err := strconv.Atoi(input) 29 | if err != nil { 30 | fmt.Println("Invalid input. Please enter an integer value") 31 | continue 32 | } 33 | fmt.Println("You guess is", guess) 34 | if guess > secretNumber { 35 | fmt.Println("Your guess is bigger than the secret number. Please try again") 36 | } else if guess < secretNumber { 37 | fmt.Println("Your guess is smaller than the secret number. Please try again") 38 | } else { 39 | fmt.Println("Correct, you Legend!") 40 | break 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lesson1/homework/simple_dict/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/ACking-you/TraningCamp/lesson1/homework/simple_dict/src" 7 | "os" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | var wg sync.WaitGroup 13 | 14 | func main() { 15 | //if len(os.Args) != 2 { 16 | // fmt.Fprint(os.Stderr, "usage: simpleDict [WORD] example: simpleDict hello") 17 | // os.Exit(1) 18 | //} 19 | input := bufio.NewReader(os.Stdin) 20 | fmt.Println("请输入需要翻译的单词,或句子") 21 | word, err := input.ReadString('\n') 22 | 23 | if err != nil { 24 | panic(err) 25 | } 26 | word = strings.TrimRight(word, "\n") 27 | src.QueryDeepl(word) 28 | 29 | wg.Add(3) 30 | 31 | go func() { 32 | defer wg.Done() 33 | src.QueryYoudao(word) 34 | }() 35 | 36 | go func() { 37 | defer wg.Done() 38 | src.QueryCaiyun(word) 39 | }() 40 | go func() { 41 | defer wg.Done() 42 | src.QueryDeepl(word) 43 | }() 44 | 45 | wg.Wait() 46 | } 47 | -------------------------------------------------------------------------------- /lesson1/homework/simple_dict/src/query_caiyun.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | func QueryCaiyun(word string) { 13 | client := &http.Client{} 14 | request := DictRequestCaiyun{TransType: "en2zh", Source: word} 15 | buf, err := json.Marshal(request) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | var data = bytes.NewReader(buf) 20 | req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | req.Header.Set("Connection", "keep-alive") 25 | req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`) 26 | req.Header.Set("sec-ch-ua-mobile", "?0") 27 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") 28 | req.Header.Set("app-name", "xy") 29 | req.Header.Set("Content-Type", "application/json;charset=UTF-8") 30 | req.Header.Set("Accept", "application/json, text/plain, */*") 31 | req.Header.Set("os-type", "web") 32 | req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") 33 | req.Header.Set("sec-ch-ua-platform", `"Windows"`) 34 | req.Header.Set("Origin", "https://fanyi.caiyunapp.com") 35 | req.Header.Set("Sec-Fetch-Site", "cross-site") 36 | req.Header.Set("Sec-Fetch-Mode", "cors") 37 | req.Header.Set("Sec-Fetch-Dest", "empty") 38 | req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") 39 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") 40 | resp, err := client.Do(req) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | defer resp.Body.Close() 45 | bodyText, err := ioutil.ReadAll(resp.Body) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | if resp.StatusCode != 200 { //防止返回错误 50 | log.Fatal("bad Status code:", resp.StatusCode, "body", string(bodyText)) 51 | } 52 | var dictResponse DictResponseCaiyun 53 | err = json.Unmarshal(bodyText, &dictResponse) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | fmt.Println("translate from Caiyun\n", "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs) 59 | for _, item := range dictResponse.Dictionary.Explanations { 60 | fmt.Println(item) 61 | } 62 | } -------------------------------------------------------------------------------- /lesson1/homework/simple_dict/src/query_deepl.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | //Deepl 是我见过翻译句子到英文最好的翻译引擎 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "unicode" 12 | ) 13 | 14 | func getDeeplHeader(text string) (header string) { 15 | var rowHeader = `{"jsonrpc":"2.0","method": "LMT_handle_jobs","params":{"jobs":[{"kind":"default","sentences":[{"text":"%s","id":0,"prefix":""}],"raw_en_context_before":[],"raw_en_context_after":[],"preferred_num_beams":4}],"lang":{"preference":{"weight":{"DE":0.28607,"EN":1.46694,"ES":0.25353,"FR":0.26007,"IT":0.23263,"JA":0.8852,"NL":0.21019,"PL":0.19671,"PT":0.18584,"RU":0.18408,"ZH":5.92388,"BG":0.13602,"CS":0.16022,"DA":0.16811,"EL":0.13271,"ET":0.15213,"FI":0.19951,"HU":0.15119,"LT":0.14065,"LV":0.11681,"RO":0.14776,"SK":0.14827,"SL":0.13934,"SV":0.18033},"default":"default"},"source_lang_user_selected":"EN","target_lang":"ZH"},"priority":1,"commonJobParams":{"browserType":1},"timestamp":1651918799405},"id":83800015}` 16 | f := false 17 | for _, ch := range text { // 如果是ZH=>EN则进行另一套请求头 18 | if unicode.Is(unicode.Han, ch) { 19 | f = true 20 | break 21 | } 22 | } 23 | if f { 24 | rowHeader = `{"jsonrpc":"2.0","method": "LMT_handle_jobs","params":{"jobs":[{"kind":"default","sentences":[{"text":"%s","id":0,"prefix":""}],"raw_en_context_before":[],"raw_en_context_after":[],"preferred_num_beams":4,"quality":"fast"}],"lang":{"preference":{"weight":{"DE":0.31213,"EN":1.44586,"ES":0.29528,"FR":0.28777,"IT":0.2898,"JA":0.85516,"NL":0.23712,"PL":0.22037,"PT":0.20319,"RU":0.18266,"ZH":5.59568,"BG":0.13856,"CS":0.1826,"DA":0.19727,"EL":0.13155,"ET":0.1751,"FI":0.24336,"HU":0.16837,"LT":0.15337,"LV":0.13137,"RO":0.16224,"SK":0.16584,"SL":0.15129,"SV":0.20629},"default":"default"},"source_lang_user_selected":"auto","target_lang":"EN"},"priority":-1,"commonJobParams":{"regionalVariant":"en-US","browserType":1},"timestamp":1651990755123},"id":40530009}` 25 | } 26 | header = fmt.Sprintf(rowHeader, text) 27 | return 28 | } 29 | 30 | func QueryDeepl(sentence string) { 31 | client := &http.Client{} 32 | var data = strings.NewReader(getDeeplHeader(sentence)) 33 | req, err := http.NewRequest("POST", "https://www2.deepl.com/jsonrpc?method=LMT_handle_jobs", data) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | req.Header.Set("authority", "www2.deepl.com") 38 | req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`) 39 | req.Header.Set("sec-ch-ua-mobile", "?0") 40 | req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") 41 | req.Header.Set("sec-ch-ua-platform", `"Windows"`) 42 | req.Header.Set("content-type", "application/json") 43 | req.Header.Set("accept", "*/*") 44 | req.Header.Set("origin", "https://www.deepl.com") 45 | req.Header.Set("sec-fetch-site", "same-site") 46 | req.Header.Set("sec-fetch-mode", "cors") 47 | req.Header.Set("sec-fetch-dest", "empty") 48 | req.Header.Set("referer", "https://www.deepl.com/") 49 | req.Header.Set("accept-language", "zh-CN,zh;q=0.9") 50 | req.Header.Set("cookie", "dapUid=0b6c1411-718e-44f2-8c50-30be8a7ab62e; privacySettings=%7B%22v%22%3A%221%22%2C%22t%22%3A1650067200%2C%22m%22%3A%22LAX_AUTO%22%2C%22consent%22%3A%5B%22NECESSARY%22%2C%22PERFORMANCE%22%2C%22COMFORT%22%5D%7D; LMTBID=v2|320b47d1-70c4-4544-9360-a9a112a1f176|e6e06c8d3f0168f11e168a5cbdf0b453; __cf_bm=k1Twpi99FvJCMnDy2Qy71VzeRvvRnp0T5Xs8PXYwe8g-1651912045-0-AdI+N4bHJ5jVMKGhAa4Yg9b+B3KGgWtT6pwbmgHV/43KraYaeVunoUVVGnpf1gb0KJkk0FxpxXJnyrupHhcQUcE=; dapVn=7; dapSid=%7B%22sid%22%3A%226403381c-d78f-424b-baf2-8ade07d83c40%22%2C%22lastUpdate%22%3A1651912606%7D") 51 | resp, err := client.Do(req) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | defer resp.Body.Close() 56 | bodyText, err := ioutil.ReadAll(resp.Body) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | if resp.StatusCode != 200 { //防止返回错误 62 | log.Fatal("bad Status code:", resp.StatusCode, "body", string(bodyText)) 63 | } 64 | 65 | var dictResponse DictResponseDeepl 66 | err = json.Unmarshal(bodyText, &dictResponse) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | fmt.Println("translate from Deepl:") 72 | 73 | //print translation result 74 | for _, translation := range dictResponse.Result.Translations { 75 | for _, beam := range translation.Beams { 76 | for _, sentences := range beam.Sentences { 77 | fmt.Println(sentences.Text) 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lesson1/homework/simple_dict/src/query_youdao.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | //发现问题:有道的请求头是通过动态加盐和sign的方式来请求每一次翻译,所以如果想直接翻译所有,则需要破解出sign的计算原理 4 | import ( 5 | "crypto/md5" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | //TODO sign解析未完成 17 | func getMD5(content string) string { 18 | data := []byte(content) 19 | has := md5.Sum(data) 20 | md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制 21 | return md5str 22 | } 23 | 24 | //暂未破解成功,这个sign不清楚怎么获得 25 | func getYoudaoHeader(text string) (header string) { 26 | salt := strconv.FormatInt(time.Now().UnixNano()/1e5, 10) 27 | sign_str := "fanyideskweb" + text + salt + "Ygy_4c=r#e#4EX^NUGUc5" 28 | sign := getMD5(sign_str) 29 | //fmt.Println(sign) 30 | //fmt.Println(salt) 31 | var requestText = `i=%s&from=AUTO&to=AUTO&smartresult=dict&client=fanyideskweb&salt=%s&sign=%s<s=1651919604941&bv=c2777327e4e29b7c4728f13e47bde9a5&doctype=json&version=2.1&keyfrom=fanyi.web&action=FY_BY_REALTlME` 32 | header = fmt.Sprintf(requestText, salt, sign, text) 33 | return 34 | } 35 | 36 | func QueryYoudao(word string) { 37 | client := &http.Client{} 38 | var data = strings.NewReader(getYoudaoHeader(word)) 39 | req, err := http.NewRequest("POST", "https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule", data) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | req.Header.Set("Connection", "keep-alive") 44 | req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`) 45 | req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01") 46 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") 47 | req.Header.Set("X-Requested-With", "XMLHttpRequest") 48 | req.Header.Set("sec-ch-ua-mobile", "?0") 49 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") 50 | req.Header.Set("sec-ch-ua-platform", `"Windows"`) 51 | req.Header.Set("Origin", "https://fanyi.youdao.com") 52 | req.Header.Set("Sec-Fetch-Site", "same-origin") 53 | req.Header.Set("Sec-Fetch-Mode", "cors") 54 | req.Header.Set("Sec-Fetch-Dest", "empty") 55 | req.Header.Set("Referer", "https://fanyi.youdao.com/") 56 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") 57 | req.Header.Set("Cookie", "JSESSIONID=abc90mh6hMYs74Yg2uDcy; OUTFOX_SEARCH_USER_ID=-1937588996@10.110.96.158; OUTFOX_SEARCH_USER_ID_NCOO=1100955033.7040293; fanyi-ad-id=305838; fanyi-ad-closed=0; ___rl__test__cookies=1651915512175") 58 | resp, err := client.Do(req) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | defer resp.Body.Close() 63 | bodyText, err := ioutil.ReadAll(resp.Body) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | if resp.StatusCode != 200 { //防止返回错误 68 | log.Fatal("bad Status code:", resp.StatusCode, "body", string(bodyText)) 69 | } 70 | 71 | var dictResponse DictResponseYoudao 72 | err = json.Unmarshal(bodyText, &dictResponse) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | //打印翻译答案 78 | fmt.Println("translate from Youdao:") 79 | for _, item := range dictResponse.TranslateResult { 80 | for _, text := range item { 81 | fmt.Println(text.Tgt) 82 | } 83 | } 84 | for _, item := range dictResponse.SmartResult.Entries { 85 | fmt.Print(item) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lesson1/homework/simple_dict/src/type.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | //彩云翻译引擎对应的结构体类型 4 | type DictRequestCaiyun struct { 5 | TransType string `json:"trans_type"` 6 | Source string `json:"source"` 7 | UserID string `json:"user_id"` 8 | } 9 | 10 | type DictResponseCaiyun struct { 11 | Rc int `json:"rc"` 12 | Wiki struct { 13 | KnownInLaguages int `json:"known_in_laguages"` 14 | Description struct { 15 | Source string `json:"source"` 16 | Target interface{} `json:"target"` 17 | } `json:"description"` 18 | ID string `json:"id"` 19 | Item struct { 20 | Source string `json:"source"` 21 | Target string `json:"target"` 22 | } `json:"item"` 23 | ImageURL string `json:"image_url"` 24 | IsSubject string `json:"is_subject"` 25 | Sitelink string `json:"sitelink"` 26 | } `json:"wiki"` 27 | Dictionary struct { 28 | Prons struct { 29 | EnUs string `json:"en-us"` 30 | En string `json:"en"` 31 | } `json:"prons"` 32 | Explanations []string `json:"explanations"` 33 | Synonym []string `json:"synonym"` 34 | Antonym []string `json:"antonym"` 35 | WqxExample [][]string `json:"wqx_example"` 36 | Entry string `json:"entry"` 37 | Type string `json:"type"` 38 | Related []interface{} `json:"related"` 39 | Source string `json:"source"` 40 | } `json:"dictionary"` 41 | } 42 | 43 | //Deepl对应的结构体 44 | type DictResponseDeepl struct { 45 | Jsonrpc string `json:"jsonrpc"` 46 | ID int `json:"id"` 47 | Result struct { 48 | Translations []struct { 49 | Beams []struct { 50 | Sentences []struct { 51 | Text string `json:"text"` 52 | Ids []int `json:"ids"` 53 | } `json:"sentences"` 54 | NumSymbols int `json:"num_symbols"` 55 | } `json:"beams"` 56 | Quality string `json:"quality"` 57 | } `json:"translations"` 58 | TargetLang string `json:"target_lang"` 59 | SourceLang string `json:"source_lang"` 60 | SourceLangIsConfident bool `json:"source_lang_is_confident"` 61 | DetectedLanguages struct { 62 | EN float64 `json:"EN"` 63 | DE float64 `json:"DE"` 64 | FR float64 `json:"FR"` 65 | ES float64 `json:"ES"` 66 | PT float64 `json:"PT"` 67 | IT float64 `json:"IT"` 68 | NL float64 `json:"NL"` 69 | PL float64 `json:"PL"` 70 | RU float64 `json:"RU"` 71 | ZH float64 `json:"ZH"` 72 | JA float64 `json:"JA"` 73 | BG float64 `json:"BG"` 74 | CS float64 `json:"CS"` 75 | DA float64 `json:"DA"` 76 | EL float64 `json:"EL"` 77 | ET float64 `json:"ET"` 78 | FI float64 `json:"FI"` 79 | HU float64 `json:"HU"` 80 | LT float64 `json:"LT"` 81 | LV float64 `json:"LV"` 82 | RO float64 `json:"RO"` 83 | SK float64 `json:"SK"` 84 | SL float64 `json:"SL"` 85 | SV float64 `json:"SV"` 86 | Unsupported float64 `json:"unsupported"` 87 | } `json:"detectedLanguages"` 88 | } `json:"result"` 89 | } 90 | 91 | // Youdao 92 | type DictResponseYoudao struct { 93 | TranslateResult [][]struct { 94 | Tgt string `json:"tgt"` 95 | Src string `json:"src"` 96 | } `json:"translateResult"` 97 | ErrorCode int `json:"errorCode"` 98 | Type string `json:"type"` 99 | SmartResult struct { 100 | Entries []string `json:"entries"` 101 | Type int `json:"type"` 102 | } `json:"smartResult"` 103 | } 104 | -------------------------------------------------------------------------------- /lesson1/proxy/v2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //auth 阶段 4 | import ( 5 | "bufio" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | ) 11 | 12 | const ( 13 | socks5Ver = 0x05 14 | cmdBind = 0x01 15 | atypIPV4 = 0x01 16 | atypeHOST = 0x03 17 | atypeIPV6 = 0x04 18 | ) 19 | 20 | func main() { 21 | server, err := net.Listen("tcp", "0.0.0.0:1080") 22 | if err != nil { 23 | panic(err) 24 | } 25 | for { 26 | client, err := server.Accept() 27 | if err != nil { 28 | log.Printf("Accept failed %v", err) 29 | continue 30 | } 31 | fmt.Printf("连接成功! clent:%v \n", client.RemoteAddr()) 32 | go process(client) 33 | } 34 | } 35 | 36 | func process(conn net.Conn) { 37 | defer func() { 38 | conn.Close() 39 | fmt.Printf("连接断开! clent:%v \n", conn.RemoteAddr()) 40 | }() 41 | 42 | reader := bufio.NewReader(conn) 43 | err := auth(reader, conn) 44 | if err != nil { 45 | log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) 46 | } 47 | log.Println("auth success") 48 | } 49 | 50 | func auth(reader *bufio.Reader, conn net.Conn) (err error) { 51 | //协议版本 52 | ver, err := reader.ReadByte() 53 | if err != nil { 54 | return fmt.Errorf("read ver failed:%w", err) 55 | } 56 | if ver != socks5Ver { 57 | return fmt.Errorf("not supported ver:%v", ver) 58 | } 59 | 60 | //支持的方法数量 61 | methodSize, err := reader.ReadByte() 62 | if err != nil { 63 | return fmt.Errorf("read methodSize failed:%w", err) 64 | } 65 | //方法值 66 | method := make([]byte, methodSize) 67 | _, err = io.ReadFull(reader, method) 68 | if err != nil { 69 | return fmt.Errorf("read method failed %w", err) 70 | } 71 | log.Println("ver", ver, "method", method) 72 | 73 | //返回的内容表示SOCKS5通信,且无需认证 74 | _, err = conn.Write([]byte{socks5Ver, 0x00}) 75 | if err != nil { 76 | return fmt.Errorf("write failed:%w", err) 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /lesson1/proxy/v3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //请求阶段 4 | import ( 5 | "bufio" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | ) 13 | 14 | const socks5Ver = 0x05 15 | const cmdBind = 0x01 16 | const atypIPV4 = 0x01 17 | const atypeHOST = 0x03 18 | const atypeIPV6 = 0x04 19 | 20 | func main() { 21 | server, err := net.Listen("tcp", "127.0.0.1:1080") 22 | if err != nil { 23 | panic(err) 24 | } 25 | for { 26 | client, err := server.Accept() 27 | if err != nil { 28 | log.Printf("Accept failed %v", err) 29 | continue 30 | } 31 | go process(client) 32 | } 33 | } 34 | 35 | func process(conn net.Conn) { 36 | defer conn.Close() 37 | reader := bufio.NewReader(conn) 38 | err := auth(reader, conn) 39 | if err != nil { 40 | log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) 41 | return 42 | } 43 | err = connect(reader, conn) 44 | if err != nil { 45 | log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) 46 | return 47 | } 48 | } 49 | 50 | func auth(reader *bufio.Reader, conn net.Conn) (err error) { 51 | // +----+----------+----------+ 52 | // |VER | NMETHODS | METHODS | 53 | // +----+----------+----------+ 54 | // | 1 | 1 | 1 to 255 | 55 | // +----+----------+----------+ 56 | // VER: 协议版本,socks5为0x05 57 | // NMETHODS: 支持认证的方法数量 58 | // METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下: 59 | // X’00’ NO AUTHENTICATION REQUIRED 60 | // X’02’ USERNAME/PASSWORD 61 | 62 | ver, err := reader.ReadByte() 63 | if err != nil { 64 | return fmt.Errorf("read ver failed:%w", err) 65 | } 66 | if ver != socks5Ver { 67 | return fmt.Errorf("not supported ver:%v", ver) 68 | } 69 | methodSize, err := reader.ReadByte() 70 | if err != nil { 71 | return fmt.Errorf("read methodSize failed:%w", err) 72 | } 73 | method := make([]byte, methodSize) 74 | _, err = io.ReadFull(reader, method) 75 | if err != nil { 76 | return fmt.Errorf("read method failed:%w", err) 77 | } 78 | 79 | // +----+--------+ 80 | // |VER | METHOD | 81 | // +----+--------+ 82 | // | 1 | 1 | 83 | // +----+--------+ 84 | _, err = conn.Write([]byte{socks5Ver, 0x00}) 85 | if err != nil { 86 | return fmt.Errorf("write failed:%w", err) 87 | } 88 | return nil 89 | } 90 | 91 | func connect(reader *bufio.Reader, conn net.Conn) (err error) { 92 | // +----+-----+-------+------+----------+----------+ 93 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 94 | // +----+-----+-------+------+----------+----------+ 95 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 96 | // +----+-----+-------+------+----------+----------+ 97 | // VER 版本号,socks5的值为0x05 98 | // CMD 0x01表示CONNECT请求 99 | // RSV 保留字段,值为0x00 100 | // ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。 101 | // 0x01表示IPv4地址,DST.ADDR为4个字节 102 | // 0x03表示域名,DST.ADDR是一个可变长度的域名 103 | // DST.ADDR 一个可变长度的值 104 | // DST.PORT 目标端口,固定2个字节 105 | 106 | buf := make([]byte, 4) 107 | _, err = io.ReadFull(reader, buf) 108 | if err != nil { 109 | return fmt.Errorf("read header failed:%w", err) 110 | } 111 | ver, cmd, atyp := buf[0], buf[1], buf[3] 112 | if ver != socks5Ver { 113 | return fmt.Errorf("not supported ver:%v", ver) 114 | } 115 | if cmd != cmdBind { 116 | return fmt.Errorf("not supported cmd:%v", ver) 117 | } 118 | addr := "" 119 | switch atyp { 120 | case atypIPV4: 121 | _, err = io.ReadFull(reader, buf) 122 | if err != nil { 123 | return fmt.Errorf("read atyp failed:%w", err) 124 | } 125 | addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]) 126 | case atypeHOST: 127 | hostSize, err := reader.ReadByte() 128 | if err != nil { 129 | return fmt.Errorf("read hostSize failed:%w", err) 130 | } 131 | host := make([]byte, hostSize) 132 | _, err = io.ReadFull(reader, host) 133 | if err != nil { 134 | return fmt.Errorf("read host failed:%w", err) 135 | } 136 | addr = string(host) 137 | case atypeIPV6: 138 | return errors.New("IPv6: no supported yet") 139 | default: 140 | return errors.New("invalid atyp") 141 | } 142 | _, err = io.ReadFull(reader, buf[:2]) 143 | if err != nil { 144 | return fmt.Errorf("read port failed:%w", err) 145 | } 146 | port := binary.BigEndian.Uint16(buf[:2]) 147 | 148 | log.Println("dial", addr, port) 149 | 150 | // +----+-----+-------+------+----------+----------+ 151 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 152 | // +----+-----+-------+------+----------+----------+ 153 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 154 | // +----+-----+-------+------+----------+----------+ 155 | // VER socks版本,这里为0x05 156 | // REP Relay field,内容取值如下 X’00’ succeeded 157 | // RSV 保留字段 158 | // ATYPE 地址类型 159 | // BND.ADDR 服务绑定的地址 160 | // BND.PORT 服务绑定的端口DST.PORT 161 | _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) 162 | if err != nil { 163 | return fmt.Errorf("write failed: %w", err) 164 | } 165 | return nil 166 | } 167 | -------------------------------------------------------------------------------- /lesson1/proxy/v4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | ) 13 | 14 | const socks5Ver = 0x05 15 | const cmdBind = 0x01 16 | const atypIPV4 = 0x01 17 | const atypeHOST = 0x03 18 | const atypeIPV6 = 0x04 19 | 20 | func main() { 21 | server, err := net.Listen("tcp", "127.0.0.1:1080") 22 | if err != nil { 23 | panic(err) 24 | } 25 | for { 26 | client, err := server.Accept() 27 | if err != nil { 28 | log.Printf("Accept failed %v", err) 29 | continue 30 | } 31 | log.Printf("连接成功! clent:%v \n", client.RemoteAddr()) 32 | go process(client) 33 | } 34 | } 35 | 36 | func process(conn net.Conn) { 37 | defer func() { 38 | conn.Close() 39 | log.Printf("连接断开! clent:%v \n", conn.RemoteAddr()) 40 | }() 41 | reader := bufio.NewReader(conn) 42 | err := auth(reader, conn) 43 | if err != nil { 44 | log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) 45 | return 46 | } 47 | err = connect(reader, conn) 48 | if err != nil { 49 | log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err) 50 | return 51 | } 52 | } 53 | 54 | func auth(reader *bufio.Reader, conn net.Conn) (err error) { 55 | // +----+----------+----------+ 56 | // |VER | NMETHODS | METHODS | 57 | // +----+----------+----------+ 58 | // | 1 | 1 | 1 to 255 | 59 | // +----+----------+----------+ 60 | // VER: 协议版本,socks5为0x05 61 | // NMETHODS: 支持认证的方法数量 62 | // METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下: 63 | // X’00’ NO AUTHENTICATION REQUIRED 64 | // X’02’ USERNAME/PASSWORD 65 | 66 | ver, err := reader.ReadByte() 67 | if err != nil { 68 | return fmt.Errorf("read ver failed:%w", err) 69 | } 70 | if ver != socks5Ver { 71 | return fmt.Errorf("not supported ver:%v", ver) 72 | } 73 | methodSize, err := reader.ReadByte() 74 | if err != nil { 75 | return fmt.Errorf("read methodSize failed:%w", err) 76 | } 77 | method := make([]byte, methodSize) 78 | _, err = io.ReadFull(reader, method) 79 | if err != nil { 80 | return fmt.Errorf("read method failed:%w", err) 81 | } 82 | 83 | // +----+--------+ 84 | // |VER | METHOD | 85 | // +----+--------+ 86 | // | 1 | 1 | 87 | // +----+--------+ 88 | _, err = conn.Write([]byte{socks5Ver, 0x00}) 89 | if err != nil { 90 | return fmt.Errorf("write failed:%w", err) 91 | } 92 | return nil 93 | } 94 | 95 | //请求阶段完成后立马进行转发阶段(因为请求阶段就已经拿到了目标的服务器的ip和端口了 96 | func connect(reader *bufio.Reader, conn net.Conn) (err error) { 97 | // +----+-----+-------+------+----------+----------+ 98 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 99 | // +----+-----+-------+------+----------+----------+ 100 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 101 | // +----+-----+-------+------+----------+----------+ 102 | // VER 版本号,socks5的值为0x05 103 | // CMD 0x01表示CONNECT请求 104 | // RSV 保留字段,值为0x00 105 | // ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。 106 | // 0x01表示IPv4地址,DST.ADDR为4个字节 107 | // 0x03表示域名,DST.ADDR是一个可变长度的域名 108 | // DST.ADDR 一个可变长度的值 109 | // DST.PORT 目标端口,固定2个字节 110 | 111 | buf := make([]byte, 4) 112 | _, err = io.ReadFull(reader, buf) 113 | if err != nil { 114 | return fmt.Errorf("read header failed:%w", err) 115 | } 116 | ver, cmd, atyp := buf[0], buf[1], buf[3] 117 | if ver != socks5Ver { 118 | return fmt.Errorf("not supported ver:%v", ver) 119 | } 120 | if cmd != cmdBind { 121 | return fmt.Errorf("not supported cmd:%v", ver) 122 | } 123 | addr := "" 124 | switch atyp { 125 | case atypIPV4: 126 | _, err = io.ReadFull(reader, buf) 127 | if err != nil { 128 | return fmt.Errorf("read atyp failed:%w", err) 129 | } 130 | addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]) 131 | case atypeHOST: 132 | hostSize, err := reader.ReadByte() 133 | if err != nil { 134 | return fmt.Errorf("read hostSize failed:%w", err) 135 | } 136 | host := make([]byte, hostSize) 137 | _, err = io.ReadFull(reader, host) 138 | if err != nil { 139 | return fmt.Errorf("read host failed:%w", err) 140 | } 141 | addr = string(host) 142 | case atypeIPV6: 143 | return errors.New("IPv6: no supported yet") 144 | default: 145 | return errors.New("invalid atyp") 146 | } 147 | _, err = io.ReadFull(reader, buf[:2]) 148 | if err != nil { 149 | return fmt.Errorf("read port failed:%w", err) 150 | } 151 | port := binary.BigEndian.Uint16(buf[:2]) 152 | 153 | //通过net.Dial和目标服务器进行tcp连接 154 | dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port)) 155 | if err != nil { 156 | return fmt.Errorf("dial dst failed:%w", err) 157 | } 158 | defer dest.Close() 159 | log.Println("dial", addr, port) 160 | 161 | // +----+-----+-------+------+----------+----------+ 162 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 163 | // +----+-----+-------+------+----------+----------+ 164 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 165 | // +----+-----+-------+------+----------+----------+ 166 | // VER socks版本,这里为0x05 167 | // REP Relay field,内容取值如下 X’00’ succeeded 168 | // RSV 保留字段 169 | // ATYPE 地址类型 170 | // BND.ADDR 服务绑定的地址 171 | // BND.PORT 服务绑定的端口DST.PORT 172 | 173 | // 由于这里是同步过程,故是等到请求阶段完成后再往下进行的转发阶段 174 | _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) 175 | if err != nil { 176 | return fmt.Errorf("write failed: %w", err) 177 | } 178 | 179 | //context没用过,还不太清楚这里的用法 180 | ctx, cancel := context.WithCancel(context.Background()) 181 | defer cancel() 182 | 183 | //标准库的 io.copy 可以实现一个单向数据转发,双向转发的话,需要启动两个 goroutinue。 184 | go func() { 185 | _, _ = io.Copy(dest, reader) 186 | cancel() 187 | }() 188 | go func() { 189 | _, _ = io.Copy(conn, dest) 190 | cancel() 191 | }() 192 | 193 | <-ctx.Done() 194 | return nil 195 | } 196 | -------------------------------------------------------------------------------- /lesson1/simple_dict/word_dict.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | type DictRequest struct { 14 | TransType string `json:"trans_type"` 15 | Source string `json:"source"` 16 | UserID string `json:"user_id"` 17 | } 18 | 19 | type DictResponse struct { 20 | Rc int `json:"rc"` 21 | Wiki struct { 22 | KnownInLaguages int `json:"known_in_laguages"` 23 | Description struct { 24 | Source string `json:"source"` 25 | Target interface{} `json:"target"` 26 | } `json:"description"` 27 | ID string `json:"id"` 28 | Item struct { 29 | Source string `json:"source"` 30 | Target string `json:"target"` 31 | } `json:"item"` 32 | ImageURL string `json:"image_url"` 33 | IsSubject string `json:"is_subject"` 34 | Sitelink string `json:"sitelink"` 35 | } `json:"wiki"` 36 | Dictionary struct { 37 | Prons struct { 38 | EnUs string `json:"en-us"` 39 | En string `json:"en"` 40 | } `json:"prons"` 41 | Explanations []string `json:"explanations"` 42 | Synonym []string `json:"synonym"` 43 | Antonym []string `json:"antonym"` 44 | WqxExample [][]string `json:"wqx_example"` 45 | Entry string `json:"entry"` 46 | Type string `json:"type"` 47 | Related []interface{} `json:"related"` 48 | Source string `json:"source"` 49 | } `json:"dictionary"` 50 | } 51 | 52 | func query(word string) { 53 | client := &http.Client{} 54 | //var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`) 55 | request := DictRequest{TransType: "en2zh", Source: word} 56 | buf, err := json.Marshal(request) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | var data = bytes.NewReader(buf) 61 | req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | req.Header.Set("Connection", "keep-alive") 66 | req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`) 67 | req.Header.Set("sec-ch-ua-mobile", "?0") 68 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") 69 | req.Header.Set("app-name", "xy") 70 | req.Header.Set("Content-Type", "application/json;charset=UTF-8") 71 | req.Header.Set("Accept", "application/json, text/plain, */*") 72 | req.Header.Set("os-type", "web") 73 | req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") 74 | req.Header.Set("sec-ch-ua-platform", `"Windows"`) 75 | req.Header.Set("Origin", "https://fanyi.caiyunapp.com") 76 | req.Header.Set("Sec-Fetch-Site", "cross-site") 77 | req.Header.Set("Sec-Fetch-Mode", "cors") 78 | req.Header.Set("Sec-Fetch-Dest", "empty") 79 | req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") 80 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") 81 | resp, err := client.Do(req) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | defer resp.Body.Close() 86 | bodyText, err := ioutil.ReadAll(resp.Body) 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | if resp.StatusCode != 200 { //防止返回错误 91 | log.Fatal("bad Status code:", resp.StatusCode, "body", string(bodyText)) 92 | } 93 | var dictResponse DictResponse 94 | err = json.Unmarshal(bodyText, &dictResponse) 95 | if err != nil { 96 | log.Fatal(err) 97 | } 98 | 99 | fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs) 100 | for _, item := range dictResponse.Dictionary.Explanations { 101 | fmt.Println(item) 102 | } 103 | } 104 | 105 | func main() { 106 | if len(os.Args) != 2 { 107 | fmt.Fprint(os.Stderr, "usage: simpleDict [WORD] example: simpleDict hello") 108 | os.Exit(1) 109 | } 110 | word := os.Args[1] 111 | query(word) 112 | } 113 | -------------------------------------------------------------------------------- /lesson2/go-project-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/array.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import "fmt" 4 | 5 | func AppendInt() { 6 | intArray := [3]int64{1, 2, 3} 7 | func(arr [3]int64) { 8 | arr[2] = 4 9 | fmt.Println("inner func array:", arr) 10 | }(intArray) 11 | 12 | fmt.Println("outer func array:", intArray) 13 | } 14 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/array_test.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import "testing" 4 | 5 | func TestAppendInt(t *testing.T) { 6 | AppendInt() 7 | } 8 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/closure.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func closure() { 8 | for i := 0; i < 3; i++ { 9 | go func() { 10 | println(i) 11 | }() 12 | } 13 | time.Sleep(3 * time.Second) 14 | } 15 | 16 | func closure1() { 17 | for i := 0; i < 3; i++ { 18 | go func(j int) { 19 | println(j) 20 | }(i) 21 | } 22 | time.Sleep(3 * time.Second) 23 | } 24 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/closure_test.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import "testing" 4 | 5 | func TestClosure(t *testing.T) { 6 | closure() 7 | } 8 | 9 | func TestClosure1(t *testing.T) { 10 | closure1() 11 | } 12 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/json.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | func NumUnmarshal() { 10 | jsonStr := `{"id":1,"name":"Jerry"}` 11 | var res map[string]interface{} 12 | _ = json.Unmarshal([]byte(jsonStr), &res) 13 | fmt.Printf("%T\n", res["id"]) 14 | i := res["id"].(int64) 15 | fmt.Println(i) 16 | } 17 | 18 | func NumDecode() { 19 | jsonStr := `{"id":1,"name":"Jerry"}` 20 | var res map[string]interface{} 21 | decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr))) 22 | decoder.UseNumber() 23 | _ = decoder.Decode(&res) 24 | i, _ := res["id"].(json.Number).Int64() 25 | fmt.Println(i) 26 | } 27 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/json_test.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import "testing" 4 | 5 | func TestNumUnmarshal(t *testing.T) { 6 | NumUnmarshal() 7 | } 8 | 9 | func TestNumDecode(t *testing.T) { 10 | NumDecode() 11 | } 12 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/string.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import ( 4 | "unicode/utf8" 5 | ) 6 | 7 | func length() { 8 | str := "⬇汉字" 9 | println(len(str)) 10 | } 11 | 12 | func length1() { 13 | str := "⬇汉字" 14 | println(utf8.RuneCountInString(str)) 15 | } 16 | -------------------------------------------------------------------------------- /lesson2/go-project-example/attention/string_test.go: -------------------------------------------------------------------------------- 1 | package attention 2 | 3 | import "testing" 4 | 5 | func TestLength(t *testing.T) { 6 | length() 7 | } 8 | 9 | func TestLength1(t *testing.T) { 10 | length1() 11 | } 12 | -------------------------------------------------------------------------------- /lesson2/go-project-example/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acking-you/TraningCamp/d2cbb85abcf8b33b3a0cddca713586e214f8bdf9/lesson2/go-project-example/avatar.jpg -------------------------------------------------------------------------------- /lesson2/go-project-example/concurrence/channel.go: -------------------------------------------------------------------------------- 1 | package concurrence 2 | 3 | func CalSquare() { 4 | var src chan int 5 | src = make(chan int) 6 | dest := make(chan int, 3) 7 | go func() { 8 | defer close(src) 9 | for i := 0; i < 10; i++ { 10 | src <- i 11 | } 12 | }() 13 | go func() { 14 | defer close(dest) 15 | for i := range src { 16 | dest <- i * i 17 | } 18 | }() 19 | for i := range dest { 20 | println(i) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lesson2/go-project-example/concurrence/channel_test.go: -------------------------------------------------------------------------------- 1 | package concurrence 2 | 3 | import "testing" 4 | 5 | func TestCalSquare(t *testing.T) { 6 | CalSquare() 7 | } 8 | -------------------------------------------------------------------------------- /lesson2/go-project-example/concurrence/goroutine.go: -------------------------------------------------------------------------------- 1 | package concurrence 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func hello() { 9 | fmt.Println("hello") 10 | } 11 | func ManyGo() { 12 | var wg sync.WaitGroup 13 | wg.Add(5) 14 | for i := 0; i < 5; i++ { 15 | go func() { 16 | defer wg.Done() 17 | hello() 18 | }() 19 | } 20 | wg.Wait() 21 | } 22 | -------------------------------------------------------------------------------- /lesson2/go-project-example/concurrence/goroutine_test.go: -------------------------------------------------------------------------------- 1 | package concurrence 2 | 3 | import "testing" 4 | 5 | func TestManyGo(t *testing.T) { 6 | ManyGo() 7 | } 8 | -------------------------------------------------------------------------------- /lesson2/go-project-example/example.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `community` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */; 2 | USE `community`; 3 | DROP TABLE IF EXISTS `user`; 4 | CREATE TABLE `user` 5 | ( 6 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', 7 | `name` varchar(128) NOT NULL DEFAULT '' COMMENT '用户昵称', 8 | `avatar` varchar(128) NOT NULL DEFAULT '' COMMENT '头像', 9 | `level` int(10) NOT NULL DEFAULT 1 COMMENT '用户等级', 10 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 11 | `modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', 12 | PRIMARY KEY (`id`) 13 | ) ENGINE = InnoDB 14 | DEFAULT CHARSET = utf8mb4 COMMENT ='用户表'; 15 | 16 | INSERT INTO `user` 17 | VALUES (1, 'Jerry', '', 1, '2022-04-01 10:00:00', '2022-04-01 10:00:00'), 18 | (2, 'Tom', '', 2, '2022-04-01 10:00:00', '2022-04-01 10:00:00'); 19 | 20 | DROP TABLE IF EXISTS `topic`; 21 | CREATE TABLE `topic` 22 | ( 23 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', 24 | `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', 25 | `title` varchar(128) NOT NULL default '' COMMENT '标题', 26 | `content` text NOT NULL COMMENT '头像', 27 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 28 | PRIMARY KEY (`id`) 29 | ) ENGINE = InnoDB 30 | DEFAULT CHARSET = utf8mb4 COMMENT ='话题表'; 31 | 32 | INSERT INTO `topic` 33 | VALUES (1, 1, '青训营开课啦', '快到碗里来!', '2022-04-01 13:50:19'); 34 | 35 | DROP TABLE IF EXISTS `post`; 36 | CREATE TABLE `post` 37 | ( 38 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', 39 | `parent_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '父id', 40 | `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', 41 | `content` text NOT NULL COMMENT '头像', 42 | `digg_count` int(10) NOT NULL DEFAULT 0 COMMENT '点赞数', 43 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 44 | PRIMARY KEY (`id`), 45 | INDEX parent_id (`parent_id`) 46 | ) ENGINE = InnoDB 47 | DEFAULT CHARSET = utf8mb4 COMMENT ='回帖表'; 48 | INSERT INTO `post` 49 | VALUES (1, 1, 1, '举手报名!', 10, '2022-04-01 14:50:19'), 50 | (2, 1, 2, '举手报名+1', 20, '2022-04-01 14:51:19'); -------------------------------------------------------------------------------- /lesson2/go-project-example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Moonlight-Zhao/go-project-example 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986 // indirect 7 | github.com/gin-contrib/sse v0.1.0 // indirect 8 | github.com/gin-gonic/gin v1.3.0 // indirect 9 | github.com/golang/protobuf v1.5.2 // indirect 10 | github.com/jinzhu/now v1.1.5 // indirect 11 | github.com/json-iterator/go v1.1.12 // indirect 12 | github.com/kr/pretty v0.3.0 // indirect 13 | github.com/mattn/go-isatty v0.0.14 // indirect 14 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 15 | github.com/rogpeppe/go-internal v1.8.0 // indirect 16 | github.com/stretchr/testify v1.7.1 // indirect 17 | github.com/ugorji/go v1.2.7 // indirect 18 | go.uber.org/atomic v1.9.0 // indirect 19 | go.uber.org/multierr v1.8.0 // indirect 20 | go.uber.org/zap v1.21.0 21 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 22 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect 23 | google.golang.org/protobuf v1.28.0 // indirect 24 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 25 | gopkg.in/gin-gonic/gin.v1 v1.3.0 26 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 27 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 28 | gopkg.in/yaml.v2 v2.4.0 // indirect 29 | gorm.io/driver/mysql v1.3.3 30 | gorm.io/gorm v1.23.4 31 | ) 32 | -------------------------------------------------------------------------------- /lesson2/go-project-example/go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986 h1:6RbXPuVEF5+jeTXrKY/UA+rcK2w6vNIA9vUZ9MKeopc= 4 | github.com/bytedance/gopkg v0.0.0-20220509134931-d1878f638986/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 10 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 11 | github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= 12 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 13 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 14 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 15 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 16 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 17 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 18 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 19 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 20 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 21 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 22 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 23 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 24 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 25 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 26 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 27 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 28 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 29 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 30 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 31 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 32 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 33 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 34 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 35 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 36 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 37 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 38 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 41 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 42 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 43 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 44 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 45 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 46 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 47 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 48 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 49 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 50 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 51 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 52 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 53 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 54 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 55 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 56 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 57 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 58 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 59 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 60 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 61 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 62 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 63 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 64 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 65 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 66 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 67 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 68 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 69 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 70 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 71 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 72 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 73 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 74 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 75 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 76 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 77 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 78 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 79 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 80 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 81 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 82 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 83 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 84 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 85 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 90 | golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= 92 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 94 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 95 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 96 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 97 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 98 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 99 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 100 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 101 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 102 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 104 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 105 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 106 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 107 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 108 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 109 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 110 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 111 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 112 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 113 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 114 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 115 | gopkg.in/gin-gonic/gin.v1 v1.3.0 h1:DjAu49rN1YttQsOkVCPlAO3INcZNFT0IKsNVMk5MRT4= 116 | gopkg.in/gin-gonic/gin.v1 v1.3.0/go.mod h1:Eljh74A/zAvUOQ835v6ySeZ+5gQG6tKjbZTaZ9iWU3A= 117 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 118 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 119 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 120 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 121 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 122 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 123 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 124 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 126 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 127 | gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8= 128 | gorm.io/driver/mysql v1.3.3/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= 129 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 130 | gorm.io/gorm v1.23.4 h1:1BKWM67O6CflSLcwGQR7ccfmC4ebOxQrTfOQGRE9wjg= 131 | gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 132 | -------------------------------------------------------------------------------- /lesson2/go-project-example/handler/publish_post.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/Moonlight-Zhao/go-project-example/service" 7 | ) 8 | 9 | type PageData struct { 10 | Code int64 `json:"code"` 11 | Msg string `json:"msg"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | func QueryPageInfo(topicIdStr string) *PageData { 16 | //参数转换 17 | topicId, err := strconv.ParseInt(topicIdStr, 10, 64) 18 | if err != nil { 19 | return &PageData{ 20 | Code: -1, 21 | Msg: err.Error(), 22 | } 23 | } 24 | //获取service层结果 25 | pageInfo, err := service.QueryPageInfo(topicId) 26 | if err != nil { 27 | return &PageData{ 28 | Code: -1, 29 | Msg: err.Error(), 30 | } 31 | } 32 | return &PageData{ 33 | Code: 0, 34 | Msg: "success", 35 | Data: pageInfo, 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /lesson2/go-project-example/handler/query_page_info.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/Moonlight-Zhao/go-project-example/service" 5 | "strconv" 6 | ) 7 | 8 | func PublishPost(uidStr, topicIdStr, content string) *PageData { 9 | //参数转换 10 | uid, _ := strconv.ParseInt(uidStr, 10, 64) 11 | topic, _ := strconv.ParseInt(topicIdStr, 10, 64) 12 | //获取service层结果 13 | postId, err := service.PublishPost(topic, uid, content) 14 | if err != nil { 15 | return &PageData{ 16 | Code: -1, 17 | Msg: err.Error(), 18 | } 19 | } 20 | return &PageData{ 21 | Code: 0, 22 | Msg: "success", 23 | Data: map[string]int64{ 24 | "post_id": postId, 25 | }, 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lesson2/go-project-example/repository/db_init.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "gorm.io/driver/mysql" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | var db *gorm.DB 9 | 10 | func Init() error { 11 | var err error 12 | dsn := "root:08898247@tcp(127.0.0.1:3306)/?charset=utf8mb4&parseTime=True&loc=Local" 13 | db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) 14 | return err 15 | } 16 | -------------------------------------------------------------------------------- /lesson2/go-project-example/repository/post.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/Moonlight-Zhao/go-project-example/util" 5 | "gorm.io/gorm" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type Post struct { 11 | Id int64 `gorm:"column:id"` 12 | ParentId int64 `gorm:"parent_id"` 13 | UserId int64 `gorm:"column:user_id"` 14 | Content string `gorm:"column:content"` 15 | DiggCount int32 `gorm:"column:digg_count"` 16 | CreateTime time.Time `gorm:"column:create_time"` 17 | } 18 | 19 | func (Post) TableName() string { 20 | return "post" 21 | } 22 | 23 | type PostDao struct { 24 | } 25 | 26 | var postDao *PostDao 27 | var postOnce sync.Once 28 | 29 | func NewPostDaoInstance() *PostDao { 30 | postOnce.Do( 31 | func() { 32 | postDao = &PostDao{} 33 | }) 34 | return postDao 35 | } 36 | 37 | func (*PostDao) QueryPostById(id int64) (*Post, error) { 38 | var post Post 39 | err := db.Where("id = ?", id).Find(&post).Error 40 | if err == gorm.ErrRecordNotFound { 41 | return nil, nil 42 | } 43 | if err != nil { 44 | util.Logger.Error("find post by id err:" + err.Error()) 45 | return nil, err 46 | } 47 | return &post, nil 48 | } 49 | 50 | func (*PostDao) QueryPostByParentId(parentId int64) ([]*Post, error) { 51 | var posts []*Post 52 | err := db.Where("parent_id = ?", parentId).Find(&posts).Error 53 | if err != nil { 54 | util.Logger.Error("find posts by parent_id err:" + err.Error()) 55 | return nil, err 56 | } 57 | return posts, nil 58 | } 59 | 60 | func (*PostDao) CreatePost(post *Post) error { 61 | if err := db.Create(post).Error; err != nil { 62 | util.Logger.Error("insert post err:" + err.Error()) 63 | return err 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /lesson2/go-project-example/repository/topic.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/Moonlight-Zhao/go-project-example/util" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Topic struct { 10 | Id int64 `gorm:"column:id"` 11 | UserId int64 `gorm:"column:user_id"` 12 | Title string `gorm:"column:title"` 13 | Content string `gorm:"column:content"` 14 | CreateTime time.Time `gorm:"column:create_time"` 15 | } 16 | 17 | func (Topic) TableName() string { 18 | return "topic" 19 | } 20 | 21 | type TopicDao struct { 22 | } 23 | 24 | var topicDao *TopicDao 25 | var topicOnce sync.Once 26 | 27 | func NewTopicDaoInstance() *TopicDao { 28 | topicOnce.Do( 29 | func() { 30 | topicDao = &TopicDao{} 31 | }) 32 | return topicDao 33 | } 34 | 35 | func (*TopicDao) QueryTopicById(id int64) (*Topic, error) { 36 | var topic Topic 37 | err := db.Where("id = ?", id).Find(&topic).Error 38 | if err != nil { 39 | util.Logger.Error("find topic by id err:" + err.Error()) 40 | return nil, err 41 | } 42 | return &topic, nil 43 | } 44 | -------------------------------------------------------------------------------- /lesson2/go-project-example/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/Moonlight-Zhao/go-project-example/util" 5 | "gorm.io/gorm" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type User struct { 11 | Id int64 `gorm:"column:id"` 12 | Name string `gorm:"column:name"` 13 | Avatar string `gorm:"column:avatar"` 14 | Level int `gorm:"column:level"` 15 | CreateTime time.Time `gorm:"column:create_time"` 16 | ModifyTime time.Time `gorm:"column:modify_time"` 17 | } 18 | 19 | func (User) TableName() string { 20 | return "user" 21 | } 22 | 23 | type UserDao struct { 24 | } 25 | 26 | var userDao *UserDao 27 | var userOnce sync.Once 28 | 29 | func NewUserDaoInstance() *UserDao { 30 | userOnce.Do( 31 | func() { 32 | userDao = &UserDao{} 33 | }) 34 | return userDao 35 | } 36 | 37 | func (*UserDao) QueryUserById(id int64) (*User, error) { 38 | var user User 39 | err := db.Where("id = ?", id).Find(&user).Error 40 | if err == gorm.ErrRecordNotFound { 41 | return nil, nil 42 | } 43 | if err != nil { 44 | util.Logger.Error("find user by id err:" + err.Error()) 45 | return nil, err 46 | } 47 | return &user, nil 48 | } 49 | 50 | func (*UserDao) MQueryUserById(ids []int64) (map[int64]*User, error) { 51 | var users []*User 52 | err := db.Where("id in (?)", ids).Find(&users).Error 53 | if err != nil { 54 | util.Logger.Error("batch find user by id err:" + err.Error()) 55 | return nil, err 56 | } 57 | userMap := make(map[int64]*User) 58 | for _, user := range users { 59 | userMap[user.Id] = user 60 | } 61 | return userMap, nil 62 | } 63 | -------------------------------------------------------------------------------- /lesson2/go-project-example/service/publish_post.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "github.com/Moonlight-Zhao/go-project-example/repository" 6 | "time" 7 | "unicode/utf8" 8 | ) 9 | 10 | func PublishPost(topicId, userId int64, content string) (int64, error) { 11 | return NewPublishPostFlow(topicId, userId, content).Do() 12 | } 13 | 14 | func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow { 15 | return &PublishPostFlow{ 16 | userId: userId, 17 | content: content, 18 | topicId: topicId, 19 | } 20 | } 21 | 22 | type PublishPostFlow struct { 23 | userId int64 24 | content string 25 | topicId int64 26 | 27 | postId int64 28 | } 29 | 30 | func (f *PublishPostFlow) Do() (int64, error) { 31 | if err := f.checkParam(); err != nil { 32 | return 0, err 33 | } 34 | if err := f.publish(); err != nil { 35 | return 0, err 36 | } 37 | return f.postId, nil 38 | } 39 | 40 | func (f *PublishPostFlow) checkParam() error { 41 | if f.userId <= 0 { 42 | return errors.New("userId id must be larger than 0") 43 | } 44 | if utf8.RuneCountInString(f.content) >= 500 { 45 | return errors.New("content length must be less than 500") 46 | } 47 | return nil 48 | } 49 | 50 | func (f *PublishPostFlow) publish() error { 51 | post := &repository.Post{ 52 | ParentId: f.topicId, 53 | UserId: f.userId, 54 | Content: f.content, 55 | CreateTime: time.Now(), 56 | } 57 | if err := repository.NewPostDaoInstance().CreatePost(post); err != nil { 58 | return err 59 | } 60 | f.postId = post.Id 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /lesson2/go-project-example/service/publish_post_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/Moonlight-Zhao/go-project-example/repository" 5 | "github.com/Moonlight-Zhao/go-project-example/util" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestMain(m *testing.M) { 11 | if err := repository.Init(); err != nil { 12 | os.Exit(1) 13 | } 14 | if err := util.InitLogger(); err != nil { 15 | os.Exit(1) 16 | } 17 | m.Run() 18 | } 19 | 20 | func TestPublishPost(t *testing.T) { 21 | 22 | type args struct { 23 | topicId int64 24 | userId int64 25 | content string 26 | } 27 | tests := []struct { 28 | name string 29 | args args 30 | wantErr bool 31 | }{ 32 | { 33 | name: "测试发布回帖", 34 | args: args{ 35 | topicId: 1, 36 | userId: 2, 37 | content: "再次回帖", 38 | }, 39 | wantErr: false, 40 | }, 41 | } 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | _, err := PublishPost(tt.args.topicId, tt.args.userId, tt.args.content) 45 | if (err != nil) != tt.wantErr { 46 | t.Errorf("PublishPost() error = %v, wantErr %v", err, tt.wantErr) 47 | return 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lesson2/go-project-example/service/query_page_info.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/Moonlight-Zhao/go-project-example/repository" 7 | "sync" 8 | ) 9 | 10 | type TopicInfo struct { 11 | Topic *repository.Topic 12 | User *repository.User 13 | } 14 | 15 | type PostInfo struct { 16 | Post *repository.Post 17 | User *repository.User 18 | } 19 | 20 | type PageInfo struct { 21 | TopicInfo *TopicInfo 22 | PostList []*PostInfo 23 | } 24 | 25 | func QueryPageInfo(topicId int64) (*PageInfo, error) { 26 | return NewQueryPageInfoFlow(topicId).Do() 27 | } 28 | 29 | func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow { 30 | return &QueryPageInfoFlow{ 31 | topicId: topId, 32 | } 33 | } 34 | 35 | type QueryPageInfoFlow struct { 36 | topicId int64 37 | pageInfo *PageInfo 38 | 39 | topic *repository.Topic 40 | posts []*repository.Post 41 | userMap map[int64]*repository.User 42 | } 43 | 44 | func (f *QueryPageInfoFlow) Do() (*PageInfo, error) { 45 | if err := f.checkParam(); err != nil { 46 | return nil, err 47 | } 48 | if err := f.prepareInfo(); err != nil { 49 | return nil, err 50 | } 51 | if err := f.packPageInfo(); err != nil { 52 | return nil, err 53 | } 54 | return f.pageInfo, nil 55 | } 56 | 57 | func (f *QueryPageInfoFlow) checkParam() error { 58 | if f.topicId <= 0 { 59 | return errors.New("topic id must be larger than 0") 60 | } 61 | return nil 62 | } 63 | 64 | func (f *QueryPageInfoFlow) prepareInfo() error { 65 | //获取topic信息 66 | var wg sync.WaitGroup 67 | wg.Add(2) 68 | var topicErr, postErr error 69 | go func() { 70 | defer wg.Done() 71 | topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId) 72 | if err != nil { 73 | topicErr = err 74 | return 75 | } 76 | f.topic = topic 77 | }() 78 | //获取post列表 79 | go func() { 80 | defer wg.Done() 81 | posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId) 82 | if err != nil { 83 | postErr = err 84 | return 85 | } 86 | f.posts = posts 87 | }() 88 | wg.Wait() 89 | if topicErr != nil { 90 | return topicErr 91 | } 92 | if postErr != nil { 93 | return postErr 94 | } 95 | //获取用户信息 96 | uids := []int64{f.topic.Id} 97 | for _, post := range f.posts { 98 | uids = append(uids, post.Id) 99 | } 100 | userMap, err := repository.NewUserDaoInstance().MQueryUserById(uids) 101 | if err != nil { 102 | return err 103 | } 104 | f.userMap = userMap 105 | return nil 106 | } 107 | 108 | func (f *QueryPageInfoFlow) packPageInfo() error { 109 | //topic info 110 | userMap := f.userMap 111 | topicUser, ok := userMap[f.topic.UserId] 112 | if !ok { 113 | return errors.New("has no topic user info") 114 | } 115 | //post list 116 | postList := make([]*PostInfo, 0) 117 | for _, post := range f.posts { 118 | postUser, ok := userMap[post.UserId] 119 | if !ok { 120 | return errors.New("has no post user info for " + fmt.Sprint(post.UserId)) 121 | } 122 | postList = append(postList, &PostInfo{ 123 | Post: post, 124 | User: postUser, 125 | }) 126 | } 127 | f.pageInfo = &PageInfo{ 128 | TopicInfo: &TopicInfo{ 129 | Topic: f.topic, 130 | User: topicUser, 131 | }, 132 | PostList: postList, 133 | } 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /lesson2/go-project-example/service/query_page_info_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestQueryPageInfo(t *testing.T) { 8 | type args struct { 9 | topicId int64 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | wantErr bool 15 | }{ 16 | { 17 | name: "查询页面", 18 | args: args{ 19 | topicId: 1, 20 | }, 21 | wantErr: false, 22 | }, 23 | } 24 | for _, tt := range tests { 25 | t.Run(tt.name, func(t *testing.T) { 26 | _, err := QueryPageInfo(tt.args.topicId) 27 | if (err != nil) != tt.wantErr { 28 | t.Errorf("QueryPageInfo() error = %v, wantErr %v", err, tt.wantErr) 29 | return 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lesson2/go-project-example/sever.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Moonlight-Zhao/go-project-example/handler" 5 | "github.com/Moonlight-Zhao/go-project-example/repository" 6 | "github.com/Moonlight-Zhao/go-project-example/util" 7 | "gopkg.in/gin-gonic/gin.v1" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | if err := Init(); err != nil { 13 | os.Exit(-1) 14 | } 15 | r := gin.Default() 16 | 17 | r.Use(gin.Logger()) 18 | 19 | r.GET("/ping", func(c *gin.Context) { 20 | c.JSON(200, gin.H{ 21 | "message": "pong", 22 | }) 23 | }) 24 | 25 | r.GET("/community/page/get/:id", func(c *gin.Context) { 26 | topicId := c.Param("id") 27 | data := handler.QueryPageInfo(topicId) 28 | c.JSON(200, data) 29 | }) 30 | 31 | r.POST("/community/post/do", func(c *gin.Context) { 32 | uid, _ := c.GetPostForm("uid") 33 | topicId, _ := c.GetPostForm("topic_id") 34 | content, _ := c.GetPostForm("content") 35 | data := handler.PublishPost(uid, topicId, content) 36 | c.JSON(200, data) 37 | }) 38 | 39 | err := r.Run() 40 | if err != nil { 41 | return 42 | } 43 | } 44 | 45 | func Init() error { 46 | if err := repository.Init(); err != nil { 47 | return err 48 | } 49 | if err := util.InitLogger(); err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /lesson2/go-project-example/util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "go.uber.org/zap" 4 | 5 | var Logger *zap.Logger 6 | 7 | func InitLogger() error { 8 | var err error 9 | Logger, err = zap.NewProduction() 10 | if err != nil { 11 | return err 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /lesson2/homework/benchmark/server_select.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "github.com/bytedance/gopkg/lang/fastrand" 5 | "math/rand" 6 | ) 7 | 8 | var ServerIndex [10]int 9 | 10 | // InitServerIndex 初始化服务器的描述符 11 | func InitServerIndex() { 12 | for i := 0; i < 10; i++ { 13 | ServerIndex[i] = i + 100 14 | } 15 | } 16 | 17 | // RandSelect 随机选择一个服务器 18 | func RandSelect() int { 19 | return ServerIndex[rand.Intn(10)] 20 | } 21 | 22 | // FastRandSelect 用外部的fast包 23 | func FastRandSelect() int { 24 | return ServerIndex[fastrand.Intn(10)] 25 | } 26 | -------------------------------------------------------------------------------- /lesson2/homework/benchmark/server_select_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import "testing" 4 | 5 | func BenchmarkSelect(b *testing.B) { 6 | InitServerIndex() 7 | b.ResetTimer() 8 | for i := 0; i < b.N; i++ { 9 | FastRandSelect() 10 | } 11 | } 12 | 13 | func BenchmarkSelectParallel(b *testing.B) { 14 | InitServerIndex() 15 | b.ResetTimer() 16 | b.RunParallel(func(pb *testing.PB) { 17 | for pb.Next() { 18 | FastRandSelect() 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /lesson2/homework/controller/publish_post.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/ACking-you/TraningCamp/lesson2/homework/service" 7 | ) 8 | 9 | func PublishPost(uidStr, topicIdStr, content string) *PageData { 10 | //参数转换 11 | uid, _ := strconv.ParseInt(uidStr, 10, 64) 12 | 13 | topic, _ := strconv.ParseInt(topicIdStr, 10, 64) 14 | //获取service层结果 15 | postId, err := service.PublishPost(topic, uid, content) 16 | if err != nil { 17 | return &PageData{ 18 | Code: 1, 19 | Msg: err.Error(), 20 | } 21 | } 22 | return &PageData{ 23 | Code: 0, 24 | Msg: "success", 25 | Data: map[string]int64{ 26 | "post_id": postId, 27 | }, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lesson2/homework/controller/query_page_info.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/ACking-you/TraningCamp/lesson2/homework/service" 5 | "strconv" 6 | ) 7 | 8 | // PageData 最终发送给客户端的json数据对应的结构体,我们需要错误码,以及对应错误码对应的消息,最后再是数据(用空接口实现泛型 9 | type PageData struct { 10 | Code int64 `json:"code"` 11 | Msg string `json:"msg"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | // QueryPageINfo 真正和客户端进行交互的函数,需要注意客户端发来的流量都是字符串形式 16 | func QueryPageINfo(topicIdStr string) *PageData { 17 | pageId, err := strconv.Atoi(topicIdStr) 18 | if err != nil { 19 | return &PageData{Code: 1, Msg: err.Error(), Data: nil} 20 | } 21 | pageInfo, err := service.QueryPageInfo(int64(pageId)) 22 | if err != nil { 23 | return &PageData{Code: 2, Msg: err.Error(), Data: nil} 24 | } 25 | return &PageData{Code: 0, Msg: "success", Data: pageInfo} 26 | } 27 | -------------------------------------------------------------------------------- /lesson2/homework/data/post: -------------------------------------------------------------------------------- 1 | {"id":1,"parent_id":1,"content":"小姐姐快来1","create_time":1650437616,"user_id":1} 2 | {"id":2,"parent_id":1,"content":"小姐姐快来2","create_time":1650437617,"user_id":2} 3 | {"id":3,"parent_id":1,"content":"小姐姐快来3","create_time":1650437618,"user_id":13} 4 | {"id":5,"parent_id":1,"content":"测试内容嗨嗨嗨嗨","create_time":1652073758,"user_id":2} -------------------------------------------------------------------------------- /lesson2/homework/data/topic: -------------------------------------------------------------------------------- 1 | {"id":1,"title":"青训营来啦!","content":"小姐姐,快到碗里来~","create_time":1650437625} 2 | {"id":2,"title":"青训营来啦!","content":"小哥哥,快到碗里来~","create_time":1650437640} -------------------------------------------------------------------------------- /lesson2/homework/repository/data_init.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | //处理数据的初始化过程,此处是没有用数据库,用文件把它一次性读到的内存 4 | 5 | import ( 6 | "bufio" 7 | "encoding/json" 8 | "os" 9 | ) 10 | 11 | var ( 12 | topicIndexMap map[int64]*Topic //暴露给外界查询的mapping 13 | postIndexMap map[int64][]*Post 14 | LastPostId int64 //存下最大的一个PostId,方便Post id的生成 15 | ) 16 | 17 | func Init(filePath string) error { 18 | if err := initTopicIndexMap(filePath); err != nil { 19 | return err 20 | } 21 | if err := initPostIndexMap(filePath); err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | func initTopicIndexMap(filePath string) error { 28 | open, err := os.Open(filePath + "topic") 29 | if err != nil { 30 | return err 31 | } 32 | scanner := bufio.NewScanner(open) 33 | topicTmpMap := make(map[int64]*Topic) 34 | //一行一行的进行扫描 35 | for scanner.Scan() { 36 | text := scanner.Text() 37 | var topic Topic 38 | if err := json.Unmarshal([]byte(text), &topic); err != nil { 39 | return err 40 | } 41 | topicTmpMap[topic.Id] = &topic 42 | } 43 | topicIndexMap = topicTmpMap 44 | return nil 45 | } 46 | 47 | func initPostIndexMap(filePath string) error { 48 | open, err := os.Open(filePath + "post") 49 | if err != nil { 50 | return err 51 | } 52 | scanner := bufio.NewScanner(open) 53 | postTmpMap := make(map[int64][]*Post) 54 | for scanner.Scan() { 55 | text := scanner.Text() 56 | var post Post 57 | if err := json.Unmarshal([]byte(text), &post); err != nil { 58 | return err 59 | } 60 | if LastPostId < post.Id { //更新lastPostId 61 | LastPostId = post.Id 62 | } 63 | posts, ok := postTmpMap[post.ParentId] 64 | if !ok { 65 | postTmpMap[post.ParentId] = []*Post{&post} 66 | continue 67 | } 68 | posts = append(posts, &post) 69 | postTmpMap[post.ParentId] = posts //根据parentId进行索引存储 70 | } 71 | postIndexMap = postTmpMap 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /lesson2/homework/repository/data_save.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | func Save(filePath string) error { 9 | if err := SaveTopicIndexMap(filePath); err != nil { 10 | return err 11 | } 12 | if err := SavePostIndexMap(filePath); err != nil { 13 | return err 14 | } 15 | return nil 16 | } 17 | 18 | func SaveTopicIndexMap(filePath string) error { 19 | open, err := os.OpenFile(filePath+"post", os.O_WRONLY|os.O_CREATE, 0666) 20 | if err != nil { 21 | return err 22 | } 23 | writer := open 24 | 25 | for _, v := range topicIndexMap { 26 | data, err := json.Marshal(v) 27 | if err != nil { 28 | return err 29 | } 30 | data = append(data, '\n') 31 | writer.Write(data) 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func SavePostIndexMap(filePath string) error { 38 | open, err := os.OpenFile(filePath+"post", os.O_WRONLY|os.O_CREATE, 0666) 39 | if err != nil { 40 | return err 41 | } 42 | writer := open 43 | 44 | for _, v := range postIndexMap { 45 | for _, post := range v { 46 | data, err := json.Marshal(*post) 47 | if err != nil { 48 | return err 49 | } 50 | data = append(data, '\n') 51 | writer.Write(data) 52 | } 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /lesson2/homework/repository/post.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "errors" 7 | "os" 8 | "sync" 9 | ) 10 | 11 | type Post struct { 12 | Id int64 `json:"id"` 13 | ParentId int64 `json:"parent_id"` 14 | Content string `json:"content"` 15 | CreateTime int64 `json:"create_time"` 16 | UserId int64 `json:"user_id"` 17 | } 18 | 19 | type PostDao struct { 20 | } 21 | 22 | var ( 23 | postDao *PostDao 24 | postOnce sync.Once 25 | ) 26 | 27 | func NewPostDao() *PostDao { 28 | postOnce.Do(func() { 29 | postDao = new(PostDao) 30 | }) 31 | return postDao 32 | } 33 | 34 | func (_ *PostDao) QueryPostsFromParentId(id int64) []*Post { 35 | return postIndexMap[id] 36 | } 37 | 38 | func (d *PostDao) AddPost(post *Post) error { 39 | //加锁保证同时请求的并发安全 40 | lock := sync.Mutex{} 41 | lock.Lock() 42 | posts, ok := postIndexMap[post.ParentId] 43 | if !ok { 44 | return errors.New("post invalid,not exist parent id") 45 | } 46 | //注意更新map里的数据,go切片并不像C++里的Vector,可能append后操作的就不是同一片 底层数组了 47 | 48 | postIndexMap[post.ParentId] = append(posts, post) 49 | err := fileDataInsertPost("./lesson2/homework/data/", post) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | lock.Unlock() 55 | return nil 56 | } 57 | 58 | func fileDataInsertPost(filePath string, post *Post) error { 59 | open, err := os.OpenFile(filePath+"post", os.O_WRONLY|os.O_APPEND, 0666) 60 | if err != nil { 61 | return err 62 | } 63 | writer := bufio.NewWriter(open) 64 | 65 | data, err := json.Marshal(*post) 66 | if err != nil { 67 | return err 68 | } 69 | writer.WriteString("\r\n") 70 | writer.Write(data) 71 | writer.Flush() 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /lesson2/homework/repository/topic.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import "sync" 4 | 5 | type Topic struct { 6 | Id int64 `json:"id"` 7 | Title string `json:"title"` 8 | Content string `json:"content"` 9 | CreateTime int64 `json:"create_time"` 10 | } 11 | 12 | // TopicDao 定义一个空的结构体,为了让请求的函数不会被重名之类的(相当于给函数加个namespace) 13 | type TopicDao struct { 14 | } 15 | 16 | // 定义全局变量实现单例模式,其中sync.Once类型能够通过Do方法使得代码块只执行一次 17 | var ( 18 | topicDao *TopicDao 19 | topicOnce sync.Once 20 | ) 21 | 22 | func NewTopicDao() *TopicDao { 23 | topicOnce.Do(func() { 24 | topicDao = new(TopicDao) //初始化指针 25 | }) 26 | return topicDao 27 | } 28 | 29 | func (_ *TopicDao) QueryTopicFromId(id int64) *Topic { 30 | return topicIndexMap[id] 31 | } 32 | -------------------------------------------------------------------------------- /lesson2/homework/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ACking-you/TraningCamp/lesson2/homework/controller" 5 | "github.com/ACking-you/TraningCamp/lesson2/homework/repository" 6 | "gopkg.in/gin-gonic/gin.v1" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | //最后再通过gin框架搭建服务器 12 | 13 | func main() { 14 | //准备数据 15 | if err := Init("./lesson2/homework/data/"); err != nil { 16 | os.Exit(-1) 17 | } 18 | 19 | //注册路由 20 | 21 | r := gin.Default() 22 | r.GET("me:id", func(c *gin.Context) { 23 | topicId := c.Param("id") 24 | topicId = strings.TrimLeft(topicId, ":,") 25 | println(topicId) 26 | data := controller.QueryPageINfo(topicId) 27 | c.JSONP(200, data) 28 | }) 29 | 30 | r.POST("/post/do", func(c *gin.Context) { 31 | uid, _ := c.GetPostForm("uid") 32 | println(uid) 33 | topicId, _ := c.GetPostForm("topic_id") 34 | println(topicId) 35 | content, _ := c.GetPostForm("content") 36 | println(content) 37 | data := controller.PublishPost(uid, topicId, content) 38 | c.JSON(200, data) 39 | }) 40 | err := r.Run() 41 | if err != nil { 42 | return 43 | } 44 | } 45 | 46 | func Init(filepath string) error { 47 | err := repository.Init(filepath) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /lesson2/homework/service/publish_post.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "github.com/ACking-you/TraningCamp/lesson2/homework/repository" 6 | "time" 7 | "unicode/utf8" 8 | ) 9 | 10 | func PublishPost(topicId, userId int64, content string) (int64, error) { 11 | return NewPublishPostFlow(topicId, userId, content).Do() 12 | } 13 | 14 | func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow { 15 | return &PublishPostFlow{ 16 | userId: userId, 17 | content: content, 18 | topicId: topicId, 19 | } 20 | } 21 | 22 | type PublishPostFlow struct { 23 | userId int64 24 | content string 25 | topicId int64 26 | 27 | postId int64 28 | } 29 | 30 | func (f *PublishPostFlow) Do() (int64, error) { 31 | if err := f.checkParam(); err != nil { 32 | return 0, err 33 | } 34 | if err := f.publish(); err != nil { 35 | return 0, err 36 | } 37 | return f.postId, nil 38 | } 39 | 40 | func (f *PublishPostFlow) checkParam() error { 41 | if f.userId <= 0 { 42 | return errors.New("userId id must be larger than 0") 43 | } 44 | if utf8.RuneCountInString(f.content) >= 500 { 45 | return errors.New("content length must be less than 500") 46 | } 47 | return nil 48 | } 49 | 50 | func (f *PublishPostFlow) publish() error { 51 | post := &repository.Post{ 52 | ParentId: f.topicId, 53 | UserId: f.userId, 54 | Content: f.content, 55 | CreateTime: time.Now().Unix(), 56 | } 57 | repository.LastPostId++ 58 | post.Id = repository.LastPostId 59 | if err := repository.NewPostDao().AddPost(post); err != nil { 60 | return err 61 | } 62 | f.postId = post.Id 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /lesson2/homework/service/query_page_info.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "github.com/ACking-you/TraningCamp/lesson2/homework/repository" 6 | "sync" 7 | ) 8 | 9 | // PageInfo 一个页面的信息包括,topic和它上面的post言论 10 | type PageInfo struct { 11 | Topic *repository.Topic 12 | PostList []*repository.Post 13 | } 14 | 15 | func QueryPageInfo(id int64) (*PageInfo, error) { 16 | return NewQueryPageInfoFlow(id).Do() 17 | } 18 | 19 | // QueryPageInfoFlow 为了防止Service构造层的高耦合度的构造PageInfo,可以用像如下通过流式处理 20 | type QueryPageInfoFlow struct { 21 | topicId int64 22 | pageInfo *PageInfo 23 | 24 | topic *repository.Topic 25 | posts []*repository.Post 26 | } 27 | 28 | func NewQueryPageInfoFlow(id int64) *QueryPageInfoFlow { 29 | return &QueryPageInfoFlow{topicId: id} 30 | } 31 | 32 | // Do 整个组装过程 33 | func (q *QueryPageInfoFlow) Do() (*PageInfo, error) { 34 | //对id进行合法性验证 35 | if err := q.checkNum(); err != nil { 36 | return nil, err 37 | } 38 | //准备好生成PageInfo的数据 39 | if err := q.prepareInfo(); err != nil { 40 | return nil, err 41 | } 42 | //打包最终的PageInfo 43 | if err := q.packPageInfo(); err != nil { 44 | return nil, err 45 | } 46 | return q.pageInfo, nil 47 | } 48 | 49 | func (q *QueryPageInfoFlow) checkNum() error { 50 | if q.topicId <= 0 { 51 | return errors.New("topic must larger than 0") 52 | } 53 | return nil 54 | } 55 | 56 | //这两个过程,由于是毫无关联的,可以用go协程进行并发处理 57 | func (q *QueryPageInfoFlow) prepareInfo() error { 58 | var wg sync.WaitGroup 59 | wg.Add(2) 60 | //获取Topic 61 | go func() { 62 | defer wg.Done() 63 | q.topic = repository.NewTopicDao().QueryTopicFromId(q.topicId) 64 | }() 65 | //获取Posts 66 | go func() { 67 | defer wg.Done() 68 | q.posts = repository.NewPostDao().QueryPostsFromParentId(q.topicId) 69 | }() 70 | 71 | wg.Wait() 72 | return nil 73 | } 74 | 75 | //更新最终的PageInfo 76 | func (q *QueryPageInfoFlow) packPageInfo() error { 77 | q.pageInfo = &PageInfo{ 78 | Topic: q.topic, 79 | PostList: q.posts, 80 | } 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /lesson2/homework/service/query_page_info_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/ACking-you/TraningCamp/lesson2/homework/repository" 5 | "github.com/stretchr/testify/assert" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | //TestMain 是在测试进行前初始化运行的代码,且最后退出也是由他进行 11 | func TestMain(m *testing.M) { 12 | repository.Init("../data/") 13 | 14 | os.Exit(m.Run()) 15 | } 16 | 17 | func TestQueryPageInfo(t *testing.T) { 18 | pageInfo, _ := QueryPageInfo(1) 19 | assert.NotEqual(t, nil, pageInfo) 20 | assert.Equal(t, "小姐姐,快到碗里来~", pageInfo.Topic.Content) 21 | } 22 | --------------------------------------------------------------------------------