Gin 简易实践

齐神666 · · 2394 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

以上摘自Gin简介,可以说Gin是众多Go Web框架中非常好用的微框架,简洁、易用、强大。当然,框架之间的对比没有太大的意义,仁者见仁智者见智。Gin具体使用方法参考 https://github.com/gin-gonic/gin,文档还是蛮详细的。

目录结构

由于Gin提供的只是骨架,并不像Beego一样 bee new quickstart 可以生成应用的目录结构,不过我们可以参考其方式组织目录结构,如下:
gin-learning
|-- conf
|     -- app.ini
|     -- database.ini
|-- controllers
|-- models
|-- routers
|     -- router.go
|-- static
|     -- css
|     -- js
|-- templates
|     -- layout
|     -- directory
|-- main.go

conf保存配置文件,controllers models templates对应MVC,routers为路由目录,static保存静态文件,main.go为入口文件。

Content

配置文件 app.ini

;develop or testing or product
app_mode = develop

http_port = :8080

配置文件database.ini

[develop]
redis.host = 10.64.144.3
redis.port = 6380
redis.password =
redis.max_idle_conns = 5
redis.max_open_conns = 10

mysql.host = 127.0.0.1
mysql.port = 3306
mysql.username = kimi
mysql.password = 123456
mysql.dbname = gin
mysql.max_idle_conns = 5
mysql.max_open_conns = 10

[testing]...

入口文件

package main

import (
    "fmt"
    "gin-learning/routers"
    "github.com/gin-gonic/gin"
    "github.com/go-ini/ini"
    "os"
)

func main() {
    // 加载配置
    cfg, err := ini.Load("conf/app.ini")
    if err != nil {
        fmt.Printf("Fail to read file: %v", err)
        os.Exit(1)
    }

    // 运行模式
    mode := cfg.Section("").Key("app_mode").String()

    if mode == "develop" {
        gin.SetMode(gin.DebugMode)
    } else {
        gin.SetMode(gin.ReleaseMode)
    }

    // 注册路由
    r := routers.Register()

    // 加载模板文件
    r.LoadHTMLGlob("templates/**/*")

    // 加载静态文件
    r.Static("/static", "static")

    http_port := cfg.Section("").Key("http_port").String()

    r.Run(http_port)
}

  • 使用r.LoadHTMLGlob("templates/**/*")r.LoadHTMLGlob("templates/*")加载模板文件,区别是前者加载templates下子目录中的模板文件,后者加载templates目录中的模板文件。
  • r.Static("/static", "static")。开启一个静态服务器加载static目录中的静态文件,否则无法访问localhost:8080/css/xx.css。

注册路由

package routers

import (
    "gin-learning/controllers"
    "github.com/gin-gonic/gin"
)

func Register() *gin.Engine {
    r := gin.New()
    r.Use(gin.Recovery())

    articles := new(controllers.Articles)

    v1 := r.Group("/")
    {
        v1.GET("/articles", articles.Index)
        v1.GET("/article/create", articles.Create)
        v1.GET("/article/edit/:id", articles.Edit)
        v1.GET("/article/del/:id", articles.Del)
        v1.POST("/article/store", articles.Store)
    }

    return r
}

路由中的articles controller

package controllers

import (
    "gin-learning/models"
    "github.com/gin-gonic/gin"
    "net/http"
    "strconv"
)

type Articles struct {
}

func (_ *Articles) Index(ctx *gin.Context) {
    articleModel := new(models.Articles)
    list := articleModel.List()
    ctx.HTML(http.StatusOK, "articles/index.html", gin.H{
        "list": list,
    })
}

func (_ *Articles) Create(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "articles/create-edit.html", nil)
}

func (_ *Articles) Edit(ctx *gin.Context) {
    id, err := strconv.Atoi(ctx.Param("id"))
    if err != nil {
        ctx.Redirect(http.StatusFound, "/articles")
        return
    }
    articleModel := new(models.Articles)
    article := articleModel.First(id)
    ctx.HTML(http.StatusOK, "articles/create-edit.html", gin.H{
        "article": article,
    })
}

func (_ *Articles) Store(ctx *gin.Context) {
    id, _ := strconv.Atoi(ctx.PostForm("id"))
    title := ctx.PostForm("title")
    author := ctx.PostForm("author")
    content := ctx.PostForm("content")
    articleModel := new(models.Articles)
    if id == 0 {
        articleModel.Insert(title, author, content)
    } else {
        articleModel.Edit(id, title, author, content)
    }

    ctx.Redirect(http.StatusFound, "/articles")
}

func (_ *Articles) Del(ctx *gin.Context) {
    id, err := strconv.Atoi(ctx.Param("id"))
    if err != nil {
        ctx.Redirect(http.StatusFound, "/articles")
        return
    }
    articleModel := new(models.Articles)
    articleModel.Del(id)
    ctx.Redirect(http.StatusFound, "/articles")
}

