Добро пожаловать во вторую часть этой серии, где мы познакомимся с платформой веб-разработки Beego для Go. Если вы пропустили первую часть, я призываю вас прочитать ее, поскольку она закладывает основу для этой серии.
В первой части мы хорошо познакомились с Beego и начали работать с ним, установив Beego и инструмент командной строки Bee, создав базовый проект, добавив действие контроллера, создав шаблон базового вида, добавив собственный маршрут и изучив, как работать с параметрами запроса.
Во второй части мы рассмотрим более интересные аспекты создания веб-приложения путем интеграции базы данных, в частности с SQLite3, а также рассмотрим модели , формы и валидацию . Я надеюсь, что вы готовы к работе, так как это будет хорошая поездка до конца.
Двухступенчатые просмотры
Во многих функциях управляющего контроллера вы увидите следующий код:
manage.Layout = "basic-layout.tpl" manage.LayoutSections = make(map[string]string) manage.LayoutSections["Header"] = "header.tpl" manage.LayoutSections["Footer"] = "footer.tpl"
Для этого нужно настроить двухэтапный макет просмотра . Если вы не знакомы с этим термином, у вас всегда есть внешний макет, такой как боковые панели , навигация , верхние и нижние колонтитулы и внутренний контент, который изменяется в зависимости от выполняемого действия.
Изображение выше иллюстрирует, что я имею в виду. Зеленые области находятся во внешнем виде оболочки, а красный — содержимое, которое изменяется в зависимости от выполняемого действия.
Ссылаясь на Layout
и LayoutSections
, мы можем указать внешний шаблон представления макета, basic-layout.tpl
и другие под-шаблоны, в нашем случае это верхний и нижний колонтитулы, доступные в header.tpl
и footer.tpl
соответственно.
При этом содержимое, сгенерированное нашим шаблоном действия, будет вставлено в шаблон представления упаковки, указав {{.LayoutContent}}
а верхний и нижний колонтитулы будут доступны в {{.Header}}
и {{.Footer}}
соответственно.
модели
Чтобы добавить поддержку базы данных, нам нужно сделать несколько вещей. Во-первых, нам нужно настроить некоторые модели. Модели в основном просто структуры с некоторой дополнительной информацией. Ниже приведен файл модели, который вы найдете в models/models.go
, который мы будем использовать в остальной части приложения.
package models type Article struct { Id int `form:"-"` Name string `form:"name,text,name:" valid:"MinSize(5);MaxSize(20)"` Client string `form:"client,text,client:"` Url string `form:"url,text,url:"` } func (a *Article) TableName() string { return "articles" }
Вы можете видеть одну модель Article
, которая моделирует очень простую статью на веб-сайте и содержит четыре свойства: Id , Name , Client и Url . Что вы заметите, так это то, что для каждого свойства есть дополнительные данные, форма упоминания и действительность
Это действительно простой способ использования модели для обработки как формирования, так и проверки формы. Давайте теперь разберем каждое из четырех свойств и объясним, что делает каждое из них.
Id int `form:"-"`
В нашей базе данных id это поле с автоинкрементом. Это прекрасно работает, так как значение должно быть создано для нас, если это новая запись, только при удалении , обновлении или поиске записи. Поэтому, указав form:"-"
, мы говорим, что идентификатор не требуется.
Name string `form:"name,text,name:" valid:"MinSize(5);MaxSize(20)"`
Здесь у нас есть немного более сложный пример, поэтому давайте разберем его, начав с "name,text,name:"
. Это означает, что когда форма анализируется, что мы вскоре увидим:
- Значение из поля формы с именем
name
будет инициализировать свойство Name - Поле будет текстовым полем
- Метка будет установлена на «имя:»
Теперь давайте посмотрим на valid:"MinSize(5);MaxSize(20)"
. Это определяет два правила проверки: MinSize
и MaxSize
. По сути, значение должно быть не менее 5 символов, но не более 20.
Существует ряд других правил проверки, которые вы можете использовать, включая Range , Email , IP , Mobile , Base64 и Phone .
Client string `form:"client,text,client:"` Url string `form:"url,text,url:"`
В последних двух примерах Client получит свое значение из поля формы client
, будет текстовым полем и будет иметь метку client:
а Url получит свое значение из поля формы url
, будет текстовым полем и будет иметь метку url:
Теперь TableName
функции TableName
.
Причина, по которой я его добавил, заключается в том, что имя таблицы статей не совпадает с именем структуры. Вместо этого это называется articles
. Если бы они были одинаковыми, то он был бы автоматически найден ORM Beego .
Тем не менее, я намеренно изменил его, так как хотел показать, что требуется, когда ваши структура и имена таблиц различаются. Теперь, когда мы говорили о схеме таблицы, я должен ее включить.
CREATE TABLE "articles" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(200) NOT NULL, "client" varchar(100), "url" varchar(400) DEFAULT NULL, "notes" text, UNIQUE (name) );
Интеграция моделей в приложение
Теперь, когда мы настроили и настроили нашу модель вместе с сопровождающей формой и информацией о проверке, нам нужно сделать ее доступной в нашем приложении. В main.go
нам нужно добавить еще три оператора import, как main.go
ниже.
"github.com/astaxie/beego/orm" _ "github.com/mattn/go-sqlite3" models "sitepointgoapp/models"
Первая импортирует библиотеку ORM Beego, вторая обеспечивает поддержку SQLite3, что необходимо, поскольку мы используем базу данных SQLite3. Третий импортирует модели, которые мы только что создали, давая им псевдоним models
.
func init() { orm.RegisterDriver("sqlite", orm.DR_Sqlite) orm.RegisterDataBase("default", "sqlite3", "database/orm_test.db") orm.RegisterModel(new(models.Article)) }
Последний шаг, который нам нужно сделать, — это зарегистрировать драйвер , базу данных и модели, которые мы будем использовать в нашем приложении. Мы делаем это с тремя утверждениями выше. Мы указываем, что используем драйвер SQLite, и устанавливаем его как соединение с базой данных по умолчанию, подключаясь к нашей тестовой базе данных, расположенной в database/orm_test.db
.
Наконец, мы регистрируем модели, которые мы собираемся использовать, в нашем случае, только models.Article
. models.Article
.
CRUD Операции
После этого у нас теперь есть поддержка базы данных, интегрированная в наше приложение. Давайте начнем с двух простых операций CRUD — удаления и обновления . Ни один из них не использует форму, так как я хотел, чтобы этот раздел был простым, уделяя больше внимания коду ORM, а не к форме и коду проверки. Мы проработаем это в действии Add
.
Удаление записи
Мы собираемся настроить действие по удалению, которое пытается удалить статью из нашей базы данных, основываясь на значении параметра id
. В routers/routers.go
добавьте следующий маршрут в функцию init:
beego.Router("/manage/delete/:id([0-9]+)", &controllers.ManageController{}, "*:Delete")
Затем добавьте следующий код в controllers/manage.go
. Давай пройдемся медленно.
func (manage *ManageController) Delete() { // convert the string value to an int articleId, _ := strconv.Atoi(manage.Ctx.Input.Param(":id"))
Здесь мы пытаемся извлечь параметр id
и преобразовать его в int из строки, используя метод strconv
пакета strconv
. Это простой пример, поэтому я пропускаю все возможные ошибки и сохраняю проанализированное значение в articleId
.
o := orm.NewOrm() o.Using("default") article := models.Article{}
поo := orm.NewOrm() o.Using("default") article := models.Article{}
Затем мы инициализируем новый экземпляр ORM и указываем, что мы используем базу данных по умолчанию . Мы могли бы установить любое количество соединений с базой данных, например, одно для чтения и одно для записи и т. Д. Наконец, мы создали новый пустой экземпляр модели Article.
// Check if the article exists first if exist := o.QueryTable(article.TableName()).Filter("Id", articleId).Exist(); exist { if num, err := o.Delete(&models.Article{Id: articleId}); err == nil { beego.Info("Record Deleted. ", num) } else { beego.Error("Record couldn't be deleted. Reason: ", err) } } else { beego.Info("Record Doesn't exist.") } }
Теперь к сердцу функции. Сначала мы запрашиваем таблицу статей, проверяя, существует ли статья со значением Id, соответствующим параметру id, или нет. Если это так, мы вызываем метод Delete
ORM, передавая новый объект Article только с установленным свойством Id
.
Если ошибки не возвращаются, то статья была удалена и beego.Info
чтобы beego.Info
что запись была удалена с использованием метода Info
. Если не удалось выполнить операцию удаления, вместо этого мы вызываем Error
, передавая объект err
, который покажет причину, по которой запись не может быть удалена.
Обновление записи
Это было удаление записи, теперь давайте обновим одну; на этот раз мы будем использовать флэш-мессенджер для большего эффекта.
func (manage *ManageController) Update() { o := orm.NewOrm() o.Using("default") flash := beego.NewFlash()
поfunc (manage *ManageController) Update() { o := orm.NewOrm() o.Using("default") flash := beego.NewFlash()
Как и прежде, мы инициализируем переменную ORM и указываем базу данных по умолчанию. Затем мы получаем указатель на объект Beego Flash, который может хранить сообщения в разных запросах.
// convert the string value to an int if articleId, err := strconv.Atoi(manage.Ctx.Input.Param(":id")); err == nil { article := models.Article{Id: articleId}
На этот раз мы пытаемся извлечь параметр id
и инициализировать новую модель Article, если она доступна.
if o.Read(&article) == nil { article.Client = "Sitepoint" article.Url = "http://www.google.com" if num, err := o.Update(&article); err == nil { flash.Notice("Record Was Updated.") flash.Store(&manage.Controller) beego.Info("Record Was Updated. ", num) }
Затем мы вызываем метод Read
, передавая объект Article, который пытается загрузить остальные свойства статьи из базы данных, если существует запись, которая соответствует идентификатору, указанному в Article.Id
.
Предполагая, что он был доступен, мы устанавливаем свойства Client
и Url
для объекта и передаем его методу Update
, который обновит запись в базе данных.
Предполагая, что ошибки не произошло, мы затем вызываем функцию Notice
для объекта Flash, передавая простое сообщение, а затем вызываем Store для сохранения информации.
} else { flash.Notice("Record Was NOT Updated.") flash.Store(&manage.Controller) beego.Error("Couldn't find article matching id: ", articleId) } } else { flash.Notice("Record Was NOT Updated.") flash.Store(&manage.Controller) beego.Error("Couldn't convert id from a string to a number. ", err) }
Если что-то пошло не так, как, например, невозможность обновить запись или мы не смогли преобразовать параметр id в целое число, мы отмечаем это во флэш-сообщении, а также в сообщении журнала.
// redirect afterwards manage.Redirect("/manage/view", 302) }
Наконец, мы вызываем метод Redirect
, передавая URL-адрес, на который мы собираемся перенаправить, и код состояния HTTP. Что происходит сейчас, так это то, что независимо от того, сможем ли мы обновить запись или нет, мы будем перенаправлены в /manage/view
, определение которой мы вскоре определим.
Просмотр всех записей
Назначение функции просмотра двоякое; во-первых, он отображает все существующие статьи в таблице статей и отображает все флэш-сообщения, которые были установлены при обновлении. Таким образом, мы знаем, было ли это действие успешным или неудачным.
func (manage *ManageController) View() { flash := beego.ReadFromRequest(&manage.Controller) if ok := flash.Data["error"]; ok != "" { // Display error messages manage.Data["errors"] = ok } if ok := flash.Data["notice"]; ok != "" { // Display error messages manage.Data["notices"] = ok }
Во-первых, мы инициализируем переменную flash
, читая запрос и ища два свойства: error
и notice
. Они соотносятся с вызовами flash.Notice
и flash.Error
. Если информация задана, мы задаем ее в свойстве Data, чтобы получить доступ к ней в шаблоне.
o := orm.NewOrm() o.Using("default") var articles []*models.Article num, err := o.QueryTable("articles").All(&articles) if err != orm.ErrNoRows && num > 0 { manage.Data["records"] = articles } }
поo := orm.NewOrm() o.Using("default") var articles []*models.Article num, err := o.QueryTable("articles").All(&articles) if err != orm.ErrNoRows && num > 0 { manage.Data["records"] = articles } }
Как и в двух последних примерах, мы затем устанавливаем соединение с базой данных по умолчанию и инициализируем часть моделей Article в articles
. Затем мы QueryTable
метод QueryTable
ORM, указав имя таблицы, а затем вызываем All
для этого, передавая фрагмент статей, который будет загружен с результатами, если они будут доступны.
Предполагая, что ничего не пошло не так, у нас есть доступные записи, и мы храним их в переменной шаблона, records.
Вставка записи
Теперь давайте посмотрим на вставку записи в действие Add, которое охватывает формы и проверку в дополнение к взаимодействию ORM.
func (manage *ManageController) Add() { o := orm.NewOrm() o.Using("default") article := models.Article{}
поfunc (manage *ManageController) Add() { o := orm.NewOrm() o.Using("default") article := models.Article{}
Я пропущу это, поскольку мы уже рассмотрели это ранее.
if err := manage.ParseForm(&article); err != nil { beego.Error("Couldn't parse the form. Reason: ", err) } else { manage.Data["Articles"] = article
Здесь мы вызываем метод ParseForm
, передавая объект article. Предполагая, что ошибка не возникла, мы устанавливаем article как переменную шаблона, которая поможет нам визуализировать форму, как мы вскоре увидим.
if manage.Ctx.Input.Method() == "POST" { valid := validation.Validation{} isValid, _ := valid.Valid(article) if !isValid { manage.Data["Errors"] = valid.ErrorsMap beego.Error("Form didn't validate.") } else {
Здесь мы проверяем, использовался ли метод POST. Если это так, то мы создаем экземпляр нового объекта Validation и передаем объект article методу Valid, чтобы проверить, действительны ли данные POST, в соответствии с правилами модели.
Если предоставленные данные недействительны, мы сохраняем любые ошибки проверки, доступные в valid.ErrorsMap
, в переменной шаблона Errors
а также регистрируем, что проверка valid.ErrorsMap
неудачно. В противном случае мы пытаемся вставить статью. Если была или не было ошибки, мы регистрируем это.
id, err := o.Insert(&article) if err == nil { msg := fmt.Sprintf("Article inserted with id:", id) beego.Debug(msg) } else { msg := fmt.Sprintf("Couldn't insert new article. Reason: ", err) beego.Debug(msg) } } } }
Завершение
Мы в конце прохождения Beego. Поскольку в платформе так много функций, просто не хватает места, чтобы уместить все в серию из двух частей.
Более того, некоторые примеры в сегодняшнем уроке могут показаться немного странными. Причиной для этого была не хорошая практика кодирования, а выделение функциональности в полуреальном мире. Если вы думаете, что композиция немного странная, вот почему. Тем не менее, я надеюсь, что вам понравилось это краткое введение в Beego и что вы попробуете библиотеку. Учитывая время, которое я провел с ним до сих пор, я действительно наслаждаюсь им и планирую продолжать его использовать.
Если у вас есть какие-либо вопросы, обязательно ознакомьтесь с онлайн-документацией или добавьте комментарий к записи. Не забывайте, что код также доступен в репозитории Github , так что проверьте его, возитесь и экспериментируйте.