Go реализует систему управления фоном чемпионата мира по футболу

задняя часть база данных Go Swagger

182.png

Всем привет, меня зовут Се Вэй, я программист.

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

В этой статье используется тот же стек технологий для реализации системы управления фоном чемпионата мира по футболу 2018 года.

Основные используемые техники:

  • gin для быстрой сборки веб-сервера
  • gin-swagger автоматически создает документацию по API
  • горм управляет базой данных
  • Fresh реализует мониторинг веб-сервера
  • viper реализует чтение конфигурации пользователя
  • база данных с помощью postgre
  • goquery реализует синтаксический анализ веб-страниц

Основная идея:

первый шаг:

Поскольку это система фонового управления чемпионата мира по футболу 2018 года, данные этого чемпионата мира должны быть необходимы, так откуда берутся данные?

целевой сайтЧМ-2018 в России.

Теперь, когда известен целевой веб-сайт, что делать дальше?

Поисковый робот.

  • matches
  • teams
  • groups
  • players
  • statistics
  • awards
  • classic

Основная необходимая информация такова.

Шаг 2:

Анализировать исходный код веб-страницы. Поисковый робот. Лучшая библиотека для веб-парсинга в Go — это goquery.

Анализируйте необходимые целевые данные один за другим.

третий шаг:

Где хранятся данные?

Конечно, вы должны следовать своим пожеланиям, сохранить текст или сохранить базу данных. Будут ли общие приложения корпоративного уровня храниться локально?

Потом еще веду базу честно. Выбор БД, по себе, я выбираю postgre.

Поскольку используется база данных, необходимо управлять базой данных. Если вы хотите, чтобы код был полон операторов SQL, вы можете написать операторы SQL. Конечно, я думаю, что лучший метод обслуживания — использовать ORM. Технология Orm используется в go.Библиотека gorm.

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

четвертый шаг:

Теперь, когда данные доступны, как внедрить систему фонового управления?

Следует использовать restful API для реализации добавления, удаления и модификации ресурсов.

рекомендуется джин. Конечно, это нормально, если вам нравятся другие фреймворки или даже если вам нравятся нативные.

Тем не менее, я думаю, что джин быстр, легок и имеет низкую стоимость обучения. Вы можете легко реализовать веб-сервер.

Расширения для джина могут быть достигнуты с помощью промежуточного программного обеспечения.

пятый шаг:

Если данные не хотят, чтобы кто-либо имел к ним доступ по своему желанию, как их можно ограничить? Эффект соответствующего внешнего интерфейса заключается в том, что вам нужно войти в систему для доступа к ресурсам, так как же реализован внутренний интерфейс?

jwt: веб-токен json использует json для передачи данных, чтобы определить, вошел ли пользователь в систему.

Конкретная практика:

  • Войти, получить jwt
  • При доступе jwt нужно монтировать в шапке при запросе

Далее описывается только основной код:

1. Структура проекта

├── configs
├── docs
│   └── swagger
├── domain
├── infra
│   ├── adapter
│   ├── config
│   ├── crypt
│   ├── download
│   ├── init
│   └── model
├── ui
│   └── api-server
│       ├── admins
│       ├── awards
│       ├── classic
│       ├── coaches
│       ├── controller
│       ├── groups
│       ├── matches
│       ├── players
│       ├── statistics
│       └── teams
└── vendor

  • configs Информация о конфигурации, в основном информация о конфигурации данных, адрес хоста, порт, имя пользователя и пароль и т. д.
  • docs документация по API, автоматически созданная gin-swagger, а не созданная вручную
  • уровень домена домена, в основном анализ, сканирование и хранение информации о веб-странице
  • Уровень инфраструктуры инфраструктуры, в основном обработка строк, алгоритм шифрования, получение исходного кода веб-страницы, определение модели базы данных
  • Пользовательский уровень визуализации пользовательского интерфейса, в основном работа API, созданного gin, включая комментарии к маршрутизации, ответам и документации swagger.
  • сторонняя библиотека поставщика

2. Получите исходный код веб-страницы

Этого можно добиться, используя встроенный net/http

func Downloader(url string) (*goquery.Document, error) {
	request, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, ErrDownloader
	}

	request.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36")
	client := http.DefaultClient

	response, err := client.Do(request)
	if err != nil {
		return nil, ErrDownloader
	}

	defer response.Body.Close()
	return goquery.NewDocumentFromReader(response.Body)
}


