Всем привет, меня зовут Се Вэй, я программист.
Воспользуйтесь выходными, чтобы обновить первый выпуск, последний упомянутый выпуск.Как быстро ознакомиться с проектом, в конце статьи, лучший способ — позаимствовать тот же стек технологий для повторной реализации проекта.
В этой статье используется тот же стек технологий для реализации системы управления фоном чемпионата мира по футболу 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(¶m); 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()
}
}
}
Что это обозначает?
- Пользователю необходимо зарегистрироваться или войти в систему, и соответствующий 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
список API
Объяснение видео версии
8. Исходный код
Текст окончен, я Се Вэй, до свидания, спасибо.