工程实践(二) | 青训营笔记

56 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第3天

从现在起go语言学习正式进入工程实践环节

在进一步实战前我们先来回忆以下之前学过的基础语法,完成几个相关的小demo

guessing-game

经典的猜数字游戏,有几个关注点

  • 在使用rand之前要设置随机数种子,否则每一次都会生成相同的随机数序列。一般使用程序启动的时间戳来初始化。
  • 目标:实现用户输入输出,并理解析成数字。
  • 每个程序执行的时候都会打开几个文件,stdin stdout stderr等,stdin文件可以用os.Stdin来得到。然后直接操作这个文件很不方便,我们会用bufio.NewReader把一个文件转换成一个reader变量。reader变量上会有很多用来操作一个流的操作,我们可以用它的ReadString方法来读取一行。
  • ReadString返回的结果包含结尾的换行符,我们把它去掉,再转换成数字。

最终代码:

 package main
 ​
 import (
     "bufio"
     "fmt"
     "math/rand"
     "os"
     "strconv"
     "strings"
     "time"
 )
 ​
 func main() {
     maxNum := 100
     rand.Seed(time.Now().UnixNano())
     secretNumber := rand.Intn(maxNum)
     fmt.Println("The secret number is ", secretNumber)
 ​
     fmt.Println("Please input your guess")
     reader := bufio.NewReader(os.Stdin)
     for {
         input, err := reader.ReadString('\n')
         if err != nil {
             fmt.Println("An error occured while reading input. Please try again", err)
             continue
         }
         input = strings.Trim(input, "\r\n")
 ​
         guess, err := strconv.Atoi(input)
         if err != nil {
             fmt.Println("Invalid input. Please enter an integer value")
             continue
         }
         fmt.Println("You guess is", guess)
         if guess > secretNumber {
             fmt.Println("Your guess is bigger than the secret number. Please try again")
         } else if guess < secretNumber {
             fmt.Println("Your guess is smaller than the secret number. Please try again")
         } else {
             fmt.Println("Correct, you Legend!")
             break
         }
     }
 }
 ​

在线词典

curl命令是一个利用URL规则在命令行下工作的文件传输工具。使用一种受支持的协议,从远程服务器传输数据,或将数据传输到远程服务器。默认情况下,已安装在macOS和大多数Linux发行版上。curl支持包括HTTP、HTTPS、ftp等众多协议,还支持POST、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征。在进行web后台程序开发测试过程中,常常会需

bash和cmd的curl不一样

两个很好用的网站用于自动代码生成:

curlconverter.com/go/(由bash curl 生成

oktools.net/json2go (由JSON 生成

核心代码:

 func query(word string) {
     client := &http.Client{}
     request := DictRequest{TransType: "en2zh", Source: word}
     buf, err := json.Marshal(request)//序列化成json
     if err != nil {
         log.Fatal(err)
     }
     var data = bytes.NewReader(buf)
     req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
     if err != nil {
         log.Fatal(err)
     }
     //req.Header.Set
     
     resp, err := client.Do(req)//响应
     if err != nil {
         log.Fatal(err)
     }
     defer resp.Body.Close()
     bodyText, err := ioutil.ReadAll(resp.Body)
     if err != nil {
         log.Fatal(err)
     }
     if resp.StatusCode != 200 {
         log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
     }
     var dictResponse DictResponse
     err = json.Unmarshal(bodyText, &dictResponse)//反序列化
     if err != nil {
         log.Fatal(err)
     }
     fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
     for _, item := range dictResponse.Dictionary.Explanations {
         fmt.Println(item)
     }
 }

SOCKS5代理

作用:

某些企业的内网为了确保安全性,有很严格的防火墙策略,副作用就是访问某些资源很麻烦,

socks5相当于在防火墙上开了个口子,让授权的用户可以通过单个端口去访问内部的所有资源。

在爬虫的时候,爬取过程中很容易会遇到IP访问频率超过限制,这个时候很多人就会去上网找一些代理IP池,这些代理IP池里的很多代理协议就是socks5。

正常浏览器访问一个网站,如果不经过代理服务器的:

  1. 先和对方的网站建立TCP连接
  2. 然后三次握手
  3. 然后发起HTTP请求
  4. 然后服务返回HTTP响应。

如果设置代理服务器,流程变复杂,变为:

  1. 首先浏览器和socks5代理建立TCP连接,代理再和真正的服务器建立TCP连接,分为四个阶段
  2. 握手阶段:浏览器会向socks5代理发送请求,内容包括一个协议的版本号,还有支持的认证的种类,socks5服务器会选中一个认证方式,返回给浏览器。如果返回的是00的话就代表不需要认证,返回其他类型的话会开始认证流程。
  3. 认证阶段:有点复杂暂略。
  4. 请求阶段:认证通过后浏览器会向socks5服务器发起请求,主要信息还是包括版本号和请求的类型,一般主要是connection请求,就代表代理服务器和要某个域名或者某个IP地址某个端口建立TCP连接。代理服务器收到响应后,会真正和后端服务器建立连接,然后返回一个响应。
  5. relay阶段:此时浏览器会发送正常请求,然后代理服务器收到请求后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。然后实际上代理服务器并不关心流量的细节,可以是HTTP流量,也可以是其他TCP流量。这个就是socks5协议的工作原理。

青训营社区话题页

青训营社区话题页forum.juejin.cn/youthcamp/p…

需求描述:

  1. 展示话题(标题,文字描述)和回帖列表
  2. 仅实现一个本地web服务
  3. 话题和回帖数据用文件存储(暂时不用数据库

具体分析:

E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。

image-20230206234611590

image-20230206234557510

分层结构

熟悉的MVC模式

image-20230206234918812

  • 数据层:数据Model,外部数据的增删改查
  • 逻辑层:业务Entity,处理核心业务逻辑输出
  • 视图层:视图view,处理和外部的交互逻辑

组件及技术点

代码开发

Repository(数据层)

以topic为例:

 package repository
 ​
 import (
     "sync"
 )
 ​
 type Topic struct {
     Id         int64  `json:"id"`
     Title      string `json:"title"`
     Content    string `json:"content"`
     CreateTime int64  `json:"create_time"`
 }
 type TopicDao struct {
 }
 ​
 var (
     topicDao  *TopicDao
     topicOnce sync.Once//适用于高并发场景下只执行一次的场景,减少存储的浪费
 )
 ​
 func NewTopicDaoInstance() *TopicDao {
     topicOnce.Do(//单例
         func() {
             topicDao = &TopicDao{}
         })
     return topicDao
 }
 func (*TopicDao) QueryTopicById(id int64) *Topic {
     return topicIndexMap[id]//模仿索引操作
 }
 ​

Service层(逻辑层)

 func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
     if err := f.checkParam(); err != nil {
         return nil, err
     }
     if err := f.prepareInfo(); err != nil {
         return nil, err
     }
     if err := f.packPageInfo(); err != nil {
         return nil, err
     }
     return f.pageInfo, nil
 }
 ​
 func (f *QueryPageInfoFlow) checkParam() error {
     if f.topicId <= 0 {
         return errors.New("topic id must be larger than 0")
     }
     return nil
 }

Controller(视图层)

  • 构建View对象
  • 业务错误码