为了方便将orm redis封装放在models包中
mysql连接池:

package models

import (
    "fmt"
    "github.com/go-ini/ini"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "os"
    "time"
)

var orm *gorm.DB

func init() {
    var err error
    var cfg *ini.File
    var maxIdleConns int
    var maxOpenConns int

    // load配置
    cfg, err = ini.Load("conf/database.ini", "conf/app.ini")
    if err != nil {
        fmt.Printf("%v", err)
        os.Exit(1)
    }
    // 运行模式
    mode := cfg.Section("").Key("app_mode").String()
    // 主机
    host := cfg.Section(mode).Key("mysql.host").String()
    // 端口
    port := cfg.Section(mode).Key("mysql.port").String()
    // 用户名
    username := cfg.Section(mode).Key("mysql.username").String()
    // 密码
    password := cfg.Section(mode).Key("mysql.password").String()
    // 数据库名称
    dbname := cfg.Section(mode).Key("mysql.dbname").String()
    // 最大空闲连接数
    maxIdleConns, err = cfg.Section(mode).Key("mysql.max_idle_conns").Int()
    if err != nil {
        fmt.Printf("%v", err)
        os.Exit(1)
    }
    // 最大打开的连接数
    maxOpenConns, err = cfg.Section(mode).Key("mysql.max_open_conns").Int()
    if err != nil {
        fmt.Printf("%v", err)
        os.Exit(1)
    }

    dsn := username + ":" + password + "@tcp(" + host + ":" + port + ")/" + dbname + "?charset=utf8&parseTime=true&loc=Local"

    orm, err = gorm.Open("mysql", dsn)
    if err != nil {
        fmt.Printf("Fail to open mysql: %v", err)
        os.Exit(1)
    }

    orm.DB().SetMaxIdleConns(maxIdleConns)
    orm.DB().SetMaxOpenConns(maxOpenConns)
    orm.DB().SetConnMaxLifetime(time.Hour)
}

func GetGorm() *gorm.DB {
    return orm
}

redis连接池:

package models

import (
    "fmt"
    "github.com/go-ini/ini"
    "github.com/gomodule/redigo/redis"
    "os"
    "time"
)

var redisPool *redis.Pool

func init() {
    var err error
    var cfg *ini.File
    var maxIdleConns int
    var maxOpenConns int

    // load配置
    cfg, err = ini.Load("conf/database.ini", "conf/app.ini")
    if err != nil {
        fmt.Printf("%v", err)
        os.Exit(1)
    }
    // 运行模式
    mode := cfg.Section("").Key("app_mode").String()
    // 主机
    host := cfg.Section(mode).Key("redis.host").String()
    // 端口
    port := cfg.Section(mode).Key("redis.port").String()
    // 密码
    password := cfg.Section(mode).Key("redis.password").String()
    // 最大空闲连接数
    maxIdleConns, err = cfg.Section(mode).Key("redis.max_idle_conns").Int()
    if err != nil {
        fmt.Printf("%v", err)
        os.Exit(1)
    }
    // 最大打开的连接数
    maxOpenConns, err = cfg.Section(mode).Key("redis.max_open_conns").Int()
    if err != nil {
        fmt.Printf("%v", err)
        os.Exit(1)
    }

    redisPool = &redis.Pool{
        MaxIdle:     maxIdleConns,
        MaxActive:   maxOpenConns,
        IdleTimeout: 240 * time.Second,
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", host+":"+port)
            if err != nil {
                fmt.Printf("%v", err)
                os.Exit(1)
            }
            if password != "" {
                if _, err := c.Do("AUTH", password); err != nil {
                    c.Close()
                    fmt.Printf("%v", err)
                    os.Exit(1)
                }
            }
            return c, nil
        },
    }
}

func GetRedisPool() *redis.Pool {
    return redisPool
}

models,并没有使用GORM在应用启动时检测创建表,需提前创建表,表结构:Articles GORM 用法

package models

import (
    "time"
)

type Articles struct {
    ID      int
    Title   string
    Author  string
    Content string
    Click   int
    // 避免时区问题,时间简单使用string
    // time.ParseInLocation("2006-01-02 15:04:05",time.Now().Format("2006-01-02 15:04:05"),time.Local)
    CreateTime string
    UpdateTime string
}

// 用id查询一条记录
func (article *Articles) First(id int) *Articles {
    orm.Where(&Articles{ID: id}).First(article)
    return article
}

// 获取文章列表
func (_ *Articles) List() []Articles {
    var articles []Articles
    orm.Select("id,title,author,content,click,create_time").Order("id desc").Find(&articles)
    return articles
}

// 返回数据插入成功后的ID
func (_ *Articles) Insert(title, author, content string) int {
    createTime := time.Now().Format("2006-01-02 15:04:05")
    article := &Articles{Title: title, Author: author, Content: content, CreateTime: createTime}
    orm.Create(article)
    return article.ID
}

// 返回受影响行数
func (article *Articles) Edit(id int, title, author, content string) int64 {
    ret := article.First(id)
    // 查无结果 ret为空的Article
    if ret.ID == 0 {
        return 0
    }
    updateTime := time.Now().Format("2006-01-02 15:04:05")
    rowsAffected := orm.Model(ret).Updates(map[string]interface{}{"title": title, "author": author, "content": content, "update_time": updateTime}).RowsAffected
    return rowsAffected
}

// 返回受影响行数
func (article *Articles) Del(id int) int64 {
    ret := article.First(id)
    if ret.ID == 0 {
        return 0
    }
    rowsAffected := orm.Delete(ret).RowsAffected
    return rowsAffected
}

GORM创建表的用法

if !db.HasTable(&Articles{}) {
    if err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").CreateTable(&Articles{}).Error; err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

应用中免不了请求第三方接口,简单封装一些HTTP请求常用方法,参考(COPY)自Beego httplib

package httplib

import (
    "bytes"
    "crypto/tls"
    "encoding/json"
    "encoding/xml"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
    "time"
)

type HttpRequest struct {
    header map[string]string
    req    *http.Request
}

// 获取http client
func httpClient() *http.Client {
    trans := &http.Transport{
        // 不验证证书
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{
        Timeout:   10 * time.Second,
        Transport: trans,
    }
    return client
}

func Get(url string) (*HttpRequest, error) {
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }

    return &HttpRequest{
        req:    req,
        header: map[string]string{},
    }, nil
}

func Post(url string) (*HttpRequest, error) {
    req, err := http.NewRequest("POST", url, nil)
    if err != nil {
        return nil, err
    }

    return &HttpRequest{
        req:    req,
        header: map[string]string{},
    }, nil
}

// 向请求中添加header
func (r *HttpRequest) Header(key, value string) *HttpRequest {
    r.header[key] = value
    return r
}

// string []byte写入请求body
func (r *HttpRequest) Body(data interface{}) *HttpRequest {
    switch t := data.(type) {
    case string:
        bf := bytes.NewBufferString(t)
        r.req.Body = ioutil.NopCloser(bf)
        r.req.ContentLength = int64(len(t))
    case []byte:
        bf := bytes.NewBuffer(t)
        r.req.Body = ioutil.NopCloser(bf)
        r.req.ContentLength = int64(len(t))
    }
    return r
}

// form写入请求body
func (r *HttpRequest) FormBody(values url.Values) (*HttpRequest, error) {
    if r.req.Body == nil && values != nil {
        r.req.Body = ioutil.NopCloser(strings.NewReader(values.Encode()))
        r.req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    }
    return r, nil
}

// json写入请求body
func (r *HttpRequest) JsonBody(v interface{}) (*HttpRequest, error) {
    if r.req.Body == nil && v != nil {
        byts, err := json.Marshal(v)
        if err != nil {
            return r, err
        }
        r.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
        r.req.ContentLength = int64(len(byts))
        r.req.Header.Set("Content-Type", "application/json")
    }
    return r, nil
}

// xml写入请求body
func (r *HttpRequest) XmlBody(v interface{}) (*HttpRequest, error) {
    if r.req.Body == nil && v != nil {
        byts, err := xml.Marshal(v)
        if err != nil {
            return r, err
        }
        r.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
        r.req.ContentLength = int64(len(byts))
        r.req.Header.Set("Content-Type", "application/xml")
    }
    return r, nil
}

// 获取响应对象
func (r *HttpRequest) Response() (*http.Response, error) {
    for k, v := range r.header {
        r.req.Header.Set(k, v)
    }

    client := httpClient()

    resp, err := client.Do(r.req)
    if err != nil {
        return nil, err
    }

    return resp, nil
}

// 获取响应体(string)
func (r *HttpRequest) String() (string, error) {
    resp, err := r.Response()
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        return "", err
    }
    return string(body), nil
}

func (r *HttpRequest) ParseJson(v interface{}) error {
    body, err := r.String()
    if err != nil {
        return err
    }
    return json.Unmarshal([]byte(body), v)
}

func (r *HttpRequest) ParseXml(v interface{}) error {
    body, err := r.String()
    if err != nil {
        return err
    }
    return xml.Unmarshal([]byte(body), v)
}

    req, err := httplib.Post("https://www.so.com")
    if err != nil {
        return
    }

    resp, err := req.Header("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36").String()
    if err != nil {
        return
    }
    fmt.Println(resp)

Usage

go run main.go

访问 localhost:8080/articles


list.jpg
edit.jpg

结语

源码地址:https://github.com/kimistar/gin-learning。千里之行始于脚下,这仅仅是学习golang的开始,以此记录学习golang的经历与体会,希望日后回顾此文章时,对golang有深层次的理解,不仅仅局限于表面。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:齐神666

查看原文:Gin 简易实践

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2394 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传