From fa4be8d5ff2acfd6d1d16a58235be76c5af515c5 Mon Sep 17 00:00:00 2001 From: moxitech Date: Sat, 5 Oct 2024 12:19:35 +0700 Subject: [PATCH] Websocket connection, signaling, streaming & mongo driver & upd simulator --- .env | 2 +- docker-compose.yaml | 1 + src/server/cmd/main.go | 7 + src/server/entity/dto/server.go | 17 ++ src/server/entity/websocket/dto.go | 12 ++ src/server/entity/websocket/models.go | 26 +++ src/server/go.mod | 7 + src/server/go.sum | 38 ++++ src/server/internal/database/mongo.go | 103 ++++++++++ src/server/internal/server/server.go | 130 ++++++++++-- src/server/internal/server/websocket.go | 194 +++++++++++++++++- .../package/math/simulator/simulator.go | 26 ++- src/server/package/parser/json/json.go | 77 +++++++ .../package/utils/randomizer/randomizer.go | 21 ++ 14 files changed, 635 insertions(+), 26 deletions(-) create mode 100644 src/server/entity/dto/server.go create mode 100644 src/server/entity/websocket/dto.go create mode 100644 src/server/entity/websocket/models.go create mode 100644 src/server/package/parser/json/json.go create mode 100644 src/server/package/utils/randomizer/randomizer.go diff --git a/.env b/.env index 025c56b..a8a9ea4 100644 --- a/.env +++ b/.env @@ -5,7 +5,7 @@ MONGO_INITDB_ROOT_USERNAME=moxitech MONGO_INITDB_ROOT_PASSWORD=moxitech MONGO_INITDB_DATABASE=drone-network-simulator MONGO_INITDB_SIM_COLLECTION=simulations - +MONGO_INITDB_SIM_HIST_COLLECTION=simulations_history POSTGRES_DB=moxitech POSTGRES_USER=moxitech diff --git a/docker-compose.yaml b/docker-compose.yaml index 2526617..92e1702 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -60,6 +60,7 @@ services: - "8080:8080" depends_on: - postgres + - mongo networks: - dns_net restart: always diff --git a/src/server/cmd/main.go b/src/server/cmd/main.go index 92c97d8..bf35d8a 100644 --- a/src/server/cmd/main.go +++ b/src/server/cmd/main.go @@ -74,6 +74,13 @@ import ( // } func main() { + // JSON_L1_TEST + // data := json.T_CreateApproximateJsonStruct_l1() + // fmt.Println(data) + + // RANDOM_STRING_TEST + // fmt.Println(randomizer.GenerateRandomString()) + D_EXPORT_VARS() err := database.NewDBConnection() if err != nil { diff --git a/src/server/entity/dto/server.go b/src/server/entity/dto/server.go new file mode 100644 index 0000000..4b83155 --- /dev/null +++ b/src/server/entity/dto/server.go @@ -0,0 +1,17 @@ +package dto + +type ServerState struct { + Gorutines int `json:"go"` + MemAllocation uint64 `json:"mem_alloc"` + TotalMemAllocation uint64 `json:"total_mem_alloc"` + NumGC uint32 `json:"numgc"` +} + +type ActiveRoomsDTO struct { + Rooms []ActiveRoomDTO `json:"rooms_ids"` +} + +type ActiveRoomDTO struct { + Name string `json:"name"` + Uuid string `json:"uuid"` +} diff --git a/src/server/entity/websocket/dto.go b/src/server/entity/websocket/dto.go new file mode 100644 index 0000000..0b01050 --- /dev/null +++ b/src/server/entity/websocket/dto.go @@ -0,0 +1,12 @@ +package websocket + +import "encoding/json" + +type SignalMessage struct { + Signal int `json:"signal"` + Data json.RawMessage `json:"data"` +} +type SignalDeleteMessage struct { + Signal int `json:"signal"` + Data string `json:"data"` +} diff --git a/src/server/entity/websocket/models.go b/src/server/entity/websocket/models.go new file mode 100644 index 0000000..13457cd --- /dev/null +++ b/src/server/entity/websocket/models.go @@ -0,0 +1,26 @@ +package websocket + +import "moxitech/dns/package/math/simulator" + +type Modulation struct { + Map *MapPartial `json:"map"` + Objects []*SystemObject `json:"objects"` + Ts_update int64 `json:"ts_update"` +} + +type MapPartial struct { + Map simulator.Map `json:"map"` + Name string `json:"name"` + Changed bool `json:"changed"` +} + +type SystemObject struct { + Name string `json:"name"` + Type int `json:"type"` // 0: drone, 1: base_station + Coords [3]int `json:"coords"` + Params *map[string]string `json:"params"` +} + +func (o *SystemObject) Delete() { + +} diff --git a/src/server/go.mod b/src/server/go.mod index 0a79e61..6b482c4 100644 --- a/src/server/go.mod +++ b/src/server/go.mod @@ -12,6 +12,7 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/fasthttp/websocket v1.5.3 // indirect github.com/gofiber/websocket/v2 v2.2.1 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect @@ -23,12 +24,18 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.56.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.mongodb.org/mongo-driver v1.17.1 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect diff --git a/src/server/go.sum b/src/server/go.sum index 30539f7..7304072 100644 --- a/src/server/go.sum +++ b/src/server/go.sum @@ -9,6 +9,8 @@ github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yG github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -32,6 +34,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -50,16 +54,50 @@ github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= +go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/src/server/internal/database/mongo.go b/src/server/internal/database/mongo.go index 636bab8..960d12b 100644 --- a/src/server/internal/database/mongo.go +++ b/src/server/internal/database/mongo.go @@ -1 +1,104 @@ package database + +import ( + "context" + "log" + "os" + "sync" + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// MongoDbInstance представляет структуру для хранения подключения к MongoDB +type MongoDbInstance struct { + Client *mongo.Client + Db *mongo.Database +} + +var instance *MongoDbInstance +var once sync.Once + +// giveMeMongoConnectionString возвращает строку подключения к MongoDB +func giveMeMongoConnectionString() string { + return "mongodb://moxitech:moxitech@localhost:27017/" // Замените строку на свою строку подключения +} + +// NewDbConnection создает подключение к базе данных MongoDB и возвращает единственный экземпляр MongoDbInstance +func NewDbConnection() *MongoDbInstance { + once.Do(func() { + clientOptions := options.Client().ApplyURI(giveMeMongoConnectionString()) + + // Устанавливаем таймаут для подключения + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Подключаемся к MongoDB + client, err := mongo.Connect(ctx, clientOptions) + if err != nil { + log.Fatalf("Ошибка подключения к MongoDB: %v", err) + } + + // Проверяем соединение + err = client.Ping(ctx, nil) + if err != nil { + log.Fatalf("Ошибка пинга MongoDB: %v", err) + } + + log.Println("Успешное подключение к MongoDB") + + instance = &MongoDbInstance{ + Client: client, + Db: client.Database(os.Getenv("MONGO_INITDB_DATABASE")), + } + }) + + return instance +} + +// InsertIntoSimulations вставляет данные в коллекцию "simulations" +func (db *MongoDbInstance) InsertIntoSimulations(data interface{}) (primitive.ObjectID, error) { + // Устанавливаем контекст с таймаутом + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + collection := db.Db.Collection(os.Getenv("MONGO_INITDB_SIM_COLLECTION")) + + // Вставляем данные в коллекцию + result, err := collection.InsertOne(ctx, data) + if err != nil { + log.Printf("Ошибка при вставке данных в коллекцию '%v': %v", os.Getenv("MONGO_INITDB_SIM_COLLECTION"), err) + return primitive.NilObjectID, err + } + + // Возвращаем ID вставленного документа + insertedID := result.InsertedID.(primitive.ObjectID) + log.Printf("Данные успешно вставлены с ID: %v", insertedID) + + return insertedID, nil +} + +// InsertIntoSimulations вставляет данные в коллекцию историй симуляций +func (db *MongoDbInstance) InsertIntoSimulationsHistory(data interface{}) (primitive.ObjectID, error) { + // Устанавливаем контекст с таймаутом + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // Получаем коллекцию "simulations_history" + collection := db.Db.Collection("simulations_history") + + // Вставляем данные в коллекцию + result, err := collection.InsertOne(ctx, data) + if err != nil { + log.Printf("Ошибка при вставке данных в коллекцию 'simulations': %v", err) + return primitive.NilObjectID, err + } + + // Возвращаем ID вставленного документа + insertedID := result.InsertedID.(primitive.ObjectID) + log.Printf("Данные успешно вставлены с ID: %v", insertedID) + + return insertedID, nil +} diff --git a/src/server/internal/server/server.go b/src/server/internal/server/server.go index b7bb70e..7cfaf0e 100644 --- a/src/server/internal/server/server.go +++ b/src/server/internal/server/server.go @@ -1,28 +1,47 @@ package server import ( + "encoding/json" "fmt" + "moxitech/dns/entity/dto" "moxitech/dns/internal/server/handlers/authorization" "os" + "runtime" + "strconv" + "time" "github.com/gofiber/fiber/v2" "github.com/gofiber/websocket/v2" ) +var ( + Rooms map[string]*WebsocketRoom +) + func SpawnServer() error { app := fiber.New() + go DispatchRooms() + Rooms = make(map[string]*WebsocketRoom) app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Welcome to Drone Network Simulator server-side API") }) - app.Get("/auth/:username/:password", authorization.AuthUser) + // GET http://localhost:8080/auth/:username/:password + app.Post("/auth/:username/:password", authorization.AuthUser) + // GET http://localhost:8080/simulations/active + app.Get("/simulations/active", GetActiveRooms) + // GET http://localhost:8080/simulations/from/history + app.Get("/simulations/from/history", authorization.AuthUser) + + // GET http://localhost:8080/get/server/state + app.Get("/get/server/state", GetServerState) // WebSocket маршрут - app.Get("/ws/connect/", websocket.New(CreateSocket)) + app.Get("/ws/connect/", websocket.New(CreateOrConnectSocket)) return app.Listen(os.Getenv("SERVER_BASE_ADDRESS")) } -// WS :: ws://localhost:8080/ws/connect?userToken=?&groupId=? -func CreateSocket(c *websocket.Conn) { +// WS :: ws://localhost:8080/ws/connect?userToken=%1&groupHash=%2 +func CreateOrConnectSocket(c *websocket.Conn) { // Получаем токен пользователя из query параметров userToken := c.Query("userToken") if userToken == "" { @@ -30,20 +49,37 @@ func CreateSocket(c *websocket.Conn) { return } // Получаем группу к которой коннектиться user из query параметров - groupId := c.Query("groupId") - if groupId == "" { - c.WriteMessage(websocket.ClosePolicyViolation, []byte("groupId is required")) + groupHash := c.Query("groupHash") + if groupHash == "" { + c.WriteMessage(websocket.ClosePolicyViolation, []byte("groupHash is required")) return } - + user_id, err := strconv.Atoi(userToken) + if err != nil { + c.WriteMessage(websocket.ClosePolicyViolation, []byte("UserToken is bad!")) + return + } + _, ok := Rooms[groupHash] + if !ok { + // TODO: прокинуть шаблон + Rooms[groupHash] = CreateGroupRequest(user_id, groupHash, "") + } else { + room := Rooms[groupHash] + val := room.ConnectGroupRequest(user_id) + err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%v", string(val)))) + if err != nil { + fmt.Printf("Error writing message: %v\n", err) + } + } + room := Rooms[groupHash] // Логика обработки подключения - fmt.Printf("User connected with token: %s\n", userToken) + fmt.Printf("[I] User connected with token or id: %s\n", userToken) for { // Читаем сообщения от клиента messageType, msg, err := c.ReadMessage() if messageType != websocket.TextMessage { - err = c.WriteMessage(websocket.BinaryMessage, []byte("Please use text message instead")) + err = c.WriteMessage(websocket.BinaryMessage, []byte("PLS USE JSON TEXT STRUCTURE!")) if err != nil { fmt.Printf("Error writing message: %v\n", err) break @@ -52,15 +88,19 @@ func CreateSocket(c *websocket.Conn) { } if err != nil { fmt.Printf("Error reading message: %v\n", err) - break } + // LOGGING : fmt.Printf("Received message from user %s: %s\n", userToken, msg) + room.UpdateGroupUptime() + // Обрабатываем сообщение - // Отправляем обратно сообщение клиенту - err = c.WriteMessage(websocket.TextMessage, msg) + data, err := json.Marshal(Rooms) + if err != nil { + fmt.Printf("Error marshal json: %v\n", err) + } + err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%v", string(data)))) if err != nil { fmt.Printf("Error writing message: %v\n", err) - break } } @@ -68,3 +108,65 @@ func CreateSocket(c *websocket.Conn) { // Закрываем соединение при выходе c.Close() } + +// GetServerState => получает системное состояние сервера на go +func GetServerState(c *fiber.Ctx) error { + numGoroutines := runtime.NumGoroutine() + memStats := &runtime.MemStats{} + runtime.ReadMemStats(memStats) + + state := dto.ServerState{ + Gorutines: numGoroutines, + MemAllocation: memStats.Alloc, + TotalMemAllocation: memStats.TotalAlloc, + NumGC: memStats.NumGC, + } + data, err := json.Marshal(state) + if err != nil { + c.WriteString("ERROR" + err.Error()) + return err + } + c.WriteString(string(data)) + return nil +} + +// GetActiveRooms получает активные комнаты +func GetActiveRooms(c *fiber.Ctx) error { + result := dto.ActiveRoomsDTO{ + Rooms: make([]dto.ActiveRoomDTO, 0), + } + for uuid := range Rooms { + if uuid == "" { + continue + } + result.Rooms = append(result.Rooms, dto.ActiveRoomDTO{Name: fmt.Sprintf("Room %v", uuid), Uuid: uuid}) + } + res, err := json.Marshal(result) + if err != nil { + c.Status(500).WriteString(fmt.Sprintf("Error! %v", res)) + return err + } + c.Write(res) + return nil +} + +// DispatchRooms => в бесконечном цикле раз в 50 минут проверяет состояние комнат +// если активность закончилась: +// 1. Сохраняет в mongodb +// 2. Отрубает сессию +func DispatchRooms() { + ticker := time.NewTicker(50 * time.Minute) // Устанавливаем таймер с интервалом 50 минут + defer ticker.Stop() // Останавливаем таймер после завершения + + for range ticker.C { + now := time.Now().Unix() // Текущее время в формате Unix + for _, room := range Rooms { + // Проверяем, если прошло больше 20 минут с последнего обновления комнаты + if room.Ts_update+20*60 < now { + // Удаление комнаты из списка по ID + delete(Rooms, room.UUID) + fmt.Printf("Room %s is inactive for more than 20 minutes.\n", room.UUID) + } + } + } +} diff --git a/src/server/internal/server/websocket.go b/src/server/internal/server/websocket.go index bef49b4..4da1f6e 100644 --- a/src/server/internal/server/websocket.go +++ b/src/server/internal/server/websocket.go @@ -1,20 +1,200 @@ package server -// CreateGroupRequest создает группу пользователей -// @UserDTO -> трансфер пользовательских данных {id} -func CreateGroupRequest() { +import ( + "encoding/json" + "fmt" + "moxitech/dns/entity/websocket" + "moxitech/dns/package/math/simulator" + u_sorting "moxitech/dns/package/utils/sorting" + "sync" + "time" +) +var roomsMutex sync.RWMutex + +type WebsocketRoom struct { + UUID string `json:"uuid"` // Строка подключения + Connections map[int]bool `json:"connections"` // Подключения: для каждого пользователя есть свое подключение + Modulation websocket.Modulation `json:"modulation"` + EndModulation *simulator.NetworkSimulation `json:"ended_modulation"` // Последняя проведенная симуляция + Ts_start int64 `json:"ts_start"` + Ts_update int64 `json:"ts_update"` +} + +// CreateGroupRequest [@FrontendInitiator:makeSimulation] создает группу пользователей +// @UserDTO -> трансфер пользовательских данных {id} +func CreateGroupRequest(user_id int, groupHash string, mapTemplateOrTemplateName string) *WebsocketRoom { + if mapTemplateOrTemplateName != "" { + fmt.Println("TODO! загрузка шаблонов карты") + } + room := WebsocketRoom{ + UUID: groupHash, + Connections: make(map[int]bool), // Инициализируем карту Connections + Modulation: websocket.Modulation{}, + Ts_start: time.Now().Unix(), + Ts_update: time.Now().Unix(), + } + + mod := websocket.Modulation{ + Map: &websocket.MapPartial{ + Map: simulator.MakeExampleMap(), + Name: "New Map", + Changed: false, + }, + Objects: make([]*websocket.SystemObject, 0), + Ts_update: time.Now().Unix(), + } + + room.Connections[user_id] = true + room.Modulation = mod + return &room +} + +// ConnectGroupRequest [@FrontendInitiator:connectSimulation] подключается к группе пользователей +// @UserDTO -> трансфер пользовательских данных {id} +func (room *WebsocketRoom) ConnectGroupRequest(user_id int) string { + roomsMutex.Lock() + defer roomsMutex.Unlock() + // TODO: пробросить структуру Modulation + room.Connections[user_id] = true + + // TODO: localhost -> nginx provide configuration OR ip + return fmt.Sprintf("ws://localhost:8080/ws/connect?userToken=%vgroupHash=%v", user_id, room.UUID) +} + +// UpdateGroupUptime [@WebsocketInitiator:любая передача данных] обновляет uptime группы +func (room *WebsocketRoom) UpdateGroupUptime() { + + room.Ts_update = time.Now().Unix() } // FlushGroupRequest сохраняет результат вычислений группы // @GroupEntity : WS entity -> сущность группы с данными и вычислениями // @IsUserRequest : boolean -> если пользовательский запрос - сейвим с именем группы, иначе создаем произвольную запись с Username + timestamp -func FlushGroupRequest() { +func (room *WebsocketRoom) FlushGroupRequest() { } -// ObserveGroupHandler проверяет массив групп пользователей и если в группе нет активных пользователей - закрываем соединение -// Запускать в горутине после старта сервера! -func ObserveGroupHandler() { +// +// ... ДОБАВИТЬ ЛОГИКУ ИЗ WEBSOCKET ACTION ... +// + +// FlushGroupRequest сохраняет результат вычислений группы +// @GroupEntity : WS entity -> сущность группы с данными и вычислениями +// func (room *WebsocketRoom) DeleteMapObjectRequest(name string) { +// roomsMutex.Lock() +// defer roomsMutex.Unlock() +// room.Modulation.Objects.DeleteObject(name) + +// } + +// NormalizeDataForSimulation - Преобразование данных для отправки в симуляцию +func (room *WebsocketRoom) NormalizeDataForSimulation() { } + +// InitBroadCast - Инициализация раздачи контента участникам комнаты +func (room *WebsocketRoom) InitBroadCast() { + +} + +func SpawnSimulation(heightData [][]float64) { + // Пример карты высот, замените на настоящие данные + // heightData := [][]float64{ + // {0, 10, 15, 20}, + // {5, 15, 25, 30}, + // {10, 20, 35, 40}, + // {15, 25, 40, 50}, + // } + + // Определяем карту высот + mapObj := &simulator.Map{ + Name: "Example Map", + MinBound: [3]float64{-1000, -1000, 0}, + MaxBound: [3]float64{1000, 1000, 500}, + HeightData: heightData, + } + // Определяем дронов + drones := []*simulator.Drone{ + { + ID: 1, + Name: "Drone 1", + Coords: [3]float64{100, 100, 50}, + Params: simulator.DroneParams{ + AntennaRadius: 500, + AntennaDirection: [3]float64{1, 0, 0}, + Waypoints: [][3]float64{{200, 200, 50}}, + Speed: 10, + MeshName: "MeshA", + }, + }, + } + // Определяем базовые станции + baseStations := []*simulator.BaseStation{ + { + ID: 1, + Name: "BaseStation1", + Coords: [3]float64{0, 0, 0}, + Params: simulator.BaseStationParams{ + AntennaRadius: 2000, + AntennaDirection: [3]float64{1, 0, 0}, + }, + }, + } + // Создаем симуляцию + sim := &simulator.NetworkSimulation{ + Map: mapObj, + Drones: drones, + BaseStations: baseStations, + TimeStep: 2, + } + // Запуск симуляции на 300 секунд + result_sim := sim.Simulate(300) + result := u_sorting.SortMap(result_sim) + for time, state := range result { + for localstate, f := range state { + fmt.Printf("%v| %v sec :: %v\n", time, localstate, f) + } + } +} + +func (room *WebsocketRoom) WebsocketAction(message []byte) { + var Signal websocket.SignalMessage + err := json.Unmarshal(message, &Signal) + if err != nil { + fmt.Printf("[WebsocketAction] %v", err) + } + switch Signal.Signal { + case 0: + room.UpdateGroupUptime() + case 1: + case 2: + case 3: + + case 21: + case 22: + + case 30: + case 31: + case 32: + + case 100: + + case 301: + + } + + // Нахождение на странице симуляции :: 0 - просто обновляет uptime + + // Добавление объекта :: 1 - базовая станция, 2 - дрон + // Удаление объекта :: 3 + // Обновление объекта :: 21 - базовая станция, 22 - дрон + + // Инициализация базовых данных :: 30 + // Инициализация загрузки карты в ПО :: 31 + // Инициализация загрузки карты из mongoDB :: 32 + + // Запуск симуляции :: 100 + + // Пользователь исключен :: 301 +} diff --git a/src/server/package/math/simulator/simulator.go b/src/server/package/math/simulator/simulator.go index de4310d..5193069 100644 --- a/src/server/package/math/simulator/simulator.go +++ b/src/server/package/math/simulator/simulator.go @@ -48,10 +48,10 @@ type BaseStation struct { } type NetworkSimulation struct { - Map *Map - Drones []*Drone - BaseStations []*BaseStation - TimeStep int + Map *Map `json:"map"` + Drones []*Drone `json:"drones"` + BaseStations []*BaseStation `json:"base_station"` + TimeStep int `json:"time_step"` } // Simulate запускает симуляцию и возвращает состояние сети на каждую секунду @@ -241,3 +241,21 @@ func CalculateDataRate(modulation string, bandwidth float64) float64 { return spectralEfficiency * bandwidth // скорость передачи данных в Mbps } + +func MakeExampleMap() Map { + heightData := [][]float64{ + {0, 10, 15, 20}, + {5, 15, 25, 30}, + {10, 20, 35, 40}, + {15, 25, 40, 50}, + } + + // Определяем карту высот + mapObj := Map{ + Name: "ExampleServerMap", + MinBound: [3]float64{-1000, -1000, 0}, + MaxBound: [3]float64{1000, 1000, 500}, + HeightData: heightData, + } + return mapObj +} diff --git a/src/server/package/parser/json/json.go b/src/server/package/parser/json/json.go new file mode 100644 index 0000000..360fc7e --- /dev/null +++ b/src/server/package/parser/json/json.go @@ -0,0 +1,77 @@ +package json + +import ( + "encoding/json" + "moxitech/dns/package/math/simulator" +) + +func Loader() { + +} + +// Dumper - генерик, задача которого вернуть json строку любого объекта +func Dumper[T any](x any) (string, error) { + data, err := json.Marshal(x) + return string(data), err +} + +// T_CreateApproximateJsonStruct -> функция которая вернет JSON строку которая приблизительно получиться в layer 1 программы (в бекэнде) +func T_CreateApproximateJsonStruct_l1() string { + heightData := [][]float64{ + {0, 10, 15, 20}, + {5, 15, 25, 30}, + {10, 20, 35, 40}, + {15, 25, 40, 50}, + } + mapObj := &simulator.Map{ + Name: "Example Map", + MinBound: [3]float64{-1000, -1000, 0}, + MaxBound: [3]float64{1000, 1000, 500}, + HeightData: heightData, + } + drones := []*simulator.Drone{ + { + ID: 1, + Name: "Drone 1", + Coords: [3]float64{100, 100, 50}, + Params: simulator.DroneParams{ + AntennaRadius: 500, + AntennaDirection: [3]float64{1, 0, 0}, + Waypoints: [][3]float64{{200, 200, 50}}, + Speed: 10, + MeshName: "MeshA", + }, + }, + } + baseStations := []*simulator.BaseStation{ + { + ID: 1, + Name: "BaseStation 1", + Coords: [3]float64{0, 0, 0}, + Params: simulator.BaseStationParams{ + AntennaRadius: 2000, + AntennaDirection: [3]float64{1, 0, 0}, + }, + }, + { + ID: 2, + Name: "BaseStation 2", + Coords: [3]float64{100, 50, 30}, + Params: simulator.BaseStationParams{ + AntennaRadius: 100, + AntennaDirection: [3]float64{1, 1, 0}, + }, + }, + } + sim := &simulator.NetworkSimulation{ + Map: mapObj, + Drones: drones, + BaseStations: baseStations, + TimeStep: 2, + } + data, err := json.Marshal(sim) + if err != nil { + panic(err) + } + return string(data) +} diff --git a/src/server/package/utils/randomizer/randomizer.go b/src/server/package/utils/randomizer/randomizer.go new file mode 100644 index 0000000..f76d8d7 --- /dev/null +++ b/src/server/package/utils/randomizer/randomizer.go @@ -0,0 +1,21 @@ +package randomizer + +import ( + "math/rand" + "strings" +) + +func GenerateRandomString() string { + // Создание строки символов, которые могут быть использованы для генерации случайной строки + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + // Генерация случайной строки длиной 10 символов + result := make([]byte, 10) + for i := range result { + result[i] = chars[rand.Intn(len(chars))] + } + + // Преобразование среза в строку и возвращение результата + return strings.TrimSpace(string(result)) + +}