Gin 简易实践

栏目: Go · 发布时间: 6年前

内容简介:以上摘自Gin简介,可以说Gin是众多Go Web框架中非常好用的微框架,简洁、易用、强大。当然,框架之间的对比没有太大的意义,仁者见仁智者见智。Gin具体使用方法参考由于Gin提供的只是骨架,并不像gin-learning

前言

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

Gin 简易实践

list.jpg

Gin 简易实践

edit.jpg

结语

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入浅出Rust

深入浅出Rust

范长春 / 机械工业出版社 / 2018-8-21 / 89.00元

本书详细描述了Rust语言的基本语法,穿插讲解一部分高级使用技巧,并以更容易理解的方式解释其背后的设计思想。全书总共分五个部分。 第一部分介绍Rust基本语法,因为对任何程序设计语言来说,语法都是基础,学习这部分是理解其他部分的前提。 第二部分介绍属于Rust独一无二的内存管理方式。它设计了一组全新的机制,既保证了安全性,又保持了强大的内存布局控制力,而且没有额外性能损失。这部分是本书......一起来看看 《深入浅出Rust》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码