Если вы столкнулись с динамической загрузкой данных, не хотите анализировать веб-страницу и не требуете высокой скорости, вы можете использовать селен

func DownloaderBySelenium(url string) (string, error) {
	caps := selenium.Capabilities{
		"browserName": "chrome",
	}

	imageCaps := map[string]interface{}{
		"profile.managed_default_content_settings.images": 2,
	}
	chromeCaps := chrome.Capabilities{
		Prefs: imageCaps,
		Path:  "",
		Args: []string{
			"--headless",
			"--no-sandbox",
			"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7",
		},
	}
	caps.AddChrome(chromeCaps)

	service, err := selenium.NewChromeDriverService(
		config.ChromeDriverPath, 9515,
	)
	defer service.Stop()

	if err != nil {
		fmt.Println(ErrSeleniumService)
		return "", ErrSeleniumService
	}
	webDriver, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", 9515))

	if err != nil {
		fmt.Println(ErrWebDriver)
		return "", ErrWebDriver
	}

	err = webDriver.Get(url)

	if err != nil {
		fmt.Println(ErrWebDriverGet)
		return "", ErrWebDriverGet
	}
	return webDriver.PageSource()

}

3. Определение таблицы базы данных и определение ответного сообщения

Определение таблицы базы данных манипулирует определением модели горма, тип, непустое значение, значение по умолчанию и т. д. реализуются с помощью тегов.


// awards 表定义

type Award struct {
	ID        uint   `gorm:"primary_key;column:id"`
	AwardName string `gorm:"type:varchar(64);not null;column:award_name"`
	URL       string `gorm:"type:varchar(128);not null;column:url"`
	Info      string `gorm:"type:varchar(128);not null;column:info"`
}


// API 响应信息定义
type AwardSerializer struct {
	ID        uint   `json:"id"`
	AwardName string `json:"award_name"`
	Info      string `json:"info"`
	URL       string `json:"url"`
}

func (a *Award) Serializer() AwardSerializer {
	return AwardSerializer{
		ID:        a.ID,
		AwardName: a.AwardName,
		Info:      a.Info,
		URL:       a.URL,
	}
}


4. Сканирование и хранение информации


func Awards(doc *goquery.Document) error {
	var err error
	count := 0
	urlList := make([]string, 0, 0)
	urlList = append(urlList, "/worldcup/awards/golden-boot/")
	urlList = append(urlList, "/worldcup/awards/golden-glove/")
	urlList = append(urlList, "/worldcup/awards/golden-ball/")
	for _, url := range urlList {
		completeAwardURl := config.RootURL + url
		doc, err := download.Downloader(completeAwardURl)
		if err != nil {
			err = ErrorAwardDownloader
			break
		}
		// db save
		awards := callBack(completeAwardURl, doc)
		fmt.Println(completeAwardURl)
		for _, award := range awards {
			fmt.Println(award)
			count++
			// push data into db
			initiator.POSTGRES.Save(&award)

		}
	}
	fmt.Println(count)

	return err
}

func callBack(url string, doc *goquery.Document) []model.Award {

	allAwardInfo := make([]model.Award, 0, 0)

	awardName := doc.Find("h1").Eq(2).Text()

	doc.Find("div p").Each(func(i int, selection *goquery.Selection) {

		if i > 6 {

			awardInfo := selection.Text()
			if strings.HasPrefix(awardInfo, "*") {
				return
			}
			oneAward := model.Award{}
			oneAward.URL = url
			oneAward.AwardName = awardName
			oneAward.Info = awardInfo
			allAwardInfo = append(allAwardInfo, oneAward)
		}

	})
	return allAwardInfo
}

5. Создавайте спокойные API

func awardsRegistry(r *gin.RouterGroup) {
	r.GET("/awards", awards.ShowAllAwardHandler)
	r.GET("/awards/:awardID", awards.ShowAwardHandler)
}
package awards

import (
	"FIFA-World-Cup/infra/init"
	"FIFA-World-Cup/infra/model"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"net/http"
)

var (
	ErrorAwardParam = errors.New("award param is not correct")
)

