golang

Beego в Golang для начинающих

  • суббота, 6 апреля 2024 г. в 00:00:14
https://habr.com/ru/companies/otus/articles/802333/

Привет, Хабр!

Beego – это фреймворк для разработки веб-приложений на языке Go, ориентированный на быстрое развертывание и простоту использования. В его основе лежит идея создания полнофункциональных приложений с минимум усилиям на настройку и кодирование. Это достигается за счет широкого выбора инструментов, включая ORM, систему маршрутизации, интерфейс кмд и многое другое. Beego придерживается принципов RESTful и MVC.

Установим и создадим первый проект

Установить Beego проще пареной репы:

go get -u github.com/beego/beego/v2

Команда загрузит Beego и добавит его в рабочую директорию GOPATH.

Также потребуется установить Bee от создателей Beego, который является инструментом командной строки:

go get -u github.com/beego/bee/v2

После установки Beego и Bee можно уже приступать к работе.

В терминале переходим в директорию, где хочется разместить новый проект, и выполняем команду:

bee new <имя_проекта>

После создания проекта, в директории проекта можно увидеть стандартную структуру каталогов Beego:

controllers — для файлов контроллеров

models — для моделей данных

routers — для настройки маршрутов

views — для шаблонов представлений

main.go — точка входа в приложение

Запуск выглядит достаточно просто:

bee run

Так запустим веб-сервер на localhost с портом по умолчанию 8080 и можно увидеть результаты работы приложения, перейдя по адресу http://localhost:8080 в браузере.

Работа с роутером

Маршрутизация в Beego определяется в файле routers/router.go. В этом файле можно указать какие контроллеры должны обрабатывать различные URL-пути. Beego поддерживает статическую и параметризованную маршрутизацию, а также группировку маршрутов. Например, пример статической маршрутизации:

package routers

import (
    "myapp/controllers"
    "github.com/beego/beego/v2/server/web"
)

func init() {
    web.Router("/", &controllers.MainController{})
}

Для обработки запросов к корню сайта / используется MainController.

Пример параметризованной маршрутизации:

web.Router("/user/:id", &controllers.UserController{})

Для обработки запросов к /user/123, где 123 — это динамический параметр id, используется UserController.

Контроллеры в Beego наследуются от beego.Controller и содержат методы, соответствующие HTTP-методам Get, Post, Delete и т.д., которые вызываются при обращении к связанным с ними маршрутам. Пример контроллера:

package controllers

import "github.com/beego/beego/v2/server/web"

type MainController struct {
    web.Controller
}

func (c *MainController) Get() {
    c.Data["Website"] = "Beego.me"
    c.Data["Email"] = "contact@beego.me"
    c.TplName = "index.tpl"
}

MainController обрабатывает GET-запросы. Данные, передаваемые в представление index.tpl, устанавливаются через c.Data.

Beego позволяет настраивать маршруты гибче, используя функцииweb.NSRouter, web.Include, и web.NSNamespace для группировки маршрутов и в целом более удобной организации приложений.

Пример группировки маршрутов:

ns := web.NewNamespace("/v1",
    web.NSRouter("/user", &controllers.UserController{}, "get:ListUsers"),
    web.NSRouter("/user/:id", &controllers.UserController{}, "get:GetUser"),
)

web.AddNamespace(ns)

Создали пространство имен /v1, в котором группируются маршруты, относящиеся к пользователям.

Также существуют перехватчики, которые позволяют выполнять код до или после определенных контроллеров:

web.InsertFilter("/user/*", web.BeforeExec, InterceptorFunc)

