Go Gin Series II: создание API блогов (1)

Go

Привет всем, я Jianyu, это адрес этого проекта:GitHub.com/Vicious Genetics/go-…, Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь общаться и общаться.

считать

Прежде всего, прежде чем начать первоначальный проект, каждый должен подумать об этом.

  • Программа конфигурации текста написана в коде, хорошо?

  • Код ошибки API жестко запрограммирован в программе?

  • db дескриптор достается кому угодноOpen, нет единого управления, понятно?

  • Чтобы получить общедоступные параметры, такие как пейджинг, каждый может написать свою логику, хорошо?

Очевидно, что в более формальных проектах ответы на эти вопросыНе можетЧтобы решить эти проблемы, мы выбираем библиотеку для чтения и записи файла конфигурации, в настоящее время больше пожараviper, Если вам интересно, вы можете кратко понять это в будущем.Если вам не интересно, подождите, пока вы не соприкоснетесь с этим позже.

Но этот сериал выбираетgo-ini/ini,этокитайский документ. Вы должны просто прочитать его документацию, а затем завершить следующее содержание.

Цель этой статьи

  • Напишите простой пакет кода ошибки API.
  • Заполните демонстрационный пример.
  • Объясните точки знаний, связанные с демонстрацией.

Представьте и инициализируйте проект

Инициализировать каталог проекта

В предыдущей главе мы инициализировалиgo-gin-exampleProject, то нам нужно продолжить добавлять следующую структуру каталогов:

go-gin-example/
├── conf
├── middleware
├── models
├── pkg
├── routers
└── runtime
  • Conf: используется для хранения файлов конфигурации
  • промежуточное ПО: промежуточное ПО приложения
  • модели: модели баз данных приложений
  • pkg: сторонний пакет
  • обработка логики маршрутизации маршрутизаторов
  • время выполнения: данные времени выполнения приложения

Добавить модули GO заменять

Открытымgo.modДокумент, новыйreplaceЭлементы конфигурации следующие:

module github.com/EDDYCJY/go-gin-example

go 1.13

require (...)

replace (
		github.com/EDDYCJY/go-gin-example/pkg/setting => ~/go-application/go-gin-example/pkg/setting
		github.com/EDDYCJY/go-gin-example/conf    	  => ~/go-application/go-gin-example/pkg/conf
		github.com/EDDYCJY/go-gin-example/middleware  => ~/go-application/go-gin-example/middleware
		github.com/EDDYCJY/go-gin-example/models 	  => ~/go-application/go-gin-example/models
		github.com/EDDYCJY/go-gin-example/routers 	  => ~/go-application/go-gin-example/routers
)

Может быть, вы не понимаете, почему вы пришли сюда, чтобы добавитьreplaceЭлементы конфигурации, сначала вы должны увидеть, что мы используем полный путь ссылки внешнего модуля (github.com/EDDYCJY/go-gin-example/xxx), и этот модуль не запихнут на удалёнку, нет возможности его скачать, поэтому нужно использоватьreplaceУкажите его для чтения пути локального модуля, чтобы можно было решить проблему чтения локального модуля.

Примечание: при каждом новом последующем локальном каталоге приложения, вам необходимо по инициативе добавить файл go.mod replace (напоминать не буду), если пропустили, то будет ошибка компиляции, нельзя найти модуль.

Исходная база данных проекта

новыйblogбаза данных, закодированная какutf8_general_ci,существуетblogВ базе данных создайте следующую таблицу

1. Таблица этикеток

CREATE TABLE `blog_tag` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT '' COMMENT '标签名称',
  `created_on` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `created_by` varchar(100) DEFAULT '' COMMENT '创建人',
  `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
  `modified_by` varchar(100) DEFAULT '' COMMENT '修改人',
  `deleted_on` int(10) unsigned DEFAULT '0',
  `state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用、1为启用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章标签管理';

2. Таблица статей

CREATE TABLE `blog_article` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `tag_id` int(10) unsigned DEFAULT '0' COMMENT '标签ID',
  `title` varchar(100) DEFAULT '' COMMENT '文章标题',
  `desc` varchar(255) DEFAULT '' COMMENT '简述',
  `content` text,
  `created_on` int(11) DEFAULT NULL,
  `created_by` varchar(100) DEFAULT '' COMMENT '创建人',
  `modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
  `modified_by` varchar(255) DEFAULT '' COMMENT '修改人',
  `deleted_on` int(10) unsigned DEFAULT '0',
  `state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用1为启用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章管理';

3. Форма сертификации

