Compare commits
1 Commits
0e2e02622d
...
42f2b68848
| Author | SHA1 | Date | |
|---|---|---|---|
| 42f2b68848 |
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -18,8 +19,12 @@ func main() {
|
|||||||
db := database.Connect()
|
db := database.Connect()
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
// --- Собираем наши зависимости (Dependency Injection) ---
|
|
||||||
userRepo := &database.UserRepository{DB: db}
|
userRepo := &database.UserRepository{DB: db}
|
||||||
|
serverRepo := &database.ServerRepository{DB: db}
|
||||||
|
serverPoller := &core.ServerPoller{Repo: serverRepo}
|
||||||
|
|
||||||
|
// Запускаем поллер в фоновой горутине
|
||||||
|
go serverPoller.Start(context.Background())
|
||||||
|
|
||||||
// Сервисы
|
// Сервисы
|
||||||
userService := &core.UserService{Repo: userRepo}
|
userService := &core.UserService{Repo: userRepo}
|
||||||
@@ -39,9 +44,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
// Хендлеры
|
// Хендлеры
|
||||||
userHandler := &api.UserHandler{Service: userService}
|
userHandler := &api.UserHandler{Service: userService}
|
||||||
authHandler := &api.AuthHandler{Service: authService} // Новый хендлер
|
authHandler := &api.AuthHandler{Service: authService}
|
||||||
profileHandler := &api.ProfileHandler{Service: profileService} // Новый хендлер
|
profileHandler := &api.ProfileHandler{Service: profileService}
|
||||||
|
serverHandler := &api.ServerHandler{Repo: serverRepo}
|
||||||
// --- Настраиваем роутер ---
|
// --- Настраиваем роутер ---
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(middleware.Logger)
|
r.Use(middleware.Logger)
|
||||||
@@ -51,7 +56,7 @@ func main() {
|
|||||||
r.Route("/api", func(r chi.Router) {
|
r.Route("/api", func(r chi.Router) {
|
||||||
r.Post("/register", userHandler.Register)
|
r.Post("/register", userHandler.Register)
|
||||||
r.Post("/login", authHandler.Login)
|
r.Post("/login", authHandler.Login)
|
||||||
// Здесь будет публичный эндпоинт для логина в веб-интерфейс
|
r.Get("/servers", serverHandler.GetServers)
|
||||||
})
|
})
|
||||||
r.Route("/authserver", func(r chi.Router) {
|
r.Route("/authserver", func(r chi.Router) {
|
||||||
r.Post("/authenticate", authHandler.Authenticate)
|
r.Post("/authenticate", authHandler.Authenticate)
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module gitea.mrixs.me/minecraft-platform/backend
|
|||||||
go 1.24.1
|
go 1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Tnze/go-mc v1.20.2 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
|
||||||
|
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
|||||||
22
internal/api/server_handler.go
Normal file
22
internal/api/server_handler.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.mrixs.me/minecraft-platform/backend/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerHandler struct {
|
||||||
|
Repo *database.ServerRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ServerHandler) GetServers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
servers, err := h.Repo.GetAllWithStatus(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get servers", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(servers)
|
||||||
|
}
|
||||||
88
internal/core/server_poller.go
Normal file
88
internal/core/server_poller.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.mrixs.me/minecraft-platform/backend/internal/database"
|
||||||
|
"gitea.mrixs.me/minecraft-platform/backend/internal/models"
|
||||||
|
"github.com/Tnze/go-mc/bot/basic"
|
||||||
|
"github.com/Tnze/go-mc/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerPoller struct {
|
||||||
|
Repo *database.ServerRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ServerPoller) Start(ctx context.Context) {
|
||||||
|
log.Println("Starting server poller...")
|
||||||
|
ticker := time.NewTicker(60 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
p.pollAllServers(ctx)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
p.pollAllServers(ctx)
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Println("Stopping server poller...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ServerPoller) pollAllServers(ctx context.Context) {
|
||||||
|
servers, err := p.Repo.GetAllEnabledServers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Poller: failed to get servers: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range servers {
|
||||||
|
go p.pollServer(ctx, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ServerPoller) pollServer(ctx context.Context, server *models.GameServer) {
|
||||||
|
resp, delay, err := net.PingAndListTimeout(server.Address, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Poller: failed to ping %s (%s): %v", server.Name, server.Address, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var status basic.ServerList
|
||||||
|
if err := json.Unmarshal(resp, &status); err != nil {
|
||||||
|
log.Printf("Poller: failed to unmarshal status for %s: %v", server.Name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MOTD может быть сложным объектом, извлекаем текст
|
||||||
|
var motdText string
|
||||||
|
if s, ok := status.Description.(string); ok {
|
||||||
|
motdText = s
|
||||||
|
} else {
|
||||||
|
if m, ok := status.Description.(map[string]interface{}); ok {
|
||||||
|
if t, ok := m["text"].(string); ok {
|
||||||
|
motdText = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData := &models.ServerStatus{
|
||||||
|
StatusJSON: string(resp),
|
||||||
|
Motd: motdText,
|
||||||
|
PlayerCount: status.Players.Online,
|
||||||
|
MaxPlayers: status.Players.Max,
|
||||||
|
VersionName: status.Version.Name,
|
||||||
|
Ping: delay.Milliseconds(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Repo.UpdateServerStatus(ctx, server.ID, updateData); err != nil {
|
||||||
|
log.Printf("Poller: failed to update status for %s: %v", server.Name, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Poller: successfully polled %s", server.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
68
internal/database/server_repository.go
Normal file
68
internal/database/server_repository.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gitea.mrixs.me/minecraft-platform/backend/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerRepository struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllEnabledServers возвращает все активные серверы для опроса.
|
||||||
|
func (r *ServerRepository) GetAllEnabledServers(ctx context.Context) ([]*models.GameServer, error) {
|
||||||
|
rows, err := r.DB.QueryContext(ctx, "SELECT id, name, address FROM game_servers WHERE is_enabled = TRUE")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var servers []*models.GameServer
|
||||||
|
for rows.Next() {
|
||||||
|
s := &models.GameServer{}
|
||||||
|
if err := rows.Scan(&s.ID, &s.Name, &s.Address); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
servers = append(servers, s)
|
||||||
|
}
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateServerStatus обновляет данные о статусе сервера.
|
||||||
|
func (r *ServerRepository) UpdateServerStatus(ctx context.Context, id int, status *models.ServerStatus) error {
|
||||||
|
query := `
|
||||||
|
UPDATE game_servers SET
|
||||||
|
status_json = $1, last_polled_at = NOW(), motd = $2, player_count = $3,
|
||||||
|
max_players = $4, version_name = $5, ping_backend_server = $6
|
||||||
|
WHERE id = $7`
|
||||||
|
_, err := r.DB.ExecContext(ctx, query,
|
||||||
|
status.StatusJSON, status.Motd, status.PlayerCount, status.MaxPlayers,
|
||||||
|
status.VersionName, status.Ping, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllWithStatus возвращает все активные серверы с их текущим статусом.
|
||||||
|
func (r *ServerRepository) GetAllWithStatus(ctx context.Context) ([]*models.GameServer, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, name, address, is_enabled, last_polled_at, motd,
|
||||||
|
player_count, max_players, version_name, ping_backend_server
|
||||||
|
FROM game_servers WHERE is_enabled = TRUE ORDER BY name`
|
||||||
|
rows, err := r.DB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var servers []*models.GameServer
|
||||||
|
for rows.Next() {
|
||||||
|
s := &models.GameServer{}
|
||||||
|
if err := rows.Scan(&s.ID, &s.Name, &s.Address, &s.IsEnabled, &s.LastPolledAt,
|
||||||
|
&s.Motd, &s.PlayerCount, &s.MaxPlayers, &s.VersionName, &s.PingBackendServer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
servers = append(servers, s)
|
||||||
|
}
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
26
internal/models/server.go
Normal file
26
internal/models/server.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type GameServer struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
IsEnabled bool `json:"is_enabled"`
|
||||||
|
StatusJSON *string `json:"-"`
|
||||||
|
LastPolledAt *time.Time `json:"last_polled_at"`
|
||||||
|
Motd *string `json:"motd"`
|
||||||
|
PlayerCount *int `json:"player_count"`
|
||||||
|
MaxPlayers *int `json:"max_players"`
|
||||||
|
VersionName *string `json:"version_name"`
|
||||||
|
PingBackendServer *int `json:"ping_proxy_server"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerStatus struct {
|
||||||
|
StatusJSON string
|
||||||
|
Motd string
|
||||||
|
PlayerCount int
|
||||||
|
MaxPlayers int
|
||||||
|
VersionName string
|
||||||
|
Ping int64
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user