├── .gitignore
├── 00-docs
├── Modules.MD
└── Recommendations-and-rules.MD
├── 01-intro
├── demoapp
│ ├── cmd
│ │ └── app
│ │ │ └── main.go
│ └── pkg
│ │ └── stringutils
│ │ ├── stringutils.go
│ │ └── stringutils_test.go
└── hello
│ ├── hello
│ └── main.go
├── 02-syntax
├── 1-basic
│ ├── basic.go
│ └── basic_test.go
├── 2-names
│ └── pkg
│ │ └── names
│ │ ├── names.go
│ │ └── names_test.go
├── 3-array_slice_map
│ ├── array_slice_map.go
│ └── array_slice_map_test.go
├── 4-pointers
│ └── pointers.go
├── 4-struct
│ └── struct.go
├── 5-methods_ifaces
│ ├── methods_ifaces.go
│ └── methods_ifaces_test.go
├── 6-errors
│ └── errors.go
└── 7-channels
│ ├── channels.go
│ └── channels_test.go
├── 03-algorithms
├── 1-search
│ ├── search.go
│ └── search_test.go
├── 2-recursion
│ └── recursion.go
├── 3-dynamic
│ └── dynamic.go
└── 4-graph
│ ├── graph.go
│ └── graph_test.go
├── 04-datastructs
├── 0-arrays
│ └── arrays.go
├── 1-list
│ ├── list.go
│ └── list_test.go
└── 2-bst
│ └── bst.go
├── 05-io
├── 1-net_io
│ ├── net_io.go
│ └── net_io_test.go
├── 2-writer-example
│ ├── strings.txt
│ └── writer.go
├── 3-bufio_example
│ └── bufio.go
├── 4-files
│ └── files.go
├── 5-input
│ └── input.go
├── 6-flags
│ └── flags.go
└── 7-json_serialize
│ └── json.go
├── 06-oop
├── 1-methods
│ └── methods.go
├── 2-dip
│ ├── ifaces.go
│ └── ifaces_test.go
├── 3-embedding
│ └── embedding.go
├── 4-constructor
│ ├── constructor.go
│ └── constructor_test.go
└── 5-hw
│ ├── hw.go
│ └── hw_test.go
├── 07-testing
├── 1-simple
│ ├── simple.go
│ └── simple_test.go
├── 2-testmain
│ ├── testmain.go
│ └── testmain_test.go
├── 3-table
│ ├── table.go
│ └── table_test.go
├── 4-refactoring
│ ├── refactoring.go
│ └── refactoring_test.go
├── 5-db
│ ├── db.go
│ └── db_test.go
├── 6-handler
│ ├── handler.go
│ └── handler_test.go
├── 7-tdd
│ ├── tdd.go
│ └── tdd_test.go
├── 8-benchmarks
│ ├── search.go
│ └── search_test.go
└── flags
│ └── flag_test.go
├── 08-prof_debug
├── 1-bench_profile
│ ├── search.go
│ └── search_test.go
├── 2-app_profile
│ └── app-profile.go
├── 3-debug
│ └── two_sum.go
└── 4-trace
│ ├── trace_cpu
│ └── trace.go
│ └── trace_mem
│ └── trace.go
├── 09-interfaces
├── 1-polymorph
│ └── polymorph.go
├── 2-quiz
│ └── quiz.go
├── 3-assertion
│ └── assertion.go
├── 4-type_switch
│ └── switch.go
├── 5-important
│ └── important.go
├── 6-generics
│ └── generics.go
└── 7-DIP
│ └── dip.go
├── 10-concurrency
├── 1-goroutine
│ └── goroutine.go
├── 10-pattern-pipeline
│ └── pipeline.go
├── 11-context
│ └── context.go
├── 2-chan
│ └── chan.go
├── 3-chan_select
│ └── select.go
├── 4-waitgroup
│ └── sync.go
├── 5-shared_mem
│ └── shared.go
├── 6-common_errors
│ └── errors.go
├── 7-atomic
│ └── atomic.go
├── 8-pattern-fan-out
│ └── fan-out.go
└── 9-pattern-fan-in
│ └── fan-in.go
├── 11-network
├── 1-daytime
│ ├── client
│ │ └── daytime-client.go
│ └── server
│ │ └── daytime-server.go
├── 2-echo
│ └── echo.go
├── 3-deadline
│ └── deadline.go
└── 4-testing
│ ├── timeserver.go
│ └── timeserver_test.go
├── 12-web-apps
├── 1-http
│ ├── client
│ │ └── simple-client.go
│ └── server
│ │ └── simple-server.go
├── 2-custom_server
│ └── custom-server.go
├── 3-gorilla_mux
│ └── gorilla.go
├── 4-template
│ └── template.go
├── 5-testing
│ ├── testing.go
│ └── testing_test.go
└── hw
│ └── main.go
├── 13-api
├── 1-api
│ ├── api.go
│ ├── api_test.go
│ ├── middleware.go
│ └── server.go
└── 2-api
│ ├── cmd
│ └── server
│ │ └── server.go
│ └── pkg
│ └── api
│ ├── api.go
│ ├── api_test.go
│ ├── books.go
│ ├── books_test.go
│ ├── jwt.go
│ ├── jwt_test.go
│ ├── middleware.go
│ ├── middleware_test.go
│ ├── session.go
│ └── session_test.go
├── 14-RPC
├── 1-Go-RPC
│ ├── cmd
│ │ ├── client
│ │ │ └── rpc-client.go
│ │ └── server
│ │ │ └── rpc-server.go
│ └── pkg
│ │ └── books
│ │ └── books.go
└── 2-gRPC
│ ├── books_proto
│ ├── books.pb.go
│ ├── books.proto
│ └── books_grpc.pb.go
│ ├── client
│ └── grpc-client.go
│ └── server
│ └── grpc-server.go
├── 15-sql
└── books_db
│ ├── 1-schema.sql
│ └── 2-data.sql
├── 16-db-apps
├── 1-database-sql
│ └── database-sql.go
├── 2-pgx
│ ├── pgx.go
│ └── pgx_test.go
├── pkg
│ ├── api
│ │ └── api.go
│ └── db
│ │ ├── db.go
│ │ ├── memsql
│ │ └── memsql.go
│ │ └── pgsql
│ │ └── pgsql.go
└── schema.sql
├── 17-system-design
├── SOLID
│ ├── 1-SRP
│ │ └── srp.go
│ ├── 2-OCP
│ │ ├── ocp.go
│ │ ├── ocp_refactored.go
│ │ ├── ocp_refactored_test.go
│ │ └── ocp_test.go
│ ├── 3-LSP
│ │ └── lsp.go
│ ├── 4-ISP
│ │ └── isp.go
│ └── 5-DIP
│ │ └── dip.go
└── app
│ ├── cmd
│ ├── appclient
│ │ └── appclient.go
│ └── appserver
│ │ └── appserver.go
│ └── internal
│ ├── api
│ └── api.go
│ ├── db
│ └── db.go
│ ├── models
│ └── models.go
│ └── server
│ └── server.go
├── 18-microservices
└── microservice
│ ├── build
│ ├── Dockerfile
│ ├── deployment.yaml
│ └── service.yaml
│ ├── cmd
│ └── app
│ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ └── pkg
│ └── api
│ └── api.go
├── 19-queue
├── 1-kafka
│ └── kafka.go
└── docker-compose.yml
├── 20-NoSQL
├── 1-mongo
│ └── mongo.go
├── 2-KVStore
│ ├── cmd
│ │ └── app
│ │ │ └── app.go
│ └── pkg
│ │ └── kvdb
│ │ └── kvdb.go
├── 3-redis
│ └── redis.go
└── docker-compose.yml
├── 21-interview
├── 01-basics.go
├── 01-basics_test.go
├── 02-channels.go
└── 02-channels_test.go
├── GoSearch
├── cmd
│ └── gosearch
│ │ └── gosearh.go
└── pkg
│ └── crawler
│ ├── crawler.go
│ ├── membot
│ └── membot.go
│ └── spider
│ ├── spider.go
│ └── spider_test.go
├── README.md
├── file.txt
├── final.md
├── go.mod
├── go.sum
└── lynks
├── memcache
├── cmd
│ └── server
│ │ └── main.go
└── pkg
│ └── redis
│ └── redis.go
└── shortener
├── cmd
└── server
│ └── main.go
└── pkg
└── urls
└── urls.go
/.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 | .vscode
15 | .idea
--------------------------------------------------------------------------------
/00-docs/Modules.MD:
--------------------------------------------------------------------------------
1 | ## Модули ##
2 |
3 | Модули традиционно вызывают много вопросов у слушателей курса. Авторы Go написали серию статей по модулям: https://go.dev/blog/using-go-modules, однако в этой заметке я постараюсь привести базовую инструкцию по правильному использованию модулей во время обучения.
4 |
5 | Модуль - это проект. Для нас проектом является весь курс, поэтому в своих репозиториях мы должны создать только один модуль в корне каталога нашего репозитория, там же где и каталог `.git`.
6 |
7 | Ваша структура каталогов домашних заданий должна примерно повторять структуру этого репозитория. То есть требуется создать корневой каталог (например `go-core-4`), в нём создать модуль с помощью, например, `go mod init go-core-4`. Внутри этого каталога нужно создавать отдельные каталоги для каждого урока.
8 | Пример структуры каталогов:
9 | ```
10 | ├───00-docs
11 | ├───01-intro
12 | │ ├───demoapp
13 | │ │ ├───cmd
14 | │ │ │ └───app
15 | │ │ └───pkg
16 | │ │ └───stringutils
17 | │ └───hello
18 | ├───02-syntax
19 | │
20 | │ go.mod
21 | │ README.md
22 | ```
23 | При создании модуля создаётся файл go.mod, в котором указано имя модуля, который мы ввели как аргумент команды `go mod init`. Например, `go.mod` этого репозитория начинается с: `module go-core-4`.
24 |
25 | Название модуля является псевдонимом корневого каталога проекта и именно с этого названия начинаются пути импорта пакетов, находящихся в каталогах внутри каталога модуля. После имени модуля для каждого пакета идёт структура каталогов для данного пакета.
26 | Например, в первом занятии у нас есть путь импорта: `"go-core-4/01-intro/demoapp/pkg/stringutils"`. Этот путь начинается с имени модуля, после чего идет путь до каталога с пакетом. То есть этот пакет находится в каталоге `/01-intro/demoapp/pkg/stringutils`
27 |
--------------------------------------------------------------------------------
/00-docs/Recommendations-and-rules.MD:
--------------------------------------------------------------------------------
1 | ## Правила и рекомендации для оформления кода домашних заданий. Версия 0.3.
2 |
3 | **Формат ответа на ДЗ**
4 | Решение ДЗ должно быть в отдельном каталоге и оформлено в виде Pull Request в ветку `master`. Каждое решение должно быть в виде отдельного PR. Ответом на ДЗ является ссылка на PR. PR не должен содержать изменений, только добавления.
5 |
6 | **Структура каталогов с домашними работами**
7 | Ответы на домашние задания лучше поместить в одном каталоге с похожими именами: `homework-01`, `homework-02` и т.д. На весь проект следует создать один модуль.
8 | Пример:
9 | ```
10 | ├───homework-01
11 | │
12 | ├───homework-02
13 | │ │ ├───cmd
14 | │ │ │ └───app
15 | │ │ │ main.go
16 | │ │ │
17 | │ │ └───pkg
18 | │ │ └───spider
19 | │ │ spider.go
20 | │
21 | ├───homework-03
22 | │
23 | │ go.mod
24 | │ go.sum
25 | │ README.md
26 | ```
27 |
28 | **Структура каталогов Go-приложения**
29 | Проект должен иметь каталоги `pkg` и `cmd`. В каталоге `pkg` содержатся подключаемые пакеты. В каталоге `cmd` содержатся исполняемые пакеты.
30 |
31 | **Каталог пакета**
32 | Каждый пакет должен быть в отдельном каталоге. Имя пакета всегда совпадает с именем каталога.
33 |
34 | **Аварийное завершение работы программы в неисполняемом пакете**
35 | Подключаемый пакет никогда не должен аварийно завершать работу программы (`log.Fatal(), os.Exit(1)` и т.д.). Начало и завершение работы программы должно происходить в каталоге `main`.
36 |
37 | **Язык комментариев и текстовых сообщений**
38 | Для выполнения заданий можно писать комментарии и сообщения на русском или английском языках. Выбирайте тот, на котором вы можете писать без серьёзных ошибок.
39 |
40 | **Комментарии**
41 | 1. Не пишите очевидных комментариев.
42 | 2. Комментарий не должен отвечать на вопрос "что?", он должен отвечать на вопрос "зачем?".
43 | 3. Если требуется комментарий для того чтобы понять что делает ваш код, то код, вероятно, стоит переписать.
44 | 4. Смотрите примеры комментариев в стандартной библиотеке.
45 |
46 | **Передача управления программой в другой пакет навсегда**
47 | Начало и завершение программы должно происходить в каталоге `main` в функции `main()`. Недопустим вызов функции, которая намеренно завершает работу программы.
48 |
49 | **Имена функций и пакетов**
50 | https://blog.golang.org/package-names
51 | https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps
52 |
53 | **Рекомендации по выбору имен**
54 | Имена пакетов должны быть короткими. Желательно из одного слова.
55 | Имена объектов внутри пакетов не должны содержать в себе имени пакета. Например, fibo.FiboNumber() - плохо, fibo.Number() - хорошо.
56 | В Go не приняты длинные имена переменных. Локальная переменная часто имеет имя из одной буквы. Чем короче имя, тем лучше.
57 | Имена функций также не должны быть очень длинными. Не принято в имени функции подробно описывать её возможности. CalculatePointToPointDistance - плохо. Distance - хорошо.
58 |
59 | **Имя пакета в тестах**
60 | В файлах тестов имя пакета должно совпадать с именем тестируемого пакета.
61 |
62 | **Фреймворки**
63 | Ни один Go-фреймворк не решает реальных проблем. Поэтому в ходе выполнения заданий не нужно использовать никакие фреймворки.
64 |
65 | **Line of sight**
66 | Удачный (нормальный) путь выполнения кода должен иметь минимальные отступы слева. То есть не нужно помещать нормальную (без ошибок) ветку кода в блоки `else` и пр.
67 | Желательно вначале обработать все ошибки, чтобы последующий код означал нормальное выполнение (happy path).
68 |
69 | **Else**
70 | Блок `else` допустим только если в блоках `if` и `else` по паре-тройке строк. Иначе используйте `if x ...` и `if !x ...`.
71 |
72 | **Чтение программы**
73 | Надо стараться организовать код так, чтобы его удобно было читать сверху вниз с минимумом возвратов.
74 |
75 | **Чрезмерное усложнение кода**
76 | Код в ответе на ДЗ должен решать поставленную задачу и не более того.
77 | Не нужно усложнять задачу, также не нужно вводить дополнительные сущности: пакеты, интерфейсы, типы данных и пр., если только это не является необходимым для решения.
78 |
79 | **Собственная рецензия**
80 | Первая рецензия на PR должна быть от автора кода. Нужно внимательно посмотреть свой код на Гитхабе и написать комментарий с рецензией. LGTM или OK вполне достаточно, но можно и подробнее.
81 |
82 | **Пустой return**
83 | Инструкция `return` всегда должна содержать имена возвращаемых переменных или их значения. Если, конечно, функция что-то возвращает.
84 |
85 | **Отсутствие проверки ошибки**
86 | Если вызываемая функция может вернуть ошибку, то эта ошибка обязательно должна быть проверена и обработана.
87 |
88 | **Отсутствия действия в случае ошибки**
89 | Реакцией на ошибку всегда должно быть действие (`return ..., continue, break`) или что-то ещё. Не достаточно просто напечатать сообщение (за редким исключением).
90 | Если ошибка не `nil`, то все остальные возвращаемые значения функции невалидны и не могут быть использованы (кроме ошибки `EOF`).
91 |
92 | **Лишние файлы в решении ДЗ**
93 | При оформлении PR удаляйте всё лишнее: файлы с примерами из лекций; скопированные файлы, не относящиеся к решению; свои заметки и пр.
94 |
95 | **.gitignore**
96 | В `.gitignore` нужно добавить `go.sum`, а также все бинарные исполняемые файлы, файлы MacOS, файлы IDE.
97 |
98 | **Конец строки**
99 | При работе с вводом из консоли не забывайте, что конец строки в Linux и MacOS - `\n`, а в Windows - `\r\n`.
100 |
101 | **Неиспользуемый код**
102 | Если вы где-то объявили интерфейс, то он должен быть реализован каким-либо типом данных. Если вы написали "заглушку" для внешней зависимости, то должен быть тест, который эту заглушку использует.
--------------------------------------------------------------------------------
/01-intro/demoapp/cmd/app/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "go-core-4/01-intro/demoapp/pkg/stringutils"
7 | )
8 |
9 | func main() {
10 | http.ListenAndServe(
11 | ":8080",
12 | http.HandlerFunc(
13 | func(w http.ResponseWriter, r *http.Request) {
14 | s := r.URL.Query()["s"]
15 | if len(s) == 0 {
16 | http.Error(w, "bad query", http.StatusBadRequest)
17 | }
18 |
19 | rev := stringutils.Rev(s[0])
20 |
21 | w.Write([]byte(rev))
22 | },
23 | ),
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/01-intro/demoapp/pkg/stringutils/stringutils.go:
--------------------------------------------------------------------------------
1 | package stringutils
2 |
3 | func Rev(s string) string {
4 | runes := []rune(s)
5 |
6 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
7 | runes[i], runes[j] = runes[j], runes[i]
8 | }
9 |
10 | return string(runes)
11 | }
12 |
--------------------------------------------------------------------------------
/01-intro/demoapp/pkg/stringutils/stringutils_test.go:
--------------------------------------------------------------------------------
1 | package stringutils
2 |
3 | import "testing"
4 |
5 | func TestRev(t *testing.T) {
6 | tests := []struct {
7 | name string
8 | s string
9 | want string
10 | }{
11 | {
12 | name: "test 1",
13 | s: "ABC",
14 | want: "CBA",
15 | },
16 | {
17 | name: "test 1",
18 | s: "АБВ",
19 | want: "ВБА",
20 | },
21 | }
22 | for _, tt := range tests {
23 | t.Run(tt.name, func(t *testing.T) {
24 | if got := Rev(tt.s); got != tt.want {
25 | t.Errorf("Rev() = %v, want %v", got, tt.want)
26 | }
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/01-intro/hello/hello:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thinknetica/go_course_4/9886acf6728f566ce271056e3694247e055e1b74/01-intro/hello/hello
--------------------------------------------------------------------------------
/01-intro/hello/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | const greeting = "Hello, world!"
6 |
7 | func main() {
8 | printMsg(greeting)
9 | }
10 |
11 | func printMsg(msg string) {
12 | fmt.Println(greeting)
13 | }
14 |
15 | // Output: Hello, world!
16 |
--------------------------------------------------------------------------------
/02-syntax/1-basic/basic.go:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | func Range() {
9 | slice := []int{10, 20, 30, 40}
10 | arr := [...]int{10, 20, 30}
11 | str := "АБВ"
12 |
13 | for i, v := range slice {
14 | fmt.Println(i, v)
15 | }
16 | fmt.Printf("\n\n")
17 |
18 | for i, v := range arr {
19 | fmt.Println(i, v)
20 | }
21 | fmt.Printf("\n\n")
22 |
23 | for i, v := range str {
24 | fmt.Println(i, v, string(v))
25 | }
26 | fmt.Printf("\n\n")
27 | }
28 |
29 | func Vars() {
30 | var i int
31 | var j int8 = 10
32 | k := 20
33 | var l = 10
34 | println(i, j, k, l)
35 | }
36 |
37 | func Types() {
38 | type circle struct {
39 | x, y int
40 | r int
41 | }
42 | c := circle{
43 | x: 10,
44 | y: 10,
45 | r: 20,
46 | }
47 | fmt.Printf("%+v\n", c)
48 |
49 | type myString string
50 | var s string = "ABC"
51 | var s1 myString = myString(s)
52 | fmt.Printf("Тип 1: %v\tТип2: %v\t\n", reflect.TypeOf(s), reflect.TypeOf(s1))
53 | }
54 |
55 | func Pointers() {
56 | var s string = "ABC"
57 | var pointer *string = &s
58 | fmt.Println(*pointer, pointer)
59 | fmt.Printf("Значение: %v\tУказатель: %v\tСсылка: %v\t\n", s, pointer, &s)
60 | }
61 |
62 | func Scopes() {
63 | x := 1
64 | {
65 | x := 2
66 | fmt.Printf("x = %v\n", x)
67 | }
68 | fmt.Printf("x = %v\n", x)
69 | }
70 |
--------------------------------------------------------------------------------
/02-syntax/1-basic/basic_test.go:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestRange(t *testing.T) {
8 | Range()
9 | }
10 |
11 | func TestVars(t *testing.T) {
12 | Vars()
13 | }
14 |
15 | func TestTypes(t *testing.T) {
16 | Types()
17 | }
18 |
19 | func TestPointers(t *testing.T) {
20 | Pointers()
21 | }
22 |
23 | func TestScopes(t *testing.T) {
24 | Scopes()
25 | }
26 |
--------------------------------------------------------------------------------
/02-syntax/2-names/pkg/names/names.go:
--------------------------------------------------------------------------------
1 | package names
2 |
3 | import zl "github.com/rs/zerolog"
4 |
5 | func index(s string, b byte) int {
6 | for i := range s {
7 | if s[i] == b {
8 | return i
9 | }
10 | }
11 | _ = zl.Level(0)
12 |
13 | return -1
14 | }
15 |
--------------------------------------------------------------------------------
/02-syntax/2-names/pkg/names/names_test.go:
--------------------------------------------------------------------------------
1 | package names
2 |
3 | import "testing"
4 |
5 | const testStr = "ABCdE"
6 |
7 | func Test_index(t *testing.T) {
8 | got := index(testStr, 'd')
9 | want := 3
10 | if got != want {
11 | t.Fatalf("получили %v, ожидалось %v", got, want)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/02-syntax/3-array_slice_map/array_slice_map.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var array = [...]int{1, 2, 3, 4, 5} // массив
7 | slice := []int{0, 1, 2, 3, 4, 5} // слайс
8 | slice = append(slice, 10)
9 |
10 | fmt.Println(slice[1:4])
11 |
12 | _ = slice[len(slice)-1]
13 | fmt.Println("array", array, len(array), cap(array))
14 | fmt.Println("slice", slice, len(slice), cap(slice))
15 |
16 | slice = make([]int, 10, 20)
17 | fmt.Println("\nslice после инициализации:", slice, len(slice), cap(slice))
18 |
19 | // ассоциативный массив
20 | var m map[int]string
21 | //m[3] = "ABC" // panic: assignment to entry in nil map
22 | //m = make(map[int]string)
23 | m = map[int]string{}
24 | m[3] = "ABC"
25 | m[6] = "DEF"
26 | for k, v := range m {
27 | fmt.Printf("ключ %d, значение %s\n", k, v)
28 | }
29 | }
30 |
31 | func sliceDimensions() {
32 | var slice []int
33 | c := cap(slice)
34 | for i := 0; i < 1_000_000; i++ {
35 | slice = append(slice, i)
36 | if cap(slice) != c {
37 | fmt.Printf("длина: %v\tемкость:%v\tкоэффициент:%v\n", len(slice), cap(slice), float64(cap(slice))/float64(c))
38 | c = cap(slice)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/02-syntax/3-array_slice_map/array_slice_map_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "testing"
4 |
5 | func Test_sliceDimensions(t *testing.T) {
6 | sliceDimensions()
7 | }
8 |
--------------------------------------------------------------------------------
/02-syntax/4-pointers/pointers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var i int = 10 // переменная
7 | var p *int // указатель
8 | p = &i // взятие адреса. указателю присваивается ссылка.
9 | var p1 *interface{}
10 | _ = p1
11 | fmt.Println(i)
12 | i++
13 | fmt.Println(i)
14 | *p++ // разыменование указателя
15 | fmt.Println(i)
16 | *(&i)++
17 | fmt.Println(i)
18 | }
19 |
20 | // Output: 10
21 | // 11
22 | // 12
23 | // 13
24 |
--------------------------------------------------------------------------------
/02-syntax/4-struct/struct.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type point struct {
6 | lat float64
7 | lon float64
8 | }
9 |
10 | func (p point) PrintPoint() {
11 | fmt.Printf("lat:lon - %f, %f\n", p.lat, p.lon)
12 | }
13 |
14 | type object struct {
15 | name string
16 | point // встраивание, обращение по имени типа
17 | }
18 |
19 | func main() {
20 | o := object{}
21 | o.name = "Object"
22 | o.lat = 10
23 | o.lon = 20
24 | o.PrintPoint()
25 | }
26 |
--------------------------------------------------------------------------------
/02-syntax/5-methods_ifaces/methods_ifaces.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // Интерфейс.
6 | type WalkerTalker interface {
7 | Walk() // контракт интерфейса
8 | Talk() string // контракт интерфейса
9 | Eater
10 | }
11 |
12 | type MyIface interface {
13 | }
14 |
15 | type Eater interface {
16 | Eat() string
17 | }
18 |
19 | // Пользовательский тип данных.
20 | type Guy struct {
21 | Eater
22 | }
23 |
24 | // Метод (передача по значению).
25 | func (g *Guy) Walk() {
26 | fmt.Println("I walk!")
27 | }
28 |
29 | // Метод (передача по ссылке).
30 | func (g *Guy) Talk() string {
31 | return "I talk!"
32 | }
33 |
34 | func main() {
35 | // Переменная интерфейсного типа инициализирована
36 | // литералом конкретного типа.
37 | var g Guy
38 | var wt WalkerTalker = &g
39 | wt.Walk()
40 | println(wt.Talk())
41 | println(wt.Eat())
42 | var mi MyIface
43 | i := 10
44 | s := "str"
45 | mi = i
46 | mi = s
47 | _ = mi
48 | g.Eat()
49 |
50 | var in interface{}
51 | in = "20"
52 | in = "abc"
53 | in = []int{1, 2, 3}
54 | _ = in
55 | }
56 |
57 | type printer interface {
58 | print()
59 | }
60 |
61 | type myStr string
62 |
63 | func (s *myStr) print() {
64 | *s = "DEF"
65 | fmt.Println(*s)
66 | }
67 |
--------------------------------------------------------------------------------
/02-syntax/5-methods_ifaces/methods_ifaces_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "testing"
4 |
5 | func Test_myStr_print(t *testing.T) {
6 | var s myStr = "ABC"
7 | var p printer
8 | p = &s
9 | p.print()
10 | t.Log(s)
11 | }
12 |
--------------------------------------------------------------------------------
/02-syntax/6-errors/errors.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | )
8 |
9 | // Пользовательский тип данных для ошибки. Совместим со встроенным.
10 | type customErr struct {
11 | msg string
12 | }
13 |
14 | // Выполнение контракта интерфейса.
15 | func (ce customErr) Error() string {
16 | return fmt.Sprintf("пользовательская ошибка: %s", ce.msg)
17 | }
18 |
19 | func newCustomErr(msg string) customErr {
20 | return customErr{msg: msg}
21 | }
22 |
23 | var ErrNotExists = newCustomErr("не найдено")
24 |
25 | func main() {
26 | err := errors.New("пример создания ошибки с помощью пакета errors") // с маленькой буквы!
27 | fmt.Println(err.Error())
28 |
29 | err = fmt.Errorf("пример создания ошибки с помощью пакета fmt")
30 | fmt.Println(err) // почему без вызова метода Error()?
31 |
32 | val, err := envVar("1234")
33 | if err == ErrNotExists {
34 | fmt.Println("customErr")
35 | }
36 | if err != nil {
37 | fmt.Println(err)
38 | }
39 | fmt.Println(val)
40 | err = ErrNotExists // корректно
41 | }
42 |
43 | // envVar возвращает переменную окружения, заданную по имени.
44 | // Если переменная не найдена - возвращается ошибка.
45 | func envVar(name string) (string, error) {
46 | val := os.Getenv(name)
47 | if val == "" {
48 | return "не найдено", ErrNotExists
49 | }
50 | return val, nil
51 | }
52 |
--------------------------------------------------------------------------------
/02-syntax/7-channels/channels.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func unbufChan() chan int {
8 | ch := make(chan int)
9 | go func() {
10 | defer close(ch)
11 | for i := 1; i <= 5; i++ {
12 | ch <- i
13 | }
14 | fmt.Println("Функция завершила работу!")
15 | }()
16 | return ch
17 | }
18 |
19 | func bufChan() chan int {
20 | ch := make(chan int, 10)
21 | go func() {
22 | defer close(ch)
23 | for i := 1; i <= 5; i++ {
24 | ch <- i
25 | }
26 | fmt.Println("Функция завершила работу!")
27 | }()
28 | return ch
29 | }
30 |
--------------------------------------------------------------------------------
/02-syntax/7-channels/channels_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func Test_unbufChan(t *testing.T) {
9 | ch := unbufChan()
10 | for val := range ch {
11 | t.Log(val)
12 | time.Sleep(time.Second)
13 | }
14 | }
15 |
16 | func Test_bufChan(t *testing.T) {
17 | ch := bufChan()
18 | for val := range ch {
19 | t.Log(val)
20 | time.Sleep(time.Second)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/03-algorithms/1-search/search.go:
--------------------------------------------------------------------------------
1 | package bsearch
2 |
3 | import "fmt"
4 |
5 | // Simple возвращает номер элемента в массиве или -1. Используется простой поиск.
6 | func Simple(data []int, item int) int {
7 | for i := range data {
8 | if data[i] == item {
9 | return i
10 | }
11 | }
12 | return -1
13 | }
14 |
15 | // Binary возвращает номер элемента в массиве или -1. Используется бинарный поиск.
16 | func Binary(data []int, item int) int {
17 | low, high := 0, len(data)-1
18 | for low <= high {
19 | mid := (low + high) / 2
20 | if data[mid] == item {
21 | return mid
22 | }
23 | if data[mid] < item {
24 | low = mid + 1
25 | } else {
26 | high = mid - 1
27 | }
28 | }
29 | return -1
30 | }
31 |
32 | func O() {
33 | slice := []int{1, 2, 3}
34 |
35 | // O(1)
36 | fmt.Println(len(slice))
37 |
38 | // O(N)
39 | for i := range slice {
40 | // ....
41 |
42 | // O(N^2)
43 | for j := range slice {
44 |
45 | // O(N^3)
46 | for k := range slice {
47 | _, _, _ = i, j, k
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/03-algorithms/1-search/search_test.go:
--------------------------------------------------------------------------------
1 | package bsearch
2 |
3 | import (
4 | "math/rand"
5 | "sort"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestSearch(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | data []int
14 | item int
15 | want int
16 | }{
17 | {
18 | name: "#1",
19 | data: []int{1, 2, 4, 6, 10},
20 | item: 6,
21 | want: 3,
22 | },
23 | {
24 | name: "#2",
25 | data: []int{1, 2, 4, 5, 6, 10},
26 | item: 6,
27 | want: 4,
28 | },
29 | {
30 | name: "#3",
31 | data: []int{1, 2, 4, 5, 6, 10},
32 | item: 16,
33 | want: -1,
34 | },
35 | {
36 | name: "#4",
37 | data: []int{1, 2, 4, 5, 6, 10},
38 | item: 2,
39 | want: 1,
40 | },
41 | {
42 | name: "#5",
43 | data: []int{1, 2, 4, 5, 6, 10, 100},
44 | item: 4,
45 | want: 2,
46 | },
47 | }
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | if got := Binary(tt.data, tt.item); got != tt.want {
51 | t.Errorf("Binary() = %v, want %v", got, tt.want)
52 | }
53 | if got := Simple(tt.data, tt.item); got != tt.want {
54 | t.Errorf("Simple() = %v, want %v", got, tt.want)
55 | }
56 | })
57 | }
58 | }
59 |
60 | func sampleData(n int) []int {
61 | rand.Seed(time.Now().UnixNano())
62 | var data []int
63 | for i := 0; i < n; i++ {
64 | data = append(data, rand.Intn(1000))
65 | }
66 |
67 | sort.Slice(data, func(i, j int) bool { return data[i] < data[j] })
68 | return data
69 | }
70 |
71 | func BenchmarkSimple(b *testing.B) {
72 | k := 1_000_000
73 | data := sampleData(k)
74 | for i := 0; i < b.N; i++ {
75 | n := rand.Intn(k)
76 | Simple(data, n)
77 | }
78 | }
79 |
80 | func BenchmarkBinary(b *testing.B) {
81 | k := 1_000_000
82 | data := sampleData(k)
83 | for i := 0; i < b.N; i++ {
84 | n := rand.Intn(k)
85 | Binary(data, n)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/03-algorithms/2-recursion/recursion.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // pow рекурсивно возводит "x" в степень "у"
8 | func pow(x, y int) int {
9 | if y == 0 { // базовый случай
10 | return 1
11 | }
12 | return x * pow(x, y-1) // рекурсивный шаг
13 | }
14 |
15 | func main() {
16 | fmt.Println("3^2:", pow(3, 2)) // 3^2: 9
17 | fmt.Println("5^3:", pow(5, 3)) // 5^3: 125
18 | }
19 |
--------------------------------------------------------------------------------
/03-algorithms/3-dynamic/dynamic.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 | )
8 |
9 | // fibo рекурсивно вычисляет числа фибоначчи, используя идею динамического программирования
10 | func fibo(n int, cache map[int]int) int {
11 | // проверка наличия уже вычисленного результата в кэше
12 | if val, ok := cache[n]; ok {
13 | return val
14 | }
15 | // базовая часть рекурсии
16 | if n < 2 {
17 | return 1
18 | }
19 | // рекурсивный шаг с записью в кэш
20 | num := fibo(n-1, cache) + fibo(n-2, cache)
21 | cache[n] = num
22 | return num
23 | }
24 |
25 | func main() {
26 | cache := make(map[int]int)
27 |
28 | for {
29 | fmt.Print("Введите число: ")
30 | var in string
31 | _, err := fmt.Scanln(&in)
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 | n, err := strconv.Atoi(in)
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 | num := fibo(n, cache)
40 | fmt.Printf("Число Фибоначчи №%d = %d\n", n, num)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/03-algorithms/4-graph/graph.go:
--------------------------------------------------------------------------------
1 | package graph
2 |
3 | // Matrix - представление графа в виде матрицы смежности.
4 | // Matrix[i][j] содержит стоимость ребра между вершинами i и j.
5 | type Matrix [][]int
6 |
7 | // List - представление графа в виде списка смежности.
8 | // Лист смежности содержит для каждой вершины графа лист
9 | // смежных вершин, с указанием стоимости ребра.
10 | type List map[int][]Neighbour
11 |
12 | // Neighbour - смежная вершина и вес ребра.
13 | type Neighbour struct {
14 | Vertex int
15 | Weight int
16 | }
17 |
18 | // NewList создаёт новый список смежности.
19 | func NewList() List {
20 | var l List
21 | l = make(map[int][]Neighbour)
22 | return l
23 | }
24 |
25 | // AddNodes добавляет вершины графа.
26 | func (l List) AddNodes(nodes ...int) {
27 | for _, node := range nodes {
28 | l[node] = []Neighbour{}
29 | }
30 | }
31 |
32 | // AddEdge добавляет ребро графа.
33 | func (l List) AddEdge(node1, node2, weight int) {
34 | l[node1] = append(l[node1], Neighbour{Vertex: node2, Weight: weight})
35 | }
36 |
37 | // Weight возвращает стоимость всех рёбер графа.
38 | func (l List) Weight() int {
39 | sum := 0
40 | for _, v := range l {
41 | for _, edge := range v {
42 | sum += edge.Weight
43 | }
44 | }
45 | return sum
46 | }
47 |
48 | // MinSpanTree - поиск минимального покрывающего (остовного) дерева
49 | // алгоритмом Прима.
50 | func (l List) MinSpanTree() List {
51 |
52 | // алгоритм:
53 | // - определяем первую вершину и добавляем в исследуемые
54 | // - цикл для исследуемых вершин:
55 | // - находим ближайшего соседа и добавляем ребро в дерево
56 |
57 | tree := NewList()
58 | current := make(map[int]bool) // вершины, исследуемые на данном шаге
59 | current[1] = true
60 | visited := make(map[int]bool) // посещённые вершины
61 |
62 | initVal := func() (v1, v2, dist int) {
63 | for k, v := range l {
64 | if k > v1 {
65 | v1, v2 = k, k
66 | }
67 | for _, item := range v {
68 | if item.Weight > dist {
69 | dist = item.Weight
70 | }
71 | }
72 | }
73 | return v1 + 1, v2 + 1, dist + 1
74 | }
75 | initV, _, _ := initVal()
76 |
77 | OUT:
78 | for {
79 | v1, v2, dist := initVal()
80 |
81 | for i := range current {
82 | v := l[i]
83 | if len(v) == 0 {
84 | continue
85 | }
86 | for _, edge := range v {
87 | if visited[edge.Vertex] {
88 | continue
89 | }
90 |
91 | if edge.Weight < dist {
92 | v1 = i
93 | v2 = edge.Vertex
94 | dist = edge.Weight
95 | }
96 | }
97 | }
98 | if v1 == initV {
99 | break OUT
100 | }
101 | visited[v1] = true
102 | visited[v2] = true
103 | neighbour := Neighbour{Vertex: v2, Weight: dist}
104 | if _, ok := tree[v1]; !ok {
105 | tree[v1] = []Neighbour{}
106 | }
107 | tree[v1] = append(tree[v1], neighbour)
108 | edges := 0
109 | for _, v := range tree {
110 | edges += len(v)
111 | }
112 | if edges+1 == len(l) {
113 | break OUT
114 | }
115 | current[v2] = true
116 | }
117 | return tree
118 | }
119 |
--------------------------------------------------------------------------------
/03-algorithms/4-graph/graph_test.go:
--------------------------------------------------------------------------------
1 | package graph
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestList_MinSpanTree(t *testing.T) {
9 | l := NewList()
10 | l.AddNodes(1, 2, 3, 4, 5, 6)
11 | l.AddEdge(1, 2, 10)
12 | l.AddEdge(1, 3, 15)
13 | l.AddEdge(1, 5, 20)
14 | l.AddEdge(2, 3, 20)
15 | l.AddEdge(2, 5, 35)
16 | l.AddEdge(2, 4, 10)
17 | l.AddEdge(3, 4, 20)
18 | l.AddEdge(3, 6, 50)
19 | l.AddEdge(4, 6, 30)
20 |
21 | mst := l.MinSpanTree()
22 | fmt.Println(mst)
23 | fmt.Println(mst.Weight())
24 |
25 | want := 85
26 | got := mst.Weight()
27 | if got != want {
28 | t.Fatalf("длина дерева %d, а ожидалось %d", got, want)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/04-datastructs/0-arrays/arrays.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/DmitriyVTitov/size"
7 | )
8 |
9 | const N = 1_000_000_000
10 |
11 | var slice = make([]int, N)
12 |
13 | func main() {
14 | newSlice := make([]int, N+1)
15 | copy(newSlice, slice)
16 | newSlice[len(newSlice)-1] = 25
17 |
18 | s := make([]int, 1_000_000)
19 | for i := 0; i < 1_000_000; i++ {
20 | s[i] = i
21 | }
22 | _ = s
23 |
24 | newSlice[1] = 10
25 | x, y := size.Of(slice), size.Of(newSlice)
26 | fmt.Printf("Size of 'slice' is %vGB\n", x/1024/1024/1024)
27 | fmt.Printf("Size of 'newSlice' is %vGB\n", y/1024/1024/1024)
28 | fmt.Printf("Size of both slices is %vGB\n", (x+y)/1024/1024/1024)
29 |
30 | s1 := []int{1, 2, 3, 4, 5}
31 | s2 := s1[1:3] // {2, 3}
32 | fmt.Println("s2:", s2)
33 | fmt.Println("s1 before:", s1)
34 | s2[0] = 10
35 | fmt.Println("s1 after:", s1)
36 | }
37 |
--------------------------------------------------------------------------------
/04-datastructs/1-list/list.go:
--------------------------------------------------------------------------------
1 | // Реализуация двусвязного списка вместе с базовыми операциями.
2 | package list
3 |
4 | import (
5 | "fmt"
6 | )
7 |
8 | // List - двусвязный список.
9 | type List struct {
10 | root *Elem
11 | }
12 |
13 | // Elem - элемент списка.
14 | type Elem struct {
15 | Val interface{}
16 | next, prev *Elem
17 | }
18 |
19 | // New создаёт список и возвращает указатель на него.
20 | func New() *List {
21 | var l List
22 | l.root = &Elem{}
23 | l.root.next = l.root
24 | l.root.prev = l.root
25 | return &l
26 | }
27 |
28 | // Push вставляет элемент в начало списка.
29 | func (l *List) Push(e Elem) *Elem {
30 | e.prev = l.root
31 | e.next = l.root.next
32 | l.root.next = &e
33 | if e.next != l.root {
34 | e.next.prev = &e
35 | }
36 | return &e
37 | }
38 |
39 | // String реализует интерфейс fmt.Stringer представляя список в виде строки.
40 | func (l *List) String() string {
41 | el := l.root.next
42 | var s string
43 | for el != l.root {
44 | s += fmt.Sprintf("%v ", el.Val)
45 | el = el.next
46 | }
47 | if len(s) > 0 {
48 | s = s[:len(s)-1]
49 | }
50 | return s
51 | }
52 |
53 | // Pop удаляет первый элемент списка.
54 | func (l *List) Pop() *List {
55 | return nil
56 | }
57 |
58 | // Reverse разворачивает список.
59 | func (l *List) Reverse() *List {
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/04-datastructs/1-list/list_test.go:
--------------------------------------------------------------------------------
1 | package list
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestList_Push(t *testing.T) {
8 | l := New()
9 | l.Push(Elem{Val: 2})
10 | l.Push(Elem{Val: 1})
11 |
12 | got := l.String()
13 | want := "1 2"
14 | if got != want {
15 | t.Fatalf("получили %s, ожидалось %s", got, want)
16 | }
17 | }
18 |
19 | func TestList_Pop(t *testing.T) {
20 | l := New()
21 | l.Push(Elem{Val: 3})
22 | l.Push(Elem{Val: 2})
23 | l.Push(Elem{Val: 1})
24 |
25 | got := l.Pop().String()
26 | want := "2 3"
27 | if got != want {
28 | t.Fatalf("получили %s, ожидалось %s", got, want)
29 | }
30 |
31 | got = l.Pop().Pop().String()
32 | want = ""
33 | if got != want {
34 | t.Fatalf("получили %s, ожидалось %s", got, want)
35 | }
36 | }
37 |
38 | func TestList_Reverse(t *testing.T) {
39 | l := New()
40 | l.Push(Elem{Val: 3})
41 | l.Push(Elem{Val: 2})
42 | l.Push(Elem{Val: 1})
43 |
44 | got := l.Reverse().String()
45 | want := "3 2 1"
46 | if got != want {
47 | t.Fatalf("получили %s, ожидалось %s", got, want)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/04-datastructs/2-bst/bst.go:
--------------------------------------------------------------------------------
1 | // Пример реализации структуры данных "Двоичное дерево поиска".
2 | // Пример для простоты приведён для дерева,
3 | // содержащего в качетве значений целые числа.
4 | // Можно по аналогии со стандартной библиотекой с помощью интерфейса
5 | // обобщить на произвольные типы данных.
6 | // Для этого потребуется контракт на функцию сравнения элементов.
7 | package main
8 |
9 | import "fmt"
10 |
11 | // Двоичное дерево поиска
12 | type Tree struct {
13 | root *Element
14 | }
15 |
16 | // Лист дерева.
17 | type Element struct {
18 | left, right *Element
19 | Value int
20 | }
21 |
22 | // Insert вставляет элемент в дерево.
23 | func (t *Tree) Insert(x int) {
24 | e := &Element{Value: x}
25 | if t.root == nil {
26 | t.root = e
27 | return
28 | }
29 | insert(t.root, e)
30 | }
31 |
32 | // inset рекурсивно вставляет элемент в нужный уровень дерева.
33 | func insert(node, new *Element) {
34 | if new.Value < node.Value {
35 | if node.left == nil {
36 | node.left = new
37 | return
38 | }
39 | insert(node.left, new)
40 | }
41 | if new.Value >= node.Value {
42 | if node.right == nil {
43 | node.right = new
44 | return
45 | }
46 | insert(node.right, new)
47 | }
48 | }
49 |
50 | // Search ищет значение в дереве.
51 | func (t *Tree) Search(x int) bool {
52 | return search(t.root, x)
53 | }
54 |
55 | func search(el *Element, x int) bool {
56 | if el == nil {
57 | return false
58 | }
59 | if el.Value == x {
60 | return true
61 | }
62 | if el.Value < x {
63 | return search(el.right, x)
64 | }
65 | return search(el.left, x)
66 | }
67 |
68 | // String реализует интерфейс Stringer для дерева.
69 | func (t Tree) String() string {
70 | return prettyPrint(t.root, 0)
71 | }
72 |
73 | // prettyPrint печатает дерево в виде дерева :)
74 | func prettyPrint(e *Element, spaces int) (res string) {
75 | if e == nil {
76 | return res
77 | }
78 | spaces++
79 | res += prettyPrint(e.right, spaces)
80 | for i := 0; i < spaces; i++ {
81 | res += "\t"
82 | }
83 | res += fmt.Sprintf("%d\n", e.Value)
84 | res += prettyPrint(e.left, spaces)
85 | return res
86 | }
87 |
88 | func initTree() *Tree {
89 | var t Tree
90 | t.Insert(10)
91 | t.Insert(15)
92 | t.Insert(15)
93 | t.Insert(15)
94 | t.Insert(15)
95 | t.Insert(25)
96 | t.Insert(30)
97 | t.Insert(35)
98 | t.Insert(5)
99 | t.Insert(20)
100 | t.Insert(1)
101 | t.Insert(2)
102 | t.Insert(6)
103 | return &t
104 | }
105 |
106 | func main() {
107 | t := initTree()
108 | fmt.Println(t)
109 | }
110 |
111 | // Output:
112 | // 35
113 | // 30
114 | // 25
115 | // 20
116 | // 15
117 | // 10
118 | // 6
119 | // 5
120 | // 2
121 | // 1
122 |
--------------------------------------------------------------------------------
/05-io/1-net_io/net_io.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "strings"
7 | "time"
8 | )
9 |
10 | // Сетевой адрес.
11 | //
12 | // Служба будет слушать запросы на всех IP-адресах
13 | // компьютера на порту 12345.
14 | // Нпример, 127.0.0.1:12345
15 | const addr = "0.0.0.0:12345"
16 |
17 | // Протокол сетевой службы.
18 | const proto = "tcp4"
19 |
20 | func main() {
21 | // Запуск сетевой службы про протоколу TCP
22 | // на порту 12345.
23 | listener, err := net.Listen(proto, addr)
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 |
28 | // Подключения обрабатываются в бесконечном цикле.
29 | // Иначе после обслуживания первого подключения сервер
30 | // завершит работу.
31 | for {
32 | // Принимаем подключение.
33 | conn, err := listener.Accept()
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 | // Вызов обработчика подключения.
38 | handleConn(conn)
39 | }
40 | }
41 |
42 | // Обработчик. Вызывается для каждого соединения.
43 | func handleConn(conn net.Conn) {
44 |
45 | // Чтение сообщения от клиента.
46 | var req []byte
47 | var buf = make([]byte, 2)
48 | for {
49 | _, err := conn.Read(buf)
50 | if err != nil {
51 | log.Println(err)
52 | return
53 | }
54 | end := false
55 | for _, b := range buf {
56 | if b == '\n' {
57 | end = true
58 | break
59 | }
60 | req = append(req, b)
61 | }
62 | if end {
63 | break
64 | }
65 | }
66 |
67 | // Удаление символов конца строки.
68 | msg := strings.TrimSuffix(string(req), "\n")
69 | msg = strings.TrimSuffix(msg, "\r")
70 |
71 | // Если получили "time" - пишем время в соединение.
72 | if msg == "time" {
73 | n, err := conn.Write([]byte(time.Now().String() + "\n"))
74 | if err != nil {
75 | log.Printf("ошибка: %v", err)
76 | return
77 | }
78 | log.Printf("клиенту отправлено %d байт", n)
79 | }
80 |
81 | // Закрытие соединения.
82 | conn.Close()
83 | }
84 |
--------------------------------------------------------------------------------
/05-io/1-net_io/net_io_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "log"
6 | "net"
7 | "sync"
8 | "testing"
9 | )
10 |
11 | func Test_handleConn(t *testing.T) {
12 | // Имитация полнодуплексного
13 | // сетевого соединения.
14 | srv, cl := net.Pipe()
15 |
16 | // Мы должны дождаться
17 | // завершения всех потоков.
18 | var wg sync.WaitGroup
19 | wg.Add(1)
20 |
21 | // Обработчик подключения запускается
22 | // в отдельном потоке.
23 | go func() {
24 | // Обработчику передается один из
25 | // концов виртуальной трубы.
26 | handleConn(srv)
27 | srv.Close()
28 | wg.Done()
29 | }()
30 |
31 | // В основном потоке теста клиент отправляет
32 | // сообщения в трубу.
33 | // Эти сообщения сервер прочитает в соседнем потоке.
34 | _, err := cl.Write([]byte("time\n"))
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 |
39 | // Чтенеие ответа от сервера.
40 | reader := bufio.NewReader(cl)
41 | b, err := reader.ReadBytes('\n')
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 |
46 | // Проверка ответа.
47 | if len(b) < 10 {
48 | t.Error("время не получено")
49 | }
50 | wg.Wait()
51 | cl.Close()
52 | t.Log(string(b))
53 | }
54 |
--------------------------------------------------------------------------------
/05-io/2-writer-example/strings.txt:
--------------------------------------------------------------------------------
1 | ГЛАВРЫБА
--------------------------------------------------------------------------------
/05-io/2-writer-example/writer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "log"
6 | "os"
7 | )
8 |
9 | // sendReversedString разворачивает строку и отправляет её получателю.
10 | func sendReversedString(s string, w io.Writer) error {
11 | runes := []rune(s)
12 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
13 | runes[i], runes[j] = runes[j], runes[i]
14 | }
15 |
16 | _, err := w.Write([]byte(string(runes)))
17 | return err
18 | }
19 |
20 | func main() {
21 | f, err := os.Create("./strings.txt")
22 | if err != nil {
23 | log.Fatal(err)
24 | }
25 | defer f.Close()
26 |
27 | // Вывод на примере файла.
28 | err = sendReversedString("АБЫРВАЛГ", f)
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 |
33 | // Вывод на примере консоли.
34 | err = sendReversedString("АБЫРВАЛГ", os.Stdout)
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 | }
39 |
40 | func get(r io.Reader) ([]byte, error) {
41 | return io.ReadAll(r)
42 | /*/
43 | var b []byte
44 | var buf = make([]byte, 10)
45 | for {
46 | _, err := r.Read(buf)
47 | if err == io.EOF {
48 | break
49 | }
50 | if err != nil {
51 | log.Println(err)
52 | return nil, err
53 | }
54 | b = append(b, buf...)
55 | }
56 | // Здесь какая-то логика.
57 | return b, nil*/
58 | }
59 |
--------------------------------------------------------------------------------
/05-io/3-bufio_example/bufio.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "io"
6 | "log"
7 | "os"
8 | )
9 |
10 | func main() {
11 | f, err := os.Open("./bufio.go")
12 | if err != nil {
13 | log.Fatal(err)
14 | }
15 |
16 | b, err := get(f)
17 | if err != nil {
18 | log.Fatal(err)
19 | }
20 |
21 | f.Close()
22 |
23 | f, err = os.Create("./bufio_copy.go")
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 |
28 | err = store(f, b)
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 |
33 | f.Close()
34 | }
35 |
36 | func store(w io.Writer, b []byte) error {
37 | // Здесь должна быть некотороая логика.
38 | // После обработки данных мы их записываем туда,
39 | // куда хочет вызывающий код.
40 | _, err := w.Write(b)
41 | return err
42 | }
43 |
44 | func get(r io.Reader) ([]byte, error) {
45 | // буфер уже является частью сканера
46 | scanner := bufio.NewScanner(r)
47 |
48 | var b []byte
49 | for scanner.Scan() {
50 | b = append(b, []byte(scanner.Text()+"\n")...)
51 | }
52 | if err := scanner.Err(); err != nil {
53 | return nil, err
54 | }
55 |
56 | // Здесь какая-то логика.
57 | return b, nil
58 | }
59 |
--------------------------------------------------------------------------------
/05-io/4-files/files.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | )
8 |
9 | func main() {
10 | f, err := os.Create("./file.txt")
11 | if err != nil {
12 | log.Println(err)
13 | return
14 | }
15 | defer f.Close()
16 |
17 | err = os.WriteFile(f.Name(), []byte("Текст"), 0666)
18 | if err != nil {
19 | log.Println(err)
20 | return
21 | }
22 |
23 | data, err := os.ReadFile(f.Name())
24 | if err != nil {
25 | log.Println(err)
26 | return
27 | }
28 | fmt.Printf("Данные файла:\n%s\n", data)
29 |
30 | // вариант чтения с помощью io.Reader
31 | var file *os.File
32 | file, err = os.Open(f.Name())
33 | if err != nil {
34 | log.Println(err)
35 | return
36 | }
37 | defer file.Close()
38 |
39 | // что здесь не правильно?
40 | buf := make([]byte, 6)
41 | n, err := file.Read(buf)
42 | if err != nil {
43 | log.Println(err)
44 | return
45 | }
46 | buf = buf[:n]
47 | fmt.Printf("Данные файла:\n%s\n", buf)
48 | }
49 |
--------------------------------------------------------------------------------
/05-io/5-input/input.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strings"
8 | )
9 |
10 | func main() {
11 | reader := bufio.NewReader(os.Stdin) // буфер для os.Stdin
12 | for {
13 | fmt.Print("NewReader-> ")
14 | text, _ := reader.ReadString('\n') // чтение строки (до символа перевода)
15 | text = strings.TrimSuffix(text, "\n") // удаление перевода строки
16 | text = strings.TrimSuffix(text, "\r") // удаление перевода строки для Windows
17 | if text == "exit" {
18 | break
19 | }
20 | fmt.Println("echo:", text)
21 | }
22 |
23 | scanner := bufio.NewScanner(os.Stdin)
24 | fmt.Print("NewScanner-> ")
25 | for scanner.Scan() {
26 | if scanner.Text() == "exit" {
27 | break
28 | }
29 | fmt.Println(scanner.Text())
30 | fmt.Print("NewScanner-> ")
31 | }
32 |
33 | for {
34 | fmt.Print("fmt.Scanln-> ")
35 | var s string
36 | fmt.Scanln(&s)
37 | if s == "exit" {
38 | break
39 | }
40 | fmt.Println(s)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/05-io/6-flags/flags.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | )
7 |
8 | func main() {
9 | s := flag.String("filename", "file.txt", "имя файла")
10 |
11 | var n int
12 | flag.IntVar(&n, "n", 10, "количество")
13 | flag.IntVar(&n, "number", 10, "количество")
14 |
15 | flag.Parse()
16 |
17 | flag.PrintDefaults()
18 |
19 | fmt.Println(*s, n)
20 | }
21 |
--------------------------------------------------------------------------------
/05-io/7-json_serialize/json.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type Point struct {
9 | Lon float64 `json:"1"`
10 | Lat float64 `json:"2"`
11 | }
12 |
13 | func main() {
14 | var points []Point
15 | for i := 0; i < 100; i++ {
16 | points = append(points, Point{Lon: float64(i), Lat: float64(i)})
17 | }
18 |
19 | b, _ := json.MarshalIndent(points, "", " ")
20 | fmt.Println(string(b))
21 | }
22 |
--------------------------------------------------------------------------------
/06-oop/1-methods/methods.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Course - курс по Go.
8 | type Course struct {
9 | author string
10 | }
11 |
12 | // SetAuthor устанавливает имя автора.
13 | func (c *Course) SetAuthor(name string) {
14 | c.author = name
15 | }
16 |
17 | // Author возвращает имя атора курса.
18 | func (c Course) Author() string {
19 | return c.author
20 | }
21 |
22 | func main() {
23 | c := new(Course) // new создаёт переменную и возвращает указатель на неё
24 | c.SetAuthor("1")
25 | fmt.Println(c.Author())
26 |
27 | var course Course
28 | course.SetAuthor("2")
29 | fmt.Println(course.Author()) // Что будет выведено здесь?
30 |
31 | // The rule about pointers vs. values for receivers is that value methods
32 | // can be invoked on pointers and values,
33 | // but pointer methods can only be invoked on pointers.
34 | }
35 |
36 | // *** Вопрос: что будет выведено, если получатели методов сделать значениями?
37 |
--------------------------------------------------------------------------------
/06-oop/2-dip/ifaces.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "log"
6 | )
7 |
8 | // Logger - журнал.
9 | type Logger interface {
10 | Log(string) error
11 | }
12 |
13 | // DBLogger - журнал в БД.
14 | type DBLogger struct {
15 | connString string // строка для подключения к БД
16 | }
17 |
18 | // Log записывает сообщение в журнал в БД.
19 | func (dbl *DBLogger) Log(msg string) error {
20 | if dbl.connString == "" {
21 | return errors.New("БД недоступна")
22 | }
23 | // выполняем запись сообщения в БД
24 | return nil
25 | }
26 |
27 | type CustomLogger struct {
28 | Logger
29 | }
30 |
31 | // MemLogger - заглушка журнала в памяти для тестов.
32 | type MemLogger struct{}
33 |
34 | // Log ничего не делает. Обратите внимание на отсутствие имён у получателя и аргумента.
35 | func (*MemLogger) Log(string) error {
36 | return nil
37 | }
38 |
39 | func main() {
40 | l := new(CustomLogger)
41 | err := logMsg(l, "сообщение")
42 | if err != nil {
43 | log.Println(err)
44 | return
45 | }
46 | }
47 |
48 | type Server struct {
49 | log Logger
50 | }
51 |
52 | func New(log Logger) *Server {
53 | s := &Server{log: log}
54 | return s
55 | }
56 |
57 | // logMsg записывает сообщение в журнал
58 | func logMsg(l Logger, msg string) error {
59 | // CODE
60 | err := l.Log(msg)
61 | return err
62 | }
63 |
--------------------------------------------------------------------------------
/06-oop/2-dip/ifaces_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_logMsg(t *testing.T) {
8 | s := New(&MemLogger{})
9 | _ = s
10 |
11 | l := new(CustomLogger)
12 | err := logMsg(l, "msg")
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/06-oop/3-embedding/embedding.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type computer struct {
8 | model string
9 | processor
10 | }
11 |
12 | type processor struct {
13 | model string
14 | cores int
15 | }
16 |
17 | func (c *computer) cpuinfo() string {
18 | return "computer cpuinfo()"
19 | }
20 |
21 | func (p *processor) cpuinfo() string {
22 | return fmt.Sprintf("%s, %d ядер", p.model, p.cores)
23 | }
24 |
25 | func (c *computer) String() string {
26 | return fmt.Sprintf("Компьютер \"%s\".\nПроцессор \"%s\".\n", c.model, c.cpuinfo())
27 | }
28 |
29 | func main() {
30 | comp := &computer{
31 | model: "Компьютер игровой",
32 | processor: processor{
33 | model: "Байкал",
34 | cores: 8,
35 | },
36 | }
37 |
38 | fmt.Println(comp.cpuinfo())
39 | }
40 |
--------------------------------------------------------------------------------
/06-oop/4-constructor/constructor.go:
--------------------------------------------------------------------------------
1 | package guitar
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Guitar - гитара.
8 | type Guitar struct {
9 | manufacturer string
10 | model string
11 |
12 | Tabs map[string]string
13 | }
14 |
15 | // New - конструктор.
16 | func New(manufacturer, model string) *Guitar {
17 | g := Guitar{
18 | manufacturer: manufacturer, // имена совпадают
19 | model: model, // но это не проблема
20 | }
21 | g.Tabs = make(map[string]string)
22 |
23 | return &g // явно указываем, что возвращается ссылка
24 | }
25 |
26 | // Info возвращает сведения о гитаре.
27 | func (g *Guitar) Info() string {
28 | return fmt.Sprintf("Гитара %s %s", g.manufacturer, g.model)
29 | }
30 |
--------------------------------------------------------------------------------
/06-oop/4-constructor/constructor_test.go:
--------------------------------------------------------------------------------
1 | package guitar
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestNew(t *testing.T) {
8 | g := New("Fender", "Stratocaster")
9 | got := g.Info()
10 | want := "Гитара Fender Stratocaster"
11 | if got != want {
12 | t.Errorf("получили %s, ожидалось %s", got, want)
13 | }
14 | }
15 |
16 | func TestGuitar(t *testing.T) {
17 | var g Guitar
18 | g.Tabs = make(map[string]string)
19 | g.Tabs["Metallica"] = "One"
20 | }
21 |
--------------------------------------------------------------------------------
/06-oop/5-hw/hw.go:
--------------------------------------------------------------------------------
1 | package hw
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | )
7 |
8 | // По условиям задачи, координаты не могут быть меньше 0.
9 |
10 | type Geom struct {
11 | X1, Y1, X2, Y2 float64
12 | }
13 |
14 | func (geom Geom) CalculateDistance() (distance float64) {
15 |
16 | if geom.X1 < 0 || geom.X2 < 0 || geom.Y1 < 0 || geom.Y2 < 0 {
17 | fmt.Println("Координаты не могут быть меньше нуля")
18 | return -1
19 | } else {
20 | distance = math.Sqrt(math.Pow(geom.X2-geom.X1, 2) + math.Pow(geom.Y2-geom.Y1, 2))
21 | }
22 |
23 | // возврат расстояния между точками
24 | return distance
25 | }
26 |
--------------------------------------------------------------------------------
/06-oop/5-hw/hw_test.go:
--------------------------------------------------------------------------------
1 | package hw
2 |
3 | import "testing"
4 |
5 | func TestGeom_CalculateDistance(t *testing.T) {
6 | tests := []struct {
7 | name string
8 | geom Geom
9 | wantDistance float64
10 | }{
11 | {
12 | name: "#1",
13 | geom: Geom{X1: 1, Y1: 1, X2: 4, Y2: 5},
14 | wantDistance: 5,
15 | },
16 | }
17 | for _, tt := range tests {
18 | t.Run(tt.name, func(t *testing.T) {
19 | if gotDistance := tt.geom.CalculateDistance(); gotDistance != tt.wantDistance {
20 | t.Errorf("Geom.CalculateDistance() = %v, want %v", gotDistance, tt.wantDistance)
21 | }
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/07-testing/1-simple/simple.go:
--------------------------------------------------------------------------------
1 | package simple
2 |
3 | func sum(a, b int) int {
4 | return a + b
5 | }
6 |
--------------------------------------------------------------------------------
/07-testing/1-simple/simple_test.go:
--------------------------------------------------------------------------------
1 | package simple
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // Тест всегда имеет определённую сигнатуру: TestFuncName(t *testing.T) { ... }
8 | func Test_sum(t *testing.T) {
9 | a, b := 3, 4 // тестовый пример
10 | want := 7 // заранее вычисленный результат
11 |
12 | got := sum(a, b) // вызов тестируемого кода
13 |
14 | if got != want { // сравнение результата с правильным значением
15 | t.Fatalf("получили %d, ожидалось %d", got, want)
16 | }
17 |
18 | t.Log("OK")
19 | }
20 |
21 | // === RUN Test_sum
22 | // --- PASS: Test_sum (0.00s)
23 | // PASS
24 | // ok go-core/8-testing/simple 0.138s
25 |
--------------------------------------------------------------------------------
/07-testing/2-testmain/testmain.go:
--------------------------------------------------------------------------------
1 | package testmain
2 |
3 | import "github.com/jackc/pgx/v4"
4 |
5 | type Service struct {
6 | db *pgx.Conn
7 | }
8 |
9 | type Product struct {
10 | Name string
11 | Price int
12 | }
13 |
14 | func (s *Service) Products() []Product {
15 | data := []Product{
16 | {
17 | Name: "Компьютер",
18 | Price: 20_000,
19 | },
20 | }
21 | return data
22 | }
23 |
--------------------------------------------------------------------------------
/07-testing/2-testmain/testmain_test.go:
--------------------------------------------------------------------------------
1 | package testmain
2 |
3 | import (
4 | "context"
5 | "log"
6 | "os"
7 | "reflect"
8 | "testing"
9 |
10 | "github.com/jackc/pgx/v4"
11 | )
12 |
13 | var testService *Service
14 |
15 | func TestMain(m *testing.M) {
16 | conn, err := pgx.Connect(context.Background(), "postgres://user:pwd@server/database")
17 | if err != nil {
18 | log.Println(err)
19 | os.Exit(1)
20 | }
21 |
22 | testService = &Service{
23 | db: conn,
24 | }
25 |
26 | m.Run()
27 | // Здесь может идти освобождение ресурсов.
28 | }
29 |
30 | func TestService_Products(t *testing.T) {
31 | got := testService.Products()
32 | want := []Product{
33 | {
34 | Name: "Компьютер",
35 | Price: 20_000,
36 | },
37 | }
38 | if !reflect.DeepEqual(got, want) {
39 | t.Errorf("Service.Products() = %v, want %v", got, want)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/07-testing/3-table/table.go:
--------------------------------------------------------------------------------
1 | package table
2 |
3 | // reverse разворачивает строку.
4 | func reverse(s string) string {
5 | runes := []rune(s)
6 |
7 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
8 | runes[i], runes[j] = runes[j], runes[i]
9 | }
10 |
11 | return string(runes)
12 | }
13 |
--------------------------------------------------------------------------------
/07-testing/3-table/table_test.go:
--------------------------------------------------------------------------------
1 | package table
2 |
3 | import "testing"
4 |
5 | func Test_reverse(t *testing.T) {
6 | type args struct {
7 | s string
8 | }
9 | tests := []struct {
10 | name string
11 | args args
12 | want string
13 | }{
14 | {
15 | name: "Тест №1",
16 | args: args{
17 | s: "string",
18 | },
19 | want: "gnirts",
20 | },
21 | {
22 | name: "Тест №2",
23 | args: args{
24 | s: "ABCdefg",
25 | },
26 | want: "gfedCBA",
27 | },
28 | {
29 | name: "Тест №3",
30 | args: args{
31 | s: "",
32 | },
33 | want: "",
34 | },
35 | {
36 | name: "Тест №4",
37 | args: args{
38 | s: "абв",
39 | },
40 | want: "вба",
41 | },
42 | {
43 | name: "Тест №4",
44 | args: args{
45 | s: "абвг",
46 | },
47 | want: "гвба",
48 | },
49 | }
50 | for _, tt := range tests {
51 | t.Run(tt.name, func(t *testing.T) {
52 | if got := reverse(tt.args.s); got != tt.want {
53 | t.Errorf("reverse() = %v, want %v", got, tt.want)
54 | }
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/07-testing/4-refactoring/refactoring.go:
--------------------------------------------------------------------------------
1 | package refactoring
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "net/http"
7 | )
8 |
9 | // Rates - курсы валют.
10 | type Rates struct {
11 | Valute struct {
12 | USD struct {
13 | ID string `json:"ID"`
14 | NumCode string `json:"NumCode"`
15 | CharCode string `json:"CharCode"`
16 | Nominal int `json:"Nominal"`
17 | Name string `json:"Name"`
18 | Value float64 `json:"Value"`
19 | Previous float64 `json:"Previous"`
20 | } `json:"USD"`
21 | }
22 | }
23 |
24 | func rublesToUSD(rubles float64) (float64, error) {
25 | ratio, err := data()
26 | if err != nil {
27 | return 0, err
28 | }
29 |
30 | return calc(rubles, ratio), nil
31 | }
32 |
33 | func data() (float64, error) {
34 | var data Rates
35 | const url = "https://www.cbr-xml-daily.ru/daily_json.js"
36 | resp, err := http.Get(url)
37 | if err != nil {
38 | return 0, err
39 | }
40 |
41 | err = json.NewDecoder(resp.Body).Decode(&data)
42 | if err != nil {
43 | return 0, err
44 | }
45 |
46 | if data.Valute.USD.Value == 0 {
47 | return 0, errors.New("деление на 0")
48 | }
49 |
50 | return data.Valute.USD.Value, nil
51 | }
52 |
53 | func calc(rubles float64, ratio float64) float64 {
54 | return rubles / ratio
55 | }
56 |
--------------------------------------------------------------------------------
/07-testing/4-refactoring/refactoring_test.go:
--------------------------------------------------------------------------------
1 | package refactoring
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_rublesToUSD(t *testing.T) {
8 | const rubles = 1000
9 | res, err := rublesToUSD(rubles)
10 | if err != nil {
11 | t.Fatal(err)
12 | }
13 | t.Logf("%v рублей - это %.2f долларов\n", rubles, res)
14 | }
15 |
16 | func Test_calc(t *testing.T) {
17 | want := 10
18 | got := calc(1000, 100)
19 | if got != float64(want) {
20 | t.Fatalf("got %v, want %v", got, want)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/07-testing/5-db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | type Product struct {
4 | Name string
5 | Price int
6 | }
7 |
8 | func Products() ([]Product, error) {
9 | data := []Product{
10 | {
11 | Name: "Пепелац",
12 | Price: 5, // в Кэцэ
13 | },
14 | }
15 | return data, nil
16 | }
17 |
18 | func NewProduct(Product) error {
19 | return nil
20 | }
21 |
22 | func UpdateProduct(Product) error {
23 | return nil
24 | }
25 |
26 | func DeleteProduct(Product) error {
27 | return nil
28 | }
29 |
--------------------------------------------------------------------------------
/07-testing/5-db/db_test.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestProductCRUD(t *testing.T) {
8 | if !testing.Short() {
9 | t.Skip()
10 | }
11 |
12 | var item = Product{
13 | Name: "Пепелац",
14 | Price: 5, // в Кэцэ
15 | }
16 |
17 | err := NewProduct(item)
18 | if err != nil {
19 | t.Error(err)
20 | }
21 |
22 | data, err := Products()
23 | if err != nil {
24 | t.Error(err)
25 | }
26 | found := false
27 | for _, p := range data {
28 | if p.Name == item.Name {
29 | found = true
30 | }
31 | }
32 | if !found {
33 | t.Errorf("не найден продукт: %v", item)
34 | }
35 |
36 | err = UpdateProduct(item)
37 | if err != nil {
38 | t.Error(err)
39 | }
40 | data, err = Products()
41 | if err != nil {
42 | t.Error(err)
43 | }
44 | found = false
45 | for _, p := range data {
46 | if p.Name == item.Name {
47 | found = true
48 | }
49 | }
50 | if !found {
51 | t.Errorf("не найден продукт: %v", item)
52 | }
53 |
54 | err = DeleteProduct(item)
55 | if err != nil {
56 | t.Error(err)
57 | }
58 | data, err = Products()
59 | if err != nil {
60 | t.Error(err)
61 | }
62 | found = false
63 | for _, p := range data {
64 | if p.Name == item.Name {
65 | found = true
66 | }
67 | }
68 | if !found {
69 | t.Errorf("не удалён продукт: %v", item)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/07-testing/6-handler/handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | // API - API.
9 | type API struct {
10 | dbConn string
11 | }
12 |
13 | type product struct {
14 | Name string
15 | Price int
16 | }
17 |
18 | func (api API) ProductsHandler(w http.ResponseWriter, r *http.Request) {
19 | data := []product{
20 | {
21 | Name: "Книга",
22 | Price: 1000,
23 | },
24 | }
25 |
26 | err := json.NewEncoder(w).Encode(data)
27 | if err != nil {
28 | http.Error(w, err.Error(), http.StatusInternalServerError)
29 | return
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/07-testing/6-handler/handler_test.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | var api API
12 |
13 | func TestAPI_ProductsHandler(t *testing.T) {
14 | // Создание объекта запроса.
15 | req := httptest.NewRequest(http.MethodGet, "/api/HANDLER", nil)
16 |
17 | // Создание объекта для записи ответа.
18 | rr := httptest.NewRecorder()
19 |
20 | // Вызов тестируемого обработчика.
21 | api.ProductsHandler(rr, req)
22 |
23 | // Проверка ответа.
24 | if !(rr.Code == http.StatusOK) {
25 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
26 | }
27 | resp := rr.Result()
28 | body, _ := ioutil.ReadAll(resp.Body)
29 | var data []product
30 | json.Unmarshal(body, &data)
31 | t.Logf("Ответ сервера:\n%v\n", data)
32 | }
33 |
--------------------------------------------------------------------------------
/07-testing/7-tdd/tdd.go:
--------------------------------------------------------------------------------
1 | package tdd
2 |
3 | func Fact(n int) int {
4 | if n < 1 {
5 | return 1
6 | }
7 |
8 | return n * Fact(n-1)
9 | }
10 |
--------------------------------------------------------------------------------
/07-testing/7-tdd/tdd_test.go:
--------------------------------------------------------------------------------
1 | package tdd
2 |
3 | import "testing"
4 |
5 | func TestFact(t *testing.T) {
6 | type args struct {
7 | n int
8 | }
9 | tests := []struct {
10 | name string
11 | args args
12 | want int
13 | }{
14 | {
15 | name: "Test #1",
16 | args: args{n: 1},
17 | want: 1,
18 | },
19 | {
20 | name: "Test #2",
21 | args: args{n: 3},
22 | want: 6,
23 | },
24 | {
25 | name: "Test #2",
26 | args: args{n: 0},
27 | want: 1,
28 | },
29 | }
30 | for _, tt := range tests {
31 | t.Run(tt.name, func(t *testing.T) {
32 | if got := Fact(tt.args.n); got != tt.want {
33 | t.Errorf("Fact() = %v, want %v", got, tt.want)
34 | }
35 | })
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/07-testing/8-benchmarks/search.go:
--------------------------------------------------------------------------------
1 | package bsearch
2 |
3 | // Binary возвращает номер элемента в массиве или -1. Используется бинарный поиск.
4 | func Binary(data []int, item int) int {
5 | low, high := 0, len(data)-1
6 | for low <= high {
7 | mid := (low + high) / 2
8 | if data[mid] == item {
9 | return mid
10 | }
11 | if data[mid] < item {
12 | low = mid + 1
13 | } else {
14 | high = mid - 1
15 | }
16 | }
17 | return -1
18 | }
19 |
20 | // Simple возвращает номер элемента в массиве или -1. Используется простой поиск.
21 | func Simple(data []int, item int) int {
22 | for i := range data {
23 | if data[i] == item {
24 | return i
25 | }
26 | }
27 | return -1
28 | }
29 |
--------------------------------------------------------------------------------
/07-testing/8-benchmarks/search_test.go:
--------------------------------------------------------------------------------
1 | package bsearch
2 |
3 | import (
4 | "math/rand"
5 | "sort"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestSearches(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | data []int
14 | item int
15 | want int
16 | }{
17 | {
18 | name: "#1",
19 | data: []int{1, 2, 4, 6, 10},
20 | item: 6,
21 | want: 3,
22 | },
23 | {
24 | name: "#2",
25 | data: []int{1, 2, 4, 5, 6, 10},
26 | item: 6,
27 | want: 4,
28 | },
29 | {
30 | name: "#3",
31 | data: []int{1, 2, 4, 5, 6, 10},
32 | item: 16,
33 | want: -1,
34 | },
35 | {
36 | name: "#4",
37 | data: []int{1, 2, 4, 5, 6, 10},
38 | item: 2,
39 | want: 1,
40 | },
41 | {
42 | name: "#5",
43 | data: []int{1, 2, 4, 5, 6, 10, 100},
44 | item: 4,
45 | want: 2,
46 | },
47 | }
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | if got := Binary(tt.data, tt.item); got != tt.want {
51 | t.Errorf("Binary() = %v, want %v", got, tt.want)
52 | }
53 | if got := Simple(tt.data, tt.item); got != tt.want {
54 | t.Errorf("Simple() = %v, want %v", got, tt.want)
55 | }
56 | })
57 | }
58 | }
59 |
60 | func sampleData() []int {
61 | rand.Seed(time.Now().UnixNano())
62 | var data []int
63 | for i := 0; i < 1_000_000; i++ {
64 | data = append(data, rand.Intn(1000))
65 | }
66 |
67 | sort.Slice(data, func(i, j int) bool { return data[i] < data[j] })
68 | return data
69 | }
70 |
71 | func BenchmarkBinary(b *testing.B) {
72 | data := sampleData()
73 | b.ResetTimer()
74 |
75 | for i := 0; i < b.N; i++ {
76 | n := rand.Intn(1000)
77 | res := Binary(data, n)
78 | _ = res
79 | }
80 | }
81 |
82 | func BenchmarkSimple(b *testing.B) {
83 | data := sampleData()
84 | b.ResetTimer()
85 |
86 | for i := 0; i < b.N; i++ {
87 | n := rand.Intn(1000)
88 | res := Simple(data, n)
89 | _ = res
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/07-testing/flags/flag_test.go:
--------------------------------------------------------------------------------
1 | package flags
2 |
3 | import (
4 | "flag"
5 | "testing"
6 | )
7 |
8 | var f = flag.Bool("int", false, "")
9 |
10 | func TestFunc(t *testing.T) {
11 | flag.Parse()
12 | if *f {
13 | t.Log("FLAG")
14 | }
15 | if !*f {
16 | t.Log("NO FLAG")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/08-prof_debug/1-bench_profile/search.go:
--------------------------------------------------------------------------------
1 | package bsearch
2 |
3 | // Binary возвращает номер элемента в массиве или -1. Используется бинарный поиск.
4 | func Binary(data []int, item int) int {
5 | low, high := 0, len(data)-1
6 | for low <= high {
7 | mid := (low + high) / 2
8 | if data[mid] == item {
9 | return mid
10 | }
11 | if data[mid] < item {
12 | low = mid + 1
13 | } else {
14 | high = mid - 1
15 | }
16 | }
17 | return -1
18 | }
19 |
20 | // Simple возвращает номер элемента в массиве или -1. Используется простой поиск.
21 | func Simple(data []int, item int) int {
22 | for i := range data {
23 | if data[i] == item {
24 | return i
25 | }
26 | }
27 | return -1
28 | }
29 |
--------------------------------------------------------------------------------
/08-prof_debug/1-bench_profile/search_test.go:
--------------------------------------------------------------------------------
1 | package bsearch
2 |
3 | import (
4 | "math/rand"
5 | "sort"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestSearches(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | data []int
14 | item int
15 | want int
16 | }{
17 | {
18 | name: "#1",
19 | data: []int{1, 2, 4, 6, 10},
20 | item: 6,
21 | want: 3,
22 | },
23 | {
24 | name: "#2",
25 | data: []int{1, 2, 4, 5, 6, 10},
26 | item: 6,
27 | want: 4,
28 | },
29 | {
30 | name: "#3",
31 | data: []int{1, 2, 4, 5, 6, 10},
32 | item: 16,
33 | want: -1,
34 | },
35 | {
36 | name: "#4",
37 | data: []int{1, 2, 4, 5, 6, 10},
38 | item: 2,
39 | want: 1,
40 | },
41 | {
42 | name: "#5",
43 | data: []int{1, 2, 4, 5, 6, 10, 100},
44 | item: 4,
45 | want: 2,
46 | },
47 | }
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | if got := Binary(tt.data, tt.item); got != tt.want {
51 | t.Errorf("Binary() = %v, want %v", got, tt.want)
52 | }
53 | if got := Simple(tt.data, tt.item); got != tt.want {
54 | t.Errorf("Simple() = %v, want %v", got, tt.want)
55 | }
56 | })
57 | }
58 | }
59 |
60 | func sampleData() []int {
61 | rand.Seed(time.Now().UnixNano())
62 | var data []int
63 | for i := 0; i < 1_000_000; i++ {
64 | data = append(data, rand.Intn(1000))
65 | }
66 |
67 | sort.Slice(data, func(i, j int) bool { return data[i] < data[j] })
68 | return data
69 | }
70 |
71 | func BenchmarkBinary(b *testing.B) {
72 | data := sampleData()
73 | for i := 0; i < b.N; i++ {
74 | n := rand.Intn(1000)
75 | res := Binary(data, n)
76 | _ = res
77 | }
78 | }
79 |
80 | func BenchmarkSimple(b *testing.B) {
81 | data := sampleData()
82 | for i := 0; i < b.N; i++ {
83 | n := rand.Intn(1000)
84 | res := Simple(data, n)
85 | _ = res
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/08-prof_debug/2-app_profile/app-profile.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "math"
5 | "net/http"
6 | _ "net/http/pprof"
7 | )
8 |
9 | func main() {
10 | go compute1(1_000_000, 1_000_000)
11 | go compute2(2_000_000, 2_000_000)
12 |
13 | go mem1()
14 | go mem2()
15 |
16 | http.ListenAndServe(":80", nil)
17 | }
18 |
19 | func compute1(n, m int) {
20 | for i := 0; i < n; i++ {
21 | for j := 0; j < m; j++ {
22 | _ = math.Sin(float64(i)) * math.Sin(float64(j))
23 | }
24 | }
25 | }
26 |
27 | func compute2(n, m int) {
28 | for i := 0; i < n; i++ {
29 | for j := 0; j < m; j++ {
30 | _ = math.Cos(float64(i)) * math.Cos(float64(j))
31 | }
32 | }
33 | }
34 |
35 | func mem1() {
36 | var arr []int32
37 | for i := 0; i < 10_000_000; i++ {
38 | arr = append(arr, int32(i))
39 | }
40 | _ = arr
41 | select {}
42 | }
43 |
44 | func mem2() {
45 | var arr []int64
46 | for i := 20_000_000; i > 0; i-- {
47 | arr = append(arr, int64(i))
48 | }
49 | _ = arr
50 | select {}
51 | }
52 |
--------------------------------------------------------------------------------
/08-prof_debug/3-debug/two_sum.go:
--------------------------------------------------------------------------------
1 | /*
2 | Given an array of integers nums and an integer target,
3 | return indices of the two numbers such that they add up to target.
4 | You may assume that each input would have exactly one solution,
5 | and you may not use the same element twice.
6 | You can return the answer in any order.
7 |
8 | Example 1:
9 |
10 | Input: nums = [2,7,11,15], target = 9
11 | Output: [0,1]
12 | Output: Because nums[0] + nums[1] == 9, we return [0, 1].
13 | Example 2:
14 |
15 | Input: nums = [3,2,4], target = 6
16 | Output: [1,2]
17 | */
18 | package main
19 |
20 | import "reflect"
21 |
22 | var a int
23 |
24 | func main() {
25 | a = 10
26 | _ = a
27 | go double(a)
28 |
29 | res := twoSum([]int{2, 7, 11, 15}, 13)
30 | if !reflect.DeepEqual(res, [2]int{0, 2}) {
31 | println("неверный результат")
32 | return
33 | }
34 | println("Номера элементов:", res[0], res[1])
35 |
36 | res = twoSum([]int{2, 10, 6, 12, 14}, 20)
37 | if !reflect.DeepEqual(res, [2]int{2, 4}) {
38 | println("неверный результат")
39 | return
40 | }
41 | println("Номера элементов:", res[0], res[1])
42 | }
43 |
44 | func twoSum(nums []int, target int) [2]int {
45 | for i := 0; i < len(nums); i++ {
46 | for j := 0; j < len(nums); j++ {
47 | if nums[i]+nums[j] == target {
48 | return [2]int{i, j}
49 | }
50 | }
51 | }
52 |
53 | return [2]int{-1, -1}
54 | }
55 |
56 | func double(a int) int {
57 | b := a * a
58 | return b
59 | }
60 |
--------------------------------------------------------------------------------
/08-prof_debug/4-trace/trace_cpu/trace.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "math"
6 | "os"
7 | "runtime/trace"
8 | "time"
9 | )
10 |
11 | func main() {
12 | // файл, в который будет сохраняться трассировка
13 | f, err := os.Create("trace.out")
14 | if err != nil {
15 | log.Fatal(err)
16 | }
17 | // запуск трассировки и отложенное завершение
18 | trace.Start(f)
19 | defer f.Close()
20 | defer trace.Stop()
21 |
22 | go compute1()
23 | go compute1()
24 | go compute2()
25 | go compute2()
26 |
27 | time.Sleep(time.Second * 1)
28 | }
29 |
30 | func compute1() {
31 | for {
32 | _ = math.Sin(1) * math.Cos(10)
33 | }
34 | }
35 |
36 | func compute2() {
37 | for {
38 | _ = math.Tan(1)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/08-prof_debug/4-trace/trace_mem/trace.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "runtime"
7 | "runtime/trace"
8 | )
9 |
10 | func main() {
11 |
12 | // файл, в который будет сохраняться трассировка
13 | f, err := os.Create("trace.out")
14 | if err != nil {
15 | log.Fatal(err)
16 | }
17 |
18 | // запуск трассировки и отложенное завершение
19 | trace.Start(f)
20 | defer f.Close()
21 | defer trace.Stop()
22 |
23 | nums := fillSlice()
24 | emptySlice(nums)
25 |
26 | runtime.GC()
27 |
28 | nums = fillSlice()
29 | emptySlice(nums)
30 | }
31 |
32 | func fillSlice() []int {
33 | var nums []int
34 | for i := 0; i < 100_000; i++ { // Изменить на 300_000.
35 | nums = append(nums, i)
36 | }
37 | return nums
38 | }
39 |
40 | func emptySlice(nums []int) {
41 | for i := len(nums) - 1; i >= 0; i-- {
42 | nums = append(nums[:i], nums[:i+1]...)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/09-interfaces/1-polymorph/polymorph.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "encoding/xml"
6 | "io"
7 | "log"
8 | "os"
9 | )
10 |
11 | // serializer - абстрактный интерфейсный тип даных.
12 | // Объявляет контракт на сериализацию.
13 | type serializer interface {
14 | serialize() ([]byte, error) // контракт интерфейса
15 | }
16 |
17 | // Конкретный тип данных.
18 | type person struct {
19 | Name string
20 | Age int
21 | }
22 |
23 | // Метод, выполняющий контракт интерфейса.
24 | func (p *person) serialize() ([]byte, error) {
25 | b, err := json.Marshal(p)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return b, nil
30 | }
31 |
32 | //
33 | // Другой тип, выполняющий контракт интерфейса.
34 | //
35 |
36 | type car struct {
37 | Model string
38 | Year int
39 | }
40 |
41 | func (c *car) serialize() ([]byte, error) {
42 | b, err := xml.Marshal(c)
43 | if err != nil {
44 | return nil, err
45 | }
46 | return b, nil
47 | }
48 |
49 | // Полиморфическая функция, принимающая интерфейс.
50 | func store(s serializer, w io.Writer) error {
51 | if wc2, ok := w.(io.WriteCloser); ok {
52 | defer wc2.Close()
53 | }
54 |
55 | b, err := s.serialize()
56 | if err != nil {
57 | return err
58 | }
59 |
60 | _, err = w.Write(b)
61 | return err
62 | }
63 |
64 | func main() {
65 | p := person{
66 | Name: "Курт",
67 | Age: 27,
68 | }
69 | var s serializer = &p
70 |
71 | f, err := os.Create("./output.txt")
72 | if err != nil {
73 | log.Fatal(err)
74 | }
75 |
76 | err = store(s, f)
77 | if err != nil {
78 | log.Fatal(err)
79 | }
80 |
81 | err = store(&p, f)
82 | if err != nil {
83 | log.Fatal(err)
84 | }
85 |
86 | // *************************************************** //
87 |
88 | c := car{
89 | Model: "Beetle",
90 | Year: 1970,
91 | }
92 |
93 | err = store(&c, os.Stdout)
94 | if err != nil {
95 | log.Fatal(err)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/09-interfaces/2-quiz/quiz.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | var iface any // 0
9 | var f func() // 1
10 | var m map[string]int // 2
11 | var p *int = nil
12 | var iface2 any = p // 3
13 | cycle(iface, f, m, iface2)
14 | }
15 |
16 | func cycle(ifaces ...any) {
17 | for i, iface := range ifaces {
18 | if iface == nil {
19 | fmt.Println(i)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/09-interfaces/3-assertion/assertion.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | )
7 |
8 | type modern interface {
9 | isModern() bool
10 | }
11 |
12 | type old interface {
13 | isOld() bool
14 | isObsolete() bool
15 | }
16 |
17 | type cassete struct {
18 | label string
19 | }
20 |
21 | func (c cassete) isModern() bool {
22 | return false
23 | }
24 |
25 | func (c cassete) isOld() bool {
26 | return true
27 | }
28 |
29 | func (c *cassete) isObsolete() bool {
30 | return false
31 | }
32 |
33 | func main() {
34 | var m modern
35 | var c cassete
36 |
37 | m = c
38 |
39 | //m.label = "Ace Of Base" - ошибка
40 | m.isModern()
41 |
42 | switch c1 := m.(type) {
43 | case cassete:
44 | c1.label = "Ace Of Base"
45 | case old:
46 | c1.isOld()
47 | }
48 |
49 | if casseteInstance, ok := m.(cassete); ok {
50 | casseteInstance.label = "Ace Of Base"
51 | log.Printf("%+v", casseteInstance)
52 | }
53 |
54 | if oldInstance, ok := m.(old); ok {
55 | fmt.Println("Old: ", oldInstance.isOld(), "\tObsolete: ", oldInstance.isObsolete())
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/09-interfaces/4-type_switch/switch.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | slice := []any{10, "String", true}
9 | for _, v := range slice {
10 | switch v.(type) {
11 | case int:
12 | fmt.Println("Число:", v)
13 | case string:
14 | fmt.Println("Строка:", v)
15 | default:
16 | fmt.Println("Неизвестно:", v)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/09-interfaces/5-important/important.go:
--------------------------------------------------------------------------------
1 | package important
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | type E struct {
9 | code int
10 | msg string
11 | }
12 |
13 | func (e *E) Error() string {
14 | return e.msg + fmt.Sprintf(": %v", e.code)
15 | }
16 |
17 | // builtin
18 | type error interface {
19 | Error() string
20 | }
21 |
22 | // io
23 | type Reader interface {
24 | Read(p []byte) (n int, err error)
25 | }
26 |
27 | // io
28 | type Writer interface {
29 | Write(p []byte) (n int, err error)
30 | }
31 |
32 | // sort
33 | type Interface interface {
34 | Len() int
35 | Less(i, j int) bool
36 | Swap(i, j int)
37 | }
38 |
39 | // fmt
40 | type Stringer interface {
41 | String() string
42 | }
43 |
44 | // net
45 | type Conn interface {
46 | Read(b []byte) (n int, err error)
47 | Write(b []byte) (n int, err error)
48 | Close() error
49 | LocalAddr() Addr
50 | RemoteAddr() Addr
51 | SetDeadline(t time.Time) error
52 | SetReadDeadline(t time.Time) error
53 | SetWriteDeadline(t time.Time) error
54 | }
55 |
56 | // net
57 | type Listener interface {
58 | Accept() (Conn, error)
59 | Close() error
60 | Addr() Addr
61 | }
62 |
63 | // net/http
64 | type Handler interface {
65 | ServeHTTP(ResponseWriter, *Request)
66 | }
67 |
68 | // net/http
69 | type ResponseWriter interface {
70 | Header() Header
71 | Write([]byte) (int, error)
72 | WriteHeader(int)
73 | }
74 |
75 | // encoding/json
76 | type Marshaler interface {
77 | MarshalJSON() ([]byte, error)
78 | }
79 |
80 | // Для целей документирования.
81 | // Реальные типы другие!
82 | type Header int
83 | type Addr int
84 | type Request int
85 |
--------------------------------------------------------------------------------
/09-interfaces/6-generics/generics.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "golang.org/x/exp/slices"
7 | )
8 |
9 | type ordered interface {
10 | int | int64 | float64 | float32 | byte | string
11 | }
12 |
13 | // min - обобщённая функция, возврающая минимальный элемент в массиве.
14 | func min[T ordered](in []T) (T, bool) {
15 | var res T
16 | if len(in) == 0 {
17 | return res, false
18 | }
19 | res = in[0]
20 | for _, v := range in {
21 | if v < res {
22 | res = v
23 | }
24 | }
25 | return res, true
26 | }
27 |
28 | func main() {
29 | ints := []int{-2, 0, 2, 3}
30 | floats := []float32{2.3, 3.4, 4.5}
31 | strings := []string{"def", "Абв", "ABC"}
32 |
33 | if val, ok := min[int](ints); ok {
34 | fmt.Printf("%v\n", val)
35 | }
36 |
37 | if val, ok := min(floats); ok {
38 | fmt.Printf("%v\n", val)
39 | }
40 |
41 | if val, ok := min(strings); ok {
42 | fmt.Printf("%v\n", val)
43 | }
44 |
45 | var slice = []int{1, 2, 3}
46 | fmt.Println(slices.Contains(slice, 2))
47 | fmt.Println(slices.Contains(slice, 20))
48 | }
49 |
--------------------------------------------------------------------------------
/09-interfaces/7-DIP/dip.go:
--------------------------------------------------------------------------------
1 | package dip
2 |
3 | // База данных - деталь реализации.
4 | type DB []Book
5 |
6 | type Book struct {
7 | ID int
8 | Title string
9 | }
10 |
11 | // Интерфейс - часть бизнес-логики.
12 | type Storage interface {
13 | Books() []Book
14 | }
15 |
16 | func (db DB) Books() []Book {
17 | return []Book{}
18 | }
19 |
20 | // Нарушение принципа DIP.
21 | type WrongServer struct {
22 | db DB
23 | }
24 |
25 | // Выполнение принципа DIP.
26 | type GoodServer struct {
27 | db Storage
28 | }
29 |
30 | // Бизнес-логика системы.
31 | func (s *WrongServer) BusinessLogic() {
32 | // Принцип DIP нарушен, обращение к БД.
33 | books := s.db.Books()
34 | _ = books
35 | }
36 |
37 | // Бизнес-логика системы.
38 | func (s *GoodServer) BusinessLogic() {
39 | // Принцип DIP выполняется, обращение к интерфейсу.
40 | books := s.db.Books()
41 | _ = books
42 | }
43 |
--------------------------------------------------------------------------------
/10-concurrency/1-goroutine/goroutine.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func printN(n int) int {
9 | fmt.Println(n)
10 | return n * 2
11 | }
12 |
13 | func main() {
14 | for i := 0; i < 10; i++ {
15 | go printN(i) // что будет выведено на экран?
16 | }
17 |
18 | time.Sleep(time.Second)
19 | }
20 |
--------------------------------------------------------------------------------
/10-concurrency/10-pattern-pipeline/pipeline.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func gen(nums ...int) <-chan int {
6 | out := make(chan int)
7 | go func() {
8 | for _, n := range nums {
9 | out <- n
10 | }
11 | close(out)
12 | }()
13 | return out
14 | }
15 |
16 | func proc(in <-chan int) <-chan int {
17 | out := make(chan int)
18 | go func() {
19 | for n := range in {
20 | out <- n * n
21 | }
22 | close(out)
23 | }()
24 | return out
25 | }
26 |
27 | func proc2(in <-chan int) <-chan int {
28 | out := make(chan int)
29 | go func() {
30 | for n := range in {
31 | out <- n * n * n
32 | }
33 | close(out)
34 | }()
35 | return out
36 | }
37 |
38 | func main() {
39 | res := proc(proc2(proc2(proc(gen(1, 2, 3, 4, 5)))))
40 |
41 | for val := range res {
42 | fmt.Println(val)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/10-concurrency/11-context/context.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 | )
8 |
9 | func worker(ctx context.Context, in <-chan string, out chan<- string) {
10 | for {
11 | select {
12 | case <-ctx.Done():
13 | fmt.Println("объект контекста вызвал завершение потока!")
14 | close(out)
15 | fmt.Println("Val:", ctx.Value("key"))
16 | return
17 | case val := <-in:
18 | fmt.Println("Обработка сообщения:", val)
19 | out <- fmt.Sprintf("Сообщение %s обработано", val)
20 | }
21 | }
22 | }
23 |
24 | func main() {
25 | data := make(chan string)
26 | result := make(chan string)
27 | ctx, cancel := context.WithTimeout(context.Background(), 700*time.Millisecond)
28 | ctx = context.WithValue(ctx, "key", "val")
29 | defer cancel()
30 |
31 | go worker(ctx, data, result)
32 |
33 | go func() {
34 | for i := 0; i < 10; i++ {
35 | if i > 8 {
36 | cancel()
37 | break
38 | }
39 | data <- fmt.Sprintf("сообщение #%d", i)
40 | time.Sleep(time.Millisecond * 500)
41 | }
42 | }()
43 |
44 | for val := range result {
45 | fmt.Println(val)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/10-concurrency/2-chan/chan.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | ch := make(chan int)
10 |
11 | go func() {
12 | for i := 1; i <= 5; i++ {
13 | ch <- i
14 | time.Sleep(time.Second)
15 | }
16 | close(ch)
17 | }()
18 |
19 | for {
20 | val, ok := <-ch
21 | fmt.Println(val, ok)
22 | if !ok {
23 | break
24 | }
25 | }
26 |
27 | for val := range ch {
28 | fmt.Println(val)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/10-concurrency/3-chan_select/select.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "strconv"
7 | "time"
8 | )
9 |
10 | func generator(ch chan<- string, num int) {
11 | for i := 0; i < 5; i++ {
12 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
13 | ch <- "Сообщение из канала №" + strconv.Itoa(num)
14 | }
15 | close(ch)
16 | }
17 |
18 | func main() {
19 | rand.Seed(time.Now().UnixNano())
20 | ch1 := make(chan string)
21 | ch2 := make(chan string)
22 | go generator(ch1, 1)
23 | go generator(ch2, 2)
24 |
25 | OUT:
26 | for {
27 | select {
28 | case val, ok := <-ch1:
29 | if !ok {
30 | break OUT
31 | }
32 | fmt.Println(val)
33 | case val := <-ch2:
34 | fmt.Println(val)
35 | default:
36 | continue
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/10-concurrency/4-waitgroup/sync.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | func printN(n int, wg *sync.WaitGroup) {
9 | defer wg.Done()
10 | fmt.Println(n)
11 | }
12 |
13 | func step1(ch chan<- string) {
14 | // ...
15 | ch <- "message from Step 1"
16 | }
17 | func step2(ch <-chan string) {
18 | fmt.Println(<-ch)
19 | // ....
20 | }
21 |
22 | func main() {
23 | const N = 10
24 | var wg sync.WaitGroup
25 | wg.Add(N)
26 | // Запуск отдельных потоков.
27 | for i := 0; i < N; i++ {
28 | go printN(i, &wg)
29 | }
30 | // Ожидание завершения потоков.
31 | wg.Wait()
32 |
33 | // Синхронизация двух потоков с помощью
34 | // сообщений в канале.
35 | var ch = make(chan string)
36 | go step1(ch)
37 | step2(ch)
38 | }
39 |
--------------------------------------------------------------------------------
/10-concurrency/5-shared_mem/shared.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | // Критическая секция.
9 | var mu sync.Mutex
10 | var counter int
11 |
12 | // inc прибавляет x к sum
13 | func inc(wg *sync.WaitGroup) {
14 | defer wg.Done()
15 |
16 | for i := 0; i < 10_000; i++ {
17 | mu.Lock()
18 | counter++
19 | mu.Unlock()
20 | }
21 | }
22 |
23 | func main() {
24 | var wg sync.WaitGroup
25 | wg.Add(2)
26 |
27 | go inc(&wg)
28 | go inc(&wg)
29 |
30 | wg.Wait()
31 |
32 | fmt.Println(counter) // Что будет на экране?
33 | }
34 |
--------------------------------------------------------------------------------
/10-concurrency/6-common_errors/errors.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func error1() {
9 | // Ошибка №1. Доступ к итератору цикла через замыкание.
10 | for i := 0; i < 10; i++ {
11 | go func() {
12 | fmt.Println(i)
13 | }()
14 | }
15 | time.Sleep(1)
16 | }
17 |
18 | func error2() {
19 | for i := 0; i < 10; i++ {
20 | go func(i int) {
21 | fmt.Println(i)
22 | }(i)
23 | }
24 | // Ошибка №2. Использование таймера для синхронизации.
25 | time.Sleep(1)
26 | }
27 |
28 | func error3() {
29 | f := func(i int) {
30 | fmt.Println(i)
31 | }
32 | // Ошибка №3. Отсутствие контроля за завершением потоков.
33 | for i := 0; i < 10; i++ {
34 | go f(i)
35 | }
36 | }
37 |
38 | func main() {
39 | //error1()
40 | //error2()
41 | //error3()
42 | }
43 |
--------------------------------------------------------------------------------
/10-concurrency/7-atomic/atomic.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "sync/atomic"
7 | )
8 |
9 | func main() {
10 | var c atomic.Int64
11 |
12 | N := 100_000
13 |
14 | var wg sync.WaitGroup
15 | wg.Add(N)
16 | for i := 0; i < N; i++ {
17 | go func() {
18 | c.Add(1)
19 | wg.Done()
20 | }()
21 | }
22 | wg.Wait()
23 |
24 | fmt.Println("Счётчик:", c.Load())
25 | }
26 |
--------------------------------------------------------------------------------
/10-concurrency/8-pattern-fan-out/fan-out.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "sync"
7 | )
8 |
9 | func processor(in <-chan int, out chan<- int) {
10 | for val := range in {
11 | out <- val * val
12 | }
13 | }
14 |
15 | func main() {
16 | src := make(chan int)
17 | res := make(chan int)
18 |
19 | n := runtime.NumCPU()
20 | // Запуск рабочих потоков.
21 | var wg sync.WaitGroup
22 | wg.Add(n)
23 | for i := 0; i < n; i++ {
24 | go func() {
25 | defer wg.Done()
26 | processor(src, res)
27 | }()
28 | }
29 |
30 | // Поток с заданиями.
31 | go func() {
32 | for i := 0; i < 10; i++ {
33 | src <- i
34 | }
35 | close(src)
36 | }()
37 |
38 | // Поток с обработкой результатов.
39 | go func() {
40 | for val := range res {
41 | fmt.Println(val)
42 | }
43 | }()
44 |
45 | wg.Wait()
46 |
47 | close(res)
48 | }
49 |
--------------------------------------------------------------------------------
/10-concurrency/9-pattern-fan-in/fan-in.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | func worker(n int) chan int {
9 | ch := make(chan int)
10 | go func() {
11 | defer close(ch)
12 | ch <- n * n
13 | }()
14 | return ch
15 | }
16 |
17 | func fanIn[T any](channels ...<-chan T) <-chan T {
18 | ch := make(chan T)
19 | var wg sync.WaitGroup
20 | wg.Add(len(channels))
21 |
22 | for _, c := range channels {
23 | go func(in <-chan T) {
24 | defer wg.Done()
25 | for i := range in {
26 | ch <- i
27 | }
28 | }(c)
29 | }
30 |
31 | go func() {
32 | wg.Wait()
33 | close(ch)
34 | }()
35 |
36 | return ch
37 | }
38 |
39 | func main() {
40 | var chans []<-chan int
41 | for i := 0; i < 10; i++ {
42 | chans = append(chans, worker(i))
43 | }
44 |
45 | ch := fanIn(chans...)
46 | for val := range ch {
47 | fmt.Println(val)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/11-network/1-daytime/client/daytime-client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "log"
7 | "net"
8 | )
9 |
10 | func main() {
11 | conn, err := net.Dial("tcp4", "localhost:13")
12 | if err != nil {
13 | log.Fatal(err)
14 | }
15 |
16 | msg, err := io.ReadAll(conn)
17 | if err != nil {
18 | log.Fatal(err)
19 | }
20 |
21 | fmt.Println("Ответ от сервера:", string(msg))
22 | }
23 |
--------------------------------------------------------------------------------
/11-network/1-daytime/server/daytime-server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Сервер текущего времени в соответсвии с RFC 867.
4 |
5 | import (
6 | "io"
7 | "log"
8 | "net"
9 | "time"
10 | )
11 |
12 | // обработчик подключения
13 | func handler(conn io.ReadWriteCloser) {
14 | daytime := time.Now().Format(time.RubyDate)
15 | conn.Write([]byte(daytime))
16 | conn.Close()
17 | }
18 |
19 | func main() {
20 | // регистрация сетевой службы на всех сетевых интерфейсах на порту 13
21 | listener, err := net.Listen("tcp4", "0.0.0.0:13")
22 | if err != nil {
23 | log.Fatal(err)
24 | }
25 | defer listener.Close()
26 |
27 | // цикл обработки клиентских подключений
28 | for {
29 | conn, err := listener.Accept()
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 |
34 | handler(conn)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/11-network/2-echo/echo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Эхо-сервер. Без цензуры.
4 |
5 | import (
6 | "bufio"
7 | "fmt"
8 | "log"
9 | "net"
10 | )
11 |
12 | // обработчик подключения
13 | func handler(conn net.Conn) {
14 | defer conn.Close()
15 | defer fmt.Println("Connection Closed")
16 |
17 | r := bufio.NewReader(conn)
18 | for {
19 | msg, _, err := r.ReadLine()
20 | if err != nil {
21 | return
22 | }
23 |
24 | msg = append(msg, '\n')
25 |
26 | _, err = conn.Write(msg)
27 | if err != nil {
28 | return
29 | }
30 | }
31 | }
32 |
33 | func main() {
34 | // регистрация сетевой службы
35 | listener, err := net.Listen("tcp4", ":12345")
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 | defer listener.Close()
40 |
41 | fmt.Println("Сервер слушает на порту: 12345")
42 |
43 | // цикл обработки клиентских подключений
44 | for {
45 | conn, err := listener.Accept()
46 | if err != nil {
47 | log.Fatal(err)
48 | }
49 | go handler(conn)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/11-network/3-deadline/deadline.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Эхо-сервер. Без цензуры.
4 |
5 | import (
6 | "bufio"
7 | "fmt"
8 | "log"
9 | "net"
10 | "time"
11 | )
12 |
13 | // обработчик подключения
14 | func handler(conn net.Conn) {
15 | defer conn.Close()
16 | defer fmt.Println("Conn closed")
17 |
18 | conn.SetDeadline(time.Now().Add(time.Second * 5))
19 |
20 | r := bufio.NewReader(conn)
21 | for {
22 | msg, _, err := r.ReadLine()
23 | if err != nil {
24 | return
25 | }
26 |
27 | fmt.Println(string(msg))
28 | _, err = conn.Write(msg)
29 | if err != nil {
30 | return
31 | }
32 |
33 | conn.SetDeadline(time.Now().Add(time.Second * 5))
34 | }
35 | }
36 |
37 | func main() {
38 | // регистрация сетевой службы
39 | listener, err := net.Listen("tcp4", ":12345")
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | // цикл обработки клиентских подключений
45 | for {
46 | conn, err := listener.Accept()
47 | if err != nil {
48 | log.Fatal(err)
49 | }
50 | fmt.Println("Conn established")
51 |
52 | go handler(conn)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/11-network/4-testing/timeserver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "log"
6 | "net"
7 | "strings"
8 | "time"
9 | )
10 |
11 | // Сетевой адрес.
12 | //
13 | // Служба будет слушать запросы на всех IP-адресах
14 | // компьютера на порту 12345.
15 | // Нпример, 127.0.0.1:12345
16 | const addr = "0.0.0.0:12345"
17 |
18 | // Протокол сетевой службы.
19 | const proto = "tcp4"
20 |
21 | func main() {
22 | // Запуск сетевой службы про протоколу TCP
23 | // на порту 12345.
24 | listener, err := net.Listen(proto, addr)
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 | // Подключения обрабатываются в бесконечном цикле.
29 | // Иначе после обслуживания первого подключения сервер
30 | //завершит работу.
31 | for {
32 | // Принимаем подключение.
33 | conn, err := listener.Accept()
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 | // Вызов обработчика подключения.
38 | go handleConn(conn)
39 | }
40 | }
41 |
42 | // Обработчик. Вызывается для каждого соединения.
43 | func handleConn(conn net.Conn) {
44 | // Чтение сообщения от клиента.
45 | reader := bufio.NewReader(conn)
46 | b, err := reader.ReadBytes('\n')
47 | if err != nil {
48 | log.Println(err)
49 | return
50 | }
51 |
52 | // Удаление символов конца строки.
53 | msg := strings.TrimSuffix(string(b), "\n")
54 | msg = strings.TrimSuffix(msg, "\r")
55 |
56 | // Если получили "time" - пишем время в соединение.
57 | if msg == "time" {
58 | conn.Write([]byte(time.Now().String() + "\n"))
59 | }
60 |
61 | // Закрытие соединения.
62 | conn.Close()
63 | }
64 |
--------------------------------------------------------------------------------
/11-network/4-testing/timeserver_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "log"
6 | "net"
7 | "sync"
8 | "testing"
9 | )
10 |
11 | func Test_handleConn(t *testing.T) {
12 | // Имитация полнодуплексного
13 | // сетевого соединения.
14 | srv, cl := net.Pipe()
15 |
16 | // Мы должны дождаться
17 | // завершения всех потоков.
18 | var wg sync.WaitGroup
19 | wg.Add(1)
20 |
21 | // Обработчик подключения запускается
22 | // в отдельном потоке.
23 | go func() {
24 | // Обработчику передается один из
25 | // концов виртуальной трубы.
26 | handleConn(srv)
27 | srv.Close()
28 | wg.Done()
29 | }()
30 |
31 | // В основном потоке теста клиент отправляет
32 | // сообщения в трубу.
33 | // Эти сообщения сервер прочитает в соседнем потоке.
34 | _, err := cl.Write([]byte("time\n"))
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 |
39 | // Чтенеие ответа от сервера.
40 | reader := bufio.NewReader(cl)
41 | b, err := reader.ReadBytes('\n')
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 |
46 | // Проверка ответа.
47 | if len(b) < 10 {
48 | t.Error("время не получено")
49 | }
50 | wg.Wait()
51 | cl.Close()
52 | }
53 |
--------------------------------------------------------------------------------
/12-web-apps/1-http/client/simple-client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "log"
8 | "net"
9 | "net/http"
10 | "time"
11 | )
12 |
13 | func main() {
14 | url := "http://localhost:8080"
15 |
16 | // Самый простой вариант запроса.
17 | resp, err := http.Get(url)
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 |
22 | body, err := io.ReadAll(resp.Body)
23 | if err != nil {
24 | log.Fatal(err)
25 | }
26 |
27 | fmt.Println(string(body))
28 |
29 | // Запрос с разными настройками.
30 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
31 | defer cancel()
32 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 |
37 | transport := &http.Transport{
38 | Proxy: http.ProxyFromEnvironment,
39 | DialContext: defaultTransportDialContext(&net.Dialer{
40 | Timeout: 30 * time.Second,
41 | KeepAlive: 30 * time.Second,
42 | }),
43 | ForceAttemptHTTP2: true,
44 | MaxIdleConns: 100,
45 | IdleConnTimeout: 90 * time.Second,
46 | TLSHandshakeTimeout: 10 * time.Second,
47 | ExpectContinueTimeout: 1 * time.Second,
48 | }
49 |
50 | client := http.Client{
51 | Transport: transport,
52 | Timeout: 5 * time.Second,
53 | }
54 |
55 | resp, err = client.Do(req)
56 | if err != nil {
57 | log.Fatal(err)
58 | }
59 |
60 | body, err = io.ReadAll(resp.Body)
61 | if err != nil {
62 | log.Fatal(err)
63 | }
64 |
65 | fmt.Println(string(body))
66 | }
67 |
68 | func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {
69 | return dialer.DialContext
70 | }
71 |
--------------------------------------------------------------------------------
/12-web-apps/1-http/server/simple-server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | func main() {
8 | // регистрация обработчика для URL `/` в маршрутизаторе по умолчанию
9 | http.HandleFunc("/", mainHandler)
10 |
11 | // старт HTTP-сервера на порту 8080 протокола TCP с маршрутизатором запросов по умолчанию
12 | http.ListenAndServe(":8080", nil)
13 | }
14 |
15 | // HTTP-обработчик
16 | func mainHandler(w http.ResponseWriter, r *http.Request) {
17 | w.Write([]byte(`
Go Simple Web App
`))
18 | }
19 |
--------------------------------------------------------------------------------
/12-web-apps/2-custom_server/custom-server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "net/http"
7 | "time"
8 | )
9 |
10 | func main() {
11 | const addr = ":8080"
12 |
13 | // Параметры веб-сервера.
14 | srv := &http.Server{
15 | ReadTimeout: 10 * time.Second,
16 | WriteTimeout: 20 * time.Second,
17 | Handler: nil,
18 | Addr: addr,
19 | }
20 |
21 | // Старт сетевой службы веб-сервера.
22 | listener, err := net.Listen("tcp4", addr)
23 | if err != nil {
24 | log.Fatal(err)
25 | }
26 |
27 | // Регистрация обработчика для URL `/` в маршрутизаторе по умолчанию.
28 | http.HandleFunc("/", mainHandler)
29 |
30 | // Старт самого веб-сервера.
31 | log.Fatal(srv.Serve(listener))
32 | //log.Fatal(srv.ServeTLS(listener, "cert.crt", "cert.key"))
33 | }
34 |
35 | // HTTP-обработчик
36 | func mainHandler(w http.ResponseWriter, r *http.Request) {
37 | w.Write([]byte(`Go Simple Web App
`))
38 | }
39 |
--------------------------------------------------------------------------------
/12-web-apps/3-gorilla_mux/gorilla.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | )
9 |
10 | func main() {
11 | // Сторонний маршрутизатор из пакета Gorilla.
12 | mux := mux.NewRouter()
13 |
14 | // Регистрация обработчика для URL `/` в маршрутизаторе по умолчанию.
15 | mux.HandleFunc("/{name}", mainHandler).Methods(http.MethodGet)
16 |
17 | // Старт HTTP-сервера на порту 8080 протокола TCP с маршрутизатором запросов "mux".
18 | http.ListenAndServe(":8080", mux)
19 | }
20 |
21 | // HTTP-обработчик.
22 | func mainHandler(w http.ResponseWriter, r *http.Request) {
23 | vars := mux.Vars(r)
24 | w.WriteHeader(http.StatusOK)
25 | fmt.Fprintf(w, "Hi, %v
", vars["name"])
26 | }
27 |
--------------------------------------------------------------------------------
/12-web-apps/4-template/template.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "html/template"
6 | "net/http"
7 |
8 | "github.com/gorilla/mux"
9 | )
10 |
11 | func main() {
12 | mux := mux.NewRouter()
13 |
14 | mux.Use(exampleMiddleware)
15 | mux.Use(exampleMiddleware2)
16 |
17 | mux.HandleFunc("/{first}/{last}", mainHandler).Methods(http.MethodGet)
18 |
19 | http.ListenAndServe(":8080", mux)
20 | }
21 |
22 | func exampleMiddleware(next http.Handler) http.Handler {
23 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 | fmt.Printf("Middleware: URL: %v\n", r.RequestURI)
25 | next.ServeHTTP(w, r)
26 | })
27 | }
28 |
29 | func exampleMiddleware2(next http.Handler) http.Handler {
30 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31 | fmt.Printf("Middleware2: URL: %v\n", r.RequestURI)
32 | next.ServeHTTP(w, r)
33 | })
34 | }
35 |
36 | // HTTP-обработчик, возвращающий HTML-страницу.
37 | func mainHandler(w http.ResponseWriter, r *http.Request) {
38 | vars := mux.Vars(r)
39 |
40 | t := template.New("main")
41 |
42 | t, err := t.Parse("Hi, {{.}}
")
43 | if err != nil {
44 | http.Error(w, "ошибка при обработке шаблона", http.StatusInternalServerError)
45 | return
46 | }
47 |
48 | t.Execute(w, vars["first"]+" "+vars["last"])
49 | }
50 |
--------------------------------------------------------------------------------
/12-web-apps/5-testing/testing.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | )
9 |
10 | // сторонний маршрутизатор из пакета Gorilla
11 | var r *mux.Router
12 |
13 | func main() {
14 | r = mux.NewRouter()
15 | endpoints(r)
16 | http.ListenAndServe(":8080", r)
17 | }
18 |
19 | func endpoints(r *mux.Router) {
20 | // Регистрация обработчика для URL `/` в маршрутизаторе по умолчанию.
21 | r.HandleFunc("/{name}", mainHandler).Methods(http.MethodGet)
22 | }
23 |
24 | // HTTP-обработчик.
25 | func mainHandler(w http.ResponseWriter, r *http.Request) {
26 | vars := mux.Vars(r)
27 | w.WriteHeader(http.StatusOK)
28 | fmt.Fprintf(w, "Hi, %v
", vars["name"])
29 | }
30 |
--------------------------------------------------------------------------------
/12-web-apps/5-testing/testing_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | var testMux *mux.Router
15 |
16 | func TestMain(m *testing.M) {
17 | testMux = mux.NewRouter()
18 | endpoints(testMux)
19 | m.Run()
20 | }
21 |
22 | func Test_mainHandler(t *testing.T) {
23 | data := []int{}
24 | payload, _ := json.Marshal(data)
25 |
26 | // Создаём HTTP=запрос.
27 | req := httptest.NewRequest(http.MethodPost, "/Name", bytes.NewBuffer(payload))
28 | req.Header.Add("Сontent-type", "plain/text")
29 |
30 | // Объект для записи ответа HTTP-сервера.
31 | rr := httptest.NewRecorder()
32 |
33 | // Вызов маршрутизатора и обслуживание запроса.
34 | testMux.ServeHTTP(rr, req)
35 |
36 | // Анализ ответа сервера (неверный метод HTTP).
37 | if rr.Code != http.StatusMethodNotAllowed {
38 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
39 | }
40 |
41 | t.Log("Response: ", rr.Body)
42 |
43 | //=========================================================
44 |
45 | req = httptest.NewRequest(http.MethodGet, "/Name", nil)
46 | req.Header.Add("Сontent-type", "plain/text")
47 |
48 | rr = httptest.NewRecorder()
49 |
50 | testMux.ServeHTTP(rr, req)
51 |
52 | if rr.Code != http.StatusOK {
53 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
54 | }
55 | body := rr.Body.String()
56 | if !strings.Contains(body, "Name") {
57 | t.Fatal(body)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/12-web-apps/hw/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/DmitriyVTitov/size"
7 | )
8 |
9 | type example struct {
10 | a []int
11 | b bool
12 | c int32
13 | d string
14 | }
15 |
16 | func main() {
17 | ex := example{
18 | a: []int{1, 2, 3}, // ?
19 | b: true, // ?
20 | d: "1234", // ?
21 | } // ?
22 | fmt.Println("Размер в байтах для ex:", size.Of(ex))
23 | // Как получается результат?
24 |
25 | ex1 := example{
26 | a: []int{1, 2, 3}, // ?
27 | b: true, // ?
28 | d: "1234", // ?
29 | c: 100,
30 | } // ?
31 | fmt.Println("Размер в байтах для ex1:", size.Of(ex1))
32 | // Как получается результат?
33 | }
34 |
--------------------------------------------------------------------------------
/13-api/1-api/api.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | )
9 |
10 | // API предоставляет интерфейс программного взаимодействия.
11 | type API struct {
12 | router *mux.Router
13 | }
14 |
15 | // Endpoints регистрирует конечные точки API.
16 | func (api *API) Endpoints() {
17 | api.router.Use(logMiddleware)
18 | api.router.Use(headersMiddleware)
19 |
20 | api.router.HandleFunc("/api/v1/books", api.books).Methods(http.MethodGet)
21 | api.router.HandleFunc("/api/v1/books", api.newBook).Methods(http.MethodPost)
22 | api.router.HandleFunc("/api/v1/books/{id}", api.deleteBook).Methods(http.MethodDelete)
23 | }
24 |
25 | func (api *API) books(w http.ResponseWriter, r *http.Request) {
26 | err := json.NewEncoder(w).Encode(books)
27 | if err != nil {
28 | http.Error(w, err.Error(), http.StatusInternalServerError)
29 | return
30 | }
31 | }
32 |
33 | func (api *API) newBook(w http.ResponseWriter, r *http.Request) {
34 | var b book
35 | err := json.NewDecoder(r.Body).Decode(&b)
36 | if err != nil {
37 | http.Error(w, err.Error(), http.StatusInternalServerError)
38 | return
39 | }
40 | books = append(books, b)
41 | }
42 |
43 | func (api *API) deleteBook(w http.ResponseWriter, r *http.Request) {
44 | }
45 |
--------------------------------------------------------------------------------
/13-api/1-api/api_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "os"
9 | "testing"
10 |
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | var api *API
15 |
16 | func TestMain(m *testing.M) {
17 | api = new(API)
18 | api.router = mux.NewRouter()
19 | api.Endpoints()
20 | os.Exit(m.Run())
21 | }
22 |
23 | func TestAPI_newBook(t *testing.T) {
24 | want := len(books) + 1 // что здесь плохо?
25 |
26 | data := book{
27 | Name: "1984",
28 | Author: "George Orwell",
29 | }
30 | payload, _ := json.Marshal(data)
31 |
32 | req := httptest.NewRequest(http.MethodPost, "/api/v1/books", bytes.NewBuffer(payload))
33 |
34 | rr := httptest.NewRecorder()
35 |
36 | api.router.ServeHTTP(rr, req)
37 | if rr.Code != http.StatusOK {
38 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
39 | }
40 | t.Log("Response: ", rr.Body)
41 |
42 | got := len(books)
43 |
44 | // что плохо в таком сравнении? как сделать лучше?
45 | if got != want {
46 | t.Fatal("книга не добавлена")
47 | }
48 | }
49 |
50 | func TestAPI_books(t *testing.T) {
51 | req := httptest.NewRequest(http.MethodGet, "/api/v1/books", nil)
52 | rr := httptest.NewRecorder()
53 | api.router.ServeHTTP(rr, req)
54 | if !(rr.Code == http.StatusOK) {
55 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
56 | }
57 | t.Log("Response: ", rr.Body)
58 | }
59 |
--------------------------------------------------------------------------------
/13-api/1-api/middleware.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | )
7 |
8 | func logMiddleware(next http.Handler) http.Handler {
9 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10 | log.Println(r.Method, r.RemoteAddr, r.RequestURI)
11 |
12 | next.ServeHTTP(w, r)
13 | })
14 | }
15 |
16 | func headersMiddleware(next http.Handler) http.Handler {
17 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
19 |
20 | next.ServeHTTP(w, r)
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/13-api/1-api/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | )
9 |
10 | type server struct {
11 | api *API
12 | router *mux.Router
13 | }
14 |
15 | func main() {
16 | srv := new(server)
17 | srv.router = mux.NewRouter()
18 | srv.api = &API{router: srv.router}
19 | srv.api.Endpoints()
20 |
21 | log.Fatal(http.ListenAndServe(":8081", srv.router))
22 | }
23 |
24 | type book struct {
25 | Name string
26 | Author string
27 | }
28 |
29 | var books = []book{
30 | {
31 | Name: "The Lord Of The Rings",
32 | Author: "J.R.R. Tolkien",
33 | },
34 | }
35 |
--------------------------------------------------------------------------------
/13-api/2-api/cmd/server/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 |
6 | "go-core-4/13-api/2-api/pkg/api"
7 | )
8 |
9 | type server struct {
10 | api *api.API
11 | }
12 |
13 | func main() {
14 | srv := new(server)
15 |
16 | srv.api = api.New()
17 |
18 | http.ListenAndServe(":8082", srv.api.Router())
19 | }
20 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | "github.com/gorilla/sessions"
9 | "github.com/rs/zerolog"
10 | )
11 |
12 | // API предоставляет интерфейс программного взаимодействия.
13 | type API struct {
14 | router *mux.Router
15 | store *sessions.CookieStore
16 |
17 | log *zerolog.Logger
18 | }
19 |
20 | // New создаёт объект API.
21 | func New() *API {
22 | api := API{
23 | router: mux.NewRouter(),
24 | store: sessions.NewCookieStore([]byte("secret_password")),
25 | }
26 |
27 | api.endpoints()
28 |
29 | return &api
30 | }
31 |
32 | func (api *API) Router() *mux.Router {
33 | return api.router
34 | }
35 |
36 | // endpoints регистрирует конечные точки API.
37 | func (api *API) endpoints() {
38 | api.router.Use(requestIDMiddleware)
39 | api.router.Use(logMiddleware)
40 | api.router.Use(api.jwtMiddleware)
41 | //api.router.Use(api.sessionsMiddleware)
42 |
43 | api.router.HandleFunc("/api/v1/authSession", api.authSession).Methods(http.MethodPost, http.MethodOptions)
44 | api.router.HandleFunc("/api/v1/authJWT", api.authJWT).Methods(http.MethodPost, http.MethodOptions)
45 |
46 | api.router.HandleFunc("/api/v1/books", api.books).Methods(http.MethodGet, http.MethodOptions)
47 | api.router.HandleFunc("/api/v1/newBook", api.newBook).Methods(http.MethodPost, http.MethodOptions)
48 |
49 | rr, _ := http.NewRequestWithContext(context.Background(), "GET", "https://yandex.ru", nil)
50 |
51 | http.DefaultClient.Do(rr)
52 | }
53 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/api_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | var api *API
9 |
10 | func TestMain(m *testing.M) {
11 | api = New()
12 | os.Exit(m.Run())
13 | }
14 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/books.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "math/rand"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | func (api *API) books(w http.ResponseWriter, r *http.Request) {
12 | err := json.NewEncoder(w).Encode(books)
13 | if err != nil {
14 | http.Error(w, err.Error(), http.StatusInternalServerError)
15 | return
16 | }
17 | }
18 |
19 | func (api *API) newBook(w http.ResponseWriter, r *http.Request) {
20 | // контекст с таймаутом
21 | timeout, cancel := context.WithTimeout(r.Context(), time.Second*10)
22 |
23 | // освобождение ресурсов, если функция завершится раньше
24 | defer cancel()
25 |
26 | // производный контекст с ключом (№ запроса)
27 | ctx := context.WithValue(timeout, "requestID", rand.Intn(1_000_000_000))
28 |
29 | // длительная операция, принимающая контекст
30 | //data := getLotsOfDataFromDatabase(ctx)
31 | _ = ctx
32 |
33 | var b book
34 | err := json.NewDecoder(r.Body).Decode(&b)
35 | if err != nil {
36 | http.Error(w, err.Error(), http.StatusInternalServerError)
37 | return
38 | }
39 | books = append(books, b)
40 | }
41 |
42 | type book struct {
43 | Name string
44 | Author string
45 | }
46 |
47 | var books = []book{
48 | {
49 | Name: "The Lord Of The Rings",
50 | Author: "J.R.R. Tolkien",
51 | },
52 | }
53 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/books_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | func TestAPI_newBook(t *testing.T) {
12 | want := len(books) + 1 // что здесь плохо?
13 | data := book{
14 | Name: "1984",
15 | Author: "George Orwell",
16 | }
17 |
18 | payload, _ := json.Marshal(data)
19 | req := httptest.NewRequest(http.MethodPost, "/api/v1/newBook", bytes.NewBuffer(payload))
20 |
21 | rr := httptest.NewRecorder()
22 |
23 | api.router.ServeHTTP(rr, req)
24 | if rr.Code != http.StatusOK {
25 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
26 | }
27 |
28 | t.Log("Response: ", rr.Body)
29 |
30 | got := len(books)
31 | // что плохо в таком сравнении? как сделать лучше?
32 | if got != want {
33 | t.Fatal("книга не добавлена")
34 | }
35 | }
36 |
37 | func TestAPI_books(t *testing.T) {
38 | req := httptest.NewRequest(http.MethodGet, "/api/v1/books", nil)
39 | rr := httptest.NewRecorder()
40 | api.router.ServeHTTP(rr, req)
41 | if !(rr.Code == http.StatusOK) {
42 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
43 | }
44 | t.Log("Response: ", rr.Body)
45 | }
46 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/jwt.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "net/http"
7 | "time"
8 |
9 | jwt "github.com/dgrijalva/jwt-go"
10 | )
11 |
12 | func (api *API) authJWT(w http.ResponseWriter, r *http.Request) {
13 | body, err := io.ReadAll(r.Body)
14 | if err != nil {
15 | http.Error(w, err.Error(), http.StatusInternalServerError)
16 | return
17 | }
18 | var auth authInfo
19 | err = json.Unmarshal(body, &auth)
20 | if err != nil {
21 | http.Error(w, err.Error(), http.StatusInternalServerError)
22 | return
23 | }
24 |
25 | if auth.Usr == "Usr" && auth.Pwd == "Pwd" {
26 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
27 | "usr": auth.Usr,
28 | "nbf": time.Now().Unix(),
29 | })
30 |
31 | // Sign and get the complete encoded token as a string using the secret
32 | tokenString, err := token.SignedString([]byte("secret-password"))
33 | if err != nil {
34 | http.Error(w, err.Error(), http.StatusInternalServerError)
35 | return
36 | }
37 | w.Write([]byte(tokenString))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/jwt_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | func TestAPI_authJWT(t *testing.T) {
12 | data := authInfo{
13 | Usr: "Usr",
14 | Pwd: "Pwd",
15 | }
16 | payload, _ := json.Marshal(data)
17 | req := httptest.NewRequest(http.MethodPost, "/api/v1/authJWT", bytes.NewBuffer(payload))
18 | rr := httptest.NewRecorder()
19 | api.router.ServeHTTP(rr, req)
20 | if rr.Code != http.StatusOK {
21 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
22 | }
23 | t.Log("Response: ", rr.Body)
24 | }
25 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/middleware.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "math/rand"
8 | "net/http"
9 | "net/http/httputil"
10 | "strings"
11 |
12 | jwt "github.com/dgrijalva/jwt-go"
13 | )
14 |
15 | // проверка валидности сеанса
16 | func (api *API) sessionsMiddleware(next http.Handler) http.Handler {
17 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18 | session, _ := api.store.Get(r, "session-cookie")
19 | if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
20 | http.Error(w, "доступ запрещён", http.StatusForbidden)
21 | return
22 | }
23 | next.ServeHTTP(w, r)
24 | })
25 | }
26 |
27 | // проверка валидности JWT
28 | func (api *API) jwtMiddleware(next http.Handler) http.Handler {
29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30 |
31 | tokenHeader := r.Header.Get("Authorization")
32 | if tokenHeader == "" {
33 | next.ServeHTTP(w, r)
34 | return
35 | }
36 |
37 | splitted := strings.Split(tokenHeader, " ")
38 | if len(splitted) != 2 {
39 | w.WriteHeader(http.StatusBadRequest)
40 | return
41 | }
42 |
43 | tokenString := splitted[1]
44 |
45 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
46 | return []byte("secret-password"), nil
47 | })
48 | if err != nil {
49 | w.WriteHeader(http.StatusBadRequest)
50 | return
51 | }
52 |
53 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
54 | fmt.Printf("Данные токена JWT: %+v\n", claims)
55 | }
56 |
57 | next.ServeHTTP(w, r)
58 | })
59 | }
60 |
61 | // присвоение номера запросу
62 | func requestIDMiddleware(next http.Handler) http.Handler {
63 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
64 | ctx := context.WithValue(r.Context(), "request_id", rand.Intn(1_000_000))
65 | newR := r.WithContext(ctx)
66 | b, _ := httputil.DumpRequest(newR, true)
67 | fmt.Printf("%+v", string(b))
68 |
69 | next.ServeHTTP(w, newR)
70 | })
71 | }
72 |
73 | // логирование запросов к API
74 | func logMiddleware(next http.Handler) http.Handler {
75 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76 | id := r.Context().Value("request_id").(int)
77 | log.Println(r.Method, r.RemoteAddr, r.RequestURI, id)
78 | next.ServeHTTP(w, r)
79 | })
80 | }
81 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/middleware_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io/ioutil"
7 | "net/http"
8 | "net/http/httptest"
9 | "testing"
10 | )
11 |
12 | func TestAPI_jwtMiddleware(t *testing.T) {
13 | data := authInfo{
14 | Usr: "Usr",
15 | Pwd: "Pwd",
16 | }
17 | payload, _ := json.Marshal(data)
18 | req := httptest.NewRequest(http.MethodPost, "/api/v1/authJWT", bytes.NewBuffer(payload))
19 | rr := httptest.NewRecorder()
20 | api.router.ServeHTTP(rr, req)
21 | if rr.Code != http.StatusOK {
22 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
23 | }
24 | b, _ := ioutil.ReadAll(rr.Body)
25 | jwt := string(b)
26 |
27 | req = httptest.NewRequest(http.MethodGet, "/api/v1/books", nil)
28 | req.Header.Add("Authorization", "Bearer "+jwt)
29 | rr = httptest.NewRecorder()
30 | api.router.ServeHTTP(rr, req)
31 | if !(rr.Code == http.StatusOK) {
32 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/session.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "net/http"
7 | )
8 |
9 | type authInfo struct {
10 | Usr string
11 | Pwd string
12 | }
13 |
14 | func (api *API) authSession(w http.ResponseWriter, r *http.Request) {
15 | body, err := io.ReadAll(r.Body)
16 | if err != nil {
17 | http.Error(w, err.Error(), http.StatusInternalServerError)
18 | return
19 | }
20 | var auth authInfo
21 | err = json.Unmarshal(body, &auth)
22 | if err != nil {
23 | http.Error(w, err.Error(), http.StatusInternalServerError)
24 | return
25 | }
26 |
27 | if auth.Usr == "Usr" && auth.Pwd == "Pwd" {
28 | session, _ := api.store.Get(r, "session-cookie")
29 | session.Values["Usr"] = auth.Usr
30 | session.Values["Pwd"] = auth.Pwd
31 | session.Values["authenticated"] = true
32 | err := session.Save(r, w)
33 | if err != nil {
34 | http.Error(w, err.Error(), http.StatusInternalServerError)
35 | return
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/13-api/2-api/pkg/api/session_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | func TestAPI_authSession(t *testing.T) {
12 | data := authInfo{
13 | Usr: "Usr",
14 | Pwd: "Pwd",
15 | }
16 | payload, _ := json.Marshal(data)
17 | req := httptest.NewRequest(http.MethodPost, "/api/v1/authSession", bytes.NewBuffer(payload))
18 | rr := httptest.NewRecorder()
19 | api.router.ServeHTTP(rr, req)
20 | if rr.Code != http.StatusOK {
21 | t.Errorf("код неверен: получили %d, а хотели %d", rr.Code, http.StatusOK)
22 | }
23 | t.Log("Response: ", rr.Body)
24 | }
25 |
--------------------------------------------------------------------------------
/14-RPC/1-Go-RPC/cmd/client/rpc-client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/rpc"
7 |
8 | "go-core-4/14-rpc/1-go-rpc/pkg/books"
9 | )
10 |
11 | type book2 struct {
12 | ID int
13 | Title string
14 | Author string
15 | }
16 |
17 | func main() {
18 | // создание клиента RPC
19 | client, err := rpc.Dial("tcp", "localhost:8080")
20 | if err != nil {
21 | log.Fatal("dialing:", err)
22 | }
23 |
24 | // получение списка книг, используя тип данных результата из пакета "books"
25 | var req = &books.Request{}
26 | var data []books.Book
27 | err = client.Call("Server.Books", req, &data)
28 | if err != nil {
29 | log.Fatal(err)
30 | }
31 | fmt.Printf("%+v\n", data)
32 |
33 | // получение книги по ID, используя локальный тип данных
34 | req = &books.Request{ID: 1}
35 | var item book2
36 | client.Call("Server.Book", req, &item)
37 | if err != nil {
38 | log.Fatal(err)
39 | }
40 | fmt.Printf("%+v\n", item)
41 |
42 | // неверное имя функции
43 | err = client.Call("Server.WrongName", req, &item)
44 | if err != nil {
45 | fmt.Println(err)
46 | }
47 | // неверные типы аргументов
48 | err = client.Call("Server.Book", 100, "ABC")
49 | if err != nil {
50 | fmt.Println(err)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/14-RPC/1-Go-RPC/cmd/server/rpc-server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "net/rpc"
7 |
8 | "go-core-4/14-rpc/1-go-rpc/pkg/books"
9 | )
10 |
11 | // Server - тип данных RCP-сервера.
12 | type Server int
13 |
14 | // Books возвращает список книг.
15 | func (s *Server) Books(req books.Request, resp *[]books.Book) error {
16 | // resp = &allBooks - не работает.
17 | *resp = allBooks
18 | return nil
19 | }
20 |
21 | // Book возвращает книгу по ID
22 | func (s *Server) Book(req books.Request, resp *books.Book) error {
23 | for _, b := range allBooks {
24 | if b.ID == req.ID {
25 | *resp = b
26 | return nil
27 | }
28 | }
29 | return nil
30 | }
31 |
32 | func main() {
33 | srv := new(Server)
34 | err := rpc.Register(srv)
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 |
39 | // регистрация сетевой службы RPC-сервера
40 | listener, err := net.Listen("tcp4", ":8080")
41 | if err != nil {
42 | log.Fatal(err)
43 | }
44 |
45 | // rpc.Accept(listener) // блокирующая команда.
46 | // цикл обработки клиентских подключений
47 | for {
48 | conn, err := listener.Accept()
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 |
53 | go rpc.ServeConn(conn)
54 | }
55 | }
56 |
57 | var allBooks = []books.Book{
58 | {
59 | ID: 1,
60 | Title: "The Lord Of The Rings",
61 | Author: "J.R.R. Tolkien",
62 | },
63 | {
64 | ID: 2,
65 | Title: "The Chronicles of Amber",
66 | Author: "Roger Zelazny",
67 | },
68 | }
69 |
--------------------------------------------------------------------------------
/14-RPC/1-Go-RPC/pkg/books/books.go:
--------------------------------------------------------------------------------
1 | package books
2 |
3 | type Book struct {
4 | ID int
5 | Title string
6 | Author string
7 | }
8 |
9 | // Request - запрос.
10 | type Request struct {
11 | ID int
12 | Title string
13 | Author string
14 | }
15 |
--------------------------------------------------------------------------------
/14-RPC/2-gRPC/books_proto/books.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.28.1
4 | // protoc v4.24.0--rc3
5 | // source: books_proto/books.proto
6 |
7 | package books_proto
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | // Книга.
24 | type Book struct {
25 | state protoimpl.MessageState
26 | sizeCache protoimpl.SizeCache
27 | unknownFields protoimpl.UnknownFields
28 |
29 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
30 | Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
31 | AuthorName string `protobuf:"bytes,3,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"`
32 | }
33 |
34 | func (x *Book) Reset() {
35 | *x = Book{}
36 | if protoimpl.UnsafeEnabled {
37 | mi := &file_books_proto_books_proto_msgTypes[0]
38 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
39 | ms.StoreMessageInfo(mi)
40 | }
41 | }
42 |
43 | func (x *Book) String() string {
44 | return protoimpl.X.MessageStringOf(x)
45 | }
46 |
47 | func (*Book) ProtoMessage() {}
48 |
49 | func (x *Book) ProtoReflect() protoreflect.Message {
50 | mi := &file_books_proto_books_proto_msgTypes[0]
51 | if protoimpl.UnsafeEnabled && x != nil {
52 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
53 | if ms.LoadMessageInfo() == nil {
54 | ms.StoreMessageInfo(mi)
55 | }
56 | return ms
57 | }
58 | return mi.MessageOf(x)
59 | }
60 |
61 | // Deprecated: Use Book.ProtoReflect.Descriptor instead.
62 | func (*Book) Descriptor() ([]byte, []int) {
63 | return file_books_proto_books_proto_rawDescGZIP(), []int{0}
64 | }
65 |
66 | func (x *Book) GetId() int64 {
67 | if x != nil {
68 | return x.Id
69 | }
70 | return 0
71 | }
72 |
73 | func (x *Book) GetTitle() string {
74 | if x != nil {
75 | return x.Title
76 | }
77 | return ""
78 | }
79 |
80 | func (x *Book) GetAuthorName() string {
81 | if x != nil {
82 | return x.AuthorName
83 | }
84 | return ""
85 | }
86 |
87 | type Empty struct {
88 | state protoimpl.MessageState
89 | sizeCache protoimpl.SizeCache
90 | unknownFields protoimpl.UnknownFields
91 | }
92 |
93 | func (x *Empty) Reset() {
94 | *x = Empty{}
95 | if protoimpl.UnsafeEnabled {
96 | mi := &file_books_proto_books_proto_msgTypes[1]
97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
98 | ms.StoreMessageInfo(mi)
99 | }
100 | }
101 |
102 | func (x *Empty) String() string {
103 | return protoimpl.X.MessageStringOf(x)
104 | }
105 |
106 | func (*Empty) ProtoMessage() {}
107 |
108 | func (x *Empty) ProtoReflect() protoreflect.Message {
109 | mi := &file_books_proto_books_proto_msgTypes[1]
110 | if protoimpl.UnsafeEnabled && x != nil {
111 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
112 | if ms.LoadMessageInfo() == nil {
113 | ms.StoreMessageInfo(mi)
114 | }
115 | return ms
116 | }
117 | return mi.MessageOf(x)
118 | }
119 |
120 | // Deprecated: Use Empty.ProtoReflect.Descriptor instead.
121 | func (*Empty) Descriptor() ([]byte, []int) {
122 | return file_books_proto_books_proto_rawDescGZIP(), []int{1}
123 | }
124 |
125 | var File_books_proto_books_proto protoreflect.FileDescriptor
126 |
127 | var file_books_proto_books_proto_rawDesc = []byte{
128 | 0x0a, 0x17, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x6f,
129 | 0x6f, 0x6b, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f,
130 | 0x62, 0x75, 0x66, 0x22, 0x4d, 0x0a, 0x04, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69,
131 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74,
132 | 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c,
133 | 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
134 | 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x4e, 0x61,
135 | 0x6d, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x67, 0x0a, 0x09, 0x42,
136 | 0x6f, 0x6f, 0x6b, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x42, 0x6f, 0x6f, 0x6b,
137 | 0x73, 0x12, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
138 | 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f,
139 | 0x6f, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x2c, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x42, 0x6f, 0x6f,
140 | 0x6b, 0x12, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f,
141 | 0x6b, 0x1a, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
142 | 0x74, 0x79, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x5f,
143 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
144 | }
145 |
146 | var (
147 | file_books_proto_books_proto_rawDescOnce sync.Once
148 | file_books_proto_books_proto_rawDescData = file_books_proto_books_proto_rawDesc
149 | )
150 |
151 | func file_books_proto_books_proto_rawDescGZIP() []byte {
152 | file_books_proto_books_proto_rawDescOnce.Do(func() {
153 | file_books_proto_books_proto_rawDescData = protoimpl.X.CompressGZIP(file_books_proto_books_proto_rawDescData)
154 | })
155 | return file_books_proto_books_proto_rawDescData
156 | }
157 |
158 | var file_books_proto_books_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
159 | var file_books_proto_books_proto_goTypes = []interface{}{
160 | (*Book)(nil), // 0: protobuf.Book
161 | (*Empty)(nil), // 1: protobuf.Empty
162 | }
163 | var file_books_proto_books_proto_depIdxs = []int32{
164 | 1, // 0: protobuf.Bookinist.Books:input_type -> protobuf.Empty
165 | 0, // 1: protobuf.Bookinist.AddBook:input_type -> protobuf.Book
166 | 0, // 2: protobuf.Bookinist.Books:output_type -> protobuf.Book
167 | 1, // 3: protobuf.Bookinist.AddBook:output_type -> protobuf.Empty
168 | 2, // [2:4] is the sub-list for method output_type
169 | 0, // [0:2] is the sub-list for method input_type
170 | 0, // [0:0] is the sub-list for extension type_name
171 | 0, // [0:0] is the sub-list for extension extendee
172 | 0, // [0:0] is the sub-list for field type_name
173 | }
174 |
175 | func init() { file_books_proto_books_proto_init() }
176 | func file_books_proto_books_proto_init() {
177 | if File_books_proto_books_proto != nil {
178 | return
179 | }
180 | if !protoimpl.UnsafeEnabled {
181 | file_books_proto_books_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
182 | switch v := v.(*Book); i {
183 | case 0:
184 | return &v.state
185 | case 1:
186 | return &v.sizeCache
187 | case 2:
188 | return &v.unknownFields
189 | default:
190 | return nil
191 | }
192 | }
193 | file_books_proto_books_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
194 | switch v := v.(*Empty); i {
195 | case 0:
196 | return &v.state
197 | case 1:
198 | return &v.sizeCache
199 | case 2:
200 | return &v.unknownFields
201 | default:
202 | return nil
203 | }
204 | }
205 | }
206 | type x struct{}
207 | out := protoimpl.TypeBuilder{
208 | File: protoimpl.DescBuilder{
209 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
210 | RawDescriptor: file_books_proto_books_proto_rawDesc,
211 | NumEnums: 0,
212 | NumMessages: 2,
213 | NumExtensions: 0,
214 | NumServices: 1,
215 | },
216 | GoTypes: file_books_proto_books_proto_goTypes,
217 | DependencyIndexes: file_books_proto_books_proto_depIdxs,
218 | MessageInfos: file_books_proto_books_proto_msgTypes,
219 | }.Build()
220 | File_books_proto_books_proto = out.File
221 | file_books_proto_books_proto_rawDesc = nil
222 | file_books_proto_books_proto_goTypes = nil
223 | file_books_proto_books_proto_depIdxs = nil
224 | }
225 |
--------------------------------------------------------------------------------
/14-RPC/2-gRPC/books_proto/books.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package protobuf;
3 |
4 | option go_package = "./books_proto";
5 |
6 | // Книга.
7 | message Book {
8 | int64 id = 1;
9 | string title = 2;
10 | string author_name = 3;
11 | }
12 |
13 | message Empty {}
14 |
15 | // Из каталога 2-gRPC:
16 | // PB: "protoc -I . --go_out=. ./books_proto/books.proto"
17 | // gRPC: "protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative .\books_proto\books.proto"
18 |
19 | // Описание службы gRPC.
20 | service Bookinist {
21 | rpc Books (Empty) returns (stream Book) {}
22 | rpc AddBook (Book) returns (Empty) {}
23 | }
--------------------------------------------------------------------------------
/14-RPC/2-gRPC/books_proto/books_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 | // versions:
3 | // - protoc-gen-go-grpc v1.2.0
4 | // - protoc v4.24.0--rc3
5 | // source: books_proto/books.proto
6 |
7 | package books_proto
8 |
9 | import (
10 | context "context"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | )
15 |
16 | // This is a compile-time assertion to ensure that this generated file
17 | // is compatible with the grpc package it is being compiled against.
18 | // Requires gRPC-Go v1.32.0 or later.
19 | const _ = grpc.SupportPackageIsVersion7
20 |
21 | // BookinistClient is the client API for Bookinist service.
22 | //
23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
24 | type BookinistClient interface {
25 | Books(ctx context.Context, in *Empty, opts ...grpc.CallOption) (Bookinist_BooksClient, error)
26 | AddBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Empty, error)
27 | }
28 |
29 | type bookinistClient struct {
30 | cc grpc.ClientConnInterface
31 | }
32 |
33 | func NewBookinistClient(cc grpc.ClientConnInterface) BookinistClient {
34 | return &bookinistClient{cc}
35 | }
36 |
37 | func (c *bookinistClient) Books(ctx context.Context, in *Empty, opts ...grpc.CallOption) (Bookinist_BooksClient, error) {
38 | stream, err := c.cc.NewStream(ctx, &Bookinist_ServiceDesc.Streams[0], "/protobuf.Bookinist/Books", opts...)
39 | if err != nil {
40 | return nil, err
41 | }
42 | x := &bookinistBooksClient{stream}
43 | if err := x.ClientStream.SendMsg(in); err != nil {
44 | return nil, err
45 | }
46 | if err := x.ClientStream.CloseSend(); err != nil {
47 | return nil, err
48 | }
49 | return x, nil
50 | }
51 |
52 | type Bookinist_BooksClient interface {
53 | Recv() (*Book, error)
54 | grpc.ClientStream
55 | }
56 |
57 | type bookinistBooksClient struct {
58 | grpc.ClientStream
59 | }
60 |
61 | func (x *bookinistBooksClient) Recv() (*Book, error) {
62 | m := new(Book)
63 | if err := x.ClientStream.RecvMsg(m); err != nil {
64 | return nil, err
65 | }
66 | return m, nil
67 | }
68 |
69 | func (c *bookinistClient) AddBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Empty, error) {
70 | out := new(Empty)
71 | err := c.cc.Invoke(ctx, "/protobuf.Bookinist/AddBook", in, out, opts...)
72 | if err != nil {
73 | return nil, err
74 | }
75 | return out, nil
76 | }
77 |
78 | // BookinistServer is the server API for Bookinist service.
79 | // All implementations must embed UnimplementedBookinistServer
80 | // for forward compatibility
81 | type BookinistServer interface {
82 | Books(*Empty, Bookinist_BooksServer) error
83 | AddBook(context.Context, *Book) (*Empty, error)
84 | mustEmbedUnimplementedBookinistServer()
85 | }
86 |
87 | // UnimplementedBookinistServer must be embedded to have forward compatible implementations.
88 | type UnimplementedBookinistServer struct {
89 | }
90 |
91 | func (UnimplementedBookinistServer) Books(*Empty, Bookinist_BooksServer) error {
92 | return status.Errorf(codes.Unimplemented, "method Books not implemented")
93 | }
94 | func (UnimplementedBookinistServer) AddBook(context.Context, *Book) (*Empty, error) {
95 | return nil, status.Errorf(codes.Unimplemented, "method AddBook not implemented")
96 | }
97 | func (UnimplementedBookinistServer) mustEmbedUnimplementedBookinistServer() {}
98 |
99 | // UnsafeBookinistServer may be embedded to opt out of forward compatibility for this service.
100 | // Use of this interface is not recommended, as added methods to BookinistServer will
101 | // result in compilation errors.
102 | type UnsafeBookinistServer interface {
103 | mustEmbedUnimplementedBookinistServer()
104 | }
105 |
106 | func RegisterBookinistServer(s grpc.ServiceRegistrar, srv BookinistServer) {
107 | s.RegisterService(&Bookinist_ServiceDesc, srv)
108 | }
109 |
110 | func _Bookinist_Books_Handler(srv interface{}, stream grpc.ServerStream) error {
111 | m := new(Empty)
112 | if err := stream.RecvMsg(m); err != nil {
113 | return err
114 | }
115 | return srv.(BookinistServer).Books(m, &bookinistBooksServer{stream})
116 | }
117 |
118 | type Bookinist_BooksServer interface {
119 | Send(*Book) error
120 | grpc.ServerStream
121 | }
122 |
123 | type bookinistBooksServer struct {
124 | grpc.ServerStream
125 | }
126 |
127 | func (x *bookinistBooksServer) Send(m *Book) error {
128 | return x.ServerStream.SendMsg(m)
129 | }
130 |
131 | func _Bookinist_AddBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
132 | in := new(Book)
133 | if err := dec(in); err != nil {
134 | return nil, err
135 | }
136 | if interceptor == nil {
137 | return srv.(BookinistServer).AddBook(ctx, in)
138 | }
139 | info := &grpc.UnaryServerInfo{
140 | Server: srv,
141 | FullMethod: "/protobuf.Bookinist/AddBook",
142 | }
143 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
144 | return srv.(BookinistServer).AddBook(ctx, req.(*Book))
145 | }
146 | return interceptor(ctx, in, info, handler)
147 | }
148 |
149 | // Bookinist_ServiceDesc is the grpc.ServiceDesc for Bookinist service.
150 | // It's only intended for direct use with grpc.RegisterService,
151 | // and not to be introspected or modified (even as a copy)
152 | var Bookinist_ServiceDesc = grpc.ServiceDesc{
153 | ServiceName: "protobuf.Bookinist",
154 | HandlerType: (*BookinistServer)(nil),
155 | Methods: []grpc.MethodDesc{
156 | {
157 | MethodName: "AddBook",
158 | Handler: _Bookinist_AddBook_Handler,
159 | },
160 | },
161 | Streams: []grpc.StreamDesc{
162 | {
163 | StreamName: "Books",
164 | Handler: _Bookinist_Books_Handler,
165 | ServerStreams: true,
166 | },
167 | },
168 | Metadata: "books_proto/books.proto",
169 | }
170 |
--------------------------------------------------------------------------------
/14-RPC/2-gRPC/client/grpc-client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "log"
8 |
9 | "google.golang.org/grpc"
10 | "google.golang.org/grpc/credentials/insecure"
11 |
12 | pb "go-core-4/14-rpc/2-grpc/books_proto"
13 | )
14 |
15 | func main() {
16 | conn, err := grpc.Dial("localhost:12345",
17 | grpc.WithTransportCredentials(insecure.NewCredentials()))
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | defer conn.Close()
22 |
23 | client := pb.NewBookinistClient(conn)
24 |
25 | ctx := context.Background()
26 |
27 | err = printAllBooksOnserver(ctx, client)
28 | if err != nil {
29 | log.Fatal(err)
30 | }
31 |
32 | client.AddBook(context.Background(), &pb.Book{Id: 3, Title: "The Lord Of The Rings"})
33 |
34 | err = printAllBooksOnserver(ctx, client)
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 | }
39 |
40 | func printAllBooksOnserver(ctx context.Context, client pb.BookinistClient) error {
41 | fmt.Println("\nЗапрашиваю книги на gRPC-сервере.")
42 | stream, err := client.Books(context.Background(), &pb.Empty{})
43 | if err != nil {
44 | return err
45 | }
46 |
47 | for {
48 | select {
49 | case <-ctx.Done():
50 | return ctx.Err()
51 | default:
52 | book, err := stream.Recv()
53 | if err == io.EOF {
54 | return nil
55 | }
56 | if err != nil {
57 | return err
58 | }
59 | fmt.Printf("Получена книга: %v\n", book)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/14-RPC/2-gRPC/server/grpc-server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "net"
7 |
8 | "google.golang.org/grpc"
9 |
10 | pb "go-core-4/14-rpc/2-grpc/books_proto"
11 | )
12 |
13 | type Bookinist struct {
14 | Data []pb.Book
15 |
16 | // Композиция типов.
17 | pb.UnimplementedBookinistServer
18 | }
19 |
20 | func (b *Bookinist) Books(_ *pb.Empty, stream pb.Bookinist_BooksServer) error {
21 | for i := range b.Data {
22 | stream.Send(&b.Data[i])
23 | }
24 | return nil
25 | }
26 |
27 | func (b *Bookinist) AddBook(_ context.Context, book *pb.Book) (*pb.Empty, error) {
28 | b.Data = append(b.Data, *book)
29 | return new(pb.Empty), nil
30 | }
31 |
32 | func main() {
33 | srv := Bookinist{}
34 | srv.Data = append(srv.Data,
35 | pb.Book{Id: 2, Title: "The Go Programming Language"},
36 | pb.Book{Id: 1, Title: "1984"},
37 | )
38 |
39 | lis, err := net.Listen("tcp", ":12345")
40 | if err != nil {
41 | log.Fatalf("failed to listen: %v", err)
42 | }
43 |
44 | grpcServer := grpc.NewServer()
45 | pb.RegisterBookinistServer(grpcServer, &srv)
46 | grpcServer.Serve(lis)
47 | }
48 |
--------------------------------------------------------------------------------
/15-sql/books_db/1-schema.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Схема учебной БД "Книги".
3 | Имена таблиц во множественном числе.
4 | */
5 |
6 | /*
7 | Удаляем таблицы, если они существуют.
8 | Удаление производится в обратном относительно создания порядке.
9 | */
10 | DROP TABLE IF EXISTS books_authors;
11 | DROP TABLE IF EXISTS books;
12 | DROP TABLE IF EXISTS authors;
13 | DROP TABLE IF EXISTS publishers;
14 |
15 | /*
16 | Создаём таблицы БД.
17 | Сначала создаются таблицы, на которые ссылаются вторичные ключи.
18 | */
19 | -- authors - писатели
20 | CREATE TABLE authors (
21 | id SERIAL PRIMARY KEY, -- первичный ключ
22 | first_name TEXT NOT NULL DEFAULT '',
23 | last_name TEXT NOT NULL DEFAULT '',
24 | year_of_birth INTEGER NOT NULL DEFAULT 0
25 | );
26 |
27 | -- publishers - издатели
28 | CREATE TABLE publishers (
29 | id SERIAL PRIMARY KEY, -- первичный ключ
30 | name TEXT NOT NULL,
31 | website TEXT NOT NULL DEFAULT ''
32 | );
33 |
34 | -- books - книги
35 | CREATE TABLE books (
36 | id BIGSERIAL PRIMARY KEY, -- первичный ключ
37 | isbn BIGINT UNIQUE, -- ISBN
38 | title TEXT NOT NULL, -- название
39 | year INTEGER DEFAULT 0, -- год выпуска (максимум текущий + 10)
40 | public_domain BOOLEAN DEFAULT FALSE, -- является ли общественным достоянием
41 | publisher_id INTEGER REFERENCES publishers(id) ON DELETE CASCADE ON UPDATE CASCADE DEFAULT 0,
42 | price INTEGER DEFAULT 0 CHECK (price >= 0),
43 | genres TEXT[] DEFAULT '{"не указано"}', -- жанры
44 | info JSONB DEFAULT '{}' -- сведения: оглавление, описание и пр.
45 | );
46 | -- индекс на базе бинарного дерева для быстрого поиска по названию книг
47 | CREATE INDEX IF NOT EXISTS books_title_idx ON books USING btree (lower(title));
48 |
49 | -- связь между книжками и писателями
50 | -- (у одной книги может быть несколько авторов)
51 | CREATE TABLE books_authors (
52 | id BIGSERIAL PRIMARY KEY, -- первичный ключ
53 | book_id BIGINT NOT NULL REFERENCES books(id),
54 | author_id INTEGER NOT NULL REFERENCES authors(id),
55 | UNIQUE(book_id, author_id)
56 | );
57 |
58 | -- функция-триггер для проверки года выпуска книги
59 | CREATE OR REPLACE FUNCTION check_book_year()
60 | RETURNS TRIGGER AS $$
61 | BEGIN
62 | IF NEW.year > (SELECT (extract(year from current_date) - 10)) AND
63 | NEW.year < (SELECT (extract(year from current_date) + 10))
64 | THEN RETURN NEW;
65 | ELSE RAISE EXCEPTION 'Invalid book year'; --RETURN NULL;
66 | END IF;
67 | END;
68 | $$ LANGUAGE plpgsql;
69 | -- регистрация тригера для таблицы
70 | CREATE OR REPLACE TRIGGER check_book_year BEFORE INSERT OR UPDATE ON books
71 | FOR EACH ROW EXECUTE PROCEDURE check_book_year();
--------------------------------------------------------------------------------
/15-sql/books_db/2-data.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Первичное наполнение БД данными.
3 | Часто требуется для заполнения таблиц, на которые ссылаются вторичные ключи,
4 | имеющие значения по умолчанию.
5 | */
6 |
7 | INSERT INTO authors (id, first_name) VALUES (0, 'автор не указан');
8 | INSERT INTO authors (id, first_name) VALUES (1, 'автор неизвестен');
9 | -- поскольку id установлен принудительно, то необходимо изменить начало послеовательности
10 | ALTER SEQUENCE authors_id_seq RESTART WITH 100;
11 |
12 | INSERT INTO publishers (id, name) VALUES (0, 'издательство не указано');
13 | ALTER SEQUENCE publishers_id_seq RESTART WITH 100;
14 |
15 | -- insert into books (title, year) VALUES ('Book', 2050); - что будет в результате выполнения?
--------------------------------------------------------------------------------
/16-db-apps/1-database-sql/database-sql.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "fmt"
7 | "log"
8 | "os"
9 |
10 | _ "github.com/go-sql-driver/mysql"
11 | )
12 |
13 | // Использование шаблона "Репозиторий".
14 | type BooksRepo interface {
15 | GetBooks(context.Context) ([]book, error)
16 | AddBook(context.Context, book) (int, error)
17 | }
18 |
19 | // Книжка.
20 | type book struct {
21 | ID int
22 | Title string
23 | Year int
24 | Public bool
25 | PublisherID int
26 | Publisher string
27 | }
28 |
29 | func main() {
30 | // Объект БД - пул подключений к СУБД.
31 | // БД - долгоживущий объект. Следует создавать только один объект для каждой БД.
32 | // Далее этот объект следует передавать как зависимость.
33 | var db *sql.DB
34 | var err error
35 |
36 | // Подключение к БД.
37 | // В зависимости от драйвера, sql.Open может не выполнять фактического подключения,
38 | // а только проверить параметры соединения с БД.
39 | pwd := os.Getenv("mysql_password")
40 | db, err = sql.Open("mysql", "root:"+pwd+"@tcp(ubuntu-server.northeurope.cloudapp.azure.com:3306)/books")
41 | if err != nil {
42 | log.Fatal(err)
43 | }
44 | // Не забываем очищать ресурсы.
45 | defer db.Close()
46 |
47 | // Проверка соединения с БД. На случай, если sql.Open этого не делает.
48 | err = db.Ping()
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 |
53 | // Получение списка книг.
54 | data, err := books(db)
55 | if err != nil {
56 | log.Fatal(err)
57 | }
58 | fmt.Printf("%+v\n", data)
59 |
60 | // добавление книг одной транзакцией
61 | data = []book{
62 | {
63 | Title: "The Chronicles of Amber",
64 | Year: 1970,
65 | },
66 | {
67 | Title: "Dune",
68 | Year: 1965,
69 | },
70 | }
71 | err = addBooks(db, data)
72 | if err != nil {
73 | log.Fatal(err)
74 | }
75 |
76 | // Получение списка книг.
77 | data, err = books(db)
78 | if err != nil {
79 | log.Fatal(err)
80 | }
81 | fmt.Printf("%+v\n", data)
82 | }
83 |
84 | // books возвращает список книг из БД.
85 | func books(db *sql.DB) ([]book, error) {
86 | // Выполнение запроса выборки данных.
87 | // Query производит следующие действия:
88 | // - подготовка запроса;
89 | // - выполнение запроса;
90 | // - закрытие соединения.
91 | rows, err := db.Query(`
92 | SELECT id, title, year
93 | FROM books
94 | WHERE id >= ? AND id < ?;`,
95 | 0, 10_000,
96 | )
97 | if err != nil {
98 | return nil, err
99 | }
100 | defer rows.Close()
101 |
102 | var books []book
103 | for rows.Next() {
104 | var b book
105 | err := rows.Scan(
106 | &b.ID,
107 | &b.Title,
108 | &b.Year,
109 | )
110 | if err != nil {
111 | return nil, err
112 | }
113 | books = append(books, b)
114 | }
115 | err = rows.Err()
116 | if err != nil {
117 | return nil, err
118 | }
119 |
120 | return books, nil
121 | }
122 |
123 | // addBooks добавляет в БД массив книг одной транзакцией.
124 | func addBooks(db *sql.DB, books []book) error {
125 | // начало транзакции
126 | tx, err := db.Begin()
127 | if err != nil {
128 | return err
129 | }
130 | // отмена транзакции в случае ошибки
131 | defer tx.Rollback()
132 |
133 | // подготовка запроса для последующего многократного выполнения
134 | stmt, err := tx.Prepare(`INSERT INTO books(title, year) VALUES (?, ?)`)
135 | if err != nil {
136 | return err
137 | }
138 | defer stmt.Close()
139 |
140 | for _, book := range books {
141 | res, err := stmt.ExecContext(context.TODO(), book.Title, book.Year)
142 | if err != nil {
143 | return err
144 | }
145 | id, _ := res.LastInsertId()
146 | fmt.Println("Создана запись с ID:", id)
147 | }
148 |
149 | // подтверждение транзакции
150 | err = tx.Commit()
151 | if err != nil {
152 | return err
153 | }
154 |
155 | return nil
156 | }
157 |
--------------------------------------------------------------------------------
/16-db-apps/2-pgx/pgx.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 |
9 | "github.com/jackc/pgx/v5"
10 | "github.com/jackc/pgx/v5/pgxpool"
11 | )
12 |
13 | // Книжка.
14 | type book struct {
15 | ID int
16 | Title string
17 | Year int
18 | Public bool
19 | PublisherID int
20 | Publisher string
21 | }
22 |
23 | func main() {
24 | ctx := context.Background()
25 | // Подключение к БД. Функция возвращает объект БД.
26 | pwd := os.Getenv("pg_password")
27 | db, err := pgxpool.New(ctx, "postgres://postgres:"+pwd+"@ubuntu-server.northeurope.cloudapp.azure.com/books")
28 | if err != nil {
29 | log.Fatalf("Unable to connect to database: %v\n", err)
30 | }
31 | // Не забываем очищать ресурсы.
32 | defer db.Close()
33 |
34 | // Получение списка книг.
35 | data, err := books(ctx, db)
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 | fmt.Printf("%+v\n", data)
40 |
41 | // добавление книг одной транзакцией
42 | data = []book{
43 | {
44 | Title: "The Chronicles of Amber",
45 | Year: 1970,
46 | },
47 | {
48 | Title: "Dune",
49 | Year: 1965,
50 | },
51 | }
52 | err = addBooks(ctx, db, data)
53 | if err != nil {
54 | log.Fatal(err)
55 | }
56 |
57 | // Получение списка книг.
58 | data, err = books(ctx, db)
59 | if err != nil {
60 | log.Fatal(err)
61 | }
62 | fmt.Printf("%+v\n", data)
63 | }
64 |
65 | // books возвращает список книг из БД.
66 | func books(ctx context.Context, db *pgxpool.Pool) ([]book, error) {
67 | // Выполнение запроса выборки данных.
68 | // Query производит следующие действия:
69 | // - подготовка запроса;
70 | // - выполнение запроса;
71 | // - закрытие соединения.
72 | rows, err := db.Query(ctx, `
73 | SELECT id, title, year
74 | FRoM books
75 | WHERE id >= $1 AND id < `,
76 | 0,
77 | )
78 | if err != nil {
79 | return nil, err
80 | }
81 | defer rows.Close()
82 |
83 | var books []book
84 | for rows.Next() {
85 | var b book
86 | err := rows.Scan(
87 | &b.ID,
88 | &b.Title,
89 | &b.Year,
90 | )
91 | if err != nil {
92 | return nil, err
93 | }
94 | books = append(books, b)
95 | }
96 | err = rows.Err()
97 | if err != nil {
98 | return nil, err
99 | }
100 | return books, nil
101 | }
102 |
103 | // addBooks добавляет в БД массив книг одной транзакцией.
104 | func addBooks(ctx context.Context, db *pgxpool.Pool, books []book) error {
105 | // начало транзакции
106 | tx, err := db.Begin(ctx)
107 | if err != nil {
108 | return err
109 | }
110 | // отмена транзакции в случае ошибки
111 | defer tx.Rollback(ctx)
112 |
113 | // пакетный запрос
114 | var batch = &pgx.Batch{}
115 |
116 | // добавление заданий в пакет
117 | for _, book := range books {
118 | batch.Queue(`INSERT INTO books(title, year) VALUES ($1, $2)`, book.Title, book.Year)
119 | }
120 |
121 | // отправка пакета в БД (может выполняться для транзакции или соединения)
122 | res := tx.SendBatch(ctx, batch)
123 |
124 | // обязательная операция закрытия соединения
125 | err = res.Close()
126 | if err != nil {
127 | return err
128 | }
129 |
130 | // подтверждение транзакции
131 | return tx.Commit(ctx)
132 | }
133 |
--------------------------------------------------------------------------------
/16-db-apps/2-pgx/pgx_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "testing"
7 |
8 | "github.com/jackc/pgx/v5/pgxpool"
9 | )
10 |
11 | var (
12 | testDB *pgxpool.Pool
13 | ctx = context.Background()
14 | )
15 |
16 | func TestMain(m *testing.M) {
17 | var err error
18 | // БД для тетов.
19 | testDB, err = pgxpool.New(context.Background(), "postgres://postgres:GoPassword@ubuntu-server.northeurope.cloudapp.azure.com/books")
20 | if err != nil {
21 | log.Fatalf("Unable to connect to database: %v\n", err)
22 | }
23 | defer testDB.Close()
24 | m.Run()
25 | }
26 |
27 | func Test_books(t *testing.T) {
28 | data, err := books(ctx, testDB)
29 | if err != nil {
30 | t.Fatal(err)
31 | }
32 | t.Logf("%+v\n", data)
33 | }
34 |
--------------------------------------------------------------------------------
/16-db-apps/pkg/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "go-core-4/16-db-apps/pkg/db"
6 | "net/http"
7 | )
8 |
9 | type API struct {
10 | db db.Interface
11 | }
12 |
13 | func New(db db.Interface) *API {
14 | return &API{db: db}
15 | }
16 |
17 | func (api *API) handler(w http.ResponseWriter, r *http.Request) {
18 | books, _ := api.db.Books(r.Context())
19 |
20 | for _, book := range books {
21 | _ = book
22 | }
23 |
24 | json.NewEncoder(w).Encode(books)
25 | }
26 |
--------------------------------------------------------------------------------
/16-db-apps/pkg/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type Interface interface {
8 | Books(ctx context.Context) ([]Book, error)
9 | AddBooks(ctx context.Context, books []Book) error
10 | }
11 |
12 | type Book struct {
13 | ID int
14 | Title string
15 | Year int
16 | Public bool
17 | PublisherID int
18 | Publisher string
19 | }
20 |
--------------------------------------------------------------------------------
/16-db-apps/pkg/db/memsql/memsql.go:
--------------------------------------------------------------------------------
1 | package memsql
2 |
3 | import (
4 | "context"
5 | "go-core-4/16-db-apps/pkg/db"
6 |
7 | "github.com/jackc/pgx/v5/pgxpool"
8 | )
9 |
10 | type DB struct {
11 | pool *pgxpool.Pool
12 | }
13 |
14 | func (db *DB) Books(ctx context.Context) ([]db.Book, error) {
15 | return nil, nil
16 | }
17 |
18 | func (db *DB) AddBooks(ctx context.Context, books []db.Book) error {
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/16-db-apps/pkg/db/pgsql/pgsql.go:
--------------------------------------------------------------------------------
1 | package pgsql
2 |
3 | import (
4 | "context"
5 | "go-core-4/16-db-apps/pkg/db"
6 |
7 | "github.com/jackc/pgx/v5/pgxpool"
8 | )
9 |
10 | type DB struct {
11 | pool *pgxpool.Pool
12 | }
13 |
14 | func (db *DB) Books(ctx context.Context) ([]db.Book, error) {
15 | return nil, nil
16 | }
17 |
18 | func (db *DB) AddBooks(ctx context.Context, books []db.Book) error {
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/16-db-apps/schema.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Схема учебной БД "Книги". Запрос в фомате MySQL.
3 | */
4 |
5 | /*
6 | Удаляем таблицы, если они существуют.
7 | Удаление производится в обратном относительно создания порядке.
8 | */
9 | DROP TABLE IF EXISTS books_authors;
10 | DROP TABLE IF EXISTS books;
11 | DROP TABLE IF EXISTS authors;
12 | DROP TABLE IF EXISTS publishers;
13 |
14 | /*
15 | Создаём таблицы БД.
16 | Сначала создаются таблицы, на которые ссылаются вторичные ключи.
17 | */
18 | -- authors - писатели
19 | CREATE TABLE authors (
20 | id SERIAL PRIMARY KEY, -- первичный ключ
21 | first_name VARCHAR(20) NOT NULL DEFAULT '',
22 | last_name VARCHAR(20) NOT NULL DEFAULT '',
23 | year_of_birth INTEGER NOT NULL DEFAULT 0
24 | );
25 |
26 | -- publishers - издатели
27 | CREATE TABLE publishers (
28 | id SERIAL PRIMARY KEY, -- первичный ключ
29 | name VARCHAR(50) NOT NULL,
30 | website VARCHAR(100) NOT NULL DEFAULT ''
31 | );
32 |
33 | -- books - книги
34 | CREATE TABLE books (
35 | id SERIAL PRIMARY KEY, -- первичный ключ
36 | title VARCHAR(50) NOT NULL, -- название
37 | year INTEGER DEFAULT 0, -- год выпуска (максимум текущий + 10)
38 | public_domain BOOLEAN DEFAULT FALSE, -- является ли общественным достоянием
39 | publisher_id INT DEFAULT 0 REFERENCES publishers(id) ON DELETE CASCADE ON UPDATE CASCADE
40 | );
41 | -- индекс на базе бинарного дерева для быстрого поиска по названию книг
42 | CREATE INDEX books_title_idx ON books(title) USING btree;
43 |
44 | -- связь между книжками и писателями
45 | -- (у одной книги может быть несколько авторов)
46 | CREATE TABLE books_authors (
47 | id SERIAL PRIMARY KEY, -- первичный ключ
48 | book_id BIGINT NOT NULL REFERENCES books(id),
49 | author_id INTEGER NOT NULL REFERENCES authors(id),
50 | UNIQUE(book_id, author_id)
51 | );
52 |
53 | /*
54 | Первичное наполнение БД данными.
55 | Часто требуется для заполнения таблиц, на которые ссылаются вторичные ключи,
56 | имеющие значения по умолчанию.
57 | */
58 |
59 | INSERT INTO authors (id, first_name) VALUES (0, 'автор не указан');
60 | ALTER TABLE authors AUTO_INCREMENT = 100;
61 |
62 | INSERT INTO publishers (id, name) VALUES (0, 'издательство не указано');
63 | ALTER TABLE publishers AUTO_INCREMENT = 100;
64 |
65 | INSERT INTO books (title) VALUES ('The Lord Of The Rings');
66 | INSERT INTO books (title) VALUES ('1984');
67 | ALTER TABLE books AUTO_INCREMENT = 100;
--------------------------------------------------------------------------------
/17-system-design/SOLID/1-SRP/srp.go:
--------------------------------------------------------------------------------
1 | // Пакет srp предоставляет функции для
2 | // получения данных и генерации отчетов.
3 | package srp
4 |
5 | type строка string
6 | type байт byte
7 | type неполадка error
8 |
9 | type КлиентПубличногоОблачногоХранилищаДанных struct {
10 | УчёткаЯндексДиск УчётныеДанные
11 | УчёткаГуглДиск УчётныеДанные
12 | УчёткаАмазонС3 УчётныеДанные
13 | }
14 |
15 | type УчётныеДанные struct {
16 | ИмяПользователя строка
17 | Пароль строка
18 | }
19 |
20 | func НовыйКлиент(учётка УчётныеДанные, типКлиента строка,
21 | ) *КлиентПубличногоОблачногоХранилищаДанных {
22 | var клиент КлиентПубличногоОблачногоХранилищаДанных
23 | switch типКлиента {
24 | case "Яндекс":
25 | // ...
26 | case "Гугол":
27 | // ...
28 | }
29 | return &клиент
30 | }
31 |
32 | func (к *КлиентПубличногоОблачногоХранилищаДанных) ПолучитьФайл(
33 | название строка,
34 | типКлиента строка,
35 | ) ([]байт, неполадка) {
36 | switch типКлиента {
37 | case "Яндекс":
38 | // ...
39 | case "Гугол":
40 | // ...
41 | }
42 |
43 | return nil, nil
44 | }
45 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/2-OCP/ocp.go:
--------------------------------------------------------------------------------
1 | package ocp
2 |
3 | import "fmt"
4 |
5 | // Пакет демонстрирует типовое нарушение
6 | // принципа OCP.
7 | // Функции занимаются анализом и обработкой
8 | // результата вместо того, чтобы вернуть
9 | // его вызывающему коду.
10 | // В результате у пользователя пакета появляется
11 | // потребность изменить код пакета, что является
12 | // нарушением принципа OCP.
13 |
14 | func Avg(nums []int) {
15 | if len(nums) == 0 {
16 | return
17 | }
18 |
19 | var num float64
20 | var sum int
21 | for _, n := range nums {
22 | sum += n
23 | }
24 |
25 | num = float64(sum / len(nums))
26 |
27 | // следует вернуть результат, а не печатать его
28 | fmt.Println(num)
29 | }
30 |
31 | func Max(nums []int) int {
32 | // функция проанализировала входные данные и
33 | // вернула нелогичный результат, хотя для этого
34 | // нет никаких оснований
35 | if len(nums) == 0 {
36 | return -1
37 | }
38 |
39 | var num int
40 | for _, n := range nums {
41 | if n > num {
42 | num = n
43 | }
44 | }
45 |
46 | return num
47 | }
48 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/2-OCP/ocp_refactored.go:
--------------------------------------------------------------------------------
1 | package ocp
2 |
3 | /*
4 | func Avg(nums []int) float64 {
5 | if len(nums) == 0 {
6 | return 0
7 | }
8 | var sum int
9 | for _, n := range nums {
10 | sum += n
11 | }
12 | return float64(sum / len(nums))
13 | }
14 |
15 | func Max(nums []int) (int, bool) {
16 | if len(nums) == 0 {
17 | return 0, false
18 | }
19 | num := nums[0]
20 | for _, n := range nums {
21 | if n > num {
22 | num = n
23 | }
24 | }
25 | return num, true
26 | }
27 | */
28 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/2-OCP/ocp_refactored_test.go:
--------------------------------------------------------------------------------
1 | package ocp
2 |
3 | /*
4 | import "testing"
5 |
6 | func TestAvg(t *testing.T) {
7 | type args struct {
8 | nums []int
9 | }
10 | tests := []struct {
11 | name string
12 | args args
13 | want float64
14 | }{
15 | {
16 | name: "Test #1",
17 | args: args{nums: []int{1, 2, 3}},
18 | want: 2.0,
19 | },
20 | }
21 | for _, tt := range tests {
22 | t.Run(tt.name, func(t *testing.T) {
23 | if got := Avg(tt.args.nums); got != tt.want {
24 | t.Errorf("Avg() = %v, want %v", got, tt.want)
25 | }
26 | })
27 | }
28 | }
29 |
30 | func TestMax(t *testing.T) {
31 | type args struct {
32 | nums []int
33 | }
34 | tests := []struct {
35 | name string
36 | args args
37 | want int
38 | }{
39 | {
40 | name: "Test #1",
41 | args: args{nums: []int{1, 2, 3}},
42 | want: 3,
43 | },
44 | {
45 | name: "Test #2",
46 | args: args{nums: []int{}},
47 | want: 0, // логично получить значение по умолчанию
48 | },
49 | }
50 | for _, tt := range tests {
51 | t.Run(tt.name, func(t *testing.T) {
52 | if got := Max(tt.args.nums); got != tt.want {
53 | t.Errorf("Max() = %v, want %v", got, tt.want)
54 | }
55 | })
56 | }
57 | }
58 | */
59 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/2-OCP/ocp_test.go:
--------------------------------------------------------------------------------
1 | package ocp
2 |
3 | import "testing"
4 |
5 | func TestAvg(t *testing.T) {
6 | type args struct {
7 | nums []int
8 | }
9 | tests := []struct {
10 | name string
11 | args args
12 | }{
13 | {
14 | name: "Test #1",
15 | args: args{nums: []int{1, 2, 3}},
16 | },
17 | }
18 | for _, tt := range tests {
19 | t.Run(tt.name, func(t *testing.T) {
20 | Avg(tt.args.nums)
21 | // Не с чем сравнить результат!
22 | })
23 | }
24 | }
25 |
26 | func TestMax(t *testing.T) {
27 | type args struct {
28 | nums []int
29 | }
30 | tests := []struct {
31 | name string
32 | args args
33 | want int
34 | }{
35 | {
36 | name: "Test #1",
37 | args: args{nums: []int{1, 2, 3}},
38 | want: 3,
39 | },
40 | {
41 | name: "Test #2",
42 | args: args{nums: []int{}},
43 | want: 0, // логично получить значение по умолчанию
44 | },
45 | }
46 | for _, tt := range tests {
47 | t.Run(tt.name, func(t *testing.T) {
48 | if got := Max(tt.args.nums); got != tt.want {
49 | t.Errorf("Max() = %v, want %v", got, tt.want)
50 | }
51 | })
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/3-LSP/lsp.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "log"
8 | "os"
9 | )
10 |
11 | // Пакет демонстрирует выполнение принципа LSP в Go.
12 |
13 | // Интерфейс с контрактом на представление
14 | // значения некоторого типа данных в виде
15 | // последовательности байт.
16 | type Serializer interface {
17 | Serialize() []byte
18 | }
19 |
20 | type String string
21 |
22 | func (s String) Serialize() []byte {
23 | return []byte(s)
24 | }
25 |
26 | type Bool bool
27 |
28 | func (b Bool) Serialize() []byte {
29 | return []byte(fmt.Sprintf("%v", b))
30 | }
31 |
32 | // Музыкальный альбом.
33 | type Album struct {
34 | Title String
35 | Year uint
36 | }
37 |
38 | // Реализация контракта.
39 | func (a Album) Serialize() []byte {
40 | b, err := json.Marshal(a)
41 | if err != nil {
42 | return nil
43 | }
44 | return b
45 | }
46 |
47 | // Функция принимает на вход объект, в который требуется
48 | // сохранить состояние: файл, принтер и т.д.
49 | // Также принимается любое количество объектов, поддерживающих
50 | // сериализацию.
51 | func WriteObject(w io.Writer, objects ...Serializer) error {
52 | for _, obj := range objects {
53 | b := obj.Serialize()
54 | _, err := w.Write(b)
55 | if err != nil {
56 | return err
57 | }
58 | }
59 | return nil
60 | }
61 |
62 | func main() {
63 | var s String = "ABC"
64 | var b Bool = true
65 | var a = Album{Title: "Nevermind", Year: 1991}
66 |
67 | f, err := os.Create("./file.txt")
68 | if err != nil {
69 | log.Fatal(err)
70 | }
71 |
72 | err = WriteObject(f, s, b, a)
73 | if err != nil {
74 | log.Fatal(err)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/4-ISP/isp.go:
--------------------------------------------------------------------------------
1 | package isp
2 |
3 | // Пакет демонстрирует принцип разделения интерфейсов.
4 |
5 | // Интерфейс, включающий в себя все возможные методы,
6 | // которые используются в пакете.
7 | type BigInterface interface {
8 | Dance()
9 | Sing()
10 | PlayGuitar()
11 | }
12 |
13 | // Минимальный интерфейс, требуемый
14 | // для функции Dance.
15 | type Dancer interface {
16 | Dance()
17 | }
18 |
19 | // Нарушение ISP, поскольку от аргумента требуется
20 | // реализация методов, которые не используются.
21 | func BadDance(dancer BigInterface) {
22 | dancer.Dance()
23 | }
24 |
25 | // Соблюдение ISP за счёт разделения интерфейсов
26 | // для разных задач.
27 | func GoodDance(dancer Dancer) {
28 | dancer.Dance()
29 | }
30 |
--------------------------------------------------------------------------------
/17-system-design/SOLID/5-DIP/dip.go:
--------------------------------------------------------------------------------
1 | package dip
2 |
3 | // База данных - деталь реализации.
4 | type DB []Book
5 |
6 | type Book struct {
7 | ID int
8 | Title string
9 | }
10 |
11 | // Интерфейс - часть бизнес-логики.
12 | type Storage interface {
13 | Books() []Book
14 | }
15 |
16 | func (db DB) Books() []Book {
17 | return []Book{}
18 | }
19 |
20 | // Нарушение принципа DIP.
21 | type WrongServer struct {
22 | db DB
23 | }
24 |
25 | // Выполнение принципа DIP.
26 | type GoodServer struct {
27 | db Storage
28 | }
29 |
30 | // Бизнес-логика системы.
31 | func (s *WrongServer) BusinessLogic() {
32 | // Принцип DIP нарушен, обращение к БД.
33 | books := s.db.Books()
34 | _ = books
35 | }
36 |
37 | // Бизнес-логика системы.
38 | func (s *GoodServer) BusinessLogic() {
39 | // Принцип DIP выполняется, обращение к интерфейсу.
40 | books := s.db.Books()
41 | _ = books
42 | }
43 |
--------------------------------------------------------------------------------
/17-system-design/app/cmd/appclient/appclient.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {}
4 |
--------------------------------------------------------------------------------
/17-system-design/app/cmd/appserver/appserver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "go-core-4/17-system-design/app/internal/server"
4 |
5 | func main() {
6 | s, _ := server.New()
7 | s.Run()
8 | }
9 |
--------------------------------------------------------------------------------
/17-system-design/app/internal/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "go-core-4/17-system-design/app/internal/db"
6 | "go-core-4/17-system-design/app/internal/models"
7 | "net/http"
8 |
9 | "github.com/gorilla/mux"
10 | )
11 |
12 | // API предоставляет интерфейс программного взаимодействия.
13 | type API struct {
14 | router *mux.Router
15 | db *db.DB
16 | }
17 |
18 | func New(db *db.DB) *API {
19 | api := API{
20 | router: mux.NewRouter(),
21 | db: db,
22 | }
23 | api.Endpoints()
24 | return &api
25 | }
26 |
27 | func (api *API) Run(addr string) error {
28 | return http.ListenAndServe(addr, api.router)
29 | }
30 |
31 | // Endpoints регистрирует конечные точки API.
32 | func (api *API) Endpoints() {
33 | api.router.HandleFunc("/api/v1/books", api.books).Methods(http.MethodGet)
34 | api.router.HandleFunc("/api/v1/books", api.newBook).Methods(http.MethodPost)
35 | api.router.HandleFunc("/api/v1/books/{id}", api.deleteBook).Methods(http.MethodDelete)
36 | }
37 |
38 | func (api *API) books(w http.ResponseWriter, r *http.Request) {
39 | err := json.NewEncoder(w).Encode(models.Books)
40 | if err != nil {
41 | http.Error(w, err.Error(), http.StatusInternalServerError)
42 | return
43 | }
44 | }
45 |
46 | func (api *API) newBook(w http.ResponseWriter, r *http.Request) {
47 | var b models.Book
48 | err := json.NewDecoder(r.Body).Decode(&b)
49 | if err != nil {
50 | http.Error(w, err.Error(), http.StatusInternalServerError)
51 | return
52 | }
53 | models.Books = append(models.Books, b)
54 | }
55 |
56 | func (api *API) deleteBook(w http.ResponseWriter, r *http.Request) {
57 | }
58 |
--------------------------------------------------------------------------------
/17-system-design/app/internal/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/jackc/pgx/v5/pgxpool"
7 | )
8 |
9 | type DB struct {
10 | pool *pgxpool.Pool
11 | }
12 |
13 | func New(connString string) (*DB, error) {
14 | pool, err := pgxpool.New(context.Background(), connString)
15 | if err != nil {
16 | return nil, err
17 | }
18 |
19 | return &DB{pool: pool}, nil
20 | }
21 |
--------------------------------------------------------------------------------
/17-system-design/app/internal/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Book struct {
4 | Name string
5 | Author string
6 | }
7 |
8 | var Books = []Book{
9 | {
10 | Name: "The Lord Of The Rings",
11 | Author: "J.R.R. Tolkien",
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/17-system-design/app/internal/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "go-core-4/17-system-design/app/internal/api"
5 | "go-core-4/17-system-design/app/internal/db"
6 | )
7 |
8 | type Server struct {
9 | api *api.API
10 | db *db.DB
11 | }
12 |
13 | const connStr = "postgres://..."
14 |
15 | func New() (*Server, error) {
16 | s := Server{}
17 | db, err := db.New(connStr)
18 | if err != nil {
19 | return nil, err
20 | }
21 | s.db = db
22 | s.api = api.New(s.db)
23 |
24 | return &s, nil
25 | }
26 |
27 | func (s *Server) Run() {
28 | // ...
29 | }
30 |
--------------------------------------------------------------------------------
/18-microservices/microservice/build/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | FROM golang:1.21
4 |
5 | WORKDIR /app
6 |
7 | COPY . .
8 | RUN go mod download
9 |
10 | WORKDIR /app/cmd/app
11 | RUN GOOS=linux go build -o app
12 |
13 | EXPOSE 8080
14 |
15 | CMD ["./app"]
16 |
17 | # docker build -f .\build\Dockerfile --progress=plain -t dmitriytitov/microapp:latest .
18 | # docker run --rm -p 8080:8080 dmitriytitov/microapp:latest
--------------------------------------------------------------------------------
/18-microservices/microservice/build/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: microapp-deployment
5 | spec:
6 | selector:
7 | matchLabels:
8 | appname: microapp
9 | replicas: 2 # количество экземпляров приложения
10 | template:
11 | metadata:
12 | labels:
13 | appname: microapp
14 | spec:
15 | containers:
16 | - name: microapp
17 | image: dmitriytitov/microapp:latest
18 | ports:
19 | - containerPort: 8080
20 |
21 | # kubectl port-forward deployment.apps/microapp-deployment 8080:8080
--------------------------------------------------------------------------------
/18-microservices/microservice/build/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: microapp-service
5 | spec:
6 | selector:
7 | appname: microapp
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
--------------------------------------------------------------------------------
/18-microservices/microservice/cmd/app/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "microapp/pkg/api"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | srv := New()
10 | srv.run()
11 | }
12 |
13 | type Server struct {
14 | api *api.API
15 | }
16 |
17 | func New() *Server {
18 | s := Server{
19 | api: api.New(),
20 | }
21 | return &s
22 | }
23 |
24 | func (s *Server) run() {
25 | http.ListenAndServe(":8080", s.api.Router)
26 | }
27 |
--------------------------------------------------------------------------------
/18-microservices/microservice/go.mod:
--------------------------------------------------------------------------------
1 | module microapp
2 |
3 | go 1.19
4 |
5 | require github.com/gorilla/mux v1.8.0
6 |
--------------------------------------------------------------------------------
/18-microservices/microservice/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
3 |
--------------------------------------------------------------------------------
/18-microservices/microservice/pkg/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "sync"
8 |
9 | "github.com/gorilla/mux"
10 | )
11 |
12 | type API struct {
13 | Router *mux.Router
14 |
15 | sync.Mutex
16 | db []Review
17 | }
18 |
19 | func New() *API {
20 | api := API{
21 | Router: mux.NewRouter(),
22 | }
23 |
24 | api.Router.HandleFunc("/", handler)
25 |
26 | return &api
27 | }
28 |
29 | type Review struct {
30 | MocieID int
31 | Text string
32 | }
33 |
34 | func handler(w http.ResponseWriter, r *http.Request) {
35 | hostname, _ := os.Hostname()
36 | w.Write([]byte(fmt.Sprintf("Microapp на машине %v", hostname)))
37 | }
38 |
--------------------------------------------------------------------------------
/19-queue/1-kafka/kafka.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "log"
7 | "time"
8 |
9 | "github.com/segmentio/kafka-go"
10 | )
11 |
12 | // Client - клиент очереди Kafka.
13 | type Client struct {
14 | Reader *kafka.Reader
15 | Writer *kafka.Writer
16 | }
17 |
18 | // New создает и инициализирует клиента Kafka.
19 | func New(brokers []string, topic string, groupId string) (*Client, error) {
20 | if len(brokers) == 0 || brokers[0] == "" || topic == "" || groupId == "" {
21 | return nil, errors.New("не указаны параметры подключения к Kafka")
22 | }
23 |
24 | c := Client{}
25 |
26 | c.Reader = kafka.NewReader(kafka.ReaderConfig{
27 | Brokers: brokers,
28 | Topic: topic,
29 | GroupID: groupId,
30 | MinBytes: 10e1,
31 | MaxBytes: 10e6,
32 | })
33 |
34 | c.Writer = &kafka.Writer{
35 | Addr: kafka.TCP(brokers[0]),
36 | Topic: topic,
37 | Balancer: &kafka.LeastBytes{},
38 | AllowAutoTopicCreation: true,
39 | }
40 |
41 | return &c, nil
42 | }
43 |
44 | func main() {
45 | // Инициализация клиента Kafka.
46 | kfk, err := New(
47 | []string{"localhost:29092"},
48 | "test-topic",
49 | "test-consumer-group",
50 | )
51 | if err != nil {
52 | log.Fatal(err)
53 | }
54 |
55 | go kfk.producer()
56 | kfk.consumer()
57 | }
58 |
59 | func (c *Client) producer() {
60 | for {
61 | msg := kafka.Message{
62 | Value: []byte(time.Now().String()),
63 | }
64 | err := c.Writer.WriteMessages(context.Background(), msg)
65 | if err != nil {
66 | log.Println(err)
67 | }
68 | time.Sleep(time.Second * 1)
69 | }
70 | }
71 |
72 | func (c *Client) consumer() {
73 | for {
74 | msg, err := c.Reader.FetchMessage(context.Background())
75 | if err != nil {
76 | log.Println(err)
77 | }
78 |
79 | log.Println(string(msg.Value))
80 |
81 | err = c.Reader.CommitMessages(context.Background(), msg)
82 | if err != nil {
83 | log.Println(err)
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/19-queue/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | zookeeper:
4 | image: confluentinc/cp-zookeeper:latest
5 | environment:
6 | ZOOKEEPER_CLIENT_PORT: 2181
7 | ZOOKEEPER_TICK_TIME: 2000
8 | ports:
9 | - 22181:2181
10 |
11 | kafka:
12 | image: confluentinc/cp-kafka:latest
13 | depends_on:
14 | - zookeeper
15 | ports:
16 | - 29092:29092
17 | environment:
18 | KAFKA_BROKER_ID: 1
19 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
20 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
21 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
22 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
23 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
--------------------------------------------------------------------------------
/20-NoSQL/1-mongo/mongo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 |
8 | "go.mongodb.org/mongo-driver/bson"
9 | "go.mongodb.org/mongo-driver/mongo"
10 | "go.mongodb.org/mongo-driver/mongo/options"
11 | )
12 |
13 | const (
14 | databaseName = "data" // имя учебной БД
15 | collectionName = "languages" // имя коллекции в учебной БД
16 | )
17 |
18 | type lang struct {
19 | ID int
20 | Name string
21 | }
22 |
23 | func main() {
24 | // подключение к СУБД MongoDB
25 | mongoOpts := options.Client().ApplyURI("mongodb://localhost:27017/")
26 | client, err := mongo.Connect(context.Background(), mongoOpts)
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 |
31 | // не забываем закрывать ресурсы
32 | defer client.Disconnect(context.Background())
33 |
34 | // проверка связи с БД
35 | err = client.Ping(context.Background(), nil)
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 |
40 | // вставка пары документов в БД
41 | data := []lang{
42 | {ID: 1, Name: "Go"},
43 | {ID: 2, Name: "JavaScript"},
44 | }
45 | err = insertDocs(client, data)
46 | if err != nil {
47 | log.Fatal(err)
48 | }
49 |
50 | // получение документов из БД
51 | docs, err := docs(client)
52 | if err != nil {
53 | log.Fatal(err)
54 | }
55 | fmt.Println(docs)
56 | }
57 |
58 | // insertDocs вставляет в БД моссив документов.
59 | func insertDocs(c *mongo.Client, data []lang) error {
60 | collection := c.Database(databaseName).Collection(collectionName)
61 | for _, doc := range data {
62 | _, err := collection.InsertOne(context.Background(), doc)
63 | if err != nil {
64 | return err
65 | }
66 | }
67 | return nil
68 | }
69 |
70 | // docs возвращает все документы из БД.
71 | func docs(c *mongo.Client) ([]lang, error) {
72 | collection := c.Database(databaseName).Collection(collectionName)
73 | filter := bson.D{}
74 |
75 | cur, err := collection.Find(context.Background(), filter)
76 | if err != nil {
77 | return nil, err
78 | }
79 | defer cur.Close(context.Background())
80 |
81 | var data []lang
82 | for cur.Next(context.Background()) {
83 | var l lang
84 | err := cur.Decode(&l)
85 | if err != nil {
86 | return nil, err
87 | }
88 | data = append(data, l)
89 | }
90 |
91 | return data, cur.Err()
92 | }
93 |
--------------------------------------------------------------------------------
/20-NoSQL/2-KVStore/cmd/app/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "go-core-4/20-NoSQL/2-KVStore/pkg/kvdb"
6 | )
7 |
8 | func main() {
9 | db := kvdb.New()
10 | db.SET("1", "test record")
11 | fmt.Println(db.GET("1"), db.GET("2"))
12 | }
13 |
--------------------------------------------------------------------------------
/20-NoSQL/2-KVStore/pkg/kvdb/kvdb.go:
--------------------------------------------------------------------------------
1 | package kvdb
2 |
3 | // DB - БД "ключ-значение"
4 | type DB map[string]string
5 |
6 | func New() DB {
7 | return make(map[string]string)
8 | }
9 |
10 | func (db DB) GET(key string) string {
11 | if val, ok := db[key]; ok {
12 | return val
13 | }
14 | return ""
15 | }
16 |
17 | func (db DB) SET(key string, val string) {
18 | db[key] = val
19 | }
20 |
--------------------------------------------------------------------------------
/20-NoSQL/3-redis/redis.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "strconv"
9 | "time"
10 |
11 | // импорт драйвера
12 | "github.com/go-redis/redis/v8"
13 | )
14 |
15 | // сущность для хранения в СУБД
16 | type book struct {
17 | ID int
18 | Title string
19 | }
20 |
21 | func main() {
22 | // подключение к СУБД
23 | redisClient := redis.NewClient(&redis.Options{
24 | Addr: "localhost:6379",
25 | Password: "", // без пароля
26 | DB: 0, // БД по умолчанию
27 | })
28 |
29 | books := []book{
30 | {ID: 1, Title: "1984"},
31 | {ID: 2, Title: "Clean Architecture"},
32 | }
33 |
34 | // выполнение запросов
35 | err := setBooks(redisClient, books)
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 |
40 | data, err := getBooks(redisClient, []int{1, 2})
41 | if err != nil {
42 | log.Fatal(err)
43 | }
44 | fmt.Println(data)
45 | }
46 |
47 | // getBooks возвращает книги из кэша.
48 | func getBooks(client *redis.Client, ids []int) ([]book, error) {
49 | var books []book
50 |
51 | for _, id := range ids {
52 | cmd := client.Get(context.Background(), "books:"+strconv.Itoa(id))
53 | var b book
54 |
55 | err := json.Unmarshal([]byte(cmd.Val()), &b)
56 | if err != nil {
57 | return nil, err
58 | }
59 |
60 | books = append(books, b)
61 | }
62 |
63 | return books, nil
64 | }
65 |
66 | // setBooks обновляет данные в кэше Redis.
67 | func setBooks(client *redis.Client, books []book) error {
68 | for _, b := range books {
69 | key := "books:" + strconv.Itoa(b.ID)
70 |
71 | val, err := json.Marshal(b)
72 | if err != nil {
73 | return err
74 | }
75 |
76 | err = client.Set(context.Background(), key, string(val), time.Minute*10).Err()
77 | if err != nil {
78 | return err
79 | }
80 | }
81 |
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/20-NoSQL/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | mongo:
3 | image: mongo
4 | volumes:
5 | - /data/db/mongo
6 | ports:
7 | - "27017:27017"
8 |
9 | redis:
10 | image: redis
11 | volumes:
12 | - /data/db/redis
13 | ports:
14 | - "6379:6379"
15 |
--------------------------------------------------------------------------------
/21-interview/01-basics.go:
--------------------------------------------------------------------------------
1 | package interview
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | func TypicalErrorsNoSync() {
9 | for i := 0; i < 10; i++ {
10 | go func() {
11 | fmt.Println(i)
12 | }()
13 | }
14 | }
15 |
16 | func TypicalErrorsIndex() {
17 | var wg sync.WaitGroup
18 | wg.Add(10)
19 |
20 | for i := 0; i < 10; i++ {
21 | go func() {
22 | fmt.Println(i)
23 | wg.Done()
24 | }()
25 | }
26 |
27 | wg.Wait()
28 | }
29 |
30 | func Slices() {
31 | s1 := []int{1, 2, 3, 4}
32 | s2 := s1[1:3]
33 | fmt.Println("До изменения S2")
34 | fmt.Println(s1)
35 | fmt.Println(s2)
36 |
37 | s2[1] = 999
38 | fmt.Println("После изменения S2")
39 | fmt.Println(s1)
40 | fmt.Println(s2)
41 |
42 | s2 = append(s2, 5, 6, 7, 8, 9)
43 | fmt.Println("После добавления к S2")
44 | fmt.Println(s1)
45 | fmt.Println(s2)
46 | }
47 |
48 | func Strings() {
49 | s := "Привет!"
50 |
51 | for index, rune := range s {
52 | fmt.Println(index, rune)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/21-interview/01-basics_test.go:
--------------------------------------------------------------------------------
1 | package interview
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestTypicalErrorsNoSync(t *testing.T) {
8 | TypicalErrorsNoSync()
9 | }
10 |
11 | func TestTypicalErrorsIndex(t *testing.T) {
12 | TypicalErrorsIndex()
13 | }
14 |
15 | func TestSlices(t *testing.T) {
16 | Slices()
17 | }
18 |
19 | func TestStrings(t *testing.T) {
20 | Strings()
21 | }
22 |
--------------------------------------------------------------------------------
/21-interview/02-channels.go:
--------------------------------------------------------------------------------
1 | package interview
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | func Select() {
9 | ch := make(chan string)
10 | select {
11 | case ch <- "string":
12 | fmt.Println("получилось записать в канал")
13 | default:
14 | fmt.Println("не получилось записать в канал")
15 | }
16 | }
17 |
18 | func ProduceConsume() {
19 | ch := make(chan int)
20 |
21 | go func() {
22 | for i := 0; i < 10; i++ {
23 | ch <- i
24 | }
25 | }()
26 |
27 | for val := range ch {
28 | fmt.Println(val)
29 | }
30 | }
31 |
32 | func FanIn[T any](channels ...<-chan T) <-chan T {
33 | ch := make(chan T)
34 | var wg sync.WaitGroup
35 | wg.Add(len(channels))
36 |
37 | for _, c := range channels {
38 | go func(in <-chan T) {
39 | defer wg.Done()
40 | for i := range in {
41 | ch <- i
42 | }
43 | }(c)
44 | }
45 |
46 | go func() {
47 | wg.Wait()
48 | close(ch)
49 | }()
50 |
51 | return ch
52 | }
53 |
--------------------------------------------------------------------------------
/21-interview/02-channels_test.go:
--------------------------------------------------------------------------------
1 | package interview
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestSelect(t *testing.T) {
8 | Select()
9 | }
10 |
11 | func TestProduceConsume(t *testing.T) {
12 | ProduceConsume()
13 | }
14 |
15 | func TestFanIn(t *testing.T) {
16 | ch1 := make(chan int)
17 | ch2 := make(chan int)
18 | go func() {
19 | for i := 0; i < 10; i++ {
20 | ch1 <- i
21 | }
22 | }()
23 | go func() {
24 | for i := 11; i < 20; i++ {
25 | ch1 <- i
26 | }
27 | }()
28 |
29 | ch := FanIn(ch1, ch2)
30 | for val := range ch {
31 | t.Log(val)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/GoSearch/cmd/gosearch/gosearh.go:
--------------------------------------------------------------------------------
1 | package main
2 |
--------------------------------------------------------------------------------
/GoSearch/pkg/crawler/crawler.go:
--------------------------------------------------------------------------------
1 | package crawler
2 |
3 | // Поисковый робот.
4 | // Осуществляет сканирование сайтов.
5 |
6 | // Interface определяет контракт поискового робота.
7 | type Interface interface {
8 | Scan(url string, depth int) ([]Document, error)
9 | BatchScan(urls []string, depth int, workers int) (<-chan Document, <-chan error)
10 | }
11 |
12 | // Document - документ, веб-страница, полученная поисковым роботом.
13 | type Document struct {
14 | ID int
15 | URL string
16 | Title string
17 | Body string
18 | }
19 |
--------------------------------------------------------------------------------
/GoSearch/pkg/crawler/membot/membot.go:
--------------------------------------------------------------------------------
1 | package membot
2 |
3 | import (
4 | "go-core-4/gosearch/pkg/crawler"
5 | )
6 |
7 | // Service - имитация служба поискового робота.
8 | type Service struct{}
9 |
10 | // New - констрктор имитации службы поискового робота.
11 | func New() *Service {
12 | s := Service{}
13 | return &s
14 | }
15 |
16 | // Scan возвращает заранее подготовленный набор данных
17 | func (s *Service) Scan(url string, depth int) ([]crawler.Document, error) {
18 |
19 | data := []crawler.Document{
20 | {
21 | ID: 0,
22 | URL: "https://yandex.ru",
23 | Title: "Яндекс",
24 | },
25 | {
26 | ID: 1,
27 | URL: "https://google.ru",
28 | Title: "Google",
29 | },
30 | }
31 |
32 | return data, nil
33 | }
34 |
--------------------------------------------------------------------------------
/GoSearch/pkg/crawler/spider/spider.go:
--------------------------------------------------------------------------------
1 | // Package spider реализует сканер содержимого веб-сайтов.
2 | // Пакет позволяет получить список ссылок и заголовков страниц внутри веб-сайта по его URL.
3 | package spider
4 |
5 | import (
6 | "net/http"
7 | "strings"
8 |
9 | "go-core-4/gosearch/pkg/crawler"
10 |
11 | "golang.org/x/net/html"
12 | )
13 |
14 | // Service - служба поискового робота.
15 | type Service struct{}
16 |
17 | // New - констрктор службы поискового робота.
18 | func New() *Service {
19 | s := Service{}
20 | return &s
21 | }
22 |
23 | // Scan осуществляет рекурсивный обход ссылок сайта, указанного в URL,
24 | // с учётом глубины перехода по ссылкам, переданной в depth.
25 | func (s *Service) Scan(url string, depth int) (data []crawler.Document, err error) {
26 | pages := make(map[string]string)
27 |
28 | parse(url, url, depth, pages)
29 |
30 | for url, title := range pages {
31 | item := crawler.Document{
32 | URL: url,
33 | Title: title,
34 | }
35 | data = append(data, item)
36 | }
37 |
38 | return data, nil
39 | }
40 |
41 | // parse рекурсивно обходит ссылки на странице, переданной в url.
42 | // Глубина рекурсии задаётся в depth.
43 | // Каждая найденная ссылка записывается в ассоциативный массив
44 | // data вместе с названием страницы.
45 | func parse(url, baseurl string, depth int, data map[string]string) error {
46 | if depth == 0 {
47 | return nil
48 | }
49 |
50 | response, err := http.Get(url)
51 | if err != nil {
52 | return err
53 | }
54 | page, err := html.Parse(response.Body)
55 | if err != nil {
56 | return err
57 | }
58 |
59 | data[url] = pageTitle(page)
60 |
61 | if depth == 1 {
62 | return nil
63 | }
64 | links := pageLinks(nil, page)
65 | for _, link := range links {
66 | link = strings.TrimSuffix(link, "/")
67 | // относительная ссылка
68 | if strings.HasPrefix(link, "/") && len(link) > 1 {
69 | link = baseurl + link
70 | }
71 | // ссылка уже отсканирована
72 | if data[link] != "" {
73 | continue
74 | }
75 | // ссылка содержит базовый url полностью
76 | if strings.HasPrefix(link, baseurl) {
77 | parse(link, baseurl, depth-1, data)
78 | }
79 | }
80 |
81 | return nil
82 | }
83 |
84 | // pageTitle осуществляет рекурсивный обход HTML-страницы и возвращает значение элемента .
85 | func pageTitle(n *html.Node) string {
86 | var title string
87 | if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil {
88 | return n.FirstChild.Data
89 | }
90 | for c := n.FirstChild; c != nil; c = c.NextSibling {
91 | title = pageTitle(c)
92 | if title != "" {
93 | break
94 | }
95 | }
96 | return title
97 | }
98 |
99 | // pageLinks рекурсивно сканирует узлы HTML-страницы и возвращает все найденные ссылки без дубликатов.
100 | func pageLinks(links []string, n *html.Node) []string {
101 | if n.Type == html.ElementNode && n.Data == "a" {
102 | for _, a := range n.Attr {
103 | if a.Key == "href" {
104 | if !sliceContains(links, a.Val) {
105 | links = append(links, a.Val)
106 | }
107 | }
108 | }
109 | }
110 | for c := n.FirstChild; c != nil; c = c.NextSibling {
111 | links = pageLinks(links, c)
112 | }
113 | return links
114 | }
115 |
116 | // sliceContains возвращает true если массив содержит переданное значение
117 | func sliceContains(slice []string, value string) bool {
118 | for _, v := range slice {
119 | if v == value {
120 | return true
121 | }
122 | }
123 | return false
124 | }
125 |
--------------------------------------------------------------------------------
/GoSearch/pkg/crawler/spider/spider_test.go:
--------------------------------------------------------------------------------
1 | package spider
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **go-core-4**
2 |
3 | # Thinknetica
4 |
5 | Примеры кода для четвертого потока курса "Современная разработка на Go".
6 | Автор: Дмитрий Титов.
7 |
--------------------------------------------------------------------------------
/file.txt:
--------------------------------------------------------------------------------
1 | ABCtrue{"Title":"Nevermind","Year":1991}
--------------------------------------------------------------------------------
/final.md:
--------------------------------------------------------------------------------
1 | # Итоговый проект
2 | ## Сервис хранения коротких гиперссылок (URL Shortener)
3 |
4 | Пример: https://www.shorturl.at/
5 |
6 | ### Название
7 | Наше приложение будет называться **Lynks**.
8 |
9 | ### Возможности сервиса
10 | Сервис должен предоставлять пользователям следующие взможности:
11 | 1. Принимать на вход длинную ссылку, формировать на её основе короткую на базе собственного домена, сохранять в БД пару ссылок и возвращать пользователю короткую ссылку.
12 | 2. При переходе пользователя по короткой ссылке на домен нашего сервиса, программа должна запросить из БД длинную ссылку на основе короткой и выполнить перенаправление пользователя на оригинальную страницу.
13 |
14 | ### Пример
15 | Допустим, что наш сервис работает на домене **lynks.org**. Тогда, если пользователь выполняет HTTP-запрос:
16 | ```
17 | curl --location 'https://lynks.org' \
18 | --header 'Content-Type: application/json' \
19 | --data '{
20 | "destination": "https://github.com/thinknetica/go_course_4"
21 | }'
22 | ```
23 |
24 | То сервис может вернуть примерно такой ответ:
25 | ```
26 | {
27 | "shortUrl": "https://lynks.org/qz6d7",
28 | "destination": "https://github.com/thinknetica/go_course_4"
29 | }
30 | ```
31 | Далее, если пользователь перейдёт по ссылке `https://lynks.org/qz6d7` то сервис выполнит HTTP Redirect (https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections) на страницу `https://github.com/thinknetica/go_course_4`.
32 |
33 | ### Состав компонентов и схема
34 | Сервис должен состоять из следующих компонентов:
35 | 1. **Микросервис коротких гиперссылок.** Этот микросервис должен предоставлять методы API для формирования короткой ссылки и выполнять переход по короткой ссылке на оригинальную страницу.
36 | 2. **БД микросервиса коротких ссылок.** Это должна быть СУБД PostgreSQL с одной таблицей для хранения пар гиперссылок. Для таблицы нужно создать индекс для эффективного поиска длинной гиперссылки по короткой.
37 | 3. **Микросервис кэширования.** Этот микросервис должен ускорить поиск гиперссылок за счёт хранения недавно созданных пар ссылок в быстрой in-memory БД. Микросервис кэширования должен предоставлять метод API, который бы принимал и сохранял в БД пару гиперссылок. Также должен быть метод API для получения длинной ссылки на основе короткой.
38 | 4. **БД микросервиса кэширования.** Следует использовать СУБД Redis для хранения и быстрого доступа к парам гиперссылок.
39 |
40 | ### Структура приложения
41 | Каждый микросервис должен иметь структуру каталогов как принято в курсе (см. занятие по архитектуре приложения).
42 | Пример структуры каталогов проекта находится в репозитории курса: https://github.com/thinknetica/go_course_4/tree/master/lynks
43 | Можно разместить оба микросервиса в одном общем каталоге внутри модуля всего курса. Создавать отдельный репозиторий не требуется.
44 |
45 | ### Дополнительные требования
46 | - Микросервисы должны предоставлять HTTP API.
47 | - Для имён запросов нужно следовать REST-методологии (коллекция-ресурс).
48 | - Для получения данных должен использоваться метод GET, для записи - POST.
49 | - Тело запросов и ответов должно быть в формате JSON.
50 | - Базы данных следует развернуть в виде контейнеров.
51 | - Микросервисы также следует запускать в контейнере. Нужно добавить Dockerfile для каждого сервиса.
52 | - Нужно сделать итоговый Docker Compose файл, который сможет запустить всё приложение целиком.
53 | - Каждый микросервис должен подсчитывать метрики запросов в формате Prometheus: количество запросов к каждому методу API (CounterVec) и время обработки запроса (HistogramVec). Пример: https://prometheus.io/docs/guides/go-application/
54 | - Микросервисы должны использовать структурированное логирование с помощью пакета https://github.com/rs/zerolog. Следует писать в консоль (stdout) каждое обращение к методам API и все возникающие в работе ошибки.
55 | - БД кэширования должна хранить пары гиперссылок не более 24 часов (следует указать время экспирации).
56 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module go-core-4
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/DmitriyVTitov/size v1.5.0
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
8 | github.com/go-redis/redis/v8 v8.11.5
9 | github.com/go-sql-driver/mysql v1.7.1
10 | github.com/gorilla/mux v1.8.0
11 | github.com/gorilla/sessions v1.2.1
12 | github.com/jackc/pgx/v4 v4.18.1
13 | github.com/jackc/pgx/v5 v5.4.3
14 | github.com/rs/zerolog v1.29.1
15 | github.com/segmentio/kafka-go v0.4.42
16 | go.mongodb.org/mongo-driver v1.12.1
17 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
18 | golang.org/x/net v0.11.0
19 | google.golang.org/grpc v1.57.0
20 | google.golang.org/protobuf v1.31.0
21 | )
22 |
23 | require (
24 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
25 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
26 | github.com/golang/protobuf v1.5.3 // indirect
27 | github.com/golang/snappy v0.0.1 // indirect
28 | github.com/gorilla/securecookie v1.1.1 // indirect
29 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
30 | github.com/jackc/pgconn v1.14.0 // indirect
31 | github.com/jackc/pgio v1.0.0 // indirect
32 | github.com/jackc/pgpassfile v1.0.0 // indirect
33 | github.com/jackc/pgproto3/v2 v2.3.2 // indirect
34 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
35 | github.com/jackc/pgtype v1.14.0 // indirect
36 | github.com/jackc/puddle/v2 v2.2.1 // indirect
37 | github.com/klauspost/compress v1.15.9 // indirect
38 | github.com/mattn/go-colorable v0.1.12 // indirect
39 | github.com/mattn/go-isatty v0.0.14 // indirect
40 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
41 | github.com/pierrec/lz4/v4 v4.1.15 // indirect
42 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
43 | github.com/xdg-go/scram v1.1.2 // indirect
44 | github.com/xdg-go/stringprep v1.0.4 // indirect
45 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
46 | golang.org/x/crypto v0.10.0 // indirect
47 | golang.org/x/sync v0.1.0 // indirect
48 | golang.org/x/sys v0.9.0 // indirect
49 | golang.org/x/text v0.10.0 // indirect
50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
51 | )
52 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g=
3 | github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
4 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
5 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
6 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
7 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
8 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
9 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
10 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
11 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
12 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
17 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
19 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
20 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
21 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
22 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
23 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
24 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
25 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
26 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
27 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
28 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
29 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
30 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
31 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
32 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
33 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
34 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
35 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
36 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
37 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
38 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
39 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
40 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
41 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
42 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
43 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
44 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
45 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
46 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
47 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
48 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
49 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
50 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
51 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
52 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
53 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
54 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
55 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
56 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
57 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
58 | github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
59 | github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
60 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
61 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
62 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
63 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
64 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
65 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
66 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
67 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
68 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
69 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
70 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
71 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
72 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
73 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
74 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
75 | github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
76 | github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
77 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
78 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
79 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
80 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
81 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
82 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
83 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
84 | github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
85 | github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
86 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
87 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
88 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
89 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
90 | github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
91 | github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
92 | github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
93 | github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
94 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
95 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
96 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
97 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
98 | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
99 | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
100 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
101 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
102 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
103 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
104 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
105 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
106 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
107 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
108 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
109 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
110 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
111 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
112 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
113 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
114 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
115 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
116 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
117 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
118 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
119 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
120 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
121 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
122 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
123 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
124 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
125 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
126 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
127 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
128 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
129 | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
130 | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
131 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
132 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
133 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
134 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
135 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
136 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
137 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
138 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
139 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
140 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
141 | github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
142 | github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
143 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
144 | github.com/segmentio/kafka-go v0.4.42 h1:qffhBZCz4WcWyNuHEclHjIMLs2slp6mZO8px+5W5tfU=
145 | github.com/segmentio/kafka-go v0.4.42/go.mod h1:d0g15xPMqoUookug0OU75DhGZxXwCFxSLeJ4uphwJzg=
146 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
147 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
148 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
149 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
150 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
151 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
152 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
153 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
154 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
155 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
156 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
157 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
158 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
159 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
160 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
161 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
162 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
163 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
164 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
165 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
166 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
167 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
168 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
169 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
170 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
171 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
172 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
173 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
174 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
175 | go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
176 | go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
177 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
178 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
179 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
180 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
181 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
182 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
183 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
184 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
185 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
186 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
187 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
188 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
189 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
190 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
191 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
192 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
193 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
194 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
195 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
196 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
197 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
198 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
199 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
200 | golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
201 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
202 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
203 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
204 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
205 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
206 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
207 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
208 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
209 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
210 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
211 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
212 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
213 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
214 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
215 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
216 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
217 | golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
218 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
219 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
220 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
221 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
222 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
223 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
224 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
225 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
226 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
227 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
228 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
229 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
230 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
231 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
232 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
233 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
234 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
235 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
236 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
237 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
238 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
239 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
240 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
241 | golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
242 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
243 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
244 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
245 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
246 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
247 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
248 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
249 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
250 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
251 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
252 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
253 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
254 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
255 | golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
256 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
257 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
258 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
259 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
260 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
261 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
262 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
263 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
264 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
265 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
266 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
267 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
268 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
269 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
270 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
271 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
272 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
273 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
274 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
275 | google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
276 | google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
277 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
278 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
279 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
280 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
281 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
282 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
283 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
284 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
285 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
286 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
287 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
288 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
289 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
290 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
291 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
292 |
--------------------------------------------------------------------------------
/lynks/memcache/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {}
4 |
--------------------------------------------------------------------------------
/lynks/memcache/pkg/redis/redis.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
--------------------------------------------------------------------------------
/lynks/shortener/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {}
4 |
--------------------------------------------------------------------------------
/lynks/shortener/pkg/urls/urls.go:
--------------------------------------------------------------------------------
1 | package urls
2 |
3 | func Shorten(src string) string {
4 | return ""
5 | }
6 |
--------------------------------------------------------------------------------