суббота, 2 февраля 2019 г.

Веб-приложение на Go: структуры данных, сохранение и загрузка страницы

Давайте начнем с определения структур данных. Вики (а мы собираемся реализовать как раз вики-приложение) состоит из серии взаимосвязанных страниц, каждая из которых имеет заголовок и тело (содержимое страницы). Здесь мы определяем Page как структуру с двумя полями, представляющими название и тело.

type Page struct {
    Title string
    Body  []byte
}

Тип []byte означает «byte срез». (См. Срезы) Элемент Body является []byte, а не string, потому что это тип, ожидаемый io библиотекой, которую мы будем использовать, как вы увидите далее.

Структура Page описывает, как данные страницы будут храниться в памяти. Но как насчет постоянного хранения? Мы можем решить эту проблему, создав метод save на Page:

func (p *Page) save() error {
    filename := p.Title + ".txt"
    return ioutil.WriteFile(filename, p.Body, 0600)
}

Сигнатура этого метода гласит: "Это метод с именем save, который принимает в качестве получателя p указатель на Page. Не принимает параметров и возвращает значение типа error."

Этот метод сохраняет Body структуры Page в текстовом файле. Для простоты мы будем использовать Title в качестве имени файла.

Метод save возвращает значение error, потому что это тип возвращаемого значения WriteFile (стандартная функция библиотеки, которая записывает срез байтов в файл). Метод save возвращает значение ошибки, чтобы позволить приложению обрабатывать ее, если что-то пойдет не так при записи файла. Если все пойдет хорошо, Page.save() вернет nil (нулевое значение для указателей, интерфейсов и некоторых других типов).

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

Помимо сохранения страниц, мы также хотим загружать страницы:

func loadPage(title string) *Page {
    filename := title + ".txt"
    body, _ := ioutil.ReadFile(filename)
    return &Page{Title: title, Body: body}
}

Функция loadPage создает имя файла из параметра заголовка, читает содержимое файла в новую переменную body и возвращает указатель на Page, созданный с соотвествующим значением заголовка и тела.

Функции могут возвращать несколько значений. Стандартная функция библиотеки io.ReadFile возвращает []byte и error. В loadPage ошибка еще не обрабатывается; «пустой идентификатор» представленный символом подчеркивания (_) используется для удаления возвращаемого значения ошибки (по сути, присвоение значения ничему).

Но что произойдет, если ReadFile обнаружит ошибку? Например, файл может не существовать. Мы не должны игнорировать такие ошибки. Давайте изменим функцию для возврата *Page и error.

func loadPage(title string) (*Page, error) {
    filename := title + ".txt"
    body, err := ioutil.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    return &Page{Title: title, Body: body}, nil
}

Вызывающие эту функцию теперь могут проверять второй параметр; если это nil, то он успешно загрузил страницу. Если нет, то это будет error, которая может быть обработана вызывающей стороной.

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

func main() {
    p1 := &Page{Title: "TestPage", 
                Body: []byte("This is a sample Page.")}
    p1.save()
    p2, _ := loadPage("TestPage")
    fmt.Println(string(p2.Body))
}

После компиляции и выполнения этого кода файл с именем TestPage.txt будет создан, содержащий содержимое p1. Затем файл будет считан в структуру p2 и ее элемент Body будет выведен на экран.

Вы можете скомпилировать и запустить программу следующим образом:

$ go build wiki.go
$ ./wiki
This is a sample Page.

(Если вы используете Windows, вы должны ввести «wiki» без «./» для запуска программы.)

Взглянем на весь код, который мы уже написали.

package main

import (
  "fmt"
  "io/ioutil"
)

type Page struct {
  Title string
  Body  []byte
}

func (p *Page) save() error {
  filename := p.Title + ".txt"
  return ioutil.WriteFile(filename, p.Body, 0600)
}

func loadPage(title string) (*Page, error) {
  filename := title + ".txt"
  body, err := ioutil.ReadFile(filename)
  if err != nil {
    return nil, err
  }
  return &Page{Title: title, Body: body}, nil
}

func main() {
  p1 := &Page{Title: "TestPage", 
              Body: []byte("This is a sample Page.")}
  p1.save()
  p2, _ := loadPage("TestPage")
  fmt.Println(string(p2.Body))
}


Читайте также:


Комментариев нет:

Отправить комментарий