InterceptorFunc будет вызываться перед каждым выполнением методов контроллера, обрабатывающего пути, соответствующие шаблону /user/*.

Также есть механизм авто-роутинга, который автоматически связывает URL-адреса с методами контроллеров на основе их имен:

web.AutoRouter(&controllers.YourController{})

Так Beego будет автоматически маршрутизировать запросы типа /yourcontroller/methodname к соответствующим методам в YourController.

Для решения более специфических задач, связанных с маршрутизацией, Beego позволяет настраивать роутер с помощью регулярных выражений и условий:

web.Router("/user/:id([0-9]+)", &controllers.UserController{})

Запросы к /user/123 будут обрабатываться UserController, при этом :id должен соответствовать регулярному выражению [0-9]+, что означает одну или более цифр.

Beego суперски подходит для создания RESTful API благодаря своей системе маршрутизации. Можно определить маршруты для каждого из HTTP-методов (GET, POST, PUT, DELETE и т.д.) и связать их с соответствующими методами в контроллерах:

web.Router("/api/user", &controllers.UserController{}, "get:GetUsers;post:CreateUser")
web.Router("/api/user/:id", &controllers.UserController{}, "get:GetUser;put:UpdateUser;delete:DeleteUser")

Используя перехватчики, авто-роутинг, кастомные пути с регулярными выражениями и поддержку RESTful URL, можно идеально управлять потоками.

Работа с БД

ORM Beego позволяет отображать таблицы БД на структуры Go, превращая строки таблиц в объекты Go.

Подключение к БД на примере MySQL

Установим:

go get -u github.com/go-sql-driver/mysql

В файле конфигурации приложения Beego conf/app.conf указываем параметры подключения к MySQL:

[database]
dbDriver = mysql
dbUser = ваш_пользователь
dbPass = ваш_пароль
dbName = имя_базы_данных
dbHost = 127.0.0.1
dbPort = 3306

В приложении (например, в main.go) юзаем информацию из конфига для инициализации подключения к БД:

import (
    "github.com/beego/beego/v2/client/orm"
    _ "github.com/go-sql-driver/mysql"
)

func init() {
    orm.RegisterDriver("mysql", orm.DRMySQL)
    // строка подключения: пользователь:пароль@tcp(хост:порт)/имя_базы_данных
    orm.RegisterDataBase("default", "mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
}

Прочие БД подключаются аналогично.

Определение моделей

Для взаимодействия с БД понадобится определить модели. В Beego модели — это структуры Go, которые отражают структуру таблиц в базе данных. Пример простой модели пользователя:

type User struct {
    Id       int
    Name     string
    Password string
}

Чтобы использовать эту модель в Beego ORM её нужно зарегистрировать в системе ORM. Это делается так:

func init() {
    orm.RegisterModel(new(User))
}

После настройки подключения к БД и определения моделей можно приступить к операциям с данными.

Чтобы создать новую запись в базе данных, используем Insert объекта ORM:

o := orm.NewOrm()
user := User{Name: "ivan", Password: "securepassword"}

id, err := o.Insert(&user)
if err != nil {
    // обработка ошибки
}

Для чтения есть метод Read:

o := orm.NewOrm()
user := User{Id: 1}

err := o.Read(&user)
if err == orm.ErrNoRows {
    // зЗапись не найдена
} else if err == nil {
    // запись найдена, `user` заполнен данными
}

Для обновления записи апдейт:

o := orm.NewOrm()
user := User{Id: 1, Name: "ivan Updated"}

_, err := o.Update(&user, "Name")
if err != nil {
    // обработка ошибки
}

Для удаления записи Delete:

o := orm.NewOrm()
user := User{Id: 1}

_, err := o.Delete(&user)
if err != nil {
    // обработка ошибки
}

Также есть транзакции, составные запросы, ленивая загрузка и т.д. Например, для выполнения транзакции:

o := orm.NewOrm()
err := o.Begin()

user := User{Name: "Ivan Transaction", Password: "transpassword"}
_, err = o.Insert(&user)
if err != nil {
    o.Rollback()
} else {
    o.Commit()
}

Также Beego ORM поддерживает работу с запросами через QueryBuilder:

var users []User
qb, _ := orm.NewQueryBuilder("mysql")
qb.Select("id", "name").From("user").Where("id > ?").OrderBy("id").Desc().Limit(10)
orm.NewOrm().Raw(qb.String(), 1).QueryRows(&users)

Создание пользовательского интерфейса

Beego использует систему шаблонов Go html/template для динамической генерации HTML. Шаблоны позволяют разделять логику приложения и его представление.

Шаблоны обычно хранятся в каталоге views и имеют расширение .tpl или .html. В шаблонах можно использовать конструкции Go Template для вставки данных, выполнения условий и циклов:

<!-- views/index.tpl -->
<html>
<body>
    <h1>{{.Title}}</h1>
    {{range .Items}}
        <div>{{ . }}</div>
    {{else}}
        <div>No items found.</div>
    {{end}}
</body>
</html>

В контроллерах Beego можно передавать данные в шаблон, используя мапу Data.

func (this *MainController) Get() {
    this.Data["Title"] = "My Page Title"
    this.Data["Items"] = []string{"Item 1", "Item 2", "Item 3"}
    this.TplName = "index.tpl"
}

Beego автоматически компилирует шаблоны и рендерит их при вызове метода ServeHTTP. Имя шаблона указывается в свойстве TplName контроллера.

Также есть возможность локализации с помощью поддержки i18n в пакете github.com/beego/beego/v2/server/web/i18n.

Файлы локализации хранятся в формате INI или JSON и содержат пары ключ-значение для переводов. По дефолту они размещаются в каталоге conf/locale:

// conf/locale/en-US.ini
hello = "Hello"

// conf/locale/ru-RU.ini
hello = "Привет"

В main.go или в начальной функции приложения можно инициализровать i18n и указать директорию с файлами локализации:

import "github.com/beego/beego/v2/server/web"

func init() {
    web.SetStaticPath("/static", "static")
    web.AddFuncMap("i18n", web.I18n)
    web.LoadAppConfig("ini", "conf/app.conf")
    if err := web.LoadI18n("conf/locale"); err != nil {
        log.Fatal("Failed to load i18n files:", err)
    }
}

Для использования локализации в шаблонах можно юзать функцию i18n.Tr.

<body>
    <h1>{{i18n.Tr "en-US" "hello"}}</h1>
</body>

И в контроллерах можно использовать ту же функцию для получения локализованных строк программно:

codefunc (this *MainController) Get() {
    locale := "ru-RU" // Обычно это значение извлекается из настроек пользователя или заголовков запроса
    greeting := web.I18n(locale, "hello")
    this.Data["Greeting"] = greeting
    this.TplName = "index.tpl"
}

Язык для пользователя обычно определяется автоматически на основе заголовков HTTP-запроса, но его также можно задать явно, например, в зависимости от выбора пользователя на сайте.

Тестирование

Тесты в Beego по дефолту размещаются в папке tests проекта.

Допустим, у нас есть контроллер MainController с методом Get, который возвращает простое сообщение. Пример теста для этого метода:

Определение контроллера:

// controllers/main.go
package controllers

import (
    "github.com/beego/beego/v2/server/web"
)

type MainController struct {
    web.Controller
}

func (c *MainController) Get() {
    c.Ctx.WriteString("Hello, Beego!")
}

Создаем файл теста в папке tests, например, main_test.go:

// tests/main_test.go
package test

import (
    "net/http"
    "net/http/httptest"
    "testing"
    "github.com/beego/beego/v2/server/web"
    "path/filepath"
    "runtime"
    _ "yourapp/routers"
    
    "github.com/stretchr/testify/assert"
)

func init() {
    _, file, _, _ := runtime.Caller(0)
    apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator))))
    web.TestBeegoInit(apppath)
}

func TestMainPage(t *testing.T) {
    r, _ := http.NewRequest("GET", "/", nil)
    w := httptest.NewRecorder()
    web.BeeApp.Handlers.ServeHTTP(w, r)

    assert.Equal(t, 200, w.Code)
    assert.Equal(t, "Hello, Beego!", w.Body.String())
}

Юзаем пакет assert библиотеки testify для проверки, что ответ от сервера соответствует ожиданиям. Создаем HTTP-запрос к главной странице и проверяем, что статус-код ответа равен 200 и тело ответа содержит "Hello, Beego!".

Тесты в Beego, как и в любом Go-приложении, можно запустить с помощью команды go test:

go test ./...

Команда рекурсивно найдет и выполнит все тесты в вашем проекте Beego.

А вот для тестирования API можно использовать пакет httptest. Предположим, есть API для получения информации о пользователях.

Пример контроллера API:

// controllers/user.go
package controllers

import (
    "encoding/json"
    "github.com/beego/beego/v2/server/web"
)

type UserController struct {
    web.Controller
}

func (c *UserController) GetUser() {
    userId := c.Ctx.Input.Param(":id")
    user := User{Id: userId, Name: "Test User"}

    c.Data["json"] = &user
    c.ServeJSON()
}

type User struct {
    Id   string `json:"id"`
    Name string `json:"name"`
}

Пример теста API:

// tests/api_test.go
package test

import (
    "bytes"
    "net/http"
    "net/http/httptest"
    "testing"
    _ "yourapp/routers"

    "github.com/stretchr/testify/assert"
)

func TestGetUser(t *testing.T) {
    req, err := http.NewRequest("GET", "/user/1", nil)
    if err != nil {
        t.Fatal(err)
    }

    rr := httptest.NewRecorder()
    web.BeeApp.Handlers.ServeHTTP(rr, req)

    assert.Equal(t, http.StatusOK, rr.Code)

    expected := `{"id":"1","name":"Test User"}`
    assert.Equal(t, expected, strings.TrimSpace(rr.Body.String()))
}

Создаем HTTP GET запрос к нашему API /user/1 и проверяем, что статус код ответа 200 OK и тело ответа соответствует ожидаемому JSON-объекту.

При тестировании компонентов, взаимодействующих с БД, нужно изолировать тесты от реальной БД. Это можно сделать с помощью мокинга или настройки тестовой БД:

func init() {
    orm.RegisterDataBase("default", "sqlite3", "file::memory:?mode=memory&cache=shared", 30)
}

Юзаем SQLite в памяти для создания изолированной тестовой среды.

func TestCreateUser(t *testing.T) {
    o := orm.NewOrm()
    o.Using("default") // 

    user := &User{Name: "New User"}
    id, err := o.Insert(user)
    if err != nil {
        t.Errorf("Не удалось создать пользователя: %v", err)
    }

    assert.NotEqual(t, 0, id)
}

Вставляем нового пользователя в тестовую БД и проверяем, что операция вставки прошла успешно, а возвращаемый идентификатор пользователя не равен нулю.

Прочие возможности

Работа с сессиями и куками

В Beego сессии активируются в файле конфигурации app.conf с использованием параметров sessionon, sessionprovider, и sessionname. Пример конфигурации для активации сессий:

sessionon = true
sessionprovider = "memory"
sessionname = "beegosessionID"

После активации, сессии можно использовать прямо в контроллерах:

func (c *MainController) AnyMethodName() {
    // установка значения сессии
    c.SetSession("key", "value")
    
    // получение значения сессии
    value := c.GetSession("key")
    
    // удаление значения сессии
    c.DelSession("key")
    
    // уничтожение сессии
    c.DestroySession()
}

Работа с куками также предельно упрощена:

func (c *MainController) AnyMethodName() {
    // установка куки
    c.Ctx.SetCookie("name", "value", expire)

    // чтение куки
    value := c.Ctx.GetCookie("name")
}

Логирование

Настройка:

logs.SetLogger(logs.AdapterFile, `{"filename":"log/myapp.log"}`)
logs.SetLevel(logs.LevelInfo)

Настраивает логирование в файл с определенным уровнем логирования.

Beego позволяет определять спец. методы обработки ошибок в контроллерах:

func (c *MainController) Error404() {
    c.Data["content"] = "Page not found"
    c.TplName = "404.tpl"
}

Автоматическая генерация документации API

Beego интегрирован с Swagger, позволяя автоматически генерировать документацию для API. Для этого необходимо использовать комментарии к коду и инструмент bee для генерации документации:

// @Title Get User
// @Description get user by uid
// @Param    uid     path    int    true        "The key for staticblock"
// @Success 200 {object} User
// @Failure 403 :uid is empty
// @router /:uid [get]
func (u *UserController) Get() {
    // реализация метода
}

Используя команду bee run -gendoc=true -downdoc=true, можно сгенерировать документацию Swagger, которая будет доступна по адресу /swagger приложения.


Beego упрощает стандартные задачи в создание веб-приложений.

Больше про языки программирования эксперты OTUS рассказывают в рамках практических онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке.