第七届字节跳动青训营 Day1 - 实战案例 | 豆包MarsCode AI刷题

121 阅读5分钟

一、猜数字游戏

要求游戏开始时生成一个随机数,每轮由玩家输入一个数字,之后程序反馈是大于答案还是小于答案,知道猜对结果游戏结束。

主要是 math/rand 库的应用,以及熟悉一下标准输入输出。

按照题目,首先我们生成一个随机结果,注意随机数种子的设置。

rand.NewSource(time.Now().UnixNano())
answer := rand.Intn(100)

每轮游戏反馈结果:

if input > answer {
    fmt.Print("Your answer is too high, please try again: ")
    _, _ = fmt.Scan(&input)
} else {
    fmt.Print("Your answer is too low, please try again: ")
    _, _ = fmt.Scan(&input)
}

显然游戏不会只进行一轮,需要循环直到猜对答案,完整代码如下:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.NewSource(time.Now().UnixNano())
    answer := rand.Intn(100)
    var input int
    fmt.Print("Enter a number: ")
    _, _ = fmt.Scan(&input)
    for input != answer {
       if input > answer {
          fmt.Print("Your answer is too high, please try again: ")
          _, _ = fmt.Scan(&input)
       } else {
          fmt.Print("Your answer is too low, please try again: ")
          _, _ = fmt.Scan(&input)
       }
    }
    fmt.Println("You are right! The answer is ", answer)
}

二、命令行字典

要求模拟请求彩云小译的api实现一个命令行字典程序。

主要是对 net/httpencoding/json 库的使用,以及结构体embedding,标签之类的技巧的练习。

我们进入 fanyi.caiyunapp.com/f12 -> NetWork,然后输入任意内容等待翻译,可以得到翻译的 api 为https://api.interpreter.caiyunai.com/v1/dict

以及请求方式为POST,请求json格式和返回json格式如下:

image.png

image.png

分析一下返回json,不难发现其中返回格式里我们需要的信息为: entry, explanations, prons, wqx_example

所以我们定义结构体如下(只需要设置需要的字段即可):

type Req struct {
    Source    string `json:"source"`
    TransType string `json:"trans_type"`
}

func NewReq(source, transType string) *Req {
    return &Req{
       Source:    source,
       TransType: transType,
    }
}

type Dictionary struct {
    Entry        string            `json:"entry"`
    Explanations []string          `json:"explanations"`
    Prons        map[string]string `json:"prons"`
    WqxExample   [][2]string       `json:"wqx_example"`
}

type Resp struct {
    Dictionary `json:"dictionary"`
}

然后模拟请求解析json即可,注意设置header信息,需要设置X-Authorization 字段,否则请求不成功。

func Request(req *Req) (*Resp, error) {
    bytesData, err := json.Marshal(*req)
    if err != nil {
       return nil, err
    }
    request, err := http.NewRequest(Method, DictApi, bytes.NewBuffer(bytesData))
    if err != nil {
       return nil, err
    }
    request.Header.Set("User-Agent", UserAgent)
    request.Header.Set("Content-Type", "application/json")
    request.Header.Set("X-Authorization", Authorization)
    client := &http.Client{}
    response, err := client.Do(request)
    if err != nil {
       return nil, err
    }
    defer func(Body io.ReadCloser) {
       err := Body.Close()
       if err != nil {
          return
       }
    }(response.Body)
    if response.StatusCode != 200 {
       return nil, errors.New(response.Status)
    }
    body, err := io.ReadAll(response.Body)
    if err != nil {
       return nil, err
    }
    var resp Resp
    err = json.Unmarshal(body, &resp)
    if err != nil {
       return nil, err
    }
    return &resp, nil
}

最后处理一下命令行参数和结果输出,完整代码如下:

package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "net/http"
    "os"
)