// ShowAwardHandler will list Awards
// @Summary List Awards
// @Accept json
// @Tags Awards
// @Security Bearer
// @Produce  json
// @Param awardID path string true "award id"
// @Resource Awards
// @Router /awards/{id} [get]
// @Success 200 {object} model.AwardSerializer
func ShowAwardHandler(c *gin.Context) {

	id := c.Param("awardID")

	var award model.Award
	if dbError := initiator.POSTGRES.Where("info LIKE ?", fmt.Sprintf("%%%s%%", id)).First(&award).Error; dbError != nil {
		c.AbortWithError(400, dbError)
		return
	}
	c.JSON(http.StatusOK, award.Serializer())

}

type ListAwardParam struct {
	Search string `form:"search"`
	Return string `form:"return"`
}

// ShowAllAwardHandler will list Awards
// @Summary List Awards
// @Accept json
// @Tags Awards
// @Security Bearer
// @Produce  json
// @Param search path string false "award_name"
// @param return path string false "return = all_list"
// @Resource Awards
// @Router /awards [get]
// @Success 200 {array} model.AwardSerializer
func ShowAllAwardHandler(c *gin.Context) {

	var param ListAwardParam

	if err := c.ShouldBindQuery(&param); err != nil {
		c.AbortWithError(400, ErrorAwardParam)
		return
	}

	var awards []model.Award

	if param.Search != "" {
		if dbError := initiator.POSTGRES.Where("award_name LIKE ?", fmt.Sprintf("%%%s%%", param.Search)).Find(&awards).Error; dbError != nil {
			c.AbortWithError(400, dbError)
			return
		}
	}

	if param.Return == "all_list" {
		if dbError := initiator.POSTGRES.Find(&awards).Error; dbError != nil {
			c.AbortWithError(400, dbError)
			return
		}
	}

	var result = make([]model.AwardSerializer, len(awards))

	for index, award := range awards {
		result[index] = award.Serializer()
	}
	c.JSON(http.StatusOK, result)
}

Комментарии над конкретными функциями ответа необходимы для создания документации по автоматизации.

6. сертификация JWT


package controller

import (
	"FIFA-World-Cup/infra/init"
	"FIFA-World-Cup/infra/model"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"strings"
)

var (
	ErrorAuth      = errors.New("please add token: 'Authorization: Bearer xxxx'")
	ErrorAuthWrong = errors.New("token is not right,example: Bearer xxxx")
)

func AuthRequired() gin.HandlerFunc {
	return func(c *gin.Context) {
		if vendor := c.Request.Header.Get("X-Requested-With"); vendor != "" {
			c.Set("X-Requested-With", vendor)
		}

		header := c.Request.Header.Get("Authorization")
		if header == "" {
			c.AbortWithError(400, ErrorAuth)
			return
		}

		authHeader := strings.Split(header, " ")

		if len(authHeader) != 2 {
			c.AbortWithError(400, ErrorAuthWrong)
			return
		}

		token := authHeader[1]

		var admin model.Admin
		fmt.Println(token)
		if dbError := initiator.POSTGRES.Where("auth_token = ?", token).First(&admin).Error; dbError != nil {
			c.AbortWithError(400, dbError)
		} else {
			c.Set("current_admin", admin)
			c.Next()
		}
	}
}

Что это обозначает?

  1. Пользователю необходимо зарегистрироваться или войти в систему, и соответствующий auth_token генерируется в фоновом режиме.

select * from admins;

 id |          created_at           |          updated_at           | deleted_at |      name      |                auth_token                |                     encrypted_password'                      |    phone     | state
----+-------------------------------+-------------------------------+------------+----------------+------------------------------------------+--------------------------------------------------------------+--------------+-------
2	2018-07-20 16:10:11.099085	2018-07-20 16:10:11.099085		FIFA-World-Cup	c6d81d35bc598ddedf3e0b798cd5d463139ab6c9	$2a$04$wKHmdGixgrISJM7wV3rKn.6HX5Bjg8.JbelGYl/443ber3aXI/K8K	110120119	admin


Каждый пользователь будет генерировать соответствующий auth_token

Чтобы получить доступ к ресурсу HEADER, вам необходимо принести этот токен для достижения цели аутентификации.

7. Эффекты

Документация Swagger-API

Swagger-API.png

список API

PostMan-API.png

Объяснение видео версии

BiliBili

8. Исходный код

FIFA-World-Cup-2018


Текст окончен, я Се Вэй, до свидания, спасибо.