From e8bd90d8d96ae2aef8133932a03990ad14ed1deb Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Thu, 15 Sep 2016 01:22:57 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D1=81=D0=BB=D1=83=D1=88?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20RabbitMQ=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D1=83?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D0=B5=20handlers=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.base.yml | 9 +++- docker-compose.dev.yml | 6 +++ docker-compose.prod.yml | 7 ++++ lib/services/services.go | 4 +- lib/worker/handlers.go | 52 +++++++++++++++++++++++ lib/worker/models.go | 17 ++++++++ lib/worker/worker.go | 91 ++++++++++++++++++++++++++++++++++++++++ minecraft-skinsystem.go | 21 +++++++++- 8 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 lib/worker/handlers.go create mode 100644 lib/worker/models.go create mode 100644 lib/worker/worker.go diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 4632d92..a7db8a2 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -2,9 +2,16 @@ version: '2' services: app: ports: - - "80:80" + - "81:80" redis: image: redis:3.0 volumes: - ./data/redis:/data + + rabbitmq: + image: rabbitmq:3.6 + environment: + RABBITMQ_DEFAULT_USER: "ely-skinsystem-app" + RABBITMQ_DEFAULT_PASS: "ely-skinsystem-app-password" + RABBITMQ_DEFAULT_VHOST: "/ely" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a1547cc..087a4bc 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -10,8 +10,14 @@ services: command: ["go", "run", "minecraft-skinsystem.go"] links: - redis + - rabbitmq redis: extends: file: docker-compose.base.yml service: redis + + rabbitmq: + extends: + file: docker-compose.base.yml + service: rabbitmq diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 5663db1..a995d39 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -7,6 +7,7 @@ services: image: erickskrauch/ely-by-skinsystem:master links: - redis + - rabbitmq restart: always redis: @@ -14,3 +15,9 @@ services: file: docker-compose.base.yml service: redis restart: always + + rabbitmq: + extends: + file: docker-compose.base.yml + service: rabbitmq + restart: always diff --git a/lib/services/services.go b/lib/services/services.go index f0f612d..5f4e996 100644 --- a/lib/services/services.go +++ b/lib/services/services.go @@ -2,9 +2,9 @@ package services import ( "github.com/mediocregopher/radix.v2/pool" - "github.com/gorilla/mux" + "github.com/streadway/amqp" ) var RedisPool *pool.Pool -var Router *mux.Router +var RabbitMQChannel *amqp.Channel diff --git a/lib/worker/handlers.go b/lib/worker/handlers.go new file mode 100644 index 0000000..7986490 --- /dev/null +++ b/lib/worker/handlers.go @@ -0,0 +1,52 @@ +package worker + +import ( + "elyby/minecraft-skinsystem/lib/data" + "log" +) + +func handleChangeUsername(model usernameChanged) (bool) { + if (model.OldUsername == "") { + record := data.SkinItem{ + UserId: model.AccountId, + Username: model.NewUsername, + } + + record.Save() + + return true + } + + record, err := data.FindByUsername(model.OldUsername) + if (err != nil) { + log.Println("Exit by not found record") + // TODO: я не уверен, что это валидное поведение + // Суть в том, что здесь может возникнуть ошибка в том случае, если записи в базе нету + // а значит его нужно, как минимум, зарегистрировать + return true + } + + record.Username = model.NewUsername + record.Save() + + log.Println("all saved!") + + return true +} + +func handleSkinChanged(model skinChanged) (bool) { + record, err := data.FindById(model.AccountId) + if (err != nil) { + return true + } + + record.SkinId = model.SkinId + record.Hash = model.Hash + record.Is1_8 = model.Is1_8 + record.IsSlim = model.IsSlim + record.Url = model.Url + + record.Save() + + return true +} diff --git a/lib/worker/models.go b/lib/worker/models.go new file mode 100644 index 0000000..eede2c2 --- /dev/null +++ b/lib/worker/models.go @@ -0,0 +1,17 @@ +package worker + +type usernameChanged struct { + AccountId int `json:"accountId"` + OldUsername string `json:"oldUsername"` + NewUsername string `json:"newUsername"` +} + +type skinChanged struct { + AccountId int `json:"userId"` + SkinId int `json:"skinId"` + OldSkinId int `json:"oldSkinId"` + Hash string `json:"hash"` + Is1_8 bool `json:"is1_8"` + IsSlim bool `json:"isSlim"` + Url string `json:"url"` +} diff --git a/lib/worker/worker.go b/lib/worker/worker.go new file mode 100644 index 0000000..ba27151 --- /dev/null +++ b/lib/worker/worker.go @@ -0,0 +1,91 @@ +package worker + +import ( + "log" + + "encoding/json" + + "elyby/minecraft-skinsystem/lib/services" +) + +const exchangeName string = "events" +const queueName string = "skinsystem-accounts-events" + +func Listen() { + var err error + ch := services.RabbitMQChannel + + err = ch.ExchangeDeclare( + exchangeName, // name + "topic", // type + true, // durable + false, // auto-deleted + false, // internal + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare an exchange") + + _, err = ch.QueueDeclare( + queueName, // name + true, // durable + false, // delete when usused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + err = ch.QueueBind( + queueName, // queue name + "accounts.#", // routing key + exchangeName, // exchange + false, + nil, + ) + failOnError(err, "Failed to bind a queue") + + msgs, err := ch.Consume( + queueName, // queue + "", // consumer + false, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args + ) + failOnError(err, "Failed to register a consumer") + + forever := make(chan bool) + + go func() { + for d := range msgs { + log.Println("Incoming message with routing key " + d.RoutingKey) + var result bool = true; + switch d.RoutingKey { + case "accounts.username-changed": + var model usernameChanged + json.Unmarshal(d.Body, &model) + result = handleChangeUsername(model) + case "accounts.skin-changed": + var model skinChanged + json.Unmarshal(d.Body, &model) + result = handleSkinChanged(model) + } + + if (result) { + d.Ack(false) + } else { + d.Reject(true) + } + } + }() + + <-forever +} + +func failOnError(err error, msg string) { + if err != nil { + log.Fatalf("%s: %s", msg, err) + } +} diff --git a/minecraft-skinsystem.go b/minecraft-skinsystem.go index 49df237..5d86c59 100644 --- a/minecraft-skinsystem.go +++ b/minecraft-skinsystem.go @@ -12,9 +12,13 @@ import ( "elyby/minecraft-skinsystem/lib/routes" "elyby/minecraft-skinsystem/lib/services" //"github.com/mediocregopher/radix.v2/redis" + + "github.com/streadway/amqp" + "elyby/minecraft-skinsystem/lib/worker" ) const redisString string = "redis:6379" +const rabbitmqString string = "amqp://ely-skinsystem-app:ely-skinsystem-app-password@rabbitmq:5672/%2fely" func main() { log.Println("Starting...") @@ -28,6 +32,19 @@ func main() { } log.Println("Connected to redis") + log.Println("Connecting to rabbitmq") + // TODO: rabbitmq становится доступен не сразу. Нужно дождаться, пока он станет доступен, периодически повторяя запросы + rabbitConnection, rabbitmqErr := amqp.Dial(rabbitmqString) + if rabbitmqErr != nil { + log.Fatalf("%s", rabbitmqErr) + } + log.Println("Connected to rabbitmq. Trying to open a channel") + rabbitChannel, rabbitmqErr := rabbitConnection.Channel() + if rabbitmqErr != nil { + log.Fatalf("%s", rabbitmqErr) + } + log.Println("Connected to rabbitmq channel") + router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/skins/{username}", routes.Skin).Methods("GET").Name("skins") router.HandleFunc("/cloaks/{username}", routes.Cape).Methods("GET").Name("cloaks") @@ -43,7 +60,7 @@ func main() { apiRouter.HandleFunc("/user/{username}/skin", routes.SetSkin).Methods("POST") services.RedisPool = redisPool - services.Router = router + services.RabbitMQChannel = rabbitChannel /*go func() { for { @@ -63,6 +80,8 @@ func main() { } }()*/ + go worker.Listen() + log.Println("Started"); log.Fatal(http.ListenAndServe(":80", router)) } From 3e3ba296d548d2ee9953a7adc0487ab7181604b7 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Thu, 15 Sep 2016 01:29:08 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=91=D0=B8=D0=BD=D0=B4=D0=B8=D0=BC=D1=81?= =?UTF-8?q?=D1=8F=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BA=20=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=D1=83,=20=D1=87=D1=82=D0=BE=20=D0=BD=D0=B0=D0=BC?= =?UTF-8?q?=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B5=D1=81=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/worker/worker.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/worker/worker.go b/lib/worker/worker.go index ba27151..27a3407 100644 --- a/lib/worker/worker.go +++ b/lib/worker/worker.go @@ -36,13 +36,10 @@ func Listen() { ) failOnError(err, "Failed to declare a queue") - err = ch.QueueBind( - queueName, // queue name - "accounts.#", // routing key - exchangeName, // exchange - false, - nil, - ) + err = ch.QueueBind(queueName, "accounts.username-changed", exchangeName, false, nil) + failOnError(err, "Failed to bind a queue") + + err = ch.QueueBind(queueName, "accounts.skin-changed", exchangeName, false, nil) failOnError(err, "Failed to bind a queue") msgs, err := ch.Consume( From 2477433dc9de81f7980cccf789333e277805c79d Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 16 Sep 2016 19:35:58 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=20red?= =?UTF-8?q?is?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- minecraft-skinsystem.go | 46 ++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/minecraft-skinsystem.go b/minecraft-skinsystem.go index 5d86c59..513aa30 100644 --- a/minecraft-skinsystem.go +++ b/minecraft-skinsystem.go @@ -3,21 +3,21 @@ package main import ( "log" "runtime" - //"time" + "time" "net/http" "github.com/gorilla/mux" + "github.com/streadway/amqp" "github.com/mediocregopher/radix.v2/pool" "elyby/minecraft-skinsystem/lib/routes" "elyby/minecraft-skinsystem/lib/services" - //"github.com/mediocregopher/radix.v2/redis" - - "github.com/streadway/amqp" "elyby/minecraft-skinsystem/lib/worker" ) const redisString string = "redis:6379" +const redisPoolSize int = 10 + const rabbitmqString string = "amqp://ely-skinsystem-app:ely-skinsystem-app-password@rabbitmq:5672/%2fely" func main() { @@ -26,8 +26,8 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) log.Println("Connecting to redis") - redisPool, redisErr := pool.New("tcp", redisString, 10) - if redisErr != nil { + redisPool, redisErr := pool.New("tcp", redisString, redisPoolSize) + if (redisErr != nil) { log.Fatal("Redis unavailable") } log.Println("Connected to redis") @@ -35,12 +35,12 @@ func main() { log.Println("Connecting to rabbitmq") // TODO: rabbitmq становится доступен не сразу. Нужно дождаться, пока он станет доступен, периодически повторяя запросы rabbitConnection, rabbitmqErr := amqp.Dial(rabbitmqString) - if rabbitmqErr != nil { + if (rabbitmqErr != nil) { log.Fatalf("%s", rabbitmqErr) } log.Println("Connected to rabbitmq. Trying to open a channel") rabbitChannel, rabbitmqErr := rabbitConnection.Channel() - if rabbitmqErr != nil { + if (rabbitmqErr != nil) { log.Fatalf("%s", rabbitmqErr) } log.Println("Connected to rabbitmq channel") @@ -62,23 +62,27 @@ func main() { services.RedisPool = redisPool services.RabbitMQChannel = rabbitChannel - /*go func() { + go func() { + period := 5 for { - time.Sleep(5 * time.Second) + time.Sleep(time.Duration(period) * time.Second) - resp := services.Redis.Cmd("PING") - if (resp.Err != nil) { - log.Println("Redis not pinged. Try to reconnect") - newClient, redisErr := redis.Dial("tcp", redisString) - if (redisErr != nil) { - log.Println("Cannot reconnect to redis") - } else { - services.Redis = newClient - log.Println("Reconnected") - } + resp := services.RedisPool.Cmd("PING") + if (resp.Err == nil) { + // Если редис успешно пинганулся, значит всё хорошо + continue + } + + log.Println("Redis not pinged. Try to reconnect") + newPool, redisErr := pool.New("tcp", redisString, redisPoolSize) + if (redisErr != nil) { + log.Printf("Cannot reconnect to redis, waiting %d seconds\n", period) + } else { + services.RedisPool = newPool + log.Println("Reconnected") } } - }()*/ + }() go worker.Listen()