const (
    DictApi       = "https://api.interpreter.caiyunai.com/v1/dict"
    Method        = "POST"
    UserAgent     = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
    Authorization = "token:qgemv4jr1y38jyq6vhvi"
)

type Req struct {
    Source    string `json:"source"`
    TransType string `json:"trans_type"`
}

func NewReq(source, transType string) *Req {
    return &Req{
       Source:    source,
       TransType: transType,
    }
}

type Dictionary struct {
    Entry        string            `json:"entry"`
    Explanations []string          `json:"explanations"`
    Prons        map[string]string `json:"prons"`
    WqxExample   [][2]string       `json:"wqx_example"`
}

type Resp struct {
    Dictionary `json:"dictionary"`
}

func Request(req *Req) (*Resp, error) {
    bytesData, err := json.Marshal(*req)
    if err != nil {
       return nil, err
    }
    request, err := http.NewRequest(Method, DictApi, bytes.NewBuffer(bytesData))
    if err != nil {
       return nil, err
    }
    request.Header.Set("User-Agent", UserAgent)
    request.Header.Set("Content-Type", "application/json")
    request.Header.Set("X-Authorization", Authorization)
    client := &http.Client{}
    response, err := client.Do(request)
    if err != nil {
       return nil, err
    }
    defer func(Body io.ReadCloser) {
       err := Body.Close()
       if err != nil {
          return
       }
    }(response.Body)
    if response.StatusCode != 200 {
       return nil, errors.New(response.Status)
    }
    body, err := io.ReadAll(response.Body)
    if err != nil {
       return nil, err
    }
    var resp Resp
    err = json.Unmarshal(body, &resp)
    if err != nil {
       return nil, err
    }
    return &resp, nil
}

func main() {
    args := os.Args[1:]
    if len(args) != 2 {
       fmt.Println("Usage: go run main.go <source> <trans_type>")
       return
    }
    req := NewReq(args[0], args[1])
    resp, err := Request(req)
    if err != nil {
       fmt.Println(err)
       return
    }
    fmt.Println(resp.Dictionary.Entry)
    for _, explanation := range resp.Dictionary.Explanations {
       fmt.Println(explanation)
    }
    for k, v := range resp.Dictionary.Prons {
       fmt.Println(k, ":", v)
    }
    for _, wqxExample := range resp.Dictionary.WqxExample {
       fmt.Println(wqxExample[0], wqxExample[1])
    }
}

三、Socks5 代理

大概按照如下流程实现即可。

  1. 建立连接: 客户端连接到代理服务器的端口(1080)。
  2. 认证阶段: 客户端发送 SOCKS5 版本和支持的认证方法。 代理服务器告诉客户端不需要认证。
  3. 请求阶段: 客户端发送请求,包含目标地址和端口。 服务器解析请求,获取目标服务器的信息。
  4. 连接目标服务器: 代理服务器与目标服务器建立连接。
  5. 响应客户端: 服务器通知客户端连接成功,准备传输数据。
  6. 数据转发: 数据在客户端和目标服务器之间来回传输,代理服务器负责中转。

完整代码:

package main

import (
    "bufio"
    "context"
    "encoding/binary"
    "errors"
    "fmt"
    "io"
    "log"
    "net"
)

const (
    socks5Ver = 0x05
    cmdBind   = 0x01
    atypeIPV4 = 0x01
    atypeHOST = 0x03
    atypeIPV6 = 0x04
)

func process(conn net.Conn) {
    defer func(conn net.Conn) {
       err := conn.Close()
       if err != nil {
          return
       }
    }(conn)
    reader := bufio.NewReader(conn)
    err := auth(reader, conn)
    if err != nil {
       log.Printf("client %v auth error: %v", conn.RemoteAddr(), err)
       return
    }
    log.Printf("client %v auth success", conn.RemoteAddr())
    err = connect(reader, conn)
    if err != nil {
       log.Printf("client %v connect error: %v", conn.RemoteAddr(), err)
       return
    }
}