CREATE TABLE `blog_auth` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT '' COMMENT '账号',
  `password` varchar(50) DEFAULT '' COMMENT '密码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `blog`.`blog_auth` (`id`, `username`, `password`) VALUES (null, 'test', 'test123456');

Написать пакет конфигурации проекта

существуетgo-gin-exampleВ каталоге приложения потянитеgo-ini/iniПакеты зависимостей следующие:

$ go get -u github.com/go-ini/ini
go: finding github.com/go-ini/ini v1.48.0
go: downloading github.com/go-ini/ini v1.48.0
go: extracting github.com/go-ini/ini v1.48.0

Далее нам нужно написать базовый файл конфигурации приложения, вgo-gin-exampleизconfновый каталогapp.iniфайл, напишите содержимое:

#debug or release
RUN_MODE = debug

[app]
PAGE_SIZE = 10
JWT_SECRET = 23347$040412

[server]
HTTP_PORT = 8000
READ_TIMEOUT = 60
WRITE_TIMEOUT = 60

[database]
TYPE = mysql
USER = 数据库账号
PASSWORD = 数据库密码
#127.0.0.1:3306
HOST = 数据库IP:数据库端口号
NAME = blog
TABLE_PREFIX = blog_

построить конфигурацию вызоваsettingмодуль, вgo-gin-exampleизpkgновый каталогsettingКаталог (обратите внимание на новую конфигурацию замены), новыйsetting.goфайл, напишите содержимое:

package setting

import (
	"log"
	"time"

	"github.com/go-ini/ini"
)

var (
	Cfg *ini.File

	RunMode string
	
	HTTPPort int
	ReadTimeout time.Duration
	WriteTimeout time.Duration

	PageSize int
	JwtSecret string
)

func init() {
	var err error
	Cfg, err = ini.Load("conf/app.ini")
	if err != nil {
		log.Fatalf("Fail to parse 'conf/app.ini': %v", err)
	}

	LoadBase()
	LoadServer()
	LoadApp()
}

func LoadBase() {
	RunMode = Cfg.Section("").Key("RUN_MODE").MustString("debug")
}

func LoadServer() {
	sec, err := Cfg.GetSection("server")
	if err != nil {
		log.Fatalf("Fail to get section 'server': %v", err)
	}

	HTTPPort = sec.Key("HTTP_PORT").MustInt(8000)
	ReadTimeout = time.Duration(sec.Key("READ_TIMEOUT").MustInt(60)) * time.Second
	WriteTimeout =  time.Duration(sec.Key("WRITE_TIMEOUT").MustInt(60)) * time.Second	
}

func LoadApp() {
	sec, err := Cfg.GetSection("app")
	if err != nil {
		log.Fatalf("Fail to get section 'app': %v", err)
	}

	JwtSecret = sec.Key("JWT_SECRET").MustString("!@)*#)!@U#@*!@!)")
	PageSize = sec.Key("PAGE_SIZE").MustInt(10)
}

Текущая структура каталогов:

go-gin-example
├── conf
│   └── app.ini
├── go.mod
├── go.sum
├── middleware
├── models
├── pkg
│   └── setting.go
├── routers
└── runtime

Написать пакет кода ошибки API

код ошибкиeмодуль, вgo-gin-exampleизpkgновый каталогeКаталог (обратите внимание на новую конфигурацию замены), создайте новыйcode.goиmsg.goфайл, напишите содержимое:

1. code.go:

package e

const (
	SUCCESS = 200
	ERROR = 500
	INVALID_PARAMS = 400

	ERROR_EXIST_TAG = 10001
	ERROR_NOT_EXIST_TAG = 10002
	ERROR_NOT_EXIST_ARTICLE = 10003

	ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
	ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002
	ERROR_AUTH_TOKEN = 20003
	ERROR_AUTH = 20004
)

2. msg.go:

package e

var MsgFlags = map[int]string {
	SUCCESS : "ok",
	ERROR : "fail",
	INVALID_PARAMS : "请求参数错误",
	ERROR_EXIST_TAG : "已存在该标签名称",
	ERROR_NOT_EXIST_TAG : "该标签不存在",
	ERROR_NOT_EXIST_ARTICLE : "该文章不存在",
	ERROR_AUTH_CHECK_TOKEN_FAIL : "Token鉴权失败",
	ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token已超时",
	ERROR_AUTH_TOKEN : "Token生成失败",
	ERROR_AUTH : "Token错误",
}

func GetMsg(code int) string {
	msg, ok := MsgFlags[code]
	if ok {
		return msg
	}

	return MsgFlags[ERROR]
}

