Структура бэкенда
Структура проекта
Section titled “Структура проекта”backend/├── cmd/│ └── server/│ └── main.go # Точка входа: конфиг, DI, запуск│├── internal/│ ├── config/│ │ └── config.go # Viper: читает ENV + config.yaml│ ││ ├── domain/ # Бизнес-логика (чистая, без зависимостей)│ │ ├── user/│ │ │ ├── model.go # User struct│ │ │ ├── repository.go # Interface репозитория│ │ │ └── service.go # Бизнес-правила│ │ ├── subscription/│ │ ├── node/│ │ ├── plan/│ │ └── traffic/│ ││ ├── repository/ # Реализации репозиториев (GORM/pgx)│ │ ├── postgres/│ │ │ ├── user.go│ │ │ ├── subscription.go│ │ │ ├── node.go│ │ │ └── traffic.go│ │ └── redis/│ │ └── session.go│ ││ ├── handler/ # HTTP handlers (Fiber)│ │ ├── middleware/│ │ │ ├── auth.go # JWT проверка│ │ │ ├── admin.go # Проверка роли│ │ │ └── ratelimit.go│ │ ├── auth.go│ │ ├── user.go│ │ ├── subscription.go│ │ ├── node.go│ │ ├── plan.go│ │ ├── stats.go│ │ ├── sub_link.go # GET /sub/:token│ │ └── ws.go # WebSocket метрики│ ││ ├── router/│ │ └── router.go # Все маршруты + middleware│ ││ ├── worker/ # Asynq задачи│ │ ├── client.go # Enqueue задач│ │ ├── server.go # Worker сервер│ │ ├── sync_user.go # Синхронизация юзера на ноды│ │ ├── sync_node.go # Синхронизация всех юзеров на ноду│ │ └── check_expired.go # Крон: проверка истёкших подписок│ ││ ├── xray/│ │ ├── client.go # gRPC клиент для агента на ноде│ │ ├── sync.go # Логика добавления/удаления клиента│ │ └── stats.go # Получение статистики трафика│ ││ └── platform/│ ├── database/│ │ └── postgres.go # Инициализация пула соединений│ ├── cache/│ │ └── redis.go│ └── logger/│ └── zap.go│├── migrations/ # SQL-файлы (Goose, embedded в бинарник)│ ├── 00001_init.sql│ ├── 00002_subscriptions.sql│ └── ... # Goose-формат: -- +goose Up / -- +goose Down│├── proto/ # .proto файлы для gRPC│ └── node_agent/│ └── agent.proto│├── config.yaml # Дефолтный конфиг├── docker-compose.yml├── Dockerfile└── go.modAPI маршруты
Section titled “API маршруты”POST /api/v1/auth/registerPOST /api/v1/auth/loginPOST /api/v1/auth/refreshDELETE /api/v1/auth/logoutПользователь (auth required)
Section titled “Пользователь (auth required)”GET /api/v1/mePATCH /api/v1/meGET /api/v1/me/subscriptionGET /api/v1/me/subscription/linkPOST /api/v1/me/subscription/link # Создать новую ссылкуGET /api/v1/me/traffic # Статистика трафикаSubscription link (публичный)
Section titled “Subscription link (публичный)”GET /sub/:token # Отдаёт конфиг для VPN-клиентовАдмин (admin role required)
Section titled “Админ (admin role required)”# ПользователиGET /api/v1/admin/usersGET /api/v1/admin/users/:idPATCH /api/v1/admin/users/:idDELETE /api/v1/admin/users/:idPOST /api/v1/admin/users/:id/ban
# ПодпискиPOST /api/v1/admin/users/:id/subscriptionPATCH /api/v1/admin/subscriptions/:id
# НодыGET /api/v1/admin/nodesPOST /api/v1/admin/nodesGET /api/v1/admin/nodes/:idPATCH /api/v1/admin/nodes/:idDELETE /api/v1/admin/nodes/:idPOST /api/v1/admin/nodes/:id/sync # Ресинх всех юзеров на ноду
# ТарифыGET /api/v1/admin/plansPOST /api/v1/admin/plansPATCH /api/v1/admin/plans/:id
# СтатистикаGET /api/v1/admin/stats/overview # Всего юзеров, трафик, активные подпискиGET /api/v1/admin/stats/traffic # Трафик по нодам за период
# WebSocketWS /api/v1/admin/ws/stats # Realtime метрики нодЗависимости (go.mod ключевые)
Section titled “Зависимости (go.mod ключевые)”github.com/gofiber/fiber/v3github.com/golang-jwt/jwt/v5github.com/jackc/pgx/v5gorm.io/gormgorm.io/driver/postgresgithub.com/redis/go-redis/v9github.com/hibiken/asynqgoogle.golang.org/grpcgithub.com/spf13/vipergo.uber.org/zapgithub.com/pressly/goose/v3github.com/go-playground/validator/v10golang.org/x/crypto # bcryptМиграции (Goose + embed)
Section titled “Миграции (Goose + embed)”Миграции компилируются прямо в бинарник — не нужно тащить SQL-файлы рядом с бинарником в продакшне.
//go:embed ../../../migrations/*.sqlvar embedMigrations embed.FS
func RunMigrations(db *sql.DB) error { goose.SetBaseFS(embedMigrations) goose.SetDialect("postgres") return goose.Up(db, "migrations")}Формат файла миграции:
-- migrations/00001_init.sql
-- +goose UpCREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, ...);
-- +goose DownDROP TABLE users;Запуск при старте сервера — до того как принимать HTTP-запросы:
db := database.Connect(cfg.Database.URL)if err := database.RunMigrations(db); err != nil { log.Fatal("migration failed", zap.Error(err))}Конфигурация (config.yaml)
Section titled “Конфигурация (config.yaml)”server: port: 8080 jwt_secret: "" access_token_ttl: 15m refresh_token_ttl: 720h
database: url: "postgres://user:pass@localhost:5432/astral" max_connections: 25
redis: url: "redis://localhost:6379"
xray: sync_timeout: 10s
cors: allowed_origins: - "https://your-domain.com"Обработка ошибок
Section titled “Обработка ошибок”Все ошибки возвращаются в едином формате:
{ "code": "SUBSCRIPTION_EXPIRED", "message": "Срок подписки истёк", "details": {}}Fiber глобальный error handler конвертирует доменные ошибки в HTTP-коды.