func auth(reader *bufio.Reader, conn net.Conn) (err error) {
    ver, err := reader.ReadByte()
    if err != nil {
       return fmt.Errorf("read ver: %w", err)
    }
    if ver != socks5Ver {
       return fmt.Errorf("not supported socks5 ver: %v", ver)
    }
    methodSize, err := reader.ReadByte()
    if err != nil {
       return fmt.Errorf("read method size: %w", err)
    }
    method := make([]byte, methodSize)
    _, err = io.ReadFull(reader, method)
    if err != nil {
       return fmt.Errorf("read method: %w", err)
    }
    log.Println("ver", ver, "method", method)
    _, err = conn.Write([]byte{socks5Ver, 0x00})
    if err != nil {
       return fmt.Errorf("write socks5 ver: %w", err)
    }
    return nil
}

func connect(reader *bufio.Reader, conn net.Conn) (err error) {
    buf := make([]byte, 4)
    _, err = io.ReadFull(reader, buf)
    if err != nil {
       return fmt.Errorf("read header: %w", err)
    }
    ver, cmd, atyp := buf[0], buf[1], buf[3]
    if ver != socks5Ver {
       return fmt.Errorf("not supported ver: %v", ver)
    }
    if cmd != cmdBind {
       return fmt.Errorf("not supported cmd: %v", ver)
    }
    addr := ""
    switch atyp {
    case atypeIPV4:
       _, err = io.ReadFull(reader, buf)
       if err != nil {
          return fmt.Errorf("read ityp: %w", err)
       }
       addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
    case atypeHOST:
       hostSize, err := reader.ReadByte()
       if err != nil {
          return fmt.Errorf("read host size: %w", err)
       }
       host := make([]byte, hostSize)
       _, err = io.ReadFull(reader, host)
       if err != nil {
          return fmt.Errorf("read host: %w", err)
       }
       addr = string(host)
    case atypeIPV6:
       return errors.New("not supported yet")
    default:
       return fmt.Errorf("invalid atyp: %v", atyp)
    }
    _, err = io.ReadFull(reader, buf[:2])
    if err != nil {
       return fmt.Errorf("read port: %w", err)
    }
    port := binary.BigEndian.Uint16(buf[:2])
    dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
    if err != nil {
       return fmt.Errorf("dial dest: %w", err)
    }
    defer func(dest net.Conn) {
       err := dest.Close()
       if err != nil {
          return
       }
    }(dest)
    log.Println("dial", addr, port)
    _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
    if err != nil {
       return fmt.Errorf("write failed: %w", err)
    }
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go func() {
       _, _ = io.Copy(dest, reader)
       cancel()
    }()
    go func() {
       _, _ = io.Copy(conn, dest)
       cancel()
    }()
    <-ctx.Done()
    return nil
}

func main() {
    server, err := net.Listen("tcp", ":1080")
    if err != nil {
       panic(err)
    }
    defer func(server net.Listener) {
       err := server.Close()
       if err != nil {
          return
       }
    }(server)
    for {
       client, err := server.Accept()
       if err != nil {
          log.Printf("accept error: %v", err)
          continue
       }
       go process(client)
    }
}
  • 常量定义:
    • SOCKS5 版本和命令类型等常量。
  • process 函数:
    • 处理每个连接。
    • 调用 auth 进行认证。
    • 调用 connect 处理连接请求。
  • auth 函数:
    • 验证 SOCKS5 版本。
    • 读取并忽略客户端支持的认证方法。
    • 返回成功响应(不需要认证)。
  • connect 函数:
    • 读取请求的版本、命令、地址类型。
    • 根据地址类型解析目标地址。
    • 读取端口号。
    • 建立到目标服务器的连接。
    • 返回成功响应给客户端。
    • 开启两个协程进行数据转发。
  • main 函数:
    • 监听 1080 端口。
    • 接受客户端连接并调用 process。