набор инструментов для письма

существуетgo-gin-exampleизpkgновый каталогutilкаталог (обратите внимание на новую конфигурацию замены) и потянитеcomПакеты зависимостей следующие:

go get -u github.com/unknwon/com

Как получить номер страницы пагинации

существуетutilновый каталогpagination.go, напишите содержание:

package util

import (
	"github.com/gin-gonic/gin"
	"github.com/unknwon/com"

	"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func GetPage(c *gin.Context) int {
	result := 0
	page, _ := com.StrTo(c.Query("page")).Int()
    if page > 0 {
        result = (page - 1) * setting.PageSize
    }

    return result
}

написать модели инициализации

ВытащитьgormПакеты зависимостей следующие:

go get -u github.com/jinzhu/gorm

ВытащитьmysqlЗависимости драйвера следующие:

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

После завершения, вgo-gin-exampleизmodelsновый каталогmodels.go, заmodelsинициализация с использованием

package models

import (
	"log"
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"

	"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

var db *gorm.DB

type Model struct {
	ID int `gorm:"primary_key" json:"id"`
	CreatedOn int `json:"created_on"`
	ModifiedOn int `json:"modified_on"`
}

func init() {
	var (
		err error
		dbType, dbName, user, password, host, tablePrefix string
	)

	sec, err := setting.Cfg.GetSection("database")
	if err != nil {
		log.Fatal(2, "Fail to get section 'database': %v", err)
	}

	dbType = sec.Key("TYPE").String()
	dbName = sec.Key("NAME").String()
	user = sec.Key("USER").String()
	password = sec.Key("PASSWORD").String()
	host = sec.Key("HOST").String()
	tablePrefix = sec.Key("TABLE_PREFIX").String()

	db, err = gorm.Open(dbType, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", 
		user, 
		password, 
		host, 
		dbName))

	if err != nil {
		log.Println(err)
	}

	gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
	    return tablePrefix + defaultTableName;
	}

	db.SingularTable(true)
	db.LogMode(true)
	db.DB().SetMaxIdleConns(10)
	db.DB().SetMaxOpenConns(100)
}

func CloseDB() {
	defer db.Close()
}

Запись файлов запуска и маршрутизации проекта

Основные приготовления сделаны, приступаем к написанию демо!

Написать демо

существуетgo-gin-exampleПод установленнымmain.goкак файл запуска (т.е.mainпакет), мы сначала пишемDemo, чтобы всем было понятно, напишите содержимое файла:

package main

import (
    "fmt"
	  "net/http"

    "github.com/gin-gonic/gin"

	  "github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func main() {
	router := gin.Default()
    router.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "test",
		})
	})

	s := &http.Server{
		Addr:           fmt.Sprintf(":%d", setting.HTTPPort),
		Handler:        router,
		ReadTimeout:    setting.ReadTimeout,
		WriteTimeout:   setting.WriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	s.ListenAndServe()
}

воплощать в жизньgo run main.go, посмотрите, отображается ли в командной строке

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /test                     --> main.main.func1 (3 handlers)

Выполнить этот блокcurl 127.0.0.1:8000/testПроверьте, чтобы вернуться{"message":"test"}.

Точка знаний

Итак, давайте расширим очки знаний, задействованные в Demo!

стандартная библиотека
  • fmt: Реализует форматированный ввод-вывод, аналогичный printf и scanf языка C. Действие форматирования («глагол») получено из языка C, но проще
  • net/http: Обеспечивает реализацию HTTP-клиента и сервера.
Gin
  • gin.Default(): возвращает Джинtype Engine struct{...}, который содержитRouterGroup, что эквивалентно созданию маршрутаHandlers, вы можете привязывать различные правила и функции маршрутизации, промежуточное ПО и т. д.
  • router.GET(...){...}: создание различных HTTP-методов для привязки.HandlersОн также поддерживает распространенные методы Restful, такие как POST, PUT, DELETE, PATCH, OPTIONS, HEAD и т. д.
  • gin.H{...}: только одинmap[string]interface{}
  • gin.Context:Contextдаginконтекст в , что позволяет нам передавать переменные между промежуточным ПО, управлять потоками, проверять запросы JSON, отвечать на запросы JSON и т. д. вginсодержит многоContextметоды, такие как наши обычно используемыеDefaultQuery,Query,DefaultPostForm,PostFormи т.д
&http.Server и ListenAndServe?

1. http.Сервер:

type Server struct {
    Addr    string
    Handler Handler
    TLSConfig *tls.Config
    ReadTimeout time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
}
  • Addr: TCP-адрес прослушивания, формат:8000
  • Обработчик: HTTP-дескриптор, по сутиServeHTTP, чтобы обработчик ответил на HTTP-запрос
  • TLSConfig: конфигурация безопасности транспортного уровня (TLS)
  • ReadTimeout: максимальное время, разрешенное для чтения
  • ReadHeaderTimeout: максимальное время, разрешенное для чтения заголовков запроса.
  • WriteTimeout: максимальное время, разрешенное для записи
  • IdleTimeout: максимальное время ожидания
  • MaxHeaderBytes: максимальное количество байтов в заголовке запроса.
  • ConnState: укажите необязательную функцию обратного вызова, которая будет вызываться при изменении подключения клиента.
  • ErrorLog: указывает необязательный регистратор для получения информации о неожиданном поведении программы и базовых системных ошибках;nilПо умолчанию делается стандартным логгером пакета логов (то есть вывод в консоль)

2. Слушайте и обслуживайте:

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

Запустите службу прослушивания, прослушивая сетевой адрес TCP, адрес и вызывая приложение для обработки запросов при подключении.

Мы видим в исходном кодеAddrэто позвать нас&http.ServerПараметры задаются в , поэтому мы используем&, мы хотим изменить значение параметра, потому что мыListenAndServeи некоторые другие методы должны быть использованы&http.Serverпараметры, они влияют друг на друга.

3.http.ListenAndServeиСерийныйизr.Run()Есть ли разница?

Посмотримr.RunРеализация:

func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

Анализируя исходный код, мы знаемПо сути никакой разницы, а также узнал, что стартапginЗдесь выводится отладочная информация прослушивания.

4. Почему в ДемоWARNING?

Сначала мы можем видетьDefault()реализация

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

Вы можете видеть, что по умолчанию экземпляр движка промежуточного программного обеспечения журнала и восстановления подключен. и вызывается в началеdebugPrintWARNINGDefault(), и его реализация заключается в выводе строки журнала

func debugPrintWARNINGDefault() {
	debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`)
}

и другойRunning in "debug" mode. Switch to "release" mode in production., является причиной режима работы, понять не сложно, он уже находится под управлением конфигурационного файла :-), и эксплуатационный и обслуживающий персонал может модифицировать его конфигурацию в любой момент.

5. Демоrouter.GETи другие правила маршрутизации не могут быть записаны вmainв сумке?

мы обнаруживаемrouter.GETи другие правила маршрутизации, которые прописаны в демоmainВ упаковке чувствуется очень странно, давайте избавимся от этой части логики!

существуетgo-gin-exampleВнизroutersновый каталогrouter.goфайл, напишите содержимое:

package routers

import (
    "github.com/gin-gonic/gin"
    
    "github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func InitRouter() *gin.Engine {
    r := gin.New()

    r.Use(gin.Logger())

    r.Use(gin.Recovery())

    gin.SetMode(setting.RunMode)

    r.GET("/test", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "test",
        })
    })

    return r
}

Исправлятьmain.goсодержимое файла:

package main

import (
	"fmt"
	"net/http"

	"github.com/EDDYCJY/go-gin-example/routers"
	"github.com/EDDYCJY/go-gin-example/pkg/setting"
)

func main() {
	router := routers.InitRouter()

	s := &http.Server{
		Addr:           fmt.Sprintf(":%d", setting.HTTPPort),
		Handler:        router,
		ReadTimeout:    setting.ReadTimeout,
		WriteTimeout:   setting.WriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	s.ListenAndServe()
}

Текущая структура каталогов:

go-gin-example/
├── conf
│   └── app.ini
├── main.go
├── middleware
├── models
│   └── models.go
├── pkg
│   ├── e
│   │   ├── code.go
│   │   └── msg.go
│   ├── setting
│   │   └── setting.go
│   └── util
│       └── pagination.go
├── routers
│   └── router.go
├── runtime

Перезапустите службу, выполнитеcurl 127.0.0.1:8000/testПроверьте, правильно ли он возвращается.

В следующем разделе мы будем использовать нашу демонстрацию в качестве отправной точки для модификации и начала кодирования!

Ссылаться на

Пример кода для этой серии

?

Если у вас есть какие-либо вопросы или ошибки, добро пожаловать вissuesЗадавайте вопросы или вносите исправления, если вам нравится или вам помогают, добро пожаловатьStar, является своеобразным поощрением и продвижением автора.

мой блог

Изучите го с жареной рыбой:GitHub.com/Vicious Genetics/Нет...

мой публичный аккаунт

image

Категории